From c226835208387ea0debc7b455bbab1d29268f590 Mon Sep 17 00:00:00 2001 From: veronie Date: Tue, 26 Jan 2021 00:32:24 +0100 Subject: [PATCH] Revamped UI completely. Added icons. --- xyz.veronie.bgg.ui/.classpath | 1 + xyz.veronie.bgg.ui/Application.e4xmi | 31 +- xyz.veronie.bgg.ui/META-INF/MANIFEST.MF | 8 +- xyz.veronie.bgg.ui/icons/download-button.png | Bin 1584 -> 0 bytes .../icons/export_nandeck_60x60.png | Bin 0 -> 5599 bytes xyz.veronie.bgg.ui/icons/folder-symbol.png | Bin 2270 -> 0 bytes .../icons/noun_Download_60x60.png | Bin 0 -> 2075 bytes .../icons/noun_Family_60x60.png | Bin 0 -> 2442 bytes xyz.veronie.bgg.ui/icons/noun_List_60x60.png | Bin 0 -> 2101 bytes .../icons/noun_Meeple_60x60.png | Bin 0 -> 1864 bytes xyz.veronie.bgg.ui/icons/noun_Save_60x60.png | Bin 0 -> 1627 bytes xyz.veronie.bgg.ui/icons/noun_Undo_60x60.png | Bin 0 -> 2006 bytes xyz.veronie.bgg.ui/icons/noun_open_60x60.png | Bin 0 -> 1831 bytes xyz.veronie.bgg.ui/icons/only_new_60x60.png | Bin 0 -> 7111 bytes xyz.veronie.bgg.ui/icons/result_add_60x60.png | Bin 0 -> 8101 bytes .../icons/result_intersect_60x60.png | Bin 0 -> 6664 bytes .../icons/result_replace_60x60.png | Bin 0 -> 7481 bytes .../icons/result_subtract_60x60.png | Bin 0 -> 7164 bytes xyz.veronie.bgg.ui/icons/save-button.png | Bin 1993 -> 0 bytes .../icons/save-button_20x20.png | Bin 3599 -> 0 bytes .../org/eclipse/wb/swt/ResourceManager.java | 438 +++++++++++++++++ .../eclipse/wb/swt/SWTResourceManager.java | 447 ++++++++++++++++++ .../bgg/ui/dialogs/SaveGameListDialog.java | 113 +++-- .../bgg/ui/handlers/HandleSaveGamelist.java | 9 +- .../xyz/veronie/bgg/ui/helpers/BatColors.java | 16 + .../veronie/bgg/ui/helpers/BatLayouts.java | 18 + .../src/xyz/veronie/bgg/ui/parts/BatMain.java | 274 +++++++++++ .../veronie/bgg/ui/parts/BggResultPart.java | 29 +- .../xyz/veronie/bgg/ui/parts/FetchPart.java | 298 ++++++++++++ .../internal/CenterImageLabelProvider.java | 35 ++ 30 files changed, 1613 insertions(+), 104 deletions(-) delete mode 100644 xyz.veronie.bgg.ui/icons/download-button.png create mode 100644 xyz.veronie.bgg.ui/icons/export_nandeck_60x60.png delete mode 100644 xyz.veronie.bgg.ui/icons/folder-symbol.png create mode 100644 xyz.veronie.bgg.ui/icons/noun_Download_60x60.png create mode 100644 xyz.veronie.bgg.ui/icons/noun_Family_60x60.png create mode 100644 xyz.veronie.bgg.ui/icons/noun_List_60x60.png create mode 100644 xyz.veronie.bgg.ui/icons/noun_Meeple_60x60.png create mode 100644 xyz.veronie.bgg.ui/icons/noun_Save_60x60.png create mode 100644 xyz.veronie.bgg.ui/icons/noun_Undo_60x60.png create mode 100644 xyz.veronie.bgg.ui/icons/noun_open_60x60.png create mode 100644 xyz.veronie.bgg.ui/icons/only_new_60x60.png create mode 100644 xyz.veronie.bgg.ui/icons/result_add_60x60.png create mode 100644 xyz.veronie.bgg.ui/icons/result_intersect_60x60.png create mode 100644 xyz.veronie.bgg.ui/icons/result_replace_60x60.png create mode 100644 xyz.veronie.bgg.ui/icons/result_subtract_60x60.png delete mode 100644 xyz.veronie.bgg.ui/icons/save-button.png delete mode 100644 xyz.veronie.bgg.ui/icons/save-button_20x20.png create mode 100644 xyz.veronie.bgg.ui/src/org/eclipse/wb/swt/ResourceManager.java create mode 100644 xyz.veronie.bgg.ui/src/org/eclipse/wb/swt/SWTResourceManager.java create mode 100644 xyz.veronie.bgg.ui/src/xyz/veronie/bgg/ui/helpers/BatColors.java create mode 100644 xyz.veronie.bgg.ui/src/xyz/veronie/bgg/ui/helpers/BatLayouts.java create mode 100644 xyz.veronie.bgg.ui/src/xyz/veronie/bgg/ui/parts/BatMain.java create mode 100644 xyz.veronie.bgg.ui/src/xyz/veronie/bgg/ui/parts/FetchPart.java create mode 100644 xyz.veronie.bgg.ui/src/xyz/veronie/bgg/ui/parts/internal/CenterImageLabelProvider.java diff --git a/xyz.veronie.bgg.ui/.classpath b/xyz.veronie.bgg.ui/.classpath index 111b080..148af0e 100644 --- a/xyz.veronie.bgg.ui/.classpath +++ b/xyz.veronie.bgg.ui/.classpath @@ -13,5 +13,6 @@ + diff --git a/xyz.veronie.bgg.ui/Application.e4xmi b/xyz.veronie.bgg.ui/Application.e4xmi index 389a6e9..a3bb17f 100644 --- a/xyz.veronie.bgg.ui/Application.e4xmi +++ b/xyz.veronie.bgg.ui/Application.e4xmi @@ -1,30 +1,10 @@ - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + @@ -34,6 +14,7 @@ + diff --git a/xyz.veronie.bgg.ui/META-INF/MANIFEST.MF b/xyz.veronie.bgg.ui/META-INF/MANIFEST.MF index 3ba545a..9cab8bd 100644 --- a/xyz.veronie.bgg.ui/META-INF/MANIFEST.MF +++ b/xyz.veronie.bgg.ui/META-INF/MANIFEST.MF @@ -3,6 +3,8 @@ Bundle-ManifestVersion: 2 Bundle-Name: Secondtry Bundle-SymbolicName: xyz.veronie.bgg.ui;singleton:=true Bundle-Version: 1.0.0.qualifier +Bundle-ClassPath: lib/sqlite-jdbc-3.34.0.jar, + swing2swt.jar Require-Bundle: org.eclipse.core.runtime, org.eclipse.swt, org.eclipse.e4.core.di, @@ -15,9 +17,9 @@ Require-Bundle: org.eclipse.core.runtime, org.eclipse.osgi.services, javax.inject, org.eclipse.e4.ui.model.workbench -Automatic-Module-Name: de.wt.secondtry Bundle-RequiredExecutionEnvironment: JavaSE-1.8 -Import-Package: javax.inject;version="1.0.0", +Import-Package: javax.annotation;version="1.0.0";resolution:=optional, + javax.inject;version="1.0.0", org.eclipse.e4.ui.model.application.descriptor.basic, org.eclipse.e4.ui.model.application.ui.basic -Bundle-ClassPath: lib/sqlite-jdbc-3.34.0.jar +Automatic-Module-Name: de.wt.secondtry diff --git a/xyz.veronie.bgg.ui/icons/download-button.png b/xyz.veronie.bgg.ui/icons/download-button.png deleted file mode 100644 index 33b99d518b22be7ba8822485e88606cf37f1a4a8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1584 zcmds1{ZCV86n@|L9zI$`T2QG}li8AcNa)N}&)XwPeO!rm(0Buu2BOESxf@ z=n|YCb2TP-$6R)G2_tNYM5pN{l1ofF-3&3ZE!TZu=c+R-+Zx$)^&jx3{j?`J=RD^l z&u`~B*x+@gYjd=OkaTyoeG?%pK^XxqQIdU=lZnFZvAP>mQ&Xq!)T@bB(^1{DhY(%U z)1Wt6PM8zJ#hng+=jQg-&aUP_3+d|WD&E<)d(Zafj+Wx~z$>R7ROBR%@*8R!9Z#M- zNziBDyN(@O5^HVeChumld|jzf?LTn+N1=D?AwrUNyX}_7?%VfoUOSTO(CxpWw?F3} zZ7IxJXD!yf`dZ(mt=AW2l=_y9dk$}0fBs_8$0eUe+AUYJ3xB=S9N$*8|Ni*{+AM)k z(EVQ%8s{Fo_xs$2;_z?TgMZ94;FjKsvgrePhxPHZFXCS*?UFJ00lBU3?u{E`4%LjU zFu+9}>OAy8ghcJ?5_Cd@Ms4b9EQ1J(TGa|%2@x)8QMI@ThDlUVb1)f(sa&s`a2AG{ z>{ZM02N>q$IuY<|a1yyzoQGe+1d%=B0vrV=m21Q$I0O?^c8kmLC^(s1Ew08sm|(I? zEX4pgIoT;z;L9+<$qt?gtC*gc^!>DyjjG&`cbu<5afrnSzELAtqga zQUeOB@oyNSX9{)c{KjB6nbeGPN}ZUDH=&g%wc>mnhZ&-H#6|cSv{I!;T#9F4hAM7x zB_4-Xrc{f?xDRHS;u6cS6IwaNDOTbRnBf$Mn1qesBZ^&2M>|Xr#U?JqQt(m5D(2#H zn4*eBT!9O~#}q+ag8);^UoURJpTWoZ>%F=zv+`xA9rH0!pag!e?U=1Zb$9H{vfaN<(#g9!|n64SDziJPRc(11#>V)hBQB==xX^Y^s|LzU#3j> zqe40f!Yq{8tSB__yydf z29LB1-v<*jxTTetkYNUwWWr`R#|#ds7%gy*8EjGs=7WhdSfnyc=x}3RsT{AtIc}^* zs>HY99yjKcIP^d{8M8_$mTrZ{JH*kTNJyHf9gGa>dlCp3!lvA@!%Em0{p=PgS z#4B)tnroyyd=nl~vr{TS8<#*@TUv{Ash!Ck*}({T zkbm76&V?SX`=roC)@DZMt1H+)^m9R~!n>LnQJdIC(mfDfN(S>sQt%^9jHzyRk#rvl zSCT<~Bm<}6b=Gk#>?LjbTWR5RwwK0FgxkpF{1GF5qKT!bMQo17kA?@xWqzapztY4s z>Uy@9#ZQNa$>V~NBJ9<~($qXQ$Kt2L5mKqYwK)6|{F(glNcekFnR#moo_q$H9$xO9 nZmY`EKD@I1;-&w}+i;3LhSLM)!Mol~<4?WX?eN-%tzzH54-d@P diff --git a/xyz.veronie.bgg.ui/icons/export_nandeck_60x60.png b/xyz.veronie.bgg.ui/icons/export_nandeck_60x60.png new file mode 100644 index 0000000000000000000000000000000000000000..8bfd4c918ef614e02d87b30bcc8e25be7a0cd62e GIT binary patch literal 5599 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 literal 0 HcmV?d00001 diff --git a/xyz.veronie.bgg.ui/icons/folder-symbol.png b/xyz.veronie.bgg.ui/icons/folder-symbol.png deleted file mode 100644 index 7863b0ac5b0e50e1d97122e2f6fced47e0601b42..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2270 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 literal 0 HcmV?d00001 diff --git a/xyz.veronie.bgg.ui/icons/noun_Meeple_60x60.png b/xyz.veronie.bgg.ui/icons/noun_Meeple_60x60.png new file mode 100644 index 0000000000000000000000000000000000000000..89d3c2ed400376715715cfce436756649a731952 GIT binary patch literal 1864 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 ZicfIzgwE1ha|r+d002ovPDHLkV1iwH`Rf1x literal 0 HcmV?d00001 diff --git a/xyz.veronie.bgg.ui/icons/noun_Undo_60x60.png b/xyz.veronie.bgg.ui/icons/noun_Undo_60x60.png new file mode 100644 index 0000000000000000000000000000000000000000..aaa98c3bbeebc0e6ecc26706201f9ff60d99e217 GIT binary patch literal 2006 zcmV;{2Pyc8P) zaB^>EX>4U6ba`-PAZ2)IW&i+q+U=KJlIti8h2L4lECERfiREB^s%8hX{5fDdopkri z+*|iPO~sTA5weiaCzB+Mzy6%z4;)l7hot7YQ{4McwIXp7r*9nt5)K`(`>^|o>pRT41 zMe|irG2H5`d*FCLa@n_KG{dPF+inHTV~q`PdTjAPYgC9qWd=&rsnVpe`$i2ZCLV0) znH#Vyn~QPbwuzE)H^yM-W-m9=Xt7x9s&AIacljS0)4_-13ht{M+Quv@qv- z*@&V)v_f9IDhp{Wa_b5&0HJwt(=G7bF8uMSeo(3)s9R>o1J-z)Ar7UF+LFy#;HT)% zw}OK8D*zKIcLjjldC3{Of8$4Te0fm$=Yvn9eLRBQAVApwJ9@Aoq5{yS!R7uyQqF?KS7NyYP?A;KYLJvHJIHY zXl*CDn1L840&!aeP|&=XMW>W_kz34SVZ0Q|NKzM@&}k6^!Ze6=(u3UxxgX*d)PIN@ z{};Ki(ESc_0qDMR`-WOy-*asfJKw^kX%xJDm>*sPdtF~@^;i3Mpm(5mpm(5mpm(5m zp#MRji60O66C3^pLKU6f@pI~%0004nX+uL$Nkc;*aB^>EX>4Tx0C=2zkv&MmP!xqv z(@I4uB6d)5$WWauh>AE$6^me@v=v%)FnQ@8G-*guTpR`0f`dPcRR>TSNJf5Faqe8n5iey ziy3&<*FAMp-9>qpci*4YtK>}v_(bA4rW+RV2Jy_MrE}gV4zrS^5T6r|8+1Y9N3P2* zzi}=)Ebz>*kx9)Hhl#~v2g@DIN`^{2O&n2Fjq-)8%L?Z$&T6H`TKD8H4CS?zG}md3 zAdV#@kc0>sHIz|-g(&SBDJD{M9`o=IJN_iOWO9|k$gzMbR7j2={11N5)+|g-x=EoJ z(Dh>5AEQ8U7iiXP`}^3on;_O#~r12klE zjsp{K{Qv*}24YJ`L;(K){{a7>y{D4^000SaNLh0L01FcU01FcV0GgZ_00007bV*G` z2ju}76b>vcSX=b~00P=cL_t(&-tC%8XcJKshQCRCqz#p5)G9upP>Pi*2--yzapS^8 zDJXQ)g@Ox<3*Fc*e1V998*%T-jVp1X2#O0I3$>_-D;4pLRiP40ee{`eaZl+G(`1}v zCO0?thXaA!ndHkkGjq2dq)RYk`|ZK#c-E0F3&8t$E-U;Eqqwng>1%{Q6JWssTG?9NP+90=fbo{|fjC zyaR@T7r-;%DKMo|Mjc=f_#Ggtequ~+dIy1YwPD+Vp^!ie#kf?_`lvzUz;WPDgs|So z0T+Q(6tElRx^gv$ufVP_VY{WQT#X_J43tx<#q=3i4s6!!rnytf`&L*VvO{KASF<=3 z<}cd_T#%B|3A#7zlIj9pYXqDHR)-DU0G!h(+gGD#EdjPjW6&P32cHJofR#W;!1K&# zEASwSyJiA7E&Xq~!^2`=4{$j^>v}!v$HgY#c?GAXlvIJ=r$bIGrARd&IO(%l@vC`2 ze*qJ)Pz+Y9IoJR^sqj494BRMj&fC@Oh~mJ}A_GTIohUI{I#gNffQJ=!Jo3@&pSwZD$H!xWMxJM_qq!qYdK~HOM0pNZejlb>+p6g5=@F5JB1KjlhA1k&vW&p4Y z?3d1rQkP52kSxCe&iYvxGpMxB17AwHIB|o3r=*{5lxw;!lmt4lva+(Wva+ffrTN#% zU*dVa2xX_vD3P~z1+V~R)+Z&j8m;oRo!NirhCO3f8}JCVCu5tey@xWuKcaT>q=5GV znLxRiL#Q1-+5#sfi(~SoQ$Pk~mrkSp%UJ_70gvVVg_6}tW-Zz#>D5P2n^&|2?(qy3 zX~~|0vPnmQZ<5WIDD%BR#L<0mA; oov1Ad zaB^>EX>4U6ba`-PAZ2)IW&i+q+U=KJlIti8h2L4lECERfiREB^s%8hX{5fF9opkri z+*|iPO~sTA5weiaCzB+Mzy6%z4;)l7hot7YR@nA#G z+<;}-T#O61O)RZOlTC_ragb{~f(Xof=R$kkve#?mSh*8inGlR{%Rjd8Z<9aM!kp`6 zBZ_`&g}iuGF49=!))ig=Li6ILTi{2#@W-e6L8*eEZkZhqSmSYqIFvqWOEzbLpQ1nC z3JTV*0FVf4E0Q4rftX2@GDTxHBIxJ`efnlEuKLNCm8v6!l}tQA1Tlld5Jl^`J#-mYlNYoGq_Su9{dfwQOc?#j1-ZSI=(l zUc44Af;~`6E>^shQY(jwg(_lI_*YmCKIBM;9{I4tk8;$8@@c7Q%gvg%(rV`}9ot0D z-MaVE>!2{OQ;alpT=4sPsnf0c2QT@_>f*M`ac#~Rwc2k2jm_0(! z+D>#a12Ikn;Tq%Jn0(;^0hX%OqAo81SwAL16&e~26Z z7rC&|{SI;g=)Q9MhFV|Wb8Qnl-@>J76ufBbKWNovy!9`pA(N8bV1@ruFEdJ zaV|P6@XWB0NzD_7iN#_E%N@*0hDtn598pw_@`bF+3g<1(YNf_n_v9}O<+YVG*J+I) zjwK|Jga{cmlu?0&DD4_4CQ@`B^Y9Nl{v^3%a+Sfzv4AR6NRA);4}Q*5p0lat9cE(j`N3 zBtI>oPypV~=$mrDz%9_T=FY8kj?)JqL$g}G0S*pX3k5fIp|w_VBWM@x zUVn#6e~wF+Zgdr!51~aXR1ieks)&jZh_=>>jf**fO9)blIWr{Zfy?CH%;ny9&U~D6 z=7M1uhG7_nVcZK3?pS~Lmj4cc{aS0F!Y#)c@S&ar7l41jTrG9!!hhCV27XlGeNTY- zkStv7W2aI959@7~Ho>({Lk0J)17DQi_r&M2G*b_rR&-hF70+r0X!58b4Qy50!bKPI zSpZAbdyPpeBjwuwJinBN;Ix#Zo&|3Kc@4qERvqyyIH}Qv&SkQT0T-Uf@2rO7k(GxH z0#7_!_>+d<;`kl&EcO7{)3R^~GjvA7mo7vs>|1b3;~|?;_66W@ywdFQ!1ZvekF1;6nucvU;6^$pnd zD0mUWFbu;m48t&jAOUojTx*N`ZUI^&=glQ`%bmBCTx<0uuH63cxLlvaeO*@!yk7!q z!1qW@oCF?ABTNG;z=T9p{lF4%C5`wBcq9S*F|ZJd;HM(s4{j^vIgpc3@*1~W*CUqu z3=Bv>9>eVhbm8`#8h}%vLxf%c?GjqXfum3af0QyNmf4efow&~nk8yeFZCq|RE+P`R z)JqPy1b#|(A}GoUZWrtH;qt$E;1}>h#$H}BC8u)#U+LCEVzo=@#yM*6V^hNjj2q&l VG|f&TL6!gj002ovPDHLkV1oavQ)>VK literal 0 HcmV?d00001 diff --git a/xyz.veronie.bgg.ui/icons/only_new_60x60.png b/xyz.veronie.bgg.ui/icons/only_new_60x60.png new file mode 100644 index 0000000000000000000000000000000000000000..77c5e58230b0bb162d57298fcbd97b03299d2832 GIT binary patch literal 7111 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 SoQN9$0000 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 D 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 YF)+Xj$~CikvByyYb||cMtEDgbUm7uoSpWb4 diff --git a/xyz.veronie.bgg.ui/icons/save-button_20x20.png b/xyz.veronie.bgg.ui/icons/save-button_20x20.png deleted file mode 100644 index 1a9b96ee1cdd9db796fa9080d822a1f4e8c4658e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3599 zcmeH~_ct2~8^;qQLTWZiUD67ws9C#8>=7lD+FNQ})Sk79qKZ;#*QyRht|CQ@5{lX@ zja@~l8O0T?^}6pl??3SVe4lfkdCv3v@crrYJT}JavM_Tq0{{RPjGmUsd4~Pt5c=~- zwAfMw0GNEk?^*_$IEA2m{e4_Lyj)O$VZJUXmrxH^03dXF%gV-olgM(sdcBt684BWm zGa|-pNW(gF>uoVXN}x0mPr4NlQkt>Sh@t${&wu+3d0`)U*aC~|T>!nit#Y*-( z4yD=z1ZOYdanu@UpbedxeRc@7H5&W_w0zXts(a?Q&w4zG-`i*Hk8p~QGm>ODMIN*K z9Piq%5pJ_z2;JM`SSGky-5y@I?o*R>xbl52HBBXs=N9VCiDmb-L#Z?uAGpoWK{jLb z$XTxmDj1g3noX}!lej|p)J_Ka;(xt&%B+qOZ79E6?yJl=D_wO ziaQP4&>{q21IqOGQ-N_H<=y_llkG)^safkz6my$$O|0GMF}EvMz(H!Wnx5q)pB7+S z!;`49WmR7@sa~FySdjeJVfGiD1wNxIdA_{3XboY-JPIHN5+IYp!~l*+pd}((x`g=v zJ#?|$Vhxi&98pp|OaeImzEOTcpMPJpaDJfNU=eo(aR5Eyl0?u3keBF43eX@+#XLC%CMJZ_*bheECx5nfunM zD&dG{Xn(Te&5%mS+AGRDc-F1FLxAlMmx)o=LfMRKu0_)QRdpV|$p)nutGZ-F^)w>F zdsP34g?(+`yGj+yX3LYB6VMmZC+k^*smNVN7Pl9@Dq1yxr0zeso`}Tw-2g9)j4JL4 zmcvqSE)7tUN){jjQ;mKyT(}Y`kE9k-T(iPTTUWu-uHgJ)ac+Rl+}Dmm`XJu0qj-Wx z(0#9NER6eg5qo4K{#BD@KAdnce!>f0XFi4MwGZ5YV@A`6sHGvx>v*BBh$zI!M-7`e ztvHY)80Ca#YJaf&*_%B7=6CF(nU;y^BWUWPMW54$Ma+}>$;1NBuCS+Y`0+6KPygQ8 zX?EkDNEdF=1x&G9UvCY^XRfCfFRIAN@{Dgeke1z~qTwc=tvQEClzT^gg`}bP_uA2Z zh*5*Jb%qP`?4!eQ{~-MYRwt$kzFV6*%tU;|vNc^aZx2TfM-c?Qc+yZdjb~LuEeE zYUajEgy#d7%zrIhF|T5el=6tj?l^-=+{5fQa2iS+~`RIBAAI_E{WzTRT@s zh;Q>nBP1eXcl2!?&McV=CKf7wIOU~oEaSv>jq5oO1p|`ACSSI_!gC~!(;gpHxW%MG z>6tnYo_Y*jrCi9(95SA{Q>I|-sa!%L->{<7X@@l=jJkX1ndy`f9NC6+KLFBT(r=~9A)JH{@7dZh&@qHpQiS2RHKs)&MZwt{ zwbkf^#|(*qfb?4G#e?pFo%fO3x&-mV^#c~((>@7-H$Ia(HKnehsKnrYkua;tBqcwa zLTl3cjx1V6@}5!oQft>rhXX;N=~;q?ZSPE|(aWYxaNkBKVw@71{rj(YwoLVy&9etx2gZmc)we;>|fD#=|qU zvl)8zW}6!cCgo^$fIGX1wue(awo*s}Cz4fGnVvjV&Y%A&W!Iuqd(=;(mO1}-qSwvu zR1tz7cUL$E-vWC+Aw-s5HWB~gV)eH*e#`1>K59VU(UO-5+5_52p<>ddVLip&iYa(`q#E0$Rt zmN)9MuTJQ2xF0b0J|y_jtW8s?AVbKM!gVo@L3XWph0AW-2e(buvG4;4U-S8@ zpEG}Z=QI#L{Qxf}I2D;RIvt8LZ!^}+UZmp3Pw2qM@e6U~aF$3>?Jm(4BaX5cO*-C| zp9&#PDNs3TI4;B38LO7mg55CTG@SU^?=oZwBm%f&?E4RG-3wnWs9Ojtlqnr}dW6bH ztOc$Xdr;BWvVbbB*}xXlQoo!Ts>wT-1+1lUjE>`9$+jm$0~dORKAL}LeOznvmfdOV^46!UGMj1YUYEe@A2VDwe{bi>bE9DMm8?&TiY6959q_MPQ9b|=LHy7HnvT9 z7XM9N*$#O?8wZ{?gXxOvh|%<0gFe~8IfQcr1IBx3Y8qoSHUGz6=fqVQnW3WBsfPUI zVETk#oOR2%i%&DC7Il45*i0<;(U`eAxnp@u(tRwwyit)E4`NhBRI*A85tSO5V;2~c zGGAtvJ;^%!SU$qv9k&$K(oAc7zzC`4HXUqZQX`8AWEv#>BE6eDO9qrd_)=oJ3{;-m z4Ri!V%ysrqniYq4(X>^Fh;}9roe`S0mZ06Du3YD_R!Dm+P{l9Owc51=wo5&@hC?^2Q*82ody1BpuGB&z=@aQ}gjTD_Yy9(Mv}7r?|{s z5(EI~G>r}JYO^r|&d$!x0r_A1SAqXefgnXSzVl6Bpb6F#ptdr3{z?IW&bM{707;dispose() method to + * release the operating system resources managed by cached objects when those + * objects and OS resources are no longer needed. + * + * This class may be freely distributed as part of any application or plugin. + *

+ * + * @author scheglov_ke + * @author Dan Rubel + * @author Wim Jongman + */ +public class ResourceManager extends SWTResourceManager { + + /** + * The map where we store our images. + */ + private static Map m_descriptorImageMap = new HashMap(); + + /** + * Returns an {@link ImageDescriptor} stored in the file at the specified path + * relative to the specified class. + * + * @param clazz the {@link Class} relative to which to find the image + * descriptor. + * @param path the path to the image file. + * @return the {@link ImageDescriptor} stored in the file at the specified path. + */ + public static ImageDescriptor getImageDescriptor(Class clazz, String path) { + return ImageDescriptor.createFromFile(clazz, path); + } + + /** + * Returns an {@link ImageDescriptor} stored in the file at the specified path. + * + * @param path the path to the image file. + * @return the {@link ImageDescriptor} stored in the file at the specified path. + */ + public static ImageDescriptor getImageDescriptor(String path) { + try { + return ImageDescriptor.createFromURL(new File(path).toURI().toURL()); + } catch (MalformedURLException e) { + return null; + } + } + + /** + * Returns an {@link Image} based on the specified {@link ImageDescriptor}. + * + * @param descriptor the {@link ImageDescriptor} for the {@link Image}. + * @return the {@link Image} based on the specified {@link ImageDescriptor}. + */ + public static Image getImage(ImageDescriptor descriptor) { + if (descriptor == null) { + return null; + } + Image image = m_descriptorImageMap.get(descriptor); + if (image == null) { + image = descriptor.createImage(); + m_descriptorImageMap.put(descriptor, image); + } + return image; + } + + /** + * Maps images to decorated images. + */ + @SuppressWarnings("unchecked") + private static Map>[] m_decoratedImageMap = new Map[LAST_CORNER_KEY]; + + /** + * Returns an {@link Image} composed of a base image decorated by another image. + * + * @param baseImage the base {@link Image} that should be decorated. + * @param decorator the {@link Image} to decorate the base image. + * @return {@link Image} The resulting decorated image. + */ + public static Image decorateImage(Image baseImage, Image decorator) { + return decorateImage(baseImage, decorator, BOTTOM_RIGHT); + } + + /** + * Returns an {@link Image} composed of a base image decorated by another image. + * + * @param baseImage + * the base {@link Image} that should be decorated. + * @param decorator + * the {@link Image} to decorate the base image. + * @param corner + * the corner to place decorator image. + * @return the resulting decorated {@link Image}. + */ + public static Image decorateImage(final Image baseImage, final Image decorator, final int corner) { + if (corner <= 0 || corner >= LAST_CORNER_KEY) { + throw new IllegalArgumentException("Wrong decorate corner"); + } + Map> cornerDecoratedImageMap = m_decoratedImageMap[corner]; + if (cornerDecoratedImageMap == null) { + cornerDecoratedImageMap = new HashMap>(); + m_decoratedImageMap[corner] = cornerDecoratedImageMap; + } + Map decoratedMap = cornerDecoratedImageMap.get(baseImage); + if (decoratedMap == null) { + decoratedMap = new HashMap(); + cornerDecoratedImageMap.put(baseImage, decoratedMap); + } + // + Image result = decoratedMap.get(decorator); + if (result == null) { + final Rectangle bib = baseImage.getBounds(); + final Rectangle dib = decorator.getBounds(); + final Point baseImageSize = new Point(bib.width, bib.height); + CompositeImageDescriptor compositImageDesc = new CompositeImageDescriptor() { + @Override + protected void drawCompositeImage(int width, int height) { + drawImage(createCachedImageDataProvider(baseImage), 0, 0); + if (corner == TOP_LEFT) { + drawImage(getUnzoomedImageDataProvider(decorator.getImageData()) , 0, 0); + } else if (corner == TOP_RIGHT) { + drawImage(getUnzoomedImageDataProvider(decorator.getImageData()), bib.width - dib.width, 0); + } else if (corner == BOTTOM_LEFT) { + drawImage(getUnzoomedImageDataProvider(decorator.getImageData()), 0, bib.height - dib.height); + } else if (corner == BOTTOM_RIGHT) { + drawImage(getUnzoomedImageDataProvider(decorator.getImageData()), bib.width - dib.width, bib.height - dib.height); + } + } + @Override + protected Point getSize() { + return baseImageSize; + } + }; + // + result = compositImageDesc.createImage(); + decoratedMap.put(decorator, result); + } + return result; + } + + private static ImageDataProvider getUnzoomedImageDataProvider(ImageData imageData) { + return zoom -> zoom == 100 ? imageData : null; + } + + + /** + * Dispose all of the cached images. + */ + public static void disposeImages() { + SWTResourceManager.disposeImages(); + // dispose ImageDescriptor images + { + for (Iterator I = m_descriptorImageMap.values().iterator(); I.hasNext();) { + I.next().dispose(); + } + m_descriptorImageMap.clear(); + } + // dispose decorated images + for (int i = 0; i < m_decoratedImageMap.length; i++) { + Map> cornerDecoratedImageMap = m_decoratedImageMap[i]; + if (cornerDecoratedImageMap != null) { + for (Map decoratedMap : cornerDecoratedImageMap.values()) { + for (Image image : decoratedMap.values()) { + image.dispose(); + } + decoratedMap.clear(); + } + cornerDecoratedImageMap.clear(); + } + } + // dispose plugin images + { + for (Iterator I = m_URLImageMap.values().iterator(); I.hasNext();) { + I.next().dispose(); + } + m_URLImageMap.clear(); + } + } + + //////////////////////////////////////////////////////////////////////////// + // + // Plugin images support + // + //////////////////////////////////////////////////////////////////////////// + /** + * Maps URL to images. + */ + private static Map m_URLImageMap = new HashMap(); + + /** + * Provider for plugin resources, used by WindowBuilder at design time. + */ + public interface PluginResourceProvider { + URL getEntry(String symbolicName, String path); + } + + /** + * Instance of {@link PluginResourceProvider}, used by WindowBuilder at design + * time. + */ + private static PluginResourceProvider m_designTimePluginResourceProvider = null; + + /** + * Returns an {@link Image} based on a plugin and file path. + * + * @param plugin the plugin {@link Object} containing the image + * @param name the path to the image within the plugin + * @return the {@link Image} stored in the file at the specified path + * + * @deprecated Use {@link #getPluginImage(String, String)} instead. + */ + @Deprecated + public static Image getPluginImage(Object plugin, String name) { + try { + URL url = getPluginImageURL(plugin, name); + if (url != null) { + return getPluginImageFromUrl(url); + } + } catch (Throwable e) { + // Ignore any exceptions + } + return null; + } + + /** + * Returns an {@link Image} based on a {@link Bundle} and resource entry path. + * + * @param symbolicName the symbolic name of the {@link Bundle}. + * @param path the path of the resource entry. + * @return the {@link Image} stored in the file at the specified path. + */ + public static Image getPluginImage(String symbolicName, String path) { + try { + URL url = getPluginImageURL(symbolicName, path); + if (url != null) { + return getPluginImageFromUrl(url); + } + } catch (Throwable e) { + // Ignore any exceptions + } + return null; + } + + /** + * Returns an {@link Image} based on given {@link URL}. + */ + private static Image getPluginImageFromUrl(URL url) { + try { + try { + String key = url.toExternalForm(); + Image image = m_URLImageMap.get(key); + if (image == null) { + InputStream stream = url.openStream(); + try { + image = getImage(stream); + m_URLImageMap.put(key, image); + } finally { + stream.close(); + } + } + return image; + } catch (Throwable e) { + // Ignore any exceptions + } + } catch (Throwable e) { + // Ignore any exceptions + } + return null; + } + + /** + * Returns an {@link ImageDescriptor} based on a plugin and file path. + * + * @param plugin the plugin {@link Object} containing the image. + * @param name the path to th eimage within the plugin. + * @return the {@link ImageDescriptor} stored in the file at the specified path. + * + * @deprecated Use {@link #getPluginImageDescriptor(String, String)} instead. + */ + @Deprecated + public static ImageDescriptor getPluginImageDescriptor(Object plugin, String name) { + try { + try { + URL url = getPluginImageURL(plugin, name); + return ImageDescriptor.createFromURL(url); + } catch (Throwable e) { + // Ignore any exceptions + } + } catch (Throwable e) { + // Ignore any exceptions + } + return null; + } + + /** + * Returns an {@link ImageDescriptor} based on a {@link Bundle} and resource + * entry path. + * + * @param symbolicName the symbolic name of the {@link Bundle}. + * @param path the path of the resource entry. + * @return the {@link ImageDescriptor} based on a {@link Bundle} and resource + * entry path. + */ + public static ImageDescriptor getPluginImageDescriptor(String symbolicName, String path) { + try { + URL url = getPluginImageURL(symbolicName, path); + if (url != null) { + return ImageDescriptor.createFromURL(url); + } + } catch (Throwable e) { + // Ignore any exceptions + } + return null; + } + + /** + * Returns an {@link URL} based on a {@link Bundle} and resource entry path. + */ + private static URL getPluginImageURL(String symbolicName, String path) { + // try runtime plugins + { + Bundle bundle = Platform.getBundle(symbolicName); + if (bundle != null) { + return bundle.getEntry(path); + } + } + // try design time provider + if (m_designTimePluginResourceProvider != null) { + return m_designTimePluginResourceProvider.getEntry(symbolicName, path); + } + // no such resource + return null; + } + + /** + * Returns an {@link URL} based on a plugin and file path. + * + * @param plugin the plugin {@link Object} containing the file path. + * @param name the file path. + * @return the {@link URL} representing the file at the specified path. + * @throws Exception + */ + private static URL getPluginImageURL(Object plugin, String name) throws Exception { + // try to work with 'plugin' as with OSGI BundleContext + try { + Class BundleClass = Class.forName("org.osgi.framework.Bundle"); //$NON-NLS-1$ + Class BundleContextClass = Class.forName("org.osgi.framework.BundleContext"); //$NON-NLS-1$ + if (BundleContextClass.isAssignableFrom(plugin.getClass())) { + Method getBundleMethod = BundleContextClass.getMethod("getBundle", new Class[0]); //$NON-NLS-1$ + Object bundle = getBundleMethod.invoke(plugin, new Object[0]); + // + Class PathClass = Class.forName("org.eclipse.core.runtime.Path"); //$NON-NLS-1$ + Constructor pathConstructor = PathClass.getConstructor(new Class[] { String.class }); + Object path = pathConstructor.newInstance(new Object[] { name }); + // + Class IPathClass = Class.forName("org.eclipse.core.runtime.IPath"); //$NON-NLS-1$ + Class PlatformClass = Class.forName("org.eclipse.core.runtime.Platform"); //$NON-NLS-1$ + Method findMethod = PlatformClass.getMethod("find", new Class[] { BundleClass, IPathClass }); //$NON-NLS-1$ + return (URL) findMethod.invoke(null, new Object[] { bundle, path }); + } + } catch (Throwable e) { + // Ignore any exceptions + } + // else work with 'plugin' as with usual Eclipse plugin + { + Class PluginClass = Class.forName("org.eclipse.core.runtime.Plugin"); //$NON-NLS-1$ + if (PluginClass.isAssignableFrom(plugin.getClass())) { + // + Class PathClass = Class.forName("org.eclipse.core.runtime.Path"); //$NON-NLS-1$ + Constructor pathConstructor = PathClass.getConstructor(new Class[] { String.class }); + Object path = pathConstructor.newInstance(new Object[] { name }); + // + Class IPathClass = Class.forName("org.eclipse.core.runtime.IPath"); //$NON-NLS-1$ + Method findMethod = PluginClass.getMethod("find", new Class[] { IPathClass }); //$NON-NLS-1$ + return (URL) findMethod.invoke(plugin, new Object[] { path }); + } + } + return null; + } + + //////////////////////////////////////////////////////////////////////////// + // + // General + // + //////////////////////////////////////////////////////////////////////////// + /** + * Dispose of cached objects and their underlying OS resources. This should only + * be called when the cached objects are no longer needed (e.g. on application + * shutdown). + */ + public static void dispose() { + disposeColors(); + disposeFonts(); + disposeImages(); + } +} \ No newline at end of file diff --git a/xyz.veronie.bgg.ui/src/org/eclipse/wb/swt/SWTResourceManager.java b/xyz.veronie.bgg.ui/src/org/eclipse/wb/swt/SWTResourceManager.java new file mode 100644 index 0000000..d8a2858 --- /dev/null +++ b/xyz.veronie.bgg.ui/src/org/eclipse/wb/swt/SWTResourceManager.java @@ -0,0 +1,447 @@ +/******************************************************************************* + * Copyright (c) 2011 Google, Inc. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Google, Inc. - initial API and implementation + *******************************************************************************/ +package org.eclipse.wb.swt; + +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.HashMap; +import java.util.Map; + +import org.eclipse.swt.SWT; +import org.eclipse.swt.graphics.Color; +import org.eclipse.swt.graphics.Cursor; +import org.eclipse.swt.graphics.Font; +import org.eclipse.swt.graphics.FontData; +import org.eclipse.swt.graphics.GC; +import org.eclipse.swt.graphics.Image; +import org.eclipse.swt.graphics.ImageData; +import org.eclipse.swt.graphics.RGB; +import org.eclipse.swt.graphics.Rectangle; +import org.eclipse.swt.widgets.Display; + +/** + * Utility class for managing OS resources associated with SWT controls such as colors, fonts, images, etc. + *

+ * !!! IMPORTANT !!! Application code must explicitly invoke the dispose() method to release the + * operating system resources managed by cached objects when those objects and OS resources are no longer + * needed (e.g. on application shutdown) + *

+ * This class may be freely distributed as part of any application or plugin. + *

+ * @author scheglov_ke + * @author Dan Rubel + */ +public class SWTResourceManager { + //////////////////////////////////////////////////////////////////////////// + // + // Color + // + //////////////////////////////////////////////////////////////////////////// + private static Map m_colorMap = new HashMap(); + /** + * Returns the system {@link Color} matching the specific ID. + * + * @param systemColorID + * the ID value for the color + * @return the system {@link Color} matching the specific ID + */ + public static Color getColor(int systemColorID) { + Display display = Display.getCurrent(); + return display.getSystemColor(systemColorID); + } + /** + * Returns a {@link Color} given its red, green and blue component values. + * + * @param r + * the red component of the color + * @param g + * the green component of the color + * @param b + * the blue component of the color + * @return the {@link Color} matching the given red, green and blue component values + */ + public static Color getColor(int r, int g, int b) { + return getColor(new RGB(r, g, b)); + } + /** + * Returns a {@link Color} given its RGB value. + * + * @param rgb + * the {@link RGB} value of the color + * @return the {@link Color} matching the RGB value + */ + public static Color getColor(RGB rgb) { + Color color = m_colorMap.get(rgb); + if (color == null) { + Display display = Display.getCurrent(); + color = new Color(display, rgb); + m_colorMap.put(rgb, color); + } + return color; + } + /** + * Dispose of all the cached {@link Color}'s. + */ + public static void disposeColors() { + for (Color color : m_colorMap.values()) { + color.dispose(); + } + m_colorMap.clear(); + } + //////////////////////////////////////////////////////////////////////////// + // + // Image + // + //////////////////////////////////////////////////////////////////////////// + /** + * Maps image paths to images. + */ + private static Map m_imageMap = new HashMap(); + /** + * Returns an {@link Image} encoded by the specified {@link InputStream}. + * + * @param stream + * the {@link InputStream} encoding the image data + * @return the {@link Image} encoded by the specified input stream + */ + protected static Image getImage(InputStream stream) throws IOException { + try { + Display display = Display.getCurrent(); + ImageData data = new ImageData(stream); + if (data.transparentPixel > 0) { + return new Image(display, data, data.getTransparencyMask()); + } + return new Image(display, data); + } finally { + stream.close(); + } + } + /** + * Returns an {@link Image} stored in the file at the specified path. + * + * @param path + * the path to the image file + * @return the {@link Image} stored in the file at the specified path + */ + public static Image getImage(String path) { + Image image = m_imageMap.get(path); + if (image == null) { + try { + image = getImage(new FileInputStream(path)); + m_imageMap.put(path, image); + } catch (Exception e) { + image = getMissingImage(); + m_imageMap.put(path, image); + } + } + return image; + } + /** + * Returns an {@link Image} stored in the file at the specified path relative to the specified class. + * + * @param clazz + * the {@link Class} relative to which to find the image + * @param path + * the path to the image file, if starts with '/' + * @return the {@link Image} stored in the file at the specified path + */ + public static Image getImage(Class clazz, String path) { + String key = clazz.getName() + '|' + path; + Image image = m_imageMap.get(key); + if (image == null) { + try { + image = getImage(clazz.getResourceAsStream(path)); + m_imageMap.put(key, image); + } catch (Exception e) { + image = getMissingImage(); + m_imageMap.put(key, image); + } + } + return image; + } + private static final int MISSING_IMAGE_SIZE = 10; + /** + * @return the small {@link Image} that can be used as placeholder for missing image. + */ + private static Image getMissingImage() { + Image image = new Image(Display.getCurrent(), MISSING_IMAGE_SIZE, MISSING_IMAGE_SIZE); + // + GC gc = new GC(image); + gc.setBackground(getColor(SWT.COLOR_RED)); + gc.fillRectangle(0, 0, MISSING_IMAGE_SIZE, MISSING_IMAGE_SIZE); + gc.dispose(); + // + return image; + } + /** + * Style constant for placing decorator image in top left corner of base image. + */ + public static final int TOP_LEFT = 1; + /** + * Style constant for placing decorator image in top right corner of base image. + */ + public static final int TOP_RIGHT = 2; + /** + * Style constant for placing decorator image in bottom left corner of base image. + */ + public static final int BOTTOM_LEFT = 3; + /** + * Style constant for placing decorator image in bottom right corner of base image. + */ + public static final int BOTTOM_RIGHT = 4; + /** + * Internal value. + */ + protected static final int LAST_CORNER_KEY = 5; + /** + * Maps images to decorated images. + */ + @SuppressWarnings("unchecked") + private static Map>[] m_decoratedImageMap = new Map[LAST_CORNER_KEY]; + /** + * Returns an {@link Image} composed of a base image decorated by another image. + * + * @param baseImage + * the base {@link Image} that should be decorated + * @param decorator + * the {@link Image} to decorate the base image + * @return {@link Image} The resulting decorated image + */ + public static Image decorateImage(Image baseImage, Image decorator) { + return decorateImage(baseImage, decorator, BOTTOM_RIGHT); + } + /** + * Returns an {@link Image} composed of a base image decorated by another image. + * + * @param baseImage + * the base {@link Image} that should be decorated + * @param decorator + * the {@link Image} to decorate the base image + * @param corner + * the corner to place decorator image + * @return the resulting decorated {@link Image} + */ + public static Image decorateImage(final Image baseImage, final Image decorator, final int corner) { + if (corner <= 0 || corner >= LAST_CORNER_KEY) { + throw new IllegalArgumentException("Wrong decorate corner"); + } + Map> cornerDecoratedImageMap = m_decoratedImageMap[corner]; + if (cornerDecoratedImageMap == null) { + cornerDecoratedImageMap = new HashMap>(); + m_decoratedImageMap[corner] = cornerDecoratedImageMap; + } + Map decoratedMap = cornerDecoratedImageMap.get(baseImage); + if (decoratedMap == null) { + decoratedMap = new HashMap(); + cornerDecoratedImageMap.put(baseImage, decoratedMap); + } + // + Image result = decoratedMap.get(decorator); + if (result == null) { + Rectangle bib = baseImage.getBounds(); + Rectangle dib = decorator.getBounds(); + // + result = new Image(Display.getCurrent(), bib.width, bib.height); + // + GC gc = new GC(result); + gc.drawImage(baseImage, 0, 0); + if (corner == TOP_LEFT) { + gc.drawImage(decorator, 0, 0); + } else if (corner == TOP_RIGHT) { + gc.drawImage(decorator, bib.width - dib.width, 0); + } else if (corner == BOTTOM_LEFT) { + gc.drawImage(decorator, 0, bib.height - dib.height); + } else if (corner == BOTTOM_RIGHT) { + gc.drawImage(decorator, bib.width - dib.width, bib.height - dib.height); + } + gc.dispose(); + // + decoratedMap.put(decorator, result); + } + return result; + } + /** + * Dispose all of the cached {@link Image}'s. + */ + public static void disposeImages() { + // dispose loaded images + { + for (Image image : m_imageMap.values()) { + image.dispose(); + } + m_imageMap.clear(); + } + // dispose decorated images + for (int i = 0; i < m_decoratedImageMap.length; i++) { + Map> cornerDecoratedImageMap = m_decoratedImageMap[i]; + if (cornerDecoratedImageMap != null) { + for (Map decoratedMap : cornerDecoratedImageMap.values()) { + for (Image image : decoratedMap.values()) { + image.dispose(); + } + decoratedMap.clear(); + } + cornerDecoratedImageMap.clear(); + } + } + } + //////////////////////////////////////////////////////////////////////////// + // + // Font + // + //////////////////////////////////////////////////////////////////////////// + /** + * Maps font names to fonts. + */ + private static Map m_fontMap = new HashMap(); + /** + * Maps fonts to their bold versions. + */ + private static Map m_fontToBoldFontMap = new HashMap(); + /** + * Returns a {@link Font} based on its name, height and style. + * + * @param name + * the name of the font + * @param height + * the height of the font + * @param style + * the style of the font + * @return {@link Font} The font matching the name, height and style + */ + public static Font getFont(String name, int height, int style) { + return getFont(name, height, style, false, false); + } + /** + * Returns a {@link Font} based on its name, height and style. Windows-specific strikeout and underline + * flags are also supported. + * + * @param name + * the name of the font + * @param size + * the size of the font + * @param style + * the style of the font + * @param strikeout + * the strikeout flag (warning: Windows only) + * @param underline + * the underline flag (warning: Windows only) + * @return {@link Font} The font matching the name, height, style, strikeout and underline + */ + public static Font getFont(String name, int size, int style, boolean strikeout, boolean underline) { + String fontName = name + '|' + size + '|' + style + '|' + strikeout + '|' + underline; + Font font = m_fontMap.get(fontName); + if (font == null) { + FontData fontData = new FontData(name, size, style); + if (strikeout || underline) { + try { + Class logFontClass = Class.forName("org.eclipse.swt.internal.win32.LOGFONT"); //$NON-NLS-1$ + Object logFont = FontData.class.getField("data").get(fontData); //$NON-NLS-1$ + if (logFont != null && logFontClass != null) { + if (strikeout) { + logFontClass.getField("lfStrikeOut").set(logFont, Byte.valueOf((byte) 1)); //$NON-NLS-1$ + } + if (underline) { + logFontClass.getField("lfUnderline").set(logFont, Byte.valueOf((byte) 1)); //$NON-NLS-1$ + } + } + } catch (Throwable e) { + System.err.println("Unable to set underline or strikeout" + " (probably on a non-Windows platform). " + e); //$NON-NLS-1$ //$NON-NLS-2$ + } + } + font = new Font(Display.getCurrent(), fontData); + m_fontMap.put(fontName, font); + } + return font; + } + /** + * Returns a bold version of the given {@link Font}. + * + * @param baseFont + * the {@link Font} for which a bold version is desired + * @return the bold version of the given {@link Font} + */ + public static Font getBoldFont(Font baseFont) { + Font font = m_fontToBoldFontMap.get(baseFont); + if (font == null) { + FontData fontDatas[] = baseFont.getFontData(); + FontData data = fontDatas[0]; + font = new Font(Display.getCurrent(), data.getName(), data.getHeight(), SWT.BOLD); + m_fontToBoldFontMap.put(baseFont, font); + } + return font; + } + /** + * Dispose all of the cached {@link Font}'s. + */ + public static void disposeFonts() { + // clear fonts + for (Font font : m_fontMap.values()) { + font.dispose(); + } + m_fontMap.clear(); + // clear bold fonts + for (Font font : m_fontToBoldFontMap.values()) { + font.dispose(); + } + m_fontToBoldFontMap.clear(); + } + //////////////////////////////////////////////////////////////////////////// + // + // Cursor + // + //////////////////////////////////////////////////////////////////////////// + /** + * Maps IDs to cursors. + */ + private static Map m_idToCursorMap = new HashMap(); + /** + * Returns the system cursor matching the specific ID. + * + * @param id + * int The ID value for the cursor + * @return Cursor The system cursor matching the specific ID + */ + public static Cursor getCursor(int id) { + Integer key = Integer.valueOf(id); + Cursor cursor = m_idToCursorMap.get(key); + if (cursor == null) { + cursor = new Cursor(Display.getDefault(), id); + m_idToCursorMap.put(key, cursor); + } + return cursor; + } + /** + * Dispose all of the cached cursors. + */ + public static void disposeCursors() { + for (Cursor cursor : m_idToCursorMap.values()) { + cursor.dispose(); + } + m_idToCursorMap.clear(); + } + //////////////////////////////////////////////////////////////////////////// + // + // General + // + //////////////////////////////////////////////////////////////////////////// + /** + * Dispose of cached objects and their underlying OS resources. This should only be called when the cached + * objects are no longer needed (e.g. on application shutdown). + */ + public static void dispose() { + disposeColors(); + disposeImages(); + disposeFonts(); + disposeCursors(); + } +} \ No newline at end of file diff --git a/xyz.veronie.bgg.ui/src/xyz/veronie/bgg/ui/dialogs/SaveGameListDialog.java b/xyz.veronie.bgg.ui/src/xyz/veronie/bgg/ui/dialogs/SaveGameListDialog.java index d298fa1..af4931e 100644 --- a/xyz.veronie.bgg.ui/src/xyz/veronie/bgg/ui/dialogs/SaveGameListDialog.java +++ b/xyz.veronie.bgg.ui/src/xyz/veronie/bgg/ui/dialogs/SaveGameListDialog.java @@ -1,70 +1,93 @@ package xyz.veronie.bgg.ui.dialogs; -import static org.eclipse.jface.widgets.WidgetFactory.text; - import org.eclipse.jface.dialogs.Dialog; +import org.eclipse.jface.dialogs.IDialogConstants; import org.eclipse.swt.SWT; -import org.eclipse.swt.events.FocusEvent; -import org.eclipse.swt.events.FocusListener; +import org.eclipse.swt.events.ModifyEvent; +import org.eclipse.swt.events.ModifyListener; import org.eclipse.swt.graphics.Point; import org.eclipse.swt.layout.GridData; import org.eclipse.swt.layout.GridLayout; import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Control; +import org.eclipse.swt.widgets.Label; import org.eclipse.swt.widgets.Shell; import org.eclipse.swt.widgets.Text; +import org.eclipse.wb.swt.SWTResourceManager; + +import xyz.veronie.bgg.ui.helpers.BatColors; public class SaveGameListDialog extends Dialog { + private Text textField; private String entryString; + /** + * Create the dialog. + * @param parentShell + */ public SaveGameListDialog(Shell parentShell) { - super(parentShell); - } - - @Override - protected Control createDialogArea(Composite parent) { - Composite container = (Composite) super.createDialogArea(parent); - - GridLayout layout = new GridLayout(1, false); - layout.marginLeft = 16; - layout.marginTop = 16; - layout.marginRight = 16; - layout.marginBottom = 16; - container.setLayout(layout); + super(parentShell); + setShellStyle(SWT.APPLICATION_MODAL); + } - Text text = text(SWT.BORDER).message("Enter game list name") - .limitTo(255) - .layoutData(new GridData(SWT.CENTER, SWT.CENTER, true, false)) - .create(container); + /** + * Create contents of the dialog. + * @param parent + */ + @Override + protected Control createDialogArea(Composite parent) { + parent.setBackground(BatColors.getBackgroundColor()); + Composite container = (Composite) super.createDialogArea(parent); + container.setFont(SWTResourceManager.getFont("Segoe UI", 11, SWT.NORMAL)); + container.setBackground(BatColors.getBackgroundColor()); + GridLayout gl_container = new GridLayout(1, false); + gl_container.verticalSpacing = 24; + gl_container.marginWidth = 24; + gl_container.marginTop = 24; + gl_container.marginRight = 24; + gl_container.marginLeft = 24; + gl_container.marginHeight = 24; + gl_container.marginBottom = 24; + gl_container.horizontalSpacing = 24; + container.setLayout(gl_container); + + Label lblEnterAName = new Label(container, SWT.NONE); + lblEnterAName.setFont(SWTResourceManager.getFont("Segoe UI", 11, SWT.NORMAL)); + lblEnterAName.setText("Enter a name for the game list:"); + + textField = new Text(container, SWT.BORDER); + textField.addModifyListener(new ModifyListener() { + public void modifyText(ModifyEvent e) { + Text t = (Text) e.widget; + entryString = t.getText(); + } + }); + textField.setFont(SWTResourceManager.getFont("Segoe UI", 11, SWT.NORMAL)); + textField.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false, 1, 1)); + return container; + } - FocusListener focusListener = new FocusListener() { - public void focusGained(FocusEvent e) { - } + /** + * Create contents of the button bar. + * @param parent + */ + @Override + protected void createButtonsForButtonBar(Composite parent) { + parent.setBackground(BatColors.getBackgroundColor()); + createButton(parent, IDialogConstants.OK_ID, IDialogConstants.OK_LABEL, true); + createButton(parent, IDialogConstants.CANCEL_ID, IDialogConstants.CANCEL_LABEL, false); + } - public void focusLost(FocusEvent e) { - Text t = (Text) e.widget; - entryString = t.getText(); - } - }; - text.addFocusListener(focusListener); - - return container; - } - - // overriding this methods allows you to set the - // title of the custom dialog - @Override - protected void configureShell(Shell newShell) { - super.configureShell(newShell); - newShell.setText("Enter game list name"); - } + /** + * Return the initial size of the dialog. + */ + @Override + protected Point getInitialSize() { + return new Point(450, 300); + } - @Override - protected Point getInitialSize() { - return new Point(450, 300); - } public String getEntryText() { return entryString; diff --git a/xyz.veronie.bgg.ui/src/xyz/veronie/bgg/ui/handlers/HandleSaveGamelist.java b/xyz.veronie.bgg.ui/src/xyz/veronie/bgg/ui/handlers/HandleSaveGamelist.java index f5cf57f..0e3814f 100644 --- a/xyz.veronie.bgg.ui/src/xyz/veronie/bgg/ui/handlers/HandleSaveGamelist.java +++ b/xyz.veronie.bgg.ui/src/xyz/veronie/bgg/ui/handlers/HandleSaveGamelist.java @@ -1,8 +1,6 @@ package xyz.veronie.bgg.ui.handlers; -import javax.inject.Inject; - import org.eclipse.e4.core.di.annotations.CanExecute; import org.eclipse.e4.core.di.annotations.Execute; import org.eclipse.jface.dialogs.Dialog; @@ -15,9 +13,14 @@ import xyz.veronie.bgg.ui.dialogs.SaveGameListDialog; public class HandleSaveGamelist { - @Inject + +// @Inject ThingProvider thingProvider; + public HandleSaveGamelist(ThingProvider thingProvider) { + this.thingProvider = thingProvider; + } + @Execute public void execute(Shell shell) { diff --git a/xyz.veronie.bgg.ui/src/xyz/veronie/bgg/ui/helpers/BatColors.java b/xyz.veronie.bgg.ui/src/xyz/veronie/bgg/ui/helpers/BatColors.java new file mode 100644 index 0000000..5fc2c29 --- /dev/null +++ b/xyz.veronie.bgg.ui/src/xyz/veronie/bgg/ui/helpers/BatColors.java @@ -0,0 +1,16 @@ +package xyz.veronie.bgg.ui.helpers; + +import org.eclipse.swt.graphics.Color; +import org.eclipse.wb.swt.SWTResourceManager; + +public class BatColors { + + public static Color getBackgroundColor() { + return SWTResourceManager.getColor(155,142,237); + } + + + public static Color getButtonBgColor() { + return SWTResourceManager.getColor(255,81,0); + } +} diff --git a/xyz.veronie.bgg.ui/src/xyz/veronie/bgg/ui/helpers/BatLayouts.java b/xyz.veronie.bgg.ui/src/xyz/veronie/bgg/ui/helpers/BatLayouts.java new file mode 100644 index 0000000..bac0f02 --- /dev/null +++ b/xyz.veronie.bgg.ui/src/xyz/veronie/bgg/ui/helpers/BatLayouts.java @@ -0,0 +1,18 @@ +package xyz.veronie.bgg.ui.helpers; + +import org.eclipse.swt.layout.GridLayout; + +public class BatLayouts { + + public static void applyStandardSpacing(GridLayout gl) { + gl.horizontalSpacing = 8; + gl.verticalSpacing = 24; + gl.marginWidth = 8; + gl.marginTop = 8; + gl.marginRight = 8; + gl.marginLeft = 8; + gl.marginHeight = 8; + gl.marginBottom = 8; + } + +} diff --git a/xyz.veronie.bgg.ui/src/xyz/veronie/bgg/ui/parts/BatMain.java b/xyz.veronie.bgg.ui/src/xyz/veronie/bgg/ui/parts/BatMain.java new file mode 100644 index 0000000..83b15d2 --- /dev/null +++ b/xyz.veronie.bgg.ui/src/xyz/veronie/bgg/ui/parts/BatMain.java @@ -0,0 +1,274 @@ + +package xyz.veronie.bgg.ui.parts; + +import java.util.List; + +import javax.annotation.PostConstruct; +import javax.inject.Inject; + +import org.eclipse.e4.core.di.annotations.Optional; +import org.eclipse.e4.core.services.events.IEventBroker; +import org.eclipse.e4.ui.di.UIEventTopic; +import org.eclipse.e4.ui.model.application.MApplication; +import org.eclipse.e4.ui.model.application.ui.basic.MPart; +import org.eclipse.e4.ui.workbench.modeling.EModelService; +import org.eclipse.e4.ui.workbench.modeling.EPartService; +import org.eclipse.e4.ui.workbench.modeling.EPartService.PartState; +import org.eclipse.jface.viewers.ArrayContentProvider; +import org.eclipse.jface.viewers.ColumnLabelProvider; +import org.eclipse.jface.viewers.ISelectionChangedListener; +import org.eclipse.jface.viewers.IStructuredSelection; +import org.eclipse.jface.viewers.SelectionChangedEvent; +import org.eclipse.jface.viewers.TableViewer; +import org.eclipse.jface.viewers.TableViewerColumn; +import org.eclipse.swt.SWT; +import org.eclipse.swt.events.MouseAdapter; +import org.eclipse.swt.events.MouseEvent; +import org.eclipse.swt.graphics.Color; +import org.eclipse.swt.graphics.Image; +import org.eclipse.swt.layout.GridData; +import org.eclipse.swt.layout.GridLayout; +import org.eclipse.swt.widgets.Button; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Label; +import org.eclipse.swt.widgets.Table; +import org.eclipse.swt.widgets.TableColumn; +import org.eclipse.wb.swt.ResourceManager; +import org.eclipse.wb.swt.SWTResourceManager; + +import xyz.veronie.bgg.result.Thing; +import xyz.veronie.bgg.result.ThingProvider; +import xyz.veronie.bgg.types.EventConstants; +import xyz.veronie.bgg.ui.handlers.HandleSaveGamelist; +import xyz.veronie.bgg.ui.helpers.BatColors; +import xyz.veronie.bgg.ui.helpers.BatLayouts; + +public class BatMain { + private Table tableGameList; + private TableViewer tableViewer; + + @Inject + private ThingProvider thingProvider; + + @Inject + private IEventBroker eventBroker; + + @Inject + public BatMain() { + + } + + @PostConstruct + public void postConstruct(Composite parent, MApplication application, EPartService partService, EModelService modelService) { + Color bgColor = BatColors.getBackgroundColor(); + + parent.setBackground(bgColor); + GridLayout gl_parent = new GridLayout(1, false); + gl_parent.horizontalSpacing = 0; + gl_parent.verticalSpacing = 0; + gl_parent.marginWidth = 0; + gl_parent.marginHeight = 0; + parent.setLayout(gl_parent); + + Composite main = new Composite(parent, SWT.NONE); + GridData gd_main = new GridData(SWT.LEFT, SWT.TOP, true, true, 1, 1); + gd_main.minimumHeight = 192; + gd_main.minimumWidth = 348; + main.setLayoutData(gd_main); + main.setBackground(bgColor); + GridLayout gl_main = new GridLayout(1, false); + gl_main.verticalSpacing = 24; + BatLayouts.applyStandardSpacing(gl_main); + main.setLayout(gl_main); + + Composite buttonRow = new Composite(main, SWT.NONE); + buttonRow.setBackground(bgColor); + GridLayout gl_buttonRow = new GridLayout(5, false); + gl_buttonRow.verticalSpacing = 8; + gl_buttonRow.marginWidth = 0; + gl_buttonRow.marginHeight = 8; + gl_buttonRow.horizontalSpacing = 8; + BatLayouts.applyStandardSpacing(gl_buttonRow); + buttonRow.setLayout(gl_buttonRow); + buttonRow.setLayoutData(new GridData(SWT.LEFT, SWT.TOP, true, false, 1, 1)); + + Button btnFetch = new Button(buttonRow, SWT.NONE); + btnFetch.addMouseListener(new MouseAdapter() { + @Override + public void mouseUp(MouseEvent e) { + List parts = modelService.findElements(application, "xyz.veronie.bgg.ui.part.fetch", MPart.class); + if(parts != null && parts.size() >= 1) { + MPart fetchPart = parts.get(0); + partService.showPart(fetchPart, PartState.CREATE); + partService.showPart(fetchPart, PartState.ACTIVATE); + } + } + }); + + btnFetch.setToolTipText("Fetch games from BGG"); + btnFetch.setForeground(SWTResourceManager.getColor(SWT.COLOR_TRANSPARENT)); + btnFetch.setBackground(SWTResourceManager.getColor(SWT.COLOR_TRANSPARENT)); + btnFetch.setImage(ResourceManager.getPluginImage("xyz.veronie.bgg.ui", "icons/noun_Download_60x60.png")); + + Button btnSave = new Button(buttonRow, SWT.NONE); + btnSave.addMouseListener(new MouseAdapter() { + @Override + public void mouseUp(MouseEvent e) { + // TODO: find a loose coupling instead. Where is EHandlerService??? + HandleSaveGamelist handleSaveGameList = new HandleSaveGamelist(thingProvider); + if(handleSaveGameList.canExecute()) { + handleSaveGameList.execute(parent.getShell()); + } + } + }); + btnSave.setToolTipText("Save list of games"); + btnSave.setBackground(SWTResourceManager.getColor(SWT.COLOR_TRANSPARENT)); + btnSave.setForeground(SWTResourceManager.getColor(SWT.COLOR_TRANSPARENT)); + btnSave.setImage(ResourceManager.getPluginImage("xyz.veronie.bgg.ui", "icons/noun_Save_60x60.png")); + + Button btnLoad = new Button(buttonRow, SWT.NONE); + btnLoad.setBackground(SWTResourceManager.getColor(SWT.COLOR_TRANSPARENT)); + btnLoad.setImage(ResourceManager.getPluginImage("xyz.veronie.bgg.ui", "icons/noun_open_60x60.png")); + btnLoad.setToolTipText("Load list of games"); + + Button btnUndo = new Button(buttonRow, SWT.NONE); + btnUndo.setToolTipText("Undo game list operation"); + btnUndo.setImage(ResourceManager.getPluginImage("xyz.veronie.bgg.ui", "icons/noun_Undo_60x60.png")); + btnUndo.setBackground(SWTResourceManager.getColor(SWT.COLOR_TRANSPARENT)); + + Button btnExport = new Button(buttonRow, SWT.NONE); + btnExport.setBackground(SWTResourceManager.getColor(SWT.COLOR_TRANSPARENT)); + btnExport.setImage(ResourceManager.getPluginImage("xyz.veronie.bgg.ui", "icons/export_nandeck_60x60.png")); + btnExport.setToolTipText("Export for nanDeck"); + + tableViewer = new TableViewer(main, SWT.BORDER | SWT.FULL_SELECTION | SWT.MULTI); + tableGameList = tableViewer.getTable(); + tableGameList.setLinesVisible(true); + tableGameList.setHeaderVisible(true); + GridData gd_tableGameList = new GridData(SWT.LEFT, SWT.TOP, true, true, 1, 1); + gd_tableGameList.heightHint = 466; + tableGameList.setLayoutData(gd_tableGameList); + + createColumns(tableViewer); + tableViewer.setContentProvider(new ArrayContentProvider()); + // Get the content for the viewer, setInput will call getElements in the + // contentProvider + tableViewer.setInput(thingProvider.getThings()); + // make the selection available to other views + // TODO: getSite().setSelectionProvider(viewer); + // Set the sorter for the table + tableViewer.addSelectionChangedListener(new ISelectionChangedListener() { + @Override + public void selectionChanged(SelectionChangedEvent event) { + IStructuredSelection selection = tableViewer.getStructuredSelection(); + Thing thing = (Thing)selection.getFirstElement(); + eventBroker.send(EventConstants.TOPIC_THING_SELECTION, thing); + } + }); + + + Composite statusRow = new Composite(main, SWT.NONE); + statusRow.setLayoutData(new GridData(SWT.LEFT, SWT.TOP, true, false, 1, 1)); + GridLayout gl_statusRow = new GridLayout(1, false); + gl_statusRow.verticalSpacing = 8; + gl_statusRow.marginWidth = 8; + gl_statusRow.marginHeight = 8; + gl_statusRow.horizontalSpacing = 8; + BatLayouts.applyStandardSpacing(gl_statusRow); + statusRow.setLayout(gl_statusRow); + + Label lblResultStatus = new Label(statusRow, SWT.NONE); + lblResultStatus.setText("0 items"); + + } + + + + + + + public TableViewer getViewer() { + return tableViewer; + } + + // This will create the columns for the table + private void createColumns(final TableViewer viewer) { + + // https://stackoverflow.com/questions/12641354/putting-an-image-in-to-a-jface-table-cell-is-causing-gap-for-image-to-appear-in + TableViewerColumn col = createTableViewerColumn(Thing.IdHeader, 250, 0); + col.setLabelProvider(new ColumnLabelProvider() { + @Override + public String getText(Object element) { + Thing t = (Thing) element; + return t.getField((int)col.getColumn().getData()); + } + }); + + TableViewerColumn col2 = createTableViewerColumn(Thing.ThumbHeader, 150, 1); + col2.setLabelProvider(new ColumnLabelProvider() { + @Override + public Image getImage(Object element) { + Thing t = (Thing) element; + Image img = t.getThumbnail(); + return img; + } + + @Override + public String getText(Object element) { + return ""; + } + + }); + + TableViewerColumn col3 = createTableViewerColumn(Thing.NameHeader, 400, 2); + col3.setLabelProvider(new ColumnLabelProvider() { + @Override + public String getText(Object element) { + Thing t = (Thing) element; + return t.getField((int)col3.getColumn().getData()); + } + }); + } + + private TableViewerColumn createTableViewerColumn(String title, int bound, final int colNumber) { + final TableViewerColumn viewerColumn = new TableViewerColumn(tableViewer, + SWT.NONE); + final TableColumn column = viewerColumn.getColumn(); + column.setText(title); + column.setWidth(bound); + column.setAlignment(SWT.LEFT); + column.setResizable(true); + column.setMoveable(true); + column.setData(colNumber); + + return viewerColumn; + + } + + + + + /** + * Passing the focus request to the viewer's control. + */ + public void setFocus() { + tableViewer.getControl().setFocus(); + } + + + @Inject + @Optional + private void subscribeTopicResultChanged + (@UIEventTopic(EventConstants.TOPIC_RESULT_CHANGED) + String empty) { + System.out.println("TOPIC_RESULT_CHANGED"); + List things = thingProvider.getThings(); + tableViewer.setInput(things); + tableViewer.refresh(true); +// if(things != null) { +// statsLabel.setText(Integer.toString(things.size()) + " items"); +// statsLabel.redraw(); +// } + } + +} \ No newline at end of file diff --git a/xyz.veronie.bgg.ui/src/xyz/veronie/bgg/ui/parts/BggResultPart.java b/xyz.veronie.bgg.ui/src/xyz/veronie/bgg/ui/parts/BggResultPart.java index 22717da..236baa0 100644 --- a/xyz.veronie.bgg.ui/src/xyz/veronie/bgg/ui/parts/BggResultPart.java +++ b/xyz.veronie.bgg.ui/src/xyz/veronie/bgg/ui/parts/BggResultPart.java @@ -33,33 +33,6 @@ import xyz.veronie.bgg.result.Thing; import xyz.veronie.bgg.result.ThingProvider; import xyz.veronie.bgg.types.EventConstants; -abstract class CenterImageLabelProvider extends OwnerDrawLabelProvider { - - protected void measure(Event event, Object element) { - } - - protected void paint(Event event, Object element) { - - Image img = getImage(element); - - if (img != null) { - Rectangle bounds = ((TableItem) event.item).getBounds(event.index); - Rectangle imgBounds = img.getBounds(); - bounds.width /= 2; - bounds.width -= imgBounds.width / 2; - bounds.height /= 2; - bounds.height -= imgBounds.height / 2; - - int x = bounds.width > 0 ? bounds.x + bounds.width : bounds.x; - int y = bounds.height > 0 ? bounds.y + bounds.height : bounds.y; - - event.gc.drawImage(img, x, y); - } - } - - protected abstract Image getImage(Object element); - -}; public class BggResultPart { private TableViewer viewer; @@ -72,7 +45,7 @@ public class BggResultPart { private IEventBroker eventBroker; @PostConstruct - public void createControls(Composite parent) { + public void createContents(Composite parent) { Composite main = new Composite(parent, SWT.FILL); main.setLayoutData(new GridData(SWT.FILL, SWT.FILL, false, false)); diff --git a/xyz.veronie.bgg.ui/src/xyz/veronie/bgg/ui/parts/FetchPart.java b/xyz.veronie.bgg.ui/src/xyz/veronie/bgg/ui/parts/FetchPart.java new file mode 100644 index 0000000..9b589ab --- /dev/null +++ b/xyz.veronie.bgg.ui/src/xyz/veronie/bgg/ui/parts/FetchPart.java @@ -0,0 +1,298 @@ + +package xyz.veronie.bgg.ui.parts; + +import java.util.ArrayList; + +import javax.annotation.PostConstruct; +import javax.inject.Inject; + +import org.eclipse.e4.core.services.events.IEventBroker; +import org.eclipse.jface.dialogs.MessageDialog; +import org.eclipse.jface.viewers.IStructuredSelection; +import org.eclipse.swt.SWT; +import org.eclipse.swt.events.MouseAdapter; +import org.eclipse.swt.events.MouseEvent; +import org.eclipse.swt.events.SelectionEvent; +import org.eclipse.swt.layout.GridData; +import org.eclipse.swt.layout.GridLayout; +import org.eclipse.swt.widgets.Button; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Control; +import org.eclipse.swt.widgets.Group; +import org.eclipse.wb.swt.ResourceManager; + +import xyz.veronie.bgg.result.BggApi; +import xyz.veronie.bgg.result.ResultConfig; +import xyz.veronie.bgg.result.ResultConfigManager; +import xyz.veronie.bgg.result.Thing; +import xyz.veronie.bgg.result.ThingProvider; +import xyz.veronie.bgg.types.EventConstants; +import xyz.veronie.bgg.types.SourceFilter; +import xyz.veronie.bgg.ui.filters.BggUserSourceFilter; +import xyz.veronie.bgg.ui.filters.FamilySourceFilter; +import xyz.veronie.bgg.ui.filters.GeeklistSourceFilter; +import xyz.veronie.bgg.ui.helpers.BatColors; +import xyz.veronie.bgg.ui.helpers.BatLayouts; +import org.eclipse.swt.widgets.Label; + +public class FetchPart { + + @Inject + private ResultConfigManager configManager; + + // inject all source filter composites + @Inject private BggUserSourceFilter bggUserSourceFilter; + @Inject private GeeklistSourceFilter geeklistSourceFilter; + @Inject private FamilySourceFilter familySourceFilter; + + @Inject + private IEventBroker eventBroker; + + @Inject + private ThingProvider thingProvider; + + @Inject + private BggApi bggApi; + + private Button btnBggUser; + private Button btnFamily; + private Button btnGeeklist; + private SourceFilter source; + private Composite main; + + private Group filterGroup; + + @Inject + public FetchPart() { + + } + + @PostConstruct + public void postConstruct(Composite parent) { + parent.setBackground(BatColors.getBackgroundColor()); + parent.setLayout(new GridLayout(1, false)); + + main = new Composite(parent, SWT.NONE); + GridLayout gl_main = new GridLayout(1, false); + BatLayouts.applyStandardSpacing(gl_main); + main.setLayout(gl_main); + main.setLayoutData(new GridData(SWT.LEFT, SWT.TOP, true, true, 1, 1)); + + Composite buttonRow = new Composite(main, SWT.NONE); + buttonRow.setLayoutData(new GridData(SWT.LEFT, SWT.CENTER, true, false, 1, 1)); + GridLayout gl_buttonRow = new GridLayout(3, false); + BatLayouts.applyStandardSpacing(gl_main); + buttonRow.setLayout(gl_buttonRow); + + btnBggUser = new Button(buttonRow, SWT.NONE); + btnBggUser.setImage(ResourceManager.getPluginImage("xyz.veronie.bgg.ui", "icons/noun_Meeple_60x60.png")); + + btnFamily = new Button(buttonRow, SWT.NONE); + btnFamily.setImage(ResourceManager.getPluginImage("xyz.veronie.bgg.ui", "icons/noun_Family_60x60.png")); + + btnGeeklist = new Button(buttonRow, SWT.NONE); + btnGeeklist.setImage(ResourceManager.getPluginImage("xyz.veronie.bgg.ui", "icons/noun_List_60x60.png")); + + + btnBggUser.addMouseListener(new MouseAdapter() { + @Override + public void mouseUp(MouseEvent e) { + selectFilter(SourceFilter.BGG_USER); + } + }); + + btnFamily.addMouseListener(new MouseAdapter() { + @Override + public void mouseUp(MouseEvent e) { + selectFilter(SourceFilter.FAMILY); + } + }); + + btnGeeklist.addMouseListener(new MouseAdapter() { + @Override + public void mouseUp(MouseEvent e) { + selectFilter(SourceFilter.GEEKLIST); + } + }); + + + Composite centerComposite = new Composite(main, SWT.NONE); + centerComposite.setLayoutData(new GridData(SWT.LEFT, SWT.TOP, true, true, 1, 1)); + GridLayout gl_centerComposite = new GridLayout(1, false); + BatLayouts.applyStandardSpacing(gl_centerComposite); + centerComposite.setLayout(gl_centerComposite); + + filterGroup = new Group(centerComposite, SWT.NONE); + filterGroup.setLayoutData(new GridData(SWT.LEFT, SWT.CENTER, true, true, 1, 1)); + filterGroup.setLayout(new GridLayout(1, false)); + + Composite applyComposite = new Composite(main, SWT.NONE); + applyComposite.setLayout(new GridLayout(5, false)); + applyComposite.setLayoutData(new GridData(SWT.LEFT, SWT.TOP, true, false, 1, 1)); + + Button btnOnlyNew = new Button(applyComposite, SWT.NONE); + btnOnlyNew.addMouseListener(new MouseAdapter() { + @Override + public void mouseUp(MouseEvent e) { + } + }); + btnOnlyNew.setImage(ResourceManager.getPluginImage("xyz.veronie.bgg.ui", "icons/only_new_60x60.png")); + btnOnlyNew.setToolTipText("Keep only new"); + + Button btnReplace = new Button(applyComposite, SWT.NONE); + btnReplace.setImage(ResourceManager.getPluginImage("xyz.veronie.bgg.ui", "icons/result_replace_60x60.png")); + btnReplace.setToolTipText("Replace"); + + Button btnAdd = new Button(applyComposite, SWT.NONE); + btnAdd.setImage(ResourceManager.getPluginImage("xyz.veronie.bgg.ui", "icons/result_add_60x60.png")); + btnAdd.setToolTipText("Add"); + + Button btnSubtract = new Button(applyComposite, SWT.NONE); + btnSubtract.setImage(ResourceManager.getPluginImage("xyz.veronie.bgg.ui", "icons/result_subtract_60x60.png")); + btnSubtract.setToolTipText("Subtract"); + + Button btnIntersect = new Button(applyComposite, SWT.NONE); + btnIntersect.setImage(ResourceManager.getPluginImage("xyz.veronie.bgg.ui", "icons/result_intersect_60x60.png")); + btnIntersect.setToolTipText("Intersect"); + + + // init filter using config manager + source = configManager.getResultConfig().source; + selectFilter(source); + showFilter(filterGroup, source); + + + } + + + private void selectFilter(SourceFilter source) { + switch(source) { + default: + case BGG_USER: + btnBggUser.setFocus(); + btnBggUser.setBackground(BatColors.getButtonBgColor()); + btnFamily.setBackground(null); + btnGeeklist.setBackground(null); + break; + case FAMILY: + btnBggUser.setFocus(); + btnBggUser.setBackground(null); + btnFamily.setBackground(BatColors.getButtonBgColor()); + btnGeeklist.setBackground(null); + break; + case GEEKLIST: + btnGeeklist.setFocus(); + btnBggUser.setBackground(null); + btnFamily.setBackground(null); + btnGeeklist.setBackground(BatColors.getButtonBgColor()); + break; + } + showFilter(filterGroup, source); + eventBroker.send(EventConstants.TOPIC_ACTION_CHANGED, source); + } + + + /// show different filter controls depending on selection in cbSource ComboViewer + private void showFilter(Composite parent, SourceFilter source) { + + // clean up + for(Control child : parent.getChildren()) { + child.dispose(); + } + + // create a new filter area based on selection: + switch(source) { + default: + case BGG_USER: + System.out.println("construct " + source); + bggUserSourceFilter.create(parent, SWT.FILL); + break; + case GEEKLIST: + System.out.println("construct " + source); + geeklistSourceFilter.create(parent, SWT.FILL); + break; + case FAMILY: + System.out.println("construct " + source); + familySourceFilter.create(parent, SWT.FILL); + break; + } + + main.layout(true, true); + } + + + private void checkEntry() { + ResultConfig resultConfig = configManager.getResultConfig(); + + if(source == SourceFilter.BGG_USER) { + String user = resultConfig.user; + if(user == null || user.isEmpty()) { + MessageDialog.openError(main.getShell(), "", "Please enter a user name."); + return; + } else { + eventBroker.send(EventConstants.TOPIC_STATUS, "Fetching " + source.toString() + " '" + user + "'..."); + try { + ArrayList things = bggApi.getThingsForUser(user); + useThingsOnResult(things); + } + catch(IllegalArgumentException ex) { + MessageDialog.openError(main.getShell(), "", ex.getMessage()); + } + } + + } else if(source == SourceFilter.GEEKLIST) { + Integer geeklistId = resultConfig.geeklistId; + if(geeklistId == null) { + MessageDialog.openError(main.getShell(), "", "Please enter a geeklist id."); + return; + } else { + eventBroker.send(EventConstants.TOPIC_STATUS, "Fetching for geeklist id '" + geeklistId + "'"); + try { + ArrayList things = bggApi.getThingsForGeeklist(geeklistId); + useThingsOnResult(things); + } + catch(IllegalArgumentException ex) { + MessageDialog.openError(main.getShell(), "", ex.getMessage()); + } + } + } else if(source == SourceFilter.FAMILY) { + Integer familyId = resultConfig.familyId; + if(familyId == null) { + MessageDialog.openError(main.getShell(), "", "Please enter a family id."); + return; + } else { + eventBroker.send(EventConstants.TOPIC_STATUS, "Fetching for family id '" + familyId + "'"); + try { + ArrayList things = bggApi.getThingsForFamily(familyId); + useThingsOnResult(things); + } + catch(IllegalArgumentException ex) { + MessageDialog.openError(main.getShell(), "", ex.getMessage()); + } + } + } + } + + private void useThingsOnResult(ArrayList things) { + switch(configManager.getResultConfig().action) { + case REP: + thingProvider.replaceThings(things); + break; + case ADD: + thingProvider.addThings(things); + break; + case SUB: + thingProvider.subtractThings(things); + break; + case AND: + thingProvider.intersectThings(things); + break; + case ONLY_NEW: + thingProvider.keepOnlyNew(things); + break; + } + eventBroker.send(EventConstants.TOPIC_RESULT_CHANGED, ""); + eventBroker.send(EventConstants.TOPIC_STATUS, "Fetched " + Integer.toString(things.size()) + " things."); + } + +} \ No newline at end of file diff --git a/xyz.veronie.bgg.ui/src/xyz/veronie/bgg/ui/parts/internal/CenterImageLabelProvider.java b/xyz.veronie.bgg.ui/src/xyz/veronie/bgg/ui/parts/internal/CenterImageLabelProvider.java new file mode 100644 index 0000000..762b6ab --- /dev/null +++ b/xyz.veronie.bgg.ui/src/xyz/veronie/bgg/ui/parts/internal/CenterImageLabelProvider.java @@ -0,0 +1,35 @@ +package xyz.veronie.bgg.ui.parts.internal; + +import org.eclipse.jface.viewers.OwnerDrawLabelProvider; +import org.eclipse.swt.graphics.Image; +import org.eclipse.swt.graphics.Rectangle; +import org.eclipse.swt.widgets.Event; +import org.eclipse.swt.widgets.TableItem; + +public abstract class CenterImageLabelProvider extends OwnerDrawLabelProvider { + + protected void measure(Event event, Object element) { + } + + protected void paint(Event event, Object element) { + + Image img = getImage(element); + + if (img != null) { + Rectangle bounds = ((TableItem) event.item).getBounds(event.index); + Rectangle imgBounds = img.getBounds(); + bounds.width /= 2; + bounds.width -= imgBounds.width / 2; + bounds.height /= 2; + bounds.height -= imgBounds.height / 2; + + int x = bounds.width > 0 ? bounds.x + bounds.width : bounds.x; + int y = bounds.height > 0 ? bounds.y + bounds.height : bounds.y; + + event.gc.drawImage(img, x, y); + } + } + + protected abstract Image getImage(Object element); + +};