Skip to content

Commit c6f676d

Browse files
committed
kPad 1.1.0 "Memorian" RC1
1 parent 3adcae7 commit c6f676d

File tree

2 files changed

+122
-53
lines changed

2 files changed

+122
-53
lines changed

.github/workflows/build.yml

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -25,20 +25,20 @@ 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.0.2" --enable-plugin=tk-inter --assume-yes-for-downloads main.py
28+
python -m nuitka --standalone --onefile --macos-create-app-bundle --macos-app-name='kPad ARM64' --macos-app-version='1.1.0' --enable-plugin=tk-inter --assume-yes-for-downloads main.py
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.0.2" --enable-plugin=tk-inter --assume-yes-for-downloads main.py
33+
arch -x86_64 python -m nuitka --standalone --onefile --macos-create-app-bundle --macos-app-name='kPad Intel' --macos-app-version='1.1.0' --enable-plugin=tk-inter --assume-yes-for-downloads main.py
3434
mv main.app kPad-mac_x86_64.app
3535
3636
- name: Create Info.plist for ARM64
3737
run: |
3838
cat > kPad-mac_arm64.app/Contents/Info.plist << EOF
39-
<?xml version="1.0" encoding="UTF-8"?>
40-
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
41-
<plist version="1.0">
39+
<?xml version='1.0' encoding='UTF-8'?>
40+
<!DOCTYPE plist PUBLIC '-//Apple//DTD PLIST 1.0//EN' 'http://www.apple.com/DTDs/PropertyList-1.0.dtd'>
41+
<plist version='1.0'>
4242
<dict>
4343
<key>CFBundleName</key>
4444
<string>kPad</string>
@@ -47,7 +47,7 @@ jobs:
4747
<key>CFBundleIdentifier</key>
4848
<string>com.maxhatei2.kPad</string>
4949
<key>CFBundleVersion</key>
50-
<string>1.0.2</string>
50+
<string>1.1.0</string>
5151
<key>CFBundleExecutable</key>
5252
<string>main</string>
5353
<key>CFBundlePackageType</key>
@@ -59,9 +59,9 @@ jobs:
5959
- name: Create Info.plist for Intel
6060
run: |
6161
cat > kPad-mac_x86_64.app/Contents/Info.plist << EOF
62-
<?xml version="1.0" encoding="UTF-8"?>
63-
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
64-
<plist version="1.0">
62+
<?xml version='1.0' encoding='UTF-8'?>
63+
<!DOCTYPE plist PUBLIC '-//Apple//DTD PLIST 1.0//EN' 'http://www.apple.com/DTDs/PropertyList-1.0.dtd'>
64+
<plist version='1.0'>
6565
<dict>
6666
<key>CFBundleName</key>
6767
<string>kPad Intel</string>
@@ -70,7 +70,7 @@ jobs:
7070
<key>CFBundleIdentifier</key>
7171
<string>com.maxhatei2.kPad</string>
7272
<key>CFBundleVersion</key>
73-
<string>1.0.2</string>
73+
<string>1.1.0</string>
7474
<key>CFBundleExecutable</key>
7575
<string>main</string>
7676
<key>CFBundlePackageType</key>

main.py

Lines changed: 112 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -4,18 +4,38 @@
44
from tkinter.messagebox import showinfo, askyesno
55
from tkinter import simpledialog
66
import time, threading
7-
8-
CONFIGURATION = {
9-
'auto_save': {
10-
'enabled': True,
11-
'time_until_next_save': 5, # seconds
12-
},
13-
'undo': {
14-
'enabled': True,
15-
'max_undo': 20, # use -1 for infinite
16-
'separate_edits_from_undos': True
17-
}
18-
}
7+
import os, json, platform
8+
9+
10+
# ██╗░░██╗██████╗░░█████╗░██████╗░
11+
# ██║░██╔╝██╔══██╗██╔══██╗██╔══██╗
12+
# █████═╝░██████╔╝███████║██║░░██║
13+
# ██╔═██╗░██╔═══╝░██╔══██║██║░░██║
14+
# ██║░╚██╗██║░░░░░██║░░██║██████╔╝
15+
# ╚═╝░░╚═╝╚═╝░░░░░╚═╝░░╚═╝╚═════╝░
16+
# Version 1.1.0 [SOURCE CODE]
17+
18+
if platform.system() == 'Darwin':
19+
config_dir = os.path.expanduser('~/Library/Application Support/kPad')
20+
elif platform.system() == 'Windows':
21+
config_dir = os.path.join(os.getenv('APPDATA'), 'kPad')
22+
else:
23+
config_dir = os.path.expanduser('~/.config/kpad')
24+
os.makedirs(config_dir, exist_ok=True)
25+
CONFIG_PATH = os.path.join(config_dir, 'config.json')
26+
if os.path.exists(CONFIG_PATH):
27+
with open(CONFIG_PATH, 'r') as f:
28+
CONFIGURATION = json.load(f)
29+
else:
30+
CONFIGURATION = {
31+
'auto_save': {'enabled': True, 'time_until_next_save': 5},
32+
'undo': {'enabled': True, 'max_undo': 20, 'separate_edits_from_undos': True},
33+
'word_wrap': True,
34+
'font': 'Menlo',
35+
'font_size': 14,
36+
'window_geometry': [500, 400],
37+
'recent_files': {'enabled': True, 'keep_recent_files_count': 5, 'recent_file_paths': []}
38+
}
1939

