Skip to content

Commit ddd2590

Browse files
committed
kPad 1.4.0 alpha1
1 parent a418db9 commit ddd2590

File tree

4 files changed

+139
-55
lines changed

4 files changed

+139
-55
lines changed

.DS_Store

0 Bytes
Binary file not shown.

.github/workflows/build.yml

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -25,12 +25,12 @@ jobs:
2525
2626
- name: Build job ARM64
2727
run: |
28-
python -m nuitka --standalone --onefile --macos-create-app-bundle --macos-app-name='kPad ARM64' --macos-app-version='1.4.0' --enable-plugin=tk-inter --assume-yes-for-downloads main.py --macos-app-icon=assets/logo.icns
28+
python -m nuitka --standalone --onefile --macos-create-app-bundle --macos-app-name='kPad ARM64' --macos-app-version='1.4.a1' --enable-plugin=tk-inter --assume-yes-for-downloads main.py --macos-app-icon=assets/logo.icns
2929
mv main.app kPad-mac_arm64.app
3030
3131
- name: Build job Intel
3232
run: |
33-
arch -x86_64 python -m nuitka --standalone --onefile --macos-create-app-bundle --macos-app-name='kPad Intel' --macos-app-version='1.4.0' --enable-plugin=tk-inter --assume-yes-for-downloads main.py --macos-app-icon=assets/logo.icns
33+
arch -x86_64 python -m nuitka --standalone --onefile --macos-create-app-bundle --macos-app-name='kPad Intel' --macos-app-version='1.4.a1' --enable-plugin=tk-inter --assume-yes-for-downloads main.py --macos-app-icon=assets/logo.icns
3434
mv main.app kPad-mac_x86_64.app
3535
3636
- name: Create Info.plist for ARM64
@@ -47,7 +47,7 @@ jobs:
4747
<key>CFBundleIdentifier</key>
4848
<string>com.maxhatei2.kPad</string>
4949
<key>CFBundleVersion</key>
50-
<string>1.4.0</string>
50+
<string>1.4.a1</string>
5151
<key>CFBundleExecutable</key>
5252
<string>main</string>
5353
<key>CFBundlePackageType</key>
@@ -81,7 +81,7 @@ jobs:
8181
<key>CFBundleIdentifier</key>
8282
<string>com.maxhatei2.kPad</string>
8383
<key>CFBundleVersion</key>
84-
<string>1.4.0</string>
84+
<string>1.4.a1</string>
8585
<key>CFBundleExecutable</key>
8686
<string>main</string>
8787
<key>CFBundlePackageType</key>

.kv

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
1.4.0
1+
1.3.0.1

main.py

Lines changed: 134 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -188,27 +188,21 @@ class App(ctk.CTk):
188188
def __init__(self, title, geometry):
189189
super().__init__()
190190

191-
self.appcmds = {
192-
'Save': save_file,
193-
'Open from file': open_from_file,
194-
'Save as': save_as,
195-
'New file': newfile,
196-
'Open Plugin Folder': open_plugin_folder,
197-
'Go To Line': go_to_line,
198-
'Toggle Theme': toggle_theme,
199-
'Open Plugin Finder': lambda: PluginsInfo(self.plugins_list),
200-
'Increment font size': increment_font_size,
201-
'Decrement font size': decrement_font_size,
202-
'Toggle word wrap': toggle_word_wrap,
203-
'Save configuration': save_config,
204-
'Go to start': go_to_start,
205-
'Go to end': go_to_end
206-
}
191+
self._Textboxes = []
192+
self._TabPaths = []
193+
self.tab_names = ['Untitled']
207194

