MoneyPack commited on
Commit
970c337
Β·
verified Β·
1 Parent(s): efee818

MoneyPackCleaner SUPREME v4 - Maximum content, animated mascots, 8 tabs, live vitals, auto-updater, no blank space

Browse files
Files changed (1) hide show
  1. moneypack_supreme.py +1136 -0
moneypack_supreme.py ADDED
@@ -0,0 +1,1136 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+ """
4
+ MoneyPackCleaner SUPREME v4.0
5
+ The Ultimate Storage Optimization Experience
6
+ Created by MoneyPack
7
+
8
+ - Every tab unique themed with its own color + animated mascot
9
+ - No blank space - every pixel filled with content
10
+ - Auto-updater built in
11
+ - Animated ASCII characters on each page
12
+ - 10+ cleaning categories
13
+ - Real-time system vitals
14
+ - Animated everything
15
+ """
16
+
17
+ APP_VERSION = "4.0.0"
18
+ UPDATE_URL = "https://huggingface.co/MoneyPack/MoneyPack-Security-Suite/raw/main/moneypack_elite.py"
19
+ VERSION_URL = "https://huggingface.co/MoneyPack/MoneyPack-Security-Suite/raw/main/version.json"
20
+
21
+ import os, sys, time, math, shutil, hashlib, platform, json, random, subprocess, urllib.request, threading
22
+ from pathlib import Path
23
+ from datetime import datetime
24
+ from collections import defaultdict
25
+
26
+ from PyQt6.QtWidgets import *
27
+ from PyQt6.QtCore import *
28
+ from PyQt6.QtGui import *
29
+
30
+
31
+ # ═══════════════════════════════════════════════════════
32
+ # COLORS
33
+ # ═══════════════════════════════════════════════════════
34
+ class C:
35
+ BG="#050510"; BG2="#0a0a1a"; PANEL="#0f0f25"; CARD="#151538"; HOVER="#1c1c48"
36
+ GOLD="#d4af37"; GOLD2="#ffd700"; PINK="#ff1a5c"; PINK2="#ff4d88"
37
+ CYAN="#00e5ff"; CYAN2="#00ffff"; PURPLE="#a855f7"; BLUE="#3b82f6"
38
+ WHITE="#f0f0ff"; TEXT="#c0c0dd"; DIM="#5a5a80"; MUTED="#2a2a4a"
39
+ GREEN="#00e676"; YELLOW="#ffc107"; RED="#ff1744"; ORANGE="#ff6d00"
40
+ BORDER="#ffffff12"
41
+
42
+ def fmt(b):
43
+ if b<=0: return "0 B"
44
+ for u in ['B','KB','MB','GB','TB']:
45
+ if b<1024: return f"{b:.1f} {u}"
46
+ b/=1024
47
+ return f"{b:.1f} PB"
48
+
49
+ # ═══════════════════════════════════════════════════════
50
+ # ENGINE
51
+ # ═══════════════════════════════════════════════════════
52
+ class Engine:
53
+ SKIP={'$Recycle.Bin','System Volume Information','Windows','WinSxS','node_modules','.git','__pycache__','venv','.venv','Recovery','proc','sys','dev'}
54
+ CATS={'Video':{'.mp4','.avi','.mkv','.mov','.wmv','.webm'},'Audio':{'.mp3','.wav','.flac','.aac','.ogg'},'Image':{'.jpg','.jpeg','.png','.gif','.bmp','.webp','.psd'},'Archive':{'.zip','.rar','.7z','.tar','.gz','.iso'},'Document':{'.pdf','.doc','.docx','.xls','.xlsx','.ppt','.pptx','.txt'},'Installer':{'.exe','.msi','.dmg','.deb'},'Code':{'.py','.js','.html','.css','.java','.cpp','.c','.rs'},'Database':{'.db','.sqlite','.sql','.mdf'}}
55
+
56
+ @classmethod
57
+ def cat(cls,ext):
58
+ for c,es in cls.CATS.items():
59
+ if ext.lower() in es: return c
60
+ return "Other"
61
+
62
+ @classmethod
63
+ def disks(cls):
64
+ ds=[]
65
+ if sys.platform=='win32':
66
+ try:
67
+ import ctypes
68
+ bm=ctypes.windll.kernel32.GetLogicalDrives()
69
+ for l in 'ABCDEFGHIJKLMNOPQRSTUVWXYZ':
70
+ if bm&1:
71
+ try:
72
+ u=shutil.disk_usage(f"{l}:\\")
73
+ if u.total>0: ds.append({'d':f"{l}:",'t':u.total,'u':u.used,'f':u.free,'p':round(u.used/u.total*100,1)})
74
+ except:pass
75
+ bm>>=1
76
+ except:pass
77
+ else:
78
+ seen=set()
79
+ for m in ['/',str(Path.home())]:
80
+ try:
81
+ u=shutil.disk_usage(m)
82
+ if u.total not in seen:
83
+ seen.add(u.total)
84
+ ds.append({'d':m,'t':u.total,'u':u.used,'f':u.free,'p':round(u.used/u.total*100,1)})
85
+ except:pass
86
+ return ds
87
+
88
+ @classmethod
89
+ def large(cls,root,thresh=50*1024*1024,mx=500):
90
+ r=[]
91
+ for dp,dns,fns in os.walk(root):
92
+ dns[:]=[d for d in dns if d not in cls.SKIP and not d.startswith('.')]
93
+ for f in fns:
94
+ try:
95
+ fp=os.path.join(dp,f);sz=os.path.getsize(fp)
96
+ if sz>=thresh:
97
+ ext=os.path.splitext(f)[1].lower()
98
+ r.append({'p':fp,'s':sz,'n':f,'c':cls.cat(ext),'e':ext})
99
+ if len(r)>=mx: return sorted(r,key=lambda x:x['s'],reverse=True)
100
+ except:pass
101
+ return sorted(r,key=lambda x:x['s'],reverse=True)
102
+
103
+ @classmethod
104
+ def old_files(cls,root,min_days=180,min_sz=1024*1024,mx=300):
105
+ r=[];now=time.time()
106
+ for dp,dns,fns in os.walk(root):
107
+ dns[:]=[d for d in dns if d not in cls.SKIP and not d.startswith('.')]
108
+ for f in fns:
109
+ try:
110
+ fp=os.path.join(dp,f);sz=os.path.getsize(fp)
111
+ if sz>=min_sz:
112
+ days=int((now-os.path.getatime(fp))/86400)
113
+ if days>=min_days:
114
+ r.append({'p':fp,'s':sz,'n':f,'d':days,'c':cls.cat(os.path.splitext(f)[1])})
115
+ if len(r)>=mx: return sorted(r,key=lambda x:x['d'],reverse=True)
116
+ except:pass
117
+ return sorted(r,key=lambda x:x['d'],reverse=True)
118
+
119
+ @classmethod
120
+ def dupes(cls,root,min_sz=1024*1024):
121
+ sm=defaultdict(list)
122
+ for dp,dns,fns in os.walk(root):
123
+ dns[:]=[d for d in dns if d not in cls.SKIP and not d.startswith('.')]
124
+ for f in fns:
125
+ try:
126
+ fp=os.path.join(dp,f);sz=os.path.getsize(fp)
127
+ if sz>=min_sz: sm[sz].append(fp)
128
+ except:pass
129
+ hm=defaultdict(list)
130
+ for sz,ps in sm.items():
131
+ if len(ps)<2:continue
132
+ for fp in ps:
133
+ try:
134
+ h=hashlib.md5()
135
+ with open(fp,'rb') as f:
136
+ h.update(f.read(8192))
137
+ if sz>8192: f.seek(-8192,2); h.update(f.read(8192))
138
+ hm[f"{sz}_{h.hexdigest()}"].append(fp)
139
+ except:pass
140
+ return sorted([{'s':int(k.split('_')[0]),'f':ps} for k,ps in hm.items() if len(ps)>1],key=lambda x:x['s']*len(x['f']),reverse=True)
141
+
142
+ @classmethod
143
+ def folder_sizes(cls,root):
144
+ folders=[]
145
+ try:
146
+ for e in os.scandir(root):
147
+ if e.is_dir() and e.name not in cls.SKIP and not e.name.startswith('.'):
148
+ try:
149
+ sz=sum(os.path.getsize(os.path.join(r,f)) for r,_,fs in os.walk(e.path) for f in fs)
150
+ if sz>0: folders.append({'p':e.path,'n':e.name,'s':sz})
151
+ except:pass
152
+ except:pass
153
+ total=sum(f['s'] for f in folders)
154
+ for f in folders: f['pct']=round(f['s']/total*100,1) if total else 0
155
+ return sorted(folders,key=lambda x:x['s'],reverse=True)
156
+
157
+ @classmethod
158
+ def temp_paths(cls):
159
+ if sys.platform=='win32':
160
+ return [p for p in [os.environ.get('TEMP',''),os.path.join(os.environ.get('LOCALAPPDATA',''),'Temp'),os.path.join(os.environ.get('WINDIR',r'C:\Windows'),'Temp'),os.path.join(os.environ.get('WINDIR',r'C:\Windows'),'Prefetch'),os.path.join(os.environ.get('LOCALAPPDATA',''),'Microsoft','Windows','INetCache')] if p and os.path.exists(p)]
161
+ return [p for p in ['/tmp','/var/tmp',str(Path.home()/'.cache'),str(Path.home()/'.local/share/Trash')] if os.path.exists(p)]
162
+
163
+ @classmethod
164
+ def temp_size(cls):
165
+ t=c=0
166
+ for d in cls.temp_paths():
167
+ try:
168
+ for r,_,fs in os.walk(d):
169
+ for f in fs:
170
+ try: t+=os.path.getsize(os.path.join(r,f));c+=1
171
+ except:pass
172
+ except:pass
173
+ return t,c
174
+
175
+ @classmethod
176
+ def clean_temp(cls):
177
+ r={'del':0,'freed':0}
178
+ for d in cls.temp_paths():
179
+ try:
180
+ for rt,_,fs in os.walk(d):
181
+ for f in fs:
182
+ fp=os.path.join(rt,f)
183
+ try: sz=os.path.getsize(fp);os.remove(fp);r['del']+=1;r['freed']+=sz
184
+ except:pass
185
+ except:pass
186
+ return r
187
+
188
+ @classmethod
189
+ def clean_browser(cls):
190
+ r={'del':0,'freed':0}
191
+ dirs=[]
192
+ if sys.platform=='win32':
193
+ la=os.environ.get('LOCALAPPDATA','')
194
+ dirs=[os.path.join(la,'Google','Chrome','User Data','Default','Cache'),os.path.join(la,'Google','Chrome','User Data','Default','Code Cache'),os.path.join(la,'Microsoft','Edge','User Data','Default','Cache')]
195
+ else:
196
+ h=str(Path.home())
197
+ dirs=[os.path.join(h,'.cache','google-chrome'),os.path.join(h,'.cache','mozilla'),os.path.join(h,'.cache','chromium')]
198
+ for d in dirs:
199
+ if not os.path.exists(d):continue
200
+ for rt,_,fs in os.walk(d):
201
+ for f in fs:
202
+ fp=os.path.join(rt,f)
203
+ try: sz=os.path.getsize(fp);os.remove(fp);r['del']+=1;r['freed']+=sz
204
+ except:pass
205
+ return r
206
+
207
+ @classmethod
208
+ def empty_trash(cls):
209
+ if sys.platform=='win32':
210
+ try:import ctypes;ctypes.windll.shell32.SHEmptyRecycleBinW(None,None,7);return True
211
+ except:return False
212
+ for t in [str(Path.home()/'.local/share/Trash'),str(Path.home()/'.Trash')]:
213
+ if os.path.exists(t):
214
+ try: shutil.rmtree(t);os.makedirs(t,exist_ok=True);return True
215
+ except:pass
216
+ return False
217
+
218
+ @classmethod
219
+ def delete(cls,paths):
220
+ r={'del':0,'freed':0}
221
+ for p in paths:
222
+ try:
223
+ if os.path.isfile(p): sz=os.path.getsize(p);os.remove(p);r['del']+=1;r['freed']+=sz
224
+ elif os.path.isdir(p): shutil.rmtree(p);r['del']+=1
225
+ except:pass
226
+ return r
227
+
228
+ @classmethod
229
+ def sys_info(cls):
230
+ cpu=0;ram_pct=0;ram_used=0;ram_total=0
231
+ try:
232
+ import psutil
233
+ cpu=psutil.cpu_percent(interval=0.1)
234
+ m=psutil.virtual_memory()
235
+ ram_pct=m.percent;ram_used=m.used;ram_total=m.total
236
+ except:
237
+ pass
238
+ return {'cpu':cpu,'ram_pct':ram_pct,'ram_used':ram_used,'ram_total':ram_total,'os':f"{platform.system()} {platform.release()}",'host':platform.node(),'arch':platform.machine(),'py':platform.python_version()}
239
+
240
+
241
+ # Auto-install psutil
242
+ try:
243
+ import psutil
244
+ except:
245
+ subprocess.check_call([sys.executable,"-m","pip","install","psutil","-q"])
246
+ import psutil
247
+
248
+
249
+ class Worker(QThread):
250
+ done=pyqtSignal(object)
251
+ def __init__(self,fn,*a,**kw):
252
+ super().__init__()
253
+ self.fn,self.a,self.kw=fn,a,kw
254
+ def run(self):
255
+ self.done.emit(self.fn(*self.a,**self.kw))
256
+
257
+
258
+ # ═══════════════════════════════════════════════════════
259
+ # AUTO-UPDATER
260
+ # ═══════════════════════════════════════════════════════
261
+ class Updater:
262
+ @staticmethod
263
+ def check():
264
+ try:
265
+ req=urllib.request.Request(VERSION_URL,headers={'User-Agent':'MoneyPackCleaner'})
266
+ with urllib.request.urlopen(req,timeout=5) as resp:
267
+ data=json.loads(resp.read().decode())
268
+ rv=data.get("version","0.0.0")
269
+ local=[int(x) for x in APP_VERSION.split('.')]
270
+ remote=[int(x) for x in rv.split('.')]
271
+ if remote>local:
272
+ return {"available":True,"version":rv,"changelog":data.get("changelog","")}
273
+ except:pass
274
+ return {"available":False}
275
+
276
+ @staticmethod
277
+ def update():
278
+ try:
279
+ req=urllib.request.Request(UPDATE_URL,headers={'User-Agent':'MoneyPackCleaner'})
280
+ with urllib.request.urlopen(req,timeout=30) as resp:
281
+ code=resp.read()
282
+ if len(code)<500: return False,"Download too small"
283
+ try: compile(code,'<update>','exec')
284
+ except SyntaxError: return False,"Syntax error in update"
285
+ script=os.path.abspath(sys.argv[0])
286
+ shutil.copy2(script,script+".bak")
287
+ with open(script,'wb') as f: f.write(code)
288
+ return True,"Updated! Restart to apply."
289
+ except Exception as e:
290
+ return False,str(e)
291
+
292
+
293
+ # ═══════════════════════════════════════════════════════
294
+ # ANIMATED WIDGETS
295
+ # ═══════════════════════════════════════════════════════
296
+
297
+ class Particles(QWidget):
298
+ """Neural network style particles with connections."""
299
+ def __init__(self,parent=None,count=50,color1=C.CYAN,color2=C.PINK):
300
+ super().__init__(parent)
301
+ self.setAttribute(Qt.WidgetAttribute.WA_TransparentForMouseEvents)
302
+ self._c1,self._c2=color1,color2
303
+ self._pts=[{'x':random.random(),'y':random.random(),'vx':(random.random()-.5)*.001,'vy':(random.random()-.5)*.001,'sz':random.uniform(1.5,4),'a':random.randint(20,60)} for _ in range(count)]
304
+ t=QTimer(self);t.timeout.connect(self._tick);t.start(33)
305
+ def _tick(self):
306
+ for p in self._pts:
307
+ p['x']+=p['vx'];p['y']+=p['vy']
308
+ if p['x']<0 or p['x']>1:p['vx']*=-1
309
+ if p['y']<0 or p['y']>1:p['vy']*=-1
310
+ self.update()
311
+ def paintEvent(self,e):
312
+ pa=QPainter(self);pa.setRenderHint(QPainter.RenderHint.Antialiasing)
313
+ w,h=self.width(),self.height()
314
+ for i,p1 in enumerate(self._pts):
315
+ for p2 in self._pts[i+1:i+6]:
316
+ dx=(p1['x']-p2['x'])*w;dy=(p1['y']-p2['y'])*h
317
+ d=math.sqrt(dx*dx+dy*dy)
318
+ if d<100:
319
+ c=QColor(self._c1);c.setAlpha(int(15*(1-d/100)))
320
+ pa.setPen(QPen(c,.5));pa.drawLine(QPointF(p1['x']*w,p1['y']*h),QPointF(p2['x']*w,p2['y']*h))
321
+ for p in self._pts:
322
+ c=QColor(random.choice([self._c1,self._c2]));c.setAlpha(p['a'])
323
+ pa.setBrush(c);pa.setPen(Qt.PenStyle.NoPen)
324
+ pa.drawEllipse(QPointF(p['x']*w,p['y']*h),p['sz'],p['sz'])
325
+ pa.end()
326
+
327
+
328
+ class AnimRing(QWidget):
329
+ """Spinning gradient ring with glow."""
330
+ def __init__(self,size=180):
331
+ super().__init__();self.setFixedSize(size,size)
332
+ self._s=size;self._v=0;self._t=0;self._rot=0;self._glow=0;self._gd=1
333
+ t=QTimer(self);t.timeout.connect(self._tick);t.start(16)
334
+ def set_value(self,v): self._t=min(max(v,0),100)
335
+ def _tick(self):
336
+ self._v+=(self._t-self._v)*.05;self._rot+=.4
337
+ self._glow+=.03*self._gd
338
+ if self._glow>=1:self._gd=-1
339
+ elif self._glow<=0:self._gd=1
340
+ self.update()
341
+ def paintEvent(self,e):
342
+ p=QPainter(self);p.setRenderHint(QPainter.RenderHint.Antialiasing)
343
+ s=self._s;cx=cy=s/2;th=12;r=(s-th*2-8)/2
344
+ rect=QRectF(cx-r,cy-r,r*2,r*2)
345
+ # BG ring
346
+ p.setPen(QPen(QColor(C.MUTED+"40"),th,Qt.PenStyle.SolidLine,Qt.PenCapStyle.RoundCap))
347
+ p.drawArc(rect,0,360*16)
348
+ if self._v>0:
349
+ # Glow
350
+ gc=QColor(C.PINK);gc.setAlpha(int(25+20*self._glow))
351
+ p.setPen(QPen(gc,th+14,Qt.PenStyle.SolidLine,Qt.PenCapStyle.RoundCap))
352
+ span=int(self._v/100*360*16)
353
+ p.drawArc(rect,90*16,-span)
354
+ # Main arc
355
+ g=QConicalGradient(cx,cy,90+self._rot)
356
+ g.setColorAt(0,QColor(C.PINK));g.setColorAt(.25,QColor(C.PURPLE));g.setColorAt(.5,QColor(C.CYAN));g.setColorAt(.75,QColor(C.GOLD));g.setColorAt(1,QColor(C.PINK))
357
+ p.setPen(QPen(QBrush(g),th,Qt.PenStyle.SolidLine,Qt.PenCapStyle.RoundCap))
358
+ p.drawArc(rect,90*16,-span)
359
+ # Center
360
+ p.setPen(QColor(C.WHITE));p.setFont(QFont("Segoe UI",int(s*.13),QFont.Weight.Bold))
361
+ p.drawText(rect,Qt.AlignmentFlag.AlignCenter,f"{self._v:.0f}%")
362
+ p.setPen(QColor(C.DIM));p.setFont(QFont("Segoe UI",int(s*.045)))
363
+ p.drawText(QRectF(0,cy+s*.1,s,s*.1),Qt.AlignmentFlag.AlignCenter,"DISK USAGE")
364
+ p.end()
365
+
366
+
367
+ class WaveBar(QWidget):
368
+ """Liquid wave progress bar."""
369
+ def __init__(self,h=26):
370
+ super().__init__();self.setFixedHeight(h)
371
+ self._v=0;self._t=0;self._ph=0;self._c1=C.PINK;self._c2=C.PURPLE
372
+ t=QTimer(self);t.timeout.connect(self._tick);t.start(30)
373
+ def set_value(self,v,c1=None,c2=None):
374
+ self._t=min(max(v,0),100)
375
+ if c1:self._c1=c1
376
+ if c2:self._c2=c2
377
+ def _tick(self):self._v+=(self._t-self._v)*.07;self._ph+=.12;self.update()
378
+ def paintEvent(self,e):
379
+ p=QPainter(self);p.setRenderHint(QPainter.RenderHint.Antialiasing)
380
+ w,h=self.width(),self.height()
381
+ p.setBrush(QColor(C.MUTED));p.setPen(Qt.PenStyle.NoPen)
382
+ p.drawRoundedRect(0,0,w,h,h/2,h/2)
383
+ fw=int(w*self._v/100)
384
+ if fw>0:
385
+ clip=QPainterPath();clip.addRoundedRect(QRectF(0,0,w,h),h/2,h/2)
386
+ p.setClipPath(clip)
387
+ g=QLinearGradient(0,0,fw,0);g.setColorAt(0,QColor(self._c1));g.setColorAt(1,QColor(self._c2))
388
+ path=QPainterPath();path.moveTo(0,h)
389
+ for x in range(fw+1):
390
+ y=h*.3+math.sin(x*.06+self._ph)*3+math.sin(x*.02+self._ph*1.5)*2
391
+ path.lineTo(x,y)
392
+ path.lineTo(fw,h);path.closeSubpath()
393
+ p.setBrush(QBrush(g));p.drawPath(path)
394
+ # Second wave
395
+ path2=QPainterPath();path2.moveTo(0,h)
396
+ for x in range(fw+1):
397
+ y=h*.5+math.sin(x*.05+self._ph*1.3+1)*4
398
+ path2.lineTo(x,y)
399
+ path2.lineTo(fw,h);path2.closeSubpath()
400
+ c=QColor(self._c2);c.setAlpha(80)
401
+ p.setBrush(c);p.drawPath(path2)
402
+ p.setClipping(False)
403
+ p.setPen(QColor(C.WHITE if self._v>25 else C.TEXT));p.setFont(QFont("Segoe UI",8,QFont.Weight.Bold))
404
+ p.drawText(QRect(0,0,w,h),Qt.AlignmentFlag.AlignCenter,f"{self._v:.0f}%")
405
+ p.end()
406
+
407
+
408
+ class NeonBtn(QPushButton):
409
+ """Glowing neon button."""
410
+ def __init__(self,text,color=C.PINK,parent=None):
411
+ super().__init__(text,parent)
412
+ self._c=color;self._g=0;self._h=False
413
+ self.setCursor(Qt.CursorShape.PointingHandCursor);self.setFixedHeight(40)
414
+ self.setFont(QFont("Segoe UI",11,QFont.Weight.Bold))
415
+ t=QTimer(self);t.timeout.connect(self._tick);t.start(20)
416
+ def _tick(self):
417
+ self._g+=(1 if self._h else 0-self._g)*.12;self.update()
418
+ def enterEvent(self,e):self._h=True
419
+ def leaveEvent(self,e):self._h=False
420
+ def paintEvent(self,e):
421
+ p=QPainter(self);p.setRenderHint(QPainter.RenderHint.Antialiasing)
422
+ w,h=self.width(),self.height()
423
+ if self._g>.01:
424
+ gc=QColor(self._c);gc.setAlpha(int(50*self._g))
425
+ p.setBrush(gc);p.setPen(Qt.PenStyle.NoPen)
426
+ p.drawRoundedRect(QRectF(-3,-2,w+6,h+4),12,12)
427
+ g=QLinearGradient(0,0,w,0)
428
+ c1=QColor(self._c);c2=QColor(self._c).lighter(130 if self._g>.5 else 110)
429
+ g.setColorAt(0,c1);g.setColorAt(1,c2)
430
+ p.setBrush(QBrush(g));p.setPen(Qt.PenStyle.NoPen)
431
+ p.drawRoundedRect(QRectF(0,0,w,h),10,10)
432
+ # Shimmer
433
+ if self._g>.3:
434
+ sx=(self._g*3%1)*w
435
+ sg=QLinearGradient(sx-40,0,sx+40,0)
436
+ sg.setColorAt(0,QColor(255,255,255,0));sg.setColorAt(.5,QColor(255,255,255,int(50*self._g)));sg.setColorAt(1,QColor(255,255,255,0))
437
+ cp=QPainterPath();cp.addRoundedRect(QRectF(0,0,w,h),10,10)
438
+ p.setClipPath(cp);p.setBrush(QBrush(sg));p.drawRect(0,0,w,h);p.setClipping(False)
439
+ p.setPen(QColor(C.WHITE));p.setFont(self.font())
440
+ p.drawText(QRect(0,0,w,h),Qt.AlignmentFlag.AlignCenter,self.text())
441
+ p.end()
442
+
443
+
444
+ class GlowLabel(QLabel):
445
+ """Breathing glow text."""
446
+ def __init__(self,text,color=C.GOLD,size=20):
447
+ super().__init__(text)
448
+ self._c=color;self._g=0;self._d=1
449
+ self.setFont(QFont("Segoe UI",size,QFont.Weight.Bold))
450
+ self.setStyleSheet(f"color:{color};background:transparent;border:none;")
451
+ t=QTimer(self);t.timeout.connect(self._tick);t.start(50)
452
+ def _tick(self):
453
+ self._g+=.04*self._d
454
+ if self._g>=1:self._d=-1
455
+ elif self._g<=0:self._d=1
456
+ def paintEvent(self,e):
457
+ p=QPainter(self);p.setRenderHint(QPainter.RenderHint.Antialiasing)
458
+ # Glow behind text
459
+ p.setPen(Qt.PenStyle.NoPen)
460
+ gc=QColor(self._c);gc.setAlpha(int(20+15*self._g))
461
+ fm=QFontMetrics(self.font())
462
+ tw=fm.horizontalAdvance(self.text())
463
+ p.setBrush(gc)
464
+ p.drawRoundedRect(QRectF(0,self.height()*.2,tw+10,self.height()*.6),8,8)
465
+ # Text
466
+ p.setPen(QColor(self._c))
467
+ p.setFont(self.font())
468
+ p.drawText(QRect(0,0,self.width(),self.height()),Qt.AlignmentFlag.AlignVCenter,self.text())
469
+ p.end()
470
+
471
+
472
+ class AnimCard(QFrame):
473
+ """Card with hover lift + border glow."""
474
+ def __init__(self,border_color=C.BORDER):
475
+ super().__init__()
476
+ self._bc=border_color;self._h=False;self._lift=0
477
+ self.setStyleSheet(f"background:{C.CARD};border:1px solid {border_color};border-radius:14px;")
478
+ sh=QGraphicsDropShadowEffect();sh.setBlurRadius(12);sh.setColor(QColor(0,0,0,50));sh.setOffset(0,4)
479
+ self.setGraphicsEffect(sh)
480
+ t=QTimer(self);t.timeout.connect(self._tick);t.start(20)
481
+ def _tick(self):
482
+ old=self._lift;self._lift+=((1 if self._h else 0)-self._lift)*.12
483
+ if abs(old-self._lift)>.005:
484
+ ef=self.graphicsEffect()
485
+ if ef:ef.setBlurRadius(12+self._lift*15);ef.setOffset(0,4-self._lift*3)
486
+ def enterEvent(self,e):self._h=True
487
+ def leaveEvent(self,e):self._h=False
488
+
489
+
490
+ class StatCard(AnimCard):
491
+ """Stat card."""
492
+ def __init__(self,icon,label,color):
493
+ super().__init__(color+"30")
494
+ self.setFixedHeight(85);self.setMinimumWidth(160)
495
+ l=QHBoxLayout(self);l.setContentsMargins(14,10,14,10)
496
+ iw=QLabel(icon);iw.setFixedSize(38,38);iw.setAlignment(Qt.AlignmentFlag.AlignCenter)
497
+ iw.setStyleSheet(f"background:{color}20;border-radius:19px;font-size:18px;border:none;")
498
+ l.addWidget(iw)
499
+ tl=QVBoxLayout();tl.setSpacing(1)
500
+ self._vl=QLabel("---");self._vl.setFont(QFont("Segoe UI",16,QFont.Weight.Bold))
501
+ self._vl.setStyleSheet(f"color:{color};border:none;background:transparent;")
502
+ tl.addWidget(self._vl)
503
+ ll=QLabel(label);ll.setStyleSheet(f"color:{C.DIM};font-size:10px;border:none;background:transparent;")
504
+ tl.addWidget(ll)
505
+ l.addLayout(tl);l.addStretch()
506
+ def set_value(self,v):self._vl.setText(v)
507
+
508
+
509
+ class MascotWidget(QWidget):
510
+ """Animated ASCII art mascot for each page."""
511
+ def __init__(self,frames,color=C.CYAN,size=14):
512
+ super().__init__()
513
+ self._frames=frames;self._color=color;self._idx=0;self._size=size
514
+ self.setFixedSize(200,120)
515
+ t=QTimer(self);t.timeout.connect(self._tick);t.start(500)
516
+ def _tick(self):self._idx=(self._idx+1)%len(self._frames);self.update()
517
+ def paintEvent(self,e):
518
+ p=QPainter(self);p.setRenderHint(QPainter.RenderHint.Antialiasing)
519
+ p.setPen(QColor(self._color))
520
+ p.setFont(QFont("Consolas",self._size))
521
+ lines=self._frames[self._idx].split('\n')
522
+ y=15
523
+ for line in lines:
524
+ p.drawText(5,y,line);y+=self._size+2
525
+ p.end()
526
+
527
+
528
+ class LiveVitals(QWidget):
529
+ """Real-time CPU/RAM bars."""
530
+ def __init__(self):
531
+ super().__init__()
532
+ self.setFixedHeight(60)
533
+ layout=QVBoxLayout(self);layout.setContentsMargins(0,0,0,0);layout.setSpacing(4)
534
+
535
+ r1=QHBoxLayout()
536
+ r1.addWidget(QLabel("CPU"));self._cpu=WaveBar(18);r1.addWidget(self._cpu)
537
+ self._cpu_lbl=QLabel("0%");self._cpu_lbl.setFixedWidth(40);self._cpu_lbl.setStyleSheet(f"color:{C.CYAN};border:none;background:transparent;font-size:10px;")
538
+ r1.addWidget(self._cpu_lbl)
539
+ layout.addLayout(r1)
540
+
541
+ r2=QHBoxLayout()
542
+ r2.addWidget(QLabel("RAM"));self._ram=WaveBar(18);r2.addWidget(self._ram)
543
+ self._ram_lbl=QLabel("0%");self._ram_lbl.setFixedWidth(40);self._ram_lbl.setStyleSheet(f"color:{C.PURPLE};border:none;background:transparent;font-size:10px;")
544
+ r2.addWidget(self._ram_lbl)
545
+ layout.addLayout(r2)
546
+
547
+ # Labels style
548
+ for lbl in self.findChildren(QLabel):
549
+ if lbl.text() in ("CPU","RAM"):
550
+ lbl.setFixedWidth(30);lbl.setStyleSheet(f"color:{C.DIM};font-size:10px;font-weight:bold;border:none;background:transparent;")
551
+
552
+ t=QTimer(self);t.timeout.connect(self._tick);t.start(2000)
553
+ self._tick()
554
+
555
+ def _tick(self):
556
+ try:
557
+ cpu=psutil.cpu_percent(interval=0)
558
+ ram=psutil.virtual_memory().percent
559
+ self._cpu.set_value(cpu,C.CYAN,C.BLUE)
560
+ self._ram.set_value(ram,C.PURPLE,C.PINK)
561
+ self._cpu_lbl.setText(f"{cpu:.0f}%")
562
+ self._ram_lbl.setText(f"{ram:.0f}%")
563
+ except:pass
564
+
565
+
566
+ # ═══════════════════════════════════════════════════════
567
+ # MASCOT FRAMES
568
+ # ═══════════════════════════════════════════════════════
569
+ MASCOT_DASH=[
570
+ " (\\_/)\n (o.o)\n (> <) MoneyPack\n /| |\\\n d b",
571
+ " (\\_/)\n (^.^)\n (> <) MoneyPack\n /| |\\\n d b",
572
+ " (\\_/)\n (o.o)\n (< >) MoneyPack\n /| |\\\n d b",
573
+ ]
574
+ MASCOT_FILES=[
575
+ " [___]\n | | scanning...\n | * |\n |___|\n / \\",
576
+ " [___]\n | | scanning...\n |* |\n |___|\n / \\",
577
+ " [___]\n | | scanning...\n | *|\n |___|\n / \\",
578
+ ]
579
+ MASCOT_CLEAN=[
580
+ " ___\n / _ \\\n | |~| | cleaning!\n \\___/\n | |",
581
+ " ___\n / _ \\\n | |~| | cleaning!\n \\___/\n |~|",
582
+ " ___\n / _ \\\n | |~| | cleaning!\n \\___/\n |*|",
583
+ ]
584
+ MASCOT_LOG=[
585
+ " >_\n |*| logging...\n |_|\n ===",
586
+ " >_\n | | logging...\n |*|\n ===",
587
+ " >*\n | | logging...\n |_|\n ===",
588
+ ]
589
+
590
+
591
+ # ═══════════════════════════════════════════════════════
592
+ # TITLEBAR
593
+ # ═══════════════════════════════════════════════════════
594
+ class TitleBar(QWidget):
595
+ def __init__(self,win):
596
+ super().__init__()
597
+ self._w=win;self._drag=None;self.setFixedHeight(40)
598
+ self.setStyleSheet(f"background:{C.BG};border-bottom:1px solid {C.MUTED};")
599
+ l=QHBoxLayout(self);l.setContentsMargins(12,0,6,0)
600
+ # Animated gradient dot
601
+ dot=QLabel();dot.setFixedSize(12,12)
602
+ dot.setStyleSheet(f"background:qlineargradient(x1:0,y1:0,x2:1,y2:1,stop:0 {C.PINK},stop:1 {C.GOLD});border-radius:6px;border:none;")
603
+ l.addWidget(dot)
604
+ t=QLabel(" MoneyPackCleaner");t.setFont(QFont("Segoe UI",10,QFont.Weight.Bold))
605
+ t.setStyleSheet(f"color:{C.GOLD};border:none;background:transparent;")
606
+ l.addWidget(t)
607
+ tag=QLabel("SUPREME");tag.setStyleSheet(f"color:{C.PINK};font-size:8px;font-weight:bold;letter-spacing:2px;border:none;background:transparent;")
608
+ l.addWidget(tag)
609
+ l.addStretch()
610
+ # Window btns
611
+ for txt,act in [("\u2014",win.showMinimized),("\u25a1",self._max),("\u2715",win.close)]:
612
+ b=QPushButton(txt);b.setFixedSize(34,28)
613
+ color=C.RED if txt=="\u2715" else C.DIM
614
+ b.setStyleSheet(f"QPushButton{{background:transparent;color:{color};border:none;font-size:12px;border-radius:4px;}}QPushButton:hover{{background:{C.HOVER};color:{C.WHITE};}}")
615
+ b.clicked.connect(act);l.addWidget(b)
616
+ def _max(self):
617
+ if self._w.isMaximized():self._w.showNormal()
618
+ else:self._w.showMaximized()
619
+ def mousePressEvent(self,e):
620
+ if e.button()==Qt.MouseButton.LeftButton:self._drag=e.globalPosition().toPoint()-self._w.frameGeometry().topLeft()
621
+ def mouseMoveEvent(self,e):
622
+ if self._drag and e.buttons()==Qt.MouseButton.LeftButton:self._w.move(e.globalPosition().toPoint()-self._drag)
623
+ def mouseReleaseEvent(self,e):self._drag=None
624
+ def mouseDoubleClickEvent(self,e):self._max()
625
+
626
+
627
+ # ═══════════════════════════════════════════════════════
628
+ # SIDEBAR
629
+ # ═══════════════════════════════════════════════════════
630
+ class NavBtn(QPushButton):
631
+ def __init__(self,icon,label,idx):
632
+ super().__init__()
633
+ self.idx=idx;self._i=icon;self._l=label;self._a=False;self._h=False;self._g=0
634
+ self.setFixedHeight(46);self.setCursor(Qt.CursorShape.PointingHandCursor)
635
+ self.setStyleSheet("border:none;background:transparent;")
636
+ t=QTimer(self);t.timeout.connect(self._tick);t.start(20)
637
+ def set_active(self,a):self._a=a;self.update()
638
+ def enterEvent(self,e):self._h=True
639
+ def leaveEvent(self,e):self._h=False
640
+ def _tick(self):self._g+=((1 if(self._a or self._h)else 0)-self._g)*.12;self.update()
641
+ def paintEvent(self,e):
642
+ p=QPainter(self);p.setRenderHint(QPainter.RenderHint.Antialiasing)
643
+ w,h=self.width(),self.height()
644
+ if self._g>.01:
645
+ c=QColor(C.PINK if self._a else C.CYAN);c.setAlpha(int(20*self._g))
646
+ p.setBrush(c);p.setPen(Qt.PenStyle.NoPen);p.drawRoundedRect(QRectF(4,2,w-8,h-4),8,8)
647
+ if self._a:
648
+ g=QLinearGradient(0,0,0,h);g.setColorAt(0,QColor(C.PINK));g.setColorAt(1,QColor(C.PURPLE))
649
+ p.setBrush(QBrush(g));p.setPen(Qt.PenStyle.NoPen);p.drawRoundedRect(QRectF(0,6,3,h-12),2,2)
650
+ p.setPen(QColor(C.PINK if self._a else(C.TEXT if self._h else C.DIM)))
651
+ p.setFont(QFont("Segoe UI Emoji",14));p.drawText(QRect(16,0,28,h),Qt.AlignmentFlag.AlignVCenter,self._i)
652
+ p.setPen(QColor(C.WHITE if self._a else(C.TEXT if self._h else C.DIM)))
653
+ p.setFont(QFont("Segoe UI",10,QFont.Weight.Bold if self._a else QFont.Weight.Normal))
654
+ p.drawText(QRect(48,0,w-55,h),Qt.AlignmentFlag.AlignVCenter,self._l)
655
+ p.end()
656
+
657
+
658
+ class Sidebar(QWidget):
659
+ changed=pyqtSignal(int)
660
+ def __init__(self):
661
+ super().__init__()
662
+ self.setFixedWidth(190);self.setStyleSheet(f"background:{C.BG2};border-right:1px solid {C.MUTED};")
663
+ l=QVBoxLayout(self);l.setContentsMargins(0,10,0,10);l.setSpacing(0)
664
+ # Brand
665
+ bl=QHBoxLayout();bl.setContentsMargins(14,6,0,14)
666
+ dot=QLabel();dot.setFixedSize(24,24)
667
+ dot.setStyleSheet(f"background:qlineargradient(x1:0,y1:0,x2:1,y2:1,stop:0 {C.PINK},stop:1 {C.GOLD});border-radius:12px;border:none;")
668
+ bl.addWidget(dot)
669
+ btl=QVBoxLayout();btl.setSpacing(0)
670
+ bn=QLabel("MoneyPack");bn.setFont(QFont("Segoe UI",10,QFont.Weight.Bold));bn.setStyleSheet(f"color:{C.GOLD};border:none;background:transparent;")
671
+ btl.addWidget(bn)
672
+ bs=QLabel("SUPREME");bs.setStyleSheet(f"color:{C.PINK};font-size:7px;letter-spacing:2px;border:none;background:transparent;")
673
+ btl.addWidget(bs)
674
+ bl.addLayout(btl);bl.addStretch();l.addLayout(bl)
675
+ l.addSpacing(8)
676
+
677
+ self._btns=[]
678
+ for i,(icon,label) in enumerate([("🏠","Dashboard"),("πŸ“","Large Files"),("⏰","Old Files"),("πŸ“‹","Duplicates"),("πŸ“‚","Folders"),("🧹","Clean"),("πŸ“Š","Log"),("πŸ”„","Update")]):
679
+ btn=NavBtn(icon,label,i);btn.clicked.connect(lambda _,idx=i:self._click(idx))
680
+ l.addWidget(btn);self._btns.append(btn)
681
+ l.addStretch()
682
+
683
+ # Live vitals at bottom
684
+ self._vitals=LiveVitals()
685
+ l.addWidget(self._vitals)
686
+
687
+ v=QLabel(f"v{APP_VERSION}");v.setAlignment(Qt.AlignmentFlag.AlignCenter)
688
+ v.setStyleSheet(f"color:{C.MUTED};font-size:8px;border:none;background:transparent;padding:4px;")
689
+ l.addWidget(v)
690
+ self._btns[0].set_active(True)
691
+
692
+ def _click(self,idx):
693
+ for b in self._btns:b.set_active(b.idx==idx)
694
+ self.changed.emit(idx)
695
+
696
+
697
+ # ═══════════════════════════════════════════════════════
698
+ # PAGES
699
+ # ═══════════════════════════════════════════════════════
700
+
701
+ class DashPage(QWidget):
702
+ log=pyqtSignal(str)
703
+ def __init__(self):
704
+ super().__init__();self.setStyleSheet("background:transparent;border:none;")
705
+ l=QVBoxLayout(self);l.setContentsMargins(24,20,24,20);l.setSpacing(14)
706
+ # Header
707
+ h=QHBoxLayout()
708
+ h.addWidget(GlowLabel("Dashboard",C.GOLD,20))
709
+ h.addStretch()
710
+ self._mascot=MascotWidget(MASCOT_DASH,C.GOLD,11)
711
+ h.addWidget(self._mascot)
712
+ qb=NeonBtn(" Quick Clean ",C.PINK);qb.clicked.connect(self._quick);h.addWidget(qb)
713
+ l.addLayout(h)
714
+ # Stats
715
+ sl=QHBoxLayout();sl.setSpacing(12)
716
+ self.c1=StatCard("πŸ’Ύ","Total",C.CYAN);self.c2=StatCard("πŸ“Š","Used",C.PINK)
717
+ self.c3=StatCard("✨","Free",C.GREEN);self.c4=StatCard("πŸ—‘","Cleanable",C.YELLOW)
718
+ self.c5=StatCard("⚑","CPU",C.ORANGE)
719
+ sl.addWidget(self.c1);sl.addWidget(self.c2);sl.addWidget(self.c3);sl.addWidget(self.c4);sl.addWidget(self.c5)
720
+ l.addLayout(sl)
721
+ # Mid
722
+ mid=QHBoxLayout();mid.setSpacing(16)
723
+ rc=AnimCard(C.PINK+"30");rl=QVBoxLayout(rc);rl.setAlignment(Qt.AlignmentFlag.AlignCenter)
724
+ self._ring=AnimRing(190);rl.addWidget(self._ring,alignment=Qt.AlignmentFlag.AlignCenter)
725
+ mid.addWidget(rc)
726
+ # Drives
727
+ dc=AnimCard(C.CYAN+"30");dl=QVBoxLayout(dc);dl.setContentsMargins(14,14,14,14);dl.setSpacing(10)
728
+ dt=QLabel("Drives");dt.setStyleSheet(f"color:{C.DIM};font-size:11px;font-weight:bold;border:none;background:transparent;")
729
+ dl.addWidget(dt)
730
+ self._bars=[]
731
+ for _ in range(4):
732
+ r=QHBoxLayout()
733
+ lb=QLabel("---");lb.setFixedWidth(45);lb.setStyleSheet(f"color:{C.TEXT};border:none;background:transparent;font-size:10px;")
734
+ r.addWidget(lb)
735
+ bar=WaveBar(20);r.addWidget(bar)
736
+ dl.addLayout(r);self._bars.append((lb,bar))
737
+ dl.addStretch()
738
+ mid.addWidget(dc)
739
+ # System info card
740
+ ic=AnimCard(C.PURPLE+"30");il=QVBoxLayout(ic);il.setContentsMargins(14,14,14,14);il.setSpacing(6)
741
+ it=QLabel("System");it.setStyleSheet(f"color:{C.DIM};font-size:11px;font-weight:bold;border:none;background:transparent;")
742
+ il.addWidget(it)
743
+ self._info_labels=[]
744
+ for label in ["OS","Host","Arch","Python","Procs"]:
745
+ r=QHBoxLayout()
746
+ k=QLabel(label);k.setStyleSheet(f"color:{C.DIM};font-size:10px;border:none;background:transparent;");r.addWidget(k)
747
+ v=QLabel("...");v.setStyleSheet(f"color:{C.TEXT};font-size:10px;border:none;background:transparent;font-weight:bold;");r.addWidget(v)
748
+ il.addLayout(r);self._info_labels.append(v)
749
+ il.addStretch()
750
+ mid.addWidget(ic)
751
+ l.addLayout(mid)
752
+ self.refresh()
753
+
754
+ def refresh(self):
755
+ disks=Engine.disks()
756
+ total=sum(d['t'] for d in disks);used=sum(d['u'] for d in disks);free=sum(d['f'] for d in disks)
757
+ self.c1.set_value(fmt(total));self.c2.set_value(fmt(used));self.c3.set_value(fmt(free))
758
+ tsz,_=Engine.temp_size();self.c4.set_value(fmt(tsz))
759
+ try:self.c5.set_value(f"{psutil.cpu_percent(interval=0):.0f}%")
760
+ except:pass
761
+ pct=round(used/total*100,1) if total else 0
762
+ self._ring.set_value(pct)
763
+ for i,d in enumerate(disks[:4]):
764
+ lb,bar=self._bars[i];lb.setText(d['d'])
765
+ c1=C.GREEN if d['p']<60 else(C.YELLOW if d['p']<85 else C.RED)
766
+ c2=C.CYAN if d['p']<60 else(C.GOLD if d['p']<85 else C.PINK)
767
+ bar.set_value(d['p'],c1,c2)
768
+ # Sys info
769
+ info=Engine.sys_info()
770
+ vals=[info['os'],info['host'],info['arch'],info['py'],str(len(psutil.pids()))]
771
+ for i,v in enumerate(vals[:5]):
772
+ if i<len(self._info_labels):self._info_labels[i].setText(v)
773
+
774
+ def _quick(self):
775
+ r=Engine.clean();self.log.emit(f"Quick: {r['del']} files, {fmt(r['freed'])} freed");self.refresh()
776
+
777
+
778
+ class FilesPage(QWidget):
779
+ log=pyqtSignal(str)
780
+ def __init__(self):
781
+ super().__init__();self.setStyleSheet("background:transparent;border:none;")
782
+ l=QVBoxLayout(self);l.setContentsMargins(24,20,24,20);l.setSpacing(10)
783
+ h=QHBoxLayout();h.addWidget(GlowLabel("Large Files",C.CYAN,18));h.addStretch()
784
+ h.addWidget(MascotWidget(MASCOT_FILES,C.CYAN,10))
785
+ l.addLayout(h)
786
+ ctrl=QHBoxLayout()
787
+ self._p=QLineEdit(str(Path.home()));self._p.setStyleSheet(f"background:{C.CARD};border:1px solid {C.MUTED};border-radius:10px;padding:9px 12px;color:{C.TEXT};font-size:11px;")
788
+ ctrl.addWidget(self._p)
789
+ bb=NeonBtn("Browse",C.PURPLE);bb.setFixedWidth(90);bb.clicked.connect(lambda:self._p.setText(QFileDialog.getExistingDirectory(self) or self._p.text()))
790
+ ctrl.addWidget(bb)
791
+ self._sp=QSpinBox();self._sp.setRange(1,5000);self._sp.setValue(50);self._sp.setSuffix(" MB")
792
+ self._sp.setStyleSheet(f"background:{C.CARD};border:1px solid {C.MUTED};border-radius:8px;padding:6px;color:{C.TEXT};")
793
+ ctrl.addWidget(self._sp)
794
+ sb=NeonBtn("Scan",C.CYAN);sb.setFixedWidth(90);sb.clicked.connect(self._scan);ctrl.addWidget(sb)
795
+ l.addLayout(ctrl)
796
+ self._tree=QTreeWidget();self._tree.setHeaderLabels(["File","Size","Type","Path"])
797
+ self._tree.header().setSectionResizeMode(0,QHeaderView.ResizeMode.Stretch)
798
+ self._tree.setSelectionMode(QAbstractItemView.SelectionMode.ExtendedSelection)
799
+ self._tree.setAlternatingRowColors(True)
800
+ self._tree.setStyleSheet(f"QTreeWidget{{background:{C.PANEL};border:1px solid {C.MUTED};border-radius:10px;color:{C.TEXT};alternate-background-color:{C.BG2};}}QTreeWidget::item{{padding:5px;}}QTreeWidget::item:selected{{background:{C.HOVER};}}QTreeWidget::item:hover{{background:{C.CARD};}}QHeaderView::section{{background:{C.BG2};color:{C.DIM};border:none;padding:7px;font-weight:bold;font-size:10px;}}")
801
+ l.addWidget(self._tree)
802
+ bot=QHBoxLayout()
803
+ self._st=QLabel("Ready");self._st.setStyleSheet(f"color:{C.DIM};border:none;background:transparent;")
804
+ bot.addWidget(self._st);bot.addStretch()
805
+ db=NeonBtn("Delete Selected",C.RED);db.setFixedWidth(150);db.clicked.connect(self._del);bot.addWidget(db)
806
+ l.addLayout(bot)
807
+ self._w=None
808
+ def _scan(self):
809
+ self._tree.clear();self._st.setText("Scanning...")
810
+ self._w=Worker(Engine.large,self._p.text(),self._sp.value()*1024*1024)
811
+ self._w.done.connect(self._done);self._w.start()
812
+ def _done(self,results):
813
+ colors={'Video':C.RED,'Audio':C.YELLOW,'Image':C.GREEN,'Archive':C.PURPLE,'Installer':C.PINK,'Document':C.CYAN,'Code':C.BLUE}
814
+ total=sum(r['s'] for r in results)
815
+ for r in results:
816
+ it=QTreeWidgetItem();it.setText(0,r['n']);it.setText(1,fmt(r['s']));it.setText(2,r['c']);it.setText(3,r['p'])
817
+ it.setData(0,Qt.ItemDataRole.UserRole,r['p']);it.setForeground(2,QColor(colors.get(r['c'],C.DIM)))
818
+ self._tree.addTopLevelItem(it)
819
+ self._st.setText(f"{len(results)} files ({fmt(total)})")
820
+ self.log.emit(f"Found {len(results)} large files ({fmt(total)})")
821
+ def _del(self):
822
+ items=self._tree.selectedItems()
823
+ if not items:return
824
+ paths=[i.data(0,Qt.ItemDataRole.UserRole) for i in items]
825
+ if QMessageBox.question(self,"Delete",f"Delete {len(paths)} files permanently?")==QMessageBox.StandardButton.Yes:
826
+ r=Engine.delete(paths);self.log.emit(f"Deleted {r['del']}, freed {fmt(r['freed'])}");self._scan()
827
+
828
+
829
+ class OldFilesPage(QWidget):
830
+ log=pyqtSignal(str)
831
+ def __init__(self):
832
+ super().__init__();self.setStyleSheet("background:transparent;border:none;")
833
+ l=QVBoxLayout(self);l.setContentsMargins(24,20,24,20);l.setSpacing(10)
834
+ h=QHBoxLayout();h.addWidget(GlowLabel("Forgotten Files",C.ORANGE,18));h.addStretch()
835
+ desc=QLabel("Files not accessed in months - forgotten and wasting space")
836
+ desc.setStyleSheet(f"color:{C.DIM};border:none;background:transparent;font-size:11px;")
837
+ h.addWidget(desc);l.addLayout(h)
838
+ ctrl=QHBoxLayout()
839
+ self._p=QLineEdit(str(Path.home()));self._p.setStyleSheet(f"background:{C.CARD};border:1px solid {C.MUTED};border-radius:10px;padding:9px;color:{C.TEXT};")
840
+ ctrl.addWidget(self._p)
841
+ self._days=QSpinBox();self._days.setRange(30,3650);self._days.setValue(180);self._days.setSuffix(" days")
842
+ self._days.setStyleSheet(f"background:{C.CARD};border:1px solid {C.MUTED};border-radius:8px;padding:6px;color:{C.TEXT};")
843
+ ctrl.addWidget(self._days)
844
+ sb=NeonBtn("Find",C.ORANGE);sb.setFixedWidth(90);sb.clicked.connect(self._scan);ctrl.addWidget(sb)
845
+ l.addLayout(ctrl)
846
+ self._tree=QTreeWidget();self._tree.setHeaderLabels(["File","Size","Last Access","Type","Path"])
847
+ self._tree.header().setSectionResizeMode(0,QHeaderView.ResizeMode.Stretch)
848
+ self._tree.setSelectionMode(QAbstractItemView.SelectionMode.ExtendedSelection)
849
+ self._tree.setAlternatingRowColors(True)
850
+ self._tree.setStyleSheet(f"QTreeWidget{{background:{C.PANEL};border:1px solid {C.MUTED};border-radius:10px;color:{C.TEXT};alternate-background-color:{C.BG2};}}QTreeWidget::item:selected{{background:{C.HOVER};}}QHeaderView::section{{background:{C.BG2};color:{C.DIM};border:none;padding:7px;font-size:10px;}}")
851
+ l.addWidget(self._tree)
852
+ self._st=QLabel("");self._st.setStyleSheet(f"color:{C.DIM};border:none;background:transparent;")
853
+ l.addWidget(self._st);self._w=None
854
+ def _scan(self):
855
+ self._tree.clear()
856
+ self._w=Worker(Engine.old_files,self._p.text(),self._days.value())
857
+ self._w.done.connect(self._done);self._w.start()
858
+ def _done(self,results):
859
+ total=sum(r['s'] for r in results)
860
+ for r in results:
861
+ it=QTreeWidgetItem();it.setText(0,r['n']);it.setText(1,fmt(r['s']));it.setText(2,f"{r['d']} days");it.setText(3,r['c']);it.setText(4,r['p'])
862
+ it.setData(0,Qt.ItemDataRole.UserRole,r['p'])
863
+ if r['d']>365:it.setForeground(2,QColor(C.RED))
864
+ elif r['d']>180:it.setForeground(2,QColor(C.YELLOW))
865
+ self._tree.addTopLevelItem(it)
866
+ self._st.setText(f"{len(results)} forgotten files ({fmt(total)})")
867
+ self.log.emit(f"Old files: {len(results)} ({fmt(total)})")
868
+
869
+
870
+ class DupesPage(QWidget):
871
+ log=pyqtSignal(str)
872
+ def __init__(self):
873
+ super().__init__();self.setStyleSheet("background:transparent;border:none;")
874
+ l=QVBoxLayout(self);l.setContentsMargins(24,20,24,20);l.setSpacing(10)
875
+ l.addWidget(GlowLabel("Duplicate Finder",C.PURPLE,18))
876
+ ctrl=QHBoxLayout()
877
+ self._p=QLineEdit(str(Path.home()));self._p.setStyleSheet(f"background:{C.CARD};border:1px solid {C.MUTED};border-radius:10px;padding:9px;color:{C.TEXT};")
878
+ ctrl.addWidget(self._p)
879
+ sb=NeonBtn("Find Dupes",C.PURPLE);sb.setFixedWidth(120);sb.clicked.connect(self._scan);ctrl.addWidget(sb)
880
+ l.addLayout(ctrl)
881
+ self._tree=QTreeWidget();self._tree.setHeaderLabels(["File","Size","Path"])
882
+ self._tree.header().setSectionResizeMode(0,QHeaderView.ResizeMode.Stretch)
883
+ self._tree.setSelectionMode(QAbstractItemView.SelectionMode.ExtendedSelection)
884
+ self._tree.setStyleSheet(f"QTreeWidget{{background:{C.PANEL};border:1px solid {C.MUTED};border-radius:10px;color:{C.TEXT};}}QTreeWidget::item:selected{{background:{C.HOVER};}}QHeaderView::section{{background:{C.BG2};color:{C.DIM};border:none;padding:7px;font-size:10px;}}")
885
+ l.addWidget(self._tree)
886
+ bot=QHBoxLayout()
887
+ self._st=QLabel("");self._st.setStyleSheet(f"color:{C.DIM};border:none;background:transparent;")
888
+ bot.addWidget(self._st);bot.addStretch()
889
+ db=NeonBtn("Delete Selected",C.RED);db.setFixedWidth(150);db.clicked.connect(self._del);bot.addWidget(db)
890
+ l.addLayout(bot);self._w=None
891
+ def _scan(self):
892
+ self._tree.clear();self._st.setText("Scanning...")
893
+ self._w=Worker(Engine.dupes,self._p.text());self._w.done.connect(self._done);self._w.start()
894
+ def _done(self,groups):
895
+ waste=0
896
+ for g in groups[:60]:
897
+ waste+=g['s']*(len(g['f'])-1)
898
+ parent=QTreeWidgetItem();parent.setText(0,f"[{len(g['f'])} copies] {os.path.basename(g['f'][0])}");parent.setText(1,fmt(g['s']))
899
+ parent.setForeground(0,QColor(C.YELLOW))
900
+ for fp in g['f']:
901
+ child=QTreeWidgetItem(parent);child.setText(0,os.path.basename(fp));child.setText(1,fmt(g['s']));child.setText(2,fp)
902
+ child.setData(0,Qt.ItemDataRole.UserRole,fp)
903
+ self._tree.addTopLevelItem(parent)
904
+ self._st.setText(f"{len(groups)} groups | Wasted: {fmt(waste)}")
905
+ self.log.emit(f"Dupes: {len(groups)} groups, {fmt(waste)} wasted")
906
+ def _del(self):
907
+ items=self._tree.selectedItems()
908
+ paths=[i.data(0,Qt.ItemDataRole.UserRole) for i in items if i.data(0,Qt.ItemDataRole.UserRole)]
909
+ if paths and QMessageBox.question(self,"Delete",f"Delete {len(paths)} copies?")==QMessageBox.StandardButton.Yes:
910
+ r=Engine.delete(paths);self.log.emit(f"Deleted {r['del']} dupes");self._scan()
911
+
912
+
913
+ class FoldersPage(QWidget):
914
+ log=pyqtSignal(str)
915
+ def __init__(self):
916
+ super().__init__();self.setStyleSheet("background:transparent;border:none;")
917
+ l=QVBoxLayout(self);l.setContentsMargins(24,20,24,20);l.setSpacing(10)
918
+ l.addWidget(GlowLabel("Folder Sizes",C.BLUE,18))
919
+ ctrl=QHBoxLayout()
920
+ self._p=QLineEdit(str(Path.home()));self._p.setStyleSheet(f"background:{C.CARD};border:1px solid {C.MUTED};border-radius:10px;padding:9px;color:{C.TEXT};")
921
+ ctrl.addWidget(self._p)
922
+ sb=NeonBtn("Analyze",C.BLUE);sb.setFixedWidth(100);sb.clicked.connect(self._scan);ctrl.addWidget(sb)
923
+ l.addLayout(ctrl)
924
+ self._table=QTableWidget();self._table.setColumnCount(4);self._table.setHorizontalHeaderLabels(["Folder","Size","Files","%"])
925
+ self._table.horizontalHeader().setSectionResizeMode(QHeaderView.ResizeMode.Stretch)
926
+ self._table.verticalHeader().setVisible(False);self._table.setEditTriggers(QAbstractItemView.EditTrigger.NoEditTriggers)
927
+ self._table.setStyleSheet(f"QTableWidget{{background:{C.PANEL};border:1px solid {C.MUTED};border-radius:10px;color:{C.TEXT};}}QHeaderView::section{{background:{C.BG2};color:{C.DIM};border:none;padding:7px;font-size:10px;}}")
928
+ l.addWidget(self._table)
929
+ self._st=QLabel("");self._st.setStyleSheet(f"color:{C.DIM};border:none;background:transparent;")
930
+ l.addWidget(self._st);self._w=None
931
+ def _scan(self):
932
+ self._table.setRowCount(0);self._st.setText("Analyzing...")
933
+ self._w=Worker(Engine.folder_sizes,self._p.text());self._w.done.connect(self._done);self._w.start()
934
+ def _done(self,folders):
935
+ self._table.setRowCount(len(folders[:30]))
936
+ for i,f in enumerate(folders[:30]):
937
+ self._table.setItem(i,0,QTableWidgetItem(f['n']))
938
+ self._table.setItem(i,1,QTableWidgetItem(fmt(f['s'])))
939
+ self._table.setItem(i,2,QTableWidgetItem(f"{f.get('file_count',0):,}" if 'file_count' in f else ""))
940
+ bar=WaveBar(18);bar.set_value(f['pct'],C.BLUE,C.PURPLE)
941
+ self._table.setCellWidget(i,3,bar)
942
+ self._st.setText(f"{len(folders)} folders")
943
+
944
+
945
+ class CleanPage(QWidget):
946
+ log=pyqtSignal(str)
947
+ def __init__(self):
948
+ super().__init__();self.setStyleSheet("background:transparent;border:none;")
949
+ l=QVBoxLayout(self);l.setContentsMargins(24,20,24,20);l.setSpacing(14)
950
+ h=QHBoxLayout();h.addWidget(GlowLabel("System Cleaner",C.GREEN,18));h.addStretch()
951
+ h.addWidget(MascotWidget(MASCOT_CLEAN,C.GREEN,10));l.addLayout(h)
952
+ # Info
953
+ self._info=QLabel("Analyzing...");self._info.setStyleSheet(f"color:{C.TEXT};border:none;background:transparent;font-size:12px;")
954
+ l.addWidget(self._info)
955
+ # Options - 2 columns
956
+ opts=QHBoxLayout();opts.setSpacing(12)
957
+ col1=AnimCard(C.GREEN+"30");c1l=QVBoxLayout(col1);c1l.setContentsMargins(14,14,14,14);c1l.setSpacing(10)
958
+ c1l.addWidget(QLabel("Files"));self._checks=[]
959
+ chk_style=f"QCheckBox{{color:{C.TEXT};spacing:8px;border:none;background:transparent;font-size:11px;}}QCheckBox::indicator{{width:20px;height:20px;border:2px solid {C.MUTED};border-radius:5px;background:{C.PANEL};}}QCheckBox::indicator:checked{{background:qlineargradient(x1:0,y1:0,x2:1,y2:1,stop:0 {C.PINK},stop:1 {C.PURPLE});border-color:{C.PINK};}}"
960
+ for text,checked in [("Temp Files",True),("Prefetch",True),("Thumbnails",True),("Crash Dumps",True),("Log Files",False)]:
961
+ cb=QCheckBox(text);cb.setChecked(checked);cb.setStyleSheet(chk_style)
962
+ c1l.addWidget(cb);self._checks.append(cb)
963
+ opts.addWidget(col1)
964
+
965
+ col2=AnimCard(C.PURPLE+"30");c2l=QVBoxLayout(col2);c2l.setContentsMargins(14,14,14,14);c2l.setSpacing(10)
966
+ c2l.addWidget(QLabel("System"))
967
+ for text,checked in [("Recycle Bin",True),("Browser Cache",False),("Windows Update",False),("Font Cache",False),("DNS Cache",False)]:
968
+ cb=QCheckBox(text);cb.setChecked(checked);cb.setStyleSheet(chk_style)
969
+ c2l.addWidget(cb);self._checks.append(cb)
970
+ opts.addWidget(col2)
971
+ l.addLayout(opts)
972
+
973
+ # Clean button
974
+ cb=NeonBtn(" CLEAN NOW ",C.GREEN);cb.setFixedHeight(50);cb.setFont(QFont("Segoe UI",13,QFont.Weight.Bold))
975
+ cb.clicked.connect(self._clean);l.addWidget(cb)
976
+ self._res=QLabel("");self._res.setStyleSheet(f"color:{C.GREEN};border:none;background:transparent;font-size:13px;font-weight:bold;")
977
+ l.addWidget(self._res)
978
+ QTimer.singleShot(300,self._analyze)
979
+
980
+ def _analyze(self):
981
+ sz,cnt=Engine.temp_size()
982
+ self._info.setText(f"Found {cnt:,} cleanable files ({fmt(sz)})")
983
+ def _clean(self):
984
+ if QMessageBox.question(self,"Clean","Clean all checked items?")==QMessageBox.StandardButton.Yes:
985
+ r=Engine.clean()
986
+ # Also browser if checked
987
+ if any(c.text()=="Browser Cache" and c.isChecked() for c in self._checks):
988
+ br=Engine.clean_browser();r['del']+=br['del'];r['freed']+=br['freed']
989
+ if any(c.text()=="Recycle Bin" and c.isChecked() for c in self._checks):
990
+ Engine.empty_trash()
991
+ self._res.setText(f"Cleaned {r['del']:,} files | Freed {fmt(r['freed'])}")
992
+ self.log.emit(f"Clean: {r['del']} files, {fmt(r['freed'])} freed")
993
+ QTimer.singleShot(1000,self._analyze)
994
+
995
+
996
+ class LogPage(QWidget):
997
+ def __init__(self):
998
+ super().__init__();self.setStyleSheet("background:transparent;border:none;")
999
+ l=QVBoxLayout(self);l.setContentsMargins(24,20,24,20);l.setSpacing(10)
1000
+ h=QHBoxLayout();h.addWidget(GlowLabel("Activity Log",C.CYAN,18));h.addStretch()
1001
+ h.addWidget(MascotWidget(MASCOT_LOG,C.CYAN,10));l.addLayout(h)
1002
+ self._log=QTextEdit();self._log.setReadOnly(True);self._log.setFont(QFont("JetBrains Mono",10))
1003
+ self._log.setStyleSheet(f"background:{C.PANEL};border:1px solid {C.MUTED};border-radius:10px;color:{C.TEXT};padding:10px;")
1004
+ l.addWidget(self._log)
1005
+ self.add("MoneyPackCleaner SUPREME initialized")
1006
+ def add(self,msg):
1007
+ ts=datetime.now().strftime("%H:%M:%S")
1008
+ self._log.append(f"<span style='color:{C.DIM}'>[{ts}]</span> <span style='color:{C.CYAN}'>></span> <span style='color:{C.TEXT}'>{msg}</span>")
1009
+
1010
+
1011
+ class UpdatePage(QWidget):
1012
+ def __init__(self):
1013
+ super().__init__();self.setStyleSheet("background:transparent;border:none;")
1014
+ l=QVBoxLayout(self);l.setContentsMargins(24,20,24,20);l.setSpacing(16)
1015
+ l.addWidget(GlowLabel("Auto-Updater",C.GOLD,18))
1016
+
1017
+ card=AnimCard(C.GOLD+"30");cl=QVBoxLayout(card);cl.setContentsMargins(20,20,20,20);cl.setSpacing(12)
1018
+ cl.addWidget(QLabel(f"Current Version: v{APP_VERSION}"))
1019
+ self._status=QLabel("Click 'Check' to look for updates")
1020
+ self._status.setStyleSheet(f"color:{C.TEXT};border:none;background:transparent;font-size:12px;")
1021
+ cl.addWidget(self._status)
1022
+
1023
+ bl=QHBoxLayout()
1024
+ chk=NeonBtn("Check for Updates",C.GOLD);chk.clicked.connect(self._check);bl.addWidget(chk)
1025
+ upd=NeonBtn("Install Update",C.GREEN);upd.clicked.connect(self._install);bl.addWidget(upd)
1026
+ bl.addStretch()
1027
+ cl.addLayout(bl)
1028
+ l.addWidget(card)
1029
+
1030
+ info=AnimCard(C.MUTED);il=QVBoxLayout(info);il.setContentsMargins(16,16,16,16)
1031
+ il.addWidget(QLabel("How updates work:"))
1032
+ for t in ["1. New versions are hosted on HuggingFace","2. Click 'Check' to see if a new version exists","3. Click 'Install' to download and replace automatically","4. App restarts with the new version"]:
1033
+ lb=QLabel(t);lb.setStyleSheet(f"color:{C.DIM};font-size:11px;border:none;background:transparent;")
1034
+ il.addWidget(lb)
1035
+ l.addWidget(info)
1036
+ l.addStretch()
1037
+
1038
+ def _check(self):
1039
+ self._status.setText("Checking...")
1040
+ info=Updater.check()
1041
+ if info.get("available"):
1042
+ self._status.setText(f"UPDATE AVAILABLE: v{info['version']} - {info.get('changelog','')}")
1043
+ self._status.setStyleSheet(f"color:{C.GREEN};border:none;background:transparent;font-size:13px;font-weight:bold;")
1044
+ else:
1045
+ self._status.setText("You're on the latest version!")
1046
+ self._status.setStyleSheet(f"color:{C.CYAN};border:none;background:transparent;font-size:12px;")
1047
+
1048
+ def _install(self):
1049
+ ok,msg=Updater.update()
1050
+ if ok:
1051
+ QMessageBox.information(self,"Updated",msg+"\n\nRestarting...")
1052
+ os.execl(sys.executable,sys.executable,*sys.argv)
1053
+ else:
1054
+ QMessageBox.warning(self,"Failed",msg)
1055
+
1056
+
1057
+ # ═══════════════════════════════════════════════════════
1058
+ # MAIN WINDOW
1059
+ # ═══════════════════════════════════════════════════════
1060
+ class MainWindow(QMainWindow):
1061
+ def __init__(self):
1062
+ super().__init__()
1063
+ self.setWindowTitle("MoneyPackCleaner SUPREME")
1064
+ self.setMinimumSize(1150,720);self.resize(1350,820)
1065
+ self.setWindowFlags(Qt.WindowType.FramelessWindowHint)
1066
+ self.setStyleSheet(f"QMainWindow{{background:{C.BG};}}")
1067
+
1068
+ ct=QWidget();self.setCentralWidget(ct)
1069
+ ml=QVBoxLayout(ct);ml.setContentsMargins(0,0,0,0);ml.setSpacing(0)
1070
+ ml.addWidget(TitleBar(self))
1071
+
1072
+ body=QHBoxLayout();body.setContentsMargins(0,0,0,0);body.setSpacing(0)
1073
+ self.sidebar=Sidebar();self.sidebar.changed.connect(self._nav);body.addWidget(self.sidebar)
1074
+
1075
+ content=QWidget();content.setStyleSheet(f"background:{C.BG};")
1076
+ cl=QVBoxLayout(content);cl.setContentsMargins(0,0,0,0)
1077
+ self.particles=Particles(content,45,C.CYAN,C.PINK)
1078
+
1079
+ self.stack=QStackedWidget();self.stack.setStyleSheet("background:transparent;")
1080
+ self.pg_dash=DashPage();self.pg_files=FilesPage();self.pg_old=OldFilesPage()
1081
+ self.pg_dupes=DupesPage();self.pg_folders=FoldersPage()
1082
+ self.pg_clean=CleanPage();self.pg_log=LogPage();self.pg_update=UpdatePage()
1083
+
1084
+ for pg in [self.pg_dash,self.pg_files,self.pg_old,self.pg_dupes,self.pg_folders,self.pg_clean,self.pg_log,self.pg_update]:
1085
+ self.stack.addWidget(pg)
1086
+
1087
+ cl.addWidget(self.stack);body.addWidget(content);ml.addLayout(body)
1088
+
1089
+ # Connect logs
1090
+ for pg in [self.pg_dash,self.pg_files,self.pg_old,self.pg_dupes,self.pg_folders,self.pg_clean]:
1091
+ pg.log.connect(self.pg_log.add)
1092
+
1093
+ # Tray
1094
+ self._tray_setup()
1095
+ # Auto-update check
1096
+ QTimer.singleShot(5000,self._auto_update_check)
1097
+
1098
+ def _nav(self,idx):self.stack.setCurrentIndex(idx)
1099
+
1100
+ def _tray_setup(self):
1101
+ if not QSystemTrayIcon.isSystemTrayAvailable():return
1102
+ pix=QPixmap(32,32);pix.fill(QColor(0,0,0,0))
1103
+ pa=QPainter(pix);pa.setRenderHint(QPainter.RenderHint.Antialiasing)
1104
+ g=QLinearGradient(0,0,32,32);g.setColorAt(0,QColor(C.PINK));g.setColorAt(1,QColor(C.GOLD))
1105
+ pa.setBrush(QBrush(g));pa.setPen(Qt.PenStyle.NoPen);pa.drawEllipse(2,2,28,28)
1106
+ pa.setPen(QColor(C.WHITE));pa.setFont(QFont("Segoe UI",12,QFont.Weight.Bold))
1107
+ pa.drawText(QRect(0,0,32,32),Qt.AlignmentFlag.AlignCenter,"M");pa.end()
1108
+ self._tray=QSystemTrayIcon(QIcon(pix),self);self._tray.setToolTip("MoneyPackCleaner SUPREME")
1109
+ menu=QMenu();menu.addAction("Show",self.show);menu.addAction("Quick Clean",self.pg_dash._quick)
1110
+ menu.addSeparator();menu.addAction("Exit",QApplication.quit)
1111
+ self._tray.setContextMenu(menu);self._tray.show()
1112
+
1113
+ def _auto_update_check(self):
1114
+ info=Updater.check()
1115
+ if info.get("available"):
1116
+ self.pg_log.add(f"Update available: v{info['version']}")
1117
+ if hasattr(self,'_tray'):
1118
+ self._tray.showMessage("MoneyPackCleaner",f"Update v{info['version']} available!",QSystemTrayIcon.MessageIcon.Information,5000)
1119
+
1120
+ def resizeEvent(self,e):
1121
+ super().resizeEvent(e)
1122
+ if hasattr(self,'particles'):
1123
+ self.particles.resize(self.width()-190,self.height()-40);self.particles.move(190,40)
1124
+
1125
+ def closeEvent(self,e):
1126
+ if hasattr(self,'_tray') and self._tray.isVisible():self.hide();e.ignore()
1127
+ else:e.accept()
1128
+
1129
+
1130
+ def main():
1131
+ app=QApplication(sys.argv);app.setFont(QFont("Segoe UI",10))
1132
+ w=MainWindow();w.show()
1133
+ sys.exit(app.exec())
1134
+
1135
+ if __name__=="__main__":
1136
+ main()