2040
_fonts = ['Menlo', 'Monaco', 'Helvetica', 'Arial', 'Times New Roman', 'Georgia', 'Avenir', 'Baskerville', 'Futura', 'Verdana', 'Gill Sans', 'Courier', 'Optima', 'American Typewriter']
2141

@@ -26,6 +46,15 @@ def __init__(self, title, geometry):
2646
self.path = None
2747
self.font_size = 14
2848

49+
def write_to_recent_files():
50+
if CONFIGURATION['recent_files']['enabled']:
51+
if len(CONFIGURATION['recent_files']['recent_file_paths']) >= CONFIGURATION['recent_files']['keep_recent_files_count']:
52+
del CONFIGURATION['recent_files']['recent_file_paths'][0]
53+
CONFIGURATION['recent_files']['recent_file_paths'].append(self.path)
54+
else:
55+
CONFIGURATION['recent_files']['recent_file_paths'].append(self.path)
56+
57+
2958
def newfile(event=None):
3059
if not '*' in self.title()[7]:
3160
self.title('kPad - Untitled')
@@ -55,12 +84,15 @@ def save_file(event=None):
5584
self.title(f'kPad - {self.path}')
5685

5786

58-
def open_from_file(event=None):
59-
self.path = askopenfilename(filetypes=[('Text files', '.txt'), ('kPad notefile', '.kpad')], defaultextension='.txt')
87+
def open_from_file(event=None, path=None):
88+
if not path:
89+
self.path = askopenfilename(filetypes=[('Text files', '.txt'), ('kPad notefile', '.kpad')], defaultextension='.txt')
6090
if self.path:
6191
self.textbox.delete('1.0', 'end')
62-
with open(self.path, "r") as file:
92+
with open(self.path, 'r') as file:
6393
self.textbox.insert('1.0', file.read())
94+
write_to_recent_files()
95+
print(CONFIGURATION['recent_files']['recent_file_paths'])
6496
else:
6597
pass
6698

@@ -79,7 +111,7 @@ def autosave():
79111

80112
def toggle_theme(event=None):
81113
mode = ctk.get_appearance_mode()
82-
ctk.set_appearance_mode("Light" if mode == "Dark" else "Dark")
114+
ctk.set_appearance_mode('Light' if mode == 'Dark' else 'Dark')
83115

84116
def go_to_start(event=None):
85117
self.textbox.yview_moveto(0)
@@ -91,34 +123,55 @@ def increment_font_size(event=None):
91123
self.font_size += 2
92124
self.font = ctk.CTkFont(family=self.font._family, size=self.font_size)
93125
self.textbox.configure(font=self.font)
94-
return "break"
126+
return 'break'
95127

96128
def decrement_font_size(event=None):
97129
self.font_size = max(2, self.font_size - 2)
98130
self.font = ctk.CTkFont(family=self.font._family, size=self.font_size)
99131
self.textbox.configure(font=self.font)
100-
return "break"
132+
return 'break'
101133

102134
def go_to_line(event=None):
103-
line = simpledialog.askinteger("Go To Line", "Enter line number:")
135+
line = simpledialog.askinteger('Go To Line', 'Enter line number:')
104136
if line is None:
105137
return
106-
total_lines = int(self.textbox.index("end-1c").split(".")[0])
138+
total_lines = int(self.textbox.index('end-1c').split('.')[0])
107139
line = max(1, min(line, total_lines))
108-
line_text = self.textbox.get(f"{line}.0", f"{line}.end")
140+
line_text = self.textbox.get(f'{line}.0', f'{line}.end')
109141
max_col = len(line_text)
110-
self.textbox.mark_set("insert", f"{line}.0")
111-
self.textbox.see("insert")
142+
self.textbox.mark_set('insert', f'{line}.0')
143+
self.textbox.see('insert')
112144
self.textbox.focus()
145+
146+
word_wrap_var = ctk.BooleanVar(value=CONFIGURATION['word_wrap'])
147+
148+
def toggle_word_wrap():
149+
if word_wrap_var.get():
150+
self.textbox.configure(wrap='word')
151+
CONFIGURATION['word_wrap'] = True
152+
else:
153+
self.textbox.configure(wrap='none')
154+
CONFIGURATION['word_wrap'] = False
155+
156+
def save_config():
157+
CONFIGURATION['font'] = self.font._family
158+
CONFIGURATION['font_size'] = self.font_size
159+
CONFIGURATION['word_wrap'] = word_wrap_var.get()
160+
CONFIGURATION['window_geometry'] = [self.winfo_width(), self.winfo_height()]
161+
with open(CONFIG_PATH, 'w') as f:
162+
json.dump(CONFIGURATION, f, indent=4)
113163

