From a385308f38b09664b7ddb6ce3cbc8e17de614447 Mon Sep 17 00:00:00 2001 From: Vic Sergeev Date: Sat, 4 Jul 2026 16:04:48 +0300 Subject: [PATCH] working --- .gitignore | 0 .idea/.gitignore | 10 + .idea/CapLog.iml | 10 + .../inspectionProfiles/profiles_settings.xml | 6 + .idea/misc.xml | 7 + .idea/modules.xml | 8 + .idea/vcs.xml | 6 + app/__init__.py | 3 + app/__pycache__/__init__.cpython-312.pyc | Bin 0 -> 237 bytes app/__pycache__/__init__.cpython-314.pyc | Bin 0 -> 241 bytes app/__pycache__/auth.cpython-312.pyc | Bin 0 -> 2748 bytes app/__pycache__/auth.cpython-314.pyc | Bin 0 -> 3047 bytes app/__pycache__/config.cpython-312.pyc | Bin 0 -> 1897 bytes app/__pycache__/config.cpython-314.pyc | Bin 0 -> 1982 bytes app/__pycache__/database.cpython-312.pyc | Bin 0 -> 13725 bytes app/__pycache__/database.cpython-314.pyc | Bin 0 -> 14356 bytes app/auth.py | 39 +++ app/config.py | 51 +++ app/database.py | 245 +++++++++++++++ app/models.py | 0 app/ui/__init__.py | 1 + app/ui/__pycache__/__init__.cpython-312.pyc | Bin 0 -> 178 bytes app/ui/__pycache__/__init__.cpython-314.pyc | Bin 0 -> 182 bytes .../calendar_widget.cpython-312.pyc | Bin 0 -> 9779 bytes .../calendar_widget.cpython-314.pyc | Bin 0 -> 10341 bytes app/ui/__pycache__/dialogs.cpython-312.pyc | Bin 0 -> 9968 bytes app/ui/__pycache__/dialogs.cpython-314.pyc | Bin 0 -> 10531 bytes .../__pycache__/main_window.cpython-312.pyc | Bin 0 -> 12006 bytes .../__pycache__/main_window.cpython-314.pyc | Bin 0 -> 12349 bytes .../__pycache__/note_editor.cpython-312.pyc | Bin 0 -> 13373 bytes .../__pycache__/note_editor.cpython-314.pyc | Bin 0 -> 13952 bytes app/ui/__pycache__/notes_list.cpython-312.pyc | Bin 0 -> 11480 bytes app/ui/__pycache__/notes_list.cpython-314.pyc | Bin 0 -> 11876 bytes app/ui/__pycache__/sidebar.cpython-312.pyc | Bin 0 -> 3464 bytes app/ui/__pycache__/sidebar.cpython-314.pyc | Bin 0 -> 3643 bytes app/ui/calendar_widget.py | 189 +++++++++++ app/ui/dialogs.py | 219 +++++++++++++ app/ui/main_window.py | 212 +++++++++++++ app/ui/note_editor.py | 297 ++++++++++++++++++ app/ui/notes_list.py | 230 ++++++++++++++ app/ui/sidebar.py | 59 ++++ captains_log.db | Bin 0 -> 20480 bytes main.py | 20 ++ requirements.txt | 2 + 44 files changed, 1614 insertions(+) create mode 100644 .gitignore create mode 100644 .idea/.gitignore create mode 100644 .idea/CapLog.iml create mode 100644 .idea/inspectionProfiles/profiles_settings.xml create mode 100644 .idea/misc.xml create mode 100644 .idea/modules.xml create mode 100644 .idea/vcs.xml create mode 100644 app/__init__.py create mode 100644 app/__pycache__/__init__.cpython-312.pyc create mode 100644 app/__pycache__/__init__.cpython-314.pyc create mode 100644 app/__pycache__/auth.cpython-312.pyc create mode 100644 app/__pycache__/auth.cpython-314.pyc create mode 100644 app/__pycache__/config.cpython-312.pyc create mode 100644 app/__pycache__/config.cpython-314.pyc create mode 100644 app/__pycache__/database.cpython-312.pyc create mode 100644 app/__pycache__/database.cpython-314.pyc create mode 100644 app/auth.py create mode 100644 app/config.py create mode 100644 app/database.py create mode 100644 app/models.py create mode 100644 app/ui/__init__.py create mode 100644 app/ui/__pycache__/__init__.cpython-312.pyc create mode 100644 app/ui/__pycache__/__init__.cpython-314.pyc create mode 100644 app/ui/__pycache__/calendar_widget.cpython-312.pyc create mode 100644 app/ui/__pycache__/calendar_widget.cpython-314.pyc create mode 100644 app/ui/__pycache__/dialogs.cpython-312.pyc create mode 100644 app/ui/__pycache__/dialogs.cpython-314.pyc create mode 100644 app/ui/__pycache__/main_window.cpython-312.pyc create mode 100644 app/ui/__pycache__/main_window.cpython-314.pyc create mode 100644 app/ui/__pycache__/note_editor.cpython-312.pyc create mode 100644 app/ui/__pycache__/note_editor.cpython-314.pyc create mode 100644 app/ui/__pycache__/notes_list.cpython-312.pyc create mode 100644 app/ui/__pycache__/notes_list.cpython-314.pyc create mode 100644 app/ui/__pycache__/sidebar.cpython-312.pyc create mode 100644 app/ui/__pycache__/sidebar.cpython-314.pyc create mode 100644 app/ui/calendar_widget.py create mode 100644 app/ui/dialogs.py create mode 100644 app/ui/main_window.py create mode 100644 app/ui/note_editor.py create mode 100644 app/ui/notes_list.py create mode 100644 app/ui/sidebar.py create mode 100644 captains_log.db create mode 100644 main.py create mode 100644 requirements.txt diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..e69de29 diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..ab1f416 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,10 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Ignored default folder with query files +/queries/ +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml +# Editor-based HTTP Client requests +/httpRequests/ diff --git a/.idea/CapLog.iml b/.idea/CapLog.iml new file mode 100644 index 0000000..f2cca7e --- /dev/null +++ b/.idea/CapLog.iml @@ -0,0 +1,10 @@ + + + + + + + + + + \ No newline at end of file diff --git a/.idea/inspectionProfiles/profiles_settings.xml b/.idea/inspectionProfiles/profiles_settings.xml new file mode 100644 index 0000000..105ce2d --- /dev/null +++ b/.idea/inspectionProfiles/profiles_settings.xml @@ -0,0 +1,6 @@ + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..25d64c7 --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,7 @@ + + + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000..2089c61 --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..35eb1dd --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/app/__init__.py b/app/__init__.py new file mode 100644 index 0000000..b71b4f8 --- /dev/null +++ b/app/__init__.py @@ -0,0 +1,3 @@ +""" +Captain's Log - Приложение для быстрых заметок +""" \ No newline at end of file diff --git a/app/__pycache__/__init__.cpython-312.pyc b/app/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..70737b62c2349276c19a50168e4d0e9449f442da GIT binary patch literal 237 zcmX@j%ge<81l7k?vx0&2V-N=h7@>^M96-i&h7^VLfPrX>d zCqG?5SK-3^iwzfcT-beK--T@#wqDo^WN%ftu;s$;i~R~0HeT$$*m$w&VgrzFRk*PI z!iEcbE^NKn1XR3>%TJT>7JGbrN`7*D{7Qz;AWMJgx>&`yxQ4|9R61uQ7Uc#M2KczG$)vkyQs2k+&Vvx5!Ff%eTeqv%` JWGP|+aseZzQ@H>D literal 0 HcmV?d00001 diff --git a/app/__pycache__/__init__.cpython-314.pyc b/app/__pycache__/__init__.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..dff823cff660ce38cac6b77f96ff297d27b5df02 GIT binary patch literal 241 zcmdPq}zQ`1q9!pF!5%(s!|nad8cc38-|=NG!??D9X=D zO)e>p0ohs%v@j;IpdcnbJ~J<~BtBlRpz;=nO>TZlX-=wL5gSlH$nC`-cYk1JWMurz M#Kg!_!~*0306tPvvj6}9 literal 0 HcmV?d00001 diff --git a/app/__pycache__/auth.cpython-312.pyc b/app/__pycache__/auth.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..ae177e6a63a627cd419e8ef70aa1a3a2409b6c24 GIT binary patch literal 2748 zcmcImU1%It6ux)%Z}PL5+Qc+Av2Lpc$JX@E){>^NZ7N!^OF{8r2@I2+X*TI58}Hq< zZdgm&MoB>lzL*L^6%ktcvIGm!TI-tyU+%D$q*H`~zW7#>2cb`%b7%I~RIT8JJ@=kF z_nbNVo$sDAKSiU>1jel&w~nb{LViW1Jc9mWbw4cTiA55`qLz}T2`XnLp}-tS2gZYm zASDWUg;>EEVuh|Mo@FAGSM+dB2Tgn?z87CR=f!pB!UV*nM= z6*tK3^hv33G>gR^=tuj8j=ucX(24wD%4FjwSZ2&lX8F*dIr(~KWXPPH95Ugpy_0!Y zHH^`T(X3%iMKO@xvZ{h7-UMQrEU)jF4Sp0^1%=cFiK&r6egX>!l8jM2Q$Gsys=xc5 z0w-$!qGx{k*sd$}~I$YPcQaCePZ;m^3**one-dGI`2vt*G42x^!1u54dgeQRSe_g4+Vm z4fth8Cr0!DLnvLfZ0ttMD{0CRz*sEC5No#`EgN1w)g=?N#*-E>;RirWlY3n~OI_QX zu5FiJEp_dkjV`OLbCC;?CAHg8yG76L;_;u=BXye>t`G2d(Cw#tZta_U(^-XMA)w;h~`OELosn2>+U5sQMsn}9A}0=H$P zia1*bJ3j0TuMgxSn_;$>#d^Hkk|{fRs*=8`b_sR;=40T3?*RcQcW#^?`FLchQ+GOb z@!X+O=i%9wJKDzO_6_&6_9bnLqiy+oe@WY3Y;m*$LOtN2TYD=icjgdyd^B{e%8jrB zK5oHWAGoQ-&6xxA&TWrDZL4Mcb*ytSaa}CNYMIX3z}%%fnFjL}MS{sXaZM#{Ifm|Y zQyb+Kppe~D9TKdD-6z0+Zvp{mY3*~TFP!#a87S^4X)iABakK%U4*XxhR0)OcLVO`^ z0+ou+EEM$xnB}!^1OX61zQx3X^FHao9G$qH%WVEt&MDNq)v5{HsJbf$&^ ziB8ulkU*ld{|!G0hKJQga30rvX5m$WwJeCCcg%Fo_B)nW{kpNnJp{a|SfvU+_1pW6UF5gbI%5`0<=@jT%CG9F$;wZPev52SF%JLZ~cQl8= zim%|R0yF)h=d-h>59vWSf}JNlI_!pd%G|wopBuI(k{Qc(n^N|>*64`Mvp%4*)YZZ% z>!4rJ>buldcsa)irpa$w=iHeKXD+5LWj@W6Hti^BJD0RRN9!wT{X*@RV8!(o{7jfI z$8E!Kn+;<;W98DgZZ(Xzb7tCm!muSU{E%ZED7sJ}=RG!T!4;O~D@1ygDQDOoq(Hzp zw(1#jU!kGLFYaF@Fs+1xp(z_IrP8fbD<>$N`S`{CQIu literal 0 HcmV?d00001 diff --git a/app/__pycache__/auth.cpython-314.pyc b/app/__pycache__/auth.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..eb6fa0fd3453b933867b6df277e93a7aaa1015f7 GIT binary patch literal 3047 zcmcImU2GIp6uvY2H~pPLf29?+AT>*|{Ipzje;pH=Iv`_Ple>Ics0HZ-QKO&fAOjcQE!L z)QjP35?78I%$PDPGwRwPDtB8_h(Ol>linsw0<+}F0Q0nw42h6_Nt4=1O3`GHP0Wi1 zff-(qe$59mp!q>6S^#8?rhu%~YFJ%_uzIZ)jIHC|Ls^!HN^k^zSnymAe1#y0VJQda zOv;1j35mgI7kN!ez^SiGZ;@U2yYe5np3AW*ij9!W{nHsbV=&&J>(P{9S+Qi4>AHop zTXVek=+MN`mm?G7r;a2HIx|i)C(J0bCXN`h%z8BwpD<=;Ck(KuZ}t?g*L5SE&M-c(=ArX1-7(v>4vO7W9 zz9RgwfbRJxkI`08cmuFT+VZ;iD*m(sMqDS*O=8w!8EO`NT8(~VG+{>HDLa!l3*f5< z(V1312*A8;)93Ne;!ABIr!8df7|pjGx={D4y6IkX`vbLkN!{wGTfZF4t2^iG9QA;$ z94KO4c}vQ7XjJU@G+1lp8=`q!+`L%~^_93ebAaC2ok0lKmHfYfp*v{buor`syr*rT z&ZXV79qMbc0F!RPwj@Ywq3LeiP?J^}DC9bC5n!zueFhY)%^(0RwR!I3#gi^9BlG=v z^~J@0M;)=1k^c*rRYDDd4fa>|Euhl8a{=C|1*k<3SPKFmf_w|w3(g1lXBcHIM8#XU7u{t*HyJgN>ca-C|n?d{y+S9N@j!b|omh~Q` zZW58!3vvUlPU$fm$Cq_YDXqbGJB#Mp-@xSsuDW5+M)av?7CYDWM8*-j^vqs9DQFVPfNiCL2 zrpmkqUi@A9(#ph8RdyN!zi^K_PbkjyU{elo?V2! z_uWpk=qbBXzO4c9tk13QttSV&)?@4IgO3k(tOp05SdR~G_E|rG^Vs?hmcO;Wv7RvT zca}b{I7J-G*PRzt7wb0Hd+!s+RNYH#US>Hk$>4;Z(|LINsvTdJ)LIEi7l8H zqu4LDVnK}IfY_!Y9K@l|TsVv)V4H9=*cKcG+lpgg+iblZ$ALR=C)h503~V<(4z>sP zg6+d6z$Wa~etZ)6l$~|ju9md96Q98moWgJ5vtRhWbcyZdbEyH!O=_jQTG$Rx%A2OF zmXb!_tXA0er3cRsrYV=#wQW+mwB4jQ%Z60c3WGSO?!seNKZSdzz7LO0?PKn{;8J(4 zu+8@m=h&p~Jah8)paDb0Bq!^V9Wi1LDiExi+L5M=z@$QM%QUr;6E`Ze`AW+dXsEut zrhQ0sC_g5%NjX_jNXY~toxT7}LyjOF#ii3@`Ef!yESCy)A|4+b8O_s>BfN@J#z)e_ z3gwM0C}QMODuS0t2YnVIYo<>j1Vhe7AOYIox^7oGCnbxkc%<}OZLnW3ST?0jZn zMOw*bzzf&CnW^mb%oQ45&Ss`APAt_|o9D9gfDQ324N5(Sn_QS(SX#b^Ua)W3%d?rc zRx?vqR~O5ZYqGwvsB7zRM8+yyX}HU)^5*8MV&C$?&9csZSZ%`r7-hJ;yQtdKvolkT zcJ5sMgG2tN>qcef4*7*YS&hbSCvPQh58WEN+rK}!H+UF5e}jM4peFW{d&&Kwy`cw* zN6ClDM?(*XemrwHeEBdjdl;R&&eu59)VDKTjWpjZ+$h{E-YDL=aCiDJa&BiDmSXXp z*(#Vh$HcoVZ>-vJ%<{I?xOjxC`a{>%ng{5aO+FyV651+ijc4pfLag#$O<){wILJ8U zaF}rf3Gq8Zt%-3n5?U&$S_|W-qqj1SIeHu8c1Mph?m$AcGG6Os+=YbJO15^4akrx% zXWZj(FXKKWv{%Zt6O0oM_cK0;x=&SGyVi_@% literal 0 HcmV?d00001 diff --git a/app/__pycache__/config.cpython-314.pyc b/app/__pycache__/config.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..b4e1387388663b8059a65a4209248fb5efd66500 GIT binary patch literal 1982 zcmb7_%Wo4$9LL9x?0OwP63347;E@+KDa1B;6jD`{LL3r{JgfncBE_uMyK85W@us_z zisWRe2thBH6H!laLfoKL3sObfTdz)Ys5@Mc`VSaIYOnpx?mDviklGpPH=p14_nVnt zc4nM2p`af@`|77tH=lJN^c!JW)+I0GjUaR z_Opgg$2t7n#Wv3);(`L)H#oKd@nSdW$aBLe&tnhvPJ8U^gfmG2IEM1RT*MLDhFq-8 zaWK#4djGCY!_KUcz{<-1SnI#<`p=nz_O9(VA8OU|*O3Hp2MPa6b;Ipz*qjgK!$(Ke zjX4s*Jn6wc62$_EVL$0jA>4@r1s4wD5ZEy80^5xvV0&;BY|PSoaU8f0_k$h4gJ6&0 z<6uwVlVFGNFxU|*I*P}DPg%3xvsO!3d>Z%Tar{0$gG2Z%p1?_*N_EV;NF1k0A3g_{ zXp(aCdZnn9c7yZM7Ll|{!W>%GOS>KE;tMq-_Ek}Rtg0zx`{9+(DfL2Tuv(=)|cm2!$yDOnSh z*_o;7A`RHWQJy?Im7bI-Z*Iearpb1Y3m0Z&>La=$?Lb+SC+clt*Ec!Cx}!jKow8I` zD~k1JwmA@iZ|FoeRY_3|%9k~>wTdzc-BBPnXoXC*tja`HpoHl(>r^n+EyaUCpemi|g48ctOWoTF5RgU8TV*+04SlIqXEc zRE`vF!3n$F!gx!Y4S<(Aad?L|F%sXd75~Vc$#{WdOq?p@gngu z^&<7->4VA32P4Y|k(GOVlSAR5>S80*^`LaW^q_pdym$Wb;z8(abrC|*cy+k}X2mw~ zfx0)^=o_qiV@)m|;u^ldJ+0{hdTUV!5X=%{HLb}r_8}o!yWSKS`)%%I9I!daID~}w zp3n?4?m|L$ZM@mdIAZHPjH9+5W87=&amIZ}h}34A{fq~Y&{NAc2N@r;_2Z0B*nE=l z5E6Q8JI!InBQ}pR9z(*w-fY9yU2B>6UVp>aQ@j3d0^)Zr88fYe8TV3N6ct^DV;*DT yXe(l!x}Ep|Xw>(S{VYBH8{|7hW;tmlQYv!L2j11%e literal 0 HcmV?d00001 diff --git a/app/__pycache__/database.cpython-312.pyc b/app/__pycache__/database.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..02bb23479108af6a910948e657b73ccf791c49d0 GIT binary patch literal 13725 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 literal 0 HcmV?d00001 diff --git a/app/__pycache__/database.cpython-314.pyc b/app/__pycache__/database.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..b3d88c34f4a6b52af40e47e93dbd05d9e0df6b21 GIT binary patch literal 14356 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 E09%$ZivR!s literal 0 HcmV?d00001 diff --git a/app/ui/__pycache__/__init__.cpython-314.pyc b/app/ui/__pycache__/__init__.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..9f8842e9050ed5fdb6abe10fa33d8a7f60cb9544 GIT binary patch literal 182 zcmdPq^MASDe9K@24fL5#`_noOnqK!H$Cg$sKw?7Oh# zV)KRF7k2n*GTvg3k59=@j*nl-@EN4_mXV89jEienOhBb`Mq*KJKv8~HYH~?&jB{c^ zNwH6UdQ4(LK}>08OniK1US>&ryk0@&Ee@O9{FKt1RJ$TJpb;P^6mtNH56p~=jJFv? Ii&%gh09jEng#Z8m literal 0 HcmV?d00001 diff --git a/app/ui/__pycache__/calendar_widget.cpython-312.pyc b/app/ui/__pycache__/calendar_widget.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..647fc2330467a1992b595a9e455c772e2784a6f6 GIT binary patch literal 9779 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 INE5C82akcaJpcdz literal 0 HcmV?d00001 diff --git a/app/ui/__pycache__/calendar_widget.cpython-314.pyc b/app/ui/__pycache__/calendar_widget.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..4727a79be251e41869bcb580ae7243bda74e36a9 GIT binary patch literal 10341 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 literal 0 HcmV?d00001 diff --git a/app/ui/__pycache__/dialogs.cpython-312.pyc b/app/ui/__pycache__/dialogs.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..4ef234e1a01980f2d047f3d767b95f88280424e0 GIT binary patch literal 9968 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 TvW?59R?b~N_XPt@iuHd0nbs>Q literal 0 HcmV?d00001 diff --git a/app/ui/__pycache__/dialogs.cpython-314.pyc b/app/ui/__pycache__/dialogs.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..643cfa6b0a32651144d12b8c8463e8c0751c1ea2 GIT binary patch literal 10531 zcmcgSZEzdMb$9qY07vixB*8C`00B{gBO1sc&*0owXsT2R}Uz`OP@Kv39rk?5OpKHlg+WzT# zcR1Vu21(gXvoqLxxBK?p+ugTs-v@R$>+BR1>7TZ|c({wA{taK$VlDyquK;kF;wXWd zpg8&zEg0ZwI7Oc{3dWNr!PJB8##82#7Qxa(wNO5aGr1|w{DNWBsS?wk`-}=KFfyhE zFs++l+Bn)z1(}QoMrMDKUCXX!f0O-f_V3{LuK45ZZ7%@7&VG@$2i z9hv4QQ*%LDvJ9q@Q}OAj#so!`xS;M2Lu-VJ(3hzfD1nAq8YZlQv6V{E94(kQYlwQB z<_vBs!6azG%o*L3V8QtbR?ZAH!&wAdh!*T2>L4vRkWa}u8cC-wq)2Qi9!aF8GecE; zd&O78m&GeEjSp}N!28;6c)SZQ?`2oL*?-Kg%Kf*+%l+Bk!OLan`HgrjYSPWX0x(R1 zxqsb2QBMK;!(M1syg9S?GMqT zzV&-K>rVMtDi{-no#maQsGYiDvnHV9*EN(t-B-4(-#hW&kN6eyIy=mSOf1DQAu~%o zrrT}K28Y0YLNca#A%kn2eXF)SFMf$DoP8Yx|27Ez8k`6?%9^|{O4faQ_RKx@#2(2o zIem*JD4Ox>u*nj=VAuc3% ziJ9h8GrT}9N;c?-|5;=b8f{ToF=Pl&@CbmScxLz+?u=v;NFwB)!DPfn%6B$X28)EB@8mUKNAC00Q@%g3kB z3PG!6O7n>+$ub)u@Ilf5j1W8u5d1!(Ov5*jEE*uz;=sl_Z`-|a-tCUmKgZfoxuLJ$Mb>1AMVZjhw|*uJY9`7 zAO;5R1fEx;nBSRc%D5DSPHL8^pbDMwRChTvUKM9xsRFN`!IbfT2dkiQz&t= zlAUGn4%AfasP0J0m{9pZiH(<4=_o4bs=`qf3X2{nQJJOTAls^t;gvIR#);;MX3jKF zqAM#VopMmeaAsLS0~RP~%dFwFfwRsUbo{a^ruGDW>5$>;aJ&gFQ!zUqXXESxrB$do zyI#(rscapuHLM1oraGx4?1fS`z8>WHQKNXEstU()KCVt(Q@8-jmma9P)+%Ipt;xNU z^KP6F| z=W?^%D;o=sw0pV>Uf}~yMfL8LzX-e6rDAE%TAXuEu2;W%x>fGhMye24v<9w8l~s_J zvP|j5^{Uv~6D5#y0asqFO@%huhi|-Re?Yq5m3s`D36_fC7l4jZ{v*x^2{O@%h$+pX7@o8VTS$}Lq_OfXa4lek!!dJiEW~e&KHQ z450M}WpkP2ynYIQ!L72zOHl2PNv6q^a5iX?Ed1PTBpH)TQ}IMXGR;O}b8>x=;E~I` z09AGwJjS)`d*UnTN@m|z4C{5g%0)Za-r!t%v;P2Q`U0Ba;*Y&xldp>N@bV+M9q8N! zD&S@x$OQ2VRYv=Iv=2yzsp)$YU{c*OOD2Jz6O<|K0va%b6d2g{3nVg|;FHrZJDT)k z2faOLlvu<*A0ctsQa7c~rY?}jpqHSY4I0P}gb_W1;+$PZ=NVW8Ujimy%bPe5G?BeH z2rqkr9LV$_K43l{No4p89^vfU+26r6KM0E`VEsUk7%S z2*9FbiA1C5mYAjz5%6u!f+Reqa$}9AW@ey4V@94JdP0df4<1G|L((ad!2<*v_*+>p z)+BTEEFV3WK`G1r4Xhw+HO5jT+hj%%Qpxb-bkL$K+_wQmNBjuB9)$|SF6N`KQ;B;> zC1|(@HFXdDjavqC0%80nz(WY*`Uj0NOr8c9MLQ9LEtN6R+z7Tx28iOAPmsur;+he3 z=TXk1=M?A2JRF(i6O!$WK;p^iP=rYAY$@szhFu|emq_O0;80zZoaG*Lur5mGsZ=xr z?waiVkr~97>eLqn#eOW6O7M{+61IYKC*wEj=X)IsgtA~q?-9>lDgF36t z{?JJ|>K6jnzcU}m*}EQCC{NGw@SDRw9ho=fnD)EfzUBCvairGnDYS1F+qV}Rn_>6^ zo4Lo1D4n0PvX6%zUxD$8jCUovYRxj<9J3#sG{qGxFg}s-;T4~O^`t)$IrT2luQs;cgQuVHd*y7Y@J-+21zWKwS{>A)Ij_tbZ z3%t|*vv#E6@fAG%qNl&u(uRX<=3XbFbOV2Njf&q{VErQNzv)^X$g=(%dk|*Z)xCV^ z%|kbZT<5;|p~d05z|+Xp5t*k$Fy-a0H@fufRKQ)m%ctKseKU~n+I`oz^&QXKo|{76 zw@(j0XtV+EO)~FG*7;b@>4lHBb=;VHeQw46%k#gwbnDXESbq1hoO^JdEi%mord?#( zm(p3LUB6tg#mL&JtEY+$UB#CD#kQ_OTTpBZstmQ|UE5U{*Ra^Kz3BE7-Q5)&7+3n{ zV|ix~mtLjilWFY`+jgkqzXq+GGpKHQ`ZzLxsJ11{SD6 zSs>$l?A7FdxG2_3w!)#PaO3e<$!`1_+(ef#)(8#NWfU-~i|XKjgHDYim8!Dg7(<9g zPOwbFQ3hAeqP&3-PoV(b+oZ^bZuj)RrM}b|E{ac8duM#9M zq8b3DTM}>T4kyfPMakTWnsGC}~4JPQ!PJ%=kBjEf5ql7yQ? zj=xDRRO&O4w7?Thmv>?#-UX_*55w)479vE*%#t4Xst<|RT^G@p}YbJ>9?S|Och=3f-4}p0tMGL(Y5W4Yx_J?Y-%ku^@>fs zg{D5SsqaqHwt4Hn*Sqhstp&FCBer+taQ5-h9Q(ab*rrb%uAHO0qUTR?><_R9!eO=r zTY=dkGFw*8=9t|WAd_4AMW+8|>{p3fiNdbK;;zGYb{)wvM~kf;7zjixc7c7>xxs-u z@rOQeB`X?Mq+B%NRgar;U@B7^AYy2ER27|wnf%l^3Yc7KWv<|WLMBK%Vn9(v*F9^c zF{;uf<)nr-Y!mSZ-h$>a5`zIs7VNLVNK;JR4h5b5pC?nU{9o&xI;SxH^**~ z+j>N{r@(Fz*)1QlTV**6GRj#*%|sw+MJWfp5g~bkr~%}a0LvTIaFE3eLJ}-Z$ROCm zVaOh25(u;5@JpFUqSR9#4#W9P3yFA=Po|*T8V<)&(QsIftIM_;!Q)KiHEMxyy$})N z(Qrfn3l)q!9wPWsV=0&)i-U*2Clb(9S87sT!A1@Mk#Z{{0x8^sBS#dSftLuu3XekC z57mGDB`o$QrjduHX0!i`oe|pX`Qn7J-t2zZYcp^Af`Uq38hKO{29yiZ#d7~CsH(D{ zD_Ry*%YkYcP{9gWPe#sqV#3EP)eR~du9WoL4;1yuS`<;ei}v@RmC?~yL)R0*_hsMb zU2LdGbmFN6rODitAib=6;In(`knff*<==#t60x9OxPhaQ8J=y&ZCLz*G)CncMrs9wiimCJWh(6)GS(t`$E5 zx{K_A*D<)sacAT}nG!iODv>jssS-Xj0R)PARkFk)$!RbL4q?|}s7iq)vR>RrGAr3_ zQ-YroAO>Vg$74J}${D<$vS6f$SI8hzqRGqf7$M)q7sI_~c#u;vEJuCF6>K<(4Q_&B zqg<-8y@HyM$OBO05lphl*?Ku7MB4BbRT)8zMY^y;okO~@Lj6KUu=*ZWXs?lHu)=6K zc@ZnwTxS$}$A=C0J;_@71ChLD#E(t3NkNU=;1!)l=~J@t-pk6(j4qPYT5%1Q^Xosy zA$MV4eqWtmugI!je*yw0rTp~&F_-@NA2)xVV|pPvVRI+}q=sT+n?7iyhie3oVk>PJ zLfQ@#Ac}SJ^^;3uznFSw=Ixo)p?u$yx#k1&wj$Gr{})&qT$#Li0slcTfYB=3Ra=p5 zDK@&{9||{yUmI4x^IX2ZPlYjnRdlr%T^+@yW(a9O+^R{5Tb)>O<7b0QHWgjY4o9Zr%LMx;TK zs6IvPP>iLPO0g?>o1Y*mo+2>DYdLK|4SR!jG7As#5>{xtlQdQWRw&fUNy#8|9xI4R zP-G6O3d8izu@$Qo?7@RsGE9$sY1(J*dYGUMX7?8qREjlt4|@HduS@?@r!zvwVyDaaO_=M%CqD}U}5rO?CcOk$o=?DxZD5ngeX(zu|X8)=&U zg!2D0)%>rN|6eH2153z2Z@bRjc>cBLA5aKCAf|(~`}(mPPrvr`f5M;n6fXZ4x8edj literal 0 HcmV?d00001 diff --git a/app/ui/__pycache__/main_window.cpython-312.pyc b/app/ui/__pycache__/main_window.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..7db96acea1b62f53ebaa7a97849c8f3e446ebfc6 GIT binary patch literal 12006 zcmb_id2kfxmG8NGPL0s$Mn-^S7@>pszytNmkb-+R>*_1_rLK8qT-@je8`DTWGCjDaym4M8LL zrl=ui3Yuc(pgCpHE1PyY(X3G=^zchIcksDgLaa~5_QC!K_>}Yqpp}c=q6!X z)DtTR7LYI<^~MT=g(Pf`u80){i%8fJ^~H*V#RkeqwNs4qRf=)3#=d&3!@7Uoq=bW| zDWl*0HRcfC6h9EB#rMRI#E->G)!@93!D{gn>9{xpX&;Nf7B2y5MmqUtEZylh$tFQK zB0G8_QI?5>qlsSG5o38i+{+$H9Q7Mynh6W65Q(u6w(d&Adm_C^Z4C?ILt!4N=9Z*z z7<}9Q2*VxRVrv<%-~uX3%EAM3NFpqz_l|pxDLh+u9I%xf(x)~R^9>xuB z!3cHK5cKY+{2tk{Hynv~MdD0ifW!8?4QeOQ!ySP$d*fdr>1T#v{oa(XCKYN^`14pE`(xP(<& zD$MmtEv)<6NTG~Zqe9NLT3GkB+WA6Auhr>VNJ}?RDO}fe8eRAG9FXqU=~`$}dYwkq z{k{f0tc4bpY0{{=&zPaDje1-Q>8p^U{FYAHa8x!P>YX%jxSq1NYj3DE&=EKg2zBl0 zY<;ZDUoKntWIxNvbSM;wM}$yFwg?d+%5u0xWx8eGzEEe&-hfQ^vWXZga4FfP6ondE z5A2rRYHW94Py6lzvMm;g^N~ST_U_%&sWzhKEX<*(x$WJAaU?kwc*tWNk3F%UvtzSH z<`9R>SvH4(hsciZ!)*7F5DD8DmKV50N_KX0EU=(ZjEyH{j~W?>Fukn6%Qjx&IkKB$ zdpMRq9Et>*&0Kx_*8Qzp z4z>oKJh(5l>u{Kh?c)-CY`4H4+!gNcNc0{I_xB%6Mh?b+Z-fR2<7n(paV1c8ut;Yn zjoQ=}K?46j;QaMCb&Yn9+9bLxO;>zLyG}Yr9dB(KZysw-(`)A-?jCjjyd+azFO}De zWew*ZpL#yiv`cE*B{sH-C4n^EhM8QWuD71d6xU0|^;12W#x|+3Elsy$if7dG^Lx)U zoNhR0mMR)XJZZXd!AccXzF9Y7K4H7&E1U40DLq~K=3a=}=dG02ck0xn}{Zz{QE!r}DKW+IfN#q>j89?!m0o^Zw|Gqd~Elx|XNG~Ix zgF7R=3J?!u>F>dxkuBsbO#(c}5)3Pwcvd*zw{mNsBDo;Khx%1a4ncxk8s?Aa6gV-w z5axtrKj+5m&Vev63}xTTRybwJs4!{dHei}n5gL?pRLx;OIb^vI*J7+$}T!>6YW_r`*ZKter zUSGyrEqSZYmZZJw=Sr)_hsTCRZ_T_1_Vt3DDycc!3VYH&>7S`h7w;HpgH4`st(06V zC!S2ZYUYZ{$G44b6J6CDb|XLPj{rkJF+|A91br&rL6Ph?N@+V}*f5$`1{g9-p+9+cEMuFZwS9lvvUVt&N9JH0 zOUf4Lz0yM38N6-XPN+MJ&{`=9q}$xOztcRKZZl8rA=9FHGUc5o5A-LG>@!uM&oJaD z)_baj7%ve^3ty%0nsWuoS@bP(`bf^=+?=Tre`ykhCGnSV>VG3%1j!yGca-ZtBxnAo zAX=Ld6*=Ui5U~ktm`n8ZBoWfZe}Vj$#SZ}VEYa{GHp)E$)Fci_{CDwv=_Tkf;`xBfH5)z@oNdKf!Hn4=nsUiYlZMW#H(Qxb#6W%_!K9|4g5=!INXrenD z<+npX$@v7d$K%ciVc^^YnGM^d4clfnG#{r37s?q%v zN}^XmQ`wAXJ7#J=X}a7bc7;f;XQ312m1k_HZD(!Mh3T^UN8H&?h-)^@{D}18@4lpq zGxTbSUM;RaAU<;_^9(0F!-@3jG|elLrhU|Ys$q6Dh|4>0kZQCgP4CQ!ZoNuxTaM|7 zp~x9;xM1(!4bg`v0oQ?ieOdioqX14Bh~8?{cs|IEhP=39V$8YdBRI7>hEUqfgRVf$ zYP(uq3Z>UPXgOs)ul5r3-K^w#SJbrKWo$GVn_ZI+Kmy4xTR0f!luMfd10`h9@eX4{ zC27f*e{4X_V=tI;pI0S*7%@ttFa*N&CGj$V<}^{TIm8z@=n3KQ0n{qtY)54)00;mk#62KEqaF&Z zA_{7x9%mpfuzyBgEZehu1R)v}TH&ywyqlwJk02XTSTU-%+>?+U40Y~ot>NccMlR5z zN~HkQNMHv@%Yfn`C`F2LT@dQ@d$>K=tfh?$#}uuft44ykKiqwU!)?l~#<*9jSLr;< zaA8bahYoIE?pbi;qMXJ;ic-$N3@9c)lfwHvG1QlVy$ay79QQ*g$m5C3GP`>rGFZNx z*{SM14xGOV&T$IZNyW-DWv9!|)=X`>ux)bNj8EFIJzd^B;+dmMROVJPZ6<8xLC9UU zYR*-GT5kJj`&{L!Oyx$Yaw7>eNtI0tc1xK9bYj62{d%J3+@_ftkuJMRH!B@XKvDz8)&!j)1dBt$h{i=>w1YU`3uWs!V1O(zygW%T#C} zDl3QxHLk2iGX(SZ4^~6hX{3|YB{jf`1P(TJk*NeS9!%!@=8&1Qf${C7-%<=8%aC`)iR z?TGn#H21zN>M6O=)!G?03R&49pS;kAIJM-vONlI|>&0~L!q_zQA2tbD+y9V>p&9#> zLmSV7)Ua7Vk%D~rkb1~CWM-V8`@1!H2JXH*_|xhK>7puI!#!F^_rYdD3oEsFU$qv} z{hZ!dhAe$+^%N}x{m#+$m$jd@a`e?`C3Jr%_XY!*2KoAsb1eB7FH<;VfLocY>DJ0n+8g$n_T^p-qLbbbV z_tx$QvB%4>Jz=movPT6Lt{f6^MU@g#{b1*J;#~&ZDq>TEUV)AEhZHrwv5$wGhJjm_ zq|npw@FZMWvhnT&18P?r7lc^?>BVy^|ejr2N9))vRtC_st7kVas#-bG_=l5og( zu&l85yMK&Ig`r3bHZ z6(P^58av=}H!K?yJW7FZ0b}kwv3A)OjP$o* zzy&J}cTH^_Ee8UvAjfhM0WNtsHkNpv|{xmwd|42*gDq=mGU?;5F_zZY#KX(un3(OnR8yoGAsZcD9%My znW84CsA;Zh?ObVt2zMY!NmqXnDU z=NO^?;-Xx>dE*NAPUDxQmE!|r1DVn~skCmkwEmPCgu|*;XWCD164J zRgTA7e!juJ?DybR|jfKuWb&kK-o_S!m^uX@f;yqZd&G5$qb5QQlu}3FTVqI(67r;Vo zSg5dQ+&*TX@Ta{sUwJDs-nEi5s zR{ZI4T>v;{Vr+0p;LL&Cy9og1?3!4X)(c`#7TA?_L$@#c5J0CL%ZgMWE@Z`|e8NC3 zTtoe9^g6WAou33i8Prvme0P2Xkh9COz^zxnxEQxSmnmz?x(xW=)qsDApfIM+X2u!mD|i;gSJB6C5e}kaZJ8_8>-4m9x>tF1)wp2=Sy%CItq#` z+%5p=73rrCBUdGhG~7mqdWU)uTK@nHJ>wg&I#IcFF{ARA$0J_&?q{}ojn7(opNryx5yi}9a(1ZnRHSP6-7?FeEU<8L8t}$1}TPJzz(%yQw1Rr;fIWyjR$y=ZHHs)gd zeSKHGN9H#pwEM6d%8+i|bQlWPBeJjmW?`iY>^l|%douleN*sJ)_W2<(IV?W-V#a$+ z@*X32zC*?HizR_(>bH)ThQK=0?;gU)XYP`~{ie^>?leN=$~sG6qwPw)4a4_a0$XiY z9wy;td)ou1KhSI1noNIavOstdo+HOXHTcG(u%!PVyASuQ&S$^%qYsoGmwY;+l1CpX zXDs=+x^L0a(kCBxX(?rfha_39ecS$j=UU;(2x3Fd)vC%~Z+p3Vl0%yzhxowV4^F4w z!XZp@Xk#MpK)6j9dI%k~vMsWWJqCnDr*_Gu*cB{r9GvCvKCTt7%|6>$moDD9^x4Lj z1;rh+P-ecvBao}EJK5rDpnGHos?s7vv!7;gQLT(b z^UG^qMZ9?*)Zy0wh~Sm2_6BzvZkRc2V3{ooMT13;Wx$ZCzE}HCNaCH~WKD{1c!(j( zUx?56P(_1he2!2EUic*8axo;kLZRo9;i#Ho3x$|OcPK<2s*zVYTrGA1B^i!I=UH^Z z=%7hWzv$#}J(cTCxB6BN-T*{HAs7YjD`Je>p(tJjm=zr_CbWU`*H@`OnKvz%%Po&w z-{iLVuHRE?*?#@L3d;_|^@oj?n(MUDQk!+$*Xc^jPUH10HI^O5>m3HCrS^X-jFwU| zQc^J}xc>&#!uy6JkvM=2xyazmKq6R3ppSrQ6T<92Si`5w z$`eEyN%-PWd5Gx2Xfo2MzAGg6BJv8ANbYo$I}UOcB5S$TM^MUhDdT}7q}v>iJ;be| zy&X{2X*+ijXt>Gv7H~jkG8q0u-S;`=`!A|VqMAOZoS##k&nf#CR2BKJ{(>t1f?D$h Z<%j=*wbf)OJ+)%Ic&zwq3d5v{{{z^le^LMd literal 0 HcmV?d00001 diff --git a/app/ui/__pycache__/main_window.cpython-314.pyc b/app/ui/__pycache__/main_window.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..81a947d617eed85e4004fe5701d473c6f8478e76 GIT binary patch literal 12349 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 literal 0 HcmV?d00001 diff --git a/app/ui/__pycache__/note_editor.cpython-312.pyc b/app/ui/__pycache__/note_editor.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..5fae9bd139f2bddcde1b95ddab98dca73f0c3e6b GIT binary patch literal 13373 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 Kj>0mT<$nWB@wUAH literal 0 HcmV?d00001 diff --git a/app/ui/__pycache__/notes_list.cpython-314.pyc b/app/ui/__pycache__/notes_list.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..9ea1fbe2643f89095a7c143b394a22d9b4b5f52a GIT binary patch literal 11876 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 IY0}mI0$6-T^Z)<= literal 0 HcmV?d00001 diff --git a/app/ui/__pycache__/sidebar.cpython-312.pyc b/app/ui/__pycache__/sidebar.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..be120585f6159788b71e3edddeda1ee489783724 GIT binary patch literal 3464 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 literal 0 HcmV?d00001 diff --git a/app/ui/calendar_widget.py b/app/ui/calendar_widget.py new file mode 100644 index 0000000..2eb5e2c --- /dev/null +++ b/app/ui/calendar_widget.py @@ -0,0 +1,189 @@ +"""Виджет календаря""" +import tkinter as tk +from tkinter import ttk +import calendar +from datetime import datetime +from app.config import Config + + +class CalendarWidget(tk.Frame): + """Кастомный виджет календаря""" + + def __init__(self, parent, db, on_date_select=None): + super().__init__(parent, bg=Config.COLORS['calendar_bg']) + self.db = db + self.on_date_select = on_date_select + + self.current_date = datetime.now() + self.selected_date = datetime.now() + + self.create_widgets() + self.update_calendar() + + def create_widgets(self): + # Заголовок с месяцем и годом + self.header_frame = tk.Frame(self, bg=Config.COLORS['calendar_bg']) + self.header_frame.pack(fill='x', pady=(10, 5)) + + self.prev_btn = tk.Button( + self.header_frame, + text="◀", + command=self.prev_month, + bg=Config.COLORS['calendar_bg'], + fg=Config.COLORS['fg'], + relief='flat', + font=('Arial', 12), + cursor='hand2' + ) + self.prev_btn.pack(side='left', padx=(20, 0)) + + self.month_label = tk.Label( + self.header_frame, + text="", + bg=Config.COLORS['calendar_bg'], + fg=Config.COLORS['calendar_header'], + font=('Arial', 14, 'bold') + ) + self.month_label.pack(side='left', expand=True) + + self.next_btn = tk.Button( + self.header_frame, + text="▶", + command=self.next_month, + bg=Config.COLORS['calendar_bg'], + fg=Config.COLORS['fg'], + relief='flat', + font=('Arial', 12), + cursor='hand2' + ) + self.next_btn.pack(side='right', padx=(0, 20)) + + # Дни недели + days_frame = tk.Frame(self, bg=Config.COLORS['calendar_bg']) + days_frame.pack(fill='x', padx=5, pady=5) + + days = ['Пн', 'Вт', 'Ср', 'Чт', 'Пт', 'Сб', 'Вс'] + for i, day in enumerate(days): + days_frame.grid_columnconfigure(i, weight=1) + tk.Label( + days_frame, + text=day, + bg=Config.COLORS['calendar_bg'], + fg=Config.COLORS['fg'], + font=('Arial', 9, 'bold') + ).grid(row=0, column=i) + + # Сетка календаря + self.calendar_frame = tk.Frame(self, bg=Config.COLORS['calendar_bg']) + self.calendar_frame.pack(fill='both', expand=True, padx=5, pady=5) + + self.day_buttons = [] + for row in range(6): + self.calendar_frame.grid_rowconfigure(row, weight=1) + for col in range(7): + self.calendar_frame.grid_columnconfigure(col, weight=1) + btn = tk.Label( + self.calendar_frame, + text="", + bg=Config.COLORS['calendar_bg'], + fg=Config.COLORS['fg'], + font=('Arial', 10), + cursor='hand2' + ) + btn.grid(row=row, column=col, sticky='nsew', padx=1, pady=1) + btn.bind('', lambda e, r=row, c=col: self.on_day_click(r, c)) + self.day_buttons.append(btn) + + def update_calendar(self): + year = self.current_date.year + month = self.current_date.month + + # Обновляем заголовок + months = ['Январь', 'Февраль', 'Март', 'Апрель', 'Май', 'Июнь', + 'Июль', 'Август', 'Сентябрь', 'Октябрь', 'Ноябрь', 'Декабрь'] + self.month_label.config(text=f"{months[month - 1]} {year}") + + # Получаем дни с заметками + days_with_notes = self.db.get_dates_with_notes(year, month) + + # Создаем календарь + cal = calendar.monthcalendar(year, month) + + for idx, btn in enumerate(self.day_buttons): + row = idx // 7 + col = idx % 7 + + if row < len(cal) and col < len(cal[row]): + day = cal[row][col] + if day != 0: + btn.config(text=str(day)) + + # Определяем цвет фона + today = datetime.now() + is_today = (day == today.day and + month == today.month and + year == today.year) + + is_selected = (day == self.selected_date.day and + month == self.selected_date.month and + year == self.selected_date.year) + + has_notes = day in days_with_notes + + if is_today and is_selected: + btn.config( + bg=Config.COLORS['today_bg'], + fg=Config.COLORS['fg'] + ) + elif is_today: + btn.config( + bg=Config.COLORS['today_bg'], + fg=Config.COLORS['fg'] + ) + elif is_selected: + btn.config( + bg=Config.COLORS['accent'], + fg=Config.COLORS['fg'] + ) + elif has_notes: + btn.config( + bg='#5a7a3a', + fg=Config.COLORS['fg'] + ) + else: + btn.config( + bg=Config.COLORS['calendar_bg'], + fg=Config.COLORS['fg'] + ) + else: + btn.config(text="", bg=Config.COLORS['calendar_bg']) + else: + btn.config(text="", bg=Config.COLORS['calendar_bg']) + + def prev_month(self): + if self.current_date.month == 1: + self.current_date = self.current_date.replace(year=self.current_date.year - 1, month=12) + else: + self.current_date = self.current_date.replace(month=self.current_date.month - 1) + self.update_calendar() + + def next_month(self): + if self.current_date.month == 12: + self.current_date = self.current_date.replace(year=self.current_date.year + 1, month=1) + else: + self.current_date = self.current_date.replace(month=self.current_date.month + 1) + self.update_calendar() + + def on_day_click(self, row, col): + year = self.current_date.year + month = self.current_date.month + cal = calendar.monthcalendar(year, month) + + if row < len(cal) and col < len(cal[row]): + day = cal[row][col] + if day != 0: + self.selected_date = datetime(year, month, day) + self.update_calendar() + + if self.on_date_select: + self.on_date_select(self.selected_date) \ No newline at end of file diff --git a/app/ui/dialogs.py b/app/ui/dialogs.py new file mode 100644 index 0000000..0664ee2 --- /dev/null +++ b/app/ui/dialogs.py @@ -0,0 +1,219 @@ +"""Диалоговые окна""" +import tkinter as tk +from tkinter import ttk, messagebox +from app.config import Config + + +class PasswordDialog(tk.Toplevel): + """Диалог установки/ввода мастер-пароля""" + + def __init__(self, parent, auth, mode='set'): + super().__init__(parent) + self.auth = auth + self.mode = mode # 'set' или 'check' + self.result = False + + self.title("Мастер-пароль" if mode == 'set' else "Введите пароль") + self.geometry("400x250") + self.resizable(False, False) + self.configure(bg=Config.COLORS['bg']) + + self.transient(parent) + self.grab_set() + + self.create_widgets() + + # Центрируем окно + self.update_idletasks() + x = parent.winfo_rootx() + (parent.winfo_width() - 400) // 2 + y = parent.winfo_rooty() + (parent.winfo_height() - 250) // 2 + self.geometry(f"+{x}+{y}") + + def create_widgets(self): + main_frame = tk.Frame(self, bg=Config.COLORS['bg']) + main_frame.pack(expand=True, fill='both', padx=20, pady=20) + + if self.mode == 'set': + text = "Придумайте мастер-пароль\nдля защиты ваших заметок:" + else: + text = "Введите мастер-пароль:" + + tk.Label( + main_frame, + text=text, + bg=Config.COLORS['bg'], + fg=Config.COLORS['fg'], + font=('Arial', 12), + wraplength=350 + ).pack(pady=(0, 15)) + + # Поле ввода пароля + self.password_var = tk.StringVar() + self.password_entry = ttk.Entry( + main_frame, + textvariable=self.password_var, + show='*', + font=('Arial', 12) + ) + self.password_entry.pack(fill='x', pady=(0, 10)) + self.password_entry.focus() + + if self.mode == 'set': + # Подтверждение пароля + tk.Label( + main_frame, + text="Подтвердите пароль:", + bg=Config.COLORS['bg'], + fg=Config.COLORS['fg'], + font=('Arial', 11) + ).pack() + + self.confirm_var = tk.StringVar() + self.confirm_entry = ttk.Entry( + main_frame, + textvariable=self.confirm_var, + show='*', + font=('Arial', 12) + ) + self.confirm_entry.pack(fill='x', pady=(0, 15)) + + # Чекбокс "спрашивать при входе" + self.ask_var = tk.BooleanVar(value=True) + tk.Checkbutton( + main_frame, + text="Спрашивать при входе", + variable=self.ask_var, + bg=Config.COLORS['bg'], + fg=Config.COLORS['fg'], + selectcolor=Config.COLORS['bg'], + activebackground=Config.COLORS['bg'], + activeforeground=Config.COLORS['fg'] + ).pack(pady=(0, 15)) + + # Кнопки + button_frame = tk.Frame(main_frame, bg=Config.COLORS['bg']) + button_frame.pack(fill='x') + + if self.mode == 'set': + tk.Button( + button_frame, + text="Установить пароль", + command=self.set_password, + bg=Config.COLORS['accent'], + fg=Config.COLORS['fg'], + font=('Arial', 11, 'bold'), + relief='flat', + cursor='hand2' + ).pack(fill='x', pady=(0, 5)) + else: + tk.Button( + button_frame, + text="Войти", + command=self.check_password, + bg=Config.COLORS['accent'], + fg=Config.COLORS['fg'], + font=('Arial', 11, 'bold'), + relief='flat', + cursor='hand2' + ).pack(fill='x', pady=(0, 5)) + + if self.mode == 'check': + tk.Button( + button_frame, + text="Отмена", + command=self.destroy, + bg=Config.COLORS['button_bg'], + fg=Config.COLORS['fg'], + relief='flat', + cursor='hand2' + ).pack(fill='x') + + def set_password(self): + password = self.password_var.get() + confirm = self.confirm_var.get() + + if not password: + messagebox.showwarning("Ошибка", "Введите пароль") + return + + if password != confirm: + messagebox.showwarning("Ошибка", "Пароли не совпадают") + return + + self.auth.set_master_password(password) + self.auth.set_ask_password_on_startup(self.ask_var.get()) + self.result = True + self.destroy() + + def check_password(self): + password = self.password_var.get() + + if self.auth.check_password(password): + self.result = True + self.destroy() + else: + messagebox.showerror("Ошибка", "Неверный пароль") + self.password_var.set('') + self.password_entry.focus() + + +class ConfirmDialog(tk.Toplevel): + """Диалог подтверждения действия""" + + def __init__(self, parent, title, message): + super().__init__(parent) + self.result = False + + self.title(title) + self.geometry("350x150") + self.resizable(False, False) + self.configure(bg=Config.COLORS['bg']) + + self.transient(parent) + self.grab_set() + + # Центрируем окно + self.update_idletasks() + x = parent.winfo_rootx() + (parent.winfo_width() - 350) // 2 + y = parent.winfo_rooty() + (parent.winfo_height() - 150) // 2 + self.geometry(f"+{x}+{y}") + + # Сообщение + tk.Label( + self, + text=message, + bg=Config.COLORS['bg'], + fg=Config.COLORS['fg'], + font=('Arial', 11), + wraplength=320 + ).pack(expand=True, pady=(20, 10)) + + # Кнопки + btn_frame = tk.Frame(self, bg=Config.COLORS['bg']) + btn_frame.pack(fill='x', padx=20, pady=(0, 20)) + + tk.Button( + btn_frame, + text="Да", + command=self.confirm, + bg=Config.COLORS['danger'], + fg=Config.COLORS['fg'], + relief='flat', + cursor='hand2', + width=10 + ).pack(side='left', padx=(0, 10)) + + tk.Button( + btn_frame, + text="Нет", + command=self.destroy, + bg=Config.COLORS['button_bg'], + fg=Config.COLORS['fg'], + relief='flat', + cursor='hand2', + width=10 + ).pack(side='left') + + def confirm(self): + self.result = True + self.destroy() \ No newline at end of file diff --git a/app/ui/main_window.py b/app/ui/main_window.py new file mode 100644 index 0000000..4c5fb5f --- /dev/null +++ b/app/ui/main_window.py @@ -0,0 +1,212 @@ +"""Главное окно приложения""" +import tkinter as tk +from tkinter import ttk, filedialog, messagebox +import json +import zipfile +import os +from datetime import datetime +from app.config import Config +from app.database import Database +from app.auth import Auth +from app.ui.sidebar import Sidebar +from app.ui.note_editor import NoteEditor +from app.ui.dialogs import PasswordDialog + + +class MainWindow(tk.Tk): + """Главное окно приложения""" + + def __init__(self): + super().__init__() + + self.title(Config.APP_NAME) + self.geometry(f"{Config.WINDOW_WIDTH}x{Config.WINDOW_HEIGHT}") + self.minsize(Config.MIN_WINDOW_WIDTH, Config.MIN_WINDOW_HEIGHT) + self.configure(bg=Config.COLORS['bg']) + + # Инициализация компонентов + self.db = Database() + self.auth = Auth(self.db) + + # Проверка пароля + if not self.check_auth(): + self.destroy() + return + + # Создание интерфейса + self.create_menu() + self.create_widgets() + + # Загрузка заметок для текущей даты + self.sidebar.refresh_notes() + + # Привязка закрытия окна + self.protocol("WM_DELETE_WINDOW", self.on_closing) + + def check_auth(self): + """Проверка аутентификации""" + if self.auth.is_password_set(): + if self.auth.ask_password_on_startup(): + dialog = PasswordDialog(self, self.auth, mode='check') + self.wait_window(dialog) + return dialog.result + else: + # Предлагаем создать пароль + dialog = PasswordDialog(self, self.auth, mode='set') + self.wait_window(dialog) + return dialog.result + + return True + + def create_menu(self): + """Создание меню""" + menubar = tk.Menu(self) + self.config(menu=menubar) + + # Меню Файл + file_menu = tk.Menu(menubar, tearoff=0) + menubar.add_cascade(label="Файл", menu=file_menu) + + file_menu.add_command(label="Экспорт в ZIP", command=self.export_to_zip, accelerator="Ctrl+E") + file_menu.add_command(label="Импорт из ZIP", command=self.import_from_zip, accelerator="Ctrl+I") + file_menu.add_separator() + file_menu.add_command(label="Выход", command=self.on_closing, accelerator="Ctrl+Q") + + # Меню Настройки + settings_menu = tk.Menu(menubar, tearoff=0) + menubar.add_cascade(label="Настройки", menu=settings_menu) + + settings_menu.add_command(label="Изменить мастер-пароль", command=self.change_password) + + # Горячие клавиши + self.bind('', lambda e: self.export_to_zip()) + self.bind('', lambda e: self.import_from_zip()) + self.bind('', lambda e: self.on_closing()) + + def create_widgets(self): + """Создание виджетов интерфейса""" + # Основной контейнер + main_container = tk.Frame(self, bg=Config.COLORS['bg']) + main_container.pack(fill='both', expand=True) + + # Боковая панель + self.sidebar = Sidebar( + main_container, + self.db, + on_note_select=self.on_note_selected, + on_note_delete=self.on_note_deleted + ) + + # Разделитель + separator = tk.Frame(main_container, width=2, bg=Config.COLORS['separator']) + separator.pack(side='left', fill='y') + + # Редактор заметок + self.editor = NoteEditor( + main_container, + self.db, + on_note_saved=self.on_note_saved, + on_note_deleted=self.on_note_deleted, + on_new_note=self.on_new_note_created + ) + self.editor.pack(side='left', fill='both', expand=True) + + def on_note_selected(self, note): + """Обработчик выбора заметки""" + self.editor.show_note(note) + + def on_note_saved(self, note): + """Обработчик сохранения заметки""" + self.sidebar.refresh_notes() + + def on_note_deleted(self): + """Обработчик удаления заметки""" + self.sidebar.refresh_notes() + + def on_new_note_created(self): + """Обработчик создания новой заметки""" + pass + + def export_to_zip(self): + """Экспорт данных в ZIP архив""" + filename = filedialog.asksaveasfilename( + defaultextension=".zip", + filetypes=[("ZIP files", "*.zip"), ("All files", "*.*")], + initialfile=f"captains_log_backup_{datetime.now().strftime('%Y%m%d_%H%M%S')}.zip" + ) + + if filename: + try: + # Экспортируем данные + data = self.db.export_data() + + # Создаем временный JSON файл + temp_json = os.path.join(os.path.dirname(filename), 'temp_export.json') + with open(temp_json, 'w', encoding='utf-8') as f: + json.dump(data, f, ensure_ascii=False, indent=2) + + # Создаем ZIP архив + with zipfile.ZipFile(filename, 'w', zipfile.ZIP_DEFLATED) as zipf: + zipf.write(temp_json, 'captains_log_backup.json') + + # Удаляем временный файл + os.remove(temp_json) + + messagebox.showinfo("Успех", f"Данные экспортированы в:\n{filename}") + except Exception as e: + messagebox.showerror("Ошибка", f"Не удалось экспортировать данные:\n{str(e)}") + + def import_from_zip(self): + """Импорт данных из ZIP архива""" + filename = filedialog.askopenfilename( + filetypes=[("ZIP files", "*.zip"), ("All files", "*.*")] + ) + + if filename: + # Подтверждение + if not messagebox.askyesno( + "Подтверждение", + "Импорт данных заменит все существующие заметки.\nПродолжить?" + ): + return + + try: + # Извлекаем из ZIP + with zipfile.ZipFile(filename, 'r') as zipf: + temp_json = os.path.join(os.path.dirname(filename), 'temp_import.json') + zipf.extract('captains_log_backup.json', os.path.dirname(filename)) + + # Читаем JSON + with open(temp_json, 'r', encoding='utf-8') as f: + data = json.load(f) + + # Импортируем данные + self.db.import_data(data) + + # Удаляем временный файл + os.remove(temp_json) + + # Обновляем интерфейс + self.sidebar.refresh_notes() + self.editor.show_empty_state() + + messagebox.showinfo("Успех", "Данные успешно импортированы") + except Exception as e: + messagebox.showerror("Ошибка", f"Не удалось импортировать данные:\n{str(e)}") + + def change_password(self): + """Изменение мастер-пароля""" + if self.auth.is_password_set(): + dialog = PasswordDialog(self, self.auth, mode='check') + self.wait_window(dialog) + + if dialog.result: + dialog = PasswordDialog(self, self.auth, mode='set') + self.wait_window(dialog) + else: + dialog = PasswordDialog(self, self.auth, mode='set') + self.wait_window(dialog) + + def on_closing(self): + """Обработчик закрытия окна""" + self.destroy() \ No newline at end of file diff --git a/app/ui/note_editor.py b/app/ui/note_editor.py new file mode 100644 index 0000000..c4ba58b --- /dev/null +++ b/app/ui/note_editor.py @@ -0,0 +1,297 @@ +"""Редактор заметок""" +import tkinter as tk +from tkinter import ttk, messagebox +from datetime import datetime +from app.config import Config +from app.ui.dialogs import ConfirmDialog + +class NoteEditor(tk.Frame): + """Правая панель с редактором заметок""" + + def __init__(self, parent, db, on_note_saved=None, on_note_deleted=None, on_new_note=None): + super().__init__(parent, bg=Config.COLORS['editor_bg']) + self.db = db + self.on_note_saved = on_note_saved + self.on_note_deleted = on_note_deleted + self.on_new_note = on_new_note + + self.current_note = None + self.is_new_note = False + self.is_editing = False + + self.create_widgets() + self.show_empty_state() + + def create_widgets(self): + # Панель инструментов + self.toolbar = tk.Frame(self, bg=Config.COLORS['sidebar_bg'], height=40) + self.toolbar.pack(fill='x') + self.toolbar.pack_propagate(False) + + # Дата и время (левый край) + self.date_label = tk.Label( + self.toolbar, + text="", + bg=Config.COLORS['sidebar_bg'], + fg=Config.COLORS['fg'], + font=('Arial', 11) + ) + self.date_label.pack(side='left', padx=10) + + # Кнопки (правый край) + buttons_frame = tk.Frame(self.toolbar, bg=Config.COLORS['sidebar_bg']) + buttons_frame.pack(side='right', padx=5) + + # Кнопка сохранения + self.save_btn = tk.Button( + buttons_frame, + text="💾", + command=self.save_note, + bg=Config.COLORS['button_bg'], + fg=Config.COLORS['fg'], + relief='flat', + font=('Arial', 14), + cursor='hand2', + width=2 + ) + self.save_btn.pack(side='left', padx=2) + + # Кнопка удаления + self.delete_btn = tk.Button( + buttons_frame, + text="🗑", + command=self.delete_current_note, + bg=Config.COLORS['button_bg'], + fg=Config.COLORS['fg'], + relief='flat', + font=('Arial', 14), + cursor='hand2', + width=2 + ) + self.delete_btn.pack(side='left', padx=2) + + # Кнопка создания новой заметки + self.new_btn = tk.Button( + buttons_frame, + text="+", + command=self.create_new_note, + bg=Config.COLORS['accent'], + fg=Config.COLORS['fg'], + relief='flat', + font=('Arial', 14, 'bold'), + cursor='hand2', + width=2 + ) + self.new_btn.pack(side='left', padx=2) + + # Скрываем кнопки сохранения и удаления по умолчанию + self.hide_editor_buttons() + + # Область контента + self.content_frame = tk.Frame(self, bg=Config.COLORS['editor_bg']) + self.content_frame.pack(fill='both', expand=True, padx=10, pady=10) + + # Текстовое поле для редактирования + self.text_widget = tk.Text( + self.content_frame, + bg=Config.COLORS['text_bg'], + fg=Config.COLORS['text_fg'], + font=('Arial', 12), + wrap='word', + relief='flat', + padx=10, + pady=10, + insertbackground=Config.COLORS['fg'] + ) + + # Scrollbar для текстового поля + self.text_scrollbar = ttk.Scrollbar( + self.content_frame, + orient='vertical', + command=self.text_widget.yview + ) + self.text_widget.configure(yscrollcommand=self.text_scrollbar.set) + + # Метка для пустого состояния + self.empty_label = tk.Label( + self.content_frame, + text="Выберите заметку из списка\nили создайте новую", + bg=Config.COLORS['editor_bg'], + fg=Config.COLORS['fg'], + font=('Arial', 14), + justify='center' + ) + self.empty_label.pack(expand=True) + + # Привязываем события изменения текста + self.text_widget.bind('<>', self.on_text_modified) + + def show_editor_buttons(self): + """Показать кнопки редактора""" + self.save_btn.pack(side='left', padx=2) + self.delete_btn.pack(side='left', padx=2) + + def hide_editor_buttons(self): + """Скрыть кнопки редактора""" + self.save_btn.pack_forget() + self.delete_btn.pack_forget() + + def show_empty_state(self): + """Показать пустое состояние""" + self.text_widget.pack_forget() + self.text_scrollbar.pack_forget() + self.empty_label.pack(expand=True) + self.date_label.config(text="") + self.hide_editor_buttons() + self.current_note = None + self.is_new_note = False + self.is_editing = False + + def show_note(self, note): + """Показать существующую заметку""" + self.current_note = note + self.is_new_note = False + self.is_editing = False + + # Скрываем пустую метку + self.empty_label.pack_forget() + + # Показываем текстовое поле + self.text_widget.pack(side='left', fill='both', expand=True) + self.text_scrollbar.pack(side='right', fill='y') + + # Устанавливаем дату + created_at = datetime.strptime( + note['created_at'], '%Y-%m-%d %H:%M:%S' + ) + self.date_label.config( + text=created_at.strftime('%d.%m.%Y %H:%M') + ) + + # Очищаем и вставляем текст + self.text_widget.delete('1.0', 'end') + self.text_widget.insert('1.0', note.get('content', '')) + self.text_widget.edit_modified(False) + + # Показываем кнопки + self.show_editor_buttons() + + # Делаем поле только для чтения + self.text_widget.configure(state='disabled') + + # Привязываем клик для редактирования + self.text_widget.bind('', self.enable_editing) + + def enable_editing(self, event=None): + """Включить режим редактирования""" + if not self.is_editing and self.current_note: + self.is_editing = True + self.text_widget.configure(state='normal') + self.text_widget.unbind('') + + def create_new_note(self): + """Создать новую заметку""" + self.current_note = None + self.is_new_note = True + self.is_editing = True + + # Скрываем пустую метку + self.empty_label.pack_forget() + + # Показываем текстовое поле + self.text_widget.pack(side='left', fill='both', expand=True) + self.text_scrollbar.pack(side='right', fill='y') + + # Устанавливаем текущую дату + now = datetime.now() + self.date_label.config(text=now.strftime('%d.%m.%Y %H:%M')) + + # Очищаем текстовое поле + self.text_widget.delete('1.0', 'end') + self.text_widget.configure(state='normal') + self.text_widget.edit_modified(False) + + # Показываем кнопки + self.show_editor_buttons() + + # Убираем привязку клика + self.text_widget.unbind('') + + # Ставим фокус + self.text_widget.focus_set() + + if self.on_new_note: + self.on_new_note() + + def save_note(self): + """Сохранить заметку""" + content = self.text_widget.get('1.0', 'end-1c') + + if not content.strip() and self.is_new_note: + messagebox.showwarning( + "Предупреждение", + "Нельзя сохранить пустую заметку" + ) + return + + if self.is_new_note: + # Создаем новую заметку + now = datetime.now().strftime('%Y-%m-%d %H:%M:%S') + note_id = self.db.create_note( + title=None, + content=content, + created_at=now + ) + self.current_note = self.db.get_note_by_id(note_id) + self.is_new_note = False + self.is_editing = False + + # Делаем поле только для чтения + self.text_widget.configure(state='disabled') + self.text_widget.bind('', self.enable_editing) + + messagebox.showinfo("Успех", "Заметка сохранена") + else: + # Обновляем существующую заметку + self.db.update_note( + self.current_note['id'], + content=content + ) + self.current_note = self.db.get_note_by_id(self.current_note['id']) + self.is_editing = False + + # Делаем поле только для чтения + self.text_widget.configure(state='disabled') + self.text_widget.bind('', self.enable_editing) + + messagebox.showinfo("Успех", "Заметка обновлена") + + self.text_widget.edit_modified(False) + + if self.on_note_saved: + self.on_note_saved(self.current_note) + + def delete_current_note(self): + """Удалить текущую заметку""" + if not self.current_note: + return + + dialog = ConfirmDialog( + self.winfo_toplevel(), + "Удаление заметки", + "Вы уверены, что хотите удалить эту заметку?" + ) + self.wait_window(dialog) + + if dialog.result: + self.db.delete_note(self.current_note['id']) + self.show_empty_state() + + if self.on_note_deleted: + self.on_note_deleted() + + def on_text_modified(self, event=None): + """Обработчик изменения текста""" + if self.text_widget.edit_modified() and self.current_note and not self.is_editing: + # Текст был изменен программно, игнорируем + self.text_widget.edit_modified(False) \ No newline at end of file diff --git a/app/ui/notes_list.py b/app/ui/notes_list.py new file mode 100644 index 0000000..ba72a82 --- /dev/null +++ b/app/ui/notes_list.py @@ -0,0 +1,230 @@ +"""Список заметок""" +import tkinter as tk +from tkinter import ttk +from datetime import datetime +from app.config import Config +from app.ui.dialogs import ConfirmDialog + + +class NotesList(tk.Frame): + """Виджет списка заметок""" + + def __init__(self, parent, db, on_note_select=None, on_note_delete=None): + super().__init__(parent, bg=Config.COLORS['list_bg']) + self.db = db + self.on_note_select = on_note_select + self.on_note_delete = on_note_delete + + self.notes = [] + self.selected_note_id = None + + self.create_widgets() + + def create_widgets(self): + # Заголовок + self.header_label = tk.Label( + self, + text="Заметки", + bg=Config.COLORS['list_bg'], + fg=Config.COLORS['fg'], + font=('Arial', 12, 'bold') + ) + self.header_label.pack(fill='x', padx=10, pady=(10, 5)) + + # Разделитель + separator = tk.Frame(self, height=1, bg=Config.COLORS['separator']) + separator.pack(fill='x', padx=5) + + # Canvas и Scrollbar для списка + self.canvas = tk.Canvas( + self, + bg=Config.COLORS['list_bg'], + highlightthickness=0 + ) + self.scrollbar = ttk.Scrollbar( + self, + orient="vertical", + command=self.canvas.yview + ) + + self.notes_frame = tk.Frame(self.canvas, bg=Config.COLORS['list_bg']) + + self.notes_frame.bind("", + lambda e: self.canvas.configure(scrollregion=self.canvas.bbox("all"))) + + self.canvas_window = self.canvas.create_window( + (0, 0), window=self.notes_frame, anchor="nw" + ) + + self.canvas.configure(yscrollcommand=self.scrollbar.set) + + self.canvas.pack(side="left", fill="both", expand=True) + self.scrollbar.pack(side="right", fill="y") + + # Bind для изменения размера + self.canvas.bind('', self.on_canvas_configure) + + # Bind для колесика мыши + self.canvas.bind_all("", self.on_mousewheel) + + def on_canvas_configure(self, event): + self.canvas.itemconfig(self.canvas_window, width=event.width) + + def on_mousewheel(self, event): + self.canvas.yview_scroll(int(-1 * (event.delta / 120)), "units") + + def load_notes(self, date): + """Загрузить заметки для выбранной даты""" + # Очищаем текущий список + for widget in self.notes_frame.winfo_children(): + widget.destroy() + + date_str = date.strftime('%Y-%m-%d') + self.notes = self.db.get_notes_with_content_by_date(date_str) + + if not self.notes: + empty_label = tk.Label( + self.notes_frame, + text="Нет заметок\nна этот день", + bg=Config.COLORS['list_bg'], + fg=Config.COLORS['fg'], + font=('Arial', 10), + justify='center' + ) + empty_label.pack(expand=True, pady=20) + return + + for note in self.notes: + self.create_note_item(note) + + def create_note_item(self, note): + """Создать элемент списка для заметки""" + item_frame = tk.Frame( + self.notes_frame, + bg=Config.COLORS['list_bg'], + cursor='hand2' + ) + item_frame.pack(fill='x', padx=5, pady=2) + + # Рамка для элемента + content_frame = tk.Frame( + item_frame, + bg=Config.COLORS['list_bg'], + highlightbackground=Config.COLORS['separator'], + highlightthickness=1 + ) + content_frame.pack(fill='x', padx=5, pady=2) + + # Левая часть с датой + date_frame = tk.Frame(content_frame, bg=Config.COLORS['list_bg']) + date_frame.pack(side='left', padx=(10, 5), pady=5) + + # Получаем дату заметки + created_at = datetime.strptime( + note['created_at'], '%Y-%m-%d %H:%M:%S' + ) + + day_month = created_at.strftime('%d.%m') + year = created_at.strftime('%Y') + + tk.Label( + date_frame, + text=day_month, + bg=Config.COLORS['list_bg'], + fg=Config.COLORS['fg'], + font=('Arial', 11, 'bold') + ).pack(anchor='w') + + tk.Label( + date_frame, + text=year, + bg=Config.COLORS['list_bg'], + fg=Config.COLORS['fg'], + font=('Arial', 9) + ).pack(anchor='w') + + # Средняя часть с заголовком + title_frame = tk.Frame(content_frame, bg=Config.COLORS['list_bg']) + title_frame.pack(side='left', fill='x', expand=True, padx=10, pady=5) + + title = note.get('title', '') + if not title: + # Берем первые 22 символа текста + content = note.get('content', '') + title = content[:22] + ('...' if len(content) > 22 else '') + if not title: + title = "Пустая заметка" + + title_label = tk.Label( + title_frame, + text=title, + bg=Config.COLORS['list_bg'], + fg=Config.COLORS['fg'], + font=('Arial', 10), + anchor='w', + justify='left' + ) + title_label.pack(anchor='w') + + # Кнопка удаления + delete_btn = tk.Button( + content_frame, + text="🗑", + command=lambda n=note: self.delete_note(n), + bg=Config.COLORS['list_bg'], + fg=Config.COLORS['danger'], + relief='flat', + font=('Arial', 14), + cursor='hand2', + width=2 + ) + delete_btn.pack(side='right', padx=(0, 10), pady=5) + + # Привязываем события клика для выбора заметки + for widget in [item_frame, content_frame, date_frame, title_frame, title_label]: + widget.bind('', lambda e, n=note: self.select_note(n)) + + # Подсветка при наведении + for widget in [content_frame, date_frame, title_frame]: + widget.bind('', lambda e, f=content_frame: + f.configure(bg=Config.COLORS['list_selected'])) + widget.bind('', lambda e, f=content_frame: + f.configure(bg=Config.COLORS['list_bg'])) + + def select_note(self, note): + """Выбрать заметку""" + self.selected_note_id = note['id'] + + # Обновляем визуальное выделение + for widget in self.notes_frame.winfo_children(): + if isinstance(widget, tk.Frame): + for child in widget.winfo_children(): + if isinstance(child, tk.Frame): + child.configure(bg=Config.COLORS['list_bg']) + + if self.on_note_select: + self.on_note_select(note) + + def delete_note(self, note): + """Удалить заметку с подтверждением""" + dialog = ConfirmDialog( + self.winfo_toplevel(), + "Удаление заметки", + "Вы уверены, что хотите удалить эту заметку?" + ) + self.wait_window(dialog) + + if dialog.result: + self.db.delete_note(note['id']) + + if self.on_note_delete: + self.on_note_delete() + + # Перезагружаем список + if hasattr(self, 'current_date'): + self.load_notes(self.current_date) + + def refresh(self, date): + """Обновить список заметок""" + self.current_date = date + self.load_notes(date) \ No newline at end of file diff --git a/app/ui/sidebar.py b/app/ui/sidebar.py new file mode 100644 index 0000000..6e2814b --- /dev/null +++ b/app/ui/sidebar.py @@ -0,0 +1,59 @@ +"""Боковая панель""" +import tkinter as tk +from app.config import Config +from app.ui.calendar_widget import CalendarWidget +from app.ui.notes_list import NotesList + + +class Sidebar(tk.Frame): + """Левая боковая панель""" + + def __init__(self, parent, db, on_date_select=None, on_note_select=None, on_note_delete=None): + super().__init__(parent, bg=Config.COLORS['sidebar_bg'], width=Config.SIDEBAR_WIDTH) + self.db = db + self.on_note_select = on_note_select + + self.pack_propagate(False) + self.pack(side='left', fill='y') + + self.create_widgets(on_date_select, on_note_delete) + + def create_widgets(self, on_date_select, on_note_delete): + # Календарь + self.calendar = CalendarWidget( + self, + self.db, + on_date_select=self.on_date_selected + ) + self.calendar.pack(fill='x') + + # Разделитель + separator = tk.Frame(self, height=2, bg=Config.COLORS['separator']) + separator.pack(fill='x', padx=10, pady=(5, 0)) + + # Список заметок + self.notes_list = NotesList( + self, + self.db, + on_note_select=self.on_note_selected, + on_note_delete=on_note_delete + ) + self.notes_list.pack(fill='both', expand=True) + + def on_date_selected(self, date): + """Обработчик выбора даты в календаре""" + self.notes_list.refresh(date) + + def on_note_selected(self, note): + """Обработчик выбора заметки""" + if self.on_note_select: + self.on_note_select(note) + + def refresh_notes(self): + """Обновить список заметок для текущей даты""" + if self.calendar: + self.notes_list.refresh(self.calendar.selected_date) + + def get_selected_date(self): + """Получить выбранную дату""" + return self.calendar.selected_date if self.calendar else None \ No newline at end of file diff --git a/captains_log.db b/captains_log.db new file mode 100644 index 0000000000000000000000000000000000000000..0b4fd297c4c90893d2c4563b8c3998f1cf543d03 GIT binary patch literal 20480 zcmeI)&raJg90%~YDdo=uVcH=Fgp^G}0>MVp(0_1Q$FiwfTUbdYI7Oy$Mn_4YamP4L z({|c%&#;%^0d^d>y~3UV?Krp44G`2EdKl8zN*X7ze~mwhttM5w@w(LD%<#k39_5Cm zOe%yZPYpv+l!Tav#N10Uk%{+K#FAXd-;_-#Pu`x4YrmEF*oiWc(Z1h%Br-@4fB*y_ z009U<00IygkidF8q0i5gEl#(aEIMe454Uc_4%#fJUtJwqt=a|0Hk`t_>$*<9kZ#N%-6GE1>5`H|8lJT8aMZGf(+}^)8tcl*3TH{jo?iboiAgT zdPZ++zlhS#vCEG7)$jD5&XpxAbNj9#Jy-8&vwf?pU3N^BHR)kjYNb-YFmsEsguXgW zlt$pO_vb?yZS%0ZE(XA}^W&PmoGOOfk7`8k&4BUv*7H{vHlQDHh`@2KjvYVSEp8hbtMkmy%X4YnfS^N_qKul3Bjx=h9|2 z>su_78xw6(wPQs))_!R}2iHJEf&c^{009U<00Izz00bZa0SG`~AOchB3c2ACf;y?r zkc&?cV!E0hz3%xz=lg%6{ZPb&1OW&@00Izz00bZa0SG_<0uX?}-4IwL;#d0*EbAzI zm`bKHkCS4`8mZKB+FV}BUbE@!{{K|bPVYtoag-2%00bZa0SG_<0uX=z1Rwwb2;5d+ MgsA#x_a96C0HY#XcmMzZ literal 0 HcmV?d00001 diff --git a/main.py b/main.py new file mode 100644 index 0000000..b6d6533 --- /dev/null +++ b/main.py @@ -0,0 +1,20 @@ +#!/usr/bin/env python3 +""" +Captain's Log - Приложение для быстрых заметок +""" + +import sys +import os + +# Добавляем текущую директорию в путь для импорта +sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) + +from app.ui.main_window import MainWindow + +def main(): + """Точка входа в приложение""" + app = MainWindow() + app.mainloop() + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..b819adf --- /dev/null +++ b/requirements.txt @@ -0,0 +1,2 @@ +tkcalendar==1.6.1 +nuitka>=2.0.0 \ No newline at end of file