-
Notifications
You must be signed in to change notification settings - Fork 1
/
linpad.py
executable file
·360 lines (305 loc) · 15.9 KB
/
linpad.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
#!/usr/bin/env python3
import sys
if sys.version_info[0] == 3:
import tkinter as tk
from tkinter import filedialog, messagebox, simpledialog, font
else:
import Tkinter as tk
import tkFileDialog as filedialog
import tkMessageBox as messagebox
import tkSimpleDialog as simpledialog
import tkFont as font
import keyword
import webbrowser
import os
class Linpad:
def __init__(self, root):
default_font = ("Arial", 12)
root.option_add("*Font", default_font)
self.root = root
self.root.title("Linpad")
self.root.geometry("800x600")
self.text_area = tk.Text(self.root, undo=True, wrap='word')
self.text_area.pack(fill=tk.BOTH, expand=1)
self.text_area.bind("<KeyRelease>", self.update_status_bar)
self.menu_bar = tk.Menu(self.root)
self.root.config(menu=self.menu_bar)
self.file_menu = tk.Menu(self.menu_bar, tearoff=0)
self.menu_bar.add_cascade(label="File", menu=self.file_menu)
self.file_menu.add_command(label="New", command=self.new_file)
self.file_menu.add_command(label="Open", command=self.open_file)
self.file_menu.add_command(label="Save", command=self.save_file)
self.file_menu.add_command(label="Save As", command=self.save_as_file)
self.file_menu.add_command(label="Print", command=self.print_file)
self.file_menu.add_separator()
self.file_menu.add_command(label="Exit", command=self.root.quit)
self.edit_menu = tk.Menu(self.menu_bar, tearoff=0)
self.menu_bar.add_cascade(label="Edit", menu=self.edit_menu)
self.edit_menu.add_command(label="Undo", command=self.undo)
self.edit_menu.add_command(label="Redo", command=self.redo)
self.edit_menu.add_separator()
self.edit_menu.add_command(label="Cut", command=lambda: self.text_area.event_generate("<<Cut>>"))
self.edit_menu.add_command(label="Copy", command=lambda: self.text_area.event_generate("<<Copy>>"))
self.edit_menu.add_command(label="Paste", command=lambda: self.text_area.event_generate("<<Paste>>"))
self.edit_menu.add_command(label="Delete", command=self.delete_selection)
self.edit_menu.add_command(label="Select All", command=lambda: self.text_area.tag_add("sel", "1.0", "end"))
self.edit_menu.add_separator()
self.edit_menu.add_command(label="Find", command=self.find_text)
self.edit_menu.add_command(label="Replace", command=self.replace_text)
self.edit_menu.add_command(label="Word Count", command=self.word_count)
self.format_menu = tk.Menu(self.menu_bar, tearoff=0)
self.menu_bar.add_cascade(label="Format", menu=self.format_menu)
self.format_menu.add_command(label="Font", command=self.choose_font)
self.format_menu.add_command(label="Toggle Dark Mode", command=self.toggle_dark_mode)
self.format_menu.add_command(label="Zoom In", command=self.zoom_in)
self.format_menu.add_command(label="Zoom Out", command=self.zoom_out)
# Add font selection submenu
self.font_menu = tk.Menu(self.format_menu, tearoff=0)
self.format_menu.add_cascade(label="Select Font", menu=self.font_menu)
self.help_menu = tk.Menu(self.menu_bar, tearoff=0)
self.menu_bar.add_cascade(label="Help", menu=self.help_menu)
self.help_menu.add_command(label="About", command=self.show_about)
self.help_menu.add_command(label="Documentation", command=self.show_documentation)
# Add Shortcuts/Command submenu
self.shortcuts_menu = tk.Menu(self.help_menu, tearoff=0)
self.help_menu.add_command(label="Shortcuts/Command", command=self.show_shortcuts)
# self.shortcuts_menu.add_command(label="Show Shortcuts", command=self.show_shortcuts)
self.status_bar = tk.Frame(self.root)
self.status_bar.pack(side=tk.BOTTOM, fill=tk.X)
self.status_label = tk.Label(self.status_bar, text="Line 1, Column 1", anchor='w')
self.status_label.pack(side=tk.LEFT, fill=tk.X, expand=True)
self.autosave_label = tk.Label(self.status_bar, text="", anchor='e')
self.autosave_label.pack(side=tk.RIGHT)
self.default_font = font.Font(family="Arial", size=12)
self.text_area.config(font=self.default_font)
self.current_file_path = None
self.autosave_interval = 30000 # Autosave every 30 seconds
self.bind_shortcuts()
self.schedule_autosave()
# List of all available fonts
self.fonts = font.families()
self.current_font_index = 0
# Populate font menu
for font_name in self.fonts:
self.font_menu.add_command(label=font_name, command=lambda f=font_name: self.set_font(f))
def set_font(self, font_name):
new_font = font.Font(family=font_name, size=12)
self.text_area.config(font=new_font)
def next_font(self):
self.current_font_index = (self.current_font_index + 1) % len(self.fonts)
self.set_font(self.fonts[self.current_font_index])
self.update_status_bar_with_font(self.fonts[self.current_font_index])
def previous_font(self):
self.current_font_index = (self.current_font_index - 1) % len(self.fonts)
self.set_font(self.fonts[self.current_font_index])
self.update_status_bar_with_font(self.fonts[self.current_font_index])
def update_status_bar(self, _=None):
row, col = self.text_area.index(tk.INSERT).split('.')
self.status_label.config(text=f"Line {int(row)}, Column {int(col) + 1}")
def update_status_bar_with_font(self, font_name):
self.autosave_label.config(text=f"Font: {font_name}")
self.highlight_syntax()
def new_file(self):
self.text_area.delete(1.0, tk.END)
self.current_file_path = None
self.update_status_bar()
def open_file(self):
file_path = filedialog.askopenfilename()
if file_path:
with open(file_path, "r") as file:
self.text_area.delete(1.0, tk.END)
self.text_area.insert(tk.END, file.read())
self.current_file_path = file_path
self.update_status_bar()
def save_file(self):
if self.current_file_path:
with open(self.current_file_path, "w") as file:
file.write(self.text_area.get(1.0, tk.END))
else:
self.save_as_file()
def save_as_file(self):
file_path = filedialog.asksaveasfilename(defaultextension=".txt",
filetypes=[("Text files", "*.txt"),
("All files", "*.*")])
if file_path:
with open(file_path, "w") as file:
file.write(self.text_area.get(1.0, tk.END))
self.current_file_path = file_path
def print_file(self):
file_path = filedialog.asksaveasfilename(defaultextension=".ps",
filetypes=[("PostScript files", "*.ps")])
if file_path:
self.text_area.postscript(file=file_path)
messagebox.showinfo("Print", "File saved for printing.")
def choose_font(self):
font_name = simpledialog.askstring("Font", "Enter font family (e.g., Arial):")
font_size = simpledialog.askinteger("Font Size", "Enter font size:")
if font_name and font_size:
new_font = font.Font(family=font_name, size=font_size)
self.text_area.config(font=new_font)
def find_text(self):
find_string = simpledialog.askstring("Find", "Enter text to find:")
self.text_area.tag_remove('found', '1.0', tk.END)
if find_string:
idx = '1.0'
while True:
idx = self.text_area.search(find_string, idx, nocase=1, stopindex=tk.END)
if not idx:
break
lastidx = f'{idx}+{len(find_string)}c'
self.text_area.tag_add('found', idx, lastidx)
idx = lastidx
self.text_area.tag_config('found', foreground='white', background='blue')
def replace_text(self):
find_string = simpledialog.askstring("Find", "Enter text to find:")
replace_string = simpledialog.askstring("Replace", "Enter text to replace with:")
if find_string and replace_string:
content = self.text_area.get("1.0", tk.END)
new_content = content.replace(find_string, replace_string)
self.text_area.delete("1.0", tk.END)
self.text_area.insert(tk.END, new_content)
def word_count(self):
content = self.text_area.get("1.0", tk.END)
word_count = len(content.split())
char_count = len(content)
messagebox.showinfo("Word Count", f"Words: {word_count}\nCharacters: {char_count}")
def highlight_syntax(self, event=None):
content = self.text_area.get('1.0', tk.END).split()
for word in content:
if word in keyword.kwlist:
idx = self.text_area.search(word, '1.0', stopindex=tk.END)
while idx:
end_idx = f"{idx}+{len(word)}c"
self.text_area.tag_add('keyword', idx, end_idx)
idx = self.text_area.search(word, end_idx, stopindex=tk.END)
self.text_area.tag_config('keyword', foreground='blue')
def toggle_dark_mode(self):
current_bg = self.text_area.cget("background")
if current_bg == "black":
self.text_area.config(background="white", foreground="black")
self.status_bar.config(background="white", foreground="black")
else:
self.text_area.config(background="black", foreground="white")
self.status_bar.config(background="black", foreground="white")
def zoom_in(self):
current_font = font.nametofont(self.text_area.cget("font")) # Get the current font as a Font object
new_size = current_font.actual("size") + 2 # Increase font size by 2
current_font.config(size=new_size) # Apply the new font size
self.text_area.config(font=current_font) # Update the text area font
def zoom_out(self):
current_font = font.nametofont(self.text_area.cget("font")) # Get the current font as a Font object
new_size = current_font.actual("size") - 2 # Decrease font size by 2
if new_size > 0: # Avoid setting font size to 0 or negative
current_font.config(size=new_size) # Apply the new font size
self.text_area.config(font=current_font) # Update the text area font
def toggle_underline(self):
try:
current_tags = self.text_area.tag_names(tk.SEL_FIRST)
if "underline" in current_tags:
self.text_area.tag_remove("underline", tk.SEL_FIRST, tk.SEL_LAST)
else:
underline_font = font.Font(self.text_area, self.text_area.cget("font"))
underline_font.configure(underline=True)
self.text_area.tag_configure("underline", font=underline_font)
self.text_area.tag_add("underline", tk.SEL_FIRST, tk.SEL_LAST)
except tk.TclError:
pass
def show_about(self):
messagebox.showinfo(
"About",
"Linpad Version 1.0\n\n"
"Linpad is a simple text editor with syntax highlighting and word count functionality. "
"I am going to make this open-source via GitHub, so everyone can collaborate to enhance and expand its features. "
"Let me know if you have some good ideas to make it better. You will find me here: [email protected]"
)
def show_documentation(self):
try:
webbrowser.open_new_tab("https://github.com/Maijied/linpad/blob/main/DOCUMENTATION.md")
except webbrowser.Error:
messagebox.showinfo("Documentation", "Please connect to the internet to view the documentation.")
def show_shortcuts(self):
shortcuts = (
"File Operations:\n"
" Ctrl+N: New File\n"
" Ctrl+O: Open File\n"
" Ctrl+S: Save File\n"
" Ctrl+Shift+S: Save As\n"
" Ctrl+P: Print File\n"
" Ctrl+Q: Quit\n\n"
"Edit Operations:\n"
" Ctrl+Z: Undo\n"
" Ctrl+Y: Redo\n"
" Ctrl+X: Cut\n"
" Ctrl+C: Copy\n"
" Ctrl+V: Paste\n"
" Delete: Delete Selection\n"
" Ctrl+A: Select All\n\n"
"Search and Replace:\n"
" Ctrl+F: Find\n"
" Ctrl+H: Replace\n\n"
"View Operations:\n"
" Ctrl+W: Word Count\n"
" Ctrl+=: Zoom In\n"
" Ctrl+-: Zoom Out\n"
" Ctrl+D: Toggle Dark Mode\n"
" Ctrl+U: Toggle Underline\n\n"
"Font Operations:\n"
" Ctrl+Right: Next Font\n"
" Ctrl+Left: Previous Font\n"
" Ctrl+Key-1: Set Font to Arial\n"
" Ctrl+Key-2: Set Font to Courier New\n"
" Ctrl+Key-3: Set Font to Comic Sans MS\n"
" Ctrl+Key-4: Set Font to Fixedsys\n"
" Ctrl+Key-5: Set Font to MS Sans Serif\n"
)
messagebox.showinfo("Keyboard Shortcuts", shortcuts)
def bind_shortcuts(self):
self.root.bind("<Control-n>", lambda _: self.new_file())
self.root.bind("<Control-o>", lambda _: self.open_file())
self.root.bind("<Control-s>", lambda _: self.save_file())
self.root.bind("<Control-Shift-S>", lambda _: self.save_as_file())
self.root.bind("<Control-p>", lambda _: self.print_file())
self.root.bind("<Control-q>", lambda _: self.root.quit())
self.root.bind("<Control-z>", lambda _: self.undo())
self.root.bind("<Control-y>", lambda _: self.redo())
self.root.bind("<Control-x>", lambda _: self.text_area.event_generate("<<Cut>>"))
self.root.bind("<Control-c>", lambda _: self.text_area.event_generate("<<Copy>>"))
# Removed Ctrl+V binding to avoid duplication
self.root.bind("<Delete>", lambda _: self.delete_selection())
self.root.bind("<Control-a>", lambda _: self.text_area.tag_add("sel", "1.0", "end"))
self.root.bind("<Control-f>", lambda _: self.find_text())
self.root.bind("<Control-h>", lambda _: self.replace_text())
self.root.bind("<Control-w>", lambda _: self.word_count())
self.root.bind("<Control-equal>", lambda _: self.zoom_in())
self.root.bind("<Control-minus>", lambda _: self.zoom_out())
self.root.bind("<Control-d>", lambda _: self.toggle_dark_mode())
self.root.bind("<Control-u>", lambda _: self.toggle_underline())
# Bind shortcuts for changing fonts
self.root.bind("<Control-Key-1>", lambda _: self.set_font("Arial"))
self.root.bind("<Control-Key-2>", lambda _: self.set_font("Courier New"))
self.root.bind("<Control-Key-3>", lambda _: self.set_font("Comic Sans MS"))
self.root.bind("<Control-Key-4>", lambda _: self.set_font("Fixedsys"))
self.root.bind("<Control-Key-5>", lambda _: self.set_font("MS Sans Serif"))
# Bind shortcuts for next and previous fonts
self.root.bind("<Control-Right>", lambda _: self.next_font())
self.root.bind("<Control-Left>", lambda _: self.previous_font())
def undo(self):
self.text_area.edit_undo()
def redo(self):
self.text_area.edit_redo()
def delete_selection(self):
self.text_area.delete(tk.SEL_FIRST, tk.SEL_LAST)
def autosave(self):
temp_file_path = os.path.join(os.path.expanduser("~"), "linpad_autosave.txt")
with open(temp_file_path, "w") as file:
file.write(self.text_area.get(1.0, tk.END))
self.update_status_bar_with_autosave(temp_file_path)
self.schedule_autosave()
def update_status_bar_with_autosave(self, temp_file_path):
self.autosave_label.config(text=f"Auto saved to: {temp_file_path}")
def schedule_autosave(self):
self.root.after(self.autosave_interval, self.autosave)
if __name__ == "__main__":
root = tk.Tk()
app = Linpad(root)
root.mainloop()