From 5f80a6b4005a7649f5d22c05bfa60bf3f54def88 Mon Sep 17 00:00:00 2001 From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com> Date: Sun, 12 Nov 2023 15:33:05 +0100 Subject: [PATCH 01/14] Update PyPi info --- setup/description_pypi.md | 4 ++-- setup/novelwriter_text.png | Bin 17670 -> 19979 bytes 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/setup/description_pypi.md b/setup/description_pypi.md index 9ee924c8..1bd8cfef 100644 --- a/setup/description_pypi.md +++ b/setup/description_pypi.md @@ -10,9 +10,9 @@ synchronisation tools. All text is saved as plain text files with a meta data he project structure is stored in a single project XML file, and other meta data is primarily saved as JSON files. -The application is written with Python 3 (3.8+) using Qt5 and PyQt5 (5.3+). It is developed on +The application is written with Python 3 (3.8+) using Qt5 and PyQt5 (5.10+). It is developed on Linux, but should in principle work fine on other operating systems as well as long as dependencies -are met. It is regularly tested on Debian and Ubuntu Linux, Windows, and macOS. +are met. It is regularly tested on Debian and Ubuntu Linux, Windows, and MacOS. novelWriter is developed and maintained by [Veronica Berglyd Olsen](https://github.com/vkbo). diff --git a/setup/novelwriter_text.png b/setup/novelwriter_text.png index e7f169cdd19761c77eb22915121baa963c308e64..084ffbf296d0d072705b9fe99de212dbe204ba10 100644 GIT binary patch literal 19979 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 z z#kC3coOwv2x@Zz3BxWeHDV%EBv<-e3RgRxiVe9bdJ-hYuLUYknc|+PGYH#zEKl$2w zSo`yc*0tNj1{0x)z z@0=vR^fa)1j|MxZ6nkITd7yeZz_kNldRATCo19D`C{wR64fLv;E89w=s>_mA_IZduTjQm?=;wbyKV+bzruKG?iP6w?9{>>Ub~coN@)i^6y*Zo{Ip!mnIKQxYaB$$H zrlN99?;H2UfJ-Q+z_!_f%6)=ooNfDac%WV<_-|PNt~7G+wz66)wX38K_TMO$$=iQ;&iZ((y#VINE{Fdgbz=Gaqv54fv0X8gAh{x z-Ef8hSCueDWR6{BvhU^R8qc5EiqVb)_(eZMAVh$=CM1m24Rn;!R`GrIhmZC3&NpRC=xu(1J4r6E;N9rIc(~ddU|2YMF51Q|Q*HnLG4TNS$XCwJ`-Nif zcCWGIHGLn|^C$K3J|_LJ{%iHH0nT%P4yB5^|2?z#9l$~Tkx1%%A-I_oY;-Q0dr#a< zo9n8oPA^thS2tgl8w5qZFtnL&aotZG@yNDFnfeO|a-N&16wRef=ih(Zx2?9e*0s!U z$pp;?^@lV9?n>a5zKD+;1t%UYME!9xQ;1Cdp;F$6n1$lZ;Y}c_kwRDZjtW-s7l>#% zo12@>K%bb2z_dRjWO#;V^{`QQ(FmhVR9N^d3CPp)7ExGZ57d}RogPwAi9qZ0AMqmt zcV}vkd>hRII``-zvX~VV6{A}|l{g-VU|uw9<^Qp>;K5I4i&vgEGB!32a&Y(%N^T3V z*8_lHeS$kpKxdZ-xV<+2V=HJjPT`!c!2EkB!hN+Uqp;RwcP5@w-#=c9L%+)fVE-0} z>8eI@kBTfRD%WOW?}QdeqYaLJ76{-B&%{_M72YhZHh}pOqScI}|7b$!2)bi=I#aAt zB06mYItA~``))=gc~`x_VOZXAn}SD8>z9~ZoB%E6x(!HgK^9a?)!3=`CuUwSCp6jT zqBiQx8a)q|0)+2bnmP7jxV$Bc?QDPf;ALeIHB3%Xq&pq+fSKKFLhibe>Epl}bUuXh^8f8^1C5>1%F4?BqgLJ0I1!Bbr{C3Sb<3Q! z!-tzd*H3Q%G8p*%`}V125t#rHn!SU=&I9Y;sTVuoaee_3F`5ten&kuS4ck^0jyUdL zGKsP~OV6@&wFU1jYfaoQDk}0_0()N=q5c`Y-pcm=T32@qmrfZb|NZ6*pIQ^54rkD$ zU?SQq?b7%4E%d()!u$;DBo$;NGO1oE#~+{ad|P zFJCU@A@WF1T0H3V(c%a~TdjjH)70|RQ4d8P7!4W>4LF_F<3L}(er+P0?&u*WH5;jh z3kj{&vb22Lx-9NZ;0=h=`bEjUD@2*3mpU)MX#3k;GNZYsq2by;;W4e!<1bS7Sgzfg zOB{WcIKuHHp!A$ROuSwK1gzyOa2CD%T+&(bs+ks?K3ks`>6@M*qMSd%pMv=Gj7?1& z38$$aH0mBEHOK50j&7YqT3o$g?>BGoJQx@o8^bT-)<9>yi^$;}FUs)R+FQa?`1FX> z-L#qZ2hES1%|xOJ3Yf5hGK3$I^+&eJ+$6)UO#Rc+@>?pwv3bv82miiDrO%{cX{Kd1TscSb}xBwvjQZoJ)}A(B<93*D zacw?;HR3tPx${2q-~nAPZ*OlWAV$O}8Pek;=HQol&eh0q4?H2i+(gbPo?)yu@Hm*o>eg=r!tOe^B_SHv=V zucHJ@z`w`CrC{4muFOJ^UkPfjg(p zL=(l)Ku>Xh6X8g@Np!sy3|rzZa-~K z5m24R%lHyo^^d5_fepNeq$f1qH_+gpv9#dSG|Xc*Z}$YQgI zZXyfi)i-!;iTdbte(u@mpwD^!n%8Gi1eqxy=K;)?<`MU_)~5rG;#}t30nJ`VYuYE? zH#Gi)q_U(B9z0M-{jPQBu}AXv7h@aq;mKtW8Ps^6{7qO?!CCzgN%P5Zd5NM?NsQZd zx&&IAQX!XS-!8ZBczHmU6K989fpj`8o&+T+tav<58{q+iU`R>Mf z(Ty6C(LoCfAytVRzD^T+_xszZ*dxy0Kafk}eUzGJUy=zHn2^3S6a@^gu(F?@Ines` zkUt!kwCh za!#3eYKWrdeGC8OADl&Y0q*W64@EGjYQvfOu1iq=@%e|j_b$?WtCl#J{l8fN%(8_r zinEhn5m_OkU%4{|22c-eI=B^wOa|J7CVN|TgB+G{jAjZ79wT_gvwZqSha=^foTT^t zsp`Rkt1h0eb#}&mC}JvxHBpkrWPkQ`W67w0)#YbhNdSwD3cxZJ6OQib4`%+f0@DhS zHsdBL5vnI#QEH7Ok)^VfqTaXl0P^@q*$3@Es3YhQ2wBvBnn6J=ZQK0e5{XB$UX7a~ zbe>e6HO(Ec-1$+Rzti_DD0^t*g@Eev(_!BXlev)@E<~KxydOgL{S!F6>_IU_8NRe8 z*=qQ8dg-Vwf2b@>zjVCxVm6`Y)u%R!rcYV4{|g)7zW>Y}uly2IfU?PqU1{moicVTr zcT^)N--PFX-q(+fba(C4*$|9sM7xBxV-7`YNg%!R^xQJOJ)SgdMMR!Ausiq%fj3sM zqfL{VS9-a9F-G7tMXky2$ne=Ei}uR7kKqK+p?;~^jksmscrVFEg)_TkAzYu2akGPm z*I(~H8G$AAnv$kdj{EWWCJfnnzV$+dmTm+&IuRQL8Ss?kvMIB1XPNN0wy>wCu;a7d z!fL|Uv84qt@Ni*)ju^7I??&oI+gE)M{c;ZSn&=q8wv|(aRBZe$>qx^|WdvU@}o85yZR_^WMc?mG=}?-9qDggUzf z`Jl%u8=DQrZ3o4n$d1>k&tzb*PoNro1=ot78bPQ(_v&&Hnjykcs4^Zz;^oP!4S3tM<6`iEH~u8Wt;6z**L&+$}hfbFneWS&ATVvp9x znyOwwCCHAG&>idYk7opod1uTg#D>nREk?*|Y8hAAKYf2XGZ_6S zb6B<9V8!RN$rd>=ZRtH?Vg?!Mr>zRvuw9&pKtd+IUV>1XkKL{&ucOkA?PwsNB|9b_ z!4y7-HDI*M4%Q1%*#oSEhazuIxxBobISm{IxIjYgdTa6r*hsGMvy_bR=uCZGcV}== z8j;s24c?YoP`wySMs4bQNLA~gK8KcNv3=u8RLG7E*M`4E+s?V}RsKoYxCv3i#!+yR zu5qs3$%yIN0q?H`81F{EX;yz;l~O7v@GSDu8;mTqju2R8w(eN@wEH}mIAKvWFmL`-la^rJiJJ8wMif;aW^-nmp%8QO_ zDk%tv9_f*s1aIG3kdX^W89CPoKC}$fEEuwh%UI{~#vOAd=~M`8aeH=!onRP0?s=)H zjm!Gw`Mg0mU@eU%Je1Jj%krAimoER&@^#*e&B$pAH~oSREB3l)rCaVXx3~6#^2lKplb*bDeTo9M zVx!Rpn(e2P!a}A`zr75I+uD+IRZ(Ws?SfiWLC*#X-Q{vE8^ekXy6220H*9~G3pSVR zRB|J80z+7cG>;aNRbTwhFlJ>5x<|k_HOLPWrT!Sk@xzpjjDm4T=G&;?ss>}j+-fU| zursl=p4dj=U9|ztnCApw4?H;X-KQrf9~^D;ITPo_1O>^__7$qnoOKFEBfqjds{frl zh5=h)k+IB<`#Mw?&SDq4!-nX8N)$nYqhd$oF;~Qf6#lqRj!{l!*@VOYy+AZZi5Z*G zp^YtKiRQ+HCt*PLoM_eMV%qA#LECWTKYsjEf0I98@I>{`2BRE_6Dj+9yCoOM(yHeH$TLPCwZ1A1kZ*=O$`Gj$qN zg5oOt)i|s2yL8C#FBVu41}+)uYfMaVuV)-XupupKoUj?)y%$wLAv*;k=zZLX#qREIX7sGr zfIrX%odx*yc2>Q4_zR1}Xl7Q1AP1+8$5)sn?bCg(cFk{k&j%hU)*b3@X=+m z=%LmSOQwVNNrSH&1D}43$a=LPZmafZA3~Otl=$1fdbJ|Fcw`D#_#YsbvU}P^U_y_8 z`<20-QyCf>dgN@|nAJIr-296tzV8RBY$dR|OP7u;c6@qH?8UN(IoSp%BeLWy5_(;^ z=O>;|(_jioNci!j^sg;Gcw@iw!H-}pNC3ok*nG(xO~)w#8KHpHkhs>)Y-13MJDY+4 zlsNsPwzjq|a6iPkydxB{n`G1Z^gxcG!6ZNBYH}QXm&B20(j)GUQV?{;trf8Gf%rl1 zXJ9Y_fZhfvG0xabm93&HJq7v3$zd75qTe!3x=VamkWbsA7mbn&d-sz)=?xwjXr%J# zh1a{U>Afs6g+p;#$@*jg)*-s*()W12oUv^JNWV)&OdO!888`0L$QrA(e|*0b!6hUp zn4_gUzd`A$Q>>RU?w*!0$x)vnpYX*=Lf_8*IcLC?e;|(C{)Geke9Z?GVmHz+h2$_~ zToq{cteBCE^wCmlXTw_g?Y+N5gIGX6O;|cOSNE1kPe5|x_V9|BV2(UxsHNjG(qYLmCz($`Yr>!?2Dm?@prGjub75hj^&+YB z=YjQRlEL4>r<}9p+$r^*KIfww9)I+_ z&pu8IL1g%<;Af?~alBWd#dgQ8rC9q`&k&#MUwd8Fme&QMj^0}EM9C!60X*#31HU(q zK@A=tbHnUlV23b}te&$wjdL%>?yWk6$p4IWCYYPnX{CfX$?ylD+fm=)&0`)YR)=HN z?&fRn>vZMEj;sRO9&V;*#fIt1IiJu50aMn{Lo+rsjKwHhL4N-jfrlm(d$xlWMmc5D zBR;K4zx&r@W=mqBA00gr4F)?EsyPLM?%&QXL{z!X`;?j=jCDq@wxZDBe@`ywyOs|E zR!?|(3H=uS*;IjKQSRsai~5X=!roq9N#p)sgG=Sk_%kx_`(!uMGvmH;=}RH7kv*>U zb6y_sNBqPCj<;!AM#$!um6H^V9w&cCD+2=qBmLQ>^maX&DI(MMeI}=-sHJSoqRbrp zM}V+n2&}-VtqV`_+n6l2a!>Ky6qnqs%UR$&gsO@yk;l1NPWl^bM3v+kh>$#`;yuba`^*t4M>`uJaWJ%N1qSsyf|8KPVBeZgZ87O zf4_Rdx;xi+CH`N#yXweoj=2<20#kZdO=jbr>oUAJo+ptki8YzyiC ztDF26P=T*KG5_3`B?Vj$2T+k<|Fn`(+pMdnZfKwAcg2)(adS;fO>ceyOS>sdkDh`v zWtNlvYxbrvw?yNF>0ot`qXrpED4~bUlTL-;-TN+!4W|NfFYPw!c8x7XlT_-+N20N6 zGhXC;Hu2WtX54DFMk?g%6lf{m4edz)Stv30jnDONz79tN`d4Fx7KQ&9 zP6p{P56cP)QsTq+JT;9`al3D7B zF2xYu=ABKY(Tx&E{AIPI1CjmYkJopABe<3oEdcb{NGqww-zQ-;dBdd_9?Si(m8$+Z zZqhMLO8iR}$`=cJ+f{a2dWiN$b+fJb471khIeP?T0N&2($!6Mx4DJVJ0JV(PFMQMU z7(U2qa>bOkaorx|0~1lN^z1*a{P5sI!SS)NYc*+nU1WB+4{>C;-(I<=fli4WOG&m7 zo}^89xG=|$CQi?Ce6WDYB0Dx!9=DM&s$5-NRRdAMt75k~V%&R%vndY?4cI7+kFH00 zS?0)42|T8vf={1%wUm;r0BQaYxFSOX^cs+DBJ}sO5ukPjY!b)V;8$%kcWhBqd1MI5 z{27Si38Y@+z5M7<{&GqIc@`fXp3%DGjXZsfUJoSERePa&%nS_v5Gripf8>O6apumQ zv$6~LzNgK=YHwkJwF{cTl+K@Ya>#xo9?E)yhdT68CqAR6AikQjq}yaKAy|T8Nzk97 zB6T4QC?ze;wBPFIINJ7ceK|0k#gKc9?P$b1OM~B@;X#2XpI_t*b63=PG!!B53k7m= z-!%U{QRAe(Y=h4Z_$B8y&Xp2h_|Em;Y#V+3BK3^@MolC3KBu)V1?rks?;>X35!)5| z{WP8+Lclb8hGQC&mFexi--6S9*{0?%lmPE1fxR1Cf0g6|-Ghn@xW0ne8i>M-;heqM z!rpe;&_!Bm*WsCOBcl9RH|SMIs@jr1cY7sr4xgokk58_JEp%`p(U6+T%>`@`rI1>V z7RXXk&%W5jM)0hGNbDUY94nK-jngmK&dWuV4DrC?r;uB%$*s-fFBjyV+b4wuq9RY8 z{BU~v_TU)gU!rly=HV0+6u)u(VuIaG9vN<(T#m#lwU75A=;#N{c@kcX`J`9}wtBEQ zxPAKGjmmPKaFcD}uv9KG&)C#~){ngCZ@B708mk`MGaZ+r`=}sBiUJCnI8vsBydi%! z9%0KnFtd;UYqu$0%;-PzmQW8fmg7hV*vD}}UiosJ~~7>Nmdf4-3?jUK->UNX zsAk@8tVdD4W<4UJE9!&Z(|YH{RtHE1$JP?yuNb({Z$Obit077`D8_WrpZM`L@Ag1=XHFpb#AB%N*ork&YQFa}iFCRtiPB&(Fj;bm&r z^#Swt3@F(={GCnY$JZcC|Ez5M(SedI&wC$nuEh=xWD0|&g~j6YkneJWLPDq1BE8Zj zR}B-uJC=Z&gg2UeB?q|GOI;HC50qHB9|Gy+!DrGB?aX~Q^ZJ*w)@i<&HHuPVpJ=|k zPt#PEA{XccS7%$H;NI&v^jEYr*{z31|Ds`7wYP?10?2RgNU}1cq5!G-l5rEMx25kU z<$9Ur>gsA2H_ZEPLOs3iG3CO>uly(T#hG!~^c~hY%A&U+F?OFBomOio!V?DIQ5xb! zZ1~lmSDj{;Ehvk|u^RLHya+6=+MzOFA(3@_k%-9h z#6E;~+7b{D%$RO^9BM{BLDc#{t_L>@;6C!Ub&b-0kW+XJ4yW0=lh+lyrIBy$Iq+$N z)F)0(+&aAKKW9Pek9oOyd9^{0ICxXOzQHO3R?#pBl^8-zv1?D1b4aCnesSJ(b9;sw zIob@gNzM$~2<(n+okR?NRchlsUMNJJD6mlvo&LmqOThzUCjF>z_4mX(@pBazFp&Go zcS;O_SkUaHuJG@p@#Sd&gD)J)?U4V1%WDTo%2^F z-CC&0_qV38bH=( z<69@Mt4qF#RSf*|ztS~zE4k4c01hNlOK48mcgJ0j{kEU9xVR`4G~l0k?;8}}a95uW z6~XO@fj;$X z%no0tEto+v57IvWx2wfAd1*?W&-~9B{eLGUHF{WY4r@ zi01wL9|Apo?dYPutU0d+q#u2_heyD(l+{2ZdARD5q$pj#bLT;t_zV};bW;3u=|DuP zcg%s#fKq}^#F-IoAqVED8y+6+m1%8U;e5J1X$bj;TR>2e=uG+^wi7DXLLJ^omT0Y- zhd7kKhpYqR9N}<`M~yfI1vfsY+ZUmLNAoG^o|fTxQb9!Y;v&kplQhS3AX!BVcFO|q zi!s5kRakIX2(74_jWbAIzC)uWIO+qb27h>(S~TkJ`}Y!GGI>o>pm6(XKS`pEnTqL6 zc&h;cqTWb@){X^%JhjjMLW>j?1A`Qi_KWRjTS;{;;ux@ymjo4ozWi%iHbAgOkQCMGR-Gbc@hhB@sLz9;$krYt2}6)3;9iIXvAQO#hq9QjDe(JJpld^p{kb2JyvsP(LL8OD2Ce(}WgyLfHB#oJ98D zZ3O|lTQXD?d_YA-rNPV1&5hTnxp{e>rve4}a&LeklN(-jX3}T$dlXnt;xZegk7{9w zzC*;?W%7G7wO~tgAxz7rq5*k1Q~c8N3VY7Q&J-UDum1H@K|w*go8&O{rDg^yOI_tn z4p@8l`q5s4%jz#mMEG|fK8b4aI8f-ENMvOVXZ3u@U+fPmWFOrAbMb>rX9D>p1M|5% zz*U_Gfc%dX(bXmRsavDjoyoua514_{U(_P-dijz`^vRRM_aM4RypV?=Rb~XV3MBBX z?}okA>2gnM&=He$dI$wFJq!B5xux5xHB~GUG5_oH0FYj1efFvN11LC^|BT*W)cOxm z)JT@CUxt3)LpGj~Ngqy>TQY#4*oRDyhn5vXHoji3U#|{;JX%I*n!#R`(?({(|3A6* zAgcTwscaQa)ZQ77dDHdtiHk1zE$dYfOw|TSN~a=YZ3z8EnD@s}zAF0@zfBOZnYXT7 z#5H@CBBPYb3)z5NG6hNw^$0@RjhEb1oAA8DT6@>qcKs<5zG+LoW-^XOrFF}XMgpF} ze3P4~9|g?hZQSr-zIL8J!CEzE>wnWM(7VO|EQ0ygk|W$esFCLhU?Y&xTpN}12iJ#q;H1zL?YRV z7h-XiU7v0$3km{Tvf<<1r9iQ|y#PW@(+45Xa}Y6jDBB(VKz8l}Qj`!FkkLG*b;o=Y z;xk+?yg)!a8{DmDW5wC1^vtYMeu-VkHjwIeH(M%lnx5) zkoUYg9PjGtT1VqTnV1xW#K00Geqq2Xe+54$Z zpQ1nke@|eKqCs5}AHT@v)ACw>%AF&ag1F0M`(@GWfd^9t=?!0ypeqMmhBM5SY%*)MB07&xCbUXvY88o;*Wb)6!pbR5yC0+(Uf0UOz zH%K#>{n5EzC=n_V*jAx{Uiv9%#x0|jG66bc1>mtDpzBE>+(8p&A+CyB3)U8M2pOo~ zc(CV%ajQJC-Hs41ONe-2%sL4e)e7lv6Q>V9a;7yscN|NC*tgCwxA(9ojz@&5x?fbV7sRI{hS4`sbjsNA~7!rsh>dwv;dW zJ`4?5eA!Cb?E={SwQRS#({$BNb78?>&Jb=A*>2FWeo#tJ<*;l^N;lhR2H!&P@$sbF;6ECax|69CeBkeyJx zzwORAWOHmFKuuUFu`$Gp`fmu=K7HXwD;q^*A@DZ+{_)sbW!+Imn7zlZr9}|AK2^I~ z#WEeA^%Oyut~@U;%#3rf=p(oJ-JF{HY`JL_O7%YSf9$Jec`tZv;L931efD7r#<)kPBQn0@Lb{zGuh z!lR-qM>1ZPcR#yYU80exZBF-t1`Uji>ijMaS2KVym0_1BHH(>3Q3F7Nu(0Ed2*$c3 zKk?iI+Z-djI1)|dtVldq0UZUw+u*WT3jgd#fPpS!?L?CleJYHKvDd(O&Hv)%5T^=$ zT9jic7GL6)G`>xA2fX!o&r({mk~LK`5AvWm6sm~P=cYhD081EK9I=kByg@sk*&4} ziq@hI)!u*mdZzq+gp4drc?<#i#7Bx1Y0c%nB>v_(8v)6g9sm7Z1G_;6<8Z6@JN%1F z^W`A&sh5|R7a*9>dqbMz_9pmp6}!=Z>-GqQHRoJGT4lZLcaYLfkXCZwO~2gaY60qE zv1v4@3iGAZb~MK>_hENsNo5yRq|uIGvFSu>$lXT*Npd6ztcy z$vUB0W>oBP3zUTnz%vYyx`PzJiR^| zxH|+{{t{-=59S+V-a4h@yfdKO@`L_Dv0bTgYj)V9doNd5trqZGZIClO2ByotC1_h< zYNN+3+x!VzPAEt_#skLageyKXBPqv8?1byU*ZCPv2s8Xt(cf%=1-FRuOXbUjcM2%K ziR0@meP5aI&A=!tqoWV8TMoC2{!;(cNcN8@gVpwnsPgHka@-sN1@@1IeuG7l-44Rj zg*qI)dOn0Akv1Zv?H2sZXqh13xVqF4xd-CP46}fK{4)&oZIi1qin4Dxa@;^fBg4?Z z;5;|S;ZV|bDZi!4cf2SPcrJ@HY|QNo4-}MzqKf2c%T2*9JnNIABz5rM(G>?VF5ZlH zHO&SSIAiWNdt~y4r04t!?4ytzl_hgRuLOZL3x9b<1{E3k1Q%pfIxYn$%KC8nFDfw0 z`%AY1-$&SPdiWbR3$fj6u>9Xy@?7vx*lSaOdV@vA0r|P42JFuwohk+CVfkYKV zHi7qL4I0fds;72;u5}hO_&T0{l{o>o@b1x<9^ZdS`(hBq2`86-xS#a@1*-{E_CNPd zFjlQrtEZNhmcF#OxHu1Cj~6z-bDj{lyyTpJzE0xu<;x#bRrOQbwrzW?zrX)ci-!ZN zi1>k0nyb}nXIEEOzhm(*&#K^oPjZY5=V2XYH%t2~#IM5ED(NF*|-s_Gyi zL?dR&N0pf=P?Ry2Z!{XqhG8sMDwXT`e12|qb@i^$Pf^1a4&qVY=kG>x=h`D+5KC=_!29)?lZq&@hUC&=0XMJR+FSaX+79P1DlD z!^1C0BocdLvDgk(Rfkkn9aI!0Cd_dSCXr6`0>ZJbkO#73=*mn0&%{XV4lu-@6T?NQtz-`@!i?2Q2uh(ysN~Nm{ z3kx&UEX^hIyJ*pI@7pSDBcLMniO-1}4<{`iKA1=(_C%x69g#?c^KhRfm?NBr8DnL` zFjg%dUZ~Y-H`mtI<^%(!8$6tbz2=F&T|39Q${+8+J*3Lj+UUH zMx#-=bLUPTz-c(WBW_xzS0DJjhVWCi0L8q)BAk2dfX(n?j+Zi5z~$QhHn*x;N%zIdwEa}{emu5M`IIsUQuA<=FL z;j$2xKU@WNl_pN`d=BJ~DJuhFf8~bM2N_&4q)gVDR^fb5?@w{%Y5C zEGwUGL3N!%UL4A>tF6^xTfm|~;?~u`}S^p?8d{QV#;|zq|>mnOI#O4{_QAkq#Xe) zyU*mDt9CY^MQvT&Yj8byUEJq_%>beTz$evDyAriCvAyQR2?C$@5wP7Dq5!VKcD+P} zOC&BgjY=ja(He6*s;foI>cH}Kn+L8%b3t2&%^*bhyXcYfsBIeBpr=UTL!+%CBg0-p;}sKNR-SN_@!yl@i$qB7X_oJ#X4YWIAR;g&Fg^P9>G zNT{|2eiYxuX0Cp+a~@pwZqkWf}8MIPq! z%;zv@@81pLym1~9t1Gs}zkveS#d@m4W-RW44r~23Uki7QDaf*t8IdzUPym;g`@prB zg)nAAdR8Q^D9p*{f{SZ+;)c9iD8HKkmWfJ?=xe$+)+2mfgkH_+5C2`>!*_F&i3&m6 z)~`o@v{!6n0|@#EHiCb0W34HxZv>L`8`d~ zi?sD2(wrCFhlN}J#Ch$=twO%d~Vacvv*lylA>DLn-uO z3@#tuO~|a502zD+AoM|;(6D`Q`>=cw@LhPMcICxD{@qI<5kDN)TxA0DB&2S^We&yE z94}Xp*x!56FZug=&9R^RO8(B`+_ucK*O;V*MV_m67JjjR{HNT%bQ|YI81=jubm@6Qd=Dg6-$8OH6Pk-e;bfxWk)n12u?}l!#J-~gS z1rLE2#3yolB5wo=v#{6z9`;--fqTxwU0WO57Ql{&?Ln;zUDEpRNqa8s`Yk?wgeCtB zMMxWgFmt#G`sKwqUW>ig&vnmpx9EPo{RjiEzHU^zesi-yO2)ddS?HjPeen`(Ud90b zxgl%NcRLTSTY0??>*{wcTt2EFVVVEveSo&m$s7Z{dG6uGg#bReuWL|`hi&u(a1TB7 f&_fSB+z0&sma31VUwVfQ00000NkvXXu0mjfSI99X literal 17670 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 Date: Thu, 23 Nov 2023 23:53:22 +0100 Subject: [PATCH 02/14] Update alpha version warning, and drop the translation --- novelwriter/guimain.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/novelwriter/guimain.py b/novelwriter/guimain.py index defb0fe5..b7eb97ca 100644 --- a/novelwriter/guimain.py +++ b/novelwriter/guimain.py @@ -345,11 +345,11 @@ class GuiMain(QMainWindow): logger.debug("Ready: GUI") if __hexversion__[-2] == "a" and not CONFIG.isDebug: - SHARED.warn(self.tr( + SHARED.warn( "You are running an untested development version of novelWriter. " - "Please be careful when working on a live project " + "Please be careful when you are working on live projects " "and make sure you take regular backups." - )) + ) logger.info("novelWriter is ready ...") self.mainStatus.setStatusMessage(self.tr("novelWriter is ready ...")) From 74c5a66a4cc8274bd27b6b0d580b38ea1807788f Mon Sep 17 00:00:00 2001 From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com> Date: Sat, 25 Nov 2023 16:24:27 +0100 Subject: [PATCH 03/14] Fix typos in changelog --- CHANGELOG.md | 86 ++++++++++++++++++++++++++-------------------------- 1 file changed, 43 insertions(+), 43 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 39b57f01..2827210b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -32,14 +32,14 @@ final release. that tag will appear in the menu. On selection, it will create a note in the first root folder of the correct kind, and set the title and tag to match the undefined reference, making it instantly defined. Issues #1580 and #823. PR #1582. -* Shortcodes have been added to the Document Editor. Shorcodes are HTML-like syntax, but uses +* Shortcodes have been added to the Document Editor. Shortcodes are HTML-like syntax, but uses square brackets instead of angular brackets. So `[b]text[/b]` will make the word "text" appear as - bold. Shortcodes currently support bold, italic, striketrough, underline, superscript and + bold. Shortcodes currently support bold, italic, strike trough, underline, superscript and subscript text. The first three are complimentary to the Markdown-like syntax that. The benefit of the shortcode emphasis syntax, however, is that it does not care about word boundaries, and can therefore be used any place in the text. Including in the middle of words. Issues #1337 and #1444. PRs #1540 and #1583. -* A show/hide toolbar has been added to the editor where toolbuttons for formatting options are +* A show/hide toolbar has been added to the editor where tool buttons for formatting options are available. The toolbar is hidden by default, but can be activated from a three dots icon in the top left corner of the editor. Issue #1585. PR #1584. * Build Definitions in the Manuscript Build tool can now be re-ordered, and the order is preserved @@ -105,7 +105,7 @@ changes have been fixed as well. See the full changelog for more details. Qt 5.15.8. In particular, it is possible to drop items on the root level, and it's possible to move root items to other locations. Neither should be possible and will severely mess up the project if done. Issue #1569. PR #1570. -* Add a custom autoscroll feature when dragging an item in the project tree to near the top or +* Add a custom auto-scroll feature when dragging an item in the project tree to near the top or bottom. This is actually a default feature of the tree widget in the Qt library, but this too is broken in some versions of Qt 5.15.x. The default feature has been permanently disabled and replaced by a custom written feature that behaves similarly. Issue #1561. PR #1571. @@ -327,7 +327,7 @@ This is a patch release that fixes a few issues and adds a Japanese translation. The issues were mostly related to spell checking. In particular, issues with finding the word boundary when using underscore characters for italics markup. These issues should now be resolved. -In addition, escaped markup characters are now renderred properly in HTML and ODT build formats. +In addition, escaped markup characters are now rendered properly in HTML and ODT build formats. A few usability improvements have also been made. The Add Item menu in the project tree no longer shows the options to create Novel Documents when an item in the tree is selected that cannot hold @@ -487,7 +487,7 @@ This is a patch release that fixes a few bugs and usability issues. The editing importance labels in Project Settings should now be a bit more intuitive. Opening a document from the Outline View that is already open in the editor should now switch to the editor view. The convert folder to note or document feature in the project tree has also been fixed. Some icons have -been updated and a renderring issue with one of them fixed. Chinese, Norwegian, US English, German +been updated and a rendering issue with one of them fixed. Chinese, Norwegian, US English, German and Spanish translations have been updated as well. A new credits tab has been added to the About dialog box, replacing the Credits section on the main About tab. @@ -757,7 +757,7 @@ opened, a request to update the file format will show up. PR #1212. * The GUI theme now determines which icon theme is to be loaded. It is no longer a separate setting. The icon theme can also be reloaded without restart. Issue #1172. PR #1212. -* The block formating features in the Format menu now also works on empty lines. Issue #1178. +* The block formatting features in the Format menu now also works on empty lines. Issue #1178. PR #1214. * There is now a Format menu entry and shortcut code for synopsis comments. Issue #1177. PR #1214. * The split document dialog now has the option to move teh source document to trash. Issue #1179. @@ -794,7 +794,7 @@ opened, a request to update the file format will show up. correct type information. PR #1197. * Saving and loading of XML data is now handled by a separate set of reader and writer classes. The reader class is capable of reading all file formats that have been used thus far. The various - data classes have been improved, and a new XML file formart version 1.5 added. Issue #1189. + data classes have been improved, and a new XML file format version 1.5 added. Issue #1189. PRs #1221 and #1232. * The index is now automatically rebuilt when the project file format is updated. Issue #1235. PR #1236. @@ -987,8 +987,8 @@ final release. setting is preserved. Previously, the Importance setting would overwrite it during the conversion. PR #1030. * Item labels, Status labels, and other labels on the GUI are now run through a "simplify" function - before being accepted. This functions strips out all whitespaces and consecutive whitespaces and - replace them with single plain whitespaces. This is a safer format to store in XML, and also + before being accepted. This functions strips out all white spaces and consecutive white spaces + and replace them with single plain white spaces. This is a safer format to store in XML, and also makes sure there aren't invisible characters floating around in the labels. PR #1038. * Due to the changes to how drag and drop works, there are no longer any restrictions on folders and documents. Only root folders remain restricted in terms of moving. Root folders can only be @@ -1494,7 +1494,7 @@ checking enabled if there is something wrong with the spell check package. **Bugfixes** * When the HTML converter replaced grater than or smaller than symbols with the corresponding HTML - entities. the poisition of the formatting tags following in the text would be shifted, but the + entities. the position of the formatting tags following in the text would be shifted, but the positions were not updated. This is now solved by updating these positions when such a symbol is encountered. This issue has been backported from 1.6 development. Issue #929. * If the pyenchant package is installed, but the underlying enchant library is broken in one way or @@ -1659,7 +1659,7 @@ backups. the case. Having two alternative spell checkers complicated the code a great deal, and the internal spell checker also required full word lists to be distributed with novelWriter. PR #875. * Added a setting in Preferences to change how the word count on the status bar is calculated. The - new setting allows the project notes to be filetred out, leaving only the word count for novel + new setting allows the project notes to be filtered out, leaving only the word count for novel files. Feature request #857. PR #882. **Installation and Packaging** @@ -1678,7 +1678,7 @@ backups. **Internationalisation** -* The US English and Nowregian translation files have been updated. PR #884. +* The US English and Norwegian translation files have been updated. PR #884. **Documentation** @@ -1709,7 +1709,7 @@ careful when using this version on live writing projects, and make sure you take **Internationalisation** -* The US English and Nowregian translation files have been updated. PR #870. +* The US English and Norwegian translation files have been updated. PR #870. **Documentation** @@ -2297,7 +2297,7 @@ buttons were blocked when they shouldn't. This release resolves these issues. This release is mainly focused on the Build Novel Project tool. Completely new export classes have been written to support Open Document and Markdown exports. In addition, the way document layouts are handled have been automated a little to assist the user in keeping header levels and document -layout flags in sync. The third new additoion is the ability to record and log idle time during a +layout flags in sync. The third new addition is the ability to record and log idle time during a writing session to improve the writing statistics information as requested by several users. Finally, it is now possible to directly edit the project dictionary via a new, simple GUI dialog. @@ -2390,7 +2390,7 @@ _These Release Notes also include the changes from 1.2 Beta 1 and RC 1._ the index on next load. For earlier versions of novelWriter before 1.1.1, it would also cause a crash. Invalid keywords are now rejected during indexing. Issue #688. PR #689. * The "Empty Trash" option was no longer working due to an earlier fix that added a requirement - that the project tree has focus to allow the emptying to procede. Since the Empty Trash feature + that the project tree has focus to allow the emptying to proceed. Since the Empty Trash feature opens a dialog, the tree loses focus, and the deletions are therefore ignored. The focus check is no longer considered when emptying the trash. Issue #701. PR #702. @@ -2450,7 +2450,7 @@ _The Release Notes have been moved and merged into the 1.2 RC 1 notes._ * A full Markdown exporter has been written and added for the Build Novel Project tool. This too replaces a Qt feature that was used to save the content of the preview window into a markdown file. The exporter allows for both standard markdown and GitHub flavour markdown. The only - relevant difference being that the latter allows strikethrough text. Issue #617. PR #650. + relevant difference being that the latter allows strike through text. Issue #617. PR #650. * The Build Novel Project tool now has a "Line height" property that is applied to the preview, and to the HTML5 and Open Document export formats. Discussion #653. Issue #654. PR #660. * The Build Novel Project tool now has an option to convert Unicode characters to HTML entities on @@ -2531,7 +2531,7 @@ but is still lacking in functionality compared to Enchant. exclude negative word counts is active. Previously, the entries with negative counts were filtered out, but the change in count was still applied to the next line, altering the value. Now, the GUI will instead just drop the lines that are negative and keep the other lines - unchanged. This is more intutitive, but it also means that the total count now longer matches the + unchanged. This is more intuitive, but it also means that the total count now longer matches the sum of the lines. PR #659. * The keyboard shortcut for deleting entries in the project tree has been changed from `Ctrl+Del` to `Ctrl+Shift+Del`. The `Ctrl+Del` shortcut is thus free to be used exclusively by the editor to @@ -2544,7 +2544,7 @@ but is still lacking in functionality compared to Enchant. given a significant speed improvement by caching the imported dictionary as a Python `set` instead of a `list`. The `set` has a hashed key lookup algorithm that is significantly faster. PR #668. -* Sessions shortar than 5 minutes, and with no word count changes, are no longer recorded in the +* Sessions shorter than 5 minutes, and with no word count changes, are no longer recorded in the session stats log file. PR #685. **Installation** @@ -3009,7 +3009,7 @@ issues are discovered, the next release will be the final version 1.0 release. * The tests for the core classes of novelWriter have been completely rewritten. Every class or source file of the core functionality (everything handling the actual project data and documents, - as well as the meta data) is now covered by its own testmodule with a 100% coverage for each + as well as the meta data) is now covered by its own test module with a 100% coverage for each module. PR #512. * Likewise, the base tests have been rewritten to cover the `Config` class, the `main` function that launches the app, and the error handling class. The structure matches the core tests from @@ -3041,7 +3041,7 @@ The "Typewriter Mode" of the editor has been improved. Essentially, this feature scroll. It tries to keep the cursor stationary in the vertical direction, and will try to scroll the document up when the cursor skips to a new line while typing (or down in case of backspaces). This is similar to the way a typewriter scrolls the paper when hitting the return key. It improves -the writing experience as the current active line will stay at the same eye heightlevel on the +the writing experience as the current active line will stay at the same eye height level on the screen. Previously, the feature would lock the cursor to a given vertical position defined by the user. @@ -3089,7 +3089,7 @@ Switching main GUI theme still requires a restart. handled by the various tools, but they shouldn't even trigger when there is no project or document to perform the action on. PR #492. * Clarify the message of the Close Project and Exit novelWriter dialogs. Previously, it may have - seemed to some users that clicking "No" would allow the closing to procede without saving + seemed to some users that clicking "No" would allow the closing to proceed without saving changes. This is not true as changes are saved automatically when editing a project. The dialog text should now make this clearer. Issue #494, PR #495. @@ -3134,7 +3134,7 @@ Switching main GUI theme still requires a restart. **User Interface** * An option has been added in Preferences to hide horizontal or vertical scroll bars on the main - GUI. These optons will hide scroll bars on the Project Tree, Document Editor, Document Viewer, + GUI. These options will hide scroll bars on the Project Tree, Document Editor, Document Viewer, Outline Tab and on the controls of the Build Novel Project tool. Scroll bars take up space, and as long as the project doesn't contain very long documents, scrolling with the mouse wheel is enough. The feature is of course entirely optional. PRs #468 and #469. @@ -3154,7 +3154,7 @@ Switching main GUI theme still requires a restart. * When applying a format from the format menu to a selection of multiple paragraphs (or lines), only the first paragraph (or line) receives the formatting. The editor doesn't allow markdown formatting to span multiple lines. Issue #451, PR #475. -* The syntax highlighter no longer uses the same colour to highlight strikethrough text as for +* The syntax highlighter no longer uses the same colour to highlight strike through text as for emphasised text. The colour is intended to stand out, which makes little sense for such text. Instead, the highlighter uses the same colour as for comments. PR #476. @@ -3305,7 +3305,7 @@ Switching main GUI theme still requires a restart. **Code Improvements** -* Minor improvemenmts have been made to the core project classes to improve encapsulation and +* Minor improvements have been made to the core project classes to improve encapsulation and better ensure consistency between the different data structures that store the novel project in memory. PR #447. * Some unused or redundant code has been removed, and in some places, functions have been merged to @@ -3322,9 +3322,9 @@ Switching main GUI theme still requires a restart. **Bugfixes** * If the horizontal scroll bar appeared at the bottom of the document editor or viewer, for - instance if a long, un-wrapable line was entered, the scroll bar would sit on top of the document - footer. The footer bar now properly moves out of the way when the horizontal scroll bar appears. - Issue #433, PR #434. + instance if a long, un-wrappable line was entered, the scroll bar would sit on top of the + document footer. The footer bar now properly moves out of the way when the horizontal scroll bar + appears. Issue #433, PR #434. **New Features** @@ -3446,7 +3446,7 @@ Switching main GUI theme still requires a restart. leading line break. This was previously handled entirely by the Qt library, which does this for some reason. Issue #395, PR #405. * A chapter heading in a file with a different layout than `Unnumbered` can now also be flagged as - an unnumbered chapter heading by adding an asterisk to the begfinning of the title text. This + an unnumbered chapter heading by adding an asterisk to the beginning of the title text. This only affects the number assignment and counter when running the Build Novel Project tool. The rest of the app ignores the asterisk. Issue #402, PR #406. @@ -3461,7 +3461,7 @@ Switching main GUI theme still requires a restart. create a copy of the example project from the source code. Either from the `sample` folder in the source, or from a `sample.zip` file generated by the `setup.py` script and saved to the `nw/assets` folder. PR #366. -* When the user clicked cancel on the colour dialog in Project Settins, the icon would be reset to +* When the user clicked cancel on the colour dialog in Project Settings, the icon would be reset to black. Instead, the colour should remain unchanged. A check that the user actually selected a colour has now been added. Issue #395, PR #403. @@ -3537,7 +3537,7 @@ planned by GitHub. See their [notes](https://github.com/github/renaming) for mor [novelwriter.readthedocs.io](https://novelwriter.readthedocs.io/) has been rewritten. It was drifting out of sync with the development of the code. In addition, many improvements have been made to the reStructuredText formatting of the documentation source by providing better - cross-reference linking and highlightings. The main repository README file has been updated to + cross-reference linking and highlighting. The main repository README file has been updated to match. PRs #375, #382, and #384. * The main `setup.py` script has been updated to also build documentation for the Qt Assistant when given a `qthelp` flag. The compiled help files are copied into the `nw/assets/help` folder, and @@ -3635,7 +3635,7 @@ longer be read by an earlier version due to the change of how autoReplace settin has been removed from the main window status bar. PR #348. * The document editor footer can optionally be hidden in Distraction Free mode. PR #351. * The Italic and Bold menu entries have been renamed to Emphasis and Strong Emphasis, which is more - in line with what they represent in Markdown and HTML. They are still renderred as Italic and + in line with what they represent in Markdown and HTML. They are still rendered as Italic and Bold in the document viewer, but the HTML export is using the `` and `` tags. PR #350. * Due to several issues with the formatting of emphasised text using `*`, `**`, and `***` wrappers, @@ -3648,9 +3648,9 @@ longer be read by an earlier version due to the change of how autoReplace settin for emphasis and strong is commonly recommended when writing Markdown anyway, so it is a sensible compromise between speed and flexibility. This PR partially reverses PR #310. Issue #353, PR #355. -* The syntax highlighter now properly highlights overlapping formattings, including emphasised text +* The syntax highlighter now properly highlights overlapping formatting, including emphasised text inside of highlighted quotes. PR #355. -* The colour highlighting of emphasis, strong and strikethrough, can now be switch off in +* The colour highlighting of emphasis, strong and strike through, can now be switch off in Preferences. The syntax highlighter will still apply the italic, bold and strike effects. PR #357. * The project path in the Details tab of Project Settings can now be selected and copied to @@ -3681,7 +3681,7 @@ longer be read by an earlier version due to the change of how autoReplace settin visible in the tree. It is called when you for instance click the header of an open document. It was also activated when opening a document from the tree view with either double-click by mouse, or by using the Enter key. This meant that the tree view would often move, which made it hard to - mouse click on items after eachother since you ended up chasing a moving target. This feature is + mouse click on items after each other since you ended up chasing a moving target. This feature is now disabled for document open. In addition, the scroll into view feature has been added to the search/replace call to move into the next document when reaching the end of the current document. This was requested in Issue #332. PR #334. @@ -3692,7 +3692,7 @@ longer be read by an earlier version due to the change of how autoReplace settin **Documentation** * The documentation has been updated to clarify the correct formatting for italic, bold and - strikethrough formatting tags. Issue #220, PR #338. + strike through formatting tags. Issue #220, PR #338. ---- @@ -3711,11 +3711,11 @@ longer be read by an earlier version due to the change of how autoReplace settin * Underline text formatting has been removed. It is not standard HTML5, nor Markdown, and was previously implemented using the double underscore notation that in standard Markdown is - renderred as bold text. Instead, novelWriter now renders a single `*` or `_` wrapping a piece of + rendered as bold text. Instead, novelWriter now renders a single `*` or `_` wrapping a piece of text _within_ a paragraphs as italicised text, and a double `**` or `__` as bold text. The keyboard shortcuts and automatic features _only_ support the `*` notation. A triple set of `***` are treated as both bold and italicised. PR #310. -* Strikethrough formatting has been added back into novelWriter using the standard Markdown `~~` +* Strike through formatting has been added back into novelWriter using the standard Markdown `~~` wrapping. PR #310. * Added support for thin spaces and non-breaking thin spaces. PR #319. * The `Ctrl+Z` key sequence (undo) would not go through the wrapper function for document action @@ -3732,7 +3732,7 @@ longer be read by an earlier version due to the change of how autoReplace settin PR #309. * The text emphasis functions, either selected from the menu or via keyboard shortcuts, will now try to respond to the command in a more meaningful way. That is, the text editor will try to - toggle the bold or italics features independently of eachother on the selected text. A menu entry + toggle the bold or italics features independently of each other on the selected text. A menu entry to apply both at the same time has also been added. PR #310. * The document editor search tool has been completely rewritten. It now appears as a search box at the top of the document, and has a number of toggle switches added to it. You can modify the @@ -4102,7 +4102,7 @@ that I can spend more time using it than creating it. **Additional thanks** to @countjocular for PRs #173 and #174, and to @johnblommers for all the helpful feedback and issue reports for the new features added in this, and previous releases. -### Noteable Changes +### Notable Changes * The Timeline View dialog is now gone. Instead, the main window area has been split into two tabs. The first, the "Editor", contains the Document Editor and Viewer panels. The second, the @@ -4253,7 +4253,7 @@ helpful feedback and issue reports for the new features added in this, and previ **Bug Fixes** * Fixed an issue when closing the import file dialog without selecting a file, the import would - procede, but fail on file not found. The import is now cancelled when there is no file selected. + proceed, but fail on file not found. The import is now cancelled when there is no file selected. PR #149. * Fixed an issue with markdown export where it did not take into account hard line breaks. Issue #151, PR #152. @@ -4663,7 +4663,7 @@ This is the initial release of a working version of novelWriter, but with very l capabilities. So far, the following has been implemented: * A document tree with a set of pre-defined root folders of a given set of classes for different - purposes for novel writing. That is, a root item for the novel itself, one for charcaters, plot + purposes for novel writing. That is, a root item for the novel itself, one for characters, plot elements, timeline, locations, objects, and a custom one. * A plain text editor with a simplified markdown format that allows for four levels of titles, and bold, italics and underline text. @@ -4671,7 +4671,7 @@ capabilities. So far, the following has been implemented: * It also allows for keyword/value sets staring with the character `@`. These will later be used to link documents together as tags point to other documents. For instance, a scene file can point the keyword `@POV:name` to a character file with the keyword `@THIS:name`. -* The text editor has a set of autoreplace features: +* The text editor has a set of auto replace features: * Dashes are made by combining two or three hyphens. * Three dots are replaced with the ellipsis. * Straight quotes with your quote format of choice. From e216f80d64b758a6ed728069ff144fdc8455eb5f Mon Sep 17 00:00:00 2001 From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com> Date: Sat, 25 Nov 2023 16:27:40 +0100 Subject: [PATCH 04/14] Revert previous change of keyboard shortcut for item delete in tree made in #1590 --- docs/source/usage_shortcuts.rst | 30 +++++++++++++++--------------- novelwriter/gui/mainmenu.py | 2 +- novelwriter/guimain.py | 5 +---- 3 files changed, 17 insertions(+), 20 deletions(-) diff --git a/docs/source/usage_shortcuts.rst b/docs/source/usage_shortcuts.rst index db9382cb..0eea9175 100644 --- a/docs/source/usage_shortcuts.rst +++ b/docs/source/usage_shortcuts.rst @@ -46,21 +46,21 @@ Project Tree Shortcuts .. csv-table:: :header: "Shortcut", "Description" - ":kbd:`F2`", "Edit the label of the selected item" - ":kbd:`Return`", "Open the selected document in the editor" - ":kbd:`Alt+Up`", "Jump or go to the previous item at same level in the tree" - ":kbd:`Alt+Down`", "Jump or go to the next item at same level in the tree" - ":kbd:`Alt+Left`", "Jump to the parent item in the tree" - ":kbd:`Alt+Right`", "Jump to the first child item in the project tree" - ":kbd:`Ctrl+.`", "Open the context menu on the selected item" - ":kbd:`Ctrl+L`", "Open the :guilabel:`Quick Links` menu" - ":kbd:`Ctrl+N`", "Open the :guilabel:`Create New Item` menu" - ":kbd:`Ctrl+O`", "Open selected document" - ":kbd:`Ctrl+R`", "Open the selected document in the viewer" - ":kbd:`Ctrl+Up`", "Move selected item one step up in the tree" - ":kbd:`Ctrl+Down`", "Move selected item one step down in the tree" - ":kbd:`Ctrl+Del`", "Move the selected item to Trash" - ":kbd:`Ctrl+Shift+Z`", "Undo the last move of a project item, if possible" + ":kbd:`F2`", "Edit the label of the selected item" + ":kbd:`Return`", "Open the selected document in the editor" + ":kbd:`Alt+Up`", "Jump or go to the previous item at same level in the tree" + ":kbd:`Alt+Down`", "Jump or go to the next item at same level in the tree" + ":kbd:`Alt+Left`", "Jump to the parent item in the tree" + ":kbd:`Alt+Right`", "Jump to the first child item in the project tree" + ":kbd:`Ctrl+.`", "Open the context menu on the selected item" + ":kbd:`Ctrl+L`", "Open the :guilabel:`Quick Links` menu" + ":kbd:`Ctrl+N`", "Open the :guilabel:`Create New Item` menu" + ":kbd:`Ctrl+O`", "Open selected document" + ":kbd:`Ctrl+R`", "Open the selected document in the viewer" + ":kbd:`Ctrl+Up`", "Move selected item one step up in the tree" + ":kbd:`Ctrl+Down`", "Move selected item one step down in the tree" + ":kbd:`Ctrl+Shift+Del`", "Move the selected item to Trash" + ":kbd:`Ctrl+Shift+Z`", "Undo the last move of a project item, if possible" .. _a_kb_editor: diff --git a/novelwriter/gui/mainmenu.py b/novelwriter/gui/mainmenu.py index 9238d3f1..1fee411f 100644 --- a/novelwriter/gui/mainmenu.py +++ b/novelwriter/gui/mainmenu.py @@ -171,7 +171,7 @@ class GuiMainMenu(QMenuBar): # Project > Delete self.aDeleteItem = self.projMenu.addAction(self.tr("Delete Item")) - self.aDeleteItem.setShortcuts(["Ctrl+Del", "Ctrl+Shift+Del"]) # Latter is deprecated + self.aDeleteItem.setShortcut("Ctrl+Shift+Del") # Cannot be Ctrl+Del, see #629 self.aDeleteItem.triggered.connect(lambda: self.mainGui.projView.requestDeleteItem(None)) # Project > Empty Trash diff --git a/novelwriter/guimain.py b/novelwriter/guimain.py index b7eb97ca..2006213e 100644 --- a/novelwriter/guimain.py +++ b/novelwriter/guimain.py @@ -1164,10 +1164,7 @@ class GuiMain(QMainWindow): """Capture the closing event of the GUI and call the close function to handle all the close process steps. """ - if self.closeMain(): - event.accept() - else: - event.ignore() + event.accept() if self.closeMain() else event.ignore() return ## From 887b4379b2ee921b9b8df8bd022ede270781d78a Mon Sep 17 00:00:00 2001 From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com> Date: Sat, 25 Nov 2023 17:50:25 +0100 Subject: [PATCH 05/14] Add comment style processing to the index class --- novelwriter/core/index.py | 32 ++++++++++++++++++++++---------- novelwriter/enum.py | 9 +++++++++ 2 files changed, 31 insertions(+), 10 deletions(-) diff --git a/novelwriter/core/index.py b/novelwriter/core/index.py index 3c833b2c..2c76c3ae 100644 --- a/novelwriter/core/index.py +++ b/novelwriter/core/index.py @@ -36,7 +36,7 @@ from typing import TYPE_CHECKING, ItemsView, Iterable, Iterator from pathlib import Path from novelwriter import SHARED -from novelwriter.enum import nwItemClass, nwItemType, nwItemLayout +from novelwriter.enum import nwComment, nwItemClass, nwItemType, nwItemLayout from novelwriter.error import logException from novelwriter.common import checkInt, isHandle, isItemClass, isTitleTag, jsonEncode from novelwriter.constants import nwFiles, nwKeyWords, nwRegEx, nwUnicode, nwHeaders @@ -338,14 +338,9 @@ class NWIndex: elif line.startswith("%"): if cTitle != TT_NONE: - toCheck = line[1:].lstrip() - synTag = toCheck[:9].lower() - tLen = len(line) - cLen = len(toCheck) - cOff = tLen - cLen - if synTag == "synopsis:": - sText = line[cOff+9:].strip() - self._itemIndex.setHeadingSynopsis(tHandle, cTitle, sText) + cStyle, cText, _ = processComment(line) + if cStyle in (nwComment.BRIEF, nwComment.SYNOPSIS): + self._itemIndex.setHeadingSynopsis(tHandle, cTitle, cText) # Count words for remaining text after last heading if pTitle != TT_NONE: @@ -1269,9 +1264,26 @@ class IndexHeading: # =============================================================================================== # -# Simple Word Counter +# text Processing Functions # =============================================================================================== # +CLASSIFIERS = { + "brief": nwComment.BRIEF, + "synopsis": nwComment.SYNOPSIS, +} + + +def processComment(text: str) -> tuple[nwComment, str, int]: + """Extract comment style and text. Should only be called on text + starting with a %. + """ + check = text[1:].lstrip() + classifier, _, content = check.partition(":") + if content and (clean := classifier.strip().lower()) in CLASSIFIERS: + return CLASSIFIERS[clean], content.strip(), text.find(":") + 1 + return nwComment.PLAIN, check, 0 + + def countWords(text: str) -> tuple[int, int, int]: """Count words in a piece of text, skipping special syntax and comments. diff --git a/novelwriter/enum.py b/novelwriter/enum.py index c1d2aef4..1258e5e2 100644 --- a/novelwriter/enum.py +++ b/novelwriter/enum.py @@ -61,6 +61,15 @@ class nwItemLayout(Enum): # END Enum nwItemLayout +class nwComment(Enum): + + PLAIN = 0 + SYNOPSIS = 1 + BRIEF = 2 + +# END Enum nwComment + + class nwTrinary(Enum): NEGATIVE = -1 From 8ed85363e2162140c8790b419be6e4fe5b02ece1 Mon Sep 17 00:00:00 2001 From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com> Date: Sat, 25 Nov 2023 17:50:59 +0100 Subject: [PATCH 06/14] Add test coverage and update the highlighter --- novelwriter/gui/dochighlight.py | 16 ++++++-------- tests/test_core/test_core_index.py | 35 ++++++++++++++++++++++++++---- 2 files changed, 38 insertions(+), 13 deletions(-) diff --git a/novelwriter/gui/dochighlight.py b/novelwriter/gui/dochighlight.py index 6a77f267..b6850097 100644 --- a/novelwriter/gui/dochighlight.py +++ b/novelwriter/gui/dochighlight.py @@ -36,6 +36,8 @@ from PyQt5.QtGui import ( from novelwriter import CONFIG, SHARED from novelwriter.common import checkInt from novelwriter.constants import nwRegEx, nwUnicode +from novelwriter.core.index import processComment +from novelwriter.enum import nwComment logger = logging.getLogger(__name__) @@ -352,16 +354,12 @@ class GuiDocHighlighter(QSyntaxHighlighter): elif text.startswith("%"): # Comments self.setCurrentBlockState(self.BLOCK_TEXT) - toCheck = text[1:].lstrip() - synTag = toCheck[:9].lower() - tLen = len(text) - cLen = len(toCheck) - cOff = tLen - cLen - if synTag == "synopsis:": - self.setFormat(0, cOff+9, self._hStyles["modifier"]) - self.setFormat(cOff+9, tLen, self._hStyles["hidden"]) + cStyle, _, cPos = processComment(text) + if cStyle == nwComment.PLAIN: + self.setFormat(0, len(text), self._hStyles["hidden"]) else: - self.setFormat(0, tLen, self._hStyles["hidden"]) + self.setFormat(0, cPos, self._hStyles["modifier"]) + self.setFormat(cPos, len(text), self._hStyles["hidden"]) else: # Text Paragraph diff --git a/tests/test_core/test_core_index.py b/tests/test_core/test_core_index.py index 9e7856e8..d12ab318 100644 --- a/tests/test_core/test_core_index.py +++ b/tests/test_core/test_core_index.py @@ -29,9 +29,9 @@ from mocked import causeException from novelwriter.core.item import NWItem from tools import C, buildTestProject, cmpFiles, writeFile -from novelwriter.enum import nwItemClass, nwItemLayout +from novelwriter.enum import nwComment, nwItemClass, nwItemLayout from novelwriter.constants import nwFiles -from novelwriter.core.index import IndexItem, NWIndex, countWords, TagsIndex +from novelwriter.core.index import IndexItem, NWIndex, countWords, TagsIndex, processComment from novelwriter.core.project import NWProject @@ -1264,7 +1264,34 @@ def testCoreIndex_ItemIndex(mockGUI, fncPath, mockRnd): @pytest.mark.core -def testCoreIndex_CountWords(): +def testCoreIndex_processComment(): + """Test the comment processing function.""" + # Regular comment + assert processComment("%Hi") == (nwComment.PLAIN, "Hi", 0) + assert processComment("% Hi") == (nwComment.PLAIN, "Hi", 0) + assert processComment("% Hi:you") == (nwComment.PLAIN, "Hi:you", 0) + + # Synopsis + assert processComment("%synopsis:") == (nwComment.PLAIN, "synopsis:", 0) + assert processComment("%synopsis: Hi") == (nwComment.SYNOPSIS, "Hi", 10) + assert processComment("% synopsis: Hi") == (nwComment.SYNOPSIS, "Hi", 11) + assert processComment("% synopsis : Hi") == (nwComment.SYNOPSIS, "Hi", 13) + assert processComment("% Synopsis : Hi") == (nwComment.SYNOPSIS, "Hi", 15) + assert processComment("% \t SYNOPSIS : Hi") == (nwComment.SYNOPSIS, "Hi", 16) + + # Brief + assert processComment("%brief:") == (nwComment.PLAIN, "brief:", 0) + assert processComment("%brief: Hi") == (nwComment.BRIEF, "Hi", 7) + assert processComment("% brief: Hi") == (nwComment.BRIEF, "Hi", 8) + assert processComment("% brief : Hi") == (nwComment.BRIEF, "Hi", 10) + assert processComment("% Brief : Hi") == (nwComment.BRIEF, "Hi", 12) + assert processComment("% \t BRIEF : Hi") == (nwComment.BRIEF, "Hi", 13) + +# END Test testCoreIndex_processComment + + +@pytest.mark.core +def testCoreIndex_countWords(): """Test the word counter and the exclusion filers.""" # Non-Text assert countWords(None) == (0, 0, 0) # type: ignore @@ -1362,4 +1389,4 @@ def testCoreIndex_CountWords(): assert wC == 14 assert pC == 2 -# END Test testCoreIndex_CountWords +# END Test testCoreIndex_countWords From 6a6b05c3380e60c82447685b85995643e273002a Mon Sep 17 00:00:00 2001 From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com> Date: Sat, 25 Nov 2023 18:12:45 +0100 Subject: [PATCH 07/14] Update tokenizer and converters --- novelwriter/core/coretools.py | 7 ++++- novelwriter/core/tohtml.py | 9 ++++-- novelwriter/core/tokenizer.py | 41 +++++++++++++++----------- novelwriter/core/tomd.py | 4 +++ novelwriter/core/toodt.py | 10 +++++-- tests/test_core/test_core_tohtml.py | 20 +++++++++++-- tests/test_core/test_core_tokenizer.py | 14 +++++++++ tests/test_core/test_core_tomd.py | 8 ++++- tests/test_core/test_core_toodt.py | 14 ++++++--- 9 files changed, 95 insertions(+), 32 deletions(-) diff --git a/novelwriter/core/coretools.py b/novelwriter/core/coretools.py index 81803576..fd040461 100644 --- a/novelwriter/core/coretools.py +++ b/novelwriter/core/coretools.py @@ -406,6 +406,7 @@ class ProjectBuilder: chSynop = self.tr("Summary of the chapter.") scSynop = self.tr("Summary of the scene.") + bfNote = self.tr("A brief description.") # Create chapters if numChapters > 0: @@ -446,7 +447,11 @@ class ProjectBuilder: aHandle = project.newFile(noteTitles[newRoot], rHandle) ntTag = simplified(noteTitles[newRoot]).replace(" ", "") aDoc = project.storage.getDocument(aHandle) - aDoc.writeDocument(f"# {noteTitles[newRoot]}\n\n@tag: {ntTag}\n\n") + aDoc.writeDocument( + f"# {noteTitles[newRoot]}\n\n" + f"% Brief: {bfNote}\n\n" + f"@tag: {ntTag}\n\n" + ) # Also add the archive and trash folders project.newRoot(nwItemClass.ARCHIVE) diff --git a/novelwriter/core/tohtml.py b/novelwriter/core/tohtml.py index 7443ed34..219f6032 100644 --- a/novelwriter/core/tohtml.py +++ b/novelwriter/core/tohtml.py @@ -287,7 +287,10 @@ class ToHtml(Tokenizer): para.append(stripEscape(tTemp.rstrip())) elif tType == self.T_SYNOPSIS and self._doSynopsis: - lines.append(self._formatSynopsis(tText)) + lines.append(self._formatSynopsis(tText, True)) + + elif tType == self.T_BRIEF and self._doSynopsis: + lines.append(self._formatSynopsis(tText, False)) elif tType == self.T_COMMENT and self._doComments: lines.append(self._formatComments(tText)) @@ -454,9 +457,9 @@ class ToHtml(Tokenizer): # Internal Functions ## - def _formatSynopsis(self, text: str) -> str: + def _formatSynopsis(self, text: str, synopsis: bool) -> str: """Apply HTML formatting to synopsis.""" - sSynop = self._localLookup("Synopsis") + sSynop = self._localLookup("Synopsis") if synopsis else self._localLookup("Brief") if self._genMode == self.M_PREVIEW: return f"

{sSynop}: {text}

\n" else: diff --git a/novelwriter/core/tokenizer.py b/novelwriter/core/tokenizer.py index a4e7f61a..b5d0fd18 100644 --- a/novelwriter/core/tokenizer.py +++ b/novelwriter/core/tokenizer.py @@ -34,8 +34,9 @@ from pathlib import Path from functools import partial from PyQt5.QtCore import QCoreApplication, QRegularExpression +from novelwriter.core.index import processComment -from novelwriter.enum import nwItemLayout +from novelwriter.enum import nwComment, nwItemLayout from novelwriter.common import formatTimeStamp, numberToRoman, checkInt from novelwriter.constants import nwHeadFmt, nwRegEx, nwShortcode, nwUnicode from novelwriter.core.project import NWProject @@ -79,17 +80,18 @@ class Tokenizer(ABC): # Block Type T_EMPTY = 1 # Empty line (new paragraph) T_SYNOPSIS = 2 # Synopsis comment - T_COMMENT = 3 # Comment line - T_KEYWORD = 4 # Command line - T_TITLE = 5 # Title - T_UNNUM = 6 # Unnumbered - T_HEAD1 = 7 # Header 1 - T_HEAD2 = 8 # Header 2 - T_HEAD3 = 9 # Header 3 - T_HEAD4 = 10 # Header 4 - T_TEXT = 11 # Text line - T_SEP = 12 # Scene separator - T_SKIP = 13 # Paragraph break + T_BRIEF = 3 # Brief comment + T_COMMENT = 4 # Comment line + T_KEYWORD = 5 # Command line + T_TITLE = 6 # Title + T_UNNUM = 7 # Unnumbered + T_HEAD1 = 8 # Header 1 + T_HEAD2 = 9 # Header 2 + T_HEAD3 = 10 # Header 3 + T_HEAD4 = 11 # Header 4 + T_TEXT = 12 # Text line + T_SEP = 13 # Scene separator + T_SKIP = 14 # Paragraph break # Block Style A_NONE = 0x0000 # No special style @@ -461,17 +463,22 @@ class Tokenizer(ABC): continue if aLine[0] == "%": - cLine = aLine[1:].lstrip() - synTag = cLine[:9].lower() - if synTag == "synopsis:": + cStyle, cText, _ = processComment(aLine) + if cStyle == nwComment.SYNOPSIS: self._tokens.append(( - self.T_SYNOPSIS, nHead, cLine[9:].strip(), None, sAlign + self.T_SYNOPSIS, nHead, cText, None, sAlign + )) + if self._doSynopsis and self._keepMarkdown: + tmpMarkdown.append("%s\n" % aLine) + elif cStyle == nwComment.BRIEF: + self._tokens.append(( + self.T_BRIEF, nHead, cText, None, sAlign )) if self._doSynopsis and self._keepMarkdown: tmpMarkdown.append("%s\n" % aLine) else: self._tokens.append(( - self.T_COMMENT, nHead, aLine[1:].strip(), None, sAlign + self.T_COMMENT, nHead, cText, None, sAlign )) if self._doComments and self._keepMarkdown: tmpMarkdown.append("%s\n" % aLine) diff --git a/novelwriter/core/tomd.py b/novelwriter/core/tomd.py index d13e3b18..3187258a 100644 --- a/novelwriter/core/tomd.py +++ b/novelwriter/core/tomd.py @@ -170,6 +170,10 @@ class ToMarkdown(Tokenizer): label = self._localLookup("Synopsis") lines.append(f"**{label}:** {tText}\n\n") + elif tType == self.T_BRIEF and self._doSynopsis: + label = self._localLookup("Brief") + lines.append(f"**{label}:** {tText}\n\n") + elif tType == self.T_COMMENT and self._doComments: label = self._localLookup("Comment") lines.append(f"**{label}:** {tText}\n\n") diff --git a/novelwriter/core/toodt.py b/novelwriter/core/toodt.py index aec5eefb..26cc76b2 100644 --- a/novelwriter/core/toodt.py +++ b/novelwriter/core/toodt.py @@ -481,7 +481,11 @@ class ToOdt(Tokenizer): pFmt.append(tFormat) elif tType == self.T_SYNOPSIS and self._doSynopsis: - tTemp, fTemp = self._formatSynopsis(tText) + tTemp, fTemp = self._formatSynopsis(tText, True) + self._addTextPar("Text_20_Meta", oStyle, tTemp, tFmt=fTemp) + + elif tType == self.T_BRIEF and self._doSynopsis: + tTemp, fTemp = self._formatSynopsis(tText, False) self._addTextPar("Text_20_Meta", oStyle, tTemp, tFmt=fTemp) elif tType == self.T_COMMENT and self._doComments: @@ -552,9 +556,9 @@ class ToOdt(Tokenizer): # Internal Functions ## - def _formatSynopsis(self, text: str) -> tuple[str, list[tuple[int, int]]]: + def _formatSynopsis(self, text: str, synopsis: bool) -> tuple[str, list[tuple[int, int]]]: """Apply formatting to synopsis lines.""" - name = self._localLookup("Synopsis") + name = self._localLookup("Synopsis") if synopsis else self._localLookup("Brief") rTxt = f"{name}: {text}" rFmt = [(0, self.FMT_B_B), (len(name) + 1, self.FMT_B_E)] return rTxt, rFmt diff --git a/tests/test_core/test_core_tohtml.py b/tests/test_core/test_core_tohtml.py index b0776ef6..7f616155 100644 --- a/tests/test_core/test_core_tohtml.py +++ b/tests/test_core/test_core_tohtml.py @@ -149,7 +149,7 @@ def testCoreToHtml_ConvertFormat(mockGUI): "

Line one
Line two
Line three

\n" ) - # Synopsis + # Synopsis, Brief html._text = "%synopsis: The synopsis ...\n" html.tokenizeText() html.doConvert() @@ -163,6 +163,14 @@ def testCoreToHtml_ConvertFormat(mockGUI): "

Synopsis: The synopsis ...

\n" ) + html.setSynopsis(True) + html._text = "%brief: A description ...\n" + html.tokenizeText() + html.doConvert() + assert html.theResult == ( + "

Brief: A description ...

\n" + ) + # Comment html._text = "% A comment ...\n" html.tokenizeText() @@ -610,9 +618,12 @@ def testCoreToHtml_Format(mockGUI): # Export Mode # =========== - assert html._formatSynopsis("synopsis text") == ( + assert html._formatSynopsis("synopsis text", True) == ( "

Synopsis: synopsis text

\n" ) + assert html._formatSynopsis("brief text", False) == ( + "

Brief: brief text

\n" + ) assert html._formatComments("comment text") == ( "

Comment: comment text

\n" ) @@ -632,9 +643,12 @@ def testCoreToHtml_Format(mockGUI): html.setPreview(True, True) - assert html._formatSynopsis("synopsis text") == ( + assert html._formatSynopsis("synopsis text", True) == ( "

Synopsis: synopsis text

\n" ) + assert html._formatSynopsis("brief text", False) == ( + "

Brief: brief text

\n" + ) assert html._formatComments("comment text") == ( "

comment text

\n" ) diff --git a/tests/test_core/test_core_tokenizer.py b/tests/test_core/test_core_tokenizer.py index c218b7d7..3dc513ce 100644 --- a/tests/test_core/test_core_tokenizer.py +++ b/tests/test_core/test_core_tokenizer.py @@ -471,6 +471,20 @@ def testCoreToken_MetaFormat(mockGUI): tokens.tokenizeText() assert tokens.theMarkdown[-1] == "% synopsis: The synopsis\n\n" + # Brief + tokens.setSynopsis(False) + tokens._text = "% brief: A description\n" + tokens.tokenizeText() + assert tokens._tokens == [ + (Tokenizer.T_BRIEF, 0, "A description", None, Tokenizer.A_NONE), + (Tokenizer.T_EMPTY, 0, "", None, Tokenizer.A_NONE), + ] + assert tokens.theMarkdown[-1] == "\n" + + tokens.setSynopsis(True) + tokens.tokenizeText() + assert tokens.theMarkdown[-1] == "% brief: A description\n\n" + # Keyword tokens._text = "@char: Bod\n" tokens.tokenizeText() diff --git a/tests/test_core/test_core_tomd.py b/tests/test_core/test_core_tomd.py index c44447a4..78e94230 100644 --- a/tests/test_core/test_core_tomd.py +++ b/tests/test_core/test_core_tomd.py @@ -106,7 +106,7 @@ def testCoreToMarkdown_ConvertFormat(mockGUI): theMD.doConvert() assert theMD.theResult == "Line one \nLine two \nLine three\n\n" - # Synopsis + # Synopsis, Brief theMD._text = "%synopsis: The synopsis ...\n" theMD.tokenizeText() theMD.doConvert() @@ -118,6 +118,12 @@ def testCoreToMarkdown_ConvertFormat(mockGUI): theMD.doConvert() assert theMD.theResult == "**Synopsis:** The synopsis ...\n\n" + theMD.setSynopsis(True) + theMD._text = "%brief: A description ...\n" + theMD.tokenizeText() + theMD.doConvert() + assert theMD.theResult == "**Brief:** A description ...\n\n" + # Comment theMD._text = "% A comment ...\n" theMD.tokenizeText() diff --git a/tests/test_core/test_core_toodt.py b/tests/test_core/test_core_toodt.py index ab6bf41c..04d5180b 100644 --- a/tests/test_core/test_core_toodt.py +++ b/tests/test_core/test_core_toodt.py @@ -447,12 +447,13 @@ def testCoreToOdt_Convert(mockGUI): '' ) - # Synopsis, Comment, Keywords + # Synopsis, Brief, Comment, Keywords odt._text = ( "### Scene\n\n" "@pov: Jane\n\n" "% synopsis: So it begins\n\n" - "% a plain comment\n\n" + "% brief: Then what\n\n" + "% A plain comment\n\n" ) odt.setSynopsis(True) odt.setComments(True) @@ -470,7 +471,9 @@ def testCoreToOdt_Convert(mockGUI): '' 'Synopsis: So it begins' '' - 'Comment: a plain comment' + 'Brief: Then what' + '' + 'Comment: A plain comment' '' ) @@ -804,9 +807,12 @@ def testCoreToOdt_Format(mockGUI): project = NWProject() odt = ToOdt(project, isFlat=True) - assert odt._formatSynopsis("synopsis text") == ( + assert odt._formatSynopsis("synopsis text", True) == ( "Synopsis: synopsis text", [(0, ToOdt.FMT_B_B), (9, ToOdt.FMT_B_E)] ) + assert odt._formatSynopsis("brief text", False) == ( + "Brief: brief text", [(0, ToOdt.FMT_B_B), (6, ToOdt.FMT_B_E)] + ) assert odt._formatComments("comment text") == ( "Comment: comment text", [(0, ToOdt.FMT_B_B), (8, ToOdt.FMT_B_E)] ) From 519e5857d16d5f2195d3b8cf1813811a67ea728d Mon Sep 17 00:00:00 2001 From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com> Date: Sat, 25 Nov 2023 18:23:09 +0100 Subject: [PATCH 08/14] Add a column to viewer panel to show the brief --- novelwriter/assets/i18n/project_en_GB.json | 1 + novelwriter/core/coretools.py | 2 +- novelwriter/core/index.py | 2 +- novelwriter/gui/docviewerpanel.py | 8 ++++++-- sample/content/14298de4d9524.nwd | 6 ++++-- sample/content/bb2c23b3c42cc.nwd | 6 ++++-- sample/nwProject.nwx | 8 ++++---- 7 files changed, 21 insertions(+), 12 deletions(-) diff --git a/novelwriter/assets/i18n/project_en_GB.json b/novelwriter/assets/i18n/project_en_GB.json index d29160b8..f630e22f 100644 --- a/novelwriter/assets/i18n/project_en_GB.json +++ b/novelwriter/assets/i18n/project_en_GB.json @@ -1,6 +1,7 @@ { "Synopsis": "Synopsis", "Comment": "Comment", + "Brief": "Brief", "Notes": "Notes", "Tag": "Tag", "Point of View": "Point of View", diff --git a/novelwriter/core/coretools.py b/novelwriter/core/coretools.py index fd040461..fe0b1ba7 100644 --- a/novelwriter/core/coretools.py +++ b/novelwriter/core/coretools.py @@ -449,8 +449,8 @@ class ProjectBuilder: aDoc = project.storage.getDocument(aHandle) aDoc.writeDocument( f"# {noteTitles[newRoot]}\n\n" - f"% Brief: {bfNote}\n\n" f"@tag: {ntTag}\n\n" + f"% Brief: {bfNote}\n\n" ) # Also add the archive and trash folders diff --git a/novelwriter/core/index.py b/novelwriter/core/index.py index 2c76c3ae..1f47256b 100644 --- a/novelwriter/core/index.py +++ b/novelwriter/core/index.py @@ -1264,7 +1264,7 @@ class IndexHeading: # =============================================================================================== # -# text Processing Functions +# Text Processing Functions # =============================================================================================== # CLASSIFIERS = { diff --git a/novelwriter/gui/docviewerpanel.py b/novelwriter/gui/docviewerpanel.py index 5f4f4cc6..e45820e8 100644 --- a/novelwriter/gui/docviewerpanel.py +++ b/novelwriter/gui/docviewerpanel.py @@ -295,6 +295,7 @@ class _ViewPanelKeyWords(QTreeWidget): C_VIEW = 2 C_DOC = 3 C_TITLE = 4 + C_BRIEF = 5 D_TAG = Qt.ItemDataRole.UserRole @@ -307,7 +308,10 @@ class _ViewPanelKeyWords(QTreeWidget): iPx = SHARED.theme.baseIconSize cMg = CONFIG.pxInt(6) - self.setHeaderLabels([self.tr("Tag"), "", "", self.tr("Document"), self.tr("Heading")]) + self.setHeaderLabels([ + self.tr("Tag"), "", "", self.tr("Document"), + self.tr("Heading"), self.tr("Brief") + ]) self.setIndentation(0) self.setSelectionMode(QAbstractItemView.SelectionMode.NoSelection) self.setIconSize(QSize(iPx, iPx)) @@ -321,7 +325,6 @@ class _ViewPanelKeyWords(QTreeWidget): treeHeader.setSectionResizeMode(self.C_NAME, QHeaderView.ResizeMode.ResizeToContents) treeHeader.setSectionResizeMode(self.C_EDIT, QHeaderView.ResizeMode.Fixed) treeHeader.setSectionResizeMode(self.C_VIEW, QHeaderView.ResizeMode.Fixed) - treeHeader.setSectionResizeMode(self.C_DOC, QHeaderView.ResizeMode.ResizeToContents) treeHeader.resizeSection(self.C_EDIT, iPx + cMg) treeHeader.resizeSection(self.C_VIEW, iPx + cMg) @@ -367,6 +370,7 @@ class _ViewPanelKeyWords(QTreeWidget): trItem.setText(self.C_DOC, nwItem.itemName) trItem.setText(self.C_TITLE, hItem.title) trItem.setData(self.C_TITLE, Qt.ItemDataRole.DecorationRole, hDec) + trItem.setText(self.C_BRIEF, hItem.synopsis) trItem.setData(self.C_DATA, self.D_TAG, tag) if tag not in self._treeMap: diff --git a/sample/content/14298de4d9524.nwd b/sample/content/14298de4d9524.nwd index 0f69520e..6150d624 100644 --- a/sample/content/14298de4d9524.nwd +++ b/sample/content/14298de4d9524.nwd @@ -1,10 +1,12 @@ %%~name: John Smith %%~path: f7e2d9f330615/14298de4d9524 %%~kind: CHARACTER/NOTE -%%~hash: 259eff30f10e101cf93e27e8764e851d356d54b8 -%%~date: Unknown/2023-08-25 16:52:03 +%%~hash: fda91c416d874aa41a47fceaedb3d62f040c7e32 +%%~date: Unknown/2023-11-25 18:16:13 # John Smith @tag: John +% Brief: The sidekick + He’s pretty cool. Not Brad Pitt though. \ No newline at end of file diff --git a/sample/content/bb2c23b3c42cc.nwd b/sample/content/bb2c23b3c42cc.nwd index 28175909..d028a12c 100644 --- a/sample/content/bb2c23b3c42cc.nwd +++ b/sample/content/bb2c23b3c42cc.nwd @@ -1,10 +1,12 @@ %%~name: Jane Smith %%~path: f7e2d9f330615/bb2c23b3c42cc %%~kind: CHARACTER/NOTE -%%~hash: a69e4ca6ceede2536c8d364f88e6b62ef0ec36c3 -%%~date: Unknown/2023-08-25 16:52:04 +%%~hash: b7291713899bd0356617a36ae606b08e5fee1b65 +%%~date: Unknown/2023-11-25 18:16:07 # Jane Smith @tag: Jane +% Brief: The heroine + She’s pretty cool. Not Angelina Jolie though. \ No newline at end of file diff --git a/sample/nwProject.nwx b/sample/nwProject.nwx index f2f09e9c..ea845e2c 100644 --- a/sample/nwProject.nwx +++ b/sample/nwProject.nwx @@ -1,6 +1,6 @@ - - + + Sample Project Sample Project Jane Smith @@ -102,11 +102,11 @@ Main Characters - + John Smith - + Jane Smith From 05e63cb01e767226267dfa32706ceefb20f7f2d9 Mon Sep 17 00:00:00 2001 From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com> Date: Sat, 25 Nov 2023 18:45:06 +0100 Subject: [PATCH 09/14] Make the columns in the viewer panel resizeable --- novelwriter/core/options.py | 3 +++ novelwriter/gui/docviewerpanel.py | 39 +++++++++++++++++++++++++++++-- novelwriter/guimain.py | 2 ++ 3 files changed, 42 insertions(+), 2 deletions(-) diff --git a/novelwriter/core/options.py b/novelwriter/core/options.py index 48a0c594..8019dee1 100644 --- a/novelwriter/core/options.py +++ b/novelwriter/core/options.py @@ -69,6 +69,9 @@ VALID_MAP = { "GuiManuscriptBuild": { "winWidth", "winHeight", "fmtWidth", "sumWidth", }, + "GuiDocViewerPanel": { + "colWidths", + } } diff --git a/novelwriter/gui/docviewerpanel.py b/novelwriter/gui/docviewerpanel.py index e45820e8..c8f56fd6 100644 --- a/novelwriter/gui/docviewerpanel.py +++ b/novelwriter/gui/docviewerpanel.py @@ -34,9 +34,10 @@ from PyQt5.QtWidgets import ( ) from novelwriter import CONFIG, SHARED +from novelwriter.enum import nwDocMode, nwItemClass +from novelwriter.common import checkInt from novelwriter.constants import nwHeaders, nwLabels, nwLists, trConst from novelwriter.core.index import IndexHeading, IndexItem -from novelwriter.enum import nwDocMode, nwItemClass logger = logging.getLogger(__name__) @@ -103,6 +104,23 @@ class GuiDocViewerPanel(QWidget): return + def openProjectTasks(self) -> None: + """Run open project tasks.""" + widths = SHARED.project.options.getValue("GuiDocViewerPanel", "colWidths", {}) + if isinstance(widths, dict): + for key, value in widths.items(): + if key in self.kwTabs and isinstance(value, list): + self.kwTabs[key].setColumnWidths(value) + return + + def closeProjectTasks(self) -> None: + """Run close project tasks.""" + widths = {} + for key, tab in self.kwTabs.items(): + widths[key] = tab.getColumnWidths() + SHARED.project.options.setValue("GuiDocViewerPanel", "colWidths", widths) + return + ## # Public Slots ## @@ -313,9 +331,11 @@ class _ViewPanelKeyWords(QTreeWidget): self.tr("Heading"), self.tr("Brief") ]) self.setIndentation(0) - self.setSelectionMode(QAbstractItemView.SelectionMode.NoSelection) self.setIconSize(QSize(iPx, iPx)) self.setFrameStyle(QFrame.Shape.NoFrame) + self.setSelectionMode(QAbstractItemView.SelectionMode.NoSelection) + self.setExpandsOnDoubleClick(False) + self.setDragEnabled(False) self.setSortingEnabled(True) self.sortByColumn(self.C_NAME, Qt.SortOrder.AscendingOrder) @@ -327,6 +347,7 @@ class _ViewPanelKeyWords(QTreeWidget): treeHeader.setSectionResizeMode(self.C_VIEW, QHeaderView.ResizeMode.Fixed) treeHeader.resizeSection(self.C_EDIT, iPx + cMg) treeHeader.resizeSection(self.C_VIEW, iPx + cMg) + treeHeader.setSectionsMovable(False) # Cache Icons Locally self._classIcon = SHARED.theme.getIcon(nwLabels.CLASS_ICON[itemClass]) @@ -387,6 +408,20 @@ class _ViewPanelKeyWords(QTreeWidget): return True return False + def setColumnWidths(self, widths: list[int]) -> None: + """Set the column widths.""" + if isinstance(widths, list) and len(widths) >= 2: + self.setColumnWidth(self.C_DOC, CONFIG.pxInt(checkInt(widths[0], 100))) + self.setColumnWidth(self.C_TITLE, CONFIG.pxInt(checkInt(widths[1], 100))) + return + + def getColumnWidths(self) -> list[int]: + """Get the widths of the user-adjustable columns.""" + return [ + CONFIG.rpxInt(self.columnWidth(self.C_DOC)), + CONFIG.rpxInt(self.columnWidth(self.C_TITLE)), + ] + ## # Private Slots ## diff --git a/novelwriter/guimain.py b/novelwriter/guimain.py index 2006213e..54857de8 100644 --- a/novelwriter/guimain.py +++ b/novelwriter/guimain.py @@ -454,6 +454,7 @@ class GuiMain(QMainWindow): self.docViewer.clearNavHistory() self.closeDocViewer(byUser=False) + self.docViewerPanel.closeProjectTasks() self.outlineView.closeProjectTasks() self.novelView.closeProjectTasks() self.projView.clearProjectView() @@ -527,6 +528,7 @@ class GuiMain(QMainWindow): self.projView.openProjectTasks() self.novelView.openProjectTasks() self.outlineView.openProjectTasks() + self.docViewerPanel.openProjectTasks() self._updateStatusWordCount() # Restore previously open documents, if any From b17b5d32c213c5ae1d9c6b8044942a910511e95f Mon Sep 17 00:00:00 2001 From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com> Date: Sat, 25 Nov 2023 18:53:11 +0100 Subject: [PATCH 10/14] Allow double-clicking in viewer panel --- novelwriter/gui/docviewerpanel.py | 18 ++++++++++++++++++ tests/test_gui/test_gui_docviewerpanel.py | 8 ++++++++ 2 files changed, 26 insertions(+) diff --git a/novelwriter/gui/docviewerpanel.py b/novelwriter/gui/docviewerpanel.py index c8f56fd6..3d6bdfdd 100644 --- a/novelwriter/gui/docviewerpanel.py +++ b/novelwriter/gui/docviewerpanel.py @@ -231,6 +231,7 @@ class _ViewPanelBackRefs(QTreeWidget): # Signals self.clicked.connect(self._treeItemClicked) + self.doubleClicked.connect(self._treeItemDoubleClicked) return @@ -271,6 +272,14 @@ class _ViewPanelBackRefs(QTreeWidget): self._parent.openDocumentRequest.emit(tHandle, nwDocMode.VIEW, "", True) return + @pyqtSlot("QModelIndex") + def _treeItemDoubleClicked(self, index: QModelIndex) -> None: + """Emit follow tag signal on user double click.""" + tHandle = index.siblingAtColumn(self.C_DATA).data(self.D_HANDLE) + if index.column() == self.C_DOC: + self._parent.openDocumentRequest.emit(tHandle, nwDocMode.VIEW, "", True) + return + ## # Internal Functions ## @@ -356,6 +365,7 @@ class _ViewPanelKeyWords(QTreeWidget): # Signals self.clicked.connect(self._treeItemClicked) + self.doubleClicked.connect(self._treeItemDoubleClicked) return @@ -436,4 +446,12 @@ class _ViewPanelKeyWords(QTreeWidget): self._parent.loadDocumentTagRequest.emit(tag, nwDocMode.VIEW) return + @pyqtSlot("QModelIndex") + def _treeItemDoubleClicked(self, index: QModelIndex) -> None: + """Emit follow tag signal on user double click.""" + tag = index.siblingAtColumn(self.C_DATA).data(self.D_TAG) + if index.column() == self.C_NAME: + self._parent.loadDocumentTagRequest.emit(tag, nwDocMode.VIEW) + return + # END Class _ViewPanelKeyWords diff --git a/tests/test_gui/test_gui_docviewerpanel.py b/tests/test_gui/test_gui_docviewerpanel.py index 1ff773ae..da43c4f3 100644 --- a/tests/test_gui/test_gui_docviewerpanel.py +++ b/tests/test_gui/test_gui_docviewerpanel.py @@ -102,6 +102,12 @@ def testGuiViewerPanel_BackRefs(qtbot, monkeypatch, nwGUI, projPath, mockRnd): tabBackRefs._treeItemClicked(tabBackRefs.model().index(0, tabBackRefs.C_VIEW)) assert nwGUI.docViewer.docHandle == C.hSceneDoc + # Double-Click + nwGUI.viewDocument(hJane) + assert nwGUI.docViewer.docHandle == hJane + tabBackRefs._treeItemDoubleClicked(tabBackRefs.model().index(0, tabBackRefs.C_DOC)) + assert nwGUI.docViewer.docHandle == C.hSceneDoc + # qtbot.stop() # END Test testGuiViewerPanel_BackRefs @@ -189,6 +195,8 @@ def testGuiViewerPanel_Tags(qtbot, monkeypatch, caplog, nwGUI, projPath, mockRnd assert nwGUI.docViewer.docHandle == C.hSceneDoc charTab._treeItemClicked(charTab.model().index(1, charTab.C_VIEW)) assert nwGUI.docViewer.docHandle == hJohn + charTab._treeItemDoubleClicked(charTab.model().index(0, charTab.C_NAME)) + assert nwGUI.docViewer.docHandle == hJane # qtbot.stop() From 53aa9704277b964945e5a7c189694bd6b3c35014 Mon Sep 17 00:00:00 2001 From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com> Date: Sat, 25 Nov 2023 23:42:05 +0100 Subject: [PATCH 11/14] Rename "brief" to "summary" --- novelwriter/assets/i18n/project_en_GB.json | 2 +- novelwriter/core/coretools.py | 2 +- novelwriter/core/index.py | 4 ++-- novelwriter/core/tohtml.py | 4 ++-- novelwriter/core/tokenizer.py | 6 +++--- novelwriter/core/tomd.py | 4 ++-- novelwriter/core/toodt.py | 4 ++-- novelwriter/enum.py | 2 +- novelwriter/gui/docviewerpanel.py | 18 +++++++++--------- sample/content/14298de4d9524.nwd | 4 ++-- sample/content/bb2c23b3c42cc.nwd | 4 ++-- tests/test_core/test_core_index.py | 18 ++++++++++-------- tests/test_core/test_core_tohtml.py | 14 +++++++------- tests/test_core/test_core_tokenizer.py | 8 ++++---- tests/test_core/test_core_tomd.py | 6 +++--- tests/test_core/test_core_toodt.py | 10 +++++----- 16 files changed, 56 insertions(+), 54 deletions(-) diff --git a/novelwriter/assets/i18n/project_en_GB.json b/novelwriter/assets/i18n/project_en_GB.json index f630e22f..d1e27ea9 100644 --- a/novelwriter/assets/i18n/project_en_GB.json +++ b/novelwriter/assets/i18n/project_en_GB.json @@ -1,7 +1,7 @@ { "Synopsis": "Synopsis", + "Summary": "Summary", "Comment": "Comment", - "Brief": "Brief", "Notes": "Notes", "Tag": "Tag", "Point of View": "Point of View", diff --git a/novelwriter/core/coretools.py b/novelwriter/core/coretools.py index fe0b1ba7..d870bb0b 100644 --- a/novelwriter/core/coretools.py +++ b/novelwriter/core/coretools.py @@ -450,7 +450,7 @@ class ProjectBuilder: aDoc.writeDocument( f"# {noteTitles[newRoot]}\n\n" f"@tag: {ntTag}\n\n" - f"% Brief: {bfNote}\n\n" + f"% Summary: {bfNote}\n\n" ) # Also add the archive and trash folders diff --git a/novelwriter/core/index.py b/novelwriter/core/index.py index 1f47256b..4795b30d 100644 --- a/novelwriter/core/index.py +++ b/novelwriter/core/index.py @@ -339,7 +339,7 @@ class NWIndex: elif line.startswith("%"): if cTitle != TT_NONE: cStyle, cText, _ = processComment(line) - if cStyle in (nwComment.BRIEF, nwComment.SYNOPSIS): + if cStyle in (nwComment.SYNOPSIS, nwComment.SUMMARY): self._itemIndex.setHeadingSynopsis(tHandle, cTitle, cText) # Count words for remaining text after last heading @@ -1268,7 +1268,7 @@ class IndexHeading: # =============================================================================================== # CLASSIFIERS = { - "brief": nwComment.BRIEF, + "summary": nwComment.SUMMARY, "synopsis": nwComment.SYNOPSIS, } diff --git a/novelwriter/core/tohtml.py b/novelwriter/core/tohtml.py index 219f6032..762c61c4 100644 --- a/novelwriter/core/tohtml.py +++ b/novelwriter/core/tohtml.py @@ -289,7 +289,7 @@ class ToHtml(Tokenizer): elif tType == self.T_SYNOPSIS and self._doSynopsis: lines.append(self._formatSynopsis(tText, True)) - elif tType == self.T_BRIEF and self._doSynopsis: + elif tType == self.T_SUMMARY and self._doSynopsis: lines.append(self._formatSynopsis(tText, False)) elif tType == self.T_COMMENT and self._doComments: @@ -459,7 +459,7 @@ class ToHtml(Tokenizer): def _formatSynopsis(self, text: str, synopsis: bool) -> str: """Apply HTML formatting to synopsis.""" - sSynop = self._localLookup("Synopsis") if synopsis else self._localLookup("Brief") + sSynop = self._localLookup("Synopsis") if synopsis else self._localLookup("Summary") if self._genMode == self.M_PREVIEW: return f"

{sSynop}: {text}

\n" else: diff --git a/novelwriter/core/tokenizer.py b/novelwriter/core/tokenizer.py index b5d0fd18..70b6d246 100644 --- a/novelwriter/core/tokenizer.py +++ b/novelwriter/core/tokenizer.py @@ -80,7 +80,7 @@ class Tokenizer(ABC): # Block Type T_EMPTY = 1 # Empty line (new paragraph) T_SYNOPSIS = 2 # Synopsis comment - T_BRIEF = 3 # Brief comment + T_SUMMARY = 3 # Summary comment T_COMMENT = 4 # Comment line T_KEYWORD = 5 # Command line T_TITLE = 6 # Title @@ -470,9 +470,9 @@ class Tokenizer(ABC): )) if self._doSynopsis and self._keepMarkdown: tmpMarkdown.append("%s\n" % aLine) - elif cStyle == nwComment.BRIEF: + elif cStyle == nwComment.SUMMARY: self._tokens.append(( - self.T_BRIEF, nHead, cText, None, sAlign + self.T_SUMMARY, nHead, cText, None, sAlign )) if self._doSynopsis and self._keepMarkdown: tmpMarkdown.append("%s\n" % aLine) diff --git a/novelwriter/core/tomd.py b/novelwriter/core/tomd.py index 3187258a..e73e142b 100644 --- a/novelwriter/core/tomd.py +++ b/novelwriter/core/tomd.py @@ -170,8 +170,8 @@ class ToMarkdown(Tokenizer): label = self._localLookup("Synopsis") lines.append(f"**{label}:** {tText}\n\n") - elif tType == self.T_BRIEF and self._doSynopsis: - label = self._localLookup("Brief") + elif tType == self.T_SUMMARY and self._doSynopsis: + label = self._localLookup("Summary") lines.append(f"**{label}:** {tText}\n\n") elif tType == self.T_COMMENT and self._doComments: diff --git a/novelwriter/core/toodt.py b/novelwriter/core/toodt.py index 26cc76b2..ddae70f6 100644 --- a/novelwriter/core/toodt.py +++ b/novelwriter/core/toodt.py @@ -484,7 +484,7 @@ class ToOdt(Tokenizer): tTemp, fTemp = self._formatSynopsis(tText, True) self._addTextPar("Text_20_Meta", oStyle, tTemp, tFmt=fTemp) - elif tType == self.T_BRIEF and self._doSynopsis: + elif tType == self.T_SUMMARY and self._doSynopsis: tTemp, fTemp = self._formatSynopsis(tText, False) self._addTextPar("Text_20_Meta", oStyle, tTemp, tFmt=fTemp) @@ -558,7 +558,7 @@ class ToOdt(Tokenizer): def _formatSynopsis(self, text: str, synopsis: bool) -> tuple[str, list[tuple[int, int]]]: """Apply formatting to synopsis lines.""" - name = self._localLookup("Synopsis") if synopsis else self._localLookup("Brief") + name = self._localLookup("Synopsis") if synopsis else self._localLookup("Summary") rTxt = f"{name}: {text}" rFmt = [(0, self.FMT_B_B), (len(name) + 1, self.FMT_B_E)] return rTxt, rFmt diff --git a/novelwriter/enum.py b/novelwriter/enum.py index 1258e5e2..26d873e8 100644 --- a/novelwriter/enum.py +++ b/novelwriter/enum.py @@ -65,7 +65,7 @@ class nwComment(Enum): PLAIN = 0 SYNOPSIS = 1 - BRIEF = 2 + SUMMARY = 2 # END Enum nwComment diff --git a/novelwriter/gui/docviewerpanel.py b/novelwriter/gui/docviewerpanel.py index 3d6bdfdd..f12a9aac 100644 --- a/novelwriter/gui/docviewerpanel.py +++ b/novelwriter/gui/docviewerpanel.py @@ -316,13 +316,13 @@ class _ViewPanelBackRefs(QTreeWidget): class _ViewPanelKeyWords(QTreeWidget): - C_DATA = 0 - C_NAME = 0 - C_EDIT = 1 - C_VIEW = 2 - C_DOC = 3 - C_TITLE = 4 - C_BRIEF = 5 + C_DATA = 0 + C_NAME = 0 + C_EDIT = 1 + C_VIEW = 2 + C_DOC = 3 + C_TITLE = 4 + C_SUMMARY = 5 D_TAG = Qt.ItemDataRole.UserRole @@ -337,7 +337,7 @@ class _ViewPanelKeyWords(QTreeWidget): self.setHeaderLabels([ self.tr("Tag"), "", "", self.tr("Document"), - self.tr("Heading"), self.tr("Brief") + self.tr("Heading"), self.tr("Summary") ]) self.setIndentation(0) self.setIconSize(QSize(iPx, iPx)) @@ -401,7 +401,7 @@ class _ViewPanelKeyWords(QTreeWidget): trItem.setText(self.C_DOC, nwItem.itemName) trItem.setText(self.C_TITLE, hItem.title) trItem.setData(self.C_TITLE, Qt.ItemDataRole.DecorationRole, hDec) - trItem.setText(self.C_BRIEF, hItem.synopsis) + trItem.setText(self.C_SUMMARY, hItem.synopsis) trItem.setData(self.C_DATA, self.D_TAG, tag) if tag not in self._treeMap: diff --git a/sample/content/14298de4d9524.nwd b/sample/content/14298de4d9524.nwd index 6150d624..9431806c 100644 --- a/sample/content/14298de4d9524.nwd +++ b/sample/content/14298de4d9524.nwd @@ -7,6 +7,6 @@ @tag: John -% Brief: The sidekick +% Summary: The sidekick -He’s pretty cool. Not Brad Pitt though. \ No newline at end of file +He’s pretty cool. Not Brad Pitt though. diff --git a/sample/content/bb2c23b3c42cc.nwd b/sample/content/bb2c23b3c42cc.nwd index d028a12c..ddb0ad5e 100644 --- a/sample/content/bb2c23b3c42cc.nwd +++ b/sample/content/bb2c23b3c42cc.nwd @@ -7,6 +7,6 @@ @tag: Jane -% Brief: The heroine +% Summary: The heroine -She’s pretty cool. Not Angelina Jolie though. \ No newline at end of file +She’s pretty cool. Not Angelina Jolie though. diff --git a/tests/test_core/test_core_index.py b/tests/test_core/test_core_index.py index d12ab318..fd07ed70 100644 --- a/tests/test_core/test_core_index.py +++ b/tests/test_core/test_core_index.py @@ -1269,7 +1269,7 @@ def testCoreIndex_processComment(): # Regular comment assert processComment("%Hi") == (nwComment.PLAIN, "Hi", 0) assert processComment("% Hi") == (nwComment.PLAIN, "Hi", 0) - assert processComment("% Hi:you") == (nwComment.PLAIN, "Hi:you", 0) + assert processComment("% Hi:You") == (nwComment.PLAIN, "Hi:You", 0) # Synopsis assert processComment("%synopsis:") == (nwComment.PLAIN, "synopsis:", 0) @@ -1278,14 +1278,16 @@ def testCoreIndex_processComment(): assert processComment("% synopsis : Hi") == (nwComment.SYNOPSIS, "Hi", 13) assert processComment("% Synopsis : Hi") == (nwComment.SYNOPSIS, "Hi", 15) assert processComment("% \t SYNOPSIS : Hi") == (nwComment.SYNOPSIS, "Hi", 16) + assert processComment("% \t SYNOPSIS : Hi:You") == (nwComment.SYNOPSIS, "Hi:You", 16) - # Brief - assert processComment("%brief:") == (nwComment.PLAIN, "brief:", 0) - assert processComment("%brief: Hi") == (nwComment.BRIEF, "Hi", 7) - assert processComment("% brief: Hi") == (nwComment.BRIEF, "Hi", 8) - assert processComment("% brief : Hi") == (nwComment.BRIEF, "Hi", 10) - assert processComment("% Brief : Hi") == (nwComment.BRIEF, "Hi", 12) - assert processComment("% \t BRIEF : Hi") == (nwComment.BRIEF, "Hi", 13) + # Summary + assert processComment("%summary:") == (nwComment.PLAIN, "summary:", 0) + assert processComment("%summary: Hi") == (nwComment.SUMMARY, "Hi", 9) + assert processComment("% summary: Hi") == (nwComment.SUMMARY, "Hi", 10) + assert processComment("% summary : Hi") == (nwComment.SUMMARY, "Hi", 12) + assert processComment("% Summary : Hi") == (nwComment.SUMMARY, "Hi", 14) + assert processComment("% \t SUMMARY : Hi") == (nwComment.SUMMARY, "Hi", 15) + assert processComment("% \t SUMMARY : Hi:You") == (nwComment.SUMMARY, "Hi:You", 15) # END Test testCoreIndex_processComment diff --git a/tests/test_core/test_core_tohtml.py b/tests/test_core/test_core_tohtml.py index 7f616155..47ea7dd0 100644 --- a/tests/test_core/test_core_tohtml.py +++ b/tests/test_core/test_core_tohtml.py @@ -149,7 +149,7 @@ def testCoreToHtml_ConvertFormat(mockGUI): "

Line one
Line two
Line three

\n" ) - # Synopsis, Brief + # Synopsis, Summary html._text = "%synopsis: The synopsis ...\n" html.tokenizeText() html.doConvert() @@ -164,11 +164,11 @@ def testCoreToHtml_ConvertFormat(mockGUI): ) html.setSynopsis(True) - html._text = "%brief: A description ...\n" + html._text = "%summary: A description ...\n" html.tokenizeText() html.doConvert() assert html.theResult == ( - "

Brief: A description ...

\n" + "

Summary: A description ...

\n" ) # Comment @@ -621,8 +621,8 @@ def testCoreToHtml_Format(mockGUI): assert html._formatSynopsis("synopsis text", True) == ( "

Synopsis: synopsis text

\n" ) - assert html._formatSynopsis("brief text", False) == ( - "

Brief: brief text

\n" + assert html._formatSynopsis("summary text", False) == ( + "

Summary: summary text

\n" ) assert html._formatComments("comment text") == ( "

Comment: comment text

\n" @@ -646,8 +646,8 @@ def testCoreToHtml_Format(mockGUI): assert html._formatSynopsis("synopsis text", True) == ( "

Synopsis: synopsis text

\n" ) - assert html._formatSynopsis("brief text", False) == ( - "

Brief: brief text

\n" + assert html._formatSynopsis("summary text", False) == ( + "

Summary: summary text

\n" ) assert html._formatComments("comment text") == ( "

comment text

\n" diff --git a/tests/test_core/test_core_tokenizer.py b/tests/test_core/test_core_tokenizer.py index 3dc513ce..29532d77 100644 --- a/tests/test_core/test_core_tokenizer.py +++ b/tests/test_core/test_core_tokenizer.py @@ -471,19 +471,19 @@ def testCoreToken_MetaFormat(mockGUI): tokens.tokenizeText() assert tokens.theMarkdown[-1] == "% synopsis: The synopsis\n\n" - # Brief + # Summary tokens.setSynopsis(False) - tokens._text = "% brief: A description\n" + tokens._text = "% summary: A description\n" tokens.tokenizeText() assert tokens._tokens == [ - (Tokenizer.T_BRIEF, 0, "A description", None, Tokenizer.A_NONE), + (Tokenizer.T_SUMMARY, 0, "A description", None, Tokenizer.A_NONE), (Tokenizer.T_EMPTY, 0, "", None, Tokenizer.A_NONE), ] assert tokens.theMarkdown[-1] == "\n" tokens.setSynopsis(True) tokens.tokenizeText() - assert tokens.theMarkdown[-1] == "% brief: A description\n\n" + assert tokens.theMarkdown[-1] == "% summary: A description\n\n" # Keyword tokens._text = "@char: Bod\n" diff --git a/tests/test_core/test_core_tomd.py b/tests/test_core/test_core_tomd.py index 78e94230..385af38a 100644 --- a/tests/test_core/test_core_tomd.py +++ b/tests/test_core/test_core_tomd.py @@ -106,7 +106,7 @@ def testCoreToMarkdown_ConvertFormat(mockGUI): theMD.doConvert() assert theMD.theResult == "Line one \nLine two \nLine three\n\n" - # Synopsis, Brief + # Synopsis, Summary theMD._text = "%synopsis: The synopsis ...\n" theMD.tokenizeText() theMD.doConvert() @@ -119,10 +119,10 @@ def testCoreToMarkdown_ConvertFormat(mockGUI): assert theMD.theResult == "**Synopsis:** The synopsis ...\n\n" theMD.setSynopsis(True) - theMD._text = "%brief: A description ...\n" + theMD._text = "%summary: A description ...\n" theMD.tokenizeText() theMD.doConvert() - assert theMD.theResult == "**Brief:** A description ...\n\n" + assert theMD.theResult == "**Summary:** A description ...\n\n" # Comment theMD._text = "% A comment ...\n" diff --git a/tests/test_core/test_core_toodt.py b/tests/test_core/test_core_toodt.py index 04d5180b..3622b4bb 100644 --- a/tests/test_core/test_core_toodt.py +++ b/tests/test_core/test_core_toodt.py @@ -447,12 +447,12 @@ def testCoreToOdt_Convert(mockGUI): '' ) - # Synopsis, Brief, Comment, Keywords + # Synopsis, Summary, Comment, Keywords odt._text = ( "### Scene\n\n" "@pov: Jane\n\n" "% synopsis: So it begins\n\n" - "% brief: Then what\n\n" + "% summary: Then what\n\n" "% A plain comment\n\n" ) odt.setSynopsis(True) @@ -471,7 +471,7 @@ def testCoreToOdt_Convert(mockGUI): '' 'Synopsis: So it begins' '' - 'Brief: Then what' + 'Summary: Then what' '' 'Comment: A plain comment' '' @@ -810,8 +810,8 @@ def testCoreToOdt_Format(mockGUI): assert odt._formatSynopsis("synopsis text", True) == ( "Synopsis: synopsis text", [(0, ToOdt.FMT_B_B), (9, ToOdt.FMT_B_E)] ) - assert odt._formatSynopsis("brief text", False) == ( - "Brief: brief text", [(0, ToOdt.FMT_B_B), (6, ToOdt.FMT_B_E)] + assert odt._formatSynopsis("summary text", False) == ( + "Summary: summary text", [(0, ToOdt.FMT_B_B), (8, ToOdt.FMT_B_E)] ) assert odt._formatComments("comment text") == ( "Comment: comment text", [(0, ToOdt.FMT_B_B), (8, ToOdt.FMT_B_E)] From bedd71b105d5156dc63b0909cea17481bcee8b51 Mon Sep 17 00:00:00 2001 From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com> Date: Sat, 25 Nov 2023 23:51:30 +0100 Subject: [PATCH 12/14] Add a summary entry to the insert menu (#1619) --- docs/source/usage_shortcuts.rst | 53 +++++++++++++++++---------------- novelwriter/enum.py | 7 +++-- novelwriter/gui/doceditor.py | 4 +++ novelwriter/gui/mainmenu.py | 7 +++++ 4 files changed, 42 insertions(+), 29 deletions(-) diff --git a/docs/source/usage_shortcuts.rst b/docs/source/usage_shortcuts.rst index 0eea9175..6e25ca36 100644 --- a/docs/source/usage_shortcuts.rst +++ b/docs/source/usage_shortcuts.rst @@ -90,6 +90,8 @@ Text Formatting Shortcuts .. csv-table:: :header: "Shortcut", "Description" + ":kbd:`Ctrl+'`", "Wrap selected text, or word under cursor, in single quotes" + ":kbd:`Ctrl+""`", "Wrap selected text, or word under cursor, in double quotes" ":kbd:`Ctrl+/`", "Toggle block format as comment" ":kbd:`Ctrl+0`", "Remove block formatting for block under cursor" ":kbd:`Ctrl+1`", "Change block format to header level 1" @@ -104,8 +106,6 @@ Text Formatting Shortcuts ":kbd:`Ctrl+B`", "Format selected text, or word under cursor, with strong emphasis (bold)" ":kbd:`Ctrl+D`", "Strikethrough selected text, or word under cursor" ":kbd:`Ctrl+I`", "Format selected text, or word under cursor, with emphasis (italic)" - ":kbd:`Ctrl+'`", "Wrap selected text, or word under cursor, in single quotes" - ":kbd:`Ctrl+""`", "Wrap selected text, or word under cursor, in double quotes" ":kbd:`Ctrl+Shift+/`", "Remove block formatting for block under cursor" @@ -127,10 +127,10 @@ Other Editor Shortcuts ":kbd:`Ctrl+X`", "Cut selected text to clipboard" ":kbd:`Ctrl+Y`", "Redo latest undo" ":kbd:`Ctrl+Z`", "Undo latest changes" - ":kbd:`Ctrl+Del`", "Delete the word after the cursor" ":kbd:`Ctrl+Backspace`", "Delete the word before the cursor" - ":kbd:`Ctrl+Return`", "Open the tag or reference under the cursor in the viewer" + ":kbd:`Ctrl+Del`", "Delete the word after the cursor" ":kbd:`Ctrl+F7`", "Toggle spell checking" + ":kbd:`Ctrl+Return`", "Open the tag or reference under the cursor in the viewer" ":kbd:`Ctrl+Shift+A`", "Select all text in the current paragraph" ":kbd:`Ctrl+Shift+I`", "Import text to the current document from a text file" @@ -147,39 +147,40 @@ a key or key combination for the inserted content. .. csv-table:: :header: "Shortcut", "Description" - ":kbd:`Ctrl+K`, :kbd:`−`", "Insert a short dash (en dash)" + ":kbd:`Ctrl+K`, :kbd:`Space`", "Insert a non-breaking space" ":kbd:`Ctrl+K`, :kbd:`_`", "Insert a long dash (em dash)" - ":kbd:`Ctrl+K`, :kbd:`Ctrl+_`", "Insert a horizontal bar (quotation dash)" + ":kbd:`Ctrl+K`, :kbd:`.`", "Insert an ellipsis" + ":kbd:`Ctrl+K`, :kbd:`'`", "Insert a modifier apostrophe" + ":kbd:`Ctrl+K`, :kbd:`*`", "Insert a list bullet" + ":kbd:`Ctrl+K`, :kbd:`%`", "Insert a per mille symbol" ":kbd:`Ctrl+K`, :kbd:`~`", "Insert a figure dash (same width as a number)" + ":kbd:`Ctrl+K`, :kbd:`−`", "Insert a short dash (en dash)" ":kbd:`Ctrl+K`, :kbd:`1`", "Insert a left single quote" ":kbd:`Ctrl+K`, :kbd:`2`", "Insert a right single quote" ":kbd:`Ctrl+K`, :kbd:`3`", "Insert a left double quote" ":kbd:`Ctrl+K`, :kbd:`4`", "Insert a right double quote" - ":kbd:`Ctrl+K`, :kbd:`'`", "Insert a modifier apostrophe" - ":kbd:`Ctrl+K`, :kbd:`.`", "Insert an ellipsis" - ":kbd:`Ctrl+K`, :kbd:`Ctrl+'`", "Insert a prime" - ":kbd:`Ctrl+K`, :kbd:`Ctrl+""`", "Insert a double prime" - ":kbd:`Ctrl+K`, :kbd:`Space`", "Insert a non-breaking space" - ":kbd:`Ctrl+K`, :kbd:`Shift+Space`", "Insert a thin space" - ":kbd:`Ctrl+K`, :kbd:`Ctrl+Space`", "Insert a thin non-breaking space" - ":kbd:`Ctrl+K`, :kbd:`*`", "Insert a list bullet" - ":kbd:`Ctrl+K`, :kbd:`Ctrl+−`", "Insert a hyphen bullet (alternative bullet)" - ":kbd:`Ctrl+K`, :kbd:`Ctrl+*`", "Insert a flower mark (alternative bullet)" - ":kbd:`Ctrl+K`, :kbd:`%`", "Insert a per mille symbol" - ":kbd:`Ctrl+K`, :kbd:`Ctrl+O`", "Insert a degree symbol" - ":kbd:`Ctrl+K`, :kbd:`Ctrl+X`", "Insert a times sign" - ":kbd:`Ctrl+K`, :kbd:`Ctrl+D`", "Insert a division sign" - ":kbd:`Ctrl+K`, :kbd:`G`", "Insert a ``@tag`` keyword" - ":kbd:`Ctrl+K`, :kbd:`V`", "Insert a ``@pov`` keyword" - ":kbd:`Ctrl+K`, :kbd:`F`", "Insert a ``@focus`` keyword" ":kbd:`Ctrl+K`, :kbd:`C`", "Insert a ``@char`` keyword" + ":kbd:`Ctrl+K`, :kbd:`E`", "Insert an ``@entity`` keyword" + ":kbd:`Ctrl+K`, :kbd:`F`", "Insert a ``@focus`` keyword" + ":kbd:`Ctrl+K`, :kbd:`G`", "Insert a ``@tag`` keyword" + ":kbd:`Ctrl+K`, :kbd:`L`", "Insert a ``@location`` keyword" + ":kbd:`Ctrl+K`, :kbd:`O`", "Insert an ``@object`` keyword" ":kbd:`Ctrl+K`, :kbd:`P`", "Insert a ``@plot`` keyword" ":kbd:`Ctrl+K`, :kbd:`S`", "Insert a synopsis comment" ":kbd:`Ctrl+K`, :kbd:`T`", "Insert a ``@time`` keyword" - ":kbd:`Ctrl+K`, :kbd:`L`", "Insert a ``@location`` keyword" - ":kbd:`Ctrl+K`, :kbd:`O`", "Insert an ``@object`` keyword" - ":kbd:`Ctrl+K`, :kbd:`E`", "Insert an ``@entity`` keyword" + ":kbd:`Ctrl+K`, :kbd:`U`", "Insert a summary comment" + ":kbd:`Ctrl+K`, :kbd:`V`", "Insert a ``@pov`` keyword" ":kbd:`Ctrl+K`, :kbd:`X`", "Insert a ``@custom`` keyword" + ":kbd:`Ctrl+K`, :kbd:`Ctrl+Space`", "Insert a thin non-breaking space" + ":kbd:`Ctrl+K`, :kbd:`Ctrl+_`", "Insert a horizontal bar (quotation dash)" + ":kbd:`Ctrl+K`, :kbd:`Ctrl+'`", "Insert a prime" + ":kbd:`Ctrl+K`, :kbd:`Ctrl+""`", "Insert a double prime" + ":kbd:`Ctrl+K`, :kbd:`Ctrl+*`", "Insert a flower mark (alternative bullet)" + ":kbd:`Ctrl+K`, :kbd:`Ctrl+−`", "Insert a hyphen bullet (alternative bullet)" + ":kbd:`Ctrl+K`, :kbd:`Ctrl+D`", "Insert a division sign" + ":kbd:`Ctrl+K`, :kbd:`Ctrl+O`", "Insert a degree symbol" + ":kbd:`Ctrl+K`, :kbd:`Ctrl+X`", "Insert a times sign" + ":kbd:`Ctrl+K`, :kbd:`Shift+Space`", "Insert a thin space" .. _a_kb_viewer: diff --git a/novelwriter/enum.py b/novelwriter/enum.py index 26d873e8..7280dc36 100644 --- a/novelwriter/enum.py +++ b/novelwriter/enum.py @@ -136,9 +136,10 @@ class nwDocInsert(Enum): QUOTE_LD = 3 QUOTE_RD = 4 SYNOPSIS = 5 - NEW_PAGE = 6 - VSPACE_S = 7 - VSPACE_M = 8 + SUMMARY = 6 + NEW_PAGE = 7 + VSPACE_S = 8 + VSPACE_M = 9 # END Enum nwDocInsert diff --git a/novelwriter/gui/doceditor.py b/novelwriter/gui/doceditor.py index fc513485..ec2aeea3 100644 --- a/novelwriter/gui/doceditor.py +++ b/novelwriter/gui/doceditor.py @@ -834,6 +834,10 @@ class GuiDocEditor(QPlainTextEdit): text = "% Synopsis: " newBlock = True goAfter = True + elif insert == nwDocInsert.SUMMARY: + text = "% Summary: " + newBlock = True + goAfter = True elif insert == nwDocInsert.NEW_PAGE: text = "[newpage]" newBlock = True diff --git a/novelwriter/gui/mainmenu.py b/novelwriter/gui/mainmenu.py index 1fee411f..40772c5a 100644 --- a/novelwriter/gui/mainmenu.py +++ b/novelwriter/gui/mainmenu.py @@ -564,6 +564,13 @@ class GuiMainMenu(QMenuBar): lambda: self.requestDocInsert.emit(nwDocInsert.SYNOPSIS) ) + # Insert > Summary Comment + self.aInsSynopsis = self.mInsComments.addAction(self.tr("Summary Comment")) + self.aInsSynopsis.setShortcut("Ctrl+K, U") + self.aInsSynopsis.triggered.connect( + lambda: self.requestDocInsert.emit(nwDocInsert.SUMMARY) + ) + # Insert > Symbols self.mInsBreaks = self.insMenu.addMenu(self.tr("Page Break and Space")) From 6fd4417350c44316d9f66053cb38d0f745c47b42 Mon Sep 17 00:00:00 2001 From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com> Date: Sat, 25 Nov 2023 23:57:12 +0100 Subject: [PATCH 13/14] Fix variable name and test --- novelwriter/gui/mainmenu.py | 6 +++--- novelwriter/guimain.py | 1 + tests/test_gui/test_gui_mainmenu.py | 4 ++++ 3 files changed, 8 insertions(+), 3 deletions(-) diff --git a/novelwriter/gui/mainmenu.py b/novelwriter/gui/mainmenu.py index 40772c5a..f6fde5c7 100644 --- a/novelwriter/gui/mainmenu.py +++ b/novelwriter/gui/mainmenu.py @@ -565,9 +565,9 @@ class GuiMainMenu(QMenuBar): ) # Insert > Summary Comment - self.aInsSynopsis = self.mInsComments.addAction(self.tr("Summary Comment")) - self.aInsSynopsis.setShortcut("Ctrl+K, U") - self.aInsSynopsis.triggered.connect( + self.aInsSummary = self.mInsComments.addAction(self.tr("Summary Comment")) + self.aInsSummary.setShortcut("Ctrl+K, U") + self.aInsSummary.triggered.connect( lambda: self.requestDocInsert.emit(nwDocInsert.SUMMARY) ) diff --git a/novelwriter/guimain.py b/novelwriter/guimain.py index 54857de8..65589ed6 100644 --- a/novelwriter/guimain.py +++ b/novelwriter/guimain.py @@ -1461,6 +1461,7 @@ class GuiMain(QMainWindow): self.addAction(self.mainMenu.aInsTimes) self.addAction(self.mainMenu.aInsDivide) self.addAction(self.mainMenu.aInsSynopsis) + self.addAction(self.mainMenu.aInsSummary) for mAction, _ in self.mainMenu.mInsKWItems.values(): self.addAction(mAction) diff --git a/tests/test_gui/test_gui_mainmenu.py b/tests/test_gui/test_gui_mainmenu.py index 5bf25674..b7d8aa0a 100644 --- a/tests/test_gui/test_gui_mainmenu.py +++ b/tests/test_gui/test_gui_mainmenu.py @@ -593,6 +593,10 @@ def testGuiMenu_Insert(qtbot, monkeypatch, nwGUI, fncPath, projPath, mockRnd): nwGUI.mainMenu.aInsSynopsis.activate(QAction.Trigger) assert nwGUI.docEditor.getText() == "Stuff\n% Synopsis: \n" + nwGUI.docEditor.setPlainText("Stuff\n") + nwGUI.mainMenu.aInsSummary.activate(QAction.Trigger) + assert nwGUI.docEditor.getText() == "Stuff\n% Summary: \n" + # Insert Break or Space # ===================== From b9b4969b8584745b9422b90dc56af330650bd0d2 Mon Sep 17 00:00:00 2001 From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com> Date: Sun, 26 Nov 2023 13:58:00 +0100 Subject: [PATCH 14/14] Rename once again, and use short instead for short descriptions of notes --- docs/source/usage_shortcuts.rst | 2 +- novelwriter/assets/i18n/project_en_GB.json | 2 +- novelwriter/core/coretools.py | 4 ++-- novelwriter/core/index.py | 4 ++-- novelwriter/core/tohtml.py | 7 +++++-- novelwriter/core/tokenizer.py | 6 +++--- novelwriter/core/tomd.py | 4 ++-- novelwriter/core/toodt.py | 7 +++++-- novelwriter/enum.py | 4 ++-- novelwriter/gui/doceditor.py | 4 ++-- novelwriter/gui/docviewerpanel.py | 18 +++++++++--------- novelwriter/gui/mainmenu.py | 10 +++++----- novelwriter/guimain.py | 2 +- sample/content/14298de4d9524.nwd | 2 +- sample/content/bb2c23b3c42cc.nwd | 2 +- sample/nwProject.nwx | 4 ++-- tests/test_core/test_core_index.py | 16 ++++++++-------- tests/test_core/test_core_tohtml.py | 14 +++++++------- tests/test_core/test_core_tokenizer.py | 8 ++++---- tests/test_core/test_core_tomd.py | 6 +++--- tests/test_core/test_core_toodt.py | 10 +++++----- tests/test_gui/test_gui_mainmenu.py | 4 ++-- 22 files changed, 73 insertions(+), 67 deletions(-) diff --git a/docs/source/usage_shortcuts.rst b/docs/source/usage_shortcuts.rst index 6e25ca36..4ddbde83 100644 --- a/docs/source/usage_shortcuts.rst +++ b/docs/source/usage_shortcuts.rst @@ -163,12 +163,12 @@ a key or key combination for the inserted content. ":kbd:`Ctrl+K`, :kbd:`E`", "Insert an ``@entity`` keyword" ":kbd:`Ctrl+K`, :kbd:`F`", "Insert a ``@focus`` keyword" ":kbd:`Ctrl+K`, :kbd:`G`", "Insert a ``@tag`` keyword" + ":kbd:`Ctrl+K`, :kbd:`H`", "Insert a short description comment" ":kbd:`Ctrl+K`, :kbd:`L`", "Insert a ``@location`` keyword" ":kbd:`Ctrl+K`, :kbd:`O`", "Insert an ``@object`` keyword" ":kbd:`Ctrl+K`, :kbd:`P`", "Insert a ``@plot`` keyword" ":kbd:`Ctrl+K`, :kbd:`S`", "Insert a synopsis comment" ":kbd:`Ctrl+K`, :kbd:`T`", "Insert a ``@time`` keyword" - ":kbd:`Ctrl+K`, :kbd:`U`", "Insert a summary comment" ":kbd:`Ctrl+K`, :kbd:`V`", "Insert a ``@pov`` keyword" ":kbd:`Ctrl+K`, :kbd:`X`", "Insert a ``@custom`` keyword" ":kbd:`Ctrl+K`, :kbd:`Ctrl+Space`", "Insert a thin non-breaking space" diff --git a/novelwriter/assets/i18n/project_en_GB.json b/novelwriter/assets/i18n/project_en_GB.json index d1e27ea9..f8e30410 100644 --- a/novelwriter/assets/i18n/project_en_GB.json +++ b/novelwriter/assets/i18n/project_en_GB.json @@ -1,6 +1,6 @@ { "Synopsis": "Synopsis", - "Summary": "Summary", + "Short Description": "Short Description", "Comment": "Comment", "Notes": "Notes", "Tag": "Tag", diff --git a/novelwriter/core/coretools.py b/novelwriter/core/coretools.py index d870bb0b..bacf9e7c 100644 --- a/novelwriter/core/coretools.py +++ b/novelwriter/core/coretools.py @@ -406,7 +406,7 @@ class ProjectBuilder: chSynop = self.tr("Summary of the chapter.") scSynop = self.tr("Summary of the scene.") - bfNote = self.tr("A brief description.") + bfNote = self.tr("A short description.") # Create chapters if numChapters > 0: @@ -450,7 +450,7 @@ class ProjectBuilder: aDoc.writeDocument( f"# {noteTitles[newRoot]}\n\n" f"@tag: {ntTag}\n\n" - f"% Summary: {bfNote}\n\n" + f"% Short: {bfNote}\n\n" ) # Also add the archive and trash folders diff --git a/novelwriter/core/index.py b/novelwriter/core/index.py index 4795b30d..119b4416 100644 --- a/novelwriter/core/index.py +++ b/novelwriter/core/index.py @@ -339,7 +339,7 @@ class NWIndex: elif line.startswith("%"): if cTitle != TT_NONE: cStyle, cText, _ = processComment(line) - if cStyle in (nwComment.SYNOPSIS, nwComment.SUMMARY): + if cStyle in (nwComment.SYNOPSIS, nwComment.SHORT): self._itemIndex.setHeadingSynopsis(tHandle, cTitle, cText) # Count words for remaining text after last heading @@ -1268,7 +1268,7 @@ class IndexHeading: # =============================================================================================== # CLASSIFIERS = { - "summary": nwComment.SUMMARY, + "short": nwComment.SHORT, "synopsis": nwComment.SYNOPSIS, } diff --git a/novelwriter/core/tohtml.py b/novelwriter/core/tohtml.py index 762c61c4..6b5dbfe2 100644 --- a/novelwriter/core/tohtml.py +++ b/novelwriter/core/tohtml.py @@ -289,7 +289,7 @@ class ToHtml(Tokenizer): elif tType == self.T_SYNOPSIS and self._doSynopsis: lines.append(self._formatSynopsis(tText, True)) - elif tType == self.T_SUMMARY and self._doSynopsis: + elif tType == self.T_SHORT and self._doSynopsis: lines.append(self._formatSynopsis(tText, False)) elif tType == self.T_COMMENT and self._doComments: @@ -459,7 +459,10 @@ class ToHtml(Tokenizer): def _formatSynopsis(self, text: str, synopsis: bool) -> str: """Apply HTML formatting to synopsis.""" - sSynop = self._localLookup("Synopsis") if synopsis else self._localLookup("Summary") + if synopsis: + sSynop = self._localLookup("Synopsis") + else: + sSynop = self._localLookup("Short Description") if self._genMode == self.M_PREVIEW: return f"

{sSynop}: {text}

\n" else: diff --git a/novelwriter/core/tokenizer.py b/novelwriter/core/tokenizer.py index 70b6d246..cdb50119 100644 --- a/novelwriter/core/tokenizer.py +++ b/novelwriter/core/tokenizer.py @@ -80,7 +80,7 @@ class Tokenizer(ABC): # Block Type T_EMPTY = 1 # Empty line (new paragraph) T_SYNOPSIS = 2 # Synopsis comment - T_SUMMARY = 3 # Summary comment + T_SHORT = 3 # Short description comment T_COMMENT = 4 # Comment line T_KEYWORD = 5 # Command line T_TITLE = 6 # Title @@ -470,9 +470,9 @@ class Tokenizer(ABC): )) if self._doSynopsis and self._keepMarkdown: tmpMarkdown.append("%s\n" % aLine) - elif cStyle == nwComment.SUMMARY: + elif cStyle == nwComment.SHORT: self._tokens.append(( - self.T_SUMMARY, nHead, cText, None, sAlign + self.T_SHORT, nHead, cText, None, sAlign )) if self._doSynopsis and self._keepMarkdown: tmpMarkdown.append("%s\n" % aLine) diff --git a/novelwriter/core/tomd.py b/novelwriter/core/tomd.py index e73e142b..54dffdae 100644 --- a/novelwriter/core/tomd.py +++ b/novelwriter/core/tomd.py @@ -170,8 +170,8 @@ class ToMarkdown(Tokenizer): label = self._localLookup("Synopsis") lines.append(f"**{label}:** {tText}\n\n") - elif tType == self.T_SUMMARY and self._doSynopsis: - label = self._localLookup("Summary") + elif tType == self.T_SHORT and self._doSynopsis: + label = self._localLookup("Short Description") lines.append(f"**{label}:** {tText}\n\n") elif tType == self.T_COMMENT and self._doComments: diff --git a/novelwriter/core/toodt.py b/novelwriter/core/toodt.py index ddae70f6..681e3a68 100644 --- a/novelwriter/core/toodt.py +++ b/novelwriter/core/toodt.py @@ -484,7 +484,7 @@ class ToOdt(Tokenizer): tTemp, fTemp = self._formatSynopsis(tText, True) self._addTextPar("Text_20_Meta", oStyle, tTemp, tFmt=fTemp) - elif tType == self.T_SUMMARY and self._doSynopsis: + elif tType == self.T_SHORT and self._doSynopsis: tTemp, fTemp = self._formatSynopsis(tText, False) self._addTextPar("Text_20_Meta", oStyle, tTemp, tFmt=fTemp) @@ -558,7 +558,10 @@ class ToOdt(Tokenizer): def _formatSynopsis(self, text: str, synopsis: bool) -> tuple[str, list[tuple[int, int]]]: """Apply formatting to synopsis lines.""" - name = self._localLookup("Synopsis") if synopsis else self._localLookup("Summary") + if synopsis: + name = self._localLookup("Synopsis") + else: + name = self._localLookup("Short Description") rTxt = f"{name}: {text}" rFmt = [(0, self.FMT_B_B), (len(name) + 1, self.FMT_B_E)] return rTxt, rFmt diff --git a/novelwriter/enum.py b/novelwriter/enum.py index 7280dc36..5bb41f71 100644 --- a/novelwriter/enum.py +++ b/novelwriter/enum.py @@ -65,7 +65,7 @@ class nwComment(Enum): PLAIN = 0 SYNOPSIS = 1 - SUMMARY = 2 + SHORT = 2 # END Enum nwComment @@ -136,7 +136,7 @@ class nwDocInsert(Enum): QUOTE_LD = 3 QUOTE_RD = 4 SYNOPSIS = 5 - SUMMARY = 6 + SHORT = 6 NEW_PAGE = 7 VSPACE_S = 8 VSPACE_M = 9 diff --git a/novelwriter/gui/doceditor.py b/novelwriter/gui/doceditor.py index ec2aeea3..5747bce8 100644 --- a/novelwriter/gui/doceditor.py +++ b/novelwriter/gui/doceditor.py @@ -834,8 +834,8 @@ class GuiDocEditor(QPlainTextEdit): text = "% Synopsis: " newBlock = True goAfter = True - elif insert == nwDocInsert.SUMMARY: - text = "% Summary: " + elif insert == nwDocInsert.SHORT: + text = "% Short: " newBlock = True goAfter = True elif insert == nwDocInsert.NEW_PAGE: diff --git a/novelwriter/gui/docviewerpanel.py b/novelwriter/gui/docviewerpanel.py index f12a9aac..47986f58 100644 --- a/novelwriter/gui/docviewerpanel.py +++ b/novelwriter/gui/docviewerpanel.py @@ -316,13 +316,13 @@ class _ViewPanelBackRefs(QTreeWidget): class _ViewPanelKeyWords(QTreeWidget): - C_DATA = 0 - C_NAME = 0 - C_EDIT = 1 - C_VIEW = 2 - C_DOC = 3 - C_TITLE = 4 - C_SUMMARY = 5 + C_DATA = 0 + C_NAME = 0 + C_EDIT = 1 + C_VIEW = 2 + C_DOC = 3 + C_TITLE = 4 + C_SHORT = 5 D_TAG = Qt.ItemDataRole.UserRole @@ -337,7 +337,7 @@ class _ViewPanelKeyWords(QTreeWidget): self.setHeaderLabels([ self.tr("Tag"), "", "", self.tr("Document"), - self.tr("Heading"), self.tr("Summary") + self.tr("Heading"), self.tr("Short Description") ]) self.setIndentation(0) self.setIconSize(QSize(iPx, iPx)) @@ -401,7 +401,7 @@ class _ViewPanelKeyWords(QTreeWidget): trItem.setText(self.C_DOC, nwItem.itemName) trItem.setText(self.C_TITLE, hItem.title) trItem.setData(self.C_TITLE, Qt.ItemDataRole.DecorationRole, hDec) - trItem.setText(self.C_SUMMARY, hItem.synopsis) + trItem.setText(self.C_SHORT, hItem.synopsis) trItem.setData(self.C_DATA, self.D_TAG, tag) if tag not in self._treeMap: diff --git a/novelwriter/gui/mainmenu.py b/novelwriter/gui/mainmenu.py index f6fde5c7..736655c3 100644 --- a/novelwriter/gui/mainmenu.py +++ b/novelwriter/gui/mainmenu.py @@ -564,11 +564,11 @@ class GuiMainMenu(QMenuBar): lambda: self.requestDocInsert.emit(nwDocInsert.SYNOPSIS) ) - # Insert > Summary Comment - self.aInsSummary = self.mInsComments.addAction(self.tr("Summary Comment")) - self.aInsSummary.setShortcut("Ctrl+K, U") - self.aInsSummary.triggered.connect( - lambda: self.requestDocInsert.emit(nwDocInsert.SUMMARY) + # Insert > Short Description Comment + self.aInsShort = self.mInsComments.addAction(self.tr("Short Description Comment")) + self.aInsShort.setShortcut("Ctrl+K, U") + self.aInsShort.triggered.connect( + lambda: self.requestDocInsert.emit(nwDocInsert.SHORT) ) # Insert > Symbols diff --git a/novelwriter/guimain.py b/novelwriter/guimain.py index 65589ed6..f61b4e83 100644 --- a/novelwriter/guimain.py +++ b/novelwriter/guimain.py @@ -1461,7 +1461,7 @@ class GuiMain(QMainWindow): self.addAction(self.mainMenu.aInsTimes) self.addAction(self.mainMenu.aInsDivide) self.addAction(self.mainMenu.aInsSynopsis) - self.addAction(self.mainMenu.aInsSummary) + self.addAction(self.mainMenu.aInsShort) for mAction, _ in self.mainMenu.mInsKWItems.values(): self.addAction(mAction) diff --git a/sample/content/14298de4d9524.nwd b/sample/content/14298de4d9524.nwd index 9431806c..6e4c795d 100644 --- a/sample/content/14298de4d9524.nwd +++ b/sample/content/14298de4d9524.nwd @@ -7,6 +7,6 @@ @tag: John -% Summary: The sidekick +% Short: The sidekick He’s pretty cool. Not Brad Pitt though. diff --git a/sample/content/bb2c23b3c42cc.nwd b/sample/content/bb2c23b3c42cc.nwd index ddb0ad5e..f2048c84 100644 --- a/sample/content/bb2c23b3c42cc.nwd +++ b/sample/content/bb2c23b3c42cc.nwd @@ -7,6 +7,6 @@ @tag: Jane -% Summary: The heroine +% Short: The heroine She’s pretty cool. Not Angelina Jolie though. diff --git a/sample/nwProject.nwx b/sample/nwProject.nwx index ea845e2c..a68d8074 100644 --- a/sample/nwProject.nwx +++ b/sample/nwProject.nwx @@ -1,6 +1,6 @@ - - + + Sample Project Sample Project Jane Smith diff --git a/tests/test_core/test_core_index.py b/tests/test_core/test_core_index.py index fd07ed70..072a83a8 100644 --- a/tests/test_core/test_core_index.py +++ b/tests/test_core/test_core_index.py @@ -1280,14 +1280,14 @@ def testCoreIndex_processComment(): assert processComment("% \t SYNOPSIS : Hi") == (nwComment.SYNOPSIS, "Hi", 16) assert processComment("% \t SYNOPSIS : Hi:You") == (nwComment.SYNOPSIS, "Hi:You", 16) - # Summary - assert processComment("%summary:") == (nwComment.PLAIN, "summary:", 0) - assert processComment("%summary: Hi") == (nwComment.SUMMARY, "Hi", 9) - assert processComment("% summary: Hi") == (nwComment.SUMMARY, "Hi", 10) - assert processComment("% summary : Hi") == (nwComment.SUMMARY, "Hi", 12) - assert processComment("% Summary : Hi") == (nwComment.SUMMARY, "Hi", 14) - assert processComment("% \t SUMMARY : Hi") == (nwComment.SUMMARY, "Hi", 15) - assert processComment("% \t SUMMARY : Hi:You") == (nwComment.SUMMARY, "Hi:You", 15) + # Short Description + assert processComment("%short:") == (nwComment.PLAIN, "short:", 0) + assert processComment("%short: Hi") == (nwComment.SHORT, "Hi", 7) + assert processComment("% short: Hi") == (nwComment.SHORT, "Hi", 8) + assert processComment("% short : Hi") == (nwComment.SHORT, "Hi", 10) + assert processComment("% Short : Hi") == (nwComment.SHORT, "Hi", 12) + assert processComment("% \t SHORT : Hi") == (nwComment.SHORT, "Hi", 13) + assert processComment("% \t SHORT : Hi:You") == (nwComment.SHORT, "Hi:You", 13) # END Test testCoreIndex_processComment diff --git a/tests/test_core/test_core_tohtml.py b/tests/test_core/test_core_tohtml.py index 47ea7dd0..820eac41 100644 --- a/tests/test_core/test_core_tohtml.py +++ b/tests/test_core/test_core_tohtml.py @@ -149,7 +149,7 @@ def testCoreToHtml_ConvertFormat(mockGUI): "

Line one
Line two
Line three

\n" ) - # Synopsis, Summary + # Synopsis, Short html._text = "%synopsis: The synopsis ...\n" html.tokenizeText() html.doConvert() @@ -164,11 +164,11 @@ def testCoreToHtml_ConvertFormat(mockGUI): ) html.setSynopsis(True) - html._text = "%summary: A description ...\n" + html._text = "%short: A short description ...\n" html.tokenizeText() html.doConvert() assert html.theResult == ( - "

Summary: A description ...

\n" + "

Short Description: A short description ...

\n" ) # Comment @@ -621,8 +621,8 @@ def testCoreToHtml_Format(mockGUI): assert html._formatSynopsis("synopsis text", True) == ( "

Synopsis: synopsis text

\n" ) - assert html._formatSynopsis("summary text", False) == ( - "

Summary: summary text

\n" + assert html._formatSynopsis("short text", False) == ( + "

Short Description: short text

\n" ) assert html._formatComments("comment text") == ( "

Comment: comment text

\n" @@ -646,8 +646,8 @@ def testCoreToHtml_Format(mockGUI): assert html._formatSynopsis("synopsis text", True) == ( "

Synopsis: synopsis text

\n" ) - assert html._formatSynopsis("summary text", False) == ( - "

Summary: summary text

\n" + assert html._formatSynopsis("short text", False) == ( + "

Short Description: short text

\n" ) assert html._formatComments("comment text") == ( "

comment text

\n" diff --git a/tests/test_core/test_core_tokenizer.py b/tests/test_core/test_core_tokenizer.py index 29532d77..deab422b 100644 --- a/tests/test_core/test_core_tokenizer.py +++ b/tests/test_core/test_core_tokenizer.py @@ -471,19 +471,19 @@ def testCoreToken_MetaFormat(mockGUI): tokens.tokenizeText() assert tokens.theMarkdown[-1] == "% synopsis: The synopsis\n\n" - # Summary + # Short tokens.setSynopsis(False) - tokens._text = "% summary: A description\n" + tokens._text = "% short: A short description\n" tokens.tokenizeText() assert tokens._tokens == [ - (Tokenizer.T_SUMMARY, 0, "A description", None, Tokenizer.A_NONE), + (Tokenizer.T_SHORT, 0, "A short description", None, Tokenizer.A_NONE), (Tokenizer.T_EMPTY, 0, "", None, Tokenizer.A_NONE), ] assert tokens.theMarkdown[-1] == "\n" tokens.setSynopsis(True) tokens.tokenizeText() - assert tokens.theMarkdown[-1] == "% summary: A description\n\n" + assert tokens.theMarkdown[-1] == "% short: A short description\n\n" # Keyword tokens._text = "@char: Bod\n" diff --git a/tests/test_core/test_core_tomd.py b/tests/test_core/test_core_tomd.py index 385af38a..b18676ea 100644 --- a/tests/test_core/test_core_tomd.py +++ b/tests/test_core/test_core_tomd.py @@ -106,7 +106,7 @@ def testCoreToMarkdown_ConvertFormat(mockGUI): theMD.doConvert() assert theMD.theResult == "Line one \nLine two \nLine three\n\n" - # Synopsis, Summary + # Synopsis, Short theMD._text = "%synopsis: The synopsis ...\n" theMD.tokenizeText() theMD.doConvert() @@ -119,10 +119,10 @@ def testCoreToMarkdown_ConvertFormat(mockGUI): assert theMD.theResult == "**Synopsis:** The synopsis ...\n\n" theMD.setSynopsis(True) - theMD._text = "%summary: A description ...\n" + theMD._text = "%short: A description ...\n" theMD.tokenizeText() theMD.doConvert() - assert theMD.theResult == "**Summary:** A description ...\n\n" + assert theMD.theResult == "**Short Description:** A description ...\n\n" # Comment theMD._text = "% A comment ...\n" diff --git a/tests/test_core/test_core_toodt.py b/tests/test_core/test_core_toodt.py index 3622b4bb..a81e8e3a 100644 --- a/tests/test_core/test_core_toodt.py +++ b/tests/test_core/test_core_toodt.py @@ -447,12 +447,12 @@ def testCoreToOdt_Convert(mockGUI): '' ) - # Synopsis, Summary, Comment, Keywords + # Synopsis, Short, Comment, Keywords odt._text = ( "### Scene\n\n" "@pov: Jane\n\n" "% synopsis: So it begins\n\n" - "% summary: Then what\n\n" + "% short: Then what\n\n" "% A plain comment\n\n" ) odt.setSynopsis(True) @@ -471,7 +471,7 @@ def testCoreToOdt_Convert(mockGUI): '' 'Synopsis: So it begins' '' - 'Summary: Then what' + 'Short Description: Then what' '' 'Comment: A plain comment' '' @@ -810,8 +810,8 @@ def testCoreToOdt_Format(mockGUI): assert odt._formatSynopsis("synopsis text", True) == ( "Synopsis: synopsis text", [(0, ToOdt.FMT_B_B), (9, ToOdt.FMT_B_E)] ) - assert odt._formatSynopsis("summary text", False) == ( - "Summary: summary text", [(0, ToOdt.FMT_B_B), (8, ToOdt.FMT_B_E)] + assert odt._formatSynopsis("short text", False) == ( + "Short Description: short text", [(0, ToOdt.FMT_B_B), (18, ToOdt.FMT_B_E)] ) assert odt._formatComments("comment text") == ( "Comment: comment text", [(0, ToOdt.FMT_B_B), (8, ToOdt.FMT_B_E)] diff --git a/tests/test_gui/test_gui_mainmenu.py b/tests/test_gui/test_gui_mainmenu.py index b7d8aa0a..980d61e9 100644 --- a/tests/test_gui/test_gui_mainmenu.py +++ b/tests/test_gui/test_gui_mainmenu.py @@ -594,8 +594,8 @@ def testGuiMenu_Insert(qtbot, monkeypatch, nwGUI, fncPath, projPath, mockRnd): assert nwGUI.docEditor.getText() == "Stuff\n% Synopsis: \n" nwGUI.docEditor.setPlainText("Stuff\n") - nwGUI.mainMenu.aInsSummary.activate(QAction.Trigger) - assert nwGUI.docEditor.getText() == "Stuff\n% Summary: \n" + nwGUI.mainMenu.aInsShort.activate(QAction.Trigger) + assert nwGUI.docEditor.getText() == "Stuff\n% Short: \n" # Insert Break or Space # =====================