cryptor-app 0.0.49__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
File without changes
@@ -0,0 +1,14 @@
1
+ from cryptor_app.main import run_dependency_check, run_connection, create_main_app
2
+
3
+ def main():
4
+ proceed_to_app = run_dependency_check()
5
+
6
+ if proceed_to_app:
7
+ print("Dependencies verified. Initializing secure application database engines...")
8
+ run_connection()
9
+ create_main_app()
10
+ else:
11
+ print("Application startup terminated by user.")
12
+
13
+ if __name__ == "__main__":
14
+ main()
@@ -0,0 +1,57 @@
1
+ from datetime import datetime, timedelta
2
+
3
+ def Run_Cookie(root, cookie, create_main_app):
4
+ # Lazy imports to keep startup snappy
5
+ from cryptor_app.extras.models import logout_func
6
+ from cryptor_app.config_files.monitor_cookie import cookie_monitor
7
+
8
+ if cookie is None:
9
+ print("Not logged in.")
10
+ return
11
+
12
+ if not hasattr(root, "monitor_active"):
13
+ root.monitor_active = False
14
+
15
+ expire_time = datetime.fromisoformat(cookie.cookie_expire_time)
16
+ time_remaining = expire_time - datetime.now()
17
+
18
+ # --- SCENARIO 1: Cookie has completely expired at 0 seconds ---
19
+ if time_remaining <= timedelta(seconds=0):
20
+ print("Session expired! Forcibly terminating environment...")
21
+
22
+ # 🛡️ Dynamic Guard: Forcibly kill the lingering popup alert box if it's still open on screen
23
+ if hasattr(root, "active_cookie_popup") and root.active_cookie_popup is not None:
24
+ try:
25
+ root.active_cookie_popup.destroy()
26
+ except Exception:
27
+ pass
28
+ root.active_cookie_popup = None
29
+
30
+ if hasattr(root, "check_run_id") and root.check_run_id is not None:
31
+ root.after_cancel(root.check_run_id)
32
+ root.check_run_id = None
33
+
34
+ logout_func(cookie[0])
35
+ root.destroy()
36
+ create_main_app()
37
+ return
38
+
39
+ # --- SCENARIO 2: Cookie expires soon (<= 3 minutes remaining) ---
40
+ elif time_remaining <= timedelta(minutes=3):
41
+ # Only spawn a new window if one isn't currently alive
42
+ if not root.monitor_active:
43
+ root.monitor_active = True
44
+ print(f"Session expiring soon! {time_remaining.total_seconds():.0f}s left.")
45
+
46
+ # Instantiate the window panel
47
+ cookie_window = cookie_monitor(root)
48
+
49
+ # 🛡️ Keep a reference on root so our 1-second checker can access and destroy it at 0s
50
+ root.active_cookie_popup = cookie_window.cookie_box
51
+
52
+ # REMOVED: root.wait_window() - We allow the main thread loop to keep ticking!
53
+
54
+ # --- SCENARIO 3: Session is perfectly safe (> 3 minutes remaining) ---
55
+ else:
56
+ root.monitor_active = False
57
+ root.active_cookie_popup = None
@@ -0,0 +1,22 @@
1
+ # # # # #
2
+ # Installed Modules
3
+ # # # # #
4
+ from tkinter.ttk import Frame, Notebook
5
+
6
+ # # # #
7
+ # Local folders Modules
8
+ # # # #
9
+ from cryptor_app.tabs.sign_in_tab import sign_in_tab
10
+ from cryptor_app.tabs.sign_up_tab import sign_up_tab
11
+
12
+ def welcome_frame(root, create_main_app):
13
+ root.geometry('256x332')
14
+
15
+ welcome_fr = Frame(root)
16
+
17
+ notebook = Notebook(welcome_fr, style="Notebook.TNotebook")
18
+ notebook.pack(fill='both', pady=2, padx=2, expand=1)
19
+ sign_in_tab(notebook, root, create_main_app)
20
+ sign_up_tab(notebook, root)
21
+
22
+ return welcome_fr
@@ -0,0 +1,234 @@
1
+ import tkinter as tk
2
+ from tkinter.ttk import Frame, Button, Label, Combobox
3
+ from tkinter.messagebox import showerror, askokcancel
4
+ from datetime import datetime
5
+ import threading
6
+ import asyncio
7
+
8
+ class AITexterPanel(Frame):
9
+ def __init__(self, master, text_scroll, title_entry, for_var, editor_container):
10
+ super().__init__(master, style="Header.TFrame")
11
+
12
+ self.text_scroll = text_scroll
13
+ self.title_entry = title_entry
14
+ self.for_var = for_var
15
+ self.editor_container = editor_container
16
+
17
+ # Context Preset Dropdown Setup
18
+ self.preset_var = tk.StringVar(value="Generate Secure Password")
19
+ self.ai_dropdown = Combobox(self, textvariable=self.preset_var, values=["Generate Secure Password", "Draft Cryptographic Note", "Custom Prompt..."], state="readonly", width=22)
20
+ self.ai_dropdown.pack(side='left', padx=(0, 6))
21
+
22
+ # Prompt Entry Field
23
+ self.prompt_var = tk.StringVar(value="Create a unique 16-character complex password with symbols.")
24
+ self.prompt_entry = tk.Entry(self, textvariable=self.prompt_var, bg="#dd3663", fg="#ffffff", insertbackground="#FA0909", relief="flat")
25
+ self.prompt_entry.pack(side='left', fill='x', expand=True, padx=(0, 6))
26
+
27
+ self.ai_dropdown.bind("<<ComboboxSelected>>", self.handle_preset_change)
28
+
29
+ # Action Button
30
+ self.ai_btn = Button(
31
+ self,
32
+ text="●",
33
+ style="RoundAI.TButton",
34
+ command=self.trigger_ai_generation,
35
+ cursor="hand2"
36
+ )
37
+ self.ai_btn.pack(side='left', padx=(2, 0))
38
+
39
+ # Tracking state parameters for the animation engine loop
40
+ self.is_generating = False
41
+ self.blink_state = False
42
+ self.overlay = None
43
+
44
+ def handle_preset_change(self, event):
45
+ choice = self.preset_var.get()
46
+ if choice == "Generate Secure Password":
47
+ self.prompt_var.set("Create a unique 16-character complex password with symbols.")
48
+ elif choice == "Draft Cryptographic Note":
49
+ self.prompt_var.set("Draft a secure template for storing multi-factor authentication backup keys.")
50
+ elif choice == "Custom Prompt...":
51
+ self.prompt_var.set("")
52
+ self.prompt_entry.focus()
53
+
54
+ def trigger_ai_generation(self):
55
+ prompt_text = self.prompt_var.get().strip()
56
+ if not prompt_text:
57
+ showerror("Prompt Empty", "Please type an AI instruction or select a security preset first.")
58
+ return
59
+
60
+ # Smart Insertion Guard
61
+ current_content = self.text_scroll.get(1.0, 'end-1c').strip()
62
+ if current_content:
63
+ confirm = askokcancel("Overwrite Warning", "The workspace contains active content. Generating fresh text will wipe the screen. Continue?")
64
+ if not confirm:
65
+ return
66
+
67
+ # Engage generation flags to trigger the blinking loop sequence
68
+ self.is_generating = True
69
+ self.ai_btn.config(state="disabled")
70
+ self.animate_blinking()
71
+ self.winfo_toplevel().update_idletasks()
72
+
73
+ # Instantiate and map our sleek loading overlay right over the editor frame zone!
74
+ self.overlay = AILoadingOverlay(self.editor_container)
75
+
76
+ # 🚀 FIX: Pass a synchronous runner method as the thread target to prevent window freezing
77
+ worker = threading.Thread(target=self._thread_run_loop, args=(prompt_text,))
78
+ worker.daemon = True
79
+ worker.start()
80
+
81
+ def _thread_run_loop(self, prompt_text):
82
+ """ Secondary isolation bridge that safely initializes a fresh async loop inside the thread """
83
+ asyncio.run(self.async_ai_worker(prompt_text))
84
+
85
+ def animate_blinking(self):
86
+ """ Continuous style-toggling function running on a background loop """
87
+ if not self.winfo_exists() or not self.is_generating:
88
+ return
89
+
90
+ # Toggle between two visual style variations
91
+ if self.blink_state:
92
+ self.ai_btn.config(text="○", style="RoundAI.TButton")
93
+ else:
94
+ self.ai_btn.config(text="●", style="RoundAI.TButton")
95
+
96
+ self.blink_state = not self.blink_state
97
+
98
+ # Keep ticking every 200 milliseconds
99
+ self.after(200, self.animate_blinking)
100
+
101
+ async def async_ai_worker(self, prompt_text):
102
+ try:
103
+ import secrets, string
104
+ from ollama import AsyncClient
105
+
106
+ # Use non-blocking async sleep instead of time.sleep()
107
+ await asyncio.sleep(1)
108
+
109
+ client = AsyncClient()
110
+
111
+ if "password" in prompt_text.lower():
112
+ alphabet = string.ascii_letters + string.digits + "!@#$%^&*()_+"
113
+ generated_result = "".join(secrets.choice(alphabet) for _ in range(16))
114
+ generated_result = f"--- GENERATED SECURE CREDENTIAL ---\n\nPassword: {generated_result}\n\nGenerated On: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n\n[Keep this note encrypted for maximum safety]"
115
+ else:
116
+ response = await client.chat(
117
+ model='llama3:8b',
118
+ messages=[
119
+ {
120
+ 'role': 'system',
121
+ 'content': 'You are a secure local cryptographic helper. Output requested text, credentials, or password data structures immediately. Do not introduce conversational chatter or say "Here is your output".'
122
+ },
123
+ {
124
+ 'role': 'user',
125
+ 'content': prompt_text
126
+ }
127
+ ]
128
+ )
129
+ generated_result = response['message']['content']
130
+
131
+ # Route data output back to GUI loop safely
132
+ self.winfo_toplevel().after(0, lambda: self.direct_ai_output_to_editor(generated_result))
133
+
134
+ except Exception as e:
135
+ self.winfo_toplevel().after(0, lambda: self.handle_worker_error(str(e)))
136
+
137
+ def direct_ai_output_to_editor(self, result_text):
138
+ self.is_generating = False
139
+
140
+ if self.overlay:
141
+ self.overlay.destroy()
142
+ self.overlay = None
143
+
144
+ self.text_scroll.delete(1.0, 'end')
145
+ self.text_scroll.insert(1.0, result_text)
146
+
147
+ if "Password" in result_text:
148
+ self.title_entry.insert("1.0","Generated Access Token")
149
+ self.for_var.set("Secure Credential Access Key")
150
+
151
+ self.ai_btn.config(state="normal", text="●", style="RoundAI.TButton")
152
+ self.text_scroll.focus()
153
+
154
+ def handle_worker_error(self, error_message):
155
+ self.is_generating = False
156
+ if self.overlay:
157
+ self.overlay.destroy()
158
+ self.overlay = None
159
+
160
+ showerror("AI API Error", f"Transaction failed:\n{error_message}")
161
+ self.ai_btn.config(state="normal", text="●", style="RoundAI.TButton")
162
+
163
+
164
+ # ----------------------------------------------------
165
+ # 🛡️ SLEEK HIGH-REACTIVITY LOADING OVERLAY PANEL
166
+ # ----------------------------------------------------
167
+ class AILoadingOverlay(Frame):
168
+ def __init__(self, master):
169
+ super().__init__(master, relief="flat")
170
+
171
+ self.place(relx=0, rely=0, relwidth=1, relheight=1)
172
+ self.configure(style="Header.TFrame")
173
+
174
+ # Centered structural layout alignment holder
175
+ center_box = Frame(self, style="Header.TFrame")
176
+ center_box.place(relx=0, rely=0.45, relwidth=1, anchor="w")
177
+
178
+ # 🔄 Native Vector Spinner Canvas Instance
179
+ self.spinner_canvas = tk.Canvas(
180
+ center_box,
181
+ width=60,
182
+ height=60,
183
+ bg="#111111",
184
+ highlightthickness=0
185
+ )
186
+ self.spinner_canvas.pack(pady=10)
187
+
188
+ self.status_lbl = Label(
189
+ center_box,
190
+ text="AI COPILOT IS WORKING\nGenerating secure cryptographic content, please wait...",
191
+ font=('Arial', 11, 'bold'),
192
+ foreground="#ffffff",
193
+ background="#111111",
194
+ justify="center",
195
+ anchor="center"
196
+ )
197
+ self.status_lbl.pack(pady=5)
198
+
199
+ self.angle_step = 0
200
+ self.animate_spinner()
201
+
202
+ def animate_spinner(self):
203
+ """ Generates a smooth, native non-blocking vector rotation loop """
204
+ if not self.winfo_exists():
205
+ return
206
+
207
+ # Clear the previous drawing frame to prevent memory accumulation leaks
208
+ self.spinner_canvas.delete("all")
209
+
210
+ # Calculate the active starting angle offset frame
211
+ start_angle = (self.angle_step * 24) % 360
212
+
213
+ # Draw the primary background matte tracking track ring
214
+ self.spinner_canvas.create_oval(
215
+ 6, 6, 54, 54,
216
+ outline="#252526",
217
+ width=3
218
+ )
219
+
220
+ # Render the active accent loading arc highlight
221
+ self.spinner_canvas.create_arc(
222
+ 6, 6, 54, 54,
223
+ start=start_angle,
224
+ extent=90,
225
+ style="arc",
226
+ outline="#3fa8a5",
227
+ width=4,
228
+ activedash=None
229
+ )
230
+
231
+ self.angle_step += 1
232
+
233
+ # Ticks every 45ms for an incredibly fluid, hardware-accelerated motion effect
234
+ self.after(45, self.animate_spinner)
@@ -0,0 +1,63 @@
1
+ import tkinter as tk
2
+ from tkinter.ttk import Frame, Button, Label
3
+
4
+ class alert_poper(Frame):
5
+ def __init__(self, master=None):
6
+ super().__init__(master)
7
+
8
+ self.stringMsg = tk.StringVar()
9
+ self.selected_notebook = tk.StringVar()
10
+
11
+ # 1. Initialize modern dark flat modal window dimensions
12
+ self.pop = tk.Toplevel(master, relief="flat", takefocus=True, padx=12, pady=12)
13
+ self.pop.attributes('-topmost', True)
14
+ self.pop.resizable(0, 0)
15
+ self.pop.title("System Alert")
16
+ self.pop.configure(bg="#1e1e1e") # Dark fallback background color
17
+
18
+ try:
19
+ self.pop.wm_iconbitmap('cryp.ico')
20
+ except:
21
+ pass
22
+
23
+ # 2. Apply theme label styling flags
24
+ Label(
25
+ self.pop,
26
+ text="Missing information from the database.",
27
+ font=('Arial', 10, 'bold'),
28
+ foreground="#ffffff",
29
+ background="#1e1e1e"
30
+ ).pack(side='top', fill='both', pady=(4, 0))
31
+
32
+ # Inner container layout wrap block
33
+ self.main_frm = Frame(self.pop, padding=(4, 16), style="Header.TFrame")
34
+ self.main_frm.pack(fill="both", side="bottom")
35
+
36
+ # 3. Dynamic layout action button integration matching design theme configurations
37
+ self.yes_btn = Button(
38
+ self.main_frm,
39
+ text='Go to register',
40
+ style="Signup.TButton",
41
+ command=lambda: self.setVal("True"),
42
+ cursor="hand2"
43
+ )
44
+ self.yes_btn.pack(side="left", padx=(0, 4))
45
+
46
+ self.no_btn = Button(
47
+ self.main_frm,
48
+ text="Cancel",
49
+ style="Delete.TButton",
50
+ command=lambda: self.pop.destroy(),
51
+ cursor="hand2"
52
+ )
53
+ self.no_btn.pack(side="right", padx=(4, 0))
54
+
55
+ self.pack()
56
+
57
+ def setVal(self, val):
58
+ """ Sets the control variables and dismisses the alert frame cleanly """
59
+ self.selected_notebook.set(val)
60
+ self.pop.destroy()
61
+
62
+ def manageWindow(self, winvar):
63
+ self.stringMsg.set(winvar)
@@ -0,0 +1,35 @@
1
+ import tkinter as tk
2
+ from tkinter.ttk import *
3
+ import time
4
+
5
+ class ClockFrame(LabelFrame):
6
+ def __init__(self, master):
7
+ super().__init__(master)
8
+ self['text'] = 'Clock'
9
+
10
+ # change the background color to black
11
+ self.label = Label(
12
+ self,
13
+ style='Clock.TLabel',
14
+ text=self.time_string(),
15
+ font=('Digital-7', 16))
16
+
17
+ self.label.pack(expand=True, fill='both')
18
+
19
+ # schedule an update every 1 second
20
+ self.label.after(1000, self.update)
21
+
22
+ def time_string(self):
23
+ hr = time.strftime('%H')
24
+ mn = time.strftime('%M')
25
+ sc = time.strftime('%S')
26
+ return f'{hr} hr\n{mn} min\n{sc} s'
27
+
28
+ def update(self):
29
+ """ update the label every 1 second """
30
+ self.label.configure(text=self.time_string())
31
+
32
+ # schedule another timer
33
+ self.label.after(1000, self.update)
34
+
35
+ self.grid(row=4, column=0, padx=5, pady=(5,50), ipady=5)
@@ -0,0 +1,95 @@
1
+ import tkinter as tk
2
+ from tkinter.ttk import *
3
+ from cryptor_app.extras.models import retrieveFiles, verifyCookie
4
+ from datetime import datetime
5
+
6
+ lifont = ('Times', 12, 'italic')
7
+
8
+ class file_list(Frame):
9
+ def __init__(self, master=None):
10
+ super().__init__(master)
11
+ self.pack(fill='both', expand=True)
12
+
13
+ session_cookie = verifyCookie()
14
+ self.all_files = retrieveFiles(session_cookie[2])
15
+
16
+ self.doc_id = tk.StringVar()
17
+ self.deleted_id = tk.StringVar()
18
+
19
+ Label(self, text=f"Total Saved Files: {len(self.all_files)}", font=lifont, anchor='center', padding=4).pack(side='top', fill='x')
20
+
21
+ self.list_frame = Frame(self)
22
+ self.list_frame.pack(fill='both', expand=True, padx=2, pady=2)
23
+
24
+ self.lst_files = Treeview(self.list_frame, selectmode='browse')
25
+ self.lst_scrbar = Scrollbar(self.list_frame, command=self.lst_files.yview)
26
+ self.lst_scrbar.pack(side='right', fill='y')
27
+ self.lst_files['yscrollcommand'] = self.lst_scrbar.set
28
+
29
+ # Change columns to display Title and Purpose ("For")
30
+ self.lst_files['columns'] = ('title', 'purpose')
31
+
32
+ self.lst_files.column('#0', width=0, stretch='no')
33
+ self.lst_files.column('title', width=130, anchor='w')
34
+ self.lst_files.column('purpose', width=130, anchor='w')
35
+
36
+ # Set readable column headers
37
+ self.lst_files.heading('#0', text='', anchor='center')
38
+ self.lst_files.heading('title', text='Title', anchor='w')
39
+ self.lst_files.heading('purpose', text='Purpose / For', anchor='w')
40
+
41
+ self.populate_tree()
42
+
43
+ self.lst_files.bind('<<TreeviewSelect>>', self.select_record)
44
+ self.lst_files.pack(fill='both', expand=True)
45
+
46
+ def select_record(self, event):
47
+ """ Fires automatically whenever an item inside the file explorer sidebar is highlighted """
48
+ widget = event.widget
49
+ selected_item = widget.focus()
50
+
51
+ if selected_item:
52
+ # 🛡️ FIX: Pull the actual file_id out of text, NOT the title values array
53
+ file_id_str = widget.item(selected_item, 'text')
54
+ if file_id_str:
55
+ self.doc_id.set(file_id_str)
56
+
57
+ def populate_tree(self):
58
+ """ Sorts files chronologically and builds the visible treeview entries """
59
+
60
+ # ⏱️ Sort files dynamically: Latest updated entries bubble directly to the top
61
+ try:
62
+ # item[7] is our last_updated ISO string timestamp from your database schema
63
+ sorted_files = sorted(
64
+ self.all_files,
65
+ key=lambda x: datetime.fromisoformat(x[7]) if (len(x) > 7 and x[7]) else datetime.min,
66
+ reverse=True
67
+ )
68
+ except Exception:
69
+ # Fallback to unsorted state if any database timestamps happen to be corrupted
70
+ sorted_files = self.all_files
71
+
72
+ count = 0
73
+ for item in sorted_files:
74
+ # item[0] = file_id, item[8] = file_title, item[9] = file_for
75
+ file_id_str = item[0].decode('utf-8') if isinstance(item[0], bytes) else str(item[0])
76
+ title_str = item[8] if (len(item) > 8 and item[8]) else "Untitled"
77
+ for_str = item[9] if (len(item) > 9 and item[9]) else "General"
78
+
79
+ # We store the underlying hard file_id inside the item tags or text value
80
+ # so auto_load_file can still capture it instantly behind the scenes!
81
+ self.lst_files.insert(
82
+ parent='',
83
+ index='end',
84
+ iid=count,
85
+ text=file_id_str, # Secretly store the file_id here
86
+ values=(title_str, for_str)
87
+ )
88
+ count += 1
89
+
90
+ def refresh_list(self):
91
+ session_cookie = verifyCookie()
92
+ self.all_files = retrieveFiles(session_cookie[2])
93
+ for item in self.lst_files.get_children():
94
+ self.lst_files.delete(item)
95
+ self.populate_tree()
@@ -0,0 +1,121 @@
1
+ import tkinter as tk
2
+ from tkinter.ttk import *
3
+ import time
4
+
5
+ class LicencesFrame(LabelFrame):
6
+ def __init__(self, master):
7
+ # Explicitly configure LabelFrame style properties to match dark themes
8
+ super().__init__(master, text='Licences')
9
+
10
+ # Create two side-by-side buttons instead of vertical stacking to save vertical space in the sidebar
11
+ self.columnconfigure(0, weight=1)
12
+ self.columnconfigure(1, weight=1)
13
+
14
+ # We pass self.master (the top-level window/root) down to the popups so they position correctly
15
+ Button(
16
+ self,
17
+ text='Licence',
18
+ style="Signup.TButton",
19
+ command=lambda: self.wait_window(LicenceDetails(self.winfo_toplevel()).top),
20
+ cursor='hand2'
21
+ ).grid(row=0, column=0, padx=5, pady=8, sticky='ew')
22
+
23
+ Button(
24
+ self,
25
+ text='Copyright',
26
+ style="Signup.TButton",
27
+ command=lambda: self.wait_window(Copyright(self.winfo_toplevel()).top),
28
+ cursor='hand2'
29
+ ).grid(row=0, column=1, padx=5, pady=8, sticky='ew')
30
+
31
+
32
+
33
+ class LicenceDetails(Frame):
34
+ def __init__(self, master=None):
35
+ super().__init__(master)
36
+
37
+ self.top = tk.Toplevel(master, relief='flat')
38
+ self.top.geometry("340x260")
39
+ self.top.resizable(0,0)
40
+ self.top.title('Licence Agreement')
41
+ self.top.attributes('-topmost', True)
42
+ self.top.configure(bg="#1e1e1e")
43
+
44
+ # The icon
45
+ try:
46
+ self.top.wm_iconbitmap("cryp.ico")
47
+ except:
48
+ pass
49
+
50
+ # Dark mode flat text block configuration
51
+ self.text_fr = tk.Text(
52
+ self.top,
53
+ height=10,
54
+ relief='flat',
55
+ bg='#2d2d2d',
56
+ fg='#ffffff',
57
+ font=('Arial', 10),
58
+ padx=10,
59
+ pady=10,
60
+ wrap='word'
61
+ )
62
+ self.text_fr.pack(fill='both', expand=True, side='top', padx=10, pady=10)
63
+
64
+ licence_text = (
65
+ "This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.\n\n"
66
+ "This is free software, and you are welcome to redistribute it under certain conditions; "
67
+ "type `show c' for details."
68
+ )
69
+ self.text_fr.insert(1.0, licence_text)
70
+ self.text_fr['state'] = 'disabled'
71
+
72
+ # Styled copyright indicator string label
73
+ lbl = Label(self.top, background="#2d2d2b", text=f"© 2021 - {time.strftime('%Y')} by Mapenzi Mudimba", font=('Arial', 9))
74
+ lbl.pack(side='bottom', pady=(0, 10))
75
+
76
+
77
+ class Copyright(Frame):
78
+ def __init__(self, master=None):
79
+ super().__init__(master)
80
+
81
+ self.top = tk.Toplevel(master, relief='flat')
82
+ self.top.geometry("360x280")
83
+ self.top.resizable(0,0)
84
+ self.top.title('Copyright Metadata')
85
+ self.top.attributes('-topmost', True)
86
+ self.top.configure(bg="#1e1e1e")
87
+
88
+ # The icon
89
+ try:
90
+ self.top.wm_iconbitmap("cryp.ico")
91
+ except:
92
+ pass
93
+
94
+ # Container wrap panel
95
+ self.frame_one = Frame(self.top, relief='flat')
96
+ self.frame_one.pack(fill='both', expand=True, padx=10, pady=10)
97
+
98
+ self.text_f = tk.Text(
99
+ self.frame_one,
100
+ height=10,
101
+ relief='flat',
102
+ bg='#2d2d2d',
103
+ fg='#ffffff',
104
+ font=('Arial', 10),
105
+ padx=10,
106
+ pady=10,
107
+ wrap='word'
108
+ )
109
+ self.text_f.pack(fill='both', expand=True)
110
+
111
+ copyright_text = (
112
+ "This page is licensed under the Python Software Foundation License Version 2.\n\n"
113
+ "Examples, recipes, and other code in the documentation are additionally licensed "
114
+ "under the Zero Clause BSD License.\n\n"
115
+ "All rights reserved."
116
+ )
117
+ self.text_f.insert(1.0, copyright_text)
118
+ self.text_f['state'] = 'disabled'
119
+
120
+ lbl = Label(self.top, text=f"© 2021 - {time.strftime('%Y')} by Mapenzi Mudimba", font=('Arial', 9), background="#2d2d2b", foreground="#a89a76")
121
+ lbl.pack(side='bottom', pady=(0, 10))
@@ -0,0 +1,34 @@
1
+ import tkinter as tk
2
+
3
+ class TextLineNumbers(tk.Canvas):
4
+ def __init__(self, *args, **kwargs):
5
+ tk.Canvas.__init__(self, *args, **kwargs)
6
+ self.textwidget = None
7
+
8
+ def attach(self, text_widget):
9
+ self.textwidget = text_widget
10
+
11
+ def redraw(self, *args):
12
+ # 🛡️ Enhanced Safety check: Stop if canvas OR the attached text box is missing
13
+ if not self.winfo_exists() or self.textwidget is None or not self.textwidget.winfo_exists():
14
+ return
15
+
16
+ '''redraw line numbers'''
17
+ self.delete("all")
18
+
19
+ try:
20
+ i = self.textwidget.index("@0,0")
21
+ while True :
22
+ dline = self.textwidget.dlineinfo(i)
23
+ if dline is None:
24
+ break
25
+ y = dline[1]
26
+ linenum = str(i).split(".")[0]
27
+ self.create_text(2, y, anchor="nw", text=linenum, fill='#ff1')
28
+ i = self.textwidget.index("%s+1line" % i)
29
+ except tk.TclError:
30
+ # Catch-all container in case a redraw fires mid-destruction
31
+ return
32
+
33
+ # Refreshes the canvas widget 30fps
34
+ self.after(30, self.redraw)