From 9e4664905e8b3f1e900632a4091b1e3f0a43f139 Mon Sep 17 00:00:00 2001 From: Mauricio Baeza Date: Mon, 14 Jun 2021 16:20:56 -0500 Subject: [PATCH] Add tools for debug --- VERSION | 1 + files/ZAZEasyMacro_v0.1.0.oxt | Bin 0 -> 70083 bytes source/META-INF/manifest.xml | 7 + source/XZAZEasyMacro.idl | 35 + source/XZAZEasyMacro.rdb | Bin 0 -> 18432 bytes source/ZAZEasyMacro.py | 20 + source/description.xml | 26 + source/description/desc_en.txt | 1 + source/description/desc_es.txt | 1 + source/images/zazeasymacro.png | Bin 0 -> 26700 bytes source/pythonpath/easymacro.py | 6912 ++++++++++++++++++++++++++++ source/pythonpath/libo.py | 29 + source/registration/license_en.txt | 14 + source/registration/license_es.txt | 14 + zaz.py | 0 15 files changed, 7060 insertions(+) create mode 100644 VERSION create mode 100644 files/ZAZEasyMacro_v0.1.0.oxt create mode 100644 source/META-INF/manifest.xml create mode 100644 source/XZAZEasyMacro.idl create mode 100644 source/XZAZEasyMacro.rdb create mode 100644 source/ZAZEasyMacro.py create mode 100644 source/description.xml create mode 100644 source/description/desc_en.txt create mode 100644 source/description/desc_es.txt create mode 100644 source/images/zazeasymacro.png create mode 100644 source/pythonpath/easymacro.py create mode 100644 source/pythonpath/libo.py create mode 100644 source/registration/license_en.txt create mode 100644 source/registration/license_es.txt mode change 100644 => 100755 zaz.py diff --git a/VERSION b/VERSION new file mode 100644 index 0000000..6c6aa7c --- /dev/null +++ b/VERSION @@ -0,0 +1 @@ +0.1.0 \ No newline at end of file diff --git a/files/ZAZEasyMacro_v0.1.0.oxt b/files/ZAZEasyMacro_v0.1.0.oxt new file mode 100644 index 0000000000000000000000000000000000000000..48fefe666225850f842a5cc7e41d25079a801791 GIT binary patch literal 70083 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%0DR)jMS{Ks3%v^UUFpYfyUdGj?m zsy5%~^OLTwc#3j3AFB97!coX8mTYLW3h~U_8!*MKEG3@<)o1okZ+2^|;)_l1eCEFSWh?|`VQ8=_V~N=l@50%B%SBf+9sN%`SfkX)ZxGN*T*)GV$nBxU zEA?Z0bqdN%tAAU3-l|l#DlZxJ$N&1z|NCG6`F|?4&(Y&HsG0f1a+wsPHaaaLqwS-H zKQiaD0OL)PzGm*kFM`;jnd3>Y=SI?GxN7amG7ZH`P-ty$90iWP< znT+$Q&Mq;y3k4}~6zatpF;u%cFCr-qYY1BMQ;BDfd(>fvQHaf%?MOhzza=bKs+24{ zYi~9$*q6<{d$PI7wh}S3t+p{yrq*_YPAl?rin$ZCj^B(!U4xcP7|vS9B3q|Wv0QeN zQYdk7CmaTfWPu!`PaaU&8ku0%zblbe%!edJAYX(M%~IXQuzeB}v?(A&2*kOp@q zzj%*t@4S0xTqAN~?qG>*PQqub8slt6-gMg<{P)r!&ywnH7U$I>ZbXZ__*Tl6Dvw6I zRD~`nEN8ZT^afrN;-_GzU|9bJ5Y`=i@vQ3hQSPE*OTm3!INC*3o_3zaKAJ$%OSGl~ zGB-7tQk+4qrx#Uo2&Q#i`qrNd(Re^@9l+;J#M((Pj&YuOMnoq;&iT+!@Fl)Cqmhr_ zo?rd~6SlRr_1AAd&1aRnFi9~+Ypd00Zh=@G$CwLtf9uD~H?MZLzWwJHfBj#uoyKi7 z2Dj|p6U@r_kFQTp4l7%)c6QESmwA@Iov?Fsc~s%Qk3=|E1wilY9KYMDY)$BDx-rQn zTL4)D2&gR5J@~M{g)cMg?d;$!zV;?VyV-2+aPuuun-_Cw1Fo_~s7I6it#H1~9{A5$ zIEA)N{v(T*>UZ*jP=B#+g8Gwg>hX!C{jChxnm^*Qw!hs+*$ri(xeAi~ttl>^t(|`c zvePttjR5}|0sb`t{7)JIUK+Jsc-TGGwj2I%Z`$MdJRY7Dq+J-Fo{!Op*4A{Jro8FF zB|}?kFS*dx<7d#1&Fy+=onJPnBVChw&2=@EC$d@9)~T7u@ntt>$8s#0HGGDvR^5kjn;hG%2X z5qCjUGhFR;4kPiG1ChHzg{^%ySO;{H%EHqd=}4_pg-}6e8p3Kq$e*D<{!jH$vpfPck^rs zkOAkvU%|^S@7+5CAkgxnUT*wr)e0qecA*KR3J&M!ij&REE34Dn&c`71? zYV3aVR&t4mR&no`EvEp%G$q{i`@hlnGh%7J$_Sk-Xy6lADi{Yg#q+G}CajzsRpKfA z#uuefsglK&t595`g_DL3CBRKN{%%5H2C)G(oc{Tmqf%)C56VnH^~&M*$A>>0ygog~ z(_OHV4vtRF=o|dqu2&9zyi|X79grZJEM%5(8d0vD`_}ux zB>3e^UGrfv*Fe7ZH(3ZmB&aqUE^Dp#H7h8;WiagiTx>$$qK+N-Yj&dKBX*+>#l2Q9 zStd+j%}1w1@4MlBDxhnhG@cu0BKE%}52)uFU*!`3DzGRJ>X&AnkxMve6S=Ie9gTG5MZ;zsBUtI$vY*E6F~%8i2a`Gx4O0s4Q}myz(>f15N>CTkX^h=LJ3#V1g#Qq#I4->el-!P(=VwMO zH-20{*W_OOn7kk~ci z8kS>*=9D302gbA12BR|sLaRX?ZRW3Op&E#k`+q$-Ja$fW z(bX|af<-#Tb8KjPg>4%b53sD~hw^Ve+&w%!Ie&e2@cwA@=Is63gUiw7@z0kC5%1l1 zfU%>+U&c2<0^G2_%BHVceeu#3V}=&zrRWO%Bx^)6_py^LD?<8U%~eO9HR?CGyrn%*dEv-N11R! z*i?APTkZ~jO8C%S>ttH@9RUcp+5G)ojWr}uaAace4A8%~%mJ1{Vj4L*#o<=2_Ckn1 znn;ZaMYadzXE!k)>yTO6a3Np#w;?@WoQHyUwHhBvGQ|iCR^ho z%&{fzF|OrlQq8OPjSsmKGlM?8Nry)f)vfWuBo;rZRhmvqv^KooB$y5?Gk?JvD{Zo0HSy(fP^Y56ABvfwhVn?EjTE@L`v8 z*nZ2)r#EM(N5}6sX&BF7_a~bcsRT4^_p4R&R0KZ~yeviEu3B{pNbK$G*bhER3mFVR z93aZN23GwsnO(*#UFkb#p2xuDcy<@ooXbc7C$IY@nRl~9J4tG=|85~5#B#LOtRYBP z>x&H|LCJ7lX!U+7x6>HFYC4TReQ51{ECQoq*b3nA=^9HrIdt*2_N$W2ZU^!_uoTZA z9j%h6*ho(C{%`|w1sPb8X*R<8rDEQ<5;;yDG|dB~P;e~c3xP~B76}%fldJ?w#E{Gf zSYf8LWv2jz82Jn->`?aCc=7L}Dd=$1e5s2kui+wn4XAr7N0sK5fC?0=X(uf3MTA0d zEz#UJL0DXZgy0uKfo7-rQ=HuDWRT9n^5?xEYRF6?Um<2dMor-n{oH%gRTjy}>;BFu&k&yxcM$sUhV(v>xtWrn3S9e8b*ko8&`rKHheCG4Zy2k;S$ zG~Gi9X!4p(bsesv6 z%|2yy<4q79bK%(_$81@K;;Z0jCD&gRRFF4R;;UwUqLy5e@l;?AYxbMB85;3)*-#tF zy8vRF>ULrEqbLP>X+o(KuLhO^u~Dhk+InnFG*wjfks1c{p*EG-i-2RQFB_ky^7!Vl z%CX2~dy5t_^-{gM{n3V~4}jl6%-&F|nrQr?`4Rr#+!5LxxMsQznY{=lQ~(Aze`tMl zuN1XyOf)q>s5FLN6=bbLy&)wqz{$f`5ohSsXbfgEEk%x%pz7__YDD~@xxR+wblZRX zk6sZvfD?};H}zX3p@!%m}e4jWGtIfH=sgeDs??K)WtztlnA{v%}V-&xpV z@$V;W@$?sIt8R7Cv9Kn|Z6<;KDttE*zE}n6Q7&$M>-&SXz)hlssYxfn#cH)U!xd2R z9~j>gC4#u^7~xKygo3c%!uyeVe7h7!9H}_tkl)3<>$Lirt$BP@jvGHZuL$C}X&<(1 zXhz}_F&4f%3Rz>Dnxdq!^Iv%1C-ll%b7%(0{CG!t4>^c)-LI5fi#DYDBmxbDC%QEYi2TJh9Mp^G@M*ZRlCOb?e5&Pj;6mMO%;B*XfidEig& zNf;ajs~J_o6A!vIwA4|jh0+?)n+wVUtp2#8_stNdz>rc+VPtz>R@`HJ{Bd$!;!tzR z;Zezr)dGDoE&Wdil}jbVJQ+Ax;wN+T%drJAC&E0tI8G8U7lO26$!Cn$53#J~xXT%8 z@yTnCj1qnEE%|->{pw3&4=4+w$Hfok=)M$)%J&UhuqWQK(M#pp^faRyBemejQ z)*u1`DWOTI7*pclfS^iHxDRHxI%R}SYHp56{#T6jfVr8?9$hc-{P|_$rM*ycv)jsx z$D6BqWmFe*2QZ+`2_K6ZW`AD$5vK!Pyu!vfo-c7WlIq_YwQu)6KW_i;Y6Ci)2bna= zw=va7>4=ya9(0st(2uqi=r^MGWc7@nL3}71&Bw*@Hw>Qa`^^{J$R`I%d zHLVhS1R!)z=DACEcCoTG1r!Js}oTKO_rEy5y}VNGzjqIqFF?17tp` zvu4@ApE>sFbmN&dI7}P8Ls(aSF@!S7DjqfEU_$botS0-x_l=DvvoFU;)q9+*=F7B}r;=M_`&~3G(^Ud{t#lkk zyx!}TG*0LRgmmBM{bt@a$(yD%G#RINI-2w{I*3~>uCDSQm5VS`!FBYCIbW#KeSMw0j*We&aVRLddMrm*38Nv?XX!46Hz z=fN5uQ`sG*i5+Oc`>GE`+CtFz8lcC2Xy|#Wib{ZH&3Exvg8Ofg*q-xkFbIC=Bh2VL zba_8_MEU=?6p+hBDozv!Cs6a6g=@X$Gig z|K><7X&nj*_7QwFI5UAezHxD6Le1sLiDX=Lq#hI%x28T1pt6hbp8Er zC+DN1<2R=Vm&ZpYf2$hN@{K%dL8=qG*L86F+V8XueseWhZr8Z;`5W`SanvcxHsSn{ z@V)a&>%y`4Fs18Oqz)YmB7?a5O{!CZ&}vb(80X>q3XN@a3FM_|IrR$d=3kh;6`|Cj zT5+V^O2;p1dMF^b>GzZ3^vzhfkN<73qpG!IG*_J%a2+?!9*ad3Y_d1fUW&@b&f4%TG$Oo5u+gRu zrxVr+V&S(z3pskjj!R5i-8@XSHBj7wD8%a*cdo*Oinh8ww*Hp^&W=jRG{EQ zfacVH9m*p-MB~PH{IAC%CGY^}dfE7O6($d&rj^p@TsWF> zf}(fimBXpJ>C3)$W5;%+mZi#JZR)br0ciP9D1_J}$S?^&?!tF4BukVxZV`O~$S^?6ZKZlP-kRTm9(KzK0Woy636coG|oy#jB|qwo!v?i*Dm&W)h04V;oN*dLuoTz4 zy|O`Cof&MnRX9vAtcay18W`pjmW z4z6(;0Or8pkFU({hw*9w@>sj2lXn?hfqNH~tw1#m1vjW*1|0$(^jY5i>hCJpgJoFQ zifk78rS*}+SaYe9;*)}rOv!y8Cwc&mI^lFD0W8RWrH0s~Zgg%&zD~ijJ-t_M zwysY~v5P?L@D2f(f5F}O0Uo_A0(=tEzEyTN8r;nPSg!%U_zv?$K6V}iqn}GU3bZg5 z8N-SscWA6RZm8MTcuTU|SR>Z9=$Rt7(3=oeD5<_Z37qenVR`cVnAJUd1lBv61?@%@ zG`hBR5s*~+QLKlcsyaZ%?D8;5!t19&YM3nzz;v9P$TWR;th|g<@D z9UYOnNU^}TE92Qj@c>-%aR|Dnfp{I(Z8FC9Rede^;Z?T*#a{XvycJJdxkzz3`~tk> z8(|*QR1BWeFps5iD2A%iZ9p27mq!q#VdVhUKyx>J7|1(uzc*Xt7*joH^_!(iAjEUHWp7;X_fg`;@~I zOr7yfjB$l59|*FDb<6{lPY|4?it)!2*E5-2`%(>&K`^^En~vv`QG7cJXl2MpeCe$| z9|RG@+Va`M3|FK#(DFTV(v3YHv6kqT^G57d+D+|w3z-sPud>^eNo@($*)?f)gU+HL z4^y|vRfrO1gxHgrUbNmR^8pmhu4bcodaW0oinP^oHr;`=y552G^shhT2kq})tiQ?4 z>@->+7HH&XMXy1?gT(~>ScGZlDKWGhxy9qA)F_@|XP?B=!+PN*=bRSI>l^pbJMLCb z#}gK>Ueof5HXkJq0WEY8D<*wHi;Ijxv33ya8nLXFhNXiT54yt|MGD z?xWji8G^>5%MSc&=cn+B^TVcUOq?!O-+~;Tou0k7GWNp;U>shdSiq?p4BMUEjy8e8 zB8z@qh4(j6Mm74~Znrbgr#Sd^6+lV9*X!7y=Rv~KZnv|$+v|7r;@?(@?6TW30A2wn zabK<0u-(_0>?%rsWlZ~nL9^Y}AzsabF}@zqG`}ri+c61durZ#9cI{@@=#IzU?%5yj zqIiaOaAnx+_1ew0UYx}DxWZe_-FCO#G0<2gvxoaQrrx@}VZYsJ=^5aINtlT)`|WVh>kS9Pp$!7&Xd&2p&>a9@IpyOUde{I++-){nZL2&9C!+gavul;5 z#OJiCI?df~%Z8nXfkX}#-4Hmyu7J!XrmQ>YbUR&}KvY9eAGx6IT~|<3+Zx_^IW zbP#EwhAJg0geKCZ1dtjaLJ$Zg^iBk%hjKtcK_y5pf^-8YC7?7xihv*rVhMr`RE~Iq zXB;_>=RDk}`*7EuJ!{RH^?mDKf87sz_J-f`aCo>c^;~TAb`a;u-euBe{ki;AP@N|K z=1?13&q}uQewr^$Uvv&4pe9(MWgxMpXyi$bryOLR4fS|4BG}H>{E%7gWIGMMc|uV< zh*xaMnmvnxSt2&KN0CAks}%E#>l+gLiDu74jg|Tu)}lRHs?5INR60+UXr;BPDCf-t z0qrlPOkJHW*1us13yV5fX15ShHpv7DSFcuP1T!!q`*|tBvpsosL+9y@?Zx?+rNTj; zu~#;)cAblfiB0#4G^3{+g`4PbEcT{upx76r(WtZy@sWV&7Zg@-q3D^|42O*rOi(-N zu~Wx6PG@qwrG`Msia;X#Z6wQ(NgDeB(j7U139Nvj-tItfWZfjDP})3?R-#muHn_~K zepaT{*+6?OK9ZLehb%~_Z%1etm)#!7%Gavng|)@9i0jD>I6$cXC9uULFI;8oc>`aO zxoF>QeE3dc@J-jW$dPXG8J|L((gJOL{1HqDSQwU4D$4sN{^}-2e$$8qsby{rt~?+n zYz1VTgL4L)p79Nu>(F8P>Ojy!HBJP{Kh#@tX`w@A;Y;N}sg>?1HA_oIJP+=&JO5SU7?s%+?S$Sw%n=K}w4&HXrZpTb$jv|tAgmTRSs$y*=* z7YB|e;9&W*66odfB@Qhz!O2STRJ^nUX!y;WZgWbz9lg||`B4XPgMHy%GYa5ump$4A zAYT3N>=+xz%+aK1?3m&dt@VKk@N+{x-Ac#mQKjk!n~>1aN>}5+mPXL41~)4^Mm8Gr za7l)kz^m_96v6$f_a|{u&uMl>Jq5+3r%+!V<>x-Ii!p82+_;I%Z=Afmb$HFU%MEvE z(JU81_lyQUMp*DlUXl{x=G8W#l`+K|cgM4N**s*d>h$2wKlLZXK$2;^8Os}mk-s#g z{fPgQko$Z$)MlLjp(d2R#9Lu7;*BaCa9jl(`IRHgkI923_#m~e7O|!pu(H#wwgl1h ziC^P{4P44_o(BwPpIBYS%4 z_0jY!Pv-Kuq_sSSIvzec0gjxr2m>TNA)-9PGsB7t zkkM^pu_{yi7urItJS=BxLczgFlG~o?el#owT$fbZtbwz~Y_G4_I0SL`eLj9`G3%rjZzi^UNP)9p3+i=ye15bt-N zxF88JZ3FSTw^=I}V)17PtAGn_Mw+STIpt2p}WYpCfuP!Bdr0bo$9ea6_lc9lZ!1Q*Xm4fC*cuyJVqFC^>g_kc+=z~z0|0=1 zfJ?}T1-}Z(E{z5NZqfn(A9XeW?SToz1)y*ktS`(PuK(^6&#I>#`arF2%M?d zcYEt@_{Bp)kI34sFC8CFi8k_te&u7Xs1b`-su4;kJ!yPaOIBG%Wjste6Yp_7Y0Sh+ zX5>cJ8V7=(AE$GuQzW#(gUbn=H2smajyTAiu;ypg&~G?J6hPEBL;M9))SM94+eCw+U>)6#R9mhhpViKM$i)uHk1J5JlhjPp@P2^tOX ziN?~5Wbc4n!zBZ5{w93ENk$L1VjFCsV}f^7_X+6~Z%2V5wYiW)>`QH%yklF2iUsL* z*S_|C5qTPQ`u1&qlTEnu%4D36%O#uFdUmdDk_1#Cr}a*QJ({7p!?i<_YrnY#L_cdo zuW(LH>W#aOlfjDb6x%Te@;fWMsl$aNhn(B>YH_n4Cbr(Ea$HM0p;uD;jf>DS)<3#r zyArq>wJtb;<0mf@lX_+zXI;e!7QE%Ufzlrn2fC zc|z9jwuq8Z$(C(?jig4UMtBiydxR4XexRuGw6pynL$<3=g^#Z3jYy9BeclT+c6f!s zXV|bGmKaR2?Kq)jUpG)4?ye zTs9Z8F|%)D!A|9KRS2=_Vnxb+?LU%@`mT8JiWP}(kO|Gk*EmvVK6mfJ9xEww!rk-}$TLb`Ge1mQb0Dxg*?odfDdJk(teD8oW9v~vubqp-6}B0F zrngsFg|T?O4ew25N1ALVk~zL z#<(ZQcBwj`1>=HL3jTkj`ko~NPsy6fNZlJ99JGOI+^#>YB<3tEW-Y9$&<5?|rc%<- za?t&iev?B-EWXb3tUsX=4p9xyM(uwzV%+ZN4C0I)Dlp6h!CP+^J zFs9zSiv7`uaW|L%0ON!5KnKFYQQ>H+{62rl@9VLbB!hZ@geu3+^Hk(Zs<`Yw7^pol zPQecV{K9aaSYJOB&XZb*Tz{tku2{>t`FsH2nWX(ih~xf$!|##eZ(96a1^gigG0t>9 z&A-*dy{GwiqJyA+BKq!qdx?IR34Tg61E%?XUig*bSHJ&BA + + + + + + diff --git a/source/XZAZEasyMacro.idl b/source/XZAZEasyMacro.idl new file mode 100644 index 0000000..3cb248c --- /dev/null +++ b/source/XZAZEasyMacro.idl @@ -0,0 +1,35 @@ +#ifndef __net_elmau_zaz_EasyMacro_idl__ +#define __net_elmau_zaz_EasyMacro_idl__ + +#include + + +/* +any, boolean, byte, char, double, float, hyper, long, short, string, void, +unsigned hyper, unsigned long, unsigned short +*/ + + +module net { module elmau { module zaz { module EasyMacro { + + interface XZAZEasyMacro : com::sun::star::uno::XInterface + { + [attribute, readonly] string OS; + [attribute, readonly] string USER; + [attribute, readonly] string INFO_DEBUG; + }; + + interface XDebug : com::sun::star::uno::XInterface { + void inspect([in] any obj); + void info([in] any data); + void debug([in] any data); + void error([in] any data); + }; + + service EasyMacro { + interface XZAZEasyMacro; + interface XDebug; + }; + +}; }; }; }; +#endif diff --git a/source/XZAZEasyMacro.rdb b/source/XZAZEasyMacro.rdb new file mode 100644 index 0000000000000000000000000000000000000000..97454586f0f10a72cd5a79273e7b1abf8c426f1c GIT binary patch literal 18432 zcmeI4e`pj(6vy9sXVauxV}hcAl3t@ikdRAFQmt6f%a2w_3>XuG7Q*IS%UN@m+>cO` zmYP43A_#^4Kx$eoRk2oDK@@8J1qDkA5!#sk0BLC}NFk!7wWYDN_PsZIdzZ~wbK-3; zcQbIi^LA%qp_u9qnCDYg&q{us$brhuOE1}1j>Zn@XewP>-v8vg}Jfw*PAB% zuvpl83l{9=d#G{e^WPtU+L(Q1bnGCk684gzi4nHv#m;+XZ~him2>a{TMhn?qDC{fm zROfwhq3ar~j-_Y2LfDU0?0S{$Qeih={`n}|u2}kCi)(H|RqQ<5j|e-uC0U|z+DlvN zkqPgEODXCA-{B{K_;$WKiT{h|7mS$v1GwVro2~KBm#uom_!F7(UAAg0>usL`#J^X) zd*0IKBa<$Y{AbeBlyxEgkG5AV2m0Q&@c+q5Ww-e~j{NJ}JGDrbOD6fx@)sb(s!#kk z{c#}()SqVI|Kq+lHk~lk$>D{!=co%)mwki2tIY@ow(jxA1@HMqj`jFO&T9Q<41J z_;SjuQmz2;-}}#=zj(af!vCeQ=l9ZhKab}FtX|yO(B7kMkLW2cRr+S&950)n@_|1i zgA*tNP0Kn~Sk?Lh?eV9;1Gvq>eKa^QQVJ?|&bqmS#*Lq8ZnLQti&6TrJEv>gv}Tie z6{TJBA8Bj#j>vPA2yn)j6AL5ejVVBdkzocYK>Y99*l`YDp(*YE@=tfpH|Niw|DW=T z%%FPSucAu-f4`KJ_elo@i2uKr6<6_mK8yc9(%IKw&i9l4pC8vRH$8)P`Tuduk^KKv z!~b8!xbXjq>Ho>~($uYFJbMYK-aw~26!E)5VJ+y6_yg{?wf?Xk+^TuGahD5fQ#0Nb z9tJfO4*LA;>>mQPrMZshfJ{1G0P5Ox^-b-yb)J@|Emt|Py+X|bss{xHW^)Th#B;dp zd_r#i7X^s_R_Cs2p!u>E{`bGUW*hO(^JOLf{CpbM-|n}Ie`1Na@fiGjFfRC?h5Klr zSn(a(>d{nkFBM3n0P$a~{eGV3zgzgusd}XElEscr?#0{-4AgiT^5t ze_EoHnE$zP{BtqoMk+8Z1&IISAHLYd>+dZ5pZxB|kHkN($CLcCo`d>(x?TKNVvfXr zxxs%q#uM|O$NX~vt?$38Ni8}>;ei;PMt`~NfQ zab|sGi2t`vA34SA@h$vM^bemU|9_p<>r4O7@5;Hu9}L^&|9h}&5B&*um_}MX7z_lBET)zq?!HbOPH^Db6^AjzK*I|{B04697Zk|F gV|jQxI))cxOu>;21?CvX@_F@}0tI-C#LS}bFa8aThyVZp literal 0 HcmV?d00001 diff --git a/source/ZAZEasyMacro.py b/source/ZAZEasyMacro.py new file mode 100644 index 0000000..d309c31 --- /dev/null +++ b/source/ZAZEasyMacro.py @@ -0,0 +1,20 @@ +import uno +import unohelper + +from net.elmau.zaz.EasyMacro import XZAZEasyMacro +from libo import LIBO + + +ID_EXTENSION = 'net.elmau.zaz.EasyMacro' +SERVICE = ('net.elmau.zaz.EasyMacro',) + + +class ZAZEasyMacro(unohelper.Base, XZAZEasyMacro, LIBO): + + def __init__(self, ctx): + self.ctx = ctx + super().__init__() + + +g_ImplementationHelper = unohelper.ImplementationHelper() +g_ImplementationHelper.addImplementation(ZAZEasyMacro, ID_EXTENSION, SERVICE) diff --git a/source/description.xml b/source/description.xml new file mode 100644 index 0000000..c129c17 --- /dev/null +++ b/source/description.xml @@ -0,0 +1,26 @@ + + + + + + Easy Macro + Macro Fácil + + + + + + + + + + El Mau + El Mau + + + + + + + + diff --git a/source/description/desc_en.txt b/source/description/desc_en.txt new file mode 100644 index 0000000..7be9ef4 --- /dev/null +++ b/source/description/desc_en.txt @@ -0,0 +1 @@ +Rapid develop macros \ No newline at end of file diff --git a/source/description/desc_es.txt b/source/description/desc_es.txt new file mode 100644 index 0000000..18f3913 --- /dev/null +++ b/source/description/desc_es.txt @@ -0,0 +1 @@ +Desarrollo rápido de macros \ No newline at end of file diff --git a/source/images/zazeasymacro.png b/source/images/zazeasymacro.png new file mode 100644 index 0000000000000000000000000000000000000000..2f210eda94433623e613c91ad5675eb6631dd370 GIT binary patch literal 26700 zcmV)9K*hg_P) zaB^>EX>4U6ba`-PAZ2)IW&i+q+O3>vvRpTkrT=3UxdhC^asW7B);p->*LM&>ieySz z)oo2ld{i)odja<|xI4m~|N1}I{p(-<3cmF0a%sJeT0Ot=$Rkev(EaDv=V$Qw{r-OK zAAc8qf834o{fo#$iJ$57zx8~dKY6_T^@5hakFVb!cWr;)r+?ol{Cwm0E9sN-g~ysU;X+2xv>~lc)t`~JSimqevh9|N?b)6Y4Y>) zdy9Ic^XHr@zsHpSef`|erTd!&T<7oC&+qRu(qGH*d7~e&HP9CP`sa?+2E-7 z;xe>z`kiO7A>n@4w7m5V?(@U{`AZv{*tYVPxpRZlug_bo68?&<^!7RV;&^KIpL_|; zzHh)1@!5ro35gx>HKY=1@GZs`0_)h>pUIUI+9Pb+pmP7<2NWGf&=Y^8CpIE3UNiDyy!x`WkC)!e{4QcHM3F zJ@&L{$$$W7)tYr1HcvQ&(#fZsdfMq{oOy}0n{K}4*4u8s+PYtM?%((AZ+6Y*gWL2rNLk35 zf{=;y<4H62DL~ZEX|tDG*5M+XZT@JRbl{p=-j$1z0GoXD+2s9@{qi_L{X_!iI!va1$5oYDORMsx+XQa`r7S=D51AWPTE-w<-iIlo`4F z!uDoD`0jX#{jhAYKl``?xP~&|!6e{-_F~{-@&#DW(Wm;kPAqweCokm8%Y6Y*#$r`U zo4GIZ$K`NN>X%j2eFWCEO(T~`1G74;@#aKv%oSE_g9V$bv0HQdTeu1GsN6V@8#kI= zSa{d#*V>7+Tz(BApoP_vX>TltO+XNjcX#qR>`f-NRj_w`g@NuWUk0>#5vb4{PY(QNSaTdMN7Kwg$Qq zeB``eq*yKs&|;NH8NK;I8tPC$n?0+a5tX0YBH0oA(3y3g&jRecp|0{ zKcSN?Mzl-T4?9FHpo&J7&N$>t1L%1pA|)Iyy@1gLN@%GHpI4l`LB4?g+;)p!JzPi$ z;0}qnI%BbIX_4nAu0@J07PJ(YhIJ|DMj{1TIQ%UUL0tRZijgT*5AL!uI5&ylm`Bff z4nBxTD2~M>U7AmG>~J$NLinA-jxJ#I6aXV%@MVn|*q&vP0N$Nk$T5UZi99t z7gLk2HD!jybW>_91w=-&2R5>E$E2QKCR^I3pfF7Bmgn<5+MnwGY1e) z&a&@~Ro$B%nNuLJ=4mlqq7^1^sBZS$gPa3lTUy>E7#x=BwjOgP@4|-e&mF0vKxZ?&f zvi-aCnb~QUhYjH-n^fT#Tou^4k+~o%K@Tb9v+g8o24vF=5v=WmhtR9dBDg}}a3Q2rs>IGp; znsaLdc?_tN%j8y_AUdx|$dC7lL<2AVE(-$SB{&kN=Y)HzeG>+3?A&KHw__`(ES}lU z;Bj7x*k5A7$Pbo@Y+}{o!guu=KvILbK{-}@!y#t~y?LX&w-IXEbl^Q4s8Y%it|$w* zS>VC1(&?Xmy5Am{3U?#|H|i}`0s{}y`v!W55<|qWzD-_^O9!C13rG$K&Jbk3UhDTL^FeAqXc5jjX&;tQP_#M`g7~+mMdy8Ny*8yC6Pe8^C8;^x^(Z3az z0C}MY91#uBmE-~811R!e6RC1X8SKl0zvB+O z-!qGr7O4k}bR{ph0F;!SQ5a|uAI1fn2kJUvKNi~v4In)L>P;f(4$1Nt?x>Y({%?>*OopvXuVt~D+e z2Ryxu9{dQcKZT$92n5}OS~phil99kDYcOytQjj+$0po)nN&9)O;7?SYyw$bwk^5B zJ=Se5Zm9Zf=`J=3V)a}LH;y6!TzbIcMqdI;I;zwNFP>!D$RrQ^h`n2+!>rDrT~ACC z9(ZS*$JN51uMjVlr9=Ifj0T_N?ZC^|3;#@y(uQo4G%J2J_n&o0pB6|*FNrSZ( zW;f&wssbBFY@vjXX*UZIOQ3l6gUd@THe?OCKpY-|6Y-A(^DH&)Anq#(3z`uzU{dAT z%Hf7yC!`1D3Ck(cCC1SWF4wS9)LLfpw|ZdWsGz9&9{K^bi}c{L@H9j8W`y!W;NfnD z${wrQf{Dxlck@#<1>`pfzx1Z;gC|1U^{km>SeQ@+wjm>0F`fVm(al@Jt>+w^ngg2% zo+1#Gz&}Ledc_II51CPyA6fA}39kn9$dt&w^z*w-asGerXTCop0USHew4(*N>odhKQ5fOlR%@~9k(#U}|L$3Dd&QeK(Z~@mr zk+2ZLni=k4yCh486oI8jB!PL517TL2ZsyS=)^@=%RYSke^3Z z5XCk9a7~;Hu@TvZ*ahT>IRtmz2pr&+yda|Cu(blW#uK>W*bsDB<-(nH1K5!@`M^?K zd3z?Rc<%CUSsIi>m5fpp4sQEL3?Z5@vo99BFSuv)g~iFPA=l8T$O_%iL{6w=p(@o@ z9y zq>*R$ME7B6n0~su*Ep=nC56n^lLvl>n=}g@~Hh62JAigM)5jr`Q$n2+0MZ zLhlHDwiEZvHmTOMa41*?kOF}vq>DHooGXxp9eH;Si&w&vNbzjHngL4%4v>+sGr$HI zyR!ajED-K|aKxjq$phF8vKgiohu%DXJqH-fq2pm|;$8`aRUowyc0>e@av-ZWxdWhq z?BP>E!ur639r+riW8pRF@#bWSyWnR4fL!|NUK~NtvV?` zdDY3ae89a!XO4)}S7Qt(-8VwoinJA8G?3tcG#4;495zWM0>xhR;II%kd^Q(eN^-#P zqfN~GQKb-)l2qfK@JCq%jhoQuLY~Qf@AB0B(1-tqckX`;jO-`2^GcMBfT(zWZFz{0 zpQi&ObL)@fW5&*2!Uz%I5k7eTgmv4B2Xx9aT!i0+ELHo0_|?t|xHLA%$h89@9lwL1 zKamM|WQiWHTu<2s7=;ukp%!$9{Na^QjnbzKo3`#r8OO~GEq4ITR(p1bc}XjAy$@8(NJ_Y~F}dJiyt$X1unj2d$`_^| zm()g>KpQy>@Bxi{Q z{1wo_!lTip$m4Ybdc=Goui4hjbF-nUWS&$TwF(NW0<2dMHu{hbq#^M|R z|BkQ*w^fa`0KYuOPyGjI!>c5a;5ekNYKOjMaXqqJEljT>J=>#-$3#xS@HiYr$zI6n zuvT>#z=PWr*9WXh_s><2_L)Q{_#i zNvkP1oKVM=l5d?&O_<}ZN;moh72qQu*4YTu3z*V~$UaDda$m%j3dT-3OmzB1&Kn_5N) zC&Ej8k%UQAYeJoBwl{?!ltZ&52~^}Tau79gw`GtA#h_MOx#}wk$kywXMNkKEz#1Vi zsvx>R+FT?OQXjfdC1|dZT?raW00`x9Lk8qv)>$$T2ESC642tkwty7=}z3{dWgE8h` zEe;&LgPoJQ4RDNYwN0nCW~#)`eNx9cV?;9g~!u`pv}2br5gdz8apLAarDmI4mR0a@$dD3-s2fP@jENoof^}Kl7rB zcRsH^AG%LSe->0ronZt=QbPs}7@ZTDjwi6;O9~*+0QF%saD<%b>?!uDiq}bO(9>#H zu;IlgJ|2C9vx2*FhJ&%2dRI&djufMT{>jG@n$Y4;Wc;>(hpSP ze)$ldMNS`qB1X5%d>qxXaG~v$4F+qq5|ovJ5cV09epERm$q=IgBiznxTB(j016Y%x z6Y$~?ewPKospDh3L^7DS#pJgvl)Y;4m=eo7yE{%p50S0xY*$-hieU@dVl=@jinx-l zYNa3tJ)1^ybPOcMTTNELB$s3k(LeRVtWbHmh0aHU5C!-?VoIoGfU|hdca0CgOe70y zmc$OrEWYg5t_)OxhmGkhN~Y8l8>JrNz%gW}Oz4U+jN$DHCWRYf|h zf#~xCsUtdiHAr9$C$6^6_T(exnFIk3ZiMFx;e~tQ{;%rJi=04-b*>D&0h_t$g+3yC zc+XyzN(Wd((flM#G5^iW3!`2PB-m<@!BS!m40m1N^4gvP|zsP2^ja&pm6hy$t?48%qhbyTZo;Sg-52?v8IQ_68z%08^WNGq}#wMcG9agCoPq0)w^w|9%w%D<0 z5CL2vxovgVhy#&feTAS-*a6&JhI(57ke5coj*#*fW=T;s7-<1)rwA4$IJF?MS?l#0 zi4Wr5(m%8fKfEn6uZzWyc65ZDc zS&zR;Eg3zveDCC)HK+i$Ab1AyN#mqm#o-79v32rj$Ft(0FO{*>6eljZ1?*j)vjNWB zXps)@@owlE0SotgJg(+Ame{dK$ARIk_cJrJ4I`MtBp^z{16X!`XnII?h4mU;Rr02` za|kRC_8VBj24KHc9l;`I(sb0EGx}(TDz}6h@OV!WMH4TBd=Lw=Xo$TQ5Uc8JN9ftM zHn@C3fJpc>aFXK(3+H1B#M|U;iO*60Xu@t;#{(KbBm5WMgus3MZG;zevLQ%>AbK#2 zDYEZeZAoAqFDIMg?y$(^Dz>k@`&ZNcILgJqZ0kJ}%0 zsZk1q<9c%fp=eeUuY3eOwoxT`kEWvgr<_!1?m6lr$>RPk}2|9%={ROJz#b5#Z)j*8~UVS>sLe4WNzn!^YmEaM!%I7vfiqdJ?TI z6>rJ))ybp|mMRCY{xDlnd5%LN90AL5n z05oY^3%jUFJt&7Qk-1%p+#egIfgQ$MORs9ICtwB7^y=dVGW{2 zbb&%|8dPEp6s0IRPXe&`YHVyn4Cv4bR$T>0aN}~HPIr|fKm}xi*JuD7*`8U~qmc-V zDJcl#=GVc>`T2P=8qV`teclyCZ%%|ZUvnd>%A;w9#=2gRzM`?PhZtDo{6v+Npt8rC zyV2Tv@vP=K&{#_`jhBW}Icwnn)i<)DVSr3B>yGPc9 zHwQ2@E~?hY(R^dol%4E-SG%o1NBE(u+6vdT#m(R`%ojD+4Ez!Z*0a{B_z zl;fXDDQWM{)ipFt18N$b+b6fPMuX&4wimh;&93=3rm?MlK1@lirif-h68hJ%>QcF< z4=i$4n_1NZwoa9Dq^9a*hCl14I5QS zM$kk9qz&#rxFj9<5ox0D0x;drs|x{0YKGuG6I-PYm=a7K<1Cc0jB_9`5qa>oyQu=? zI7;yldwNQg;53cmhgWIjMp)B?!Gnbt4xtvQLddA*2{1?=)ap(GjqtCtKYXq?e`foE zSh$01%z2`TEvO9M_*}FP?Zw@rhn=V#HkJn>`y&RIqfv5uq2Jp9jdUs z(NF#kagyR1giKBTbL@;Qw8vk<%B`yIbTB&NJO+k$0 zAo?|eU#EDvO9Vw0duS|ilYbgO>T>0!G);VIKn-KBbGxtg%T2wOI4<@kjmQftb4tiG z>=5#JsUIEGW79cGv4_TL5hh4|S!1+DR~-u=-kP8u!kWa2gRwp8Za1>mkw+Tg*8H{s zK5QZ$6g9q7Rn>^P`u&;$mW#EOHwmCYS5UAAiB1p2a~~RGL{wD``jP5L?B15<+);rS z>XXliB}P&NA;k5*JWyx9^DGGawKPaW=U=1Xc%AqJUN?kiiq}`K*CA7Ukn}rpVxzzf zqmtR6qxK%j0}Y$bq82B!gJjpSENJ$s>7bEqqNnL>3ko9U98-z_OYs?vN`&yYINu%K zDJ8G6LwDTXD_rT&-XXB>;lh);S(>3QhpX1xg79;7^?bLEtYCrAvU^70f|yrjbeswz z03z(bLuQ>P11IoeNKcj+N$Ratf;sJVb*+nC~0~(a0!uks~c}JC|me!N27<>7m zc|G9LCQz@rVSDGHv8x`iv%Q_>P2jk9pg}~%ZUGdk)3Er3;wtb%dO^O`TPZSx4K3&N3j}AjYCH z@yqgwnj-tlHH_ZTA+>ktL=&_)%W@oXnN~n!7L5W%P0Mz1G#sLNr3LM4ni?@iNP)B! zI+y==a)}g-95ci+}0R(yB3BhgkRVp5NYR26V)g%D$?5ZL44*MN- zkOmA@IS+G>MQW#O$Om9lw|K0rBCsYvG2!nFj0!xpN|s=1wvJ^Y?t(n$U20jQci0l13rU;1SJCF!Xaf@n&=e_Mq`L2PbS z4rAtCw-Ui6m5)Jb`_V6BLE-8MXZx*j?r(d4YN{dCt4i7%BROgr)~gnf2hPd0Ff`MW z-@yd9f_-H_7TO3vgceaXM36}<;QP{^n(@P4%&K?Q+-O2_5ZOG16-B@VwM)5ib%T(> zRpYKI)5US#Bx6?#3O?7|D2iw$ETQTJ*iG};b=+RwBzD#&6Qs!6oqW~%lL21COMO8> z@QDR~u&lmm_^^dHoqlzuC*_Q9qmV2a+`sT{QK6^K zHaT!~@Mvpt+@x&I zv$fhwK4&cr2}PU?!H^uD=prkm=+IM%K);&bYZ@?wAg5lB=_MP~>o650b*i(XO`#Q) zEX*(X(~VUNd8(t>>SzM3noib9U6h67iAR*E8H_^HF^BXDbU|%0Eaj)-r-GjY4;m26 z13}+qpf8K~)ck8CcT_ieov~Lu#Qn9;@XpZql|mM6rK<1SH6((nkZ^<7Og-?WYRUpk zN7OJ>l3RYI!ig2n#H5Y~?(@55bJqUSY)C5q=Bu=vKp}{_WH#jd3&V!IMBF!r0Jv>y27~@x5twSM_i_d)Hx3HXGbE4b*{W_!K^9?@Sya z5ZEuxVI#4NS6Vc#n&*LI>V}jTg#Fd{5Dhx%5#yUMS8YZn1y0r25Q6qxvH{`6iw1HX zO{r6oBobZ%>OTUWf6`INCMZC2BXUDHIK&W(R4$Z+%E>_!UBvI+NyFJ{8eW~ABCVy< z*{c2_EHalcMzQe{)X)Py9fdWG#CiY|+S5clk*==}*y>P5jZLbggIiprxaJ%hpFP9H z)HGEDPu9^we!gQ;8b(!#{sw~R%5k7IFfH7|={)8sBK>9<}8Ui7suE+yHn!y>&ve-l5t`nTsAqUv3b9CxOr(}s2or_1w5~V|j zSpzBW(JuB%8j=I5>8bXpMt4@#Y~A4&A;A`3|`d>}Py*Xae$3pI%ZQBp@d>kFJ9++|&i~b$T=k zb-)jYASyR&YF{-H0)D;PV3$o$1s|joo}mm7DZU%(OEb?+$M-)6-5Xaso70euPU=DM zzzB9&gKT|}A#k%}RLO zkYLZiTSXh7epC+Inh6HIh*_Qf)dV|K9+p$r!8c@nN?1(Lg#@&!1ONQXj*xCs1L(Zd zlBU6$pxQP@scf!M{R5qBHJ4S;QVX-JtHD)pqO~^yqAxFKNM(<;FydB4Kz$ES3M!Fu z^Olob(qJX0q1gDqsuMPta~e#lEO0FlK;QSQd06L{2>TNhKM$ccq8KoW>ZN_Erm%KqAuBgYy|OC>fhi-vp#|~GL6S!RRd~!XhIJMMn39HR4z?*6E*>OO~3~6 zw~5~QnlUg^Z2G<LByLIwqa;0u7czi^vsy^1(XeskYLV5l3ns zRp6S)3c=3egCS(0%A5jujVS5M4>F=Wp%mYs&xKqNFo+b>**CB29to)oEC5Wa0=4f@ zT!4x2u;ly#vskaV$ANqwRN--krkh~rHxk9xB6{_89#sRh?8g3l-M2bD@ zt7M>nGc*yZgHxJ(G?`;KgiS$)zncVqHn4^aGkwn!7EDS?59k0YCKru~tsYnrpi*ZG zKuFKuI!!#jnlCTso(y4HOr<=1e@CcO6|btV-_Z9X#G!FUk1(_W_v>JcS0i-f!WE4v z+D(T9M4U+yLb3Wn5uISir7IC)QiBVf2N!VNTaLptEUF_xI29bE0k1koYnpz16=`4s z%4fl4*jpq8kvSSK=03_hJDbTi^p2Q71FhuUoCx*;K(B}x0dP$xpMnW($$%Rp8d>Mr zo9?9QTT#?|0D?btRvq~pW5QP`wl|u_3h+)CjilQyboxj zgZg}0)A1S>Zx$3AYFAXrt>*PiFp<8RP9F}{hkzin_P(|Sh$rP#spMloMdFwMS(;3- z`I(54Wk2nxP3cJzL@EY^yOeCv>A?Fr*zbkoYtGiAPDzj|MMJQW z*A&_c9HA4G@{2#&^{D%B$9zMvLbwdv&d2O(6Y%EkxH@ z=qe5S2sp{#DH2ELkaDn)zV-$lXh&`-OG2RLH=25AY6Vy&eH^w;vz9>zoU8OMVntsO zv6n}!W+?*Oswow9+3(WVBf-%gyOc^LI0mkoG;%-#)Tv2_qj8RkT;bAj-dnY!Bk52M z*jxg9e{{49N~pj+c1=g(-%*fIuM4qaPUBFSOWhA?E7G_ueZdx=jr!9O|Abcv{LptQ zX%H23VGT7AAIMEuStG-#Yj3$$wCGI)tcEKQo%&ZZ_Df$lfY{J?grW7O=D32U6cRgz zwn3aDkFg5*>idS?cU5>99DS$ORe$k)1JmcEg}xO=#*K{*5P=T;TC6(Uel%BttkFaZ zu^al(*Q89aU}9d{J9Y=AG~&A~m6VGP_l|qI&WjwMv#JaD2~wFg#)^8`$*%A90ODXc z6vfpMNp9td@kDSXpx3Jkw2pc<7$LApU84ws_(H5hkfJxJY!-c08WN3wmNtH8S7a7E z#@^o9kEuR!1!cB+tk~p47K>+Kk?Rg=kL7EfAEsd>^(#A@wXpR~-3pBf`_~J&8D{}2 z@f@^>q!aIeQdXbJ{Wm3Li3P*U+a>@20fcEoLr_UWLm+T+Z)Rz1WdHzpoPCi!NW(xJ z#a}<9DisF{ia2DbPF6%k9JLBXs1Ry}Rvk<({emV9Ns5c3;979-W3lSs;;gHKs~`w| zfVj9iDY{6B|4RxjVmvtR$GdxvyLW)UUS_Hp90yd*GE#}SkjbtJp;vSvgno=6C^1u? z6U8Jv$Jaf4e7%eDEbnuFjvggvGQcMg&obSxh&PC*H!Yp>K5>KfMlwl!8t44~66z#`7{DY2PB$rIC5*RraP=N}`@q_=t?{3Zfv%OftgRzYb`B$1oUnL7uPLK-UBXofPp7n zG9*U|(Ddi?!220}Qx@pE1-jSV-kSS3eE`zbRq_TnI0Qxtl)dip?#}k!{yo#|?*~RV za+L`>{<8o800v@9M??Vs0RI60puMM)00009a7bBm000N(000N(0phfhS^xk52XskI zMF-;v2Ny0F&B49P001BWNkl$6%~*o$fYSQ^d1PwCcE?g{&;7SNeB=iKo<5q z&pz4A%uadd{l2H4BT6X(bmLG<5%L1KTrPaRVn7X`GjJae4;%$H0gHfN5cvnC&cp%l z{rW3+)~y>DLzu@TJ8qG6LZ^Ab=Z)u>LwJ4rr=iV_4OyjM%UtFt7oD_&>c; zqXr3U*COx$FcZk{*c}f{2Ic@;EP#Xo1%aACFQB;Ncn{DHn4Yh3V+Q^3LjWKFfcQ7X zhyauawmLQ^0v`Zx10(~VI^Oe3ciu_jpMN6y-=~1_8AP!*CQL2ax;1Z3of-g0fc%qs z)U8Xx@4w#)tO2c?13Q36fvp*V;iMBsk22u?`|q>BQ!V~IKUAP)KI4BE8D(()>lZwVD#R-6m8iOpf!+U?J5H_uXdA<^EClT{K=C% zDneTk+K3<`B%C=DnB`Ya(;eSGQLuBIQuG-={*q5LV8#r91D5WRY6B3L%e`jVvVe$n zHK~ILhDctNdKhg^Tn?}Y_&y-wUHVHCAsXrczk;P}i-dZW@_cdyDqA;h49xmVpzc+w06dWj z4j7J7o(VqR6`gm2QkY3e3K-&;ADty2VqHCoF%0ne$^%Utn+pKbR6;_0(#DNn4Xs+W zbBxICF(Sl>&_RSR$Bqpk;XIVQXO95&9GgBcEq&rjDbM})V{s<0wdt0C4stcc0gHQ| zXLqEv5{?4?1P%c`4G8+Fb7x)@;ZGep5g?1+eg1irX@VB90RF)+PWH?u&-aIm7he}W z^`=(REenfHtI;0ZuYiLN92wSXH5f=1_$dfFjjmItM2ra0BJ>CZ(y@EW6x8X{<$z#o z)1SCp$rY+r%~r`c0FZ0+@D`sQiT3US)B@fB-bLhA;4UCDcWonJ4BA8dl|Ye%-+xC$ znECO?H?M-O{}gd-I_ybFF|+wHrUN7afLu*_=BuwT;TRBuj(WhdkgP(fr-1c98|&Ra z1&*0)4Nflo8`SlIldP&|%uUfKP!Z1wv2$ z{BtT;YRL_4dkJ!UcV7hqtQi2vHGFub6osF70;R(jDg#R$5<`6;QQ#SXls$XM+o;hQ z1si~StmCI!9K@zjAP-OqcmW;7nrMMlWD56ZecJ!GrApnqT^^&9Y*HYH%U(2yO1-M(m z8mISm*0^yjjEPCNk_z094z9Z!D2ld%<-demISxz%z6N%AVYeG-F>@vlJ^5sgeQ6zY zW~&B-m6cW&BO(O}{SkrBaGq4H}?eH-=H) zQqrCBbw5F8%GvZ}bTqL_5vvrX+qSjMEK7j?z=Md?0`3IvKqvjoK!>$li1Gp5(dSv~ zhIK%_ubz5}_97hEyEkXPw5TYBcIIgjNU1u}+53Vb)6ET~Qj)nj>AHuzcP(3%lz;vK zsB7sa!Io%LUcvrb+qchh@ZHLV3n^N5B!vgPdAG-}>FJ4CGdz|YpZSPRs6anT~` zG;A0E$n~HpAAQ6xefqfY`3|6ycR)v$)&lm0*Q&*^%kZeroic^bo`0Sw(t?&8a$YD( z6$5TZ8wgruI4l`h1@v)R(}F8hVC0r9*If^Z)<9ne*b2Iw1*T@h`-~B334CFV$A>`g z*qbz1F~=lKIZ{X00pZr>1fVH~@hhg8qJYCd_c?QD*sK{M!j;_ryU#z*?kQ6=Dh$Jg zX^uhXS7gRjW?0s=wTQ$krA~{b3(W`OuJJfp<8>Mxt^;tNf@R~f2OzdTN`QgDo3Vjt z-s?+Y)vJT{ZPc<}yq2$FDW>@w&{v>zAz()H=Gs(iO_a{Xi(l3?`%RwAIHh=CneTlWd{$9Civ_WFP_PqG0lPOZmTIo#?jPNcsD35Q#*ou|Q+Axica& zNzI~T)@1z{k-bW(1c2SZf~zpT$~5Wda$y15woT74;py?&!$Df09kK>66nHhjL2?L1 z)~kntLx_|{7e70e$zi!29A=Who6$h0LWQpQ`lEL2V5G-`QtAxw94=Q8pf>P0I#1%S zOn#OhcnSC)O05Qg-bEco`UGZGIN`JOB`X zyYVz{{Fmq`V4FS$IvoJU?%YYS(xtEZy~GX}!0P}0*S5D7utc91IJdm##VFAd<(}mH zNoE;o6NXWvNQ)N9Z+-dYW!?X$@4wH|zJ1AEr3y(KH#P)jkydBxHXr;wApj8nq9X?m zFrapAe7c^~7@(7blgyFu@F#p{&#G#D`p{+2plfnZuibYarj!jz-sZp{`o3s5U zO$q?SU&*q34YNc8o3)qo3=PqCfhA|e?^yPc#AqFj2 z5CDk3Q^M)f^oWWAco0~aQIY^!C`hc)zdtc=zn!D*{qTVUR4rPR79~pHJ9Nm9s3;YG z^r)JWqKh25-0p0@=KkHgd0XpeuLJKoxNfz7luG;wIIwpwg)h?y8vsZSq%NgPQ?btPO zB4`WS|5=;k(aCEkZSUm-vL|#W0FWF^BYN~;)39N;2E^=r*I%#Su zA`4OKbgugKd9ZtT9`?gx7Xg4|fu;=`#?L)^q?%ntN9^W9n^qfJpUfg22LS)A_|i+f zGJLpSk3q+X2vKLxer+uhS*19%rR!rKy2y0^AU6U1IBOQ;pL&XX={JX=9cO*f4Qg!P z+{cLYK&ixAnlTG!R>J0ni55Ic0&c%x1+pl%>TRU{1_qcKA9P?qvj{F@PoB9)U=c&jH zhwVj&!|XsuE$l!S2Rw{n9JbS>w*gE1H7GU!kn__U&6;s&?pzGG7oB38%dvYCI`(T3 zKt%1@4E_E08|wMzO`Aqk{`^dD+7xuC{#vr;J3E3dAFvi}FqS0!p=ML%+$B0|2=Z zX!78}{L-tJ9d7=A=(fqZ9joUY;PIO+`)=irKk`ZA#<)lqieCX;RN^@__sz&*HUX_M zj4jGEJv9al;NIrVsZ_0603iNML%Mcl@sB?blyjbV0t&0_9?YgN+U%%*Y{nT!4dI0a;GBdRXD} z=>CemVgqA(4)uNbVU8_W;7mtug)Y!nz=}>&PWKM&A z_#u_6+&gsW0aGjL1<^jHv-X0GE?BV%m?KcQV`!++UWAq+Ed2WG06_eZoGakbl)!jg zl>YO|D>R!rm5|DnZLV7mU9!pRIII~Er#zm|T+l49NcgN4Er=0eaOci!TC*k~V&!zQ zibV{xLX+wvy^wn1}w7GaX@bB{+x(pbK+dVgTg9d8w;>CEqUR*BM zRlBzU2f1>xtJ9fSITQi9G-}K^xr0OUqV3VtG;Vy{SI!eY?UwJLg9S1z?jf1&RRN3N#Is=aX_aC8)=DT>+`^qb_JtoALc;*@UfA(1bAlH+u^ws;& zl~TaUJoHyThz0o8==#7OON5C7jyfccV07uzThJD~ThO}0cW9^Sb{kw-?b)(==~71D ze}B533+X@tj7d($9Tdc*4?f`6K7H(6ozcZ7Dmo5N1frF$M#(LYKFYh_etX$t;tovn zE2i9z?>LIs>9YN*#bcnPzGJRu55;d zCNp~90>KUij7&@sJH);; z&SV&FOfv=;l|eUIgGghg)XB(8Ol%7PRo*3wT3l6h{3P4SM;X-oIvJ<6FTA-dm`c@8!Kx^FYZ%%o=z9(a2c``aW0FbLigFAO-$Jnu0 zFQ@>z>r?eurFQz$bG#rz2>grm=F_4+IB+TDeTMhw!Ny_3(v(`=HZHf7SQTPT^upHn z6BZ!O0*Qzu0g1R=iOTEsNd_Q_2HQL6h~V>)$9fktf!7rziB{rAXE^Q$po#02TNrcb zPyirTi>ydqH)-DULx@z2HO-U$@(i!ktV!PT<=OZ7=kw8dEuf1t^trQdUpl|{Ubb9k z)0#C5uT?9x!J@KQ#65!s(KgK@l_?!Pc#xh&ir7m09oGF-w7?dAfuBx5`%t1R-KDBy z-%6nVxGY%N0sy%(@v04A=ZpRU)buA4uk{So&^;0X4kA)EMX9qBbJ%mNTC#)*4?JKe zVdX-pqUhMI+~_)H&!U})8I1)01EyLQHm7d#a6BxySku#hC;c*L(2FmoN+^!av;659 z>ZF-poiXI&qZ%~Gao_-`egFNbp(wGYc{*09t@)caU88hWnKvz+=U$6D*a=xK2iF-z z`e@;DS+QjYILIZ^l#f1Q`D?FXXe-YO(oCHB6&&{$FS=#9YHh8qHswlyGt>NVPP|mD z8c7>c?IY#TL63boxBsJEDYMQASoLLqgJd6lJ9R26G&sV5FC5@_4>(?|RV%-EjyE5F z95IX_v=`c@ZorwhwNJtCXvF&Un!6}P(N?Xt14At1NgH(8p=0REGiQJUJ6;+|s zlTWI?pL}w|yfe!}XH!8-XE_NRcNHtf7~LT`0Fdn99N)HSgXk4$JGusxP4@#!{Y5!< zOqwK8BRuFFtJK)Qn?9EUu~-+N1AF(SX19`@SA6{6w^Tt za2!Kqf`|kZ1E7)pJH?s;z0d%y=lMT+q8wrw}mGslR4NM4j$0NiG6 zP5>SO4o?d>1Oos8c&Bx1ozSe*BGPiL0a`1Vo(#m^VBoMgM{bn*9j$wSj=8#D!I}br zs*eGH0CcNZk>ssg35DJca459(YI^uxcQJ15+8dEu=0d6Y8Nl%bux4Cd2}kN zy==~ssHo3F@4S=mZ?Kg#T7;B;{)s@Txejn7qpfwnKmE-&e6VOyV5SE+2&|tx*?ypJ z9N^z9VPP$O@$qWJ`t>)=v(EhZV|-pOA`)qFj@uoaqY>~+o<@y$;IYR7vpfJ0m^*ng zDLSKXzGEdl4m1e4?KU;y&p&UNm*!}*ToU-!0S>PvO8!#4e}7`$emgM30|0@St5xId zh7E|WliP%}kcwv&953!iVd)p51AF%}@b0^9XU@y$Vrr4r?i8Rgu=ti{%>v-auEogT zJ;(7UPx4ycyr9cWI<@cDfR743_80?i0LR#?_3Cl@x8D$5FLx@sMS)GHffp2fU7>4N zUL83yFtY;y(PSk%RX`VRv-<^pt)N9v@#2i$yVvhu(!C;7(qI3Dt`bs{;Um)2B}Ha-KZ64I>!ST#Bw!1#lF@D4cAXzEAxe{0nBzX@dl;gBJI{bJsD>@kjf`z-<$ zFwKeRY(SfKp$j?x7F4DT!H_z(&S>h18B%J%q?@+pR}QIMnKRqAG4!v$aJkd3n+Nvp zrEkfSRPElK9wSC%`I=pBy_Nktck-pjLr`$=H6LFQAzE~T&{3t-SC=r?I|c_+pmF1z ztAqSUgybz-K&cT9aGU{Zpwt#esEqrp)N*zW*`ogzElU8+>JfdgRQ&Yc{c zH;+S`HA^B<|eFF$YV$ z*0lWl?~#xY1*k^-`d#$+f3|GlIT1SL$%9HsA@SI;oC^>uhIq-9IL zE&M`ahgAX!#*RHE_{S%88(aI8$M)vAOM-5a15cA|o2@MNN zw*q%9SB^)4*>Aqdsw}iDb5g9w!%s;`1YZa40WhRV6O_k;D8)DLzI(xAm&}{TdzC5? z4x&z-lHhXX7_xF@&I8E6Hf@O3T(KtbieuMcV9|?<7V*}M8GZ?l=S7(F%rl@%fqqR| zzmb1{;=typd+y==g$uL&+A2+^lEQAc0BjsQ7^REKg#e*|d%#mqv3vV=9upxZG*pU$ z6a^^`GU=(O_+{x*07k$5I*mp6*^x>hA~X}BmeoPGbw~)kYS$+5>{)j2*nx=foCpVZ z?V_FtbwqeTgnheqapd4Z9u}cf!GgS6uO4@c(5*@pIz~oPHLW9Wmgw}UQ-}zK!opaj z6!Acr&`?^6@Y{LPk3aeMzcI9q8=_n;T3XyP$LSz;{aoNMy009-G~hW8Fv(BNofF=9 zht>W2155&*cN*$|h6)l&wP{1Y$&;_j^&c0p2h%VhI2n>$E|+2Wd?gS$h*BXya^8m? zN?QEmk5tnwr%<3Gt5@g5+O>q{%Ei{xr}K95Hfs1XSXpcLVek;LuX$-m2#p=MB!NJ zXYi6GD9t5vSE*4W)8ANHSp}e2o;-O{Ct8jH$R8_KaCH8BTv|4N(6OrpN*x`uf4^UH zj=rTz>FSQA`3vA|#_=veLCq8}_UKX7uzB;VelPogu&PxRa5g`n{3~C_$H~D2fLMQ!qG~ahAgCa=UfcDxXgP8UDu~sb=@wY4a=C zd*TGHhzPM%OZS=e>k(bYE6tjA{I~%WP~cKPa7YL#S~N6)(kHU0|GfRwQ=I>oevO0^CsJ{dWBeeo4H_Ui_|UnG_5^;9tXI$PfbsPw zpRk~PdqSb0RR;7fhdS^c@P?~kL1MLo@YeO*AN`3v(+?t#4spTteD!kZy(F8i88@)j5j3(6EJ-~x_$7$`OUb= z$u>9?K|!dL6nQ9reg(P(Z~?vgCx?|P#qK?OR6aPPfFh4Rs_#XqR4x)yw5XZ9Yu8bG zDx5ie8n^x@2-p)`upnQ5@(J@gbf80cI8%D{qRodNrn{e?zW&-?fx>~Uh{UH(EfIwl zDI*I3pT7S-EBp4v1cn|v#zCME95NsTuU95K{4izr?!EpZR_uTQbkKwJZ*(OwQCnadGaIzi3Z5)b?YK}awh}FUYRk2nH@Ud z21CxC4gGcE#B)qru`OC4A_j0sLPL*M?B3lIYnn;1rkPZsOBYYF#_bUo$dbN&F~D$x zGkRd9xX3ZqP3eWeH zw+^5=x>1iEk$VVu7+61!>(ij8ga~mLxOOuU{sOk7q~NwZ=|6t;6+@qVl0%j(UKp5X zDbUd&A;bZ(n>TZ7C0$Zt`=(7iQ>6+az%NSCLWIr0hNL8dLqgcKZ5w?mRN#RD1DNyn z+Z?vUg=sr?k|$q2%H+5sMK?`144=wWB^^DZ zMt~lGv(C^Dh%{G9d9D^G>{_Z6-aUKL*-X8_CxOJOY^7^%=1n+#n#iaqqP5ccf@9aK zN-4hqhogXWlMLYa1DEU3($7Ed>#JKZvvbD|-YQq_^5uD;`S|fX(Y}2e820P|?NGG? zw|)wCxgJJ4%rA@1cb{><)LpwMTB_7V&#`vJ3SMi_Ahq;k>Y@nvWBa#j&Oy3Ws6euA zeYP3wikid%9ZNm`Jg-lhl;a0&V4F7VojMhCX!=)5upiCLT5l;uGV@A z80;+C&`~SEjg-~b9avzv-H(h-Nx5u*{9A;gFo3jbG4>+*v4UM$;f5Ww!>_bJXS9jd zrhi;PL6gQLC!78RkZa08&cw&t;27xu$94?k<#DEo@&j<3Ja&v-Q>WsB8i2D9?m7(P zKjTajpD)un+V2VgvKJlAe05h#{ihE)ve!wGqHA(pki01=_BlQRT4dP$Gp0EO?L71w zx~59{%R0g7KJ;MQ%TX`WG?Vcn!nT1V8ZiTdrg96wN27ug7Od$;`7=6ygtlsbNi zq4-{9nx5{fSMye_S`-Kg5qF6aYLqq?XDi(zLLKXSv4{wwL7pp7LM_bN{Vo6=1ws_O z=7F~Fv}(nhnIxVuvNiqjO(g4ge%GWjxJ!cBduR{tI2b!=|36d_MEp zH{QtcgY-tTW{6?9F->Rjh*Q9Q3ij1}>#fU@_ea#KMSc;K$0NSu$KfiZZ`T2e+Sagy z1dP0S)k1x(tXR)}6J1rl2=GD$!)UkT%P&nb0Z81qaRfn_H!W3R9=cy*mS}M2&TJn$ z){f{+HxRZ5IR)stVg=Q&VaKp+2T14Q#qAnTWzmJ+Vlp{wF7V|SexP@>6R`(roC6#R z_z&>+mH0Ufurd}d#6>z;$KStu8dZg0IInXfVfADzW?pI%&liMmS0} zwdQvM@a=`lwssJ~(AO?&y_Ura4vbSOdjaxFjT$yMdRyXD_<7{-w@u61<8rxjEI7u! z{kAhjr*&xFhN_z7xGuyT$ zOwX)7_O=k1m7E2DwYh0tjNEli~j1GTq~8nuV>wVG3^Ai7OmDB1$} zZ5FI|F{Bj;X)1bc$sU08df)+0E?HvB=o5gaGbnmr14GfhF1RfwhS$c7$+5stO7X*t z8T_F|r;jp#qyGE3rF`F(Y?`-<$QIk`xNG}% zr*du^eI_Jd+I8|-;7A5`14pfu-)BwoqUc^JVu59aTck=CkyDd_=We-01K;gt?7n?O zYYuPQHOz|{E*w(9Ih-Y10CH-{5(EmM)M}u#W7khW2Vj>%mn)C;gT5A$R()iGV^zUY zI7fJZaew@AC6`FbxWV(+U&Neebr&F{a%D#R_17g|=&hD5$yKOO+KOGcFzvEXBPzqJ z8E-9mVFFJ^!@wcky5aSB_}}>PxWMIx)YZa--j`?|Jq~mO=8DKEuTqJhKynwYcNhjl zdZD7Blh5)4C45SK1ax>7Fb^M2x46YzfJ?6b^;Z^s_F3wCUO#dqp_xm+p$3Fs5kPv> zsYAl5RiOKluOiJ}Z=(C>4e$Z}((!5ArcDg4QU#A*&IV=x$8ZHBf{GVs@|8CReb4%2 zm2UTJ-n{v8F7(IDnW;)TK+#sME{&T4F4$n|LfSj5TQKL8ugaifQPDzPWX~}L& z65)767SHc4Q3CJYz0Cn?zWp}a7cb`2k|h#tDdm8vs#J;hZQQuZ(1xB>mgLhu8D4ga zBqUk$%OEux;2fGVcrah~>P4OmI$2QWw`E$3nD>hOAQm_(q09B>a*XK%3I>E$t;+ic z5BiNsZ%~yg7#bY&(Vp+rg;>EWDR8~I=K8MZxR`o{w`0DdEPa4c$B*MqNdaAGBaij& zXGWYaVjzgDouFj9b!ZX7Rd)Jv6OI|Vu82aMT?S+U2_jv*0c3MSFYw*L3z(}%_34x8_A{qW(fz>(=_rEl(4liot&WN6Y?aOyc@vTy{>eA)>Mw-jDV#P0Aia<$p=A6x?jsUe4>@Dpl zL5BcuwQ9xwFTX^f5=za_phIvUNlTSp4($Hs0(!t7AAG=YkB66s4NKkD_qpddG;JC#J(()`bP`=P zOL>pVAo>(>TDLC62C9G-lPB|7n>Jbg%_RY3RPWxb`|v~DIz4$a&TtF3>kJ&b|E;&^ zFknEA2gbUUD;Zh8zFj$^D!L(PF2_O>4NP@KMqZ7Ab?L@E0T_AoD0%be&yi!5Xc(X? z8@_L?;JHm`fCh-u&(-^4BNYrR9X>o2AZ)69*4?ozfZzTF9+nn$y&=gob z4p3=kz?+tmOp~aJ6-nN*Wi`4a>@5Pl?@$SW8S=33syW6AFr(hy6Ewrz#R^7+@WAkqtT;z zX~>Wq{i3H08^((w9RA@41j<`_T2}`+4goddVfB3@M)-Z(GdYh%=W;9r@BH!$F6}(C zD``|*w|8$xz@DL>rT~L}|2@m+Z6`v!o=bfln-8OXW(@=iR3bI{#QwCzVFYkpLgchW zL{RCOXQqtDl`Sjg(W46OFskC%^|*rFc^fvQRp-t*da`uC;|@H3 z{Y8*rxG>ETKu3q}rj>D#z9@C#^BW{>wQZU>r!4vXvK}l1m^*PIR_BoSMVng@7;7Ek zQSd{d#~!<6Ia9%_js#0>3XTUAo83fItxA8(2jZk? z9T9PkJ7mBYUtFAnhzJ%qMru(pm6`%SRet3aJ~&TF`Rh%G_V35%^U*;B&z3E2ped$# z2I%4dM*=#`p<|hL?M~cCt(J|nKUY+L#|?b_+H2%%+!%qMErX1c;!ODS-EY3hl5CVg z(|X(n8SsJouDg;>I*xS%9e@5gb>gGTDqZXvK2qH%MB0Ek{p+u+%+Ok5{uf`^UrStI z3^S*$Wy%n(zxE=1lGAGnzIT@{&C5fF=2#{C&0@vq>~>?oQ}}!ci*g9lMVy_clxw*liemPNx{kYGbt<$ zu=tyAaOp0p5;x9E^*jPl*zQMns5Zik=Tt&aj9b1s(xbN8NibgRQ18*ON@2 zk1ZQE5Zk^zo;`ao6B7lWPe=P2h7LG148$-%SAEw*X&0@dOWA_ZbF6Cnt}{lDW~QcUre*@AT;iJb-4Y zHhl@aY7{6SFTVbIj*~^~(DV?fq!JSP0?%bQWDoGJktfeM^W;gQ8Z@|G;7AvX6#=~N zbf{{xsvQ;KLI?2$13sm{CoAw6X{Al|;+4TuU9nm<;*K0ir>Muxn8C(ZU!_W|T9+WJ z8EiC7>esJNyj==5CRc5hhg|!HW0+>7bz*at$^2S zS|C>uSpZrlw%Yedi{pA6?NjS)EzAZ`N5OwZJ17yDsnW{Sp4aLo@ScKatxW|@*ambv z2H%BgZdKdV=H=?vd6z5g+p+BuVSwu#9mHyZR=_gn=CL;a50N_*Fl+t#94A)26dq2D zNIi6}K#XIuq@calWq=P|#fmw@oBfSaOz`@^my#C3tu8bH0bZUxo7VH^GY&9v=T;IK zsg_;BMgIbDxsaeB`Y&J3Nl?k)#qIWC7=mFKxLktUEfNtShk@4f=V$5M5jARH8pcx6 z=W7&KFgHlIN{BbK&&_TSGg#@|oC{K}di7BH)_XfP=M$LjhLb6}&B3=8IFf;XoAAF3 zl2s78`g^8Ydk@me_M1S{4N4`A`|UR>>SEJ5kv?+(TF|{l{^KrL@F`%^F&ZBuBzyz!0V9U8ZV<(&F-Tfi@RY5k6YE5)t8*sHnjS z@$qM@MKUuwrQlxRPti5bY&r|X&$@sWYlj7l6F_ZX*Ux9pP_<5-oV>!m1MEUado}`^ znsB0Y)22*S>bimBU|bv#l`Go-x!g~%($AJQGqeYJq2rhblh0F->s&c2^Ve?Or`vJP zAz@Xko?jVOD)p1b#Pqq1{Q1SQe%j}{&+GH0WIV?-r7#K<5EEV%&FMR#D^=R@YEOlW zgaOly+_?pn;C)0Z?_2!!j7=!LM^#Z!B_-J(C^jzz}k*I#Gtph37r!Z3^!<@0&R zb7g%vs*7+d5Ho8Q|7vl=lzGb0)IO~ZTRJ_(2TZ)ocRBIB_gLPqAFd4CKmluj>Fd@} zxmvaJFDC6t6KJilX7&I-T*2fT+PynlMvla^lz>FbM6nr|v}O%=)Toi;Ud}g@CNbVB zjAjBQtkom|%xQsw&^Ij^2IO`_s2f6D5Ft>wbZF=md0sb<9Em`F7lem`JFkcl2Cf>0 zAw6r>%#i~T5*9`=bR0v8v_;089+)FHHM!9RzU(E^10)g@bVVmU3*-`sM=39$YzLMy zP2S3(VJVqWGWdPKVt}rYgK)0>TuD&S70rszHE;4}Ja. + +import base64 +import csv +import ctypes +import datetime +import getpass +import gettext +import hashlib +import json +import logging +import os +import platform +import re +import shlex +import shutil +import socket +import ssl +import subprocess +import sys +import tempfile +import threading +import time +import traceback +import zipfile + +from collections import OrderedDict +from collections.abc import MutableMapping +from decimal import Decimal +from enum import IntEnum +from functools import wraps +from pathlib import Path +from pprint import pprint +from string import Template +from typing import Any, Union +from urllib.request import Request, urlopen +from urllib.error import URLError, HTTPError + +import imaplib +import smtplib +from smtplib import SMTPException, SMTPAuthenticationError +from email.mime.multipart import MIMEMultipart +from email.mime.base import MIMEBase +from email.mime.text import MIMEText +from email.utils import formatdate +from email import encoders +import mailbox + +import uno +import unohelper +from com.sun.star.awt import MessageBoxButtons as MSG_BUTTONS +from com.sun.star.awt.MessageBoxResults import YES +from com.sun.star.awt import Rectangle, Size, Point +from com.sun.star.awt.PosSize import POSSIZE, SIZE +from com.sun.star.awt import Key, KeyModifier, KeyEvent +from com.sun.star.container import NoSuchElementException +from com.sun.star.datatransfer import XTransferable, DataFlavor + +from com.sun.star.beans import PropertyValue, NamedValue +from com.sun.star.sheet import TableFilterField +from com.sun.star.table.CellContentType import EMPTY, VALUE, TEXT, FORMULA +from com.sun.star.util import Time, Date, DateTime + +from com.sun.star.text.ControlCharacter import PARAGRAPH_BREAK +from com.sun.star.text.TextContentAnchorType import AS_CHARACTER + +from com.sun.star.lang import Locale +from com.sun.star.lang import XEventListener +from com.sun.star.awt import XActionListener +from com.sun.star.awt import XMenuListener +from com.sun.star.awt import XMouseListener +from com.sun.star.awt import XMouseMotionListener +from com.sun.star.awt import XFocusListener +from com.sun.star.awt import XKeyListener +from com.sun.star.awt import XItemListener +from com.sun.star.awt import XTabListener +from com.sun.star.awt import XWindowListener +from com.sun.star.awt import XTopWindowListener +from com.sun.star.awt.grid import XGridDataListener +from com.sun.star.awt.grid import XGridSelectionListener +from com.sun.star.script import ScriptEventDescriptor + +# ~ https://api.libreoffice.org/docs/idl/ref/namespacecom_1_1sun_1_1star_1_1awt_1_1FontUnderline.html +from com.sun.star.awt import FontUnderline +from com.sun.star.style.VerticalAlignment import TOP, MIDDLE, BOTTOM + +from com.sun.star.view.SelectionType import SINGLE, MULTI, RANGE + +from com.sun.star.sdb.CommandType import TABLE, QUERY, COMMAND + +try: + from peewee import Database, DateTimeField, DateField, TimeField, \ + __exception_wrapper__ +except ImportError as e: + Database = DateField = TimeField = DateTimeField = object + print('You need install peewee, only if you will develop with Base') + + +LOG_FORMAT = '%(asctime)s - %(levelname)s - %(message)s' +LOG_DATE = '%d/%m/%Y %H:%M:%S' +logging.addLevelName(logging.ERROR, '\033[1;41mERROR\033[1;0m') +logging.addLevelName(logging.DEBUG, '\x1b[33mDEBUG\033[1;0m') +logging.addLevelName(logging.INFO, '\x1b[32mINFO\033[1;0m') +logging.basicConfig(level=logging.DEBUG, format=LOG_FORMAT, datefmt=LOG_DATE) +log = logging.getLogger(__name__) + + +# ~ You can get custom salt +# ~ codecs.encode(os.urandom(16), 'hex') +# ~ but, not modify this file, modify in import file +SALT = b'c9548699d4e432dfd2b46adddafbb06d' + +TIMEOUT = 10 +LOG_NAME = 'ZAZ' +FILE_NAME_CONFIG = 'zaz-{}.json' + +LEFT = 0 +CENTER = 1 +RIGHT = 2 + +CALC = 'calc' +WRITER = 'writer' +DRAW = 'draw' +IMPRESS = 'impress' +BASE = 'base' +MATH = 'math' +BASIC = 'basic' +MAIN = 'main' +TYPE_DOC = { + CALC: 'com.sun.star.sheet.SpreadsheetDocument', + WRITER: 'com.sun.star.text.TextDocument', + DRAW: 'com.sun.star.drawing.DrawingDocument', + IMPRESS: 'com.sun.star.presentation.PresentationDocument', + BASE: 'com.sun.star.sdb.DocumentDataSource', + MATH: 'com.sun.star.formula.FormulaProperties', + BASIC: 'com.sun.star.script.BasicIDE', + MAIN: 'com.sun.star.frame.StartModule', +} + +OBJ_CELL = 'ScCellObj' +OBJ_RANGE = 'ScCellRangeObj' +OBJ_RANGES = 'ScCellRangesObj' +TYPE_RANGES = (OBJ_CELL, OBJ_RANGE, OBJ_RANGES) + +OBJ_SHAPES = 'com.sun.star.drawing.SvxShapeCollection' +OBJ_SHAPE = 'com.sun.star.comp.sc.ScShapeObj' +OBJ_GRAPHIC = 'SwXTextGraphicObject' + +OBJ_TEXTS = 'SwXTextRanges' +OBJ_TEXT = 'SwXTextRange' + + +# ~ from com.sun.star.sheet.FilterOperator import EMPTY, NO_EMPTY, EQUAL, NOT_EQUAL +class FilterOperator(IntEnum): + EMPTY = 0 + NO_EMPTY = 1 + EQUAL = 2 + NOT_EQUAL = 3 + +# ~ https://api.libreoffice.org/docs/idl/ref/servicecom_1_1sun_1_1star_1_1awt_1_1UnoControlEditModel.html#a54d3ff280d892218d71e667f81ce99d4 +class Border(IntEnum): + NO_BORDER = 0 + BORDER = 1 + SIMPLE = 2 + + +# ~ https://api.libreoffice.org/docs/idl/ref/namespacecom_1_1sun_1_1star_1_1sheet.html#aa5aa6dbecaeb5e18a476b0a58279c57a +class ValidationType(): + from com.sun.star.sheet.ValidationType \ + import ANY, WHOLE, DECIMAL, DATE, TIME, TEXT_LEN, LIST, CUSTOM +VT = ValidationType + + +# ~ https://api.libreoffice.org/docs/idl/ref/namespacecom_1_1sun_1_1star_1_1sheet.html#aecf58149730f4c8c5c18c70f3c7c5db7 +class ValidationAlertStyle(): + from com.sun.star.sheet.ValidationAlertStyle \ + import STOP, WARNING, INFO, MACRO +VAS = ValidationAlertStyle + + +# ~ https://api.libreoffice.org/docs/idl/ref/namespacecom_1_1sun_1_1star_1_1sheet_1_1ConditionOperator2.html +class ConditionOperator(): + from com.sun.star.sheet.ConditionOperator2 \ + import NONE, EQUAL, NOT_EQUAL, GREATER, GREATER_EQUAL, LESS, \ + LESS_EQUAL, BETWEEN, NOT_BETWEEN, FORMULA, DUPLICATE, NOT_DUPLICATE +CO = ConditionOperator + + +class DataPilotFieldOrientation(): + from com.sun.star.sheet.DataPilotFieldOrientation \ + import HIDDEN, COLUMN, ROW, PAGE, DATA +DPFO = DataPilotFieldOrientation + + +OS = platform.system() +IS_WIN = OS == 'Windows' +IS_MAC = OS == 'Darwin' +USER = getpass.getuser() +PC = platform.node() +DESKTOP = os.environ.get('DESKTOP_SESSION', '') +INFO_DEBUG = f"{sys.version}\n\n{platform.platform()}\n\n" + '\n'.join(sys.path) + +PYTHON = 'python' +if IS_WIN: + PYTHON = 'python.exe' + +_MACROS = {} +_start = 0 + +SECONDS_DAY = 60 * 60 * 24 +DIR = { + 'images': 'images', + 'locales': 'locales', +} + +KEY = { + 'enter': 1280, +} + +MODIFIERS = { + 'shift': KeyModifier.SHIFT, + 'ctrl': KeyModifier.MOD1, + 'alt': KeyModifier.MOD2, + 'ctrlmac': KeyModifier.MOD3, +} + +# ~ Menus +NODE_MENUBAR = 'private:resource/menubar/menubar' +MENUS = { + 'file': '.uno:PickList', + 'tools': '.uno:ToolsMenu', + 'help': '.uno:HelpMenu', + 'windows': '.uno:WindowList', + 'edit': '.uno:EditMenu', + 'view': '.uno:ViewMenu', + 'insert': '.uno:InsertMenu', + 'format': '.uno:FormatMenu', + 'styles': '.uno:FormatStylesMenu', + 'sheet': '.uno:SheetMenu', + 'data': '.uno:DataMenu', + 'table': '.uno:TableMenu', + 'form': '.uno:FormatFormMenu', + 'page': '.uno:PageMenu', + 'shape': '.uno:ShapeMenu', + 'slide': '.uno:SlideMenu', + 'show': '.uno:SlideShowMenu', +} + +DEFAULT_MIME_TYPE = 'png' +MIME_TYPE = { + 'png': 'image/png', + 'jpg': 'image/jpeg', +} + +MESSAGES = { + 'es': { + 'OK': 'Aceptar', + 'Cancel': 'Cancelar', + 'Select path': 'Seleccionar ruta', + 'Select directory': 'Seleccionar directorio', + 'Select file': 'Seleccionar archivo', + 'Incorrect user or password': 'Nombre de usuario o contraseña inválidos', + 'Allow less secure apps in GMail': 'Activa: Permitir aplicaciones menos segura en GMail', + } +} + + +CTX = uno.getComponentContext() +SM = CTX.getServiceManager() + + +def create_instance(name: str, with_context: bool=False, args: Any=None) -> Any: + if with_context: + instance = SM.createInstanceWithContext(name, CTX) + elif args: + instance = SM.createInstanceWithArguments(name, (args,)) + else: + instance = SM.createInstance(name) + return instance + + +def get_app_config(node_name: str, key: str=''): + name = 'com.sun.star.configuration.ConfigurationProvider' + service = 'com.sun.star.configuration.ConfigurationAccess' + cp = create_instance(name, True) + node = PropertyValue(Name='nodepath', Value=node_name) + try: + ca = cp.createInstanceWithArguments(service, (node,)) + if ca and not key: + return ca + if ca and ca.hasByName(key): + return ca.getPropertyValue(key) + except Exception as e: + error(e) + return '' + + +LANGUAGE = get_app_config('org.openoffice.Setup/L10N/', 'ooLocale') +LANG = LANGUAGE.split('-')[0] +try: + COUNTRY = LANGUAGE.split('-')[1] +except: + COUNTRY = '' +LOCALE = Locale(LANG, COUNTRY, '') +NAME = TITLE = get_app_config('org.openoffice.Setup/Product', 'ooName') +VERSION = get_app_config('org.openoffice.Setup/Product','ooSetupVersion') + +INFO_DEBUG = f"{NAME} v{VERSION} {LANGUAGE}\n\n{INFO_DEBUG}" + +node = '/org.openoffice.Office.Calc/Calculate/Other/Date' +y = get_app_config(node, 'YY') +m = get_app_config(node, 'MM') +d = get_app_config(node, 'DD') +DATE_OFFSET = datetime.date(y, m, d).toordinal() + + +def error(info): + log.error(info) + return + + +def debug(*args): + data = [str(a) for a in args] + log.debug('\t'.join(data)) + return + + +def info(*args): + data = [str(a) for a in args] + log.info('\t'.join(data)) + return + + +def save_log(path: str, data): + with open(path, 'a') as f: + f.write(f'{str(now())[:19]} -{LOG_NAME}- ') + pprint(data, stream=f) + return + + +def catch_exception(f): + @wraps(f) + def func(*args, **kwargs): + try: + return f(*args, **kwargs) + except Exception as e: + name = f.__name__ + if IS_WIN: + msgbox(traceback.format_exc()) + log.error(name, exc_info=True) + return func + + +def inspect(obj: Any) -> None: + zaz = create_instance('net.elmau.zaz.inspect') + if hasattr(obj, 'obj'): + obj = obj.obj + zaz.inspect(obj) + return + + +def mri(obj: Any) -> None: + m = create_instance('mytools.Mri') + if m is None: + msg = 'Extension MRI not found' + error(msg) + return + + if hasattr(obj, 'obj'): + obj = obj.obj + m.inspect(obj) + return + + +def run_in_thread(fn): + def run(*k, **kw): + t = threading.Thread(target=fn, args=k, kwargs=kw) + t.start() + return t + return run + + +def now(only_time: bool=False): + now = datetime.datetime.now() + if only_time: + now = now.time() + return now + + +def today(): + return datetime.date.today() + + +def _(msg): + if LANG == 'en': + return msg + + if not LANG in MESSAGES: + return msg + + return MESSAGES[LANG][msg] + + +def msgbox(message, title=TITLE, buttons=MSG_BUTTONS.BUTTONS_OK, type_msg='infobox'): + """ Create message box + type_msg: infobox, warningbox, errorbox, querybox, messbox + http://api.libreoffice.org/docs/idl/ref/interfacecom_1_1sun_1_1star_1_1awt_1_1XMessageBoxFactory.html + """ + toolkit = create_instance('com.sun.star.awt.Toolkit') + parent = toolkit.getDesktopWindow() + box = toolkit.createMessageBox(parent, type_msg, buttons, title, str(message)) + return box.execute() + + +def question(message, title=TITLE): + result = msgbox(message, title, MSG_BUTTONS.BUTTONS_YES_NO, 'querybox') + return result == YES + + +def warning(message, title=TITLE): + return msgbox(message, title, type_msg='warningbox') + + +def errorbox(message, title=TITLE): + return msgbox(message, title, type_msg='errorbox') + + +def get_type_doc(obj: Any) -> str: + for k, v in TYPE_DOC.items(): + if obj.supportsService(v): + return k + return '' + + +def _get_class_doc(obj: Any) -> Any: + classes = { + CALC: LOCalc, + WRITER: LOWriter, + DRAW: LODraw, + IMPRESS: LOImpress, + BASE: LOBase, + MATH: LOMath, + BASIC: LOBasic, + } + type_doc = get_type_doc(obj) + return classes[type_doc](obj) + + +def dict_to_property(values: dict, uno_any: bool=False): + ps = tuple([PropertyValue(Name=n, Value=v) for n, v in values.items()]) + if uno_any: + ps = uno.Any('[]com.sun.star.beans.PropertyValue', ps) + return ps + + +def _array_to_dict(values): + d = {v[0]: v[1] for v in values} + return d + + +def _property_to_dict(values): + d = {v.Name: v.Value for v in values} + return d + + +def json_dumps(data): + return json.dumps(data, indent=4, sort_keys=True) + + +def json_loads(data): + return json.loads(data) + + +def data_to_dict(data): + if isinstance(data, tuple) and isinstance(data[0], tuple): + return _array_to_dict(data) + + if isinstance(data, tuple) and isinstance(data[0], (PropertyValue, NamedValue)): + return _property_to_dict(data) + return {} + + +def _get_dispatch() -> Any: + return create_instance('com.sun.star.frame.DispatchHelper') + + +# ~ https://wiki.documentfoundation.org/Development/DispatchCommands +# ~ Used only if not exists in API +def call_dispatch(frame: Any, url: str, args: dict={}) -> None: + dispatch = _get_dispatch() + opt = dict_to_property(args) + dispatch.executeDispatch(frame, url, '', 0, opt) + return + + +def get_desktop(): + return create_instance('com.sun.star.frame.Desktop', True) + + +def _date_to_struct(value): + if isinstance(value, datetime.datetime): + d = DateTime() + d.Year = value.year + d.Month = value.month + d.Day = value.day + d.Hours = value.hour + d.Minutes = value.minute + d.Seconds = value.second + elif isinstance(value, datetime.date): + d = Date() + d.Day = value.day + d.Month = value.month + d.Year = value.year + elif isinstance(value, datetime.time): + d = Time() + d.Hours = value.hour + d.Minutes = value.minute + d.Seconds = value.second + return d + + +def _struct_to_date(value): + d = None + if isinstance(value, Time): + d = datetime.time(value.Hours, value.Minutes, value.Seconds) + elif isinstance(value, Date): + if value != Date(): + d = datetime.date(value.Year, value.Month, value.Day) + elif isinstance(value, DateTime): + if value.Year > 0: + d = datetime.datetime( + value.Year, value.Month, value.Day, + value.Hours, value.Minutes, value.Seconds) + return d + + +def _get_url_script(args: dict): + library = args['library'] + module = '.' + name = args['name'] + language = args.get('language', 'Python') + location = args.get('location', 'user') + + if language == 'Python': + module = '.py$' + elif language == 'Basic': + module = f".{module}." + if location == 'user': + location = 'application' + + url = 'vnd.sun.star.script' + url = f'{url}:{library}{module}{name}?language={language}&location={location}' + return url + + +def _call_macro(args: dict): + #~ https://wiki.openoffice.org/wiki/Documentation/DevGuide/Scripting/Scripting_Framework_URI_Specification + + url = _get_url_script(args) + args = args.get('args', ()) + + service = 'com.sun.star.script.provider.MasterScriptProviderFactory' + factory = create_instance(service) + script = factory.createScriptProvider('').getScript(url) + result = script.invoke(args, None, None)[0] + + return result + + +def call_macro(args, in_thread=False): + result = None + if in_thread: + t = threading.Thread(target=_call_macro, args=(args,)) + t.start() + else: + result = _call_macro(args) + return result + + +def run(command, capture=False, split=True): + if not split: + return subprocess.check_output(command, shell=True).decode() + + cmd = shlex.split(command) + result = subprocess.run(cmd, capture_output=capture, text=True, shell=IS_WIN) + if capture: + result = result.stdout + else: + result = result.returncode + return result + + +def popen(command): + try: + proc = subprocess.Popen(shlex.split(command), shell=IS_WIN, + stdout=subprocess.PIPE, stderr=subprocess.STDOUT) + for line in proc.stdout: + yield line.decode().rstrip() + except Exception as e: + error(e) + yield (e.errno, e.strerror) + + +def sleep(seconds): + time.sleep(seconds) + return + + +class TimerThread(threading.Thread): + + def __init__(self, event, seconds, macro): + threading.Thread.__init__(self) + self.stopped = event + self.seconds = seconds + self.macro = macro + + def run(self): + info('Timer started... {}'.format(self.macro['name'])) + while not self.stopped.wait(self.seconds): + _call_macro(self.macro) + info('Timer stopped... {}'.format(self.macro['name'])) + return + + +def start_timer(name, seconds, macro): + global _MACROS + _MACROS[name] = threading.Event() + thread = TimerThread(_MACROS[name], seconds, macro) + thread.start() + return + + +def stop_timer(name): + global _MACROS + _MACROS[name].set() + del _MACROS[name] + return + + +def install_locales(path: str, domain: str='base', dir_locales=DIR['locales']): + path_locales = _P.join(_P(path).path, dir_locales) + try: + lang = gettext.translation(domain, path_locales, languages=[LANG]) + lang.install() + _ = lang.gettext + except Exception as e: + from gettext import gettext as _ + error(e) + return _ + + +def _export_image(obj, args): + name = 'com.sun.star.drawing.GraphicExportFilter' + exporter = create_instance(name) + path = _P.to_system(args['URL']) + args = dict_to_property(args) + exporter.setSourceDocument(obj) + exporter.filter(args) + return _P.exists(path) + + +def sha256(data): + result = hashlib.sha256(data.encode()).hexdigest() + return result + +def sha512(data): + result = hashlib.sha512(data.encode()).hexdigest() + return result + + +def get_config(key='', default={}, prefix='conf'): + name_file = FILE_NAME_CONFIG.format(prefix) + values = None + path = _P.join(_P.config('UserConfig'), name_file) + if not _P.exists(path): + return default + + values = _P.from_json(path) + if key: + values = values.get(key, default) + + return values + + +def set_config(key, value, prefix='conf'): + name_file = FILE_NAME_CONFIG.format(prefix) + path = _P.join(_P.config('UserConfig'), name_file) + values = get_config(default={}, prefix=prefix) + values[key] = value + result = _P.to_json(path, values) + return result + + +def start(): + global _start + _start = now() + info(_start) + return + + +def end(get_seconds: bool=False): + global _start + e = now() + td = e - _start + result = str(td) + if get_seconds: + result = td.total_seconds() + return result + + +def get_epoch(): + n = now() + return int(time.mktime(n.timetuple())) + + +def render(template, data): + s = Template(template) + return s.safe_substitute(**data) + + +def get_size_screen(): + if IS_WIN: + user32 = ctypes.windll.user32 + res = f'{user32.GetSystemMetrics(0)}x{user32.GetSystemMetrics(1)}' + else: + args = 'xrandr | grep "*" | cut -d " " -f4' + res = run(args, split=False) + return res.strip() + + +def url_open(url, data=None, headers={}, verify=True, get_json=False): + err = '' + req = Request(url) + for k, v in headers.items(): + req.add_header(k, v) + try: + # ~ debug(url) + if verify: + if not data is None and isinstance(data, str): + data = data.encode() + response = urlopen(req, data=data) + else: + context = ssl._create_unverified_context() + response = urlopen(req, context=context) + except HTTPError as e: + error(e) + err = str(e) + except URLError as e: + error(e.reason) + err = str(e.reason) + else: + headers = dict(response.info()) + result = response.read() + if get_json: + result = json.loads(result) + + return result, headers, err + + +def _get_key(password): + from cryptography.hazmat.primitives import hashes + from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC + + kdf = PBKDF2HMAC(algorithm=hashes.SHA256(), length=32, salt=SALT, + iterations=100000) + key = base64.urlsafe_b64encode(kdf.derive(password.encode())) + return key + + +def encrypt(data, password): + from cryptography.fernet import Fernet + + f = Fernet(_get_key(password)) + if isinstance(data, str): + data = data.encode() + token = f.encrypt(data).decode() + return token + + +def decrypt(token, password): + from cryptography.fernet import Fernet, InvalidToken + + data = '' + f = Fernet(_get_key(password)) + try: + data = f.decrypt(token.encode()).decode() + except InvalidToken as e: + error('Invalid Token') + return data + + +def switch_design_mode(doc): + call_dispatch(doc.frame, '.uno:SwitchControlDesignMode') + return + + +class SmtpServer(object): + + def __init__(self, config): + self._server = None + self._error = '' + self._sender = '' + self._is_connect = self._login(config) + + def __enter__(self): + return self + + def __exit__(self, exc_type, exc_value, traceback): + self.close() + + @property + def is_connect(self): + return self._is_connect + + @property + def error(self): + return self._error + + def _login(self, config): + name = config['server'] + port = config['port'] + is_ssl = config['ssl'] + self._sender = config['user'] + hosts = ('gmail' in name or 'outlook' in name) + try: + if is_ssl and hosts: + self._server = smtplib.SMTP(name, port, timeout=TIMEOUT) + self._server.ehlo() + self._server.starttls() + self._server.ehlo() + elif is_ssl: + self._server = smtplib.SMTP_SSL(name, port, timeout=TIMEOUT) + self._server.ehlo() + else: + self._server = smtplib.SMTP(name, port, timeout=TIMEOUT) + + self._server.login(self._sender, config['password']) + msg = 'Connect to: {}'.format(name) + debug(msg) + return True + except smtplib.SMTPAuthenticationError as e: + if '535' in str(e): + self._error = _('Incorrect user or password') + return False + if '534' in str(e) and 'gmail' in name: + self._error = _('Allow less secure apps in GMail') + return False + except smtplib.SMTPException as e: + self._error = str(e) + return False + except Exception as e: + self._error = str(e) + return False + return False + + def _body(self, msg): + body = msg.replace('\\n', '
') + return body + + def send(self, message): + file_name = 'attachment; filename={}' + email = MIMEMultipart() + email['From'] = self._sender + email['To'] = message['to'] + email['Cc'] = message.get('cc', '') + email['Subject'] = message['subject'] + email['Date'] = formatdate(localtime=True) + if message.get('confirm', False): + email['Disposition-Notification-To'] = email['From'] + email.attach(MIMEText(self._body(message['body']), 'html')) + + for path in message.get('files', ()): + fn = _P(path).file_name + part = MIMEBase('application', 'octet-stream') + part.set_payload(_P.read_bin(path)) + encoders.encode_base64(part) + part.add_header('Content-Disposition', f'attachment; filename={fn}') + email.attach(part) + + receivers = ( + email['To'].split(',') + + email['CC'].split(',') + + message.get('bcc', '').split(',')) + try: + self._server.sendmail(self._sender, receivers, email.as_string()) + msg = 'Email sent...' + debug(msg) + if message.get('path', ''): + self.save_message(email, message['path']) + return True + except Exception as e: + self._error = str(e) + return False + return False + + def save_message(self, email, path): + mbox = mailbox.mbox(path, create=True) + mbox.lock() + try: + msg = mailbox.mboxMessage(email) + mbox.add(msg) + mbox.flush() + finally: + mbox.unlock() + return + + def close(self): + try: + self._server.quit() + msg = 'Close connection...' + debug(msg) + except: + pass + return + + +def _send_email(server, messages): + with SmtpServer(server) as server: + if server.is_connect: + for msg in messages: + server.send(msg) + else: + error(server.error) + return server.error + + +def send_email(server, message): + messages = message + if isinstance(message, dict): + messages = (message,) + t = threading.Thread(target=_send_email, args=(server, messages)) + t.start() + return + + +class ImapServer(object): + + def __init__(self, config): + self._server = None + self._error = '' + self._is_connect = self._login(config) + + def __enter__(self): + return self + + def __exit__(self, exc_type, exc_value, traceback): + self.close() + + @property + def is_connect(self): + return self._is_connect + + @property + def error(self): + return self._error + + def _login(self, config): + try: + # ~ hosts = 'gmail' in config['server'] + if config['ssl']: + self._server = imaplib.IMAP4_SSL(config['server'], config['port']) + else: + self._server = imaplib.IMAP4(config['server'], config['port']) + self._server.login(config['user'], config['password']) + self._server.select() + return True + except imaplib.IMAP4.error as e: + self._error = str(e) + return False + except Exception as e: + self._error = str(e) + return False + return False + + def get_folders(self, exclude=()): + folders = {} + result, subdir = self._server.list() + for s in subdir: + print(s.decode('utf-8')) + return folders + + def close(self): + try: + self._server.close() + self._server.logout() + msg = 'Close connection...' + debug(msg) + except: + pass + return + + +# ~ Classes + +class LOBaseObject(object): + + def __init__(self, obj): + self._obj = obj + + def __setattr__(self, name, value): + exists = hasattr(self, name) + if not exists and not name in ('_obj', '_index', '_view'): + setattr(self._obj, name, value) + else: + super().__setattr__(name, value) + + def __enter__(self): + return self + + def __exit__(self, exc_type, exc_value, traceback): + pass + + @property + def obj(self): + return self._obj + + +class LODocument(object): + FILTERS = { + 'doc': 'MS Word 97', + 'docx': 'MS Word 2007 XML', + } + + def __init__(self, obj): + self._obj = obj + self._cc = self.obj.getCurrentController() + self._undo = True + + def __enter__(self): + return self + + def __exit__(self, exc_type, exc_value, traceback): + self.close() + + @property + def obj(self): + return self._obj + + @property + def title(self): + return self.obj.getTitle() + @title.setter + def title(self, value): + self.obj.setTitle(value) + + @property + def type(self): + return self._type + + @property + def uid(self): + return self.obj.RuntimeUID + + @property + def frame(self): + return self._cc.getFrame() + + @property + def is_saved(self): + return self.obj.hasLocation() + + @property + def is_modified(self): + return self.obj.isModified() + + @property + def is_read_only(self): + return self.obj.isReadOnly() + + @property + def path(self): + return _P.to_system(self.obj.URL) + + @property + def dir(self): + return _P(self.path).path + + @property + def file_name(self): + return _P(self.path).file_name + + @property + def name(self): + return _P(self.path).name + + @property + def status_bar(self): + return self._cc.getStatusIndicator() + + @property + def visible(self): + w = self.frame.ContainerWindow + return w.isVisible() + @visible.setter + def visible(self, value): + w = self.frame.ContainerWindow + w.setVisible(value) + + @property + def zoom(self): + return self._cc.ZoomValue + @zoom.setter + def zoom(self, value): + self._cc.ZoomValue = value + + @property + def undo(self): + return self._undo + @undo.setter + def undo(self, value): + self._undo = value + um = self.obj.UndoManager + if value: + try: + um.leaveUndoContext() + except: + pass + else: + um.enterHiddenUndoContext() + + def clear_undo(self): + self.obj.getUndoManager().clear() + return + + @property + def selection(self): + sel = self.obj.CurrentSelection + # ~ return _get_class_uno(sel) + return sel + + @property + def table_auto_formats(self): + taf = create_instance('com.sun.star.sheet.TableAutoFormats') + return taf.ElementNames + + def create_instance(self, name): + obj = self.obj.createInstance(name) + return obj + + def set_focus(self): + w = self.frame.ComponentWindow + w.setFocus() + return + + def copy(self): + call_dispatch(self.frame, '.uno:Copy') + return + + def insert_contents(self, args={}): + call_dispatch(self.frame, '.uno:InsertContents', args) + return + + def paste(self): + sc = create_instance('com.sun.star.datatransfer.clipboard.SystemClipboard') + transferable = sc.getContents() + self._cc.insertTransferable(transferable) + # ~ return self.obj.getCurrentSelection() + return + + def select(self, obj): + self._cc.select(obj) + return + + def to_pdf(self, path: str='', args: dict={}): + """ + https://wiki.documentfoundation.org/Macros/Python_Guide/PDF_export_filter_data + """ + path_pdf = path + filter_name = '{}_pdf_Export'.format(self.type) + filter_data = dict_to_property(args, True) + args = { + 'FilterName': filter_name, + 'FilterData': filter_data, + } + opt = dict_to_property(args) + try: + self.obj.storeToURL(_P.to_url(path), opt) + except Exception as e: + error(e) + path_pdf = '' + + return _P.exists(path_pdf) + + def export(self, path: str, ext: str='', args: dict={}): + if not ext: + ext = _P(path).ext + filter_name = self.FILTERS[ext] + filter_data = dict_to_property(args, True) + args = { + 'FilterName': filter_name, + 'FilterData': filter_data, + } + opt = dict_to_property(args) + try: + self.obj.storeToURL(_P.to_url(path), opt) + except Exception as e: + error(e) + path = '' + return _P.exists(path) + + def save(self, path: str='', args: dict={}) -> bool: + result = True + opt = dict_to_property(args) + if path: + try: + self.obj.storeAsURL(_P.to_url(path), opt) + except Exception as e: + error(e) + result = False + else: + self.obj.store() + return result + + def close(self): + self.obj.close(True) + return + + +class LOCellStyle(LOBaseObject): + + def __init__(self, obj): + super().__init__(obj) + + @property + def name(self): + return self.obj.Name + + @property + def properties(self): + properties = self.obj.PropertySetInfo.Properties + data = {p.Name: getattr(self.obj, p.Name) for p in properties} + return data + @properties.setter + def properties(self, values): + _set_properties(self.obj, values) + + +class LOCellStyles(object): + + def __init__(self, obj, doc): + self._obj = obj + self._doc = doc + + def __len__(self): + return len(self.obj) + + def __getitem__(self, index): + return LOCellStyle(self.obj[index]) + + def __setitem__(self, key, value): + self.obj[key] = value + + def __delitem__(self, key): + if not isinstance(key, str): + key = key.Name + del self.obj[key] + + def __contains__(self, item): + return item in self.obj + + @property + def obj(self): + return self._obj + + @property + def names(self): + return self.obj.ElementNames + + def new(self, name: str=''): + obj = self._doc.create_instance('com.sun.star.style.CellStyle') + if name: + self.obj[name] = obj + obj = LOCellStyle(obj) + return obj + + +class LOCalc(LODocument): + + def __init__(self, obj): + super().__init__(obj) + self._type = CALC + self._sheets = obj.Sheets + + def __getitem__(self, index): + return LOCalcSheet(self._sheets[index]) + + def __setitem__(self, key, value): + self._sheets[key] = value + + def __len__(self): + return self._sheets.Count + + def __contains__(self, item): + return item in self._sheets + + @property + def names(self): + names = self.obj.Sheets.ElementNames + return names + + @property + def selection(self): + sel = self.obj.CurrentSelection + if sel.ImplementationName in TYPE_RANGES: + sel = LOCalcRange(sel) + elif sel.ImplementationName == OBJ_SHAPES: + if len(sel) == 1: + sel = sel[0] + sel = LODrawPage(sel.Parent)[sel.Name] + else: + debug(sel.ImplementationName) + return sel + + @property + def active(self): + return LOCalcSheet(self._cc.ActiveSheet) + + @property + def headers(self): + return self._cc.ColumnRowHeaders + @headers.setter + def headers(self, value): + self._cc.ColumnRowHeaders = value + + @property + def tabs(self): + return self._cc.SheetTabs + @tabs.setter + def tabs(self, value): + self._cc.SheetTabs = value + + @property + def cs(self): + return self.cell_styles + @property + def cell_styles(self): + obj = self.obj.StyleFamilies['CellStyles'] + return LOCellStyles(obj, self) + + @property + def db_ranges(self): + # ~ return LOCalcDataBaseRanges(self.obj.DataBaseRanges) + return self.obj.DatabaseRanges + + def activate(self, sheet): + obj = sheet + if isinstance(sheet, LOCalcSheet): + obj = sheet.obj + elif isinstance(sheet, str): + obj = self._sheets[sheet] + self._cc.setActiveSheet(obj) + return + + def new_sheet(self): + s = self.create_instance('com.sun.star.sheet.Spreadsheet') + return s + + def insert(self, name): + names = name + if isinstance(name, str): + names = (name,) + for n in names: + self._sheets[n] = self.new_sheet() + return LOCalcSheet(self._sheets[n]) + + def move(self, name, pos=-1): + index = pos + if pos < 0: + index = len(self) + if isinstance(name, LOCalcSheet): + name = name.name + self._sheets.moveByName(name, index) + return + + def remove(self, name): + if isinstance(name, LOCalcSheet): + name = name.name + self._sheets.removeByName(name) + return + + def copy(self, name, new_name='', pos=-1): + if isinstance(name, LOCalcSheet): + name = name.name + index = pos + if pos < 0: + index = len(self) + self._sheets.copyByName(name, new_name, index) + return LOCalcSheet(self._sheets[new_name]) + + def copy_from(self, doc, source='', target='', pos=-1): + index = pos + if pos < 0: + index = len(self) + + names = source + if not source: + names = doc.names + elif isinstance(source, str): + names = (source,) + + new_names = target + if not target: + new_names = names + elif isinstance(target, str): + new_names = (target,) + + for i, name in enumerate(names): + self._sheets.importSheet(doc.obj, name, index + i) + self[index + i].name = new_names[i] + + return LOCalcSheet(self._sheets[index]) + + def sort(self, reverse=False): + names = sorted(self.names, reverse=reverse) + for i, n in enumerate(names): + self.move(n, i) + return + + def render(self, data, sheet=None, clean=True): + if sheet is None: + sheet = self.active + return sheet.render(data, clean=clean) + + +class LOChart(object): + + def __init__(self, name, obj, draw_page): + self._name = name + self._obj = obj + self._eobj = self._obj.EmbeddedObject + self._type = 'Column' + self._cell = None + self._shape = self._get_shape(draw_page) + self._pos = self._shape.Position + + def __getitem__(self, index): + return LOBaseObject(self.diagram.getDataRowProperties(index)) + + def __enter__(self): + return self + + def __exit__(self, exc_type, exc_value, traceback): + pass + + @property + def obj(self): + return self._obj + + @property + def name(self): + return self._name + + @property + def diagram(self): + return self._eobj.Diagram + + @property + def type(self): + return self._type + @type.setter + def type(self, value): + self._type = value + if value == 'Bar': + self.diagram.Vertical = True + return + type_chart = f'com.sun.star.chart.{value}Diagram' + self._eobj.setDiagram(self._eobj.createInstance(type_chart)) + + @property + def cell(self): + return self._cell + @cell.setter + def cell(self, value): + self._cell = value + self._shape.Anchor = value.obj + + @property + def position(self): + return self._pos + @position.setter + def position(self, value): + self._pos = value + self._shape.Position = value + + def _get_shape(self, draw_page): + for shape in draw_page: + if shape.PersistName == self.name: + break + return shape + + +class LOSheetCharts(object): + + def __init__(self, obj, sheet): + self._obj = obj + self._sheet = sheet + + def __getitem__(self, index): + return LOChart(index, self.obj[index], self._sheet.draw_page) + + def __enter__(self): + return self + + def __exit__(self, exc_type, exc_value, traceback): + pass + + def __contains__(self, item): + return item in self.obj + + def __len__(self): + return len(self.obj) + + @property + def obj(self): + return self._obj + + def new(self, name, pos_size, data): + self.obj.addNewByName(name, pos_size, data, True, True) + return LOChart(name, self.obj[name], self._sheet.draw_page) + + +class LOSheetTableField(object): + + def __init__(self, obj): + self._obj = obj + + def __enter__(self): + return self + + def __exit__(self, exc_type, exc_value, traceback): + pass + + @property + def obj(self): + return self._obj + + @property + def name(self): + return self.obj.Name + + @property + def orientation(self): + return self.obj.Orientation + @orientation.setter + def orientation(self, value): + self.obj.Orientation = value + + +# ~ com.sun.star.sheet.DataPilotFieldOrientation.ROW +class LOSheetTable(object): + + def __init__(self, obj): + self._obj = obj + self._source = None + + def __getitem__(self, index): + field = self.obj.DataPilotFields[index] + return LOSheetTableField(field) + + def __enter__(self): + return self + + def __exit__(self, exc_type, exc_value, traceback): + pass + + @property + def obj(self): + return self._obj + + @property + def filter(self): + return self.obj.ShowFilterButton + @filter.setter + def filter(self, value): + self.obj.ShowFilterButton = value + + @property + def source(self): + return self._source + @source.setter + def source(self, value): + self._source = value + self.obj.SourceRange = value.range_address + + @property + def rows(self): + return self.obj.RowFields + @rows.setter + def rows(self, values): + if not isinstance(values, tuple): + values = (values,) + for v in values: + with self[v] as f: + f.orientation = DPFO.ROW + @property + def columns(self): + return self.obj.ColumnFields + @columns.setter + def columns(self, values): + if not isinstance(values, tuple): + values = (values,) + for v in values: + with self[v] as f: + f.orientation = DPFO.COLUMN + + @property + def data(self): + return self.obj.DataFields + @data.setter + def data(self, values): + if not isinstance(values, tuple): + values = (values,) + for v in values: + with self[v] as f: + f.orientation = DPFO.DATA + + +class LOSheetTables(object): + + def __init__(self, obj, sheet): + self._obj = obj + self._sheet = sheet + + def __getitem__(self, index): + return LOSheetTable(self.obj[index]) + + def __enter__(self): + return self + + def __exit__(self, exc_type, exc_value, traceback): + pass + + def __contains__(self, item): + return item in self.obj + + @property + def obj(self): + return self._obj + + @property + def count(self): + return self.obj.Count + + @property + def names(self): + return self.obj.ElementNames + + def new(self, name, target): + table = self.obj.createDataPilotDescriptor() + self.obj.insertNewByName(name, target.address, table) + return LOSheetTable(self.obj[name]) + + def remove(self, name): + self.obj.removeByName(name) + return + + +class LOFormControl(LOBaseObject): + EVENTS = { + 'action': 'actionPerformed', + 'click': 'mousePressed', + } + TYPES = { + 'actionPerformed': 'XActionListener', + 'mousePressed': 'XMouseListener', + } + + def __init__(self, obj, view, form): + super().__init__(obj) + self._view = view + self._form = form + self._m = view.Model + self._index = -1 + + def __setattr__(self, name, value): + if name in ('_form', '_view', '_m', '_index'): + self.__dict__[name] = value + else: + super().__setattr__(name, value) + + def __str__(self): + return f'{self.name} ({self.type}) {[self.index]}' + + @property + def form(self): + return self._form + + @property + def doc(self): + return self.obj.Parent.Forms.Parent + + @property + def name(self): + return self._m.Name + @name.setter + def name(self, value): + self._m.Name = value + + @property + def tag(self): + return self._m.Tag + @tag.setter + def tag(self, value): + self._m.Tag = value + + @property + def index(self): + return self._index + @index.setter + def index(self, value): + self._index = value + + @property + def enabled(self): + return self._m.Enabled + @enabled.setter + def enabled(self, value): + self._m.Enabled = value + + @property + def events(self): + return self.form.getScriptEvents(self.index) + def add_event(self, name, macro): + if not 'name' in macro: + macro['name'] = '{}_{}'.format(self.name, name) + + event = ScriptEventDescriptor() + event.AddListenerParam = '' + event.EventMethod = self.EVENTS[name] + event.ListenerType = self.TYPES[event.EventMethod] + event.ScriptCode = _get_url_script(macro) + event.ScriptType = 'Script' + + for ev in self.events: + if ev.EventMethod == event.EventMethod and \ + ev.ListenerType == event.ListenerType: + self.form.revokeScriptEvent(self.index, + event.ListenerType, event.EventMethod, event.AddListenerParam) + break + + self.form.registerScriptEvent(self.index, event) + return + + def set_focus(self): + self._view.setFocus() + return + + +class LOFormControlLabel(LOFormControl): + + def __init__(self, obj, view, form): + super().__init__(obj, view, form) + + @property + def type(self): + return 'label' + + @property + def value(self): + return self._m.Label + @value.setter + def value(self, value): + self._m.Label = value + + +class LOFormControlText(LOFormControl): + + def __init__(self, obj, view, form): + super().__init__(obj, view, form) + + @property + def type(self): + return 'text' + + @property + def value(self): + return self._m.Text + @value.setter + def value(self, value): + self._m.Text = value + + +class LOFormControlButton(LOFormControl): + + def __init__(self, obj, view, form): + super().__init__(obj, view, form) + + @property + def type(self): + return 'button' + + @property + def value(self): + return self._m.Label + @value.setter + def value(self, value): + self._m.Text = Label + + +FORM_CONTROL_CLASS = { + 'label': LOFormControlLabel, + 'text': LOFormControlText, + 'button': LOFormControlButton, +} + + +class LOForm(object): + MODELS = { + 'label': 'com.sun.star.form.component.FixedText', + 'text': 'com.sun.star.form.component.TextField', + 'button': 'com.sun.star.form.component.CommandButton', + } + + def __init__(self, obj, draw_page): + self._obj = obj + self._dp = draw_page + self._controls = {} + self._init_controls() + + def __getitem__(self, index): + control = self.obj[index] + return self._controls[control.Name] + + def __enter__(self): + return self + + def __exit__(self, exc_type, exc_value, traceback): + pass + + def __contains__(self, item): + return item in self.obj + + def __len__(self): + return len(self.obj) + + def __str__(self): + return f'Form: {self.name}' + + def _init_controls(self): + types = { + 'com.sun.star.form.OFixedTextModel': 'label', + 'com.sun.star.form.OEditModel': 'text', + 'com.sun.star.form.OButtonModel': 'button', + } + for i, control in enumerate(self.obj): + name = control.Name + tipo = types[control.ImplementationName] + view = self.doc.CurrentController.getControl(control) + control = FORM_CONTROL_CLASS[tipo](control, view) + control.index = i + setattr(self, name, control) + self._controls[name] = control + return + + @property + def obj(self): + return self._obj + + @property + def name(self): + return self.obj.Name + @name.setter + def name(self, value): + self.obj.Name = value + + @property + def source(self): + return self.obj.DataSourceName + @source.setter + def source(self, value): + self.obj.DataSourceName = value + + @property + def type(self): + return self.obj.CommandType + @type.setter + def type(self, value): + self.obj.CommandType = value + + @property + def command(self): + return self.obj.Command + @command.setter + def command(self, value): + self.obj.Command = value + + @property + def doc(self): + return self.obj.Parent.Parent + + def _special_properties(self, tipo, args): + if tipo == 'button': + # ~ if 'ImageURL' in args: + # ~ args['ImageURL'] = self._set_image_url(args['ImageURL']) + args['FocusOnClick'] = args.get('FocusOnClick', False) + return args + return args + + def add(self, args): + name = args['Name'] + tipo = args.pop('Type').lower() + w = args.pop('Width') + h = args.pop('Height') + x = args.pop('X', 0) + y = args.pop('Y', 0) + control = self.doc.createInstance('com.sun.star.drawing.ControlShape') + control.setSize(Size(w, h)) + control.setPosition(Point(x, y)) + model = self.doc.createInstance(self.MODELS[tipo]) + args = self._special_properties(tipo, args) + _set_properties(model, args) + control.Control = model + index = len(self) + self.obj.insertByIndex(index, model) + self._dp.add(control) + view = self.doc.CurrentController.getControl(self.obj.getByName(name)) + control = FORM_CONTROL_CLASS[tipo](control, view, self.obj) + control.index = index + setattr(self, name, control) + self._controls[name] = control + return control + + +class LOSheetForms(object): + + def __init__(self, draw_page): + self._dp = draw_page + self._obj = draw_page.Forms + + def __getitem__(self, index): + return LOForm(self.obj[index], self._dp) + + def __enter__(self): + return self + + def __exit__(self, exc_type, exc_value, traceback): + pass + + def __contains__(self, item): + return item in self.obj + + def __len__(self): + return len(self.obj) + + @property + def obj(self): + return self._obj + + @property + def doc(self): + return self.obj.Parent + + @property + def count(self): + return len(self) + + @property + def names(self): + return self.obj.ElementNames + + def insert(self, name): + form = self.doc.createInstance('com.sun.star.form.component.Form') + self.obj.insertByName(name, form) + return LOForm(form, self._dp) + + def remove(self, index): + if isinstance(index, int): + self.obj.removeByIndex(index) + else: + self.obj.removeByName(index) + return + + +# ~ IsFiltered, +# ~ IsManualPageBreak, +# ~ IsStartOfNewPage +class LOSheetRows(object): + + def __init__(self, sheet, obj): + self._sheet = sheet + self._obj = obj + + def __getitem__(self, index): + if isinstance(index, int): + rows = LOSheetRows(self._sheet, self.obj[index]) + else: + rango = self._sheet[index.start:index.stop,0:] + rows = LOSheetRows(self._sheet, rango.obj.Rows) + return rows + + def __len__(self): + return self.obj.Count + + @property + def obj(self): + return self._obj + + @property + def visible(self): + return self._obj.IsVisible + @visible.setter + def visible(self, value): + self._obj.IsVisible = value + + @property + def color(self): + return self.obj.CellBackColor + @color.setter + def color(self, value): + self.obj.CellBackColor = value + + @property + def is_transparent(self): + return self.obj.IsCellBackgroundTransparent + @is_transparent.setter + def is_transparent(self, value): + self.obj.IsCellBackgroundTransparent = value + + @property + def height(self): + return self.obj.Height + @height.setter + def height(self, value): + self.obj.Height = value + + def optimal(self): + self.obj.OptimalHeight = True + return + + def insert(self, index, count): + self.obj.insertByIndex(index, count) + return + + def remove(self, index, count): + self.obj.removeByIndex(index, count) + return + + +# ~ IsManualPageBreak, +# ~ IsStartOfNewPage +class LOSheetColumns(object): + + def __init__(self, sheet, obj): + self._sheet = sheet + self._obj = obj + + def __getitem__(self, index): + if isinstance(index, (int, str)): + rows = LOSheetColumns(self._sheet, self.obj[index]) + else: + rango = self._sheet[0,index.start:index.stop] + rows = LOSheetColumns(self._sheet, rango.obj.Columns) + return rows + + def __len__(self): + return self.obj.Count + + @property + def obj(self): + return self._obj + + @property + def visible(self): + return self._obj.IsVisible + @visible.setter + def visible(self, value): + self._obj.IsVisible = value + + @property + def width(self): + return self.obj.Width + @width.setter + def width(self, value): + self.obj.Width = value + + def optimal(self): + self.obj.OptimalWidth = True + return + + def insert(self, index, count): + self.obj.insertByIndex(index, count) + return + + def remove(self, index, count): + self.obj.removeByIndex(index, count) + return + + +class LOCalcSheet(object): + + def __init__(self, obj): + self._obj = obj + + def __getitem__(self, index): + return LOCalcRange(self.obj[index]) + + def __enter__(self): + return self + + def __exit__(self, exc_type, exc_value, traceback): + pass + + def __str__(self): + return f'easymacro.LOCalcSheet: {self.name}' + + @property + def obj(self): + return self._obj + + @property + def name(self): + return self._obj.Name + @name.setter + def name(self, value): + self._obj.Name = value + + @property + def code_name(self): + return self._obj.CodeName + @code_name.setter + def code_name(self, value): + self._obj.CodeName = value + + @property + def visible(self): + return self._obj.IsVisible + @visible.setter + def visible(self, value): + self._obj.IsVisible = value + + @property + def is_protected(self): + return self._obj.isProtected() + + @property + def password(self): + return '' + @visible.setter + def password(self, value): + self.obj.protect(value) + + def unprotect(self, value): + try: + self.obj.unprotect(value) + return True + except: + pass + return False + + @property + def color(self): + return self._obj.TabColor + @color.setter + def color(self, value): + self._obj.TabColor = get_color(value) + + @property + def used_area(self): + cursor = self.get_cursor() + cursor.gotoEndOfUsedArea(True) + return LOCalcRange(self[cursor.AbsoluteName].obj) + + @property + def draw_page(self): + return LODrawPage(self.obj.DrawPage) + + @property + def dp(self): + return self.draw_page + + @property + def shapes(self): + return self.draw_page + + @property + def doc(self): + return LOCalc(self.obj.DrawPage.Forms.Parent) + + @property + def charts(self): + return LOSheetCharts(self.obj.Charts, self) + + @property + def tables(self): + return LOSheetTables(self.obj.DataPilotTables, self) + + @property + def rows(self): + return LOSheetRows(self, self.obj.Rows) + + @property + def columns(self): + return LOSheetColumns(self, self.obj.Columns) + + @property + def forms(self): + return LOSheetForms(self.obj.DrawPage) + + @property + def events(self): + names = ('OnFocus', 'OnUnfocus', 'OnSelect', 'OnDoubleClick', + 'OnRightClick', 'OnChange', 'OnCalculate') + evs = self.obj.Events + events = {n: _property_to_dict(evs.getByName(n)) for n in names + if evs.getByName(n)} + return events + @events.setter + def events(self, values): + pv = '[]com.sun.star.beans.PropertyValue' + ev = self.obj.Events + for name, v in values.items(): + url = _get_url_script(v) + args = dict_to_property(dict(EventType='Script', Script=url)) + # ~ e.replaceByName(k, args) + uno.invoke(ev, 'replaceByName', (name, uno.Any(pv, args))) + + @property + def search_descriptor(self): + return self.obj.createSearchDescriptor() + + @property + def replace_descriptor(self): + return self.obj.createReplaceDescriptor() + + def activate(self): + self.doc.activate(self.obj) + return + + def clean(self): + doc = self.doc + sheet = doc.create_instance('com.sun.star.sheet.Spreadsheet') + doc._sheets.replaceByName(self.name, sheet) + return + + def move(self, pos=-1): + index = pos + if pos < 0: + index = len(self.doc) + self.doc._sheets.moveByName(self.name, index) + return + + def remove(self): + self.doc._sheets.removeByName(self.name) + return + + def copy(self, new_name='', pos=-1): + index = pos + if pos < 0: + index = len(self.doc) + self.doc._sheets.copyByName(self.name, new_name, index) + return LOCalcSheet(self.doc._sheets[new_name]) + + def copy_to(self, doc, target='', pos=-1): + index = pos + if pos < 0: + index = len(doc) + name = self.name + if not target: + new_name = name + + doc._sheets.importSheet(self.doc.obj, name, index) + sheet = doc[name] + sheet.name = new_name + return sheet + + def get_cursor(self, cell=None): + if cell is None: + cursor = self.obj.createCursor() + else: + cursor = self.obj.createCursorByRange(cell) + return cursor + + def render(self, data, rango=None, clean=True): + if rango is None: + rango = self.used_area + return rango.render(data, clean) + + def find(self, search_string, rango=None): + if rango is None: + rango = self.used_area + return rango.find(search_string) + + +class LOCalcRange(object): + + def __init__(self, obj): + self._obj = obj + self._sd = None + self._is_cell = obj.ImplementationName == OBJ_CELL + + def __getitem__(self, index): + return LOCalcRange(self.obj[index]) + + def __iter__(self): + self._r = 0 + self._c = 0 + return self + + def __next__(self): + try: + rango = self[self._r, self._c] + except Exception as e: + raise StopIteration + self._c += 1 + if self._c == self.columns: + self._c = 0 + self._r +=1 + return rango + + def __enter__(self): + return self + + def __exit__(self, exc_type, exc_value, traceback): + pass + + def __contains__(self, item): + return item.in_range(self) + + def __str__(self): + if self.is_none: + s = 'Range: None' + else: + s = f'Range: {self.name}' + return s + + @property + def obj(self): + return self._obj + + @property + def is_none(self): + return self.obj is None + + @property + def is_cell(self): + return self._is_cell + + @property + def back_color(self): + return self._obj.CellBackColor + @back_color.setter + def back_color(self, value): + self._obj.CellBackColor = get_color(value) + + @property + def dp(self): + return self.sheet.dp + + @property + def sheet(self): + return LOCalcSheet(self.obj.Spreadsheet) + + @property + def doc(self): + doc = self.obj.Spreadsheet.DrawPage.Forms.Parent + return LODocument(doc) + + @property + def name(self): + return self.obj.AbsoluteName + + @property + def code_name(self): + name = self.name.replace('$', '').replace('.', '_').replace(':', '') + return name + + @property + def columns(self): + return self.obj.Columns.Count + + @property + def column(self): + c1 = self.address.Column + c2 = c1 + 1 + ra = self.current_region.range_address + r1 = ra.StartRow + r2 = ra.EndRow + 1 + return LOCalcRange(self.sheet[r1:r2, c1:c2].obj) + + @property + def rows(self): + return LOSheetRows(self.sheet, self.obj.Rows) + + @property + def row(self): + r1 = self.address.Row + r2 = r1 + 1 + ra = self.current_region.range_address + c1 = ra.StartColumn + c2 = ra.EndColumn + 1 + return LOCalcRange(self.sheet[r1:r2, c1:c2].obj) + + @property + def type(self): + return self.obj.Type + + @property + def value(self): + v = None + if self.type == VALUE: + v = self.obj.getValue() + elif self.type == TEXT: + v = self.obj.getString() + elif self.type == FORMULA: + v = self.obj.getFormula() + return v + @value.setter + def value(self, data): + if isinstance(data, str): + # ~ print(isinstance(data, str), data[0]) + if data[0] in '=': + self.obj.setFormula(data) + # ~ print('Set Formula') + else: + self.obj.setString(data) + elif isinstance(data, Decimal): + self.obj.setValue(float(data)) + elif isinstance(data, (int, float, bool)): + self.obj.setValue(data) + elif isinstance(data, datetime.datetime): + d = data.toordinal() + t = (data - datetime.datetime.fromordinal(d)).seconds / SECONDS_DAY + self.obj.setValue(d - DATE_OFFSET + t) + elif isinstance(data, datetime.date): + d = data.toordinal() + self.obj.setValue(d - DATE_OFFSET) + elif isinstance(data, datetime.time): + d = (data.hour * 3600 + data.minute * 60 + data.second) / SECONDS_DAY + self.obj.setValue(d) + + @property + def date(self): + value = int(self.obj.Value) + date = datetime.date.fromordinal(value + DATE_OFFSET) + return date + + @property + def time(self): + seconds = self.obj.Value * SECONDS_DAY + time_delta = datetime.timedelta(seconds=seconds) + time = (datetime.datetime.min + time_delta).time() + return time + + @property + def datetime(self): + return datetime.datetime.combine(self.date, self.time) + + @property + def data(self): + return self.obj.getDataArray() + @data.setter + def data(self, values): + if self._is_cell: + self.to_size(len(values), len(values[0])).data = values + else: + self.obj.setDataArray(values) + + @property + def dict(self): + rows = self.data + k = rows[0] + data = [dict(zip(k, r)) for r in rows[1:]] + return data + @dict.setter + def dict(self, values): + data = [tuple(values[0].keys())] + data += [tuple(d.values()) for d in values] + self.data = data + + @property + def formula(self): + return self.obj.getFormulaArray() + @formula.setter + def formula(self, values): + self.obj.setFormulaArray(values) + + @property + def array_formula(self): + return self.obj.ArrayFormula + @array_formula.setter + def array_formula(self, value): + self.obj.ArrayFormula = value + + @property + def address(self): + return self.obj.CellAddress + + @property + def range_address(self): + return self.obj.RangeAddress + + @property + def cursor(self): + cursor = self.obj.Spreadsheet.createCursorByRange(self.obj) + return cursor + + @property + def current_region(self): + cursor = self.cursor + cursor.collapseToCurrentRegion() + return LOCalcRange(self.sheet[cursor.AbsoluteName].obj) + + @property + def next_cell(self): + a = self.current_region.range_address + col = a.StartColumn + row = a.EndRow + 1 + return LOCalcRange(self.sheet[row, col].obj) + + @property + def position(self): + return self.obj.Position + + @property + def size(self): + return self.obj.Size + + @property + def possize(self): + data = { + 'Width': self.size.Width, + 'Height': self.size.Height, + 'X': self.position.X, + 'Y': self.position.Y, + } + return data + + @property + def visible(self): + cursor = self.cursor + rangos = cursor.queryVisibleCells() + rangos = [LOCalcRange(self.sheet[r.AbsoluteName].obj) for r in rangos] + return tuple(rangos) + + @property + def merged_area(self): + cursor = self.cursor + cursor.collapseToMergedArea() + rango = LOCalcRange(self.sheet[cursor.AbsoluteName].obj) + return rango + + @property + def empty(self): + cursor = self.sheet.get_cursor(self.obj) + cursor = self.cursor + rangos = cursor.queryEmptyCells() + rangos = [LOCalcRange(self.sheet[r.AbsoluteName].obj) for r in rangos] + return tuple(rangos) + + @property + def merge(self): + return self.obj.IsMerged + @merge.setter + def merge(self, value): + self.obj.merge(value) + + @property + def style(self): + return self.obj.CellStyle + @style.setter + def style(self, value): + self.obj.CellStyle = value + + @property + def auto_format(self): + return '' + @auto_format.setter + def auto_format(self, value): + self.obj.autoFormat(value) + + @property + def validation(self): + return self.obj.Validation + @validation.setter + def validation(self, values): + current = self.validation + if not values: + current.Type = ValidationType.ANY + current.ShowInputMessage = False + else: + is_list = False + for k, v in values.items(): + if k == 'Type' and v == VT.LIST: + is_list = True + if k == 'Formula1' and is_list: + if isinstance(v, (tuple, list)): + v = ';'.join(['"{}"'.format(i) for i in v]) + setattr(current, k, v) + self.obj.Validation = current + + def select(self): + self.doc.select(self.obj) + return + + def search(self, options, find_all=True): + rangos = None + + descriptor = self.sheet.search_descriptor + descriptor.setSearchString(options['Search']) + descriptor.SearchCaseSensitive = options.get('CaseSensitive', False) + descriptor.SearchWords = options.get('Words', False) + if hasattr(descriptor, 'SearchRegularExpression'): + descriptor.SearchRegularExpression = options.get('RegularExpression', False) + if hasattr(descriptor, 'SearchType') and 'Type' in options: + descriptor.SearchType = options['Type'] + + if find_all: + found = self.obj.findAll(descriptor) + else: + found = self.obj.findFirst(descriptor) + + if found: + if found.ImplementationName == OBJ_CELL: + rangos = LOCalcRange(found) + else: + rangos = [LOCalcRange(f) for f in found] + + return rangos + + def replace(self, options): + descriptor = self.sheet.replace_descriptor + descriptor.setSearchString(options['Search']) + descriptor.setReplaceString(options['Replace']) + descriptor.SearchCaseSensitive = options.get('CaseSensitive', False) + descriptor.SearchWords = options.get('Words', False) + if hasattr(descriptor, 'SearchRegularExpression'): + descriptor.SearchRegularExpression = options.get('RegularExpression', False) + if hasattr(descriptor, 'SearchType') and 'Type' in options: + descriptor.SearchType = options['Type'] + count = self.obj.replaceAll(descriptor) + return count + + def in_range(self, rango): + if isinstance(rango, LOCalcRange): + address = rango.range_address + else: + address = rango.RangeAddress + result = self.cursor.queryIntersection(address) + return bool(result.Count) + + def offset(self, rows=0, cols=1): + ra = self.range_address + col = ra.EndColumn + cols + row = ra.EndRow + rows + return LOCalcRange(self.sheet[row, col].obj) + + def to_size(self, rows, cols): + cursor = self.cursor + cursor.collapseToSize(cols, rows) + return LOCalcRange(self.sheet[cursor.AbsoluteName].obj) + + def copy(self, source): + self.sheet.obj.copyRange(self.address, source.range_address) + return + + def copy_to(self, cell, formula=False): + rango = cell.to_size(self.rows, self.columns) + if formula: + rango.formula = self.data + else: + rango.data = self.data + return + + def copy_from(self, rango, formula=False): + data = rango + if isinstance(rango, LOCalcRange): + if formula: + data = rango.formula + else: + data = rango.data + rows = len(data) + cols = len(data[0]) + if formula: + self.to_size(rows, cols).formula = data + else: + self.to_size(rows, cols).data = data + return + + def optimal_width(self): + self.obj.Columns.OptimalWidth = True + return + + def clean_render(self, template='\{(\w.+)\}'): + self._sd.SearchRegularExpression = True + self._sd.setSearchString(template) + self.obj.replaceAll(self._sd) + return + + def render(self, data, clean=True): + self._sd = self.sheet.obj.createSearchDescriptor() + self._sd.SearchCaseSensitive = False + for k, v in data.items(): + cell = self._render_value(k, v) + return cell + + def _render_value(self, key, value, parent=''): + cell = None + if isinstance(value, dict): + for k, v in value.items(): + # ~ print(1, 'RENDER', k, v) + cell = self._render_value(k, v, key) + return cell + elif isinstance(value, (list, tuple)): + self._render_list(key, value) + return + + search = f'{{{key}}}' + if parent: + search = f'{{{parent}.{key}}}' + ranges = self.find_all(search) + + if ranges is None: + return + + # ~ for cell in ranges or range(0): + for cell in ranges: + self._set_new_value(cell, search, value) + return LOCalcRange(cell) + + def _set_new_value(self, cell, search, value): + if not cell.ImplementationName == 'ScCellObj': + return + + if isinstance(value, str): + pattern = re.compile(search, re.IGNORECASE) + new_value = pattern.sub(value, cell.String) + cell.String = new_value + else: + LOCalcRange(cell).value = value + return + + def _render_list(self, key, rows): + for row in rows: + for k, v in row.items(): + self._render_value(k, v) + return + + def find(self, search_string): + if self._sd is None: + self._sd = self.sheet.obj.createSearchDescriptor() + self._sd.SearchCaseSensitive = False + + self._sd.setSearchString(search_string) + cell = self.obj.findFirst(self._sd) + if cell: + cell = LOCalcRange(cell) + return cell + + def find_all(self, search_string): + if self._sd is None: + self._sd = self.sheet.obj.createSearchDescriptor() + self._sd.SearchCaseSensitive = False + + self._sd.setSearchString(search_string) + ranges = self.obj.findAll(self._sd) + return ranges + + def filter(self, args, with_headers=True): + ff = TableFilterField() + ff.Field = args['Field'] + ff.Operator = args['Operator'] + if isinstance(args['Value'], str): + ff.IsNumeric = False + ff.StringValue = args['Value'] + else: + ff.IsNumeric = True + ff.NumericValue = args['Value'] + + fd = self.obj.createFilterDescriptor(True) + fd.ContainsHeader = with_headers + fd.FilterFields = ((ff,)) + # ~ self.obj.AutoFilter = True + self.obj.filter(fd) + return + + def copy_format_from(self, rango): + rango.select() + self.doc.copy() + self.select() + args = { + 'Flags': 'T', + 'MoveMode': 4, + } + url = '.uno:InsertContents' + call_dispatch(self.doc.frame, url, args) + return + + def to_image(self): + self.select() + self.doc.copy() + args = {'SelectedFormat': 141} + url = '.uno:ClipboardFormatItems' + call_dispatch(self.doc.frame, url, args) + return self.sheet.shapes[-1] + + def insert_image(self, path, args={}): + ps = self.possize + args['Width'] = args.get('Width', ps['Width']) + args['Height'] = args.get('Height', ps['Height']) + args['X'] = args.get('X', ps['X']) + args['Y'] = args.get('Y', ps['Y']) + # ~ img.ResizeWithCell = True + img = self.sheet.dp.insert_image(path, args) + img.anchor = self.obj + args.clear() + return img + + def insert_shape(self, tipo, args={}): + ps = self.possize + args['Width'] = args.get('Width', ps['Width']) + args['Height'] = args.get('Height', ps['Height']) + args['X'] = args.get('X', ps['X']) + args['Y'] = args.get('Y', ps['Y']) + + shape = self.sheet.dp.add(tipo, args) + shape.anchor = self.obj + args.clear() + return + + def filter_by_color(self, cell): + rangos = cell.column[1:,:].visible + for r in rangos: + for c in r: + if c.back_color != cell.back_color: + c.rows.visible = False + return + + def clear(self, what=1023): + # ~ http://api.libreoffice.org/docs/idl/ref/namespacecom_1_1sun_1_1star_1_1sheet_1_1CellFlags.html + self.obj.clearContents(what) + return + + def transpose(self): + # ~ 'Flags': 'A', + # ~ 'FormulaCommand': 0, + # ~ 'SkipEmptyCells': False, + # ~ 'AsLink': False, + # ~ 'MoveMode': 4, + self.select() + self.doc.copy() + self.clear(1023) + self[0,0].select() + self.doc.insert_contents({'Transpose': True}) + _CB.set('') + return + + def transpose_data(self, formula=False): + data = self.data + if formula: + data = self.formula + data = tuple(zip(*data)) + self.clear(1023) + self[0,0].copy_from(data, formula=formula) + return + + def merge_by_row(self): + for r in range(len(self.rows)): + self[r].merge = True + return + + def fill(self, source=1): + self.obj.fillAuto(0, source) + return + + def _cast(self, t, v): + if not t: + return v + + if t == datetime.date: + nv = datetime.date.fromordinal(int(v) + DATE_OFFSET) + else: + nv = t(v) + return nv + + def get_data(self, types): + values = [ + [self._cast(types[i], v) for i, v in enumerate(row)] + for row in self.data + ] + return values + + +class LOWriterStyles(object): + + def __init__(self, styles): + self._styles = styles + + @property + def names(self): + return {s.DisplayName: s.Name for s in self._styles} + + def __str__(self): + return '\n'.join(tuple(self.names.values())) + + +class LOWriterStylesFamilies(object): + + def __init__(self, styles): + self._styles = styles + + def __getitem__(self, index): + styles = { + 'Character': 'CharacterStyles', + 'Paragraph': 'ParagraphStyles', + 'Page': 'PageStyles', + 'Frame': 'FrameStyles', + 'Numbering': 'NumberingStyles', + 'Table': 'TableStyles', + 'Cell': 'CellStyles', + } + name = styles.get(index, index) + return LOWriterStyles(self._styles[name]) + + def __iter__(self): + self._index = 0 + return self + + def __next__(self): + obj = LOWriterStyles(self._styles[self._index]) + self._index += 1 + return obj + # ~ raise StopIteration + + @property + def names(self): + return self._styles.ElementNames + + def __str__(self): + return '\n'.join(self.names) + + +class LOWriterPageStyle(LOBaseObject): + + def __init__(self, obj): + super().__init__(obj) + + def __str__(self): + return f'Page Style: {self.name}' + + @property + def name(self): + return self._obj.Name + + +class LOWriterPageStyles(object): + + def __init__(self, styles): + self._styles = styles + + def __getitem__(self, index): + return LOWriterPageStyle(self._styles[index]) + + @property + def names(self): + return self._styles.ElementNames + + def __str__(self): + return '\n'.join(self.names) + + +class LOWriterTextRange(object): + + def __init__(self, obj, doc): + self._obj = obj + self._doc = doc + self._is_paragraph = self.obj.ImplementationName == 'SwXParagraph' + self._is_table = self.obj.ImplementationName == 'SwXTextTable' + self._is_text = self.obj.ImplementationName == 'SwXTextPortion' + self._parts = [] + if self._is_paragraph: + self._parts = [LOWriterTextRange(p, doc) for p in obj] + + def __iter__(self): + self._index = 0 + return self + + def __next__(self): + try: + obj = self._parts[self._index] + except IndexError: + raise StopIteration + + self._index += 1 + return obj + + @property + def obj(self): + return self._obj + + @property + def string(self): + s = '' + if not self._is_table: + s = self.obj.String + return s + @string.setter + def string(self, value): + self.obj.String = value + + @property + def value(self): + return self.string + @value.setter + def value(self, value): + self.string = value + + @property + def style(self): + s = '' + if self.is_paragraph: + s = self.obj.ParaStyleName + elif self.is_text: + s = self.obj.CharStyleName + return s + @style.setter + def style(self, value): + if self.is_paragraph: + self.obj.ParaStyleName = value + elif self.is_text: + self.obj.CharStyleName = value + + @property + def is_paragraph(self): + return self._is_paragraph + + @property + def is_table(self): + return self._is_table + + @property + def is_text(self): + return self._is_text + + @property + def text(self): + return self.obj.Text + + @property + def cursor(self): + return self.text.createTextCursorByRange(self.obj) + + @property + def dp(self): + return self._doc.dp + + def delete(self): + cursor = self.cursor + cursor.gotoStartOfParagraph(False) + cursor.gotoNextParagraph(True) + cursor.String = '' + return + + def offset(self): + cursor = self.cursor.getEnd() + return LOWriterTextRange(cursor, self._doc) + + def insert_content(self, data, cursor=None, replace=False): + if cursor is None: + cursor = self.cursor + self.text.insertTextContent(cursor, data, replace) + return + + def new_line(self, count=1): + cursor = self.cursor + for i in range(count): + self.text.insertControlCharacter(cursor, PARAGRAPH_BREAK, False) + return self._doc.selection + + def insert_table(self, data): + table = self._doc.create_instance('com.sun.star.text.TextTable') + rows = len(data) + cols = len(data[0]) + table.initialize(rows, cols) + self.insert_content(table) + table.DataArray = data + name = table.Name + table = LOWriterTextTable(self._doc.tables[name], self._doc) + return table + + def insert_image(self, path, args={}): + w = args.get('Width', 1000) + h = args.get('Height', 1000) + image = self._doc.create_instance('com.sun.star.text.GraphicObject') + image.GraphicURL = _P.to_url(path) + image.AnchorType = AS_CHARACTER + image.Width = w + image.Height = h + self.insert_content(image) + return self._doc.dp.last + + +class LOWriterTextRanges(object): + + def __init__(self, obj, doc): + self._obj = obj + self._doc = doc + self._paragraphs = [LOWriterTextRange(p, doc) for p in obj] + + def __len__(self): + return len(self._paragraphs) + + def __getitem__(self, index): + return self._paragraphs[index] + + def __iter__(self): + self._index = 0 + return self + + def __next__(self): + try: + obj = self._paragraphs[self._index] + except IndexError: + raise StopIteration + + self._index += 1 + return obj + + @property + def obj(self): + return self._obj + + +class LOWriterTextTable(object): + + def __init__(self, obj, doc): + self._obj = obj + self._doc = doc + + @property + def obj(self): + return self._obj + + @property + def name(self): + return self._obj.Name + + @property + def data(self): + return self.obj.DataArray + @data.setter + def data(self, values): + self.obj.DataArray = values + + @property + def style(self): + return self.obj.TableTemplateName + @style.setter + def style(self, value): + self.obj.autoFormat(value) + + +class LOWriterTextTables(object): + + def __init__(self, doc): + self._doc = doc + self._obj = doc.obj.TextTables + + def __getitem__(self, key): + return LOWriterTextTable(self._obj[key], self._doc) + + def __len__(self): + return self._obj.Count + + def insert(self, data, text_range=None): + if text_range is None: + text_range = self._doc.selection + text_range.insert_table(data) + return + + +class LOWriter(LODocument): + + def __init__(self, obj): + super().__init__(obj) + self._type = WRITER + + @property + def text(self): + return self.paragraphs + + @property + def paragraphs(self): + return LOWriterTextRanges(self.obj.Text, self) + + @property + def tables(self): + return LOWriterTextTables(self) + + @property + def selection(self): + sel = self.obj.CurrentSelection + if sel.ImplementationName == OBJ_TEXTS: + if len(sel) == 1: + sel = LOWriterTextRanges(sel, self)[0] + else: + sel = LOWriterTextRanges(sel, self) + return sel + + if sel.ImplementationName == OBJ_SHAPES: + if len(sel) == 1: + sel = sel[0] + sel = LODrawPage(sel.Parent)[sel.Name] + return sel + + if sel.ImplementationName == OBJ_GRAPHIC: + sel = self.dp[sel.Name] + else: + debug(sel.ImplementationName) + + return sel + + @property + def dp(self): + return self.draw_page + @property + def draw_page(self): + return LODrawPage(self.obj.DrawPage) + + @property + def view_cursor(self): + return self._cc.ViewCursor + + @property + def cursor(self): + return self.obj.Text.createTextCursor() + + @property + def page_styles(self): + ps = self.obj.StyleFamilies['PageStyles'] + return LOWriterPageStyles(ps) + + @property + def styles(self): + return LOWriterStylesFamilies(self.obj.StyleFamilies) + + @property + def search_descriptor(self): + return self.obj.createSearchDescriptor() + + @property + def replace_descriptor(self): + return self.obj.createReplaceDescriptor() + + def goto_start(self): + self.view_cursor.gotoStart(False) + return self.selection + + def goto_end(self): + self.view_cursor.gotoEnd(False) + return self.selection + + def search(self, options, find_all=True): + descriptor = self.search_descriptor + descriptor.setSearchString(options.get('Search', '')) + descriptor.SearchCaseSensitive = options.get('CaseSensitive', False) + descriptor.SearchWords = options.get('Words', False) + if 'Attributes' in options: + attr = dict_to_property(options['Attributes']) + descriptor.setSearchAttributes(attr) + if hasattr(descriptor, 'SearchRegularExpression'): + descriptor.SearchRegularExpression = options.get('RegularExpression', False) + if hasattr(descriptor, 'SearchType') and 'Type' in options: + descriptor.SearchType = options['Type'] + + result = False + if find_all: + found = self.obj.findAll(descriptor) + if len(found): + result = [LOWriterTextRange(f, self) for f in found] + else: + found = self.obj.findFirst(descriptor) + if found: + result = LOWriterTextRange(found, self) + + return result + + def replace(self, options): + descriptor = self.replace_descriptor + descriptor.setSearchString(options['Search']) + descriptor.setReplaceString(options['Replace']) + descriptor.SearchCaseSensitive = options.get('CaseSensitive', False) + descriptor.SearchWords = options.get('Words', False) + if 'Attributes' in options: + attr = dict_to_property(options['Attributes']) + descriptor.setSearchAttributes(attr) + if hasattr(descriptor, 'SearchRegularExpression'): + descriptor.SearchRegularExpression = options.get('RegularExpression', False) + if hasattr(descriptor, 'SearchType') and 'Type' in options: + descriptor.SearchType = options['Type'] + found = self.obj.replaceAll(descriptor) + return found + + def select(self, text): + if hasattr(text, 'obj'): + text = text.obj + self._cc.select(text) + return + + +class LOShape(LOBaseObject): + IMAGE = 'com.sun.star.drawing.GraphicObjectShape' + + def __init__(self, obj, index): + self._index = index + super().__init__(obj) + + @property + def type(self): + t = self.shape_type[21:] + if self.is_image: + t = 'image' + return t + + @property + def shape_type(self): + return self.obj.ShapeType + + @property + def is_image(self): + return self.shape_type == self.IMAGE + + @property + def name(self): + return self.obj.Name or f'{self.type}{self.index}' + @name.setter + def name(self, value): + self.obj.Name = value + + @property + def index(self): + return self._index + + @property + def size(self): + s = self.obj.Size + a = dict(Width=s.Width, Height=s.Height) + return a + + @property + def string(self): + return self.obj.String + @string.setter + def string(self, value): + self.obj.String = value + + @property + def description(self): + return self.obj.Description + @description.setter + def description(self, value): + self.obj.Description = value + + @property + def cell(self): + return self.anchor + + @property + def anchor(self): + obj = self.obj.Anchor + if obj.ImplementationName == OBJ_CELL: + obj = LOCalcRange(obj) + elif obj.ImplementationName == OBJ_TEXT: + obj = LOWriterTextRange(obj, LODocs().active) + else: + debug('Anchor', obj.ImplementationName) + return obj + @anchor.setter + def anchor(self, value): + if hasattr(value, 'obj'): + value = value.obj + self.obj.Anchor = value + + @property + def visible(self): + return self.obj.Visible + @visible.setter + def visible(self, value): + self.obj.Visible = value + + @property + def path(self): + return self.url + @property + def url(self): + url = '' + if self.is_image: + url = _P.to_system(self.obj.GraphicURL.OriginURL) + return url + + @property + def mimetype(self): + mt = '' + if self.is_image: + mt = self.obj.GraphicURL.MimeType + return mt + + @property + def linked(self): + l = False + if self.is_image: + l = self.obj.GraphicURL.Linked + return l + + def delete(self): + self.remove() + return + def remove(self): + self.obj.Parent.remove(self.obj) + return + + def save(self, path: str, mimetype=DEFAULT_MIME_TYPE): + if _P.is_dir(path): + name = self.name + ext = mimetype.lower() + else: + p = _P(path) + path = p.path + name = p.name + ext = p.ext.lower() + + path = _P.join(path, f'{name}.{ext}') + args = dict( + URL = _P.to_url(path), + MimeType = MIME_TYPE[ext], + ) + if not _export_image(self.obj, args): + path = '' + return path + + # ~ def save2(self, path: str): + # ~ size = len(self.obj.Bitmap.DIB) + # ~ data = self.obj.GraphicStream.readBytes((), size) + # ~ data = data[-1].value + # ~ path = _P.join(path, f'{self.name}.png') + # ~ _P.save_bin(path, b'') + # ~ return + + +class LODrawPage(LOBaseObject): + + def __init__(self, obj): + super().__init__(obj) + + def __getitem__(self, index): + if isinstance(index, int): + shape = LOShape(self.obj[index], index) + else: + for i, o in enumerate(self.obj): + shape = self.obj[i] + name = shape.Name or f'shape{i}' + if name == index: + shape = LOShape(shape, i) + break + return shape + + def __iter__(self): + self._index = 0 + return self + + def __next__(self): + if self._index == self.count: + raise StopIteration + shape = self[self._index] + self._index += 1 + return shape + + + @property + def name(self): + return self.obj.Name + + @property + def doc(self): + return self.obj.Forms.Parent + + @property + def width(self): + return self.obj.Width + + @property + def height(self): + return self.obj.Height + + @property + def count(self): + return self.obj.Count + + @property + def last(self): + return self[self.count - 1] + + def create_instance(self, name): + return self.doc.createInstance(name) + + def add(self, type_shape, args={}): + """Insert a shape in page, type shapes: + Line + Rectangle + Ellipse + Text + """ + index = self.count + w = args.get('Width', 3000) + h = args.get('Height', 3000) + x = args.get('X', 1000) + y = args.get('Y', 1000) + name = args.get('Name', f'{type_shape.lower()}{index}') + + service = f'com.sun.star.drawing.{type_shape}Shape' + shape = self.create_instance(service) + shape.Size = Size(w, h) + shape.Position = Point(x, y) + shape.Name = name + self.obj.add(shape) + return LOShape(self.obj[index], index) + + def remove(self, shape): + if hasattr(shape, 'obj'): + shape = shape.obj + return self.obj.remove(shape) + + def remove_all(self): + while self.count: + self.obj.remove(self.obj[0]) + return + + def insert_image(self, path, args={}): + index = self.count + w = args.get('Width', 3000) + h = args.get('Height', 3000) + x = args.get('X', 1000) + y = args.get('Y', 1000) + name = args.get('Name', f'image{index}') + + image = self.create_instance('com.sun.star.drawing.GraphicObjectShape') + image.GraphicURL = _P.to_url(path) + image.Size = Size(w, h) + image.Position = Point(x, y) + image.Name = name + self.obj.add(image) + return LOShape(self.obj[index], index) + + +class LODrawImpress(LODocument): + + def __init__(self, obj): + super().__init__(obj) + + def __getitem__(self, index): + if isinstance(index, int): + page = self.obj.DrawPages[index] + else: + page = self.obj.DrawPages.getByName(index) + return LODrawPage(page) + + @property + def selection(self): + sel = self.obj.CurrentSelection[0] + # ~ return _get_class_uno(sel) + return sel + + @property + def current_page(self): + return LODrawPage(self._cc.getCurrentPage()) + + def paste(self): + call_dispatch(self.frame, '.uno:Paste') + return self.current_page[-1] + + def add(self, type_shape, args={}): + return self.current_page.add(type_shape, args) + + def insert_image(self, path, args={}): + self.current_page.insert_image(path, args) + return + + # ~ def export(self, path, mimetype='png'): + # ~ args = dict( + # ~ URL = _P.to_url(path), + # ~ MimeType = MIME_TYPE[mimetype], + # ~ ) + # ~ result = _export_image(self.obj, args) + # ~ return result + + +class LODraw(LODrawImpress): + + def __init__(self, obj): + super().__init__(obj) + self._type = DRAW + + +class LOImpress(LODrawImpress): + + def __init__(self, obj): + super().__init__(obj) + self._type = IMPRESS + + +class BaseDateField(DateField): + + def db_value(self, value): + return _date_to_struct(value) + + def python_value(self, value): + return _struct_to_date(value) + + +class BaseTimeField(TimeField): + + def db_value(self, value): + return _date_to_struct(value) + + def python_value(self, value): + return _struct_to_date(value) + + +class BaseDateTimeField(DateTimeField): + + def db_value(self, value): + return _date_to_struct(value) + + def python_value(self, value): + return _struct_to_date(value) + + +class FirebirdDatabase(Database): + field_types = {'BOOL': 'BOOLEAN', 'DATETIME': 'TIMESTAMP'} + + def __init__(self, database, **kwargs): + super().__init__(database, **kwargs) + self._db = database + + def _connect(self): + return self._db + + def create_tables(self, models, **options): + options['safe'] = False + tables = self._db.tables + models = [m for m in models if not m.__name__.lower() in tables] + super().create_tables(models, **options) + + def execute_sql(self, sql, params=None, commit=True): + with __exception_wrapper__: + cursor = self._db.execute(sql, params) + return cursor + + def last_insert_id(self, cursor, query_type=None): + # ~ debug('LAST_ID', cursor) + return 0 + + def rows_affected(self, cursor): + return self._db.rows_affected + + @property + def path(self): + return self._db.path + + +class BaseRow: + pass + + +class BaseQuery(object): + PY_TYPES = { + 'VARCHAR': 'getString', + 'INTEGER': 'getLong', + 'DATE': 'getDate', + # ~ 'SQL_LONG': 'getLong', + # ~ 'SQL_VARYING': 'getString', + # ~ 'SQL_FLOAT': 'getFloat', + # ~ 'SQL_BOOLEAN': 'getBoolean', + # ~ 'SQL_TYPE_DATE': 'getDate', + # ~ 'SQL_TYPE_TIME': 'getTime', + # ~ 'SQL_TIMESTAMP': 'getTimestamp', + } + # ~ TYPES_DATE = ('SQL_TYPE_DATE', 'SQL_TYPE_TIME', 'SQL_TIMESTAMP') + TYPES_DATE = ('DATE', 'SQL_TYPE_TIME', 'SQL_TIMESTAMP') + + def __init__(self, query): + self._query = query + self._meta = query.MetaData + self._cols = self._meta.ColumnCount + self._names = query.Columns.ElementNames + self._data = self._get_data() + + def __getitem__(self, index): + return self._data[index] + + def __iter__(self): + self._index = 0 + return self + + def __next__(self): + try: + row = self._data[self._index] + except IndexError: + raise StopIteration + self._index += 1 + return row + + def _to_python(self, index): + type_field = self._meta.getColumnTypeName(index) + # ~ print('TF', type_field) + value = getattr(self._query, self.PY_TYPES[type_field])(index) + if type_field in self.TYPES_DATE: + value = _struct_to_date(value) + return value + + def _get_row(self): + row = BaseRow() + for i in range(1, self._cols + 1): + column_name = self._meta.getColumnName(i) + value = self._to_python(i) + setattr(row, column_name, value) + return row + + def _get_data(self): + data = [] + while self._query.next(): + row = self._get_row() + data.append(row) + return data + + @property + def tuples(self): + data = [tuple(r.__dict__.values()) for r in self._data] + return tuple(data) + + @property + def dicts(self): + data = [r.__dict__ for r in self._data] + return tuple(data) + + +class LOBase(object): + DB_TYPES = { + str: 'setString', + int: 'setInt', + float: 'setFloat', + bool: 'setBoolean', + Date: 'setDate', + Time: 'setTime', + DateTime: 'setTimestamp', + } + # ~ setArray + # ~ setBinaryStream + # ~ setBlob + # ~ setByte + # ~ setBytes + # ~ setCharacterStream + # ~ setClob + # ~ setNull + # ~ setObject + # ~ setObjectNull + # ~ setObjectWithInfo + # ~ setPropertyValue + # ~ setRef + + def __init__(self, obj, args={}): + self._obj = obj + self._type = BASE + self._dbc = create_instance('com.sun.star.sdb.DatabaseContext') + self._rows_affected = 0 + path = args.get('path', '') + self._path = _P(path) + self._name = self._path.name + if _P.exists(path): + if not self.is_registered: + self.register() + db = self._dbc.getByName(self.name) + else: + db = self._dbc.createInstance() + db.URL = 'sdbc:embedded:firebird' + db.DatabaseDocument.storeAsURL(self._path.url, ()) + self.register() + self._obj = db + self._con = db.getConnection('', '') + + def __contains__(self, item): + return item in self.tables + + @property + def obj(self): + return self._obj + + @property + def name(self): + return self._name + + @property + def path(self): + return str(self._path) + + @property + def is_registered(self): + return self._dbc.hasRegisteredDatabase(self.name) + + @property + def tables(self): + tables = [t.Name.lower() for t in self._con.getTables()] + return tables + + @property + def rows_affected(self): + return self._rows_affected + + def register(self): + if not self.is_registered: + self._dbc.registerDatabaseLocation(self.name, self._path.url) + return + + def revoke(self, name): + self._dbc.revokeDatabaseLocation(name) + return True + + def save(self): + self.obj.DatabaseDocument.store() + self.refresh() + return + + def close(self): + self._con.close() + return + + def refresh(self): + self._con.getTables().refresh() + return + + def initialize(self, database_proxy, tables=[]): + db = FirebirdDatabase(self) + database_proxy.initialize(db) + if tables: + db.create_tables(tables) + return + + def _validate_sql(self, sql, params): + limit = ' LIMIT ' + for p in params: + sql = sql.replace('?', f"'{p}'", 1) + if limit in sql: + sql = sql.split(limit)[0] + sql = sql.replace('SELECT', f'SELECT FIRST {params[-1]}') + return sql + + def cursor(self, sql, params): + if sql.startswith('SELECT'): + sql = self._validate_sql(sql, params) + cursor = self._con.prepareStatement(sql) + return cursor + + if not params: + cursor = self._con.createStatement() + return cursor + + cursor = self._con.prepareStatement(sql) + for i, v in enumerate(params, 1): + t = type(v) + if not t in self.DB_TYPES: + error('Type not support') + debug((i, t, v, self.DB_TYPES[t])) + getattr(cursor, self.DB_TYPES[t])(i, v) + return cursor + + def execute(self, sql, params): + debug(sql, params) + cursor = self.cursor(sql, params) + + if sql.startswith('SELECT'): + result = cursor.executeQuery() + elif params: + result = cursor.executeUpdate() + self._rows_affected = result + self.save() + else: + result = cursor.execute(sql) + self.save() + + return result + + def select(self, sql): + debug('SELECT', sql) + if not sql.startswith('SELECT'): + return () + + cursor = self._con.prepareStatement(sql) + query = cursor.executeQuery() + return BaseQuery(query) + + def get_query(self, query): + sql, args = query.sql() + sql = self._validate_sql(sql, args) + return self.select(sql) + + +class LOMath(LODocument): + + def __init__(self, obj): + super().__init__(obj) + self._type = MATH + + +class LOBasic(LODocument): + + def __init__(self, obj): + super().__init__(obj) + self._type = BASIC + + +class LODocs(object): + _desktop = None + + def __init__(self): + self._desktop = get_desktop() + LODocs._desktop = self._desktop + + def __getitem__(self, index): + document = None + for i, doc in enumerate(self._desktop.Components): + if isinstance(index, int) and i == index: + document = _get_class_doc(doc) + break + elif isinstance(index, str) and doc.Title == index: + document = _get_class_doc(doc) + break + return document + + def __contains__(self, item): + doc = self[item] + return not doc is None + + def __iter__(self): + self._i = -1 + return self + + def __next__(self): + self._i += 1 + doc = self[self._i] + if doc is None: + raise StopIteration + else: + return doc + + def __len__(self): + # ~ len(self._desktop.Components) + for i, _ in enumerate(self._desktop.Components): + pass + return i + 1 + + @property + def active(self): + return _get_class_doc(self._desktop.getCurrentComponent()) + + @classmethod + def new(cls, type_doc=CALC, args={}): + if type_doc == BASE: + return LOBase(None, args) + + path = f'private:factory/s{type_doc}' + opt = dict_to_property(args) + doc = cls._desktop.loadComponentFromURL(path, '_default', 0, opt) + return _get_class_doc(doc) + + @classmethod + def open(cls, path, args={}): + """ Open document in path + Usually options: + Hidden: True or False + AsTemplate: True or False + ReadOnly: True or False + Password: super_secret + MacroExecutionMode: 4 = Activate macros + Preview: True or False + + http://api.libreoffice.org/docs/idl/ref/interfacecom_1_1sun_1_1star_1_1frame_1_1XComponentLoader.html + http://api.libreoffice.org/docs/idl/ref/servicecom_1_1sun_1_1star_1_1document_1_1MediaDescriptor.html + """ + path = _P.to_url(path) + opt = dict_to_property(args) + doc = cls._desktop.loadComponentFromURL(path, '_default', 0, opt) + if doc is None: + return + + return _get_class_doc(doc) + + def connect(self, path): + return LOBase(None, {'path': path}) + + +def _add_listeners(events, control, name=''): + listeners = { + 'addActionListener': EventsButton, + 'addMouseListener': EventsMouse, + 'addFocusListener': EventsFocus, + 'addItemListener': EventsItem, + 'addKeyListener': EventsKey, + 'addTabListener': EventsTab, + } + if hasattr(control, 'obj'): + control = control.obj + # ~ debug(control.ImplementationName) + is_grid = control.ImplementationName == 'stardiv.Toolkit.GridControl' + is_link = control.ImplementationName == 'stardiv.Toolkit.UnoFixedHyperlinkControl' + is_roadmap = control.ImplementationName == 'stardiv.Toolkit.UnoRoadmapControl' + is_pages = control.ImplementationName == 'stardiv.Toolkit.UnoMultiPageControl' + + for key, value in listeners.items(): + if hasattr(control, key): + if is_grid and key == 'addMouseListener': + control.addMouseListener(EventsMouseGrid(events, name)) + continue + if is_link and key == 'addMouseListener': + control.addMouseListener(EventsMouseLink(events, name)) + continue + if is_roadmap and key == 'addItemListener': + control.addItemListener(EventsItemRoadmap(events, name)) + continue + + getattr(control, key)(listeners[key](events, name)) + + if is_grid: + controllers = EventsGrid(events, name) + control.addSelectionListener(controllers) + control.Model.GridDataModel.addGridDataListener(controllers) + return + + +def _set_properties(model, properties): + if 'X' in properties: + properties['PositionX'] = properties.pop('X') + if 'Y' in properties: + properties['PositionY'] = properties.pop('Y') + keys = tuple(properties.keys()) + values = tuple(properties.values()) + model.setPropertyValues(keys, values) + return + + +class EventsListenerBase(unohelper.Base, XEventListener): + + def __init__(self, controller, name, window=None): + self._controller = controller + self._name = name + self._window = window + + @property + def name(self): + return self._name + + def disposing(self, event): + self._controller = None + if not self._window is None: + self._window.setMenuBar(None) + + +class EventsMouse(EventsListenerBase, XMouseListener, XMouseMotionListener): + + def __init__(self, controller, name): + super().__init__(controller, name) + + def mousePressed(self, event): + event_name = '{}_click'.format(self._name) + if event.ClickCount == 2: + event_name = '{}_double_click'.format(self._name) + if hasattr(self._controller, event_name): + getattr(self._controller, event_name)(event) + return + + def mouseReleased(self, event): + pass + + def mouseEntered(self, event): + pass + + def mouseExited(self, event): + pass + + # ~ XMouseMotionListener + def mouseMoved(self, event): + pass + + def mouseDragged(self, event): + pass + + +class EventsMouseLink(EventsMouse): + + def __init__(self, controller, name): + super().__init__(controller, name) + self._text_color = 0 + + def mouseEntered(self, event): + model = event.Source.Model + self._text_color = model.TextColor or 0 + model.TextColor = get_color('blue') + return + + def mouseExited(self, event): + model = event.Source.Model + model.TextColor = self._text_color + return + + +class EventsButton(EventsListenerBase, XActionListener): + + def __init__(self, controller, name): + super().__init__(controller, name) + + def actionPerformed(self, event): + event_name = f'{self.name}_action' + if hasattr(self._controller, event_name): + getattr(self._controller, event_name)(event) + return + + +class EventsFocus(EventsListenerBase, XFocusListener): + CONTROLS = ( + 'stardiv.Toolkit.UnoControlEditModel', + ) + + def __init__(self, controller, name): + super().__init__(controller, name) + + def focusGained(self, event): + service = event.Source.Model.ImplementationName + # ~ print('Focus enter', service) + if service in self.CONTROLS: + obj = event.Source.Model + obj.BackgroundColor = COLOR_ON_FOCUS + return + + def focusLost(self, event): + service = event.Source.Model.ImplementationName + if service in self.CONTROLS: + obj = event.Source.Model + obj.BackgroundColor = -1 + return + + +class EventsKey(EventsListenerBase, XKeyListener): + """ + event.KeyChar + event.KeyCode + event.KeyFunc + event.Modifiers + """ + + def __init__(self, controller, name): + super().__init__(controller, name) + + def keyPressed(self, event): + pass + + def keyReleased(self, event): + event_name = '{}_key_released'.format(self._name) + if hasattr(self._controller, event_name): + getattr(self._controller, event_name)(event) + # ~ else: + # ~ if event.KeyFunc == QUIT and hasattr(self._cls, 'close'): + # ~ self._cls.close() + return + + +class EventsItem(EventsListenerBase, XItemListener): + + def __init__(self, controller, name): + super().__init__(controller, name) + + def disposing(self, event): + pass + + def itemStateChanged(self, event): + event_name = '{}_item_changed'.format(self.name) + if hasattr(self._controller, event_name): + getattr(self._controller, event_name)(event) + return + + +class EventsItemRoadmap(EventsItem): + + def itemStateChanged(self, event): + dialog = event.Source.Context.Model + dialog.Step = event.ItemId + 1 + return + + +class EventsGrid(EventsListenerBase, XGridDataListener, XGridSelectionListener): + + def __init__(self, controller, name): + super().__init__(controller, name) + + def dataChanged(self, event): + event_name = '{}_data_changed'.format(self.name) + if hasattr(self._controller, event_name): + getattr(self._controller, event_name)(event) + return + + def rowHeadingChanged(self, event): + pass + + def rowsInserted(self, event): + pass + + def rowsRemoved(self, evemt): + pass + + def selectionChanged(self, event): + event_name = '{}_selection_changed'.format(self.name) + if hasattr(self._controller, event_name): + getattr(self._controller, event_name)(event) + return + + +class EventsMouseGrid(EventsMouse): + selected = False + + def mousePressed(self, event): + super().mousePressed(event) + # ~ obj = event.Source + # ~ col = obj.getColumnAtPoint(event.X, event.Y) + # ~ row = obj.getRowAtPoint(event.X, event.Y) + # ~ print(col, row) + # ~ if col == -1 and row == -1: + # ~ if self.selected: + # ~ obj.deselectAllRows() + # ~ else: + # ~ obj.selectAllRows() + # ~ self.selected = not self.selected + return + + def mouseReleased(self, event): + # ~ obj = event.Source + # ~ col = obj.getColumnAtPoint(event.X, event.Y) + # ~ row = obj.getRowAtPoint(event.X, event.Y) + # ~ if row == -1 and col > -1: + # ~ gdm = obj.Model.GridDataModel + # ~ for i in range(gdm.RowCount): + # ~ gdm.updateRowHeading(i, i + 1) + return + + +class EventsTab(EventsListenerBase, XTabListener): + + def __init__(self, controller, name): + super().__init__(controller, name) + + def activated(self, id): + event_name = '{}_activated'.format(self.name) + if hasattr(self._controller, event_name): + getattr(self._controller, event_name)(id) + return + + +class EventsMenu(EventsListenerBase, XMenuListener): + + def __init__(self, controller): + super().__init__(controller, '') + + def itemHighlighted(self, event): + pass + + def itemSelected(self, event): + name = event.Source.getCommand(event.MenuId) + if name.startswith('menu'): + event_name = '{}_selected'.format(name) + else: + event_name = 'menu_{}_selected'.format(name) + if hasattr(self._controller, event_name): + getattr(self._controller, event_name)(event) + return + + def itemActivated(self, event): + return + + def itemDeactivated(self, event): + return + + +class EventsWindow(EventsListenerBase, XTopWindowListener, XWindowListener): + + def __init__(self, cls): + self._cls = cls + super().__init__(cls.events, cls.name, cls._window) + + def windowOpened(self, event): + event_name = '{}_opened'.format(self._name) + if hasattr(self._controller, event_name): + getattr(self._controller, event_name)(event) + return + + def windowActivated(self, event): + control_name = '{}_activated'.format(event.Source.Model.Name) + if hasattr(self._controller, control_name): + getattr(self._controller, control_name)(event) + return + + def windowDeactivated(self, event): + control_name = '{}_deactivated'.format(event.Source.Model.Name) + if hasattr(self._controller, control_name): + getattr(self._controller, control_name)(event) + return + + def windowMinimized(self, event): + pass + + def windowNormalized(self, event): + pass + + def windowClosing(self, event): + if self._window: + control_name = 'window_closing' + else: + control_name = '{}_closing'.format(event.Source.Model.Name) + + if hasattr(self._controller, control_name): + getattr(self._controller, control_name)(event) + # ~ else: + # ~ if not self._modal and not self._block: + # ~ event.Source.Visible = False + return + + def windowClosed(self, event): + control_name = '{}_closed'.format(event.Source.Model.Name) + if hasattr(self._controller, control_name): + getattr(self._controller, control_name)(event) + return + + # ~ XWindowListener + def windowResized(self, event): + sb = self._cls._subcont + sb.setPosSize(0, 0, event.Width, event.Height, SIZE) + event_name = '{}_resized'.format(self._name) + if hasattr(self._controller, event_name): + getattr(self._controller, event_name)(event) + return + + def windowMoved(self, event): + pass + + def windowShown(self, event): + pass + + def windowHidden(self, event): + pass + + +# ~ BorderColor = ? +# ~ FontStyleName = ? +# ~ HelpURL = ? +class UnoBaseObject(object): + + def __init__(self, obj, path=''): + self._obj = obj + self._model = obj.Model + + def __setattr__(self, name, value): + exists = hasattr(self, name) + if not exists and not name in ('_obj', '_model'): + setattr(self._model, name, value) + else: + super().__setattr__(name, value) + + def __enter__(self): + return self + + def __exit__(self, exc_type, exc_value, traceback): + pass + + @property + def obj(self): + return self._obj + + @property + def model(self): + return self._model + @property + def m(self): + return self._model + + @property + def properties(self): + return {} + @properties.setter + def properties(self, values): + _set_properties(self.model, values) + + @property + def name(self): + return self.model.Name + + @property + def parent(self): + return self.obj.Context + + @property + def tag(self): + return self.model.Tag + @tag.setter + def tag(self, value): + self.model.Tag = value + + @property + def visible(self): + return self.obj.Visible + @visible.setter + def visible(self, value): + self.obj.setVisible(value) + + @property + def enabled(self): + return self.model.Enabled + @enabled.setter + def enabled(self, value): + self.model.Enabled = value + + @property + def step(self): + return self.model.Step + @step.setter + def step(self, value): + self.model.Step = value + + @property + def align(self): + return self.model.Align + @align.setter + def align(self, value): + self.model.Align = value + + @property + def valign(self): + return self.model.VerticalAlign + @valign.setter + def valign(self, value): + self.model.VerticalAlign = value + + @property + def font_weight(self): + return self.model.FontWeight + @font_weight.setter + def font_weight(self, value): + self.model.FontWeight = value + + @property + def font_height(self): + return self.model.FontHeight + @font_height.setter + def font_height(self, value): + self.model.FontHeight = value + + @property + def font_name(self): + return self.model.FontName + @font_name.setter + def font_name(self, value): + self.model.FontName = value + + @property + def font_underline(self): + return self.model.FontUnderline + @font_underline.setter + def font_underline(self, value): + self.model.FontUnderline = value + + @property + def text_color(self): + return self.model.TextColor + @text_color.setter + def text_color(self, value): + self.model.TextColor = value + + @property + def back_color(self): + return self.model.BackgroundColor + @back_color.setter + def back_color(self, value): + self.model.BackgroundColor = value + + @property + def multi_line(self): + return self.model.MultiLine + @multi_line.setter + def multi_line(self, value): + self.model.MultiLine = value + + @property + def help_text(self): + return self.model.HelpText + @help_text.setter + def help_text(self, value): + self.model.HelpText = value + + @property + def border(self): + return self.model.Border + @border.setter + def border(self, value): + # ~ Bug for report + self.model.Border = value + + @property + def width(self): + return self._model.Width + @width.setter + def width(self, value): + self.model.Width = value + + @property + def height(self): + return self.model.Height + @height.setter + def height(self, value): + self.model.Height = value + + def _get_possize(self, name): + ps = self.obj.getPosSize() + return getattr(ps, name) + + def _set_possize(self, name, value): + ps = self.obj.getPosSize() + setattr(ps, name, value) + self.obj.setPosSize(ps.X, ps.Y, ps.Width, ps.Height, POSSIZE) + return + + @property + def x(self): + if hasattr(self.model, 'PositionX'): + return self.model.PositionX + return self._get_possize('X') + @x.setter + def x(self, value): + if hasattr(self.model, 'PositionX'): + self.model.PositionX = value + else: + self._set_possize('X', value) + + @property + def y(self): + if hasattr(self.model, 'PositionY'): + return self.model.PositionY + return self._get_possize('Y') + @y.setter + def y(self, value): + if hasattr(self.model, 'PositionY'): + self.model.PositionY = value + else: + self._set_possize('Y', value) + + @property + def tab_index(self): + return self._model.TabIndex + @tab_index.setter + def tab_index(self, value): + self.model.TabIndex = value + + @property + def tab_stop(self): + return self._model.Tabstop + @tab_stop.setter + def tab_stop(self, value): + self.model.Tabstop = value + + @property + def ps(self): + ps = self.obj.getPosSize() + return ps + @ps.setter + def ps(self, ps): + self.obj.setPosSize(ps.X, ps.Y, ps.Width, ps.Height, POSSIZE) + + def set_focus(self): + self.obj.setFocus() + return + + def ps_from(self, source): + self.ps = source.ps + return + + def center(self, horizontal=True, vertical=False): + p = self.parent.Model + w = p.Width + h = p.Height + if horizontal: + x = w / 2 - self.width / 2 + self.x = x + if vertical: + y = h / 2 - self.height / 2 + self.y = y + return + + def move(self, origin, x=0, y=5, center=False): + if x: + self.x = origin.x + origin.width + x + else: + self.x = origin.x + if y: + self.y = origin.y + origin.height + y + else: + self.y = origin.y + + if center: + self.center() + return + + +class UnoLabel(UnoBaseObject): + + def __init__(self, obj): + super().__init__(obj) + + @property + def type(self): + return 'label' + + @property + def value(self): + return self.model.Label + @value.setter + def value(self, value): + self.model.Label = value + + +class UnoLabelLink(UnoLabel): + + def __init__(self, obj): + super().__init__(obj) + + @property + def type(self): + return 'link' + + +class UnoButton(UnoBaseObject): + + def __init__(self, obj): + super().__init__(obj) + + @property + def type(self): + return 'button' + + @property + def value(self): + return self.model.Label + @value.setter + def value(self, value): + self.model.Label = value + + +class UnoRadio(UnoBaseObject): + + def __init__(self, obj): + super().__init__(obj) + + @property + def type(self): + return 'radio' + + @property + def value(self): + return self.model.Label + @value.setter + def value(self, value): + self.model.Label = value + + +class UnoCheckBox(UnoBaseObject): + + def __init__(self, obj): + super().__init__(obj) + + @property + def type(self): + return 'checkbox' + + @property + def value(self): + return self.model.State + @value.setter + def value(self, value): + self.model.State = value + + @property + def label(self): + return self.model.Label + @label.setter + def label(self, value): + self.model.Label = value + + @property + def tri_state(self): + return self.model.TriState + @tri_state.setter + def tri_state(self, value): + self.model.TriState = value + + +# ~ https://api.libreoffice.org/docs/idl/ref/servicecom_1_1sun_1_1star_1_1awt_1_1UnoControlEditModel.html +class UnoText(UnoBaseObject): + + def __init__(self, obj): + super().__init__(obj) + + @property + def type(self): + return 'text' + + @property + def value(self): + return self.model.Text + @value.setter + def value(self, value): + self.model.Text = value + + @property + def echochar(self): + return chr(self.model.EchoChar) + @echochar.setter + def echochar(self, value): + self.model.EchoChar = ord(value[0]) + + def validate(self): + return + + +class UnoImage(UnoBaseObject): + + def __init__(self, obj): + super().__init__(obj) + + @property + def type(self): + return 'image' + + @property + def value(self): + return self.url + @value.setter + def value(self, value): + self.url = value + + @property + def url(self): + return self.m.ImageURL + @url.setter + def url(self, value): + self.m.ImageURL = None + self.m.ImageURL = _P.to_url(value) + + +class UnoListBox(UnoBaseObject): + + def __init__(self, obj): + super().__init__(obj) + self._path = '' + + def __setattr__(self, name, value): + if name in ('_path',): + self.__dict__[name] = value + else: + super().__setattr__(name, value) + + @property + def type(self): + return 'listbox' + + @property + def value(self): + return self.obj.getSelectedItem() + + @property + def count(self): + return len(self.data) + + @property + def data(self): + return self.model.StringItemList + @data.setter + def data(self, values): + self.model.StringItemList = list(sorted(values)) + + @property + def path(self): + return self._path + @path.setter + def path(self, value): + self._path = value + + def unselect(self): + self.obj.selectItem(self.value, False) + return + + def select(self, pos=0): + if isinstance(pos, str): + self.obj.selectItem(pos, True) + else: + self.obj.selectItemPos(pos, True) + return + + def clear(self): + self.model.removeAllItems() + return + + def _set_image_url(self, image): + if _P.exists(image): + return _P.to_url(image) + + path = _P.join(self._path, DIR['images'], image) + return _P.to_url(path) + + def insert(self, value, path='', pos=-1, show=True): + if pos < 0: + pos = self.count + if path: + self.model.insertItem(pos, value, self._set_image_url(path)) + else: + self.model.insertItemText(pos, value) + if show: + self.select(pos) + return + + +class UnoRoadmap(UnoBaseObject): + + def __init__(self, obj): + super().__init__(obj) + self._options = () + + def __setattr__(self, name, value): + if name in ('_options',): + self.__dict__[name] = value + else: + super().__setattr__(name, value) + + @property + def options(self): + return self._options + @options.setter + def options(self, values): + self._options = values + for i, v in enumerate(values): + opt = self.model.createInstance() + opt.ID = i + opt.Label = v + self.model.insertByIndex(i, opt) + return + + @property + def enabled(self): + return True + @enabled.setter + def enabled(self, value): + for m in self.model: + m.Enabled = value + return + + def set_enabled(self, index, value): + self.model.getByIndex(index).Enabled = value + return + + +class UnoTree(UnoBaseObject): + + def __init__(self, obj, ): + super().__init__(obj) + self._tdm = None + self._data = [] + + def __setattr__(self, name, value): + if name in ('_tdm', '_data'): + self.__dict__[name] = value + else: + super().__setattr__(name, value) + + @property + def selection(self): + sel = self.obj.Selection + return sel.DataValue, sel.DisplayValue + + @property + def parent(self): + parent = self.obj.Selection.Parent + if parent is None: + return () + return parent.DataValue, parent.DisplayValue + + def _get_parents(self, node): + value = (node.DisplayValue,) + parent = node.Parent + if parent is None: + return value + return self._get_parents(parent) + value + + @property + def parents(self): + values = self._get_parents(self.obj.Selection) + return values + + @property + def root(self): + if self._tdm is None: + return '' + return self._tdm.Root.DisplayValue + @root.setter + def root(self, value): + self._add_data_model(value) + + def _add_data_model(self, name): + tdm = create_instance('com.sun.star.awt.tree.MutableTreeDataModel') + root = tdm.createNode(name, True) + root.DataValue = 0 + tdm.setRoot(root) + self.model.DataModel = tdm + self._tdm = self.model.DataModel + return + + @property + def path(self): + return self.root + @path.setter + def path(self, value): + self.data = _P.walk_dir(value, True) + + @property + def data(self): + return self._data + @data.setter + def data(self, values): + self._data = list(values) + self._add_data() + + def _add_data(self): + if not self.data: + return + + parents = {} + for node in self.data: + parent = parents.get(node[1], self._tdm.Root) + child = self._tdm.createNode(node[2], False) + child.DataValue = node[0] + parent.appendChild(child) + parents[node[0]] = child + self.obj.expandNode(self._tdm.Root) + return + + +# ~ https://api.libreoffice.org/docs/idl/ref/namespacecom_1_1sun_1_1star_1_1awt_1_1grid.html +class UnoGrid(UnoBaseObject): + + def __init__(self, obj): + super().__init__(obj) + self._gdm = self.model.GridDataModel + self._data = [] + self._formats = () + + def __setattr__(self, name, value): + if name in ('_gdm', '_data', '_formats'): + self.__dict__[name] = value + else: + super().__setattr__(name, value) + + def __getitem__(self, key): + value = self._gdm.getCellData(key[0], key[1]) + return value + + def __setitem__(self, key, value): + self._gdm.updateCellData(key[0], key[1], value) + return + + @property + def type(self): + return 'grid' + + @property + def columns(self): + return {} + @columns.setter + def columns(self, values): + # ~ self._columns = values + #~ https://api.libreoffice.org/docs/idl/ref/interfacecom_1_1sun_1_1star_1_1awt_1_1grid_1_1XGridColumn.html + model = create_instance('com.sun.star.awt.grid.DefaultGridColumnModel', True) + for properties in values: + column = create_instance('com.sun.star.awt.grid.GridColumn', True) + for k, v in properties.items(): + setattr(column, k, v) + model.addColumn(column) + self.model.ColumnModel = model + return + + @property + def data(self): + return self._data + @data.setter + def data(self, values): + self._data = values + self.clear() + headings = tuple(range(1, len(values) + 1)) + self._gdm.addRows(headings, values) + # ~ rows = range(grid_dm.RowCount) + # ~ colors = [COLORS['GRAY'] if r % 2 else COLORS['WHITE'] for r in rows] + # ~ grid.Model.RowBackgroundColors = tuple(colors) + return + + @property + def value(self): + if self.column == -1 or self.row == -1: + return '' + return self[self.column, self.row] + @value.setter + def value(self, value): + if self.column > -1 and self.row > -1: + self[self.column, self.row] = value + + @property + def row(self): + return self.obj.CurrentRow + + @property + def row_count(self): + return self._gdm.RowCount + + @property + def column(self): + return self.obj.CurrentColumn + + @property + def column(self): + return self.obj.CurrentColumn + + @property + def is_valid(self): + return not (self.row == -1 or self.column == -1) + + @property + def formats(self): + return self._formats + @formats.setter + def formats(self, values): + self._formats = values + + def clear(self): + self._gdm.removeAllRows() + return + + def _format_columns(self, data): + row = data + if self.formats: + for i, f in enumerate(formats): + if f: + row[i] = f.format(data[i]) + return row + + def add_row(self, data): + self._data.append(data) + row = self._format_columns(data) + self._gdm.addRow(self.row_count + 1, row) + return + + def set_cell_tooltip(self, col, row, value): + self._gdm.updateCellToolTip(col, row, value) + return + + def get_cell_tooltip(self, col, row): + value = self._gdm.getCellToolTip(col, row) + return value + + def sort(self, column, asc=True): + self._gdm.sortByColumn(column, asc) + self.update_row_heading() + return + + def update_row_heading(self): + for i in range(self.row_count): + self._gdm.updateRowHeading(i, i + 1) + return + + def remove_row(self, row): + self._gdm.removeRow(row) + del self._data[row] + self.update_row_heading() + return + + +class UnoPage(object): + + def __init__(self, obj): + self._obj = obj + self._events = None + + def __enter__(self): + return self + + def __exit__(self, exc_type, exc_value, traceback): + pass + + @property + def obj(self): + return self._obj + + @property + def model(self): + return self._obj.Model + + # ~ @property + # ~ def id(self): + # ~ return self.m.TabPageID + + @property + def parent(self): + return self.obj.Context + + def _set_image_url(self, image): + if _P.exists(image): + return _P.to_url(image) + + path = _P.join(self._path, DIR['images'], image) + return _P.to_url(path) + + def _special_properties(self, tipo, args): + if tipo == 'link' and not 'Label' in args: + args['Label'] = args['URL'] + return args + + if tipo == 'button': + if 'ImageURL' in args: + args['ImageURL'] = self._set_image_url(args['ImageURL']) + args['FocusOnClick'] = args.get('FocusOnClick', False) + return args + + if tipo == 'roadmap': + args['Height'] = args.get('Height', self.height) + if 'Title' in args: + args['Text'] = args.pop('Title') + return args + + if tipo == 'tree': + args['SelectionType'] = args.get('SelectionType', SINGLE) + return args + + if tipo == 'grid': + args['ShowRowHeader'] = args.get('ShowRowHeader', True) + return args + + if tipo == 'pages': + args['Width'] = args.get('Width', self.width) + args['Height'] = args.get('Height', self.height) + + return args + + def add_control(self, args): + tipo = args.pop('Type').lower() + root = args.pop('Root', '') + sheets = args.pop('Sheets', ()) + columns = args.pop('Columns', ()) + + args = self._special_properties(tipo, args) + model = self.model.createInstance(UNO_MODELS[tipo]) + _set_properties(model, args) + name = args['Name'] + self.model.insertByName(name, model) + control = self.obj.getControl(name) + _add_listeners(self._events, control, name) + control = UNO_CLASSES[tipo](control) + + if tipo in ('listbox',): + control.path = self.path + + if tipo == 'tree' and root: + control.root = root + elif tipo == 'grid' and columns: + control.columns = columns + elif tipo == 'pages' and sheets: + control.sheets = sheets + control.events = self.events + + setattr(self, name, control) + return control + + +class UnoPages(UnoBaseObject): + + def __init__(self, obj): + super().__init__(obj) + self._sheets = [] + self._events = None + + def __setattr__(self, name, value): + if name in ('_sheets', '_events'): + self.__dict__[name] = value + else: + super().__setattr__(name, value) + + def __getitem__(self, index): + name = index + if isinstance(index, int): + name = f'sheet{index}' + sheet = self.obj.getControl(name) + page = UnoPage(sheet) + page._events = self._events + return page + + @property + def type(self): + return 'pages' + + @property + def current(self): + return self.obj.ActiveTabID + @property + def active(self): + return self.current + + @property + def sheets(self): + return self._sheets + @sheets.setter + def sheets(self, values): + self._sheets = values + for i, title in enumerate(values): + sheet = self.m.createInstance('com.sun.star.awt.UnoPageModel') + sheet.Title = title + self.m.insertByName(f'sheet{i + 1}', sheet) + return + + @property + def events(self): + return self._events + @events.setter + def events(self, controllers): + self._events = controllers + + @property + def visible(self): + return self.obj.Visible + @visible.setter + def visible(self, value): + self.obj.Visible = value + + def insert(self, title): + self._sheets.append(title) + id = len(self._sheets) + sheet = self.m.createInstance('com.sun.star.awt.UnoPageModel') + sheet.Title = title + self.m.insertByName(f'sheet{id}', sheet) + return self[id] + + def remove(self, id): + self.obj.removeTab(id) + return + + def activate(self, id): + self.obj.activateTab(id) + return + + +UNO_CLASSES = { + 'label': UnoLabel, + 'link': UnoLabelLink, + 'button': UnoButton, + 'radio': UnoRadio, + 'checkbox': UnoCheckBox, + 'text': UnoText, + 'image': UnoImage, + 'listbox': UnoListBox, + 'roadmap': UnoRoadmap, + 'tree': UnoTree, + 'grid': UnoGrid, + 'pages': UnoPages, +} + +UNO_MODELS = { + 'label': 'com.sun.star.awt.UnoControlFixedTextModel', + 'link': 'com.sun.star.awt.UnoControlFixedHyperlinkModel', + 'button': 'com.sun.star.awt.UnoControlButtonModel', + 'radio': 'com.sun.star.awt.UnoControlRadioButtonModel', + 'checkbox': 'com.sun.star.awt.UnoControlCheckBoxModel', + 'text': 'com.sun.star.awt.UnoControlEditModel', + 'image': 'com.sun.star.awt.UnoControlImageControlModel', + 'listbox': 'com.sun.star.awt.UnoControlListBoxModel', + 'roadmap': 'com.sun.star.awt.UnoControlRoadmapModel', + 'tree': 'com.sun.star.awt.tree.TreeControlModel', + 'grid': 'com.sun.star.awt.grid.UnoControlGridModel', + 'pages': 'com.sun.star.awt.UnoMultiPageModel', + 'groupbox': 'com.sun.star.awt.UnoControlGroupBoxModel', + 'combobox': 'com.sun.star.awt.UnoControlComboBoxModel', +} +# ~ 'CurrencyField': 'com.sun.star.awt.UnoControlCurrencyFieldModel', +# ~ 'DateField': 'com.sun.star.awt.UnoControlDateFieldModel', +# ~ 'FileControl': 'com.sun.star.awt.UnoControlFileControlModel', +# ~ 'FormattedField': 'com.sun.star.awt.UnoControlFormattedFieldModel', +# ~ 'NumericField': 'com.sun.star.awt.UnoControlNumericFieldModel', +# ~ 'PatternField': 'com.sun.star.awt.UnoControlPatternFieldModel', +# ~ 'ProgressBar': 'com.sun.star.awt.UnoControlProgressBarModel', +# ~ 'ScrollBar': 'com.sun.star.awt.UnoControlScrollBarModel', +# ~ 'SimpleAnimation': 'com.sun.star.awt.UnoControlSimpleAnimationModel', +# ~ 'SpinButton': 'com.sun.star.awt.UnoControlSpinButtonModel', +# ~ 'Throbber': 'com.sun.star.awt.UnoControlThrobberModel', +# ~ 'TimeField': 'com.sun.star.awt.UnoControlTimeFieldModel', + + +class LODialog(object): + SEPARATION = 5 + MODELS = { + 'label': 'com.sun.star.awt.UnoControlFixedTextModel', + 'link': 'com.sun.star.awt.UnoControlFixedHyperlinkModel', + 'button': 'com.sun.star.awt.UnoControlButtonModel', + 'radio': 'com.sun.star.awt.UnoControlRadioButtonModel', + 'checkbox': 'com.sun.star.awt.UnoControlCheckBoxModel', + 'text': 'com.sun.star.awt.UnoControlEditModel', + 'image': 'com.sun.star.awt.UnoControlImageControlModel', + 'listbox': 'com.sun.star.awt.UnoControlListBoxModel', + 'roadmap': 'com.sun.star.awt.UnoControlRoadmapModel', + 'tree': 'com.sun.star.awt.tree.TreeControlModel', + 'grid': 'com.sun.star.awt.grid.UnoControlGridModel', + 'pages': 'com.sun.star.awt.UnoMultiPageModel', + 'groupbox': 'com.sun.star.awt.UnoControlGroupBoxModel', + 'combobox': 'com.sun.star.awt.UnoControlComboBoxModel', + } + + def __init__(self, args): + self._obj = self._create(args) + self._model = self.obj.Model + self._events = None + self._modal = True + self._controls = {} + self._color_on_focus = COLOR_ON_FOCUS + self._id = '' + self._path = '' + self._init_controls() + + def _create(self, args): + service = 'com.sun.star.awt.DialogProvider' + path = args.pop('Path', '') + if path: + dp = create_instance(service, True) + dlg = dp.createDialog(_P.to_url(path)) + return dlg + + if 'Location' in args: + name = args['Name'] + library = args.get('Library', 'Standard') + location = args.get('Location', 'application').lower() + if location == 'user': + location = 'application' + url = f'vnd.sun.star.script:{library}.{name}?location={location}' + if location == 'document': + dp = create_instance(service, args=docs.active.obj) + else: + dp = create_instance(service, True) + # ~ uid = docs.active.uid + # ~ url = f'vnd.sun.star.tdoc:/{uid}/Dialogs/{library}/{name}.xml' + dlg = dp.createDialog(url) + return dlg + + dlg = create_instance('com.sun.star.awt.UnoControlDialog', True) + model = create_instance('com.sun.star.awt.UnoControlDialogModel', True) + toolkit = create_instance('com.sun.star.awt.Toolkit', True) + _set_properties(model, args) + dlg.setModel(model) + dlg.setVisible(False) + dlg.createPeer(toolkit, None) + return dlg + + def _get_type_control(self, name): + name = name.split('.')[2] + types = { + 'UnoFixedTextControl': 'label', + 'UnoEditControl': 'text', + 'UnoButtonControl': 'button', + } + return types[name] + + def _init_controls(self): + for control in self.obj.getControls(): + tipo = self._get_type_control(control.ImplementationName) + name = control.Model.Name + control = UNO_CLASSES[tipo](control) + setattr(self, name, control) + return + + @property + def obj(self): + return self._obj + + @property + def model(self): + return self._model + + @property + def controls(self): + return self._controls + + @property + def path(self): + return self._path + @property + def id(self): + return self._id + @id.setter + def id(self, value): + self._id = value + self._path = _P.from_id(value) + + @property + def height(self): + return self.model.Height + @height.setter + def height(self, value): + self.model.Height = value + + @property + def width(self): + return self.model.Width + @width.setter + def width(self, value): + self.model.Width = value + + @property + def visible(self): + return self.obj.Visible + @visible.setter + def visible(self, value): + self.obj.Visible = value + + @property + def step(self): + return self.model.Step + @step.setter + def step(self, value): + self.model.Step = value + + @property + def events(self): + return self._events + @events.setter + def events(self, controllers): + self._events = controllers(self) + self._connect_listeners() + + @property + def color_on_focus(self): + return self._color_on_focus + @color_on_focus.setter + def color_on_focus(self, value): + self._color_on_focus = get_color(value) + + def _connect_listeners(self): + for control in self.obj.Controls: + _add_listeners(self.events, control, control.Model.Name) + return + + def _set_image_url(self, image): + if _P.exists(image): + return _P.to_url(image) + + path = _P.join(self._path, DIR['images'], image) + return _P.to_url(path) + + def _special_properties(self, tipo, args): + if tipo == 'link' and not 'Label' in args: + args['Label'] = args['URL'] + return args + + if tipo == 'button': + if 'ImageURL' in args: + args['ImageURL'] = self._set_image_url(args['ImageURL']) + args['FocusOnClick'] = args.get('FocusOnClick', False) + return args + + if tipo == 'roadmap': + args['Height'] = args.get('Height', self.height) + if 'Title' in args: + args['Text'] = args.pop('Title') + return args + + if tipo == 'tree': + args['SelectionType'] = args.get('SelectionType', SINGLE) + return args + + if tipo == 'grid': + args['ShowRowHeader'] = args.get('ShowRowHeader', True) + return args + + if tipo == 'pages': + args['Width'] = args.get('Width', self.width) + args['Height'] = args.get('Height', self.height) + + return args + + def add_control(self, args): + tipo = args.pop('Type').lower() + root = args.pop('Root', '') + sheets = args.pop('Sheets', ()) + columns = args.pop('Columns', ()) + + args = self._special_properties(tipo, args) + model = self.model.createInstance(self.MODELS[tipo]) + _set_properties(model, args) + name = args['Name'] + self.model.insertByName(name, model) + control = self.obj.getControl(name) + _add_listeners(self.events, control, name) + control = UNO_CLASSES[tipo](control) + + if tipo in ('listbox',): + control.path = self.path + + if tipo == 'tree' and root: + control.root = root + elif tipo == 'grid' and columns: + control.columns = columns + elif tipo == 'pages' and sheets: + control.sheets = sheets + control.events = self.events + + setattr(self, name, control) + self._controls[name] = control + return control + + def center(self, control, x=0, y=0): + w = self.width + h = self.height + + if isinstance(control, tuple): + wt = self.SEPARATION * -1 + for c in control: + wt += c.width + self.SEPARATION + x = w / 2 - wt / 2 + for c in control: + c.x = x + x = c.x + c.width + self.SEPARATION + return + + if x < 0: + x = w + x - control.width + elif x == 0: + x = w / 2 - control.width / 2 + if y < 0: + y = h + y - control.height + elif y == 0: + y = h / 2 - control.height / 2 + control.x = x + control.y = y + return + + def open(self, modal=True): + self._modal = modal + if modal: + return self.obj.execute() + else: + self.visible = True + return + + def close(self, value=0): + if self._modal: + value = self.obj.endDialog(value) + else: + self.visible = False + self.obj.dispose() + return value + + def set_values(self, data): + for k, v in data.items(): + self._controls[k].value = v + return + + +class LOSheets(object): + + def __getitem__(self, index): + return LODocs().active[index] + + def __enter__(self): + return self + + def __exit__(self, exc_type, exc_value, traceback): + pass + + +class LOCells(object): + + def __getitem__(self, index): + return LODocs().active.active[index] + + +class LOShortCut(object): +# ~ getKeyEventsByCommand + + def __init__(self, app): + self._app = app + self._scm = None + self._init_values() + + def _init_values(self): + name = 'com.sun.star.ui.GlobalAcceleratorConfiguration' + instance = 'com.sun.star.ui.ModuleUIConfigurationManagerSupplier' + service = TYPE_DOC[self._app] + manager = create_instance(instance, True) + uicm = manager.getUIConfigurationManager(service) + self._scm = uicm.ShortCutManager + return + + def __contains__(self, item): + cmd = self._get_command(item) + return bool(cmd) + + def _get_key_event(self, command): + events = self._scm.AllKeyEvents + for event in events: + cmd = self._scm.getCommandByKeyEvent(event) + if cmd == command: + break + return event + + def _to_key_event(self, shortcut): + key_event = KeyEvent() + keys = shortcut.split('+') + for v in keys[:-1]: + key_event.Modifiers += MODIFIERS[v.lower()] + key_event.KeyCode = getattr(Key, keys[-1].upper()) + return key_event + + def _get_command(self, shortcut): + command = '' + key_event = self._to_key_event(shortcut) + try: + command = self._scm.getCommandByKeyEvent(key_event) + except NoSuchElementException: + debug(f'No exists: {shortcut}') + return command + + def add(self, shortcut, command): + if isinstance(command, dict): + command = _get_url_script(command) + key_event = self._to_key_event(shortcut) + self._scm.setKeyEvent(key_event, command) + self._scm.store() + return + + def reset(self): + self._scm.reset() + self._scm.store() + return + + def remove(self, shortcut): + key_event = self._to_key_event(shortcut) + try: + self._scm.removeKeyEvent(key_event) + self._scm.store() + except NoSuchElementException: + debug(f'No exists: {shortcut}') + return + + def remove_by_command(self, command): + if isinstance(command, dict): + command = _get_url_script(command) + try: + self._scm.removeCommandFromAllKeyEvents(command) + self._scm.store() + except NoSuchElementException: + debug(f'No exists: {command}') + return + + +class LOShortCuts(object): + + def __getitem__(self, index): + return LOShortCut(index) + + +class LOMenu(object): + + def __init__(self, app): + self._app = app + self._ui = None + self._pymenus = None + self._menu = None + self._menus = self._get_menus() + + def __getitem__(self, index): + if isinstance(index, int): + self._menu = self._menus[index] + else: + for menu in self._menus: + cmd = menu.get('CommandURL', '') + if MENUS[index.lower()] == cmd: + self._menu = menu + break + # ~ line = self._menu.get('CommandURL', '') + # ~ line += self._get_submenus(self._menu['ItemDescriptorContainer']) + return self._menu + + def _get_menus(self): + instance = 'com.sun.star.ui.ModuleUIConfigurationManagerSupplier' + service = TYPE_DOC[self._app] + manager = create_instance(instance, True) + self._ui = manager.getUIConfigurationManager(service) + self._pymenus = self._ui.getSettings(NODE_MENUBAR, True) + data = [] + for menu in self._pymenus: + data.append(data_to_dict(menu)) + return data + + def _get_info(self, menu): + line = menu.get('CommandURL', '') + line += self._get_submenus(menu['ItemDescriptorContainer']) + return line + + def _get_submenus(self, menu, level=1): + line = '' + for i, v in enumerate(menu): + data = data_to_dict(v) + cmd = data.get('CommandURL', '----------') + line += f'\n{" " * level}├─ ({i}) {cmd}' + submenu = data.get('ItemDescriptorContainer', None) + if not submenu is None: + line += self._get_submenus(submenu, level + 1) + return line + + def __str__(self): + info = '\n'.join([self._get_info(m) for m in self._menus]) + return info + + def _get_index_menu(self, menu, command): + index = -1 + for i, v in enumerate(menu): + data = data_to_dict(v) + cmd = data.get('CommandURL', '') + if cmd == command: + index = i + break + return index + + def insert(self, name, args): + idc = None + replace = False + command = args['CommandURL'] + label = args['Label'] + + self[name] + menu = self._menu['ItemDescriptorContainer'] + submenu = args.get('Submenu', False) + if submenu: + idc = self._ui.createSettings() + + index = self._get_index_menu(menu, command) + if index == -1: + if 'Index' in args: + index = args['Index'] + else: + index = self._get_index_menu(menu, args['After']) + 1 + else: + replace = True + + data = dict ( + CommandURL = command, + Label = label, + Style = 0, + Type = 0, + ItemDescriptorContainer = idc, + ) + self._save(menu, data, index, replace) + self._insert_submenu(idc, submenu) + return + + def _get_command(self, args): + shortcut = args.get('ShortCut', '') + cmd = args['CommandURL'] + if isinstance(cmd, dict): + cmd = _get_url_script(cmd) + if shortcut: + LOShortCut(self._app).add(shortcut, cmd) + return cmd + + def _insert_submenu(self, parent, menus): + for i, v in enumerate(menus): + submenu = v.pop('Submenu', False) + if submenu: + idc = self._ui.createSettings() + v['ItemDescriptorContainer'] = idc + v['Type'] = 0 + if v['Label'] == '-': + v['Type'] = 1 + else: + v['CommandURL'] = self._get_command(v) + self._save(parent, v, i) + if submenu: + self._insert_submenu(idc, submenu) + return + + def remove(self, name, command): + self[name] + menu = self._menu['ItemDescriptorContainer'] + index = self._get_index_menu(menu, command) + if index > -1: + uno.invoke(menu, 'removeByIndex', (index,)) + self._ui.replaceSettings(NODE_MENUBAR, self._pymenus) + self._ui.store() + return + + def _save(self, menu, properties, index, replace=False): + properties = dict_to_property(properties, True) + if replace: + uno.invoke(menu, 'replaceByIndex', (index, properties)) + else: + uno.invoke(menu, 'insertByIndex', (index, properties)) + self._ui.replaceSettings(NODE_MENUBAR, self._pymenus) + self._ui.store() + return + + +class LOMenus(object): + + def __getitem__(self, index): + return LOMenu(index) + + +class LOWindow(object): + EMPTY = """ + +""" + MODELS = { + 'label': 'com.sun.star.awt.UnoControlFixedTextModel', + 'link': 'com.sun.star.awt.UnoControlFixedHyperlinkModel', + 'button': 'com.sun.star.awt.UnoControlButtonModel', + 'radio': 'com.sun.star.awt.UnoControlRadioButtonModel', + 'checkbox': 'com.sun.star.awt.UnoControlCheckBoxModel', + 'text': 'com.sun.star.awt.UnoControlEditModel', + 'image': 'com.sun.star.awt.UnoControlImageControlModel', + 'listbox': 'com.sun.star.awt.UnoControlListBoxModel', + 'roadmap': 'com.sun.star.awt.UnoControlRoadmapModel', + 'tree': 'com.sun.star.awt.tree.TreeControlModel', + 'grid': 'com.sun.star.awt.grid.UnoControlGridModel', + 'pages': 'com.sun.star.awt.UnoMultiPageModel', + 'groupbox': 'com.sun.star.awt.UnoControlGroupBoxModel', + 'combobox': 'com.sun.star.awt.UnoControlComboBoxModel', + } + + def __init__(self, args): + self._events = None + self._menu = None + self._container = None + self._model = None + self._id = '' + self._path = '' + self._obj = self._create(args) + + def _create(self, properties): + ps = ( + properties.get('X', 0), + properties.get('Y', 0), + properties.get('Width', 500), + properties.get('Height', 500), + ) + self._title = properties.get('Title', TITLE) + self._create_frame(ps) + self._create_container(ps) + self._create_subcontainer(ps) + # ~ self._create_splitter(ps) + return + + def _create_frame(self, ps): + service = 'com.sun.star.frame.TaskCreator' + tc = create_instance(service, True) + self._frame = tc.createInstanceWithArguments(( + NamedValue('FrameName', 'EasyMacroWin'), + NamedValue('PosSize', Rectangle(*ps)), + )) + self._window = self._frame.getContainerWindow() + self._toolkit = self._window.getToolkit() + desktop = get_desktop() + self._frame.setCreator(desktop) + desktop.getFrames().append(self._frame) + self._frame.Title = self._title + return + + def _create_container(self, ps): + service = 'com.sun.star.awt.UnoControlContainer' + self._container = create_instance(service, True) + service = 'com.sun.star.awt.UnoControlContainerModel' + model = create_instance(service, True) + model.BackgroundColor = get_color((225, 225, 225)) + self._container.setModel(model) + self._container.createPeer(self._toolkit, self._window) + self._container.setPosSize(*ps, POSSIZE) + self._frame.setComponent(self._container, None) + return + + def _create_subcontainer(self, ps): + service = 'com.sun.star.awt.ContainerWindowProvider' + cwp = create_instance(service, True) + + path_tmp = _P.save_tmp(self.EMPTY) + subcont = cwp.createContainerWindow( + _P.to_url(path_tmp), '', self._container.getPeer(), None) + _P.kill(path_tmp) + + subcont.setPosSize(0, 0, 500, 500, POSSIZE) + subcont.setVisible(True) + self._container.addControl('subcont', subcont) + self._subcont = subcont + self._model = subcont.Model + return + + def _create_popupmenu(self, menus): + menu = create_instance('com.sun.star.awt.PopupMenu', True) + for i, m in enumerate(menus): + label = m['label'] + cmd = m.get('event', '') + if not cmd: + cmd = label.lower().replace(' ', '_') + if label == '-': + menu.insertSeparator(i) + else: + menu.insertItem(i, label, m.get('style', 0), i) + menu.setCommand(i, cmd) + # ~ menu.setItemImage(i, path?, True) + menu.addMenuListener(EventsMenu(self.events)) + return menu + + def _create_menu(self, menus): + #~ https://api.libreoffice.org/docs/idl/ref/interfacecom_1_1sun_1_1star_1_1awt_1_1XMenu.html + #~ nItemId specifies the ID of the menu item to be inserted. + #~ aText specifies the label of the menu item. + #~ nItemStyle 0 = Standard, CHECKABLE = 1, RADIOCHECK = 2, AUTOCHECK = 4 + #~ nItemPos specifies the position where the menu item will be inserted. + self._menu = create_instance('com.sun.star.awt.MenuBar', True) + for i, m in enumerate(menus): + self._menu.insertItem(i, m['label'], m.get('style', 0), i) + cmd = m['label'].lower().replace(' ', '_') + self._menu.setCommand(i, cmd) + submenu = self._create_popupmenu(m['submenu']) + self._menu.setPopupMenu(i, submenu) + + self._window.setMenuBar(self._menu) + return + + def _add_listeners(self, control=None): + if self.events is None: + return + controller = EventsWindow(self) + self._window.addTopWindowListener(controller) + self._window.addWindowListener(controller) + # ~ self._container.addKeyListener(EventsKeyWindow(self)) + return + + def _set_image_url(self, image): + if _P.exists(image): + return _P.to_url(image) + + path = _P.join(self._path, DIR['images'], image) + return _P.to_url(path) + + def _special_properties(self, tipo, args): + if tipo == 'link' and not 'Label' in args: + args['Label'] = args['URL'] + return args + + if tipo == 'button': + if 'ImageURL' in args: + args['ImageURL'] = self._set_image_url(args['ImageURL']) + args['FocusOnClick'] = args.get('FocusOnClick', False) + return args + + if tipo == 'roadmap': + args['Height'] = args.get('Height', self.height) + if 'Title' in args: + args['Text'] = args.pop('Title') + return args + + if tipo == 'tree': + args['SelectionType'] = args.get('SelectionType', SINGLE) + return args + + if tipo == 'grid': + args['ShowRowHeader'] = args.get('ShowRowHeader', True) + return args + + if tipo == 'pages': + args['Width'] = args.get('Width', self.width) + args['Height'] = args.get('Height', self.height) + + return args + + def add_control(self, args): + tipo = args.pop('Type').lower() + root = args.pop('Root', '') + sheets = args.pop('Sheets', ()) + columns = args.pop('Columns', ()) + + args = self._special_properties(tipo, args) + model = self.model.createInstance(self.MODELS[tipo]) + _set_properties(model, args) + name = args['Name'] + self.model.insertByName(name, model) + control = self._subcont.getControl(name) + _add_listeners(self.events, control, name) + control = UNO_CLASSES[tipo](control) + + # ~ if tipo in ('listbox',): + # ~ control.path = self.path + + if tipo == 'tree' and root: + control.root = root + elif tipo == 'grid' and columns: + control.columns = columns + elif tipo == 'pages' and sheets: + control.sheets = sheets + control.events = self.events + + setattr(self, name, control) + return control + + @property + def events(self): + return self._events + @events.setter + def events(self, controllers): + self._events = controllers(self) + self._add_listeners() + + @property + def model(self): + return self._model + + @property + def width(self): + return self._container.Size.Width + + @property + def height(self): + return self._container.Size.Height + + @property + def name(self): + return self._title.lower().replace(' ', '_') + + def add_menu(self, menus): + self._create_menu(menus) + return + + def open(self): + self._window.setVisible(True) + return + + def close(self): + self._window.setMenuBar(None) + self._window.dispose() + self._frame.close(True) + return + + +def create_window(args): + return LOWindow(args) + + +class classproperty: + def __init__(self, method=None): + self.fget = method + + def __get__(self, instance, cls=None): + return self.fget(cls) + + def getter(self, method): + self.fget = method + return self + + +class ClipBoard(object): + SERVICE = 'com.sun.star.datatransfer.clipboard.SystemClipboard' + CLIPBOARD_FORMAT_TEXT = 'text/plain;charset=utf-16' + + class TextTransferable(unohelper.Base, XTransferable): + + def __init__(self, text): + df = DataFlavor() + df.MimeType = ClipBoard.CLIPBOARD_FORMAT_TEXT + df.HumanPresentableName = "encoded text utf-16" + self.flavors = (df,) + self._data = text + + def getTransferData(self, flavor): + return self._data + + def getTransferDataFlavors(self): + return self.flavors + + + @classmethod + def set(cls, value): + ts = cls.TextTransferable(value) + sc = create_instance(cls.SERVICE) + sc.setContents(ts, None) + return + + @classproperty + def contents(cls): + df = None + text = '' + sc = create_instance(cls.SERVICE) + transferable = sc.getContents() + data = transferable.getTransferDataFlavors() + for df in data: + if df.MimeType == cls.CLIPBOARD_FORMAT_TEXT: + break + if df: + text = transferable.getTransferData(df) + return text +_CB = ClipBoard + + +class Paths(object): + FILE_PICKER = 'com.sun.star.ui.dialogs.FilePicker' + FOLDER_PICKER = 'com.sun.star.ui.dialogs.FolderPicker' + + def __init__(self, path=''): + if path.startswith('file://'): + path = str(Path(uno.fileUrlToSystemPath(path)).resolve()) + self._path = Path(path) + + @property + def path(self): + return str(self._path.parent) + + @property + def file_name(self): + return self._path.name + + @property + def name(self): + return self._path.stem + + @property + def ext(self): + return self._path.suffix[1:] + + @property + def info(self): + return self.path, self.file_name, self.name, self.ext + + @property + def url(self): + return self._path.as_uri() + + @property + def size(self): + return self._path.stat().st_size + + @classproperty + def home(self): + return str(Path.home()) + + @classproperty + def documents(self): + return self.config() + + @classproperty + def temp_dir(self): + return tempfile.gettempdir() + + @classproperty + def python(self): + if IS_WIN: + path = self.join(self.config('Module'), PYTHON) + elif IS_MAC: + path = self.join(self.config('Module'), '..', 'Resources', PYTHON) + else: + path = sys.executable + return path + + @classmethod + def dir_tmp(self, only_name=False): + dt = tempfile.TemporaryDirectory() + if only_name: + dt = dt.name + return dt + + @classmethod + def tmp(cls, ext=''): + tmp = tempfile.NamedTemporaryFile(suffix=ext) + return tmp.name + + @classmethod + def save_tmp(cls, data): + path_tmp = cls.tmp() + cls.save(path_tmp, data) + return path_tmp + + @classmethod + def config(cls, name='Work'): + """ + Return de path name in config + http://api.libreoffice.org/docs/idl/ref/interfacecom_1_1sun_1_1star_1_1util_1_1XPathSettings.html + """ + path = create_instance('com.sun.star.util.PathSettings') + return cls.to_system(getattr(path, name)) + + @classmethod + def get(cls, init_dir='', filters: str=''): + """ + Options: http://api.libreoffice.org/docs/idl/ref/namespacecom_1_1sun_1_1star_1_1ui_1_1dialogs_1_1TemplateDescription.html + filters: 'xml' or 'txt,xml' + """ + if not init_dir: + init_dir = cls.documents + init_dir = cls.to_url(init_dir) + file_picker = create_instance(cls.FILE_PICKER) + file_picker.setTitle(_('Select path')) + file_picker.setDisplayDirectory(init_dir) + file_picker.initialize((2,)) + if filters: + filters = [(f.upper(), f'*.{f.lower()}') for f in filters.split(',')] + file_picker.setCurrentFilter(filters[0][0]) + for f in filters: + file_picker.appendFilter(f[0], f[1]) + + path = '' + if file_picker.execute(): + path = cls.to_system(file_picker.getSelectedFiles()[0]) + return path + + @classmethod + def get_dir(cls, init_dir=''): + folder_picker = create_instance(cls.FOLDER_PICKER) + if not init_dir: + init_dir = cls.documents + init_dir = cls.to_url(init_dir) + folder_picker.setTitle(_('Select directory')) + folder_picker.setDisplayDirectory(init_dir) + + path = '' + if folder_picker.execute(): + path = cls.to_system(folder_picker.getDirectory()) + return path + + @classmethod + def get_file(cls, init_dir: str='', filters: str='', multiple: bool=False): + """ + init_folder: folder default open + multiple: True for multiple selected + filters: 'xml' or 'xml,txt' + """ + if not init_dir: + init_dir = cls.documents + init_dir = cls.to_url(init_dir) + + file_picker = create_instance(cls.FILE_PICKER) + file_picker.setTitle(_('Select file')) + file_picker.setDisplayDirectory(init_dir) + file_picker.setMultiSelectionMode(multiple) + + if filters: + filters = [(f.upper(), f'*.{f.lower()}') for f in filters.split(',')] + file_picker.setCurrentFilter(filters[0][0]) + for f in filters: + file_picker.appendFilter(f[0], f[1]) + + path = '' + if file_picker.execute(): + files = file_picker.getSelectedFiles() + path = [cls.to_system(f) for f in files] + if not multiple: + path = path[0] + return path + + @classmethod + def replace_ext(cls, path, new_ext): + p = Paths(path) + name = f'{p.name}.{new_ext}' + path = cls.join(p.path, name) + return path + + @classmethod + def exists(cls, path): + result = False + if path: + path = cls.to_system(path) + result = Path(path).exists() + return result + + @classmethod + def exists_app(cls, name_app): + return bool(shutil.which(name_app)) + + @classmethod + def open(cls, path): + if IS_WIN: + os.startfile(path) + else: + pid = subprocess.Popen(['xdg-open', path]).pid + return + + @classmethod + def is_dir(cls, path): + return Path(path).is_dir() + + @classmethod + def is_file(cls, path): + return Path(path).is_file() + + @classmethod + def join(cls, *paths): + return str(Path(paths[0]).joinpath(*paths[1:])) + + @classmethod + def save(cls, path, data, encoding='utf-8'): + result = bool(Path(path).write_text(data, encoding=encoding)) + return result + + @classmethod + def save_bin(cls, path, data): + result = bool(Path(path).write_bytes(data)) + return result + + @classmethod + def read(cls, path, encoding='utf-8'): + data = Path(path).read_text(encoding=encoding) + return data + + @classmethod + def read_bin(cls, path): + data = Path(path).read_bytes() + return data + + @classmethod + def to_url(cls, path): + if not path.startswith('file://'): + path = Path(path).as_uri() + return path + + @classmethod + def to_system(cls, path): + if path.startswith('file://'): + path = str(Path(uno.fileUrlToSystemPath(path)).resolve()) + return path + + @classmethod + def kill(cls, path): + result = True + p = Path(path) + + try: + if p.is_file(): + p.unlink() + elif p.is_dir(): + shutil.rmtree(path) + except OSError as e: + log.error(e) + result = False + + return result + + @classmethod + def files(cls, path, pattern='*'): + files = [str(p) for p in Path(path).glob(pattern) if p.is_file()] + return files + + @classmethod + def dirs(cls, path): + dirs = [str(p) for p in Path(path).iterdir() if p.is_dir()] + return dirs + + @classmethod + def walk(cls, path, filters=''): + paths = [] + if filters in ('*', '*.*'): + filters = '' + for folder, _, files in os.walk(path): + if filters: + pattern = re.compile(r'\.(?:{})$'.format(filters), re.IGNORECASE) + paths += [cls.join(folder, f) for f in files if pattern.search(f)] + else: + paths += [cls.join(folder, f) for f in files] + return paths + + @classmethod + def walk_dir(cls, path, tree=False): + folders = [] + if tree: + i = 0 + p = 0 + parents = {path: 0} + for root, dirs, _ in os.walk(path): + for name in dirs: + i += 1 + rn = cls.join(root, name) + if not rn in parents: + parents[rn] = i + folders.append((i, parents[root], name)) + else: + for root, dirs, _ in os.walk(path): + folders += [cls.join(root, name) for name in dirs] + return folders + + @classmethod + def from_id(cls, id_ext): + pip = CTX.getValueByName('/singletons/com.sun.star.deployment.PackageInformationProvider') + path = _P.to_system(pip.getPackageLocation(id_ext)) + return path + + @classmethod + def from_json(cls, path): + data = json.loads(cls.read(path)) + return data + + @classmethod + def to_json(cls, path, data): + data = json.dumps(data, indent=4, ensure_ascii=False, sort_keys=True) + return cls.save(path, data) + + @classmethod + def from_csv(cls, path, args={}): + # ~ See https://docs.python.org/3.7/library/csv.html#csv.reader + with open(path) as f: + rows = tuple(csv.reader(f, **args)) + return rows + + @classmethod + def to_csv(cls, path, data, args={}): + with open(path, 'w') as f: + writer = csv.writer(f, **args) + writer.writerows(data) + return + + @classmethod + def zip(cls, source, target='', pwd=''): + path_zip = target + if not isinstance(source, (tuple, list)): + path, _, name, _ = _P(source).info + start = len(path) + 1 + if not target: + path_zip = f'{path}/{name}.zip' + + if isinstance(source, (tuple, list)): + files = [(f, f[len(_P(f).path)+1:]) for f in source] + elif _P.is_file(source): + files = ((source, source[start:]),) + else: + files = [(f, f[start:]) for f in _P.walk(source)] + + compression = zipfile.ZIP_DEFLATED + with zipfile.ZipFile(path_zip, 'w', compression=compression) as z: + for f in files: + z.write(f[0], f[1]) + return + + @classmethod + def zip_content(cls, path): + with zipfile.ZipFile(path) as z: + names = z.namelist() + return names + + @classmethod + def unzip(cls, source, target='', members=None, pwd=None): + path = target + if not target: + path = _P(source).path + with zipfile.ZipFile(source) as z: + if not pwd is None: + pwd = pwd.encode() + if isinstance(members, str): + members = (members,) + z.extractall(path, members=members, pwd=pwd) + return True + + @classmethod + def merge_zip(cls, target, zips): + try: + with zipfile.ZipFile(target, 'w', compression=zipfile.ZIP_DEFLATED) as t: + for path in zips: + with zipfile.ZipFile(path, compression=zipfile.ZIP_DEFLATED) as s: + for name in s.namelist(): + t.writestr(name, s.open(name).read()) + except Exception as e: + error(e) + return False + + return True + + @classmethod + def image(cls, path): + gp = create_instance('com.sun.star.graphic.GraphicProvider') + image = gp.queryGraphic(( + PropertyValue(Name='URL', Value=cls.to_url(path)), + )) + return image + + @classmethod + def copy(cls, source, target='', name=''): + p, f, n, e = _P(source).info + if target: + p = target + if name: + e = '' + n = name + path_new = cls.join(p, f'{n}{e}') + shutil.copy(source, path_new) + return path_new +_P = Paths + + +class Dates(object): + + @classmethod + def date(cls, year, month, day): + d = datetime.date(year, month, day) + return d + + +class SpellChecker(object): + + def __init__(self): + service = 'com.sun.star.linguistic2.SpellChecker' + self._spellchecker = create_instance(service, True) + self._locale = LOCALE + + @property + def locale(self): + slocal = f'{self._locale.Language}-{self._locale.Country}' + return slocale + @locale.setter + def locale(self, value): + lang = value.split('-') + self._locale = Locale(lang[0], lang[1], '') + + def is_valid(self, word): + result = self._spellchecker.isValid(word, self._locale, ()) + return result + + def spell(self, word): + result = self._spellchecker.spell(word, self._locale, ()) + if result: + result = result.getAlternatives() + if not isinstance(result, tuple): + result = () + return result + + +def spell(word, locale=''): + sc = SpellChecker() + if locale: + sc.locale = locale + return sc.spell(word) + + +def __getattr__(name): + if name == 'active': + return LODocs().active + if name == 'active_sheet': + return LODocs().active.active + if name == 'selection': + return LODocs().active.selection + if name == 'current_region': + return LODocs().active.selection.current_region + if name in ('rectangle', 'pos_size'): + return Rectangle() + if name == 'paths': + return Paths + if name == 'docs': + return LODocs() + if name == 'sheets': + return LOSheets() + if name == 'cells': + return LOCells() + if name == 'menus': + return LOMenus() + if name == 'shortcuts': + return LOShortCuts() + if name == 'clipboard': + return ClipBoard + if name == 'dates': + return Dates + raise AttributeError(f"module '{__name__}' has no attribute '{name}'") + + +def create_dialog(args): + return LODialog(args) + + +def inputbox(message, default='', title=TITLE, echochar=''): + + class ControllersInput(object): + + def __init__(self, dlg): + self.d = dlg + + def cmd_ok_action(self, event): + self.d.close(1) + return + + args = { + 'Title': title, + 'Width': 200, + 'Height': 80, + } + dlg = LODialog(args) + dlg.events = ControllersInput + + args = { + 'Type': 'Label', + 'Name': 'lbl_msg', + 'Label': message, + 'Width': 140, + 'Height': 50, + 'X': 5, + 'Y': 5, + 'MultiLine': True, + 'Border': 1, + } + dlg.add_control(args) + + args = { + 'Type': 'Text', + 'Name': 'txt_value', + 'Text': default, + 'Width': 190, + 'Height': 15, + } + if echochar: + args['EchoChar'] = ord(echochar[0]) + dlg.add_control(args) + dlg.txt_value.move(dlg.lbl_msg) + + args = { + 'Type': 'button', + 'Name': 'cmd_ok', + 'Label': _('OK'), + 'Width': 40, + 'Height': 15, + 'DefaultButton': True, + 'PushButtonType': 1, + } + dlg.add_control(args) + dlg.cmd_ok.move(dlg.lbl_msg, 10, 0) + + args = { + 'Type': 'button', + 'Name': 'cmd_cancel', + 'Label': _('Cancel'), + 'Width': 40, + 'Height': 15, + 'PushButtonType': 2, + } + dlg.add_control(args) + dlg.cmd_cancel.move(dlg.cmd_ok) + + if dlg.open(): + return dlg.txt_value.value + + return '' + + +def get_fonts(): + toolkit = create_instance('com.sun.star.awt.Toolkit') + device = toolkit.createScreenCompatibleDevice(0, 0) + return device.FontDescriptors + + +# ~ https://en.wikipedia.org/wiki/Web_colors +def get_color(value): + COLORS = { + 'aliceblue': 15792383, + 'antiquewhite': 16444375, + 'aqua': 65535, + 'aquamarine': 8388564, + 'azure': 15794175, + 'beige': 16119260, + 'bisque': 16770244, + 'black': 0, + 'blanchedalmond': 16772045, + 'blue': 255, + 'blueviolet': 9055202, + 'brown': 10824234, + 'burlywood': 14596231, + 'cadetblue': 6266528, + 'chartreuse': 8388352, + 'chocolate': 13789470, + 'coral': 16744272, + 'cornflowerblue': 6591981, + 'cornsilk': 16775388, + 'crimson': 14423100, + 'cyan': 65535, + 'darkblue': 139, + 'darkcyan': 35723, + 'darkgoldenrod': 12092939, + 'darkgray': 11119017, + 'darkgreen': 25600, + 'darkgrey': 11119017, + 'darkkhaki': 12433259, + 'darkmagenta': 9109643, + 'darkolivegreen': 5597999, + 'darkorange': 16747520, + 'darkorchid': 10040012, + 'darkred': 9109504, + 'darksalmon': 15308410, + 'darkseagreen': 9419919, + 'darkslateblue': 4734347, + 'darkslategray': 3100495, + 'darkslategrey': 3100495, + 'darkturquoise': 52945, + 'darkviolet': 9699539, + 'deeppink': 16716947, + 'deepskyblue': 49151, + 'dimgray': 6908265, + 'dimgrey': 6908265, + 'dodgerblue': 2003199, + 'firebrick': 11674146, + 'floralwhite': 16775920, + 'forestgreen': 2263842, + 'fuchsia': 16711935, + 'gainsboro': 14474460, + 'ghostwhite': 16316671, + 'gold': 16766720, + 'goldenrod': 14329120, + 'gray': 8421504, + 'grey': 8421504, + 'green': 32768, + 'greenyellow': 11403055, + 'honeydew': 15794160, + 'hotpink': 16738740, + 'indianred': 13458524, + 'indigo': 4915330, + 'ivory': 16777200, + 'khaki': 15787660, + 'lavender': 15132410, + 'lavenderblush': 16773365, + 'lawngreen': 8190976, + 'lemonchiffon': 16775885, + 'lightblue': 11393254, + 'lightcoral': 15761536, + 'lightcyan': 14745599, + 'lightgoldenrodyellow': 16448210, + 'lightgray': 13882323, + 'lightgreen': 9498256, + 'lightgrey': 13882323, + 'lightpink': 16758465, + 'lightsalmon': 16752762, + 'lightseagreen': 2142890, + 'lightskyblue': 8900346, + 'lightslategray': 7833753, + 'lightslategrey': 7833753, + 'lightsteelblue': 11584734, + 'lightyellow': 16777184, + 'lime': 65280, + 'limegreen': 3329330, + 'linen': 16445670, + 'magenta': 16711935, + 'maroon': 8388608, + 'mediumaquamarine': 6737322, + 'mediumblue': 205, + 'mediumorchid': 12211667, + 'mediumpurple': 9662683, + 'mediumseagreen': 3978097, + 'mediumslateblue': 8087790, + 'mediumspringgreen': 64154, + 'mediumturquoise': 4772300, + 'mediumvioletred': 13047173, + 'midnightblue': 1644912, + 'mintcream': 16121850, + 'mistyrose': 16770273, + 'moccasin': 16770229, + 'navajowhite': 16768685, + 'navy': 128, + 'oldlace': 16643558, + 'olive': 8421376, + 'olivedrab': 7048739, + 'orange': 16753920, + 'orangered': 16729344, + 'orchid': 14315734, + 'palegoldenrod': 15657130, + 'palegreen': 10025880, + 'paleturquoise': 11529966, + 'palevioletred': 14381203, + 'papayawhip': 16773077, + 'peachpuff': 16767673, + 'peru': 13468991, + 'pink': 16761035, + 'plum': 14524637, + 'powderblue': 11591910, + 'purple': 8388736, + 'red': 16711680, + 'rosybrown': 12357519, + 'royalblue': 4286945, + 'saddlebrown': 9127187, + 'salmon': 16416882, + 'sandybrown': 16032864, + 'seagreen': 3050327, + 'seashell': 16774638, + 'sienna': 10506797, + 'silver': 12632256, + 'skyblue': 8900331, + 'slateblue': 6970061, + 'slategray': 7372944, + 'slategrey': 7372944, + 'snow': 16775930, + 'springgreen': 65407, + 'steelblue': 4620980, + 'tan': 13808780, + 'teal': 32896, + 'thistle': 14204888, + 'tomato': 16737095, + 'turquoise': 4251856, + 'violet': 15631086, + 'wheat': 16113331, + 'white': 16777215, + 'whitesmoke': 16119285, + 'yellow': 16776960, + 'yellowgreen': 10145074, + } + + if isinstance(value, tuple): + color = (value[0] << 16) + (value[1] << 8) + value[2] + else: + if value[0] == '#': + r, g, b = bytes.fromhex(value[1:]) + color = (r << 16) + (g << 8) + b + else: + color = COLORS.get(value.lower(), -1) + return color + + +COLOR_ON_FOCUS = get_color('LightYellow') + + +class LOServer(object): + HOST = 'localhost' + PORT = '8100' + ARG = f'socket,host={HOST},port={PORT};urp;StarOffice.ComponentContext' + CMD = ['soffice', + '-env:SingleAppInstance=false', + '-env:UserInstallation=file:///tmp/LO_Process8100', + '--headless', '--norestore', '--invisible', + f'--accept={ARG}'] + + def __init__(self): + self._server = None + self._ctx = None + self._sm = None + self._start_server() + self._init_values() + + def _init_values(self): + global CTX + global SM + + if not self.is_running: + return + + ctx = uno.getComponentContext() + service = 'com.sun.star.bridge.UnoUrlResolver' + resolver = ctx.ServiceManager.createInstanceWithContext(service, ctx) + self._ctx = resolver.resolve('uno:{}'.format(self.ARG)) + self._sm = self._ctx.getServiceManager() + CTX = self._ctx + SM = self._sm + return + + @property + def is_running(self): + try: + s = socket.create_connection((self.HOST, self.PORT), 5.0) + s.close() + debug('LibreOffice is running...') + return True + except ConnectionRefusedError: + return False + + def _start_server(self): + if self.is_running: + return + + for i in range(3): + self._server = subprocess.Popen(self.CMD, + stdout=subprocess.PIPE, stderr=subprocess.PIPE) + time.sleep(3) + if self.is_running: + break + return + + def stop(self): + if self._server is None: + print('Search pgrep soffice') + else: + self._server.terminate() + debug('LibreOffice is stop...') + return + + def create_instance(self, name, with_context=True): + if with_context: + instance = self._sm.createInstanceWithContext(name, self._ctx) + else: + instance = self._sm.createInstance(name) + return instance diff --git a/source/pythonpath/libo.py b/source/pythonpath/libo.py new file mode 100644 index 0000000..6c2fc99 --- /dev/null +++ b/source/pythonpath/libo.py @@ -0,0 +1,29 @@ +#!/usr/bin/env python3 + +from net.elmau.zaz.EasyMacro import XDebug +import easymacro as app + + +class Debug(XDebug): + + def info(self, message): + app.info(message) + return + + def debug(self, message): + app.debug(message) + return + + def error(self, message): + app.error(message) + return + + def inspect(self, obj): + app.inspect(obj) + return + + +class LIBO(Debug): + OS = app.OS + USER = app.USER + INFO_DEBUG = app.INFO_DEBUG diff --git a/source/registration/license_en.txt b/source/registration/license_en.txt new file mode 100644 index 0000000..7925d1e --- /dev/null +++ b/source/registration/license_en.txt @@ -0,0 +1,14 @@ +This file is part of ZAZEasyMacro. + + ZAZEasyMacro is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + ZAZEasyMacro is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with ZAZEasyMacro. If not, see . diff --git a/source/registration/license_es.txt b/source/registration/license_es.txt new file mode 100644 index 0000000..7925d1e --- /dev/null +++ b/source/registration/license_es.txt @@ -0,0 +1,14 @@ +This file is part of ZAZEasyMacro. + + ZAZEasyMacro is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + ZAZEasyMacro is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with ZAZEasyMacro. If not, see . diff --git a/zaz.py b/zaz.py old mode 100644 new mode 100755