114164
menu = Menu(self)
115165
file_menu = Menu(menu, tearoff=0)
116-
file_menu.add_command(label="Open", command=open_from_file)
117-
file_menu.add_command(label="Save As...", command=save_as)
166+
file_menu.add_command(label='Open', command=open_from_file)
167+
file_menu.add_command(label='Save As...', command=save_as)
118168
file_menu.add_command(label='Save...', command=save_file)
119169
file_menu.add_separator()
120-
file_menu.add_command(label="Exit", command=self.destroy)
121-
menu.add_cascade(label="File", menu=file_menu)
170+
for index, path in CONFIGURATION['recent_files']['recent_files_paths']:
171+
file_menu.add_command(label=f'Open {path} ({index+1})...')
172+
file_menu.add_separator()
173+
file_menu.add_command(label='Exit', command=self.destroy)
174+
menu.add_cascade(label='File', menu=file_menu)
122175
font_menu = Menu(menu, tearoff=0)
123176
for _font in _fonts:
124177
font_menu.add_command(label=_font, command=lambda f=_font: set_font(f))
@@ -128,17 +181,20 @@ def go_to_line(event=None):
128181
view_menu.add_command(label='Go to End...', command=go_to_end)
129182
view_menu.add_separator()
130183
view_menu.add_command(label='Go to Line...', command=go_to_line)
184+
view_menu.add_separator()
185+
view_menu.add_checkbutton(label='Word Wrap...', onvalue=True, variable=word_wrap_var, command=toggle_word_wrap)
131186
menu.add_cascade(label='View', menu=view_menu)
132187

133188
self.configure(menu=menu)
134189

135-
self.font = ctk.CTkFont(family=_fonts[0], size=self.font_size)
190+
self.font = ctk.CTkFont(family=CONFIGURATION['font'], size=CONFIGURATION['font_size'])
191+
self.font_size = CONFIGURATION['font_size']
136192

137193
def update_cursor_info(event=None):
138-
pos = self.textbox.index("insert")
139-
line, col = map(int, pos.split("."))
140-
chars = len(self.textbox.get("1.0", "end-1c"))
141-
self.stats_line_col.configure(text=f"Ln: {line} Col: {col + 1} Ch: {chars}")
194+
pos = self.textbox.index('insert')
195+
line, col = map(int, pos.split('.'))
196+
chars = len(self.textbox.get('1.0', 'end-1c'))
197+
self.stats_line_col.configure(text=f'Ln: {line} Col: {col + 1} Ch: {chars}')
142198
if self.path != None:
143199
self.title(f'kPad - *{self.path}')
144200
else:
@@ -149,16 +205,28 @@ def update_cursor_info(event=None):
149205

150206
self.textbox = ctk.CTkTextbox(self, undo=CONFIGURATION['undo']['enabled'], autoseparators=CONFIGURATION['undo']['separate_edits_from_undos'], maxundo=CONFIGURATION['undo']['max_undo'])
151207
self.textbox.configure(font=self.font)
152-
153-
self.textbox.bind("<KeyRelease>", update_cursor_info)
154-
self.textbox.bind("<ButtonRelease>", update_cursor_info)
155-
self.bind("<Control-l>", go_to_line)
156-
self.bind('<Command-s>', save_file)
157-
self.bind('<Command-o>', open_from_file)
158-
self.bind('<Command-t>', toggle_theme)
159-
self.bind('<Command-n>', newfile)
160-
self.bind('<Command-plus>', increment_font_size)
161-
self.bind('<Command-minus>', decrement_font_size)
208+
if word_wrap_var.get():
209+
self.textbox.configure(wrap='word')
210+
else:
211+
self.textbox.configure(wrap='none')
212+
213+
self.textbox.bind('<KeyRelease>', update_cursor_info)
214+
self.textbox.bind('<ButtonRelease>', update_cursor_info)
215+
216+
# Cross-platform key bindings
217+
system = platform.system()
218+
if system == "Darwin":
219+
mod = "Command"
220+
else:
221+
mod = "Control"
222+
223+
self.bind(f'<{mod}-l>', go_to_line)
224+
self.bind(f'<{mod}-s>', save_file)
225+
self.bind(f'<{mod}-o>', open_from_file)
226+
self.bind(f'<{mod}-t>', toggle_theme)
227+
self.bind(f'<{mod}-n>', newfile)
228+
self.bind(f'<{mod}-equal>', increment_font_size)
229+
self.bind(f'<{mod}-minus>', decrement_font_size)
162230

163231

164232
self.stats_text_frame = ctk.CTkFrame(self)
@@ -168,6 +236,7 @@ def update_cursor_info(event=None):
168236
self.stats_line_col.pack(side=ctk.RIGHT)
169237

170238
self.textbox.pack(fill='both', expand=True)
239+
self.protocol('WM_DELETE_WINDOW', lambda: [save_file(), save_config(), self.destroy()])
171240

172241

173-
App('kPad - Untitled', [500, 400]).mainloop()
242+
App('kPad - Untitled', CONFIGURATION['window_geometry']).mainloop()

0 commit comments

Comments
 (0)