From 9d1bbdaa85ffb73b94df0dba5493c3f171b212ff Mon Sep 17 00:00:00 2001 From: Santiago Parra Date: Fri, 13 Dec 2024 02:29:47 +0100 Subject: [PATCH] Implemetacion de lo que he avanzo con el proyecto final --- app | 1 - app/ConfigMgr.py | 83 +++++++++++ app/__init__.py | 0 app/__pycache__/ConfigMgr.cpython-312.pyc | Bin 0 -> 5398 bytes app/__pycache__/ConfigMgr.cpython-313.pyc | Bin 0 -> 5475 bytes app/__pycache__/__init__.cpython-312.pyc | Bin 0 -> 161 bytes app/__pycache__/__init__.cpython-313.pyc | Bin 0 -> 143 bytes app/__pycache__/main.cpython-312.pyc | Bin 0 -> 5207 bytes app/chat_server/Server.py | 89 ++++++++++++ app/chat_server/__init__.py | 0 .../__pycache__/Server.cpython-312.pyc | Bin 0 -> 5759 bytes .../__pycache__/Server.cpython-313.pyc | Bin 0 -> 5827 bytes .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 173 bytes .../__pycache__/__init__.cpython-313.pyc | Bin 0 -> 155 bytes app/chat_server/messages.txt | 2 + app/config.ini | 4 + app/main.py | 130 ++++++++++++++++++ app/requirements.txt | 13 ++ app/widgets/ChatTab.py | 111 +++++++++++++++ app/widgets/ClockLabel.py | 16 +++ app/widgets/UsageLabels.py | 50 +++++++ app/widgets/__init__.py | 4 + .../__pycache__/ChatTab.cpython-312.pyc | Bin 0 -> 8441 bytes .../__pycache__/ChatTab.cpython-313.pyc | Bin 0 -> 8429 bytes .../__pycache__/ClockLabel.cpython-312.pyc | Bin 0 -> 1116 bytes .../__pycache__/ClockLabel.cpython-313.pyc | Bin 0 -> 1163 bytes .../__pycache__/UsageLabels.cpython-312.pyc | Bin 0 -> 3196 bytes .../__pycache__/UsageLabels.cpython-313.pyc | Bin 0 -> 3302 bytes .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 308 bytes .../__pycache__/__init__.cpython-313.pyc | Bin 0 -> 297 bytes app/widgets/abc/ThreadedLabel.py | 22 +++ app/widgets/abc/ThreadedTab.py | 26 ++++ app/widgets/abc/__init__.py | 4 + .../__pycache__/ThreadedLabel.cpython-312.pyc | Bin 0 -> 1572 bytes .../__pycache__/ThreadedLabel.cpython-313.pyc | Bin 0 -> 1652 bytes .../__pycache__/ThreadedTab.cpython-312.pyc | Bin 0 -> 1865 bytes .../__pycache__/ThreadedTab.cpython-313.pyc | Bin 0 -> 1945 bytes .../abc/__pycache__/__init__.cpython-312.pyc | Bin 0 -> 284 bytes .../abc/__pycache__/__init__.cpython-313.pyc | Bin 0 -> 273 bytes 39 files changed, 554 insertions(+), 1 deletion(-) delete mode 160000 app create mode 100644 app/ConfigMgr.py create mode 100644 app/__init__.py create mode 100644 app/__pycache__/ConfigMgr.cpython-312.pyc create mode 100644 app/__pycache__/ConfigMgr.cpython-313.pyc create mode 100644 app/__pycache__/__init__.cpython-312.pyc create mode 100644 app/__pycache__/__init__.cpython-313.pyc create mode 100644 app/__pycache__/main.cpython-312.pyc create mode 100644 app/chat_server/Server.py create mode 100644 app/chat_server/__init__.py create mode 100644 app/chat_server/__pycache__/Server.cpython-312.pyc create mode 100644 app/chat_server/__pycache__/Server.cpython-313.pyc create mode 100644 app/chat_server/__pycache__/__init__.cpython-312.pyc create mode 100644 app/chat_server/__pycache__/__init__.cpython-313.pyc create mode 100644 app/chat_server/messages.txt create mode 100644 app/config.ini create mode 100644 app/main.py create mode 100644 app/requirements.txt create mode 100644 app/widgets/ChatTab.py create mode 100644 app/widgets/ClockLabel.py create mode 100644 app/widgets/UsageLabels.py create mode 100644 app/widgets/__init__.py create mode 100644 app/widgets/__pycache__/ChatTab.cpython-312.pyc create mode 100644 app/widgets/__pycache__/ChatTab.cpython-313.pyc create mode 100644 app/widgets/__pycache__/ClockLabel.cpython-312.pyc create mode 100644 app/widgets/__pycache__/ClockLabel.cpython-313.pyc create mode 100644 app/widgets/__pycache__/UsageLabels.cpython-312.pyc create mode 100644 app/widgets/__pycache__/UsageLabels.cpython-313.pyc create mode 100644 app/widgets/__pycache__/__init__.cpython-312.pyc create mode 100644 app/widgets/__pycache__/__init__.cpython-313.pyc create mode 100644 app/widgets/abc/ThreadedLabel.py create mode 100644 app/widgets/abc/ThreadedTab.py create mode 100644 app/widgets/abc/__init__.py create mode 100644 app/widgets/abc/__pycache__/ThreadedLabel.cpython-312.pyc create mode 100644 app/widgets/abc/__pycache__/ThreadedLabel.cpython-313.pyc create mode 100644 app/widgets/abc/__pycache__/ThreadedTab.cpython-312.pyc create mode 100644 app/widgets/abc/__pycache__/ThreadedTab.cpython-313.pyc create mode 100644 app/widgets/abc/__pycache__/__init__.cpython-312.pyc create mode 100644 app/widgets/abc/__pycache__/__init__.cpython-313.pyc diff --git a/app b/app deleted file mode 160000 index 752997b..0000000 --- a/app +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 752997b2e6303a61982840307e55c910cccd6536 diff --git a/app/ConfigMgr.py b/app/ConfigMgr.py new file mode 100644 index 0000000..9d2a8ce --- /dev/null +++ b/app/ConfigMgr.py @@ -0,0 +1,83 @@ +import tkinter as tk +from tkinter import ttk +import os +import configparser + +class ConfigMgr: + + def __init__(self, top_level, config_changed_listener=None): + self.top_level = top_level + self.config_window = None + self.config = configparser.ConfigParser() + self.__load_config() + self.config_changed_listener=config_changed_listener + self.config.read('config.ini') + + def __load_config(self): + if os.path.exists('config.ini'): + self.config.read('config.ini') + else: + print("Config file not found, creating default config file") + with open('config.ini', 'w') as f: + self.__write_default_config(f) + self.config.read('config.ini') + + def __write_default_config(self, file): + chat_config = ("[Chat]\n" + "server=http://localhost:2020\n" + "name=User\n") + file.write(chat_config) + + def display_config_window(self): + if (self.config_window is None + or not tk.Toplevel.winfo_exists(self.config_window)): + self.config_window = self.__build_config_window() + else: + self.config_window.lift() + + def __build_config_window(self): + config_window = tk.Toplevel(self.top_level) + config_window.title("Config") + config_window.geometry("400x300") + + notebook = ttk.Notebook(config_window) + notebook.pack(expand=True, fill="both") + + chat_tab = ttk.Frame(notebook) + notebook.add(chat_tab, text="Chat Config") + + chat_server_label = tk.Label(chat_tab, text="Chat Server URL") + chat_server_label.pack() + self.chat_server_variable = tk.StringVar() + try: + self.chat_server_variable.set(self.config["Chat"]["server"]) + except KeyError: + self.chat_server_variable.set("") + chat_server_input = tk.Entry(chat_tab, textvariable=self.chat_server_variable) + chat_server_input.pack() + + chat_name_label = tk.Label(chat_tab, text="Name in the Chat") + chat_name_label.pack() + self.chat_name_variable = tk.StringVar() + try: + self.chat_name_variable.set(self.config["Chat"]["name"]) + except KeyError: + self.chat_name_variable.set("") + chat_name_input = tk.Entry(chat_tab, textvariable=self.chat_name_variable) + chat_name_input.pack() + + self.save_button = tk.Button(config_window, text="Save", command=self.save_config) + self.save_button.pack(pady=10) + + return config_window + + def save_config(self): + self.config["Chat"] = {"server": self.chat_server_variable.get(), + "name": self.chat_name_variable.get()} + with open('config.ini', 'w') as configfile: + self.config.write(configfile) + + self.config_changed_listener() + + # Close window + self.config_window.destroy() \ No newline at end of file diff --git a/app/__init__.py b/app/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/app/__pycache__/ConfigMgr.cpython-312.pyc b/app/__pycache__/ConfigMgr.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..409d5675ff720093b9c5b4dd5772f3752bdc6802 GIT binary patch literal 5398 zcmbUlZEO=qcGkOVkK;A@A|C`Nae|YuNgAiXT?+&x0)%kTs4Y-Idev&<-6S@S*SEVN zI5iR{@gtFtfL{dZq^9Z*Kp>T0C;nbfr|aoXr>tUbqlJ?y9e?_dgG!vZKlf(W>vbHr zqBl{VXWqPdGyC3~_c4DBhieeD@#*t3S3?N>iwxYx6g#g*pmQ5ZNTSkcl7e4f8qN78 zeUyWvCuw_*nPhzE1d`}$NMauNoYj+Tj^f$x$zXz~ELztuCY%`u7Rk`7JutqFWP5{2 zpM)jfH3}F5vq?r`0J0Jb&@cG`2BZK$46K7z@TiiRPR@KVqfR8~GS;L|{$Ksx=A5VU zNGMH92u74U)LFNe^1D6n_d&4>D_oJl>J*5pvbmHL%KL!2#0#i#B`rd!+O0T=dQdzS zg>s1ZO}wc|*q=9)%p`ei1$8AWq~%L;+6p@p!a_15DGL_o46~xD$*RRULix}c`JFkd zMR3p{D?vyrq9nKz&aaS|6*DukB&3s?E@yxbqspSh(-x!2>1maGgt`rYMg$v;M&BHX z&&g6!j87|?m|2wMOYzgm%>3o3B%OMlD8n|^O@&xz30#+zdQalWZl58{S2kdR669T)XT+?(B@S7?b ziih-6rba-~C8c{5si4<)d)_LdXuV`ilFu^$J?v3Sw0om|fv$ilS2Lb`neo7lggqe1$cAUoO+NR%5~$$Rw!6*yn& ziZ4&4$_*Frq~*&D#`YMCL!Ta<74^?*zzescz@Fhf!!;Rk zPCj%FNNT_9tN?4ucwm&tW zOuIhY_4`aZIj!@QtvZd^N74A3q@o29%&vn**TMC!Lrce=a2;Us&LWfBWpKNm za6MbKN}QS`_*G*LyTuBN~hQq}ZZ?p}eSiY_-f+LHTCkxN<8JhuvW z&{xgzO0>j0WIbo=x$_ipE)~tYu~%r|M~uFr! zD{P*9>u;?txp0}}{CWRdbNgw@k?>x;EE@{Aq0qvt+8Z7~F4`JGU1At2-k;U!T` zic@KMv2Ft5cr=-b>a%jxre#QEx0o~HB^lD9cR_GgAfe37!Oq{4T953N6{Q^Cr1rp2 zB`UNNdyCbRIs|?^BP(;VuI5ytM+;9Vx;&*Q7s;~3MT;F%$p*A2NfvuroPtUuct(e8 zZRQhEwYDcn*5Djha8GH`8t^0D#KNcK+;LS^REs^Hfjz7SdjUDKNA4e;*L5Xhg*5mG zw?uIUQ}zsx?QrKrPYg+?b3knK(#0vOzWf+z`;ePZ$xL=$w`#q;9hx$PO`(zU1Iiq` zImu6xAXJIM!8?^1XJH<@^+fp@Nq7jTa~ z>M(G}O0IzU=Z(#`M{bR*hSwX1mhc9yyAfUvubjQ-UkVp+oXqaOwSVROdSkCM%Pn&^ z&#!*4#1-(K=aG&t#_x=;o?qK{ybu{%!#g(c4igjJ1sr`A>%DvW%hO*?kW#7Zg^vyn z*%80T;Cf7sH#q)Y?CXL11COSS{zH#vjee5#{@_RLoetB-$IOv)#>lxs@v} z!p=#uORHTQk{Zy&?FMdN-Lr<<3%K{Y+Q=G?{O5-rRM+|vQCq*i+vMJPLBqsYIlKez!xGx$a$e)5lv1LO7RPs0P_Bk0@u*!X_>+xIB~M`9;;(SPZt0B%ZgNxf(% zLubJ{ek{dJ6xA_yy54u}-!#=ZMlCVq z5##0W3R)6j4&w{qP3CH4*Hu8cC;^`5<`ZUHF%k^9kXE72*R8q&8KBSusA{Z&Aga) zX4D2CRf#rLk_)P&T2)D;x213&*~!1{IaQMN+kjY*)&<}idd2wJzL#|YcHm_L!$x0n zEZh5XTQ}SOYMctPPU&E;;r&)XzX(qPvg(8_av@ZbgsCK`Im|DR9!aO|4d37q=gf!I mPXR|#XssOpTvm#Dinjj~wLC?^r>OBY^8rQmyhH@r%l`)kdO3jr literal 0 HcmV?d00001 diff --git a/app/__pycache__/ConfigMgr.cpython-313.pyc b/app/__pycache__/ConfigMgr.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..3082899c2315a67e3ec491377e910bdf17550d53 GIT binary patch literal 5475 zcmbUlOKcm*b(Xs%N90nXWc@5t(n`^9C0SCPIF9@SBFTyp+f2(112RD>y?LK`6p4folu+M$%U8k({gXD_!j&qIjzi@-l90rlMudT{?=*VLM|^_Q z#}d{a;|SM|dXdC-Ac;Hdb7m9&G9&tbppCf5Sgfv}UvfqqSVBXkMHcJ@h)-gGXo!;f zfFYd3bs*xG`~U+|0ANrG0>lyqSSQty(6UbqTXoaj^g8zm@}s?v)NmwI51hnYkP;9hlcF$Hu^$a=H@7gu8Lh{LreAv$1=H$h^;z3Uy!rvMKx7}yi+A|) zOQ#iZ#;i8k=>~SmayGA|WOu;%%1bLsZdpyq*^H*EIiSN4RY{4g#c66bP3mC7gzj3S zY!w8-dUg8fLJ}6#7BnTNXBOVhsWT*VQC*l(wezsoh1pC_$u1~`!h*N-SYge=vJ3~( zW!Y*f_q%gkQsCLc5S*yh3xKYX8WywmhXk)ONQ*m5nqHC=2Ry7~HUwAqY~ z7}1fNuNR|ZPgv9>e*etOCw}POX&id0@vz)C?M+kl?6>uysaSBSQUR%o+G;us6)?q2 z&X;5oAcpFatWX=X$8_e%`|Gb-jEO0;ZQ5v?-tFl(dyW}B$F_S;tk3N5U7-KYJd=OP z;9uI|`=1rEXOVA%=zhu~_fi(u5V0#bu#ikGaaUShz^Eb6J@(g*k>KPgL2qeQfjxNk z7OthQmPM9WiIe=3U^C~-w5XM-DnQG-z&RFZ?W;n7W|EZxQqZR54JMkss#gJ8tb{3M zj!Sa2cI7H9?8=*!02Qw;dtx4s16`E&?bW!zqXWLS%qhbo0AbS1WDFGBSU z1o7p$iCU{lJe<>?^uO>qe~e=hn`41w;Dssu6vwC|vWv~JUbj{L{%nrb|7?!cSk>lO zFd2N|bM}>Y?AEC6OAR310k^08m7MQW6qCK-rTm_F2l=DS?< zAX2~x*)hxu>YWrW>s2>;gNcu)krS;{(SYLz_FuN@Rf;) z4_}^`c*o)v^ZJVDqd`qU$)zmnQnQ!C)GwF3BzRp;S3lG*H`tw$?W=`k;*7=74lCez zZwmn;PZ2LFB%>^5)yvU1xNaen6Z92TuyHx+%q>n*E~*d(y$Z~;f=l@e7hvTdsPUm| z6_nJP*g`~Tl3@y{1+@HnMh9Pym(~0QRVQnNI$R6K^SZj2&!4B$md;!LSwa_Jl~l^| zpHUVe`>2z22+o$@RfyHRL<0q9yRy4Wi`9T0jpZzST3tIuNS;{!Q#n|}YO*KLJ(tk~ zQ>(h3&skv&4&kOHPGiv?5wY#n94Cn(#&kA_ZCbjrXf;-DBWrJRPgf>aSkho_P()oyo;k(BO8&;cWwsOBL*I)qm!RZ zZl2rTH{gu&8~n9%TW8mK10T8{>-uc&#@yDqJFlEFVzYPf{$0G^#FTas3-|g4zC82! znJ?lrpXzzwW9!Cj$KG%7{U$FOym+(ktC3qHx6{V(iLY0TVH)8+4xr9%hp2fxX}~Y` zZWZ189kI`bZVYX`cIVKH(Q#_MVHdZVIA-8j5qI6|?fY`{^U*KH=%U?p(NTL*8V{KK zpurE`?R=sTu=ZkYW}WGFn?|7HvO*JcLbo`}{9c76kz z@naEff?mU_;<|1ysx`u&=gp9qF(1sLD_0fCHwZoRUNA(17B_J$C7U&7)rw}z0g?Sg zRGol&>1=re?s;q@Twm^YYYESZx;+pwl&UmweolHiSxQg+aWIFk!5ngof+G~r>rH45 zBG!>P_*m>R7%Cd%I+-U8S_t(Cgr?b!xe2CJO(*#^H^nTy$ZGS?(=m|?Y)gX#)f z_7)6jzXJfFw0O`Irwnn*6lV=_c3Yg=9UL(SPa1TL;Fin;<9i9r7WJL$Z z0w!P8Wce1dlLgxf6B+>7fNG@H1K{`QF&FR;K8yzaBM+N6zwnUv`v)G@_xd~kJ;&7f zom9h~BL=LXejZ*ERN}Zmdcg<{DF}UkafmO{S_=jC%K<*_ob4n@K_A>!q|vRzIYPoWaQ`R2NdO3rY4uT1 literal 0 HcmV?d00001 diff --git a/app/__pycache__/__init__.cpython-313.pyc b/app/__pycache__/__init__.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..977055bb9ca61f2adc50cc69cd6913f20356f000 GIT binary patch literal 143 zcmey&%ge<81Y9an=^*+sh=2h`DC08=kTI1Zok5e)ZzV$!6Oi{ABy~&A*(xTqIJKxa zrZ_RLBs0b(zdSD|KQW~^#w{~1F()RmpdcnbJ~J<~BtBlRpz;=nO>TZlX-=wL5i3wT S$oOIq<0CU8BV!RWkOcrE_#mGE literal 0 HcmV?d00001 diff --git a/app/__pycache__/main.cpython-312.pyc b/app/__pycache__/main.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..3ef0306b242ce6d7aef36bb1a5d5ca9f2fcc74b2 GIT binary patch literal 5207 zcma)9O;8(07H)}=&@TcE25jtLz&~CdVl*SSMg}>% zRictCP&q)=9-^a;Y@q|SryO(Ks=bJ^svwE8c`p^db`@BV2+1XJ?1F0`GTGklV;3QiuY$xqiE!?x^_Uf3XN=y~+50C>@ zbvKjd_{WwH_G)=kX)$Sye_|sn3hyOtg%;bd9Z;>LeHW-Ua$pOpF+O13W3QyOu+PpI z`09vXwxt$D8u5PIUO2E^V{*jKw;&G|-m^RqyWWE9*rKtUbjEL5_t`7&8GNYFY26GJ z(X|84kGr=6Y(G{=OSKGUn$MF&Te&NKj zU~T+APZILh%GS5&zuc+AHqGrwV;m<(Bn)VaqLw9L1*# zXDpo9OM2syC9_w27WWsc}|6v*4zWgVi1-TpK1K5u*<3YHapTDvx?pQ{Igd zeM+O|@o1$9{A++u)z^Q5$M;Czl*P-lc9Q{wZK+=F420t4Dtaf*TZu4fTVDo+9iw3v z^QCkx86i)Js z7X9W%uF&l0%xFny;$x0+tC`CjdRhp*{l|qXf)ws;O`dtD)eIJE|$^gv#70U5-v> zn4qS})G+fK=nPR5z?iqhsFc~j#x15s_k`g+bCRiNzWchQr&Aqc5@n7lP%h|XS})@y zH9Dcu4PI?wf$~=m?J-O;?@lEpmGYO{v-ZI>6h~rI)5(~oP4+ynDQOIpx(28$DH}YE zOc>J?4Dl7lHC2Dsy3n%(WeC5mp1sn7R()k zf_zFKG*F_1(ks9WyA5CHB?CZ-B9n1?0XCF>w^PF+Gs01gf{DD^SZ%o_%K=Qv>SVZ< z@{dtl8NJH4TQx!22RZj5SK`4Eo#YvY6s6TkqJj#;3g;MR&hc0^1x!%LxX!bXk};wD zqm1%zF6N@VUReF3Tahb6BSQ~{B9E?*Ub+1!d;#iqQs(Y5r#?w}swnS&=G7-CkuY=+ z^FwR(KxxH@o}w-;406uqn4)gZYpFo>JWNS3V$7!$cLwESf*PTA4e>ExgwJSD z=6o$n=1F6)pqPmE{@qSFkkiyGrF0v!W7DGQm!V{+4!umF^^z4P#viSTmtLb^%B z=pes@am4DnAm5}&QlgTs0SxAhZo<41&|)UpjOA2Pkuq?K597#`IRs2aQYtE8UMGf( zxrV8fAj~(;mk7@Q3z)Q7NX&2U%p3?YDq~j1pOGZhl2S~9?)RDyn2^?4s9I=+ifm(h>_5VekFHqON(Y@#B-pgS7+-D1s`N;F&!L09pLak%Zt0v?LX6bBtCjIqP zR#->EtZ&9w^dBst_xL6_6P$~eP}?sJ`*LUh`d+r~l^Z!+Yp7`zH7(xFJt?B55;_FP z-Le{1QN!Y)+&~dEl+Z!r;8xbJZWO3r6u$c~>-hzU)_h+na5(E-N8UBmyo#E0Lq*hF zLY+2a*kE)SjFwf@0_Ii-mu?v1^VOAAo;9>zHPGGF(N~xLxOVdH>dCv$Pu@3h4_+dV z>3QzllGg~v>GfT)UP5sFGycWatiOczK~(GZ&fQyhJpXtR=e}C$Dh1DFee0o?Il3@C zKb;fuEu~O68(0U_*uup8#G;-LtPGawE@b^YBmUXYOlWZ+CoKutPzjw}M}akD#8t{W zmW~*~?psIxHB`Tf>UrXpnu@5tgnEqAYp87%wdD?%P=^89w~F@V`XRAg8Z1i>#+~tL9^Vb%?$R|pHvyjA1hXPCcf4E-?4P*oS z3g2wqOx;9IxGYc2zFXp_(@L(2XJN5cbWHuzJZHMegm{DZ%E last_id] + return {'messages': new_messages} diff --git a/app/chat_server/__init__.py b/app/chat_server/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/app/chat_server/__pycache__/Server.cpython-312.pyc b/app/chat_server/__pycache__/Server.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..b6f4dc0a4634711b15376adc86e5884dfbedfc04 GIT binary patch literal 5759 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 C@v)Qu literal 0 HcmV?d00001 diff --git a/app/chat_server/__pycache__/Server.cpython-313.pyc b/app/chat_server/__pycache__/Server.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..1c982647ca5bd87c326e011d3e81ae9876f139f7 GIT binary patch literal 5827 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 literal 0 HcmV?d00001 diff --git a/app/chat_server/__pycache__/__init__.cpython-313.pyc b/app/chat_server/__pycache__/__init__.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..76fdae94035b0952eb32f9e5e25778fda34119e2 GIT binary patch literal 155 zcmey&%ge<81l%f7=^*+sh=2h`DC08=kTI1Zok5e)ZzV$!6Oi{ABz4Qm*(xTqIJKxa zrZ_RLBs0b(zdSD|KQW~^#w{~1F()RmpdcnWBe5hNsHQBnC?-BWGcU6wK3=b&@)n0p dZhlH>PO4oIE6^yARmC92M`lJw#v*1Q3ji>^C7u8P literal 0 HcmV?d00001 diff --git a/app/chat_server/messages.txt b/app/chat_server/messages.txt new file mode 100644 index 0000000..b463e24 --- /dev/null +++ b/app/chat_server/messages.txt @@ -0,0 +1,2 @@ +1|Santi|Hola como estas? +2|Santi|Andres no me copies el TO DO diff --git a/app/config.ini b/app/config.ini new file mode 100644 index 0000000..f0b8909 --- /dev/null +++ b/app/config.ini @@ -0,0 +1,4 @@ +[Chat] +server = http://localhost:2020 +name = Santi + diff --git a/app/main.py b/app/main.py new file mode 100644 index 0000000..bc80d0f --- /dev/null +++ b/app/main.py @@ -0,0 +1,130 @@ +import tkinter as tk +from tkinter import Menu +from tkinter import ttk +import threading + +from app.chat_server.Server import Server +from app.widgets import ClockLabel +from app.ConfigMgr import ConfigMgr +from app.widgets.ChatTab import ChatTab +from app.widgets.UsageLabels import CPULabel, RAMLabel, BatteryLabel, NetworkLabel + +# Evento para detener threads de manera segura +stop_event = threading.Event() + +def on_closing(): + """Gestiona el cierre de la aplicación de manera segura.""" + # Detiene todos los threads relacionados con stop_event + stop_event.set() + + # Cierra la ventana principal + root.quit() + root.destroy() + +def on_config_changed(): + """Actualiza la configuración del servidor y nombre de usuario en el chat.""" + chat_frame.change_server_url(config_manager.config["Chat"]["server"]) + chat_frame.change_sender_name(config_manager.config["Chat"]["name"]) + +# Crea la ventana principal +root = tk.Tk() +root.title("Responsive Window") # Título de la ventana +root.geometry("1150x700") # Tamaño inicial de la ventana + +# Inicializa el gestor de configuración +config_manager = ConfigMgr(root, config_changed_listener=on_config_changed) + +# Inicializa el servidor de chat +server = Server(host="localhost", port=2020, stop_event=stop_event) + +# Configura la ventana principal para que sea responsive +root.columnconfigure(0, weight=0) # Columna izquierda con tamaño fijo +root.columnconfigure(1, weight=1) # Columna central ajustable +root.columnconfigure(2, weight=0) # Columna derecha con tamaño fijo +root.rowconfigure(0, weight=1) # Fila principal ajustable +root.rowconfigure(1, weight=0) # Barra de estado con tamaño fijo + +# Crea el menú superior +menu_bar = Menu(root) + +# Menú Archivo +file_menu = Menu(menu_bar, tearoff=0) +file_menu.add_command(label="New") # Comando Nuevo +file_menu.add_command(label="Open") # Comando Abrir +file_menu.add_separator() # Separador visual +file_menu.add_command(label="Config", command=config_manager.display_config_window) # Configuración +file_menu.add_command(label="Exit", command=on_closing) # Salir + +# Menú Edición +edit_menu = Menu(menu_bar, tearoff=0) +edit_menu.add_command(label="Copy") # Comando Copiar +edit_menu.add_command(label="Paste") # Comando Pegar + +# Añade los menús al menú principal +menu_bar.add_cascade(label="File", menu=file_menu) +menu_bar.add_cascade(label="Edit", menu=edit_menu) + +# Asigna el menú a la ventana principal +root.config(menu=menu_bar) + +# Crea los marcos laterales y central +frame_left = tk.Frame(root, bg="lightblue", width=200) # Marco izquierdo +frame_center = tk.Frame(root, bg="white") # Marco central +chat_frame = ChatTab(root, chat_server_url=config_manager.config["Chat"]["server"], + sender_name=config_manager.config["Chat"]["name"], + stop_event=stop_event, width=200, bg="lightgreen") # Marco derecho para el chat + +# Coloca los marcos en la cuadrícula +frame_left.grid(row=0, column=0, sticky="ns") # Marco izquierdo +frame_center.grid(row=0, column=1, sticky="nsew") # Marco central +chat_frame.grid(row=0, column=2, sticky="ns") # Marco derecho + +# Configura tamaños fijos para los marcos laterales +frame_left.grid_propagate(False) +chat_frame.grid_propagate(False) + +# Divide el marco central en dos partes (superior ajustable e inferior fijo) +frame_center.rowconfigure(0, weight=1) # Parte superior ajustable +frame_center.rowconfigure(1, weight=0) # Parte inferior fija +frame_center.columnconfigure(0, weight=1) # Ancho ajustable + +# Crea sub-marcos dentro del marco central +frame_top = tk.Frame(frame_center, bg="lightyellow") # Parte superior +frame_bottom = tk.Frame(frame_center, bg="lightgray", height=100) # Parte inferior + +# Coloca los sub-marcos en el marco central +frame_top.grid(row=0, column=0, sticky="nsew") # Parte superior +frame_bottom.grid(row=1, column=0, sticky="ew") # Parte inferior + +# Fija el tamaño de la parte inferior +frame_bottom.grid_propagate(False) + +# Crea la barra de estado +status_bar = tk.Label(root, text="Status bar", bg="lightgray", anchor="w") # Barra de estado +status_bar.grid(row=1, column=0, columnspan=3, sticky="ew") + +# Configura un cuaderno (notebook) para widgets +style = ttk.Style() +style.configure("CustomNotebook.TNotebook.Tab", font=("Arial", 12, "bold")) +notebook = ttk.Notebook(frame_top, style="CustomNotebook.TNotebook") +notebook.pack(fill="both", expand=True) + +# Añade etiquetas de uso del sistema +label_cpu = CPULabel(status_bar, bg="lightgreen", font=("Helvetica", 10), relief="groove", anchor="center", width=10, stop_event=stop_event) # Uso de CPU +label_ram = RAMLabel(status_bar, bg="lightcoral", font=("Helvetica", 10), relief="groove", anchor="center", width=10, stop_event=stop_event) # Uso de RAM +label_battery = BatteryLabel(status_bar, bg="lightblue", font=("Helvetica", 10), relief="groove", anchor="center", width=20, stop_event=stop_event) # Batería +label_net = NetworkLabel(status_bar, text="Network", bg="lightpink", font=("Helvetica", 10), relief="groove", anchor="center", width=20, stop_event=stop_event) # Red +label_time = ClockLabel(status_bar, font=("Helvetica", 12), bd=1, fg="darkblue", relief="sunken", anchor="center", width=20, stop_event=stop_event) # Reloj + +# Coloca las etiquetas en la barra de estado +label_cpu.pack(side="left", fill="both", expand=True) +label_ram.pack(side="left", fill="both", expand=True) +label_battery.pack(side="left", fill="both", expand=True) +label_net.pack(side="left", fill="both", expand=True) +label_time.pack(side="right", fill="both", expand=True) + +# Configura la acción para el cierre de la ventana +root.protocol("WM_DELETE_WINDOW", on_closing) + +# Inicia la aplicación +root.mainloop() \ No newline at end of file diff --git a/app/requirements.txt b/app/requirements.txt new file mode 100644 index 0000000..3856835 --- /dev/null +++ b/app/requirements.txt @@ -0,0 +1,13 @@ +blinker==1.9.0 +certifi==2024.8.30 +charset-normalizer==3.4.0 +click==8.1.7 +Flask==3.1.0 +idna==3.10 +itsdangerous==2.2.0 +Jinja2==3.1.4 +MarkupSafe==3.0.2 +psutil==6.1.0 +requests==2.32.3 +urllib3==2.2.3 +Werkzeug==3.1.3 diff --git a/app/widgets/ChatTab.py b/app/widgets/ChatTab.py new file mode 100644 index 0000000..925854d --- /dev/null +++ b/app/widgets/ChatTab.py @@ -0,0 +1,111 @@ +import time +import tkinter as tk +from tkinter import ttk +from tkinter.ttk import Notebook +from tkinter import Tk + +import requests + +from app.widgets.abc import ThreadedTab + + +class ChatTab(ThreadedTab): + + def __init__(self, root: Notebook | Tk, chat_server_url: str, sender_name: str, **kwargs): + self.chat_server_url = chat_server_url + self.sender_name = sender_name + self.conn = False + self.last_msg = 0 + super().__init__(root, **kwargs) + + def task(self): + try: + self.get_messages() + if not self.conn: + self.connected() + self.conn = True + except requests.ConnectionError: + self.disconnected() + time.sleep(1) + + def build(self): + # Create the main frame for the chat interface + self.chat_frame = tk.Frame(self) + self.chat_frame.pack(fill="both", expand=True) + + # Create the status label + self.status_label = tk.Label(self.chat_frame, text="", font=("Helvetica", 16)) + self.status_label.pack(fill="x") + + # Create the history frame with a scrollbar + self.history_frame = tk.Frame(self.chat_frame) + self.history_frame.pack(fill="both", expand=True) + + self.history_canvas = tk.Canvas(self.history_frame) + self.history_canvas.pack(side="left", fill="both", expand=True) + + self.scrollbar = ttk.Scrollbar(self.history_frame, orient="vertical", command=self.history_canvas.yview) + self.scrollbar.pack(side="right", fill="y") + + self.history_canvas.configure(yscrollcommand=self.scrollbar.set) + self.history_canvas.bind('', lambda e: self.history_canvas.configure(scrollregion=self.history_canvas.bbox("all"))) + + self.history_container = tk.Frame(self.history_canvas) + self.history_container.bind("", self.on_frame_configure) + self.history_canvas.create_window((0, 0), window=self.history_container, anchor="nw") + + # Create the input frame at the bottom + self.input_frame = tk.Frame(self.chat_frame) + self.input_frame.pack(fill="x") + + self.message_entry = tk.Entry(self.input_frame) + self.message_entry.pack(side="left", fill="x", expand=True, padx=5, pady=5) + self.message_entry.bind("", lambda event: self.send_message()) # Bind Enter key to send_message + + self.send_button = tk.Button(self.input_frame, text="Send", command=self.send_message) + self.send_button.pack(side="right", padx=5, pady=5) + + def on_frame_configure(self, event): + self.history_canvas.configure(scrollregion=self.history_canvas.bbox("all")) + + def send_message(self): + message = self.message_entry.get() + if message and self.conn: + response = requests.post(f"{self.chat_server_url}/send_message", json={"content": message, "sender": self.sender_name}) + self.last_msg = response.json().get("id") + self.display_message(self.sender_name, message) + self.message_entry.delete(0, tk.END) + + def display_message(self, sender, message): + message_frame = tk.Frame(self.history_container, bg="lightgray", pady=5) + message_frame.pack(fill="x", padx=5, pady=5) + + message_label = tk.Label(message_frame, text=f"{sender}: {message}", anchor="w", justify="left", wraplength=300) + message_label.pack(fill="x") + + self.history_canvas.update_idletasks() + self.history_canvas.yview_moveto(1) + + def get_messages(self): + response = requests.post(f"{self.chat_server_url}/get_messages", json={"last_id": self.last_msg}) + messages = response.json().get("messages") + for message in messages: + self.display_message(message["sender"], message["content"]) + + self.last_msg = messages[-1]["id"] if messages else self.last_msg + + def connected(self): + self.conn = True + self.status_label.config(text="Connected to chat", fg="green") + self.send_button.config(state="normal") + + def disconnected(self): + self.conn = False + self.status_label.config(text="Disconnected from Chat", fg="red") + self.send_button.config(state="disabled") + + def change_sender_name(self, new_name: str): + self.sender_name = new_name + + def change_server_url(self, new_url: str): + self.chat_server_url = new_url \ No newline at end of file diff --git a/app/widgets/ClockLabel.py b/app/widgets/ClockLabel.py new file mode 100644 index 0000000..c4f784c --- /dev/null +++ b/app/widgets/ClockLabel.py @@ -0,0 +1,16 @@ +import time +from datetime import datetime + +from .abc import ThreadedLabel + +class ClockLabel(ThreadedLabel): + + def task(self): + now = datetime.now() + day_of_week = now.strftime("%A") + time_str = now.strftime("%H:%M:%S") + date_str = now.strftime("%Y-%m-%d") + label_text = f"{day_of_week}, {date_str} - {time_str}" + try: self.config(text=label_text) + except RuntimeError: pass + time.sleep(0.5) \ No newline at end of file diff --git a/app/widgets/UsageLabels.py b/app/widgets/UsageLabels.py new file mode 100644 index 0000000..8e7e709 --- /dev/null +++ b/app/widgets/UsageLabels.py @@ -0,0 +1,50 @@ +import time +import psutil + +from .abc import ThreadedLabel + +class CPULabel(ThreadedLabel): + + def task(self, *args): + cpu_percent = psutil.cpu_percent() + try: self.config(text=f'CPU: {cpu_percent}%') + except RuntimeError: pass + time.sleep(1) + +class RAMLabel(ThreadedLabel): + + def task(self, *args): + memory = psutil.virtual_memory() + try: self.config(text=f'RAM: {memory.percent}%') + except RuntimeError: pass + time.sleep(1) + +class BatteryLabel(ThreadedLabel): + + def task(self, *args): + battery = psutil.sensors_battery() + if battery is None: + self.config(text='Battery: N/A') + return + battery_percent = battery.percent + is_charging = battery.power_plugged + time_left = battery.secsleft + text = f'Battery: {battery_percent:.0f}%' + if is_charging: + text += ', Plugged in' + else: + text += f', ({time_left // 3600}h {time_left % 3600 // 60}m left)' + + try: self.config(text=text) + except RuntimeError: pass # Catch update on closed widget + + time.sleep(1) + +class NetworkLabel(ThreadedLabel): + + def task(self, *args): + network = psutil.net_io_counters() + try: self.config(text=f'Net: {network.bytes_sent / 1024 / 1024:.2f} MB snt,' + f' {network.bytes_recv / 1024 / 1024:.2f} MB rcv') + except RuntimeError: pass # Catch update on closed widget + time.sleep(1) \ No newline at end of file diff --git a/app/widgets/__init__.py b/app/widgets/__init__.py new file mode 100644 index 0000000..3f69feb --- /dev/null +++ b/app/widgets/__init__.py @@ -0,0 +1,4 @@ +from .ClockLabel import ClockLabel +from .UsageLabels import CPULabel, RAMLabel + +__all__ = ['ClockLabel', 'CPULabel', 'RAMLabel'] \ No newline at end of file diff --git a/app/widgets/__pycache__/ChatTab.cpython-312.pyc b/app/widgets/__pycache__/ChatTab.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..a2bf6807970b2266de58d6ba9e86f9a278fc303c GIT binary patch literal 8441 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 As{jB1 literal 0 HcmV?d00001 diff --git a/app/widgets/__pycache__/ChatTab.cpython-313.pyc b/app/widgets/__pycache__/ChatTab.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..5f3497493c8e649a499d28a06e8c60117353eeb6 GIT binary patch literal 8429 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 Af&c&j literal 0 HcmV?d00001 diff --git a/app/widgets/__pycache__/ClockLabel.cpython-312.pyc b/app/widgets/__pycache__/ClockLabel.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..2471248b575d3b93840ee49870bb680d77071bc8 GIT binary patch literal 1116 zcmZ`&&rcIU6n?X_Th@hwmeAl2ObIq|qu7I<8iE0%5`%;&iJ=@eOJ@o#+ugdmwX`V- z2M#4_fKxTZq*sFn{sp{xA>x6onwY2uZYIUVaB`+g0Zn*^H{W~j`{vEgy!{XiD!^