208-
self.textbox = ctk.CTkTextbox(self, undo=CONFIGURATION['undo']['enabled'],
195+
model_textbox = ctk.CTkTextbox(self, undo=CONFIGURATION['undo']['enabled'],
209196
autoseparators=CONFIGURATION['undo']['separate_edits_from_undos'],
210197
maxundo=CONFIGURATION['undo']['max_undo'])
211-
self.textbox.pack(fill='both', expand=True)
198+
199+
self.textbox = model_textbox
200+
201+
self._Textboxes.append(model_textbox)
202+
self._TabPaths.append(None)
203+
204+
self.tabs = ctk.CTkSegmentedButton(self, values=['Untitled'])
205+
self.tabs.pack(fill='x', side=ctk.TOP)
212206

213207
self.after(1, lambda: threading.Thread(target=AutoUpdate, args=(self,), daemon=True).start())
214208

@@ -246,42 +240,105 @@ def __load_plugins():
246240
editor_api = PluginAPI(self.textbox, self)
247241
self.plugins_list = PLUGINS_LIST
248242

249-
self.path = None
243+
self._TabPaths = [None]
250244
self.font_size = 14
251245
self.modified = False
246+
index = self.tabs.index(self.tab_names[0])
252247

253248
def write_to_recent_files():
254249
if CONFIGURATION['recent_files']['enabled']:
255250
if len(CONFIGURATION['recent_files']['recent_file_paths']) >= CONFIGURATION['recent_files']['keep_recent_files_count']:
256251
del CONFIGURATION['recent_files']['recent_file_paths'][0]
257-
CONFIGURATION['recent_files']['recent_file_paths'].append(self.path)
252+
CONFIGURATION['recent_files']['recent_file_paths'].append(self._TabPaths[index])
258253
else:
259-
CONFIGURATION['recent_files']['recent_file_paths'].append(self.path)
254+
CONFIGURATION['recent_files']['recent_file_paths'].append(self._TabPaths[index])
255+
256+
def newtab(event=None, file_path=None):
257+
new_textbox = ctk.CTkTextbox(
258+
self,
259+
undo=CONFIGURATION['undo']['enabled'],
260+
autoseparators=CONFIGURATION['undo']['separate_edits_from_undos'],
261+
maxundo=CONFIGURATION['undo']['max_undo']
262+
)
263+
for tb in self._Textboxes:
264+
tb.pack_forget()
265+
self._Textboxes.append(new_textbox)
266+
self._TabPaths.append(file_path or None)
267+
if file_path and os.path.exists(file_path):
268+
with open(file_path, 'r', encoding='utf-8') as f:
269+
new_textbox.insert('1.0', f.read())
270+
tab_name = file_path or f'Untitled {len(self._Textboxes)}'
271+
self.tab_names.append(tab_name)
272+
self.tabs.configure(values=self.tab_names)
273+
self.tabs.set(tab_name)
274+
new_textbox.pack(fill='both', expand=True)
275+
self.textbox = new_textbox
276+
277+
def on_tab_selected(value):
278+
nonlocal index
279+
for tb in self._Textboxes:
280+
tb.pack_forget()
281+
index = self.tab_names.index(value)
282+
self._Textboxes[index].pack(fill='both', expand=True)
283+
self._TabPaths[index] = self._TabPaths[index]
284+
self.textbox = self._Textboxes[index]
285+
286+
def delete_tab(index):
287+
tb = self._Textboxes.pop(index)
288+
tb.pack_forget()
289+
self._TabPaths.pop(index)
290+
self.tab_names.pop(index)
291+
new_index = max(0, index - 1)
292+
if not self._Textboxes:
293+
new_textbox = ctk.CTkTextbox(self,
294+
undo=CONFIGURATION['undo']['enabled'],
295+
autoseparators=CONFIGURATION['undo']['separate_edits_from_undos'],
296+
maxundo=CONFIGURATION['undo']['max_undo'])
297+
self._Textboxes.append(new_textbox)
298+
self._TabPaths.append(None)
299+
self.tab_names.append("Untitled")
300+
new_textbox.pack(fill='both', expand=True)
301+
self.tabs.configure(values=self.tab_names)
302+
self.tabs.set("Untitled")
303+
self.textbox = new_textbox
304+
self.textbox.focus()
305+
return
306+
self.tabs.set(self.tab_names[new_index])
307+
on_tab_selected(self.tab_names[new_index])
308+
self.tabs.configure(values=self.tab_names)
309+
310+
def __get_values_and_delete(event=None):
311+
value = self.tabs.get()
312+
index = self.tab_names.index(value)
313+
delete_tab(index)
314+
315+
316+
self.tabs.configure(command=on_tab_selected)
260317

261318

262319
def newfile(event=None):
263320
if not '*' in self.title()[7]:
264321
self.title('kPad - Untitled')
265-
self.path = None
322+
self._TabPaths[index] = None
266323
self.textbox.delete('1.0', 'end')
267324
else:
268325
ask = askyesno('File unsaved!', 'Do you want to save your file before making a new one?')
269326
if ask:
270327
save_file()
271328
else:
272329
self.title('kPad - Untitled')
273-
self.path = None
330+
self._TabPaths[index] = None
274331
self.textbox.delete('1.0', 'end')
275332

276333

277334
def save_as(event=None):
278-
self.path = asksaveasfilename(filetypes=[('Text files', '.txt'), ('Other', '.*.*')], defaultextension='.txt')
279-
if not self.path:
335+
self._TabPaths[index] = asksaveasfilename(filetypes=[('Text files', '.txt'), ('Other', '.*.*')], defaultextension='.txt')
336+
if not self._TabPaths[index]:
280337
return
281338
try:
282-
with open(self.path, 'w', encoding='utf-8') as file:
339+
with open(self._TabPaths[index], 'w', encoding='utf-8') as file:
283340
file.write(self.textbox.get('1.0', 'end-1c'))
284-
self.title(f'kPad - {self.path}')
341+
self.title(f'kPad - {self._TabPaths[index]}')
285342
self.modified = False
286343
if self.title().endswith('*'):
287344
self.title(self.title()[:-1])
@@ -298,10 +355,10 @@ def open_plugin_folder():
298355
subprocess.call(['xdg-open', plugin_dir])
299356

300357
def save_file(event=None):
301-
if not self.path:
358+
if not self._TabPaths[index]:
302359
save_as()
303360
return
304-
with open(self.path, 'w', encoding='utf-8') as f:
361+
with open(self._TabPaths[index], 'w', encoding='utf-8') as f:
305362
f.write(self.textbox.get('1.0', 'end-1c'))
306363
self.modified = False
307364
if self.title().endswith('*'):
@@ -310,10 +367,10 @@ def save_file(event=None):
310367

311368
def open_from_file(event=None, path=None):
312369
if not path:
313-
self.path = askopenfilename(filetypes=[('Text files', '.txt'), ('All files', '*.*')], defaultextension='.txt')
314-
if self.path:
370+
self._TabPaths[index] = askopenfilename(filetypes=[('Text files', '.txt'), ('All files', '*.*')], defaultextension='.txt')
371+
if self._TabPaths[index]:
315372
self.textbox.delete('1.0', 'end')
316-
with open(self.path, 'r') as file:
373+
with open(self._TabPaths[index], 'r') as file:
317374
self.textbox.insert('1.0', file.read())
318375
write_to_recent_files()
319376
print(CONFIGURATION['recent_files']['recent_file_paths'])
@@ -328,7 +385,13 @@ def set_font(font):
328385
def autosave():
329386
while True:
330387
if CONFIGURATION['auto_save']['enabled']:
331-
if self.path and 'Untitled' not in self.title():
388+
current_tab_name = self.tabs.get()
389+
try:
390+
current_index = self.tab_names.index(current_tab_name)
391+
except ValueError:
392+
time.sleep(CONFIGURATION['auto_save']['time_until_next_save'])
393+
continue
394+
if self._TabPaths[current_index] and 'Untitled' not in self.title():
332395
save_file()
333396
time.sleep(CONFIGURATION['auto_save']['time_until_next_save'])
334397

@@ -426,10 +489,6 @@ def handle_brackets(event):
426489
menu.add_cascade(label='View', menu=view_menu)
427490
plugins_menu = Menu(menu, tearoff=0)
428491
menu.add_cascade(label='Plugins', menu=plugins_menu)
429-
for plugin in self.plugins_list:
430-
meta = plugin['meta']
431-
name = meta.get('name') if meta else plugin['module'].__name__
432-
plugins_menu.add_command(label=name, command=lambda p=plugin: p['module'].action(editor_api))
433492
plugins_menu.add_separator()
434493
plugins_menu.add_command(label='Show Plugin Information...', command=lambda: PluginsInfo(self.plugins_list))
435494

@@ -466,10 +525,11 @@ def update_cursor_info(event=None):
466525
self.bind(f'<{mod}-s>', save_file)
467526
self.bind(f'<{mod}-o>', open_from_file)
468527
self.bind(f'<{mod}-t>', toggle_theme)
469-
self.bind(f'<{mod}-n>', newfile)
528+
self.bind(f'<{mod}-n>', lambda event=None: newtab())
470529
self.bind(f'<{mod}-equal>', increment_font_size)
471530
self.bind(f'<{mod}-minus>', decrement_font_size)
472531
self.bind(f'<{mod}-k>', lambda event=None: FastCommand(self))
532+
self.bind(f'<{mod}-e>', __get_values_and_delete)
473533

474534
self.textbox.bind('<Key>', handle_brackets)
475535

@@ -494,7 +554,7 @@ def _on_quit_():
494554
if self.modified:
495555
result = askyesnocancel('Quit', 'Save changes before quitting?')
496556
if result is True:
497-
if self.path:
557+
if self._TabPaths[index]:
498558
save_file()
499559
else:
500560
save_as()
@@ -503,6 +563,27 @@ def _on_quit_():
503563
save_config()
504564
self.destroy()
505565

566+
self.textbox.pack(fill='both', expand=True)
567+
self.protocol('WM_DELETE_WINDOW', _on_quit_)
568+
569+
# Define appcmds BEFORE plugin auto-start so plugins can use it
570+
self.appcmds = {
571+
'Save': save_file,
572+
'Open from file': open_from_file,
573+
'Save as': save_as,
574+
'New file': newfile,
575+
'Open Plugin Folder': open_plugin_folder,
576+
'Go To Line': go_to_line,
577+
'Toggle Theme': toggle_theme,
578+
'Open Plugin Finder': lambda: PluginsInfo(self.plugins_list),
579+
'Increment font size': increment_font_size,
580+
'Decrement font size': decrement_font_size,
581+
'Toggle word wrap': toggle_word_wrap,
582+
'Save configuration': save_config,
583+
'Go to start': go_to_start,
584+
'Go to end': go_to_end
585+
}
586+
506587
for plugin in self.plugins_list:
507588
name = plugin.get('meta', {}).get('name', plugin['module'].__name__)
508589
if name in CONFIGURATION.get('auto_start_plugins', []):
@@ -511,9 +592,6 @@ def _on_quit_():
511592
except Exception as e:
512593
print(f"[PLUGIN ERROR] Auto-start failed for {name}: {e}")
513594

514-
self.textbox.pack(fill='both', expand=True)
515-
self.protocol('WM_DELETE_WINDOW', _on_quit_)
516-
517595
self.save_file = save_file
518596
self.open_from_file = open_from_file
519597
self.save_as = save_as
@@ -528,6 +606,12 @@ def _on_quit_():
528606
self.gostart = go_to_start
529607
self.goend = go_to_end
530608

609+
610+
for plugin in self.plugins_list:
611+
meta = plugin['meta']
612+
name = meta.get('name') if meta else plugin['module'].__name__
613+
plugins_menu.add_command(label=name, command=lambda p=plugin: p['module'].action(editor_api))
614+
531615
class PluginsInfo(ctk.CTkToplevel):
532616
def __init__(self, plugins_list):
533617
super().__init__()
@@ -681,7 +765,7 @@ def __init__(self, parent):
681765
)
682766
results_box.pack(fill='both', side=ctk.BOTTOM)
683767

684-
COMMANDS = app.appcmds
768+
COMMANDS = parent.appcmds
685769

686770
def exit_window(event=None):
687771
parent.textbox.focus()
@@ -697,17 +781,17 @@ def run_command(event=None):
697781

698782
def filter_(event=None):
699783
query = command_entry.get().lower()
700-
if not query == '' or query.split(' ') == '':
701-
results_box.delete(0, "end")
784+
results_box.delete(0, "end")
785+
COMMANDS = parent.appcmds
786+
if query.strip() != "":
702787
for name in COMMANDS:
703788
if query in name.lower():
704789
results_box.insert("end", name)
705-
if results_box.size() > 0:
706-
results_box.selection_set(0)
707790
else:
708-
results_box.delete(0, 'end')
709-
for command in COMMANDS:
710-
results_box.insert('end', command)
791+
for name in COMMANDS:
792+
results_box.insert("end", name)
793+
if results_box.size() > 0:
794+
results_box.selection_set(0)
711795

712796
def run_filtering(event=None):
713797
threading.Thread(target=filter_, daemon=True).start()

0 commit comments

Comments
 (0)