CharacterManaJ/0000755000175000017500000000000012560206305013570 5ustar paulliupaulliuCharacterManaJ/icon.ico0000644000175000017500000001246612560206305015225 0ustar paulliupaulliu  & h( @ tyx}~+#+x[xgb{YyyVx[~dgk}]|<1;||@B@oOoס˖ϜשΜќw2,2Y^{|.*.p׼տӳM7MTX}|~,#,gv¬<6<9-9@4@?7?UOUu^B^dddAA[v}|}%%pRps0)0C0CfLfgNgQ" !!!$$"''%**)110&&' ..=13Iopos``@@U88Kci{~x~rxsyX\m?@R33D""-"-1?>BT>BUHK^QUh92>nhn|\|e,",K?K'"'ONh}{jNjmSm""00065?mr;:85.4F4E;,9ooo_brx'(7"6:C7;E24=#%'..0734</0BRRlxxSRlYXtUUqTSny~WWr`bWVqTUp{{WWrhk~~XXtXXszXXs\\{ooYYuegvvXYuVUoqvUUouuccZZvgjhhZZwVUn}fiUUoYYu[Zvgk\\z[\yWVpsy`aZZw}}ZZw]]ybfVVr\\zZZubgYYvkkCharacterManaJ/.fbprefs0000644000175000017500000001610412560206305015222 0ustar paulliupaulliu#FindBugs User Preferences #Sun Feb 20 00:21:29 JST 2011 detectorAppendingToAnObjectOutputStream=AppendingToAnObjectOutputStream|true detectorBadAppletConstructor=BadAppletConstructor|false detectorBadResultSetAccess=BadResultSetAccess|true detectorBadSyntaxForRegularExpression=BadSyntaxForRegularExpression|true detectorBadUseOfReturnValue=BadUseOfReturnValue|true detectorBadlyOverriddenAdapter=BadlyOverriddenAdapter|true detectorBooleanReturnNull=BooleanReturnNull|true detectorCallToUnsupportedMethod=CallToUnsupportedMethod|false detectorCheckImmutableAnnotation=CheckImmutableAnnotation|true detectorCheckTypeQualifiers=CheckTypeQualifiers|true detectorCloneIdiom=CloneIdiom|true detectorComparatorIdiom=ComparatorIdiom|true detectorConfusedInheritance=ConfusedInheritance|true detectorConfusionBetweenInheritedAndOuterMethod=ConfusionBetweenInheritedAndOuterMethod|true detectorCrossSiteScripting=CrossSiteScripting|true detectorDoInsideDoPrivileged=DoInsideDoPrivileged|true detectorDontCatchIllegalMonitorStateException=DontCatchIllegalMonitorStateException|true detectorDontIgnoreResultOfPutIfAbsent=DontIgnoreResultOfPutIfAbsent|true detectorDontUseEnum=DontUseEnum|true detectorDroppedException=DroppedException|true detectorDumbMethodInvocations=DumbMethodInvocations|true detectorDumbMethods=DumbMethods|true detectorDuplicateBranches=DuplicateBranches|true detectorEmptyZipFileEntry=EmptyZipFileEntry|true detectorEqualsOperandShouldHaveClassCompatibleWithThis=EqualsOperandShouldHaveClassCompatibleWithThis|true detectorFinalizerNullsFields=FinalizerNullsFields|true detectorFindBadCast2=FindBadCast2|true detectorFindBadForLoop=FindBadForLoop|true detectorFindCircularDependencies=FindCircularDependencies|false detectorFindDeadLocalStores=FindDeadLocalStores|true detectorFindDoubleCheck=FindDoubleCheck|true detectorFindEmptySynchronizedBlock=FindEmptySynchronizedBlock|true detectorFindFieldSelfAssignment=FindFieldSelfAssignment|true detectorFindFinalizeInvocations=FindFinalizeInvocations|true detectorFindFloatEquality=FindFloatEquality|true detectorFindHEmismatch=FindHEmismatch|true detectorFindInconsistentSync2=FindInconsistentSync2|true detectorFindJSR166LockMonitorenter=FindJSR166LockMonitorenter|true detectorFindLocalSelfAssignment2=FindLocalSelfAssignment2|true detectorFindMaskedFields=FindMaskedFields|true detectorFindMismatchedWaitOrNotify=FindMismatchedWaitOrNotify|true detectorFindNakedNotify=FindNakedNotify|true detectorFindNonSerializableStoreIntoSession=FindNonSerializableStoreIntoSession|true detectorFindNonSerializableValuePassedToWriteObject=FindNonSerializableValuePassedToWriteObject|true detectorFindNonShortCircuit=FindNonShortCircuit|true detectorFindNullDeref=FindNullDeref|true detectorFindNullDerefsInvolvingNonShortCircuitEvaluation=FindNullDerefsInvolvingNonShortCircuitEvaluation|true detectorFindOpenStream=FindOpenStream|true detectorFindPuzzlers=FindPuzzlers|true detectorFindRefComparison=FindRefComparison|true detectorFindReturnRef=FindReturnRef|true detectorFindRunInvocations=FindRunInvocations|true detectorFindSelfComparison=FindSelfComparison|true detectorFindSelfComparison2=FindSelfComparison2|true detectorFindSleepWithLockHeld=FindSleepWithLockHeld|true detectorFindSpinLoop=FindSpinLoop|true detectorFindSqlInjection=FindSqlInjection|true detectorFindTwoLockWait=FindTwoLockWait|true detectorFindUncalledPrivateMethods=FindUncalledPrivateMethods|true detectorFindUnconditionalWait=FindUnconditionalWait|true detectorFindUninitializedGet=FindUninitializedGet|true detectorFindUnrelatedTypesInGenericContainer=FindUnrelatedTypesInGenericContainer|true detectorFindUnreleasedLock=FindUnreleasedLock|true detectorFindUnsatisfiedObligation=FindUnsatisfiedObligation|true detectorFindUnsyncGet=FindUnsyncGet|true detectorFindUselessControlFlow=FindUselessControlFlow|true detectorFormatStringChecker=FormatStringChecker|true detectorHugeSharedStringConstants=HugeSharedStringConstants|true detectorIDivResultCastToDouble=IDivResultCastToDouble|true detectorIncompatMask=IncompatMask|true detectorInconsistentAnnotations=InconsistentAnnotations|true detectorInefficientMemberAccess=InefficientMemberAccess|false detectorInefficientToArray=InefficientToArray|true detectorInfiniteLoop=InfiniteLoop|true detectorInfiniteRecursiveLoop=InfiniteRecursiveLoop|true detectorInfiniteRecursiveLoop2=InfiniteRecursiveLoop2|false detectorInheritanceUnsafeGetResource=InheritanceUnsafeGetResource|true detectorInitializationChain=InitializationChain|true detectorInstantiateStaticClass=InstantiateStaticClass|true detectorInvalidJUnitTest=InvalidJUnitTest|true detectorIteratorIdioms=IteratorIdioms|true detectorLazyInit=LazyInit|true detectorLoadOfKnownNullValue=LoadOfKnownNullValue|true detectorLostLoggerDueToWeakReference=LostLoggerDueToWeakReference|true detectorMethodReturnCheck=MethodReturnCheck|true detectorMultithreadedInstanceAccess=MultithreadedInstanceAccess|true detectorMutableLock=MutableLock|true detectorMutableStaticFields=MutableStaticFields|true detectorNaming=Naming|true detectorNumberConstructor=NumberConstructor|true detectorOverridingEqualsNotSymmetrical=OverridingEqualsNotSymmetrical|true detectorPreferZeroLengthArrays=PreferZeroLengthArrays|true detectorPublicSemaphores=PublicSemaphores|false detectorQuestionableBooleanAssignment=QuestionableBooleanAssignment|true detectorReadOfInstanceFieldInMethodInvokedByConstructorInSuperclass=ReadOfInstanceFieldInMethodInvokedByConstructorInSuperclass|true detectorReadReturnShouldBeChecked=ReadReturnShouldBeChecked|true detectorRedundantInterfaces=RedundantInterfaces|true detectorRepeatedConditionals=RepeatedConditionals|true detectorRuntimeExceptionCapture=RuntimeExceptionCapture|true detectorSerializableIdiom=SerializableIdiom|true detectorStartInConstructor=StartInConstructor|true detectorStaticCalendarDetector=StaticCalendarDetector|true detectorStringConcatenation=StringConcatenation|true detectorSuperfluousInstanceOf=SuperfluousInstanceOf|true detectorSuspiciousThreadInterrupted=SuspiciousThreadInterrupted|true detectorSwitchFallthrough=SwitchFallthrough|true detectorSynchronizeAndNullCheckField=SynchronizeAndNullCheckField|true detectorSynchronizeOnClassLiteralNotGetClass=SynchronizeOnClassLiteralNotGetClass|true detectorSynchronizingOnContentsOfFieldToProtectField=SynchronizingOnContentsOfFieldToProtectField|true detectorURLProblems=URLProblems|true detectorUncallableMethodOfAnonymousClass=UncallableMethodOfAnonymousClass|true detectorUnnecessaryMath=UnnecessaryMath|true detectorUnreadFields=UnreadFields|true detectorUseObjectEquals=UseObjectEquals|false detectorUselessSubclassMethod=UselessSubclassMethod|false detectorVarArgsProblems=VarArgsProblems|true detectorVolatileUsage=VolatileUsage|true detectorWaitInLoop=WaitInLoop|true detectorWrongMapIterator=WrongMapIterator|true detectorXMLFactoryBypass=XMLFactoryBypass|true detector_threshold=2 effort=default filter_settings=Medium|BAD_PRACTICE,CORRECTNESS,MT_CORRECTNESS,PERFORMANCE,STYLE|false filter_settings_neg=MALICIOUS_CODE,NOISE,I18N,SECURITY,EXPERIMENTAL| run_at_full_build=false CharacterManaJ/resources/0000755000175000017500000000000012560206305015602 5ustar paulliupaulliuCharacterManaJ/resources/_HOW_TO_LOCALIZE.txt0000644000175000017500000000137412560206305021030 0ustar paulliupaulliuローカライズしたい言語ごとにリソースファイルをコピーしてください。 Please copy the resource file for each language you want to localize. ファイル名の末尾は言語コードを指定する必要があります。 The end of the file name must be specified language code. http://www.loc.gov/standards/iso639-2/php/English_list.php 例 (Example) mainframe_ja.xml : 日本語 Japanese mainframe_en.xml : 英語 English mainframe_pt_BR : ブラジル Brazil mainframe_zh_TW : 台湾 Chinese mainframe.xml : デフォルト default * 作成したリソースファイルを募集しております。 We are looking for a resource that you created. http://sourceforge.jp/forum/forum.php?forum_id=23932 CharacterManaJ/resources/preview.png0000644000175000017500000013240212560206305017773 0ustar paulliupaulliuPNG  IHDR,u]{ IDATxipdurV(^Ujfl!9,RԘBc٣#=xq1#Kqh!*n^@$g^xKebPj0{Oy7ۧǟy%CxO F"ǿxRV0w|u*?u8ֻ}n!<`939͗kF7]q'q4qB*7L3_p7OJ>"Э%a\!mwB+33?k r|cϥRB[׏ 'RT,|:/PB!Pm67VZXh_illm hx:O+Aeۆi6%IojwW*XZF9L >o嗟焠k "\T^#p!yȻ}!G,NLۆ"I͛+Wo-.^kZbpD. .|ٳgO PO, DBtwbp.9eTuDU|<ں@p8ȌBR(߸֟?^S)47PB Y^7V+9a}{' ANr2)!zx<>98O>s3 ˂8!TLbY,J̮zMJ a<8@rBQ]F>~rT{72(ȥt\6Lsg{PGDTu~3,eiۈG"0=}(t<D4x 1UiY0, n5 ĩ1$QRp/dQD볳xbz":~0B*ݲ0ɡ!mzh|vshW~C˄>ؗK?0_v;; h6\.1L`mv@)fWVl9w!K(j8;9|*öA:˹8aPjTYF2SƊ3? PWeoNf;K(X(O=ϟ:}zB-Jss fT kkA"JI|_MJ.n//cP3g`$a]Nb5jg''Q؎(H7ޅ,I ^~+3" m d0ͽך!yBz  pu~k[[%E1lƐK$PdIj6T /cx i8{NB n.-A7Mt{$Ir]b: UĐsNJb., `qRitzxsx ey@Bzpfb_z:ޮɍETmܸwDXߵ#c$db`f kk&HD`-oɠJ!ֈP% 'ܗOǷoy #ȏm1ivRL{%"GFůDU"Բ[,+~B'D4L2<{ر-EBX.\sM,!L"LgCL v>7d4+l{-%Oֻ#ϣ`Te;G*I(jkaVC\4[["I(;΍qb++0l3 )SCC \6 Ktݰ'!`_/wϝ=k@8,FB*C2#o͑h$R: Ƚl(R2#EK4V$j4p{i ##ȧR((ŏo7|$!cmk w߇$q"f4U%􁝢Xoԕ91:Ա_&%iUca#@ϟN( .PFs b^'xȢ<⚆Loy[2s*br[$zy. &We,85>X$Q b~m LEAʵ@$Ȣ\2k4KKHh$}~*d +Ό<53: C2ćAH@,.$?:)~ܹ.}&iWggvK$O7XX_eۤlb8w/oj Nb,Zgfplpps VVu}#(2^y h\2 q9,A*E*8ोA)>z zE-GP 0"cX\VVzukNfv1`?yW y,8htrPx s}d$DA Ӷ1R(ݔnRT]DU5砄 nvaY]l6R(>y ?CC-B,/ssSS2lX 書R&h$FU*8Lۆ$Hy95Rt s856s!|8wںO<}띞s(E&Prɱi,BK׹eF[k]hRJ}BL(X1HOsnv>!qx.cVs]4/UU"覉Jzb&Y1.>ێYprt.np!®%E0o_ UD0r,c0c Ytۘ_]E6Q00Sccz 9xɁW=s_Ɛń\2b(Xo{~턼sdgONNUM mk"p#a|ޏb%m]G8Hh/IP.tU#]:&H!_A݆8HDp,̭br`xKPıc`V ui)aYh:r#M&J%dIޅ88>< Q\.@TMXd!p>Z@8GD>f!&th^ͳݢ `qcí+V0X,Qr]q7Q9BQb1pNǭdSUHFKK>W b0B0^,$FyHKiQCр$Pх583+2"ny~H>+_el0, OD]ÈήĄֻ?u.:R&Jq Q#敨xП_^tVmASU 868D4t<RȢ"G`6\*d,;++_[{KL"~>@[vzn !0mk p\ƙ]C"9Ip6닋iٞ!", w PEN|Jj_ ?C6J)jJ4U& rw^g6TI`6&Ȳg?ýJ=@_hUo癑 rpCA@1F,,aXa6%ZcHFB>~nuĄ.]8}ݲ* bZ 6uȢz}J4>u Hma ]]\Ho޽Z'z5 vZ (bq}X [* w^`0N1hb*DY>D˷7 [^{n sXWN/>͘IZd0 i;n.-a mvp/B,|'qnb-|_vM94EA)٭?0(G"h:#J!"FUX V x]:9˒-0LNQ#xdh}Y!CKzF>~˻8|*E|v-%ossxcve@{ 1O=@Et{\{^< .;,dQH)//,I$v|n. ࡢ8rd&} ֻ@1~C33'j`!!(]\Fd43896b|y|\䥎?))~'D4b:-4;ȒZFfmKB08#Cz?ȥR=ցBv ֻ .~zFHضfi (fݺ!|w8ûU`$ǎAu h;wpocJ̓0ƐO&wml68}xQ!GѨCPa.|3 n$QJ++xk~wWWqaj ϝ= pQHqqz5>Da0J9DQD^Gײv}dCE f}6\ "Gp&L2Fj=ܿwfⅲDb(!iXt -/½B*w]!ht:HbzH;ϋs񁁏 !#wR&̓'Oo mm,j6 +v~yXrwWMǎF(x#cH㨷Zf1MH)\BQo". @6{ID}`-JARU,9"=̴mv񨁩> kkkmӗDZ ^_(5!"_,B:y۰HipޘzX:LJTd~\s5 ùZ-4;$YPk셵ABNXK񡙙/qY o7; MMi>4:.BF$EAs8ǶaFp, iYבAzDxp< Q(_v|Ȃ|o-,9\D[[H!bD~ d,e/|.&Z 812Weq,onD^п41]Tc,$wI&.|d}F]^ N"xeYzx: A("HOhV?q'`eI;mC4!2DoI['H((T: R[Q013d"E Z:*xT4FӗcA>ƋEL aesNz/WwA A>Š&xAöL~OB;|2YEnM(X?y|x8iQ9 !n7VH"q òmTewk]`o% 2Ţ bcyRH9׍ 0aYHC(Ѣ(!h6p8h܄QaMܣgAm4`Ƽ@m]F\H.?`wUaz ӄnE"HF(}[blF c&={1U}m {$<3noD"=cZ[.(4ܿQQDM%9eYhV6^~t'ȓyR lT*7vwSj5V^Ly;!L8,*L5ļxh6:^+\2yМc|>4`L ~̙ C/'0:2p;w@4$v)1hZAn%ĝ-8c R6n>zET(ERA}m u ̯"8!HhF 5?f Jퟏq]]*HbXTYXwL&x]O9t,v;`.wNc&`LJ-5q HN> 9Nss8s̎ ]MU[=B`[ltM0] IDATۆ,Ie1؆nfE߾;ظ{JX @$9?#r(HmX mc@.i۸I1l<97ODɊD UQ098<?5ǰ,wBmcht9$id30Ƃd7!RXEE޼ބ8[pl;hB(uRDIMs8u4;//ի(߾իW書:6fgQ[YAV%z*&*"խ-,nl@""k5ܽDvNCW*% D݃ ?HۺoڝNt_"M$ec ۈǟ<&2ΑFDhaXLoy״GaqR6{NŢm:HH(Xo'O:i( p{F5qm]w~S)US)C6Lscau`Yh{c,AZEۅ*I0L %Ǐ|_vMbQ0\.C7 >;jacۺLV oj>t(6IyO/kj&*F_6jULFӧ19+864/\˫%4 n] V$4 C. ahnyCFXoR[zj7Bƿ I/SS( Jc*&Z..UA{ { W! k7cP$ ,cPW> 4]?s,=8g<=y@9YFj~qDb@Ӈ>0z(e2gNM_LF2='w`Ge^kX@<QdzgR,#BSXzAT |W0R(`fdm) rf UnD10Ӳ`&aJ)tF&7ݶ/R00Z(@S~ `BPױl"Jkefqm~:q"WxOaYl40M ڎ(HD"tHܮwVVıc{>[KK}G/\8GR " d!HbAaX4]8>gYOSBh?7t`6n//c}+*Jt x7/"Qocnem]w=ch7~{' vs9h^+VE:wKUBe،!na1UUc1qn8PU.⚆JMo832|X.0mH(T,a[ p!"s0uHBzxw)[7~!^"p|x_öEUKPR{ D5 Ї+WQׯl"zkxmf|,LA!hwUt;x<UtQJFqcaA;7݆c $#Hm;++>iK/A$ Ap#RO}TULsb:}^ء&#&i񓣣?jJ܉6iB~˺]GT P>Eh{YjOO_4"^4A)< }r$MM:XVcIs;=巙ir ݺ!Mh4xuxF// m%u%H(&z D+{wS O?4 ޘC, ˿("GwB9B)=qu$4D4;Bv;Eoa}9ǩQHV+ o1a9[%)s`w%?98g`;~_/)u\EV67w]#17訷A0X4 Mpgq!I0Ơ*R޲v8w 8+Ya;.]B! n8s v.l<BPlr!{'4c6Nòl]ںPB Q??y| .J圣@eLMd ;{l7;:^ouBbA4ȢRJm6B_(mq=؞'৞8W{/$p>SOd!Օ #Hy+%yy~sE"(s>-q[${P(!ȧR) B)LL\ e)_!yBD&LrR1,AZ7!~BPkw0Y,`'Xp i'!V_kBdfs6-+8G(!*تn[cNJɢF벐gKvv}q_c('6~|@ (xմp/@*,I'ȱAG`?["dIB6 FsVERBp]ddP>$-'T!FEE({,y,O(xiyw@f'1gs Y"wSIPDYPv!E"}u~+-jN> `!#>'p/eǁ,InO)#z0aðL X-?I)u[lBR 㸻@('wc}w?ښ( rkgEI_- vf/aAFDG "6:{LE"{o;nTU4L^uL iv7s{Nj$9P$N۝b(2mODQv]5$Upj1Mq~tֵ`!:yB@(݁yr/E";Dݱt#gN|g^_b#0MX x48}geX2?8TE<ց>`uDL*ANx)?87;.9'h -n zl{ M@fQBY]ƃBdo-(ZV('b~m/j"K[l:>2;QQJ\ms c n+eJ!HMt{l_; ]w'ꋴ(g7,foFJINt`PE4;X?wl/~B&hT mw79z=[Ѵݿ?kf3 9q oݽX.SBAcR\91 w]#{Xm).!I$X:sΡJҎa̍ ›67N?@:rZ1hÎoim-TcF(XGDRFcތ<gϏ^tsg)^pΡ{!|zw/ ^wǡPh(nAsq_!ȧÝrU`+k:-4-&$Q ZK=t#v}"( {o  ('v`SCHۢ2J!LJ[(PE"[KK?F]`RIG?9HcAw7w?t#ZV67iAFΧ>܆*ː% LxύzEyb'T~Da=7$>D?yhR1(};5! DZXAa8> ȧRC~^A` 0-P9rYcGV HM09B0=r Y1>(9dQz$(BQ\:y kPI\M-,^_'M0RQ-F_iϮ~{~,=> uȒD'8̓}.!t I. 0h`zDԛp`p{sK/^<0ƐF1ц@)*",9n 'ʽ-Ywi; Vﱫp"bb(tӄY{MeathЉ"$!.zsP~R}HǣرJܥ~~t} c :Ё>u-e2#K"qN &cu]OUB׃Fu* 1(&>QZ-|L8JQL|8+4j!DguEEXY_$OQeyJvoTIbe Å*J0a (BnSO^IVQvmoRǔcYh&꺁mt e=``DX+GF/1d1dߏ~dB2M1:}@m; Dܮ> o70Z,f Cep$Bp|| vzĝe(8 rmo I?H: ò~bΟt4a&886޸;م%,nlhRin(!eXH&ޘC1,IHb0 F {/߻aFb񞏩in-o>"#ɒD?u/w h˳08ܝ[$z)ݸ{k={19GK7'EE"E6@BVGǶ0A DAp~fV068MX\x+o㉡1w]!ZGst:o?zn/`emGx{G"# LEU50xr<0x2r]8CPE ,;ymȳ^k,j((*89:=ays`~SS!3Oݯቩcι-jULN  tfW~gl}VϹI_?Ipbd?s+HQxQiw~ض^xR wP_4TX\rynu]|ӟ?yZ0`YmuwsdIw nzL<7֥t,w;T¤ŊtWV^o#ʲf9ΟzNdAȽf$ /=sbjpEFܸaIh",.ܭ~s|MB>. IDAT۳, BDP(F0EQnQ* O[??ùSR( ȧ%sȥr(eJ&Pp`}OiBdLMMr,p:QJ,!zU,$p(###J=0߯5> nt DA%AFqdq$Qtt[5pJAXM{ ʵ6?@.gqD(ũiz&<<ު}QKj"[[HbHr؁'@C #?H23$ B9rv;Mv[ڒz_Gu[i^TuۯGCZnR/=/^w=,\Nj!|WOQ@0™VinŮ 'ˈ׭ſ|\Jhu:"G9wH{D ӋPx!xqqN ͌zePUVZj V@E˔y1V,!PAq?n8Ln+F@)EQn--/;nA08!fȂ%Lz1y8.7 RlǃPdqFc?lGO) r^ۇb˿u;s߱kGS/`qB1 W=,jEPձ Gvv6 u/ؑc?@O_v7F A::Z Nl"ǐc~?##0b-x2<Յk-C0h`96T0ce,X9++/G rPK.M{pskM(bA&JuwRP0gg2.^|^7pJ2Bb[E?H\UQՓ0J8YFL&w?ǰVV*u8Ca(0\ @$EI\$ɺHɽ &5J0t#T(8NyY!ekt^ r&ocR#ŸNp hT^wAv6=bB!`c#-@ 7^ePAFkoǞ`pG jqgo1X9cq1?``iP(VhZ#OaըWr,ŽhaqbPp X-12i)T!E}.n_)6~GKJI_׷|cY E6=I.cd >-)P((Q%UxJ& ,%1jV+/@kfj ]q^}=h4pʨ`ra؉ շn U߿PsͭP˻M:<(1 1KV)999P0M(+*Vņu`Avqc钥(4kBTAA}%K `y51NJ3B\qR9+kَC. ;[αN;%u4 ڂ9<`TՅTPZ:z{O #FS kMV2 L#pTR<;p nm_܌PprF|rr=yf$X.̣~jtX!]/⟾r'7|kh!L mYYY(*.eǼy(.ÁV݀ J+Kac>sgɫ>[>?T f9G* |Edɒץg=ϋn&L%-<2l'Zb _ʵ\-.A4AܐVrqy62,b-IgpYZ;(uaA9++P2!JA#9˲5N`ʢGCA^zWnyd ĢEؽ! ]8݃u׭4`:65Ja (v:hE)& p{14UBP|?vpA,3j->6/$.#.4cҘm!fda%JCeF '8 0hJ[8p|0t R)wN,X@[Oqi J+[(üQE؜N(6J)YY #GG@O/ KKp_{-(+*BѭGX qYR\t9уN`O&XT7V{>$f]~\Aǭh=Ԋަ^|`| Tm؆'`X|&0\Nd}Mh/Ql<~ZaSp\ʲB˲,d[9ШT']v7 s%5?gUHx0)7 Ys 3Yœ638E)?8;a^|UUG( EAm!}ρEx׿BmrϿCAAn[R R ZP@V˳B^ AB 1VZy5, 82a眑!ݐ<>_J܆8W5&Pd*r:m} 'J1)eȂ5 6io?S^\.m YBAbmX@8^( i;~RңG/Rz'=6\,,6jSAM1KBPl &T 0k`9|_7z,rTI)RޯI lj5͒m-p# :~,;a8`M? T"~f!^?6 ?;}ÄJVpb߾@)ۄ‚Q$OݘCbK?1xT6Fz-!!vӯ7 /i%,.WO6|" $x>zȑIRZZl%u|tT; y梱,@P@)qQ^ ÓP:OIQ5J nupz}PH&ERjݞLJUeY ^J ikAxm٨6LR! i~s{.9$ Kr}^l^3`MQ` ::mbH I"~=xhHi9PI'  D~ӄݣ 0Zd + `M"v[}ソb)Z =4[G rJq0 r z4w.t`@P]K$\!H{#SaeEŇxOyH#bɒ`('ۛF}Ab_Ȃ5@_{{ZFBXZ˵|fRb=xGٌ^ l.s{>l] ?^_X"(^|\,(pe F)X*? bC?c]ŏR egJ)=6B(BPD@0rI Y{)`M2 }|͜Vc4VSmmgi53~Io4eq=⃿ x[a>!yR/CPLjI:!P(KBBCQ/9CA):0`y~vPj*rN$Fd&SmQ;!v{e;L QEA5A\ z# !Bkw  {E-(hg|H(ҿvXkHV$-:l@g * :H} ҭ؟Jż3r3bg$+ #띢PU㻇'$A&LF9;rcfvq2+&@)e:T(,aݓ'2D#KzPQ۩P(,Z[kR,+ NDɰ@V6jhY | fV$g*#^D |maP bJ`jơc@V'|Wrw01.?<|Q㱹|;`M1߽}>W@Kw}H \8.h@%V(qШsTJ1h ;7e^!<΋'jn/hjNͭ;~ =(&ta8fk |J0O0`d {q# sN)fxjmN+X¨բ '' aY ˟aD*@Z!JQ(5ڥ]d^beѳ*R p @yP>PG; 2%4S| CjSx)u:K2 <> *϶S S%]޹k8tl Z-֯~qx< e) * nj{{Oϳo^&ryE7 KÂ*TP@Y-_nxbh$eK0dIJH",5n[ZQ߄Lh +ZV.'QjyC}(ϏqT} G3.'DBR)h48uz}y+2 z^{q#[X3O;V.^ש7ftiZ-d ,-5aPk~3O@+ GŅ33 *Cg, @lo {z@– XSQ ൣxm4Dn]YbŨcǡ]H40<,|p̶]P(WK\,*'vt|H-%YfEEEn9 JtHEOFV6D^{==Xt 9y@IUL<&"XaVC48, ZBa^㒆].~ժ;0p|8 a >"R!u7[z]P^t ZZqb =l뽐k&90'Ջ`}Ja 0r" `Q @LV4h%/ ^A2@g_$k?c;$T mz)jP)Upy}xIWhaařY<sVFK: vtpĿ1"@T35Mn_| %RMMζW9aȂ5PJk/ߜo2RI5$R_DK, twN:1VJ4dHPG|Ǐ!xמW K& 3,#0U^̲,)js,.0,-±,n70csvF a.ȃkV,GqqV.IGbW_ IDATJФ;FN@ho`@k ,ݰ;0((ʂYM6:u !sƵ99Ufa~Nn|XȂ>@<҂Dv*ͷtu;'VY@TVs^/ٳ[3}e# ,aի_JT&mN}ՅNH"*[[1/?*b>C3ꑝgh+a(**FV,W Ŷݒ- lj0l.vlΟ= ^;б (4MAɲ87Hwr8ڊGy y:= ėd#@PTh~*TRRB+SXC^Y ssCPAXNo~PHb\A@P  dk+[Z`0u` e ˲[>C V?%円)2cBY0e }ߦM?^wA/ "Dz8ގaQXL1?&,ˋ&^ v{u`Xg[CN{ 0v++rmL!pjT dsP!ʤ8N!ˤ9?Y/vyԛ·JT~K_:|+}xA&؜Έ+ȱ,AW?vTΛ|'78dǵ˖c L Yf1%yyeWWV>~GU핥BE\8ӃbOVP`c#lv;Lz]U Dz}`3tZ}@;/TUU=Ȗ-[HIIId;/}%͂&QJAJ@vF*R* Amo5\WF$SZPXx/|p_~v6yhnFg8AT!t 87R} N\oZVn3 CTJg srζZ ./3Adt/|5?vy1 a@! !p8pE2t KvƀAxmZN-ܢz[)O=$///RFo/,pTktFX1K^  ;m\(wX6߿`zU_ mmhZDzFrFL,?pwNix3#5๦?=|ЙvRj3  p;0RBV(7 8ޘƟ Â*._}}LJq'\>u}{ & Zuj*uj+`.΃9SآhTb3!6!&?{Ur-\c…ؽ{mmm%kr A75N! (0ĥ&^/J4/<@gαJ ?㷬Y)k:rr`T)GmcBA46ln=ɺۑ;j[yy9?%Kvŋ SCTm*x`bu-A9}ӧ]c.:zZTI} 򲲐o2p|vnk{RHwMa@)E[[[ꡅ ~o>VӊZc0"YTVV&<.77w&O9Ba~)(+*61E-S (+hˌBKaXe'H0%,}v;i~)Oa^aTBo`޽{$%q]pU g `'?S4bھ};o:V*^2t㥔bݕW'۽'I,XKϿw~AA~0Y%aDm0dKK]"ڸq[n=pmE>CCC)Ѐ_~HNvkm5K QR"8k!%҉DBDq85:xYYzɒ8~|&dƎ,Xsa.],"UUǣl^ܬ!l/>JPٳ՟[jUYRh4`Zyx$cZ@Dr+P@t %!Bkܞ?1@POjs`(mܹnۓvb`qlZ\RK-%<ϣlԨT ^y*> $RbΝ;8R)\LLX[RTUU}' ܷ_X~JRfJKzYYY8,]zVͨ1k< U* e >WX$,VK_[cJ΄Cvu7;(]ͳ&*VS644`۶mZokZxDeN^h-_3'ZZ~GȌFT_dz88~bup#a !{eŪ GRło㖤&G3j_1􇗈u!V;w|YCk8ڱyF|qkR) x1$wqi X,Ǝx#Zx<8|c u [[P)ٮ'z>Xd kSQZNR]_|~?j%g xQPZJE]wFlڴ [lIYb ߠ" *80|'v4F d  l`o;qbz([T* r>5)+,\͆%ݤnWe& ޻7.oߎΤVkKrZA^0}8u08PPxAe#9Xo><0m;vߛт]%,ɋ`Y^ L欬2F 'šUEEEqa <'@>Owވplڴ ;v@mm-6l/b;wET׃1`+|<,,.%X\RBV^={"///朒;WSS3wdxBDwoBٸuhANQE>L `QWmN}f?tu)bj۶m `8xp^u;p_?1 \#7 cqF-[ }v\ 2I$kkk%ZW8=={^ױPjjqڝeƄ,XsNh4yt|QWTO~N$ 6o (// .oöm{Q5Q682Ki PB]R Vso@ʫe˖H.\jBpAp hhh=p:.JCuu5jkku3a Gf`Q*U^CXB@xy#444)ϑs;P[[RipX555J4k(]G/\<7N#X:XbӦM(**Žp^J*vs={q{NNgm5D0~E! dVP&cd?]::~رUUUDmmmZjŊ3x:t ֢Ũ@EEO} P]]?1  C<^o|6m›oZTVVF[WWSQa֦'NDFo]<QLw؁6K(` ?_U({5{O? svNHv9Ѕ+98qO>dxuu)fBn7xJoR[FզMp1lٲ%$Rh\<ӣ^'`Æ 8Ad0 #Wdd, 0[,,[V9xGee%xb ~a\(^ GDA-?ٳg/6mBmm-sQMu% ! ]ÎB٣^OGaK%N-[&beM:EY$J Xf ~6Պ_{iPJqw:.FQxeY[RX,,^YSWW͛7h4FYRɬ+IE &!&nq[Yt\e# cƍO<Y_eΝM"+{;w^011ڊEļGI${1?~ 7FJSiZu%QSSI?~6Lt,]*$͸ Q AdddC+իW؋H.Xvc>*xg ]'Z ~Dك'OFT)=xqX,>fÆ Ûo ue0SU9PJyo=eƂ,XsJ(UWSjߏy-ܵkxhڵk {18#5n{bΝ8yd$NEe9/F[WH !رcك;wٺ]ĉɐ2E p'VH&Y7nFmu߹O21Ȃ5 2ļl$-a]"ZPۥmҘV\~xBUuE):aǎZV5+9^***n 1 #[X,X jٝz#V;/Pɂ6iL0*+#0Z' f>ŲB0tB̢2# ,&@}c}IA*ڝK'x#L֜ i ѳaDe tOEeȂ5bDxwR T|,'Dccc$ؾzj< һۿ+_ N$!1 'KKpS`b()xȂ7=!m+'I%PbY?0xʒ9{]v!;6dLu򄋢Ju2/,#" ,%;wO{>JiZ@%eQJfYҘcRu+i,7DLu DaJB1ܑkF'644zg=WY+bEJ"Iˊc2Iw/43 E_&Buu5B }P(Ň2# ,%;]JB2.*V^=JT˒ćRaQJ#ǤyEW#l׮]udtoLfCDR ;P)GC1 ?p{'{2`J644o4蜒u_*XVݻ77Ee(*u͑vc> (dFT1$v8ҩV*# <1" ,%W Dcx3ZCH|AxgEcc#~H]*BRDzB"1,BuȲۼys4dXtc;te J)k4ry)`꿆dL{WWy衇o>8q?N23RZ/f_* 02ٺG/%Bsss`|طo_v… #yf޽_ߴD mnpMES)XDʆc|f\z]-ٿe0G'n TU4pD& sw ,2ڌ~晖 o)a6oN|oa4668cNÃ3HXC@SG=n)ڦu0`BA_J뮘PRfhKDBL ɨWLص񻂢u5%i ))//;p`Yar)@a28qT*w}ay$e#Gcıbk BSvšecϞ=N8e˪qԴo鬫n(>d kӟtc͈'y̙-æ۷*r # ,vE3%2MgM]Y m3< 1;WXfoьud. d kAJ!hu1g3=Yf!D#ǯD"Ø!vq9 $Lj&f2!e82YfRκzggb۷oaL3`?IkNIw"pv=tȂ5hiiUn&Wj"d:C{S<ن,XvYLw`(@(KR9yQ'jlܸ1}q} ! ,6=W㩖0\헬kLQZZ JQKYf B.9XM3%2sYf ҊTntH¡׏cx [Ȃ5GBI88N3-6e疫)\:k l IDATeb.K BL\ySjYY,9_BfF7Kfk3kUC]zMz|)p㱍K Ef [XJ) E]]]1AQ{zcty8uF |3w&Ӽq[]{Nx _9&Af2Ӄ,XD]]]1?1Q"x&C L Yf+(x`XKQ(yh+"dZQ\`"V^;w0f=-]fkqb0f-c!X,Y]fkظp¤n)fbÆ S; iEF%vZաaԾ_7TM4YUAfȂ5$ h]]ݨ}srTuX555X]Ȃ5$jM?00m۶.BžQbnW `0 Z*++ZY3Ưjkk/[X `0۶mCMMMk՗m+IF:nɳ` w vsee%q KKK/8NgIۃP+ٲtkSUU_q,Xb/'\8>uݧYf9۶m71+=̊r]GDZCM x#ϷmvYűR5Mmhh@MM yT23,X3DtѾL0ˑ{` F5AQQܮ2B"h_Fu]5 sy5MbV)Ċa2SG.((@HpA0hh<@^FMJe?C||*LZرc(3Ȃ5C$pOBSy/F^#b+3#lhR)Dfۀ`0 w$[*7bRZE&-,WZd!eBՎiM2ћ*~ݾ PvDF::Ȃ5PT())$/f!YN[w2 "ݰ!Wzd[YEcN唉8%b<rjkki}}}ƍ|=ٍ,X3@D7S1ъ1s*d2pGIsN|__Lu/3+荅Ͷ(QǼuȂ5$p Z3Lhqq83:K?8Tk*k,Lh͖sCpjR/Z3zLc$pO*ѥ,XX1h,3=KY!Xt阏TDk _Mvl+VTTRhđk kRL inŁ&B2`M? ':R`%b,EJ=n4)TsN׏n(sI _(OS[msELUOE9Bxѳ+5a%!N"FRL>`]̞VrOJC/ ;w9ƍCZd$H|)5{LAJÔg1EuavvFŢ %"yi4S__@둝 2SVkǩ1<93^b;80c-R>CCCcNi 悋8ٱc럚q\Ȃ53A .x4$! 6ĸӀ,X3L^08* /SJ0a .D۶m8p`ۤ^D&!``q8¥P)!g럢"J2z3EI.-=.^A z!PJA]Ufґ3 gm&lٲ%Ҿ+Qq Zj?:oƍX~=QySMMM"֎gOؾ};z9YVb! "TAWWרaZa6l\iK0O&h8x㍂$DՑ/ 1ٲ6<`06lĆ b[,G޽{gfaZ3Z[[cu3I,HsYfJ)0u8OUS9L5EtɚS(T+Sta2mm^]]'ND?x)W:fC ZW3)>da(Q_8| *CZ&=BHsssd =a։0"\ɶm@)A aӈÚax闿%@=cRs͛GyO>d80M0 EVn  T?_/40 Y `0 غu}ms 7ɲϵ~=O`6}Od&dzŎU#ss,xcLR5PjkVq1t ( ں Jc9qUJ$q,F*mϏuLX,_UTT$'[Յ_~9g_CY9T wfxQ폁t[P\l46fCԂR/;{(Xt: Uu>v̎MO~c>ӱB;xMܟVo?^t 2CY£>y|T?޹w>B 싈 jھtV[]ڪuˣV$"OkZbպ?lE)A!!$53w;?I-37_=3C3|w)˚8}4x\YTg1ً/w\(~tu ݧzGr\sz'l>Իp8 #^kn)1 I`'IIW=Bk&M*X}`@x+iQsB2F+\q383qK!XKy :*X) 0A 6QN $_%x17 hl2syܧ-yʪ̴4\?T~FEɤHC\vW{͗xlvbV a{_ qGJM :pn}E)ñScΓ'l i9<> ˲yd0vB`/Nt 5 p]&VA^u dyY,9ho7d*EWWSAhbư# \,t'Lm9WJ]]QcN͗ ?a0h']]3.^Lv 鏪~uñbB`xpB+EPyv@!f'wD=' 9޽q(Q(Rߥ9fLNL|+$56 $Ee0 P03(pgJB2 L_122X,p:OPzILm̰s4d9#bY΀ ѱ9n>4%)qBt邎Ra@NV$烵Z }Df;iJO/JqV1hjV!qg$,w/JIM&"re+7` 1W &G:shl߁s ?{I)Ap,9Xus&/^IFdUAifF墫@kz'TB opv{$tUC8pЧzߠ)"91 ֎N@VTLg] !vUL41'b apF'UW]޸rF _ˠCl[bi>t:ulB#GNE *XC LOK_U8{8:nٴ'M4٦µ6ñf?E;cڲV\}g3UB` HmR1 !&j9 tDC]%'Q啈eje)q;!u\t+ Gm ; I 6m(H7\nOc`=g8W IKV_=ȏEN) PJ!:7E+I?N B&iaZz/6â'<%,TR=;s:̑CXqY*FuUR}(&SIr[J{ B+E,Y2ybŴXb5^Ád*SkITkڻڥdflRSƒPN Vp\gM>Z܇ v; ȱ-n *X)M-Wlܗѽ.Drot+ {+ ;z* ʀ" alص_ z(ڶPZ|.m!!VnZ\UC`)0ڵg/pVDpomfoPu<8E)Mbn~PJ˖/חOepfKͤ6 ݽa u#BB8@UO9Qx +JKFw0,I:JDˑnu ͆g)'I.ʓ^g\eEYF$:+EIJS9 ~0~v?7}/f`ោ4nJdp'22j0R<ɱIm!x-m*%β`Z ZC`^q}! e;8 8sLrqma^Ao^H'y.n5 V L%$1 zrB?``&!aEZn:ܞ߉"hSdY\)5TUW3is>ou`pWP+!{dήZ>rpj,R[FPd$Q4EJr$}h ˎ;p8_1 UM<Ƈ*XLߟfGsMh `ȶvlYXx|B-H5Eֈ1r½!7jmhvIQΨd.)4_^~&;Aduww"y2 ƪQ4?'keMM@b]^_BjΫ_Z\4T+V9M:Ɔ{zj~IJc@fZ;j:vxdZ-\]i%AxSmcpS]8N'Nvf,Ǔ12^ߝ6?pAkh:|,"Rk6kg0qNJii uߠC3PQVqmsfϛaBDE-gl fc%Ԙ+V&ߔ38D%À'GEH;X$3DؓBB{ $YAՂ,iu@Kcw嗜?J+{őa4TF6aRE;{< ǝH<ހLG{= 8F19唎X8 azKK+ڛϿ` Gapٌnƹ <ʡ!r˔} \0$FXNjf^lp9%fyb`iYm&f^/+{FfsW 睰KߵxߝT@VvDȊgNn o0 !Kǝ%v)zUNͤ2zBT(u_?(Z5'H@0s\u_?(7eP;J̝=#@yS3#DKN i?;: {447_XHKؑ[^~JȠY@?#tQ,p3O65RYU fej 8i# =6D>V:.{ VW=E K'?l3>1cm؆tyA|~X#8'7,CqJzo\+C$TB`5Qj_UUE?QoFd&-o 8rkT10Z=F~#D`IDATM?\|IӁc`i@D\wS~<ñCk~ڙ*X`4EVu{H C>8nM̲/Re=ӏHx @WuMH%X*((Ƽr:7aG!8, 2 K~Ȳy{.\xE%oGmS*X2Pt 7Mp|1'   0 ~[z9zܳ7oh5Iݏep,_,)  096+[E}ZZst, e2 dUX-lAmX ʲ  ^e `Ɇ8r@Ac_`JdU@0Bj&-p:g&?;Zou/[nwks??g?paݭ 4')*DY!,PL|hq9Qt P^/PշO}V[VPV /PXuCaYp #|(*rAJ_&(V-)_ !Bl#ǟ|']㗤nȪŒt'3nV|}nߗm¨`1c9m\"dĤ|\;CVCR/~CK8@bRx[! ;R{)Ͷ#Bn)A,à[Qn>%h47vʤnQ'4}sk~:ciuo~A`5(PT9;}j]{͊8"GT4/Jm$XkuHP<QQ|_.)tEg̲ :=>z~O[:vG\eq, A^;0 3f+fgo׽|vpڊv_住~*!Vˢ; b4 L9g u%& S`ivIYFrtuv WYQg {lUต2 !? Lj,EU!JQqiisZmimDy%~sWaVYx]G; }@yi6vZwțwyxXzuS=Κ]_gLϻ_[?lklwIǃG\~ |nRo05@}ө0 fL;wc޼yoR?B<';l`&B/M ]قñǹImC&tArhpt[mM=k/ɋ=?y]m`_6~ٝӍ./񤲟^VbRT) 2te9;ol믻'},[\_)_  CGF^4Sy]bZN͆'[xSP#(r"lMz, =}qtu->cBXzte?J^}kW ??7B`uQO>>oѷj3.ȵs@*!^U%sfMn:++ keMM@Q3 }.ߞuUR dPUX3!m:‚SR'݇B' ^o; g<1ҤsEZ Sz(Ac(͂Q/a~gG~?{~Ḃ'j.-_Q[m;eWo3k,*d9we&2meEgg{m̓GԆzxH*M9| Lq )1)',ܩ $K)*˻{0 'mY̞äI_SorsvG5xE sι8/}ïzT4.lȇa =4e: IW +uix)ArœpiPtkC}|u$|Ŭ?5ټvY޾֪sN_4ߝ>Q:Ҡ+kj[ 1 $[qͼsUU ~qlZvV@tKk̟.GT4a{8.r{>:@^J a;Ԁ|N ݄c}K=q8 Q"c=n_ ('2/lzkn\XyǵܰeqK1 &#'sRr.#*X!r36|&P3Pf1uNbվOОWX ')tDUQVVb Q]Ul;2} _TN>kiCk+ "n[[ΆxZYSx_^x.ZkP˾ڵ2'}~Q0=,\^~ 1 @ezՈމǍpd^|/=umfWT:ŒoeÏJMvn# 9}'PȲ׽zdg@T5U)cәM;jVݞZ>l[nޯ,#/= WN)[~5Dtu:AlimsBIi+CO8x@H،x } Wث~~f7/~Ӎ~*Xtݢ(0LPD nўE_~e  #` F<|]o:~󗇎Cx~[9œn3h F,+nTQ!quUxo4̥e *r}wMusGBCw<~?ή(͚[ouNqBK#b l2p `YtP'GѓO=Ϭ#)*N\-;O/p'hq`[D\{S&{7k8,^oܤ ِee7 YmkoL壶8(cO7ok&d2 qݙ:x|BK#d1P:6t͖ZڱM6FRc-Zt`ĺ~޺8X zX.ǩt,AK#_3rB5 pmc_hӵIKk؟pΝnwiwo=ڲeQoqT#2C/^emnٻW#gxHT܁2YКc4}6Z,SL c*XNlbY;-Xi'Qƚ5kZ#%Ԉي[9L8((C: J/T4B0Ko׮]/v/ w2 ìyP-F]ez+y=VE~T((PҐGy43U@۠ eCPN@B9/>eNI=T(r!t? ELFvv$}Bkd@?< `Q(, 2bEPF CXCffIENDB`CharacterManaJ/resources/icons/0000755000175000017500000000000012560206305016715 5ustar paulliupaulliuCharacterManaJ/resources/icons/sort2.png0000644000175000017500000000605012560206305020475 0ustar paulliupaulliuPNG  IHDRa pHYs   OiCCPPhotoshop ICC profilexڝSgTS=BKKoR RB&*! J!QEEȠQ, !{kּ> H3Q5 B.@ $pd!s#~<<+"x M0B\t8K@zB@F&S`cbP-`'{[! eDh;VEX0fK9-0IWfH  0Q){`##xFW<+*x<$9E[-qWW.(I+6aa@.y24x6_-"bbϫp@t~,/;m%h^ uf@Wp~<5j>{-]cK'Xto(hw?G%fIq^D$.Tʳ?D*A, `6B$BB dr`)B(Ͱ*`/@4Qhp.U=pa( Aa!ڈbX#!H$ ɈQ"K5H1RT UH=r9\F;2G1Q= C7F dt1r=6Ыhڏ>C03l0.B8, c˱" VcϱwE 6wB aAHXLXNH $4 7 Q'"K&b21XH,#/{C7$C2'ITFnR#,4H#dk9, +ȅ3![ b@qS(RjJ4e2AURݨT5ZBRQ4u9̓IKhhitݕNWGw Ljg(gwLӋT071oUX**| J&*/Tު UUT^S}FU3S ԖUPSSg;goT?~YYLOCQ_ cx,!k u5&|v*=9C3J3WRf?qtN (~))4L1e\kXHQG6EYAJ'\'GgSSݧ M=:.kDwn^Loy}/TmG X $ <5qo</QC]@Caaᄑ.ȽJtq]zۯ6iܟ4)Y3sCQ? 0k߬~OCOg#/c/Wװwa>>r><72Y_7ȷOo_C#dz%gA[z|!?:eAAA!h쐭!ΑiP~aa~ 'W?pX15wCsDDDޛg1O9-J5*>.j<74?.fYXXIlK9.*6nl {/]py.,:@LN8A*%w% yg"/6шC\*NH*Mz쑼5y$3,幄'L Lݛ:v m2=:1qB!Mggfvˬen/kY- BTZ(*geWf͉9+̳ې7ᒶKW-X潬j9(xoʿܔĹdff-[n ڴ VE/(ۻCɾUUMfeI?m]Nmq#׹=TR+Gw- 6 U#pDy  :v{vg/jBFS[b[O>zG499?rCd&ˮ/~јѡ򗓿m|x31^VwwO| (hSЧc3- cHRMz%u0`:o_FSIDATxڔ?KAw9+?}/`P Y*rl AH ڈv[I#D {S͙?Y o7 Tx1 _,~|u$hZUU:v{|"竬uXކ&@#ЧKeeL nP:Q@!%'t|(sDǎ7 Xk0 `GCF.7IFf;9q~jV@ X3k04[1DHꆶNXwBkG( )&kK`u7s,.*`R"$m9S؃ů𵬶*/7ǭ$Ӡ]uv@Ղμ<ܕ_~Vx}IENDB`CharacterManaJ/resources/icons/pin-icon1.png0000644000175000017500000000177412560206305021231 0ustar paulliupaulliuPNG  IHDR sRGBbKGD pHYs  tIME0oϫ|IDAT8ˍMlEƟB) ē 7c9P4"D9ΦR6Њ\͂AMfG7@3j9כX^Q]'1Iuo˺/^mh%Fwï`,+䩆g\dϟg^n^ӼK%)_ߦdwUN;SӘ1t-M9[eXWBr3p28RR|_wXKf*heLSYoY)G+u[m}Vn\ C H3Q5 B.@ $pd!s#~<<+"x M0B\t8K@zB@F&S`cbP-`'{[! eDh;VEX0fK9-0IWfH  0Q){`##xFW<+*x<$9E[-qWW.(I+6aa@.y24x6_-"bbϫp@t~,/;m%h^ uf@Wp~<5j>{-]cK'Xto(hw?G%fIq^D$.Tʳ?D*A, `6B$BB dr`)B(Ͱ*`/@4Qhp.U=pa( Aa!ڈbX#!H$ ɈQ"K5H1RT UH=r9\F;2G1Q= C7F dt1r=6Ыhڏ>C03l0.B8, c˱" VcϱwE 6wB aAHXLXNH $4 7 Q'"K&b21XH,#/{C7$C2'ITFnR#,4H#dk9, +ȅ3![ b@qS(RjJ4e2AURݨT5ZBRQ4u9̓IKhhitݕNWGw Ljg(gwLӋT071oUX**| J&*/Tު UUT^S}FU3S ԖUPSSg;goT?~YYLOCQ_ cx,!k u5&|v*=9C3J3WRf?qtN (~))4L1e\kXHQG6EYAJ'\'GgSSݧ M=:.kDwn^Loy}/TmG X $ <5qo</QC]@Caaᄑ.ȽJtq]zۯ6iܟ4)Y3sCQ? 0k߬~OCOg#/c/Wװwa>>r><72Y_7ȷOo_C#dz%gA[z|!?:eAAA!h쐭!ΑiP~aa~ 'W?pX15wCsDDDޛg1O9-J5*>.j<74?.fYXXIlK9.*6nl {/]py.,:@LN8A*%w% yg"/6шC\*NH*Mz쑼5y$3,幄'L Lݛ:v m2=:1qB!Mggfvˬen/kY- BTZ(*geWf͉9+̳ې7ᒶKW-X潬j9(xoʿܔĹdff-[n ڴ VE/(ۻCɾUUMfeI?m]Nmq#׹=TR+Gw- 6 U#pDy  :v{vg/jBFS[b[O>zG499?rCd&ˮ/~јѡ򗓿m|x31^VwwO| (hSЧc3- cHRMz%u0`:o_FIDATxڤkg?wrN1E!&^pZ(u)9D8(^(-.\KT.ZoKLN]p\N/;q*dB mHQ16ls ,- Z{ ˤ uق!MB6ϑ$ !Q7SB3O'! OJ?~hlaQ0ћ@ 8~oWf(:CM$[ .!]AU v7z!s᠖/>sg~  q@(/! nL nL5<)ժn} ) ?WX$xM9ix1"ٺP#~pp9=֟& B k*!\.)F d=`)r`(AR,v~8Sz#xYص&"aLTdǹ'ꥶ"%45Q -LG}@f?b͕o1B_LC&;}^[?l&}VKKÀbvmV1yLY;NIENDB`CharacterManaJ/resources/icons/arrow_up2.png0000644000175000017500000000614312560206305021347 0ustar paulliupaulliuPNG  IHDRa pHYs   OiCCPPhotoshop ICC profilexڝSgTS=BKKoR RB&*! J!QEEȠQ, !{kּ> H3Q5 B.@ $pd!s#~<<+"x M0B\t8K@zB@F&S`cbP-`'{[! eDh;VEX0fK9-0IWfH  0Q){`##xFW<+*x<$9E[-qWW.(I+6aa@.y24x6_-"bbϫp@t~,/;m%h^ uf@Wp~<5j>{-]cK'Xto(hw?G%fIq^D$.Tʳ?D*A, `6B$BB dr`)B(Ͱ*`/@4Qhp.U=pa( Aa!ڈbX#!H$ ɈQ"K5H1RT UH=r9\F;2G1Q= C7F dt1r=6Ыhڏ>C03l0.B8, c˱" VcϱwE 6wB aAHXLXNH $4 7 Q'"K&b21XH,#/{C7$C2'ITFnR#,4H#dk9, +ȅ3![ b@qS(RjJ4e2AURݨT5ZBRQ4u9̓IKhhitݕNWGw Ljg(gwLӋT071oUX**| J&*/Tު UUT^S}FU3S ԖUPSSg;goT?~YYLOCQ_ cx,!k u5&|v*=9C3J3WRf?qtN (~))4L1e\kXHQG6EYAJ'\'GgSSݧ M=:.kDwn^Loy}/TmG X $ <5qo</QC]@Caaᄑ.ȽJtq]zۯ6iܟ4)Y3sCQ? 0k߬~OCOg#/c/Wװwa>>r><72Y_7ȷOo_C#dz%gA[z|!?:eAAA!h쐭!ΑiP~aa~ 'W?pX15wCsDDDޛg1O9-J5*>.j<74?.fYXXIlK9.*6nl {/]py.,:@LN8A*%w% yg"/6шC\*NH*Mz쑼5y$3,幄'L Lݛ:v m2=:1qB!Mggfvˬen/kY- BTZ(*geWf͉9+̳ې7ᒶKW-X潬j9(xoʿܔĹdff-[n ڴ VE/(ۻCɾUUMfeI?m]Nmq#׹=TR+Gw- 6 U#pDy  :v{vg/jBFS[b[O>zG499?rCd&ˮ/~јѡ򗓿m|x31^VwwO| (hSЧc3- cHRMz%u0`:o_FIDATx쐽K[a_AG(th!(qPp(BC U3cBi 7cb4MyoK1YDz<;1P'ozjf_{BہnL m&"NzI+EReI])T#| s|vɷqrg˴LʀNiBκ2vCᩭ BXt;ƖI|Ft|- |/[%$gSQVQUi 0j{b w+UZ,!uD$t{(/K_|d] 1od1>+H/˟q|X5b(@H/˟_4``/*tr/03*M3222?aP 䌻߿~cdc ;`_P ؼ=L@Sϟ6(JA]wNHمK9??IÓmoH&;#G?~y5"$gZ"&/ ||9*deT T!oe'*Q5@M IENDB`CharacterManaJ/resources/icons/copy.png0000644000175000017500000000061112560206305020373 0ustar paulliupaulliuPNG  IHDRatEXtSoftwareAdobe ImageReadyqe<+IDATxڔD@r .@2TDm7JA-h]NWP?LX&gsNy'3uVqߟ++p00 yOןqh3emK˲LY'PEo-+Ik$!,y6Ms8e0e-]׷ra!i4*(_{0@UU<(8#A<$뎖>uIWv!g(gY.x>iE[;30 m )' DIENDB`CharacterManaJ/resources/icons/arrow_up.png0000644000175000017500000000614512560206305021267 0ustar paulliupaulliuPNG  IHDRa pHYs   OiCCPPhotoshop ICC profilexڝSgTS=BKKoR RB&*! J!QEEȠQ, !{kּ> H3Q5 B.@ $pd!s#~<<+"x M0B\t8K@zB@F&S`cbP-`'{[! eDh;VEX0fK9-0IWfH  0Q){`##xFW<+*x<$9E[-qWW.(I+6aa@.y24x6_-"bbϫp@t~,/;m%h^ uf@Wp~<5j>{-]cK'Xto(hw?G%fIq^D$.Tʳ?D*A, `6B$BB dr`)B(Ͱ*`/@4Qhp.U=pa( Aa!ڈbX#!H$ ɈQ"K5H1RT UH=r9\F;2G1Q= C7F dt1r=6Ыhڏ>C03l0.B8, c˱" VcϱwE 6wB aAHXLXNH $4 7 Q'"K&b21XH,#/{C7$C2'ITFnR#,4H#dk9, +ȅ3![ b@qS(RjJ4e2AURݨT5ZBRQ4u9̓IKhhitݕNWGw Ljg(gwLӋT071oUX**| J&*/Tު UUT^S}FU3S ԖUPSSg;goT?~YYLOCQ_ cx,!k u5&|v*=9C3J3WRf?qtN (~))4L1e\kXHQG6EYAJ'\'GgSSݧ M=:.kDwn^Loy}/TmG X $ <5qo</QC]@Caaᄑ.ȽJtq]zۯ6iܟ4)Y3sCQ? 0k߬~OCOg#/c/Wװwa>>r><72Y_7ȷOo_C#dz%gA[z|!?:eAAA!h쐭!ΑiP~aa~ 'W?pX15wCsDDDޛg1O9-J5*>.j<74?.fYXXIlK9.*6nl {/]py.,:@LN8A*%w% yg"/6шC\*NH*Mz쑼5y$3,幄'L Lݛ:v m2=:1qB!Mggfvˬen/kY- BTZ(*geWf͉9+̳ې7ᒶKW-X潬j9(xoʿܔĹdff-[n ڴ VE/(ۻCɾUUMfeI?m]Nmq#׹=TR+Gw- 6 U#pDy  :v{vg/jBFS[b[O>zG499?rCd&ˮ/~јѡ򗓿m|x31^VwwO| (hSЧc3- cHRMz%u0`:o_FIDATx=K[a&6~@"6iTit{KA.rEh!].ЭP! B-V`L?s9}t\V|;]lĈٻD4 >m;8}05J|4ugmG_WfTϵָZc\p|Wfn>_v>y!,7\ms@+ҹ̝,[{|cDQ``+e`T}-xH=LL==KxOH4xC&5/*??VO[[Z7+Z>xzi`sc %U~"ƥ XPUX!TςAq~VR"ORI P< 6}Z7IENDB`CharacterManaJ/resources/icons/config.png0000644000175000017500000000670512560206305020700 0ustar paulliupaulliuPNG  IHDRa pHYs   OiCCPPhotoshop ICC profilexڝSgTS=BKKoR RB&*! J!QEEȠQ, !{kּ> H3Q5 B.@ $pd!s#~<<+"x M0B\t8K@zB@F&S`cbP-`'{[! eDh;VEX0fK9-0IWfH  0Q){`##xFW<+*x<$9E[-qWW.(I+6aa@.y24x6_-"bbϫp@t~,/;m%h^ uf@Wp~<5j>{-]cK'Xto(hw?G%fIq^D$.Tʳ?D*A, `6B$BB dr`)B(Ͱ*`/@4Qhp.U=pa( Aa!ڈbX#!H$ ɈQ"K5H1RT UH=r9\F;2G1Q= C7F dt1r=6Ыhڏ>C03l0.B8, c˱" VcϱwE 6wB aAHXLXNH $4 7 Q'"K&b21XH,#/{C7$C2'ITFnR#,4H#dk9, +ȅ3![ b@qS(RjJ4e2AURݨT5ZBRQ4u9̓IKhhitݕNWGw Ljg(gwLӋT071oUX**| J&*/Tު UUT^S}FU3S ԖUPSSg;goT?~YYLOCQ_ cx,!k u5&|v*=9C3J3WRf?qtN (~))4L1e\kXHQG6EYAJ'\'GgSSݧ M=:.kDwn^Loy}/TmG X $ <5qo</QC]@Caaᄑ.ȽJtq]zۯ6iܟ4)Y3sCQ? 0k߬~OCOg#/c/Wװwa>>r><72Y_7ȷOo_C#dz%gA[z|!?:eAAA!h쐭!ΑiP~aa~ 'W?pX15wCsDDDޛg1O9-J5*>.j<74?.fYXXIlK9.*6nl {/]py.,:@LN8A*%w% yg"/6шC\*NH*Mz쑼5y$3,幄'L Lݛ:v m2=:1qB!Mggfvˬen/kY- BTZ(*geWf͉9+̳ې7ᒶKW-X潬j9(xoʿܔĹdff-[n ڴ VE/(ۻCɾUUMfeI?m]Nmq#׹=TR+Gw- 6 U#pDy  :v{vg/jBFS[b[O>zG499?rCd&ˮ/~јѡ򗓿m|x31^VwwO| (hSЧc3- cHRMz%u0`:o_FIDATxڌ]hUGϽsgg6d15lk?-hG V "Ƣ`A}|QQ**$" ?؇ZSB+IKILMvwvfTPyS JC*x!ڰOs<{< mG  @Eh@ݯ釵pE PlqOo-T@k}7l9R< āCĥ.nM-$1RbbzniBIqlNqpa\ Yۋ98Q+`5w?w3[n VYkG"=~XN@#YP JM;1yd2OE)ݽptޞ#pW f`1Lde2mҍaZ EcV`vjB)lR\鮁+aM{\N-  hOoP]dfoY> wz_yql!L$""gIDDS"XK^)G[m;d:?>Es3ke/߆ޟXNZ0MjBCCg%[W%1Qy?ZR]vl5"Q3egm<枽~ȿ6yQT0̗SrPg?߷|!;7 H3Q5 B.@ $pd!s#~<<+"x M0B\t8K@zB@F&S`cbP-`'{[! eDh;VEX0fK9-0IWfH  0Q){`##xFW<+*x<$9E[-qWW.(I+6aa@.y24x6_-"bbϫp@t~,/;m%h^ uf@Wp~<5j>{-]cK'Xto(hw?G%fIq^D$.Tʳ?D*A, `6B$BB dr`)B(Ͱ*`/@4Qhp.U=pa( Aa!ڈbX#!H$ ɈQ"K5H1RT UH=r9\F;2G1Q= C7F dt1r=6Ыhڏ>C03l0.B8, c˱" VcϱwE 6wB aAHXLXNH $4 7 Q'"K&b21XH,#/{C7$C2'ITFnR#,4H#dk9, +ȅ3![ b@qS(RjJ4e2AURݨT5ZBRQ4u9̓IKhhitݕNWGw Ljg(gwLӋT071oUX**| J&*/Tު UUT^S}FU3S ԖUPSSg;goT?~YYLOCQ_ cx,!k u5&|v*=9C3J3WRf?qtN (~))4L1e\kXHQG6EYAJ'\'GgSSݧ M=:.kDwn^Loy}/TmG X $ <5qo</QC]@Caaᄑ.ȽJtq]zۯ6iܟ4)Y3sCQ? 0k߬~OCOg#/c/Wװwa>>r><72Y_7ȷOo_C#dz%gA[z|!?:eAAA!h쐭!ΑiP~aa~ 'W?pX15wCsDDDޛg1O9-J5*>.j<74?.fYXXIlK9.*6nl {/]py.,:@LN8A*%w% yg"/6шC\*NH*Mz쑼5y$3,幄'L Lݛ:v m2=:1qB!Mggfvˬen/kY- BTZ(*geWf͉9+̳ې7ᒶKW-X潬j9(xoʿܔĹdff-[n ڴ VE/(ۻCɾUUMfeI?m]Nmq#׹=TR+Gw- 6 U#pDy  :v{vg/jBFS[b[O>zG499?rCd&ˮ/~јѡ򗓿m|x31^VwwO| (hSЧc3- cHRMz%u0`:o_F LIDATxڌK\Wuu媮nw]b8zxD?JI^T$q,ւus<ZIBߧ[SY dd %- Qq$>)GiRUn%F)F $+"њBz ]+{(18)h EaQJaH!XZG+)flsդ5N+>EN*v!`9 Džd9cck~R0'f'.sXTuvZ<.#"z,/t)n!yaqSX+PPAb8ORDRǐF#= dyg|]e\:RS e z4ARdEAH!H)OfXO;!`$e}NpRBI$N:'@k͙Fn!$Kɲ  4{~ŋ}Nrc4XYY!N)xAH1ʭeXÀ,(M9s$i-I(|yJkλf4ۼ7IҴi> 8:>aIzvw?aZ-`{;,Zx`p Oi5iֻw6;<~EXqwtq9µRO;I7O#j`eG=H MGGG9 )%IEt[m"VS 4RF1NM,\trN )beDQ箳swm58H+%J)TՈ{]&ʵO^ }I `w]~ܲH')i1gB9RBe͗WVssTҋ$^ں0g_GOdqiFlc4'ܸ蹋qe]ZB(X_Zgvo">yͭMut]K}_>׹qG;l7_re!$"8n޹ɍgnp29a>sx3ݝ',7`>t[_BV#/l!`zB߾%~?$4M g(PJ_n~+\y 8S@vs/<${Y)U9rp^kwhrk8!Ilaq!m,9te71DNU)ٸÝز?Ya3BPd֖ߥ*ijʗ{.v2j H3Q5 B.@ $pd!s#~<<+"x M0B\t8K@zB@F&S`cbP-`'{[! eDh;VEX0fK9-0IWfH  0Q){`##xFW<+*x<$9E[-qWW.(I+6aa@.y24x6_-"bbϫp@t~,/;m%h^ uf@Wp~<5j>{-]cK'Xto(hw?G%fIq^D$.Tʳ?D*A, `6B$BB dr`)B(Ͱ*`/@4Qhp.U=pa( Aa!ڈbX#!H$ ɈQ"K5H1RT UH=r9\F;2G1Q= C7F dt1r=6Ыhڏ>C03l0.B8, c˱" VcϱwE 6wB aAHXLXNH $4 7 Q'"K&b21XH,#/{C7$C2'ITFnR#,4H#dk9, +ȅ3![ b@qS(RjJ4e2AURݨT5ZBRQ4u9̓IKhhitݕNWGw Ljg(gwLӋT071oUX**| J&*/Tު UUT^S}FU3S ԖUPSSg;goT?~YYLOCQ_ cx,!k u5&|v*=9C3J3WRf?qtN (~))4L1e\kXHQG6EYAJ'\'GgSSݧ M=:.kDwn^Loy}/TmG X $ <5qo</QC]@Caaᄑ.ȽJtq]zۯ6iܟ4)Y3sCQ? 0k߬~OCOg#/c/Wװwa>>r><72Y_7ȷOo_C#dz%gA[z|!?:eAAA!h쐭!ΑiP~aa~ 'W?pX15wCsDDDޛg1O9-J5*>.j<74?.fYXXIlK9.*6nl {/]py.,:@LN8A*%w% yg"/6шC\*NH*Mz쑼5y$3,幄'L Lݛ:v m2=:1qB!Mggfvˬen/kY- BTZ(*geWf͉9+̳ې7ᒶKW-X潬j9(xoʿܔĹdff-[n ڴ VE/(ۻCɾUUMfeI?m]Nmq#׹=TR+Gw- 6 U#pDy  :v{vg/jBFS[b[O>zG499?rCd&ˮ/~јѡ򗓿m|x31^VwwO| (hSЧc3- cHRMz%u0`:o_F~IDATxڔ=eooo1Q%^ IبDNAENV4L"&mrWID j^BvoߏYT33]v<]ȜÊ +;JfW "W>kD"p6l{~ !v34ZGL#:C\tuMp ٱ 4l 0b0A όfINFp1-PCcyM;B4g8_n Fɓ@ɐHqfJ M6Mə[nNC0ԊkF4|jE 5#9M0> d8ȡI@ ,m[IMр8>R7YI;,vt~3aa+jk\r){in`{n H3Q5 B.@ $pd!s#~<<+"x M0B\t8K@zB@F&S`cbP-`'{[! eDh;VEX0fK9-0IWfH  0Q){`##xFW<+*x<$9E[-qWW.(I+6aa@.y24x6_-"bbϫp@t~,/;m%h^ uf@Wp~<5j>{-]cK'Xto(hw?G%fIq^D$.Tʳ?D*A, `6B$BB dr`)B(Ͱ*`/@4Qhp.U=pa( Aa!ڈbX#!H$ ɈQ"K5H1RT UH=r9\F;2G1Q= C7F dt1r=6Ыhڏ>C03l0.B8, c˱" VcϱwE 6wB aAHXLXNH $4 7 Q'"K&b21XH,#/{C7$C2'ITFnR#,4H#dk9, +ȅ3![ b@qS(RjJ4e2AURݨT5ZBRQ4u9̓IKhhitݕNWGw Ljg(gwLӋT071oUX**| J&*/Tު UUT^S}FU3S ԖUPSSg;goT?~YYLOCQ_ cx,!k u5&|v*=9C3J3WRf?qtN (~))4L1e\kXHQG6EYAJ'\'GgSSݧ M=:.kDwn^Loy}/TmG X $ <5qo</QC]@Caaᄑ.ȽJtq]zۯ6iܟ4)Y3sCQ? 0k߬~OCOg#/c/Wװwa>>r><72Y_7ȷOo_C#dz%gA[z|!?:eAAA!h쐭!ΑiP~aa~ 'W?pX15wCsDDDޛg1O9-J5*>.j<74?.fYXXIlK9.*6nl {/]py.,:@LN8A*%w% yg"/6шC\*NH*Mz쑼5y$3,幄'L Lݛ:v m2=:1qB!Mggfvˬen/kY- BTZ(*geWf͉9+̳ې7ᒶKW-X潬j9(xoʿܔĹdff-[n ڴ VE/(ۻCɾUUMfeI?m]Nmq#׹=TR+Gw- 6 U#pDy  :v{vg/jBFS[b[O>zG499?rCd&ˮ/~јѡ򗓿m|x31^VwwO| (hSЧc3- cHRMz%u0`:o_FIDATxSKBq=>#hi#6 $jp #|D zM-YRBT5Ԩ}<(;pss5 !Nw`n0y1ܑBn>bu!petP& b:N/>H%@=IOLs;`粜<$kmqՂp, bb#d 3n <ğl[cu܇6H&s NAKskD|wVghi$}- DCIh4h/&NX* H3Q5 B.@ $pd!s#~<<+"x M0B\t8K@zB@F&S`cbP-`'{[! eDh;VEX0fK9-0IWfH  0Q){`##xFW<+*x<$9E[-qWW.(I+6aa@.y24x6_-"bbϫp@t~,/;m%h^ uf@Wp~<5j>{-]cK'Xto(hw?G%fIq^D$.Tʳ?D*A, `6B$BB dr`)B(Ͱ*`/@4Qhp.U=pa( Aa!ڈbX#!H$ ɈQ"K5H1RT UH=r9\F;2G1Q= C7F dt1r=6Ыhڏ>C03l0.B8, c˱" VcϱwE 6wB aAHXLXNH $4 7 Q'"K&b21XH,#/{C7$C2'ITFnR#,4H#dk9, +ȅ3![ b@qS(RjJ4e2AURݨT5ZBRQ4u9̓IKhhitݕNWGw Ljg(gwLӋT071oUX**| J&*/Tު UUT^S}FU3S ԖUPSSg;goT?~YYLOCQ_ cx,!k u5&|v*=9C3J3WRf?qtN (~))4L1e\kXHQG6EYAJ'\'GgSSݧ M=:.kDwn^Loy}/TmG X $ <5qo</QC]@Caaᄑ.ȽJtq]zۯ6iܟ4)Y3sCQ? 0k߬~OCOg#/c/Wװwa>>r><72Y_7ȷOo_C#dz%gA[z|!?:eAAA!h쐭!ΑiP~aa~ 'W?pX15wCsDDDޛg1O9-J5*>.j<74?.fYXXIlK9.*6nl {/]py.,:@LN8A*%w% yg"/6шC\*NH*Mz쑼5y$3,幄'L Lݛ:v m2=:1qB!Mggfvˬen/kY- BTZ(*geWf͉9+̳ې7ᒶKW-X潬j9(xoʿܔĹdff-[n ڴ VE/(ۻCɾUUMfeI?m]Nmq#׹=TR+Gw- 6 U#pDy  :v{vg/jBFS[b[O>zG499?rCd&ˮ/~јѡ򗓿m|x31^VwwO| (hSЧc3- cHRMz%u0`:o_FIDATx;K#QI6f2hBBlN邐EP!XHXtAB "6Z,VֲZ lb$3s7謏U,{NjZxOx8PjWKo~՘vM{?_ V3ܮCuUN9bYL `Y_X[ ݱBMO\oNlE`A6B"a'OGr͐QN:fȵo#X+#y׳{?5x2'|J8%`6 i PC* ynĦ 64|EWbW 41\> X X DArl,Ekg{Ȋ?-onIENDB`CharacterManaJ/resources/icons/color.png0000644000175000017500000000056312560206305020545 0ustar paulliupaulliuPNG  IHDRatEXtSoftwareAdobe ImageReadyqe<IDATxb\p4L,y߿ 5 @Ygx06t;H 6X~0D˖፜6 1pss3,Z?r @6CtDÿ𺈉aҥ.Ix /V/&L.@0uLa@j@j߿ "* \F X^.`&P6<}0&AR`I]+V˂)&7@PߵWIENDB`CharacterManaJ/resources/icons/sort.png0000644000175000017500000000062712560206305020417 0ustar paulliupaulliuPNG  IHDRatEXtSoftwareAdobe ImageReadyqe<9IDATxڔSˎP -*0`\pc`+~!uNp&Ҟ֐R(D\l7Ҵy'8.AAer=#buܸ\.tvt^&(IulFu͈xɡ%ڶ%d GkB /YEAeY< 3p d !}ߧ5SUA;S1ég@-7zGc6Mq&mWB-9Np7aJ?dY_NVE7sֱ<'.W +>IENDB`CharacterManaJ/resources/icons/arrow_down2.png0000644000175000017500000000613312560206305021671 0ustar paulliupaulliuPNG  IHDRa pHYs   OiCCPPhotoshop ICC profilexڝSgTS=BKKoR RB&*! J!QEEȠQ, !{kּ> H3Q5 B.@ $pd!s#~<<+"x M0B\t8K@zB@F&S`cbP-`'{[! eDh;VEX0fK9-0IWfH  0Q){`##xFW<+*x<$9E[-qWW.(I+6aa@.y24x6_-"bbϫp@t~,/;m%h^ uf@Wp~<5j>{-]cK'Xto(hw?G%fIq^D$.Tʳ?D*A, `6B$BB dr`)B(Ͱ*`/@4Qhp.U=pa( Aa!ڈbX#!H$ ɈQ"K5H1RT UH=r9\F;2G1Q= C7F dt1r=6Ыhڏ>C03l0.B8, c˱" VcϱwE 6wB aAHXLXNH $4 7 Q'"K&b21XH,#/{C7$C2'ITFnR#,4H#dk9, +ȅ3![ b@qS(RjJ4e2AURݨT5ZBRQ4u9̓IKhhitݕNWGw Ljg(gwLӋT071oUX**| J&*/Tު UUT^S}FU3S ԖUPSSg;goT?~YYLOCQ_ cx,!k u5&|v*=9C3J3WRf?qtN (~))4L1e\kXHQG6EYAJ'\'GgSSݧ M=:.kDwn^Loy}/TmG X $ <5qo</QC]@Caaᄑ.ȽJtq]zۯ6iܟ4)Y3sCQ? 0k߬~OCOg#/c/Wװwa>>r><72Y_7ȷOo_C#dz%gA[z|!?:eAAA!h쐭!ΑiP~aa~ 'W?pX15wCsDDDޛg1O9-J5*>.j<74?.fYXXIlK9.*6nl {/]py.,:@LN8A*%w% yg"/6шC\*NH*Mz쑼5y$3,幄'L Lݛ:v m2=:1qB!Mggfvˬen/kY- BTZ(*geWf͉9+̳ې7ᒶKW-X潬j9(xoʿܔĹdff-[n ڴ VE/(ۻCɾUUMfeI?m]Nmq#׹=TR+Gw- 6 U#pDy  :v{vg/jBFS[b[O>zG499?rCd&ˮ/~јѡ򗓿m|x31^VwwO| (hSЧc3- cHRMz%u0`:o_FIDATx;,Cq?PA, lH (ڨ4LFF`&6qS%M޺}{jka%g/|k}zEbId 2)^sWea(C@N@(p\tÝtgͮ3ۅ 9g?9}z<{S8,i!v]~Da_ ip{,3$k+mN>Jv](ޱ/ ǫvT =s;Y2Qka./+0^>n6R_cx6 t$ ,IENDB`CharacterManaJ/resources/icons/favorite.png0000644000175000017500000000124612560206305021245 0ustar paulliupaulliuPNG  IHDRatEXtSoftwareAdobe ImageReadyqe<HIDATxڌSOhar`9K' js1%uƒP:A1jRYa\}s?sv9wغ|f~]Αy:'a{EvqUk}zbl8pM:p>RUetap5 TSk[m;H<=n%OqA/_ A+.4Bw+Ň~ kȧ=8楄þn:@SD`Y8J!%>j:M]/_WmJ Bl$0ҋ39dc7D*4Ab{ :{ ۭO\wNE8tDer!S@l:JCCŷ S|uFzRK0IMafHp(W{QV"Ѣ8.*(PrC*B& ipf>~ON ,7+B۵l_޼kk~+Gz1KF4qguG5ظaL\cI@/w[w"K$9%IENDB`CharacterManaJ/resources/icons/right2.png0000644000175000017500000000651612560206305020632 0ustar paulliupaulliuPNG  IHDRa pHYs   OiCCPPhotoshop ICC profilexڝSgTS=BKKoR RB&*! J!QEEȠQ, !{kּ> H3Q5 B.@ $pd!s#~<<+"x M0B\t8K@zB@F&S`cbP-`'{[! eDh;VEX0fK9-0IWfH  0Q){`##xFW<+*x<$9E[-qWW.(I+6aa@.y24x6_-"bbϫp@t~,/;m%h^ uf@Wp~<5j>{-]cK'Xto(hw?G%fIq^D$.Tʳ?D*A, `6B$BB dr`)B(Ͱ*`/@4Qhp.U=pa( Aa!ڈbX#!H$ ɈQ"K5H1RT UH=r9\F;2G1Q= C7F dt1r=6Ыhڏ>C03l0.B8, c˱" VcϱwE 6wB aAHXLXNH $4 7 Q'"K&b21XH,#/{C7$C2'ITFnR#,4H#dk9, +ȅ3![ b@qS(RjJ4e2AURݨT5ZBRQ4u9̓IKhhitݕNWGw Ljg(gwLӋT071oUX**| J&*/Tު UUT^S}FU3S ԖUPSSg;goT?~YYLOCQ_ cx,!k u5&|v*=9C3J3WRf?qtN (~))4L1e\kXHQG6EYAJ'\'GgSSݧ M=:.kDwn^Loy}/TmG X $ <5qo</QC]@Caaᄑ.ȽJtq]zۯ6iܟ4)Y3sCQ? 0k߬~OCOg#/c/Wװwa>>r><72Y_7ȷOo_C#dz%gA[z|!?:eAAA!h쐭!ΑiP~aa~ 'W?pX15wCsDDDޛg1O9-J5*>.j<74?.fYXXIlK9.*6nl {/]py.,:@LN8A*%w% yg"/6шC\*NH*Mz쑼5y$3,幄'L Lݛ:v m2=:1qB!Mggfvˬen/kY- BTZ(*geWf͉9+̳ې7ᒶKW-X潬j9(xoʿܔĹdff-[n ڴ VE/(ۻCɾUUMfeI?m]Nmq#׹=TR+Gw- 6 U#pDy  :v{vg/jBFS[b[O>zG499?rCd&ˮ/~јѡ򗓿m|x31^VwwO| (hSЧc3- cHRMz%u0`:o_FyIDATxڤ;Vgs]oKRd$ nP$+Q@"b)*xiV\rYQ ^Iha {axG1hjOl ODfi @CoFgbw p ;]#% .JS禡>2ΣDLrI8t:%h C'@9.!a9 bw;Z- >k?U30X)1i2#S{ه p,I 2_LjEBúgH;q$/3ܝO} IENDB`CharacterManaJ/resources/icons/pin-icon2.png0000644000175000017500000000154612560206305021227 0ustar paulliupaulliuPNG  IHDR sRGBbKGD pHYs  tIMEIDAT8MoUUߵ9{HoRRjF!Q6ƁSf!FXc#ȀD2 AZ,n{=콗#F:t;\d>hKs?8qth} 6dAtn߰1V:g 2O]3LPʂC/_¬qq SRhf=[5%J"*xXㄹCVqӥ~cz[iR{l~?"|x|+~ ̓Gfҗ 10vߚan'+씾ݘS-FGD@X߼qvV} n^Z P+JҲ Ւ;; Wľi($ $ Ѭv'J% !8K˓ ani˅l` v%|=#e?>Ș\{l * 0( '7-n&|g뷝}K?Msɉy?윎CScϾ@QxgcFJQiȸyDXk~~h685hny07ػQf%7:tE'. 1P֘)p«Oy3'׏~cgx^і\;Jv iދͧZHz@Ȁ!O݄[ +k%ӢIENDB`CharacterManaJ/resources/icons/color2.png0000644000175000017500000000576712560206305020642 0ustar paulliupaulliuPNG  IHDRa pHYs   OiCCPPhotoshop ICC profilexڝSgTS=BKKoR RB&*! J!QEEȠQ, !{kּ> H3Q5 B.@ $pd!s#~<<+"x M0B\t8K@zB@F&S`cbP-`'{[! eDh;VEX0fK9-0IWfH  0Q){`##xFW<+*x<$9E[-qWW.(I+6aa@.y24x6_-"bbϫp@t~,/;m%h^ uf@Wp~<5j>{-]cK'Xto(hw?G%fIq^D$.Tʳ?D*A, `6B$BB dr`)B(Ͱ*`/@4Qhp.U=pa( Aa!ڈbX#!H$ ɈQ"K5H1RT UH=r9\F;2G1Q= C7F dt1r=6Ыhڏ>C03l0.B8, c˱" VcϱwE 6wB aAHXLXNH $4 7 Q'"K&b21XH,#/{C7$C2'ITFnR#,4H#dk9, +ȅ3![ b@qS(RjJ4e2AURݨT5ZBRQ4u9̓IKhhitݕNWGw Ljg(gwLӋT071oUX**| J&*/Tު UUT^S}FU3S ԖUPSSg;goT?~YYLOCQ_ cx,!k u5&|v*=9C3J3WRf?qtN (~))4L1e\kXHQG6EYAJ'\'GgSSݧ M=:.kDwn^Loy}/TmG X $ <5qo</QC]@Caaᄑ.ȽJtq]zۯ6iܟ4)Y3sCQ? 0k߬~OCOg#/c/Wװwa>>r><72Y_7ȷOo_C#dz%gA[z|!?:eAAA!h쐭!ΑiP~aa~ 'W?pX15wCsDDDޛg1O9-J5*>.j<74?.fYXXIlK9.*6nl {/]py.,:@LN8A*%w% yg"/6шC\*NH*Mz쑼5y$3,幄'L Lݛ:v m2=:1qB!Mggfvˬen/kY- BTZ(*geWf͉9+̳ې7ᒶKW-X潬j9(xoʿܔĹdff-[n ڴ VE/(ۻCɾUUMfeI?m]Nmq#׹=TR+Gw- 6 U#pDy  :v{vg/jBFS[b[O>zG499?rCd&ˮ/~јѡ򗓿m|x31^VwwO| (hSЧc3- cHRMz%u0`:o_F"IDATxڔJ@ώӥ X,`e#,N} H3Q5 B.@ $pd!s#~<<+"x M0B\t8K@zB@F&S`cbP-`'{[! eDh;VEX0fK9-0IWfH  0Q){`##xFW<+*x<$9E[-qWW.(I+6aa@.y24x6_-"bbϫp@t~,/;m%h^ uf@Wp~<5j>{-]cK'Xto(hw?G%fIq^D$.Tʳ?D*A, `6B$BB dr`)B(Ͱ*`/@4Qhp.U=pa( Aa!ڈbX#!H$ ɈQ"K5H1RT UH=r9\F;2G1Q= C7F dt1r=6Ыhڏ>C03l0.B8, c˱" VcϱwE 6wB aAHXLXNH $4 7 Q'"K&b21XH,#/{C7$C2'ITFnR#,4H#dk9, +ȅ3![ b@qS(RjJ4e2AURݨT5ZBRQ4u9̓IKhhitݕNWGw Ljg(gwLӋT071oUX**| J&*/Tު UUT^S}FU3S ԖUPSSg;goT?~YYLOCQ_ cx,!k u5&|v*=9C3J3WRf?qtN (~))4L1e\kXHQG6EYAJ'\'GgSSݧ M=:.kDwn^Loy}/TmG X $ <5qo</QC]@Caaᄑ.ȽJtq]zۯ6iܟ4)Y3sCQ? 0k߬~OCOg#/c/Wװwa>>r><72Y_7ȷOo_C#dz%gA[z|!?:eAAA!h쐭!ΑiP~aa~ 'W?pX15wCsDDDޛg1O9-J5*>.j<74?.fYXXIlK9.*6nl {/]py.,:@LN8A*%w% yg"/6шC\*NH*Mz쑼5y$3,幄'L Lݛ:v m2=:1qB!Mggfvˬen/kY- BTZ(*geWf͉9+̳ې7ᒶKW-X潬j9(xoʿܔĹdff-[n ڴ VE/(ۻCɾUUMfeI?m]Nmq#׹=TR+Gw- 6 U#pDy  :v{vg/jBFS[b[O>zG499?rCd&ˮ/~јѡ򗓿m|x31^VwwO| (hSЧc3- cHRMz%u0`:o_FIDATxڌ]hu?99b/u8Vi,4H&&H zAyh`$M%Zlvs BH{} _/? *$&x`&y9 g/ȇQ,WAm zZɞoc 84gT":TK%.7)b[\ {pft{<3~IT:˝Ht01"\ЇX bQ(,~638c+}?[TAfp)L_Od'psO#N2 1 -Cmvc85X5v.TƆk)wC8 @03\ 7Mōd]BRBr-0<4 ЊXV^n~V̪r! ѹhK?`৯ȒrrL.2CWUQ=yPUU_V.{[wvh>;Խڑ%V&zGp f7r'hN%=AS QaLCd IZRGЌbмFyT^J)G:: ;?[̥dya9 mog/ML/_[dv4\x蛫Pm6">]D[_"oߺ3?c=WۃkZD!gJ=e$cD3u; ?>ڤ[IENDB`CharacterManaJ/resources/icons/right.png0000644000175000017500000000652212560206305020545 0ustar paulliupaulliuPNG  IHDRa pHYs   OiCCPPhotoshop ICC profilexڝSgTS=BKKoR RB&*! J!QEEȠQ, !{kּ> H3Q5 B.@ $pd!s#~<<+"x M0B\t8K@zB@F&S`cbP-`'{[! eDh;VEX0fK9-0IWfH  0Q){`##xFW<+*x<$9E[-qWW.(I+6aa@.y24x6_-"bbϫp@t~,/;m%h^ uf@Wp~<5j>{-]cK'Xto(hw?G%fIq^D$.Tʳ?D*A, `6B$BB dr`)B(Ͱ*`/@4Qhp.U=pa( Aa!ڈbX#!H$ ɈQ"K5H1RT UH=r9\F;2G1Q= C7F dt1r=6Ыhڏ>C03l0.B8, c˱" VcϱwE 6wB aAHXLXNH $4 7 Q'"K&b21XH,#/{C7$C2'ITFnR#,4H#dk9, +ȅ3![ b@qS(RjJ4e2AURݨT5ZBRQ4u9̓IKhhitݕNWGw Ljg(gwLӋT071oUX**| J&*/Tު UUT^S}FU3S ԖUPSSg;goT?~YYLOCQ_ cx,!k u5&|v*=9C3J3WRf?qtN (~))4L1e\kXHQG6EYAJ'\'GgSSݧ M=:.kDwn^Loy}/TmG X $ <5qo</QC]@Caaᄑ.ȽJtq]zۯ6iܟ4)Y3sCQ? 0k߬~OCOg#/c/Wװwa>>r><72Y_7ȷOo_C#dz%gA[z|!?:eAAA!h쐭!ΑiP~aa~ 'W?pX15wCsDDDޛg1O9-J5*>.j<74?.fYXXIlK9.*6nl {/]py.,:@LN8A*%w% yg"/6шC\*NH*Mz쑼5y$3,幄'L Lݛ:v m2=:1qB!Mggfvˬen/kY- BTZ(*geWf͉9+̳ې7ᒶKW-X潬j9(xoʿܔĹdff-[n ڴ VE/(ۻCɾUUMfeI?m]Nmq#׹=TR+Gw- 6 U#pDy  :v{vg/jBFS[b[O>zG499?rCd&ˮ/~јѡ򗓿m|x31^VwwO| (hSЧc3- cHRMz%u0`:o_F}IDATxڔ;hVi;PVo,"V֢66ł6bŢM`XmZzQ`-.E ҷax0T` SOŘf%Yj %.vJ~*< K0s+6])̘t;#CO}5zm¬o#`252C pŌI8&* 7R8>'7o F*Y1v(1y\Z,&8@j@\LGUbBIg`z\Q1`_6_O[Fl #RHH2$92ge=0hcry-%%$$'4%NO'C.?kBqAb=RKS?-R E)$\~ 33hUpSv0ո׉aB%fxI 9W,L!`p/ŭ^Śkbpp#-8^`NYZ~f໴t]ץKAgZ<ĉ&r4y=r93V{V߯b{5kIENDB`CharacterManaJ/resources/icons/save.png0000644000175000017500000000635112560206305020366 0ustar paulliupaulliuPNG  IHDRa pHYs   OiCCPPhotoshop ICC profilexڝSgTS=BKKoR RB&*! J!QEEȠQ, !{kּ> H3Q5 B.@ $pd!s#~<<+"x M0B\t8K@zB@F&S`cbP-`'{[! eDh;VEX0fK9-0IWfH  0Q){`##xFW<+*x<$9E[-qWW.(I+6aa@.y24x6_-"bbϫp@t~,/;m%h^ uf@Wp~<5j>{-]cK'Xto(hw?G%fIq^D$.Tʳ?D*A, `6B$BB dr`)B(Ͱ*`/@4Qhp.U=pa( Aa!ڈbX#!H$ ɈQ"K5H1RT UH=r9\F;2G1Q= C7F dt1r=6Ыhڏ>C03l0.B8, c˱" VcϱwE 6wB aAHXLXNH $4 7 Q'"K&b21XH,#/{C7$C2'ITFnR#,4H#dk9, +ȅ3![ b@qS(RjJ4e2AURݨT5ZBRQ4u9̓IKhhitݕNWGw Ljg(gwLӋT071oUX**| J&*/Tު UUT^S}FU3S ԖUPSSg;goT?~YYLOCQ_ cx,!k u5&|v*=9C3J3WRf?qtN (~))4L1e\kXHQG6EYAJ'\'GgSSݧ M=:.kDwn^Loy}/TmG X $ <5qo</QC]@Caaᄑ.ȽJtq]zۯ6iܟ4)Y3sCQ? 0k߬~OCOg#/c/Wװwa>>r><72Y_7ȷOo_C#dz%gA[z|!?:eAAA!h쐭!ΑiP~aa~ 'W?pX15wCsDDDޛg1O9-J5*>.j<74?.fYXXIlK9.*6nl {/]py.,:@LN8A*%w% yg"/6шC\*NH*Mz쑼5y$3,幄'L Lݛ:v m2=:1qB!Mggfvˬen/kY- BTZ(*geWf͉9+̳ې7ᒶKW-X潬j9(xoʿܔĹdff-[n ڴ VE/(ۻCɾUUMfeI?m]Nmq#׹=TR+Gw- 6 U#pDy  :v{vg/jBFS[b[O>zG499?rCd&ˮ/~јѡ򗓿m|x31^VwwO| (hSЧc3- cHRMz%u0`:o_FIDATxڌ=kQ{n6Kb -$iҋ?R m?@ Bj,$H|mV63;sϽbAS^^cu7!JeRI-Y{pZN`v6.@ Jat -Le0`G XknA0= DcSt dfwAf"lӈ UqhWBpREB=zE|WHgpTnfa9 >+42Eyخ  /K$M'JiO"Bb @@ D*+rL R V7[x/Pgy<ǷuYEP}b4c?]~xn5mj%M;hef #?.Zvwz]~G'zj5JSxp]ՎF:mj#<;91$+RV{axzh KY҆ϷhYzmy$&QaIENDB`CharacterManaJ/resources/logging.properties0000644000175000017500000000040112560206305021341 0ustar paulliupaulliu# for Debug #handlers=java.util.logging.ConsoleHandler,charactermanaj.util.ApplicationLogHandler #java.util.logging.ConsoleHandler.level=FINEST #.level=FINE handlers=charactermanaj.util.ApplicationLogHandler .level=INFO charactermanaj.level=INFO CharacterManaJ/resources/schema/0000755000175000017500000000000012560206305017042 5ustar paulliupaulliuCharacterManaJ/resources/schema/partsset.xsd0000644000175000017500000000103712560206305021430 0ustar paulliupaulliu CharacterManaJ/resources/schema/parts-definition.xml0000644000175000017500000000232312560206305023043 0ustar paulliupaulliu 緋龍華 麒麟 K.Hmix 1st Edition http://khmix.sakura.ne.jp/ http://khmix.sakura.ne.jp/file/character_02.08.zip Intake インテーク 緋龍華 麒麟 K.Hmix 1st Edition http://khmix.sakura.ne.jp/ Wave ウェーブ CharacterManaJ/resources/schema/0.8/0000755000175000017500000000000012560206305017347 5ustar paulliupaulliuCharacterManaJ/resources/schema/0.8/partsset.xsd0000644000175000017500000000100412560206305021727 0ustar paulliupaulliu CharacterManaJ/resources/schema/0.8/character.xsd0000644000175000017500000003575312560206305022040 0ustar paulliupaulliu キャラクターデータの定義 キャラクターデータ名。該当するlangがなければ最初の定義をデフォルト値とする。 備考 作者名 説明文 イメージのサイズ(幅と高さ) 雑多なプロパティのコレクション 雑多なプロパティ カテゴリの定義リスト、出現順で画面に表示される。 カテゴリの定義 表示するパーツの行数(初期値) カテゴリの表示名、該当するlangがない場合は最初のものをデフォルトとする。 パーツを構成するレイヤーの定義リスト パーツを構成するレイヤーの定義 レイヤーの表示名、該当するlangがない場合は最初のものをデフォルトとする。 パーツ全体でレイヤーを重ね合わせる順序。 色設定を連動させるグループの指定(省略可) カラーグループID 初期状態で連動させるか? このレイヤーの画像を格納しているディレクトリ名 レイヤーの識別子 カテゴリーの識別子 このカテゴリで複数のパーツが選択可能であるか? カラーグループの定義リスト カラーグループの定義 カラーグループの表示名。該当するlangがない場合は最初をデフォルトとする。 カラーグループの識別子 定義済みパーツ組み合わせ例(プリセット)の定義リスト、定義順に表示される。 このXMLのバージョン番号、1.0固定。 CharacterManaJ/resources/schema/0.8/character_inc.xsd0000644000175000017500000002765312560206305022671 0ustar paulliupaulliu 空文字を許可しないトークンの定義 RGB変換パラメータ オフセット 倍率 ガンマ HSB変換パラメータ RGB置換タイプ RGB置換タイプ 淡色化(0でグレー化、1で淡色化なし) 定義済みパーツ組み合わせ例(プリセット)の定義リスト、定義順に表示される。 定義済みパーツ組み合わせ例(プリセット)の定義 表示名、該当するlangがない場合は最初をデフォルトとする。 背景色 アフィン変換用パラメータ、4または6つの要素からなるマトリックス カテゴリごとのパーツ定義、パーツが空の場合は該当カテゴリは選択なし パーツ 色定義(オプション) カラーグループ カラーグループの同期を行う RGB変換 HSB変換 RGB置換 レイヤー識別子 パーツ名 カテゴリの識別子 定義済みパーツ組み合わせ例(プリセット)の識別子 デフォルトのプリセットを示す識別子 CharacterManaJ/resources/schema/parts-definition.xsd0000644000175000017500000001166412560206305023051 0ustar paulliupaulliu 空文字を許可しないトークンの定義 パーツリストの定義 パーツの作者 パーツのダウンロードURL パーツの定義 パーツのローカライズ名 パーツ名(ファイルのネームボディ) カテゴリ、省略時は任意のカテゴリ パーツのバージョン 最終更新日時(代表) キャラクターデータディレクトリのURI CharacterManaJ/resources/schema/character.xsd0000644000175000017500000004341112560206305021521 0ustar paulliupaulliu キャラクターデータの定義 キャラクターデータ名。該当するlangがなければ最初の定義をデフォルト値とする。 備考 作者名 説明文 イメージのサイズ(幅と高さ) 雑多なプロパティのコレクション 雑多なプロパティ カテゴリの定義リスト、出現順で画面に表示される。 カテゴリの定義 表示するパーツの行数(初期値) カテゴリの表示名、該当するlangがない場合は最初のものをデフォルトとする。 パーツを構成するレイヤーの定義リスト パーツを構成するレイヤーの定義 レイヤーの表示名、該当するlangがない場合は最初のものをデフォルトとする。 パーツ全体でレイヤーを重ね合わせる順序。 色設定を連動させるグループの指定(省略可) カラーグループID 初期状態で連動させるか? このレイヤーの画像を格納しているディレクトリ名 カラーモデル名 レイヤーの識別子 カテゴリーの識別子 このカテゴリで複数のパーツが選択可能であるか? カラーグループの定義リスト カラーグループの定義 カラーグループの表示名。該当するlangがない場合は最初をデフォルトとする。 カラーグループの識別子 お薦めリンクのリスト お勧めリンク 説明文 言語 URL 言語 定義済みパーツ組み合わせ例(プリセット)の定義リスト、定義順に表示される。 このXMLのバージョン番号、1.0固定。 CharacterManaJ/resources/schema/character_inc.xsd0000644000175000017500000003074512560206305022360 0ustar paulliupaulliu 空文字を許可しないトークンの定義 RGB変換パラメータ オフセット 倍率 ガンマ HSB変換パラメータ RGB置換タイプ RGB置換タイプ 淡色化(0でグレー化、1で淡色化なし) 定義済みパーツ組み合わせ例(プリセット)の定義リスト、定義順に表示される。 定義済みパーツ組み合わせ例(プリセット)の定義 表示名、該当するlangがない場合は最初をデフォルトとする。 背景色 アフィン変換用パラメータ、4または6つの要素からなるマトリックス カテゴリごとのパーツ定義、パーツが空の場合は該当カテゴリは選択なし パーツ 色定義(オプション) カラーグループ カラーグループの同期を行う RGB変換 HSB変換 RGB置換 レイヤー識別子 パーツ名 カテゴリの識別子 定義済みパーツ組み合わせ例(プリセット)の識別子 デフォルトのプリセットを示す識別子 キャラクターデータディレクトリのURI CharacterManaJ/resources/schema/xml.xsd0000644000175000017500000002164312560206305020370 0ustar paulliupaulliu

lang (as an attribute name)

denotes an attribute whose value is a language code for the natural language of the content of any element; its value is inherited. This name is reserved by virtue of its definition in the XML specification.

Notes

Attempting to install the relevant ISO 2- and 3-letter codes as the enumerated possible values is probably never going to be a realistic possibility.

See BCP 47 at http://www.rfc-editor.org/rfc/bcp/bcp47.txt and the IANA language subtag registry at http://www.iana.org/assignments/language-subtag-registry for further information.

The union allows for the 'un-declaration' of xml:lang with the empty string.

space (as an attribute name)

denotes an attribute whose value is a keyword indicating what whitespace processing discipline is intended for the content of the element; its value is inherited. This name is reserved by virtue of its definition in the XML specification.

base (as an attribute name)

denotes an attribute whose value provides a URI to be used as the base for interpreting any relative URIs in the scope of the element on which it appears; its value is inherited. This name is reserved by virtue of its definition in the XML Base specification.

See http://www.w3.org/TR/xmlbase/ for information about this attribute.

id (as an attribute name)

denotes an attribute whose value should be interpreted as if declared to be of type ID. This name is reserved by virtue of its definition in the xml:id specification.

See http://www.w3.org/TR/xml-id/ for information about this attribute.

Father (in any context at all)

denotes Jon Bosak, the chair of the original XML Working Group. This name is reserved by the following decision of the W3C XML Plenary and XML Coordination groups:

In appreciation for his vision, leadership and dedication the W3C XML Plenary on this 10th day of February, 2000, reserves for Jon Bosak in perpetuity the XML name "xml:Father".

About this schema document

This schema defines attributes and an attribute group suitable for use by schemas wishing to allow xml:base, xml:lang, xml:space or xml:id attributes on elements they define.

To enable this, such a schema must import this schema for the XML namespace, e.g. as follows:

          <schema . . .>
           . . .
           <import namespace="http://www.w3.org/XML/1998/namespace"
                      schemaLocation="http://www.w3.org/2001/xml.xsd"/>
     

or

           <import namespace="http://www.w3.org/XML/1998/namespace"
                      schemaLocation="http://www.w3.org/2009/01/xml.xsd"/>
     

Subsequently, qualified reference to any of the attributes or the group defined below will have the desired effect, e.g.

          <type . . .>
           . . .
           <attributeGroup ref="xml:specialAttrs"/>
     

will define a type which will schema-validate an instance element with any of those attributes.

Versioning policy for this schema document

In keeping with the XML Schema WG's standard versioning policy, this schema document will persist at http://www.w3.org/2009/01/xml.xsd.

At the date of issue it can also be found at http://www.w3.org/2001/xml.xsd.

The schema document at that URI may however change in the future, in order to remain compatible with the latest version of XML Schema itself, or with the XML namespace itself. In other words, if the XML Schema or XML namespaces change, the version of this document at http://www.w3.org/2001/xml.xsd will change accordingly; the version at http://www.w3.org/2009/01/xml.xsd will not change.

Previous dated (and unchanging) versions of this schema document are at:

CharacterManaJ/resources/appConfig_ja.xml0000644000175000017500000000040112560206305020677 0ustar paulliupaulliu Lucida Grande, Meiryo, MS UI Gothic, MS Gothic CharacterManaJ/resources/template/0000755000175000017500000000000012560206305017415 5ustar paulliupaulliuCharacterManaJ/resources/template/characterDataTemplates.xml0000644000175000017500000000051112560206305024541 0ustar paulliupaulliu character3.xml,character2.xml CharacterNantokaki Ver2.x Compatible CharacterNantokaki Ver3 Compatible CharacterManaJ/resources/template/characterDataTemplates_zh.xml0000644000175000017500000000053612560206305025251 0ustar paulliupaulliu character3.xml,character2.xml キャラクターなんとか機 ver2.x 互換 キャラクターなんとか機 ver3 互換 CharacterManaJ/resources/template/character3.xml0000644000175000017500000004010412560206305022155 0ustar paulliupaulliu Default(v3) デフォルト(v3) Unknown 名無し 无名 無名 300 400 true 6 Hair - Front 髪型 - 手前 发型 - 这边 髮型 - 這邊 Variable 可変色 可变颜色 可變顏色 13 hair_front HSB Accessory アクセサリ 饰品 飾品 14 hair_front_accessory HSB 6 Hair - Back 髪型 - 後ろ 发型 - 背后 髮型 - 背後 Variable 可変色 可变颜色 可變顏色 2 hair_back HSB Accessory アクセサリ 饰品 飾品 3 hair_back_accessory HSB 6 Head Head 10 head HSB 6 Expression 表情 脸色 臉色 Face 表情 脸色 臉色 15 face_front HSB Accessory アクセサリ 饰品 飾品 12 face_back HSB Hair 髪色 头发颜色 頭髮顏色 16 face_haircolor HSB 6 Eyes Eye 17 eye HSB Variable 可変色 可变颜色 可變顏色 18 eye_color HSB 6 Body 身体 身体 身體 Variable 可変色 可变颜色 可變顏色 7 body_front_color HSB Clothes ドレス 衣服 衣服 6 body_front HSB Skin 皮肤 皮膚 4 body_back HSB Accessory アクセサリ 饰品 飾品 8 body_front_accessory HSB 10 Accessory アクセサリー 饰品 飾品 Top 最前面 最跟前 最跟前 19 accessory_front HSB Middle(R) 中間(R) 中间(右) 中間(右) 11 accessory_middle_front HSB Middle(L) 中間(L) 中间(左) 中間(左) 9 accessory_middle_back HSB Underwear アンダーウェア 内衣 內衣 5 accessory_underwear HSB Back 最背面 最后面 最後面 1 accessory_back HSB Hair 头发 頭髮 Eye 瞳孔 瞳孔 Skin 肤色 膚色 Dress 衣服 衣服 Originator (K.Hmix 1st Edition) (キャラクターなんとか機本家) K.Hmix 1st Edition http://khmix.sakura.ne.jp/ The storage of an additional parts キャラクターなんとか機 追加パーツ保管庫 キャラクターなんとか機 追加パーツ保管庫 (零件的保管库) キャラクターなんとか機 追加パーツ保管庫 (零件的保管庫) http://nantoka.main.jp/ CharacterManaJ/resources/template/characterDataTemplates_ja.xml0000644000175000017500000000053612560206305025222 0ustar paulliupaulliu character3.xml,character2.xml キャラクターなんとか機 ver2.x 互換 キャラクターなんとか機 ver3 互換 CharacterManaJ/resources/template/character2.xml0000644000175000017500000003500212560206305022155 0ustar paulliupaulliu Default デフォルト Unknown 名無し 无名 無名 300 400 true 6 Hair - Front 髪型 - 手前 发型 - 这边 髮型 - 這邊 Variable 可変色 可变颜色 可變顏色 12 hair_front HSB Accessory アクセサリ 饰品 飾品 13 hair_front_accessory HSB 6 Hair - Back 髪型 - 後ろ 发型 - 背后 髮型 - 背後 Variable 可変色 可变颜色 可變顏色 2 hair_back HSB Accessory アクセサリ 饰品 飾品 3 hair_back_accessory HSB 6 Head Head 9 head HSB 6 Expression 表情 脸色 臉色 Face 表情 脸色 臉色 14 face_front HSB Accessory アクセサリ 饰品 飾品 11 face_back HSB 6 Eyes Eye 15 eye HSB 6 Body 身体 身体 身體 Variable 可変色 可变颜色 可變顏色 7 body_front_color HSB Clothes ドレス 衣服 衣服 6 body_front HSB Skin 皮肤 皮膚 4 body_back HSB 10 Accessory アクセサリー 饰品 飾品 Top 最前面 最跟前 最跟前 16 accessory_front HSB Middle(R) 中間(R) 中间(右) 中間(右) 10 accessory_middle_front HSB Middle(L) 中間(L) 中间(左) 中間(左) 8 accessory_middle_back HSB Underwear アンダーウェア 内衣 內衣 5 accessory_underwear HSB Back 最背面 最后面 最後面 1 accessory_back HSB Hair 头发 頭髮 Eye 瞳孔 瞳孔 Skin 肤色 膚色 Dress 衣服 衣服 Originator (K.Hmix 1st Edition) (キャラクターなんとか機本家) K.Hmix 1st Edition http://khmix.sakura.ne.jp/ The storage of an additional parts キャラクターなんとか機 追加パーツ保管庫 キャラクターなんとか機 追加パーツ保管庫 (零件的保管库) キャラクターなんとか機 追加パーツ保管庫 (零件的保管庫) http://nantoka.main.jp/ CharacterManaJ/resources/appConfig.xml0000644000175000017500000000175612560206305020243 0ustar paulliupaulliu 7000 #ffc800 false #ff0000 #ffff 0.8 true #808080 #ffffff #ffffff #ff0000 csWindows31J 4096 4096 true true #ffff00 CharacterManaJ/resources/languages/0000755000175000017500000000000012560206305017550 5ustar paulliupaulliuCharacterManaJ/resources/languages/imageselectpanel_zh.xml0000644000175000017500000000073612560206305024303 0ustar paulliupaulliu 缩小 放大 打开颜色窗口 置于下一图层 置于上一图层 按名称排序 全部取消 CharacterManaJ/resources/languages/searchpartsdialog_zh.xml0000644000175000017500000000133012560206305024467 0ustar paulliupaulliu 搜索 搜索条件 部件名称 作者: 分类: 清除 结果 S选择 部件 分类 作者 100 50 80 CharacterManaJ/resources/languages/mainframe_ja.xml0000644000175000017500000000223712560206305022707 0ustar paulliupaulliu キャラクターなんとかJ - 無題 http://charactermanaj.sourceforge.jp/help/0.9/ ヘルプドキュメントは以下のURLにあります。 確認 パーツ固有の色情報をリセットしますか? 背景色の選択 お気に入りの名前 色情報を含める 上書きする バグレポートは以下のURLにあります。 http://sourceforge.jp/projects/charactermanaj/ticket/ フォーラムは以下のURLにあります。 http://sourceforge.jp/projects/charactermanaj/forums/ CharacterManaJ/resources/languages/ukagakaConvertDialog.xml0000644000175000017500000000100312560206305024351 0ustar paulliupaulliu Export for Ukagaka (PNG/PNA) Cancel Save Preview Auto Manual Overwrite original file Transparent color key CharacterManaJ/resources/languages/previewpanel.xml0000644000175000017500000000162412560206305022776 0ustar paulliupaulliu Loading... Save the image Copy to the clipboard Change the background Show the information Add to Favorites Flip Pin Check Alpha Check Brightness Zoom factor Zoom factor 30 CharacterManaJ/resources/languages/importwizdialog_zh.xml0000644000175000017500000000741012560206305024221 0ustar paulliupaulliu 导入 导入(新的配置) 完成 下一步 上一部 完成 取消 浏览 导入压缩文件(zip,cmj) 从文件夹导入 找不到文件 找不到文件夹 你确认要取消么? 确认 导入内容 默认/预设 部件 例图 压缩信息 ID 版本 名称 作者 说明 例图 将说明添加到此配置 没有内容 这不是一个标准CMJ的压缩包,但可能包含了一些图片. 配置的ID不匹配。 id="{0}" 配置的版本不匹配。 rev="{0}" 导入部件 全部选择 全部取消 按名称排序 按更新时间排序 选择 取消 选择 名称 分类 图像大小 透明 最后更新 当前配置的最后更新 作者 当前配置的作者 版本 当前配置的版本 50 100 80 50 50 80 80 80 80 50 50 导入默认/预设 导入使用的部件 选择 名称 缺失的部件 50 100 200 完成 CharacterManaJ/resources/languages/colordialog_zh.xml0000644000175000017500000000251512560206305023274 0ustar paulliupaulliu 颜色 应用 重置 应用到所有项目 RGB替换 替换 亮度 对比度 RGB R G B Alpha通道 偏移量 倍率 Gamma修正 HSB + 对比度 HSY + 对比度 色相 饱和度 亮度 色相 饱和度 辉度 颜色组 同步 CharacterManaJ/resources/languages/profileselectordialog_ja.xml0000644000175000017500000000622012560206305025325 0ustar paulliupaulliu 500 500 プロファイルの選択 新規 構造の複製 修正 照会 削除 場所を開く インポート エクスポート テンプレートの作成 キャラクターデータのテンプレートの選択 300 プロファイルの説明 プロファイル一覧 サンプルピクチャ 300 プロファイルを開く キャンセル ここにピクチャをドロップします ピクチャはありません カット ペースト 「{0}」を削除してもよろしいですか? 完全に削除する 削除の確認 このプロファイルは削除できません (使用中) (編集不可) プロファイルの選択 選択されたプロファイルへのインポートを行う。 新規にプロファイルを作成してインポートを行う。 名前 ID リビジョン サイズ 説明 作者 場所 200 100 100 80 300 150 300 テンプレートの名前 確認 CharacterManaJ/resources/languages/informationdialog_zh.xml0000644000175000017500000000217112560206305024501 0ustar paulliupaulliu 信息 关闭 部件 分类 图层 排序 大小 颜色模式 图片路径 动作 80 80 80 50 50 50 150 80 复制文件路径 编辑 打开 CharacterManaJ/resources/languages/ukagakaImageSaveHelper_zh.xml0000644000175000017500000000043712560206305025325 0ustar paulliupaulliu surface 确认 CharacterManaJ/resources/languages/colordialog.xml0000644000175000017500000000251012560206305022566 0ustar paulliupaulliu Color - Apply Reset All items Replace RGB Replace Bright Contrast RGB Red Green Blue Alpha Offset Factor Gamma HSB + Contrast HSY + Contrast Hue Saturation Brightness Hue Saturation Luminance Color Group Group Synchronized CharacterManaJ/resources/languages/samplepicturepanel_ja.xml0000644000175000017500000000051312560206305024640 0ustar paulliupaulliu ダブルクリックでピクチャサイズをフィットする ダブルクリックでピクチャサイズをフルサイズにする CharacterManaJ/resources/languages/partsmanagedialog_zh.xml0000644000175000017500000000340312560206305024455 0ustar paulliupaulliu 部件管理 部件列表 按名称排序 按作者排序 按最后更新排序 更新 批量输入下载地址 批量输入版本 作者信息 作者: 主页: 打开 取消 更新 有多个不同的作者被选中,确定要批量输入同一数值么? 确认 输入下载地址 输入版本号 确定要放弃编辑么? 部件ID 最后更新 分类 名称 作者 版本 下载地址 100 80 80 100 80 50 150 CharacterManaJ/resources/languages/ukagakaImageSaveHelper_ja.xml0000644000175000017500000000052412560206305025273 0ustar paulliupaulliu surface 確認 CharacterManaJ/resources/languages/profileditdialog.xml0000644000175000017500000001134412560206305023616 0ustar paulliupaulliu Profile(Edit) Profile(New) Update Create Cancel Show Directory Are you sure you want to change the structure? Structure is changed. Is revision updated? ID: The unique identifier to a character is specified here.
For example, "default" is the standard profile of the “character-nantoka-ki”.]]>
revision Rev: Config: Name: Picture Width: Picture Height: Author: Description: Add Remove Can not remove the color group because it is being used. Up Down Add Remove Can not remove the category because it is being used. Up Down Add Remove Sort Up Down Watch directories Basic Color Group Categories Layers Confirm Are you sure you want to cancel? Color Group Name Category Name Multiple selectable Display Row Count Used Layers 100 50 50 300 Layer Name Category Color Group Order Color Model Directory 100 100 100 50 100 300 Presets Default Preset Name Parts 50 50 150 200 Recommendations description URL 200 200 Add Remove Up Down
CharacterManaJ/resources/languages/appconfigdialog_ja.xml0000644000175000017500000001336312560206305024100 0ustar paulliupaulliu appConfig.xml アプリケーションの設定 更新 キャンセル 起動時にデータフォルダを選択する. プロパティ名 設定値 200 100 確認 編集を破棄しますか? エラー 不備があります。修正してください。 設定を変更した場合、アプリケーションの再起動が必要です。 設定 言語のカスタマイズ 確認 01;JPEG圧縮時のクオリティ(1が最大、0.1が最小) 02;クリップボードの透過サポートを有効にする.(Windows/OSX) 03;ZIPファイルに格納されているファイル名のエンコーディング(csWindows31Jが標準) 04;パーツ名からカラーグループを判定するパターン(正規表現)(@がカラーグループ名の場所になります.) 05;自動的にパーツ選択パネルを縮小する. 10;プレビューの初期表示の最大幅 11;プレビューの初期表示の最大高さ 12;レンダリングヒントを使用する 13;拡大時にバイキュービック方式による最適化を使用する. 14;表示最適化を適用するズームの閾値(通常モード)(0は常に無効) 15;表示最適化を適用するズームの閾値(チェックモード)(0は常に無効) 16;ズーム倍率の選択候補(カンマ区切り) 17;ズームパネルを表示する. 18;ズームパネルをアクティブにする範囲(0で常に無効) 19;ホイールによるスクロールの単位 1A;壁紙をオフスクリーンで描画する. 1B;オフスクリーンの既定サイズ 20;カラー変更時、自動的にプレビューに適用する. 21;カラーダイアログで存在しないレイヤーをディセーブルにしない. 31;フォルダ監視の有効・無効 32;フォルダに書き込み権限がない場合は監視しない 33;フォルダの監視間隔(mSec) 34;正常時でもログを終了時に消去しない。 35;起動時に古いログを消去するまでの日数。(0の場合は削除しない) 36;情報ダイアログのアクションを「開く」にする。(false時は「編集」) 50;アイテム選択行(フォーカス行)の背景色 51;パーツセットのエクスポート時の警告色 52;アイテム選択(チェック行)の背景色 53;サンプルピクチャの背景色 54;プレビューの背景色(デフォルト) 55;不正行の背景色 56;グリッド(罫線)の色 57;パーツ作者入力で複数作者選択時の入力ボックスの背景色 58;パーツ選択パネルのホバー色 59;デフォルトのフォントサイズ 5a;優先するフォントファミリー名(カンマ区切り) 90;JARファイル用バッファサイズ 91;ファイル転送用バッファサイズ A0;グリッドを描画する確認モードのビットマスク(0-3, 0は無効にする場合) A1;プレビュー画面のグリッドカラー(ARGB) A2;プレビュー画面のグリッドサイズ A3;チェックモード時の余白 A4;チェックモードの情報ツールチップの表示有無 B0;パーツのランダム選択の最大履歴数 CharacterManaJ/resources/languages/appconfigdialog_zh.xml0000644000175000017500000001051312560206305024121 0ustar paulliupaulliu appConfig.xml 程序设置 应用 取消 启动时询问工作路径 200 100 确认 你确定要关闭么? 错误 请填充未填项 应用新设置需要重新启动程序 Settings 自定义语言 确认 01;JPEG压缩质量(1最好,0.1最差) 02;复制到剪贴板时使用透明图像(Windows/OSX) 03;ZIP解码(默认为csWindows31J) 04;以部件名称判定图案的颜色组(使用正则表达式)('@'后面为色组名) 05;自动缩放项目栏 10;预览图最大宽度 11;预览图最大高度 12;使用提示渲染 13;使用二次立方 14;最佳渲染时缩放倍率的阈值(通常模式)(0为不缩放) 15;最佳渲染时缩放倍率的阈值(检查模式)(0为不缩放) 16;预定义缩放 17;显示缩放栏 18;缩放栏位置 19;滚轮调整单元 1A;背景全屏显示 1B;默认全屏大小 20;自动刷新颜色 21;在颜色菜单显示不存在的图层 31;开启文件夹监视 32;文件只读时关闭文件夹监视 33;监视间隔时间(ms) 34;退出时不清除log 35;启动时清除多少天以上的log(0表示不清除) 36;信息栏动作(true为打开;false为编辑) 50;选择时的背景色 51;警告时的背景色 52;已检查项目的背景色 53;例图的背景色 54;预览的背景色 55;Invalid Cell的背景色 56;网格颜色 57;作者冲突的背景色 58;鼠标悬停于部件选择面板时文字颜色 59;默认字体大小 5a;字体系列 90;Jar文件缓存 91;文件缓存 A0;预览时显示网格 A1;网格颜色(ARGB) A2;网格大小 A3;保留空白空间(检查模式) A4;开启运行状态控件(检查模式) B0;随机模式记忆的历史数量 CharacterManaJ/resources/languages/partsmanagedialog_ja.xml0000644000175000017500000000364212560206305024433 0ustar paulliupaulliu パーツの管理 パーツリスト 名前順に整列 作者順に整列 更新日順に整列 最新にする ダウンロードURLを一括指定 バージョンを一括指定 作者情報 作者名: ホームページ: 開く キャンセル 更新 複数の作者が選択されていますが、一括適用を行いますか? 確認 ダウンロードURLの入力 バージョン番号の入力 編集を破棄してもよろしいですか? パーツID 更新日 カテゴリ パーツ名 作者 バージョン ダウンロードURL 100 80 80 100 80 50 150 CharacterManaJ/resources/languages/informationdialog.xml0000644000175000017500000000215512560206305024002 0ustar paulliupaulliu Information Close Parts Category Layer Order Size Color Mode Image Action 80 80 80 50 50 50 150 80 Copy the file path Edit Open CharacterManaJ/resources/languages/partsmanagedialog.xml0000644000175000017500000000347412560206305023764 0ustar paulliupaulliu Manage Parts's author Parts List Sort by name Sort by Author Sort by Last-modified Up to date Batch input for the download URL. Batch input for the version. Author Information Author: Homepage: Open Cancel Update Is the batch application done though two or more authors have been selected? Confirm Input the Download URL Input the version number May I annul the edit? Parts ID Last-Modified Category Localized name Author Version Download URL 100 80 80 100 80 50 150 CharacterManaJ/resources/languages/informationdialog_ja.xml0000644000175000017500000000231512560206305024452 0ustar paulliupaulliu 情報 閉じる パーツ名 カテゴリ名 レイヤー 順序 サイズ カラーモード 画像ファイル アクション 80 80 80 50 50 50 150 80 ファイルパスをクリップボードにコピー 編集 開く CharacterManaJ/resources/languages/exportwizdialog.xml0000644000175000017500000000552312560206305023532 0ustar paulliupaulliu Author Description Character definition Export a subset Export Complete Confirm Are you sure you want to cancel? File already exists. Are you sure you want to overwrite? File not exists. Next Previous Finish Cancel Include contents Comments Sample picture Parts Preset/Favorites Sample picture Parts List Selected Category Parts name Last-Modified Author Version 50 100 150 100 80 50 Select All Deselect All Sort Sort By Timestamp Check Uncheck Preset/Favorite List Selected Name Default Missing parts 50 50 150 300 Select All Deselect All Sort Export used parts CharacterManaJ/resources/languages/managefavoritesdialog.xml0000644000175000017500000000101412560206305024621 0ustar paulliupaulliu Manage favorites Select Remove Rename Close Input the Favorite's name ARE YOU SURE YOU WANT TO REMOVE THE SELECTED FAVORITES? CONFIRM CharacterManaJ/resources/languages/selectCharatersDirDialog.xml0000644000175000017500000000135412560206305025170 0ustar paulliupaulliu 550 CharacterManaJ Select a workspace Workspace: OK Cancel Browse Clear recent list Clear cache Use this as the default and do not ask again. Are you sure you want to delete all cache? CONFIRM CharacterManaJ/resources/languages/partsrandomchooserdialog_zh.xml0000644000175000017500000000061212560206305026067 0ustar paulliupaulliu 随机工具 关闭 随机 忽略 更多 全部随机 撤销 CharacterManaJ/resources/languages/wallpaperdialog.xml0000644000175000017500000000165112560206305023444 0ustar paulliupaulliu Background Wallpaper Background color Opacity Lattice (small) Lattice (large) None File Predefined Choose OK Cancel The file is required. The predefined image is required. A file does not exist, or it cannot read. CharacterManaJ/resources/languages/mainframe_zh.xml0000644000175000017500000000172312560206305022735 0ustar paulliupaulliu CharacterManaJ - 无标题 http://charactermanaj.sourceforge.jp/help/0.9/ 帮助文档(日) 确认 你确定么? 选择背景色 预设 包括颜色设定 覆盖 问题反馈(日) http://sourceforge.jp/projects/charactermanaj/ticket/ 论坛(日) http://sourceforge.jp/projects/charactermanaj/forums/ CharacterManaJ/resources/languages/selectCharatersDirDialog_zh.xml0000644000175000017500000000135112560206305025666 0ustar paulliupaulliu 550 CharacterManaJ 选择工作路径 工作路径: 确认 取消 浏览 删除最近的列表 删除缓存 将当前设置为默认并不再询问 你确定要删除所有缓存么? 确认 CharacterManaJ/resources/languages/selectCharatersDirDialog_ja.xml0000644000175000017500000000153412560206305025642 0ustar paulliupaulliu キャラクターなんとかJ キャラクターデータを格納するフォルダを選択してください。 フォルダ: 選択 キャンセル 参照 履歴のクリア キャッシュのクリア 次回から、このフォルダを使用する. 全てのキャッシュをクリアしてもよろしいですか? 確認 CharacterManaJ/resources/languages/colordialog_ja.xml0000644000175000017500000000263512560206305023250 0ustar paulliupaulliu 色 - 適用 リセット すべてのアイテム RGB置換 置換パターン 明るさ コントラスト RGB 透過 オフセット 倍率 ガンマ補正 HSB + コントラスト HSY + コントラスト 色相 彩度 明度 色相 彩度 輝度 色グループ 色グループ 連動する CharacterManaJ/resources/languages/colorbox.xml0000644000175000017500000000035212560206305022121 0ustar paulliupaulliu Select the color Select the color CharacterManaJ/resources/languages/imageSaveHelper.xml0000644000175000017500000000232012560206305023330 0ustar paulliupaulliu Confirm Output options JPEG Quality Zoom Factor Mode Picture type Type A background color is forced. NONE BILINER BICUBIC Normal Opaque Gray Alpha CharacterManaJ/resources/languages/ukagakaConvertDialog_zh.xml0000644000175000017500000000077612560206305025072 0ustar paulliupaulliu 输出到伪春菜 (PNG/PNA) 取消 保存 预览 自动 手动 覆盖原始文件 选择透明颜色 CharacterManaJ/resources/languages/partsrandomchooserdialog.xml0000644000175000017500000000060512560206305025370 0ustar paulliupaulliu Random Chooser Close Random Ignore More Random All Back CharacterManaJ/resources/languages/importwizdialog.xml0000644000175000017500000000747412560206305023532 0ustar paulliupaulliu Import Import (new profile) Complete Next Previous Finish Cancel Browse Import archived File (zip, cmj) Import from Directory File not found. Directory not found. Are you sure you want to cancel? Confirm Import contents Presets/Favorites Parts Sample picture Archive information ID Rev. Name Author Note Sample picture Append this description to the profile. No contents. This is not a formal archive, but may be containing some picture. Profile ID mismatch. id="{0}" Profile REV mismatch. rev="{0}" Import parts select All deselect All Sort Sort by timestamp Check Uncheck Checked Parts name Category Image size Transparency Last-Modified Last-Modified on the current profile Author Author on the current profile Version Version on the current profile 50 100 80 50 50 80 80 80 80 50 50 Import favorites Import used parts Checked Name Missing parts 50 100 200 Complete CharacterManaJ/resources/languages/imageselectpanel_ja.xml0000644000175000017500000000074412560206305024253 0ustar paulliupaulliu 縮小する 拡大する 色ダイアログを開く 上へ 下へ 並び替え 全て選択解除する CharacterManaJ/resources/languages/managefavoritesdialog_ja.xml0000644000175000017500000000111612560206305025276 0ustar paulliupaulliu お気に入りの管理 選択する 削除する 名前を変更する 閉じる お気に入りの名前を入力する 選択したお気に入りを消去してもよろしいですか? 確認 CharacterManaJ/resources/languages/managefavoritesdialog_zh.xml0000644000175000017500000000076512560206305025336 0ustar paulliupaulliu 预设管理 选择 删除 重命名 关闭 输入预设的名称 你确定要删除选择的预设么? 确认 CharacterManaJ/resources/languages/previewpanel_ja.xml0000644000175000017500000000253112560206305023446 0ustar paulliupaulliu Loading... 画像をファイルに保存する
(シフトキーで「伺か」用出力)]]>
画像をクリップボードにコピーする
(シフトキーでスクリーンイメージ取得)]]>
背景を設定する
(シフトキーで背景色のみ変更)]]>
情報を表示する お気に入りに追加する
(シフトキーで「お気に入りの管理」)]]>
画像を左右反転する 固定する 透過確認 輝度確認 表示倍率 表示倍率 30
CharacterManaJ/resources/languages/searchpartsdialog.xml0000644000175000017500000000133512560206305023773 0ustar paulliupaulliu Search Search Condition Parts Name: Author: Category: Clear Results Select Parts Category Author 100 50 80 CharacterManaJ/resources/languages/ukagakaImageSaveHelper.xml0000644000175000017500000000046212560206305024622 0ustar paulliupaulliu surface Confirm CharacterManaJ/resources/languages/imageSaveHelper_zh.xml0000644000175000017500000000231012560206305024030 0ustar paulliupaulliu 确认 输出设置 JPEG 质量 范围 比例 模式 图片类型 类型 需要指定背景色 无修正 BILINER BICUBIC 正常 不透明 灰度 Alpha通道 CharacterManaJ/resources/languages/previewpanel_zh.xml0000644000175000017500000000157512560206305023504 0ustar paulliupaulliu 载入中... 保存图像 复制到剪贴板 更改背景 显示信息 增加到预设 翻转 固定 检查透明通道 检查亮度 缩放 缩放 30 CharacterManaJ/resources/languages/profileselectordialog.xml0000644000175000017500000000533312560206305024657 0ustar paulliupaulliu 500 500 Profile Selector New Copy Structure Edit View Remove Browse Import Export Create Template Choose a Character Data Template 300 Description Profiles Sample 300 Open Cancel dropHere No Picture Cut Paste ARE YOU SURE YOU WANT TO DELETE {0}? Delete completely. CONFIRM can not delete this profile. (Opened) (no edit) Select profile Import to the selected profile. Import to the new profile. Name ID Revision Size Description Author Location 200 100 100 80 300 150 300 Template Name Confirm CharacterManaJ/resources/languages/exportwizdialog_zh.xml0000644000175000017500000000550112560206305024227 0ustar paulliupaulliu 作者 描述 人物设定 导出部分(使用不同设置) 导出 完成 确认 你确定要取消么? 文件已存在,是否覆盖文件? 文件不存在。 下一步 上一步 完成 取消 导出内容 描述 例图 部件 默认/预设 例图 部件列表 选择 分类 名称 最后更新 作者 版本 50 100 150 100 80 50 全部选择 全部取消 按名称排序 按更新时间排序 选择 取消 默认/预设列表 选择 名称 默认 缺失的部件 50 50 150 300 全部选择 全部取消 按名称排序 导出使用的部件 CharacterManaJ/resources/languages/profileditdialog_ja.xml0000644000175000017500000001245112560206305024270 0ustar paulliupaulliu プロファイルの編集 プロファイルの作成 更新 作成 キャンセル フォルダを表示 構造が変更されます。よろしいですか? 構造が変更されます。リビジョンを更新しますか? ID: キャラクターセットごとにユニークな名前をつけます。英数字および記号のみ。
たとえば、キャラクターなんとか機デフォルトパーツセット(Ver2)であれば「Default」とします。]]>
カテゴリやレイヤーの構造が変るたびに違う識別子を割り当てます。英数字および記号のみ。 Rev: 設定ファイル: 名前: キャンバスの幅: キャンバスの高さ: 作者: 説明: 追加 削除 このカラーグループは使用中のため削除できません。 上へ 下へ 追加 削除 このカテゴリは使用中のため削除できません。 上へ 下へ 追加 削除 整列 上へ 下へ フォルダを監視し、パーツ画像の変更を検知できるようにする。 基本 カラーグループ カテゴリ レイヤー 確認 編集をキャンセルしますか? カラーグループ名 カテゴリ名 複数選択可 表示行数 使用しているレイヤー 100 50 50 300 レイヤー名 カテゴリ カラーグループ 重ね順 カラーモデル フォルダ 100 100 100 50 100 300 プリセット デフォルト プリセット パーツセット名 使用パーツ 50 50 150 200 お勧めリンク 説明 URL 200 200 追加 削除 上へ 下へ
CharacterManaJ/resources/languages/colorbox_zh.xml0000644000175000017500000000034212560206305022621 0ustar paulliupaulliu 选择颜色 选择颜色 CharacterManaJ/resources/languages/profileselectordialog_zh.xml0000644000175000017500000000523112560206305025355 0ustar paulliupaulliu 500 500 选择配置 新建 复制配置 编辑 View 移除 浏览.. 导入 导出 创建缓存 选择一个人物数据缓存 300 描述 配置 例图 300 打开 取消 在此填充 无图片 剪切 复制 你确认要删除 {0}? 完全删除 确认 无法删除此配置 (已打开) (无编辑) 选择配置 向选择的配置导入 导入新的配置 名称 ID 版本 大小 描述 作者 路径 200 100 100 80 300 150 300 缓存名称 确认 CharacterManaJ/resources/languages/ukagakaConvertDialog_ja.xml0000644000175000017500000000105312560206305025030 0ustar paulliupaulliu 伺か用PNG/PNAの出力 キャンセル 保存 プレビュー 自動 手動 既存のファイルに上書きする 透過とする色の選択 CharacterManaJ/resources/languages/mainframe.xml0000644000175000017500000000173112560206305022233 0ustar paulliupaulliu CharacterManaJ - no name http://charactermanaj.sourceforge.jp/help/0.9/ Help Document is here. CONFIRM AER YOU SURE? Choose the background-color Favorites Include colors Overwrite Report bugs http://sourceforge.jp/projects/charactermanaj/ticket/ Forum http://sourceforge.jp/projects/charactermanaj/forums/ CharacterManaJ/resources/languages/searchpartsdialog_ja.xml0000644000175000017500000000137612560206305024452 0ustar paulliupaulliu パーツの検索 検索条件 バーツ名: 作者: カテゴリ: クリア 該当結果 選択 パーツ名 カテゴリ 作者 100 50 80 CharacterManaJ/resources/languages/imageselectpanel.xml0000644000175000017500000000067012560206305023577 0ustar paulliupaulliu Shrink Expand Open the color dialog Up Down Sort Deselect all CharacterManaJ/resources/languages/profileditdialog_zh.xml0000644000175000017500000001126412560206305024320 0ustar paulliupaulliu 配置(编辑) 配置(新建) 更新 创建 取消 打开位置 你确认要改变图层构造么? 图层构造以已改变,是否立刻更新版本? ID: 当前使用的ID为保留字段
例如,"default"是“character-nantoka-ki”配置的标准ID!]]>
版本 版本: 设置: 名称: 图像宽度: 图像高度: 作者: 说明: 增加 移除 无法移除此颜色组,当前颜色组正在使用。 向上 向下 增加 移除 无法移除此分类,当前分类正在使用。 向上 向下 增加 移除 排序 向上 向下 显示目录 基本设置 颜色组 分类 图层 确认 你确定要取消么? 颜色组名称 分类名称 可多选 显示行数 使用的图层 100 50 50 300 图层名称 分类 颜色组 排序 颜色模式 目录 100 100 100 50 100 300 预设 默认 预设 名称 部件 50 50 150 200 相关链接 描述 URL 200 200 增加 移除 向上 向下
CharacterManaJ/resources/languages/colorbox_ja.xml0000644000175000017500000000033412560206305022573 0ustar paulliupaulliu 色選択 色選択 CharacterManaJ/resources/languages/partsrandomchooserdialog_ja.xml0000644000175000017500000000065112560206305026043 0ustar paulliupaulliu パーツのランダム選択 閉じる ランダム 除外 追加 ランダム一括 Back CharacterManaJ/resources/languages/imageSaveHelper_ja.xml0000644000175000017500000000253412560206305024011 0ustar paulliupaulliu 確認 出力オプション JPEG 品質 拡大・縮小 倍率 モード 画像タイプ タイプ 常に背景色を使用する 補正なし バイリニア バイキュービック 通常 透過なし グレースケール アルファチャネル CharacterManaJ/resources/languages/wallpaperdialog_ja.xml0000644000175000017500000000212112560206305024107 0ustar paulliupaulliu 背景の設定 壁紙(タイル)の選択 背景色の選択 壁紙画像の不透明率 市松模様 (小) 市松模様 (大) なし ファイルから選択 既定から選択 選択 OK キャンセル 画像ファイルを指定してください。 定義済み壁紙を選択してください。 指定したファイルが存在しないか、読み取りできません。 CharacterManaJ/resources/languages/samplepicturepanel.xml0000644000175000017500000000037612560206305024175 0ustar paulliupaulliu Double-click to fit. Double-click to view the image full size. CharacterManaJ/resources/languages/importwizdialog_ja.xml0000644000175000017500000001060712560206305024174 0ustar paulliupaulliu インポート インポート (新規プロファイル作成) 次へ 戻る 実行 キャンセル Complete 参照... アーカイブファイル(zip,cmj)からインポート フォルダからインポート ファイル: ファイルがありません。 フォルダがありません。 キャンセルしますか? 確認 インポートするコンテンツ プリセット/お気に入り パーツ サンプルピクチャ アーカイブ情報 ID REV 名前 作者 説明 サンプルピクチャ キャラクター定義の説明に上記の説明を追記する。 インポートできる内容がありません。 CharacterManaJのエクスポート形式と異なりますが、使用できる可能性のあるパーツがあります。 プロファイルIDが一致しません。 id="{0}" プロファイルIDは一致しますが、リビジョンが一致しません。 rev="{0}" インポートするパーツ 全て選択 全て解除 名前順で整列 日付順で整列 チェックする チェックを外す 選択 パーツ名 カテゴリ名 イメージサイズ 透過 最終更新日 現在の最終更新日 作者 現在の作者 バージョン 現在のバージョン 50 100 80 50 50 80 80 80 80 50 50 インポートするお気に入り 使用しているパーツのインポート 選択 プリセット名 不足するパーツ 50 100 200 インポートが完了しました。 CharacterManaJ/resources/languages/wallpaperdialog_zh.xml0000644000175000017500000000160712560206305024146 0ustar paulliupaulliu 背景 壁纸 背景色 背景图不透明率 网格 (小) 网格 (大) 文件 预设 选择 确认 取消 需要图像文件 需要预设图像 文件不存在或无法读取 CharacterManaJ/resources/languages/samplepicturepanel_zh.xml0000644000175000017500000000034512560206305024672 0ustar paulliupaulliu 双击填充 双击查看完整图像 CharacterManaJ/resources/languages/exportwizdialog_ja.xml0000644000175000017500000000621612560206305024204 0ustar paulliupaulliu 作者 説明 キャラクター定義 部分的なエクスポート(差分セット) エクスポート 完了しました。 確認 キャンセルしますか? ファイルが既に存在します。上書きしてもよろしいですか? ファイルがありません。 次へ 戻る 実行 キャンセル エクスポートするコンテンツ コメント サンプルイメージ パーツ プリセット/お気に入り サンプルイメージ パーツの選択 選択 カテゴリ パーツ名 更新日時 作者 バージョン 50 100 150 100 80 50 すべて選択 すべて選択解除 名前順で整列 日付順で整列 チェックする チェックを外す プリセット/お気に入りの選択 選択 既定 名前 不足するパーツ 50 50 150 300 すべて選択 すべて選択解除 整列 使用パーツをエクスポート対象にする CharacterManaJ/resources/languages/appconfigdialog.xml0000644000175000017500000001056712560206305023431 0ustar paulliupaulliu appConfig.xml Application Configurations Apply Cancel Ask the data directory during startup. Key Value 200 100 Confirm Are you sure want to close? Error Please correct incompleteness. Change to property needs an application restart. Settings Custom Localization Confirm 01;Compression Quality 02;Use the transparency image in Clipboard.(Windows/OSX) 03;ZIP File Encoding 04;The judgment pattern of a color group.('@' is color group name) 05;Auto shrink category panels 10;Preview Max-Width 11;Preview Max-Height 12;Use rendering hints 13;Use Bicubic 14;Threshold of zoom factor of the rendering optimization.(normal) 15;Threshold of zoom factor of the rendering optimization.(check) 16;Predefined zoom factors 17;Enable zoom panel 18;Area where zoom is activated. 19;The unit of scrolling by a wheel 1A;Draw Wallpaper by offscreen. 1B;Offscreen default size. 20;Auto Color Refresh 21;The layer not existing is not disabled in the color dialog. 31;Enable Watch Directory 32;Disable Watch Directory If Not Writable 33;Directory Watch Interval (mSec) 34;No Remove Log 35;a number in days until purge log 36;Information Dialog Open Mode 50;Selected Item's Background Color 51;Export Preset's Warnings Foreground Color 52;Checked Item Background Color 53;Sample Picture Background Color 54;Preview Background Color 55;Invalid Cell's Background Color 56;Grid Color 57;Author Conflict Background Color 58;Parts Select Panel Hovering Color 59;Default Font Size 5a;Default Font Family Name 90;Jar File Buffer 91;File Buffer A0;Draw grid on preview A1;Grid color (ARGB) A2;Grid size A3;Unfilled space (check-mode only) A4;Enable CheckInfo tooltip (check-mode only) B0;Limit Of The Random Parts History CharacterManaJ/resources/appinfo/0000755000175000017500000000000012560206305017236 5ustar paulliupaulliuCharacterManaJ/resources/appinfo/about.html0000644000175000017500000001677612560206305021257 0ustar paulliupaulliu

ChracterManaJ Version @@SPECIFICATION-VERSIONINFO@@

Implementation-Version: @@IMPLEMENTS-VERSIONINFO@@

[Software Requirements]

Use in the following environment is assumed.

  • Mac OS X
    • 10.4 Tiger (Intel/PPC) J2SE5
    • 10.5 Leopard (Intel/PPC) J2SE5
    • 10.5 Leopard (Intel) JavaSE6
    • 10.6 Snow Leopard (Intel) JavaSE6
    • 10.7 Lion (Intel) JavaSE6 (Java for Mac OS X 10.7 is required.)
    • 10.7 Lion (Intel) Java7 (Oracle, Java7u45 or later)
    • 10.8 Mountain Lion (Intel) JavaSE6u65 or later (Java for OS X 2013-005 is required.)
    • 10.8 Mountain Lion (Intel) Java7 (Oracle, java7u45 or later)
    • 10.9 Marvericks (Intel) JavaSE6u65 or later (Java for OS X 2013-005 is required.)
    • 10.9 Marvericks (Intel) Java7 (Oracle, java7u45 or later)
  • Microsoft Windows
    • Windows 2000 SP4 (32Bit) JavaSE6 (Japanese Only)
    • Windows XP SP2 or later (32Bit) JavaSE6
    • Windows Vista SP1 or later (32/64Bit) JavaSE6
    • Windows 7 (32/64Bit) JavaSE6, Java7u45 or later
    • Windows 8 (32/64Bit) JavaSE6, Java7u45 or later
    • Windows 8.1 (32/64Bit) JavaSE6, Java7u45 or later
    Installation of language support of East Asia is indispensable.
    (Control Panel / Regional and Language Options / Language Tab / Install files for East Asian languages)
  • Linux
    • Ubuntu 10.04 sun-java-jdk6
    • Ubuntu 12.04 openjdk7
    • Fedora 14 Desktop openjdk-1.6.0 or sun-java-jdk6(recommended)

I think that it will operate fundamentally if there is an execution environment which supported the desktop of J2SE5 since J2SE5 has described.

JavaSE7 is recommended.

"lib/charsets.jar" is indispensable in order to treat a Japanese file name.

When "lib/charsets.jar" does not exist, it is necessary to re-install JRE.
(For example, when "East Asian Languages" is installed afterwards.)


Could someone please translate this japanese text.

Java7の注意点

Mac上での本アプリケーションは、AppleのJava6用と、OracleのJava7用と、それぞれ専用版のアプリケーションに分けています。

[About this software]

このアプリケーションは複数の画像を選択し重ね合わせることで一つの画像を作り出す、モンタージュを行うアプリケーションです。

用途としてパーツを組み合わせてキャラクターをデザインするという、一種の着せ替えを楽しむことを想定していますが、それに限りません。

モンタージュするための素材の画像データがないと、このアプリケーションは何もできません。ご自身で作成するか、既にある画像データを用意するなどしてください。

カテゴリ、レイヤー、カラーグループを増減させたりなど、画像の組み合わせ方については新しいプロファイルを作成することで自分好みの設定にすることができます。

このアプリケーションは、K.Hみっくす ふぁーすとえでぃしょん さんの
「キャラクターなんとか機」
を目標にJAVAによりフルスクラッチから書き起こしたものです。

初期状態で、「キャラクターなんとか機」と同じカテゴリ、レイヤー順序、ディレクトリ、カラーグループの設定がされたプロファイルが作成されますので、 「キャラクターなんとか機」のパーツデータをインポートして使うことができます。

[ライセンス/使用許諾条件]

「キャラクターなんとかJ」はオープンソースソフトウェアです。ソースコードは「The Apache License Version 2.0」です。

本ソフトウェアは完全に無保証です。本ソフトウェアは「現状のまま」、かつ明示か暗黙であるかを問わず、一切の保証を付けずに提供されます。

ここから最新リリース 、 および最新リリースのソースコードを取得することができます。

商用利用であるかを否かを問わず、どなたでも、自由にお使いいただけます。登録や費用は一切必要ありません。再配布も自由になさってかまいません。

このアプリケーション自身(javaコード)はseraphyによって書かれました。
人的リソースに限りがあるため、メンテナンスや改善に時間がかかることが予想されます。
このアプリケーションをより良くしようという有志がおられましたら、ぜひ、ご協力・ご連絡くださいますようお願いいたします。歓迎いたします。

また、バグレポート等ありましたら、どなた様も上記プロジェクトページにてご報告くださいますよう、お願いいたします。

[キャラクターデータについて]

このアプリケーションを動作させるためには、パーツの画像データが必要です。

初期状態のプロファイルでは「キャラクターなんとか機 Ver2」で使える画像を利用できるよう設定されます。 キャラクターデータのインポート方法はヘルプをご参照ください。

 「キャラクターなんとか機 Ver2」を目標としましたが、色調整等のパラメータを解析しきれなかったため完全には色を再現できていません。 そのかわり、各レイヤーごとに色の微調整ができるようにしているため、近い色を再現するはできると思います。 その他、いろいろ不備があると思いますがご容赦ください。

[パーツデータの作者様へ]

パーツデータをお持ちか、作成された方で、オープンソース系(たとえばクリエイティブコモンズなど) のライセンスで配布を許可くださる方は、ぜひ、ご連絡ください。

プロジェクトの配布ファイルに加えさせていただこうと思います。

[利用コンポーネントについて]

J2SE5の標準APIの他、Apache Antの一部(The Apache License Version 2.0)をライブラリとして使用しています。

また、カラーモデルのHSY(色相・彩度・輝度)計算には、 "gununuの日記"さん のところのC++計算ルーチンをJava用に書き直したものを使用しています。

CharacterManaJ/resources/appinfo/about_ja.html0000644000175000017500000002057512560206305021721 0ustar paulliupaulliu

ChracterManaJ Version @@SPECIFICATION-VERSIONINFO@@

実装バージョン: @@IMPLEMENTS-VERSIONINFO@@

[このアプリケーションについて]

このアプリケーションは複数の画像を選択し重ね合わせることで一つの画像を作り出す、モンタージュを行うアプリケーションです。

用途としてパーツを組み合わせてキャラクターをデザインするという、一種の着せ替えを楽しむことを想定していますが、それに限りません。

モンタージュするための素材の画像データがないと、このアプリケーションは何もできません。ご自身で作成するか、既にある画像データを用意するなどしてください。

カテゴリ、レイヤー、カラーグループを増減させたりなど、画像の組み合わせ方については新しいプロファイルを作成することで自分好みの設定にすることができます。

このアプリケーションは、K.Hみっくす ふぁーすとえでぃしょん さんの
「キャラクターなんとか機」
を目標にJAVAによりフルスクラッチから書き起こしたものです。

初期状態で、「キャラクターなんとか機」と同じカテゴリ、レイヤー順序、ディレクトリ、カラーグループの設定がされたプロファイルが作成されますので、 「キャラクターなんとか機」のパーツデータをインポートして使うことができます。

[ライセンス/使用許諾条件]

「キャラクターなんとかJ」はオープンソースソフトウェアです。ソースコードは「The Apache License Version 2.0」です。

本ソフトウェアは完全に無保証です。本ソフトウェアは「現状のまま」、かつ明示か暗黙であるかを問わず、一切の保証を付けずに提供されます。

ここから最新リリース 、 および最新リリースのソースコードを取得することができます。

商用利用であるかを否かを問わず、どなたでも、自由にお使いいただけます。登録や費用は一切必要ありません。再配布も自由になさってかまいません。

このアプリケーション自身(javaコード)はseraphyによって書かれました。
人的リソースに限りがあるため、メンテナンスや改善に時間がかかることが予想されます。
このアプリケーションをより良くしようという有志がおられましたら、ぜひ、ご協力・ご連絡くださいますようお願いいたします。歓迎いたします。

また、バグレポート等ありましたら、どなた様も上記プロジェクトページにてご報告くださいますよう、お願いいたします。

[キャラクターデータについて]

このアプリケーションを動作させるためには、パーツの画像データが必要です。

初期状態のプロファイルでは「キャラクターなんとか機 Ver2」で使える画像を利用できるよう設定されます。 キャラクターデータのインポート方法はヘルプをご参照ください。

 「キャラクターなんとか機 Ver2」を目標としましたが、色調整等のパラメータを解析しきれなかったため完全には色を再現できていません。 そのかわり、各レイヤーごとに色の微調整ができるようにしているため、近い色を再現するはできると思います。 その他、いろいろ不備があると思いますがご容赦ください。

[パーツデータの作者様へ]

パーツデータをお持ちか、作成された方で、オープンソース系(たとえばクリエイティブコモンズなど) のライセンスで配布を許可くださる方は、ぜひ、ご連絡ください。

プロジェクトの配布ファイルに加えさせていただこうと思います。

[動作環境]

以下の環境での利用を想定しています。

  • Mac OS X
    • 10.4 Tiger (Intel/PPC) J2SE5
    • 10.5 Leopard (Intel/PPC) J2SE5
    • 10.5 Leopard (Intel) JavaSE6
    • 10.6 Snow Leopard (Intel) JavaSE6
    • 10.7 Lion (Intel) JavaSE6 (Java for Mac OS X 10.7 is required.)
    • 10.7 Lion (Intel) Java7 (Oracle, Java7u45 or later)
    • 10.8 Mountain Lion (Intel) JavaSE6u65 or later (Java for OS X 2013-005 is required.)
    • 10.8 Mountain Lion (Intel) Java7 (Oracle, java7u45 or later)
    • 10.9 Marvericks (Intel) JavaSE6u65 or later (Java for OS X 2013-005 is required.)
    • 10.9 Marvericks (Intel) Java7 (Oracle, java7u45 or later)
  • Microsoft Windows
    • Windows 2000 SP4 (32Bit) JavaSE6 (日本語版のみ)
    • Windows XP SP2+ (32Bit) JavaSE6 (日本語版、それ以外はEast asian languagesをコンパネからインストール済みのこと)
    • Windows Vista SP1以降 (32/64Bit) JavaSE6
    • Windows 7 (32/64Bit) JavaSE6, Java7u45 or later
    • Windows 8 (32/64Bit) JavaSE6, Java7u45 or later
    • Windows 8.1 (32/64Bit) JavaSE6, Java7u45 or later
  • Linux
    • Ubuntu 10.04 sun-java-jdk6
    • Ubuntu 12.04 openjdk7
    • Fedora 14 Desktop openjdk-1.6.0/sun-java-jdk6 (sun-javaを推奨)

J2SE5で記述しているため、J2SE5のデスクトップをサポートした実行環境があれば基本的には動作すると思います。

Windows環境ではJ2SE5でも動作しますが、Java7を推奨します。

Mac OS Xも同様にJava7(Oracle)を推奨します。(AppleのJavaSE5/JavaSE6でも動作します。)

OS X 10.7以降は標準ではJavaはインストールされていません。AppleのサイトからJava6をインストールするか、OracleのサイトからJava7をインストールする必要があります.

Mac OS X上での本アプリケーションは、AppleのJava5/6用と、OracleのJava7用と、それぞれ専用版のアプリケーションに分けています。

メモリは、Mac OS Xのアプリケーションバンドル形式、またはWindowsのEXE形式での配布形式では、初期状態で96MB、最大で128MBの設定を行っています。

実行可能JAR形式ではJREのデフォルトのサイズが使われます。これは搭載している物理メモリによって割り当てられるサイズが64MBあるいは256MBのように変わります。

おそらく通常の利用方法において64MBの割り当てでは、やや不足する感があると思いますが、一応、動作はすると思います。

実際のメモリ使用状況については Aboutメニューからシステム情報タブの中で確認することができます。

Linuxをお使いの場合は、openjdk7を推奨します。

(Linux上でJavaSE6を使われる場合は、sun-java6を推奨します。基本的にはopenjdk6でも動作しますが、クリップボードが機能しません。)

[利用コンポーネントについて]

J2SE5の標準APIの他、Apache Antの一部(The Apache License Version 2.0)をライブラリとして使用しています。

また、カラーモデルのHSY(色相・彩度・輝度)計算には、 "gununuの日記"さん のところのC++計算ルーチンをJava用に書き直したものを使用しています。

CharacterManaJ/resources/appinfo/about_zh.html0000644000175000017500000002010112560206305021731 0ustar paulliupaulliu

ChracterManaJ Version @@SPECIFICATION-VERSIONINFO@@

实际版本: @@IMPLEMENTS-VERSIONINFO@@

[关于此程序]

このアプリケーションは複数の画像を選択し重ね合わせることで一つの画像を作り出す、モンタージュを行うアプリケーションです。

用途としてパーツを組み合わせてキャラクターをデザインするという、一種の着せ替えを楽しむことを想定していますが、それに限りません。

モンタージュするための素材の画像データがないと、このアプリケーションは何もできません。ご自身で作成するか、既にある画像データを用意するなどしてください。

カテゴリ、レイヤー、カラーグループを増減させたりなど、画像の組み合わせ方については新しいプロファイルを作成することで自分好みの設定にすることができます。

このアプリケーションは、K.Hみっくす ふぁーすとえでぃしょん さんの
「キャラクターなんとか機」
を目標にJAVAによりフルスクラッチから書き起こしたものです。

初期状態で、「キャラクターなんとか機」と同じカテゴリ、レイヤー順序、ディレクトリ、カラーグループの設定がされたプロファイルが作成されますので、 「キャラクターなんとか機」のパーツデータをインポートして使うことができます。

[程序使用许可条件]

「CharacterManaJ」是一个开源软件。源代码遵从「The Apache License Version 2.0」协议。

本ソフトウェアは完全に無保証です。本ソフトウェアは「現状のまま」、かつ明示か暗黙であるかを問わず、一切の保証を付けずに提供されます。

ここから最新リリース 、 および最新リリースのソースコードを取得することができます。

商用利用であるかを否かを問わず、どなたでも、自由にお使いいただけます。登録や費用は一切必要ありません。再配布も自由になさってかまいません。

このアプリケーション自身(javaコード)はseraphyによって書かれました。
人的リソースに限りがあるため、メンテナンスや改善に時間がかかることが予想されます。
このアプリケーションをより良くしようという有志がおられましたら、ぜひ、ご協力・ご連絡くださいますようお願いいたします。歓迎いたします。

また、バグレポート等ありましたら、どなた様も上記プロジェクトページにてご報告くださいますよう、お願いいたします。

[キャラクターデータについて]

このアプリケーションを動作させるためには、パーツの画像データが必要です。

初期状態のプロファイルでは「キャラクターなんとか機 Ver2」で使える画像を利用できるよう設定されます。 キャラクターデータのインポート方法はヘルプをご参照ください。

 「キャラクターなんとか機 Ver2」を目標としましたが、色調整等のパラメータを解析しきれなかったため完全には色を再現できていません。 そのかわり、各レイヤーごとに色の微調整ができるようにしているため、近い色を再現するはできると思います。 その他、いろいろ不備があると思いますがご容赦ください。

[パーツデータの作者様へ]

パーツデータをお持ちか、作成された方で、オープンソース系(たとえばクリエイティブコモンズなど) のライセンスで配布を許可くださる方は、ぜひ、ご連絡ください。

プロジェクトの配布ファイルに加えさせていただこうと思います。

[软件需求]

以下操作环境可以正常运行

  • Mac OS X
    • 10.4 Tiger (Intel/PPC) J2SE5
    • 10.5 Leopard (Intel/PPC) J2SE5
    • 10.5 Leopard (Intel) JavaSE6
    • 10.6 Snow Leopard (Intel) JavaSE6
    • 10.7 Lion (Intel) JavaSE6 (Java for Mac OS X 10.7 is required.)
    • 10.7 Lion (Intel) Java7 (Oracle, Java7u45 or later)
    • 10.8 Mountain Lion (Intel) JavaSE6u65 or later (Java for OS X 2013-005 is required.)
    • 10.8 Mountain Lion (Intel) Java7 (Oracle, java7u45 or later)
    • 10.9 Marvericks (Intel) JavaSE6u65 or later (Java for OS X 2013-005 is required.)
    • 10.9 Marvericks (Intel) Java7 (Oracle, java7u45 or later)
  • Microsoft Windows
    • Windows 2000 SP4 (32Bit) JavaSE6 (仅日文版可用)
    • Windows XP SP2 或更新的版本 (32Bit) JavaSE6
    • Windows Vista SP1 或更新的版本 (32/64Bit) JavaSE6
    • Windows 7 (32/64Bit) JavaSE6, Java7u45 or later
    • Windows 8 (32/64Bit) JavaSE6, Java7u45 or later
    • Windows 8.1 (32/64Bit) JavaSE6, Java7u45 or later
    必须安装东亚语言支持包
    (控制面板→区域和语言→键盘和语言→安装/卸载语言)(天朝人民可以无视这条)
  • Linux
    • Ubuntu 10.04 sun-java-jdk6
    • Ubuntu 12.04 openjdk7
    • Fedora 14 Desktop openjdk-1.6.0 或者 sun-java-jdk6(推荐)

安装Java5.0以上可以很顺利的运行此软件

推荐JavaSE7以上

"lib/charsets.jar"是必备运行库,用以处理日文字符

如果"lib/charsets.jar"不存在,你需要重新安装JRE(天朝人民可以无视这条)
(比如安装了东亚语言包以前安装的JRE)


求好心人帮忙翻译下面的日文

Java7需要注意:

本程序在MAC上分为apple专用的JAVA6版本和Oracle专用的JAVA7版本

[使用的组件]

除了J2SE5的标准API之外还是用了Apache Ant(The Apache License Version 2.0)的一部分库

また、カラーモデルのHSY(色相・彩度・輝度)計算には、 "gununuの日記"さん のところのC++計算ルーチンをJava用に書き直したものを使用しています。

[关于汉化]

本人日语盲,只会谷歌+润色,还好会点英文,总算是翻出来了

新版本不定时更新,如果自行下载的版本可以将程序的JAR解包,把Translate文件夹和menu文件夹内带_zh的文件复制到新版本的JAR文件的相同位置即可,感谢作者菊苣给了如此方便的汉化模式

选择面板的英文暂时没找到汉化方式,可能连日文版也是英文的,求证

appinfo里面的about大段的日文,本人无能为力,还好不影响程序使用,如果有志愿者可以联系rain-snowolf-wood@qq.com

如果对汉化有什么建议可以联系上面的邮箱

本汉化版在不违反原始的软件利用条约之下可以随意传播,但是希望能够放上转载地址,请尊重每一个人的努力和汗水,谢谢!

CharacterManaJ/resources/menu/0000755000175000017500000000000012560206305016546 5ustar paulliupaulliuCharacterManaJ/resources/menu/menu_zh.xml0000644000175000017500000002140512560206305020737 0ustar paulliupaulliu 文件 F false 编辑 E false 预设 A false 工具 T false 帮助 H false 打开配置 O false ? N 保存为图片 S false ? S 伪春菜 U false 保存为伪春菜图像 S false ? alt S 转换 C false 打开当前配置目录 B false ? D 编辑当前配置 E false 管理部件 M false ? P 导入 I false 向当前配置导入 I false 新建配置 N false 导出 E false 设置 C true 关闭配置 X false ? W 查找 F false ? F 复制到剪贴板 C false control C 水平翻转 H false 重置颜色 R false ? R 设置为例图 P false 信息 I false ? I 全部取消 false 所有项目均可取消 false 自动缩放项目栏 false 缩放 Z false 背景色 B false 背景设置 W false 添加到预设 A false ? B 管理预设 M false ? M 随机生成 R false 帮助 H false 错误反馈(日) R false 论坛(日) F false 关于 A true 相关链接 false CharacterManaJ/resources/menu/menu_ja.xml0000644000175000017500000001643412560206305020716 0ustar paulliupaulliu ファイル F (F) 編集 E (E) お気に入り A (A) ツール T (T) false ヘルプ H (H) プロファイルを開く O (O) 画像を保存する S (S) 伺か用に保存する U (U) false 伺か用PNG/PNAの保存 S (S) false 32ビット透過PNGを伺か用PNG/PNAに変換する C (C) false パーツフォルダを開く D (D) false ? D このプロファイルを編集する E (E) false パーツの管理 M (M) インポート I (I) このプロファイルにインポートする I (I) 新規プロファイルを作成する N (N) エクスポート E (E) 設定 C (C) プロファイルを閉じる X (X) パーツの検索 F (F) 画像をクリップボードにコピーする C (C) 画像を左右反転する H (H) 色情報のリセット R (R) false サンプルピクチャに設定する P (P) false 情報 I (I) パーツの選択を解除する 単一選択カテゴリのパーツを解除可能にする カテゴリパネルを切替え時に縮小する false ズーム Z (Z) false 背景色の変更 B (B) false 背景の設定 W (W) false お気に入りに追加 A (A) お気に入りの管理 M (M) ランダム R (R) false ヘルプ H (H) バグレポート R (R) フォーラム F (F) CharacterManaJについて A (A) お勧めサイト CharacterManaJ/resources/menu/menu.xml0000644000175000017500000002144012560206305020235 0ustar paulliupaulliu File F false Edit E false Favorites A false Tool T false Help H false Open Profile O false ? N Save the picture S false ? S Ukagaka U false Save as Ukagaka S false ? alt S Convert C false Browse this parts data B false ? D Edit this profile E false Manage the Parts Collection M false ? P Import I false Import to this profile I false Create New Profile N false Export E false Preference C true Close profile X false ? W Find F false ? F Copy to the clipboard C false control C Flip Horizontal H false Reset Colors R false ? R Set As Sample picture P false Information I false ? I Deselect all false All category is Deselectable false Auto shrink category panels false Zoom Z false Background color B false Background Setup W false Add Favorite A false ? B Manage Favorite M false ? M Random R false Help H false Report Bugs R false Forum F false About A true Recommended link false CharacterManaJ/resources/images/0000755000175000017500000000000012560206305017047 5ustar paulliupaulliuCharacterManaJ/resources/images/wallpaper.xml0000644000175000017500000000035412560206305021562 0ustar paulliupaulliu wallpaper.xml 01;lattice_s 02;lattice_l CharacterManaJ/resources/images/lattice_l.png0000644000175000017500000000031612560206305021515 0ustar paulliupaulliuPNG  IHDR szzsRGBbKGD pHYs  tIME({NIDATX a6rm g6UD8$+G8o8| xp &qfiIENDB`CharacterManaJ/resources/images/lattice_s.png0000644000175000017500000000030112560206305021516 0ustar paulliupaulliuPNG  IHDRw=sRGBbKGD pHYs  tIME% ;BgAIDATHc$p-|cccJh ,st8 Gh>|0F、&!Qy2—IENDB`CharacterManaJ/resources/version.properties0000644000175000017500000000015312560206305021404 0ustar paulliupaulliuvendor=seraphy@seraphyware specification_version=1.0 implements_version=0.998 exe_file_version=0.9.9.8 CharacterManaJ/resources/splash.png0000644000175000017500000013633512560206305017615 0ustar paulliupaulliuPNG  IHDR+miCCPICC ProfilexTkA6n"Zkx"IYhE6bk Ed3In6&*Ezd/JZE(ޫ(b-nL~7}ov r4 Ril|Bj A4%UN$As{z[V{wwҶ@G*q Y<ߡ)t9Nyx+=Y"|@5-MS%@H8qR>׋infObN~N>! ?F?aĆ=5`5_M'Tq. VJp8dasZHOLn}&wVQygE0  HPEaP@<14r?#{2u$jtbDA{6=Q<("qCA*Oy\V;噹sM^|vWGyz?W15s-_̗)UKuZ17ߟl;=..s7VgjHUO^gc)1&v!.K `m)m$``/]?[xF QT*d4o(/lșmSqens}nk~8X<R5 vz)Ӗ9R,bRPCRR%eKUbvؙn9BħJeRR~NցoEx pHYs   IDATxx\y`3(EUX,ˎ,;n&l/q6/uҞYr\e9bYT(Eb `zo %[Ypp{?o+G.Y.lh.$3ɬD7l6^3 p̌w.2uf %*ZJ|*QNI6PPe Cs\Dc^bYl*cp8NL&L'NS(KJ%JDgGlyEI>K(9JIY,EݡD<;Fܵ6A+N\զYTJM6Ӆ+^umedD"LtL&H$bX<OtB,~Fr'6׽ekNTR%J$:#&a4PZ> I|Q%%lT.+7>5P$OD u^Qj e΢1i Da:R8dɹp*;1rTV7̉Xd=#NVYѯ+rZ>P*UBV1|.0Dfѱ1^fmmZ-.-LioWjAkTrH4 'I,.)7O8/jp-h8I燦}Aޢo2)2r9CX_NJ}1#2x4'ghRiLT$oհS\YjԗT< jq0^Եy淺ii]<.t Fsl2kxr^Vk_.ƢLcN›ʄPPKA_,J(&iHl$`ˤ>V}ryC~3&ہH(6=f3f^fQjKŅ<(=ت"st:F(tRQkZj6K\|SU=^G*?l:1,6˴*UU8dGYBV1* !_LՖme.%0A. 9(LKT|wsB>j Ccse MppEI&?4D#BFU:ljjRa)I+ߓ#peҴ^mX.B (j 毅*)YTHpB%QR O yCO$c)QѪO |pm/g4+J #}De1e/`L ,1\C\t3Ͳĭ*g AkRxʇx=\t떵ެg/YG*?ږ&ɀ%S f=D D$| n2IDB^)lUֹƾ JRUJVkٟ7>BN\:a *>UTAĹwA4*OcĐwcdUԖ^r'f:BosOɬi---?P;w`44ohY^_''w{ey`{RJMu^T5olv׸50)K?WyՆFz:fYUEU-*eZ6Z/*=JhrmOZtfӂB@y6WKgAu!t"ɲ7T6h،7175LDܵd6s wvڶmڪ߼^{ŋn#Ȫu.W877_/W\fW4^~1LádmFV%1PԌwޖwd" įY!R3+6؅0R̊lA _X|^QQEHC!PR x1LyuuEfcZJDSY"4=Ó-;**fҊb3^~ݻw+_җ8pe7ډ;Kaju&k ߥBUNt Xj)MC*-p^Zq94\ +(J'3-+JƣԗED)PR(U5eQPfYj*gTFm}[[i0T%º:twbbog.O]bz S|/?dߎYMo Lޫ z+l0dy "-8¨ F@ v:֠V(̰S4I¡ϷdKEԉK78~T{-%8-U3fx< օFx<8ʼK7?!T/0JWOqkhXz mky=71a^ţ?}? 媽r(eAv_ZRfq)qVxG@=O}\VӄBqkP(-4I_X~_}G\-_sekpDX7_F^oqoݾ ɠꇽ} Ru.xF߸yX gƼS]cSLyEv8voLP>ӗu]:IwV2,ъ2Dw/}o5⏽ŕ?i8vwRcLSaӶZ<_\XSeZa¡~m1p:h:|7F='fq|*﬍UX +mYpFKA_RkZw7B؟]sOww/WW:G~qe=U+]c%^0tl|t˖-_ʗs8iwɠ!eaxWotxLp%dXݧեׂM"7}8:-jfq6v-l /js\QثoW g|dg>Yx7x#t{8}c3 ;>XI;7/kva@gMgY9\ÑrD~)_ԯxm])8췸B @,"*7fc;Pu*5KԬvCk,OCy^/x; %^¨RUֻ|~^Osfk!* s{:ܾ3csNtC I2a)msCPZ?_3‰Kp9<7?O@JmTͱTG|̚OZ$}A3=:˽ ##[>Tf}$!-Zav2s%`U. t+Dz5&M7lj'0>rg<4ۍԐYe'N{}ӖygfC5#p8>:SN:;g>G9DGUH > siQ&vzk](?0w~?FTKm\KC3rW-$geaɍ/YsGÀd"J(7^DA-3t-Mft |p|rm07! f~n=;TaiUZr$J]JXPY _SXYU VF*8xZTU^8YQYNiA%'*E78voqWuvuk};+ʴC<1Tj""!!yI4P\6]SF 97a_܊uKS}3IW+i9vTBӣMn82^d&Z IjԡtTXиҚDDPsvYgT'iNssuNxтW\_Q/>TdeR4iC9T&֪n=QJid{F'|q\dH@Fe}OR2۫g&0!z*a1! -h?rH  ٲ!GGhQ›\K;\לmwC(Nz>ݗK$IwZ ݲ{.z=AlVKrhPK|@+j\7nHNlv1X|¥:K[T?ˡIIDO4ḾQ>oͩܗ%E‚oGCOcOѽѺf*7"s˖&%0["76IeE S;=fjӧ+k,N i \Ha39֨'[[#z``#Ql{zճkp';p Fgzl;*/#*"M] F|5S^H_Y=X6/]8vAe*(:UNf4e%O*ЩR{GܚKيIcL -[ƻ|US$_6#Hh3#nZ*"’fp~R_2LM9v–#"XS էNؒcM"͕~H%*̳&D:X[ډ#?K} =5.wH$;q?7gv]!BOFaqs`5psSW, yp,ɱC Ql, cMrep4$$0y1gJNYIeοq "AYE^IOL}}G;;}3 j 2A*wرg_E۬/#d?xoCd E&;$Tɩy+2X/~M h~Sg[l#UPb;1_+843f?9JdðU!zd$*$uqמ]IJ(^f )7/bduu _nf;D|,XbtT@jNi'A7c3;LzPJXRO'KfK2t`&62;~z_\tvvj.Äv%"Rz@Z=LܾryJ: 3GyWd\ ^~ߤ;.ݓN =9HX80Hjl5|֫xef!S;~M.6_ɴFkNcQe F]sI YL%K6'wc:u:-9)4%6dqp)s;`)_h+60JMy%l\(pI1 ѽho2[,~Dro2!KF!BPf3yVM]h Jv IDAT`򤍞ĪܓCQ<Zulj>6XBHeF*Jǝ6ZoIAtcVV:bΡ#;v-#pɯySn˒# Tt8&bx\r&:gdC'F C3d&X3 v2R,'YOƅю0Wf=!PAԴKP-^>ft.% K.IK)mиv\:ߺ6U8/ ꓧ "b+J0,Lb'm"^#W#hhltzpϡ-B&X gѠ̈́kiW>U8.ߩ|5%?w y{kF[mr *N)c]Դ?s£r X1<8Ne14O Xێu.,e#" HzNfiQLNN>VP`fȐRPK,*H PDAhH&,iXub1BR$ɫ\(_!^ДeeUlfmz vH dRZkj,tj߼#B5xx7v7McOGjܼWxZ&&.? }<كkeɢd6sS,Hx09/Bډ7Bgw v S-ꏨL4c ̰OH,렺rWC,-+JBFf"[=c?[k/CXWGĺ>aK7x3`Q@}|`*J$ȼ+#Fx%n(QF.Pd.rB`ݞ1HƜsQLQwvx,}∹SYzǖbk!:Ow_׀xǐ|j+|,( VhyYBsxu-UժT C0s%Q`ɅQALL͈j1OK.QiC 5XZZ,m/-wۇІD> DoW\uS\}4[oG֝nܶ{ \*1;9b|31QA>|݀>_zy1苺k#B<#?AG'yi2@vYat TN c{;?~ݱgΊtP z=ˀ-a1sRR)[?ERt-hVg+hTzm{7WC~N!)`^fKDxDey\XftT LU: kfg2/U5[peF_^]bސ!:O+zC[-iΞ7OcmφH^B=N1ti<A/|S-c3|SaK@*|y=<( Fhךl3KԛwdshKd* `.2B-xB !d:0 PH]Ih ]sG/uU/!|EQQ&B:? ݦ9O _kg5S/B; ZY%o9]V%bLlb8`~{. mՆ)NH(Sdq1S&3i"Z ut<\Un07T'4.P9 8Lstj_ %X"1@4 P ?슉Xbm[L6hߴ|w*+-8ݐj0}gjIeOގc~΅*k]g/6hTdS~('C)7=" oڻ>Mpj9SS0A66v.CӮ vK$"E ^TeZ|M|$uِx_$<*1HGiҘAH7q?3L=%mZ E A`e{a (25. 6Ky)cc1`9d_u3 .bg~p~ڲ=]s{_|HX:x-65:ad[)K`$% VF O)B̷ 9% Yai$@"Rg:ǥۤbi2XJ ͝/\2 QoԢudLG& }f:[D68drr!MА*Jrse-aEFFFsICY o#bɐԅ;U)%}NZ"Ys/o432Kru&=}L꪿1D 6VjAq[b L@+k-$hXv-MpdJd`h&nMGP@/P2&*Ϥۂ $S!4BIjNuryB Bږ3 0rZ-A?Eq¾|9oXa'N:F2Sp `uv*8T|$%YC1+(-L(i ;dgg%Z{SΊb 5M|n~]c\1V$#{8y5{P["PXK"XoIR",@⸐W ת6Q'18M {4YODFLj-lU1R{'maQq$-Fүh<쮯~#'6 KHhFA|X&pF^ S" ^x˒3|I"唓LH/]F/}=Z^|k W>ץhb̑ c{{>)WJu9 DlcDc-%2 ɋAnqe$V"ɠdlr!WAb G%Bc܂2L ӑ}"mȪbX?@ iYq@R"\P&TWeiwF'ŪC݂gqY~y5!Z=߀|RǔZW%f&KXp 1,5;;3K /9>:Xy7zώ~O#}Q`[(gaCg0LpRj 0 wJh\LK|"&lD\gF!"ic$K5حft' G?F8BQv: #b#_^hTIOLQo2WN ԤLNV `p`wRZv Aegr7aGZK/vy2'j;RaԒpY`GaY|J=6yHRd12F)aB}#SѤbw#ARQauG m9XX:S\֍C/KMkI1q +*+Ӿ|WE ԀWsd`.|/ZtՎ֬?1ZG*,b*\^ʖBanګ nO_zvy䫯2.ϼ0z1, 3`x@Mx#`+ZN$6 lli4z87j2jJx"DMaT4fáHHԇC lȀ!f>?6 w !$jo !zɹ4; rQ uHd1 9il^! @x.i+-x~su4DO-{ia[tM $(ޘȶCHq_ c/?Zye]pur|4h-DwJVO0WV;2 fN|TQ܈&ZqyJU>&#,cP~qqkMBdҫR܌7(%}Rb~9$ᮻԡ T 0'qzB):QYӬS.YHYlwot $!92c?ܳrfec?› %U8Fy[>t/B= FcBZY2V҃?M߸Cnϕ IQPH0:93M 'B`U9« CL(EK&*"!|#s4y5'a 9BRsw&&^Tb]qgyC^e6M ׻^&t VOd6lglY[hS4LO}-3ŞR4}1gB) t.^(_$* R|1- ˉ7j2篿1Vp4J=ᙺJvêTp]lD7y(6uիz43H[o\T0 :/̖}wĠ TT CNx$F3/Anԇe G[ AP^fp_'R:]fJfpj!|Hւ9V{Zb8ݪRV)L%_4WH)p(߻u绐SqPefnWhL80mf77nyF~d0/T>#,ژJH*zRzʻbXC 2FebjBgǿގ-]\Yx9*O;?~Il3/KF1;_޼; QC? ,RWܷqإi(4Ņ_`@[6ϒvn2 Tq1-JZ!>rPH*?.I;r zJ!B}e4/QX h4UH̭BMTLƐ7#@'ue2+r0Bj.X~1iwUDzwt9M-8CnA[+eopp9矎 L^*M Dc F(bOnڿjsP[w'ΌY b(,QF lq{m=6vm _ u gJćSh!N[ΌYjD"PߤAz`eUg;64 `ӔiA{vi3Wiq_}m_Zjߴ%4dR>榅WK7y{bi^YE{[wȽ+s:9<uۉv[JD5y/aD 5?&!Q*EozN>y5Qn%Ju,{Dα8QTb: $\];1L@F9v,Dgr!o-[PbG`5:\E3ۚ7 jT.:@mRTbScls8: /l΍[kyhw oS, W,;5jF' IDATF{`JI(HY}~ﮏޣS[g'8_;:GxeW}s^4C !P,Q"vML ֺgwfcM g͠,KVHYAdFXEDWeN`ջ6 ɐP(ėfFsirj0U"¤CB$rQDdYYtnM:SEJ{.%/,)hc ˸:BUjdž©UwAܼoCյwF^-\GOt裷uW"7%iZiWexY3pA(ܻk'x$$FCYNd`|7xON@Xee"{2&CӜ/WNm*r,mo!U$ɒ>2$ T8zqHJ7&LΑiwpjO$$D$qE3mMF}8`-`/Ńd]7P*GFmi@ %a_8d*Չ55oD_ySKN_[vO`S/_8a ֫z8I+\YG~v&`̄}XAQbn|SlZ# l.9{ZG ;'xK@E*sd2ir3g,|W t!;T+w͍M04޶EӲdX, p˥`b  OfT*HM3;,.A$T08̫G$¡J; `łM7!z*kF;Wֹr 8H*~:K}'q%&9m.`&M:g4h !8>C  A!(loYS` +.xu޿| U~&F 0K%K۴?zC[yɴJS(a\·efԄvn±XHM&DR%Bݵ_}H4Y o\vEBrk:^Us;N~ X*B#oΛ3FRʮ| ŋy?xIEBRuyJk}o2Cino&2o1CdqQJe}UP-APQDqWCi4fi6k>wp e2T(O ͭ|9ooBmķ_:AYw4j|ϰ;k>c8kWQ .ZX~҂+QF713*c GDᶮT.}G4:وGDKݖӽnyŻ\mzˬrUD8HMAФVl <Ɨm['K+l9GEl,Z>#PV1|iLUY nWY=29MՈJFoXLZ\LR @X('E]׶Ppo$6xTEмyĀӪ UZ%l_6m̌Pߣ j"LTdvNI4:ϩCڬ5UV[݁h{d FMmYnuAFm#iM"+Ye{ Eh`')>ǎ{'y VirY.ŷYoi񁮘 7\5SPvֶ)<7R13D "kņT|4l׻/ {9+ z)bU\]T81Oڬ7z?'?~[?z_!["/p BJ?76QΠy zGhDP4 5{:$wmSkuUNgpxz!Yv1;::&''A]4hYYAf5&PH&"MMLseC9"8 ncM+j r`[΃'?zd)B ~8mO y!(A e&JI;]K](!7|_{wNAEѨK4:AlJCuaS'])@#jQG _2=?_n@r9Z5iuiFp8<00PWWW 2/DH7UmզEskUu.N9<\{Laeϲ<yeNɵN`,fˊKJ1x◽|&{Z&ʧ`M%@/|B7>G ~˚ #/~o?,/sߵaF}B>,>JR:*! |G#<95Íp5f٨54|PUd7 G立'$e J>s`Tj& Fa87FР f-$ʡKc,PR`9>ݶk8_x6t[IqQH]QZF_!wOC<߄o/ofb8y[`]N\G˹<>[c%pb S{ƀ0!.HE +(BeN =̏~?,)shTt؝UZpHFFF{7 D΁֍p Ts0{ +W..U-i!sP1Bl*l[;GG:Zfdj6?'>_~m=+4l|pwPCCEѣg;~?}0cس]Sc[Yrv/t̹P(,n./  Q.lXo -ՄBD8 V4V_멫z 􍣕oM A憇\W_dYM̀9oVw\$r~v^[ED| r'}cmݴ|M\rW7sv[/\,q\\ ⳿oͣ;ooU]^WVa\9ct*8 W9\]g&ooܳ+L75];}t\y~ɧ&&9GoT^A^yo[Rpb].Vl47/j}2D8M77TkV0zdZԎ0HtʂeY2E8陌S^?Mbr[͎>~<-:6Ӣ|^JVy"}~ Fɑ] ,!Y<r.ԕ?ٽm3ߘ捲2SdwZnj'aU"[;nS$~\"G*U tjaU'3|fŮvViUj;;90;z<ݮu֕mp+-i챱C0JL~?}Dl 8%=M<>o<ɔ'`&2}crzG`ItOU cG@@P,XVKܱGy]_S!vχ0UFWNM|es&o'i .T4jb{~Ž +bBn5*Yh 4|IE|7]M]:e`I{Iao8oP$5da7R' kGG|{ ctѨ8ߕQā475>];.M #~+TyHK/5M '8nR=w.imcޑxNUտ{q>al\8Xzu(|4J/ˮk}fb赧BJ2P:W۞FX/^?|Yp6`9LMf5_eϓ;- G{sh*RivZJک7B]֛Vߵr>ǵ)cڢ}xVECEB`& n1>at!^T3;:l oO߇i YK}BYGWΖ8@ߧ9x7Ô! i۫7l%c- &9;&Cϔ&%VSmS;=Ao,^O0u] ]^9#c";:ԹDVk8~Ua~p:%{:d|[8Ld>[>ubf9 .≳cp-S:Xeo(4rVG\YYsmsw4x>?ʐiƒi$8^3/|m@•; XOB89V)QM$/Mj.4# ϵml??W?,ix,eyBEfX8p<|WwF}Rvsuں==,HO |`d8=Ҵ~ɉxW%7ᩣKeMnd΍|'7y< J؛l{rRz-ϟORpJ!V0G0ʜ4IL{plL$Z`AI6uvj)) ;pY'fQdġw߂._SY?0xjWaO.ʺ_p:ҩ;WZahU8?#\]@]eRtBY_BT7-8^B}IEQEiW\pSbccT}py"ho}[x3DҞHH V!ɨ]yb jH[Lk)CYng?}nzk'/tStw⿋4jNhVdD-ؓ-N.2X8<4(4H|PuJ[R ,h .<=u7S frh"9NqˎN2_uXAVnXkp?߮}7߷7"9a$h(@"2쉢M#cR) @,Nve f?$xa_ T0>Ʉ7UWȅO:vZYڐl`r'iM+W[g' O?lkt\x dl :8DQ(p]O2﷒N@D$4a>ɋB:']i:y(;␇~ԉ-7pՋ[I˼-ja*8jG'VZc%뚘wu;Np4v:ZOOXQn~OՕ h+ZGFފmu+?'ޡc%7s\D9Qd4(Uy۪$d$@q0_WSg Ah 3'% =T)Rxho_4{iЊl)Ztu\|b[ot|6sb"1$k[\sm^xf߻Ѩ/瓩\2% [,KV \ψ!| Z"T;{ F0F'tRtS%b% !@1H>{{EJދ-[cjB |$^RHB yCfk%mӮgZ57lb4jvf{9sj|$W F\#!9 !eLON h4= a{ |s%SImpyNBEyXj,b}P<$A|FX aD=bI,L: Pf-9I7pH.|vάADxVmy˯BHbP v,0gpbHNeΌ[ \ `,)mOF@9/(+T8|!MPdHZQQ|8AΑe('Jr%ܭ p8Dt8_&xQIut0|p\_7=x / n(H(($G"I$C\[K4 ^)> hf́& vtRfHQo~n!ۗ)%UAd |pK>Y4E0=VLB~!Jb@|@'0ϣy8u. @#i4ukeBo;w p@tщw-YE'`( ο].TCL,O'}rDDVMjv&*sLVeU~96\NFvѣG8j5Y|>S,b )0%F@XI}Wjmt>_G.Iʆ3z}6HǕa j'z (!/4w&lY|п,+olA˾%u"6KEݠ&K8S@ yavF3&_ tH3 Tm IDAT<.m?"$DoH܅|~ٲ_/`RKv O8tj9͟nXP yߟm[&-Mn?.]h) .+'ojhtNd#0t& D1/4 ݂B0 ?|⥋c.w,$7xJ#^ ĝL&FxQӝ6Qg zr##±bv67qm,(qa{l}eWC* d1w΂ŕׁZE)boI5g0,lM 2H'PβK:/м {l/ p2LQr$ Dfq9ryf޾I4AȤ"!Tz^=|O>jy˳55 aV&Z&WP"i;Fo+C#'c" BG|~M.Px#kkk}y5k9x )4X/D= AqngSsj %\ 1`V^SC&͕+*H !Gc&n+f ׃~qu*kNJa;եmJ%0c29M唙6AW;"}Ͻ~O5kji5 H8No1؀(`uhÅ!&L~s^Co\k$Yl?&[mR3}; %YZ(~pPa*F2UY=j.x(9\`LBH+W"Boӧ8jkHاnY;#UJE٩53 ͒ŔA eᆌD,#V9W,>1ʻd,&H$ *+C,*i CbL4;`39 *sÆӢD_l۲bC(T1XpF'^0 c4/ =(UyaŚShnq(3Phpbq[F)*6-a?ģ0# w^_"GHWInn 8\^3)Nw .g O,da+hw#`M S*'CaPa ̨HbBd2Qy` -m|Aftx d: 8,;' ?޸a=`}ă/y~ҥR]ྤJR$ !ABoK>(y ra bIgIPP vJ8Lj%)DY2Y<4 f2EHЯ:[>S/t\S4m %$X[nu SvL%lK#Àdܩ[&m %I&"oDOK%0<5P i"hLO~!_Pĸ/`D.$^N4 ^M?AT:a=pQyAA< G֖K=с .0!1$|t3jgER|^ZoD(И1pEf.jZut5E cavY\>'I 8#p ld3d:7E[Ͷyㆽw]$S kP88,k5]2cLB%*AСHc>RXW[s֦c=>=moj"bnO QR pk F}? bPbs 0A3x,Jm_ 9|O>`>ѹB!aw .c1eW7 W{i%[q⫴]6No8\h~U?~m% "vh ".Wy}@(OɁPO_+uC͇CmzOrqK q4'!~ |S}}}bfAoP&ۥJ*]>Ckl G.SUy9YM'[urX(!4 h6 6] PGIdBv˾`UY5u9۳4ڧij,J|S-(RWU3= xC0Å HEcJ*DɌ T6n1w[FW"7D$|xjnor"f"+7sG~`͘.WX}#ݱW"!~HY.7kXJ}=OMW>A_~yT: :S[}(|MaQnI|U 05T:NW2 LYxg1ڈݩ[Lb֬X XY^S5ލ˗r ^;,nokmհJIP7AڞUm(bݙ~x3-Mg{v@ݎ\.("ˊ9" 1MV)oʲs8s/ơpD΂5 ԧO2OSV]wd֬؏~A#X(Jʊ@#<^AijH\\tJaFLu˖3wѾC˼"a NB,QΘ=XI Z'˖h3>\s =+qoT醩B]~hdX^i&NJxa#_8 EW hgg?~t+]WM?Yf]#L\bp(|LBgzukW f{@ !@9Aٺrr KQd 2uubwf3 Y,9C"7{`Ӈ3lU~E%! F -l|Pe05$' 3vL?_U3CmT`3;y;ۻoKiȮ{O>*RvXM[6"Jiyi%0{*<3' LaxE|$~<|ۺ o^ D*#!PͶ5e̤43w^#UIJKmi4|NL$sPҿ=_xviXlzUܑ74PCd/3%95]zk}P4Tתԙ:~$r 2u{h-x !;ofDq{Q?WLBg}YyꙹV\Q__aMS8(0@UVزdX,X!ʯBv:C?io2YKSNٲi.KDlCTˉ2[',糯\1^{~+7_ //W*S1}.Aұ)J@PN뷘o+gN7}H 0&!0L'xMND܊ʊcGOtM] 7[+t,]"&ҭ?믈 ʟܚP(qHºpJ29ήmvljv]`sͼJvpor@kd?ݽZ&[ףfpaP(~/uo}n.-&`r.j*p"=+.^Vœ83Nx.P4o|`{~h |Ji0^Ӯ;h?{ ;Z!0hZpK֛UkWר{Q`gW5lqY\tLQ^?L8AƽiyLӝ?50wb ESyNC\ux^]^;`zz8gNc_?V[cO}J 9ߴI =2|l% O2Jv+n{-|(!ZBdp ?2l߼]ΜK*/.+^b! Iu7, ^/qHxAAQ"Ii^T3pʢB:!ѹ<M'JbB0,JJY_W=.9,-;%rɈ4ӤrѴY8,.I߯;e=~suEƔprZtR(G"`ߠu\F6 -a(D縧wW3=8Y:\-VfJx"Ѫ7h M^V'VP4t_ {`bmk ټe"񌲢ESz9%P`] IDAT(UJYF@D, '\3M;}s1W40X<+|`[>k 7&3J ܁ T*z5 8cs/ZIxo *Lٶ+TOV]/I 79W(@kS-K/P+(麂ݺ7Yʌ\vZ1 p7kH[{z#@.wJ~?s9le]K ,SP`TvgΩ) MޫoqYuͷd+`Oc F< q7՝;Lº]{"sVUŃ{8[;XL֚+Vy ֽXR֬y`lH0Z":`S jEmaA4zߴm攪oNXV 8 L߸#PF5Qk3M8wtɎ˖HUFJ:}u}̂0.P%O`ꯜx j4暠)yء;|:[ 7-g ,lMFɁ<n˞xht> > aeҽ;xH-e25__bz- % p[$˫ -ho~4sکS yRZ|&@_K|f0D+$8'z>}~Jgۧ U; L=榆=cŚ1l`/5 hHCjq09tyz<~ӧm޾kGSs/J9u3Uj"&wm!D%w3H&JN3Te"L T6EVң "+Bd&`_ȧz۬ vzl-$WQYqf ޫ7XZtF;++*rVWVV@:ߴm'>߲h^ou0%!xH D%`0)** JX~El[M*3ޞƶx$Vf)I[,>e+Te/;_&ݑ0//l @Ę+c xoR=(l"C"PuDcirH^T|zEzzwm b8- a`?8EtD!BU"ZMTՈ ` ΔQGʪe-X2d4tW_qcm?t {/~0*K-[@ȅv&Ůā<:D ~IaWAa^aY NA^,km64 h]Œ1OrAKAXG\  0ED.R`s` %@x]R<|0@oZ8Un>ظNb YgP8fc  wS`P>𭃴ZUQJ\P. u] O ˔bª Ӱ1%DBX6x+ e{LDZfLSsFmnhR6h2á~r;:pĥTc<% zN#e6 rV_]rs<@$&ytNh8ڮь} Q">][`VxiC|w6ˀK ˜3; S]kkJVE$,,%%bXz|`Kÿ5KC?IڣC0BD2>JWrϦ7^lֿ_K"^\-ɀQZgryC]8+x&!ޙk2c.bp mnz /d9.I(O=ym9*ڐ 5Blo;z %S*:;l4W/*WKE! "ep,@{)&RA1 I^ (eX,jl;vnFӟ}ǹ|[ZTh2(vƆ 1P[dUhM9L& 1=.c^CM[N|C+`z:;?4ҪJHB֌I“-{2ɮsg.~5o2k\4X$F86#a)s)I 9Nod$k;j5sW{{јW3L4|0ڛPxh Yi wO=69@(Ȃ-P/ױB|9`sykV0\Fa6̱}VM#bi,1&SfMxA @Md!>qB-&ogjM8qV%|>Zg\ǥX4.&QBRHK(Zױ|_ΞqlR^- z=T " ~_0gAq]cyBÚ>|.vXXXdQDG3Y"zLnwn^<32K+]ř'M~o0$*fTIn)Τ538$Iegm~]Z )nϤK׸:IwMBBl%)TXK\M:R.D@'&s{템SZ_P$uVx,8`ןh8kVteԕ%YťB(! p]lc މB%O8D&PQ0Wh(?Yp,D{5,3Q)U_cQI6Jֱu1T{AcQg:|P%3̖-0d2,&鱸%X_SRT.ȁ@^Qfa8~Z>V >7h4%53fD0ZcQ+&3;G q'vp&y D7#d5}^-f#c tOP&Y=@.ǽv?y3'LƠ֜wGBg]?Ң2 ,S[F4k Î=G x\TUgi=>%0v)3rBg8#S$ƞLiK,BU[&!T(X3Npn6z`IM,} O-n_pA C3LRô~4b60IK|?&l,aAD!8_K t;:l&;7`FI%ĘΎ,jybI}w9rNn@=np_ʷvoM㢸Bxce˖@eDaW( BDC{茄&O*n#'9h(l vyG0a </9+H#g5 (3aL|h2%Z,l8,ļ ȱ.Oԃpnno,΀Ge)a_c,:*fU/oϾ{z%#+QHqS#q8}LE~#H˙ө˖n߼wʥYڑ4[2(v}!eDbV |Z|NmҐ\h)Ԋ o/E7_ nPΐEDN3* nM:pAY 6K*b :. HX"=ݺ%eIn^q&sf]%dy@Хsg/Q3A L_0j G~z/Ι[+ԙZ0~wLY% h*%ބp+QbdS7s-!0eX`to*_!*՞x[mل` xMNUwF!u~LOwԲGWg/O7}ퟨD[^|YB+$?Bu(Qex ! /}YIT@u\Ûr5h 'deaqj {vSา_ޱ@]-;zɬęUIv̚oR~Kb/,Zӧ4|%~V !jRbI+^w gߞvBbHuoygQ`%w5okyx' L"9z ,n;{A\HtJȱ!{G1ZɴeG + F 8ń:uٽgo}O[7jnE?Ĩ Psm[>OgVV/9^L'4n׽ol#ZZNn /~ݣ./2K95KуJ0@N+7`޲18mh GObbddpY[[:!,GfUL\ճc_>IK&G6K ]8l9oQ(}6I ȸPaH,eWRܚC>mӋS>og.Yu)B`@@]lp<"zE/s ! I$ve }p(5?> b b-֨*i~:ξkZÉc;zG] !od;_x`C]˕)>:D28/}><(AB=ÀxrBn;iJ WwW9Z͡oFmx4g jDag U_&jtp_?x7`gCFQc]sܟ/tefMz,$xMR?~6>m˦QWi=; RUNO?^H/ ,k| <_,u]"%]SԷĀgjidk6 Ά =yI_LӇ@Q=bgȨJ?-ZJ5Z)33f 弄JoT_%B|M| @OܦA0Cs__ȮX2Do* Rצ2msΊ&i5}s~"Ϯo-^,Ort?|gb'zx]ʇ\jL:9_hbE\Fz7%1#am߸1U}/H %֤)JF5޼y|[Dbڿhk\P"d O۩3h~+~aù_F:*kdIltȚ~g=nȝ5j-P ~pyDоuSټ}\Ӯ6#=rǝ7~sG&gTTB-J^7܎?L:}+oT@,q%24#Y?c#x0cësKpGO_[? Om<ʃ1_O5kŵ%JmtZ)UrN!ޅ[@㼸YIDATU Xw. G ~˗] VQtN 49CcП9f;H8 \P|E&c]#CyJ'L`*qؾeB]H:aR*VlGr3TrY6#۶,yR;a.}h4ԋ?mNwIB%"Ac(78TL+]K'BT^3U5UfNR?Ia n>؉BТGy5MV9'Q fP #1&gd,f_w 5rg.\t !N)\<ŕAdB0/ R !d||O}C#v)]VU`[?ű dD= {<1Y<NҒG P9j!gU@&y+ {֮AͰw(Eā! D̜R8{Ce{F^Uo=Fy}7Cp@oq=}[5 E<.RdJz cKNA;0tee,ui9~uYvJBP ү @Q9jR\Vg:zuF&L,UrVv7-HJO~hk={~ `i pҐM՞o*.YIp,#ŤI]X@<,0Hzby\A;jx]j&7p9aŨXt7*C`IDgw!'9]Ê5Wۚ1 yN(R1-?#[M` y{GA"50#Nñf[#Ll1))WN6}JJ1o~b>98g4ÌH]RƗJblU,G,K)DMz ,R(3 GPc[5{20[t9a|\zix]zs1#XBRHN_kݨy%#&HB4RL84UXP 6ͪ&ʍR| PBjN@LL `"wҍb0‰r2CR,}8HW8qL)*(L }PRWPOPwR-*K)eq)լu>m iI%8ETp*zɈj6tֶgXh#%+;H^zZ #"-4mz6@z vj;'[fI;!%"\v;1FڶP9:P;P -'mim 8 x`+m]`TzZe^opbyI)UUr!Ը IK::dT3'@YZ 6ܒ L,#C*AahPcuN*3?L N$[[CH@t|T fd. L:[Li=5FU)d/;7}响# *bޝɽ`!TFSVS풀[+4O39_h~N] jF:_m>e=6|7Ģٷ6t<Jv='a^dԂ5ZFkjjqe߳]O#nb6&d.%jvps2chܓp:LFnUbނh8t^-M]憮۩eW]+&ftd|^_ gI_/i`z]7 by3<AaeCK|~ taRn|]>gJSgLyIIͷU4 n{;LCb # `BZsh?5֧~FjKA47~o>Wپ6N< A)7${wMh65ڀDQEՕփǩIX*+`xOdP$HP&˗][>'B\k&Z 8a$'39v.O^59nyz/9D|Xxmf)]:A*,}5Hg"BC/~tC{x+F >$SAa lj,..Ѭ}8>"F|.0)ކryFm.^ysuTj LϔR,35 < /`%IT]} /#"- х~id >!6!?-?^JTnХB@sy)_-.b=*kHx屡T3^Xp)~BX$sE]? pRBG', !BfXHHS=e}i?5{f\{`Pn.T aۏ,)+Wl6?+wz$[SΛSd՜9J4U;Q&e0QӔCeAmAz{‡wϧ7px!_jr@.~y+I{6SW\-)&W3AlBLUO$|Q1Υ(ڠ|:= 4{bv( A}mœ?iY]G}KY v@+ +*4.A3J?1!M3?;tj;M43{;*) z5Cs?B!7a.rgϻef}WL(*eQIά!uUoCK_H|b~AVZŗKrIVUT#f^SJ59n7| (Su0H  :,gZ & eqji$D{]9p'w\(ZRX3:m^IQ]zG/.H*r@}!u_< I`Z_2'vg]``DV>".׫]dLJ[)\\]d1(jNP3WV(؀Nz$9SD1%w,P)N enr(k^ MJ@#O{I]͛ 8k71*Odꊳuφ gϖOyBNDb'ALN4ACx$WbI]z5EXp7F~ْ tcڎϯ͛q@W^v5K,`s;<+:%CVg}W:L=*|F?o[?OȞ5҅wރqώP0+>?~cҢH5RcϪpFmuD8T-]NQحlڸYfY)ٳCYA x)0C`ի\Ȯ(wB"{ۓ1fmujF*Vp\ ~$~3x^z4)laA=kQM30b:=6#j00kW>3glKKu8T+NcZN[RZ[0]eפZ'5ٔ(eBBT%Q<أJ7D{"mH||DV ]U 9=Gۂ!?,gzˁQ;0bjWCqv8XJP= _4k6ǪQ26mb8Y*׆'-x1ݣx͐:k#_uLin(Ғo{jwkBwkR|(}gGTTJE!ydn'X`FX'ZJjF'55bIaOl8iոhW{IaMĎ汦ZJ~NJG!9Mt ?'2P6++_ !HuQ+Ҡ*V' YF65·U28&SG^Tk=\ؗgKFȩ NbSȮAs^::*1HL-B%Bƨc>dcG8_l{3YS) [q "sŧpVX`qb\ NW-NjL デフォルト 名無し デフォルトのキャラクター 300 400 true 6 髪型 - 手前 可変色 12 hair_front HSY アクセサリ 13 hair_front_accessory HSY 6 髪型 - 後ろ 可変色 2 hair_back HSY アクセサリ 3 hair_back_accessory HSY 6 9 head HSY 6 表情 アクセサリ 11 face_back HSY 表情 14 face_front HSY 6 15 eye HSY 可変色 16 eye_color HSY 6 身体 4 body_back HSY ドレス 6 body_front HSY 可変色 7 body_front_color HSY 10 アクセサリー 最背面 1 accessory_back HSY アンダーウェア 5 accessory_underwear HSY 中間(L) 8 accessory_middle_back HSY 中間(R) 10 accessory_middle_front HSY 最前面 17 accessory_front HSY (キャラクターなんとか機本家) K.Hmix 1st Edition http://khmix.sakura.ne.jp/x キャラクターなんとか機 追加パーツ保管庫 http://nantoka.main.jp/x お気に入り1b -1.0 0.0 0.0 1.0 300.0 0.0 sample1c 6aaa 6aaa 012反転 -1.0 0.0 0.0 1.0 300.0 0.0 010c sample1b 89 012色なし -1.0 0.0 0.0 1.0 300.0 0.0 010 001REV -1.0 0.0 0.0 1.0 300.0 0.0 お気に入り3 9 sample2b favorites--1 お気に入り2 sample1 0101b お気に入り1 sample1e お気に入り2 プリセットとかとか sample3 0101b プリセット2 プリセット1 お気に入り1 TESTREV 0101GREEN プリセット3 わりと好み 000 sample1d むらさき sample2 0101c sample1f sample1a 001 foxy 0101 default 0101c TESTREV2 -1.0 0.0 0.0 1.0 300.0 0.0 001REV2 -1.0 0.0 0.0 1.0 600.0 0.0 11 0101GREEN CharacterManaJ/unitTest/charactermanaj/model/prop1.xml0000644000175000017500000000022012560206305023227 0ustar paulliupaulliu 1 CharacterManaJ/unitTest/charactermanaj/model/AppConfigTest.java0000644000175000017500000000665112560206305025033 0ustar paulliupaulliupackage charactermanaj.model; import java.awt.Color; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.InputStream; import java.io.InputStreamReader; import java.io.Reader; import java.net.URL; import java.util.Properties; import charactermanaj.util.BeanPropertiesUtilities; import junit.framework.TestCase; public class AppConfigTest extends TestCase { public static class Bean1 { private int val1; private String val2; private Color val3; private boolean val4; public int getVal1() { return val1; } public void setVal1(int val1) { this.val1 = val1; } public String getVal2() { return val2; } public void setVal2(String val2) { this.val2 = val2; } public Color getVal3() { return val3; } public void setVal3(Color val3) { this.val3 = val3; } public boolean isVal4() { return val4; } public void setVal4(boolean val4) { this.val4 = val4; } @Override public String toString() { return val1 + ":" + val2 + ":" + val3; } public String getX() { throw new UnsupportedOperationException(); } public void setY() { throw new UnsupportedOperationException(); } } public void test1() throws Exception { Properties prop = new Properties(); assertTrue(prop.isEmpty()); URL[] urls = new URL[] { getClass().getResource("prop1.xml"), getClass().getResource("prop2.xml"), }; for (URL url : urls) { assertTrue(url != null); InputStream is = url.openStream(); try { prop.loadFromXML(is); } finally { is.close(); } } assertTrue(prop.size() == 2); System.out.println(prop); } public void test2() throws Exception { Bean1 o = new Bean1(); o.setVal1(123); o.setVal2("abc"); o.setVal3(Color.blue); o.setVal4(true); Properties props = new Properties(); BeanPropertiesUtilities.saveToProperties(o, props); System.out.println(props); assertTrue(props.size() == 4); assertTrue(props.getProperty("val1").equals("123")); assertTrue(props.getProperty("val2").equals("abc")); assertTrue(props.getProperty("val3").equals("#ff")); assertTrue(props.getProperty("val4").equals("true")); Bean1 o2 = new Bean1(); BeanPropertiesUtilities.loadFromProperties(o2, props); System.out.println(o2); assertTrue(o2.getVal1() == 123); assertTrue(o2.getVal2().equals("abc")); assertTrue(o2.getVal3().equals(Color.blue)); assertTrue(o2.isVal4()); } public void test3() throws Exception { Properties props1 = new Properties(); AppConfig appConfig = AppConfig.getInstance(); BeanPropertiesUtilities.saveToProperties(appConfig, props1); String val1 = props1.toString(); System.out.println(val1); BeanPropertiesUtilities.loadFromProperties(appConfig, props1); Properties props2 = new Properties(); BeanPropertiesUtilities.saveToProperties(appConfig, props2); String val2 = props1.toString(); System.out.println(val2); assertTrue(val1.equals(val2)); ByteArrayOutputStream bos = new ByteArrayOutputStream(); props2.storeToXML(bos, "appConfig.xml"); bos.close(); Reader rd = new InputStreamReader(new ByteArrayInputStream(bos.toByteArray()), "UTF-8"); int ch; while ((ch = rd.read()) != -1) { System.out.print((char) ch); } rd.close(); appConfig.saveConfig(); } } CharacterManaJ/unitTest/charactermanaj/model/prop2.xml0000644000175000017500000000022012560206305023230 0ustar paulliupaulliu 2 CharacterManaJ/unitTest/charactermanaj/model/CharacterDataFactoryTest.java0000644000175000017500000000262512560206305027200 0ustar paulliupaulliupackage charactermanaj.model; import java.io.File; import junit.framework.TestCase; public class CharacterDataFactoryTest extends TestCase { public void testLoad() throws Exception { // CharacterDataPersistent cf = CharacterDataPersistent.getInstance(); // File baseDir = new File("./characters/default/character.xml"); // CharacterData cd = cf.loadProfile(baseDir.toURL()); // PartsDataLoader loader = new PartsDataLoader(baseDir); // cd.appendCharacterDataChangeListsner(new CharacterDataChangeListener() { // public void characterDataChange(CharacterDataChangeEvent e) { // System.out.println(e.getPartsIdentifier().getPartsCategory() // .getLocalizedCategoryName() // + ":" // + e.getPartsIdentifier().getLocalizedPartsName() // + ":" + e.getMode()); // } // }); // cd.loadPartsData(loader); // System.out.println("*2nd"); // cd.loadPartsData(loader); } public void testSave() throws Exception { // CharacterDataPersistent cf = CharacterDataPersistent.getInstance(); // CharacterData cd = cf.load(new File("./characters/default2/character.xml").toURL()); // // cf.save(cd, new File("./characters/default2")); } public void test1() throws Exception { File d = new File("a").getAbsoluteFile(); System.out.println(d); File f = new File(d, "b/c/d").getCanonicalFile(); System.out.println(f.isAbsolute() + ":" + f); } } CharacterManaJ/unitTest/charactermanaj/model/CharacterDataPersistentTest.java0000644000175000017500000000113612560206305027725 0ustar paulliupaulliupackage charactermanaj.model; import java.net.URI; import charactermanaj.model.io.CharacterDataXMLReader; public class CharacterDataPersistentTest { public static void main(String[] args) throws Exception { (new CharacterDataPersistentTest()).run(); } public void run() { try { URI uri = getClass().getResource("character.xml").toURI(); CharacterDataXMLReader persist = new CharacterDataXMLReader(); CharacterData cd = persist.loadCharacterDataFromXML(uri); System.out.println("result=" + cd); } catch (Exception ex) { ex.printStackTrace(); } } } CharacterManaJ/.project0000644000175000017500000000060612560206305015241 0ustar paulliupaulliu CharacterManaJ org.eclipse.jdt.core.javabuilder org.eclipse.jdt.core.javanature CharacterManaJ/build.xml0000644000175000017500000000736212560206305015421 0ustar paulliupaulliu CharacterManaJ CharacterManaJ/.settings/0000755000175000017500000000000012560206305015506 5ustar paulliupaulliuCharacterManaJ/.settings/org.eclipse.ltk.core.refactoring.prefs0000644000175000017500000000021112560206305024775 0ustar paulliupaulliu#Wed Jul 21 22:46:49 JST 2010 eclipse.preferences.version=1 org.eclipse.ltk.core.refactoring.enable.project.refactoring.history=false CharacterManaJ/.settings/org.eclipse.jdt.core.prefs0000644000175000017500000001164212560206305022474 0ustar paulliupaulliu#Fri Mar 11 06:56:48 JST 2011 eclipse.preferences.version=1 org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.5 org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve org.eclipse.jdt.core.compiler.compliance=1.5 org.eclipse.jdt.core.compiler.debug.lineNumber=generate org.eclipse.jdt.core.compiler.debug.localVariable=generate org.eclipse.jdt.core.compiler.debug.sourceFile=generate org.eclipse.jdt.core.compiler.problem.annotationSuperInterface=warning org.eclipse.jdt.core.compiler.problem.assertIdentifier=error org.eclipse.jdt.core.compiler.problem.autoboxing=ignore org.eclipse.jdt.core.compiler.problem.comparingIdentical=warning org.eclipse.jdt.core.compiler.problem.deadCode=warning org.eclipse.jdt.core.compiler.problem.deprecation=warning org.eclipse.jdt.core.compiler.problem.deprecationInDeprecatedCode=disabled org.eclipse.jdt.core.compiler.problem.deprecationWhenOverridingDeprecatedMethod=disabled org.eclipse.jdt.core.compiler.problem.discouragedReference=warning org.eclipse.jdt.core.compiler.problem.emptyStatement=ignore org.eclipse.jdt.core.compiler.problem.enumIdentifier=error org.eclipse.jdt.core.compiler.problem.fallthroughCase=ignore org.eclipse.jdt.core.compiler.problem.fatalOptionalError=enabled org.eclipse.jdt.core.compiler.problem.fieldHiding=ignore org.eclipse.jdt.core.compiler.problem.finalParameterBound=warning org.eclipse.jdt.core.compiler.problem.finallyBlockNotCompletingNormally=warning org.eclipse.jdt.core.compiler.problem.forbiddenReference=ignore org.eclipse.jdt.core.compiler.problem.hiddenCatchBlock=warning org.eclipse.jdt.core.compiler.problem.incompatibleNonInheritedInterfaceMethod=warning org.eclipse.jdt.core.compiler.problem.incompleteEnumSwitch=ignore org.eclipse.jdt.core.compiler.problem.indirectStaticAccess=ignore org.eclipse.jdt.core.compiler.problem.localVariableHiding=ignore org.eclipse.jdt.core.compiler.problem.methodWithConstructorName=warning org.eclipse.jdt.core.compiler.problem.missingDeprecatedAnnotation=ignore org.eclipse.jdt.core.compiler.problem.missingHashCodeMethod=ignore org.eclipse.jdt.core.compiler.problem.missingOverrideAnnotation=ignore org.eclipse.jdt.core.compiler.problem.missingSerialVersion=warning org.eclipse.jdt.core.compiler.problem.missingSynchronizedOnInheritedMethod=ignore org.eclipse.jdt.core.compiler.problem.noEffectAssignment=warning org.eclipse.jdt.core.compiler.problem.noImplicitStringConversion=warning org.eclipse.jdt.core.compiler.problem.nonExternalizedStringLiteral=ignore org.eclipse.jdt.core.compiler.problem.nullReference=warning org.eclipse.jdt.core.compiler.problem.overridingPackageDefaultMethod=warning org.eclipse.jdt.core.compiler.problem.parameterAssignment=ignore org.eclipse.jdt.core.compiler.problem.possibleAccidentalBooleanAssignment=ignore org.eclipse.jdt.core.compiler.problem.potentialNullReference=ignore org.eclipse.jdt.core.compiler.problem.rawTypeReference=warning org.eclipse.jdt.core.compiler.problem.redundantNullCheck=ignore org.eclipse.jdt.core.compiler.problem.redundantSuperinterface=ignore org.eclipse.jdt.core.compiler.problem.specialParameterHidingField=disabled org.eclipse.jdt.core.compiler.problem.staticAccessReceiver=warning org.eclipse.jdt.core.compiler.problem.suppressWarnings=enabled org.eclipse.jdt.core.compiler.problem.syntheticAccessEmulation=ignore org.eclipse.jdt.core.compiler.problem.typeParameterHiding=warning org.eclipse.jdt.core.compiler.problem.uncheckedTypeOperation=warning org.eclipse.jdt.core.compiler.problem.undocumentedEmptyBlock=ignore org.eclipse.jdt.core.compiler.problem.unhandledWarningToken=warning org.eclipse.jdt.core.compiler.problem.unnecessaryElse=ignore org.eclipse.jdt.core.compiler.problem.unnecessaryTypeCheck=ignore org.eclipse.jdt.core.compiler.problem.unqualifiedFieldAccess=ignore org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownException=ignore org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionExemptExceptionAndThrowable=enabled org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionIncludeDocCommentReference=enabled org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionWhenOverriding=disabled org.eclipse.jdt.core.compiler.problem.unusedImport=warning org.eclipse.jdt.core.compiler.problem.unusedLabel=warning org.eclipse.jdt.core.compiler.problem.unusedLocal=warning org.eclipse.jdt.core.compiler.problem.unusedParameter=ignore org.eclipse.jdt.core.compiler.problem.unusedParameterIncludeDocCommentReference=enabled org.eclipse.jdt.core.compiler.problem.unusedParameterWhenImplementingAbstract=disabled org.eclipse.jdt.core.compiler.problem.unusedParameterWhenOverridingConcrete=disabled org.eclipse.jdt.core.compiler.problem.unusedPrivateMember=warning org.eclipse.jdt.core.compiler.problem.unusedWarningToken=warning org.eclipse.jdt.core.compiler.problem.varargsArgumentNeedCast=warning org.eclipse.jdt.core.compiler.source=1.5 CharacterManaJ/.settings/org.eclipse.core.runtime.prefs0000644000175000017500000000012312560206305023366 0ustar paulliupaulliu#Wed Jul 21 22:48:08 JST 2010 eclipse.preferences.version=1 line.separator=\r\n CharacterManaJ/.settings/org.eclipse.core.resources.prefs0000644000175000017500000000023012560206305023714 0ustar paulliupaulliueclipse.preferences.version=1 encoding//resources/appinfo/about.html=UTF-8 encoding//resources/appinfo/about_ja.html=UTF-8 encoding/=UTF-8 CharacterManaJ/.settings/org.eclipse.jdt.ui.prefs0000644000175000017500000000017412560206305022157 0ustar paulliupaulliueclipse.preferences.version=1 formatter_profile=org.eclipse.jdt.ui.default.eclipse_profile formatter_settings_version=12 CharacterManaJ/.settings/org.eclipse.jst.jsp.core.prefs0000644000175000017500000000235512560206305023307 0ustar paulliupaulliu#Wed Jul 21 22:46:49 JST 2010 eclipse.preferences.version=1 validation.actions-missing-required-attribute=1 validation.actions-non-empty-inline-tag=2 validation.actions-unknown-attribute=2 validation.directive-include-fragment-file-not-found=1 validation.directive-include-fragment-file-not-specified=1 validation.directive-taglib-duplicate-prefixes-different-uris=1 validation.directive-taglib-duplicate-prefixes-same-uris=-1 validation.directive-taglib-missing-prefix=1 validation.directive-taglib-missing-uri-or-tagdir=1 validation.directive-taglib-unresolvable-uri-or-tagdir=1 validation.el-general-syntax=1 validation.el-lexical-failure=-1 validation.java-=-1 validation.java-local-variable-is-never-used=-1 validation.java-null-local-variable-reference=-1 validation.java-potential-null-local-variable-reference=-1 validation.java-unused-import=-1 validation.translation-tag-class-not-found=2 validation.translation-tei-class-not-found=2 validation.translation-tei-class-not-instantiated=2 validation.translation-tei-class-runtime-exception=2 validation.translation-tei-message=1 validation.translation-usebean-ambiguous-type-info=2 validation.translation-usebean-invalid-id=1 validation.translation-usebean-missing-type-info=1 CharacterManaJ/others/0000755000175000017500000000000012560206305015074 5ustar paulliupaulliuCharacterManaJ/others/lattice_l.xcf0000644000175000017500000000375212560206305017545 0ustar paulliupaulliugimp xcf file BB2Bgimp-image-grid(style solid) (fgcolor (color-rgba 0.000000 0.000000 0.000000 1.000000)) (bgcolor (color-rgba 1.000000 1.000000 1.000000 1.000000)) (xspacing 10.000000) (yspacing 10.000000) (spacing-unit inches) (xoffset 0.000000) (yoffset 0.000000) (offset-unit inches) gamma0.45454999804496765`K 背景     T h x 新規レイヤー      + ; 選択マスク   CharacterManaJ/others/lattice_s.xcf0000644000175000017500000000413212560206305017545 0ustar paulliupaulliugimp xcf fileBBBgimp-image-grid(style solid) (fgcolor (color-rgba 0.000000 0.000000 0.000000 1.000000)) (bgcolor (color-rgba 1.000000 1.000000 1.000000 1.000000)) (xspacing 10.000000) (yspacing 10.000000) (spacing-unit inches) (xoffset 0.000000) (yoffset 0.000000) (offset-unit inches) gamma0.45454999804496765背景     Thx新規レイヤー     @@@@選択マスク 2FV@CharacterManaJ/others/splash.pxm0000644000175000017500000030633112560206305017122 0ustar paulliupaulliuPXMT_DOCHEADER+@N n ؒأMETADATA   streamtyped@NSMutableDictionary NSDictionaryNSObjectiNSString+_MASKS_VISIBLE_RECT_{{0, 0}, {254, 165}}_MASKS_SELECTION_ NSMutableDataNSDataI[73c] streamtyped@NSMutableIndexSet NSIndexSetNSObjectI_LAYERS_SELECTION_8[56c] streamtyped@ NSIndexSetNSObjectI_LAYERS_VISIBLE_RECT_{{0, 0}, {254, 237}}_DOCUMENT_LAST_SLICE_INFO_qualityNSNumberNSValue*qdPXSliceFormatKeyPXSliceFormatJPEG _PX_VERSION_1.5.1_DOCUMENT_WINDOW_RECT_{{493, 413}, {334, 263}} _PRINT_INFO_}[381c] streamtyped@ NSPrintInfoNSObjectNSMutableDictionary NSDictionaryiNSString+NSBottomMarginNSNumberNSValue*fZ NSRightMarginHNSHorizontallyCenteredcNSVerticallyCentered NSTopMarginZ NSLeftMarginHNSVerticalPaginationNSHorizonalPagination__OLD_METADATA_FOR_SPOTLIGHT__ resolutiondHresolutionType colorModekeywordsNSMutableArrayNSArray canvasSize {299, 204} layersNamesNSMutableStringNow Loading...Now Loading... コピー名称未設定 1 csProfileName一般 RGB プロファイル_DOCUMENT_SLICES_INFO_PXSlicesPreviewEnabledKeycPXSlicesVisibleKeyʆ _IMAGE_ZOOM_f_IMAGE_VISIBLE_RECT_{{-18, -18}, {334, 239}}_ORIGINAL_EXIF_{Exif} ColorSpacePixelXDimension+PixelYDimension ColorModelRGB Orientation DPIHeightϧH PixelWidthϧ+{IPTC}ImageOrientationKeywordsProgramVersionPixelmator 1.5.1 PixelHeightϧ ProfileNameƒ*kCGImageDestinationLossyCompressionQualityϧDepth{TIFF}ResolutionUnit YResolutionϧHSoftwarePixelmator 1.5.1ݒDateTime˜2010-09-09 00:23:00 +0900 XResolutionϧH CompressionHasAlphaʒ{JFIF} DensityUnitYDensityϧH IsProgressive˦XDensityϧH{PNG}YPixelsPerMeter XPixelsPerMeter DPIWidthϧH_DOCUMENT_SLICES__ICC_PROFILE_NAME_Ɔ GUIDES_INFO0 COLORSYNC00appl mntrRGB XYZ   acspAPPLappl-appl dscmdescogXYZlwtptrXYZbXYZrTRCcprt8chad,gTRCbTRCmluc enUS&~esES&daDK.deDE,fiFI(frFU(*itIT(VnlNL(nbNO&ptBR&svSE&jaJPRkoKR@zhTWlzhCNruRU"plPL,Yleinen RGB-profiiliGenerisk RGB-profilProfil Gnrique RVBN, RGB 000000u( RGB r_icϏPerfil RGB GenricoAllgemeines RGB-Profilfn RGB cϏeNGenerel RGB-beskrivelseAlgemeen RGB-profiel| RGB \ |Profilo RGB GenericoGeneric RGB Profile1I89 ?@>D8;L RGBUniwersalny profil RGBdescGeneric RGB ProfileGeneric RGB ProfileXYZ Zus4XYZ RXYZ tM=XYZ (6curvtextCopyright 2007 Apple Inc., all rights reserved.sf32 B&lLAYERSȒr1^1Now Loading...d'91535B6F2-E8E5-4153-B3A7-0B1C95F1727D-850-00000C448808E8B5 z@txV.{ bCA{ `{I?齘$1$7ӌIowy}o{~̼́yw?sۼ%7W֭|*5+<Ћ|RiM}fNBmO*U+=7c)Y'eS\:7Z#EeXg+oC/Ȓ[еo[?/ۚ9*iW7/c)W}M[M_'2Zcy;5~{q||b]׾g?w/>hiPVN")Ǝo~ç+ߨKjX*䷭g5v{|%7E{L32jGl ^_+1Om #K7#z0T+^/e6/qkbhiM]/Ӭޣ5J=B=ߎKHjoVnӒL -EG{L3B7F^-? _W7.nN; {VgV'FKӁ e5'!5'-;x܂γ3.>!yjcKHJ˨NI-jd7}RN:).>~x9sRt^9ym>c-9̪~So\_u靻GogBBz^?U29=trN]4sTUWV|I~Ñq}[k /yy󳆙 D<99S/~+)_rK+'&e6=0Y}5ώm_?{W(":_O[75wmxwI -%IȮ[^.}zo/~٣iv^ɺqU5{gQSY5C !+ 3|O_0a0_v^O[Q{kǧ-qTqRrN;IΨk襡~\F3eꙻʺwjf= /9o-ŏs ,'qf~̞x6_b,E{$לzY$䭸/\%+CK38:=#ۿOvmG?4o}?O(5k7 +'ƙǏ{LG?wLswPc: rtSc1=s,~YCTN=ᬓzmJja}[cꁟKM%rLz<f!æΫ!W7+(쐃܏$%VzO;yY Aˊo>۳ϗ}86y)/Ʃ/IfoV(/iï\^\#˺U˶H>8Gn2k|Տy7>>1UdP5׽苒_V|쯰d2^ܿL}l..&nx3zU AJX2sx_wDnvTX:J;co:oC^mل_NG%gN9{hنfAJNɫagi?X8wodeXeT$~%Yy_n.|+%_6/Աو_l0,9FFn'*y~U|  7Hyם't_șg7UFJV+J 57*(8HjHS(jsRrvYw F9 tУYĶHWT~o*ju>W']?F?B]r Ĺ12?)7BO&&nι]\3g6>}f~&헙_r/Ծ2ޯ;[ i:M}J~$o'7vi%-%}S>=Xklwrdfݮ؎QPGL~]?v#glmҿPoKJ}!k<"N.zR'$& mX5^[ 8Nj?-++ndCD_r9sy k`$ _*|oDfBGu)'Oë=gnwB9:x^<<9~FA7HY#­| Ncwy5sw;m7},nS/y|VqG!u{F(i{k;{wlSPx-w̎Ux L5chs+8~Fc? @Һw%g|%['AJAl9zڿFA?qs֯W<^ |kk.O?W:Č`_M_"ɩu:zl7cnqm#sA8\wo]P;*t>7_9+1 Yx w"Oc#3Oy՟α5jb;|Ş0[2ufw]C{ g㜩oƸ;YKU.뺜/pg^c`owpBbFS]{#Gߟa7HYz?|׸ٟΥ9:6/k. ꒫BȂ/2g-6~u< ~rJ?ǭ>1 r>˶nwN -D=Hۍעd#Wkbg[n!2G/g,؍BiE}p*|iǷ>-kB7 zpajkNm}Dk·)R0l+9#ۑ3~V߹'<Ǎݢ-r@?$BP'=Jl]|S:,-r^wfoye5Kilv߻|PBwB:?U35'uGA~1Q섏]JI&s1Q%| #G}`YkߊۉQb4=?OP3;>wgcwmBq넄h=F1x $wYrnIJέxc9HNɯ|co_E{Lo M streamtyped@NSMutableDictionary NSDictionaryNSObjecti:#-Now Loading... コピーd'9F5547121-1986-412D-B105-82239712D16E-850-00000C848ED4E1F5L@x ԶXc<9P"Se@*Q1(s(2ej2dnCQtd*Lu뾟{=_{pk_='p88Sᐪ:nlvc:Rµ g)7Qvႅ3ێKY0)l=ۆ*rrBs_͵ rF+ZX.^B\\oN*2*m c 7% ORru¦۾[(, S\XaS-E￸ U±æ?¦۷ 䄼Ha ~w)¦۾O,_{ _cAa n6 }FA_;.F;baG O/|' ҿT> } /L¦۾͘/;. n6`V΂6"; ʯ\*wZOs5Fneed/qF< y_kG(Ի9sn>_ϟe~9Nk 3Sg|jU⣛jRPf^=?׏sVWH)t\_簈+ZƳ97k/G#BxswލK>%2_Q啅A' Sݔ4_ ȴ*o/H5WQ/? / ϊ)~wA].A:sa̫o7rٱ{.ZpFW'\G .UT}͙CFJ]ئ#cm|xDA~o ?(R[ ʳ`/:s=Rsڝ`}3 ?p4|P=/IƩU1oAo2&7.i?<~AS ns ~+Oo $6 0lڹ z_!: {li{ ʮ[H]̉| >)Uu[7r1}B͗r<C;!ϑĵF.wKd|z?u.(GwR8y>(kOae\neS2%i2,}L*8׺YSp9ENAٳ;8L[ I^>u_i{Oٰ؉VطɊ9sk}F_sM>q0Hx yg lE1|M w[{>RNwUKyʼn[yc{,x/+G/Dgb^?tY&u-rg_-$m}z##.+>iG{Gop:C<|qHGi}gs Kޅ1?26gVFVAt]>6uP3g7wNpR:^W-̷gIZg yj!yoh>Ǝ^'G^?{"Vm;!v} Hte?C:~)ܰ16z+?V~?35HY7u*8#7i;9،V#rˇlQf;f|)/k_^M;)G|tt%ىSZ(xy]#~{:y?޳_l]M+lo>slZm6pW c#-/s}<yG_v>|ѵ"|ҋbwSvFwY^W`{6xa7r%XG'S;6y•ӯ̥XU- z!y|b0ܗ-9c3{_򷅿7Ӕ][~ K}g dƺO 9LSج_'J;{ [+sV|q@u>Mj:;oN[q e gj?GObrf0@ĵ dgĿqWob:߉_H\3ٓȬe9ک͑圭׵}aWxutYo\HOʟhYPo'?~U il_~'yR6_ks\$~>G37,K4oU].Ly2Wx,M_alHycA^]B3'dK'3̛7=N]9qn#Qg^G6oHY,J :*o,|v~?ם#$dz<u걟$6S_ ?삌ke9& ;&!8u5wȻ~kF; lKjsÍ{eT+;ROG';Ƴv*1 'rwW]{ciؑmgPHL8:Ss/tW YpVtׂ=m qP}Ȟ/9CH1dw7{09Ml}?3?=Nst~/>BҸF H9kW=y#h_Խ[l9iK[i'f7 ({/ l՚iDnɟ}K^D/mem=z̽:,sgMۋŸܓv2mkR/5ӓ&{-l~`[k=5s;5N/[oHlgX^k}b9qH-˕>mU{a㕽O@w42A21?>Ⱥys# {_B%;&zov?q^uT]|%/TrO$N^bīJgw4V:߬?'8E TلU|7{1.MA\gـtOl־)3to0>K]ds*߬GWI'q.8ygbI3>'Nwd#^co]Vsۆ>|VdKte[Іk ? =Gp| SP3)@ )pܼ_MݭXBǢ3uD7_S>q 먽[O1kPxƈx3ՏiW;]>92G^[fKYj?yߊcA#=];ۿ90>uQ?W.#kbI^?#w;DoSmmǻSm|0ueN?UO?9u 8ף,Y55'4%'7k e.}eG)w?xkXī4}Mr>+«~coo3?3ٲfIsBkg)evY6s&X4WزgDS[x ؾ$_$mY?h dξXU-I$3Y+1i'#/~v!1|;>}C & 8ya/E=ć;lqw6ι~ 9oa6# qS hM streamtyped@NSMutableDictionary NSDictionaryNSObjecti+名称未設定 1d'9DFD4605F-2882-4E44-A3EC-8C08D90F55F6-850-00000BFF954CD5BEDxw\U?77$7}{oMcDP4{JQ*"(EQT6:XfޒZ}<99|>s{lkL-V۞Қ(!'Ź.=Tj#*wDx5v"xOKңįB*GwC%cWOno@4d`oy6˕'m=(%Ǩ<(}Z?R}}kQڞ̴1U4 ҐnfM{0xauk,Z }g< OjSx8W")q2+) þ4߫3;:C'8ƾ"k-y /Ҍs… |yoy .>STVV lav@60%!;s~YWkqyR.gH9_J X>SZ_F/[U$oC'% Uh4x쉆}a l}`p7  ˑ| Iy@s1%b [B a['\(V q'|{ 9ɣȹrqj1]Lio( [t- 1Xi̗/55Zxq,bHf0.^WݻGxOWaYI0"Q0%C*> gWʧe ~Gp5?15;&ǸO=ϽUv©, >Sż3WK{ۿdI}~6Y@a VR XjZ.[a%|cDtlOvh{qQz^f9PL;ۑio7Ͻ{%tCub5`]{(8F[[x }|' HMM}$P j2oL~l-w:Y~HKK ܹ3}s2Fɢ{`QtV`9bgg3|wat~l A<:3+"dӫ6w=6?8qs RWBy6nݺgٳطoO?}}DT hX\U Hj3Cg`3#~Uܒ8CLq* +56'mߤ߉cQYJulIpf~㣏>RyIcݾ}׮]cFI|_g\VV}F'1'c~Xkߍ+ȿ ? ;K?!?YŞJGR6yðdn@]_>;["8bۿJ>U3tKζT׌oGxB$g_AoA!䏐_"(pEus7TɈ[ǔ)IQo%VhCyۋ$vӏ??>;>㘪~.\DJcU_5юH>Pf'&S3qr8/(fXB\ MGAyyBܒ~rKrKZMïv[?%GFy0x*5~IyhIn9-gŒlc;>=Ç0b~4z,oSg3>#ſb3~8_u (o ar%:q7ǴCq}ʅ!Q(G9,NMïβ!<.IJxW(:+%E^ye#,RܡePmsLFyQf z]]]įzn~=Vђ(?O~$2@tY@W$]ُ}yӝپHvt,&t}XQ]9G fd?rx>ʕ+D__>jR~sZ[[ͯXߋ34zl,iQ]N?~!"phއs_VѸa O s/\dhD/4{W4pfKRTېg =sC񪠠猥\w|dgSSS3-_ ιW4  ;Z~O8v޾ۗ1p".w!\-bJGfP={ _4'=N ʴkǸB M0Xc-BsQ~tm  y2 +sN:ݻwF0?X6ϹsHkU=9#Lïo ؿ ?C \߸ӷF0t߾M#{ET'*vhjxbCs<8%2zL:>UEz?$MF\Ru/dp2ʧ r3ێ۵qAɁqs=sK |C0 f N&? ;̭-q71%fkykgqQ =vz?*. tܷ2Y8/ ixg,2Z 0'ǩ$KmHyɘ(Ŷ {H?w6–@k[ -I^GCG3ӨhV1gwn&Q.rzF?/ߗ34R"ٯ ؊d&z޻.'p0Ό1ٱq`d_ˈ]4^wd0è' -۾?C`b4J%0,a,T K]h.M]&K"a:bI}1gnLyh>j7giؙGn-9 vys8z\=##Qhr3P,K8Sc4 vyH'R(k:Ÿ>3 q)=硱}10AXGJ%>]k`Nz!R^b&Vu~h=H9-wIܦ(E(O׃b6B[G3̙3ܳp;SCdyc~=VU^\|熜K}l+إhH]szu;߿iC']&i3U{ v yGԏyf2:Mxhl4رؒ-bNS-CJcA YC܅s׆k zk-{I97s~/IS/?-յ&ȗq8(Q~ʪ{X1 >\385^*g ߻Pvv'^BܡBt^(bS1ϼu 'oCǍ܁"y6nGĉ$_VQ-\<~8&%EhbCF9ij.T"7*8 ʷ>pŵJt\ H7f#&fN#ι`$'. mJy$&2 F,^n))xwù`ρ v|7  u񁬽$ݕyl7,SR?]2VKn+{ͧb-eΎgxdcA(~ ŗ9юi~ҁ'{,Fny=0w q[|W7.aޒvn˽XږXnCn:>S/CV{c~=7a#쇓w>ɡ8n>os :S@FYݛ:fFv^uC<,vx#o]G~IGYa!6mBKB<_'M# xTf%r?URw_&iUֺ铽FL=qkvv .ճN,Dh|1o.q-]p(F`k,k #ME|~Ek,cGhݷ6zB.&OSf// qg#XBZZ E}(ܾ~>v#)G1  \UbjS`'h'UFx" >'_FHb$w7&C$8 imXa)i3*K,r.L+uiĵc{rƘ_3o]|r &Ưu'j瀩2=1֧<5ԇ-H HgeFt`[ו~E6%Ւ @$砢i/^;`/;'}(ߣdIA<0kHf 5|_:t͐6X3̧%ZMCS!9`*pd zM˰j܏Td =<]>ζvcP뵞 V}9cLïm"]rئ2كc!-H{1Ngئ|^͹ GTV"voLʌ/AVhۻG IlW81d,h>T}2$-uV0+8ejdbu}`ja sr?znESޓ'$ Uqu9T!"~Z;s|vQPؚC6އbas?f˱Oi083Y?k7qh/:x'Ź7gi5"yo4$`:%bh#G!sFO!1fwA^ш-J/G9wN>yf 9֟|G;?N+p>"\a퍚# 8r޽̵? ٸoC☟JlN@dA2Dzn]@׍A4_F/ z ڑo WUІI2J84e9Zkn\\}F ~l;Y} y·>t G:]dGaOa~X+ p=Zhf Ů\;4)/5Y̏DT^ 0?۩Ql~O}{mg&"K?ZRE4^a t2˶Mk-Wj.51oM=;;'&_{'7*tClt) Rz\?qE95O,rHGfȥܶ~|=!qqLذҚy`JLh7`{+pHhhܒlpa~XssGT.;8 hƌ,Ɲ)CX[6|kW;QvH{'`3q[y1V(.Ga1-2ߑ,|̹>zY!S_Q gX֡D+Wv_?>dY_"x<# 8Fgycpߌݣ?Zqoi/Ԇ}Yw$H}R¹mc)cאC,75<9@q}pg'K{U{ 6Vb$ASU,bǷj&q 񚵇4f`cp=GXtzS(nCySۣR՗ZQw{y}о7ΏS\.{ /"15BW}뿻)'%_հFY{tL<Y(r/9*|Gs<ɧ+嵗3P(H OtMd}(?=B69ۇDٔv\FO_g\}$q/H#!%RHro?Nrl# /N ;S0 f+º^y.~k.S]33(P0e\~M v )Sp"Q<暿RgbND8]v  ABփεZ;9&Ï.\5тk!i(mDBw R^9:`ajyR7$=CZC9k`TNHq6kR9TDz[ȱe$-%*j0#M99X9՞S*MXj^emo:P~Q ?1&Zv-_3v;rOfYE>S-~}^GU]AFHy[uog߈>Ƿ4wG^/Ph#fO`\x]kN $g`&xܗSe+rn0PރHږklx8B:A-u޵2OYJ1}XT=?E1J:duըֈK8,u<̤RGjː{TSǭ36c6sfߏ806&uU4ed7hOfe)kiHxO`'I=HI5O%İg`LK:f.\=(ޑ8E*͋ω{d~:t,ΕG{ X#qq)Y3+-'C,z%/{3˯lmǦc!k8ҵBRM"yKVdC_k$ 6iN8Mkl_í'=jWn~8#{_,i}и".I1U#F\r&FD̢=9A_GOٳbA4ֳ9!4O_Jf+ų r>/^9 ޑpz8ܞE V5qA 7Ċ疾^ ߢkF/R@p2 XCg3Ӎm v|k+^e=GLU|iY04>L̘}H+ )i@'{&?cpvUvkƙ}2g_ i3IrO Ɨw ,S=EXRh̓^{]&0hL E>)Eŋy\A#P7/F}lx}7fk-q3_;S1d߼iq_ܲxE0|eU10Y|>P#h Z8<9eCAb<ѕuJ F޶@:7I[{1NGJQ=2%nU_3ʚtVWYp]}_-ńUos8vc.h{iυM&[8qO"Ճ}++.d^xa^]La[ p. C#⸜u7Su{\|PF Wa]G6QL_zGɘc1vҒkbzFzAyW/0\#6~}>V6"Ukţ۩c %&V=VkFY2Gqߡ3nž|_i9qF$ws0t'dzi~sZ^R ȷ[}#Y^ShlM!ZI?x1fc'_up-*}+ObJrR[Q[>ͻ.0Z-8GyF>2>]ܬQJy{JCe&_m?y/ ^^*/+[?҂9Oޏ鼉p=BM^)R=VkXvAȸZq(8QT'+,<{6֡q"|HYZrh[QN(Ű?>:COOq10)~H331_k~ߪ41r(Mi}L8Л,oc3bl\!?m0f.IdOKi){3PZ/D7&Ao5=dT!G<}qqNԱزH~/HX 7`_$G1˝=_aYC,50Yÿ1m<1rJE#KΙ,Dv>R%[`SHҜ"4Bt\In`bM'`_8)k9H +2kP;TG㚌/'k1@ y0Kpx4/˓\MZ͵I@PI˔I_1 Z]{y^/' {\Ja}WǢyS0˄8:'EZHKp)4GD~|b⁛BLևNhṃil|._=֚SV^^HG@F[_3#yls)_8W{*LESIիc _-5-m/0~I9NJ}xtYU\,޺sl]`d%/k&ĦFQ.jsKD\ߓ͏ұ&!?"/c<3]Y~IhhJgߜ$x=^|q7mұmv&xn+<1>PY rR$>"=7#~:]y AzSa~rz+> e p2x='F9W@!{v죬؄6N+4)#P:AۄcN^{af'ch3v8s_`\1,8>G\-1V,#a,E('ג߷\KnwmR(%W*4E?ut1qΉ?^>$]EJf7:c#DɏW/Q/iIkG =y}KVQ;(NCZr3Q5|uT4(>M^f ,x9R/C|bc--UDk+7g?|j3I _9'*xM;5nlW)#R ȈB{HZSSYjl{|57c6Eli::W,Y'NG.3tb?28??W* S};L/YtS&A4.LcڀaC+b9<:դh^y9BmLh,\A&g<#h+>J%~zDtZfEίe2;$p=S`gr^H=8w ٗ؜wu8~X쌴t,lnwɯ=Rtն$cXI5Qt 3=dݥZ"Z[g>dtͤϤ{Rﯿ}AݻppÓjwgi8^L~aJqLN*Y3Ygxq/Zoj%]"TNI`8:H]/=Gi#5|THcqZcjoa~هd&'>!{.,[/jQQh۫X_mP/0_ςd~T+*t\s<ǰ~,V)cvkl6c֊e)<~BE?z'KXG}˗Vr)n}R\5_1 v=^SnfU`($u4mƑTiQ=/Cqеq/ԃz&FoF0˾d(K-2EBjX+,ۈȤAfgp [1\RRa+!f#+߫,7ѫJkk?iQcBl 0{J; q5l)C0psZ)wmuXcd8B't\륵9\5Ä^[m6~ViX'] ,mQ}~m|,TCgH/X jg#bRCV5ؙȾ<}%Fb͒ߢzZҜ6"*4*:uK72CWle eωK[],/k5ވH++r 4% ,:Y,31I$'bVl/ū9 m]v `sm8/z* }ֱC@crAQ[U5vu E#Vќ|^kqM FLđEzVdp~V$6)[\+dnG8K6 ) s̲=Be@ұcϳrk{ͯ7( f)ÂcYb[<7 ..=)ӬZU_9zYhS[&a'vjj~}ygkyv&O [l2qBY1rJ & kC^R}!q:=ufg/Itc&L}/kHJ#+)"NEkFM[@Or^X> Ir Ml?2 fYE9E㐴]HGC=רUTϩra53b'Gi\zd =yKg.݀3r}~ ˌ6WnX&5.['C W~-BfϷ9\H=E7$\×zņ<k٧wfvb8uaVm~X_z|0=_2}ܒJ=rq(ǂ;NQbaD~Xr81/I}܄RQIsj ɂO'h0R8>5]_5W\ ǬH_߷F\6+午:]eڻqN钊k(>vSKdz^J:ie.pfm&)J:'zo9+KܕHu. 9`$|M:gڂtgDq^SlFo_@ӭ*؆ Zn1ǸF\YG~~׌58Yܑ탍n[^`P/l63(_풹_aobib*ms[E2Pw=bN}@┽Wm:3cCT!fVww^{Rh#" { %{tlUݸse{m \Y/%_ɏK #N뇱|3-pOa[+zI&J׋h/r.QgcO| y!TkNQYHd~ƒct"9 ?f|>W!=KĶ2ҩGbH`nI+,aŰNAZ1F]3x[Y0:`+*jXY5XG1;>cY7Jo׎r_=l"X阥1+ >.2yc9y#}3gaֺů :k/j_; ah{fvLïkqi. 1iVaHyQpEy!YX|/ = {@ه1ǹ^o KYDZ*$; V6%%gk~'YCsOTս[u)E8C^A>i;S;?>kH/,}Ho/燘ykdBO3^‹kMS?Z1w'#pZT5wWc4BߐV f/I='˛#Uȴ Fb]1fN zwXq,4ߦqNf)[ǃr-V7Y@J2K?^鬋*סkgǹ\hȤzm|5ʶ Vy`;b?-.m:/h;T'ũ$EbJǫ1:?Xdr7#mPG[Wk%פGʽ/S.{eZՂcj#+D\XblfkYGX'y I*j&Oh2dIG h6Q$΋>]<$~5`+4As v-dfIxE<NqS%{/@ْ6؂Cvలյdmo"$z v:F[uFs2Is|ys`ht쟊&vqɻ\JVYĎ4Y)J}ҤDZzWroCd=„hf+'N#5:oTˑܒS_+,Y ujRk~q62eHż I@ bwR! )nmhKOqy1:lz;yG96\,zi9YƫrK{ٌP.>)̜JtTfwGe,ǥ/6njUkKz.M^_(qAރvtŘoD}h[{uO,6n-$n^p GHL*&{cآ3Z:`;tlY0OV"< e5P4EDB%vfq=#K [9 4vU ߙ]rHDbVH<.cش݁uےZsSWèǡl]Gx#.udL//0t? W{i5mH# {3>4UEδvg?}Kl1vĆMX`"-̙Va0w:,_a6[a-S_濈0[bVmc}EWJ|X*q\ډ!Ą5Ʀ0 fY=bdyì33K[Ӓ#˸#^0ӆOH1\@ei|EYpMh]j3cAFYwqJ'3y׀yS粶udoGط;~?txդNQ=Ͱh-{3FOMjfi5-1Lտ~9>'mWbbů7 9i+ Ÿ/-WLDFpTbwc BRf/+r-Ut,̑Sˬtlu~`'Ȭ2|Sl sQ: yxޛ׌Ώan?53~j?}|FR k X H;[M0*;!xuR9 nF{8p2i+v*9]J|1fT}}w~Wl mÂ7ݏ"-=~g:--׿O#Oc/c߿o}4U*##| ܹv-\~/^OW{lJa$,bVA/cċkT;>xڮuaSk=g{oƛ_֦r>;î]gbdllZ3}x~8D48_9/XkޅTcJ>fm`5z'+Z݌6 +;]i^*F(*8炾`kkr$fIsM>;8?!f^Ԝ M$VMƩW>`;sssWen냿?ܔgX0yE6M`a]g~'~|6˿ۿA:?daikkO r~mkI=(>l6_뗄9U*To'ΛE&Q8;ѦceF?ekQ(h0CaܲSwe|abF=??,^]8r\&Jb)U|o>`C9ɾŬ(07ݎ}v7Ƕt^p;~?O~cGQGO;GNNO?ewW_Y]dF/bF[W,N&"MF->ff*K;1`%S}7cMïlb]yq|% & }Pc @c,Xe5x^M@1(&ؿ' NxȬ:]Sn|#rFF>wů 9zs6r!W t+<۲?ɟ>(1죏>k0b;Bbt[#U0mx# }PyI9g2h^%.ua_~)ڿ}k~H3klfϱFq04?ƽ\L-zmyW@*Rk0grػBщ%o9Š6`'Ƒ(!<.gڼRŨ7[w3g='_;o=?`a9~+Ɲ5?υZ:{Wl|} Kbq}2/}@➨*I1۪ε Xhc_^1*Ev?|C?4z,zY9pqk܏W(YynCg{uŠnػ_XU{ ./İWéK7>1scѓb+al;Bş4?Vsh9׈ '~#+DYE{%~}g̰wFZ>:?q(ZIs2cmBz+|#Ȫ*'ٵc4zl=ooi%?zS]ӸC]G}^)4Lc케-۬8=r_Y8ULio}[oœ}6~` U.K؍P`<ϿW`Wo$U 6 +܁c#石)dtw8s/%aR6<sO<ֹz˹_m.o>:%BlTk~MïvqW`Xx r$ǍnR=-tS X}CbՁ'|+9$VMd2k7}*"o|o^\oCMv#? ^7 x5Y[jͭCoZlk*BW?8wJ_ ;BbY"{a8gh#FmП;r45߼RV] r rd,v 5%,U}6#Mïk]p-K4Ԗk(Sc֨ KIWϠȩRd{}>Gxr[fW|O?lǿA} lwupuz:Q]Pͪ#xvӻwVK=f㩧b^gEUWwJyNeܻp6UΌ4cĮWs=$,%mb`M-T8#MïkMG>O~< Gao6:N]Sɨy Ų$u $.=g̚8<ɺ#>wkFsNµ~3U6K=8چ~6aسMlsg䠔Gȯ{?ǽc xgZguI, MxصT/-\#Ϸ_}l]U.)p vS8#Mï9})p(bZFǠ̗*9Jc=Zs'O,Yyx`ScޟfcճK%zi%Zxyi'XO?pkb/Uۅβ܎A\D:k=86g-5r h>Ȯt 85eZ ׿4_Hv5hw;8+ 6IY5y~8M7bde.X߻p\~Cwv8q2/`W?Z:NݧGqқzO|G8o5T/D&+,1[xcOc.p>Nߓrncv7ٴwZGӈ4K34X;'/Np* fFuY#wUJUãQחaSiSjDwdF%,J"@KƶnbM<qAai?Hyq7]n,]Vپ5݆o?l5UJ̱/Y|o> 4*Α6rc^I1+0.{v""zrxП"*鿰t!n]=(K?R Lh6`/734ZNG j1Xog--h>l3o/+"{*nI̒)MI_)a%F' ;?]dA16H˼&<'ErcS&Q/h4:fF_X+AIe-n׽g_VkoBmK7sil_!3i_V`t9uQkaX?^}`>bN]WמѼb}dB똻B8Q͝ȍI@s#kXJߥcU2 F Pⱥ q.(sWdX1=L} "#hC?s8u}~N;wctGDE. ]|$+nzRo'kw4qw'F wϝ̐Oӝsc"3f}ι瞋K]XEs< qw.)*/xA^ly_^Ү""mY-pl.;:U\EbƱb(S36n@lܮkBo?1r*<3i$ëK=xHL*4whW  aXLj/֗aɇ/Co#ͻNnW [fX5#0 6]PT+c*1l` I=V]$8W)eVbޮ]f!~^=Vԡ&JѮx?VS[Fۈڙ,U܍,8u]9:GUmpe9"( zb܇Ua_^RՓ$+ab+5ƱmdV ePP%2E Ws*YNg,~ \V0'(96BjȿQ켥H~="~8qV2Wx\;(>,"~2¾gs܅XĵDķ#y5b6zpmDL1L;#RN/b>-oMbv&$zx2ܧSEloV0* £0Zz0ʑS&n~}q~Es_ށ-5G˅J{S(ZԵ+bHca.?\M(}|'|C9k2P]zdƈl`1n~~R9"kgH¯V9Q9l #Z0W/Acȧ Eĕ/?k"_)Z ɭ PPZ9 bGQ+?.5 m۸f <|E"EwZka|5R?) ~BIPK^ *h;łxu,3e.!/ _k"f /*k b.ڙǼ™E9{R# %{Q ز 3ׯӞo-.nvD .=݂sFZ 2._!vCk|s42UدI! ji!1ɨϳxEO k]ܗ0vn1$6rʯ 2)MEp [%kNx4CIT'n9 ٥We2Qƴk~Y56FALwv \ d6n~Q?#JΚbv&$zeagg m8bCɿk▰vo=H=l. *' , ;ݟXSɹE*UTg-AYR' xEu%ˠc҈Sx!,صn@8Kv)~X; EʍJƵ]/7Ρ.ܭuBNh3)$C-/h n9S F0΀A*q~ׯvun8y-.>a0c>$!ΏSWX8xqٸrfcR A>4E"~+QZFWυԪC5Iݵh^@Vp,SNx":[;nGV@t?~X_w8!/%C-h;z74~ۯwcq~[̃)ka킽G .r R*QT*R<{ 8ص`j|x+uYu4`ʺ?ϯxGQ?2Tt+ j#;'ߜj[1aϬ}Du W27(Uw͸[m'k6Rc%:k׹H 䪂V4(h4UWndzMWnEb~. j#-#m섹%u j:W-cPlAm-uLZ+ p^x/B(j@èlj'/$b~l)Zthg׾s ōUuCFcYu?~ ~¨Tٹ0B׏%C-DG$&'9c}CcDgޝwۨdFܼ[븬6:TZU{ ׺jibL_ vKvޏԀHIuòF}q~8WITJ(/VڵظaSsşbap iY̏ 7x݃pOP9c1c\|.-5}L`Zb VmX !'. T5`Wk۹#3h"kH+(,xXQYȨcÍf 99aR,?Bj| li'mp p6˞ÎX,j c=֯źu6$퀽qF! Z XUI/6/\O]sao3vUKxWxI-ʠ!{V ЕbաMD~kw6!NꝅދU¯Zι> r 0d7^iYA[sr4Wa>6mĚ56KvMwxa֭-EAI2ԍKO="j!?3$ r R%-[46f(a\nX"҂Q\P$8*8,w;} g+db{Ք`jmcc#hLS&,6æxk刓̥%C-aW[[Cp[Aw[Vg\(ӁضÕ3km/֮ںj4lsS[jٔCèʺ0~A@|"oݢ_6?cڲXRs1c|i!fo[ʽ1pψrqXMRںkruj+fm^ p_^~=Ԣnܸ?jPڻwa,woEfWzp1)5 )oܘ#ʑՌ˥*FtZBa^ CRh0UuJ;oY B|bHZlظ/،YK`e%cK¯ZA8"J8- : 8}UNivΟ&gau{Up/~߲UBZon[3 b>&$h9;lz]AT դsza 6uJA(>cK/*^S  2ņgFbˋ͝m c.bb'$h18.~uw 7wHgz?s3!eaU\{3/ru4xKIqޒ<I39 (dj^~I4KgkhjC@ P6Tis8!Zg>a1[༥TMo} qnËŮϜ_^אݎfxʥ P7KQ5h`mi&)'/7'ƒI#Xk$I%0z{%zLT0 gvRI'+A}k`P湊˯Y˶ ʯWzRn|~ݰ3,{CT6Sf!CNjI%HQݷcZg4a!oo3,eW߳h*߁;ڲѰ7vT+eKp8+˂yKxD,#FܢP`q5BNjI%H 7nľ{a S''8{!-8}rsVnB[z/:E+y/'ZKPhXoͰv-ŷ?s1w*^O=qhdI~/ԒK p$l nAHXLEj{@WvE1X QykH@H|3kzEc#PPP{?aYid;{qQx7ٿ*x #akd1f[7oz5{ j+`oj +gwEc^y1-gRJ¯R&2 mqP(;3+sdffzFyy9bccE߾փͫ7'^ƔϟBΜЄf7ank`q?l5y|8Z:LOBbfTq'\P\sHMn# 68zJ F3T֡,2+scc]>ev끕@uGл[uH>׮vy../pX lesدI) X&BGO)a1fQ&_FA\X $d' O n {߼ أ`owʃ(9FxjAIKE `%8 CZD=ʼn}%y}^|Zk򞃾q0r:u6|:KjX _~>; +-8kFk$f -xV\Ř5 b3o=AyD-sG#K8m0_Ó>)ip=sX1g!Lla Cy ůR3/>zt끕_M4͈ 5֚P;KE- >/dPPQߊNĞ#ؾaWy/ÓiSHI-3<_BMy9|BZx7qM7vJil+!*s_ޤFVȋLE{ l4 o4>:QGf\,JC'ƘtL5~MM:Iꬹg.Cmm8T$\gBq}5ۖ#N2K¯V:Jb! D|R6b3q7jmSEk~yp t>y%vpeflPΆwX.\pu=S}*8sdPԊ+Ŏ!/9f32gARb"#"` ՐK>f?ab&$z e W xv\.Gl%$eUP$kwh_3^=WW Gǧ9 N`758녴M.Zs#c+# +XX + : -(JDK|-#'c@h|Yw^)Wͫ92}9~mM*I@QE$-_[+mbR'"31HైET0*#2+9˨LU[o~"i«Xp?dNXfcrkpGDRRr"_Uԉ܃kBFp3akib4~H[ĤjE+i_Gtr6]N.s YmP)5 }>p 1ǐ_x#ܛNm5$'Pky^:015EJv!V}4$J`g`ԷER_TE2,YqRR{pQ3Z g%P* Uþa%-8 jhHy"li`19c[xlGq_.kKNFa~A?65T!7&VDk[>;[=[6x|?O?*?ԫW^z,#/ (Թr鲘'u>zeP.I ,T&$\jåN_:_Z}L1f%:ꇞ=E&JZ'Q9<޻_5]wc7^8uBEm]*G$_茎+NyiOcrN'cG%Myf0e5 go?!߄ }ql~l]K_ԣGS8z6aPoDZ6+b.iqhcȓx2k\kh_|]qbRǿfXAgx_D%з,1'x#4 Ime}9Fٽ1'9GTEii"vip3?^g1~_?!]}6Ư0s[}Dۖ Rܤ_yMZo2_Wm'!H^&ۯν}qvlop3FCn)*{{XƧ#7*.˂uǾkX1.U%!͜ūc:%W"-U yxfV FĤ"5/Bz2w'DALz?ܼLә!^st}jsOӰw~g-ذ'.CI*P+g)hAӤ̗7 y[یRHY JdP8XX YAqPwx7kh>j8~0{ X#~qدI! {釘#!dBjUe_H ,}39@,;{<+O?ǭAl׿ۛw?>(IS~Xe+'3c~8)sjY8r rJ̧erNɇJ(\*n##E: ,3޴z0RĞ;> #7iMW|/>>ܚ9xY@31¡fX$VUFm~6w&2:Duc[ [a}oOŵKW@fFq&jxPe ?wWC:;OKf5HƯ. {;?}٣gVICCe3~+^PXY]dDEE ?yf '5YIH @/il wc;y'9x\#ݏp5By|ր3hmK^H)7jʫhADR\`c`_g"%=_T"?ۃef>^iRϱg zz$Eu@5[Z>ix~+嵓C0"""{!(r駞#H0޾yT\ԧ[L21zR|vI$bL?vl{0|B# W8z*zPTՁ5a~Rjk; 9 <T^ߡ3cm_k0a-+>z _w e_z uC% %Eg;)dR[ #֮0U҄>bQT\66)w+8Wơ-;Q6t/-a.['Tgg_r_;:R+u}1Fytdef!//eeez_FGc10RтY%>e ̼ڏXe5~ݰ`\gq د?K¯FaWّg.shϤګ,nAAo'Cy+1tiS~Vl5 ~Yp]CGE=.~ *ۇ$|UBGxOFbg *4uQ{Cai\FF`4R}#8(fLu ~kATCyND ^F jչRo0m\ٹhk^}~?ItL|,d wmhᴲRol"X|GQyMDBF> ph:TOk3Wmݑ \kq'&&_+.ĝ!fٲSLŗQcEbXad*yt>5vV#tvZvD"y-}),Gw#\PCaL:.8>r ISWǯ~K0m< ǃљ+n5)vIuH/Д͞8*w<3Sսb>K<ǜpV孠 %-/;&pL}')Iw ݗ_+{H^W{ ѩ\Y<KAq{]Ưˁ6 ֯O+fAqlbD'$o!4(GڊbDFNm01 Μ׀6 < yƒAȌ4绻c8oydPb㏢#خu U(Mx#9!qqfcY_?ZiR_PE~T: {!)6y\~>fo_6.Fqdzk($o$ٰAmu!sw׹/"V*-瑨0Uf"eGCO>{#b\f yWЀAYaĩx@[p_&gzVdqWɎuWçcc̛kݵ~]U~7~l?מFVZ"=#G|wzu^zhA^isfg}{}X9ˡw8q/"^|^?4}!#>Cg4&ớ"665y/(+^Cǜ8ftFץX%}gWk[+lFU@׌/Uuy/>nAS^9j/r៊G%\Bq\&jrz0~&_+ʁ fk@dǧ ZgD$_sFȁ} 3s>oOMfc0 ~]U~S r}:-T:$>OŏskAb9I745&>(UWRُ_(zMl`kkck'@vX"dWGSWߏ7{kg?sx>klԶ`mb6* &@^]A4zn噃 eE1ժ05"&}Hi3EWWs5481~m^Gg`՘á}wv`?_qܿ^צX%}(ȫ׍p5CEV#] K'8陣,҄{bMTwdl22bKP/=>1y #ƏקaoUO}t)VIu_ȵ*s{AQ"'/fnV(K~w dȽ UoߎkV $ 4AFF"#k]c g6*3OuxAί&& ~y9:QNu H)|~Ӧg&Fw>×s~#kدMJ¯BrVj:tlvTd ¯Xk"֝ 4Xr:ߟZׄWGߺysRE}@=Kx/ g] vNo ITG[{G#&?aռ&b_~RW i&cT8_T7'^qTZ_Ҟ&dq,=e)F}Q9Uڎ!~(TI+k3:M)8^ODX8nzk)r0)1Nv8W@VZ Y=%FzyƯks`į[$/t؀uϨ+tLE,f-(c}kЭM?Gߧ4Z}4eKSy-hkTOfzز844b=mWAO7'O8`^Mad`!]i/?p:iٲK\*/)VIu_!k2G3˯ܰw(3+4A^$gUVʉ=F /[eyܰsb|Ƒ~8%sPӎuJӀFx6ޫq%xy/րIY~'򫉀6) 3s~򃍦"*h ;mji@A\ȋn =ō=b@eVhY0c1ʯrVw1u?7c[T{J_K*χ?Ipg~7-ce%PTxB^< xO!B[e=*Rs!8(*M/\Q3fVP+:܏ݓ_o(J='~*Hį@0hkHtV-1_ٍN\.Ĥ"0Z/+/Sjl{/`1Oz 5[a*}#}_ |N^/)x 6I@FFJ 6P8o;SG8;\&Ӳ% mm_K'xn62r׾S˥b=ZӜuLZ?H8iљ9<Ưr :ފ&MbުXuZv:"?_b_-\|#Ν<;B<m]~H3Bz 6Xcfkl6ǚU:شA۶crH?[ƶ0/rvy輴Ke,l#G澋URׇ_)¥R2 ׀ZXVк z&埩! ~}6~3=6aql=/?!WXk2>?@HԱvFVbp?vrg̉y469b&lv H,@gs5zFgKߛI rd3ѷYw46BAU+ʚ󫠗_1~e^Ev7#֮0b2º:~󽷤0cup=)VIu_2ŎJm'ZAes?ۑQ3joRI'ⳮ ep.k1 abS]L,Age~!s&Wo 5`l(_P-W]~]&~]Gp=*9tKqt_C}D+1cZٱ>b6* [Zzk)n؋~6gsR0;UԉN擯@Tj;b3N 0CQ~O G\F!{q WWXB <)Y SݧSs?_{G|hE?޲^b6* >L?*8~NmY_)x>yu0ֆVZ=kE^fq3ᣵITϑr{޳HnGxR BaVmVq|O%O?zI~&@O۫vDץEH~Ŵŋ5=R;Sg| UE޼s_į.H^bǤmIoEXD7ѧ6:!o=!`W_g*,&f % ˯>2`0.~L5YO>2?׎_g޶Ab&-DK0(h¥ %=]BiCoǁEOw;NְeJ qbgD*9._nFQ+W wDà2qݣ^nڹ{`Z(`ckۦ#;.afw甶/08!~_OΪ u@d|D㽄"d_v_.Hq;s&R)u50?n,PB~ #{Շgn~Y3~嗹%Ok˭"n[e`,?!3ts#KQko[دA~M>*":y^~G#^v,vx/◣5mL[f΄* aF_c俼܍{1vo޽'~?[@ܠk%Ɖ vn^,+ܳ*{}֮892$%o=8h;2@/!,9LՑ ~L=M`Z E/aދǎ1=`/AL76dc泶0f{Y E^nK>'*]C8{q+YY痩`\;g1ƪۤpb (K@hU9Yuj ?j5x?i0~MASKSPREVIEW'+xw\U?77$7}{oMcDP4{JQ*"(EQT6:XfޒZ}<99|>s{lkL-V۞Қ(!'Ź.=Tj#*wDx5v"xOKңįB*GwC%cWOno@4d`oy6˕'m=(%Ǩ<(}Z?R}}kQڞ̴1U4 ҐnfM{0xauk,Z }g< OjSx8W")q2+) þ4߫3;:C'8ƾ"k-y /Ҍs… |yoy .>STVV lav@60%!;s~YWkqyR.gH9_J X>SZ_F/[U$oC'% Uh4x쉆}a l}`p7  ˑ| Iy@s1%b [B a['\(V q'|{ 9ɣȹrqj1]Lio( [t- 1Xi̗/55Zxq,bHf0.^WݻGxOWaYI0"Q0%C*> gWʧe ~Gp5?15;&ǸO=ϽUv©, >Sż3WK{ۿdI}~6Y@a VR XjZ.[a%|cDtlOvh{qQz^f9PL;ۑio7Ͻ{%tCub5`]{(8F[[x }|' HMM}$P j2oL~l-w:Y~HKK ܹ3}s2Fɢ{`QtV`9bgg3|wat~l A<:3+"dӫ6w=6?8qs RWBy6nݺgٳطoO?}}DT hX\U Hj3Cg`3#~Uܒ8CLq* +56'mߤ߉cQYJulIpf~㣏>RyIcݾ}׮]cFI|_g\VV}F'1'c~Xkߍ+ȿ ? ;K?!?YŞJGR6yðdn@]_>;["8bۿJ>U3tKζT׌oGxB$g_AoA!䏐_"(pEus7TɈ[ǔ)IQo%VhCyۋ$vӏ??>;>㘪~.\DJcU_5юH>Pf'&S3qr8/(fXB\ MGAyyBܒ~rKrKZMïv[?%GFy0x*5~IyhIn9-gŒlc;>=Ç0b~4z,oSg3>#ſb3~8_u (o ar%:q7ǴCq}ʅ!Q(G9,NMïβ!<.IJxW(:+%E^ye#,RܡePmsLFyQf z]]]įzn~=Vђ(?O~$2@tY@W$]ُ}yӝپHvt,&t}XQ]9G fd?rx>ʕ+D__>jR~sZ[[ͯXߋ34zl,iQ]N?~!"phއs_VѸa O s/\dhD/4{W4pfKRTېg =sC񪠠猥\w|dgSSS3-_ ιW4  ;Z~O8v޾ۗ1p".w!\-bJGfP={ _4'=N ʴkǸB M0Xc-BsQ~tm  y2 +sN:ݻwF0?X6ϹsHkU=9#Lïo ؿ ?C \߸ӷF0t߾M#{ET'*vhjxbCs<8%2zL:>UEz?$MF\Ru/dp2ʧ r3ێ۵qAɁqs=sK |C0 f N&? ;̭-q71%fkykgqQ =vz?*. tܷ2Y8/ ixg,2Z 0'ǩ$KmHyɘ(Ŷ {H?w6–@k[ -I^GCG3ӨhV1gwn&Q.rzF?/ߗ34R"ٯ ؊d&z޻.'p0Ό1ٱq`d_ˈ]4^wd0è' -۾?C`b4J%0,a,T K]h.M]&K"a:bI}1gnLyh>j7giؙGn-9 vys8z\=##Qhr3P,K8Sc4 vyH'R(k:Ÿ>3 q)=硱}10AXGJ%>]k`Nz!R^b&Vu~h=H9-wIܦ(E(O׃b6B[G3̙3ܳp;SCdyc~=VU^\|熜K}l+إhH]szu;߿iC']&i3U{ v yGԏyf2:Mxhl4رؒ-bNS-CJcA YC܅s׆k zk-{I97s~/IS/?-յ&ȗq8(Q~ʪ{X1 >\385^*g ߻Pvv'^BܡBt^(bS1ϼu 'oCǍ܁"y6nGĉ$_VQ-\<~8&%EhbCF9ij.T"7*8 ʷ>pŵJt\ H7f#&fN#ι`$'. mJy$&2 F,^n))xwù`ρ v|7  u񁬽$ݕyl7,SR?]2VKn+{ͧb-eΎgxdcA(~ ŗ9юi~ҁ'{,Fny=0w q[|W7.aޒvn˽XږXnCn:>S/CV{c~=7a#쇓w>ɡ8n>os :S@FYݛ:fFv^uC<,vx#o]G~IGYa!6mBKB<_'M# xTf%r?URw_&iUֺ铽FL=qkvv .ճN,Dh|1o.q-]p(F`k,k #ME|~Ek,cGhݷ6zB.&OSf// qg#XBZZ E}(ܾ~>v#)G1  \UbjS`'h'UFx" >'_FHb$w7&C$8 imXa)i3*K,r.L+uiĵc{rƘ_3o]|r &Ưu'j瀩2=1֧<5ԇ-H HgeFt`[ו~E6%Ւ @$砢i/^;`/;'}(ߣdIA<0kHf 5|_:t͐6X3̧%ZMCS!9`*pd zM˰j܏Td =<]>ζvcP뵞 V}9cLïm"]rئ2كc!-H{1Ngئ|^͹ GTV"voLʌ/AVhۻG IlW81d,h>T}2$-uV0+8ejdbu}`ja sr?znESޓ'$ Uqu9T!"~Z;s|vQPؚC6އbas?f˱Oi083Y?k7qh/:x'Ź7gi5"yo4$`:%bh#G!sFO!1fwA^ш-J/G9wN>yf 9֟|G;?N+p>"\a퍚# 8r޽̵? ٸoC☟JlN@dA2Dzn]@׍A4_F/ z ڑo WUІI2J84e9Zkn\\}F ~l;Y} y·>t G:]dGaOa~X+ p=Zhf Ů\;4)/5Y̏DT^ 0?۩Ql~O}{mg&"K?ZRE4^a t2˶Mk-Wj.51oM=;;'&_{'7*tClt) Rz\?qE95O,rHGfȥܶ~|=!qqLذҚy`JLh7`{+pHhhܒlpa~XssGT.;8 hƌ,Ɲ)CX[6|kW;QvH{'`3q[y1V(.Ga1-2ߑ,|̹>zY!S_Q gX֡D+Wv_?>dY_"x<# 8Fgycpߌݣ?Zqoi/Ԇ}Yw$H}R¹mc)cאC,75<9@q}pg'K{U{ 6Vb$ASU,bǷj&q 񚵇4f`cp=GXtzS(nCySۣR՗ZQw{y}о7ΏS\.{ /"15BW}뿻)'%_հFY{tL<Y(r/9*|Gs<ɧ+嵗3P(H OtMd}(?=B69ۇDٔv\FO_g\}$q/H#!%RHro?Nrl# /N ;S0 f+º^y.~k.S]33(P0e\~M v )Sp"Q<暿RgbND8]v  ABփεZ;9&Ï.\5тk!i(mDBw R^9:`ajyR7$=CZC9k`TNHq6kR9TDz[ȱe$-%*j0#M99X9՞S*MXj^emo:P~Q ?1&Zv-_3v;rOfYE>S-~}^GU]AFHy[uog߈>Ƿ4wG^/Ph#fO`\x]kN $g`&xܗSe+rn0PރHږklx8B:A-u޵2OYJ1}XT=?E1J:duըֈK8,u<̤RGjː{TSǭ36c6sfߏ806&uU4ed7hOfe)kiHxO`'I=HI5O%İg`LK:f.\=(ޑ8E*͋ω{d~:t,ΕG{ X#qq)Y3+-'C,z%/{3˯lmǦc!k8ҵBRM"yKVdC_k$ 6iN8Mkl_í'=jWn~8#{_,i}и".I1U#F\r&FD̢=9A_GOٳbA4ֳ9!4O_Jf+ų r>/^9 ޑpz8ܞE V5qA 7Ċ疾^ ߢkF/R@p2 XCg3Ӎm v|k+^e=GLU|iY04>L̘}H+ )i@'{&?cpvUvkƙ}2g_ i3IrO Ɨw ,S=EXRh̓^{]&0hL E>)Eŋy\A#P7/F}lx}7fk-q3_;S1d߼iq_ܲxE0|eU10Y|>P#h Z8<9eCAb<ѕuJ F޶@:7I[{1NGJQ=2%nU_3ʚtVWYp]}_-ńUos8vc.h{iυM&[8qO"Ճ}++.d^xa^]La[ p. C#⸜u7Su{\|PF Wa]G6QL_zGɘc1vҒkbzFzAyW/0\#6~}>V6"Ukţ۩c %&V=VkFY2Gqߡ3nž|_i9qF$ws0t'dzi~sZ^R ȷ[}#Y^ShlM!ZI?x1fc'_up-*}+ObJrR[Q[>ͻ.0Z-8GyF>2>]ܬQJy{JCe&_m?y/ ^^*/+[?҂9Oޏ鼉p=BM^)R=VkXvAȸZq(8QT'+,<{6֡q"|HYZrh[QN(Ű?>:COOq10)~H331_k~ߪ41r(Mi}L8Л,oc3bl\!?m0f.IdOKi){3PZ/D7&Ao5=dT!G<}qqNԱزH~/HX 7`_$G1˝=_aYC,50Yÿ1m<1rJE#KΙ,Dv>R%[`SHҜ"4Bt\In`bM'`_8)k9H +2kP;TG㚌/'k1@ y0Kpx4/˓\MZ͵I@PI˔I_1 Z]{y^/' {\Ja}WǢyS0˄8:'EZHKp)4GD~|b⁛BLևNhṃil|._=֚SV^^HG@F[_3#yls)_8W{*LESIիc _-5-m/0~I9NJ}xtYU\,޺sl]`d%/k&ĦFQ.jsKD\ߓ͏ұ&!?"/c<3]Y~IhhJgߜ$x=^|q7mұmv&xn+<1>PY rR$>"=7#~:]y AzSa~rz+> e p2x='F9W@!{v죬؄6N+4)#P:AۄcN^{af'ch3v8s_`\1,8>G\-1V,#a,E('ג߷\KnwmR(%W*4E?ut1qΉ?^>$]EJf7:c#DɏW/Q/iIkG =y}KVQ;(NCZr3Q5|uT4(>M^f ,x9R/C|bc--UDk+7g?|j3I _9'*xM;5nlW)#R ȈB{HZSSYjl{|57c6Eli::W,Y'NG.3tb?28??W* S};L/YtS&A4.LcڀaC+b9<:դh^y9BmLh,\A&g<#h+>J%~zDtZfEίe2;$p=S`gr^H=8w ٗ؜wu8~X쌴t,lnwɯ=Rtն$cXI5Qt 3=dݥZ"Z[g>dtͤϤ{Rﯿ}AݻppÓjwgi8^L~aJqLN*Y3Ygxq/Zoj%]"TNI`8:H]/=Gi#5|THcqZcjoa~هd&'>!{.,[/jQQh۫X_mP/0_ςd~T+*t\s<ǰ~,V)cvkl6c֊e)<~BE?z'KXG}˗Vr)n}R\5_1 v=^SnfU`($u4mƑTiQ=/Cqеq/ԃz&FoF0˾d(K-2EBjX+,ۈȤAfgp [1\RRa+!f#+߫,7ѫJkk?iQcBl 0{J; q5l)C0psZ)wmuXcd8B't\륵9\5Ä^[m6~ViX'] ,mQ}~m|,TCgH/X jg#bRCV5ؙȾ<}%Fb͒ߢzZҜ6"*4*:uK72CWle eωK[],/k5ވH++r 4% ,:Y,31I$'bVl/ū9 m]v `sm8/z* }ֱC@crAQ[U5vu E#Vќ|^kqM FLđEzVdp~V$6)[\+dnG8K6 ) s̲=Be@ұcϳrk{ͯ7( f)ÂcYb[<7 ..=)ӬZU_9zYhS[&a'vjj~}ygkyv&O [l2qBY1rJ & kC^R}!q:=ufg/Itc&L}/kHJ#+)"NEkFM[@Or^X> Ir Ml?2 fYE9E㐴]HGC=רUTϩra53b'Gi\zd =yKg.݀3r}~ ˌ6WnX&5.['C W~-BfϷ9\H=E7$\×zņ<k٧wfvb8uaVm~X_z|0=_2}ܒJ=rq(ǂ;NQbaD~Xr81/I}܄RQIsj ɂO'h0R8>5]_5W\ ǬH_߷F\6+午:]eڻqN钊k(>vSKdz^J:ie.pfm&)J:'zo9+KܕHu. 9`$|M:gڂtgDq^SlFo_@ӭ*؆ Zn1ǸF\YG~~׌58Yܑ탍n[^`P/l63(_풹_aobib*ms[E2Pw=bN}@┽Wm:3cCT!fVww^{Rh#" { %{tlUݸse{m \Y/%_ɏK #N뇱|3-pOa[+zI&J׋h/r.QgcO| y!TkNQYHd~ƒct"9 ?f|>W!=KĶ2ҩGbH`nI+,aŰNAZ1F]3x[Y0:`+*jXY5XG1;>cY7Jo׎r_=l"X阥1+ >.2yc9y#}3gaֺů :k/j_; ah{fvLïkqi. 1iVaHyQpEy!YX|/ = {@ه1ǹ^o KYDZ*$; V6%%gk~'YCsOTս[u)E8C^A>i;S;?>kH/,}Ho/燘ykdBO3^‹kMS?Z1w'#pZT5wWc4BߐV f/I='˛#Uȴ Fb]1fN zwXq,4ߦqNf)[ǃr-V7Y@J2K?^鬋*סkgǹ\hȤzm|5ʶ Vy`;b?-.m:/h;T'ũ$EbJǫ1:?Xdr7#mPG[Wk%פGʽ/S.{eZՂcj#+D\XblfkYGX'y I*j&Oh2dIG h6Q$΋>]<$~5`+4As v-dfIxE<NqS%{/@ْ6؂Cvలյdmo"$z v:F[uFs2Is|ys`ht쟊&vqɻ\JVYĎ4Y)J}ҤDZzWroCd=„hf+'N#5:oTˑܒS_+,Y ujRk~q62eHż I@ bwR! )nmhKOqy1:lz;yG96\,zi9YƫrK{ٌP.>)̜JtTfwGe,ǥ/6njUkKz.M^_(qAރvtŘoD}h[{uO,6n-$n^p GHL*&{cآ3Z:`;tlY0OV"< e5P4EDB%vfq=#K [9 4vU ߙ]rHDbVH<.cش݁uےZsSWèǡl]Gx#.udL//0t? W{i5mH# {3>4UEδvg?}Kl1vĆMX`"-̙Va0w:,_a6[a-S_濈0[bVmc}EWJ|X*q\ډ!Ą5Ʀ0 fY=bdyì33K[Ӓ#˸#^0ӆOH1\@ei|EYpMh]j3cAFYwqJ'3y׀yS粶udoGط;~?txդNQ=Ͱh-{3FOMjfi5-1Lտ~9>'mWbbů7 9i+ Ÿ/-WLDFpTbwc BRf/+r-Ut,̑Sˬtlu~`'Ȭ2|Sl sQ: yxޛ׌Ώan?53~j?}|FR k X H;[M0*;!xuR9 nF{8p2i+v*9]J|1fT}}w~Wl mÂ7ݏ"-=~g:--׿O#Oc/c߿o}4U*##| ܹv-\~/^OW{lJa$,bVA/cċkT;>xڮuaSk=g{oƛ_֦r>;î]gbdllZ3}x~8D48_9/XkޅTcJ>fm`5z'+Z݌6 +;]i^*F(*8炾`kkr$fIsM>;8?!f^Ԝ M$VMƩW>`;sssWen냿?ܔgX0yE6M`a]g~'~|6˿ۿA:?daikkO r~mkI=(>l6_뗄9U*To'ΛE&Q8;ѦceF?ekQ(h0CaܲSwe|abF=??,^]8r\&Jb)U|o>`C9ɾŬ(07ݎ}v7Ƕt^p;~?O~cGQGO;GNNO?ewW_Y]dF/bF[W,N&"MF->ff*K;1`%S}7cMïlb]yq|% & }Pc @c,Xe5x^M@1(&ؿ' NxȬ:]Sn|#rFF>wů 9zs6r!W t+<۲?ɟ>(1죏>k0b;Bbt[#U0mx# }PyI9g2h^%.ua_~)ڿ}k~H3klfϱFq04?ƽ\L-zmyW@*Rk0grػBщ%o9Š6`'Ƒ(!<.gڼRŨ7[w3g='_;o=?`a9~+Ɲ5?υZ:{Wl|} Kbq}2/}@➨*I1۪ε Xhc_^1*Ev?|C?4z,zY9pqk܏W(YynCg{uŠnػ_XU{ ./İWéK7>1scѓb+al;Bş4?Vsh9׈ '~#+DYE{%~}g̰wFZ>:?q(ZIs2cmBz+|#Ȫ*'ٵc4zl=ooi%?zS]ӸC]G}^)4Lc케-۬8=r_Y8ULio}[oœ}6~` U.K؍P`<ϿW`Wo$U 6 +܁c#石)dtw8s/%aR6<sO<ֹz˹_m.o>:%BlTk~MïvqW`Xx r$ǍnR=-tS X}CbՁ'|+9$VMd2k7}*"o|o^\oCMv#? ^7 x5Y[jͭCoZlk*BW?8wJ_ ;BbY"{a8gh#FmП;r45߼RV] r rd,v 5%,U}6#Mïk]p-K4Ԗk(Sc֨ KIWϠȩRd{}>Gxr[fW|O?lǿA} lwupuz:Q]Pͪ#xvӻwVK=f㩧b^gEUWwJyNeܻp6UΌ4cĮWs=$,%mb`M-T8#MïkMG>O~< Gao6:N]Sɨy Ų$u $.=g̚8<ɺ#>wkFsNµ~3U6K=8چ~6aسMlsg䠔Gȯ{?ǽc xgZguI, MxصT/-\#Ϸ_}l]U.)p vS8#Mï9})p(bZFǠ̗*9Jc=Zs'O,Yyx`ScޟfcճK%zi%Zxyi'XO?pkb/Uۅβ܎A\D:k=86g-5r h>Ȯt 85eZ ׿4_Hv5hw;8+ 6IY5y~8M7bde.X߻p\~Cwv8q2/`W?Z:NݧGqқzO|G8o5T/D&+,1[xcOc.p>Nߓrncv7ٴwZGӈ4K34X;'/Np* fFuY#wUJUãQחaSiSjDwdF%,J"@KƶnbM<qAai?Hyq7]n,]Vپ5݆o?l5UJ̱/Y|o> 4*Α6rc^I1+0.{v""zrxП"*鿰t!n]=(K?R Lh6`/734ZNG j1Xog--h>l3o/+"{*nI̒)MI_)a%F' ;?]dA16H˼&<'ErcS&Q/h4:fF_X+AIe-n׽g_VkoBmK7sil_!3i_V`t9uQkaX?^}`>bN]WמѼb}dB똻B8Q͝ȍI@s#kXJߥcU2 F Pⱥ q.(sWdX1=L} "#hC?s8u}~N;wctGDE. ]|$ Ut{wz%wq{+1bb51+& aq orn{+][ߞ~m1u(3CA}_R:~bk8pGc=`ў5vG̯hW?7*ɇ;Fdz|?Ac IdCG NnphcAlZ$Kb ?쿡{~Hx..V8αͰi`;ʡVƤ jİ6$Xah]Zcֶ嘳m5_-ƷktC/[c:x=ȇOy'NOܙ6"+~\m#)o|dg/ӷ ILocvoHB#;u0򑓽6}q~ "ľ/OV#ˉ镶 P% kWBEِ%~hlĦ70cQjRmֳYBV#?J/G}'SS?w ﷝ރv#-Zdz1,$">a#&1BZ*(~1镒D(ge\Z (C^ v_L⡓nzQkhSm#-a]A/gsػiSƽ| PW_۪AEzlAuYg@KD _Uc b/":™E>{R#Kbv14o6`~ڸVӞou)\D@AL*{E댴V < N_A6nfoh $);BdzEEJD}@g!. 8%}mljAl{౜'>8)6Xy0zO_M_34JJEzPQ.U*(a6e|v(vܺ߭k+!vE *}ݽ>#/' X<0ȇ%G);Bdz8̊Ϫt&Զr{ F$&ns'{ٹ3aj-}sxau`b]rN%QX7)=IT/^@Eى"^QĐEe1jąX80fk9Z %ʍHĭA1e94YWVp+2=);Bdz<ʎ|5Z7_و[w$udOvg@(oEȿ/녃{l]ĥm>L1r眕7g\0k"3AGu}y_j-QZWŢ5LJr$ʓ_ҼhΧxtuCZ] H,_w7^eXi %=ߍLLnp O_PcT?[K+SϘ[\W+"+DI PѭWYXxhݨg] Kgy'ΦQ\Y(gAEQvydEƯZLcl-bbb6t qL3KO$OaUu>D jb|*U|[ ]3n(6uUEw(Kutg+ǐ*UĠiLvhpcvC.E~S>\dz4)i)nCӽ8(ϕ,$}K0~2Q?D5~K1Mj%}Ihm5Tt Zۯ9{f݃]Czʭ_qpܼD좺̂IFMn1p ;{YV)-6 K@\Bܸ)f _?8zv.L?+d;k31sp0shn _K}昽b^߭Lħ EkЮ{M®ܽsg*K͕_/Gs;x("rq HL/+ U3shJEƯZl3:Wc# cKYCjPSׁ“(ϾHO#Q=Gjd܆TTu3} ,3bsUw3K[nflZyۖU.i ƥIW Óa[K^6Iylj~EĩLUOP*_vL594"c-v ,.)ALlYXRu5;Y?`%Xg+=Jp AJnKz>5׊j;SH:aYt*֯768q&K^}~H} f{k vqBf1}T<nqMP93ҟ0sTdz8XQYcfAh9cçĞ}&8} g/Ym;p Ni`+{@K^ƢE Xֈ7Zc+ljCqI!Z XS?n]k +#-2~=9ގO4zۻD WzD,؉vp]hњ 8:sfbu֜];w;Io(F:^UvU94 |7k-\:sL&Hb\mg +fa-f.1Y=37.O[c\w".=1>Eܲ녳w.X]؉ۗ»:tǴkD_oFhXبǥ< #C{#tĈH,ܾkva ˬYJOP1\ ?*FFQ/"STkJXBӨ_EP7ϢBm1lHQqfn^kŗ_ZVێٻ0bǴkq/ )򸦣+qVΪ]Is8s(6Q>+'W-^2VOoc ܋.B7V s(/us{׉xƭ窊_Kc2nkWF"x`S-Y 6vZ_2yrͷadی],a cاz =ơ} rױ>9 TGDb˟^޵%׉ 1ߚaai-;㟟oü=kx?ġh;^06deٻzvClzZ'8Ʀ0QсGuK;^c[~ 9m[@-̝*%~/Pb׹6n,""q<c4`@Nm:6}SNkK&  0GTo +#6;1z&XLz(G*jaٳ֩NsZ:w"5KĮ:XHuϋصF{'㳟f~=wP ršSYOkK& Z7u{M/M=vu-ϛ^9M`h_u}ؐ8ҙ˪ Z%;%Cgk0)huZW/rgFbvӻalረ<އrW~E/yľ]bٍk ''5x{x&/(ʛbqa|;k]Zy~5PTVd}rIvΟ6u8q{0ݩ!Gz0ޔ#ـ5_!sYt.>t|BN,]*$4]8rRc~u|'+2`iKK{hiaX~f^-Ĭ-y_ݷ SO[K&9h3*ob3f/n}1^uͻ pA!V.5Lz՟5J'$>T DTqf{W}nPȅt."v]@dv5o~O'1nhM;hAkaq)fo^9q~d"%kqM\7WƮ{[nn܁b(GLzKqR(R[R0d9<T#XdNflkvc}PJ*V1疨>٩8wA 'C+';o2F;wK>?ճ7Z:8p03x_]؁ O"c-ѽMs+!EEE"""oPv{> ~fsP8o^t\xYcwC6!1,*9 碡iTvs~} p Tʡ_ErZ(\| noFfh"^{x:~PG^^?X#*( rOaƺyۅE{VM<"c#TK uQ< m ]{{{Gsqqq<T<* /~6I=ξSƞ#8tz\HzI=e#+=kylfHn$]b00V8~!8p6n̎Dݷf=/}~O ٤5}nhAmqlkum} gm_GO"#)d<WqL4N]9 S|ΉFyy9%߹Տk7g^ D%r53;X2;G`.#\UˇW=υuD msWS~']U49/h~l?dFKe?}鎯bӪUcK1 C$%&}2ƴ~Ƨ^tuu,DIB&#x°$D9Bu;!."ٜFQyDƯGRNݸ*Wo~FXX$Wd_/>',~9r^EŜ_sWS°lef%ľsD o3-Ptc8S1Q rɊkUXr{jsO>ۼ/>{IǏ^:#Xw.vq -嵨)xe("4 EnpBΞ+jg>YWd |}`,?#e8Hq{F9Pcx?2|8$+ʺ%Cf,+^9;bvu? mjDr$%:w aɰviQZ#FNVV߆2֠x-(DsLz$'3q=2e6.b<kc8ILnFVچҹ {fgj_SƙpzXkDeBo঒&3+^gN;s81HS]nϩi)2~=7Z+]?80G*Սns <(ǫxsWʝ: Y̘sKuKW`[q]3-.|U+'8s?^|}GzUfW^|MuUNvSP%E[=4~?(^z^ۗ~iS=H%8z}tOF,Fq(m^|oV\={zঅXISX UJx ^qwz,]xOhb]b" KƳ*TgO[og;~_G"/?9iiix'-S/ _:ދorXב$k*#ڱ5iډ_P~QHWv&ܯLnr}fsCQvxg9>,Xy(o3ۙݨRD&eᆱ+N^tHN) 0V6'9so-۔\H*F&F3qiy012#'\&nO҂^#{5Ϊ߿ĺ_{6MfG&Mü _TGDݵ|>M;둖Wr%nqlMoYF1 k7ZZK˘:x}_颜2x#")wV5s!1:뙝Ŧ~nkз,,5aoEI'g:g4zZ:tN_O{K<,?g|/0ԓ/ap1 kz ~4DƯGV|j 5.܌`3`iK ̒l-˿={/ؾb *$xoEt@9b.4cjE Ͽk6CA#AMaCoraMcC^ PQ.b;<1QI[k+ -HWXiov^}E)sgq3}0~ɽ)SNdzd岩3̨ DZsS eF{+nDl]QPՄ^T`ncli{܄%v6k5x k[GWO J=t0|?s'nElx#(sxMm53Kh_]b!VԷvf;:"7` t#/1 ն7L`chʸ兄xD ӗWC\_^~cƯgcO`ɚeصwfm\3c*C|^M+Tg8Y5;:]L$MD ;Ts‘^m=]P-Ŧ]6O>O>sþNoj`L'EB'm1 ^Bc# +XX }G׀GWC+o3{~W_~Ka:9 sc_VcZ>Љ.EEȫu0zu=H !ͮMFH,♭sk|uv.n,NCYl&rB?">:m#y9z _FG$9mkxp/8KWamnl?-bR=rE+~Cdb6ӻ]Z7T3،v(a:酗jkB͠ *aքka =m̹l.Gt-CKcgKmۺ{އC\WI|W|fƮ3]+0{ y6mDƯGJ\xo0VrKhCԠaH|;RЮIp٬Scq__!`اttڴTe?,KZrI&%}I{@A^Y_z KU}#rƣ &弿iBN9'ԥ(=7)y]C6yzG:ZyVR1zii<~ۋrȝ+ʼ/xW|^"vm/7 h>U7L\6"#!vpE'~ _Ȏ{dΫU3+~ O<txZOk7Q5lCG=~Q/;"#!FG~e˨~KTӘQ m8lo=3[~[[oоf Ջ8vXM ɾԣ'ć ^<z#RSۤ9" ۡiLb,#L³!ZFu74zU˗=u}d'/x套uafo_ )sBdz$䔺lnZAGer7k^|ᏌWyWyY կ% $oY5DB%~߿ff6.)J:5m5nCϼj\&$dsIs~}g#mN9XEi))viz^[^E?|vu7ׇ bTG{9ͧ|M =mS^mL!\F/3/M$د~+Z!C= cSVd` cƛXb\ ʁػ}(݋B{l3'foۡެFy1H ૯6`je \RɄR*pY9CqE%N>| 7EGus;ز0T{ymzg";)H N?x!yc,> Wb"\VEycCEy Y0R Gʊy_W.~ Flt ?3cV/\g_5Sr05G?hGMby^۴zn;XANH/qn5~{{ZTE/_m6CY݀H%]R}Ñ^5g>~=mHC |lϏA2~-ص>soEƯrN}eO_]Z Ek|PxQNceu5w7_:WUPtdD' / pMk޴B;{q'/c3FQF>^1ظU数E|f$C6rzU",!>ҷB|u"8B}sO\Ÿi"~QwW/}pczG zzNܛry 9Xbd3ȼSك%HșBy@g5JZ\<X+"#..b #O̙{3O 5 rx.aSq%ʓsPR W%S;c <졵H1aػ[6 g@8` CSkhBQU PTք {;%cW[ cR9 S}YE:=w2KK˿s{"aO)|W)ZoTCBcRRc$y Es UuY=6~;`n+9gNV)f80]k,ߵ݅ pHcSS.K L$j|Oo<|qӢ~DcN8x%dRqm=(*~8/k=/rn*jZNm1]5`[謨Gy\&Z'dxH`]sq?VǑ|yOFۢ]|kk*4WS\lµ5{d0.i}9fE\W K9kxhQ.&=m!6."~' {]Aneuqϓî=XFLx|]gl?"8VEX;AHF^ 06uqQYi09f$"Qi?7O݈KˇREmj+}#Q[mc ~ qfռŸ%SW8Ak˾ H|1V$'s='3Wׇ.+scLK}לFDw*ˎLTѶXww ak3 {0}ᦩ=옍bYF˧t\R)ƙê<6NjAT *j:1Džk)>C'|"7wmu(KCNh<IgcY_d?ZhR f]SE~D*~f;&-+:*Q,]z -咑UҚa Fҭ>oɿ<";;VV<z U)܏D99$dWq|9 />to Eo{u|2>WWKg DGD<5ObW_?L-`{px|k>]1s"V!s):nl=m/ߨ_wD;QTڌb70}jwcŹcZU'K5UMx x L"ۇjn w !]w{[>XѨmKϥ>dKfv2R6%O@d;vن}=uYw%}SMQ)+Mv䗉e46[O e MvLZQ?rPC[]%Ɗن|"e硽5#~m_m_^Ԏk?/R\%p]Z`;}4jZsIܛ]oYD^! ScZ+',~aģNݔ2~ SyKc˷1;,;Yaq3ح,ޣZw`?\b +(5W~4-Ԭy@_Y<)YE*/{_Wv6;2ֵJLO;g7DOO?vԹH ٹñ\ UX9y9ycfOnߨʷW7b]0Hl\ZXcS7wc1,3Wԃ0ݴAؖv]N2~ ^,$#L_4 K,QFؼ?X|crx? .}p5Esh; ga;܋=_ YN(MY#گgyq쥦c^+үăr҆#Ӿbzi95|{af;=YWRPV23zl!75+.q ۫jҾ۷aO٣ V4h'Jdt_:s uwsnJ_Ax;)״WLryIH'l}lc  ; _ˢ5JC`9ߧ_H&jatӪ>23~G QՎ2]b0E׃{]?!oK-I~!9V7|qkЌdf?֗bz!-3AXd iTUskBQ4"_o kb5m4<3ʪv~]{{3n~ə"f/Xx&k0b6vԘM]Ư˯Ѽz -.#/wzS7ڴq].$I~=#'<~(Du%dCc.5QwK$*Zcw}44vqP75 93V!sm<g. |XFvOSޭkļkX9s Eyl4n:M`R"muMȈHPЅuF F,Ul']8u^1b yT}Z*^=Y9zER `\' )W/ScQM_ӗ_1=9+fhηǯڡϷn+^6ѣ^QY/ֱf1ےg<$ jkžˣΡ$y]o\߱^~y}o-oS;|Ob4?}XT{ws\RF'Ll9޹wnx<,~>a_b2),TUz"Q[[;s(B}#8Ȗ#'-qd!>~_*8r9Z 5D vm6<wQ@6Cp2*Noyy=5r/~>}zimo+6 &Cn'k J;z;dͰN8k)ZD iuɗ҃;6tdݝy/@#5⚡ERq ?㗧 CXE'ӌ%:Qld?6ey;MHuk4.TD o\s9#Rr4(!RD>Zmyyc)dE} My^2G`%#-&MM^/7P#PFzm}ᜢx?!G:VC-[;8^CvIeC/Jr&3lKd7oYMu-',iQPQу3޹O)ۏguÚ01t?_bXO~ecOITE}<ǛB_S/ '6%Z`G}UB]I_9mT}Mb~0a|Ui۳r) #k1-JqOܾ=ol|Pt.s5]9x,ylPjm~)N@k赜=)x-I)FrTʤ|aGUxoܤ#6攄F|`ǁVoFc]6ߟl#}G?E4֨on5/q:̄dUZs=u⢏`{r(!αK-dtGTcqRShGn?sW_mU:ݓ_s;|]t/4}!?ΑuQ5K'0QAGG}ĽOV+8aT4)N>)=[]=H C_J/jd.P+h}^6? WXS3IK94\-ض`=x͊?_ڱq}֭w;yPk~U0X!)݅VhG/ W%|Z> "cu>JѷEH }'mQL\:^\(GEнö#{1'u&3o/8ޟA=DC:E&hm`=N-SVRuұX! -O^ rKO=#()I%~u39/[pxmgOaRa'}sy6lÆ+H+e(_nlcK\_A1>kmH[ /?gA~c ZcQld ::&a=Cy} !{TΣWIf=zJ˒{ApcoO`+Bau^˯X m@iYNl\^4R?$.Zkb~Q2=oobs[ڇ_9c)W nMZWloK3)?/[O/(_1}#עN_׾B~O_OGrKλ.N9䛲w3Hyb|^'ɠla\4oG=?g#Giy똯1|C/Z_fbcsD!"< sOS|ݒ'X"(*jCӵ yot]+EO1R1U@dJبks]MTKtfڇӘ,ȝ3IΛ1 ) zu6dwKY~) E7dn?u67#i5m"a_nsܫ*a9Dܞp;51v_LrɗO#s׌;^0᳞wu*d0J1:^)ΣhoVu^H(\5=L$sLM*3\nZșͯ" n4=kujkE#3B5b)> RXؤb᪱k/+s6Ъ)%jH~ s |_ so1WuJJ>u4Nց\ryIs2 uQ5WI{O c.{STW}w푙"e_vVW<~x!Z+,zt!Q͢Dgkf'ꙐQ55>\-#Nظ;2f- 4hXk#΃0$ j%Kу'OQBÀl% `-# yg-_{f' e%"ӵ{mNyJ/G5B;N;}|Ɏl>}phti!?kƸѐD|-c@Fa+an e(dsx0F'Oyc&3]T9U>F~.ă^qNq ?ِIX|s=* /~81~XVH͸-Տ7 1n_FK.p}Kz"56a^^$]c)$}2T݄zՆvE;X`}|+L4t$ѼctFքHk[u$p-XŵKbb琘9>8-1o)rudԷ0ǹ/$Xu|m2&(̒n{k@Zr`T&(*]c3TQo=[9?j04MFo.4uNFK9#RL2"S h |qi;~T{k~ _f8/m⾵!ܸ;kM>wsM(\UjTvoz4r&wz25Hz<qIu7-OkƱ;Gڮ-\x_{{yJbXkKoi8Z:MݩByZk~m߇4%zxh uV+=jœi565c`07=;h{K7^Xun?vG=M- ToWs^^ flݱ#sۥ-k_F[1wW#20Wˠ .\7J/k{aBv(u!Sĥ:fi>U1۳3%v$Ư _eGU"eM8h_j/g9{95c!6c?mӗc;9MFhr&ڽ|f[̻PCqX8:)Ω1_أ9 4W;^iy6+//};so!OJSi3GOM쾝*dpΓG%v5Z:`<޾/<?F'ѸF=V.ˊ$~xOxk{xiX⿒G1MGw4t8\[">9͡ٸҶ-."ތ'Uz]~3!,<OWM׷l,>ԋs(_Hl\݌j'$`Y`AW` lb/9~]Ufů8ƯHY#O4"Zފ~uv1fsf?=;?[ܾ^ޟ! sߡғj˼bybGt.LDv7/a܇Q,9qW&!c34u} c>nXWQQ SU' t@ghZ߂d]$f4ØSχ_+ qW |jhr1kq~Y[]o~#ǫګ %PdE\DB[ gwKj 8׃Ř{a^}ƭw2~l羖yGƔClkNnkRmg{k_FckJ?N /NgԔɤEI&C3pPSjґBvZMԪTRLS@J3P=cq! N3Y,meU,[>Z"9I$$ʒ g@/v(mĮM~~nYbrz=^p0t"p$bSG#(ϬΰiLwvy_UMkI+~X$|5:tYJdiP+dRNFmU˙3P!34;B#79 4(*P;\q̭[}=﵂qk.,bڿ?GbG{4*RI񖫙3F.+s^_uZl<_/1QƇڱq=lJrz,徘NJ#,* 0+jt~J)~M,TCAec~8,,RSKW Jsj3gU&dA9Ᵽss9忄Q*16d|'c*y}.泖Z7/f'DlvB)*u8_z#!md&'e-'BU^bG;0~)Ґ&@Qrތ00~2 tړD8;L RGBUniwersalny profil RGBdescGeneric RGB ProfileGeneric RGB ProfileXYZ Zus4XYZ RXYZ tM=XYZ (6curvtextCopyright 2007 Apple Inc., all rights reserved.sf32 B&l8BIMHH8BIM+FFFF8BIMnorm:名称未設定 18BIMluniT yg*-[ 1- i i i i8BIMnormXNow Loading... コピー8BIMluni(Now Loading... 00018BIMnormDNow Loading...8BIMluni Now Loading...}}{}vovwwyyyyyywwxy|nymvzvwzzusy{yzv|rfs{wphj}yoozro{~|{mrot}{|zbajot||}to|y}||yqpno{{yxvttvy{wy~|zxwx{ubm~˝|lk{}`fbMxtghx鹉ƚь쵦g{y|eZUPP]yunƚЊ|h{~}tRNSaxfϚčtzlxհКѓxZr禝КѬ|OpǸǤК͚Ql俤КȋpK{КLj՗fSˑښКxcVڎКѴ~w~uldb晽}КѭgaawКѢvb~`WКўˈ|vddjdS茶Кѥαm\oZg_f[֟КѤŲSoYg~Wj|^au^ͫ֜Кџ×ǯJ\]djYkfcRoКѥݶfϺrKf^\h^[n]e]КѸzô{bPd\`\sv\[un_`{К}۹οq~^Smp`d_`\}b_[|^fIӔΎКѐۭİu`m_UXYl}[`^YųV`]q]bYКƂ٨Ÿ´uW``_KfYo^_i|dN_]^b_mКѢ٪~ԠتötWc\`cGy\Yy~qcKMa`q]fPʠКπ٫yݩߪp]^`Hs~]`Sy}Ԑt`^LjlCY_l_`iEКђܱÐНϑkg{zd\YU~رλsY_a^@VFa]qh_c\gК|߽yڟ׈Қ—rūԫɾhoZßᤘyDz]``bSInJPb`d``hU⾊КѤМ{߼ݬޯpˊɗ{ܥ䧌ݟ]e_``dF_]D]````aaȁКсޫ֝Òʕ}٢xӇڢ~ݦ嬈˺uJv\``a_@}lSIc````eSvКѿśx߹{߮Ϙ܇ඓᯫږbvN]```cQI`ETb```a`{zКч|w֜Ԝ”ߎ˗~͓qtMvO|e_```bDdlVBb````cRԉКoZM߻pÔ}ߴtzڢȽ]{LvRpw\```aXD`IKc````apϞКѮkW]Ӿۢu௓vިwަⷐiZ}LyUf^````cHUk]=^a```dRʺКv`Oĵr؞؟ڶuþe``RZ~M}{Y]h^```aYCr`RLc````c^}КatZVv_ZrV˘Ҝ~˷\˽h\`Zj_w|Pp[V~\````cISaaF\a```cQxКѥycSmrd]^VhĚs|gʯj~}}}tEk{\`_XdkvVe]Rt`````aWFko_TOc````b^ԊКp^Nl_WkFnY^]T[\E^]]_iOU^`bQkbo[Z`Qhq]````aIW\aK]a```cTΦКbrZY}qv[]hHh_`\^{^[8a`aasz}yYFd_aVore[QO7.3/)QcovYSr|m]````bSKe`aTIc````dWКѥdSqk{hTsFcb_aWhr`U#R`]`aOK;\zdafS׭z\adpa\`fI^_``aUKka?yaf:-^```aI\]u]```G_`````cZzԦКneaIk\fvpBG+P^`]`aQMT`afTۦ\`ejog|^`eSX_``a]Lu`I^m^o8A```bMUab_``cLSb`````fM̓КѨ~aZ_WtQQ7VT[`}]`aS`U]agW۠\`ejۊpkb_cUc`_``d[}^UH}ZkdK\``aSOb[```cO@d`````a`kyКeQW^gJbVaVRau]`aN`TXcgYۛ\`ejyx_h_cZq`_``elw^_=\cg_^``aXKb]pb_`a^:_``````cOКЃkRњTmYo_`bXOdk^`d@bSSef]۔Dž]`cns~Yo^bWaa_``ekbd?wa`e````a]Ia_ad_``eBLe``````bSКјqPoьlNzx]`bY]ib_`bHcOZef]ۏ]aayܫX~w]bYma_``ffykbKVj^a``````H_`_kd```cQ2aa`````a]gКѩvOyycDo^`a[sp]{b`cIudPidfZۘz]a^n[r{]bZd_``ednw^XEw\``````bI\```````ab=9f``````bS~КѦmQpvF^rl^`a\syZte_cJheQۀ`g\܎r]aZݥ_iy^bY|g_``eg۟\dG\``````bIYa```````g;GUe``````cLКѪsVqh{XZ`jh_`a[Zje_bO[fQܴTi\ݔ|r\cTncbq_bWwi^``ekp_fMda_`````bKWa```````g>SQe``````bMК̤tsleynd_cd``a_Ȁ\cd`bUNgQUj]ݜnt]eQ߰h_i`cVtj^`a`xgaWml^`````bMVa```````eJGRca````a^bКѽsdjxsb_`a``a`voa`b`aZMhQkf]׆ru]fS߄m]c`cT߀j^`a]ww\cqsy\`````bNVb```````cS>їNca````bYvКѠtysf^`````bpۮi^``a^WjQݔ^`qvu]f\ᾇu\``eSߚj^`cV|^fZ\`````cLVb```````b]9ѧT\c````dWКq}ll]`````dnuy\```d`xlPTX|w]dnုy\``fRḆj^`eSxiaUv_`````dDTb```````a`7pQbb```eMКr~eu\`````ejp^```fhtqPdYzu_[̈́|\``fUxk^`eS\a}c_````e@Mc```````ab3їVXcb``fRКu{]\`````e_ےh^``fdvOP_}vaR㏨\``f`pj^`fbu`fVtg_````f9Ce```````ad4łQ\ea`dOКуyW]`````gUo{\``ffOVB{rxdXၳ\`aazrg^a_ޗ|`Sn^````g6@f```````ad8ѽO[gddQКѕ]]b_````gSr^``ffqNHkqax暝\`cU}d_dRsa`|zx\````f5=f```````ab;ŀʼnRTejSКeWn]````eRݯ}g_`fcSQhnS~\`eS生a_gVuaRy\```ac8=f```````a_Jp̜lVYNК~w[````eRݐt]`feἅ\vfmZ飩VK`]飖`a_ࣀpTĀ]```bZ@Ae```````b[eҌƛgEКѻo^````dU|\`dkpodGb^Ό|>QVwȗz|QWIֈvi[u_```dNFHe```````dO˸Кыe_```bZl^abzysuyeV{`KVHթ}~V[do_N````gAPKd```````fEtКѷx\```a`zr`a^or_鶕[zx^g[ȻXtO:2fpdYSNLKMMS^gZ-4>]b_``g9MVb```````g>ԅКp]````eh⦍c`R䞋G믽~YߜA* $.ba_`a_CA_a``````abLͬКјe^```gWsLJ>`IdޓP(  K^ekotz"\```eNU;e```````eOqzКn}[```eSۓYfd`ЃDqܮɡϻ 9N(7=<3 ;?~`_`g>^=g```````g6}Кєa_``bVփaJh`aË26JQPF1B\_qa_a_?SMd``````cZ?ϝКs|[``aaޢG  e``````dV9srКы[``eR NcdSkmcQ3:ٌbdQVFJd`````ae6LԂКtl]`bZ@#  sP[stlV7#/!"}vڈ`g]^a`````gAE\ΥКn^_`fe픲+ FaӿF -9O@9[pvmV6AZ7; ڀbUVf`````ePK;~ŅКo|[`eN7sH:Xc0(G]c\F+gSY}b_ٴRd````eQGQ>zКxl\a`g#?zs#'8>:-/rorO|`a```dUFc4eԈКюc]fNN 11ȅ=./6@gvsrkWf```fVHcS8ͤКѮl``\SĨȲW&٦RٹQe``fJC`cCkÈК˃x]eR Ĺʿozrٌ[b`fRK`cPMuКѶz]`x B<glkcagLu`aC}КѲz}aS'9CA=3/29CCHH-;ٌcXhcVncNPњКѸ|vfQ\UjWc\De˷КɒqrYْQSTHf{Кѹbt|^ϭD@MyКѤċ[Z8ӊКѯTVeĒͥКӀ>ƕК˄9ɗzКgTtʖ|К\NW>YʖӋКq^`XTXXPIʘΤК~E```bXD˙ʿКv^]``a^Cz˜xК^M``aaDk˟wКiH```cHcˠԇКэZ```cLRˠϞКђۼߊL```cOIˡʸКѣ谠⼰t]```cTFˡ~КѧɬtEa```bWDˡ{КѥG[````bZD|ˡӆКѦŕڌOXa````a]DsˡіКѦuIXaa````a_Dvˡ̭КѨ“ՍRP]aa`````a`DqˡĈКѪΞҘ^HUaaa```````aFfˠzКѫ‚PIVaaaaa```````bH]ˡxКѮַ̠gMKX``````````````cJYˠӆКѯ߽ҥj{WV````````````````cMT̖՝КѱĤCQbx›zlx}W```````````````cQK}FKKGGGGS[^~КѱťTd`[DP```````a``````cVE߈Y```````faµКѳŇ`b`MJ``````_^`````a\Dɥxnhfgjr_X```````fVКѲfea^ٸD`````aVCc`````?G[HHKMPTY^```````_ZVQOPSX````````fRКѮUghB`````bP9a```cIN]````````````````````````````````dSКѹz}~{{ŴQj@`````bKDXa`cSIc`````````````````````````````````bZКсћi?`````_HQNba_D_````````^`````````````````````````acmКх򾶴kS````aVK]GbcJSb````````_``````````````````````````fZКёp~{Y````bOSbG`\Ha````````````````````````````````````gRКѠ˄e````bG[dKZNRb````````````````````````````````````fPКђ吨ѥfݯXLS_<`bSRI^`````````````````````````````````````dTКѝ좴ʢkټl=KY[FJa`````````````````````````````````````a]КсɧپĆM16\_````````````````````````````````````adkКĨ͊ꖃ٣n>\HLX``````````````````````````````````f\Књr뙠١<:UDBM[``````````````````````````````fTКѤΩتُ빞p_O[```````````````````````````eSКȭ縱ԗ0ʇp[NNPX_`````````````````````bWКѦ}tטܙ}Ӆ_էZRQT]````````````````aa|Кʹj%`޾ht "帓p^NPBS```````````edКήoMփӣﯥپwK```````````gWКϴv*DԦ걨걣٥"&ᠪQ```````````fRКǩxy_!Cۢݵר뺑6W˶ڀW```````````dSК}%!Tܢ⿁ұ+' ˜̩z[```````````b[КѤd)Cݢ઎̶ҹ !!/𦕺sq՝t_```````````acjКі {з|+TJ``````````````````cVКx.qlƙ[c-Q`````````````````a^КDŽڶ{ԩ߇}넡ꯠYͬ_eJ7``````````````````diКѢݶ߀nXyۭj㿡!$-_b_&``````````````````fYК೮׬џpɳa%!&,``dD V`````````````````fQКѷSƱj $ĞОb=]NR`_a\8`````````````````dQКɿwѬ뾓ϵwr /Neb___e3`````````````````bXКѕܸˆiDb____cS\````````````````abrКшޡ$tû~!,b`____`d A`````````````````f]КѱѮc $=h{yn71+gz>d______e@&`````````````````gTКъǡnZ@!"q[O 4~T S'!Rc______a\`````````````````eRК֎e B\*9 P)?!+a`_______h*N````````````````cTКђ|'c]S࿑ "$#&Ub_______dI3````````````````a]КѸ4 c Aq1eɼœ"n/h_______`a'`````````````````dpКўög2 ]ɾfj1*i________h-]````````````````f]Кь٧PR|W`zKZb_______eBD````````````````fUКуɿ =Ŗչǀ@e_______bV<^_``````````````eSКЂ˿ν Rƶĩ7d_______`a3][``````````````cTКyϟWmk.3<Ñķ@_________d=ZN``````````````a[КѴxUp *Ƕȟ¿YTa_______cJOKVa````````````abrКѫȤةPưȿŞݾɣxFd_______aZ@SLc`````````````e_Кќ絧ٱD˽ξŕ̿Ɔ0i________b:[Fa`````````````gXКګ܌3/ ̧ټĕƿƟ'j________dAWH\`````````````fRКѲî $= ˦Řʿ°@a`_______cLKMUa````````````fRКуԣǐA 驽š׾jMd_______`Z9TOb````````````dRКuž.=ĿIJ˘8g________c9[Lb````````````bWКlϷǺI/"{*;Ťܿəǽ+h________dCTC^````````````a]КuǶּŚͭ3g`_______bNM>Pb```````````abmКdzɞÿ\Xc_______`[?CEd````````````ehКLJɮ͜ZKd________c:L=d````````````e^КʩѠÿÀ)G_`________dAM9c````````````fWКѶˤդ̾ʞO%Kje`_________cJG7a````````````gUКѦӧ̡٢ɩc9A[le____________aY<9_````````````gUКћϿ隿̝٥ǿb.;Sdea______________`b1=\````````````gTКі΍˘٬װw98Yhfc`_________________d8>Za```````````gTКэ˕سʯgic_____________________dBCXa```````````gVКуʓֿz{_______________________bNETb```````````gSКсǑŲ_______________________aZ;Rd```````````gUКѿŏźb______________________`a6Pe```````````gUК2:CʏŽr_______________________d;Sf```````````fXКѣDkjSſ_______________________dCNg```````````ecКXW``yڹ}ša______________________cIHf``````````acmК=b_cö™p______________________bOIf``````````a^КѯCd_i×ޘ______________________aXBf``````````bVКэNc_oĖ`_____________________`];e``````````eSКpUb_yŚk_____________________`f/c``````````fSКrZe_šޚ____________________`dI`a`````````f\Кѧckťa__________________c`=[a````````a`wϚђ!/ťh________________`dU.\a````````cTƚЌħ______________`d]@""-[a````````fRƚkkkkkkkkkkkklmlbUOPSQJsimllllllllnkmkkkkkkkkklmldWPOQSKMhmlllllllmj{vskkkkkklmmkdWPOQSNH[gmlllllllnfkkklmmlg^UPPRSNJSngmlllllllnklmmje]VQPQSQJMetffmllllllmh~md[URQQQNJLYuxofmlllllloe{SIIHILOWe|sfnllllllltưƤƺƘļƳźĸƿ­ƷƪôƻƜƽƜęƷĵŞƠ~ٳƹ֟ƺִw~z_ٺ꺉ءԾ®}oiabsջڳךɺeagy΀ڮ량}ʘᆘڧհԉԹnڧ禝͓Ö֫aǸ֥֞do俤՞̋Y՘Ӡžֹ~bː۷˳џѪןz`ڎؐىНɑ΅{i晽Γўֲֹwv|ӡʰ֓y֜vk˞Ԟ՚||͂{a茶ĕԇտqַoֶt}d֟Žgӊm֝l՚sw⩆yͫ׺ßԫ[ɰ֪qr|̓m~zc򛁭įݶhdzӎ\ՠж~sr״spګr}h܈͙yc|Ѫ֡qvqՒrpԈuu~ьݤseԋwӫ{tvqytp۷s}XӔȧёwԇtil²֮mԪӼ֛pvsnנjurٍrydϑk۾vut\ǧȨ~nԨիvt|`vrsyuqŘѐk|١qvzUϠ֖qnդ֞٬{֭\_wwr~aʠƓ¿ŸԊrsvWԝֳrvgԸՖ֣㭎vt]τQnttvMʣ֯ۦԳ୦zך|qnhԸܘʒ܎muwsN׮kUxrtzri좸Ѳ绵ⷴյϷܢnڹΌծrvvygYψ[byv|uva۲ž߻ѯɊŲdzöܚ۵r|tvv{Vv׮rSrwvvvxwƽũ亱՚۴罴罴Ěב\ՓqvvwuNφfYzvvvv|cƄٻϵƱ߶̱vӓ_֯rvvvzdZ׫vTgyvvvwvʁϕ乵㹴ճ۵䭋Ԑ`ԓa|tvvvyR{υkPyvvvvzd¸ɉo_ϵyֳ˱z罴բsɘ]ԓe֓qvvvxmQׯwZ\zvvvwxzɼęУۄks龹~DZ³y뾔̮{֭o]ՕiӹȶsvvvvzWiσrKswvvv{dʺuaӿͦ~庴廴Έ|vvfշo^՘msǦ̀svvvxnRץue]zvvvvzeÎuoiեӓtnfܷ⺸xòrvotc֞pk՝qvvvvzZfwwVqwvvvzdɁƘyfΎ|rujŵⳍ|ȚmtȶSטqvvm|j}֨rfմuuvvvxlUӉuhbzvvvvyhɋ̫saոׯtlVԳՉnsrhգopSsstu\qȢahȺٱsvxdυypoֲudπrvvvvyYk֤qx\rwvvvzgÞxnn֛ՒptYթπtvqu՚so@wvwwbrΛnVϩ{twjʧ}qebC7=82e{nf֚rvvvvyg\}ԿwxhYzvvvv{aư|fЃɀhV{ԞyuwlӍve?zvwqrәwOզՈn\D5V]m|}bidFts]72Gպu}tvvvvuUpσsxUqwvvvzmƂ߱tbt۷roAәtvxh΄yXB}vxk{nՖ~{NճX16EO֞qxz]xZ>rvvvwsYwrܟqvxj]yvvvvvY˖v]SԻs|rZL*avrvxbշ]Hqf]|v~Wxmbqw{W~wqv}MdչuuvxgO΃xMw}G6svvvxYqrګrvvwWtvvvvvzouɞ}xXնq}QV4`nurvwd֭]gtgvw~Ttsh֨qw|Snsu|YMջuuvwrBԑvXuІsEOvvvy_iwyȺtvvz]fyvvvvv~YÂ˺ܝvnձtkddCjekurvwg֩uhykrxRqtnǫqw}SgԋyuzdLջvuvv{HբshY֛o|]qvvygbypvuvzaO{vvvvvwwp|c׮ְlvZyjxfXwrvx_֨vd{|gzRo{{qw|T֕ttzk[Ծvuvv}RfִsuIֱqzusvvxm\yrxuvwtGuvvvvvvz`Ãe֯hntvxhJ{sv{O֭x][}~UlrwzW`֝nsykiawuvv}Sky{Gxu|vvvvwrYwuw|uvv|P]|vvvvvvyeЌcڢ`rvxkJ́yuvyXְzVY}~Torwx[z֨mrynlXyuvv}R΄xVbςsxwvvvvvXuvt{uvvzdilzxvv~dۚu֠qvvvvv}Pm̀svv~RgԒUybbvx`vՠqvv}Wt΃sv}W~w}Ptvvvv~FR|vvvvvvvw{?cq}xv{`ܗlֱrvvvvvN_֙qvv~VݞW\Qq{V֥qvwweitxtkpubfЈsvvvvBM}vvvvvvvw{B`q{{btuytvvvvPxֳsvv}Yx\zYlwd~ܧqvzim|t|`exw^Օqvvvv}AK~vvvvvvvwyGǗdg}exkшrvvvv}U~tv~Xfٿd`mbqv}^{xtTxS`֡qvvvwzEK~vvvvvvvxuWxhn`方֟pvvvv|ZlԐrv~Yq|rYԮk\v\ھvwulzhpԭrvvvyoNO}vvvvvvvyquɊ|Sշsuvvw{d|k֤qw|]jԉloKrt{ðLdkgclRkRdոtvvv{`UW|vvvvvvv{b}tvvvyomjֲswyc{a}Zǀw[jT͜jpZcu]tqܻwuvvOb]|vvvvvvv}SǼǁ۟Քqvvvwv`ԾvxsrtWmćqst\skbG9W_TKEA>>>?EPXI&>3c`տyuvvF^jyvvvvvvvJֺɅձrvvvv|YyvbJqƕw\O2   %zxuvxuROtwvvvvvvwx\ʺ}svvvTgǡ\ZFzfVxˢeĂH&  K^ekotzNtvuv|`iH|vvvvvvv|bºË՛pvvv|[tg~{]pQbɼίοȻ 9N %((# 8+g[vuvKsJvvvvvvvBẺʂytvvyjsxZ[_WҿË'"054.!BXApxuxvLf_{vvvvvvzoJ巺ė֛pvvwwj; 4eˡWٸ|A%6::6* lmhzt}WmOtxvvvvvvHoиҕ{svv~\) 9эټ~u*8>?:0 R&czu~EvK}vvvvvv|jF~~צpvv}\— EaO3EF@5" :bfx{`FG[|vvvvvx}B]Ƀ΄rvyoů7   q;9JKF8$%}bdv~Q`txvvvvvOTmÞֶsuv}a& >s[? $=: %oD]ɂøm_ʍY|BNjЩ~R榌GrÝuˏcKιԥ}>ĈhÿºoNʁ]ürGbv{iMHiNJrQvmcclcZÜ}ǺļǝOvvvymSuŹļOrvvxsRȺÇcǻͺh^vvwwTz㸺ɀxùþºvTvvvzXq帺Ɇľl^vvvz]b为ęϱ̷cZvvvzbY帺뮸ۦձٿ_lvvvzgU帺簭뾣ᾏYOvvvvylT䷺džگbOovvvvyoRⷺȈŒÙdNjvvvvvxrR᷺Ɣ͡XNkvvvvvvxtR߷ÓѧgLZqvvvvvvvwvS~ܷΝˣnSQgvvvvvvvvvwxUu۷ĉɦdNThvvvvvvvvvvvvyXnַʁֶ̩kUOYlvvvvvvvvvvvvvvzZiηȆ߼ÕjQBH;jvvvvvvvvvvvvvvvv{_cǸʗԾݷP^izծuOAGR\`yZkvvvvvvvvvvvvvvvzdZyRWVQQPPW_bԽ踺g|vp9k|~zmwgmROZ`^]bsfbvvvvvvwwvvvvvvzjT҆nvvvvvvv~tƺּwyv>HYlvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv~f񑅤wС˲l볆XHGQpvvvvvvvvvvvvvvvvvvvvvvvvvvv|f̘}tz毯խv1„h\X[aluvvvvvvvvvvvvvvvvvvvvvykƥhg}ьo秊mY֪d^`frvvvvvvvvvvvvvvvvwwֶu#KВYY\nn} $幗wgX[Mgvvvvvvvvvvv}wϫj>^ö| p[vvvvvvvvvvviة7qxlzx}t ܜcvvvvvvvvvvv~dʣZ2ǏmzgВ}3W½~}kvvvvvvvvvvv{gAԒmX˞ն) —xpvvvvvvvvvvvyp^&jܕmdt࿬Ȭ0웎mkɔrtvvvvvvvvvvvwzCӐk}bοz І嫓Hbuvvvvvvvvvvvv~k| %XỌi[|εfcpRmĺXvvvvvvvvvvvvvdr'7zrr_UeNA  ľWvvvvvvvvvvvvv|eЕ #Vk˜~u.Jyi_lTvvvvvvvvvvvvvyjƥMPԹjޮR#_>-  JOvvvvvvvvvvvvvww칗UwqO8^ב_& Tvvvvvvvvvvvvvv}uXޘhfˮnp_tǴs zsvvvvvvvvvvvvvv~hΰrqD^젶ʦȿV 7|Yj^vvvvvvvvvvvvvv}eΟڕg5 *Nrڏǫw*% vvvvvvvvvvvvvvvvvvzi۠"PrVMq}ƽ(+ bvvvvvvvvvvvvvvvvvxsϬuʡѓ[WZm{kfgn椞V¢ )+! ?vvvvvvvvvvvvvvvvvw{}ѮؖWOd#@eݿ g,)))))))**$vvvvvvvvvvvvvvvvw{d/ zlcbbaad_ѼݷuƹI))))))))))))))))))))))*#C~vvvvvvvvvvxs+)Oⷺoƺj))))))))))))))))))))))*&7~vvvvvvvvvvyj"+)_ļ䷺kǺ/)))))))))))))))))))))*(,}vvvvvvvvvv|e%*)rƾ踺mɻC)))))))))))))))))))))),$zwvvvvvvvvv~e&+*ļ긺uʼm))))))))))))))))))))*+ uwvvvvvvvvv}o+6Ļvʼ2))))))))))))))))))+*nxvvvvvvvvww&%»𺹺xʼ>))))))))))))))))*+%qxvvvvvvvvzhſﻹ˽V+++++++++++++++-*5qywwwwwwww~eĶnnnnnnnnnnnnnonkeccddcij«snnnnnnnnnnonkfccddagĪĬ|nnnnnnnnonkfccddcbxĩĤŮnnnnoonlieccddbcnĬ¢½ưooomkifdcdddbg|Įľ¼ƲskheddddcberįįƵjaabbehp}ı̷ǽ̱º̖̺˾ƛ̽Ǿ̯ÿ̞ğĿ̘ȥ|}̽˥}ò̤z}nX[p~tnuwnklr~}TZVA^t\RS_|꺉õƆu쵆cUqjxlgjq{XNJDDNgcXõŁzkV}j~unlkoqwx{}~~}{cGDHTf|Vnŵƺnth~{wtrrpmooqrtryZ\հƵƍm~qLZ禝ƵƤmuzD[ǸǜƵÓqpmGk俤Ƶƾvl`Bx~Ƶƽ՗um~XI˒ړƵƷzkmunUOڎƵƫulld\V]晽yƵƥzlyY~SStƵƚoyeUlSKƵƗ{ljeUU[VJ茶ƵƝ^O`}NX|QXV֟ƵƝжH`MYlKZjPSfGͫ֕ƵƘÒԸu@zxuOPV[M\XVH_Ƶƞݶd¨{aAsnq|YPOZ{PO_uOXV|ƵƮtѻƳiUEoV|uoOSOadOOd_RRz|ƵwөamqPH\aSvVQSOlUQOj}PXAӔֈƵƉҚٷdS^QJKywM\ukOSPMqnJRPqaPUSƵƼ~ϕ׷ϼdKqSRQ?qsXM_suzQQZk{VCRPyPURkƵƚϗnɍТócKToOSUJ[P5PSSSSVGljƵp~RDp|wxiҼ͋xxΌexϦgĻVSSFn}NrlBkiMPqZPSSSTM:bqRGBUSSSSU]zƵWdNIpzdQMcL̶ܽqȇ~`}¿OZOSN[QekE`mOKlnOSSSSU?HySS=OSSSSUGzƵƝiUG]aVPRIYckoYnn]mkkmXiv;[iOSSLV\eJWsPH~|cRRSSSTK<\_RIDUSSSSTY␺ƵjvPC{}\wQK[=}yu]MPPHnNO;PPPRLhmmonDH~yPSTFt\U_OMzRGrZv`PSSSST?KnqOTAPSSSSUIܯƵYaMMk`eOPY>tzZQSORjPN/SSSSSjmikgM=rVQSK_sbWNFD/(,)$FU_dMHaj_rPSSSSUHAXSTI?USSSSVT΂ƵƝzWH`[iYIc=Tm}TRSKYaSH0VSSO`zq_i`S9zr~_MA1&=AKVairWEJF2P\O@'$2t~RrXQSSSSR#'18wmOTUBYS?+<=YYdePSSSSUBIVhOUHCUSSSSWPzƵƹldOHvNyqEeJ;czPSTFeXW>:XSTMnsrGhSV??m2?IESUDorOSSJJaQB|xN8MlSlRSSSSTK?PlvPSS=YSUKqstI|jPUI2]?DSRTERvOSSPAjR=^S7JMgYQSSSSS=PVSRUDGUSSSSYIܯƵrmQB_`PMfGEVsK;?O`kjFaK+&&*fSYBCYSUIwtsYklOTNApk`eqXQSMBxPSSU<}sRDIG%IaPSSSSU?IPqZPSOi}RM:M >SSSSSTIAS[ePSU?JTSSSSWHυƵc~QQ8TTkb?N2 bT~vPSTCoS8WQUSLXSXDrsgWpOSTV[}UkOSW5d|QRSU@RVR4|vH,,PSSSSP>SP|mOSTJAUSSSSSYU娛ƵƖyjSC<PWdO?6FSywPSTE}}@3O]SVSYBpvdUqOSVNwsS|uOSX;J~RRSTI<[T7gSX3&PSSST?OPdvPSSS=QSSSSSUNƵdWTA_|OXd`:=&DNRrwPSSG~vBIl\SSYBkwi]sOSWKVYk~PRWCARRSSP6dS>Q^P`19SSSUCJSUQSSUAHUSSSSSXG܈Ƶƛ~lSNpzQKcGG0JHLRkvPSSHsRIqbPTYCixlfvOSXJ]`\TRVHDzSRSSV?epPI>kN\VAOSSTHDUOuSRSUD8VSSSSSSShxƵƵn|WFwyKQX?UKTIBSduPSTBysSGsuJUYCfxqs~yOSWJcfQ~ZQULT]SRSSXJU|PR4yOVYRPSSTMAUP`TRSSP3RSSSSSSUCzƵu[G}ƑxI^M^QSTJoyUAEXXEcsvj~PSTSrtsMmfPUMeMTRSSXFd[T>G[PTSSSSSS>RSQ\VRSSUG,SSSSSSSTP\ƵƜxeE}phU;o`PSTNMh`PiURU?d{VAKVYDnk{b~PTP]RzNaiPUNgbzVQSSXFOfPK6eOSSSSSSU?OSSSSSSSSU52YSSSSSSUHtƵƜ{]JfleGUSSSSSTPYƵƳlZ`fbyUQSSSSSSPOSSUSTM;ZAOXJlZY}PYCc^PUSUIu\ZPSTP^`eOUMYgOSSSSSUCJTSSSSSSSUH7ƌDUTSSSSUMmƵƘjgctXPSSSSSULpnZPSSSP?v[BgPP\ZY|PXFodOSSWFlZPSUJjV}PYDYrOSSSSSUAIUSSSSSSSTP3ƜKOUSSSSVK}Ƶgk]s\PSSSSSVLRfOSSSVC_\AEKyft[yPVP^gOSSXDr[PSVEbZTGn^|QSSSSSU;IUSSSSSSSTS1ƺfGUUSSSXCƵhlWudOSSSSSWJ[|PSSSXJVaAKMhf[vQOjmjOSSYEb\PSXCVrPSS\URSSSSV9BUSSSSSSSSU.ƌKLUTSSXGƵmiQxnOSSSSSXDb~ZPSSYI_e@nDMi]nTFkmOSSXJY[PSXJ_SX@^YQSSSSY2;VSSSSSSSSV/ƹxGOXTSVEƵ{hK}zPSSSSSYAPiOSSXKwmBF9h[fVGhqOSSS\XYQTQbgkRG}[_PSSSSY/6XSSSSSSSSV3ƲvFOYVVFƵƍPQUQSSSSY@[|PSSXL[uCf?lVaS[vrOSUJyaVQVFUTSVdfOSSSSX/4YSSSSSSSSU6{ƺKIX[HƵƾ`K_PSSSSXBvhYQSXJ^G|EyU_FiuOSXFsTQYG^rT@UoOSSSSV04YSSSSSSSTRDpfLMCƵƶwhmOSSSSVCd|dPSXLpPtdqV^JwKASLSSRdql`Ig~ivPSSSUN89XSSSSSSSUOaߔƼa?ƵƱe}PRSSSVHt[qOSVPV_eT;QQswk6GK_xgjEL=be[FR}QSSSVC=>VSSSSSSSVEċƿƵƃXQSSSUNfUzPSUZsgcYeqWGeSAJ>jlJNPTRCmg}SRSSY9D@VSSSSSSSX;tƵƭofOSSSSSV_STPjZbKzh|Oy¾fgQYLmKbD2+SZPGA=;;;M8QTSSSSSSZ3X̄ƵƦjVPSSYN' 6Ɔϻ~u0I`kmdP/R#XURY1S5XSSSSSSVJ2qqƵƃ~qOSSXD Bdj^wyo[9:\\~TVD=7?VSSSSSTX.BㇾƵk[PSUNy4  |"uVfx`=%4%%}_ZySYBWQTSSSSSY8;RܰƵe}PRSXSy$;nR}<  2?UB?f}y_<D_>B ꗄWuUJ`>XSSSSSXC@3yҀƵfkOSWDľt/èЪd?Acm2+OgofN/k\cꕅYqUDqEVSSSSVG=F5zƵo\OTSsǡӻZF%+>EA02|䄇UnCkRRSSSSVIUH0ܯƵƤa}SSOqvQ ĿʬĻb&io@sCVSSX>;RU:cτƵ{huPXGܳ m텇SaaYNTSXF5SUDCtƵƫponPSi JؾCÂRKIVTY^xƵư[fujUےl~3g3D{ƵƛzONRs0ΜᑸƵƥtJݝ<[ќۯƵƷ޺qÇ[5ԠͅƵ|Ʋ̝u/֡xƵ`½g>nנ~ƵUýſi:Wmr`@3Oנ⑷ƵgżCSMGILF?ءܰƵrɿžř:SSSUM;ؤ˃ƵjʾƾCPSSTP:tئvƵW˷ƿ_BSSSS;bتxƵcŽýo=SSSU>[ج⌼ƵƆƾþeGSSSVBI٫ݨƵƌĿƪƱZASSSVD@ثʼnƵƜৰў̪ѷ}RNSSSUH<ث{ƵƠݩ൜صO:SSSSUK;ث{ƵƞѦX;NSSSSUN:uثƵƞŎܹ[>KSSSSSTP:lجƵƞØ|N;LSSSSSSTQ:pثڹƵƟ’Ŝ\>BPSSSSSSSSS;kثтƵƢΛcC;ISSSSSSSSSST<^ثyƵƣݽzX>=JSSSSSSSSSSSSU>U٫zƵƥֵ|dH=@LSSSSSSSSSSSSSSU?P٪⌺ƵƦ߼۸dMAI7KSSSSSSSSSSSSSSSSVBKڠ㧞ƵƧѪDEAABDPY_ƾƵƧҩIVSO1ixzuksdjQOY`^]ao\ESSSSSSSSSSSSSSUJKO(-MRSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSVcƵƹÃޖMHλji/B7>KSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSYUƵƑ[gނӽkB@l<46ALSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSXLƵƙ|oǜg볈WE??OSSSSSSSSSSSSSSSSSSSSSSSSSSSVHƵƾulrۧɢq#9ӊfTGFGLRSSSSSSSSSSSSSSSSSSSSSUKƵƞyc訂`}uȄiݟjf զ{VKHIPSSSSSSSSSSSSSSSSSStƵëg*NŇvSxUXhg|{ +ɩ嶑mYGJSSSSSSSSSSSSSSSSSXWƵƧէiC /GZa`XQPL*$!GLqֈ 1//////2!)SSSSSSSSSSSSSSSSSYLƵƃʺdDDB&$yf_  *OxW [-)0///////-SSSSSSSSSSSSSSSSSXHƵwYRKFa,; 6Y꼜 A/////////3GSSSSSSSSSSSSSSSSVJƵƇh)jb!XoTb͘ !*0///////1%3SSSSSSSSSSSSSSSSTPƵƮ8 i Do%@_{ʧџ! )z(3/////////(SSSSSSSSSSSSSSSSSViƵƖj4 OSSSSSSSSSSSSSXHƵƬ '= ײԡλN/////////0&GCITSSSSSSSSSSSSXGƵԦՙA Ԭ•t'2////////,2HDUSSSSSSSSSSSSVGƵqʜ.=ƦѾנ&3////////0%OATSSSSSSSSSSSSUKƵiǛJ1${-dzҮף,3////////2#P:PSSSSSSSSSSSSTPyƵržΨҡԶ?2////////0'J5EUSSSSSSSSSSSSUgƵ}վզЮg,0////////-;:;VSSSSSSSSSSSSV`Ƶƽ׺ڣ`&1////////0+A5VSSSSSSSSSSSSXXƵƷ׳ݩʆ0&//////////2&G2USSSSSSSSSSSSYOƵƬٯӢS$)32//////////1%E/TSSSSSSSSSSSSYLƵƞִڬӰg4%-42/////////////,:2RSSSSSSSSSSSSYMƵƔڨǠi2&)12/////////////////,4OSSSSSSSSSSSSYMƵƏѓڢݼr/",320//////////////////2'7MSSSSSSSSSSSSYLƵƇؠɩwqgbdI30/////////////////////2"?LTSSSSSSSSSSSYNƵ~ם̡e|ola_gqi///////////////////////0'AIUSSSSSSSSSSSYLƵ|֛ԢLgkpqw}////////////////////////,9GVSSSSSSSSSSSYMƵƵҘҭwƸ?////////////////////////1FVSSSSSSSSSSSYNƵ'$;יҸø`///////////////////////2,LXSSSSSSSSSSSXRƵƖ$43bac{Ĺ///////////////////////2'LYSSSSSSSSSSSX\ƵL,/?|h^eebdfd[ahVOwźB//////////////////////0$FYSSSSSSSSSSSVfƵƽ+0/TѸxjcaaa`c^tȻ]//////////////////////0(HYSSSSSSSSSSTPzƵƠ$2/gƷpȻx///////////////////////,AYSSSSSSSSSSUJƵƆ'1/xnɽ://////////////////////.7XSSSSSSSSSSWIƵm*0/»q˾V//////////////////////2-USSSSSSSSSSYJƵn-22zy/////////////////////1&RSSSSSSSSSSXUƵƞ1D}˿A//////////////////0/ OTSSSSSSSSSSoŵƋ%1ýҁP/////////////////2+PTSSSSSSSSUIõƇ¼ш³i11111111111111130"1PUTTTTTTTTZJõþɻxwwwwwwwwwwwwwwsmjklkkʻwwwwwwwwwwxwtnjjklhn˼wwwwwwwwxwtnkjkliiz¿̽wwwwwxwuqmjjkliksĿ̾xxwvtqmkkklkipĿtpmkkkkjio}ÿ¹¾tiijjnr{6;?%=@ >?@=& @OkaeH/41XkjeR*5XkjxeR:3Xkj)eR6Xkj&-j'9bR> ,u.Xkj- .j&'R7 x%*Xku_25 1.j5$/3:zR(>  S9XkvM_!9 .j+ '6JBkwiR4<. XW51XkcZؖ`Q$ 2j$ -''=hRaC( ]` "&Xkld00(y: IVj5 00/A<N.0 .li"#jR=/0-[p*0P!!XkT{k6&jE #I=B)<l MuR@ 7"-sj#*$Xk{{E3h\D.jC3BF1$ク8X;[qR@?>4&CY#&XkwK:!p jC1aM;_nnjR@ tF#;-r_kwKo+,YpjC1pGF8;_s9R@#5q#B 2]kwK  c'jC1}y#;_sKR@-u#9^kwKD/qw"jC1ksSB;_sm=R@ ")#V>R+kw>:&}}zuo jC1m{}LC;_i_R@`)T9Dd#kv6,cjD=5Z:T($R@!%>1.ww}Ewwy_wwwm#@kH.e:p\n Z#jHalllllllllktDa^rxoYb-DPBbllcRR5 cg^y9#jLk&ɐ^!kja6ggwRR?CR z }faUi*otӗU㔵/ #kR *eDR dv/_cpPfDxA[3K5 A7qouiON%+Y?#|CBN7u+=^#{mL)*w*+Q{:e4;.}! αN;WwP,UQЏ{ʕ+mц!ͤʿ+&U +ZzPw>͡"ʺIۋ< jiYN}Bh8Y`J`cap{q>pT3jUfOeQ Ox?Io96Cl3/JJJ9Bk3B.S5 k.S5 -IZN50&3H4By܎ ;`wky܎ NK3ܙ`ɇHm'ܰ:|B0Ee27k0E8F6p? 6AB1Nnzk1NlPppE;2B7$ BRk7$ Y$MR%mii&>thEBl Dkl kHWoxff[sFE BB%v_pkB%=V:ffbc E=1B&?r&k1IYcff MZEB%A nTk%AI$ff{G6|E }BNuh8Xj kNuhI\ffpGeE@'B=T8< yk=T֕gTBcBrffpG7EB)&Kdk)0?,ffpG]E B\ke ffpG^1EBBfKkf ffpG j/IC,_J8+tEEEEEEEEFJWm ,_J8+NggqHZ==#=#=O HtLo`%tLoh)Hyݠ{mmwN#=#=XLP ] *pvuVb:G#=#=ngcnQcnGf=M+j^Sfr#=#=N\\\-\\\\,dĖh U\\: \\\']e`]\\\\\\\\\]am dĖh QzpI\\\H(yb\\\J9\\\B9\\\=+\\\M;:\\\D \\\\ \\\\C$l C P*̾'iғ "4.DCCCDDCCCFDCCCFFCCCCECCCCDCCCCDDCCCDCCCCDECCCDCCCCCCCCCCDUDCCCDCCCCDECCCDCCCCCDCCCCCDCCCCFDCCCECCCCDCCCCCDCCCCCDGDCCCDCCCCDFCCCCFEEECDCCCCCCCICCCCFDCCCEFCCCCDCCCCCCCDCCCCFUHDCFDCCCEUHDCFKDFDEC`IECUFCCCCKJUIDDCCCCDCCCECCCCFCCCCCCCCDFCDCCCEECCDDCCCDDCCCECCCCCCCCDFECCCCCCCDDDCCDCCCDDDFCCCCECCCDECCCEJCCCCCCDICDDCCCDDHCCCDDCCCFDCCCDUCCCCFEDCCCCCCCCCCFCCCCDCCCCGCCCCEDCCCEEDCCCCCCCCCCFICCCCCCCCCCCCDECCCCCCCCCDGCCCCDCCCCDCCCDECCCCCCCCCINDCCCCCCCCDGCCCDDCCCGIDCCCDCCCCFDCCCCCCCDCCCCCECCCCCUCCCCENCCCCDCCCEDCCCCCCCDCCCCCEDCCCDCDDDDCCCC`FCCCCCCDCCCCCCCCCDCCCCDCCCCCCCCDCCCCCCfCCCCCCDDCCCDCCCDDCCCFEDCCCGCCCCFDCCCDEGCCCCCFGDCCDFCCCCCDCCCDDCCCEDCCCDEGCCCCCFECCCCGDCCCEIDCCCDDCEDCCCCCCCDCCCDDCCCCCDDCECCCCCFDCCCDEUPCCCCCCCDCCCFDCCCDUCCCCFDCCCDFUDCCCCCCCCJDCCCCCFCCCCCDCCCEDCCCDFUDCCCCEDDCCCCCCEDCCCDNJCCCCCCCCCCDCCCCCCCDCCCEECCCCECCCCCDDCCCFLDCCCECCCCFCCCCFEDCCDECCCCCCCCCCDCCCCfDCCCECCCCFEDCCDNGCCCCDCCCDFCCCCCCCCCDCCCCCDDCCCDFCCCDNCCCCDDCCCFGCCCCDCCCCFUDCCDCCCCNCCCCDECCCCCCDCCCCDDCCCEUDCCDCCCCNCDFEFDCCCEUDCCDDCCCCCCCCDCCCCDLECCCDECCCDDCCCDDCCCFDCCCCfCCCCFECCCCDCCCGCCCDCCCDHCCDGCCCCFDCCCEECCCCDCCCGFCCCCCCCCCCCEGCCCCDCCCCCCCCDCCCCDDCCCDCCCCGDCCCDDCCCFFDCCCCDCCCFECCCDCCCCECCCCDCCCECCCCCCCDUDCCCEECCCDCCCCEUDCCCCCCCCCCCCEFCCCDECCCCCCCCDCCCCDDCCCDCCCCLDCCCDDCCCFDCCCDHDCCCFGCCCCDCCCFDCCCDDCCCNCCCCIDCCDDCCCEGCCCCDCCCFCDCCCCDDEFFFCCCEFCCCDCCCCCCCCCDCCCCDDCCCDDCCCEDCCCDDCCCFCCCCDCCCCFCCCCDDCCCDCCCDDCCDCCCCDCCCEDCCCECCCCDDCCCDECCCDEECCCEFCCCDCCCCCCCCCDCCCCDDCCCDDCCCDDCCCDDCCCFUCCCCDCCCFCCCDIDCCCDCCCCCCEDCCCCCDNDCCCECCCDIDCCCDCCCFDCCCE`DCCCJFCCCCCCCCCDCCCCDDCCCDDCCCDDCCCCDDCCCFFCCCCCCCFDCCCDCCCCCDCCCCCDDDCCCCCDDCCCEDCCCDCCCCCFCCCDDCCCEDCCCDCCCCCCCCCCDCCCCDDCCCDNDCCCCFCCCCCDUfffUffffffffDCCCFCCCCCCCDFCCCCCECCCCDICCCCCDGCCCCCDDCCCDFFFFFFFFEECDUFCCCCCECCCCDCCCCGICCCCCDECCCCEfUDCCCCCDECCCCECCCDCCCCCDCCCCDGDGDCCCCCCDCCCCCICCCCGICCCCGCCCCDNCCCCCCCCCCCCCDCEDCCCCCDCCCCEDCCCCECCCCCCCCCCCCCCCCCLCCCCCCDCEDCCCCCCCCCDEDCCDCCCCCCDCCCCDEDCCCCDCCCDDCCCDDCCCDECCCDCCCCCCCCCCDDCCCCCCCCCICCCCGICCCCGDCCCECCCCCCFUDCCCCCCCCCCCCKDCCCDICCCCCCCCCCCCCCCCCCCCCCLUDCCCCCCCCCCCCKIDCCCCCCCCCDCCCENCCCCCCCCCCCDCCCCECCCDCCCCDDCCCDDCCCCCCCCFDCCCCCCCCCICCCCGICCCCGDCCCCDCCCCEFCCCCCCCCDCJCCCCECCCCDCCCCCCCCCCCCCCCCLFCCCCCCCCDCDDCCCCCCCCGCCCDJCDCCCCCCCEGCCCCDCCCDCCCCCCCCCDLDEDDCDLCCCCCCCCCCICCCCGICCCCGEEEEDJEEEEFJECDCDDEHEEEFUEEEHDDEDEEEEEEEEEDDDDFJECDCDDEHEECEDFfFEEECFCCDCCDKDEEEECEEEFCEEEGGEEEECCCCEFEEECUEEEEFUEEEEFDDGDDCCCIUECCCDfHDCCCCFIDCCCCDCCCCCCCCCHCCCCCCCCCCCCDGUDCCCCCCCDDDLCEHP;999:;999:;999:<999:9999999999::999::9999<999::999:99999:U9999::9999=9999:999::9999999999;:9999:999::999:999999:=:9999:999;<9999<;;;:99999999I:999:99999<9999999999999:999:U=::F99999U=::F<>;;;:@I::9999999:9999:999999:<;:9999:9999:;U@;99999:9999;99999U:999:;999:FU9999::999@:99999>9999<99999;999:FU9999:99:9<;999999999C::999999999:999999;:999:;:999::999999999;L9999;:999:9999:>:999:999;999999;999:f999999999:>:999C;999:9999;9999999999:99999:9999;;999:;999999999;;999:9:999:U:999:999C<999:=99::999<999:99999U:999:999CC9;9;9999:U999::99999999:9999:L<999::999;999999999;:9999f:999:>999::999;:999;99:@999=:999:99999>999::999;;:9999999999:@999::99999999:9999:9999:9999=:99999999;F:9999:999:;9999;999;:999;999:999:999:@99999;9999;999;U:999999999999:;999::99999999:9999;9999:9999B:99999999;<999:;:999:;9999:999:;999:;99:;;99:I999:99999;9999:999:C:99999:;::;999::9999:99999999:9999;9999::999<:99999999;;9999:999:C99999999>9999999:<99::999;99999C99999999>:9999:=999::9999999999999:9999;9999::999::99999999;@9999:999:9999I@:999@:99::99:999:999C999999999I@:999@:999>:999:@9999:F999999999:9999;9999:<99999999999999;:9999999::999:9999::999999D:99999999999:999:9999:F99999999::999:9999999999:9999;9999:;:999:<:99999UfffUffffffff9999;99999999::99:;;:999;I:9999:;999999:9999;;;;;;;;:;;:U::99:;;:999;9999;I99999:;9999:fU:99999:;999:;999;<999:;9999:;D;::999999:999::9999;:9999;:999:;999999<:9999:999999999:9999:99999>:9999999999999999@:9999:9999999999999:;><;:99999999999:9:9999:999::9999:999:;99999999999999999999:999::9999;:9999;:999:9999999U:99999999999:<:9999=;999:99999999999999999@U:99999999999:<=9999999999:9999C:999999999:;9999:999:;999:;999:9:999999999999::999::9999;:9999;:999:99999;;:99999999:@9999<999;99999999999999999@;:99999999:9999999999;999:@::999999:<;999:99999:9999:9999B<:999:@9999::999::9999;:9999;;:::>::::::@;::99:;@9::9@:::;:::::::::::::::::F@;::99:;@@<;9:;;f::::<9;::::9<D:::;:::::::::;;:::<:999<9:::<@:::::@:::::99@;9999IU:99:9f@999999=:999999::99999:;:99999999999:GU:::9999999;L<;=@}}{}vovwwyyyyyywwxy|nymvzvwzzusy{yzv|rfs{wphj}yoozro{~|{mrot}{|zbajot||}to|y}||yqpno{{yxvttvy{wy~|zxwx{ubm~˝|lk{}`fbMxtghx鹉ƚь쵦g{y|eZUPP]yunƚЊ|h{~}tRNSaxfϚčtzlxհКѓxZr禝КѬ|OpǸǤК͚Ql俤КȋpK{КLj՗fSˑښКxcVڎКѴ~w~uldb晽}КѭgaawКѢvb~`WКўˈ|vddjdS茶Кѥαm\oZg_f[֟КѤŲSoYg~Wj|^au^ͫ֜Кџ×ǯJ\]djYkfcRoКѥݶfϺrKf^\h^[n]e]КѸzô{bPd\`\sv\[un_`{К}۹οq~^Smp`d_`\}b_[|^fIӔΎКѐۭİu`m_UXYl}[`^YųV`]q]bYКƂ٨Ÿ´uW``_KfYo^_i|dN_]^b_mКѢ٪~ԠتötWc\`cGy\Yy~qcKMa`q]fPʠКπ٫yݩߪp]^`Hs~]`Sy}Ԑt`^LjlCY_l_`iEКђܱÐНϑkg{zd\YU~رλsY_a^@VFa]qh_c\gК|߽yڟ׈Қ—rūԫɾhoZßᤘyDz]``bSInJPb`d``hU⾊КѤМ{߼ݬޯpˊɗ{ܥ䧌ݟ]e_``dF_]D]````aaȁКсޫ֝Òʕ}٢xӇڢ~ݦ嬈˺uJv\``a_@}lSIc````eSvКѿśx߹{߮Ϙ܇ඓᯫږbvN]```cQI`ETb```a`{zКч|w֜Ԝ”ߎ˗~͓qtMvO|e_```bDdlVBb````cRԉКoZM߻pÔ}ߴtzڢȽ]{LvRpw\```aXD`IKc````apϞКѮkW]Ӿۢu௓vިwަⷐiZ}LyUf^````cHUk]=^a```dRʺКv`Oĵr؞؟ڶuþe``RZ~M}{Y]h^```aYCr`RLc````c^}КatZVv_ZrV˘Ҝ~˷\˽h\`Zj_w|Pp[V~\````cISaaF\a```cQxКѥycSmrd]^VhĚs|gʯj~}}}tEk{\`_XdkvVe]Rt`````aWFko_TOc````b^ԊКp^Nl_WkFnY^]T[\E^]]_iOU^`bQkbo[Z`Qhq]````aIW\aK]a```cTΦКbrZY}qv[]hHh_`\^{^[8a`aasz}yYFd_aVore[QO7.3/)QcovYSr|m]````bSKe`aTIc````dWКѥdSqk{hTsFcb_aWhr`U#R`]`aOK;\zdafS׭z\adpa\`fI^_``aUKka?yaf:-^```aI\]u]```G_`````cZzԦКneaIk\fvpBG+P^`]`aQMT`afTۦ\`ejog|^`eSX_``a]Lu`I^m^o8A```bMUab_``cLSb`````fM̓КѨ~aZ_WtQQ7VT[`}]`aS`U]agW۠\`ejۊpkb_cUc`_``d[}^UH}ZkdK\``aSOb[```cO@d`````a`kyКeQW^gJbVaVRau]`aN`TXcgYۛ\`ejyx_h_cZq`_``elw^_=\cg_^``aXKb]pb_`a^:_``````cOКЃkRњTmYo_`bXOdk^`d@bSSef]۔Dž]`cns~Yo^bWaa_``ekbd?wa`e````a]Ia_ad_``eBLe``````bSКјqPoьlNzx]`bY]ib_`bHcOZef]ۏ]aayܫX~w]bYma_``ffykbKVj^a``````H_`_kd```cQ2aa`````a]gКѩvOyycDo^`a[sp]{b`cIudPidfZۘz]a^n[r{]bZd_``ednw^XEw\``````bI\```````ab=9f``````bS~КѦmQpvF^rl^`a\syZte_cJheQۀ`g\܎r]aZݥ_iy^bY|g_``eg۟\dG\``````bIYa```````g;GUe``````cLКѪsVqh{XZ`jh_`a[Zje_bO[fQܴTi\ݔ|r\cTncbq_bWwi^``ekp_fMda_`````bKWa```````g>SQe``````bMК̤tsleynd_cd``a_Ȁ\cd`bUNgQUj]ݜnt]eQ߰h_i`cVtj^`a`xgaWml^`````bMVa```````eJGRca````a^bКѽsdjxsb_`a``a`voa`b`aZMhQkf]׆ru]fS߄m]c`cT߀j^`a]ww\cqsy\`````bNVb```````cS>їNca````bYvКѠtysf^`````bpۮi^``a^WjQݔ^`qvu]f\ᾇu\``eSߚj^`cV|^fZ\`````cLVb```````b]9ѧT\c````dWКq}ll]`````dnuy\```d`xlPTX|w]dnုy\``fRḆj^`eSxiaUv_`````dDTb```````a`7pQbb```eMКr~eu\`````ejp^```fhtqPdYzu_[̈́|\``fUxk^`eS\a}c_````e@Mc```````ab3їVXcb``fRКu{]\`````e_ےh^``fdvOP_}vaR㏨\``f`pj^`fbu`fVtg_````f9Ce```````ad4łQ\ea`dOКуyW]`````gUo{\``ffOVB{rxdXၳ\`aazrg^a_ޗ|`Sn^````g6@f```````ad8ѽO[gddQКѕ]]b_````gSr^``ffqNHkqax暝\`cU}d_dRsa`|zx\````f5=f```````ab;ŀʼnRTejSКeWn]````eRݯ}g_`fcSQhnS~\`eS生a_gVuaRy\```ac8=f```````a_Jp̜lVYNК~w[````eRݐt]`feἅ\vfmZ飩VK`]飖`a_ࣀpTĀ]```bZ@Ae```````b[eҌƛgEКѻo^````dU|\`dkpodGb^Ό|>QVwȗz|QWIֈvi[u_```dNFHe```````dO˸Кыe_```bZl^abzysuyeV{`KVHթ}~V[do_N````gAPKd```````fEtКѷx\```a`zr`a^or_鶕[zx^g[ȻXtO:2fpdYSNLKMMS^gZ-4>]b_``g9MVb```````g>ԅКp]````eh⦍c`R䞋G믽~YߜA* $.ba_`a_CA_a``````abLͬКјe^```gWsLJ>`IdޓP(  K^ekotz"\```eNU;e```````eOqzКn}[```eSۓYfd`ЃDqܮɡϻ 9N(7=<3 ;?~`_`g>^=g```````g6}Кєa_``bVփaJh`aË26JQPF1B\_qa_a_?SMd``````cZ?ϝКs|[``aaޢG  e``````dV9srКы[``eR NcdSkmcQ3:ٌbdQVFJd`````ae6LԂКtl]`bZ@#  sP[stlV7#/!"}vڈ`g]^a`````gAE\ΥКn^_`fe픲+ FaӿF -9O@9[pvmV6AZ7; ڀbUVf`````ePK;~ŅКo|[`eN7sH:Xc0(G]c\F+gSY}b_ٴRd````eQGQ>zКxl\a`g#?zs#'8>:-/rorO|`a```dUFc4eԈКюc]fNN 11ȅ=./6@gvsrkWf```fVHcS8ͤКѮl``\SĨȲW&٦RٹQe``fJC`cCkÈК˃x]eR Ĺʿozrٌ[b`fRK`cPMuКѶz]`x B<glkcagLu`aC}КѲz}aS'9CA=3/29CCHH-;ٌcXhcVncNPњКѸ|vfQ\UjWc\De˷КɒqrYْQSTHf{Кѹbt|^ϭD@MyКѤċ[Z8ӊКѯTVeĒͥКӀ>ƕК˄9ɗzКgTtʖ|К\NW>YʖӋКq^`XTXXPIʘΤК~E```bXD˙ʿКv^]``a^Cz˜xК^M``aaDk˟wКiH```cHcˠԇКэZ```cLRˠϞКђۼߊL```cOIˡʸКѣ谠⼰t]```cTFˡ~КѧɬtEa```bWDˡ{КѥG[````bZD|ˡӆКѦŕڌOXa````a]DsˡіКѦuIXaa````a_Dvˡ̭КѨ“ՍRP]aa`````a`DqˡĈКѪΞҘ^HUaaa```````aFfˠzКѫ‚PIVaaaaa```````bH]ˡxКѮַ̠gMKX``````````````cJYˠӆКѯ߽ҥj{WV````````````````cMT̖՝КѱĤCQbx›zlx}W```````````````cQK}FKKGGGGS[^~КѱťTd`[DP```````a``````cVE߈Y```````faµКѳŇ`b`MJ``````_^`````a\Dɥxnhfgjr_X```````fVКѲfea^ٸD`````aVCc`````?G[HHKMPTY^```````_ZVQOPSX````````fRКѮUghB`````bP9a```cIN]````````````````````````````````dSКѹz}~{{ŴQj@`````bKDXa`cSIc`````````````````````````````````bZКсћi?`````_HQNba_D_````````^`````````````````````````acmКх򾶴kS````aVK]GbcJSb````````_``````````````````````````fZКёp~{Y````bOSbG`\Ha````````````````````````````````````gRКѠ˄e````bG[dKZNRb````````````````````````````````````fPКђ吨ѥfݯXLS_<`bSRI^`````````````````````````````````````dTКѝ좴ʢkټl=KY[FJa`````````````````````````````````````a]КсɧپĆM16\_````````````````````````````````````adkКĨ͊ꖃ٣n>\HLX``````````````````````````````````f\Књr뙠١<:UDBM[``````````````````````````````fTКѤΩتُ빞p_O[```````````````````````````eSКȭ縱ԗ0ʇp[NNPX_`````````````````````bWКѦ}tטܙ}Ӆ_էZRQT]````````````````aa|Кʹj%`޾ht "帓p^NPBS```````````edКήoMփӣﯥپwK```````````gWКϴv*DԦ걨걣٥"&ᠪQ```````````fRКǩxy_!Cۢݵר뺑6W˶ڀW```````````dSК}%!Tܢ⿁ұ+' ˜̩z[```````````b[КѤd)Cݢ઎̶ҹ !!/𦕺sq՝t_```````````acjКі {з|+TJ``````````````````cVКx.qlƙ[c-Q`````````````````a^КDŽڶ{ԩ߇}넡ꯠYͬ_eJ7``````````````````diКѢݶ߀nXyۭj㿡!$-_b_&``````````````````fYК೮׬џpɳa%!&,``dD V`````````````````fQКѷSƱj $ĞОb=]NR`_a\8`````````````````dQКɿwѬ뾓ϵwr /Neb___e3`````````````````bXКѕܸˆiDb____cS\````````````````abrКшޡ$tû~!,b`____`d A`````````````````f]КѱѮc $=h{yn71+gz>d______e@&`````````````````gTКъǡnZ@!"q[O 4~T S'!Rc______a\`````````````````eRК֎e B\*9 P)?!+a`_______h*N````````````````cTКђ|'c]S࿑ "$#&Ub_______dI3````````````````a]КѸ4 c Aq1eɼœ"n/h_______`a'`````````````````dpКўög2 ]ɾfj1*i________h-]````````````````f]Кь٧PR|W`zKZb_______eBD````````````````fUКуɿ =Ŗչǀ@e_______cV<^_```a``````````eSКЂ˿ν Rƶĩ7d___kw`a4]a}```````````cTКyϟ Wmk.3<Ñķ@___ery=]sz`````````a[КɼɽѴxɞUp *Ƕȟ¿YTa_J`````````abrКѫȤةŌPưȿŞݾɣxFd_|``````````e_Кҩߩќ絧ٱ΂D˽ξŕ̿Ɔ0i_``````````gXКի᪖ګ܌҂3/ ̧ټĕƿƟ'j_~``````````fRКլᬖѲîx$= ˦Řʿ°@a`c{```````````fRКլ᫖уԣǐ)A 驽š׾jMd`Z9TOb````````````dRКլ᫖Ŀg_``r`aКլř᫖ΨϷإ\/"Ψ߭;斖_`ٻКլ踖᫖쨖쫖ּ㡖薖x˖gʖ柖λ閖Кլ՟᫖ݘ˖뙖ﮖȤߗ̀іkіЦКլƗ᫖묖𗖖䖖ޖ¯񱖖ٖ椖Ӝ_`ǖȖÖКլ᫖ǖ–ӖѲܖ̖ˑ󨖖_`}ưָКլ՟᫖ᗖ–ϫ𘖖斖X%KvÖ_``gUז׻ҠКլŗ᫖昖薖斖і𖖖ٺŖ陖N[le___```gUȖԽѴКլ᫖ܖ򯖖򟖖Ö򖖗Җʡ𖖖–٥ἢܖea____ʖ_````gTӴÖКլמ⭖ϖÖϘߖϖ٬______Ŗ_````gTԨ̖Кլė֯ՖȖ疖疖Ζӕ斖˖ٮז______Ȗ_````gVԵÖКլ󱖖㖖ǖ𖖘ᖖѓŖݖĖ_______````gSƖѭКլڞ蜖񞖖ɑܹҖݛ_`__`__````gUǖЗКլŗ樖ϖ˖ŏՖ񶖖x____v_````gUΠϟКլﰖ˗ߛʏꘖ᝖זӜ_`}```fXזѼ策ɖɖКժܝҹ쉖˜ڬǖҺΡݱ͹_````ec蹖峖ɖɖКӧ–񦖖}맖̀ϖkϖ``acm֜ږ沖ɖɖКᥖꪖBݖôם⠖斖쾗}ŖjƖ``a^ŲЖ߳ɖɖКβѯXIJ;ޘΪtg``bVКэ]`__t߹e`b```eSȦٸКpUb_yŚl__________``___`f/d```````a``fSбͲКrZe_šޚ_`______`___________`dI`a`````````f\ȠКѧckťa__________________c`=[a````````a`w騖Ϛђ!/ťh________________`dU.\a````````cT԰ƚЌħ______________`d]@""-[a````````fRƚkkkkkkkkkkkklmlbUOPSQJsimllllllllnk _mkkkkkkkkklmldWPOQSKMhmlllllllmj{vskkkkkklmmkdWPOQSNH[gmlllllllnfkkklmmlg^UPPRSNJSngmlllllllnklmmje]VQPQSQJMetffmllllllmh~md[URQQQNJLYuxofmlllllloe{SIIHILOWe|sfnllllllltưƤƺƘļƳźĸƿ­ƷƪôƻƜƽƜęƷĵŞƠ~ٳƹ֟ƺִw~z_ٺ꺉ءԾ®}oiabsջڳךɺeagy΀ڮ량}ʘᆘڧհԉԹnڧ禝͓Ö֫aǸ֥֞do俤՞̋Y՘Ӡžֹ~bː۷˳џѪןz`ڎؐىНɑ΅{i晽Γўֲֹwv|ӡʰ֓y֜vk˞Ԟ՚||͂{a茶ĕԇտqַoֶt}d֟Žgӊm֝l՚sw⩆yͫ׺ßԫ[ɰ֪qr|̓m~zc򛁭įݶhdzӎ\ՠж~sr״spګr}h܈͙yc|Ѫ֡qvqՒrpԈuu~ьݤseԋwӫ{tvqytp۷s}XӔȧёwԇtil²֮mԪӼ֛pvsnנjurٍrydϑk۾vut\ǧȨ~nԨիvt|`vrsyuqŘѐk|١qvzUϠ֖qnդ֞٬{֭\_wwr~aʠƓ¿ŸԊrsvWԝֳrvgԸՖ֣㭎vt]τQnttvMʣ֯ۦԳ୦zך|qnhԸܘʒ܎muwsN׮kUxrtzri좸Ѳ绵ⷴյϷܢnڹΌծrvvygYψ[byv|uva۲ž߻ѯɊŲdzöܚ۵r|tvv{Vv׮rSrwvvvxwƽũ亱՚۴罴罴Ěב\ՓqvvwuNφfYzvvvv|cƄٻϵƱ߶̱vӓ_֯rvvvzdZ׫vTgyvvvwvʁϕ乵㹴ճ۵䭋Ԑ`ԓa|tvvvyR{υkPyvvvvzd¸ɉo_ϵyֳ˱z罴բsɘ]ԓe֓qvvvxmQׯwZ\zvvvwxzɼęУۄks龹~DZ³y뾔̮{֭o]ՕiӹȶsvvvvzWiσrKswvvv{dʺuaӿͦ~庴廴Έ|vvfշo^՘msǦ̀svvvxnRץue]zvvvvzeÎuoiեӓtnfܷ⺸xòrvotc֞pk՝qvvvvzZfwwVqwvvvzdɁƘyfΎ|rujŵⳍ|ȚmtȶSטqvvm|j}֨rfմuuvvvxlUӉuhbzvvvvyhɋ̫saոׯtlVԳՉnsrhգopSsstu\qȢahȺٱsvxdυypoֲudπrvvvvyYk֤qx\rwvvvzgÞxnn֛ՒptYթπtvqu՚so@wvwwbrΛnVϩ{twjʧ}qebC7=82e{nf֚rvvvvyg\}ԿwxhYzvvvv{aư|fЃɀhV{ԞyuwlӍve?zvwqrәwOզՈn\D5V]m|}bidFts]72Gպu}tvvvvuUpσsxUqwvvvzmƂ߱tbt۷roAәtvxh΄yXB}vxk{nՖ~{NճX16EO֞qxz]xZ>rvvvwsYwrܟqvxj]yvvvvvY˖v]SԻs|rZL*avrvxbշ]Hqf]|v~Wxmbqw{W~wqv}MdչuuvxgO΃xMw}G6svvvxYqrګrvvwWtvvvvvzouɞ}xXնq}QV4`nurvwd֭]gtgvw~Ttsh֨qw|Snsu|YMջuuvwrBԑvXuІsEOvvvy_iwyȺtvvz]fyvvvvv~YÂ˺ܝvnձtkddCjekurvwg֩uhykrxRqtnǫqw}SgԋyuzdLջvuvv{HբshY֛o|]qvvygbypvuvzaO{vvvvvwwp|c׮ְlvZyjxfXwrvx_֨vd{|gzRo{{qw|T֕ttzk[Ծvuvv}RfִsuIֱqzusvvxm\yrxuvwtGuvvvvvvz`Ãe֯hntvxhJ{sv{O֭x][}~UlrwzW`֝nsykiawuvv}Sky{Gxu|vvvvwrYwuw|uvv|P]|vvvvvvyeЌcڢ`rvxkJ́yuvyXְzVY}~Torwx[z֨mrynlXyuvv}R΄xVbςsxwvvvvvXuvt{uvvzdilzxvv~dۚu֠qvvvvv}Pm̀svv~RgԒUybbvx`vՠqvv}Wt΃sv}W~w}Ptvvvv~FR|vvvvvvvw{?cq}xv{`ܗlֱrvvvvvN_֙qvv~VݞW\Qq{V֥qvwweitxtkpubfЈsvvvvBM}vvvvvvvw{B`q{{btuytvvvvPxֳsvv}Yx\zYlwd~ܧqvzim|t|`exw^Օqvvvv}AK~vvvvvvvwyGǗdg}exkшrvvvv}U~tv~Xfٿd`mbqv}^{xtTxS`֡qvvvwzEK~vvvvvvvxuWxhn`方֟pvvvv|ZlԐrv~Yq|rYԮk\v\ھvwulzhpԭrvvvyoNO}vvvvvvvyquɊ|Sշsuvvw{d|k֤qw|]jԉloKrt{ðLdkgclRkRdոtvvv{`UW|vvvvvvv{b}tvvvyomjֲswyc{a}Zǀw[jT͜jpZcu]tqܻwuvvOb]|vvvvvvv}SǼǁ۟Քqvvvwv`ԾvxsrtWmćqst\skbG9W_TKEA>>>?EPXI&>3c`տyuvvF^jyvvvvvvvJֺɅձrvvvv|YyvbJqƕw\O2   %zxuvxuROtwvvvvvvwx\ʺ}svvvTgǡ\ZFzfVxˢeĂH&  K^ekotzNtvuv|`iH|vvvvvvv|bºË՛pvvv|[tg~{]pQbɼίοȻ 9N %((# 8+g[vuvKsJvvvvvvvBẺʂytvvyjsxZ[_WҿË'"054.!BXApxuxvLf_{vvvvvvzoJ巺ė֛pvvwwj; 4eˡWٸ|A%6::6* lmhzt}WmOtxvvvvvvHoиҕ{svv~\) 9эټ~u*8>?:0 R&czu~EvK}vvvvvv|jF~~צpvv}\— EaO3EF@5" :bfx{`FG[|vvvvvx}B]Ƀ΄rvyoů7   q;9JKF8$%}bdv~Q`txvvvvvOTmÞֶsuv}a& >s[? $=: %oD]ɂøm_ʍY|BNjЩ~R榌GrÝuˏcKιԥ}>ĈhÿºoNʁ]ürGbv{iMHiNJrQvmcclcZÜ}ǺļǝOvvvymSuŹļOrvvxsRȺÇcǻͺh^vvwwTz㸺ɀxùþºvTvvvzXq帺Ɇľl^vvvz]b为ęϱ̷cZvvvzbY帺뮸ۦձٿ_lvvvzgU帺簭뾣ᾏYOvvvvylT䷺džگbOovvvvyoRⷺȈŒÙdNjvvvvvxrR᷺Ɣ͡XNkvvvvvvxtR߷ÓѧgLZqvvvvvvvwvS~ܷΝˣnSQgvvvvvvvvvwxUu۷ĉɦdNThvvvvvvvvvvvvyXnַʁֶ̩kUOYlvvvvvvvvvvvvvvzZiηȆ߼ÕjQBH;jvvvvvvvvvvvvvvvv{_cǸʗԾݷP^izծuOAGR\`yZkvvvvvvvvvvvvvvvzdZyRWVQQPPW_bԽ踺g|vp9k|~zmwgmROZ`^]bsfbvvvvvvwwvvvvvvzjT҆nvvvvvvv~tƺּwyv>HYlvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv~f񑅤wС˲l볆XHGQpvvvvvvvvvvvvvvvvvvvvvvvvvvv|f̘}tz毯խv1„h\X[aluvvvvvvvvvvvvvvvvvvvvvykƥhg}ьo秊mY֪d^`frvvvvvvvvvvvvvvvvwwֶu#KВYY\nn} $幗wgX[Mgvvvvvvvvvvv}wϫj>^ö| p[vvvvvvvvvvviة7qxlzx}t ܜcvvvvvvvvvvv~dʣZ2ǏmzgВ}3W½~}kvvvvvvvvvvv{gAԒmX˞ն) —xpvvvvvvvvvvvyp^&jܕmdt࿬Ȭ0웎mkɔrtvvvvvvvvvvvwzCӐk}bοz І嫓Hbuvvvvvvvvvvvv~k| %XỌi[|εfcpRmĺXvvvvvvvvvvvvvdr'7zrr_UeNA  ľWvvvvvvvvvvvvv|eЕ #Vk˜~u.Jyi_lTvvvvvvvvvvvvvyjƥMPԹjޮR#_>-  JOvvvvvvvvvvvvvww칗UwqO8^ב_& Tvvvvvvvvvvvvvv}uXޘhfˮnp_tǴs zsvvvvvvvvvvvvvv~hΰrqD^젶ʦȿV 7|Yj^vvvvvvvvvvvvvv}eΟڕg5 *Nrڏǫw*% vvvvvvvvvvvvvvvvvvzi۠"PrVMq}ƽ(+ bvvvvvvvvvvvvvvvvvxsϬuʡѓ[WZm{kfgn椞V¢ )+! ?vvvvvvvvvvvvvvvvvw{}ѮؖWOd#@eݿ g,)))))))**$vvvvvvvvvvvvvvvvw{d/ kCCCCT)TCCCvkCCCCveCCCCCCwVCCCCCCDCCFCCCICCbvCCCCCCCCMCCpCCCCCCICCbwCCCдʾCCCHCCS/'.+))xCCCT)TCCCvkCCCIvvfCCCCCCCCCCCCCCCfCCCCCC}CCCYCCQCCCCECCCCCCCCC~CCCМʇYCCCCCCCCCCCC[+*))))CCCT)TCCCvkCCCvvvfCCCyCCCCCCCCC\CCCCCCCCCCCCCCCVCChCCnCCICCCCCCCCCТCCCCCCCCCCCCCCCk))))))YCCCT)TCCCvkCCCvvvfCCCeCCCCCCCCCDCCICCCCCCCCCCCCCCICCtHCC{ɍط|CCCCCCCCCmCCCCaϷCCCCCCd))))))hCCCT)TCCCvkCCCvvvfCCC~CCCCCCCCCCCCuCCCCCCrCCCUCCsCC~CCFCCCȋzCCCCCCrCCCCCCWŞCCCCCCU))))))CCCT)TCCCvkCCCvvveCCCCCCtCCCCCCZCCClCCCTCCO\CCGCCLcCCeCCjCCEzCCCSCCO]CCGTCCC꾵CCCKCCE)*))*)wRCCCT)TCCCvkCCCvvvfCCCCCCnDCCCCCCDCCCCCC{CCCCCCnCCCCCHCCCCCoCCC̺wCCCCCCnCCC\oCCC|CCCK))))GCCCCT)TCCCvkCCCvvvgCCCWCCCSCCCCCCCCCCCCCFCCIPCCDACCCC|CCCCCʿCCCFCCIPCCDRCCCCCCCüCCCC{|yCCCCCT)OCCCvhCCCvvv~jCCCCCCCўPCCCCCCCCtCCCCCCCCCCCYCCCCCCCCCzǮDCCC=CCCCPCCCCˣCCCCCCCCCCCCCCCCCCCCzǮDCCCyCCCh׿WCCCCCƣsCCCC]VCCDCCCS)zCCCvCCCvvw}vCCCCCCCCCCCCHCCCCCCsCCCCCCCCzCCCCCCCCtCCCCCCCCCCCpXCCCEf^bcfCCC`]MCCCCCCCCCCCCCCCCuCCCCCCCCCCCpICCCCCCCCCiCCCƢ`CCCCCCCCCDTCCCPUCCC{ꀩCCCvvwzCCCMCCCCCCTCCCCCCsCCCCCCCCjCCCxCCCCOCCCCCCCK,CCCiCCCw_oCCCCCCCCCCCCCCCCOCCCCCCCKXCCCCCCRCCCuǢECCCCCCECCCYQCCCRCCCvvxsCCCn캘vxCCCCCCsCCCCCCCCƼ鼼tYo8իuYoĦ\j~oHJHE¼|üvvyjݼvCCCӼ󺼼󺼼5䷺ƭ/ؙ))Eմ}vxvvv|eqECCU%*)rƾ踺mɻD))))))))))+*)))),${wvvvvvvwvv~eDCSCCCC&+*ļ긺vʼm)*))))))*)))))))))))*+ uwvvvvvvvvv}oTCCCCHisPCCCCC+6Ļvʼ2))))))))))))))))))+*nxvvvvvvvvwwcCCCCCCCCCCG&%»𺹺xʼ>))))))))))))))))*+%qxvvvvvvvvzhpFCCCCNdſﻹ˽V+++++++++++++++-*5qywwwwwwww~eĶnnnnnnnnnnnnnonkeccddc)yij«snnnnnnnnnnonkfccddagĪĬ|nnnnnnnnonkfccddcbxĩĤŮnnnnoonlieccddbcnĬ¢½ưooomkifdcdddbg|Įľ¼ƲskheddddcberįįƵjaabbehp}ıĵ̷ǽ̱º̖̺˾ƛ̽Ǿ̯ÿ̞ğĿ̘ȥ|}̽˥}ò̤z}nX[p~tnuwnklr~}TZVA^t\RS_|꺉õƆu쵆cUqjxlgjq{XNJDDNgcXõŁzkV}j~unlkoqwx{}~~}{cGDHTf|Vnŵƺnth~{wtrrpmooqrtryZ\հƵƍm~qLZ禝ƵƤmuzD[ǸǜƵÓqpmGk俤Ƶƾvl`Bx~Ƶƽ՗um~XI˒ړƵƷzkmunUOڎƵƫulld\V]晽yƵƥzlyY~SStƵƚoyeUlSKƵƗ{ljeUU[VJ茶ƵƝ^O`}NX|QXV֟ƵƝжH`MYlKZjPSfGͫ֕ƵƘÒԸu@zxuOPV[M\XVH_Ƶƞݶd¨{aAsnq|YPOZ{PO_uOXV|ƵƮtѻƳiUEoV|uoOSOadOOd_RRz|ƵwөamqPH\aSvVQSOlUQOj}PXAӔֈƵƉҚٷdS^QJKywM\ukOSPMqnJRPqaPUSƵƼ~ϕ׷ϼdKqSRQ?qsXM_suzQQZk{VCRPyPURkƵƚϗnɍТócKToOSUJ[P5PSSSSVGljƵp~RDp|wxiҼ͋xxΌexϦgĻVSSFn}NrlBkiMPqZPSSSTM:bqRGBUSSSSU]zƵWdNIpzdQMcL̶ܽqȇ~`}¿OZOSN[QekE`mOKlnOSSSSU?HySS=OSSSSUGzƵƝiUG]aVPRIYckoYnn]mkkmXiv;[iOSSLV\eJWsPH~|cRRSSSTK<\_RIDUSSSSTY␺ƵjvPC{}\wQK[=}yu]MPPHnNO;PPPRLhmmonDH~yPSTFt\U_OMzRGrZv`PSSSST?KnqOTAPSSSSUIܯƵYaMMk`eOPY>tzZQSORjPN/SSSSSjmikgM=rVQSK_sbWNFD/(,)$FU_dMHaj_rPSSSSUHAXSTI?USSSSVT΂ƵƝzWH`[iYIc=Tm}TRSKYaSH0VSSO`zq_i`S9zr~_MA1&=AKVairWEJF2P\O@'$2t~RrXQSSSSR#'18wmOTUBYS?+<=YYdePSSSSUBIVhOUHCUSSSSWPzƵƹldOHvNyqEeJ;czPSTFeXW>:XSTMnsrGhSV??m2?IESUDorOSSJJaQB|xN8MlSlRSSSSTK?PlvPSS=YSUKqstI|jPUI2]?DSRTERvOSSPAjR=^S7JMgYQSSSSS=PVSRUDGUSSSSYIܯƵrmQB_`PMfGEVsK;?O`kjFaK+&&*fSYBCYSUIwtsYklOTNApk`eqXQSMBxPSSU<}sRDIG%IaPSSSSU?IPqZPSOi}RM:M >SSSSSTIAS[ePSU?JTSSSSWHυƵc~QQ8TTkb?N2 bT~vPSTCoS8WQUSLXSXDrsgWpOSTV[}UkOSW5d|QRSU@RVR4|vH,,PSSSSP>SP|mOSTJAUSSSSSYU娛ƵƖyjSC<PWdO?6FSywPSTE}}@3O]SVSYBpvdUqOSVNwsS|uOSX;J~RRSTI<[T7gSX3&PSSST?OPdvPSSS=QSSSSSUNƵdWTA_|OXd`:=&DNRrwPSSG~vBIl\SSYBkwi]sOSWKVYk~PRWCARRSSP6dS>Q^P`19SSSUCJSUQSSUAHUSSSSSXG܈Ƶƛ~lSNpzQKcGG0JHLRkvPSSHsRIqbPTYCixlfvOSXJ]`\TRVHDzSRSSV?epPI>kN\VAOSSTHDUOuSRSUD8VSSSSSSShxƵƵn|WFwyKQX?UKTIBSduPSTBysSGsuJUYCfxqs~yOSWJcfQ~ZQULT]SRSSXJU|PR4yOVYRPSSTMAUP`TRSSP3RSSSSSSUCzƵu[G}ƑxI^M^QSTJoyUAEXXEcsvj~PSTSrtsMmfPUMeMTRSSXFd[T>G[PTSSSSSS>RSQ\VRSSUG,SSSSSSSTP\ƵƜxeE}phU;o`PSTNMh`PiURU?d{VAKVYDnk{b~PTP]RzNaiPUNgbzVQSSXFOfPK6eOSSSSSSU?OSSSSSSSSU52YSSSSSSUHtƵƜ{]JfleGUSSSSSTPYƵƳlZ`fbyUQSSSSSSPOSSUSTM;ZAOXJlZY}PYCc^PUSUIu\ZPSTP^`eOUMYgOSSSSSUCJTSSSSSSSUH7ƌDUTSSSSUMmƵƘjgctXPSSSSSULpnZPSSSP?v[BgPP\ZY|PXFodOSSWFlZPSUJjV}PYDYrOSSSSSUAIUSSSSSSSTP3ƜKOUSSSSVK}Ƶgk]s\PSSSSSVLRfOSSSVC_\AEKyft[yPVP^gOSSXDr[PSVEbZTGn^|QSSSSSU;IUSSSSSSSTS1ƺfGUUSSSXCƵhlWudOSSSSSWJ[|PSSSXJVaAKMhf[vQOjmjOSSYEb\PSXCVrPSS\URSSSSV9BUSSSSSSSSU.ƌKLUTSSXGƵmiQxnOSSSSSXDb~ZPSSYI_e@nDMi]nTFkmOSSXJY[PSXJ_SX@^YQSSSSY2;VSSSSSSSSV/ƹxGOXTSVEƵ{hK}zPSSSSSYAPiOSSXKwmBF9h[fVGhqOSSS\XYQTQbgkRG}[_PSSSSY/6XSSSSSSSSV3ƲvFOYVVFƵƍPQUQSSSSY@[|PSSXL[uCf?lVaS[vrOSUJyaVQVFUTSVdfOSSSSX/4YSSSSSSSSU6{ƺKIX[HƵƾ`K_PSSSSXBvhYQSXJ^G|EyU_FiuOSXFsTQYG^rT@UoOSSSSV04YSSSSSSSTRDpfLMCƵƶwhmOSSSSVCd|dPSXLpPtdqV^JwKASLSSRdql`Ig~ivPSSSUN89XSSSSSSSUOaߔƼa?ƵƱe}PRSSSVHt[qOSVPV_eT;QQswk6GK_xgjEL=be[FR}QSSSVC=>VSSSSSSSVEċƿƵƃXQSSSUNfUzPSUZsgcYeqWGeSAJ>jlJNPTRCmg}SRSSY9D@VSSSSSSSX;tƵƭofOSSSSSV_STPjZbKzh|Oy¾fgQYLmKbD2+SZPGA=;;;M8QTSSSSSSZ3X̄ƵƦjVPSSYN' 6Ɔϻ~u0I`kmdP/R#XURY1S5XSSSSSSVJ2qqƵƃ~qOSSXD Bdj^wyo[9:\\~TVD=7?VSSSSSTX.BㇾƵk[PSUNy4  |"uVfx`=%4%%}_ZySYBWQTSSSSSY8;RܰƵe}PRSXSy$;nR}<  2?UB?f}y_<D_>B ꗄWuUJ`>XSSSSSXC@3yҀƵfkOSWDľt/èЪd?Acm2+OgofN/k\cꕅYqUDqEVSSSSVG=F5zƵo\OTSsǡӻZF%+>EA02|䄇UnCkRRSSSSVIUH0ܯƵƤa}SSOqvQ ĿʬĻb&io@sCVSSX>;RU:cτƵ{huPXGܳ m텇SaaYNTSXF5SUDCtƵƫponPSi JؾCÂRKIVTY^xƵư[fujUےl~3g3D{ƵƛzONRs0ΜᑸƵƥtJݝ<[ќۯƵƷ޺qÇ[5ԠͅƵ|Ʋ̝u/֡xƵ`½g>nנ~ƵUýſi:Wmr`@3Oנ⑷ƵgżCSMGILF?ءܰƵrɿžř:SSSUM;ؤ˃ƵjʾƾCPSSTP:tئvƵW˷ƿ_BSSSS;bتxƵcŽýo=SSSU>[ج⌼ƵƆƾþeGSSSVBI٫ݨƵƌĿƪƱZASSSVD@ثʼnƵƜৰў̪ѷ}RNSSSUH<ث{ƵƠݩ൜صO:SSSSUK;ث{ƵƞѦX;NSSSSUN:uثƵƞŎܹ[>KSSSSSTP:lجƵƞØ|N;LSSSSSSTQ:pثڹƵƟ’Ŝ\>BPSSSSSSSSS;kثтƵƢΛcC;ISSSSSSSSSST<^ثyƵƣݽzX>=JSSSSSSSSSSSSU>U٫zƵƥֵ|dH=@LSSSSSSSSSSSSSSU?P٪⌺ƵƦ߼۸dMAI7KSSSSSSSSSSSSSSSSVBKڠ㧞ƵƧѪDEAABDPY_ƾƵƧҩIVSO1ixzuksdjQOY`^]ao\ESSSSSSSSSSSSSSUJKO(-MRSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSVcƵƹÃޖMHλji/B7>KSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSYUƵƑ[gނӽkB@l<46ALSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSXLƵƙ|oǜg볈WE??OSSSSSSSSSSSSSSSSSSSSSSSSSSSVHƵƾulrۧɢq#9ӊfTGFGLRSSSSSSSSSSSSSSSSSSSSSUKƵƞyc訂`}uȄiݟjf զ{VKHIPSSSSSSSSSSSSSSSSSStƵëg*NŇvSxUXhg|{ +ɩ嶑mYGJSSSSSSSSSSSSSSSSSXWƵƧէiC /GZa`XQPL*$!GLqֈ 1//////2!)SSSSSSSSSSSSSSSSSYLƵƃʺdDDB&$yf_  *OxW [-)0///////-SSSSSSSSSSSSSSSSSXHƵwYRKFa,; 6Y꼜 A/////////3GSSSSSSSSSSSSSSSSVJƵƇh)jb!XoTb͘ !*0///////1%3SSSSSSSSSSSSSSSSTPƵƮ8 i Do%@_{ʧџ! )z(3/////////(SSSSSSSSSSSSSSSSSViƵƖj4 caN//-PT~{sSSSSSSSSSSSUJƵpƷ \ut06@іÙ:///6GN#ThoSSSSSSSSSTOƵƽƫnۓ\p -ĩ֦Ȝέ\*//z%uSSSSSSSSSSUkƵf9999d999ƣ~ǒtg999WԼԫ׮ڄ&2/k999jX999cSSSSSSSSSSWWƵf9999Ne999ƕ給xkq~yx999IʕҞՏ'3/p999JV999HSSSSSSSSSSYOƵk99999h999ƻ٦|yy}9998/ ۲ѝԪ13/q999OT999gSSSSSSSSSSXHƵm99999;l999Ƭ999x'= ײԡλN//s999O0RSSSSSSSSSSSXGƵm999999Rl999Ԧՙ999)A Ԭ•t'2s999O/,2HDUSSSSSSSSSSSSVGƵm999b999l999۾999ػǺƦ3r999O/bSSgSTƵm999?99\=99;c999O/LUUUSMUUUJ99@cƽ\=9RwUUUwƵm999҆999Tl999\999999999RZ999~999K999999`999999999RΨi9999999999JM99999999v999OP999O[999J9999999cƿ99999999v999aƵm999O999l999<9999C`F99999999G999999N999=9999CaH99999f99999K4-42//q999O/R999SU999<}SSYM999uʈ999u999mƵm999x999^l999999pt999Q99G99z99;999999999x999ǁP999999999999U2/////999O/R999SU999SSSYM999op999Ɣ999mƵm999L999o999999999999M99`99k99?ע999999999999999999999999f//////]999O/R999SU999SSSYL999o\999Ƥ999mƵm999:99?t999999}99999999@99m?99tޠz999999999e9999X̳999}999]//////l999O/R999SU999SSSYN999ou999ƒ999mƵm999v999d{999999dp999L99k99w99=999ܝx999999j999999NŞ999~999M//////999O/R999SU999SSSYL999oɕ999h999mƵm999K999X999F99CL99=99C[99\99d99;؛x999J99ET99=K999齵}999A99;/0//0/zI999O/R999SU999SSSYM999oˠ999^Ʒ:999mƵm999:999999d999999e99999>99999gҘ999r999999g999Sg999u999P////L~9999O/R999SU999SSSYN999oͿM999ƸI9999mƵk999u999999<99@E99:99999u99999י999=99@G99:I99999999999t99999P/N999SQ999SSSXR999oƶ9999߶F99999mo999mӳ99999999Ƶh999J99999}9999pĩ{:99989999J9999ε~99999999999999999999sĩ|:999r999`ѸN99999k9999T{M99:999O/w999Sw999SSSX\999k{999999999>999rv999g99999999Ƶc99999999`99999999999iV999;d^eea999X]Ox9999999999999999n99999999999i@999999999`999|X999999999;R999JZ999v_999SSSVf999gD999999K999}u999^99999999ƵZ999e9999C9999999Aƽ1999aѸ999v^g9999999999999999F9999999AQ999999I999m;999999<999SV999L^999SSTPz999]跒nh999u999b99999999Ƶø縸yaKaƠ=ӤnPgxjBDDJZSSUJe999ƵƵƆ::ٜ//JָXSUSSSWIƨ^Ћ;99GƵm*0/»q˾W//////////10////2-VSSSSSSSTSSYJx:9J˺y9999Ƶn-22{y/0//////0////////////1&RSSSSSSSSSSXUƯK9999>alG99999jƵƞ1D}˿A//////////////////0/ OTSSSSSSSSSSo[9999999999n|,K'hȶi|:DUQz۶o怠᳆̺ܵ}Z]fV\ZgYkYZk\\VyaZcZjhZUiUoYgvYUvUu{Wk~XXX\ϔS~ΗWbVUȪRϴxRXUΎx(:;4'040oͶ:.4,obӠ{NS05rD2h\e"?"Ngiu,5OCTlksxzw⛤ }Z]bV\ZbYkY[g\[Ws`ZcZghZV}fUoYevXVqUu{Wh~XXzX\ďTyÑW`WTɾRƫxSYUňr'672%.3/oõ;5F;o_Ӡjm"06mF>n|,K'O}k|:DUQRܷp{w}ؑ 䰓s8mkil32 Jsy|trxv{tv˜s|zyz£twxwxm½ļ©vwyx{}uxvDŽxnļ~uxýtxuyuyꩼtxxvѭ™txtyxnҪ›txxw©wwxysvznļwxxw´|vxysq{q½Ŀ{wxwǺuxxuq{vqĺvxvĽuxx*ww}ygļÿtxxɯŌtxyy{yeǼɬǑsyvʏtxzwzl{Ľߣŝt{vŕuzuqzpvСltv鴬nxln{soţlnᄁjmg|vgގUK{mRD-"?TU^hM1EKPȑg>"] !"%)0' =I꿛N1 185 'ذwd8\f`F)!|Ŋ=dolX1 lz7A{veywe; YAjҭM>HQ9%498yɥyU4] !$'*1& .1oƠO1 185 'ج_Xwd8\f`F([bNJ=dolX1 z70]t~{veywe; Y;O\}M&2 exit 1 fi # プロパティファイルよりバージョン情報の読み込み VERSION=$(cat ../resources/version.properties | sed -n -E 's/implements_version=([0123456789.]+).*$/\1/p') echo "version=($VERSION)" DMG_NAME="CharacterManaJ_${VERSION}_with_jre" echo "DMG_NAME=${DMG_NAME}.dmg" WORK_DMG_NAME="${DMG_NAME}_with_jre_work" echo "WORK_DMG_NAME=${WORK_DMG_NAME}.dmg" VOLUME_NAME="CharacterManaJ Ver${VERSION}" if [ -f "$TMPDIR/${WORK_DMG_NAME}.dmg" ]; then rm -frv "$TMPDIR/${WORK_DMG_NAME}.dmg" fi hdiutil create -size 300m -fs HFS+ -volname "$VOLUME_NAME" -layout NONE -type UDIF "$TMPDIR/${WORK_DMG_NAME}.dmg" hdiutil attach "$TMPDIR/${WORK_DMG_NAME}.dmg" DIST_DIR="/Volumes/${VOLUME_NAME}" cp -v README_mac.txt "$DIST_DIR/README.txt" echo "copy: java8macWithJRE/CharacterManaJ.app $DIST_DIR/" ditto java8macWithJRE/CharacterManaJ.app "$DIST_DIR/CharacterManaJ.app" hdiutil detach "$DIST_DIR" if [ -f "$TMPDIR/${DMG_NAME}.dmg" ]; then rm -f "$TMPDIR/${DMG_NAME}.dmg" fi hdiutil convert "$TMPDIR/${WORK_DMG_NAME}.dmg" -format UDZO -imagekey zlib-level=9 -o "$TMPDIR/${DMG_NAME}.dmg" rm -fv "$TMPDIR/${WORK_DMG_NAME}.dmg" mv -fv "$TMPDIR/${DMG_NAME}.dmg" . echo "done" CharacterManaJ/dist/build_all.sh0000644000175000017500000000140312560206305017014 0ustar paulliupaulliu#!/bin/bash # -*- coding: utf-8 -*- echo "*****************************" echo "Mac用の配布物の一括作成を行います" echo "*****************************" ./build_appbundle6.sh if [ $? -ne 0 ]; then echo "error" >&2 exit 1 fi echo "*****************************" ./build_appbundle8.sh if [ $? -ne 0 ]; then echo "error" >&2 exit 1 fi echo "*****************************" ./build_appbundle8_jre.sh if [ $? -ne 0 ]; then echo "error" >&2 exit 1 fi echo "*****************************" ./build_dmg.sh if [ $? -ne 0 ]; then echo "error" >&2 exit 1 fi echo "*****************************" ./build_with_jre_dmg.sh if [ $? -ne 0 ]; then echo "error" >&2 exit 1 fi echo "*****************************" echo "all done" CharacterManaJ/dist/build_dmg.sh0000644000175000017500000000426112560206305017020 0ustar paulliupaulliu#! /bin/bash # -*- coding: utf-8 -*- if [ ! -d "java6mac" -o ! -d "java8mac" ]; then echo "java6mac/java8mac folder not found." >&2 exit 1 fi # バージョン情報をプロパティファイルより抜き出す VERSION=$(cat ../resources/version.properties | sed -n -E 's/implements_version=([0123456789.]+).*$/\1/p') echo "version=($VERSION)" # 生成するDMG名 DMG_NAME="CharacterManaJ_${VERSION}" echo "DMG_NAME=${DMG_NAME}.dmg" # 作成に一時使用するDMG名 WORK_DMG_NAME="${DMG_NAME}_work" echo "WORK_DMG_NAME=${WORK_DMG_NAME}.dmg" # ボリューム名 VOLUME_NAME="CharacterManaJ Ver${VERSION}" # 一時DMGの削除 if [ -f "$TMPDIR/${WORK_DMG_NAME}.dmg" ]; then rm -frv "$TMPDIR/${WORK_DMG_NAME}.dmg" fi # 一時DMGの作成 hdiutil create -size 50m -fs HFS+ -volname "$VOLUME_NAME" -layout NONE -type UDIF "$TMPDIR/${WORK_DMG_NAME}.dmg" # 一時DMGのマウント hdiutil attach "$TMPDIR/${WORK_DMG_NAME}.dmg" # マウント先 DIST_DIR="/Volumes/${VOLUME_NAME}" # 配布物のコピー cp -v README_mac.txt "$DIST_DIR/README.txt" cp -v CharacterManaJ.jar "$DIST_DIR/" # 配布物(java6)のコピー echo "copy: java6mac $DIST_DIR/java6mac" cp -rp java6mac "$DIST_DIR/java6mac" # 配布物(java8)のコピー echo "copy: java8mac/CharacterManaJ.app $DIST_DIR/" cp -rp java8mac/CharacterManaJ.app "$DIST_DIR/" # 配布先に移動 pushd "$DIST_DIR" echo "*create hardlink" # jarファイルをハードリンクにする ln -fv CharacterManaJ.jar java6mac/CharacterManaJ.app/Contents/Resources/Java/CharacterManaJ.jar ln -fv CharacterManaJ.jar CharacterManaJ.app/Contents/Java/CharacterManaJ.jar popd # 一時DMGのデタッチ hdiutil detach "$DIST_DIR" # 配布用DMGの削除 if [ -f "$TMPDIR/${DMG_NAME}.dmg" ]; then rm -f "$TMPDIR/${DMG_NAME}.dmg" fi # 一時DMGから圧縮単一ファイル型の配布用DMGに変換 # (convertはHFS+ディスク上で作業する必要がある) hdiutil convert "$TMPDIR/${WORK_DMG_NAME}.dmg" -format UDZO -imagekey zlib-level=9 -o "$TMPDIR/${DMG_NAME}.dmg" # 作業済み一時DMGの削除 rm -fv "$TMPDIR/${WORK_DMG_NAME}.dmg" # 配布用DMGのテンポラリからの移動 mv -fv "$TMPDIR/${DMG_NAME}.dmg" . echo "done" CharacterManaJ/dist/README_ja.txt0000644000175000017500000000474312560206305016713 0ustar paulliupaulliu[[リリースノート (キャラクターなんとかJ - 0.998)]] 2015/07/20 ホームページ http://osdn.jp/projects/charactermanaj/ [ver0.997からの変更点] 1. 頂き物の中国語リソース(zh)を本体にマージしました。 2. フォントの選択で表示できない文字がある場合は除外するようにしました。 ※ Ver0.997で優先フォントの指定をできるように修正したのですが、日本語フォントを優先的に使用させるため、メイリオ等があれば、それを選択するようにしていました。 しかし、頂いた中国語リソースを使う場合、メイリオがある場合には中国語を使用しつつメイリオで表示させることになるため、中国語文字が文字化けしてしまう問題がありました。 現在のロケールにあわせた文字列リソースを表示できないフォントは選択から除外するようにしました。 [インストール方法] charactermamaj.exeを好きなフォルダに置いて実行するだけです。 特にインストール作業は必要ありません。 ※ 本アプリケーションには画像データは含まれていません。 パーツセットを配布しているサイト等より、画像データを入手してください。 (詳しくはWikiに本家のキャラクターセットの利用方法について記載しております。) charactermanaj.l4j.iniファイルは起動時の設定を調整する場合に、exeと同じフォルダに置いて使います。 たとえば、-Xmx256mと指定した場合は、最大で256MBのメモリを使用します。 (このファイルがない場合はデフォルト値を用います。) [動作環境について] 以下の環境での想定を行っております。 Windows 7(32/64) Windows 8.1(32/64) 可能であればJava7またはJava8での利用をおすすめします。 JavaSE5で作成されているため、JavaSE5のデスクトップをサポートする環境であれば基本的には動作すると思います。 [使用・作成されるファイル等について] 使用・作成されるフォルダ等についてはWikiをご参照ください。 http://sourceforge.jp/projects/charactermanaj/wiki/FrontPage [ソースコードについて] ver0.995からソースコードはGitで管理しています。 https://sourceforge.jp/projects/charactermanaj/scm/git/CharacterManaJ/ CharacterManaJ/dist/build_appbundle6.sh0000644000175000017500000000134412560206305020310 0ustar paulliupaulliu#! /bin/bash # -*- coding: utf-8 -*- JARNAME=CharacterManaJ.jar APPDIR=java6mac/CharacterManaJ.app if [ ! -d "$APPDIR" ]; then echo "directory not found: $APPDIR" >&2 exit 1 fi # 実行可能jarのコピー cp -pv $JARNAME $APPDIR/Contents/Resources/java/${JARNAME} # Java起動スタブをコピー cp -apv "/System/Library/Frameworks/JavaVM.framework/Versions/Current/Resources/MacOS/JavaApplicationStub" $APPDIR/Contents/MacOS/ # バンドル属性をセット /usr/bin/setFile -a B $APPDIR # Java起動スタブに実行パーミッションを設定 chmod 755 $APPDIR/Contents/MacOS/JavaApplicationStub # リソースディレクトリのパーミッション設定 chmod -R 774 $APPDIR/Contents/Resources/ echo "done" CharacterManaJ/dist/README_ja_withJRE.txt0000644000175000017500000000552512560206305020306 0ustar paulliupaulliu[[リリースノート (キャラクターなんとかJ - 0.998 - JRE8同梱版)]] 2015/07/20 ホームページ http://osdn.jp/projects/charactermanaj/ [ver0.997からの変更点] 1. 頂き物の中国語リソース(zh)を本体にマージしました。 2. フォントの選択で表示できない文字がある場合は除外するようにしました。 ※ Ver0.997で優先フォントの指定をできるように修正したのですが、日本語フォントを優先的に使用させるため、メイリオ等があれば、それを選択するようにしていました。 しかし、頂いた中国語リソースを使う場合、メイリオがある場合には中国語を使用しつつメイリオで表示させることになるため、中国語文字が文字化けしてしまう問題がありました。 現在のロケールにあわせた文字列リソースを表示できないフォントは選択から除外するようにしました。 [インストール方法] CharacterManaJ_0.998_with_JRE8を好きなフォルダに展開して実行するだけです。 特にインストール作業は必要ありません。 OracleのJavaランタイムがアプリケーションに同梱されています。 マシンにJavaをインストールしていない場合でも、Javaのインストールなしに実行できます。 ※ 本アプリケーションには画像データは含まれていません。 パーツセットを配布しているサイト等より、画像データを入手してください。 (詳しくはWikiに本家のキャラクターセットの利用方法について記載しております。) charactermanaj.l4j.iniファイルは起動時の設定を調整する場合に、exeと同じフォルダに置いて使います。 たとえば、-Xmx256mと指定した場合は、最大で256MBのメモリを使用します。 (このファイルがない場合はデフォルト値を用います。) [動作環境について] 以下の環境での想定を行っております。 Windows 7(32/64) Windows 8.1(32/64) 同梱されているJREは、Java8(jdk1.8.0_51 32ビット版)となります。 より新しいJavaを利用したい場合は、jreフォルダの中身を新しいjreのものとまるごと差し替えてください。 また、jreフォルダを削除するとシステムにインストールされているJavaを利用するようになります。 [使用・作成されるファイル等について] 使用・作成されるフォルダ等についてはWikiをご参照ください。 http://sourceforge.jp/projects/charactermanaj/wiki/FrontPage [ソースコードについて] ver0.995からソースコードはGitで管理しています。 https://sourceforge.jp/projects/charactermanaj/scm/git/CharacterManaJ/CharacterManaJ/dist/appConfig.xml0000644000175000017500000000175612560206305017174 0ustar paulliupaulliu 7000 #ffc800 false #ff0000 #ffff 0.8 true #808080 #ffffff #ffffff #ff0000 csWindows31J 4096 4096 true true #ffff00 CharacterManaJ/dist/.gitignore0000644000175000017500000000027412560206305016526 0ustar paulliupaulliu/jre /java8macWithJRE /java6mac/CharacterManaJ.app/Contents/Resources/Java/CharacterManaJ.jar /java8mac/CharacterManaJ.app/Contents/Java/CharacterManaJ.jar *.dmg *.exe *.jar *.zip CharacterManaJ/dist/build_appbundle8_jre.xml0000644000175000017500000000234712560206305021344 0ustar paulliupaulliu CharacterManaJ(For Java7 On OSX) CharacterManaJ/dist/java8mac/0000755000175000017500000000000012560206305016225 5ustar paulliupaulliuCharacterManaJ/dist/java8mac/CharacterManaJ.app/0000755000175000017500000000000012560206305021607 5ustar paulliupaulliuCharacterManaJ/dist/java8mac/CharacterManaJ.app/Contents/0000755000175000017500000000000012560206305023404 5ustar paulliupaulliuCharacterManaJ/dist/java8mac/CharacterManaJ.app/Contents/PlugIns/0000755000175000017500000000000012560206305024765 5ustar paulliupaulliuCharacterManaJ/dist/java8mac/CharacterManaJ.app/Contents/PlugIns/.gitignore0000644000175000017500000000002212560206305026747 0ustar paulliupaulliu/jdk1.7.0_60.jdk CharacterManaJ/dist/java8mac/CharacterManaJ.app/Contents/Info.plist0000644000175000017500000000254412560206305025361 0ustar paulliupaulliu CFBundleDevelopmentRegion ja_JP CFBundleDisplayName CharacterManaJ CFBundleExecutable JavaAppLauncher CFBundleIconFile icon.icns CFBundleIdentifier charactermanaj.CharacterManaJ CFBundleInfoDictionaryVersion 6.0 CFBundleName CharacterManaJ CFBundlePackageType APPL CFBundleShortVersionString 1.0 CFBundleSignature ???? CFBundleVersion 1 JVMArguments JVMMainClassName charactermanaj.CharacterManaJ JVMOptions -splash:$APP_ROOT/Contents/Resources/splash.png -Xms96m -Xmx128m LSApplicationCategoryType public.app-category.graphics-design LSEnvironment LC_CTYPE UTF-8 NSHumanReadableCopyright seraphyware CharacterManaJ/dist/java8mac/CharacterManaJ.app/Contents/PkgInfo0000644000175000017500000000001012560206305024653 0ustar paulliupaulliuAPPL????CharacterManaJ/dist/java8mac/CharacterManaJ.app/Contents/MacOS/0000755000175000017500000000000012560206305024346 5ustar paulliupaulliuCharacterManaJ/dist/java8mac/CharacterManaJ.app/Contents/MacOS/JavaAppLauncher0000755000175000017500000004425412560206305027311 0ustar paulliupaulliu H__PAGEZEROx__TEXT00__text__TEXT__stubs__TEXTF*HF*__stub_helper__TEXT**__gcc_except_tab__TEXT+`+__cstring__TEXTx+x+__unwind_info__TEXT.h.__eh_frame__TEXTh/h/h__DATA00 __program_vars__DATA0(0__nl_symbol_ptr__DATA(0(0 __got__DATA8080__la_symbol_ptr__DATAP0`P0__objc_imageinfo__DATA00__objc_selrefs__DATA00__objc_msgrefs__DATA1P1__objc_classrefs__DATA1@1__cfstring__DATA22__common__DATA4 H__LINKEDIT@@"0@@ABxC$,F P E /usr/lib/dyld'O>ryUI$  * X/System/Library/Frameworks/Cocoa.framework/Versions/A/Cocoa 8/usr/lib/libSystem.B.dylib 8/usr/lib/libobjc.A.dylib h{/System/Library/Frameworks/CoreFoundation.framework/Versions/A/CoreFoundation `A,/System/Library/Frameworks/Foundation.framework/Versions/C/Foundation X/r-/System/Library/Frameworks/AppKit.framework/Versions/C/AppKit&pCjHHH}HuHHHH9uH UHHEHuH HEH HMH HHHEHHMHHeHEHEHHE|HhH#HXHXHHxH d HMH  HUH5HHPHPHEH HUHHHHHHHHpHpHMH HUHH@HH@HpHEHxHEHHMHHSH8HHMHHH8-HpHEHiHMHH E2dEHhHEHEHhE艅dHEHEHHMHHEE܋E܉EEH]HhHhЉdHhHhЉdd9 NHhHUHHpHHHEH}HHPHbHH\HHHHHHHHHHH HHHHHPHHHHHH]HHHHHH 9HH5+H=HHHHHHHHDžHHAH$HHHHHHHHHH HHHyHHHHH HHHHHH1HHHHHH HH5HHHHHHHHH EHHHHHHHHHHlj>HHHxHDžpHxHt3HxH HHHxHxHHpHpHHTHH>HH8HHHHpHpHHH 5H1H=}LVHhHH`HHhLL`dHXHH1H5LHHPHHPHHHHXLHH@H@HHH HHHHHHHHH5HH8HH8H0H0HHhHhHHHHHHHHH>H(H(HH H H 1H=ULH HHHH LLHHdH1H5HHHHHHHLHHHH(H H(HHXHH0HH0HH/HHH`H`H8H H8H5]HHHHHHHXH &H@H hH@HX@0L @HHHLHDDkHHHHPH HHH HHHH*HHHHHHHPH HPHX1HHHHHHHHH@H@HHHXHH`HH`HHjHHHHhH Hh1H=LHHHHHLLHHHX1H5HHHHHxHHLxHpHpHHpH 6HpHHHDžXHDž`HDžhHDžpHDžxHDžHDžHDžH@HxH-HxHHXL HhHH`LH`HXHHXLhHPHPHHHHhHH HDž(HhHH H9t#H@HH0HLj> H`H(HHH(HHH(HHHHHHH OO<t~HPHHHHXHH=E0H@HH8HH@H0HH0H8D/I/P H(HH9HHHxHHXL'H HHLHHHHL HHHHH,HDž HDžH HH=HH`@0H=uHH@HHHHH6 HHHH8HHH HH5H=HHHHHHH0H0HuAH1HHHHH HHHH0HHHjHH\H5HHHHHHHH(H(HuAHHHHHH HHHH(H0HHHHHHHHH(HHHH=HHHHH΃$$HHcHHIHHH)HHDž HHuHc H4 I HPHH=HHHzHHGHHcH  H8HH HHHΉ|HpHpH|HcH  HDž0HDž8HDž@HDžHHDžPHDžXHDž`HDžhH0HHHHpH0AL `HhHH`LHhHH`HXHXHHHH@HHHDžH@HHH9t#H0HH0HLjH8HHHHHHHHHHHH HHH;HPH HHHHHP HHHHH HHHA HHH։DH8H8HDHcH  HHH9H HHpH0Lb H0HH(LH(H HH L0HHHHHHDž HDž HhHH " HHHΉHHoHHcH  HDžHDžHDžHDž HDž(HDž0HDž8HDž@H(H H H HHHAL HHHLHHHHHHEHMHHHHEHEHHHMH9t#H(H(H(0HLjBHHMHHHEHHHEHH0HH8H H8HHHH H0H0 HHHHHH HH@HH@HH։lHH9HHcH  HEHMH9H4 H HHHL HHHLHHHHLHHHEHMH HDž HDžHHp$H@HH=H~H~H=H~H>F8F0F(F 11H׉HƋHHHDIHH@LHPH̋LM4EHHEHPHHEHE艅?EH KH HUH9щu2H]HHЉHH%%%% % %%%%%%%LAS%hh!h9hFhThahmh|hhhhx4^^&{3>CFNSExceptionJVMRuntimeContents/Home/jre/lib/jli/libjli.dylib/Library/Internet Plug-Ins/JavaAppletPlugin.plugin/Contents/Home/lib/jli/libjli.dylibJLI_LaunchJRELoadErrorJavaLaunchErrorJVMMainClassNameMainClassNameRequired/Contents/Java-Djava.class.path=%@/ClassesJavaDirectoryNotFound.jar:%@/%@-Djava.library.path=%@/Contents/MacOSJVMOptionsJVMArguments$APP_ROOTjavastringByReplacingOccurrencesOfString:withString:arrayappendFormat:hasSuffix:contentsOfDirectoryAtPath:error:defaultManagerstringWithFormat:stringByAppendingString:bundlePathraiseexceptionWithName:reason:userInfo:localizedStringForKey:value:table:fileSystemRepresentationstringByAppendingPathComponent:builtInPlugInsPathinfoDictionaryUTF8StringmainBundledrainrunModalsetMessageText:reasonsetAlertStyle:initautoreleaseallocobjectForKey:countByEnumeratingWithState:objects:count:count $Q@0L<G*L+PX+ @zPLRx 4$@w 4\ 44 4(4**********++....v.p.e.Z.K.8..------}-n-M-B-4-.-,.....+ +&,, ',7,H,^,m,,,,,%, , , "UE\A`BpPQ@___stack_chk_guardQrH@dyld_stub_binder@___objc_personality_v0@_objc_msgSend_fixup@_OBJC_CLASS_$_NSArray@@_OBJC_CLASS_$_NSException@_OBJC_EHTYPE_$_NSException@___CFConstantStringClassReference@_OBJC_CLASS_$_NSAutoreleasePool@_OBJC_CLASS_$_NSBundle@_OBJC_CLASS_$_NSFileManager@_OBJC_CLASS_$_NSMutableString@_OBJC_CLASS_$_NSString@_OBJC_CLASS_$_NSAlertrP@__Unwind_Resume_or_RethrowrX@___stack_chk_failr`@_chdirrh@_dlopenrp@_dlsymrx@_exitr@_strdupr@_NSHomeDirectoryr@_objc_begin_catchr@_objc_end_catchr@_objc_enumerationMutationr@_objc_msgSend_ startS_/mainXlaunch]NXArgbenvirontmh_execute_headerO_prognamey!!&cjvohhhh!@+X+&4.46(4BV 4_Pgms;Rm$>L`h"  #@"  GCC_except_table1GCC_except_table2_NXArgc_NXArgv___progname__mh_execute_header_environ_launch_mainstart_NSHomeDirectory_OBJC_CLASS_$_NSAlert_OBJC_CLASS_$_NSArray_OBJC_CLASS_$_NSAutoreleasePool_OBJC_CLASS_$_NSBundle_OBJC_CLASS_$_NSException_OBJC_CLASS_$_NSFileManager_OBJC_CLASS_$_NSMutableString_OBJC_CLASS_$_NSString_OBJC_EHTYPE_$_NSException__Unwind_Resume_or_Rethrow___CFConstantStringClassReference___objc_personality_v0___stack_chk_fail___stack_chk_guard_chdir_dlopen_dlsym_exit_objc_begin_catch_objc_end_catch_objc_enumerationMutation_objc_msgSend_objc_msgSend_fixup_strdupdyld_stub_binderCharacterManaJ/dist/java8mac/CharacterManaJ.app/Contents/Resources/0000755000175000017500000000000012560206305025356 5ustar paulliupaulliuCharacterManaJ/dist/java8mac/CharacterManaJ.app/Contents/Resources/icon.icns0000755000175000017500000001154712560206305027177 0ustar paulliupaulliuicnsgis32wyrzuvþuvzypwسÃvwnoēu赝uoӥo赤rtss{nĪrqpˬlݡltq7"CE=#.7<B_84E9o֠jm"0?E>n|,K'hȶi|:DUQz۶o怠᳆̺ܵ}Z]fV\ZgYkYZk\\VyaZcZjhZUiUoYgvYUvUu{Wk~XXX\ϔS~ΗWbVUȪRϴxRXUΎx(:;4'040oͶ:.4,obӠ{NS05rD2h\e"?"Ngiu,5OCTlksxzw⛤ }Z]bV\ZbYkY[g\[Ws`ZcZghZV}fUoYevXVqUu{Wh~XXzX\ďTyÑW`WTɾRƫxSYUňr'672%.3/oõ;5F;o_Ӡjm"06mF>n|,K'O}k|:DUQRܷp{w}ؑ 䰓s8mkil32 Jsy|trxv{tv˜s|zyz£twxwxm½ļ©vwyx{}uxvDŽxnļ~uxýtxuyuyꩼtxxvѭ™txtyxnҪ›txxw©wwxysvznļwxxw´|vxysq{q½Ŀ{wxwǺuxxuq{vqĺvxvĽuxx*ww}ygļÿtxxɯŌtxyy{yeǼɬǑsyvʏtxzwzl{Ľߣŝt{vŕuzuqzpvСltv鴬nxln{soţlnᄁjmg|vgގUK{mRD-"?TU^hM1EKPȑg>"] !"%)0' =I꿛N1 185 'ذwd8\f`F)!|Ŋ=dolX1 lz7A{veywe; YAjҭM>HQ9%498yɥyU4] !$'*1& .1oƠO1 185 'ج_Xwd8\f`F([bNJ=dolX1 z70]t~{veywe; Y;O\}M׋infObN~N>! ?F?aĆ=5`5_M'Tq. VJp8dasZHOLn}&wVQygE0  HPEaP@<14r?#{2u$jtbDA{6=Q<("qCA*Oy\V;噹sM^|vWGyz?W15s-_̗)UKuZ17ߟl;=..s7VgjHUO^gc)1&v!.K `m)m$``/]?[xF QT*d4o(/lșmSqens}nk~8X<R5 vz)Ӗ9R,bRPCRR%eKUbvؙn9BħJeRR~NցoEx pHYs   IDATxx\y`3(EUX,ˎ,;n&l/q6/uҞYr\e9bYT(Eb `zo %[Ypp{?o+G.Y.lh.$3ɬD7l6^3 p̌w.2uf %*ZJ|*QNI6PPe Cs\Dc^bYl*cp8NL&L'NS(KJ%JDgGlyEI>K(9JIY,EݡD<;Fܵ6A+N\զYTJM6Ӆ+^umedD"LtL&H$bX<OtB,~Fr'6׽ekNTR%J$:#&a4PZ> I|Q%%lT.+7>5P$OD u^Qj e΢1i Da:R8dɹp*;1rTV7̉Xd=#NVYѯ+rZ>P*UBV1|.0Dfѱ1^fmmZ-.-LioWjAkTrH4 'I,.)7O8/jp-h8I燦}Aޢo2)2r9CX_NJ}1#2x4'ghRiLT$oհS\YjԗT< jq0^Եy淺ii]<.t Fsl2kxr^Vk_.ƢLcN›ʄPPKA_,J(&iHl$`ˤ>V}ryC~3&ہH(6=f3f^fQjKŅ<(=ت"st:F(tRQkZj6K\|SU=^G*?l:1,6˴*UU8dGYBV1* !_LՖme.%0A. 9(LKT|wsB>j Ccse MppEI&?4D#BFU:ljjRa)I+ߓ#peҴ^mX.B (j 毅*)YTHpB%QR O yCO$c)QѪO |pm/g4+J #}De1e/`L ,1\C\t3Ͳĭ*g AkRxʇx=\t떵ެg/YG*?ږ&ɀ%S f=D D$| n2IDB^)lUֹƾ JRUJVkٟ7>BN\:a *>UTAĹwA4*OcĐwcdUԖ^r'f:BosOɬi---?P;w`44ohY^_''w{ey`{RJMu^T5olv׸50)K?WyՆFz:fYUEU-*eZ6Z/*=JhrmOZtfӂB@y6WKgAu!t"ɲ7T6h،7175LDܵd6s wvڶmڪ߼^{ŋn#Ȫu.W877_/W\fW4^~1LádmFV%1PԌwޖwd" įY!R3+6؅0R̊lA _X|^QQEHC!PR x1LyuuEfcZJDSY"4=Ó-;**fҊb3^~ݻw+_җ8pe7ډ;Kaju&k ߥBUNt Xj)MC*-p^Zq94\ +(J'3-+JƣԗED)PR(U5eQPfYj*gTFm}[[i0T%º:twbbog.O]bz S|/?dߎYMo Lޫ z+l0dy "-8¨ F@ v:֠V(̰S4I¡ϷdKEԉK78~T{-%8-U3fx< օFx<8ʼK7?!T/0JWOqkhXz mky=71a^ţ?}? 媽r(eAv_ZRfq)qVxG@=O}\VӄBqkP(-4I_X~_}G\-_sekpDX7_F^oqoݾ ɠꇽ} Ru.xF߸yX gƼS]cSLyEv8voLP>ӗu]:IwV2,ъ2Dw/}o5⏽ŕ?i8vwRcLSaӶZ<_\XSeZa¡~m1p:h:|7F='fq|*﬍UX +mYpFKA_RkZw7B؟]sOww/WW:G~qe=U+]c%^0tl|t˖-_ʗs8iwɠ!eaxWotxLp%dXݧեׂM"7}8:-jfq6v-l /js\QثoW g|dg>Yx7x#t{8}c3 ;>XI;7/kva@gMgY9\ÑrD~)_ԯxm])8췸B @,"*7fc;Pu*5KԬvCk,OCy^/x; %^¨RUֻ|~^Osfk!* s{:ܾ3csNtC I2a)msCPZ?_3‰Kp9<7?O@JmTͱTG|̚OZ$}A3=:˽ ##[>Tf}$!-Zav2s%`U. t+Dz5&M7lj'0>rg<4ۍԐYe'N{}ӖygfC5#p8>:SN:;g>G9DGUH > siQ&vzk](?0w~?FTKm\KC3rW-$geaɍ/YsGÀd"J(7^DA-3t-Mft |p|rm07! f~n=;TaiUZr$J]JXPY _SXYU VF*8xZTU^8YQYNiA%'*E78voqWuvuk};+ʴC<1Tj""!!yI4P\6]SF 97a_܊uKS}3IW+i9vTBӣMn82^d&Z IjԡtTXиҚDDPsvYgT'iNssuNxтW\_Q/>TdeR4iC9T&֪n=QJid{F'|q\dH@Fe}OR2۫g&0!z*a1! -h?rH  ٲ!GGhQ›\K;\לmwC(Nz>ݗK$IwZ ݲ{.z=AlVKrhPK|@+j\7nHNlv1X|¥:K[T?ˡIIDO4ḾQ>oͩܗ%E‚oGCOcOѽѺf*7"s˖&%0["76IeE S;=fjӧ+k,N i \Ha39֨'[[#z``#Ql{zճkp';p Fgzl;*/#*"M] F|5S^H_Y=X6/]8vAe*(:UNf4e%O*ЩR{GܚKيIcL -[ƻ|US$_6#Hh3#nZ*"’fp~R_2LM9v–#"XS էNؒcM"͕~H%*̳&D:X[ډ#?K} =5.wH$;q?7gv]!BOFaqs`5psSW, yp,ɱC Ql, cMrep4$$0y1gJNYIeοq "AYE^IOL}}G;;}3 j 2A*wرg_E۬/#d?xoCd E&;$Tɩy+2X/~M h~Sg[l#UPb;1_+843f?9JdðU!zd$*$uqמ]IJ(^f )7/bduu _nf;D|,XbtT@jNi'A7c3;LzPJXRO'KfK2t`&62;~z_\tvvj.Äv%"Rz@Z=LܾryJ: 3GyWd\ ^~ߤ;.ݓN =9HX80Hjl5|֫xef!S;~M.6_ɴFkNcQe F]sI YL%K6'wc:u:-9)4%6dqp)s;`)_h+60JMy%l\(pI1 ѽho2[,~Dro2!KF!BPf3yVM]h Jv IDAT`򤍞ĪܓCQ<Zulj>6XBHeF*Jǝ6ZoIAtcVV:bΡ#;v-#pɯySn˒# Tt8&bx\r&:gdC'F C3d&X3 v2R,'YOƅю0Wf=!PAԴKP-^>ft.% K.IK)mиv\:ߺ6U8/ ꓧ "b+J0,Lb'm"^#W#hhltzpϡ-B&X gѠ̈́kiW>U8.ߩ|5%?w y{kF[mr *N)c]Դ?s£r X1<8Ne14O Xێu.,e#" HzNfiQLNN>VP`fȐRPK,*H PDAhH&,iXub1BR$ɫ\(_!^ДeeUlfmz vH dRZkj,tj߼#B5xx7v7McOGjܼWxZ&&.? }<كkeɢd6sS,Hx09/Bډ7Bgw v S-ꏨL4c ̰OH,렺rWC,-+JBFf"[=c?[k/CXWGĺ>aK7x3`Q@}|`*J$ȼ+#Fx%n(QF.Pd.rB`ݞ1HƜsQLQwvx,}∹SYzǖbk!:Ow_׀xǐ|j+|,( VhyYBsxu-UժT C0s%Q`ɅQALL͈j1OK.QiC 5XZZ,m/-wۇІD> DoW\uS\}4[oG֝nܶ{ \*1;9b|31QA>|݀>_zy1苺k#B<#?AG'yi2@vYat TN c{;?~ݱgΊtP z=ˀ-a1sRR)[?ERt-hVg+hTzm{7WC~N!)`^fKDxDey\XftT LU: kfg2/U5[peF_^]bސ!:O+zC[-iΞ7OcmφH^B=N1ti<A/|S-c3|SaK@*|y=<( Fhךl3KԛwdshKd* `.2B-xB !d:0 PH]Ih ]sG/uU/!|EQQ&B:? ݦ9O _kg5S/B; ZY%o9]V%bLlb8`~{. mՆ)NH(Sdq1S&3i"Z ut<\Un07T'4.P9 8Lstj_ %X"1@4 P ?슉Xbm[L6hߴ|w*+-8ݐj0}gjIeOގc~΅*k]g/6hTdS~('C)7=" oڻ>Mpj9SS0A66v.CӮ vK$"E ^TeZ|M|$uِx_$<*1HGiҘAH7q?3L=%mZ E A`e{a (25. 6Ky)cc1`9d_u3 .bg~p~ڲ=]s{_|HX:x-65:ad[)K`$% VF O)B̷ 9% Yai$@"Rg:ǥۤbi2XJ ͝/\2 QoԢudLG& }f:[D68drr!MА*Jrse-aEFFFsICY o#bɐԅ;U)%}NZ"Ys/o432Kru&=}L꪿1D 6VjAq[b L@+k-$hXv-MpdJd`h&nMGP@/P2&*Ϥۂ $S!4BIjNuryB Bږ3 0rZ-A?Eq¾|9oXa'N:F2Sp `uv*8T|$%YC1+(-L(i ;dgg%Z{SΊb 5M|n~]c\1V$#{8y5{P["PXK"XoIR",@⸐W ת6Q'18M {4YODFLj-lU1R{'maQq$-Fүh<쮯~#'6 KHhFA|X&pF^ S" ^x˒3|I"唓LH/]F/}=Z^|k W>ץhb̑ c{{>)WJu9 DlcDc-%2 ɋAnqe$V"ɠdlr!WAb G%Bc܂2L ӑ}"mȪbX?@ iYq@R"\P&TWeiwF'ŪC݂gqY~y5!Z=߀|RǔZW%f&KXp 1,5;;3K /9>:Xy7zώ~O#}Q`[(gaCg0LpRj 0 wJh\LK|"&lD\gF!"ic$K5حft' G?F8BQv: #b#_^hTIOLQo2WN ԤLNV `p`wRZv Aegr7aGZK/vy2'j;RaԒpY`GaY|J=6yHRd12F)aB}#SѤbw#ARQauG m9XX:S\֍C/KMkI1q +*+Ӿ|WE ԀWsd`.|/ZtՎ֬?1ZG*,b*\^ʖBanګ nO_zvy䫯2.ϼ0z1, 3`x@Mx#`+ZN$6 lli4z87j2jJx"DMaT4fáHHԇC lȀ!f>?6 w !$jo !zɹ4; rQ uHd1 9il^! @x.i+-x~su4DO-{ia[tM $(ޘȶCHq_ c/?Zye]pur|4h-DwJVO0WV;2 fN|TQ܈&ZqyJU>&#,cP~qqkMBdҫR܌7(%}Rb~9$ᮻԡ T 0'qzB):QYӬS.YHYlwot $!92c?ܳrfec?› %U8Fy[>t/B= FcBZY2V҃?M߸Cnϕ IQPH0:93M 'B`U9« CL(EK&*"!|#s4y5'a 9BRsw&&^Tb]qgyC^e6M ׻^&t VOd6lglY[hS4LO}-3ŞR4}1gB) t.^(_$* R|1- ˉ7j2篿1Vp4J=ᙺJvêTp]lD7y(6uիz43H[o\T0 :/̖}wĠ TT CNx$F3/Anԇe G[ AP^fp_'R:]fJfpj!|Hւ9V{Zb8ݪRV)L%_4WH)p(߻u绐SqPefnWhL80mf77nyF~d0/T>#,ژJH*zRzʻbXC 2FebjBgǿގ-]\Yx9*O;?~Il3/KF1;_޼; QC? ,RWܷqإi(4Ņ_`@[6ϒvn2 Tq1-JZ!>rPH*?.I;r zJ!B}e4/QX h4UH̭BMTLƐ7#@'ue2+r0Bj.X~1iwUDzwt9M-8CnA[+eopp9矎 L^*M Dc F(bOnڿjsP[w'ΌY b(,QF lq{m=6vm _ u gJćSh!N[ΌYjD"PߤAz`eUg;64 `ӔiA{vi3Wiq_}m_Zjߴ%4dR>榅WK7y{bi^YE{[wȽ+s:9<uۉv[JD5y/aD 5?&!Q*EozN>y5Qn%Ju,{Dα8QTb: $\];1L@F9v,Dgr!o-[PbG`5:\E3ۚ7 jT.:@mRTbScls8: /l΍[kyhw oS, W,;5jF' IDATF{`JI(HY}~ﮏޣS[g'8_;:GxeW}s^4C !P,Q"vML ֺgwfcM g͠,KVHYAdFXEDWeN`ջ6 ɐP(ėfFsirj0U"¤CB$rQDdYYtnM:SEJ{.%/,)hc ˸:BUjdž©UwAܼoCյwF^-\GOt裷uW"7%iZiWexY3pA(ܻk'x$$FCYNd`|7xON@Xee"{2&CӜ/WNm*r,mo!U$ɒ>2$ T8zqHJ7&LΑiwpjO$$D$qE3mMF}8`-`/Ńd]7P*GFmi@ %a_8d*Չ55oD_ySKN_[vO`S/_8a ֫z8I+\YG~v&`̄}XAQbn|SlZ# l.9{ZG ;'xK@E*sd2ir3g,|W t!;T+w͍M04޶EӲdX, p˥`b  OfT*HM3;,.A$T08̫G$¡J; `łM7!z*kF;Wֹr 8H*~:K}'q%&9m.`&M:g4h !8>C  A!(loYS` +.xu޿| U~&F 0K%K۴?zC[yɴJS(a\·efԄvn±XHM&DR%Bݵ_}H4Y o\vEBrk:^Us;N~ X*B#oΛ3FRʮ| ŋy?xIEBRuyJk}o2Cino&2o1CdqQJe}UP-APQDqWCi4fi6k>wp e2T(O ͭ|9ooBmķ_:AYw4j|ϰ;k>c8kWQ .ZX~҂+QF713*c GDᶮT.}G4:وGDKݖӽnyŻ\mzˬrUD8HMAФVl <Ɨm['K+l9GEl,Z>#PV1|iLUY nWY=29MՈJFoXLZ\LR @X('E]׶Ppo$6xTEмyĀӪ UZ%l_6m̌Pߣ j"LTdvNI4:ϩCڬ5UV[݁h{d FMmYnuAFm#iM"+Ye{ Eh`')>ǎ{'y VirY.ŷYoi񁮘 7\5SPvֶ)<7R13D "kņT|4l׻/ {9+ z)bU\]T81Oڬ7z?'?~[?z_!["/p BJ?76QΠy zGhDP4 5{:$wmSkuUNgpxz!Yv1;::&''A]4hYYAf5&PH&"MMLseC9"8 ncM+j r`[΃'?zd)B ~8mO y!(A e&JI;]K](!7|_{wNAEѨK4:AlJCuaS'])@#jQG _2=?_n@r9Z5iuiFp8<00PWWW 2/DH7UmզEskUu.N9<\{Laeϲ<yeNɵN`,fˊKJ1x◽|&{Z&ʧ`M%@/|B7>G ~˚ #/~o?,/sߵaF}B>,>JR:*! |G#<95Íp5f٨54|PUd7 G立'$e J>s`Tj& Fa87FР f-$ʡKc,PR`9>ݶk8_x6t[IqQH]QZF_!wOC<߄o/ofb8y[`]N\G˹<>[c%pb S{ƀ0!.HE +(BeN =̏~?,)shTt؝UZpHFFF{7 D΁֍p Ts0{ +W..U-i!sP1Bl*l[;GG:Zfdj6?'>_~m=+4l|pwPCCEѣg;~?}0cس]Sc[Yrv/t̹P(,n./  Q.lXo -ՄBD8 V4V_멫z 􍣕oM A憇\W_dYM̀9oVw\$r~v^[ED| r'}cmݴ|M\rW7sv[/\,q\\ ⳿oͣ;ooU]^WVa\9ct*8 W9\]g&ooܳ+L75];}t\y~ɧ&&9GoT^A^yo[Rpb].Vl47/j}2D8M77TkV0zdZԎ0HtʂeY2E8陌S^?Mbr[͎>~<-:6Ӣ|^JVy"}~ Fɑ] ,!Y<r.ԕ?ٽm3ߘ捲2SdwZnj'aU"[;nS$~\"G*U tjaU'3|fŮvViUj;;90;z<ݮu֕mp+-i챱C0JL~?}Dl 8%=M<>o<ɔ'`&2}crzG`ItOU cG@@P,XVKܱGy]_S!vχ0UFWNM|es&o'i .T4jb{~Ž +bBn5*Yh 4|IE|7]M]:e`I{Iao8oP$5da7R' kGG|{ ctѨ8ߕQā475>];.M #~+TyHK/5M '8nR=w.imcޑxNUտ{q>al\8Xzu(|4J/ˮk}fb赧BJ2P:W۞FX/^?|Yp6`9LMf5_eϓ;- G{sh*RivZJک7B]֛Vߵr>ǵ)cڢ}xVECEB`& n1>at!^T3;:l oO߇i YK}BYGWΖ8@ߧ9x7Ô! i۫7l%c- &9;&Cϔ&%VSmS;=Ao,^O0u] ]^9#c";:ԹDVk8~Ua~p:%{:d|[8Ld>[>ubf9 .≳cp-S:Xeo(4rVG\YYsmsw4x>?ʐiƒi$8^3/|m@•; XOB89V)QM$/Mj.4# ϵml??W?,ix,eyBEfX8p<|WwF}Rvsuں==,HO |`d8=Ҵ~ɉxW%7ᩣKeMnd΍|'7y< J؛l{rRz-ϟORpJ!V0G0ʜ4IL{plL$Z`AI6uvj)) ;pY'fQdġw߂._SY?0xjWaO.ʺ_p:ҩ;WZahU8?#\]@]eRtBY_BT7-8^B}IEQEiW\pSbccT}py"ho}[x3DҞHH V!ɨ]yb jH[Lk)CYng?}nzk'/tStw⿋4jNhVdD-ؓ-N.2X8<4(4H|PuJ[R ,h .<=u7S frh"9NqˎN2_uXAVnXkp?߮}7߷7"9a$h(@"2쉢M#cR) @,Nve f?$xa_ T0>Ʉ7UWȅO:vZYڐl`r'iM+W[g' O?lkt\x dl :8DQ(p]O2﷒N@D$4a>ɋB:']i:y(;␇~ԉ-7pՋ[I˼-ja*8jG'VZc%뚘wu;Np4v:ZOOXQn~OՕ h+ZGFފmu+?'ޡc%7s\D9Qd4(Uy۪$d$@q0_WSg Ah 3'% =T)Rxho_4{iЊl)Ztu\|b[ot|6sb"1$k[\sm^xf߻Ѩ/瓩\2% [,KV \ψ!| Z"T;{ F0F'tRtS%b% !@1H>{{EJދ-[cjB |$^RHB yCfk%mӮgZ57lb4jvf{9sj|$W F\#!9 !eLON h4= a{ |s%SImpyNBEyXj,b}P<$A|FX aD=bI,L: Pf-9I7pH.|vάADxVmy˯BHbP v,0gpbHNeΌ[ \ `,)mOF@9/(+T8|!MPdHZQQ|8AΑe('Jr%ܭ p8Dt8_&xQIut0|p\_7=x / n(H(($G"I$C\[K4 ^)> hf́& vtRfHQo~n!ۗ)%UAd |pK>Y4E0=VLB~!Jb@|@'0ϣy8u. @#i4ukeBo;w p@tщw-YE'`( ο].TCL,O'}rDDVMjv&*sLVeU~96\NFvѣG8j5Y|>S,b )0%F@XI}Wjmt>_G.Iʆ3z}6HǕa j'z (!/4w&lY|п,+olA˾%u"6KEݠ&K8S@ yavF3&_ tH3 Tm IDAT<.m?"$DoH܅|~ٲ_/`RKv O8tj9͟nXP yߟm[&-Mn?.]h) .+'ojhtNd#0t& D1/4 ݂B0 ?|⥋c.w,$7xJ#^ ĝL&FxQӝ6Qg zr##±bv67qm,(qa{l}eWC* d1w΂ŕׁZE)boI5g0,lM 2H'PβK:/м {l/ p2LQr$ Dfq9ryf޾I4AȤ"!Tz^=|O>jy˳55 aV&Z&WP"i;Fo+C#'c" BG|~M.Px#kkk}y5k9x )4X/D= AqngSsj %\ 1`V^SC&͕+*H !Gc&n+f ׃~qu*kNJa;եmJ%0c29M唙6AW;"}Ͻ~O5kji5 H8No1؀(`uhÅ!&L~s^Co\k$Yl?&[mR3}; %YZ(~pPa*F2UY=j.x(9\`LBH+W"Boӧ8jkHاnY;#UJE٩53 ͒ŔA eᆌD,#V9W,>1ʻd,&H$ *+C,*i CbL4;`39 *sÆӢD_l۲bC(T1XpF'^0 c4/ =(UyaŚShnq(3Phpbq[F)*6-a?ģ0# w^_"GHWInn 8\^3)Nw .g O,da+hw#`M S*'CaPa ̨HbBd2Qy` -m|Aftx d: 8,;' ?޸a=`}ă/y~ҥR]ྤJR$ !ABoK>(y ra bIgIPP vJ8Lj%)DY2Y<4 f2EHЯ:[>S/t\S4m %$X[nu SvL%lK#Àdܩ[&m %I&"oDOK%0<5P i"hLO~!_Pĸ/`D.$^N4 ^M?AT:a=pQyAA< G֖K=с .0!1$|t3jgER|^ZoD(И1pEf.jZut5E cavY\>'I 8#p ld3d:7E[Ͷyㆽw]$S kP88,k5]2cLB%*AСHc>RXW[s֦c=>=moj"bnO QR pk F}? bPbs 0A3x,Jm_ 9|O>`>ѹB!aw .c1eW7 W{i%[q⫴]6No8\h~U?~m% "vh ".Wy}@(OɁPO_+uC͇CmzOrqK q4'!~ |S}}}bfAoP&ۥJ*]>Ckl G.SUy9YM'[urX(!4 h6 6] PGIdBv˾`UY5u9۳4ڧij,J|S-(RWU3= xC0Å HEcJ*DɌ T6n1w[FW"7D$|xjnor"f"+7sG~`͘.WX}#ݱW"!~HY.7kXJ}=OMW>A_~yT: :S[}(|MaQnI|U 05T:NW2 LYxg1ڈݩ[Lb֬X XY^S5ލ˗r ^;,nokmհJIP7AڞUm(bݙ~x3-Mg{v@ݎ\.("ˊ9" 1MV)oʲs8s/ơpD΂5 ԧO2OSV]wd֬؏~A#X(Jʊ@#<^AijH\\tJaFLu˖3wѾC˼"a NB,QΘ=XI Z'˖h3>\s =+qoT醩B]~hdX^i&NJxa#_8 EW hgg?~t+]WM?Yf]#L\bp(|LBgzukW f{@ !@9Aٺrr KQd 2uubwf3 Y,9C"7{`Ӈ3lU~E%! F -l|Pe05$' 3vL?_U3CmT`3;y;ۻoKiȮ{O>*RvXM[6"Jiyi%0{*<3' LaxE|$~<|ۺ o^ D*#!PͶ5e̤43w^#UIJKmi4|NL$sPҿ=_xviXlzUܑ74PCd/3%95]zk}P4Tתԙ:~$r 2u{h-x !;ofDq{Q?WLBg}YyꙹV\Q__aMS8(0@UVزdX,X!ʯBv:C?io2YKSNٲi.KDlCTˉ2[',糯\1^{~+7_ //W*S1}.Aұ)J@PN뷘o+gN7}H 0&!0L'xMND܊ʊcGOtM] 7[+t,]"&ҭ?믈 ʟܚP(qHºpJ29ήmvljv]`sͼJvpor@kd?ݽZ&[ףfpaP(~/uo}n.-&`r.j*p"=+.^Vœ83Nx.P4o|`{~h |Ji0^Ӯ;h?{ ;Z!0hZpK֛UkWר{Q`gW5lqY\tLQ^?L8AƽiyLӝ?50wb ESyNC\ux^]^;`zz8gNc_?V[cO}J 9ߴI =2|l% O2Jv+n{-|(!ZBdp ?2l߼]ΜK*/.+^b! Iu7, ^/qHxAAQ"Ii^T3pʢB:!ѹ<M'JbB0,JJY_W=.9,-;%rɈ4ӤrѴY8,.I߯;e=~suEƔprZtR(G"`ߠu\F6 -a(D縧wW3=8Y:\-VfJx"Ѫ7h M^V'VP4t_ {`bmk ټe"񌲢ESz9%P`] IDAT(UJYF@D, '\3M;}s1W40X<+|`[>k 7&3J ܁ T*z5 8cs/ZIxo *Lٶ+TOV]/I 79W(@kS-K/P+(麂ݺ7Yʌ\vZ1 p7kH[{z#@.wJ~?s9le]K ,SP`TvgΩ) MޫoqYuͷd+`Oc F< q7՝;Lº]{"sVUŃ{8[;XL֚+Vy ֽXR֬y`lH0Z":`S jEmaA4zߴm攪oNXV 8 L߸#PF5Qk3M8wtɎ˖HUFJ:}u}̂0.P%O`ꯜx j4暠)yء;|:[ 7-g ,lMFɁ<n˞xht> > aeҽ;xH-e25__bz- % p[$˫ -ho~4sکS yRZ|&@_K|f0D+$8'z>}~Jgۧ U; L=榆=cŚ1l`/5 hHCjq09tyz<~ӧm޾kGSs/J9u3Uj"&wm!D%w3H&JN3Te"L T6EVң "+Bd&`_ȧz۬ vzl-$WQYqf ޫ7XZtF;++*rVWVV@:ߴm'>߲h^ou0%!xH D%`0)** JX~El[M*3ޞƶx$Vf)I[,>e+Te/;_&ݑ0//l @Ę+c xoR=(l"C"PuDcirH^T|zEzzwm b8- a`?8EtD!BU"ZMTՈ ` ΔQGʪe-X2d4tW_qcm?t {/~0*K-[@ȅv&Ůā<:D ~IaWAa^aY NA^,km64 h]Œ1OrAKAXG\  0ED.R`s` %@x]R<|0@oZ8Un>ظNb YgP8fc  wS`P>𭃴ZUQJ\P. u] O ˔bª Ӱ1%DBX6x+ e{LDZfLSsFmnhR6h2á~r;:pĥTc<% zN#e6 rV_]rs<@$&ytNh8ڮь} Q">][`VxiC|w6ˀK ˜3; S]kkJVE$,,%%bXz|`Kÿ5KC?IڣC0BD2>JWrϦ7^lֿ_K"^\-ɀQZgryC]8+x&!ޙk2c.bp mnz /d9.I(O=ym9*ڐ 5Blo;z %S*:;l4W/*WKE! "ep,@{)&RA1 I^ (eX,jl;vnFӟ}ǹ|[ZTh2(vƆ 1P[dUhM9L& 1=.c^CM[N|C+`z:;?4ҪJHB֌I“-{2ɮsg.~5o2k\4X$F86#a)s)I 9Nod$k;j5sW{{јW3L4|0ڛPxh Yi wO=69@(Ȃ-P/ױB|9`sykV0\Fa6̱}VM#bi,1&SfMxA @Md!>qB-&ogjM8qV%|>Zg\ǥX4.&QBRHK(Zױ|_ΞqlR^- z=T " ~_0gAq]cyBÚ>|.vXXXdQDG3Y"zLnwn^<32K+]ř'M~o0$*fTIn)Τ538$Iegm~]Z )nϤK׸:IwMBBl%)TXK\M:R.D@'&s{템SZ_P$uVx,8`ןh8kVteԕ%YťB(! p]lc މB%O8D&PQ0Wh(?Yp,D{5,3Q)U_cQI6Jֱu1T{AcQg:|P%3̖-0d2,&鱸%X_SRT.ȁ@^Qfa8~Z>V >7h4%53fD0ZcQ+&3;G q'vp&y D7#d5}^-f#c tOP&Y=@.ǽv?y3'LƠ֜wGBg]?Ң2 ,S[F4k Î=G x\TUgi=>%0v)3rBg8#S$ƞLiK,BU[&!T(X3Npn6z`IM,} O-n_pA C3LRô~4b60IK|?&l,aAD!8_K t;:l&;7`FI%ĘΎ,jybI}w9rNn@=np_ʷvoM㢸Bxce˖@eDaW( BDC{茄&O*n#'9h(l vyG0a </9+H#g5 (3aL|h2%Z,l8,ļ ȱ.Oԃpnno,΀Ge)a_c,:*fU/oϾ{z%#+QHqS#q8}LE~#H˙ө˖n߼wʥYڑ4[2(v}!eDbV |Z|NmҐ\h)Ԋ o/E7_ nPΐEDN3* nM:pAY 6K*b :. HX"=ݺ%eIn^q&sf]%dy@Хsg/Q3A L_0j G~z/Ι[+ԙZ0~wLY% h*%ބp+QbdS7s-!0eX`to*_!*՞x[mل` xMNUwF!u~LOwԲGWg/O7}ퟨD[^|YB+$?Bu(Qex ! /}YIT@u\Ûr5h 'deaqj {vSา_ޱ@]-;zɬęUIv̚oR~Kb/,Zӧ4|%~V !jRbI+^w gߞvBbHuoygQ`%w5okyx' L"9z ,n;{A\HtJȱ!{G1ZɴeG + F 8ń:uٽgo}O[7jnE?Ĩ Psm[>OgVV/9^L'4n׽ol#ZZNn /~ݣ./2K95KуJ0@N+7`޲18mh GObbddpY[[:!,GfUL\ճc_>IK&G6K ]8l9oQ(}6I ȸPaH,eWRܚC>mӋS>og.Yu)B`@@]lp<"zE/s ! I$ve }p(5?> b b-֨*i~:ξkZÉc;zG] !od;_x`C]˕)>:D28/}><(AB=ÀxrBn;iJ WwW9Z͡oFmx4g jDag U_&jtp_?x7`gCFQc]sܟ/tefMz,$xMR?~6>m˦QWi=; RUNO?^H/ ,k| <_,u]"%]SԷĀgjidk6 Ά =yI_LӇ@Q=bgȨJ?-ZJ5Z)33f 弄JoT_%B|M| @OܦA0Cs__ȮX2Do* Rצ2msΊ&i5}s~"Ϯo-^,Ort?|gb'zx]ʇ\jL:9_hbE\Fz7%1#am߸1U}/H %֤)JF5޼y|[Dbڿhk\P"d O۩3h~+~aù_F:*kdIltȚ~g=nȝ5j-P ~pyDоuSټ}\Ӯ6#=rǝ7~sG&gTTB-J^7܎?L:}+oT@,q%24#Y?c#x0cësKpGO_[? Om<ʃ1_O5kŵ%JmtZ)UrN!ޅ[@㼸YIDATU Xw. G ~˗] VQtN 49CcП9f;H8 \P|E&c]#CyJ'L`*qؾeB]H:aR*VlGr3TrY6#۶,yR;a.}h4ԋ?mNwIB%"Ac(78TL+]K'BT^3U5UfNR?Ia n>؉BТGy5MV9'Q fP #1&gd,f_w 5rg.\t !N)\<ŕAdB0/ R !d||O}C#v)]VU`[?ű dD= {<1Y<NҒG P9j!gU@&y+ {֮AͰw(Eā! D̜R8{Ce{F^Uo=Fy}7Cp@oq=}[5 E<.RdJz cKNA;0tee,ui9~uYvJBP ү @Q9jR\Vg:zuF&L,UrVv7-HJO~hk={~ `i pҐM՞o*.YIp,#ŤI]X@<,0Hzby\A;jx]j&7p9aŨXt7*C`IDgw!'9]Ê5Wۚ1 yN(R1-?#[M` y{GA"50#Nñf[#Ll1))WN6}JJ1o~b>98g4ÌH]RƗJblU,G,K)DMz ,R(3 GPc[5{20[t9a|\zix]zs1#XBRHN_kݨy%#&HB4RL84UXP 6ͪ&ʍR| PBjN@LL `"wҍb0‰r2CR,}8HW8qL)*(L }PRWPOPwR-*K)eq)լu>m iI%8ETp*zɈj6tֶgXh#%+;H^zZ #"-4mz6@z vj;'[fI;!%"\v;1FڶP9:P;P -'mim 8 x`+m]`TzZe^opbyI)UUr!Ը IK::dT3'@YZ 6ܒ L,#C*AahPcuN*3?L N$[[CH@t|T fd. L:[Li=5FU)d/;7}响# *bޝɽ`!TFSVS풀[+4O39_h~N] jF:_m>e=6|7Ģٷ6t<Jv='a^dԂ5ZFkjjqe߳]O#nb6&d.%jvps2chܓp:LFnUbނh8t^-M]憮۩eW]+&ftd|^_ gI_/i`z]7 by3<AaeCK|~ taRn|]>gJSgLyIIͷU4 n{;LCb # `BZsh?5֧~FjKA47~o>Wپ6N< A)7${wMh65ڀDQEՕփǩIX*+`xOdP$HP&˗][>'B\k&Z 8a$'39v.O^59nyz/9D|Xxmf)]:A*,}5Hg"BC/~tC{x+F >$SAa lj,..Ѭ}8>"F|.0)ކryFm.^ysuTj LϔR,35 < /`%IT]} /#"- х~id >!6!?-?^JTnХB@sy)_-.b=*kHx屡T3^Xp)~BX$sE]? pRBG', !BfXHHS=e}i?5{f\{`Pn.T aۏ,)+Wl6?+wz$[SΛSd՜9J4U;Q&e0QӔCeAmAz{‡wϧ7px!_jr@.~y+I{6SW\-)&W3AlBLUO$|Q1Υ(ڠ|:= 4{bv( A}mœ?iY]G}KY v@+ +*4.A3J?1!M3?;tj;M43{;*) z5Cs?B!7a.rgϻef}WL(*eQIά!uUoCK_H|b~AVZŗKrIVUT#f^SJ59n7| (Su0H  :,gZ & eqji$D{]9p'w\(ZRX3:m^IQ]zG/.H*r@}!u_< I`Z_2'vg]``DV>".׫]dLJ[)\\]d1(jNP3WV(؀Nz$9SD1%w,P)N enr(k^ MJ@#O{I]͛ 8k71*Odꊳuφ gϖOyBNDb'ALN4ACx$WbI]z5EXp7F~ْ tcڎϯ͛q@W^v5K,`s;<+:%CVg}W:L=*|F?o[?OȞ5҅wރqώP0+>?~cҢH5RcϪpFmuD8T-]NQحlڸYfY)ٳCYA x)0C`ի\Ȯ(wB"{ۓ1fmujF*Vp\ ~$~3x^z4)laA=kQM30b:=6#j00kW>3glKKu8T+NcZN[RZ[0]eפZ'5ٔ(eBBT%Q<أJ7D{"mH||DV ]U 9=Gۂ!?,gzˁQ;0bjWCqv8XJP= _4k6ǪQ26mb8Y*׆'-x1ݣx͐:k#_uLin(Ғo{jwkBwkR|(}gGTTJE!ydn'X`FX'ZJjF'55bIaOl8iոhW{IaMĎ汦ZJ~NJG!9Mt ?'2P6++_ !HuQ+Ҡ*V' YF65·U28&SG^Tk=\ؗgKFȩ NbSȮAs^::*1HL-B%Bƨc>dcG8_l{3YS) [q "sŧpVX`qb\ NW-NjL CharacterManaJ CharacterManaJ/dist/build_appbundle8.sh0000644000175000017500000000167512560206305020321 0ustar paulliupaulliu#! /bin/bash # -*- coding: utf-8 -*- JARNAME=CharacterManaJ.jar APPDIR=java8mac/CharacterManaJ.app PlistBuddy=/usr/libexec/PlistBuddy if [ ! -d "$APPDIR" ]; then echo "directory not found: $APPDIR" >&2 exit 1 fi # 実行可能jarのコピー cp -pv $JARNAME $APPDIR/Contents/java/${JARNAME} # バンドル属性をセット /usr/bin/setFile -a B $APPDIR # Java起動スタブに実行パーミッションを設定 chmod 755 $APPDIR/Contents/MacOS/JavaAppLauncher # リソースディレクトリのパーミッション設定 chmod -R 774 $APPDIR/Contents/Resources/ # JVMRuntimeの設定を取得する. jdkname=$($PlistBuddy -c "Print JVMRuntime" $APPDIR/Contents/Info.plist 2>/dev/null) echo jdkname=$jdkname if [ ! -z "$jdkname" ]; then # JVMRuntimeなしに設定する $PlistBuddy -c "Delete :JVMRuntime" $APPDIR/Contents/Info.plist # JVMRuntimeを消す rm -frv $APPDIR/Contents/PlugIns/${jdkname} fi echo "done" CharacterManaJ/dist/java6mac/0000755000175000017500000000000012560206305016223 5ustar paulliupaulliuCharacterManaJ/dist/java6mac/CharacterManaJ.app/0000755000175000017500000000000012560206305021605 5ustar paulliupaulliuCharacterManaJ/dist/java6mac/CharacterManaJ.app/Contents/0000755000175000017500000000000012560206305023402 5ustar paulliupaulliuCharacterManaJ/dist/java6mac/CharacterManaJ.app/Contents/Info.plist0000644000175000017500000000262212560206305025354 0ustar paulliupaulliu CFBundleName CharacterManaJ CFBundleDisplayName CharacterManaJ CFBundleIdentifier charactermanaj.Main CFBundleVersion 1.0 CFBundleAllowMixedLocalizations true CFBundleExecutable JavaApplicationStub CFBundleDevelopmentRegion Japan CFBundlePackageType APPL CFBundleShortVersionString 1.0 CFBundleSignature ???? CFBundleInfoDictionaryVersion 6.0 CFBundleIconFile icon.icns Java Properties appbase.dir $APP_PACKAGE/../ VMOptions -Xmx128m -Xms96m MainClass charactermanaj.Main JVMVersion 1.5+ ClassPath $JAVAROOT/CharacterManaJ.jar SplashFile $APP_PACKAGE/Contents/Resources/splash.png CharacterManaJ/dist/java6mac/CharacterManaJ.app/Contents/PkgInfo0000644000175000017500000000001012560206305024651 0ustar paulliupaulliuAPPL????CharacterManaJ/dist/java6mac/CharacterManaJ.app/Contents/MacOS/0000755000175000017500000000000012560206305024344 5ustar paulliupaulliuCharacterManaJ/dist/java6mac/CharacterManaJ.app/Contents/MacOS/JavaApplicationStub0000755000175000017500000010410012560206305030171 0ustar paulliupaulliu8` P8@ T8__PAGEZERO__TEXT__text__TEXT __symbol_stub__TEXT~`~ __stub_helper__TEXT __cstring__TEXT__unwind_info__TEXTH__DATA __dyld__DATA __nl_symbol_ptr__DATA __la_symbol_ptr__DATA @__cfstring__DATAX X__common__DATAh D8__LINKEDIT0 ` ! PX!$P! /usr/lib/dyldD? |5}$ P /System/Library/PrivateFrameworks/JavaApplicationLauncher.framework/Versions/A/JavaApplicationLauncher `A,/System/Library/Frameworks/Foundation.framework/Versions/C/Foundation 4F/usr/lib/libgcc_s.1.dylib 4/usr/lib/libSystem.B.dylib h{/System/Library/Frameworks/CoreFoundation.framework/Versions/A/CoreFoundation& #j]$ML$ˉ\$USWVEh u 5l }=p u(t"PX/E؄t/D؊@uأt ;[u tС tAED$$^UED$$HEt$ \$ |$t$M $$h% % USWV ^tVlj<$t0Ílt$$xt\$} |$}<$E $U) ^_[]Ë(D$$`USWV^ $ǍL$<$D$ D$É<$tv^D$<$D$<$u0T$bT$$4$3ut$$i1D$4$o^_[]ËD$$% % % %$ %( %, %0 %4 %8 %< %@ %D %H %L %P %T h h h h$ h( h, h0 h4 h8 h< vh@ jhD ^hH RhL FhP :hT .__dyld_make_delayed_module_initializer_calls__dyld_mod_term_funcs/..ERROR: Could not change the current working directory to %s ERRORCFURLGetFileSystemRepresentation() failed on main bundle CFBundleCopyBundleURL() failed on main bundle JavaCFBundleGetMainBundle() failed  44 4 (4@LXdp|) <BEa h  l  t 4 p =To&1HPW^dmtX   _NXArgc_NXArgv___progname__mh_execute_header_environ_CFBundleCopyBundleURL_CFBundleGetInfoDictionary_CFBundleGetMainBundle_CFDictionaryGetValue_CFRelease_CFURLGetFileSystemRepresentation_MRJApplicationMainArgs___CFConstantStringClassReference___keymgr_dwarf2_register_sections___stderrp__cthread_init_routine_atexit_chdir_errno_exit_fprintf_fputs_launchJavaApplication_mach_init_routine_perror_strlcatradr://5614542 $& v0# com.apple.JavaApplicationStub[J\dV]}ʼXwwmtB%Bx"*ȋ{3_zgI@p+\O\\1IUg P <com.apple.JavaApplicationStub 0 *H 01 0 +0 *H  000  *H 0b1 0 UUS10U  Apple Inc.1&0$U Apple Certification Authority10U Apple Root CA0 070214211919Z 150214211919Z01 0 UUS10U  Apple Inc.1&0$U Apple Certification Authority1301U*Apple Code Signing Certification Authority0"0  *H 0 q k% ]kYZ9#59H!' d8k P[]Tes/b8drm|iGa&,$j?̬)LIyuv LSxv2ʑ9Pjf K;-Ɉ-i¤*a/ۇ!L-rn?̣@Q-YN]e3'g$LJy]i>H$m0ȧ#8_a00U0U% 0 +0U00UiwBNVBQ 0U#0+iGv k.@GM^06U/0-0+)'%http://www.apple.com/appleca/root.crl0  *H cLrѦKX~̫м'0=ƭaV }lٝRb$0ыbj?[DޫۭK?QKK:IaNՀzľ[Wb*Fר7?8 8B]㫚V>C '"W:vG0<9sb̡G˟!+oW6f㍙D4EY X')7dCf)d^+-?BpK"hͤo000  *H 0b1 0 UUS10U  Apple Inc.1&0$U Apple Certification Authority10U Apple Root CA0 060425214036Z 350209214036Z0b1 0 UUS10U  Apple Inc.1&0$U Apple Certification Authority10U Apple Root CA0"0  *H 0 䑩 GP^y-6WLUKl"0>P Af$kУ*z G[73Mir]_%UM] d5#KYPXPg ˬ, op?0C=+I(ε^=: !.t< bqGSU/ApLE~LkPAtb A30XZ2hesg^eIv3ew-z0v0U0U00U+iGv k.@GM^0U#0+iGv k.@GM^0U 00 *Hcd00*+https://www.apple.com/appleca/0+0Reliance on this certificate by any party assumes acceptance of the then applicable standard terms and conditions of use, certificate policy and certification practice statements.0  *H \6L-x팛wvw0O=G7@,ԱؾsdyO4آ>xk}9S 8ıO k+Y |@Vtӷ#;Go$ѷpE'mx~"5%kԢ$#s`[ /DH`8=&g 3j /Sj[dc3w:,V!ںsO6U٧2Bq~RB$*M^cKP 7uu!000  *H 01 0 UUS10U  Apple Inc.1&0$U Apple Certification Authority1301U*Apple Code Signing Certification Authority0 070223220256Z 150114220256Z0V1 0 UUS10U  Apple Inc.10U Apple Software10USoftware Signing0"0  *H 0 Mza/]3NE.·;sD`ϬA91Mٸ=Acz&ҵ/&!InvR")-||f]ny 2;d:*KEA3%|k=bK_; {|}}S7,mn%6 s=ҬSEKǿ눗|{a(tEw']ruS,V3[SR@Uῢ00U0 U00U% 0 +0U sS!p386CL0U#0iwBNVBQ 0U 00 *Hcd00)+http://www.apple.com/appleca/0+0Reliance on this certificate by any party assumes acceptance of the then applicable standard terms and conditions of use, certificate policy and certification practice statements.0=U604020.,http://www.apple.com/appleca/codesigning.crl0  *H +\_5$r*.;)ẃ,M|j@Zl*YEzI$ݴug7SF;KkW4ܗ/Jw.N| /T!DיWP) ; GIuC"P!Bə e@)狐q~uLȤ_y99+!bpQ|z#r|U=mfs a;~teQVyɁ10001 0 UUS10U  Apple Inc.1&0$U Apple Certification Authority1301U*Apple Code Signing Certification Authority0 +0  *H nvt޺T^ʽH=O"|z|[k5~˽HY϶7ũKGQ;/2n͈*Nj]o2ӃDKFQa-UO xrv%PϺܥT.OOq "QothU9IDYɕw{Pֵ%H$m0ȧ#8_a00U0U% 0 +0U00UiwBNVBQ 0U#0+iGv k.@GM^06U/0-0+)'%http://www.apple.com/appleca/root.crl0  *H cLrѦKX~̫м'0=ƭaV }lٝRb$0ыbj?[DޫۭK?QKK:IaNՀzľ[Wb*Fר7?8 8B]㫚V>C '"W:vG0<9sb̡G˟!+oW6f㍙D4EY X')7dCf)d^+-?BpK"hͤo000  *H 0b1 0 UUS10U  Apple Inc.1&0$U Apple Certification Authority10U Apple Root CA0 060425214036Z 350209214036Z0b1 0 UUS10U  Apple Inc.1&0$U Apple Certification Authority10U Apple Root CA0"0  *H 0 䑩 GP^y-6WLUKl"0>P Af$kУ*z G[73Mir]_%UM] d5#KYPXPg ˬ, op?0C=+I(ε^=: !.t< bqGSU/ApLE~LkPAtb A30XZ2hesg^eIv3ew-z0v0U0U00U+iGv k.@GM^0U#0+iGv k.@GM^0U 00 *Hcd00*+https://www.apple.com/appleca/0+0Reliance on this certificate by any party assumes acceptance of the then applicable standard terms and conditions of use, certificate policy and certification practice statements.0  *H \6L-x팛wvw0O=G7@,ԱؾsdyO4آ>xk}9S 8ıO k+Y |@Vtӷ#;Go$ѷpE'mx~"5%kԢ$#s`[ /DH`8=&g 3j /Sj[dc3w:,V!ںsO6U٧2Bq~RB$*M^cKP 7uu!000  *H 01 0 UUS10U  Apple Inc.1&0$U Apple Certification Authority1301U*Apple Code Signing Certification Authority0 070223220256Z 150114220256Z0V1 0 UUS10U  Apple Inc.10U Apple Software10USoftware Signing0"0  *H 0 Mza/]3NE.·;sD`ϬA91Mٸ=Acz&ҵ/&!InvR")-||f]ny 2;d:*KEA3%|k=bK_; {|}}S7,mn%6 s=ҬSEKǿ눗|{a(tEw']ruS,V3[SR@Uῢ00U0 U00U% 0 +0U sS!p386CL0U#0iwBNVBQ 0U 00 *Hcd00)+http://www.apple.com/appleca/0+0Reliance on this certificate by any party assumes acceptance of the then applicable standard terms and conditions of use, certificate policy and certification practice statements.0=U604020.,http://www.apple.com/appleca/codesigning.crl0  *H +\_5$r*.;)ẃ,M|j@Zl*YEzI$ݴug7SF;KkW4ܗ/Jw.N| /T!DיWP) ; GIuC"P!Bə e@)狐q~uLȤ_y99+!bpQ|z#r|U=mfs a;~teQVyɁ10001 0 UUS10U  Apple Inc.1&0$U Apple Certification Authority1301U*Apple Code Signing Certification Authority0 +0  *H )vucS1so:eRσtsO:,vФ-L(lL3'=3Y'D<$`Y-j hx"YWc^Ef47y`Xrnmu$t ^ZyLfA_D}4o1-zA((a6\7YPd'J Qǜi@"U&[Q[}+wWjKi"Yp~'x.eL CharacterManaJ/dist/java6mac/CharacterManaJ.app/Contents/Resources/0000755000175000017500000000000012560206305025354 5ustar paulliupaulliuCharacterManaJ/dist/java6mac/CharacterManaJ.app/Contents/Resources/icon.icns0000755000175000017500000001154712560206305027175 0ustar paulliupaulliuicnsgis32wyrzuvþuvzypwسÃvwnoēu赝uoӥo赤rtss{nĪrqpˬlݡltq7"CE=#.7<B_84E9o֠jm"0?E>n|,K'hȶi|:DUQz۶o怠᳆̺ܵ}Z]fV\ZgYkYZk\\VyaZcZjhZUiUoYgvYUvUu{Wk~XXX\ϔS~ΗWbVUȪRϴxRXUΎx(:;4'040oͶ:.4,obӠ{NS05rD2h\e"?"Ngiu,5OCTlksxzw⛤ }Z]bV\ZbYkY[g\[Ws`ZcZghZV}fUoYevXVqUu{Wh~XXzX\ďTyÑW`WTɾRƫxSYUňr'672%.3/oõ;5F;o_Ӡjm"06mF>n|,K'O}k|:DUQRܷp{w}ؑ 䰓s8mkil32 Jsy|trxv{tv˜s|zyz£twxwxm½ļ©vwyx{}uxvDŽxnļ~uxýtxuyuyꩼtxxvѭ™txtyxnҪ›txxw©wwxysvznļwxxw´|vxysq{q½Ŀ{wxwǺuxxuq{vqĺvxvĽuxx*ww}ygļÿtxxɯŌtxyy{yeǼɬǑsyvʏtxzwzl{Ľߣŝt{vŕuzuqzpvСltv鴬nxln{soţlnᄁjmg|vgގUK{mRD-"?TU^hM1EKPȑg>"] !"%)0' =I꿛N1 185 'ذwd8\f`F)!|Ŋ=dolX1 lz7A{veywe; YAjҭM>HQ9%498yɥyU4] !$'*1& .1oƠO1 185 'ج_Xwd8\f`F([bNJ=dolX1 z70]t~{veywe; Y;O\}M׋infObN~N>! ?F?aĆ=5`5_M'Tq. VJp8dasZHOLn}&wVQygE0  HPEaP@<14r?#{2u$jtbDA{6=Q<("qCA*Oy\V;噹sM^|vWGyz?W15s-_̗)UKuZ17ߟl;=..s7VgjHUO^gc)1&v!.K `m)m$``/]?[xF QT*d4o(/lșmSqens}nk~8X<R5 vz)Ӗ9R,bRPCRR%eKUbvؙn9BħJeRR~NցoEx pHYs   IDATxx\y`3(EUX,ˎ,;n&l/q6/uҞYr\e9bYT(Eb `zo %[Ypp{?o+G.Y.lh.$3ɬD7l6^3 p̌w.2uf %*ZJ|*QNI6PPe Cs\Dc^bYl*cp8NL&L'NS(KJ%JDgGlyEI>K(9JIY,EݡD<;Fܵ6A+N\զYTJM6Ӆ+^umedD"LtL&H$bX<OtB,~Fr'6׽ekNTR%J$:#&a4PZ> I|Q%%lT.+7>5P$OD u^Qj e΢1i Da:R8dɹp*;1rTV7̉Xd=#NVYѯ+rZ>P*UBV1|.0Dfѱ1^fmmZ-.-LioWjAkTrH4 'I,.)7O8/jp-h8I燦}Aޢo2)2r9CX_NJ}1#2x4'ghRiLT$oհS\YjԗT< jq0^Եy淺ii]<.t Fsl2kxr^Vk_.ƢLcN›ʄPPKA_,J(&iHl$`ˤ>V}ryC~3&ہH(6=f3f^fQjKŅ<(=ت"st:F(tRQkZj6K\|SU=^G*?l:1,6˴*UU8dGYBV1* !_LՖme.%0A. 9(LKT|wsB>j Ccse MppEI&?4D#BFU:ljjRa)I+ߓ#peҴ^mX.B (j 毅*)YTHpB%QR O yCO$c)QѪO |pm/g4+J #}De1e/`L ,1\C\t3Ͳĭ*g AkRxʇx=\t떵ެg/YG*?ږ&ɀ%S f=D D$| n2IDB^)lUֹƾ JRUJVkٟ7>BN\:a *>UTAĹwA4*OcĐwcdUԖ^r'f:BosOɬi---?P;w`44ohY^_''w{ey`{RJMu^T5olv׸50)K?WyՆFz:fYUEU-*eZ6Z/*=JhrmOZtfӂB@y6WKgAu!t"ɲ7T6h،7175LDܵd6s wvڶmڪ߼^{ŋn#Ȫu.W877_/W\fW4^~1LádmFV%1PԌwޖwd" įY!R3+6؅0R̊lA _X|^QQEHC!PR x1LyuuEfcZJDSY"4=Ó-;**fҊb3^~ݻw+_җ8pe7ډ;Kaju&k ߥBUNt Xj)MC*-p^Zq94\ +(J'3-+JƣԗED)PR(U5eQPfYj*gTFm}[[i0T%º:twbbog.O]bz S|/?dߎYMo Lޫ z+l0dy "-8¨ F@ v:֠V(̰S4I¡ϷdKEԉK78~T{-%8-U3fx< օFx<8ʼK7?!T/0JWOqkhXz mky=71a^ţ?}? 媽r(eAv_ZRfq)qVxG@=O}\VӄBqkP(-4I_X~_}G\-_sekpDX7_F^oqoݾ ɠꇽ} Ru.xF߸yX gƼS]cSLyEv8voLP>ӗu]:IwV2,ъ2Dw/}o5⏽ŕ?i8vwRcLSaӶZ<_\XSeZa¡~m1p:h:|7F='fq|*﬍UX +mYpFKA_RkZw7B؟]sOww/WW:G~qe=U+]c%^0tl|t˖-_ʗs8iwɠ!eaxWotxLp%dXݧեׂM"7}8:-jfq6v-l /js\QثoW g|dg>Yx7x#t{8}c3 ;>XI;7/kva@gMgY9\ÑrD~)_ԯxm])8췸B @,"*7fc;Pu*5KԬvCk,OCy^/x; %^¨RUֻ|~^Osfk!* s{:ܾ3csNtC I2a)msCPZ?_3‰Kp9<7?O@JmTͱTG|̚OZ$}A3=:˽ ##[>Tf}$!-Zav2s%`U. t+Dz5&M7lj'0>rg<4ۍԐYe'N{}ӖygfC5#p8>:SN:;g>G9DGUH > siQ&vzk](?0w~?FTKm\KC3rW-$geaɍ/YsGÀd"J(7^DA-3t-Mft |p|rm07! f~n=;TaiUZr$J]JXPY _SXYU VF*8xZTU^8YQYNiA%'*E78voqWuvuk};+ʴC<1Tj""!!yI4P\6]SF 97a_܊uKS}3IW+i9vTBӣMn82^d&Z IjԡtTXиҚDDPsvYgT'iNssuNxтW\_Q/>TdeR4iC9T&֪n=QJid{F'|q\dH@Fe}OR2۫g&0!z*a1! -h?rH  ٲ!GGhQ›\K;\לmwC(Nz>ݗK$IwZ ݲ{.z=AlVKrhPK|@+j\7nHNlv1X|¥:K[T?ˡIIDO4ḾQ>oͩܗ%E‚oGCOcOѽѺf*7"s˖&%0["76IeE S;=fjӧ+k,N i \Ha39֨'[[#z``#Ql{zճkp';p Fgzl;*/#*"M] F|5S^H_Y=X6/]8vAe*(:UNf4e%O*ЩR{GܚKيIcL -[ƻ|US$_6#Hh3#nZ*"’fp~R_2LM9v–#"XS էNؒcM"͕~H%*̳&D:X[ډ#?K} =5.wH$;q?7gv]!BOFaqs`5psSW, yp,ɱC Ql, cMrep4$$0y1gJNYIeοq "AYE^IOL}}G;;}3 j 2A*wرg_E۬/#d?xoCd E&;$Tɩy+2X/~M h~Sg[l#UPb;1_+843f?9JdðU!zd$*$uqמ]IJ(^f )7/bduu _nf;D|,XbtT@jNi'A7c3;LzPJXRO'KfK2t`&62;~z_\tvvj.Äv%"Rz@Z=LܾryJ: 3GyWd\ ^~ߤ;.ݓN =9HX80Hjl5|֫xef!S;~M.6_ɴFkNcQe F]sI YL%K6'wc:u:-9)4%6dqp)s;`)_h+60JMy%l\(pI1 ѽho2[,~Dro2!KF!BPf3yVM]h Jv IDAT`򤍞ĪܓCQ<Zulj>6XBHeF*Jǝ6ZoIAtcVV:bΡ#;v-#pɯySn˒# Tt8&bx\r&:gdC'F C3d&X3 v2R,'YOƅю0Wf=!PAԴKP-^>ft.% K.IK)mиv\:ߺ6U8/ ꓧ "b+J0,Lb'm"^#W#hhltzpϡ-B&X gѠ̈́kiW>U8.ߩ|5%?w y{kF[mr *N)c]Դ?s£r X1<8Ne14O Xێu.,e#" HzNfiQLNN>VP`fȐRPK,*H PDAhH&,iXub1BR$ɫ\(_!^ДeeUlfmz vH dRZkj,tj߼#B5xx7v7McOGjܼWxZ&&.? }<كkeɢd6sS,Hx09/Bډ7Bgw v S-ꏨL4c ̰OH,렺rWC,-+JBFf"[=c?[k/CXWGĺ>aK7x3`Q@}|`*J$ȼ+#Fx%n(QF.Pd.rB`ݞ1HƜsQLQwvx,}∹SYzǖbk!:Ow_׀xǐ|j+|,( VhyYBsxu-UժT C0s%Q`ɅQALL͈j1OK.QiC 5XZZ,m/-wۇІD> DoW\uS\}4[oG֝nܶ{ \*1;9b|31QA>|݀>_zy1苺k#B<#?AG'yi2@vYat TN c{;?~ݱgΊtP z=ˀ-a1sRR)[?ERt-hVg+hTzm{7WC~N!)`^fKDxDey\XftT LU: kfg2/U5[peF_^]bސ!:O+zC[-iΞ7OcmφH^B=N1ti<A/|S-c3|SaK@*|y=<( Fhךl3KԛwdshKd* `.2B-xB !d:0 PH]Ih ]sG/uU/!|EQQ&B:? ݦ9O _kg5S/B; ZY%o9]V%bLlb8`~{. mՆ)NH(Sdq1S&3i"Z ut<\Un07T'4.P9 8Lstj_ %X"1@4 P ?슉Xbm[L6hߴ|w*+-8ݐj0}gjIeOގc~΅*k]g/6hTdS~('C)7=" oڻ>Mpj9SS0A66v.CӮ vK$"E ^TeZ|M|$uِx_$<*1HGiҘAH7q?3L=%mZ E A`e{a (25. 6Ky)cc1`9d_u3 .bg~p~ڲ=]s{_|HX:x-65:ad[)K`$% VF O)B̷ 9% Yai$@"Rg:ǥۤbi2XJ ͝/\2 QoԢudLG& }f:[D68drr!MА*Jrse-aEFFFsICY o#bɐԅ;U)%}NZ"Ys/o432Kru&=}L꪿1D 6VjAq[b L@+k-$hXv-MpdJd`h&nMGP@/P2&*Ϥۂ $S!4BIjNuryB Bږ3 0rZ-A?Eq¾|9oXa'N:F2Sp `uv*8T|$%YC1+(-L(i ;dgg%Z{SΊb 5M|n~]c\1V$#{8y5{P["PXK"XoIR",@⸐W ת6Q'18M {4YODFLj-lU1R{'maQq$-Fүh<쮯~#'6 KHhFA|X&pF^ S" ^x˒3|I"唓LH/]F/}=Z^|k W>ץhb̑ c{{>)WJu9 DlcDc-%2 ɋAnqe$V"ɠdlr!WAb G%Bc܂2L ӑ}"mȪbX?@ iYq@R"\P&TWeiwF'ŪC݂gqY~y5!Z=߀|RǔZW%f&KXp 1,5;;3K /9>:Xy7zώ~O#}Q`[(gaCg0LpRj 0 wJh\LK|"&lD\gF!"ic$K5حft' G?F8BQv: #b#_^hTIOLQo2WN ԤLNV `p`wRZv Aegr7aGZK/vy2'j;RaԒpY`GaY|J=6yHRd12F)aB}#SѤbw#ARQauG m9XX:S\֍C/KMkI1q +*+Ӿ|WE ԀWsd`.|/ZtՎ֬?1ZG*,b*\^ʖBanګ nO_zvy䫯2.ϼ0z1, 3`x@Mx#`+ZN$6 lli4z87j2jJx"DMaT4fáHHԇC lȀ!f>?6 w !$jo !zɹ4; rQ uHd1 9il^! @x.i+-x~su4DO-{ia[tM $(ޘȶCHq_ c/?Zye]pur|4h-DwJVO0WV;2 fN|TQ܈&ZqyJU>&#,cP~qqkMBdҫR܌7(%}Rb~9$ᮻԡ T 0'qzB):QYӬS.YHYlwot $!92c?ܳrfec?› %U8Fy[>t/B= FcBZY2V҃?M߸Cnϕ IQPH0:93M 'B`U9« CL(EK&*"!|#s4y5'a 9BRsw&&^Tb]qgyC^e6M ׻^&t VOd6lglY[hS4LO}-3ŞR4}1gB) t.^(_$* R|1- ˉ7j2篿1Vp4J=ᙺJvêTp]lD7y(6uիz43H[o\T0 :/̖}wĠ TT CNx$F3/Anԇe G[ AP^fp_'R:]fJfpj!|Hւ9V{Zb8ݪRV)L%_4WH)p(߻u绐SqPefnWhL80mf77nyF~d0/T>#,ژJH*zRzʻbXC 2FebjBgǿގ-]\Yx9*O;?~Il3/KF1;_޼; QC? ,RWܷqإi(4Ņ_`@[6ϒvn2 Tq1-JZ!>rPH*?.I;r zJ!B}e4/QX h4UH̭BMTLƐ7#@'ue2+r0Bj.X~1iwUDzwt9M-8CnA[+eopp9矎 L^*M Dc F(bOnڿjsP[w'ΌY b(,QF lq{m=6vm _ u gJćSh!N[ΌYjD"PߤAz`eUg;64 `ӔiA{vi3Wiq_}m_Zjߴ%4dR>榅WK7y{bi^YE{[wȽ+s:9<uۉv[JD5y/aD 5?&!Q*EozN>y5Qn%Ju,{Dα8QTb: $\];1L@F9v,Dgr!o-[PbG`5:\E3ۚ7 jT.:@mRTbScls8: /l΍[kyhw oS, W,;5jF' IDATF{`JI(HY}~ﮏޣS[g'8_;:GxeW}s^4C !P,Q"vML ֺgwfcM g͠,KVHYAdFXEDWeN`ջ6 ɐP(ėfFsirj0U"¤CB$rQDdYYtnM:SEJ{.%/,)hc ˸:BUjdž©UwAܼoCյwF^-\GOt裷uW"7%iZiWexY3pA(ܻk'x$$FCYNd`|7xON@Xee"{2&CӜ/WNm*r,mo!U$ɒ>2$ T8zqHJ7&LΑiwpjO$$D$qE3mMF}8`-`/Ńd]7P*GFmi@ %a_8d*Չ55oD_ySKN_[vO`S/_8a ֫z8I+\YG~v&`̄}XAQbn|SlZ# l.9{ZG ;'xK@E*sd2ir3g,|W t!;T+w͍M04޶EӲdX, p˥`b  OfT*HM3;,.A$T08̫G$¡J; `łM7!z*kF;Wֹr 8H*~:K}'q%&9m.`&M:g4h !8>C  A!(loYS` +.xu޿| U~&F 0K%K۴?zC[yɴJS(a\·efԄvn±XHM&DR%Bݵ_}H4Y o\vEBrk:^Us;N~ X*B#oΛ3FRʮ| ŋy?xIEBRuyJk}o2Cino&2o1CdqQJe}UP-APQDqWCi4fi6k>wp e2T(O ͭ|9ooBmķ_:AYw4j|ϰ;k>c8kWQ .ZX~҂+QF713*c GDᶮT.}G4:وGDKݖӽnyŻ\mzˬrUD8HMAФVl <Ɨm['K+l9GEl,Z>#PV1|iLUY nWY=29MՈJFoXLZ\LR @X('E]׶Ppo$6xTEмyĀӪ UZ%l_6m̌Pߣ j"LTdvNI4:ϩCڬ5UV[݁h{d FMmYnuAFm#iM"+Ye{ Eh`')>ǎ{'y VirY.ŷYoi񁮘 7\5SPvֶ)<7R13D "kņT|4l׻/ {9+ z)bU\]T81Oڬ7z?'?~[?z_!["/p BJ?76QΠy zGhDP4 5{:$wmSkuUNgpxz!Yv1;::&''A]4hYYAf5&PH&"MMLseC9"8 ncM+j r`[΃'?zd)B ~8mO y!(A e&JI;]K](!7|_{wNAEѨK4:AlJCuaS'])@#jQG _2=?_n@r9Z5iuiFp8<00PWWW 2/DH7UmզEskUu.N9<\{Laeϲ<yeNɵN`,fˊKJ1x◽|&{Z&ʧ`M%@/|B7>G ~˚ #/~o?,/sߵaF}B>,>JR:*! |G#<95Íp5f٨54|PUd7 G立'$e J>s`Tj& Fa87FР f-$ʡKc,PR`9>ݶk8_x6t[IqQH]QZF_!wOC<߄o/ofb8y[`]N\G˹<>[c%pb S{ƀ0!.HE +(BeN =̏~?,)shTt؝UZpHFFF{7 D΁֍p Ts0{ +W..U-i!sP1Bl*l[;GG:Zfdj6?'>_~m=+4l|pwPCCEѣg;~?}0cس]Sc[Yrv/t̹P(,n./  Q.lXo -ՄBD8 V4V_멫z 􍣕oM A憇\W_dYM̀9oVw\$r~v^[ED| r'}cmݴ|M\rW7sv[/\,q\\ ⳿oͣ;ooU]^WVa\9ct*8 W9\]g&ooܳ+L75];}t\y~ɧ&&9GoT^A^yo[Rpb].Vl47/j}2D8M77TkV0zdZԎ0HtʂeY2E8陌S^?Mbr[͎>~<-:6Ӣ|^JVy"}~ Fɑ] ,!Y<r.ԕ?ٽm3ߘ捲2SdwZnj'aU"[;nS$~\"G*U tjaU'3|fŮvViUj;;90;z<ݮu֕mp+-i챱C0JL~?}Dl 8%=M<>o<ɔ'`&2}crzG`ItOU cG@@P,XVKܱGy]_S!vχ0UFWNM|es&o'i .T4jb{~Ž +bBn5*Yh 4|IE|7]M]:e`I{Iao8oP$5da7R' kGG|{ ctѨ8ߕQā475>];.M #~+TyHK/5M '8nR=w.imcޑxNUտ{q>al\8Xzu(|4J/ˮk}fb赧BJ2P:W۞FX/^?|Yp6`9LMf5_eϓ;- G{sh*RivZJک7B]֛Vߵr>ǵ)cڢ}xVECEB`& n1>at!^T3;:l oO߇i YK}BYGWΖ8@ߧ9x7Ô! i۫7l%c- &9;&Cϔ&%VSmS;=Ao,^O0u] ]^9#c";:ԹDVk8~Ua~p:%{:d|[8Ld>[>ubf9 .≳cp-S:Xeo(4rVG\YYsmsw4x>?ʐiƒi$8^3/|m@•; XOB89V)QM$/Mj.4# ϵml??W?,ix,eyBEfX8p<|WwF}Rvsuں==,HO |`d8=Ҵ~ɉxW%7ᩣKeMnd΍|'7y< J؛l{rRz-ϟORpJ!V0G0ʜ4IL{plL$Z`AI6uvj)) ;pY'fQdġw߂._SY?0xjWaO.ʺ_p:ҩ;WZahU8?#\]@]eRtBY_BT7-8^B}IEQEiW\pSbccT}py"ho}[x3DҞHH V!ɨ]yb jH[Lk)CYng?}nzk'/tStw⿋4jNhVdD-ؓ-N.2X8<4(4H|PuJ[R ,h .<=u7S frh"9NqˎN2_uXAVnXkp?߮}7߷7"9a$h(@"2쉢M#cR) @,Nve f?$xa_ T0>Ʉ7UWȅO:vZYڐl`r'iM+W[g' O?lkt\x dl :8DQ(p]O2﷒N@D$4a>ɋB:']i:y(;␇~ԉ-7pՋ[I˼-ja*8jG'VZc%뚘wu;Np4v:ZOOXQn~OՕ h+ZGFފmu+?'ޡc%7s\D9Qd4(Uy۪$d$@q0_WSg Ah 3'% =T)Rxho_4{iЊl)Ztu\|b[ot|6sb"1$k[\sm^xf߻Ѩ/瓩\2% [,KV \ψ!| Z"T;{ F0F'tRtS%b% !@1H>{{EJދ-[cjB |$^RHB yCfk%mӮgZ57lb4jvf{9sj|$W F\#!9 !eLON h4= a{ |s%SImpyNBEyXj,b}P<$A|FX aD=bI,L: Pf-9I7pH.|vάADxVmy˯BHbP v,0gpbHNeΌ[ \ `,)mOF@9/(+T8|!MPdHZQQ|8AΑe('Jr%ܭ p8Dt8_&xQIut0|p\_7=x / n(H(($G"I$C\[K4 ^)> hf́& vtRfHQo~n!ۗ)%UAd |pK>Y4E0=VLB~!Jb@|@'0ϣy8u. @#i4ukeBo;w p@tщw-YE'`( ο].TCL,O'}rDDVMjv&*sLVeU~96\NFvѣG8j5Y|>S,b )0%F@XI}Wjmt>_G.Iʆ3z}6HǕa j'z (!/4w&lY|п,+olA˾%u"6KEݠ&K8S@ yavF3&_ tH3 Tm IDAT<.m?"$DoH܅|~ٲ_/`RKv O8tj9͟nXP yߟm[&-Mn?.]h) .+'ojhtNd#0t& D1/4 ݂B0 ?|⥋c.w,$7xJ#^ ĝL&FxQӝ6Qg zr##±bv67qm,(qa{l}eWC* d1w΂ŕׁZE)boI5g0,lM 2H'PβK:/м {l/ p2LQr$ Dfq9ryf޾I4AȤ"!Tz^=|O>jy˳55 aV&Z&WP"i;Fo+C#'c" BG|~M.Px#kkk}y5k9x )4X/D= AqngSsj %\ 1`V^SC&͕+*H !Gc&n+f ׃~qu*kNJa;եmJ%0c29M唙6AW;"}Ͻ~O5kji5 H8No1؀(`uhÅ!&L~s^Co\k$Yl?&[mR3}; %YZ(~pPa*F2UY=j.x(9\`LBH+W"Boӧ8jkHاnY;#UJE٩53 ͒ŔA eᆌD,#V9W,>1ʻd,&H$ *+C,*i CbL4;`39 *sÆӢD_l۲bC(T1XpF'^0 c4/ =(UyaŚShnq(3Phpbq[F)*6-a?ģ0# w^_"GHWInn 8\^3)Nw .g O,da+hw#`M S*'CaPa ̨HbBd2Qy` -m|Aftx d: 8,;' ?޸a=`}ă/y~ҥR]ྤJR$ !ABoK>(y ra bIgIPP vJ8Lj%)DY2Y<4 f2EHЯ:[>S/t\S4m %$X[nu SvL%lK#Àdܩ[&m %I&"oDOK%0<5P i"hLO~!_Pĸ/`D.$^N4 ^M?AT:a=pQyAA< G֖K=с .0!1$|t3jgER|^ZoD(И1pEf.jZut5E cavY\>'I 8#p ld3d:7E[Ͷyㆽw]$S kP88,k5]2cLB%*AСHc>RXW[s֦c=>=moj"bnO QR pk F}? bPbs 0A3x,Jm_ 9|O>`>ѹB!aw .c1eW7 W{i%[q⫴]6No8\h~U?~m% "vh ".Wy}@(OɁPO_+uC͇CmzOrqK q4'!~ |S}}}bfAoP&ۥJ*]>Ckl G.SUy9YM'[urX(!4 h6 6] PGIdBv˾`UY5u9۳4ڧij,J|S-(RWU3= xC0Å HEcJ*DɌ T6n1w[FW"7D$|xjnor"f"+7sG~`͘.WX}#ݱW"!~HY.7kXJ}=OMW>A_~yT: :S[}(|MaQnI|U 05T:NW2 LYxg1ڈݩ[Lb֬X XY^S5ލ˗r ^;,nokmհJIP7AڞUm(bݙ~x3-Mg{v@ݎ\.("ˊ9" 1MV)oʲs8s/ơpD΂5 ԧO2OSV]wd֬؏~A#X(Jʊ@#<^AijH\\tJaFLu˖3wѾC˼"a NB,QΘ=XI Z'˖h3>\s =+qoT醩B]~hdX^i&NJxa#_8 EW hgg?~t+]WM?Yf]#L\bp(|LBgzukW f{@ !@9Aٺrr KQd 2uubwf3 Y,9C"7{`Ӈ3lU~E%! F -l|Pe05$' 3vL?_U3CmT`3;y;ۻoKiȮ{O>*RvXM[6"Jiyi%0{*<3' LaxE|$~<|ۺ o^ D*#!PͶ5e̤43w^#UIJKmi4|NL$sPҿ=_xviXlzUܑ74PCd/3%95]zk}P4Tתԙ:~$r 2u{h-x !;ofDq{Q?WLBg}YyꙹV\Q__aMS8(0@UVزdX,X!ʯBv:C?io2YKSNٲi.KDlCTˉ2[',糯\1^{~+7_ //W*S1}.Aұ)J@PN뷘o+gN7}H 0&!0L'xMND܊ʊcGOtM] 7[+t,]"&ҭ?믈 ʟܚP(qHºpJ29ήmvljv]`sͼJvpor@kd?ݽZ&[ףfpaP(~/uo}n.-&`r.j*p"=+.^Vœ83Nx.P4o|`{~h |Ji0^Ӯ;h?{ ;Z!0hZpK֛UkWר{Q`gW5lqY\tLQ^?L8AƽiyLӝ?50wb ESyNC\ux^]^;`zz8gNc_?V[cO}J 9ߴI =2|l% O2Jv+n{-|(!ZBdp ?2l߼]ΜK*/.+^b! Iu7, ^/qHxAAQ"Ii^T3pʢB:!ѹ<M'JbB0,JJY_W=.9,-;%rɈ4ӤrѴY8,.I߯;e=~suEƔprZtR(G"`ߠu\F6 -a(D縧wW3=8Y:\-VfJx"Ѫ7h M^V'VP4t_ {`bmk ټe"񌲢ESz9%P`] IDAT(UJYF@D, '\3M;}s1W40X<+|`[>k 7&3J ܁ T*z5 8cs/ZIxo *Lٶ+TOV]/I 79W(@kS-K/P+(麂ݺ7Yʌ\vZ1 p7kH[{z#@.wJ~?s9le]K ,SP`TvgΩ) MޫoqYuͷd+`Oc F< q7՝;Lº]{"sVUŃ{8[;XL֚+Vy ֽXR֬y`lH0Z":`S jEmaA4zߴm攪oNXV 8 L߸#PF5Qk3M8wtɎ˖HUFJ:}u}̂0.P%O`ꯜx j4暠)yء;|:[ 7-g ,lMFɁ<n˞xht> > aeҽ;xH-e25__bz- % p[$˫ -ho~4sکS yRZ|&@_K|f0D+$8'z>}~Jgۧ U; L=榆=cŚ1l`/5 hHCjq09tyz<~ӧm޾kGSs/J9u3Uj"&wm!D%w3H&JN3Te"L T6EVң "+Bd&`_ȧz۬ vzl-$WQYqf ޫ7XZtF;++*rVWVV@:ߴm'>߲h^ou0%!xH D%`0)** JX~El[M*3ޞƶx$Vf)I[,>e+Te/;_&ݑ0//l @Ę+c xoR=(l"C"PuDcirH^T|zEzzwm b8- a`?8EtD!BU"ZMTՈ ` ΔQGʪe-X2d4tW_qcm?t {/~0*K-[@ȅv&Ůā<:D ~IaWAa^aY NA^,km64 h]Œ1OrAKAXG\  0ED.R`s` %@x]R<|0@oZ8Un>ظNb YgP8fc  wS`P>𭃴ZUQJ\P. u] O ˔bª Ӱ1%DBX6x+ e{LDZfLSsFmnhR6h2á~r;:pĥTc<% zN#e6 rV_]rs<@$&ytNh8ڮь} Q">][`VxiC|w6ˀK ˜3; S]kkJVE$,,%%bXz|`Kÿ5KC?IڣC0BD2>JWrϦ7^lֿ_K"^\-ɀQZgryC]8+x&!ޙk2c.bp mnz /d9.I(O=ym9*ڐ 5Blo;z %S*:;l4W/*WKE! "ep,@{)&RA1 I^ (eX,jl;vnFӟ}ǹ|[ZTh2(vƆ 1P[dUhM9L& 1=.c^CM[N|C+`z:;?4ҪJHB֌I“-{2ɮsg.~5o2k\4X$F86#a)s)I 9Nod$k;j5sW{{јW3L4|0ڛPxh Yi wO=69@(Ȃ-P/ױB|9`sykV0\Fa6̱}VM#bi,1&SfMxA @Md!>qB-&ogjM8qV%|>Zg\ǥX4.&QBRHK(Zױ|_ΞqlR^- z=T " ~_0gAq]cyBÚ>|.vXXXdQDG3Y"zLnwn^<32K+]ř'M~o0$*fTIn)Τ538$Iegm~]Z )nϤK׸:IwMBBl%)TXK\M:R.D@'&s{템SZ_P$uVx,8`ןh8kVteԕ%YťB(! p]lc މB%O8D&PQ0Wh(?Yp,D{5,3Q)U_cQI6Jֱu1T{AcQg:|P%3̖-0d2,&鱸%X_SRT.ȁ@^Qfa8~Z>V >7h4%53fD0ZcQ+&3;G q'vp&y D7#d5}^-f#c tOP&Y=@.ǽv?y3'LƠ֜wGBg]?Ң2 ,S[F4k Î=G x\TUgi=>%0v)3rBg8#S$ƞLiK,BU[&!T(X3Npn6z`IM,} O-n_pA C3LRô~4b60IK|?&l,aAD!8_K t;:l&;7`FI%ĘΎ,jybI}w9rNn@=np_ʷvoM㢸Bxce˖@eDaW( BDC{茄&O*n#'9h(l vyG0a </9+H#g5 (3aL|h2%Z,l8,ļ ȱ.Oԃpnno,΀Ge)a_c,:*fU/oϾ{z%#+QHqS#q8}LE~#H˙ө˖n߼wʥYڑ4[2(v}!eDbV |Z|NmҐ\h)Ԋ o/E7_ nPΐEDN3* nM:pAY 6K*b :. HX"=ݺ%eIn^q&sf]%dy@Хsg/Q3A L_0j G~z/Ι[+ԙZ0~wLY% h*%ބp+QbdS7s-!0eX`to*_!*՞x[mل` xMNUwF!u~LOwԲGWg/O7}ퟨD[^|YB+$?Bu(Qex ! /}YIT@u\Ûr5h 'deaqj {vSา_ޱ@]-;zɬęUIv̚oR~Kb/,Zӧ4|%~V !jRbI+^w gߞvBbHuoygQ`%w5okyx' L"9z ,n;{A\HtJȱ!{G1ZɴeG + F 8ń:uٽgo}O[7jnE?Ĩ Psm[>OgVV/9^L'4n׽ol#ZZNn /~ݣ./2K95KуJ0@N+7`޲18mh GObbddpY[[:!,GfUL\ճc_>IK&G6K ]8l9oQ(}6I ȸPaH,eWRܚC>mӋS>og.Yu)B`@@]lp<"zE/s ! I$ve }p(5?> b b-֨*i~:ξkZÉc;zG] !od;_x`C]˕)>:D28/}><(AB=ÀxrBn;iJ WwW9Z͡oFmx4g jDag U_&jtp_?x7`gCFQc]sܟ/tefMz,$xMR?~6>m˦QWi=; RUNO?^H/ ,k| <_,u]"%]SԷĀgjidk6 Ά =yI_LӇ@Q=bgȨJ?-ZJ5Z)33f 弄JoT_%B|M| @OܦA0Cs__ȮX2Do* Rצ2msΊ&i5}s~"Ϯo-^,Ort?|gb'zx]ʇ\jL:9_hbE\Fz7%1#am߸1U}/H %֤)JF5޼y|[Dbڿhk\P"d O۩3h~+~aù_F:*kdIltȚ~g=nȝ5j-P ~pyDоuSټ}\Ӯ6#=rǝ7~sG&gTTB-J^7܎?L:}+oT@,q%24#Y?c#x0cësKpGO_[? Om<ʃ1_O5kŵ%JmtZ)UrN!ޅ[@㼸YIDATU Xw. G ~˗] VQtN 49CcП9f;H8 \P|E&c]#CyJ'L`*qؾeB]H:aR*VlGr3TrY6#۶,yR;a.}h4ԋ?mNwIB%"Ac(78TL+]K'BT^3U5UfNR?Ia n>؉BТGy5MV9'Q fP #1&gd,f_w 5rg.\t !N)\<ŕAdB0/ R !d||O}C#v)]VU`[?ű dD= {<1Y<NҒG P9j!gU@&y+ {֮AͰw(Eā! D̜R8{Ce{F^Uo=Fy}7Cp@oq=}[5 E<.RdJz cKNA;0tee,ui9~uYvJBP ү @Q9jR\Vg:zuF&L,UrVv7-HJO~hk={~ `i pҐM՞o*.YIp,#ŤI]X@<,0Hzby\A;jx]j&7p9aŨXt7*C`IDgw!'9]Ê5Wۚ1 yN(R1-?#[M` y{GA"50#Nñf[#Ll1))WN6}JJ1o~b>98g4ÌH]RƗJblU,G,K)DMz ,R(3 GPc[5{20[t9a|\zix]zs1#XBRHN_kݨy%#&HB4RL84UXP 6ͪ&ʍR| PBjN@LL `"wҍb0‰r2CR,}8HW8qL)*(L }PRWPOPwR-*K)eq)լu>m iI%8ETp*zɈj6tֶgXh#%+;H^zZ #"-4mz6@z vj;'[fI;!%"\v;1FڶP9:P;P -'mim 8 x`+m]`TzZe^opbyI)UUr!Ը IK::dT3'@YZ 6ܒ L,#C*AahPcuN*3?L N$[[CH@t|T fd. L:[Li=5FU)d/;7}响# *bޝɽ`!TFSVS풀[+4O39_h~N] jF:_m>e=6|7Ģٷ6t<Jv='a^dԂ5ZFkjjqe߳]O#nb6&d.%jvps2chܓp:LFnUbނh8t^-M]憮۩eW]+&ftd|^_ gI_/i`z]7 by3<AaeCK|~ taRn|]>gJSgLyIIͷU4 n{;LCb # `BZsh?5֧~FjKA47~o>Wپ6N< A)7${wMh65ڀDQEՕփǩIX*+`xOdP$HP&˗][>'B\k&Z 8a$'39v.O^59nyz/9D|Xxmf)]:A*,}5Hg"BC/~tC{x+F >$SAa lj,..Ѭ}8>"F|.0)ކryFm.^ysuTj LϔR,35 < /`%IT]} /#"- х~id >!6!?-?^JTnХB@sy)_-.b=*kHx屡T3^Xp)~BX$sE]? pRBG', !BfXHHS=e}i?5{f\{`Pn.T aۏ,)+Wl6?+wz$[SΛSd՜9J4U;Q&e0QӔCeAmAz{‡wϧ7px!_jr@.~y+I{6SW\-)&W3AlBLUO$|Q1Υ(ڠ|:= 4{bv( A}mœ?iY]G}KY v@+ +*4.A3J?1!M3?;tj;M43{;*) z5Cs?B!7a.rgϻef}WL(*eQIά!uUoCK_H|b~AVZŗKrIVUT#f^SJ59n7| (Su0H  :,gZ & eqji$D{]9p'w\(ZRX3:m^IQ]zG/.H*r@}!u_< I`Z_2'vg]``DV>".׫]dLJ[)\\]d1(jNP3WV(؀Nz$9SD1%w,P)N enr(k^ MJ@#O{I]͛ 8k71*Odꊳuφ gϖOyBNDb'ALN4ACx$WbI]z5EXp7F~ْ tcڎϯ͛q@W^v5K,`s;<+:%CVg}W:L=*|F?o[?OȞ5҅wރqώP0+>?~cҢH5RcϪpFmuD8T-]NQحlڸYfY)ٳCYA x)0C`ի\Ȯ(wB"{ۓ1fmujF*Vp\ ~$~3x^z4)laA=kQM30b:=6#j00kW>3glKKu8T+NcZN[RZ[0]eפZ'5ٔ(eBBT%Q<أJ7D{"mH||DV ]U 9=Gۂ!?,gzˁQ;0bjWCqv8XJP= _4k6ǪQ26mb8Y*׆'-x1ݣx͐:k#_uLin(Ғo{jwkBwkR|(}gGTTJE!ydn'X`FX'ZJjF'55bIaOl8iոhW{IaMĎ汦ZJ~NJG!9Mt ?'2P6++_ !HuQ+Ҡ*V' YF65·U28&SG^Tk=\ؗgKFȩ NbSȮAs^::*1HL-B%Bƨc>dcG8_l{3YS) [q "sŧpVX`qb\ NW-NjL&2 exit 1 fi # まるごとコピーする echo "copy $EXPORTDIRSRC --> $EXPORTDIR" rm -fr "$EXPORTDIR" mkdir -p "$EXPORTDIR" ditto -v "$EXPORTDIRSRC" "$EXPORTDIR" # 作業用出力先 mkdir -pv $TARGETDIR # appbundlerによるjre付きバンドルの作成 ant -f build_appbundle8_jre.xml # 生成したJRE付きのバンドルファイル内からjdk名を取得する jdkname=$($PlistBuddy -c "print JVMRuntime" $TARGETDIR/CharacterManaJ.app/Contents/Info.plist) echo new jdk_name=$jdkname if [ -z "$jdkname" ]; then echo "can't read the new JVMRuntime." >&2 exit 1 fi # 現行のjdk名を取得する oldjdkname=$($PlistBuddy -c "print JVMRuntime" $EXPORTDIR/CharacterManaJ.app/Contents/Info.plist) echo current: jdk_name=$oldjdkname if [ "$oldjdkname" = "$jdkname" ]; then # 同じjdkバージョンならなにもしない echo "*already same jdk" else if [ ! -z "$oldjdkname" ]; then # 既存のjdkを消す rm -fr $EXPORTDIR/CharacterManaJ.app/Contents/PlugIns/$oldjdkname fi # 新しいjdk名に書き換える if [ -z "$oldjdkname" ]; then $PlistBuddy -c "add :JVMRuntime string $jdkname" $EXPORTDIR/CharacterManaJ.app/Contents/Info.plist else $PlistBuddy -c "set :JVMRuntime $jdkname" $EXPORTDIR/CharacterManaJ.app/Contents/Info.plist fi; # 新しいjdkをコピーする echo "copy $TARGETDIR/CharacterManaJ.app/Contents/PlugIns/$jdkname --> $EXPORTDIR/CharacterManaJ.app/Contents/PlugIns/" ditto -v $TARGETDIR/CharacterManaJ.app/Contents/PlugIns/$jdkname $EXPORTDIR/CharacterManaJ.app/Contents/PlugIns/$jdkname fi # 生成完了後は不要なので消す rm -fr $TARGETDIR echo "done" CharacterManaJ/.gitignore0000644000175000017500000000003412560206305015555 0ustar paulliupaulliu/characters /docs /lib /bin CharacterManaJ/src/0000755000175000017500000000000012560206305014357 5ustar paulliupaulliuCharacterManaJ/src/charactermanaj/0000755000175000017500000000000012560206305017322 5ustar paulliupaulliuCharacterManaJ/src/charactermanaj/model/0000755000175000017500000000000012560206305020422 5ustar paulliupaulliuCharacterManaJ/src/charactermanaj/model/PartsCategory.java0000644000175000017500000001354212560206305024061 0ustar paulliupaulliupackage charactermanaj.model; import java.io.Serializable; import java.util.Arrays; import java.util.Collections; import java.util.List; /** * パーツカテゴリ.
* 同値であるかはカテゴリIDが一致するかによってのみ判定します.
* それ以外の情報は無視されます.
* @author seraphy */ public final class PartsCategory implements Comparable, Serializable { /** * シリアライズバージョンID. */ private static final long serialVersionUID = -8652242530280056201L; /** * 順序 */ private final int order; /** * カテゴリ識別名 */ private final String categoryId; /** * カテゴリ表示名 */ private final String localizedCategoryName; /** * 複数選択可能? */ private final boolean multipleSelectable; /** * 表示行数 */ private final int visibleRows; /** * レイヤー情報 */ private final List layers; /** * カテゴリを構築する.
* @param order 順序 * @param categoryId カテゴリ識別名 * @param localizedCategoryName カテゴリ表示名 * @param multipleSelectable 複数選択可能? * @param visibleRows 表示行数 * @param layers レイヤー情報の配列、nullの場合は空とみなす */ public PartsCategory(final int order, final String categoryId, String localizedCategoryName, boolean multipleSelectable, int visibleRows, Layer[] layers) { if (categoryId == null || categoryId.trim().length() == 0) { throw new IllegalArgumentException(); } if (layers == null) { layers = new Layer[0]; } if (localizedCategoryName == null || localizedCategoryName.trim().length() == 0) { localizedCategoryName = categoryId; } this.order = order; this.categoryId = categoryId.trim(); this.localizedCategoryName = localizedCategoryName.trim(); this.multipleSelectable = multipleSelectable; this.layers = Collections.unmodifiableList(Arrays.asList(layers.clone())); this.visibleRows = visibleRows; } /** * カテゴリの順序を比較して返す.
* 順序で比較し、同一順序であれば表示名で比較し、それでも同一であれば識別子で比較します.
* @param o 比較対象. * @return 順序 */ public int compareTo(PartsCategory o) { if (o == this) { return 0; } int ret = order - o.order; if (ret == 0) { ret = localizedCategoryName.compareTo(o.localizedCategoryName); } if (ret == 0) { ret = categoryId.compareTo(o.categoryId); } return ret; } @Override public int hashCode() { return this.categoryId.hashCode(); } @Override public boolean equals(Object obj) { if (obj == this) { return true; } if (obj != null && obj instanceof PartsCategory) { // IDが等しいか? PartsCategory o = (PartsCategory) obj; if (categoryId.equals(o.getCategoryId())) { // それ以外の情報も等しいか? // (用法的に、異なるインスタンスで同じIDをもつことは希であり、 // その上、IDが同一で、それ以外の内容が一致しないことは更に希である。) if (order == o.order && localizedCategoryName .equals(o.localizedCategoryName) && multipleSelectable == o.multipleSelectable && visibleRows == o.visibleRows && layers.equals(o.layers)) { return true; } } } return false; } /** * 同一カテゴリであるか判定します.
* nullの場合は常にfalseを返します.
* @param obj パーツカテゴリ、またはnull * @return 同一のパーツカテゴリIDであればtrue、そうでなければfalse */ public boolean isSameCategoryID(PartsCategory obj) { if (obj == this) { return true; } if (obj != null) { return categoryId.equals(obj.categoryId); } return false; } public static boolean equals(PartsCategory o1, PartsCategory o2) { if (o1 == o2) { return true; } if (o1 == null || o2 == null) { return false; } return o1.equals(o2); } /** * 定義順を取得する * @return 定義順 */ public int getOrder() { return order; } /** * 複数選択可能であるか? * @return 複数選択可能であるか? */ public boolean isMultipleSelectable() { return multipleSelectable; } /** * 表示行数を取得する. * @return 表示行数 */ public int getVisibleRows() { return visibleRows; } /** * このカテゴリに指定したレイヤーが含まれるか検証する. * @param layer レイヤー * @return 含まれる場合はtrue、含まれない場合はfalse */ public boolean hasLayer(Layer layer) { if (layer == null) { return false; } for (Layer memberLayer : layers) { if (Layer.equals(memberLayer, layer)) { return true; } } return false; } /** * レイヤー情報 * @return レイヤー情報 */ public List getLayers() { return layers; } /** * レイヤーを取得する.
* 該当するレイヤーがなければnull * @param layerId レイヤー名 * @return レイヤーもしくはnull */ public Layer getLayer(String layerId) { if (layerId == null) { return null; } for (Layer layer : layers) { if (layer.getId().equals(layerId)) { return layer; } } return null; } /** * カテゴリ識別名を取得する. * @return カテゴリ識別名 */ public String getCategoryId() { return categoryId; } /** * カテゴリ表示名を取得する. * @return カテゴリ表示名 */ public String getLocalizedCategoryName() { return this.localizedCategoryName; } @Override public String toString() { return getLocalizedCategoryName(); } } CharacterManaJ/src/charactermanaj/model/ColorGroup.java0000644000175000017500000000334512560206305023365 0ustar paulliupaulliupackage charactermanaj.model; import java.io.Serializable; /** * カラーグループ.
* カラーグループはimmutableであり、構築された値は変更されることはない.
* @author seraphy */ public final class ColorGroup implements Serializable { private static final long serialVersionUID = -2127943872189828172L; private final String id; private final boolean enabled; private final String localizedName; public static final ColorGroup NA = new ColorGroup("n/a", "", false); public ColorGroup(final String id, final String localizedName) { this(id, localizedName, true); } private ColorGroup(final String id, final String localizedName, final boolean enabled) { if (id == null || id.trim().length() == 0) { throw new IllegalArgumentException(); } this.id = id.trim(); this.localizedName = (localizedName == null || localizedName.trim().length() == 0) ? id : localizedName; this.enabled = enabled; } public boolean isEnabled() { return enabled; } public String getId() { return id; } public String getLocalizedName() { return localizedName; } @Override public int hashCode() { return id.hashCode(); } @Override public boolean equals(Object obj) { if (obj == this) { return true; } if (obj != null && obj instanceof ColorGroup) { ColorGroup o = (ColorGroup) obj; return id.equals(o.getId()); } return false; } public static boolean equals(ColorGroup v1, ColorGroup v2) { if (v1 == v2) { return true; } if (v1 == null || v2 == null) { return false; } return v1.equals(v2); } @Override public String toString() { return getLocalizedName(); } } CharacterManaJ/src/charactermanaj/model/Layer.java0000644000175000017500000001111412560206305022337 0ustar paulliupaulliupackage charactermanaj.model; import java.io.File; import java.io.Serializable; /** * レイヤー情報.
* 重ね合わせ順による比較が可能.
* immutableなクラスであり、構築された後に変更されることはない.
* 同値であるかは、ID、順序、Dirのみで判断され、それ以外の情報は無視される.
* @author seraphy */ public final class Layer implements Comparable, Serializable { private static final long serialVersionUID = -6437516046486547811L; /** * 重ね合わせ順 */ private final int order; /** * レイヤー識別名 */ private final String id; /** * レイヤー表示名 */ private final String localizedName; /** * カラーグループ */ private final ColorGroup colorGroup; /** * カラーグループ同期(初期) */ private final boolean initSync; /** * 対象ディレクトリ */ private final String dir; /** * カラーモデル名 */ private final String colorModelName; /** * レイヤー情報を構築する * * @param id * @param localizedName * @param order * @param colorGroup * @param initSync * @param dir * @param colorModelName */ public Layer(String id, String localizedName, int order, ColorGroup colorGroup, boolean initSync, String dir, String colorModelName) { if (id == null || id.length() == 0 || order < 0 || dir == null) { throw new IllegalArgumentException(); } if (localizedName == null || localizedName.length() == 0) { localizedName = id; } if (colorGroup == null) { colorGroup = ColorGroup.NA; } if (colorModelName == null || colorModelName.trim().length() == 0) { colorModelName = null; } this.id = id; this.localizedName = localizedName; this.order = order; this.colorGroup = colorGroup; this.initSync = initSync; this.dir = dir; this.colorModelName = colorModelName; } /** * 重ね合わせ順に比較する. * 同順位の場合は名前・ディレクトリから順序を決める. */ public int compareTo(Layer o) { int ret = order - o.order; if (ret == 0) { ret = id.compareTo(o.id); } if (ret == 0) { File d1 = new File(dir); File d2 = new File(o.dir); ret = d1.compareTo(d2); } return ret; } /** * レイヤー識別名を取得する * @return レイヤー識別名 */ public String getId() { return id; } /** * レイヤー表示名を取得する * @return レイヤー表示名 */ public String getLocalizedName() { return localizedName; } /** * 重ね合わせ順を取得する. * @return 重ね合わせ順 */ public int getOrder() { return order; } /** * カラーグループを取得する. * @return カラーグループ */ public ColorGroup getColorGroup() { return colorGroup; } /** * カラーグループの同期フラグ(初期)を取得する. * @return 同期フラグ */ public boolean isInitSync() { return initSync; } /** * 対象ディレクトリを取得する. * @return 対象ディレクトリ */ public String getDir() { return dir; } /** * カラーモデル名を取得する. * * @return カラーモデル名 */ public String getColorModelName() { return colorModelName; } /** * 同一レイヤーであるか判断する.
* ID、順序、Dirで判断する.
* (カラーグループ、カラーグループ同期、表示名、は無視される.)
*/ @Override public boolean equals(Object obj) { if (obj == this) { return true; } if (obj != null && obj instanceof Layer) { Layer o = (Layer) obj; File d1 = new File(dir); File d2 = new File(o.dir); return id.equals(o.id) && order == o.order && d1.equals(d2); } return false; } /** * 同一レイヤーであるか判断する.
* IDのみで判断する.
* (カラーグループ、カラーグループ同期、表示名、順序、Dirは無視される.)
* @param a 比較1 * @param b 比較2 * @return 等しければtrue、そうでなければfalse */ public static boolean equals(Layer a, Layer b) { if (a == b) { return true; } if (a == null || b == null) { return false; } return a.equals(b); } @Override public int hashCode() { return id.hashCode(); } @Override public String toString() { return "Layer(id=" + id + ", name=" + localizedName + ", order=" + order + ")"; } } CharacterManaJ/src/charactermanaj/model/IndependentPartsColorInfo.java0000644000175000017500000000702012560206305026346 0ustar paulliupaulliupackage charactermanaj.model; import java.io.Serializable; import java.util.List; import java.util.logging.Level; import java.util.logging.Logger; import charactermanaj.graphics.filters.ColorConvertParameter; /** * 素のカラー情報.
* レイヤーやカテゴリなどのリレーションシップがない、
* 特定のキャラクターデータモデルのツリーの一部には組み込まれていない状態のもの.
*/ public class IndependentPartsColorInfo implements Serializable { /** * シリアライズバージョンID */ private static final long serialVersionUID = -8114086036157411198L; /** * ロガー */ private static final Logger logger = Logger .getLogger(IndependentPartsColorInfo.class.getName()); /** * layerID */ private String layerId; /** * カラーグループのid */ private String colorGroupId; /** * カラーの同期指定 */ private boolean syncColorGroup; /** * カラー変換パラメータ.
*/ private ColorConvertParameter colorConvertParameter = new ColorConvertParameter(); public void setLayerId(String layerId) { this.layerId = layerId; } public String getLayerId() { return layerId; } public void setColorGroupId(String colorGroupId) { this.colorGroupId = colorGroupId; } public String getColorGroupId() { return colorGroupId; } public void setSyncColorGroup(boolean syncColorGroup) { this.syncColorGroup = syncColorGroup; } public boolean isSyncColorGroup() { return syncColorGroup; } public void setColorConvertParameter( ColorConvertParameter colorConvertParameter) { this.colorConvertParameter = colorConvertParameter; } public ColorConvertParameter getColorConvertParameter() { return colorConvertParameter; } /** * インスタンス独立の素のカラー情報から、カテゴリやレイヤー、カラーグループのインスタンスと関連づけられたカラー情報に変換してかえす. * * @param characterData * キャラクターデータ * @param category * パーツカテゴリインスタンス * @param partsColorInfoList * 素のパーツカラー情報、なければnull可 * @return パーツカラー情報、パーツカラー情報がなければnull */ public static PartsColorInfo buildPartsColorInfo( CharacterData characterData, PartsCategory category, List partsColorInfoList) { if (characterData == null || category == null) { throw new IllegalArgumentException(); } if (partsColorInfoList == null) { return null; } PartsColorInfo partsColorInfo = null; for (IndependentPartsColorInfo info : partsColorInfoList) { String layerId = info.getLayerId(); Layer layer = category.getLayer(layerId); if (layer == null) { logger.log(Level.WARNING, "undefined layer: " + layerId); break; } if (partsColorInfo == null) { partsColorInfo = new PartsColorInfo(category); } ColorInfo colorInfo = partsColorInfo.get(layer); // color group String colorGroupId = info.getColorGroupId(); ColorGroup colorGroup = characterData.getColorGroup(colorGroupId); boolean syncColorGroup = info.isSyncColorGroup(); colorInfo.setColorGroup(colorGroup); colorInfo.setSyncColorGroup(syncColorGroup); // color parameters colorInfo.setColorParameter(info.getColorConvertParameter()); } return partsColorInfo; } }CharacterManaJ/src/charactermanaj/model/WorkingSet.java0000644000175000017500000001277612560206305023376 0ustar paulliupaulliupackage charactermanaj.model; import java.io.File; import java.io.IOException; import java.io.ObjectInputStream; import java.io.ObjectInputStream.GetField; import java.io.ObjectStreamClass; import java.io.Serializable; import java.net.URI; import java.net.URL; import java.util.Map; import java.util.logging.Level; import java.util.logging.Logger; import charactermanaj.ui.model.WallpaperInfo; public class WorkingSet implements Serializable { /** * ロガー */ private static final Logger logger = Logger.getLogger(WorkingSet.class.getName()); private static final long serialVersionUID = -4728766140876842242L; private Map partsColorInfoMap; private String characterDataRev; /** * 現在の選択中のパーツと色設定からのパーツセット */ private PartsSet partsSet; private URI characterDocBase; private File lastUsedSaveDir; private File lastUsedExportDir; // ver0.92 private PartsSet lastUsePresetParts; // ver0.94 private CharacterData characterData; // ver0.97 private WallpaperInfo wallpaperInfo; /** * デシリアライズ * * @param inp * オブジェクトの復元ストリーム * @throws IOException * 例外 * @throws ClassNotFoundException * 例外 */ @SuppressWarnings("unchecked") private void readObject(ObjectInputStream inp) throws IOException, ClassNotFoundException { GetField fields = inp.readFields(); ObjectStreamClass sig = fields.getObjectStreamClass(); if (logger.isLoggable(Level.FINE)) { logger.log(Level.FINE, "WorkingSetのデシリアライズ name=" + sig.getName() + "/sid=" + sig.getSerialVersionUID()); } partsColorInfoMap = (Map) fields.get("partsColorInfoMap", null); characterDataRev = (String) fields.get("characterDataRev", null); partsSet = (PartsSet) fields.get("partsSet", null); Object anyDocBase = fields.get("characterDocBase", null); if (anyDocBase != null && anyDocBase instanceof URL) { File file = new File(((URL) anyDocBase).getPath()); anyDocBase = file.toURI(); } // ver0.95からURI, それ以前はURL characterDocBase = (URI) anyDocBase; lastUsedSaveDir = (File) fields.get("lastUsedSaveDir", null); lastUsedExportDir = (File) fields.get("lastUsedExportDir", null); // ver0.92 lastUsePresetParts = (PartsSet) fields.get("lastUsePresetParts", null); // ver0.94 characterData = (CharacterData) fields.get("characterData", null); // ver0.97 wallpaperInfo = (WallpaperInfo) fields.get("wallpaperInfo", null); } public void setCharacterDataRev(String characterDataRev) { this.characterDataRev = characterDataRev; } /** * REV情報.
* キャラクターデータが設定されていない場合に使用される.
* (ver0.96以前旧シリアライズデータ互換用)
* * @return */ public String getCharacterDataRev() { return characterDataRev; } public Map getPartsColorInfoMap() { return partsColorInfoMap; } public void setPartsColorInfoMap( Map partsColorInfoMap) { this.partsColorInfoMap = partsColorInfoMap; } public void setCharacterDocBase(URI characterDocBase) { this.characterDocBase = characterDocBase; } public void setPartsSet(PartsSet partsSet) { this.partsSet = partsSet; } public URI getCharacterDocBase() { return characterDocBase; } public PartsSet getPartsSet() { return partsSet; } public void setLastUsedSaveDir(File lastUsedSaveDir) { this.lastUsedSaveDir = lastUsedSaveDir; } public void setLastUsedExportDir(File lastUsedExportDir) { this.lastUsedExportDir = lastUsedExportDir; } public File getLastUsedSaveDir() { return lastUsedSaveDir; } public File getLastUsedExportDir() { return lastUsedExportDir; } /** * 最後に使用したお気に入りの情報.
* 一度もプリセットを使ってなければnull.
* ver0.94以前には存在しなかったため、nullになりえます。 * * @return */ public PartsSet getLastUsePresetParts() { return lastUsePresetParts; } /** * /** 最後に使用したお気に入りの情報.
* 一度もプリセットを使ってなければnull.
* (ver0.94以前には存在しなかったため、nullになりえます。) * * @param lastUsePresetParts */ public void setLastUsePresetParts(PartsSet lastUsePresetParts) { this.lastUsePresetParts = lastUsePresetParts; } public void setCharacterData(CharacterData characterData) { this.characterData = characterData; } /** * 使用していたキャラクター定義を取得します.
* ver0.95よりも以前には存在しないため、nullとなりえます.
* * @return キャラクターデータ */ public CharacterData getCharacterData() { return characterData; } /** * 壁紙情報を取得します.
* ver0.97よりも以前には存在しないため、nullとなりえます.
* * @return 壁紙情報 */ public WallpaperInfo getWallpaperInfo() { return wallpaperInfo; } public void setWallpaperInfo(WallpaperInfo wallpaperInfo) { this.wallpaperInfo = wallpaperInfo; } @Override public String toString() { return "docBase:" + characterDocBase + "/rev:" + characterDataRev; } } CharacterManaJ/src/charactermanaj/model/PartsManageDataConverter.java0000644000175000017500000000425212560206305026154 0ustar paulliupaulliupackage charactermanaj.model; /** * パーツ設定情報から、パーツ管理情報に変換する.
* @author seraphy * */ public class PartsManageDataConverter { private PartsManageData partsManageData; /** * パーツ管理情報は自動作成される.
* @param partsSpecResolver */ public PartsManageDataConverter() { this(null); } /** * 書き込み先となるパーツ管理情報を指定して構築する.
* パーツ管理情報がnullの場合は自動作成される.
* @param partsManageData パーツ管理情報(書き込み先) */ public PartsManageDataConverter(PartsManageData partsManageData) { if (partsManageData == null) { this.partsManageData = new PartsManageData(); } else { this.partsManageData = partsManageData; } } /** * パーツ管理情報を取得する. * @return パーツ管理情報 */ public PartsManageData getPartsManageData() { return partsManageData; } /** * パーツ識別子とパーツ設定情報を指定して、パーツ管理情報に変換して登録する.
* @param partsIdentifier パーツ識別子 * @param partsSpec パーツ設定情報(null可) */ public void convert(PartsIdentifier partsIdentifier, PartsSpec partsSpec) { if (partsIdentifier == null) { throw new IllegalArgumentException(); } String localizedName = partsIdentifier.getLocalizedPartsName(); double version; String downloadURL; if (partsSpec != null) { version = partsSpec.getVersion(); downloadURL = partsSpec.getDownloadURL(); } else { version = 0; downloadURL = null; } PartsAuthorInfo partsAuthorInfo; if (partsSpec != null) { partsAuthorInfo = partsSpec.getAuthorInfo(); } else { partsAuthorInfo = null; } PartsManageData.PartsVersionInfo versionInfo = new PartsManageData.PartsVersionInfo(); versionInfo.setDownloadURL(downloadURL); versionInfo.setVersion(version); PartsManageData.PartsKey partsKey = new PartsManageData.PartsKey(partsIdentifier); partsManageData.putPartsInfo(partsKey, localizedName, partsAuthorInfo, versionInfo); } } CharacterManaJ/src/charactermanaj/model/AppConfig.java0000644000175000017500000007326712560206305023152 0ustar paulliupaulliupackage charactermanaj.model; import java.awt.Color; import java.io.BufferedOutputStream; import java.io.File; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.net.URI; import java.net.URL; import java.nio.charset.Charset; import java.util.ArrayList; import java.util.List; import java.util.Locale; import java.util.Properties; import java.util.Set; import java.util.logging.Level; import java.util.logging.Logger; import java.util.regex.Pattern; import charactermanaj.util.ApplicationLogHandler; import charactermanaj.util.BeanPropertiesUtilities; import charactermanaj.util.ConfigurationDirUtilities; /** * アプリケーションの全域にわたる設定.
* アプリケーション設定は、クラスパス上のリソース、コートベース直下のappConfig.xml、ユーザーごとのappConfig.xmlの順に読み込まれます * .
* * @author seraphy */ public final class AppConfig { /** * アプリケーション設定ファイルの名前 */ private static final String CONFIG_NAME = "appConfig.xml"; /** * 全ユーザー用キャラクターディレクトリのシステムプロパティのキー名.
*/ public static final String COMMON_CHARACTER_DIR_PROPERTY_NAME = "character.dir"; /** * 開発用仕様バージョン番号 */ private static final String DEFAULT_SPECIFICATION_VERSION = "1.0"; /** * ロガー */ private static final Logger logger = Logger.getLogger(AppConfig.class.getName()); /** * シングルトンインスタンス */ private static final AppConfig singleton = new AppConfig(); /** * インスタンスを取得する. * * @return インスタンス */ public static AppConfig getInstance() { return singleton; } /** * プライベートコンストラクタ */ private AppConfig() { loadAppVersions(); } private String implementationVersion; private String specificationVersion; /** * 実装バージョンを取得する.
* ビルドされたjarパッケージからバージョン情報を取得する.
* クラスパスの実行からのバージョンは常に「develop」となる.
* * @return 実装バージョン */ public String getImplementationVersion() { return implementationVersion; } /** * 仕様バージョンを取得する.
* ビルドされたjarパッケージからバージョン情報を取得する.
* クラスパスの実行からのバージョンは常に「develop」となる.
* * @return 仕様バージョン */ public String getSpecificationVersion() { return specificationVersion; } /** * ビルドされたjarパッケージからバージョン情報を取得する.
* クラスパスの実行からのバージョンは常に「develop」となる.
*/ private void loadAppVersions() { Package pak = this.getClass().getPackage(); String implementationVersion = "develop"; String specificationVersion = DEFAULT_SPECIFICATION_VERSION; if (pak != null) { String vInfo = pak.getImplementationVersion(); if (vInfo != null && implementationVersion.trim().length() > 0) { implementationVersion = vInfo.trim(); } String specVInfo = pak.getSpecificationVersion(); if (specVInfo != null && specVInfo.trim().length() > 0) { specificationVersion = specVInfo.trim(); } } this.implementationVersion = implementationVersion; this.specificationVersion = specificationVersion; } /** * 設定ファイルのロケール固有版へのファイル末尾の修飾文字列を読み込み順に取得する. * @param locale ロケール、nullの場合はデフォルト * @return ロケールを表すファイル末尾の修飾文字列の読み込み順のリスト */ private String[] getLocalizedSuffix(Locale locale) { if (locale == null) { locale = Locale.getDefault(); } String language = locale.getLanguage(); String country = locale.getCountry(); String variant = locale.getVariant(); return new String[] { "", "_" + language, "_" + language + "_" + country, "_" + language + "_" + country + "_" + variant, }; } /** * 指定されたファイル名の拡張子の前にロケール固有の修飾文字列を付与したリスト作成して返す. * @param base ファイル名 * @param locale ロケール、nullの場合はデフォルト * @return ロケールの検索順序でのロケール固有の修飾文字列が付与されたファイルのリスト */ private List expandLocalizedSuffix(File base, Locale locale) { String path = base.getPath(); int pt = path.lastIndexOf("."); String left, right; if (pt >= 0) { left = path.substring(0, pt); right = path.substring(pt); } else { left = path; right = ""; } ArrayList files = new ArrayList(); for (String suffix : getLocalizedSuffix(locale)) { String newPath = left + suffix + right; System.out.println("newpath=" + newPath); files.add(new File(newPath)); } return files; } /** * 設定ファイルの読み込み順序で、読み込むべきURIのリストを返す.
*
    *
  • (1) リソース上の/appConfig.xml
  • *
  • (2) appConfigFileシステムプロパティで指定されたファイル
  • *
  • (3) コードベース下のappConfig.xml
  • *
  • (4) アプリケーションデータ保存先のappConfig.xml
  • *
* appConfigFileシステムプロパティがある場合は、(1)(2)の順。
* 指定がない場合は、(1)(3)(4)の順に読み取る.
* * @return 優先順位での設定ファイルの読み込み先URIのリスト * @throws IOException */ public List getCandidateURIs() throws IOException { List uris = new ArrayList(); // リソース中の既定 (ロケール識別あり) for (File localizedFile : expandLocalizedSuffix(new File(getClass() .getResource("/" + CONFIG_NAME).getPath()), null)) { uris.add(localizedFile.toURI()); } String specifiedAppConfig = System.getProperty("appConfigFile"); if (specifiedAppConfig != null) { // システムプロパティでappConfig.xmlを明示している場合は、それを読み込む。 // (appConfigFileシステムプロパティが空の場合は、リソース埋め込みの既定の設定だけをよみこむ) if (specifiedAppConfig.trim().length() > 0) { File specifiedAppConfigFile = new File(specifiedAppConfig); uris.add(specifiedAppConfigFile.toURI()); } } else { // システムプロパティて明示していない場合は、まずコードベースを使用する.(ロケール識別あり) File codeBase = ConfigurationDirUtilities.getApplicationBaseDir(); for (File localizedFile : expandLocalizedSuffix(new File(codeBase, CONFIG_NAME).getCanonicalFile(), null)) { uris.add(localizedFile.toURI()); } // システムプロパティて明示していない場合は、次にユーザディレクトリを使用する. File userDataDir = ConfigurationDirUtilities.getUserDataDir(); uris.add(new File(userDataDir, CONFIG_NAME).toURI()); } return uris; } /** * 保存先の試行順序ごとのファイルのリスト。 * * @return 保存先(優先順) */ public List getPrioritySaveFileList() { ArrayList saveFiles = new ArrayList(); String specifiedAppConfig = System.getProperty("appConfigFile"); if (specifiedAppConfig != null) { // システムプロパティでappConfig.xmlを明示している場合 if (specifiedAppConfig.trim().length() > 0) { File specifiedAppConfigFile = new File(specifiedAppConfig); if (!specifiedAppConfigFile.exists() || specifiedAppConfigFile.canWrite()) { // まだ存在しないか、書き込み可能である場合のみ候補とする. saveFiles.add(specifiedAppConfigFile); } } } else { // システムプロパティappConfigFileがなければユーザディレクトリへ書き込む // ユーザディレクトリは常に候補とする. File userDataDir = ConfigurationDirUtilities.getUserDataDir(); saveFiles.add(new File(userDataDir, CONFIG_NAME)); } return saveFiles; } /** * プロパティをロードする.
* 存在しないか、読み取りに失敗した場合は、該当ファイルはスキップされる.
*/ public void loadConfig() { Properties config = new Properties(); try { for (URI uri : getCandidateURIs()) { if (uri == null) { continue; // リソースがない場合はnullになる } // ファイルの実在チェック (チェックできる場合のみ) if ("file".equals(uri.getScheme())) { File file = new File(uri); if (!file.exists()) { logger.log(Level.CONFIG, "appConfig.xml is not found.:" + file); continue; } } // appConfig.xmlの読み込みを行う. // Properties#loadFromXML() はXMLからキーを読み取り、既存のキーに対して上書きする. // XMLに存在しないキーは読み込み前のままなので、繰り返し呼び出すことで「重ね合わせ」することができる. try { URL resourceURL = uri.toURL(); InputStream is = resourceURL.openStream(); try { config.loadFromXML(is); logger.log(Level.CONFIG, "appConfig.xml is loaded.:" + uri); } finally { is.close(); } } catch (FileNotFoundException ex) { logger.log(Level.CONFIG, "appConfig.xml is not found.: " + uri, ex); // 無視する (無い場合は十分にありえるので「情報」レベルでログ。) } catch (Exception ex) { logger.log(Level.WARNING, "appConfig.xml loading failed.: " + uri, ex); // 無視する } } } catch (IOException ex) { throw new RuntimeException("appConfig.xml loading failed.", ex); } catch (RuntimeException ex) { throw new RuntimeException("appConfig.xml loading failed.", ex); } BeanPropertiesUtilities.loadFromProperties(this, config); } /** * プロパティをアプリケーションデータの指定した保存先に保存する. * * @throws IOException * 保存に失敗した場合 */ public void saveConfig(List prioritySaveFiles) throws IOException { Properties config = getProperties(); IOException oex = null; for (File configStore : prioritySaveFiles) { try { OutputStream os = new BufferedOutputStream( new FileOutputStream(configStore)); try { config.storeToXML(os, CONFIG_NAME, "UTF-8"); return; // 成功した時点で終了 } finally { os.close(); } } catch (IOException ex) { logger.log(Level.WARNING, "アプリケーション設定の保存に失敗しました" + ex, ex); oex = ex; } } // 例外が発生していれば、最後の例外を返す. if (oex != null) { throw oex; } } /** * プロパティをアプリケーションデータの保存先に保存する. * * @throws IOException * 保存に失敗した場合 */ public void saveConfig() throws IOException { saveConfig(getPrioritySaveFileList()); } /** * Propertiesの値を設定した場合に設定できない項目があるかチェックする.
* このメソッドを呼び出しても、アプリケーション設定自身は何も影響されない.
* * @param props * 適用するプロパティ * @return 設定できなかったプロパティキーのコレクション、問題なければ空が返される. */ public static Set checkProperties(Properties props) { if (props == null) { throw new IllegalArgumentException(); } AppConfig dummy = new AppConfig(); // アプリケーションから参照されないダミーのインスタンスを作成する. return BeanPropertiesUtilities.loadFromProperties(dummy, props); } /** * Propertiesの値で設定を更新する.
* * @param props * 適用するプロパティ * @return 設定できなかったプロパティキーのコレクション、問題なければ空が返される. */ public Set update(Properties props) { if (props == null) { throw new IllegalArgumentException(); } return BeanPropertiesUtilities.loadFromProperties(this, props); } /** * このアプリケーション設定をプロパティに書き出して返します.
* * @return プロパティ */ public Properties getProperties() { Properties config = new Properties(); BeanPropertiesUtilities.saveToProperties(this, config); return config; } /** * プロファイル選択ダイアログのプロファイルのサンプルイメージの背景色 * * @return サンプルイメージの背景色 */ public Color getSampleImageBgColor() { return sampleImageBgColor; } public void setSampleImageBgColor(Color sampleImageBgColor) { if (sampleImageBgColor == null) { throw new IllegalArgumentException(); } this.sampleImageBgColor = sampleImageBgColor; } private Color sampleImageBgColor = Color.white; /** * デフォルトのイメージ背景色を取得する. * * @return デフォルトのイメージ背景色 */ public Color getDefaultImageBgColor() { return defaultImageBgColor; } public void setDefaultImageBgColor(Color defaultImageBgColor) { if (defaultImageBgColor == null) { throw new IllegalArgumentException(); } this.defaultImageBgColor = defaultImageBgColor; } private Color defaultImageBgColor = Color.white; /** * 使用中アイテムの背景色を取得する. * * @return 使用中アイテムの背景色 */ public Color getCheckedItemBgColor() { return checkedItemBgColor; } public void setCheckedItemBgColor(Color checkedItemBgColor) { if (checkedItemBgColor == null) { throw new IllegalArgumentException(); } this.checkedItemBgColor = checkedItemBgColor; } private Color checkedItemBgColor = Color.cyan.brighter(); /** *  選択アイテムの背景色を取得する * * @return 選択アイテムの背景色 */ public Color getSelectedItemBgColor() { return selectedItemBgColor; } public void setSelectedItemBgColor(Color selectedItemBgColor) { this.selectedItemBgColor = selectedItemBgColor; } private Color selectedItemBgColor = Color.orange; /** * 不備のあるデータ行の背景色を取得する. * * @return 不備のあるデータ行の背景色 */ public Color getInvalidBgColor() { return invalidBgColor; } public void setInvalidBgColor(Color invalidBgColor) { if (invalidBgColor == null) { throw new IllegalArgumentException(); } this.invalidBgColor = invalidBgColor; } private Color invalidBgColor = Color.red.brighter().brighter(); /** * JPEG画像変換時の圧縮率を取得する. * * @return 圧縮率 */ public float getCompressionQuality() { return compressionQuality; } public void setCompressionQuality(float compressionQuality) { if (compressionQuality < .1f || compressionQuality > 1f) { throw new IllegalArgumentException(); } this.compressionQuality = compressionQuality; } private float compressionQuality = .8f; /** * エクスポートウィザードのプリセットにパーツ不足時の警告色(前景色)を取得する. * * @return エクスポートウィザードのプリセットにパーツ不足時の警告色(前景色) */ public Color getExportPresetWarningsForegroundColor() { return exportPresetWarningsForegroundColor; } public void setExportPresetWarningsForegroundColor( Color exportPresetWarningsForegroundColor) { this.exportPresetWarningsForegroundColor = exportPresetWarningsForegroundColor; } private Color exportPresetWarningsForegroundColor = Color.red; /** * JARファイル転送用バッファサイズ.
* * @return JARファイル転送用バッファサイズ. */ public int getJarTransferBufferSize() { return jarTransferBufferSize; } public void setJarTransferBufferSize(int jarTransferBufferSize) { if (jarTransferBufferSize <= 0) { throw new IllegalArgumentException(); } this.jarTransferBufferSize = jarTransferBufferSize; } private int jarTransferBufferSize = 4096; /** * ZIPファイル名のエンコーディング.
* * @return ZIPファイル名のエンコーディング.
*/ public String getZipNameEncoding() { return zipNameEncoding; } public void setZipNameEncoding(String zipNameEncoding) { if (zipNameEncoding == null) { throw new IllegalArgumentException(); } try { Charset.forName(zipNameEncoding); } catch (Exception ex) { throw new RuntimeException("unsupported charset: " + zipNameEncoding); } this.zipNameEncoding = zipNameEncoding; } private String zipNameEncoding = "csWindows31J"; /** * ディセーブルなテーブルのセルのフォアグラウンドカラーを取得する. * * @return ディセーブルなテーブルのセルのフォアグラウンドカラー */ public Color getDisabledCellForgroundColor() { return disabledCellForegroundColor; } public void setDisabledCellForegroundColor(Color disabledCellForegroundColor) { if (disabledCellForegroundColor == null) { throw new IllegalArgumentException(); } this.disabledCellForegroundColor = disabledCellForegroundColor; } private Color disabledCellForegroundColor = Color.gray; /** * ディレクトリを監視する間隔(mSec)を取得する. * * @return ディレクトリを監視する間隔(mSec) */ public int getDirWatchInterval() { return dirWatchInterval; } public void setDirWatchInterval(int dirWatchInterval) { if (dirWatchInterval <= 0) { throw new IllegalArgumentException(); } this.dirWatchInterval = dirWatchInterval; } private int dirWatchInterval = 7 * 1000; /** * ディレクトリの監視を有効にするか? * * @return ディレクトリの監視を有効にする場合はtrue */ public boolean isEnableDirWatch() { return enableDirWatch; } public void setEnableDirWatch(boolean enableDirWatch) { this.enableDirWatch = enableDirWatch; } private boolean enableDirWatch = true; /** * ファイル転送に使うバッファサイズ.
* * @return バッファサイズ */ public int getFileTransferBufferSize() { return fileTransferBufferSize; } public void setFileTransferBufferSize(int fileTransferBufferSize) { if (fileTransferBufferSize <= 0) { throw new IllegalArgumentException(); } this.fileTransferBufferSize = fileTransferBufferSize; } private int fileTransferBufferSize = 4096; /** * プレビューのインジケータを表示するまでのディレイ(mSec)を取得する. * * @return プレビューのインジケータを表示するまでのディレイ(mSec) */ public long getPreviewIndicatorDelay() { return previewIndeicatorDelay; } public void setPreviewIndeicatorDelay(long previewIndeicatorDelay) { if (previewIndeicatorDelay < 0) { throw new IllegalArgumentException(); } this.previewIndeicatorDelay = previewIndeicatorDelay; } private long previewIndeicatorDelay = 300; /** * 情報ダイアログの編集ボタンを「開く」アクションにする場合はtrue、「編集」アクションにする場合はfalse * * @return trueならばOpen、falseならばEdit */ public boolean isInformationDialogOpenMethod() { return informationDialogOpenMethod; } public void setInformationDialogOpenMethod( boolean informationDialogOpenMethod) { this.informationDialogOpenMethod = informationDialogOpenMethod; } private boolean informationDialogOpenMethod = true; /** * ログを常に残すか?
* falseの場合は{@link ApplicationLogHandler}の実装に従って終了時に 必要なければログは削除される.
* * @return 常に残す場合はtrue、そうでなければfalse */ public boolean isNoRemoveLog() { return noRemoveLog; } public void setNoRemoveLog(boolean noRemoveLog) { this.noRemoveLog = noRemoveLog; } private boolean noRemoveLog = false; /** * テーブルのグリッド色.
* * @return テーブルのグリッド色 */ public Color getGridColor() { return gridColor; } public void setGridColor(Color gridColor) { if (gridColor == null) { throw new IllegalArgumentException(); } this.gridColor = gridColor; } private Color gridColor = Color.gray; /** * カラーダイアログの値が変更されたら、自動的にプレビューを更新するか? * * @return カラーダイアログの値が変更されたら、自動的にプレビューを更新する場合はtrue (デフォルトはtrue) */ public boolean isEnableAutoColorChange() { return enableAutoColorChange; } public void setEnableAutoColorChange(boolean enableAutoColorChange) { this.enableAutoColorChange = enableAutoColorChange; } private boolean enableAutoColorChange = true; public void setAuthorEditConflictBgColor(Color authorEditConflictBgColor) { if (authorEditConflictBgColor == null) { throw new IllegalArgumentException(); } this.authorEditConflictBgColor = authorEditConflictBgColor; } /** * パーツの作者編集時に複数作者を選択した場合のに入力ボックスの背景色 * * @return 背景色 */ public Color getAuthorEditConflictBgColor() { return authorEditConflictBgColor; } Color authorEditConflictBgColor = Color.yellow; public void setMainFrameMaxWidth(int width) { this.mainFrameMaxWidth = width; } /** * メインフレームの初期表示時の最大幅 * * @return メインフレームの初期表示時の最大幅 */ public int getMainFrameMaxWidth() { return mainFrameMaxWidth; } private int mainFrameMaxWidth = 800; public void setMainFrameMaxHeight(int height) { this.mainFrameMaxHeight = height; } /** * メインフレームの初期表示時の最大高さ * * @return メインフレームの初期表示時の最大高さ */ public int getMainFrameMaxHeight() { return mainFrameMaxHeight; } private int mainFrameMaxHeight = 600; /** * カラーダイアログで存在しないレイヤーをディセーブルにしない. * * @return ディセーブルにしない場合はtrue */ public boolean isNotDisableLayerTab() { return notDisableLayerTab; } public void setNotDisableLayerTab(boolean notDisableLayerTab) { this.notDisableLayerTab = notDisableLayerTab; } private boolean notDisableLayerTab; /** * ログを消去する日数.
* この指定日を経過した古いログは削除される.
* 0の場合は削除されない. * * @return */ public long getPurgeLogDays() { return purgeLogDays; } public void setPurgeLogDays(long purgeLogDays) { this.purgeLogDays = purgeLogDays; } private long purgeLogDays = 10; public String getPartsColorGroupPattern() { return partsColorGroupPattern; } public void setPartsColorGroupPattern(String pattern) { if (pattern != null && pattern.trim().length() > 0) { Pattern.compile(pattern); } partsColorGroupPattern = pattern; } private String partsColorGroupPattern = "^.*\\(@\\).*$"; private Color selectPanelTitleColor = Color.BLUE; public Color getSelectPanelTitleColor() { return selectPanelTitleColor; } public void setSelectPanelTitleColor(Color color) { if (color == null) { throw new IllegalArgumentException(); } selectPanelTitleColor = color; } private boolean enableAutoShrinkPanel; public boolean isEnableAutoShrinkPanel() { return enableAutoShrinkPanel; } public void setEnableAutoShrinkPanel(boolean enableAutoShrinkPanel) { this.enableAutoShrinkPanel = enableAutoShrinkPanel; } public boolean isDisableWatchDirIfNotWritable() { return disableWatchDirIfNotWritable; } public void setDisableWatchDirIfNotWritable(boolean disableWatchDirIfNotWritable) { this.disableWatchDirIfNotWritable = disableWatchDirIfNotWritable; } private boolean disableWatchDirIfNotWritable = true; public void setEnablePNGSupportForWindows(boolean enablePNGSupportForWindows) { this.enablePNGSupportForWindows = enablePNGSupportForWindows; } public boolean isEnablePNGSupportForWindows() { return enablePNGSupportForWindows; } private boolean enablePNGSupportForWindows = true; /** * 画像表示(通常モード)でオプティマイズを有効にする最大倍率. */ private double renderingOptimizeThresholdForNormal = 2.; public void setRenderingOptimizeThresholdForNormal( double renderingOptimizeThresholdForNormal) { this.renderingOptimizeThresholdForNormal = renderingOptimizeThresholdForNormal; } public double getRenderingOptimizeThresholdForNormal() { return renderingOptimizeThresholdForNormal; } /** * 画像表示(チェックモード)でオプティマイズを有効にする最大倍率. */ private double renderingOptimizeThresholdForCheck = 0.; public void setRenderingOptimizeThresholdForCheck( double renderingOptimizeThresholdForCheck) { this.renderingOptimizeThresholdForCheck = renderingOptimizeThresholdForCheck; } public double getRenderingOptimizeThresholdForCheck() { return renderingOptimizeThresholdForCheck; } /** * バイキュービックをサポートする場合 */ private boolean enableInterpolationBicubic = true; public void setEnableInterpolationBicubic(boolean enableInterpolationBicubic) { this.enableInterpolationBicubic = enableInterpolationBicubic; } public boolean isEnableInterpolationBicubic() { return enableInterpolationBicubic; } /** * 事前定義済みの倍率候補.
*/ private String predefinedZoomRanges = "20, 50, 80, 100, 120, 150, 200, 300, 400, 800"; public String getPredefinedZoomRanges() { return predefinedZoomRanges; } public void setPredefinedZoomRanges(String predefinedZoomRanges) { this.predefinedZoomRanges = predefinedZoomRanges; } /** * ズームパネルを初期状態で表示するか? */ private boolean enableZoomPanel = true; public boolean isEnableZoomPanel() { return enableZoomPanel; } public void setEnableZoomPanel(boolean enableZoomPanel) { this.enableZoomPanel = enableZoomPanel; } /** * ズームパネルをアクティブにする下部範囲 */ private int zoomPanelActivationArea = 30; public int getZoomPanelActivationArea() { return zoomPanelActivationArea; } public void setZoomPanelActivationArea(int zoomPanelActivationArea) { this.zoomPanelActivationArea = zoomPanelActivationArea; } /** * レンダリングヒントを使用するか? */ private boolean enableRenderingHints = true; public void setEnableRenderingHints(boolean enableRenderingHints) { this.enableRenderingHints = enableRenderingHints; } public boolean isEnableRenderingHints() { return enableRenderingHints; } /** * グリッド描画とマスク */ private int drawGridMask = 2; public int getDrawGridMask() { return drawGridMask; } public void setDrawGridMask(int drawGridMask) { this.drawGridMask = drawGridMask & 0x03; } private int previewGridColor = 0x7f7f0000; public int getPreviewGridColor() { return previewGridColor; } public void setPreviewGridColor(int previewGridColor) { this.previewGridColor = previewGridColor; } private int previewGridSize = 20; public int getPreviewGridSize() { return previewGridSize; } public void setPreviewGridSize(int previewGridSize) { this.previewGridSize = previewGridSize; } /** * チェックモード時の余白サイズ(片側) */ private int previewUnfilledSpaceForCheckMode = 0; public int getPreviewUnfilledSpaceForCheckMode() { return previewUnfilledSpaceForCheckMode; } public void setPreviewUnfilledSpaceForCheckMode( int previewUnfilledSpaceForCheckMode) { this.previewUnfilledSpaceForCheckMode = previewUnfilledSpaceForCheckMode; } /** * チェックモードでツールチップを表示するか? */ private boolean enableCheckInfoTooltip = true; public boolean isEnableCheckInfoTooltip() { return enableCheckInfoTooltip; } public void setEnableCheckInfoTooltip(boolean enableCheckInfoTooltip) { this.enableCheckInfoTooltip = enableCheckInfoTooltip; } /** * ホイールによるスクロールの単位.
*/ private int wheelScrollUnit = 10; public int getWheelScrollUnit() { return wheelScrollUnit; } public void setWheelScrollUnit(int wheelScrollUnit) { this.wheelScrollUnit = wheelScrollUnit; } /** * 壁紙にオフスクリーン描画を使用するか?.
* (あまり劇的なパフォーマンス効果はない.) */ private boolean enableOffscreenWallpaper = false; public boolean isEnableOffscreenWallpaper() { return enableOffscreenWallpaper; } public void setEnableOffscreenWallpaper(boolean enableOffscreenWallpaper) { this.enableOffscreenWallpaper = enableOffscreenWallpaper; } /** * 壁紙のオフスクリーンの既定サイズ. */ private int offscreenWallpaperSize = 300; public int getOffscreenWallpaperSize() { return offscreenWallpaperSize; } public void setOffscreenWallpaperSize(int offscreenWallpaperSize) { this.offscreenWallpaperSize = offscreenWallpaperSize; } /** * ランダム選択パーツの履歴数 */ private int randomChooserMaxHistory = 10; public int getRandomChooserMaxHistory() { return randomChooserMaxHistory; } public void setRandomChooserMaxHistory(int randomChooserMaxHistory) { this.randomChooserMaxHistory = randomChooserMaxHistory; } /** * デフォルトのフォントサイズ、0以下の場合はシステム既定のまま */ private int defaultFontSize = 12; public int getDefaultFontSize() { return defaultFontSize; } public void setDefaultFontSize(int defaultFontSize) { this.defaultFontSize = defaultFontSize; } /** * デフォルトのフォントファミリー、カンマ区切り */ private String fontPriority = "Lucida Grande"; public String getFontPriority() { return fontPriority; } public void setFontPriority(String fontPriority) { this.fontPriority = fontPriority; } } CharacterManaJ/src/charactermanaj/model/RecommendationURL.java0000644000175000017500000000255112560206305024617 0ustar paulliupaulliupackage charactermanaj.model; import java.io.Serializable; /** * お勧めリンク * @author seraphy */ public class RecommendationURL implements Serializable, Cloneable { private static final long serialVersionUID = 3568122645473390201L; private String displayName; private String url; @Override public RecommendationURL clone() { try { return (RecommendationURL) super.clone(); } catch (CloneNotSupportedException ex) { throw new RuntimeException(ex); } } @Override public int hashCode() { if (url == null) { return 0; } return url.hashCode(); } @Override public boolean equals(Object obj) { if (obj == this) { return true; } if (obj != null && obj instanceof RecommendationURL) { RecommendationURL o = (RecommendationURL) obj; return (displayName == null ? (o.displayName == null) : displayName.equals(o.displayName)) && (url == null ? (o.url == null) : url.equals(o.url)); } return false; } public String getDisplayName() { return displayName; } public void setDisplayName(String displayName) { this.displayName = displayName; } public String getUrl() { return url; } public void setUrl(String url) { this.url = url; } @Override public String toString() { return "displayName=" + displayName + "/url=" + url; } } CharacterManaJ/src/charactermanaj/model/PartsAuthorInfo.java0000644000175000017500000000110612560206305024353 0ustar paulliupaulliupackage charactermanaj.model; import java.io.Serializable; /** * パーツの作者情報 * @author seraphy */ public class PartsAuthorInfo implements Serializable { private static final long serialVersionUID = -5308326806956816225L; private String author; private String homePage; public String getAuthor() { return author; } public String getHomePage() { return homePage; } public void setAuthor(String author) { this.author = author; } public void setHomePage(String homePage) { this.homePage = homePage; } } CharacterManaJ/src/charactermanaj/model/ColorInfo.java0000644000175000017500000000463312560206305023165 0ustar paulliupaulliupackage charactermanaj.model; import java.io.Serializable; import charactermanaj.graphics.filters.ColorConvertParameter; /** * カラーグループおよび連動フラグを含む色情報.
* @author seraphy */ public class ColorInfo implements Serializable, Cloneable { private static final long serialVersionUID = 2448550538711608223L; private ColorConvertParameter colorParameter = new ColorConvertParameter(); private boolean syncColorGroup = false; private ColorGroup colorGroup = ColorGroup.NA; @Override public ColorInfo clone() { ColorInfo colorInfo; try { colorInfo = (ColorInfo) super.clone(); } catch (CloneNotSupportedException ex) { throw new RuntimeException(ex.getMessage(), ex); } colorInfo.colorParameter = (ColorConvertParameter) this.colorParameter.clone(); return colorInfo; } @Override public int hashCode() { return colorParameter.hashCode() ^ colorGroup.hashCode(); } @Override public boolean equals(Object obj) { if (obj == this) { return true; } if (obj != null && obj instanceof ColorInfo) { ColorInfo o = (ColorInfo) obj; return colorGroup.equals(o.colorGroup) && syncColorGroup == o.syncColorGroup && colorParameter.equals(o.colorParameter); } return false; } public ColorConvertParameter getColorParameter() { return colorParameter; } public void setColorParameter(ColorConvertParameter colorParameter) { if (colorParameter == null) { this.colorParameter = new ColorConvertParameter(); } else { this.colorParameter = colorParameter; } } public boolean isSyncColorGroup() { return syncColorGroup; } public void setSyncColorGroup(boolean syncColorGroup) { this.syncColorGroup = syncColorGroup; } public ColorGroup getColorGroup() { return colorGroup; } public void setColorGroup(ColorGroup colorGroup) { if (colorGroup == null) { this.colorGroup = ColorGroup.NA; } else { this.colorGroup = colorGroup; } } @Override public String toString() { StringBuilder buf = new StringBuilder(); buf.append(getClass().getSimpleName() + "@" + Integer.toHexString(System.identityHashCode(this))); buf.append("("); buf.append("(colorGroup: " + colorGroup + "(sync: " + syncColorGroup + ")), "); buf.append("(colorParameter: " + colorParameter + ")"); buf.append(")"); return buf.toString(); } } CharacterManaJ/src/charactermanaj/model/PartsSpecDecorater.java0000644000175000017500000000023712560206305025024 0ustar paulliupaulliupackage charactermanaj.model; public interface PartsSpecDecorater { void decoratePartsSpec(PartsIdentifier partsIdentifier, PartsSpec partsSpec); } CharacterManaJ/src/charactermanaj/model/OrderedMap.java0000644000175000017500000001235112560206305023311 0ustar paulliupaulliupackage charactermanaj.model; import java.io.IOException; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.io.Serializable; import java.util.AbstractList; import java.util.AbstractMap; import java.util.AbstractSet; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; /** * 定義順でのリストアクセスと、キーによる一意アクセスの双方を格納にするマップ兼リスト.
* 元となるコレクションからキーを分離し、キーでアクセス可能にする.
* マップへのvalues()等のコレクションアクセスでは元のコレクションと同じ定義順で返される.
* より明確には{{@link #asList()}を使うとリストとしてアクセスすることができる.
* 読み込み専用で、書き込みはできない.
* * @author seraphy * * @param データに含まれるキーの型 * @param データの型 */ public class OrderedMap extends AbstractMap implements Serializable { /** * シリアライズバージョンID. */ private static final long serialVersionUID = 5049488493598426918L; /** * 空を示す定数インスタンス */ public static final OrderedMap EMPTY_MAP = new OrderedMap(); @SuppressWarnings("unchecked") public static final OrderedMap emptyMap() { return (OrderedMap) EMPTY_MAP; } public interface KeyDetector { K getKey(V data); } protected static class OrderedMapEntry implements Map.Entry, Serializable { private static final long serialVersionUID = 5111249402034089224L; private final K key; private final V data; protected OrderedMapEntry(K key, V data) { this.key = key; this.data = data; } public K getKey() { return key; } public V getValue() { return data; } public V setValue(V arg0) { throw new UnsupportedOperationException(); } } /** * リストとしてのエントリ */ private ArrayList> entries = new ArrayList>(); /** * マップアクセス用.
* シリアライズ時はスキップされ、デシリアライズ時にリストから復元される.
*/ private transient HashMap entryMap = new HashMap(); /** * デシリアライズ.
* @param stream * @throws IOException * @throws ClassNotFoundException */ private void readObject(ObjectInputStream stream) throws IOException, ClassNotFoundException { stream.defaultReadObject(); entryMap = new HashMap(); for (Map.Entry entry : entries) { entryMap.put(entry.getKey(), entry.getValue()); } } /** * シリアライズ * @param stream * @throws IOException */ private void writeObject(ObjectOutputStream stream) throws IOException { stream.defaultWriteObject(); } /** * 空のコレクションを作成する. */ protected OrderedMap() { super(); } /** * 元となるコレクションをコピーし、そのコレクションの各データのキーを分離しマップアクセス可能にする.
* @param datas 元となるコレクション * @param keyDetector コレクションの各要素からキーを分離するためのインターフェイス */ public OrderedMap(Collection datas, KeyDetector keyDetector) { if (datas == null || keyDetector == null) { throw new IllegalArgumentException(); } for (V data : datas) { K key = keyDetector.getKey(data); if (key == null) { throw new IllegalArgumentException("null key: " + data); } entries.add(new OrderedMapEntry(key, data)); entryMap.put(key, data); } if (entries.size() != entryMap.size()) { throw new IllegalArgumentException("duplicate-key"); } } @Override public Set> entrySet() { return new AbstractSet>() { @Override public int size() { return entries.size(); } @Override public Iterator> iterator() { final Iterator> ite = entries.iterator();; return new Iterator>() { public boolean hasNext() { return ite.hasNext(); } public java.util.Map.Entry next() { return ite.next(); } public void remove() { throw new UnsupportedOperationException(); } }; } }; } /** * マップの値を定義順のリストとして扱えるようにリストインターフェイスで返す.
* 読み込み専用です.
* @return マップの値のリスト */ public List asList() { return new AbstractList() { @Override public int size() { return entries.size(); } @Override public V get(int index) { return entries.get(index).getValue(); } }; } @Override public V get(Object key) { return entryMap.get(key); } @Override public int size() { return entries.size(); } } CharacterManaJ/src/charactermanaj/model/io/0000755000175000017500000000000012560206305021031 5ustar paulliupaulliuCharacterManaJ/src/charactermanaj/model/io/PartsImageCollectionParser.java0000644000175000017500000000664112560206305027130 0ustar paulliupaulliupackage charactermanaj.model.io; import java.util.List; import java.util.Map; import charactermanaj.graphics.filters.ColorConvertParameter; import charactermanaj.graphics.io.ImageResource; import charactermanaj.model.ColorInfo; import charactermanaj.model.Layer; import charactermanaj.model.PartsColorInfo; import charactermanaj.model.PartsFiles; import charactermanaj.model.PartsIdentifier; import charactermanaj.model.PartsSet; import charactermanaj.model.PartsSpec; import charactermanaj.model.PartsSpecResolver; /** * パーツセットから複合画像イメージを生成するために必要なイメージリソースを抽出する. * @author seraphy * */ public class PartsImageCollectionParser { /** * 抽出された複合画像イメージの個々のイメージソースと、カラー情報を受け取るハンドラ.
* @author seraphy */ public interface PartsImageCollectionHandler { /** * 個々のイメージリソースとカラー情報を受け取るハンドラ.
* * @param partsIdentifier 対象のパーツ識別子 * @param layer パーツのレイヤー * @param imageResource パーツのレイヤーの画像リソース * @param param カラー情報、設定されていない場合はnull */ void detectImageSource(PartsIdentifier partsIdentifier, Layer layer, ImageResource imageResource, ColorConvertParameter param); } /** * パーツ設定を解決するためのインターフェイス */ protected PartsSpecResolver partsSpecResolver; /** * パーツ設定のリゾルバを指定して構築する * @param partsSpecResolver パーツ設定のリゾルバ */ public PartsImageCollectionParser(PartsSpecResolver partsSpecResolver) { if (partsSpecResolver == null) { throw new IllegalArgumentException("resolver is null"); } this.partsSpecResolver = partsSpecResolver; } public PartsSpecResolver getPartsSpecResolver() { return this.partsSpecResolver; } /** * パーツセットを指定して複合画像を生成するために必要なイメージソースおよびカラー設定を解決する.
* protectedなので派生クラスで呼び出すか、publicに昇格させる.
* @param partsSet パーツセット */ public void parse(PartsSet partsSet, PartsImageCollectionHandler listener) { if (listener == null) { throw new IllegalArgumentException("listener is null"); } if (partsSet == null) { throw new IllegalArgumentException("PartsSet is null"); } for (List partsIdentifiers : partsSet.values()) { for (PartsIdentifier partsIdentifier : partsIdentifiers) { PartsColorInfo partsColorInfo = partsSet.getColorInfo(partsIdentifier); PartsSpec partsSpec = partsSpecResolver.getPartsSpec(partsIdentifier); if (partsSpec != null) { PartsFiles partsFiles = partsSpec.getPartsFiles(); for (Map.Entry entry : partsFiles.entrySet()) { Layer layer = entry.getKey(); ImageResource file = entry.getValue(); ColorConvertParameter param = null; if (partsColorInfo != null) { ColorInfo colorInfo = partsColorInfo.get(layer); if (colorInfo != null) { param = colorInfo.getColorParameter(); } } listener.detectImageSource(partsIdentifier, layer, file, param); } } } } } } CharacterManaJ/src/charactermanaj/model/io/CharacterDataArchiveFile.java0000644000175000017500000001103412560206305026463 0ustar paulliupaulliupackage charactermanaj.model.io; import java.awt.image.BufferedImage; import java.io.IOException; import java.util.Collection; import charactermanaj.model.CharacterData; import charactermanaj.model.PartsManageData; import charactermanaj.model.io.AbstractCharacterDataArchiveFile.PartsImageContent; /** * アーカイブ形式のファイルを読み取るためのインターフェイス.
* @author seraphy */ public interface CharacterDataArchiveFile { /** * クローズする. * @throws IOException */ void close() throws IOException; /** * コンテンツルートへのプレフィックスを取得する.
* アーカイブの実際のルートに単一のフォルダしかない場合、そのフォルダがルートプレフィックスとなる.
* アーカイブの実際のルートに複数のフォルダがあるか、ファイルがある場合、ルートプレフィックスは空文字である.
* @return コンテンツルートへのプレフィックス */ String getRootPrefix(); /** * 指定したコンテンツが存在するか? * @param name コンテンツ名 * @return 存在すればtrue、存在しなければfalse */ boolean hasContent(String name); /** * キャラクター定義を読み込む.
* アーカイブに存在しなければnull * @return キャラクター定義、もしくはnull * @throws IOException 読み取りに失敗した場合 */ CharacterData readCharacterData() throws IOException; /** * お気に入りを読み込みキャラクター定義に追加する.
* アーカイブにお気に入りが存在しなければ何もしない.
* @param characterData キャラクター定義(お気に入りが追加される) * @throws IOException 読み取りに失敗した場合 */ void readFavorites(CharacterData characterData) throws IOException; /** * キャラクター定義をINIファイルより読み取る.
* character.iniが存在しなければnull.
* 「キャラクターなんとか機」の設定ファイルを想定している.
* @return キャラクター定義、もしくはnull * @throws IOException 読み取りに失敗した場合 */ CharacterData readCharacterINI() throws IOException; /** * サンプルピクチャを読み込む.
* アーカイブに存在しなければnull. * @return サンプルピクチャ、もしくはnull * @throws IOException 読み取りに失敗した場合 */ BufferedImage readSamplePicture() throws IOException; /** * アーカイブにある「readme.txt」、もしくは「readme」というファイルをテキストファイルとして読み込む.
* readme.txtが優先される。ともに存在しない場合はnull. * @return テキスト、もしくはnull * @throws 読み込みに失敗した場合 */ String readReadMe() throws IOException; /** * ファイルをテキストとして読み取り、返す.
* UTF-16LE/BE/UTF-8についてはBOMにより判定する.
* BOMがない場合はUTF-16/8ともに判定できない.
* BOMがなければMS932もしくはEUC_JPであると仮定して読み込む.
* @param name コンテンツ名 * @return テキスト、コンテンツが存在しない場合はnull * @throws IOException 読み込みに失敗した場合 */ String readTextFile(String name) throws IOException; /** * アーカイブに含まれる、キャラクター定義と同じフォルダをもつpngファイルからパーツイメージを取得する.
* パーツの探索は引数で指定したキャラクター定義によって行われます.
* @param characterData インポート先のキャラクターデータ、フォルダ名などを判別するため。nullの場合は空のマップを返す.
* @param newly 新規インポート用であるか?(新規でない場合は引数で指定したキャラクターセットと同じパーツは読み込まれない) * @return パーツイメージコンテンツのコレクション、なければ空 */ Collection getPartsImageContents(CharacterData characterData, boolean newly); /** * アーカイブに含まれるparts-info.xmlを読み込み返す.
* 存在しなければ空のインスタンスを返す.
* @return パーツ管理データ * @throws IOException */ PartsManageData getPartsManageData() throws IOException; } CharacterManaJ/src/charactermanaj/model/io/AbstractCharacterDataFileWriter.java0000644000175000017500000001213012560206305030040 0ustar paulliupaulliupackage charactermanaj.model.io; import java.awt.image.BufferedImage; import java.io.File; import java.io.IOException; import java.util.Map; import java.util.Properties; import java.util.logging.Level; import java.util.logging.Logger; import charactermanaj.model.CharacterData; import charactermanaj.model.PartsIdentifier; import charactermanaj.model.PartsSpec; public abstract class AbstractCharacterDataFileWriter implements CharacterDataWriter { /** * ロガー */ private static final Logger logger = Logger.getLogger(AbstractCharacterDataFileWriter.class.getName()); protected File outFile; protected File tmpFile; protected Exception occureException; protected AbstractCharacterDataFileWriter(File outFile) throws IOException { if (outFile == null) { throw new IllegalArgumentException(); } if (outFile.exists()) { if (!outFile.canWrite()) { throw new IOException("not writable: " + outFile); } } File tmpFile = new File(outFile.getPath() + ".tmp"); this.tmpFile = tmpFile; this.outFile = outFile; } public void writeExportProp(Properties prop) throws IOException { if (prop == null) { throw new IllegalArgumentException(); } try { internalWriteExportProp(prop); } catch (IOException ex) { occureException = ex; throw ex; } catch (Exception ex) { occureException = ex; IOException ex2 = new IOException("write characterdata failed."); ex2.initCause(ex); throw ex2; } } protected abstract void internalWriteExportProp(Properties prop) throws IOException; public void writeCharacterData(CharacterData characterData) throws IOException { if (characterData == null) { throw new IllegalArgumentException(); } try { internalWriteCharacterData(characterData); } catch (IOException ex) { occureException = ex; throw ex; } catch (Exception ex) { occureException = ex; IOException ex2 = new IOException("write characterdata failed."); ex2.initCause(ex); throw ex2; } } protected abstract void internalWriteCharacterData(CharacterData characterData) throws IOException; public void writeTextUTF16LE(String name, String contents) throws IOException { if (name == null) { throw new IllegalArgumentException(); } try { internalWriteTextUTF16LE(name, contents); } catch (IOException ex) { occureException = ex; throw ex; } catch (Exception ex) { occureException = ex; IOException ex2 = new IOException("internalWriteTextUTF16 failed."); ex2.initCause(ex); throw ex2; } } protected abstract void internalWriteTextUTF16LE(String name, String contents) throws IOException; public void writeSamplePicture(BufferedImage samplePicture) throws IOException { if (samplePicture == null) { throw new IllegalArgumentException(); } try { internalWriteSamplePicture(samplePicture); } catch (IOException ex) { occureException = ex; throw ex; } catch (Exception ex) { occureException = ex; IOException ex2 = new IOException("write sample picture failed."); ex2.initCause(ex); throw ex2; } } protected abstract void internalWriteSamplePicture(BufferedImage samplePicture) throws IOException; public void writePartsImages(Map partsImages) throws IOException { if (partsImages == null) { throw new IllegalArgumentException(); } try { internalWritePartsImages(partsImages); } catch (IOException ex) { occureException = ex; throw ex; } catch (Exception ex) { occureException = ex; IOException ex2 = new IOException("write parts images failed."); ex2.initCause(ex); throw ex2; } } protected abstract void internalWritePartsImages(Map partsImages) throws IOException; public void writePartsManageData(Map partsImages) throws IOException { if (partsImages == null) { throw new IllegalArgumentException(); } try { internalWritePartsManageData(partsImages); } catch (IOException ex) { occureException = ex; throw ex; } catch (Exception ex) { occureException = ex; IOException ex2 = new IOException("write parts images failed."); ex2.initCause(ex); throw ex2; } } protected abstract void internalWritePartsManageData(Map partsImages) throws IOException; public void close() throws IOException { try { internalClose(); if (outFile.exists()) { if (!outFile.delete()) { throw new IOException("old file can't delete. " + outFile); } } } catch (Exception ex) { if (occureException == null) { occureException = ex; } } if (occureException != null) { if (!tmpFile.delete()) { logger.log(Level.WARNING, "temporary file can't delete. " + tmpFile); } return; } if (!tmpFile.renameTo(outFile)) { throw new IOException("rename failed. " + tmpFile); } } protected abstract void internalClose() throws IOException; } CharacterManaJ/src/charactermanaj/model/io/CharacterDataDirectoryFile.java0000644000175000017500000001101012560206305027040 0ustar paulliupaulliupackage charactermanaj.model.io; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.net.URI; import java.util.Collection; import java.util.Collections; import java.util.logging.Level; import java.util.logging.Logger; import charactermanaj.model.CharacterData; import charactermanaj.util.FileNameNormalizer; /** * ディレクトリをアーカイブと見立てる * * @author seraphy */ public class CharacterDataDirectoryFile extends AbstractCharacterDataArchiveFile { /** * ロガー */ private static final Logger logger = Logger.getLogger(CharacterDataDirectoryFile.class.getName()); /** * 対象ディレクトリ */ protected File baseDir; /** * ディレクトリ上のファイルコンテンツ * * @author seraphy */ protected static class DirFileContent implements FileContent { /** * ディレクトリ名 + ファイル名からなるエントリ名.
* エントリ名の区切り文字は常に「/」とする.
*/ private String entryName; /** * 実際のファイルへのパス */ private File entry; protected DirFileContent(File entry, String entryName) { this.entry = entry; this.entryName = entryName; } public String getEntryName() { return entryName; } public long lastModified() { return entry.lastModified(); } public InputStream openStream() throws IOException { return new FileInputStream(entry); } } /** * アーカイブファイルをベースとしたURIを返す.
* * @param name * コンテンツ名 * @return URI * @throws IOException * URIを生成できない場合 */ protected URI getContentURI(String name) throws IOException { return new File(baseDir, name).toURI(); } @Override public Collection getPartsImageContents( CharacterData characterData, boolean newly) { if (!newly && isOverlapped(characterData)) { // 既存のプロファイルへのインポートで指定されたインポートもととなるディレクトリが // 既存のプロファイルのディレクトリと重なっていれば自分自身のインポートであるとして // 空のパーツリストを返す. return Collections.emptyList(); } return super.getPartsImageContents(characterData, newly); } /** * このディレクトリに対してターゲットプロファイルのディレクトリがかぶっているか? つまり、ターゲット自身のディレクトリをソースとしていないか? * * @param characterData * ソースプロファイル * @return 被っている場合はtrue、被っていない場合はfalse */ protected boolean isOverlapped(CharacterData characterData) { if (characterData == null) { return false; } URI docBase = characterData.getDocBase(); if (docBase == null || !"file".equals(docBase.getScheme())) { return false; } String folderPlace = File.separator; String basePath = new File(docBase).getParent() + folderPlace; String sourcePath = baseDir.getPath() + folderPlace; // インポートもとディレクトリがインポート先のキャラクター定義のディレクトリを含むか? boolean result = basePath.contains(sourcePath); logger.log(Level.FINE, "checkOverlapped: " + basePath + " * " + sourcePath + " :" + result); return result; } public void close() throws IOException { // ディレクトリなのでクローズ処理は必要ない. } public CharacterDataDirectoryFile(File file) throws IOException { super(file); baseDir = file; load(baseDir, ""); searchRootPrefix(); } private void load(File dir, String prefix) { if (!dir.exists() || !dir.isDirectory()) { // ディレクトリでなければ何もせず戻る return; } // ファイル名をノーマライズする FileNameNormalizer normalizer = FileNameNormalizer.getDefault(); File[] files = dir.listFiles(); if (files != null) { for (File file : files) { String name = normalizer.normalize(file.getName()); String entryName = prefix + name; if (file.isDirectory()) { // エントリ名の区切り文字は常に「/」とする. (ZIP/JARのentry互換のため) load(file, entryName + "/"); } else { addEntry(new DirFileContent(file, entryName)); } } } } } CharacterManaJ/src/charactermanaj/model/io/CharacterDataZipArchiveFile.java0000644000175000017500000000257312560206305027156 0ustar paulliupaulliupackage charactermanaj.model.io; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.util.Enumeration; import org.apache.tools.zip.ZipEntry; import org.apache.tools.zip.ZipFile; import charactermanaj.model.AppConfig; public class CharacterDataZipArchiveFile extends AbstractCharacterDataArchiveFile { protected ZipFile zipFile; protected class ZipFileContent implements FileContent { private ZipEntry entry; protected ZipFileContent(ZipEntry entry) { this.entry = entry; } public String getEntryName() { return entry.getName(); } public long lastModified() { return entry.getTime(); } public InputStream openStream() throws IOException { return zipFile.getInputStream(entry); } } public void close() throws IOException { zipFile.close(); } public CharacterDataZipArchiveFile(File file) throws IOException { super(file); AppConfig appConfig = AppConfig.getInstance(); String encoding = appConfig.getZipNameEncoding(); zipFile = new ZipFile(file, encoding); load(); } private void load() { @SuppressWarnings("unchecked") Enumeration enm = zipFile.getEntries(); while (enm.hasMoreElements()) { ZipEntry entry = enm.nextElement(); addEntry(new ZipFileContent(entry)); } searchRootPrefix(); } } CharacterManaJ/src/charactermanaj/model/io/CharacterDataIniReader.java0000644000175000017500000001300012560206305026137 0ustar paulliupaulliupackage charactermanaj.model.io; import java.awt.Dimension; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.UnsupportedEncodingException; import java.util.HashMap; import java.util.Map; import java.util.logging.Level; import java.util.logging.Logger; import charactermanaj.model.CharacterData; import charactermanaj.model.PartsCategory; import charactermanaj.model.PartsIdentifier; import charactermanaj.model.PartsSet; import charactermanaj.model.io.CharacterDataDefaultProvider.DefaultCharacterDataVersion; /** * character.iniファイルを読み込むためのクラス. * * @author seraphy */ public class CharacterDataIniReader { /** * ロガー */ private static final Logger logger = Logger .getLogger(CharacterDataIniReader.class.getName()); /** * character.iniファイルから、キャラクター定義を生成します.
* docBaseは設定されていないため、戻り値に設定する必要があります.
* * @param is * character.iniの入力ストリーム * @param version * デフォルトキャラクターセットのバージョン * @return キャラクターデータ * @throws IOException * 読み取りに失敗した場合 */ public CharacterData readCharacterDataFromIni(InputStream is, DefaultCharacterDataVersion version) throws IOException { if (is == null || version == null) { throw new IllegalArgumentException(); } CharacterDataDefaultProvider defProv = new CharacterDataDefaultProvider(); CharacterData cd = defProv.createDefaultCharacterData(version); // イメージサイズ int siz_x = 0; int siz_y = 0; // パーツセット HashMap plainPartsSet = new HashMap(); try { BufferedReader rd; try { rd = new BufferedReader(new InputStreamReader(is, "MS932")); // SJISで読み取る. } catch (UnsupportedEncodingException ex) { logger.log(Level.SEVERE, "SJIS encoded file cannot be read.", ex); rd = new BufferedReader(new InputStreamReader(is)); // システムデフォルトで読み込む } try { String line; int sectionMode = 0; while ((line = rd.readLine()) != null) { line = line.trim(); if (line.length() == 0) { continue; } if (line.startsWith("[")) { // セクションの判定 if (line.toLowerCase().equals("[size]")) { // Sizeセクション sectionMode = 1; } else if (line.toLowerCase().equals("[parts]")) { // Partsセクション sectionMode = 2; } else { // それ以外のセクションなのでモードをリセット. // 色情報のセクション「Color」は現在のところサポートしていない. sectionMode = 0; } } else { int eqpos = line.indexOf('='); String key, val; if (eqpos >= 0) { // キーは小文字に揃える (大小を無視して比較できるようにするため) key = line.substring(0, eqpos).toLowerCase().trim(); val = line.substring(eqpos + 1); } else { key = line.toLowerCase().trim(); val = ""; } if (sectionMode == 1) { // Sizeセクション try { if (key.equals("size_x")) { siz_x = Integer.parseInt(val); } else if (key.equals("size_y")) { siz_y = Integer.parseInt(val); } } catch (RuntimeException ex) { logger.log(Level.WARNING, "character.ini invalid. key=" + key + "/val=" + val, ex); // 変換できないものは無視する. } } else if (sectionMode == 2) { // Partsセクション if (key.length() > 0) { plainPartsSet.put(key, val); } } } } } finally { rd.close(); } } catch (IOException ex) { // エラーが発生したら、character.iniは無かったことにして続ける. logger.log(Level.WARNING, "character.ini invalid.", ex); return null; } finally { try { is.close(); } catch (IOException ex) { logger.log(Level.SEVERE, "can't close file.", ex); } } // イメージサイズの設定 if (siz_x > 0 && siz_y > 0) { cd.setImageSize(new Dimension(siz_x, siz_y)); } // パーツセットを構築する. boolean existsPartsetParts = false; if (!plainPartsSet.isEmpty()) { PartsSet partsSet = new PartsSet("default", "default", true); for (Map.Entry entry : plainPartsSet.entrySet()) { String categoryId = entry.getKey(); String partsName = entry.getValue(); PartsCategory partsCategory = cd.getPartsCategory(categoryId); if (partsCategory != null) { PartsIdentifier partsIdentifier; if (partsName == null || partsName.length() == 0) { partsIdentifier = null; } else { partsIdentifier = new PartsIdentifier(partsCategory, partsName, partsName); existsPartsetParts = true; } partsSet.appendParts(partsCategory, partsIdentifier, null); } } if (!partsSet.isEmpty() && existsPartsetParts) { // パーツセットが空でなく、 // なにかしらのパーツが登録されている場合のみパーツセットを登録する. cd.addPartsSet(partsSet); cd.setDefaultPartsSetId("default"); } } return cd; } } CharacterManaJ/src/charactermanaj/model/io/CharacterDataXMLWriter.java0000644000175000017500000005511412560206305026146 0ustar paulliupaulliupackage charactermanaj.model.io; import java.awt.Color; import java.io.IOException; import java.io.OutputStream; import java.io.OutputStreamWriter; import java.nio.charset.Charset; import java.util.Collection; import java.util.HashMap; import java.util.List; import java.util.Locale; import java.util.Map; import javax.xml.XMLConstants; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.ParserConfigurationException; import javax.xml.transform.OutputKeys; import javax.xml.transform.Transformer; import javax.xml.transform.TransformerConfigurationException; import javax.xml.transform.TransformerException; import javax.xml.transform.TransformerFactory; import javax.xml.transform.dom.DOMSource; import javax.xml.transform.stream.StreamResult; import org.w3c.dom.Attr; import org.w3c.dom.Document; import org.w3c.dom.Element; import charactermanaj.graphics.filters.ColorConv; import charactermanaj.graphics.filters.ColorConvertParameter; import charactermanaj.model.CharacterData; import charactermanaj.model.ColorGroup; import charactermanaj.model.ColorInfo; import charactermanaj.model.Layer; import charactermanaj.model.PartsCategory; import charactermanaj.model.PartsColorInfo; import charactermanaj.model.PartsIdentifier; import charactermanaj.model.PartsSet; import charactermanaj.model.RecommendationURL; /** * パーツ管理情報のXMLへの書き込み用クラス. * * @author seraphy */ public class CharacterDataXMLWriter { /** * キャラクター定義バージョン */ private static final String VERSION_SIG_1_0 = "1.0"; /** * キャラクター定義XMLファイルの名前空間 */ private static final String DEFAULT_NS = "http://charactermanaj.sourceforge.jp/schema/charactermanaj"; private final String NS; public CharacterDataXMLWriter() { this(DEFAULT_NS); } public CharacterDataXMLWriter(String ns) { this.NS = ns; } /** * キャラクターデータのXMLの書き込み. * * @param characterData * @param outstm * @throws IOException */ public void writeXMLCharacterData(CharacterData characterData, OutputStream outstm) throws IOException { if (outstm == null || characterData == null) { throw new IllegalArgumentException(); } Locale locale = Locale.getDefault(); String lang = locale.getLanguage(); Document doc; try { DocumentBuilderFactory factory = DocumentBuilderFactory .newInstance(); factory.setNamespaceAware(true); DocumentBuilder builder = factory.newDocumentBuilder(); doc = builder.newDocument(); } catch (ParserConfigurationException ex) { throw new RuntimeException("JAXP Configuration failed.", ex); } Element root = doc.createElementNS(NS, "character"); root.setAttribute("version", VERSION_SIG_1_0); root.setAttribute("xmlns:xml", XMLConstants.XML_NS_URI); root.setAttribute("xmlns:xsi", XMLConstants.W3C_XML_SCHEMA_INSTANCE_NS_URI); root.setAttribute("xsi:schemaLocation", NS + " character.xsd"); root.setAttribute("id", characterData.getId()); root.setAttribute("rev", characterData.getRev()); doc.appendChild(root); // name Element nodeName = doc.createElementNS(NS, "name"); Attr attrLang = doc.createAttributeNS(XMLConstants.XML_NS_URI, "lang"); attrLang.setValue(lang); nodeName.setAttributeNodeNS(attrLang); nodeName.setTextContent(characterData.getName()); root.appendChild(nodeName); // information String author = characterData.getAuthor(); String description = characterData.getDescription(); if ((author != null && author.length() > 0) || (description != null && description.length() > 0)) { Element nodeInfomation = doc.createElementNS(NS, "information"); if (author != null && author.length() > 0) { Element nodeAuthor = doc.createElementNS(NS, "author"); Attr attrNodeAuthorLang = doc.createAttributeNS( XMLConstants.XML_NS_URI, "lang"); attrNodeAuthorLang.setValue(lang); nodeAuthor.setAttributeNodeNS(attrNodeAuthorLang); nodeAuthor.setTextContent(author); nodeInfomation.appendChild(nodeAuthor); } if (description != null && description.length() > 0) { // 説明の改行コードはXML上ではLFとする. description = description.replace("\r\n", "\n"); description = description.replace("\r", "\n"); Element nodeDescription = doc .createElementNS(NS, "description"); Attr attrNodeDescriptionLang = doc.createAttributeNS( XMLConstants.XML_NS_URI, "lang"); attrNodeDescriptionLang.setValue(lang); nodeDescription.setAttributeNodeNS(attrNodeDescriptionLang); nodeDescription.setTextContent(description); nodeInfomation.appendChild(nodeDescription); } root.appendChild(nodeInfomation); } // size Element nodeSize = doc.createElementNS(NS, "image-size"); Element nodeWidth = doc.createElementNS(NS, "width"); nodeWidth.setTextContent(Integer.toString((int) characterData .getImageSize().getWidth())); Element nodeHeight = doc.createElementNS(NS, "height"); nodeHeight.setTextContent(Integer.toString((int) characterData .getImageSize().getHeight())); nodeSize.appendChild(nodeWidth); nodeSize.appendChild(nodeHeight); root.appendChild(nodeSize); // settings Element nodeSettings = doc.createElementNS(NS, "settings"); root.appendChild(nodeSettings); for (String settingsEntryName : characterData.getPropertyNames()) { String value = characterData.getProperty(settingsEntryName); if (value != null) { Element nodeEntry = doc.createElementNS(NS, "entry"); nodeEntry.setAttribute("key", settingsEntryName); nodeEntry.setTextContent(value); nodeSettings.appendChild(nodeEntry); } } // categories Element nodeCategories = doc.createElementNS(NS, "categories"); for (PartsCategory category : characterData.getPartsCategories()) { // category Element nodeCategory = doc.createElementNS(NS, "category"); nodeCategory.setAttribute("id", category.getCategoryId()); nodeCategory.setAttribute("multipleSelectable", category.isMultipleSelectable() ? "true" : "false"); // visible-rows Element nodeVisibleRows = doc.createElementNS(NS, "visible-rows"); nodeVisibleRows.setTextContent(Integer.toString(category .getVisibleRows())); nodeCategory.appendChild(nodeVisibleRows); // category name Element nodeCategoryName = doc.createElementNS(NS, "display-name"); Attr attrCategoryNameLang = doc.createAttributeNS( XMLConstants.XML_NS_URI, "lang"); attrCategoryNameLang.setValue(lang); nodeCategoryName.setAttributeNodeNS(attrCategoryNameLang); nodeCategoryName .setTextContent(category.getLocalizedCategoryName()); nodeCategory.appendChild(nodeCategoryName); // layers Element nodeLayers = doc.createElementNS(NS, "layers"); for (Layer layer : category.getLayers()) { // layer Element nodeLayer = doc.createElementNS(NS, "layer"); nodeLayer.setAttribute("id", layer.getId()); Element nodeLayerName = doc.createElementNS(NS, "display-name"); Attr attrLayerNameLang = doc.createAttributeNS( XMLConstants.XML_NS_URI, "lang"); attrLayerNameLang.setValue(lang); nodeLayerName.setAttributeNodeNS(attrLayerNameLang); nodeLayerName.setTextContent(layer.getLocalizedName()); nodeLayer.appendChild(nodeLayerName); Element nodeOrder = doc.createElementNS(NS, "order"); nodeOrder.setTextContent(Integer.toString(layer.getOrder())); nodeLayer.appendChild(nodeOrder); ColorGroup colorGroup = layer.getColorGroup(); if (colorGroup != null && colorGroup.isEnabled()) { Element nodeColorGroup = doc.createElementNS(NS, "colorGroup"); nodeColorGroup.setAttribute("refid", colorGroup.getId()); nodeColorGroup.setAttribute("init-sync", layer.isInitSync() ? "true" : "false"); nodeLayer.appendChild(nodeColorGroup); } Element nodeDir = doc.createElementNS(NS, "dir"); nodeDir.setTextContent(layer.getDir()); nodeLayer.appendChild(nodeDir); String colorModelName = layer.getColorModelName(); if (colorModelName != null && colorModelName.length() > 0) { Element nodeColorModel = doc.createElementNS(NS, "colorModel"); nodeColorModel.setTextContent(layer.getColorModelName()); nodeLayer.appendChild(nodeColorModel); } nodeLayers.appendChild(nodeLayer); } nodeCategory.appendChild(nodeLayers); nodeCategories.appendChild(nodeCategory); } root.appendChild(nodeCategories); // ColorGroupを構築する Collection colorGroups = characterData.getColorGroups(); if (colorGroups.size() > 0) { Element nodeColorGroups = doc.createElementNS(NS, "colorGroups"); int colorGroupCount = 0; for (ColorGroup colorGroup : colorGroups) { if (!colorGroup.isEnabled()) { continue; } Element nodeColorGroup = doc.createElementNS(NS, "colorGroup"); nodeColorGroup.setAttribute("id", colorGroup.getId()); Element nodeColorGroupName = doc.createElementNS(NS, "display-name"); Attr attrColorGroupNameLang = doc.createAttributeNS( XMLConstants.XML_NS_URI, "lang"); attrColorGroupNameLang.setValue(lang); nodeColorGroupName.setAttributeNodeNS(attrColorGroupNameLang); nodeColorGroupName .setTextContent(colorGroup.getLocalizedName()); nodeColorGroup.appendChild(nodeColorGroupName); nodeColorGroups.appendChild(nodeColorGroup); colorGroupCount++; } if (colorGroupCount > 0) { root.appendChild(nodeColorGroups); } } // Recommendations List recommendations = characterData .getRecommendationURLList(); if (recommendations != null) { Element nodeRecommendations = doc.createElementNS(NS, "recommendations"); for (RecommendationURL recommendation : recommendations) { Element nodeRecommendation = doc.createElementNS(NS, "recommendation"); String displayName = recommendation.getDisplayName(); String url = recommendation.getUrl(); Element nodeDescription = doc .createElementNS(NS, "description"); Attr attrRecommendationDescriptionLang = doc.createAttributeNS( XMLConstants.XML_NS_URI, "lang"); attrRecommendationDescriptionLang.setValue(lang); nodeDescription .setAttributeNodeNS(attrRecommendationDescriptionLang); nodeDescription.setTextContent(displayName); Element nodeURL = doc.createElementNS(NS, "URL"); Attr attrRecommendationURLLang = doc.createAttributeNS( XMLConstants.XML_NS_URI, "lang"); attrRecommendationURLLang.setValue(lang); nodeURL.setAttributeNodeNS(attrRecommendationURLLang); nodeURL.setTextContent(url); nodeRecommendation.appendChild(nodeDescription); nodeRecommendation.appendChild(nodeURL); nodeRecommendations.appendChild(nodeRecommendation); } root.appendChild(nodeRecommendations); } // presetsのelementを構築する. Element nodePresets = doc.createElementNS(NS, "presets"); if (writePartsSetElements(doc, nodePresets, characterData, true, false) > 0) { root.appendChild(nodePresets); } // output xml TransformerFactory txFactory = TransformerFactory.newInstance(); txFactory.setAttribute("indent-number", Integer.valueOf(4)); Transformer tfmr; try { tfmr = txFactory.newTransformer(); } catch (TransformerConfigurationException ex) { throw new RuntimeException("JAXP Configuration Failed.", ex); } tfmr.setOutputProperty(OutputKeys.INDENT, "yes"); // JDK-4504745 : javax.xml.transform.Transformer encoding does not work properly // http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4504745 final String encoding = "UTF-8"; tfmr.setOutputProperty("encoding", encoding); try { tfmr.transform(new DOMSource(doc), new StreamResult( new OutputStreamWriter(outstm, Charset.forName(encoding)))); } catch (TransformerException ex) { IOException ex2 = new IOException("XML Convert failed."); ex2.initCause(ex); throw ex2; } } /** * キャラクターデータ内のPresetおよびFavotiesのPartssetの双方共通のパーツセット要素のリストを構築する. * * @param doc * ドキュメントオブジェクト(createElementNS用) * @param baseElement * 親要素、キャラクターデータの場合はPreset、Favoritesの場合はPartssetを示す要素 * @param characterData * キャラクターデータ * @param writePresets * Preset属性のパーツセットを登録する場合はtrue、Preset属性時はデフォルトプリセット属性も(あれば)登録される * @param writeFavorites * Preset属性のないパーツセットを登録する場合はtrue * @return 登録したパーツセットの個数 */ protected int writePartsSetElements(Document doc, Element baseElement, CharacterData characterData, boolean writePresets, boolean writeFavorites) { Map partsSetMap = characterData.getPartsSets(); Locale locale = Locale.getDefault(); String lang = locale.getLanguage(); HashMap registeredPartsSetMap = new HashMap(); for (Map.Entry partsSetsEntry : partsSetMap .entrySet()) { PartsSet partsSet = partsSetsEntry.getValue(); if (partsSet.isPresetParts() && !writePresets) { continue; } if (!partsSet.isPresetParts() && !writeFavorites) { continue; } if (partsSet.isEmpty()) { // 空のパーツセットは登録しない. continue; } Element nodePreset = createPartsSetXML(doc, lang, partsSet); baseElement.appendChild(nodePreset); registeredPartsSetMap.put(partsSet.getPartsSetId(), partsSet); } // プリセット登録時はデフォルトのプリセットIDがあれば、それも登録する. // (ただし、該当パーツセットが書き込み済みである場合のみ) if (writePresets) { String defaultPresetId = characterData.getDefaultPartsSetId(); if (defaultPresetId != null && defaultPresetId.length() > 0) { PartsSet defaultPartsSet = registeredPartsSetMap .get(defaultPresetId); if (defaultPartsSet != null && defaultPartsSet.isPresetParts()) { baseElement.setAttribute("default-preset", defaultPresetId); } } } return registeredPartsSetMap.size(); } /** * パーツセットのXM要素を生成して返す. * * @param doc * ノードを生成するためのファクトリ * @param lang * 言語識別用(パーツセット名などの登録時のlang属性に必要) * @param partsSet * パーツセット、nullの場合はxsi:nul="true"が返される. * @return パーツセット1つ分のXML要素 */ public Element createPartsSetXML(Document doc, String lang, PartsSet partsSet) { if (doc == null || lang == null) { throw new IllegalArgumentException(); } String partsSetId = partsSet.getPartsSetId(); String localizedName = partsSet.getLocalizedName(); Element nodePreset = doc.createElementNS(NS, "preset"); if (partsSet == null || partsSet.isEmpty()) { // 指定されていないか有効でない場合は無しとみなす. nodePreset.setAttributeNS(XMLConstants.W3C_XML_SCHEMA_INSTANCE_NS_URI, "xsi:nil", "true"); return nodePreset; } nodePreset.setAttribute("id", partsSetId); // display-name Element nodeName = doc.createElementNS(NS, "display-name"); Attr attrLang = doc.createAttributeNS(XMLConstants.XML_NS_URI, "lang"); attrLang.setValue(lang); nodeName.setAttributeNode(attrLang); nodeName.setTextContent(localizedName); nodePreset.appendChild(nodeName); // bgColor Color bgColor = partsSet.getBgColor(); if (bgColor != null) { Element nodeBgColor = doc.createElementNS(NS, "background-color"); nodeBgColor.setAttribute("color", "#" + Integer.toHexString(bgColor.getRGB() & 0xffffff)); nodePreset.appendChild(nodeBgColor); } // affine transform parameter double[] affineTransformParameter = partsSet .getAffineTransformParameter(); if (affineTransformParameter != null) { Element nodeAffineTransform = doc.createElementNS(NS, "affine-transform-parameter"); StringBuilder tmp = new StringBuilder(); for (double affineItem : affineTransformParameter) { if (tmp.length() > 0) { tmp.append(" "); } tmp.append(Double.toString(affineItem)); } nodeAffineTransform.setTextContent(tmp.toString()); nodePreset.appendChild(nodeAffineTransform); } // categories for (Map.Entry> entry : partsSet .entrySet()) { PartsCategory partsCategory = entry.getKey(); // category Element nodeCategory = doc.createElementNS(NS, "category"); nodeCategory.setAttribute("refid", partsCategory.getCategoryId()); nodePreset.appendChild(nodeCategory); List partsIdentifiers = entry.getValue(); for (PartsIdentifier partsIdentifier : partsIdentifiers) { String partsName = partsIdentifier.getPartsName(); Element nodeParts = doc.createElementNS(NS, "parts"); nodeParts.setAttribute("name", partsName); nodeCategory.appendChild(nodeParts); PartsColorInfo partsColorInfo = partsSet .getColorInfo(partsIdentifier); if (partsColorInfo != null) { Element nodeColor = createPartsColorInfoXML(doc, partsColorInfo); nodeParts.appendChild(nodeColor); } } } return nodePreset; } /** * パーツカラー情報のXML要素を生成して返す.
* * @param doc * 要素を作成するためのファクトリ * @param partsColorInfo * パーツカラー情報 * @return パーツカラー情報の要素 */ public Element createPartsColorInfoXML(Document doc, PartsColorInfo partsColorInfo) { if (doc == null || partsColorInfo == null) { throw new IllegalArgumentException(); } Element nodeColor = doc.createElementNS(NS, "color"); for (Map.Entry colorInfoEntry : partsColorInfo .entrySet()) { Layer layer = colorInfoEntry.getKey(); ColorInfo colorInfo = colorInfoEntry.getValue(); Element nodeLayer = doc .createElementNS(NS, "layer"); nodeLayer.setAttribute("refid", layer.getId()); nodeColor.appendChild(nodeLayer); // ColorGroup ColorGroup colorGroup = colorInfo.getColorGroup(); boolean colorSync = colorInfo.isSyncColorGroup(); if (colorGroup.isEnabled()) { Element nodeColorGroup = doc.createElementNS( NS, "color-group"); nodeColorGroup.setAttribute("group", colorGroup.getId()); nodeColorGroup.setAttribute("synchronized", colorSync ? "true" : "false"); nodeLayer.appendChild(nodeColorGroup); } // RGB ColorConvertParameter param = colorInfo .getColorParameter(); Element nodeRGB = doc.createElementNS(NS, "rgb"); Object[][] rgbArgss = { {"red", param.getOffsetR(), param.getFactorR(), param.getGammaR()}, {"green", param.getOffsetG(), param.getFactorG(), param.getGammaG()}, {"blue", param.getOffsetB(), param.getFactorB(), param.getGammaB()}, {"alpha", param.getOffsetA(), param.getFactorA(), param.getGammaA()},}; for (Object[] rgbArgs : rgbArgss) { Element nodeRGBItem = doc.createElementNS(NS, rgbArgs[0].toString()); nodeRGBItem.setAttribute("offset", rgbArgs[1].toString()); nodeRGBItem.setAttribute("factor", rgbArgs[2].toString()); nodeRGBItem.setAttribute("gamma", rgbArgs[3].toString()); nodeRGB.appendChild(nodeRGBItem); } nodeLayer.appendChild(nodeRGB); // HSB Element nodeHSB = doc.createElementNS(NS, "hsb"); nodeHSB.setAttribute("hue", Float.toString(param.getHue())); nodeHSB.setAttribute("saturation", Float.toString(param.getSaturation())); nodeHSB.setAttribute("brightness", Float.toString(param.getBrightness())); if (param.getContrast() != 0.f) { // ver0.96追加、optional // ぴったり0.0fだったら省略する. nodeHSB.setAttribute("contrast", Float.toString(param.getContrast())); } nodeLayer.appendChild(nodeHSB); // RGB Replace Element nodeRGBReplace = doc.createElementNS(NS, "rgb-replace"); ColorConv colorConv = param.getColorReplace(); if (colorConv == null) { colorConv = ColorConv.NONE; } nodeRGBReplace.setAttribute("replace-type", colorConv.name()); nodeRGBReplace.setAttribute("gray", Float.toString(param.getGrayLevel())); nodeLayer.appendChild(nodeRGBReplace); } return nodeColor; } public void saveFavorites(CharacterData characterData, OutputStream outstm) throws IOException { if (characterData == null || outstm == null) { throw new IllegalArgumentException(); } Document doc; try { DocumentBuilderFactory factory = DocumentBuilderFactory .newInstance(); factory.setNamespaceAware(true); DocumentBuilder builder = factory.newDocumentBuilder(); doc = builder.newDocument(); } catch (ParserConfigurationException ex) { throw new RuntimeException("JAXP Configuration Exception.", ex); } Element root = doc.createElementNS(NS, "partssets"); root.setAttribute("xmlns:xml", XMLConstants.XML_NS_URI); root.setAttribute("xmlns:xsi", "http://www.w3.org/2001/XMLSchema-instance"); root.setAttribute("xsi:schemaLocation", NS + " partsset.xsd"); doc.appendChild(root); // presetsのelementを構築する.(Presetは除く) writePartsSetElements(doc, root, characterData, false, true); // output xml TransformerFactory txFactory = TransformerFactory.newInstance(); txFactory.setAttribute("indent-number", Integer.valueOf(4)); Transformer tfmr; try { tfmr = txFactory.newTransformer(); } catch (TransformerConfigurationException ex) { throw new RuntimeException("JAXP Configuration Failed.", ex); } tfmr.setOutputProperty(OutputKeys.INDENT, "yes"); // http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4504745 final String encoding = "UTF-8"; tfmr.setOutputProperty("encoding", encoding); try { tfmr.transform(new DOMSource(doc), new StreamResult( new OutputStreamWriter(outstm, Charset.forName(encoding)))); } catch (TransformerException ex) { IOException ex2 = new IOException("XML Convert failed."); ex2.initCause(ex); throw ex2; } } } CharacterManaJ/src/charactermanaj/model/io/PartsDataLoader.java0000644000175000017500000000114312560206305024705 0ustar paulliupaulliupackage charactermanaj.model.io; import java.util.Map; import charactermanaj.model.PartsCategory; import charactermanaj.model.PartsIdentifier; import charactermanaj.model.PartsSpec; /** * パーツデータのロードを行うインターフェイス * @author seraphy */ public interface PartsDataLoader { /** * カテゴリを指定してパーツデータをロードする.
* 該当がない場合は空が返される * @param category カテゴリ * @return パーツデータのマップ */ Map load(PartsCategory category); } CharacterManaJ/src/charactermanaj/model/io/RecentDataPersistent.java0000644000175000017500000002144512560206305025775 0ustar paulliupaulliupackage charactermanaj.model.io; import java.io.BufferedInputStream; import java.io.BufferedOutputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.net.URI; import java.sql.Timestamp; import java.util.Properties; import java.util.logging.Level; import java.util.logging.Logger; import charactermanaj.model.CharacterData; import charactermanaj.util.DirectoryConfig; /** * 最後に使用したデータの使用状況を保存・復元するためのクラス * * @author seraphy */ public final class RecentDataPersistent { /** * キャラクターデータの親フォルダごとに保存する、最後に使用したキャラクターデータを保存するファイル名 */ private static final String RECENT_CAHARCTER_INFO_XML = "recent-character.xml"; /** * ロガー */ private static final Logger logger = Logger.getLogger(RecentDataPersistent.class.getName()); /** * シングルトン */ private static final RecentDataPersistent inst = new RecentDataPersistent(); /** * プライベートコンストラクタ */ private RecentDataPersistent() { super(); } /** * インスタンスを取得する * * @return インスタンス */ public static RecentDataPersistent getInstance() { return inst; } /** * キャラクターデータの親フォルダごとに保存する、最後に使用したキャラクターデータを保存するファイル * * @return */ private File getRecentCharacterXML() { File currentCharactersDir = DirectoryConfig.getInstance() .getCharactersDir(); File recentCharacterXML = new File(currentCharactersDir, RECENT_CAHARCTER_INFO_XML); return recentCharacterXML; } /** * 最後に使用したキャラクターデータのフォルダ名を親ディレクトリからの相対パスとして保存する.
* ただし、書き込み禁止である場合は何もしない.
* * @param characterData * キャラクターデータ * @throws IOException * 書き込みに失敗した場合 */ public boolean saveRecent(CharacterData characterData) throws IOException { URI docBase = null; if (characterData != null) { docBase = characterData.getDocBase(); } String characterDataName = null; if (docBase != null) { File currentCharactersDir = DirectoryConfig.getInstance() .getCharactersDir(); File characterXml = new File(docBase); File characterDir = characterXml.getParentFile(); // "Foo/Bar/character.xml"の、「Foo」の部分を取得する. File baseDir = characterDir.getParentFile(); if (currentCharactersDir.equals(baseDir)) { // "Foo/Bar/character.xml"の「Bar」の部分を取得する. // ※ キャラクターデータの親フォルダ上のフォルダ名だけを保存する.(相対パス) characterDataName = characterDir.getName(); } } Properties props = new Properties(); props.setProperty("lastUseCharacterData", characterDataName == null ? "" : characterDataName); File recentCharacterXML = getRecentCharacterXML(); if (recentCharacterXML.exists() && !recentCharacterXML.canWrite()) { // ファイルが書き込み禁止の場合は何もしない. logger.log(Level.FINE, "recent-character.xml is readonly."); return false; } // ファイルがまだ実在しないか、書き込み可能である場合のみ保存する. OutputStream os = new BufferedOutputStream(new FileOutputStream( recentCharacterXML)); try { String comment = "recent-character: lastModified=" + (new Timestamp(System.currentTimeMillis()).toString()); props.storeToXML(os, comment); } finally { os.close(); } return true; } /** * 親ディレクトリからの相対パスとして記録されている、最後に使用したキャラクターデータのフォルダ名から、 * 最後に使用したキャラクターデータをロードして返す.
* 該当するキャラクターデータが存在しないか、読み込みに失敗した場合は「履歴なし」としてnullを返す.
* * @return キャラクターデータ、もしくはnull */ public CharacterData loadRecent() { File recentCharacterXML = getRecentCharacterXML(); if (recentCharacterXML.exists()) { try { Properties props = new Properties(); InputStream is = new BufferedInputStream(new FileInputStream( recentCharacterXML)); try { props.loadFromXML(is); } finally { is.close(); } String characterDataName = props .getProperty("lastUseCharacterData"); if (characterDataName != null && characterDataName.trim().length() > 0) { // ※ キャラクターデータの親フォルダ上のフォルダ名だけを保存されているので // 相対パスから、character.xmlの絶対パスを求める File currentCharactersDir = DirectoryConfig.getInstance() .getCharactersDir(); File characterDir = new File(currentCharactersDir, characterDataName); File characterXml = new File(characterDir, CharacterDataPersistent.CONFIG_FILE); if (characterXml.exists()) { // character.xmlが存在すれば復元を試行する. CharacterDataPersistent persist = CharacterDataPersistent .getInstance(); return persist.loadProfile(characterXml.toURI()); } } } catch (Exception ex) { // 失敗した場合は最後に使用したデータが存在しないものとみなす. logger.log(Level.WARNING, "recent data loading failed. " + recentCharacterXML, ex); } } // 履歴がない場合、もしくは読み取れなかった場合はnullを返す. return null; } // /** // * 最後に使用したキャラクターデータを取得する. // * // * @return キャラクターデータ。最後に使用したデータが存在しない場合はnull // * @throws IOException // * 読み込みに失敗した場合 // */ // private CharacterData loadRecentSer() throws IOException { // UserData recentCharacterStore = getRecentCharacterStore(); // if (!recentCharacterStore.exists()) { // return null; // } // // RecentData recentData; // try { // File currentCharactersDir = // DirectoryConfig.getInstance().getCharactersDir(); // // Object rawRecentData = recentCharacterStore.load(); // if (rawRecentData instanceof RecentData) { // // 旧形式 (単一) // recentData = (RecentData) rawRecentData; // logger.log(Level.INFO, "old-recentdata-type: " + recentData); // // // 旧形式で保存されているURIが、現在選択しているキャラクターデータと同じ親ディレクトリ // // でなければ復元しない. // URI uri = recentData.getDocBase(); // File parentDir = new File(uri).getParentFile().getParentFile(); // 2段上 // if (!currentCharactersDir.equals(parentDir)) { // logger.log(Level.INFO, // "unmatched characters-dir. current=" // + currentCharactersDir + "/recent=" // + parentDir); // recentData = null; // } // // } else if (rawRecentData instanceof Map) { // // 新形式 (複数のキャラクターディレクトリに対応) // @SuppressWarnings("unchecked") // Map recentDataMap = (Map) // rawRecentData; // recentData = recentDataMap.get(currentCharactersDir); // logger.log(Level.FINE, "recent-data: " + currentCharactersDir + "=" + // recentData); // // } else { // // 不明な形式 // logger.log(Level.SEVERE, // "invalid file format. " + recentCharacterStore // + "/class=" + rawRecentData.getClass()); // recentData = null; // } // // } catch (Exception ex) { // // RecentData情報の復元に失敗した場合は最後に使用したデータが存在しないものとみなす. // logger.log(Level.WARNING, "recent data loading failed. " + // recentCharacterStore, ex); // recentData = null; // } // // if (recentData != null) { // CharacterDataPersistent persist = CharacterDataPersistent.getInstance(); // return persist.loadProfile(recentData.getDocBase()); // } // // // 履歴がない場合、もしくは読み取れなかった場合はnullを返す. // return null; // } // // /** // * 最後に使用したキャラクタデータの保存先を取得する // * // * @return 保存先 // */ // protected UserData getRecentCharacterStore() { // UserDataFactory userDataFactory = UserDataFactory.getInstance(); // UserData recentCharacterStore = // userDataFactory.getUserData(RECENT_CHARACTER_SER); // return recentCharacterStore; // } } CharacterManaJ/src/charactermanaj/model/io/PartsImageDirectoryWatchAgentThread.java0000644000175000017500000002550512560206305030722 0ustar paulliupaulliupackage charactermanaj.model.io; import java.io.File; import java.io.FileFilter; import java.net.URI; import java.util.ArrayList; import java.util.Collections; import java.util.IdentityHashMap; import java.util.LinkedList; import java.util.logging.Level; import java.util.logging.Logger; import java.util.zip.CRC32; import charactermanaj.model.AppConfig; import charactermanaj.model.CharacterData; import charactermanaj.model.Layer; import charactermanaj.model.PartsCategory; public class PartsImageDirectoryWatchAgentThread implements Runnable { /** * ロガー */ private static final Logger logger = Logger.getLogger(PartsImageDirectoryWatchAgent.class.getName()); private final CharacterData characterData; private final File baseDir; /** * リスナーとサスペンド要求マップのロックオブジェクト */ private final Object lockListeners = new Object(); /** * 監視を通知されるリスナー. */ private LinkedList listeners = new LinkedList(); /** * サスペンド要求のマップ. */ private IdentityHashMap suspendStateMap = new IdentityHashMap(); /** * ロックオブジェクト */ private final Object lock = new Object(); /** * 停止フラグ */ private volatile boolean stopFlag; /** * 監視インターバル */ private int dirWatchInterval; /** * スレッド、生成されていなければnull */ private Thread thread; /** * 監視結果1、まだ監視されていなければnull */ private volatile Long signature; /** * 監視結果2、検出されたアイテムの個数 */ private volatile int itemCount; /** * 監視結果3、検出されたアイテムの最終更新日。ただし未来の日付は除外する。 */ private volatile long maxLastModified; public PartsImageDirectoryWatchAgentThread(CharacterData characterData) { if (characterData == null) { throw new IllegalArgumentException(); } URI docBase = characterData.getDocBase(); File baseDir = null; if (docBase != null && "file".equals(docBase.getScheme())) { baseDir = new File(docBase).getParentFile(); } this.characterData = characterData; this.baseDir = baseDir; this.dirWatchInterval = AppConfig.getInstance().getDirWatchInterval(); } public CharacterData getCharcterData() { return characterData; } /** * 監視を開始する.
* 一定時間待機後、フォルダを監視し、前回監視結果と比較して変更があれば通知を行う.
* その処理をstop指示が来るまで繰り返す.
* 開始しても、まず指定時間待機するため、すぐにはディレクトリの走査は行わない.
* 且つ、初回監視結果は変更通知されないため、二回以上走査しないかぎり通知は発生しない.
* これは意図したものである.
* 明示的に初回監視を行うには明示的に{@link #reset()}を呼び出す.
*/ private void start() { logger.log(Level.FINE, "watchAgent request start. " + this); synchronized (lock) { if (thread != null && thread.isAlive()) { // すでに開始済みであれば何もしない. return; } // 書き込み禁止キャラクター定義に対する監視を無効とするか? AppConfig appConfig = AppConfig.getInstance(); if (appConfig.isDisableWatchDirIfNotWritable()) { try { if (baseDir == null || !baseDir.exists() || !baseDir.isDirectory() || !baseDir.canWrite()) { logger.log(Level.INFO, "does not monitor the directory because it is not writable." + baseDir); return; } URI docBase = characterData.getDocBase(); if (docBase != null) { File docBaseFile = new File(docBase); if (!docBaseFile.exists() || !docBaseFile.canWrite()) { logger.log(Level.INFO, "does not monitor the directory because the character.xml is not writable." + characterData); return; } } } catch (Exception ex) { logger.log(Level.WARNING, "watch-dir start failed. " + characterData, ex); return; } } // スレッドを開始する. stopFlag = false; thread = new Thread(this); thread.setDaemon(true); thread.start(); } } /** * 監視を停止する.
* * @return 停止した場合はtrue、すでに停止していたか開始されていない場合はfalse */ private boolean stop() { logger.log(Level.FINE, "watchAgent request stop. " + this); boolean stopped = false; synchronized (lock) { // スレッドが生きていれば停止する. if (thread != null && thread.isAlive()) { stopFlag = true; thread.interrupt(); try { thread.join(10000); // 10Secs } catch (InterruptedException ex) { logger.log(Level.FINE, "stop request interrupted.", ex); // 抜ける } stopped = true; } // スレッドは停止されていると見なす. thread = null; } return stopped; } /** * スレッドの停止フラグがたてられるまで、一定時間待機と監視とを永久に繰り返す.
* ただし、スレッド自身はデーモンとして動作させているので他の非デーモンスレッドが存在しなくなれば停止する.
*/ public void run() { logger.log(Level.FINE, "watch-dir thead started. " + this); // 初回スキャンは無視するためリセット状態とする. this.signature = null; // スキャンループ while (!stopFlag) { try { Thread.sleep(dirWatchInterval); watch(new Runnable() { public void run() { fireWatchEvent(); } }); } catch (InterruptedException ex) { logger.log(Level.FINE, "watch-dir thead interrupted."); // 何もしない } catch (Exception ex) { logger.log(Level.SEVERE, "PartsImageDirectoryWatchAgent failed.", ex); // 何もしない. } } logger.log(Level.FINE, "watch-dir thead stopped. " + this); } /** * 監視を行う.
* 停止フラグが設定されるか、割り込みされた場合は処理を中断してInterruptedException例外を返して終了する.
* * @param notifier * 通知するためのオブジェクト * @throws InterruptedException * 割り込みされた場合 */ protected void watch(Runnable notifier) throws InterruptedException { if (baseDir == null || !baseDir.exists() || !baseDir.isDirectory()) { return; } int itemCount = 0; long maxLastModified = 0; long now = System.currentTimeMillis() + dirWatchInterval; CRC32 crc = new CRC32(); for (PartsCategory partsCategory : characterData.getPartsCategories()) { for (Layer layer : partsCategory.getLayers()) { File watchDir = new File(baseDir, layer.getDir()); ArrayList files = new ArrayList(); if (watchDir.exists() && watchDir.isDirectory()) { File[] list = watchDir.listFiles(new FileFilter() { public boolean accept(File pathname) { return pathname.isFile() && pathname.getName().toLowerCase().endsWith(".png"); } }); if (list == null) { list = new File[0]; } for (File file : list) { if (Thread.interrupted() || stopFlag) { throw new InterruptedException(); } itemCount++; long lastModified = file.lastModified(); if (lastModified <= now) { // 未来の日付は除外する. // 未来の日付のファイルが一つでもあると他のファイルが実際に更新されても判定できなくなるため。 maxLastModified = Math.max(maxLastModified, lastModified); } files.add(file.getName() + ":" + lastModified); }; Collections.sort(files); } for (String file : files) { crc.update(file.getBytes()); } } } long signature = crc.getValue(); if (this.signature != null) { // 初回は無視される. if (this.signature.longValue() != signature || this.itemCount != itemCount || this.maxLastModified != maxLastModified) { // ハッシュ値が異なるか、アイテム数が異なるか、最大の最終更新日が異なる場合、変更されたとみなす. if (notifier != null) { notifier.run(); } } } this.signature = Long.valueOf(signature); this.maxLastModified = maxLastModified; this.itemCount = itemCount; } /** * イベントリスナを登録する * * @param l * リスナ */ public void addPartsImageDirectoryWatchListener(PartsImageDirectoryWatchListener l) { if (l != null) { synchronized (lockListeners) { listeners.add(l); } changeSuspendState(); } } /** * イベントリスナを登録解除する * * @param l * リスナ */ public void removePartsImageDirectoryWatchListener(PartsImageDirectoryWatchListener l) { if (l != null) { synchronized (lockListeners) { listeners.remove(l); suspendStateMap.remove(l); } changeSuspendState(); } } public void suspend(PartsImageDirectoryWatchListener l) { if (l != null) { synchronized (lockListeners) { Integer cnt = suspendStateMap.get(l); if (cnt == null) { cnt = Integer.valueOf(1); } else { cnt = cnt + 1; } suspendStateMap.put(l, cnt); } changeSuspendState(); } } public void resume(PartsImageDirectoryWatchListener l) { if (l != null) { synchronized (lockListeners) { Integer cnt = suspendStateMap.get(l); if (cnt != null) { cnt = cnt - 1; if (cnt > 0) { suspendStateMap.put(l, cnt); } else { suspendStateMap.remove(l); } } } changeSuspendState(); } } protected void changeSuspendState() { boolean active; synchronized (lockListeners) { // リスナが接続されており、且つ、サスペンド要求が一つもない場合はスレッド実行可 active = !listeners.isEmpty() && suspendStateMap.isEmpty(); } if (active) { start(); } else { stop(); } } /** * イベントを通知する. */ protected void fireWatchEvent() { PartsImageDirectoryWatchListener[] listeners; synchronized (this.listeners) { listeners = this.listeners.toArray(new PartsImageDirectoryWatchListener[this.listeners.size()]); } PartsImageDirectoryWatchEvent e = new PartsImageDirectoryWatchEvent(characterData); for (PartsImageDirectoryWatchListener listener : listeners) { listener.detectPartsImageChange(e); } } } CharacterManaJ/src/charactermanaj/model/io/PartsManageDataDecorateLoader.java0000644000175000017500000000767312560206305027503 0ustar paulliupaulliupackage charactermanaj.model.io; import java.util.HashMap; import java.util.Map; import charactermanaj.model.PartsAuthorInfo; import charactermanaj.model.PartsCategory; import charactermanaj.model.PartsIdentifier; import charactermanaj.model.PartsManageData; import charactermanaj.model.PartsSpec; /** * パーツ管理情報をパーツデータに補填するデコレータ・ローダー.
* ローダから読み込まれたパーツデータに対して、それに該当する管理情報があれば、その管理情報で更新し、 * 更新した結果を返す.
* @author seraphy */ public class PartsManageDataDecorateLoader implements PartsDataLoader { /** * パーツ管理情報を構築するファクトリ.
* ローダーが呼び出された段階で、最新のパーツ管理情報をロードするためのもの.
* @author seraphy * */ public interface PartsManageDataFactory { /** * パーツ管理情報をロードする.
* パーツ管理情報が存在しない場合、もしくは読み取りできなった場合は管理情報はないものとして空のインスタンスを返す.
* @return バーツ管理情報、存在しない場合は空を返す.
*/ PartsManageData createPartsManageData(); } private PartsDataLoader parent; private PartsManageDataFactory partsManageDataFactory; public PartsManageDataDecorateLoader(PartsDataLoader parent, PartsManageDataFactory partsManageDataFactory) { if (parent == null || partsManageDataFactory == null) { throw new IllegalArgumentException(); } this.parent = parent; this.partsManageDataFactory = partsManageDataFactory; } public Map load(PartsCategory category) { // 親よりパーツデータをロードする. Map partsSpecs = parent.load(category); // 最新のパーツ管理情報をロードする. PartsManageData partsManageData = partsManageDataFactory.createPartsManageData(); if (partsManageData == null || partsManageData.isEmpty()) { // パーツ管理情報がnullであるか空であれば、元のまま何もせず返す. return partsSpecs; } // 返却用 HashMap newPartsSpecs = new HashMap(); // パーツ管理データからのデータを補填する. for (Map.Entry entry : partsSpecs.entrySet()) { PartsIdentifier orgPartsId = entry.getKey(); PartsIdentifier newPartsId; PartsSpec partsSpec = entry.getValue(); PartsManageData.PartsKey partsKey = new PartsManageData.PartsKey(orgPartsId); // ローカライズ名が指定されているか? String localizedName = partsManageData.getLocalizedName(partsKey); if (localizedName != null && localizedName.length() > 0 && !localizedName.equals(orgPartsId.getLocalizedPartsName())) { // ローカライズ名が指定されており、且つ、元のローカライズ名と異なる場合 newPartsId = orgPartsId.setLocalizedPartsName(localizedName); } else { // ローカライズ名の変更なし newPartsId = orgPartsId; } // パーツ作者情報があれば設定する. PartsAuthorInfo partsAuthorInfo = partsManageData.getPartsAuthorInfo(partsKey); if (partsAuthorInfo != null) { partsSpec.setAuthorInfo(partsAuthorInfo); } // パーツのバージョンとダウンロード情報があれば設定する. PartsManageData.PartsVersionInfo versionInfo = partsManageData.getVersion(partsKey); if (versionInfo != null) { double version = versionInfo.getVersion(); String downloadURL = versionInfo.getDownloadURL(); partsSpec.setVersion(version); partsSpec.setDownloadURL(downloadURL); } newPartsSpecs.put(newPartsId, partsSpec); } return newPartsSpecs; } } CharacterManaJ/src/charactermanaj/model/io/CharacterDataPersistent.java0000644000175000017500000010154212560206305026446 0ustar paulliupaulliupackage charactermanaj.model.io; import java.awt.Color; import java.awt.image.BufferedImage; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileFilter; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.net.URI; import java.net.URL; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Collections; import java.util.Date; import java.util.Iterator; import java.util.List; import java.util.NoSuchElementException; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; import java.util.concurrent.atomic.AtomicBoolean; import java.util.logging.Level; import java.util.logging.Logger; import org.w3c.dom.Node; import org.w3c.dom.NodeList; import charactermanaj.graphics.io.FileImageResource; import charactermanaj.graphics.io.ImageLoader; import charactermanaj.graphics.io.ImageSaveHelper; import charactermanaj.graphics.io.LoadedImage; import charactermanaj.model.AppConfig; import charactermanaj.model.CharacterData; import charactermanaj.model.Layer; import charactermanaj.model.PartsCategory; import charactermanaj.model.io.CharacterDataDefaultProvider.DefaultCharacterDataVersion; import charactermanaj.util.DirectoryConfig; import charactermanaj.util.FileNameNormalizer; import charactermanaj.util.FileUserData; import charactermanaj.util.UserData; public class CharacterDataPersistent { /** * キャラクター定義ファイル名 */ public static final String CONFIG_FILE = "character.xml"; /** * キャラクターなんとか機用のiniファイル名 */ public static final String COMPATIBLE_CONFIG_NAME = "character.ini"; /** * ロガー */ private static final Logger logger = Logger .getLogger(CharacterDataPersistent.class.getName()); /** * キャラクターデータを格納したXMLのリーダー */ private final CharacterDataXMLReader characterDataXmlReader = new CharacterDataXMLReader(); /** * キャラクターデータを格納したXMLのライタ */ private final CharacterDataXMLWriter characterDataXmlWriter = new CharacterDataXMLWriter(); /** * サンプルイメージファイル名 */ private static final String SAMPLE_IMAGE_FILENAME = "preview.png"; /** * プロファイルの列挙時のエラーハンドラ.
* * @author seraphy */ public interface ProfileListErrorHandler { /** * エラーが発生したことを通知される * * @param baseDir * 読み込み対象のXMLのファイル * @param ex * 例外 */ void occureException(File baseDir, Throwable ex); } /** * プライベートコンストラクタ.
* シングルトン実装であるため、一度だけ呼び出される. */ private CharacterDataPersistent() { super(); } /** * シングルトン */ private static final CharacterDataPersistent singleton = new CharacterDataPersistent(); /** * インスタンスを取得する * * @return インスタンス */ public static CharacterDataPersistent getInstance() { return singleton; } /** * キャラクターデータを新規に保存する.
* REVがnullである場合は保存に先立ってランダムにREVが設定される.
* 保存先ディレクトリはユーザー固有のキャラクターデータ保存先のディレクトリにキャラクター定義のIDを基本とする ディレクトリを作成して保存される.
* ただし、そのディレクトリがすでに存在する場合はランダムな名前で決定される.
* 実際のxmlの保存先にあわせてDocBaseが設定されて返される.
* * @param characterData * キャラクターデータ (IDは設定済みであること.それ以外はvalid, editableであること。) * @throws IOException * 失敗 */ public void createProfile(CharacterData characterData) throws IOException { if (characterData == null) { throw new IllegalArgumentException(); } String id = characterData.getId(); if (id == null || id.trim().length() == 0) { throw new IOException("missing character-id:" + characterData); } // ユーザー個別のキャラクターデータ保存先ディレクトリを取得 DirectoryConfig dirConfig = DirectoryConfig.getInstance(); File charactersDir = dirConfig.getCharactersDir(); if (!charactersDir.exists()) { if (!charactersDir.mkdirs()) { throw new IOException("can't create the characters directory. " + charactersDir); } } if (logger.isLoggable(Level.FINE)) { logger.log(Level.FINE, "check characters-dir: " + charactersDir + ": exists=" + charactersDir.exists()); } // 新規に保存先ディレクトリを作成. // 同じ名前のディレクトリがある場合は日付+連番をつけて衝突を回避する File baseDir = null; String suffix = ""; String name = characterData.getName(); if (name == null) { // 表示名が定義されていなければIDで代用する.(IDは必須) name = characterData.getId(); } for (int retry = 0;; retry++) { baseDir = new File(charactersDir, name + suffix); if (!baseDir.exists()) { break; } if (retry > 100) { throw new IOException("character directory conflict.:" + baseDir); } // 衝突回避の末尾文字を設定 suffix = generateSuffix(retry); } if (!baseDir.exists()) { if (!baseDir.mkdirs()) { throw new IOException("can't create directory. " + baseDir); } logger.log(Level.INFO, "create character-dir: " + baseDir); } // 保存先を確認 File characterPropXML = new File(baseDir, CONFIG_FILE); if (characterPropXML.exists() && !characterPropXML.isFile()) { throw new IOException("character.xml is not a regular file.:" + characterPropXML); } if (characterPropXML.exists() && !characterPropXML.canWrite()) { throw new IOException("character.xml is not writable.:" + characterPropXML); } // DocBaseを実際の保存先に更新 URI docBase = characterPropXML.toURI(); characterData.setDocBase(docBase); // リビジョンが指定されてなければ新規にリビジョンを割り当てる。 if (characterData.getRev() == null) { characterData.setRev(generateRev()); } // 保存する. saveCharacterDataToXML(characterData); // ディレクトリを準備する preparePartsDir(characterData); } /** * リビジョンを生成して返す. * * @return リビジョン用文字列 */ public String generateRev() { SimpleDateFormat fmt = new SimpleDateFormat("yyyy-MM-dd_HHmmss"); return fmt.format(new Date()); } /** * 衝突回避用の末尾文字を生成する. * * @param retryCount * リトライ回数 * @return 末尾文字 */ protected String generateSuffix(int retryCount) { SimpleDateFormat fmt = new SimpleDateFormat("yyyy-MM-dd_HHmmss"); String suffix = "_" + fmt.format(new Date()); if (retryCount > 0) { suffix = suffix + "_" + retryCount; } return suffix; } /** * キャラクターデータを更新する. * * @param characterData * キャラクターデータ(有効かつ編集可能であること) * @throws IOException * 失敗 */ public void updateProfile(CharacterData characterData) throws IOException { if (characterData == null) { throw new IllegalArgumentException(); } characterData.checkWritable(); if (!characterData.isValid()) { throw new IOException("invalid profile: " + characterData); } // 保存する saveCharacterDataToXML(characterData); // ディレクトリを準備する preparePartsDir(characterData); } /** * キャラクターデータのパーツイメージを保存するディレクトリを準備する * * @param characterData * キャラクターデータ * @param baseDir * ベースディレクトリ * @throws IOException * 失敗 */ protected void preparePartsDir(CharacterData characterData) throws IOException { if (characterData == null) { throw new IllegalArgumentException(); } characterData.checkWritable(); if (!characterData.isValid()) { throw new IOException("invalid profile: " + characterData); } URI docBase = characterData.getDocBase(); if (!"file".equals(docBase.getScheme())) { throw new IOException("ファイル以外はサポートしていません。:" + docBase); } File docBaseFile = new File(docBase); File baseDir = docBaseFile.getParentFile(); if (!baseDir.exists()) { if (!baseDir.mkdirs()) { throw new IOException("can't create directory. " + baseDir); } } for (PartsCategory partsCategory : characterData.getPartsCategories()) { for (Layer layer : partsCategory.getLayers()) { File layerDir = new File(baseDir, layer.getDir()); if (!layerDir.exists()) { if (!layerDir.mkdirs()) { throw new IOException("can't create directory. " + layerDir); } } } } } /** * キャラクターデータを読み込んだ場合に返されるコールバック */ public interface ListProfileCallback { /** * キャラクターデータを読み込んだ場合.
* 戻り値がfalseの場合は読み込みを以降の読み込みを中断します.
* (ただし、すでに読み込み開始している分については中断されません.) * * @param characterData * @return 継続する場合はtrue、中止する場合はfalse */ boolean receiveCharacterData(CharacterData characterData); /** * キャラクターデータの読み込みに失敗した場合.
* 戻り値がfalseの場合は読み込みを以降の読み込みを中断します.
* (ただし、すでに読み込み開始している分については中断されません.) * * @param dir * 読み込み対象ディレクトリ * @param ex * 例外の内容 * @return 継続する場合はtrue、中止する場合はfalse */ boolean occureException(File dir, Exception ex); } /** * 指定したディレクトリの下のサブフォルダに、character.iniがある場合は、 * キャラクターなんとか機のディレクトリとして、標準のcharacter.xmlを生成する.
* ただし、書き込み禁止の場合は何もしない.
* すでにcharacter.xmlがある場合も何もしない.
* * @param dataDir * キャラクターなんとか機のデータディレクトリ */ public void convertCharacterNantokaIniToXml(File dataDir) { if (dataDir == null || !dataDir.isDirectory() || !dataDir.canWrite()) { return; } File[] dirs = dataDir.listFiles(); if (dirs == null) { dirs = new File[0]; } for (File dir : dirs) { if (!dir.isDirectory()) { continue; } File characterXmlFile = new File(dir, CharacterDataPersistent.CONFIG_FILE); if (characterXmlFile.exists()) { // すでにキャラクター定義XMLが存在する場合はスキップする. continue; } File characterIniFile = new File(dir, CharacterDataPersistent.COMPATIBLE_CONFIG_NAME); if (characterIniFile.exists() && characterIniFile.canWrite() && dir.canWrite()) { // character.iniが存在し、書き込み可能であれば、それをcharacter.xmlに変換する. // eye_colorフォルダがあるか? File eyeColorFolder = new File(dir, "eye_color"); boolean hasEyeColorFolder = eyeColorFolder.exists() && eyeColorFolder.isDirectory(); DefaultCharacterDataVersion version; if (hasEyeColorFolder) { // eye_colorフォルダがあればver3形式とみなす. version = DefaultCharacterDataVersion.V3; } else { version = DefaultCharacterDataVersion.V2; } // readmeがあるか? String readme = null; File readmeFile = new File(dir, "readme.txt"); if (readmeFile.exists() && readmeFile.canRead()) { try { readme = TextReadHelper .readTextTryEncoding(new FileInputStream( readmeFile)); } catch (IOException ex) { logger.log(Level.WARNING, ex.toString(), ex); } } try { convertFromCharacterIni(characterIniFile, characterXmlFile, version, readme); } catch (Exception ex) { logger.log(Level.WARNING, "character.xmlの生成に失敗しました。:" + characterXmlFile, ex); } } } } /** * キャラクターデータを非同期に読み込む.
* 読み込み完了したものが随時、コールバックに渡される. * * @param callback * @return すべての読み込みが完了したか判定し待機することのできるFuture */ public Future listProfileAsync(final ListProfileCallback callback) { if (callback == null) { throw new IllegalArgumentException(); } // キャラクターデータが格納されている親ディレクトリ DirectoryConfig dirConfig = DirectoryConfig.getInstance(); File baseDir = dirConfig.getCharactersDir(); // キャラクターなんとか機のcharacter.iniがあれば、character.xmlに変換する. convertCharacterNantokaIniToXml(baseDir); // ファイル名をノーマライズする FileNameNormalizer normalizer = FileNameNormalizer.getDefault(); // キャンセルしたことを示すフラグ final AtomicBoolean cancelled = new AtomicBoolean(false); // 有効な論理CPU(CORE)数のスレッドで同時実行させる int numOfProcessors = Runtime.getRuntime().availableProcessors(); final ExecutorService executorSrv = Executors .newFixedThreadPool(numOfProcessors); try { // キャラクターデータ対象ディレクトリを列挙し、並列に解析する File[] dirs = baseDir.listFiles(new FileFilter() { public boolean accept(File pathname) { boolean accept = pathname.isDirectory() && !pathname.getName().startsWith("."); if (accept) { File configFile = new File(pathname, CONFIG_FILE); accept = configFile.exists() && configFile.canRead(); } return accept; } }); if (dirs == null) { dirs = new File[0]; } for (File dir : dirs) { String path = normalizer.normalize(dir.getPath()); final File normDir = new File(path); executorSrv.submit(new Runnable() { public void run() { boolean terminate = false; File characterDataXml = new File(normDir, CONFIG_FILE); if (characterDataXml.exists()) { try { File docBaseFile = new File(normDir, CONFIG_FILE); URI docBase = docBaseFile.toURI(); CharacterData characterData = loadProfile(docBase); terminate = !callback .receiveCharacterData(characterData); } catch (Exception ex) { terminate = !callback.occureException(normDir, ex); } } if (terminate) { // 中止が指示されたらスレッドプールを終了する logger.log(Level.FINE, "shutdownNow listProfile"); executorSrv.shutdownNow(); cancelled.set(true); } } }); } } finally { // タスクの登録を受付終了し、現在のすべてのタスクが完了したらスレッドは終了する. executorSrv.shutdown(); } // タスクの終了を待機できる疑似フューチャーを作成する. Future awaiter = new Future() { public boolean cancel(boolean mayInterruptIfRunning) { if (executorSrv.isTerminated()) { // すでに停止完了済み return false; } executorSrv.shutdownNow(); cancelled.set(true); return true; } public boolean isCancelled() { return cancelled.get(); } public boolean isDone() { return executorSrv.isTerminated(); } public Object get() throws InterruptedException, ExecutionException { try { return get(300, TimeUnit.SECONDS); } catch (TimeoutException ex) { throw new ExecutionException(ex); } } public Object get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException { executorSrv.shutdown(); if (!executorSrv.isTerminated()) { executorSrv.awaitTermination(timeout, unit); } return null; } }; return awaiter; } /** * プロファイルを列挙する.
* 読み取りに失敗した場合はエラーハンドラに通知されるが例外は返されない.
* 一つも正常なプロファイルがない場合は空のリストが返される.
* エラーハンドラの通知は非同期に行われる. * * @param errorHandler * エラーハンドラ、不要ならばnull * @return プロファイルのリスト(表示名順)、もしくは空 */ public List listProfiles( final ProfileListErrorHandler errorHandler) { final List profiles = new ArrayList(); Future awaiter = listProfileAsync(new ListProfileCallback() { public boolean receiveCharacterData(CharacterData characterData) { synchronized (profiles) { profiles.add(characterData); } return true; } public boolean occureException(File dir, Exception ex) { if (errorHandler != null) { errorHandler.occureException(dir, ex); } return true; } }); // すべてのキャラクターデータが読み込まれるまで待機する. try { awaiter.get(); } catch (Exception ex) { logger.log(Level.WARNING, "listProfile abort.", ex); } Collections.sort(profiles, CharacterData.SORT_DISPLAYNAME); return Collections.unmodifiableList(profiles); } public CharacterData loadProfile(URI docBase) throws IOException { if (docBase == null) { throw new IllegalArgumentException(); } // XMLから読み取る CharacterData characterData = characterDataXmlReader .loadCharacterDataFromXML(docBase); return characterData; } protected void saveCharacterDataToXML(CharacterData characterData) throws IOException { if (characterData == null) { throw new IllegalArgumentException(); } characterData.checkWritable(); if (!characterData.isValid()) { throw new IOException("invalid profile: " + characterData); } URI docBase = characterData.getDocBase(); if (!"file".equals(docBase.getScheme())) { throw new IOException("ファイル以外はサポートしていません: " + docBase); } // XML形式で保存(メモリへ) ByteArrayOutputStream bos = new ByteArrayOutputStream(); try { characterDataXmlWriter.writeXMLCharacterData(characterData, bos); } finally { bos.close(); } // 成功したら実際にファイルに出力 File characterPropXML = new File(docBase); File baseDir = characterPropXML.getParentFile(); if (!baseDir.exists()) { if (!baseDir.mkdirs()) { logger.log(Level.WARNING, "can't create directory. " + baseDir); } } FileOutputStream fos = new FileOutputStream(characterPropXML); try { fos.write(bos.toByteArray()); } finally { fos.close(); } } public void saveFavorites(CharacterData characterData) throws IOException { if (characterData == null) { throw new IllegalArgumentException(); } // xml形式 UserData favoritesData = getFavoritesUserData(characterData); OutputStream os = favoritesData.getOutputStream(); try { characterDataXmlWriter.saveFavorites(characterData, os); } finally { os.close(); } } private UserData getFavoritesUserData(CharacterData characterData) { if (characterData == null) { throw new IllegalArgumentException(); } // xml形式の場合、キャラクターディレクトリ上に設定する. URI docBase = characterData.getDocBase(); File characterDir = new File(docBase).getParentFile(); return new FileUserData(new File(characterDir, "favorites.xml")); } /** * お気に入り(Favorites)を読み込む.
* 現在のパーツセットに追加する形で読み込まれ、同じパーツセットIDのものは上書きされます.
* * @param characterData * キャラクターデータ * @throws IOException * 読み込みに失敗した場合 */ public void loadFavorites(CharacterData characterData) throws IOException { if (characterData == null) { throw new IllegalArgumentException(); } UserData favoritesXml = getFavoritesUserData(characterData); if (favoritesXml.exists()) { InputStream is = favoritesXml.openStream(); try { characterDataXmlReader.loadPartsSet(characterData, is); } finally { is.close(); } } } /** * 既存のキャラクター定義を削除する.
* 有効なdocBaseがあり、そのxmlファイルが存在するものについて、削除を行う.
* forceRemoveがtrueでない場合はキャラクター定義 character.xmlファイルの拡張子を * リネームすることでキャラクター定義として認識させなくする.
* forceRevmoeがtrueの場合は実際にファイルを削除する.
* character.xml、favorites、workingsetのキャッシュも削除される.
* * @param cd * キャラクター定義 * @param forceRemove * ファイルを削除する場合はtrue、リネームして無効にするだけならfalse * @throws IOException * 削除またはリネームできなかった場合 */ public void remove(CharacterData cd, boolean forceRemove) throws IOException { if (cd == null || cd.getDocBase() == null) { throw new IllegalArgumentException(); } URI docBase = cd.getDocBase(); File xmlFile = new File(docBase); if (!xmlFile.exists() || !xmlFile.isFile()) { // すでに存在しない場合 return; } // favories.xmlの削除 if (forceRemove) { UserData[] favoritesDatas = new UserData[]{getFavoritesUserData(cd)}; for (UserData favoriteData : favoritesDatas) { if (favoriteData != null && favoriteData.exists()) { logger.log(Level.INFO, "remove file: " + favoriteData); favoriteData.delete(); } } } // ワーキングセットの削除 // XML形式でのワーキングセットの保存 WorkingSetPersist workingSetPersist = WorkingSetPersist.getInstance(); workingSetPersist.removeWorkingSet(cd); // xmlファイルの拡張子を変更することでキャラクター定義として認識させない. // (削除に失敗するケースに備えて先にリネームする.) String suffix = "." + System.currentTimeMillis() + ".deleted"; File bakFile = new File(xmlFile.getPath() + suffix); if (!xmlFile.renameTo(bakFile)) { throw new IOException("can not rename configuration file.:" + xmlFile); } // ディレクトリ File baseDir = xmlFile.getParentFile(); if (!forceRemove) { // 削除されたディレクトリであることを識別できるようにディレクトリ名も変更する. File parentBak = new File(baseDir.getPath() + suffix); if (!baseDir.renameTo(parentBak)) { throw new IOException("can't rename directory. " + baseDir); } } else { // 完全に削除する removeRecursive(baseDir); } } /** * 指定したファイルを削除します.
* 指定したファイルがディレクトリを示す場合、このディレクトリを含む配下のすべてのファイルとディレクトリを削除します.
* * @param file * ファイル、またはディレクトリ * @throws IOException * 削除できない場合 */ protected void removeRecursive(File file) throws IOException { if (file == null) { throw new IllegalArgumentException(); } if (!file.exists()) { return; } if (file.isDirectory()) { File[] children = file.listFiles(); if (children != null) { for (File child : children) { removeRecursive(child); } } } if (!file.delete()) { throw new IOException("can't delete file. " + file); } } protected Iterable iterable(final NodeList nodeList) { final int mx; if (nodeList == null) { mx = 0; } else { mx = nodeList.getLength(); } return new Iterable() { public Iterator iterator() { return new Iterator() { private int idx = 0; public boolean hasNext() { return idx < mx; } public Node next() { if (idx >= mx) { throw new NoSuchElementException(); } return nodeList.item(idx++); } public void remove() { throw new UnsupportedOperationException(); } }; } }; } protected URL getEmbeddedResourceURL(String schemaName) { return this.getClass().getResource(schemaName); } /** * サンプルピクチャを読み込む.
* ピクチャが存在しなければnullを返す. キャラクター定義がValidでない場合は常にnullを返す.
* * @param characterData * キャラクター定義、null不可 * @param loader * イメージのローダー、null不可 * @return ピクチャのイメージ、もしくはnull * @throws IOException * ピクチャの読み取りに失敗した場合 */ public BufferedImage loadSamplePicture(CharacterData characterData, ImageLoader loader) throws IOException { if (characterData == null || loader == null) { throw new IllegalArgumentException(); } if (!characterData.isValid()) { return null; } File sampleImageFile = getSamplePictureFile(characterData); if (sampleImageFile != null && sampleImageFile.exists()) { LoadedImage loadedImage = loader.load(new FileImageResource( sampleImageFile)); return loadedImage.getImage(); } return null; } /** * キャラクターのサンプルピクチャが登録可能であるか?
* キャラクターデータが有効であり、且つ、ファイルの書き込みが可能であればtrueを返す.
* キャラクターデータがnullもしくは無効であるか、ファイルプロトコルでないか、ファイルが書き込み禁止であればfalseょ返す.
* * @param characterData * キャラクターデータ * @return 書き込み可能であればtrue、そうでなければfalse */ public boolean canSaveSamplePicture(CharacterData characterData) { if (characterData == null || !characterData.isValid()) { return false; } File sampleImageFile = getSamplePictureFile(characterData); if (sampleImageFile != null) { if (sampleImageFile.exists() && sampleImageFile.canWrite()) { return true; } if (!sampleImageFile.exists()) { File parentDir = sampleImageFile.getParentFile(); if (parentDir != null) { return parentDir.canWrite(); } } } return false; } /** * サンプルピクチャとして認識されるファイル位置を返す.
* ファイルが実在するかは問わない.
* DocBaseが未設定であるか、ファィルプロトコルとして返せない場合はnullを返す.
* * @param characterData * キャラクター定義 * @return サンプルピクチャの保存先のファイル位置、もしくはnull */ protected File getSamplePictureFile(CharacterData characterData) { if (characterData == null) { throw new IllegalArgumentException(); } URI docBase = characterData.getDocBase(); if (docBase != null && "file".endsWith(docBase.getScheme())) { File docBaseFile = new File(docBase); return new File(docBaseFile.getParentFile(), SAMPLE_IMAGE_FILENAME); } return null; } /** * サンプルピクチャを保存する. * * @param characterData * キャラクターデータ * @param samplePicture * サンプルピクチャ * @throws IOException * 保存に失敗した場合 */ public void saveSamplePicture(CharacterData characterData, BufferedImage samplePicture) throws IOException { if (!canSaveSamplePicture(characterData)) { throw new IOException("can not write a sample picture.:" + characterData); } File sampleImageFile = getSamplePictureFile(characterData); // canSaveSamplePictureで書き込み先検証済み if (samplePicture != null) { // 登録または更新 // pngで保存するので背景色は透過になるが、一応、コードとしては入れておく。 AppConfig appConfig = AppConfig.getInstance(); Color sampleImageBgColor = appConfig.getSampleImageBgColor(); ImageSaveHelper imageSaveHelper = new ImageSaveHelper(); imageSaveHelper.savePicture(samplePicture, sampleImageBgColor, sampleImageFile, null); } else { // 削除 if (sampleImageFile.exists()) { if (!sampleImageFile.delete()) { throw new IOException("sample pucture delete failed. :" + sampleImageFile); } } } } /** * character.iniを読み取り、character.xmlを生成します.
* すでにcharacter.xmlがある場合は上書きされます.
* 途中でエラーが発生した場合はcharacter.xmlは削除されます.
* * @param characterIniFile * 読み取るcharatcer.iniファイル * @param characterXmlFile * 書き込まれるcharacter.xmlファイル * @param version * デフォルトキャラクターセットのバージョン * @param description * 説明 * @throws IOException * 失敗した場合 */ public void convertFromCharacterIni(File characterIniFile, File characterXmlFile, DefaultCharacterDataVersion version, String description) throws IOException { if (characterIniFile == null || characterXmlFile == null || version == null) { throw new IllegalArgumentException(); } // character.iniから、character.xmlの内容を構築する. FileInputStream is = new FileInputStream(characterIniFile); CharacterData characterData; try { CharacterDataIniReader iniReader = new CharacterDataIniReader(); characterData = iniReader.readCharacterDataFromIni(is, version); } finally { is.close(); } // 説明文を設定する if (description != null) { characterData.setDescription(description); } // docBase URI docBase = characterXmlFile.toURI(); characterData.setDocBase(docBase); // character.xmlの書き込み boolean succeeded = false; try { FileOutputStream outstm = new FileOutputStream(characterXmlFile); try { characterDataXmlWriter.writeXMLCharacterData(characterData, outstm); } finally { outstm.close(); } succeeded = true; } finally { if (!succeeded) { // 途中で失敗した場合は生成ファイルを削除しておく. try { if (characterXmlFile.exists()) { characterXmlFile.delete(); } } catch (Exception ex) { logger.log(Level.WARNING, "ファイルの削除に失敗しました。:" + characterXmlFile, ex); } } } } /** * お勧めリンクリストが設定されていない場合(nullの場合)、デフォルトのお勧めリストを設定する.
* すでに設定されている場合(空を含む)は何もしない.
*
* おすすめリンクがサポートされてなかったころのデータは、おすすめリンク用のタグそのものが存在せずnullとなる.
* サポート後のデータでリンクを未設定にしている場合は、空のリストとなる.
* したがって、nullの場合のみ、おすすめリンクを補完する.
* * @param characterData * キャラクターデータ */ public void compensateRecommendationList(CharacterData characterData) { if (characterData == null) { throw new IllegalArgumentException(); } if (characterData.getRecommendationURLList() != null) { // 補填の必要なし return; } CharacterDataDefaultProvider defProv = new CharacterDataDefaultProvider(); CharacterData defaultCd = defProv .createDefaultCharacterData(DefaultCharacterDataVersion.V3); characterData.setRecommendationURLList(defaultCd .getRecommendationURLList()); } } CharacterManaJ/src/charactermanaj/model/io/WorkingSetXMLReader.java0000644000175000017500000002015212560206305025474 0ustar paulliupaulliupackage charactermanaj.model.io; import static charactermanaj.util.XMLUtilities.getChildElements; import static charactermanaj.util.XMLUtilities.getElementText; import java.awt.Color; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.net.URI; import java.net.URISyntaxException; import java.util.HashMap; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.logging.Logger; import org.w3c.dom.Document; import org.w3c.dom.Element; import charactermanaj.model.IndependentPartsColorInfo; import charactermanaj.model.IndependentPartsSetInfo; import charactermanaj.model.WorkingSet2; import charactermanaj.ui.model.WallpaperInfo; import charactermanaj.ui.model.WallpaperInfo.WallpaperResourceType; import charactermanaj.util.XMLUtilities; /** * ワーキングセットのXMLデータを読み込む.
* * @author seraphy */ public class WorkingSetXMLReader { /** * ロガー */ private static final Logger logger = Logger .getLogger(WorkingSetXMLReader.class.getCanonicalName()); /** * WorkingSetのXMLファイルの名前空間 */ private static final String NS_PREFIX = "http://charactermanaj.sourceforge.jp/schema/charactermanaj-workingset"; /** * XMLコンテンツに対する入力ストリームからワーキングセットを取り出す.
* * @param is * 入力ストリーム * @throws IOException * 読み取りに失敗 */ public WorkingSet2 loadWorkingSet(InputStream is) throws IOException { if (is == null) { throw new IllegalArgumentException(); } WorkingSet2 workingSet = new WorkingSet2(); CharacterDataXMLReader characterDataXMLReader = new CharacterDataXMLReader(); Document doc = XMLUtilities.loadDocument(is); String lang = Locale.getDefault().getLanguage(); try { Element docElm = doc.getDocumentElement(); if (!"character-workingset".equals(docElm.getNodeName())) { throw new IOException("Invalid Format."); } String ns = docElm.getNamespaceURI(); if (ns == null || !ns.startsWith(NS_PREFIX)) { throw new IOException("unsupported xml format"); } String docVersion = docElm.getAttribute("version").trim(); if (!"1.0".equals(docVersion)) { throw new IOException("unsupported version: " + docVersion); } // docbase String characterDocBase = docElm.getAttribute("characterDocBase") .trim(); try { workingSet.setCharacterDocBase(new URI(characterDocBase)); } catch (URISyntaxException ex) { IOException ex2 = new IOException("WorkingSet invalid format."); ex2.initCause(ex); throw ex2; } // character data signature String characterDataSig = getElementText(docElm, "characterDataSig"); workingSet.setCharacterDataSig(characterDataSig); // キーはカテゴリid, 値は、パーツ名をキーとしレイヤーごとのカラー情報のリストを値とするマップ HashMap>> partsColorMap = new HashMap>>(); for (Element partsColorInfoMapElm : getChildElements(docElm, "partsColorInfoMap")) { // カラー定義マップを読み込む HashMap> colorMap = new HashMap>(); for (Element colorsElm : getChildElements(partsColorInfoMapElm, "colors")) { for (Element colorElm : getChildElements(colorsElm, "color")) { String colorId = colorElm.getAttribute("id"); List colorInfoList = characterDataXMLReader .readPartsColor(colorElm); colorMap.put(colorId, colorInfoList); } } // パーツごとのカラー情報を読み込む for (Element partsListElm : getChildElements( partsColorInfoMapElm, "partsList")) { for (Element partsElm : getChildElements(partsListElm, "partsIdentifier")) { String categoryId = partsElm.getAttribute("categoryId"); String name = partsElm.getAttribute("name"); String colorId = partsElm.getAttribute("colorId"); Map> partsMap = partsColorMap .get(categoryId); if (partsMap == null) { partsMap = new HashMap>(); partsColorMap.put(categoryId, partsMap); } List colorInfo = colorMap .get(colorId); if (colorInfo == null) { logger.warning("undefined colorId:" + colorId); } else { partsMap.put(name, colorInfo); } } } } workingSet.setPartsColorMap(partsColorMap); // 最後に使用した保存先ディレクトリ String lastUsedSaveDirStr = getElementText(docElm, "lastUsedSaveDir"); if (lastUsedSaveDirStr != null && lastUsedSaveDirStr.trim().length() > 0) { workingSet.setLastUsedSaveDir(new File(lastUsedSaveDirStr .trim())); } // 最後に使用したエクスポート先ディレクトリ String lastUsedExportDirStr = getElementText(docElm, "lastUsedExportDir"); if (lastUsedExportDirStr != null && lastUsedExportDirStr.trim().length() > 0) { workingSet.setLastUsedExportDir(new File(lastUsedExportDirStr .trim())); } // 壁紙情報 WallpaperInfo wallpaperInfo = null; for (Element wallpaperElm : getChildElements(docElm, "wallpaperInfo")) { wallpaperInfo = readWallpaperInfo(wallpaperElm); break; // wallpaperInfoは最初の一要素しか想定しない. } workingSet.setWallpaperInfo(wallpaperInfo); // 現在のパーツ情報 for (Element currentPartsSetsElm : getChildElements(docElm, "currentPartsSet")) { for (Element presetElm : getChildElements(currentPartsSetsElm, "preset")) { IndependentPartsSetInfo currentPartsSet = characterDataXMLReader .loadPartsSet(presetElm, lang); workingSet.setCurrentPartsSet(currentPartsSet); break; // 最初の一要素のみ } break; // 最初の一要素のみ } // 最後に使ったお気に入り情報 for (Element lastUsePresetPartsElm : getChildElements(docElm, "lastUsePresetParts")) { for (Element presetElm : getChildElements( lastUsePresetPartsElm, "preset")) { IndependentPartsSetInfo lastUsePresetParts = characterDataXMLReader .loadPartsSet(presetElm, lang); workingSet.setLastUsePresetParts(lastUsePresetParts); break; // 最初の一要素のみ } break; // 最初の一要素のみ } return workingSet; } catch (RuntimeException ex) { IOException ex2 = new IOException("WorkingSet invalid format."); ex2.initCause(ex); throw ex2; } } /** * 壁紙情報を読み込む * * @param elm * 壁紙要素 * @return 壁紙情報、elmがnullの場合はnullを返す. */ private WallpaperInfo readWallpaperInfo(Element elm) { if (elm == null) { return null; } WallpaperInfo wallpaperInfo = new WallpaperInfo(); String typStr = getElementText(elm, "type"); WallpaperResourceType typ = WallpaperResourceType.valueOf(typStr); wallpaperInfo.setType(typ); String res = getElementText(elm, "resource"); if (res != null && res.trim().length() > 0) { wallpaperInfo.setResource(res.trim()); } String fileStr = getElementText(elm, "file"); if (fileStr != null && fileStr.trim().length() > 0) { wallpaperInfo.setFile(new File(fileStr.trim())); } float alpha = 0f; String alphaStr = getElementText(elm, "alpha"); if (alphaStr != null && alphaStr.trim().length() > 0) { alpha = Float.parseFloat(alphaStr); wallpaperInfo.setAlpha(alpha); } String backgroundColorStr = getElementText(elm, "backgroundColor"); if (backgroundColorStr != null && backgroundColorStr.trim().length() > 0) { Color backgroundColor = Color.decode(backgroundColorStr.trim()); wallpaperInfo.setBackgroundColor(backgroundColor); } return wallpaperInfo; } } CharacterManaJ/src/charactermanaj/model/io/CharacterDataDefaultProvider.java0000644000175000017500000002750212560206305027410 0ustar paulliupaulliupackage charactermanaj.model.io; import java.io.BufferedOutputStream; import java.io.File; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.net.URI; import java.net.URL; import java.sql.Timestamp; import java.util.EnumSet; import java.util.Enumeration; import java.util.LinkedHashMap; import java.util.Map; import java.util.Properties; import java.util.logging.Level; import java.util.logging.Logger; import charactermanaj.model.CharacterData; import charactermanaj.util.ConfigurationDirUtilities; import charactermanaj.util.LocalizedResourcePropertyLoader; import charactermanaj.util.ResourceLoader; import charactermanaj.util.ResourceNames; import charactermanaj.util.SetupLocalization; /** * デフォルトキャラクターセットのプロバイダ * * @author seraphy */ public class CharacterDataDefaultProvider { /** * リソースに格納されているデフォルトのキャラクター定義のリソースパスまでのプレフィックス.
*/ public static final String DEFAULT_CHARACTER_PREFIX = "template/"; /** * テンプレートをリストしているXML形式のプロパティファイル名 */ public static final String TEMPLATE_LIST_XML = "characterDataTemplates"; /** * デフォルトのキャラクターセット名(ver2) */ public static final String DEFAULT_CHARACTER_NAME_V2 = "character2.xml"; /** * デフォルトのキャラクターセット名(ver3) */ public static final String DEFAULT_CHARACTER_NAME_V3 = "character3.xml"; /** * ロガー */ private static final Logger logger = Logger .getLogger(CharacterDataDefaultProvider.class.getName()); public enum DefaultCharacterDataVersion { V2() { public String getResourceName() { return DEFAULT_CHARACTER_NAME_V2; } }, V3() { public String getResourceName() { return DEFAULT_CHARACTER_NAME_V3; } }; public abstract String getResourceName(); public CharacterData create(CharacterDataDefaultProvider prov) { if (prov == null) { throw new IllegalArgumentException(); } try { return prov.loadPredefinedCharacterData(getResourceName()); } catch (IOException ex) { throw new RuntimeException( "can not create the default profile from application's resource", ex); } } } /** * デフォルトのキャラクター定義を生成して返す.
* 一度生成された場合はキャッシュされる.
* 生成されたキャラクター定義のdocBaseはnullであるため、docBaseをセットすること.
* * @param version * デフォルトキャラクターセットのバージョン * @return キャラクター定義 */ public synchronized CharacterData createDefaultCharacterData( DefaultCharacterDataVersion version) { if (version == null) { throw new IllegalArgumentException(); } return version.create(this); } /** * テンプレートリストの定義プロパティを読み込む.
* neutral引数がfalseの場合は現在のロケールを優先する.
* 引数がtrueの場合は読み込み順を逆転させ、言語中立を優先する.
* * @param neutral * 言語中立を優先する場合 * @return テンプレートリストのプロパティ */ private Properties getTemplateListProperties(boolean neutral) { // テンプレートリソースは実行中に増減する可能性があるため、 // 共有キャッシュには入れない. LocalizedResourcePropertyLoader loader = new LocalizedResourcePropertyLoader( null); String name = DEFAULT_CHARACTER_PREFIX + TEMPLATE_LIST_XML; ResourceNames resNames = LocalizedResourcePropertyLoader .getResourceNames(name, null); if (neutral) { // 言語中立を優先する場合は、プロパティの重ね順を逆転させて言語中立で最後に上書きする. resNames = resNames.reverse(); } return loader.getLocalizedProperties(name); } /** * キャラクターデータのxmlファイル名をキーとし、表示名を値とするマップ.
* 表示順序でアクセス可能.
* * @return 順序付マップ */ public Map getCharacterDataTemplates() { // キャラクターデータのxmlファイル名をキーとし、表示名を値とするマップ final LinkedHashMap templateNameMap = new LinkedHashMap(); // テンプレートの定義プロパティのロード Properties props = getTemplateListProperties(false); // 順序優先 String strOrders = props.getProperty("displayOrder"); if (strOrders != null) { for (String key : strOrders.split(",")) { key = key.trim(); String val = props.getProperty(key); if (val != null && val.trim().length() > 0) { String resKey = DEFAULT_CHARACTER_PREFIX + key; if (getResource(resKey) != null) { // 現存するテンプレートのみ登録 templateNameMap.put(key, val); } } } } // 順序が指定されていないアイテムの追加 Enumeration enm = props.propertyNames(); while (enm.hasMoreElements()) { String key = (String) enm.nextElement(); String val = props.getProperty(key); if (key.endsWith(".xml")) { String resKey = DEFAULT_CHARACTER_PREFIX + key; if (getResource(resKey) != null) { // 現存するテンプレートのみ登録 templateNameMap.put(key, val); } } } // フォルダにある未登録のxmlファイルもテンプレート一覧に加える // (ただし、テンプレートリストプロパティを除く) try { File templDir = getTemplateDir(false); if (templDir.isDirectory()) { File[] files = templDir.listFiles(new java.io.FileFilter() { public boolean accept(File pathname) { String name = pathname.getName(); if (templateNameMap.containsKey(name)) { // すでに登録済みなのでスキップする. return false; } if (name.startsWith(TEMPLATE_LIST_XML)) { // テンプレートリストプロパティファイルは除外する. return false; } return pathname.isFile() && name.endsWith(".xml"); } }); if (files == null) { files = new File[0]; } CharacterDataPersistent persist = CharacterDataPersistent .getInstance(); for (File file : files) { try { URI docBase = file.toURI(); CharacterData cd = persist.loadProfile(docBase); if (cd != null && cd.isValid()) { String name = file.getName(); templateNameMap.put(name, cd.getName()); } } catch (IOException ex) { logger.log(Level.WARNING, "failed to read templatedir." + file, ex); } } } } catch (IOException ex) { // ディレクトリの一覧取得に失敗しても無視する. logger.log(Level.FINE, "failed to read templatedir.", ex); } return templateNameMap; } /** * XMLリソースファイルから、定義済みのキャラクターデータを生成して返す.
* (現在のロケールの言語に対応するデータを取得し、なければ最初の言語で代替する.)
* 生成されたキャラクター定義のdocBaseはnullであるため、使用する場合はdocBaseをセットすること.
* 都度、XMLファイルから読み込まれる.
* * @return デフォルトキャラクターデータ * @throws IOException * 失敗 */ public CharacterData loadPredefinedCharacterData(String name) throws IOException { CharacterData cd; String resKey = DEFAULT_CHARACTER_PREFIX + name; URL predefinedCharacter = getResource(resKey); if (predefinedCharacter == null) { throw new FileNotFoundException(resKey); } InputStream is = predefinedCharacter.openStream(); try { logger.log(Level.INFO, "load a predefined characterdata. resKey=" + resKey); CharacterDataXMLReader characterDataXmlReader = new CharacterDataXMLReader(); cd = characterDataXmlReader.loadCharacterDataFromXML(is, null); } finally { is.close(); } return cd; } /** * リソースを取得する.
* * @param resKey * リソースキー * @return リソース、なければnull */ protected URL getResource(String resKey) { ResourceLoader resourceLoader = new ResourceLoader(); return resourceLoader.getResource(resKey); } /** * カスタマイズ用のテンプレートディレクトリを取得する.
* まだ作成されていない場合で、prepareが指示されている場合はフォルダを準備し、 既定のファイルを作成する.
* * @param prepare * 実際にディレクトリを準備する場合はtrue * @return テンプレートディレクトリ */ public File getTemplateDir(boolean prepare) throws IOException { File baseDir = ConfigurationDirUtilities.getUserDataDir(); SetupLocalization setup = new SetupLocalization(baseDir); File resourceDir = setup.getResourceDir(); if (prepare) { // テンプレートリソースが未設定であれば設定する. setup.setupToLocal( EnumSet.of(SetupLocalization.Resources.Template), false); // ディレクトリがなければ作成しておく if (resourceDir.exists()) { resourceDir.mkdirs(); } } return new File(resourceDir, DEFAULT_CHARACTER_PREFIX); } /** * "characterDataTemplates*.xml"のファイルは管理ファイルのため、
* ユーザによる書き込みは禁止とする.
* * @param name * @return 書き込み可能であるか? */ public boolean canFileSave(String name) { if (name.trim().startsWith("characterDataTemplates")) { return false; } return true; } /** * 指定したキャラクターデータをテンプレートとして保存する.
* * @param name * 保存するテンプレートファイル名 * @param cd * キャラクターデータ * @param localizedName * 表示名 * @throws IOException */ public void saveTemplate(String name, CharacterData cd, String localizedName) throws IOException { if (name == null || !canFileSave(name)) { throw new IllegalArgumentException(); } // テンプレートファイル位置の準備 // (リソースが、まだファイルに展開されていなれば展開する) File templDir = getTemplateDir(true); File templFile = new File(templDir, name); // キャラクターデータをXML形式でテンプレートファイルへ保存 CharacterDataXMLWriter characterDataXmlWriter = new CharacterDataXMLWriter(); BufferedOutputStream bos = new BufferedOutputStream( new FileOutputStream(templFile)); try { // パーツセットなしの状態とし、名前をローカライズ名に設定する. CharacterData templCd = cd.duplicateBasicInfo(false); templCd.setName(localizedName); characterDataXmlWriter.writeXMLCharacterData(templCd, bos); } finally { bos.close(); } // テンプレートの定義プロパティのロード(言語中立を優先) Properties neutralProps = getTemplateListProperties(true); // テンプレート一覧の更新 neutralProps.put(name, localizedName); // テンプレート一覧の保存 File neutralPropsFile = new File(templDir, TEMPLATE_LIST_XML + ".xml"); BufferedOutputStream fos = new BufferedOutputStream( new FileOutputStream(neutralPropsFile)); try { neutralProps.storeToXML(fos, new Timestamp(System.currentTimeMillis()).toString()); } finally { bos.close(); } } } CharacterManaJ/src/charactermanaj/model/io/CharacterDataXMLReader.java0000644000175000017500000006017312560206305026075 0ustar paulliupaulliupackage charactermanaj.model.io; import static charactermanaj.util.XMLUtilities.getChildElements; import static charactermanaj.util.XMLUtilities.getElementText; import static charactermanaj.util.XMLUtilities.getFirstChildElement; import static charactermanaj.util.XMLUtilities.getLocalizedElementText; import java.awt.Color; import java.awt.Dimension; import java.io.IOException; import java.io.InputStream; import java.net.URI; import java.net.URL; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.logging.Level; import java.util.logging.Logger; import org.w3c.dom.Document; import org.w3c.dom.Element; import charactermanaj.graphics.colormodel.ColorModels; import charactermanaj.graphics.filters.ColorConv; import charactermanaj.graphics.filters.ColorConvertParameter; import charactermanaj.model.CharacterData; import charactermanaj.model.ColorGroup; import charactermanaj.model.IndependentPartsColorInfo; import charactermanaj.model.IndependentPartsSetInfo; import charactermanaj.model.IndependentPartsSetInfoList; import charactermanaj.model.Layer; import charactermanaj.model.PartsCategory; import charactermanaj.model.PartsSet; import charactermanaj.model.RecommendationURL; import charactermanaj.util.XMLUtilities; /** * キャラクターデータを格納したXMLを読み込むためのクラス. * * @author seraphy */ public class CharacterDataXMLReader { /** * character.xmlのデフォルトの名前空間 */ private static final String NS_PREFIX = "http://charactermanaj.sourceforge.jp/schema/charactermanaj"; /** * favorites.xmlのデフォルトの名前空間. * (character.xmlの名前空間と別にすべきだったかもしれないが、v0.991まで、これでやってきたので、とりあえず、このままとする。) */ private static final String NS_PREFIX_FAVORITES = "http://charactermanaj.sourceforge.jp/schema/charactermanaj"; /** * ロガー */ private static final Logger logger = Logger .getLogger(CharacterDataXMLReader.class.getName()); /** * キャラクター定義(プロファイル)をロードする. * * @param docBase * 対象xml * @return キャラクターデータ * @throws IOException */ public CharacterData loadCharacterDataFromXML(URI docBase) throws IOException { if (docBase == null) { throw new IllegalArgumentException(); } URL docBaseURL = docBase.toURL(); CharacterData cd; InputStream is = docBaseURL.openStream(); try { cd = loadCharacterDataFromXML(is, docBase); } finally { is.close(); } return cd; } /** * XMLコンテンツに対する入力ストリームからキャラクターデータを取り出す.
* docbaseはXMLファイルの位置を示すものであり、XMLデータ中には含まれず、キャラクターデータのロード時にセットされる.
* そのため引数としてdocbaseを引き渡す.
* 読み取りは現在のデフォルトロケールで行われる.
* * @param is * 入力ストリーム * @param docBase * XMLファイルの位置を示すURI、nullの場合はnullが設定される。 * @param docInfo * ドキュメントのタイプ * @return 読み取られたプロファイル * @throws IOException * 読み取りに失敗 */ public CharacterData loadCharacterDataFromXML(InputStream is, URI docBase) throws IOException { return loadCharacterDataFromXML(is, docBase, Locale.getDefault()); } /** * XMLコンテンツに対する入力ストリームからキャラクターデータを取り出す.
* docbaseはXMLファイルの位置を示すものであり、XMLデータ中には含まれず、キャラクターデータのロード時にセットされる.
* そのため引数としてdocbaseを引き渡す.
* 設定ファイル中の表示文字列にロケール指定がある場合、引数に指定したロケールに合致する言語の情報を取得する.
* 合致するものがなければ最初のものを使用する.
* * @param is * 入力ストリーム * @param docBase * XMLファイルの位置を示すURI、nullの場合はnullが設定される。 * @param docInfo * ドキュメントのタイプ * @param locale * 読み取るロケール * @return 読み取られたプロファイル * @throws IOException * 読み取りに失敗 */ public CharacterData loadCharacterDataFromXML(InputStream is, URI docBase, Locale locale) throws IOException { if (is == null || locale == null) { throw new IllegalArgumentException(); } Document doc = XMLUtilities.loadDocument(is); CharacterData characterData = new CharacterData(); characterData.setDocBase(docBase); try { Element docElm = doc.getDocumentElement(); if (!"character".equals(docElm.getNodeName())) { throw new IOException("Invalid Format."); } String ns = docElm.getNamespaceURI(); if (ns == null || !ns.startsWith(NS_PREFIX)) { throw new IOException("unsupported xml format"); } String docVersion = docElm.getAttribute("version").trim(); if (!"1.0".equals(docVersion)) { throw new IOException("unsupported version: " + docVersion); } String characterId = docElm.getAttribute("id").trim(); String characterRev = docElm.getAttribute("rev").trim(); characterData.setId(characterId); characterData.setRev(characterRev); // language String lang = locale.getLanguage(); // name String characterName = getLocalizedElementText(docElm, "name", lang); if (characterName == null) { characterName = "default"; } characterData.setName(characterName.trim()); // information/author, information/description String author = null; String description = null; for (Element infoElm : getChildElements(docElm, "information")) { if (author == null) { author = getLocalizedElementText(infoElm, "author", lang); } if (description == null) { description = getLocalizedElementText(infoElm, "description", lang); } } if (author == null) { author = ""; } characterData.setAuthor(author.trim()); if (description == null) { description = null; } characterData.setDescription(description); // image-size/width, image-size/height int width = 0; int height = 0; for (Element sizeElm : getChildElements(docElm, "image-size")) { String tmpWidth = getLocalizedElementText(sizeElm, "width", lang); if (tmpWidth != null && tmpWidth.trim().length() > 0) { width = Integer.parseInt(tmpWidth.trim()); } String tmpHeight = getLocalizedElementText(sizeElm, "height", lang); if (tmpHeight != null && tmpHeight.trim().length() > 0) { height = Integer.parseInt(tmpHeight.trim()); } break; } if (width <= 0) { width = 300; } if (height <= 0) { height = 400; } characterData.setImageSize(new Dimension(width, height)); // settings for (Element settingElm : getChildElements(docElm, "settings")) { for (Element entElm : getChildElements(settingElm, "entry")) { String key = entElm.getAttribute("key").trim(); String val = entElm.getTextContent(); characterData.setProperty(key, val); } } // colorGroups/colorGroup ArrayList colorGroups = new ArrayList(); for (Element colorGroupsElm : getChildElements(docElm, "colorGroups")) { for (Element colorGroupElm : getChildElements(colorGroupsElm, "colorGroup")) { String colorGroupId = colorGroupElm.getAttribute("id") .trim(); String colorGroupDisplayName = getLocalizedElementText( colorGroupElm, "display-name", lang); ColorGroup colorGroup = new ColorGroup(colorGroupId, colorGroupDisplayName); colorGroups.add(colorGroup); } } characterData.setColorGroups(colorGroups); // categories/category ArrayList categories = new ArrayList(); for (Element catsElm : getChildElements(docElm, "categories")) { for (Element catElm : getChildElements(catsElm, "category")) { String categoryId = catElm.getAttribute("id").trim(); boolean multipleSelectable = Boolean.parseBoolean(catElm .getAttribute("multipleSelectable")); String categoryDisplayName = getLocalizedElementText( catElm, "display-name", lang); int visibleRows = 0; String tmpVisibleRows = getLocalizedElementText(catElm, "visible-rows", lang); if (tmpVisibleRows != null && tmpVisibleRows.trim().length() > 0) { visibleRows = Integer.parseInt(tmpVisibleRows.trim()); } if (visibleRows <= 0) { visibleRows = 0; } // layers/layer ArrayList layers = new ArrayList(); for (Element layersElm : getChildElements(catElm, "layers")) { for (Element layerElm : getChildElements(layersElm, "layer")) { String layerId = layerElm.getAttribute("id"); String layerDisplayName = getLocalizedElementText( layerElm, "display-name", lang); // レイヤーの重ね順 String strOrder = getElementText(layerElm, "order"); int order = layers.size(); if (strOrder != null && strOrder.trim().length() > 0) { order = Integer.parseInt(strOrder.trim()); } // レイヤーの画像ディレクトリ名 String layerDir = getElementText(layerElm, "dir"); if (layerDir == null || layerDir.trim().length() == 0) { throw new IOException("layer's dir is null"); } // カラーモデル(省略可) String colorModelName = getElementText(layerElm, "colorModel"); if (colorModelName == null || colorModelName.length() == 0) { // 省略時はデフォルトのカラーモデル名を使用する. colorModelName = ColorModels.DEFAULT.name(); } // layer/colorGroup カラーグループ boolean initSync = false; ColorGroup colorGroup = null; Element lcgElm = getFirstChildElement(layerElm, "colorGroup"); if (lcgElm != null) { String tmpInitSync = lcgElm .getAttribute("init-sync"); if (tmpInitSync.trim().length() > 0) { initSync = Boolean.parseBoolean(tmpInitSync .trim()); } if (colorGroup == null) { String colorGroupRefId = lcgElm .getAttribute("refid").trim(); colorGroup = characterData .getColorGroup(colorGroupRefId); } } Layer layer = new Layer(layerId, layerDisplayName, order, colorGroup, initSync, layerDir, colorModelName); layers.add(layer); } } PartsCategory category = new PartsCategory( categories.size(), categoryId, categoryDisplayName, multipleSelectable, visibleRows, layers.toArray(new Layer[layers.size()])); categories.add(category); } } characterData.setPartsCategories(categories .toArray(new PartsCategory[categories.size()])); // presets for (Element presetssElm : getChildElements(docElm, "presets")) { loadPartsSet(characterData, presetssElm, true, lang); } // recommendations List recommendationURLList = null; // お勧めノードがない場合はnull for (Element recmsElm : getChildElements(docElm, "recommendations")) { for (Element recmElm : getChildElements(recmsElm, "recommendation")) { String recommentDescription = getLocalizedElementText( recmElm, "description", lang); String url = getLocalizedElementText(recmElm, "URL", lang); if (recommentDescription != null) { recommentDescription = recommentDescription.trim(); } if (url != null) { url = url.trim(); } RecommendationURL recommendationURL = new RecommendationURL(); recommendationURL.setDisplayName(recommentDescription); recommendationURL.setUrl(url); if (recommendationURLList == null) { recommendationURLList = new ArrayList(); } recommendationURLList.add(recommendationURL); } } characterData.setRecommendationURLList(recommendationURLList); } catch (RuntimeException ex) { IOException ex2 = new IOException("CharacterData invalid format."); ex2.initCause(ex); throw ex2; } return characterData; } /** * 入力ストリームからパーツセット定義(Favorites.xml)を読み込んで、 characterDataに追加登録する.
* * @param characterData * お気に入りを登録されるキャラクターデータ * @param inpstm * お気に入りのxmlへの入力ストリーム * @param docInfo * ドキュメントタイプ * @throws IOException * 読み込みに失敗した場合 */ public void loadPartsSet(CharacterData characterData, InputStream inpstm) throws IOException { if (characterData == null || inpstm == null) { throw new IllegalArgumentException(); } Document doc = XMLUtilities.loadDocument(inpstm); Element docElm = doc.getDocumentElement(); if (!"partssets".equals(docElm.getNodeName())) { logger.log(Level.WARNING, "invalid partsets format."); return; } String ns = docElm.getNamespaceURI(); if (ns == null || !ns.startsWith(NS_PREFIX_FAVORITES)) { logger.log(Level.WARNING, "invalid partsets format."); return; } String lang = Locale.getDefault().getLanguage(); loadPartsSet(characterData, docElm, false, lang); } /** * CharacterDataのプリセットまたはFavoritesのパーツセットのXMLからパーツセットを読み取って登録する.
* * @param characterData * キャラクターデータ * @param nodePartssets * パーツセットのノード、プリセットまたはパーツセットノード * @param presetParts * ロードしたパーツセットにプリセットフラグをたてる場合はtrue * @param lang * 言語 */ protected void loadPartsSet(CharacterData characterData, Element nodePartssets, boolean presetParts, String lang) { IndependentPartsSetInfoList partsSetLst = loadPartsSetList( nodePartssets, lang); logger.info("partsSetList: size=" + partsSetLst.size()); if (presetParts) { characterData .setDefaultPartsSetId(partsSetLst.getDefaultPresetId()); } for (IndependentPartsSetInfo partsSetInfo : partsSetLst) { PartsSet partsSet = IndependentPartsSetInfo.convertPartsSet( partsSetInfo, characterData, presetParts); characterData.addPartsSet(partsSet); } } /** * CharacterDataのプリセットまたはFavoritesのパーツセットのXMLからパーツセットを読み取って登録する.
* * @param nodePartssets * パーツセットのノード、プリセットまたはパーツセットノード * @param lang * 言語 */ public IndependentPartsSetInfoList loadPartsSetList(Element nodePartssets, String lang) { if (nodePartssets == null || lang == null || lang.length() == 0) { throw new IllegalArgumentException(); } IndependentPartsSetInfoList partsSetLst = new IndependentPartsSetInfoList(); // デフォルトのパーツセットID String defaultPresetId = nodePartssets.getAttribute("default-preset"); if (defaultPresetId != null) { defaultPresetId = defaultPresetId.trim(); } // パーツセットリストの読み込み for (Element presetElm : getChildElements(nodePartssets, "preset")) { IndependentPartsSetInfo partsSetInfo = loadPartsSet(presetElm, lang); if (partsSetInfo != null) { String partsSetId = partsSetInfo.getId(); // デフォルトのパーツセットIDがない場合は先頭をデフォルトとみなす. if (defaultPresetId == null || defaultPresetId.length() == 0) { defaultPresetId = partsSetId; } partsSetLst.add(partsSetInfo); } } if (defaultPresetId.length() == 0) { // デフォルトパーツセットがないことを示すためのnull defaultPresetId = null; } partsSetLst.setDefaultPresetId(defaultPresetId); return partsSetLst; } /** * CharacterDataのプリセットまたはFavoritesのパーツセットのXMLからパーツセットを読み取る.
* * @param nodePartssets * パーツセットのノード、プリセットまたはパーツセットノード * @param lang * 言語 * @return 素のパーツセット情報、無ければnull */ public IndependentPartsSetInfo loadPartsSet(Element presetElm, String lang) { if (presetElm == null || lang == null) { return null; } IndependentPartsSetInfo partsSetInfo = new IndependentPartsSetInfo(); // id String partsSetId = presetElm.getAttribute("id"); if (partsSetId != null) { partsSetId = partsSetId.trim(); } if (partsSetId != null && partsSetId.length() == 0) { partsSetId = null; } partsSetInfo.setId(partsSetId); // display-name String displayName = getLocalizedElementText(presetElm, "display-name", lang); partsSetInfo.setDisplayName(displayName); // bgColor Element bgColorElm = getFirstChildElement(presetElm, "background-color"); if (bgColorElm != null) { String tmpBgColor = bgColorElm.getAttribute("color"); try { Color bgColor = Color.decode(tmpBgColor); partsSetInfo.setBackgroundColor(bgColor); } catch (Exception ex) { logger.log(Level.WARNING, "bgColor parameter is invalid. :" + tmpBgColor, ex); // 無視する } } // affine-transform-parameter String tmpAffienTrans = getElementText(presetElm, "affine-transform-parameter"); if (tmpAffienTrans != null && tmpAffienTrans.trim().length() > 0) { try { ArrayList affineTransformParameterArr = new ArrayList(); for (String strParam : tmpAffienTrans.split("\\s+")) { affineTransformParameterArr.add(Double.valueOf(strParam)); } double[] affineTransformParameter = new double[affineTransformParameterArr .size()]; int idx = 0; for (double aaffineItem : affineTransformParameterArr) { affineTransformParameter[idx++] = aaffineItem; } partsSetInfo .setAffineTransformParameter(affineTransformParameter); } catch (Exception ex) { logger.log(Level.WARNING, "affine transform parameter is invalid. :" + tmpAffienTrans, ex); // 無視する. } } // カテゴリIDをキーとし、パーツ名をキーとしカラー情報のリストを値とするマップを値とする. Map>> partsMap = partsSetInfo .getPartsMap(); // Category for (Element catElm : getChildElements(presetElm, "category")) { String categoryId = catElm.getAttribute("refid"); if (categoryId != null) { categoryId = categoryId.trim(); } if (categoryId == null || categoryId.length() == 0) { logger.log(Level.WARNING, "missing category refid: " + catElm); continue; } // パーツ名をキーとしカラー情報のリストを値とするマップ. Map> categoryPartsMap = partsMap .get(categoryId); if (categoryPartsMap == null) { categoryPartsMap = new HashMap>(); partsMap.put(categoryId, categoryPartsMap); } // Parts for (Element partsElm : getChildElements(catElm, "parts")) { String partsName = partsElm.getAttribute("name"); if (partsName != null) { partsName = partsName.trim(); } if (partsName == null || partsName.length() == 0) { logger.log(Level.WARNING, "missing parts name. " + partsElm); continue; } // Color/Layer List infoList = null; for (Element colorElm : getChildElements(partsElm, "color")) { infoList = readPartsColor(colorElm); break; } categoryPartsMap.put(partsName, infoList); } } return partsSetInfo; } /** * パーツごとのカラー情報のXMLを読み込んで返す.
* パーツは複数のレイヤーから構成されるので、複数レイヤーのカラー情報のリストとして返される.
* (パーツは複数レイヤーをまとめる1つのカテゴリになるので、カテゴリ単位の情報となる.)
* * @param colorElm * カラーのXML要素 * @return 素のカラー情報 */ public List readPartsColor(Element colorElm) { if (colorElm == null) { throw new IllegalArgumentException(); } ArrayList infoList = new ArrayList(); for (Element layerElm : getChildElements(colorElm, "layer")) { IndependentPartsColorInfo info = new IndependentPartsColorInfo(); String layerId = layerElm.getAttribute("refid"); if (layerId != null) { layerId = layerId.trim(); } if (layerId == null || layerId.length() == 0) { logger.log(Level.WARNING, "missing layer-id: " + layerElm); continue; } info.setLayerId(layerId); // color-group Element colorGroupElm = getFirstChildElement(layerElm, "color-group"); if (colorGroupElm != null) { String colorGroupId = colorGroupElm.getAttribute("group") .trim(); info.setColorGroupId(colorGroupId); boolean syncColorGroup = Boolean.parseBoolean(colorGroupElm .getAttribute("synchronized").trim()); info.setSyncColorGroup(syncColorGroup); } // rgb ColorConvertParameter param = info.getColorConvertParameter(); Element nodeRgb = getFirstChildElement(layerElm, "rgb"); if (nodeRgb != null) { for (Element elmRgb : getChildElements(nodeRgb, null)) { String rgbName = elmRgb.getNodeName(); int offset = Integer .parseInt(elmRgb.getAttribute("offset")); float factor = Float.parseFloat(elmRgb .getAttribute("factor")); float gamma = Float .parseFloat(elmRgb.getAttribute("gamma")); if ("red".equals(rgbName)) { param.setOffsetR(offset); param.setFactorR(factor); param.setGammaR(gamma); } else if ("green".equals(rgbName)) { param.setOffsetG(offset); param.setFactorG(factor); param.setGammaG(gamma); } else if ("blue".equals(rgbName)) { param.setOffsetB(offset); param.setFactorB(factor); param.setGammaB(gamma); } else if ("alpha".equals(rgbName)) { param.setOffsetA(offset); param.setFactorA(factor); param.setGammaA(gamma); } } } // hsb Element elmHsb = getFirstChildElement(layerElm, "hsb"); if (elmHsb != null) { float hue = Float.parseFloat(elmHsb.getAttribute("hue")); float saturation = Float.parseFloat(elmHsb .getAttribute("saturation")); float brightness = Float.parseFloat(elmHsb .getAttribute("brightness")); String strContrast = elmHsb.getAttribute("contrast").trim(); param.setHue(hue); param.setSaturation(saturation); param.setBrightness(brightness); if (strContrast != null && strContrast.length() > 0) { // ver0.96追加 optional float contrast = Float.parseFloat(strContrast); param.setContrast(contrast); } } // rgb-replace Element elmRgbReplace = getFirstChildElement(layerElm, "rgb-replace"); if (elmRgbReplace != null) { Float grayLevel = Float.parseFloat(elmRgbReplace .getAttribute("gray")); ColorConv colorType = ColorConv.valueOf(elmRgbReplace .getAttribute("replace-type")); param.setGrayLevel(grayLevel); param.setColorReplace(colorType); } infoList.add(info); } return infoList; } } CharacterManaJ/src/charactermanaj/model/io/ExportInfoKeys.java0000644000175000017500000000054012560206305024624 0ustar paulliupaulliupackage charactermanaj.model.io; public interface ExportInfoKeys { String EXPORT_PRESETS = "EXPORT_PRESETS"; String EXPORT_SAMPLE_PICTURE = "EXPORT_SAMPLE_PICTURE"; String EXPORT_CHARACTER_DATA = "EXPORT_CHARACTER_DATA"; String EXPORT_PARTS_IMAGES = "EXPORT_PARTS_IMAGES"; String EXPORT_TIMESTAMP = "EXPORT_TIMESTAMP"; } CharacterManaJ/src/charactermanaj/model/io/PartsInfoXMLReader.java0000644000175000017500000002145512560206305025314 0ustar paulliupaulliupackage charactermanaj.model.io; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.net.URI; import java.sql.Timestamp; import java.text.SimpleDateFormat; import java.util.LinkedList; import java.util.Locale; import java.util.logging.Level; import java.util.logging.Logger; import javax.xml.XMLConstants; import javax.xml.parsers.SAXParser; import javax.xml.parsers.SAXParserFactory; import org.xml.sax.Attributes; import org.xml.sax.SAXException; import org.xml.sax.helpers.DefaultHandler; import charactermanaj.model.PartsAuthorInfo; import charactermanaj.model.PartsManageData; /** * パーツ管理情報のXMLの読み込み用クラス. * * @author seraphy */ public class PartsInfoXMLReader { /** * ロガー */ private static final Logger logger = Logger .getLogger(PartsInfoXMLReader.class.getName()); /** * 指定したDocBaseと同じフォルダにあるparts-info.xmlからパーツ管理情報を取得して返す.
* ファイルが存在しない場合は空のインスタンスを返す.
* 返されるインスタンスは編集可能です.
* * @param docBase * character.xmlの位置 * @return パーツ管理情報、存在しない場合は空のインスタンス * @throws IOException * 読み込み中に失敗した場合 */ public PartsManageData loadPartsManageData(URI docBase) throws IOException { if (docBase == null) { throw new IllegalArgumentException(); } if (!"file".equals(docBase.getScheme())) { throw new IOException("ファイル以外はサポートしていません。:" + docBase); } File docBaseFile = new File(docBase); File baseDir = docBaseFile.getParentFile(); // パーツ管理情報ファイルの確認 final File partsInfoXML = new File(baseDir, "parts-info.xml"); if (!partsInfoXML.exists()) { // ファイルが存在しなければ空を返す. return new PartsManageData(); } PartsManageData partsManageData; InputStream is = new FileInputStream(partsInfoXML); try { partsManageData = loadPartsManageData(is); } finally { is.close(); } return partsManageData; } public PartsManageData loadPartsManageData(InputStream is) throws IOException { if (is == null) { throw new IllegalArgumentException(); } // パーツ管理情報 final PartsManageData partsManageData = new PartsManageData(); // SAXParserの準備 SAXParser saxParser; try { SAXParserFactory saxPartserFactory = SAXParserFactory.newInstance(); saxPartserFactory.setNamespaceAware(true); saxParser = saxPartserFactory.newSAXParser(); } catch (Exception ex) { throw new RuntimeException("JAXP Configuration failed.", ex); } // デフォルトのロケールから言語を取得 final Locale locale = Locale.getDefault(); final String lang = locale.getLanguage(); try { // 要素のスタック final LinkedList stack = new LinkedList(); // 日時コンバータ final SimpleDateFormat dateTimeFmt = new SimpleDateFormat( "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"); // DOMではなくSAXで読み流す. saxParser.parse(is, new DefaultHandler() { private StringBuilder buf = new StringBuilder(); private PartsAuthorInfo partsAuthorInfo; private String authorName; private String homepageURL; private String authorNameLang; private String homepageLang; private String downloadURL; private String partsLocalNameLang; private String partsLocalName; private String partsCategoryId; private String partsName; private double partsVersion; private Timestamp partsLastModified; @Override public void startDocument() throws SAXException { logger.log(Level.FINEST, "parts-info : start"); } @Override public void endDocument() throws SAXException { logger.log(Level.FINEST, "parts-info : end"); } @Override public void characters(char[] ch, int start, int length) throws SAXException { buf.append(ch, start, length); } @Override public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException { stack.addFirst(qName); int mx = stack.size(); if (mx >= 2 && stack.get(1).equals("parts")) { if ("local-name".equals(qName)) { partsLocalNameLang = attributes.getValue( XMLConstants.XML_NS_URI, "lang"); } } else if (mx >= 2 && stack.get(1).equals("author")) { if ("name".equals(qName)) { authorNameLang = attributes.getValue( XMLConstants.XML_NS_URI, "lang"); } else if ("home-page".equals(qName)) { homepageLang = attributes.getValue( XMLConstants.XML_NS_URI, "lang"); } } else if ("author".equals(qName)) { partsAuthorInfo = null; authorName = null; authorNameLang = null; homepageURL = null; homepageLang = null; } else if ("download-url".equals(qName)) { downloadURL = null; } else if ("parts".equals(qName)) { partsLocalName = null; partsLocalNameLang = null; partsCategoryId = attributes.getValue("category"); partsName = attributes.getValue("name"); // バージョン String strVersion = attributes.getValue("version"); try { if (strVersion == null || strVersion.length() == 0) { partsVersion = 0.; } else { partsVersion = Double.parseDouble(strVersion); if (partsVersion < 0) { partsVersion = 0; } } } catch (Exception ex) { logger.log(Level.INFO, "parts-info.xml: invalid version." + strVersion); partsVersion = 0; } // 更新日時 String strLastModified = attributes .getValue("lastModified"); if (strLastModified != null && strLastModified.trim().length() > 0) { try { partsLastModified = new Timestamp(dateTimeFmt .parse(strLastModified.trim()) .getTime()); } catch (Exception ex) { logger.log(Level.INFO, "parts-info.xml: invalid dateTime." + strLastModified); } } } buf = new StringBuilder(); } @Override public void endElement(String uri, String localName, String qName) throws SAXException { int mx = stack.size(); if (mx >= 2 && "parts".equals(stack.get(1))) { if ("local-name".equals(qName)) { if (partsLocalName == null || lang.equals(partsLocalNameLang)) { partsLocalName = buf.toString(); } } } else if (mx >= 2 && "author".equals(stack.get(1))) { if ("name".equals(qName)) { if (authorName == null || lang.equals(authorNameLang)) { authorName = buf.toString(); } } else if ("home-page".equals(qName)) { if (homepageURL == null || lang.equals(homepageLang)) { homepageURL = buf.toString(); } } } else if ("author".equals(qName)) { logger.log(Level.FINE, "parts-info: author: " + authorName + " /homepage:" + homepageURL); if (authorName != null && authorName.length() > 0) { partsAuthorInfo = new PartsAuthorInfo(); partsAuthorInfo.setAuthor(authorName); partsAuthorInfo.setHomePage(homepageURL); } else { partsAuthorInfo = null; } } else if ("download-url".equals(qName)) { downloadURL = buf.toString(); logger.log(Level.FINE, "parts-info: download-url: " + downloadURL); } else if ("parts".equals(qName)) { if (logger.isLoggable(Level.FINE)) { logger.log(Level.FINE, "parts-info.xml: parts-name: " + partsName + " /category: " + partsCategoryId + " /parts-local-name: " + partsLocalName + " /version:" + partsVersion + "/lastModified:" + partsLastModified); } PartsManageData.PartsVersionInfo versionInfo = new PartsManageData.PartsVersionInfo(); versionInfo.setVersion(partsVersion); versionInfo.setDownloadURL(downloadURL); versionInfo.setLastModified(partsLastModified); PartsManageData.PartsKey partsKey = new PartsManageData.PartsKey( partsName, partsCategoryId); partsManageData.putPartsInfo(partsKey, partsLocalName, partsAuthorInfo, versionInfo); } stack.removeFirst(); } }); } catch (SAXException ex) { IOException ex2 = new IOException("parts-info.xml read failed."); ex2.initCause(ex); throw ex2; } return partsManageData; } } CharacterManaJ/src/charactermanaj/model/io/PartsSpecDecorateLoader.java0000644000175000017500000000545212560206305026404 0ustar paulliupaulliupackage charactermanaj.model.io; import java.util.Collection; import java.util.Collections; import java.util.Map; import java.util.regex.Matcher; import java.util.regex.Pattern; import charactermanaj.model.AppConfig; import charactermanaj.model.ColorGroup; import charactermanaj.model.PartsCategory; import charactermanaj.model.PartsIdentifier; import charactermanaj.model.PartsSpec; /** * パーツ名の末尾が、カラーグループの表記を括弧でくくったものと同じであれば、 * そのパーツ固有のカラーグループとして設定するためのデコレータ.
* このクラス自身はパーツのロードは行わず、コンストラクタで指定したローダーによりロードを行い、 * その結果に対してカラーグループの設定を行う.
* * @author seraphy * */ public class PartsSpecDecorateLoader implements PartsDataLoader { private PartsDataLoader parent; private Collection colorGroups; /** * パーツローダとカラーグループを指定して構築する. * @param parent 元パーツローダー * @param colorGroups カラーグループのコレクション、nullの場合は空とみなす. */ public PartsSpecDecorateLoader(PartsDataLoader parent, Collection colorGroups) { if (parent == null) { throw new IllegalArgumentException(); } if (colorGroups == null) { colorGroups = Collections.emptyList(); } this.parent = parent; this.colorGroups = colorGroups; } public Map load(PartsCategory category) { Map partsSpecs = parent.load(category); decolatePartsSpec(partsSpecs); return partsSpecs; } /** * パーツ識別子の表示名に、カラーグループの表示名により判定されるパターンに合致する場合、 * パーツ設定のカラーグループを、そのカラーグループとして設定する. * @param partsSpecs パーツマップ */ protected void decolatePartsSpec(Map partsSpecs) { String templ = AppConfig.getInstance().getPartsColorGroupPattern(); if (templ == null || templ.trim().length() == 0) { // パターンが設定されていない場合は無視する. return; } // パーツ名にカラーグループが含まれる場合、それを登録する. for (ColorGroup colorGroup : colorGroups) { String pattern = templ.replace("@", colorGroup.getLocalizedName()); Pattern pat = Pattern.compile(pattern); for (PartsSpec partsSpec : partsSpecs.values()) { Matcher mat = pat.matcher(partsSpec.getPartsIdentifier() .getLocalizedPartsName()); if (mat.matches()) { partsSpec.setColorGroup(colorGroup); } } } } } CharacterManaJ/src/charactermanaj/model/io/CharacterDataFileReaderWriterFactory.java0000644000175000017500000000475512560206305031045 0ustar paulliupaulliupackage charactermanaj.model.io; import java.io.File; import java.io.IOException; import java.net.URI; public class CharacterDataFileReaderWriterFactory { private static final CharacterDataFileReaderWriterFactory singleton = new CharacterDataFileReaderWriterFactory(); private CharacterDataFileReaderWriterFactory() { super(); } public static CharacterDataFileReaderWriterFactory getInstance() { return singleton; } /** * ファイルの拡張子に応じてzip/cmj形式でのライターを構築して帰します.
* 拡張子がjarとcmjは同じ意味で、ともにjarファイル形式となります.
* zip/cmj/jar以外の拡張子はIOExceptionとなります.
* @param outfile 出力先ファイル名 * @return ライター * @throws IOException 該当するライターがみつからない場合 */ public CharacterDataWriter createWriter(File outfile) throws IOException { if (outfile == null) { throw new IllegalArgumentException(); } String name = outfile.getName().toLowerCase(); if (name.endsWith(".jar") || name.endsWith(".cmj")) { return new CharacterDataJarFileWriter(outfile); } else if (name.endsWith(".zip")) { return new CharacterDataZipFileWriter(outfile); } throw new IOException("unsupported file type: " + name); } public CharacterDataArchiveFile openArchive(URI archiveFile) throws IOException { if (archiveFile == null) { throw new IllegalArgumentException(); } if ("file".equals(archiveFile.getScheme())) { // ファイルまたはディレクトリの場合 File file = new File(archiveFile); return openArchive(file); } // file以外は現在のところサポートしない。 throw new UnsupportedOperationException(); } public CharacterDataArchiveFile openArchive(File archiveFile) throws IOException { if (archiveFile == null) { throw new IllegalArgumentException(); } if (archiveFile.exists() && archiveFile.isDirectory()) { // ディレクトリの場合 return new CharacterDataDirectoryFile(archiveFile); } // zipまたはcmjファイルの場合 String name = archiveFile.getName().toLowerCase(); if (name.endsWith(".jar") || name.endsWith(".cmj")) { return new CharacterDataJarArchiveFile(archiveFile); } else if (name.endsWith(".zip")) { return new CharacterDataZipArchiveFile(archiveFile); } throw new IOException("unsupported file type: " + name); } } CharacterManaJ/src/charactermanaj/model/io/AbstractCharacterDataArchiveFile.java0000644000175000017500000007126512560206305030163 0ustar paulliupaulliupackage charactermanaj.model.io; import java.awt.image.BufferedImage; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.net.URI; import java.net.URISyntaxException; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.Locale; import java.util.Map; import java.util.logging.Level; import java.util.logging.Logger; import javax.imageio.ImageIO; import charactermanaj.graphics.io.PNGFileImageHeader; import charactermanaj.graphics.io.PNGFileImageHeaderReader; import charactermanaj.model.CharacterData; import charactermanaj.model.Layer; import charactermanaj.model.PartsCategory; import charactermanaj.model.PartsManageData; import charactermanaj.model.io.CharacterDataDefaultProvider.DefaultCharacterDataVersion; public abstract class AbstractCharacterDataArchiveFile implements CharacterDataArchiveFile { private static final Logger logger = Logger .getLogger(AbstractCharacterDataArchiveFile.class.getName()); protected File archiveFile; protected String rootPrefix = ""; public interface FileContent { String getEntryName(); long lastModified(); InputStream openStream() throws IOException; } @Override public String toString() { return "CharacterDataArchiveFile(file=" + archiveFile + ")"; } public static final class CategoryLayerPair { private PartsCategory partsCategory; private Layer layer; public CategoryLayerPair(PartsCategory partsCategory, Layer layer) { if (partsCategory == null || layer == null) { throw new IllegalArgumentException(); } this.partsCategory = partsCategory; this.layer = layer; } @Override public int hashCode() { return partsCategory.hashCode() ^ layer.hashCode(); } @Override public boolean equals(Object obj) { if (obj == this) { return true; } if (obj != null && obj instanceof CategoryLayerPair) { CategoryLayerPair o = (CategoryLayerPair) obj; return partsCategory.equals(o.partsCategory) && layer.equals(o.layer); } return false; } public Layer getLayer() { return layer; } public PartsCategory getPartsCategory() { return partsCategory; } @Override public String toString() { return "(" + partsCategory + ":" + layer + ")"; } } public static final class PartsImageContent implements FileContent { private final FileContent fileContent; private final Collection categoryLayerPairs; private final String dirName; private final String partsName; private final String fileName; private final PNGFileImageHeader pngFileImageHeader; /** * パーツイメージを示す.
* * @param fileContent * ファイルコンテンツ * @param categoryLayerPairs * カテゴリとレイヤーのペア * @param partsName * ファイル名(ファイル名のみ。拡張子を含まない。パーツ名の元として用いることを想定。) * @param pngFileImageHeader * PNGファイルヘッダ */ protected PartsImageContent(FileContent fileContent, Collection categoryLayerPairs, String fileName, String partsName, PNGFileImageHeader pngFileImageHeader) { if (fileContent == null || categoryLayerPairs == null || categoryLayerPairs.isEmpty() || fileName == null || partsName == null || pngFileImageHeader == null) { throw new IllegalArgumentException(); } this.fileContent = fileContent; this.categoryLayerPairs = Collections .unmodifiableCollection(categoryLayerPairs); this.fileName = fileName; this.partsName = partsName; this.pngFileImageHeader = pngFileImageHeader; CategoryLayerPair categoryLayerPair = categoryLayerPairs.iterator() .next(); dirName = categoryLayerPair.getLayer().getDir(); } @Override public int hashCode() { return getEntryName().hashCode(); } @Override public boolean equals(Object obj) { if (obj == this) { return true; } if (obj != null && obj instanceof PartsImageContent) { return getEntryName().equals( ((PartsImageContent) obj).getEntryName()); } return false; } public Collection getCategoryLayerPairs() { return categoryLayerPairs; } public String getDirName() { return dirName; } public String getEntryName() { return fileContent.getEntryName(); } public String getFileName() { return fileName; } public String getPartsName() { return partsName; } public PNGFileImageHeader getPngFileImageHeader() { return pngFileImageHeader; } public long lastModified() { return fileContent.lastModified(); } public InputStream openStream() throws IOException { return fileContent.openStream(); } @Override public String toString() { return fileContent.getEntryName(); } } protected HashMap entries = new HashMap(); protected AbstractCharacterDataArchiveFile(File archiveFile) { if (archiveFile == null) { throw new IllegalArgumentException(); } this.archiveFile = archiveFile; } public File getArchiveFile() { return this.archiveFile; } protected void addEntry(FileContent fileContent) { if (fileContent == null) { throw new IllegalArgumentException(); } if (logger.isLoggable(Level.FINE)) { logger.log(Level.FINE, fileContent.getEntryName()); } entries.put(fileContent.getEntryName(), fileContent); } /** * アーカイブファイルをベースとしたURIを返す.
* * @param name * コンテンツ名 * @return URI * @throws IOException * URIを生成できない場合 */ protected URI getContentURI(String name) throws IOException { try { URI baseURI = archiveFile.toURI(); return new URI("jar:" + baseURI.toString() + "/!" + name); } catch (URISyntaxException ex) { IOException iex = new IOException(ex.getMessage()); iex.initCause(ex); throw iex; } } /** * 指定したコンテンツが存在するか? * * @param name * コンテンツ名 * @return 存在すればtrue、存在しなければfalse */ public boolean hasContent(String name) { return entries.containsKey(name); } /** * 指定したコンテンツを取得する.
* 存在しない場合はnullを返す.
* * @param name * コンテンツ名 * @return 存在すればコンテンツ、存在しなければnull */ public FileContent getContent(String name) { return entries.get(name); } public String getRootPrefix() { return this.rootPrefix; } /** * アーカイブのルート上に単一のフォルダしかない場合、そのフォルダを真のルートとして設定する.
* 返されるルート名には末尾に「/」を含む.
* ルート上に複数のフォルダがあるかファイルが存在する場合は空文字を設定する.
*/ protected void searchRootPrefix() { HashSet dirs = new HashSet(); for (String name : entries.keySet()) { int pos = name.indexOf('/'); if (pos < 0) { // ルート上にファイルがあるので、ここがルート rootPrefix = ""; return; } if (pos >= 0) { String dir = name.substring(0, pos + 1); dirs.add(dir); } } if (dirs.size() == 1) { // ルート上に単一のフォルダしかないので、 // このフォルダが真のルート rootPrefix = dirs.iterator().next(); return; } // ルート上に複数のフォルダがあるので、ここがルート rootPrefix = ""; } /** * 指定したディレクトリ(フルパス指定)のファイルのみを取り出す.
* サブフォルダは含まない.
* ディレクトリに空文字またはnullまたは「/」を指定した場合はルートを意味する.
* * @param dir * ディレクトリ * @return ファイルへのフルパスのコレクション */ public Map getFiles(String dir) { if (dir == null) { dir = ""; } if (dir.length() > 0 && !dir.endsWith("/")) { dir += "/"; } if (dir.equals("/")) { dir = ""; // アーカイブ内コンテンツのパスは先頭は「/」ではないため。 } HashMap files = new HashMap(); int ep = dir.length(); for (Map.Entry entry : entries.entrySet()) { String name = entry.getKey(); FileContent fileContent = entry.getValue(); if (name.startsWith(dir)) { String suffix = name.substring(ep); int sep = suffix.indexOf('/'); if (sep < 0) { files.put(name, fileContent); } } } return files; } /** * キャラクター定義を読み込む.
* アーカイブに存在しないか、妥当性がない場合はnullを返す.
* * @return キャラクター定義、もしくはnull */ public CharacterData readCharacterData() { FileContent characterFile = entries.get(rootPrefix + CharacterDataPersistent.CONFIG_FILE); if (characterFile == null) { return null; } CharacterDataXMLReader xmlReader = new CharacterDataXMLReader(); try { // character.xmlを読み込む CharacterData cd; InputStream is = characterFile.openStream(); try { URI docBase = getContentURI(rootPrefix + CharacterDataPersistent.CONFIG_FILE); cd = xmlReader.loadCharacterDataFromXML(is, docBase); } finally { is.close(); } return cd; } catch (Exception ex) { logger.log(Level.INFO, "character.xml load failed.", ex); return null; } } /** * キャラクター定義をINIファイルより読み取る.
* アーカイブのコンテンツルート上のcharacter.iniを探す.
* それがなければ、アーカイブ上のどこかにある/character.iniを探して、もっとも短い場所にある1つを採用する.
* character.iniが何処にも存在しないか、読み取り時にエラーとなった場合はnullを返す.
* 「キャラクターなんとか機 v2.2」の設定ファイルを想定している.
* ただし、character.iniの下にeye_colorフォルダがある場合は「ver3」とみなす。
* (設定ファイルの形式はv2, v3の間で変わらず) * * @return キャラクター定義、もしくはnull */ public CharacterData readCharacterINI() { FileContent characterFile = null; characterFile = entries.get(rootPrefix + CharacterDataPersistent.COMPATIBLE_CONFIG_NAME); // どこかにあるcharacter.iniを探す // および、eye_colorフォルダがあるか?(あればver3形式とみなす。) boolean hasEyeColorFolder = false; ArrayList characterInis = new ArrayList(); for (Map.Entry entry : entries.entrySet()) { String entryName = entry.getKey(); if (entryName.endsWith("/" + CharacterDataPersistent.COMPATIBLE_CONFIG_NAME)) { characterInis.add(entryName); } if (entryName.contains("/eye_color/") || entryName.endsWith("/eye_color")) { hasEyeColorFolder = true; } } if (characterFile == null) { // もっとも短い名前のものを採用 Collections.sort(characterInis); if (characterInis.size() > 0) { characterFile = entries.get(characterInis.get(0)); } } if (characterFile == null) { // character.iniがないので何もしない. return null; } DefaultCharacterDataVersion version; if (hasEyeColorFolder) { // "eye_color"フォルダがあればver3形式とみなす version = DefaultCharacterDataVersion.V3; } else { version = DefaultCharacterDataVersion.V2; } try { // デフォルトのキャラクター定義を構築する. CharacterData cd; InputStream is = characterFile.openStream(); try { CharacterDataIniReader iniReader = new CharacterDataIniReader(); cd = iniReader.readCharacterDataFromIni(is, version); } finally { is.close(); } // docBaseを設定する. URI docBase = getContentURI(rootPrefix + CharacterDataPersistent.COMPATIBLE_CONFIG_NAME); cd.setDocBase(docBase); return cd; } catch (Exception ex) { logger.log(Level.INFO, "character.ini load failed", ex); return null; } } /** * お気に入りを読み込みキャラクター定義に追加する.
* アーカイブにお気に入り(favorites.xml)が存在しなければ何もしない.
* 読み取り中に失敗した場合は、その時点で読み込みを止めて戻る.
* * @param characterData * キャラクター定義(お気に入りが追加される) */ public void readFavorites(CharacterData characterData) { if (characterData == null) { throw new IllegalArgumentException("characterDataにnullは指定できません。"); } FileContent favoritesXml = entries.get(rootPrefix + "favorites.xml"); if (favoritesXml == null) { // favorites.xmlがなければ何もしない return; } CharacterDataXMLReader xmlReader = new CharacterDataXMLReader(); try { // favorites.xmlを読み込む InputStream is = favoritesXml.openStream(); try { xmlReader.loadPartsSet(characterData, is); } catch (Exception ex) { logger.log(Level.INFO, "favorites.xml load failed.", ex); } finally { is.close(); } } catch (Exception ex) { logger.log(Level.INFO, "favorites.xml load failed", ex); } } /** * サンプルピクチャを読み込む.
* アーカイブに存在しないか、画像として読み取れなかった場合はnull * * @return サンプルピクチャ、もしくはnull */ public BufferedImage readSamplePicture() { FileContent samplePictureFile = entries.get(rootPrefix + "preview.png"); if (samplePictureFile == null) { Map files = getFiles(rootPrefix); samplePictureFile = files.get("preview.jpg"); if (samplePictureFile == null) { samplePictureFile = files.get("preview.jpeg"); } if (samplePictureFile == null) { for (Map.Entry entry : files.entrySet()) { String name = entry.getKey(); if (name.endsWith(".jpg") || name.endsWith(".jpeg") || name.endsWith(".png")) { samplePictureFile = entry.getValue(); break; } } } } if (samplePictureFile == null) { return null; } try { BufferedImage pic; InputStream is = samplePictureFile.openStream(); try { pic = ImageIO.read(is); } finally { is.close(); } return pic; } catch (Exception ex) { logger.log(Level.INFO, "sample picture load failed.", ex); return null; } } /** * アーカイブにある「readme.txt」、もしくは「readme」というファイルをテキストファイルとして読み込む.
* readme.txtが優先される。ともに存在しない場合はnull.
* 改行コードはプラットフォーム固有の改行コードに変換して返される.
* 読み取れなかった場合はnull.
* * @return テキスト、もしくはnull */ public String readReadMe() { Locale locale = Locale.getDefault(); String lang = locale.getLanguage(); ArrayList candiates = new ArrayList(); Map files = getFiles(rootPrefix); for (String findName : new String[]{"readme_" + lang + ".txt", "readme_" + lang, "readme.txt", "readme", null}) { for (Map.Entry entry : files.entrySet()) { String name = entry.getKey().toLowerCase(); if (findName == null && name.endsWith(".txt")) { candiates.add(entry.getValue()); break; } if (name.equals(findName)) { candiates.add(entry.getValue()); } } } if (candiates.isEmpty()) { return null; } // みつかったファイルについて読み込みを優先順で試行する. for (FileContent file : candiates) { try { return readTextUTF16(file); } catch (Exception ex) { logger.log(Level.WARNING, "read file failed. :" + file, ex); // 無視して次の候補を試行する. } } // すべて失敗したか、そもそもファイルがみつからなかった。 return null; } /** * ファイルをテキストとして読み取り、返す.
* UTF-16LE/BE/UTF-8についてはBOMにより判定する.
* BOMがない場合はUTF-16/8ともに判定できない.
* BOMがなければMS932もしくはEUC_JPであると仮定して読み込む.
* 改行コードはプラットフォーム固有の改行コードに変換して返される.
* * @param name * コンテンツ名 * @return テキスト、コンテンツが存在しない場合はnull * @throws IOException * 読み込みに失敗した場合 */ public String readTextFile(String name) throws IOException { if (name == null) { throw new IllegalArgumentException(); } FileContent content = entries.get(name); if (content == null) { return null; } return readTextUTF16(content); } /** * ファイルをテキストとして読み取り、返す.
* UTF-16LE/BE/UTF-8についてはBOMにより判定する.
* BOMがない場合はUTF-16/8ともに判定できない.
* BOMがなければMS932もしくはEUC_JPであると仮定して読み込む.
* * @param content * コンテンツ * @return テキスト * @throws IOException * 読み込みに失敗した場合 */ public String readTextUTF16(FileContent content) throws IOException { if (content == null) { throw new IllegalArgumentException(); } return TextReadHelper.readTextTryEncoding(content.openStream()); } /** * キャラクター定義のカテゴリと、そのレイヤー情報から、画像のディレクトリの一覧をアーカイブ上のディレクトリの一覧として返す.
* ディレクトリの末尾は常にスラ付きとなる.
* enabledRootPefixがtrueの場合、ディレクトリの先頭はアーカイブのコンテンツルートとなる.
* 同一のディレクトリに対して複数のレイヤー(複数カテゴリを含む)が参照している場合、それらを含めて返す.
* 参照されているディレクトリがない場合は返される結果には含まれない.
* * @param characterData * キャラクター定義 * @param enabledRootPrefix * ルートプレフィックス(アーカイブのコンテンツルート)を付与する場合 * @return * パーツで使用する画像のディレクトリとして認識されるディレクトリの一覧、キーはアーカイブのディレクトリ位置、値は参照する1つ以上のレイヤー */ protected Map> getLayerDirs( CharacterData characterData, boolean enabledRootPrefix) { if (characterData == null) { return Collections.emptyMap(); } // イメージディレクトリの一覧 String rootPrefix = getRootPrefix(); HashMap> layerDirs = new HashMap>(); for (PartsCategory partsCategory : characterData.getPartsCategories()) { for (Layer layer : partsCategory.getLayers()) { String dir = layer.getDir(); if (!dir.endsWith("/")) { dir += "/"; // スラ付きにする. } if (enabledRootPrefix) { // アーカイブのルートコンテキストからのディレクトリ位置とする. dir = rootPrefix + dir; } Collection sameDirLayers = layerDirs .get(dir); if (sameDirLayers == null) { sameDirLayers = new ArrayList(); layerDirs.put(dir, sameDirLayers); } sameDirLayers.add(new CategoryLayerPair(partsCategory, layer)); } } return layerDirs; } /** * アーカイブに含まれるフォルダをもつpngファイルからパーツイメージを取得する.
* * @param インポート先のキャラクターデータ * 、フォルダ名などを判別するため。nullの場合はディレクトリなしとみなす.
* @param newly * 新規インポート用であるか?(新規でない場合は引数で指定したキャラクターセットと同じパーツは読み込まれない). * (アーカイブファイルからの読み込みでは無視される) * @return パーツイメージコンテンツのコレクション、なければ空 */ public Collection getPartsImageContents( CharacterData characterData, boolean newly) { // コンテンツルートからの絶対位置指定でパーツイメージを取得する. Collection results = getPartsImageContentsStrict(characterData); if (results.isEmpty()) { // コンテンツルートからの絶対位置にパーツがない場合は、任意のディレクトリ位置からパーツイメージを推定する. results = getPartsImageContentsLazy(characterData); } return results; } /** * コンテンツルートからの絶対位置のフォルダからpngファイルからパーツイメージを取得する.
* * @param インポート先のキャラクターデータ * 、フォルダ名などを判別するため。nullの場合はディレクトリなしとみなす.
* @return パーツイメージコンテンツのコレクション、なければ空 */ protected Collection getPartsImageContentsStrict( CharacterData characterData) { final Map> layerDirMap = getLayerDirs( characterData, true); CategoryLayerPairResolveStrategy strategy = new CategoryLayerPairResolveStrategy() { public Collection resolveCategoryLayerPairs( String dir) { Collection categoryLayerPairs = layerDirMap .get(dir); if (categoryLayerPairs == null || categoryLayerPairs.isEmpty()) { // ディレクトリ名に一致するものがないので、この画像は無視する return null; } return categoryLayerPairs; } }; return getPartsImageContents(strategy); } /** * アーカイブに含まれる任意のフォルダからpngファイルからパーツイメージを取得する.
* ディレクトリ名の大文字・小文字は区別されません.
* * @param インポート先のキャラクターデータ * 、フォルダ名などを判別するため。nullの場合はディレクトリなしとみなす.
* @return パーツイメージコンテンツのコレクション、なければ空 */ protected Collection getPartsImageContentsLazy( CharacterData characterData) { final Map> layerDirMap = getLayerDirs( characterData, false); CategoryLayerPairResolveStrategy strategy = new CategoryLayerPairResolveStrategy() { public Collection resolveCategoryLayerPairs( String dir) { dir = (dir == null) ? "" : dir.toLowerCase(); for (Map.Entry> entry : layerDirMap .entrySet()) { String dirSuffix = entry.getKey().toLowerCase(); Collection categoryLayerPairs = entry .getValue(); if (dir.endsWith(dirSuffix)) { return categoryLayerPairs; } } return null; } }; return getPartsImageContents(strategy); } /** * ディレクトリ名からカテゴリとレイヤーを取得するためのインターフェイス.
* * @author seraphy */ protected interface CategoryLayerPairResolveStrategy { /** * ディレクトリを指定して、それに該当するカテゴリとレイヤーペアのコレクションを返します.
* 同一のディレクトリに対して複数のレイヤーが割り当てられている可能性があるためコレクションで返されます.
* 空のコレクションにはなりません.
* レイヤーとして認識されていないディレクトリの場合はnullを返します.
* * @param dir * ディレクトリ * @return カテゴリ・レイヤーのペアのコレクション、またはnull (空のコレクションにはならない。) */ Collection resolveCategoryLayerPairs(String dir); } /** * アーカイブに含まれるフォルダをもつpngファイルからパーツイメージを取得する。 * * @param strategy * ディレクトリが売れ入れ可能であるか判断するストラテジー * @return パーツイメージコンテンツのコレクション、なければ空 */ protected Collection getPartsImageContents( CategoryLayerPairResolveStrategy strategy) { if (strategy == null) { throw new IllegalArgumentException(); } ArrayList results = new ArrayList(); for (Map.Entry entry : entries.entrySet()) { String name = entry.getKey(); FileContent fileContent = entry.getValue(); String[] split = name.split("/"); if (split.length < 2) { // 最低でもフォルダ下になければならない continue; } String lastName = split[split.length - 1]; if (!lastName.toLowerCase().endsWith(".png")) { // png拡張子でなければならない continue; } // ディレクトリ名 String dir = name.substring(0, name.length() - lastName.length()); // ディレクトリ名から対応するレイヤーを取得します. Collection categoryLayerPairs = strategy .resolveCategoryLayerPairs(dir); if (categoryLayerPairs == null) { // パーツイメージのディレクトリとして定義されていない場合は、この画像は無視される. continue; } // PNGファイルヘッダの取得と確認 PNGFileImageHeader pngFileHeader = readPNGFileHeader(fileContent); if (pngFileHeader == null) { // PNGファイルとして不正なものは無視する. logger.log(Level.WARNING, "invalid png: " + name); continue; } // パーツ名(拡張子を除いたもの) String partsName; int extpos = lastName.lastIndexOf('.'); partsName = lastName.substring(0, extpos); PartsImageContent partsImageContent = new PartsImageContent( fileContent, categoryLayerPairs, lastName, partsName, pngFileHeader); results.add(partsImageContent); } return results; } /** * PNGファイルとしてファイルを読み込みPNGヘッダ情報を返します.
* PNGでないか、ファイルの読み込みに失敗した場合はnullを返します.
* * @param fileContent * 画像ファイル * @return PNGヘッダ情報、またはnull */ protected PNGFileImageHeader readPNGFileHeader(FileContent fileContent) { PNGFileImageHeaderReader pngHeaderReader = PNGFileImageHeaderReader .getInstance(); PNGFileImageHeader pngFileHeader = null; try { InputStream is = fileContent.openStream(); try { pngFileHeader = pngHeaderReader.readHeader(is); } finally { is.close(); } } catch (IOException ex) { logger.log(Level.WARNING, "not png header. " + fileContent, ex); pngFileHeader = null; // 無視する. } return pngFileHeader; } /** * アーカイブに含まれるparts-info.xmlを読み込み返す.
* 存在しなければ空のインスタンスを返す.
* * @return パーツ管理データ * @throws IOException */ public PartsManageData getPartsManageData() throws IOException { FileContent content = entries.get(rootPrefix + "parts-info.xml"); if (content == null) { return new PartsManageData(); } PartsManageData partsManageData; InputStream is = content.openStream(); try { PartsInfoXMLReader xmlWriter = new PartsInfoXMLReader(); partsManageData = xmlWriter.loadPartsManageData(is); } finally { is.close(); } return partsManageData; } } CharacterManaJ/src/charactermanaj/model/io/WorkingSetPersist.java0000644000175000017500000001076712560206305025355 0ustar paulliupaulliupackage charactermanaj.model.io; import java.io.File; import java.io.FileFilter; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.util.logging.Level; import java.util.logging.Logger; import charactermanaj.model.CharacterData; import charactermanaj.model.WorkingSet; import charactermanaj.model.WorkingSet2; import charactermanaj.util.UserData; import charactermanaj.util.UserDataFactory; /** * ワーキングセットの保存と復元.
* * @author seraphy */ public class WorkingSetPersist { /** * ロガー */ private static final Logger logger = Logger .getLogger(WorkingSetPersist.class.getName()); /** * ワーキングセットのサフィックス. */ public static final String WORKINGSET_FILE_SUFFIX = "workingset.xml"; private static final WorkingSetPersist singletion = new WorkingSetPersist(); public static WorkingSetPersist getInstance() { return singletion; } /** * すべてのワーキングセットをクリアする.
*/ public void removeAllWorkingSet() { UserDataFactory userDataFactory = UserDataFactory.getInstance(); File dir = userDataFactory.getSpecialDataDir("foo-" + WORKINGSET_FILE_SUFFIX); if (dir.exists() && dir.isDirectory()) { File[] files = dir.listFiles(new FileFilter() { public boolean accept(File pathname) { return pathname.isFile() && pathname.getName().endsWith( WORKINGSET_FILE_SUFFIX); } }); if (files == null) { logger.log(Level.WARNING, "dir access failed. " + dir); return; } for (File file : files) { boolean success = file.delete(); logger.log(Level.INFO, "remove file: " + file + " /success=" + success); } } } /** * ワーキングセットを削除する. * * @param cd * 対象のキャラクターデータ */ public void removeWorkingSet(CharacterData cd) { UserDataFactory userDataFactory = UserDataFactory.getInstance(); UserData workingSetXmlData = userDataFactory.getMangledNamedUserData( cd.getDocBase(), WORKINGSET_FILE_SUFFIX); if (workingSetXmlData != null && workingSetXmlData.exists()) { logger.log(Level.INFO, "remove file: " + workingSetXmlData); workingSetXmlData.delete(); } } /** * ワーキングセットを保存する.
* ワーキングセットインスタンスには、あらかじめ全て設定しておく必要がある.
* * @param workingSet * ワーキングセット * @throws IOException * 失敗 */ public void saveWorkingSet(WorkingSet workingSet) throws IOException { if (workingSet == null) { throw new IllegalArgumentException(); } CharacterData characterData = workingSet.getCharacterData(); if (characterData == null) { throw new IllegalArgumentException("character-data must be set."); } // XML形式でのワーキングセットの保存 UserDataFactory userDataFactory = UserDataFactory.getInstance(); UserData workingSetXmlData = userDataFactory.getMangledNamedUserData( characterData.getDocBase(), WORKINGSET_FILE_SUFFIX); OutputStream outstm = workingSetXmlData.getOutputStream(); try { WorkingSetXMLWriter workingSetXmlWriter = new WorkingSetXMLWriter(); workingSetXmlWriter.writeWorkingSet(workingSet, outstm); } finally { outstm.close(); } } /** * ワーキングセットを取得する.
* * @param characterData * 対象のキャラクターデータ * @return ワーキングセット、なければnull * @throws IOException * 読み込みに失敗した場合 */ public WorkingSet2 loadWorkingSet(CharacterData characterData) throws IOException { if (characterData == null) { throw new IllegalArgumentException(); } // XML形式でのワーキングセットの復元 UserDataFactory userDataFactory = UserDataFactory.getInstance(); UserData workingSetXmlData = userDataFactory.getMangledNamedUserData( characterData.getDocBase(), WORKINGSET_FILE_SUFFIX); if (workingSetXmlData == null || !workingSetXmlData.exists()) { // 保存されていない場合 return null; } WorkingSet2 workingSet2; InputStream is = workingSetXmlData.openStream(); try { WorkingSetXMLReader WorkingSetXMLReader = new WorkingSetXMLReader(); workingSet2 = WorkingSetXMLReader.loadWorkingSet(is); } finally { is.close(); } return workingSet2; } } CharacterManaJ/src/charactermanaj/model/io/CharacterDataJarArchiveFile.java0000644000175000017500000000224312560206305027122 0ustar paulliupaulliupackage charactermanaj.model.io; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.util.Enumeration; import java.util.jar.JarEntry; import java.util.jar.JarFile; public class CharacterDataJarArchiveFile extends AbstractCharacterDataArchiveFile { protected JarFile jarFile; protected class JarFileContent implements FileContent { private JarEntry entry; protected JarFileContent(JarEntry entry) { this.entry = entry; } public String getEntryName() { return entry.getName(); } public long lastModified() { return entry.getTime(); } public InputStream openStream() throws IOException { return jarFile.getInputStream(entry); } } public void close() throws IOException { jarFile.close(); } public CharacterDataJarArchiveFile(File file) throws IOException { super(file); jarFile = new JarFile(file); load(); } private void load() { Enumeration enm = jarFile.entries(); while (enm.hasMoreElements()) { JarEntry entry = enm.nextElement(); addEntry(new JarFileContent(entry)); } searchRootPrefix(); } } CharacterManaJ/src/charactermanaj/model/io/PartsImageDirectoryWatchAgentFactory.java0000644000175000017500000001422412560206305031116 0ustar paulliupaulliupackage charactermanaj.model.io; import java.net.URI; import java.util.HashMap; import java.util.LinkedList; import java.util.logging.Level; import java.util.logging.Logger; import charactermanaj.model.CharacterData; /** * ディレクトリ監視エージェントのファクトリ.
* このファクトリ単位でディレクト利監視エージェントは保持されます.
* このファクトリ上に、すでに同一のキャラクターデータを監視するエージェントがいる場合、それらのエージェントの実体は * ハンドルによって共有されます.
* @author seraphy */ public final class PartsImageDirectoryWatchAgentFactory { private final Logger logger = Logger.getLogger(getClass().getName()); private static PartsImageDirectoryWatchAgentFactory inst = new PartsImageDirectoryWatchAgentFactory(); private HashMap agents = new HashMap(); private final Object lock = new Object(); private PartsImageDirectoryWatchAgentFactory() { super(); } public static PartsImageDirectoryWatchAgentFactory getFactory() { return inst; } /** * キャラクターデータを指定して、そのキャラクターデータに対する監視エージェントを作成し、そのハンドルを返します.
* すでに、そのキャラクターデータに対するエージェントが存在する場合は、そのハンドルを返します.
* 無効なキャラクターデータ、もしくはnullを指定した場合は、エージェントの実体を持たないダミーのハンドルが返されます.
* @param characterData キャラクターデータ * @return 作成された、もしくは既に作成されている監視エージェントのハンドル */ public PartsImageDirectoryWatchAgent getAgent(CharacterData characterData) { if (characterData == null || !characterData.isValid()) { // キャラクターデータがnullまたは無効である場合は、 // 何もしないダミーのディレクトリハンドルを返す. return new NullWatchAgentHandle(characterData); } URI docBase = characterData.getDocBase(); PartsImageDirectoryWatchAgentThread agentImpl; synchronized (lock) { if (agents.containsKey(docBase)) { agentImpl = agents.get(docBase); } else { agentImpl = new PartsImageDirectoryWatchAgentThread(characterData); agents.put(docBase, agentImpl); logger.log(Level.FINE, "watch agent is cached. " + agentImpl); } } return new PartsImageDirectoryWatchAgentHandle(agentImpl); } } /** * 監視スレッドの実体がない、何もしないハンドル. * @author seraphy */ class NullWatchAgentHandle implements PartsImageDirectoryWatchAgent, PartsImageDirectoryWatchListener { private CharacterData characterData; public NullWatchAgentHandle(CharacterData characterData) { this.characterData = characterData; } public void addPartsImageDirectoryWatchListener( PartsImageDirectoryWatchListener l) { } // なにもしない. public void connect() { // なにもしない. } public void detectPartsImageChange(PartsImageDirectoryWatchEvent e) { // なにもしない. } public void disconnect() { // なにもしない. } public CharacterData getCharcterData() { return characterData; } public void removePartsImageDirectoryWatchListener( PartsImageDirectoryWatchListener l) { // なにもしない. } public void resume() { // なにもしない. } public void suspend() { // なにもしない. } } /** * 監視ディレクトリに対する監視スレッドの実体を複数のメインフレームで共有するための、個々のフレーム用のハンドル.
* @author seraphy */ class PartsImageDirectoryWatchAgentHandle implements PartsImageDirectoryWatchAgent, PartsImageDirectoryWatchListener { /** * ロガー */ private final Logger logger = Logger.getLogger(getClass().getName()); /** * 監視スレッドの実体 */ private final PartsImageDirectoryWatchAgentThread agent; /** * 監視を通知されるリスナー */ private final LinkedList listeners = new LinkedList(); /** * このハンドルで開始要求されているか示すフラグ. */ private boolean connected; protected PartsImageDirectoryWatchAgentHandle(PartsImageDirectoryWatchAgentThread agent) { this.agent = agent; } public CharacterData getCharcterData() { return agent.getCharcterData(); } public void connect() { logger.log(Level.FINE, "agent connect request. " + this); if ( !connected) { agent.addPartsImageDirectoryWatchListener(this); connected = true; } } public void disconnect() { logger.log(Level.FINE, "agent disconnect request. " + this); if (connected) { connected = false; agent.removePartsImageDirectoryWatchListener(this); } } public void suspend() { logger.log(Level.FINE, "agent stop request. " + this); agent.suspend(this); } public void resume() { logger.log(Level.FINE, "agent resume request. " + this); agent.resume(this); } public void addPartsImageDirectoryWatchListener( PartsImageDirectoryWatchListener l) { if (l != null) { synchronized (listeners) { listeners.add(l); } } } public void removePartsImageDirectoryWatchListener( PartsImageDirectoryWatchListener l) { if (l != null) { synchronized (listeners) { listeners.remove(l); } } } public void detectPartsImageChange(PartsImageDirectoryWatchEvent e) { if (!connected) { logger.log(Level.FINE, "skip agent event. " + this); return; } logger.log(Level.FINE, "agent event occured. " + this); PartsImageDirectoryWatchListener[] ls; synchronized (listeners) { ls = listeners.toArray(new PartsImageDirectoryWatchListener[listeners.size()]); } for (PartsImageDirectoryWatchListener l : ls) { l.detectPartsImageChange(e); } } } CharacterManaJ/src/charactermanaj/model/io/PartsImageDirectoryWatchListener.java0000644000175000017500000000024112560206305030307 0ustar paulliupaulliupackage charactermanaj.model.io; public interface PartsImageDirectoryWatchListener { void detectPartsImageChange(PartsImageDirectoryWatchEvent e); } CharacterManaJ/src/charactermanaj/model/io/PartsImageDirectoryWatchAgent.java0000644000175000017500000000234412560206305027566 0ustar paulliupaulliupackage charactermanaj.model.io; import charactermanaj.model.CharacterData; /** * パーツファイルのディレクトリの監視を行うエージェント.
* @author seraphy */ public interface PartsImageDirectoryWatchAgent { /** * 監視対象としているキャラクター定義を取得する. * @return キャラクター定義 */ CharacterData getCharcterData(); /** * 監視を接続する.
* 接続されるまでディレクトリの監視状態は通知されない.
*/ void connect(); /** * 監視を切断する.
* 接続されるまでディレクトリの監視状態は通知されない.
*/ void disconnect(); /** * 監視対象ディレクトリの監視を停止する.
*/ void suspend(); /** * 監視対象ディレクトリの監視再開を許可する.
*/ void resume(); /** * イベントリスナを登録する * @param l リスナ */ void addPartsImageDirectoryWatchListener(PartsImageDirectoryWatchListener l); /** * イベントリスナを登録解除する * @param l リスナ */ void removePartsImageDirectoryWatchListener(PartsImageDirectoryWatchListener l); } CharacterManaJ/src/charactermanaj/model/io/WorkingSetXMLWriter.java0000644000175000017500000002722512560206305025556 0ustar paulliupaulliupackage charactermanaj.model.io; import java.awt.Color; import java.io.File; import java.io.IOException; import java.io.OutputStream; import java.io.OutputStreamWriter; import java.net.URI; import java.nio.charset.Charset; import java.util.LinkedHashMap; import java.util.Locale; import java.util.Map; import javax.xml.XMLConstants; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.ParserConfigurationException; import javax.xml.transform.OutputKeys; import javax.xml.transform.Transformer; import javax.xml.transform.TransformerConfigurationException; import javax.xml.transform.TransformerException; import javax.xml.transform.TransformerFactory; import javax.xml.transform.dom.DOMSource; import javax.xml.transform.stream.StreamResult; import org.w3c.dom.Document; import org.w3c.dom.Element; import charactermanaj.model.CharacterData; import charactermanaj.model.PartsColorInfo; import charactermanaj.model.PartsIdentifier; import charactermanaj.model.PartsSet; import charactermanaj.model.WorkingSet; import charactermanaj.ui.model.WallpaperInfo; import charactermanaj.ui.model.WallpaperInfo.WallpaperResourceType; /** * WorkingSetのXMLへの書き込み */ public class WorkingSetXMLWriter { /** * WorkingSetのバージョン */ private static final String VERSION_SIG_1_0 = "1.0"; /** * WorkingSetのXMLファイルの名前空間 */ private static final String NS = "http://charactermanaj.sourceforge.jp/schema/charactermanaj-workingset"; /** * キャラクターデータのXML化 */ private CharacterDataXMLWriter characterDataXmlWriter = new CharacterDataXMLWriter(NS); /** * ワーキングセットをXML表現で出力ストリームに出力します.
* * @param ws * ワーキングセット * @param outstm * 出力先ストリーム * @throws IOException * 失敗 */ public void writeWorkingSet(WorkingSet ws, OutputStream outstm) throws IOException { if (ws == null || outstm == null) { throw new IllegalArgumentException(); } Document doc = createWorkingSetXML(ws); // output xml TransformerFactory txFactory = TransformerFactory.newInstance(); txFactory.setAttribute("indent-number", Integer.valueOf(4)); Transformer tfmr; try { tfmr = txFactory.newTransformer(); } catch (TransformerConfigurationException ex) { throw new RuntimeException("JAXP Configuration Failed.", ex); } tfmr.setOutputProperty(OutputKeys.INDENT, "yes"); // JDK-4504745 : javax.xml.transform.Transformer encoding does not work properly // http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4504745 final String encoding = "UTF-8"; tfmr.setOutputProperty("encoding", encoding); try { tfmr.transform(new DOMSource(doc), new StreamResult( new OutputStreamWriter(outstm, Charset.forName(encoding)))); } catch (TransformerException ex) { IOException ex2 = new IOException("XML Convert failed."); ex2.initCause(ex); throw ex2; } } /** * ワーキングセットのXMLドキュメントを生成します. * * @param ws * ワーキングセット * @return XMLドキュメント */ public Document createWorkingSetXML(WorkingSet ws) { if (ws == null) { throw new IllegalArgumentException(); } Document doc; try { DocumentBuilderFactory factory = DocumentBuilderFactory .newInstance(); factory.setNamespaceAware(true); DocumentBuilder builder = factory.newDocumentBuilder(); doc = builder.newDocument(); } catch (ParserConfigurationException ex) { throw new RuntimeException("JAXP Configuration failed.", ex); } Locale locale = Locale.getDefault(); String lang = locale.getLanguage(); Element root = doc.createElementNS(NS, "character-workingset"); root.setAttribute("version", VERSION_SIG_1_0); root.setAttributeNS(XMLConstants.XMLNS_ATTRIBUTE_NS_URI, "xmlns:xsi", XMLConstants.W3C_XML_SCHEMA_INSTANCE_NS_URI); root.setAttributeNS(XMLConstants.XMLNS_ATTRIBUTE_NS_URI, "xmlns:xml", XMLConstants.XML_NS_URI); root.setAttribute("xsi:schemaLocation", NS + " character_ws.xsd"); // ドキュメントベース URI docbase = ws.getCharacterDocBase(); root.setAttribute("characterDocBase", docbase == null ? "" : docbase.toString()); // キャラクターデータのシグネチャ CharacterData cd = ws.getCharacterData(); Element characterDataSigElm = doc.createElementNS(NS, "characterDataSig"); if (cd == null || !cd.isValid()) { // 指定されていないか有効でない場合は無しとみなす. characterDataSigElm.setAttributeNS(XMLConstants.W3C_XML_SCHEMA_INSTANCE_NS_URI, "xsi:nil", "true"); } else { characterDataSigElm.setTextContent(cd.toSignatureString()); } root.appendChild(characterDataSigElm); // パーツカラー情報 root.appendChild(writePartsColorInfoMap(doc, ws.getPartsColorInfoMap())); // 現在のパーツセット PartsSet currentPartsSet = ws.getPartsSet(); Element partsSetElm = doc.createElementNS(NS, "currentPartsSet"); if (currentPartsSet == null || currentPartsSet.isEmpty()) { partsSetElm.setAttributeNS(XMLConstants.W3C_XML_SCHEMA_INSTANCE_NS_URI, "xsi:nil", "true"); } else { Element elm = characterDataXmlWriter.createPartsSetXML(doc, lang, currentPartsSet); partsSetElm.appendChild(elm); } root.appendChild(partsSetElm); // 最後に使用した保存先ディレクトリ Element lastUsedSaveDirElm = doc.createElementNS(NS, "lastUsedSaveDir"); File lastUsedSaveDir = ws.getLastUsedSaveDir(); if (lastUsedSaveDir == null) { lastUsedSaveDirElm.setAttributeNS(XMLConstants.W3C_XML_SCHEMA_INSTANCE_NS_URI, "xsi:nil", "true"); } else { lastUsedSaveDirElm.setTextContent(lastUsedSaveDir.getPath()); } root.appendChild(lastUsedSaveDirElm); // 最後に使用したエクスポート先ディレクトリ Element lastUsedExportDirElm = doc.createElementNS(NS, "lastUsedExportDir"); File lastUsedExportDir = ws.getLastUsedExportDir(); if (lastUsedExportDir == null) { lastUsedExportDirElm.setAttributeNS(XMLConstants.W3C_XML_SCHEMA_INSTANCE_NS_URI, "xsi:nil", "true"); } else { lastUsedExportDirElm.setTextContent(lastUsedExportDir.getPath()); } root.appendChild(lastUsedExportDirElm); // 最後に使用したパーツセット情報、なければnull PartsSet lastUsePresetParts = ws.getLastUsePresetParts(); Element lastUsePresetPartsElm = doc.createElementNS(NS, "lastUsePresetParts"); if (lastUsePresetParts == null || lastUsePresetParts.isEmpty()) { lastUsePresetPartsElm.setAttributeNS(XMLConstants.W3C_XML_SCHEMA_INSTANCE_NS_URI, "xsi:nil", "true"); } else { Element elm = characterDataXmlWriter.createPartsSetXML(doc, lang, lastUsePresetParts); lastUsePresetPartsElm.appendChild(elm); } root.appendChild(lastUsePresetPartsElm); // 壁紙情報 root.appendChild(writeWallpaper(doc, ws.getWallpaperInfo())); doc.appendChild(root); return doc; } /** * パーツごとのカラー情報のXML要素を生成して返します. * * @param doc * 要素のファクトリ * @param partsColorMap * パーツごとのカラー情報のマップ * @return パーツごとのカラー情報のXML要素 */ public Element writePartsColorInfoMap(Document doc, Map partsColorMap) { Element partsColorInfoMapElm = doc.createElementNS(NS, "partsColorInfoMap"); if (partsColorMap != null) { // 使用しているカラーの設定ごとに番号を振る // (同じカラー設定であれば同じ番号とする.) LinkedHashMap colorMap = new LinkedHashMap(); for (Map.Entry partsColorEntry : partsColorMap.entrySet()) { PartsColorInfo partsColorInfo = partsColorEntry.getValue(); if (partsColorInfo != null && !partsColorInfo.isEmpty()) { if (!colorMap.containsKey(partsColorInfo)) { colorMap.put(partsColorInfo, Integer.toString(colorMap.size() + 1)); } } } // すべてのカラー設定を出力する. Element colorsElm = doc.createElementNS(NS, "colors"); for (Map.Entry colorMapEntry : colorMap.entrySet()) { PartsColorInfo partsColorInfo = colorMapEntry.getKey(); String id = colorMapEntry.getValue(); Element partsColorElm = characterDataXmlWriter.createPartsColorInfoXML(doc, partsColorInfo); partsColorElm.setAttribute("id", id); colorsElm.appendChild(partsColorElm); } partsColorInfoMapElm.appendChild(colorsElm); // パーツと、そのパーツのカラー設定番号の一覧を出力する. Element partsListElm = doc.createElementNS(NS, "partsList"); for (Map.Entry partsColorEntry : partsColorMap.entrySet()) { PartsIdentifier partsIdentifier = partsColorEntry.getKey(); PartsColorInfo partsColorInfo = partsColorEntry.getValue(); if (partsColorInfo != null && !partsColorInfo.isEmpty()) { String colorId = colorMap.get(partsColorInfo); if (colorId == null) { throw new RuntimeException("colorMapが不整合です"); } Element partsElm = doc.createElementNS(NS, "partsIdentifier"); String categoryId = partsIdentifier.getPartsCategory().getCategoryId(); partsElm.setAttribute("categoryId", categoryId); partsElm.setAttribute("name", partsIdentifier.getPartsName()); partsElm.setAttribute("colorId", colorId); partsListElm.appendChild(partsElm); } } partsColorInfoMapElm.appendChild(partsListElm); } return partsColorInfoMapElm; } /** * 壁紙情報をXML要素として生成する. * * @param doc * XML要素のファクトリ * @param wallpaperInfo * 壁紙情報 * @return 壁紙情報のXML要素 */ public Element writeWallpaper(Document doc, WallpaperInfo wallpaperInfo) { Element elm = doc.createElementNS(NS, "wallpaperInfo"); if (wallpaperInfo == null) { elm.setAttributeNS(XMLConstants.W3C_XML_SCHEMA_INSTANCE_NS_URI, "xsi:nil", "true"); } else { // タイプ WallpaperResourceType typ = wallpaperInfo.getType(); Element typElm = doc.createElementNS(NS, "type"); typElm.setTextContent(typ.name()); elm.appendChild(typElm); // リソース String res = wallpaperInfo.getResource(); Element resElm = doc.createElementNS(NS, "resource"); if (res == null || res.trim().length() == 0) { resElm.setAttributeNS(XMLConstants.W3C_XML_SCHEMA_INSTANCE_NS_URI, "xsi:nil", "true"); } else { resElm.setTextContent(res); } elm.appendChild(resElm); // ファイル File file = wallpaperInfo.getFile(); Element fileElm = doc.createElementNS(NS, "file"); if (file == null) { fileElm.setAttributeNS(XMLConstants.W3C_XML_SCHEMA_INSTANCE_NS_URI, "xsi:nil", "true"); } else { fileElm.setTextContent(file.getPath()); } elm.appendChild(fileElm); // アルファ float alpha = wallpaperInfo.getAlpha(); Element alphaElm = doc.createElementNS(NS, "alpha"); alphaElm.setTextContent(Float.toString(alpha)); elm.appendChild(alphaElm); // 背景色 Color backgroundColor = wallpaperInfo.getBackgroundColor(); Element bgColorElm = doc.createElementNS(NS, "backgroundColor"); bgColorElm.setTextContent("#" + Integer.toHexString(backgroundColor.getRGB() & 0xffffff)); elm.appendChild(bgColorElm); } return elm; } } CharacterManaJ/src/charactermanaj/model/io/PartsDataLoaderFactory.java0000644000175000017500000000243212560206305026237 0ustar paulliupaulliupackage charactermanaj.model.io; import java.io.File; import java.io.IOException; import java.net.URI; /** * パーツデータのローダーファクトリ.
* @author seraphy * */ public class PartsDataLoaderFactory { private static final PartsDataLoaderFactory singleton = new PartsDataLoaderFactory(); private PartsDataLoaderFactory() { super(); } public static PartsDataLoaderFactory getInstance() { return singleton; } /** * 設定ファイル(character.xml)のDocBaseをもとに、キャラクターデータのローダーを作成する.
* 現在のところ、fileプロトコルのみサポートする.
* @param docBase 設定ファイルのURI * @return ローダー * @throws IOException URIに対応するローダーが存在しないか、構築できない場合 */ public PartsDataLoader createPartsLoader(URI docBase) throws IOException { if (docBase == null) { throw new IllegalArgumentException(); } if (!"file".equals(docBase.getScheme())) { throw new IOException("ファイル以外はサポートしていません。:" + docBase); } File docbaseFile = new File(docBase); File baseDir = docbaseFile.getParentFile(); return new FilePartsDataLoader(baseDir); } } CharacterManaJ/src/charactermanaj/model/io/CharacterDataZipFileWriter.java0000644000175000017500000000727312560206305027053 0ustar paulliupaulliupackage charactermanaj.model.io; import java.io.File; import java.io.IOException; import java.io.OutputStream; import java.io.UnsupportedEncodingException; import java.nio.charset.Charset; import java.nio.charset.CharsetEncoder; import java.nio.charset.CodingErrorAction; import org.apache.tools.zip.ZipEntry; import org.apache.tools.zip.ZipOutputStream; import charactermanaj.model.AppConfig; public class CharacterDataZipFileWriter extends AbstractCharacterDataArchivedFileWriter { /** * Zipストリーム */ protected ZipOutputStream zipOutStm; /** * ファイル名のエンコーディング */ protected CharsetEncoder enc; /** * ルートコンテンツへのプレフィックス */ protected String rootPrefix = ""; public CharacterDataZipFileWriter(File outFile) throws IOException { super(outFile); AppConfig appConfig = AppConfig.getInstance(); String zipNameEncoding = appConfig.getZipNameEncoding(); // コンストラクタにストリームではなくファイル名を指定することで、 // 内部でランダムアクセスファイルを使うようになるためヘッダのCRCチェックの書き込み等で有利 this.zipOutStm = new ZipOutputStream(tmpFile); // ファイル名の文字コードを設定する. // (JDKの標準のZipOutputStreamはUTF-8になるが、一般的にはMS932が多いため、Apache Antのものを借用し指定する.) this.enc = Charset.forName(zipNameEncoding).newEncoder(); zipOutStm.setEncoding(zipNameEncoding); enc.onUnmappableCharacter(CodingErrorAction.REPORT); // zipの場合、根本に1つフォルダをたてておく. // 一般的なフォルダ圧縮したものと体裁をそろえるため. String fname = outFile.getName(); int extpos = fname.lastIndexOf('.'); if (extpos > 0) { // ドットで始まる名前の場合は無視 fname = fname.substring(0, extpos); } setRootPrefix(fname); } public void setRootPrefix(String rootPrefix) { if (rootPrefix == null || rootPrefix.trim().equals("/")) { rootPrefix = ""; } if (rootPrefix.length() > 0 && !rootPrefix.endsWith("/")) { rootPrefix += "/"; } this.rootPrefix = rootPrefix.trim(); } public String getRootPrefix() { return rootPrefix; } @Override protected void closeEntry() throws IOException { zipOutStm.closeEntry(); } @Override protected OutputStream getOutputStream() throws IOException { return zipOutStm; } @Override protected void putNextEntry(String name, long lastModified) throws IOException { // ルートプレフィックスをすべてのエントリの登録時に付与する. String fname = rootPrefix + name; // ファイル名がキャラクターセットに合致するか? checkName(fname); // Zipエントリの登録 ZipEntry entry = new ZipEntry(fname); if (lastModified > 0) { entry.setTime(lastModified); } zipOutStm.putNextEntry(entry); } protected void internalClose() throws IOException { zipOutStm.close(); } /** * ファイル名がエンコーディング可能であるかチェックする.
* @param name チェックする名前 * @throws IOException ファイル名が不正である場合 * @throws UnsupportedEncodingException ファイル名がエンコーディングできない場合 */ protected void checkName(String name) throws UnsupportedEncodingException, IOException { if (name == null || name.length() == 0) { throw new IOException("missing entry name"); } if (!enc.canEncode(name)) { throw new UnsupportedEncodingException("file name encoding error.: " + name); } } } CharacterManaJ/src/charactermanaj/model/io/PartsInfoXMLWriter.java0000644000175000017500000002165612560206305025371 0ustar paulliupaulliupackage charactermanaj.model.io; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.OutputStream; import java.io.OutputStreamWriter; import java.net.URI; import java.nio.charset.Charset; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Locale; import javax.xml.XMLConstants; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.ParserConfigurationException; import javax.xml.transform.OutputKeys; import javax.xml.transform.Transformer; import javax.xml.transform.TransformerConfigurationException; import javax.xml.transform.TransformerException; import javax.xml.transform.TransformerFactory; import javax.xml.transform.dom.DOMSource; import javax.xml.transform.stream.StreamResult; import org.w3c.dom.Attr; import org.w3c.dom.Document; import org.w3c.dom.Element; import charactermanaj.model.PartsAuthorInfo; import charactermanaj.model.PartsManageData; import charactermanaj.model.PartsManageData.PartsKey; public class PartsInfoXMLWriter { /** * パーツ定義XMLファイルの名前空間 */ public static final String NS_PARTSDEF = "http://charactermanaj.sourceforge.jp/schema/charactermanaj-partsdef"; /** * パーツ管理情報をDocBaseと同じフォルダ上のparts-info.xmlに書き出す.
* XML生成中に失敗した場合は既存の管理情報は残される.
* (管理情報の書き込み中にI/O例外が発生した場合は管理情報は破壊される.)
* * @param docBase * character.xmlの位置 * @param partsManageData * パーツ管理情報 * @throws IOException * 出力に失敗した場合 */ public void savePartsManageData(URI docBase, PartsManageData partsManageData) throws IOException { if (docBase == null || partsManageData == null) { throw new IllegalArgumentException(); } if (!"file".equals(docBase.getScheme())) { throw new IOException("ファイル以外はサポートしていません: " + docBase); } File docBaseFile = new File(docBase); File baseDir = docBaseFile.getParentFile(); // データからXMLを構築してストリームに出力する. // 完全に成功したXMLのみ書き込むようにするため、一旦バッファする。 ByteArrayOutputStream bos = new ByteArrayOutputStream(); try { savePartsManageData(partsManageData, bos); } finally { bos.close(); } // バッファされたXMLデータを実際のファイルに書き込む File partsInfoXML = new File(baseDir, "parts-info.xml"); FileOutputStream os = new FileOutputStream(partsInfoXML); try { os.write(bos.toByteArray()); } finally { os.close(); } } /** * パーツ管理情報をXMLとしてストリームに書き出す.
* * @param partsManageData * パーツ管理データ * @param outstm * 出力先ストリーム * @throws IOException * 出力に失敗した場合 */ public void savePartsManageData(PartsManageData partsManageData, OutputStream outstm) throws IOException { if (partsManageData == null || outstm == null) { throw new IllegalArgumentException(); } Document doc; try { DocumentBuilderFactory factory = DocumentBuilderFactory .newInstance(); factory.setNamespaceAware(true); DocumentBuilder builder = factory.newDocumentBuilder(); doc = builder.newDocument(); } catch (ParserConfigurationException ex) { throw new RuntimeException("JAXP Configuration Exception.", ex); } Locale locale = Locale.getDefault(); String lang = locale.getLanguage(); Element root = doc.createElementNS(NS_PARTSDEF, "parts-definition"); root.setAttribute("xmlns:xml", XMLConstants.XML_NS_URI); root.setAttribute("xmlns:xsi", "http://www.w3.org/2001/XMLSchema-instance"); root.setAttribute("xsi:schemaLocation", NS_PARTSDEF + " parts-definition.xsd"); doc.appendChild(root); // 作者情報を取得する Collection partsAuthors = partsManageData .getAuthorInfos(); for (PartsAuthorInfo partsAuthorInfo : partsAuthors) { String author = partsAuthorInfo.getAuthor(); if (author == null || author.length() == 0) { continue; } // 作者情報の登録 Element nodeAuthor = doc.createElementNS(NS_PARTSDEF, "author"); Element nodeAuthorName = doc.createElementNS(NS_PARTSDEF, "name"); Attr attrLang = doc.createAttributeNS(XMLConstants.XML_NS_URI, "lang"); attrLang.setValue(lang); nodeAuthorName.setAttributeNodeNS(attrLang); nodeAuthorName.setTextContent(author); nodeAuthor.appendChild(nodeAuthorName); String homepageURL = partsAuthorInfo.getHomePage(); if (homepageURL != null && homepageURL.length() > 0) { Element nodeHomepage = doc.createElementNS(NS_PARTSDEF, "home-page"); Attr attrHomepageLang = doc.createAttributeNS( XMLConstants.XML_NS_URI, "lang"); attrHomepageLang.setValue(lang); nodeHomepage.setAttributeNodeNS(attrHomepageLang); nodeHomepage.setTextContent(homepageURL); nodeAuthor.appendChild(nodeHomepage); } root.appendChild(nodeAuthor); Collection partsKeys = partsManageData .getPartsKeysByAuthor(author); // ダウンロード別にパーツキーの集約 HashMap> downloadMap = new HashMap>(); for (PartsKey partsKey : partsKeys) { PartsManageData.PartsVersionInfo versionInfo = partsManageData .getVersionStrict(partsKey); String downloadURL = versionInfo.getDownloadURL(); if (downloadURL == null) { downloadURL = ""; } List partsKeyGrp = downloadMap.get(downloadURL); if (partsKeyGrp == null) { partsKeyGrp = new ArrayList(); downloadMap.put(downloadURL, partsKeyGrp); } partsKeyGrp.add(partsKey); } // ダウンロード別にパーツ情報の登録 ArrayList downloadURLs = new ArrayList( downloadMap.keySet()); Collections.sort(downloadURLs); // 日時コンバータ final SimpleDateFormat dateTimeFmt = new SimpleDateFormat( "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"); for (String downloadURL : downloadURLs) { List partsKeyGrp = downloadMap.get(downloadURL); Collections.sort(partsKeyGrp); Element nodeDownload = doc.createElementNS(NS_PARTSDEF, "download-url"); nodeDownload.setTextContent(downloadURL); root.appendChild(nodeDownload); for (PartsKey partsKey : partsKeyGrp) { PartsManageData.PartsVersionInfo versionInfo = partsManageData .getVersionStrict(partsKey); Element nodeParts = doc.createElementNS(NS_PARTSDEF, "parts"); nodeParts.setAttribute("name", partsKey.getPartsName()); if (partsKey.getCategoryId() != null) { nodeParts.setAttribute("category", partsKey.getCategoryId()); } if (versionInfo.getVersion() > 0) { nodeParts.setAttribute("version", Double.toString(versionInfo.getVersion())); } if (versionInfo.getLastModified() != null) { nodeParts.setAttribute("lastModified", dateTimeFmt .format(versionInfo.getLastModified())); } String localizedName = partsManageData .getLocalizedName(partsKey); if (localizedName != null && localizedName.trim().length() > 0) { Element nodeLocalizedName = doc.createElementNS( NS_PARTSDEF, "local-name"); Attr attrLocalizedNameLang = doc.createAttributeNS( XMLConstants.XML_NS_URI, "lang"); attrLocalizedNameLang.setValue(lang); nodeLocalizedName .setAttributeNodeNS(attrLocalizedNameLang); nodeLocalizedName.setTextContent(localizedName); nodeParts.appendChild(nodeLocalizedName); } root.appendChild(nodeParts); } } } // output xml TransformerFactory txFactory = TransformerFactory.newInstance(); txFactory.setAttribute("indent-number", Integer.valueOf(4)); Transformer tfmr; try { tfmr = txFactory.newTransformer(); } catch (TransformerConfigurationException ex) { throw new RuntimeException("JAXP Configuration Failed.", ex); } tfmr.setOutputProperty(OutputKeys.INDENT, "yes"); // http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4504745 final String encoding = "UTF-8"; tfmr.setOutputProperty("encoding", encoding); try { tfmr.transform(new DOMSource(doc), new StreamResult( new OutputStreamWriter(outstm, Charset.forName(encoding)))); } catch (TransformerException ex) { IOException ex2 = new IOException("XML Convert failed."); ex2.initCause(ex); throw ex2; } } } CharacterManaJ/src/charactermanaj/model/io/ImportModel.java0000644000175000017500000002620312560206305024132 0ustar paulliupaulliupackage charactermanaj.model.io; import java.awt.image.BufferedImage; import java.io.BufferedInputStream; import java.io.BufferedOutputStream; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.net.URI; import java.util.Collection; import java.util.logging.Level; import java.util.logging.Logger; import charactermanaj.model.AppConfig; import charactermanaj.model.CharacterData; import charactermanaj.model.PartsAuthorInfo; import charactermanaj.model.PartsCategory; import charactermanaj.model.PartsManageData; import charactermanaj.model.io.AbstractCharacterDataArchiveFile.CategoryLayerPair; import charactermanaj.model.io.AbstractCharacterDataArchiveFile.PartsImageContent; import charactermanaj.ui.progress.ProgressHandle; public class ImportModel { /** * ロガー.
*/ private static final Logger logger = Logger.getLogger(ImportModel.class.getName()); /** * インポートもとファイル */ private URI importSource; /** * プロファイル先のキャラクター定義、新規の場合はnull */ private CharacterData currentCharacterData; /** * インポートもとアーカイブ. ロードされた場合に非nullとなる. */ private CharacterDataArchiveFile archiveFile; /** * インポートされたキャラクター定義、なければnull */ private CharacterData sourceCharacterData; /** * インポートされたサンプルピクチャ、なければnull */ private BufferedImage samplePicture; /** * インポートされたreadme */ private String readme; /** * インポート先のキャラクター定義、もしくは現在のキャラクター定義のディレクトリ構成から 読み取ることのできるパーツのコレクション、なければ空.
*/ private Collection partsImageContentsMap; /** * パーツ管理データ、なければ空 */ private PartsManageData partsManageData; public void openImportSource(URI importSource, CharacterData currentCharacterData) throws IOException { if (archiveFile != null || importSource == null) { throw new IllegalStateException("既にアーカイブがオープンされています。"); } this.importSource = importSource; this.currentCharacterData = currentCharacterData; } public void closeImportSource() throws IOException { if (archiveFile != null) { try { archiveFile.close(); } finally { // クローズに失敗しても閉じたことにする. reset(); } } } public void loadContents(ProgressHandle progressHandle) throws IOException { if (archiveFile != null) { throw new IllegalStateException("既にアーカイブがオープンされています。"); } if (importSource == null) { throw new IllegalStateException("インポートファィルが指定されていません。"); } CharacterDataFileReaderWriterFactory factory = CharacterDataFileReaderWriterFactory.getInstance(); progressHandle.setCaption("open archive..."); archiveFile = factory.openArchive(importSource); readme = archiveFile.readReadMe(); progressHandle.setCaption("search the character definition..."); sourceCharacterData = archiveFile.readCharacterData(); if (sourceCharacterData == null) { // character.xmlがない場合は、character.iniで試行する. sourceCharacterData = archiveFile.readCharacterINI(); if (sourceCharacterData != null) { // readmeがあれば、それを説明として登録しておく if (readme != null && readme.trim().length() > 0) { sourceCharacterData.setDescription(readme); } } } else { // character.xmlがあった場合、favorites.xmlもあれば読み込む. archiveFile.readFavorites(sourceCharacterData); } // サンプルピクチャの読み込み、なければnull progressHandle.setCaption("load sample picture..."); samplePicture = archiveFile.readSamplePicture(); // パーツセットの読み込み、なければ空 progressHandle.setCaption("load partssets..."); if (currentCharacterData != null) { // 既存のキャラクターセットの定義をもとにパーツディレクトリを探索する場合 // (インポートもと・インポート先が同一であればパーツは除外される.) partsImageContentsMap = archiveFile.getPartsImageContents(currentCharacterData, false); } else { // インポート元にあるキャラクター定義をもとにパーツディレクトリを探索する場合 partsImageContentsMap = archiveFile.getPartsImageContents(sourceCharacterData, true); } // パーツ管理データの読み込み progressHandle.setCaption("load parts definitions..."); partsManageData = archiveFile.getPartsManageData(); } protected void reset() { importSource = null; archiveFile = null; sourceCharacterData = null; samplePicture = null; readme = null; partsImageContentsMap = null; partsManageData = null; } public URI getImportSource() { return importSource; } protected void checkArchiveOpened() { if (archiveFile == null) { throw new IllegalStateException("アーカイブはオープンされていません。"); } } public CharacterData getCharacterData() { checkArchiveOpened(); return sourceCharacterData; } public BufferedImage getSamplePicture() { checkArchiveOpened(); return samplePicture; } public String getReadme() { checkArchiveOpened(); return readme; } public Collection getPartsImageContents() { checkArchiveOpened(); return partsImageContentsMap; } public PartsManageData getPartsManageData() { checkArchiveOpened(); return partsManageData; } /** * パーツデータをプロファイルの画像ディレクトリに一括コピーする.
* * @param partsImageContents * コピー対象のパーツデータ * @param cd * コピー先のプロファイル * @throws IOException * 失敗 */ public void copyPartsImageContents( Collection partsImageContents, CharacterData cd) throws IOException { if (cd == null || cd.getDocBase() == null) { throw new IllegalArgumentException("invalid character data"); } // コピー先ディレクトリの確定 URI docbase = cd.getDocBase(); if ( !"file".equals(docbase.getScheme())) { throw new IOException("ファイル以外はサポートしていません: " + docbase); } File configFile = new File(docbase); File baseDir = configFile.getParentFile(); if (baseDir == null || !baseDir.isDirectory()) { throw new IOException("親フォルダがディレクトリではありません: " + baseDir); } AppConfig appConfig = AppConfig.getInstance(); byte[] stmbuf = new byte[appConfig.getFileTransferBufferSize()]; // ファイルコピー for (PartsImageContent content : partsImageContents) { InputStream is = new BufferedInputStream(content.openStream()); try { File outDir = new File(baseDir, content.getDirName()); if (!outDir.exists()) { if (!outDir.mkdirs()) { logger.log(Level.WARNING, "can't create the directory. " + outDir); } } File outFile = new File(outDir, content.getFileName()); OutputStream os = new BufferedOutputStream(new FileOutputStream(outFile)); try { for (;;) { int rd = is.read(stmbuf); if (rd < 0) { break; } os.write(stmbuf, 0, rd); } } finally { os.close(); } if (!outFile.setLastModified(content.lastModified())) { logger.log(Level.WARNING, "can't change the modified-date: " + outFile); } } finally { is.close(); } } } /** * パーツ管理情報を更新または作成する.
* パーツ管理情報がnullまたは空であれば何もしない.
* そうでなければインポートするパーツに該当するパーツ管理情報を、現在のプロファイルのパーツ管理情報に追記・更新し、 それを書き出す.
* インポートもとにパーツ管理情報がなく、既存にある場合、インポートしても管理情報は削除されません.(上書きセマンティクスのため) * * @param partsImageContents * インポートするパーツ * @param partsManageData * パーツ管理データ * @param current * 現在のパーツ管理データを保持しているプロファイル、新規の場合はnull * @param target * 書き込み先のプロファイル * @throws IOException * 書き込みに失敗した場合 */ public void updatePartsManageData( Collection partsImageContents, PartsManageData partsManageData, CharacterData current, CharacterData target) throws IOException { if (target == null || !target.isValid()) { throw new IllegalArgumentException(); } if (partsImageContents == null || partsImageContents.isEmpty() || partsManageData == null || partsManageData.isEmpty()) { // インポートするパーツが存在しないか、管理情報がない場合は更新しようがないので何もしないで戻る. return; } PartsInfoXMLReader xmlReader = new PartsInfoXMLReader(); PartsManageData mergedPartsManagedData; if (current != null && current.isValid()) { // 現在のプロファイルからパーツ管理情報を取得する. mergedPartsManagedData = xmlReader.loadPartsManageData(current.getDocBase()); } else { // 新規の場合は空 mergedPartsManagedData = new PartsManageData(); } // インポート対象のパーツに該当するパーツ管理情報のみを取り出して追記する. for (PartsImageContent partsImageContent : partsImageContents) { String partsName = partsImageContent.getPartsName(); for (CategoryLayerPair catLayerPair : partsImageContent.getCategoryLayerPairs()) { PartsCategory partsCategory = catLayerPair.getPartsCategory(); String categoryId = partsCategory.getCategoryId(); PartsManageData.PartsKey partsKey = new PartsManageData.PartsKey(partsName, categoryId); PartsAuthorInfo partsAuthorInfo = partsManageData.getPartsAuthorInfo(partsKey); PartsManageData.PartsVersionInfo versionInfo = partsManageData.getVersion(partsKey); String localizedName = partsManageData.getLocalizedName(partsKey); if (partsAuthorInfo != null || versionInfo != null || localizedName != null) { // いずれかの情報の登録がある場合、パーツ管理情報として追記する. mergedPartsManagedData.putPartsInfo(partsKey, localizedName, partsAuthorInfo, versionInfo); } } } // パーツ管理情報を更新する. PartsInfoXMLWriter partsInfoXMLWriter = new PartsInfoXMLWriter(); partsInfoXMLWriter.savePartsManageData(target.getDocBase(), mergedPartsManagedData); } } CharacterManaJ/src/charactermanaj/model/io/FilePartsDataLoader.java0000644000175000017500000000507312560206305025513 0ustar paulliupaulliupackage charactermanaj.model.io; import java.io.File; import java.io.FileFilter; import java.util.HashMap; import java.util.Map; import charactermanaj.graphics.io.FileImageResource; import charactermanaj.model.Layer; import charactermanaj.model.PartsCategory; import charactermanaj.model.PartsFiles; import charactermanaj.model.PartsIdentifier; import charactermanaj.model.PartsSpec; import charactermanaj.util.FileNameNormalizer; /** * ディレクトリを指定して、そこからキャラクターのパーツデータをロードするローダー.
* * @author seraphy * */ public class FilePartsDataLoader implements PartsDataLoader { /** * ベースディレクトリ */ private File baseDir; public FilePartsDataLoader(File baseDir) { if (baseDir == null) { throw new IllegalArgumentException(); } this.baseDir = baseDir; } public File getBaseDir() { return baseDir; } public Map load(PartsCategory category) { if (category == null) { throw new IllegalArgumentException(); } // ファイル名をノーマライズする FileNameNormalizer normalizer = FileNameNormalizer.getDefault(); final Map images = new HashMap(); for (Layer layer : category.getLayers()) { File searchDir = new File(baseDir, layer.getDir()); if (!searchDir.exists() || !searchDir.isDirectory()) { continue; } File[] imgFiles = searchDir.listFiles(new FileFilter() { public boolean accept(File pathname) { if (pathname.isFile()) { String lcfname = pathname.getName().toLowerCase(); return lcfname.endsWith(".png"); } return false; } }); if (imgFiles == null) { imgFiles = new File[0]; } for (File imgFile : imgFiles) { String partsName = normalizer.normalize(imgFile.getName()); int extpos = partsName.lastIndexOf("."); if (extpos > 0) { partsName = partsName.substring(0, extpos); } PartsIdentifier partsIdentifier = new PartsIdentifier(category, partsName, partsName); PartsSpec partsSpec = images.get(partsIdentifier); if (partsSpec == null) { partsSpec = createPartsSpec(partsIdentifier); images.put(partsIdentifier, partsSpec); } PartsFiles parts = partsSpec.getPartsFiles(); parts.put(layer, new FileImageResource(imgFile)); } } return images; } protected PartsSpec createPartsSpec(PartsIdentifier partsIdentifier) { return new PartsSpec(partsIdentifier); } } CharacterManaJ/src/charactermanaj/model/io/PartsImageDirectoryWatchEvent.java0000644000175000017500000000070112560206305027604 0ustar paulliupaulliupackage charactermanaj.model.io; import java.util.EventObject; import charactermanaj.model.CharacterData; public class PartsImageDirectoryWatchEvent extends EventObject { private static final long serialVersionUID = 8090309437115158185L; public PartsImageDirectoryWatchEvent(CharacterData characterData) { super(characterData); } public CharacterData getCharacterData() { return (CharacterData) getSource(); } } CharacterManaJ/src/charactermanaj/model/io/TextReadHelper.java0000644000175000017500000000543312560206305024561 0ustar paulliupaulliupackage charactermanaj.model.io; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; /** * テキストの読み込みヘルパー.
* * @author seraphy */ public final class TextReadHelper { /** * プライベートコンストラクタ */ private TextReadHelper() { super(); } /** * 入力ストリームを指定して、テキストファイルを読み込みます.
* 入力ストリームは内部で閉じられます.
* * @param is * 入力ストリーム * @return テキスト、もしくはnull * @throws IOException * 失敗 */ public static String readTextTryEncoding(InputStream is) throws IOException { if (is == null) { return null; } // 一旦メモリに取り込む ByteArrayOutputStream bos = new ByteArrayOutputStream(); try { int ch; while ((ch = is.read()) != -1) { bos.write((byte) ch); } } finally { is.close(); } byte[] buf = bos.toByteArray(); String enc = null; if (buf.length >= 2) { // Windowsのメモ帳はUTF-16にBOMをつけるので、これで判定できる。 // 本アプリケーションのエクスポート時もUTF-16LEのBOM付きで出力する。 // 一般的なエディタはUTF-16BEにはBOMをつけないので、事前に判定することはできない。 if ((buf[0] & 0xff) == 0xff && (buf[1] & 0xff) == 0xfe) { enc = "UTF-16LE"; } else if ((buf[0] & 0xff) == 0xfe && (buf[1] & 0xff) == 0xff) { enc = "UTF-16BE"; } } if (enc == null && buf.length >= 3) { if ((buf[0] & 0xff) == 0xef && (buf[1] & 0xff) == 0xbb && (buf[1] & 0xff) == 0xbf) { // Windowsのメモ帳などはUTF-8にBOMをつけるので、これで判定できる。 // 一般的なエディタではUTF-8のBOMはつけないのでUTF-8であるかどうかを事前判定することはできない。 enc = "UTF-8"; } } if (enc == null) { // BOMがない場合はMS932かEUC_JPのいずれかであろう、と仮定する。 enc = "JISAutoDetect"; // SJIS/EUC_JPの自動判定 } // 文字列として変換 StringBuilder str = new StringBuilder(); InputStreamReader rd = new InputStreamReader(new ByteArrayInputStream( buf), enc); try { int ch; while ((ch = rd.read()) != -1) { str.append((char) ch); } } finally { rd.close(); } // 改行コードをプラットフォーム固有のものに変換 String line = str.toString(); line = line.replace("\r\n", "\n"); line = line.replace("\r", "\n"); line = line.replace("\n", System.getProperty("line.separator")); return line; } } CharacterManaJ/src/charactermanaj/model/io/AbstractCharacterDataArchivedFileWriter.java0000644000175000017500000001733112560206305031516 0ustar paulliupaulliupackage charactermanaj.model.io; import java.awt.Color; import java.awt.Dimension; import java.awt.image.BufferedImage; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.io.OutputStreamWriter; import java.io.Writer; import java.nio.charset.Charset; import java.sql.Timestamp; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Properties; import charactermanaj.graphics.io.ImageResource; import charactermanaj.graphics.io.ImageSaveHelper; import charactermanaj.model.AppConfig; import charactermanaj.model.CharacterData; import charactermanaj.model.Layer; import charactermanaj.model.PartsCategory; import charactermanaj.model.PartsFiles; import charactermanaj.model.PartsIdentifier; import charactermanaj.model.PartsManageData; import charactermanaj.model.PartsManageDataConverter; import charactermanaj.model.PartsSet; import charactermanaj.model.PartsSpec; public abstract class AbstractCharacterDataArchivedFileWriter extends AbstractCharacterDataFileWriter { protected AbstractCharacterDataArchivedFileWriter(File outFile) throws IOException { super(outFile); } protected abstract OutputStream getOutputStream() throws IOException; protected abstract void putNextEntry(String name, long lastModified) throws IOException; protected abstract void closeEntry() throws IOException; @Override protected void internalWriteExportProp(Properties prop) throws IOException { // export prop putNextEntry("export-info.xml", 0); prop.storeToXML(getOutputStream(), "exportProp"); } @Override protected void internalWriteCharacterData(CharacterData characterData) throws IOException { CharacterDataXMLWriter xmlWriter = new CharacterDataXMLWriter(); // character.xmlの出力 putNextEntry(CharacterDataPersistent.CONFIG_FILE, 0); xmlWriter.writeXMLCharacterData(characterData, getOutputStream()); closeEntry(); // character.iniの出力 internalWriteCharacterIni(characterData); } /** * character.iniを出力します.
* * @param characterData * キャラクターデータ * @throws IOException * 出力に失敗した場合 */ protected void internalWriteCharacterIni(CharacterData characterData) throws IOException { StringBuilder buf = new StringBuilder(); buf.append("; created by charactermanaj " + new Timestamp(System.currentTimeMillis()) + "\r\n"); buf.append("[Size]\r\n"); Dimension dim = characterData.getImageSize(); if (dim == null) { dim = new Dimension(300, 400); } buf.append("size_x=" + dim.width + "\r\n"); buf.append("size_y=" + dim.height + "\r\n"); buf.append("\r\n"); buf.append("[Parts]\r\n"); Map partsMap = new HashMap(); for (PartsCategory partsCategory : characterData.getPartsCategories()) { String categoryId = partsCategory.getCategoryId(); partsMap.put(categoryId, ""); } Map partsSets = characterData.getPartsSets(); PartsSet partsSet = partsSets.get(characterData.getDefaultPartsSetId()); if (partsSet == null && !partsSets.isEmpty()) { // デフォルトのパーツセットが指定されていない場合は、どれか1つを選択する. partsSet = partsSets.values().iterator().next(); } if (partsSet != null) { for (Map.Entry> entry : partsSet .entrySet()) { PartsCategory partsCategory = entry.getKey(); StringBuilder partsNames = new StringBuilder(); for (PartsIdentifier partsIdentifier : entry.getValue()) { if (partsNames.length() > 0) { partsNames.append(","); } partsNames.append(partsIdentifier.getPartsName()); } String categoryId = partsCategory.getCategoryId(); partsMap.put(categoryId, partsNames.toString()); } } for (PartsCategory partsCategory : characterData.getPartsCategories()) { String categoryId = partsCategory.getCategoryId(); String partsNames = partsMap.get(categoryId); buf.append(categoryId + "=" + partsNames + "\r\n"); } // 色情報はすべてダミー(character.iniは色情報を省略しても問題ないようだが、一応) buf.append("\r\n"); buf.append("[Color]\r\n"); buf.append("hair_rgb=0\r\n"); buf.append("hair_gray=0\r\n"); buf.append("eye_rgb=0\r\n"); buf.append("eye_gray=0\r\n"); buf.append("skin_rgb=0\r\n"); buf.append("skin_gray=0\r\n"); buf.append("body_rgb=0\r\n"); buf.append("body_gray=0\r\n"); // UTF16LEで出力する. internalWriteTextUTF16LE(CharacterDataPersistent.COMPATIBLE_CONFIG_NAME, buf.toString()); } @Override protected void internalWriteTextUTF16LE(String name, String contents) throws IOException { if (contents == null) { contents = ""; } // LFまたはCR改行であればCR/LF改行に変換. contents = contents.replace("\r\n", "\n"); contents = contents.replace("\r", "\n"); contents = contents.replace("\n", "\r\n"); putNextEntry(name, 0); OutputStream os = getOutputStream(); os.write((byte) 0xff); os.write((byte) 0xfe); os.flush(); Writer wr = new OutputStreamWriter(os, Charset.forName("UTF-16LE")) { @Override public void close() throws IOException { // ZipのOutputStreamをクローズしてはならないため // OutputStreamWriter自身はクローズは呼び出さない. flush(); closeEntry(); } }; try { wr.append(contents); wr.flush(); } finally { wr.close(); } } @Override protected void internalWriteSamplePicture(BufferedImage samplePicture) throws IOException { putNextEntry("preview.png", 0); ImageSaveHelper imageSaveHelper = new ImageSaveHelper(); imageSaveHelper.savePicture(samplePicture, Color.white, getOutputStream(), "image/png", null); closeEntry(); } @Override protected void internalWritePartsImages( Map partsImages) throws IOException { AppConfig appConfig = AppConfig.getInstance(); byte[] buf = new byte[appConfig.getJarTransferBufferSize()]; for (Map.Entry entry : partsImages.entrySet()) { PartsIdentifier partsIdentifier = entry.getKey(); PartsSpec partsSpec = entry.getValue(); PartsFiles partsFiles = partsSpec.getPartsFiles(); for (Map.Entry imageEntry : partsFiles.entrySet()) { Layer layer = imageEntry.getKey(); ImageResource imageResource = imageEntry.getValue(); String name = layer.getDir() + "/" + partsIdentifier.getPartsName() + ".png"; name = name.replace("//", "/"); putNextEntry(name, imageResource.lastModified()); OutputStream os = getOutputStream(); InputStream is = imageResource.openStream(); try { int rd; while ((rd = is.read(buf)) >= 0) { os.write(buf, 0, rd); } } finally { is.close(); } closeEntry(); } } } @Override protected void internalWritePartsManageData( Map partsImages) throws IOException { PartsManageDataConverter partsManageDataConverter = new PartsManageDataConverter(); for (Map.Entry entry : partsImages.entrySet()) { PartsIdentifier partsIdentifier = entry.getKey(); PartsSpec partsSpec = entry.getValue(); partsManageDataConverter.convert(partsIdentifier, partsSpec); } PartsManageData partsManageData = partsManageDataConverter.getPartsManageData(); PartsInfoXMLWriter xmlWriter = new PartsInfoXMLWriter(); putNextEntry("parts-info.xml", 0); xmlWriter.savePartsManageData(partsManageData, getOutputStream()); closeEntry(); } } CharacterManaJ/src/charactermanaj/model/io/CharacterDataJarFileWriter.java0000644000175000017500000000215012560206305027012 0ustar paulliupaulliupackage charactermanaj.model.io; import java.io.BufferedOutputStream; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.OutputStream; import java.util.jar.JarEntry; import java.util.jar.JarOutputStream; public class CharacterDataJarFileWriter extends AbstractCharacterDataArchivedFileWriter { protected JarOutputStream jarOutStm; public CharacterDataJarFileWriter(File outFile) throws IOException { super(outFile); this.jarOutStm = new JarOutputStream( new BufferedOutputStream(new FileOutputStream(tmpFile))); } @Override protected void closeEntry() throws IOException { jarOutStm.closeEntry(); } @Override protected OutputStream getOutputStream() throws IOException { return jarOutStm; } @Override protected void putNextEntry(String name, long lastModified) throws IOException { JarEntry entry = new JarEntry(name); if (lastModified > 0) { entry.setTime(lastModified); } jarOutStm.putNextEntry(entry); } protected void internalClose() throws IOException { jarOutStm.close(); } } CharacterManaJ/src/charactermanaj/model/io/CharacterDataWriter.java0000644000175000017500000000152512560206305025562 0ustar paulliupaulliupackage charactermanaj.model.io; import java.awt.image.BufferedImage; import java.io.IOException; import java.util.Map; import java.util.Properties; import charactermanaj.model.CharacterData; import charactermanaj.model.PartsIdentifier; import charactermanaj.model.PartsSpec; public interface CharacterDataWriter { void writeExportProp(Properties prop) throws IOException; void writeCharacterData(CharacterData characterData) throws IOException; void writeTextUTF16LE(String name, String contents) throws IOException; void writeSamplePicture(BufferedImage samplePicture) throws IOException; void writePartsImages(Map partsImages) throws IOException; void writePartsManageData(Map partsImages) throws IOException; void close() throws IOException; } CharacterManaJ/src/charactermanaj/model/util/0000755000175000017500000000000012560206305021377 5ustar paulliupaulliuCharacterManaJ/src/charactermanaj/model/util/StartupSupport.java0000644000175000017500000001761512560206305025313 0ustar paulliupaulliupackage charactermanaj.model.util; import java.io.File; import java.io.FileFilter; import java.io.OutputStream; import java.net.URI; import java.util.logging.Level; import java.util.logging.Logger; import charactermanaj.model.AppConfig; import charactermanaj.model.WorkingSet; import charactermanaj.model.io.WorkingSetPersist; import charactermanaj.model.io.WorkingSetXMLWriter; import charactermanaj.ui.RecentCharactersDir; import charactermanaj.util.FileUserData; import charactermanaj.util.UserData; import charactermanaj.util.UserDataFactory; /** * 開始前の事前準備するためのサポートクラス * * @author seraphy */ public abstract class StartupSupport { private static StartupSupport inst; /** * インスタンスを取得する. * * @return シングルトンインスタンス */ public static synchronized StartupSupport getInstance() { if (inst == null) { inst = new StartupSupport() { private final Logger logger = Logger.getLogger(StartupSupport.class.getName()); @Override public void doStartup() { StartupSupport[] startups = { new PurgeOldLogs(), new ConvertRecentCharDirsSerToXmlProps(), new ConvertWorkingSetSerToXml(), new PurgeOldCaches(), }; for (StartupSupport startup : startups) { logger.log(Level.FINE, "startup operation start. class=" + startup.getClass().getSimpleName()); try { startup.doStartup(); logger.log(Level.FINE, "startup operation is done."); } catch (Exception ex) { logger.log(Level.WARNING, "startup operation failed.", ex); } } } }; } return inst; } /** * スタートアップ処理を実施します. */ public abstract void doStartup(); } /** * シリアライズによるキャラクターディレクトリリストの保存をXML形式のPropertiesにアップグレードします。 * * @author seraphy * */ class ConvertRecentCharDirsSerToXmlProps extends StartupSupport { /** * ロガー */ private final Logger logger = Logger.getLogger(getClass().getName()); @Override public void doStartup() { // 旧形式(ver0.991以前) UserDataFactory factory = UserDataFactory.getInstance(); final String FILENAME = "recent-characterdirs.ser"; File prevFile = new File(factory.getSpecialDataDir(FILENAME), FILENAME); try { if (prevFile.exists()) { FileUserData recentCharDirs = new FileUserData(prevFile); RecentCharactersDir obj = (RecentCharactersDir) recentCharDirs .load(); // 新しい形式で保存する. obj.saveRecents(); // 古いファイルを削除する prevFile.delete(); } } catch (Exception ex) { logger.log(Level.WARNING, FILENAME + " convert failed.", ex); } } } /** * シリアライズによるキャラクターディレクトリリストの保存をXML形式のPropertiesにアップグレードします。 * * @author seraphy * */ class ConvertWorkingSetSerToXml extends StartupSupport { /** * ロガー */ private final Logger logger = Logger.getLogger(getClass().getName()); @Override public void doStartup() { final String FILENAME = "workingset.ser"; try { UserDataFactory userDataFactory = UserDataFactory.getInstance(); File dir = userDataFactory.getSpecialDataDir(FILENAME); if (!dir.exists()) { return; } File[] files = dir.listFiles(new FileFilter() { public boolean accept(File pathname) { String name = pathname.getName(); return name.endsWith(FILENAME); } }); if (files == null) { logger.log(Level.WARNING, "cache-dir access failed. " + dir); return; } WorkingSetXMLWriter wr = new WorkingSetXMLWriter(); for (File file : files) { FileUserData fileData = new FileUserData(file); if (fileData.exists()) { try { // serファイルをデシリアライズする. WorkingSet ws = (WorkingSet) fileData.load(); URI docBase = ws.getCharacterDocBase(); if (docBase != null) { // XML形式で保存しなおす. UserData workingSetXmlData = userDataFactory .getMangledNamedUserData(docBase, WorkingSetPersist.WORKINGSET_FILE_SUFFIX); if (!workingSetXmlData.exists()) { // XML形式データがまだない場合のみ保存しなおす. OutputStream outstm = workingSetXmlData .getOutputStream(); try { wr.writeWorkingSet(ws, outstm); } finally { outstm.close(); } } } // serファイルは削除する. fileData.delete(); } catch (Exception ex) { logger.log(Level.WARNING, FILENAME + " convert failed.", ex); } } } } catch (Exception ex) { logger.log(Level.WARNING, FILENAME + " convert failed.", ex); } } } /** * 古いログファイルを消去する. * * @author seraphy */ class PurgeOldLogs extends StartupSupport { /** * ロガー */ private final Logger logger = Logger.getLogger(getClass().getName()); @Override public void doStartup() { UserDataFactory userDataFactory = UserDataFactory.getInstance(); File logsDir = userDataFactory.getSpecialDataDir("*.log"); if (logsDir.exists()) { AppConfig appConfig = AppConfig.getInstance(); long purgeOldLogsMillSec = appConfig.getPurgeLogDays() * 24L * 3600L * 1000L; if (purgeOldLogsMillSec > 0) { File[] files = logsDir.listFiles(); if (files == null) { logger.log(Level.WARNING, "log-dir access failed."); return; } long purgeThresold = System.currentTimeMillis() - purgeOldLogsMillSec; for (File file : files) { try { String name = file.getName(); if (file.isFile() && file.canWrite() && name.endsWith(".log")) { long lastModified = file.lastModified(); if (lastModified > 0 && lastModified < purgeThresold) { boolean result = file.delete(); logger.log(Level.INFO, "remove file " + file + "/succeeded=" + result); } } } catch (Exception ex) { logger.log(Level.WARNING, "remove file failed. " + file, ex); } } } } } } /** * 古いキャッシュファイルを消去する.
* -character.xml-cache.ser, -favorites.serは、直接xmlでの読み込みになったため、 ただちに消去しても問題ない.
* recent-character.serは、使用されなくなったため、ただちに消去して良い.
* mangled_info.xmlは、*.serを消去したあとには不要となるため、消去する.
* (今後使われることはない) * * @author seraphy */ class PurgeOldCaches extends StartupSupport { /** * ロガー */ private final Logger logger = Logger.getLogger(getClass().getName()); @Override public void doStartup() { UserDataFactory userDataFactory = UserDataFactory.getInstance(); File cacheDir = userDataFactory.getSpecialDataDir(".ser"); if (cacheDir.exists()) { File[] files = cacheDir.listFiles(); if (files == null) { logger.log(Level.WARNING, "cache-dir access failed."); return; } for (File file : files) { try { if (!file.isFile() || !file.canWrite()) { // ファイルでないか、書き込み不可の場合はスキップする. continue; } String name = file.getName(); if (name.endsWith("-character.xml-cache.ser") || name.endsWith("-favorites.ser") || name.equals("recent-character.ser") || name.equals("mangled_info.xml")) { boolean result = file.delete(); logger.log(Level.INFO, "remove file " + file + "/succeeded=" + result); } } catch (Exception ex) { logger.log(Level.WARNING, "remove file failed. " + file, ex); } } } } } CharacterManaJ/src/charactermanaj/model/PartsColorInfo.java0000644000175000017500000001224012560206305024170 0ustar paulliupaulliupackage charactermanaj.model; import java.io.Serializable; import java.util.AbstractMap; import java.util.Collections; import java.util.HashMap; import java.util.Map; import java.util.Set; import java.util.logging.Level; import java.util.logging.Logger; import charactermanaj.graphics.filters.ColorConvertParameter; /** * パーツ単位のカラーグループを含む色情報のコレクション.
* パーツは複数のレイヤーから構成されるため、レイヤーごとのカラーグループを含む色情報の集合を意味する.
* キーのセットはカテゴリに属するレイヤーに固定されており、追加・削除することはできない.
* @author seraphy */ public final class PartsColorInfo extends AbstractMap implements Serializable, Cloneable { /** * ロガー */ private static final Logger logger = Logger.getLogger(PartsColorInfo.class.getName()); private static final long serialVersionUID = -8639109147043912257L; /** * パーツが属するカテゴリのレイヤー構成に対する色情報のマップ.
*/ private HashMap colorInfoMap = new HashMap(); /** * カテゴリ */ private final PartsCategory partsCategory; /** * カテゴリを指定して色情報が未設定のインスタンスを構築する.
* カテゴリに属するレイヤーが初期化されている.
* @param partsCategory カテゴリ */ public PartsColorInfo(PartsCategory partsCategory) { if (partsCategory == null) { throw new IllegalArgumentException(); } this.partsCategory = partsCategory; init(); } @Override public PartsColorInfo clone() { try { PartsColorInfo inst = (PartsColorInfo) super.clone(); inst.colorInfoMap = new HashMap(); for (Map.Entry entry : colorInfoMap.entrySet()) { Layer layer = entry.getKey(); ColorInfo colorInfo = entry.getValue(); inst.colorInfoMap.put(layer, colorInfo.clone()); } return inst; } catch (CloneNotSupportedException ex) { throw new RuntimeException(ex); } } /** * パーツカラー情報を指定したパーツカテゴリに存在するレイヤーに正規化して返す.
* カテゴリに存在しないレイヤーの情報は破棄され、結果は有効なレイヤーのみの色情報となる.
* @param partsCategory パーツカテゴリ * @return 正規化されたパーツカラー情報 */ public PartsColorInfo createCompatible(PartsCategory partsCategory) { if (partsCategory == null) { throw new IllegalArgumentException(); } PartsColorInfo newInfo = new PartsColorInfo(partsCategory); newInfo.init(); for (Map.Entry entry : colorInfoMap.entrySet()) { Layer layer = entry.getKey(); ColorInfo colorInfo = entry.getValue(); if (partsCategory.hasLayer(layer)) { newInfo.put(layer, colorInfo.clone()); } else { logger.log(Level.INFO, "missing layer '" + layer + "' in " + partsCategory); } } return newInfo; } /** * 2つのパーツカラー情報が同じであるか判定する.
* 双方がnullである場合はtrueとなります.
* いずれか一方がnullである場合はfalseとなります.
* @param a 対象1、null可 * @param b 対象2, null可 * @return 同一であればtrue、そうでなければfalse */ public static boolean equals(PartsColorInfo a, PartsColorInfo b) { if (a == b) { return true; } if (a == null || b == null) { return false; } return a.equals(b); } private void init() { for (Layer layer : partsCategory.getLayers()) { colorInfoMap.put(layer, createColorInfo(layer)); } } protected ColorInfo createColorInfo(Layer layer) { ColorInfo colorInfo = new ColorInfo(); colorInfo.setColorGroup(layer.getColorGroup()); colorInfo.setSyncColorGroup(layer.getColorGroup().isEnabled()); colorInfo.setColorParameter(new ColorConvertParameter()); return colorInfo; } public PartsCategory getPartsCategory() { return partsCategory; } @Override public Set> entrySet() { return Collections.unmodifiableSet(colorInfoMap.entrySet()); } /** * カテゴリに属するレイヤーの色情報を設定する.
* カテゴリに該当しないレイヤーを指定した場合はIllegalArgumentException例外となる.
* @param key レイヤー * @param value 色情報 */ @Override public ColorInfo put(Layer key, ColorInfo value) { if (key == null || value == null) { throw new IllegalArgumentException(); } if (!colorInfoMap.containsKey(key)) { throw new IllegalArgumentException("invalid layer: " + key); } return colorInfoMap.put(key, value); } @Override public String toString() { StringBuilder buf = new StringBuilder(); buf.append(getClass().getSimpleName() + "@" + Integer.toHexString(System.identityHashCode(this))); buf.append("("); buf.append(colorInfoMap.toString()); buf.append(")"); return buf.toString(); } } CharacterManaJ/src/charactermanaj/model/PartsManageData.java0000644000175000017500000002544312560206305024271 0ustar paulliupaulliupackage charactermanaj.model; import java.sql.Timestamp; import java.util.AbstractCollection; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.Iterator; import java.util.Map; /** * パーツ管理情報.
* パーツ識別子のかわりに、パーツキーを用いる.
* パーツキーは、CategoryIdがnullであることを許可している.
* カテゴリを省略し、パーツ名だけでパーツ管理情報を検索できるようにするためのもの.
* * @author seraphy */ public class PartsManageData extends AbstractCollection { /** * パーツキー.
* パーツ識別子 {@link PartsIdentifier} とほぼ同等であるが、カテゴリがnullであることを許可している点が異なる.
* * @author seraphy */ public static final class PartsKey implements Comparable { private final String partsName; private final String categoryId; public PartsKey(String partsName) { this(partsName, null); } public PartsKey(String partsName, String categoryId) { if (partsName == null || partsName.length() == 0) { throw new IllegalArgumentException(); } if (categoryId != null && categoryId.trim().length() == 0) { categoryId = null; } this.partsName = partsName; this.categoryId = categoryId; } public PartsKey(PartsIdentifier partsIdentifier) { if (partsIdentifier == null) { throw new IllegalArgumentException(); } this.partsName = partsIdentifier.getPartsName(); this.categoryId = partsIdentifier.getPartsCategory().getCategoryId(); } public int compareTo(PartsKey o) { int ret = partsName.compareTo(o.partsName); if (ret == 0) { if (categoryId == null || o.categoryId == null) { ret = (categoryId == o.categoryId) ? 0 : (categoryId == null ? -1 : 1); } } return ret; } public String getCategoryId() { return categoryId; } public String getPartsName() { return partsName; } @Override public int hashCode() { return partsName.hashCode(); } @Override public boolean equals(Object obj) { if (obj == this) { return true; } if (obj != null && obj instanceof PartsKey) { PartsKey o = (PartsKey) obj; if (partsName.equals(o.partsName)) { return categoryId == null ? (o.categoryId == null) : categoryId.equals(o.categoryId); } } return false; } } /** * パーツごとのバージョンとダウンロードURL情報を保持するホルダークラス.
* * @author seraphy */ public static final class PartsVersionInfo { private double version; private String downloadURL; private Timestamp lastModified; public PartsVersionInfo() { super(); } public PartsVersionInfo(double version, String downloadURL) { this(version, downloadURL, null); } public PartsVersionInfo(double version, String downloadURL, Timestamp lastModified) { this.version = version; this.downloadURL = downloadURL; this.lastModified = lastModified; } public double getVersion() { return version; } public String getDownloadURL() { return downloadURL; } public void setVersion(double version) { this.version = version; } public void setDownloadURL(String downloadURL) { this.downloadURL = downloadURL; } public Timestamp getLastModified() { return lastModified; } public void setLastModified(Timestamp lastModified) { this.lastModified = lastModified; } } /** * パーツキーと、それに対する作者情報 */ private HashMap partsAuthorInfoMap = new HashMap(); /** * パーツキーと、それに対するローカライズ名 */ private HashMap partsLocalizedNameMap = new HashMap(); /** * パーツキーと、それに対するバージョン情報 */ private HashMap partsVersionInfoMap = new HashMap(); /** * すべてクリアする.
*/ @Override public void clear() { partsAuthorInfoMap.clear(); partsLocalizedNameMap.clear(); partsVersionInfoMap.clear(); } /** * パーツキーに結びつく、各種情報を登録する.
* * @param partsKey * パーツキー * @param localizedName * ローカライズ名(なければnull) * @param partsAuthorInfo * 作者情報 (なければnull) * @param versionInfo * バージョン情報 (なければnull) */ public void putPartsInfo(PartsKey partsKey, String localizedName, PartsAuthorInfo partsAuthorInfo, PartsVersionInfo versionInfo) { if (partsKey == null) { throw new IllegalArgumentException(); } partsAuthorInfoMap.put(partsKey, partsAuthorInfo); partsLocalizedNameMap.put(partsKey, localizedName); partsVersionInfoMap.put(partsKey, versionInfo); } /** * パーツキーを指定して該当する作者情報を取得する.
* 完全に一致する作者情報がない場合は、カテゴリを無視して、パーツキーのパーツ名(ID)の一致する、いずれかの作者情報を返す.
* * @param partsKey * パーツキー * @return 作者情報、完全に一致するものがなく、且つ、パーツ名(ID)に一致する情報もない場合はnull */ public PartsAuthorInfo getPartsAuthorInfo(PartsKey partsKey) { if (partsKey == null) { return null; } PartsAuthorInfo authorInfo = partsAuthorInfoMap.get(partsKey); if (authorInfo == null) { for (Map.Entry entry : partsAuthorInfoMap.entrySet()) { PartsKey key = entry.getKey(); if (key.getPartsName().equals(partsKey.getPartsName())) { authorInfo = entry.getValue(); break; } } } return authorInfo; } /** * パーツキーと完全に一致する作者情報を取得する.
* 存在しない場合はnullを返す.
* * @param partsKey * パーツキー * @return 作者情報、もしくはnull */ public PartsAuthorInfo getPartsAuthorInfoStrict(PartsKey partsKey) { if (partsKey == null) { return null; } return partsAuthorInfoMap.get(partsKey); } /** * パーツキーを指定して該当するバージョン情報を取得する.
* 完全に一致するバージョン情報がない場合は、カテゴリを無視して、パーツキーのパーツ名(ID)の一致する、いずれかのバージョン情報を返す.
* * @param partsKey * パーツキー * @return バージョン情報、完全に一致するものがなく、且つ、パーツ名(ID)に一致する情報もない場合はnull */ public PartsVersionInfo getVersion(PartsKey partsKey) { if (partsKey == null) { return null; } PartsVersionInfo versionInfo = partsVersionInfoMap.get(partsKey); if (versionInfo == null) { for (Map.Entry entry : partsVersionInfoMap.entrySet()) { PartsKey key = entry.getKey(); if (key.getPartsName().equals(partsKey.getPartsName())) { versionInfo = entry.getValue(); break; } } } return versionInfo; } /** * パーツキーを指定して該当するローカライズ名を取得する.
* 完全に一致するバージョン情報がない場合は、カテゴリを無視して、パーツキーのパーツ名(ID)の一致する、いずれかのローカライズ名を返す.
* * @param partsKey * パーツキー * @return バージョン情報、完全に一致するものがなく、且つ、パーツ名(ID)に一致する情報もない場合はnull */ public String getLocalizedName(PartsKey partsKey) { if (partsKey == null) { return null; } String localizedName = partsLocalizedNameMap.get(partsKey); if (localizedName == null) { for (Map.Entry entry : partsLocalizedNameMap.entrySet()) { PartsKey key = entry.getKey(); if (key.getPartsName().equals(partsKey.getPartsName())) { localizedName = entry.getValue(); break; } } } return localizedName; } /** * パーツキーと完全に一致するバージョン情報を取得する.
* * @param partsKey * パーツキー * @return パーツキーに完全に一致するバージョン情報、該当がなければnull */ public PartsVersionInfo getVersionStrict(PartsKey partsKey) { if (partsKey == null) { return null; } return partsVersionInfoMap.get(partsKey); } /** * パーツキーと完全に一致するローカライズ名を取得する.
* * @param partsKey * パーツキー * @return パーツキーに完全に一致するローカライズ名、該当がなければnull */ public String getLocalizedNameStrict(PartsKey partsKey) { if (partsKey == null) { return null; } return partsLocalizedNameMap.get(partsKey); } /** * すべての作者情報を返す.
* * @return 作者情報のコレクション */ public Collection getAuthorInfos() { HashMap authorInfos = new HashMap(); for (PartsAuthorInfo authorInfo : partsAuthorInfoMap.values()) { if (authorInfo != null) { String author = authorInfo.getAuthor(); if (author != null && author.length() > 0) { authorInfos.put(author, authorInfo); } } } return authorInfos.values(); } /** * 指定した作者に該当する登録されているパーツキーの一覧を返す.
* * @param author * 作者 * @return 作者に該当するパーツキー */ public Collection getPartsKeysByAuthor(String author) { if (author == null) { return Collections.emptyList(); } ArrayList partsKeys = new ArrayList(); for (Map.Entry entry : partsAuthorInfoMap.entrySet()) { PartsKey partsKey = entry.getKey(); PartsAuthorInfo partsAuthorInfo = entry.getValue(); if (partsAuthorInfo != null) { String author2 = partsAuthorInfo.getAuthor(); if (author2 == null) { author2 = ""; } if (author2.equals(author)) { partsKeys.add(partsKey); } } } return partsKeys; } @Override public Iterator iterator() { return partsAuthorInfoMap.keySet().iterator(); } @Override public int size() { return partsAuthorInfoMap.size(); } } CharacterManaJ/src/charactermanaj/model/CharacterDataChangeListener.java0000644000175000017500000000031712560206305026570 0ustar paulliupaulliupackage charactermanaj.model; import java.util.EventListener; public interface CharacterDataChangeListener extends EventListener { void notifyChangeCharacterData(CharacterDataChangeEvent e); } CharacterManaJ/src/charactermanaj/model/PartsSpecResolver.java0000644000175000017500000000204512560206305024714 0ustar paulliupaulliupackage charactermanaj.model; import java.awt.Dimension; import java.util.Map; /** * パーツ設定を取得するためのインターフェイス * @author seraphy */ public interface PartsSpecResolver extends PartsCategoryResolver { /** * イメージのサイズを取得する * @return イメージサイズ、設定がなければnull */ Dimension getImageSize(); /** * 指定したパーツ識別子に対するパーツ設定を取得する.
* なければnull * @param partsIdentifier パーツ識別子 * @return パーツ設定、なければnull */ PartsSpec getPartsSpec(PartsIdentifier partsIdentifier); /** * 指定したカテゴリに該当するパーツ識別子とパーツ設定のマップを取得する.
* 該当するカテゴリがなければ空のマップを返す.
* @param category パーツカテゴリ * @return パーツ設定のマップ、なければ空 */ Map getPartsSpecMap(PartsCategory category); } CharacterManaJ/src/charactermanaj/model/PartsIdentifier.java0000644000175000017500000000571612560206305024372 0ustar paulliupaulliupackage charactermanaj.model; import java.io.Serializable; /** * パーツ識別子.
* パーツ識別子の同値性は同一カテゴリID、且つ、同一のパーツ名(ID)であることによってのみ判定される.
* 表示名については不問.
* カテゴリIDのみ判定され、カテゴリの同値性についても問わない.
* @author seraphy * */ public final class PartsIdentifier implements Serializable, Comparable { private static final long serialVersionUID = 8943101890389091718L; private final PartsCategory partsCategory; private final String partsName; private final String localizedName; public PartsIdentifier(final PartsCategory partsCategory, final String partsName, final String localizedName) { if (partsName == null || partsCategory == null) { throw new IllegalArgumentException(); } this.partsCategory = partsCategory; this.partsName = partsName; this.localizedName = (localizedName == null || localizedName.trim().length() == 0) ? partsName : localizedName; } public PartsCategory getPartsCategory() { return partsCategory; } public boolean hasLayer(Layer layer) { return partsCategory.hasLayer(layer); } @Override public int hashCode() { return partsName.hashCode(); } @Override public boolean equals(Object obj) { if (obj == this) { return true; } if (obj != null && obj instanceof PartsIdentifier) { return partsName.equals(((PartsIdentifier) obj).partsName) && partsCategory.isSameCategoryID(((PartsIdentifier) obj).getPartsCategory()); } return false; } public static boolean equals(PartsIdentifier a, PartsIdentifier b) { if (a == b) { return true; } if (a == null || b == null) { return false; } return a.equals(b); } public int compareTo(PartsIdentifier o) { if (o == this) { return 0; } int ret = partsCategory.compareTo(o.partsCategory); if (ret == 0) { ret = localizedName.compareTo(o.localizedName); } if (ret == 0) { ret = partsName.compareTo(o.partsName); } return ret; } public String getPartsName() { return partsName; } public String getLocalizedPartsName() { return localizedName; } /** * ローカライズされた名前を変更する.
* [注意] このクラスは不変クラスなので、インスタンスを変更するのではなく、変更された状態の * 新しいインスタンスを返します.
* @param localizedName ローカライズされた名前 * @return 新しいインスタンス */ public PartsIdentifier setLocalizedPartsName(String localizedName) { if (localizedName == null || localizedName.trim().length() == 0) { throw new IllegalArgumentException(); } return new PartsIdentifier(partsCategory, partsName, localizedName); } @Override public String toString() { return getLocalizedPartsName(); } } CharacterManaJ/src/charactermanaj/model/PartsCategoryResolver.java0000644000175000017500000000101612560206305025574 0ustar paulliupaulliupackage charactermanaj.model; import java.util.List; public interface PartsCategoryResolver { /** * パーツカテゴリの一覧を取得する.
* @return パーツカテゴリの一覧。(表示順) */ List getPartsCategories(); /** * パーツカテゴリのIDを指定して該当のインスタンスを取得します.
* @param id パーツカテゴリID * @return インスタンス、なければnull */ PartsCategory getPartsCategory(String id); } CharacterManaJ/src/charactermanaj/model/PartsSpec.java0000644000175000017500000000351712560206305023177 0ustar paulliupaulliupackage charactermanaj.model; import java.io.Serializable; /** * パーツの構成情報.
* @author seraphy */ public class PartsSpec implements Serializable { private static final long serialVersionUID = -5275967668710789314L; private PartsIdentifier partsIdentifier; private ColorGroup colorGroup = ColorGroup.NA; private PartsFiles partsFiles; /** * パーツの作者情報、指定がなければnull */ private PartsAuthorInfo authorInfo; /** * パーツのバージョン、指定がなければ0 */ private double version; /** * ダウンロードURL */ private String downloadURL; public PartsSpec(PartsIdentifier partsIdentifier) { if (partsIdentifier == null) { throw new IllegalArgumentException(); } this.partsIdentifier = partsIdentifier; this.partsFiles = new PartsFiles(partsIdentifier); } public PartsIdentifier getPartsIdentifier() { return partsIdentifier; } public PartsFiles getPartsFiles() { return partsFiles; } public void setAuthorInfo(PartsAuthorInfo authorInfo) { this.authorInfo = authorInfo; } public PartsAuthorInfo getAuthorInfo() { return authorInfo; } public String getAuthor() { if (authorInfo != null) { return authorInfo.getAuthor(); } return null; } public void setVersion(double version) { this.version = version; } public double getVersion() { return version; } public String getDownloadURL() { return downloadURL; } public void setDownloadURL(String downloadURL) { this.downloadURL = downloadURL; } public void setColorGroup(ColorGroup colorGroup) { if (colorGroup == null) { colorGroup = ColorGroup.NA; } this.colorGroup = colorGroup; } public ColorGroup getColorGroup() { return colorGroup; } } CharacterManaJ/src/charactermanaj/model/IndependentPartsSetInfoList.java0000644000175000017500000000323412560206305026662 0ustar paulliupaulliupackage charactermanaj.model; import java.util.ArrayList; /** * 素のパーツセットのコレクション.
* レイヤーやカテゴリなどのリレーションシップがない、
* 特定のキャラクターデータモデルのツリーの一部には組み込まれていない状態のもの.
*/ public class IndependentPartsSetInfoList extends ArrayList { /** * シリアライズバージョンID */ private static final long serialVersionUID = -6121741586284912547L; /** * デフォルトのパーツセットID.
* ない場合はnull.
*/ private String defaultPresetId; public String getDefaultPresetId() { return defaultPresetId; } /** * デフォルトパーツセットIDを設定する.
* nullはパーツセットIDがないことを示す.
* 空文字はnullとみなされる.
* * @param defaultPresetId */ public void setDefaultPresetId(String defaultPresetId) { if (defaultPresetId != null) { defaultPresetId = defaultPresetId.trim(); if (defaultPresetId.length() == 0) { // デフォルトパーツセットがないことを示すためのnull defaultPresetId = null; } } this.defaultPresetId = defaultPresetId; } @Override public boolean add(IndependentPartsSetInfo o) { if (o == null) { throw new IllegalArgumentException(); } return super.add(o); } @Override public IndependentPartsSetInfo set(int index, IndependentPartsSetInfo element) { if (element == null) { throw new IllegalArgumentException(); } return super.set(index, element); } }CharacterManaJ/src/charactermanaj/model/CharacterData.java0000644000175000017500000007415712560206305023771 0ustar paulliupaulliupackage charactermanaj.model; import java.awt.Dimension; import java.io.File; import java.io.IOException; import java.io.Serializable; import java.net.URI; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Properties; import java.util.logging.Level; import java.util.logging.Logger; import charactermanaj.model.io.PartsDataLoader; /** * キャラクターデータ * @author seraphy */ public class CharacterData implements Serializable, PartsSpecResolver { /** * シリアライズバージョン */ private static final long serialVersionUID = -381763373314240953L; /** * ロガー */ private static final Logger logger = Logger.getLogger(CharacterData.class.getName()); /** * キャラクターデータを表示名順にソートするための比較器.
*/ public static final Comparator SORT_DISPLAYNAME = new Comparator() { public int compare(CharacterData o1, CharacterData o2) { if (!o1.isValid() || !o2.isValid()) { return o1.isValid() == o2.isValid() ? 0 : o1.isValid() ? 1 : -1; } int ret = o1.getName().compareTo(o2.getName()); if (ret == 0) { ret = o1.getId().compareTo(o2.getId()); } if (ret == 0) { ret = o1.getDocBase().toString().compareTo(o2.getDocBase().toString()); } return ret; } }; /** * キャラクターデータを定義しているXMLの位置.
* docBase自身はxml定義には含まれず、xmlをロードした位置を記憶するためにPersistentクラスによって設定される.
*/ private URI docBase; /** * キャラクターデータの内部用ID.
* キャラクターデータの構造を判定するために用いる.
*/ private String id; /** * キャラクターデータの更新番号.
* キャラクターデータの構造が変更されたことを識別するために用いる.
*/ private String rev; /** * 表示用名 */ private String localizedName; /** * 作成者 */ private String author; /** * 説明 */ private String description; /** * イメージサイズ */ private Dimension imageSize; /** * カテゴリ(定義順) */ private OrderedMap partsCategories = OrderedMap.emptyMap(); /** * 雑多なプロパティ.
*/ private Properties properties = new Properties(); /** * プリセットのマップ.
* キーはプリセット自身のID、値はプリセット自身.
*/ private HashMap presets = new HashMap(); /** * デフォルトのプリセットのID */ private String defaultPartsSetId; /** * カラーグループの定義.
*/ private OrderedMap colorGroups = OrderedMap.emptyMap(); /** * お勧めリンクリスト.
* Ver0.96以前には存在しないのでnullになり得る. */ private List recommendationURLList; /** * パーツカラーマネージャ.
* (非シリアライズデータ、デシリアライズ時には新規インスタンスが作成される).
*/ private transient PartsColorManager partsColorMrg = new PartsColorManager(this); /** * パーツデータローダー.
* パーツをロードしたときに設定され、リロードするときに使用する.
* パーツを一度もロードしていない場合はnull. * (非シリアライズデータ、デシリアライズ時はnullのまま).
*/ private transient PartsDataLoader partsDataLoader; /** * パーツイメージのセット.
* (キャラクターセットはパーツイメージをもったままシリアライズされることは想定していないが、可能ではある。) */ private Map> images = new HashMap>(); /** * シリアライズする * @param stream 出力先 * @throws IOException 失敗 */ private void writeObject(java.io.ObjectOutputStream stream) throws IOException { stream.defaultWriteObject(); } /** * 基本情報のみをコピーして返します.
* DocBase, ID, REV, Name, Author, Description, ImageSize、および, PartsCategory, ColorGroup, PartSetのコレクションがコピーされます.
* それ以外のものはコピーされません.
* @return 基本情報をコピーした新しいインスタンス */ public CharacterData duplicateBasicInfo() { return duplicateBasicInfo(true); } /** * 基本情報のみをコピーして返します.
* DocBase, ID, REV, Name, Author, Description, ImageSize、および, PartsCategory, ColorGroupがコピーされます.
* 引数のneedPartsSetがtrueの場合は,PartSetのコレクションもコピーされます.
* ディレクトリの監視状態、お勧めリンクもコピーされます.
* それ以外のものはコピーされません.
* @param needPartsSets パーツセットもコピーする場合、falseの場合はパーツセットは空となります. * @return 基本情報をコピーした新しいインスタンス */ public CharacterData duplicateBasicInfo(boolean needPartsSets) { CharacterData cd = new CharacterData(); cd.setId(this.id); cd.setRev(this.rev); cd.setDocBase(this.docBase); cd.setName(this.localizedName); cd.setAuthor(this.author); cd.setDescription(this.description); cd.setImageSize((Dimension)(this.imageSize == null ? null : this.imageSize.clone())); cd.setWatchDirectory(this.isWatchDirectory()); ArrayList recommendationURLList = null; if (this.recommendationURLList != null) { recommendationURLList = new ArrayList(); for (RecommendationURL recommendationUrl : this.recommendationURLList) { recommendationURLList.add(recommendationUrl.clone()); } } cd.setRecommendationURLList(recommendationURLList); ArrayList partsCategories = new ArrayList(); partsCategories.addAll(this.getPartsCategories()); cd.setPartsCategories(partsCategories.toArray(new PartsCategory[partsCategories.size()])); ArrayList colorGroups = new ArrayList(); colorGroups.addAll(this.getColorGroups()); cd.setColorGroups(colorGroups); if (needPartsSets) { for (PartsSet partsSet : this.getPartsSets().values()) { cd.addPartsSet(partsSet.clone()); } cd.setDefaultPartsSetId(this.defaultPartsSetId); } return cd; } /** * キャラクターデータが同じ構造であるか?
* カラーグループ、カテゴリ、レイヤーの各情報が等しければtrue、それ以外はfalse.
* 上記以外の項目(コメントや作者、プリセット等)については判定しない.
* サイズ、カラーグループの表示名や順序、カテゴリの順序や表示名、 * 複数アイテム可などの違いは構造の変更とみなさない.
* レイヤーはレイヤーID、重ね合わせ順、対象ディレクトリの3点が変更されている場合は構造の変更とみなす.
* いずれも個数そのものが変わっている場合は変更とみなす.
* 自分または相手がValidでなければ常にfalseを返す.
* @param other 比較対象, null可 * @return 同じ構造であればtrue、そうでなければfalse */ public boolean isSameStructure(CharacterData other) { if (!this.isValid() || other == null || !other.isValid()) { // 自分または相手がinvalidであれば構造的には常に不一致と見なす. return false; } // カラーグループが等しいか? (順序は問わない) // IDのみによって判定する ArrayList colorGroup1 = new ArrayList(getColorGroups()); ArrayList colorGroup2 = new ArrayList(other.getColorGroups()); if (colorGroup1.size() != colorGroup2.size()) { return false; } if (!colorGroup1.containsAll(colorGroup2)) { return false; } // カテゴリが等しいか? (順序は問わない) // IDによってのみ判定する. ArrayList categories1 = new ArrayList(getPartsCategories()); ArrayList categories2 = new ArrayList(other.getPartsCategories()); Comparator sortCategoryId = new Comparator() { public int compare(PartsCategory o1, PartsCategory o2) { int ret = o1.getCategoryId().compareTo(o2.getCategoryId()); if (ret == 0) { ret = o1.getOrder() - o2.getOrder(); } return ret; } }; // カテゴリID順に並び替えて, IDのみを比較する. Collections.sort(categories1, sortCategoryId); Collections.sort(categories2, sortCategoryId); int numOfCategories = categories1.size(); if (numOfCategories != categories2.size()) { // カテゴリ数不一致 return false; } for (int idx = 0; idx < numOfCategories; idx++) { PartsCategory category1 = categories1.get(idx); PartsCategory category2 = categories2.get(idx); String categoryId1 = category1.getCategoryId(); String categoryId2 = category2.getCategoryId(); if ( !categoryId1.equals(categoryId2)) { // カテゴリID不一致 return false; } } // レイヤーが等しいか? // ID、重ね順序、dirによってのみ判定する. int mx = categories1.size(); for (int idx = 0; idx < mx; idx++) { PartsCategory category1 = categories1.get(idx); PartsCategory category2 = categories2.get(idx); ArrayList layers1 = new ArrayList(category1.getLayers()); ArrayList layers2 = new ArrayList(category2.getLayers()); Comparator sortLayerId = new Comparator() { public int compare(Layer o1, Layer o2) { int ret = o1.getId().compareTo(o2.getId()); if (ret == 0) { ret = o1.getOrder() - o2.getOrder(); } return ret; } }; Collections.sort(layers1, sortLayerId); Collections.sort(layers2, sortLayerId); // ID、順序、Dirで判断する.(それ以外のレイヤー情報はequalsでは比較されない) if ( !layers1.equals(layers2)) { // レイヤー不一致 return false; } } return true; } /** * 引数で指定したキャラクター定義とアッパーコンパチブルであるか?
* 構造が同一であるか、サイズ違い、もしくはレイヤーの順序、カテゴリの順序、 * もしくはレイヤーまたはカテゴリが増えている場合で、減っていない場合はtrueとなる.
* 引数がnullの場合は常にfalseとなる. * @param other 前の状態のキャラクター定義、null可 * @return アッパーコンパチブルであればtrue、そうでなければfalse */ public boolean isUpperCompatibleStructure(CharacterData other) { if (!this.isValid() || other == null || !other.isValid()) { // 自分または相手がinvalidであれば構造的には常に互換性なしと見なす. return false; } // カラーグループが等しいか? (順序は問わない) // IDのみによって判定する ArrayList colorGroupNew = new ArrayList(getColorGroups()); ArrayList colorGroupOld = new ArrayList(other.getColorGroups()); if (!colorGroupNew.containsAll(colorGroupOld)) { // 自分が相手分のすべてのものを持っていなければ互換性なし. return false; } // カテゴリをすべて含むか? (順序は問わない) // IDによってのみ判定する. Map categoriesNew = new HashMap(); for (PartsCategory category : getPartsCategories()) { categoriesNew.put(category.getCategoryId(), category); } Map categoriesOld = new HashMap(); for (PartsCategory category : other.getPartsCategories()) { categoriesOld.put(category.getCategoryId(), category); } if ( !categoriesNew.keySet().containsAll(categoriesOld.keySet())) { // 自分が相手のすべてのカテゴリを持っていなければ互換性なし. return false; } // レイヤーをすべて含むか? // ID、Dirによってのみ判定する. for (Map.Entry categoryOldEntry : categoriesOld.entrySet()) { String categoryId = categoryOldEntry.getKey(); PartsCategory categoryOld = categoryOldEntry.getValue(); PartsCategory categoryNew = categoriesNew.get(categoryId); if (categoryNew == null) { return false; } Map layersNew = new HashMap(); for (Layer layer : categoryNew.getLayers()) { layersNew.put(layer.getId(), layer); } Map layersOld = new HashMap(); for (Layer layer : categoryOld.getLayers()) { layersOld.put(layer.getId(), layer); } if ( !layersNew.keySet().containsAll(layersOld.keySet())) { // 自分が相手のすべてのレイヤー(ID)を持っていなければ互換性なし. return false; } for (Map.Entry layerOldEntry : layersOld.entrySet()) { String layerId = layerOldEntry.getKey(); Layer layerOld = layerOldEntry.getValue(); Layer layerNew = layersNew.get(layerId); if (layerNew == null) { return false; } File dirOld = new File(layerOld.getDir()); File dirNew = new File(layerNew.getDir()); if ( !dirOld.equals(dirNew)) { // ディレクトリが一致しなければ互換性なし. return false; } } } return true; } /** * キャラクターデータの構造を表す文字列を返す.
* カテゴリ、レイヤー、色グループのみで構成される.
* id, revなどは含まない.
* @return キャラクターデータの構造を表す文字列 */ public String toStructureString() { // カラーグループ StringBuilder buf = new StringBuilder(); buf.append("{colorGroup:["); for (ColorGroup colorGroup : getColorGroups()) { buf.append(colorGroup.getId()); buf.append(","); } buf.append("],"); // カテゴリ buf.append("category:["); for (PartsCategory category : getPartsCategories()) { buf.append("{id:"); buf.append(category.getCategoryId()); buf.append(",layer:["); for (Layer layer : category.getLayers()) { buf.append("{id:"); buf.append(layer.getId()); buf.append(",dir:"); buf.append(layer.getDir()); buf.append("},"); } buf.append("]},"); } buf.append("]}"); return buf.toString(); } /** * キャラクターデータのID, REVと構造を識別するシグネチャの文字列を返す.
* (構造はカテゴリ、レイヤー、色グループのみ).
* @return シグネチャの文字列 */ public String toSignatureString() { StringBuilder buf = new StringBuilder(); buf.append("{id:"); buf.append(getId()); buf.append(",rev:"); buf.append(getRev()); buf.append(",structure:"); buf.append(toStructureString()); buf.append("}"); return buf.toString(); } /** * デシリアライズする. * @param stream 入力もと * @throws IOException 失敗 * @throws ClassNotFoundException 失敗 */ private void readObject(java.io.ObjectInputStream stream) throws IOException, ClassNotFoundException { stream.defaultReadObject(); partsColorMrg = new PartsColorManager(this); } /** * お勧めリンクのリストを取得する.
* 古いキャラクターデータで、お勧めリストノードが存在しない場合はnullとなる.
* @return お気に入りリンクのリスト、もしくはnull */ public List getRecommendationURLList() { return recommendationURLList; } /** * お勧めリンクリストを設定する.
* @param recommendationURLList、null可 */ public void setRecommendationURLList( List recommendationURLList) { this.recommendationURLList = recommendationURLList; } /** * 作者を設定する. * @param author 作者 */ public void setAuthor(String author) { this.author = author; } /** * 説明を設定する.
* 説明の改行コードはプラットフォーム固有の改行コードに変換される.
* @param description */ public void setDescription(String description) { if (description != null) { description = description.replace("\r\n", "\n"); description = description.replace("\r", "\n"); description = description.replace("\n", System.getProperty("line.separator")); } this.description = description; } public String getAuthor() { return author; } /** * 説明を取得する.
* 説明の改行コードはプラットフォーム固有の改行コードとなる.
* @return 説明 */ public String getDescription() { return description; } public String getId() { return id; } public void setId(String id) { this.id = id; } public String getRev() { return rev; } public void setRev(String rev) { this.rev = rev; } public void setDocBase(URI docBase) { this.docBase = docBase; } public URI getDocBase() { return docBase; } /** * ディレクトリを監視するか? (デフォルトは監視する) * @return ディレクトリを監視する場合はtrue */ public boolean isWatchDirectory() { try { String value = properties.getProperty("watch-dir"); if (value != null) { return Boolean.parseBoolean(value); } } catch (RuntimeException ex) { logger.log(Level.WARNING, "watch-dir property is invalid.", ex); } // デフォルトは監視する. return true; } /** * ディレクトリを監視するか指定する. * @param watchDir 監視する場合はtrue、しない場合はfalse */ public void setWatchDirectory(boolean watchDir) { properties.setProperty("watch-dir", Boolean.toString(watchDir)); } public String getProperty(String key) { if (key == null || key.trim().length() == 0) { throw new IllegalArgumentException(); } return properties.getProperty(key.trim()); } public void setProperty(String key, String value) { if (key == null || key.trim().length() == 0) { throw new IllegalArgumentException(); } properties.setProperty(key.trim(), value); } public Collection getPropertyNames() { ArrayList names = new ArrayList(); for (Object key : properties.keySet()) { names.add(key.toString()); } return names; } /** * 有効なキャラクターデータであるか? * ID, Name, DocBaseが存在するものが有効なキャラクターデータである.
* @return 有効であればtrue */ public boolean isValid() { return id != null && id.length() > 0 && localizedName != null && localizedName.length() > 0 && docBase != null; } /** * 編集可能か?
* まだdocbaseが指定されていない新しいインスタンスであるか、 * もしくはdocbaseが実在しファイルであり且つ読み込み可能であるか、 * もしくはdocbaseがまだ存在しない場合は、その親ディレクトリが読み書き可能であるか? * @return 編集可能であればtrue */ public boolean canWrite() { try { checkWritable(); return true; } catch (IOException ex) { return false; } } /** * 編集可能か?
* まだdocbaseが指定されていない新しいインスタンスであるか、 * もしくはdocbaseが実在しファイルであり且つ読み込み可能であるか、 * もしくはdocbaseがまだ存在しない場合は、その親ディレクトリが読み書き可能であるか? * @throws IOException 編集可能でなければIOException例外が発生する. */ public void checkWritable() throws IOException { if (docBase == null) { throw new IOException("invalid profile: " + this); } if ( !"file".equals(docBase.getScheme())) { throw new IOException("ファイルプロトコルではないため書き込みはできません。:" + docBase); } File xmlFile = new File(docBase); if (xmlFile.exists()) { // character.xmlファイルがある場合 if ( !xmlFile.canWrite() || !xmlFile.canRead()) { throw new IOException("書き込み、もしくは読み込みが禁止されているプロファイルです。" + docBase); } } else { // character.xmlファイルが、まだ存在していない場合 File parent = xmlFile.getParentFile(); if ( !parent.exists()) { throw new IOException("親ディレクトリがありません。" + docBase); } if ( !parent.canWrite() || !parent.canRead()) { throw new IOException("親ディレクトリは書き込み、もしくは読み込みが禁止されています。" + docBase); } } } /** * キャラクター名を設定する. * @param name */ public void setName(String name) { this.localizedName = name; } /** * キャラクター名を取得する. * @return */ public String getName() { return localizedName; } public void setImageSize(Dimension imageSize) { if (imageSize != null) { imageSize = (Dimension) imageSize.clone(); } this.imageSize = imageSize; } public Dimension getImageSize() { return imageSize != null ? (Dimension) imageSize.clone() : null; } public void setColorGroups(Collection colorGroups) { if (colorGroups == null) { throw new IllegalArgumentException(); } ArrayList colorGroupWithNA = new ArrayList(); colorGroupWithNA.add(ColorGroup.NA); for (ColorGroup colorGroup : colorGroups) { if (colorGroup.isEnabled()) { colorGroupWithNA.add(colorGroup); } } OrderedMap ret = new OrderedMap( colorGroupWithNA, new OrderedMap.KeyDetector() { public String getKey(ColorGroup data) { return data.getId(); } }); this.colorGroups = ret; } /** * カラーグループIDからカラーグループを取得する.
* 存在しない場合はN/Aを返す.
* @param colorGroupId カラーグループID * @return カラーグループ */ public ColorGroup getColorGroup(String colorGroupId) { ColorGroup cg = colorGroups.get(colorGroupId); if (cg != null) { return cg; } return ColorGroup.NA; } public Collection getColorGroups() { return colorGroups.values(); } public PartsCategory getPartsCategory(String categoryId) { if (partsCategories == null) { return null; } return partsCategories.get(categoryId); } public void setPartsCategories(PartsCategory[] partsCategories) { if (partsCategories == null) { partsCategories = new PartsCategory[0]; } this.partsCategories = new OrderedMap( Arrays.asList(partsCategories), new OrderedMap.KeyDetector() { public String getKey(PartsCategory data) { return data.getCategoryId(); } }); } public List getPartsCategories() { return partsCategories.asList(); } /** * パーツデータがロード済みであるか?
* 少なくとも{@link #loadPartsData(PartsDataLoader)}が一度呼び出されていればtrueとなる.
* falseの場合はパーツローダが設定されていないことを示す.
* @return パーツデータがロード済みであればtrue、そうでなければfalse */ public boolean isPartsLoaded() { return partsDataLoader != null; } /** * パーツデータをロードする.
* パーツローダを指定し、このローダはパーツの再ロード時に使用するため保持される.
* @param partsDataLoader ローダー */ public void loadPartsData(PartsDataLoader partsDataLoader) { if (partsDataLoader == null) { throw new IllegalArgumentException(); } this.partsDataLoader = partsDataLoader; reloadPartsData(); } /** * パーツデータをリロードする.
* ロード時に使用したローダーを使ってパーツを再ロードします.
* まだ一度もロードしていない場合はIllegalStateException例外が発生します.
* @return 変更があった場合はtrue、ない場合はfalse */ public boolean reloadPartsData() { if (partsDataLoader == null) { throw new IllegalStateException("partsDataLoader is not set."); } // パーツデータのロード images.clear(); for (PartsCategory category : partsCategories.asList()) { images.put(category, partsDataLoader.load(category)); } // NOTE: とりあえずパーツの変更を検査せず、常に変更ありにしておく。とりあえず実害ない。 return true; } /** * {@inheritDoc} */ public PartsSpec getPartsSpec(PartsIdentifier partsIdentifier) { if (partsIdentifier == null) { throw new IllegalArgumentException(); } PartsCategory partsCategory = partsIdentifier.getPartsCategory(); Map partsSpecMap = images.get(partsCategory); if (partsSpecMap != null) { PartsSpec partsSpec = partsSpecMap.get(partsIdentifier); if (partsSpec != null) { return partsSpec; } } return null; } /** * {@inheritDoc} */ public Map getPartsSpecMap(PartsCategory category) { Map partsImageMap = images.get(category); if (partsImageMap == null) { return Collections.emptyMap(); } return partsImageMap; } public PartsColorManager getPartsColorManager() { return this.partsColorMrg; } /** * パーツセットを登録します.
* お気に入りとプリセットの両方の共用です.
* IDおよび名前がないものは登録されず、falseを返します.
* パーツセットは、このキャラクター定義に定義されているカテゴリに正規化されます.
* 正規化された結果カテゴリが一つもなくなった場合は何も登録されず、falseを返します.
* 登録された場合はtrueを返します.
* 同一のIDは上書きされます.
* @param partsSet * @return 登録された場合はtrue、登録できない場合はfalse */ public boolean addPartsSet(PartsSet partsSet) { if (partsSet == null) { throw new IllegalArgumentException(); } if (partsSet.getPartsSetId() == null || partsSet.getPartsSetId().length() == 0 || partsSet.getLocalizedName() == null || partsSet.getLocalizedName().length() == 0) { return false; } PartsSet compatiblePartsSet = partsSet.createCompatible(this); if (compatiblePartsSet.isEmpty()) { return false; } presets.put(compatiblePartsSet.getPartsSetId(), compatiblePartsSet); return true; } /** * プリセットパーツおよびパーツセット(Favorites)のコレクション. * @return パーツセットのコレクション */ public Map getPartsSets() { return presets; } /** * プリセットパーツおよびパーツセットをリセットします.
* @param noRemovePreset プリセットは削除せず残し、プリセット以外のパーツセットをクリアする場合はtrue、falseの場合は全て削除される. */ public void clearPartsSets(boolean noRemovePreset) { if (!noRemovePreset) { // 全部消す presets.clear(); defaultPartsSetId = null; } else { // プリセット以外を消す Iterator> ite = presets.entrySet().iterator(); while (ite.hasNext()) { Map.Entry entry = ite.next(); if (!entry.getValue().isPresetParts()) { // デフォルトパーツセットであれば、デフォルトパーツセットもnullにする. // (ただし、デフォルトパーツセットはプリセットであることを想定しているので、この処理は安全策用。) if (entry.getKey().equals(defaultPartsSetId)) { defaultPartsSetId = null; } ite.remove(); } } } } /** * デフォルトのパーツセットを取得する.
* そのパーツセットIDが実在するか、あるいは、それがプリセットであるか、などは一切関知しない.
* 呼び出しもとで必要に応じてチェックすること.
* @return デフォルトとして指定されているパーツセットのID、なければnull */ public String getDefaultPartsSetId() { return defaultPartsSetId; } /** * デフォルトのパーツセットIDを指定する.
* nullの場合はデフォルトのパーツセットがないことを示す.
* パーツセットはプリセットであることが想定されるが、
* 実際に、その名前のパーツセットが存在するか、あるいは、そのパーツセットがプリセットであるか、などの判定は一切行わない.
* @param defaultPartsSetId パーツセットID、もしくはnull */ public void setDefaultPartsSetId(String defaultPartsSetId) { this.defaultPartsSetId = defaultPartsSetId; } @Override public String toString() { StringBuilder buf = new StringBuilder(); buf.append("character-id: " + id); buf.append("/rev:" + rev); buf.append("/name:" + localizedName); buf.append("/image-size:" + imageSize.width + "x" + imageSize.height); buf.append("/docBase:" + docBase); return buf.toString(); } } CharacterManaJ/src/charactermanaj/model/WorkingSet2.java0000644000175000017500000001314712560206305023451 0ustar paulliupaulliupackage charactermanaj.model; import java.io.File; import java.net.URI; import java.util.Collections; import java.util.List; import java.util.Map; import charactermanaj.ui.model.WallpaperInfo; /** * WorkingSetのXMLの読み込み時に使用する.
* 特定のキャラクターデータのインスタンスとの関連を持たない状態の設定値を保持している.
* * @author seraphy */ public class WorkingSet2 { /** * ドキュメントベース */ private URI characterDocBase; /** * キャラクターデータのID, Revと基本構造のシグネチャ */ private String characterDataSig; /** * キーはカテゴリid, 値は、パーツ名をキーとしレイヤーごとのカラー情報のリストを値とするマップ */ private Map>> partsColorMap = Collections .emptyMap(); /** * 現在の選択中のパーツと色設定からのパーツセット */ private IndependentPartsSetInfo currentPartsSet; /** * 最後に使用したディレクトリ(保存用) */ private File lastUsedSaveDir; /** * 最後に使用したディレクトリ(Export用) */ private File lastUsedExportDir; /** * 最後に使用したお気に入り情報.
* (最後に使用したお気に入り情報は、ver0.92からサポート)
*/ private IndependentPartsSetInfo lastUsePresetParts; /** * 壁紙情報.
* (壁紙はver0.97からサポート)
*/ private WallpaperInfo wallpaperInfo; public void setCharacterDocBase(URI characterDocBase) { this.characterDocBase = characterDocBase; } public URI getCharacterDocBase() { return characterDocBase; } public String getCharacterDataSig() { return characterDataSig; } public void setCharacterDataSig(String characterDataSig) { this.characterDataSig = characterDataSig; } /** * パーツカラーマップ * * @return キーはカテゴリid, 値は、パーツ名をキーとしレイヤーごとのカラー情報のリストを値とするマップ */ public Map>> getPartsColorMap() { return partsColorMap; } public void setPartsColorMap( Map>> partsColorMap) { if (partsColorMap == null) { this.partsColorMap = Collections.emptyMap(); } this.partsColorMap = partsColorMap; } public void setLastUsedExportDir(File lastUsedExportDir) { this.lastUsedExportDir = lastUsedExportDir; } public File getLastUsedExportDir() { return lastUsedExportDir; } public void setLastUsedSaveDir(File lastUsedSaveDir) { this.lastUsedSaveDir = lastUsedSaveDir; } public File getLastUsedSaveDir() { return lastUsedSaveDir; } public void setWallpaperInfo(WallpaperInfo wallpaperInfo) { this.wallpaperInfo = wallpaperInfo; } public WallpaperInfo getWallpaperInfo() { return wallpaperInfo; } public IndependentPartsSetInfo getCurrentPartsSet() { return currentPartsSet; } public void setCurrentPartsSet(IndependentPartsSetInfo currentPartsSet) { this.currentPartsSet = currentPartsSet; } public IndependentPartsSetInfo getLastUsePresetParts() { return lastUsePresetParts; } public void setLastUsePresetParts(IndependentPartsSetInfo lastUsePresetParts) { this.lastUsePresetParts = lastUsePresetParts; } /** * キャラクターデータを指定して、指定されたキャラクターデータ上のインスタンスと関連づけられた * カテゴリおよびパーツ名などのインスタンスで構成されるパーツ識別名とカラー情報を、 引数で指定したマップに出力する. * * @param characterData * キャラクターデータ * @param partsColorInfoMap * パーツ識別名とカラー情報を出力するマップ */ public void createCompatible(CharacterData characterData, Map partsColorInfoMap) { if (characterData == null || partsColorInfoMap == null) { throw new IllegalArgumentException(); } for (Map.Entry>> catEntry : partsColorMap .entrySet()) { String categoryId = catEntry.getKey(); for (Map.Entry> layerEntry : catEntry .getValue().entrySet()) { String partsName = layerEntry.getKey(); List partsColorInfos = layerEntry .getValue(); PartsCategory partsCategory = characterData .getPartsCategory(categoryId); if (partsCategory != null) { String localizedName = partsName; PartsIdentifier partsIdentifier = new PartsIdentifier( partsCategory, partsName, localizedName); PartsColorInfo partsColorInfo = IndependentPartsColorInfo .buildPartsColorInfo(characterData, partsCategory, partsColorInfos); if (partsColorInfo != null) { partsColorInfoMap.put(partsIdentifier, partsColorInfo); } } } } } @Override public String toString() { StringBuilder buf = new StringBuilder(); buf.append("(characterDocBase=").append(characterDocBase); buf.append(", characterDataSig=").append(characterDataSig); buf.append(", partsColorMap=").append(partsColorMap); buf.append(", currentPartsSet=").append(currentPartsSet); buf.append(", lastUsedSaveDir=").append(lastUsedSaveDir); buf.append(", lastUsedExportDir=").append(lastUsedExportDir); buf.append(")"); return buf.toString(); } } CharacterManaJ/src/charactermanaj/model/IndependentPartsSetInfo.java0000644000175000017500000001215212560206305026025 0ustar paulliupaulliupackage charactermanaj.model; import java.awt.Color; import java.io.Serializable; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.logging.Level; import java.util.logging.Logger; /** * 素のパーツセットの情報.
* レイヤーやカテゴリなどのリレーションシップがない、
* 特定のキャラクターデータモデルのツリーの一部には組み込まれていない状態のもの.
*/ public class IndependentPartsSetInfo implements Serializable { /** * シリアライズバージョンID */ private static final long serialVersionUID = 7280485045920860407L; /** * ロガー */ private static final Logger logger = Logger .getLogger(IndependentPartsSetInfo.class.getName()); /** * バーツセットのID */ private String id; /** * パーツセットの表示名 */ private String displayName; /** * 背景色、未設定であればnull */ private Color backgroundColor; /** * アフィン変換パラメータ、未設定であればnull */ private double[] affineTransformParameter; /** * カテゴリIDをキーとし、パーツ名をキーとしカラー情報のリストを値とするマップを値とする. */ private Map>> partsMap = new HashMap>>(); public String getId() { return id; } public void setId(String id) { this.id = id; } public String getDisplayName() { return displayName; } public void setDisplayName(String displayName) { this.displayName = displayName; } public Color getBackgroundColor() { return backgroundColor; } public void setBackgroundColor(Color backgroundColor) { this.backgroundColor = backgroundColor; } public double[] getAffineTransformParameter() { return affineTransformParameter; } public void setAffineTransformParameter( double[] affineTransformParameter) { this.affineTransformParameter = affineTransformParameter; } /** * カテゴリIDをキーとし、パーツ名をキーとしカラー情報のリストを値とするマップを値とする. * * @return カテゴリIDをキーとし、パーツ名をキーとしカラー情報のリストを値とするマップを値とする. */ public Map>> getPartsMap() { return partsMap; } public void setPartsMap( Map>> partsMap) { if (partsMap == null) { throw new IllegalArgumentException(); } this.partsMap = partsMap; } /** * インスタンス独立のパーツセット情報から、指定されたキャラクターデータに関連づけられた パーツ情報に変換して返す.
* * @param partsSetInfo * インスタンス独立のパーツセット情報 * @param characterData * キャラクターデータ * @param presetParts * プリセットか? * @return キャラクターデータに関連づけられたパーツセットインスタンス */ public static PartsSet convertPartsSet( IndependentPartsSetInfo partsSetInfo, CharacterData characterData, boolean presetParts) { if (partsSetInfo == null || characterData == null) { throw new IllegalArgumentException(); } PartsSet partsSet = new PartsSet(); partsSet.setPartsSetId(partsSetInfo.getId()); partsSet.setLocalizedName(partsSetInfo.getDisplayName()); partsSet.setPresetParts(presetParts); Color backgroundColor = partsSetInfo.getBackgroundColor(); if (backgroundColor != null) { partsSet.setBgColor(backgroundColor); } double[] affineTrans = partsSetInfo.getAffineTransformParameter(); if (affineTrans != null) { partsSet.setAffineTransformParameter(affineTrans); } Map>> partsMap = partsSetInfo .getPartsMap(); for (Map.Entry>> categoryEntry : partsMap .entrySet()) { String categoryId = categoryEntry.getKey(); Map> categoryPartsMap = categoryEntry .getValue(); PartsCategory partsCategory = characterData .getPartsCategory(categoryId); if (partsCategory == null) { logger.log(Level.WARNING, "undefined category-id: " + categoryId); continue; } for (Map.Entry> partsEntry : categoryPartsMap .entrySet()) { String partsName = partsEntry.getKey(); List colorInfoList = partsEntry .getValue(); PartsIdentifier partsIdentifier = new PartsIdentifier( partsCategory, partsName, partsName); PartsColorInfo partsColorInfo = IndependentPartsColorInfo .buildPartsColorInfo(characterData, partsCategory, colorInfoList); partsSet.appendParts(partsCategory, partsIdentifier, partsColorInfo); } } return partsSet; } } CharacterManaJ/src/charactermanaj/model/CharacterDataChangeEvent.java0000644000175000017500000000153312560206305026065 0ustar paulliupaulliupackage charactermanaj.model; import java.util.EventObject; public class CharacterDataChangeEvent extends EventObject { private static final long serialVersionUID = -99746684880598436L; private CharacterData characterData; private boolean changeStructure; private boolean reloadPartsAndFavorites; public CharacterDataChangeEvent(Object src, CharacterData characterData, boolean changeStructure, boolean reloadPartsAndFavorites) { super(src); this.characterData = characterData; this.changeStructure = changeStructure; this.reloadPartsAndFavorites = reloadPartsAndFavorites; } public CharacterData getCharacterData() { return characterData; } public boolean isChangeStructure() { return changeStructure; } public boolean isReloadPartsAndFavorites() { return reloadPartsAndFavorites; } } CharacterManaJ/src/charactermanaj/model/CharacterDataChangeObserver.java0000644000175000017500000000347012560206305026575 0ustar paulliupaulliupackage charactermanaj.model; import javax.swing.event.EventListenerList; /** * キャラクターデータが変更されたことを通知するためのメカニズム * * @author seraphy * */ public abstract class CharacterDataChangeObserver { private static CharacterDataChangeObserver inst = new CharacterDataChangeObserverImpl(); public static CharacterDataChangeObserver getDefault() { return inst; } public abstract void addCharacterDataChangeListener( CharacterDataChangeListener l); public abstract void removeCharacterDataChangeListener( CharacterDataChangeListener l); public abstract void notifyCharacterDataChange(CharacterDataChangeEvent e); public void notifyCharacterDataChange(Object wnd, CharacterData cd, boolean changeStructure, boolean reloadPartsAndFavorites) { if (cd == null) { throw new IllegalArgumentException(); } notifyCharacterDataChange(new CharacterDataChangeEvent(wnd, cd, changeStructure, reloadPartsAndFavorites)); } } class CharacterDataChangeObserverImpl extends CharacterDataChangeObserver { private EventListenerList listeners = new EventListenerList(); @Override public void addCharacterDataChangeListener(CharacterDataChangeListener l) { listeners.add(CharacterDataChangeListener.class, l); } @Override public void removeCharacterDataChangeListener(CharacterDataChangeListener l) { listeners.remove(CharacterDataChangeListener.class, l); } @Override public void notifyCharacterDataChange(CharacterDataChangeEvent e) { if (e == null) { throw new IllegalArgumentException(); } CharacterDataChangeListener[] lst = listeners .getListeners(CharacterDataChangeListener.class); for (CharacterDataChangeListener l : lst) { l.notifyChangeCharacterData(e); } } } CharacterManaJ/src/charactermanaj/model/PartsSet.java0000644000175000017500000004245112560206305023040 0ustar paulliupaulliupackage charactermanaj.model; import java.awt.Color; import java.io.Serializable; import java.util.AbstractMap; import java.util.ArrayList; import java.util.Arrays; import java.util.Comparator; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; /** * パーツセット.
* 各カテゴリの選択パーツと、そのパーツの色情報、および背景色をセットにしたもの.
* 保存する必要がなければIDおよび表示名は使用されないため、nullとなりえる.
* * @author seraphy * */ public final class PartsSet extends AbstractMap> implements Serializable, Cloneable { /** * シリアライズバージョンID. */ private static final long serialVersionUID = 5972528889825451761L; /** * PartsSet用のデフォルトのコンパレータ.
* 名前順、ID順にソートする.
*/ public static final Comparator DEFAULT_COMPARATOR = new Comparator() { public int compare(PartsSet o1, PartsSet o2) { int ret = o1.getLocalizedName().compareTo(o2.getLocalizedName()); if (ret == 0) { ret = o1.getPartsSetId().compareTo(o2.getPartsSetId()); } if (ret == 0) { ret = o1.hashCode() - o2.hashCode(); } return ret; } }; /** * パーツセットID.
* 一時的な名前なしのパーツセットとして使われる場合はnull */ private String partsSetId; /** * パーツセットの表示名.
* 一時的な名前なしのパーツセットとして使われる場合はnull */ private String localizedName; /** * プリセットパーツフラグ */ private boolean presetParts; /** * パーツセットとともに使われる背景色.
*/ private Color bgColor; /** * アフィン変換用パラメータ.
* 変換しない場合はnull.
*/ private double[] affineTransformParameter; /** * パーツリスト */ private HashMap> parts = new HashMap>(); /** * パーツに対するカラー情報.
* かならずしも、パーツに対してカラー情報を設定する必要はない.
* カラー情報がない場合は空.
*/ private HashMap partsColorInfoMap = new HashMap(); /** * 無名、空のパーツセットを作成する. */ public PartsSet() { this(null, null, false); } /** * 名前つきパーツセットを作成する.
* * @param partsSetId * パーツセットID * @param localizedName * 表示名 * @param presetParts * プリセットフラグ */ public PartsSet(String partsSetId, String localizedName, boolean presetParts) { this.partsSetId = partsSetId; this.localizedName = localizedName; this.presetParts = presetParts; } /** * パーツセットをディープコピーする.
* 現在のキャラクターデータのカテゴリインスタンスに関連づけて再生させる場合は、 * resolverに現在のキャラクターデータのカテゴリリゾルバを指定します.
* * @param org * 元オブジェクト * @param resolver * パーツセットのカテゴリを再生するためのリゾルバ、再生する必要がなければnull可 */ protected PartsSet(PartsSet org, PartsCategoryResolver resolver) { if (org == null) { throw new IllegalArgumentException(); } this.partsSetId = org.partsSetId; this.localizedName = org.localizedName; this.presetParts = org.presetParts; this.bgColor = org.bgColor; this.affineTransformParameter = org.affineTransformParameter == null ? null : org.affineTransformParameter.clone(); // ColorInfoMapの正規化 for (Map.Entry partsColorInfoEntry : org.partsColorInfoMap.entrySet()) { PartsIdentifier partsIdentifier = partsColorInfoEntry.getKey(); if (resolver != null) { PartsCategory orgPartsCategory = partsIdentifier.getPartsCategory(); PartsCategory repPartsCategory = resolver.getPartsCategory(orgPartsCategory.getCategoryId()); if (repPartsCategory == null) { // 同一IDのカテゴリがリゾルバになければ、このパーツは無かったことにする. continue; } if (orgPartsCategory != repPartsCategory) { // インスタンスが一致しなければリゾルバ側の結果を優先する. partsIdentifier = new PartsIdentifier( repPartsCategory, partsIdentifier.getPartsName(), partsIdentifier.getLocalizedPartsName()); } } PartsCategory repPartsCategory = partsIdentifier.getPartsCategory(); PartsColorInfo copiedPartsColorInfo = partsColorInfoEntry .getValue().createCompatible(repPartsCategory); partsColorInfoMap.put(partsIdentifier, copiedPartsColorInfo); } // PartsIdentifierの正規化 for (Map.Entry> partsEntry : org.parts.entrySet()) { PartsCategory orgPartsCategory = partsEntry.getKey(); PartsCategory partsCategory = orgPartsCategory; if (resolver != null) { PartsCategory repPartsCategory = resolver.getPartsCategory(orgPartsCategory.getCategoryId()); if (repPartsCategory == null) { // 同一IDのカテゴリがリゾルバになければ、このカテゴリはなかったことにする. continue; } if (repPartsCategory != orgPartsCategory) { // インスタンスが一致しなければリゾルバ側の結果を優先する. partsCategory = repPartsCategory; } } ArrayList partsIdentifiers = new ArrayList(partsEntry.getValue()); parts.put(partsCategory, partsIdentifiers); } } /** * リゾルバを使用してパーツカテゴリ(パーツ識別子のカテゴリも含む)のインスタンスを入れ替えます.
* パーツセットはキャラクターデータとは独立してロード・セーブされることがあるため、同じ情報でも異なるインスタンスとなる場合があり、 * これを是正するために、このメソッドを使用します.
* リゾルバから取得できないパーツカテゴリは除去されます.
* * @param resolver * リゾルバ * @return 互換性のあるパーツセット */ public PartsSet createCompatible(PartsCategoryResolver resolver) { if (resolver == null) { throw new IllegalArgumentException(); } // XXX: 本当は、こんなことはしたくない。 return new PartsSet(this, resolver); } /** * パーツセットをディープコピーする.
* * @return コピーされたパーツセット */ @Override public PartsSet clone() { return new PartsSet(this, null); } @Override public int hashCode() { return super.hashCode() ^ (partsSetId == null ? 0 : partsSetId.hashCode()); } @Override public boolean equals(Object o) { if (o == this) { return true; } if (o != null && o instanceof PartsSet) { PartsSet obj = (PartsSet) o; // 双方のIDがnullもしくは、同一インスタンスであるか、ID文字列が等値である場合 if (partsSetId == obj.partsSetId || (partsSetId != null && partsSetId.equals(obj.partsSetId))) { // AbstractMapのequalsでパーツの構成物を比較する. if (super.equals(obj)) { // カラー定義が等しいか比較する. if (partsColorInfoMap.equals(obj.partsColorInfoMap)) { // 背景色がともにnullもしくは同一インスタンスであるか、背景色が等値である場合 if (bgColor == obj.bgColor || (bgColor != null && bgColor.equals(obj.bgColor))) { // アフィン変換パラメータがともにnullもしくは同一インスタンスであるか、同値である場合. if (affineTransformParameter == obj.affineTransformParameter || (affineTransformParameter != null && Arrays.equals(affineTransformParameter, obj.affineTransformParameter))) { return true; } } } } } } return false; } public void setPresetParts(boolean presetParts) { this.presetParts = presetParts; } /** * プリセット用パーツセットであるか? これがfalseの場合は一時的なパーツセットか、もしくはお気に入り用である.
* * @return プリセット用パーツセットである場合 */ public boolean isPresetParts() { return presetParts; } public void setBgColor(Color bgColor) { this.bgColor = bgColor; } /** * バックグラウンドカラーを取得する.
* 設定されていなければnull.
* * @return バックグラウンドカラー、もしくはnull */ public Color getBgColor() { return bgColor; } /** * アフィン変換用パラメータを指定する.
* 配列は4または6でなければならない.
* アフィン変換しない場合はnull * * @param affineTransformParameter * 変換パラメータ(4または6個の要素)、もしくはnull */ public void setAffineTransformParameter(double[] affineTransformParameter) { if (affineTransformParameter != null && !(affineTransformParameter.length == 4 || affineTransformParameter.length == 6)) { throw new IllegalArgumentException("affineTransformParameter invalid length."); } this.affineTransformParameter = affineTransformParameter == null ? null : affineTransformParameter.clone(); } /** * アフィン変換用のパラメータを取得する.
* 変換しない場合はnull.
* * @return アフィン変換用のパラメータ、またはnull */ public double[] getAffineTransformParameter() { return affineTransformParameter == null ? null : affineTransformParameter.clone(); } public void setPartsSetId(String partsSetId) { this.partsSetId = partsSetId; } public void setLocalizedName(String localizedName) { this.localizedName = localizedName; } /** * プリセットIDを取得する.
* 一時的なパーツセットである場合はnull * * @return プリセットID、またはnull */ public String getPartsSetId() { return partsSetId; } /** * プリセット名を取得する.
* 一時的なパーツセットである場合はnull * * @return プリセット名、またはnull */ public String getLocalizedName() { return localizedName; } /** * パーツセットのエントリセットを取得する. */ @Override public Set>> entrySet() { return parts.entrySet(); } /** * パーツカラー情報を取得する.
* カラー情報が関連づけられていない場合はnullが返される.
* * @param partsIdentifier * パーツ識別子 * @return カラー情報、もしくはnull */ public PartsColorInfo getColorInfo(PartsIdentifier partsIdentifier) { PartsColorInfo partsColorInfo = partsColorInfoMap.get(partsIdentifier); return partsColorInfo == null ? null : partsColorInfo.clone(); } /** * カテゴリ別にパーツを登録する.
* partsNameがnullまたは空文字の場合はカテゴリのみ登録する.
* これにより、そのカテゴリに選択がないことを示す.
* 複数選択可能なカテゴリの場合、複数回の呼び出しで登録する.(登録順)
* * @param category * カテゴリ * @param partsIdentifier * パーツ識別子、またはnull * @param partsColorInfo * パーツの色情報、なければnull可 */ public void appendParts(PartsCategory category, PartsIdentifier partsIdentifier, PartsColorInfo partsColorInfo) { if (category == null) { throw new IllegalArgumentException(); } List partsIdentifiers = parts.get(category); if (partsIdentifiers == null) { partsIdentifiers = new ArrayList(); parts.put(category, partsIdentifiers); } if (partsIdentifier != null) { partsIdentifiers.add(partsIdentifier); if (partsColorInfo != null) { partsColorInfoMap.put(partsIdentifier, partsColorInfo.clone()); } } } /** * すべてのパーツのカラー情報を除去する.
*/ public void removeColorInfo() { partsColorInfoMap.clear(); } /** * パーツセットが構造的に一致するか検証します.
* nullの場合は常にfalseとなります.
* * @param other * 比較対象、null可 * @return パーツ構成が一致すればtrue、そうでなければfalse */ public boolean isSameStructure(PartsSet other) { if (other != null && other.size() == this.size()) { // カテゴリが一致し、各カテゴリに登録されているパーツ識別子のリストも順序を含めて一致する場合、 // 構造的に一致すると判定する. for (Map.Entry> entry : entrySet()) { PartsCategory category = entry.getKey(); List ownList = entry.getValue(); List otherList = other.get(category); if (ownList == null || otherList == null || !ownList.equals(otherList)) { return false; } } return true; } return false; } /** * 2つのパーツセットが構造的に一致するか検証します.
* いずれか一方がnullであればfalseを返します.双方がnullであればtrueを返します.
* 双方がnullでなければ{@link #isSameStructure(PartsSet)}で判定されます.
* * @param a * 比較対象1、null可 * @param b * 比較対象2、null可 * @return パーツ構成が一致すればtrue、そうでなければfalse */ public static boolean isSameStructure(PartsSet a, PartsSet b) { if (a == b) { return true; } if (a == null || b == null) { return false; } return a.isSameStructure(b); } /** * 保持しているパーツ識別子のカラー情報と同一のカラー情報をもっているか判定します.
* 相手側はカテゴリや順序を問わず、少なくとも自分と同じパーツ識別子をもっていれば足りるため、 パーツ構成が同一であるかの判定は行いません.
* パーツ構造を含めて判定を行う場合は事前に{@link #isSameStructure(PartsSet)}を呼び出します.
* nullの場合は常にfalseとなります.
* * @param other * 判定先、null可 * @return 同一であればtrue、そうでなければfalse */ public boolean isSameColor(PartsSet other) { if (other != null && other.size() == size()) { for (List partsIdentifiers : values()) { for (PartsIdentifier partsIdentifier : partsIdentifiers) { PartsColorInfo ownColorInfo = getColorInfo(partsIdentifier); PartsColorInfo otherColorInfo = other.getColorInfo(partsIdentifier); if ( !PartsColorInfo.equals(ownColorInfo, otherColorInfo)) { return false; } } } return true; } return false; } /** * 引数aが保持しているパーツ識別子のカラー情報と同一のカラー情報を引数bがもっているか判定します.
* 引数b側はカテゴリや順序を問わず、少なくとも引数aと同じパーツ識別子をもっていれば足りるため、 パーツ構成が同一であるかの判定は行いません.
* パーツ構造を含めて判定を行う場合は事前に{@link #isSameStructure(PartsSet, PartsSet)}を呼び出します.
* 双方がnullであればtrueとなります.
* いずれか一方がnullの場合はfalseとなります.
* * @param a * 対象1、null可 * @param b * 対象2、null可 * @return 同一であればtrue、そうでなければfalse */ public static boolean isSameColor(PartsSet a, PartsSet b) { if (a == b) { return true; } if (a == null || b == null) { return false; } return a.isSameColor(b); } /** * このパーツセットが名前をもっているか? * * @return 名前がある場合はtrue、設定されていないか空文字の場合はfalse */ public boolean hasName() { return localizedName != null && localizedName.length() > 0; } @Override public String toString() { StringBuilder buf = new StringBuilder(); buf.append(getClass().getSimpleName() + "@" + Integer.toHexString(System.identityHashCode(this))); buf.append("("); buf.append("partsSetId: " + partsSetId + ", "); buf.append("localizedName: " + localizedName + ", "); buf.append("presetFlg: " + presetParts + ", "); buf.append("background-color: " + bgColor + ", "); buf.append("affin-trans-param: " + Arrays.toString(affineTransformParameter) + ", "); buf.append("parts: " + parts + ", "); buf.append("partsColorMap: " + partsColorInfoMap); buf.append(")"); return buf.toString(); } } CharacterManaJ/src/charactermanaj/model/PartsFiles.java0000644000175000017500000000331512560206305023343 0ustar paulliupaulliupackage charactermanaj.model; import java.io.Serializable; import java.util.AbstractMap; import java.util.Collections; import java.util.HashMap; import java.util.Map; import java.util.Set; import charactermanaj.graphics.io.ImageResource; public class PartsFiles extends AbstractMap implements Serializable { private static final long serialVersionUID = 5799830380308843243L; private HashMap partsMap = new HashMap(); private final PartsIdentifier partsIdentifier; public PartsFiles(PartsIdentifier partsName) { if (partsName == null) { throw new IllegalArgumentException(); } this.partsIdentifier = partsName; } public PartsIdentifier getPartsIdentifier() { return partsIdentifier; } @Override public Set> entrySet() { return Collections.unmodifiableSet(partsMap.entrySet()); } @Override public ImageResource put(final Layer key, final ImageResource value) { if (key == null || value == null) { throw new IllegalArgumentException(); } if (!partsIdentifier.hasLayer(key)) { throw new IllegalArgumentException(key.toString()); } return partsMap.put(key, value); } @Override public ImageResource get(Object key) { return partsMap.get(key); } @Override public boolean containsKey(Object key) { return partsMap.containsKey(key); } public long lastModified() { long maxLastModified = 0; for (ImageResource imageResource : values()) { long lastModified = imageResource.lastModified(); if (lastModified > maxLastModified) { maxLastModified = lastModified; } } return maxLastModified; } } CharacterManaJ/src/charactermanaj/model/PartsColorManager.java0000644000175000017500000003014312560206305024651 0ustar paulliupaulliupackage charactermanaj.model; import java.util.HashMap; import java.util.Iterator; import java.util.Map; import java.util.logging.Level; import java.util.logging.Logger; import charactermanaj.graphics.filters.ColorConvertParameter; /** * パーツごとの色情報を設定・取得したり、最後に設定した色情報を記憶するなどの色情報アクセスのためのクラス.
* @author seraphy * */ public class PartsColorManager { private static final Logger logger = Logger.getLogger(PartsColorManager.class.getName()); /** * カテゴリごとのパーツカラー情報.
* @author seraphy */ public static final class CategoryColorInfo { private final PartsColorInfo partsColorInfo; private final boolean applyAll; public CategoryColorInfo(PartsColorInfo partsColorInfo, boolean applyAll) { this.partsColorInfo = partsColorInfo; this.applyAll = applyAll; } public PartsColorInfo getPartsColorInfo() { return partsColorInfo; } public boolean isApplyAll() { return applyAll; } } /** * パーツ単位でのカラーグループを含む色情報.
* カテゴリ全体に適用される場合はパーツ単位の色情報はリセットする.
*/ private HashMap partsColorInfoMap = new HashMap(); /** * カテゴリごとに共通となる場合のカラーグループを含む色情報.
* パーツ単位の色情報が定義されていない場合、カテゴリ単位での情報が使用される.
*/ private HashMap categoryColorInfoMap = new HashMap(); /** * カラーグループごとの色情報.
*/ private HashMap recentColorGroupMap = new HashMap(); /** * パーツ設定のリゾルバ */ private PartsSpecResolver partsSpecResolver; /** * パーツ設定リゾルバを指定して構築する. * @param partsSpecResolver リゾルバ */ public PartsColorManager(PartsSpecResolver partsSpecResolver) { if (partsSpecResolver == null) { throw new IllegalArgumentException(); } this.partsSpecResolver = partsSpecResolver; } /** * パーツ識別子ごとの色情報を取得します.
* まだ一度も登録されていない場合は、現在の状態から色情報を作成して返します.
* その場合、registered引数がtrueである場合は生成と同時に初期値として登録済みとする.
* そうでない場合は生成された色情報は一時的なものとなる. * @param partsIdentifier パーツ識別子 * @param registered 色情報を新規に作成した場合に登録する場合はtrue * @return 色情報 */ public PartsColorInfo getPartsColorInfo(PartsIdentifier partsIdentifier, boolean registered) { if (partsIdentifier == null) { throw new IllegalArgumentException(); } // パーツ識別子ごとのカラー情報を取得する. PartsColorInfo partsColorInfo = partsColorInfoMap.get(partsIdentifier); if (partsColorInfo == null) { // パーツ識別子ごとのカラー情報が設定されていない場合は // カテゴリ別情報からカラー情報を生成する. partsColorInfo = createDefaultColorInfo(partsIdentifier); if (registered) { // 生成されたカラー情報をパーツ識別子ごとのカラー情報に適用する. partsColorInfoMap.put(partsIdentifier, partsColorInfo); } } return partsColorInfo; } /** * パーツ識別子ごとのパーツ色情報を保存します.
* @param partsIdentifier パーツ識別子 * @param partsColorInfo パーツの色情報 * @param applyAll パーツ識別子ではなく、カテゴリに対して保存する場合 */ public void setPartsColorInfo(PartsIdentifier partsIdentifier, PartsColorInfo partsColorInfo, boolean applyAll) { if (partsIdentifier == null || partsColorInfo == null) { throw new IllegalArgumentException(); } partsColorInfo = partsColorInfo.clone(); PartsCategory partsCategory = partsIdentifier.getPartsCategory(); if (applyAll) { // カテゴリ指定の場合 // パーツ個別色をリセットすることでカテゴリを優先させる. resetPartsColorInfo(partsCategory); if (logger.isLoggable(Level.FINEST)) { logger.log(Level.FINEST, "setPartsColorInfo(Category): " + partsIdentifier + "=" + partsColorInfo); } } else { // パーツ個別指定の場合 partsColorInfoMap.put(partsIdentifier, partsColorInfo); if (logger.isLoggable(Level.FINEST)) { logger.log(Level.FINEST, "setPartsColorInfo(Parts): " + partsIdentifier + "=" + partsColorInfo); } } // カラーグループとしての最新のカラー情報を保存する.(有効なカラーグループで連動指定がある場合のみ) // ただし、「すべてに適用」でない場合は保存しない. if (applyAll) { setRecentColorGroup(partsColorInfo); } // カテゴリごとの最新の色情報を設定する. // (「すべてに適用」であるか、単数選択カテゴリで、まだ「すべてに適用」の色情報がない場合のみ.) // (複数選択カテゴリの場合は明示的に「すべてに適用」を選択していないかぎり保存されない.) CategoryColorInfo categoryColorInfo = categoryColorInfoMap.get(partsCategory); if (applyAll || (!partsCategory.isMultipleSelectable() && (categoryColorInfo == null || !categoryColorInfo.isApplyAll()))) { categoryColorInfo = new CategoryColorInfo(partsColorInfo, applyAll); categoryColorInfoMap.put(partsCategory, categoryColorInfo); } } /** * パーツの色情報を指定して、パーツ識別子の各レイーヤの色グループ情報を保存します.
* 連動指定が有効であり、有効なカラーグループである場合のみ保存されます.
* @param partsColorInfo パーツ識別子 */ protected void setRecentColorGroup(PartsColorInfo partsColorInfo) { if (partsColorInfo == null) { return; } for (Map.Entry entry : partsColorInfo.entrySet()) { ColorInfo colorInfo = entry.getValue(); ColorGroup colorGroup = colorInfo.getColorGroup(); if (colorInfo.isSyncColorGroup() && colorGroup != null && colorGroup.isEnabled()) { ColorConvertParameter colorParam = colorInfo.getColorParameter(); if (colorParam != null) { colorParam = colorParam.clone(); } ColorConvertParameter oldColorParam = recentColorGroupMap.put(colorGroup, colorParam); if (logger.isLoggable(Level.FINEST)) { if ( !ColorConvertParameter.equals(colorParam, oldColorParam)) { logger.log(Level.FINEST, "setRecentColorGroup(" + colorGroup + ")=" + colorParam); } } } } } /** * カラーグループごとのもっとも最近に設定した色情報を取得します.
* カラーグループが有効でないか、まだ一度も登録されていない場合はnullが返されます.
* @param colorGroup 色グループ、null可 * @return 色情報、もしくはnull */ protected ColorConvertParameter getRecentColorGroup(ColorGroup colorGroup) { if (colorGroup == null || !colorGroup.isEnabled()) { return null; } ColorConvertParameter colorParam = recentColorGroupMap.get(colorGroup); if (colorParam != null) { colorParam = colorParam.clone(); } return colorParam; } /** * 指定したパーツカテゴリの色情報を取得します.
* 登録がない場合はnullが返されます.
* @param partsCategory パーツカテゴリ * @return 指定したパーツカテゴリの色情報、またはnull */ public CategoryColorInfo getPartsColorInfo(PartsCategory partsCategory) { return categoryColorInfoMap.get(partsCategory); } /** * すべてのパーツ識別子の色情報をリセットします.
*/ public void resetPartsColorInfo() { resetPartsColorInfo(null); } /** * 指定したカテゴリと、カテゴリに属するパーツ識別子ごとの色情報をリセットします.
* 引数partsCategoryがnullの場合は全パーツ識別子、全カテゴリと、すべてのカラーグループをリセットします.
* @param partsCategory パーツカテゴリまたはnull */ public void resetPartsColorInfo(PartsCategory partsCategory) { if (partsCategory == null) { recentColorGroupMap.clear(); partsColorInfoMap.clear(); categoryColorInfoMap.clear(); return; } categoryColorInfoMap.remove(partsCategory); Iterator> ite = partsColorInfoMap.entrySet().iterator(); while (ite.hasNext()) { Map.Entry entry = ite.next(); PartsIdentifier partsIdentifier = entry.getKey(); if (partsIdentifier.getPartsCategory().equals(partsCategory)) { ite.remove(); } } } /** * パーツ識別子ごとの色情報を現在の状態から新たに構築する. * @param partsIdentifier パーツ識別子 * @return 色情報 */ protected PartsColorInfo createDefaultColorInfo(PartsIdentifier partsIdentifier) { PartsCategory category = partsIdentifier.getPartsCategory(); PartsColorInfo partsColorInfo = new PartsColorInfo(category); // パーツ固有のカラーグループの指定があるか? PartsSpec partsSpec = partsSpecResolver.getPartsSpec(partsIdentifier); ColorGroup partsSpecColorGroup = null; if (partsSpec != null) { partsSpecColorGroup = partsSpec.getColorGroup(); } if (partsSpecColorGroup != null && partsSpecColorGroup.isEnabled()) { // パーツ固定のカラーグループの指定があれば // 全レイヤーを該当カラーグループに設定する. for (Map.Entry entry : partsColorInfo.entrySet()) { ColorInfo colorInfo = entry.getValue(); colorInfo = colorInfo.clone(); colorInfo.setColorGroup(partsSpecColorGroup); colorInfo.setSyncColorGroup(true); entry.setValue(colorInfo); } } else { // パーツ固有のカラーグループがなければ // 同一カテゴリの最近設定されたカラー情報をもとに、パーツカラー情報を作成する. CategoryColorInfo categoryColorInfo = categoryColorInfoMap.get(category); if (categoryColorInfo != null) { PartsColorInfo categoryPartsColorInfo = categoryColorInfo.getPartsColorInfo(); for (Map.Entry entry : categoryPartsColorInfo.entrySet()) { Layer layer = entry.getKey(); ColorInfo colorInfo = entry.getValue(); if (colorInfo != null && partsColorInfo.containsKey(layer)) { colorInfo = colorInfo.clone(); // ただし、同一カテゴリに設定されたカラー情報が「すべてに適用」でない場合は、 // レイヤー固有のカラーグループを維持する. if ( !categoryColorInfo.isApplyAll()) { ColorGroup layerColorGroup = layer.getColorGroup(); if (layerColorGroup == null) { layerColorGroup = ColorGroup.NA; } colorInfo.setColorGroup(layerColorGroup); } partsColorInfo.put(layer, colorInfo); } } } } // カラーグループが指定されている場合、もっとも最近に設定されたカラーグループの色情報に設定し直す for (Map.Entry entry : partsColorInfo.entrySet()) { ColorInfo colorInfo = entry.getValue(); ColorGroup colorGroup = colorInfo.getColorGroup(); if (colorGroup != null && colorGroup.isEnabled() && colorInfo.isSyncColorGroup()) { ColorConvertParameter param = getRecentColorGroup(colorGroup); if (param != null) { colorInfo.setColorParameter(param); } } } return partsColorInfo; } public Map getPartsColorInfoMap() { return partsColorInfoMap; } } CharacterManaJ/src/charactermanaj/util/0000755000175000017500000000000012560206305020277 5ustar paulliupaulliuCharacterManaJ/src/charactermanaj/util/SetupLocalization.java0000644000175000017500000001502612560206305024617 0ustar paulliupaulliupackage charactermanaj.util; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.net.JarURLConnection; import java.net.URISyntaxException; import java.net.URL; import java.net.URLConnection; import java.util.ArrayList; import java.util.Collection; import java.util.EnumSet; import java.util.Enumeration; import java.util.jar.JarEntry; import java.util.jar.JarFile; import java.util.logging.Level; import java.util.logging.Logger; /** * 言語リソースを管理する. * * @author seraphy */ public class SetupLocalization extends ResourceLoader { private final Logger logger = Logger.getLogger(getClass().getName()); public static final String DIRNAME_RESOURCES = "resources"; /** * リソースフォルダ下のサブディレクトリ一覧.
*/ public enum Resources { Languages("languages"), Menu("menu"), Template("template"); private final String dirName; private Resources(String dirName) { this.dirName = dirName; } public String getDirName() { return dirName; } @Override public String toString() { return getDirName(); } } private File baseDir; /** * アプリケーションデータ用ディレクトリを指定して構築する. * * @param baseDir * データディレクトリ */ public SetupLocalization(File baseDir) { if (baseDir == null || !baseDir.isDirectory()) { throw new IllegalArgumentException(); } this.baseDir = baseDir; } /** * コピー対象とするリソース一覧を取得する.
* * @param resourceSet * リソースディレクトリのサブディレクトリ名のリスト * @return リソース一覧(言語関連リソース、テンプレートなど) * @throws IOException * 失敗 */ protected Collection getResourceList(EnumSet resourceSet) throws IOException { if (resourceSet == null) { resourceSet = EnumSet.noneOf(Resources.class); } ArrayList resources = new ArrayList(); ClassLoader cl = getClass().getClassLoader(); for (Resources resourceKey : resourceSet) { String name = resourceKey.getDirName(); URL loc = cl.getResource(name); if (loc == null) { continue; } String protocol = loc.getProtocol(); if ("file".equals(protocol)) { // ファイル上にクラスやリソースがある場合 try { File dir = new File(loc.toURI()); File[] files = dir.listFiles(); if (files != null) { for (File file : files) { if (file.isDirectory()) { continue; } resources.add(name + "/" + file.getName()); } } } catch (URISyntaxException e) { throw new RuntimeException(e); } } else if ("jar".equals(protocol)) { // jarにクラスやリソースがある場合 JarURLConnection conn = (JarURLConnection) loc.openConnection(); JarEntry dirEntry = conn.getJarEntry(); assert dirEntry != null; // "jar:file:xxxx.jar!yyyy" のyyyyの部分 String prefix = dirEntry.getName() + "/"; JarFile jarFile = conn.getJarFile(); try { Enumeration enm = jarFile.entries(); while (enm.hasMoreElements()) { JarEntry entry = enm.nextElement(); if (entry.isDirectory()) { continue; } String entryName = entry.getName(); if (entryName.startsWith(prefix)) { resources.add(entryName); } } } finally { if (!conn.getUseCaches()) { // キャッシュしてある場合は明示的にクローズしない. // (そもそもクローズする必要はないかも) // (たぶん、システムなどがインスタンスを再利用していると思われるため) // (jdk5でクローズすると例外が発生する。jdk7のリビジョンによっても発生するようだ) // http://bugs.sun.com/view_bug.do?bug_id=7050028 jarFile.close(); } } } } logger.log(Level.FINE, "resource list: " +resources); return resources; } /** * リソースをファイルにコピーする.
* * @param fromURL * @param toFile * @throws IOException */ protected void copyResource(URL fromURL, File toFile) throws IOException { logger.log(Level.INFO, "copy resource '" + fromURL + "' to '" + toFile + "'"); File dir = toFile.getParentFile(); if ( !dir.exists()) { if ( !dir.mkdirs()) { throw new IOException("can't create directory. " + dir); } } URLConnection conn = fromURL.openConnection(); conn.setDoInput(true); InputStream is = conn.getInputStream(); try { long lastModified = conn.getLastModified(); OutputStream os = new FileOutputStream(toFile); try { byte[] buf = new byte[4096]; for (;;) { int rd = is.read(buf); if (rd <= 0) { break; } os.write(buf, 0, rd); } } finally { os.close(); } boolean result = toFile.setLastModified(lastModified); logger.log(Level.FINE, "setLastModified(" + toFile+ ") succeeded=" + result); } finally { is.close(); } } /** * リソースディレクトリを返す. * * @return リソースディレクトリ */ public File getResourceDir() { try { return new File(baseDir, DIRNAME_RESOURCES).getCanonicalFile(); } catch (Exception ex) { throw new RuntimeException(ex); } } /** * ローカルシステム上のアプリケーションデータディレクトリに言語リソースをコピーする. * * @param resourceSet * コピーするリソースセット. * @param overwrite * 上書きを許可する場合はtrue、スキップする場合はfalse * @throws IOException * 失敗 */ public void setupToLocal(EnumSet resourceSet, boolean overwrite) throws IOException { File toDir = getResourceDir(); ClassLoader cl = getDefaultClassLoader(); for (String resourceName : getResourceList(resourceSet)) { URL url = cl.getResource(resourceName); if (url != null) { File toFile = new File(toDir, resourceName).getCanonicalFile(); if (overwrite || !toFile.exists()) { // 上書き許可か、まだファイルが存在しなければコピーする. copyResource(url, toFile); } } else { logger.log(Level.WARNING, "missing resource: " + resourceName); } } } } CharacterManaJ/src/charactermanaj/util/LocalizedMessageAware.java0000644000175000017500000000065312560206305025341 0ustar paulliupaulliupackage charactermanaj.util; /** * ローカライズリソースを持っていることを示すインターフェイス.
* @author seraphy */ public interface LocalizedMessageAware { /** * ローカライズリソースのIDを取得する.
* 設定されていない場合はnullが返される.
* @return リソースID、もしくはnull */ String getLocalizedResourceId(); } CharacterManaJ/src/charactermanaj/util/AWTExceptionLoggingHandler.java0000644000175000017500000000233312560206305026262 0ustar paulliupaulliupackage charactermanaj.util; import java.util.logging.Level; import java.util.logging.Logger; import javax.swing.JFrame; import javax.swing.SwingUtilities; import charactermanaj.ui.MainFrame; /** * SwingのEDT内での例外をロギングするためのハンドラ. * @author seraphy */ public class AWTExceptionLoggingHandler { /** * ロガー */ private static final Logger logger = Logger.getLogger(AWTExceptionLoggingHandler.class.getName()); /** * 例外のハンドル.
* @param ex 例外 */ public void handle(final Throwable ex) { // まずはロギング logger.log(Level.SEVERE, "exception occurred on the event dispatch thread. " + ex, ex); // エラーダイアログを表示する.(非同期) SwingUtilities.invokeLater(new Runnable() { public void run() { JFrame currentFrame = MainFrame.getActivedMainFrame(); if (currentFrame == null || !currentFrame.isDisplayable() || !currentFrame.isVisible()) { // メインフレームがまだ無いか、表示されていないか破棄済みであれば無いとみなす. currentFrame = null; } ErrorMessageHelper.showErrorDialog(currentFrame, ex); } }); } } CharacterManaJ/src/charactermanaj/util/ErrorMessageHelper.java0000644000175000017500000000355612560206305024711 0ustar paulliupaulliupackage charactermanaj.util; import java.awt.Component; import java.awt.Dimension; import java.io.PrintWriter; import java.io.StringWriter; import java.util.logging.Level; import java.util.logging.Logger; import javax.swing.JOptionPane; import javax.swing.JScrollPane; import javax.swing.JTextArea; /** * 例外を表示するダイアログ.
* ログにも記録される.
* @author seraphy */ public final class ErrorMessageHelper { /** * ロガー */ private static final Logger logger = Logger.getLogger(ErrorMessageHelper.class.getName()); private ErrorMessageHelper() { super(); } /** * 例外が発生したことを示すダイアログを表示し、ログに記録する.
* @param parent ダイアログを表示する親、null可 * @param ex 例外、nullの場合はなにもせずに戻る. */ public static void showErrorDialog(Component parent, Throwable ex) { if (ex == null) { return; } // ログに記録する. logger.log(Level.SEVERE, ex.getLocalizedMessage(), ex); // 例外を表示するパネルの生成 JTextArea textArea = new JTextArea(); StringWriter sw = new StringWriter(); PrintWriter pw = new PrintWriter(sw); ex.printStackTrace(pw); // 例外のコールスタックをパネルに表示できるように出力 pw.close(); textArea.setText(sw.toString()); textArea.setSelectionStart(0); textArea.setSelectionEnd(0); textArea.setEditable(false); JScrollPane scr = new JScrollPane(textArea); scr.setPreferredSize(new Dimension(400, 150)); scr.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS); scr.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS); // ダイアログの表示 JOptionPane.showMessageDialog(parent, scr, "ERROR", JOptionPane.ERROR_MESSAGE); } } CharacterManaJ/src/charactermanaj/util/UIHelper.java0000644000175000017500000001351212560206305022621 0ustar paulliupaulliupackage charactermanaj.util; import java.awt.Component; import java.awt.Container; import java.awt.Graphics; import java.awt.image.BufferedImage; import java.io.IOException; import java.net.URL; import java.util.ArrayList; import java.util.Collection; import javax.imageio.ImageIO; import javax.swing.AbstractButton; import javax.swing.Icon; import javax.swing.ImageIcon; import javax.swing.JButton; import javax.swing.JMenu; public final class UIHelper extends ResourceLoader { private static final UIHelper singleton = new UIHelper(); private UIHelper() { super(); } public static final UIHelper getInstance() { return singleton; } /** * 指定したコンテナに含まれる指定したコンポーネント型のすべてのコンポーネントを返す.
* 一つも該当するものがなければ空を返す * @param 対象のコンポーネントのクラス型 * @param clz クラス * @param container 対象のコンテナ * @return コンポーネントのコレクション、もしくは空 */ @SuppressWarnings("unchecked") public Collection getDescendantOfClass(Class clz, Container container) { if (container == null || clz == null) { throw new IllegalArgumentException(); } Collection components = new ArrayList(); getDescendantOfClass(clz, container, components); return (Collection) components; } private void getDescendantOfClass(Class clz, Container container, Collection results) { if (container == null) { return; } Component[] children = (container instanceof JMenu) ? ((JMenu) container).getMenuComponents() : container.getComponents(); int mx = children.length; for (int idx = 0; idx < mx; idx++) { Component comp = children[idx]; if (clz.isInstance(comp)) { results.add(comp); } else if (comp instanceof Container) { getDescendantOfClass(clz, (Container) comp, results); } } } /** * 2つのステートをもつアイコンを作成します.
* このアイコンは、使用するコンポーネントがAbstractButton派生クラスであれば、isSelectedの結果が * trueである場合は2番目のアイコンイメージを表示します.
* isSelectedの結果がfalseであるか、もしくはAbstractButton派生クラスでなければ * 最初のアイコンイメージを表示します.
* @param iconName1 アイコン1 * @param iconName2 アイコン2 * @return アイコン */ public Icon createTwoStateIcon(String iconName1, String iconName2) { if (iconName1 == null || iconName2 == null || iconName1.length() == 0 || iconName2.length() == 0) { throw new IllegalArgumentException(); } final BufferedImage pinIcon1 = getImage(iconName1); final BufferedImage pinIcon2 = getImage(iconName2); Icon icon = new Icon() { public void paintIcon(Component c, Graphics g, int x, int y) { boolean selected = false; if (c instanceof AbstractButton) { AbstractButton btn = (AbstractButton) c; selected = btn.isSelected(); } BufferedImage iconImage; if ( !selected) { iconImage = pinIcon1; } else { iconImage = pinIcon2; } int w = iconImage.getWidth(); int h = iconImage.getHeight(); g.drawImage(iconImage, x, y, w, h, 0, 0, w, h, null); } public int getIconHeight() { return pinIcon1.getHeight(); } public int getIconWidth() { return pinIcon1.getWidth(); } }; return icon; } /** * アイコンボタン(非透過)を作成して返す.
* リソースが取得できない場合は実行時例外が返される.
* @param iconName 画像リソース名 * @return アイコンボタン */ public JButton createIconButton(String iconName) { if (iconName == null || iconName.length() == 0) { throw new IllegalArgumentException(); } JButton btn = new JButton(); btn.setIcon(new ImageIcon(getImage(iconName))); return btn; } /** * 通常時の画像のみをもつ透過ボタンを作成して返す.
* リソースが取得できない場合は実行時例外が返される.
* @param normal 通常時の画像リソース * @return 透過ボタン */ public JButton createTransparentButton(String normal) { return createTransparentButton(normal, null); } /** * リソースから通常とホバー時の画像をもつ透過ボタンを作成して返す.
* リソースが取得できない場合は実行時例外が返される.
* @param normal 通常時の画像リソース * @param rollover ホバー時の画像リソース * @return 透過ボタン */ public JButton createTransparentButton(String normal, String rollover) { if (normal == null || normal.length() == 0) { throw new IllegalArgumentException(); } ImageIcon normIcon = new ImageIcon(getImage(normal)); JButton btn = new JButton(normIcon); if (rollover != null && rollover.length() != 0) { ImageIcon rolloverIcon = new ImageIcon(getImage(rollover)); btn.setRolloverEnabled(true); btn.setRolloverIcon(rolloverIcon); btn.setPressedIcon(rolloverIcon); } btn.setOpaque(false); btn.setBorderPainted(false); btn.setContentAreaFilled(false); return btn; } /** * リソースから画像を取得する.
* 画像が取得できない場合は実行時例外を返す.
* @param name リソース * @return 画像 */ public BufferedImage getImage(String name) { URL url = getResource(name); if (url == null) { throw new RuntimeException("resource not found. " + name); } try { return ImageIO.read(url); } catch (IOException ex) { throw new RuntimeException("image load error." + ex.getMessage(), ex); } } } CharacterManaJ/src/charactermanaj/util/LocalizedResourceTextLoader.java0000644000175000017500000000266512560206305026565 0ustar paulliupaulliupackage charactermanaj.util; import java.net.URL; import java.nio.charset.Charset; import java.util.Locale; /** * リソースからローカライズされたテキストを取得する.
* * @author seraphy * */ public class LocalizedResourceTextLoader extends ResourceLoader { private static final LocalizedResourceTextLoader inst = new LocalizedResourceTextLoader(); private LocalizedTextResource textResource = new LocalizedTextResource() { @Override protected URL getResource(String resourceName) { return LocalizedResourceTextLoader.this.getResource(resourceName); } }; private LocalizedResourceTextLoader() { super(); } public static LocalizedResourceTextLoader getInstance() { return inst; } /** * リソース名を指定して、テキストファイルを読み込んで、その文字列を返す.
* リソースは現在のデフォルトロケールを優先で検索されます.
* ファイルエンコーディングを引数csで指定する.
* * @param name * リソース名 * @param cs * ファイルのエンコーディング * @return ファイルの内容(テキスト) */ public String getText(String name, Charset cs) { return textResource.getText(name, cs); } public String getText(String name, Charset cs, Locale locale) { return textResource.getText(name, cs, locale); } } CharacterManaJ/src/charactermanaj/util/DesktopUtilities.java0000644000175000017500000001427412560206305024457 0ustar paulliupaulliupackage charactermanaj.util; import java.awt.Component; import java.awt.Toolkit; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.io.File; import java.io.IOException; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.net.URI; import java.util.logging.Level; import java.util.logging.Logger; import javax.swing.BoxLayout; import javax.swing.JLabel; import javax.swing.JOptionPane; import javax.swing.JPanel; import javax.swing.JTextField; /** * デスクトップへのアクセスを提供するユーテリティ.
* JDK6の機能を使うため、JDK5以前では何もしない.(エラーにはならない) * @author seraphy */ public class DesktopUtilities { /** * ロガー */ private static final Logger logger = Logger.getLogger(DesktopUtilities.class.getName()); /** * デスクトップオブジェクト。JDK6以降でなければ、もしくはデスクトップをサポートしていなければnull */ private static Object desktopObj; /** * ブラウズメソッド */ private static Method methodBrowse; /** * 編集メソッド */ private static Method methodEdit; /** * 開くメソッド */ private static Method methodOpen; static { try { Class clz = Class.forName("java.awt.Desktop"); Method mtdGetDesktop = clz.getMethod("getDesktop"); methodBrowse = clz.getMethod("browse", URI.class); methodEdit = clz.getMethod("edit", File.class); methodOpen = clz.getMethod("open", File.class); desktopObj = mtdGetDesktop.invoke(null); } catch (ClassNotFoundException ex) { // JDK6以降でない場合 logger.log(Level.CONFIG, "AWT Desktop is not suuported."); desktopObj = null; } catch (Exception ex) { // その他の例外は基本的に発生しないが、発生したとしても // 単にサポートしていないと見なして継続する. logger.log(Level.SEVERE, "AWT Desktop failed.", ex); desktopObj = null; } } private DesktopUtilities() { throw new RuntimeException("utilities class."); } public static boolean isSupported() { return desktopObj != null; } protected static boolean callMethod(Method method, Object arg) throws IOException { if (desktopObj == null) { return false; } try { if (logger.isLoggable(Level.FINER)) { logger.log(Level.FINER, "invoke: " + method + "/arg=" + arg); } method.invoke(desktopObj, arg); return true; } catch (InvocationTargetException ex) { Throwable iex = ex.getCause(); if (iex != null && iex instanceof IOException) { throw (IOException) iex; } throw new RuntimeException(ex.getMessage(), ex); } catch (IllegalAccessException ex) { throw new RuntimeException(ex.getMessage(), ex); } } /** * ファイルを開く. * @param uri ファイル * @return サポートしていない場合はfalse、実行できればtrue。 * @throws IOException 実行できなかった場合 */ public static boolean browse(URI uri) throws IOException { return callMethod(methodBrowse, uri); } /** * 指定したdocBaseの親ディレクトリを開く. * @param docBase * @return サポートしていない場合はfalse、実行できればtrue。 * @throws IOException 実行できなかった場合 */ public static boolean browseBaseDir(URI docBase) throws IOException { File baseDir = null; try { if (docBase != null) { baseDir = new File(docBase).getParentFile(); } } catch (Exception ex) { baseDir = null; } if (baseDir == null) { Toolkit tk = Toolkit.getDefaultToolkit(); tk.beep(); return false; } return DesktopUtilities.open(baseDir); } /** * ファイルを編集する. * @param file ファイル * @return サポートしていない場合はfalse、実行できればtrue。 * @throws IOException 実行できなかった場合 */ public static boolean edit(File file) throws IOException { return callMethod(methodEdit, file); } /** * ファイルを編集する. * @param file ファイル * @return サポートしていない場合はfalse、実行できればtrue。 * @throws IOException 実行できなかった場合 */ public static boolean open(File file) throws IOException { return callMethod(methodOpen, file); } /** * ブラウザでURLを開きます.
* JDK1.6未満の場合はブラウザを開く代わりにURLと、それを説明するメッセージボックスが表示されます.
* @param parent 親フレーム、またはダイアログ * @param url URL * @param description ブラウズがサポートされていない場合に表示するダイアログでのURLの説明文 */ public static void browse(final Component parent, final String url, final String description) { try { URI helpURI = new URI(url); if (!DesktopUtilities.browse(helpURI) ){ // JDK5で実行中の場合 JPanel panel = new JPanel(); BoxLayout layout = new BoxLayout(panel, BoxLayout.Y_AXIS); panel.setLayout(layout); panel.add(new JLabel(description)); JTextField txtURL = new JTextField(url); panel.add(txtURL); JOptionPane.showMessageDialog(parent, panel); } } catch (Exception ex) { ErrorMessageHelper.showErrorDialog(parent, ex); } } /** * ブラウザでURLを開くアクションの生成.
* エラー時はエラーダイアログが開かれる.
* 返されるアクションがとるアクションイベントは無視されるため、nullを渡しても問題ありません.
* @param parent 親フレーム、またはダイアログ * @param url URLの文字列 * @param description ブラウズがサポートされていない場合に表示するダイアログでのURLの説明文 * @return アクション */ public static ActionListener createBrowseAction(final Component parent, final String url, final String description) { return new ActionListener() { public void actionPerformed(ActionEvent e) { browse(parent, url, description); } }; } } CharacterManaJ/src/charactermanaj/util/LocalizedMessageComboBoxRender.java0000644000175000017500000000361012560206305027146 0ustar paulliupaulliupackage charactermanaj.util; import java.awt.Component; import java.util.Properties; import javax.swing.JList; import javax.swing.plaf.basic.BasicComboBoxRenderer; /** * ローカライズリソースをサポートするオブジェクトをコンボボックスに表示するコンボボックスレンダー */ public class LocalizedMessageComboBoxRender extends BasicComboBoxRenderer { /** * シリアライズバージョンID */ private static final long serialVersionUID = 2148264299941543651L; /** * リソースホルダ */ private Properties strings; public LocalizedMessageComboBoxRender(Properties strings) { if (strings == null) { throw new IllegalArgumentException(); } this.strings = strings; } @Override public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) { Object localizedString = getLocalizedString(value); return super.getListCellRendererComponent(list, localizedString, index, isSelected, cellHasFocus); } /** * ローカライズリソースIDを取得する.
* サポートしていないか、該当がなければ、toString()の結果を返す.
* @param value オブジェクト * @return ローカライズされた文字列、もしくは通常の文字列 */ protected String getLocalizedString(Object value) { if (value == null) { return "(null)"; } if (value instanceof LocalizedMessageAware) { LocalizedMessageAware o = (LocalizedMessageAware) value; String id = o.getLocalizedResourceId(); String localizedString = null; if (id != null) { localizedString = strings.getProperty(id); if (localizedString == null) { return id; } } if (localizedString != null) { return localizedString; } } return value.toString(); } } CharacterManaJ/src/charactermanaj/util/UserDataFactory.java0000644000175000017500000000757712560206305024222 0ustar paulliupaulliupackage charactermanaj.util; import java.io.File; import java.net.URI; import java.util.UUID; import java.util.logging.Level; import java.util.logging.Logger; /** * ユーザーデータの保存先を生成するファクトリ * * @author seraphy */ public class UserDataFactory { /** * ロガー */ private static final Logger logger = Logger.getLogger(UserDataFactory.class.getName()); /** * シングルトン */ private static UserDataFactory inst = new UserDataFactory(); /** * インスタンスを取得する. * * @return インスタンス */ public static UserDataFactory getInstance() { return inst; } /** * プライベートコンストラクタ */ private UserDataFactory() { super(); } /** * 拡張子を含むファイル名を指定し、そのファイルが保存されるべきユーザディレクトリを判定して返す.
* nullまたは空の場合、もしくは拡張子がない場合はユーザディレクトリのルートを返します.
* フォルダがなければ作成されます.
* * @param name * ファイル名、もしくはnull * @return ファィルの拡張子に対応したデータ保存先フォルダ */ public File getSpecialDataDir(String name) { File userDataDir = ConfigurationDirUtilities.getUserDataDir(); if (name != null && name.length() > 0) { int seppos = name.lastIndexOf('-'); if (name.endsWith(".xml") && seppos >= 0) { // 「foo-????.xml」形式の場合は「????」でグループ化する String groupName = name.substring(seppos + 1, name.length() - 4); if (groupName.length() > 0) { userDataDir = new File(userDataDir, groupName); } } else { // 拡張子によるグループ化 int pos = name.lastIndexOf('.'); if (pos >= 0) { String ext = name.substring(pos + 1); if (ext.length() > 0) { if ("ser".equals(ext)) { userDataDir = new File(userDataDir, "caches"); } else { userDataDir = new File(userDataDir, ext + "s"); } } } } } // フォルダがなければ作成する. if (!userDataDir.exists()) { boolean result = userDataDir.mkdirs(); logger.log(Level.INFO, "makeDir: " + userDataDir + " /succeeded=" + result); } return userDataDir; } /** * 指定した名前のユーザーデータ保存先を作成する. * * @param name * ファイル名 * @return 保存先 */ public UserData getUserData(String name) { if (name == null || name.trim().length() == 0) { throw new IllegalArgumentException(); } return new FileUserData(new File(getSpecialDataDir(name), name)); } /** * docBaseの名前ベースのUUIDをプレフィックスをもつユーザーデータ保存先を作成する.
* * @param docBase * URI、null可 * @param name * ファイル名 * @return 保存先 */ public UserData getMangledNamedUserData(URI docBase, String name) { String mangledName = getMangledName(docBase); return getUserData(mangledName + "-" + name); } /** * docBaseをハッシュ値化文字列にした、名前ベースのUUIDを返す.
* docBaseがnullの場合は空文字とみなして変換する.
* (衝突の可能性は無視する。)
* * @param docBase * URI、null可 * @return 名前ベースのUUID */ private String getMangledName(URI docBase) { String docBaseStr; if (docBase == null) { docBaseStr = ""; } else { docBaseStr = docBase.toString(); } String mangledName = UUID.nameUUIDFromBytes(docBaseStr.getBytes()).toString(); if (logger.isLoggable(Level.FINEST)) { logger.log(Level.FINEST, "mangledName " + docBase + "=" + mangledName); } return mangledName; } } CharacterManaJ/src/charactermanaj/util/ApplicationLogHandler.java0000644000175000017500000001042212560206305025344 0ustar paulliupaulliupackage charactermanaj.util; import java.io.File; import java.io.FileOutputStream; import java.io.OutputStreamWriter; import java.io.PrintWriter; import java.sql.Timestamp; import java.text.SimpleDateFormat; import java.util.logging.Handler; import java.util.logging.Level; import java.util.logging.LogRecord; import charactermanaj.model.AppConfig; /** * このアプリケーションの活動を記録するログハンドラ.
* アプリケーション用のディレクトリのlogsフォルダ下に開始日時のファイル名をもつログファイルを作成し、ログを記録する.
* ただし、終了時、警告以上のログが一度も書き込まれなかった場合はログファィルは自動的に削除される.
* * @author seraphy */ public class ApplicationLogHandler extends Handler { private static final String LOGS_DIR = "logs"; private final Object lock = new Object(); private final File logFile; private PrintWriter pw; private boolean notRemove; public ApplicationLogHandler() { File appDir = ConfigurationDirUtilities.getUserDataDir(); File logsDir = new File(appDir, LOGS_DIR); if (!logsDir.exists()) { if (!logsDir.mkdirs()) { // ログ記録場所が作成できていないのでコンソールに出すしかない. System.err.println("can't create the log directory. " + logsDir); } } String fname = getCurrentTimeForFileName() + ".log"; logFile = new File(logsDir, fname); PrintWriter tmp; try { tmp = new PrintWriter(new OutputStreamWriter(new FileOutputStream(logFile))); } catch (Exception ex) { ex.printStackTrace(); // ロガーが失敗しているので、この失敗はコンソールに出すしかない。 tmp = null; } this.pw = tmp; } @Override public void close() throws SecurityException { synchronized (lock) { // 終了時にAppConfigにアクセスする. // (アプリケーションの終了時にアクセスすることで初期化タイミングの問題を避ける.) try { AppConfig appConfig = AppConfig.getInstance(); if (appConfig.isNoRemoveLog()) { notRemove = true; } } catch (Exception ex) { // なんらかのアクセスに失敗した場合でも継続できるようにする. // ロガーが閉じられようとしているので、pwに直接出力する notRemove = true; try { if (pw != null) { ex.printStackTrace(pw); } } catch (Exception iex) { iex.printStackTrace(); // コンソールに出す他ない } } if (pw != null) { pw.close(); pw = null; } if (logFile != null && !notRemove) { // 警告未満のログしかない場合はログファイルは毎回削除する. if (!logFile.delete()) { System.err.println("can't delete file. " + logFile); } } } } @Override public void flush() { synchronized (lock) { if (pw != null) { pw.flush(); } } } @Override public void publish(LogRecord record) { if (record == null) { return; } // メッセージの記録 synchronized (lock) { if (pw == null) { return; } Level lv = record.getLevel(); String name = record.getLoggerName(); pw.println("#" + getCurrentTime() + " " + name + " " + lv.getLocalizedName() + " " + record.getMessage()); // 例外があれば、例外の記録 Throwable tw = record.getThrown(); if (tw != null) { tw.printStackTrace(pw); // 例外のコールスタックをロガーに出力 } // フラッシュする.(随時、ファイルの中身を見ることができるように.) pw.flush(); // 警告以上であれば終了時にファイルを消さない if (lv.intValue() >= Level.WARNING.intValue()) { notRemove = true; } } } public String getCurrentTime() { Timestamp tm = new Timestamp(System.currentTimeMillis()); SimpleDateFormat dt = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS"); return dt.format(tm); } public String getCurrentTimeForFileName() { Timestamp tm = new Timestamp(System.currentTimeMillis()); SimpleDateFormat dt = new SimpleDateFormat("yyyy-MM-dd_HHmmssSSS"); return dt.format(tm); } } CharacterManaJ/src/charactermanaj/util/XMLUtilities.java0000644000175000017500000001506012560206305023500 0ustar paulliupaulliupackage charactermanaj.util; import java.io.IOException; import java.io.InputStream; import java.util.ArrayList; import java.util.Iterator; import java.util.NoSuchElementException; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.ParserConfigurationException; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.Node; import org.w3c.dom.NodeList; import org.xml.sax.ErrorHandler; import org.xml.sax.SAXException; import org.xml.sax.SAXParseException; /** * XML用のユーテリティ.
* * @author seraphy */ public final class XMLUtilities { /** * プライベートコンストラクタ */ private XMLUtilities() { super(); } /** * XMLドキュメントをロードして返します. 名前空間を有効とします. * * @param is * ロードするXMLドキュメントの入力ストリーム * @return ドキュメント * @throws IOException * 読み込みに失敗した場合 */ public static Document loadDocument(InputStream is) throws IOException { Document doc; try { DocumentBuilderFactory factory = DocumentBuilderFactory .newInstance(); factory.setNamespaceAware(true); DocumentBuilder builder = factory.newDocumentBuilder(); final ArrayList errors = new ArrayList(); builder.setErrorHandler(new ErrorHandler() { public void error(SAXParseException exception) throws SAXException { errors.add(exception); } public void fatalError(SAXParseException exception) throws SAXException { errors.add(exception); } public void warning(SAXParseException exception) throws SAXException { errors.add(exception); } }); doc = builder.parse(is); if (errors.size() > 0) { throw errors.get(0); } return doc; } catch (ParserConfigurationException ex) { throw new RuntimeException("JAXP Configuration Exception.", ex); } catch (SAXException ex) { IOException ex2 = new IOException("xml read failed."); ex2.initCause(ex); throw ex2; } } /** * 指定した名前の子要素で、lang属性が一致するものの値を返す.
* langが一致するものがない場合は最初の要素の値を返す.
* 一つも要素がない場合はnullを返す. * * @param parent * 親要素 * @param elementName * 子要素の名前 * @param lang * 言語属性 * @return 言語属性が一致する子要素の値、もしくは最初の子要素の値、もしくはnull */ public static String getLocalizedElementText(Element parent, String elementName, String lang) { String text = null; for (Element childelm : getChildElements(parent, elementName)) { String val = childelm.getTextContent(); // 最初の定義をデフォルト値として用いる. if (text == null) { text = val; } // lang指定が一致すれば、それを優先する. String langNm = childelm.getAttributeNS( "http://www.w3.org/XML/1998/namespace", "lang"); if (lang.equals(langNm) && val.length() > 0) { text = val; break; } } return text; } /** * 指定した名前の子要素の最初の値を返す. * * @param parent * 親要素 * @param elementName * 子要素の名前 * @return 値、要素がなければnull */ public static String getElementText(Element parent, String elementName) { for (Element childelm : getChildElements(parent, elementName)) { return childelm.getTextContent(); } return null; } /** * 指定した名前の最初の子要素を返す. なければnull. * * @param parent * 親要素 * @param name * 子要素の名前 * @return 最初の子要素、もしくはnull */ public static Element getFirstChildElement(Element parent, final String name) { for (Element elm : getChildElements(parent, name)) { return elm; } return null; } /** * 指定した名前の子要素の列挙子を返す. nullの場合は、すべての子要素を返す. * * @param elm * 親要素 * @param name * 子要素の名前、もしくはnull * @return 子要素の列挙子、該当がない場合は空の列挙子が返される */ public static Iterable getChildElements(Element elm, final String name) { return iterable(elm.getChildNodes(), new Filter() { public boolean isAccept(Node node) { if (node != null && node.getNodeType() == Node.ELEMENT_NODE) { if (name == null || name.equals(node.getNodeName())) { return true; } } return false; } }); } /** * すべての子ノードを列挙子として返す * * @param nodeList * @return */ public static Iterable iterable(final NodeList nodeList) { return iterable(nodeList, null); } /** * フィルタ */ public interface Filter { boolean isAccept(Node node); } /** * 指定したノードリストからフィルタ条件にマッチするものだけを列挙子として返す. * * @param nodeList * ノードリスト、nullの場合は空とみなす * @param filter * フィルタ条件、nullの場合はすべて合致とみなす * @return 合致するものだけを列挙する列挙子 */ public static Iterable iterable( final NodeList nodeList, final Filter filter) { final int mx; if (nodeList == null) { mx = 0; } else { mx = nodeList.getLength(); } return new Iterable() { public Iterator iterator() { return new Iterator() { private int idx = 0; private Node nextNode = getNextNode(); private Node getNextNode() { while (idx < mx) { Node node = nodeList.item(idx++); if (filter == null || filter.isAccept(node)) { return node; } } return null; } public boolean hasNext() { return nextNode != null; } @SuppressWarnings("unchecked") public T next() { Node cur = nextNode; if (cur == null) { throw new NoSuchElementException(); } nextNode = getNextNode(); return (T) cur; } public void remove() { throw new UnsupportedOperationException(); } }; } }; } } CharacterManaJ/src/charactermanaj/util/DirectoryConfig.java0000644000175000017500000000202712560206305024235 0ustar paulliupaulliupackage charactermanaj.util; import java.io.File; /** * 起動時に選択するキャラクターデータを格納する親ディレクトリ * * @author seraphy */ public class DirectoryConfig { /** * シングルトン */ private static final DirectoryConfig inst = new DirectoryConfig(); private File charactersDir; private DirectoryConfig() { super(); } /** * キャラクターデータを格納するディレクトリを取得する.
* まだ未設定であればIllegalStateException例外が発生する.
* * @return キャラクターデータを格納するディレクトリ */ public File getCharactersDir() { if (charactersDir == null) { throw new IllegalStateException("キャラクターディレクトリが設定されていません."); } return charactersDir; } public void setCharactersDir(File charactersDir) { this.charactersDir = charactersDir; } public static DirectoryConfig getInstance() { return inst; } } CharacterManaJ/src/charactermanaj/util/JavaVersionUtils.java0000644000175000017500000000311412560206305024411 0ustar paulliupaulliupackage charactermanaj.util; public final class JavaVersionUtils { private JavaVersionUtils() { super(); } /** * Javaの簡易なバージョンを取得する.
* 不明な場合は0を返す.
* * @return バージョン */ public static double getJavaVersion() { try { String version = System.getProperty("java.version"); String[] versions = version.split("\\."); if (versions.length > 2) { return Double.valueOf(versions[0] + "." + versions[1]); } } catch (RuntimeException ex) { ex.printStackTrace(); } return 0d; } /** * Javaの詳細なバージョンを取得する. メジャー・マイナー・メンテナンス・アップデートの4要素を返す.
* * @return */ public static int[] getJavaVersions() { return getJavaVersions(System.getProperty("java.version")); } private static int[] getJavaVersions(String version) { int[] ret = new int[4]; try { int posIdentifier = version.indexOf('-'); if (posIdentifier >= 0) { version = version.substring(0, posIdentifier); } int posUpdate = version.indexOf("_"); int update = 0; if (posUpdate >= 0) { update = Integer.parseInt(version.substring(posUpdate + 1)); version = version.substring(0, posUpdate); } String[] versions = version.split("\\."); for (int idx = 0; idx < 3 && idx < versions.length; idx++) { ret[idx] = Integer.parseInt(versions[idx]); } ret[3] = update; } catch (RuntimeException ex) { ex.printStackTrace(); } return ret; } } CharacterManaJ/src/charactermanaj/util/SystemUtil.java0000644000175000017500000000372512560206305023273 0ustar paulliupaulliupackage charactermanaj.util; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; /** * システムクラスのためのユーテリティ.
* Findbugsの警告がうっとおしいのでリフレクションで隠す.
* * @author seraphy */ public final class SystemUtil { private static final Class clsSystem; private static final Method garbageCollection; private static final Method exit; private static final int gcLoop = 3; private static final long sleepTime = 100; static { try { clsSystem = System.class; garbageCollection = clsSystem.getMethod("gc"); exit = clsSystem.getMethod("exit", int.class); } catch (NoSuchMethodException ex) { throw new RuntimeException(ex.getMessage(), ex); } catch (SecurityException ex) { throw new RuntimeException(ex.getMessage(), ex); } } private SystemUtil() { super(); } /** * 数回ガベージコレクションをかける.
*/ public static void gc() { try { for (int i = 0; i < gcLoop; i++) { if (i != 0) { Thread.sleep(sleepTime); } garbageCollection.invoke(null); } } catch (InterruptedException ex) { // 無視する. } catch (InvocationTargetException ex) { Throwable iex = ex.getCause(); if (iex == null) { iex = ex; } throw new RuntimeException(iex.getMessage(), iex); } catch (IllegalAccessException ex) { throw new RuntimeException(ex.getMessage(), ex); } } /** * JVMを終了する. * @param exitCode 終了コード */ public static void exit(int exitCode) { try { exit.invoke(null, exitCode); } catch (InvocationTargetException ex) { Throwable iex = ex.getCause(); if (iex == null) { iex = ex; } throw new RuntimeException(iex.getMessage(), iex); } catch (IllegalAccessException ex) { throw new RuntimeException(ex.getMessage(), ex); } } } CharacterManaJ/src/charactermanaj/util/ConfigurationDirUtilities.java0000644000175000017500000001351512560206305026311 0ustar paulliupaulliupackage charactermanaj.util; import java.io.File; import java.io.IOException; import java.net.URL; import java.security.CodeSource; import java.security.ProtectionDomain; import java.util.logging.Level; import java.util.logging.Logger; import charactermanaj.Main; /** * アプリケーションの設定ファイル等の位置を取得するユーテリティクラス.
* Mainクラスのロード時に最も早くロードされるであろうクラスの一つである.
* @author seraphy */ public final class ConfigurationDirUtilities { public static final String CONFIGURATION_DIR_NAME = "CharacterManaJ"; private static File userDataDir; private static File applicationBaseDir; private ConfigurationDirUtilities() { throw new RuntimeException("utilities class."); } /** * ユーザーごとのアプリケーションデータ保存先を取得する.
* 環境変数「APPDATA」もしくはシステムプロパティ「appdata.dir」からベース位置を取得する.
* いずれも設定されておらず、Mac OS Xであれば「~/Library」をベース位置とする。 * Mac OS Xでなければ「~/」をベース位置とする.
* これに対してシステムプロパティ「characterdata.dirname」(デフォルトは「CharacterManaJ」)という * フォルダをユーザー毎のアプリケーションデータの保存先ディレクトリとする.
*/ public synchronized static File getUserDataDir() { if (userDataDir == null) { String appData = null; // システムプロパティ「appdata.dir」を探す appData = System.getProperty("appdata.dir"); if (appData == null) { // なければ環境変数APPDATAを探す // Windows2000/XP/Vista/Windows7には存在する. appData = System.getenv("APPDATA"); } if (appData == null && Main.isMacOSX()) { // システムプロパティも環境変数にも設定がなく、実行環境がMac OS Xであれば // ~/Libraryをベースにする.(Mac OS Xならば必ずある。) appData = new File(System.getProperty("user.home"), "Library").getPath(); } if (appData == null) { // なければシステムプロパティ「user.home」を使う // このプロパティは必ず存在する. appData = System.getProperty("user.home"); } // システムプロパティ「characterdata.dirname」のディレクトリ名、なければ「CharacterManaJ」を設定する. String characterDirName = System.getProperty("characterdata.dirname", CONFIGURATION_DIR_NAME); userDataDir = new File(appData, characterDirName).getAbsoluteFile(); // ディレクトリを準備する. if (!userDataDir.exists()) { if (!userDataDir.mkdirs()) { // ログ保管場所も設定されていないのでコンソールに出すしかない. System.err.println("can't create the user data directory. " + userDataDir); } } } return userDataDir; } /** * アプリケーションディレクトリを取得する.
* このクラスをコードソースから、ベースとなる位置を割り出す.
* クラスが格納されているクラスパスのフォルダか、JARに固められている場合は、そのJARファイルの、その親ディレクトリを指し示す.
* このクラスのプロテクションドメインのコードソースがnullでコードの位置が取得できないか、 * コードの位置を示すURLがファイルプロトコルでない場合は実行時例外が返される.
* ただし、システムプロパティ「appbase.dir」が明示的に設定されていれば、それが優先される.
*/ public synchronized static File getApplicationBaseDir() { if (applicationBaseDir == null) { String appbaseDir = System.getProperty("appbase.dir"); if (appbaseDir != null && appbaseDir.length() > 0) { // 明示的にアプリケーションベースディレクトリが指定されている場合. try { applicationBaseDir = new File(appbaseDir).getCanonicalFile(); } catch (IOException ex) { ex.printStackTrace(); // 継続する.まだログの準備ができていない可能性が高いので標準エラー出力へ. } } if (applicationBaseDir == null) { // 明示的に指定されていない場合はコードの実行位置から割り出す. ProtectionDomain pdomain = ConfigurationDirUtilities.class.getProtectionDomain(); CodeSource codeSource = pdomain.getCodeSource(); if (codeSource == null) { throw new RuntimeException("codeSource is null: domain=" + pdomain); } URL codeBaseUrl = codeSource.getLocation(); if (!codeBaseUrl.getProtocol().equals("file")) { throw new RuntimeException("codeLocation is not file protocol.: " + codeBaseUrl); } // クラスパスフォルダ、またはJARファイルの、その親 applicationBaseDir = new File(codeBaseUrl.getPath()).getParentFile(); } } return applicationBaseDir; } /** * デフォルトのユーザー固有のキャラクターデータディレクトリを取得する.
* ユーザー固有のキャラクターディレクトリがまだ存在しない場合は作成される.
* @return ユーザー固有のキャラクターデータディレクトリ */ public static File getDefaultCharactersDir() { Logger logger = Logger.getLogger(ConfigurationDirUtilities.class.getName()); File characterBaseDir = new File(ConfigurationDirUtilities.getUserDataDir(), "characters"); if (!characterBaseDir.exists()) { if (!characterBaseDir.mkdirs()) { logger.log(Level.WARNING, "can't create the charatcer base directory. " + characterBaseDir); } } return characterBaseDir; } } CharacterManaJ/src/charactermanaj/util/UserData.java0000644000175000017500000000256612560206305022663 0ustar paulliupaulliupackage charactermanaj.util; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; /** * ユーザーデータの保存先 * @author seraphy */ public interface UserData { /** * データを開く * @return 入力ストリーム * @throws IOException 開けなかった場合 */ InputStream openStream() throws IOException; /** * データを書き込む * @return 出力ストリーム * @throws IOException 開けなかった場合 */ OutputStream getOutputStream() throws IOException; /** * 更新日時(エポックタイム)、まだ存在しない場合は0 * @return 更新日時 */ long lastModified(); /** * 存在するか? * @return 存在すればtrue */ boolean exists(); /** * 削除する.
* すでに存在しない場合は何もしない.
* @return 削除された場合はtrue */ boolean delete(); /** * 引数に指定したオブジェクトをシリアライズします. * @param obj オブジェクト * @throws IOException 保存できなかった場合 */ void save(Object obj) throws IOException; /** * オブジェクトをデシリアライズして返します. * @return オブジェクト * @throws IOException 取得できなかった場合 */ Object load() throws IOException; } CharacterManaJ/src/charactermanaj/util/LocalizedResourcePropertyLoader.java0000644000175000017500000001534612560206305027465 0ustar paulliupaulliupackage charactermanaj.util; import java.io.InputStream; import java.net.URL; import java.util.HashMap; import java.util.Locale; import java.util.Map; import java.util.Properties; /** * xml形式のリソース上のプロパティファイルのローカライズされた読み込みを行うためのクラス.
* リソースは、単純名、言語名を末尾に付与したもの、言語名と国を末尾に付与したもの、言語名と国とバリアントを末尾に付与したもの、の順で読み取られる.
* 順番に読み込んで重ね合わせる.
* 一度読み込んだものはキャッシュに保存され次回以降は、それが用いられる.
*/ public class LocalizedResourcePropertyLoader extends ResourceLoader { /** * プロパティファイル群と、それに対するキャッシュ */ private Map propCache; /** * キャッシュを共有するシングルトンインスタンス. */ private static final LocalizedResourcePropertyLoader inst = new LocalizedResourcePropertyLoader( new HashMap()); /** * 独立したキャッシュを指定することのできるコンストラクタ.
* * @param propCache * キャッシュ、不要であればnull可 */ public LocalizedResourcePropertyLoader( Map propCache) { this.propCache = propCache; } /** * インスタンスを取得する * * @return インスタンス */ public static LocalizedResourcePropertyLoader getCachedInstance() { return inst; } /** * リソース名を指定してデフォルトのロケールでローカライズされたリソースプロパティを読み込む.
* リソースはxml形式である。リソース名には.xmlを付与しない.(自動的に内部で付与される.) * * @param name * リソース名 * @return プロパティ */ public Properties getLocalizedProperties(String name) { return getLocalizedProperties(name, null); } /** * リソース名を指定して指定したロケールでローカライズされたリソースプロパティを読み込む.
* リソースはxml形式である。リソース名には.xmlを付与しない.(自動的に内部で付与される.) * * @param name * リソース名 * @param locale * ロケール、nullの場合はデフォルトのロケール * @return プロパティ */ public Properties getLocalizedProperties(String name, Locale locale) { return getProperties(getResourceNames(name, locale)); } /** * リソース名を指定して指定したロケールでローカライズされたリソースプロパティの一覧を取得する.
* リソースはxml形式である。リソース名には.xmlを付与しない.(自動的に内部で付与される.)
* 返される順序は、読み込み順となる。(順番に読み込んで上書きしてゆくことを想定する).
* ロケール中立のものが先頭となり、指定したロケールにもっとも一致するものが最後となる.
* * @param name * リソース名 * @param locale * ロケール、nullの場合はデフォルトのロケール * @return プロパティリソースの一覧(読み込み順) */ public static ResourceNames getResourceNames(String name, Locale locale) { if (name == null || name.length() == 0) { throw new IllegalArgumentException(); } if (locale == null) { locale = Locale.getDefault(); } String language = locale.getLanguage(); String country = locale.getCountry(); String variant = locale.getVariant(); String[] resourceNames = { name + ".xml", name + "_" + language + ".xml", name + "_" + language + "_" + country + ".xml", name + "_" + language + "_" + country + "_" + variant + ".xml", }; return new ResourceNames(resourceNames); } /** * リソース名群をもとにキャッシュもしくはプロパティをロードして返す.
* キャッシュされていない場合はプロパティをロードして、それをキャッシュに格納する.
* (共有キャッシュ時、もしくは独自のキャッシュが指定されている場合).
* リソースが一つも存在しない場合は実行時例外を発生させる.
* * @param resourceNames * リソース名群 * @return プロパティ */ public Properties getProperties(ResourceNames resourceNames) { if (resourceNames == null) { throw new IllegalArgumentException(); } Properties prop; if (propCache != null) { synchronized (propCache) { prop = propCache.get(resourceNames); if (prop == null) { prop = loadProperties(resourceNames); propCache.put(resourceNames, prop); } } } else { prop = loadProperties(resourceNames); } if (prop == null) { throw new RuntimeException("missing resource: " + resourceNames); } return prop; } /** * リソース名群からリソースプロパティをロードして返す.
* 一つも存在しない場合はnullを返す.
* * @param resourceNames * リソース群名 * @return プロパティ */ protected Properties loadProperties(ResourceNames resourceNames) { if (resourceNames == null) { throw new IllegalArgumentException(); } // システム埋め込みリソースでプロパティを取得したのちに、ユーザ指定のプロパティの内容で上書きする. // バージョンアップによりキーが増えて、既存のローカルファイル上のプロパティファイルにキーが存在しない場合でも // 安全なようにするためのもの。 ClassLoader[] loaders = new ClassLoader[] { getDefaultClassLoader(), getLocalizedClassLoader(null), }; boolean foundResource = false; Properties props = new Properties(); for (ClassLoader loader : loaders) { if (loader == null) { continue; } for (String resourceName : resourceNames) { URL resource = loader.getResource(resourceName); if (resource != null) { Properties org = new Properties(); try { InputStream is = resource.openStream(); try { org.loadFromXML(is); } finally { is.close(); } } catch (Exception ex) { throw new RuntimeException("resource loading error." + resource, ex); } foundResource = true; props.putAll(org); } } } if (foundResource) { return props; } return null; } } CharacterManaJ/src/charactermanaj/util/FileUserData.java0000644000175000017500000000443512560206305023460 0ustar paulliupaulliupackage charactermanaj.util; import java.io.BufferedInputStream; import java.io.BufferedOutputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.io.OutputStream; import java.util.logging.Level; import java.util.logging.Logger; /** * ファイルベースのユーザーデータの保存先の実装 * @author seraphy */ public class FileUserData implements UserData { /** * ロガー */ private static final Logger logger = Logger.getLogger(FileUserData.class.getName()); /** * 保存先ファイル */ private File file; public FileUserData(File file) { if (file == null) { throw new IllegalArgumentException(); } this.file = file; } public boolean exists() { return file.exists() && file.isFile(); } public long lastModified() { return file.lastModified(); } public InputStream openStream() throws IOException { return new BufferedInputStream(new FileInputStream(file)); } public OutputStream getOutputStream() throws IOException { return new BufferedOutputStream(new FileOutputStream(file)); } public boolean delete() { try { return file.delete(); } catch (Exception ex) { // セキュリティ例外ぐらい. logger.log(Level.WARNING, "file removing failed." + file, ex); return false; } } public void save(Object userData) throws IOException { ObjectOutputStream oos = new ObjectOutputStream(getOutputStream()); try { oos.writeObject(userData); oos.close(); } finally { oos.close(); } } public Object load() throws IOException { ObjectInputStream ois = new ObjectInputStream(openStream()); try { try { return ois.readObject(); } catch (ClassNotFoundException ex) { // 復元先クラスがみつからないということは、このアプリケーションの保存した形式としておかしい IOException ex2 = new IOException("invalid format."); ex2.initCause(ex2); throw ex2; } } finally { ois.close(); } } @Override public String toString() { return "FileUserData{file:" + file + "}"; } }CharacterManaJ/src/charactermanaj/util/FileNameNormalizer.java0000644000175000017500000000266412560206305024675 0ustar paulliupaulliupackage charactermanaj.util; import java.lang.reflect.Method; /** * ファイル名をノーマライズする.
* ただし、サポートされていない場合は何もしない.
* @author seraphy */ public class FileNameNormalizer { private static FileNameNormalizer DEFAULT = new FileNameNormalizer(); public static void setDefault(FileNameNormalizer def) { if (def == null) { throw new IllegalArgumentException(); } DEFAULT = def; } public static FileNameNormalizer getDefault() { return DEFAULT; } public String normalize(String name) { return name; } public static boolean setupNFCNormalizer() { final Method method; final Object nfd; try { Class normalizerCls = Class.forName("java.text.Normalizer"); Class formCls = Class.forName("java.text.Normalizer$Form"); method = normalizerCls.getMethod("normalize", CharSequence.class, formCls); nfd = formCls.getField("NFC").get(null); } catch (Exception ex) { ex.printStackTrace(System.err); return false; } FileNameNormalizer normalizer = new FileNameNormalizer() { @Override public String normalize(String name) { if (name != null) { try { return (String) method.invoke(null, name, nfd); } catch (Exception ex) { ex.printStackTrace(System.err); } } return name; } }; setDefault(normalizer); return true; } } CharacterManaJ/src/charactermanaj/util/ResourceLoader.java0000644000175000017500000000722112560206305024062 0ustar paulliupaulliupackage charactermanaj.util; import java.io.File; import java.net.MalformedURLException; import java.net.URL; import java.net.URLClassLoader; import java.security.AccessController; import java.security.PrivilegedAction; import java.security.PrivilegedExceptionAction; /** * リソースをロードするための抽象基底クラス. * * @author seraphy */ public class ResourceLoader { /** * クラスローダを取得する.
* まずローカルファイル上のリソースディレクトリがあれば、それを検索する.
* つぎにスレッドに関連づけられているコンテキストクラスローダか、もしなければ、このクラスをロードしたクラスローダを用いて検索する.
* * @return クラスローダ */ public ClassLoader getClassLoader() { return getLocalizedClassLoader(getDefaultClassLoader()); } /** * クラスローダを取得する.
* スレッドに関連づけられているコンテキストクラスローダか、もしなければ、このクラスをロードしたクラスローダを返す.
* * @return クラスローダ */ public ClassLoader getDefaultClassLoader() { return AccessController.doPrivileged(new PrivilegedAction() { public ClassLoader run() { ClassLoader cl = Thread.currentThread().getContextClassLoader(); if (cl == null) { cl = ResourceLoader.class.getClassLoader(); } return cl; } }); } /** * ローカルファイル上のリソースディレクトリにアクセスするクラスローダ取得する.
* 作成されていなければparentをそのまま返す.
* リソースはローカルファイル上のパスで検索されたのちにparentで検索されます.(標準のURLClassLoaderとは違う探索方法)
* * @param parent * 親クラスローダ、nullの場合は親の探索をしない. * @return ローカルシステム上のリソースディレクトリにアクセスするクラスローダ、なければparentのまま */ public ClassLoader getLocalizedClassLoader(final ClassLoader parent) { try { File baseDir = ConfigurationDirUtilities.getUserDataDir(); SetupLocalization localize = new SetupLocalization(baseDir); final File resourceDir = localize.getResourceDir(); if (!resourceDir.exists() || !resourceDir.isDirectory()) { return parent; } URLClassLoader cl = AccessController.doPrivileged(new PrivilegedExceptionAction() { public URLClassLoader run() throws MalformedURLException { URL[] urls = new URL[] { resourceDir.toURI().toURL() }; return new URLClassLoader(urls, parent) { @Override public URL getResource(String name) { URL url = findResource(name); // 子が優先 (標準と逆) if (url == null) { ClassLoader parent = getParent(); if (parent != null) { url = parent.getResource(name); } } return url; } }; } }); return cl; } catch (Exception ex) { ex.printStackTrace(); return null; } } /** * クラスローダによりリソースをロードする.
* 該当するリソースが存在しない場合はnullを返す.
* リソース名がnullの場合もnullを返す.
* * @param name * リソース名またはnull * @return リソースがあれば、そのURL。なければnull */ public URL getResource(String name) { if (name == null) { return null; } return getClassLoader().getResource(name); } } CharacterManaJ/src/charactermanaj/util/ResourceNames.java0000644000175000017500000000262712560206305023724 0ustar paulliupaulliupackage charactermanaj.util; import java.util.AbstractList; import java.util.Arrays; /** * 関連もしくは類似するリソースをまとめて取り扱うためにグループ化するためのクラス.
* * @author seraphy */ public class ResourceNames extends AbstractList { private final String[] resourceNames; public ResourceNames(String[] resourceNames) { if (resourceNames == null) { throw new IllegalArgumentException(); } this.resourceNames = resourceNames; } /** * 順次を逆転させた新しいインスタンスを返す * * @return 順序を逆転させたインスタンス */ public ResourceNames reverse() { int len = resourceNames.length; String[] tmp = new String[len]; for (int idx = 0; idx < len; idx++) { tmp[len - idx - 1] = resourceNames[idx]; } return new ResourceNames(tmp); } @Override public int hashCode() { return Arrays.hashCode(resourceNames); } @Override public boolean equals(Object obj) { if (obj == this) { return true; } if (obj != null && obj instanceof ResourceNames) { ResourceNames o = (ResourceNames) obj; return Arrays.equals(resourceNames, o.resourceNames); } return false; } @Override public int size() { return resourceNames.length; } @Override public String get(int index) { return resourceNames[index]; } } CharacterManaJ/src/charactermanaj/util/LocalizedTextResource.java0000644000175000017500000001111512560206305025424 0ustar paulliupaulliupackage charactermanaj.util; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.net.URL; import java.nio.charset.Charset; import java.util.Locale; /** * リソース名を指定してローカライズされたテキストを取得するための抽象実装.
* リソースの取得部は派生クラスにて実装する必要がある.
* * @author seraphy */ public abstract class LocalizedTextResource { /** * リソース名を指定して、テキストファイルを読み込んで、その文字列を返す.
* リソースは現在のデフォルトロケールを優先で検索されます.
* ファイルエンコーディングを引数csで指定する.
* * @param name * リソース名 * @param cs * ファイルのエンコーディング * @return ファイルの内容(テキスト) */ public String getText(String name, Charset cs) { return getText(name, cs, Locale.getDefault()); } /** * リソース名と文字コードを指定して、ロケールに対応する文字列を取得する.
* リソースがなければ実行時例外が発生する. * * @param name * リソース名 * @param cs * 文字コード * @param locale * 取得するロケール * @return テキスト */ public String getText(String name, Charset cs, Locale locale) { ResourceNames resourceNames = createResourceNames(name, locale); String text = loadText(resourceNames, cs); if (text == null) { throw new RuntimeException("resource not found: " + resourceNames); } return text; } /** * リソース名とロケールを指定して読み込む実リソース名のグループを作成して返す. * * @param name * リソース名 * @param locale * ロケール * @return リソース名グループ(優先順) */ protected ResourceNames createResourceNames(String name, Locale locale) { if (name == null || name.length() == 0 || locale == null) { throw new IllegalArgumentException(); } String language = locale.getLanguage(); String country = locale.getCountry(); String variant = locale.getVariant(); int extpos = name.lastIndexOf("."); int folderpos = name.lastIndexOf("/"); String basename; String ext; if (folderpos > extpos) { basename = name; ext = ""; } else { basename = name.substring(0, extpos); ext = name.substring(extpos); } String[] resourceNamesStr = { basename + "_" + language + "_" + country + "_" + variant + ext, basename + "_" + language + "_" + country + ext, basename + "_" + language + ext, basename + ext,}; return new ResourceNames(resourceNamesStr); } /** * リソース名グループを指定して、リソースをテキストとして取得する.
* リソース名グループの優先順にリソースの取得を試みて最初に成功したものを返す.
* ひとつも成功しなければnullが返される.
* * @param resourceNames * リソース名グループ * @param cs * 文字コード * @return リソースのテキスト */ protected String loadText(ResourceNames resourceNames, Charset cs) { if (resourceNames == null || cs == null) { throw new IllegalArgumentException(); } for (String resourceName : resourceNames) { URL url = getResource(resourceName); if (url == null) { // リソースがなければ次の候補へスキップする. continue; } StringBuilder buf = new StringBuilder(); try { InputStream is = url.openStream(); try { BufferedReader rd = new BufferedReader( new InputStreamReader(is, cs)); try { int ch; while ((ch = rd.read()) != -1) { buf.append((char) ch); } } finally { rd.close(); } } finally { is.close(); } } catch (IOException ex) { throw new RuntimeException("resource loading error: " + ex, ex); } // 1つでも成功すれば、それで終了する. return buf.toString(); } // 一つも成功しなかった場合 return null; } /** * リソース名からリソースを取得する.
* 存在しなければnullを返す.
* * @param resourceName * リソース名 * @return リソース、またはnull */ protected abstract URL getResource(String resourceName); } CharacterManaJ/src/charactermanaj/util/BeanPropertiesUtilities.java0000644000175000017500000001510412560206305025761 0ustar paulliupaulliupackage charactermanaj.util; import java.awt.Color; import java.beans.BeanInfo; import java.beans.IntrospectionException; import java.beans.Introspector; import java.beans.PropertyDescriptor; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.math.BigDecimal; import java.math.BigInteger; import java.util.HashSet; import java.util.Properties; import java.util.Set; import java.util.logging.Level; import java.util.logging.Logger; /** * Setter/Getterのペアをもつビーンのプロパティを文字列化してプロパティに設定するか、 * プロパティからビーンに値を設定するためのユーテリティクラス.
* @author seraphy */ public final class BeanPropertiesUtilities { private static final Logger logger = Logger.getLogger(BeanPropertiesUtilities.class.getName()); private BeanPropertiesUtilities() { throw new RuntimeException("utilities class."); } /** * ビーンのSetter/Getterのペアをもつプロパティに対して、Propertiesより該当するプロパティの値を * 読み取り、プロパティに設定します.
* Propertiesに該当するプロパティ名が設定されていなければスキップされます.
* Propertiesにビーンにないプロパティ名があった場合、それは単に無視されます.
* Propertyの値が空文字の場合、Beanのプロパティの型が文字列以外であればnullが設定されます.
* (文字列の場合、空文字のまま設定されます.書き込み時、nullは空文字にされるため、文字列についてはnullを表現することはできません。)
* @param bean 設定されるビーン * @param props プロパティソース * @return 値の設定を拒否されたプロパティの名前、エラーがなければ空 */ public static Set loadFromProperties(Object bean, Properties props) { if (bean == null || props == null) { throw new IllegalArgumentException(); } HashSet rejectNames = new HashSet(); try { BeanInfo beanInfo = Introspector.getBeanInfo(bean.getClass()); for (PropertyDescriptor propDesc : beanInfo .getPropertyDescriptors()) { Class typ = propDesc.getPropertyType(); Method mtdReader = propDesc.getReadMethod(); Method mtdWriter = propDesc.getWriteMethod(); if (mtdReader != null && mtdWriter != null) { // 読み書き双方が可能なもののみ対象とする. String name = propDesc.getName(); String strVal = props.getProperty(name); if (strVal == null) { // 設定がないのでスキップ continue; } Object val; Throwable reject = null; try { if (String.class.equals(typ)) { val = strVal; } else if (strVal.length() == 0) { val = null; } else { if (Boolean.class.equals(typ) || boolean.class.equals(typ)) { val = Boolean.valueOf(strVal); } else if (Integer.class.equals(typ) || int.class.equals(typ)) { val = Integer.valueOf(strVal); } else if (Long.class.equals(typ) || long.class.equals(typ)) { val = Long.valueOf(strVal); } else if (Float.class.equals(typ) || float.class.equals(typ)) { val = Float.valueOf(strVal); } else if (Double.class.equals(typ) || double.class.equals(typ)) { val = Double.valueOf(strVal); } else if (BigInteger.class.equals(typ)) { val = new BigInteger(strVal); } else if (BigDecimal.class.equals(typ)) { val = new BigDecimal(strVal); } else if (Color.class.equals(typ)) { val = Color.decode(strVal); } else { rejectNames.add(name); logger.log(Level.WARNING, "unsupported propery type: " + typ + "/beanClass=" + bean.getClass() + " #" + name); continue; } } mtdWriter.invoke(bean, val); reject = null; } catch (InvocationTargetException ex) { reject = ex; } catch (IllegalAccessException ex) { reject = ex; } catch (RuntimeException ex) { reject = ex; } if (reject != null) { rejectNames.add(name); logger.log(Level.WARNING, "invalid propery: " + typ + "/beanClass=" + bean.getClass() + " #" + name + " /val=" + strVal , reject); } } } } catch (IntrospectionException ex) { throw new RuntimeException("bean intorospector failed. :" + bean.getClass(), ex); } return rejectNames; } /** * ビーンのSetter/Getterのペアをもつプロパティの各値をPropertiesに文字列として登録します.
* nullの場合は空文字が設定されます.
* @param bean プロパティに転送する元情報となるビーン * @param props 設定されるプロパティ */ public static void saveToProperties(Object bean, Properties props) { if (bean == null || props == null) { throw new IllegalArgumentException(); } try { BeanInfo beanInfo = Introspector.getBeanInfo(bean.getClass()); for (PropertyDescriptor propDesc : beanInfo.getPropertyDescriptors()) { Method mtdReader = propDesc.getReadMethod(); Method mtdWriter = propDesc.getWriteMethod(); if (mtdReader != null && mtdWriter != null) { // 読み書き双方が可能なもののみ対象とする. String name = propDesc.getName(); Object val = mtdReader.invoke(bean); String strVal; if (val == null) { strVal = ""; } else if (val instanceof String) { strVal = (String) val; } else if (val instanceof Number) { strVal = ((Number) val).toString(); } else if (val instanceof Boolean) { strVal = ((Boolean) val).booleanValue() ? "true" : "false"; } else if (val instanceof Color) { strVal = "#" + Integer.toHexString(((Color) val).getRGB() & 0xffffff); } else { logger.log(Level.WARNING, "unsupported propery type: " + val.getClass() + "/beanClass=" + bean.getClass() + " #" + name); continue; } props.setProperty(name, strVal); } } } catch (IllegalAccessException ex) { throw new RuntimeException("bean property read failed. :" + bean.getClass(), ex); } catch (InvocationTargetException ex) { throw new RuntimeException("bean property read failed. :" + bean.getClass(), ex); } catch (IntrospectionException ex) { throw new RuntimeException("bean intorospector failed. :" + bean.getClass(), ex); } } } CharacterManaJ/src/charactermanaj/util/ApplicationLoggerConfigurator.java0000644000175000017500000000704312560206305027134 0ustar paulliupaulliupackage charactermanaj.util; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.InputStream; import java.util.logging.ConsoleHandler; import java.util.logging.Level; import java.util.logging.LogManager; import java.util.logging.Logger; public final class ApplicationLoggerConfigurator { private static final String LOGGING_PROPERTIES = "logging.properties"; private ApplicationLoggerConfigurator() { super(); } public static void configure() { // ログマネージャ. // 初期時にJRE等にある初期設定がなされている. LogManager logManager = LogManager.getLogManager(); // 設定を一旦クリアする. // ルートロガーがInfoになり、ハンドラはすべてリセット。ルートロガー以外の設定は空にされる. logManager.reset(); Exception configurationError = null; try { // ユーザーごとのアプリケーション設定ディレクトリ上の設定ファイルを取得する. File appDataDir = ConfigurationDirUtilities.getUserDataDir(); File logConfig = new File(appDataDir, LOGGING_PROPERTIES); if ( !logConfig.exists()) { // ユーザ指定のロギングプロパティがない場合、リソースからコピーする copyDefaultLogProperty(logConfig); } InputStream is = null; if (logConfig.exists()) { // ユーザー指定のロギングプロパティがある場合 is = new FileInputStream(logConfig); } else { // リソース上のロギングプロパティ is = ApplicationLoggerConfigurator.class.getResourceAsStream("/" + LOGGING_PROPERTIES); } if (is != null) { try { // ログを再設定する. logManager.readConfiguration(is); } finally { is.close(); } } } catch (Exception ex) { // 初期化に失敗した場合はログに記録するために例外を保存するが、 // 処理は継続する. configurationError = ex; } // ロガーを取得 Logger logger = Logger.getLogger(ApplicationLoggerConfigurator.class.getName()); // 初期化時に失敗した場合、デフォルトのコンソールハンドラを設定し、ログに出力する. if (configurationError != null) { logger.addHandler(new ConsoleHandler()); logger.addHandler(new ApplicationLogHandler()); logger.log(Level.WARNING, "LogConfigurationFailed", configurationError); } // 初期化時のログ logger.info("open logger."); logger.info("application configuration: baseDir=" + ConfigurationDirUtilities.getApplicationBaseDir() + " appData=" + ConfigurationDirUtilities.getUserDataDir()); } /** * デフォルトのログプロパティをユーザディレクトリにコピーする. * @param logConfig ユーザディレクトリ上のログプロパティファイル位置 */ private static void copyDefaultLogProperty(File logConfig) { try { InputStream is = ApplicationLoggerConfigurator.class .getResourceAsStream("/" + LOGGING_PROPERTIES); if (is != null) { try { FileOutputStream fos = new FileOutputStream(logConfig); try { byte buf[] = new byte[4096]; for (;;) { int rd = is.read(buf); if (rd <= 0) { break; } fos.write(buf, 0, rd); } } finally { fos.close(); } } finally { is.close(); } } } catch (Exception ex) { ex.printStackTrace(); // 失敗しても継続する } } } CharacterManaJ/src/charactermanaj/graphics/0000755000175000017500000000000012560206305021122 5ustar paulliupaulliuCharacterManaJ/src/charactermanaj/graphics/ImageBuildJobAbstractAdaptor.java0000644000175000017500000000477712560206305027440 0ustar paulliupaulliupackage charactermanaj.graphics; import java.io.IOException; import charactermanaj.graphics.ImageBuilder.ImageOutput; import charactermanaj.graphics.ImageBuilder.ImageSourceCollector; import charactermanaj.graphics.filters.ColorConvertParameter; import charactermanaj.graphics.io.ImageResource; import charactermanaj.model.Layer; import charactermanaj.model.PartsIdentifier; import charactermanaj.model.PartsSet; import charactermanaj.model.PartsSpecResolver; import charactermanaj.model.io.PartsImageCollectionParser; /** * 非同期に複合画像を生成するイメージビルダに引き渡すジョブを生成するためのアダプタクラス.
* パーツセットとパーツ設定リゾルバからイメージビルダに引き渡す情報を解決するジョブを生成する.
* @author seraphy * */ public abstract class ImageBuildJobAbstractAdaptor implements AsyncImageBuilder.AsyncImageBuildJob { protected PartsImageCollectionParser partsImageCollectorParser; public ImageBuildJobAbstractAdaptor(PartsSpecResolver partsSpecResolver) { if (partsSpecResolver == null) { throw new IllegalArgumentException(); } this.partsImageCollectorParser = new PartsImageCollectionParser(partsSpecResolver); } public void loadParts(final ImageSourceCollector collector) throws IOException { if (collector == null) { throw new IllegalArgumentException("collector is null"); } PartsSet partsSet = getPartsSet(); if (partsSet == null) { throw new RuntimeException("PartsSet is null"); } collector.setSize(partsImageCollectorParser.getPartsSpecResolver().getImageSize()); collector.setImageBgColor(partsSet.getBgColor()); collector.setAffineTramsform(partsSet.getAffineTransformParameter()); partsImageCollectorParser.parse(partsSet, new PartsImageCollectionParser.PartsImageCollectionHandler() { public void detectImageSource(PartsIdentifier partsIdentifier, Layer layer, ImageResource imageResource, ColorConvertParameter param) { if (param == null) { param = new ColorConvertParameter(); } collector.setImageSource(layer, imageResource, param); } }); collector.setComplite(); } protected abstract PartsSet getPartsSet() throws IOException; public abstract void buildImage(ImageOutput output); public abstract void handleException(Throwable ex); public void onAbandoned() { // なにもしない } public void onQueueing(long ticket) { // なにもしない } } CharacterManaJ/src/charactermanaj/graphics/ImageBuilder.java0000644000175000017500000004521012560206305024320 0ustar paulliupaulliupackage charactermanaj.graphics; import java.awt.Color; import java.awt.Dimension; import java.awt.Graphics2D; import java.awt.Rectangle; import java.awt.RenderingHints; import java.awt.geom.AffineTransform; import java.awt.image.AffineTransformOp; import java.awt.image.BufferedImage; import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.concurrent.Semaphore; import java.util.concurrent.TimeUnit; import charactermanaj.graphics.colormodel.ColorModel; import charactermanaj.graphics.colormodel.ColorModels; import charactermanaj.graphics.filters.ColorConvertParameter; import charactermanaj.graphics.io.ImageResource; import charactermanaj.graphics.io.LoadedImage; import charactermanaj.model.AppConfig; import charactermanaj.model.Layer; /** * 各パーツの各レイヤーごとの画像を色変換したのちレイヤーの順序に従い重ね合わせ合成する。 * * @author seraphy */ public class ImageBuilder { /** * 各パーツ情報の読み取りタイムアウト */ private static final int MAX_TIMEOUT = 20; // Secs /** * 各パーツ情報を設定するためのインターフェイス.
* パーツ登録が完了したら、{@link #setComplite()}を呼び出す必要がある.
* * @author seraphy * */ public interface ImageSourceCollector { /** * 画像サイズを設定する.
* * @param size * サイズ */ void setSize(Dimension size); /** * 画像の背景色を設定する.
* 画像生成処理そのものは背景色を使用しないが、画像の生成完了と同じタイミングで背景色を変えるために ホルダーとして用いることを想定している.
* * @param color */ void setImageBgColor(Color color); /** * アフィン変換処理のためのパラメータを指定する.
* 配列サイズは4または6でなければならない.
* * @param param * パラメータ、変換しない場合はnull */ void setAffineTramsform(double[] param); /** * 各パーツを登録する.
* 複数パーツある場合は、これを繰り返し呼び出す.
* すべて呼び出したらsetCompliteを呼び出す.
* * @param layer * レイヤー * @param imageResource * イメージソース * @param param * 色変換情報 */ void setImageSource(Layer layer, ImageResource imageResource, ColorConvertParameter param); /** * パーツの登録が完了したことを通知する。 */ void setComplite(); } /** * 合成が完了した画像を通知するインターフェイス * * @author seraphy */ public interface ImageOutput { /** * 画像の背景色を取得する. * * @return 背景色 */ Color getImageBgColor(); /** *  画像を取得する. * * @return 画像 */ BufferedImage getImageOutput(); } /** * イメージを構築するためのジョブ定義.
* イメージを構築するためのパーツを登録するハンドラと、合成されたイメージを取り出すハンドラ、および エラーハンドラからなる.
* * @author seraphy */ public interface ImageBuildJob { /** * 合成する、各パーツを登録するためのハンドラ.
* * @param collector * 登録するためのインターフェイス */ void loadParts(ImageSourceCollector collector) throws IOException; /** * 合成されたイメージを取得するハンドラ * * @param output * イメージを取得するためのインターフェイス */ void buildImage(ImageOutput output); /** * 例外ハンドラ * * @param ex * 例外 */ void handleException(Throwable ex); } /** * イメージ構築に使用したパーツ情報 * * @author seraphy */ private static final class BuildedPartsInfo { private final ImageBuildPartsInfo partsInfo; private final long lastModified; public BuildedPartsInfo(ImageBuildPartsInfo partsInfo, LoadedImage loadedImage) { this.partsInfo = partsInfo; this.lastModified = loadedImage.getLastModified(); } public ImageBuildPartsInfo getPartsInfo() { return partsInfo; } public long getLastModified() { return lastModified; } } /** * イメージ構築用情報.
* イメージ構築結果も格納される.
* * @author seraphy */ private static final class ImageBuildInfo { private ArrayList partsInfos = new ArrayList(); private ArrayList buildPartsInfos = new ArrayList(); private BufferedImage canvas; private Rectangle rct = new Rectangle(0, 0, 0, 0); private Color imageBgColor; private double[] affineParamHolder; private boolean sorted; @Override public int hashCode() { return partsInfos.hashCode(); } @Override public boolean equals(Object obj) { if (obj != null && obj instanceof ImageBuildInfo) { ImageBuildInfo other = (ImageBuildInfo) obj; if (!getPartsInfos().equals(other.getPartsInfos())) { // パーツ情報を重ね順をあわせて比較している. return false; } if (!rct.equals(other.rct)) { return false; } if (!(imageBgColor == null ? other.imageBgColor == null : imageBgColor.equals(other.imageBgColor))) { return false; } if (!(affineParamHolder == null ? other.affineParamHolder == null : Arrays.equals(affineParamHolder, other.affineParamHolder))) { return false; } return true; } return false; } /** * このイメージ構築情報と、すでに構築したイメージ情報を比較し、 同一であるか判定する.
* 引数に指定したイメージ構築情報が、まだ構築されていない場合は常にfalseとなる.
* イメージリソースが更新されていれば同一構成であってもfalseとなる.
* * @param usedInfo * すでに構築済みのイメージ情報(結果が入っているもの) * @return 同一であればtrue、そうでなければfalse */ public boolean isAlreadyLoaded(ImageBuildInfo usedInfo) { if (usedInfo == null || usedInfo.getCanvas() == null) { return false; } if ( !usedInfo.equals(this)) { // 構成が違うのでfalse return false; } // 要求されているパーツ情報と、読み込み済みのパーツ情報が同一であるか判定する. int mx = partsInfos.size(); int mxUsed = usedInfo.buildPartsInfos.size(); if (mx != mxUsed) { return false; // 念のため } for (int idx = 0; idx < mx; idx++) { ImageBuildPartsInfo partsInfo = partsInfos.get(idx); BuildedPartsInfo buildedPartsInfo = usedInfo.buildPartsInfos.get(idx); if ( !partsInfo.equals(buildedPartsInfo.getPartsInfo())) { // パーツ構成が一致しない.(念のため) return false; } long lastModified = partsInfo.getFile().lastModified(); if (lastModified != buildedPartsInfo.getLastModified()) { // 画像ファイルが更新されている. return false; } } return true; } /** * イメージ構築に使用したパーツ情報を記録する. * * @param partsInfo * パーツ情報 * @param loadedImage * イメージ */ public void addUsedPartsInfo(ImageBuildPartsInfo partsInfo, LoadedImage loadedImage) { buildPartsInfos.add(new BuildedPartsInfo(partsInfo, loadedImage)); } /** * イメージ構築結果を取得する. * * @return イメージ構築結果 */ public BufferedImage getCanvas() { return canvas; } /** * イメージ構築結果を格納する. * * @param canvas * イメージ構築結果 */ public void setCanvas(BufferedImage canvas) { this.canvas = canvas; } public double[] getAffineParamHolder() { return affineParamHolder; } public void setAffineParamHolder(double[] affineParamHolder) { this.affineParamHolder = affineParamHolder; } public Color getImageBgColor() { return imageBgColor; } public void setImageBgColor(Color imageBgColor) { this.imageBgColor = imageBgColor; } public Rectangle getRct() { return rct; } public void setRect(int w, int h) { rct.width = w; rct.height = h; } /** * パーツのリストを取得する.
* 取得された時点で、パーツ情報は重ね合わせ順にソートされている.
* リストは変更不可です.
* * @return パーツ情報のリスト */ public List getPartsInfos() { if ( !sorted) { Collections.sort(partsInfos); sorted = true; } return Collections.unmodifiableList(partsInfos); } public void add(ImageBuildPartsInfo imageBuildPartsInfo) { sorted = false; partsInfos.add(imageBuildPartsInfo); } public int getPartsCount() { return partsInfos.size(); } } /** * イメージのローダー */ private ColorConvertedImageCachedLoader imageLoader; /** * 最後に使用したイメージビルド情報.(初回ならばnull) */ private ImageBuildInfo lastUsedImageBuildInfo; /** * イメージのローダーを指定して構築します.
* * @param imageLoader * イメージローダー */ public ImageBuilder(ColorConvertedImageCachedLoader imageLoader) { if (imageLoader == null) { throw new IllegalArgumentException(); } this.imageLoader = imageLoader; } /** * イメージビルドジョブより、構築すべきイメージの情報を取得する. * * @param imageBuildJob * イメージビルドジョブ * @return 取得されたイメージ構築情報 * @throws IOException * 失敗 * @throws InterruptedException * 割り込み */ protected ImageBuildInfo getPartsInfo(ImageBuildJob imageBuildJob) throws IOException, InterruptedException { final ImageBuildInfo imageBuildInfo = new ImageBuildInfo(); final Semaphore compliteLock = new Semaphore(0); // ジョブリクエスト側に合成するイメージの情報を引き渡すように要求する. // loadPartsが非同期に行われる場合、すぐに制御を戻す. imageBuildJob.loadParts(new ImageSourceCollector() { // ジョブリクエスト側よりイメージサイズの設定として呼び出される public void setSize(Dimension size) { synchronized (imageBuildInfo) { imageBuildInfo.setRect(size.width, size.height); } } public void setImageBgColor(Color color) { synchronized (imageBuildInfo) { imageBuildInfo.setImageBgColor(color); } } public void setAffineTramsform(double[] param) { if (param != null && !(param.length == 4 || param.length == 6)) { throw new IllegalArgumentException("affineTransformParameter invalid length."); } synchronized (imageBuildInfo) { imageBuildInfo.setAffineParamHolder(param); } } // ジョブリクエスト側よりパーツの登録として呼び出される public void setImageSource(Layer layer, ImageResource imageResource, ColorConvertParameter param) { synchronized (imageBuildInfo) { imageBuildInfo.add(new ImageBuildPartsInfo( imageBuildInfo.getPartsCount(), layer, imageResource, param)); } } // ジョブリクエスト側よりイメージサイズとパーツの登録が完了したことを通知される. public void setComplite() { compliteLock.release(); } }); // ImageCollectorは非同期に呼び出されても良いことを想定している. // MAX_TIMEOUTを経過してもsetCompliteが呼び出されない場合、処理を放棄する. if (!compliteLock.tryAcquire(MAX_TIMEOUT, TimeUnit.SECONDS)) { throw new RuntimeException("ImageCollector Timeout."); } return imageBuildInfo; } /** * イメージビルド情報をもとにイメージを構築して返す. * * @param imageBuildInfo * イメージビルド情報と、その結果 * @throws IOException * 失敗 */ protected void buildImage(ImageBuildInfo imageBuildInfo) throws IOException { // 出力画像のカンバスを作成 int w = imageBuildInfo.getRct().width; int h = imageBuildInfo.getRct().height; final BufferedImage canvas = new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB); Graphics2D g = (Graphics2D) canvas.getGraphics(); try { // レンダリングヒント AppConfig appConfig = AppConfig.getInstance(); if (appConfig.isEnableRenderingHints()) { g.setRenderingHint( RenderingHints.KEY_ALPHA_INTERPOLATION, RenderingHints.VALUE_ALPHA_INTERPOLATION_QUALITY); g.setRenderingHint( RenderingHints.KEY_COLOR_RENDERING, RenderingHints.VALUE_COLOR_RENDER_QUALITY); g.setRenderingHint( RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY); } // 各パーツを重ね合わせ順にカンバスに描画する imageLoader.unlockImages(); for (ImageBuildPartsInfo partsInfo : imageBuildInfo.getPartsInfos()) { ImageResource imageFile = partsInfo.getFile(); ColorConvertParameter colorConvParam = partsInfo.getColorParam(); // カラーモデル Layer layer = partsInfo.getLayer(); String colorModelName = layer.getColorModelName(); ColorModel colorModel = ColorModels.safeValueOf(colorModelName); LoadedImage loadedImage = imageLoader.load(imageFile, colorConvParam, colorModel); // イメージ構築に使用した各パーツの結果を格納する. imageBuildInfo.addUsedPartsInfo(partsInfo, loadedImage); // イメージをキャンバスに重ねる. BufferedImage img = loadedImage.getImage(); g.drawImage(img, 0, 0, w, h, 0, 0, w, h, null); } } finally { g.dispose(); } // アフィン処理を行う.(パラメータが指定されていれば) final BufferedImage affineTransformedCanvas; double[] affineTransformParameter = imageBuildInfo.getAffineParamHolder(); if (affineTransformParameter == null || affineTransformParameter.length != 6) { affineTransformedCanvas = canvas; } else { AffineTransform affineTransform = new AffineTransform(affineTransformParameter); AffineTransformOp affineTransformOp = new AffineTransformOp(affineTransform, AffineTransformOp.TYPE_BILINEAR); affineTransformedCanvas = new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB); affineTransformOp.filter(canvas, affineTransformedCanvas); } // 最終的にできあがったキャンバスを結果として格納する. imageBuildInfo.setCanvas(affineTransformedCanvas); } /** * イメージ構築ジョブを要求します.
* 戻り値がtrueである場合は、ただちに完了したことを示します.
* * @param imageBuildJob * イメージを構築するジョブ * @return 画像がただちに得られた場合はtrue、そうでなければfalse */ public boolean requestJob(final ImageBuildJob imageBuildJob) { if (imageBuildJob == null) { throw new IllegalArgumentException(); } // 合成する画像パーツの取得処理 final ImageBuildInfo imageBuildInfo; try { imageBuildInfo = getPartsInfo(imageBuildJob); } catch (Throwable ex) { // 予期せぬ例外の通知 imageBuildJob.handleException(ex); return false; } try { synchronized (imageBuildInfo) { final BufferedImage canvas; // 前回構築したパーツと同じであれば再構築せず、以前のものを使う if (imageBuildInfo.isAlreadyLoaded(lastUsedImageBuildInfo)) { canvas = lastUsedImageBuildInfo.getCanvas(); } else { // パーツの合成処理 buildImage(imageBuildInfo); canvas = imageBuildInfo.getCanvas(); lastUsedImageBuildInfo = imageBuildInfo; } // 完成したカンバスを合成結果として通知する. imageBuildJob.buildImage(new ImageOutput() { public BufferedImage getImageOutput() { return canvas; } public Color getImageBgColor() { return imageBuildInfo.getImageBgColor(); } }); } } catch (Throwable ex) { // 予期せぬ例外の通知 imageBuildJob.handleException(ex); return false; } // 完了 return true; } } /** * 合成する個々のイメージ情報 .
* レイヤー順に順序づけられており、同一レイヤーであればOrder順に順序づけられます.
* * @author seraphy */ final class ImageBuildPartsInfo implements Comparable { private int order; private Layer layer; private ImageResource imageResource; private ColorConvertParameter colorParam; public ImageBuildPartsInfo(int order, Layer layer, ImageResource imageResource, ColorConvertParameter colorParam) { this.order = order; this.layer = layer; this.imageResource = imageResource; this.colorParam = colorParam; } @Override public int hashCode() { return order ^ layer.hashCode() ^ imageResource.hashCode(); } @Override public boolean equals(Object obj) { if (obj == this) { return true; } if (obj != null && obj instanceof ImageBuildPartsInfo) { ImageBuildPartsInfo o = (ImageBuildPartsInfo) obj; return order == o.order && layer.equals(o.layer) && imageResource.equals(o.imageResource) && colorParam.equals(o.colorParam); } return false; } public int compareTo(ImageBuildPartsInfo o) { // レイヤー順 int ret = layer.compareTo(o.layer); if (ret == 0) { // 同一レイヤーであれば定義順 ret = order - o.order; } if (ret == 0) { // それでも同じであればイメージソースの固有の順序 ret = imageResource.compareTo(o.imageResource); } return ret; } public int getOrder() { return order; } public Layer getLayer() { return layer; } public ColorConvertParameter getColorParam() { return colorParam; } public ImageResource getFile() { return imageResource; } } CharacterManaJ/src/charactermanaj/graphics/ColorConvertedImageLoaderImpl.java0000644000175000017500000000757112560206305027643 0ustar paulliupaulliupackage charactermanaj.graphics; import java.awt.image.BufferedImage; import java.awt.image.RescaleOp; import java.io.Closeable; import java.io.IOException; import charactermanaj.graphics.colormodel.ColorModel; import charactermanaj.graphics.colormodel.ColorModels; import charactermanaj.graphics.filters.ColorConvertFilter; import charactermanaj.graphics.filters.ColorConvertParameter; import charactermanaj.graphics.filters.ContrastTableFactory; import charactermanaj.graphics.filters.GammaTableFactory; import charactermanaj.graphics.io.ImageLoader; import charactermanaj.graphics.io.ImageResource; import charactermanaj.graphics.io.LoadedImage; /** * 画像リソースをロードし色変換された結果の画像イメージとして返す.
* @author seraphy */ public class ColorConvertedImageLoaderImpl implements ColorConvertedImageLoader, Closeable { private static final ColorConvertParameter NULL_COLORCONVPARAM = new ColorConvertParameter(); private ImageLoader loader; public ColorConvertedImageLoaderImpl(ImageLoader loader) { if (loader == null) { throw new IllegalArgumentException(); } this.loader = loader; } /** * 画像リソースをロードし色変換した結果のBufferedImageを返します.
* 返される形式はARGBに変換されています.
* * @param file * 画像リソース * @param colorConvParam * 色変換パラメータ、nullの場合はデフォルト * @param colorModel * カラーモデル、nullの場合はデフォルト * @return 画像イメージ * @throws IOException * 形式が不明であるか、ファィルがないか読み取りに失敗した場合 */ public LoadedImage load(ImageResource file, ColorConvertParameter colorConvParam, ColorModel colorModel) throws IOException { if (file == null) { throw new IllegalArgumentException(); } if (colorConvParam == null) { colorConvParam = NULL_COLORCONVPARAM; } if (colorModel == null) { colorModel = ColorModels.DEFAULT; } LoadedImage loadedImage = loader.load(file); BufferedImage originalImage = loadedImage.getImage(); BufferedImage image = colorConvert(originalImage, colorConvParam, colorModel); return new LoadedImage(image, loadedImage.getLastModified()); } public void close() { if (loader instanceof Closeable) { try { ((Closeable) loader).close(); } catch (RuntimeException ex) { throw ex; } catch (Exception ex) { throw new RuntimeException(ex); } } } /** * 色変換ロジック. * @param img 元画像(ARGB形式) * @param param 変換パラメータ * @return 色変換後の画像 */ private BufferedImage colorConvert(BufferedImage img, ColorConvertParameter param, ColorModel colorModel) { float[] factors = { param.getFactorR(), param.getFactorG(), param.getFactorB(), param.getFactorA(), }; float[] offsets = { param.getOffsetR(), param.getOffsetG(), param.getOffsetB(), param.getOffsetA(), }; RescaleOp rescale_op = new RescaleOp(factors, offsets, null); float[] gammas = { param.getGammaA(), param.getGammaR(), param.getGammaG(), param.getGammaB(), }; float[] hsbs = { param.getHue(), param.getSaturation(), param.getBrightness() }; float contrast = param.getContrast(); ColorConvertFilter colorConvert_op = new ColorConvertFilter( colorModel, param.getColorReplace(), hsbs, param.getGrayLevel(), new GammaTableFactory(gammas), new ContrastTableFactory((float) Math.exp(contrast * 2.f)) // 対数補正 ); img = colorConvert_op.filter(img, null); img = rescale_op.filter(img, img); return img; } } CharacterManaJ/src/charactermanaj/graphics/filters/0000755000175000017500000000000012560206305022572 5ustar paulliupaulliuCharacterManaJ/src/charactermanaj/graphics/filters/TableFactory.java0000644000175000017500000000015612560206305026016 0ustar paulliupaulliupackage charactermanaj.graphics.filters; public interface TableFactory { int[][] createTable(); } CharacterManaJ/src/charactermanaj/graphics/filters/ColorConvertFilter.java0000644000175000017500000001216312560206305027225 0ustar paulliupaulliupackage charactermanaj.graphics.filters; import charactermanaj.graphics.colormodel.ColorModel; /** * 色変換フィルタ.
* @author seraphy */ public class ColorConvertFilter extends AbstractFilter { /** * 色置換用インターフェイス.
* @author seraphy */ public interface ColorReplace { /** * R,G,Bの順に格納されている色データに対して編集を行う.
* @param rgb 編集対象 */ void convert(int[] rgb); } /** * カラーモデル */ private final ColorModel colorModel; /** * 色置換オブジェクト */ private final ColorReplace colorReplace; /** * HSB値の各オフセット(3要素) */ private final float[] hsbOffsets; /** * 淡色化率.
* 0に近づくほどグレースケールに近づく.
* 0で完全なグレースケール、1の場合は色の変更はなし。 */ private final float grayLevel; /** * ガンマ値補正用テーブル.
*/ private final int[][] gammaTbl; /** * コントラスト補正用テーブル */ private final int[][] contrastTbl; /** * 色変換フィルタを構築する.
* * @param colorModel * カラーモデル * @param colorReplace * 色置換オブジェクト、不要ならばnull * @param hsbOffsets * HSBオフセット(3要素)、不要ならばnull * @param grayLevel * 淡色化率、1でそのまま、0でグレースケール化。 * @param gammaTableFactory * ガンマ補正値ファクトリ、不要ならばnull * @param contrastTableFactory * コントラスト補正ファクトリ、不要ならばnull */ public ColorConvertFilter( ColorModel colorModel, ColorReplace colorReplace, float[] hsbOffsets, float grayLevel, GammaTableFactory gammaTableFactory, ContrastTableFactory contrastTableFactory) { if (colorModel == null) { throw new IllegalArgumentException(); } this.colorModel = colorModel; if (gammaTableFactory == null) { gammaTableFactory = new GammaTableFactory(1.f); } if (contrastTableFactory == null) { contrastTableFactory = new ContrastTableFactory(1.f); } if (hsbOffsets != null && hsbOffsets.length < 3) { throw new IllegalArgumentException("hsbOffset too short."); } if (hsbOffsets != null) { if (hsbOffsets[0] == 0 && hsbOffsets[1] == 0 && hsbOffsets[2] == 0) { hsbOffsets = null; } else { hsbOffsets = (float[]) hsbOffsets.clone(); } } if (grayLevel < 0) { grayLevel = 0; } else if (grayLevel > 1) { grayLevel = 1.f; } this.grayLevel = grayLevel; this.gammaTbl = gammaTableFactory.createTable(); this.contrastTbl = contrastTableFactory.createTable(); this.hsbOffsets = hsbOffsets; this.colorReplace = colorReplace; } /** * ピクセルデータに対して色変換を行う.
* @param pixcels ピクセルデータ */ protected void filter(int[] pixcels) { final float grayLevel = this.grayLevel; final float negGrayLevel = 1.f - grayLevel; // グレースケール変換テーブル int[] precalc = new int[256]; int[] negPrecalc = new int[256]; for (int i = 0; i < 256; i++) { precalc[i] = (int)(i * grayLevel) & 0xff; negPrecalc[i] = (int)(i * negGrayLevel) & 0xff; } // 全ピクセルに対して計算を行う final ColorReplace colorReplace = this.colorReplace; int[] rgbvals = new int[3]; final float[] hsbOffsets = this.hsbOffsets; final float[] hsvvals = new float[3]; final int[][] gammaTbl = this.gammaTbl; final int mx = pixcels.length; for (int i = 0; i < mx; i++) { int argb = pixcels[i]; // ガンマ変換 int a = gammaTbl[0][(argb >> 24) & 0xff]; int r = gammaTbl[1][(argb >> 16) & 0xff]; int g = gammaTbl[2][(argb >> 8) & 0xff]; int b = gammaTbl[3][(argb) & 0xff]; // 色交換 if (colorReplace != null) { rgbvals[0] = r; rgbvals[1] = g; rgbvals[2] = b; colorReplace.convert(rgbvals); r = rgbvals[0]; g = rgbvals[1]; b = rgbvals[2]; } // 輝度 int br = ((77 * r + 150 * g + 29 * b) >> 8) & 0xff; // 輝度(グレースケール)に近づける r = ((int)(precalc[r] + negPrecalc[br])) & 0xff; g = ((int)(precalc[g] + negPrecalc[br])) & 0xff; b = ((int)(precalc[b] + negPrecalc[br])) & 0xff; // 色調変換 if (hsbOffsets != null) { colorModel.RGBtoHSV(r, g, b, hsvvals); for (int l = 0; l < 3; l++) { hsvvals[l] += hsbOffsets[l]; } for (int l = 1; l < 3; l++) { if (hsvvals[l] < 0) { hsvvals[l] = 0; } else if (hsvvals[l] > 1.f) { hsvvals[l] = 1.f; } } int rgb = colorModel.HSVtoRGB(hsvvals[0], hsvvals[1], hsvvals[2]); r = (rgb >> 16) & 0xff; g = (rgb >> 8) & 0xff; b = (rgb) & 0xff; } // コントラスト変換 r = contrastTbl[0][r]; g = contrastTbl[1][g]; b = contrastTbl[2][b]; argb = (a << 24) | (r << 16) | (g << 8) | b; pixcels[i] = argb; } } } CharacterManaJ/src/charactermanaj/graphics/filters/ContrastTableFactory.java0000644000175000017500000000166512560206305027542 0ustar paulliupaulliupackage charactermanaj.graphics.filters; /** * コントラストの計算済みテーブルを用意する.
* 最大で255x3あれば足りる.
* * @author seraphy * */ public class ContrastTableFactory implements TableFactory { private float contrast = 1.f; public ContrastTableFactory() { this(1.f); } public ContrastTableFactory(float contrast) { this.contrast = contrast; } public int[][] createTable() { int[] table = new int[256]; for (int level = 0; level <= 255; level++) { float f = level / 255.f; f = getContrast(f); int c = (int)(f * 256); if (c > 255) { c = 255; } else if (c < 0) { c = 0; } table[level] = c; } int[][] tables = new int[3][]; for (int idx = 0; idx < 3; idx++) { tables[idx] = table; } return tables; } protected float getContrast(float f) { return (f - 0.5f) * contrast + 0.5f; } } CharacterManaJ/src/charactermanaj/graphics/filters/ColorConvertParameter.java0000644000175000017500000001613212560206305027720 0ustar paulliupaulliupackage charactermanaj.graphics.filters; import java.io.IOException; import java.io.ObjectInputStream; import java.io.ObjectInputStream.GetField; import java.io.Serializable; /** * カラー情報.
* RGB置換、GrayLevel、RGBオフセット各種、HSB調整値を持つ.
* @author seraphy */ public final class ColorConvertParameter implements Serializable, Cloneable { private static final long serialVersionUID = 3092895708547846162L; /** * 色置換パターン */ private ColorConv rgbChanelMixierPattern; /** * グレーレベル(0でモノトーン、1でノーマル、0.5で半分ほどモノトーン化) */ private float grayLevel = 1.f; private float hue = 0.f; private float saturation = 0.f; private float brightness = 0.f; private float contrast = 0.f; private int offsetR = 0; private int offsetG = 0; private int offsetB = 0; private int offsetA = 0; private float factorR = 1.f; private float factorG = 1.f; private float factorB = 1.f; private float factorA = 1.f; private float gammaR = 1.f; private float gammaG = 1.f; private float gammaB = 1.f; private float gammaA = 1.f; private void readObject(ObjectInputStream stream) throws IOException, ClassNotFoundException { GetField fields = stream.readFields(); this.rgbChanelMixierPattern = (ColorConv) fields.get("rgbChanelMixierPattern", ColorConv.NONE); this.grayLevel = fields.get("grayLevel", 1.f); this.hue = fields.get("hue", 0.f); this.saturation = fields.get("saturation", 0.f); this.brightness = fields.get("brightness", 0.f); this.contrast = fields.get("contrast", 0.f); this.offsetR = fields.get("offsetR", 0); this.offsetG = fields.get("offsetG", 0); this.offsetB = fields.get("offsetB", 0); this.offsetA = fields.get("offsetA", 0); this.factorR = fields.get("factorR", 1.f); this.factorG = fields.get("factorG", 1.f); this.factorB = fields.get("factorB", 1.f); this.factorA = fields.get("factorA", 1.f); this.gammaR = fields.get("gammaR", 1.f); this.gammaG = fields.get("gammaG", 1.f); this.gammaB = fields.get("gammaB", 1.f); this.gammaA = fields.get("gammaA", 1.f); } @Override public int hashCode() { int ret = 0; if (rgbChanelMixierPattern != null) { ret = rgbChanelMixierPattern.ordinal(); } ret ^= (int)(grayLevel * 100); ret ^= (int)(hue * 100); ret ^= (int)(saturation * 100); ret ^= (int)(brightness * 100); ret ^= (int)(contrast * 100); ret ^= offsetR; ret ^= offsetG; ret ^= offsetB; ret ^= offsetA; ret ^= (int)(factorR * 100); ret ^= (int)(factorG * 100); ret ^= (int)(factorB * 100); ret ^= (int)(factorA * 100); ret ^= (int)(gammaR * 100); ret ^= (int)(gammaG * 100); ret ^= (int)(gammaB * 100); ret ^= (int)(gammaA * 100); return ret; } @Override public boolean equals(Object obj) { if (obj == this) { return true; } if (obj != null && obj instanceof ColorConvertParameter) { ColorConvertParameter o = (ColorConvertParameter) obj; return rgbChanelMixierPattern == o.rgbChanelMixierPattern && grayLevel == o.grayLevel && hue == o.hue && saturation == o.saturation && contrast == o.contrast && brightness == o.brightness && offsetR == o.offsetR && offsetG == o.offsetG && offsetB == o.offsetB && offsetA == o.offsetA && factorR == o.factorR && factorG == o.factorG && factorB == o.factorB && factorA == o.factorA && gammaR == o.gammaR && gammaG == o.gammaG && gammaB == o.gammaB && gammaA == o.gammaA; } return false; } public static boolean equals(ColorConvertParameter a, ColorConvertParameter b) { if (a == b) { return true; } if (a != null && b != null) { return a.equals(b); } return false; } @Override public ColorConvertParameter clone() { try { // シャローコピー. すべてimmutableな単純型だけのメンバなので問題なし. return (ColorConvertParameter) super.clone(); } catch (CloneNotSupportedException e) { throw new Error("internal error."); } } public ColorConv getColorReplace() { return rgbChanelMixierPattern; } public void setColorReplace(ColorConv colorReplace) { this.rgbChanelMixierPattern = colorReplace; } public float getGrayLevel() { return grayLevel; } public void setGrayLevel(float grayLevel) { this.grayLevel = grayLevel; } public float getHue() { return hue; } public void setHue(float hue) { this.hue = hue; } public float getSaturation() { return saturation; } public void setSaturation(float saturation) { this.saturation = saturation; } public float getBrightness() { return brightness; } public void setBrightness(float brightness) { this.brightness = brightness; } public float getContrast() { return contrast; } public void setContrast(float contrast) { this.contrast = contrast; } public int getOffsetR() { return offsetR; } public void setOffsetR(int offsetR) { this.offsetR = offsetR; } public int getOffsetG() { return offsetG; } public void setOffsetG(int offsetG) { this.offsetG = offsetG; } public int getOffsetB() { return offsetB; } public void setOffsetB(int offsetB) { this.offsetB = offsetB; } public int getOffsetA() { return offsetA; } public void setOffsetA(int offsetA) { this.offsetA = offsetA; } public float getFactorR() { return factorR; } public void setFactorR(float factorR) { this.factorR = factorR; } public float getFactorG() { return factorG; } public void setFactorG(float factorG) { this.factorG = factorG; } public float getFactorB() { return factorB; } public void setFactorB(float factorB) { this.factorB = factorB; } public float getFactorA() { return factorA; } public void setFactorA(float factorA) { this.factorA = factorA; } public float getGammaR() { return gammaR; } public void setGammaR(float gammaR) { this.gammaR = gammaR; } public float getGammaG() { return gammaG; } public void setGammaG(float gammaG) { this.gammaG = gammaG; } public float getGammaB() { return gammaB; } public void setGammaB(float gammaB) { this.gammaB = gammaB; } public float getGammaA() { return gammaA; } public void setGammaA(float gammaA) { this.gammaA = gammaA; } @Override public String toString() { StringBuilder buf = new StringBuilder(); buf.append(getClass().getSimpleName() + "@" + Integer.toHexString(System.identityHashCode(this)) + "("); buf.append("(replace:" + rgbChanelMixierPattern + ", grayLevel:" + grayLevel + ")"); buf.append("(H:" + hue + ", S:" + saturation + ", B:" + brightness + ", C:" + contrast +")"); buf.append("(Red:" + offsetR + "/" + factorR + "/" + gammaR + ", Green:" + offsetG + "/" + factorG + "/" + gammaG + ", Blue:" + offsetB + "/" + factorB + "/" + gammaB + ", Alpha:" + offsetA + "/" + factorA + "/" + gammaA + ")"); buf.append(")"); return buf.toString(); } } CharacterManaJ/src/charactermanaj/graphics/filters/BackgroundColorFilter.java0000644000175000017500000001410012560206305027655 0ustar paulliupaulliupackage charactermanaj.graphics.filters; import java.awt.Color; import charactermanaj.graphics.colormodel.HSYColorModel; public class BackgroundColorFilter extends AbstractFilter { /** * 背景モード. * @author seraphy */ public enum BackgroundColorMode { ALPHABREND(false, false, 1) { @Override public void filter(BackgroundColorFilter me, int[] pixcels) { me.alphabrend(pixcels); } }, OPAQUE(false, true, 2) { @Override public void filter(BackgroundColorFilter me, int[] pixcels) { me.opaque(pixcels); } }, GRAYSCALE(true, false, 4) { @Override public void filter(BackgroundColorFilter me, int[] pixcels) { me.grayscale(pixcels); } }, DRAW_ALPHA(true, true, 8) { @Override public void filter(BackgroundColorFilter me, int[] pixcels) { me.drawAlpha(pixcels); } }; private final boolean grayscale; private final boolean noAlphachanel; private final int mask; public abstract void filter(BackgroundColorFilter me, int[] pixcels); BackgroundColorMode(boolean grayscale, boolean noAlphachanel, int mask) { this.grayscale = grayscale; this.noAlphachanel = noAlphachanel; this.mask = mask; } public boolean isNoAlphaChannel() { return this.noAlphachanel; } public boolean isGrayscale() { return this.grayscale; } public int mask() { return mask; } public static BackgroundColorMode valueOf(boolean noAlphachanel, boolean grayscale) { for (BackgroundColorMode mode : values()) { if (mode.isNoAlphaChannel() == noAlphachanel && mode.isGrayscale() == grayscale) { return mode; } } throw new RuntimeException("構成に誤りがあります."); } } /** * 背景モード */ private BackgroundColorMode mode; /** * 背景色、グレースケールまたはアルファ表示モードでは不要 */ private Color bgColor; /** * 背景モードと背景色を指定して背景色描画フィルタを構築する. * @param mode モード * @param bgColor 背景色、(グレースケールまたはアルファではnull化) */ public BackgroundColorFilter(BackgroundColorMode mode, Color bgColor) { if (mode == null) { // モードは必須. throw new IllegalArgumentException(); } if (!mode.isGrayscale() && bgColor == null) { // グレースケールもしくはアルファ表示モード以外は背景色は必須. throw new IllegalArgumentException(); } this.mode = mode; this.bgColor = bgColor; } @Override protected void filter(int[] pixcels) { mode.filter(this, pixcels); } /** * 普通のアルファブレンドします. * @param pixcels */ public void alphabrend(int[] pixcels) { int br = bgColor.getRed(); int bg = bgColor.getGreen(); int bb = bgColor.getBlue(); final int mx = pixcels.length; for (int idx = 0; idx < mx; idx++) { int argb = pixcels[idx]; int b = argb & 0xff; int g = (argb >>>= 8) & 0xff; int r = (argb >>>= 8) & 0xff; int a = (argb >>>= 8) & 0xff; if (a == 0) { // 完全透過ならば背景色まま b = bb; g = bg; r = br; } else if (a != 0xff) { // 完全非透過でなければアルファブレンド b = ((b * a) / 0xff + (bb * (0xff - a) / 0xff)) & 0xff; g = ((g * a) / 0xff + (bg * (0xff - a) / 0xff)) & 0xff; r = ((r * a) / 0xff + (br * (0xff - a) / 0xff)) & 0xff; } argb = 0xff000000 | (r << 16) | (g << 8) | b; pixcels[idx] = argb; } } /** * 完全透過の部分のみ背景色を設定し、それ以外は元の色のままアルファを取り除く.
* @param pixcels ピクセルデータ */ public void opaque(int[] pixcels) { int bgRgb = bgColor.getRGB(); final int mx = pixcels.length; for (int idx = 0; idx < mx; idx++) { int argb = pixcels[idx]; int a = (argb >>> 24) & 0xff; int rgb = (argb & 0xffffff); if (a == 0) { rgb = bgRgb; } argb = 0xff000000 | rgb; pixcels[idx] = argb; } } /** * アルファを取り除き、グレスケールで表現する.
* RGBチャネルのうち、RBはアルファ適用されたグレースケールで、 * Gチャネルはアルファ未適用のグレースケールで表現される.
* @param pixcels ピクセルデータ */ public void grayscale(int[] pixcels) { final int mx = pixcels.length; for (int idx = 0; idx < mx; idx++) { int argb = pixcels[idx]; int b = argb & 0xff; int g = (argb >>>= 8) & 0xff; int r = (argb >>>= 8) & 0xff; int a = (argb >>>= 8) & 0xff; int gray_brend = 0; int gray_plain = 0; if (a != 0) { // 輝度の算定(グレースケール化) gray_brend = HSYColorModel.getGrayscale(r, g, b); gray_plain = gray_brend; if (a != 0xff) { // アルファによる調整 gray_brend = ((gray_brend * a) / 0xff) & 0xff; } } argb = 0xff000000 | (gray_brend << 16) | (gray_plain << 8) | gray_brend; pixcels[idx] = argb; } } /** * アルファチャネルとグレースケールを重ねて表示する.
* アルファ未適用のグレースケールをRチャネル、 * アルファを無し・半透明・透明の3段階にしたものをGチャネル、 * アルファをBチャネルで表現する.
* Gチャネルは、完全透過は濃緑(0x80)、完全非透過は黒(0xff)、半透明は明緑(0xff)となる.
* @param pixcels ピクセルデータ */ public void drawAlpha(int[] pixcels) { final int mx = pixcels.length; for (int idx = 0; idx < mx; idx++) { int argb = pixcels[idx]; int b = argb & 0xff; int g = (argb >>>= 8) & 0xff; int r = (argb >>>= 8) & 0xff; int a = (argb >>>= 8) & 0xff; int gray_plain = (r + g + b) / 3; int alpha_off = (a == 0) ? 0x80 : (a == 0xff) ? 0x00 : 0xff; argb = 0xff000000 | (gray_plain << 16) | (alpha_off << 8) | a; pixcels[idx] = argb; } } } CharacterManaJ/src/charactermanaj/graphics/filters/AbstractFilter.java0000644000175000017500000001202312560206305026344 0ustar paulliupaulliupackage charactermanaj.graphics.filters; import java.awt.Rectangle; import java.awt.RenderingHints; import java.awt.geom.Point2D; import java.awt.geom.Rectangle2D; import java.awt.image.BufferedImage; import java.awt.image.BufferedImageOp; import java.awt.image.ColorModel; import java.awt.image.DataBufferInt; import java.awt.image.WritableRaster; /** * 色フィルタの抽象クラス.
* @author seraphy */ public abstract class AbstractFilter implements BufferedImageOp { /** * 色変換を行うルーチン.
* 派生クラスでオーバーライドする.
* @param pixcels ARGB形式のピクセルデータ */ protected abstract void filter(int[] pixcels); public BufferedImage createCompatibleDestImage(BufferedImage src, ColorModel destCM) { if (destCM == null) { destCM = src.getColorModel(); } int w = src.getWidth(); int h = src.getHeight(); return new BufferedImage(destCM, destCM.createCompatibleWritableRaster(w, h), destCM.isAlphaPremultiplied(), null); } public BufferedImage filter(BufferedImage src, BufferedImage dest) { if (dest == null) { dest = createCompatibleDestImage(src, null); } int w = src.getWidth(); int h = src.getHeight(); int imageType = src.getType(); int [] pixcels; boolean shared = false; if (src == dest && (imageType == BufferedImage.TYPE_INT_ARGB || imageType == BufferedImage.TYPE_INT_RGB)) { // 元イメージと出力先イメージが同一であり、且つ、 // イメージがARGB/RGB形式であればピクセルデータはイメージが持つバッファそのものを共有アクセスする. // したがって、setPixcelsの呼び出しは不要. pixcels = null; shared = true; } else { // 元イメージと出力先イメージが異なるか、もしくは、 // イメージがARGB/RGB形式以外であれば、RGB形式のint配列に変換して処理する. // イメージに書き戻すためにsetPixcelsの呼び出しが必要となる. int len = w * h; pixcels = new int[len]; } pixcels = getPixcels(src, 0, 0, w, h, pixcels); filter(pixcels); if (!shared) { setPixcels(dest, 0, 0, w, h, pixcels); } return dest; } public Rectangle2D getBounds2D(BufferedImage src) { int w = src.getWidth(); int h = src.getHeight(); return new Rectangle(0, 0, w, h); } public Point2D getPoint2D(Point2D srcPt, Point2D dstPt) { return (Point2D) srcPt.clone(); } public RenderingHints getRenderingHints() { return null; } /** * ピクセルデータを取得する.
* 画像のタイプがARGBもしくはRGBの場合はpixcelsにnullを指定して格納先を指定しない場合は * 格納先を自動的に構築する.
* そうでない場合はピクセルデータはイメージのピクセルバッファを、そのまま返す.(つまり、変更は即イメージの変更になる.)
* ARGB,RGB以外のイメージは常に幅x高さ分のRGB(もしくはARGB)を格納できるだけのバッファを指定しなければならない.
* @param img 対象のイメージ * @param x 位置x * @param y 位置y * @param w 幅 * @param h 高さ * @param pixcels 格納先バッファ、もしくはnull * @return ピクセルデータ */ protected int[] getPixcels(BufferedImage img, int x, int y, int w, int h, int[] pixcels) { if (w <= 0 || h <= 0) { return new int[0]; } int len = w * h; if (pixcels != null && pixcels.length < len) { throw new IllegalArgumentException("array too short."); } int imageType = img.getType(); if (imageType == BufferedImage.TYPE_INT_ARGB || imageType == BufferedImage.TYPE_INT_RGB) { WritableRaster raster = img.getRaster(); if (pixcels == null) { DataBufferInt buf = (DataBufferInt) raster.getDataBuffer(); return buf.getData(); } return (int[]) raster.getDataElements(x, y, w, h, pixcels); } if (pixcels == null) { throw new IllegalArgumentException("image type error."); } return img.getRGB(x, y, w, h, pixcels, 0, w); } /** * ピクセルデータをイメージに書き戻す.
* ピクセルデータがnullであるか幅または高さが0であれば何もしない.
* @param img 対象のイメージ * @param x 位置x * @param y 位置y * @param w 幅 * @param h 高さ * @param pixcels ピクセルデータ、nullの場合は何もしない. */ protected void setPixcels(BufferedImage img, int x, int y, int w, int h, int[] pixcels) { int len = w * h; if (pixcels == null || w == 0 || h == 0) { return; } if (pixcels.length < len) { throw new IllegalArgumentException("array too short."); } int imageType = img.getType(); if (imageType == BufferedImage.TYPE_INT_ARGB || imageType == BufferedImage.TYPE_INT_RGB) { WritableRaster raster = img.getRaster(); raster.setDataElements(x, y, w, h, pixcels); return; } img.setRGB(x, y, w, h, pixcels, 0, w); } } CharacterManaJ/src/charactermanaj/graphics/filters/ColorConv.java0000644000175000017500000000416312560206305025345 0ustar paulliupaulliupackage charactermanaj.graphics.filters; import charactermanaj.graphics.filters.ColorConvertFilter.ColorReplace; /** * 色置換オブジェクトの列挙子.
* 「キャラクターなんとか機」のカラー変更にあわせて、画像は青・白(黒)の濃淡だけで表現し、 * RGBのマッピングを変えることで色変換する.(色調変換ではない。)
* G成分はないがしろにされている。 * @author seraphy */ public enum ColorConv implements ColorReplace { /** * 変換なし */ NONE { @Override public void convert(int[] rgb) { // do nothing. } }, /** * 青系.
* RGB成分をRRBにする.
*/ BLUE { @Override public void convert(int[] rgb) { rgb[1] = rgb[0]; } }, /** * 紫系.
* RGB成分をBRBにする.
*/ VIOLET { @Override public void convert(int[] rgb) { rgb[1] = rgb[0]; rgb[0] = rgb[2]; } }, /** * 赤系.
* RGB成分をBRRにする.
*/ RED { @Override public void convert(int[] rgb) { rgb[1] = rgb[0]; rgb[0] = rgb[2]; rgb[2] = rgb[1]; } }, /** * 黄系.
* RGB成分をBBRにする.
*/ YELLOW { @Override public void convert(int[] rgb) { rgb[1] = rgb[2]; rgb[2] = rgb[0]; rgb[0] = rgb[1]; } }, /** * 緑系.
* RGB成分をRBRにする.
*/ GREEN { @Override public void convert(int[] rgb) { rgb[1] = rgb[2]; rgb[2] = rgb[0]; } }, /** * シアン系にする.
* RGB成分をRBBにする.
*/ CYAN { @Override public void convert(int[] rgb) { rgb[1] = rgb[2]; } }, /** * 黒系にする.
* RGB成分をRRRにする.
*/ BLACK { @Override public void convert(int[] rgb) { rgb[1] = rgb[0]; rgb[2] = rgb[0]; } }, /** * 白系にする.
* RGB成分をBBBにする.
*/ WHITE { @Override public void convert(int[] rgb) { rgb[0] = rgb[2]; rgb[1] = rgb[2]; } }; /** * 色置換する. */ public abstract void convert(int[] rgb); } CharacterManaJ/src/charactermanaj/graphics/filters/GammaTableFactory.java0000644000175000017500000000405312560206305026761 0ustar paulliupaulliupackage charactermanaj.graphics.filters; /** * ガンマ補正値を構築するためのファクトリ. * @author seraphy */ public class GammaTableFactory implements TableFactory { /** * ARGBの、それぞれのガンマ補正値の配列 */ private float[] gammas; /** * ARGBすべてが同一のガンマ値で構築する. * @param gamma ガンマ値 */ public GammaTableFactory(float gamma) { setGamma(gamma); } /** * ARGBそれぞれ異なるガンマ値で構築する.
* @param gammas ガンマ値 */ public GammaTableFactory(float[] gammas) { setGamma(gammas); } /** * ARGBすべてが同一のガンマ値を設定する. * @param gamma ガンマ値 */ public final void setGamma(float gamma) { setGamma(new float[] {gamma, gamma, gamma, gamma}); } /** * ARGBそれぞれ異なるガンマ値を設定する.
* @param gammas ガンマ値 */ public final void setGamma(float[] gammas) { if (gammas == null || gammas.length < 3) { throw new IllegalArgumentException(); } this.gammas = gammas; } /** * ARGB/RGBの、それぞれのガンマ値に対する0-255の範囲に対する補正後の値を格納する * 二次元配列を構築する.
* @return ガンマテーブル */ public int[][] createTable() { int mx = gammas.length; int[][] gammaTbls = new int[mx][]; for (int i = 0; i < 4; i++) { float gamma; if (i < mx) { gamma = gammas[i]; } else { gamma = 1.f; } gammaTbls[i] = createGamma(gamma); } return gammaTbls; } /** * ガンマ値に対する0-255の入力に対する、そのガンマ補正値の配列を返す.
* @param gamma ガンマ値 * @return ガンマテーブル */ private int[] createGamma(float gamma) { if (gamma < 0.01f) { gamma = 0.01f; } int gammaTbl[] = new int[256]; for (int gi = 0; gi <= 0xff; gi++) { gammaTbl[gi] = (int)(Math.pow(gi / 255.0, 1 / gamma) * 255) & 0xff; } return gammaTbl; } } CharacterManaJ/src/charactermanaj/graphics/ColorConvertedImageCachedLoader.java0000644000175000017500000000612212560206305030100 0ustar paulliupaulliupackage charactermanaj.graphics; import java.io.IOException; import charactermanaj.graphics.colormodel.ColorModel; import charactermanaj.graphics.filters.ColorConvertParameter; import charactermanaj.graphics.io.ImageCache; import charactermanaj.graphics.io.ImageCachedLoader; import charactermanaj.graphics.io.ImageLoader; import charactermanaj.graphics.io.ImageResource; import charactermanaj.graphics.io.LoadedImage; /** * 画像リソースに対する色変換後の画像イメージを返します.
* 一度読み込まれ色変換された画像は、画像ファイルの更新日が同一であり、且つ、色パラメータに変更がなければ * 読み込み済みの画像イメージを返します.
* @author seraphy * */ public class ColorConvertedImageCachedLoader extends ColorConvertedImageLoaderImpl { private ImageCache caches = new ImageCache(); public ColorConvertedImageCachedLoader() { this(new ImageCachedLoader()); } public ColorConvertedImageCachedLoader(ImageLoader imageLoader) { super(imageLoader); } @Override public LoadedImage load(ImageResource file, ColorConvertParameter colorConvParam, ColorModel colorModel) throws IOException { if (file == null) { throw new IllegalArgumentException(); } ColorConvertParameter param; if (colorConvParam == null) { param = new ColorConvertParameter(); } else { param = colorConvParam.clone(); } ColorConvertedImageKey key = new ColorConvertedImageKey(param, file); synchronized (caches) { LoadedImage loadedImage = caches.get(key); if (loadedImage == null) { loadedImage = super.load(file, param, colorModel); caches.set(key, loadedImage); } return loadedImage; } } @Override public void close() { caches.clear(); super.close(); } public void unlockImages() { caches.unlockImages(); } } final class ColorConvertedImageKey { private final ColorConvertParameter colorConvParameter; private final ImageResource imageResource; private final long lastModified; private final int hashCode; public ColorConvertedImageKey(ColorConvertParameter colorConvParameter, ImageResource imageResource) { if (colorConvParameter == null || imageResource == null) { throw new IllegalArgumentException(); } this.colorConvParameter = colorConvParameter; this.imageResource = imageResource; this.lastModified = imageResource.lastModified(); this.hashCode = imageResource.hashCode() ^ colorConvParameter.hashCode() ^ (int) this.lastModified; } @Override public int hashCode() { return this.hashCode; } @Override public boolean equals(Object obj) { if (obj == this) { return true; } if (obj != null && obj instanceof ColorConvertedImageKey) { ColorConvertedImageKey other = (ColorConvertedImageKey) obj; return lastModified == other.lastModified && imageResource.equals(other.imageResource) && colorConvParameter.equals(other.colorConvParameter); } return false; } } CharacterManaJ/src/charactermanaj/graphics/io/0000755000175000017500000000000012560206305021531 5ustar paulliupaulliuCharacterManaJ/src/charactermanaj/graphics/io/UkagakaImageConverter.java0000644000175000017500000001744612560206305026607 0ustar paulliupaulliupackage charactermanaj.graphics.io; import java.awt.Color; import java.awt.image.BufferedImage; import java.awt.image.Raster; import java.util.Arrays; import java.util.logging.Level; import java.util.logging.Logger; /** * 伺か用PNG変換クラス. * @author seraphy */ public class UkagakaImageConverter { /** * ロガー */ private static final Logger logger = Logger.getLogger(UkagakaImageConverter.class.getName()); /** * シングルトン */ private static final UkagakaImageConverter inst = new UkagakaImageConverter(); /** * シングルトンコンストラクタ */ protected UkagakaImageConverter() { super(); } public static UkagakaImageConverter getInstance() { return inst; } /** * 伺か用PNA(アルファチャネルのグレースケール表現)に変換する. * @param img 変換元の透過画像(TYPE_INT_ARGB専用) * @return 伺か用PNA画像(TYPE_INT_RGB, アルファチャネルのグレースケール表現) */ public BufferedImage createUkagakaPNA(BufferedImage img) { if (img == null) { throw new IllegalArgumentException("引数にnullは指定できません。"); } if (img.getType() != BufferedImage.TYPE_INT_ARGB) { throw new IllegalArgumentException("TYPE_INT_ARGB専用です."); } int w = img.getWidth(); int h = img.getHeight(); Raster raster = img.getData(); BufferedImage outimg = new BufferedImage(w, h, BufferedImage.TYPE_BYTE_GRAY); // アルファ値をグレースケール表現に変換 int[] argb = null; for (int y = 0; y < h; y++) { for (int x = 0; x < w; x++) { argb = raster.getPixel(x, y, argb); int a = argb[3]; // alpha int o = a << 16 | a << 8 | a; outimg.setRGB(x, y, o); } } return outimg; } /** * 伺か用PNGに変換するための透過色に設定できる色を選択する.
* 該当がない場合(選択できなかった場合)はnullを返す.
* @param img 変換元の透過画像(TYPE_INT_ARGB専用) * @return 伺か用PNG画像(非透過)の透過色キー、該当がない場合はnull */ public Color detectTransparentColorKey(BufferedImage img) { if (img == null) { throw new IllegalArgumentException("引数にnullは指定できません。"); } if (img.getType() != BufferedImage.TYPE_INT_ARGB) { throw new IllegalArgumentException("TYPE_INT_ARGB専用です."); } int w = img.getWidth(); int h = img.getHeight(); Raster raster = img.getData(); // 512色インデックスグループ化 final int colorMx = 512; int[] colorCounts = new int[colorMx]; // ピクセル単位の512色インデックスごとの使用数を計測 int[] argb = new int[4]; for (int y = 0; y < h; y++) { for (int x = 0; x < w; x++) { argb = raster.getPixel(x, y, argb); int a = argb[3]; if (a == 0) { continue; // 完全透過はノーカウント } // 上位3ビットのみ int r = (argb[0] >>> 5) & 0x07; int g = (argb[1] >>> 5) & 0x07; int b = (argb[2] >>> 5) & 0x07; // インデックス生成 int idx = r << 6 | g << 3 | b; colorCounts[idx]++; } } if (logger.isLoggable(Level.FINER)) { logger.log(Level.FINER, "counts=" + Arrays.toString(colorCounts)); } // 色インデックスの色相と選択候補の重み float[] colorHues = new float[colorMx]; float[] hsb = new float[3]; // 候補の重み float[] colorWeights = new float[colorMx]; // 上位3ビットインデックスから8ビットRGB値への変換マップ int[] colorMap = new int[colorMx]; for (int idx = 0; idx < colorMx; idx++) { int r = ((idx >>> 6) & 0x07); int g = ((idx >>> 3) & 0x07); int b = ((idx) & 0x07); r = r << 5 | 0x1f; g = g << 5 | 0x1f; b = b << 5 | 0x1f; hsb = Color.RGBtoHSB(r, g, b, hsb); colorHues[idx] = hsb[0]; colorWeights[idx] = hsb[1]; // 濃さを重みとする. colorMap[idx] = r << 16 | g << 8 | b; } if (logger.isLoggable(Level.FINER)) { logger.log(Level.FINER, "weight=" + Arrays.toString(colorWeights)); } // 色インデックスごとの未使用色相の個数カウント float[] unusedColorScore = new float[colorMx]; float tolerance = 0.0625f; for (int idx = 0; idx < colorMx; idx++) { if (colorCounts[idx] > 0) { // 使用していればスキップ. continue; } // 類似色相のスコア float hue = colorHues[idx]; float score = 0.f; for (int ref = 0; ref < colorMx; ref++) { if (colorCounts[ref] > 0) { // 使用していればスキップ continue; } float refHue = colorHues[ref]; // 色相が等しいものを1、色相がズレるほどに0に近づく float diff = (tolerance - Math.abs(hue - refHue)) / tolerance; if (diff < 0) { // 範囲を超えていればスキップ continue; } score += diff; } // 色による重み付け float weight = colorWeights[idx]; unusedColorScore[idx] = score * weight; } if (logger.isLoggable(Level.FINER)) { logger.log(Level.FINER, "scores=" + Arrays.toString(unusedColorScore)); } // もっとも重いスコアを選択 float maxScore = 0; int maxIdx = -1; for (int idx = 0; idx < colorMx; idx++) { float score = unusedColorScore[idx]; if (score > maxScore) { maxScore = score; maxIdx = idx; } } if (logger.isLoggable(Level.FINER)) { logger.log(Level.FINER, "selectedIdx=" + maxIdx + "/score=" + maxScore); } // 透過用色の取得 if (maxIdx >= 0) { return new Color(colorMap[maxIdx]); } // 候補を見つけられなかった場合. return null; } /** * 伺か用PNGに変換する.
* @param img 変換元の透過画像(TYPE_INT_ARGB専用) * @param transparentColorKey 透過色キー、nullの場合は自動選択 * @return 伺か用PNG画像(TYPE_INT_RGB, 左上に透過色指定あり) */ public BufferedImage createUkagakaPNG(BufferedImage img, Color transparentColorKey) { if (img == null) { throw new IllegalArgumentException("引数にnullは指定できません。"); } if (img.getType() != BufferedImage.TYPE_INT_ARGB) { throw new IllegalArgumentException("TYPE_INT_ARGB専用です."); } // 透過色に設定するカラーキーの取得 if (transparentColorKey == null) { transparentColorKey = detectTransparentColorKey(img); } int transparencyColor; if (transparentColorKey != null) { transparencyColor = transparentColorKey.getRGB() & 0xffffff; } else { // カラーキーの取得がでなければ、黒に限りなく近い非黒を透過色として代替する. logger.log(Level.INFO, "透過色の選択ができなかったため、0x010101で代用します."); transparencyColor = 0x010101; } int w = img.getWidth(); int h = img.getHeight(); Raster raster = img.getData(); // 完全な透過ピクセルに対して算定した透過色を割り当て、 // 画像の左上に透過色を設定する. int argb[] = new int[4]; BufferedImage outimg = new BufferedImage(w, h, BufferedImage.TYPE_INT_RGB); for (int y = 0; y < h; y++) { for (int x = 0; x < w; x++) { argb = raster.getPixel(x, y, argb); int a = argb[3]; // alpha int c; if (a == 0) { // 完全透過の場合のみ透過色を設定 c = transparencyColor; } else { // それ以外はアルファを無視してRGBのみ int r = argb[0]; int g = argb[1]; int b = argb[2]; c = r << 16 | g << 8 | b; } outimg.setRGB(x, y, c); } // 左上(0,0)に透過とする色を設定 outimg.setRGB(0, 0, transparencyColor); } return outimg; } } CharacterManaJ/src/charactermanaj/graphics/io/ImageLoader.java0000644000175000017500000000103112560206305024540 0ustar paulliupaulliupackage charactermanaj.graphics.io; import java.io.IOException; /** * 画像をロードします.
* @author seraphy */ public interface ImageLoader { /** * 画像リソースからBufferedImageを返します.
* 返される形式はARGBに変換されています.
* @param imageResource 画像リソース * @throws IOException 読み取りに失敗した場合、もしくは画像の形式が不明な場合 */ LoadedImage load(ImageResource imageResource) throws IOException; } CharacterManaJ/src/charactermanaj/graphics/io/ImageCachedLoader.java0000644000175000017500000000432712560206305025643 0ustar paulliupaulliupackage charactermanaj.graphics.io; import java.io.Closeable; import java.io.IOException; /** * 一度読み込んだ画像をキャッシュする画像ローダ.
* すでに読み込まれており、ファイルの更新日に変更がなければ読み込み済みの画像をかえす.
* @author seraphy */ public class ImageCachedLoader extends ImageLoaderImpl implements Closeable { /** * リソースに対するイメージキャッシュ.
* リソースは複数のプロファイルで共有しえるのでstaticとしている。 */ private static ImageCache caches = new ImageCache(); @Override public LoadedImage load(ImageResource imageResource) throws IOException { if (imageResource == null) { throw new IllegalArgumentException(); } ImageResourceCacheKey key = new ImageResourceCacheKey(imageResource); synchronized (caches) { LoadedImage loadedImage = caches.get(key); if (loadedImage != null) { long lastModified = loadedImage.getLastModified(); if (lastModified != imageResource.lastModified()) { // キャッシュされているが、すでに古い場合は破棄する. loadedImage = null; } } if (loadedImage == null) { loadedImage = super.load(imageResource); caches.set(key, loadedImage); caches.unlockImages(); // 即時解放許可 } return loadedImage; } } public void close() { caches.clear(); } } final class ImageResourceCacheKey { private final ImageResource imageResource; private final int hashCode; public ImageResourceCacheKey(ImageResource imageResource) { if (imageResource == null) { throw new IllegalArgumentException(); } this.imageResource = imageResource; this.hashCode = imageResource.hashCode(); } @Override public int hashCode() { return this.hashCode; } @Override public boolean equals(Object obj) { if (obj == this) { return true; } if (obj != null && obj instanceof ImageResourceCacheKey) { ImageResourceCacheKey other = (ImageResourceCacheKey) obj; return imageResource.equals(other.imageResource); } return false; } } CharacterManaJ/src/charactermanaj/graphics/io/PNGFileImageHeaderReader.java0000644000175000017500000001131512560206305027020 0ustar paulliupaulliupackage charactermanaj.graphics.io; import java.io.BufferedInputStream; import java.io.DataInputStream; import java.io.EOFException; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.net.URI; import java.net.URL; import java.util.Arrays; /** * PNGファイルのヘッダ情報を読み取る * @author seraphy */ public class PNGFileImageHeaderReader { /** * PNGファイルのファイルヘッダ */ public static final byte[] FILE_HEADER = { (byte) 0x89, (byte) 0x50, (byte) 0x4e, (byte) 0x47, (byte) 0x0d, (byte) 0x0a, (byte) 0x1a, (byte) 0x0a }; private static final PNGFileImageHeaderReader singletion = new PNGFileImageHeaderReader(); private PNGFileImageHeaderReader() { super(); } public static PNGFileImageHeaderReader getInstance() { return singletion; } /** * URLを指定してヘッダ情報を取得する.
* @param url * @return ヘッダ、PNGでない場合はnull * @throws IOException 読み取りに失敗した場合 */ public PNGFileImageHeader readHeader(URI uri) throws IOException { if (uri == null) { throw new IllegalArgumentException(); } URL url = uri.toURL(); InputStream is = url.openStream(); try { return readHeader(is); } finally { is.close(); } } /** * ファイルを指定してヘッダ情報を取得する. * @param file * @return ヘッダ、PNGでない場合はnull * @throws IOException 読み取りに失敗した場合 */ public PNGFileImageHeader readHeader(File file) throws IOException { if (file == null) { throw new IllegalArgumentException(); } InputStream is = new BufferedInputStream(new FileInputStream(file)); try { return readHeader(is); } finally { is.close(); } } /** * ストリームを指定してヘッダ情報を読み取る.
* ストリームは読み取った分だけ消費された状態で返される.
* @param is ストリーム * @return ヘッダ、PNGでない場足はnull * @throws IOException 読み取りに失敗した場合 */ public PNGFileImageHeader readHeader(InputStream is) throws IOException { if (is == null) { throw new IllegalArgumentException(); } DataInputStream dis = new DataInputStream(is); try { // ファイルヘッダの読み取り byte[] fileHeader = new byte[FILE_HEADER.length]; dis.readFully(fileHeader); if (!Arrays.equals(fileHeader, FILE_HEADER)) { // ヘッダが一致しない return null; } PNGFileImageHeader imageHeader = null; boolean hasTransparencyInfomation = false; for (;;) { int chunkLen = dis.readInt(); // チャンクの長さ byte[] chunkType = new byte[4]; dis.readFully(chunkType); if (Arrays.equals(chunkType, "IHDR".getBytes())) { imageHeader = new PNGFileImageHeader(); imageHeader.setWidth(dis.readInt()); // 4bytes imageHeader.setHeight(dis.readInt()); // 4bytes imageHeader.setBitDepth(((int) dis.readByte()) & 0xff); // 1byte imageHeader.setColorType(((int) dis.readByte()) & 0xff); // 1byte imageHeader.setCompressionMethod(((int) dis.readByte()) & 0xff); // 1byte imageHeader.setFilterMethod(((int) dis.readByte()) & 0xff); // 1byte imageHeader.setInterlaceMethod(((int) dis.readByte()) & 0xff); // 1byte int zan = chunkLen - 13; if (zan < 0) { throw new EOFException("IHDR too short"); } if (dis.skipBytes(zan) != zan) { throw new IOException("チャンクのサイズが不正です."); } } else if (Arrays.equals(chunkType, "tRNS".getBytes())) { // カラータイプによりチャンクの中身の形式は異なる. // インデックス(ColorType=3)の場合は透過色のインデックス // グレースケールの場合は、透過色とするスケール値など. hasTransparencyInfomation = chunkLen > 0; if (dis.skipBytes(chunkLen) != chunkLen) { throw new IOException("チャンクのサイズが不正です."); } } else if (Arrays.equals(chunkType, "IEND".getBytes())) { // 終了チャンク break; } else { // IHDR以外のチャンクは読み飛ばす if (dis.skipBytes(chunkLen) != chunkLen) { throw new IOException("チャンクのサイズが不正です."); } } dis.readInt(); // CRC32を読み飛ばす } if (imageHeader != null) { imageHeader.setTransparencyInformation(hasTransparencyInfomation); } return imageHeader; } catch (EOFException e) { // 何もしない } return null; } } CharacterManaJ/src/charactermanaj/graphics/io/PNGFileImageHeader.java0000644000175000017500000000610412560206305025675 0ustar paulliupaulliupackage charactermanaj.graphics.io; /** * PNGヘッダ情報.
* http://en.wikipedia.org/wiki/Portable_Network_Graphics#cite_note-4 , * http://www.libpng.org/pub/png/spec/1.2/PNG-Chunks.html#C.IHDR あたりを参照.
* @author seraphy */ public class PNGFileImageHeader { private int width; // 4bytes private int height; // 4bytes private int bitDepth; // 1byte private int colorType; // 1byte private int compressionMethod; // 1byte private int filterMethod; // 1byte private int interlaceMethod; // 1byte private boolean transparencyInformation; public int getWidth() { return width; } public void setWidth(int width) { this.width = width; } public int getHeight() { return height; } public void setHeight(int height) { this.height = height; } public int getBitDepth() { return bitDepth; } public void setBitDepth(int bitDepth) { this.bitDepth = bitDepth; } /** * カラータイプを取得する.
* Bit0がインデックス or グレースケール、Bit1がカラー、Bit2がアルファ.
* その組み合わせで8通りあるが、1、5、7はサポート外となる.
*
    *
  • 0: greyscale
  • *
  • 2: Truecolor (Color)
  • *
  • 3: Indexed (Color | Palette)
  • *
  • 4: greyscale alpha (Alpha)
  • *
  • 6: Alpha Color (Color | Alpha) (CharacterManaJでは、これを想定する.)
  • *
* @return */ public int getColorType() { return colorType; } public void setColorType(int colorType) { this.colorType = colorType; } public int getCompressionMethod() { return compressionMethod; } public void setCompressionMethod(int compressionMethod) { this.compressionMethod = compressionMethod; } public int getFilterMethod() { return filterMethod; } public void setFilterMethod(int filterMethod) { this.filterMethod = filterMethod; } public int getInterlaceMethod() { return interlaceMethod; } public void setInterlaceMethod(int interlaceMethod) { this.interlaceMethod = interlaceMethod; } public void setTransparencyInformation(boolean hasTransparencyInformation) { this.transparencyInformation = hasTransparencyInformation; } /** * 透過情報があるか?
* ColorTypeが3(Indexed Color), 2(TrueColor)で、透過情報がある場合は、透過情報つきカラーである.
* そうでなければ透過なしカラーである.
* @return */ public boolean hasTransparencyInformation() { return transparencyInformation; } @Override public String toString() { StringBuilder buf = new StringBuilder(); buf.append("PNG(widht:" + width + ", height:" + height); buf.append(", bitDepth:" + bitDepth + ", colorType: " + colorType); buf.append(", hasTransparency: " + transparencyInformation); buf.append(", compressionMethod:" + compressionMethod); buf.append(", filterMethod:" + filterMethod); buf.append(", interlaceMethod:" + interlaceMethod); buf.append(")"); return buf.toString(); } } CharacterManaJ/src/charactermanaj/graphics/io/ImageCache.java0000644000175000017500000001072712560206305024351 0ustar paulliupaulliupackage charactermanaj.graphics.io; import java.awt.image.BufferedImage; import java.lang.ref.Reference; import java.lang.ref.ReferenceQueue; import java.lang.ref.SoftReference; import java.util.HashMap; import java.util.logging.Level; import java.util.logging.Logger; /** * 画像のキャッシュ.
* キャッシュは自動的にガベージコレクタにより回収されます.
* ただし、{@link #unlockImages()}が呼び出されるまで、{@link #set(Object, BufferedImage)}されたイメージは * ガベージコレクトの対象にはなりません。 * @author seraphy * * @param */ public class ImageCache { private static final Logger logger = Logger.getLogger(ImageCache.class.getName()); private static final ImageCacheMBeanImpl imageCacheMBean = ImageCacheMBeanImpl.getSingleton(); private HashMap> lockedImages = new HashMap>(); private ReferenceQueue queue = new ReferenceQueue(); private HashMap> caches = new HashMap>(); public ImageCache() { imageCacheMBean.incrementInstance(); } @Override protected void finalize() throws Throwable { clear(); imageCacheMBean.decrementInstance(); super.finalize(); } public LoadedImage get(K key) { if (key == null) { return null; } synchronized (caches) { BufferedImageWithKeyReference ref = caches.get(key); LoadedImage img = null; if (ref != null) { img = ref.get(); } imageCacheMBean.incrementReadCount(img != null); sweep(); return img; } } public void set(K key, LoadedImage img) { if (key == null) { return; } synchronized (caches) { // 現在キャッシュされているものがあれば、いったん解放する. BufferedImageWithKeyReference ref = caches.get(key); if (ref != null) { ref.enqueue(); } if (img == null) { if (logger.isLoggable(Level.FINE)) { logger.log(Level.FINE, "remove cache: " + key); } caches.remove(key); } else { BufferedImageWithKeyReference cacheData = new BufferedImageWithKeyReference(key, img, queue); lockedImages.put(key, cacheData); caches.put(key, cacheData); imageCacheMBean.cacheIn(cacheData.getImageSize()); } // 解放済みのアイテムエントリを除去する. sweep(); } } public void unlockImages() { synchronized (caches) { lockedImages.clear(); sweep(); } } /** * すべてのエントリをキャッシュアウトしてクリアする. */ public void clear() { synchronized (caches) { lockedImages.clear(); for (BufferedImageWithKeyReference ref : caches.values()) { ref.enqueue(); } sweep(); caches.clear(); } } public void sweep() { synchronized (caches) { // ガベージコレクト済みアイテムを除去する Reference ref = null; boolean removed = false; while ((ref = queue.poll()) != null) { @SuppressWarnings("unchecked") BufferedImageWithKeyReference r = (BufferedImageWithKeyReference) ref; K key = r.getKey(); if (key != null) { if (caches.get(key).get() == null) { if (logger.isLoggable(Level.FINE)) { logger.log(Level.FINE, "removed cache: " + key); } removed = true; caches.remove(key); } } int imageSize = r.getImageSize(); imageCacheMBean.cacheOut(imageSize); } if (removed) { if (logger.isLoggable(Level.FINE)) { logger.log(Level.FINE, "cache[" + Integer.toHexString(this.hashCode()) + "] size:" + caches.size()); } } } } } /** * キー情報つきSoftReference * @author seraphy * * @param キー */ class BufferedImageWithKeyReference extends SoftReference { private final K key; private final int imageSize; public BufferedImageWithKeyReference(K key, LoadedImage img, ReferenceQueue queue) { super(img, queue); this.key = key; this.imageSize = (img == null) ? 0 : img.getImageSize(); } public K getKey() { return key; } public int getImageSize() { return imageSize; } } CharacterManaJ/src/charactermanaj/graphics/io/FileImageResource.java0000644000175000017500000000303212560206305025724 0ustar paulliupaulliupackage charactermanaj.graphics.io; import java.io.BufferedInputStream; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.io.Serializable; import java.net.URI; /** * ファイルシステム上にある画像リソースを示す. * @author seraphy */ public class FileImageResource implements ImageResource, Serializable { /** * シリアライズバージョン */ private static final long serialVersionUID = 5397113740824387869L; /** * ファイル */ private File file; public FileImageResource(File file) { if (file == null) { throw new IllegalArgumentException(); } this.file = file; } public long lastModified() { return file.lastModified(); } public InputStream openStream() throws IOException { return new BufferedInputStream(new FileInputStream(file)); } @Override public int hashCode() { return file.hashCode(); } public int compareTo(ImageResource o) { return getFullName().compareTo(o.getFullName()); } @Override public boolean equals(Object obj) { if (obj == this) { return true; } if (obj != null && obj instanceof FileImageResource) { FileImageResource o = (FileImageResource) obj; return file.equals(o.file); } return false; } public String getFullName() { return file.getPath(); } public URI getURI() { return file.toURI(); } @Override public String toString() { return file.toString(); } } CharacterManaJ/src/charactermanaj/graphics/io/UkagakaImageSaveHelper.java0000644000175000017500000002666112560206305026675 0ustar paulliupaulliupackage charactermanaj.graphics.io; import java.awt.Color; import java.awt.Graphics2D; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.image.BufferedImage; import java.io.BufferedOutputStream; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.util.Properties; import java.util.logging.Level; import java.util.logging.Logger; import javax.imageio.IIOImage; import javax.imageio.ImageIO; import javax.imageio.ImageReadParam; import javax.imageio.ImageReader; import javax.imageio.ImageWriteParam; import javax.imageio.ImageWriter; import javax.imageio.event.IIOWriteWarningListener; import javax.imageio.stream.ImageInputStream; import javax.imageio.stream.ImageOutputStream; import javax.swing.JFileChooser; import javax.swing.JFrame; import javax.swing.JOptionPane; import javax.swing.filechooser.FileFilter; import charactermanaj.ui.UkagakaConvertDialog; import charactermanaj.util.LocalizedResourcePropertyLoader; /** * 伺か用PNG/PNA出力ヘルパ. * @author seraphy * */ public class UkagakaImageSaveHelper { /** * ロガー */ private static final Logger logger = Logger.getLogger(UkagakaImageSaveHelper.class.getName()); /** * リソース */ protected static final String STRINGS_RESOURCE = "languages/ukagakaImageSaveHelper"; /** * PNGファイルフィルタ */ protected static final FileFilter pngFilter = new FileFilter() { @Override public boolean accept(File f) { return f.isDirectory() || f.getName().endsWith(".png"); } @Override public String getDescription() { return "PNG(*.png)"; } }; /** * 最後に開いたディレクトリ.
* まだ使用していなければnull.
*/ protected File lastUseOpenDir; /** * 最後に保存したディレクトリ.
* まだ使用していなければnull.
*/ protected File lastUseSaveDir; /** * 最後に保存したファイル名 */ protected String lastUseSaveName = "surface"; /** * 最後に使用した透過色キー.
* まだ使用していなければnull.
*/ protected Color transparentColorKey; /** * 最後に使用した透過色キーモード. */ protected boolean autoTransparentColor = true; /** * コンストラクタ */ public UkagakaImageSaveHelper() { Properties strings = LocalizedResourcePropertyLoader.getCachedInstance() .getLocalizedProperties(STRINGS_RESOURCE); lastUseSaveName = strings.getProperty("default.lastUseSaveName"); } protected JFileChooser createFileChooser(final boolean save) { JFileChooser fileChooser = new JFileChooser() { private static final long serialVersionUID = 1L; @Override public void approveSelection() { File outFile = getSelectedFile(); if (outFile == null) { return; } String lcName = outFile.getName().toLowerCase(); FileFilter selfilter = getFileFilter(); if (selfilter == pngFilter) { if (!lcName.endsWith(".png")) { outFile = new File(outFile.getPath() + ".png"); setSelectedFile(outFile); } } if (save && outFile.exists()) { Properties strings = LocalizedResourcePropertyLoader.getCachedInstance() .getLocalizedProperties(STRINGS_RESOURCE); if (JOptionPane.showConfirmDialog(this, strings.getProperty("confirmOverwrite"), strings.getProperty("confirm"), JOptionPane.YES_NO_OPTION, JOptionPane.WARNING_MESSAGE) != JOptionPane.YES_OPTION) { return; } } super.approveSelection(); } }; if (lastUseSaveDir != null) { fileChooser.setCurrentDirectory(lastUseSaveDir); } fileChooser.setFileFilter(pngFilter); if ( !save) { fileChooser.setAcceptAllFileFilterUsed(false); } fileChooser.addChoosableFileFilter(pngFilter); return fileChooser; } /** * 透過画像(TYPE_INT_ARGB)から、伺か用のPNG/PNAファイルに出力する. * @param parent 親フレーム * @param img 対象イメージ * @param colorKey マニュアル指定時の候補(前回のものを優先) * @throws IOException 出力に失敗した場合 */ public void save(JFrame parent, BufferedImage img, Color colorKey) throws IOException { final UkagakaConvertDialog dlg = new UkagakaConvertDialog(parent); if (!autoTransparentColor && transparentColorKey != null) { // 前回マニュアル透過色キー指定であれば、それを使う. colorKey = transparentColorKey; } dlg.setExportImage(img, colorKey); dlg.setAutoTransparentColor(autoTransparentColor); dlg.setSaveActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { JFileChooser fileChooser = createFileChooser(true); fileChooser.setSelectedFile(new File(lastUseSaveDir, lastUseSaveName)); int ret = fileChooser.showSaveDialog(dlg); if (ret != JFileChooser.APPROVE_OPTION) { return; } // 結果 dlg.setResult(fileChooser.getSelectedFile()); dlg.dispose(); } }); dlg.setVisible(true); File selectedFile = (File) dlg.getResult(); if (selectedFile == null) { return; } lastUseSaveName = selectedFile.getName(); lastUseSaveDir = selectedFile.getParentFile(); File pngFile = selectedFile; File pnaFile = makePNAFileName(pngFile); File[] outfiles = {pngFile, pnaFile}; BufferedImage[] outimages = { dlg.getOpaqueImage(), dlg.getAlphaImage() }; savePNGImages(outfiles, outimages); } /** * 複数のファイルとイメージを指定して書き込みます.
* ファイルとイメージの個数は一致していなければなりません.
* 同じ添え字のファイルに対して、その添え字のイメージが出力されます.
* いずれかで失敗した場合、その時点で処理は打ち切られて例外が返されます.
* (すでに出力されたファイル、もしくは書き込み中のファイルは放置されます.)
* @param outfiles ファイルの配列 * @param outimages イメージの配列 * @throws IOException 失敗 */ protected void savePNGImages(File[] outfiles, BufferedImage[] outimages) throws IOException { if (outfiles == null || outimages == null) { throw new IllegalArgumentException("引数にnullは指定でまきせん。"); } if (outfiles.length != outimages.length) { throw new IllegalArgumentException("ファイルおよびイメージの個数は一致していなければなりません."); } ImageWriter iw = ImageIO.getImageWritersByFormatName("png").next(); try { iw.addIIOWriteWarningListener(new IIOWriteWarningListener() { public void warningOccurred(ImageWriter source, int imageIndex, String warning) { logger.log(Level.WARNING, warning); } }); for (int idx = 0; idx < outfiles.length; idx++) { File outfile = outfiles[idx]; BufferedImage outimage = outimages[idx]; BufferedOutputStream bos = new BufferedOutputStream( new FileOutputStream(outfile)); try { ImageWriteParam iwp = iw.getDefaultWriteParam(); IIOImage ioimg = new IIOImage(outimage, null, null); ImageOutputStream imgstm = ImageIO.createImageOutputStream(bos); try { iw.setOutput(imgstm); iw.write(null, ioimg, iwp); } finally { imgstm.close(); } } finally { bos.close(); } } } finally { iw.dispose(); } } /** * 複数ファイルを指定して既存のファイルから伺かPNG/PNAに変換して出力する.(ユーテリティ) * @param parent 親フレーム * @param colorKey 透過色キー(候補) * @throws IOException 失敗 */ public void convertChooseFiles(JFrame parent, Color colorKey) throws IOException { JFileChooser fileChooser = createFileChooser(false); fileChooser.setCurrentDirectory(lastUseOpenDir); fileChooser.setMultiSelectionEnabled(true); int ret = fileChooser.showOpenDialog(parent); if (ret != JFileChooser.APPROVE_OPTION) { return; } // 選択したディレクトリを記憶する. File[] files = fileChooser.getSelectedFiles(); if (files == null || files.length == 0) { return; } lastUseOpenDir = files[0].getParentFile(); final UkagakaConvertDialog dlg = new UkagakaConvertDialog(parent, null, true); if (!autoTransparentColor && transparentColorKey != null) { // 前回マニュアル透過色キー指定であれば、それを使う. colorKey = transparentColorKey; } dlg.setAutoTransparentColor(autoTransparentColor); ImageReader ir = ImageIO.getImageReadersByFormatName("png").next(); try { for (final File file : files) { String fname = file.getName(); ImageReadParam param = ir.getDefaultReadParam(); BufferedImage img; ImageInputStream iis = ImageIO.createImageInputStream(file); try { ir.setInput(iis); img = ir.read(0, param); } finally { iis.close(); } img = convertIntARGB(img); dlg.setCaption(fname); dlg.setExportImage(img, colorKey); dlg.setSaveActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { if ( !dlg.isOverwriteOriginalFile()) { JFileChooser fileChooser = createFileChooser(true); fileChooser.setCurrentDirectory(file.getParentFile()); fileChooser.setSelectedFile(file); int ret = fileChooser.showSaveDialog(dlg); if (ret != JFileChooser.APPROVE_OPTION) { return; } // 選択結果 dlg.setResult(fileChooser.getSelectedFile()); } else { // ソースと同じ (上書き) dlg.setResult(file); } dlg.dispose(); } }); dlg.setVisible(true); File selectedFile = (File) dlg.getResult(); if (selectedFile == null) { // キャンセルされた場合 break; } File pngFile = selectedFile; File pnaFile = makePNAFileName(pngFile); File[] outfiles = {pngFile, pnaFile}; BufferedImage[] outimages = { dlg.getOpaqueImage(), dlg.getAlphaImage() }; savePNGImages(outfiles, outimages); } } finally { ir.dispose(); } } /** * BufferedImageをINT_TYPE_ARGBに設定する.
* @param img イメージ * @return 形式をINT_ARGBに変換されたイメージ */ protected BufferedImage convertIntARGB(BufferedImage img) { if (img == null || img.getType() == BufferedImage.TYPE_INT_ARGB) { // nullであるか、変換不要であれば、そのまま返す. return img; } int w = img.getWidth(); int h = img.getHeight(); BufferedImage dst = new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB); Graphics2D g = dst.createGraphics(); try { g.drawImage(img, 0, 0, w, h, 0, 0, w, h, null); } finally { g.dispose(); } return dst; } /** * 拡張子をPNAに変換して返す. * @param pngFile PNGファイル名 * @return PNAファイル名 */ protected File makePNAFileName(File pngFile) { if (pngFile == null) { return null; } String fname = pngFile.getName(); int extpos = fname.lastIndexOf('.'); if (extpos >= 0) { fname = fname.substring(0, extpos); } fname += ".pna"; return new File(pngFile.getParent(), fname); } } CharacterManaJ/src/charactermanaj/graphics/io/ImageCacheMBeanImpl.java0000644000175000017500000000616612560206305026100 0ustar paulliupaulliupackage charactermanaj.graphics.io; import java.lang.management.ManagementFactory; import javax.management.JMException; import javax.management.MBeanServer; import javax.management.ObjectName; import javax.management.StandardMBean; public final class ImageCacheMBeanImpl implements ImageCacheMBean { private static ImageCacheMBeanImpl singleton = new ImageCacheMBeanImpl(); private ImageCacheMBeanImpl() { super(); } public static ImageCacheMBeanImpl getSingleton() { return singleton; } public static void setupMBean() throws JMException { MBeanServer srv = ManagementFactory.getPlatformMBeanServer(); srv.registerMBean( new StandardMBean(singleton, ImageCacheMBean.class), new ObjectName("CharacterManaJ:type=ImageCache,name=Singleton")); } private long readCount; private long cacheHitCount; private long totalBytes; private long maxBytes; private int totalCount; private int instanceCount; public synchronized long getReadCount() { return readCount; } public synchronized void setReadCount(long readCount) { this.readCount = readCount; } public synchronized long getCacheHitCount() { return cacheHitCount; } public synchronized void setCacheHitCount(long cacheHitCount) { this.cacheHitCount = cacheHitCount; } public synchronized long getTotalBytes() { return totalBytes; } public synchronized void setTotalBytes(long totalBytes) { this.totalBytes = totalBytes; } public synchronized long getMaxBytes() { return maxBytes; } public synchronized void setMaxBytes(long maxBytes) { this.maxBytes = maxBytes; } public synchronized void incrementReadCount(boolean cacheHit) { readCount++; if (cacheHit) { cacheHitCount++; } } public synchronized void cacheIn(long bytes) { totalCount++; totalBytes += bytes; if (totalBytes > maxBytes) { maxBytes = totalBytes; } } public synchronized void cacheOut(long bytes) { totalCount--; totalBytes -= bytes; } public synchronized int getTotalCount() { return totalCount; } public synchronized int getInstanceCount() { return instanceCount; } public synchronized void incrementInstance() { instanceCount++; } public synchronized void decrementInstance() { instanceCount--; } public synchronized void reset() { cacheHitCount = 0; readCount = 0; totalCount = 0; totalBytes = 0; maxBytes = 0; } @Override public String toString() { synchronized (this) { StringBuilder buf = new StringBuilder(); buf.append("imageCacheMBean "); buf.append(cacheHitCount); buf.append("/"); buf.append(readCount); return buf.toString(); } } } CharacterManaJ/src/charactermanaj/graphics/io/ImageSaveHelper.java0000644000175000017500000005354112560206305025405 0ustar paulliupaulliupackage charactermanaj.graphics.io; import java.awt.BorderLayout; import java.awt.Color; import java.awt.Component; import java.awt.Font; import java.awt.Graphics2D; import java.awt.GridBagConstraints; import java.awt.GridBagLayout; import java.awt.Insets; import java.awt.RenderingHints; import java.awt.image.BufferedImage; import java.io.File; import java.io.IOException; import java.io.OutputStream; import java.util.Arrays; import java.util.Iterator; import java.util.List; import java.util.Properties; import java.util.logging.Level; import java.util.logging.Logger; import javax.imageio.IIOImage; import javax.imageio.ImageIO; import javax.imageio.ImageWriteParam; import javax.imageio.ImageWriter; import javax.imageio.event.IIOWriteWarningListener; import javax.imageio.stream.ImageOutputStream; import javax.swing.BorderFactory; import javax.swing.Box; import javax.swing.JCheckBox; import javax.swing.JComboBox; import javax.swing.JFileChooser; import javax.swing.JLabel; import javax.swing.JOptionPane; import javax.swing.JPanel; import javax.swing.JSpinner; import javax.swing.SpinnerNumberModel; import javax.swing.filechooser.FileFilter; import charactermanaj.graphics.io.OutputOption.PictureMode; import charactermanaj.graphics.io.OutputOption.ZoomRenderingType; import charactermanaj.util.LocalizedMessageComboBoxRender; import charactermanaj.util.LocalizedResourcePropertyLoader; /** * イメージを保存するためのヘルパークラス.
*/ public class ImageSaveHelper { /** * ロガー */ private static final Logger logger = Logger.getLogger(ImageSaveHelper.class.getName()); /** * リソース */ protected static final String STRINGS_RESOURCE = "languages/imageSaveHelper"; /** * このヘルパクラス用のファイルフィルタの抽象実装 * @author seraphy */ protected static abstract class ImageSaveHelperFilter extends FileFilter { @Override public boolean accept(File f) { if (f.isDirectory()) { // ディレクトリは選択可 return true; } return isSupported(f); } protected boolean isSupported(File f) { // サポートしている拡張子のいずれかにマッチするか? // (大文字・小文字は区別しない.) String lcName = f.getName().toLowerCase(); for (String ext : getSupprotedExtension()) { if (lcName.endsWith("." + ext.toLowerCase())) { return true; } } return false; } /** * 現在の選択されたファイル名を取得し、そのファイル名がデフォルトの拡張子で終端していなければ * デフォルトの拡張子を設定してファイルチューザに設定し直す.
* @param fileChooser ファイルチューザ * @return デフォルトの拡張子で終端されたファイル */ public File supplyDefaultExtension(JFileChooser fileChooser) { File outFile = fileChooser.getSelectedFile(); if (outFile == null) { return null; } if ( !isSupported(outFile)) { String extName = "." + getSupprotedExtension()[0]; outFile = new File(outFile.getPath() + extName); fileChooser.setSelectedFile(outFile); } return outFile; } /** * サポートするファイルの拡張子を取得する.
* 最初のものがデフォルトの拡張子として用いられる.
* @return ファイルの拡張子 */ protected abstract String[] getSupprotedExtension(); } /** * PNGファイルフィルタ */ protected static final FileFilter pngFilter = new ImageSaveHelperFilter() { @Override public String getDescription() { return "PNG(*.png)"; } @Override protected String[] getSupprotedExtension() { return new String[] {"png"}; } }; /** * JPEGファイルフィルタ */ protected static final FileFilter jpegFilter = new ImageSaveHelperFilter() { @Override public String getDescription() { return "JPEG(*.jpg;*.jpeg)"; } @Override protected String[] getSupprotedExtension() { return new String[] {"jpeg", "jpg"}; } }; /** * BMPファイルフィルタ */ protected static final FileFilter bmpFilter = new ImageSaveHelperFilter() { @Override public String getDescription() { return "Bitmap(*.bmp)"; } @Override protected String[] getSupprotedExtension() { return new String[] {"bmp"}; } }; /** * このヘルパクラスで定義されているファイルフィルタのリスト */ protected static final List fileFilters = Arrays.asList( pngFilter, jpegFilter, bmpFilter); /** * イメージビルダファクトリ */ protected OutputImageBuilderFactory imageBuilderFactory; /** * 最後に使用した出力オプション.
* 未使用であれば規定値.
*/ protected OutputOption outputOption; /** * 最後に使用したディレクトリ */ protected File lastUseSaveDir; /** * 最後に使用したフィルタ */ protected FileFilter lastUseFilter = pngFilter; /** * 最後に使用したディレクトリを設定する * @param lastUseSaveDir 最後に使用したディレクトリ、設定しない場合はnull */ public void setLastUseSaveDir(File lastUseSaveDir) { this.lastUseSaveDir = lastUseSaveDir; } /** * 最後に使用したディレクトリを取得する * @return 最後に使用したディレクトリ、なければnull */ public File getLastUsedSaveDir() { return lastUseSaveDir; } /** * コンストラクタ */ public ImageSaveHelper() { imageBuilderFactory = new OutputImageBuilderFactory(); outputOption = imageBuilderFactory.createDefaultOutputOption(); } /** * 画像ファイルの保存用ダイアログを表示する. * @param parent 親ウィンドウ * @return ファイル名 */ public File showSaveFileDialog(Component parent) { // 最後に使用したディレクトリを指定してファイルダイアログを構築する. JFileChooser fileChooser = new JFileChooser(lastUseSaveDir) { private static final long serialVersionUID = -9091369410030011886L; /** * OKボタン押下時の処理. */ @Override public void approveSelection() { File outFile = getSelectedFile(); if (outFile == null) { return; } // 選択したファイルフィルタに従ってデフォルトの拡張子を付与する. FileFilter selfilter = getFileFilter(); if (selfilter instanceof ImageSaveHelperFilter) { outFile = ((ImageSaveHelperFilter) selfilter).supplyDefaultExtension(this); } // ファイルが存在すれば上書き確認する. if (outFile.exists()) { Properties strings = LocalizedResourcePropertyLoader.getCachedInstance() .getLocalizedProperties(STRINGS_RESOURCE); if (JOptionPane.showConfirmDialog(this, strings.getProperty("confirmOverwrite"), strings.getProperty("confirm"), JOptionPane.YES_NO_OPTION, JOptionPane.WARNING_MESSAGE) != JOptionPane.YES_OPTION) { return; } } super.approveSelection(); } }; // // アクセサリパネルの追加˙ // final OutputOptionPanel accessoryPanel = new OutputOptionPanel(this.outputOption); // fileChooser.setAccessory(accessoryPanel); // ファイルフィルタ設定 fileChooser.setAcceptAllFileFilterUsed(false); for (FileFilter fileFilter : fileFilters) { fileChooser.addChoosableFileFilter(fileFilter); } // 最後に使用したファイルフィルタが既定のフィルタでないか未設定であればPNGにする. if (lastUseFilter == null || !fileFilters.contains(lastUseFilter)) { lastUseFilter = pngFilter; } // 最後に使用したフィルタをデフォルトのフィルタに設定する. fileChooser.setFileFilter(lastUseFilter); // ファイルダイアログを開く. int ret = fileChooser.showSaveDialog(parent); if (ret != JFileChooser.APPROVE_OPTION) { return null; } // // 出力オプションの保存 // OutputOption outputOption = accessoryPanel.getOutputOption(); // this.outputOption = outputOption; // 最後に使用したフィルタ、および選択したディレクトリを記憶する. File outFile = fileChooser.getSelectedFile(); lastUseSaveDir = outFile.getParentFile(); lastUseFilter = fileChooser.getFileFilter(); // 選択したファイルを返す. return outFile; } public OutputOption getOutputOption() { return outputOption.clone(); } public void setOutputOption(OutputOption outputOption) { if (outputOption == null) { throw new IllegalArgumentException(); } this.outputOption = outputOption.clone(); } /** * ファイル名を指定してイメージをファイルに出力します.
* 出力形式は拡張子より判定します.
* サポートされていない拡張子の場合はIOException例外が発生します.
* @param img イメージ * @param imgBgColor JPEGの場合の背景色 * @param outFile 出力先ファイル(拡張子が必須) * @param warnings 警告を記録するバッファ、必要なければnull * @throws IOException 失敗 */ public void savePicture(BufferedImage img, Color imgBgColor, File outFile, final StringBuilder warnings) throws IOException { if (img == null || outFile == null) { throw new IllegalArgumentException(); } // ファイル名から拡張子を取り出します. String fname = outFile.getName(); int extpos = fname.lastIndexOf("."); if (extpos < 0) { throw new IOException("missing file extension."); } String ext = fname.substring(extpos + 1).toLowerCase(); // 拡張子に対するImageIOのライタを取得します. Iterator ite = ImageIO.getImageWritersBySuffix(ext); if (!ite.hasNext()) { throw new IOException("unsupported file extension: " + ext); } ImageWriter iw = ite.next(); // ライタを使いイメージを書き込みます. savePicture(img, imgBgColor, iw, outFile, warnings); } /** * イメージをMIMEで指定された形式で出力します. * @param img イメージ * @param imgBgColor JPEGの場合の背景色 * @param outstm 出力先 * @param mime MIME * @param warnings 警告を書き込むバッファ、必要なければnull * @throws IOException 例外 */ public void savePicture(BufferedImage img, Color imgBgColor, OutputStream outstm, String mime, final StringBuilder warnings) throws IOException { if (img == null || outstm == null || mime == null) { throw new IllegalArgumentException(); } // mimeがパラメータ付きの場合は、パラメータを除去する. int pt = mime.indexOf(';'); if (pt >= 0) { mime = mime.substring(0, pt).trim(); } // サポートしているmimeタイプを検出. Iterator ite = ImageIO.getImageWritersByMIMEType(mime); if (!ite.hasNext()) { throw new IOException("unsupported mime: " + mime); } ImageWriter iw = ite.next(); savePicture(img, imgBgColor, iw, outstm, warnings); outstm.flush(); } protected void savePicture(BufferedImage img, Color imgBgColor, ImageWriter iw, Object output, final StringBuilder warnings) throws IOException { try { iw.addIIOWriteWarningListener(new IIOWriteWarningListener() { public void warningOccurred(ImageWriter source, int imageIndex, String warning) { if (warnings.length() > 0) { warnings.append(System.getProperty("line.separator")); } if (warnings != null) { warnings.append(warning); } logger.log(Level.WARNING, warning); } }); boolean jpeg = false; boolean bmp = false; for (String mime : iw.getOriginatingProvider().getMIMETypes()) { if (mime.contains("image/jpeg") || mime.contains("image/jpg")) { jpeg = true; break; } if (mime.contains("image/bmp") || mime.contains("image/x-bmp") || mime.contains("image/x-windows-bmp")) { bmp = true; break; } } ImageWriteParam iwp = iw.getDefaultWriteParam(); IIOImage ioimg; if (jpeg) { iwp.setCompressionMode(ImageWriteParam.MODE_EXPLICIT); iwp.setCompressionQuality((float) outputOption.getJpegQuality()); // JPEGは透過色をサポートしていないので背景色を設定する. ioimg = new IIOImage(createJpegFormatPicture(img, imgBgColor), null, null); } else if (bmp) { // BMPは透過色をサポートしていないので背景色を設定する. ioimg = new IIOImage(createBMPFormatPicture(img, imgBgColor), null, null); } else if (outputOption.isForceBgColor()) { // 背景色強制 // JPEG, BMP以外(PNGを想定) ioimg = new IIOImage(createOpaquePNGFormatPicture(img, imgBgColor), null, null); } else { // 透過有効のまま // JPEG, BMP以外(PNGを想定) ioimg = new IIOImage(img, null, null); } ImageOutputStream imgstm = ImageIO.createImageOutputStream(output); try { iw.setOutput(imgstm); iw.write(null, ioimg, iwp); } finally { imgstm.close(); } } finally { iw.dispose(); } } /** * ARGB形式から、アルファチャンネルを削除し、かわりに背景色を設定したBGR形式画像を返します.
* JPEG画像として用いることを想定しています.
* @param img 変換するイメージ * @param imgBgColor 背景色 * @return 変換されたイメージ */ public BufferedImage createJpegFormatPicture(BufferedImage img, Color imgBgColor) { if (imgBgColor == null) { imgBgColor = Color.WHITE; } return createFormatPicture(img, imgBgColor, BufferedImage.TYPE_INT_BGR); } /** * ARGB形式から、アルファチャンネルを削除し、かわりに背景色を設定したBGR形式画像を返します.
* BMP画像として用いることを想定しています.
* @param img 変換するイメージ * @param imgBgColor 背景色 * @return 変換されたイメージ */ public BufferedImage createBMPFormatPicture(BufferedImage img, Color imgBgColor) { if (imgBgColor == null) { imgBgColor = Color.WHITE; } return createFormatPicture(img, imgBgColor, BufferedImage.TYPE_3BYTE_BGR); } /** * ARGB形式から、アルファチャンネルを削除し、かわりに背景色を設定したRGB形式画像を返します.
* 背景付きPNG画像として用いることを想定しています.
* @param img 変換するイメージ * @param imgBgColor 背景色 * @return 変換されたイメージ */ public BufferedImage createOpaquePNGFormatPicture(BufferedImage img, Color imgBgColor) { if (imgBgColor == null) { imgBgColor = Color.WHITE; } return createFormatPicture(img, imgBgColor, BufferedImage.TYPE_INT_ARGB); } /** * ARGB形式から、アルファチャンネルを削除し、かわりに背景色を設定したBGR形式画像を返します.
* JPEG画像として用いることを想定しています.
* @param img 変換するイメージ * @param imgBgColor 背景色 * @return 変換されたイメージ */ protected BufferedImage createFormatPicture(BufferedImage img, Color imgBgColor, int type) { if (img == null) { throw new IllegalArgumentException(); } int w = img.getWidth(); int h = img.getHeight(); BufferedImage tmpImg = new BufferedImage(w, h, type); Graphics2D g = tmpImg.createGraphics(); try { g.setRenderingHint( RenderingHints.KEY_ALPHA_INTERPOLATION, RenderingHints.VALUE_ALPHA_INTERPOLATION_QUALITY); if (imgBgColor != null) { g.setColor(imgBgColor); g.fillRect(0, 0, w, h); } g.drawImage(img, 0, 0, w, h, 0, 0, w, h, null); } finally { g.dispose(); } return tmpImg; } } /** * 出力オプションパネル * @author seraphy */ class OutputOptionPanel extends JPanel { private static final long serialVersionUID = 1L; private JSpinner jpegQualitySpinner; private JCheckBox lblZoom; private JSpinner zoomSpinner; private JComboBox zoomAlgoCombo; private JComboBox pictureModeCombo; private JCheckBox checkForceBgColor; public OutputOptionPanel() { this(new OutputOption()); } public OutputOptionPanel(OutputOption outputOption) { Properties strings = LocalizedResourcePropertyLoader.getCachedInstance() .getLocalizedProperties(ImageSaveHelper.STRINGS_RESOURCE); setBorder(BorderFactory.createCompoundBorder( BorderFactory.createEmptyBorder(3, 3, 3, 3), BorderFactory.createTitledBorder( strings.getProperty("ouputOption.caption")))); setLayout(new GridBagLayout()); GridBagConstraints gbc = new GridBagConstraints(); gbc.anchor = GridBagConstraints.EAST; gbc.fill = GridBagConstraints.BOTH; gbc.insets = new Insets(3, 3, 3, 3); gbc.gridheight = 1; gbc.ipadx = 0; gbc.ipady = 0; gbc.weighty = 0; // 左端余白 gbc.gridx = 0; gbc.gridy = 0; gbc.gridwidth = 1; gbc.weightx = 0.; add(Box.createHorizontalStrut(6), gbc); // JPEG gbc.gridx = 0; gbc.gridy = 0; gbc.gridwidth = 3; gbc.weightx = 1.; JLabel lblJpeg = new JLabel( strings.getProperty("outputOption.jpeg.caption")); lblJpeg.setFont(lblJpeg.getFont().deriveFont(Font.BOLD)); add(lblJpeg, gbc); gbc.gridx = 1; gbc.gridy = 1; gbc.gridwidth = 1; gbc.weightx = 0.; add(new JLabel( strings.getProperty("outputOption.jpeg.quality"), JLabel.RIGHT), gbc); SpinnerNumberModel spmodel = new SpinnerNumberModel(100, 10, 100, 1); this.jpegQualitySpinner = new JSpinner(spmodel); gbc.gridx = 2; gbc.gridy = 1; gbc.gridwidth = 1; add(jpegQualitySpinner, gbc); gbc.gridx = 3; gbc.gridy = 1; gbc.gridwidth = 1; add(new JLabel("%"), gbc); // ZOOM gbc.gridx = 0; gbc.gridy = 2; gbc.gridwidth = 3; gbc.weightx = 1.; gbc.insets = new Insets(10, 3, 3, 3); lblZoom = new JCheckBox(strings.getProperty("outputOption.zoom.caption")); lblZoom.setFont(lblJpeg.getFont().deriveFont(Font.BOLD)); add(lblZoom, gbc); gbc.gridx = 1; gbc.gridy = 3; gbc.gridwidth = 1; gbc.weightx = 0.; gbc.insets = new Insets(3, 3, 3, 3); add(new JLabel( strings.getProperty("outputOption.zoom.factor"), JLabel.RIGHT), gbc); SpinnerNumberModel zoomSpModel = new SpinnerNumberModel(100, 20, 800, 1); this.zoomSpinner = new JSpinner(zoomSpModel); gbc.gridx = 2; gbc.gridy = 3; gbc.gridwidth = 1; add(zoomSpinner, gbc); gbc.gridx = 3; gbc.gridy = 3; gbc.gridwidth = 1; add(new JLabel("%"), gbc); gbc.gridx = 1; gbc.gridy = 4; gbc.gridwidth = 1; gbc.weightx = 1.; add(new JLabel( strings.getProperty("outputOption.zoom.renderingMode"), JLabel.RIGHT), gbc); this.zoomAlgoCombo = new JComboBox(ZoomRenderingType.values()); this.zoomAlgoCombo.setRenderer(new LocalizedMessageComboBoxRender(strings)); gbc.gridx = 2; gbc.gridy = 4; gbc.gridwidth = 2; gbc.weightx = 0.; add(zoomAlgoCombo, gbc); // 画像モード gbc.gridx = 0; gbc.gridy = 5; gbc.gridwidth = 3; gbc.insets = new Insets(10, 3, 3, 3); JLabel lblPictureMode = new JLabel( strings.getProperty("outputOption.picture")); lblPictureMode.setFont(lblJpeg.getFont().deriveFont(Font.BOLD)); add(lblPictureMode, gbc); gbc.gridx = 1; gbc.gridy = 6; gbc.gridwidth = 1; gbc.insets = new Insets(3, 3, 3, 3); add(new JLabel( strings.getProperty("outputOption.picture.type"), JLabel.RIGHT), gbc); this.pictureModeCombo = new JComboBox(PictureMode.values()); this.pictureModeCombo.setRenderer( new LocalizedMessageComboBoxRender(strings)); gbc.gridx = 2; gbc.gridy = 6; gbc.gridwidth = 2; gbc.weightx = 1.; add(pictureModeCombo, gbc); gbc.gridx = 1; gbc.gridy = 7; gbc.gridwidth = 3; gbc.weightx = 0.; checkForceBgColor = new JCheckBox( strings.getProperty("outputOption.picture.forceBgColor")); add(checkForceBgColor, gbc); JPanel pnlBgAlpha = new JPanel(new BorderLayout(3, 3)); pnlBgAlpha.add(new JLabel("背景アルファ"), BorderLayout.WEST); SpinnerNumberModel bgAlphaModel = new SpinnerNumberModel(255, 0, 255, 1); JSpinner bgAlphaSpinner = new JSpinner(bgAlphaModel); pnlBgAlpha.add(bgAlphaSpinner, BorderLayout.CENTER); gbc.gridx = 1; gbc.gridy = 8; gbc.gridwidth = 3; gbc.weightx = 0.; add(pnlBgAlpha, gbc); // 余白 gbc.gridx = 0; gbc.gridy = 9; gbc.gridwidth = 4; gbc.weightx = 1.; gbc.weighty = 1.; add(Box.createGlue(), gbc); // update setOutputOption(outputOption); } public void setOutputOption(OutputOption outputOption) { if (outputOption == null) { outputOption = new OutputOption(); } jpegQualitySpinner.setValue((int) (outputOption.getJpegQuality() * 100)); lblZoom.setSelected(outputOption.isEnableZoom()); zoomSpinner.setValue((int) (outputOption.getZoomFactor() * 100)); zoomAlgoCombo.setSelectedItem(outputOption.getZoomRenderingType()); pictureModeCombo.setSelectedItem(outputOption.getPictureMode()); checkForceBgColor.setSelected(outputOption.isForceBgColor()); } public OutputOption getOutputOption() { OutputOption outputOption = new OutputOption(); outputOption.setJpegQuality(((Integer) jpegQualitySpinner.getValue() / 100.)); outputOption.setEnableZoom(lblZoom.isSelected()); outputOption.setZoomFactor(((Integer) zoomSpinner.getValue() / 100.)); outputOption.setZoomRenderingType((ZoomRenderingType) zoomAlgoCombo.getSelectedItem()); outputOption.setPictureMode((PictureMode) pictureModeCombo.getSelectedItem()); outputOption.setForceBgColor(checkForceBgColor.isSelected()); return outputOption; } } CharacterManaJ/src/charactermanaj/graphics/io/OutputOption.java0000644000175000017500000000703412560206305025071 0ustar paulliupaulliupackage charactermanaj.graphics.io; import java.io.Serializable; import charactermanaj.util.LocalizedMessageAware; /** * 出力オプションモデル. * @author seraphy */ public class OutputOption implements Serializable, Cloneable { private static final long serialVersionUID = -879599688492364852L; /** * 拡大縮小時に使用するレンダリングオプション. * @author seraphy */ public enum ZoomRenderingType implements LocalizedMessageAware { NONE, BILINER, BICUBIC; public String getLocalizedResourceId() { return "outputOption.zoomRenderingType." + name(); } } /** * 出力する画像モード. * @author seraphy */ public enum PictureMode implements LocalizedMessageAware { NORMAL, OPAQUE, GRAY, ALPHA; public String getLocalizedResourceId() { return "outputOption.pictureMode." + name(); } } /** * JPEG品質 */ private double jpegQuality = 1.; /** * ズームの使用可否 */ private boolean enableZoom; /** * 拡大率 */ private double zoomFactor = 1.; /** * 拡大縮小に使うアルゴリズム */ private ZoomRenderingType zoomRenderingType = ZoomRenderingType.NONE; /** * 出力画像のタイプ */ private PictureMode pictureMode = PictureMode.NORMAL; /** * 背景色を強制するか? */ private boolean forceBgColor; @Override public OutputOption clone() { try { return (OutputOption) super.clone(); } catch (CloneNotSupportedException ex) { throw new RuntimeException(ex); } } public double getJpegQuality() { return jpegQuality; } public void setJpegQuality(double jpegQuality) { if (jpegQuality < 0.1) { jpegQuality = 0.1; } if (jpegQuality > 1) { jpegQuality = 1.; } this.jpegQuality = jpegQuality; } public double getZoomFactor() { return zoomFactor; } public void setZoomFactor(double zoomFactor) { if (zoomFactor < 0.1) { zoomFactor = 0.1; } if (zoomFactor > 10.) { zoomFactor = 10.; } this.zoomFactor = zoomFactor; } public ZoomRenderingType getZoomRenderingType() { return zoomRenderingType; } public void setZoomRenderingType(ZoomRenderingType zoomRenderingType) { if (zoomRenderingType == null) { zoomRenderingType = ZoomRenderingType.NONE; } this.zoomRenderingType = zoomRenderingType; } public PictureMode getPictureMode() { return pictureMode; } public void setPictureMode(PictureMode pictureMode) { if (pictureMode == null) { pictureMode = PictureMode.NORMAL; } this.pictureMode = pictureMode; } public boolean isForceBgColor() { return forceBgColor; } public void setForceBgColor(boolean forceBgColor) { this.forceBgColor = forceBgColor; } public void setEnableZoom(boolean enableZoom) { this.enableZoom = enableZoom; } public boolean isEnableZoom() { return enableZoom; } /** * 推奨値に変更する. */ public void changeRecommend() { if (zoomFactor > 1.) { zoomRenderingType = ZoomRenderingType.BICUBIC; } else if (zoomFactor < 1.) { zoomRenderingType = ZoomRenderingType.BILINER; } else { zoomRenderingType = ZoomRenderingType.NONE; } } @Override public String toString() { return "(OutputOption:(jpegQuality:" + jpegQuality + ")(enableZoom:" + enableZoom + ")(zoomFactor:" + zoomFactor + ")(renderingType:" + zoomRenderingType + ")(pictureMode:" + pictureMode + ")(forceBgColor:" + forceBgColor + "))"; } }CharacterManaJ/src/charactermanaj/graphics/io/ImageLoaderImpl.java0000644000175000017500000000446712560206305025402 0ustar paulliupaulliupackage charactermanaj.graphics.io; import java.awt.Graphics; import java.awt.image.BufferedImage; import java.io.IOException; import java.io.InputStream; import java.sql.Timestamp; import java.util.logging.Level; import java.util.logging.Logger; import javax.imageio.ImageIO; /** * 画像を読み取ります.
* @author seraphy */ public class ImageLoaderImpl implements ImageLoader { /** * ロガー */ private static final Logger logger = Logger.getLogger(ImageLoaderImpl.class.getName()); /** * 画像リソースからBufferedImageを返します.
* 返される形式はARGBに変換されています.
* @param imageResource 画像リソース * @throws IOException 読み取りに失敗した場合、もしくは画像の形式が不明な場合 */ public LoadedImage load(ImageResource imageResource) throws IOException { if (imageResource == null) { throw new IllegalArgumentException(); } BufferedImage img; InputStream is = imageResource.openStream(); try { img = ImageIO.read(is); } finally { is.close(); } if (img == null) { logger.log(Level.WARNING, "unsuppoted image: " + imageResource); throw new IOException("unsupported image"); } // ARGB形式でなければ変換する. img = convertARGB(img); long lastModified = imageResource.lastModified(); if (logger.isLoggable(Level.FINE)) { logger.log(Level.FINE, "load image: " + imageResource + " ;lastModified=" + new Timestamp(lastModified)); } return new LoadedImage(img, lastModified); } /** * イメージがARGB形式でなければ、ARGB形式に変換して返す.
* そうでなければ、そのまま返す. * @param image イメージ * @return ARGB形式のイメージ */ protected BufferedImage convertARGB(BufferedImage image) { if (image == null) { throw new IllegalArgumentException(); } int typ = image.getType(); if (typ == BufferedImage.TYPE_INT_ARGB) { return image; } // ARGB形式でなければ変換する. BufferedImage img2 = new BufferedImage(image.getWidth(), image.getHeight(), BufferedImage.TYPE_INT_ARGB); Graphics g = img2.getGraphics(); try { g.drawImage(image, 0, 0, null); } finally { g.dispose(); } return img2; } } CharacterManaJ/src/charactermanaj/graphics/io/OutputImageBuilder.java0000644000175000017500000000075512560206305026155 0ustar paulliupaulliupackage charactermanaj.graphics.io; import java.awt.image.BufferedImage; public abstract class OutputImageBuilder { private OutputOption outputOption; public OutputImageBuilder(OutputOption outputOption) { if (outputOption == null) { throw new IllegalArgumentException(); } this.outputOption = outputOption; } public OutputOption getOutputOption() { return outputOption.clone(); } public abstract BufferedImage buildImage(BufferedImage src); } CharacterManaJ/src/charactermanaj/graphics/io/OutputImageBuilderFactory.java0000644000175000017500000000107312560206305027477 0ustar paulliupaulliupackage charactermanaj.graphics.io; import charactermanaj.model.AppConfig; public class OutputImageBuilderFactory { public OutputOption createDefaultOutputOption() { AppConfig appConfig = AppConfig.getInstance(); OutputOption outputOption = new OutputOption(); outputOption.setJpegQuality(appConfig.getCompressionQuality()); return outputOption; } public OutputImageBuilder createOutputImageBuilder(OutputOption outputOption) { if (outputOption == null) { outputOption = createDefaultOutputOption(); } return null; } } CharacterManaJ/src/charactermanaj/graphics/io/LoadedImage.java0000644000175000017500000000211512560206305024526 0ustar paulliupaulliupackage charactermanaj.graphics.io; import java.awt.image.BufferedImage; import java.awt.image.DataBuffer; /** * ロードされたイメージ情報 * @author seraphy */ public final class LoadedImage { private final BufferedImage image; private final long lastModified; private final int imageSize; public LoadedImage(BufferedImage image, long lastModified) { this.image = image; this.lastModified = lastModified; this.imageSize = getBufferSize(image); } public BufferedImage getImage() { return image; } public long getLastModified() { return lastModified; } public int getImageSize() { return imageSize; } /** * 画像バッファのバイト数を求める.
* @param image イメージ * @return バイト数 */ private static int getBufferSize(BufferedImage image) { if (image == null) { return 0; } DataBuffer buff = image.getRaster().getDataBuffer(); int bytes = buff.getSize() * DataBuffer.getDataTypeSize(buff.getDataType()) / 8; return bytes; } } CharacterManaJ/src/charactermanaj/graphics/io/ImagePreviewFileChooser.java0000644000175000017500000000633712560206305027114 0ustar paulliupaulliupackage charactermanaj.graphics.io; import java.awt.Image; import java.awt.Toolkit; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.io.File; import javax.swing.BorderFactory; import javax.swing.JFileChooser; import javax.swing.filechooser.FileFilter; import charactermanaj.Main; import charactermanaj.ui.MiniPictureBox; /** * 画像ファイルを選択するファイルチューザ.
* 選択したファイルの画像のサムネイルを表示する. * @author seraphy */ public class ImagePreviewFileChooser extends JFileChooser { private static final long serialVersionUID = -66951985128705674L; /*** * プレビューパネル */ private MiniPictureBox previewPanel; /** * デフォルトコンストラクタ */ public ImagePreviewFileChooser() { super(); initAccessory(); initFileFilter(); } /** * 初期ディレクトり指定コンストラクタ * @param initDir 初期ディレクトリ */ public ImagePreviewFileChooser(File initDir) { super(initDir); initAccessory(); initFileFilter(); } protected Image getSelectedImage() { return previewPanel.getImage(); } protected void setSelectedImage(Image selectedImage) { previewPanel.setImage(selectedImage); } /** * イメージをロードする.
* ファィルパスがnullまたはファイルを示さないか実在しない場合はnull.
* 既定ではイメージは非同期読み込みとなる.
* @param file ファイルパス * @return イメージ、もしくはnull */ protected Image loadImage(File file) { if (file == null || !file.exists() || !file.isFile()) { return null; } Toolkit tk = Toolkit.getDefaultToolkit(); return tk.createImage(file.getPath()); } protected void initAccessory() { previewPanel = createAccessory(); previewPanel.setVisible(true); setAccessory(previewPanel); addPropertyChangeListener( JFileChooser.SELECTED_FILE_CHANGED_PROPERTY, new PropertyChangeListener() { public void propertyChange(PropertyChangeEvent evt) { previewPanel.setImage(loadImage(getSelectedFile())); } }); } protected MiniPictureBox createAccessory() { MiniPictureBox pictureBox = new MiniPictureBox(); pictureBox.setBorder(BorderFactory.createCompoundBorder( BorderFactory.createEmptyBorder( 0, Main.isLinuxOrMacOSX() ? 5 : 10, 0, Main.isLinuxOrMacOSX() ? 5 : 0), pictureBox.getBorder())); return pictureBox; } protected void initFileFilter() { setAcceptAllFileFilterUsed(false); setFileFilter(createImageFileFilter()); } protected FileFilter createImageFileFilter() { return new FileFilter() { private final String[] acceptExts = {".png", ".jpeg", ".jpg", ".gif", ".bmp"}; @Override public boolean accept(File f) { if (f.isDirectory()) { return true; } String lcName = f.getName().toLowerCase(); for (String acceptExt : acceptExts) { if (lcName.endsWith(acceptExt)) { return true; } } return false; } public String getDescription() { return "Picture(*.jpeg;*.png;*.gif;*.bmp)"; }; }; } } CharacterManaJ/src/charactermanaj/graphics/io/ImageCacheMBean.java0000644000175000017500000000042212560206305025243 0ustar paulliupaulliupackage charactermanaj.graphics.io; public interface ImageCacheMBean { long getReadCount(); long getCacheHitCount(); long getTotalBytes(); long getMaxBytes(); int getTotalCount(); int getInstanceCount(); void reset(); } CharacterManaJ/src/charactermanaj/graphics/io/EmbeddedImageResource.java0000644000175000017500000000445112560206305026544 0ustar paulliupaulliupackage charactermanaj.graphics.io; import java.io.IOException; import java.io.InputStream; import java.io.Serializable; import java.net.URI; import java.net.URISyntaxException; import java.net.URL; import java.util.logging.Level; import java.util.logging.Logger; import charactermanaj.util.ResourceLoader; /** * クラスローダからリソースを読み込むイメージリソース.
* @author seraphy */ public class EmbeddedImageResource extends ResourceLoader implements ImageResource, Serializable { /** * シリアライズバージョン */ private static final long serialVersionUID = 703707046457343373L; /** * ロガー */ private static final Logger logger = Logger.getLogger(EmbeddedImageResource.class.getName()); /** * ファイル */ private String resourceName; public EmbeddedImageResource(String resourceName) { if (resourceName == null) { throw new IllegalArgumentException(); } this.resourceName = resourceName; } public int compareTo(ImageResource o) { return getFullName().compareTo(o.getFullName()); } @Override public int hashCode() { return getFullName().hashCode(); } @Override public boolean equals(Object obj) { if (obj == this) { return true; } if (obj instanceof ImageResource) { ImageResource o = (ImageResource) obj; return getFullName().equals(o.getFullName()); } return false; } public String getFullName() { return resourceName; } public URI getURI() { URL url = getResource(resourceName); if (url != null) { try { return url.toURI(); } catch(URISyntaxException ex) { logger.log(Level.WARNING, "resource name is invalid. " + resourceName, ex); // 何もしない. } } return null; } /** * リソースが実在すれば日付は常に1を返す.
* リソースが存在しなければ0を返す.
*/ public long lastModified() { URL url = getResource(resourceName); if (url == null) { return 1; } return 0; } public InputStream openStream() throws IOException { URL url = getResource(resourceName); if (url == null) { return null; } return url.openStream(); } @Override public String toString() { return getFullName(); } } CharacterManaJ/src/charactermanaj/graphics/io/ImageResource.java0000644000175000017500000000212012560206305025121 0ustar paulliupaulliupackage charactermanaj.graphics.io; import java.io.IOException; import java.io.InputStream; import java.net.URI; /** * 画像リソース * @author seraphy */ public interface ImageResource extends Comparable { /** * 画像リソースをストリームで取得します. * @return 入力ストリーム * @throws IOException 開けなかった場合 */ InputStream openStream() throws IOException; /** * 更新日時 * @return 更新日時を示すエポックタイム */ long lastModified(); /** * 同値用ハッシュ * @return ハッシュ */ int hashCode(); /** * 同値判定 * @param obj 比較対象 * @return 同一であればtrue */ boolean equals(Object obj); /** * ソート用比較 */ int compareTo(ImageResource o); /** * リソース位置を示すフルネーム * @return リソース位置を示すフルネーム */ String getFullName(); /** * リソース位置を示すURI * @return リソース位置を示すURI */ URI getURI(); } CharacterManaJ/src/charactermanaj/graphics/ColorConvertedImageLoader.java0000644000175000017500000000214612560206305027012 0ustar paulliupaulliupackage charactermanaj.graphics; import java.io.IOException; import charactermanaj.graphics.colormodel.ColorModel; import charactermanaj.graphics.filters.ColorConvertParameter; import charactermanaj.graphics.io.ImageResource; import charactermanaj.graphics.io.LoadedImage; /** * 画像リソースを読み込み、カラー変換した画像を返す. * @author seraphy * */ public interface ColorConvertedImageLoader { /** * 画像リソースをロードし色変換した結果のBufferedImageを返します.
* 返される形式はARGBに変換されています.
* * @param file * 画像リソース * @param colorConvParam * 色変換パラメータ、nullの場合はデフォルト * @param colorModel * カラーモデル * @return 画像イメージ * @throws IOException * 形式が不明であるか、ファィルがないか読み取りに失敗した場合 */ LoadedImage load(ImageResource file, ColorConvertParameter colorConvParam, ColorModel colorModel) throws IOException; } CharacterManaJ/src/charactermanaj/graphics/AsyncImageBuilder.java0000644000175000017500000001125512560206305025320 0ustar paulliupaulliupackage charactermanaj.graphics; import java.util.logging.Level; import java.util.logging.Logger; /** * 各パーツ情報をもとに非同期にイメージを合成する * @author seraphy */ public class AsyncImageBuilder extends ImageBuilder implements Runnable { /** * ロガー */ private static final Logger logger = Logger.getLogger(AsyncImageBuilder.class.getName()); /** * 非同期にイメージを構築するためのジョブ定義.
* リクエストを受け付けたことを示すイベントおよび、リクエストが放棄されたことを示すイベントを受け取ることができる.
* @author seraphy * */ public interface AsyncImageBuildJob extends ImageBuildJob { /** * リクエストを受け付けた場合に呼び出される.
* @param ticket このイメージビルダでリクエストを受け付けた通し番号 */ void onQueueing(long ticket); /** * リクエストを処理するまえに破棄された場合に呼び出される.
*/ void onAbandoned(); } /** * 同期オブジェクト */ private final Object lock = new Object(); /** * チケットのシリアルナンバー.
* リクエストがあるごとにインクリメントされる.
*/ private long ticketSerialNum = 0; /** * リクエストされているジョブ、なければnull */ private ImageBuildJob requestJob; /** * 停止フラグ(volatile) */ private volatile boolean stopFlag; /** * スレッド */ private Thread thread; /** * イメージローダを指定して構築する. * @param imageLoader イメージローダー */ public AsyncImageBuilder(ColorConvertedImageCachedLoader imageLoader) { super(imageLoader); thread = new Thread(this); thread.setDaemon(true); } /** * スレッドの実行部. */ public void run() { logger.log(Level.FINE, "AsyncImageBuilder thread started."); // 停止フラグがたてられるまで繰り返す. while (!stopFlag) { try { ImageBuildJob job; synchronized (lock) { while (!stopFlag && requestJob == null) { // ジョブリクエストがくるまで待機 lock.wait(1000); } if (stopFlag) { break; } // ジョブを一旦ローカル変数に保存 job = requestJob; // ジョブの受け付けを再開. requestJob = null; lock.notifyAll(); } // リクエストを処理する. AsyncImageBuilder.super.requestJob(job); } catch (InterruptedException ex) { logger.log(Level.FINE, "AsyncImageBuilder thead interrupted."); // 割り込みされた場合、単にループを再開する. } catch (Exception ex) { logger.log(Level.SEVERE, "AsyncImageBuilder failed.", ex); // ジョブ合成中の予期せぬ例外はログに記録するのみで // スレッドそのものは停止させない. // (Error系は、たぶんアプリ自身が続行不能な障害なので停止する.) } } logger.log(Level.FINE, "AsyncImageBuilder thread stopped."); } /** * イメージ作成ジョブをリクエストする.
* イメージ作成ジョブは非同期に実行される.
* 処理がはじまる前に新しいリクエストで上書きされた場合、前のリクエストは単に捨てられる.
*/ @Override public boolean requestJob(ImageBuildJob imageSource) { synchronized (lock) { // 現在処理待ちのリクエストがあれば、新しいリクエストで上書きする. if (this.requestJob != null && this.requestJob instanceof AsyncImageBuildJob) { ((AsyncImageBuildJob) this.requestJob).onAbandoned(); } // リクエストをセットして待機中のスレッドに通知を出す. this.requestJob = imageSource; if (imageSource != null && imageSource instanceof AsyncImageBuildJob) { ((AsyncImageBuildJob) imageSource).onQueueing(++ticketSerialNum); } lock.notifyAll(); } return false; } /** * スレッドが生きているか? * @return 生きていればtrue */ public boolean isAlive() { return thread.isAlive(); } /** * スレッドを開始する. */ public void start() { if (!thread.isAlive()) { stopFlag = false; thread.start(); } } /** * スレッドを停止する. */ public void stop() { if (thread.isAlive()) { stopFlag = true; thread.interrupt(); try { // スレッドの停止を待機する. thread.join(); } catch (InterruptedException ex) { // do nothing. } } } } CharacterManaJ/src/charactermanaj/graphics/colormodel/0000755000175000017500000000000012560206305023261 5ustar paulliupaulliuCharacterManaJ/src/charactermanaj/graphics/colormodel/HSYColorModel.java0000644000175000017500000001027612560206305026555 0ustar paulliupaulliupackage charactermanaj.graphics.colormodel; /** * HSYカラーモデルの計算.
* "gununuの日記"さんのところのC++計算ルーチンをJava用に書き直したもの.
* * @author seraphy * @see http://d.hatena.ne.jp/gununu/20090721/1248171222 */ public class HSYColorModel implements ColorModel { /** * 輝度計算用の係数R */ private static final float IR = 0.298912f; /** * 輝度計算用の係数G */ private static final float IG = 0.586611f; /** * 輝度計算用の係数B */ private static final float IB = 0.114478f; private static final String[] ITEM_TITLES = { "colorModel.HSY.hue", "colorModel.HSY.saturation", "colorModel.HSY.luminance", }; public String getTitle() { return "colorModel.HSY.title"; } public String getItemTitle(int index) { return ITEM_TITLES[index]; } /** * RGBから輝度を求める. * * @param r * @param g * @param b * @return 輝度 */ public static int getGrayscale(int r, int g, int b) { return (int) (IR * r + IG * g + IB * b) & 0xff; } /** * RGBからHSYに変換する.
* * @param rr * 0-255範囲のR * @param gg * 0-255範囲のG * @param bb * 0-255範囲のB * @param hsyVals * H色相, S彩度, Y輝度を0-1の実数表現した配列(書き込み先) * @return 引数と同じhsyvalsを返す. */ public float[] RGBtoHSV(int r, int g, int b, float[] hsyVals) { if (hsyVals == null || hsyVals.length < 3) { throw new IllegalArgumentException(); } int max = Math.max(Math.max(r, g), b); int min = Math.min(Math.min(r, g), b); float saturation = (max - min) / 255.f; float rr = r / 255.f; float gg = g / 255.f; float bb = b / 255.f; float lum = (IR * rr + IG * gg + IB * bb); if (lum > 1.f) { lum = 1.f; } float hue; if (saturation == 0) { hue = 0; } else { if (max == r) { hue = (gg - bb) / saturation * 60f; } else if (max == g) { hue = (bb - rr) / saturation * 60f + 120f; } else { hue = (rr - gg) / saturation * 60f + 240f; } if (hue < 0) { hue += 360f; } hue /= 360f; } hsyVals[0] = hue; hsyVals[1] = saturation; hsyVals[2] = lum; return hsyVals; } /** * HSYからRGBに変換する.
* * @param hue * 0-1範囲の色相 * @param sat * 0-1範囲の彩度 * @param lum * 0-1範囲の輝度 * @return RGB値 * @see http://d.hatena.ne.jp/gununu/20090721/1248171222 */ public int HSVtoRGB(float hue, float sat, float lum) { hue = (hue - (float) Math.floor(hue)); if (sat < 0) { sat = 0f; } else if (sat > 1f) { sat = 1f; } if (lum < 0) { lum = 0f; } else if (lum > 1f) { lum = 1f; } float r, g, b; if (hue <= 1 / 6.0f) { float h = hue * 6; r = (1 - IR - IG * h); g = (-IR + (1 - IG) * h); b = (-IR - IG * h); } else if (hue <= 3 / 6.0f) { float h = (hue - 1 / 3.0f) * 6; if (hue > 2 / 6.0f) { r = (-IG - IB * h); g = (1 - IG - IB * h); b = (-IG + (1 - IB) * h); } else { r = (-IG + (IR - 1) * h); g = (1 - IG + IR * h); b = (-IG + IR * h); } } else if (hue <= 5 / 6.0f) { float h = (hue - 2 / 3.0f) * 6; if (hue > 4 / 6.0f) { r = (-IB + (1 - IR) * h); g = (-IB - IR * h); b = (1 - IB - IR * h); } else { r = (-IB + IG * h); g = (-IB + (IG - 1) * h); b = (1 - IB + IG * h); } } else { float h = (hue - 1) * 6; r = (1 - IR + IB * h); g = (-IR + IB * h); b = (-IR + (IB - 1) * h); } r *= sat; g *= sat; b *= sat; float ma = Math.max(r, Math.max(g, b)); float mi = Math.min(r, Math.min(g, b)); float x = 1; float t; if (ma + lum > 1f) { t = (1f - lum) / ma; x = t; } if (mi + lum < 0) { t = lum / (-mi); if (t < x) { x = t; } } int red = (int) ((lum + r * x) * 255) & 0xff; int green = (int) ((lum + g * x) * 255) & 0xff; int blue = (int) ((lum + b * x) * 255) & 0xff; return 0xff000000 | red << 16 | green << 8 | blue; } } CharacterManaJ/src/charactermanaj/graphics/colormodel/ColorModel.java0000644000175000017500000000135512560206305026167 0ustar paulliupaulliupackage charactermanaj.graphics.colormodel; /** * カラーモデル. * * @author seraphy */ public interface ColorModel { /** * カラーモデルの説明 * * @return 説明 */ String getTitle(); /** * 各項目の説明 * * @param index * インデックス(0-2) * @return 説明 */ String getItemTitle(int index); /** * RGBからHSVに変換する. * * @param r * @param g * @param b * @param hsvVals * @return */ float[] RGBtoHSV(int r, int g, int b, float[] hsvVals); /** * HSVからRGBに変換する. * * @param hue * @param sat * @param lum * @return */ int HSVtoRGB(float hue, float sat, float lum); } CharacterManaJ/src/charactermanaj/graphics/colormodel/HSBColorModel.java0000644000175000017500000000146112560206305026522 0ustar paulliupaulliupackage charactermanaj.graphics.colormodel; import java.awt.Color; /* * Java標準のHSBカラーモデル.
*/ public class HSBColorModel implements ColorModel { private static final String[] ITEM_TITLES = { "colorModel.HSB.hue", "colorModel.HSB.saturation", "colorModel.HSB.brightness", }; public String getTitle() { return "colorModel.HSB.title"; } public String getItemTitle(int index) { return ITEM_TITLES[index]; } /** * RGBからHSBに変換する. */ public float[] RGBtoHSV(int r, int g, int b, float[] hsvVals) { return Color.RGBtoHSB(r, g, b, hsvVals); } /** * HSBからRGBに変換する. */ public int HSVtoRGB(float hue, float saturation, float brightness) { return Color.HSBtoRGB(hue, saturation, brightness); } } CharacterManaJ/src/charactermanaj/graphics/colormodel/ColorModels.java0000644000175000017500000000261612560206305026353 0ustar paulliupaulliupackage charactermanaj.graphics.colormodel; public enum ColorModels implements ColorModel { /** * HSB(色相・彩度・明度) */ HSB(new HSBColorModel()), /** * HSY(色相・彩度・輝度) */ HSY(new HSYColorModel()); /** * デフォルトのカラーモデル.
*/ public static ColorModels DEFAULT = HSB; /** * カラーモデル名からカラーモデルを取得する.
* nullまたは空文字、または該当がない場合はデフォルトを採用する.
* * @param colorModelName * カラーモデル名 * @return カラーモデル */ public static ColorModels safeValueOf(String colorModelName) { try { if (colorModelName != null && colorModelName.length() > 0) { return valueOf(colorModelName); } } catch (RuntimeException ex) { // 何もしない. } return DEFAULT; } private final ColorModel colorModel; ColorModels(ColorModel colorModel) { this.colorModel = colorModel; } public String getTitle() { return colorModel.getTitle(); } public String getItemTitle(int index) { return colorModel.getItemTitle(index); } public int HSVtoRGB(float hue, float sat, float lum) { return colorModel.HSVtoRGB(hue, sat, lum); } public float[] RGBtoHSV(int r, int g, int b, float[] hsvVals) { return colorModel.RGBtoHSV(r, g, b, hsvVals); } } CharacterManaJ/src/charactermanaj/Main.java0000644000175000017500000003407612560206305021063 0ustar paulliupaulliupackage charactermanaj; import java.awt.Font; import java.awt.GraphicsEnvironment; import java.io.File; import java.lang.Thread.UncaughtExceptionHandler; import java.lang.reflect.Method; import java.util.Enumeration; import java.util.HashSet; import java.util.Locale; import java.util.Properties; import java.util.TreeSet; import java.util.logging.Level; import java.util.logging.Logger; import javax.management.JMException; import javax.swing.JOptionPane; import javax.swing.SwingUtilities; import javax.swing.UIManager; import javax.swing.plaf.FontUIResource; import charactermanaj.clipboardSupport.ImageSelection; import charactermanaj.graphics.io.ImageCacheMBeanImpl; import charactermanaj.model.AppConfig; import charactermanaj.model.util.StartupSupport; import charactermanaj.ui.MainFrame; import charactermanaj.ui.ProfileListManager; import charactermanaj.ui.SelectCharatersDirDialog; import charactermanaj.util.AWTExceptionLoggingHandler; import charactermanaj.util.ApplicationLoggerConfigurator; import charactermanaj.util.ConfigurationDirUtilities; import charactermanaj.util.DirectoryConfig; import charactermanaj.util.ErrorMessageHelper; import charactermanaj.util.JavaVersionUtils; import charactermanaj.util.LocalizedResourcePropertyLoader; /** * エントリポイント用クラス * * @author seraphy */ public final class Main implements Runnable { /** * ロガー.
*/ private static final Logger logger = Logger.getLogger(Main.class.getName()); /** * Mac OS Xであるか? */ private static final boolean isMacOSX; /** * Linuxであるか? */ private static final boolean isLinux; /** * クラスイニシャライザ.
* 実行環境に関する定数を取得・設定する.
*/ static { // Mac OS Xでの実行判定 // システムプロパティos.nameは、すべてのJVM実装に存在する. // 基本ディレクトリの位置の決定に使うため、 // なによりも、まず、これを判定しないとダメ.(順序が重要) String lcOS = System.getProperty("os.name").toLowerCase(); isMacOSX = lcOS.startsWith("mac os x"); isLinux = lcOS.indexOf("linux") >= 0; } /** * ロガーの初期化.
* 失敗しても継続する.
*/ private static void initLogger() { try { // ロガーの準備 // ローカルファイルシステム上のユーザ定義ディレクトリから // ログの設定を読み取る.(OSにより、設定ファイルの位置が異なることに注意) ApplicationLoggerConfigurator.configure(); if (JavaVersionUtils.getJavaVersion() >= 1.7) { // java7以降は、sun.awt.exception.handlerが使えないので、 // EDTスレッドで未処理例外ハンドラを明示的に設定する. final AWTExceptionLoggingHandler logHandler = new AWTExceptionLoggingHandler(); SwingUtilities.invokeLater(new Runnable() { public void run() { final UncaughtExceptionHandler handler = Thread .getDefaultUncaughtExceptionHandler(); Thread.setDefaultUncaughtExceptionHandler(new UncaughtExceptionHandler() { public void uncaughtException(Thread t, Throwable ex) { logHandler.handle(ex); if (handler != null) { handler.uncaughtException(t, ex); } } }); } }); } else { // SwingのEDT内の例外ハンドラの設定 (ロギングするだけ) // (ただし、unofficial trickである.) System.setProperty("sun.awt.exception.handler", AWTExceptionLoggingHandler.class.getName()); } } catch (Throwable ex) { // ロガーの準備に失敗した場合はロガーがないかもなので // コンソールに出力する. ex.printStackTrace(); logger.log(Level.SEVERE, "logger initiation failed. " + ex, ex); } } /** * UIをセットアップする. * * @throws Exception * いろいろな失敗 */ private static void setupUIManager(AppConfig appConfig) throws Exception { // System.setProperty("swing.aatext", "true"); // System.setProperty("awt.useSystemAAFontSettings", "on"); if (isMacOSX()) { // MacOSXであれば、スクリーンメニューを有効化 System.setProperty("apple.laf.useScreenMenuBar", "true"); System.setProperty( "com.apple.mrj.application.apple.menu.about.name", "CharacterManaJ"); // Java7以降であればノーマライズをセットアップする. if (JavaVersionUtils.getJavaVersion() >= 1.7) { charactermanaj.util.FileNameNormalizer.setupNFCNormalizer(); } } // 実行プラットフォームのネイティブな外観にする. UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName()); // JSpliderのvalueを非表示 (GTKではデフォルトで有効のため) UIManager.put("Slider.paintValue", Boolean.FALSE); // 優先するフォントファミリ中の実在するフォントファミリのセット(大文字小文字の区別なし) TreeSet availablePriorityFontSets = new TreeSet( String.CASE_INSENSITIVE_ORDER); // 少なくともメニューが表示できるようなフォントを選択する Properties strings = LocalizedResourcePropertyLoader.getCachedInstance() .getLocalizedProperties("menu/menu"); HashSet useChars = new HashSet(); Enumeration enmStrings = strings.propertyNames(); while (enmStrings.hasMoreElements()) { String propertyName = (String) enmStrings.nextElement(); String propertyValue = strings.getProperty(propertyName); for (char ch : propertyValue.toCharArray()) { useChars.add(ch); } } // 優先するフォントファミリの実在チェックと、もっとも優先されるフォントファミリの確定 String selectedFontFamily = null; String fontPriorityStr = appConfig.getFontPriority(); if (fontPriorityStr.trim().length() > 0) { String[] fontPriority = fontPriorityStr.split(","); for (String availableFontFamily : GraphicsEnvironment .getLocalGraphicsEnvironment().getAvailableFontFamilyNames(Locale.ENGLISH)) { for (String fontFamily : fontPriority) { fontFamily = fontFamily.trim(); if (fontFamily.length() > 0) { if (availableFontFamily.equalsIgnoreCase(fontFamily)) { // 見つかった実在フォントが、現在のロケールのメニューを正しく表示できるか? Font font = Font.decode(availableFontFamily); logger.log(Level.INFO, "実在するフォントの確認:" + availableFontFamily); boolean canDisplay = false; for (char ch : useChars) { canDisplay = font.canDisplay(ch); if (!canDisplay) { logger.log(Level.INFO, "このフォントはメニュー表示に使用できません: " + selectedFontFamily + "/ch=" + ch); break; } } if (canDisplay) { if (selectedFontFamily == null) { // 最初に見つかったメニューを表示可能な優先フォント selectedFontFamily = availableFontFamily; } // メニューを表示可能なフォントのみ候補に入れる availablePriorityFontSets.add(fontFamily); } } } } } if (selectedFontFamily == null) { // フォールバック用フォントとして「Dialog」を用いる. // 仮想フォントファミリである「Dialog」は日本語も表示可能である. selectedFontFamily = "Dialog"; } } // デフォルトのフォントサイズ、0以下の場合はシステム標準のまま int defFontSize = appConfig.getDefaultFontSize(); // UIデフォルトのフォント設定で、優先フォント以外のフォントファミリが指定されているものを // すべて最優先フォントファミリに設定する. // また、設定されたフォントサイズが0よりも大きければ、そのサイズに設定する. for (java.util.Map.Entry entry : UIManager.getDefaults() .entrySet()) { Object key = entry.getKey(); Object val = UIManager.get(key); if (val instanceof FontUIResource) { FontUIResource fontUIResource = (FontUIResource) val; int fontSize = fontUIResource.getSize(); String fontFamily = fontUIResource.getFamily(); if (defFontSize > 0) { fontSize = defFontSize; } if (selectedFontFamily != null && !availablePriorityFontSets.contains(fontFamily)) { // 現在のデフォルトUIに指定された優先フォント以外が設定されており、 // 且つ、優先フォントの指定があれば、優先フォントに差し替える. if (logger.isLoggable(Level.FINE)) { logger.log(Level.FINE, "UIDefaultFont: " + key + "= " + fontFamily + " -> " + selectedFontFamily); } fontFamily = selectedFontFamily; } fontUIResource = new FontUIResource(fontFamily, fontUIResource.getStyle(), fontSize); UIManager.put(entry.getKey(), fontUIResource); } } } /** * 初期処理およびメインフレームを構築する.
* SwingのUIスレッドで実行される.
*/ public void run() { try { // アプリケーション設定の読み込み AppConfig appConfig = AppConfig.getInstance(); appConfig.loadConfig(); // UIManagerのセットアップ. try { setupUIManager(appConfig); } catch (Exception ex) { // UIManagerの設定に失敗した場合はログに書いて継続する. ex.printStackTrace(); logger.log(Level.WARNING, "UIManager setup failed.", ex); } // クリップボードサポートの設定 if (!ImageSelection.setupSystemFlavorMap()) { logger.log(Level.WARNING, "failed to set the clipboard-support."); } // LANG, またはLC_CTYPEが設定されていない場合はエラーを表示する // OSXのJava7(Oracle)を実行する場合、環境変数LANGまたはLC_CTYPEに正しくファイル名の文字コードが設定されていないと // ファイル名を正しく取り扱えず文字化けするため、実行前に確認し警告を表示する。 // ただし、この挙動はJava7u60では修正されているので、それ以降であれば除外する. int[] versions = JavaVersionUtils.getJavaVersions(); if (isMacOSX() && (versions[0] == 1 && versions[1] == 7 && versions[3] < 60)) { String lang = System.getenv("LANG"); String lcctype = System.getenv("LC_CTYPE"); if ((lang == null || lang.trim().length() == 0) && (lcctype == null || lcctype.trim().length() == 0)) { JOptionPane .showMessageDialog( null, "\"LANG\" or \"LC_CTYPE\" environment variable must be set.", "Configuration Error", JOptionPane.ERROR_MESSAGE); } } // 起動時のシステムプロパティでキャラクターディレクトリが指定されていて実在すれば、それを優先する. File currentCharacterDir = null; String charactersDir = System.getProperty("charactersDir"); if (charactersDir != null && charactersDir.length() > 0) { File charsDir = new File(charactersDir); if (charsDir.exists() && charsDir.isDirectory()) { currentCharacterDir = charsDir; } } if (currentCharacterDir == null) { // キャラクターセットディレクトリの選択 File defaultCharacterDir = ConfigurationDirUtilities .getDefaultCharactersDir(); currentCharacterDir = SelectCharatersDirDialog .getCharacterDir(defaultCharacterDir); if (currentCharacterDir == null) { // キャンセルされたので終了する. logger.info("luncher canceled."); return; } } // キャラクターデータフォルダの設定 DirectoryConfig.getInstance().setCharactersDir(currentCharacterDir); // スタートアップ時の初期化 StartupSupport.getInstance().doStartup(); // デフォルトのプロファイルを開く. // (最後に使ったプロファイルがあれば、それが開かれる.) MainFrame mainFrame = ProfileListManager.openDefaultProfile(); if (isMacOSX()) { try { // MacOSXであればスクリーンメニューからのイベントをハンドルできるようにする. // OSXにしか存在しないクラスを利用するためリフレクションとしている. // ただしJDKによっては、Apple Java Extensionsがないことも予想されるので、 // その場合はエラーにしない。 Class clz = Class .forName("charactermanaj.ui.MainFramePartialForMacOSX"); Method mtd = clz.getMethod("setupScreenMenu", MainFrame.class); mtd.invoke(null, mainFrame); } catch (Throwable ex) { logger.log(Level.CONFIG, "The Apple Java Extensions is not found.", ex); } } // 表示(および位置あわせ) mainFrame.showMainFrame(); } catch (Throwable ex) { // なんらかの致命的な初期化エラーがあった場合、ログとコンソールに表示 // ダイアログが表示されるかどうかは状況次第. ex.printStackTrace(); logger.log(Level.SEVERE, "Application initiation failed.", ex); ErrorMessageHelper.showErrorDialog(null, ex); // メインフレームを破棄します. MainFrame.closeAllProfiles(); } } /** * エントリポイント.
* 最初のメインフレームを開いたときにMac OS Xであればスクリーンメニューの登録も行う.
* * @param args * 引数(未使用) */ public static void main(String[] args) { // ロガー等の初期化 initLogger(); // MBeanのセットアップ try { ImageCacheMBeanImpl.setupMBean(); } catch (JMException ex) { // 失敗しても無視して継続する. logger.log(Level.SEVERE, ex.getMessage(), ex); } // フレームの生成等は、SwingのEDTで実行する. SwingUtilities.invokeLater(new Main()); } /** * Mac OS Xで動作しているか? * * @return Max OS X上であればtrue */ public static boolean isMacOSX() { return isMacOSX; } /** * Mac OS X、もしくはlinuxで動作しているか? * * @return Mac OS X、もしくはlinuxで動作していればtrue */ public static boolean isLinuxOrMacOSX() { return isLinux || isMacOSX; } } CharacterManaJ/src/charactermanaj/ui/0000755000175000017500000000000012560206305017737 5ustar paulliupaulliuCharacterManaJ/src/charactermanaj/ui/AboutBox.java0000644000175000017500000002666312560206305022342 0ustar paulliupaulliupackage charactermanaj.ui; import java.awt.BorderLayout; import java.awt.Dimension; import java.awt.event.ActionEvent; import java.io.File; import java.io.FileOutputStream; import java.io.OutputStreamWriter; import java.io.StringReader; import java.io.Writer; import java.net.URL; import java.nio.charset.Charset; import java.text.DecimalFormat; import java.util.Enumeration; import java.util.Map; import java.util.Properties; import java.util.TreeMap; import java.util.logging.Level; import java.util.logging.Logger; import javax.swing.AbstractAction; import javax.swing.BorderFactory; import javax.swing.JButton; import javax.swing.JEditorPane; import javax.swing.JFileChooser; import javax.swing.JFrame; import javax.swing.JOptionPane; import javax.swing.JPanel; import javax.swing.JScrollPane; import javax.swing.JTabbedPane; import javax.swing.event.ChangeEvent; import javax.swing.event.ChangeListener; import javax.swing.event.HyperlinkEvent; import javax.swing.event.HyperlinkListener; import javax.swing.text.Document; import javax.swing.text.EditorKit; import charactermanaj.model.AppConfig; import charactermanaj.util.DesktopUtilities; import charactermanaj.util.DirectoryConfig; import charactermanaj.util.ErrorMessageHelper; import charactermanaj.util.LocalizedTextResource; import charactermanaj.util.SystemUtil; /** * Aboutボックスを表示する. * * @author seraphy */ public class AboutBox { /** * ロガー */ private static Logger logger = Logger.getLogger(AboutBox.class.getName()); private JFrame parent; public AboutBox(JFrame parent) { if (parent == null) { throw new IllegalArgumentException(); } this.parent = parent; } /** * Aboutボックスを表示する. */ public void showAboutBox() { final JTabbedPane tabs = new JTabbedPane(); tabs.setPreferredSize(new Dimension(500, 400)); final JPanel aboutPanel = createAboutPanel(); final JSysInfoPanel sysInfoPanel = new JSysInfoPanel() { private static final long serialVersionUID = 1L; @Override protected void onGc() { super.onGc(); setText(getSysInfoText()); } }; tabs.addTab("About", aboutPanel); tabs.addTab("System", sysInfoPanel); tabs.addChangeListener(new ChangeListener() { public void stateChanged(ChangeEvent e) { if (tabs.getSelectedIndex() == 1) { sysInfoPanel.setText(getSysInfoText()); } } }); JOptionPane.showMessageDialog(parent, tabs, "About", JOptionPane.INFORMATION_MESSAGE); } protected JPanel createAboutPanel() { LocalizedTextResource textResource = new LocalizedTextResource() { @Override protected URL getResource(String resourceName) { return getClass().getClassLoader().getResource(resourceName); } }; String message = textResource.getText("appinfo/about.html", Charset.forName("UTF-8")); AppConfig appConfig = AppConfig.getInstance(); String versionInfo = appConfig.getImplementationVersion(); String specificationVersionInfo = appConfig.getSpecificationVersion(); message = message.replace("@@IMPLEMENTS-VERSIONINFO@@", versionInfo); message = message.replace("@@SPECIFICATION-VERSIONINFO@@", specificationVersionInfo); JPanel aboutPanel = new JPanel(new BorderLayout()); JEditorPane editorPane = new JEditorPane(); editorPane.addHyperlinkListener(new HyperlinkListener() { public void hyperlinkUpdate(HyperlinkEvent e) { if (e.getEventType().equals(HyperlinkEvent.EventType.ACTIVATED)) { URL url = e.getURL(); if (url != null) { try { if (!DesktopUtilities.browse(url.toURI())) { JOptionPane.showMessageDialog(parent, url.toString()); } } catch (Exception ex) { ErrorMessageHelper.showErrorDialog(parent, ex); } } } } }); editorPane.setEditable(false); editorPane.putClientProperty(JEditorPane.HONOR_DISPLAY_PROPERTIES, Boolean.TRUE); editorPane.setContentType("text/html"); // HTML上のcharsetの指定を無視する. Document doc = editorPane.getDocument(); doc.putProperty("IgnoreCharsetDirective", Boolean.TRUE); // editorPane.setText(message); // HTML上のcontent-typeを無視する設定はread時のみ有効のようなのでreadを使う. EditorKit editorKit = editorPane.getEditorKit(); try { StringReader rd = new StringReader(message); try { editorKit.read(rd, doc, 0); } finally { rd.close(); } } catch (Exception ex) { logger.log(Level.SEVERE, ex.toString(), ex); } editorPane.setSelectionStart(0); editorPane.setSelectionEnd(0); aboutPanel.add(new JScrollPane(editorPane), BorderLayout.CENTER); return aboutPanel; } /** * システム情報を取得してHTML形式の文字列として返す.
* ランタイム情報、システムプロパティ情報、環境変数情報を取得する.
* * @return システム情報のHTML文字列 */ protected String getSysInfoText() { // ランタイム情報の取得 long freeMem, totalMem, maxMem; Runtime rt = Runtime.getRuntime(); totalMem = rt.totalMemory() / 1024; freeMem = rt.freeMemory() / 1024; maxMem = rt.maxMemory() / 1024; DecimalFormat decimalFmt = new DecimalFormat("#,###,##0"); StringBuilder buf = new StringBuilder(); buf.append(""); buf.append("

Runtime Information

"); buf.append(""); buf.append(""); buf.append(""); buf.append(""); buf.append("
Max Memory:" + decimalFmt.format(maxMem) + " KiB
Total Memory:" + decimalFmt.format(totalMem) + " KiB
Free Memory:" + decimalFmt.format(freeMem) + " KiB
"); // キャラクターデータベースの取得 DirectoryConfig dirConfig = DirectoryConfig.getInstance(); String charactersDir = null; try { charactersDir = dirConfig.getCharactersDir().toString(); } catch (RuntimeException ex) { charactersDir = "**INVALID**"; } buf.append("

Character Database

"); buf.append(""); buf.append(""); buf.append(""); buf.append("
Location" + escape(charactersDir) + "
"); // サポートしているエンコーディングの列挙 buf.append("

Available Charsets

"); Charset defaultCharset = Charset.defaultCharset(); StringBuilder bufChars = new StringBuilder(); boolean foundWin31j = false; bufChars.append(""); for (Map.Entry entry : Charset.availableCharsets().entrySet()) { String name = entry.getKey(); Charset charset = entry.getValue(); boolean isDef = charset.equals(defaultCharset); boolean win31j = name.toLowerCase().equals("windows-31j"); StringBuilder aliasBuf = new StringBuilder(); for (String alias : charset.aliases()) { if (aliasBuf.length() > 0) { aliasBuf.append(", "); } aliasBuf.append(alias); if (alias.toLowerCase().equals("cswindows31j")) { win31j = true; } } foundWin31j = foundWin31j || win31j; bufChars.append(""); } bufChars.append("
"); if (isDef || win31j) { bufChars.append(""); } if (win31j) { bufChars.append(""); } bufChars.append(name); if (win31j) { bufChars.append(""); } if (isDef || win31j) { bufChars.append("*"); } bufChars.append("" + aliasBuf.toString() + "
"); if (!foundWin31j) { buf.append("

This system is not supporting Japanese.

"); } buf.append(bufChars.toString()); // システムプロパティの取得 buf.append("

System Properties

"); try { Properties sysprops = System.getProperties(); Enumeration enmKey = sysprops.keys(); TreeMap propMap = new TreeMap(); // プロパティキーのアルファベッド順にソート while (enmKey.hasMoreElements()) { String key = (String) enmKey.nextElement(); String value = sysprops.getProperty(key); propMap.put(key, value == null ? "null" : value); } for (Map.Entry entry : propMap.entrySet()) { buf.append(""); buf.append(""); buf.append(""); } buf.append("
" + escape(entry.getKey()) + "" + escape(entry.getValue()) + "
"); } catch (Exception ex) { buf.append(escape(ex.toString())); } // 環境変数の取得 buf.append("

System Environments

"); try { TreeMap envMap = new TreeMap(); // 環境変数名のアルファベット順にソート envMap.putAll(System.getenv()); buf.append(""); for (Map.Entry entry : envMap.entrySet()) { buf.append(""); buf.append(""); buf.append(""); } buf.append("
" + escape(entry.getKey()) + "" + escape(entry.getValue()) + "
"); } catch (Exception ex) { buf.append(escape(ex.toString())); } // HTMLとして文字列を返す. buf.append(""); return buf.toString(); } protected String escape(String value) { if (value == null) { return null; } value = value.replace("&", "&"); value = value.replace("<", "<"); value = value.replace(">", ">"); return value; } } class JSysInfoPanel extends JPanel { private static final long serialVersionUID = 1L; private JEditorPane editorPane; public JSysInfoPanel() { super(new BorderLayout()); editorPane = new JEditorPane(); editorPane.setEditable(false); editorPane.putClientProperty(JEditorPane.HONOR_DISPLAY_PROPERTIES, Boolean.TRUE); editorPane.setContentType("text/html"); editorPane.setText(""); JButton btnSave = new JButton(new AbstractAction("save") { private static final long serialVersionUID = 1L; public void actionPerformed(ActionEvent e) { onSave(); } }); JButton btnGc = new JButton(new AbstractAction("garbage collect") { private static final long serialVersionUID = 1L; public void actionPerformed(ActionEvent e) { onGc(); } }); JPanel btnPanel = new JPanel(new BorderLayout()); btnPanel.setBorder(BorderFactory.createEmptyBorder(3, 3, 3, 3)); btnPanel.add(btnSave, BorderLayout.EAST); btnPanel.add(btnGc, BorderLayout.WEST); add(new JScrollPane(editorPane), BorderLayout.CENTER); add(btnPanel, BorderLayout.SOUTH); } public void setText(String message) { editorPane.setText(message); editorPane.setSelectionStart(0); editorPane.setSelectionEnd(0); } protected void onGc() { SystemUtil.gc(); } protected void onSave() { JFileChooser chooser = new JFileChooser(); chooser.setSelectedFile(new File("sysinfo.html")); if (chooser.showSaveDialog(this) != JFileChooser.APPROVE_OPTION) { return; } File file = chooser.getSelectedFile(); try { FileOutputStream os = new FileOutputStream(file); try { Writer wr = new OutputStreamWriter(os, Charset.forName("UTF-8")); try { wr.write(editorPane.getText()); wr.flush(); } finally { wr.close(); } } finally { os.close(); } } catch (Exception ex) { ErrorMessageHelper.showErrorDialog(this, ex); } } } CharacterManaJ/src/charactermanaj/ui/model/0000755000175000017500000000000012560206305021037 5ustar paulliupaulliuCharacterManaJ/src/charactermanaj/ui/model/PredefinedWallpaper.java0000644000175000017500000000467012560206305025626 0ustar paulliupaulliupackage charactermanaj.ui.model; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Map; import java.util.Properties; import charactermanaj.util.LocalizedMessageAware; import charactermanaj.util.LocalizedResourcePropertyLoader; /** * 定義済み壁紙 * @author seraphy */ public class PredefinedWallpaper implements Comparable, LocalizedMessageAware { private static final String PREDEFINED_WALLPAPER_RESOURCE = "images/wallpaper"; private final String key; private final String msgid; private final String resource; protected PredefinedWallpaper(String key, String msgid, String resource) { this.key = key; this.msgid = msgid; this.resource = resource; } public String getLocalizedResourceId() { return "predefinedWallpaper." + msgid; } public String getKey() { return key; } public String getMsgid() { return msgid; } public String getResource() { return resource; } @Override public boolean equals(Object obj) { if (obj == this) { return true; } if (obj instanceof PredefinedWallpaper) { PredefinedWallpaper o = (PredefinedWallpaper) obj; return key.equals(o.key); } return false; } @Override public int hashCode() { return key.hashCode(); } public int compareTo(PredefinedWallpaper o) { return key.compareTo(o.key); } @Override public String toString() { return msgid; } /** * 定義済み壁紙リソースのリストを取得する * @return 定義済み壁紙リソースリスト */ public static List getPredefinedWallpapers() { Properties predefinedWallpapers = LocalizedResourcePropertyLoader.getCachedInstance() .getLocalizedProperties(PREDEFINED_WALLPAPER_RESOURCE); ArrayList results = new ArrayList(); for (Map.Entry entry : predefinedWallpapers.entrySet()) { String key = (String) entry.getKey(); String value = (String) entry.getValue(); String msgid; int pt = value.indexOf(';'); if (pt >= 0) { msgid = value.substring(pt + 1); } else { msgid = value; } PredefinedWallpaper predefinedWallpaper = new PredefinedWallpaper( key, msgid, "images/" + key); results.add(predefinedWallpaper); } Collections.sort(results); return results; } }CharacterManaJ/src/charactermanaj/ui/model/ColorChangeListener.java0000644000175000017500000000036112560206305025574 0ustar paulliupaulliupackage charactermanaj.ui.model; import java.util.EventListener; public interface ColorChangeListener extends EventListener { void onColorChange(ColorChangeEvent event); void onColorGroupChange(ColorChangeEvent event); } CharacterManaJ/src/charactermanaj/ui/model/WallpaperInfo.java0000644000175000017500000000560712560206305024455 0ustar paulliupaulliupackage charactermanaj.ui.model; import java.awt.Color; import java.io.File; import java.io.Serializable; /** * 壁紙情報.
* @author seraphy */ public class WallpaperInfo implements Serializable, Cloneable { /** * シリアライズバージョンID */ private static final long serialVersionUID = -3661482624140948074L; /** * 壁紙のリソースタイプ * @author seraphy */ public enum WallpaperResourceType { /** * なし */ NONE, /** * ファイル */ FILE, /** * 定義済み */ PREDEFINED } /** * 壁紙リソースのタイプ */ private WallpaperResourceType type = WallpaperResourceType.NONE; /** * ファイル */ private File file; /** * リソース */ private String resource; /** * 壁紙のアルファ値 */ private float alpha = 1.f; /** * 背景色 */ private Color backgroundColor = Color.WHITE; @Override public WallpaperInfo clone() { try { return (WallpaperInfo) super.clone(); } catch (CloneNotSupportedException ex) { throw new RuntimeException(ex); } } public WallpaperResourceType getType() { return type; } public void setType(WallpaperResourceType type) { if (type == null) { type = WallpaperResourceType.NONE; } this.type = type; } public File getFile() { return file; } public void setFile(File file) { this.file = file; } public String getResource() { return resource; } public void setResource(String resource) { if (resource != null) { resource = resource.trim(); if (resource.length() == 0) { resource = null; } } this.resource = resource; } /** * 背景画像のアルファ値を取得する. * @return アルファ値 */ public float getAlpha() { return alpha; } /** * 背景画像のアルファ値を設定する.
* 範囲は0から1の間であり、それを超えた場合は制限される.
* @param alpha アルファ値 */ public void setAlpha(float alpha) { if (alpha < 0) { alpha = 0; } else if (alpha > 1.f) { alpha = 1.f; } this.alpha = alpha; } /** * 背景色を取得する. * @return 背景色 */ public Color getBackgroundColor() { return backgroundColor; } /** * 背景色を設定する.
* nullを指定した場合は白とみなす.
* @param backgroundColor 背景色 */ public void setBackgroundColor(Color backgroundColor) { if (backgroundColor == null) { backgroundColor = Color.WHITE; } this.backgroundColor = backgroundColor; } @Override public String toString() { return "(WallpaperInfo:(type:" + type + ")(file:" + file + ")(resource:" + resource + ")(alpha:" + alpha + ")(bgColor:" + backgroundColor + "))"; } } CharacterManaJ/src/charactermanaj/ui/model/AbstractTableModelWithComboBoxModel.java0000644000175000017500000001162612560206305030652 0ustar paulliupaulliupackage charactermanaj.ui.model; import java.awt.Toolkit; import java.util.ArrayList; import java.util.LinkedList; import javax.swing.ComboBoxModel; import javax.swing.event.ListDataEvent; import javax.swing.event.ListDataListener; import javax.swing.table.AbstractTableModel; public /** * テーブルモデルをベースに、それをコンボボックスモデルとしても利用できるように拡張した抽象基底クラス.
* @author seraphy */ abstract class AbstractTableModelWithComboBoxModel extends AbstractTableModel implements ComboBoxModel { private static final long serialVersionUID = -6775939667002896930L; protected ArrayList elements = new ArrayList(); private boolean editable = true; public boolean removeRow(int rowIndex) { if (rowIndex < 0 || rowIndex >= elements.size()) { return false; } elements.remove(rowIndex); fireTableRowsDeleted(rowIndex, rowIndex); return true; } /** * 編集可能フラグ.
* モデル自身は、このフラグについて何ら関知せず、常に編集可能である.
* @param editable */ public void setEditable(boolean editable) { this.editable = editable; } /** * 編集可能フラグ.
* モデル自身は、このフラグについて何ら関知せず、常に編集可能である.
* 初期状態でtrueである.
* @return */ public boolean isEditable() { return editable; } public void clear() { elements.clear(); fireTableDataChanged(); } public T getRow(int index) { return elements.get(index); } public Object getElementAt(int index) { return getRow(index); } public boolean addRow(T obj) { if (obj == null) { throw new IllegalArgumentException(); } boolean ret = elements.add(obj); int row = elements.size() - 1; fireTableRowsInserted(row, row); return ret; } public int moveUp(int rowIndex) { if (rowIndex < 1 || rowIndex >= elements.size()) { Toolkit tk = Toolkit.getDefaultToolkit(); tk.beep(); return rowIndex; } T value = elements.get(rowIndex); elements.remove(rowIndex); elements.add(rowIndex - 1, value); fireTableRowsUpdated(rowIndex -1, rowIndex); return rowIndex - 1; } public int moveDown(int rowIndex) { if (rowIndex < 0 || rowIndex >= elements.size() - 1) { Toolkit tk = Toolkit.getDefaultToolkit(); tk.beep(); return rowIndex; } T value = elements.get(rowIndex); elements.remove(rowIndex); elements.add(rowIndex + 1, value); fireTableRowsUpdated(rowIndex, rowIndex + 1); return rowIndex + 1; } ///////////////////////////////////////// @Override public void fireTableCellUpdated(int row, int column) { super.fireTableCellUpdated(row, column); fireListUpdated(row, row); } @Override public void fireTableRowsDeleted(int firstRow, int lastRow) { super.fireTableRowsDeleted(firstRow, lastRow); fireListRemoved(firstRow, lastRow); } @Override public void fireTableRowsInserted(int firstRow, int lastRow) { super.fireTableRowsInserted(firstRow, lastRow); fireListAdded(firstRow, lastRow); } @Override public void fireTableRowsUpdated(int firstRow, int lastRow) { super.fireTableRowsUpdated(firstRow, lastRow); fireListUpdated(firstRow, lastRow); } @Override public void fireTableDataChanged() { super.fireTableDataChanged(); int siz = getRowCount(); if (siz > 0) { fireListUpdated(0, siz - 1); } } public int getRowCount() { return elements.size(); } ////////////////////////////// private Object selectedObject; private LinkedList listDataListeners = new LinkedList(); public Object getSelectedItem() { return selectedObject; } public void setSelectedItem(Object anItem) { selectedObject = anItem; } public void removeListDataListener(ListDataListener l) { if (l != null) { listDataListeners.remove(l); } } public void addListDataListener(ListDataListener l) { if (l != null) { listDataListeners.add(l); } } public void fireListUpdated(int firstRow, int lastRow) { ListDataEvent e = new ListDataEvent(this, ListDataEvent.CONTENTS_CHANGED, firstRow, lastRow); for (ListDataListener listener : listDataListeners) { listener.contentsChanged(e); } } public void fireListAdded(int firstRow, int lastRow) { ListDataEvent e = new ListDataEvent(this, ListDataEvent.INTERVAL_ADDED, firstRow, lastRow); for (ListDataListener listener : listDataListeners) { listener.intervalAdded(e); } } public void fireListRemoved(int firstRow, int lastRow) { ListDataEvent e = new ListDataEvent(this, ListDataEvent.INTERVAL_REMOVED, firstRow, lastRow); for (ListDataListener listener : listDataListeners) { listener.intervalRemoved(e); } } public int getSize() { return getRowCount(); } } CharacterManaJ/src/charactermanaj/ui/model/FavoritesChangeListener.java0000644000175000017500000000030612560206305026457 0ustar paulliupaulliupackage charactermanaj.ui.model; import java.util.EventListener; public interface FavoritesChangeListener extends EventListener { void notifyChangeFavorites(FavoritesChangeEvent e); } CharacterManaJ/src/charactermanaj/ui/model/PartsColorCoordinator.java0000644000175000017500000002654312560206305026210 0ustar paulliupaulliupackage charactermanaj.ui.model; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.IdentityHashMap; import java.util.Map; import java.util.logging.Level; import java.util.logging.Logger; import charactermanaj.graphics.filters.ColorConvertParameter; import charactermanaj.model.AppConfig; import charactermanaj.model.ColorGroup; import charactermanaj.model.ColorInfo; import charactermanaj.model.Layer; import charactermanaj.model.PartsColorInfo; import charactermanaj.model.PartsColorManager; import charactermanaj.model.PartsIdentifier; import charactermanaj.model.PartsSpec; import charactermanaj.model.PartsSpecResolver; import charactermanaj.ui.ColorDialog; import charactermanaj.ui.ImageSelectPanel; import charactermanaj.ui.ImageSelectPanel.ImageSelectPanelEvent; import charactermanaj.ui.ImageSelectPanel.ImageSelectPanelListener; /** * パーツの選択パネルとカラーダイアログを関連づけて調整するコーディネータオブジェクト.
* @author seraphy * */ public class PartsColorCoordinator { /** * ロガー */ private static final Logger logger = Logger.getLogger(PartsColorCoordinator.class.getName()); /** * パーツ選択パネルとカラーダイアログの関係を示すマップ.
*/ private IdentityHashMap colorDialogMap = new IdentityHashMap(); /** * パーツ識別子ごとのカラー情報を保存するパーツカラーマネージャ.
*/ private PartsColorManager partsColorMrg; /** * パーツ設定 */ private PartsSpecResolver partsSpecResolver; /** * 同一の色グループに設定値を同期させるためのコーディネータ.
*/ private ColorGroupCoordinator colorGroupCoordinator; /** * コンストラクタ.
* @param partsSpecResolver パーツ * @param partsColorMrg パーツ識別子ごとの色情報を管理するオブジェクト * @param colorGroupCoordinator 同一の色グループに設定値を同期させるためのコーディネータ. */ public PartsColorCoordinator(PartsSpecResolver partsSpecResolver, PartsColorManager partsColorMrg, ColorGroupCoordinator colorGroupCoordinator) { if (partsSpecResolver == null || partsColorMrg == null || colorGroupCoordinator == null) { throw new IllegalArgumentException(); } this.partsSpecResolver = partsSpecResolver; this.partsColorMrg = partsColorMrg; this.colorGroupCoordinator = colorGroupCoordinator; } /** * パーツ選択パネルと色ダイアログの関係を登録する.
* @param imageSelectPanel パーツ選択パネル * @param colorDialog 色ダイアログ */ public void register(final ImageSelectPanel imageSelectPanel, final ColorDialog colorDialog) { if (imageSelectPanel == null || colorDialog == null) { throw new IllegalArgumentException(); } if (colorDialogMap.containsKey(imageSelectPanel)) { throw new IllegalArgumentException("already registered: " + imageSelectPanel); } colorDialogMap.put(imageSelectPanel, colorDialog); imageSelectPanel.addImageSelectListener(new ImageSelectPanelListener() { public void onChangeColor(ImageSelectPanelEvent event) { // なにもしない } public void onPreferences(ImageSelectPanelEvent event) { // なにもしない } public void onChange(ImageSelectPanelEvent event) { PartsColorCoordinator.this.loadColorSettingToColorDialog(imageSelectPanel, colorDialog); } public void onSelectChange(ImageSelectPanelEvent event) { PartsColorCoordinator.this.loadColorSettingToColorDialog(imageSelectPanel, colorDialog); } public void onTitleClick(ImageSelectPanelEvent event) { // なにもしない } public void onTitleDblClick(ImageSelectPanelEvent event) { // なにもしない } }); colorDialog.addColorChangeListener(new ColorChangeListener() { public void onColorChange(ColorChangeEvent event) { saveColorSettingAll(); } public void onColorGroupChange(ColorChangeEvent event) { saveColorSettingAll(); } }); } /** * パーツ選択パネルの現在の選択に対する保存されているカラー情報を色ダイアログに設定する.
* @param imageSelectPanel パーツ選択パネル * @param colorDialog 色ダイアログ */ protected void loadColorSettingToColorDialog(ImageSelectPanel imageSelectPanel, ColorDialog colorDialog) { PartsIdentifier selectedParts = imageSelectPanel.getSelectedPartsIdentifier(); // 選択されているパーツのパーツ名と有効なレイヤーをカラーダイアログに設定する. // 選択されているパーツがない場合はデフォルトに戻す. colorDialog.setPartsIdentifier(selectedParts); colorDialog.setEnableLayers(getEnabledLayers(selectedParts)); if (selectedParts == null) { // 選択されているパーツがない場合は、ここまで。 return; } PartsColorInfo partsColorInfo = partsColorMrg.getPartsColorInfo(selectedParts, false); for (Map.Entry entry : partsColorInfo.entrySet()) { Layer layer = entry.getKey(); ColorInfo colorInfo = entry.getValue(); ColorGroup colorGroup = colorInfo.getColorGroup(); if (colorGroup == null) { colorGroup = ColorGroup.NA; } colorDialog.setColorGroup(layer, colorGroup); boolean syncColorGroup = colorInfo.isSyncColorGroup(); colorDialog.setSyncColorGroup(layer, syncColorGroup); ColorConvertParameter param = colorInfo.getColorParameter(); colorDialog.setColorConvertParameter(layer, param); if (syncColorGroup) { colorGroupCoordinator.syncColorGroup(colorDialog.getPartsCategory(), layer, colorDialog); } } } /** * 現在選択中のパーツの組み合わせに対応するカラーダイアログの設定情報をPartsColorManagerに保存する.
* (カラーダイアログの値が変更されるたびに呼び出される.)
*/ protected void saveColorSettingAll() { for (Map.Entry entry : colorDialogMap.entrySet()) { ImageSelectPanel imageSelectPanel = entry.getKey(); ColorDialog colorDialog = entry.getValue(); saveColorSettingFromColorDialog(imageSelectPanel, colorDialog); } } /** * カテゴリべつの現在選択中のパーツ識別子と、それに対応するカラーダイアログの設定値をPartsColorManagerに保存する.
* @param imageSelectPanel カテゴリ別のパーツ選択 * @param colorDialog 対応するカラーダイアログ */ protected void saveColorSettingFromColorDialog(ImageSelectPanel imageSelectPanel, ColorDialog colorDialog) { PartsIdentifier selectedParts = imageSelectPanel.getSelectedPartsIdentifier(); if (selectedParts == null) { return; } Map paramMap = colorDialog.getColorConvertParameters(); PartsColorInfo partsColorInfo = partsColorMrg.getPartsColorInfo(selectedParts, true); for (Map.Entry entry : paramMap.entrySet()) { Layer layer = entry.getKey(); ColorConvertParameter param = entry.getValue(); ColorInfo colorInfo = new ColorInfo(); ColorGroup colorGroup = colorDialog.getColorGroup(layer); colorInfo.setColorGroup(colorGroup); boolean syncColorGroup = colorDialog.isSyncColorGroup(layer); colorInfo.setSyncColorGroup(syncColorGroup); colorInfo.setColorParameter(param); partsColorInfo.put(layer, colorInfo); } boolean applyAll = colorDialog.isApplyAll(); partsColorMrg.setPartsColorInfo(selectedParts, partsColorInfo, applyAll); } /** * 全カラーダイアログで設定されている各レイヤーごとの色パラメータを全て取得する.
* @return 各レイヤーごとの色パラメータ */ public Map getColorConvertParameterMap() { final HashMap colorConvertParameterMap = new HashMap(); for (ColorDialog colorDlg : colorDialogMap.values()) { for (Map.Entry entry : colorDlg.getColorConvertParameters().entrySet()) { Layer layer = entry.getKey(); ColorConvertParameter colorConvertParameter = entry.getValue(); colorConvertParameterMap.put(layer, colorConvertParameter); } } return colorConvertParameterMap; } /** * 現在選択されている各カテゴリのパーツの組み合わせに対するカラー情報に各カテゴリの色ダイアログを設定する.
* (選択中のパーツ名も設定される.)
*/ public void initColorDialog() { for (Map.Entry entry : colorDialogMap.entrySet()) { ImageSelectPanel imageSelectPanel = entry.getKey(); ColorDialog colorDialog = entry.getValue(); PartsIdentifier partsIdentifier = imageSelectPanel.getSelectedPartsIdentifier(); // 選択されているパーツのパーツ名と有効なレイヤーをカラーダイアログに設定する. // 選択されているパーツがない場合はデフォルトに戻す. colorDialog.setPartsIdentifier(partsIdentifier); colorDialog.setEnableLayers(getEnabledLayers(partsIdentifier)); if (partsIdentifier != null) { PartsColorInfo partsColorInfo = partsColorMrg.getPartsColorInfo(partsIdentifier, false); for (Map.Entry colorInfoEntry : partsColorInfo.entrySet()) { Layer layer = colorInfoEntry.getKey(); ColorInfo colorInfo = colorInfoEntry.getValue(); if (logger.isLoggable(Level.FINE)) { logger.log(Level.FINE, layer + "=" + colorInfo); } colorDialog.setColorGroup(layer, colorInfo.getColorGroup()); colorDialog.setSyncColorGroup(layer, colorInfo.isSyncColorGroup()); colorDialog.setColorConvertParameter(layer, colorInfo.getColorParameter()); } } } } /** * 指定したパーツ識別子を構成する実際に存在するレイヤーを返します.
* (カテゴリが2つ以上のレイヤーをもっている場合でもパーツが、カテゴリのレイヤー数を全て使い切っていない場合は、使っているレイヤーのみが返されます.)
* 指定したパーツ識別子がnullの場合はnullを返します.
* 指定したパーツ識別子がnullではなく、且つ、パーツリゾルバから取得できない場合は空が返されます.
* ただし、アプリケーション設定で「存在しないレイヤーをディセーブルにしない」がtrueであれば、常にnullを返します.
* @param partsIdentifier パーツ識別子、もしくはnull * @return レイヤーのコレクション、もしくはnull */ protected Collection getEnabledLayers(PartsIdentifier partsIdentifier) { AppConfig appConfig = AppConfig.getInstance(); if (appConfig.isNotDisableLayerTab()) { return null; } if (partsIdentifier != null) { PartsSpec partsSpec = partsSpecResolver.getPartsSpec(partsIdentifier); ArrayList layers = new ArrayList(); if (partsSpec != null) { for (Layer layer : partsSpec.getPartsFiles().keySet()) { layers.add(layer); } } return layers; } return null; } } CharacterManaJ/src/charactermanaj/ui/model/ColorChangeEvent.java0000644000175000017500000000166112560206305025074 0ustar paulliupaulliupackage charactermanaj.ui.model; import java.util.EventObject; import charactermanaj.model.Layer; import charactermanaj.ui.ColorDialog; public class ColorChangeEvent extends EventObject { private static final long serialVersionUID = -4185234778107466586L; private Layer layer; private boolean cascaded; public ColorChangeEvent(ColorDialog colorDialog, Layer layer) { this(colorDialog, layer, false); } public ColorChangeEvent(ColorChangeEvent src, boolean cascaded) { this((ColorDialog) src.getSource(), src.getLayer(), cascaded); } protected ColorChangeEvent(ColorDialog colorDialog, Layer layer, boolean cascaded) { super(colorDialog); if (layer == null) { throw new IllegalArgumentException("null layer"); } this.layer = layer; this.cascaded = cascaded; } public Layer getLayer() { return layer; } public boolean isCascaded() { return cascaded; } } CharacterManaJ/src/charactermanaj/ui/model/FavoritesChangeObserver.java0000644000175000017500000000320112560206305026456 0ustar paulliupaulliupackage charactermanaj.ui.model; import javax.swing.event.EventListenerList; import charactermanaj.model.CharacterData; /** * お気に入りが変更されたことを通知するためのメカニズム.
* * @author seraphy * */ public abstract class FavoritesChangeObserver { private static FavoritesChangeObserver defobj = new FavoritesChangeObserverImpl(); public static FavoritesChangeObserver getDefault() { return defobj; } public abstract void addFavoritesChangeListener(FavoritesChangeListener l); public abstract void removeFavoritesChangeListener(FavoritesChangeListener l); public abstract void notifyFavoritesChange(FavoritesChangeEvent e); public void notifyFavoritesChange(Object wnd, CharacterData cd) { if (cd == null) { throw new IllegalArgumentException(); } notifyFavoritesChange(new FavoritesChangeEvent(wnd, cd)); } } class FavoritesChangeObserverImpl extends FavoritesChangeObserver { private EventListenerList listeners = new EventListenerList(); @Override public void addFavoritesChangeListener(FavoritesChangeListener l) { listeners.add(FavoritesChangeListener.class, l); } @Override public void removeFavoritesChangeListener(FavoritesChangeListener l) { listeners.remove(FavoritesChangeListener.class, l); } @Override public void notifyFavoritesChange(FavoritesChangeEvent e) { if (e == null) { throw new IllegalArgumentException(); } FavoritesChangeListener[] lst = listeners .getListeners(FavoritesChangeListener.class); for (FavoritesChangeListener l : lst) { l.notifyChangeFavorites(e); } } } CharacterManaJ/src/charactermanaj/ui/model/FavoritesChangeEvent.java0000644000175000017500000000116212560206305025754 0ustar paulliupaulliupackage charactermanaj.ui.model; import java.util.EventObject; import charactermanaj.model.CharacterData; /** * お気に入り変更イベント.
* * @author seraphy */ public class FavoritesChangeEvent extends EventObject { /** * シリアライズバージョンID */ private static final long serialVersionUID = 3206827658882098336L; private CharacterData characterData; public FavoritesChangeEvent(Object src, CharacterData characterData) { super(src); this.characterData = characterData; } public CharacterData getCharacterData() { return characterData; } } CharacterManaJ/src/charactermanaj/ui/model/PartsSelectionManager.java0000644000175000017500000002650312560206305026142 0ustar paulliupaulliupackage charactermanaj.ui.model; import java.awt.Color; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import charactermanaj.model.PartsCategory; import charactermanaj.model.PartsColorInfo; import charactermanaj.model.PartsColorManager; import charactermanaj.model.PartsIdentifier; import charactermanaj.model.PartsSet; import charactermanaj.ui.ImageSelectPanel; /** * パーツ選択ペインの管理クラス. * @author seraphy */ public class PartsSelectionManager { /** * 背景色を管理するオブジェクトが実装するインターフェイス. * @author seraphy */ public interface ImageBgColorProvider { /** * 背景色を取得する. * @return 背景色 */ Color getImageBgColor(); /** * 背景色を設定する * @param imageBgColor 背景色 */ void setImageBgColor(Color imageBgColor); } /** * カテゴリ別パーツ選択パネル.
*/ private HashMap imageSelectPanels = new HashMap(); /** * パーツカラーマネージャ.
*/ private PartsColorManager partsColorMrg; /** * 背景色プロバイダ. */ private ImageBgColorProvider imageBgColorProvider; /** * アフィン変換用のパラメータ.
* 変換しない場合はnull.
*/ private double[] affineTransformParameter; /** * 単一選択カテゴリの選択解除の有効/無効.
* 有効にする場合はtrue.
*/ private boolean deselectableAllCategory; /** * コンストラクタ * @param partsColorMrg パーツカラーマネージャ * @param imageBgColorProvider 背景色プロバイダ */ public PartsSelectionManager( PartsColorManager partsColorMrg, ImageBgColorProvider imageBgColorProvider ) { if (partsColorMrg == null || imageBgColorProvider == null) { throw new IllegalArgumentException(); } this.partsColorMrg = partsColorMrg; this.imageBgColorProvider = imageBgColorProvider; } /** * パーツをロードする. */ public void loadParts() { for (ImageSelectPanel panel : imageSelectPanels.values()) { panel.loadParts(); } } public void register(ImageSelectPanel imageSelectPanel) { if (imageSelectPanel == null) { throw new IllegalArgumentException(); } imageSelectPanels.put(imageSelectPanel.getPartsCategory(), imageSelectPanel); } public List getSelectedPartsIdentifiers(PartsCategory partsCategory) { if (partsCategory == null) { throw new IllegalArgumentException(); } ImageSelectPanel panel = imageSelectPanels.get(partsCategory); if (panel != null) { return Collections.unmodifiableList(panel.getSelectedPartsIdentifiers()); } return Collections.emptyList(); } public PartsIdentifier getSelectedPartsIdentifier(PartsCategory partsCategory) { if (partsCategory == null) { throw new IllegalArgumentException(); } ImageSelectPanel panel = imageSelectPanels.get(partsCategory); if (panel != null) { return panel.getSelectedPartsIdentifier(); } return null; } public Collection getAllCategories() { ArrayList partsCategories = new ArrayList(); partsCategories.addAll(imageSelectPanels.keySet()); return partsCategories; } /** * 各カテゴリの選択状態と背景をパーツセットで指定されたものに設定します.
* @param partsSet パーツセット */ public void selectPartsSet(PartsSet partsSet) { if (partsSet == null) { throw new IllegalArgumentException(); } for (ImageSelectPanel panel : imageSelectPanels.values()) { PartsCategory partsCategory = panel.getPartsCategory(); List partsIdentifiers = partsSet.get(partsCategory); panel.selectParts(partsIdentifiers); if (partsIdentifiers != null) { for (PartsIdentifier partsIdentifier : partsIdentifiers) { PartsColorInfo partsColorInfo = partsSet.getColorInfo(partsIdentifier); if (partsColorInfo != null) { partsColorMrg.setPartsColorInfo(partsIdentifier, partsColorInfo, false); } } } } Color bgColor = partsSet.getBgColor(); if (bgColor != null) { setImageBgColor(bgColor); } affineTransformParameter = partsSet.getAffineTransformParameter(); // clone済み } /** * 現在の選択中のパーツと色設定からパーツセットを構築します. * 選択がなにもない場合は空のパーツセットとなります.
* @return パーツセット */ public PartsSet createPartsSet() { PartsSet presetParts = new PartsSet(); for (ImageSelectPanel imageSelectPanel : imageSelectPanels.values()) { PartsCategory category = imageSelectPanel.getPartsCategory(); for (PartsIdentifier partsIdentifier : imageSelectPanel.getSelectedPartsIdentifiers()) { PartsColorInfo partsColorInfo = partsColorMrg.getPartsColorInfo(partsIdentifier, false); presetParts.appendParts(category, partsIdentifier, partsColorInfo); } } presetParts.setBgColor(getImageBgColor()); presetParts.setAffineTransformParameter(affineTransformParameter); // 相手側でcloneする return presetParts; } /** * すべてのカテゴリのリストで選択中のアイテムが見えるようにスクロールする. */ public void scrollToSelectedParts() { for (ImageSelectPanel imageSelectPanel : imageSelectPanels.values()) { imageSelectPanel.scrollToSelectedRow(); } } /** * 指定したパーツ識別子にフォーカスを当てて、必要に応じてスクロールします.
* 該当するパーツ識別子がどこにもなければ何もしません.
* @param partsIdentifier パーツ識別子 */ public void setSelection(PartsIdentifier partsIdentifier) { if (partsIdentifier == null) { return; } PartsCategory partsCategory = partsIdentifier.getPartsCategory(); if (isMinimizeMode(partsCategory)) { setMinimizeModeIfOther(partsCategory, true); } ImageSelectPanel imageSelectPanel = imageSelectPanels.get(partsCategory); if (imageSelectPanel != null) { imageSelectPanel.setSelection(partsIdentifier); } } /** * 背景色を取得する. * @return 背景色 */ protected Color getImageBgColor() { return imageBgColorProvider.getImageBgColor(); } /** * 背景色を設定する * @param imageBgColor 背景色 */ protected void setImageBgColor(Color imageBgColor) { imageBgColorProvider.setImageBgColor(imageBgColor); } /** * アフィン変換用のパラメータを取得する.
* 変換しない場合はnull.
* @return アフィン変換用のパラメータ、もしくはnull */ public double[] getAffineTransformParameter() { return affineTransformParameter == null ? null : affineTransformParameter.clone(); } /** * アフィン変換用のパラメータを設定する.
* 変換しない場合はnull.
* 要素数は4または6でなければならない.
* @param affineTransformParameter アフィン変換用のパラメータ、もしくはnull */ public void setAffineTransformParameter(double[] affineTransformParameter) { if (affineTransformParameter != null && !(affineTransformParameter.length == 4 || affineTransformParameter.length == 6)) { throw new IllegalArgumentException("affineTransformParameter invalid length."); } this.affineTransformParameter = affineTransformParameter == null ? null : affineTransformParameter.clone(); } /** * 選択選択パーツカテゴリの選択解除を許可するか?
* @return 許可する場合はtrue */ public boolean isDeselectableSingleCategory() { return deselectableAllCategory; } /** * 選択選択パーツカテゴリの選択解除を許可するか設定する.
* @param deselectable 許可する場合はtrue */ public void setDeselectableSingleCategory(boolean deselectable) { this.deselectableAllCategory = deselectable; for (ImageSelectPanel imageSelectPanel : this.imageSelectPanels.values()) { imageSelectPanel.setDeselectableSingleCategory(deselectable); } } /** * パーツ選択をすべて解除する.
* 単一選択カテゴリが解除されるかどうかは、{@link #isDeselectableSingleCategory()}による.
*/ public void deselectAll() { for (ImageSelectPanel imageSelectPanel : this.imageSelectPanels.values()) { if (imageSelectPanel.getPartsCategory().isMultipleSelectable() || imageSelectPanel.isDeselectableSingleCategory()) { imageSelectPanel.deselectAll(); } } } /** * 指定したカテゴリ以外のパネルを最小化する.
* (指定したカテゴリがnullでなければ、そのカテゴリの最小化は解除される.)
* @param partsCategory 最小化の対象外のパネル、nullの場合は不問 * @param minimize 指定したカテゴリ以外を最小化する場合はtrue、falseの場合はすべてが最小化解除される. */ public void setMinimizeModeIfOther(PartsCategory partsCategory, boolean minimize) { for (Map.Entry entry : imageSelectPanels.entrySet()) { PartsCategory cat = entry.getKey(); ImageSelectPanel imageSelectPanel = entry.getValue(); if (partsCategory != null && cat.equals(partsCategory)) { imageSelectPanel.setMinimizeMode(false); } else { imageSelectPanel.setMinimizeMode(minimize); } } } /** * 指定したカテゴリが最小化モードであるか? * @param partsCategory カテゴリ * @return 指定したカテゴリが最小化モードであればtrue、該当するカテゴリがない場合は常にfalse */ public boolean isMinimizeMode(PartsCategory partsCategory) { ImageSelectPanel panel = imageSelectPanels.get(partsCategory); if (panel == null) { return false; } return panel.isMinimizeMode(); } /** * 指定したカテゴリが最小化モードでなく、且つ、他がすべて最小化モードであるか? * (指定カテゴリがない場合は全パネルが最小化モードである場合)
* @param partsCategory カテゴリ、もしくはnull * @return 指定したカテゴリが最小化モードでなく、且つ、他がすべて最小化モードである場合はtrue */ public boolean isNotMinimizeModeJust(PartsCategory partsCategory) { for (Map.Entry entry : imageSelectPanels.entrySet()) { PartsCategory cat = entry.getKey(); ImageSelectPanel imageSelectPanel = entry.getValue(); if (partsCategory != null) { if (cat.equals(partsCategory)) { // 指定したカテゴリが最小化モードであればfalse if (imageSelectPanel.isMinimizeMode()) { return false; } } else { // 指定したカテゴリ以外が最小化モードでなければfalse if ( !imageSelectPanel.isMinimizeMode()) { return false; } } } } // 指定カテゴリ以外がすべて最小化モードである場合 // (指定カテゴリがない場合は全パネルが最小化モードである場合) return true; } } CharacterManaJ/src/charactermanaj/ui/model/ColorGroupCoordinator.java0000644000175000017500000002015212560206305026201 0ustar paulliupaulliupackage charactermanaj.ui.model; import java.util.ArrayList; import java.util.Collection; import java.util.Iterator; import java.util.LinkedList; import java.util.Map; import charactermanaj.graphics.filters.ColorConvertParameter; import charactermanaj.model.ColorGroup; import charactermanaj.model.ColorInfo; import charactermanaj.model.Layer; import charactermanaj.model.PartsCategory; import charactermanaj.model.PartsColorInfo; import charactermanaj.model.PartsColorManager; import charactermanaj.model.PartsIdentifier; import charactermanaj.ui.ColorDialog; /** * 同一カラーグループでの連動をサポートするためのコーディネータオブジェクト.
* @author seraphy */ public class ColorGroupCoordinator { /** * パーツの選択状態を管理するマネージャ.
*/ private final PartsSelectionManager partsSelectionMrg; /** * 色ダイアログのコレクション */ private final LinkedList colorDialogs = new LinkedList(); /** * カラーグループの変更通知を受けるリスナーのコレクション.
*/ private final LinkedList listeners = new LinkedList(); /** * パーツ識別子ごとの色情報を管理するパーツカラーマネージャ */ private final PartsColorManager partsColorMrg; /** * 色ダイアログからの通知を受け取るリスナ.(自クラス内オブジェクト) */ protected final ColorChangeListener listener; /** * コンストラクタ * @param partsSelectionMrg パーツの選択を管理するマネージャ * @param partsColorMrg パーツ識別子ごとの色を管理するマネージャ */ public ColorGroupCoordinator(PartsSelectionManager partsSelectionMrg, PartsColorManager partsColorMrg) { if (partsSelectionMrg == null || partsColorMrg == null) { throw new IllegalArgumentException(); } this.partsSelectionMrg = partsSelectionMrg; this.partsColorMrg = partsColorMrg; listener = new ColorChangeListener() { public void onColorChange(ColorChangeEvent event) { Layer layer = event.getLayer(); ColorDialog colorDialog = (ColorDialog) event.getSource(); PartsCategory partsCategory = colorDialog.getPartsCategory(); ColorGroupCoordinator.this.syncColorGroup(partsCategory, layer, colorDialog); ColorGroupCoordinator.this.fireColorChangeEvent(event); } public void onColorGroupChange(ColorChangeEvent event) { Layer layer = event.getLayer(); ColorDialog colorDialog = (ColorDialog) event.getSource(); ColorGroup colorGroup = colorDialog.getColorGroup(layer); ColorGroupCoordinator.this.onChangeColorGroup(colorDialog, layer, colorGroup); ColorGroupCoordinator.this.fireColorGroupChangeEvent(event); } }; } /** * カラーダイアログを登録する.
* @param colorDialog カラーダイアログ */ public void registerColorDialog(ColorDialog colorDialog) { if (colorDialog == null) { throw new IllegalArgumentException(); } this.colorDialogs.add(colorDialog); colorDialog.addColorChangeListener(listener); } /** * カラーダイアログの登録を解除する.
* @param colorDialog カラーダイアログ */ public void unregisterColorDialog(ColorDialog colorDialog) { Iterator ite = colorDialogs.iterator(); while (ite.hasNext()) { ColorDialog dlg = ite.next(); if (dlg == colorDialog) { dlg.removeColorChangeListener(listener); ite.remove(); } } } /** * カラーグループが変更されたことを通知するリスナを登録する.
* @param listener リスナー */ public void addColorChangeListener(ColorChangeListener listener) { if (listener == null) { throw new IllegalArgumentException(); } listeners.add(listener); } /** * カラーグループが変更されたことを通知するリスナを登録解除する.
* @param listener リスナー */ public void removeColorChangeListener(ColorChangeListener listener) { listeners.remove(listener); } protected void fireColorChangeEvent(ColorChangeEvent e) { if (e == null) { throw new IllegalArgumentException(); } for (ColorChangeListener listener : listeners) { listener.onColorChange(e); } } protected void fireColorGroupChangeEvent(ColorChangeEvent e) { if (e == null) { throw new IllegalArgumentException(); } for (ColorChangeListener listener : listeners) { listener.onColorGroupChange(e); } } protected void onChangeColorGroup(ColorDialog destColorDialog, Layer layer, ColorGroup colorGroup) { if (destColorDialog == null || layer == null || colorGroup == null) { throw new IllegalArgumentException(); } for (ColorDialog colorDlg : colorDialogs) { for (Layer srcLayer : colorDlg.getPartsCategory().getLayers()) { if (!srcLayer.equals(layer)) { if (ColorGroup.equals(colorGroup, colorDlg.getColorGroup(srcLayer)) && colorDlg.isSyncColorGroup(srcLayer)) { ColorConvertParameter param = colorDlg.getColorConvertParameter(srcLayer); destColorDialog.setColorConvertParameter(layer, param); break; } } } } } /** * パーツの色ダイアログが変更されたことにより、同一の他のカラーグループのレイヤーのカラーダイアログの設定値をコピーする.
* (色ダイアログのパラメータ変更により呼び出される.)
* @param partsCategory パーツカテゴリ * @param eventSourceLayer 変更もとのレイヤー * @param sourceColorDialog 変更されたカラーダイアログ */ public void syncColorGroup(PartsCategory partsCategory, Layer eventSourceLayer, ColorDialog sourceColorDialog) { if (partsCategory == null || sourceColorDialog == null) { throw new IllegalArgumentException(); } // 変更もと ArrayList syncSources = new ArrayList(); if (eventSourceLayer != null) { if (sourceColorDialog.isSyncColorGroup(eventSourceLayer)) { syncSources.add(eventSourceLayer); } } else { for (Layer layer2 : partsCategory.getLayers()) { if (sourceColorDialog.isSyncColorGroup(layer2)) { syncSources.add(layer2); } } } // 変更もとのレイヤーのカラーグループを他のレイヤーにも適用する. for (Layer sourceLayer : syncSources) { ColorGroup sourceColorGroup = sourceColorDialog.getColorGroup(sourceLayer); if (sourceColorGroup != null && sourceColorGroup.isEnabled()) { ColorConvertParameter param = sourceColorDialog.getColorConvertParameter(sourceLayer); // 他のパネルに適用する for (ColorDialog targetColorDialog : colorDialogs) { for (Layer targetLayer : targetColorDialog.getPartsCategory().getLayers()) { if (!targetLayer.equals(sourceLayer)) { if (ColorGroup.equals(targetColorDialog.getColorGroup(targetLayer), sourceColorGroup)) { if (targetColorDialog.isSyncColorGroup(targetLayer)) { targetColorDialog.setColorConvertParameter(targetLayer, param); } } } } } // 色ダイアログで選択中でない有効なパーツも含めてパーツカラーを更新する. for (PartsCategory targetPartsCategory : partsSelectionMrg.getAllCategories()) { Collection selectedPartss = partsSelectionMrg.getSelectedPartsIdentifiers(targetPartsCategory); for (PartsIdentifier partsIdentifier : selectedPartss) { // カラーダイアログで選択されていない他のパーツも含めてパーツカラーを更新する. PartsColorInfo partsColorInfo = partsColorMrg.getPartsColorInfo(partsIdentifier, true); for (Map.Entry entry : partsColorInfo.entrySet()) { ColorInfo colorInfo = entry.getValue(); if (ColorGroup.equals(sourceColorGroup, colorInfo.getColorGroup())) { if (colorInfo.isSyncColorGroup()) { colorInfo.setColorParameter(param); } } } } } } } } } CharacterManaJ/src/charactermanaj/ui/model/WallpaperFactoryErrorRecoverHandler.java0000644000175000017500000000502012560206305031014 0ustar paulliupaulliupackage charactermanaj.ui.model; import java.awt.image.BufferedImage; import java.io.File; import java.util.logging.Level; import java.util.logging.Logger; import charactermanaj.graphics.io.ImageResource; import charactermanaj.ui.Wallpaper; import charactermanaj.ui.model.WallpaperInfo.WallpaperResourceType; /** * 壁紙オブジェクトの構築に回復しながら継続するためのハンドラ.
* @author seraphy */ public class WallpaperFactoryErrorRecoverHandler implements WallpaperFactory.ErrorHandler { protected static final Logger logger = Logger.getLogger(WallpaperFactoryErrorRecoverHandler.class.getName()); private boolean errorOccured = false; private boolean recovered = false; /** * 何らかのエラーが発生し回復できなかった場合 * @return */ public boolean isErrorOccured() { return errorOccured; } /** * 何らかのエラーが発生したが回復された場合 * @return */ public boolean isRecovered() { return recovered; } public void setErrorOccured(boolean errorOccured) { this.errorOccured = errorOccured; } public void setRecovered(boolean recovered) { this.recovered = recovered; } public File missingImageFile(WallpaperInfo wallpaperInfo, File file) throws WallpaperFactoryException { if (file == null) { logger.log(Level.FINE, "壁紙ファイルの指定がありません."); } else { logger.log(Level.WARNING, "壁紙ファイルが存在しないか読み込みできません:" + file); } // ファイルは、もとより指定されていなかったものとして回復する. wallpaperInfo.setType(WallpaperResourceType.NONE); setRecovered(true); return null; } public BufferedImage imageCreationFailed(WallpaperInfo wallpaperInfo, ImageResource imageResource, Throwable ex) throws WallpaperFactoryException { logger.log(Level.WARNING, "壁紙ファイルの読み込みに失敗しました。:" + imageResource, ex); // ファイルは、もとより指定されていなかったものとして回復する. wallpaperInfo.setType(WallpaperResourceType.NONE); setRecovered(true); return null; } public Wallpaper internalError(WallpaperInfo wallpaperInfo, Wallpaper wallpaper, Throwable ex) throws WallpaperFactoryException { logger.log(Level.WARNING, "壁紙の構築に失敗しました。" + wallpaperInfo, ex); setErrorOccured(true); throw new WallpaperFactoryException("internal error: " + ex, ex); } } CharacterManaJ/src/charactermanaj/ui/model/WallpaperFactoryException.java0000644000175000017500000000073412560206305027044 0ustar paulliupaulliupackage charactermanaj.ui.model; /** * 壁紙の構築エラー.
* @author seraphy */ public class WallpaperFactoryException extends Exception { private static final long serialVersionUID = 6160297739997949904L; public WallpaperFactoryException() { super(); } public WallpaperFactoryException(String message) { super(message); } public WallpaperFactoryException(String message, Throwable cause) { super(message, cause); } } CharacterManaJ/src/charactermanaj/ui/model/WallpaperFactory.java0000644000175000017500000001651412560206305025170 0ustar paulliupaulliupackage charactermanaj.ui.model; import java.awt.image.BufferedImage; import java.io.File; import java.io.IOException; import charactermanaj.graphics.io.EmbeddedImageResource; import charactermanaj.graphics.io.FileImageResource; import charactermanaj.graphics.io.ImageLoaderImpl; import charactermanaj.graphics.io.ImageResource; import charactermanaj.graphics.io.LoadedImage; import charactermanaj.ui.Wallpaper; import charactermanaj.ui.model.WallpaperInfo.WallpaperResourceType; /** * 壁紙情報から壁紙オブジェクトを作成するファクトリクラス.
* @author seraphy */ public class WallpaperFactory { /** * エラーが発生した場合にハンドリングするインターフェィス.
* ハンドラは、そのままエラーを送出するか、もしくは回復して続行させることができる.
* @author seraphy */ public interface ErrorHandler { /** * 指定された壁紙ファイルが、ファイルとして実在しない場合のエラー.
* 代わりのファイルを指定するかnullを返して画像なしとして扱うか、 * もしくは例外を送出することができる.
* @param wallpaperInfo 設定対象(回復時には更新可能) * @param file 対象となったファイル * @return 読み替えるファイル、nullの場合は画像なしとみなす. * @throws WallpaperFactoryException 例外とする場合 */ File missingImageFile(WallpaperInfo wallpaperInfo, File file) throws WallpaperFactoryException; /** * 指定した画像リソースの読み込みに失敗した場合のエラー.
* 代わりの画像を指定するかnullを返して画像なしとして扱うか、 * もしくは例外を送出することができる.
* @param wallpaperInfo 設定対象(回復時には更新可能) * @param imageResource 対象となった画像リソース * @param ex 失敗事由 * @return 代わりの画像、もしくは画像なしとするためにnullを返すことができる * @throws WallpaperFactoryException 例外とする場合 */ BufferedImage imageCreationFailed(WallpaperInfo wallpaperInfo, ImageResource imageResource, Throwable ex) throws WallpaperFactoryException; /** * その他の内部例外(RuntimeException)が発生した場合のハンドラ.
* 代わりの壁紙オブジェクトを返すか、もしくは例外を送出することができる.
* @param wallpaperInfo 壁紙情報 * @param wallpaper 構築中の壁紙オブジェクト * @param ex 発生した例外 * @return 代わりの壁紙オブジェクト (nullは返してはならない.) * @throws WallpaperFactoryException 例外とする場合 */ Wallpaper internalError(WallpaperInfo wallpaperInfo, Wallpaper wallpaper, Throwable ex) throws WallpaperFactoryException; } /** * 壁紙画像を読み取るためのイメージローダ.
*/ private ImageLoaderImpl imageLoader = new ImageLoaderImpl(); /** * シングルトン */ private static final WallpaperFactory inst = new WallpaperFactory(); /** * エラーを例外として送出する既定のハンドラ.
*/ public final ErrorHandler defaultErrorHandler = new WallpaperFactoryDefaultErrorHandler(); private WallpaperFactory() { super(); } public static WallpaperFactory getInstance() { return inst; } /** * 壁紙情報から壁紙オブジェクトを作成して返します.
* 壁紙情報に不備があるか何らかの問題により壁紙が作成できない場合はエラーハンドラが呼び出されます.
* エラーハンドラは例外を送出するか、もしくは回復して続行させることができます.
* 引数に渡される壁紙情報はエラーハンドラにより修復される可能性があります.
* @param wallpaperInfo 壁紙情報、nullの場合はデフォルト設定が用いられる. * @param errorHandler エラーハンドラ、省略した場合は{@link #DEFAULT_ERROR_HANDLER}が用いられる. * @return 生成された壁紙オブジェクト * @throws WallpaperFactoryException 壁紙オブジェクトの生成に失敗したことを通知する例外 */ public Wallpaper createWallpaper(WallpaperInfo wallpaperInfo, ErrorHandler errorHandler) throws WallpaperFactoryException { if (wallpaperInfo == null) { return new Wallpaper(); } if (errorHandler == null) { errorHandler = defaultErrorHandler; } Wallpaper wallpaper = new Wallpaper(); try { // 背景画像の設定. WallpaperResourceType typ = wallpaperInfo.getType(); ImageResource imageResource = null; if (typ == WallpaperResourceType.FILE) { // 選択ファイルから File imageFile = wallpaperInfo.getFile(); if (imageFile == null || !imageFile.exists() || !imageFile.isFile() || !imageFile.canRead()) { // ハンドラによってエラーを通知するか、もしくは回復する. imageFile = errorHandler.missingImageFile(wallpaperInfo, imageFile); } if (imageFile != null) { imageResource = new FileImageResource(imageFile); } } else if (typ == WallpaperResourceType.PREDEFINED) { // リソースファイルから String resource = wallpaperInfo.getResource(); if (resource != null && resource.trim().length() > 0) { imageResource = new EmbeddedImageResource(resource); } } BufferedImage wallpaperImg = null; if (imageResource != null) { try { LoadedImage wallpaperLoadedImage = imageLoader.load(imageResource); wallpaperImg = wallpaperLoadedImage.getImage(); } catch (IOException ex) { // ハンドラによってエラーを通知するか、もしくは回復する. wallpaperImg = errorHandler.imageCreationFailed(wallpaperInfo, imageResource, ex); } } wallpaper.setWallpaperImage(wallpaperImg); // アルファ値 wallpaper.setWallpaperAlpha(wallpaperInfo.getAlpha()); // 背景色 wallpaper.setBackgroundColor(wallpaperInfo.getBackgroundColor()); } catch (RuntimeException ex) { // ハンドラによってエラーを通知するか、もしくは回復する. wallpaper = errorHandler.internalError(wallpaperInfo, wallpaper, ex); if (wallpaper == null) { throw ex; } } return wallpaper; } } /** * 壁紙を構築時に問題があった場合に例外を送出するエラーハンドラ.
* @author seraphy */ class WallpaperFactoryDefaultErrorHandler implements WallpaperFactory.ErrorHandler { public BufferedImage imageCreationFailed(WallpaperInfo wallpaperInfo, ImageResource imageResource, Throwable ex) throws WallpaperFactoryException { throw new WallpaperFactoryException("image creation failed: " + imageResource, ex); } public Wallpaper internalError(WallpaperInfo wallpaperInfo, Wallpaper wallpaper, Throwable ex) throws WallpaperFactoryException { throw new WallpaperFactoryException("internal error: " + (ex == null ? "" : ex.getMessage()), ex); } public File missingImageFile(WallpaperInfo wallpaperInfo, File file) throws WallpaperFactoryException { throw new WallpaperFactoryException("missing image file: " + file); } } CharacterManaJ/src/charactermanaj/ui/ImportWizardDialog.java0000644000175000017500000030757412560206305024375 0ustar paulliupaulliupackage charactermanaj.ui; import java.awt.BorderLayout; import java.awt.CardLayout; import java.awt.Color; import java.awt.Component; import java.awt.Container; import java.awt.Cursor; import java.awt.Dimension; import java.awt.Font; import java.awt.GridBagConstraints; import java.awt.GridBagLayout; import java.awt.Insets; import java.awt.Rectangle; import java.awt.Toolkit; import java.awt.dnd.DropTarget; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.ComponentAdapter; import java.awt.event.ComponentEvent; import java.awt.event.ComponentListener; import java.awt.event.KeyEvent; import java.awt.event.WindowAdapter; import java.awt.event.WindowEvent; import java.awt.image.BufferedImage; import java.io.File; import java.io.IOException; import java.io.Serializable; import java.net.URI; import java.sql.Timestamp; import java.text.MessageFormat; import java.util.AbstractCollection; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; import java.util.HashSet; import java.util.IdentityHashMap; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Properties; import java.util.Set; import javax.swing.AbstractAction; import javax.swing.Action; import javax.swing.ActionMap; import javax.swing.BorderFactory; import javax.swing.Box; import javax.swing.BoxLayout; import javax.swing.ButtonGroup; import javax.swing.InputMap; import javax.swing.JButton; import javax.swing.JCheckBox; import javax.swing.JComponent; import javax.swing.JDialog; import javax.swing.JFileChooser; import javax.swing.JFrame; import javax.swing.JLabel; import javax.swing.JOptionPane; import javax.swing.JPanel; import javax.swing.JPopupMenu; import javax.swing.JRadioButton; import javax.swing.JRootPane; import javax.swing.JScrollPane; import javax.swing.JTable; import javax.swing.JTextArea; import javax.swing.JTextField; import javax.swing.KeyStroke; import javax.swing.ListSelectionModel; import javax.swing.event.ChangeEvent; import javax.swing.event.ChangeListener; import javax.swing.event.DocumentEvent; import javax.swing.event.DocumentListener; import javax.swing.event.TableModelEvent; import javax.swing.event.TableModelListener; import javax.swing.table.TableCellRenderer; import javax.swing.table.TableColumnModel; import charactermanaj.Main; import charactermanaj.graphics.io.PNGFileImageHeader; import charactermanaj.model.AppConfig; import charactermanaj.model.CharacterData; import charactermanaj.model.PartsAuthorInfo; import charactermanaj.model.PartsCategory; import charactermanaj.model.PartsIdentifier; import charactermanaj.model.PartsManageData; import charactermanaj.model.PartsSet; import charactermanaj.model.PartsSpec; import charactermanaj.model.io.AbstractCharacterDataArchiveFile.CategoryLayerPair; import charactermanaj.model.io.AbstractCharacterDataArchiveFile.PartsImageContent; import charactermanaj.model.io.CharacterDataPersistent; import charactermanaj.model.io.ImportModel; import charactermanaj.ui.model.AbstractTableModelWithComboBoxModel; import charactermanaj.ui.progress.ProgressHandle; import charactermanaj.ui.progress.Worker; import charactermanaj.ui.progress.WorkerException; import charactermanaj.ui.progress.WorkerWithProgessDialog; import charactermanaj.ui.util.FileDropTarget; import charactermanaj.util.ErrorMessageHelper; import charactermanaj.util.LocalizedResourcePropertyLoader; /** * インポートウィザードダイアログ.
* * @author seraphy */ public class ImportWizardDialog extends JDialog { private static final long serialVersionUID = 1L; protected static final String STRINGS_RESOURCE = "languages/importwizdialog"; public static final int EXIT_PROFILE_UPDATED = 1; public static final int EXIT_PROFILE_CREATED = 2; public static final int EXIT_CANCELED = 0; /** * インポートウィザードの実行結果.
*/ private int exitCode = EXIT_CANCELED; /** * インポートされたキャラクターデータ.
*/ private CharacterData importedCharacterData; /** * 現在表示中もしくは選択中のプロファイル.
* 新規の場合はnull */ protected CharacterData current; private CardLayout mainPanelLayout; private ImportWizardCardPanel activePanel; private AbstractAction actNext; private AbstractAction actPrev; private AbstractAction actFinish; protected ImportFileSelectPanel importFileSelectPanel; protected ImportTypeSelectPanel importTypeSelectPanel; protected ImportPartsSelectPanel importPartsSelectPanel; protected ImportPresetSelectPanel importPresetSelectPanel; protected ImportModel importModel = new ImportModel(); /** * プロファイルにパーツデータ・プリセットデータをインポートします.
* * @param parent * 親フレーム * @param current * 更新対象となる現在のプロファイル(新規インポートの場合はnull) * @param initFiles * アーカイブファィルまたはディレクトリの初期選択、なければnullまたは空 */ public ImportWizardDialog(JFrame parent, CharacterData current, List initFiles) { super(parent, true); initComponent(parent, current); importFileSelectPanel.setSelectFile(initFiles); } /** * プロファイルにパーツデータ・プリセットデータをインポートします.
* * @param parent * 親ダイアログ * @param current * 選択していてるプロファイル、新規インポートの場合はnull */ public ImportWizardDialog(JDialog parent, CharacterData current) { super(parent, true); initComponent(parent, current); } /** * ウィザードダイアログのコンポーネントを初期化します.
* currentがnullの場合は新規インポート、そうでない場合は更新インポートとります。 * * @param parent * 親コンテナ * @param current * インポート対象プロファイル、新規の場合はnull */ private void initComponent(Component parent, CharacterData current) { this.current = current; setDefaultCloseOperation(JDialog.DO_NOTHING_ON_CLOSE); addWindowListener(new WindowAdapter() { @Override public void windowClosing(WindowEvent e) { onClose(); } }); Properties strings = LocalizedResourcePropertyLoader.getCachedInstance() .getLocalizedProperties(STRINGS_RESOURCE); // タイトル if (current == null) { setTitle(strings.getProperty("title.new")); } else { setTitle(strings.getProperty("title.update")); } // メインパネル Container contentPane = getContentPane(); contentPane.setLayout(new BorderLayout()); final JPanel mainPanel = new JPanel(); mainPanel.setBorder(BorderFactory.createEtchedBorder()); this.mainPanelLayout = new CardLayout(5, 5); mainPanel.setLayout(mainPanelLayout); contentPane.add(mainPanel, BorderLayout.CENTER); ChangeListener changeListener = new ChangeListener() { public void stateChanged(ChangeEvent e) { updateBtnPanelState(); } }; ComponentListener componentListener = new ComponentAdapter() { public void componentShown(ComponentEvent e) { onComponentShown((ImportWizardCardPanel) e.getComponent()); } }; // アクション this.actNext = new AbstractAction(strings.getProperty("btn.next")) { private static final long serialVersionUID = 1L; public void actionPerformed(ActionEvent e) { setEnableButtons(false); String nextPanelName = doNext(); if (nextPanelName != null) { mainPanelLayout.show(mainPanel, nextPanelName); } else { // 移動先ページ名なければ、現在のページでボタン状態を再設定する. // 移動先ページ名がある場合、実際に移動し表示されるまでディセーブルのままとする. updateBtnPanelState(); } } }; this.actPrev = new AbstractAction(strings.getProperty("btn.prev")) { private static final long serialVersionUID = 1L; public void actionPerformed(ActionEvent e) { setEnableButtons(false); String prevPanelName = doPrevious(); if (prevPanelName != null) { mainPanelLayout.show(mainPanel, prevPanelName); } else { updateBtnPanelState(); } } }; this.actFinish = new AbstractAction(strings.getProperty("btn.finish")) { private static final long serialVersionUID = 1L; public void actionPerformed(ActionEvent e) { onFinish(); } }; AbstractAction actCancel = new AbstractAction(strings.getProperty("btn.cancel")) { private static final long serialVersionUID = 1L; public void actionPerformed(ActionEvent e) { onClose(); } }; // ImportFileSelectPanel this.importFileSelectPanel = new ImportFileSelectPanel(); this.importFileSelectPanel.addComponentListener(componentListener); this.importFileSelectPanel.addChangeListener(changeListener); mainPanel.add(this.importFileSelectPanel, ImportFileSelectPanel.PANEL_NAME); // ImportTypeSelectPanel this.importTypeSelectPanel = new ImportTypeSelectPanel(); this.importTypeSelectPanel.addComponentListener(componentListener); this.importTypeSelectPanel.addChangeListener(changeListener); mainPanel.add(this.importTypeSelectPanel, ImportTypeSelectPanel.PANEL_NAME); // ImportPartsSelectPanel this.importPartsSelectPanel = new ImportPartsSelectPanel(); this.importPartsSelectPanel.addComponentListener(componentListener); this.importPartsSelectPanel.addChangeListener(changeListener); mainPanel.add(this.importPartsSelectPanel, ImportPartsSelectPanel.PANEL_NAME); // ImportPresetSelectPanel this.importPresetSelectPanel = new ImportPresetSelectPanel(); this.importPresetSelectPanel.addComponentListener(componentListener); this.importPresetSelectPanel.addChangeListener(changeListener); mainPanel.add(this.importPresetSelectPanel, ImportPresetSelectPanel.PANEL_NAME); // button panel JPanel btnPanel = new JPanel(); btnPanel.setBorder(BorderFactory.createEmptyBorder(3, 3, 3, 45)); GridBagLayout btnPanelLayout = new GridBagLayout(); btnPanel.setLayout(btnPanelLayout); actPrev.setEnabled(false); actNext.setEnabled(false); actFinish.setEnabled(false); GridBagConstraints gbc = new GridBagConstraints(); gbc.gridx = 0; gbc.gridy = 0; gbc.gridheight = 1; gbc.gridwidth = 1; gbc.anchor = GridBagConstraints.EAST; gbc.fill = GridBagConstraints.BOTH; gbc.ipadx = 0; gbc.ipady = 0; gbc.insets = new Insets(3, 3, 3, 3); gbc.weightx = 1.; gbc.weighty = 0.; btnPanel.add(Box.createHorizontalGlue(), gbc); gbc.gridx = Main.isLinuxOrMacOSX() ? 2 : 1; gbc.gridy = 0; gbc.weightx = 0.; btnPanel.add(new JButton(this.actPrev), gbc); gbc.gridx = Main.isLinuxOrMacOSX() ? 3 : 2; gbc.gridy = 0; JButton btnNext = new JButton(this.actNext); btnPanel.add(btnNext, gbc); gbc.gridx = Main.isLinuxOrMacOSX() ? 4 : 3; gbc.gridy = 0; btnPanel.add(new JButton(this.actFinish), gbc); gbc.gridx = Main.isLinuxOrMacOSX() ? 1 : 4; gbc.gridy = 0; JButton btnCancel = new JButton(actCancel); btnPanel.add(btnCancel, gbc); contentPane.add(btnPanel, BorderLayout.SOUTH); // インプットマップ/アクションマップ Toolkit tk = Toolkit.getDefaultToolkit(); JRootPane rootPane = getRootPane(); rootPane.setDefaultButton(btnNext); InputMap im = rootPane.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW); ActionMap am = rootPane.getActionMap(); im.put(KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0), "closeImportWizDialog"); im.put(KeyStroke.getKeyStroke(KeyEvent.VK_W, tk.getMenuShortcutKeyMask()), "closeImportWizDialog"); am.put("closeImportWizDialog", actCancel); // 表示 setSize(500, 550); setLocationRelativeTo(parent); mainPanelLayout.first(mainPanel); updateBtnPanelState(); } protected void onComponentShown(JPanel panel) { ImportWizardCardPanel activePanel = (ImportWizardCardPanel) panel; activePanel.onActive(this, this.activePanel); this.activePanel = activePanel; updateBtnPanelState(); } protected void updateBtnPanelState() { if (activePanel != null) { actPrev.setEnabled(activePanel.isReadyPrevious()); actNext.setEnabled(activePanel.isReadyNext()); actFinish.setEnabled(activePanel.isReadyFinish()); } else { setEnableButtons(false); } } public void setEnableButtons(boolean enabled) { actPrev.setEnabled(enabled); actNext.setEnabled(enabled); actFinish.setEnabled(enabled); } protected String doNext() { if (activePanel == null) { throw new IllegalStateException(); } String nextPanelName; setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); try { nextPanelName = activePanel.doNext(); } finally { setCursor(Cursor.getDefaultCursor()); } return nextPanelName; } protected String doPrevious() { if (activePanel == null) { throw new IllegalStateException(); } return activePanel.doPrevious(); } protected void onClose() { Properties strings = LocalizedResourcePropertyLoader.getCachedInstance() .getLocalizedProperties(STRINGS_RESOURCE); if (JOptionPane.showConfirmDialog(this, strings.getProperty("confirm.close"), strings.getProperty("confirm"), JOptionPane.YES_NO_OPTION, JOptionPane.QUESTION_MESSAGE) != JOptionPane.YES_OPTION) { return; } // アーカイブを閉じる. importFileSelectPanel.closeArchive(); // キャンセル this.exitCode = EXIT_CANCELED; this.importedCharacterData = null; // ウィンドウを閉じる dispose(); } /** * インポートの実行.
*/ protected void onFinish() { Properties strings = LocalizedResourcePropertyLoader.getCachedInstance() .getLocalizedProperties(ImportWizardDialog.STRINGS_RESOURCE); try { // 新規プロファイル作成、または更新の実行 setEnableButtons(false); setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); int exitCode; CharacterData importedCharacterData; try { if (current == null) { // 新規作成 importedCharacterData = createNewProfile(); exitCode = EXIT_PROFILE_CREATED; } else { // 更新 importedCharacterData = updateProfile(); exitCode = EXIT_PROFILE_UPDATED; } } finally { setCursor(Cursor.getDefaultCursor()); } // アーカイブを閉じる importFileSelectPanel.closeArchive(); // 完了メッセージ JOptionPane.showMessageDialog(this, strings.getProperty("complete")); // 完了後、ウィンドウを閉じる. this.exitCode = exitCode; this.importedCharacterData = importedCharacterData; dispose(); } catch (Exception ex) { ErrorMessageHelper.showErrorDialog(this, ex); // ディセーブルにしていたボタンをパネルの状態に戻す. updateBtnPanelState(); } } /** * ウィザードが閉じられた場合の終了コード. {@link #EXIT_PROFILE_UPDATED}であればプロファイルが更新されており,
* {@link #EXIT_PROFILE_CREATED}であればプロファイルが作成されている.
* {@link #EXIT_CANCELED}であればキャンセルされている.
* * @return 終了コード */ public int getExitCode() { return exitCode; } /** * 新規または更新されたプロファイル、キャンセルされた場合はnull * * @return プロファイル */ public CharacterData getImportedCharacterData() { return importedCharacterData; } /** * アーカイブからの新規プロファイルの作成 * * @return 作成された新規プロファイル * @throws IOException * 失敗 */ protected CharacterData createNewProfile() throws IOException { CharacterData cd = importModel.getCharacterData(); if (cd == null || !cd.isValid()) { throw new IllegalStateException("imported caharcer data is invalid." + cd); } CharacterDataPersistent persist = CharacterDataPersistent.getInstance(); CharacterData characterData = cd.duplicateBasicInfo(); // キャラクターセット名と作者名を設定する characterData.setName(importTypeSelectPanel.getCharacterName()); characterData.setAuthor(importTypeSelectPanel.getAuthor()); // プリセットをインポートする場合 characterData.clearPartsSets(false); if (importTypeSelectPanel.isImportPreset()) { for (PartsSet partsSet : importPresetSelectPanel.getSelectedPartsSets()) { PartsSet ps = partsSet.clone(); ps.setPresetParts(true); characterData.addPartsSet(ps); } characterData.setDefaultPartsSetId(importPresetSelectPanel.getPrefferedDefaultPartsSetId()); } // プロファイルの新規作成 // docBaseが設定されて返される. persist.createProfile(characterData); // インポートするパーツの更新 if (importTypeSelectPanel.isImportPartsImages()) { // パーツのコピー Collection partsImageContents = importPartsSelectPanel.getSelectedPartsImageContents(); importModel.copyPartsImageContents(partsImageContents, characterData); // パーツ管理情報の登録 PartsManageData partsManageData = importModel.getPartsManageData(); importModel.updatePartsManageData(partsImageContents, partsManageData, null, characterData); } // インポートするピクチャの更新 if (importTypeSelectPanel.isImportSampleImage()) { BufferedImage samplePicture = importModel.getSamplePicture(); if (samplePicture != null) { persist.saveSamplePicture(characterData, samplePicture); } } return characterData; } /** * プロファイルの更新 * * @return 更新されたプロファイル * @throws IOException * 失敗 */ protected CharacterData updateProfile() throws IOException { if (current == null || !current.isValid()) { throw new IllegalStateException("current profile is not valid. :" + current); } CharacterDataPersistent persist = CharacterDataPersistent.getInstance(); CharacterData characterData = current.duplicateBasicInfo(); boolean imported = false; boolean modCharacterDef = false; boolean modFavories = false; // インポートするパーツの更新 if (importTypeSelectPanel.isImportPartsImages()) { // パーツのコピー Collection partsImageContents = importPartsSelectPanel.getSelectedPartsImageContents(); importModel.copyPartsImageContents(partsImageContents, characterData); // パーツ管理情報の追記・更新 PartsManageData partsManageData = importModel.getPartsManageData(); importModel.updatePartsManageData(partsImageContents, partsManageData, characterData, characterData); imported = true; } // インポートするピクチャの更新 if (importTypeSelectPanel.isImportSampleImage()) { BufferedImage samplePicture = importModel.getSamplePicture(); if (samplePicture != null) { persist.saveSamplePicture(characterData, samplePicture); imported = true; } } // インポートするパーツセットの更新 if (importTypeSelectPanel.isImportPreset()) { for (PartsSet partsSet : importPresetSelectPanel.getSelectedPartsSets()) { PartsSet ps = partsSet.clone(); ps.setPresetParts(false); characterData.addPartsSet(ps); } imported = true; modCharacterDef = true; modFavories = true; } // 説明の更新 if (importTypeSelectPanel.isAddDescription() && imported) { URI archivedFile = importModel.getImportSource(); String note = importTypeSelectPanel.getAdditionalDescription(); if (note != null && note.length() > 0) { String description = characterData.getDescription(); if (description == null) { description = ""; } String lf = System.getProperty("line.separator"); Timestamp tm = new Timestamp(System.currentTimeMillis()); description += lf + "--- import: " + tm + " : " + archivedFile + " ---" + lf; description += note + lf; characterData.setDescription(description); modCharacterDef = true; } } // キャラクター定義の更新 if (modCharacterDef) { persist.updateProfile(characterData); // キャラクター定義の構造に変化なし current.setDescription(characterData.getDescription()); } // お気に入りの更新 if (modFavories) { persist.saveFavorites(characterData); } return characterData; } } /** * タブの抽象基底クラス.
* * @author seraphy */ abstract class ImportWizardCardPanel extends JPanel { private static final long serialVersionUID = 1L; private LinkedList listeners = new LinkedList(); public void addChangeListener(ChangeListener l) { if (l != null) { listeners.add(l); } } public void removeChangeListener(ChangeListener l) { if (l != null) { listeners.remove(l); } } public void fireChangeEvent() { ChangeEvent e = new ChangeEvent(this); for (ChangeListener l : listeners) { l.stateChanged(e); } } public void onActive(ImportWizardDialog parent, ImportWizardCardPanel previousPanel) { // なにもしない } public boolean isReadyPrevious() { return false; } public boolean isReadyNext() { return false; } public boolean isReadyFinish() { return false; } public String doNext() { throw new UnsupportedOperationException(); } public String doPrevious() { throw new UnsupportedOperationException(); } } /** * ファイル選択パネル * * @author seraphy */ class ImportFileSelectPanel extends ImportWizardCardPanel { private static final long serialVersionUID = 1L; public static final String PANEL_NAME = "fileSelectPanel"; /** * アーカイブ用ファイルダイアログ */ private static ArchiveFileDialog archiveFileDialog = new ArchiveFileDialog(); private ImportWizardDialog parent; /** * ファイル名を指定してインポート */ private JRadioButton radioArchiveFile; /** * ファイル名入力ボックス */ private JTextField txtArchiveFile; /** * ファイル選択ボタン */ private Action actChooseFile; /** * ディレクトリを指定してインポート */ private JRadioButton radioDirectory; /** * ディレクトリ入力ボックス */ private JTextField txtDirectory; /** * ディレクトリ選択ボタン */ private Action actChooseDirectory; /* 以下、対象ファイルの読み取り結果 */ public ImportFileSelectPanel() { setLayout(new BorderLayout()); Properties strings = LocalizedResourcePropertyLoader.getCachedInstance() .getLocalizedProperties(ImportWizardDialog.STRINGS_RESOURCE); DocumentListener documentListener = new DocumentListener() { public void removeUpdate(DocumentEvent e) { fireEvent(); } public void insertUpdate(DocumentEvent e) { fireEvent(); } public void changedUpdate(DocumentEvent e) { fireEvent(); } protected void fireEvent() { fireChangeEvent(); } }; txtArchiveFile = new JTextField(); txtDirectory = new JTextField(); txtArchiveFile.getDocument().addDocumentListener(documentListener); txtDirectory.getDocument().addDocumentListener(documentListener); actChooseFile = new AbstractAction(strings.getProperty("browse")) { private static final long serialVersionUID = 1L; public void actionPerformed(ActionEvent e) { onChooseFile(); } }; actChooseDirectory = new AbstractAction(strings.getProperty("browse")) { private static final long serialVersionUID = 1L; public void actionPerformed(ActionEvent e) { onChooseDirectory(); } }; JPanel fileChoosePanel = new JPanel(); GridBagLayout fileChoosePanelLayout = new GridBagLayout(); fileChoosePanel.setLayout(fileChoosePanelLayout); radioArchiveFile = new JRadioButton(strings.getProperty("importingArchiveFile")); radioDirectory = new JRadioButton(strings.getProperty("importingDirectory")); ChangeListener radioChangeListener = new ChangeListener() { public void stateChanged(ChangeEvent e) { updateUIState(); fireChangeEvent(); } }; radioArchiveFile.addChangeListener(radioChangeListener); radioDirectory.addChangeListener(radioChangeListener); ButtonGroup btnGroup = new ButtonGroup(); btnGroup.add(radioArchiveFile); btnGroup.add(radioDirectory); // アーカイブからのインポートをデフォルトとする radioArchiveFile.setSelected(true); GridBagConstraints gbc = new GridBagConstraints(); gbc.gridx = 0; gbc.gridy = 0; gbc.weightx = 1.; gbc.weighty = 0.; gbc.gridheight = 1; gbc.gridwidth = 3; gbc.ipadx = 0; gbc.ipady = 0; gbc.insets = new Insets(3, 3, 3, 3); gbc.anchor = GridBagConstraints.WEST; gbc.fill = GridBagConstraints.BOTH; fileChoosePanel.add(radioArchiveFile, gbc); gbc.gridx = 0; gbc.gridy = 1; gbc.gridwidth = 1; gbc.ipadx = 45; gbc.weightx = 0; fileChoosePanel.add(Box.createHorizontalGlue(), gbc); gbc.gridx = 1; gbc.gridy = 1; gbc.ipadx = 0; gbc.weightx = 1.; fileChoosePanel.add(txtArchiveFile, gbc); gbc.gridx = 2; gbc.gridy = 1; gbc.ipadx = 0; gbc.weightx = 0.; fileChoosePanel.add(new JButton(actChooseFile), gbc); gbc.gridx = 0; gbc.gridy = 2; gbc.ipadx = 0; gbc.gridwidth = 3; gbc.weightx = 1.; fileChoosePanel.add(radioDirectory, gbc); gbc.gridx = 0; gbc.gridy = 3; gbc.ipadx = 45; gbc.gridwidth = 1; gbc.weightx = 0; fileChoosePanel.add(Box.createHorizontalGlue(), gbc); gbc.gridx = 1; gbc.gridy = 3; gbc.ipadx = 0; gbc.weightx = 1.; fileChoosePanel.add(txtDirectory, gbc); gbc.gridx = 2; gbc.gridy = 3; gbc.ipadx = 0; gbc.weightx = 0.; fileChoosePanel.add(new JButton(actChooseDirectory), gbc); gbc.gridx = 0; gbc.gridy = 4; gbc.ipadx = 0; gbc.gridwidth = 3; gbc.weightx = 1.; gbc.weighty = 1.; fileChoosePanel.add(Box.createGlue(), gbc); add(fileChoosePanel, BorderLayout.CENTER); // ドロップターゲット new DropTarget(this, new FileDropTarget() { @Override protected void onDropFiles(List dropFiles) { if (dropFiles == null || dropFiles.isEmpty()) { return; } setSelectFile(dropFiles); } @Override protected void onException(Exception ex) { ErrorMessageHelper.showErrorDialog(ImportFileSelectPanel.this, ex); } }); updateUIState(); } /** * アーカイブファイルまたはディレクトリを選択状態とする.
* nullの場合は選択を解除する. * * @param dropFile * アーカイブファイルまたはディレクトリ、もしくはnull */ public void setSelectFile(List dropFiles) { File dropFile = null; if (dropFiles != null && dropFiles.size() > 0) { dropFile = dropFiles.get(0); } if (dropFile == null) { // 選択なしの場合 txtDirectory.setText(""); txtArchiveFile.setText(""); radioDirectory.setSelected(false); radioArchiveFile.setSelected(false); } else if (dropFile.isDirectory()) { // ディレクトリの場合 txtDirectory.setText(dropFile.getPath()); radioDirectory.setSelected(true); } else if (dropFile.isFile()) { // ファイルの場合 txtArchiveFile.setText(dropFile.getPath()); radioArchiveFile.setSelected(true); } } protected void updateUIState() { boolean enableArchiveFile = radioArchiveFile.isSelected(); boolean enableDirectory = radioDirectory.isSelected(); txtArchiveFile.setEnabled(enableArchiveFile); actChooseFile.setEnabled(enableArchiveFile); txtDirectory.setEnabled(enableDirectory); actChooseDirectory.setEnabled(enableDirectory); } protected void onChooseFile() { File initFile = null; if (txtArchiveFile.getText().trim().length() > 0) { initFile = new File(txtArchiveFile.getText()); } File file = archiveFileDialog.showOpenDialog(this, initFile); if (file != null) { txtArchiveFile.setText(file.getPath()); fireChangeEvent(); } } protected void onChooseDirectory() { String directoryTxt = txtDirectory.getText(); JFileChooser dirChooser = new JFileChooser(directoryTxt); dirChooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY); if (dirChooser.showOpenDialog(this) != JFileChooser.APPROVE_OPTION) { return; } File dir = dirChooser.getSelectedFile(); if (dir != null) { txtDirectory.setText(dir.getPath()); fireChangeEvent(); } } @Override public boolean isReadyNext() { if (radioArchiveFile.isSelected()) { String fileTxt = txtArchiveFile.getText(); if (fileTxt != null && fileTxt.trim().length() > 0) { return true; } } else if (radioDirectory.isSelected()) { String directoryTxt = txtDirectory.getText(); if (directoryTxt != null && directoryTxt.trim().length() > 0) { return true; } } return false; } @Override public void onActive(ImportWizardDialog parent, ImportWizardCardPanel previousPanel) { this.parent = parent; // 開いているアーカイブがあれば閉じる closeArchive(); } /** * 開いているアーカイブがあればクローズする. */ public void closeArchive() { try { parent.importModel.closeImportSource(); } catch (IOException ex) { ErrorMessageHelper.showErrorDialog(this, ex); // エラーが発生しても、とりあえず無視する. } } @Override public String doNext() { if (!isReadyNext()) { return null; } Properties strings = LocalizedResourcePropertyLoader.getCachedInstance() .getLocalizedProperties(ImportWizardDialog.STRINGS_RESOURCE); URI importArchive; if (radioArchiveFile.isSelected()) { // ファイルによるインポート File file = new File(txtArchiveFile.getText()); if (!file.exists() || !file.isFile()) { JOptionPane.showMessageDialog(this, strings .getProperty("fileNotFound"), "ERROR", JOptionPane.ERROR_MESSAGE); return null; } importArchive = file.toURI(); } else if (radioDirectory.isSelected()) { // ディレクトリによるインポート File file = new File(txtDirectory.getText()); if ( !file.exists() || !file.isDirectory()) { JOptionPane.showMessageDialog(this, strings .getProperty("directoryNotFound"), "ERROR", JOptionPane.ERROR_MESSAGE); return null; } importArchive = file.toURI(); } else { // それ以外はサポートしていない. return null; } try { parent.importModel.openImportSource(importArchive, parent.current); // ワーカースレッドでアーカイブの読み込みを行う. Worker worker = new Worker() { public Void doWork(ProgressHandle progressHandle) throws IOException { parent.importModel.loadContents(progressHandle); return null; } }; WorkerWithProgessDialog dlg = new WorkerWithProgessDialog(parent, worker); dlg.startAndWait(); // 読み込めたら次ページへ return ImportTypeSelectPanel.PANEL_NAME; } catch (WorkerException ex) { ErrorMessageHelper.showErrorDialog(this, ex.getCause()); } catch (Exception ex) { ErrorMessageHelper.showErrorDialog(this, ex); } return null; } } class URLTableRow implements Serializable { private static final long serialVersionUID = 3452190266438145049L; private String downloadURL; private String author; public String getAuthor() { return author; } public String getDownloadURL() { return downloadURL; } public void setAuthor(String author) { this.author = author; } public void setDownloadURL(String downloadURL) { this.downloadURL = downloadURL; } } class URLTableModel extends AbstractTableModelWithComboBoxModel { private static final long serialVersionUID = 7075478118793390224L; private static final String[] COLUMN_NAMES; private static final int[] COLUMN_WIDTHS; static { COLUMN_NAMES = new String[] { "作者", "URL", }; COLUMN_WIDTHS = new int[] { 100, 300, }; } @Override public String getColumnName(int column) { return COLUMN_NAMES[column]; } public int getColumnCount() { return COLUMN_NAMES.length; } public Object getValueAt(int rowIndex, int columnIndex) { URLTableRow row = getRow(rowIndex); switch (columnIndex) { case 0: return row.getAuthor(); case 1: return row.getDownloadURL(); } return ""; } @Override public Class getColumnClass(int columnIndex) { return String.class; } public void adjustColumnModel(TableColumnModel columnModel) { for (int idx = 0; idx < COLUMN_WIDTHS.length; idx++) { columnModel.getColumn(idx).setPreferredWidth(COLUMN_WIDTHS[idx]); } } public void initModel(CharacterData characterData) { clear(); HashMap downloadUrlsMap = new HashMap(); if (characterData != null) { for (PartsCategory category : characterData.getPartsCategories()) { for (Map.Entry entry : characterData .getPartsSpecMap(category).entrySet()) { PartsSpec partsSpec = entry.getValue(); String author = partsSpec.getAuthor(); String downloadURL = partsSpec.getDownloadURL(); if (downloadURL != null && downloadURL.trim().length() > 0) { if (author == null || author.trim().length() == 0) { author = ""; } downloadUrlsMap.put(downloadURL, author); } } } } for (Map.Entry entry : downloadUrlsMap.entrySet()) { String downloadURL = entry.getKey(); String author = entry.getValue(); URLTableRow row = new URLTableRow(); row.setDownloadURL(downloadURL); row.setAuthor(author); addRow(row); } Collections.sort(elements, new Comparator() { public int compare(URLTableRow o1, URLTableRow o2) { int ret = o1.getAuthor().compareTo(o2.getAuthor()); if (ret == 0) { ret = o1.getDownloadURL().compareTo(o2.getDownloadURL()); } return ret; } }); fireTableDataChanged(); } } /** * ファイル選択パネル * * @author seraphy */ class ImportTypeSelectPanel extends ImportWizardCardPanel { private static final long serialVersionUID = 1L; public static final String PANEL_NAME = "importTypeSelectPanel"; private ImportWizardDialog parent; private SamplePicturePanel samplePicturePanel; private JTextField txtCharacterId; private JTextField txtCharacterRev; private JTextField txtCharacterName; private JTextField txtAuthor; private JTextArea txtDescription; private JCheckBox chkPartsImages; private JCheckBox chkPresets; private JCheckBox chkSampleImage; private JCheckBox chkAddDescription; private String additionalDescription; /* 以下、選択結果 */ public ImportTypeSelectPanel() { Properties strings = LocalizedResourcePropertyLoader.getCachedInstance() .getLocalizedProperties(ImportWizardDialog.STRINGS_RESOURCE); GridBagLayout basicPanelLayout = new GridBagLayout(); GridBagConstraints gbc = new GridBagConstraints(); setLayout(basicPanelLayout); JPanel contentsSpecPanel = new JPanel(); contentsSpecPanel.setBorder(BorderFactory.createCompoundBorder( BorderFactory.createEmptyBorder(5, 5, 5, 5), BorderFactory .createTitledBorder(strings.getProperty("basic.contentsSpec")))); BoxLayout contentsSpecPanelLayout = new BoxLayout(contentsSpecPanel, BoxLayout.PAGE_AXIS); contentsSpecPanel.setLayout(contentsSpecPanelLayout); chkPartsImages = new JCheckBox(strings.getProperty("basic.chk.partsImages")); chkPresets = new JCheckBox(strings.getProperty("basic.chk.presets")); chkSampleImage = new JCheckBox(strings.getProperty("basic.chk.samplePicture")); contentsSpecPanel.add(chkPartsImages); contentsSpecPanel.add(chkPresets); contentsSpecPanel.add(chkSampleImage); // JPanel archiveInfoPanel = new JPanel(); archiveInfoPanel.setBorder(BorderFactory.createCompoundBorder(BorderFactory .createEmptyBorder(5, 5, 5, 5), BorderFactory .createTitledBorder(strings.getProperty("basic.archiveInfo")))); Dimension archiveInfoPanelMinSize = new Dimension(300, 200); archiveInfoPanel.setMinimumSize(archiveInfoPanelMinSize); archiveInfoPanel.setPreferredSize(archiveInfoPanelMinSize); GridBagLayout commentPanelLayout = new GridBagLayout(); archiveInfoPanel.setLayout(commentPanelLayout); gbc.gridx = 0; gbc.gridy = 0; gbc.gridheight = 1; gbc.gridwidth = 2; gbc.weightx = 0.; gbc.weighty = 0.; gbc.insets = new Insets(3, 3, 3, 3); gbc.anchor = GridBagConstraints.WEST; gbc.fill = GridBagConstraints.BOTH; gbc.gridx = 0; gbc.gridy = 1; gbc.gridheight = 1; gbc.gridwidth = 1; gbc.weightx = 0.; gbc.weighty = 0.; archiveInfoPanel.add(new JLabel(strings.getProperty("basic.profileId"), JLabel.RIGHT), gbc); gbc.gridx = 1; gbc.gridy = 1; gbc.gridheight = 1; gbc.gridwidth = 1; gbc.weightx = 0.; gbc.weighty = 0.; txtCharacterId = new JTextField(); txtCharacterId.setEditable(false); // 読み取り専用 txtCharacterId.setEnabled(false); archiveInfoPanel.add(txtCharacterId, gbc); gbc.gridx = 0; gbc.gridy = 2; gbc.gridheight = 1; gbc.gridwidth = 1; gbc.weightx = 0.; gbc.weighty = 0.; archiveInfoPanel.add(new JLabel(strings.getProperty("basic.profileRev"), JLabel.RIGHT), gbc); gbc.gridx = 1; gbc.gridy = 2; gbc.gridheight = 1; gbc.gridwidth = 1; gbc.weightx = 0.; gbc.weighty = 0.; txtCharacterRev = new JTextField(); txtCharacterRev.setEditable(false); // 読み取り専用 txtCharacterRev.setEnabled(false); archiveInfoPanel.add(txtCharacterRev, gbc); gbc.gridx = 0; gbc.gridy = 3; gbc.gridheight = 1; gbc.gridwidth = 1; gbc.weightx = 0.; gbc.weighty = 0.; archiveInfoPanel.add(new JLabel(strings.getProperty("basic.profileName"), JLabel.RIGHT), gbc); gbc.gridx = 1; gbc.gridy = 3; gbc.gridheight = 1; gbc.gridwidth = 1; gbc.weightx = 0.; gbc.weighty = 0.; txtCharacterName = new JTextField(); archiveInfoPanel.add(txtCharacterName, gbc); gbc.gridx = 0; gbc.gridy = 4; gbc.gridheight = 1; gbc.gridwidth = 1; gbc.weightx = 0.; gbc.weighty = 0.; archiveInfoPanel.add( new JLabel(strings.getProperty("author"), JLabel.RIGHT), gbc); gbc.gridx = 1; gbc.gridy = 4; gbc.gridwidth = 1; gbc.weightx = 1.; txtAuthor = new JTextField(); archiveInfoPanel.add(txtAuthor, gbc); gbc.gridx = 0; gbc.gridy = 5; gbc.gridwidth = 1; gbc.gridheight = 1; gbc.weightx = 0.; archiveInfoPanel.add(new JLabel(strings.getProperty("description"), JLabel.RIGHT), gbc); gbc.gridx = 1; gbc.gridy = 5; gbc.gridwidth = 1; gbc.gridheight = 5; gbc.weighty = 1.; gbc.weightx = 1.; txtDescription = new JTextArea(); // 説明は更新可能にしておく。 archiveInfoPanel.add(new JScrollPane(txtDescription), gbc); gbc.gridx = 0; gbc.gridy = 10; gbc.gridheight = 1; gbc.gridwidth = 2; gbc.weightx = 0.; gbc.weighty = 0.; gbc.weighty = 0.; gbc.weightx = 0.; chkAddDescription = new JCheckBox(strings.getProperty("appendDescription")); archiveInfoPanel.add(chkAddDescription, gbc); // / samplePicturePanel = new SamplePicturePanel(); JScrollPane samplePicturePanelSP = new JScrollPane(samplePicturePanel); samplePicturePanelSP.setBorder(null); JPanel samplePictureTitledPanel = new JPanel(new BorderLayout()); samplePictureTitledPanel.add(samplePicturePanelSP, BorderLayout.CENTER); samplePictureTitledPanel.setBorder(BorderFactory.createCompoundBorder( BorderFactory.createEmptyBorder(5, 5, 5, 5), BorderFactory .createTitledBorder(strings .getProperty("basic.sampleImage")))); // / gbc.gridx = 0; gbc.gridy = 0; gbc.gridheight = 1; gbc.gridwidth = 2; gbc.weightx = 1.; gbc.weighty = 0.; gbc.anchor = GridBagConstraints.WEST; gbc.fill = GridBagConstraints.BOTH; add(contentsSpecPanel, gbc); gbc.gridx = 0; gbc.gridy = 1; gbc.gridheight = 1; gbc.gridwidth = 1; gbc.weightx = 0.; gbc.weighty = 1.; gbc.anchor = GridBagConstraints.WEST; gbc.fill = GridBagConstraints.BOTH; add(archiveInfoPanel, gbc); gbc.gridx = 1; gbc.gridy = 1; gbc.gridheight = 1; gbc.gridwidth = 1; gbc.weightx = 1; gbc.weighty = 1.; gbc.anchor = GridBagConstraints.WEST; gbc.fill = GridBagConstraints.BOTH; add(samplePictureTitledPanel, gbc); // アクションリスナ ActionListener modListener = new ActionListener() { public void actionPerformed(ActionEvent e) { fireChangeEvent(); } }; chkPartsImages.addActionListener(modListener); chkPresets.addActionListener(modListener); chkSampleImage.addActionListener(modListener); chkAddDescription.addActionListener(modListener); } @Override public void onActive(ImportWizardDialog parent, ImportWizardCardPanel previousPanel) { this.parent = parent; if (previousPanel == parent.importPartsSelectPanel) { return; } Properties strings = LocalizedResourcePropertyLoader.getCachedInstance() .getLocalizedProperties(ImportWizardDialog.STRINGS_RESOURCE); // 呼び出しもと情報 CharacterData current = parent.current; // キャラクター定義情報 CharacterData cd = parent.importModel.getCharacterData(); String readme; // 開いているか選択しているプロファイルが有効であれば更新可能 final boolean updatable = (current != null && current.isValid()); // 新規の場合でインポートもとが有効なキャラクターセットであれば作成可能 final boolean creatable = (current == null && cd != null && cd.isValid()); // 新規作成の場合はキャラクター定義名と作者名を更新可能とする txtCharacterName.setEnabled(current == null); txtCharacterName.setEditable(current == null); txtAuthor.setEditable(current == null); txtAuthor.setEnabled(current == null); // ID、REVが一致するか? boolean matchID = false; boolean matchREV = false; if (cd != null && cd.isValid()) { txtCharacterId.setText(cd.getId()); txtCharacterRev.setText(cd.getRev()); txtCharacterName.setText(cd.getName()); if (current != null) { // 既存のプロファイルを選択していてインポート結果のキャラクター定義がある場合はID, REVを比較する. matchID = current.getId() == null ? cd.getId() == null : current.getId().equals(cd.getId()); matchREV = current.getRev() == null ? cd.getRev() == null : current.getRev().equals(cd.getRev()); } else { // 既存のプロファイルが存在しない場合は、ID,REVの比較は成功とみなす matchID = true; matchREV = true; } AppConfig appConfig = AppConfig.getInstance(); Color invalidBgColor = appConfig.getInvalidBgColor(); txtCharacterId.setBackground(matchID ? getBackground() : invalidBgColor); txtCharacterRev.setBackground(matchREV ? getBackground() : invalidBgColor); txtAuthor.setText(cd.getAuthor()); readme = cd.getDescription(); } else { // ID, REV等は存在しないので空にする txtCharacterId.setText(""); txtCharacterRev.setText(""); txtCharacterName.setText(""); txtAuthor.setText(""); // readmeで代用 readme = parent.importModel.getReadme(); } // 説明を追記する. boolean existsReadme = (readme != null && readme.trim().length() > 0); additionalDescription = existsReadme ? readme : ""; txtDescription.setText(additionalDescription); chkAddDescription.setEnabled((updatable || creatable) && existsReadme); chkAddDescription.setSelected((updatable || creatable) && existsReadme); // プリセットまたはお気に入りが存在するか? boolean hasPresetOrFavorites = (cd == null) ? false : !cd.getPartsSets().isEmpty(); chkPresets.setEnabled(hasPresetOrFavorites); chkPresets.setSelected(hasPresetOrFavorites); // パーツイメージ Collection partsImageContentsMap = parent.importModel.getPartsImageContents(); // パーツが存在するか? boolean hasParts = !partsImageContentsMap.isEmpty(); chkPartsImages.setEnabled(hasParts); chkPartsImages.setSelected(hasParts); // サンプルピクチャ BufferedImage samplePicture = parent.importModel.getSamplePicture(); if (samplePicture != null && (updatable || creatable)) { // サンプルピクチャが存在し、インポートか新規作成が可能であれば有効にする. samplePicturePanel.setSamplePicture(samplePicture); chkSampleImage.setEnabled(true); chkSampleImage.setSelected(current == null); // 新規作成の場合のみデフォルトでON } else { samplePicturePanel.setSamplePicture(samplePicture); chkSampleImage.setEnabled(false); chkSampleImage.setSelected(false); } // パーツまたはお気に入り・プリセットが存在する場合、 // および、新規の場合はキャラクター定義が存在する場合はコンテンツ有り boolean hasContents = hasParts || hasPresetOrFavorites || (current == null && cd != null && cd.isValid()); if (!hasContents) { JOptionPane.showMessageDialog(this, strings.getProperty("noContents")); } else if (cd == null) { JOptionPane.showMessageDialog(this, strings.getProperty("notFormalArchive")); } else if (!matchID) { String fmt = strings.getProperty("unmatchedProfileId"); String msg = MessageFormat.format(fmt, cd.getId() == null ? "" : cd.getId()); JOptionPane.showMessageDialog(this, msg); } else if (!matchREV) { String fmt = strings.getProperty("unmatchedProfileRev"); String msg = MessageFormat.format(fmt, cd.getRev() == null ? "" : cd.getRev()); JOptionPane.showMessageDialog(this, msg); } } public boolean isImportPreset() { return chkPresets.isSelected(); } public boolean isImportPartsImages() { return chkPartsImages.isSelected(); } public boolean isImportSampleImage() { return chkSampleImage.isSelected(); } public boolean isAddDescription() { return chkAddDescription.isSelected(); } /** * 説明として追加するドキュメント.
* これはユーザーが編集可能であり、ユーザー編集後の値が取得される.
* * @return 説明として追加するドキュメント */ public String getAdditionalDescription() { return txtDescription.getText(); } /** * キャラクター定義名を取得する. * * @return キャラクター定義名 */ public String getCharacterName() { return txtCharacterName.getText(); } /** * 作者名を取得する. * * @return 作者名 */ public String getAuthor() { return txtAuthor.getText(); } @Override public boolean isReadyPrevious() { return true; } @Override public String doPrevious() { return ImportFileSelectPanel.PANEL_NAME; } @Override public boolean isReadyNext() { if (isImportPartsImages() || isImportPreset()) { // パーツイメージの選択もしくはパーツセットの選択を指定している場合は次へ進む return true; } return false; } @Override public boolean isReadyFinish() { if (!isImportPartsImages() && !isImportPreset()) { if ((parent != null && parent.current == null) || isImportSampleImage()) { // 新規プロファイル作成か、サンプルイメージの更新のみで // イメージもパーツセットもいらなければ、ただちに作成可能. return true; } } return false; } @Override public String doNext() { return ImportPartsSelectPanel.PANEL_NAME; } } /** * パーツ選択パネル * * @author seraphy */ class ImportPartsSelectPanel extends ImportWizardCardPanel { private static final long serialVersionUID = 1L; public static final String PANEL_NAME = "importPartsSelectPanel"; private ImportWizardDialog parent; private ImportPartsTableModel partsTableModel; private JPanel profileSizePanel; private JTextField txtProfileHeight; private int profileWidth; private int profileHeight; private JTextField txtProfileWidth; private JTable partsTable; private Action actSelectAll; private Action actDeselectAll; private Action actSort; private Action actSortByTimestamp; public ImportPartsSelectPanel() { Properties strings = LocalizedResourcePropertyLoader.getCachedInstance() .getLocalizedProperties(ImportWizardDialog.STRINGS_RESOURCE); setLayout(new BorderLayout()); profileSizePanel = new JPanel(); GridBagLayout profileSizePanelLayout = new GridBagLayout(); profileSizePanel.setLayout(profileSizePanelLayout); profileSizePanel.setBorder(BorderFactory .createTitledBorder("プロファイルのサイズ")); GridBagConstraints gbc = new GridBagConstraints(); gbc.gridx = 0; gbc.gridy = 0; gbc.gridheight = 1; gbc.gridwidth = 1; gbc.anchor = GridBagConstraints.EAST; gbc.fill = GridBagConstraints.BOTH; gbc.insets = new Insets(3, 3, 3, 3); gbc.weightx = 0.; gbc.weighty = 0.; gbc.ipadx = 0; gbc.ipady = 0; profileSizePanel.add(new JLabel("幅:", JLabel.RIGHT), gbc); txtProfileWidth = new JTextField(); txtProfileWidth.setEditable(false); gbc.gridx = 1; gbc.gridy = 0; profileSizePanel.add(txtProfileWidth, gbc); gbc.gridx = 2; gbc.gridy = 0; profileSizePanel.add(new JLabel("高さ:", JLabel.RIGHT), gbc); txtProfileHeight = new JTextField(); txtProfileHeight.setEditable(false); gbc.gridx = 3; gbc.gridy = 0; profileSizePanel.add(txtProfileHeight, gbc); gbc.gridx = 4; gbc.gridy = 0; gbc.weightx = 1.; profileSizePanel.add(Box.createHorizontalGlue(), gbc); add(profileSizePanel, BorderLayout.NORTH); partsTableModel = new ImportPartsTableModel(); partsTableModel.addTableModelListener(new TableModelListener() { public void tableChanged(TableModelEvent e) { fireChangeEvent(); } }); AppConfig appConfig = AppConfig.getInstance(); final Color disabledForeground = appConfig.getDisabledCellForgroundColor(); partsTable = new JTable(partsTableModel) { private static final long serialVersionUID = 1L; @Override public Component prepareRenderer(TableCellRenderer renderer, int row, int column) { Component comp = super.prepareRenderer(renderer, row, column); if (comp instanceof JCheckBox) { // BooleanのデフォルトのレンダラーはJCheckBoxを継承したJTable$BooleanRenderer comp.setEnabled(isCellEditable(row, column) && isEnabled()); } // 行モデル取得 ImportPartsTableModel model = (ImportPartsTableModel) getModel(); ImportPartsModel rowModel = model.getRow(row); Long lastModifiedAtCur = rowModel.getLastModifiedAtCurrentProfile(); if (lastModifiedAtCur != null) { // 既存のパーツが存在すれば太字 comp.setFont(getFont().deriveFont(Font.BOLD)); } else { // 新規パーツであれば通常フォント comp.setFont(getFont()); } // 列ごとの警告の判定 boolean warnings = false; if (column == ImportPartsTableModel.COLUMN_LASTMODIFIED) { // 既存のほうが日付が新しければワーニング if (lastModifiedAtCur != null && rowModel.getLastModified() < lastModifiedAtCur.longValue()) { warnings = true; } } else if (column == ImportPartsTableModel.COLUMN_ALPHA) { // アルファ情報がない画像は警告 if (!rowModel.isAlphaColor()) { warnings = true; } } else if (column == ImportPartsTableModel.COLUMN_SIZE) { // プロファイルの画像サイズと一致しないか、不揃いな画像であれば警告 if (rowModel.isUnmatchedSize() || profileWidth != rowModel.getWidth() || profileHeight != rowModel.getHeight()) { warnings = true; } } // 前景色、ディセーブル時は灰色 Color foregroundColor = isCellSelected(row, column) ? getSelectionForeground() : getForeground(); comp.setForeground(isEnabled() ? foregroundColor : disabledForeground); // 背景色、警告行は赤色に if (warnings) { AppConfig appConfig = AppConfig.getInstance(); Color invalidBgColor = appConfig.getInvalidBgColor(); comp.setBackground(invalidBgColor); } else { if (isCellSelected(row, column)) { comp.setBackground(getSelectionBackground()); } else { comp.setBackground(getBackground()); } } return comp; } }; partsTable.setAutoResizeMode(JTable.AUTO_RESIZE_OFF); partsTable.setShowGrid(true); partsTable.setGridColor(appConfig.getGridColor()); partsTable.putClientProperty("terminateEditOnFocusLost", Boolean.TRUE); partsTableModel.adjustColumnModel(partsTable.getColumnModel()); partsTable.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION); partsTable.setRowSelectionAllowed(true); Action actPartsSetCheck = new AbstractAction(strings.getProperty("parts.popup.check")) { private static final long serialVersionUID = 1L; public void actionPerformed(ActionEvent e) { int[] selRows = partsTable.getSelectedRows(); partsTableModel.setCheck(selRows, true); } }; Action actPartsUnsetCheck = new AbstractAction(strings.getProperty("parts.popup.uncheck")) { private static final long serialVersionUID = 1L; public void actionPerformed(ActionEvent e) { int[] selRows = partsTable.getSelectedRows(); partsTableModel.setCheck(selRows, false); } }; final JPopupMenu partsTablePopupMenu = new JPopupMenu(); partsTablePopupMenu.add(actPartsSetCheck); partsTablePopupMenu.add(actPartsUnsetCheck); partsTable.setComponentPopupMenu(partsTablePopupMenu); JScrollPane partsTableSP = new JScrollPane(partsTable); partsTableSP.setBorder(null); JPanel partsTableTitledPanel = new JPanel(new BorderLayout()); partsTableTitledPanel.add(partsTableSP, BorderLayout.CENTER); partsTableTitledPanel.setBorder(BorderFactory.createTitledBorder(strings .getProperty("parts.title"))); add(partsTableTitledPanel, BorderLayout.CENTER); actSelectAll = new AbstractAction(strings .getProperty("parts.btn.selectAll")) { private static final long serialVersionUID = 1L; public void actionPerformed(ActionEvent e) { onSelectAll(); } }; actDeselectAll = new AbstractAction(strings .getProperty("parts.btn.deselectAll")) { private static final long serialVersionUID = 1L; public void actionPerformed(ActionEvent e) { onDeselectAll(); } }; actSort = new AbstractAction(strings.getProperty("parts.btn.sort")) { private static final long serialVersionUID = 1L; public void actionPerformed(ActionEvent e) { onSort(); } }; actSortByTimestamp = new AbstractAction(strings .getProperty("parts.btn.sortByTimestamp")) { private static final long serialVersionUID = 1L; public void actionPerformed(ActionEvent e) { onSortByTimestamp(); } }; JPanel btnPanel = new JPanel(); GridBagLayout btnPanelLayout = new GridBagLayout(); btnPanel.setLayout(btnPanelLayout); gbc.gridx = 0; gbc.gridy = 0; gbc.gridheight = 1; gbc.gridwidth = 1; gbc.anchor = GridBagConstraints.EAST; gbc.fill = GridBagConstraints.BOTH; gbc.insets = new Insets(3, 3, 3, 3); gbc.ipadx = 0; gbc.ipady = 0; JButton btnSelectAll = new JButton(actSelectAll); btnPanel.add(btnSelectAll, gbc); gbc.gridx = 1; gbc.gridy = 0; JButton btnDeselectAll = new JButton(actDeselectAll); btnPanel.add(btnDeselectAll, gbc); gbc.gridx = 2; gbc.gridy = 0; JButton btnSort = new JButton(actSort); btnPanel.add(btnSort, gbc); gbc.gridx = 3; gbc.gridy = 0; JButton btnSortByTimestamp = new JButton(actSortByTimestamp); btnPanel.add(btnSortByTimestamp, gbc); gbc.gridx = 4; gbc.gridy = 0; gbc.weightx = 1.; btnPanel.add(Box.createHorizontalGlue(), gbc); add(btnPanel, BorderLayout.SOUTH); } @Override public void onActive(ImportWizardDialog parent, ImportWizardCardPanel previousPanel) { this.parent = parent; if (previousPanel == parent.importPresetSelectPanel) { return; } // インポート対象のプロファイルサイズ CharacterData characterData; if (parent.current == null) { // 新規インポート characterData = parent.importModel.getCharacterData(); } else { // 更新インポート characterData = parent.current; } int profileWidth = 0; int profileHeight = 0; if (characterData != null) { Dimension imageSize = characterData.getImageSize(); if (imageSize != null) { profileWidth = imageSize.width; profileHeight = imageSize.height; } } txtProfileWidth.setText(Integer.toString(profileWidth)); txtProfileHeight.setText(Integer.toString(profileHeight)); profileSizePanel.revalidate(); this.profileHeight = profileHeight; this.profileWidth = profileWidth; // パーツのインポート指定があれば編集可能に、そうでなければ表示のみ // (パーツセットのインポートの確認のため、パーツ一覧は表示できるようにしておく) boolean enabled = parent.importTypeSelectPanel.isImportPartsImages(); partsTable.setEnabled(enabled); actDeselectAll.setEnabled(enabled); actSelectAll.setEnabled(enabled); actSort.setEnabled(enabled); actSortByTimestamp.setEnabled(enabled); CharacterData currentProfile = parent.current; Collection partsImageContents = parent.importModel.getPartsImageContents(); PartsManageData partsManageData = parent.importModel.getPartsManageData(); partsTableModel.initModel(partsImageContents, partsManageData, currentProfile); // プリセットのモデルも更新する. Collection partsSets = null; if (parent.importTypeSelectPanel.isImportPreset()) { CharacterData cd = parent.importModel.getCharacterData(); if (cd != null && cd.isValid()) { partsSets = cd.getPartsSets().values(); } } String defaultPartsSetId; CharacterData presetImportTarget; if (parent.current == null) { presetImportTarget = null; CharacterData cd = parent.importModel.getCharacterData(); if (cd != null) { defaultPartsSetId = cd.getDefaultPartsSetId(); } else { defaultPartsSetId = null; } } else { presetImportTarget = parent.current; defaultPartsSetId = null; // 既存の場合はデフォルトのパーツセットであるかは表示する必要ないのでnullにする. } parent.importPresetSelectPanel.initModel(partsSets, defaultPartsSetId, presetImportTarget); } @Override public boolean isReadyPrevious() { return true; } @Override public String doPrevious() { this.partsTableModel.clear(); return ImportTypeSelectPanel.PANEL_NAME; } @Override public boolean isReadyNext() { if (this.parent != null) { if (this.parent.importTypeSelectPanel.isImportPreset()) { // パーツセットのインポート指定があれば次へ return true; } } return false; } @Override public boolean isReadyFinish() { if (this.parent != null) { if (this.parent.importTypeSelectPanel.isImportPartsImages() && !this.parent.importTypeSelectPanel.isImportPreset()) { // パーツセットのインポート指定がなければ可 return true; } } return false; } public String doNext() { return ImportPresetSelectPanel.PANEL_NAME; }; protected void onSelectAll() { partsTableModel.selectAll(); } protected void onDeselectAll() { partsTableModel.deselectAll(); } protected void onSort() { partsTableModel.sort(); if (partsTableModel.getRowCount() > 0) { Rectangle rct = partsTable.getCellRect(0, 0, true); partsTable.scrollRectToVisible(rct); } } protected void onSortByTimestamp() { partsTableModel.sortByTimestamp(); if (partsTableModel.getRowCount() > 0) { Rectangle rct = partsTable.getCellRect(0, 0, true); partsTable.scrollRectToVisible(rct); } } /** * 選択されたイメージコンテンツのコレクション.
* * @return 選択されたイメージコンテンツのコレクション、なければ空 */ public Collection getSelectedPartsImageContents() { return partsTableModel.getSelectedPartsImageContents(); } /** * すでにプロファイルに登録済みのパーツ識別子、および、これからインポートする予定の選択されたパーツ識別子のコレクション.
* * @return インポートされた、またはインポートするパーツ識別子のコレクション.なければ空. */ public Collection getImportedPartsIdentifiers() { HashSet partsIdentifiers = new HashSet(); partsIdentifiers.addAll(partsTableModel.getCurrentProfilePartsIdentifers()); partsIdentifiers.addAll(partsTableModel.getSelectedPartsIdentifiers()); return partsIdentifiers; } public void selectByPartsIdentifiers(Collection partsIdentifiers) { partsTableModel.selectByPartsIdentifiers(partsIdentifiers); } } /** * 同じパーツ名をもつイメージのコレクション.
* パーツの各レイヤーの集合を想定する.
* * @author seraphy */ class ImportPartsImageSet extends AbstractCollection { /** * パーツ名 */ private String partsName; /** * 各レイヤー */ private ArrayList contentSet = new ArrayList(); private Long lastModified; private int width; private int height; private boolean unmatchedSize; private boolean alphaColor; private Collection partsCategories; private boolean checked; public ImportPartsImageSet(String partsName) { if (partsName == null || partsName.length() == 0) { throw new IllegalArgumentException(); } this.partsName = partsName; } public String getPartsName() { return partsName; } @Override public int size() { return contentSet.size(); } public Iterator iterator() { return contentSet.iterator(); } @Override public boolean add(PartsImageContent o) { if (o == null) { throw new IllegalArgumentException(); } if (!partsName.equals(o.getPartsName())) { throw new IllegalArgumentException(); } lastModified = null; // リセットする. return contentSet.add(o); } public int getWidth() { recheck(); return width; } public int getHeight() { recheck(); return height; } public boolean isUnmatchedSize() { recheck(); return unmatchedSize; } public boolean isAlphaColor() { recheck(); return alphaColor; } public long lastModified() { recheck(); return lastModified.longValue(); } public Collection getPartsCategories() { recheck(); return this.partsCategories; } protected void recheck() { if (lastModified != null) { return; } long lastModified = 0; int maxWidth = 0; int maxHeight = 0; int minWidth = 0; int minHeight = 0; boolean alphaColor = !this.contentSet.isEmpty(); HashSet partsCategories = new HashSet(); for (PartsImageContent partsImageContent : this.contentSet) { PNGFileImageHeader header = partsImageContent.getPngFileImageHeader(); maxWidth = Math.max(maxWidth, header.getWidth()); maxHeight = Math.max(maxHeight, header.getHeight()); minWidth = Math.max(minWidth, header.getWidth()); minHeight = Math.max(minHeight, header.getHeight()); if (header.getColorType() != 6 && !header.hasTransparencyInformation()) { // TrueColor + Alpha (6)か、アルファ情報があるもの以外はアルファなしとする. alphaColor = false; } for (CategoryLayerPair clPair : partsImageContent.getCategoryLayerPairs()) { partsCategories.add(clPair.getPartsCategory()); } long tm = partsImageContent.lastModified(); lastModified = Math.max(lastModified, tm); } this.lastModified = Long.valueOf(lastModified); this.width = maxWidth; this.height = maxHeight; this.unmatchedSize = (minWidth != maxWidth) || (minHeight != maxHeight); this.alphaColor = alphaColor; this.partsCategories = Collections.unmodifiableCollection(partsCategories); } public void setChecked(boolean checked) { this.checked = checked; } public boolean isChecked() { return checked; } } class ImportPartsModel { private PartsIdentifier partsIdentifier; private PartsAuthorInfo authorInfo; private PartsManageData.PartsVersionInfo versionInfo; private PartsSpec partsSpecAtCurrent; private ImportPartsImageSet imageSet; private int numOfLink; private Long lastModifiedAtCurrentProfile; /** * 行モデルを構築する * * @param partsIdentifier * パーツ識別子 * @param authorInfo * 作者情報(なければnull) * @param versionInfo * バージョン情報(なければnull) * @param imageSet * イメージファイルのセット * @param numOfLink * カテゴリの参照カウント数(複数カテゴリに参照される場合は2以上となる) */ public ImportPartsModel(PartsIdentifier partsIdentifier, PartsAuthorInfo authorInfo, PartsManageData.PartsVersionInfo versionInfo, PartsSpec partsSpecAtCurrent, ImportPartsImageSet imageSet, int numOfLink) { if (partsIdentifier == null || imageSet == null) { throw new IllegalArgumentException(); } this.partsIdentifier = partsIdentifier; this.authorInfo = authorInfo; this.versionInfo = versionInfo; this.partsSpecAtCurrent = partsSpecAtCurrent; this.imageSet = imageSet; this.numOfLink = numOfLink; if (partsSpecAtCurrent != null) { lastModifiedAtCurrentProfile = Long.valueOf(partsSpecAtCurrent .getPartsFiles().lastModified()); } else { lastModifiedAtCurrentProfile = null; } } public int getNumOfLink() { return numOfLink; } public PartsIdentifier getPartsIdentifier() { return partsIdentifier; } public ImportPartsImageSet getImageSet() { return imageSet; } public String getPartsName() { return partsIdentifier.getLocalizedPartsName(); } public String getAuthor() { if (authorInfo != null) { return authorInfo.getAuthor(); } return null; } public String getAuthorAtCurrent() { if (partsSpecAtCurrent != null) { PartsAuthorInfo partsAuthorInfo = partsSpecAtCurrent.getAuthorInfo(); if (partsAuthorInfo != null) { return partsAuthorInfo.getAuthor(); } } return null; } public double getVersion() { if (versionInfo != null) { return versionInfo.getVersion(); } return 0; } public double getVersionAtCurrent() { if (partsSpecAtCurrent != null) { return partsSpecAtCurrent.getVersion(); } return 0; } public PartsCategory getPartsCategory() { return partsIdentifier.getPartsCategory(); } public void setChecked(boolean checked) { imageSet.setChecked(checked); } public boolean isChecked() { return imageSet.isChecked(); } public int getWidth() { return imageSet.getWidth(); } public int getHeight() { return imageSet.getHeight(); } public boolean isUnmatchedSize() { return imageSet.isUnmatchedSize(); } public boolean isAlphaColor() { return imageSet.isAlphaColor(); } public long getLastModified() { return imageSet.lastModified(); } public Long getLastModifiedAtCurrentProfile() { return lastModifiedAtCurrentProfile; } } class ImportPartsTableModel extends AbstractTableModelWithComboBoxModel { private static final long serialVersionUID = 1L; private static final String[] COLUMN_NAMES; private static final int[] COLUMN_WIDTHS; public static final int COLUMN_LASTMODIFIED = 5; public static final int COLUMN_ALPHA = 4; public static final int COLUMN_SIZE = 3; private Set currentProfilePartsIdentifiers; static { Properties strings = LocalizedResourcePropertyLoader.getCachedInstance() .getLocalizedProperties(ImportWizardDialog.STRINGS_RESOURCE); COLUMN_NAMES = new String[] { strings.getProperty("parts.column.check"), strings.getProperty("parts.column.partsname"), strings.getProperty("parts.column.category"), strings.getProperty("parts.column.imagesize"), strings.getProperty("parts.column.alpha"), strings.getProperty("parts.column.lastmodified"), strings.getProperty("parts.column.org-lastmodified"), strings.getProperty("parts.column.author"), strings.getProperty("parts.column.org-author"), strings.getProperty("parts.column.version"), strings.getProperty("parts.column.org-version"), }; COLUMN_WIDTHS = new int[] { Integer.parseInt(strings.getProperty("parts.column.check.size")), Integer.parseInt(strings.getProperty("parts.column.partsname.size")), Integer.parseInt(strings.getProperty("parts.column.category.size")), Integer.parseInt(strings.getProperty("parts.column.imagesize.size")), Integer.parseInt(strings.getProperty("parts.column.alpha.size")), Integer.parseInt(strings.getProperty("parts.column.lastmodified.size")), Integer.parseInt(strings.getProperty("parts.column.org-lastmodified.size")), Integer.parseInt(strings.getProperty("parts.column.author.size")), Integer.parseInt(strings.getProperty("parts.column.org-author.size")), Integer.parseInt(strings.getProperty("parts.column.version.size")), Integer.parseInt(strings.getProperty("parts.column.org-version.size")), }; } /** * モデルを初期化する.
* * @param partsImageContents * インポートもとアーカイブに含まれる、全パーツイメージコンテンツ * @param currentProfile * インポート先のプロファイル、現在プロファイルが既に持っているパーツを取得するためのもの。 */ public void initModel(Collection partsImageContents, PartsManageData partsManageData, CharacterData currentProfile) { clear(); if (partsImageContents == null || partsManageData == null) { return; } // 現在のプロファイルが所有する全パーツ一覧を構築する. // 現在のプロファイルがなければ空. HashSet currentProfilePartsIdentifiers = new HashSet(); if (currentProfile != null) { for (PartsCategory partsCategory : currentProfile.getPartsCategories()) { currentProfilePartsIdentifiers.addAll(currentProfile.getPartsSpecMap(partsCategory).keySet()); } } this.currentProfilePartsIdentifiers = Collections.unmodifiableSet(currentProfilePartsIdentifiers); // 同じパーツ名をもつ各レイヤーを集める HashMap partsImageSets = new HashMap(); for (PartsImageContent content : partsImageContents) { String partsName = content.getPartsName(); ImportPartsImageSet partsImageSet = partsImageSets.get(partsName); if (partsImageSet == null) { partsImageSet = new ImportPartsImageSet(partsName); partsImageSets.put(partsName, partsImageSet); } partsImageSet.add(content); } // 名前順に並び替える ArrayList partsNames = new ArrayList(partsImageSets.keySet()); Collections.sort(partsNames); // 登録する for (String partsName : partsNames) { ImportPartsImageSet partsImageSet = partsImageSets.get(partsName); int numOfLink = partsImageSet.getPartsCategories().size(); for (PartsCategory partsCategory : partsImageSet.getPartsCategories()) { // パーツ管理情報の索引キー PartsManageData.PartsKey partsKey = new PartsManageData.PartsKey(partsName, partsCategory.getCategoryId()); // ローカライズされたパーツ名があれば取得する。なければオリジナルのまま String localizedPartsName = partsManageData.getLocalizedName(partsKey); if (localizedPartsName == null || localizedPartsName.length() == 0) { localizedPartsName = partsName; } // 作者情報・バージョン情報があれば取得する. PartsAuthorInfo partsAuthorInfo = partsManageData.getPartsAuthorInfo(partsKey); PartsManageData.PartsVersionInfo versionInfo = partsManageData.getVersion(partsKey); // パーツ識別子を構築する PartsIdentifier partsIdentifier = new PartsIdentifier(partsCategory, partsName, localizedPartsName); // 現在のプロファイル上のパーツ情報を取得する.(なければnull) PartsSpec partsSpec; if (currentProfile != null) { partsSpec = currentProfile.getPartsSpec(partsIdentifier); } else { partsSpec = null; } // 行モデルを構築する. ImportPartsModel rowModel = new ImportPartsModel( partsIdentifier, partsAuthorInfo, versionInfo, partsSpec, partsImageSet, numOfLink); addRow(rowModel); } } // 既存がないか、既存よりも新しい日付であれば自動的にチェックを設定する. // もしくはバージョンが上であれば自動的にチェックをつける. for (ImportPartsModel rowModel : elements) { // 現在のプロファイル上のファイル群の最終更新日 Long lastModifiedAtCurrent = rowModel.getLastModifiedAtCurrentProfile(); if (lastModifiedAtCurrent == null) { lastModifiedAtCurrent = Long.valueOf(0); } // インポートするファイル群の最終更新日 ImportPartsImageSet partsImageSet = rowModel.getImageSet(); // 新しければ自動的にチェックをつける. if (lastModifiedAtCurrent.longValue() < partsImageSet.lastModified()) { partsImageSet.setChecked(true); } // バージョンが新しければチェックをつける. (改変版や作者名改名もあるので、作者名が同一であるかは問わない.) double versionAtCurrent = rowModel.getVersionAtCurrent(); double version = rowModel.getVersion(); if (versionAtCurrent < version) { partsImageSet.setChecked(true); } } // 並び替え sort(); } /** * 選択されているパーツを構成するファイルのコレクションを返します.
* * @return パーツイメージコンテンツのコレクション、選択がなければ空 */ public Collection getSelectedPartsImageContents() { IdentityHashMap partsImageSets = new IdentityHashMap(); for (ImportPartsModel rowModel : elements) { ImportPartsImageSet partsImageSet = rowModel.getImageSet(); if (partsImageSet.isChecked()) { partsImageSets.put(partsImageSet, partsImageSet); } } ArrayList partsImageContents = new ArrayList(); for (ImportPartsImageSet partsImageSet : partsImageSets.values()) { partsImageContents.addAll(partsImageSet); } return partsImageContents; } /** * 選択されているパーツ識別子のコレクションを返します.
* 返されるコレクションには同一のパーツ識別子が複数存在しないことが保証されます.
* 一つも選択がない場合は空が返されます.
* * @return パーツ識別子のコレクション.
*/ public Collection getSelectedPartsIdentifiers() { HashSet partsIdentifiers = new HashSet(); for (ImportPartsModel rowModel : elements) { if (rowModel.isChecked()) { partsIdentifiers.add(rowModel.getPartsIdentifier()); } } return partsIdentifiers; } /** * 現在のプロファイルが所有している全パーツの識別子.
* 現在のプロファイルがないか、まったく所有していなければ空.
* * @return 現在のプロファイルが所有するパーツの識別子のコレクション.(重複しない一意であることが保証される.) */ public Collection getCurrentProfilePartsIdentifers() { return currentProfilePartsIdentifiers; } public int getColumnCount() { return COLUMN_NAMES.length; } @Override public String getColumnName(int column) { return COLUMN_NAMES[column]; } public Object getValueAt(int rowIndex, int columnIndex) { ImportPartsModel rowModel = getRow(rowIndex); switch (columnIndex) { case 0: return rowModel.isChecked(); case 1: return rowModel.getPartsName(); case 2: return rowModel.getPartsCategory().getLocalizedCategoryName(); case 3: return rowModel.getWidth() + "x" + rowModel.getHeight() + (rowModel.isUnmatchedSize() ? "*" : ""); case 4: return rowModel.isAlphaColor(); case 5: long lastModified = rowModel.getLastModified(); if (lastModified > 0) { return new Timestamp(lastModified).toString(); } return ""; case 6: Long lastModifiedAtCur = rowModel.getLastModifiedAtCurrentProfile(); if (lastModifiedAtCur != null && lastModifiedAtCur.longValue() > 0) { return new Timestamp(lastModifiedAtCur.longValue()).toString(); } return ""; case 7: return rowModel.getAuthor(); case 8: return rowModel.getAuthorAtCurrent(); case 9: double version = rowModel.getVersion(); if (version > 0) { return Double.toString(version); } return ""; case 10: double versionAtCurrent = rowModel.getVersionAtCurrent(); if (versionAtCurrent > 0) { return Double.toString(versionAtCurrent); } return ""; } return ""; } @Override public void setValueAt(Object aValue, int rowIndex, int columnIndex) { ImportPartsModel rowModel = getRow(rowIndex); switch (columnIndex) { case 0: rowModel.setChecked(((Boolean) aValue).booleanValue()); break; default: return; } if (rowModel.getNumOfLink() > 1) { fireTableDataChanged(); } else { fireListUpdated(rowIndex, rowIndex); } } @Override public Class getColumnClass(int columnIndex) { switch (columnIndex) { case 0: return Boolean.class; case 1: return String.class; case 2: return String.class; case 3: return String.class; case 4: return Boolean.class; case 5: return String.class; case 6: return String.class; case 7: return String.class; case 8: return String.class; case 9: return String.class; case 10: return String.class; } return String.class; } @Override public boolean isCellEditable(int rowIndex, int columnIndex) { if (columnIndex == 0) { return true; } return false; } public void adjustColumnModel(TableColumnModel columnModel) { int mx = columnModel.getColumnCount(); for (int idx = 0; idx < mx; idx++) { columnModel.getColumn(idx).setWidth(COLUMN_WIDTHS[idx]); } } public void selectAll() { boolean modified = false; for (ImportPartsModel rowModel : elements) { if (!rowModel.isChecked()) { rowModel.setChecked(true); modified = true; } } if (modified) { fireTableDataChanged(); } } public void deselectAll() { boolean modified = false; for (ImportPartsModel rowModel : elements) { if (rowModel.isChecked()) { rowModel.setChecked(false); modified = true; } } if (modified) { fireTableDataChanged(); } } public void sort() { Collections.sort(elements, new Comparator () { public int compare(ImportPartsModel o1, ImportPartsModel o2) { int ret = (o1.isChecked() ? 0 : 1) - (o2.isChecked() ? 0 : 1); if (ret == 0) { ret = o1.getPartsIdentifier().compareTo(o2.getPartsIdentifier()); } return ret; } }); fireTableDataChanged(); } public void sortByTimestamp() { Collections.sort(elements, new Comparator () { public int compare(ImportPartsModel o1, ImportPartsModel o2) { long ret = (o1.isChecked() ? 0 : 1) - (o2.isChecked() ? 0 : 1); if (ret == 0) { Long tm1 = o1.getLastModifiedAtCurrentProfile(); Long tm2 = o2.getLastModifiedAtCurrentProfile(); long lastModified1 = Math.max(o1.getLastModified(), tm1 == null ? 0 : tm1.longValue()); long lastModified2 = Math.max(o2.getLastModified(), tm2 == null ? 0 : tm2.longValue()); ret = lastModified1 - lastModified2; } if (ret == 0) { ret = o1.getPartsIdentifier().compareTo(o2.getPartsIdentifier()); } return ret == 0 ? 0 : ret > 0 ? 1 : -1; } }); fireTableDataChanged(); } /** * 指定したパーツ識別子をチェック状態にする. * * @param partsIdentifiers * パーツ識別子のコレクション、nullの場合は何もしない. */ public void selectByPartsIdentifiers(Collection partsIdentifiers) { boolean modified = false; if (partsIdentifiers != null) { for (PartsIdentifier partsIdentifier : partsIdentifiers) { for (ImportPartsModel rowModel : elements) { if (rowModel.getPartsIdentifier().equals(partsIdentifier)) { if (!rowModel.isChecked()) { rowModel.setChecked(true); modified = true; } } } } } if (modified) { fireTableDataChanged(); } } public void setCheck(int[] selRows, boolean checked) { if (selRows == null || selRows.length == 0) { return; } Arrays.sort(selRows); for (int selRow : selRows) { ImportPartsModel rowModel = getRow(selRow); rowModel.setChecked(checked); } fireTableRowsUpdated(selRows[0], selRows[selRows.length - 1]); } } /** * プリセット選択パネル * * @author seraphy */ class ImportPresetSelectPanel extends ImportWizardCardPanel { private static final long serialVersionUID = 1L; public static final String PANEL_NAME = "importPresetSelectPanel"; private ImportPresetTableModel presetTableModel; private ImportWizardDialog parent; private JTable presetTable; private Action actSelectAll; private Action actDeselectAll; private Action actSelectUsedParts; public ImportPresetSelectPanel() { Properties strings = LocalizedResourcePropertyLoader.getCachedInstance() .getLocalizedProperties(ImportWizardDialog.STRINGS_RESOURCE); setBorder(BorderFactory.createTitledBorder(strings.getProperty("preset.title"))); setLayout(new BorderLayout()); presetTableModel = new ImportPresetTableModel(); presetTableModel.addTableModelListener(new TableModelListener() { public void tableChanged(TableModelEvent e) { if (e.getType() == TableModelEvent.UPDATE) { fireChangeEvent(); } } }); AppConfig appConfig = AppConfig.getInstance(); final Color warningForegroundColor = appConfig.getExportPresetWarningsForegroundColor(); final Color disabledForeground = appConfig.getDisabledCellForgroundColor(); presetTable = new JTable(presetTableModel) { private static final long serialVersionUID = 1L; @Override public Component prepareRenderer(TableCellRenderer renderer, int row, int column) { Component comp = super.prepareRenderer(renderer, row, column); if (comp instanceof JCheckBox) { // BooleanのデフォルトのレンダラーはJCheckBoxを継承したJTable$BooleanRenderer comp.setEnabled(isCellEditable(row, column) && isEnabled()); } ImportPresetModel presetModel = presetTableModel.getRow(row); // インポート先のプリセットを上書きする場合、もしくはデフォルトのパーツセットの場合は太字にする. if (presetModel.isOverwrite() || presetTableModel.isDefaultPartsSet(row)) { comp.setFont(getFont().deriveFont(Font.BOLD)); } else { comp.setFont(getFont()); } // インポートするプリセットのパーツが不足している場合、警告色にする. if (!isEnabled()) { comp.setForeground(disabledForeground); } else { if (presetModel.isCheched() && presetModel.getMissingPartsIdentifiers().size() > 0) { comp.setForeground(warningForegroundColor); } else { comp.setForeground(getForeground()); } } return comp; } }; presetTable.setShowGrid(true); presetTable.setGridColor(appConfig.getGridColor()); presetTable.putClientProperty("terminateEditOnFocusLost", Boolean.TRUE); actSelectUsedParts = new AbstractAction(strings.getProperty("preset.popup.selectUsedParts")) { private static final long serialVersionUID = 1L; public void actionPerformed(ActionEvent e) { exportUsedParts(); } }; final JPopupMenu popupMenu = new JPopupMenu(); popupMenu.add(actSelectUsedParts); presetTable.setComponentPopupMenu(popupMenu); presetTable.setAutoResizeMode(JTable.AUTO_RESIZE_OFF); presetTableModel.adjustColumnModel(presetTable.getColumnModel()); add(new JScrollPane(presetTable), BorderLayout.CENTER); actSelectAll = new AbstractAction(strings.getProperty("parts.btn.selectAll")) { private static final long serialVersionUID = 1L; public void actionPerformed(ActionEvent e) { onSelectAll(); } }; actDeselectAll = new AbstractAction(strings.getProperty("parts.btn.deselectAll")) { private static final long serialVersionUID = 1L; public void actionPerformed(ActionEvent e) { onDeselectAll(); } }; Action actSort = new AbstractAction(strings .getProperty("parts.btn.sort")) { private static final long serialVersionUID = 1L; public void actionPerformed(ActionEvent e) { onSort(); } }; JPanel btnPanel = new JPanel(); GridBagLayout btnPanelLayout = new GridBagLayout(); btnPanel.setLayout(btnPanelLayout); GridBagConstraints gbc = new GridBagConstraints(); gbc.gridx = 0; gbc.gridy = 0; gbc.gridheight = 1; gbc.gridwidth = 1; gbc.anchor = GridBagConstraints.EAST; gbc.fill = GridBagConstraints.BOTH; gbc.insets = new Insets(3, 3, 3, 3); gbc.ipadx = 0; gbc.ipady = 0; JButton btnSelectAll = new JButton(actSelectAll); btnPanel.add(btnSelectAll, gbc); gbc.gridx = 1; gbc.gridy = 0; JButton btnDeselectAll = new JButton(actDeselectAll); btnPanel.add(btnDeselectAll, gbc); gbc.gridx = 2; gbc.gridy = 0; JButton btnSort = new JButton(actSort); btnPanel.add(btnSort, gbc); gbc.gridx = 3; gbc.gridy = 0; gbc.weightx = 1.; btnPanel.add(Box.createHorizontalGlue(), gbc); add(btnPanel, BorderLayout.SOUTH); } @Override public void onActive(ImportWizardDialog parent, ImportWizardCardPanel previousPanel) { this.parent= parent; actSelectUsedParts.setEnabled(parent.importTypeSelectPanel.isImportPartsImages()); checkMissingParts(); } public void checkMissingParts() { Collection importedPartsIdentifiers = this.parent.importPartsSelectPanel.getImportedPartsIdentifiers(); presetTableModel.checkMissingParts(importedPartsIdentifiers); } protected void onSelectAll() { presetTableModel.selectAll(); } protected void onDeselectAll() { presetTableModel.deselectAll(); } protected void onSort() { presetTableModel.sort(); if (presetTableModel.getRowCount() > 0) { Rectangle rct = presetTable.getCellRect(0, 0, true); presetTable.scrollRectToVisible(rct); } } protected void exportUsedParts() { ArrayList requirePartsIdentifiers = new ArrayList(); int[] selRows = presetTable.getSelectedRows(); for (int selRow : selRows) { ImportPresetModel presetModel = presetTableModel.getRow(selRow); PartsSet partsSet = presetModel.getPartsSet(); for (List partsIdentifiers : partsSet.values()) { for (PartsIdentifier partsIdentifier : partsIdentifiers) { requirePartsIdentifiers.add(partsIdentifier); } } } this.parent.importPartsSelectPanel.selectByPartsIdentifiers(requirePartsIdentifiers); checkMissingParts(); } @Override public boolean isReadyPrevious() { return true; } @Override public boolean isReadyNext() { return false; } @Override public boolean isReadyFinish() { if (this.parent != null) { return true; } return false; } @Override public String doPrevious() { return ImportPartsSelectPanel.PANEL_NAME; } @Override public String doNext() { return null; } public Collection getSelectedPartsSets() { return presetTableModel.getSelectedPartsSets(); } /** * デフォルトのパーツセットIDとして使用されることが推奨されるパーツセットIDを取得する.
* 明示的なデフォルトのパーツセットIDがなければ、もしくは、 明示的に指定されているパーツセットIDが選択されているパーツセットの中になければ、 * 選択されているパーツセットの最初のアイテムを返す.
* 選択しているパーツセットが一つもなければnullを返す.
* * @return デフォルトのパーツセット */ public String getPrefferedDefaultPartsSetId() { String defaultPartsSetId = presetTableModel.getDefaultPartsSetId(); String firstPartsSetId = null; boolean existsDefaultPartsSetId = false; for (PartsSet partsSet : getSelectedPartsSets()) { if (firstPartsSetId == null) { firstPartsSetId = partsSet.getPartsSetId(); } if (partsSet.getPartsSetId().equals(defaultPartsSetId)) { existsDefaultPartsSetId = true; } } if (!existsDefaultPartsSetId || defaultPartsSetId == null || defaultPartsSetId.length() == 0) { defaultPartsSetId = firstPartsSetId; } return defaultPartsSetId; } public void initModel(Collection partsSets, String defaultPartsSetId, CharacterData presetImportTarget) { presetTableModel.initModel(partsSets, defaultPartsSetId, presetImportTarget); } } class ImportPresetModel { private boolean cheched; private PartsSet partsSet; private boolean overwrite; private Collection missingPartsIdentifiers = Collections.emptySet(); public ImportPresetModel(PartsSet partsSet, boolean overwrite, boolean checked) { if (partsSet == null) { throw new IllegalArgumentException(); } this.partsSet = partsSet; this.cheched = checked; this.overwrite = overwrite; } public boolean isCheched() { return cheched; } public void setCheched(boolean cheched) { this.cheched = cheched; } public PartsSet getPartsSet() { return partsSet; } public String getPartsSetName() { return partsSet.getLocalizedName(); } public void setPartsSetName(String name) { if (name == null || name.trim().length() == 0) { throw new IllegalArgumentException(); } partsSet.setLocalizedName(name); } public Collection getMissingPartsIdentifiers() { return missingPartsIdentifiers; } public boolean hasMissingParts() { return true; } public boolean isOverwrite() { return overwrite; } public boolean checkMissingParts(Collection importedPartsIdentifiers) { HashSet missingPartsIdentifiers = new HashSet(); for (List partsIdentifiers : partsSet.values()) { for (PartsIdentifier partsIdentifier : partsIdentifiers) { boolean exists = false; if (importedPartsIdentifiers != null && importedPartsIdentifiers.contains(partsIdentifier)) { exists = true; } if (!exists) { missingPartsIdentifiers.add(partsIdentifier); } } } boolean modified = (!missingPartsIdentifiers.equals(this.missingPartsIdentifiers)); if (modified) { this.missingPartsIdentifiers = missingPartsIdentifiers; } return modified; } } class ImportPresetTableModel extends AbstractTableModelWithComboBoxModel { private static final long serialVersionUID = 1L; private static final String[] COLUMN_NAMES; // = {"選択", "プリセット名", // "不足するパーツ"}; private static final int[] COLUMN_WIDTHS; // = {50, 100, 200}; static { Properties strings = LocalizedResourcePropertyLoader.getCachedInstance() .getLocalizedProperties(ImportWizardDialog.STRINGS_RESOURCE); COLUMN_NAMES = new String[] { strings.getProperty("preset.column.check"), strings.getProperty("preset.column.name"), strings.getProperty("preset.column.missings"), }; COLUMN_WIDTHS = new int[] { Integer.parseInt(strings.getProperty("preset.column.check.size")), Integer.parseInt(strings.getProperty("preset.column.name.size")), Integer.parseInt(strings.getProperty("preset.column.missings.size")), }; } private String defaultPartsSetId; public String getDefaultPartsSetId() { return defaultPartsSetId; } public void setDefaultPartsSetId(String defaultPartsSetId) { this.defaultPartsSetId = defaultPartsSetId; } public int getColumnCount() { return COLUMN_NAMES.length; } @Override public String getColumnName(int column) { return COLUMN_NAMES[column]; } @Override public Class getColumnClass(int columnIndex) { switch (columnIndex) { case 0: return Boolean.class; case 1: return String.class; case 2: return String.class; } return String.class; } @Override public boolean isCellEditable(int rowIndex, int columnIndex) { if (columnIndex == 0 || columnIndex == 1) { return true; } return false; } public Object getValueAt(int rowIndex, int columnIndex) { ImportPresetModel rowModel = getRow(rowIndex); switch (columnIndex) { case 0: return rowModel.isCheched(); case 1: return rowModel.getPartsSetName(); case 2: return getMissingPartsIdentifiersString(rowModel); } return ""; } private String getMissingPartsIdentifiersString(ImportPresetModel rowModel) { StringBuilder buf = new StringBuilder(); for (PartsIdentifier partsIdentifier : rowModel.getMissingPartsIdentifiers()) { if (buf.length() > 0) { buf.append(", "); } buf.append(partsIdentifier.getLocalizedPartsName()); } return buf.toString(); } @Override public void setValueAt(Object aValue, int rowIndex, int columnIndex) { ImportPresetModel rowModel = getRow(rowIndex); switch (columnIndex) { case 0: rowModel.setCheched(((Boolean) aValue).booleanValue()); break; case 1: String name = (String) aValue; name = (name != null) ? name.trim() : ""; if (name.length() > 0) { rowModel.setPartsSetName(name); } default: return; } fireTableRowsUpdated(rowIndex, rowIndex); } /** * 指定した行のパーツセットがデフォルトパーツセットであるか? * * @param rowIndex * 行インデックス * @return デフォルトパーツセットであればtrue、そうでなければfalse */ public boolean isDefaultPartsSet(int rowIndex) { ImportPresetModel rowModel = getRow(rowIndex); return rowModel.getPartsSet().getPartsSetId().equals(defaultPartsSetId); } public void adjustColumnModel(TableColumnModel columnModel) { int mx = columnModel.getColumnCount(); for (int idx = 0; idx < mx; idx++) { columnModel.getColumn(idx).setWidth(COLUMN_WIDTHS[idx]); } } /** * パーツセットリストを構築する.
* * @param partsSets * 登録するパーツセット * @param defaultPartsSetId * デフォルトのパーツセットID、なければnull * @param presetImportTarget * インポート先、新規の場合はnull (上書き判定のため) */ public void initModel(Collection partsSets, String defaultPartsSetId, CharacterData presetImportTarget) { clear(); if (partsSets == null) { return; } // インポート先の既存のパーツセット Map currentProfilesPartsSet; if (presetImportTarget != null) { currentProfilesPartsSet = presetImportTarget.getPartsSets(); } else { // 新規の場合は既存パーツセットは空. currentProfilesPartsSet = Collections.emptyMap(); } // インポートもとのパーツセットをテープルモデルに登録する. for (PartsSet partsSet : partsSets) { String partsSetId = partsSet.getPartsSetId(); if (partsSetId == null || partsSetId.length() == 0) { continue; } PartsSet compatiblePartsSet; if (presetImportTarget != null) { // 既存のキャラクター定義へのインポート時は、パーツセットのカテゴリを合わせる. // 一つもカテゴリが合わない場合は空のパーツセットになる. compatiblePartsSet = partsSet.createCompatible(presetImportTarget); } else { compatiblePartsSet = partsSet; // 新規の場合はフィッティングの必要なし. } if (!compatiblePartsSet.isEmpty()) { // 空のパーツセットは登録対象にしない. boolean overwrite = currentProfilesPartsSet.containsKey(partsSetId); boolean checked = (presetImportTarget == null); // 新規の場合は既定で選択状態とする. ImportPresetModel rowModel = new ImportPresetModel(partsSet, overwrite, checked); addRow(rowModel); } } // デフォルトのパーツセットIDを設定、存在しない場合はnull this.defaultPartsSetId = defaultPartsSetId; sort(); } public Collection getSelectedPartsSets() { ArrayList partsSets = new ArrayList(); for (ImportPresetModel rowModel : elements) { if (rowModel.isCheched()) { partsSets.add(rowModel.getPartsSet()); } } return partsSets; } public void selectAll() { boolean modified = false; for (ImportPresetModel rowModel : elements) { if (!rowModel.isCheched()) { rowModel.setCheched(true); modified = true; } } if (modified) { fireTableDataChanged(); } } public void deselectAll() { boolean modified = false; for (ImportPresetModel rowModel : elements) { if (rowModel.isCheched()) { rowModel.setCheched(false); modified = true; } } if (modified) { fireTableDataChanged(); } } public void sort() { Collections.sort(elements, new Comparator() { public int compare(ImportPresetModel o1, ImportPresetModel o2) { int ret = (o1.isCheched() ? 0 : 1) - (o2.isCheched() ? 0 : 1); if (ret == 0) { ret = o1.getPartsSetName().compareTo(o2.getPartsSetName()); } return ret; } }); fireTableDataChanged(); } public void checkMissingParts(Collection importedPartsIdentifiers) { boolean changed = false; for (ImportPresetModel rowModel : elements) { if (rowModel.checkMissingParts(importedPartsIdentifiers)) { changed = true; } } if (changed) { fireTableDataChanged(); } } } CharacterManaJ/src/charactermanaj/ui/MiniPictureBox.java0000644000175000017500000000664512560206305023516 0ustar paulliupaulliupackage charactermanaj.ui; import java.awt.Dimension; import java.awt.FontMetrics; import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.Image; import java.awt.Insets; import java.awt.RenderingHints; import java.awt.geom.Rectangle2D; import java.awt.image.ImageObserver; import javax.swing.BorderFactory; import javax.swing.JPanel; import javax.swing.border.BevelBorder; /** * サンプルイメージを表示する小さなピクチャボックス.
* 非同期イメージに対応している.
* @author seraphy */ public class MiniPictureBox extends JPanel { private static final long serialVersionUID = 3210952907784110605L; /** * 表示するイメージ.
* 非同期読み込みのイメージにも対応.
*/ private Image image; /** * 読み込みエラーが発生しているか? */ private boolean errorOccured; /** * コンストラクタ */ public MiniPictureBox() { setBorder(BorderFactory.createBevelBorder(BevelBorder.LOWERED)); setPreferredSize(new Dimension(150, 300)); } @Override protected void paintComponent(Graphics g0) { Graphics2D g = (Graphics2D) g0; super.paintComponent(g); Image img = getImage(); if (img != null) { if (errorOccured) { FontMetrics fm = g.getFontMetrics(); String message = "ERROR"; Rectangle2D rct = fm.getStringBounds(message, g); Insets insets = getInsets(); g.drawString(message, insets.left, insets.top + (int) rct.getHeight()); } else { // 画像の読み込みに失敗しているか? checkImage(img, -1, -1, new ImageObserver() { public boolean imageUpdate(Image img, int infoflags, int x, int y, int width, int height) { if ((infoflags & (ImageObserver.ERROR)) != 0) { errorOccured = true; repaint(); } return true; } }); if ( !prepareImage(img, this)) { // まだロードできていない場合は // ロードできるまで表示しない. return; } // イメージサイズの取得 int imgW = img.getWidth(this); int imgH = img.getHeight(this); // 表示エリア int x = 0; int y = 0; int w = getWidth(); int h = getHeight(); Insets insets = getInsets(); if (insets != null) { x = insets.left; y = insets.top; w -= insets.left + insets.right; h -= insets.top + insets.bottom; } // 倍率算定 double vx = (double) w / (double) imgW; double vy = (double) h / (double) imgH; double factor = Math.min(vx, vy); int scaledW = (int)(imgW * factor); int scaledH = (int)(imgH * factor); int offset_x = (w - scaledW) / 2; int offset_y = (h - scaledH) / 2; // 描画 g.setRenderingHint( RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR); g.drawImage(img, x + offset_x, y + offset_y, x + offset_x + scaledW, y + offset_y + scaledH, 0, 0, imgW, imgH, this); } } } public Image getImage() { return image; } public void setImage(Image image) { Image oldimg = this.image; if ((oldimg != null && image == null) || (image != null && (oldimg == null || !oldimg.equals(image)))) { this.image = image; this.errorOccured = false; repaint(); firePropertyChange("image", oldimg, image); } } }CharacterManaJ/src/charactermanaj/ui/WallpaperDialog.java0000644000175000017500000003675112560206305023665 0ustar paulliupaulliupackage charactermanaj.ui; import java.awt.BorderLayout; import java.awt.Color; import java.awt.Container; import java.awt.Dimension; import java.awt.GridBagConstraints; import java.awt.GridBagLayout; import java.awt.Insets; import java.awt.Toolkit; import java.awt.event.ActionEvent; import java.awt.event.KeyEvent; import java.awt.event.WindowAdapter; import java.io.File; import java.util.ArrayList; import java.util.Properties; import javax.swing.AbstractAction; import javax.swing.ActionMap; import javax.swing.BorderFactory; import javax.swing.Box; import javax.swing.BoxLayout; import javax.swing.ButtonGroup; import javax.swing.InputMap; import javax.swing.JButton; import javax.swing.JComponent; import javax.swing.JDialog; import javax.swing.JFileChooser; import javax.swing.JFrame; import javax.swing.JLabel; import javax.swing.JList; import javax.swing.JOptionPane; import javax.swing.JPanel; import javax.swing.JRadioButton; import javax.swing.JRootPane; import javax.swing.JScrollPane; import javax.swing.JSpinner; import javax.swing.JTextField; import javax.swing.KeyStroke; import javax.swing.SpinnerNumberModel; import javax.swing.event.ListSelectionEvent; import javax.swing.event.ListSelectionListener; import charactermanaj.Main; import charactermanaj.graphics.io.ImagePreviewFileChooser; import charactermanaj.ui.model.PredefinedWallpaper; import charactermanaj.ui.model.WallpaperInfo; import charactermanaj.ui.model.WallpaperInfo.WallpaperResourceType; import charactermanaj.util.LocalizedMessageComboBoxRender; import charactermanaj.util.LocalizedResourcePropertyLoader; /** * 壁紙選択ダイアログ * @author seraphy */ public class WallpaperDialog extends JDialog { private static final long serialVersionUID = 1L; /** * リソース */ protected static final String STRINGS_RESOURCE = "languages/wallpaperdialog"; /** * 定義済み壁紙のキャッシュ */ private static ArrayList predefinedWallpapers = new ArrayList(); /** * 壁紙情報 */ private WallpaperInfo wallpaperInfo = new WallpaperInfo(); /** * 選択なしラジオ */ private JRadioButton radioNone; /** * ファイルから選択ラジオ */ private JRadioButton radioFile; /** * 定義済みから選択ラジオ */ private JRadioButton radioPredefined; /** * 定義済み壁紙リスト */ private JList listPredefinedWallpapers; /** * 背景画像の透過率 */ private JSpinner spinnerAlpha; /** * 選択ファイルフィールド */ private JTextField txtFile; /** * ファイルを選択アクション */ private AbstractAction actChooseFile; /** * 背景色選択コンポーネント */ private ColorBox colorBox; /** * コンストラクタ * @param parent 親フレーム */ public WallpaperDialog(JFrame parent) { super(parent, true); try { setDefaultCloseOperation(JDialog.DO_NOTHING_ON_CLOSE); addWindowListener(new WindowAdapter() { public void windowClosing(java.awt.event.WindowEvent e) { onCancel(); }; }); initPredefinedWallpapers(); initComponent(); } catch (RuntimeException ex) { dispose(); throw ex; } } /** * 定義済み壁紙リストを初期化する.
* 既に初期化済みであれば何もしない.
*/ private static synchronized void initPredefinedWallpapers() { if ( !predefinedWallpapers.isEmpty()) { return; } predefinedWallpapers.addAll(PredefinedWallpaper.getPredefinedWallpapers()); } /** * このダイアログのコンポーネントを初期化します.
*/ private void initComponent() { Properties strings = LocalizedResourcePropertyLoader.getCachedInstance() .getLocalizedProperties(WallpaperDialog.STRINGS_RESOURCE); setTitle(strings.getProperty("title")); Container contentPane = getContentPane(); contentPane.setLayout(new BorderLayout(3, 3)); getRootPane().setBorder(BorderFactory.createEmptyBorder(3, 3, 3, 3)); JPanel wallpaperPanel = createWallpaperChoosePanel(strings); JPanel bgcolorPanel = createBgColorPanel(strings); JPanel btnPanel = createButtonPanel(strings); contentPane.add(bgcolorPanel, BorderLayout.NORTH); contentPane.add(wallpaperPanel, BorderLayout.CENTER); contentPane.add(btnPanel, BorderLayout.SOUTH); setSize(400, 350); setLocationRelativeTo(getParent()); } private JPanel createButtonPanel(Properties strings) { JPanel btnPanel = new JPanel(); AbstractAction actOK = new AbstractAction(strings.getProperty("btn.ok")) { private static final long serialVersionUID = 1L; public void actionPerformed(ActionEvent e) { onOK(); } }; AbstractAction actCancel = new AbstractAction(strings.getProperty("btn.cancel")) { private static final long serialVersionUID = 1L; public void actionPerformed(ActionEvent e) { onCancel(); } }; JButton btnOK = new JButton(actOK); JButton btnCancel = new JButton(actCancel); BoxLayout bl = new BoxLayout(btnPanel, BoxLayout.LINE_AXIS); btnPanel.setLayout(bl); btnPanel.setBorder(BorderFactory.createEmptyBorder(3, 3, 3, 45)); btnPanel.add(Box.createHorizontalGlue()); if (Main.isLinuxOrMacOSX()) { btnPanel.add(btnCancel); btnPanel.add(btnOK); } else { btnPanel.add(btnOK); btnPanel.add(btnCancel); } // Enter/Returnキーを既定ボタンにする. JRootPane rootPane = getRootPane(); rootPane.setDefaultButton(btnOK); // CTRL-Wでウィンドウを非表示にする. (ESCでは非表示にしない) InputMap im = rootPane.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW); ActionMap am = rootPane.getActionMap(); Toolkit tk = Toolkit.getDefaultToolkit(); im.put(KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0), "closeWallpaperDialog"); im.put(KeyStroke.getKeyStroke(KeyEvent.VK_W, tk.getMenuShortcutKeyMask()), "closeWallpaperDialog"); am.put("closeWallpaperDialog", actCancel); return btnPanel; } private JPanel createBgColorPanel(Properties strings) { JPanel bgcolorPanel = new JPanel(); bgcolorPanel.setBorder( BorderFactory.createCompoundBorder( BorderFactory.createTitledBorder(strings.getProperty("group.bgcolor")), BorderFactory.createEmptyBorder(3, 3, 3, 3))); GridBagLayout gbl = new GridBagLayout(); bgcolorPanel.setLayout(gbl); colorBox = new ColorBox(); colorBox.getColorDisplayPanel().setPreferredSize(new Dimension(48, 20)); GridBagConstraints gbc = new GridBagConstraints(); gbc.ipadx = 0; gbc.ipady = 0; gbc.insets = new Insets(0, 0, 0, 0); gbc.weighty = 0.; gbc.gridheight = 1; gbc.anchor = GridBagConstraints.CENTER; gbc.fill = GridBagConstraints.NONE; bgcolorPanel.add(colorBox, gbc); return bgcolorPanel; } private JPanel createWallpaperChoosePanel(Properties strings) { JPanel wallpaperPanel = new JPanel(); wallpaperPanel.setBorder(BorderFactory.createTitledBorder(strings.getProperty("group.wallpaper"))); GridBagLayout gbl = new GridBagLayout(); wallpaperPanel.setLayout(gbl); radioNone = new JRadioButton(strings.getProperty("radio.none")); radioFile = new JRadioButton(strings.getProperty("radio.file")); radioPredefined = new JRadioButton(strings.getProperty("radio.predefined")); ButtonGroup btnGroup = new ButtonGroup(); btnGroup.add(radioNone); btnGroup.add(radioFile); btnGroup.add(radioPredefined); radioNone.setSelected(true); txtFile = new JTextField(); actChooseFile = new AbstractAction(strings.getProperty("btn.chooseFile")) { private static final long serialVersionUID = 1L; public void actionPerformed(ActionEvent e) { onChooseFile(); } }; JButton btnChooseFile = new JButton(actChooseFile); listPredefinedWallpapers = new JList(predefinedWallpapers .toArray(new PredefinedWallpaper[predefinedWallpapers.size()])); listPredefinedWallpapers.setCellRenderer(new LocalizedMessageComboBoxRender(strings)); SpinnerNumberModel alphaSpModel = new SpinnerNumberModel(100, 0, 100, 1); spinnerAlpha = new JSpinner(alphaSpModel); GridBagConstraints gbc = new GridBagConstraints(); gbc.ipadx = 0; gbc.ipady = 0; gbc.insets = new Insets(0, 0, 0, 0); gbc.weighty = 0.; gbc.gridheight = 1; gbc.anchor = GridBagConstraints.WEST; gbc.fill = GridBagConstraints.BOTH; gbc.gridx = 0; gbc.gridy = 0; gbc.gridwidth = 3; gbc.weightx = 0.; wallpaperPanel.add(radioNone, gbc); gbc.gridx = 0; gbc.gridy = 1; gbc.gridwidth = 3; gbc.weightx = 0.; wallpaperPanel.add(radioFile, gbc); gbc.gridx = 0; gbc.gridy = 2; gbc.gridwidth = 1; gbc.weightx = 0.; wallpaperPanel.add(Box.createHorizontalStrut(20), gbc); gbc.gridx = 1; gbc.gridy = 2; gbc.gridwidth = 1; gbc.weightx = 1.; wallpaperPanel.add(txtFile, gbc); gbc.gridx = 2; gbc.gridy = 2; gbc.gridwidth = 1; gbc.weightx = 0.; wallpaperPanel.add(btnChooseFile, gbc); gbc.gridx = 0; gbc.gridy = 3; gbc.gridwidth = 3; gbc.weightx = 0.; wallpaperPanel.add(radioPredefined, gbc); gbc.gridx = 1; gbc.gridy = 4; gbc.gridwidth = 2; gbc.weightx = 1.; gbc.weighty = 1.; wallpaperPanel.add(new JScrollPane(listPredefinedWallpapers), gbc); gbc.gridx = 0; gbc.gridy = 5; gbc.gridwidth = 3; gbc.weightx = 0.; gbc.weighty = 0.; gbc.insets = new Insets(5, 0, 0, 0); wallpaperPanel.add(new JLabel( strings.getProperty("label.wallpaperImageAlpha")), gbc); JPanel alphaPanel = new JPanel(new BorderLayout(3, 3)); alphaPanel.setBorder(BorderFactory.createEmptyBorder(3, 0, 3, 0)); alphaPanel.add(spinnerAlpha, BorderLayout.CENTER); alphaPanel.add(new JLabel("%"), BorderLayout.EAST); gbc.gridx = 1; gbc.gridy = 6; gbc.gridwidth = 2; gbc.weightx = 0.; gbc.weighty = 0.; gbc.ipady = 0; gbc.insets = new Insets(0, 0, 0, 0); gbc.fill = GridBagConstraints.NONE; gbc.anchor = GridBagConstraints.WEST; wallpaperPanel.add(alphaPanel, gbc); // リスナ listPredefinedWallpapers.addListSelectionListener(new ListSelectionListener() { public void valueChanged(ListSelectionEvent e) { radioPredefined.setSelected(true); } }); return wallpaperPanel; } protected void onChooseFile() { File selectedFile = wallpaperInfo.getFile(); final JFileChooser fileChooser = new ImagePreviewFileChooser(); fileChooser.setSelectedFile(selectedFile); if (fileChooser.showOpenDialog(this) != JFileChooser.APPROVE_OPTION) { return; } selectedFile = fileChooser.getSelectedFile(); txtFile.setText(selectedFile == null ? "" : selectedFile.getPath()); radioFile.setSelected(selectedFile != null); } /** * 壁紙情報インスタンスの内容でコンポーネントを設定する. */ protected void applyByWallpaperInfo() { // ファイル File imageFile = wallpaperInfo.getFile(); txtFile.setText(imageFile == null ? "" : imageFile.getPath()); // リソース PredefinedWallpaper selectedPredefinedWp = null; String resource = wallpaperInfo.getResource(); for (PredefinedWallpaper predefinedWp : predefinedWallpapers) { if (predefinedWp.getResource().equals(resource)) { selectedPredefinedWp = predefinedWp; } } listPredefinedWallpapers.setSelectedValue(selectedPredefinedWp, true); // 透過率 int alphaInt = (int)(wallpaperInfo.getAlpha() * 100); spinnerAlpha.setValue(alphaInt); // 背景色 Color bgColor = wallpaperInfo.getBackgroundColor(); colorBox.setColorKey(bgColor); // タイプ WallpaperResourceType typ = wallpaperInfo.getType(); if (typ == WallpaperResourceType.FILE) { radioFile.setSelected(true); } else if (typ == WallpaperResourceType.PREDEFINED) { radioPredefined.setSelected(true); } else { radioNone.setSelected(true); } } /** * ダイアログのコンポーネントの状態から壁紙情報を構築して返す.
* 生成した壁紙情報が妥当であるかは検証しない.
* @return 新しい壁紙情報インスタンス */ public WallpaperInfo createWallpaperInfo() { WallpaperInfo wallpaperInfo = this.wallpaperInfo.clone(); // 選択したタイプ WallpaperResourceType typ; if (radioFile.isSelected()) { // 背景画像ファイル選択 typ = WallpaperResourceType.FILE; } else if (radioPredefined.isSelected()) { // リソース選択 typ = WallpaperResourceType.PREDEFINED; } else { // それ以外は選択なし typ = WallpaperResourceType.NONE; } // 画像ファイルの現在の選択 String strSelectedFile = txtFile.getText(); File selectedFile = null; if (strSelectedFile != null) { strSelectedFile = strSelectedFile.trim(); if (strSelectedFile.length() > 0) { selectedFile = new File(strSelectedFile); } } wallpaperInfo.setFile(selectedFile); // 定義済みリソースの現在の選択 PredefinedWallpaper predefinedWp = (PredefinedWallpaper) listPredefinedWallpapers.getSelectedValue(); wallpaperInfo.setResource(predefinedWp == null ? null : predefinedWp.getResource()); // タイプの設定 wallpaperInfo.setType(typ); // 背景画像の透過率 int alphaInt = (Integer) spinnerAlpha.getValue(); float alpha = (float) alphaInt / 100.f; wallpaperInfo.setAlpha(alpha); // 背景色 Color bgColor = colorBox.getColorKey(); wallpaperInfo.setBackgroundColor(bgColor); return wallpaperInfo; } protected void onOK() { WallpaperInfo wallpaperInfo = createWallpaperInfo(); if (!checkValidate(wallpaperInfo)) { return; } this.wallpaperInfo = wallpaperInfo; dispose(); } protected void onCancel() { wallpaperInfo = null; dispose(); } protected boolean checkValidate(WallpaperInfo wallpaperInfo) { String messageid = null; WallpaperResourceType typ = wallpaperInfo.getType(); if (typ == WallpaperResourceType.FILE) { File selectedFile = wallpaperInfo.getFile(); if (selectedFile == null) { messageid = "error.require.imageFile"; } else if (!selectedFile.exists() || !selectedFile.isFile() || !selectedFile.canRead()) { messageid = "error.invalid.imageFile"; } } else if (typ == WallpaperResourceType.PREDEFINED) { String resource = wallpaperInfo.getResource(); if (resource == null || resource.trim().length() == 0) { messageid = "error.require.resource"; } } if (messageid != null) { Properties strings = LocalizedResourcePropertyLoader.getCachedInstance() .getLocalizedProperties(WallpaperDialog.STRINGS_RESOURCE); String message = strings.getProperty(messageid); JOptionPane.showMessageDialog(this, message, "ERROR", JOptionPane.ERROR_MESSAGE); } return messageid == null; } public void setWallpaperInfo(WallpaperInfo wallpaperInfo) { if (wallpaperInfo == null) { wallpaperInfo = new WallpaperInfo(); } this.wallpaperInfo = wallpaperInfo; } public WallpaperInfo getWallpaperInfo() { return wallpaperInfo; } /** * ダイアログを表示し、その結果を返す.
* キャンセルされた場合はnullが返される.
* @return 結果、もしくはnull */ public WallpaperInfo showDialog() { applyByWallpaperInfo(); setVisible(true); return wallpaperInfo; } } CharacterManaJ/src/charactermanaj/ui/SelectCharatersDirDialog.java0000644000175000017500000003762712560206305025454 0ustar paulliupaulliupackage charactermanaj.ui; import java.awt.BorderLayout; import java.awt.Container; import java.awt.Dimension; import java.awt.Font; import java.awt.GridBagConstraints; import java.awt.GridBagLayout; import java.awt.Insets; import java.awt.Toolkit; import java.awt.dnd.DropTarget; import java.awt.event.ActionEvent; import java.awt.event.FocusAdapter; import java.awt.event.FocusEvent; import java.awt.event.KeyEvent; import java.awt.event.WindowAdapter; import java.awt.event.WindowEvent; import java.io.File; import java.util.ArrayList; import java.util.List; import java.util.Properties; import java.util.logging.Level; import java.util.logging.Logger; import javax.swing.AbstractAction; import javax.swing.BorderFactory; import javax.swing.Box; import javax.swing.InputMap; import javax.swing.JButton; import javax.swing.JCheckBox; import javax.swing.JComboBox; import javax.swing.JDialog; import javax.swing.JFileChooser; import javax.swing.JFrame; import javax.swing.JLabel; import javax.swing.JOptionPane; import javax.swing.JPanel; import javax.swing.JRootPane; import javax.swing.KeyStroke; import charactermanaj.Main; import charactermanaj.model.io.WorkingSetPersist; import charactermanaj.ui.util.FileDropTarget; import charactermanaj.util.ErrorMessageHelper; import charactermanaj.util.LocalizedResourcePropertyLoader; /** * 起動時にキャラクターデータディレクトリを選択するためのモーダルダイアログ.
* * @author seraphy */ public class SelectCharatersDirDialog extends JDialog { private static final long serialVersionUID = -888834575856349442L; private static final Logger logger = Logger.getLogger(SelectCharatersDirDialog.class.getName()); /** * 最後に使用したキャラクターデータディレクトリと、その履歴情報. */ private final RecentCharactersDir recentCharactersDir; /** * 既定のディレクトリ */ private File defaultCharactersDir; /** * 選択されたディレクトリ */ private File selectedCharacterDir; /** * 次回起動時に問い合わせない */ private boolean doNotAskAgain; /** * ディレクトリ選択コンボ */ private JComboBox combDir; /** * 次回起動時に問い合わせないチェックボックス */ private JCheckBox chkDoNotAsk; public File getDefaultCharactersDir() { return defaultCharactersDir; } public void setDefaultCharactersDir(File defaultCharactersDir) { this.defaultCharactersDir = defaultCharactersDir; } public File getSelectedCharacterDir() { return selectedCharacterDir; } public boolean isDoNotAskAgain() { return doNotAskAgain; } /** * コンストラクタ * * @param parent * 親(通常は、null) * @param recentCharactersDir * 最後に使用したキャラクターデータディレクトリと、その履歴情報. */ protected SelectCharatersDirDialog(JFrame parent, RecentCharactersDir recentCharactersDir) { super(parent, true); try { if (recentCharactersDir == null) { throw new IllegalArgumentException( "recentCharactersDirにnullは指定できません。"); } this.recentCharactersDir = recentCharactersDir; setDefaultCloseOperation(DO_NOTHING_ON_CLOSE); addWindowListener(new WindowAdapter() { @Override public void windowClosing(WindowEvent e) { onClose(); } }); initComponent(); } catch (RuntimeException ex) { logger.log(Level.SEVERE, "キャラクターディレクトリ選択ダイアログの生成に失敗しました。", ex); dispose(); throw ex; } } private void initComponent() { Properties strings = LocalizedResourcePropertyLoader.getCachedInstance() .getLocalizedProperties("languages/selectCharatersDirDialog"); Container contentPane = getContentPane(); contentPane.setLayout(new BorderLayout(3, 3)); AbstractAction actOk = new AbstractAction(strings.getProperty("btn.ok")) { private static final long serialVersionUID = 1L; public void actionPerformed(ActionEvent e) { onOK(); } }; AbstractAction actClose = new AbstractAction(strings.getProperty("btn.cancel")) { private static final long serialVersionUID = 1L; public void actionPerformed(ActionEvent e) { onClose(); } }; AbstractAction actBrowse = new AbstractAction(strings.getProperty("btn.chooseDir")) { private static final long serialVersionUID = 1L; public void actionPerformed(ActionEvent e) { onBrowse(); } }; AbstractAction actRemoveRecent = new AbstractAction(strings.getProperty("btn.clearRecentList")) { private static final long serialVersionUID = 1L; public void actionPerformed(ActionEvent e) { onRemoveRecent(); } }; AbstractAction actRemoveWorkingSets = new AbstractAction( strings.getProperty("btn.clearWorkingSets")) { private static final long serialVersionUID = 1L; public void actionPerformed(ActionEvent e) { onRemoveWorkingSets(); } }; final JButton btnRemoveWorkingSets = new JButton(actRemoveWorkingSets); final JButton btnRemoveRecent = new JButton(actRemoveRecent); final JButton btnOK = new JButton(actOk); final JButton btnCancel = new JButton(actClose); final JButton btnBroseForDir = new JButton(actBrowse); Toolkit tk = Toolkit.getDefaultToolkit(); final JRootPane rootPane = getRootPane(); FocusAdapter focusAdapter = new FocusAdapter() { @Override public void focusGained(FocusEvent e) { JButton btn = (JButton) e.getSource(); rootPane.setDefaultButton(btn); } @Override public void focusLost(FocusEvent e) { rootPane.setDefaultButton(btnOK); } }; rootPane.setDefaultButton(btnOK); InputMap im = rootPane.getInputMap(); im.put(KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0), "close"); im.put(KeyStroke.getKeyStroke(KeyEvent.VK_W, tk.getMenuShortcutKeyMask()), "close"); rootPane.getActionMap().put("close", actClose); btnRemoveWorkingSets.addFocusListener(focusAdapter); btnRemoveRecent.addFocusListener(focusAdapter); btnOK.addFocusListener(focusAdapter); btnCancel.addFocusListener(focusAdapter); btnBroseForDir.addFocusListener(focusAdapter); JPanel dirPanel = new JPanel(new BorderLayout(3, 3)); dirPanel.setBorder(BorderFactory.createEmptyBorder(3, 10, 3, 3)); JLabel lbl = new JLabel(strings.getProperty("caption"), JLabel.CENTER); lbl.setFont(lbl.getFont().deriveFont(Font.BOLD)); lbl.setBorder(BorderFactory.createEmptyBorder(5, 0, 5, 0)); Dimension dim = lbl.getPreferredSize(); dim.width = Integer.parseInt(strings.getProperty("width")); lbl.setPreferredSize(dim); dirPanel.add(lbl, BorderLayout.NORTH); combDir = new JComboBox(); combDir.setEditable(true); dirPanel.add(combDir, BorderLayout.CENTER); dirPanel.add(new JLabel(strings.getProperty("lbl.dir")), BorderLayout.WEST); dirPanel.add(btnBroseForDir, BorderLayout.EAST); contentPane.add(dirPanel, BorderLayout.NORTH); JPanel btnPanel = new JPanel(); GridBagLayout gbl = new GridBagLayout(); btnPanel.setLayout(gbl); chkDoNotAsk = new JCheckBox(strings.getProperty("chk.doNotAskAgein")); chkDoNotAsk.setSelected(recentCharactersDir.isDoNotAskAgain()); GridBagConstraints gbc = new GridBagConstraints(); gbc.gridx = 0; gbc.gridy = 0; gbc.gridwidth = 5; gbc.gridheight = 1; gbc.weightx = 1.; gbc.weighty = 0.; gbc.fill = GridBagConstraints.BOTH; gbc.anchor = GridBagConstraints.WEST; gbc.ipadx = 0; gbc.ipady = 0; gbc.insets = new Insets(3, 3, 3, 3); btnPanel.add(chkDoNotAsk, gbc); gbc.gridx = 0; gbc.gridy = 1; gbc.gridwidth = 1; gbc.gridheight = 1; gbc.weightx = 0.; gbc.weighty = 0.; btnPanel.add(btnRemoveRecent, gbc); gbc.gridx = 1; gbc.gridy = 1; gbc.gridwidth = 1; gbc.gridheight = 1; gbc.weightx = 0.; gbc.weighty = 0.; btnPanel.add(btnRemoveWorkingSets, gbc); gbc.gridx = 2; gbc.gridy = 1; gbc.gridwidth = 1; gbc.gridheight = 1; gbc.weightx = 1.; gbc.weighty = 0.; btnPanel.add(Box.createGlue(), gbc); gbc.gridx = Main.isLinuxOrMacOSX() ? 4 : 3; gbc.gridy = 1; gbc.gridwidth = 1; gbc.gridheight = 1; gbc.weightx = 0.; gbc.weighty = 0.; btnPanel.add(btnOK, gbc); gbc.gridx = Main.isLinuxOrMacOSX() ? 3 : 4; gbc.gridy = 1; gbc.gridwidth = 1; gbc.gridheight = 1; gbc.weightx = 0.; gbc.weighty = 0.; btnPanel.add(btnCancel, gbc); gbc.gridx = 5; gbc.gridy = 1; gbc.gridwidth = 1; gbc.gridheight = 1; gbc.weightx = 0.; gbc.weighty = 0.; gbc.ipadx = 32; gbc.ipady = 0; btnPanel.add(Box.createGlue(), gbc); setTitle(strings.getProperty("title")); setResizable(false); contentPane.add(btnPanel, BorderLayout.SOUTH); // フォルダのドロップによる入力を許可 new DropTarget(this, new FileDropTarget() { @Override protected void onDropFiles(List dropFiles) { setSelectFile(dropFiles); } @Override protected void onException(Exception ex) { ErrorMessageHelper.showErrorDialog(SelectCharatersDirDialog.this, ex); } }); pack(); setLocationRelativeTo(null); } /** * ドロップによるファイル名の設定.
* 最初の1つだけを使用する.
* リストが空であるか、最初のファイルが、フォルダでなければ何もしない.
* * @param dropFiles * ドロップされたファイルリスト */ protected void setSelectFile(List dropFiles) { if (dropFiles.isEmpty()) { return; } File dropFile = dropFiles.get(0); if ( !dropFile.exists() || !dropFile.isDirectory()) { return; } combDir.setSelectedItem(dropFile); } protected void onClose() { selectedCharacterDir = null; dispose(); } protected void onOK() { try { Object value = combDir.getSelectedItem(); if (value != null && value instanceof String) { value = new File((String) value); } if (value != null && value instanceof File) { File file = (File) value; if (!file.exists()) { boolean result = file.mkdirs(); logger.log(Level.INFO, "mkdirs(" + file+ ") succeeded=" + result); } if (file.isDirectory()) { logger.log(Level.CONFIG, "selectedCharactersDir=" + file); selectedCharacterDir = file; doNotAskAgain = chkDoNotAsk.isSelected(); dispose(); return; } } // 選択されていないかファイルでない場合 Toolkit tk = Toolkit.getDefaultToolkit(); tk.beep(); } catch (Exception ex) { ErrorMessageHelper.showErrorDialog(this, ex); } } protected void onBrowse() { try { Object selectedItem = combDir.getSelectedItem(); String directoryTxt = null; if (selectedItem != null) { directoryTxt = selectedItem.toString(); } JFileChooser dirChooser = new JFileChooser(directoryTxt); dirChooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY); if (dirChooser.showOpenDialog(this) != JFileChooser.APPROVE_OPTION) { return; } File dir = dirChooser.getSelectedFile(); if (dir != null) { combDir.setSelectedItem(dir); } } catch (Exception ex) { ErrorMessageHelper.showErrorDialog(this, ex); } } protected void onRemoveWorkingSets() { try { Properties strings = LocalizedResourcePropertyLoader .getCachedInstance().getLocalizedProperties( "languages/selectCharatersDirDialog"); // 削除の確認ダイアログ if (JOptionPane.showConfirmDialog(this, strings.getProperty("confirm.clearWorkingSets"), strings.getProperty("confirm.clearWorkingSets.title"), JOptionPane.YES_NO_OPTION, JOptionPane.WARNING_MESSAGE) != JOptionPane.YES_OPTION) { return; } // 全てのワーキングセットをクリアする. WorkingSetPersist workingSetPersist = WorkingSetPersist .getInstance(); workingSetPersist.removeAllWorkingSet(); } catch (Exception ex) { ErrorMessageHelper.showErrorDialog(this, ex); } } protected void onRemoveRecent() { try { Object current = combDir.getSelectedItem(); recentCharactersDir.clrar(); setRecents(); if (current != null) { combDir.setSelectedItem(current); } } catch (Exception ex) { ErrorMessageHelper.showErrorDialog(this, ex); } } protected void setRecents() { // 現在の候補をクリア. while (combDir.getItemCount() > 0) { combDir.removeItemAt(0); } // 前回使用したディレクトリを最優先候補 ArrayList priorityDirs = new ArrayList(); File lastUseCatacterDir = recentCharactersDir.getLastUseCharacterDir(); if (lastUseCatacterDir != null) { if (defaultCharactersDir != null && !lastUseCatacterDir.equals(defaultCharactersDir)) { combDir.addItem(lastUseCatacterDir); priorityDirs.add(lastUseCatacterDir); } } // デフォルトのキャラクターデータを第2位に設定 if (defaultCharactersDir != null) { combDir.addItem(defaultCharactersDir); priorityDirs.add(defaultCharactersDir); } // それ以外の履歴を設定 for (File charactersDir : recentCharactersDir .getRecentCharacterDirsOrderByNewly()) { if (charactersDir == null) { continue; } if (!priorityDirs.contains(charactersDir)) { combDir.addItem(charactersDir); } } // 第一候補を選択状態とする. if (combDir.getItemCount() > 0) { combDir.setSelectedIndex(0); } } /** * キャラクターデータディレクトリを履歴および既定のディレクトリから、任意の使用するディレクトリを選択する.
* 既定のディレクトリは常に選択候補とする.
* 新しいディレクトリを指定した場合は、履歴に追加される.
* 「再度問い合わせなし」を選択している場合で、そのディレクトリが実在すれば、選択ダイアログを表示せず、それを返す.
* * @param defaultCharacterDir * 既定のディレクトリ * @return 選択したディレクトリ、キャンセルした場合はnull */ public static File getCharacterDir(File defaultCharacterDir) { RecentCharactersDir recentChars; try { recentChars = RecentCharactersDir.load(); } catch (Exception ex) { logger.log(Level.WARNING, "最後に使用したキャラクターディレクトリ情報の読み込みに失敗しました。", ex); recentChars = null; } if (recentChars == null) { recentChars = new RecentCharactersDir(); } logger.log(Level.CONFIG, "RecentCharacterDirs.doNotAskAgain=" + recentChars.isDoNotAskAgain()); if (recentChars.isDoNotAskAgain()) { // 「再度問い合わせ無し」の場合で、過去のディレクトリが有効であれば、それを返す. File recentCharDir = recentChars.getLastUseCharacterDir(); if (recentCharDir != null && recentCharDir.exists() && recentCharDir.isDirectory()) { return recentCharDir; } recentChars.setDoNotAskAgain(false); // 不正である場合は「再度問い合わせ無し」をリセットする. } File selectedCharacterDir; SelectCharatersDirDialog dlg = new SelectCharatersDirDialog(null, recentChars); dlg.setDefaultCharactersDir(defaultCharacterDir); dlg.setRecents(); dlg.setVisible(true); selectedCharacterDir = dlg.getSelectedCharacterDir(); if (selectedCharacterDir != null) { recentChars.setLastUseCharacterDir(selectedCharacterDir); try { recentChars.setDoNotAskAgain(dlg.isDoNotAskAgain()); recentChars.saveRecents(); } catch (Exception ex) { logger.log(Level.WARNING, "最後に使用したキャラクターディレクトリ情報の保存に失敗しました。", ex); } } return selectedCharacterDir; } } CharacterManaJ/src/charactermanaj/ui/PartsRandomChooserDialog.java0000644000175000017500000005041112560206305025500 0ustar paulliupaulliupackage charactermanaj.ui; import java.awt.BorderLayout; import java.awt.Component; import java.awt.Container; import java.awt.GridBagConstraints; import java.awt.GridBagLayout; import java.awt.Toolkit; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.KeyEvent; import java.awt.event.WindowAdapter; import java.awt.event.WindowEvent; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Properties; import java.util.Random; import java.util.concurrent.atomic.AtomicInteger; import javax.swing.AbstractAction; import javax.swing.Action; import javax.swing.ActionMap; import javax.swing.BorderFactory; import javax.swing.Box; import javax.swing.InputMap; import javax.swing.JButton; import javax.swing.JCheckBox; import javax.swing.JComboBox; import javax.swing.JComponent; import javax.swing.JDialog; import javax.swing.JFrame; import javax.swing.JPanel; import javax.swing.JRootPane; import javax.swing.JScrollBar; import javax.swing.JScrollPane; import javax.swing.JToggleButton; import javax.swing.KeyStroke; import javax.swing.event.EventListenerList; import charactermanaj.model.AppConfig; import charactermanaj.model.CharacterData; import charactermanaj.model.PartsCategory; import charactermanaj.model.PartsIdentifier; import charactermanaj.model.PartsSet; import charactermanaj.util.LocalizedResourcePropertyLoader; /** * パーツのランダム選択ダイアログ.
* * @author seraphy */ public class PartsRandomChooserDialog extends JDialog { private static final long serialVersionUID = -8427874726724107481L; protected static final String STRINGS_RESOURCE = "languages/partsrandomchooserdialog"; /** * メインフレームとの間でパーツの選択状態の取得・設定を行うためのインターフェイス.
*/ public interface PartsSetSynchronizer { /** * 現在フレームで設定されているパーツセットを取得する. * * @return */ PartsSet getCurrentPartsSet(); /** * ランダム選択パネルのパーツセットでフレームを設定する. * * @param partsSet */ void setPartsSet(PartsSet partsSet); /** * 指定されたパーツがランダム選択対象外であるか? * * @param partsIdentifier * パーツ * @return 対象外であればtrue */ boolean isExcludePartsIdentifier(PartsIdentifier partsIdentifier); /** * 指定したパーツがランダム選択対象外であるか設定する. * * @param partsIdentifier * パーツ * @param exclude * 対象外であればtrue */ void setExcludePartsIdentifier(PartsIdentifier partsIdentifier, boolean exclude); } /** * ランダム選択パネルを縦に並べるボックス */ private Box centerPnl; /** * キャラクターデータ */ private CharacterData characterData; /** * メインフレームとの同期用 */ private PartsSetSynchronizer partsSync; /** * 一括ランダムアクション */ private Action actRandomAll; /** * 選択を戻すアクション */ private Action actBack; /** * 閉じるアクション */ private Action actCancel; /** * 履歴 */ private LinkedList> history = new LinkedList>(); /** * 最大の履歴保持数 */ private int maxHistory; /** * コンストラクタ * * @param parent * メインフレーム(親) * @param characterData * キャラクターデータ * @param partsSync * メインフレームとの同期用 */ public PartsRandomChooserDialog(JFrame parent, CharacterData characterData, PartsSetSynchronizer partsSync) { super(parent, false); try { if (characterData == null || partsSync == null) { throw new IllegalArgumentException(); } setDefaultCloseOperation(JDialog.DO_NOTHING_ON_CLOSE); addWindowListener(new WindowAdapter() { @Override public void windowClosing(WindowEvent e) { onClose(); } }); this.characterData = characterData; this.partsSync = partsSync; AppConfig appConfig = AppConfig.getInstance(); this.maxHistory = appConfig.getRandomChooserMaxHistory(); if (this.maxHistory < 0) { this.maxHistory = 0; } initLayout(); pack(); setLocationRelativeTo(parent); } catch (RuntimeException ex) { dispose(); throw ex; } } /** * レイアウトを行う. */ private void initLayout() { Properties strings = LocalizedResourcePropertyLoader .getCachedInstance().getLocalizedProperties(STRINGS_RESOURCE); setTitle(strings.getProperty("partsRandomChooser")); Container contentPane = getContentPane(); contentPane.setLayout(new BorderLayout()); this.centerPnl = Box.createVerticalBox(); ActionListener changePartsIdentifierListener = new ActionListener() { public void actionPerformed(ActionEvent e) { if (eventLock.get() == 0) { onChangePartsIdentifiers(); } } }; PartsSet partsSet = partsSync.getCurrentPartsSet(); eventLock.incrementAndGet(); try { for (PartsCategory category : characterData.getPartsCategories()) { List partsIdentifiers = partsSet.get(category); int partsLen = (partsIdentifiers != null) ? partsIdentifiers .size() : 0; boolean enable = true; if (partsLen < 1) { partsLen = 1; // 未選択の場合でも1つは作成する. enable = false; // 未選択の場合はディセーブルとする. } for (int partsIdx = 0; partsIdx < partsLen; partsIdx++) { PartsIdentifier partsIdentifier = null; if (partsIdentifiers != null && partsIdx < partsIdentifiers.size()) { partsIdentifier = partsIdentifiers.get(partsIdx); } boolean lastInCategory = (partsIdx == partsLen - 1); int idx = centerPnl.getComponentCount(); RandomChooserPanel pnl = addPartsChooserPanel(centerPnl, idx, category, lastInCategory, changePartsIdentifierListener); // 未選択の場合、もしくは複数選択カテゴリの場合はランダムはディセーブルとする pnl.setEnableRandom(enable && !category.isMultipleSelectable()); if (partsIdentifier != null) { pnl.setSelectedPartsIdentifier(partsIdentifier); } } } } finally { eventLock.decrementAndGet(); } JScrollPane scr = new JScrollPane(centerPnl) { private static final long serialVersionUID = 1L; @Override public JScrollBar createVerticalScrollBar() { JScrollBar sb = super.createVerticalScrollBar(); sb.setUnitIncrement(12); return sb; } }; scr.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS); contentPane.add(scr, BorderLayout.CENTER); this.actRandomAll = new AbstractAction(strings.getProperty("randomAll")) { private static final long serialVersionUID = 1L; public void actionPerformed(ActionEvent e) { onRandomAll(); } }; this.actBack = new AbstractAction(strings.getProperty("back")) { private static final long serialVersionUID = 1L; public void actionPerformed(ActionEvent e) { onBack(); } }; this.actCancel = new AbstractAction(strings.getProperty("close")) { private static final long serialVersionUID = 1L; public void actionPerformed(ActionEvent e) { onClose(); } }; JButton btnClose = new JButton(actCancel); JButton btnRandomAll = new JButton(actRandomAll); JButton btnBack = new JButton(actBack); Box btnPanel = Box.createHorizontalBox(); btnPanel.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 42)); btnPanel.add(btnRandomAll); btnPanel.add(btnBack); btnPanel.add(Box.createHorizontalGlue()); btnPanel.add(btnClose); contentPane.add(btnPanel, BorderLayout.SOUTH); JRootPane rootPane = getRootPane(); rootPane.setDefaultButton(btnRandomAll); Toolkit tk = Toolkit.getDefaultToolkit(); InputMap im = rootPane.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW); ActionMap am = rootPane.getActionMap(); im.put(KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0), "closeDialog"); im.put(KeyStroke.getKeyStroke(KeyEvent.VK_W, tk.getMenuShortcutKeyMask()), "closeDialog"); am.put("closeDialog", actCancel); addHistory(getSelection()); updateUIState(); } /** * ボタンの状態を設定する. */ protected void updateUIState() { actBack.setEnabled(history.size() > 1); } /** * ダイアログを破棄して閉じる. */ protected void onClose() { dispose(); } /** * パネル構築時、および一括ランダム選択時などでパーツのコンボボックスの選択が複数変更される場合に * イベントを一度だけ処理するようにグループ化するためのロック. */ private final AtomicInteger eventLock = new AtomicInteger(0); /** * センターパネル上に配置したランダム選択パネルのリストを取得する.
* (ランダム選択パネルの個数は実行時に自由に可変できるため.) * * @return ランダム選択パネルのリスト */ protected List getRandomChooserPanels() { ArrayList panels = new ArrayList(); int mx = centerPnl.getComponentCount(); for (int idx = 0; idx < mx; idx++) { Component comp = centerPnl.getComponent(idx); if (comp instanceof RandomChooserPanel) { RandomChooserPanel pnl = (RandomChooserPanel) comp; panels.add(pnl); } } return panels; } /** * 現在選択中の状態を取得する. * * @return */ protected Map getSelection() { HashMap selection = new HashMap(); for (RandomChooserPanel pnl : getRandomChooserPanels()) { PartsIdentifier partsIdentifier = pnl.getSelectedPartsIdentifier(); selection.put(pnl, partsIdentifier); } return selection; } /** * 履歴に追加する. * * @param selection */ protected void addHistory(Map selection) { if (selection == null || selection.isEmpty()) { return; } // 履歴に追加する. history.addLast(selection); // 最大数を越えた場合は除去する while (history.size() > maxHistory) { history.removeFirst(); } updateUIState(); } /** * 前回の選択状態に戻す */ protected void onBack() { if (history.size() <= 1) { return; } // ヒストリーの直前のものを取り出す // 先頭のものは現在表示中のものなので、2つ取り出す必要がある. history.removeLast(); Map selection = history.getLast(); // すべてのランダム選択パネルに再適用する. eventLock.incrementAndGet(); try { for (Map.Entry entry : selection .entrySet()) { RandomChooserPanel pnl = entry.getKey(); PartsIdentifier partsIdentifier = entry.getValue(); pnl.setSelectedPartsIdentifier(partsIdentifier); } PartsSet partsSet = makePartsSet(selection.values()); if (!partsSet.isEmpty()) { partsSync.setPartsSet(partsSet); } } finally { eventLock.decrementAndGet(); } updateUIState(); } /** * 一括ランダム選択 */ protected void onRandomAll() { eventLock.incrementAndGet(); try { for (RandomChooserPanel pnl : getRandomChooserPanels()) { if (pnl.isEnableRandom()) { // ランダム選択を有効としているものだけを対象とする. pnl.selectRandom(); } } onChangePartsIdentifiers(); } finally { eventLock.decrementAndGet(); } } /** * パーツの選択からパーツセットを生成して返す. * * @param selection * @return */ protected PartsSet makePartsSet(Collection selection) { PartsSet partsSet = new PartsSet(); for (PartsIdentifier partsIdentifier : selection) { if (partsIdentifier != null) { PartsCategory category = partsIdentifier.getPartsCategory(); partsSet.appendParts(category, partsIdentifier, null); // 色は不問とする } } return partsSet; } /** * パーツの選択が変更されたことを通知される.
* 現在のランダム選択状態を、プレビューの状態に反映させる. */ protected void onChangePartsIdentifiers() { Map selection = getSelection(); PartsSet partsSet = makePartsSet(selection.values()); if (!partsSet.isEmpty()) { partsSync.setPartsSet(partsSet); addHistory(selection); } } /** * アイテムごとのランダム選択パネル * * @author seraphy */ protected class RandomChooserPanel extends JPanel { private static final long serialVersionUID = 1L; private EventListenerList listeners = new EventListenerList(); private JCheckBox label; private JComboBox partsCombo; private JToggleButton btnReject; public RandomChooserPanel(final PartsCategory category, final boolean lastInCategory) { Properties strings = LocalizedResourcePropertyLoader .getCachedInstance().getLocalizedProperties( STRINGS_RESOURCE); setBorder(BorderFactory.createCompoundBorder( BorderFactory.createEmptyBorder(3, 3, 3, 3), BorderFactory.createCompoundBorder( BorderFactory.createEtchedBorder(), BorderFactory.createEmptyBorder(3, 3, 3, 3)))); setLayout(new GridBagLayout()); GridBagConstraints gbc = new GridBagConstraints(); gbc.gridx = 0; gbc.gridy = 0; gbc.gridheight = 1; gbc.gridwidth = 1; gbc.anchor = GridBagConstraints.EAST; gbc.fill = GridBagConstraints.BOTH; gbc.weightx = 1.; gbc.weighty = 0.; String categoryName = category.getLocalizedCategoryName(); this.label = new JCheckBox(categoryName, true); add(label, gbc); JButton btnRandom = new JButton(new AbstractAction( strings.getProperty("random")) { private static final long serialVersionUID = -1; public void actionPerformed(ActionEvent e) { onClickRandom(e); } }); gbc.gridx = 1; gbc.weightx = 0; add(btnRandom, gbc); ArrayList partsList = new ArrayList(); partsList.addAll(characterData.getPartsSpecMap(category).keySet()); Collections.sort(partsList); if (category.isMultipleSelectable()) { // 複数選択カテゴリは未選択状態が可能なため先頭に空行を入れる. partsList.add(0, null); } this.partsCombo = new JComboBox( partsList.toArray(new PartsIdentifier[partsList.size()])); partsCombo.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { onSelectChangePartsIdentifier(e); } }); gbc.gridx = 0; gbc.gridy = 1; gbc.weightx = 1.; add(partsCombo, gbc); this.btnReject = new JToggleButton(new AbstractAction( strings.getProperty("reject")) { private static final long serialVersionUID = -1; public void actionPerformed(ActionEvent e) { onClickReject(e); } }); gbc.gridx = 1; gbc.gridy = 1; gbc.weightx = 0; add(btnReject, gbc); if (category.isMultipleSelectable() && lastInCategory) { JButton btnAdd = new JButton(new AbstractAction( strings.getProperty("add")) { private static final long serialVersionUID = -1; public void actionPerformed(ActionEvent e) { onClickAdd(e); } }); gbc.gridx = 1; gbc.gridy = 2; gbc.weightx = 0; add(btnAdd, gbc); } updateButtonState(); } public void addActionListener(ActionListener l) { listeners.add(ActionListener.class, l); } public void removeActionListener(ActionListener l) { listeners.remove(ActionListener.class, l); } public boolean isEnableRandom() { return label.isSelected(); } public void setEnableRandom(boolean selected) { label.setSelected(selected); } public PartsIdentifier getSelectedPartsIdentifier() { return (PartsIdentifier) partsCombo.getSelectedItem(); } public void setSelectedPartsIdentifier(PartsIdentifier partsIdentifier) { partsCombo.setSelectedItem(partsIdentifier); } protected void updateButtonState() { PartsIdentifier partsIdentifier = getSelectedPartsIdentifier(); if (partsIdentifier == null) { btnReject.setEnabled(false); return; } boolean exclude = partsSync .isExcludePartsIdentifier(partsIdentifier); btnReject.setSelected(exclude); btnReject.setEnabled(true); } protected void onSelectChangePartsIdentifier(ActionEvent e) { updateButtonState(); ActionEvent evt = new ActionEvent(this, ActionEvent.ACTION_PERFORMED, "selectChangePartsIdentifier"); for (ActionListener l : listeners .getListeners(ActionListener.class)) { l.actionPerformed(evt); } } protected void onClickReject(ActionEvent e) { PartsIdentifier partsIdentifier = getSelectedPartsIdentifier(); if (partsIdentifier == null) { return; } boolean exclude = partsSync .isExcludePartsIdentifier(partsIdentifier); partsSync.setExcludePartsIdentifier(partsIdentifier, !exclude); updateButtonState(); } protected void onClickRandom(ActionEvent e) { selectRandom(); } public void selectRandom() { ArrayList partsIdentifiers = new ArrayList(); int mx = partsCombo.getItemCount(); for (int idx = 0; idx < mx; idx++) { PartsIdentifier partsIdentifier = (PartsIdentifier) partsCombo .getItemAt(idx); if (partsIdentifier != null) { if (!partsSync.isExcludePartsIdentifier(partsIdentifier)) { partsIdentifiers.add(partsIdentifier); } } } int len = partsIdentifiers.size(); if (len == 0) { // 選択しようがないので何もしない. return; } Random rng = new Random(); int selidx = rng.nextInt(len); setSelectedPartsIdentifier(partsIdentifiers.get(selidx)); } protected void onClickAdd(ActionEvent e) { // 何もしない. } } /** * カテゴリのパーツのランダム選択パネルを作成する.
* パネルが追加ボタンをもつときには、作成されたパネルにもパーツ変更リスナは適用される.
* * @param centerPnl * 追加されるパネル * @param addPos * 追加する位置 * @param category * カテゴリ * @param lastInCategory * 作成するパネルに、追加ボタンをつけるか? * @param changePartsIdentifierListener * パーツ選択が変わった場合のリスナ * @return 作成されたランダム選択パネル */ protected RandomChooserPanel addPartsChooserPanel(final Box centerPnl, final int addPos, final PartsCategory category, final boolean lastInCategory, final ActionListener changePartsIdentifierListener) { RandomChooserPanel pnl = new RandomChooserPanel(category, lastInCategory) { private static final long serialVersionUID = 1L; @Override protected void onClickAdd(ActionEvent e) { int mx = centerPnl.getComponentCount(); for (int idx = 0; idx < mx; idx++) { Component comp = centerPnl.getComponent(idx); if (comp.equals(this)) { // 同じカテゴリのものを追加する addPartsChooserPanel(centerPnl, idx + 1, category, lastInCategory, changePartsIdentifierListener); centerPnl.validate(); // Addボタンを非表示にする. ((JButton) e.getSource()).setVisible(false); break; } } } }; // パーツ選択変更を通知するリスナを設定する. pnl.addActionListener(changePartsIdentifierListener); centerPnl.add(pnl, addPos); return pnl; } } CharacterManaJ/src/charactermanaj/ui/ArchiveFileChooser.java0000644000175000017500000000577212560206305024321 0ustar paulliupaulliupackage charactermanaj.ui; import java.io.File; import java.util.Properties; import javax.swing.JFileChooser; import javax.swing.JOptionPane; import javax.swing.filechooser.FileFilter; import charactermanaj.model.AppConfig; import charactermanaj.util.LocalizedResourcePropertyLoader; /** * OK押下時に拡張子の補完とオーバーライドの警告を行うファイルチューザー.
* @author seraphy */ public class ArchiveFileChooser extends JFileChooser { private static final long serialVersionUID = -3908688762049311010L; protected static final String STRINGS_RESOURCE = "languages/exportwizdialog"; /** * Jarファイルフィルタ */ public static final FileFilter cmjFileFilter = new FileFilter() { @Override public String getDescription() { return "CharacterManaJ (*.cmj)"; } @Override public boolean accept(File f) { return f.isDirectory() || f.getName().endsWith(".cmj"); } }; /** * Zipファイルフィルタ */ public static final FileFilter zipFileFilter = new FileFilter() { @Override public String getDescription() { AppConfig appConfig = AppConfig.getInstance(); String zipNameEncoding = appConfig.getZipNameEncoding(); return "ZIP (" + zipNameEncoding + ") (*.zip)"; } @Override public boolean accept(File f) { return f.isDirectory() || f.getName().endsWith(".zip"); } }; protected boolean writeMode; protected ArchiveFileChooser(File initFile, boolean writeMode) { super(initFile); this.writeMode = writeMode; // フィルタの登録 addChoosableFileFilter(zipFileFilter); addChoosableFileFilter(cmjFileFilter); // デフォルトのフィルタ setFileFilter(zipFileFilter); } @Override public void approveSelection() { File file = getSelectedFile(); if (file == null) { return; } // ディレクトリ名を指定した場合は、そこに移動する. if (file.exists() && file.isDirectory()) { setCurrentDirectory(file); setSelectedFile(null); return; } String lcName = file.getName().toLowerCase(); FileFilter selfilter = getFileFilter(); if (selfilter == cmjFileFilter) { if (!lcName.endsWith(".cmj")) { file = new File(file.getPath() + ".cmj"); setSelectedFile(file); } } if (selfilter == zipFileFilter) { if (!lcName.endsWith(".zip")) { file = new File(file.getPath() + ".zip"); setSelectedFile(file); } } Properties strings = LocalizedResourcePropertyLoader.getCachedInstance() .getLocalizedProperties(STRINGS_RESOURCE); if (writeMode && file.exists()) { if (JOptionPane.showConfirmDialog(this, strings.getProperty("confirm.overwrite"), strings.getProperty("confirm"), JOptionPane.YES_NO_OPTION) != JOptionPane.YES_OPTION) { return; } } if (!writeMode && !file.exists()) { JOptionPane.showMessageDialog(this, strings.getProperty("requiredExists")); return; } super.approveSelection(); } }; CharacterManaJ/src/charactermanaj/ui/MenuBuilder.java0000644000175000017500000001536612560206305023030 0ustar paulliupaulliupackage charactermanaj.ui; import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.RenderingHints; import java.util.HashMap; import java.util.Properties; import javax.swing.JCheckBoxMenuItem; import javax.swing.JMenu; import javax.swing.JMenuBar; import javax.swing.JMenuItem; import javax.swing.JSeparator; import charactermanaj.ui.scrollablemenu.JScrollableMenu; import charactermanaj.util.LocalizedResourcePropertyLoader; /** * メニューを構築します. * * @author seraphy */ public class MenuBuilder { /** * メニュー項目のリソース */ protected static final String MENU_STRINGS_RESOURCE = "menu/menu"; /** * アンチエイリアスの設定が必要か? */ private static final boolean needAntiAlias = isNeedAntialias(); /** * メニュー項目のアンチエイリアスが必要か判定する.
* java.specification.versionが1.5で始まる場合は必要とみなす.
* * @return アンチエイリアスが必要であればtrue */ private static boolean isNeedAntialias() { return System.getProperty("java.specification.version").startsWith("1.5"); } /** * 生成したメニューのマップ */ private final HashMap menuMap = new HashMap(); /** * 生成したメニュー項目のマップ */ private final HashMap menuItemMap = new HashMap(); /** * 生成されたメニューを名前を指定して取得します.
* 存在しない場合は実行時例外が発生します.
* * @param name * メニュー名 * @return メニュー */ public JMenu getJMenu(String name) { JMenu menu = menuMap.get(name); if (menu == null) { throw new RuntimeException("登録されていないメニューです. " + name); } return menu; } /** * 生成されたメニュー項目を名前を指定して取得します.
* 存在しない場合は実行時例外が発生します.
* * @param name * メニュー項目名 * @return メニュー項目 */ public JMenuItem getJMenuItem(String name) { JMenuItem menuItem = menuItemMap.get(name); if (menuItem == null) { throw new RuntimeException("登録されていないメニュー項目です. " + name); } return menuItem; } /** * メニュー設定に従いメニューバーを構築して返します.
* 生成したメニューとメニュー項目は、{@link #getJMenu(String)}, {@link #getJMenuItem(String)} * で取得できます.
* * @param menus * メニュー設定 * @return 構築されたメニューバー */ public JMenuBar createMenuBar(MenuDataFactory[] menus) { // メニューリソース Properties menuProps = LocalizedResourcePropertyLoader.getCachedInstance() .getLocalizedProperties(MENU_STRINGS_RESOURCE); // メニューバー final JMenuBar menuBar = createJMenuBar(); // 現在のメニュー設定をクリアする. menuMap.clear(); menuItemMap.clear(); // メニュー設定に従いメニューを構築する. for (MenuDataFactory menuDataFactory : menus) { MenuData menuData = menuDataFactory.createMenuData(menuProps); createMenu(new MenuAppender() { public void addMenu(JMenu menu) { menuBar.add(menu); } }, menuData, menuProps); } return menuBar; } private interface MenuAppender { void addMenu(JMenu menu); } protected void createMenu(MenuAppender parentMenu, MenuData menuData, Properties menuProps) { final JMenu menu = createJMenu(); if (menuData.makeMenu(menu)) { parentMenu.addMenu(menu); menuMap.put(menuData.getName(), menu); for (MenuData child : menuData) { if (child == null) { // セパレータ menu.add(new JSeparator()); } else if (child.getActionListener() == null) { // アクションリスナなしの場合はサブメニューと見なす createMenu(new MenuAppender() { public void addMenu(JMenu childMenu) { menu.add(childMenu); } }, child, menuProps); } else { // メニュー項目(チェックボックスつきメニュー項目を含む) JMenuItem menuItem; if (child.isCheckbox()) { menuItem = createJCheckBoxMenuItem(); } else { menuItem = createJMenuItem(); } if (child.makeMenu(menuItem)) { menu.add(menuItem); menuItemMap.put(child.getName(), menuItem); } } } } } /** * JMenuBarを構築します.
* アンチエイリアスが必要な場合はアンチエイリアスが設定されます.
* * @return JMenuBar */ public JMenuBar createJMenuBar() { return new JMenuBar() { private static final long serialVersionUID = 1L; @Override public void paint(Graphics g) { setAntiAlias(g); super.paint(g); } }; } /** * JMenuを構築します.
* アンチエイリアスが必要な場合はアンチエイリアスが設定されます.
* * @return JMenu */ public JMenu createJMenu() { if (JScrollableMenu.isScreenMenu()) { return new JMenu() { private static final long serialVersionUID = 1L; @Override public void paint(Graphics g) { setAntiAlias(g); super.paint(g); } }; } else { return new JScrollableMenu() { private static final long serialVersionUID = 1L; @Override public void paint(Graphics g) { setAntiAlias(g); super.paint(g); } }; } } /** * JCheckBoxMenuItemを構築します.
* アンチエイリアスが必要な場合はアンチエイリアスが設定されます.
* * @return JCheckBoxMenuItem */ public JCheckBoxMenuItem createJCheckBoxMenuItem() { return new JCheckBoxMenuItem() { private static final long serialVersionUID = 1L; @Override public void paint(Graphics g) { setAntiAlias(g); super.paint(g); } }; } /** * JMenuItemを構築します.
* アンチエイリアスが必要な場合はアンチエイリアスが設定されます.
* * @return JMenuItem */ public JMenuItem createJMenuItem() { return new JMenuItem() { private static final long serialVersionUID = 1L; @Override public void paint(Graphics g) { setAntiAlias(g); super.paint(g); } }; } /** * アンチエイリアスを有効にする. * * @param g */ private static void setAntiAlias(Graphics g) { if (needAntiAlias) { ((Graphics2D) g).setRenderingHint( RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON); } } } CharacterManaJ/src/charactermanaj/ui/PartsManageDialog.java0000644000175000017500000011141612560206305024130 0ustar paulliupaulliupackage charactermanaj.ui; import java.awt.BorderLayout; import java.awt.Color; import java.awt.Component; import java.awt.Container; import java.awt.GridBagConstraints; import java.awt.GridBagLayout; import java.awt.Insets; import java.awt.Toolkit; import java.awt.event.ActionEvent; import java.awt.event.KeyEvent; import java.awt.event.WindowAdapter; import java.awt.event.WindowEvent; import java.net.URI; import java.sql.Timestamp; import java.util.Arrays; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Properties; import java.util.concurrent.Semaphore; import java.util.logging.Level; import java.util.logging.Logger; import javax.swing.AbstractAction; import javax.swing.Action; import javax.swing.ActionMap; import javax.swing.BorderFactory; import javax.swing.Box; import javax.swing.InputMap; import javax.swing.JButton; import javax.swing.JComponent; import javax.swing.JDialog; import javax.swing.JFrame; import javax.swing.JLabel; import javax.swing.JOptionPane; import javax.swing.JPanel; import javax.swing.JPopupMenu; import javax.swing.JRootPane; import javax.swing.JScrollPane; import javax.swing.JSeparator; import javax.swing.JTable; import javax.swing.JTextField; import javax.swing.KeyStroke; import javax.swing.UIManager; import javax.swing.event.DocumentEvent; import javax.swing.event.DocumentListener; import javax.swing.event.ListSelectionEvent; import javax.swing.event.ListSelectionListener; import javax.swing.event.TableModelEvent; import javax.swing.event.TableModelListener; import javax.swing.table.TableCellRenderer; import javax.swing.table.TableColumnModel; import charactermanaj.Main; import charactermanaj.model.AppConfig; import charactermanaj.model.CharacterData; import charactermanaj.model.PartsAuthorInfo; import charactermanaj.model.PartsCategory; import charactermanaj.model.PartsIdentifier; import charactermanaj.model.PartsManageData; import charactermanaj.model.PartsManageData.PartsKey; import charactermanaj.model.PartsManageData.PartsVersionInfo; import charactermanaj.model.PartsSpec; import charactermanaj.model.io.PartsInfoXMLReader; import charactermanaj.model.io.PartsInfoXMLWriter; import charactermanaj.ui.model.AbstractTableModelWithComboBoxModel; import charactermanaj.util.DesktopUtilities; import charactermanaj.util.ErrorMessageHelper; import charactermanaj.util.LocalizedResourcePropertyLoader; public class PartsManageDialog extends JDialog { private static final long serialVersionUID = 1L; protected static final String STRINGS_RESOURCE = "languages/partsmanagedialog"; private static final Logger logger = Logger.getLogger(PartsManageDialog.class.getName()); private CharacterData characterData; private PartsManageTableModel partsManageTableModel; private JTable partsManageTable; private JTextField txtHomepage; private JTextField txtAuthor; private boolean updated; public PartsManageDialog(JFrame parent, CharacterData characterData) { super(parent, true); if (characterData == null) { throw new IllegalArgumentException(); } this.characterData = characterData; setDefaultCloseOperation(JDialog.DO_NOTHING_ON_CLOSE); addWindowListener(new WindowAdapter() { @Override public void windowClosing(WindowEvent e) { onClose(); } }); Properties strings = LocalizedResourcePropertyLoader.getCachedInstance() .getLocalizedProperties(STRINGS_RESOURCE); setTitle(strings.getProperty("title")); Container contentPane = getContentPane(); // パーツリストテーブル JPanel partsListPanel = new JPanel(); partsListPanel.setBorder(BorderFactory.createCompoundBorder( BorderFactory.createEmptyBorder(5, 5, 5, 5), BorderFactory .createTitledBorder(strings.getProperty("partslist")))); GridBagLayout partsListPanelLayout = new GridBagLayout(); partsListPanel.setLayout(partsListPanelLayout); partsManageTableModel = new PartsManageTableModel(); partsManageTable = new JTable(partsManageTableModel) { private static final long serialVersionUID = 1L; @Override public Component prepareRenderer(TableCellRenderer renderer, int rowIdx, int columnIdx) { PartsManageTableModel.Columns column = PartsManageTableModel.Columns .values()[columnIdx]; Component comp = super.prepareRenderer(renderer, rowIdx, columnIdx); PartsManageTableRow row = partsManageTableModel.getRow(rowIdx); Timestamp current = row.getTimestamp(); Timestamp lastModified = row.getLastModified(); boolean warnings = false; if (current != null && !current.equals(lastModified)) { // 現在のパーツの最終更新日と、パーツ管理情報の作成時のパーツの最終更新日が不一致の場合 warnings = true; } // 背景色、警告行は赤色に if (warnings && column == PartsManageTableModel.Columns.LastModified) { AppConfig appConfig = AppConfig.getInstance(); Color invalidBgColor = appConfig.getInvalidBgColor(); comp.setBackground(invalidBgColor); } else { if (isCellSelected(rowIdx, columnIdx)) { comp.setBackground(getSelectionBackground()); } else { comp.setBackground(getBackground()); } } return comp; } }; partsManageTable.setShowGrid(true); partsManageTable.setGridColor(AppConfig.getInstance().getGridColor()); partsManageTableModel.adjustColumnModel(partsManageTable.getColumnModel()); partsManageTable.setAutoResizeMode(JTable.AUTO_RESIZE_OFF); partsManageTable.putClientProperty("terminateEditOnFocusLost", Boolean.TRUE); JScrollPane partsManageTableSP = new JScrollPane(partsManageTable); partsManageTable.getSelectionModel().addListSelectionListener(new ListSelectionListener() { public void valueChanged(ListSelectionEvent e) { if (!e.getValueIsAdjusting()) { onChangeSelection(); } } }); partsManageTableModel.addTableModelListener(new TableModelListener() { public void tableChanged(TableModelEvent e) { onTableDataChange(e.getFirstRow(), e.getLastRow()); } }); GridBagConstraints gbc = new GridBagConstraints(); gbc.gridx = 0; gbc.gridy = 0; gbc.gridheight = 1; gbc.gridwidth = 4; gbc.anchor = GridBagConstraints.EAST; gbc.fill = GridBagConstraints.BOTH; gbc.insets = new Insets(3, 3, 3, 3); gbc.ipadx = 0; gbc.ipady = 0; gbc.weightx = 1.; gbc.weighty = 1.; partsListPanel.add(partsManageTableSP, gbc); Action actSortByName = new AbstractAction(strings.getProperty("sortByName")) { private static final long serialVersionUID = 1L; public void actionPerformed(ActionEvent e) { onSortByName(); } }; Action actSortByAuthor = new AbstractAction(strings.getProperty("sortByAuthor")) { private static final long serialVersionUID = 1L; public void actionPerformed(ActionEvent e) { onSortByAuthor(); } }; Action actSortByTimestamp = new AbstractAction(strings.getProperty("sortByTimestamp")) { private static final long serialVersionUID = 1L; public void actionPerformed(ActionEvent e) { onSortByTimestamp(); } }; gbc.gridx = 0; gbc.gridy = 1; gbc.gridheight = 1; gbc.gridwidth = 1; gbc.weightx = 0.; gbc.weighty = 0.; partsListPanel.add(new JButton(actSortByName), gbc); gbc.gridx = 1; gbc.gridy = 1; gbc.weightx = 0.; gbc.weighty = 0.; partsListPanel.add(new JButton(actSortByAuthor), gbc); gbc.gridx = 2; gbc.gridy = 1; gbc.weightx = 0.; gbc.weighty = 0.; partsListPanel.add(new JButton(actSortByTimestamp), gbc); gbc.gridx = 3; gbc.gridy = 1; gbc.weightx = 1.; gbc.weighty = 0.; partsListPanel.add(Box.createHorizontalGlue(), gbc); contentPane.add(partsListPanel, BorderLayout.CENTER); // テーブルのコンテキストメニュー final JPopupMenu popupMenu = new JPopupMenu(); Action actApplyAllLastModified = new AbstractAction(strings.getProperty("applyAllLastModified")) { private static final long serialVersionUID = 1L; public void actionPerformed(ActionEvent e) { onApplyAllLastModified(); } }; Action actApplyAllDownloadURL = new AbstractAction(strings.getProperty("applyAllDownloadURL")) { private static final long serialVersionUID = 1L; public void actionPerformed(ActionEvent e) { onApplyAllDownloadURL(); } }; Action actApplyAllVersion = new AbstractAction(strings.getProperty("applyAllVersion")) { private static final long serialVersionUID = 1L; public void actionPerformed(ActionEvent e) { onApplyAllVersion(); } }; popupMenu.add(actApplyAllLastModified); popupMenu.add(new JSeparator()); popupMenu.add(actApplyAllVersion); popupMenu.add(actApplyAllDownloadURL); partsManageTable.setComponentPopupMenu(popupMenu); // 作者情報パネル JPanel authorPanel = new JPanel(); authorPanel.setBorder(BorderFactory.createCompoundBorder(BorderFactory .createEmptyBorder(5, 5, 5, 5), BorderFactory .createTitledBorder(strings.getProperty("author.info")))); GridBagLayout authorPanelLayout = new GridBagLayout(); authorPanel.setLayout(authorPanelLayout); gbc.gridx = 0; gbc.gridy = 0; gbc.gridheight = 1; gbc.gridwidth = 1; gbc.anchor = GridBagConstraints.EAST; gbc.fill = GridBagConstraints.BOTH; gbc.insets = new Insets(3, 3, 3, 3); gbc.ipadx = 0; gbc.ipady = 0; gbc.weightx = 0.; gbc.weighty = 0.; authorPanel.add(new JLabel(strings.getProperty("author"), JLabel.RIGHT), gbc); gbc.gridx = 1; gbc.gridy = 0; gbc.gridwidth = 2; gbc.weightx = 1.; txtAuthor = new JTextField(); authorPanel.add(txtAuthor, gbc); gbc.gridx = 0; gbc.gridy = 1; gbc.gridwidth = 1; gbc.weightx = 0.; authorPanel.add(new JLabel(strings.getProperty("homepage"), JLabel.RIGHT), gbc); gbc.gridx = 1; gbc.gridy = 1; gbc.gridwidth = 1; gbc.weightx = 1.; txtHomepage = new JTextField(); authorPanel.add(txtHomepage, gbc); gbc.gridx = 2; gbc.gridy = 1; gbc.gridwidth = 1; gbc.weightx = 0.; Action actBrowseHomepage = new AbstractAction(strings.getProperty("open")) { private static final long serialVersionUID = 1L; public void actionPerformed(ActionEvent e) { onBrosweHomepage(); } }; authorPanel.add(new JButton(actBrowseHomepage), gbc); if (!DesktopUtilities.isSupported()) { actBrowseHomepage.setEnabled(false); } txtAuthor.getDocument().addDocumentListener(new DocumentListener() { public void removeUpdate(DocumentEvent e) { onEditAuthor(); } public void insertUpdate(DocumentEvent e) { onEditAuthor(); } public void changedUpdate(DocumentEvent e) { onEditAuthor(); } }); txtHomepage.getDocument().addDocumentListener(new DocumentListener() { public void removeUpdate(DocumentEvent e) { onEditHomepage(); } public void insertUpdate(DocumentEvent e) { onEditHomepage(); } public void changedUpdate(DocumentEvent e) { onEditHomepage(); } }); // ボタンパネル JPanel btnPanel = new JPanel(); btnPanel.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 45)); GridBagLayout btnPanelLayout = new GridBagLayout(); btnPanel.setLayout(btnPanelLayout); Action actClose = new AbstractAction(strings.getProperty("cancel")) { private static final long serialVersionUID = 1L; public void actionPerformed(ActionEvent e) { onClose(); } }; Action actOK = new AbstractAction(strings.getProperty("update")) { private static final long serialVersionUID = 1L; public void actionPerformed(ActionEvent e) { onOK(); } }; gbc.gridx = 0; gbc.gridy = 0; gbc.weightx = 1.; btnPanel.add(Box.createHorizontalGlue(), gbc); gbc.gridx = Main.isLinuxOrMacOSX() ? 2 : 1; gbc.gridy = 0; gbc.weightx = 0.; btnPanel.add(new JButton(actOK), gbc); gbc.gridx = Main.isLinuxOrMacOSX() ? 1 : 2; gbc.gridy = 0; gbc.weightx = 0.; btnPanel.add(new JButton(actClose), gbc); // ダイアログ下部 JPanel southPanel = new JPanel(new BorderLayout()); southPanel.add(authorPanel, BorderLayout.NORTH); southPanel.add(btnPanel, BorderLayout.SOUTH); contentPane.add(southPanel, BorderLayout.SOUTH); // キーボード Toolkit tk = Toolkit.getDefaultToolkit(); JRootPane rootPane = getRootPane(); InputMap im = rootPane.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW); ActionMap am = rootPane.getActionMap(); im.put(KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0), "closePartsManageDialog"); im.put(KeyStroke.getKeyStroke(KeyEvent.VK_W, tk.getMenuShortcutKeyMask()), "closePartsManageDialog"); am.put("closePartsManageDialog", actClose); // モデル構築 partsManageTableModel.initModel(characterData); // ウィンドウ配置 setSize(500, 400); setLocationRelativeTo(parent); } private Semaphore authorEditSemaphore = new Semaphore(1); protected void onChangeSelection() { try { authorEditSemaphore.acquire(); try { int [] selRows = partsManageTable.getSelectedRows(); HashSet authors = new HashSet(); for (int selRow : selRows) { PartsManageTableRow row = partsManageTableModel.getRow(selRow); authors.add(row.getAuthor() == null ? "" : row.getAuthor()); } if (authors.size() > 1) { AppConfig appConfig = AppConfig.getInstance(); txtAuthor.setBackground(appConfig.getAuthorEditConflictBgColor()); txtHomepage.setBackground(appConfig.getAuthorEditConflictBgColor()); } else { Color bgColor = UIManager.getColor("TextField.background"); if (bgColor == null) { bgColor = Color.white; } txtAuthor.setBackground(bgColor); txtHomepage.setBackground(bgColor); } if (authors.isEmpty()) { // 選択されているauthorがない場合は全部編集不可 txtAuthor.setEditable(false); txtAuthor.setText(""); txtHomepage.setEditable(false); txtHomepage.setText(""); } else { // 選択されているAuthorが1つ以上あればAuthorは編集可 txtAuthor.setEditable(true); txtHomepage.setEditable(true); if (authors.size() == 1) { // 選択されているAuthorが一個であれば、それを表示 String author = authors.iterator().next(); String homepage = partsManageTableModel.getHomepage(author); txtAuthor.setText(author); txtHomepage.setText(homepage); } else { // 選択されているAuthorが二個以上あれば編集可能だがテキストには表示しない. txtAuthor.setText(""); txtHomepage.setText(""); } } } finally { authorEditSemaphore.release(); } } catch (InterruptedException ex) { ErrorMessageHelper.showErrorDialog(this, ex); } catch (RuntimeException ex) { ErrorMessageHelper.showErrorDialog(this, ex); } } protected void onTableDataChange(int firstRow, int lastRow) { onChangeSelection(); } protected void onApplyAllLastModified() { int[] selRows = partsManageTable.getSelectedRows(); if (selRows.length == 0) { Toolkit tk = Toolkit.getDefaultToolkit(); tk.beep(); return; } Arrays.sort(selRows); for (int selRow : selRows) { PartsManageTableRow row = partsManageTableModel.getRow(selRow); Timestamp dt = row.getTimestamp(); row.setLastModified(dt); } partsManageTableModel.fireTableRowsUpdated(selRows[0], selRows[selRows.length - 1]); } protected void onApplyAllDownloadURL() { int[] selRows = partsManageTable.getSelectedRows(); if (selRows.length == 0) { Toolkit tk = Toolkit.getDefaultToolkit(); tk.beep(); return; } Arrays.sort(selRows); HashSet authors = new HashSet(); for (int selRow : selRows) { PartsManageTableRow row = partsManageTableModel.getRow(selRow); authors.add(row.getAuthor() == null ? "" : row.getAuthor()); } Properties strings = LocalizedResourcePropertyLoader.getCachedInstance() .getLocalizedProperties(STRINGS_RESOURCE); if (authors.size() > 1) { if (JOptionPane.showConfirmDialog(this, strings.getProperty("confirm.authorConflict"), strings.getProperty("confirm"), JOptionPane.OK_CANCEL_OPTION) != JOptionPane.OK_OPTION) { return; } } PartsManageTableRow firstRow = partsManageTableModel.getRow(selRows[0]); String downloadURL = firstRow.getDownloadURL(); if (downloadURL == null) { downloadURL = ""; } String downloadURL_new = JOptionPane.showInputDialog(this, strings.getProperty("input.downloadURL"), downloadURL); if (downloadURL_new == null || downloadURL.equals(downloadURL_new)) { // キャンセルされたか、内容に変化ない場合は何もしない return; } for (int selRow : selRows) { PartsManageTableRow row = partsManageTableModel.getRow(selRow); row.setDownloadURL(downloadURL_new); Timestamp dt = row.getTimestamp(); row.setLastModified(dt); } partsManageTableModel.fireTableRowsUpdated(selRows[0], selRows[selRows.length - 1]); } protected void onApplyAllVersion() { Toolkit tk = Toolkit.getDefaultToolkit(); int[] selRows = partsManageTable.getSelectedRows(); if (selRows.length == 0) { tk.beep(); return; } Arrays.sort(selRows); HashSet authors = new HashSet(); for (int selRow : selRows) { PartsManageTableRow row = partsManageTableModel.getRow(selRow); authors.add(row.getAuthor() == null ? "" : row.getAuthor()); } Properties strings = LocalizedResourcePropertyLoader.getCachedInstance() .getLocalizedProperties(STRINGS_RESOURCE); if (authors.size() > 1) { if (JOptionPane.showConfirmDialog(this, strings.getProperty("confirm.authorConflict"), strings.getProperty("confirm"), JOptionPane.OK_CANCEL_OPTION) != JOptionPane.OK_OPTION) { return; } } PartsManageTableRow firstRow = partsManageTableModel.getRow(selRows[0]); double version = firstRow.getVersion(); String strVersion = (version < 0) ? "" : Double.toString(version); String strVersion_new = JOptionPane.showInputDialog(this, strings.getProperty("input.version"), strVersion); if (strVersion_new == null || strVersion.equals(strVersion_new)) { // キャンセルされたか、内容に変化ない場合は何もしない return; } double version_new; try { version_new = Double.parseDouble(strVersion_new); } catch (Exception ex) { // 数値として不正であれば何もしない. tk.beep(); return; } for (int selRow : selRows) { PartsManageTableRow row = partsManageTableModel.getRow(selRow); row.setVersion(version_new); Timestamp dt = row.getTimestamp(); row.setLastModified(dt); } partsManageTableModel.fireTableRowsUpdated(selRows[0], selRows[selRows.length - 1]); } protected void onEditHomepage() { try { if (!authorEditSemaphore.tryAcquire()) { return; } try { String author = txtAuthor.getText(); String homepage = txtHomepage.getText(); partsManageTableModel.setHomepage(author, homepage); } finally { authorEditSemaphore.release(); } } catch (Exception ex) { ErrorMessageHelper.showErrorDialog(this, ex); } } protected void onEditAuthor() { try { if (!authorEditSemaphore.tryAcquire()) { return; } try { String author = txtAuthor.getText(); int[] selRows = partsManageTable.getSelectedRows(); int firstRow = -1; int lastRow = Integer.MAX_VALUE; for (int selRow : selRows) { PartsManageTableRow row = partsManageTableModel.getRow(selRow); String oldValue = row.getAuthor(); if (oldValue == null || !oldValue.equals(author)) { row.setAuthor(author); Timestamp dt = row.getTimestamp(); row.setLastModified(dt); firstRow = Math.max(firstRow, selRow); lastRow = Math.min(lastRow, selRow); } } String homepage = partsManageTableModel.getHomepage(author); if (homepage == null) { homepage = ""; } txtHomepage.setText(homepage); if (firstRow >= 0) { partsManageTable.repaint(); } } finally { authorEditSemaphore.release(); } } catch (Exception ex) { ErrorMessageHelper.showErrorDialog(this, ex); } } protected void onClose() { Properties strings = LocalizedResourcePropertyLoader.getCachedInstance() .getLocalizedProperties(STRINGS_RESOURCE); if (JOptionPane.showConfirmDialog(this, strings.getProperty("confirm.cancel"), strings.getProperty("confirm"), JOptionPane.YES_NO_OPTION) != JOptionPane.YES_OPTION) { return; } updated = false; dispose(); } protected void onBrosweHomepage() { Toolkit tk = Toolkit.getDefaultToolkit(); String homepage = txtHomepage.getText(); if (homepage == null || homepage.trim().length() == 0) { tk.beep(); return; } try { URI uri = new URI(homepage); DesktopUtilities.browse(uri); } catch (Exception ex) { tk.beep(); logger.log(Level.INFO, "browse failed. : " + homepage, ex); } } protected void onSortByAuthor() { partsManageTableModel.sortByAuthor(); } protected void onSortByName() { partsManageTableModel.sortByName(); } protected void onSortByTimestamp() { partsManageTableModel.sortByTimestamp(); } protected void onOK() { if (partsManageTable.isEditing()) { Toolkit tk = Toolkit.getDefaultToolkit(); tk.beep(); return; } int mx = partsManageTableModel.getRowCount(); // 作者ごとのホームページ情報の取得 // (同一作者につきホームページは一つ) HashMap authorInfoMap = new HashMap(); for (int idx = 0; idx < mx; idx++) { PartsManageTableRow row = partsManageTableModel.getRow(idx); String author = row.getAuthor(); String homepage = row.getHomepage(); if (author != null && author.trim().length() > 0) { PartsAuthorInfo authorInfo = authorInfoMap.get(author.trim()); if (authorInfo == null) { authorInfo = new PartsAuthorInfo(); authorInfo.setAuthor(author.trim()); authorInfoMap.put(authorInfo.getAuthor(), authorInfo); } authorInfo.setHomePage(homepage); } } PartsManageData partsManageData = new PartsManageData(); // パーツごとの作者とバージョン、ダウンロード先の取得 for (int idx = 0; idx < mx; idx++) { PartsManageTableRow row = partsManageTableModel.getRow(idx); String author = row.getAuthor(); PartsAuthorInfo partsAuthorInfo = null; if (author != null && author.trim().length() > 0) { partsAuthorInfo = authorInfoMap.get(author.trim()); } double version = row.getVersion(); String downloadURL = row.getDownloadURL(); String localizedName = row.getLocalizedName(); Timestamp lastModified = row.getLastModified(); PartsManageData.PartsVersionInfo versionInfo = new PartsManageData.PartsVersionInfo(); versionInfo.setVersion(version); versionInfo.setDownloadURL(downloadURL); versionInfo.setLastModified(lastModified); PartsIdentifier partsIdentifier = row.getPartsIdentifier(); PartsManageData.PartsKey partsKey = new PartsManageData.PartsKey(partsIdentifier); partsManageData.putPartsInfo(partsKey, localizedName, partsAuthorInfo, versionInfo); } // パーツ管理情報を書き込む. PartsInfoXMLWriter xmlWriter = new PartsInfoXMLWriter(); try { URI docBase = characterData.getDocBase(); xmlWriter.savePartsManageData(docBase, partsManageData); } catch (Exception ex) { ErrorMessageHelper.showErrorDialog(this, ex); return; } updated = true; dispose(); } /** * パーツ管理情報が更新されたか? * * @return 更新された場合はtrue、そうでなければfalse */ public boolean isUpdated() { return updated; } } class PartsManageTableRow { private PartsIdentifier partsIdentifier; private Timestamp timestamp; private String localizedName; private String author; private String homepage; private String downloadURL; private double version; private Timestamp lastModified; public PartsManageTableRow(PartsIdentifier partsIdentifier, PartsSpec partsSpec, Timestamp lastModified) { if (partsIdentifier == null || partsSpec == null) { throw new IllegalArgumentException(); } this.partsIdentifier = partsIdentifier; this.localizedName = partsIdentifier.getLocalizedPartsName(); this.timestamp = new Timestamp(partsSpec.getPartsFiles().lastModified()); this.lastModified = lastModified; PartsAuthorInfo autherInfo = partsSpec.getAuthorInfo(); if (autherInfo != null) { author = autherInfo.getAuthor(); homepage = autherInfo.getHomePage(); } if (author == null) { author = ""; } if (homepage == null) { homepage = ""; } downloadURL = partsSpec.getDownloadURL(); version = partsSpec.getVersion(); } public PartsIdentifier getPartsIdentifier() { return partsIdentifier; } public Timestamp getTimestamp() { return timestamp; } public String getLocalizedName() { return localizedName; } public void setLocalizedName(String localizedName) { if (localizedName == null || localizedName.trim().length() == 0) { throw new IllegalArgumentException(); } this.localizedName = localizedName; } public String getAuthor() { return author; } public String getDownloadURL() { return downloadURL; } public double getVersion() { return version; } public void setAuthor(String author) { this.author = author; } public void setDownloadURL(String downloadURL) { this.downloadURL = downloadURL; } public void setVersion(double version) { this.version = version; } public String getHomepage() { return homepage; } public void setHomepage(String homepage) { this.homepage = homepage; } public Timestamp getLastModified() { return lastModified; } public void setLastModified(Timestamp lastModified) { this.lastModified = lastModified; } } class PartsManageTableModel extends AbstractTableModelWithComboBoxModel { private static final long serialVersionUID = 1L; private static final Logger logger = Logger .getLogger(PartsManageTableModel.class.getName()); private static Properties strings = LocalizedResourcePropertyLoader .getCachedInstance().getLocalizedProperties( PartsManageDialog.STRINGS_RESOURCE); /** * カラムの定義 */ public enum Columns { PartsID("column.partsid", false, String.class) { @Override public Object getValue(PartsManageTableRow row) { return row.getPartsIdentifier().getPartsName(); } }, LastModified("column.lastmodified", false, String.class) { @Override public Object getValue(PartsManageTableRow row) { return row.getTimestamp().toString(); } }, Category("column.category", false, String.class) { @Override public Object getValue(PartsManageTableRow row) { return row.getPartsIdentifier().getPartsCategory() .getLocalizedCategoryName(); } }, PartsName("column.partsname", true, String.class) { @Override public Object getValue(PartsManageTableRow row) { return row.getLocalizedName(); } @Override public void setValue(PartsManageTableRow row, Object value) { String localizedName = (String) value; if (localizedName != null && localizedName.trim().length() > 0) { String oldValue = row.getLocalizedName(); if (oldValue != null && oldValue.equals(localizedName)) { return; // 変化なし } row.setLocalizedName(localizedName); } } }, Author("column.author", true, String.class) { @Override public Object getValue(PartsManageTableRow row) { return row.getAuthor(); } @Override public void setValue(PartsManageTableRow row, Object value) { String author = (String) value; if (author == null) { author = ""; } String oldValue = row.getAuthor(); if (oldValue != null && oldValue.equals(author)) { return; // 変化なし } row.setAuthor(author); } }, Version("column.version", true, Double.class) { @Override public Object getValue(PartsManageTableRow row) { return row.getVersion() > 0 ? row.getVersion() : null; } @Override public void setValue(PartsManageTableRow row, Object value) { Double version = (Double) value; if (version == null || version.doubleValue() <= 0) { version = Double.valueOf(0.); } Double oldValue = row.getVersion(); if (oldValue != null && oldValue.equals(version)) { return; // 変化なし } row.setVersion(version); } }, DownloadURL("column.downloadURL", true, String.class) { @Override public Object getValue(PartsManageTableRow row) { return row.getDownloadURL(); } @Override public void setValue(PartsManageTableRow row, Object value) { String downloadURL = (String) value; if (downloadURL == null) { downloadURL = ""; } String oldValue = row.getDownloadURL(); if (oldValue != null && oldValue.equals(downloadURL)) { return; // 変化なし } row.setDownloadURL(downloadURL); } }; private final Class columnClass; private final boolean editable; private final String columnName; private String displayName; private int width; private Columns(String columnName, boolean editable, Class columnClass) { this.columnName = columnName; this.editable = editable; this.columnClass = columnClass; } public abstract Object getValue(PartsManageTableRow row); public boolean isEditable() { return editable; } public Class getColumnClass() { return columnClass; } public String getDisplayName() { init(); return displayName; } public int getWidth() { init(); return width; } public void setValue(PartsManageTableRow row, Object value) { // 何もしない. } private void init() { if (displayName != null) { return; } displayName = strings.getProperty(columnName); width = Integer .parseInt(strings.getProperty(columnName + ".width")); } } public int getColumnCount() { return Columns.values().length; } @Override public String getColumnName(int column) { return Columns.values()[column].getDisplayName(); } public void adjustColumnModel(TableColumnModel columnModel) { Columns[] columns = Columns.values(); for (int idx = 0; idx < columns.length; idx++) { columnModel.getColumn(idx).setPreferredWidth( columns[idx].getWidth()); } } public Object getValueAt(int rowIndex, int columnIndex) { PartsManageTableRow row = getRow(rowIndex); Columns column = Columns.values()[columnIndex]; return column.getValue(row); } @Override public void setValueAt(Object aValue, int rowIndex, int columnIndex) { PartsManageTableRow row = getRow(rowIndex); Columns column = Columns.values()[columnIndex]; if (!column.isEditable()) { return; } column.setValue(row, aValue); // 更新日を最新にする Timestamp dt = row.getTimestamp(); row.setLastModified(dt); // 変更通知 fireTableRowsUpdated(rowIndex, columnIndex); } @Override public Class getColumnClass(int columnIndex) { Columns column = Columns.values()[columnIndex]; return column.getColumnClass(); } @Override public boolean isCellEditable(int rowIndex, int columnIndex) { Columns column = Columns.values()[columnIndex]; return column.isEditable(); } public void initModel(CharacterData characterData) { if (characterData == null) { throw new IllegalArgumentException(); } clear(); // 既存のパーツ管理情報ファイルがあれば読み込む URI docBase = characterData.getDocBase(); PartsManageData partsManageData = null; if (docBase != null) { try { PartsInfoXMLReader reader = new PartsInfoXMLReader(); partsManageData = reader.loadPartsManageData(docBase); } catch (Exception ex) { logger.log(Level.WARNING, ex.toString(), ex); } } if (partsManageData == null) { partsManageData = new PartsManageData(); } for (PartsCategory partsCategory : characterData.getPartsCategories()) { for (Map.Entry entry : characterData .getPartsSpecMap(partsCategory).entrySet()) { PartsIdentifier partsIdentifier = entry.getKey(); PartsSpec partsSpec = entry.getValue(); // パーツ管理情報ファイルから、パーツ管理情報を設定した時点の // ファイルサイズや更新日時などを読み取る. PartsKey partsKey = new PartsKey(partsIdentifier); PartsVersionInfo versionInfo = partsManageData .getVersion(partsKey); Timestamp lastModified = null; if (versionInfo != null) { lastModified = versionInfo.getLastModified(); } PartsManageTableRow row = new PartsManageTableRow( partsIdentifier, partsSpec, lastModified); addRow(row); } } sortByAuthor(); } /** * ホームページを設定する.
* ホームページはAuthorに対して1つであるが、Authorが自由編集可能であるため便宜的にRowに持たせている.
* 結果として同じAuthorに対して同じ値を設定する必要がある.
* ホームページはテーブルに表示されないのでリスナーへの通知は行わない.
* * @param author * 作者、空またはnullは何もしない. * @param homepage * ホームページ */ public void setHomepage(String author, String homepage) { if (author == null || author.length() == 0) { return; } for (PartsManageTableRow row : elements) { String targetAuthor = row.getAuthor(); if (targetAuthor == null) { targetAuthor = ""; } if (targetAuthor.equals(author)) { row.setHomepage(homepage); } } } /** * ホームページを取得する.
* 該当する作者がないか、作者がnullまたは空の場合は常にnullを返す.
* * @param author * 作者 * @return ホームページ、またはnull */ public String getHomepage(String author) { if (author == null || author.length() == 0) { return null; } for (PartsManageTableRow row : elements) { String targetAuthor = row.getAuthor(); if (targetAuthor == null) { targetAuthor = ""; } if (targetAuthor.equals(author)) { return row.getHomepage(); } } return null; } protected static final Comparator NAMED_SORTER = new Comparator() { public int compare(PartsManageTableRow o1, PartsManageTableRow o2) { // カテゴリで順序づけ int ret = o1.getPartsIdentifier().getPartsCategory().compareTo( o2.getPartsIdentifier().getPartsCategory()); if (ret == 0) { // 表示名で順序づけ String lnm1 = o1.getLocalizedName(); String lnm2 = o2.getLocalizedName(); if (lnm1 == null) { lnm1 = ""; } if (lnm2 == null) { lnm2 = ""; } ret = lnm1.compareTo(lnm2); } if (ret == 0) { // それでも判定できなければ元の識別子で判定する. ret = o1.getPartsIdentifier().compareTo(o2.getPartsIdentifier()); } return ret; } }; public void sortByName() { Collections.sort(elements, NAMED_SORTER); fireTableDataChanged(); } public void sortByTimestamp() { Collections.sort(elements, new Comparator() { public int compare(PartsManageTableRow o1, PartsManageTableRow o2) { // 更新日で順序づけ (新しいもの順) int ret = o1.getTimestamp().compareTo(o2.getTimestamp()) * -1; if (ret == 0) { // それでも判定できなければ名前順と同じ ret = NAMED_SORTER.compare(o1, o2); } return ret; } }); fireTableDataChanged(); } public void sortByAuthor() { Collections.sort(elements, new Comparator() { public int compare(PartsManageTableRow o1, PartsManageTableRow o2) { // 作者で順序づけ String author1 = o1.getAuthor(); String author2 = o2.getAuthor(); if (author1 == null) { author1 = ""; } if (author2 == null) { author2 = ""; } int ret = author1.compareTo(author2); if (ret == 0) { // それでも判定できなければ名前順と同じ ret = NAMED_SORTER.compare(o1, o2); } return ret; } }); fireTableDataChanged(); } } CharacterManaJ/src/charactermanaj/ui/SearchPartsDialog.java0000644000175000017500000003541012560206305024144 0ustar paulliupaulliupackage charactermanaj.ui; import java.awt.BorderLayout; import java.awt.Container; import java.awt.GridBagConstraints; import java.awt.GridBagLayout; import java.awt.Insets; import java.awt.Toolkit; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.KeyEvent; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import java.awt.event.WindowAdapter; import java.awt.event.WindowEvent; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Properties; import java.util.WeakHashMap; import javax.swing.AbstractAction; import javax.swing.ActionMap; import javax.swing.BorderFactory; import javax.swing.Box; import javax.swing.InputMap; import javax.swing.JButton; import javax.swing.JComboBox; import javax.swing.JComponent; import javax.swing.JDialog; import javax.swing.JFrame; import javax.swing.JLabel; import javax.swing.JPanel; import javax.swing.JRootPane; import javax.swing.JScrollPane; import javax.swing.JTable; import javax.swing.JTextField; import javax.swing.KeyStroke; import javax.swing.event.DocumentEvent; import javax.swing.event.DocumentListener; import javax.swing.table.TableColumnModel; import charactermanaj.model.AppConfig; import charactermanaj.model.PartsCategory; import charactermanaj.model.PartsIdentifier; import charactermanaj.model.PartsSpec; import charactermanaj.model.PartsSpecResolver; import charactermanaj.ui.model.AbstractTableModelWithComboBoxModel; import charactermanaj.ui.model.PartsSelectionManager; import charactermanaj.util.LocalizedResourcePropertyLoader; public class SearchPartsDialog extends JDialog { private static final long serialVersionUID = 1L; private static final WeakHashMap ALL_DIALOGS = new WeakHashMap(); protected static final String STRINGS_RESOURCE = "languages/searchpartsdialog"; private PartsSpecResolver partsSpecResolver; private PartsSelectionManager partsSelectionManager; private JTable searchPartsTable; private SearchPartsTableModel searchPartsTableModel; private JTextField txtPartsName; private JComboBox cmbAuthors; private JComboBox cmbCategories; public static SearchPartsDialog[] getDialogs() { return ALL_DIALOGS.keySet().toArray(new SearchPartsDialog[ALL_DIALOGS.size()]); } public SearchPartsDialog(JFrame parent, PartsSpecResolver partsSpecResolver, PartsSelectionManager partsSelectionManager) { super(parent, false); if (partsSpecResolver == null) { throw new IllegalArgumentException(); } this.partsSpecResolver = partsSpecResolver; this.partsSelectionManager = partsSelectionManager; setDefaultCloseOperation(JDialog.DO_NOTHING_ON_CLOSE); addWindowListener(new WindowAdapter() { @Override public void windowClosing(WindowEvent e) { onClose(); } }); Properties strings = LocalizedResourcePropertyLoader.getCachedInstance() .getLocalizedProperties(STRINGS_RESOURCE); setTitle(strings.getProperty("title")); // モデル searchPartsTableModel = new SearchPartsTableModel(); // 検索条件パネル JPanel searchCondPanel = new JPanel(); GridBagLayout searchCondPanelLayout = new GridBagLayout(); searchCondPanel.setLayout(searchCondPanelLayout); GridBagConstraints gbc = new GridBagConstraints(); searchCondPanel.setBorder(BorderFactory.createCompoundBorder( BorderFactory.createEmptyBorder(5, 5, 5, 5), BorderFactory .createTitledBorder(strings.getProperty("search.condition")))); gbc.gridx = 0; gbc.gridy = 0; gbc.gridheight = 1; gbc.gridwidth = 1; gbc.anchor = GridBagConstraints.EAST; gbc.fill = GridBagConstraints.BOTH; gbc.ipadx = 0; gbc.ipady = 0; gbc.insets = new Insets(3, 3, 3, 3); gbc.weightx = 0.; gbc.weighty = 0.; searchCondPanel.add(new JLabel(strings.getProperty("partsname"), JLabel.RIGHT), gbc); gbc.gridx = 1; gbc.gridy = 0; gbc.weightx = 1.; txtPartsName = new JTextField(); searchCondPanel.add(txtPartsName, gbc); gbc.gridx = 0; gbc.gridy = 1; gbc.gridwidth = 1; gbc.weightx = 0.; searchCondPanel.add(new JLabel(strings.getProperty("author"), JLabel.RIGHT), gbc); gbc.gridx = 1; gbc.gridy = 1; gbc.gridwidth = 2; gbc.weightx = 1.; ArrayList authors = new ArrayList(); authors.add(""); authors.addAll(getAuthors(partsSpecResolver)); cmbAuthors = new JComboBox(authors.toArray(new String[authors.size()])); searchCondPanel.add(cmbAuthors, gbc); gbc.gridx = 0; gbc.gridy = 2; gbc.gridwidth = 1; gbc.weightx = 0.; searchCondPanel.add(new JLabel(strings.getProperty("partscategory"), JLabel.RIGHT), gbc); gbc.gridx = 1; gbc.gridy = 2; gbc.gridwidth = 2; gbc.weightx = 1.; ArrayList categories = new ArrayList(); categories.add(null); categories.addAll(partsSpecResolver.getPartsCategories()); cmbCategories = new JComboBox(categories.toArray(new PartsCategory[categories.size()])); searchCondPanel.add(cmbCategories, gbc); gbc.gridx = 2; gbc.gridy = 0; gbc.gridheight = 1; gbc.gridwidth = 1; gbc.weightx = 0.; gbc.weighty = 1.; gbc.anchor = GridBagConstraints.SOUTH; gbc.fill = GridBagConstraints.HORIZONTAL; JButton btnClear = new JButton(new AbstractAction(strings.getProperty("clear")) { private static final long serialVersionUID = 1L; public void actionPerformed(ActionEvent e) { txtPartsName.setText(""); } }); searchCondPanel.add(btnClear, gbc); // 検索結果 JPanel searchResult = new JPanel(new BorderLayout()); searchResult.setBorder(BorderFactory.createCompoundBorder( BorderFactory.createEmptyBorder(0, 5, 0, 5), BorderFactory .createTitledBorder(strings.getProperty("results")))); searchPartsTable = new JTable(searchPartsTableModel); searchPartsTable.setShowGrid(true); searchPartsTable.setGridColor(AppConfig.getInstance().getGridColor()); JScrollPane searchPartsTableSP = new JScrollPane(searchPartsTable); searchPartsTableModel.adjustColumnModel(searchPartsTable.getColumnModel()); searchResult.add(searchPartsTableSP, BorderLayout.CENTER); searchPartsTable.addMouseListener(new MouseAdapter() { @Override public void mouseClicked(MouseEvent e) { if (e.getClickCount() == 2) { // ダブルクリック // 正確に2回 onSelect(); } } }); // テーブルのキーイベント ActionMap tblAm = searchPartsTable.getActionMap(); InputMap tblIm = searchPartsTable.getInputMap(); tblIm.put(KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0), "onSelect"); tblAm.put("onSelect", new AbstractAction() { private static final long serialVersionUID = 1L; public void actionPerformed(ActionEvent e) { onSelect(); } }); JPanel btnPanel = new JPanel(); btnPanel.setBorder(BorderFactory.createEmptyBorder(0, 5, 0, 10)); GridBagLayout btnPanelLayout = new GridBagLayout(); btnPanel.setLayout(btnPanelLayout); gbc.gridx = 0; gbc.gridy = 0; gbc.gridheight = 1; gbc.gridwidth = 1; gbc.anchor = GridBagConstraints.EAST; gbc.fill = GridBagConstraints.BOTH; gbc.ipadx = 0; gbc.ipady = 0; gbc.insets = new Insets(3, 3, 3, 3); gbc.weightx = 1.; gbc.weighty = 0.; btnPanel.add(Box.createHorizontalGlue(), gbc); gbc.gridx = 1; gbc.gridy = 0; gbc.weightx = 0.; JButton btnSelect = new JButton(new AbstractAction(strings.getProperty("select")) { private static final long serialVersionUID = 1L; public void actionPerformed(ActionEvent e) { onSelect(); } }); btnPanel.add(btnSelect, gbc); searchResult.add(btnPanel, BorderLayout.SOUTH); // 検索条件の入力が変更されたことを検知するリスナの登録 txtPartsName.getDocument().addDocumentListener(new DocumentListener() { public void removeUpdate(DocumentEvent e) { onChangeCondition(); } public void insertUpdate(DocumentEvent e) { onChangeCondition(); } public void changedUpdate(DocumentEvent e) { onChangeCondition(); } }); ActionListener changeListener = new ActionListener() { public void actionPerformed(ActionEvent e) { onChangeCondition(); } }; cmbAuthors.addActionListener(changeListener); cmbCategories.addActionListener(changeListener); // ESCキーとCTRL-Wで閉じる. Toolkit tk = Toolkit.getDefaultToolkit(); JRootPane rootPane = getRootPane(); InputMap im = rootPane.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW); ActionMap am = rootPane.getActionMap(); im.put(KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0), "closeSearchDialog"); im.put(KeyStroke.getKeyStroke(KeyEvent.VK_W, tk.getMenuShortcutKeyMask()), "closeSearchDialog"); am.put("closeSearchDialog", new AbstractAction() { private static final long serialVersionUID = 1L; public void actionPerformed(ActionEvent e) { onClose(); } }); // ウィンドウがアクティブになったときに検索フィールドにフォーカスをあてる addWindowFocusListener(new WindowAdapter() { public void windowGainedFocus(WindowEvent e) { txtPartsName.requestFocusInWindow(); } }); // 画面の設定 Container contentPane = getContentPane(); contentPane.setLayout(new BorderLayout()); contentPane.add(searchCondPanel, BorderLayout.NORTH); contentPane.add(searchResult, BorderLayout.CENTER); setSize(250, 300); setLocationRelativeTo(parent); // ダイアログの登録 ALL_DIALOGS.put(this, null); } public List getAuthors(PartsSpecResolver partsSpecResolver) { if (partsSpecResolver == null) { throw new IllegalArgumentException(); } HashSet authorsSet = new HashSet(); for (PartsCategory category : partsSpecResolver.getPartsCategories()) { for (Map.Entry entry : partsSpecResolver.getPartsSpecMap(category).entrySet()) { PartsSpec partsSpec = entry.getValue(); String author = partsSpec.getAuthor(); if (author != null) { authorsSet.add(author); } } } ArrayList authors = new ArrayList(authorsSet); Collections.sort(authors); return authors; } protected void onClose() { dispose(); } /** * 「選択」ボタンまたはテーブルのダブルクリックのハンドラ.
* 選択されている行のパーツ識別子をもとに、パーツにフォーカスをあてる.
*/ protected void onSelect() { int selRow = searchPartsTable.getSelectedRow(); if (selRow >= 0) { Map.Entry entry = searchPartsTableModel.getRow(selRow); PartsIdentifier partsIdentifier = entry.getKey(); partsSelectionManager.setSelection(partsIdentifier); } } protected void onChangeCondition() { String partsNamesRaw = txtPartsName.getText(); partsNamesRaw = partsNamesRaw.replace(" ", " "); // 全角空白を半角に変換 String[] condPartsNames = partsNamesRaw.split("\\s+"); PartsCategory condPartsCategory = (PartsCategory) cmbCategories.getSelectedItem(); String condAuthor = (String) cmbAuthors.getSelectedItem(); if (condAuthor != null && condAuthor.trim().length() == 0) { condAuthor = null; } ArrayList> partsIdentifiers = new ArrayList>(); for (PartsCategory partsCategory : partsSpecResolver.getPartsCategories()) { if (condPartsCategory != null && !condPartsCategory.equals(partsCategory)) { continue; } for (Map.Entry entry : partsSpecResolver.getPartsSpecMap(partsCategory).entrySet()) { PartsIdentifier partsIdentifier = entry.getKey(); PartsSpec partsSpec = entry.getValue(); if (condAuthor != null) { String author = partsSpec.getAuthor(); if (author == null || !author.equals(condAuthor)) { continue; } } String localizedPartsName = partsIdentifier.getLocalizedPartsName(); if (localizedPartsName != null) { for (String condPartsName : condPartsNames) { if (localizedPartsName.indexOf(condPartsName) >= 0) { partsIdentifiers.add(entry); continue; } } } } } Collections.sort(partsIdentifiers, new Comparator>() { public int compare(Entry o1, Entry o2) { PartsIdentifier partsIdentifier1 = o1.getKey(); PartsIdentifier partsIdentifier2 = o2.getKey(); return partsIdentifier1.compareTo(partsIdentifier2); } }); searchPartsTableModel.initModel(partsIdentifiers); } } class SearchPartsTableModel extends AbstractTableModelWithComboBoxModel> { private static final long serialVersionUID = 1L; private static final String[] COLUMN_NAMES; private static final int[] COLUMN_WIDTHS; static { Properties strings = LocalizedResourcePropertyLoader.getCachedInstance() .getLocalizedProperties(SearchPartsDialog.STRINGS_RESOURCE); COLUMN_NAMES = new String[] { strings.getProperty("column.partsname"), strings.getProperty("column.category"), strings.getProperty("column.author"), }; COLUMN_WIDTHS = new int[] { Integer.parseInt(strings.getProperty("column.partsname.width")), Integer.parseInt(strings.getProperty("column.category.width")), Integer.parseInt(strings.getProperty("column.author.width")), }; } public void adjustColumnModel(TableColumnModel columnModel) { for (int idx = 0; idx < COLUMN_WIDTHS.length; idx++) { columnModel.getColumn(idx).setPreferredWidth(COLUMN_WIDTHS[idx]); } } public int getColumnCount() { return COLUMN_NAMES.length; } @Override public String getColumnName(int column) { return COLUMN_NAMES[column]; } public Object getValueAt(int rowIndex, int columnIndex) { Map.Entry row = getRow(rowIndex); PartsIdentifier partsIdentifier = row.getKey(); PartsSpec partsSpec = row.getValue(); switch (columnIndex) { case 0: return partsIdentifier.getLocalizedPartsName(); case 1: return partsIdentifier.getPartsCategory().getLocalizedCategoryName(); case 2: return partsSpec.getAuthor(); } return ""; } public void initModel(List> partsIdentifiers) { clear(); if (partsIdentifiers != null) { for (Map.Entry entry : partsIdentifiers) { addRow(entry); } } } } CharacterManaJ/src/charactermanaj/ui/util/0000755000175000017500000000000012560206305020714 5ustar paulliupaulliuCharacterManaJ/src/charactermanaj/ui/util/WindowAdjustLocationSupport.java0000644000175000017500000000450512560206305027273 0ustar paulliupaulliupackage charactermanaj.ui.util; import java.awt.Dimension; import java.awt.GraphicsEnvironment; import java.awt.Insets; import java.awt.Point; import java.awt.Rectangle; import java.awt.Window; import javax.swing.JFrame; /** * ウィンドウの位置を調整するサポートクラス.
* * @author seraphy */ public final class WindowAdjustLocationSupport { /** * プライベートコンストラクタ */ private WindowAdjustLocationSupport() { super(); } /** * ウィンドウの表示位置をメインウィンドウの右側に調整する.
* 横位置Xはメインフレームの右側とし、縦位置Yはメインフレームの上位置からのoffset_yを加えた位置とする.
* * @param mainWindow * 基準位置となるメインウィンドウ * @param window * 位置を調整するウィンドウ * @param offset_y * 表示のYオフセット * @param sameHeight * 高さをメインウィンドウにそろえるか? */ public static void alignRight(JFrame mainWindow, Window window, int offset_y, boolean sameHeight) { // メインウィンドウよりも左側に位置づけする. // 縦位置はメインウィンドウの上端からオフセットを加えたものとする. Point pt = mainWindow.getLocation(); Insets insets = mainWindow.getInsets(); pt.x += mainWindow.getWidth(); pt.y += (offset_y * insets.top); // メインスクリーンサイズを取得する. GraphicsEnvironment genv = GraphicsEnvironment .getLocalGraphicsEnvironment(); Rectangle desktopSize = genv.getMaximumWindowBounds(); // メインスクリーンのサイズ(デスクトップ領域のみ) // メインスクリーンサイズを超えた場合は、はみ出た分を移動する. if ((pt.x + window.getWidth()) > desktopSize.width) { pt.x -= ((pt.x + window.getWidth()) - desktopSize.width); } if ((pt.y + window.getHeight()) > desktopSize.height) { pt.y -= ((pt.y + window.getHeight()) - desktopSize.height); } window.setLocation(pt); // 高さはメインフレームと同じにする. if (sameHeight) { Dimension siz = window.getSize(); siz.height = mainWindow.getHeight() - offset_y; window.setSize(siz); } } } CharacterManaJ/src/charactermanaj/ui/util/FileDropTarget.java0000644000175000017500000000764112560206305024442 0ustar paulliupaulliupackage charactermanaj.ui.util; import java.awt.datatransfer.DataFlavor; import java.awt.datatransfer.UnsupportedFlavorException; import java.awt.dnd.DnDConstants; import java.awt.dnd.DropTargetAdapter; import java.awt.dnd.DropTargetDropEvent; import java.io.File; import java.io.IOException; import java.net.URI; import java.net.URISyntaxException; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import java.util.logging.Level; import java.util.logging.Logger; /** * ファイルドロップターゲット.
* Windows/Macと、Linuxの両方のデスクトップのドロップをサポートする. * @author seraphy */ public class FileDropTarget extends DropTargetAdapter { /** * ロガー */ private final Logger logger = Logger.getLogger(getClass().getName()); protected FileDropListener fileDropListener; public FileDropTarget() { this(null); } public FileDropTarget(FileDropListener fileDropListener) { this.fileDropListener = fileDropListener; } public FileDropListener getFileDropListener() { return fileDropListener; } public void setFileDropListener(FileDropListener fileDropListener) { this.fileDropListener = fileDropListener; } protected void onDropFiles(List dropFiles) { if (fileDropListener != null) { if ( !dropFiles.isEmpty()) { fileDropListener.onDropFiles(dropFiles); } } } public void drop(DropTargetDropEvent dtde) { try { // urlListFlavor (RFC 2483 for the text/uri-list format) DataFlavor uriListFlavor; try { uriListFlavor = new DataFlavor("text/uri-list;class=java.lang.String"); } catch (ClassNotFoundException ex) { logger.log(Level.WARNING, "urlListFlavor is not supported.", ex); uriListFlavor = null; } final List dropFiles = new ArrayList(); // ドロップされたものが1つのファイルであれば受け入れる。 for (DataFlavor flavor : dtde.getCurrentDataFlavors()) { logger.log(Level.FINE, "flavor: " + flavor); if (DataFlavor.javaFileListFlavor.equals(flavor)) { dtde.acceptDrop(DnDConstants.ACTION_COPY); @SuppressWarnings({ "unchecked", "rawtypes" }) List files = (List) dtde.getTransferable().getTransferData(DataFlavor.javaFileListFlavor); logger.log(Level.FINER, "DragAndDrop files(javaFileListFlavor)=" + files); dropFiles.addAll(files); break; } if (uriListFlavor != null && uriListFlavor.equals(flavor)) { // LinuxではjavaFileListFlavorではなく、text/uri-listタイプで送信される. dtde.acceptDrop(DnDConstants.ACTION_COPY); String uriList = (String) dtde.getTransferable().getTransferData(uriListFlavor); logger.log(Level.FINER, "DragAndDrop files(text/uri-list)=" + uriList); for (String fileStr : uriList.split("\r\n")) { // RFC2483によると改行コードはCRLF fileStr = fileStr.trim(); if (fileStr.startsWith("#")) { continue; } try { URI uri = new URI(fileStr); File dropFile = new File(uri); dropFiles.add(dropFile); break; } catch (URISyntaxException ex) { logger.log(Level.WARNING, "invalid drop file: " + fileStr, ex); } } } } // 存在しないファイルを除去する. for (Iterator ite = dropFiles.iterator(); ite.hasNext();) { File dropFile = ite.next(); if (dropFile == null || !dropFile.exists()) { ite.remove(); } } // ドロップされたファイルを通知する. onDropFiles(dropFiles); } catch (UnsupportedFlavorException ex) { logger.log(Level.WARNING, "unsipported flovaor." , ex); onException(ex); } catch (IOException ex) { logger.log(Level.WARNING, "drop target failed." , ex); onException(ex); } } protected void onException(Exception ex) { // do nothing. } } CharacterManaJ/src/charactermanaj/ui/util/ScrollPaneDragScrollSupport.java0000644000175000017500000002515212560206305027200 0ustar paulliupaulliupackage charactermanaj.ui.util; import java.awt.Cursor; import java.awt.Dimension; import java.awt.Point; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import java.awt.event.MouseListener; import java.awt.event.MouseMotionListener; import java.awt.event.MouseWheelEvent; import java.awt.event.MouseWheelListener; import javax.swing.JComponent; import javax.swing.JScrollPane; import javax.swing.JViewport; import javax.swing.SwingUtilities; import charactermanaj.model.AppConfig; /** * JScrollPaneでマウスによるドラッグスクロールをサポートするためのヘルパクラス.
* @author seraphy */ public class ScrollPaneDragScrollSupport { /** * 対象となるスクロールペイン */ private JScrollPane scrollPane; /** * ホイールによるスクロールで移動する分割単位.
* 表示されているエリアをn等分割したサイズごとにスクロールする.
*/ private int wheelDivider; /** * JScrollPaneを指定して構築する. * @param scrollPane */ public ScrollPaneDragScrollSupport(JScrollPane scrollPane) { if (scrollPane == null) { throw new IllegalArgumentException(); } this.scrollPane = scrollPane; AppConfig appConfig = AppConfig.getInstance(); wheelDivider = Math.max(2, appConfig.getWheelScrollUnit()); } /** * ドラッグ開始位置を示す.
* スクロールが調整されるたびに新しい座標にセットし直す.
* ドラッグ中であれば非nullとなる.
* ドラッグが完了した場合、もしくはドラッグが開始されていなければnullとなる.
*/ private Point dragPt; /** * ドラッグによるスクロールが可能か?
* 垂直・水平のいずれのスクロールバーがない状況ではドラッグは開始されない.
* @return ドラッグによるスクロールが可能である場合はtrue */ public boolean isDragScrollable() { JViewport vp = scrollPane.getViewport(); Dimension viewSize = vp.getViewSize(); Dimension visibleSize = vp.getExtentSize(); if (viewSize.width <= visibleSize.width && viewSize.height <= visibleSize.height) { // ビューポートにビューが全部表示されていればドラッグは開始されない. return false; } return true; } /** * 現在のドラッグ位置を取得する.
* ドラッグが開始されていなければnullとなる.
* @return ドラッグ位置 */ public Point getDragPt() { return dragPt; } /** * 現在ドラッグ中であるか? * @return ドラッグ中であればtrue */ public boolean isDragging() { return dragPt != null; } /** * カーソルを設定する. * @param cursor カーソル */ protected void setCursor(Cursor cursor) { scrollPane.setCursor(cursor); } /** * ドラッグの開始または終了を行う.
* すでに開始済みで開始要求するか、開始されておらず停止要求した場合は何もしない.
* @param start 開始する場合はtrue、終了する場合はfalse * @param mousePt 開始位置 */ public void drag(boolean start, Point mousePt) { if (start) { if (dragPt == null) { JViewport vp = scrollPane.getViewport(); Dimension viewSize = vp.getViewSize(); Dimension visibleSize = vp.getExtentSize(); if (viewSize.width <= visibleSize.width && viewSize.height <= visibleSize.height) { // ビューポートにビューが全部表示されていればドラッグは開始されない. dragPt = null; return; } // ドラッグ中であることを示す dragPt = mousePt; setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR)); } } else if (dragPt != null) { // ドラッグ中であれば解除する. // (ドラッグ解除済みであれば何もしない.) dragging(mousePt); setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR)); dragPt = null; } } /** * マウスによるドラッグによるスクロール.
* 前回位置(初回なら開始位置)との差分からスクロール量を判定する.
* @param mousePt 現在のマウス位置 */ public void dragging(Point mousePt) { if (dragPt == null || mousePt == null) { // 前回値がないか今回値がない場合は何もしない. return; } // 前回座標との差分を求める int diff_x = dragPt.x - mousePt.x; int diff_y = dragPt.y - mousePt.y; scroll(diff_x, diff_y); // 現在位置を記録 dragPt = mousePt; } /** * マウス座標単位で指定したオフセット分スクロールする. * @param diff_x 水平方向スクロール数 * @param diff_y 垂直方向スクロール数 */ public void scroll(int diff_x, int diff_y) { if (diff_x == 0 && diff_y == 0) { return; } JViewport vp = scrollPane.getViewport(); Dimension viewSize = vp.getViewSize(); Dimension visibleSize = vp.getExtentSize(); Point vpt = vp.getViewPosition(); vpt.x += diff_x; if (vpt.x < 0) { vpt.x = 0; } else if (vpt.x + visibleSize.width > viewSize.width) { // はみ出た分を引く vpt.x -= (vpt.x + visibleSize.width - viewSize.width); } vpt.y += diff_y; if (vpt.y < 0) { vpt.y = 0; } else if (vpt.y + visibleSize.height > viewSize.height) { // はみ出た分を引く vpt.y -= (vpt.y + visibleSize.height - viewSize.height); } vp.setViewPosition(vpt); } /** * ホイールによるスクロール量の分割数.
* 表示されている領域に対してn等分割したサイズを * 一回あたりのスクロール量とする.
* @return スクロール量の分割数 */ public int getWheelDivider() { return wheelDivider; } public void setWheelFactor(int wheelDivider) { this.wheelDivider = Math.max(2, wheelDivider); } /** * マウスホイールによる水平・垂直スクロールを行うためのコンビニエスとメソッド.
* シフトキーで水平、それ以外は垂直とする.
* @param e ホイールイベント */ public void scrollByWheel(final MouseWheelEvent e) { if (e == null) { return; } JViewport vp = scrollPane.getViewport(); Dimension visibleSize = vp.getExtentSize(); int rotation = e.getWheelRotation(); int diff_x = 0; int diff_y = 0; if (e.isShiftDown()) { // 水平スクロール int unit = visibleSize.width / getWheelDivider(); diff_x = rotation * unit; } else { // 垂直スクロール int unit = visibleSize.height / getWheelDivider(); diff_y = rotation * unit; } scroll(diff_x, diff_y); } /** * セットアップしたリスナを保存するもの */ private MouseListener mouseListener; /** * セットアップしたリスナを保存するもの */ private MouseMotionListener mouseMotionListener; /** * セットアップしたリスナを保存するもの */ private MouseWheelListener mouseWheelListener; /** * リスナをセットアップしたコンポーネント */ private JComponent installTarget; /** * ドラッグの開始に相応しいボタンプレスであるか判定するためのインターフェイス. * @author seraphy */ public interface DragPridicator { /** * このマウスイベントでドラッグ開始しても良いか? * @param e マウスイベント * @return ドラッグを開始しても良い場合はtrue */ boolean isDraggable(MouseEvent e); } /** * マウスによるドラッグをサポートする、一般的なマウスリスナとマウスモーションリスナをセットアップする.
* すでに登録済みであれば何もしない.
* このメソッドはマウスリスナに特別な処理が必要ない場合に定型的な処理を代行するコンビニエスメソッドです.
* @param comp マウスリスナをセットアップするターゲットのコンポーネント * @param predicator マウスによるドラッグの開始を行うか判定するためのオブジェクト、nullの場合は不問 */ public void installDraggingListener(final JComponent comp, final DragPridicator predicator) { if (comp == null) { throw new IllegalArgumentException(); } if (mouseListener == null) { mouseListener = new MouseAdapter() { @Override public void mousePressed(MouseEvent e) { if (predicator == null || predicator.isDraggable(e)) { Point pt = SwingUtilities.convertPoint( comp, e.getPoint(), scrollPane); drag(true, pt); } } @Override public void mouseReleased(MouseEvent e) { if (predicator == null || predicator.isDraggable(e)) { Point pt = SwingUtilities.convertPoint( comp, e.getPoint(), scrollPane); drag(false, pt); } } }; // リスナを登録する. comp.addMouseListener(mouseListener); } if (mouseMotionListener == null) { mouseMotionListener = new MouseMotionListener() { public void mouseMoved(MouseEvent e) { // 何もしない. } public void mouseDragged(MouseEvent e) { Point pt = SwingUtilities.convertPoint( comp, e.getPoint(), scrollPane); dragging(pt); } }; // リスナを登録する. comp.addMouseMotionListener(mouseMotionListener); } if (mouseWheelListener == null) { mouseWheelListener = new MouseWheelListener() { public void mouseWheelMoved(MouseWheelEvent e) { scrollByWheel(e); e.consume(); } }; // ホイールスクロールのデフォルト設定を解除する. scrollPane.setWheelScrollingEnabled(false); // リスナを登録する. comp.addMouseWheelListener(mouseWheelListener); } installTarget = comp; } /** * セットアップしたリスナを解除する.
* 登録されていない場合は何もしない.
*/ public void uninstallDraggingListener() { if (mouseListener != null && installTarget != null) { installTarget.removeMouseListener(mouseListener); mouseListener = null; } if (mouseMotionListener != null && installTarget != null) { installTarget.removeMouseMotionListener(mouseMotionListener); mouseMotionListener = null; } if (mouseWheelListener != null && installTarget != null) { installTarget.removeMouseWheelListener(mouseWheelListener); mouseWheelListener = null; } } } CharacterManaJ/src/charactermanaj/ui/util/SingleRootFileSystemView.java0000644000175000017500000000217412560206305026510 0ustar paulliupaulliupackage charactermanaj.ui.util; import java.io.File; import java.io.IOException; import javax.swing.filechooser.FileSystemView; /** * ファイルチューザのコンストラクタに指定することで、特定のディレクトリ以外に移動できないようにする
* ためのファイルシステムビューを構築します.
* 親ディレクトリへの移動、別のルートディレクトリの選択はできず、新規ディレクトリの作成もできません.
* * @author seraphy */ public class SingleRootFileSystemView extends FileSystemView { /** * 対象ディレクトリ */ private File dir; public SingleRootFileSystemView(File templDir) { if (templDir == null) { throw new IllegalArgumentException(); } this.dir = templDir; } @Override public File createNewFolder(File containingDir) throws IOException { return null; } @Override public File getDefaultDirectory() { return dir; } @Override public File getHomeDirectory() { return dir; } @Override public File[] getRoots() { return new File[]{dir}; } } CharacterManaJ/src/charactermanaj/ui/util/SpinnerWheelSupportListener.java0000644000175000017500000000420012560206305027261 0ustar paulliupaulliupackage charactermanaj.ui.util; import java.awt.event.MouseWheelEvent; import java.awt.event.MouseWheelListener; import javax.swing.SpinnerModel; import javax.swing.SpinnerNumberModel; /** * スピナーをホイールによって上下できるようにするためのホイールリスナ. * @author seraphy */ public class SpinnerWheelSupportListener implements MouseWheelListener { /** * 対象となるスピナーのモデル */ protected SpinnerModel model; /** * スピナーのモデルを指定して構築します. * @param model */ public SpinnerWheelSupportListener(SpinnerModel model) { if (model == null) { throw new IllegalArgumentException(); } this.model = model; } /** * ホイールによりスピナーモデルの現在値を上下させる.
* モデルが数値型である場合は範囲チェックをし、その範囲で適用します.
* 数値外であれば適用した結果エラーとなる場合は単に無視します.
* @param e マウスホイールイベント */ public void mouseWheelMoved(MouseWheelEvent e) { int rotate = e.getWheelRotation(); Object nextval = null; if (rotate < 0) { // 上スクロール(up) nextval = model.getNextValue(); } else if (rotate > 0) { // 下スクロール(down) nextval = model.getPreviousValue(); } if (nextval != null) { if (model instanceof SpinnerNumberModel) { SpinnerNumberModel nmodel = (SpinnerNumberModel) model; @SuppressWarnings("unchecked") Comparable max = nmodel.getMaximum(); @SuppressWarnings("unchecked") Comparable min = nmodel.getMinimum(); if (max.compareTo((Number) nextval) < 0) { nextval = null; } else if (min.compareTo((Number) nextval) > 0) { nextval = null; } } try { if (nextval != null) { model.setValue(nextval); } } catch (IllegalArgumentException ex) { // 範囲外になった場合はIllegalArgumentExceptionが発生するが、 // ユーザ操作によるものなので単に無視する } } } } CharacterManaJ/src/charactermanaj/ui/util/FileDropListener.java0000644000175000017500000000063712560206305024777 0ustar paulliupaulliupackage charactermanaj.ui.util; import java.io.File; import java.util.List; /** * ファイルがドロップされたことを通知されるリスナ.
* @author seraphy * */ public interface FileDropListener { /** * ファイルがドロップされたことを通知する. * @param dropFiles ドロップされたファイル */ void onDropFiles(List dropFiles); } CharacterManaJ/src/charactermanaj/ui/SamplePicturePanel.java0000644000175000017500000002007312560206305024341 0ustar paulliupaulliupackage charactermanaj.ui; import static java.lang.Math.min; import java.awt.Color; import java.awt.Component; import java.awt.Container; import java.awt.Dimension; import java.awt.Font; import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.Insets; import java.awt.Rectangle; import java.awt.RenderingHints; import java.awt.Shape; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import java.awt.font.FontRenderContext; import java.awt.image.BufferedImage; import java.util.Properties; import javax.swing.JPanel; import javax.swing.JScrollPane; import javax.swing.JViewport; import charactermanaj.model.AppConfig; import charactermanaj.ui.util.ScrollPaneDragScrollSupport; import charactermanaj.util.LocalizedResourcePropertyLoader; /** * サンプルピクチャ用パネル.
* ピクチャの自動縮小と等倍表示切り替えをサポートする.
* * @author seraphy */ public class SamplePicturePanel extends JPanel { private static final long serialVersionUID = 4026181978500938152L; protected BufferedImage samplePicture; protected boolean visiblePicture = true; protected Color sampleImageBgColor = AppConfig.getInstance().getSampleImageBgColor(); protected String alternateText = ""; protected boolean enableRealsize; /** * マウスドラッグによるスクロールのサポート */ protected ScrollPaneDragScrollSupport scrollSupport; { setMinimumSize(new Dimension(64, 64)); addMouseListener(new MouseAdapter() { @Override public void mouseClicked(MouseEvent e) { if (e.getClickCount() == 2) { // 正確に2回 onDblClick(); e.consume(); } } }); } @Override public void addNotify() { super.addNotify(); // 親がJScrollPaneである場合、 // マウスのドラッグによるスクロールをサポートするようにマウスリスナをセットアップする. Component parent = getParent(); if (parent != null && (parent instanceof JViewport)) { Component gparent = parent.getParent(); if (gparent != null && (gparent instanceof JScrollPane)) { JScrollPane scrollPane = (JScrollPane) gparent; scrollSupport = new ScrollPaneDragScrollSupport(scrollPane); scrollSupport.installDraggingListener(this, null); } } } @Override public void removeNotify() { if (scrollSupport != null) { scrollSupport.uninstallDraggingListener(); scrollSupport = null; } super.removeNotify(); } public SamplePicturePanel() { super(); } public SamplePicturePanel(BufferedImage samplePicture) { super(); this.samplePicture = samplePicture; this.alternateText = ""; this.enableRealsize = false; adjustPreferrerdSize(false); } public SamplePicturePanel(BufferedImage samplePicture, String alternateText) { super(); if (alternateText == null) { alternateText = ""; } this.samplePicture = samplePicture; this.alternateText = alternateText; this.enableRealsize = false; adjustPreferrerdSize(false); } @Override protected void paintComponent(Graphics g0) { Graphics2D g = (Graphics2D) g0; super.paintComponent(g); if (samplePicture != null && isVisiblePicture()) { Rectangle rct = getBounds(); Insets insets = getInsets(); int x = insets.left; int y = insets.top; int w = rct.width - insets.left - insets.right; int h = rct.height - insets.top - insets.bottom; int imgW = samplePicture.getWidth(); int imgH = samplePicture.getHeight(); double factor1 = (double) h / (double) imgH; // 縦を納めた場合の、縦の縮小率 double factor2 = (double) w / (double) imgW; // 横を納めた場合の、横の縮小率 double factor = min(factor1, factor2); // 縦横を納めるのに最低必要な縮小率 int scaledW = (int)(imgW * factor); int scaledH = (int)(imgH * factor); int offset_x = (w - scaledW) / 2; int offset_y = (h - scaledH) / 2; Object renderingHint; if (factor <= 1.) { // 等倍未満 renderingHint = RenderingHints.VALUE_INTERPOLATION_BILINEAR; } else if (factor <= 2.) { // 2倍まで renderingHint = RenderingHints.VALUE_INTERPOLATION_BICUBIC; } else { // それ以上 renderingHint = RenderingHints.VALUE_INTERPOLATION_NEAREST_NEIGHBOR; } g.setRenderingHint( RenderingHints.KEY_INTERPOLATION, renderingHint); g.drawImage(samplePicture, x + offset_x, y + offset_y, x + offset_x + scaledW, y + offset_y + scaledH, 0, 0, imgW, imgH, sampleImageBgColor, null); } else if (alternateText.length() > 0) { Rectangle rct = getBounds(); Insets insets = getInsets(); int x = insets.left; int y = insets.top; int w = rct.width - insets.left - insets.right; int h = rct.height - insets.top - insets.bottom; Font font = g.getFont(); FontRenderContext frc = g.getFontRenderContext(); int textHeight = (int) font.getMaxCharBounds(frc).getHeight(); Shape clipOld = g.getClip(); g.setClip(x, y, w, h); g.drawString(alternateText, x, y + textHeight); g.setClip(clipOld); } } public void adjustPreferrerdSize(boolean fullsize) { Dimension minSize = getMinimumSize(); Dimension siz = minSize; String tooltip = null; if (samplePicture != null) { int div = fullsize ? 1 : 2; Insets insets = getInsets(); siz = new Dimension(samplePicture.getWidth() / div + insets.left + insets.right, samplePicture.getHeight() / div + insets.top + insets.bottom); Container parent = getParent(); if (parent != null && parent instanceof JViewport) { Properties strings = LocalizedResourcePropertyLoader .getCachedInstance().getLocalizedProperties("languages/samplepicturepanel"); if (!fullsize) { tooltip = strings.getProperty("dblclick.fullsize"); } else { tooltip = strings.getProperty("dblclick.fit"); } } } setToolTipText(tooltip); siz.width = Math.max(minSize.width, siz.width); siz.height = Math.max(minSize.height, siz.height); Dimension ord = getPreferredSize(); if (ord == null || !ord.equals(siz)) { setPreferredSize(siz); revalidate(); } } @Override public Dimension getPreferredSize() { Container parent = getParent(); if (!enableRealsize && parent != null && parent instanceof JViewport) { JViewport viewport = (JViewport) parent; Dimension siz = viewport.getExtentSize(); Insets insets = viewport.getInsets(); Dimension preferredSize = new Dimension(siz.width - insets.left - insets.right, siz.height - insets.top - insets.bottom); return preferredSize; } return super.getPreferredSize(); } protected void onDblClick() { Container parent = getParent(); if (parent != null && parent instanceof JViewport) { enableRealsize = !enableRealsize; adjustPreferrerdSize(enableRealsize); } } public boolean isVisiblePicture() { return visiblePicture; } public void setVisiblePicture(boolean visiblePicture) { if (this.visiblePicture != visiblePicture) { this.visiblePicture = visiblePicture; repaint(); } } public void setSamplePicture(BufferedImage samplePicture) { if (this.samplePicture != samplePicture) { this.samplePicture = samplePicture; enableRealsize = false; adjustPreferrerdSize(false); repaint(); } } public BufferedImage getSamplePictrue() { return this.samplePicture; } public Color getSamplePictureBgColor() { return this.sampleImageBgColor; } public void setSamplePictureBgColor(Color color) { if (color == null) { throw new IllegalArgumentException(); } if (!sampleImageBgColor.equals(color)) { this.sampleImageBgColor = color; repaint(); } } public String getAlternateText() { return alternateText; } public void setAlternateText(String alternateText) { if (alternateText == null) { alternateText = ""; } if (!this.alternateText.equals(alternateText)) { this.alternateText = alternateText; repaint(); } } } CharacterManaJ/src/charactermanaj/ui/ManageFavoriteDialog.java0000644000175000017500000003541612560206305024623 0ustar paulliupaulliupackage charactermanaj.ui; import java.awt.BorderLayout; import java.awt.Container; import java.awt.Dimension; import java.awt.GridBagConstraints; import java.awt.GridBagLayout; import java.awt.Point; import java.awt.Toolkit; import java.awt.event.ActionEvent; import java.awt.event.KeyEvent; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import java.awt.event.WindowAdapter; import java.awt.event.WindowEvent; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Properties; import javax.swing.AbstractAction; import javax.swing.Action; import javax.swing.ActionMap; import javax.swing.BorderFactory; import javax.swing.Box; import javax.swing.InputMap; import javax.swing.JButton; import javax.swing.JComponent; import javax.swing.JDialog; import javax.swing.JFrame; import javax.swing.JMenuItem; import javax.swing.JOptionPane; import javax.swing.JPanel; import javax.swing.JPopupMenu; import javax.swing.JRootPane; import javax.swing.JScrollPane; import javax.swing.JTable; import javax.swing.KeyStroke; import javax.swing.ListSelectionModel; import javax.swing.SwingUtilities; import javax.swing.UIManager; import javax.swing.event.ListSelectionEvent; import javax.swing.event.ListSelectionListener; import javax.swing.table.AbstractTableModel; import charactermanaj.model.CharacterData; import charactermanaj.model.PartsSet; import charactermanaj.util.LocalizedResourcePropertyLoader; import charactermanaj.util.UIHelper; /** * お気に入りの編集ダイアログ * * @author seraphy */ public class ManageFavoriteDialog extends JDialog { private static final long serialVersionUID = 1L; protected static final String STRINGS_RESOURCE = "languages/managefavoritesdialog"; private CharacterData characterData; private PartsSetListTableModel partsSetListModel; private JTable partsSetList; private FavoriteManageCallback callback; private Action actSelect; private Action actDelete; private Action actRename; public static class PartsSetListTableModel extends AbstractTableModel { /** * シリアライズバージョンID */ private static final long serialVersionUID = 3012538368342673506L; /** * パーツセットのリスト */ private List partsSetList = Collections.emptyList(); private enum Columns { DISPLAY_NAME("Name") { @Override public Object getValue(PartsSet partsSet) { if (partsSet != null) { return partsSet.getLocalizedName(); } return null; } }, IS_PRESET("Type") { @Override public Object getValue(PartsSet partsSet) { if (partsSet != null) { return partsSet.isPresetParts() ? "Preset" : "Favorites"; } return null; } }; private String columnName; private Columns(String columnName) { this.columnName = columnName; } public Class getColumnClass() { return String.class; } public String getColumnName() { return columnName; } public abstract Object getValue(PartsSet partsSet); } private static Columns[] columns = Columns.values(); public int getColumnCount() { return columns.length; } public int getRowCount() { return partsSetList.size(); } public Object getValueAt(int rowIndex, int columnIndex) { PartsSet partsSet = getRow(rowIndex); return columns[columnIndex].getValue(partsSet); } @Override public Class getColumnClass(int columnIndex) { return columns[columnIndex].getColumnClass(); } @Override public String getColumnName(int column) { return columns[column].getColumnName(); } public PartsSet getRow(int rowIndex) { return partsSetList.get(rowIndex); } public void updateRow(int rowIndex, PartsSet partsSet) { partsSetList.set(rowIndex, partsSet); fireTableRowsUpdated(rowIndex, rowIndex); } public List getPartsSetList() { return new ArrayList(partsSetList); } public void setPartsSetList(List partsSetList) { if (partsSetList == null) { partsSetList = Collections.emptyList(); } this.partsSetList = new ArrayList(partsSetList); fireTableDataChanged(); } } /** * パーツセットの選択および保存を行うためのコールバック. */ public interface FavoriteManageCallback { /** * 引数で指定されたパーツセットを表示する. * * @param partsSet */ void selectFavorites(PartsSet partsSet); /** * 指定したキャラクターデータのお気に入りを保存する.
* presetを変更した場合はcharacter.xmlを更新するためにsavePreset引数をtrueとする.
* * @param characterData * お気に入りを保存するキャラクターデータ * @param savePreset * character.xmlを更新する場合(presetの更新) */ void updateFavorites(CharacterData characterData, boolean savePreset); } public ManageFavoriteDialog(JFrame parent, CharacterData characterData) { super(parent, false); if (characterData == null) { throw new IllegalArgumentException(); } setDefaultCloseOperation(JDialog.DO_NOTHING_ON_CLOSE); addWindowListener(new WindowAdapter() { @Override public void windowClosing(WindowEvent e) { onClose(); } }); Properties strings = LocalizedResourcePropertyLoader .getCachedInstance() .getLocalizedProperties(STRINGS_RESOURCE); setTitle(strings.getProperty("manageFavorites")); this.characterData = characterData; characterData.getPartsSets(); Container contentPane = getContentPane(); contentPane.setLayout(new BorderLayout()); partsSetListModel = new PartsSetListTableModel(); partsSetList = new JTable(partsSetListModel); partsSetList.setRowSelectionAllowed(true); partsSetList .setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION); partsSetList.setTableHeader(null); partsSetList.getColumnModel().getColumn(1).setMaxWidth(150); partsSetList.getSelectionModel().addListSelectionListener( new ListSelectionListener() { public void valueChanged(ListSelectionEvent e) { updateButtonUI(); } }); actSelect = new AbstractAction(strings.getProperty("select")) { private static final long serialVersionUID = 1L; public void actionPerformed(ActionEvent e) { onSelect(); } }; actDelete = new AbstractAction(strings.getProperty("remove")) { private static final long serialVersionUID = 1L; public void actionPerformed(ActionEvent e) { onDelete(); } }; actRename = new AbstractAction(strings.getProperty("rename")) { private static final long serialVersionUID = 1L; public void actionPerformed(ActionEvent e) { onRename(); } }; JPanel buttonsPanel = new JPanel(); GridBagLayout gb = new GridBagLayout(); GridBagConstraints gbc = new GridBagConstraints(); buttonsPanel.setLayout(gb); gbc.gridx = 0; gbc.gridy = 0; gbc.anchor = GridBagConstraints.EAST; gbc.fill = GridBagConstraints.BOTH; buttonsPanel.add(new JButton(actSelect), gbc); gbc.gridx = 0; gbc.gridy = 1; buttonsPanel.add(new JButton(actDelete), gbc); gbc.gridx = 0; gbc.gridy = 2; buttonsPanel.add(new JButton(actRename), gbc); gbc.gridx = 0; gbc.gridy = 3; gbc.weighty = 1.; buttonsPanel.add(Box.createGlue(), gbc); JPanel panel2 = new JPanel(); panel2.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 42)); panel2.setLayout(new BorderLayout()); Action actCancel = new AbstractAction(strings.getProperty("close")) { private static final long serialVersionUID = 1L; public void actionPerformed(ActionEvent e) { onClose(); } }; JButton btnClose = new JButton(actCancel); panel2.add(btnClose, BorderLayout.EAST); JScrollPane scr = new JScrollPane(partsSetList); scr.setBorder(BorderFactory.createEtchedBorder()); scr.setPreferredSize(new Dimension(300, 150)); contentPane.add(scr, BorderLayout.CENTER); contentPane.add(buttonsPanel, BorderLayout.EAST); contentPane.add(panel2, BorderLayout.SOUTH); Toolkit tk = Toolkit.getDefaultToolkit(); JRootPane rootPane = getRootPane(); rootPane.setDefaultButton(btnClose); InputMap im = rootPane.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW); ActionMap am = rootPane.getActionMap(); im.put(KeyStroke.getKeyStroke(KeyEvent.VK_DELETE, 0), "deleteFav"); im.put(KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0), "closeManageFavoriteDialog"); im.put(KeyStroke.getKeyStroke(KeyEvent.VK_W, tk.getMenuShortcutKeyMask()), "closeManageFavoriteDialog"); am.put("deleteFav", actDelete); am.put("closeManageFavoriteDialog", actCancel); setSize(400, 500); setLocationRelativeTo(parent); final JPopupMenu popupMenu = new JPopupMenu(); popupMenu.add(new JMenuItem(actSelect)); popupMenu.add(new JMenuItem(actRename)); popupMenu.add(new JMenuItem(actDelete)); partsSetList.addMouseListener(new MouseAdapter() { @Override public void mouseClicked(MouseEvent e) { if (e.getClickCount() == 2) { onSelect(); } } @Override public void mousePressed(MouseEvent e) { if (SwingUtilities.isRightMouseButton(e)) { // 右クリックによる選択 Point pt = e.getPoint(); int rowIndex = partsSetList.rowAtPoint(pt); if (rowIndex >= 0) { int[] selrows = partsSetList.getSelectedRows(); if (!Arrays.asList(selrows).contains(rowIndex)) { // 現在の選択行以外を右クリックした場合、その行を選択行とする. ListSelectionModel selModel = partsSetList .getSelectionModel(); selModel.setSelectionInterval(rowIndex, rowIndex); } } } evaluatePopup(e); } @Override public void mouseReleased(MouseEvent e) { evaluatePopup(e); } private void evaluatePopup(MouseEvent e) { if (e.isPopupTrigger()) { popupMenu.show(partsSetList, e.getX(), e.getY()); } } }); initListModel(); updateButtonUI(); } /** * 現在のキャラクターデータの最新の状態でお気に入り一覧を更新する. */ public void initListModel() { ArrayList partssets = new ArrayList(); for (PartsSet partsset : characterData.getPartsSets().values()) { partssets.add(partsset); } Collections.sort(partssets, PartsSet.DEFAULT_COMPARATOR); partsSetListModel.setPartsSetList(partssets); } protected void updateButtonUI() { int[] rows = partsSetList.getSelectedRows(); actSelect.setEnabled(rows.length == 1); actRename.setEnabled(rows.length == 1); actDelete.setEnabled(rows.length >= 1); } /** * 選択されている「お気に入り」のパーツセットの一覧を取得する.
* プリセットが選択されている場合、それは除外される.
* * @param beep * プリセットが選択されている場合にビープを鳴らすか? * @return お気に入りのパーツセットのリスト、選択がなければ空のリスト. */ protected List getSelectedPartsSet() { ArrayList selectedPartsSet = new ArrayList(); int[] rows = partsSetList.getSelectedRows(); for (int row : rows) { PartsSet partsSet = partsSetListModel.getRow(row); selectedPartsSet.add(partsSet); } return selectedPartsSet; } /** * お気に入りの削除 */ protected void onDelete() { List removePartsSet = getSelectedPartsSet(); if (removePartsSet.isEmpty() || callback == null) { return; } // 削除の確認ダイアログ Properties strings = LocalizedResourcePropertyLoader.getCachedInstance() .getLocalizedProperties(STRINGS_RESOURCE); String msg = strings.getProperty("favorite.remove.confirm"); JOptionPane optionPane = new JOptionPane(msg, JOptionPane.QUESTION_MESSAGE, JOptionPane.YES_NO_OPTION) { private static final long serialVersionUID = 1L; @Override public void selectInitialValue() { String noBtnCaption = UIManager .getString("OptionPane.noButtonText"); for (JButton btn : UIHelper.getInstance().getDescendantOfClass( JButton.class, this)) { if (btn.getText().equals(noBtnCaption)) { // 「いいえ」ボタンにフォーカスを設定 btn.requestFocus(); } } } }; JDialog dlg = optionPane.createDialog(ManageFavoriteDialog.this, strings.getProperty("confirm.remove")); dlg.setVisible(true); Object ret = optionPane.getValue(); if (ret == null || ((Number) ret).intValue() != JOptionPane.YES_OPTION) { return; } // お気に入りリストから削除する. boolean dirty = false; boolean deletePreset = false; Map partsSetMap = characterData.getPartsSets(); for (PartsSet partsSet : removePartsSet) { Iterator> ite = partsSetMap.entrySet().iterator(); while (ite.hasNext()) { Map.Entry entry = ite.next(); PartsSet target = entry.getValue(); if (target == partsSet) { dirty = true; if (target.isPresetParts()) { // presetを削除した場合はcharacter.xmlの更新が必要 deletePreset = true; } ite.remove(); } } } if (dirty) { callback.updateFavorites(characterData, deletePreset); initListModel(); } } /** * お気に入りのリネーム */ protected void onRename() { int row = partsSetList.getSelectedRow(); if (row < 0 || callback == null) { return; } PartsSet partsSet = partsSetListModel.getRow(row); Properties strings = LocalizedResourcePropertyLoader .getCachedInstance().getLocalizedProperties(STRINGS_RESOURCE); String localizedName = JOptionPane.showInputDialog(this, strings.getProperty("inputName"), partsSet.getLocalizedName()); if (localizedName != null) { partsSet.setLocalizedName(localizedName); callback.updateFavorites(characterData, partsSet.isPresetParts()); initListModel(); } } /** * 選択したお気に入りを表示する. */ protected void onSelect() { int row = partsSetList.getSelectedRow(); if (row < 0) { return; } PartsSet partsSet = partsSetListModel.getRow(row); if (callback != null) { callback.selectFavorites(partsSet); } } protected void onClose() { dispose(); } public void setFavoriteManageCallback(FavoriteManageCallback callback) { this.callback = callback; } public FavoriteManageCallback getFavoriteManageCallback() { return callback; } } CharacterManaJ/src/charactermanaj/ui/ImageSelectPanel.java0000644000175000017500000012411312560206305023746 0ustar paulliupaulliupackage charactermanaj.ui; import java.awt.BorderLayout; import java.awt.Color; import java.awt.Component; import java.awt.Dimension; import java.awt.Font; import java.awt.Insets; import java.awt.Rectangle; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.FocusAdapter; import java.awt.event.FocusEvent; import java.awt.event.KeyEvent; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import java.io.Serializable; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.Comparator; import java.util.EventListener; import java.util.EventObject; import java.util.HashMap; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.Properties; import javax.swing.AbstractAction; import javax.swing.Action; import javax.swing.ActionMap; import javax.swing.BorderFactory; import javax.swing.DefaultListSelectionModel; import javax.swing.InputMap; import javax.swing.JButton; import javax.swing.JLabel; import javax.swing.JPanel; import javax.swing.JPopupMenu; import javax.swing.JScrollPane; import javax.swing.JTable; import javax.swing.JToolBar; import javax.swing.KeyStroke; import javax.swing.ListSelectionModel; import javax.swing.event.ListSelectionEvent; import javax.swing.event.ListSelectionListener; import javax.swing.event.TableModelEvent; import javax.swing.event.TableModelListener; import javax.swing.table.AbstractTableModel; import javax.swing.table.DefaultTableColumnModel; import javax.swing.table.TableCellRenderer; import javax.swing.table.TableColumn; import charactermanaj.model.AppConfig; import charactermanaj.model.PartsCategory; import charactermanaj.model.PartsIdentifier; import charactermanaj.model.PartsSpec; import charactermanaj.model.PartsSpecResolver; import charactermanaj.util.LocalizedResourcePropertyLoader; import charactermanaj.util.UIHelper; /** * 各パーツの選択パネル(カテゴリ別) * @author seraphy */ public class ImageSelectPanel extends JPanel { private static final long serialVersionUID = 1L; protected static final String STRINGS_RESOURCE = "languages/imageselectpanel"; /** * 変更通知を受けるリスナ * @author seraphy */ public interface ImageSelectPanelListener extends EventListener { /** * 選択が変更された場合 * @param event */ void onSelectChange(ImageSelectPanelEvent event); /** * アイテムが選択された場合 * @param event */ void onChange(ImageSelectPanelEvent event); /** * 色変更ボタンが押された場合 * @param event */ void onChangeColor(ImageSelectPanelEvent event); /** * 設定ボタンが押された場合 * @param event */ void onPreferences(ImageSelectPanelEvent event); /** * タイトルがクリックされた場合 * @param event */ void onTitleClick(ImageSelectPanelEvent event); /** * タイトルがクリックされた場合 * @param event */ void onTitleDblClick(ImageSelectPanelEvent event); }; /** * 変更通知イベント * @author seraphy */ public static class ImageSelectPanelEvent extends EventObject { private static final long serialVersionUID = 1L; public ImageSelectPanelEvent(ImageSelectPanel src) { super(src); } public ImageSelectPanel getImageSelectPanel() { return (ImageSelectPanel) getSource(); } } /** * 表示モード * @author seraphy */ public enum DisplayMode { /** * 最小化モード */ MINIMIZED, /** * 通常モード */ NORMAL, /** * 最大サイズフリーモード */ EXPANDED } /** * パネルノ拡大・縮小時のステップサイズ */ private static final int rowStep = 2; /** * 変更通知を受けるリスナー */ private final LinkedList listeners = new LinkedList(); /** * リストの一行の高さ */ private final int rowHeight; /** * パネルの最小高さ (ボーダー上限 + ヘッダ行の高さ) */ private final int minHeight; /** * 現在の表示行数 */ private int numOfVisibleRows; /** * 最小化モードであるか? */ private DisplayMode displayMode; /** * パーツ情報ソース */ private PartsSpecResolver partsSpecResolver; /** * パーツ選択テーブル */ private final JTable partsSelectTable; /** * パーツ選択テーブルモデル */ private final PartsSelectListModel partsSelectTableModel; /** * 選択中のアイテム(複数選択の場合はフォーカスされているもの)、もしくはnull */ private PartsIdentifier selectedPartsIdentifier; /** * 選択中のアイテムのリスト(順序あり)、もしくは空 */ private List selectedPartsIdentifiers = Collections.emptyList(); /** * このパネルが対象とするカテゴリ情報 */ private final PartsCategory partsCategory; /** * イメージ選択パネルを構築する * @param partsCategory パーツカテゴリ * @param partsSpecResolver キャラクターデータ */ public ImageSelectPanel(final PartsCategory partsCategory, final PartsSpecResolver partsSpecResolver) { if (partsCategory == null || partsSpecResolver == null) { throw new IllegalArgumentException(); } this.partsCategory = partsCategory; this.partsSpecResolver = partsSpecResolver; setLayout(new BorderLayout()); setBorder(BorderFactory.createCompoundBorder( BorderFactory.createEmptyBorder(3, 3, 3, 3), BorderFactory.createCompoundBorder( BorderFactory.createEtchedBorder(), BorderFactory.createEmptyBorder(3, 3, 3, 3)) ) ); partsSelectTableModel = new PartsSelectListModel(partsCategory); final DefaultTableColumnModel columnModel = new DefaultTableColumnModel(); TableColumn checkColumn = new TableColumn(0, 32); checkColumn.setMaxWidth(42); columnModel.addColumn(checkColumn); columnModel.addColumn(new TableColumn(1, 100)); final DefaultListSelectionModel selectionModel = new DefaultListSelectionModel(); selectionModel.addListSelectionListener(new ListSelectionListener() { public void valueChanged(ListSelectionEvent e) { if (!e.getValueIsAdjusting()) { onSelectChange(new ImageSelectPanelEvent(ImageSelectPanel.this)); } } }); AppConfig appConfig = AppConfig.getInstance(); final Properties strings = LocalizedResourcePropertyLoader.getCachedInstance() .getLocalizedProperties(STRINGS_RESOURCE); final Color selectedItemColor = appConfig.getCheckedItemBgColor(); partsSelectTable = new JTable(partsSelectTableModel, columnModel, selectionModel) { private static final long serialVersionUID = 1L; @Override public Component prepareRenderer(TableCellRenderer renderer, int row, int column) { Component comp = super.prepareRenderer(renderer, row, column); if (isCellSelected(row, column) && hasFocus()) { // フォーカスのあるセル選択の背景色 comp.setBackground(getSelectionBackground()); } else { // フォーカスのないセル選択行 Boolean chk = (Boolean) getModel().getValueAt(row, 0); comp.setForeground(getForeground()); if (chk.booleanValue()) { // チェック済みの場合の背景色 comp.setBackground(selectedItemColor); } else { // 通常の背景色 comp.setBackground(getBackground()); } } return comp; } @Override public String getToolTipText(MouseEvent event) { // マウスが置かれている行のツールチップとしてパーツ名を表示する. int row = rowAtPoint(event.getPoint()); int mx = partsSelectTableModel.getRowCount(); if (row >= 0 && row < mx) { PartsSelectRow rowModel = partsSelectTableModel.getRow(row); PartsIdentifier partsIdentifier = rowModel.getPartsIdentifier(); PartsSpec partsSpec = partsSpecResolver.getPartsSpec(partsIdentifier); String suffix = ""; if (partsSpec != null) { // パーツの作者名とバージョンがあれば、それを末尾につけて表示する. String author = partsSpec.getAuthor(); double version = partsSpec.getVersion(); if (author != null) { if (version > 0) { suffix = " (" + author + " " + version + ")"; } else { suffix = " (" + author + ")"; } } } return partsIdentifier.getLocalizedPartsName() + suffix; } return null; } }; partsSelectTable.addFocusListener(new FocusAdapter() { @Override public void focusGained(FocusEvent e) { partsSelectTable.repaint(); } @Override public void focusLost(FocusEvent e) { partsSelectTable.repaint(); } }); final JPopupMenu partsSelectTablePopupMenu = new JPopupMenu(); Action actDeselectAll = new AbstractAction( strings.getProperty("popupmenu.deselectall")) { private static final long serialVersionUID = 9132032971228670868L; public void actionPerformed(ActionEvent e) { deselectAll(); } }; partsSelectTablePopupMenu.add(actDeselectAll); partsSelectTable.addMouseListener(new MouseAdapter() { @Override public void mousePressed(MouseEvent e) { evaluatePopup(e); } @Override public void mouseReleased(MouseEvent e) { evaluatePopup(e); } private void evaluatePopup(MouseEvent e) { if ((partsCategory.isMultipleSelectable() || isDeselectableSingleCategory()) && e.isPopupTrigger()) { partsSelectTablePopupMenu.show(partsSelectTable, e.getX(), e.getY()); } } }); partsSelectTableModel.addTableModelListener(new TableModelListener() { public void tableChanged(TableModelEvent e) { if (e.getType() == TableModelEvent.UPDATE) { onChange(new ImageSelectPanelEvent(ImageSelectPanel.this)); } } }); partsSelectTable.setSelectionBackground(appConfig.getSelectedItemBgColor()); if (partsCategory.isMultipleSelectable()) { partsSelectTable.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION); } else { partsSelectTable.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); } partsSelectTable.setRowSelectionAllowed(true); partsSelectTable.setTableHeader(null); partsSelectTable.setAutoResizeMode(JTable.AUTO_RESIZE_LAST_COLUMN); partsSelectTable.setShowVerticalLines(false); partsSelectTable.setShowHorizontalLines(false); InputMap im = partsSelectTable.getInputMap(); im.put(KeyStroke.getKeyStroke(KeyEvent.VK_SPACE, 0), "toggleCheck"); im.put(KeyStroke.getKeyStroke(KeyEvent.VK_DELETE, 0), "resetCheck"); ActionMap am = partsSelectTable.getActionMap(); am.put("toggleCheck", new AbstractAction() { private static final long serialVersionUID = 1L; public void actionPerformed(ActionEvent e) { int[] selectedRows = partsSelectTable.getSelectedRows(); boolean[] checks = partsSelectTableModel.getChecks(selectedRows); int checkedCount = 0; for (boolean checked : checks) { if (checked) { checkedCount++; } } if (checks.length == checkedCount) { // 選択しているアイテムのすべてがチェック済みである partsSelectTableModel.setChecks(false, selectedRows); } else { // 選択しているアイテムの一部もしくは全部がチェックされていない partsSelectTableModel.setChecks(true, selectedRows); } } }); am.put("resetCheck", new AbstractAction() { private static final long serialVersionUID = 1L; public void actionPerformed(ActionEvent e) { partsSelectTableModel.setChecks(false, partsSelectTable.getSelectedRows()); } }); JScrollPane scrollPane = new JScrollPane(partsSelectTable); scrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS); UIHelper uiUtl = UIHelper.getInstance(); JButton leftBtn = uiUtl.createTransparentButton("icons/left.png", "icons/left2.png"); JButton rightBtn = uiUtl.createTransparentButton("icons/right.png", "icons/right2.png"); JButton colorBtn = uiUtl.createTransparentButton("icons/color.png", "icons/color2.png"); JButton configBtn = uiUtl.createTransparentButton("icons/config.png", "icons/config2.png"); leftBtn.setToolTipText(strings.getProperty("tooltip.shrink")); rightBtn.setToolTipText(strings.getProperty("tooltip.expand")); colorBtn.setToolTipText(strings.getProperty("tooltip.color")); configBtn.setToolTipText(strings.getProperty("tooltip.config")); leftBtn.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { if (isMinimizeMode()) { setMinimizeMode(false); } else { shrink(); } } }); rightBtn.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { if (isMinimizeMode()) { setMinimizeMode(false); } else { expand(); } } }); colorBtn.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { onChangeColor(); } }); configBtn.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { onPreferences(); } }); JPanel btnPanelGrp = new JPanel(new BorderLayout()); JToolBar toolBar = new JToolBar(); toolBar.setFloatable(false); toolBar.add(leftBtn); toolBar.add(rightBtn); toolBar.add(colorBtn); //toolBar.add(configBtn); // 設定ボタン (現在は非表示) btnPanelGrp.add(toolBar, BorderLayout.NORTH); if (partsCategory.isMultipleSelectable()) { UIHelper uiUty = UIHelper.getInstance(); JButton upBtn = uiUty.createTransparentButton("icons/arrow_up.png", "icons/arrow_up2.png"); JButton downBtn = uiUty.createTransparentButton("icons/arrow_down.png", "icons/arrow_down2.png"); JButton sortBtn = uiUty.createTransparentButton("icons/sort.png", "icons/sort2.png"); upBtn.setToolTipText(strings.getProperty("tooltip.up")); downBtn.setToolTipText(strings.getProperty("tooltip.down")); sortBtn.setToolTipText(strings.getProperty("tooltip.sort")); upBtn.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { onUp(); } }); downBtn.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { onDown(); } }); sortBtn.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { onSort(); } }); JToolBar toolBar2 = new JToolBar(); toolBar2.setFloatable(false); toolBar2.add(upBtn); toolBar2.add(downBtn); toolBar2.add(sortBtn); btnPanelGrp.add(toolBar2, BorderLayout.SOUTH); } JPanel header = new JPanel(new BorderLayout()); header.add(btnPanelGrp, BorderLayout.EAST); final JLabel title = new JLabel(" " + partsCategory.getLocalizedCategoryName() + " "); Font font = title.getFont(); title.setFont(font.deriveFont(Font.BOLD)); final Color defaultTitleColor = title.getForeground(); final Color hilightColor = appConfig.getSelectPanelTitleColor(); title.addMouseListener(new MouseAdapter() { @Override public void mousePressed(MouseEvent e) { if (e.getClickCount() == 2) { // 正確に2回 onTitleDblClick(); } else if (e.getClickCount() == 1) { // 正確に1回 onTitleClick(); } } @Override public void mouseEntered(MouseEvent e) { title.setForeground(hilightColor); } @Override public void mouseExited(MouseEvent e) { title.setForeground(defaultTitleColor); } }); header.add(title, BorderLayout.CENTER); add(header, BorderLayout.NORTH); add(scrollPane, BorderLayout.CENTER); rowHeight = partsSelectTable.getRowHeight(); // パネルの最小高さ (ボーダー上下 + ヘッダ行高さ) Insets insets = getInsets(); minHeight = header.getPreferredSize().height + insets.top + insets.bottom; // デフォルトのパネル幅を設定する. Dimension dim = new Dimension(200, 200); setPreferredSize(dim); // パネルの初期サイズ numOfVisibleRows = partsCategory.getVisibleRows(); setDisplayMode(DisplayMode.NORMAL); } /** * 表示行数から推奨のパネル高さを求める.
* パネル高さは1行の高さ x 表示行数 + ヘッダ + ボーダー上下である.
* @param numOfVisibleRows 表示行数 * @return 推奨のパネル高さ */ protected int calcPreferredHeight(int numOfVisibleRows) { return minHeight + Math.max(0, rowHeight * numOfVisibleRows); } /** * パーツをパネルにロードします.
* 既存の内容はリセットされたのち、現在の選択パーツ位置にスクロールします.
*/ public void loadParts() { partsSelectTableModel.load(partsSpecResolver.getPartsSpecMap(partsCategory).keySet()); scrollToSelectedRow(); } /** * このイメージ選択パネルの該当カテゴリを返します.
* @return カテゴリ */ public PartsCategory getPartsCategory() { return partsCategory; } /** * 現在選択している、すべてのパーツの選択を解除します.
* 単一選択カテゴリであるかどうかを問わず、常にすべて解除されます.
* 変更イベントが発生します.
*/ public void deselectAll() { PartsSelectListModel rowModelList = (PartsSelectListModel) partsSelectTable.getModel(); ArrayList rowModels = rowModelList.getRowModelList(); // すべての選択を解除する. for (PartsSelectRow rowModel : rowModels) { rowModel.setChecked(false); } // コンポーネントではなく、モデルに対する直接変更であるため、イベントは発生しません. // そのため再描画させる必要があります. partsSelectTable.repaint(); // アイテムの選択が変更されたことを通知する. onChange(new ImageSelectPanelEvent(ImageSelectPanel.this)); } /** * カテゴリのリストでパーツを選択しなおします.
* 変更イベントは発生しません.
* @param partsIdentifiers */ public void selectParts(Collection partsIdentifiers) { if (partsIdentifiers == null) { partsIdentifiers = Collections.emptyList(); } PartsSelectListModel rowModelList = (PartsSelectListModel) partsSelectTable.getModel(); ArrayList rowModels = rowModelList.getRowModelList(); for (PartsSelectRow rowModel : rowModels) { rowModel.setChecked(false); } ArrayList partsIdentifiersBuf = new ArrayList(partsIdentifiers); Collections.reverse(partsIdentifiersBuf); for (PartsIdentifier partsIdentifier : partsIdentifiersBuf) { Iterator ite = rowModels.iterator(); while (ite.hasNext()) { PartsSelectRow rowModel = ite.next(); if (rowModel.getPartsIdentifier().equals(partsIdentifier)) { rowModel.setChecked(true); if (partsIdentifiersBuf.size() >= 2 && partsCategory.isMultipleSelectable()) { ite.remove(); rowModels.add(0, rowModel); } break; } } } // 選択を保存する selectedPartsIdentifier = getSelectedPartsIdentifier(); selectedPartsIdentifiers = getSelectedPartsIdentifiers(); // コンポーネントではなく、モデルに対する直接変更であるため、イベントは発生しません. // そのため再描画させる必要があります. partsSelectTable.repaint(); // あたらしく選択されたアイテムが表示されるようにスクロールします. scrollToSelectedRow(); } /** * カテゴリのリストで選択中のアイテムが見えるようにスクロールする. */ public void scrollToSelectedRow() { PartsSelectListModel rowModelList = (PartsSelectListModel) partsSelectTable.getModel(); ArrayList rowModels = rowModelList.getRowModelList(); int mx = rowModels.size(); for (int row = 0; row < mx; row++) { if (rowModels.get(row).isChecked()) { Rectangle rct = partsSelectTable.getCellRect(row, 0, true); partsSelectTable.scrollRectToVisible(rct); break; } } } /** * カテゴリのパネルを最小表示.
* 最小化の場合は、高さは表示行数ゼロとなりタイトルとボーダーだけとなる.
* 最小化解除した場合は、標準高さは既定、最大サイズはフリーとなる.
* @param shrinkMode 最小化モードならばtrue、フリーモードならばfalse */ public void setMinimizeMode(boolean minimizeMode) { setDisplayMode(minimizeMode ? DisplayMode.MINIMIZED : DisplayMode.EXPANDED); } /** * 表示モードを切り替えパネルサイズを調整する.
* @param displayMode 表示モード */ public void setDisplayMode(DisplayMode displayMode) { if (displayMode == null) { displayMode = DisplayMode.NORMAL; } Dimension siz = getPreferredSize(); Dimension sizMax = getMaximumSize(); if (displayMode == DisplayMode.MINIMIZED) { int preferredHeight = calcPreferredHeight(0); siz.height = preferredHeight; sizMax.height = preferredHeight; } else if (displayMode == DisplayMode.EXPANDED) { int preferredHeight = calcPreferredHeight(numOfVisibleRows); siz.height = preferredHeight; sizMax.height = Integer.MAX_VALUE; } else { // DisplayMode.NORMALの場合 int preferredHeight = calcPreferredHeight(numOfVisibleRows); siz.height = preferredHeight; sizMax.height = preferredHeight; } setPreferredSize(siz); setMinimumSize(siz); setMaximumSize(sizMax); this.displayMode = displayMode; revalidate(); } public DisplayMode getDisplayMode() { return displayMode; } public boolean isMinimizeMode() { return displayMode == DisplayMode.MINIMIZED; } /** * カテゴリのパネルを縮小する.
* ただし、ヘッダ部よりは小さくならない.
* 現在の表示モードが標準でなければ縮小せず標準に戻す.
*/ public void shrink() { if (displayMode == DisplayMode.NORMAL) { // 表示行数を減ずる numOfVisibleRows = Math.max(0, numOfVisibleRows - rowStep); } // 通常モードの適用 setDisplayMode(DisplayMode.NORMAL); } /** * カテゴリのパネルを拡大する.
* 現在の表示モードが標準でなければ拡大前せず標準に戻す.
*/ public void expand() { if (displayMode == DisplayMode.NORMAL) { // 表示行数を加算する numOfVisibleRows += Math.max(0, rowStep); } // 通常モードの適用 setDisplayMode(DisplayMode.NORMAL); } public void addImageSelectListener(ImageSelectPanelListener listener) { if (listener == null) { throw new IllegalArgumentException(); } listeners.add(listener); } public void removeImageSelectListener(ImageSelectPanelListener listener) { listeners.remove(listener); } public void requestListFocus() { partsSelectTable.requestFocus(); } /** * 指定したパーツ識別子にフォーカスを当てます.
* 必要に応じてスクロールされます.
* 該当するパーツ識別子がなければ何もしません.
* @param partsIdentifier パーツ識別子 */ public void setSelection(PartsIdentifier partsIdentifier) { if (partsIdentifier == null) { return; } PartsCategory partsCategory = partsIdentifier.getPartsCategory(); if (!this.partsCategory.equals(partsCategory)) { return; } ArrayList rowModelList = ((PartsSelectListModel) partsSelectTable.getModel()).getRowModelList(); int mx = rowModelList.size(); for (int idx = 0; idx < mx; idx++) { PartsSelectRow partsSelectRow = rowModelList.get(idx); if (partsSelectRow.getPartsIdentifier().equals(partsIdentifier)) { partsSelectTable.getSelectionModel().setSelectionInterval(idx, idx); Rectangle rct = partsSelectTable.getCellRect(idx, 0, true); partsSelectTable.scrollRectToVisible(rct); partsSelectTable.requestFocus(); return; } } } /** * フォーカスのあるアイテムを1つ上に移動します. */ protected void onUp() { int selRow = partsSelectTable.getSelectedRow(); if (selRow < 0) { return; } if (selRow > 0) { ArrayList rowModelList = ((PartsSelectListModel) partsSelectTable.getModel()).getRowModelList(); PartsSelectRow rowModel = rowModelList.get(selRow); rowModelList.remove(selRow); rowModelList.add(selRow - 1, rowModel); partsSelectTable.setRowSelectionInterval(selRow - 1, selRow - 1); Rectangle rct = partsSelectTable.getCellRect(selRow - 1, 0, true); partsSelectTable.scrollRectToVisible(rct); onChange(new ImageSelectPanelEvent(this)); } partsSelectTable.repaint(); partsSelectTable.requestFocus(); } /** * フォーカスのあるアイテムを1つ下に移動します. */ protected void onDown() { int selRow = partsSelectTable.getSelectedRow(); if (selRow < 0) { return; } int mx = partsSelectTable.getRowCount(); if (selRow < mx - 1) { ArrayList rowModelList = ((PartsSelectListModel) partsSelectTable.getModel()).getRowModelList(); PartsSelectRow rowModel = rowModelList.get(selRow); rowModelList.remove(selRow); rowModelList.add(selRow + 1, rowModel); partsSelectTable.setRowSelectionInterval(selRow + 1, selRow + 1); Rectangle rct = partsSelectTable.getCellRect(selRow + 1, 0, true); partsSelectTable.scrollRectToVisible(rct); onChange(new ImageSelectPanelEvent(this)); } partsSelectTable.repaint(); partsSelectTable.requestFocus(); } /** * 選択中のアイテムを選択順序を維持したまま上側に、それ以外は名前順で下側に集めるようにソートします.
*/ protected void onSort() { if (partsSelectTable.getRowCount() > 0) { partsSelectTableModel.sort(); partsSelectTable.setRowSelectionInterval(0, 0); Rectangle rct = partsSelectTable.getCellRect(0, 0, true); partsSelectTable.scrollRectToVisible(rct); partsSelectTable.repaint(); } partsSelectTable.requestFocus(); } /** * タイトルがクリックされた場合 */ protected void onTitleClick() { ImageSelectPanelEvent event = new ImageSelectPanelEvent(this); for (ImageSelectPanelListener listener : listeners) { listener.onTitleClick(event); } } /** * タイトルがダブルクリックされた場合 */ protected void onTitleDblClick() { ImageSelectPanelEvent event = new ImageSelectPanelEvent(this); for (ImageSelectPanelListener listener : listeners) { listener.onTitleDblClick(event); } } /** * カラー変更ボタンが押下された場合 * @param event */ protected void onChangeColor() { ImageSelectPanelEvent event = new ImageSelectPanelEvent(this); for (ImageSelectPanelListener listener : listeners) { listener.onChangeColor(event); } } /** * 設定ボタンが押下された場合 * @param event */ protected void onPreferences() { ImageSelectPanelEvent event = new ImageSelectPanelEvent(this); for (ImageSelectPanelListener listener : listeners) { listener.onPreferences(event); } } /** * アイテムのチェック状態が変更された場合. * @param event */ protected void onChange(ImageSelectPanelEvent event) { List selectedNews = getSelectedPartsIdentifiers(); if (!selectedNews.equals(selectedPartsIdentifiers)) { selectedPartsIdentifiers = selectedNews; for (ImageSelectPanelListener listener : listeners) { listener.onChange(event); } onSelectChange(event); } } /** * アイテムの選択(フォーカス)が変更された場合. * @param event */ protected void onSelectChange(ImageSelectPanelEvent event) { PartsIdentifier selectedNew = getSelectedPartsIdentifier(); if (!PartsIdentifier.equals(selectedNew, selectedPartsIdentifier)) { selectedPartsIdentifier = selectedNew; for (ImageSelectPanelListener listener : listeners) { listener.onSelectChange(event); } } } /** * 使用中のアイテムの一覧を返す.(選択順)
* @return 使用中のアイテムの一覧.(選択順)、ひとつもなければ空 */ public List getSelectedPartsIdentifiers() { return partsSelectTableModel.getSelectedPartsIdentifiers(); } /** * 使用中のアイテムを返す.
* 複数選択可能である場合は、使用中のアイテムでフォーカスがある最初のアイテムを返す.
* 単一選択の場合は、最初の使用中アイテムを返す.
* 複数選択可能で、使用中のアイテムにひとつもフォーカスがあたってない場合は、 * 最初の使用中アイテムを返す.
* 使用中アイテムがなければnullを返す. * @return 使用中アイテム、もしくはnull */ public PartsIdentifier getSelectedPartsIdentifier() { // フォーカスがあたっていて、且つ、チェック状態のアイテムを上から順に走査し、 // 該当があれば、最初のものを返す. int[] selRows = partsSelectTable.getSelectedRows(); Arrays.sort(selRows); for (int selRow : selRows) { PartsSelectRow row = partsSelectTableModel.getRow(selRow); if (row.isChecked()) { return row.getPartsIdentifier(); } } // チェック状態のアイテムの最初のものを返す. List checkedRows = getSelectedPartsIdentifiers(); if (checkedRows.size() > 0) { return checkedRows.get(0); } // 該当なし return null; } /** * 選択選択パーツカテゴリの選択解除を許可するか?
* @return 許可する場合はtrue */ public boolean isDeselectableSingleCategory() { return partsSelectTableModel.isDeselectableSingleCategory(); } /** * 選択選択パーツカテゴリの選択解除を許可するか設定する.
* @param deselectable 許可する場合はtrue */ public void setDeselectableSingleCategory(boolean deselectable) { partsSelectTableModel.setDeselectableSingleCategory(deselectable); } } /** * リストの行モデル.
* パーツデータ、表示名と使用中フラグを管理する. * @author seraphy */ final class PartsSelectRow implements Serializable, Comparable { private static final long serialVersionUID = 5732273802364827L; private PartsIdentifier partsIdentifier; private boolean checked; private int displayOrder; public PartsSelectRow(final PartsIdentifier partsIdentifier, final boolean checked) { this.partsIdentifier = partsIdentifier; this.checked = checked; } /** * 選択されているものを上、そうでないものを下に振り分ける。 * 選択されているもの同士、選択されていないもの同士は、互いのディスプレイ順でソートされる.
* 選択されているもの同士、選択されていないもの同士で、且つ、同一のディスプレイ順序であればパーツの表示名順でソートされる.
* @param o 対象 * @return 比較結果 */ public int compareTo(PartsSelectRow o) { int ret = (checked == o.checked) ? 0 : (checked ? -1 : 1); if (ret == 0 && checked) { ret = displayOrder - o.displayOrder; } if (ret == 0) { ret = partsIdentifier.compareTo(o.partsIdentifier); } return ret; } public void setDisplayOrder(int displayOrder) { this.displayOrder = displayOrder; } public int getDisplayOrder() { return this.displayOrder; } @Override public boolean equals(Object obj) { if (obj == this) { return true; } if (obj != null && obj instanceof PartsSelectRow) { return this.compareTo((PartsSelectRow) obj) == 0; } return false; } public int hashCode() { return partsIdentifier.hashCode(); } public PartsIdentifier getPartsIdentifier() { return partsIdentifier; } /** * {@link PartsIdentifier#getLocalizedPartsName()}に委譲します. * @return パーツ名 */ public String getPartsName() { return partsIdentifier.getLocalizedPartsName(); } public boolean isChecked() { return checked; } public void setChecked(boolean checked) { this.checked = checked; } } /** * リストのモデル * @author seraphy */ class PartsSelectListModel extends AbstractTableModel { private static final long serialVersionUID = 7604828023134579608L; private PartsCategory partsCategory; private ArrayList partsSelectRowList; /** * カテゴリが複数パーツでない場合でも選択解除を許可するフラグ. */ private boolean deselectableSingleCategory; public PartsSelectListModel(PartsCategory partsCategory) { if (partsCategory == null) { throw new IllegalArgumentException(); } this.partsSelectRowList = new ArrayList(); this.partsCategory = partsCategory; } public void load(Collection partsIdentifiers) { if (partsIdentifiers == null) { throw new IllegalArgumentException(); } // 現在選択されているパーツを保存する HashMap selectedPartsIdentifiers = new HashMap(); for (PartsIdentifier partsIdentifier : getSelectedPartsIdentifiers()) { selectedPartsIdentifiers.put(partsIdentifier, selectedPartsIdentifiers.size()); } // パーツイメージマップからパーツ名を列挙する. ArrayList partsSelectList = new ArrayList(); for (PartsIdentifier partsIdentifier : partsIdentifiers) { Integer selIndex = selectedPartsIdentifiers.get(partsIdentifier); PartsSelectRow rowModel = new PartsSelectRow(partsIdentifier, selIndex != null); // 選択されているものは、選択されているものの順序を維持する.それ以外は名前順でソートされる. int order = (selIndex != null) ? selIndex.intValue() : 0; rowModel.setDisplayOrder(order); partsSelectList.add(rowModel); } if (partsCategory.isMultipleSelectable()) { // パーツを選択有無(順序維持)・名前順に並び替える. Collections.sort(partsSelectList); } else { // 単一選択モード時はパーツ識別子でソートする. Collections.sort(partsSelectList, new Comparator() { public int compare(PartsSelectRow o1, PartsSelectRow o2) { return o1.getPartsIdentifier().compareTo(o2.getPartsIdentifier()); } }); } this.partsSelectRowList = partsSelectList; fireTableDataChanged(); } /** * 選択選択パーツカテゴリの選択解除を許可するか?
* @return 許可する場合はtrue */ public boolean isDeselectableSingleCategory() { return deselectableSingleCategory; } /** * 選択選択パーツカテゴリの選択解除を許可するか設定する.
* @param deselectable 許可する場合はtrue */ public void setDeselectableSingleCategory(boolean deselectable) { this.deselectableSingleCategory = deselectable; } public PartsSelectRow getRow(int rowIndex) { return partsSelectRowList.get(rowIndex); } public ArrayList getRowModelList() { return this.partsSelectRowList; } public int getColumnCount() { // ヘッダは非表示のためヘッダ名は取得する必要なし. // col 0: 選択ボックス // col 1: パーツ表示名 return 2; } public int getRowCount() { return partsSelectRowList.size(); } public Object getValueAt(int rowIndex, int columnIndex) { PartsSelectRow rowModel = partsSelectRowList.get(rowIndex); switch (columnIndex) { case 0: return Boolean.valueOf(rowModel.isChecked()); case 1: return rowModel.getPartsName(); default: } return ""; } @Override public Class getColumnClass(int columnIndex) { switch (columnIndex) { case 0: return Boolean.class; case 1: return String.class; default: } return String.class; } @Override public boolean isCellEditable(int rowIndex, int columnIndex) { if (columnIndex == 0) { return true; } return false; } @Override public void setValueAt(Object aValue, int rowIndex, int columnIndex) { if (columnIndex != 0) { return; } PartsSelectRow rowModel = partsSelectRowList.get(rowIndex); boolean checked = ((Boolean) aValue).booleanValue(); if (!checked && rowModel.isChecked() && !deselectableSingleCategory && !partsCategory.isMultipleSelectable()) { // 複数選択が可能でない場合、現在選択中のチェックは一つしかないはずのため、これを外すことはしない。 // ただし単一選択パーツカテゴリでの選択解除が許可されている場合を除く。 return; } rowModel.setChecked(checked); // カテゴリが複数パーツ選択を許可しておらず、且つ、チェックをつけた場合、 // すでにチェックされている他の パーツ行のチェックを外す必要がある。 boolean unchecked = false; if (checked && !partsCategory.isMultipleSelectable()) { int mx = partsSelectRowList.size(); for (int idx = 0; idx < mx; idx++) { if (idx != rowIndex) { PartsSelectRow otherRow = partsSelectRowList.get(idx); if (otherRow.isChecked()) { otherRow.setChecked(false); unchecked = true; } } } } if (!unchecked) { // 指定されたセルの変更のみなので単一変更を通知する. fireTableCellUpdated(rowIndex, columnIndex); } else { // 他のセルも変更されたので一括変更を通知する. fireTableDataChanged(); } } /** * 選択されているパーツを上に、それ以外を下に振り分ける.
* それぞれはパーツの表示名順でソートされる.
*/ public void sort() { int mx = partsSelectRowList.size(); for (int idx = 0; idx < mx; idx++) { partsSelectRowList.get(idx).setDisplayOrder(idx); } Collections.sort(partsSelectRowList); fireTableDataChanged(); } /** * チェックされているパーツのパーツ識別子のリストを返す.
* リストの順序はパーツの表示されている順序と等しい.
* 選択がなければ空のリストが返される. * @return チェックされているパーツのパーツ識別子のリスト、もしくは空 */ public List getSelectedPartsIdentifiers() { ArrayList selectedRows = new ArrayList(); for (PartsSelectRow rowModel : partsSelectRowList) { if (rowModel.isChecked()) { selectedRows.add(rowModel.getPartsIdentifier()); } } return selectedRows; } /** * 指定したインデックスのパーツのチェック状態を返す. * @param rowIndexes 調べるインデックスの配列 * @return 引数に対応したインデックスのチェック状態、nullまたは空の場合は空を返す */ public boolean[] getChecks(int[] rowIndexes) { if (rowIndexes == null) { rowIndexes = new int[0]; } int mx = rowIndexes.length; boolean[] results = new boolean[mx]; for (int idx = 0; idx < mx; idx++) { int rowIndex = rowIndexes[idx]; PartsSelectRow row = partsSelectRowList.get(rowIndex); results[idx] = row.isChecked(); } return results; } /** * 指定したインデックスのチェック状態を設定する. * @param checked チェックする場合はtrue、チェックを解除する場合はfalse * @param selectedRows インデックスの配列、nullまたは空の場合は何もしない. */ public void setChecks(boolean checked, int[] selectedRows) { if (selectedRows == null || selectedRows.length == 0) { return; } ArrayList affectRows = new ArrayList(); if (!checked) { // 選択解除 if (!partsCategory.isMultipleSelectable()) { // 複数選択可能でない場合、選択はひとつしかないはずなので // クリアする必要はない。 return; } // 選択を解除する. for (int selRow : selectedRows) { PartsSelectRow row = partsSelectRowList.get(selRow); if (row.isChecked()) { row.setChecked(false); affectRows.add(selRow); } } } else { // 選択 if (partsCategory.isMultipleSelectable()) { // 複数選択可能であれば単純に選択を有効にする for (int selRow : selectedRows) { PartsSelectRow row = partsSelectRowList.get(selRow); if (!row.isChecked()) { row.setChecked(true); affectRows.add(selRow); } } } else { // 複数選択可能でない場合は最初のアイテムのみをチェックをつけ、 // それ以外のチェックを外す. int selRow = selectedRows[0]; PartsSelectRow row = partsSelectRowList.get(selRow); if (!row.isChecked()) { row.setChecked(true); affectRows.add(selRow); int mx = partsSelectRowList.size(); for (int idx = 0; idx < mx; idx++) { PartsSelectRow otherRow = partsSelectRowList.get(idx); if (idx != selRow) { if (otherRow.isChecked()) { otherRow.setChecked(false); affectRows.add(idx); } } } } } } if (affectRows.isEmpty()) { // なにも変わりないのでイベントも発生しない. return; } // 変更された最初の行から最後の行までの範囲で変更を通知する. // (変更されていない中間も含まれる) int minIdx = 0; int maxIdx = 0; for (int idx : affectRows) { minIdx = Math.min(minIdx, idx); maxIdx = Math.max(maxIdx, idx); } fireTableRowsUpdated(minIdx, maxIdx); } } CharacterManaJ/src/charactermanaj/ui/ImageSelectPanelList.java0000644000175000017500000000265312560206305024606 0ustar paulliupaulliupackage charactermanaj.ui; import java.util.AbstractList; import java.util.ArrayList; import java.util.HashMap; import charactermanaj.model.PartsCategory; public class ImageSelectPanelList extends AbstractList { protected ArrayList imageSelectPanels = new ArrayList(); protected HashMap imageSelectPanelMap = new HashMap(); public ImageSelectPanelList() { } @Override public boolean add(ImageSelectPanel o) { if (o == null) { throw new IllegalArgumentException(); } PartsCategory partsCategory = o.getPartsCategory(); if (imageSelectPanelMap.containsKey(partsCategory)) { throw new IllegalArgumentException("duplicate category: " + partsCategory); } imageSelectPanelMap.put(partsCategory, o); return imageSelectPanels.add(o); } @Override public ImageSelectPanel get(int index) { return imageSelectPanels.get(index); } @Override public int size() { return imageSelectPanels.size(); } public ImageSelectPanel findByPartsCategory(PartsCategory partsCategory) { if (partsCategory == null) { throw new IllegalArgumentException(); } ImageSelectPanel panel = imageSelectPanelMap.get(partsCategory); if (panel == null) { throw new IllegalArgumentException("not registered: " + partsCategory); } return panel; } } CharacterManaJ/src/charactermanaj/ui/ProfileSelectorDialog.java0000644000175000017500000012737212560206305025037 0ustar paulliupaulliupackage charactermanaj.ui; import java.awt.BorderLayout; import java.awt.Color; import java.awt.Component; import java.awt.Container; import java.awt.Cursor; import java.awt.Dimension; import java.awt.Font; import java.awt.GridBagConstraints; import java.awt.GridBagLayout; import java.awt.GridLayout; import java.awt.Insets; import java.awt.Toolkit; import java.awt.dnd.DropTarget; import java.awt.event.ActionEvent; import java.awt.event.KeyEvent; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import java.awt.event.WindowAdapter; import java.awt.event.WindowEvent; import java.awt.image.BufferedImage; import java.io.File; import java.io.IOException; import java.io.PrintWriter; import java.io.StringWriter; import java.net.URI; import java.text.MessageFormat; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.List; import java.util.Map; import java.util.Properties; import java.util.logging.Level; import java.util.logging.Logger; import javax.swing.AbstractAction; import javax.swing.Action; import javax.swing.ActionMap; import javax.swing.BorderFactory; import javax.swing.Box; import javax.swing.BoxLayout; import javax.swing.ButtonGroup; import javax.swing.InputMap; import javax.swing.JButton; import javax.swing.JCheckBox; import javax.swing.JComboBox; import javax.swing.JComponent; import javax.swing.JDialog; import javax.swing.JFileChooser; import javax.swing.JFrame; import javax.swing.JLabel; import javax.swing.JList; import javax.swing.JMenuItem; import javax.swing.JOptionPane; import javax.swing.JPanel; import javax.swing.JPopupMenu; import javax.swing.JRadioButton; import javax.swing.JRootPane; import javax.swing.JScrollPane; import javax.swing.JSeparator; import javax.swing.JSplitPane; import javax.swing.JTable; import javax.swing.JTextArea; import javax.swing.KeyStroke; import javax.swing.ListCellRenderer; import javax.swing.ListSelectionModel; import javax.swing.SwingUtilities; import javax.swing.UIManager; import javax.swing.event.ListSelectionEvent; import javax.swing.event.ListSelectionListener; import javax.swing.table.AbstractTableModel; import javax.swing.table.TableCellRenderer; import javax.swing.table.TableColumnModel; import charactermanaj.Main; import charactermanaj.clipboardSupport.ClipboardUtil; import charactermanaj.graphics.io.FileImageResource; import charactermanaj.graphics.io.ImageCachedLoader; import charactermanaj.graphics.io.LoadedImage; import charactermanaj.model.AppConfig; import charactermanaj.model.CharacterData; import charactermanaj.model.CharacterDataChangeObserver; import charactermanaj.model.io.CharacterDataDefaultProvider; import charactermanaj.model.io.CharacterDataPersistent; import charactermanaj.model.io.PartsImageDirectoryWatchAgent; import charactermanaj.model.io.PartsImageDirectoryWatchAgentFactory; import charactermanaj.ui.util.FileDropTarget; import charactermanaj.ui.util.SingleRootFileSystemView; import charactermanaj.util.DesktopUtilities; import charactermanaj.util.ErrorMessageHelper; import charactermanaj.util.LocalizedResourcePropertyLoader; import charactermanaj.util.UIHelper; /** * プロファイルを選択するためのダイアログ、およびデフォルトプロファイルを開く * * @author seraphy */ public class ProfileSelectorDialog extends JDialog { private static final long serialVersionUID = -6883202891172975022L; private static final Logger logger = Logger.getLogger(ProfileSelectorDialog.class.getName()); protected static final String STRINGS_RESOURCE = "languages/profileselectordialog"; /** * サンプルイメージをロードするためのローダー */ private ImageCachedLoader imageLoader = new ImageCachedLoader(); /** * サンプルイメージファイルが保存可能であるか?
* 有効なキャラクターデータが選択されており、サンプルイメージの更新が許可されていればtrue.
*/ private boolean canWriteSamplePicture; /** * サンプルイメージを表示するパネル */ private SamplePicturePanel sampleImgPanel; private Action actOK; private Action actCancel; private Action actProfileNew; private Action actProfileCopy; private Action actProfileEdit; private Action actProfileRemove; private Action actProfileBrowse; private Action actProfileImport; private Action actProfileExport; private Action actProfileTemplate; /** * プロファイル一覧を表示するリストコンポーネント */ private JTable characterList; /** * プロファイル一覧のリストモデル */ private ProfileSelectorTableModel characterListModel; /** * プロファイルの説明用テキストエリア */ private JTextArea descriptionArea; /** * ダイアログをOKで閉じた場合に選択していたキャラクターデータを示す.
* nullの場合はキャンセルを意味する. */ private CharacterData selectedCharacterData; /** * プロファイルの選択ダイアログを構築する. * * @param parent * 親フレーム、もしくはnull * @param characterDatas * プロファイルのリスト */ public ProfileSelectorDialog(JFrame parent, List characterDatas) { super(parent, true); if (characterDatas == null) { throw new IllegalArgumentException(); } setDefaultCloseOperation(JDialog.DO_NOTHING_ON_CLOSE); addWindowListener(new WindowAdapter() { @Override public void windowClosing(WindowEvent e) { onClosing(); } }); final Properties strings = LocalizedResourcePropertyLoader .getCachedInstance().getLocalizedProperties(STRINGS_RESOURCE); setTitle(strings.getProperty("title")); JPanel pnlProfiles = new JPanel(new BorderLayout()); characterListModel = new ProfileSelectorTableModel(); characterListModel.setModel(characterDatas); characterList = new JTable(characterListModel) { private static final long serialVersionUID = 1L; @Override public Component prepareRenderer(TableCellRenderer renderer, int row, int column) { CharacterData cd = characterListModel.getRow(row); Component comp = super.prepareRenderer(renderer, row, column); if (ProfileListManager.isUsingCharacterData(cd)) { // 使用中のものは太字で表示 Font f = comp.getFont(); comp.setFont(f.deriveFont(Font.BOLD)); } if (!cd.canWrite()) { // 書き込み不可のものはイタリックで表示 Font f = comp.getFont(); comp.setFont(f.deriveFont(Font.ITALIC)); } return comp; } }; characterList.setAutoResizeMode(JTable.AUTO_RESIZE_OFF); characterList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); characterListModel.adjustColumnModel(characterList.getColumnModel()); characterList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); characterList.getSelectionModel().addListSelectionListener( new ListSelectionListener() { public void valueChanged(ListSelectionEvent e) { if (!e.getValueIsAdjusting()) { updateUIState(); } } }); characterList.addMouseListener(new MouseAdapter() { @Override public void mouseClicked(MouseEvent e) { if (e.getClickCount() == 2) { // 正確に2回 onOK(); } } }); JScrollPane characterListSP = new JScrollPane(characterList); characterListSP.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS); characterListSP .setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS); pnlProfiles.add(characterListSP, BorderLayout.CENTER); actOK = new AbstractAction(strings.getProperty("btn.select")) { private static final long serialVersionUID = 1L; public void actionPerformed(ActionEvent e) { onOK(); } }; actCancel = new AbstractAction(strings.getProperty("btn.cancel")) { private static final long serialVersionUID = 1L; public void actionPerformed(ActionEvent e) { onCancel(); } }; actProfileNew = new AbstractAction(strings.getProperty("profile.new")) { private static final long serialVersionUID = 1L; public void actionPerformed(ActionEvent e) { onProfileNew(true); } }; actProfileCopy = new AbstractAction(strings.getProperty("profile.copy")) { private static final long serialVersionUID = 1L; public void actionPerformed(ActionEvent e) { onProfileNew(false); } }; actProfileEdit = new AbstractAction(strings.getProperty("profile.edit")) { private static final long serialVersionUID = 1L; public void actionPerformed(ActionEvent e) { onProfileEdit(); } }; actProfileRemove = new AbstractAction( strings.getProperty("profile.remove")) { private static final long serialVersionUID = 1L; public void actionPerformed(ActionEvent e) { onProfileRemove(); } }; actProfileBrowse = new AbstractAction( strings.getProperty("profile.browse")) { private static final long serialVersionUID = 1L; public void actionPerformed(ActionEvent e) { onProfileBrowse(); } }; actProfileImport = new AbstractAction( strings.getProperty("profile.import")) { private static final long serialVersionUID = 1L; public void actionPerformed(ActionEvent e) { onProfileImport(); } }; actProfileExport = new AbstractAction( strings.getProperty("profile.export")) { private static final long serialVersionUID = 1L; public void actionPerformed(ActionEvent e) { onProfileExport(); } }; actProfileTemplate = new AbstractAction( strings.getProperty("profile.template")) { private static final long serialVersionUID = 1L; public void actionPerformed(ActionEvent e) { onProfileTemplate(); } }; final JPopupMenu popupTblMenu = new JPopupMenu(); popupTblMenu.add(new JMenuItem(actOK)); popupTblMenu.add(new JSeparator()); popupTblMenu.add(new JMenuItem(actProfileCopy)); popupTblMenu.add(new JMenuItem(actProfileEdit)); popupTblMenu.add(new JMenuItem(actProfileRemove)); popupTblMenu.add(new JSeparator()); popupTblMenu.add(new JMenuItem(actProfileBrowse)); popupTblMenu.add(new JMenuItem(actProfileImport)); popupTblMenu.add(new JMenuItem(actProfileExport)); popupTblMenu.add(new JSeparator()); popupTblMenu.add(new JMenuItem(actProfileTemplate)); characterList.addMouseListener(new MouseAdapter() { @Override public void mousePressed(MouseEvent e) { evaluatePopup(e); } @Override public void mouseReleased(MouseEvent e) { evaluatePopup(e); } private void evaluatePopup(MouseEvent e) { if (e.isPopupTrigger()) { popupTblMenu.show(characterList, e.getX(), e.getY()); } } }); JButton btnProfileNew = new JButton(actProfileNew); JButton btnProfileCopy = new JButton(actProfileCopy); JButton btnProfileEdit = new JButton(actProfileEdit); JButton btnProfileRemove = new JButton(actProfileRemove); JButton btnProfileBrowse = new JButton(actProfileBrowse); JButton btnProfileImport = new JButton(actProfileImport); JButton btnProfileExport = new JButton(actProfileExport); JButton btnProfileTemplate = new JButton(actProfileTemplate); JPanel pnlProfileEditButtons = new JPanel(); pnlProfileEditButtons.setLayout(new GridBagLayout()); GridBagConstraints gbc = new GridBagConstraints(); gbc.gridx = 0; gbc.gridy = 0; gbc.gridwidth = 1; gbc.gridheight = 1; gbc.weightx = 0.; gbc.weighty = 0.; gbc.anchor = GridBagConstraints.WEST; gbc.fill = GridBagConstraints.BOTH; gbc.ipadx = 0; gbc.ipady = 0; gbc.insets = new Insets(0, 3, 0, 3); pnlProfileEditButtons.add(btnProfileNew, gbc); gbc.gridx = 0; gbc.gridy = 1; pnlProfileEditButtons.add(btnProfileCopy, gbc); gbc.gridx = 0; gbc.gridy = 2; pnlProfileEditButtons.add(btnProfileEdit, gbc); gbc.gridx = 0; gbc.gridy = 3; pnlProfileEditButtons.add(btnProfileRemove, gbc); gbc.gridx = 0; gbc.gridy = 4; gbc.weighty = 1.; pnlProfileEditButtons.add(Box.createGlue(), gbc); gbc.gridx = 0; gbc.gridy = 5; gbc.weighty = 0.; pnlProfileEditButtons.add(btnProfileBrowse, gbc); gbc.gridx = 0; gbc.gridy = 6; gbc.weighty = 0.; pnlProfileEditButtons.add(btnProfileImport, gbc); gbc.gridx = 0; gbc.gridy = 7; pnlProfileEditButtons.add(btnProfileExport, gbc); gbc.gridx = 0; gbc.gridy = 8; pnlProfileEditButtons.add(btnProfileTemplate, gbc); JPanel pnlProfilesGroup = new JPanel(new BorderLayout()); pnlProfilesGroup.setBorder(BorderFactory.createCompoundBorder(BorderFactory .createEmptyBorder(3, 3, 3, 3), BorderFactory .createTitledBorder(strings.getProperty("profiles")))); pnlProfilesGroup.add(pnlProfiles, BorderLayout.CENTER); pnlProfilesGroup.add(pnlProfileEditButtons, BorderLayout.EAST); JPanel infoPanel = new JPanel(new GridLayout(1, 2)); JPanel descriptionPanel = new JPanel(new BorderLayout()); descriptionPanel.setBorder(BorderFactory.createCompoundBorder( BorderFactory.createEmptyBorder(3, 3, 3, 3), BorderFactory .createTitledBorder(strings.getProperty("description")))); descriptionArea = new JTextArea(); descriptionArea.setEditable(false); descriptionPanel.add(new JScrollPane(descriptionArea), BorderLayout.CENTER); // サンプルピクャパネル sampleImgPanel = new SamplePicturePanel(); // サンプルピクチャファイルのドラッグアンドドロップ new DropTarget(sampleImgPanel, new FileDropTarget() { @Override protected void onDropFiles(final List dropFiles) { if (dropFiles == null || dropFiles.isEmpty()) { return; } // インポートダイアログを開く. // ドロップソースの処理がブロッキングしないように、 // ドロップハンドラの処理を終了して呼び出す. SwingUtilities.invokeLater(new Runnable() { public void run() { onDrop(dropFiles); } }); } @Override protected void onException(Exception ex) { ErrorMessageHelper.showErrorDialog(ProfileSelectorDialog.this, ex); } }); // サンプルピクチャのコンテキストメニュー final JPopupMenu popupMenu = new JPopupMenu(); final JMenuItem popupMenuCut = popupMenu.add(new AbstractAction(strings.getProperty("samplepicture.cut")) { private static final long serialVersionUID = 1L; public void actionPerformed(ActionEvent e) { onSamplePictureCut(); } }); final JMenuItem popupMenuPaste = popupMenu.add(new AbstractAction(strings.getProperty("samplepicture.paste")) { private static final long serialVersionUID = 1L; public void actionPerformed(ActionEvent e) { onSamplePicturePaste(); } }); sampleImgPanel.addMouseListener(new MouseAdapter() { @Override public void mousePressed(MouseEvent e) { evaluatePopup(e); } @Override public void mouseReleased(MouseEvent e) { evaluatePopup(e); } private void evaluatePopup(MouseEvent e) { if (e.isPopupTrigger()) { popupMenuCut.setEnabled(sampleImgPanel.getSamplePictrue() != null && canWriteSamplePicture); popupMenuPaste.setEnabled(canWriteSamplePicture && ClipboardUtil.hasImage()); popupMenu.show(sampleImgPanel, e.getX(), e.getY()); } } }); JScrollPane sampleImgPanelSp = new JScrollPane(sampleImgPanel); sampleImgPanelSp.setBorder(null); JPanel sampleImgTitledPanel = new JPanel(new BorderLayout()); sampleImgTitledPanel.add(sampleImgPanelSp, BorderLayout.CENTER); sampleImgTitledPanel.setBorder(BorderFactory.createCompoundBorder( BorderFactory.createEmptyBorder(3, 3, 3, 3), BorderFactory .createTitledBorder(strings.getProperty("sample-image")))); infoPanel.add(descriptionPanel); infoPanel.add(sampleImgTitledPanel); JSplitPane centerPane = new JSplitPane(JSplitPane.VERTICAL_SPLIT, true); centerPane.setResizeWeight(1.f); // ウィンドウサイズ変更時に上を可変とする. centerPane.setDividerLocation(Integer.parseInt(strings .getProperty("dividerLocation"))); centerPane.add(pnlProfilesGroup); centerPane.add(infoPanel); Container contentPane = getContentPane(); contentPane.setLayout(new BorderLayout()); contentPane.add(centerPane, BorderLayout.CENTER); // OK/CANCEL ボタンパネル JPanel btnPanel = new JPanel(); BoxLayout boxLayout = new BoxLayout(btnPanel, BoxLayout.LINE_AXIS); btnPanel.setLayout(boxLayout); btnPanel.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 42)); JButton btnOK = new JButton(actOK); JButton btnCancel = new JButton(actCancel); if (Main.isLinuxOrMacOSX()) { btnPanel.add(btnCancel); btnPanel.add(btnOK); } else { btnPanel.add(btnOK); btnPanel.add(btnCancel); } JPanel btnPanel2 = new JPanel(new BorderLayout()); btnPanel2.add(btnPanel, BorderLayout.EAST); contentPane.add(btnPanel2, BorderLayout.SOUTH); Toolkit tk = Toolkit.getDefaultToolkit(); JRootPane rootPane = getRootPane(); rootPane.setDefaultButton(btnOK); InputMap im = rootPane.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW); ActionMap am = rootPane.getActionMap(); im.put(KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0), "closeProfileSelectorDialog"); im.put(KeyStroke.getKeyStroke(KeyEvent.VK_W, tk.getMenuShortcutKeyMask()), "closeProfileSelectorDialog"); am.put("closeProfileSelectorDialog", actCancel); int width = Integer.parseInt(strings.getProperty("windowWidth")); int height = Integer.parseInt(strings.getProperty("windowHeight")); setSize(width, height); setLocationRelativeTo(parent); characterList.requestFocus(); updateUIState(); } public CharacterData getSelectedCharacterData() { return selectedCharacterData; } /** * キャラクターデータの選択変更に伴い、ボタンやサンプルピクチャなどを切り替える. */ protected void updateUIState() { CharacterData characterData = null; int selRow = characterList.getSelectedRow(); if (selRow >= 0) { characterData = characterListModel.getRow(selRow); } final Properties strings = LocalizedResourcePropertyLoader .getCachedInstance().getLocalizedProperties(STRINGS_RESOURCE); boolean selected = (characterData != null); boolean enableEdit = (characterData != null) && characterData.canWrite(); actOK.setEnabled(selected); actProfileNew.setEnabled(true); actProfileCopy.setEnabled(selected); actProfileEdit.setEnabled(selected); actProfileRemove.setEnabled(selected && enableEdit); actProfileImport.setEnabled(true); actProfileExport.setEnabled(selected); actProfileBrowse.setEnabled(selected); actProfileTemplate.setEnabled(selected); if (enableEdit) { actProfileEdit.putValue(Action.NAME, strings.getProperty("profile.edit")); } else { actProfileEdit.putValue(Action.NAME, strings.getProperty("profile.edit.readonly")); } // 有効なキャラクターデータであり、且つ、書き込み可能であり、且つ、使用中でなければ削除可能 boolean removable = characterData != null && characterData.isValid() && !ProfileListManager.isUsingCharacterData(characterData) && characterData.canWrite(); actProfileRemove.setEnabled(removable); boolean canWriteSamplePicture = false; BufferedImage sampleImage = null; if (characterData != null && characterData.isValid()) { // description StringWriter sw = new StringWriter(); PrintWriter descriptionBuf = new PrintWriter(sw); URI docBase = characterData.getDocBase(); String author = characterData.getAuthor(); String description = characterData.getDescription(); if (docBase != null) { descriptionBuf.println("configuration: " + docBase); } if (author != null && author.length() > 0) { descriptionBuf.println("author: " + author); } Dimension imageSize = characterData.getImageSize(); if (imageSize != null) { descriptionBuf.println("size: " + imageSize.width + "x" + imageSize.height); } if (description != null) { description = description.replace("\r\n", "\n"); description = description.replace("\r", "\n"); description = description.replace("\n", System.getProperty("line.separator")); descriptionBuf.println(description); } descriptionArea.setText(sw.toString()); descriptionArea.setSelectionStart(0); descriptionArea.setSelectionEnd(0); // sample picture try { CharacterDataPersistent persist = CharacterDataPersistent.getInstance(); sampleImage = persist.loadSamplePicture(characterData, imageLoader); canWriteSamplePicture = persist.canSaveSamplePicture(characterData); } catch (Exception ex) { // サンプルピクチャの読み込みに失敗したら、サンプルピクチャを表示しないだけで処理は継続する. logger.log(Level.WARNING, "sample picture loading failed. " + characterData , ex); sampleImage = null; } } this.canWriteSamplePicture = canWriteSamplePicture; String dropHere = strings.getProperty("dropHere"); String noPicture = strings.getProperty("nopicture"); sampleImgPanel.setSamplePicture(sampleImage); sampleImgPanel.setAlternateText(canWriteSamplePicture ? dropHere : noPicture); } /** * サンプルピクチャのファイルを削除し、表示されている画像をクリップボードに保存する */ protected void onSamplePictureCut() { int selRow = characterList.getSelectedRow(); if (selRow < 0) { return; } CharacterData characterData = characterListModel.getRow(selRow); BufferedImage img = sampleImgPanel.getSamplePictrue(); Toolkit tk = Toolkit.getDefaultToolkit(); if (characterData == null || !characterData.isValid() || !canWriteSamplePicture || img == null) { tk.beep(); return; } try { // クリップボードにイメージをセット Color bgColor = AppConfig.getInstance().getSampleImageBgColor(); ClipboardUtil.setImage(img, bgColor); // サンプルピクチャを削除 CharacterDataPersistent persist = CharacterDataPersistent.getInstance(); persist.saveSamplePicture(characterData, null); // プレビューを更新 sampleImgPanel.setSamplePicture(null); } catch (Exception ex) { ErrorMessageHelper.showErrorDialog(this, ex); } } /** * サンプルピクチャをクリップボードから貼り付け、それをファイルに保存する */ protected void onSamplePicturePaste() { CharacterData characterData = null; int selRow = characterList.getSelectedRow(); if (selRow >= 0) { characterData = characterListModel.getRow(selRow); } Toolkit tk = Toolkit.getDefaultToolkit(); if (characterData == null || !characterData.isValid() || !canWriteSamplePicture) { tk.beep(); return; } try { BufferedImage img = ClipboardUtil.getImage(); if (img != null) { // 画像が読み込まれた場合、それを保存する. CharacterDataPersistent persist = CharacterDataPersistent.getInstance(); persist.saveSamplePicture(characterData, img); sampleImgPanel.setSamplePicture(img); } else { // サンプルピクチャは更新されなかった。 tk.beep(); } } catch (Exception ex) { ErrorMessageHelper.showErrorDialog(this, ex); } } /** * サンプルピクチャパネルにドロップされた画像ファイルをサンプルピクチャとしてコピーします.
* * @param dtde * ドロップイベント */ protected void onDrop(Collection dropFiles) { CharacterData characterData = null; int selRow = characterList.getSelectedRow(); if (selRow >= 0) { characterData = characterListModel.getRow(selRow);; } Toolkit tk = Toolkit.getDefaultToolkit(); if (dropFiles == null || dropFiles.isEmpty() || !canWriteSamplePicture || characterData == null || !characterData.isValid() || !canWriteSamplePicture) { tk.beep(); return; } try { // 最初のファィルのみ取得する. File dropFile = dropFiles.iterator().next(); // ドロップファイルがあれば、イメージとして読み込む BufferedImage img = null; if (dropFile != null && dropFile.isFile() && dropFile.canRead()) { try { LoadedImage loadedImage = imageLoader.load(new FileImageResource(dropFile)); img = loadedImage.getImage(); } catch (IOException ex) { // イメージのロードができない = 形式が不正であるなどの場合は // 読み込みせず、継続する. img = null; } } if (img != null) { // 画像が読み込まれた場合、それを保存する. CharacterDataPersistent persist = CharacterDataPersistent.getInstance(); persist.saveSamplePicture(characterData, img); sampleImgPanel.setSamplePicture(img); } else { // サンプルピクチャは更新されなかった。 tk.beep(); } } catch (Exception ex) { ErrorMessageHelper.showErrorDialog(this, ex); } } @Override public void dispose() { imageLoader.close(); super.dispose(); } /** * 閉じる場合 */ protected void onClosing() { dispose(); } /** * OKボタン押下 */ protected void onOK() { selectedCharacterData = null; int selRow = characterList.getSelectedRow(); if (selRow >= 0) { selectedCharacterData = characterListModel.getRow(selRow); } if (selectedCharacterData == null) { Toolkit tk = Toolkit.getDefaultToolkit(); tk.beep(); return; } dispose(); } /** * キャンセルボタン押下 */ protected void onCancel() { selectedCharacterData = null; onClosing(); } /** * プロファイルの作成 * * @param makeDefault * デフォルトのプロファイルで作成する場合 */ protected void onProfileNew(boolean makeDefault) { CharacterData cd = null; int selRow = characterList.getSelectedRow(); if (selRow >= 0) { cd = characterListModel.getRow(selRow); } CharacterDataPersistent persist = CharacterDataPersistent.getInstance(); if (makeDefault || cd == null) { try { final Properties strings = LocalizedResourcePropertyLoader .getCachedInstance().getLocalizedProperties(STRINGS_RESOURCE); // キャラクターデータ選択用のコンボボックスの準備 JComboBox comboTemplates = new JComboBox(); comboTemplates.setEditable(false); final JLabel lbl = new JLabel(); comboTemplates.setRenderer(new ListCellRenderer() { public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) { @SuppressWarnings("unchecked") Map.Entry entry = (Map.Entry) value; if (entry != null) { lbl.setText(entry.getValue()); } return lbl; } }); // キャラクターデータのテンプレートを一覧登録する. CharacterDataDefaultProvider defProv = new CharacterDataDefaultProvider(); for (final Map.Entry entry : defProv .getCharacterDataTemplates().entrySet()) { comboTemplates.addItem(entry); } // コンボボックスの幅を広げる. // (短いとInputBoxのタイトルが隠れるため) Dimension preferredSize = comboTemplates.getPreferredSize(); int comboWidth = Integer.parseInt(strings .getProperty("profileNew.chooseTemplate.combo.width")); preferredSize.width = Math.max(preferredSize.width, comboWidth); comboTemplates.setPreferredSize(preferredSize); int ret = JOptionPane.showConfirmDialog(this, comboTemplates, strings.getProperty("profileNew.chooseTemplate.title"), JOptionPane.OK_CANCEL_OPTION); if (ret != JOptionPane.OK_OPTION) { // キャンセルされた場合 return; } @SuppressWarnings("unchecked") Map.Entry selection = (Map.Entry) comboTemplates .getSelectedItem(); if (selection == null) { // 未選択の場合 return; } // テンプレートを読み込む String characterXmlName = selection.getKey(); cd = defProv.loadPredefinedCharacterData(characterXmlName); } catch (Exception ex) { ErrorMessageHelper.showErrorDialog(this, ex); return; } } // 基本情報をコピーします。 CharacterData newCd = cd.duplicateBasicInfo(); // DocBaseはnullにする。これにより新規作成と判断される. newCd.setDocBase(null); // 新規なのでパーツセット情報はリセットする newCd.clearPartsSets(false); ProfileEditDialog editDlg = new ProfileEditDialog(this, newCd); editDlg.setVisible(true); newCd = editDlg.getResult(); if (newCd == null) { // キャンセルされた場合 return; } // 新規プロファイルを保存する. try { persist.createProfile(newCd); persist.saveFavorites(newCd); } catch (Exception ex) { ErrorMessageHelper.showErrorDialog(this, ex); return; } // 作成されたプロファイルを一覧に追加する. characterListModel.add(newCd); } /** * プロファィルの編集 */ protected void onProfileEdit() { CharacterData cd = null; int selRow = characterList.getSelectedRow(); if (selRow >= 0) { cd = characterListModel.getRow(selRow); } if (cd == null || !cd.isValid()) { return; } try { // プロファイル編集ダイアログを開き、その結果を取得する. CharacterData newCd = ProfileListManager.editProfile(this, cd); if (newCd == null) { // キャンセルされた場合 return; } // 現在開いているメインフレームに対してキャラクター定義が変更されたことを通知する. CharacterDataChangeObserver.getDefault().notifyCharacterDataChange( this, newCd, true, true); // プロファイル一覧画面も更新する. characterListModel.set(selRow, newCd); characterList.repaint(); } catch (Exception ex) { ErrorMessageHelper.showErrorDialog(this, ex); return; } } /** * プロファイルの削除 */ protected void onProfileRemove() { CharacterData cd = null; int selRow = characterList.getSelectedRow(); if (selRow >= 0) { cd = characterListModel.getRow(selRow); } if (cd == null || !cd.isValid() || ProfileListManager.isUsingCharacterData(cd) || !cd.canWrite()) { // 無効なキャラクター定義であるか、使用中であるか、書き込み不可であれば削除は実行できない. Toolkit tk = Toolkit.getDefaultToolkit(); tk.beep(); return; } final Properties strings = LocalizedResourcePropertyLoader .getCachedInstance().getLocalizedProperties(STRINGS_RESOURCE); String msgTempl = strings.getProperty("profile.remove.confirm"); MessageFormat fmt = new MessageFormat(msgTempl); String msg = fmt.format(new Object[]{cd.getName()}); JPanel msgPanel = new JPanel(new BorderLayout(5, 5)); msgPanel.add(new JLabel(msg), BorderLayout.CENTER); JCheckBox chkRemoveForce = new JCheckBox(strings.getProperty("profile.remove.force")); msgPanel.add(chkRemoveForce, BorderLayout.SOUTH); JOptionPane optionPane = new JOptionPane(msgPanel, JOptionPane.QUESTION_MESSAGE, JOptionPane.YES_NO_OPTION) { private static final long serialVersionUID = 1L; @Override public void selectInitialValue() { String noBtnCaption = UIManager.getString("OptionPane.noButtonText"); for (JButton btn : UIHelper.getInstance().getDescendantOfClass(JButton.class, this)) { if (btn.getText().equals(noBtnCaption)) { // 「いいえ」ボタンにフォーカスを設定 btn.requestFocus(); } } } }; JDialog dlg = optionPane.createDialog(this, strings.getProperty("confirm.remove")); dlg.setVisible(true); Object ret = optionPane.getValue(); if (ret == null || ((Number) ret).intValue() != JOptionPane.YES_OPTION) { return; } if (!cd.canWrite() || cd.getDocBase() == null) { JOptionPane.showMessageDialog(this, strings.getProperty("profile.remove.cannot")); return; } boolean forceRemove = chkRemoveForce.isSelected(); try { CharacterDataPersistent persiste = CharacterDataPersistent.getInstance(); persiste.remove(cd, forceRemove); } catch (Exception ex) { ErrorMessageHelper.showErrorDialog(this, ex); return; } // モデルから該当キャラクターを削除して再描画 characterListModel.remove(selRow); characterList.repaint(); updateUIState(); } /** * 場所を開く */ protected void onProfileBrowse() { CharacterData cd = null; int selRow = characterList.getSelectedRow(); if (selRow >= 0) { cd = characterListModel.getRow(selRow); } if (cd == null || !cd.isValid()) { Toolkit tk = Toolkit.getDefaultToolkit(); tk.beep(); return; } try { URI docBase = cd.getDocBase(); if (!DesktopUtilities.browseBaseDir(docBase)) { JOptionPane.showMessageDialog(this, docBase); } } catch (Exception ex) { ErrorMessageHelper.showErrorDialog(this, ex); } } /** * インポート */ protected void onProfileImport() { try { CharacterData selCd = null; int selRow = characterList.getSelectedRow(); if (selRow >= 0) { selCd = characterListModel.getRow(selRow); } // 選択したプロファイルを更新するか、新規にプロファイルを作成するか選択できるようにする if (selCd != null) { final Properties strings = LocalizedResourcePropertyLoader .getCachedInstance().getLocalizedProperties(STRINGS_RESOURCE); JPanel radioPanel = new JPanel(new BorderLayout()); JRadioButton btnUpdate = new JRadioButton(strings.getProperty("importToUpdateProfile")); JRadioButton btnNew = new JRadioButton(strings.getProperty("importToCreateProfile")); ButtonGroup radios = new ButtonGroup(); radios.add(btnUpdate); radios.add(btnNew); btnUpdate.setSelected(true); radioPanel.add(btnUpdate, BorderLayout.NORTH); radioPanel.add(btnNew, BorderLayout.SOUTH); int ret = JOptionPane.showConfirmDialog(this, radioPanel, strings.getProperty("confirmUpdateProfile"), JOptionPane.OK_CANCEL_OPTION); if (ret != JOptionPane.OK_OPTION) { return; } if (btnNew.isSelected()) { // 選択されていないことにする. selCd = null; } } // キャラクターデータをロードし直す. CharacterData cd; if (selCd != null) { cd = selCd.duplicateBasicInfo(); try { ProfileListManager.loadCharacterData(cd); ProfileListManager.loadFavorites(cd); } catch (IOException ex) { ErrorMessageHelper.showErrorDialog(this, ex); // 継続する. } } else { cd = null; } // ディレクトリ監視エージェントの停止 PartsImageDirectoryWatchAgentFactory agentFactory = PartsImageDirectoryWatchAgentFactory.getFactory(); PartsImageDirectoryWatchAgent agent = agentFactory.getAgent(cd); agent.suspend(); try { // インポートウィザードの実行 ImportWizardDialog importWizDialog = new ImportWizardDialog(this, cd); importWizDialog.setVisible(true); CharacterData newCd = importWizDialog.getImportedCharacterData(); if (importWizDialog.getExitCode() == ImportWizardDialog.EXIT_PROFILE_CREATED) { // 作成されたプロファイルを一覧に追加する. characterListModel.add(newCd); } else if (importWizDialog.getExitCode() == ImportWizardDialog.EXIT_PROFILE_UPDATED) { // 更新されたプロファイルを通知する CharacterDataChangeObserver.getDefault() .notifyCharacterDataChange(this, newCd, true, true); } } finally { agent.resume(); } } catch (Exception ex) { ErrorMessageHelper.showErrorDialog(this, ex); } } /** * エクスポート */ protected void onProfileExport() { CharacterData cd = null; int selRow = characterList.getSelectedRow(); if (selRow >= 0) { cd = characterListModel.getRow(selRow); } if (cd == null || !cd.isValid()) { Toolkit tk = Toolkit.getDefaultToolkit(); tk.beep(); return; } try { // コピーした情報に対してパーツデータをロードする. final CharacterData newCd = cd.duplicateBasicInfo(); setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); try { ProfileListManager.loadCharacterData(newCd); } finally { setCursor(Cursor.getDefaultCursor()); } // エクスポートウィザードを表示 BufferedImage sampleImage = sampleImgPanel.getSamplePictrue(); ExportWizardDialog exportWizDialog = new ExportWizardDialog(this, newCd, sampleImage); exportWizDialog.setVisible(true); } catch (Exception ex) { ErrorMessageHelper.showErrorDialog(this, ex); return; } } /** * 選択したプロファイルをテンプレートとして登録する. */ protected void onProfileTemplate() { try { CharacterData cd = null; int selRow = characterList.getSelectedRow(); if (selRow >= 0) { cd = characterListModel.getRow(selRow); } if (cd == null || !cd.isValid()) { Toolkit tk = Toolkit.getDefaultToolkit(); tk.beep(); return; } String defualtName = cd.getId() + "_" + cd.getRev() + ".xml"; // Windowsでのファイル名として使用禁止の文字を置換する. for (char c : "<>|:;*?/\\\"".toCharArray()) { defualtName = defualtName.replace(c, '_'); } // カスタマイズ用テンプレートファイルの格納場所を取得する. final CharacterDataDefaultProvider defProv = new CharacterDataDefaultProvider(); final File templDir = defProv.getTemplateDir(true); // 指定されたディレクトリ以外に表示・移動できないファイルシステムビューを使用したファイルチューザ JFileChooser fileChooser = new JFileChooser( new SingleRootFileSystemView(templDir)) { private static final long serialVersionUID = 1L; @Override public void approveSelection() { File outFile = getSelectedFile(); if (outFile == null) { return; } String name = outFile.getName(); if (!defProv.canFileSave(name) || !name.endsWith(".xml")) { // 書き込み不可ファイル、もしくはxml以外なので許可しない. Toolkit tk = Toolkit.getDefaultToolkit(); tk.beep(); return; } // ファイルが存在すれば上書き確認する. if (outFile.exists()) { Properties strings = LocalizedResourcePropertyLoader .getCachedInstance().getLocalizedProperties( STRINGS_RESOURCE); if (JOptionPane.showConfirmDialog(this, strings.getProperty("confirmOverwrite"), strings.getProperty("confirm"), JOptionPane.YES_NO_OPTION, JOptionPane.WARNING_MESSAGE) != JOptionPane.YES_OPTION) { return; } } super.approveSelection(); } }; // 保存先ファイル名 fileChooser.setSelectedFile(new File(templDir, defualtName)); int ret = fileChooser.showSaveDialog(this); if (ret != JFileChooser.APPROVE_OPTION) { return; } // テンプレート名 String localizedName = cd.getName(); final Properties strings = LocalizedResourcePropertyLoader .getCachedInstance().getLocalizedProperties(STRINGS_RESOURCE); localizedName = JOptionPane.showInputDialog(this, strings.getProperty("inputTemplateName"), localizedName); if (localizedName == null) { return; } File outFile = fileChooser.getSelectedFile(); defProv.saveTemplate(outFile.getName(), cd, localizedName); } catch (Exception ex) { ErrorMessageHelper.showErrorDialog(this, ex); return; } } } /** * プロファイル一覧リストのモデル */ class ProfileSelectorTableModel extends AbstractTableModel { private static final long serialVersionUID = 1L; private enum Columns { NAME("profile.column.name") { @Override public String getValue(CharacterData cd) { return cd.getName(); } }, ID("profile.column.id") { @Override public String getValue(CharacterData cd) { return cd.getId(); } }, REVISION("profile.column.revision") { @Override public String getValue(CharacterData cd) { return cd.getRev(); } }, CANVAS_SIZE("profile.column.canvasSize") { @Override public String getValue(CharacterData cd) { Dimension siz = cd.getImageSize(); if (siz != null) { return siz.width + "x" + siz.height; } return ""; } }, DESCRIPTION("profile.column.description") { @Override public String getValue(CharacterData cd) { return cd.getDescription(); } }, AUTHOR("profile.column.author") { @Override public String getValue(CharacterData cd) { return cd.getAuthor(); } }, LOCATION("profile.column.location") { @Override public String getValue(CharacterData cd) { return cd.getDocBase().toString(); } }; private final String columnName; private String displayName; private int width; private Columns(String columnName) { this.columnName = columnName; } public String getDisplayName() { loadProperty(); return displayName; } public int getWidth() { loadProperty(); return width; } private void loadProperty() { if (displayName != null) { return; } final Properties strings = LocalizedResourcePropertyLoader .getCachedInstance().getLocalizedProperties( ProfileSelectorDialog.STRINGS_RESOURCE); displayName = strings.getProperty(columnName); width = Integer .parseInt(strings.getProperty(columnName + ".width")); } public abstract String getValue(CharacterData cd); } private List rows = Collections .emptyList(); public void setModel(List rows) { if (rows == null) { throw new IllegalArgumentException(); } this.rows = new ArrayList(rows); fireTableDataChanged(); } public void add(CharacterData cd) { if (cd == null) { throw new IllegalArgumentException(); } this.rows.add(cd); int lastRow = this.rows.size() - 1; fireTableRowsInserted(lastRow, lastRow); } public void set(int selRow, CharacterData cd) { this.rows.set(selRow, cd); fireTableRowsDeleted(selRow, selRow); } public void remove(int selRow) { this.rows.remove(selRow); fireTableRowsDeleted(selRow, selRow); } public List getModel() { return rows; } public CharacterData getRow(int rowIndex) { CharacterData cd = rows.get(rowIndex); return cd; } public int getColumnCount() { return Columns.values().length; } public int getRowCount() { return rows.size(); } public void adjustColumnModel(TableColumnModel columnModel) { Columns[] columns = Columns.values(); for (int idx = 0; idx < columns.length; idx++) { columnModel.getColumn(idx).setPreferredWidth( columns[idx].getWidth()); } } @Override public String getColumnName(int column) { return Columns.values()[column].getDisplayName(); } public Object getValueAt(int rowIndex, int columnIndex) { CharacterData cd = getRow(rowIndex); Columns column = Columns.values()[columnIndex]; return column.getValue(cd); } @Override public boolean isCellEditable(int rowIndex, int columnIndex) { return false; } @Override public void setValueAt(Object aValue, int rowIndex, int columnIndex) { // なにもしない. } } CharacterManaJ/src/charactermanaj/ui/ColorBox.java0000644000175000017500000001620212560206305022332 0ustar paulliupaulliupackage charactermanaj.ui; import java.awt.BorderLayout; import java.awt.Color; import java.awt.Dimension; import java.awt.Graphics; import java.awt.Insets; import java.awt.Rectangle; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import java.util.Properties; import javax.swing.AbstractAction; import javax.swing.BorderFactory; import javax.swing.JButton; import javax.swing.JColorChooser; import javax.swing.JPanel; import javax.swing.border.BevelBorder; import javax.swing.event.EventListenerList; import charactermanaj.util.LocalizedResourcePropertyLoader; /** * 色表示ボックス.
* 色が変更された場合はプロパティ「colorKey」に対するプロパティ変更リスナへの通知が行われます.
* ダブルクリックまたはボタンが押下され色を指定したことによるアクションリスナへの通知が行われます.
* 既定のコマンドは「colorKey」です.
* アクションは色選択ダイアログがOKされたことによるアクションであり、色が前後で変更されなくても通知されます.
* @author seraphy */ public class ColorBox extends JPanel { private static final long serialVersionUID = -8745278154296281466L; /** * リソース */ protected static final String STRINGS_RESOURCE = "languages/colorbox"; /** * コマンド */ private String actionCommand = "colorKey"; /** * 初期カラー */ private Color colorKey; /** * 色の表示パネル */ private JPanel colorDisplayPanel; /** * 色選択アクション */ private AbstractAction actChooseColor; /** * 色ボックスを構築します.
* 色選択ボタンが付与されます.
*/ public ColorBox() { this(null, true); } /** * 初期カラーと、色選択ボックスの表示有無を指定して構築します. * @param colorKey 初期カラー * @param colorPicker 色選択ボタンの表示有無 */ public ColorBox(Color colorKey, boolean colorPicker) { if (colorKey == null) { colorKey = Color.WHITE; } this.colorKey = colorKey; Properties strings = LocalizedResourcePropertyLoader.getCachedInstance() .getLocalizedProperties(STRINGS_RESOURCE); colorDisplayPanel = createColorDiaplyPanel(); colorDisplayPanel.addMouseListener(new MouseAdapter() { @Override public void mouseClicked(MouseEvent e) { if (actChooseColor.isEnabled() && e.getClickCount() == 2) { onChooseColor(new ActionEvent(this, 1, getActionCommand(), e.getWhen(), e.getModifiers())); } } }); colorDisplayPanel.setBorder(BorderFactory.createCompoundBorder( BorderFactory.createEmptyBorder(0, 0, 0, 3), BorderFactory.createBevelBorder(BevelBorder.LOWERED))); colorDisplayPanel.setPreferredSize(new Dimension(32, 24)); actChooseColor = new AbstractAction(strings.getProperty("btn.chooseColorKey")) { private static final long serialVersionUID = 1L; public void actionPerformed(ActionEvent e) { onChooseColor(e); } }; JButton btnChooseColor = new JButton(actChooseColor); btnChooseColor.setVisible(colorPicker); actChooseColor.setEnabled(colorPicker); setLayout(new BorderLayout()); add(colorDisplayPanel, BorderLayout.CENTER); add(btnChooseColor, BorderLayout.EAST); } protected JPanel createColorDiaplyPanel() { return new JPanel() { private static final long serialVersionUID = -8554046012311330274L; @Override protected void paintComponent(Graphics g) { super.paintComponent(g); Rectangle rct = getBounds(); Insets insets = getInsets(); int x = insets.left; int y = insets.top; int w = rct.width - insets.left - insets.right; int h = rct.height - insets.top - insets.bottom; g.setColor(getColorKey()); g.fillRect(x, y, w, h); } }; } protected JPanel getColorDisplayPanel() { return colorDisplayPanel; } @Override public void setEnabled(boolean enabled) { super.setEnabled(enabled); actChooseColor.setEnabled(enabled); } public void setColorKey(Color colorKey) { if (colorKey == null) { colorKey = Color.WHITE; } if ( !this.colorKey.equals(colorKey)) { Color oldc = this.colorKey; this.colorKey = colorKey; repaint(); firePropertyChange("colorKey", oldc, colorKey); } } /** * Adds an ActionListener to the button. * @param l the ActionListener to be added */ public void addActionListener(ActionListener l) { listenerList.add(ActionListener.class, l); } /** * Removes an ActionListener from the button. * If the listener is the currently set Action * for the button, then the Action * is set to null. * * @param l the listener to be removed */ public void removeActionListener(ActionListener l) { listenerList.remove(ActionListener.class, l); } /** * Notifies all listeners that have registered interest for * notification on this event type. The event instance * is lazily created using the event * parameter. * * @param event the ActionEvent object * @see EventListenerList */ protected void fireActionPerformed(ActionEvent event) { Object[] listeners = listenerList.getListenerList(); ActionEvent e = null; for (int i = listeners.length - 2; i >= 0; i -= 2) { if (listeners[i] == ActionListener.class) { if (e == null) { String actionCommand = event.getActionCommand(); if(actionCommand == null) { actionCommand = getActionCommand(); } e = new ActionEvent(this, ActionEvent.ACTION_PERFORMED, actionCommand, event.getWhen(), event.getModifiers()); } ((ActionListener) listeners[i + 1]).actionPerformed(e); } } } public void setActionCommand(String actionCommand) { this.actionCommand = actionCommand; } public String getActionCommand() { return actionCommand; } public Color getColorKey() { return colorKey; } protected String getColorDialogTitle() { Properties strings = LocalizedResourcePropertyLoader.getCachedInstance() .getLocalizedProperties(STRINGS_RESOURCE); return strings.getProperty("caption.chooseColorKey"); } protected void onChooseColor(ActionEvent e) { Color colorKey = getColorKey(); colorKey = JColorChooser.showDialog( this, getColorDialogTitle(), colorKey); if (colorKey != null) { setColorKey(colorKey); fireActionPerformed(e); } } }CharacterManaJ/src/charactermanaj/ui/ProfileListManager.java0000644000175000017500000003776612560206305024354 0ustar paulliupaulliupackage charactermanaj.ui; import java.awt.Cursor; import java.io.File; import java.io.IOException; import java.net.URI; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.concurrent.Future; import java.util.logging.Level; import java.util.logging.Logger; import javax.swing.JDialog; import javax.swing.JFrame; import charactermanaj.model.CharacterData; import charactermanaj.model.PartsManageData; import charactermanaj.model.io.CharacterDataDefaultProvider; import charactermanaj.model.io.CharacterDataDefaultProvider.DefaultCharacterDataVersion; import charactermanaj.model.io.CharacterDataPersistent; import charactermanaj.model.io.CharacterDataPersistent.ListProfileCallback; import charactermanaj.model.io.CharacterDataPersistent.ProfileListErrorHandler; import charactermanaj.model.io.PartsDataLoader; import charactermanaj.model.io.PartsDataLoaderFactory; import charactermanaj.model.io.PartsInfoXMLReader; import charactermanaj.model.io.PartsManageDataDecorateLoader; import charactermanaj.model.io.PartsSpecDecorateLoader; import charactermanaj.model.io.RecentDataPersistent; import charactermanaj.util.ErrorMessageHelper; /** * プロファイルの選択・管理を行うクラス. * * @author seraphy */ public final class ProfileListManager { /** * ロガー */ private static final Logger logger = Logger.getLogger(ProfileListManager.class.getName()); /** * プライベートコンストラクタ */ private ProfileListManager() { super(); } /** * すべてのメインフレームで使用中のキャラクターデータのコレクション.
*/ private static final HashMap activeCharacterDatas = new HashMap(); /** * キャラクターデータが使用中であるか?
* キャラクターデータのDocBaseをもとに判断する.
* nullを指定した場合は常にfalseを返す.
* * @param characterData * キャラクターデータ、またはnull * @return 使用中であればtrue */ public static boolean isUsingCharacterData(CharacterData characterData) { URI characterDocBase = (characterData == null) ? null : characterData.getDocBase(); synchronized (activeCharacterDatas) { Integer cnt = (characterDocBase == null) ? null : activeCharacterDatas.get(characterDocBase); return cnt != null && cnt.intValue() > 0; } } /** * キャラクターデータが使用中であることを登録する。 * * @param characterData * キャラクターデータ */ public static void registerUsedCharacterData(CharacterData characterData) { if (characterData == null) { return; } synchronized (activeCharacterDatas) { URI characterDocBase = characterData.getDocBase(); if (characterDocBase != null) { Integer cnt = activeCharacterDatas.get(characterDocBase); if (cnt == null) { cnt = Integer.valueOf(1); } else { cnt = Integer.valueOf(cnt.intValue() + 1); } activeCharacterDatas.put(characterDocBase, cnt); } } } /** * キャラクターデータの使用中であることを登録解除する。 * * @param characterData * キャラクターデータ */ public static void unregisterUsedCharacterData(CharacterData characterData) { if (characterData == null) { return; } // 使用中のキャラクターデータとしてのカウントを減らす synchronized (activeCharacterDatas) { URI characterDocBase = characterData.getDocBase(); if (characterDocBase != null) { Integer cnt = activeCharacterDatas.get(characterDocBase); if (cnt != null) { cnt = Integer.valueOf(cnt.intValue() - 1); if (cnt.intValue() <= 0) { activeCharacterDatas.remove(characterDocBase); } else { activeCharacterDatas.put(characterDocBase, cnt); } } } } } /** * プロファイル選択ダイアログを表示し、選択されたプロファイルのメインフレームを作成して返す.
* プロファイルの選択をキャンセルした場合はnullを返す.
* * @param parent * 親フレーム * @return メインフレーム、もしくはnull * @throws IOException * 読み込みに失敗した場合 */ public static MainFrame openProfile(JFrame parent) throws IOException { // キャラクタープロファイルのリストをロード CharacterDataPersistent persist = CharacterDataPersistent.getInstance(); List characterDatas = persist .listProfiles(new ProfileListErrorHandler() { public void occureException(File baseDir, Throwable ex) { logger.log(Level.WARNING, "invalid profile. :" + baseDir, ex); } }); // 選択ダイアログを表示 ProfileSelectorDialog selDlg = new ProfileSelectorDialog(parent, characterDatas); selDlg.setVisible(true); CharacterData characterData = selDlg.getSelectedCharacterData(); if (characterData == null || !characterData.isValid()) { // キャンセルしたか、開くことのできないデータ return null; } // メインフレームを準備 MainFrame newFrame; parent.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); try { newFrame = openProfile(characterData); } finally { parent.setCursor(Cursor.getDefaultCursor()); } return newFrame; } /** * 指定したキャラクター定義で新しいメインフレームを作成して返す.
* * @param characterData * キャラクターデータ * @return 作成されたメインフレーム * @throws IOException * 例外 */ public static MainFrame openProfile(CharacterData characterData) throws IOException { if (characterData == null || !characterData.isValid()) { throw new IOException("開くことのできないキャラクターデータです。:" + characterData); } // キャラクターデータのロード loadCharacterData(characterData); loadFavorites(characterData); // メインフレームを構築 MainFrame newFrame = new MainFrame(characterData); // 最後に使ったプロファイルとして登録 saveRecent(characterData); return newFrame; } /** * キャラクター定義編集用ダイアログを生成して返す. * * @author seraphy */ private interface ProfileEditorDialogFactory { ProfileEditDialog create(CharacterData characterData); } /** * キャラクター定義を編集する.
* * @param parent * 親ダイアログ、もしくはnull * @param characterData * キャラクター定義(参照のみ、変更されない.) * @return 編集されたキャラクター定義、もしくはキャンセルされた場合はnull * @throws IOException * 失敗 */ public static CharacterData editProfile(final JDialog parent, CharacterData characterData) throws IOException { return internalEditProfile(characterData, new ProfileEditorDialogFactory() { public ProfileEditDialog create(CharacterData characterData) { return new ProfileEditDialog(parent, characterData); } }); } /** * キャラクター定義を編集する.
* * @param parent * 親フレーム、もしくはnull * @param characterData * キャラクター定義(参照のみ、変更されない.) * @return 編集されたキャラクター定義、もしくはキャンセルされた場合はnull * @throws IOException * 失敗 */ public static CharacterData editProfile(final JFrame parent, CharacterData characterData) throws IOException { return internalEditProfile(characterData, new ProfileEditorDialogFactory() { public ProfileEditDialog create(CharacterData characterData) { return new ProfileEditDialog(parent, characterData); } }); } /** * キャラクター定義を編集する.
* * @param characterData * キャラクター定義(参照のみ、変更されない.) * @param dialogFactory * キャラクター定義編集ダイアログを生成するファクトリ * @return 編集されたキャラクター定義、もしくはキャンセルされた場合はnull * @throws IOException * 失敗 */ private static CharacterData internalEditProfile(CharacterData characterData, ProfileEditorDialogFactory dialogFactory) throws IOException { if (characterData == null || !characterData.isValid()) { throw new IOException("開くことのできないキャラクターデータです。:" + characterData); } // キャラクターデータのコピーを作成する.(プリセットも含む) CharacterData original = characterData.duplicateBasicInfo(true); original.clearPartsSets(true); // プリセット以外のパーツセットはクリアする. try { loadFavorites(original); } catch (IOException ex) { ErrorMessageHelper.showErrorDialog(null, ex); // Favoritesの読み込みに失敗しても継続する. } // 編集用ダイアログを構築して開く ProfileEditDialog editDlg = dialogFactory.create(original); editDlg.setVisible(true); // 編集結果を得る. CharacterData newCd = editDlg.getResult(); if (newCd == null) { // キャンセルされた場合 return null; } // 保存する. CharacterDataPersistent persist = CharacterDataPersistent.getInstance(); persist.updateProfile(newCd); persist.saveFavorites(newCd); return newCd; } /** * 最後にしようしたプロファイル、それがなければデフォルトプロファイルを開いて、そのメインフレームを返す. * * @return メインフレーム * @throws IOException * 開けなかった場合 */ public static MainFrame openDefaultProfile() throws IOException { CharacterDataPersistent persistent = CharacterDataPersistent.getInstance(); CharacterData characterData; // 最後に使用したプロファイルのロードを試行する. try { characterData = loadRecent(); if (characterData != null) { // キャラクターデータを読み込む loadCharacterData(characterData); loadFavorites(characterData); } } catch (Exception ex) { ErrorMessageHelper.showErrorDialog(null, ex); characterData = null; } // 最後に使用したプロファイルの記録がないか、プロファイルのロードに失敗した場合は // プロファイル一覧から最初のプロファイルを選択する. if (characterData == null) { final ArrayList profiles = new ArrayList(); Future future = persistent .listProfileAsync(new ListProfileCallback() { public boolean receiveCharacterData( CharacterData characterData) { try { loadCharacterData(characterData); loadFavorites(characterData); synchronized (profiles) { profiles.add(characterData); } // 読み込めたものが1つでもあれば、以降は不要なので打ち切り return false; } catch (Exception ex) { logger.log(Level.SEVERE, "プロファイルのロードに失敗しました。" + characterData, ex); // プロファイルの読み込みに失敗した場合は次を試行する. return true; } } public boolean occureException(File baseDir, Exception ex) { logger.log(Level.WARNING, "invalid profile. :" + baseDir, ex); // エラーでも継続する return true; } }); try { future.get(); synchronized (profiles) { if (!profiles.isEmpty()) { characterData = profiles.get(0); } } } catch (Exception ex) { logger.log(Level.SEVERE, "プロファイルのロードに失敗しました。" + ex, ex); } } // プロファイルが一個もなければ、デフォルトのプロファイルの生成を試行する. if (characterData == null) { logger.info("オープンできるプロファイルがないため、新規プロファイルを作成します。"); try { CharacterDataDefaultProvider defProv = new CharacterDataDefaultProvider(); characterData = defProv .createDefaultCharacterData(DefaultCharacterDataVersion.V3); persistent.createProfile(characterData); } catch (IOException ex) { // デフォルトのプロファイルが作成できないことは致命的であるが、 // アプリケーションを起動させるために継続する. logger.log(Level.SEVERE, "default profile creation failed.", ex); // キャラクター定義として無効なダミーのインスタンスを生成して返す. // 何もできないが、メインフレームを空の状態で表示させることは可能. characterData = new CharacterData(); } } // 最後に使用したプロファイルとして記録 saveRecent(characterData); // メインフレームを生成して返す return new MainFrame(characterData); } /** * キャラクターデータに、パーツデータをロードする.
* お気に入りはロードされないので、必要ならば、このあとで{@link #loadFavorites(CharacterData)}を呼び出す.
* * @param characterData * @throws IOException * 開けなかった場合 */ public static void loadCharacterData(final CharacterData characterData) throws IOException { if (characterData != null && characterData.isValid()) { final PartsInfoXMLReader xmlReader = new PartsInfoXMLReader(); PartsDataLoaderFactory loaderFactory = PartsDataLoaderFactory.getInstance(); PartsDataLoader loader = loaderFactory.createPartsLoader(characterData.getDocBase()); PartsDataLoader colorGroupInfoDecorater = new PartsSpecDecorateLoader(loader, characterData.getColorGroups()); PartsManageDataDecorateLoader partsMngDecorater = new PartsManageDataDecorateLoader(colorGroupInfoDecorater, new PartsManageDataDecorateLoader.PartsManageDataFactory() { public PartsManageData createPartsManageData() { try { return xmlReader .loadPartsManageData(characterData .getDocBase()); } catch (Exception ex) { logger.log(Level.WARNING, "parts-info.xml loading failed.", ex); return new PartsManageData(); } } }); characterData.loadPartsData(partsMngDecorater); } } /** * キャラクターデータに、お気に入りをロードする.
* * @param characterData * @throws IOException * 開けなかった場合 */ public static void loadFavorites(final CharacterData characterData) throws IOException { if (characterData != null && characterData.isValid()) { final CharacterDataPersistent persistent = CharacterDataPersistent.getInstance(); persistent.loadFavorites(characterData); } } /** * 最後に使用したキャラクターデータとして記録する. * * @param characterData * @throws IOException * 保存できなった場合 */ public static void saveRecent(CharacterData characterData) throws IOException { RecentDataPersistent recentPersist = RecentDataPersistent.getInstance(); recentPersist.saveRecent(characterData); } /** * 最後に使用したキャラクターデータを取得する. * * @return キャラクターデータ。最後に使用したデータが存在しない場合はnull * @throws IOException * 読み込みに失敗した場合 */ public static CharacterData loadRecent() throws IOException { RecentDataPersistent recentPersist = RecentDataPersistent.getInstance(); return recentPersist.loadRecent(); } } CharacterManaJ/src/charactermanaj/ui/progress/0000755000175000017500000000000012560206305021603 5ustar paulliupaulliuCharacterManaJ/src/charactermanaj/ui/progress/WorkerException.java0000644000175000017500000000077312560206305025605 0ustar paulliupaulliupackage charactermanaj.ui.progress; /** * プログレスダイアログのワーカースレッド実行中に例外が発生した場合 * @author seraphy */ public class WorkerException extends Exception { private static final long serialVersionUID = -8315947965963588713L; public WorkerException() { super(); } public WorkerException(String message) { super(message); } public WorkerException(String message, Throwable cause) { super(message, cause); } } CharacterManaJ/src/charactermanaj/ui/progress/WorkerWithProgessDialog.java0000644000175000017500000002245712560206305027250 0ustar paulliupaulliupackage charactermanaj.ui.progress; import java.awt.BorderLayout; import java.awt.Component; import java.awt.Container; import java.awt.Dimension; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.lang.Thread.UncaughtExceptionHandler; import javax.swing.BorderFactory; import javax.swing.JDialog; import javax.swing.JFrame; import javax.swing.JLabel; import javax.swing.JProgressBar; import javax.swing.JRootPane; import javax.swing.SwingUtilities; import javax.swing.Timer; import javax.swing.border.BevelBorder; /** * ワーカースレッドの実行中、プログレスを表示するモーダルダイアログ.
* ワーカースレッドの実行が完了するとダイアログは自動的に閉じられる.
* モーダルダイアログであるため、ワーカースレッドの実行中はユーザはUIを操作することはできない.
* @author seraphy * * @param ワーカーの処理結果の戻り値の型 */ public class WorkerWithProgessDialog extends JDialog { private static final long serialVersionUID = 1L; /** * ワーカースレッドが停止したことを示すフラグ */ private volatile boolean exitThread; /** * ワーカースレッドの戻り値 */ private volatile T result; /** * ワーカースレッドが例外により終了した場合の例外 */ private volatile Throwable occuredException; /** * ワーカースレッド */ private Thread thread; /** * ワーカースレッドの状態を監視しプログレスに反映させるタイマー */ private Timer timer; /** * プログレスの更新頻度(タイマーのインターバル) */ private static int interval = 200; /** * 親フレームとワーカーを指定して構築する.
* @param parent 親フレーム * @param worker ワーカー */ public WorkerWithProgessDialog(JFrame parent, Worker worker) { super(parent, true); try { if (worker == null) { throw new IllegalArgumentException(); } initComponent(parent, worker); } catch (RuntimeException ex) { dispose(); throw ex; } } /** * 親ダイアログとワーカーを指定して構築する.
* @param parent 親フレーム * @param worker ワーカー */ public WorkerWithProgessDialog(JDialog parent, Worker worker) { super(parent, true); try { if (worker == null) { throw new IllegalArgumentException(); } initComponent(parent, worker); } catch (RuntimeException ex) { dispose(); throw ex; } } /** * コンポーネントの初期化 * @param parent 親フレームまたは親ダイアログ * @param worker ワーカー */ private void initComponent(Component parent, Worker worker) { // 閉じるボタンは無効 setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE); // リサイズ不可 setResizable(false); // ウィンドウ装飾なし (閉じるボタンやタイトルバーなども無し) setUndecorated(true); Container container = getContentPane(); // プログレスバー final JProgressBar progressBar = new JProgressBar(); progressBar.setIndeterminate(true); progressBar.setStringPainted(false); container.add(progressBar, BorderLayout.SOUTH); // デフォルトのラベル表示 String title = "please wait for a while."; final JLabel lblCaption = new JLabel(title); container.add(lblCaption, BorderLayout.NORTH); // ウィンドウ枠 JRootPane rootPane = getRootPane(); rootPane.setBorder(BorderFactory.createCompoundBorder( BorderFactory.createBevelBorder(BevelBorder.RAISED), BorderFactory.createEmptyBorder(5, 5, 5, 5)) ); // 親ウィンドウの幅の70% Dimension dim = progressBar.getPreferredSize(); dim.width = (int)(parent.getWidth() * 0.7); progressBar.setPreferredSize(dim); // パックする. pack(); // 親の中央に表示 setLocationRelativeTo(parent); // ワーカースレッドとプログレスダイアログとの状態通信用オブジェクト final ProgressInfoHolder progressHandle = new ProgressInfoHolder() { @Override public synchronized void flush() { final String caption = getCaption(); final Boolean indeterminate = getIndeterminate(); final Integer progressMaximum = getProgressMaximum(); final Integer progressCurrent = getProgressCurrent(); if (caption != null || progressMaximum != null || progressCurrent != null || indeterminate != null) { SwingUtilities.invokeLater(new Runnable() { public void run() { // 設定されている値でプログレスダイアログに状態を反映する. if (caption != null) { lblCaption.setText(caption); } if (progressMaximum != null) { progressBar.setMaximum(progressMaximum.intValue()); } if (progressCurrent != null) { progressBar.setValue(progressCurrent.intValue()); } if (indeterminate != null) { progressBar.setIndeterminate(indeterminate.booleanValue()); progressBar.setStringPainted( !indeterminate.booleanValue()); } } }); } super.flush(); } }; // プログレスダイアログに状態を反映させるためのタイマー timer = new Timer(interval, new ActionListener() { public void actionPerformed(ActionEvent e) { if (exitThread || !thread.isAlive()) { // スレッドが終了していればスレッド停止を通知する. onExitWork(); } else { // スレッドが生きていれば、スレッドの進行状態を // プログレスダイアログに反映させる. progressHandle.flush(); } } }); // ワーカースレッドの構築. thread = new Thread(createJob(worker, progressHandle)); thread.setDaemon(true); // ワーカースレッドが予期せぬハンドルされていない例外により終了した場合のハンドラ. thread.setUncaughtExceptionHandler(new UncaughtExceptionHandler() { public void uncaughtException(Thread t, Throwable e) { occuredException = e; onExitWork(); } }); } /** * プログレスの表示間隔を取得する * @return 表示間隔 */ public static int getInterval() { return interval; } /** * プログレスの表示間隔を設定する * @param interval 表示間隔 */ public static void setInterval(int interval) { WorkerWithProgessDialog.interval = interval; } /** * ワーカーをラップするワーカースレッドのジョブを作成する.
* @param worker ワーカー * @param progressHandle 進行状態の通知ハンドル * @return ジョブ */ protected Runnable createJob(final Worker worker, final ProgressHandle progressHandle) { return new Runnable() { public void run() { try { try { worker.doWork(progressHandle); } catch (Throwable ex) { occuredException = ex; } } finally { onExitWork(); } } }; } /** * ワーカースレッドより、スレッドが終了したことを通知される.
* ワーカースレッド自身か、ワーカースレッドの例外ハンドラか、 * タイマーから呼び出されるため、2回以上呼び出される可能性がある.
*/ protected void onExitWork() { exitThread = true; SwingUtilities.invokeLater(new Runnable() { public void run() { // プログレスダイアログが表示されている場合、 // それを破棄する.(モーダルの解除) if (isDisplayable() && isVisible()) { dispose(); } } }); } /** * ワーカースレッドを開始し、プログレスダイアログを表示し、 * ワーカースレッドの完了まで待機する.
* @throws WorkerException ワーカースレッドが例外により終了した場合 */ public void startAndWait() throws WorkerException { // 初期化 result = null; occuredException = null; exitThread = false; // ワーカースレッドの開始 thread.start(); try { timer.start(); try { // モーダルダイアログの開始 // (モーダルダイアログが非表示されるまで制御を返さない.) setVisible(true); } finally { timer.stop(); } } finally { for (;;) { try { // ワーカースレッドの停止を待機する. thread.join(); break; } catch (InterruptedException ex) { // 割り込みされた場合は、ワーカースレッドを割り込みする. thread.interrupt(); } } } // ワーカースレッドが例外により終了した場合 // その例外を送出する. if (occuredException != null) { throw new WorkerException( "worker has failed." + occuredException.getMessage(), occuredException ); } } /** * ワーカースレッドの戻り値を取得する.
* 正常終了していない場合、または処理中の場合は意味を持たない.
* @return ワーカースレッドの戻り値 */ public T getResult() { return result; } } CharacterManaJ/src/charactermanaj/ui/progress/ProgressHandle.java0000644000175000017500000000155312560206305025372 0ustar paulliupaulliupackage charactermanaj.ui.progress; /** * ワーカースレッドからプログレスダイアログに状態を通知するためのインターフェイス.
* 中途半端な状態で反映されないように、複数のプロパティを設定する場合は * 同期をとること.
* @author seraphy */ public interface ProgressHandle { /** * 進行状態の最大値を設定する. * @param maximum */ void setProgressMaximum(int maximum); /** * 進行状態の現在値を設定する. * @param current */ void setProgressCurrent(int current); /** * 進行状態が不明であることを設定する. * @param indeterminate */ void setIndeterminate(boolean indeterminate); /** * キャプションを設定する. * @param caption */ void setCaption(String caption); } CharacterManaJ/src/charactermanaj/ui/progress/ProgressInfoHolder.java0000644000175000017500000000304012560206305026221 0ustar paulliupaulliupackage charactermanaj.ui.progress; /** * プログレスダイアログと、そのワーカースレッドの間で進行状態を通信するためのホルダ.
* @author seraphy */ public class ProgressInfoHolder implements ProgressHandle { /** * キャプション */ private String caption; /** * 進行状態不明フラグ. */ private Boolean indeterminate; /** * 進行状態の現在値 */ private Integer progressCurrent; /** * 進行状態の最大値 */ private Integer progressMaximum; public synchronized String getCaption() { return caption; } public synchronized void setCaption(String caption) { this.caption = caption; } public synchronized Boolean getIndeterminate() { return indeterminate; } public synchronized void setIndeterminate(boolean indeterminate) { this.indeterminate = indeterminate; } public synchronized Integer getProgressCurrent() { return progressCurrent; } public synchronized void setProgressCurrent(int progressCurrent) { this.progressCurrent = progressCurrent; } public synchronized Integer getProgressMaximum() { return progressMaximum; } public synchronized void setProgressMaximum(int progressMaximum) { this.progressMaximum = progressMaximum; } /** * 現在の状態で確定し、ただちに状態をリセットする.
*/ public synchronized void flush() { caption = null; indeterminate = null; progressCurrent = null; progressMaximum = null; } } CharacterManaJ/src/charactermanaj/ui/progress/Worker.java0000644000175000017500000000065112560206305023721 0ustar paulliupaulliupackage charactermanaj.ui.progress; /** * ワーカー.
* @author seraphy * * @param ワーカーの戻り型 */ public interface Worker { /** * ワーカーを実行する.
* * @param progressHandle 進行状態を通知するハンドル * @return 処理結果 * @throws Exception 何らかの失敗 */ T doWork(ProgressHandle progressHandle) throws Exception; } CharacterManaJ/src/charactermanaj/ui/RecentCharactersDir.java0000644000175000017500000001423312560206305024464 0ustar paulliupaulliupackage charactermanaj.ui; import java.io.BufferedInputStream; import java.io.BufferedOutputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.io.Serializable; import java.net.URI; import java.net.URISyntaxException; import java.util.ArrayList; import java.util.Collections; import java.util.Enumeration; import java.util.List; import java.util.Properties; import java.util.TreeMap; import charactermanaj.util.ConfigurationDirUtilities; /** * 最後に使用したキャラクターデータディレクトリと、その履歴情報.
* * @author seraphy */ public class RecentCharactersDir implements Serializable { private static final long serialVersionUID = -5274310741380875405L; /** * 使用したキャラクターデータディレクトリリストの保存先ファイル名 */ private static final String RECENT_CHARACTERDIRS_XML = "recent-characterdirs.xml"; /** * キャラクターデータディレクトリ一覧のプロパティキーのプレフィックス */ private static final String DIRS_PREFIX = "characterDataDirs."; /** * 最後に使用したディレクトリ */ private File lastUseCharacterDir; /** * 過去に使用したディレクトリ情報 */ private ArrayList recentCharacterDirs = new ArrayList(); /** * ディレクトリの問い合わせ不要フラグ. */ private boolean doNotAskAgain; /** * 使用したキャラクターディレクトリの履歴、古いもの順 * * @return */ public List getRecentCharacterDirs() { return recentCharacterDirs; } /** * 使用したキャラクターディレクトリの履歴、新しいもの順.
* 表示用であり、追加・削除・変更は不可. * * @return */ public List getRecentCharacterDirsOrderByNewly() { ArrayList dirs = new ArrayList(recentCharacterDirs); Collections.reverse(dirs); return Collections.unmodifiableList(dirs); } public void setLastUseCharacterDir(File lastUseCharacterDir) { this.lastUseCharacterDir = lastUseCharacterDir; } public File getLastUseCharacterDir() { return lastUseCharacterDir; } public void clrar() { doNotAskAgain = false; lastUseCharacterDir = null; recentCharacterDirs.clear(); } public boolean isDoNotAskAgain() { return doNotAskAgain; } public void setDoNotAskAgain(boolean doNotAskAgain) { this.doNotAskAgain = doNotAskAgain; } /** * ユーザーディレクトリ上の、過去に利用したキャラクターデータディレクトリの一覧をロードする.
* 存在しなければ空を返す. * * @return * @throws IOException */ public static RecentCharactersDir load() throws IOException { Properties props = new Properties(); // ユーザーディレクトリのルート上に最後に使ったファイルリストをxml形式で保存する. File userDataDir = ConfigurationDirUtilities.getUserDataDir(); File recentUseDirs = new File(userDataDir, RECENT_CHARACTERDIRS_XML); if (recentUseDirs.exists()) { InputStream is = new BufferedInputStream(new FileInputStream( recentUseDirs)); try { props.loadFromXML(is); } finally { is.close(); } } RecentCharactersDir inst = new RecentCharactersDir(); inst.doNotAskAgain = Boolean.parseBoolean(props .getProperty("doNotAskAgain")); String lastUseCharacterDataDir = props .getProperty("lastUseCharacterDataDir"); File lastUseCharacterDir = null; TreeMap dirsMap = new TreeMap(); try { if (lastUseCharacterDataDir != null && lastUseCharacterDataDir.trim().length() > 0) { lastUseCharacterDir = new File(new URI( lastUseCharacterDataDir)); } Enumeration enmKeys = props.propertyNames(); while (enmKeys.hasMoreElements()) { String key = (String) enmKeys.nextElement(); if (key.startsWith(DIRS_PREFIX)) { String value = props.getProperty(key); if (value != null && value.trim().length() > 0) { dirsMap.put(key, new File(new URI(value))); } } } } catch (URISyntaxException ex) { IOException ex2 = new IOException("invalid file name: " + ex); ex2.initCause(ex); throw ex2; } inst.lastUseCharacterDir = lastUseCharacterDir; for (File dir : dirsMap.values()) { if (!dir.equals(lastUseCharacterDir)) { inst.recentCharacterDirs.add(dir); } } if (lastUseCharacterDir != null) { inst.recentCharacterDirs.add(lastUseCharacterDir); } return inst; } /** * 利用したキャラクターデータディレクトリの履歴情報をユーザーディレクトリに保存する. * * @throws IOException */ public void saveRecents() throws IOException { Properties props = new Properties(); // 最後に使用したディレクトリ if (lastUseCharacterDir != null) { props.put("lastUseCharacterDataDir", lastUseCharacterDir.toURI() .toString()); // 最後に使用したディレクトリを末尾に移動 recentCharacterDirs.remove(lastUseCharacterDir); recentCharacterDirs.add(lastUseCharacterDir); } else { props.put("lastUseCharacterDataDir", ""); } // ディレクトリリスト int idx = 0; for (File dir : recentCharacterDirs) { String key = DIRS_PREFIX + String.format("%04d", idx); props.put(key, dir.toURI().toString()); idx++; } // ディレクトリを再度問い合わせないか? props.put("doNotAskAgain", doNotAskAgain ? "true" : "false"); // ユーザーディレクトリのルート上に最後に使ったファイルリストをxml形式で保存する. File userDataDir = ConfigurationDirUtilities.getUserDataDir(); File recentUseDirs = new File(userDataDir, RECENT_CHARACTERDIRS_XML); OutputStream os = new BufferedOutputStream(new FileOutputStream( recentUseDirs)); try { props.storeToXML(os, "recent-characterdirs"); } finally { os.close(); } } }CharacterManaJ/src/charactermanaj/ui/AppConfigDialog.java0000644000175000017500000004064312560206305023577 0ustar paulliupaulliupackage charactermanaj.ui; import java.awt.BorderLayout; import java.awt.Color; import java.awt.Component; import java.awt.Container; import java.awt.GridBagConstraints; import java.awt.GridBagLayout; import java.awt.Insets; import java.awt.Toolkit; import java.awt.event.ActionEvent; import java.awt.event.KeyEvent; import java.awt.event.MouseEvent; import java.awt.event.WindowAdapter; import java.awt.event.WindowEvent; import java.io.File; import java.util.Collections; import java.util.EnumSet; import java.util.Properties; import java.util.Set; import java.util.logging.Level; import java.util.logging.Logger; import javax.swing.AbstractAction; import javax.swing.Action; import javax.swing.ActionMap; import javax.swing.BorderFactory; import javax.swing.Box; import javax.swing.InputMap; import javax.swing.JButton; import javax.swing.JCheckBox; import javax.swing.JComponent; import javax.swing.JDialog; import javax.swing.JFrame; import javax.swing.JLabel; import javax.swing.JOptionPane; import javax.swing.JPanel; import javax.swing.JRootPane; import javax.swing.JScrollPane; import javax.swing.JTable; import javax.swing.KeyStroke; import javax.swing.table.TableCellRenderer; import javax.swing.table.TableColumnModel; import charactermanaj.Main; import charactermanaj.model.AppConfig; import charactermanaj.ui.model.AbstractTableModelWithComboBoxModel; import charactermanaj.util.ConfigurationDirUtilities; import charactermanaj.util.DesktopUtilities; import charactermanaj.util.ErrorMessageHelper; import charactermanaj.util.LocalizedResourcePropertyLoader; import charactermanaj.util.SetupLocalization; /** * アプリケーション設定ダイアログ * * @author seraphy */ public class AppConfigDialog extends JDialog { private static final long serialVersionUID = 1L; private static final Logger logger = Logger.getLogger(AppConfigDialog.class.getName()); private AppConfigTableModel appConfigTableModel; private JTable appConfigTable; private JCheckBox chkResetDoNotAskAgain; private RecentCharactersDir recentCharactersDir; public AppConfigDialog(JFrame parent) { super(parent, true); try { setDefaultCloseOperation(JDialog.DO_NOTHING_ON_CLOSE); addWindowListener(new WindowAdapter() { @Override public void windowClosing(WindowEvent e) { onClose(); } }); initComponent(); loadData(); } catch (RuntimeException ex) { logger.log(Level.SEVERE, "appConfig construct failed.", ex); dispose(); throw ex; } } private void initComponent() { Properties strings = LocalizedResourcePropertyLoader.getCachedInstance() .getLocalizedProperties("languages/appconfigdialog"); setTitle(strings.getProperty("title")); Container contentPane = getContentPane(); contentPane.setLayout(new BorderLayout()); // buttons JPanel btnPanel = new JPanel(); btnPanel.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 45)); GridBagLayout btnPanelLayout = new GridBagLayout(); btnPanel.setLayout(btnPanelLayout); GridBagConstraints gbc = new GridBagConstraints(); Action actApply = new AbstractAction(strings.getProperty("btn.apply")) { private static final long serialVersionUID = 1L; public void actionPerformed(ActionEvent e) { onUpdate(); } }; Action actCancel = new AbstractAction(strings.getProperty("btn.cancel")) { private static final long serialVersionUID = 1L; public void actionPerformed(ActionEvent e) { onClose(); } }; Action actLocalization = new AbstractAction(strings.getProperty("btn.setupLocalization")) { private static final long serialVersionUID = 1L; public void actionPerformed(ActionEvent e) { onSetupLocalization(); } }; chkResetDoNotAskAgain = new JCheckBox(strings.getProperty("chk.askForCharactersDir")); gbc.gridx = 0; gbc.gridy = 0; gbc.gridheight = 1; gbc.gridwidth = 3; gbc.anchor = GridBagConstraints.WEST; gbc.fill = GridBagConstraints.NONE; gbc.insets = new Insets(0, 0, 0, 0); gbc.ipadx = 0; gbc.ipady = 0; gbc.weightx = 1.; gbc.weighty = 0.; btnPanel.add(chkResetDoNotAskAgain, gbc); gbc.gridx = 0; gbc.gridy = 1; gbc.gridheight = 1; gbc.gridwidth = 3; gbc.anchor = GridBagConstraints.WEST; gbc.fill = GridBagConstraints.NONE; gbc.insets = new Insets(3, 3, 3, 3); gbc.ipadx = 0; gbc.ipady = 0; gbc.weightx = 1.; gbc.weighty = 0.; btnPanel.add(new JButton(actLocalization), gbc); gbc.gridx = 0; gbc.gridy = 2; gbc.gridheight = 1; gbc.gridwidth = 1; gbc.fill = GridBagConstraints.BOTH; gbc.weightx = 1.; gbc.weighty = 0.; btnPanel.add(Box.createHorizontalGlue(), gbc); gbc.gridx = Main.isLinuxOrMacOSX() ? 2 : 1; gbc.weightx = 0.; JButton btnApply = new JButton(actApply); btnPanel.add(btnApply, gbc); gbc.gridx = Main.isLinuxOrMacOSX() ? 1 : 2; gbc.weightx = 0.; JButton btnCancel = new JButton(actCancel); btnPanel.add(btnCancel, gbc); add(btnPanel, BorderLayout.SOUTH); setSize(350, 400); setLocationRelativeTo(getParent()); // Notes JLabel lblCaution = new JLabel(strings.getProperty("caution"), JLabel.CENTER); lblCaution.setBorder(BorderFactory.createEmptyBorder(3, 3, 3, 3)); lblCaution.setForeground(Color.red); contentPane.add(lblCaution, BorderLayout.NORTH); // Model appConfigTableModel = new AppConfigTableModel(); // JTable AppConfig appConfig = AppConfig.getInstance(); final Color invalidBgColor = appConfig.getInvalidBgColor(); appConfigTable = new JTable(appConfigTableModel) { private static final long serialVersionUID = 1L; @Override public Component prepareRenderer(TableCellRenderer renderer, int row, int column) { Component comp = super.prepareRenderer(renderer, row, column); AppConfigRowModel rowModel = appConfigTableModel.getRow(row); if (rowModel.isRejected()) { comp.setBackground(invalidBgColor); } else { if (isCellSelected(row, column)) { comp.setBackground(getSelectionBackground()); } else { comp.setBackground(getBackground()); } } return comp; } @Override public String getToolTipText(MouseEvent event) { int row = rowAtPoint(event.getPoint()); int col = columnAtPoint(event.getPoint()); if (col == 0) { AppConfigRowModel rowModel = appConfigTableModel.getRow(row); return rowModel.getDisplayName(); } return super.getToolTipText(event); } }; appConfigTable.setAutoResizeMode(JTable.AUTO_RESIZE_LAST_COLUMN); appConfigTable.setShowGrid(true); appConfigTable.setGridColor(AppConfig.getInstance().getGridColor()); appConfigTable.putClientProperty("terminateEditOnFocusLost", Boolean.TRUE); appConfigTableModel.adjustColumnModel(appConfigTable.getColumnModel()); JScrollPane appConfigTableSP = new JScrollPane(appConfigTable); appConfigTableSP.setBorder(BorderFactory.createCompoundBorder( BorderFactory.createEmptyBorder(0, 3, 0, 3), BorderFactory.createTitledBorder(strings.getProperty("table.caption"))) ); appConfigTableSP.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS); contentPane.add(appConfigTableSP, BorderLayout.CENTER); // RootPane Toolkit tk = Toolkit.getDefaultToolkit(); JRootPane rootPane = getRootPane(); InputMap im = rootPane.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW); ActionMap am = rootPane.getActionMap(); im.put(KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0), "closeAppConfigDialog"); im.put(KeyStroke.getKeyStroke(KeyEvent.VK_W, tk.getMenuShortcutKeyMask()), "closeAppConfigDialog"); am.put("closeAppConfigDialog", actCancel); // 保存先が無効であれば適用ボタンを有効にしない. boolean enableSave = !appConfig.getPrioritySaveFileList().isEmpty(); btnApply.setEnabled(enableSave); } private void loadData() { Properties original = AppConfig.getInstance().getProperties(); appConfigTableModel.initModel(original); try { recentCharactersDir = RecentCharactersDir.load(); if (recentCharactersDir != null) { File lastUseCharactersDir = recentCharactersDir.getLastUseCharacterDir(); boolean enableLastUseCharacterDir = lastUseCharactersDir != null && lastUseCharactersDir.isDirectory(); boolean doNotAskAgain = enableLastUseCharacterDir && recentCharactersDir.isDoNotAskAgain(); chkResetDoNotAskAgain.setEnabled(enableLastUseCharacterDir); chkResetDoNotAskAgain.setSelected(!doNotAskAgain); } } catch (Exception ex) { recentCharactersDir = null; logger.log(Level.WARNING, "RecentCharactersDir load failed.", ex); } } /** * ローカライズリソースをユーザディレクトリ上に展開する. */ protected void onSetupLocalization() { Properties strings = LocalizedResourcePropertyLoader.getCachedInstance() .getLocalizedProperties("languages/appconfigdialog"); if (JOptionPane.showConfirmDialog(this, strings.getProperty("setupLocalization"), strings.getProperty("confirm.setupLocalization.caption"), JOptionPane.OK_CANCEL_OPTION, JOptionPane.WARNING_MESSAGE) != JOptionPane.OK_OPTION) { return; } try { File baseDir = ConfigurationDirUtilities.getUserDataDir(); SetupLocalization setup = new SetupLocalization(baseDir); setup.setupToLocal( EnumSet.allOf(SetupLocalization.Resources.class), true); File resourceDir = setup.getResourceDir(); DesktopUtilities.open(resourceDir); } catch (Exception ex) { ErrorMessageHelper.showErrorDialog(this, ex); } } protected void onClose() { if (appConfigTableModel.isModified()) { Properties strings = LocalizedResourcePropertyLoader.getCachedInstance() .getLocalizedProperties("languages/appconfigdialog"); if (JOptionPane.showConfirmDialog(this, strings.getProperty("confirm.close"), strings.getProperty("confirm.close.caption"), JOptionPane.YES_NO_OPTION, JOptionPane.QUESTION_MESSAGE) != JOptionPane.YES_OPTION) { return; } } dispose(); } protected void onUpdate() { if (appConfigTable.isEditing()) { // 編集中ならば許可しない. Toolkit tk = Toolkit.getDefaultToolkit(); tk.beep(); return; } Properties strings = LocalizedResourcePropertyLoader.getCachedInstance() .getLocalizedProperties("languages/appconfigdialog"); // 編集されたプロパティを取得する. Properties props = appConfigTableModel.getProperties(); // 編集されたプロパティが適用可能か検証する. Set rejectNames = AppConfig.checkProperties(props); if (!rejectNames.isEmpty()) { // エラーがある場合 appConfigTableModel.setRejectNames(rejectNames); JOptionPane.showMessageDialog(this, strings.getProperty("error.message"), strings.getProperty("error.caption"), JOptionPane.ERROR_MESSAGE); return; } try { // アプリケーション設定を更新し、保存する. AppConfig appConfig = AppConfig.getInstance(); appConfig.update(props); appConfig.saveConfig(); // キャラクターデータディレクトリの起動時の選択 if (chkResetDoNotAskAgain.isEnabled()) { boolean doNotAskAgain = !chkResetDoNotAskAgain.isSelected(); if (doNotAskAgain != recentCharactersDir.isDoNotAskAgain()) { recentCharactersDir.setDoNotAskAgain(doNotAskAgain); recentCharactersDir.saveRecents(); } } } catch (Exception ex) { ErrorMessageHelper.showErrorDialog(this, ex); return; } // アプリケーションの再起動が必要なことを示すダイアログを表示する. String message = strings.getProperty("caution"); JOptionPane.showMessageDialog(this, message); dispose(); } } class AppConfigRowModel implements Comparable { private Properties target; private String key; private String displayName; private boolean rejected; public AppConfigRowModel(Properties target, String key, String displayName) { this.target = target; this.key = key; this.displayName = displayName; } @Override public int hashCode() { return key == null ? 0 : key.hashCode(); } @Override public boolean equals(Object obj) { if (obj == this) { return true; } if (obj != null && obj instanceof AppConfigRowModel) { AppConfigRowModel o = (AppConfigRowModel) obj; return key == null ? o.key == null : key.equals(o.key); } return false; } public int compareTo(AppConfigRowModel o) { if (o == this) { return 0; } int ret = displayName.compareTo(o.displayName); if (ret == 0) { ret = key.compareTo(o.key); } return ret; } public String getKey() { return key; } public void setValue(String value) { if (value == null) { value = ""; } target.setProperty(key, value); } public String getValue() { return target.getProperty(key); } public String getDisplayName() { int sep = displayName.indexOf(';'); if (sep >= 0) { return displayName.substring(sep + 1); } return displayName; } public boolean isRejected() { return rejected; } public void setRejected(boolean rejected) { this.rejected = rejected; } } class AppConfigTableModel extends AbstractTableModelWithComboBoxModel { private static final long serialVersionUID = 1L; private static final String[] COLUMN_NAMES; private static final int[] COLUMN_WIDTHS; private Properties target = new Properties(); private Properties original; static { Properties strings = LocalizedResourcePropertyLoader.getCachedInstance() .getLocalizedProperties("languages/appconfigdialog"); COLUMN_NAMES = new String[] { strings.getProperty("column.key"), strings.getProperty("column.value"), }; COLUMN_WIDTHS = new int[] { Integer.parseInt(strings.getProperty("column.key.width")), Integer.parseInt(strings.getProperty("column.value.width")), }; } public void initModel(Properties original) { clear(); target.clear(); this.original = original; if (original != null) { target.putAll(original); Properties strings = LocalizedResourcePropertyLoader.getCachedInstance() .getLocalizedProperties("languages/appconfigdialog"); for (Object key : target.keySet()) { String displayName = strings.getProperty((String) key); if (displayName == null || displayName.length() == 0) { displayName = (String) key; } AppConfigRowModel rowModel = new AppConfigRowModel(target, (String) key, displayName); addRow(rowModel); } } sort(); } public void sort() { Collections.sort(elements); fireTableDataChanged(); } public void setRejectNames(Set rejectNames) { for (AppConfigRowModel rowModel : elements) { String key = rowModel.getKey(); boolean rejected = (rejectNames != null && rejectNames.contains(key)); rowModel.setRejected(rejected); } fireTableDataChanged(); } /** * 編集されているか? * * @return 編集されていればtrue、そうでなければfalse */ public boolean isModified() { if (original == null) { return true; } return !original.equals(target); } public Properties getProperties() { return target; } public int getColumnCount() { return COLUMN_NAMES.length; } @Override public Class getColumnClass(int columnIndex) { return String.class; } @Override public String getColumnName(int column) { return COLUMN_NAMES[column]; } @Override public boolean isCellEditable(int rowIndex, int columnIndex) { if (columnIndex == 1) { return true; } return false; } public Object getValueAt(int rowIndex, int columnIndex) { AppConfigRowModel rowModel = getRow(rowIndex); switch (columnIndex) { case 0: return rowModel.getDisplayName(); case 1: return rowModel.getValue(); } return ""; } @Override public void setValueAt(Object aValue, int rowIndex, int columnIndex) { AppConfigRowModel rowModel = getRow(rowIndex); if (columnIndex == 1) { rowModel.setValue((String) aValue); fireTableCellUpdated(rowIndex, columnIndex); } } public void adjustColumnModel(TableColumnModel columnModel) { int mx = columnModel.getColumnCount(); for (int idx = 0; idx < mx; idx++) { columnModel.getColumn(idx).setWidth(COLUMN_WIDTHS[idx]); } } } CharacterManaJ/src/charactermanaj/ui/MenuData.java0000644000175000017500000000667212560206305022313 0ustar paulliupaulliupackage charactermanaj.ui; import java.awt.event.ActionListener; import java.util.AbstractCollection; import java.util.ArrayList; import java.util.Iterator; import javax.swing.JMenuItem; import javax.swing.KeyStroke; import charactermanaj.Main; public class MenuData extends AbstractCollection { private String name; private boolean checkbox; private String text; private Character mnemonic; private String mnemonicDisp; private boolean ignoreMacOSX; private String shortcutKey; private ActionListener actionListener; private ArrayList children = new ArrayList(); public MenuData() { this(null, false, null, null, false, null, null); } public MenuData(String text, boolean checkbox, Character mnemonic, String mnemonicDisp, boolean ignoreMacOSX, String shortcutKey, ActionListener actionListener) { this.text = text; this.checkbox = checkbox; this.mnemonic = mnemonic; this.mnemonicDisp = mnemonicDisp; this.ignoreMacOSX = ignoreMacOSX; this.shortcutKey = shortcutKey; this.actionListener = actionListener; } public void setName(String name) { this.name = name; } public String getName() { return name; } public void setCheckbox(boolean checkbox) { this.checkbox = checkbox; } public boolean isCheckbox() { return checkbox; } public String getText() { return text; } public void setText(String text) { this.text = text; } public Character getMnemonic() { return mnemonic; } public void setMnemonic(Character mnemonic) { this.mnemonic = mnemonic; } public String getMnemonicDisp() { return mnemonicDisp; } public void setMnimonicDisp(String mnemonicDisp) { this.mnemonicDisp = mnemonicDisp; } public boolean isIgnoreMacOSX() { return ignoreMacOSX; } public void setIgnoreMacOSX(boolean ignoreMacOSX) { this.ignoreMacOSX = ignoreMacOSX; } public ActionListener getActionListener() { return actionListener; } public void setActionListener(ActionListener actionListener) { this.actionListener = actionListener; } public String getShortcutKey() { return shortcutKey; } public void setShortcutKey(String shortcutKey) { this.shortcutKey = shortcutKey; } @Override public int size() { return children.size(); } @Override public Iterator iterator() { return children.iterator(); } @Override public boolean add(MenuData o) { return children.add(o); } public boolean makeMenu(JMenuItem menu) { if (! isIgnoreMacOSX() || ! Main.isMacOSX()) { if (Main.isMacOSX()) { menu.setText(getText()); } else { Character mnemonic = getMnemonic(); String mnemonicDisp =getMnemonicDisp(); if (mnemonicDisp == null) { mnemonicDisp = ""; } menu.setName(getName()); menu.setText(getText() + mnemonicDisp); if (mnemonic != null) { menu.setMnemonic(mnemonic); } } if (actionListener != null) { menu.addActionListener(actionListener); } if (shortcutKey != null && shortcutKey.length() > 0) { if (Main.isMacOSX()) { shortcutKey = shortcutKey.replace("?", "meta"); } else { shortcutKey = shortcutKey.replace("?", "control"); } KeyStroke ks = KeyStroke.getKeyStroke(shortcutKey); if (ks != null) { menu.setAccelerator(ks); } } return true; } return false; } } CharacterManaJ/src/charactermanaj/ui/scrollablemenu/0000755000175000017500000000000012560206305022746 5ustar paulliupaulliuCharacterManaJ/src/charactermanaj/ui/scrollablemenu/ScrollableMenuEventListener.java0000644000175000017500000000077312560206305031237 0ustar paulliupaulliupackage 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); } CharacterManaJ/src/charactermanaj/ui/scrollablemenu/arrow-up.png0000644000175000017500000000372512560206305025237 0ustar paulliupaulliuPNG  IHDR iCCPICC Profile(UoT?o\?US[IB*unS6mUo xB ISA$=t@hpS]Ƹ9w>5@WI`]5;V! A'@{N\..ƅG_!7suV$BlW=}i; F)A<.&Xax,38S(b׵*%31l #O-zQvaXOP5o6Zz&⻏^wkI/#&\%x/@{7S މjPh͔&mry>k7=ߪB#@fs_{덱п0-LZ~%Gpˈ{YXf^+_s-T>D@קƸ-9!r[2]3BcnCs?>*ԮeD|%4` :X2 pQSLPRaeyqĘ י5Fit )CdL$o$rpӶb>4+̹F_{Яki+x.+B.{L<۩ =UHcnf<>F ^e||pyv%b:iX'%8Iߔ? rw[vITVQN^dpYI"|#\cz[2M^S0[zIJ/HHȟ- Ic Adobe ImageReady PTaIDAT8œJPƿDpk.-tw"F.͜$-]X"}Ь*$Vq:I4|?9)miV f.3Gn̑UJ{~"K) /X{~uuɞpP<&DҰ"Z]݁m!DҐRֈH 7Nu]7͖J8?ku]R1Gl"Lt66r;xx|Zn7WRɘa~AB\:`8bj`8B:m@K\%Փx `_aF (0).3?X `1dD# T˰>IENDB`CharacterManaJ/src/charactermanaj/ui/scrollablemenu/JScrollableMenu.java0000644000175000017500000003050312560206305026633 0ustar paulliupaulliupackage charactermanaj.ui.scrollablemenu; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.net.URL; import java.util.ArrayList; import java.util.Collection; import javax.swing.ImageIcon; import javax.swing.JMenu; import javax.swing.JMenuItem; import javax.swing.Timer; import javax.swing.event.MenuEvent; import javax.swing.event.MenuListener; /** * スクロール可能メニュー. メニュー項目を設定したあと、{@link #initScroller() }でスクローラーを初期化します. つぎに、 * {@link #setScrollableItems(java.util.Collection) }で、スクロールさせる メニュー項目を設定します。 * 表示可能なアイテム数を調整するために、このメニューオブジェクトのselectedイベントの タイミングで、 * {@link #adjustMaxVisible(int) }を呼び出して表示項目数を調整します。 * * @author seraphy */ public class JScrollableMenu extends JMenu { /** * シリアライズバージョンID */ private static final long serialVersionUID = -5174737355715398136L; /** * 自動スクロールの既定の間隔(mSec). */ public static final int DEFAULT_REPEAT_DELAY = 200; /** * 高速自動スクロールの既定の間隔(mSec). */ public static final int DEFAULT_FAST_REPEAT_DELAY = 80; /** * 既定の最大表示アイテム数. */ public static final int DEFAULT_MAX_VISIBLE = 10; /** * リピートの閾値. スクロール数が、この数値を超えた場合に高速スクロール化する. */ public static final int DEFAULT_REPEAT_THRESHOLD = 3; /** * スクロールするアイテムのメニューの開始位置. */ private int _startPos; /** * 現在表示されている最初のアイテムのオフセット. */ private int _offset; /** * スクロールするメニュー項目のリスト. */ private ArrayList _menus = new ArrayList(); /** * 自動スクロールのためのタイマー. */ private Timer _timer; /** * 自動スクロールしたカウント. */ private int _scrollCount; /** * スクローラー(上). */ private JScrollerMenuItem _upButton; /** * スクローラー(下). */ private JScrollerMenuItem _downButton; /** * 通常スクロール時の自動スクロールの間隔. */ private int _delay = DEFAULT_REPEAT_DELAY; /** * 高速スクロール時の自動スクロールの間隔. */ private int _delayFast = DEFAULT_FAST_REPEAT_DELAY; /** * リピートの閾値. スクロール数が、この数値を超えた場合に高速スクロール化する. */ private int _repeat_threshold = DEFAULT_REPEAT_THRESHOLD; /** * 現在のスクロール方向を示すフラグ. タイマーハンドラの中で判定するため. nullの場合はスクロールしていないを示す. */ private Boolean _directionUp; /** * 最大表示アイテム数. */ private int maxVisible = DEFAULT_MAX_VISIBLE; /** * 表示名を省略してメニューを構築する. */ public JScrollableMenu() { this(""); } /** * 表示名を指定してメニューを構築する. * * @param name */ public JScrollableMenu(String name) { super(name); initScrollableMenu(); } /** * スクロール可能メニューの基本状態を設定する. */ private void initScrollableMenu() { // 自動スクロールのためのタイマ this._timer = new Timer(_delay, new ActionListener() { public void actionPerformed(ActionEvent e) { // スクロール doScroll(); // スクロール数をカウントアップ _scrollCount++; // スクロール数が閾値を超えたら高速化 if (_scrollCount >= _repeat_threshold) { ((Timer) e.getSource()).setDelay(_delayFast); } } }); addMenuListener(new MenuListener() { public void menuCanceled(MenuEvent e) { // このメニューがキャンセルされたときにスクロールを停止する JScrollableMenu.this._timer.stop(); _directionUp = null; } public void menuDeselected(MenuEvent e) { // このメニューが非選択状態になったときスクロールを停止する JScrollableMenu.this._timer.stop(); _directionUp = null; } public void menuSelected(MenuEvent e) { // 何もしない } }); } /** * スクローラーを初期化します. スクロールしない固定のメニュー項目などを設定したあとで、このメソッドを呼び出します. * すでに初期化されている場合は何もしません. */ public void initScroller() { if (_upButton != null || _downButton != null) { // すでに初期化済み removeAllScrollableItems(); return; } // スクローラー用ボタンアイコンを、このクラスからの相対パスで取得する. // (派生クラスからでもリソースの相対位置を変えないようにするためクラス名は固定とする) Class cls = JScrollableMenu.class; URL downPngURL = cls.getResource("arrow-down.png"); URL upPngURL = cls.getResource("arrow-up.png"); if (downPngURL == null || upPngURL == null) { throw new RuntimeException("png resource not found."); } ImageIcon iconDown = new ImageIcon(downPngURL); ImageIcon iconUp = new ImageIcon(upPngURL); // スクローラー用メニュー項目 _upButton = new JScrollerMenuItem(iconUp); _downButton = new JScrollerMenuItem(iconDown); // スクローラーのマウスイベントを受け取る final ScrollableMenuEventListener sc = new ScrollableMenuEventListener() { public void start(ScrollableMenuEvent e) { Boolean direction; if (e.getSource().equals(_upButton)) { // 上スクロール direction = Boolean.TRUE; } else { // 下スクロール direction = Boolean.FALSE; } // マウスクリックに対するスクロール doScroll(direction); // 自動スクロール開始 _scrollCount = 0; _timer.setDelay(_delay); _timer.start(); } public void end(ScrollableMenuEvent e) { // 自動スクロール停止 _timer.stop(); _directionUp = null; } }; _upButton.addScrollableMenuEventListener(sc); _downButton.addScrollableMenuEventListener(sc); add(_upButton); _startPos = getItemCount(); // upButtonの次のインデックス add(_downButton); // Mac OS Xのスクリーンメニューはスクロール可能なので、 // スクローラー用アイテムは非表示にして、デフォルトの機能に任せる。 // (逆に、スクリーンメニューではカスタムメニューは、うまく機能しない。) if (isScreenMenu()) { _upButton.setVisible(false); _downButton.setVisible(false); } } /** * 1行スクロールする * * @param direction * 上方向の場合はtrue、下の場合はfalse、停止はnull */ public void doScroll(Boolean direction) { _directionUp = direction; doScroll(); } /** * スクロールする. */ protected void doScroll() { // 現在の方向に応じて処理内容を分岐する. if (_directionUp != null) { if (_directionUp.booleanValue()) { scrollDown(); } else { scrollUp(); } } } /** * Mac OS Xのスクリーンメニューを使用しているか? * * @return 使用している場合はtrue */ public static boolean isScreenMenu() { String macScreenMenu = System.getProperty("apple.laf.useScreenMenuBar"); if (macScreenMenu != null && macScreenMenu.toLowerCase().equals("true")) { return true; } return false; } /** * 表示可能な最大行数を設定する. * * @param maxVisible * 最大行数 */ public void setMaxVisible(int maxVisible) { this.maxVisible = maxVisible; } /** * 表示可能な最大行数を取得する. * * @return 表示可能な最大行数 */ public int getMaxVisible() { return this.maxVisible; } /** * 画面の高さを指定して、表示可能なスクロールのアイテム数を算定し、 スクロールを表示し直す. * * @param height * 画面の高さを示す(px) */ public void adjustMaxVisible(int height) { int numOfItems = 0; if (_menus.size() > 0) { int heightPerItem = _menus.get(0).getPreferredSize().height; if (heightPerItem <= 0) { // 調整できないので何もしない. return; } numOfItems = height / heightPerItem; } numOfItems = numOfItems - (_startPos + 1 + 2); // 既存 + up/downボタン分 + // 上下余白を差し引く if (numOfItems < 0) { numOfItems = 1; } this.maxVisible = numOfItems; updateScrollableMenus(); } /** * 通常スクロールの間隔を取得する. * * @return 通常スクロールの間隔(mSec) */ public int getRepeatDelay() { return this._delay; } /** * 高速スクロールの間隔を取得する. * * @return 高速スクロールの間隔(mSec) */ public int getRepeatDelayFast() { return this._delayFast; } /** * 通常スクロールの間隔を設定する. * * @param delay * 通常スクロールの間隔(mSec) */ public void setRepeatDelay(int delay) { this._delay = delay; } /** * 高速スクロールの間隔を設定する. * * @param delayFast * 高速スクロールの間隔(mSec) */ public void setRepeatDelayFast(int delayFast) { this._delayFast = delayFast; } /** * スクロール可能アイテムを設定します. 既存のアイテムがある場合は、すべて登録解除されます. 事前にスクローラーは初期化済みでなければなりません. * * @param menus * メニューリスト */ public void setScrollableItems(Collection menus) { if (_upButton == null || _downButton == null) { throw new IllegalStateException("initScrollerを先に呼び出してください"); } removeAllScrollableItems(); if (menus != null) { for (JMenuItem item : menus) { int idx = _startPos + _menus.size(); this.add(item, idx); _menus.add(item); } } updateScrollableMenus(); } /** * 現在のスクロール可能アイテムをすべて除去します. */ public void removeAllScrollableItems() { for (JMenuItem item : _menus) { this.remove(item); } _menus.clear(); _offset = 0; } /** * 現在のスクロール範囲でスクロール可能項目を表示します. */ public void updateScrollableMenus() { boolean screenMenu = isScreenMenu(); int numOfItems = _menus.size(); for (int idx = 0; idx < numOfItems; idx++) { boolean visible = false; if (idx >= _offset && idx < (_offset + maxVisible) || screenMenu) { // メニュー項目が表示範囲内であれば表示、範囲外であれび非表示とする。 // ただし、Mac OS Xのスクリーンメニューであれば無条件にすべて表示。 visible = true; } _menus.get(idx).setVisible(visible); } } /** * 現在表示されているスクロール項目のオフセットを取得する. * * @return 現在のオフセット */ public int getOffset() { return _offset; } /** * 上方向にスクロールします. これ以上スクロールできない場合は何もしません. その場合、自動スクロール中であればスクロールは停止します. */ public void scrollUp() { int numOfItems = _menus.size(); int limit = numOfItems - maxVisible; if (limit < 0) { limit = 0; } _offset++; if (_offset >= limit) { _offset = limit; _timer.stop(); _directionUp = null; } updateScrollableMenus(); } /** * 下方向にスクロールします. これ以上スクロールできない場合は何もしません。 その場合、自動スクロール中であればスクロールは停止します。 */ public void scrollDown() { _offset--; if (_offset < 0) { _offset = 0; _timer.stop(); _directionUp = null; } updateScrollableMenus(); } } CharacterManaJ/src/charactermanaj/ui/scrollablemenu/ScrollableMenuEvent.java0000644000175000017500000000222112560206305027517 0ustar paulliupaulliupackage 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(); } } CharacterManaJ/src/charactermanaj/ui/scrollablemenu/arrow-down.png0000644000175000017500000000375712560206305025567 0ustar paulliupaulliuPNG  IHDR iCCPICC Profile(UoT?o\?US[IB*unS6mUo xB ISA$=t@hpS]Ƹ9w>5@WI`]5;V! A'@{N\..ƅG_!7suV$BlW=}i; F)A<.&Xax,38S(b׵*%31l #O-zQvaXOP5o6Zz&⻏^wkI/#&\%x/@{7S މjPh͔&mry>k7=ߪB#@fs_{덱п0-LZ~%Gpˈ{YXf^+_s-T>D@קƸ-9!r[2]3BcnCs?>*ԮeD|%4` :X2 pQSLPRaeyqĘ י5Fit )CdL$o$rpӶb>4+̹F_{Яki+x.+B.{L<۩ =UHcnf<>F ^e||pyv%b:iX'%8Iߔ? rw[vITVQN^dpYI"|#\cz[2M^S0[zIJ/HHȟ- Ic Adobe ImageReady PT{IDAT8ӻK#Qq""FDB  "Z NbH%(fe&iD5Q`?d' 5/}(;'PJ5&kuCϮW it:."5 -.-W} !pgײD"?ã#X8ռv@) D W4>3ف~2++m۞ "rW+ HE"i3\_o,Y޶y]iN=:19E ]`cme1^oD|op8::6:R)f}|F?`p\.e29z~ PAfr{-\f-<IENDB`CharacterManaJ/src/charactermanaj/ui/scrollablemenu/JScrollerMenuItem.java0000644000175000017500000000477512560206305027171 0ustar paulliupaulliupackage 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); } } } } CharacterManaJ/src/charactermanaj/ui/ArchiveFileDialog.java0000644000175000017500000000615512560206305024112 0ustar paulliupaulliupackage charactermanaj.ui; import java.awt.Component; import java.io.File; import javax.swing.filechooser.FileFilter; /** * エクスポート・インポート用ファイルダイアログ * @author seraphy * */ public class ArchiveFileDialog { /** * 最後に使ったディレクトリ */ protected File lastUsedDir; /** * 最後に使用したフィルタ */ protected FileFilter lastUsedFileFiler; /** * 保存ダイアログを開く * @param parent 親 * @return 保存先ファイル、キャンセルした場合はnull */ public File showSaveDialog(Component parent) { ArchiveFileChooser fileChooser = new ArchiveFileChooser(lastUsedDir, true); int ret = fileChooser.showSaveDialog(parent); if (ret != ArchiveFileChooser.APPROVE_OPTION) { return null; } File outFile = fileChooser.getSelectedFile(); lastUsedDir = outFile.getParentFile(); return outFile; } /** * 開くダイアログを開く.
* initFileがnullの場合は前回選択したディレクトリが初期状態となる. * @param parent 親 * @param initFile 初期選択ファイル、もしくはディレクトリ、もしくはnull * @return 選択ファイル、キャンセルした場合はnull */ public File showOpenDialog(Component parent, File initFile) { // 初期ファイル名が指定されている場合、そのディレクトリをカレントにしてみる. // 指定されてなければ最後に使ったディレクトリを設定する. File initDir = null; if (initFile != null) { if (initFile.isDirectory()) { initDir = initFile; } else { initDir = initFile.getParentFile(); } } // 初期ファイルの指定がなければ前回の最後に使用したディレクトリを使用 if (initDir == null) { initDir = lastUsedDir; } ArchiveFileChooser fileChooser = new ArchiveFileChooser(initDir, false); // 最後に使用したフィルタがあれば、それを選択状態とする. if (lastUsedFileFiler != null) { fileChooser.setFileFilter(lastUsedFileFiler); } // 初期ファイル名が指定されていれば、それを選択状態としてみる. if (initFile != null) { fileChooser.setSelectedFile(initFile); } // ファイル選択ダイアログの表示 int ret = fileChooser.showOpenDialog(parent); if (ret != ArchiveFileChooser.APPROVE_OPTION) { // キャンセル return null; } // 選択ファイルの取得 File outFile = fileChooser.getSelectedFile(); // 最後に選択したディレクトリとフィルタを記憶する. lastUsedDir = outFile.getParentFile(); lastUsedFileFiler = fileChooser.getFileFilter(); return outFile; } /** * 最後に使用したディレクトリを取得する. * @return */ public File getLastUSedDir() { return lastUsedDir; } /** * 最後に使用したディレクトリを設定する. * @param lastUSedDir */ public void setLastUSedDir(File lastUSedDir) { this.lastUsedDir = lastUSedDir; } } CharacterManaJ/src/charactermanaj/ui/InformationDialog.java0000644000175000017500000004176512560206305024224 0ustar paulliupaulliupackage charactermanaj.ui; import static java.lang.Math.max; import java.awt.BorderLayout; import java.awt.Component; import java.awt.Container; import java.awt.GridBagConstraints; import java.awt.GridBagLayout; import java.awt.Insets; import java.awt.Toolkit; import java.awt.datatransfer.Clipboard; import java.awt.datatransfer.StringSelection; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.KeyEvent; import java.awt.event.WindowAdapter; import java.awt.event.WindowEvent; import java.io.File; import java.io.IOException; import java.io.PrintWriter; import java.io.StringWriter; import java.net.URI; import java.util.Collections; import java.util.Properties; import java.util.logging.Level; import java.util.logging.Logger; import javax.swing.AbstractAction; import javax.swing.AbstractCellEditor; import javax.swing.ActionMap; import javax.swing.BorderFactory; import javax.swing.Box; import javax.swing.InputMap; import javax.swing.JButton; import javax.swing.JComponent; import javax.swing.JDialog; import javax.swing.JFrame; import javax.swing.JPanel; import javax.swing.JPopupMenu; import javax.swing.JRootPane; import javax.swing.JScrollPane; import javax.swing.JTable; import javax.swing.KeyStroke; import javax.swing.table.DefaultTableCellRenderer; import javax.swing.table.TableCellEditor; import javax.swing.table.TableCellRenderer; import javax.swing.table.TableColumn; import javax.swing.table.TableColumnModel; import charactermanaj.graphics.filters.ColorConvertParameter; import charactermanaj.graphics.io.ImageResource; import charactermanaj.graphics.io.PNGFileImageHeader; import charactermanaj.graphics.io.PNGFileImageHeaderReader; import charactermanaj.model.AppConfig; import charactermanaj.model.Layer; import charactermanaj.model.PartsIdentifier; import charactermanaj.model.PartsSet; import charactermanaj.model.PartsSpecResolver; import charactermanaj.model.io.PartsImageCollectionParser; import charactermanaj.model.io.PartsImageCollectionParser.PartsImageCollectionHandler; import charactermanaj.ui.model.AbstractTableModelWithComboBoxModel; import charactermanaj.util.DesktopUtilities; import charactermanaj.util.ErrorMessageHelper; import charactermanaj.util.LocalizedResourcePropertyLoader; /** * 情報ダイアログを開く * @author seraphy */ public class InformationDialog extends JDialog { private static final long serialVersionUID = 1L; private static final Logger logger = Logger.getLogger(InformationDialog.class.getName()); protected static final String STRINGS_RESOURCE = "languages/informationdialog"; private JTable informationTable; private InformationTableModel informationTableModel; private boolean modeOpen; public InformationDialog(JFrame parent, PartsSpecResolver partsSpecResolver, PartsSet partsSet) { super(parent, true); AppConfig appConfig = AppConfig.getInstance(); modeOpen = appConfig.isInformationDialogOpenMethod(); if (partsSpecResolver == null) { throw new IllegalArgumentException("partsSpecResolver is null"); } if (partsSet == null) { throw new IllegalArgumentException("partsSet is null"); } setDefaultCloseOperation(JDialog.DO_NOTHING_ON_CLOSE); addWindowListener(new WindowAdapter() { @Override public void windowClosing(WindowEvent e) { onClose(); } }); final Properties strings = LocalizedResourcePropertyLoader .getCachedInstance().getLocalizedProperties(STRINGS_RESOURCE); setTitle(strings.getProperty("title")); informationTableModel = new InformationTableModel(); final PNGFileImageHeaderReader pngHeaderReader = PNGFileImageHeaderReader.getInstance(); PartsImageCollectionParser parser = new PartsImageCollectionParser(partsSpecResolver); parser.parse(partsSet, new PartsImageCollectionHandler() { public void detectImageSource(PartsIdentifier partsIdentifier, Layer layer, final ImageResource imageResource, ColorConvertParameter param) { AbstractAction act = new AbstractAction(strings.getProperty(modeOpen ? "btn.edit.open" : "btn.edit.edit")) { private static final long serialVersionUID = 1L; public void actionPerformed(ActionEvent e) { onOpen(imageResource); } }; URI uri = imageResource.getURI(); if (uri != null && "file".equals(uri.getScheme()) && DesktopUtilities.isSupported()) { act.setEnabled(true); } else { act.setEnabled(false); } PNGFileImageHeader pngHeader; try { pngHeader = pngHeaderReader.readHeader(uri); } catch (IOException ex) { logger.log(Level.WARNING, "PNG Header loading error.: " + uri, ex); pngHeader = null; } InformationModel information = new InformationModel(partsIdentifier, layer, imageResource, param, pngHeader, act); informationTableModel.addRow(information); } }); informationTableModel.sort(); informationTable = new JTable(informationTableModel) { private static final long serialVersionUID = 1L; // セルの幅を大きいものにあわせる public Component prepareRenderer(final TableCellRenderer renderer, final int row, final int column) { final Component prepareRenderer = super.prepareRenderer(renderer, row, column); final TableColumn tableColumn = getColumnModel().getColumn(column); int preferredWidth = max(prepareRenderer .getPreferredSize().width, tableColumn .getPreferredWidth()); // セルかヘッダのどちらか幅の大きいほう if (tableColumn.getPreferredWidth() != preferredWidth) { tableColumn.setPreferredWidth(preferredWidth); } return prepareRenderer; } }; informationTableModel.adjustColumnModel(informationTable.getColumnModel()); informationTable.setShowGrid(true); informationTable.setGridColor(appConfig.getGridColor()); informationTable.putClientProperty("terminateEditOnFocusLost", Boolean.TRUE); informationTable.setAutoResizeMode(JTable.AUTO_RESIZE_OFF); informationTable.setRowHeight(informationTable.getRowHeight() + 4); informationTable.setDefaultRenderer(JButton.class, new ButtonCellRender()); informationTable.setDefaultEditor(JButton.class, new ButtonCellEditor()); // セルデータの幅にあわせる(事前に) for (int row = 0; row < informationTable.getRowCount(); row++) { for (int col = 0; col < informationTable.getColumnCount(); col++) { TableCellRenderer renderer = informationTable.getCellRenderer(row, col); informationTable.prepareRenderer(renderer, row, col); } } final JPopupMenu popupMenu = new JPopupMenu(); popupMenu.add(new AbstractAction(strings.getProperty("popupmenu.copyPath")) { private static final long serialVersionUID = 1L; public void actionPerformed(ActionEvent e) { onCopyFilePath(); } }); informationTable.setComponentPopupMenu(popupMenu); Container contentPane = getContentPane(); contentPane.setLayout(new BorderLayout()); JScrollPane informationTableSP = new JScrollPane(informationTable); JPanel informationTableSPPabel = new JPanel(new BorderLayout()); informationTableSPPabel.setBorder(BorderFactory.createEmptyBorder(5, 5, 0, 5)); informationTableSPPabel.add(informationTableSP, BorderLayout.CENTER); contentPane.add(informationTableSPPabel, BorderLayout.CENTER); AbstractAction actClose = new AbstractAction(strings.getProperty("btnClose")) { private static final long serialVersionUID = 1L; public void actionPerformed(ActionEvent e) { onClose(); } }; JPanel btnPanel = new JPanel(); btnPanel.setBorder(BorderFactory.createEmptyBorder(3, 5, 3, 45)); GridBagLayout btnPanelLayout = new GridBagLayout(); btnPanel.setLayout(btnPanelLayout); GridBagConstraints gbc = new GridBagConstraints(); gbc.gridx = 0; gbc.gridy = 0; gbc.gridheight = 1; gbc.gridwidth = 1; gbc.anchor = GridBagConstraints.EAST; gbc.fill = GridBagConstraints.BOTH; gbc.insets = new Insets(3, 3, 3, 3); gbc.ipadx = 0; gbc.ipady = 0; gbc.weightx = 1.; gbc.weighty = 0.; btnPanel.add(Box.createHorizontalGlue(), gbc); gbc.gridx = 1; gbc.gridy = 0; gbc.weightx = 0.; gbc.weighty = 0.; JButton btnClose = new JButton(actClose); btnPanel.add(btnClose, gbc); contentPane.add(btnPanel, BorderLayout.SOUTH); Toolkit tk = Toolkit.getDefaultToolkit(); JRootPane rootPane = getRootPane(); rootPane.setDefaultButton(btnClose); InputMap im = rootPane.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW); ActionMap am = rootPane.getActionMap(); im.put(KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE,0), "closeInformationDialog"); im.put(KeyStroke.getKeyStroke(KeyEvent.VK_W, tk.getMenuShortcutKeyMask()), "closeInformationDialog"); am.put("closeInformationDialog", actClose); pack(); setLocationRelativeTo(parent); } protected void onClose() { dispose(); } protected void onCopyFilePath() { StringWriter sw = new StringWriter(); PrintWriter pw = new PrintWriter(sw); for (int selRow : informationTable.getSelectedRows()) { InformationModel information = informationTableModel.getRow(selRow); pw.println(information.getImageResourceName()); } Toolkit tk = Toolkit.getDefaultToolkit(); String text = sw.toString(); if (text.length() == 0) { tk.beep(); return; } StringSelection textSelection = new StringSelection(sw.toString()); Clipboard cb = tk.getSystemClipboard(); cb.setContents(textSelection, null); } protected void onOpen(ImageResource imageResource) { try { URI uri = imageResource.getURI(); if (uri != null && "file".equals(uri.getScheme())) { File file = new File(uri); DesktopUtilities.open(file); } } catch (Exception ex) { ErrorMessageHelper.showErrorDialog(this, ex); } } } class InformationTableModel extends AbstractTableModelWithComboBoxModel { private static final long serialVersionUID = 1L; private static final String[] columnNames; private static final int[] columnWidths; public static final int COLUMN_BUTTON; static { final Properties strings = LocalizedResourcePropertyLoader .getCachedInstance().getLocalizedProperties(InformationDialog.STRINGS_RESOURCE); columnNames = new String[] { strings.getProperty("column.partsName"), strings.getProperty("column.categoryName"), strings.getProperty("column.layerName"), strings.getProperty("column.layerOrder"), strings.getProperty("column.imagesize"), strings.getProperty("column.colortype"), strings.getProperty("column.imageName"), strings.getProperty("column.editbtn"), }; COLUMN_BUTTON = 7; columnWidths = new int[] { Integer.parseInt(strings.getProperty("column.partsName.width")), Integer.parseInt(strings.getProperty("column.categoryName.width")), Integer.parseInt(strings.getProperty("column.layerName.width")), Integer.parseInt(strings.getProperty("column.layerOrder.width")), Integer.parseInt(strings.getProperty("column.layerOrder.imagesize.width")), Integer.parseInt(strings.getProperty("column.layerOrder.colortype.width")), Integer.parseInt(strings.getProperty("column.imageName.width")), Integer.parseInt(strings.getProperty("column.editbtn.width")), }; } public void adjustColumnModel(TableColumnModel columnModel) { for (int idx = 0; idx < columnWidths.length; idx++) { columnModel.getColumn(idx).setPreferredWidth(columnWidths[idx]); } } public int getColumnCount() { return columnNames.length; } @Override public String getColumnName(int column) { return columnNames[column]; } @Override public Class getColumnClass(int columnIndex) { switch (columnIndex) { case 0: return String.class; case 1: return String.class; case 2: return String.class; case 3: return Integer.class; case 4: return String.class; case 5: return String.class; case 6: return String.class; case 7: return JButton.class; } return String.class; } public Object getValueAt(int rowIndex, int columnIndex) { InformationModel information = getRow(rowIndex); switch (columnIndex) { case 0: return information.getPartsName(); case 1: return information.getCategoryName(); case 2: return information.getLayerName(); case 3: return information.getLayerOrder(); case 4: return information.getImageSizeStr(); case 5: return information.getColorTypeStr(); case 6: return information.getImageResourceName(); case 7: return information.getButton(); } return ""; } public void sort() { Collections.sort(elements); fireTableDataChanged(); } @Override public boolean isCellEditable(int rowIndex, int columnIndex) { InformationModel information = getRow(rowIndex); if (columnIndex == COLUMN_BUTTON) { return information.getButton().isEnabled(); } return false; } } class InformationModel implements Comparable { private PartsIdentifier partsIdentifier; private Layer layer; private ImageResource imageResource; private JButton btnOpen; private PNGFileImageHeader pngHeader; public InformationModel(PartsIdentifier partsIdentifier, Layer layer, ImageResource imageResource, ColorConvertParameter colorConvertParameter, PNGFileImageHeader pngHeader, AbstractAction actOpen) { this.partsIdentifier = partsIdentifier; this.layer = layer; this.imageResource = imageResource; this.pngHeader = pngHeader; this.btnOpen = new JButton(actOpen) { private static final long serialVersionUID = 1L; @Override public String toString() { // JTableをクリップボードにコピーしたときに設定されるカラムの文字列表現 return "open"; } }; } @Override public int hashCode() { return partsIdentifier.hashCode(); } @Override public boolean equals(Object obj) { if (obj == this) { return true; } if (obj != null && obj instanceof InformationModel) { InformationModel o = (InformationModel) obj; return partsIdentifier.equals(o.partsIdentifier) && layer.equals(o.layer); } return false; } public int compareTo(InformationModel o) { int ret = partsIdentifier.compareTo(o.partsIdentifier); if (ret == 0) { ret = layer.compareTo(o.layer); } if (ret == 0) { ret = imageResource.compareTo(o.imageResource); } return ret; } public String getPartsName() { return this.partsIdentifier.getLocalizedPartsName(); } public String getCategoryName() { return this.partsIdentifier.getPartsCategory().getLocalizedCategoryName(); } public String getLayerName() { return this.layer.getLocalizedName(); } public int getLayerOrder() { return this.layer.getOrder(); } public String getImageResourceName() { return this.imageResource.getFullName(); } public JButton getButton() { return btnOpen; } public String getImageSizeStr() { if (pngHeader == null) { return "INVALID"; } return pngHeader.getWidth() + "x" + pngHeader.getHeight(); } public String getColorTypeStr() { if (pngHeader == null) { return "INVALID"; } StringBuilder buf = new StringBuilder(); int colorType = pngHeader.getColorType(); if ((colorType & 0x01) != 0) { buf.append("Indexed "); } if ((colorType & 0x02) != 0) { buf.append("Color "); } else { buf.append("Greyscale "); } if (colorType == 6 || pngHeader.hasTransparencyInformation()) { // 6:TrueColor または アルファ情報がある場合のみアルファ有りとする. buf.append("Alpha "); } buf.append(pngHeader.getBitDepth() + "bit"); return buf.toString().trim(); } } /** * ボタンレンダー.
* @author seraphy */ class ButtonCellRender extends DefaultTableCellRenderer { private static final long serialVersionUID = 1L; @Override public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) { super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column); return (JButton) value; } } /** * ボタンエディタ.
* @author seraphy */ class ButtonCellEditor extends AbstractCellEditor implements TableCellEditor { private static final long serialVersionUID = 1L; public Component getTableCellEditorComponent(final JTable table, final Object value, final boolean isSelected, final int row, final int column) { final JButton orgBtn = (JButton) value; final JButton btn = new JButton(new AbstractAction(orgBtn.getText()) { private static final long serialVersionUID = 1L; public void actionPerformed(ActionEvent e) { fireEditingCanceled(); for (ActionListener listener : orgBtn.getActionListeners()) { listener.actionPerformed(e); } } }); return btn; } public Object getCellEditorValue() { return null; } } CharacterManaJ/src/charactermanaj/ui/MainFramePartialForMacOSX.java0000644000175000017500000000443312560206305025444 0ustar paulliupaulliupackage charactermanaj.ui; import java.awt.Image; import java.lang.reflect.Method; import java.util.logging.Level; import java.util.logging.Logger; import charactermanaj.util.SystemUtil; import com.apple.eawt.Application; import com.apple.eawt.ApplicationAdapter; import com.apple.eawt.ApplicationEvent; /** * Mac OS X用のメインフレームサポートクラス.
* スクリーンメニューのハンドラなどを接続している.
* @author seraphy * */ public class MainFramePartialForMacOSX { /** * ロガー */ private static final Logger logger = Logger.getLogger(MainFramePartialForMacOSX.class.getName()); private MainFramePartialForMacOSX() { super(); } public static void setupScreenMenu(final MainFrame mainFrame) { if (mainFrame == null) { throw new IllegalArgumentException(); } Application app = Application.getApplication(); app.setEnabledAboutMenu(true); app.setEnabledPreferencesMenu(true); ApplicationAdapter listener = new ApplicationAdapter() { public void handleAbout(ApplicationEvent arg0) { if (MainFrame.getActivedMainFrame() != null) { MainFrame.getActivedMainFrame().onAbout(); } arg0.setHandled(true); } public void handleQuit(ApplicationEvent arg0) { if (MainFrame.getActivedMainFrame() != null) { MainFrame.closeAllProfiles(); } arg0.setHandled(true); // JVMを明示的にシャットダウンする. (何もしないと強制終了になるため。) SystemUtil.exit(0); } public void handlePreferences(ApplicationEvent arg0) { if (MainFrame.getActivedMainFrame() != null) { MainFrame.getActivedMainFrame().onPreferences(); } arg0.setHandled(true); } }; app.addApplicationListener(listener); try { Class clz = app.getClass(); Method mtd = clz.getMethod("setDockIconImage", new Class[] {Image.class}); mtd.invoke(app, new Object[] {mainFrame.icon}); } catch (NoSuchMethodException ex) { // メソッドがない = Tiger以前の失敗であろうから、単に無視するだけで良い. logger.log(Level.CONFIG, "dockIcon not supported.", ex); } catch (Exception ex) { // 実行時の失敗だが、DockIconが設定できないだけなので継続する. logger.log(Level.WARNING, "dockIcon failed.", ex); } } } CharacterManaJ/src/charactermanaj/ui/ExportWizardDialog.java0000644000175000017500000015477212560206305024404 0ustar paulliupaulliupackage charactermanaj.ui; import java.awt.BorderLayout; import java.awt.CardLayout; import java.awt.Color; import java.awt.Component; import java.awt.Container; import java.awt.Cursor; import java.awt.Dimension; import java.awt.Font; import java.awt.GridBagConstraints; import java.awt.GridBagLayout; import java.awt.Insets; import java.awt.Rectangle; import java.awt.Toolkit; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.ComponentAdapter; import java.awt.event.ComponentEvent; import java.awt.event.ComponentListener; import java.awt.event.KeyEvent; import java.awt.event.WindowAdapter; import java.awt.event.WindowEvent; import java.awt.image.BufferedImage; import java.io.File; import java.io.IOException; import java.io.PrintWriter; import java.io.StringWriter; import java.sql.Timestamp; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; import java.util.HashSet; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Properties; import javax.swing.AbstractAction; import javax.swing.Action; import javax.swing.ActionMap; import javax.swing.BorderFactory; import javax.swing.Box; import javax.swing.BoxLayout; import javax.swing.InputMap; import javax.swing.JButton; import javax.swing.JCheckBox; import javax.swing.JComponent; import javax.swing.JDialog; import javax.swing.JFrame; import javax.swing.JLabel; import javax.swing.JOptionPane; import javax.swing.JPanel; import javax.swing.JPopupMenu; import javax.swing.JRootPane; import javax.swing.JScrollPane; import javax.swing.JTable; import javax.swing.JTextArea; import javax.swing.JTextField; import javax.swing.KeyStroke; import javax.swing.ListSelectionModel; import javax.swing.event.ChangeEvent; import javax.swing.event.ChangeListener; import javax.swing.event.TableModelEvent; import javax.swing.event.TableModelListener; import javax.swing.table.TableCellRenderer; import javax.swing.table.TableColumnModel; import charactermanaj.Main; import charactermanaj.model.AppConfig; import charactermanaj.model.CharacterData; import charactermanaj.model.PartsCategory; import charactermanaj.model.PartsIdentifier; import charactermanaj.model.PartsSet; import charactermanaj.model.PartsSpec; import charactermanaj.model.PartsSpecResolver; import charactermanaj.model.io.CharacterDataFileReaderWriterFactory; import charactermanaj.model.io.CharacterDataWriter; import charactermanaj.model.io.ExportInfoKeys; import charactermanaj.ui.model.AbstractTableModelWithComboBoxModel; import charactermanaj.ui.progress.ProgressHandle; import charactermanaj.ui.progress.Worker; import charactermanaj.ui.progress.WorkerException; import charactermanaj.ui.progress.WorkerWithProgessDialog; import charactermanaj.util.ErrorMessageHelper; import charactermanaj.util.LocalizedResourcePropertyLoader; public class ExportWizardDialog extends JDialog { private static final long serialVersionUID = 1L; protected static final String STRINGS_RESOURCE = "languages/exportwizdialog"; protected static ArchiveFileDialog archiveFileDialog = new ArchiveFileDialog(); private JPanel activePanel; private AbstractAction actNext; private AbstractAction actPrev; private AbstractAction actFinish; private ExportInformationPanel basicPanel; private ExportPartsSelectPanel partsSelectPanel; private ExportPresetSelectPanel presetSelectPanel; private CharacterData source; public static File getLastUsedDir() { return archiveFileDialog.getLastUSedDir(); } public static void setLastUsedDir(File lastUsedDir) { archiveFileDialog.setLastUSedDir(lastUsedDir); } public ExportWizardDialog(JFrame parent, CharacterData characterData, BufferedImage samplePicture) { super(parent, true); initComponent(parent, characterData, samplePicture); } public ExportWizardDialog(JDialog parent, CharacterData characterData, BufferedImage samplePicture) { super(parent, true); initComponent(parent, characterData, samplePicture); } private void initComponent(Component parent, CharacterData characterData, BufferedImage samplePicture) { if (characterData == null) { throw new IllegalArgumentException(); } this.source = characterData; setDefaultCloseOperation(JDialog.DO_NOTHING_ON_CLOSE); addWindowListener(new WindowAdapter() { @Override public void windowClosing(WindowEvent e) { onClose(); } }); Properties strings = LocalizedResourcePropertyLoader.getCachedInstance() .getLocalizedProperties(STRINGS_RESOURCE); // タイトル setTitle(strings.getProperty("title")); // メインパネル Container contentPane = getContentPane(); contentPane.setLayout(new BorderLayout()); final JPanel mainPanel = new JPanel(); mainPanel.setBorder(BorderFactory.createEtchedBorder()); final CardLayout mainPanelLayout = new CardLayout(5, 5); mainPanel.setLayout(mainPanelLayout); contentPane.add(mainPanel, BorderLayout.CENTER); ComponentListener componentListener = new ComponentAdapter() { public void componentShown(ComponentEvent e) { onComponentShown((JPanel) e.getComponent()); } }; // アクション this.actNext = new AbstractAction(strings.getProperty("btn.next")) { private static final long serialVersionUID = 1L; public void actionPerformed(ActionEvent e) { mainPanelLayout.next(mainPanel); } }; this.actPrev = new AbstractAction(strings.getProperty("btn.prev")) { private static final long serialVersionUID = 1L; public void actionPerformed(ActionEvent e) { mainPanelLayout.previous(mainPanel); } }; this.actFinish = new AbstractAction(strings.getProperty("btn.finish")) { private static final long serialVersionUID = 1L; public void actionPerformed(ActionEvent e) { onFinish(); } }; AbstractAction actCancel = new AbstractAction(strings.getProperty("btn.cancel")) { private static final long serialVersionUID = 1L; public void actionPerformed(ActionEvent e) { onClose(); } }; ChangeListener actChangeValue = new ChangeListener() { public void stateChanged(ChangeEvent e) { updateBtnPanelState(); } }; ChangeListener actPanelEnabler = new ChangeListener() { public void stateChanged(ChangeEvent e) { updatePanelStatus(); } }; // panel1 : basic this.basicPanel = new ExportInformationPanel(characterData, samplePicture); this.basicPanel.addComponentListener(componentListener); this.basicPanel.addChangeListener(actChangeValue); this.basicPanel.addChangeListener(actPanelEnabler); mainPanel.add(this.basicPanel, "basicPanel"); // panel2 : panelSelectPanel this.partsSelectPanel = new ExportPartsSelectPanel(characterData); this.partsSelectPanel.addComponentListener(componentListener); this.partsSelectPanel.addChangeListener(actChangeValue); mainPanel.add(this.partsSelectPanel, "partsSelectPanel"); // panel3 : presetSelectPanel this.presetSelectPanel = new ExportPresetSelectPanel( this.partsSelectPanel, this.basicPanel, characterData.getPartsSets().values(), characterData.getDefaultPartsSetId()); this.presetSelectPanel.addComponentListener(componentListener); this.presetSelectPanel.addChangeListener(actChangeValue); mainPanel.add(this.presetSelectPanel, "presetSelectPanel"); // button panel JPanel btnPanel = new JPanel(); btnPanel.setBorder(BorderFactory.createEmptyBorder(3, 3, 3, 45)); GridBagLayout btnPanelLayout = new GridBagLayout(); btnPanel.setLayout(btnPanelLayout); actPrev.setEnabled(false); actNext.setEnabled(false); actFinish.setEnabled(false); GridBagConstraints gbc = new GridBagConstraints(); gbc.gridx = 0; gbc.gridy = 0; gbc.gridheight = 1; gbc.gridwidth = 1; gbc.anchor = GridBagConstraints.EAST; gbc.fill = GridBagConstraints.BOTH; gbc.ipadx = 0; gbc.ipady = 0; gbc.insets = new Insets(3, 3, 3, 3); gbc.weightx = 1.; gbc.weighty = 0.; btnPanel.add(Box.createHorizontalGlue(), gbc); gbc.gridx = Main.isLinuxOrMacOSX() ? 2 : 1; gbc.gridy = 0; gbc.weightx = 0.; btnPanel.add(new JButton(this.actPrev), gbc); gbc.gridx = Main.isLinuxOrMacOSX() ? 3 : 2; gbc.gridy = 0; JButton btnNext = new JButton(this.actNext); btnPanel.add(btnNext, gbc); gbc.gridx = Main.isLinuxOrMacOSX() ? 4 : 3; gbc.gridy = 0; btnPanel.add(new JButton(this.actFinish), gbc); gbc.gridx = Main.isLinuxOrMacOSX() ? 1 : 4; gbc.gridy = 0; JButton btnCancel = new JButton(actCancel); btnPanel.add(btnCancel, gbc); contentPane.add(btnPanel, BorderLayout.SOUTH); // インプットマップ/アクションマップ Toolkit tk = Toolkit.getDefaultToolkit(); JRootPane rootPane = getRootPane(); rootPane.setDefaultButton(btnNext); InputMap im = rootPane.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW); ActionMap am = rootPane.getActionMap(); im.put(KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0), "closeExportWizDialog"); im.put(KeyStroke.getKeyStroke(KeyEvent.VK_W, tk.getMenuShortcutKeyMask()), "closeExportWizDialog"); am.put("closeExportWizDialog", actCancel); // 表示 setSize(500, 500); setLocationRelativeTo(parent); mainPanelLayout.first(mainPanel); updateBtnPanelState(); updatePanelStatus(); } protected void onComponentShown(JPanel panel) { activePanel = panel; updateBtnPanelState(); } protected void updatePanelStatus() { partsSelectPanel.setEnabled(basicPanel.isExportPartsImages()); presetSelectPanel.setEnabled(basicPanel.isExportPresets()); } protected void updateBtnPanelState() { actPrev.setEnabled(activePanel != null && activePanel != basicPanel); actNext.setEnabled(activePanel != null && activePanel != presetSelectPanel); actFinish.setEnabled(isComplete()); } protected void checkMissingParts(Collection partaSets) { if (partaSets == null) { partaSets = presetSelectPanel.getSelectedPresets(); } partsSelectPanel.checkMissingPartsList(partaSets); } protected boolean isComplete() { if (basicPanel.isExportPartsImages()) { if (partsSelectPanel.getSelectedCount() == 0) { // パーツイメージのエクスポートを指定した場合、エクスポートするパーツの選択は必須 return false; } } if (basicPanel.isExportPresets()) { if (presetSelectPanel.getSelectedCount() == 0) { // プリセットのエクスポートを指定した場合、エクスポートするプリセットの選択は必須 return false; } } return true; } protected void onClose() { Properties strings = LocalizedResourcePropertyLoader.getCachedInstance() .getLocalizedProperties(ExportWizardDialog.STRINGS_RESOURCE); if (JOptionPane.showConfirmDialog(this, strings.getProperty("confirm.close"), strings.getProperty("confirm"), JOptionPane.YES_NO_OPTION, JOptionPane.QUESTION_MESSAGE) != JOptionPane.YES_OPTION) { return; } dispose(); } protected void onFinish() { if (!isComplete()) { Toolkit tk = Toolkit.getDefaultToolkit(); tk.beep(); return; } try { final File outFile = archiveFileDialog.showSaveDialog(this); if (outFile == null) { return; } // 出力 setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); try { Worker worker = new Worker() { public Object doWork(ProgressHandle progressHandle) throws IOException { doExport(outFile); return null; } }; WorkerWithProgessDialog dlg = new WorkerWithProgessDialog(this, worker); dlg.startAndWait(); } finally { setCursor(Cursor.getDefaultCursor()); } // 完了メッセージ Properties strings = LocalizedResourcePropertyLoader.getCachedInstance() .getLocalizedProperties(ExportWizardDialog.STRINGS_RESOURCE); JOptionPane.showMessageDialog(this, strings.getProperty("complete")); // 完了後、ウィンドウを閉じる. dispose(); } catch (WorkerException ex) { ErrorMessageHelper.showErrorDialog(this, ex.getCause()); } catch (Exception ex) { ErrorMessageHelper.showErrorDialog(this, ex); } } protected void doExport(File outFile) throws IOException { CharacterDataFileReaderWriterFactory writerFactory = CharacterDataFileReaderWriterFactory.getInstance(); CharacterDataWriter exportWriter = writerFactory.createWriter(outFile); try { // 基本情報のみをコピーし、これをエクスポートするキャラクター定義のベースとする。 // (プリセットとパーツイメージはリセットされている状態。) CharacterData cd = source.duplicateBasicInfo(); cd.clearPartsSets(false); boolean exportPresets = basicPanel.isExportPresets(); boolean exportSamplePicture = basicPanel.isExportSamplePicture(); boolean exportCharacterData = true; boolean exportPartsImages = basicPanel.isExportPartsImages(); // 基本情報を設定する. cd.setAuthor(basicPanel.getAuthor()); cd.setDescription(basicPanel.getDescription()); // エクスポート情報を出力する. Properties exportProp = new Properties(); exportProp.setProperty(ExportInfoKeys.EXPORT_PRESETS, Boolean.toString(exportPresets)); exportProp.setProperty(ExportInfoKeys.EXPORT_SAMPLE_PICTURE, Boolean.toString(exportSamplePicture)); exportProp.setProperty(ExportInfoKeys.EXPORT_CHARACTER_DATA, Boolean.toString(exportCharacterData)); exportProp.setProperty(ExportInfoKeys.EXPORT_PARTS_IMAGES, Boolean.toString(exportPartsImages)); exportProp.setProperty(ExportInfoKeys.EXPORT_TIMESTAMP, Long.toString(System.currentTimeMillis())); exportWriter.writeExportProp(exportProp); // プリセットをエクスポートする場合、プリセット情報を登録する. if (exportPresets) { HashSet registered = new HashSet(); for (PartsSet partsSet : presetSelectPanel.getSelectedPresets()) { registered.add(partsSet.getPartsSetId()); cd.addPartsSet(partsSet); } // プリセットとして登録済みのもののみ既定に設定可能 String defaultPresetId = presetSelectPanel.getDefaultPresetId(); if (registered.contains(defaultPresetId)) { cd.setDefaultPartsSetId(defaultPresetId); } } // キャラクターデータを出力する. exportWriter.writeCharacterData(cd); // readme.txtを出力する. String readmeContents = cd.getDescription(); if (readmeContents != null && readmeContents.trim().length() > 0) { AppConfig appConfig = AppConfig.getInstance(); StringWriter sw = new StringWriter(); PrintWriter pw = new PrintWriter(sw); pw.println("exported by CharacterManaJ (version " + appConfig.getSpecificationVersion() + " " + appConfig.getImplementationVersion() + ")"); pw.println(); pw.println(readmeContents); pw.close(); exportWriter.writeTextUTF16LE("readme.txt", sw.toString()); } // サンプルピクチャをエクスポートする if (exportSamplePicture) { BufferedImage pic = null; pic = basicPanel.getSamplePicture(); if (pic != null) { exportWriter.writeSamplePicture(pic); } } if (exportPartsImages) { Map partsSpecMap = partsSelectPanel.getSelectedParts(); // パーツ管理情報を出力する exportWriter.writePartsManageData(partsSpecMap); // パーツイメージを出力する exportWriter.writePartsImages(partsSpecMap); } } finally { exportWriter.close(); } } } interface ExportResolverBase { void addChangeListener(ChangeListener l); void removeChangeListener(ChangeListener l); } /** * 基本情報 * @author seraphy */ interface ExportInformationResolver extends ExportResolverBase { BufferedImage getSamplePicture(); boolean isExportSamplePicture(); boolean isExportPartsImages(); boolean isExportPresets(); String getAuthor(); String getDescription(); } abstract class AbstractImportPanel extends JPanel implements ExportResolverBase { private static final long serialVersionUID = 1L; protected LinkedList listeners = new LinkedList(); public void addChangeListener(ChangeListener l) { if (l != null) { listeners.add(l); } } public void removeChangeListener(ChangeListener l) { if (l != null) { listeners.remove(l); } } protected void fireChangeEvent() { ChangeEvent e = new ChangeEvent(this); for (ChangeListener listener : listeners) { listener.stateChanged(e); } } } /** * 基本情報パネル * @author seraphy */ class ExportInformationPanel extends AbstractImportPanel implements ExportInformationResolver { private static final long serialVersionUID = 1L; private BufferedImage samplePicture; private SamplePicturePanel sampleImagePanel; private JTextField txtAuthor; private JTextArea txtDescription; private JCheckBox chkPartsImages; private JCheckBox chkPresets; private JCheckBox chkSampleImage; protected ExportInformationPanel(final CharacterData characterData, final BufferedImage samplePicture) { if (characterData == null) { throw new IllegalArgumentException(); } setName("basicPanel"); this.samplePicture = samplePicture; Properties strings = LocalizedResourcePropertyLoader.getCachedInstance() .getLocalizedProperties(ExportWizardDialog.STRINGS_RESOURCE); GridBagLayout basicPanelLayout = new GridBagLayout(); GridBagConstraints gbc = new GridBagConstraints(); setLayout(basicPanelLayout); JPanel contentsSpecPanel = new JPanel(); contentsSpecPanel.setBorder(BorderFactory.createCompoundBorder( BorderFactory.createEmptyBorder(5, 5, 5, 5), BorderFactory.createTitledBorder(strings.getProperty("basic.contentsSpec")))); BoxLayout contentsSpecPanelLayout = new BoxLayout(contentsSpecPanel, BoxLayout.PAGE_AXIS); contentsSpecPanel.setLayout(contentsSpecPanelLayout); JCheckBox chkCharacterDef = new JCheckBox(strings.getProperty("characterdef")); chkPartsImages = new JCheckBox(strings.getProperty("basic.chk.partsImages")); chkPresets = new JCheckBox(strings.getProperty("basic.chk.presets")); chkSampleImage = new JCheckBox(strings.getProperty("basic.chk.samplePicture")); chkCharacterDef.setEnabled(false); // キャラクター定義は固定 chkCharacterDef.setSelected(true); contentsSpecPanel.add(chkCharacterDef); contentsSpecPanel.add(chkPartsImages); contentsSpecPanel.add(chkPresets); contentsSpecPanel.add(chkSampleImage); /// JPanel commentPanel = new JPanel(); Dimension archiveInfoPanelMinSize = new Dimension(300, 200); commentPanel.setMinimumSize(archiveInfoPanelMinSize); commentPanel.setPreferredSize(archiveInfoPanelMinSize); commentPanel.setBorder(BorderFactory.createCompoundBorder( BorderFactory.createEmptyBorder(5, 5, 5, 5), BorderFactory.createTitledBorder(strings.getProperty("basic.comment")))); GridBagLayout commentPanelLayout = new GridBagLayout(); commentPanel.setLayout(commentPanelLayout); gbc.gridx = 0; gbc.gridy = 0; gbc.gridheight = 1; gbc.gridwidth = 1; gbc.weightx = 0.; gbc.weighty = 0.; gbc.insets = new Insets(3, 3, 3, 3); gbc.anchor = GridBagConstraints.WEST; gbc.fill = GridBagConstraints.BOTH; commentPanel.add(new JLabel(strings.getProperty("author"), JLabel.RIGHT), gbc); gbc.gridx = 1; gbc.gridy = 0; gbc.gridwidth = 1; gbc.weightx = 1.; gbc.weighty = 0.; txtAuthor = new JTextField(); commentPanel.add(txtAuthor, gbc); gbc.gridx = 0; gbc.gridy = 1; gbc.gridwidth = 1; gbc.gridheight = 1; gbc.weightx = 0.; gbc.weighty = 0.; commentPanel.add(new JLabel(strings.getProperty("description"), JLabel.RIGHT), gbc); gbc.gridx = 1; gbc.gridy = 1; gbc.gridwidth = 1; gbc.gridheight = 2; gbc.weighty = 1.; gbc.weightx = 1.; txtDescription = new JTextArea(); commentPanel.add(new JScrollPane(txtDescription), gbc); /// sampleImagePanel = new SamplePicturePanel(samplePicture); sampleImagePanel.setBorder(BorderFactory.createCompoundBorder( BorderFactory.createEmptyBorder(5, 5, 5, 5), BorderFactory.createTitledBorder(strings.getProperty("basic.sampleImage")))); /// gbc.gridx = 0; gbc.gridy = 0; gbc.gridheight = 1; gbc.gridwidth = 2; gbc.weightx = 1.; gbc.weighty = 0.; gbc.anchor = GridBagConstraints.WEST; gbc.fill = GridBagConstraints.BOTH; add(contentsSpecPanel, gbc); gbc.gridx = 0; gbc.gridy = 1; gbc.gridheight = 1; gbc.gridwidth = 1; gbc.weightx = 0.; gbc.weighty = 1.; gbc.anchor = GridBagConstraints.WEST; gbc.fill = GridBagConstraints.BOTH; add(commentPanel, gbc); gbc.gridx = 1; gbc.gridy = 1; gbc.gridheight = 1; gbc.gridwidth = 1; gbc.weightx = 1.; gbc.weighty = 1.; gbc.anchor = GridBagConstraints.WEST; gbc.fill = GridBagConstraints.BOTH; add(sampleImagePanel, gbc); loadBasicInfo(characterData); // アクションリスナ ActionListener modListener = new ActionListener() { public void actionPerformed(ActionEvent e) { updateSamplePicture(); fireChangeEvent(); } }; chkPartsImages.addActionListener(modListener); chkPresets.addActionListener(modListener); chkSampleImage.addActionListener(modListener); } protected void updateSamplePicture() { sampleImagePanel.setVisiblePicture(chkSampleImage.isSelected()); } protected void loadBasicInfo(CharacterData characterData) { if (samplePicture == null) { // サンプルイメージがなければディセーブル chkSampleImage.setEnabled(false); chkSampleImage.setSelected(false); sampleImagePanel.setVisiblePicture(false); } else { chkSampleImage.setSelected(true); sampleImagePanel.setVisiblePicture(true); } chkPartsImages.setSelected(true); chkPresets.setSelected(true); String author = characterData.getAuthor(); String description = characterData.getDescription(); txtAuthor.setText(author == null ? "" : author); txtDescription.setText(description == null ? "" : description); } public BufferedImage getSamplePicture() { return samplePicture; } public boolean isExportSamplePicture() { return chkSampleImage.isSelected(); } public boolean isExportPartsImages() { return chkPartsImages.isSelected(); } public boolean isExportPresets() { return chkPresets.isSelected(); } public String getAuthor() { return txtAuthor.getText(); } public String getDescription() { return txtDescription.getText(); } } /** * エクスポート対象パーツ * @author seraphy */ interface ExportPartsResolver extends ExportResolverBase { int getSelectedCount(); void selectByPartsSet(Collection partsSet); Map getSelectedParts(); Map> checkMissingPartsList(Collection partsSets); } /** * エクスポート対象パーツ選択パネル * @author seraphy */ class ExportPartsSelectPanel extends AbstractImportPanel implements ExportPartsResolver { private static final long serialVersionUID = 1L; private ExportPartsTableModel partsTableModel; private JTable partsTable; private Action actSelectAll; private Action actDeselectAll; private Action actSort; private Action actSortByTimestamp; protected ExportPartsSelectPanel(PartsSpecResolver partsSpecResolver) { if (partsSpecResolver == null) { throw new IllegalArgumentException(); } Properties strings = LocalizedResourcePropertyLoader.getCachedInstance() .getLocalizedProperties(ExportWizardDialog.STRINGS_RESOURCE); setName("choosePartsPanel"); setBorder(BorderFactory.createTitledBorder(strings.getProperty("parts.title"))); setLayout(new BorderLayout()); partsTableModel = new ExportPartsTableModel(); partsTableModel.addTableModelListener(new TableModelListener() { public void tableChanged(TableModelEvent e) { fireChangeEvent(); } }); loadPartsInfo(partsSpecResolver); AppConfig appConfig = AppConfig.getInstance(); final Color disabledForeground = appConfig.getDisabledCellForgroundColor(); partsTable = new JTable(partsTableModel) { private static final long serialVersionUID = 1L; @Override public Component prepareRenderer(TableCellRenderer renderer, int row, int column) { Component comp = super.prepareRenderer(renderer, row, column); if (comp instanceof JCheckBox) { // BooleanのデフォルトのレンダラーはKCheckBoxを継承したJTable$BooleanRenderer comp.setEnabled(isCellEditable(row, column) && isEnabled()); } comp.setForeground(isEnabled() ? (isCellSelected(row,column) ? getSelectionForeground() : getForeground()) : disabledForeground); return comp; } }; partsTable.setShowGrid(true); partsTable.setGridColor(appConfig.getGridColor()); partsTable.setAutoResizeMode(JTable.AUTO_RESIZE_OFF); partsTable.putClientProperty("terminateEditOnFocusLost", Boolean.TRUE); partsTableModel.adjustColumnModel(partsTable.getColumnModel()); partsTable.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION); partsTable.setRowSelectionAllowed(true); Action actPartsSetCheck = new AbstractAction(strings.getProperty("parts.popup.check")) { private static final long serialVersionUID = 1L; public void actionPerformed(ActionEvent e) { int[] selRows = partsTable.getSelectedRows(); partsTableModel.setCheck(selRows, true); } }; Action actPartsUnsetCheck = new AbstractAction(strings.getProperty("parts.popup.uncheck")) { private static final long serialVersionUID = 1L; public void actionPerformed(ActionEvent e) { int[] selRows = partsTable.getSelectedRows(); partsTableModel.setCheck(selRows, false); } }; final JPopupMenu partsTablePopupMenu = new JPopupMenu(); partsTablePopupMenu.add(actPartsSetCheck); partsTablePopupMenu.add(actPartsUnsetCheck); partsTable.setComponentPopupMenu(partsTablePopupMenu); add(new JScrollPane(partsTable), BorderLayout.CENTER); actSelectAll = new AbstractAction(strings.getProperty("parts.btn.selectAll")) { private static final long serialVersionUID = 1L; public void actionPerformed(ActionEvent e) { onSelectAll(); } }; actDeselectAll = new AbstractAction(strings.getProperty("parts.btn.deselectAll")) { private static final long serialVersionUID = 1L; public void actionPerformed(ActionEvent e) { onDeselectAll(); } }; actSort = new AbstractAction(strings.getProperty("parts.btn.sort")) { private static final long serialVersionUID = 1L; public void actionPerformed(ActionEvent e) { onSort(); } }; actSortByTimestamp = new AbstractAction(strings.getProperty("parts.btn.sortByTimestamp")) { private static final long serialVersionUID = 1L; public void actionPerformed(ActionEvent e) { onSortByTimestamp(); } }; JPanel btnPanel = new JPanel(); GridBagLayout btnPanelLayout = new GridBagLayout(); btnPanel.setLayout(btnPanelLayout); GridBagConstraints gbc = new GridBagConstraints(); gbc.gridx = 0; gbc.gridy = 0; gbc.gridheight = 1; gbc.gridwidth = 1; gbc.anchor = GridBagConstraints.EAST; gbc.fill = GridBagConstraints.BOTH; gbc.insets = new Insets(3, 3, 3, 3); gbc.ipadx = 0; gbc.ipady = 0; JButton btnSelectAll = new JButton(actSelectAll); btnPanel.add(btnSelectAll, gbc); gbc.gridx = 1; gbc.gridy = 0; JButton btnDeselectAll = new JButton(actDeselectAll); btnPanel.add(btnDeselectAll, gbc); gbc.gridx = 2; gbc.gridy = 0; JButton btnSort = new JButton(actSort); btnPanel.add(btnSort, gbc); gbc.gridx = 3; gbc.gridy = 0; JButton btnSortByTimestamp = new JButton(actSortByTimestamp); btnPanel.add(btnSortByTimestamp, gbc); gbc.gridx = 4; gbc.gridy = 0; gbc.weightx = 1.; btnPanel.add(Box.createHorizontalGlue(), gbc); add(btnPanel, BorderLayout.SOUTH); } protected void loadPartsInfo(PartsSpecResolver partsSpecResolver) { partsTableModel.clear(); for (PartsCategory partsCategory : partsSpecResolver.getPartsCategories()) { Map partsSpecMap = partsSpecResolver.getPartsSpecMap(partsCategory); for (Map.Entry entry : partsSpecMap.entrySet()) { PartsIdentifier partsIdentifier = entry.getKey(); PartsSpec partsSpec = entry.getValue(); ExportPartsSelectModel model = new ExportPartsSelectModel(partsIdentifier, partsSpec, false); partsTableModel.addRow(model); } } partsTableModel.sort(); } protected void onSelectAll() { partsTableModel.selectAll(); } protected void onDeselectAll() { partsTableModel.deselectAll(); } protected void onSort() { partsTableModel.sort(); if (partsTableModel.getRowCount() > 0) { Rectangle rct = partsTable.getCellRect(0, 0, true); partsTable.scrollRectToVisible(rct); } } protected void onSortByTimestamp() { partsTableModel.sortByTimestamp(); if (partsTableModel.getRowCount() > 0) { Rectangle rct = partsTable.getCellRect(0, 0, true); partsTable.scrollRectToVisible(rct); } } public Map getSelectedParts() { return partsTableModel.getSelectedParts(); } public Map> checkMissingPartsList(Collection partsSets) { return partsTableModel.checkMissingPartsList(partsSets); } public void selectByPartsSet(Collection partsSets) { partsTableModel.selectByPartsSet(partsSets); } public int getSelectedCount() { return partsTableModel.getSelectedCount(); } @Override public void setEnabled(boolean enabled) { partsTable.setEnabled(enabled); partsTableModel.setEnabled(enabled); actSelectAll.setEnabled(enabled); actDeselectAll.setEnabled(enabled); super.setEnabled(enabled); } } interface ExportPresetResolve extends ExportResolverBase { int getSelectedCount(); List getSelectedPresets(); } class ExportPresetSelectPanel extends AbstractImportPanel implements ExportPresetResolve { private static final long serialVersionUID = 1L; private ExportPartsResolver exportPartsResolver; private ExportPresetTableModel presetTableModel; private JTable presetTable; private Action actSelectAll; private Action actDeselectAll; protected ExportPresetSelectPanel( final ExportPartsResolver exportPartsResolver, final ExportInformationResolver exportInfoResolver, Collection partsSets, String defaultPresetId) { this.exportPartsResolver = exportPartsResolver; setName("presetSelectPanel"); Properties strings = LocalizedResourcePropertyLoader.getCachedInstance() .getLocalizedProperties(ExportWizardDialog.STRINGS_RESOURCE); setName("choosePartsPanel"); setBorder(BorderFactory.createTitledBorder(strings.getProperty("preset.title"))); setLayout(new BorderLayout()); presetTableModel = new ExportPresetTableModel(); presetTableModel.addTableModelListener(new TableModelListener() { public void tableChanged(TableModelEvent e) { if (e.getType() == TableModelEvent.UPDATE) { fireChangeEvent(); } } }); exportPartsResolver.addChangeListener(new ChangeListener() { public void stateChanged(ChangeEvent e) { checkMissingParts(); } }); loadPresetInfo(partsSets, defaultPresetId); AppConfig appConfig = AppConfig.getInstance(); final Color warningForegroundColor = appConfig.getExportPresetWarningsForegroundColor(); final Color disabledForeground = appConfig.getDisabledCellForgroundColor(); presetTable = new JTable(presetTableModel) { private static final long serialVersionUID = 1L; @Override public Component prepareRenderer(TableCellRenderer renderer, int row, int column) { Component comp = super.prepareRenderer(renderer, row, column); if (comp instanceof JCheckBox) { // BooleanのデフォルトのレンダラーはKCheckBoxを継承したJTable$BooleanRenderer comp.setEnabled(isCellEditable(row, column) && isEnabled()); } ExportPresetModel presetModel = presetTableModel.getRow(row); if (presetModel.isPresetParts()) { comp.setFont(getFont().deriveFont(Font.BOLD)); } else { comp.setFont(getFont()); } if (!isEnabled()) { comp.setForeground(disabledForeground); } else { if (presetModel.isSelected() && presetModel.getMissingPartsIdentifiers().size() > 0) { comp.setForeground(warningForegroundColor); } else { comp.setForeground(getForeground()); } } return comp; } }; presetTable.setShowGrid(true); presetTable.setGridColor(appConfig.getGridColor()); presetTable.putClientProperty("terminateEditOnFocusLost", Boolean.TRUE); final Action actSelectUsedParts = new AbstractAction( strings.getProperty("preset.popup.selectUsedParts")) { private static final long serialVersionUID = 1L; public void actionPerformed(ActionEvent e) { exportUsedParts(); } }; final JPopupMenu popupMenu = new JPopupMenu(); popupMenu.add(actSelectUsedParts); presetTable.setComponentPopupMenu(popupMenu); presetTable.setAutoResizeMode(JTable.AUTO_RESIZE_OFF); presetTableModel.adjustColumnModel(presetTable.getColumnModel()); add(new JScrollPane(presetTable), BorderLayout.CENTER); actSelectAll = new AbstractAction(strings.getProperty("parts.btn.selectAll")) { private static final long serialVersionUID = 1L; public void actionPerformed(ActionEvent e) { onSelectAll(); } }; actDeselectAll = new AbstractAction(strings.getProperty("parts.btn.deselectAll")) { private static final long serialVersionUID = 1L; public void actionPerformed(ActionEvent e) { onDeselectAll(); } }; Action actSort = new AbstractAction(strings.getProperty("parts.btn.sort")) { private static final long serialVersionUID = 1L; public void actionPerformed(ActionEvent e) { onSort(); } }; JPanel btnPanel = new JPanel(); GridBagLayout btnPanelLayout = new GridBagLayout(); btnPanel.setLayout(btnPanelLayout); GridBagConstraints gbc = new GridBagConstraints(); gbc.gridx = 0; gbc.gridy = 0; gbc.gridheight = 1; gbc.gridwidth = 1; gbc.anchor = GridBagConstraints.EAST; gbc.fill = GridBagConstraints.BOTH; gbc.insets = new Insets(3, 3, 3, 3); gbc.ipadx = 0; gbc.ipady = 0; JButton btnSelectAll = new JButton(actSelectAll); btnPanel.add(btnSelectAll, gbc); gbc.gridx = 1; gbc.gridy = 0; JButton btnDeselectAll = new JButton(actDeselectAll); btnPanel.add(btnDeselectAll, gbc); gbc.gridx = 2; gbc.gridy = 0; JButton btnSort = new JButton(actSort); btnPanel.add(btnSort, gbc); gbc.gridx = 3; gbc.gridy = 0; gbc.weightx = 1.; btnPanel.add(Box.createHorizontalGlue(), gbc); add(btnPanel, BorderLayout.SOUTH); } protected void loadPresetInfo(Collection partsSets, String defaultPresetId) { presetTableModel.clear(); for (PartsSet orgPartsSet : partsSets) { PartsSet partsSet = orgPartsSet.clone(); ExportPresetModel model = new ExportPresetModel(partsSet, partsSet.isPresetParts()); presetTableModel.addRow(model); } presetTableModel.setDefaultPresetId(defaultPresetId); presetTableModel.sort(); checkMissingParts(); } public void checkMissingParts() { ArrayList changedPartsSets = new ArrayList(); HashMap partsSetModelMap = new HashMap(); int mx = presetTableModel.getRowCount(); for (int idx = 0; idx < mx; idx++) { ExportPresetModel presetModel = presetTableModel.getRow(idx); PartsSet partsSet = presetModel.getPartsSet(); partsSetModelMap.put(partsSet, presetModel); changedPartsSets.add(partsSet); } Map> missingPartsIdentifiersMap = exportPartsResolver .checkMissingPartsList(changedPartsSets); for (Map.Entry> entry : missingPartsIdentifiersMap.entrySet()) { PartsSet partsSet = entry.getKey(); List missingPartsIdentifiers = entry.getValue(); ExportPresetModel presetModel = partsSetModelMap.get(partsSet); presetModel.setMissingPartsIdentifiers(missingPartsIdentifiers); } if (!missingPartsIdentifiersMap.isEmpty()) { presetTableModel.fireTableDataChanged(); } } protected void onSelectAll() { presetTableModel.selectAll(); } protected void onDeselectAll() { presetTableModel.deselectAll(); } protected void onSort() { presetTableModel.sort(); if (presetTableModel.getRowCount() > 0) { Rectangle rct = presetTable.getCellRect(0, 0, true); presetTable.scrollRectToVisible(rct); } } public List getSelectedPresets() { return presetTableModel.getSelectedPresets(); } protected void exportUsedParts() { ArrayList partsSets = new ArrayList(); int[] selRows = presetTable.getSelectedRows(); for (int selRow : selRows) { ExportPresetModel presetModel = presetTableModel.getRow(selRow); partsSets.add(presetModel.getPartsSet()); } exportPartsResolver.selectByPartsSet(partsSets); } public int getSelectedCount() { return presetTableModel.getSelectedCount(); } public String getDefaultPresetId() { return presetTableModel.getDefaultPresetId(); } @Override public void setEnabled(boolean enabled) { this.presetTable.setEnabled(enabled); this.presetTableModel.setEnabled(enabled); this.actSelectAll.setEnabled(enabled); this.actDeselectAll.setEnabled(enabled); super.setEnabled(enabled); } } class ExportPartsTableModel extends AbstractTableModelWithComboBoxModel { private static final long serialVersionUID = 1L; private static final String[] columnNames; private static final int[] columnWidths; private boolean enabled = true; static { Properties strings = LocalizedResourcePropertyLoader.getCachedInstance() .getLocalizedProperties(ExportWizardDialog.STRINGS_RESOURCE); columnNames = new String[] { strings.getProperty("parts.column.selected"), strings.getProperty("parts.column.category"), strings.getProperty("parts.column.name"), strings.getProperty("parts.column.timestamp"), strings.getProperty("parts.column.author"), strings.getProperty("parts.column.version"), }; columnWidths = new int[] { Integer.parseInt(strings.getProperty("parts.column.selected.width")), Integer.parseInt(strings.getProperty("parts.column.category.width")), Integer.parseInt(strings.getProperty("parts.column.name.width")), Integer.parseInt(strings.getProperty("parts.column.timestamp.width")), Integer.parseInt(strings.getProperty("parts.column.author.width")), Integer.parseInt(strings.getProperty("parts.column.version.width")), }; } public void adjustColumnModel(TableColumnModel columnModel) { for (int idx = 0; idx < columnWidths.length; idx++) { columnModel.getColumn(idx).setPreferredWidth(columnWidths[idx]); } } public int getColumnCount() { return columnNames.length; } @Override public String getColumnName(int column) { return columnNames[column]; } public Object getValueAt(int rowIndex, int columnIndex) { ExportPartsSelectModel partsSelectModel = getRow(rowIndex); switch (columnIndex) { case 0: return Boolean.valueOf(partsSelectModel.isChecked() && enabled); case 1: return partsSelectModel.getPartsCategory().getLocalizedCategoryName(); case 2: return partsSelectModel.getPartsName(); case 3: Timestamp tm = partsSelectModel.getTimestamp(); if (tm != null) { return tm.toString(); } return ""; case 4: return partsSelectModel.getAuthor(); case 5: return partsSelectModel.getVersion(); default: } return ""; } @Override public void setValueAt(Object aValue, int rowIndex, int columnIndex) { ExportPartsSelectModel partsSelectModel = getRow(rowIndex); switch (columnIndex) { case 0: partsSelectModel.setChecked(((Boolean) aValue).booleanValue()); break; default: return; } fireTableRowsUpdated(rowIndex, rowIndex); } @Override public boolean isCellEditable(int rowIndex, int columnIndex) { if (columnIndex == 0) { return isEnabled(); } return false; } @Override public Class getColumnClass(int columnIndex) { switch (columnIndex) { case 0: return Boolean.class; case 1: return String.class; case 2: return String.class; case 3: return String.class; default: } return String.class; } public void sort() { Collections.sort(elements); fireTableDataChanged(); } public void sortByTimestamp() { Collections.sort(elements, new Comparator() { public int compare(ExportPartsSelectModel o1, ExportPartsSelectModel o2) { int ret = 0; Timestamp t1 = o1.getTimestamp(); Timestamp t2 = o2.getTimestamp(); if (t1 == null || t2 == null) { if (t1 == null && t2 == null) { ret = 0; } else if (t1 == null) { ret = 1; } else { ret = -1; } } else { ret = t2.compareTo(t1); // 逆順(日付の新しいもの順) } if (ret == 0) { ret = o1.compareTo(o2); } return ret; } }); fireTableDataChanged(); } public void selectAll() { for (ExportPartsSelectModel model : elements) { model.setChecked(true); } fireTableDataChanged(); } public void deselectAll() { for (ExportPartsSelectModel model : elements) { model.setChecked(false); } fireTableDataChanged(); } /** * 選択されているパーツイメージのマップを返す.
* @return 選択されているパーツイメージのマップ */ public Map getSelectedParts() { HashMap selectedPartsMap = new HashMap(); for (ExportPartsSelectModel partsSelectModel : elements) { if (partsSelectModel.isChecked() && isEnabled()) { selectedPartsMap.put(partsSelectModel.getPartsIdentifier(), partsSelectModel.getPartsSpec()); } } return selectedPartsMap; } /** * パーツセットのコレクションを指定し、パーツセットの各パーツがすべてエクスポート対象になっているものだけを返す.
* @param partsSets パーツセットのリスト * @return 不足しているパーツセットと、その不足しているパーツリストを返す. */ public Map> checkMissingPartsList(Collection partsSets) { if (partsSets == null) { throw new IllegalArgumentException(); } Map selectedPartsMap = getSelectedParts(); HashMap> missingPartsMap = new HashMap>(); for (PartsSet partsSet : partsSets) { ArrayList missingPartss = new ArrayList(); for (List partsIdentifiers : partsSet.values()) { for (PartsIdentifier requirePartsIdentifier : partsIdentifiers) { if (!selectedPartsMap.containsKey(requirePartsIdentifier)) { missingPartss.add(requirePartsIdentifier); } } } Collections.sort(missingPartss); missingPartsMap.put(partsSet, missingPartss); } return missingPartsMap; } /** * パーツセットで使用されているパーツを選択状態にする. * @param partsSet パーツセットのコレクション */ public void selectByPartsSet(Collection partsSets) { if (partsSets == null) { throw new IllegalArgumentException(); } HashSet requirePartsIdentifiers = new HashSet(); for (PartsSet partsSet : partsSets) { for (List partsIdentifiers : partsSet.values()) { for (PartsIdentifier partsIdentifier : partsIdentifiers) { requirePartsIdentifiers.add(partsIdentifier); } } } for (ExportPartsSelectModel partsSelectModel : elements) { if (requirePartsIdentifiers.contains(partsSelectModel.getPartsIdentifier())) { partsSelectModel.setChecked(true); } } fireTableDataChanged(); } /** * 選択されているパーツ数を返す. * @return パーツ数 */ public int getSelectedCount() { int count = 0; for (ExportPartsSelectModel partsSelectModel : elements) { if (partsSelectModel.isChecked() && isEnabled()) { count++; } } return count; } public void setEnabled(boolean enabled) { if (this.enabled != enabled) { this.enabled = enabled; fireTableDataChanged(); } } public boolean isEnabled() { return enabled; } public void setCheck(int[] selRows, boolean checked) { if (selRows == null || selRows.length == 0) { return; } Arrays.sort(selRows); for (int selRow : selRows) { ExportPartsSelectModel rowModel = getRow(selRow); rowModel.setChecked(checked); } fireTableRowsUpdated(selRows[0], selRows[selRows.length - 1]); } } class ExportPartsSelectModel implements Comparable { private boolean checked; private PartsIdentifier partsIdentifier; private PartsSpec partsSpec; private Timestamp timestamp; public ExportPartsSelectModel(PartsIdentifier partsIdentifier, PartsSpec partsSpec, boolean selected) { if (partsIdentifier == null || partsSpec == null) { throw new IllegalArgumentException(); } this.partsIdentifier = partsIdentifier; this.partsSpec = partsSpec; this.checked = selected; long maxLastModified = partsSpec.getPartsFiles().lastModified(); if (maxLastModified > 0) { timestamp = new Timestamp(maxLastModified); } else { timestamp = null; } } @Override public int hashCode() { return partsIdentifier.hashCode(); } @Override public boolean equals(Object obj) { if (obj == this) { return true; } if (obj != null && obj instanceof ExportPartsSelectModel) { ExportPartsSelectModel o = (ExportPartsSelectModel) obj; return partsIdentifier.equals(o.partsIdentifier); } return false; } public int compareTo(ExportPartsSelectModel o) { int ret = (checked ? 0 : 1) - (o.checked ? 0 : 1); // 逆順 if (ret == 0) { ret = partsIdentifier.compareTo(o.partsIdentifier); } return ret; } public PartsIdentifier getPartsIdentifier() { return this.partsIdentifier; } public PartsSpec getPartsSpec() { return this.partsSpec; } public boolean isChecked() { return checked; } public void setChecked(boolean checked) { this.checked = checked; } public PartsCategory getPartsCategory() { return this.partsIdentifier.getPartsCategory(); } public String getPartsName() { return this.partsIdentifier.getLocalizedPartsName(); } public Timestamp getTimestamp() { return timestamp == null ? null : (Timestamp) timestamp.clone(); } public String getAuthor() { return partsSpec.getAuthor(); } public String getVersion() { double version = partsSpec.getVersion(); if (version <= 0) { return ""; } return Double.toString(version); } } class ExportPresetTableModel extends AbstractTableModelWithComboBoxModel { private static final long serialVersionUID = 1L; private static final String[] columnNames; private static final int[] columnWidths; private boolean enabled = true; static { Properties strings = LocalizedResourcePropertyLoader.getCachedInstance() .getLocalizedProperties(ExportWizardDialog.STRINGS_RESOURCE); columnNames = new String[] { strings.getProperty("preset.column.selected"), strings.getProperty("preset.column.default"), strings.getProperty("preset.column.name"), strings.getProperty("preset.column.missingparts"), }; columnWidths = new int[] { Integer.parseInt(strings.getProperty("preset.column.selected.width")), Integer.parseInt(strings.getProperty("preset.column.default.width")), Integer.parseInt(strings.getProperty("preset.column.name.width")), Integer.parseInt(strings.getProperty("preset.column.missingparts.width")), }; } private String defaultPresetId; public void adjustColumnModel(TableColumnModel columnModel) { for (int idx = 0; idx < columnWidths.length; idx++) { columnModel.getColumn(idx).setPreferredWidth(columnWidths[idx]); } } public int getColumnCount() { return columnNames.length; } @Override public String getColumnName(int column) { return columnNames[column]; } public Object getValueAt(int rowIndex, int columnIndex) { ExportPresetModel presetModel = getRow(rowIndex); switch (columnIndex) { case 0: return Boolean.valueOf(presetModel.isSelected() && isEnabled()); case 1: return Boolean.valueOf(presetModel.getPartsSet().getPartsSetId().equals(defaultPresetId) && isEnabled()); case 2: return presetModel.getPartsSetName(); case 3: StringBuilder buf = new StringBuilder(); for (PartsIdentifier partsIdentifier : presetModel.getMissingPartsIdentifiers()) { if (buf.length() > 0) { buf.append(", "); } buf.append(partsIdentifier.getLocalizedPartsName()); } return buf.toString(); default: } return ""; } @Override public void setValueAt(Object aValue, int rowIndex, int columnIndex) { ExportPresetModel presetModel = getRow(rowIndex); switch (columnIndex) { case 0: if (((Boolean) aValue).booleanValue()) { presetModel.setSelected(true); } else { presetModel.setSelected(false); if (presetModel.getPartsSet().getPartsSetId().equals(defaultPresetId)) { // 選択解除したものが既定のパーツセットであった場合、既定も解除する. defaultPresetId = null; fireTableRowsUpdated(rowIndex, rowIndex); return; } } break; case 1: if (((Boolean) aValue).booleanValue()) { defaultPresetId = presetModel.getPartsSet().getPartsSetId(); presetModel.setSelected(true); // 既定のパーツセットにした場合は自動的にエクスポート対象にもする。 fireTableDataChanged(); return; } default: return; } fireTableRowsUpdated(rowIndex, rowIndex); } @Override public Class getColumnClass(int columnIndex) { switch (columnIndex) { case 0: return Boolean.class; case 1: return Boolean.class; case 2: return String.class; case 3: return String.class; default: } return String.class; } @Override public boolean isCellEditable(int rowIndex, int columnIndex) { if (columnIndex == 0 || columnIndex == 1) { return isEnabled(); } return false; } public void sort() { Collections.sort(elements); fireTableDataChanged(); } public void selectAll() { for (ExportPresetModel model : elements) { model.setSelected(true); } fireTableDataChanged(); } public void deselectAll() { for (ExportPresetModel model : elements) { model.setSelected(false); } fireTableDataChanged(); } /** * 選択されているパーツセットのリストを返す.
* なにもなければ空.
* @return 選択されているパーツセットのリスト */ public List getSelectedPresets() { ArrayList partsSets = new ArrayList(); for (ExportPresetModel presetModel : elements) { if (presetModel.isSelected() && isEnabled()) { PartsSet partsSet = presetModel.getPartsSet().clone(); partsSet.setPresetParts(true); partsSets.add(partsSet); } } return partsSets; } public int getSelectedCount() { int count = 0; for (ExportPresetModel presetModel : elements) { if (presetModel.isSelected() && isEnabled()) { count++; } } return count; } public String getDefaultPresetId() { return defaultPresetId; } /** * デフォルトのプリセットを設定する.
* @param defaultPresetId */ public void setDefaultPresetId(String defaultPresetId) { this.defaultPresetId = defaultPresetId; } public void setEnabled(boolean enabled) { if (this.enabled != enabled) { this.enabled = enabled; fireTableDataChanged(); } } public boolean isEnabled() { return enabled; } } class ExportPresetModel implements Comparable { private boolean selected; private PartsSet partsSet; private List missingPartsIdentifiers; public ExportPresetModel(PartsSet partsSet, boolean selected) { if (partsSet == null) { throw new IllegalArgumentException(); } this.partsSet = partsSet; this.selected = selected; } @Override public int hashCode() { return partsSet.hashCode(); } @Override public boolean equals(Object obj) { if (obj == this) { return true; } if (obj != null && obj instanceof ExportPresetModel) { ExportPresetModel o = (ExportPresetModel) obj; return partsSet.equals(o.partsSet); } return false; } public int compareTo(ExportPresetModel o) { int ret = (selected ? 0 : 1) - (o.selected ? 0 : 1); if (ret == 0) { ret = getPartsSetName().compareTo(o.getPartsSetName()); } return ret; } public String getPartsSetName() { String name = partsSet.getLocalizedName(); return name == null ? "" : name; } public boolean isPresetParts() { return partsSet.isPresetParts(); } public boolean isSelected() { return selected; } public void setSelected(boolean selected) { this.selected = selected; } public PartsSet getPartsSet() { return partsSet; } public void setMissingPartsIdentifiers( List missingPartsIdentifiers) { this.missingPartsIdentifiers = Collections.unmodifiableList(missingPartsIdentifiers); } public List getMissingPartsIdentifiers() { if (missingPartsIdentifiers == null) { return Collections.emptyList(); } return missingPartsIdentifiers; } } CharacterManaJ/src/charactermanaj/ui/ColorDialog.java0000644000175000017500000011740112560206305023004 0ustar paulliupaulliupackage charactermanaj.ui; import java.awt.BorderLayout; import java.awt.Container; import java.awt.GridBagConstraints; import java.awt.GridBagLayout; import java.awt.GridLayout; import java.awt.Insets; import java.awt.Toolkit; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.ComponentAdapter; import java.awt.event.ComponentEvent; import java.awt.event.KeyEvent; import java.awt.event.WindowAdapter; import java.awt.event.WindowEvent; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.util.Collection; import java.util.HashMap; import java.util.LinkedList; import java.util.Map; import java.util.Properties; import java.util.concurrent.Semaphore; import java.util.concurrent.atomic.AtomicInteger; import java.util.logging.Level; import java.util.logging.Logger; import javax.swing.AbstractAction; import javax.swing.Action; import javax.swing.ActionMap; import javax.swing.BorderFactory; import javax.swing.Box; import javax.swing.BoxLayout; import javax.swing.InputMap; import javax.swing.JButton; import javax.swing.JCheckBox; import javax.swing.JComboBox; import javax.swing.JComponent; import javax.swing.JDialog; import javax.swing.JFrame; import javax.swing.JLabel; import javax.swing.JPanel; import javax.swing.JRootPane; import javax.swing.JSlider; import javax.swing.JSpinner; import javax.swing.JTabbedPane; import javax.swing.KeyStroke; import javax.swing.SpinnerNumberModel; import javax.swing.event.ChangeEvent; import javax.swing.event.ChangeListener; import charactermanaj.Main; import charactermanaj.graphics.colormodel.ColorModel; import charactermanaj.graphics.colormodel.ColorModels; import charactermanaj.graphics.filters.ColorConv; import charactermanaj.graphics.filters.ColorConvertParameter; import charactermanaj.model.AppConfig; import charactermanaj.model.ColorGroup; import charactermanaj.model.Layer; import charactermanaj.model.PartsCategory; import charactermanaj.model.PartsIdentifier; import charactermanaj.ui.model.ColorChangeEvent; import charactermanaj.ui.model.ColorChangeListener; import charactermanaj.ui.util.SpinnerWheelSupportListener; import charactermanaj.util.LocalizedResourcePropertyLoader; /** * カラーダイアログ.
* カラーダイアログはカテゴリ別に関連づけられており、カテゴリ内の各レイヤーに対応するタブを持つ.
* * @author seraphy */ public class ColorDialog extends JDialog { private static final long serialVersionUID = 1L; /** * ロガー */ private static final Logger logger = Logger.getLogger(ColorDialog.class.getName()); /** * このカラーダイアログが対応するカテゴリ */ private final PartsCategory partsCategory; /** * レイヤーごとのタブのマップ */ private HashMap tabs = new HashMap(); /** * タブペイン */ private JTabbedPane tabbedPane; /** * レイヤーに対するタブインデックスのマップ */ private HashMap tabbedPaneIndexMap = new HashMap(); /** * 色変更イベントのリスナのコレクション */ private LinkedList listeners = new LinkedList(); /** * キャプションのプレフィックス */ private String captionBase; /** * 現在表示しているカラー情報の対象としているパーツ識別子 */ private PartsIdentifier partsIdentifier; /** * カテゴリ全体に適用するチェックボックス */ private JCheckBox chkApplyAll; /** * リセットアクション */ private Action actReset; /** * コンストラクタ * * @param parent * 親フレーム * @param partsCategory * カテゴリ * @param colorGroups * 選択可能なカラーグループのコレクション */ public ColorDialog(JFrame parent, PartsCategory partsCategory, Collection colorGroups) { super(parent); this.partsCategory = partsCategory; final Properties strings = LocalizedResourcePropertyLoader.getCachedInstance().getLocalizedProperties("languages/colordialog"); String caption = strings.getProperty("colordialog.caption"); String name = partsCategory.getLocalizedCategoryName(); captionBase = caption + name; setTitle(captionBase); // ダイアログを非表示にする. final AbstractAction actHide = new AbstractAction() { private static final long serialVersionUID = 1L; public void actionPerformed(ActionEvent e) { setVisible(false); } }; // 非表示アクション addWindowListener(new WindowAdapter() { @Override public void windowClosing(WindowEvent e) { // ウィンドウの閉じるボタン押下により、ダイアログを「非表示」にする. actHide.actionPerformed(new ActionEvent(ColorDialog.this, 0, "closing")); } }); Container container = getContentPane(); this.tabbedPane = new JTabbedPane(JTabbedPane.TOP, JTabbedPane.WRAP_TAB_LAYOUT); for (final Layer layer : partsCategory.getLayers()) { final ColorDialogTabPanel tabContainer = new ColorDialogTabPanel(this, layer, colorGroups); final ColorChangeListener innerListener = new ColorChangeListener() { private Semaphore semaphore = new Semaphore(1); // イベントが循環発生することを防ぐ public void onColorChange(ColorChangeEvent event) { if (semaphore.tryAcquire()) { try { ColorDialog.this.fireColorChangeEvent(layer, false); } finally { semaphore.release(); } } } public void onColorGroupChange(ColorChangeEvent event) { if (semaphore.tryAcquire()) { try { ColorDialog.this.fireColorGroupChangeEvent(layer); ColorDialog.this.fireColorChangeEvent(layer, false); } finally { semaphore.release(); } } } }; tabContainer.addColorChangeListener(innerListener); tabContainer.addComponentListener(new ComponentAdapter() { @Override public void componentShown(ComponentEvent e) { // レイヤータブが切り替えられるたびに、そのレイヤーの状況にあわせてリセットボタンの状態を更新する. updateResetButton(tabContainer); } }); tabContainer.addPropertyChangeListener("colorConvertParameter", new PropertyChangeListener() { public void propertyChange(PropertyChangeEvent evt) { // レイヤーの情報が変るたびにリセットボタンの状態を更新する updateResetButton(tabContainer); } }); tabbedPane.addTab(layer.getLocalizedName(), tabContainer); tabbedPaneIndexMap.put(layer, tabbedPane.getTabCount() - 1); tabs.put(layer, tabContainer); } // 適用アクション Action actApply = new AbstractAction(strings.getProperty("button.apply")) { private static final long serialVersionUID = 1L; public void actionPerformed(ActionEvent e) { apply(); } }; // リセットアクション actReset = new AbstractAction(strings.getProperty("button.reset")) { private static final long serialVersionUID = 1L; public void actionPerformed(ActionEvent e) { ColorDialogTabPanel tab = (ColorDialogTabPanel) tabbedPane .getSelectedComponent(); if (tab != null) { resetColor(tab); apply(); } } }; JPanel btnPanel = new JPanel(); btnPanel.setBorder(BorderFactory.createEmptyBorder(3, 3, 3, 3)); GridBagLayout gbl = new GridBagLayout(); btnPanel.setLayout(gbl); int colIdx = 0; GridBagConstraints gbc = new GridBagConstraints(); gbc.gridx = colIdx++; gbc.gridy = 0; gbc.ipadx = 0; gbc.ipady = 0; gbc.anchor = GridBagConstraints.EAST; gbc.fill = GridBagConstraints.BOTH; gbc.gridheight = 1; gbc.gridwidth = 1; gbc.weightx = 0.; gbc.weighty = 0.; chkApplyAll = new JCheckBox(strings.getProperty("checkbox.applyAllItems")); chkApplyAll.setSelected(!partsCategory.isMultipleSelectable()); chkApplyAll.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { // すべてに適用のチェックが変更された場合は全レイヤーの色の変更通知を出す. apply(); } }); btnPanel.add(chkApplyAll, gbc); gbc.gridx = colIdx++; gbc.gridy = 0; gbc.weightx = 1.; btnPanel.add(Box.createHorizontalGlue(), gbc); // 「適用」ボタン、アプリケーション設定により有無を選択できる. JButton btnApply = null; AppConfig appConfig = AppConfig.getInstance(); if ( !appConfig.isEnableAutoColorChange()) { gbc.gridx = colIdx++; gbc.gridy = 0; gbc.weightx = 0.; btnApply = new JButton(actApply); btnPanel.add(btnApply, gbc); } gbc.gridx = colIdx++; gbc.gridy = 0; gbc.weightx = 0.; JButton btnReset = new JButton(actReset); btnPanel.add(btnReset, gbc); container.setLayout(new BorderLayout()); container.add(tabbedPane, BorderLayout.CENTER); container.add(btnPanel, BorderLayout.SOUTH); Toolkit tk = Toolkit.getDefaultToolkit(); JRootPane rootPane = getRootPane(); // 「適用」ボタンがある場合は、デフォルトボタンに設定する. if (btnApply != null) { rootPane.setDefaultButton(btnApply); } // CTRL-Wでウィンドウを非表示にする. (ESCでは非表示にしない) InputMap im = rootPane.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW); ActionMap am = rootPane.getActionMap(); im.put(KeyStroke.getKeyStroke(KeyEvent.VK_W, tk.getMenuShortcutKeyMask()), "hideColorDialog"); am.put("hideColorDialog", actHide); pack(); } /** * 各レイヤーのカラー情報のタブが開かれた場合、もしくはカラーの設定値が変更されるたびに 呼び出されて、リセットボタンの状態を変更します.
* 現在のタブが選択しているパネルと異なるパネルからの要求については無視されます.
* * @param panel * 色情報が変更された、もしくは開かれた対象パネル */ protected void updateResetButton(ColorDialogTabPanel panel) { ColorDialogTabPanel currentPanel = (ColorDialogTabPanel) tabbedPane.getSelectedComponent(); if (currentPanel != null && currentPanel.equals(panel)) { actReset.setEnabled(panel.isColorConvertParameterModified()); } } /** * このカラーダイアログが対応するパーツカテゴリを取得する.
* * @return パーツカテゴリ */ public PartsCategory getPartsCategory() { return partsCategory; } /** * 指定したレイヤーのカラーグループが「連動」しているか?
* カテゴリに属していないレイヤーを指定した場合は常にfalseを返す.
* * @param layer * レイヤー * @return 連動している場合はtrue、そうでなければfalse */ public boolean isSyncColorGroup(Layer layer) { ColorDialogTabPanel tab = tabs.get(layer); if (tab == null) { return false; } return tab.isSyncColorGroup(); } /** * 指定したレイヤーのカラーグループの連動フラグを設定する.
* カテゴリに属していないレイヤーを指定した場合は何もしない.
* * @param layer * レイヤー * @param selected * 連動フラグ */ public void setSyncColorGroup(Layer layer, boolean selected) { ColorDialogTabPanel tab = tabs.get(layer); if (tab != null) { tab.setSyncColorGroup(selected); } } /** * レイヤーごとの色情報のマップを指定して、各レイヤーに色情報を設定する.
* カテゴリに属していないレイヤーが含まれる場合は例外となる.
* * @param params * レイヤーと色情報のマップ */ public void setColorConvertParameters(Map params) { if (params == null) { throw new IllegalArgumentException(); } for (Layer layer : partsCategory.getLayers()) { ColorConvertParameter param = params.get(layer); if (param == null) { param = new ColorConvertParameter(); } setColorConvertParameter(layer, param); } } /** * 対象となるパーツ識別子を指定する。
* カラーダイアログのキャプションにパーツ名を設定される.
* nullを指定した場合はキャプションからパーツ名が除去される.
* * @param partsIdentifier * パーツ識別子、もしくはnull */ public void setPartsIdentifier(PartsIdentifier partsIdentifier) { this.partsIdentifier = partsIdentifier; if (partsIdentifier == null) { setTitle(captionBase); } else { setTitle(captionBase + "(" + partsIdentifier.getLocalizedPartsName() + ")"); } } /** * 対象となるパーツ識別子を取得する.
* 設定されていなければnullが返される.
* * @return パーツ識別子、もしくはnull */ public PartsIdentifier getPartsIdentifier() { return partsIdentifier; } /** * 各レイヤーのタブの有効・無効状態を設定します.
* カテゴリに属さないレイヤーは無視されます.
* nullを指定した場合は、すべてのレイヤーが「有効」となります.
* * @param layers * 有効とするレイヤーのコレクション、もしくはnull */ public void setEnableLayers(Collection layers) { for (Map.Entry entry : tabs.entrySet()) { Layer layer = entry.getKey(); boolean enabled = (layers == null) || layers.contains(layer); Integer tabIndex = tabbedPaneIndexMap.get(layer); if (tabIndex != null) { if (Main.isMacOSX()) { // OSXの場合、タブをディセーブルにしても表示が変化ないので // タブタイトルを変更することでディセーブルを示す. // (html3で表現しようとしたところ、かなりバギーだったため採用せず) tabbedPane.setTitleAt(tabIndex, enabled ? layer.getLocalizedName() : "-"); } tabbedPane.setEnabledAt(tabIndex, enabled); if (logger.isLoggable(Level.FINEST)) { logger.log(Level.FINEST, "setEnableLayers(" + layer + ")=" + enabled); } } } } /** * 「すべてに適用」フラグを取得する.
* * @return すべてに適用フラグ */ public boolean isApplyAll() { return chkApplyAll.isSelected(); } /** * 各レイヤーと、その色情報をマップとして取得する.
* * @return 各レイヤーと、その色情報のマップ */ public Map getColorConvertParameters() { HashMap params = new HashMap(); for (Layer layer : partsCategory.getLayers()) { ColorDialogTabPanel tab = tabs.get(layer); ColorConvertParameter param = tab.getColorConvertParameter(); params.put(layer, param); } return params; } /** * レイヤーを指定して、色情報を設定する.
* カテゴリに属していないレイヤーを指定した場合は例外となる.
* * @param layer * レイヤー * @param param * 色情報 */ public void setColorConvertParameter(Layer layer, ColorConvertParameter param) { if (layer == null || param == null) { throw new IllegalArgumentException(); } ColorDialogTabPanel tab = tabs.get(layer); if (tab == null) { throw new IllegalArgumentException("layer not found. " + layer + "/tabs=" + tabs); } tab.setColorConvertParameter(param); } /** * 指定したレイヤーの色情報を取得する.
* カテゴリに属していないレイヤーを指定した場合は例外となる.
* * @param layer * レイヤー * @return 色情報 */ public ColorConvertParameter getColorConvertParameter(Layer layer) { if (layer == null) { throw new IllegalArgumentException(); } ColorDialogTabPanel tab = tabs.get(layer); if (tab == null) { throw new IllegalArgumentException("layer not found. " + layer); } return tab.getColorConvertParameter(); } /** * 指定したレイヤーのカラーグループを取得する.
* カテゴリに属さないレイヤーを指定した場合は例外となる.
* * @param layer * レイヤー * @return カラーグループ */ public ColorGroup getColorGroup(Layer layer) { if (layer == null) { throw new IllegalArgumentException(); } ColorDialogTabPanel tab = tabs.get(layer); if (tab == null) { throw new IllegalArgumentException("layer not found. " + layer); } return tab.getColorGroup(); } /** * 指定したレイヤーのカラーグループを設定する.
* カテゴリに属さないレイヤーを指定した場合は例外となる.
* * @param layer * レイヤー * @param colorGroup * カラーグループ */ public void setColorGroup(Layer layer, ColorGroup colorGroup) { if (layer == null || colorGroup == null) { throw new IllegalArgumentException(); } ColorDialogTabPanel tab = tabs.get(layer); if (tab != null) { tab.setColorGroup(colorGroup); } } /** * 色ダイアログが変更された場合に通知を受けるリスナーを登録する.
* * @param listener * リスナー */ public void addColorChangeListener(ColorChangeListener listener) { if (listener == null) { throw new IllegalArgumentException(); } listeners.add(listener); } /** * 色ダイアログが変更された場合に通知を受けるリスナーを登録解除する.
* * @param listener */ public void removeColorChangeListener(ColorChangeListener listener) { listeners.remove(listener); } /** * 全レイヤーに対するカラー変更イベントを明示的に送信する. */ protected void apply() { for (Layer layer : getPartsCategory().getLayers()) { ColorDialog.this.fireColorChangeEvent(layer, true); } } /** * カラーをリセットする. */ protected void resetColor(ColorDialogTabPanel tab) { tab.resetColor(); } /** * 指定したレイヤーに対するカラー変更イベントを通知する.
* ただし、force引数がfalseである場合、アプリケーション設定で即時プレビューが指定されていない場合は通知しない.
* * @param layer * レイヤー * @param force * アプリケーション設定に関わらず送信する場合はtrue */ protected void fireColorChangeEvent(Layer layer, boolean force) { if (layer == null) { throw new IllegalArgumentException(); } if (!force) { AppConfig appConfig = AppConfig.getInstance(); if (!appConfig.isEnableAutoColorChange()) { return; } } ColorChangeEvent event = new ColorChangeEvent(this, layer); for (ColorChangeListener listener : listeners) { listener.onColorChange(event); } } /** * 色グループが変更されたことを通知する.
* * @param layer * レイヤー */ protected void fireColorGroupChangeEvent(Layer layer) { if (layer == null) { throw new IllegalArgumentException(); } ColorChangeEvent event = new ColorChangeEvent(this, layer); for (ColorChangeListener listener : listeners) { listener.onColorGroupChange(event); } } @Override public String toString() { return "ColorDialog(partsCategory:" + partsCategory + ")"; } } class ColorDialogTabPanel extends JPanel { private static final long serialVersionUID = 1L; private JComboBox cmbColorReplace; private JSpinner spinGray; private JSpinner spinOffsetR; private JSpinner spinOffsetG; private JSpinner spinOffsetB; private JSpinner spinOffsetA; private JSpinner spinFactorR; private JSpinner spinFactorG; private JSpinner spinFactorB; private JSpinner spinFactorA; private JSpinner spinHue; private JSpinner spinSaturation; private JSpinner spinBrightness; private JSpinner spinContrast; private JSpinner spinGammaR; private JSpinner spinGammaG; private JSpinner spinGammaB; private JSpinner spinGammaA; private JComboBox cmbColorGroup; private JCheckBox chkColorGroupSync; private final ColorDialog parent; /** * パラメータの明示的変更時に他のパラメータへの反映イベントを停止させるためのセマフォ */ private AtomicInteger changeEventDisableSemaphore = new AtomicInteger(); /** * 明示的に設定されたカラーパラメータを保存する.(リセットに使用するため) */ private ColorConvertParameter paramOrg = new ColorConvertParameter(); private ColorConvertParameter chachedParam; private LinkedList listeners = new LinkedList(); public void addColorChangeListener(ColorChangeListener listener) { if (listener == null) { throw new IllegalArgumentException(); } listeners.add(listener); } public void removeColorChangeListener(ColorChangeListener listener) { listeners.remove(listener); } protected void fireColorChangeEvent(Layer layer) { if (layer == null) { throw new IllegalArgumentException(); } chachedParam = null; if (changeEventDisableSemaphore.get() <= 0) { ColorChangeEvent event = new ColorChangeEvent(parent, layer); for (ColorChangeListener listener : listeners) { listener.onColorChange(event); } } } protected void fireColorGroupChangeEvent(Layer layer) { if (layer == null) { throw new IllegalArgumentException(); } chachedParam = null; if (changeEventDisableSemaphore.get() <= 0) { ColorChangeEvent event = new ColorChangeEvent(parent, layer); for (ColorChangeListener listener : listeners) { listener.onColorGroupChange(event); } } } public ColorDialogTabPanel(final ColorDialog parent, final Layer layer, Collection colorGroups) { if (parent == null || layer == null || colorGroups == null) { throw new IllegalArgumentException(); } this.parent = parent; final Properties strings = LocalizedResourcePropertyLoader.getCachedInstance().getLocalizedProperties("languages/colordialog"); setLayout(new BorderLayout()); JPanel container = new JPanel(); BoxLayout boxlayout = new BoxLayout(container, BoxLayout.PAGE_AXIS); container.setLayout(boxlayout); add(container, BorderLayout.NORTH); // 変更イベントハンドラ final ChangeListener changeEventHandler = new ChangeListener() { public void stateChanged(ChangeEvent e) { fireColorChangeEvent(layer); firePropertyChange("colorConvertParameter", null, null); } }; // 色置換パネル JPanel colorReplacePanel = new JPanel(); colorReplacePanel.setBorder(BorderFactory.createCompoundBorder( BorderFactory.createEmptyBorder(3, 3, 3, 3), BorderFactory.createTitledBorder(strings.getProperty("group.replacergb.caption")))); GridBagLayout gbl = new GridBagLayout(); colorReplacePanel.setLayout(gbl); GridBagConstraints gbc = new GridBagConstraints(); JLabel lblColorReplace = new JLabel(strings.getProperty("replacergb"), JLabel.RIGHT); cmbColorReplace = new JComboBox(ColorConv.values()); JLabel lblGray = new JLabel(strings.getProperty("bright"), JLabel.RIGHT); SpinnerNumberModel grayModel = new SpinnerNumberModel(1., 0., 1., 0.01); grayModel.addChangeListener(changeEventHandler); cmbColorReplace.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { fireColorChangeEvent(layer); firePropertyChange("colorConvertParameter", null, null); } }); spinGray = new JSpinner(grayModel); spinGray.addMouseWheelListener(new SpinnerWheelSupportListener(grayModel)); gbc.gridx = 0; gbc.gridy = 0; gbc.gridwidth = 1; gbc.gridheight = 1; gbc.weightx = 0.; gbc.weighty = 0.; gbc.anchor = GridBagConstraints.WEST; gbc.fill = GridBagConstraints.BOTH; gbc.ipadx = 0; gbc.ipady = 0; gbc.insets = new Insets(3, 3, 3, 3); colorReplacePanel.add(lblColorReplace, gbc); gbc.gridx = 1; gbc.gridy = 0; gbc.gridwidth = 1; gbc.gridheight = 1; gbc.weightx = 1.; gbc.weighty = 0.; colorReplacePanel.add(cmbColorReplace, gbc); gbc.gridx = 2; gbc.gridy = 0; gbc.gridwidth = 1; gbc.gridheight = 1; gbc.weightx = 0.; gbc.weighty = 0.; colorReplacePanel.add(lblGray, gbc); gbc.gridx = 3; gbc.gridy = 0; gbc.weightx = 1.; gbc.weighty = 0.; gbc.gridwidth = 1; gbc.gridheight = 1; colorReplacePanel.add(spinGray, gbc); // RGB変更パネル JPanel colorLevelPanel = new JPanel(); colorLevelPanel.setBorder(BorderFactory.createCompoundBorder( BorderFactory.createEmptyBorder(3, 3, 3, 3), BorderFactory.createTitledBorder(strings.getProperty("group.rgb.caption")))); GridLayout gl = new GridLayout(4, 5); gl.setHgap(2); gl.setVgap(2); colorLevelPanel.setLayout(gl); colorLevelPanel.add(Box.createGlue()); colorLevelPanel.add(new JLabel(strings.getProperty("red"), JLabel.CENTER)); colorLevelPanel.add(new JLabel(strings.getProperty("green"), JLabel.CENTER)); colorLevelPanel.add(new JLabel(strings.getProperty("blue"), JLabel.CENTER)); colorLevelPanel.add(new JLabel(strings.getProperty("alpha"), JLabel.CENTER)); colorLevelPanel.add(new JLabel(strings.getProperty("offset"), JLabel.RIGHT)); SpinnerNumberModel offsetModelR = new SpinnerNumberModel(0, -255, 255, 1); SpinnerNumberModel offsetModelG = new SpinnerNumberModel(0, -255, 255, 1); SpinnerNumberModel offsetModelB = new SpinnerNumberModel(0, -255, 255, 1); SpinnerNumberModel offsetModelA = new SpinnerNumberModel(0, -255, 255, 1); offsetModelR.addChangeListener(changeEventHandler); offsetModelG.addChangeListener(changeEventHandler); offsetModelB.addChangeListener(changeEventHandler); offsetModelA.addChangeListener(changeEventHandler); spinOffsetR = new JSpinner(offsetModelR); spinOffsetG = new JSpinner(offsetModelG); spinOffsetB = new JSpinner(offsetModelB); spinOffsetA = new JSpinner(offsetModelA); spinOffsetR.addMouseWheelListener(new SpinnerWheelSupportListener(offsetModelR)); spinOffsetG.addMouseWheelListener(new SpinnerWheelSupportListener(offsetModelG)); spinOffsetB.addMouseWheelListener(new SpinnerWheelSupportListener(offsetModelB)); spinOffsetA.addMouseWheelListener(new SpinnerWheelSupportListener(offsetModelA)); colorLevelPanel.add(spinOffsetR); colorLevelPanel.add(spinOffsetG); colorLevelPanel.add(spinOffsetB); colorLevelPanel.add(spinOffsetA); colorLevelPanel.add(new JLabel(strings.getProperty("factor"), JLabel.RIGHT)); SpinnerNumberModel factorModelR = new SpinnerNumberModel(1., 0.01, 99, 0.01); SpinnerNumberModel factorModelG = new SpinnerNumberModel(1., 0.01, 99, 0.01); SpinnerNumberModel factorModelB = new SpinnerNumberModel(1., 0.01, 99, 0.01); SpinnerNumberModel factorModelA = new SpinnerNumberModel(1., 0.01, 99, 0.01); factorModelR.addChangeListener(changeEventHandler); factorModelG.addChangeListener(changeEventHandler); factorModelB.addChangeListener(changeEventHandler); factorModelA.addChangeListener(changeEventHandler); spinFactorR = new JSpinner(factorModelR); spinFactorG = new JSpinner(factorModelG); spinFactorB = new JSpinner(factorModelB); spinFactorA = new JSpinner(factorModelA); spinFactorR.addMouseWheelListener(new SpinnerWheelSupportListener(factorModelR)); spinFactorG.addMouseWheelListener(new SpinnerWheelSupportListener(factorModelG)); spinFactorB.addMouseWheelListener(new SpinnerWheelSupportListener(factorModelB)); spinFactorA.addMouseWheelListener(new SpinnerWheelSupportListener(factorModelA)); colorLevelPanel.add(spinFactorR); colorLevelPanel.add(spinFactorG); colorLevelPanel.add(spinFactorB); colorLevelPanel.add(spinFactorA); colorLevelPanel.add(new JLabel(strings.getProperty("gamma"), JLabel.RIGHT)); SpinnerNumberModel gammaModelR = new SpinnerNumberModel(1., 0.01, 99, 0.01); SpinnerNumberModel gammaModelG = new SpinnerNumberModel(1., 0.01, 99, 0.01); SpinnerNumberModel gammaModelB = new SpinnerNumberModel(1., 0.01, 99, 0.01); SpinnerNumberModel gammaModelA = new SpinnerNumberModel(1., 0.01, 99, 0.01); gammaModelR.addChangeListener(changeEventHandler); gammaModelG.addChangeListener(changeEventHandler); gammaModelB.addChangeListener(changeEventHandler); gammaModelA.addChangeListener(changeEventHandler); spinGammaR = new JSpinner(gammaModelR); spinGammaG = new JSpinner(gammaModelG); spinGammaB = new JSpinner(gammaModelB); spinGammaA = new JSpinner(gammaModelA); spinGammaR.addMouseWheelListener(new SpinnerWheelSupportListener(gammaModelR)); spinGammaG.addMouseWheelListener(new SpinnerWheelSupportListener(gammaModelG)); spinGammaB.addMouseWheelListener(new SpinnerWheelSupportListener(gammaModelB)); spinGammaA.addMouseWheelListener(new SpinnerWheelSupportListener(gammaModelA)); colorLevelPanel.add(spinGammaR); colorLevelPanel.add(spinGammaG); colorLevelPanel.add(spinGammaB); colorLevelPanel.add(spinGammaA); // 色調パネル ColorModel colorModel = ColorModels.safeValueOf(layer .getColorModelName()); JPanel colorTunePanel = new JPanel(); colorTunePanel.setBorder(BorderFactory.createCompoundBorder( BorderFactory.createEmptyBorder(3, 3, 3, 3), BorderFactory.createTitledBorder(strings.getProperty(colorModel.getTitle())))); GridLayout gl2 = new GridLayout(3, 4); gl2.setHgap(3); gl2.setVgap(3); colorTunePanel.setLayout(gl2); colorTunePanel.add(new JLabel(strings.getProperty(colorModel .getItemTitle(0)), JLabel.CENTER)); // Hue 色相 colorTunePanel.add(new JLabel(strings.getProperty(colorModel .getItemTitle(1)), JLabel.CENTER)); // Saturation 彩度 colorTunePanel.add(new JLabel(strings.getProperty(colorModel .getItemTitle(2)), JLabel.CENTER)); // Brightness 明度 colorTunePanel.add(new JLabel(strings.getProperty("contrast"), JLabel.CENTER)); // Contrast コントラスト SpinnerNumberModel hsbModelH = new SpinnerNumberModel(0., -1., 1., 0.001); SpinnerNumberModel hsbModelS = new SpinnerNumberModel(0., -1., 1., 0.001); SpinnerNumberModel hsbModelB = new SpinnerNumberModel(0., -1., 1., 0.001); SpinnerNumberModel hsbModelC = new SpinnerNumberModel(0., -1., 1., 0.001); hsbModelH.addChangeListener(changeEventHandler); hsbModelS.addChangeListener(changeEventHandler); hsbModelB.addChangeListener(changeEventHandler); hsbModelC.addChangeListener(changeEventHandler); spinHue = new JSpinner(hsbModelH); spinSaturation = new JSpinner(hsbModelS); spinBrightness = new JSpinner(hsbModelB); spinContrast = new JSpinner(hsbModelC); spinHue.addMouseWheelListener(new SpinnerWheelSupportListener(hsbModelH)); spinSaturation.addMouseWheelListener(new SpinnerWheelSupportListener(hsbModelS)); spinBrightness.addMouseWheelListener(new SpinnerWheelSupportListener(hsbModelB)); spinContrast.addMouseWheelListener(new SpinnerWheelSupportListener(hsbModelC)); colorTunePanel.add(spinHue); colorTunePanel.add(spinSaturation); colorTunePanel.add(spinBrightness); colorTunePanel.add(spinContrast); JSlider sliderHue = new JSlider(); JSlider sliderSaturation = new JSlider(); JSlider sliderBrightness = new JSlider(); JSlider sliderContrast = new JSlider(); sliderHue.setPreferredSize(spinHue.getPreferredSize()); sliderSaturation.setPreferredSize(spinSaturation.getPreferredSize()); sliderBrightness.setPreferredSize(spinBrightness.getPreferredSize()); sliderContrast.setPreferredSize(spinContrast.getPreferredSize()); colorTunePanel.add(sliderHue); colorTunePanel.add(sliderSaturation); colorTunePanel.add(sliderBrightness); colorTunePanel.add(sliderContrast); JSlider sliders[] = new JSlider[] {sliderHue, sliderSaturation, sliderBrightness, sliderContrast}; JSpinner spinners[] = new JSpinner[] {spinHue, spinSaturation, spinBrightness, spinContrast}; for (int idx = 0; idx < spinners.length; idx++) { final JSlider sl = sliders[idx]; final JSpinner sp = spinners[idx]; SpinnerNumberModel spModel = (SpinnerNumberModel) sp.getModel(); sl.setMinimum((int)(((Number)spModel.getMinimum()).floatValue() * 100)); sl.setMaximum((int)(((Number)spModel.getMaximum()).floatValue() * 100)); sl.setValue((int)(((Number) sp.getValue()).doubleValue() * 100.)); final Semaphore loopBlocker = new Semaphore(1); // イベントが循環発生することを防ぐ sl.addChangeListener(new ChangeListener() { public void stateChanged(ChangeEvent e) { if (loopBlocker.tryAcquire()) { try { double rate = sl.getValue() / 100.; sp.setValue(Double.valueOf(rate)); } finally { loopBlocker.release(); } } } }); sp.addChangeListener(new ChangeListener() { public void stateChanged(ChangeEvent e) { if (loopBlocker.tryAcquire()) { try { int rate = (int)(((Number) sp.getValue()).doubleValue() * 100.); sl.setValue(rate); } finally { loopBlocker.release(); } } } }); } // カラーグループ ColorGroup colorGroup = layer.getColorGroup(); JPanel colorGroupPanel = new JPanel(); colorGroupPanel.setBorder(BorderFactory.createCompoundBorder( BorderFactory.createEmptyBorder(3, 3, 3, 3), BorderFactory.createTitledBorder(strings.getProperty("colorgroup")))); GridBagLayout gbl2 = new GridBagLayout(); colorGroupPanel.setLayout(gbl2); GridBagConstraints gbc2 = new GridBagConstraints(); JLabel lblColorGroup = new JLabel(strings.getProperty("group"), JLabel.RIGHT); cmbColorGroup = new JComboBox(colorGroups.toArray(new ColorGroup[colorGroups.size()])); cmbColorGroup.setSelectedItem(colorGroup); cmbColorGroup.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { ColorGroup selColorGroup = (ColorGroup) cmbColorGroup.getSelectedItem(); chkColorGroupSync.setSelected(selColorGroup.isEnabled()); fireColorGroupChangeEvent(layer); } }); chkColorGroupSync = new JCheckBox(strings.getProperty("synchronized")); chkColorGroupSync.setSelected(layer.isInitSync()); gbc2.gridx = 0; gbc2.gridy = 0; gbc2.gridwidth = 1; gbc2.gridheight = 1; gbc2.weightx = 0.; gbc2.weighty = 0.; gbc2.anchor = GridBagConstraints.WEST; gbc2.fill = GridBagConstraints.BOTH; gbc2.ipadx = 0; gbc2.ipady = 0; gbc2.insets = new Insets(3, 3, 3, 3); colorGroupPanel.add(lblColorGroup, gbc2); gbc2.gridx = 1; gbc2.gridy = 0; gbc2.gridwidth = 1; gbc2.gridheight = 1; gbc2.weightx = 1.; gbc2.weighty = 0.; colorGroupPanel.add(cmbColorGroup, gbc2); gbc2.gridx = 2; gbc2.gridy = 0; gbc2.gridwidth = GridBagConstraints.REMAINDER; gbc2.gridheight = 1; gbc2.weightx = 0.; gbc2.weighty = 0.; colorGroupPanel.add(chkColorGroupSync, gbc2); if (colorGroupPanel != null) { container.add(colorGroupPanel); } container.add(colorLevelPanel); container.add(colorReplacePanel); container.add(colorTunePanel); } /** * このパネルで変更された色情報の状態をリセットする.
* 最後に{@link #setColorConvertParameter(ColorConvertParameter)}された値で 設定し直す.
*/ public void resetColor() { setColorConvertParameter(paramOrg); } public void setColorConvertParameter(ColorConvertParameter param) { if (param == null) { throw new IllegalArgumentException(); } paramOrg = param.clone(); ColorConv colorReplace = param.getColorReplace(); if (colorReplace == null) { colorReplace = ColorConv.NONE; } changeEventDisableSemaphore.incrementAndGet(); try { cmbColorReplace.setSelectedItem(colorReplace); spinGray.setValue(Double.valueOf(param.getGrayLevel())); spinOffsetR.setValue(Integer.valueOf(param.getOffsetR())); spinOffsetG.setValue(Integer.valueOf(param.getOffsetG())); spinOffsetB.setValue(Integer.valueOf(param.getOffsetB())); spinOffsetA.setValue(Integer.valueOf(param.getOffsetA())); spinFactorR.setValue(Double.valueOf(param.getFactorR())); spinFactorG.setValue(Double.valueOf(param.getFactorG())); spinFactorB.setValue(Double.valueOf(param.getFactorB())); spinFactorA.setValue(Double.valueOf(param.getFactorA())); spinGammaR.setValue(Double.valueOf(param.getGammaR())); spinGammaG.setValue(Double.valueOf(param.getGammaG())); spinGammaB.setValue(Double.valueOf(param.getGammaB())); spinGammaA.setValue(Double.valueOf(param.getGammaA())); spinHue.setValue(Double.valueOf(param.getHue())); spinSaturation.setValue(Double.valueOf(param.getSaturation())); spinBrightness.setValue(Double.valueOf(param.getBrightness())); spinContrast.setValue(Double.valueOf(param.getContrast())); } finally { changeEventDisableSemaphore.decrementAndGet(); } chachedParam = param; firePropertyChange("colorConvertParameter", null, param); } public ColorConvertParameter getColorConvertParameter() { if (chachedParam != null) { return chachedParam; } ColorConvertParameter param = new ColorConvertParameter(); param.setColorReplace((ColorConv) cmbColorReplace.getSelectedItem()); param.setGrayLevel(((Number) spinGray.getValue()).floatValue()); param.setOffsetR(((Number) spinOffsetR.getValue()).intValue()); param.setOffsetG(((Number) spinOffsetG.getValue()).intValue()); param.setOffsetB(((Number) spinOffsetB.getValue()).intValue()); param.setOffsetA(((Number) spinOffsetA.getValue()).intValue()); param.setFactorR(((Number) spinFactorR.getValue()).floatValue()); param.setFactorG(((Number) spinFactorG.getValue()).floatValue()); param.setFactorB(((Number) spinFactorB.getValue()).floatValue()); param.setFactorA(((Number) spinFactorA.getValue()).floatValue()); param.setGammaR(((Number) spinGammaR.getValue()).floatValue()); param.setGammaG(((Number) spinGammaG.getValue()).floatValue()); param.setGammaB(((Number) spinGammaB.getValue()).floatValue()); param.setGammaA(((Number) spinGammaA.getValue()).floatValue()); param.setHue(((Number) spinHue.getValue()).floatValue()); param.setSaturation(((Number) spinSaturation.getValue()).floatValue()); param.setBrightness(((Number) spinBrightness.getValue()).floatValue()); param.setContrast(((Number) spinContrast.getValue()).floatValue()); chachedParam = param; return param; } /** * カラー設定が変更されているか? * * @return 変更されている場合はtrue、そうでなければfalse */ public boolean isColorConvertParameterModified() { return !paramOrg.equals(getColorConvertParameter()); } public ColorGroup getColorGroup() { return (ColorGroup) cmbColorGroup.getSelectedItem(); } public void setColorGroup(ColorGroup colorGroup) { if (colorGroup == null) { colorGroup = ColorGroup.NA; } changeEventDisableSemaphore.incrementAndGet(); try { cmbColorGroup.setSelectedItem(colorGroup); } finally { changeEventDisableSemaphore.decrementAndGet(); } } public boolean isSyncColorGroup() { return chkColorGroupSync == null ? false : chkColorGroupSync.isSelected(); } public void setSyncColorGroup(boolean selected) { if (chkColorGroupSync != null) { changeEventDisableSemaphore.incrementAndGet(); try { chkColorGroupSync.setSelected(selected); } finally { changeEventDisableSemaphore.decrementAndGet(); } } } } CharacterManaJ/src/charactermanaj/ui/MainFrame.java0000644000175000017500000025610112560206305022446 0ustar paulliupaulliupackage charactermanaj.ui; import static java.lang.Math.max; import static java.lang.Math.min; import java.awt.BorderLayout; import java.awt.Color; import java.awt.Component; import java.awt.Container; import java.awt.Cursor; import java.awt.Dimension; import java.awt.Font; import java.awt.Frame; import java.awt.GraphicsEnvironment; import java.awt.Point; import java.awt.Rectangle; import java.awt.Toolkit; import java.awt.dnd.DropTarget; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.MouseWheelEvent; import java.awt.event.MouseWheelListener; import java.awt.event.WindowAdapter; import java.awt.event.WindowEvent; import java.awt.image.BufferedImage; import java.io.File; import java.io.IOException; import java.lang.reflect.InvocationTargetException; import java.net.URI; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Properties; import java.util.TreeMap; import java.util.UUID; import java.util.logging.Level; import java.util.logging.Logger; import javax.swing.Box; import javax.swing.BoxLayout; import javax.swing.JCheckBox; import javax.swing.JColorChooser; import javax.swing.JFrame; import javax.swing.JMenu; import javax.swing.JMenuBar; import javax.swing.JMenuItem; import javax.swing.JOptionPane; import javax.swing.JPanel; import javax.swing.JPopupMenu; import javax.swing.JScrollBar; import javax.swing.JScrollPane; import javax.swing.JSeparator; import javax.swing.JSplitPane; import javax.swing.JViewport; import javax.swing.SwingUtilities; import javax.swing.event.AncestorEvent; import javax.swing.event.AncestorListener; import javax.swing.event.MenuEvent; import javax.swing.event.MenuListener; import charactermanaj.Main; import charactermanaj.clipboardSupport.ClipboardUtil; import charactermanaj.graphics.AsyncImageBuilder; import charactermanaj.graphics.ColorConvertedImageCachedLoader; import charactermanaj.graphics.ImageBuildJobAbstractAdaptor; import charactermanaj.graphics.ImageBuilder.ImageOutput; import charactermanaj.graphics.io.ImageSaveHelper; import charactermanaj.graphics.io.OutputOption; import charactermanaj.graphics.io.UkagakaImageSaveHelper; import charactermanaj.model.AppConfig; import charactermanaj.model.CharacterData; import charactermanaj.model.CharacterDataChangeEvent; import charactermanaj.model.CharacterDataChangeListener; import charactermanaj.model.CharacterDataChangeObserver; import charactermanaj.model.ColorGroup; import charactermanaj.model.IndependentPartsSetInfo; import charactermanaj.model.PartsCategory; import charactermanaj.model.PartsColorInfo; import charactermanaj.model.PartsColorManager; import charactermanaj.model.PartsIdentifier; import charactermanaj.model.PartsSet; import charactermanaj.model.RecommendationURL; import charactermanaj.model.WorkingSet; import charactermanaj.model.WorkingSet2; import charactermanaj.model.io.CharacterDataPersistent; import charactermanaj.model.io.PartsImageDirectoryWatchAgent; import charactermanaj.model.io.PartsImageDirectoryWatchAgentFactory; import charactermanaj.model.io.PartsImageDirectoryWatchEvent; import charactermanaj.model.io.PartsImageDirectoryWatchListener; import charactermanaj.model.io.RecentDataPersistent; import charactermanaj.model.io.WorkingSetPersist; import charactermanaj.ui.ImageSelectPanel.ImageSelectPanelEvent; import charactermanaj.ui.ImageSelectPanel.ImageSelectPanelListener; import charactermanaj.ui.ManageFavoriteDialog.FavoriteManageCallback; import charactermanaj.ui.PreviewPanel.PreviewPanelEvent; import charactermanaj.ui.PreviewPanel.PreviewPanelListener; import charactermanaj.ui.model.ColorChangeEvent; import charactermanaj.ui.model.ColorChangeListener; import charactermanaj.ui.model.ColorGroupCoordinator; import charactermanaj.ui.model.FavoritesChangeEvent; import charactermanaj.ui.model.FavoritesChangeListener; import charactermanaj.ui.model.FavoritesChangeObserver; import charactermanaj.ui.model.PartsColorCoordinator; import charactermanaj.ui.model.PartsSelectionManager; import charactermanaj.ui.model.WallpaperFactory; import charactermanaj.ui.model.WallpaperFactoryErrorRecoverHandler; import charactermanaj.ui.model.WallpaperFactoryException; import charactermanaj.ui.model.WallpaperInfo; import charactermanaj.ui.scrollablemenu.JScrollableMenu; import charactermanaj.ui.util.FileDropTarget; import charactermanaj.ui.util.WindowAdjustLocationSupport; import charactermanaj.util.DesktopUtilities; import charactermanaj.util.ErrorMessageHelper; import charactermanaj.util.LocalizedResourcePropertyLoader; import charactermanaj.util.SystemUtil; import charactermanaj.util.UIHelper; /** * メインフレーム.
* アプリケーションがアクティブである場合は最低でも1つのメインフレームが表示されている.
* * @author seraphy */ public class MainFrame extends JFrame implements FavoritesChangeListener, CharacterDataChangeListener { private static final long serialVersionUID = 1L; private static final Logger logger = Logger.getLogger(MainFrame.class.getName()); protected static final String STRINGS_RESOURCE = "languages/mainframe"; protected static final String MENU_STRINGS_RESOURCE = "menu/menu"; /** * メインフレームのアイコン.
*/ protected BufferedImage icon; /** * 現在アクティブなメインフレーム.
* フォーカスが切り替わるたびにアクティブフレームを追跡する.
* Mac OS XのAbout/Preferences/Quitのシステムメニューからよびだされた場合に * オーナーたるべきメインフレームを識別するためのもの.
*/ private static volatile MainFrame activedMainFrame; /** * このメインフレームが対象とするキャラクターデータ.
*/ protected CharacterData characterData; /** * プレビューペイン */ private PreviewPanel previewPane; /** * パーツ選択マネージャ */ protected PartsSelectionManager partsSelectionManager; /** * パネルの最小化モード */ private boolean minimizeMode; /** * パーツ選択パネルリスト */ protected ImageSelectPanelList imageSelectPanels; /** * パーツ選択パネルを納めるスクロールペイン */ protected JScrollPane imgSelectPanelsPanelSp; /** * カラーグループのマネージャ */ protected ColorGroupCoordinator colorGroupCoordinator; /** * パーツカラーのマネージャ */ protected PartsColorCoordinator partsColorCoordinator; /** * キャッシュつきのイメージローダ.
*/ private ColorConvertedImageCachedLoader imageLoader; /** * パーツを組み立てて1つのプレビュー可能なイメージを構築するためのビルダ */ private AsyncImageBuilder imageBuilder; /** * パーツイメージを画像として保存する場合のヘルパー.
* 最後に使ったディレクトリを保持するためのメンバ変数としている.
*/ private ImageSaveHelper imageSaveHelper = new ImageSaveHelper(); /** * 伺か用出力ヘルパ.
* 最後に使ったディレクトリ、ファイル名、モードなどを保持するためのメンバ変数としている.
*/ private UkagakaImageSaveHelper ukagakaImageSaveHelper = new UkagakaImageSaveHelper(); /** * パーツディレクトリを定期的にチェックし、パーツイメージが変更・追加・削除されている場合に パーツリストを更新するためのウォッチャー */ private PartsImageDirectoryWatchAgent watchAgent; /** * デフォルトのパーツセット表示名 */ private String defaultPartsSetTitle; /** * 最後に使用したプリセット.
* (一度もプリセットを使用していなければnull). */ private PartsSet lastUsePresetParts; /** * 最後に使用した検索ダイアログ.
* nullであれば一度も使用していない.
* (nullでなくとも閉じられている可能性がある.)
*/ private SearchPartsDialog lastUseSearchPartsDialog; /** * 最後に使用したお気に入りダイアログ.
* nullであれば一度も使用していない.
* (nullでなくとも閉じられている可能性がある.) */ private ManageFavoriteDialog lastUseManageFavoritesDialog; /** * 最後に使用したパーツのランダム選択ダイアログ.
* nullであれば一度も使用していない.
* (nullでなくとも閉じられている可能性がある.) */ private PartsRandomChooserDialog lastUsePartsRandomChooserDialog; /** * 最後に使用した壁紙情報 */ private WallpaperInfo wallpaperInfo; /** * アクティブなメインフレームを設定する. * * @param mainFrame * メインフレーム */ public static void setActivedMainFrame(MainFrame mainFrame) { if (mainFrame == null) { throw new IllegalArgumentException(); } activedMainFrame = mainFrame; } /** * 現在アクティブなメインフレームを取得する. まだメインフレームが開かれていない場合はnull.
* 最後のメインフレームが破棄中、もしくは破棄済みであれば破棄されたフレームを示すことに注意.
* * @return メインフレーム、もしくはnull */ public static MainFrame getActivedMainFrame() { return activedMainFrame; } /** * キャラクターデータが変更された場合に通知される. */ public void notifyChangeCharacterData(final CharacterDataChangeEvent e) { final CharacterData cd = e.getCharacterData(); if (cd != null && cd.getDocBase().equals( MainFrame.this.characterData.getDocBase())) { SwingUtilities.invokeLater(new Runnable() { public void run() { try { Cursor oldCur = getCursor(); setCursor(Cursor .getPredefinedCursor(Cursor.WAIT_CURSOR)); try { if (e.isChangeStructure()) { // 現在情報の保存 saveWorkingSet(); // 画面構成の再構築 initComponent(cd); } if (e.isReloadPartsAndFavorites()) { // パーツとお気に入りのリロード reloadPartsAndFavorites(cd, true); } } finally { setCursor(oldCur != null ? oldCur : Cursor .getDefaultCursor()); } } catch (Exception ex) { ErrorMessageHelper.showErrorDialog(MainFrame.this, ex); } } }); } } /** * お気に入りデータが変更された場合に通知される. * * @param e */ public void notifyChangeFavorites(FavoritesChangeEvent e) { CharacterData cd = e.getCharacterData(); if (cd != null && cd.getDocBase().equals( MainFrame.this.characterData.getDocBase())) { if (!MainFrame.this.equals(e.getSource()) && !characterData.equals(cd)) { // プリセットとお気に入りを最新化する. // ただし、自分自身から送信したイベントの場合は最新化は不要. characterData.clearPartsSets(false); for (Map.Entry entry : cd.getPartsSets() .entrySet()) { PartsSet partsSet = entry.getValue(); characterData.addPartsSet(partsSet); } } // お気に入り管理ダイアログ上のお気に入り一覧を最新に更新する. if (lastUseManageFavoritesDialog != null && lastUseManageFavoritesDialog.isDisplayable()) { lastUseManageFavoritesDialog.initListModel(); } } } /** * メインフレームを構築する. * * @param characterData * キャラクターデータ */ public MainFrame(CharacterData characterData) { try { if (characterData == null) { throw new IllegalArgumentException(); } setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE); addWindowListener(new WindowAdapter() { @Override public void windowClosing(WindowEvent e) { onCloseProfile(); } @Override public void windowClosed(WindowEvent e) { stopAgents(); } @Override public void windowActivated(WindowEvent e) { setActivedMainFrame(MainFrame.this); } @Override public void windowOpened(WindowEvent e) { // do nothing. } }); // アイコンの設定 icon = UIHelper.getInstance().getImage("icons/icon.png"); setIconImage(icon); // 画面コンポーネント作成 initComponent(characterData); JMenuBar menuBar = createMenuBar(); setJMenuBar(menuBar); // お気に入り変更通知を受け取る FavoritesChangeObserver.getDefault().addFavoritesChangeListener( this); // キャラクターデータの変更通知を受け取る CharacterDataChangeObserver.getDefault() .addCharacterDataChangeListener(this); // メインスクリーンサイズを取得する. GraphicsEnvironment genv = GraphicsEnvironment.getLocalGraphicsEnvironment(); Rectangle desktopSize = genv.getMaximumWindowBounds(); // メインスクリーンのサイズ(デスクトップ領域のみ) logger.log(Level.CONFIG, "desktopSize=" + desktopSize); Dimension imageSize = characterData.getImageSize(); // 画像サイズ300x400を基準サイズとして、それ以下にはならない. // アプリケーション設定の最大サイズ以上の場合はウィンドウサイズは固定してスクロールバーに任せる AppConfig appConfig = AppConfig.getInstance(); int maxWidth = min(desktopSize.width, appConfig.getMainFrameMaxWidth()); int maxHeight = min(desktopSize.height, appConfig.getMainFrameMaxHeight()); int imageWidth = min(maxWidth, max(300, imageSize != null ? imageSize.width : 0)); int imageHeight = min(maxHeight, max(400, imageSize != null ? imageSize.height : 0)); // 300x400の画像の場合にメインフレームが600x550だとちょうどいい感じ. // それ以上大きい画像の場合は増えた分だけフレームを大きくしておく. setSize(imageWidth - 300 + 600, imageHeight - 400 + 550); // 次回表示時にプラットフォーム固有位置に表示するように予約 setLocationByPlatform(true); } catch (RuntimeException ex) { logger.log(Level.SEVERE, "メインフレームの構築中に予期せぬ例外が発生しました。", ex); dispose(); // コンストラクタが呼ばれた時点でJFrameは構築済みなのでdisposeの必要がある. throw ex; } catch (Error ex) { logger.log(Level.SEVERE, "メインフレームの構築中に致命的な例外が発生しました。", ex); dispose(); // コンストラクタが呼ばれた時点でJFrameは構築済みなのでdisposeの必要がある. throw ex; } } /** * メインフレームを表示する.
* デスクトップ領域からはみ出した場合は位置を補正する.
*/ public void showMainFrame() { // メインスクリーンサイズを取得する. GraphicsEnvironment genv = GraphicsEnvironment.getLocalGraphicsEnvironment(); Rectangle desktopSize = genv.getMaximumWindowBounds(); // メインスクリーンのサイズ(デスクトップ領域のみ) logger.log(Level.CONFIG, "desktopSize=" + desktopSize); // プラットフォーム固有の位置あわせで表示する. // 表示した結果、はみ出している場合は0,0に補正する. setVisible(true); Point loc = getLocation(); logger.log(Level.CONFIG, "windowLocation=" + loc); Dimension windowSize = getSize(); if (loc.y + windowSize.height >= desktopSize.height) { loc.y = 0; } if (loc.x + windowSize.width >= desktopSize.width) { loc.x = 0; } if (loc.x == 0 || loc.y == 0) { setLocation(loc); } // デスクトップよりも大きい場合は小さくする. boolean resize = false; Dimension dim = getSize(); if (dim.height > desktopSize.height) { dim.height = desktopSize.height; resize = true; } if (dim.width > desktopSize.width) { dim.width = desktopSize.width; resize = true; } if (resize) { setSize(dim); } } /** * このメインフレームに関連づけられているエージェントスレッドを停止します.
* すでに停止している場合は何もしません。 */ protected void stopAgents() { // エージェントを停止 if (watchAgent != null) { try { watchAgent.disconnect(); } catch (Throwable ex) { logger.log(Level.SEVERE, "フォルダ監視スレッドの停止に失敗しました。", ex); } watchAgent = null; } // イメージビルダを停止 if (imageBuilder != null) { try { imageBuilder.stop(); } catch (Throwable ex) { logger.log(Level.SEVERE, "非同期イメージビルダスレッドの停止に失敗しました。", ex); } imageBuilder = null; } } /** * メインフレームを破棄します.
*/ @Override public void dispose() { FavoritesChangeObserver.getDefault() .removeFavoritesChangeListener(this); CharacterDataChangeObserver.getDefault() .removeCharacterDataChangeListener(this); imageLoader.close(); stopAgents(); super.dispose(); } /** * 画面コンポーネントを設定します.
* すでに設定されている場合は一旦削除されたのちに再作成されます.
*/ private void initComponent(CharacterData characterData) { CharacterData oldCd; synchronized (this) { oldCd = this.characterData; if (oldCd != null) { // 使用中のキャラクターデータであることを登録解除する。 ProfileListManager.unregisterUsedCharacterData(oldCd); } this.characterData = characterData; // 使用中のキャラクターデータであることを登録する. ProfileListManager.registerUsedCharacterData(characterData); } // 設定まわり準備 AppConfig appConfig = AppConfig.getInstance(); Properties strings = LocalizedResourcePropertyLoader.getCachedInstance() .getLocalizedProperties(STRINGS_RESOURCE); // タイトル表示 String title; if (Main.isMacOSX()) { // Mac OS Xの場合はウィンドウにタイトルはつけない。 title = ""; } else { title = strings.getProperty("title"); } setTitle(title + characterData.getName()); // デフォルトのパーツセット表示名 defaultPartsSetTitle = strings.getProperty("defaultPartsSetTitle"); // エージェントの停止 stopAgents(); // コンポーネント配置 Container contentPane = getContentPane(); // すでにあるコンポーネントを削除 for (Component comp : contentPane.getComponents()) { contentPane.remove(comp); } // 開いている検索ダイアログを閉じる closeSearchDialog(); // 開いているお気に入り管理ダイアログを閉じる closeManageFavoritesDialog(); // 開いているランダム選択ダイアログを閉じる. closePartsRandomChooserDialog(); PartsColorManager partsColorManager = characterData.getPartsColorManager(); // デフォルトの背景色の設定 Color bgColor = appConfig.getDefaultImageBgColor(); wallpaperInfo = new WallpaperInfo(); wallpaperInfo.setBackgroundColor(bgColor); if (imageLoader != null) { imageLoader.close(); } imageLoader = new ColorConvertedImageCachedLoader(); imageBuilder = new AsyncImageBuilder(imageLoader); partsSelectionManager = new PartsSelectionManager(partsColorManager, new PartsSelectionManager.ImageBgColorProvider() { public Color getImageBgColor() { return wallpaperInfo.getBackgroundColor(); } public void setImageBgColor(Color imageBgColor) { applyBackgroundColorOnly(imageBgColor); } }); colorGroupCoordinator = new ColorGroupCoordinator(partsSelectionManager, partsColorManager); partsColorCoordinator = new PartsColorCoordinator(characterData, partsColorManager, colorGroupCoordinator); PartsImageDirectoryWatchAgentFactory agentFactory = PartsImageDirectoryWatchAgentFactory.getFactory(); watchAgent = agentFactory.getAgent(characterData); previewPane = new PreviewPanel(); previewPane.setTitle(defaultPartsSetTitle); previewPane.addPreviewPanelListener(new PreviewPanelListener() { public void addFavorite(PreviewPanelEvent e) { if (!e.isShiftKeyPressed()) { // お気に入り登録 onRegisterFavorite(); } else { // シフトキーにて、お気に入りの管理を開く onManageFavorites(); } } public void changeBackgroundColor(PreviewPanelEvent e) { if ( !e.isShiftKeyPressed()) { // 壁紙選択 onChangeWallpaper(); } else { // シフトキーにて背景色変更 onChangeBgColor(); } } public void copyPicture(PreviewPanelEvent e) { onCopy(e.isShiftKeyPressed()); } public void savePicture(PreviewPanelEvent e) { if ( !e.isShiftKeyPressed()) { // 画像出力 onSavePicture(); } else { // シフトキーにて「伺か」用出力 onSaveAsUkagaka(); } } public void showInformation(PreviewPanelEvent e) { onInformation(); } public void flipHorizontal(PreviewPanelEvent e) { onFlipHolizontal(); } }); imageSelectPanels = new ImageSelectPanelList(); JPanel imgSelectPanelsPanel = new JPanel(); BoxLayout bl = new BoxLayout(imgSelectPanelsPanel, BoxLayout.PAGE_AXIS); imgSelectPanelsPanel.setLayout(bl); for (PartsCategory category : characterData.getPartsCategories()) { final ImageSelectPanel imageSelectPanel = new ImageSelectPanel(category, characterData); imgSelectPanelsPanel.add(imageSelectPanel); imageSelectPanels.add(imageSelectPanel); partsSelectionManager.register(imageSelectPanel); } imgSelectPanelsPanelSp = new JScrollPane(imgSelectPanelsPanel) { private static final long serialVersionUID = 1L; @Override public JScrollBar createVerticalScrollBar() { JScrollBar sb = super.createVerticalScrollBar(); sb.setUnitIncrement(12); return sb; } }; imgSelectPanelsPanelSp.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS); JSplitPane splitPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, true, imgSelectPanelsPanelSp, previewPane); contentPane.add(splitPane, BorderLayout.CENTER); imgSelectPanelsPanelSp.requestFocus(); ArrayList colorGroups = new ArrayList(); colorGroups.addAll(characterData.getColorGroups()); final ColorChangeListener colorChangeListener = new ColorChangeListener() { public void onColorGroupChange(ColorChangeEvent event) { // do nothing. } public void onColorChange(ColorChangeEvent event) { MainFrame.this.requestPreview(); } }; colorGroupCoordinator.addColorChangeListener(colorChangeListener); for (int idx = 0; idx < imageSelectPanels.size(); idx++) { ImageSelectPanel imageSelectPanel = imageSelectPanels.get(idx); final PartsCategory partsCategory = imageSelectPanel.getPartsCategory(); final ColorDialog colorDialog = new ColorDialog(this, partsCategory, colorGroups); colorGroupCoordinator.registerColorDialog(colorDialog); partsColorCoordinator.register(imageSelectPanel, colorDialog); final int curidx = idx; imageSelectPanel.addImageSelectListener(new ImageSelectPanelListener() { public void onChangeColor(ImageSelectPanelEvent event) { WindowAdjustLocationSupport.alignRight( MainFrame.this, colorDialog, curidx, false); colorDialog.setVisible(!colorDialog.isVisible()); } public void onPreferences(ImageSelectPanelEvent event) { // do nothing. (not supported) } public void onChange(ImageSelectPanelEvent event) { MainFrame.this.requestPreview(); } public void onSelectChange(ImageSelectPanelEvent event) { // do nothing. } public void onTitleClick(ImageSelectPanelEvent event) { PartsCategory partsCategory = (event != null) ? event.getImageSelectPanel().getPartsCategory() : null; MainFrame.this.onClickPartsCategoryTitle(partsCategory, false); } public void onTitleDblClick(ImageSelectPanelEvent event) { PartsCategory partsCategory = (event != null) ? event.getImageSelectPanel().getPartsCategory() : null; MainFrame.this.onClickPartsCategoryTitle(partsCategory, true); } }); imageSelectPanel.addAncestorListener(new AncestorListener() { public void ancestorAdded(AncestorEvent event) { } public void ancestorMoved(AncestorEvent event) { } public void ancestorRemoved(AncestorEvent event) { // パネルもしくは、その親が削除されたときにダイアログも非表示とする。 colorDialog.setVisible(false); } }); } // 全パーツのロード partsSelectionManager.loadParts(); // 保存されているワーキングセットを復元する. // 復元できなかった場合はパーツセットを初期選択する. if ( !loadWorkingSet()) { if (showDefaultParts(true)) { requestPreview(); } } // 選択されているパーツを見える状態にする scrollToSelectedParts(); // 非同期イメージローダの処理開始 if (!imageBuilder.isAlive()) { imageBuilder.start(); } // ドロップターゲットの設定 new DropTarget(imgSelectPanelsPanelSp, new FileDropTarget() { @Override protected void onDropFiles(final List dropFiles) { if (dropFiles == null || dropFiles.isEmpty()) { return; } // インポートダイアログを開く. // ドロップソースの処理がブロッキングしないように、 // ドロップハンドラの処理を終了してからインポートダイアログが開くようにする. SwingUtilities.invokeLater(new Runnable() { public void run() { onImport(dropFiles); } }); } @Override protected void onException(Exception ex) { ErrorMessageHelper.showErrorDialog(MainFrame.this, ex); } }); // ディレクトリを監視し変更があった場合にパーツをリロードするリスナ watchAgent.addPartsImageDirectoryWatchListener(new PartsImageDirectoryWatchListener() { public void detectPartsImageChange(PartsImageDirectoryWatchEvent e) { Runnable refreshJob = new Runnable() { public void run() { onDetectPartsImageChange(); } }; if (SwingUtilities.isEventDispatchThread()) { refreshJob.run(); } else { SwingUtilities.invokeLater(refreshJob); } } }); // 監視が有効であれば、ディレクトリの監視をスタートする if (appConfig.isEnableDirWatch() && characterData.isWatchDirectory()) { watchAgent.connect(); } // パーツカテゴリの自動縮小が設定されている場合 minimizeMode = false; if (appConfig.isEnableAutoShrinkPanel()) { onClickPartsCategoryTitle(null, true); } // コンポーネントの再構築の場合 if (oldCd != null) { validate(); } } /** * パーツが変更されたことを検知した場合.
* パーツデータをリロードし、各カテゴリのパーツ一覧を再表示させ、プレビューを更新する.
*/ protected void onDetectPartsImageChange() { try { reloadPartsAndFavorites(null, true); } catch (IOException ex) { logger.log(Level.SEVERE, "parts reload failed. " + characterData, ex); } } /** * すべてのカテゴリのリストで選択中のアイテムが見えるようにスクロールする. */ protected void scrollToSelectedParts() { partsSelectionManager.scrollToSelectedParts(); } /** * 指定したパーツカテゴリ以外のパーツ選択パネルを最小化する. * * @param partsCategory * パーツカテゴリ、nullの場合は全て最小化する. * @param dblClick * ダブルクリック */ protected void onClickPartsCategoryTitle(PartsCategory partsCategory, boolean dblClick) { if (logger.isLoggable(Level.FINE)) { logger.log(Level.FINE, "onClickPartsCategoryTitle category=" + partsCategory + "/clickCount=" + dblClick); } if (dblClick) { minimizeMode = !minimizeMode; if (!minimizeMode) { partsSelectionManager.setMinimizeModeIfOther(null, false); return; } } if (minimizeMode) { if (partsSelectionManager.isNotMinimizeModeJust(partsCategory)) { partsSelectionManager.setMinimizeModeIfOther(null, true); // 全部縮小 } else { partsSelectionManager.setMinimizeModeIfOther(partsCategory, true); if (partsCategory != null) { // 対象のパネルがスクロールペイン内に見える用にスクロールする. // スクロールバーの位置指定などの座標系の操作は「要求」であり、実際に適用されるまで本当の位置は分らない。 // よって以下の処理は非同期に行い、先に座標を確定させたものに対して行う必要がある。 final ImageSelectPanel panel = imageSelectPanels.findByPartsCategory(partsCategory); SwingUtilities.invokeLater(new Runnable() { public void run() { final Point pt = panel.getLocation(); JViewport viewPort = imgSelectPanelsPanelSp.getViewport(); viewPort.setViewPosition(pt); viewPort.revalidate(); } }); } } } } /** * デフォルトパーツを選択する.
* デフォルトパーツがなければお気に入りの最初のものを選択する.
* それもなければ空として表示する.
* パーツの適用に失敗した場合はfalseを返します.(例外は返されません.)
* * @param force * すでに選択があっても選択しなおす場合はtrue、falseの場合は選択があれば何もしない. * @return パーツ選択された場合。force=trueの場合はエラーがなければ常にtrueとなります。 */ protected boolean showDefaultParts(boolean force) { try { if (!force) { // 現在選択中のパーツを取得する.(なければ空) PartsSet sel = partsSelectionManager.createPartsSet(); if (!sel.isEmpty()) { // 強制選択でない場合、すでに選択済みのパーツがあれば何もしない. return false; } } // デフォルトのパーツセットを取得する String defaultPresetId = characterData.getDefaultPartsSetId(); PartsSet partsSet = null; if (defaultPresetId != null) { partsSet = characterData.getPartsSets().get(defaultPresetId); } // デフォルトのパーツセットがなければ、お気に入りの最初を選択する. if (partsSet == null) { List partssets = getPartsSetList(); if (!partssets.isEmpty()) { partsSet = partssets.get(0); } } // パーツセットがあれば、それを表示要求する. // パーツセットがなければカラーダイアログを初期化するのみ if (partsSet == null) { partsColorCoordinator.initColorDialog(); } else { selectPresetParts(partsSet); } } catch (Exception ex) { logger.log(Level.WARNING, "パーツのデフォルト適用に失敗しました。", ex); return false; } return true; } /** * プリセットを適用しキャラクターイメージを再構築します.
* 実行時エラーは画面のレポートされます.
* * @param presetParts * パーツセット, nullの場合は何もしない. */ protected void selectPresetParts(PartsSet presetParts) { if (presetParts == null) { return; } try { // 最後に使用したプリセットとして記憶する. lastUsePresetParts = presetParts; // プリセットパーツで選択を変える partsSelectionManager.selectPartsSet(presetParts); // カラーパネルを選択されているアイテムをもとに再設定する partsColorCoordinator.initColorDialog(); // 再表示 requestPreview(); } catch (Exception ex) { ErrorMessageHelper.showErrorDialog(this, ex); } } /** * プリセットとお気に入りを表示順に並べて返す. * * @return プリセットとお気に入りのリスト(表示順) */ protected List getPartsSetList() { ArrayList partssets = new ArrayList(); partssets.addAll(characterData.getPartsSets().values()); Collections.sort(partssets, PartsSet.DEFAULT_COMPARATOR); return partssets; } protected static final class TreeLeaf implements Comparable { public enum TreeLeafType { NODE, LEAF } private String name; private TreeLeafType typ; public TreeLeaf(TreeLeafType typ, String name) { if (name == null) { name = ""; } this.typ = typ; this.name = name; } public String getName() { return name; } public TreeLeafType getTyp() { return typ; } @Override public boolean equals(Object obj) { if (obj != null && obj instanceof TreeLeaf) { TreeLeaf o = (TreeLeaf) obj; return typ == o.typ && name.equals(o.name); } return false; } @Override public int hashCode() { return typ.hashCode() ^ name.hashCode(); } public int compareTo(TreeLeaf o) { int ret = name.compareTo(o.name); if (ret == 0) { ret = (typ.ordinal() - o.typ.ordinal()); } return ret; } @Override public String toString() { return name; } } protected TreeMap buildFavoritesItemTree( List partssets) { if (partssets == null) { partssets = Collections.emptyList(); } TreeMap favTree = new TreeMap(); for (PartsSet partsSet : partssets) { String flatname = partsSet.getLocalizedName(); String[] tokens = flatname.split("\\|"); if (tokens.length == 0) { continue; } TreeMap r = favTree; for (int idx = 0; idx < tokens.length - 1; idx++) { String name = tokens[idx]; TreeLeaf leafName = new TreeLeaf(TreeLeaf.TreeLeafType.NODE, name); @SuppressWarnings("unchecked") TreeMap n = (TreeMap) r .get(leafName); if (n == null) { n = new TreeMap(); r.put(leafName, n); } r = n; } String lastName = tokens[tokens.length - 1]; TreeLeaf lastLeafName = new TreeLeaf(TreeLeaf.TreeLeafType.LEAF, lastName); @SuppressWarnings("unchecked") List leafValue = (List) r.get(lastLeafName); if (leafValue == null) { leafValue = new ArrayList(); r.put(lastLeafName, leafValue); } leafValue.add(partsSet); } return favTree; } protected interface FavoriteMenuItemBuilder { JMenuItem createFavoriteMenuItem(String name, PartsSet partsSet); JMenu createSubMenu(String name); } private void buildFavoritesMenuItems(List menuItems, FavoriteMenuItemBuilder favMenuItemBuilder, TreeMap favTree) { for (Map.Entry entry : favTree.entrySet()) { TreeLeaf treeLeaf = entry.getKey(); String name = treeLeaf.getName(); if (treeLeaf.getTyp() == TreeLeaf.TreeLeafType.LEAF) { // 葉ノードには、JMenuItemを設定する. @SuppressWarnings("unchecked") List leafValue = (List) entry.getValue(); for (final PartsSet partsSet : leafValue) { JMenuItem favoriteMenu = favMenuItemBuilder .createFavoriteMenuItem(name, partsSet); menuItems.add(favoriteMenu); } } else if (treeLeaf.getTyp() == TreeLeaf.TreeLeafType.NODE) { // 枝ノードは、サブメニューを作成し、子ノードを設定する @SuppressWarnings("unchecked") TreeMap childNode = (TreeMap) entry .getValue(); JMenu subMenu = favMenuItemBuilder.createSubMenu(name); menuItems.add(subMenu); ArrayList subMenuItems = new ArrayList(); buildFavoritesMenuItems(subMenuItems, favMenuItemBuilder, childNode); for (JMenuItem subMenuItem : subMenuItems) { subMenu.add(subMenuItem); } } else { throw new RuntimeException("unknown type: " + treeLeaf); } } } /** * お気に入りのJMenuItemを作成するファンクションオブジェクト */ private FavoriteMenuItemBuilder favMenuItemBuilder = new FavoriteMenuItemBuilder() { private MenuBuilder menuBuilder = new MenuBuilder(); /** * お気に入りメニューの作成 */ public JMenuItem createFavoriteMenuItem(final String name, final PartsSet partsSet) { JMenuItem favoriteMenu = menuBuilder.createJMenuItem(); favoriteMenu.setName(partsSet.getPartsSetId()); favoriteMenu.setText(name); if (partsSet.isPresetParts()) { Font font = favoriteMenu.getFont(); favoriteMenu.setFont(font.deriveFont(Font.BOLD)); } favoriteMenu.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { selectPresetParts(partsSet); } }); // メニューアイテム上でマウスホイールを動かした場合は上下にスクロールさせる. // (ただし、OSXのスクリーンメニュー使用時は無視する.) addMouseWheelListener(favoriteMenu); return favoriteMenu; } /** * サブメニューの作成 */ public JMenu createSubMenu(String name) { JMenu menu = menuBuilder.createJMenu(); menu.setText(name); // メニューアイテム上でマウスホイールを動かした場合は上下にスクロールさせる. // (ただし、OSXのスクリーンメニュー使用時は無視する.) addMouseWheelListener(menu); return menu; } /** * メニューアイテム上でホイールを上下させたときにメニューをスクロールさせるためのホイールハンドラを設定する. * * @param favoriteMenu */ protected void addMouseWheelListener(final JMenuItem favoriteMenu) { if (JScrollableMenu.isScreenMenu()) { return; } favoriteMenu.addMouseWheelListener(new MouseWheelListener() { public void mouseWheelMoved(MouseWheelEvent e) { int rotation = e.getWheelRotation(); JPopupMenu popupMenu = (JPopupMenu) favoriteMenu .getParent(); JMenu parentMenu = (JMenu) popupMenu.getInvoker(); if (parentMenu != null && parentMenu instanceof JScrollableMenu) { final JScrollableMenu favMenu = (JScrollableMenu) parentMenu; favMenu.doScroll(rotation < 0); } e.consume(); } }); } }; /** * お気に入りメニューが開いたとき * * @param menu */ protected void onSelectedFavoriteMenu(JMenu menu) { // 表示順にソート List partssets = getPartsSetList(); TreeMap favTree = buildFavoritesItemTree(partssets); // メニューの再構築 ArrayList favoritesMenuItems = new ArrayList(); buildFavoritesMenuItems(favoritesMenuItems, favMenuItemBuilder, favTree); if (menu instanceof JScrollableMenu) { // スクロールメニューの場合 JScrollableMenu favMenu = (JScrollableMenu) menu; // スクロールメニューの初期化 favMenu.initScroller(); // スクロールメニューアイテムの設定 favMenu.setScrollableItems(favoritesMenuItems); // 高さを補正する // お気に入りメニューが選択された場合、 // お気に入りアイテム一覧を表示するよりも前に // 表示可能なアイテム数を現在のウィンドウの高さから算定する. Toolkit tk = Toolkit.getDefaultToolkit(); Dimension scrsiz = tk.getScreenSize(); int height = scrsiz.height; // MainFrame.this.getHeight(); favMenu.adjustMaxVisible(height); logger.log(Level.FINE, "scrollableMenu maxVisible=" + favMenu.getMaxVisible()); } else { // 通常メニューの場合 // 既存メニューの位置をセパレータより判断する. int mx = menu.getMenuComponentCount(); int separatorIdx = -1; for (int idx = 0; idx < mx; idx++) { Component item = menu.getMenuComponent(idx); if (item instanceof JSeparator) { separatorIdx = idx; break; } } // 既存メニューの削除 if (separatorIdx > 0) { while (menu.getMenuComponentCount() > separatorIdx + 1) { menu.remove(separatorIdx + 1); } } // お気に入りアイテムのメニューを登録する. for (JMenuItem menuItem : favoritesMenuItems) { menu.add(menuItem); } } } /** * ヘルプメニューを開いたときにお勧めメニューを構築する. * * @param menu */ protected void onSelectedRecommendationMenu(JMenu mnuRecomendation) { // 現在のお勧めメニューを一旦削除 while (mnuRecomendation.getMenuComponentCount() > 0) { mnuRecomendation.remove(0); } // お勧めリンクの定義がない場合はデフォルトを用いる.(明示的な空の場合は何もしない.) CharacterDataPersistent persist = CharacterDataPersistent.getInstance(); persist.compensateRecommendationList(characterData); // お勧めリンクメニューを作成する. List recommendations = characterData.getRecommendationURLList(); if (recommendations != null) { MenuBuilder menuBuilder = new MenuBuilder(); for (RecommendationURL recommendation : recommendations) { String displayName = recommendation.getDisplayName(); String url = recommendation.getUrl(); JMenuItem mnuItem = menuBuilder.createJMenuItem(); mnuItem.setText(displayName); mnuItem.addActionListener( DesktopUtilities.createBrowseAction(MainFrame.this, url, displayName) ); mnuRecomendation.add(mnuItem); } } // お勧めリンクメニューのリストがnullでなく空でもない場合は有効、そうでなければ無効にする. mnuRecomendation.setEnabled(recommendations != null && !recommendations.isEmpty()); } /** * 最後に選択されたお気に入りと同じ構成であれば、 このお気に入りの名前をプレビューペインのタイトルに設定する.
* そうでなければデフォルトのパーツセット名(no titleとか)を表示する.
* 色情報が異なる場合に末尾に「*」マークがつけられる.
* * @param requestPartsSet * 表示するパーツセット(名前は設定されていなくて良い。お気に入り側を使うので。), nullの場合はデフォルトのパーツ名 */ protected void showPresetName(PartsSet requestPartsSet) { String title = getSuggestPartsSetName(requestPartsSet, true); if (title == null) { title = defaultPartsSetTitle; } previewPane.setTitle(title); } /** * パーツセット名を推定する.
* 最後に選択されたお気に入りと同じ構成であれば、 このお気に入りの名前を返す.
* お気に入りが選択されていないか構成が異なる場合、お気に入りに名前がない場合はnullを返す.
* * @param requestPartsSet * 表示するパーツセット(名前は設定されていなくて良い。お気に入り側を使うので。) * @param markColorChange * 色情報が異なる場合に末尾に「*」マークをつける場合はtrue */ private String getSuggestPartsSetName(PartsSet requestPartsSet, boolean markColorChange) { String partsSetTitle = null; if (lastUsePresetParts != null && PartsSet.isSameStructure(requestPartsSet, lastUsePresetParts)) { partsSetTitle = lastUsePresetParts.getLocalizedName(); if (markColorChange && !PartsSet.isSameColor(requestPartsSet, lastUsePresetParts)) { if (partsSetTitle != null) { partsSetTitle += "*"; } } } if (partsSetTitle != null && partsSetTitle.trim().length() > 0) { return partsSetTitle; } return null; } /** * プレビューの更新を要求する. 更新は非同期に行われる. */ protected void requestPreview() { if (!characterData.isValid()) { return; } // 選択されているパーツの各イメージを取得しレイヤー順に並び替えて合成する. // 合成は別スレッドにて非同期に行われる. // リクエストは随時受け付けて、最新のリクエストだけが処理される. // (処理がはじまる前に新しいリクエストで上書きされた場合、前のリクエストは単に捨てられる.) imageBuilder.requestJob(new ImageBuildJobAbstractAdaptor(characterData) { /** * 構築するパーツセット情報 */ private PartsSet requestPartsSet; /** * 非同期のイメージ構築要求の番号.
*/ private long ticket; @Override public void onQueueing(long ticket) { this.ticket = ticket; previewPane.setLoadingRequest(ticket); } @Override public void buildImage(ImageOutput output) { // 合成結果のイメージを引数としてイメージビルダから呼び出される. final BufferedImage img = output.getImageOutput(); Runnable refreshJob = new Runnable() { public void run() { previewPane.setPreviewImage(img); previewPane.setLoadingComplete(ticket); showPresetName(requestPartsSet); } }; if (SwingUtilities.isEventDispatchThread()) { refreshJob.run(); } else { try { SwingUtilities.invokeAndWait(refreshJob); } catch (Exception ex) { logger.log(Level.WARNING, "build image failed.", ex); } } } @Override public void handleException(final Throwable ex) { // 合成中に例外が発生した場合、イメージビルダから呼び出される. Runnable showExceptionJob = new Runnable() { public void run() { ErrorMessageHelper.showErrorDialog(MainFrame.this, ex); } }; if (SwingUtilities.isEventDispatchThread()) { showExceptionJob.run(); } else { SwingUtilities.invokeLater(showExceptionJob); } } @Override protected PartsSet getPartsSet() { // 合成できる状態になった時点でイメージビルダから呼び出される. final PartsSet[] result = new PartsSet[1]; Runnable collectPartsSetJob = new Runnable() { public void run() { PartsSet partsSet = partsSelectionManager.createPartsSet(); result[0] = partsSet; } }; if (SwingUtilities.isEventDispatchThread()) { collectPartsSetJob.run(); } else { try { // スレッドによるSwingのイベントディスパッチスレッド以外からの呼び出しの場合、 // Swingディスパッチスレッドでパーツの選択状態を取得する. SwingUtilities.invokeAndWait(collectPartsSetJob); } catch (InvocationTargetException e) { throw new RuntimeException(e.getMessage(), e); } catch (InterruptedException e) { throw new RuntimeException("interrupted:" + e, e); } } if (logger.isLoggable(Level.FINE)) { logger.log(Level.FINE, "preview: " + result[0]); } requestPartsSet = result[0]; return requestPartsSet; } }); } /** * プロファイルを開く */ protected void onOpenProfile() { try { MainFrame main2 = ProfileListManager.openProfile(this); if (main2 != null) { main2.showMainFrame(); } } catch (Exception ex) { ErrorMessageHelper.showErrorDialog(this, ex); } } /** * 背景色を変更する. */ protected void onChangeBgColor() { getJMenuBar().setEnabled(false); try { Properties strings = LocalizedResourcePropertyLoader.getCachedInstance() .getLocalizedProperties(STRINGS_RESOURCE); Color color = wallpaperInfo.getBackgroundColor(); color = JColorChooser.showDialog(this, strings.getProperty("chooseBgColor"), color); if (color != null) { applyBackgroundColorOnly(color); } } finally { getJMenuBar().setEnabled(true); } } /** * 壁紙を変更する. */ protected void onChangeWallpaper() { try { WallpaperDialog wallpaperDialog = new WallpaperDialog(this); // 最後に使用した壁紙情報でダイアログを設定する. wallpaperDialog.setWallpaperInfo(this.wallpaperInfo); // 壁紙情報を設定するモーダルダイアログを開く WallpaperInfo wallpaperInfo = wallpaperDialog.showDialog(); if (wallpaperInfo == null) { return; } // 壁紙情報を保存し、その情報をもとに背景を再描画する. applyWallpaperInfo(wallpaperInfo, false); } catch (WallpaperFactoryException ex) { ErrorMessageHelper.showErrorDialog(this, ex); } catch (RuntimeException ex) { ErrorMessageHelper.showErrorDialog(this, ex); } } /** * 背景色のみ変更し、背景を再描画する.
* 壁紙情報全体の更新よりも効率化するためのメソッドである.
* * @param bgColor * 背景色 */ protected void applyBackgroundColorOnly(Color bgColor) { wallpaperInfo.setBackgroundColor(bgColor); previewPane.getWallpaper() .setBackgroundColor(wallpaperInfo.getBackgroundColor()); } /** * 壁紙情報を保存し、その情報をもとに背景を再描画する.
* ignoreErrorがtrueである場合、適用に失敗した場合はログに記録するのみで、 壁紙情報は保存されず、壁紙も更新されない.
* * @param wallpaperInfo * 壁紙情報、null不可 * @param ignoreError * 失敗を無視する場合 * @throws IOException * 失敗 */ protected void applyWallpaperInfo(WallpaperInfo wallpaperInfo, boolean ignoreError) throws WallpaperFactoryException { if (wallpaperInfo == null) { throw new IllegalArgumentException(); } // 壁紙情報から壁紙インスタンスを生成する. WallpaperFactory wallpaperFactory = WallpaperFactory.getInstance(); Wallpaper wallpaper = null; try { // 壁紙情報の構築時に問題が発生した場合、 // 回復処理をして継続するかエラーとするか? WallpaperFactoryErrorRecoverHandler handler = null; if (ignoreError) { handler = new WallpaperFactoryErrorRecoverHandler(); } // 壁紙情報 wallpaper = wallpaperFactory.createWallpaper(wallpaperInfo, handler); } catch (WallpaperFactoryException ex) { logger.log(Level.WARNING, "壁紙情報の適用に失敗しました。", ex); if ( !ignoreError) { throw ex; } } catch (RuntimeException ex) { logger.log(Level.WARNING, "壁紙情報の適用に失敗しました。", ex); if ( !ignoreError) { throw ex; } } if (wallpaper == null) { return; } // 壁紙を更新する. previewPane.setWallpaper(wallpaper); // 壁紙情報として記憶する. this.wallpaperInfo = wallpaperInfo; } /** * プリビューしている画像をファイルに保存する。 サポートしているのはPNG/JPEGのみ。 */ protected void onSavePicture() { Toolkit tk = Toolkit.getDefaultToolkit(); BufferedImage img = previewPane.getPreviewImage(); Color imgBgColor = wallpaperInfo.getBackgroundColor(); if (img == null) { tk.beep(); return; } try { // 出力オプションの調整 OutputOption outputOption = imageSaveHelper.getOutputOption(); outputOption.setZoomFactor(previewPane.getZoomFactor()); outputOption.changeRecommend(); imageSaveHelper.setOutputOption(outputOption); // ファイルダイアログ表示 File outFile = imageSaveHelper.showSaveFileDialog(this); if (outFile == null) { return; } logger.log(Level.FINE, "savePicture: " + outFile); logger.log(Level.FINE, "outputOption: " + outputOption); // 画像のファイルへの出力 StringBuilder warnings = new StringBuilder(); setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); try { imageSaveHelper.savePicture(img, imgBgColor, outFile, warnings); } finally { setCursor(Cursor.getDefaultCursor()); } if (warnings.length() > 0) { JOptionPane.showMessageDialog(this, warnings.toString(), "WARNINGS", JOptionPane.WARNING_MESSAGE); } } catch (Exception ex) { ErrorMessageHelper.showErrorDialog(this, ex); } } /** * 伺か用PNG/PNAの出力. */ protected void onSaveAsUkagaka() { BufferedImage img = previewPane.getPreviewImage(); Color bgColor = wallpaperInfo.getBackgroundColor(); if (img == null) { Toolkit tk = Toolkit.getDefaultToolkit(); tk.beep(); return; } try { ukagakaImageSaveHelper.save(this, img, bgColor); } catch (IOException ex) { ErrorMessageHelper.showErrorDialog(this, ex); } } /** * 伺か用PNG/PNAの変換 */ protected void onConvertUkagaka() { try { Color colorKey = wallpaperInfo.getBackgroundColor(); ukagakaImageSaveHelper.convertChooseFiles(this, colorKey); } catch (IOException ex) { ErrorMessageHelper.showErrorDialog(this, ex); } } /** * プロファイルの場所を開く */ protected void onBrowseProfileDir() { if (!characterData.isValid()) { Toolkit tk = Toolkit.getDefaultToolkit(); tk.beep(); return; } try { DesktopUtilities.browseBaseDir(characterData.getDocBase()); } catch (Exception ex) { ErrorMessageHelper.showErrorDialog(this, ex); } } /** * このプロファイルを編集する. */ protected void onEditProfile() { if (!characterData.isValid()) { Toolkit tk = Toolkit.getDefaultToolkit(); tk.beep(); return; } try { CharacterData cd = this.characterData; CharacterData newCd = ProfileListManager.editProfile(this, cd); if (newCd != null) { CharacterDataChangeObserver.getDefault() .notifyCharacterDataChange(this, newCd, true, true); } } catch (Exception ex) { ErrorMessageHelper.showErrorDialog(this, ex); } } /** * パーツの管理ダイアログを開く.
*/ protected void onManageParts() { if (!characterData.isValid()) { Toolkit tk = Toolkit.getDefaultToolkit(); tk.beep(); return; } PartsManageDialog mrgDlg = new PartsManageDialog(this, characterData); mrgDlg.setVisible(true); if (mrgDlg.isUpdated()) { // パーツ管理情報が更新された場合、 // パーツデータをリロードする. if (characterData.reloadPartsData()) { partsSelectionManager.loadParts(); requestPreview(); } } } /** * 「パーツ検索」ダイアログを開く.
* すでに開いているダイアログがあれば、それにフォーカスを当てる.
*/ protected void openSearchDialog() { if (!characterData.isValid()) { Toolkit tk = Toolkit.getDefaultToolkit(); tk.beep(); return; } if (lastUseSearchPartsDialog != null) { // 開いているダイアログがあれば、それにフォーカスを当てる. if (lastUseSearchPartsDialog.isDisplayable() && lastUseSearchPartsDialog.isVisible()) { lastUseSearchPartsDialog.requestFocus(); return; } } SearchPartsDialog searchPartsDlg = new SearchPartsDialog(this, characterData, partsSelectionManager); WindowAdjustLocationSupport.alignRight(this, searchPartsDlg, 0, true); searchPartsDlg.setVisible(true); lastUseSearchPartsDialog = searchPartsDlg; } /** * 「パーツ検索」ダイアログを閉じる.
*/ protected void closeSearchDialog() { lastUseSearchPartsDialog = null; for (SearchPartsDialog dlg : SearchPartsDialog.getDialogs()) { if (dlg != null && dlg.isDisplayable() && dlg.getParent() == this) { dlg.dispose(); } } } /** * 「お気に入りの管理」ダイアログを閉じる */ protected void closeManageFavoritesDialog() { if (lastUseManageFavoritesDialog != null) { if (lastUseManageFavoritesDialog.isDisplayable()) { lastUseManageFavoritesDialog.dispose(); } lastUseManageFavoritesDialog = null; } } /** * 「パーツのランダム選択ダイアログ」を閉じる */ protected void closePartsRandomChooserDialog() { if (lastUsePartsRandomChooserDialog != null) { if (lastUsePartsRandomChooserDialog.isDisplayable()) { lastUsePartsRandomChooserDialog.dispose(); } lastUsePartsRandomChooserDialog = null; } } /** * クリップボードにコピー * * @param screenImage * スクリーンイメージ */ protected void onCopy(boolean screenImage) { try { BufferedImage img = previewPane.getPreviewImage(); if (img == null) { Toolkit tk = Toolkit.getDefaultToolkit(); tk.beep(); return; } if (screenImage) { // 表示している内容をそのままコピーする. img = previewPane.getScreenImage(); } Color imgBgColor = wallpaperInfo.getBackgroundColor(); ClipboardUtil.setImage(img, imgBgColor); } catch (Exception ex) { ErrorMessageHelper.showErrorDialog(this, ex); } } /** * アプリケーションの設定ダイアログを開く */ protected void onPreferences() { AppConfigDialog appConfigDlg = new AppConfigDialog(this); appConfigDlg.setVisible(true); } /** * 新規モードでインポートウィザードを実行する.
*/ protected void onImportNew() { if (!characterData.isValid()) { Toolkit tk = Toolkit.getDefaultToolkit(); tk.beep(); return; } try { // インポートウィザードの実行(新規モード) ImportWizardDialog importWizDialog = new ImportWizardDialog(this, null, null); importWizDialog.setVisible(true); int exitCode = importWizDialog.getExitCode(); if (exitCode == ImportWizardDialog.EXIT_PROFILE_CREATED) { CharacterData cd = importWizDialog.getImportedCharacterData(); if (cd != null && cd.isValid()) { // インポートしたキャラクターデータのプロファイルを開く. setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); try { MainFrame mainFrame = ProfileListManager.openProfile(cd); mainFrame.setVisible(true); } finally { setCursor(Cursor.getDefaultCursor()); } } } } catch (Exception ex) { ErrorMessageHelper.showErrorDialog(this, ex); } } /** * 現在のプロファイルに対するインポートウィザードを実行する.
* インポートが実行された場合は、パーツをリロードする.
* インポートウィザード表示中は監視スレッドは停止される.
* * @param initFile * アーカイブファィルまたはディレクトリ、指定がなければnull */ protected void onImport(List initFiles) { if (!characterData.isValid()) { Toolkit tk = Toolkit.getDefaultToolkit(); tk.beep(); return; } try { watchAgent.suspend(); try { // インポートウィザードの実行 ImportWizardDialog importWizDialog = new ImportWizardDialog(this, characterData, initFiles); importWizDialog.setVisible(true); if (importWizDialog.getExitCode() == ImportWizardDialog.EXIT_PROFILE_UPDATED) { CharacterData importedCd = importWizDialog.getImportedCharacterData(); CharacterDataChangeObserver.getDefault() .notifyCharacterDataChange(this, importedCd, false, true); } } finally { watchAgent.resume(); } } catch (Exception ex) { ErrorMessageHelper.showErrorDialog(this, ex); } } /** * パーツとお気に入りをリロードする.
* まだロードされていない場合はあらたにロードする.
* 引数newCdが指定されている場合は、現在のキャラクター定義の説明文を更新する.
* (説明文の更新以外には使用されない.)
* * @param newCd * 説明文更新のための更新されたキャラクターデータを指定する。null可 * @param forceRepaint * 必ず再描画する場合 * @throws IOException * 失敗 */ protected synchronized void reloadPartsAndFavorites(CharacterData newCd, boolean forceRepaint) throws IOException { if (newCd != null) { // (インポート画面では説明文のみ更新するので、それだけ取得) characterData.setDescription(newCd.getDescription()); } if ( !characterData.isPartsLoaded()) { // キャラクターデータが、まだ読み込まれていなければ読み込む. ProfileListManager.loadCharacterData(characterData); ProfileListManager.loadFavorites(characterData); partsSelectionManager.loadParts(); } else { // パーツデータをリロードする. if (characterData.reloadPartsData()) { partsSelectionManager.loadParts(); } // お気に入りをリロードする. CharacterDataPersistent persiste = CharacterDataPersistent.getInstance(); persiste.loadFavorites(characterData); // お気に入りが更新されたことを通知する. FavoritesChangeObserver.getDefault().notifyFavoritesChange( MainFrame.this, characterData); } // 現在選択されているパーツセットがない場合はデフォルトのパーツセットを選択する. if (showDefaultParts(false) || forceRepaint) { requestPreview(); } } protected void onExport() { if (!characterData.isValid()) { Toolkit tk = Toolkit.getDefaultToolkit(); tk.beep(); return; } ExportWizardDialog exportWizDlg = new ExportWizardDialog(this, characterData, previewPane.getPreviewImage()); exportWizDlg.setVisible(true); } protected void onResetColor() { if (!characterData.isValid()) { Toolkit tk = Toolkit.getDefaultToolkit(); tk.beep(); return; } Properties strings = LocalizedResourcePropertyLoader.getCachedInstance() .getLocalizedProperties(STRINGS_RESOURCE); if (JOptionPane.showConfirmDialog(this, strings.get("confirm.resetcolors"), strings.getProperty("confirm"), JOptionPane.YES_NO_OPTION, JOptionPane.QUESTION_MESSAGE) != JOptionPane.YES_OPTION) { return; } characterData.getPartsColorManager().resetPartsColorInfo(); partsColorCoordinator.initColorDialog(); requestPreview(); } /** * プロファイルを閉じる. */ protected void onCloseProfile() { saveWorkingSet(); ProfileListManager.unregisterUsedCharacterData(characterData); if (characterData.isValid()) { // 最後に使用したキャラクターデータとして記憶する. try { RecentDataPersistent recentPersist = RecentDataPersistent.getInstance(); recentPersist.saveRecent(characterData); } catch (Exception ex) { logger.log(Level.WARNING, "recent data saving failed.", ex); // recent情報の記録に失敗しても致命的ではないので、これは無視する. } } // イメージビルダスレッド・ディレクトリ監視スレッドを停止する. stopAgents(); // フレームウィンドウを破棄する. dispose(); // 破棄されたことをロギングする. logger.log(Level.FINE, "dispose mainframe."); } /** * 開いている、すべてのプロファイルを閉じる.
* (Mac OS Xのcmd+Qで閉じる場合などで使用される.)
*/ public static void closeAllProfiles() { // ウィンドウが閉じられることでアクティブなフレームが切り替わる場合を想定し、 // 現在のアクティブなウィンドウをあらかじめ記憶しておく MainFrame mainFrame = activedMainFrame; // gcをかけてファイナライズを促進させる SystemUtil.gc(); // ファイナライズされていないFrameのうち、ネイティブリソースと関連づけられている // フレームについて、それがMainFrameのインスタンスであれば閉じる. // ただし、現在アクティブなものは除く for (Frame frame : JFrame.getFrames()) { try { if (frame.isDisplayable()) { // ネイティブリソースと関連づけられているフレーム if (frame instanceof MainFrame && frame != mainFrame) { // MainFrameのインスタンスであるので閉じる処理が可能. // (現在アクティブなメインフレームは最後に閉じるため、ここでは閉じない.) ((MainFrame) frame).onCloseProfile(); } } } catch (Throwable ex) { logger.log(Level.SEVERE, "mainframe closing failed.", ex); // フレームを閉じるときに失敗した場合、通常、致命的問題だが // クローズ処理は継続しなければならない. } } // 現在アクティブなフレームを閉じる. // 最後に閉じることで「最後に使ったプロファイル」として記憶させる. if (activedMainFrame != null && activedMainFrame.isDisplayable()) { try { activedMainFrame.onCloseProfile(); } catch (Throwable ex) { logger.log(Level.SEVERE, "mainframe closing failed.", ex); // フレームを閉じるときに失敗した場合、通常、致命的問題だが // クローズ処理は継続しなければならない. } } } /** * 画面の作業状態を保存する. */ protected void saveWorkingSet() { if (!characterData.isValid()) { return; } try { // ワーキングセットの作成 WorkingSet workingSet = new WorkingSet(); workingSet.setCharacterDocBase(characterData.getDocBase()); workingSet.setCharacterDataRev(characterData.getRev()); PartsSet partsSet = partsSelectionManager.createPartsSet(); workingSet.setPartsSet(partsSet); workingSet.setPartsColorInfoMap(characterData .getPartsColorManager().getPartsColorInfoMap()); workingSet.setLastUsedSaveDir(imageSaveHelper.getLastUsedSaveDir()); workingSet.setLastUsedExportDir(ExportWizardDialog.getLastUsedDir()); workingSet.setLastUsePresetParts(lastUsePresetParts); workingSet .setCharacterData(characterData.duplicateBasicInfo(false)); // パーツセットは保存しない. workingSet.setWallpaperInfo(wallpaperInfo); // XML形式でのワーキングセットの保存 WorkingSetPersist workingSetPersist = WorkingSetPersist .getInstance(); workingSetPersist.saveWorkingSet(workingSet); } catch (Exception ex) { ErrorMessageHelper.showErrorDialog(this, ex); } } /** * 画面の作業状態を復元する. * * @return ワーキングセットを読み込んだ場合はtrue、そうでなければfalse */ protected boolean loadWorkingSet() { if (!characterData.isValid()) { return false; } try { WorkingSetPersist workingSetPersist = WorkingSetPersist .getInstance(); WorkingSet2 workingSet2 = workingSetPersist .loadWorkingSet(characterData); if (workingSet2 == null) { // ワーキングセットがない場合. return false; } URI docBase = characterData.getDocBase(); if (docBase != null && !docBase.equals(workingSet2.getCharacterDocBase())) { // docBaseが一致せず return false; } String sig = characterData.toSignatureString(); if (!sig.equals(workingSet2.getCharacterDataSig())) { // 構造が一致せず. return false; } // パーツの色情報を復元する. Map partsColorInfoMap = characterData .getPartsColorManager().getPartsColorInfoMap(); workingSet2.createCompatible(characterData, partsColorInfoMap); // 選択されているパーツの復元 IndependentPartsSetInfo partsSetInfo = workingSet2 .getCurrentPartsSet(); if (partsSetInfo != null) { PartsSet partsSet = IndependentPartsSetInfo.convertPartsSet( partsSetInfo, characterData, false); selectPresetParts(partsSet); // 最後に選択したお気に入り情報の復元 IndependentPartsSetInfo lastUsePresetPartsInfo = workingSet2 .getLastUsePresetParts(); if (lastUsePresetPartsInfo != null && lastUsePresetPartsInfo.getId() != null && lastUsePresetPartsInfo.getId().trim().length() > 0) { PartsSet lastUsePresetParts = IndependentPartsSetInfo .convertPartsSet(lastUsePresetPartsInfo, characterData, false); if (lastUsePresetParts.isSameStructure(partsSet)) { this.lastUsePresetParts = lastUsePresetParts; showPresetName(lastUsePresetParts); } } } // 最後に保存したディレクトリを復元する. imageSaveHelper.setLastUseSaveDir(workingSet2.getLastUsedSaveDir()); ExportWizardDialog.setLastUsedDir(workingSet2 .getLastUsedExportDir()); // 壁紙情報を取得する. WallpaperInfo wallpaperInfo = workingSet2.getWallpaperInfo(); if (wallpaperInfo != null) { // 壁紙情報を保存し、その情報をもとに背景を再描画する. // (適用に失敗した場合はエラーは無視し、壁紙情報は保存しない.) applyWallpaperInfo(wallpaperInfo, true); } return true; } catch (Exception ex) { ErrorMessageHelper.showErrorDialog(this, ex); } return false; } protected void onAbout() { try { AboutBox aboutBox = new AboutBox(this); aboutBox.showAboutBox(); } catch (Exception ex) { ErrorMessageHelper.showErrorDialog(this, ex); } } protected void onHelp() { Properties strings = LocalizedResourcePropertyLoader.getCachedInstance() .getLocalizedProperties(STRINGS_RESOURCE); String helpURL = strings.getProperty("help.url"); String helpDescription = strings.getProperty("help.show"); DesktopUtilities.browse(this, helpURL, helpDescription); } protected void onFlipHolizontal() { if (!characterData.isValid()) { Toolkit tk = Toolkit.getDefaultToolkit(); tk.beep(); return; } double[] affineTransformParameter = partsSelectionManager.getAffineTransformParameter(); if (affineTransformParameter == null) { // 左右フリップするアフィン変換パラメータを構築する. Dimension siz = characterData.getImageSize(); if (siz != null) { affineTransformParameter = new double[] {-1., 0, 0, 1., siz.width, 0}; } } else { // アフィン変換パラメータをクリアする. affineTransformParameter = null; } partsSelectionManager.setAffineTransformParameter(affineTransformParameter); requestPreview(); } protected void onSetDefaultPicture() { if (!characterData.isValid()) { Toolkit tk = Toolkit.getDefaultToolkit(); tk.beep(); return; } try { BufferedImage samplePicture = previewPane.getPreviewImage(); if (samplePicture != null) { CharacterDataPersistent persist = CharacterDataPersistent.getInstance(); persist.saveSamplePicture(characterData, samplePicture); } } catch (Exception ex) { ErrorMessageHelper.showErrorDialog(this, ex); } } protected void onInformation() { if (!characterData.isValid()) { Toolkit tk = Toolkit.getDefaultToolkit(); tk.beep(); return; } PartsSet partsSet = partsSelectionManager.createPartsSet(); InformationDialog infoDlg = new InformationDialog(this, characterData, partsSet); infoDlg.setVisible(true); } protected void onManageFavorites() { if (!characterData.isValid()) { Toolkit tk = Toolkit.getDefaultToolkit(); tk.beep(); return; } if (lastUseManageFavoritesDialog != null) { // 開いているダイアログがあれば、それにフォーカスを当てる. if (lastUseManageFavoritesDialog.isDisplayable() && lastUseManageFavoritesDialog.isVisible()) { lastUseManageFavoritesDialog.requestFocus(); return; } } // お気に入り編集ダイアログを開く ManageFavoriteDialog dlg = new ManageFavoriteDialog(this, characterData); dlg.setFavoriteManageCallback(new FavoriteManageCallback() { public void selectFavorites(PartsSet partsSet) { // お気に入り編集ダイアログで選択されたパーツを選択表示する. selectPresetParts(partsSet); } public void updateFavorites(CharacterData characterData, boolean savePreset) { // お気に入りを登録する. try { setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); try { CharacterDataPersistent persiste = CharacterDataPersistent .getInstance(); if (savePreset) { persiste.updateProfile(characterData); } persiste.saveFavorites(characterData); // お気に入りが更新されたことを通知する. FavoritesChangeObserver.getDefault() .notifyFavoritesChange(MainFrame.this, characterData); } finally { setCursor(Cursor.getDefaultCursor()); } } catch (Exception ex) { ErrorMessageHelper.showErrorDialog(MainFrame.this, ex); } } }); WindowAdjustLocationSupport.alignRight(this, dlg, 0, true); dlg.setVisible(true); lastUseManageFavoritesDialog = dlg; } protected void onRegisterFavorite() { if (!characterData.isValid()) { Toolkit tk = Toolkit.getDefaultToolkit(); tk.beep(); return; } try { // パーツセットを生成 PartsSet partsSet = partsSelectionManager.createPartsSet(); if (partsSet.isEmpty()) { // 空のパーツセットは登録しない. return; } Properties strings = LocalizedResourcePropertyLoader.getCachedInstance() .getLocalizedProperties(STRINGS_RESOURCE); // お気に入りに登録するパーツセットが最後に使用したお気に入りと同じ構成であれば、 // そのお気に入り名を使用する. String initName = getSuggestPartsSetName(partsSet, false); // カラー情報の有無のチェックボックス. JCheckBox chkColorInfo = new JCheckBox(strings.getProperty("input.favoritesColorInfo")); chkColorInfo.setSelected(true); String partsSetId = null; if (initName != null && lastUsePresetParts != null) { partsSetId = lastUsePresetParts.getPartsSetId(); } // 上書き保存の可否のチェックボックス JCheckBox chkOverwrite = new JCheckBox(strings.getProperty("input.favoritesOverwrite")); chkOverwrite.setSelected(partsSetId != null && partsSetId.length() > 0); chkOverwrite.setEnabled(partsSetId != null && partsSetId.length() > 0); // チェックボックスパネル Box checkboxsPanel = new Box(BoxLayout.PAGE_AXIS); checkboxsPanel.add(chkColorInfo); checkboxsPanel.add(chkOverwrite); // 入力ダイアログを開く String name = (String) JOptionPane.showInputDialog(this, checkboxsPanel, strings.getProperty("input.favorites"), JOptionPane.QUESTION_MESSAGE, null, null, initName == null ? "" : initName); if (name == null || name.trim().length() == 0) { return; } boolean includeColorInfo = chkColorInfo.isSelected(); if (!includeColorInfo) { // カラー情報を除去する. partsSet.removeColorInfo(); } // 新規の場合、もしくは上書きしない場合はIDを設定する. if (partsSetId == null || !chkOverwrite.isSelected()) { partsSetId = "ps" + UUID.randomUUID().toString(); } partsSet.setPartsSetId(partsSetId); // 名前を設定する. partsSet.setLocalizedName(name); // ファイルに保存 setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); try { CharacterDataPersistent persiste = CharacterDataPersistent.getInstance(); // 現在の最新情報を取り出す. characterData.clearPartsSets(true); persiste.loadFavorites(characterData); // お気に入りコレクションに登録 characterData.addPartsSet(partsSet); persiste.saveFavorites(characterData); // お気に入りが更新されたことを通知する. FavoritesChangeObserver.getDefault().notifyFavoritesChange( MainFrame.this, characterData); } finally { setCursor(Cursor.getDefaultCursor()); } // 最後に選択したお気に入りにする lastUsePresetParts = partsSet; showPresetName(partsSet); } catch (Exception ex) { ErrorMessageHelper.showErrorDialog(this, ex); } } /** * ランダム選択ダイアログを開く. */ protected void onToolRandom() { if (!characterData.isValid()) { Toolkit tk = Toolkit.getDefaultToolkit(); tk.beep(); return; } if (lastUsePartsRandomChooserDialog != null) { // 開いているダイアログがあれば、それにフォーカスを当てる. if (lastUsePartsRandomChooserDialog.isDisplayable() && lastUsePartsRandomChooserDialog.isVisible()) { lastUsePartsRandomChooserDialog.requestFocus(); return; } } // お気に入り編集ダイアログを開く PartsRandomChooserDialog dlg = new PartsRandomChooserDialog(this, characterData, new PartsRandomChooserDialog.PartsSetSynchronizer() { public PartsSet getCurrentPartsSet() { // 現在のパーツセットを生成 return partsSelectionManager.createPartsSet(); } public void setPartsSet(PartsSet partsSet) { selectPresetParts(partsSet); } public boolean isExcludePartsIdentifier(PartsIdentifier partsIdentifier) { Boolean exclude = randomExcludePartsIdentifierMap .get(partsIdentifier); return exclude != null && exclude.booleanValue(); } public void setExcludePartsIdentifier(PartsIdentifier partsIdentifier, boolean exclude) { randomExcludePartsIdentifierMap.put(partsIdentifier, exclude); } }); WindowAdjustLocationSupport.alignRight(this, dlg, 0, true); dlg.setVisible(true); lastUsePartsRandomChooserDialog = dlg; } /** * ランダム選択パーツで選択候補から除外するパーツのマップ. */ private HashMap randomExcludePartsIdentifierMap = new HashMap(); /** * すべての解除可能なパーツの選択を解除する。 */ protected void onDeselectAll() { partsSelectionManager.deselectAll(); } /** * 単一選択カテゴリのパーツの解除を許可する。 */ protected void onDeselectableAllCategory() { partsSelectionManager .setDeselectableSingleCategory( !partsSelectionManager .isDeselectableSingleCategory()); } /** * プレビューのズームボックスの表示制御 */ protected void onEnableZoom() { previewPane.setVisibleZoomBox( !previewPane.isVisibleZoomBox()); } /** * メニューバーを構築します. * * @return メニューバー */ protected JMenuBar createMenuBar() { final Properties strings = LocalizedResourcePropertyLoader .getCachedInstance().getLocalizedProperties(STRINGS_RESOURCE); MenuDataFactory[] menus = new MenuDataFactory[] { new MenuDataFactory("menu.file", new MenuDataFactory[] { new MenuDataFactory("file.openProfile", new ActionListener() { public void actionPerformed(ActionEvent e) { onOpenProfile(); } }), new MenuDataFactory("file.savePicture", new ActionListener() { public void actionPerformed(ActionEvent e) { onSavePicture(); } }), new MenuDataFactory("file.ukagaka", new MenuDataFactory[] { new MenuDataFactory("file.saveAsUkagaka", new ActionListener() { public void actionPerformed(ActionEvent e) { onSaveAsUkagaka(); }; }), new MenuDataFactory("file.convertUkagaka", new ActionListener() { public void actionPerformed(ActionEvent e) { onConvertUkagaka(); }; }), }), null, new MenuDataFactory("file.editprofile", new ActionListener() { public void actionPerformed(ActionEvent e) { onEditProfile(); } }), new MenuDataFactory("file.opendir", new ActionListener() { public void actionPerformed(ActionEvent e) { onBrowseProfileDir(); } }), new MenuDataFactory("file.import", new MenuDataFactory[] { new MenuDataFactory("file.importMe", new ActionListener() { public void actionPerformed(ActionEvent e) { onImport(null); }; }), new MenuDataFactory("file.importNew", new ActionListener() { public void actionPerformed(ActionEvent e) { onImportNew(); }; }), }), new MenuDataFactory("file.export", new ActionListener() { public void actionPerformed(ActionEvent e) { onExport(); }; }), new MenuDataFactory("file.manageParts", new ActionListener() { public void actionPerformed(ActionEvent e) { onManageParts(); } }), new MenuDataFactory("file.preferences", new ActionListener() { public void actionPerformed(ActionEvent e) { onPreferences(); }; }), null, new MenuDataFactory("file.closeProfile", new ActionListener() { public void actionPerformed(ActionEvent e) { onCloseProfile(); } }), }), new MenuDataFactory("menu.edit", new MenuDataFactory[] { new MenuDataFactory("edit.search", new ActionListener() { public void actionPerformed(ActionEvent e) { openSearchDialog(); } }), new MenuDataFactory("edit.copy", new ActionListener() { public void actionPerformed(ActionEvent e) { onCopy((e.getModifiers() & ActionEvent.SHIFT_MASK) != 0); } }), new MenuDataFactory("edit.flipHorizontal", new ActionListener() { public void actionPerformed(ActionEvent e) { onFlipHolizontal(); } }), new MenuDataFactory("edit.resetcolor", new ActionListener() { public void actionPerformed(ActionEvent e) { onResetColor(); } }), null, new MenuDataFactory("edit.setDefaultPicture", new ActionListener() { public void actionPerformed(ActionEvent e) { onSetDefaultPicture(); } }), new MenuDataFactory("edit.information", new ActionListener() { public void actionPerformed(ActionEvent e) { onInformation(); } }), null, new MenuDataFactory("edit.deselectall", new ActionListener() { public void actionPerformed(ActionEvent e) { onDeselectAll(); } }), new MenuDataFactory("edit.deselectparts", true, new ActionListener() { public void actionPerformed(ActionEvent e) { onDeselectableAllCategory(); } }), new MenuDataFactory("edit.enableAutoShrink", true, new ActionListener() { public void actionPerformed(ActionEvent e) { onClickPartsCategoryTitle(null, true); } }), null, new MenuDataFactory("edit.enableZoomBox", true, new ActionListener() { public void actionPerformed(ActionEvent e) { onEnableZoom(); } }), null, new MenuDataFactory("edit.changeBgColor", new ActionListener() { public void actionPerformed(ActionEvent e) { onChangeBgColor(); } }), new MenuDataFactory("edit.changeWallpaper", new ActionListener() { public void actionPerformed(ActionEvent e) { onChangeWallpaper(); } }), }), new MenuDataFactory("menu.favorite", new MenuDataFactory[] { new MenuDataFactory("favorite.register", new ActionListener() { public void actionPerformed(ActionEvent e) { onRegisterFavorite(); } }), new MenuDataFactory("favorite.manage", new ActionListener() { public void actionPerformed(ActionEvent e) { onManageFavorites(); } }), null, }), new MenuDataFactory("menu.tool", new MenuDataFactory[]{new MenuDataFactory( "tool.random", new ActionListener() { public void actionPerformed(ActionEvent e) { onToolRandom(); } }),}), new MenuDataFactory("menu.help", new MenuDataFactory[] { new MenuDataFactory("help.recommendations", (ActionListener) null), null, new MenuDataFactory("help.help", new ActionListener() { public void actionPerformed(ActionEvent e) { onHelp(); } }), new MenuDataFactory("help.forum", DesktopUtilities.createBrowseAction( MainFrame.this, strings.getProperty("help.forum.url"), strings.getProperty("help.forum.description")) ), new MenuDataFactory("help.bugreport", DesktopUtilities.createBrowseAction( MainFrame.this, strings.getProperty("help.reportbugs.url"), strings.getProperty("help.reportbugs.description")) ), new MenuDataFactory("help.about", new ActionListener() { public void actionPerformed(ActionEvent e) { onAbout(); } }), }), }; final MenuBuilder menuBuilder = new MenuBuilder(); JMenuBar menuBar = menuBuilder.createMenuBar(menus); menuBuilder.getJMenu("menu.edit").addMenuListener(new MenuListener() { public void menuCanceled(MenuEvent e) { // do nothing. } public void menuDeselected(MenuEvent e) { // do nothing. } public void menuSelected(MenuEvent e) { menuBuilder.getJMenuItem("edit.copy").setEnabled(previewPane.getPreviewImage() != null); menuBuilder.getJMenuItem("edit.deselectparts").setSelected( partsSelectionManager.isDeselectableSingleCategory()); menuBuilder.getJMenuItem("edit.enableAutoShrink").setSelected(minimizeMode); menuBuilder.getJMenuItem("edit.enableZoomBox").setSelected(previewPane.isVisibleZoomBox()); } }); final JMenu mnuFavorites = menuBuilder.getJMenu("menu.favorite"); mnuFavorites.addMenuListener(new MenuListener() { public void menuCanceled(MenuEvent e) { // do nothing. } public void menuDeselected(MenuEvent e) { // do nothing. } public void menuSelected(MenuEvent e) { onSelectedFavoriteMenu(mnuFavorites); } }); // J2SE5の場合は「パーツディレクトリを開く」コマンドは使用不可とする. if (System.getProperty("java.version").startsWith("1.5")) { menuBuilder.getJMenuItem("file.opendir").setEnabled(false); } // お勧めサイトメニュー構築 final JMenu mnuRecomendation = menuBuilder.getJMenu("help.recommendations"); JMenu mnuHelp = menuBuilder.getJMenu("menu.help"); mnuHelp.addMenuListener(new MenuListener() { public void menuCanceled(MenuEvent e) { // do nothing. } public void menuDeselected(MenuEvent e) { // do nothing. } public void menuSelected(MenuEvent e) { onSelectedRecommendationMenu(mnuRecomendation); } }); return menuBar; } } CharacterManaJ/src/charactermanaj/ui/UkagakaConvertDialog.java0000644000175000017500000003131212560206305024627 0ustar paulliupaulliupackage charactermanaj.ui; import java.awt.BorderLayout; import java.awt.Color; import java.awt.Container; import java.awt.GridBagConstraints; import java.awt.GridBagLayout; import java.awt.Insets; import java.awt.TextField; import java.awt.Toolkit; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.KeyEvent; import java.awt.event.WindowAdapter; import java.awt.event.WindowEvent; import java.awt.image.BufferedImage; import java.util.Properties; import javax.swing.AbstractAction; import javax.swing.ActionMap; import javax.swing.BorderFactory; import javax.swing.Box; import javax.swing.BoxLayout; import javax.swing.ButtonGroup; import javax.swing.InputMap; import javax.swing.JButton; import javax.swing.JCheckBox; import javax.swing.JComponent; import javax.swing.JDialog; import javax.swing.JFrame; import javax.swing.JPanel; import javax.swing.JRadioButton; import javax.swing.JRootPane; import javax.swing.JScrollPane; import javax.swing.KeyStroke; import javax.swing.event.ChangeEvent; import javax.swing.event.ChangeListener; import charactermanaj.Main; import charactermanaj.graphics.io.UkagakaImageConverter; import charactermanaj.util.LocalizedResourcePropertyLoader; /** * 伺か用PNG/PNA出力設定ダイアログ * @author seraphy */ public class UkagakaConvertDialog extends JDialog { private static final long serialVersionUID = 4189631881766588004L; /** * リソース */ protected static final String STRINGS_RESOURCE = "languages/ukagakaConvertDialog"; /** * キャプション */ private TextField caption = new TextField(); /** * キャンセル */ private AbstractAction actCancel; /** * 保存(デフォルトアクション) */ private AbstractAction actSave; /** * プレビュー(PNG) */ private SamplePicturePanel opaqueImagePanel = new SamplePicturePanel(); /** * プレビュー(PNA) */ private SamplePicturePanel alphaImagePanel = new SamplePicturePanel(); /** * 透過色キー表示ボックス */ private ColorBox colorBox = new ColorBox(); /** * エクスポート対象の元イメージ. */ private BufferedImage originalImage; /** * 透過色をマニュアルとするか? */ private boolean manualTransparentColorKey; /** * 透過キー自動モード */ private JRadioButton radioAuto; /** * 透過キー手動選択モード */ private JRadioButton radioManual; /** * 上書きモードチェックボックス */ private JCheckBox chkOverwriteOption; /** * 終了コード */ private Object result; /** * 保存ボタンアクションリスナ.
*/ private ActionListener saveActionListener; /** * 上書きオプションの表示フラグ */ private boolean showOverwriteOption; /** * 伺か用PNG/PNA出力設定ダイアログを構築する. * @param parent 親フレーム */ public UkagakaConvertDialog(JFrame parent) { this(parent, false); } /** * 伺か用PNG/PNA出力設定ダイアログを構築する. * @param parent 親フレーム * @param overwriteOption 上書きオプションの表示 */ public UkagakaConvertDialog(JFrame parent, boolean overwriteOption) { this(parent, null, overwriteOption); } /** * 伺か用PNG/PNA出力設定ダイアログを構築する. * @param parent 親フレーム * @param saveActionListener 保存ボタンアクション * @param overwriteOption 上書きオプションの表示 */ public UkagakaConvertDialog(JFrame parent, ActionListener saveActionListener, boolean overwriteOption) { super(parent, true); this.saveActionListener = saveActionListener; this.showOverwriteOption = overwriteOption; addWindowListener(new WindowAdapter() { @Override public void windowClosing(WindowEvent e) { onClose(); } @Override public void windowOpened(WindowEvent e) { result = null; } }); try { initComponent(); } catch (RuntimeException ex) { dispose(); throw ex; } } /** * コンポーネントの初期化 */ private void initComponent() { Properties strings = LocalizedResourcePropertyLoader.getCachedInstance() .getLocalizedProperties(STRINGS_RESOURCE); setTitle(strings.getProperty("title")); Toolkit tk = Toolkit.getDefaultToolkit(); actCancel = new AbstractAction(strings.getProperty("btn.cancel")) { private static final long serialVersionUID = -1L; public void actionPerformed(ActionEvent e) { onClose(); } }; actSave = new AbstractAction(strings.getProperty("btn.save")) { private static final long serialVersionUID = -1L; public void actionPerformed(ActionEvent e) { onSave(); } }; JButton btnCancel = new JButton(actCancel); JButton btnSave = new JButton(actSave); Container contentPane = getContentPane(); contentPane.setLayout(new BorderLayout(3, 3)); contentPane.add(caption, BorderLayout.NORTH); caption.setEditable(false); caption.setVisible(false); JScrollPane opaqueSp = new JScrollPane(opaqueImagePanel); JScrollPane alphaSp = new JScrollPane(alphaImagePanel); JPanel previewSpPane = new JPanel(); BoxLayout boxlayout = new BoxLayout(previewSpPane, BoxLayout.LINE_AXIS); previewSpPane.setLayout(boxlayout); previewSpPane.add(opaqueSp); previewSpPane.add(Box.createHorizontalStrut(3)); previewSpPane.add(alphaSp); previewSpPane.setBorder(BorderFactory.createCompoundBorder( BorderFactory.createTitledBorder(strings.getProperty("preview")), BorderFactory.createEmptyBorder(3, 3, 3, 3) )); JPanel centerPane = new JPanel(new BorderLayout()); centerPane.add(previewSpPane, BorderLayout.CENTER); JPanel transparentColorPanel = new JPanel(); GridBagLayout tc_gbl = new GridBagLayout(); transparentColorPanel.setLayout(tc_gbl); transparentColorPanel.setBorder(BorderFactory.createCompoundBorder( BorderFactory.createTitledBorder(strings.getProperty("caption.chooseTransparentColorKey")), BorderFactory.createEmptyBorder(3, 3, 3, 3) )); centerPane.add(transparentColorPanel, BorderLayout.SOUTH); GridBagConstraints gbc = new GridBagConstraints(); radioAuto = new JRadioButton(strings.getProperty("radio.auto")); radioAuto.addChangeListener(new ChangeListener() { public void stateChanged(ChangeEvent e) { onChange(!radioAuto.isSelected()); } }); radioManual = new JRadioButton(strings.getProperty("radio.manual")); radioAuto.addChangeListener(new ChangeListener() { public void stateChanged(ChangeEvent e) { onChange(!radioAuto.isSelected()); } }); ButtonGroup btngroup = new ButtonGroup(); btngroup.add(radioAuto); btngroup.add(radioManual); radioAuto.setSelected(!manualTransparentColorKey); gbc.gridx = 0; gbc.gridy = 0; gbc.gridheight = 1; gbc.gridwidth = 1; gbc.weightx = 0.; gbc.weighty = 0.; gbc.insets = new Insets(3, 3, 3, 3); gbc.anchor = GridBagConstraints.WEST; gbc.fill = GridBagConstraints.BOTH; transparentColorPanel.add(radioAuto, gbc); gbc.gridx = 1; gbc.gridy = 0; transparentColorPanel.add(radioManual, gbc); gbc.gridx = 2; gbc.gridy = 0; transparentColorPanel.add(colorBox, gbc); contentPane.add(centerPane, BorderLayout.CENTER); JPanel btnPanel = new JPanel(); GridBagLayout gbl = new GridBagLayout(); btnPanel.setLayout(gbl); gbc.gridx = 0; gbc.gridy = 0; gbc.gridheight = 1; gbc.gridwidth = 1; gbc.weightx = 1.; gbc.weighty = 0.; gbc.insets = new Insets(3, 3, 3, 3); gbc.anchor = GridBagConstraints.WEST; gbc.fill = GridBagConstraints.BOTH; chkOverwriteOption = new JCheckBox(strings.getProperty("chk.overwriteOriginalFile")); if (showOverwriteOption) { btnPanel.add(chkOverwriteOption, gbc); } else { btnPanel.add(Box.createHorizontalGlue(), gbc); } gbc.gridx = Main.isLinuxOrMacOSX() ? 2 : 1; gbc.gridy = 0; gbc.weightx = 0.; btnPanel.add(btnSave, gbc); gbc.gridx = Main.isLinuxOrMacOSX() ? 1 : 2; gbc.gridy = 0; btnPanel.add(btnCancel, gbc); btnPanel.setBorder(BorderFactory.createEmptyBorder(3, 3, 3, 45)); contentPane.add(btnPanel, BorderLayout.SOUTH); JRootPane rootPane = getRootPane(); InputMap im = rootPane.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW); ActionMap am = rootPane.getActionMap(); im.put(KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0), "closeExportWizDialog"); im.put(KeyStroke.getKeyStroke(KeyEvent.VK_W, tk.getMenuShortcutKeyMask()), "closeExportWizDialog"); am.put("closeExportWizDialog", actCancel); rootPane.setDefaultButton(btnSave); rootPane.setBorder(BorderFactory.createEmptyBorder(3, 5, 3, 5)); setSize(450, 450); setLocationRelativeTo(getParent()); // colorBoxの色変更イベントのハンドル colorBox.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { onChooseTransparentColorKey(); } }); } /** * 出力するイメージを設定する. * @param img イメージ(TYPE_INT_ARGBのみ) * @param colorKey 透過色に指定するカラーキー(候補)、nullの場合はデフォルト */ public void setExportImage(BufferedImage img, Color colorKey) { if (img == null) { throw new IllegalArgumentException(); } if (img.getType() != BufferedImage.TYPE_INT_ARGB) { throw new IllegalArgumentException("TYPE_INT_ARGB以外は指定できません。"); } if (colorKey == null) { colorKey = Color.GREEN; } this.originalImage = img; colorBox.setColorKey(colorKey); rebuildImage(); } public BufferedImage getOpaqueImage() { return opaqueImagePanel.getSamplePictrue(); } public BufferedImage getAlphaImage() { return alphaImagePanel.getSamplePictrue(); } public void setAutoTransparentColor(boolean mode) { if (mode) { radioAuto.setSelected(true); radioManual.setSelected(false); } else { radioManual.setSelected(true); radioAuto.setSelected(false); } } public Color getTransparentColorKey() { return colorBox.getColorKey(); } public void setTransparentColorKey(Color colorKey) { colorBox.setColorKey(colorKey); } public boolean isAutoTransparentColor() { return radioAuto.isSelected(); } public boolean isOverwriteOriginalFile() { return chkOverwriteOption.isSelected(); } public void setOverwriteOriginalFile(boolean overwriteOriginalFile) { chkOverwriteOption.setSelected(overwriteOriginalFile); } protected void onClose() { result = null; dispose(); } protected void onSave() { if (saveActionListener != null) { ActionEvent e = new ActionEvent(this, 0, "save"); saveActionListener.actionPerformed(e); } } public void setSaveActionListener(ActionListener saveActionListener) { this.saveActionListener = saveActionListener; } public ActionListener getSaveActionListener() { return saveActionListener; } public Object getResult() { return result; } public void setResult(Object result) { this.result = result; } public void setCaption(String text) { if (text == null || text.length() == 0) { caption.setText(""); caption.setVisible(false); } else { caption.setText(text); caption.setVisible(true); } } public String getCaption() { return caption.getText(); } /** * 透過色のマニュアル選択.
*/ protected void onChooseTransparentColorKey() { // モードを手動に切り替え setAutoTransparentColor(false); // プレビューを再構築 rebuildImage(); } /** * 伺か用PNGの透過色キーの自動・マニュアルの切り替えイベント.
* @param modeManual */ protected void onChange(boolean modeManual) { if (manualTransparentColorKey != modeManual) { manualTransparentColorKey = modeManual; rebuildImage(); } } /** * 伺か用のPNG/PNA画像を生成してプレビューに設定します.
*/ protected void rebuildImage() { if (originalImage == null) { return; } UkagakaImageConverter conv = UkagakaImageConverter.getInstance(); BufferedImage pna = conv.createUkagakaPNA(originalImage); Color transparentColorKey = null; if (manualTransparentColorKey) { transparentColorKey = colorBox.getColorKey(); } else { transparentColorKey = conv.detectTransparentColorKey(originalImage); } BufferedImage png = conv.createUkagakaPNG(originalImage, transparentColorKey); opaqueImagePanel.setSamplePicture(png); alphaImagePanel.setSamplePicture(pna); } } CharacterManaJ/src/charactermanaj/ui/ProfileEditDialog.java0000644000175000017500000025144412560206305024142 0ustar paulliupaulliupackage charactermanaj.ui; import java.awt.BorderLayout; import java.awt.Color; import java.awt.Component; import java.awt.Container; import java.awt.Dimension; import java.awt.GridBagConstraints; import java.awt.GridBagLayout; import java.awt.Insets; import java.awt.Toolkit; import java.awt.event.ActionEvent; import java.awt.event.KeyEvent; import java.awt.event.WindowAdapter; import java.awt.event.WindowEvent; import java.net.URI; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; import java.util.LinkedList; import java.util.List; import java.util.Properties; import java.util.UUID; import java.util.logging.Level; import java.util.logging.Logger; import java.util.regex.Pattern; import javax.swing.AbstractAction; import javax.swing.Action; import javax.swing.BorderFactory; import javax.swing.Box; import javax.swing.ComboBoxModel; import javax.swing.DefaultCellEditor; import javax.swing.InputMap; import javax.swing.JButton; import javax.swing.JCheckBox; import javax.swing.JComboBox; import javax.swing.JComponent; import javax.swing.JDialog; import javax.swing.JFrame; import javax.swing.JLabel; import javax.swing.JOptionPane; import javax.swing.JPanel; import javax.swing.JRootPane; import javax.swing.JScrollPane; import javax.swing.JSpinner; import javax.swing.JTabbedPane; import javax.swing.JTable; import javax.swing.JTextArea; import javax.swing.JTextField; import javax.swing.KeyStroke; import javax.swing.ListSelectionModel; import javax.swing.SpinnerNumberModel; import javax.swing.SwingConstants; import javax.swing.event.DocumentEvent; import javax.swing.event.DocumentListener; import javax.swing.event.ListDataEvent; import javax.swing.event.ListDataListener; import javax.swing.table.TableCellRenderer; import javax.swing.table.TableColumnModel; import charactermanaj.Main; import charactermanaj.graphics.colormodel.ColorModels; import charactermanaj.model.AppConfig; import charactermanaj.model.CharacterData; import charactermanaj.model.ColorGroup; import charactermanaj.model.Layer; import charactermanaj.model.PartsCategory; import charactermanaj.model.PartsIdentifier; import charactermanaj.model.PartsSet; import charactermanaj.model.RecommendationURL; import charactermanaj.model.io.CharacterDataDefaultProvider; import charactermanaj.model.io.CharacterDataDefaultProvider.DefaultCharacterDataVersion; import charactermanaj.model.io.CharacterDataPersistent; import charactermanaj.ui.model.AbstractTableModelWithComboBoxModel; import charactermanaj.util.DesktopUtilities; import charactermanaj.util.ErrorMessageHelper; import charactermanaj.util.LocalizedResourcePropertyLoader; public class ProfileEditDialog extends JDialog { private static final long serialVersionUID = 8559918820826437849L; /** * ローカライズ文字列 */ protected static final String STRINGS_RESOURCE = "languages/profileditdialog"; protected static class JTextFieldEx extends JTextField { private static final long serialVersionUID = -8608404290439184405L; private boolean error; @Override public Color getBackground() { if (error) { AppConfig appConfig = AppConfig.getInstance(); return appConfig.getInvalidBgColor(); } return super.getBackground(); } public void setError(boolean error) { if (this.error != error) { this.error = error; repaint(); } } public boolean isError() { return error; } } /** * オリジナルのデータ. */ private CharacterData original; /** * キャラクターデータID */ private JTextFieldEx txtCharacterID; /** * キャラクターデータ Rev */ private JTextFieldEx txtCharacterRev; /** * キャラクターデータ DocBase(読み込み専用) */ private JTextField txtCharacterDocBase; /** * キャラクター名 */ private JTextFieldEx txtCharacterName; /** * イメージ幅 */ private JSpinner txtImageWidth; /** * イメージ高さ */ private JSpinner txtImageHeight; /** * 作者 */ private JTextField txtAuthor; /** * 説明 */ private JTextArea txtDescription; /** * ディレクトリの監視 */ private JCheckBox chkWatchDir; /** * カラーグループのモデル */ private ColorGroupsTableModel colorGroupsTableModel; /** * カテゴリのモデル */ private CategoriesTableModel categoriesTableModel; /** * レイヤーのモデル */ private LayersTableModel layersTableModel; /** * パーツセットのモデル */ private PartssetsTableModel partssetsTableModel; /** * お勧めリンクのモデル */ private RecommendationTableModel recommendationsTableModel; /** * 画面の内容から生成された新しいキャラクターデータ、もしくはnull */ private CharacterData result; /** * OKボタン */ private JButton btnOK; /** * キャラクターデータの編集画面を構築する.
* * @param parent * 親、もしくはnull * @param original * オリジナルのキャラクターデータ(変更されない) */ public ProfileEditDialog(JFrame parent, CharacterData original) { super(parent, true); initDialog(parent, original); } /** * キャラクターデータの編集画面を構築する.
* * @param parent * 親、もしくはnull * @param original * オリジナルのキャラクターデータ(変更されない) */ public ProfileEditDialog(JDialog parent, CharacterData original) { super(parent, true); initDialog(parent, original); } /** * 編集ダイアログを初期化する. * * @param origianl * 編集もとキャラクター定義 */ private void initDialog(Component parent, CharacterData original) { // 元情報 if (original == null) { throw new IllegalArgumentException(); } this.original = original; // ウィンドウイベントのハンドル setDefaultCloseOperation(JDialog.DO_NOTHING_ON_CLOSE); addWindowListener(new WindowAdapter() { @Override public void windowClosing(WindowEvent e) { onClose(); } }); // 設定 AppConfig appConfig = AppConfig.getInstance(); final Properties strings = LocalizedResourcePropertyLoader .getCachedInstance().getLocalizedProperties(STRINGS_RESOURCE); // タイトル String title; if (original.isValid()) { title = strings.getProperty("title.edit"); } else { title = strings.getProperty("title.new"); } setTitle(title); // OK/CANCEL PANEL JPanel buttonsPanel = new JPanel(); buttonsPanel.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 42)); GridBagLayout buttonsPanelLayout = new GridBagLayout(); buttonsPanel.setLayout(buttonsPanelLayout); GridBagConstraints gbc = new GridBagConstraints(); String okCaption; if (original.isValid()) { okCaption = strings.getProperty("button.ok.edit"); } else { okCaption = strings.getProperty("button.ok.new"); } btnOK = new JButton(new AbstractAction(okCaption) { private static final long serialVersionUID = 1L; public void actionPerformed(ActionEvent e) { onOK(); } }); btnOK.setEnabled(false); // 初期状態はディセーブル、updateUIStateで更新する. Action actOpenDir = new AbstractAction(strings.getProperty("button.openDir")) { private static final long serialVersionUID = 1L; public void actionPerformed(ActionEvent e) { onOpenDir(); } }; actOpenDir.setEnabled(original.isValid()); JButton btnOpenDir = new JButton(actOpenDir); Action actCancel = new AbstractAction(strings.getProperty("button.cancel")) { private static final long serialVersionUID = 1L; public void actionPerformed(ActionEvent e) { onClose(); } }; JButton btnCancel = new JButton(actCancel); gbc.gridx = 0; gbc.gridy = 0; gbc.weightx = 0.; gbc.anchor = GridBagConstraints.EAST; gbc.fill = GridBagConstraints.BOTH; buttonsPanel.add(btnOpenDir, gbc); gbc.gridx = 1; gbc.gridy = 0; gbc.weightx = 1.; gbc.anchor = GridBagConstraints.EAST; gbc.fill = GridBagConstraints.BOTH; buttonsPanel.add(Box.createGlue(), gbc); gbc.gridx = Main.isLinuxOrMacOSX() ? 3 : 2; gbc.gridy = 0; gbc.weightx = 0.; buttonsPanel.add(btnOK, gbc); gbc.gridx = Main.isLinuxOrMacOSX() ? 2 : 3; gbc.gridy = 0; buttonsPanel.add(btnCancel, gbc); Container contentPane = getContentPane(); contentPane.setLayout(new BorderLayout()); contentPane.add(buttonsPanel, BorderLayout.SOUTH); // InputMap/ActionMap Toolkit tk = Toolkit.getDefaultToolkit(); JRootPane rootPane = getRootPane(); rootPane.setDefaultButton(btnOK); InputMap im = rootPane.getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT); im.put(KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0), "closeWindow"); im.put(KeyStroke.getKeyStroke(KeyEvent.VK_W, tk.getMenuShortcutKeyMask()), "closeWindow"); rootPane.getActionMap().put("closeWindow", actCancel); // Main JPanel mainPanel = new JPanel(); GridBagLayout mainPanelLayout = new GridBagLayout(); mainPanel.setLayout(mainPanelLayout); this.txtCharacterID = new JTextFieldEx(); this.txtCharacterRev = new JTextFieldEx(); this.txtCharacterDocBase = new JTextField(); this.txtCharacterID.setEditable(true); this.txtCharacterRev.setEditable(true); this.txtCharacterDocBase.setEditable(false); this.txtCharacterName = new JTextFieldEx(); this.txtImageWidth = new JSpinner(new SpinnerNumberModel(1, 1, Integer.MAX_VALUE, 1)); // 現実に可能であるかを問わず制限を設けない this.txtImageHeight = new JSpinner(new SpinnerNumberModel(1, 1, Integer.MAX_VALUE, 1)); // 現実に可能であるかを問わず制限を設けない this.txtAuthor = new JTextField(); this.txtDescription = new JTextArea(); gbc.gridx = 0; gbc.gridy = 0; gbc.gridwidth = 1; gbc.gridheight = 1; gbc.weightx = 0; gbc.weighty = 0; gbc.insets = new Insets(1, 3, 1, 5); gbc.anchor = GridBagConstraints.EAST; gbc.fill = GridBagConstraints.BOTH; mainPanel.add(new JLabel(strings.getProperty("docbase.caption"), SwingConstants.RIGHT), gbc); gbc.gridx = 1; gbc.gridy = 0; gbc.gridwidth = 3; gbc.gridheight = 1; gbc.weightx = 1.; gbc.weighty = 0; gbc.anchor = GridBagConstraints.EAST; gbc.fill = GridBagConstraints.BOTH; mainPanel.add(txtCharacterDocBase, gbc); gbc.gridx = 0; gbc.gridy = 1; gbc.gridwidth = 1; gbc.gridheight = 1; gbc.weightx = 0; gbc.weighty = 0; gbc.insets = new Insets(1, 3, 1, 5); gbc.anchor = GridBagConstraints.EAST; gbc.fill = GridBagConstraints.BOTH; mainPanel.add(new JLabel(strings.getProperty("id.caption"), SwingConstants.RIGHT), gbc); txtCharacterID.setToolTipText(strings.getProperty("id.caption.help")); txtCharacterRev.setToolTipText(strings.getProperty("rev.caption.help")); gbc.gridx = 1; gbc.gridy = 1; gbc.gridwidth = 3; gbc.gridheight = 1; gbc.weightx = 1.; gbc.weighty = 0; gbc.anchor = GridBagConstraints.EAST; gbc.fill = GridBagConstraints.BOTH; mainPanel.add(txtCharacterID, gbc); gbc.gridx = 0; gbc.gridy = 2; gbc.gridwidth = 1; gbc.gridheight = 1; gbc.weightx = 0; gbc.weighty = 0; gbc.insets = new Insets(1, 3, 1, 5); gbc.anchor = GridBagConstraints.EAST; gbc.fill = GridBagConstraints.BOTH; mainPanel.add(new JLabel(strings.getProperty("rev.caption"), SwingConstants.RIGHT), gbc); gbc.gridx = 1; gbc.gridy = 2; gbc.gridwidth = 3; gbc.gridheight = 1; gbc.weightx = 1.; gbc.weighty = 0; gbc.anchor = GridBagConstraints.EAST; gbc.fill = GridBagConstraints.BOTH; mainPanel.add(txtCharacterRev, gbc); gbc.gridx = 0; gbc.gridy = 3; gbc.gridwidth = 1; gbc.gridheight = 1; gbc.weightx = 0; gbc.weighty = 0; gbc.anchor = GridBagConstraints.EAST; gbc.fill = GridBagConstraints.BOTH; mainPanel.add(new JLabel(strings.getProperty("name.caption"), SwingConstants.RIGHT), gbc); gbc.gridx = 1; gbc.gridy = 3; gbc.gridwidth = 3; gbc.gridheight = 1; gbc.weightx = 1.; gbc.weighty = 0; gbc.anchor = GridBagConstraints.EAST; gbc.fill = GridBagConstraints.BOTH; mainPanel.add(txtCharacterName, gbc); gbc.gridx = 0; gbc.gridy = 4; gbc.gridwidth = 1; gbc.gridheight = 1; gbc.weightx = 0; gbc.weighty = 0; gbc.anchor = GridBagConstraints.EAST; gbc.fill = GridBagConstraints.BOTH; mainPanel.add(new JLabel(strings.getProperty("image-width.caption"), SwingConstants.RIGHT), gbc); gbc.gridx = 1; gbc.gridy = 4; gbc.gridwidth = 1; gbc.gridheight = 1; gbc.weightx = 0.5; gbc.weighty = 0; gbc.anchor = GridBagConstraints.EAST; gbc.fill = GridBagConstraints.BOTH; mainPanel.add(txtImageWidth, gbc); gbc.gridx = 2; gbc.gridy = 4; gbc.gridwidth = 1; gbc.gridheight = 1; gbc.weightx = 0; gbc.weighty = 0; gbc.anchor = GridBagConstraints.EAST; gbc.fill = GridBagConstraints.BOTH; mainPanel.add(new JLabel(strings.getProperty("image-height.caption"), JLabel.RIGHT), gbc); gbc.gridx = 3; gbc.gridy = 4; gbc.gridwidth = 1; gbc.gridheight = 1; gbc.weightx = 0.5; gbc.weighty = 0; gbc.anchor = GridBagConstraints.EAST; gbc.fill = GridBagConstraints.BOTH; mainPanel.add(txtImageHeight, gbc); gbc.gridx = 0; gbc.gridy = 5; gbc.gridwidth = 1; gbc.gridheight = 1; gbc.weightx = 0; gbc.weighty = 0; gbc.anchor = GridBagConstraints.EAST; gbc.fill = GridBagConstraints.BOTH; mainPanel.add(new JLabel(strings.getProperty("author.caption"), SwingConstants.RIGHT), gbc); gbc.gridx = 1; gbc.gridy = 5; gbc.gridwidth = 3; gbc.gridheight = 1; gbc.weightx = 1.; gbc.weighty = 0; gbc.anchor = GridBagConstraints.EAST; gbc.fill = GridBagConstraints.BOTH; mainPanel.add(txtAuthor, gbc); gbc.gridx = 0; gbc.gridy = 6; gbc.gridwidth = 1; gbc.gridheight = 1; gbc.weightx = 0; gbc.weighty = 0; gbc.anchor = GridBagConstraints.EAST; gbc.fill = GridBagConstraints.BOTH; mainPanel.add(new JLabel(strings.getProperty("description.caption"), SwingConstants.RIGHT), gbc); gbc.gridx = 1; gbc.gridy = 6; gbc.gridwidth = 3; gbc.gridheight = 2; gbc.weightx = 1.; gbc.weighty = 1.; gbc.anchor = GridBagConstraints.EAST; gbc.fill = GridBagConstraints.BOTH; mainPanel.add(new JScrollPane(txtDescription), gbc); // model this.colorGroupsTableModel = new ColorGroupsTableModel(); this.categoriesTableModel = new CategoriesTableModel(); this.layersTableModel = new LayersTableModel(); this.partssetsTableModel = new PartssetsTableModel(); this.recommendationsTableModel = new RecommendationTableModel(); this.colorGroupsTableModel.setEditable(true); this.categoriesTableModel.setEditable(true); this.layersTableModel.setEditable(true); this.partssetsTableModel.setEditable(true); this.recommendationsTableModel.setEditable(true); // colorGroup JPanel colorGroupPanel = new JPanel(new BorderLayout()); final JTable colorGroupTable = new JTable(colorGroupsTableModel); colorGroupTable.setShowGrid(true); colorGroupTable.setGridColor(appConfig.getGridColor()); colorGroupTable.putClientProperty("terminateEditOnFocusLost", Boolean.TRUE); colorGroupTable.setRowHeight(colorGroupTable.getRowHeight() + 4); colorGroupTable.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); colorGroupPanel.add(new JScrollPane(colorGroupTable), BorderLayout.CENTER); JPanel colorGroupBtnPanel = new JPanel(); GridBagLayout colorGroupBtnPanelLayout = new GridBagLayout(); colorGroupBtnPanel.setLayout(colorGroupBtnPanelLayout); AbstractAction actColorGroupAdd = new AbstractAction(strings.getProperty("colorgroup.add.caption")) { private static final long serialVersionUID = 1L; public void actionPerformed(ActionEvent e) { colorGroupsTableModel.addNewColorGroup(); } }; AbstractAction actColorGroupDel = new AbstractAction(strings.getProperty("colorgroup.delete.caption")) { private static final long serialVersionUID = 1L; public void actionPerformed(ActionEvent e) { int selRow = colorGroupTable.getSelectedRow(); if (selRow >= 0) { ColorGroupsTableRow colorGroup = colorGroupsTableModel.getRow(selRow); if (layersTableModel.isUsed(colorGroup)) { JOptionPane.showMessageDialog(ProfileEditDialog.this, strings.getProperty("warning.used-colorgroup")); } else { colorGroupsTableModel.removeRow(selRow); } } } }; AbstractAction actColorGroupMoveUp = new AbstractAction(strings.getProperty("colorgroup.moveup.caption")) { private static final long serialVersionUID = 1L; public void actionPerformed(ActionEvent e) { int rowIndex = colorGroupTable.getSelectedRow(); if (rowIndex >= 0) { int newSel = colorGroupsTableModel.moveUp(rowIndex); colorGroupTable.getSelectionModel().setSelectionInterval(newSel, newSel); } } }; AbstractAction actColorGroupMoveDown = new AbstractAction(strings.getProperty("colorgroup.movedown.caption")) { private static final long serialVersionUID = 1L; public void actionPerformed(ActionEvent e) { int rowIndex = colorGroupTable.getSelectedRow(); if (rowIndex >= 0) { int newSel = colorGroupsTableModel.moveDown(rowIndex); colorGroupTable.getSelectionModel().setSelectionInterval(newSel, newSel); } } }; gbc.gridx = 0; gbc.gridy = 0; gbc.gridwidth = 1; gbc.gridheight = 1; gbc.weightx = 0.; gbc.weighty = 0.; gbc.anchor = GridBagConstraints.EAST; gbc.fill = GridBagConstraints.BOTH; colorGroupBtnPanel.add(new JButton(actColorGroupAdd), gbc); gbc.gridx = 0; gbc.gridy = 1; colorGroupBtnPanel.add(new JButton(actColorGroupDel), gbc); gbc.gridx = 0; gbc.gridy = 2; colorGroupBtnPanel.add(new JButton(actColorGroupMoveUp), gbc); gbc.gridx = 0; gbc.gridy = 3; colorGroupBtnPanel.add(new JButton(actColorGroupMoveDown), gbc); gbc.gridx = 0; gbc.gridy = 4; gbc.weighty = 1.; colorGroupBtnPanel.add(Box.createGlue(), gbc); colorGroupPanel.add(colorGroupBtnPanel, BorderLayout.EAST); final Color disabledForeground = appConfig.getDisabledCellForgroundColor(); // categories JPanel categoriesPanel = new JPanel(new BorderLayout()); final JTable categoriesTable = new JTable(categoriesTableModel) { private static final long serialVersionUID = 1L; @Override public Component prepareRenderer(TableCellRenderer renderer, int row, int column) { Component comp = super.prepareRenderer(renderer, row, column); if (comp instanceof JCheckBox) { // BooleanのデフォルトのレンダラーはJCheckBoxを継承したJTable$BooleanRenderer comp.setEnabled(isCellEditable(row, column) && isEnabled()); } if (isCellSelected(row, column)) { comp.setForeground(getSelectionForeground()); comp.setBackground(getSelectionBackground()); } else { // 前景色、ディセーブル時は灰色 Color foregroundColor = getForeground(); comp.setForeground(isEnabled() ? foregroundColor : disabledForeground); comp.setBackground(getBackground()); } return comp; } }; categoriesTable.setShowGrid(true); categoriesTable.setGridColor(appConfig.getGridColor()); categoriesTable.putClientProperty("terminateEditOnFocusLost", Boolean.TRUE); categoriesTable.setRowHeight(categoriesTable.getRowHeight() + 4); categoriesTable.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); categoriesTable.setAutoResizeMode(JTable.AUTO_RESIZE_OFF); categoriesPanel.add(new JScrollPane(categoriesTable), BorderLayout.CENTER); categoriesTableModel.adjustColumnModel(categoriesTable.getColumnModel()); JPanel categoriesBtnPanel = new JPanel(); GridBagLayout categoryBtnPanelLayout = new GridBagLayout(); categoriesBtnPanel.setLayout(categoryBtnPanelLayout); AbstractAction actCategoryAdd = new AbstractAction(strings.getProperty("categories.add.caption")) { private static final long serialVersionUID = 1L; public void actionPerformed(ActionEvent e) { categoriesTableModel.addCategory(); } }; AbstractAction actCategoryDel = new AbstractAction(strings.getProperty("categories.delete.caption")) { private static final long serialVersionUID = 1L; public void actionPerformed(ActionEvent e) { int selRow = categoriesTable.getSelectedRow(); if (selRow >= 0) { CategoriesTableRow partsCategory = categoriesTableModel.getRow(selRow); if (layersTableModel.isUsed(partsCategory)) { JOptionPane.showMessageDialog(ProfileEditDialog.this, strings.getProperty("warning.used-category")); } else { categoriesTableModel.removeRow(selRow); } } } }; AbstractAction actCategoryMoveUp = new AbstractAction(strings.getProperty("categories.moveup.caption")) { private static final long serialVersionUID = 1L; public void actionPerformed(ActionEvent e) { int rowIndex = categoriesTable.getSelectedRow(); if (rowIndex >= 0) { int newSel = categoriesTableModel.moveUp(rowIndex); categoriesTable.getSelectionModel().setSelectionInterval(newSel, newSel); } } }; AbstractAction actCategoryMoveDown = new AbstractAction(strings.getProperty("categories.movedown.caption")) { private static final long serialVersionUID = 1L; public void actionPerformed(ActionEvent e) { int rowIndex = categoriesTable.getSelectedRow(); if (rowIndex >= 0) { int newSel = categoriesTableModel.moveDown(rowIndex); categoriesTable.getSelectionModel().setSelectionInterval(newSel, newSel); } } }; gbc.gridx = 0; gbc.gridy = 0; gbc.gridwidth = 1; gbc.gridheight = 1; gbc.weightx = 0.; gbc.weighty = 0.; gbc.anchor = GridBagConstraints.EAST; gbc.fill = GridBagConstraints.BOTH; categoriesBtnPanel.add(new JButton(actCategoryAdd), gbc); gbc.gridx = 0; gbc.gridy = 1; categoriesBtnPanel.add(new JButton(actCategoryDel), gbc); gbc.gridx = 0; gbc.gridy = 2; categoriesBtnPanel.add(new JButton(actCategoryMoveUp), gbc); gbc.gridx = 0; gbc.gridy = 3; categoriesBtnPanel.add(new JButton(actCategoryMoveDown), gbc); gbc.gridx = 0; gbc.gridy = 4; gbc.weighty = 1.; categoriesBtnPanel.add(Box.createGlue(), gbc); categoriesPanel.add(categoriesBtnPanel, BorderLayout.EAST); // layers JPanel layersPanel = new JPanel(new BorderLayout()); final Color invalidBgColor = appConfig.getInvalidBgColor(); final JTable layersTable = new JTable(layersTableModel) { private static final long serialVersionUID = 1L; @Override public Component prepareRenderer(TableCellRenderer renderer, int row, int column) { Component comp = super.prepareRenderer(renderer, row, column); if (comp instanceof JCheckBox) { // BooleanのデフォルトのレンダラーはJCheckBoxを継承したJTable$BooleanRenderer comp.setEnabled(isCellEditable(row, column) && isEnabled()); } LayersTableModel model = (LayersTableModel) getModel(); LayersTableRow layer = model.getRow(row); comp.setForeground(getForeground()); comp.setBackground(layer.isValid() ? getBackground() : invalidBgColor); // 前景色、ディセーブル時は灰色 Color foregroundColor = getForeground(); comp.setForeground(isEnabled() ? foregroundColor : disabledForeground); return comp; } }; layersTableModel.adjustColumnModel(layersTable.getColumnModel()); JComboBox colorGroupCombo = new JComboBox( new FirstItemInjectionComboBoxModelWrapper(colorGroupsTableModel, ColorGroupsTableRow.NA)); JComboBox categoriesCombo = new JComboBox(categoriesTableModel); JComboBox colorModelsCombo = new JComboBox(ColorModels.values()); layersTable.setShowGrid(true); layersTable.setGridColor(appConfig.getGridColor()); layersTable.putClientProperty("terminateEditOnFocusLost", Boolean.TRUE); layersTable.setRowHeight(layersTable.getRowHeight() + 4); layersTable.setDefaultEditor(ColorGroupsTableRow.class, new DefaultCellEditor(colorGroupCombo)); layersTable.setDefaultEditor(CategoriesTableRow.class, new DefaultCellEditor(categoriesCombo)); layersTable.setDefaultEditor(ColorModels.class, new DefaultCellEditor(colorModelsCombo)); layersTable.setAutoResizeMode(JTable.AUTO_RESIZE_OFF); layersTable.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); layersPanel.add(new JScrollPane(layersTable), BorderLayout.CENTER); JPanel layersBtnPanel = new JPanel(); GridBagLayout layersBtnPanelLayout = new GridBagLayout(); layersBtnPanel.setLayout(layersBtnPanelLayout); AbstractAction actLayerAdd = new AbstractAction(strings.getProperty("layers.add.caption")) { private static final long serialVersionUID = 1L; public void actionPerformed(ActionEvent e) { layersTableModel.addNewLayer(); } }; AbstractAction actLayerDel = new AbstractAction(strings.getProperty("layers.delete.caption")) { private static final long serialVersionUID = 1L; public void actionPerformed(ActionEvent e) { int selRow = layersTable.getSelectedRow(); if (selRow >= 0) { layersTableModel.removeRow(selRow); } } }; AbstractAction actLayerSort = new AbstractAction(strings.getProperty("layers.sort.caption")) { private static final long serialVersionUID = 1L; public void actionPerformed(ActionEvent e) { layersTableModel.sort(); } }; AbstractAction actLayerMoveUp = new AbstractAction(strings.getProperty("layers.moveup.caption")) { private static final long serialVersionUID = 1L; public void actionPerformed(ActionEvent e) { int rowIndex = layersTable.getSelectedRow(); if (rowIndex >= 0) { int newSel = layersTableModel.moveUp(rowIndex); layersTable.getSelectionModel().setSelectionInterval(newSel, newSel); } } }; AbstractAction actLayerMoveDown = new AbstractAction(strings.getProperty("layers.movedown.caption")) { private static final long serialVersionUID = 1L; public void actionPerformed(ActionEvent e) { int rowIndex = layersTable.getSelectedRow(); if (rowIndex >= 0) { int newSel = layersTableModel.moveDown(rowIndex); layersTable.getSelectionModel().setSelectionInterval(newSel, newSel); } } }; gbc.gridx = 0; gbc.gridy = 0; gbc.gridwidth = 1; gbc.gridheight = 1; gbc.weightx = 0.; gbc.weighty = 0.; gbc.anchor = GridBagConstraints.EAST; gbc.fill = GridBagConstraints.BOTH; layersBtnPanel.add(new JButton(actLayerAdd), gbc); gbc.gridx = 0; gbc.gridy = 1; layersBtnPanel.add(new JButton(actLayerDel), gbc); gbc.gridx = 0; gbc.gridy = 2; layersBtnPanel.add(new JButton(actLayerMoveUp), gbc); gbc.gridx = 0; gbc.gridy = 3; layersBtnPanel.add(new JButton(actLayerMoveDown), gbc); gbc.gridx = 0; gbc.gridy = 4; layersBtnPanel.add(new JButton(actLayerSort), gbc); gbc.gridx = 0; gbc.gridy = 5; gbc.weighty = 1.; layersBtnPanel.add(Box.createGlue(), gbc); layersPanel.add(layersBtnPanel, BorderLayout.EAST); chkWatchDir = new JCheckBox(strings.getProperty("layers.watchdir")); layersPanel.add(chkWatchDir, BorderLayout.SOUTH); // Presets JPanel partssetsPanel = new JPanel(new BorderLayout()); JTable partssetsTable = new JTable(partssetsTableModel) { private static final long serialVersionUID = 1L; @Override public Component prepareRenderer(TableCellRenderer renderer, int row, int column) { Component comp = super.prepareRenderer(renderer, row, column); if (comp instanceof JCheckBox) { // BooleanのデフォルトのレンダラーはJCheckBoxを継承したJTable$BooleanRenderer comp.setEnabled(isCellEditable(row, column) && isEnabled()); } if (isCellSelected(row, column)) { comp.setForeground(getSelectionForeground()); comp.setBackground(getSelectionBackground()); } else { // 前景色、ディセーブル時は灰色 Color foregroundColor = getForeground(); comp.setForeground(isEnabled() ? foregroundColor : disabledForeground); comp.setBackground(getBackground()); } return comp; } }; partssetsTableModel.adjustColumnModel(partssetsTable.getColumnModel()); partssetsTable.setRowHeight(layersTable.getRowHeight() + 4); partssetsTable.setShowGrid(true); partssetsTable.setGridColor(appConfig.getGridColor()); partssetsTable.putClientProperty("terminateEditOnFocusLost", Boolean.TRUE); partssetsTable.setAutoResizeMode(JTable.AUTO_RESIZE_OFF); partssetsTable.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); partssetsPanel.add(new JScrollPane(partssetsTable), BorderLayout.CENTER); // Recommendations JPanel recommendationsPanel = new JPanel(new BorderLayout()); final JTable recommendationsTable = new JTable(recommendationsTableModel) { private static final long serialVersionUID = 1L; @Override public Component prepareRenderer(TableCellRenderer renderer, int row, int column) { Component comp = super.prepareRenderer(renderer, row, column); if (isCellSelected(row, column)) { comp.setForeground(getSelectionForeground()); comp.setBackground(getSelectionBackground()); } else { // 前景色、ディセーブル時は灰色 Color foregroundColor = getForeground(); comp.setForeground(isEnabled() ? foregroundColor : disabledForeground); comp.setBackground(getBackground()); } return comp; } }; recommendationsTableModel.adjustColumnModel(recommendationsTable.getColumnModel()); recommendationsTable.setRowHeight(layersTable.getRowHeight() + 4); recommendationsTable.setShowGrid(true); recommendationsTable.setGridColor(appConfig.getGridColor()); recommendationsTable.putClientProperty("terminateEditOnFocusLost", Boolean.TRUE); recommendationsTable.setAutoResizeMode(JTable.AUTO_RESIZE_OFF); recommendationsTable.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); recommendationsPanel.add(new JScrollPane(recommendationsTable), BorderLayout.CENTER); JPanel recommendationsBtnPanel = new JPanel(); GridBagLayout recommendationsBtnPanelLayout = new GridBagLayout(); recommendationsBtnPanel.setLayout(recommendationsBtnPanelLayout); AbstractAction actRecommendationAdd = new AbstractAction(strings.getProperty("recommendations.add.caption")) { private static final long serialVersionUID = 1L; public void actionPerformed(ActionEvent e) { recommendationsTableModel.addNew(); } }; AbstractAction actRecommendationDel = new AbstractAction(strings.getProperty("recommendations.delete.caption")) { private static final long serialVersionUID = 1L; public void actionPerformed(ActionEvent e) { int selRow = recommendationsTable.getSelectedRow(); if (selRow >= 0) { recommendationsTableModel.removeRow(selRow); } } }; AbstractAction actRecommendationMoveUp = new AbstractAction(strings.getProperty("recommendations.moveup.caption")) { private static final long serialVersionUID = 1L; public void actionPerformed(ActionEvent e) { int rowIndex = recommendationsTable.getSelectedRow(); if (rowIndex >= 0) { int newSel = recommendationsTableModel.moveUp(rowIndex); recommendationsTable.getSelectionModel().setSelectionInterval(newSel, newSel); } } }; AbstractAction actRecommendationMoveDown = new AbstractAction(strings.getProperty("recommendations.movedown.caption")) { private static final long serialVersionUID = 1L; public void actionPerformed(ActionEvent e) { int rowIndex = recommendationsTable.getSelectedRow(); if (rowIndex >= 0) { int newSel = recommendationsTableModel.moveDown(rowIndex); recommendationsTable.getSelectionModel().setSelectionInterval(newSel, newSel); } } }; gbc.gridx = 0; gbc.gridy = 0; gbc.gridwidth = 1; gbc.gridheight = 1; gbc.weightx = 0.; gbc.weighty = 0.; gbc.anchor = GridBagConstraints.EAST; gbc.fill = GridBagConstraints.BOTH; recommendationsBtnPanel.add(new JButton(actRecommendationAdd), gbc); gbc.gridx = 0; gbc.gridy = 1; recommendationsBtnPanel.add(new JButton(actRecommendationDel), gbc); gbc.gridx = 0; gbc.gridy = 2; recommendationsBtnPanel.add(new JButton(actRecommendationMoveUp), gbc); gbc.gridx = 0; gbc.gridy = 3; recommendationsBtnPanel.add(new JButton(actRecommendationMoveDown), gbc); gbc.gridx = 0; gbc.gridy = 4; gbc.weighty = 1.; recommendationsBtnPanel.add(Box.createGlue(), gbc); recommendationsPanel.add(recommendationsBtnPanel, BorderLayout.EAST); // データのロード loadCharacterData(original); // レイヤーのカテゴリ使用状態を監視するリスナ関連 final HashMap> usedLayerMap = new HashMap>(); final Runnable resetUsedLayers = new Runnable() { public void run() { usedLayerMap.clear(); } }; layersTableModel.addListDataListener(new ListDataListener() { public void contentsChanged(ListDataEvent e) { resetUsedLayers.run(); } public void intervalAdded(ListDataEvent e) { resetUsedLayers.run(); } public void intervalRemoved(ListDataEvent e) { resetUsedLayers.run(); } }); categoriesTableModel.setUsedCategoryDetector(new CategoriesTableModel.UsedCategoryDetector() { public List getLayers(CategoriesTableRow partsCategory) { if (usedLayerMap.isEmpty()) { int mx = layersTableModel.getRowCount(); for (int idx = 0; idx < mx; idx++) { LayersTableRow layer = layersTableModel.getRow(idx); CategoriesTableRow pc = layer.getPartsCategory(); List usedLayers = usedLayerMap.get(pc); if (usedLayers == null) { usedLayers = new ArrayList(); usedLayerMap.put(pc, usedLayers); } usedLayers.add(layer); } } return usedLayerMap.get(partsCategory); } }); // 生成可能であるかチェックするためのリスナ layersTableModel.addListDataListener(new ListDataListener() { public void contentsChanged(ListDataEvent e) { updateUIState(); layersTable.repaint(); // エラー有無表示を最新の状態で再判定・再描画するため } public void intervalAdded(ListDataEvent e) { updateUIState(); } public void intervalRemoved(ListDataEvent e) { updateUIState(); } }); // キャラクターID/REV/NAMEが変更されたことを通知され、OKボタンを判定するためのリスナ DocumentListener textChangeListener = new DocumentListener() { public void removeUpdate(DocumentEvent e) { updateUIState(); } public void insertUpdate(DocumentEvent e) { updateUIState(); } public void changedUpdate(DocumentEvent e) { updateUIState(); } }; txtCharacterID.getDocument().addDocumentListener(textChangeListener); txtCharacterRev.getDocument().addDocumentListener(textChangeListener); txtCharacterName.getDocument().addDocumentListener(textChangeListener); // TABS JTabbedPane tabbedPane = new JTabbedPane(); tabbedPane.add(strings.getProperty("panel.basicinfomation"), mainPanel); tabbedPane.add(strings.getProperty("panel.colorgroup"), colorGroupPanel); tabbedPane.add(strings.getProperty("panel.categories"), categoriesPanel); tabbedPane.add(strings.getProperty("panel.layers"), layersPanel); tabbedPane.add(strings.getProperty("panel.partssets"), partssetsPanel); tabbedPane.add(strings.getProperty("panel.recommendations"), recommendationsPanel); contentPane.add(tabbedPane, BorderLayout.CENTER); setSize(500, 500); setLocationRelativeTo(parent); updateUIState(); } /** * CharacterDataから画面へ転記する. * * @param original * オリジナル情報 */ protected void loadCharacterData(CharacterData original) { if (original == null) { throw new IllegalArgumentException(); } colorGroupsTableModel.clear(); categoriesTableModel.clear(); layersTableModel.clear(); partssetsTableModel.clear(); recommendationsTableModel.clear(); // 基本情報 txtCharacterID.setText(original.getId()); txtCharacterRev.setText(original.getRev()); txtCharacterDocBase.setText(original.getDocBase() == null ? "" : original.getDocBase().toString()); txtCharacterName.setText(original.getName()); txtAuthor.setText(original.getAuthor() != null ? original.getAuthor() : ""); txtDescription.setText(original.getDescription() != null ? original.getDescription() : ""); Dimension siz = original.getImageSize(); txtImageWidth.setValue(siz != null ? siz.width : 300); txtImageHeight.setValue(siz != null ? siz.height : 400); // カラーグループ HashMap colorGroupMap = new HashMap(); for (ColorGroup colorGroup : original.getColorGroups()) { if (colorGroup.isEnabled()) { ColorGroupsTableRow mutableColorGroup = ColorGroupsTableRow.valueOf(colorGroup); colorGroupsTableModel.addRow(mutableColorGroup); colorGroupMap.put(colorGroup, mutableColorGroup); } } // カテゴリとレイヤー for (PartsCategory partsCategory : original.getPartsCategories()) { categoriesTableModel.addRow(new CategoriesTableRow(partsCategory)); for (Layer layer : partsCategory.getLayers()) { LayersTableRow editableLayer = new LayersTableRow(); ColorGroupsTableRow mutableColorGroup = colorGroupMap.get(layer.getColorGroup()); if (mutableColorGroup == null) { mutableColorGroup = ColorGroupsTableRow.NA; } editableLayer.setColorGroup(mutableColorGroup); editableLayer.setPartsCategory(new CategoriesTableRow(partsCategory)); editableLayer.setDir(layer.getDir()); editableLayer.setColorModel(ColorModels.safeValueOf(layer.getColorModelName())); editableLayer.setOrder(layer.getOrder()); editableLayer.setLayerId(layer.getId()); editableLayer.setLayerName(layer.getLocalizedName()); layersTableModel.addRow(editableLayer); } } // ディレクトリ監視有無 chkWatchDir.setSelected(original.isWatchDirectory()); // パーツセット ArrayList partsSets = new ArrayList(); partsSets.addAll(original.getPartsSets().values()); Collections.sort(partsSets, PartsSet.DEFAULT_COMPARATOR); for (PartsSet partsSet : partsSets) { partssetsTableModel.addRow(new PresetsTableRow(partsSet)); } partssetsTableModel.setDefaultPartsSetId(original.getDefaultPartsSetId()); // お勧めリンク List recommendationURLList = original.getRecommendationURLList(); if (recommendationURLList == null) { // キャラクターデータのお勧めリンクがnull(古い形式)の場合は、デフォルトのお勧めリンクで代替する. CharacterDataDefaultProvider defProv = new CharacterDataDefaultProvider(); CharacterData defaultCd = defProv .createDefaultCharacterData(DefaultCharacterDataVersion.V3); recommendationURLList = defaultCd.getRecommendationURLList(); } if (recommendationURLList != null) { for (RecommendationURL recommendationURL : recommendationURLList) { recommendationsTableModel.addRow(new RecommendationTableRow(recommendationURL)); } } } protected void onOpenDir() { try { URI docBase = original.getDocBase(); if (!DesktopUtilities.browseBaseDir(docBase)) { JOptionPane.showMessageDialog(this, docBase); } } catch (Exception ex) { ErrorMessageHelper.showErrorDialog(this, ex); } } /** * 画面を閉じる場合 */ protected void onClose() { result = null; boolean writable = !original.isValid() || original.canWrite(); // 新規または更新可能 if (writable) { final Properties strings = LocalizedResourcePropertyLoader .getCachedInstance().getLocalizedProperties(STRINGS_RESOURCE); if (JOptionPane.showConfirmDialog(this, strings.get("confirm.close"), strings.getProperty("confirm"), JOptionPane.YES_NO_OPTION, JOptionPane.WARNING_MESSAGE) != JOptionPane.YES_OPTION) { // YESでなければ継続しない. return; } } dispose(); } /** * 画面の状態を更新する */ protected void updateUIState() { boolean valid = isValidData(new ValidationReport() { public void validateReport(JComponent comp, boolean valid) { if (comp != null && comp instanceof JTextFieldEx) { ((JTextFieldEx) comp).setError(!valid); } } }); boolean writable = !original.isValid() || original.canWrite(); // 新規または更新可能 btnOK.setEnabled(valid && writable); } private interface ValidationReport { void validateReport(JComponent comp, boolean valid); } /** * 入力データが有効であるか判定する. * * @return 有効であればtrue */ protected boolean isValidData(ValidationReport report) { // ID, REVが英数字であるか判定 Pattern pat = Pattern.compile("\\p{Graph}+"); String id = txtCharacterID.getText().trim(); String rev = txtCharacterRev.getText().trim(); boolean validId = pat.matcher(id).matches(); boolean validRev = pat.matcher(rev).matches(); boolean validName = txtCharacterName.getText().trim().length() > 0; // レイヤーの不備判定 boolean validLayers = true; int cnt = 0; int mx = layersTableModel.getRowCount(); for (int idx = 0; idx < mx; idx++) { LayersTableRow layer = layersTableModel.getRow(idx); if (!layer.isValid()) { // レイヤーに不備がある validLayers = false; break; } cnt++; } if (cnt == 0) { // レイヤーがない validLayers = false; } if (report != null) { report.validateReport(txtCharacterID, validId); report.validateReport(txtCharacterRev, validRev); report.validateReport(txtCharacterName, validName); } return validLayers && validId && validRev && validName; } /** * OKボタン押下 */ protected void onOK() { if ( !isValidData(null)) { // 編集可能でないか、まだ保存可能になっていない場合はビープ音を鳴らして警告 Toolkit tk = Toolkit.getDefaultToolkit(); tk.beep(); return; } // 編集可能であり、且つ、保存可能な状態であれば CharacterData newCd = createCharacterData(); final Properties strings = LocalizedResourcePropertyLoader .getCachedInstance().getLocalizedProperties(STRINGS_RESOURCE); if (original.isValid() && !original.isSameStructure(newCd)) { if (original.getRev().equals(newCd.getRev())) { // 構造が変更されているがREVが変らない場合 int ret = JOptionPane.showConfirmDialog(this, strings.get("confirm.needchangerevision"), strings.getProperty("confirm"), JOptionPane.YES_NO_CANCEL_OPTION, JOptionPane.WARNING_MESSAGE); if (ret == JOptionPane.CANCEL_OPTION) { return; } if (ret == JOptionPane.YES_OPTION) { // リビジョンを生成して割り当てる CharacterDataPersistent persist = CharacterDataPersistent.getInstance(); newCd.setRev(persist.generateRev()); } } else if ( !newCd.isUpperCompatibleStructure(original)){ // 上位互換のない構造が変更されていることを通知する. if (JOptionPane.showConfirmDialog(this, strings.get("confirm.changestructre"), strings.getProperty("confirm"), JOptionPane.YES_NO_OPTION, JOptionPane.WARNING_MESSAGE) != JOptionPane.YES_OPTION) { return; } } } result = newCd; dispose(); return; } /** * 画面の情報からキャラクターデータを構築して返します.
* * @return キャラクターデータ */ protected CharacterData createCharacterData() { CharacterData cd = new CharacterData(); // オリジナルのDocBaseVを転記する. cd.setDocBase(original.getDocBase()); // ID, REV cd.setId(txtCharacterID.getText().trim()); cd.setRev(txtCharacterRev.getText().trim()); // キャラクターセット名 cd.setName(txtCharacterName.getText().trim()); // 情報 cd.setAuthor(txtAuthor.getText().trim()); cd.setDescription(txtDescription.getText()); // サイズ Dimension imageSize = new Dimension(); imageSize.width = ((Number)(txtImageWidth.getValue())).intValue(); imageSize.height = ((Number)(txtImageHeight.getValue())).intValue(); cd.setImageSize(imageSize); // カラーグループ int mxColorGroup = colorGroupsTableModel.getRowCount(); ArrayList colorGroups = new ArrayList(); for (int idx = 0; idx < mxColorGroup; idx++) { colorGroups.add(colorGroupsTableModel.getRow(idx).convert()); } cd.setColorGroups(colorGroups); // レイヤーの構築 HashMap> layerMap = new HashMap>(); int mxLayer = layersTableModel.getRowCount(); for (int idx = 0; idx < mxLayer; idx++) { LayersTableRow editableLayer = layersTableModel.getRow(idx); Layer layer = editableLayer.toLayer(); CategoriesTableRow partsCategory = editableLayer.getPartsCategory(); if (layer != null && partsCategory != null) { List layers = layerMap.get(partsCategory); if (layers == null) { layers = new ArrayList(); layerMap.put(partsCategory, layers); } layers.add(layer); } } // カテゴリおよびレイヤー ArrayList categories = new ArrayList(); int mxCategory = categoriesTableModel.getRowCount(); for (int idx = 0; idx < mxCategory; idx++) { CategoriesTableRow partsCategory = categoriesTableModel.getRow(idx); List layers = layerMap.get(partsCategory); if (layers != null) { partsCategory.setLayers(layers); categories.add(partsCategory.convert()); } } cd.setPartsCategories(categories.toArray(new PartsCategory[categories.size()])); // ディレクトリの監視 cd.setWatchDirectory(chkWatchDir.isSelected()); // パーツセット情報 int mxPartssets = partssetsTableModel.getRowCount(); for (int idx = 0; idx < mxPartssets; idx++) { PartsSet partsSet = partssetsTableModel.getRow(idx).convert(); cd.addPartsSet(partsSet); } cd.setDefaultPartsSetId(partssetsTableModel.getDefaultPartsSetId()); // お気に入りリンク情報 int mxRecommendations = recommendationsTableModel.getRowCount(); ArrayList recommendationURLList = new ArrayList(); for (int idx = 0; idx < mxRecommendations; idx++) { RecommendationTableRow row = recommendationsTableModel.getRow(idx); String displayName = row.getLocalizedName(); String url = row.getURL(); if ((displayName != null && displayName.trim().length() > 0) && (url != null && url.trim().length() > 0)) { RecommendationURL recommendationURL = new RecommendationURL(); recommendationURL.setDisplayName(displayName.trim()); recommendationURL.setUrl(url.trim()); recommendationURLList.add(recommendationURL); } } CharacterDataDefaultProvider defProv = new CharacterDataDefaultProvider(); CharacterData defaultCd = defProv .createDefaultCharacterData(DefaultCharacterDataVersion.V3); List defaultRecommendationURLList = defaultCd.getRecommendationURLList(); if (defaultRecommendationURLList != null && defaultRecommendationURLList.equals(recommendationURLList)) { // デフォルトのお勧めリストと内容が同じの場合は、明示的にリストを設定しない. recommendationURLList = null; } cd.setRecommendationURLList(recommendationURLList); return cd; } /** * 結果を取得する. キャンセルされた場合はnullが返される.
* * @return キャラクターデータ、またはnull */ public CharacterData getResult() { return result; } } /** * 編集用カラーグループ * * @author seraphy */ class ColorGroupsTableRow { private final String id; private final boolean enabled; private String localizedName; public static final ColorGroupsTableRow NA = new ColorGroupsTableRow("n/a", "", false); public ColorGroupsTableRow(final String id, final String localizedName) { this(id, localizedName, true); } public static ColorGroupsTableRow valueOf(ColorGroup colorGroup) { if (colorGroup == null || !colorGroup.isEnabled()) { return NA; } return new ColorGroupsTableRow(colorGroup.getId(), colorGroup.getLocalizedName(), true); } public ColorGroup convert() { if (!isEnabled()) { return ColorGroup.NA; } return new ColorGroup(getId(), getLocalizedName()); } private ColorGroupsTableRow(final String id, final String localizedName, final boolean enabled) { if (id == null || id.trim().length() == 0) { throw new IllegalArgumentException(); } this.id = id.trim(); this.localizedName = (localizedName == null || localizedName.trim().length() == 0) ? id : localizedName; this.enabled = enabled; } public void setLocalizedName(String localizedName) { if (localizedName == null || localizedName.trim().length() == 0) { throw new IllegalArgumentException(); } if (!enabled) { throw new UnsupportedOperationException("unmodified object."); } this.localizedName = localizedName; } public boolean isEnabled() { return enabled; } public String getId() { return id; } public String getLocalizedName() { return localizedName; } @Override public int hashCode() { return id.hashCode(); } @Override public boolean equals(Object obj) { if (obj == this) { return true; } if (obj != null && obj instanceof ColorGroupsTableRow) { ColorGroupsTableRow o = (ColorGroupsTableRow) obj; return id.equals(o.getId()); } return false; } public static boolean equals(ColorGroupsTableRow v1, ColorGroupsTableRow v2) { if (v1 == v2) { return true; } if (v1 == null || v2 == null) { return false; } return v1.equals(v2); } @Override public String toString() { return getLocalizedName(); } } /** * カラーグループのテーブル編集モデル * * @author seraphy */ class ColorGroupsTableModel extends AbstractTableModelWithComboBoxModel { private static final long serialVersionUID = 2952439955567262351L; private static final String[] colorGroupColumnNames; private static final Logger logger = Logger.getLogger(ColorGroupsTableModel.class.getName()); static { final Properties strings = LocalizedResourcePropertyLoader .getCachedInstance().getLocalizedProperties(ProfileEditDialog.STRINGS_RESOURCE); colorGroupColumnNames = new String[] { strings.getProperty("colorgroup.column.colorgroupname"), }; } private int serialCounter = 1; public int getColumnCount() { return colorGroupColumnNames.length; } @Override public String getColumnName(int column) { return colorGroupColumnNames[column]; } public Object getValueAt(int rowIndex, int columnIndex) { ColorGroupsTableRow colorGroup = elements.get(rowIndex); switch (columnIndex) { case 0: return colorGroup.getLocalizedName(); } return "****"; } @Override public void setValueAt(Object aValue, int rowIndex, int columnIndex) { ColorGroupsTableRow colorGroup = elements.get(rowIndex); try { switch (columnIndex) { case 0: String localizedName = (String) aValue; if (localizedName != null && localizedName.trim().length() > 0) { colorGroup.setLocalizedName(localizedName.trim()); } break; default: return; } fireTableCellUpdated(rowIndex, columnIndex); } catch (Exception ex) { logger.log(Level.FINE, "value set failed. (" + rowIndex + ", " + columnIndex + "): " + aValue, ex); // 無視する } } @Override public Class getColumnClass(int columnIndex) { return String.class; } @Override public boolean isCellEditable(int rowIndex, int columnIndex) { return isEditable(); } public void addNewColorGroup() { String id = "cg" + UUID.randomUUID().toString(); String localizedName = "ColorGroup" + (serialCounter++); ColorGroupsTableRow colorGroup = new ColorGroupsTableRow(id, localizedName); addRow(colorGroup); } } /** * カラーグループ用のコンボボックスモデルに対して最初のアイテムとしてN/Aを常に追加するモデルに変換するラップクラス.
* * @author seraphy */ class FirstItemInjectionComboBoxModelWrapper implements ComboBoxModel { private ComboBoxModel parent; private T selectedItem; private T firstItem; private LinkedList listDataListeners = new LinkedList(); public FirstItemInjectionComboBoxModelWrapper(ComboBoxModel parent, T firstItem) { if (parent == null || firstItem == null) { throw new IllegalArgumentException(); } this.parent = parent; this.firstItem = firstItem; parent.addListDataListener(new ListDataListener() { public void contentsChanged(ListDataEvent e) { fireListUpdated(convertRowIndex(e)); } public void intervalAdded(ListDataEvent e) { fireListAdded(convertRowIndex(e)); } public void intervalRemoved(ListDataEvent e) { fireListRemoved(convertRowIndex(e)); } /** * 親コンボボックスモデルのインデックスを+1したイベントに変換する. * * @param e * 元イベント * @return インデックス変換後のイベント */ protected ListDataEvent convertRowIndex(ListDataEvent e) { return new ListDataEvent(e.getSource(), e.getType(), e .getIndex0() + 1, e.getIndex1() + 1); } }); } protected void fireListUpdated(ListDataEvent e) { for (ListDataListener listener : listDataListeners) { listener.contentsChanged(e); } } protected void fireListAdded(ListDataEvent e) { for (ListDataListener listener : listDataListeners) { listener.intervalAdded(e); } } protected void fireListRemoved(ListDataEvent e) { for (ListDataListener listener : listDataListeners) { listener.intervalRemoved(e); } } public Object getSelectedItem() { return selectedItem; } @SuppressWarnings("unchecked") public void setSelectedItem(Object anItem) { selectedItem = (T) anItem; if (!firstItem.equals(anItem)) { parent.setSelectedItem(anItem); } } public void addListDataListener(ListDataListener l) { if (l != null) { listDataListeners.add(l); } } public void removeListDataListener(ListDataListener l) { if (l != null) { listDataListeners.remove(l); } } public Object getElementAt(int index) { if (index == 0) { return firstItem; } return parent.getElementAt(index - 1); } public int getSize() { return parent.getSize() + 1; } } class CategoriesTableRow implements Comparable { /** * 順序 */ private int order; /** * カテゴリ識別名 */ private String categoryId; /** * カテゴリ表示名 */ private String localizedCategoryName; /** * 複数選択可能? */ private boolean multipleSelectable; /** * 表示行数 */ private int visibleRows; /** * レイヤー情報 */ private ArrayList layers = new ArrayList(); /** * カテゴリを構築する.
* * @param categoryId * カテゴリ識別名 * @param localizedCategoryName * カテゴリ表示名 * @param multipleSelectable * 複数選択可能? * @param layers * レイヤー情報の配列 */ public CategoriesTableRow(final int order, final String categoryId, String localizedCategoryName, boolean multipleSelectable, int visibleRows, Layer[] layers) { if (categoryId == null || categoryId.trim().length() == 0) { throw new IllegalArgumentException(); } if (layers == null) { layers = new Layer[0]; } if (localizedCategoryName == null || localizedCategoryName.trim().length() == 0) { localizedCategoryName = categoryId; } this.order = order; this.categoryId = categoryId.trim(); this.localizedCategoryName = localizedCategoryName.trim(); this.multipleSelectable = multipleSelectable; this.layers.addAll(Arrays.asList(layers)); this.visibleRows = visibleRows; } public CategoriesTableRow(PartsCategory partsCategory) { if (partsCategory == null) { throw new IllegalArgumentException(); } this.order = partsCategory.getOrder(); this.categoryId = partsCategory.getCategoryId(); this.localizedCategoryName = partsCategory.getLocalizedCategoryName(); this.multipleSelectable = partsCategory.isMultipleSelectable(); this.layers.addAll(partsCategory.getLayers()); this.visibleRows = partsCategory.getVisibleRows(); } public PartsCategory convert() { return new PartsCategory(order, categoryId, localizedCategoryName, multipleSelectable, visibleRows, layers .toArray(new Layer[layers.size()])); } public int compareTo(CategoriesTableRow o) { if (o == this) { return 0; } int ret = order - o.order; if (ret == 0) { ret = localizedCategoryName.compareTo(o.localizedCategoryName); } if (ret == 0) { ret = categoryId.compareTo(o.categoryId); } return ret; } @Override public boolean equals(Object obj) { if (obj == this) { return true; } if (obj != null && obj instanceof CategoriesTableRow) { return categoryId.equals(((CategoriesTableRow) obj).getCategoryId()); } return false; } public static boolean equals(CategoriesTableRow o1, CategoriesTableRow o2) { if (o1 == o2) { return true; } if (o1 == null || o2 == null) { return false; } return o1.equals(o2); } /** * 定義順を取得する * * @return 定義順 */ public int getOrder() { return order; } /** * 定義順を設定する * * @param order * 定義順 */ public void setOrder(int order) { this.order = order; } /** * 複数選択可能であるか? * * @return 複数選択可能であるか? */ public boolean isMultipleSelectable() { return multipleSelectable; } /** * 複数選択可能であるか設定する * * @param multipleSelectable * 複数選択可能であればtrue */ public void setMultipleSelectable(boolean multipleSelectable) { this.multipleSelectable = multipleSelectable; } /** * 表示行数を取得する. * * @return 表示行数 */ public int getVisibleRows() { return visibleRows; } /** * 表示行数を設定する * * @param visibleRows * 表示行数 */ public void setVisibleRows(int visibleRows) { this.visibleRows = visibleRows; } /** * このカテゴリに指定したレイヤーが含まれるか検証する. * * @param layer * レイヤー * @return 含まれる場合はtrue、含まれない場合はfalse */ public boolean hasLayer(Layer layer) { if (layer != null) { for (Layer memberLayer : layers) { if (Layer.equals(memberLayer, layer)) { return true; } } } return false; } /** * レイヤー情報 * * @return レイヤー情報 */ public Collection getLayers() { return Collections.unmodifiableCollection(layers); } /** * レイヤー情報 * * @param layers */ public void setLayers(Collection layers) { this.layers.clear(); if (layers != null) { this.layers.addAll(layers); } } /** * レイヤーを取得する.
* 該当するレイヤーがなければnull * * @param layerId * レイヤー名 * @return レイヤーもしくはnull */ public Layer getLayer(String layerId) { if (layerId == null) { return null; } for (Layer layer : layers) { if (layer.getId().equals(layerId)) { return layer; } } return null; } /** * カテゴリ識別名を取得する. * * @return カテゴリ識別名 */ public String getCategoryId() { return categoryId; } /** * カテゴリ表示名を取得する. * * @return カテゴリ表示名 */ public String getLocalizedCategoryName() { return this.localizedCategoryName; } /** * カテゴリ表示名を設定する. * * @param localizedCategoryName * カテゴリ表示名 */ public void setLocalizedCategoryName(String localizedCategoryName) { if (localizedCategoryName == null || localizedCategoryName.trim().length() == 0) { throw new IllegalArgumentException(); } this.localizedCategoryName = localizedCategoryName.trim(); } @Override public int hashCode() { return this.categoryId.hashCode(); } @Override public String toString() { return getLocalizedCategoryName(); } } /** * カテゴリのテーブル編集モデル. * * @author seraphy * */ class CategoriesTableModel extends AbstractTableModelWithComboBoxModel { private static final long serialVersionUID = 1L; private static final Logger logger = Logger.getLogger(CategoriesTableModel.class.getName()); public interface UsedCategoryDetector { List getLayers(CategoriesTableRow partsCategory); } private static final String[] categoriesColumnName; private static final int[] categoriesColumnWidths; static { final Properties strings = LocalizedResourcePropertyLoader .getCachedInstance().getLocalizedProperties(ProfileEditDialog.STRINGS_RESOURCE); categoriesColumnName = new String[] { strings.getProperty("categories.column.categoryname"), strings.getProperty("categories.column.multipleselectable"), strings.getProperty("categories.column.displayrowcount"), strings.getProperty("categories.column.usedlayers"), }; categoriesColumnWidths = new int[] { Integer.parseInt(strings.getProperty("categories.column.categoryname.width")), Integer.parseInt(strings.getProperty("categories.column.multipleselectable.width")), Integer.parseInt(strings.getProperty("categories.column.displayrowcount.width")), Integer.parseInt(strings.getProperty("categories.column.usedlayers.width")), }; } private int serialCounter = 1; private UsedCategoryDetector usedCategoryDetector; public CategoriesTableModel() { } public void adjustColumnModel(TableColumnModel columnModel) { for (int idx = 0; idx < categoriesColumnWidths.length; idx++) { columnModel.getColumn(idx).setPreferredWidth(categoriesColumnWidths[idx]); } } public void setUsedCategoryDetector( UsedCategoryDetector usedCategoryDetector) { this.usedCategoryDetector = usedCategoryDetector; } public UsedCategoryDetector getUsedCategoryDetector() { return usedCategoryDetector; } public void addCategory() { String id = "cat" + UUID.randomUUID().toString(); String name = "Category" + (serialCounter++); CategoriesTableRow partsCategory = new CategoriesTableRow( serialCounter, id, name, false, 10, null); addRow(partsCategory); } /** * 定義順を振り直す. */ public void reorder() { int mx = elements.size(); for (int idx = 0; idx < mx; idx++) { CategoriesTableRow partsCategory = elements.get(idx); partsCategory.setOrder(idx + 1); } fireTableDataChanged(); } @Override public int moveDown(int rowIndex) { int ret = super.moveDown(rowIndex); reorder(); return ret; } @Override public int moveUp(int rowIndex) { int ret = super.moveUp(rowIndex); reorder(); return ret; } @Override public String getColumnName(int column) { return categoriesColumnName[column]; } public int getColumnCount() { return categoriesColumnName.length; } public Object getValueAt(int rowIndex, int columnIndex) { CategoriesTableRow partsCategory = elements.get(rowIndex); switch (columnIndex) { case 0: return partsCategory.getLocalizedCategoryName(); case 1: return Boolean.valueOf(partsCategory.isMultipleSelectable()); case 2: return partsCategory.getVisibleRows(); case 3: StringBuilder layerNames = new StringBuilder(); List layers = null; if (usedCategoryDetector != null) { layers = usedCategoryDetector.getLayers(partsCategory); } if (layers != null) { for (LayersTableRow layer : layers) { if (layerNames.length() > 0) { layerNames.append(", "); } layerNames.append(layer.getLayerName()); } } return layerNames.toString(); default: return "***"; } } @Override public void setValueAt(Object aValue, int rowIndex, int columnIndex) { CategoriesTableRow partsCategory = elements.get(rowIndex); try { switch (columnIndex) { case 0: partsCategory.setLocalizedCategoryName((String) aValue); break; case 1: partsCategory.setMultipleSelectable(((Boolean) aValue).booleanValue()); break; case 2: partsCategory.setVisibleRows(((Number) aValue).intValue()); break; default: return; } fireTableCellUpdated(rowIndex, columnIndex); } catch (RuntimeException ex) { logger.log(Level.FINE, "value set failed. (" + rowIndex + ", " + columnIndex + "): " + aValue, ex); // 無視する. } } @Override public Class getColumnClass(int columnIndex) { if (columnIndex == 1) { return Boolean.class; } if (columnIndex == 2) { return Integer.class; } return String.class; } @Override public boolean isCellEditable(int rowIndex, int columnIndex) { if (columnIndex >= categoriesColumnName.length - 1) { return false; } return isEditable(); } } /** * レイヤーのテーブル編集モデル * * @author seraphy */ class LayersTableModel extends AbstractTableModelWithComboBoxModel { private static final long serialVersionUID = 1L; private static final Logger logger = Logger.getLogger(LayersTableModel.class.getName()); private static final String[] layerColumnNames; private static final int[] layersColumnWidths; private enum Columns { /** * レイヤー名 */ LAYER_NAME("layers.column.layername", String.class) { @Override public Object getValue(LayersTableRow layer) { return layer.getLayerName(); } @Override public boolean setValue(LayersTableRow layer, Object aValue) { layer.setLayerName((String) aValue); return true; } }, /** * カテゴリ */ CATEGORY("layers.column.category", CategoriesTableRow.class) { @Override public Object getValue(LayersTableRow layer) { return layer.getPartsCategory(); } @Override public boolean setValue(LayersTableRow layer, Object aValue) { layer.setPartsCategory((CategoriesTableRow) aValue); return true; } }, /** * カラーグループ */ COLOR_GROUP("layers.column.colorgroup", ColorGroupsTableRow.class) { @Override public Object getValue(LayersTableRow layer) { return layer.getColorGroup(); } @Override public boolean setValue(LayersTableRow layer, Object aValue) { layer.setColorGroup((ColorGroupsTableRow) aValue); return true; } }, /** * 順序 */ ORDER("layers.column.order", Integer.class) { @Override public Object getValue(LayersTableRow layer) { return layer.getOrder(); } @Override public boolean setValue(LayersTableRow layer, Object aValue) { layer.setOrder(((Number) aValue).intValue()); return true; } }, /** * カラーモデル */ COLOR_MODEL("layers.column.colorModel", ColorModels.class) { @Override public Object getValue(LayersTableRow layer) { return layer.getColorModel(); } @Override public boolean setValue(LayersTableRow layer, Object aValue) { layer.setColorModel(((ColorModels) aValue)); return true; } }, /** * ディレクトリ */ DIRECTORY("layers.column.directory", String.class) { @Override public Object getValue(LayersTableRow layer) { return layer.getDir(); } @Override public boolean setValue(LayersTableRow layer, Object aValue) { layer.setDir((String) aValue); return true; } }; private final String resourceKey; private final Class typ; Columns(String resourceKey, Class typ) { this.resourceKey = resourceKey; this.typ = typ; } public String getResourceKey() { return resourceKey; } public Class getColumnClass() { return typ; } public abstract Object getValue(LayersTableRow layer); public abstract boolean setValue(LayersTableRow layer, Object aValue); } static { final Properties strings = LocalizedResourcePropertyLoader .getCachedInstance().getLocalizedProperties(ProfileEditDialog.STRINGS_RESOURCE); int columnsLen = Columns.values().length; layerColumnNames = new String[columnsLen]; layersColumnWidths = new int[columnsLen]; for (Columns column : Columns.values()) { try { layerColumnNames[column.ordinal()] = strings.getProperty(column .getResourceKey()); layersColumnWidths[column.ordinal()] = Integer.parseInt(strings .getProperty(column.getResourceKey() + ".width")); } catch (RuntimeException ex) { logger.log(Level.SEVERE, "resource not found. related=" + column, ex); throw ex; } } } private int serialCounter = 1; public LayersTableModel() { super(); } public void adjustColumnModel(TableColumnModel columnModel) { for (int idx = 0; idx < layersColumnWidths.length; idx++) { columnModel.getColumn(idx).setPreferredWidth(layersColumnWidths[idx]); } } public void addNewLayer() { LayersTableRow layer = new LayersTableRow(); String layerId = "lay" + UUID.randomUUID().toString(); String layerName = "Layer" + (serialCounter++); layer.setLayerId(layerId); layer.setLayerName(layerName); addRow(layer); } public int getColumnCount() { return layerColumnNames.length; } @Override public String getColumnName(int column) { return layerColumnNames[column]; } public Object getValueAt(int rowIndex, int columnIndex) { LayersTableRow layer = elements.get(rowIndex); Columns column = Columns.values()[columnIndex]; return column.getValue(layer); } @Override public void setValueAt(Object aValue, int rowIndex, int columnIndex) { LayersTableRow layer = elements.get(rowIndex); try { Columns column = Columns.values()[columnIndex]; if (column.setValue(layer, aValue)) { fireTableCellUpdated(rowIndex, columnIndex); } } catch (Exception ex) { logger.log(Level.FINE, "value set failed. (" + rowIndex + ", " + columnIndex + "): " + aValue, ex); // 無視する. } } @Override public Class getColumnClass(int columnIndex) { Columns column = Columns.values()[columnIndex]; return column.getColumnClass(); } @Override public boolean isCellEditable(int rowIndex, int columnIndex) { return isEditable(); } public void sort() { Collections.sort(elements, new Comparator() { public int compare(LayersTableRow o1, LayersTableRow o2) { int ret; CategoriesTableRow p1 = o1.getPartsCategory(); CategoriesTableRow p2 = o2.getPartsCategory(); if (p1 == p2) { ret = 0; } else if (p1 != null && p2 != null) { ret = p1.compareTo(p2); } else if (p1 == null) { ret = -1; } else { ret = 1; } if (ret == 0) { ret = o1.getOrder() - o2.getOrder(); } if (ret == 0) { ret = o1.getLayerId().compareTo(o2.getLayerId()); } return ret; } }); fireTableDataChanged(); } protected boolean isUsed(ColorGroupsTableRow colorGroup) { if (colorGroup != null) { for (LayersTableRow layer : elements) { if (ColorGroupsTableRow.equals(layer.getColorGroup(), colorGroup)) { return true; } } } return false; } protected boolean isUsed(CategoriesTableRow partsCategory) { if (partsCategory != null) { for (LayersTableRow layer : elements) { if (CategoriesTableRow.equals(layer.getPartsCategory(), partsCategory)) { return true; } } } return false; } } /** * レイヤーのテーブル編集モデルで使うレイヤー編集クラス * * @author seraphy */ class LayersTableRow { private String layerId; private String layerName; private CategoriesTableRow partsCategory; private ColorGroupsTableRow colorGroup = ColorGroupsTableRow.NA; private int order; private String dir; private ColorModels colorModel = ColorModels.DEFAULT; public LayersTableRow() { super(); } public String getLayerId() { return layerId; } public void setLayerId(String layerId) { if (layerId == null || layerId.trim().length() == 0) { throw new IllegalArgumentException(); } this.layerId = layerId.trim(); } public String getLayerName() { return layerName; } public void setLayerName(String layerName) { if (layerName == null || layerName.trim().length() == 0) { throw new IllegalArgumentException(); } this.layerName = layerName.trim(); } public CategoriesTableRow getPartsCategory() { return partsCategory; } public void setPartsCategory(CategoriesTableRow partsCategory) { this.partsCategory = partsCategory; } public ColorGroupsTableRow getColorGroup() { return colorGroup; } public void setColorGroup(ColorGroupsTableRow colorGroup) { if (colorGroup == null) { throw new IllegalArgumentException(); } this.colorGroup = colorGroup; } public int getOrder() { return order; } public void setOrder(int order) { this.order = order; } public String getDir() { return dir; } public void setDir(String dir) { if (dir == null || dir.trim().length() == 0) { throw new IllegalArgumentException(); } dir = dir.trim(); if (dir.indexOf("/") >= 0 || dir.indexOf("\\") >= 0 || dir.indexOf("..") >= 0 || dir.endsWith(".")) { throw new IllegalArgumentException("not simple name: " + dir); } this.dir = dir; } public ColorModels getColorModel() { return colorModel; } public void setColorModel(ColorModels colorModel) { this.colorModel = colorModel; } public boolean isValid() { return layerName != null && layerName.trim().length() > 0 && dir != null && dir.trim().length() > 0 && partsCategory != null && colorGroup != null; } public Layer toLayer() { if (!isValid()) { return null; } ColorGroup colorGroup = getColorGroup().convert(); return new Layer( getLayerId(), getLayerName(), getOrder(), colorGroup, colorGroup.isEnabled(), getDir(), getColorModel().name()); } } /** * パーツセットのテーブルの行編集モデル * * @author seraphy */ class PresetsTableRow { private PartsSet partsSet; public PresetsTableRow(PartsSet partsSet) { if (partsSet == null) { throw new IllegalArgumentException(); } this.partsSet = partsSet.clone(); } public String getPartsSetId() { return partsSet.getPartsSetId(); } public String getLocalizedName() { return partsSet.getLocalizedName(); } public void setLocalizedName(String localizedName) { partsSet.setLocalizedName(localizedName); } public boolean isPresetParts() { return partsSet.isPresetParts(); } public void setPresetParts(boolean checked) { partsSet.setPresetParts(checked); } public PartsSet convert() { return partsSet.clone(); } } /** * パーツセットのテーブル編集モデル * * @author seraphy */ class PartssetsTableModel extends AbstractTableModelWithComboBoxModel { private static final long serialVersionUID = 1L; private static final Logger logger = Logger.getLogger(PartssetsTableModel.class.getName()); private static final String[] partssetsColumnNames; private static final int[] partssetsColumnWidths; private String defaultPartsSetId; static { final Properties strings = LocalizedResourcePropertyLoader .getCachedInstance().getLocalizedProperties(ProfileEditDialog.STRINGS_RESOURCE); partssetsColumnNames = new String[] { strings.getProperty("partssets.column.default"), strings.getProperty("partssets.column.preset"), strings.getProperty("partssets.column.partssetname"), strings.getProperty("partssets.column.usedpartsname"), }; partssetsColumnWidths = new int[] { Integer.parseInt(strings.getProperty("partssets.column.default.width")), Integer.parseInt(strings.getProperty("partssets.column.preset.width")), Integer.parseInt(strings.getProperty("partssets.column.partssetname.width")), Integer.parseInt(strings.getProperty("partssets.column.usedpartsname.width")), }; } public PartssetsTableModel() { } public void setDefaultPartsSetId(String defaultPartsSetId) { this.defaultPartsSetId = defaultPartsSetId; } public String getDefaultPartsSetId() { return defaultPartsSetId; } public void adjustColumnModel(TableColumnModel columnModel) { for (int idx = 0; idx < partssetsColumnWidths.length; idx++) { columnModel.getColumn(idx).setPreferredWidth(partssetsColumnWidths[idx]); } } public int getColumnCount() { return partssetsColumnNames.length; } @Override public String getColumnName(int column) { return partssetsColumnNames[column]; } public Object getValueAt(int rowIndex, int columnIndex) { PresetsTableRow rowModel = elements.get(rowIndex); switch (columnIndex) { case 0: return rowModel.getPartsSetId().equals(defaultPartsSetId); case 1: return Boolean.valueOf(rowModel.isPresetParts()); case 2: return rowModel.getLocalizedName(); case 3: return getUsedParts(rowModel); default: return null; } } private String getUsedParts(PresetsTableRow rowModel) { StringBuilder buf = new StringBuilder(); PartsSet partsSet = rowModel.convert(); ArrayList categories = new ArrayList(partsSet.keySet()); Collections.sort(categories); for (PartsCategory category : categories) { if (buf.length() > 0) { buf.append(", "); } buf.append("[" + category.getLocalizedCategoryName() + "] "); List partsIdentifiers = partsSet.get(category); if (partsIdentifiers.isEmpty()) { buf.append("empty"); } else { int mx = partsIdentifiers.size(); for (int idx = 0; idx < mx; idx++) { if (idx != 0) { buf.append(", "); } buf.append(partsIdentifiers.get(idx).getLocalizedPartsName()); } } } return buf.toString(); } @Override public void setValueAt(Object aValue, int rowIndex, int columnIndex) { PresetsTableRow rowModel = elements.get(rowIndex); try { switch (columnIndex) { case 0: if (((Boolean) aValue).booleanValue()) { // デフォルトのパーツセットに指定した場合、プリセットもOnとなる。 rowModel.setPresetParts(true); defaultPartsSetId = rowModel.getPartsSetId(); fireTableDataChanged(); return; } break; case 1: if (((Boolean) aValue).booleanValue()) { rowModel.setPresetParts(true); } else { // デフォルトのパーツセットをプリセットから外した場合、 // デフォルトのパーツセットは未設定となる. rowModel.setPresetParts(false); if (rowModel.getPartsSetId().equals(defaultPartsSetId)) { defaultPartsSetId = null; fireTableRowsUpdated(rowIndex, rowIndex); return; } } break; case 2: String localizedName = (String) aValue; if (localizedName != null && localizedName.trim().length() > 0) { rowModel.setLocalizedName(localizedName.trim()); } break; case 3: return; default: return; } fireTableCellUpdated(rowIndex, columnIndex); } catch (Exception ex) { logger.log(Level.FINE, "value set failed. (" + rowIndex + ", " + columnIndex + "): " + aValue, ex); // 無視する. } } @Override public Class getColumnClass(int columnIndex) { switch (columnIndex) { case 0: return Boolean.class; case 1: return Boolean.class; case 2: return String.class; case 3: return String.class; default: } return String.class; } @Override public boolean isCellEditable(int rowIndex, int columnIndex) { switch (columnIndex ) { case 0: return isEditable(); case 1: return isEditable(); case 2: return isEditable(); case 3: return false; default: } return false; } } /** * お勧めリンクのテーブルの行編集モデル * * @author seraphy */ class RecommendationTableRow { private RecommendationURL recommendationURL; public RecommendationTableRow(RecommendationURL recommendationURL) { if (recommendationURL == null) { throw new IllegalArgumentException(); } this.recommendationURL = recommendationURL.clone(); } public String getLocalizedName() { return recommendationURL.getDisplayName(); } public void setLocalizedName(String localizedName) { recommendationURL.setDisplayName(localizedName); } public String getURL() { return recommendationURL.getUrl(); } public void setURL(String url) { recommendationURL.setUrl(url); } public RecommendationURL convert() { return recommendationURL.clone(); } } /** * お勧めリンクのテーブル編集モデル * * @author seraphy */ class RecommendationTableModel extends AbstractTableModelWithComboBoxModel { private static final long serialVersionUID = 1L; private static final Logger logger = Logger.getLogger(PartssetsTableModel.class.getName()); private static final String[] partssetsColumnNames; private static final int[] partssetsColumnWidths; static { final Properties strings = LocalizedResourcePropertyLoader .getCachedInstance().getLocalizedProperties(ProfileEditDialog.STRINGS_RESOURCE); partssetsColumnNames = new String[] { strings.getProperty("recommendations.column.displayName"), strings.getProperty("recommendations.column.url"), }; partssetsColumnWidths = new int[] { Integer.parseInt(strings.getProperty("recommendations.column.displayName.width")), Integer.parseInt(strings.getProperty("recommendations.column.url.width")), }; } public RecommendationTableModel() { } public void adjustColumnModel(TableColumnModel columnModel) { for (int idx = 0; idx < partssetsColumnWidths.length; idx++) { columnModel.getColumn(idx).setPreferredWidth(partssetsColumnWidths[idx]); } } public void addNew() { addRow(new RecommendationTableRow(new RecommendationURL())); } public int getColumnCount() { return partssetsColumnNames.length; } @Override public String getColumnName(int column) { return partssetsColumnNames[column]; } public Object getValueAt(int rowIndex, int columnIndex) { RecommendationTableRow rowModel = elements.get(rowIndex); switch (columnIndex) { case 0: return rowModel.getLocalizedName(); case 1: return rowModel.getURL(); default: return null; } } @Override public void setValueAt(Object aValue, int rowIndex, int columnIndex) { RecommendationTableRow rowModel = elements.get(rowIndex); try { switch (columnIndex) { case 0: if (aValue != null && ((String) aValue).trim().length() > 0) { rowModel.setLocalizedName((String) aValue); } break; case 1: if (aValue != null && ((String) aValue).trim().length() > 0) { rowModel.setURL((String) aValue); } break; default: return; } fireTableCellUpdated(rowIndex, columnIndex); } catch (Exception ex) { logger.log(Level.FINE, "value set failed. (" + rowIndex + ", " + columnIndex + "): " + aValue, ex); // 無視する. } } @Override public Class getColumnClass(int columnIndex) { switch (columnIndex) { case 0: return String.class; case 1: return String.class; default: } return String.class; } @Override public boolean isCellEditable(int rowIndex, int columnIndex) { switch (columnIndex ) { case 0: return isEditable(); case 1: return isEditable(); default: } return false; } } CharacterManaJ/src/charactermanaj/ui/Wallpaper.java0000644000175000017500000004556012560206305022543 0ustar paulliupaulliupackage charactermanaj.ui; import static java.lang.Math.ceil; import java.awt.AlphaComposite; import java.awt.Color; import java.awt.Composite; import java.awt.Graphics2D; import java.awt.GraphicsConfiguration; import java.awt.Image; import java.awt.Rectangle; import java.awt.Transparency; import java.awt.image.BufferedImage; import java.awt.image.VolatileImage; import java.beans.PropertyChangeListener; import java.beans.PropertyChangeSupport; import java.util.logging.Level; import java.util.logging.Logger; import charactermanaj.model.AppConfig; /** * 壁紙 * @author seraphy */ public class Wallpaper { /** * ロガー */ private static final Logger logger = Logger.getLogger(Wallpaper.class.getName()); /** * 壁紙の推奨されるブロックサイズ(幅).
* このサイズに満たない壁紙用画像は、このサイズに近い値まで敷き詰めて保持しておく.
*/ private static final int wallpaperPreferredWidth = 128; /** * 壁紙の推奨されるブロックサイズ(高さ).
* このサイズに満たない壁紙用画像は、このサイズに近い値まで敷き詰めて保持しておく.
*/ private static final int wallpaperPreferredHeight = 128; /** * プロパティ変更イベントのキー名 */ public static final String KEY_WALLPAPER_IMAGE = "wallpaperImage"; /** * プロパティ変更通知サポート. */ private PropertyChangeSupport propertyChangeSupport = new PropertyChangeSupport(this); /** * 背景色.
*/ private Color backgroundColor = Color.WHITE; /** * 壁紙画像.
*/ private BufferedImage wallpaperImg; /** * 壁紙画像のアルファ値.
*/ private float wallpaperAlpha = 1.f; /** * 壁紙用オフスクリーンサーフェイス.
* なければnull.
*/ private VolatileImage wallpaperVolatileImg; /** * 壁紙用オフスクリーンを生成したときに使用した背景色.
* なければnull */ private Color wallpaperVolatileBgColor; /** * 壁紙が表示されない状態で壁紙イメージを構築する.
*/ public Wallpaper() { this(null); } /** * 壁紙の元画像を指定して壁紙イメージを構築する.
* nullを指定した場合は壁紙は表示されない.
* @param wallpaperImg 壁紙イメージ、もしくはnull */ public Wallpaper(BufferedImage wallpaperImg) { this.wallpaperImg = makeExpandedWallpaper(makeExpandedWallpaper(wallpaperImg)); } /** * 壁紙画像を設定する.
* nullの場合は解除される.
* 壁紙が小さい場合は推奨されるブロックサイズまで敷き詰めなおした状態で * 保持する.(描画不可軽減のため.) * したがって、{@link #getWallpaperImage()}を呼び出したときには * 拡張されたサイズとなっている.
* @param wallpaperImg */ public void setWallpaperImage(BufferedImage wallpaperImg) { // 現在のオフスクリーンを破棄する. disposeOffscreen(); // 新しいイメージ BufferedImage wallpaperImgOld = makeExpandedWallpaper(this.wallpaperImg); this.wallpaperImg = makeExpandedWallpaper(wallpaperImg); propertyChangeSupport.firePropertyChange("wallpaperImage", wallpaperImgOld, this.wallpaperImg); } /** * 壁紙画像を取得する.
* 壁紙画像はブロックサイズまで拡張されたものとなっている.
* @return 壁紙画像、なければnull */ public BufferedImage getWallpaperImage() { return wallpaperImg; } public float getWallpaperAlpha() { return wallpaperAlpha; } /** * 壁紙画像を描画する場合のアルファ値を設定する.
* 負の値は0に、1以上は1に制限される.
* @param wallpaperAlpha アルファ値(0から1の範囲) */ public void setWallpaperAlpha(float wallpaperAlpha) { // 現在のオフスクリーンを破棄する. disposeOffscreen(); // 範囲無いに制限する. if (wallpaperAlpha < 0) { wallpaperAlpha = 0; } else if (wallpaperAlpha > 1.f) { wallpaperAlpha = 1.f; } // アルファ値を設定する. float oldalpha = this.wallpaperAlpha; if (oldalpha != wallpaperAlpha) { this.wallpaperAlpha = wallpaperAlpha; propertyChangeSupport.firePropertyChange("wallpaperAlpha", oldalpha, wallpaperAlpha); } } public Color getBackgroundColor() { return backgroundColor; } public void setBackgroundColor(Color backgroundColor) { // 現在のオフスクリーンを破棄する. disposeOffscreen(); // 色が省略された場合の補正. if (backgroundColor == null) { backgroundColor = Color.WHITE; } // 背景色を設定する. Color colorOld = this.backgroundColor; if ( !colorOld.equals(backgroundColor)) { this.backgroundColor = backgroundColor; propertyChangeSupport.firePropertyChange("backgroundColor", colorOld, backgroundColor); } } /** * 壁紙を左上(0,0)を原点に指定した幅・高さでタイル状に敷き詰めて描画します.
* 壁紙が設定されていなければ何もしません.
* アプリケーション設定でオフスクリーンの使用が有効である場合、グラフィクスコンテキストに * あわせてオフスクリーンイメージをあらかじめキャッシュとして作成して転送する.
* オフスクリーンは初回描画時に構築され、以降、必要に応じて再作成される.
* オフスクリーンを即座に破棄する場合には{@link #disposeOffscreen()}を呼び出す.
* @param g 描画先 * @param bgColor 背景色 * @param w 幅 (画面幅) * @param h 高さ (画面高) */ public void drawWallpaper(Graphics2D g, int w, int h) { drawWallpaper(g, w, h, false); } /** * 壁紙を左上(0,0)を原点に指定した幅・高さでタイル状に敷き詰めて描画します.
* 壁紙が設定されていなければ何もしません.
* アプリケーション設定でオフスクリーンの使用が有効である場合、グラフィクスコンテキストに * あわせてオフスクリーンイメージをあらかじめキャッシュとして作成して転送する.
* ただし、引数でオフスクリーンを使用しないと指定した場合はオフスクリーンには一切関知せず、 * 通常の画像による背景描画を行う.
* (この場合、オフスクリーンは作成されず、現在あるものを再作成も破棄もしない.) * @param g 描画先 * @param bgColor 背景色 * @param w 幅 (画面幅) * @param h 高さ (画面高) * @param noUseOffscreen オフスクリーンを使用しない.(たとえ利用可能であっても) */ public void drawWallpaper(Graphics2D g, int w, int h, boolean noUseOffscreen) { // 背景色 Color bgColor = getBackgroundColor(); // 背景色で塗りつぶす // (壁紙がある場合は不要) if (wallpaperImg == null) { fillBackgroundColor(g, bgColor, w, h); } // 壁紙を敷き詰める if (wallpaperImg != null) { // オフスクリーンによる背景描画が行われたか? boolean drawOffscreen = false; if ( !noUseOffscreen) { // オフクリーンサーフェイスのチェックまたは生成 AppConfig appConfig = AppConfig.getInstance(); if (appConfig.isEnableOffscreenWallpaper()) { checkOrCreateOffscreen(g, bgColor, w, h); } else { disposeOffscreen(); } // オフスクリーンフェイスが有効であれば、オフスクリーンで描画する. if (wallpaperVolatileImg != null) { int src_w = wallpaperVolatileImg.getWidth(); int src_h = wallpaperVolatileImg.getHeight(); drawWallpaper(g, w, h, wallpaperVolatileImg, src_w, src_h); // オフスクリーンがロストしていなければ // オフスクリーンで描画されたと判定する. drawOffscreen = !wallpaperVolatileImg.contentsLost(); } } // オフスクリーンサーフェイスで描画されていなければ通常の画像で描画する. if ( !drawOffscreen) { if (wallpaperVolatileImg != null && logger.isLoggable(Level.INFO)) { logger.log(Level.INFO, "offscreen is lost."); } fillBackgroundColor(g, bgColor, w, h); Composite oldcomp = g.getComposite(); try { float alpha = getWallpaperAlpha(); if (alpha < 1.f) { // アルファが100%の場合は、あえて設定しない. AlphaComposite comp = AlphaComposite.getInstance(AlphaComposite.SRC_OVER, alpha); g.setComposite(comp); } int src_w = wallpaperImg.getWidth(); int src_h = wallpaperImg.getHeight(); drawWallpaper(g, w, h, wallpaperImg, src_w, src_h); } finally { g.setComposite(oldcomp); } } } } /** * 現在使用しているオフスクリーン用ネイティブリソースを破棄する.
*/ protected void disposeOffscreen() { if (wallpaperVolatileImg != null) { wallpaperVolatileImg.flush(); wallpaperVolatileImg = null; if (logger.isLoggable(Level.FINER)) { logger.log(Level.FINER, "オフスクリーンを破棄しました。"); } } } /** * オフスクリーンイメージが有効であるかチェックし、 * 有効でなければオフスクリーンを再作成、もしくは再描画する.
* オフスクリーンは背景画像と同じサイズで作成される.
* 背景画像が設定されていなければオフスクリーンも無効とする.
* @param g 実際のスクリーンデバイス(互換性あるオフスクリーンを作成するため) * @param bgColor 背景色 * @param offscreen_max_w オフスクリーンの最大サイズ(幅) * @param offscreen_max_h オフスクリーンの最大サイズ(高さ) */ protected void checkOrCreateOffscreen(Graphics2D g, Color bgColor, int offscreen_max_w, int offscreen_max_h) { if (wallpaperImg == null) { // 壁紙もと画像がなければ何もしない. disposeOffscreen(); return; } try { GraphicsConfiguration gConf = g.getDeviceConfiguration(); // オフスクリーンの状態を確認する. int validate = VolatileImage.IMAGE_INCOMPATIBLE; if (wallpaperVolatileImg != null) { validate = wallpaperVolatileImg.validate(gConf); if (logger.isLoggable(Level.FINEST)) { logger.log(Level.FINEST, "オフスクリーンの状態: " + validate); } } // 構築時の背景色と変更があるか? if (validate == VolatileImage.IMAGE_OK && (wallpaperVolatileBgColor != null && bgColor != null)) { if ( !wallpaperVolatileBgColor.equals(bgColor)) { validate = VolatileImage.IMAGE_RESTORED; } } // 壁紙元画像サイズ int src_w = wallpaperImg.getWidth(); int src_h = wallpaperImg.getHeight(); // オフスクリーンサイズの算定. // 要求された最大幅かアプリ設定の最大幅の小さいほうを最大幅とし、 // それが壁紙もと画像よりも小さければ壁紙サイズと同じとする. AppConfig appConfig = AppConfig.getInstance(); int offscreen_w = appConfig.getOffscreenWallpaperSize(); int offscreen_h = appConfig.getOffscreenWallpaperSize(); offscreen_w = Math.max(src_w, Math.min(offscreen_max_w, offscreen_w)); offscreen_h = Math.max(src_h, Math.min(offscreen_max_h, offscreen_h)); // ブロックサイズを満たすために必要な元サイズの繰り返し数 int nx = (int) ceil((double) offscreen_w / src_w); int ny = (int) ceil((double) offscreen_h / src_h); // 繰り返し数からブロックサイズに近い元サイズで割り切れるサイズを求める offscreen_w = src_w * nx; offscreen_h = src_h * ny; // オフスクリーンが有効、もしくは描画が必要である状態の場合にサイズのチェックを行う. if (validate == VolatileImage.IMAGE_OK || validate == VolatileImage.IMAGE_RESTORED) { int currentOffW = Math.max(1, wallpaperVolatileImg.getWidth()); int currentOffH = Math.max(1, wallpaperVolatileImg.getHeight()); double ratioW = ((double) offscreen_w / currentOffW); double ratioH = ((double) offscreen_h / currentOffH); // オフスクリーンの描画済みサイズが要求サイズの2割を超えるか割り込んだ場合は // 再作成が必要. if (ratioW < 0.8 || ratioW > 1.2 || ratioH < 0.8 || ratioH > 1.2) { validate = VolatileImage.IMAGE_INCOMPATIBLE; if (logger.isLoggable(Level.FINE)) { logger.log(Level.FINE, "オフスクリーンサイズの変更が必要です。: " + ratioW + "," + ratioH); } } } // オフスクリーンの状態が再構築または再描画が必要であるか? if (validate != VolatileImage.IMAGE_OK ) { // オフスクリーンがないか、コンパチでなくなっている場合はオフスクリーンを生成する. if (wallpaperVolatileImg == null || validate == VolatileImage.IMAGE_INCOMPATIBLE) { // 現在使用しているネイティブリソースを破棄する. disposeOffscreen(); // 新しいオフスクリーンサーフェイスを作成する. wallpaperVolatileImg = gConf.createCompatibleVolatileImage( offscreen_w, offscreen_h, Transparency.OPAQUE); if (wallpaperVolatileImg == null) { logger.log(Level.INFO, "オフスクリーンイメージは生成できません。"); } else { if (logger.isLoggable(Level.FINER)) { logger.log(Level.FINER, "オフスクリーンを構築しました。(サイズ:" + offscreen_w + "," + offscreen_h + ")"); } } } // 再描画する. if (wallpaperVolatileImg != null) { if (logger.isLoggable(Level.FINER)) { logger.log(Level.FINER, "オフスクリーンの描画 (サイズ:" + offscreen_w + "," + offscreen_h + ")"); } Graphics2D vg = wallpaperVolatileImg.createGraphics(); try { int ow = wallpaperVolatileImg.getWidth(); int oh = wallpaperVolatileImg.getHeight(); fillBackgroundColor(vg, bgColor, ow, oh); Composite oldcomp = vg.getComposite(); try { float alpha = getWallpaperAlpha(); if (alpha < 1.f) { // アルファが100%の場合は、あえて設定しない. AlphaComposite comp = AlphaComposite.getInstance(AlphaComposite.SRC_OVER, alpha); vg.setComposite(comp); } drawWallpaper(vg, ow, oh, wallpaperImg, src_w, src_h); } finally { vg.setComposite(oldcomp); } } finally { vg.dispose(); } wallpaperVolatileBgColor = bgColor; } } } catch (RuntimeException ex) { logger.log(Level.SEVERE, "オフスクリーンイメージの生成中に例外が発生しました。", ex); // 現在使用しているネイティブリソースを破棄する. disposeOffscreen(); } } /** * 背景色で塗りつぶす * @param g 対象 * @param bgColor 背景色、nullの場合は何もしない. * @param w 幅 * @param h 高さ */ protected void fillBackgroundColor(Graphics2D g, Color bgColor, int w, int h) { if (bgColor == null) { return; } Color oldc = g.getColor(); try { Rectangle clip = g.getClipBounds(); if (clip == null) { clip = new Rectangle(0, 0, w, h); } g.setColor(bgColor); g.fill(clip); } finally { g.setColor(oldc); } } /** * 壁紙を指定の領域まで敷き詰めて描画する.
* @param g * @param w 敷き詰めるサイズ(幅) * @param h 敷き詰めるサイズ(高さ) * @param wallpaperImg 敷き詰める画像 * @param src_w 壁紙画像のサイズ * @param src_v 壁紙画像の高さ */ protected void drawWallpaper(Graphics2D g, int w, int h, Image wallpaperImg, int src_w, int src_h) { if (wallpaperImg == null) { return; } // 表示範囲で表示できる壁紙を表示できる個数 int nx = (int) ceil((double) w / src_w); int ny = (int) ceil((double) h / src_h); // 描画対象領域 Rectangle clip = g.getClipBounds(); // 描画対象領域にかかる壁紙を描画する. Rectangle rct = new Rectangle(0, 0, src_w, src_h); for (int iy = 0; iy <= ny; iy++) { for (int ix = 0; ix <= nx; ix++) { rct.x = ix * src_w; rct.y = iy * src_h; if (clip == null || clip.intersects(rct)) { g.drawImage( wallpaperImg, rct.x, rct.y, rct.x + rct.width, rct.y + rct.height, 0, 0, src_w, src_h, null ); } } } } /** * 壁紙を一定の大きさに敷き詰める.
* すでに十分大きい場合は何もしない.
* @param wallpaper 対象のイメージ * @return 拡張後のイメージ、もしくは同じイメージ */ protected BufferedImage makeExpandedWallpaper(BufferedImage wallpaper) { if (wallpaper == null) { return null; } // 敷き詰める画像の元サイズ int src_w = wallpaper.getWidth(); int src_h = wallpaper.getHeight(); // ブロックサイズよりも元サイズが大きければ何もしない. if (src_w > wallpaperPreferredWidth && src_h > wallpaperPreferredHeight) { return wallpaper; } // ブロックサイズを満たすために必要な元サイズの繰り返し数 int nx = (int) ceil((double) wallpaperPreferredWidth / src_w); int ny = (int) ceil((double) wallpaperPreferredHeight / src_h); // 繰り返し数からブロックサイズに近い元サイズで割り切れるサイズを求める int w = src_w * nx; int h = src_h * ny; // ブロックサイズまで元画像を敷き詰める BufferedImage wallpaperNew = new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB); Graphics2D g = wallpaperNew.createGraphics(); try { drawWallpaper(g, w, h, wallpaper, src_w, src_h); } finally { g.dispose(); } return wallpaperNew; } public void addPropertyChangeListener(PropertyChangeListener listener) { propertyChangeSupport.addPropertyChangeListener(listener); } public void addPropertyChangeListener(String propertyName, PropertyChangeListener listener) { propertyChangeSupport.addPropertyChangeListener(propertyName, listener); } public void removePropertyChangeListener(PropertyChangeListener listener) { propertyChangeSupport.removePropertyChangeListener(listener); } public void removePropertyChangeListener(String propertyName, PropertyChangeListener listener) { propertyChangeSupport.removePropertyChangeListener(propertyName, listener); } } CharacterManaJ/src/charactermanaj/ui/MenuDataFactory.java0000644000175000017500000000577212560206305023643 0ustar paulliupaulliupackage charactermanaj.ui; import java.awt.event.ActionListener; import java.util.AbstractCollection; import java.util.Iterator; import java.util.NoSuchElementException; import java.util.Properties; public class MenuDataFactory extends AbstractCollection { private String name; private boolean checkbox; private ActionListener actionListener; private MenuDataFactory[] factories; public MenuDataFactory(String name) { this(name, false, null, null); } public MenuDataFactory(String name, MenuDataFactory[] factories) { this(name, false, null, factories); } public MenuDataFactory(String name, ActionListener actionListener) { this(name, false, actionListener, null); } public MenuDataFactory(String name, boolean checkbox, ActionListener actionListener) { this(name, checkbox, actionListener, null); } public String getName() { return name; } public boolean isCheckbox() { return checkbox; } public MenuDataFactory(String name, boolean checkbox, ActionListener actionListener, MenuDataFactory[] factories) { if (name == null || name.length() == 0) { throw new IllegalArgumentException(); } if (factories == null) { factories = new MenuDataFactory[0]; } this.name = name; this.checkbox = checkbox; this.actionListener = actionListener; this.factories = factories; } public MenuData createMenuData(Properties props) { if (props == null) { throw new IllegalArgumentException(); } String text = props.getProperty(name + ".text"); String mnemonic = props.getProperty(name + ".mnemonic"); String mnemonicDisp = props.getProperty(name + ".mnemonicDisp"); String ignoreMacOSX = props.getProperty(name + ".ignoreMacOSX"); String shortcutKey = props.getProperty(name + ".shortcut-key"); MenuData menuData = new MenuData(); menuData.setName(getName()); menuData.setCheckbox(isCheckbox()); menuData.setText(text); if (mnemonic != null && mnemonic.length() > 0) { menuData.setMnemonic(mnemonic.charAt(0)); menuData.setMnimonicDisp(mnemonicDisp); } menuData.setIgnoreMacOSX(ignoreMacOSX != null && Boolean.valueOf(ignoreMacOSX)); menuData.setActionListener(actionListener); menuData.setShortcutKey(shortcutKey); for (MenuDataFactory factory : factories) { if (factory != null) { menuData.add(factory.createMenuData(props)); } else { menuData.add(null); } } return menuData; } @Override public int size() { return factories.length; } @Override public Iterator iterator() { return new Iterator() { private int idx = 0; public boolean hasNext() { return idx < factories.length; } public MenuDataFactory next() { if (idx >= factories.length) { throw new NoSuchElementException(); } return factories[idx++]; } public void remove() { throw new UnsupportedOperationException(); } }; } } CharacterManaJ/src/charactermanaj/ui/PreviewPanel.java0000644000175000017500000017414712560206305023221 0ustar paulliupaulliupackage charactermanaj.ui; import static java.lang.Math.abs; import static java.lang.Math.ceil; import static java.lang.Math.floor; import static java.lang.Math.log; import static java.lang.Math.max; import static java.lang.Math.min; import static java.lang.Math.pow; import static java.lang.Math.round; import java.awt.BorderLayout; import java.awt.Color; import java.awt.Component; import java.awt.Cursor; import java.awt.Dimension; import java.awt.FontMetrics; import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.GridBagConstraints; import java.awt.GridBagLayout; import java.awt.Insets; import java.awt.Point; import java.awt.Rectangle; import java.awt.RenderingHints; import java.awt.Toolkit; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import java.awt.event.MouseMotionAdapter; import java.awt.event.MouseMotionListener; import java.awt.event.MouseWheelEvent; import java.awt.event.MouseWheelListener; import java.awt.geom.Rectangle2D; import java.awt.image.BufferedImage; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.util.ArrayList; import java.util.EventObject; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Properties; import java.util.TreeSet; import java.util.concurrent.Semaphore; import java.util.logging.Level; import java.util.logging.Logger; import javax.swing.AbstractButton; import javax.swing.BorderFactory; import javax.swing.Box; import javax.swing.Icon; import javax.swing.JButton; import javax.swing.JCheckBox; import javax.swing.JComboBox; import javax.swing.JLabel; import javax.swing.JLayeredPane; import javax.swing.JPanel; import javax.swing.JScrollBar; import javax.swing.JScrollPane; import javax.swing.JSlider; import javax.swing.JTextField; import javax.swing.JToolBar; import javax.swing.JViewport; import javax.swing.OverlayLayout; import javax.swing.SwingUtilities; import javax.swing.Timer; import javax.swing.UIManager; import javax.swing.border.Border; import javax.swing.event.ChangeEvent; import javax.swing.event.ChangeListener; import javax.swing.plaf.basic.BasicComboBoxEditor; import charactermanaj.Main; import charactermanaj.graphics.filters.BackgroundColorFilter; import charactermanaj.graphics.filters.BackgroundColorFilter.BackgroundColorMode; import charactermanaj.model.AppConfig; import charactermanaj.ui.util.ScrollPaneDragScrollSupport; import charactermanaj.util.LocalizedResourcePropertyLoader; import charactermanaj.util.UIHelper; /** * プレビューパネル * * @author seraphy */ public class PreviewPanel extends JPanel { private static final long serialVersionUID = 1L; protected static final String STRINGS_RESOURCE = "languages/previewpanel"; /** * プレビューパネルの上部ツールバーの通知を受けるリスナ * * @author seraphy */ public interface PreviewPanelListener { /** * 保存 * * @param e */ void savePicture(PreviewPanelEvent e); /** * コピー * * @param e */ void copyPicture(PreviewPanelEvent e); /** * 背景色変更 * * @param e */ void changeBackgroundColor(PreviewPanelEvent e); /** * 情報 * * @param e */ void showInformation(PreviewPanelEvent e); /** * お気に入りに追加 * * @param e */ void addFavorite(PreviewPanelEvent e); /** * 左右反転 * * @param e */ void flipHorizontal(PreviewPanelEvent e); } /** * ロード中を示すインジケータ */ private final String indicatorText; /** * ロード中であるか判定するタイマー */ private final Timer timer; /** * インジケータを表示するまでのディレイ */ private long indicatorDelay; @Override public void addNotify() { super.addNotify(); if (!timer.isRunning()) { timer.start(); } } @Override public void removeNotify() { if (timer.isRunning()) { timer.stop(); } super.removeNotify(); } public static class PreviewPanelEvent extends EventObject { private static final long serialVersionUID = 1L; private int modifiers; public PreviewPanelEvent(Object src, ActionEvent e) { this(src, (e == null) ? 0 : e.getModifiers()); } public PreviewPanelEvent(Object src, int modifiers) { super(src); this.modifiers = modifiers; } public int getModifiers() { return modifiers; } public boolean isShiftKeyPressed() { return (modifiers & ActionEvent.SHIFT_MASK) != 0; } } private final Object lock = new Object(); private long loadingTicket; private long loadedTicket; private long firstWaitingTimestamp; private boolean indicatorShown; private String title; private JLabel lblTitle; private JLayeredPane layeredPane; private CheckInfoLayerPanel checkInfoLayerPanel; private PreviewImagePanel previewImgPanel; private JScrollPane previewImgScrollPane; private ScrollPaneDragScrollSupport scrollSupport; private PreviewControlPanel previewControlPanel; private double latestToggleZoom = 2.; private LinkedList listeners = new LinkedList(); public PreviewPanel() { setLayout(new BorderLayout()); final AppConfig appConfig = AppConfig.getInstance(); final Properties strings = LocalizedResourcePropertyLoader.getCachedInstance() .getLocalizedProperties(STRINGS_RESOURCE); // 画像をロード中であることを示すインジケータの確認サイクル. timer = new Timer(100, new ActionListener() { public void actionPerformed(ActionEvent e) { onTimer(); } }); indicatorText = strings.getProperty("indicatorText"); indicatorDelay = appConfig.getPreviewIndicatorDelay(); UIHelper uiUtl = UIHelper.getInstance(); JButton saveBtn = uiUtl.createIconButton("icons/save.png"); JButton copyBtn = uiUtl.createIconButton("icons/copy.png"); JButton colorBtn = uiUtl.createIconButton("icons/color.png"); JButton informationBtn = uiUtl.createIconButton("icons/information.png"); JButton favoriteBtn = uiUtl.createIconButton("icons/favorite.png"); JButton flipHolizontalBtn = uiUtl.createIconButton("icons/flip.png"); saveBtn.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { savePicture(new PreviewPanelEvent(PreviewPanel.this, e)); } }); copyBtn.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { copyPicture(new PreviewPanelEvent(PreviewPanel.this, e)); } }); colorBtn.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { changeBackgroundColor(new PreviewPanelEvent(PreviewPanel.this, e)); } }); informationBtn.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { showInformation(new PreviewPanelEvent(PreviewPanel.this, e)); } }); favoriteBtn.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { addFavorite(new PreviewPanelEvent(PreviewPanel.this, e)); } }); flipHolizontalBtn.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { flipHolizontal(new PreviewPanelEvent(PreviewPanel.this, e)); } }); saveBtn.setToolTipText(strings.getProperty("tooltip.save")); copyBtn.setToolTipText(strings.getProperty("tooltip.copy")); colorBtn.setToolTipText(strings.getProperty("tooltip.changeBgColor")); informationBtn.setToolTipText(strings.getProperty("tooltip.showInformation")); favoriteBtn.setToolTipText(strings.getProperty("tooltip.registerFavorites")); flipHolizontalBtn.setToolTipText(strings.getProperty("tooltip.flipHorizontal")); final JToolBar toolBar = new JToolBar(); toolBar.setFloatable(false); toolBar.add(flipHolizontalBtn); toolBar.add(copyBtn); toolBar.add(saveBtn); toolBar.add(Box.createHorizontalStrut(8)); toolBar.add(colorBtn); toolBar.add(Box.createHorizontalStrut(4)); toolBar.add(favoriteBtn); toolBar.add(informationBtn); lblTitle = new JLabel() { private static final long serialVersionUID = 1L; public Dimension getPreferredSize() { Dimension dim = super.getPreferredSize(); int maxWidth = getParent().getWidth() - toolBar.getWidth(); if (dim.width > maxWidth) { dim.width = maxWidth; } return dim; }; public Dimension getMaximumSize() { return getPreferredSize(); }; public Dimension getMinimumSize() { Dimension dim = getPreferredSize(); dim.width = 50; return dim; }; }; lblTitle.setBorder(BorderFactory.createEmptyBorder(3, 10, 3, 3)); JPanel previewPaneHeader = new JPanel(); previewPaneHeader.setLayout(new BorderLayout()); previewPaneHeader.add(lblTitle, BorderLayout.WEST); previewPaneHeader.add(toolBar, BorderLayout.EAST); previewImgPanel = new PreviewImagePanel(); previewImgScrollPane = new JScrollPane(previewImgPanel); previewImgScrollPane.setAutoscrolls(false); previewImgScrollPane.setWheelScrollingEnabled(false); previewImgScrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS); previewImgScrollPane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS); scrollSupport = new ScrollPaneDragScrollSupport(previewImgScrollPane) { @Override protected void setCursor(Cursor cursor) { PreviewPanel.this.setCursor(cursor); } }; add(previewPaneHeader, BorderLayout.NORTH); layeredPane = new JLayeredPane(); layeredPane.setLayout(new OverlayLayout(layeredPane)); layeredPane.add(previewImgScrollPane, JLayeredPane.DEFAULT_LAYER); checkInfoLayerPanel = new CheckInfoLayerPanel(); layeredPane.add(checkInfoLayerPanel, JLayeredPane.POPUP_LAYER); checkInfoLayerPanel.setVisible(false); add(layeredPane, BorderLayout.CENTER); previewControlPanel = new PreviewControlPanel(); Dimension dim = previewControlPanel.getPreferredSize(); Dimension prevDim = previewImgScrollPane.getPreferredSize(); dim.width = prevDim.width; previewControlPanel.setPreferredSize(dim); add(previewControlPanel, BorderLayout.SOUTH); previewControlPanel.setPinned(appConfig.isEnableZoomPanel()); // 倍率が変更された場合 previewControlPanel.addPropertyChangeListener("zoomFactorInt", new PropertyChangeListener() { public void propertyChange(PropertyChangeEvent evt) { Integer newValue = (Integer) evt.getNewValue(); zoomWithCenterPosition(newValue.doubleValue() / 100., null); } }); // 背景モードが切り替えられた場合 previewControlPanel.addPropertyChangeListener("backgroundColorMode", new PropertyChangeListener() { public void propertyChange(PropertyChangeEvent evt) { BackgroundColorMode bgColorMode = (BackgroundColorMode) evt.getNewValue(); previewImgPanel.setBackgroundColorMode(bgColorMode); if (bgColorMode != BackgroundColorMode.ALPHABREND && appConfig.isEnableCheckInfoTooltip() ) { // チェック情報ツールチップの表示 checkInfoLayerPanel.setMessage(null); checkInfoLayerPanel.setVisible(true); } else { // チェック情報ツールチップの非表示 checkInfoLayerPanel.setVisible(false); } } }); previewImgScrollPane.addMouseMotionListener(new MouseMotionAdapter() { @Override public void mouseMoved(MouseEvent e) { Rectangle rct = previewImgScrollPane.getBounds(); int y = e.getY(); if (y > rct.height - appConfig.getZoomPanelActivationArea()) { previewControlPanel.setVisible(true); } else { if ( !previewControlPanel.isPinned()) { previewControlPanel.setVisible(false); } } } }); // 標準のホイールリスナは削除する. for (final MouseWheelListener listener : previewImgScrollPane.getMouseWheelListeners()) { previewImgScrollPane.removeMouseWheelListener(listener); } previewImgScrollPane.addMouseWheelListener(new MouseWheelListener() { public void mouseWheelMoved(MouseWheelEvent e) { if ((Main.isMacOSX() && e.isAltDown()) || ( !Main.isMacOSX() && e.isControlDown())) { // Mac OS XならOptionキー、それ以外はコントロールキーとともにホイールスクロールの場合 zoomByWheel(e); } else { // ズーム以外のホイール操作はスクロールとする. scrollByWheel(e); } // 現在画像位置の情報の更新 updateCheckInfoMessage(e.getPoint()); } }); previewImgScrollPane.addMouseListener(new MouseAdapter() { @Override public void mousePressed(MouseEvent e) { if (e.getClickCount() == 2) { // ダブルクリック // (正確に2回目。3回目以降はダブルクリック + シングルクリック) toggleZoom(e.getPoint()); } else { scrollSupport.drag(true, e.getPoint()); } } @Override public void mouseReleased(MouseEvent e) { scrollSupport.drag(false, e.getPoint()); } }); previewImgScrollPane.addMouseMotionListener(new MouseMotionListener() { public void mouseMoved(MouseEvent e) { updateCheckInfoMessage(e.getPoint()); } public void mouseDragged(MouseEvent e) { scrollSupport.dragging(e.getPoint()); // 現在画像位置の情報の更新 updateCheckInfoMessage(e.getPoint()); } }); } /** * 倍率を切り替える. */ protected void toggleZoom(Point mousePos) { if (previewImgPanel.isDefaultZoom()) { // 等倍であれば以前の倍率を適用する. zoomWithCenterPosition(latestToggleZoom, mousePos); } else { // 等倍でなければ現在の倍率を記憶して等倍にする. double currentZoomFactor = previewImgPanel.getZoomFactor(); latestToggleZoom = currentZoomFactor; zoomWithCenterPosition(1., mousePos); } } /** * マウス位置に対して画像情報のツールチップを表示する * * @param mousePosition * マウス位置 */ protected void updateCheckInfoMessage(Point mousePosition) { if ( !checkInfoLayerPanel.isVisible()) { return; } // マウス位置から画像位置を割り出す Point imgPos = null; if (mousePosition != null) { Point panelPt = SwingUtilities.convertPoint(previewImgScrollPane, mousePosition, previewImgPanel); imgPos = previewImgPanel.getImagePosition(panelPt); } if (imgPos != null) { // 画像位置があれば、その位置の情報を取得する. int argb = previewImgPanel.getImageARGB(imgPos); int a = (argb >> 24) & 0xff; int r = (argb >> 16) & 0xff; int g = (argb >> 8) & 0xff; int b = argb & 0xff; int y = (int) (0.298912f * r + 0.586611f * g + 0.114478f * b); String text = String.format( "(%3d,%3d)¥nA:%3d, Y:%3d¥nR:%3d, G:%3d, B:%3d", imgPos.x, imgPos.y, a, y, r, g, b); checkInfoLayerPanel.setMessage(text); checkInfoLayerPanel.setPotision(mousePosition); } else { // 画像位置がなければツールチップは空にする. checkInfoLayerPanel.setMessage(null); } } /** * マウス座標単位で指定したオフセット分スクロールする. * * @param diff_x * 水平方向スクロール数 * @param diff_y * 垂直方向スクロール数 */ protected void scroll(int diff_x, int diff_y) { scrollSupport.scroll(diff_x, diff_y); } /** * マウスホイールによる水平・垂直スクロール.
* シフトキーで水平、それ以外は垂直とする.
* * @param e * ホイールイベント */ protected void scrollByWheel(final MouseWheelEvent e) { scrollSupport.scrollByWheel(e); // イベントは処理済みとする. e.consume(); } /** * ホイールによる拡大縮小.
* ホイールの量は関係なく、方向だけで判定する.
* プラットフォームごとに修飾キーの判定が異なるので、 呼び出しもとであらかじめ切り分けて呼び出すこと.
* * @param e * ホイールイベント */ protected void zoomByWheel(final MouseWheelEvent e) { int wheelRotation = e.getWheelRotation(); double currentZoom = previewImgPanel.getZoomFactor(); double zoomFactor; if (wheelRotation < 0) { // ホイール上で拡大 zoomFactor = currentZoom * 1.1; } else if (wheelRotation > 0){ // ホイール下で縮小 zoomFactor = currentZoom * 0.9; } else { return; } // 倍率変更する zoomWithCenterPosition(zoomFactor, e.getPoint()); // イベント処理済み e.consume(); } /** * ズームスライダまたはコンボのいずれかの値を更新すると、他方からも更新通知があがるため 二重処理を防ぐためのセマフォ.
*/ private Semaphore zoomLock = new Semaphore(1); /** * プレビューに表示する画像の倍率を更新する.
* 指定した座標が拡大縮小の中心点になるようにスクロールを試みる.
* 座標がnullの場合は現在表示されている中央を中心とするようにスクロールを試みる.
* (スクロールバーが表示されていない、もしくは十分にスクロールできない場合は必ずしも中心とはならない.)
* コントロールパネルの表示値も更新する.
* コントロールパネルからの更新通知をうけて再入しないように、 同時に一つしか実行されないようにしている.
* * @param zoomFactor * 倍率、範囲外のものは範囲内に補正される. * @param mousePos * スクロールペイン上のマウス座標、もしくはnull(nullの場合は表示中央) */ protected void zoomWithCenterPosition(double zoomFactor, Point mousePos) { if ( !zoomLock.tryAcquire()) { return; } try { // 範囲制限. if (zoomFactor < 0.2) { zoomFactor = 0.2; } else if (zoomFactor > 8.) { zoomFactor = 8.; } JViewport vp = previewImgScrollPane.getViewport(); Point viewCenter; if (mousePos != null) { // スクロールペインのマウス座標を表示パネルの位置に換算する. viewCenter = SwingUtilities.convertPoint(this, mousePos, previewImgPanel); } else { // 表示パネル上の現在表示しているビューポートの中央の座標を求める Rectangle viewRect = vp.getViewRect(); viewCenter = new Point( (viewRect.x + viewRect.width / 2), (viewRect.y + viewRect.height / 2) ); } // 現在のビューサイズ(余白があれば余白も含む) Dimension viewSize = previewImgPanel.getScaledSize(true); // 倍率変更 previewControlPanel.setZoomFactor(zoomFactor); previewImgPanel.setZoomFactor(zoomFactor); // 新しいのビューサイズ(余白があれば余白も含む) Dimension viewSizeAfter = previewImgPanel.getScaledSize(true); Dimension visibleSize = vp.getExtentSize(); if (viewSize != null && viewSizeAfter != null && viewSizeAfter.width > 0 && viewSizeAfter.height > 0 && viewSizeAfter.width > visibleSize.width && viewSizeAfter.height > visibleSize.height) { // 新しいビューの大きさよりも表示可能領域が小さい場合のみ vp.setViewSize(viewSizeAfter); // スクロールペインに表示されている画面サイズを求める. // スクロールバーがある方向は、コンテンツの最大と等しいが // スクロールバーがない場合は画面サイズのほうが大きいため、 // 倍率変更による縦横の移動比は、それぞれ異なる. int visible_width = max(visibleSize.width, viewSize.width); int visible_height = max(visibleSize.height, viewSize.height); int visible_width_after = max(visibleSize.width, viewSizeAfter.width); int visible_height_after = max(visibleSize.height, viewSizeAfter.height); // 前回の倍率から今回の倍率の倍率. // オリジナルに対する倍率ではない. // また、画像は縦横同率であるが表示ウィンドウはスクロールバー有無により同率とは限らない. double zoomDiffX = (double) visible_width_after / (double) visible_width; double zoomDiffY = (double) visible_height_after / (double) visible_height; // 拡大後の座標の補正 Point viewCenterAfter = new Point(); viewCenterAfter.x = (int) round(viewCenter.x * zoomDiffX); viewCenterAfter.y = (int) round(viewCenter.y * zoomDiffY); // 倍率適用前後の座標の差分 int diff_x = viewCenterAfter.x - viewCenter.x; int diff_y = viewCenterAfter.y - viewCenter.y; // スクロール scroll(diff_x, diff_y); } // スクロールの単位を画像1ドットあたりの表示サイズに変更する. // (ただし1を下回らない) JScrollBar vsb = previewImgScrollPane.getVerticalScrollBar(); JScrollBar hsb = previewImgScrollPane.getHorizontalScrollBar(); vsb.setUnitIncrement(max(1, (int) ceil(zoomFactor))); hsb.setUnitIncrement(max(1, (int) ceil(zoomFactor))); } finally { zoomLock.release(); } } /** * プレビューに表示するタイトル.
* * @param title * タイトル */ public void setTitle(String title) { if (title == null) { title = ""; } if (!title.equals(this.title)) { this.title = title; lblTitle.setText(title + (indicatorShown ? indicatorText : "")); lblTitle.setToolTipText(title); } } public String getTitle() { return this.title; } /** * ロードに時間がかかっているか判定し、 インジケータを表示するためのタイマーイベントハンドラ.
*/ protected void onTimer() { boolean waiting; long firstRequest; synchronized (lock) { waiting = isWaiting(); firstRequest = firstWaitingTimestamp; } boolean indicatorShown = (waiting && ((System.currentTimeMillis() - firstRequest) > indicatorDelay)); if (this.indicatorShown != indicatorShown) { this.indicatorShown = indicatorShown; lblTitle.setText(title + (indicatorShown ? indicatorText : "")); } } /** * チケットの状態が、ロード完了待ち状態であるか?
* ロード中のチケットが、ロード完了のチケットより新しければロード中と見なす.
* * @return 完了待ちであればtrue、そうでなければfalse */ protected boolean isWaiting() { synchronized (lock) { return loadingTicket > loadedTicket; } } /** * ロード要求が出されるたびに、そのロード要求チケットを登録する.
* チケットは要求されるたびに増加するシーケンスとする.
* * @param ticket * ロード要求チケット */ public void setLoadingRequest(long ticket) { synchronized (lock) { if ( !isWaiting() && this.loadedTicket < ticket) { // 現在認識しているチケットの状態がロード完了であり、 // それよりも新しいチケットが要求されたならば、 // 今回のチケットから待ち時間の計測を開始する. this.firstWaitingTimestamp = System.currentTimeMillis(); } this.loadingTicket = ticket; } } /** * ロード完了するたびに呼び出される.
* * @param ticket * ロード要求チケット. */ public void setLoadingComplete(long ticket) { synchronized (lock) { this.loadedTicket = ticket; } } /** * 表示画像を設定する.
* * @param previewImg * 表示画像、もしくはnull */ public void setPreviewImage(BufferedImage previewImg) { previewImgPanel.setPreviewImage(previewImg); } /** * 表示されている画像を取得する.
* 表示画像が設定されていなければnull.
* * @return 表示画像、もしくはnull */ public BufferedImage getPreviewImage() { return previewImgPanel.getPreviewImage(); } /** * 表示している画面イメージそのままを取得する. * * @return 表示画像 */ public BufferedImage getScreenImage() { JViewport vp = previewImgScrollPane.getViewport(); Dimension dim = vp.getExtentSize(); BufferedImage img = new BufferedImage(dim.width, dim.height, BufferedImage.TYPE_INT_ARGB); Graphics2D g = img.createGraphics(); try { vp.paint(g); } finally { g.dispose(); } return img; } /** * 壁紙を設定する.
* * @param wallpaperImg * 壁紙、null不可 */ public void setWallpaper(Wallpaper wallpaper) { previewImgPanel.setWallpaper(wallpaper); } /** * 壁紙を取得する.
* 壁紙が未設定の場合は空の壁紙インスタンスが返される.
* * @return 壁紙 */ public Wallpaper getWallpaper() { return previewImgPanel.getWallpaper(); } /** * 表示倍率を取得する. * * @return 表示倍率 */ public double getZoomFactor() { return previewControlPanel.getZoomFactor(); } /** * 表示倍率を設定する * * @param zoomFactor * 表示倍率 */ public void setZoomFactor(double zoomFactor) { previewControlPanel.setZoomFactor(zoomFactor); } /** * ズームパネルのピン留め制御 * * @param visible * 表示する場合はtrue */ public void setVisibleZoomBox(boolean visible) { previewControlPanel.setPinned(visible); } /** * ズームパネルがピン留めされているか? * * @return ピン留めされていればtrue */ public boolean isVisibleZoomBox() { return previewControlPanel.isPinned(); } public void addPreviewPanelListener(PreviewPanelListener listener) { if (listener == null) { throw new IllegalArgumentException(); } listeners.add(listener); } public void removePreviewPanelListener(PreviewPanelListener listener) { listeners.remove(listener); } protected void savePicture(PreviewPanelEvent e) { for (PreviewPanelListener listener : listeners) { listener.savePicture(e); } } protected void flipHolizontal(PreviewPanelEvent e) { for (PreviewPanelListener listener : listeners) { listener.flipHorizontal(e); } } protected void copyPicture(PreviewPanelEvent e) { for (PreviewPanelListener listener : listeners) { listener.copyPicture(e); } } protected void changeBackgroundColor(PreviewPanelEvent e) { for (PreviewPanelListener listener : listeners) { listener.changeBackgroundColor(e); } } protected void showInformation(PreviewPanelEvent e) { for (PreviewPanelListener listener : listeners) { listener.showInformation(e); } } protected void addFavorite(PreviewPanelEvent e) { for (PreviewPanelListener listener : listeners) { listener.addFavorite(e); } } } /** * チェック情報の表示用レイヤーパネル.
* * @author seraphy */ class CheckInfoLayerPanel extends JPanel { private static final long serialVersionUID = 1L; /** * ロガー */ private static final Logger logger = Logger.getLogger(CheckInfoLayerPanel.class.getName()); /** * ボックスの余白 */ private Insets padding = new Insets(3, 3, 3, 3); /** * 表示位置プロパティ */ private Point pos = new Point(); /** * 表示メッセージプロパティ.
* ¥nで改行となる.
* 空文字ならば非表示.
*/ private String message = ""; /** * 解析済みメッセージ.
* 業に分割される.
* 空文字は空のリストとなる.
*/ private String[] messageLines; /** * 解析済みフォントの高さ.
*/ private int fontHeight; /** * 描画済みエリア.
* 次回描画前に消去する必要のある領域.
* まだ一度も描画してなければnull.
*/ private Rectangle eraseRect; /** * 現在、描画すべきエリア.
* なければnull.
*/ private Rectangle requestRect; /** * 画面に関連づけられていない状態でのテキスト表示サイズは 計算できないため、画面追加時に再計算させるための 予約フラグ.
*/ private boolean requestRecalcOnAdd; /** * フォントのためのデスクトップヒント.(あれば) */ @SuppressWarnings("rawtypes") private Map desktopHintsForFont; /** * 透明コンポーネントとして構築する.
*/ @SuppressWarnings("rawtypes") public CheckInfoLayerPanel() { setOpaque(false); Toolkit tk = Toolkit.getDefaultToolkit(); desktopHintsForFont = (Map) tk.getDesktopProperty("awt.font.desktophints"); logger.log(Level.CONFIG, "awt.font.desktophints=" + desktopHintsForFont); } /** * 指定エリアに情報を描画する.
*/ @Override protected void paintComponent(Graphics g0) { Graphics2D g = (Graphics2D) g0; super.paintComponent(g); // クリップ領域 Rectangle clip = g.getClipBounds(); // System.out.println("clip:" + clip + " /eraseRect:" + eraseRect + " /drawRect:" + requestRect); // 削除すべき領域が描画範囲に含まれているか? // (含まれていれば、その領域は消去済みである.) if (clip == null || (eraseRect != null && clip.contains(eraseRect))) { eraseRect = null; } // 表示領域の判定 if (requestRect == null || requestRect.isEmpty() || !(clip != null && clip.intersects(requestRect))) { // 表示すべき領域が存在しないか、描画要求範囲にない. return; } if (messageLines == null || messageLines.length == 0) { // 表示するものがなければ何もしない. return; } // フォントのレンダリングヒント if (desktopHintsForFont != null) { g.addRenderingHints(desktopHintsForFont); } // 箱の描画 g.setColor(new Color(255, 255, 255, 192)); g.fillRect(requestRect.x, requestRect.y, requestRect.width, requestRect.height); g.setColor(Color.GRAY); g.drawRect(requestRect.x, requestRect.y, requestRect.width - 1, requestRect.height - 1); // 情報の描画 g.setColor(Color.BLACK); int oy = fontHeight; for (String messageLine : messageLines) { g.drawString(messageLine, requestRect.x + padding.left, requestRect.y + padding.top - 1 + oy); oy += fontHeight; } // 描画された領域を次回消去領域として記憶する. if (eraseRect == null || eraseRect.isEmpty()) { // 消去済みであれば、今回分のみを次回消去領域とする. eraseRect = (Rectangle) requestRect.clone(); } else { // 消去済みエリアが未消去で残っている場合は // 今回領域を結合する. eraseRect.add(requestRect); } } /** * 画面にアタッチされた場合、描画領域の再計算が 必要であれば計算する.
*/ @Override public void addNotify() { super.addNotify(); if (requestRecalcOnAdd) { requestRecalcOnAdd = false; calcRepaint(); } } /** * 要求されたプロパティから、フォント高さによる表示領域を計算し、 その領域の再描画を要求する.(描画する内容がなれば、描画要求しない.)
* 前回表示領域があれば、消去するために、そのエリアも再描画を要求する.
* それ以外のエリアは描画要求しない.(描画の最適化による負荷軽減策)
* フォントサイズを求めるためにグラフィクスへのアクセスが必要となるが、 まだ取得できない場合は{@link #addNotify()}の呼び出し時に * 再計算するようにフラグを立てておく.
*/ protected void calcRepaint() { Graphics2D g = (Graphics2D) getGraphics(); if (g == null) { requestRecalcOnAdd = true; return; } try { // 前回描画領域のクリアのために呼び出す. if (eraseRect != null && !eraseRect.isEmpty()) { repaint(eraseRect); } // 空であれば新たな描画なし. if (message.length() == 0) { requestRect = null; return; } FontMetrics fm = g.getFontMetrics(); String[] messageLines = message.split("¥n"); Rectangle2D rct = null; for (String messageLine : messageLines) { Rectangle2D tmp = fm.getStringBounds(messageLine, g); if (rct != null) { rct.add(tmp); } else { rct = tmp; } } int fw = (int) rct.getWidth(); int fh = (int) rct.getHeight(); int w = fw + padding.left + padding.right; int h = fh * messageLines.length + padding.top + padding.bottom; // 指定した位置の右上あたりにする int x = pos.x + 16; int y = pos.y - h; // サイズ int client_w = getWidth(); int client_h = getHeight(); if (x + w > client_w) { // 画面右の場合はカーソルの左に移動 x = pos.x - w - 10; } if (y < 0) { // 画面上の場合はカーソルの下に移動 y = pos.y + 10; } if (y + h > client_h) { y -= (y + h - client_h); } // 結果の格納 this.requestRect = new Rectangle(x, y, w, h); this.messageLines = messageLines; this.fontHeight = fh; // 再描画の要求 Rectangle paintRect = (Rectangle) requestRect.clone(); repaint(paintRect); } finally { g.dispose(); } } public void setPotision(Point requestPt) { if (requestPt == null) { throw new IllegalArgumentException(); } if ( !requestPt.equals(pos)) { Point oldpos = pos; pos = (Point) requestPt.clone(); calcRepaint(); firePropertyChange("position", oldpos, pos); } } public Point getPosition() { return (Point) pos.clone(); } public void setMessage(String message) { if (message == null) { message = ""; } message = message.replace("¥r¥n", "¥n"); if ( !message.equals(this.message)) { String oldmes = this.message; this.message = message; calcRepaint(); firePropertyChange("message", oldmes, message); } } public String getMessage() { return message; } } /** * 画像表示パネル * * @author seraphy */ class PreviewImagePanel extends JPanel { private static final long serialVersionUID = 1L; /** * 背景モード.
*/ private BackgroundColorMode bgColorMode; /** * 壁紙.
*/ private Wallpaper wallpaper; /** * 壁紙変更イベントのリスナ */ private PropertyChangeListener wallpaperListener; /** * 透過オリジナル画像.
*/ private BufferedImage previewImg; /** * 表示用画像(背景モードによる調整あり).
* 事前に拡大縮小を適用済みの場合は、{@link #scaledZoomFactor}に 適用している倍率が設定される.
* 表示用に改めてイメージを生成する必要がない場合は、 透過オリジナルと同じインスタンスとなりえる.
*/ private BufferedImage previewImgForDraw; /** * 表示用画像がスケール済みである場合、そのスケールが設定される.
* スケール済みでない場合はnullとなる.
*/ private Double scaledZoomFactor; /** * 倍率 */ private double zoomFactor = 1.; /** * 許容誤差 */ private static final double TOLERANT = 0.001; /** * コンストラクタ */ public PreviewImagePanel() { super(); // 通常モード bgColorMode = BackgroundColorMode.ALPHABREND; // 壁紙変更通知リスナ wallpaperListener = new PropertyChangeListener() { public void propertyChange(PropertyChangeEvent evt) { onChangeWallpaper(); } }; // 壁紙 wallpaper = new Wallpaper(); wallpaper.addPropertyChangeListener(wallpaperListener); } /** * 画像を表示する. */ @Override protected void paintComponent(Graphics g0) { Graphics2D g = (Graphics2D) g0; super.paintComponent(g); if (previewImgForDraw == null) { return; } // 倍率を適用した画像を画面の中央に配置できるように計算する. // (画像が倍率適用済みであれば1倍とする) Rectangle imgRct = adjustImageRectangle(); // 表示用画像がスケール済みでない場合はレンダリングオプションを適用する. if (scaledZoomFactor == null) { Object renderingOption = getRenderingOption(); g.setRenderingHint(RenderingHints.KEY_INTERPOLATION, renderingOption); } // 背景処理 if (bgColorMode == BackgroundColorMode.ALPHABREND) { // 表示の最大範囲 (可視領域外も含む) int w = getWidth(); int h = getHeight(); wallpaper.drawWallpaper(g, w, h); } // レンダリング g.drawImage(previewImgForDraw, imgRct.x, imgRct.y, imgRct.x + imgRct.width, imgRct.y + imgRct.height, 0, 0, previewImgForDraw.getWidth(), previewImgForDraw.getHeight(), null); // 通常モード以外のグリッド描画に該当するモードはグリッドを前景に描く AppConfig appConfig = AppConfig.getInstance(); int drawGridMask = appConfig.getDrawGridMask(); if ((drawGridMask & bgColorMode.mask()) != 0) { Color oldc = g.getColor(); try { g.setColor(new Color(appConfig.getPreviewGridColor(), true)); drawGrid(g, imgRct.x, imgRct.y, appConfig.getPreviewGridSize()); } finally { g.setColor(oldc); } } } /** * グリッドを描画する.
* 開始位置の-1単位位置から画像サイズの+1単位までがグリッド範囲となる。 * * @param g * @param offset_x * 開始位置 * @param offset_y * 開始位置 * @param unit * グリッド単位(pixel) */ protected void drawGrid(Graphics2D g, int offset_x, int offset_y, int unit) { Rectangle clip = g.getClipBounds(); int src_w = previewImg.getWidth(); int src_h = previewImg.getHeight(); int my = src_h / unit; int mx = src_w / unit; int st_x = offset_x + (int)(-1 * unit * zoomFactor); int en_x = offset_x + (int)((mx + 1) * unit * zoomFactor); int w = en_x - st_x + 1; for (int y = -1; y <= my + 1; y++) { int y1 = y * unit; Rectangle rct = new Rectangle( st_x, offset_y + (int)(y1 * zoomFactor), w, 1); if (clip == null || clip.intersects(rct)) { g.drawLine(rct.x, rct.y, rct.x + rct.width, rct.y); } } int st_y = offset_y + (int)(-1 * unit * zoomFactor); int en_y = offset_y + (int)((my + 1) * unit * zoomFactor); int h = en_y - st_y + 1; for (int x = -1; x <= mx + 1; x++) { int x1 = x * unit; Rectangle rct = new Rectangle( offset_x + (int)(x1 * zoomFactor), st_y, 1, h); g.drawLine(rct.x, rct.y, rct.x, rct.y + rct.height); } } /** * 現在の倍率に応じたレンダリングオプションを取得する.
* * @return レンダリングオプション */ protected Object getRenderingOption() { AppConfig appConfig = AppConfig.getInstance(); double rendringOptimizeThreshold; if (bgColorMode == BackgroundColorMode.ALPHABREND) { rendringOptimizeThreshold = appConfig.getRenderingOptimizeThresholdForNormal(); } else { rendringOptimizeThreshold = appConfig.getRenderingOptimizeThresholdForCheck(); } Object renderingHint; if (zoomFactor < rendringOptimizeThreshold) { // 補正を適用する最大倍率以内である場合 if (zoomFactor <= 1. || !appConfig.isEnableInterpolationBicubic()) { // 縮小する場合、もしくはバイキュービックをサポートしない場合 renderingHint = RenderingHints.VALUE_INTERPOLATION_BILINEAR; } else { // 拡大する場合でバイキュービックをサポートしている場合 renderingHint = RenderingHints.VALUE_INTERPOLATION_BICUBIC; } } else { // 補正を適用する最大倍率を超えている場合は補正なし. renderingHint = RenderingHints.VALUE_INTERPOLATION_NEAREST_NEIGHBOR; } return renderingHint; } /** * 倍率と、画面のサイズと、表示するオリジナルの画像サイズをもとに、 倍率を適用した画像サイズを、画面に収まる位置に補正して返す.
* 返される矩形の幅と高さ(width, height)は拡大後の画像サイズに等しい.
* 拡大後の画像が画面よりも小さければセンタリングするように矩形の開始位置(x, y)がオフセットされる.
* そうでなければ矩形の開始位置(x, y)は0となる.
* 画像が設定されていなければ幅と高さがゼロの矩形が返される.
* * @return 画像を表示するオフセットと大きさ、もしくは空の矩形 */ public Rectangle adjustImageRectangle() { if (previewImg == null) { return new Rectangle(0, 0, 0, 0); // 幅・高さともにゼロ } int client_w = getWidth(); int client_h = getHeight(); int src_w = previewImg.getWidth(); int src_h = previewImg.getHeight(); int w = (int) round(src_w * zoomFactor); int h = (int) round(src_h * zoomFactor); int offset_x = 0; if (w < client_w) { offset_x = (client_w - w) / 2; } int offset_y = 0; if (h < client_h) { offset_y = (client_h - h) / 2; } return new Rectangle(offset_x, offset_y, w, h); } /** * パネルのマウス座標から、実寸の画像のピクセル位置を返す.
* 画像が表示されていないか、範囲外であればnullを返す.
* * @param pt * パネルの座標 * @return 画像の位置、もしくはnull */ public Point getImagePosition(Point pt) { if (pt == null || previewImg == null) { // プレビュー画像が設定されていなければnull return null; } Rectangle imgRct = adjustImageRectangle(); if ( !imgRct.contains(pt.x, pt.y)) { // 範囲外であればnull return null; } // オフセットを除去する. Point ret = (Point) pt.clone(); ret.x -= imgRct.x; ret.y -= imgRct.y; // 倍率を解除する. ret.x = (int) floor(ret.x / zoomFactor); ret.y = (int) floor(ret.y / zoomFactor); return ret; } /** * 画像の位置から画面の位置を割り出す.
* * @param pt * 画像の位置 * @return 画面の位置 */ public Point getMousePosition(Point pt) { if (pt == null || previewImg == null) { // プレビュー画像が設定されていなければnull return null; } Rectangle imgRct = adjustImageRectangle(); // 表示倍率を加える Point ret = (Point) pt.clone(); ret.x = (int) ceil(ret.x * zoomFactor); ret.y = (int) ceil(ret.y * zoomFactor); // オフセットを加える ret.x += imgRct.x; ret.y += imgRct.y; return ret; } /** * 指定した位置のRGB値を取得する.
* 範囲外の場合は0が返される.
* * @param pt * イメージの位置 * @return イメージのARGB値 (ビット順序は、A:24, R:16, G:8, B:0) */ public int getImageARGB(Point pt) { if (pt == null) { throw new IllegalArgumentException(); } try { return previewImg.getRGB(pt.x, pt.y); } catch (RuntimeException ex) { return 0; // 範囲外 } } /** * 倍率を適用した画像パネルのサイズを計算し適用する.
* モードにより余白が加えられる.
*/ protected void recalcScaledSize() { Dimension scaledSize = getScaledSize(true); if (scaledSize != null) { setPreferredSize(scaledSize); revalidate(); } } /** * 元画像の倍率適用後のサイズを返す.
* 元画像が設定されていなければnull.
* needOffsetがfalseであれば表示モードに関わらず、画像の拡大・縮小後の純粋なサイズ、 * trueであれば余白が必要な表示モードの場合の余白が付与された場合のサイズが返される.
* * @param needOffset * 余白を加味したサイズが必要な場合はtrue * @return 倍率適用後のサイズ、もしくはnull */ protected Dimension getScaledSize(boolean needOffset) { if (previewImg == null) { return null; } int src_w = previewImg.getWidth(); int src_h = previewImg.getHeight(); int w = (int) round(src_w * zoomFactor); int h = (int) round(src_h * zoomFactor); Dimension scaledSize = new Dimension(w, h); if (bgColorMode != BackgroundColorMode.ALPHABREND) { // 通常モード以外は画像よりも少し大きめにすることで // キャンバスに余白をつける AppConfig appConfig = AppConfig.getInstance(); int unfilledSpace = appConfig.getPreviewUnfilledSpaceForCheckMode(); scaledSize.width += max(0, unfilledSpace * 2); scaledSize.height += max(0, unfilledSpace * 2); } return scaledSize; } /** * プレビュー画像を設定する. * * @param previewImg */ public void setPreviewImage(BufferedImage previewImg) { BufferedImage oldimg = this.previewImg; this.previewImg = previewImg; recalcScaledSize(); makeDrawImage(true); repaint(); firePropertyChange("previewImage", oldimg, previewImg); } public BufferedImage getPreviewImage() { return previewImg; } /** * 壁紙を設定する. * * @param wallpaper */ public void setWallpaper(Wallpaper wallpaper) { if (wallpaper == null) { throw new IllegalArgumentException(); } if ( !this.wallpaper.equals(wallpaper)) { Wallpaper wallpaperOld = this.wallpaper; if (wallpaperOld != null) { wallpaperOld.removePropertyChangeListener(wallpaperListener); } this.wallpaper = wallpaper; if (this.wallpaper != null) { this.wallpaper.addPropertyChangeListener(wallpaperListener); } firePropertyChange("wallpaper", wallpaperOld, this.wallpaper); onChangeWallpaper(); } } public Wallpaper getWallpaper() { return wallpaper; } protected void onChangeWallpaper() { repaint(); } /** * 背景モード調整済みの表示用画像を作成する. * * @param changeImage * 画像の変更あり */ protected void makeDrawImage(boolean changeImage) { if (previewImg == null) { // 画像が設定されていなければ空 this.previewImgForDraw = null; scaledZoomFactor = null; return; } BufferedImage img; if (changeImage || scaledZoomFactor != null) { // 画像が変更されているか、スケール済みであれば // 背景モードの再適用が必要. if (bgColorMode == BackgroundColorMode.ALPHABREND) { // アルファブレンド通常モードは背景用にあえて作成する必要はない. img = previewImg; } else { // アルファブレンド通常モード以外は背景に作成する Color bgColor = wallpaper.getBackgroundColor(); BackgroundColorFilter bgColorFilter = new BackgroundColorFilter(bgColorMode, bgColor); img = bgColorFilter.filter(previewImg, null); } } else { // 画像が変更されておらず、スケール済みでもなければ // すでに作成済みの画像が使用できる. img = previewImgForDraw; } // レンダリングオプション Object renderingOption = getRenderingOption(); // バイキュービックでなければ、事前の拡大縮小は行わずに、表示時に行う. if ( !renderingOption.equals(RenderingHints.VALUE_INTERPOLATION_BICUBIC)) { previewImgForDraw = img; scaledZoomFactor = null; return; } // バイキュービックの場合、倍率を適用したサイズに予め加工しておく. Dimension scaledSize = getScaledSize(false); BufferedImage offscreen = new BufferedImage( scaledSize.width, scaledSize.height, BufferedImage.TYPE_INT_ARGB); Graphics2D g = offscreen.createGraphics(); try { g.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BICUBIC); g.drawImage(img, 0, 0, scaledSize.width, scaledSize.height, 0, 0, img.getWidth(), img.getHeight(), null); } finally { g.dispose(); } previewImgForDraw = offscreen; scaledZoomFactor = Double.valueOf(zoomFactor); } public void setBackgroundColorMode(BackgroundColorMode bgColorMode) { if (bgColorMode == null) { throw new IllegalArgumentException(); } if (this.bgColorMode != bgColorMode) { BackgroundColorMode oldcm = bgColorMode; this.bgColorMode = bgColorMode; makeDrawImage(true); recalcScaledSize(); repaint(); firePropertyChange("backgroundColorMode", oldcm, bgColorMode); } } public BackgroundColorMode setBackgroundColorMode() { return bgColorMode; } public void setZoomFactor(double zoomFactor) { if (abs(zoomFactor - this.zoomFactor) > TOLERANT) { // 0.001未満の差異は誤差とみなして反映しない. double oldzoom = this.zoomFactor; this.zoomFactor = zoomFactor; recalcScaledSize(); makeDrawImage(false); repaint(); firePropertyChange("zoomFactor", oldzoom, zoomFactor); } } public double getZoomFactor() { return zoomFactor; } /** * 倍率が100%であるか? * * @return 100%であればtrue */ public boolean isDefaultZoom() { return zoomFactor - 1 < TOLERANT; } } /** * 倍率・背景モードを操作するための下部パネル用 * * @author seraphy */ class PreviewControlPanel extends JPanel { private static final long serialVersionUID = 1L; private static final Logger logger = Logger.getLogger(PreviewControlPanel.class.getName()); protected static final String STRINGS_RESOURCE = "languages/previewpanel"; /** * ピン留めチェックボックス */ private JCheckBox chkPinning; /** * アルファ確認チェックボックス */ private JCheckBox chkNoAlpha; /** * グレースケール確認チェックボックス */ private JCheckBox chkGrayscale; /** * 倍率用スライダ */ private JSlider zoomSlider; /** * 倍率入力用コンボボックス */ private JComboBox zoomCombo; /** * スライダの最小値 */ private static final int MIN_INDEX = -170; /** * スライダの最大値 */ private static final int MAX_INDEX = 219; /** * 最小倍率 */ private double minimumZoomFactor; /** * 最大倍率 */ private double maximumZoomFactor; /** * 現在の倍率(100倍済み) */ private int currentZoomFactorInt; /** * 現在の背景色モード */ private BackgroundColorMode backgroundColorMode; /** * 任意の底Aをもつ対数 logA(N)を計算して返す. * * @param a * 底 * @param x * 引数 * @return logA(N) */ private static double logN(double a, double x) { return log(x) / log(a); } /** * 倍率(等倍を1とする)に対するスライダのインデックス値を返す.
* スライダは10ステップごとに前のステップの10%づつ増減する.(複利式)
* * @param zoomFactor * 倍率(1を等倍とする) * @return インデックス */ private static int zoomFactorToIndex(double zoomFactor) { return (int) round(logN((1. + 0.1), zoomFactor) * 10); } /** * スライダのインデックス値から倍率(等倍を1とする)を返す.
* 10ステップごとに10%づつ増減する.(複利式)
* * @param index * インデックス * @return 倍率(1を等倍とする) */ private static double zoomFactorFromIndex(int index) { return pow(1. + 0.1, index / 10.); } /** * コンストラクタ */ public PreviewControlPanel() { final Properties strings = LocalizedResourcePropertyLoader.getCachedInstance() .getLocalizedProperties(STRINGS_RESOURCE); UIHelper uiHelper = UIHelper.getInstance(); // ピンアイコン Icon pinIcon = uiHelper.createTwoStateIcon( "icons/pin-icon1.png", "icons/pin-icon2.png"); // ピンチェックボックス chkPinning = new JCheckBox(pinIcon); chkPinning.setToolTipText(strings.getProperty("tooltip.zoompanel.pinning")); // 円ボタン型チェックボックス用アイコンの実装 final Icon stateIcon = new Icon() { public int getIconHeight() { return 12; } public int getIconWidth() { return 6; }; public void paintIcon(Component c, Graphics g, int x, int y) { boolean sw = false; if (c instanceof AbstractButton) { AbstractButton btn = (AbstractButton) c; sw = btn.isSelected(); } int w = getIconWidth(); int h = getIconHeight(); int s = min(w, h); int ox = 0; int oy = 0; if (w > s) { ox = (w - s) / 2; } if (h > s) { oy = (h - s) / 2; } if (sw) { AppConfig appConfig = AppConfig.getInstance(); Color fillColor = appConfig.getSelectedItemBgColor(); g.setColor(fillColor); g.fillOval(x + ox, y + oy, s, w); } g.setColor(Color.GRAY); g.drawOval(x + ox, y + oy, s, s); } }; // アルファ確認とグレースケール確認用のチェックボックス chkNoAlpha = new JCheckBox(stateIcon); chkGrayscale = new JCheckBox(stateIcon); chkNoAlpha.setToolTipText(strings.getProperty("tooltip.zoompanel.checkalpha")); chkGrayscale.setToolTipText(strings.getProperty("tooltip.zoompanel.checkgrayscale")); backgroundColorMode = BackgroundColorMode.ALPHABREND; final ChangeListener chkAlphaGrayChangeListener = new ChangeListener() { public void stateChanged(ChangeEvent e) { onChangeCheckAlphaGray(); } }; chkNoAlpha.addChangeListener(chkAlphaGrayChangeListener); chkGrayscale.addChangeListener(chkAlphaGrayChangeListener); // 倍率スライダ zoomSlider = new JSlider(JSlider.HORIZONTAL, MIN_INDEX, MAX_INDEX, 0); zoomSlider.setToolTipText(strings.getProperty("tooltip.zoompanel.zoomfactor_slider")); // 倍率コンボ zoomCombo = new JComboBox(); zoomCombo.setToolTipText(strings.getProperty("tooltip.zoompanel.zoomfactor_combo")); // 倍率の既定リストの設定と、最大・最小値の算定 minimumZoomFactor = zoomFactorFromIndex(zoomSlider.getMinimum()); maximumZoomFactor = zoomFactorFromIndex(zoomSlider.getMaximum()); int minZoomRange = (int) round(minimumZoomFactor * 100.); int maxZoomRange = (int) round(maximumZoomFactor * 100.); List predefinedZoomRanges = getPredefinedZoomRanges(); for (int zoomRange : predefinedZoomRanges) { if (zoomRange < minZoomRange) { minZoomRange = zoomRange; } if (zoomRange > maxZoomRange) { maxZoomRange = zoomRange; } zoomCombo.addItem(Integer.toString(zoomRange)); } final int[] zoomRanges = {minZoomRange, maxZoomRange}; currentZoomFactorInt = 100; zoomCombo.setSelectedItem(Integer.toString(currentZoomFactorInt)); zoomCombo.setEditable(true); if ( !Main.isMacOSX()) { // Windows環境だとデフォルトで9桁分のテキストフィールドが作成され // それがレイアウトの推奨サイズとして実際に使われてしまうため、 // 明示的に3桁にとどめておくようにオーバーライドする. // Mac OS Xならば問題ない. zoomCombo.setEditor(new BasicComboBoxEditor() { { editor = new JTextField(3) { private static final long serialVersionUID = 1L; @Override public void setBorder(Border border) { // 何もしない. } public void setText(String s) { if (getText().equals(s)) { return; } super.setText(s); } }; } }); } // スライダを変更することによりコンボボックスを変更する、 // もしくはコンボボックスを変更することでスライダを変更するが、 // 互いに通知を呼び合うことになるため他方を無視するためのセマフォ final Semaphore changeLock = new Semaphore(1); zoomCombo.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { boolean adjusted = false; String value = (String) zoomCombo.getSelectedItem(); int zoomFactorInt; try { zoomFactorInt = Integer.parseInt(value); if (zoomFactorInt < zoomRanges[0]) { zoomFactorInt = zoomRanges[0]; adjusted = true; } else if (zoomFactorInt > zoomRanges[1]) { zoomFactorInt = zoomRanges[1]; adjusted = true; } } catch (RuntimeException ex) { zoomFactorInt = 100; adjusted = true; } if (adjusted) { zoomCombo.setSelectedItem(Integer.toString(zoomFactorInt)); Toolkit tk = Toolkit.getDefaultToolkit(); tk.beep(); } if (changeLock.tryAcquire()) { try { zoomSlider.setValue(zoomFactorToIndex(zoomFactorInt / 100.)); } finally { changeLock.release(); } } fireZoomFactorChange(zoomFactorInt); } }); zoomSlider.addChangeListener(new ChangeListener() { public void stateChanged(ChangeEvent e) { int index = zoomSlider.getValue(); double zoomFactor = zoomFactorFromIndex(index); int zoomFactorInt = (int) round(zoomFactor * 100); if (changeLock.tryAcquire()) { try { zoomCombo.setSelectedItem(Integer.toString(zoomFactorInt)); } finally { changeLock.release(); } fireZoomFactorChange(zoomFactorInt); } } }); // パーツの配備 GridBagLayout gbl = new GridBagLayout(); setLayout(gbl); GridBagConstraints gbc = new GridBagConstraints(); gbc.gridx = 0; gbc.gridy = 0; gbc.ipadx = 0; gbc.ipady = 0; gbc.gridheight = 1; gbc.gridwidth = 1; gbc.fill = GridBagConstraints.NONE; gbc.anchor = GridBagConstraints.CENTER; gbc.insets = new Insets(0, 0, 0, 5); gbc.weightx = 0.; gbc.weighty = 0.; add(chkPinning, gbc); gbc.gridx = 1; gbc.weightx = 0.; gbc.insets = new Insets(0, 0, 0, 0); add(chkGrayscale, gbc); gbc.gridx = 2; gbc.weightx = 0.; gbc.insets = new Insets(0, 0, 0, 5); add(chkNoAlpha, gbc); gbc.gridx = 3; gbc.weightx = 1.; gbc.fill = GridBagConstraints.HORIZONTAL; add(zoomSlider, gbc); gbc.gridx = 4; gbc.weightx = 0.; gbc.insets = new Insets(3, 0, 3, 0); gbc.fill = GridBagConstraints.VERTICAL; add(zoomCombo, gbc); Integer scrollbarWidth = (Integer) UIManager.get("ScrollBar.width"); logger.log(Level.CONFIG, "ScrollBar.width=" + scrollbarWidth); if (scrollbarWidth == null) { scrollbarWidth = Integer.parseInt( strings.getProperty("uiconstraint.scrollbar.width")); } gbc.gridx = 5; gbc.weightx = 0.; gbc.anchor = GridBagConstraints.WEST; gbc.insets = new Insets(0, 0, 0, scrollbarWidth); add(new JLabel("%"), gbc); } /** * アプリケーション設定より事前定義済みの倍率候補を取得する * * @return 倍率候補のリスト(順序あり) */ protected List getPredefinedZoomRanges() { AppConfig appConfig = AppConfig.getInstance(); String strs = appConfig.getPredefinedZoomRanges(); TreeSet ranges = new TreeSet(); for (String str : strs.split(",")) { str = str.trim(); if (str.length() > 0) { try { int zoomFactor = Integer.parseInt(str); ranges.add(Integer.valueOf(zoomFactor)); } catch (RuntimeException ex) { // 無視する. } } } ranges.add(Integer.valueOf(100)); // 等倍は常に設定する. return new ArrayList(ranges); } /** * 倍率が変更されたことを通知する. */ protected void fireZoomFactorChange(int newZoomFactor) { if (currentZoomFactorInt != newZoomFactor) { int oldValue = currentZoomFactorInt; currentZoomFactorInt = newZoomFactor; firePropertyChange("zoomFactorInt", oldValue, newZoomFactor); } } private Semaphore changeChkLock = new Semaphore(1); protected void onChangeCheckAlphaGray() { changeChkLock.tryAcquire(); try { BackgroundColorMode backgroundColorMode = BackgroundColorMode.valueOf( chkNoAlpha.isSelected(), chkGrayscale.isSelected() ); setBackgroundColorMode(backgroundColorMode); } finally { changeChkLock.release(); } } public BackgroundColorMode getBackgroundColorMode() { return this.backgroundColorMode; } public void setBackgroundColorMode(BackgroundColorMode backgroundColorMode) { if (backgroundColorMode == null) { throw new IllegalArgumentException(); } BackgroundColorMode oldcm = this.backgroundColorMode; if (oldcm != backgroundColorMode) { this.backgroundColorMode = backgroundColorMode; changeChkLock.tryAcquire(); try { chkNoAlpha.setSelected(backgroundColorMode.isNoAlphaChannel()); chkGrayscale.setSelected(backgroundColorMode.isGrayscale()); } finally { changeChkLock.release(); } firePropertyChange("backgroundColorMode", oldcm, backgroundColorMode); } } public boolean isPinned() { return chkPinning.isSelected(); } public void setPinned(boolean pinned) { chkPinning.setSelected(pinned); if (isDisplayable()) { setVisible(pinned); } } public double getZoomFactor() { return (double) currentZoomFactorInt / 100.; } public void setZoomFactor(double zoomFactor) { if (zoomFactor < minimumZoomFactor) { zoomFactor = minimumZoomFactor; } if (zoomFactor > maximumZoomFactor) { zoomFactor = maximumZoomFactor; } int zoomFactorInt = (int) round(zoomFactor * 100.); if (zoomFactorInt != currentZoomFactorInt) { zoomCombo.setSelectedItem(Integer.toString(zoomFactorInt)); } } } CharacterManaJ/src/charactermanaj/CharacterManaJ.java0000644000175000017500000000074312560206305022774 0ustar paulliupaulliupackage charactermanaj; /** * Java7 on OSX で、クラス名がメニューの「〜の終了」「〜について」の起動クラス名がアプリ名に使われており、 * info.pinfoのBundleNameでも変更できないため、回避方法がみつかるまで、本クラス名を表示させることにする。 * * @author seraphy */ public class CharacterManaJ { public static void main(String[] args) throws Exception { Main.main(args); } } CharacterManaJ/src/charactermanaj/clipboardSupport/0000755000175000017500000000000012560206305022656 5ustar paulliupaulliuCharacterManaJ/src/charactermanaj/clipboardSupport/ImageSelection.java0000644000175000017500000002035112560206305026412 0ustar paulliupaulliupackage charactermanaj.clipboardSupport; import java.awt.Color; import java.awt.datatransfer.Clipboard; import java.awt.datatransfer.DataFlavor; import java.awt.datatransfer.FlavorMap; import java.awt.datatransfer.SystemFlavorMap; import java.awt.datatransfer.Transferable; import java.awt.datatransfer.UnsupportedFlavorException; import java.awt.image.BufferedImage; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.util.Arrays; import java.util.List; import java.util.logging.Level; import java.util.logging.Logger; import javax.imageio.ImageIO; import charactermanaj.graphics.io.ImageSaveHelper; import charactermanaj.model.AppConfig; /** * クリップボードに画像をコピーするためのセレクション.
* @author seraphy */ public class ImageSelection implements Transferable { /** * ロガー */ private static final Logger logger = Logger.getLogger(ImageSelection.class.getName()); /** * 実行環境がWindowsであるか? */ private static final boolean platformWindows; /** * 対象となるイメージ */ private BufferedImage img; /** * 背景色(jpeg画像変換時) */ private Color bgColor; /** * MIME汎用(PNG). */ private static final DataFlavor PNG_FLAVOR = new DataFlavor("image/png", "image/png"); /** * MIME汎用(JPEG). */ private static final DataFlavor JPEG_FLAVOR = new DataFlavor("image/jpeg", "image/jpeg"); /** * MIME汎用(BMP). */ private static final DataFlavor BMP_FLAVOR = new DataFlavor("image/bmp", "image/bmp"); /** * サポートされている形式.
* 順序は優先順.
*/ private static final List SUPPORTED_FLAVORS; /** * クラスイニシャライザ */ static { String lcOS = System.getProperty("os.name").toLowerCase(); platformWindows = lcOS.indexOf("windows") >= 0; if (platformWindows) { // Windowsの場合 SUPPORTED_FLAVORS = Arrays.asList(new DataFlavor[] { PNG_FLAVOR, DataFlavor.imageFlavor, }); } else { // Linux, Mac OS Xの場合を想定 SUPPORTED_FLAVORS = Arrays.asList(new DataFlavor[] { PNG_FLAVOR, JPEG_FLAVOR, BMP_FLAVOR, DataFlavor.imageFlavor, }); } } /** * システムのフレーバーマップを設定する.
* @return 正常にセットアップできた場合はtrue、そうでなければfalse */ public static boolean setupSystemFlavorMap() { try { AppConfig appConfig = AppConfig.getInstance(); if (appConfig.isEnablePNGSupportForWindows()) { // "PNG"へのマップを明示的に設定する. // (Windowsの場合、デフォルトでは、画像はDBI転送となり透過情報を持つことができないため.) FlavorMap defFlavorMap = SystemFlavorMap.getDefaultFlavorMap(); if (defFlavorMap instanceof SystemFlavorMap) { SystemFlavorMap sysFlavorMap = (SystemFlavorMap) defFlavorMap; sysFlavorMap.setNativesForFlavor(PNG_FLAVOR, new String[] {"PNG"}); sysFlavorMap.setNativesForFlavor(JPEG_FLAVOR, new String[] {"JFIF"}); } } return true; } catch (Exception ex) { logger.log(Level.SEVERE, "systemFlavorMap setup failed.", ex); } return false; } /** * セレクションを構築する. * @param img 対象となるイメージ */ public ImageSelection(BufferedImage img, Color bgColor) { if (img == null) { throw new IllegalArgumentException(); } this.img = img; this.bgColor = (bgColor == null) ? Color.white : bgColor; } public Object getTransferData(DataFlavor flavor) throws UnsupportedFlavorException, IOException { if (flavor != null) { logger.log(Level.FINE, "getTransferData flavor=" + flavor); try { ImageSaveHelper imageSaveHelper = new ImageSaveHelper(); if (flavor.equals(PNG_FLAVOR) || flavor.equals(JPEG_FLAVOR) || flavor.equals(BMP_FLAVOR)) { // image/png, image/jpeg, image/bmpの場合は、 // そのファイル形式のデータを生成して、それを返す. ByteArrayOutputStream bos = new ByteArrayOutputStream(); try { imageSaveHelper.savePicture(img, bgColor, bos, flavor.getMimeType(), null); } finally { bos.close(); } return new ByteArrayInputStream(bos.toByteArray()); } if (flavor.equals(DataFlavor.imageFlavor)) { // "image/x-java-image"の場合 AppConfig appConfig = AppConfig.getInstance(); if (platformWindows || !appConfig.isEnablePNGSupportForWindows()) { // Windowsの場合は、背景色で塗りつぶしたBMP画像に変換して返す. // JDK5/6のシステムクリップボードへのコピーでは透過画像をサポートしておらず透過部分が黒色になるため. // ネイティブPNGとのマッピングが有効であれば、Windowsでは、そちらで対応する. return imageSaveHelper.createBMPFormatPicture(img, bgColor); } else { // Windows以外、且つ、透過サポートが有効の場合 return img; } } } catch (RuntimeException ex) { logger.log(Level.WARNING, "The exception occurred during the data transfer of a clipboard.", ex); throw ex; } catch (IOException ex) { logger.log(Level.WARNING, "The exception occurred during the data transfer of a clipboard.", ex); throw ex; } } throw new UnsupportedFlavorException(flavor); } public DataFlavor[] getTransferDataFlavors() { return SUPPORTED_FLAVORS.toArray(new DataFlavor[SUPPORTED_FLAVORS.size()]); } public boolean isDataFlavorSupported(DataFlavor flavor) { return flavor != null && SUPPORTED_FLAVORS.contains(flavor); } public static boolean isSupprotedFlavorAvailable(Clipboard cb) { if (cb != null) { for (DataFlavor flavor : SUPPORTED_FLAVORS) { if (cb.isDataFlavorAvailable(flavor)) { return true; } } } return false; } public static BufferedImage getImage(Clipboard cb) throws IOException { if (cb == null) { return null; } try { // サポートされている形式をチェックする. for (DataFlavor flavor : cb.getAvailableDataFlavors()) { logger.log(Level.FINE, "dataFlavor(in Clipboard)=" + flavor); } DataFlavor availableFlavor = null; for (DataFlavor flavor : SUPPORTED_FLAVORS) { // 優先順にチェックし最初に見つかったサポートされている形式を採用する. if (cb.isDataFlavorAvailable(flavor)) { availableFlavor = flavor; break; } } logger.log(Level.FINE, "selected flavor=" + availableFlavor); if (availableFlavor != null) { if (availableFlavor.equals(DataFlavor.imageFlavor)) { // 汎用の画像形式で取得を試みる。 // 透過画像は使えないため、ここで取得されるものは非透過画像である。 return (BufferedImage) cb.getData(DataFlavor.imageFlavor); } if (availableFlavor.equals(PNG_FLAVOR) || availableFlavor.equals(JPEG_FLAVOR) || availableFlavor.equals(BMP_FLAVOR)) { // image/png, image/bmp, image/jpegのいずれか InputStream is = (InputStream) cb.getData(availableFlavor); if (is != null) { BufferedImage img; try { img = ImageIO.read(is); } finally { is.close(); } return img; } } } } catch (IOException ex) { logger.log(Level.WARNING, "The exception occurred in access to a clipboard.", ex); throw ex; } catch (UnsupportedFlavorException ex) { // 直前にisDataFlavorAvailableで確認しているので、 // よほどタイミングが悪くなければエラーは発生しないはず。 logger.log(Level.WARNING, "The exception occurred in access to a clipboard.", ex); throw new IOException(ex.getMessage()); } // サポートしているものが無い場合. return null; } } CharacterManaJ/src/charactermanaj/clipboardSupport/ClipboardUtil.java0000644000175000017500000000354112560206305026261 0ustar paulliupaulliupackage charactermanaj.clipboardSupport; import java.awt.Color; import java.awt.Toolkit; import java.awt.datatransfer.Clipboard; import java.awt.image.BufferedImage; import java.io.IOException; /** * クリップボード用ユーテリティクラス.
* @author seraphy */ public final class ClipboardUtil { private ClipboardUtil() { super(); } /** * クリップボードにイメージを設定する.
* JDKのクリップボード経由の画像転送では透過色を表現できないので、背景色を指定する必要がある.
* (ただし、このアプリケーション内であれば透過色を維持したままコピー可能.)
* @param img イメージ * @param bgColor 背景色 */ public static void setImage(BufferedImage img, Color bgColor) { if (img == null || bgColor == null) { throw new IllegalArgumentException(); } Toolkit tk = Toolkit.getDefaultToolkit(); Clipboard cb = tk.getSystemClipboard(); ImageSelection imageSelection = new ImageSelection(img, bgColor); cb.setContents(imageSelection, null); } /** * クリップボード内にイメージがあるか? * @return イメージがあればtrue */ public static boolean hasImage() { Toolkit tk = Toolkit.getDefaultToolkit(); Clipboard cb = tk.getSystemClipboard(); return ImageSelection.isSupprotedFlavorAvailable(cb); } /** * クリップボードからイメージを取得する.
* 取得できる形式がない場合はnullを返す.
* @return 画像、もしくはnull * @throws IOException 読み取り中に例外が発生した場合 */ public static BufferedImage getImage() throws IOException { Toolkit tk = Toolkit.getDefaultToolkit(); Clipboard cb = tk.getSystemClipboard(); return ImageSelection.getImage(cb); } } CharacterManaJ/src/org/0000755000175000017500000000000012560206305015146 5ustar paulliupaulliuCharacterManaJ/src/org/apache/0000755000175000017500000000000012560206305016367 5ustar paulliupaulliuCharacterManaJ/src/org/apache/tools/0000755000175000017500000000000012560206305017527 5ustar paulliupaulliuCharacterManaJ/src/org/apache/tools/zip/0000755000175000017500000000000012560206305020331 5ustar paulliupaulliuCharacterManaJ/src/org/apache/tools/zip/ZipEncodingHelper.java0000644000175000017500000002277212560206305024557 0ustar paulliupaulliu/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * */ package org.apache.tools.zip; import java.nio.ByteBuffer; import java.nio.charset.Charset; import java.nio.charset.UnsupportedCharsetException; import java.util.HashMap; import java.util.Map; /** * Static helper functions for robustly encoding filenames in zip files. */ @SuppressWarnings({ "unchecked", "rawtypes" }) abstract class ZipEncodingHelper { /** * A class, which holds the high characters of a simple encoding * and lazily instantiates a Simple8BitZipEncoding instance in a * thread-safe manner. */ private static class SimpleEncodingHolder { private final char [] highChars; private Simple8BitZipEncoding encoding; /** * Instantiate a simple encoding holder. * * @param highChars The characters for byte codes 128 to 255. * * @see Simple8BitZipEncoding#Simple8BitZipEncoding(char[]) */ SimpleEncodingHolder(char [] highChars) { this.highChars = highChars; } /** * @return The associated {@link Simple8BitZipEncoding}, which * is instantiated if not done so far. */ public synchronized Simple8BitZipEncoding getEncoding() { if (this.encoding == null) { this.encoding = new Simple8BitZipEncoding(this.highChars); } return this.encoding; } } private static final Map simpleEncodings; static { simpleEncodings = new HashMap(); char[] cp437_high_chars = new char[] { 0x00c7, 0x00fc, 0x00e9, 0x00e2, 0x00e4, 0x00e0, 0x00e5, 0x00e7, 0x00ea, 0x00eb, 0x00e8, 0x00ef, 0x00ee, 0x00ec, 0x00c4, 0x00c5, 0x00c9, 0x00e6, 0x00c6, 0x00f4, 0x00f6, 0x00f2, 0x00fb, 0x00f9, 0x00ff, 0x00d6, 0x00dc, 0x00a2, 0x00a3, 0x00a5, 0x20a7, 0x0192, 0x00e1, 0x00ed, 0x00f3, 0x00fa, 0x00f1, 0x00d1, 0x00aa, 0x00ba, 0x00bf, 0x2310, 0x00ac, 0x00bd, 0x00bc, 0x00a1, 0x00ab, 0x00bb, 0x2591, 0x2592, 0x2593, 0x2502, 0x2524, 0x2561, 0x2562, 0x2556, 0x2555, 0x2563, 0x2551, 0x2557, 0x255d, 0x255c, 0x255b, 0x2510, 0x2514, 0x2534, 0x252c, 0x251c, 0x2500, 0x253c, 0x255e, 0x255f, 0x255a, 0x2554, 0x2569, 0x2566, 0x2560, 0x2550, 0x256c, 0x2567, 0x2568, 0x2564, 0x2565, 0x2559, 0x2558, 0x2552, 0x2553, 0x256b, 0x256a, 0x2518, 0x250c, 0x2588, 0x2584, 0x258c, 0x2590, 0x2580, 0x03b1, 0x00df, 0x0393, 0x03c0, 0x03a3, 0x03c3, 0x00b5, 0x03c4, 0x03a6, 0x0398, 0x03a9, 0x03b4, 0x221e, 0x03c6, 0x03b5, 0x2229, 0x2261, 0x00b1, 0x2265, 0x2264, 0x2320, 0x2321, 0x00f7, 0x2248, 0x00b0, 0x2219, 0x00b7, 0x221a, 0x207f, 0x00b2, 0x25a0, 0x00a0 }; SimpleEncodingHolder cp437 = new SimpleEncodingHolder(cp437_high_chars); simpleEncodings.put("CP437",cp437); simpleEncodings.put("Cp437",cp437); simpleEncodings.put("cp437",cp437); simpleEncodings.put("IBM437",cp437); simpleEncodings.put("ibm437",cp437); char[] cp850_high_chars = new char[] { 0x00c7, 0x00fc, 0x00e9, 0x00e2, 0x00e4, 0x00e0, 0x00e5, 0x00e7, 0x00ea, 0x00eb, 0x00e8, 0x00ef, 0x00ee, 0x00ec, 0x00c4, 0x00c5, 0x00c9, 0x00e6, 0x00c6, 0x00f4, 0x00f6, 0x00f2, 0x00fb, 0x00f9, 0x00ff, 0x00d6, 0x00dc, 0x00f8, 0x00a3, 0x00d8, 0x00d7, 0x0192, 0x00e1, 0x00ed, 0x00f3, 0x00fa, 0x00f1, 0x00d1, 0x00aa, 0x00ba, 0x00bf, 0x00ae, 0x00ac, 0x00bd, 0x00bc, 0x00a1, 0x00ab, 0x00bb, 0x2591, 0x2592, 0x2593, 0x2502, 0x2524, 0x00c1, 0x00c2, 0x00c0, 0x00a9, 0x2563, 0x2551, 0x2557, 0x255d, 0x00a2, 0x00a5, 0x2510, 0x2514, 0x2534, 0x252c, 0x251c, 0x2500, 0x253c, 0x00e3, 0x00c3, 0x255a, 0x2554, 0x2569, 0x2566, 0x2560, 0x2550, 0x256c, 0x00a4, 0x00f0, 0x00d0, 0x00ca, 0x00cb, 0x00c8, 0x0131, 0x00cd, 0x00ce, 0x00cf, 0x2518, 0x250c, 0x2588, 0x2584, 0x00a6, 0x00cc, 0x2580, 0x00d3, 0x00df, 0x00d4, 0x00d2, 0x00f5, 0x00d5, 0x00b5, 0x00fe, 0x00de, 0x00da, 0x00db, 0x00d9, 0x00fd, 0x00dd, 0x00af, 0x00b4, 0x00ad, 0x00b1, 0x2017, 0x00be, 0x00b6, 0x00a7, 0x00f7, 0x00b8, 0x00b0, 0x00a8, 0x00b7, 0x00b9, 0x00b3, 0x00b2, 0x25a0, 0x00a0 }; SimpleEncodingHolder cp850 = new SimpleEncodingHolder(cp850_high_chars); simpleEncodings.put("CP850",cp850); simpleEncodings.put("Cp850",cp850); simpleEncodings.put("cp850",cp850); simpleEncodings.put("IBM850",cp850); simpleEncodings.put("ibm850",cp850); } /** * Grow a byte buffer, so it has a minimal capacity or at least * the double capacity of the original buffer * * @param b The original buffer. * @param newCapacity The minimal requested new capacity. * @return A byte buffer r with * r.capacity() = max(b.capacity()*2,newCapacity) and * all the data contained in b copied to the beginning * of r. * */ static ByteBuffer growBuffer(ByteBuffer b, int newCapacity) { b.limit(b.position()); b.rewind(); int c2 = b.capacity() * 2; ByteBuffer on = ByteBuffer.allocate(c2 < newCapacity ? newCapacity : c2); on.put(b); return on; } /** * The hexadecimal digits 0,...,9,A,...,F encoded as * ASCII bytes. */ private static final byte[] HEX_DIGITS = new byte [] { 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46 }; /** * Append %Uxxxx to the given byte buffer. * The caller must assure, that bb.remaining()>=6. * * @param bb The byte buffer to write to. * @param c The character to write. */ static void appendSurrogate(ByteBuffer bb, char c) { bb.put((byte) '%'); bb.put((byte) 'U'); bb.put(HEX_DIGITS[(c >> 12)&0x0f]); bb.put(HEX_DIGITS[(c >> 8)&0x0f]); bb.put(HEX_DIGITS[(c >> 4)&0x0f]); bb.put(HEX_DIGITS[c & 0x0f]); } /** * name of the encoding UTF-8 */ static final String UTF8 = "UTF8"; /** * variant name of the encoding UTF-8 used for comparisions. */ private static final String UTF_DASH_8 = "utf-8"; /** * name of the encoding UTF-8 */ static final ZipEncoding UTF8_ZIP_ENCODING = new FallbackZipEncoding(UTF8); /** * Instantiates a zip encoding. * * @param name The name of the zip encoding. Specify null for * the platform's default encoding. * @return A zip encoding for the given encoding name. */ static ZipEncoding getZipEncoding(String name) { // fallback encoding is good enough for utf-8. if (isUTF8(name)) { return UTF8_ZIP_ENCODING; } if (name == null) { return new FallbackZipEncoding(); } SimpleEncodingHolder h = (SimpleEncodingHolder) simpleEncodings.get(name); if (h!=null) { return h.getEncoding(); } try { Charset cs = Charset.forName(name); return new NioZipEncoding(cs); } catch (UnsupportedCharsetException e) { return new FallbackZipEncoding(name); } } /** * Whether a given encoding - or the platform's default encoding * if the parameter is null - is UTF-8. */ static boolean isUTF8(String encoding) { if (encoding == null) { // check platform's default encoding encoding = System.getProperty("file.encoding"); } return UTF8.equalsIgnoreCase(encoding) || UTF_DASH_8.equalsIgnoreCase(encoding); } } CharacterManaJ/src/org/apache/tools/zip/ZipEncoding.java0000644000175000017500000000633212560206305023411 0ustar paulliupaulliu/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ package org.apache.tools.zip; import java.io.IOException; import java.nio.ByteBuffer; /** * An interface for encoders that do a pretty encoding of ZIP * filenames. * *

There are mostly two implementations, one that uses java.nio * {@link java.nio.charset.Charset Charset} and one implementation, * which copes with simple 8 bit charsets, because java-1.4 did not * support Cp437 in java.nio.

* *

The main reason for defining an own encoding layer comes from * the problems with {@link java.lang.String#getBytes(String) * String.getBytes}, which encodes unknown characters as ASCII * quotation marks ('?'). Quotation marks are per definition an * invalid filename on some operating systems like Windows, which * leads to ignored ZIP entries.

* *

All implementations should implement this interface in a * reentrant way.

*/ interface ZipEncoding { /** * Check, whether the given string may be losslessly encoded using this * encoding. * * @param name A filename or ZIP comment. * @return Whether the given name may be encoded with out any losses. */ boolean canEncode(String name); /** * Encode a filename or a comment to a byte array suitable for * storing it to a serialized zip entry. * *

Examples for CP 437 (in pseudo-notation, right hand side is * C-style notation):

*
     *  encode("\u20AC_for_Dollar.txt") = "%U20AC_for_Dollar.txt"
     *  encode("\u00D6lf\u00E4sser.txt") = "\231lf\204sser.txt"
     * 
* * @param name A filename or ZIP comment. * @return A byte buffer with a backing array containing the * encoded name. Unmappable characters or malformed * character sequences are mapped to a sequence of utf-16 * words encoded in the format %Uxxxx. It is * assumed, that the byte buffer is positioned at the * beinning of the encoded result, the byte buffer has a * backing array and the limit of the byte buffer points * to the end of the encoded result. * @throws IOException */ ByteBuffer encode(String name) throws IOException; /** * @param data The byte values to decode. * @return The decoded string. * @throws IOException */ String decode(byte [] data) throws IOException; } CharacterManaJ/src/org/apache/tools/zip/NioZipEncoding.java0000644000175000017500000000772712560206305024070 0ustar paulliupaulliu/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ package org.apache.tools.zip; import java.io.IOException; import java.nio.ByteBuffer; import java.nio.CharBuffer; import java.nio.charset.Charset; import java.nio.charset.CharsetEncoder; import java.nio.charset.CoderResult; import java.nio.charset.CodingErrorAction; /** * A ZipEncoding, which uses a java.nio {@link * java.nio.charset.Charset Charset} to encode names. * *

This implementation works for all cases under java-1.5 or * later. However, in java-1.4, some charsets don't have a java.nio * implementation, most notably the default ZIP encoding Cp437.

* *

The methods of this class are reentrant.

*/ class NioZipEncoding implements ZipEncoding { private final Charset charset; /** * Construct an NIO based zip encoding, which wraps the given * charset. * * @param charset The NIO charset to wrap. */ public NioZipEncoding(Charset charset) { this.charset = charset; } /** * @see * org.apache.tools.zip.ZipEncoding#canEncode(java.lang.String) */ public boolean canEncode(String name) { CharsetEncoder enc = this.charset.newEncoder(); enc.onMalformedInput(CodingErrorAction.REPORT); enc.onUnmappableCharacter(CodingErrorAction.REPORT); return enc.canEncode(name); } /** * @see * org.apache.tools.zip.ZipEncoding#encode(java.lang.String) */ public ByteBuffer encode(String name) { CharsetEncoder enc = this.charset.newEncoder(); enc.onMalformedInput(CodingErrorAction.REPORT); enc.onUnmappableCharacter(CodingErrorAction.REPORT); CharBuffer cb = CharBuffer.wrap(name); ByteBuffer out = ByteBuffer.allocate(name.length() + (name.length() + 1) / 2); while (cb.remaining() > 0) { CoderResult res = enc.encode(cb, out,true); if (res.isUnmappable() || res.isMalformed()) { // write the unmappable characters in utf-16 // pseudo-URL encoding style to ByteBuffer. if (res.length() * 6 > out.remaining()) { out = ZipEncodingHelper.growBuffer(out, out.position() + res.length() * 6); } for (int i=0; iAssumes local file data and central directory entries are * identical - unless told the opposite.

* */ public class UnrecognizedExtraField implements CentralDirectoryParsingZipExtraField { /** * The Header-ID. * * @since 1.1 */ private ZipShort headerId; /** * Set the header id. * @param headerId the header id to use */ public void setHeaderId(ZipShort headerId) { this.headerId = headerId; } /** * Get the header id. * @return the header id */ public ZipShort getHeaderId() { return headerId; } /** * Extra field data in local file data - without * Header-ID or length specifier. * * @since 1.1 */ private byte[] localData; /** * Set the extra field data in the local file data - * without Header-ID or length specifier. * @param data the field data to use */ public void setLocalFileDataData(byte[] data) { localData = ZipUtil.copy(data); } /** * Get the length of the local data. * @return the length of the local data */ public ZipShort getLocalFileDataLength() { return new ZipShort(localData.length); } /** * Get the local data. * @return the local data */ public byte[] getLocalFileDataData() { return ZipUtil.copy(localData); } /** * Extra field data in central directory - without * Header-ID or length specifier. * * @since 1.1 */ private byte[] centralData; /** * Set the extra field data in central directory. * @param data the data to use */ public void setCentralDirectoryData(byte[] data) { centralData = ZipUtil.copy(data); } /** * Get the central data length. * If there is no central data, get the local file data length. * @return the central data length */ public ZipShort getCentralDirectoryLength() { if (centralData != null) { return new ZipShort(centralData.length); } return getLocalFileDataLength(); } /** * Get the central data. * @return the central data if present, else return the local file data */ public byte[] getCentralDirectoryData() { if (centralData != null) { return ZipUtil.copy(centralData); } return getLocalFileDataData(); } /** * @param data the array of bytes. * @param offset the source location in the data array. * @param length the number of bytes to use in the data array. * @see ZipExtraField#parseFromLocalFileData(byte[], int, int) */ public void parseFromLocalFileData(byte[] data, int offset, int length) { byte[] tmp = new byte[length]; System.arraycopy(data, offset, tmp, 0, length); setLocalFileDataData(tmp); } /** * @param data the array of bytes. * @param offset the source location in the data array. * @param length the number of bytes to use in the data array. */ public void parseFromCentralDirectoryData(byte[] data, int offset, int length) { byte[] tmp = new byte[length]; System.arraycopy(data, offset, tmp, 0, length); setCentralDirectoryData(tmp); if (localData == null) { setLocalFileDataData(tmp); } } } CharacterManaJ/src/org/apache/tools/zip/ExtraFieldUtils.java0000644000175000017500000003007312560206305024247 0ustar paulliupaulliu/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * */ package org.apache.tools.zip; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.zip.ZipException; /** * ZipExtraField related methods * */ // CheckStyle:HideUtilityClassConstructorCheck OFF (bc) @SuppressWarnings({"unchecked", "rawtypes"}) public class ExtraFieldUtils { private static final int WORD = 4; /** * Static registry of known extra fields. * * @since 1.1 */ private static final Map implementations; static { implementations = new HashMap(); register(AsiExtraField.class); register(JarMarker.class); register(UnicodePathExtraField.class); register(UnicodeCommentExtraField.class); } /** * Register a ZipExtraField implementation. * *

The given class must have a no-arg constructor and implement * the {@link ZipExtraField ZipExtraField interface}.

* @param c the class to register * * @since 1.1 */ public static void register(Class c) { try { ZipExtraField ze = (ZipExtraField) c.newInstance(); implementations.put(ze.getHeaderId(), c); } catch (ClassCastException cc) { throw new RuntimeException(c + " doesn\'t implement ZipExtraField"); } catch (InstantiationException ie) { throw new RuntimeException(c + " is not a concrete class"); } catch (IllegalAccessException ie) { throw new RuntimeException(c + "\'s no-arg constructor is not public"); } } /** * Create an instance of the approriate ExtraField, falls back to * {@link UnrecognizedExtraField UnrecognizedExtraField}. * @param headerId the header identifier * @return an instance of the appropiate ExtraField * @exception InstantiationException if unable to instantiate the class * @exception IllegalAccessException if not allowed to instatiate the class * @since 1.1 */ public static ZipExtraField createExtraField(ZipShort headerId) throws InstantiationException, IllegalAccessException { Class c = (Class) implementations.get(headerId); if (c != null) { return (ZipExtraField) c.newInstance(); } UnrecognizedExtraField u = new UnrecognizedExtraField(); u.setHeaderId(headerId); return u; } /** * Split the array into ExtraFields and populate them with the * given data as local file data, throwing an exception if the * data cannot be parsed. * @param data an array of bytes as it appears in local file data * @return an array of ExtraFields * @throws ZipException on error */ public static ZipExtraField[] parse(byte[] data) throws ZipException { return parse(data, true, UnparseableExtraField.THROW); } /** * Split the array into ExtraFields and populate them with the * given data, throwing an exception if the data cannot be parsed. * @param data an array of bytes * @param local whether data originates from the local file data * or the central directory * @return an array of ExtraFields * @since 1.1 * @throws ZipException on error */ public static ZipExtraField[] parse(byte[] data, boolean local) throws ZipException { return parse(data, local, UnparseableExtraField.THROW); } /** * Split the array into ExtraFields and populate them with the * given data. * @param data an array of bytes * @param local whether data originates from the local file data * or the central directory * @param onUnparseableData what to do if the extra field data * cannot be parsed. * @return an array of ExtraFields * @throws ZipException on error * @since Ant 1.8.1 */ public static ZipExtraField[] parse(byte[] data, boolean local, UnparseableExtraField onUnparseableData) throws ZipException { List v = new ArrayList(); int start = 0; LOOP: while (start <= data.length - WORD) { ZipShort headerId = new ZipShort(data, start); int length = (new ZipShort(data, start + 2)).getValue(); if (start + WORD + length > data.length) { switch(onUnparseableData.getKey()) { case UnparseableExtraField.THROW_KEY: throw new ZipException("bad extra field starting at " + start + ". Block length of " + length + " bytes exceeds remaining" + " data of " + (data.length - start - WORD) + " bytes."); case UnparseableExtraField.READ_KEY: UnparseableExtraFieldData field = new UnparseableExtraFieldData(); if (local) { field.parseFromLocalFileData(data, start, data.length - start); } else { field.parseFromCentralDirectoryData(data, start, data.length - start); } v.add(field); /*FALLTHROUGH*/ case UnparseableExtraField.SKIP_KEY: // since we cannot parse the data we must assume // the extra field consumes the whole rest of the // available data break LOOP; default: throw new ZipException("unknown UnparseableExtraField key: " + onUnparseableData.getKey()); } } try { ZipExtraField ze = createExtraField(headerId); if (local || !(ze instanceof CentralDirectoryParsingZipExtraField)) { ze.parseFromLocalFileData(data, start + WORD, length); } else { ((CentralDirectoryParsingZipExtraField) ze) .parseFromCentralDirectoryData(data, start + WORD, length); } v.add(ze); } catch (InstantiationException ie) { throw new ZipException(ie.getMessage()); } catch (IllegalAccessException iae) { throw new ZipException(iae.getMessage()); } start += (length + WORD); } ZipExtraField[] result = new ZipExtraField[v.size()]; return (ZipExtraField[]) v.toArray(result); } /** * Merges the local file data fields of the given ZipExtraFields. * @param data an array of ExtraFiles * @return an array of bytes * @since 1.1 */ public static byte[] mergeLocalFileDataData(ZipExtraField[] data) { final boolean lastIsUnparseableHolder = data.length > 0 && data[data.length - 1] instanceof UnparseableExtraFieldData; int regularExtraFieldCount = lastIsUnparseableHolder ? data.length - 1 : data.length; int sum = WORD * regularExtraFieldCount; for (int i = 0; i < data.length; i++) { sum += data[i].getLocalFileDataLength().getValue(); } byte[] result = new byte[sum]; int start = 0; for (int i = 0; i < regularExtraFieldCount; i++) { System.arraycopy(data[i].getHeaderId().getBytes(), 0, result, start, 2); System.arraycopy(data[i].getLocalFileDataLength().getBytes(), 0, result, start + 2, 2); byte[] local = data[i].getLocalFileDataData(); System.arraycopy(local, 0, result, start + WORD, local.length); start += (local.length + WORD); } if (lastIsUnparseableHolder) { byte[] local = data[data.length - 1].getLocalFileDataData(); System.arraycopy(local, 0, result, start, local.length); } return result; } /** * Merges the central directory fields of the given ZipExtraFields. * @param data an array of ExtraFields * @return an array of bytes * @since 1.1 */ public static byte[] mergeCentralDirectoryData(ZipExtraField[] data) { final boolean lastIsUnparseableHolder = data.length > 0 && data[data.length - 1] instanceof UnparseableExtraFieldData; int regularExtraFieldCount = lastIsUnparseableHolder ? data.length - 1 : data.length; int sum = WORD * regularExtraFieldCount; for (int i = 0; i < data.length; i++) { sum += data[i].getCentralDirectoryLength().getValue(); } byte[] result = new byte[sum]; int start = 0; for (int i = 0; i < regularExtraFieldCount; i++) { System.arraycopy(data[i].getHeaderId().getBytes(), 0, result, start, 2); System.arraycopy(data[i].getCentralDirectoryLength().getBytes(), 0, result, start + 2, 2); byte[] local = data[i].getCentralDirectoryData(); System.arraycopy(local, 0, result, start + WORD, local.length); start += (local.length + WORD); } if (lastIsUnparseableHolder) { byte[] local = data[data.length - 1].getCentralDirectoryData(); System.arraycopy(local, 0, result, start, local.length); } return result; } /** * "enum" for the possible actions to take if the extra field * cannot be parsed. */ public static final class UnparseableExtraField { /** * Key for "throw an exception" action. */ public static final int THROW_KEY = 0; /** * Key for "skip" action. */ public static final int SKIP_KEY = 1; /** * Key for "read" action. */ public static final int READ_KEY = 2; /** * Throw an exception if field cannot be parsed. */ public static final UnparseableExtraField THROW = new UnparseableExtraField(THROW_KEY); /** * Skip the extra field entirely and don't make its data * available - effectively removing the extra field data. */ public static final UnparseableExtraField SKIP = new UnparseableExtraField(SKIP_KEY); /** * Read the extra field data into an instance of {@link * UnparseableExtraFieldData UnparseableExtraFieldData}. */ public static final UnparseableExtraField READ = new UnparseableExtraField(READ_KEY); private final int key; private UnparseableExtraField(int k) { key = k; } /** * Key of the action to take. */ public int getKey() { return key; } } } CharacterManaJ/src/org/apache/tools/zip/FallbackZipEncoding.java0000644000175000017500000000570212560206305025031 0ustar paulliupaulliu/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ package org.apache.tools.zip; import java.io.IOException; import java.nio.ByteBuffer; /** * A fallback ZipEncoding, which uses a java.io means to encode names. * *

This implementation is not favorable for encodings other than * utf-8, because java.io encodes unmappable character as question * marks leading to unreadable ZIP entries on some operating * systems.

* *

Furthermore this implementation is unable to tell, whether a * given name can be safely encoded or not.

* *

This implementation acts as a last resort implementation, when * neither {@link Simple8BitZipEnoding} nor {@link NioZipEncoding} is * available.

* *

The methods of this class are reentrant.

*/ class FallbackZipEncoding implements ZipEncoding { private final String charset; /** * Construct a fallback zip encoding, which uses the platform's * default charset. */ public FallbackZipEncoding() { this.charset = null; } /** * Construct a fallback zip encoding, which uses the given charset. * * @param charset The name of the charset or null for * the platform's default character set. */ public FallbackZipEncoding(String charset) { this.charset = charset; } /** * @see * org.apache.tools.zip.ZipEncoding#canEncode(java.lang.String) */ public boolean canEncode(String name) { return true; } /** * @see * org.apache.tools.zip.ZipEncoding#encode(java.lang.String) */ public ByteBuffer encode(String name) throws IOException { if (this.charset == null) { return ByteBuffer.wrap(name.getBytes()); } else { return ByteBuffer.wrap(name.getBytes(this.charset)); } } /** * @see * org.apache.tools.zip.ZipEncoding#decode(byte[]) */ public String decode(byte[] data) throws IOException { if (this.charset == null) { return new String(data); } else { return new String(data,this.charset); } } } CharacterManaJ/src/org/apache/tools/zip/ZipExtraField.java0000644000175000017500000000526512560206305023716 0ustar paulliupaulliu/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * */ package org.apache.tools.zip; import java.util.zip.ZipException; /** * General format of extra field data. * *

Extra fields usually appear twice per file, once in the local * file data and once in the central directory. Usually they are the * same, but they don't have to be. {@link * java.util.zip.ZipOutputStream java.util.zip.ZipOutputStream} will * only use the local file data in both places.

* */ public interface ZipExtraField { /** * The Header-ID. * @return the header id * @since 1.1 */ ZipShort getHeaderId(); /** * Length of the extra field in the local file data - without * Header-ID or length specifier. * @return the length of the field in the local file data * @since 1.1 */ ZipShort getLocalFileDataLength(); /** * Length of the extra field in the central directory - without * Header-ID or length specifier. * @return the length of the field in the central directory * @since 1.1 */ ZipShort getCentralDirectoryLength(); /** * The actual data to put into local file data - without Header-ID * or length specifier. * @return the data * @since 1.1 */ byte[] getLocalFileDataData(); /** * The actual data to put into central directory - without Header-ID or * length specifier. * @return the data * @since 1.1 */ byte[] getCentralDirectoryData(); /** * Populate data from this array as if it was in local file data. * @param data an array of bytes * @param offset the start offset * @param length the number of bytes in the array from offset * * @since 1.1 * @throws ZipException on error */ void parseFromLocalFileData(byte[] data, int offset, int length) throws ZipException; } CharacterManaJ/src/org/apache/tools/zip/UnicodePathExtraField.java0000644000175000017500000000527112560206305025354 0ustar paulliupaulliu/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * */ package org.apache.tools.zip; /** * Info-ZIP Unicode Path Extra Field (0x7075): * *

Stores the UTF-8 version of the file name field as stored in the * local header and central directory header.

* *
 *         Value         Size        Description
 *         -----         ----        -----------
 * (UPath) 0x7075        Short       tag for this extra block type ("up")
 *         TSize         Short       total data size for this block
 *         Version       1 byte      version of this extra field, currently 1
 *         NameCRC32     4 bytes     File Name Field CRC32 Checksum
 *         UnicodeName   Variable    UTF-8 version of the entry File Name
 * 
*/ public class UnicodePathExtraField extends AbstractUnicodeExtraField { public static final ZipShort UPATH_ID = new ZipShort(0x7075); public UnicodePathExtraField () { } /** * Assemble as unicode path extension from the name given as * text as well as the encoded bytes actually written to the archive. * * @param text The file name * @param bytes the bytes actually written to the archive * @param off The offset of the encoded filename in bytes. * @param len The length of the encoded filename or comment in * bytes. */ public UnicodePathExtraField(String text, byte[] bytes, int off, int len) { super(text, bytes, off, len); } /** * Assemble as unicode path extension from the name given as * text as well as the encoded bytes actually written to the archive. * * @param name The file name * @param bytes the bytes actually written to the archive */ public UnicodePathExtraField(String name, byte[] bytes) { super(name, bytes); } public ZipShort getHeaderId() { return UPATH_ID; } } CharacterManaJ/src/org/apache/tools/zip/UnparseableExtraFieldData.java0000644000175000017500000000761012560206305026203 0ustar paulliupaulliu/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ package org.apache.tools.zip; /** * Wrapper for extra field data that doesn't conform to the recommended format of header-tag + size + data. * *

The header-id is artificial (and not listed as a know ID in * {@link http://www.pkware.com/documents/casestudies/APPNOTE.TXT * APPNOTE.TXT}. Since it isn't used anywhere except to satisfy the * ZipExtraField contract it shouldn't matter anyway.

* @since Ant 1.8.1 */ public final class UnparseableExtraFieldData implements CentralDirectoryParsingZipExtraField { private static final ZipShort HEADER_ID = new ZipShort(0xACC1); private byte[] localFileData; private byte[] centralDirectoryData; /** * The Header-ID. * * @return a completely arbitrary value that should be ignored. */ public ZipShort getHeaderId() { return HEADER_ID; } /** * Length of the complete extra field in the local file data. * * @return The LocalFileDataLength value */ public ZipShort getLocalFileDataLength() { return new ZipShort(localFileData == null ? 0 : localFileData.length); } /** * Length of the complete extra field in the central directory. * * @return The CentralDirectoryLength value */ public ZipShort getCentralDirectoryLength() { return centralDirectoryData == null ? getLocalFileDataLength() : new ZipShort(centralDirectoryData.length); } /** * The actual data to put into local file data. * * @return The LocalFileDataData value */ public byte[] getLocalFileDataData() { return ZipUtil.copy(localFileData); } /** * The actual data to put into central directory. * * @return The CentralDirectoryData value */ public byte[] getCentralDirectoryData() { return centralDirectoryData == null ? getLocalFileDataData() : ZipUtil.copy(centralDirectoryData); } /** * Populate data from this array as if it was in local file data. * * @param buffer the buffer to read data from * @param offset offset into buffer to read data * @param length the length of data */ public void parseFromLocalFileData(byte[] buffer, int offset, int length) { localFileData = new byte[length]; System.arraycopy(buffer, offset, localFileData, 0, length); } /** * Populate data from this array as if it was in central directory data. * * @param buffer the buffer to read data from * @param offset offset into buffer to read data * @param length the length of data * @exception ZipException on error */ public void parseFromCentralDirectoryData(byte[] buffer, int offset, int length) { centralDirectoryData = new byte[length]; System.arraycopy(buffer, offset, centralDirectoryData, 0, length); if (localFileData == null) { parseFromLocalFileData(buffer, offset, length); } } } CharacterManaJ/src/org/apache/tools/zip/ZipLong.java0000644000175000017500000001170212560206305022557 0ustar paulliupaulliu/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * */ package org.apache.tools.zip; /** * Utility class that represents a four byte integer with conversion * rules for the big endian byte order of ZIP files. * */ public final class ZipLong implements Cloneable { private static final int WORD = 4; // private static final int BYTE_BIT_SIZE = 8; private static final int BYTE_MASK = 0xFF; private static final int BYTE_1 = 1; private static final int BYTE_1_MASK = 0xFF00; private static final int BYTE_1_SHIFT = 8; private static final int BYTE_2 = 2; private static final int BYTE_2_MASK = 0xFF0000; private static final int BYTE_2_SHIFT = 16; private static final int BYTE_3 = 3; private static final long BYTE_3_MASK = 0xFF000000L; private static final int BYTE_3_SHIFT = 24; private long value; /** * Create instance from a number. * @param value the long to store as a ZipLong * @since 1.1 */ public ZipLong(long value) { this.value = value; } /** * Create instance from bytes. * @param bytes the bytes to store as a ZipLong * @since 1.1 */ public ZipLong (byte[] bytes) { this(bytes, 0); } /** * Create instance from the four bytes starting at offset. * @param bytes the bytes to store as a ZipLong * @param offset the offset to start * @since 1.1 */ public ZipLong (byte[] bytes, int offset) { value = ZipLong.getValue(bytes, offset); } /** * Get value as four bytes in big endian byte order. * @since 1.1 * @return value as four bytes in big endian order */ public byte[] getBytes() { return ZipLong.getBytes(value); } /** * Get value as Java long. * @since 1.1 * @return value as a long */ public long getValue() { return value; } /** * Get value as four bytes in big endian byte order. * @param value the value to convert * @return value as four bytes in big endian byte order */ public static byte[] getBytes(long value) { byte[] result = new byte[WORD]; result[0] = (byte) ((value & BYTE_MASK)); result[BYTE_1] = (byte) ((value & BYTE_1_MASK) >> BYTE_1_SHIFT); result[BYTE_2] = (byte) ((value & BYTE_2_MASK) >> BYTE_2_SHIFT); result[BYTE_3] = (byte) ((value & BYTE_3_MASK) >> BYTE_3_SHIFT); return result; } /** * Helper method to get the value as a Java long from four bytes starting at given array offset * @param bytes the array of bytes * @param offset the offset to start * @return the correspondanding Java long value */ public static long getValue(byte[] bytes, int offset) { long value = (bytes[offset + BYTE_3] << BYTE_3_SHIFT) & BYTE_3_MASK; value += (bytes[offset + BYTE_2] << BYTE_2_SHIFT) & BYTE_2_MASK; value += (bytes[offset + BYTE_1] << BYTE_1_SHIFT) & BYTE_1_MASK; value += (bytes[offset] & BYTE_MASK); return value; } /** * Helper method to get the value as a Java long from a four-byte array * @param bytes the array of bytes * @return the correspondanding Java long value */ public static long getValue(byte[] bytes) { return getValue(bytes, 0); } /** * Override to make two instances with same value equal. * @param o an object to compare * @return true if the objects are equal * @since 1.1 */ public boolean equals(Object o) { if (o == null || !(o instanceof ZipLong)) { return false; } return value == ((ZipLong) o).getValue(); } /** * Override to make two instances with same value equal. * @return the value stored in the ZipLong * @since 1.1 */ public int hashCode() { return (int) value; } public Object clone() { try { return super.clone(); } catch (CloneNotSupportedException cnfe) { // impossible throw new RuntimeException(cnfe); } } } CharacterManaJ/src/org/apache/tools/zip/ZipShort.java0000644000175000017500000001060512560206305022760 0ustar paulliupaulliu/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * */ package org.apache.tools.zip; /** * Utility class that represents a two byte integer with conversion * rules for the big endian byte order of ZIP files. * */ public final class ZipShort implements Cloneable { private static final int BYTE_MASK = 0xFF; private static final int BYTE_1_MASK = 0xFF00; private static final int BYTE_1_SHIFT = 8; private int value; /** * Create instance from a number. * @param value the int to store as a ZipShort * @since 1.1 */ public ZipShort (int value) { this.value = value; } /** * Create instance from bytes. * @param bytes the bytes to store as a ZipShort * @since 1.1 */ public ZipShort (byte[] bytes) { this(bytes, 0); } /** * Create instance from the two bytes starting at offset. * @param bytes the bytes to store as a ZipShort * @param offset the offset to start * @since 1.1 */ public ZipShort (byte[] bytes, int offset) { value = ZipShort.getValue(bytes, offset); } /** * Get value as two bytes in big endian byte order. * @return the value as a a two byte array in big endian byte order * @since 1.1 */ public byte[] getBytes() { byte[] result = new byte[2]; result[0] = (byte) (value & BYTE_MASK); result[1] = (byte) ((value & BYTE_1_MASK) >> BYTE_1_SHIFT); return result; } /** * Get value as Java int. * @return value as a Java int * @since 1.1 */ public int getValue() { return value; } /** * Get value as two bytes in big endian byte order. * @param value the Java int to convert to bytes * @return the converted int as a byte array in big endian byte order */ public static byte[] getBytes(int value) { byte[] result = new byte[2]; result[0] = (byte) (value & BYTE_MASK); result[1] = (byte) ((value & BYTE_1_MASK) >> BYTE_1_SHIFT); return result; } /** * Helper method to get the value as a java int from two bytes starting at given array offset * @param bytes the array of bytes * @param offset the offset to start * @return the correspondanding java int value */ public static int getValue(byte[] bytes, int offset) { int value = (bytes[offset + 1] << BYTE_1_SHIFT) & BYTE_1_MASK; value += (bytes[offset] & BYTE_MASK); return value; } /** * Helper method to get the value as a java int from a two-byte array * @param bytes the array of bytes * @return the correspondanding java int value */ public static int getValue(byte[] bytes) { return getValue(bytes, 0); } /** * Override to make two instances with same value equal. * @param o an object to compare * @return true if the objects are equal * @since 1.1 */ public boolean equals(Object o) { if (o == null || !(o instanceof ZipShort)) { return false; } return value == ((ZipShort) o).getValue(); } /** * Override to make two instances with same value equal. * @return the value stored in the ZipShort * @since 1.1 */ public int hashCode() { return value; } public Object clone() { try { return super.clone(); } catch (CloneNotSupportedException cnfe) { // impossible throw new RuntimeException(cnfe); } } } CharacterManaJ/src/org/apache/tools/zip/AsiExtraField.java0000644000175000017500000002363712560206305023673 0ustar paulliupaulliu/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * */ package org.apache.tools.zip; import java.util.zip.CRC32; import java.util.zip.ZipException; /** * Adds Unix file permission and UID/GID fields as well as symbolic * link handling. * *

This class uses the ASi extra field in the format: *

 *         Value         Size            Description
 *         -----         ----            -----------
 * (Unix3) 0x756e        Short           tag for this extra block type
 *         TSize         Short           total data size for this block
 *         CRC           Long            CRC-32 of the remaining data
 *         Mode          Short           file permissions
 *         SizDev        Long            symlink'd size OR major/minor dev num
 *         UID           Short           user ID
 *         GID           Short           group ID
 *         (var.)        variable        symbolic link filename
 * 
* taken from appnote.iz (Info-ZIP note, 981119) found at ftp://ftp.uu.net/pub/archiving/zip/doc/

* *

Short is two bytes and Long is four bytes in big endian byte and * word order, device numbers are currently not supported.

* */ public class AsiExtraField implements ZipExtraField, UnixStat, Cloneable { private static final ZipShort HEADER_ID = new ZipShort(0x756E); private static final int WORD = 4; /** * Standard Unix stat(2) file mode. * * @since 1.1 */ private int mode = 0; /** * User ID. * * @since 1.1 */ private int uid = 0; /** * Group ID. * * @since 1.1 */ private int gid = 0; /** * File this entry points to, if it is a symbolic link. * *

empty string - if entry is not a symbolic link.

* * @since 1.1 */ private String link = ""; /** * Is this an entry for a directory? * * @since 1.1 */ private boolean dirFlag = false; /** * Instance used to calculate checksums. * * @since 1.1 */ private CRC32 crc = new CRC32(); /** Constructor for AsiExtraField. */ public AsiExtraField() { } /** * The Header-ID. * @return the value for the header id for this extrafield * @since 1.1 */ public ZipShort getHeaderId() { return HEADER_ID; } /** * Length of the extra field in the local file data - without * Header-ID or length specifier. * @return a ZipShort for the length of the data of this extra field * @since 1.1 */ public ZipShort getLocalFileDataLength() { return new ZipShort(WORD // CRC + 2 // Mode + WORD // SizDev + 2 // UID + 2 // GID + getLinkedFile().getBytes().length); } /** * Delegate to local file data. * @return the centralDirectory length * @since 1.1 */ public ZipShort getCentralDirectoryLength() { return getLocalFileDataLength(); } /** * The actual data to put into local file data - without Header-ID * or length specifier. * @return get the data * @since 1.1 */ public byte[] getLocalFileDataData() { // CRC will be added later byte[] data = new byte[getLocalFileDataLength().getValue() - WORD]; System.arraycopy(ZipShort.getBytes(getMode()), 0, data, 0, 2); byte[] linkArray = getLinkedFile().getBytes(); // CheckStyle:MagicNumber OFF System.arraycopy(ZipLong.getBytes(linkArray.length), 0, data, 2, WORD); System.arraycopy(ZipShort.getBytes(getUserId()), 0, data, 6, 2); System.arraycopy(ZipShort.getBytes(getGroupId()), 0, data, 8, 2); System.arraycopy(linkArray, 0, data, 10, linkArray.length); // CheckStyle:MagicNumber ON crc.reset(); crc.update(data); long checksum = crc.getValue(); byte[] result = new byte[data.length + WORD]; System.arraycopy(ZipLong.getBytes(checksum), 0, result, 0, WORD); System.arraycopy(data, 0, result, WORD, data.length); return result; } /** * Delegate to local file data. * @return the local file data * @since 1.1 */ public byte[] getCentralDirectoryData() { return getLocalFileDataData(); } /** * Set the user id. * @param uid the user id * @since 1.1 */ public void setUserId(int uid) { this.uid = uid; } /** * Get the user id. * @return the user id * @since 1.1 */ public int getUserId() { return uid; } /** * Set the group id. * @param gid the group id * @since 1.1 */ public void setGroupId(int gid) { this.gid = gid; } /** * Get the group id. * @return the group id * @since 1.1 */ public int getGroupId() { return gid; } /** * Indicate that this entry is a symbolic link to the given filename. * * @param name Name of the file this entry links to, empty String * if it is not a symbolic link. * * @since 1.1 */ public void setLinkedFile(String name) { link = name; mode = getMode(mode); } /** * Name of linked file * * @return name of the file this entry links to if it is a * symbolic link, the empty string otherwise. * * @since 1.1 */ public String getLinkedFile() { return link; } /** * Is this entry a symbolic link? * @return true if this is a symbolic link * @since 1.1 */ public boolean isLink() { return getLinkedFile().length() != 0; } /** * File mode of this file. * @param mode the file mode * @since 1.1 */ public void setMode(int mode) { this.mode = getMode(mode); } /** * File mode of this file. * @return the file mode * @since 1.1 */ public int getMode() { return mode; } /** * Indicate whether this entry is a directory. * @param dirFlag if true, this entry is a directory * @since 1.1 */ public void setDirectory(boolean dirFlag) { this.dirFlag = dirFlag; mode = getMode(mode); } /** * Is this entry a directory? * @return true if this entry is a directory * @since 1.1 */ public boolean isDirectory() { return dirFlag && !isLink(); } /** * Populate data from this array as if it was in local file data. * @param data an array of bytes * @param offset the start offset * @param length the number of bytes in the array from offset * @since 1.1 * @throws ZipException on error */ public void parseFromLocalFileData(byte[] data, int offset, int length) throws ZipException { long givenChecksum = ZipLong.getValue(data, offset); byte[] tmp = new byte[length - WORD]; System.arraycopy(data, offset + WORD, tmp, 0, length - WORD); crc.reset(); crc.update(tmp); long realChecksum = crc.getValue(); if (givenChecksum != realChecksum) { throw new ZipException("bad CRC checksum " + Long.toHexString(givenChecksum) + " instead of " + Long.toHexString(realChecksum)); } int newMode = ZipShort.getValue(tmp, 0); // CheckStyle:MagicNumber OFF byte[] linkArray = new byte[(int) ZipLong.getValue(tmp, 2)]; uid = ZipShort.getValue(tmp, 6); gid = ZipShort.getValue(tmp, 8); if (linkArray.length == 0) { link = ""; } else { System.arraycopy(tmp, 10, linkArray, 0, linkArray.length); link = new String(linkArray); } // CheckStyle:MagicNumber ON setDirectory((newMode & DIR_FLAG) != 0); setMode(newMode); } /** * Get the file mode for given permissions with the correct file type. * @param mode the mode * @return the type with the mode * @since 1.1 */ protected int getMode(int mode) { int type = FILE_FLAG; if (isLink()) { type = LINK_FLAG; } else if (isDirectory()) { type = DIR_FLAG; } return type | (mode & PERM_MASK); } public Object clone() { try { AsiExtraField cloned = (AsiExtraField) super.clone(); cloned.crc = new CRC32(); return cloned; } catch (CloneNotSupportedException cnfe) { // impossible throw new RuntimeException(cnfe); } } } CharacterManaJ/src/org/apache/tools/zip/UnicodeCommentExtraField.java0000644000175000017500000000533712560206305026065 0ustar paulliupaulliu/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * */ package org.apache.tools.zip; /** * Info-ZIP Unicode Comment Extra Field (0x6375): * *

Stores the UTF-8 version of the file comment as stored in the * central directory header.

* *
 *         Value         Size        Description
 *         -----         ----        -----------
 *  (UCom) 0x6375        Short       tag for this extra block type ("uc")
 *         TSize         Short       total data size for this block
 *         Version       1 byte      version of this extra field, currently 1
 *         ComCRC32      4 bytes     Comment Field CRC32 Checksum
 *         UnicodeCom    Variable    UTF-8 version of the entry comment
 * 
*/ public class UnicodeCommentExtraField extends AbstractUnicodeExtraField { public static final ZipShort UCOM_ID = new ZipShort(0x6375); public UnicodeCommentExtraField () { } /** * Assemble as unicode comment extension from the name given as * text as well as the encoded bytes actually written to the archive. * * @param text The file name * @param bytes the bytes actually written to the archive * @param off The offset of the encoded comment in bytes. * @param len The length of the encoded comment or comment in * bytes. */ public UnicodeCommentExtraField(String text, byte[] bytes, int off, int len) { super(text, bytes, off, len); } /** * Assemble as unicode comment extension from the comment given as * text as well as the bytes actually written to the archive. * * @param comment The file comment * @param bytes the bytes actually written to the archive */ public UnicodeCommentExtraField(String comment, byte[] bytes) { super(comment, bytes); } public ZipShort getHeaderId() { return UCOM_ID; } } CharacterManaJ/src/org/apache/tools/zip/CentralDirectoryParsingZipExtraField.java0000644000175000017500000000274512560206305030440 0ustar paulliupaulliu/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * */ package org.apache.tools.zip; import java.util.zip.ZipException; /** * {@link ZipExtraField ZipExtraField} that knows how to parse central * directory data. * * @since Ant 1.8.0 */ public interface CentralDirectoryParsingZipExtraField extends ZipExtraField { /** * Populate data from this array as if it was in central directory data. * @param data an array of bytes * @param offset the start offset * @param length the number of bytes in the array from offset * * @throws ZipException on error */ void parseFromCentralDirectoryData(byte[] data, int offset, int length) throws ZipException; } CharacterManaJ/src/org/apache/tools/zip/ZipOutputStream.java0000644000175000017500000010327212560206305024340 0ustar paulliupaulliu/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * */ package org.apache.tools.zip; import java.io.File; import java.io.FileOutputStream; import java.io.FilterOutputStream; import java.io.IOException; import java.io.OutputStream; import java.io.RandomAccessFile; import java.nio.ByteBuffer; import java.util.Date; import java.util.HashMap; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.zip.CRC32; import java.util.zip.Deflater; import java.util.zip.ZipException; /** * Reimplementation of {@link java.util.zip.ZipOutputStream * java.util.zip.ZipOutputStream} that does handle the extended * functionality of this package, especially internal/external file * attributes and extra fields with different layouts for local file * data and central directory entries. * *

This class will try to use {@link java.io.RandomAccessFile * RandomAccessFile} when you know that the output is going to go to a * file.

* *

If RandomAccessFile cannot be used, this implementation will use * a Data Descriptor to store size and CRC information for {@link * #DEFLATED DEFLATED} entries, this means, you don't need to * calculate them yourself. Unfortunately this is not possible for * the {@link #STORED STORED} method, here setting the CRC and * uncompressed size information is required before {@link * #putNextEntry putNextEntry} can be called.

* */ @SuppressWarnings({"unchecked", "rawtypes"}) public class ZipOutputStream extends FilterOutputStream { private static final int BYTE_MASK = 0xFF; private static final int SHORT = 2; private static final int WORD = 4; private static final int BUFFER_SIZE = 512; /* * Apparently Deflater.setInput gets slowed down a lot on Sun JVMs * when it gets handed a really big buffer. See * https://issues.apache.org/bugzilla/show_bug.cgi?id=45396 * * Using a buffer size of 8 kB proved to be a good compromise */ private static final int DEFLATER_BLOCK_SIZE = 8192; /** * Compression method for deflated entries. * * @since 1.1 */ public static final int DEFLATED = java.util.zip.ZipEntry.DEFLATED; /** * Default compression level for deflated entries. * * @since Ant 1.7 */ public static final int DEFAULT_COMPRESSION = Deflater.DEFAULT_COMPRESSION; /** * Compression method for stored entries. * * @since 1.1 */ public static final int STORED = java.util.zip.ZipEntry.STORED; /** * default encoding for file names and comment. */ static final String DEFAULT_ENCODING = null; /** * General purpose flag, which indicates that filenames are * written in utf-8. */ public static final int UFT8_NAMES_FLAG = 1 << 11; /** * General purpose flag, which indicates that filenames are * written in utf-8. * @deprecated use {@link #UFT8_NAMES_FLAG} instead */ public static final int EFS_FLAG = UFT8_NAMES_FLAG; /** * Current entry. * * @since 1.1 */ private ZipEntry entry; /** * The file comment. * * @since 1.1 */ private String comment = ""; /** * Compression level for next entry. * * @since 1.1 */ private int level = DEFAULT_COMPRESSION; /** * Has the compression level changed when compared to the last * entry? * * @since 1.5 */ private boolean hasCompressionLevelChanged = false; /** * Default compression method for next entry. * * @since 1.1 */ private int method = java.util.zip.ZipEntry.DEFLATED; /** * List of ZipEntries written so far. * * @since 1.1 */ private final List entries = new LinkedList(); /** * CRC instance to avoid parsing DEFLATED data twice. * * @since 1.1 */ private final CRC32 crc = new CRC32(); /** * Count the bytes written to out. * * @since 1.1 */ private long written = 0; /** * Data for local header data * * @since 1.1 */ private long dataStart = 0; /** * Offset for CRC entry in the local file header data for the * current entry starts here. * * @since 1.15 */ private long localDataStart = 0; /** * Start of central directory. * * @since 1.1 */ private long cdOffset = 0; /** * Length of central directory. * * @since 1.1 */ private long cdLength = 0; /** * Helper, a 0 as ZipShort. * * @since 1.1 */ private static final byte[] ZERO = {0, 0}; /** * Helper, a 0 as ZipLong. * * @since 1.1 */ private static final byte[] LZERO = {0, 0, 0, 0}; /** * Holds the offsets of the LFH starts for each entry. * * @since 1.1 */ private final Map offsets = new HashMap(); /** * The encoding to use for filenames and the file comment. * *

For a list of possible values see http://java.sun.com/j2se/1.5.0/docs/guide/intl/encoding.doc.html. * Defaults to the platform's default character encoding.

* * @since 1.3 */ private String encoding = null; /** * The zip encoding to use for filenames and the file comment. * * This field is of internal use and will be set in {@link * #setEncoding(String)}. */ private ZipEncoding zipEncoding = ZipEncodingHelper.getZipEncoding(DEFAULT_ENCODING); // CheckStyle:VisibilityModifier OFF - bc /** * This Deflater object is used for output. * *

This attribute is only protected to provide a level of API * backwards compatibility. This class used to extend {@link * java.util.zip.DeflaterOutputStream DeflaterOutputStream} up to * Revision 1.13.

* * @since 1.14 */ protected Deflater def = new Deflater(level, true); /** * This buffer servers as a Deflater. * *

This attribute is only protected to provide a level of API * backwards compatibility. This class used to extend {@link * java.util.zip.DeflaterOutputStream DeflaterOutputStream} up to * Revision 1.13.

* * @since 1.14 */ protected byte[] buf = new byte[BUFFER_SIZE]; // CheckStyle:VisibilityModifier ON /** * Optional random access output. * * @since 1.14 */ private RandomAccessFile raf = null; /** * whether to use the general purpose bit flag when writing UTF-8 * filenames or not. */ private boolean useUTF8Flag = true; /** * Whether to encode non-encodable file names as UTF-8. */ private boolean fallbackToUTF8 = false; /** * whether to create UnicodePathExtraField-s for each entry. */ private UnicodeExtraFieldPolicy createUnicodeExtraFields = UnicodeExtraFieldPolicy.NEVER; /** * Creates a new ZIP OutputStream filtering the underlying stream. * @param out the outputstream to zip * @since 1.1 */ public ZipOutputStream(OutputStream out) { super(out); } /** * Creates a new ZIP OutputStream writing to a File. Will use * random access if possible. * @param file the file to zip to * @since 1.14 * @throws IOException on error */ public ZipOutputStream(File file) throws IOException { super(null); try { raf = new RandomAccessFile(file, "rw"); raf.setLength(0); } catch (IOException e) { if (raf != null) { try { raf.close(); } catch (IOException inner) { // ignore } raf = null; } out = new FileOutputStream(file); } } /** * This method indicates whether this archive is writing to a * seekable stream (i.e., to a random access file). * *

For seekable streams, you don't need to calculate the CRC or * uncompressed size for {@link #STORED} entries before * invoking {@link #putNextEntry}. * @return true if seekable * @since 1.17 */ public boolean isSeekable() { return raf != null; } /** * The encoding to use for filenames and the file comment. * *

For a list of possible values see http://java.sun.com/j2se/1.5.0/docs/guide/intl/encoding.doc.html. * Defaults to the platform's default character encoding.

* @param encoding the encoding value * @since 1.3 */ public void setEncoding(final String encoding) { this.encoding = encoding; this.zipEncoding = ZipEncodingHelper.getZipEncoding(encoding); useUTF8Flag &= ZipEncodingHelper.isUTF8(encoding); } /** * The encoding to use for filenames and the file comment. * * @return null if using the platform's default character encoding. * * @since 1.3 */ public String getEncoding() { return encoding; } /** * Whether to set the language encoding flag if the file name * encoding is UTF-8. * *

Defaults to true.

*/ public void setUseLanguageEncodingFlag(boolean b) { useUTF8Flag = b && ZipEncodingHelper.isUTF8(encoding); } /** * Whether to create Unicode Extra Fields. * *

Defaults to NEVER.

*/ public void setCreateUnicodeExtraFields(UnicodeExtraFieldPolicy b) { createUnicodeExtraFields = b; } /** * Whether to fall back to UTF and the language encoding flag if * the file name cannot be encoded using the specified encoding. * *

Defaults to false.

*/ public void setFallbackToUTF8(boolean b) { fallbackToUTF8 = b; } /** * Finishs writing the contents and closes this as well as the * underlying stream. * * @since 1.1 * @throws IOException on error */ public void finish() throws IOException { closeEntry(); cdOffset = written; for (Iterator i = entries.iterator(); i.hasNext(); ) { writeCentralFileHeader((ZipEntry) i.next()); } cdLength = written - cdOffset; writeCentralDirectoryEnd(); offsets.clear(); entries.clear(); } /** * Writes all necessary data for this entry. * * @since 1.1 * @throws IOException on error */ public void closeEntry() throws IOException { if (entry == null) { return; } long realCrc = crc.getValue(); crc.reset(); if (entry.getMethod() == DEFLATED) { def.finish(); while (!def.finished()) { deflate(); } entry.setSize(adjustToLong(def.getTotalIn())); entry.setCompressedSize(adjustToLong(def.getTotalOut())); entry.setCrc(realCrc); def.reset(); written += entry.getCompressedSize(); } else if (raf == null) { if (entry.getCrc() != realCrc) { throw new ZipException("bad CRC checksum for entry " + entry.getName() + ": " + Long.toHexString(entry.getCrc()) + " instead of " + Long.toHexString(realCrc)); } if (entry.getSize() != written - dataStart) { throw new ZipException("bad size for entry " + entry.getName() + ": " + entry.getSize() + " instead of " + (written - dataStart)); } } else { /* method is STORED and we used RandomAccessFile */ long size = written - dataStart; entry.setSize(size); entry.setCompressedSize(size); entry.setCrc(realCrc); } // If random access output, write the local file header containing // the correct CRC and compressed/uncompressed sizes if (raf != null) { long save = raf.getFilePointer(); raf.seek(localDataStart); writeOut(ZipLong.getBytes(entry.getCrc())); writeOut(ZipLong.getBytes(entry.getCompressedSize())); writeOut(ZipLong.getBytes(entry.getSize())); raf.seek(save); } writeDataDescriptor(entry); entry = null; } /** * Begin writing next entry. * @param ze the entry to write * @since 1.1 * @throws IOException on error */ public void putNextEntry(ZipEntry ze) throws IOException { closeEntry(); entry = ze; entries.add(entry); if (entry.getMethod() == -1) { // not specified entry.setMethod(method); } if (entry.getTime() == -1) { // not specified entry.setTime(System.currentTimeMillis()); } // Size/CRC not required if RandomAccessFile is used if (entry.getMethod() == STORED && raf == null) { if (entry.getSize() == -1) { throw new ZipException("uncompressed size is required for" + " STORED method when not writing to a" + " file"); } if (entry.getCrc() == -1) { throw new ZipException("crc checksum is required for STORED" + " method when not writing to a file"); } entry.setCompressedSize(entry.getSize()); } if (entry.getMethod() == DEFLATED && hasCompressionLevelChanged) { def.setLevel(level); hasCompressionLevelChanged = false; } writeLocalFileHeader(entry); } /** * Set the file comment. * @param comment the comment * @since 1.1 */ public void setComment(String comment) { this.comment = comment; } /** * Sets the compression level for subsequent entries. * *

Default is Deflater.DEFAULT_COMPRESSION.

* @param level the compression level. * @throws IllegalArgumentException if an invalid compression * level is specified. * @since 1.1 */ public void setLevel(int level) { if (level < Deflater.DEFAULT_COMPRESSION || level > Deflater.BEST_COMPRESSION) { throw new IllegalArgumentException("Invalid compression level: " + level); } hasCompressionLevelChanged = (this.level != level); this.level = level; } /** * Sets the default compression method for subsequent entries. * *

Default is DEFLATED.

* @param method an int from java.util.zip.ZipEntry * @since 1.1 */ public void setMethod(int method) { this.method = method; } /** * Writes bytes to ZIP entry. * @param b the byte array to write * @param offset the start position to write from * @param length the number of bytes to write * @throws IOException on error */ public void write(byte[] b, int offset, int length) throws IOException { if (entry.getMethod() == DEFLATED) { if (length > 0) { if (!def.finished()) { if (length <= DEFLATER_BLOCK_SIZE) { def.setInput(b, offset, length); deflateUntilInputIsNeeded(); } else { final int fullblocks = length / DEFLATER_BLOCK_SIZE; for (int i = 0; i < fullblocks; i++) { def.setInput(b, offset + i * DEFLATER_BLOCK_SIZE, DEFLATER_BLOCK_SIZE); deflateUntilInputIsNeeded(); } final int done = fullblocks * DEFLATER_BLOCK_SIZE; if (done < length) { def.setInput(b, offset + done, length - done); deflateUntilInputIsNeeded(); } } } } } else { writeOut(b, offset, length); written += length; } crc.update(b, offset, length); } /** * Writes a single byte to ZIP entry. * *

Delegates to the three arg method.

* @param b the byte to write * @since 1.14 * @throws IOException on error */ public void write(int b) throws IOException { byte[] buff = new byte[1]; buff[0] = (byte) (b & BYTE_MASK); write(buff, 0, 1); } /** * Closes this output stream and releases any system resources * associated with the stream. * * @exception IOException if an I/O error occurs. * @since 1.14 */ public void close() throws IOException { finish(); if (raf != null) { raf.close(); } if (out != null) { out.close(); } } /** * Flushes this output stream and forces any buffered output bytes * to be written out to the stream. * * @exception IOException if an I/O error occurs. * @since 1.14 */ public void flush() throws IOException { if (out != null) { out.flush(); } } /* * Various ZIP constants */ /** * local file header signature * * @since 1.1 */ protected static final byte[] LFH_SIG = ZipLong.getBytes(0X04034B50L); /** * data descriptor signature * * @since 1.1 */ protected static final byte[] DD_SIG = ZipLong.getBytes(0X08074B50L); /** * central file header signature * * @since 1.1 */ protected static final byte[] CFH_SIG = ZipLong.getBytes(0X02014B50L); /** * end of central dir signature * * @since 1.1 */ protected static final byte[] EOCD_SIG = ZipLong.getBytes(0X06054B50L); /** * Writes next block of compressed data to the output stream. * @throws IOException on error * * @since 1.14 */ protected final void deflate() throws IOException { int len = def.deflate(buf, 0, buf.length); if (len > 0) { writeOut(buf, 0, len); } } /** * Writes the local file header entry * @param ze the entry to write * @throws IOException on error * * @since 1.1 */ protected void writeLocalFileHeader(ZipEntry ze) throws IOException { boolean encodable = zipEncoding.canEncode(ze.getName()); final ZipEncoding entryEncoding; if (!encodable && fallbackToUTF8) { entryEncoding = ZipEncodingHelper.UTF8_ZIP_ENCODING; } else { entryEncoding = zipEncoding; } ByteBuffer name = entryEncoding.encode(ze.getName()); if (createUnicodeExtraFields != UnicodeExtraFieldPolicy.NEVER) { if (createUnicodeExtraFields == UnicodeExtraFieldPolicy.ALWAYS || !encodable) { ze.addExtraField(new UnicodePathExtraField(ze.getName(), name.array(), name.arrayOffset(), name.limit())); } String comm = ze.getComment(); if (comm != null && !"".equals(comm)) { boolean commentEncodable = this.zipEncoding.canEncode(comm); if (createUnicodeExtraFields == UnicodeExtraFieldPolicy.ALWAYS || !commentEncodable) { ByteBuffer commentB = entryEncoding.encode(comm); ze.addExtraField(new UnicodeCommentExtraField(comm, commentB.array(), commentB.arrayOffset(), commentB.limit()) ); } } } offsets.put(ze, ZipLong.getBytes(written)); writeOut(LFH_SIG); written += WORD; //store method in local variable to prevent multiple method calls final int zipMethod = ze.getMethod(); writeVersionNeededToExtractAndGeneralPurposeBits(zipMethod, !encodable && fallbackToUTF8); written += WORD; // compression method writeOut(ZipShort.getBytes(zipMethod)); written += SHORT; // last mod. time and date writeOut(toDosTime(ze.getTime())); written += WORD; // CRC // compressed length // uncompressed length localDataStart = written; if (zipMethod == DEFLATED || raf != null) { writeOut(LZERO); writeOut(LZERO); writeOut(LZERO); } else { writeOut(ZipLong.getBytes(ze.getCrc())); writeOut(ZipLong.getBytes(ze.getSize())); writeOut(ZipLong.getBytes(ze.getSize())); } // CheckStyle:MagicNumber OFF written += 12; // CheckStyle:MagicNumber ON // file name length writeOut(ZipShort.getBytes(name.limit())); written += SHORT; // extra field length byte[] extra = ze.getLocalFileDataExtra(); writeOut(ZipShort.getBytes(extra.length)); written += SHORT; // file name writeOut(name.array(), name.arrayOffset(), name.limit()); written += name.limit(); // extra field writeOut(extra); written += extra.length; dataStart = written; } /** * Writes the data descriptor entry. * @param ze the entry to write * @throws IOException on error * * @since 1.1 */ protected void writeDataDescriptor(ZipEntry ze) throws IOException { if (ze.getMethod() != DEFLATED || raf != null) { return; } writeOut(DD_SIG); writeOut(ZipLong.getBytes(entry.getCrc())); writeOut(ZipLong.getBytes(entry.getCompressedSize())); writeOut(ZipLong.getBytes(entry.getSize())); // CheckStyle:MagicNumber OFF written += 16; // CheckStyle:MagicNumber ON } /** * Writes the central file header entry. * @param ze the entry to write * @throws IOException on error * * @since 1.1 */ protected void writeCentralFileHeader(ZipEntry ze) throws IOException { writeOut(CFH_SIG); written += WORD; // version made by // CheckStyle:MagicNumber OFF writeOut(ZipShort.getBytes((ze.getPlatform() << 8) | 20)); written += SHORT; final int zipMethod = ze.getMethod(); final boolean encodable = zipEncoding.canEncode(ze.getName()); writeVersionNeededToExtractAndGeneralPurposeBits(zipMethod, !encodable && fallbackToUTF8); written += WORD; // compression method writeOut(ZipShort.getBytes(zipMethod)); written += SHORT; // last mod. time and date writeOut(toDosTime(ze.getTime())); written += WORD; // CRC // compressed length // uncompressed length writeOut(ZipLong.getBytes(ze.getCrc())); writeOut(ZipLong.getBytes(ze.getCompressedSize())); writeOut(ZipLong.getBytes(ze.getSize())); // CheckStyle:MagicNumber OFF written += 12; // CheckStyle:MagicNumber ON // file name length final ZipEncoding entryEncoding; if (!encodable && fallbackToUTF8) { entryEncoding = ZipEncodingHelper.UTF8_ZIP_ENCODING; } else { entryEncoding = zipEncoding; } ByteBuffer name = entryEncoding.encode(ze.getName()); writeOut(ZipShort.getBytes(name.limit())); written += SHORT; // extra field length byte[] extra = ze.getCentralDirectoryExtra(); writeOut(ZipShort.getBytes(extra.length)); written += SHORT; // file comment length String comm = ze.getComment(); if (comm == null) { comm = ""; } ByteBuffer commentB = entryEncoding.encode(comm); writeOut(ZipShort.getBytes(commentB.limit())); written += SHORT; // disk number start writeOut(ZERO); written += SHORT; // internal file attributes writeOut(ZipShort.getBytes(ze.getInternalAttributes())); written += SHORT; // external file attributes writeOut(ZipLong.getBytes(ze.getExternalAttributes())); written += WORD; // relative offset of LFH writeOut((byte[]) offsets.get(ze)); written += WORD; // file name writeOut(name.array(), name.arrayOffset(), name.limit()); written += name.limit(); // extra field writeOut(extra); written += extra.length; // file comment writeOut(commentB.array(), commentB.arrayOffset(), commentB.limit()); written += commentB.limit(); } /** * Writes the "End of central dir record". * @throws IOException on error * * @since 1.1 */ protected void writeCentralDirectoryEnd() throws IOException { writeOut(EOCD_SIG); // disk numbers writeOut(ZERO); writeOut(ZERO); // number of entries byte[] num = ZipShort.getBytes(entries.size()); writeOut(num); writeOut(num); // length and location of CD writeOut(ZipLong.getBytes(cdLength)); writeOut(ZipLong.getBytes(cdOffset)); // ZIP file comment ByteBuffer data = this.zipEncoding.encode(comment); writeOut(ZipShort.getBytes(data.limit())); writeOut(data.array(), data.arrayOffset(), data.limit()); } /** * Smallest date/time ZIP can handle. * * @since 1.1 */ private static final byte[] DOS_TIME_MIN = ZipLong.getBytes(0x00002100L); /** * Convert a Date object to a DOS date/time field. * @param time the Date to convert * @return the date as a ZipLong * @since 1.1 */ protected static ZipLong toDosTime(Date time) { return new ZipLong(toDosTime(time.getTime())); } /** * Convert a Date object to a DOS date/time field. * *

Stolen from InfoZip's fileio.c

* @param t number of milliseconds since the epoch * @return the date as a byte array * @since 1.26 */ @SuppressWarnings("deprecation") protected static byte[] toDosTime(long t) { Date time = new Date(t); // CheckStyle:MagicNumberCheck OFF - I do not think that using constants // here will improve the readablity int year = time.getYear() + 1900; if (year < 1980) { return DOS_TIME_MIN; } int month = time.getMonth() + 1; long value = ((year - 1980) << 25) | (month << 21) | (time.getDate() << 16) | (time.getHours() << 11) | (time.getMinutes() << 5) | (time.getSeconds() >> 1); return ZipLong.getBytes(value); // CheckStyle:MagicNumberCheck ON } /** * Retrieve the bytes for the given String in the encoding set for * this Stream. * @param name the string to get bytes from * @return the bytes as a byte array * @throws ZipException on error * * @since 1.3 */ protected byte[] getBytes(String name) throws ZipException { try { ByteBuffer b = ZipEncodingHelper.getZipEncoding(encoding).encode(name); byte[] result = new byte[b.limit()]; System.arraycopy(b.array(), b.arrayOffset(), result, 0, result.length); return result; } catch (IOException ex) { throw new ZipException("Failed to encode name: " + ex.getMessage()); } } /** * Write bytes to output or random access file. * @param data the byte array to write * @throws IOException on error * * @since 1.14 */ protected final void writeOut(byte[] data) throws IOException { writeOut(data, 0, data.length); } /** * Write bytes to output or random access file. * @param data the byte array to write * @param offset the start position to write from * @param length the number of bytes to write * @throws IOException on error * * @since 1.14 */ protected final void writeOut(byte[] data, int offset, int length) throws IOException { if (raf != null) { raf.write(data, offset, length); } else { out.write(data, offset, length); } } /** * Assumes a negative integer really is a positive integer that * has wrapped around and re-creates the original value. * @param i the value to treat as unsigned int. * @return the unsigned int as a long. * @since 1.34 */ protected static long adjustToLong(int i) { if (i < 0) { return 2 * ((long) Integer.MAX_VALUE) + 2 + i; } else { return i; } } private void deflateUntilInputIsNeeded() throws IOException { while (!def.needsInput()) { deflate(); } } private void writeVersionNeededToExtractAndGeneralPurposeBits(final int zipMethod, final boolean utfFallback) throws IOException { // CheckStyle:MagicNumber OFF int versionNeededToExtract = 10; int generalPurposeFlag = (useUTF8Flag || utfFallback) ? UFT8_NAMES_FLAG : 0; if (zipMethod == DEFLATED && raf == null) { // requires version 2 as we are going to store length info // in the data descriptor versionNeededToExtract = 20; // bit3 set to signal, we use a data descriptor generalPurposeFlag |= 8; } // CheckStyle:MagicNumber ON // version needed to extract writeOut(ZipShort.getBytes(versionNeededToExtract)); // general purpose bit flag writeOut(ZipShort.getBytes(generalPurposeFlag)); } /** * enum that represents the possible policies for creating Unicode * extra fields. */ public static final class UnicodeExtraFieldPolicy { /** * Always create Unicode extra fields. */ public static final UnicodeExtraFieldPolicy ALWAYS = new UnicodeExtraFieldPolicy("always"); /** * Never create Unicode extra fields. */ public static final UnicodeExtraFieldPolicy NEVER = new UnicodeExtraFieldPolicy("never"); /** * Create Unicode extra fields for filenames that cannot be * encoded using the specified encoding. */ public static final UnicodeExtraFieldPolicy NOT_ENCODEABLE = new UnicodeExtraFieldPolicy("not encodeable"); private final String name; private UnicodeExtraFieldPolicy(String n) { name = n; } public String toString() { return name; } } } CharacterManaJ/src/org/apache/tools/zip/AbstractUnicodeExtraField.java0000644000175000017500000001170712560206305026224 0ustar paulliupaulliu/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * */ package org.apache.tools.zip; import java.io.UnsupportedEncodingException; import java.util.zip.CRC32; import java.util.zip.ZipException; /** * A common base class for Unicode extra information extra fields. */ public abstract class AbstractUnicodeExtraField implements ZipExtraField { private long nameCRC32; private byte[] unicodeName; private byte[] data; protected AbstractUnicodeExtraField() { } /** * Assemble as unicode extension from the name/comment and * encoding of the orginal zip entry. * * @param text The file name or comment. * @param bytes The encoded of the filename or comment in the zip * file. * @param off The offset of the encoded filename or comment in * bytes. * @param len The length of the encoded filename or commentin * bytes. */ protected AbstractUnicodeExtraField(String text, byte[] bytes, int off, int len) { CRC32 crc32 = new CRC32(); crc32.update(bytes, off, len); nameCRC32 = crc32.getValue(); try { unicodeName = text.getBytes("UTF-8"); } catch (UnsupportedEncodingException e) { throw new RuntimeException("FATAL: UTF-8 encoding not supported.", e); } } /** * Assemble as unicode extension from the name/comment and * encoding of the orginal zip entry. * * @param text The file name or comment. * @param bytes The encoded of the filename or comment in the zip * file. */ protected AbstractUnicodeExtraField(String text, byte[] bytes) { this(text, bytes, 0, bytes.length); } private void assembleData() { if (unicodeName == null) { return; } data = new byte[5 + unicodeName.length]; // version 1 data[0] = 0x01; System.arraycopy(ZipLong.getBytes(nameCRC32), 0, data, 1, 4); System.arraycopy(unicodeName, 0, data, 5, unicodeName.length); } /** * @return The CRC32 checksum of the filename or comment as * encoded in the central directory of the zip file. */ public long getNameCRC32() { return nameCRC32; } /** * @param nameCRC32 The CRC32 checksum of the filename as encoded * in the central directory of the zip file to set. */ public void setNameCRC32(long nameCRC32) { this.nameCRC32 = nameCRC32; data = null; } /** * @return The utf-8 encoded name. */ public byte[] getUnicodeName() { return unicodeName; } /** * @param unicodeName The utf-8 encoded name to set. */ public void setUnicodeName(byte[] unicodeName) { this.unicodeName = unicodeName; data = null; } public byte[] getCentralDirectoryData() { if (data == null) { this.assembleData(); } return data; } public ZipShort getCentralDirectoryLength() { if (data == null) { assembleData(); } return new ZipShort(data.length); } public byte[] getLocalFileDataData() { return getCentralDirectoryData(); } public ZipShort getLocalFileDataLength() { return getCentralDirectoryLength(); } public void parseFromLocalFileData(byte[] buffer, int offset, int length) throws ZipException { if (length < 5) { throw new ZipException("UniCode path extra data must have at least" + " 5 bytes."); } int version = buffer[offset]; if (version != 0x01) { throw new ZipException("Unsupported version [" + version + "] for UniCode path extra data."); } nameCRC32 = ZipLong.getValue(buffer, offset + 1); unicodeName = new byte[length - 5]; System.arraycopy(buffer, offset + 5, unicodeName, 0, length - 5); data = null; } } CharacterManaJ/src/org/apache/tools/zip/ZipFile.java0000644000175000017500000006500412560206305022543 0ustar paulliupaulliu/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * */ package org.apache.tools.zip; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.io.RandomAccessFile; import java.util.Calendar; import java.util.Collections; import java.util.Date; import java.util.Enumeration; import java.util.HashMap; import java.util.Map; import java.util.logging.Level; import java.util.logging.Logger; import java.util.zip.CRC32; import java.util.zip.Inflater; import java.util.zip.InflaterInputStream; import java.util.zip.ZipException; /** * Replacement for java.util.ZipFile. * *

This class adds support for file name encodings other than UTF-8 * (which is required to work on ZIP files created by native zip tools * and is able to skip a preamble like the one found in self * extracting archives. Furthermore it returns instances of * org.apache.tools.zip.ZipEntry instead of * java.util.zip.ZipEntry.

* *

It doesn't extend java.util.zip.ZipFile as it would * have to reimplement all methods anyway. Like * java.util.ZipFile, it uses RandomAccessFile under the * covers and supports compressed and uncompressed entries.

* *

The method signatures mimic the ones of * java.util.zip.ZipFile, with a couple of exceptions: * *

    *
  • There is no getName method.
  • *
  • entries has been renamed to getEntries.
  • *
  • getEntries and getEntry return * org.apache.tools.zip.ZipEntry instances.
  • *
  • close is allowed to throw IOException.
  • *
* */ @SuppressWarnings({"unchecked", "rawtypes"}) public class ZipFile { private static final Logger logger = Logger.getLogger(ZipFile.class.getName()); private static final int HASH_SIZE = 509; private static final int SHORT = 2; private static final int WORD = 4; private static final int NIBLET_MASK = 0x0f; private static final int BYTE_SHIFT = 8; private static final int POS_0 = 0; private static final int POS_1 = 1; private static final int POS_2 = 2; private static final int POS_3 = 3; /** * Maps ZipEntrys to Longs, recording the offsets of the local * file headers. */ private final Map entries = new HashMap(HASH_SIZE); /** * Maps String to ZipEntrys, name -> actual entry. */ private final Map nameMap = new HashMap(HASH_SIZE); private static final class OffsetEntry { private long headerOffset = -1; private long dataOffset = -1; } /** * The encoding to use for filenames and the file comment. * *

For a list of possible values see http://java.sun.com/j2se/1.5.0/docs/guide/intl/encoding.doc.html. * Defaults to the platform's default character encoding.

*/ private String encoding = null; /** * The zip encoding to use for filenames and the file comment. */ private final ZipEncoding zipEncoding; /** * The actual data source. */ private RandomAccessFile archive; /** * Whether to look for and use Unicode extra fields. */ private final boolean useUnicodeExtraFields; /** * Opens the given file for reading, assuming the platform's * native encoding for file names. * * @param f the archive. * * @throws IOException if an error occurs while reading the file. */ public ZipFile(File f) throws IOException { this(f, null); } /** * Opens the given file for reading, assuming the platform's * native encoding for file names. * * @param name name of the archive. * * @throws IOException if an error occurs while reading the file. */ public ZipFile(String name) throws IOException { this(new File(name), null); } /** * Opens the given file for reading, assuming the specified * encoding for file names, scanning unicode extra fields. * * @param name name of the archive. * @param encoding the encoding to use for file names * * @throws IOException if an error occurs while reading the file. */ public ZipFile(String name, String encoding) throws IOException { this(new File(name), encoding, true); } /** * Opens the given file for reading, assuming the specified * encoding for file names and scanning for unicode extra fields. * * @param f the archive. * @param encoding the encoding to use for file names, use null * for the platform's default encoding * * @throws IOException if an error occurs while reading the file. */ public ZipFile(File f, String encoding) throws IOException { this(f, encoding, true); } /** * Opens the given file for reading, assuming the specified * encoding for file names. * * @param f the archive. * @param encoding the encoding to use for file names, use null * for the platform's default encoding * @param useUnicodeExtraFields whether to use InfoZIP Unicode * Extra Fields (if present) to set the file names. * * @throws IOException if an error occurs while reading the file. */ public ZipFile(File f, String encoding, boolean useUnicodeExtraFields) throws IOException { this.encoding = encoding; this.zipEncoding = ZipEncodingHelper.getZipEncoding(encoding); this.useUnicodeExtraFields = useUnicodeExtraFields; archive = new RandomAccessFile(f, "r"); boolean success = false; try { Map entriesWithoutUTF8Flag = populateFromCentralDirectory(); resolveLocalFileHeaderData(entriesWithoutUTF8Flag); success = true; } finally { if (!success) { try { archive.close(); } catch (IOException e2) { // swallow, throw the original exception instead } } } } /** * The encoding to use for filenames and the file comment. * * @return null if using the platform's default character encoding. */ public String getEncoding() { return encoding; } /** * Closes the archive. * @throws IOException if an error occurs closing the archive. */ public void close() throws IOException { archive.close(); } /** * close a zipfile quietly; throw no io fault, do nothing * on a null parameter * @param zipfile file to close, can be null */ public static void closeQuietly(ZipFile zipfile) { if (zipfile != null) { try { zipfile.close(); } catch (IOException e) { //ignore } } } /** * Returns all entries. * @return all entries as {@link ZipEntry} instances */ public Enumeration getEntries() { return Collections.enumeration(entries.keySet()); } /** * Returns a named entry - or null if no entry by * that name exists. * @param name name of the entry. * @return the ZipEntry corresponding to the given name - or * null if not present. */ public ZipEntry getEntry(String name) { return (ZipEntry) nameMap.get(name); } /** * Returns an InputStream for reading the contents of the given entry. * @param ze the entry to get the stream for. * @return a stream to read the entry from. * @throws IOException if unable to create an input stream from the zipenty * @throws ZipException if the zipentry has an unsupported * compression method */ public InputStream getInputStream(ZipEntry ze) throws IOException, ZipException { OffsetEntry offsetEntry = (OffsetEntry) entries.get(ze); if (offsetEntry == null) { return null; } long start = offsetEntry.dataOffset; BoundedInputStream bis = new BoundedInputStream(start, ze.getCompressedSize()); switch (ze.getMethod()) { case ZipEntry.STORED: return bis; case ZipEntry.DEFLATED: bis.addDummy(); return new InflaterInputStream(bis, new Inflater(true)); default: throw new ZipException("Found unsupported compression method " + ze.getMethod()); } } private static final int CFH_LEN = /* version made by */ SHORT /* version needed to extract */ + SHORT /* general purpose bit flag */ + SHORT /* compression method */ + SHORT /* last mod file time */ + SHORT /* last mod file date */ + SHORT /* crc-32 */ + WORD /* compressed size */ + WORD /* uncompressed size */ + WORD /* filename length */ + SHORT /* extra field length */ + SHORT /* file comment length */ + SHORT /* disk number start */ + SHORT /* internal file attributes */ + SHORT /* external file attributes */ + WORD /* relative offset of local header */ + WORD; /** * Reads the central directory of the given archive and populates * the internal tables with ZipEntry instances. * *

The ZipEntrys will know all data that can be obtained from * the central directory alone, but not the data that requires the * local file header or additional data to be read.

* * @return a Map<ZipEntry, NameAndComment>> of * zipentries that didn't have the language encoding flag set when * read. */ private Map populateFromCentralDirectory() throws IOException { HashMap noUTF8Flag = new HashMap(); positionAtCentralDirectory(); byte[] cfh = new byte[CFH_LEN]; byte[] signatureBytes = new byte[WORD]; archive.readFully(signatureBytes); long sig = ZipLong.getValue(signatureBytes); final long cfhSig = ZipLong.getValue(ZipOutputStream.CFH_SIG); if (sig != cfhSig && startsWithLocalFileHeader()) { throw new IOException("central directory is empty, can't expand" + " corrupt archive."); } while (sig == cfhSig) { archive.readFully(cfh); int off = 0; ZipEntry ze = new ZipEntry(); int versionMadeBy = ZipShort.getValue(cfh, off); off += SHORT; ze.setPlatform((versionMadeBy >> BYTE_SHIFT) & NIBLET_MASK); off += SHORT; // skip version info final int generalPurposeFlag = ZipShort.getValue(cfh, off); final boolean hasUTF8Flag = (generalPurposeFlag & ZipOutputStream.UFT8_NAMES_FLAG) != 0; final ZipEncoding entryEncoding = hasUTF8Flag ? ZipEncodingHelper.UTF8_ZIP_ENCODING : zipEncoding; off += SHORT; ze.setMethod(ZipShort.getValue(cfh, off)); off += SHORT; // FIXME this is actually not very cpu cycles friendly as we are converting from // dos to java while the underlying Sun implementation will convert // from java to dos time for internal storage... long time = dosToJavaTime(ZipLong.getValue(cfh, off)); ze.setTime(time); off += WORD; ze.setCrc(ZipLong.getValue(cfh, off)); off += WORD; ze.setCompressedSize(ZipLong.getValue(cfh, off)); off += WORD; ze.setSize(ZipLong.getValue(cfh, off)); off += WORD; int fileNameLen = ZipShort.getValue(cfh, off); off += SHORT; int extraLen = ZipShort.getValue(cfh, off); off += SHORT; int commentLen = ZipShort.getValue(cfh, off); off += SHORT; off += SHORT; // disk number ze.setInternalAttributes(ZipShort.getValue(cfh, off)); off += SHORT; ze.setExternalAttributes(ZipLong.getValue(cfh, off)); off += WORD; byte[] fileName = new byte[fileNameLen]; archive.readFully(fileName); ze.setName(entryEncoding.decode(fileName)); // LFH offset, OffsetEntry offset = new OffsetEntry(); offset.headerOffset = ZipLong.getValue(cfh, off); // data offset will be filled later entries.put(ze, offset); nameMap.put(ze.getName(), ze); byte[] cdExtraData = new byte[extraLen]; archive.readFully(cdExtraData); ze.setCentralDirectoryExtra(cdExtraData); byte[] comment = new byte[commentLen]; archive.readFully(comment); ze.setComment(entryEncoding.decode(comment)); archive.readFully(signatureBytes); sig = ZipLong.getValue(signatureBytes); if (!hasUTF8Flag && useUnicodeExtraFields) { noUTF8Flag.put(ze, new NameAndComment(fileName, comment)); } } return noUTF8Flag; } private static final int MIN_EOCD_SIZE = /* end of central dir signature */ WORD /* number of this disk */ + SHORT /* number of the disk with the */ /* start of the central directory */ + SHORT /* total number of entries in */ /* the central dir on this disk */ + SHORT /* total number of entries in */ /* the central dir */ + SHORT /* size of the central directory */ + WORD /* offset of start of central */ /* directory with respect to */ /* the starting disk number */ + WORD /* zipfile comment length */ + SHORT; private static final int MAX_EOCD_SIZE = MIN_EOCD_SIZE /* maximum length of zipfile comment */ + 0xFFFF; private static final int CFD_LOCATOR_OFFSET = /* end of central dir signature */ WORD /* number of this disk */ + SHORT /* number of the disk with the */ /* start of the central directory */ + SHORT /* total number of entries in */ /* the central dir on this disk */ + SHORT /* total number of entries in */ /* the central dir */ + SHORT /* size of the central directory */ + WORD; /** * Searches for the "End of central dir record", parses * it and positions the stream at the first central directory * record. */ private void positionAtCentralDirectory() throws IOException { boolean found = false; long off = archive.length() - MIN_EOCD_SIZE; long stopSearching = Math.max(0L, archive.length() - MAX_EOCD_SIZE); if (off >= 0) { archive.seek(off); byte[] sig = ZipOutputStream.EOCD_SIG; int curr = archive.read(); while (off >= stopSearching && curr != -1) { if (curr == sig[POS_0]) { curr = archive.read(); if (curr == sig[POS_1]) { curr = archive.read(); if (curr == sig[POS_2]) { curr = archive.read(); if (curr == sig[POS_3]) { found = true; break; } } } } archive.seek(--off); curr = archive.read(); } } if (!found) { throw new ZipException("archive is not a ZIP archive"); } archive.seek(off + CFD_LOCATOR_OFFSET); byte[] cfdOffset = new byte[WORD]; archive.readFully(cfdOffset); archive.seek(ZipLong.getValue(cfdOffset)); } /** * Number of bytes in local file header up to the "length of * filename" entry. */ private static final long LFH_OFFSET_FOR_FILENAME_LENGTH = /* local file header signature */ WORD /* version needed to extract */ + SHORT /* general purpose bit flag */ + SHORT /* compression method */ + SHORT /* last mod file time */ + SHORT /* last mod file date */ + SHORT /* crc-32 */ + WORD /* compressed size */ + WORD /* uncompressed size */ + WORD; /** * Walks through all recorded entries and adds the data available * from the local file header. * *

Also records the offsets for the data to read from the * entries.

*/ private void resolveLocalFileHeaderData(Map entriesWithoutUTF8Flag) throws IOException { Enumeration e = getEntries(); while (e.hasMoreElements()) { ZipEntry ze = (ZipEntry) e.nextElement(); OffsetEntry offsetEntry = (OffsetEntry) entries.get(ze); long offset = offsetEntry.headerOffset; archive.seek(offset + LFH_OFFSET_FOR_FILENAME_LENGTH); byte[] b = new byte[SHORT]; archive.readFully(b); int fileNameLen = ZipShort.getValue(b); archive.readFully(b); int extraFieldLen = ZipShort.getValue(b); int lenToSkip = fileNameLen; while (lenToSkip > 0) { int skipped = archive.skipBytes(lenToSkip); if (skipped <= 0) { throw new RuntimeException("failed to skip file name in" + " local file header"); } lenToSkip -= skipped; } byte[] localExtraData = new byte[extraFieldLen]; archive.readFully(localExtraData); ze.setExtra(localExtraData); /*dataOffsets.put(ze, new Long(offset + LFH_OFFSET_FOR_FILENAME_LENGTH + SHORT + SHORT + fileNameLen + extraFieldLen)); */ offsetEntry.dataOffset = offset + LFH_OFFSET_FOR_FILENAME_LENGTH + SHORT + SHORT + fileNameLen + extraFieldLen; if (entriesWithoutUTF8Flag.containsKey(ze)) { setNameAndCommentFromExtraFields(ze, (NameAndComment) entriesWithoutUTF8Flag.get(ze)); } } } /** * Convert a DOS date/time field to a Date object. * * @param zipDosTime contains the stored DOS time. * @return a Date instance corresponding to the given time. */ protected static Date fromDosTime(ZipLong zipDosTime) { long dosTime = zipDosTime.getValue(); return new Date(dosToJavaTime(dosTime)); } /* * Converts DOS time to Java time (number of milliseconds since epoch). */ private static long dosToJavaTime(long dosTime) { Calendar cal = Calendar.getInstance(); // CheckStyle:MagicNumberCheck OFF - no point cal.set(Calendar.YEAR, (int) ((dosTime >> 25) & 0x7f) + 1980); cal.set(Calendar.MONTH, (int) ((dosTime >> 21) & 0x0f) - 1); cal.set(Calendar.DATE, (int) (dosTime >> 16) & 0x1f); cal.set(Calendar.HOUR_OF_DAY, (int) (dosTime >> 11) & 0x1f); cal.set(Calendar.MINUTE, (int) (dosTime >> 5) & 0x3f); cal.set(Calendar.SECOND, (int) (dosTime << 1) & 0x3e); // CheckStyle:MagicNumberCheck ON return cal.getTime().getTime(); } /** * Retrieve a String from the given bytes using the encoding set * for this ZipFile. * * @param bytes the byte array to transform * @return String obtained by using the given encoding * @throws ZipException if the encoding cannot be recognized. */ protected String getString(byte[] bytes) throws ZipException { try { return ZipEncodingHelper.getZipEncoding(encoding).decode(bytes); } catch (IOException ex) { throw new ZipException("Failed to decode name: " + ex.getMessage()); } } /** * Checks whether the archive starts with a LFH. If it doesn't, * it may be an empty archive. */ private boolean startsWithLocalFileHeader() throws IOException { archive.seek(0); final byte[] start = new byte[WORD]; archive.readFully(start); for (int i = 0; i < start.length; i++) { if (start[i] != ZipOutputStream.LFH_SIG[i]) { return false; } } return true; } /** * If the entry has Unicode*ExtraFields and the CRCs of the * names/comments match those of the extra fields, transfer the * known Unicode values from the extra field. */ private void setNameAndCommentFromExtraFields(ZipEntry ze, NameAndComment nc) { UnicodePathExtraField name = (UnicodePathExtraField) ze.getExtraField(UnicodePathExtraField.UPATH_ID); String originalName = ze.getName(); String newName = getUnicodeStringIfOriginalMatches(name, nc.name); if (newName != null && !originalName.equals(newName)) { ze.setName(newName); nameMap.remove(originalName); nameMap.put(newName, ze); } if (nc.comment != null && nc.comment.length > 0) { UnicodeCommentExtraField cmt = (UnicodeCommentExtraField) ze.getExtraField(UnicodeCommentExtraField.UCOM_ID); String newComment = getUnicodeStringIfOriginalMatches(cmt, nc.comment); if (newComment != null) { ze.setComment(newComment); } } } /** * If the stored CRC matches the one of the given name, return the * Unicode name of the given field. * *

If the field is null or the CRCs don't match, return null * instead.

*/ private String getUnicodeStringIfOriginalMatches(AbstractUnicodeExtraField f, byte[] orig) { if (f != null) { CRC32 crc32 = new CRC32(); crc32.update(orig); long origCRC32 = crc32.getValue(); if (origCRC32 == f.getNameCRC32()) { try { return ZipEncodingHelper .UTF8_ZIP_ENCODING.decode(f.getUnicodeName()); } catch (IOException ex) { // UTF-8 unsupported? should be impossible the // Unicode*ExtraField must contain some bad bytes logger.log(Level.WARNING, "ZipFile: UTF-8 unsupported." + " should be impossible the Unicode*ExtraField must contain some bad bytes.", ex); return null; } } } return null; } /** * InputStream that delegates requests to the underlying * RandomAccessFile, making sure that only bytes from a certain * range can be read. */ private class BoundedInputStream extends InputStream { private long remaining; private long loc; private boolean addDummyByte = false; BoundedInputStream(long start, long remaining) { this.remaining = remaining; loc = start; } public int read() throws IOException { if (remaining-- <= 0) { if (addDummyByte) { addDummyByte = false; return 0; } return -1; } synchronized (archive) { archive.seek(loc++); return archive.read(); } } public int read(byte[] b, int off, int len) throws IOException { if (remaining <= 0) { if (addDummyByte) { addDummyByte = false; b[off] = 0; return 1; } return -1; } if (len <= 0) { return 0; } if (len > remaining) { len = (int) remaining; } int ret = -1; synchronized (archive) { archive.seek(loc); ret = archive.read(b, off, len); } if (ret > 0) { loc += ret; remaining -= ret; } return ret; } /** * Inflater needs an extra dummy byte for nowrap - see * Inflater's javadocs. */ void addDummy() { addDummyByte = true; } } private static final class NameAndComment { private final byte[] name; private final byte[] comment; private NameAndComment(byte[] name, byte[] comment) { this.name = name; this.comment = comment; } } } CharacterManaJ/src/org/apache/tools/zip/ZipEntry.java0000644000175000017500000004133112560206305022762 0ustar paulliupaulliu/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * */ package org.apache.tools.zip; import java.util.ArrayList; import java.util.LinkedHashMap; import java.util.List; import java.util.zip.ZipException; /** * Extension that adds better handling of extra fields and provides * access to the internal and external file attributes. * *

The extra data is expected to follow the recommendation of * {@link http://www.pkware.com/documents/casestudies/APPNOTE.TXT * APPNOTE.txt}:

*
    *
  • the extra byte array consists of a sequence of extra fields
  • *
  • each extra fields starts by a two byte header id followed by * a two byte sequence holding the length of the remainder of * data.
  • *
* *

Any extra data that cannot be parsed by the rules above will be * consumed as "unparseable" extra data and treated differently by the * methods of this class. Versions prior to Apache Commons Compress * 1.1 would have thrown an exception if any attempt was made to read * or write extra data not conforming to the recommendation.

* */ @SuppressWarnings({"unchecked", "rawtypes"}) public class ZipEntry extends java.util.zip.ZipEntry implements Cloneable { public static final int PLATFORM_UNIX = 3; public static final int PLATFORM_FAT = 0; private static final int SHORT_MASK = 0xFFFF; private static final int SHORT_SHIFT = 16; private int internalAttributes = 0; private int platform = PLATFORM_FAT; private long externalAttributes = 0; private LinkedHashMap/**/ extraFields = null; private UnparseableExtraFieldData unparseableExtra = null; private String name = null; /** * Creates a new zip entry with the specified name. * @param name the name of the entry * @since 1.1 */ public ZipEntry(String name) { super(name); } /** * Creates a new zip entry with fields taken from the specified zip entry. * @param entry the entry to get fields from * @since 1.1 * @throws ZipException on error */ public ZipEntry(java.util.zip.ZipEntry entry) throws ZipException { super(entry); byte[] extra = entry.getExtra(); if (extra != null) { setExtraFields(ExtraFieldUtils.parse(extra, true, ExtraFieldUtils .UnparseableExtraField.READ)); } else { // initializes extra data to an empty byte array setExtra(); } } /** * Creates a new zip entry with fields taken from the specified zip entry. * @param entry the entry to get fields from * @throws ZipException on error * @since 1.1 */ public ZipEntry(ZipEntry entry) throws ZipException { this((java.util.zip.ZipEntry) entry); setInternalAttributes(entry.getInternalAttributes()); setExternalAttributes(entry.getExternalAttributes()); setExtraFields(entry.getExtraFields(true)); } /** * @since 1.9 */ protected ZipEntry() { super(""); } /** * Overwrite clone. * @return a cloned copy of this ZipEntry * @since 1.1 */ public Object clone() { ZipEntry e = (ZipEntry) super.clone(); e.setInternalAttributes(getInternalAttributes()); e.setExternalAttributes(getExternalAttributes()); e.setExtraFields(getExtraFields(true)); return e; } /** * Retrieves the internal file attributes. * * @return the internal file attributes * @since 1.1 */ public int getInternalAttributes() { return internalAttributes; } /** * Sets the internal file attributes. * @param value an int value * @since 1.1 */ public void setInternalAttributes(int value) { internalAttributes = value; } /** * Retrieves the external file attributes. * @return the external file attributes * @since 1.1 */ public long getExternalAttributes() { return externalAttributes; } /** * Sets the external file attributes. * @param value an long value * @since 1.1 */ public void setExternalAttributes(long value) { externalAttributes = value; } /** * Sets Unix permissions in a way that is understood by Info-Zip's * unzip command. * @param mode an int value * @since Ant 1.5.2 */ public void setUnixMode(int mode) { // CheckStyle:MagicNumberCheck OFF - no point setExternalAttributes((mode << SHORT_SHIFT) // MS-DOS read-only attribute | ((mode & 0200) == 0 ? 1 : 0) // MS-DOS directory flag | (isDirectory() ? 0x10 : 0)); // CheckStyle:MagicNumberCheck ON platform = PLATFORM_UNIX; } /** * Unix permission. * @return the unix permissions * @since Ant 1.6 */ public int getUnixMode() { return platform != PLATFORM_UNIX ? 0 : (int) ((getExternalAttributes() >> SHORT_SHIFT) & SHORT_MASK); } /** * Platform specification to put into the "version made * by" part of the central file header. * * @return PLATFORM_FAT unless {@link #setUnixMode setUnixMode} * has been called, in which case PLATORM_UNIX will be returned. * * @since Ant 1.5.2 */ public int getPlatform() { return platform; } /** * Set the platform (UNIX or FAT). * @param platform an int value - 0 is FAT, 3 is UNIX * @since 1.9 */ protected void setPlatform(int platform) { this.platform = platform; } /** * Replaces all currently attached extra fields with the new array. * @param fields an array of extra fields * @since 1.1 */ public void setExtraFields(ZipExtraField[] fields) { extraFields = new LinkedHashMap(); for (int i = 0; i < fields.length; i++) { if (fields[i] instanceof UnparseableExtraFieldData) { unparseableExtra = (UnparseableExtraFieldData) fields[i]; } else { extraFields.put(fields[i].getHeaderId(), fields[i]); } } setExtra(); } /** * Retrieves all extra fields that have been parsed successfully. * @return an array of the extra fields */ public ZipExtraField[] getExtraFields() { return getExtraFields(false); } /** * Retrieves extra fields. * @param includeUnparseable whether to also return unparseable * extra fields as {@link UnparseableExtraFieldData} if such data * exists. * @return an array of the extra fields * @since 1.1 */ public ZipExtraField[] getExtraFields(boolean includeUnparseable) { if (extraFields == null) { return !includeUnparseable || unparseableExtra == null ? new ZipExtraField[0] : new ZipExtraField[] { unparseableExtra }; } List result = new ArrayList(extraFields.values()); if (includeUnparseable && unparseableExtra != null) { result.add(unparseableExtra); } return (ZipExtraField[]) result.toArray(new ZipExtraField[0]); } /** * Adds an extra field - replacing an already present extra field * of the same type. * *

If no extra field of the same type exists, the field will be * added as last field.

* @param ze an extra field * @since 1.1 */ public void addExtraField(ZipExtraField ze) { if (ze instanceof UnparseableExtraFieldData) { unparseableExtra = (UnparseableExtraFieldData) ze; } else { if (extraFields == null) { extraFields = new LinkedHashMap(); } extraFields.put(ze.getHeaderId(), ze); } setExtra(); } /** * Adds an extra field - replacing an already present extra field * of the same type. * *

The new extra field will be the first one.

* @param ze an extra field * @since 1.1 */ public void addAsFirstExtraField(ZipExtraField ze) { if (ze instanceof UnparseableExtraFieldData) { unparseableExtra = (UnparseableExtraFieldData) ze; } else { LinkedHashMap copy = extraFields; extraFields = new LinkedHashMap(); extraFields.put(ze.getHeaderId(), ze); if (copy != null) { copy.remove(ze.getHeaderId()); extraFields.putAll(copy); } } setExtra(); } /** * Remove an extra field. * @param type the type of extra field to remove * @since 1.1 */ public void removeExtraField(ZipShort type) { if (extraFields == null) { throw new java.util.NoSuchElementException(); } if (extraFields.remove(type) == null) { throw new java.util.NoSuchElementException(); } setExtra(); } /** * Removes unparseable extra field data. */ public void removeUnparseableExtraFieldData() { if (unparseableExtra == null) { throw new java.util.NoSuchElementException(); } unparseableExtra = null; setExtra(); } /** * Looks up an extra field by its header id. * * @return null if no such field exists. */ public ZipExtraField getExtraField(ZipShort type) { if (extraFields != null) { return (ZipExtraField) extraFields.get(type); } return null; } /** * Looks up extra field data that couldn't be parsed correctly. * * @return null if no such field exists. */ public UnparseableExtraFieldData getUnparseableExtraFieldData() { return unparseableExtra; } /** * Parses the given bytes as extra field data and consumes any * unparseable data as an {@link UnparseableExtraFieldData} * instance. * @param extra an array of bytes to be parsed into extra fields * @throws RuntimeException if the bytes cannot be parsed * @since 1.1 * @throws RuntimeException on error */ public void setExtra(byte[] extra) throws RuntimeException { try { ZipExtraField[] local = ExtraFieldUtils.parse(extra, true, ExtraFieldUtils.UnparseableExtraField.READ); mergeExtraFields(local, true); } catch (Exception e) { // actually this is not be possible as of Ant 1.8.1 throw new RuntimeException("Error parsing extra fields for entry: " + getName() + " - " + e.getMessage(), e); } } /** * Unfortunately {@link java.util.zip.ZipOutputStream * java.util.zip.ZipOutputStream} seems to access the extra data * directly, so overriding getExtra doesn't help - we need to * modify super's data directly. * * @since 1.1 */ protected void setExtra() { super.setExtra(ExtraFieldUtils.mergeLocalFileDataData(getExtraFields(true))); } /** * Sets the central directory part of extra fields. */ public void setCentralDirectoryExtra(byte[] b) { try { ZipExtraField[] central = ExtraFieldUtils.parse(b, false, ExtraFieldUtils.UnparseableExtraField.READ); mergeExtraFields(central, false); } catch (Exception e) { throw new RuntimeException(e.getMessage(), e); } } /** * Retrieves the extra data for the local file data. * @return the extra data for local file * @since 1.1 */ public byte[] getLocalFileDataExtra() { byte[] extra = getExtra(); return extra != null ? extra : new byte[0]; } /** * Retrieves the extra data for the central directory. * @return the central directory extra data * @since 1.1 */ public byte[] getCentralDirectoryExtra() { return ExtraFieldUtils.mergeCentralDirectoryData(getExtraFields(true)); } /** * Make this class work in JDK 1.1 like a 1.2 class. * *

This either stores the size for later usage or invokes * setCompressedSize via reflection.

* @param size the size to use * @deprecated since 1.7. * Use setCompressedSize directly. * @since 1.2 */ public void setComprSize(long size) { setCompressedSize(size); } /** * Get the name of the entry. * @return the entry name * @since 1.9 */ public String getName() { return name == null ? super.getName() : name; } /** * Is this entry a directory? * @return true if the entry is a directory * @since 1.10 */ public boolean isDirectory() { return getName().endsWith("/"); } /** * Set the name of the entry. * @param name the name to use */ protected void setName(String name) { this.name = name; } /** * Get the hashCode of the entry. * This uses the name as the hashcode. * @return a hashcode. * @since Ant 1.7 */ public int hashCode() { // this method has severe consequences on performance. We cannot rely // on the super.hashCode() method since super.getName() always return // the empty string in the current implemention (there's no setter) // so it is basically draining the performance of a hashmap lookup return getName().hashCode(); } /** * The equality method. In this case, the implementation returns 'this == o' * which is basically the equals method of the Object class. * @param o the object to compare to * @return true if this object is the same as o * @since Ant 1.7 */ public boolean equals(Object o) { return (this == o); } /** * If there are no extra fields, use the given fields as new extra * data - otherwise merge the fields assuming the existing fields * and the new fields stem from different locations inside the * archive. * @param f the extra fields to merge * @param local whether the new fields originate from local data */ private void mergeExtraFields(ZipExtraField[] f, boolean local) throws ZipException { if (extraFields == null) { setExtraFields(f); } else { for (int i = 0; i < f.length; i++) { ZipExtraField existing; if (f[i] instanceof UnparseableExtraFieldData) { existing = unparseableExtra; } else { existing = getExtraField(f[i].getHeaderId()); } if (existing == null) { addExtraField(f[i]); } else { if (local || !(existing instanceof CentralDirectoryParsingZipExtraField)) { byte[] b = f[i].getLocalFileDataData(); existing.parseFromLocalFileData(b, 0, b.length); } else { byte[] b = f[i].getCentralDirectoryData(); ((CentralDirectoryParsingZipExtraField) existing) .parseFromCentralDirectoryData(b, 0, b.length); } } } setExtra(); } } } CharacterManaJ/src/org/apache/tools/zip/JarMarker.java0000644000175000017500000000623412560206305023057 0ustar paulliupaulliu/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * */ package org.apache.tools.zip; import java.util.zip.ZipException; /** * If this extra field is added as the very first extra field of the * archive, Solaris will consider it an executable jar file. * * @since Ant 1.6.3 */ public final class JarMarker implements ZipExtraField { private static final ZipShort ID = new ZipShort(0xCAFE); private static final ZipShort NULL = new ZipShort(0); private static final byte[] NO_BYTES = new byte[0]; private static final JarMarker DEFAULT = new JarMarker(); /** No-arg constructor */ public JarMarker() { // empty } /** * Since JarMarker is stateless we can always use the same instance. * @return the DEFAULT jarmaker. */ public static JarMarker getInstance() { return DEFAULT; } /** * The Header-ID. * @return the header id */ public ZipShort getHeaderId() { return ID; } /** * Length of the extra field in the local file data - without * Header-ID or length specifier. * @return 0 */ public ZipShort getLocalFileDataLength() { return NULL; } /** * Length of the extra field in the central directory - without * Header-ID or length specifier. * @return 0 */ public ZipShort getCentralDirectoryLength() { return NULL; } /** * The actual data to put into local file data - without Header-ID * or length specifier. * @return the data * @since 1.1 */ public byte[] getLocalFileDataData() { return NO_BYTES; } /** * The actual data to put central directory - without Header-ID or * length specifier. * @return the data */ public byte[] getCentralDirectoryData() { return NO_BYTES; } /** * Populate data from this array as if it was in local file data. * @param data an array of bytes * @param offset the start offset * @param length the number of bytes in the array from offset * * @throws ZipException on error */ public void parseFromLocalFileData(byte[] data, int offset, int length) throws ZipException { if (length != 0) { throw new ZipException("JarMarker doesn't expect any data"); } } } CharacterManaJ/src/org/apache/tools/zip/ZipUtil.java0000644000175000017500000000250112560206305022572 0ustar paulliupaulliu/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * */ package org.apache.tools.zip; /** * Utility class for handling DOS and Java time conversions. * @since Ant 1.8.1 */ public abstract class ZipUtil { /** * Create a copy of the given array - or return null if the * argument is null. */ static byte[] copy(byte[] from) { if (from != null) { byte[] to = new byte[from.length]; System.arraycopy(from, 0, to, 0, to.length); return to; } return null; } } CharacterManaJ/src/org/apache/tools/zip/Simple8BitZipEncoding.java0000644000175000017500000001700012560206305025304 0ustar paulliupaulliu/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ package org.apache.tools.zip; import java.io.IOException; import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.Collections; import java.util.List; /** * This ZipEncoding implementation implements a simple 8bit character * set, which mets the following restrictions: * *
    *
  • Characters 0x0000 to 0x007f are encoded as the corresponding * byte values 0x00 to 0x7f.
  • *
  • All byte codes from 0x80 to 0xff are mapped to a unique unicode * character in the range 0x0080 to 0x7fff. (No support for * UTF-16 surrogates) *
* *

These restrictions most notably apply to the most prominent * omissions of java-1.4's {@link java.nio.charset.Charset Charset} * implementation, Cp437 and Cp850.

* *

The methods of this class are reentrant.

*/ @SuppressWarnings("unchecked") class Simple8BitZipEncoding implements ZipEncoding { /** * A character entity, which is put to the reverse mapping table * of a simple encoding. */ @SuppressWarnings("rawtypes") private static final class Simple8BitChar implements Comparable { public final char unicode; public final byte code; Simple8BitChar(byte code, char unicode) { this.code = code; this.unicode = unicode; } public int compareTo(Object o) { Simple8BitChar a = (Simple8BitChar) o; return this.unicode - a.unicode; } public String toString() { return "0x" + Integer.toHexString(0xffff & (int) unicode) + "->0x" + Integer.toHexString(0xff & (int) code); } } /** * The characters for byte values of 128 to 255 stored as an array of * 128 chars. */ private final char[] highChars; /** * A list of {@link Simple8BitChar} objects sorted by the unicode * field. This list is used to binary search reverse mapping of * unicode characters with a character code greater than 127. */ @SuppressWarnings("rawtypes") private final List reverseMapping; /** * @param highChars The characters for byte values of 128 to 255 * stored as an array of 128 chars. */ @SuppressWarnings("rawtypes") public Simple8BitZipEncoding(char[] highChars) { this.highChars = highChars; this.reverseMapping = new ArrayList(this.highChars.length); byte code = 127; for (int i = 0; i < this.highChars.length; ++i) { this.reverseMapping.add(new Simple8BitChar(++code, this.highChars[i])); } Collections.sort(this.reverseMapping); } /** * Return the character code for a given encoded byte. * * @param b The byte to decode. * @return The associated character value. */ public char decodeByte(byte b) { // code 0-127 if (b >= 0) { return (char) b; } // byte is signed, so 128 == -128 and 255 == -1 return this.highChars[128 + (int) b]; } /** * @param c The character to encode. * @return Whether the given unicode character is covered by this encoding. */ public boolean canEncodeChar(char c) { if (c >= 0 && c < 128) { return true; } Simple8BitChar r = this.encodeHighChar(c); return r != null; } /** * Pushes the encoded form of the given character to the given byte buffer. * * @param bb The byte buffer to write to. * @param c The character to encode. * @return Whether the given unicode character is covered by this encoding. * If false is returned, nothing is pushed to the * byte buffer. */ public boolean pushEncodedChar(ByteBuffer bb, char c) { if (c >= 0 && c < 128) { bb.put((byte) c); return true; } Simple8BitChar r = this.encodeHighChar(c); if (r == null) { return false; } bb.put(r.code); return true; } /** * @param c A unicode character in the range from 0x0080 to 0x7f00 * @return A Simple8BitChar, if this character is covered by this encoding. * A null value is returned, if this character is not * covered by this encoding. */ private Simple8BitChar encodeHighChar(char c) { // for performance an simplicity, yet another reincarnation of // binary search... int i0 = 0; int i1 = this.reverseMapping.size(); while (i1 > i0) { int i = i0 + (i1 - i0) / 2; Simple8BitChar m = (Simple8BitChar) this.reverseMapping.get(i); if (m.unicode == c) { return m; } if (m.unicode < c) { i0 = i + 1; } else { i1 = i; } } if (i0 >= this.reverseMapping.size()) { return null; } Simple8BitChar r = (Simple8BitChar) this.reverseMapping.get(i0); if (r.unicode != c) { return null; } return r; } /** * @see * org.apache.tools.zip.ZipEncoding#canEncode(java.lang.String) */ public boolean canEncode(String name) { for (int i=0;i CharacterManaJ/launch4j.xml0000644000175000017500000000246712560206305016033 0ustar paulliupaulliu false gui dist\CharacterManaJ.jar dist\charactermanaj.exe normal http://java.com/download false false icon.ico jre 1.5.0 preferJre 64 128 @exe_file_version@ @implements_version@ CharacterManaJ https://osdn.jp/projects/charactermanaj/simple/ @exe_file_version@ @implements_version@ CharacterManaJ seraphy@users.osdn.me CharacterManaJ charactermanaj.exe

About the XML namespace

This schema document describes the XML namespace, in a form suitable for import by other schema documents.

See http://www.w3.org/XML/1998/namespace.html and http://www.w3.org/TR/REC-xml for information about this namespace.

Note that local names in this namespace are intended to be defined only by the World Wide Web Consortium or its subgroups. The names currently defined in this namespace are listed below. They should not be used with conflicting semantics by any Working Group, specification, or document instance.

See further below in this document for more information about how to refer to this schema document from your own XSD schema documents and about the namespace-versioning policy governing this schema document.