Wv;-tT@doy_39*RO zmOUPfsmPOw?ov0MrF*OwutacT)}}h4WK^G~M$#Ago-?@BL(ElyLIM#;2m~!b0v81p zd&(WdO3wMpCYvkxxq1J3h-Itb9@R1eFaK*Bhv5P$qfGOQPf!TjIMgo)r!a&g20n#> zj~HTESO+4OMdqb4UWYf*f>ee~%ZZkUuq?d`G<68v)+Qs<)yZ+U^hem!h%TLXXZ&rx z-Bw#{ex)8Zt%4^zuASn~=mnCNnM%)iZIALMPu#U_%VttulN^K6oEq>%hZ-rH&zaXs z^rB{^v;w7bp3G?)+smJe22u<@2paG01ehYyu!9G&EG20@ma-h(ED`!FHkvl`^AR>r zdThMt&RS+{+_s7|>00;Frf$UaTrO5flNstdvEwCRlK<3y4t_=P!n<`&^g zdw9iIHlCXmyc<4WHP(#Ut)1}IO6zWCxH_;lP*Zj~uT|v#qVB%ch1zg!zLwdAn$De%+XD@w}dtXm%%e@B S6E5$I3hG|%`3-DoX#WC3uK9)l literal 0 HcmV?d00001 diff --git a/app/widgets/__pycache__/ClockLabel.cpython-313.pyc b/app/widgets/__pycache__/ClockLabel.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..82abdbb591bab44ef8e83796a771154d9265c346 GIT binary patch literal 1163 zcmZ`&Pfrs;6o0d`+in(0RYCf4KSkJkcjh~0b0s8yha+twU)@?lWO1wv4xpR)}xfguB!WFUqOsUMhZ zV5XGhloDt+oN|7lnPD%&!TENm2p2ygF4H){ts9UNwkBnywsdfwfzg)YqAbd^8*l*N zEf{TOccmi`(mnzofx!C+IV*L6A!lXIl`QUp5ye%qkcl2D*%Gsg5g9`|jTOaB^`t zSy+)S>MK|u*7=-dpDz2QFwxhERFsL92G}hx1*+$>qL^tA$(P-tT?#rgRaY2pGFE0h zRQMFnp|p~U1hPk+BI^+09R=$NDHq8Sr3-;7V1(xim!v6aj@Sw!YI|a^dXs{Q8)-A^ zQRbPR#S58tT{_9^$JCsp-hyAQn73@#a!jjIF_-K@iTa*-upyT!PxzHAZy|^uG75v} zlUL>J8GPkvD=;49A6)=YdhT+YU+Pl@7cAd_3QQJ z`urB&n%YW#=($?cc4Nm^b1!ml=4-i+vDA09b5(n(t&O~n|M`XC*r^?L=d;^B z1h2f^9ltgP+hf}G2!5|1&foEO6A4s8T#L7d1R5cWxGxGJ8DC*JT+j1c++9baJJ2b}&TYp8c^;5YE2 G8T$`}#Qy03 literal 0 HcmV?d00001 diff --git a/app/widgets/__pycache__/UsageLabels.cpython-312.pyc b/app/widgets/__pycache__/UsageLabels.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..1281f7e81648b94dd644339c117512c1c7d24bb6 GIT binary patch literal 3196 zcmb_eO>7fK6rT0|*p6)yn_wVdx+#eplQdgWXi>_KoI-j?uu=r7NYt(z@7i&&*JgG@ z;*}$D;NVED;8GEUqNb+?!GS}ia!k1PVgpj#r3a+82YNz^D^*bseY0yPCNWfz`lNk3 z`{vEtnR(y$W`6c~nh?l8@)wCGEJDALkD9=@DQh$Ex{V|xQ7JTPy{S=3vPpExmZnE( z3fa(SNMddviIwU5v^8v$F|8a}?R8exv^v1*tg~{awF#`QI;%ZLh1@SmxX4QibcixM z7bmbBm1Jo+IxeST)~_s0Jcz9MK;1?%f;pm5n?&6}qjb&|q7BEVBbTgkHDBUS)@g@l zZ4uOMq;2#~+Q6_y?pMEOZFw|V+o3uiiOSpVBFVNuKcMedBT-0m)?6md{jPmkZ;9FT z%%Q_b<7)jK2*d7PMqjs2qZ#@#nx;Z*L}y_c{ro!Bd5)o!Bi5F z=lS&^AuUVEs1VPnQAL;JYr=3+nVR9Hs1_AQa@s^j5k_z(C&#qRU{Z;uglIM^Oedv; ztf|5!HJXsk9anp^IrwQ*y`p<+S?DF74)V7!sO#ug|G`y%SIOV?)PJPlDto*)$L7Ws z*~Kf1Up{Ov9y(SSd*(U*yTg66X|Czk(QiC|S7_97@QI`S&c&jmeO>iJlyCiE3awDi zu!ml8Q=lXF7{eioN;EBtqR}LZ>5Md$B5k)QUY&}jtRCD7gYTg=&=M1O*uK?p^f;OWxp9Z&!ia#8CVP<$I;rdHmt$#X}zxAf5OxAn{=r zT5&nUd+8NF1v-NFz#s5_qD%ysC<%~HqReF+*ytpt9m2ys5Y;pp=e9$HB-E@>7oei- z03x>QyWdo$9@y$@PTtlCG_y3=HxdtXsLCt@_6BuqmX+u{Eirc)iCv&)xjcipJSTBF zBz%@r_CVX7XC;RYbrk^VG~2K?si|slEwGkv<5}mq`dMoPGp0t69A|XqBQqk!izAM=Rsn=~2{L7Ot z2i>0LQ*vAjSqyMm3}{wmMa^JU9Jf+zQULSdZ+NnqX&H-IE2!a6<_H^yG#CVDBi6H zR$DtttsMnt+1oaMX6eIXo3M1X*nG6W{NnOMj9ZbC``x*-FQ65h4=rk?w(jEoo~41M z+@oV9;mmShNjO*R9b8TnTfs9@IQzmn59TGp-oq6RIX#;P0sGoPK#IpopKqWIJ#KRj z1nI|}6zIkTJ`BE?C_g;r7LhXVt(AGwvr`^NWNkWwuWaWrNr1`YKcTS$9>4LX(TKH@ zO^2#m)iiLCu(bh;xB0OP>;rC-H5w_?rc9FBwnZfSrl)yl*{BB%_~n2*3v5b1pF|8$ zgzoK&>rVds0Iw=q53k!v6UVOAog9uTZxmS*lNm9VfxC;0RqNVuwH^Ck>7gZ+*H<{J#%tRdrC?@V{{-NvsA>l!7M!s=+hM9i`ylvRdpMdWEQA zDo_q|tOkTqKzP_u3VigawG=q@C{{c;u>9UDntEr5DvVVSwQHE#0^tUCnNPw&v=VfN z`{))bzP+3B-#S%6@TwfKllET- IUgr7vFB7~^(r*2cwIo7oKz zOO<*;PNfPiROzYdso}<S+e3PyN|36a`er{I6ATroBl*qSH#5&O z@BM!BX4mI)BWTC(B(s|gLcfs@tHHP4c5g#>AMuE%W)L=d3RAp|r)O**(U|7YQFILP z_721|DSC`HmSMYYWx?uburj*U307BwmDR0ouzDJ-jw}`S?htpe9SUTa(tKC1VM&~q z<}ZtLQa)|`l_ALt(snyQ+(#({dqmjAQ<&y$9SGZb8Z%jYn9-aUW)u4Q9Tujht%YPU zIkW?QyQ?7Xqa;Y9#ch_h-ycI`=7(%cptu#V{Rt4Vgslbfwo#g=KcqO6bXtRPJ@D3- z(UTyKnY-ao7WJYe*`VGWNQA(0s0YQ4XV5vi2W6-*6Du)rmI+Ry`X5l5U6pRDn8ZKK zYHU$iRC9UFlP)d_MG2=RS=HEdLC)l|nm4{EtGNZ~62=7#>p;~QB`-faR^1?(ap-5OsDIzRS%`T;{iFjca7rv0vs*;+{$znbw7K^D{x%sRF zmV_c^B|X&2NO1}NNK~$CJ}dkq^p{?MFvZv+?+)XYgSDDr8 ztDimWt#qF*C#$}(-<{sO?yueVPJZM2dyht~omKoca6C=ga73D$48Pr>>|ps}SPTCck2cpJIj zvM6C|1|gXSdmReaRuGcyV6Q`}!Ork>g68dhC@G_~%qHw1b5Ml+Cs>{xhMP%dKRyh) zBf;>_VYr_Gs4iWHIlSAnct#mxZu6bPdVS|fk^sHM2__CS0JitQ&0L3nQ(*gIB`XgT zTr3jRpO4I zWmhfGu`;6P%RTJ0FDyfd^mwYK!=bTu-$ zeySR|R2i9G&sN%DW~_YisZn88<_W8h@3F|`+b=E53#EnJEE{M1Q=RB>r+X?)KMqkK zUqkFLEWkt%0_t%h1tDM+1Tylr{EkWLtpdJo@OzLrYUDRbiO%moL3ty7zw!msjM2Pp z7!Y7e8hA>W+=SsT&0|Ixp4Hz%bq2-j+=DHf4H)M*z_|K7(Ufsccnd*fCb%5318L}y zQ<;*ByE4TovO2_-97M(Gn+-L_X);NcR3TRo(gk=E!Q;j#GVZx0Qs{wYs%Gj~g5|HN zISz0{64RvU1R8UuGh!Idsgyvx#EAN{C%Zi`UL6<*@D5C__f-d`*Of~D%rit?rn+if zecN4;YFFf8U$yJZqxNdoxku?r=hXUp&uFS=hAJojLR8CT>Hw&lyqCtK{b-}#9X&;F zoTNa$2GeI?yO;<9x;myA3)5sI>@*6pUcG5?E{!1>=JSZuG@TN`!5_s BfExe+ literal 0 HcmV?d00001 diff --git a/app/widgets/__pycache__/__init__.cpython-312.pyc b/app/widgets/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..d1bb0abd54fcdb98e9a9797038763a9c659155a1 GIT binary patch literal 308 zcmX@j%ge<81P}be(;I;FV-N=hn4pZ$8bHQ$h7^Vr#vF!RhA0L`5SuB7F_$TdDVI5l znUNu#A%%GnV-!mzize$!Mxb&{##>y@Ir+)iK8Z=GIhstjIGh7Q!R%WcL5{u-;}dgo;^S8`dw*sIDvuy DS&mNi literal 0 HcmV?d00001 diff --git a/app/widgets/__pycache__/__init__.cpython-313.pyc b/app/widgets/__pycache__/__init__.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..2b53f3ea73d9fc0f246bcf1484dc7d8968e1c0b9 GIT binary patch literal 297 zcmey&%ge<81Y9an=?y^oF^B^LOi;#W4IpDGLoh=yqc?*WLlJ`lh|T29=*3jTCNSj3XfqRIM_5vW{~@fMeJPJVK>PhwJPjwaJB4(EVSF#8rqkfSd|6sQ%b zw1@>numTA`O;)hTE$-0b#Pn3KqGE*fE%x~M#GIV?_>~NwfvOp9**aUrgche36~`1O z=9Of|_~)g%6lIpB#<-*wXP4v`#JFYVCFaB=78JykXQrg5mK4Xt$7kkcmc+;F6;$5h ru*uC&Da}c>E8+&426A<=0Fd~=%*e=imqF=1gZ3jXgGTluPM{zF2QEo* literal 0 HcmV?d00001 diff --git a/app/widgets/abc/ThreadedLabel.py b/app/widgets/abc/ThreadedLabel.py new file mode 100644 index 0000000..4e37887 --- /dev/null +++ b/app/widgets/abc/ThreadedLabel.py @@ -0,0 +1,22 @@ +import threading +from abc import ABC, abstractmethod +from threading import Thread +from tkinter import Label + + +class ThreadedLabel(ABC, Label): + + def __init__(self, root: Label, stop_event: threading.Event, **kwargs): + super().__init__(root, **kwargs) + self.stop_event = stop_event + self.declared_thread: Thread = threading.Thread(target=self.__loop) + + self.declared_thread.start() + + def __loop(self): + while not self.stop_event.is_set(): + self.task() + + @abstractmethod + def task(self, *args): + pass \ No newline at end of file diff --git a/app/widgets/abc/ThreadedTab.py b/app/widgets/abc/ThreadedTab.py new file mode 100644 index 0000000..0d90e5e --- /dev/null +++ b/app/widgets/abc/ThreadedTab.py @@ -0,0 +1,26 @@ +import threading +from abc import ABC, abstractmethod +from tkinter import Tk +from tkinter.ttk import Notebook +from tkinter import Frame + +class ThreadedTab(ABC, Frame): + + def __init__(self, root: Notebook | Tk, stop_event: threading.Event, **kwargs): + super().__init__(root, **kwargs) + self.stop_event = stop_event + self._thread = threading.Thread(target=self.__loop) + self.build() + self._thread.start() + + def __loop(self): + while not self.stop_event.is_set(): + self.task() + + @abstractmethod + def build(self): + pass + + @abstractmethod + def task(self, *args): + raise NotImplementedError("Method not implemented") \ No newline at end of file diff --git a/app/widgets/abc/__init__.py b/app/widgets/abc/__init__.py new file mode 100644 index 0000000..075749b --- /dev/null +++ b/app/widgets/abc/__init__.py @@ -0,0 +1,4 @@ +from .ThreadedLabel import ThreadedLabel +from .ThreadedTab import ThreadedTab + +__all__ = ['ThreadedLabel', 'ThreadedTab'] \ No newline at end of file diff --git a/app/widgets/abc/__pycache__/ThreadedLabel.cpython-312.pyc b/app/widgets/abc/__pycache__/ThreadedLabel.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..9b99c2e867b77fdbaecdc6f659de0a7d91811e04 GIT binary patch literal 1572 zcmb7D&2Jk;6o0c{UZ-{(xo+A*1W{Y$Z24HE9(q9ts0ykYMdeFKdl_xK<8+g?*UV1Z zI`tukNF-8#azM_J1D7fsfD3Tq58z~!%F)t_N}RYE+)7nXyxH}VIO4)c`{p-0^WOWt zuV0GA3BZ+`|7hug0`P}CWXl+V;XHyac;Jx$9Jwn2X(*1;P#u-Xvl>7{bF>6&f!;72 zgMb3>fTwSOXE22syIN|^$+d}<`%@kkEG1UoeCtv?=`KcsyEV~Z;(F*=BsM;{&Y3Hb zKJPBFpq479a%2gH@8S6qWX8ZD9yp3enCdA^TTWs+tO8KiHla>Db)(=Ip0)u_j+v6( z?&z75--KJlnRp*8BQB=ynU^?^H6Df{o`^))qU;81ikf=JxQ_T3e#7rkd`Y?>NOrrW zb>LH^+aY!0Lr49Zd`1X#lnzi#cDh6^L(67{4)zZGS1{j@-H{Kpl$8naZQ(#OL(8QM z9qd`q1lnLJ3o_xtu}#JZsmIBshvvCocn6}S95OvnRA=kcIkfg)qeG2uX^!E@$>DU zZtqpEZfbkx%tN!#H)ne0Oy4}yGtb;J&kfNK)c$P&JjhbZh8f;L(UoPw{peag+)|^< ztW42Ms3X&JWM%i*Qvcs3*?zH&<-bc7EOk^9KcW#6u_oMT#UlJTx{G8WvwRw5Y@}6Q zs~kOl3G&kj?qZuJd}2+uO(at~o6_RXB3>(xbCMBM@PE=&>wr~CJXyzNUpL(b zqconNv=Mr%fy4z$KVEf%^oGlc;&NCdL&Uo zJF#M!DJ2#Ao6BZNxg^t=6DxjGFrG3`96u*t$m}L$o|>ATjU{C^}2{ literal 0 HcmV?d00001 diff --git a/app/widgets/abc/__pycache__/ThreadedLabel.cpython-313.pyc b/app/widgets/abc/__pycache__/ThreadedLabel.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..41aebf3356840002c53b5541552fb37671012616 GIT binary patch literal 1652 zcmbtU&2Jk;6o0c{-h8+YCQZ{2gb6{3BS8yIsX$Nx36&a`AZ2R_(WRq}J$1L)*k*Rp z3iXl`qEaQ`5RoG%q+Sr5fK&elM^uT1Rv>ZeEnq%+8&}*IY2Un=dGF2d{ob4H z=JSJqV`%z=#t8%9H(K%n>^2 z`U9Rb0VgwaIq2o&P!--_a(EibuFj`WL_Gm=KZkbOlC74~gRyG0xwtBts%9Rz%|?9) zzwS3EPO~kz@bK_FBKQ#TPeGNR5;UM}EED@GkPV?Qc{|={3#nbs!)B;Bmz0+tP)T*d zWT#&g!L;f1jt;2b^TnDdz8 zmT+w9GYhZz)mX^bkAtQP{r8$;QHEEAzbN80EP#I|YzC1ZwA=o*u#T`}KUl5#eTZIc ze=I3;J=VY7OKSkn;sq2BHsP+DzmeN<$G1m2?(A1%JMQe>#PrR=-I?RNGpBZDPW_;6 zU;puXXQuM0^^-gKt2?~wPIlbMU3aSEPTg^*yBG#)jAS;bveX~luTgAK{j!6D)5^gP z$_{#!na$Sqmw~*Ao5x1avh;tVBhGhU0_=hY++xdRGb>IQ^H?ZtsUTh}GdYS^$CO@4 z9zm&{tlXoIQ%g-SLdGXhY{Fh~?8{?c9NR6ujEc@+Knh@@NT{dMUNu!S(?Q>fg+u1!#Bi@GQ#x=SfI`84Xf*pH0zNRl8PHvub{ z+=M}jxRE4X&Qr@bgLT2V9^`zzRo{q6ALjg{jUdWS3Y=dGWvrsGDVi;O9rXGn<*e;@ z=(mtGoqU3J333YOFt)b}R9NGIQZiiKPy#z%=d}%Id;#~UF!3`e_D#mv&oJ@}Ja-Q!@4?YO RodU~$zOa4xFF+*;@GqpiRL1}S literal 0 HcmV?d00001 diff --git a/app/widgets/abc/__pycache__/ThreadedTab.cpython-312.pyc b/app/widgets/abc/__pycache__/ThreadedTab.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..ae9a58916322994c2bb79a11ff3d820af1dc0dcc GIT binary patch literal 1865 zcmaJ>&2Jk;6rb4-d+pds>O@Hz6>wXmW(&l35+Od+qNorm5s7LgG}?G4$tG*BnO!%v z>qCShkw}4t14_AZ;Ls>YoRHweAHbz)ixLfuS_v-P3~nXV6YuTXi4#yq+Bd)VX7jWkv1cn)qo(mED$}MS)wkY$NA}vYK`88ESmNP= z@0e67GfNYHEbJ05#!h0{_DEU_pnnF@a$-@3Sc*fr%C&luK-64c%yj=0 z<2h}O%uz>O%~(lCTP2pkQ;xn$cG_F5H0Tifq29AbZcr=pAu*G(XJ$cg+u_cPJu5Zg z`=Lw+pXehf-oE4eQ0t@z=aT=UY4Oza{;3gDnTFCN zLR-x9&`e1^s8+a;24im74H*-dyG(@Bnxm}=}Vv9t^a1~SE1(X)equ>g6_!&rItL7~JQ*DlQYfnBaU{CeR%w_LrE zckIwET&+QWl?zveU*pBlzwDN6uV7ayg=N=)41$6^TP$=%HeRU-q^&;KZR}*1f|cM3 zh<{edLnFJM+%-mS5AGUc-wfR|#`Z^FUeh-E9uDXChA-?6U$_%~e{=ihgW(CVX7-KD zt@L_22ATEDo-w*>jQ(nzX@NUZJZutBuoDu%#CfQjVAX^=7zm${xhP9>JU zAGf{+VukGIhPDb{7WQ&uyScFkxr_Um{##S)QyW*BDj66)TH4E96r=FRF{(h1slu0G z3ℑ0BjTS5=>pOh(XZnr@8})S`N)oE3J^nM&`f%kz~A)tq~C2ciB0-bDvVU6ZN6@ zqlYYC_QSkes(8Ev4+F=CZ2+FMsUyoJA$(yf5m{2ht`4x?`Is~DBH(&|cbrHro*wjq zSRsG(p1phS_4~aOca4cCUNeiguMAHmV=~QH$#<$A;tXS-RBbO_5%?|$>_t zz#mgT_x;rO6ye{!iaydbNMi6Wh3eUs3aE*wnP}T4Bij?OO-6}D-pzCzuL}26U^SvZ zWiniHfnO12BzxN0co;6liDFQ3i02St-$Y?QGbUqx-Vozp!+ZkV(Nrm=zmfh&0hGpyk^lez literal 0 HcmV?d00001 diff --git a/app/widgets/abc/__pycache__/ThreadedTab.cpython-313.pyc b/app/widgets/abc/__pycache__/ThreadedTab.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..beb64f27e42566117c05ffa3582d26f87cdc0ae1 GIT binary patch literal 1945 zcma)6&2Jk;6rWk|+H1#&Qzwm6tDtQf)Ho6?9|;mosDP%G601Rq6DguwMjLzUZoToE z*>NlAB_~9rN-c+oa^r-W6DK4%@gG!8P?czC1QJ|&GnkJad2e@}IL(Ez{Py?W%)EK? zd-Gnqw>L##q((oiey0%f8!DXLi5-k#rU!_Rr6t2w0c+5aOR#fLSt_4^v z#^(||5nv7MI@b*{Oh!m4o+qV*shIkt8hYl*DlO>GP?;|%GWOp47iFKd;tOt-#i}iC zc$ET`%3=+qKJN*8#q$trm$|iSmqRC^j69OI0R1C~)+C&k2v-?HnX9P2t;1Kb1u_9GA%=mxpEKe-6;T!TA}kR>`n zUn5Jg8TuAA$g<%=mcnthlOM96cWeh=*hvvqAy08(@v1F=8qIGuY%X=ioVp_z5*c$n zuOSmGTqm`arsGz?8Tb_iO{%`_Ugt<*=r3No4F~!%$ynL7e4jBNIW}Od$(f7i%_ZOF zzUfLO{>u`Z#xwT+c&M1ve^~R*~TZlALAbEHg>L* zgc*Mp#Pc=sRPS9+Z0o}h2DkLFuZOnvvEBUW-OSURv6DNqojdbTe0yi}&Q@;fbM7Xx04;)%8u=1$G5WMyXpRWv+J{Wi>(+L$Q_z}k{#zK zVc#Jd@yvim>>3*oB!3yeCgG>x(-jdv4!S(n9Zv892XPs4w?=-|(?|WIZ><0JFbF8? z{S=nyWQ1)BCGzRem7vKPbx#=1YQwcxp-0%*^ah~a%^p}TaqjVg5>O^peB?g91tVmR z8-QhhcbrHup6+9_TO+^soq9ZS{-?gF$NE$dFZjY+1c@nRurTY;QW=ve##X&b(?y(S z?Bk~8Ml%`4t~=Zpu2Z+`9@H@Q5RNo(>;_W1gdHxZ5I%rbWH+ENjPE#AQ?%ihPU3@qC{*ih#{jhuwc?R&I>k UkrRKZ19bZS>HE#U34p-Ie^)n<(EtDd literal 0 HcmV?d00001 diff --git a/app/widgets/abc/__pycache__/__init__.cpython-312.pyc b/app/widgets/abc/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..66e19c492a070ef42e68a8a57489a7ac2f3506f3 GIT binary patch literal 284 zcmX@j%ge<81YW-3>6JkGF^B^LOi;#W6(D0eLkdF*V-78T~f`iV)&`tk9Zd6^~g@p=W7zc_4i^HWN5QtgU3 mfrf+JSu6@9J}@&fGTvp-xXYmOfLm&Y?*%TcM)o3hpbP-k&q*@? literal 0 HcmV?d00001 diff --git a/app/widgets/abc/__pycache__/__init__.cpython-313.pyc b/app/widgets/abc/__pycache__/__init__.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..9bfc320138c39e174804102c628ff4430b40712e GIT binary patch literal 273 zcmey&%ge<81l%f7>6JkGF^B^LOi;#W6(D0OLoh=yqc?*WLlJ`lh|T29=*3vX2xKz{ zv*v@#i>QbF~x~_C7Ch)d8sZ%nPsUlE~&-YCHVz0Zkc(B zIWdU^1u^BBDe0*t#W9IV$uaTqnR%Hd@$q^EmA5!-a`RJ4b5iY!IDsaD99Jv~Bt9@R bGBVy}(74N>@_<`vhVKO~tw#1DcAyLZ)AdBZ literal 0 HcmV?d00001