2
2
from tkinter import ttk
3
3
from tkinter .messagebox import showinfo
4
4
from tkinter .filedialog import askopenfilename , asksaveasfilename
5
- from tkinter .simpledialog import askstring
6
5
import os
7
- #1.2
6
+ from tkinter .simpledialog import askstring
7
+ import sys
8
+
8
9
class NotePy :
9
10
def __init__ (self , ** kwargs ):
11
+ # Initialize main window
10
12
self .__root = tk .Tk ()
11
13
self .__root .title ("Untitled - NotePy" )
12
14
15
+ # Set window size
13
16
self .__thisWidth = kwargs .get ('width' , 600 )
14
- self .__thisHeight = kwargs .get ('height' , 600 )
17
+ self .__thisHeight = kwargs .get ('height' , 460 )
15
18
19
+ # Center the window
16
20
screenWidth = self .__root .winfo_screenwidth ()
17
21
screenHeight = self .__root .winfo_screenheight ()
18
22
left = (screenWidth / 2 ) - (self .__thisWidth / 2 )
19
23
top = (screenHeight / 2 ) - (self .__thisHeight / 2 )
20
24
self .__root .geometry (f'{ self .__thisWidth } x{ self .__thisHeight } +{ int (left )} +{ int (top )} ' )
21
25
26
+ # Set icon (use a placeholder or remove if not available)
22
27
try :
23
- self .__root .iconbitmap ("Notepad .ico" )
28
+ self .__root .iconbitmap ("NotePy .ico" )
24
29
except tk .TclError :
25
30
pass
26
31
32
+ # Create a frame for the text area and scrollbar
27
33
self .__mainFrame = ttk .Frame (self .__root )
28
34
self .__mainFrame .grid (sticky = 'nsew' )
29
35
36
+ # Create a frame for the status bar
30
37
self .__statusFrame = ttk .Frame (self .__root )
31
38
self .__statusFrame .grid (row = 1 , column = 0 , sticky = 'ew' )
32
39
40
+ # Create a text area with a scrollbar
33
41
self .__thisTextArea = tk .Text (self .__mainFrame , wrap = 'word' , font = ('Arial' , 12 ))
34
42
self .__thisScrollBar = ttk .Scrollbar (self .__mainFrame , orient = 'vertical' , command = self .__thisTextArea .yview )
35
43
self .__thisTextArea .config (yscrollcommand = self .__thisScrollBar .set )
36
44
45
+ # Pack widgets into the main frame
37
46
self .__thisTextArea .grid (row = 0 , column = 0 , sticky = 'nsew' )
38
47
self .__thisScrollBar .grid (row = 0 , column = 1 , sticky = 'ns' )
39
48
49
+ # Configure row and column weights
40
50
self .__mainFrame .grid_rowconfigure (0 , weight = 1 )
41
51
self .__mainFrame .grid_columnconfigure (0 , weight = 1 )
42
52
53
+ # Status bar elements
43
54
self .__statusBar = tk .Label (self .__statusFrame , text = "Ln 1 | Col 1 | 100% | CRLF | UTF-8" , anchor = 'w' )
44
55
self .__statusBar .grid (row = 0 , column = 0 , sticky = 'ew' )
45
56
57
+ # Create menu bar
46
58
self .__thisMenuBar = tk .Menu (self .__root )
47
59
self .__root .config (menu = self .__thisMenuBar )
48
60
61
+ # File menu
49
62
self .__thisFileMenu = tk .Menu (self .__thisMenuBar , tearoff = 0 )
50
63
self .__thisFileMenu .add_command (label = "New" , command = self .__newFile , accelerator = "Ctrl+N" )
64
+ self .__thisFileMenu .add_command (label = "New Window" , command = self .__newWindow , accelerator = "Ctrl+Shift+N" )
51
65
self .__thisFileMenu .add_command (label = "Open" , command = self .__openFile , accelerator = "Ctrl+O" )
52
66
self .__thisFileMenu .add_command (label = "Save" , command = self .__saveFile , accelerator = "Ctrl+S" )
67
+ self .__thisFileMenu .add_command (label = "Save As" , command = self .__saveAsFile , accelerator = "Ctrl+Shift+S" )
53
68
self .__thisFileMenu .add_separator ()
54
69
self .__thisFileMenu .add_command (label = "Exit" , command = self .__quitApplication , accelerator = "Ctrl+Q" )
55
70
self .__thisMenuBar .add_cascade (label = "File" , menu = self .__thisFileMenu )
56
71
72
+ # Edit menu
57
73
self .__thisEditMenu = tk .Menu (self .__thisMenuBar , tearoff = 0 )
58
74
self .__thisEditMenu .add_command (label = "Cut" , command = self .__cut , accelerator = "Ctrl+X" )
59
75
self .__thisEditMenu .add_command (label = "Copy" , command = self .__copy , accelerator = "Ctrl+C" )
@@ -62,42 +78,59 @@ def __init__(self, **kwargs):
62
78
self .__thisEditMenu .add_command (label = "Redo" , command = self .__redo , accelerator = "Ctrl+Y" )
63
79
self .__thisEditMenu .add_command (label = "Find" , command = self .__findReplace , accelerator = "Ctrl+F" )
64
80
self .__thisEditMenu .add_command (label = "Replace" , command = self .__findReplace , accelerator = "Ctrl+H" )
81
+ self .__thisEditMenu .add_command (label = "Select All" , command = self .__selectAll , accelerator = "Ctrl+A" )
65
82
self .__thisMenuBar .add_cascade (label = "Edit" , menu = self .__thisEditMenu )
66
83
84
+ # Format menu
67
85
self .__thisFormatMenu = tk .Menu (self .__thisMenuBar , tearoff = 0 )
68
86
self .__thisFormatMenu .add_command (label = "Font Size" , command = self .__setFontSize )
69
87
self .__thisFormatMenu .add_command (label = "Font Style" , command = self .__setFontStyle )
70
88
self .__thisMenuBar .add_cascade (label = "Format" , menu = self .__thisFormatMenu )
71
89
90
+ # Help menu
72
91
self .__thisHelpMenu = tk .Menu (self .__thisMenuBar , tearoff = 0 )
73
92
self .__thisHelpMenu .add_command (label = "About NotePy" , command = self .__showAbout )
74
93
self .__thisMenuBar .add_cascade (label = "Help" , menu = self .__thisHelpMenu )
75
94
95
+ # Initialize file variable
76
96
self .__file = None
77
97
self .__line_endings = "CRLF"
78
98
self .__encoding = "UTF-8"
79
99
100
+ # Bind events
80
101
self .__thisTextArea .bind ('<KeyRelease>' , self .__updateStatusBar )
81
102
self .__root .bind_all ('<Control-n>' , lambda e : self .__newFile ())
103
+ self .__root .bind_all ('<Control-N>' , lambda e : self .__newWindow ())
82
104
self .__root .bind_all ('<Control-o>' , lambda e : self .__openFile ())
83
105
self .__root .bind_all ('<Control-s>' , lambda e : self .__saveFile ())
106
+ self .__root .bind_all ('<Control-S>' , lambda e : self .__saveAsFile ())
84
107
self .__root .bind_all ('<Control-q>' , lambda e : self .__quitApplication ())
85
108
self .__root .bind_all ('<Control-x>' , lambda e : self .__cut ())
86
109
self .__root .bind_all ('<Control-c>' , lambda e : self .__copy ())
87
110
self .__root .bind_all ('<Control-v>' , lambda e : self .__paste ())
88
111
self .__root .bind_all ('<Control-z>' , lambda e : self .__undo ())
89
112
self .__root .bind_all ('<Control-y>' , lambda e : self .__redo ())
113
+ self .__root .bind_all ('<Control-a>' , lambda e : self .__selectAll ())
90
114
self .__root .bind_all ('<Control-f>' , lambda e : self .__findReplace ())
91
115
self .__root .bind_all ('<Control-h>' , lambda e : self .__findReplace ())
92
116
117
+ # Open file if provided as an argument (for "Open with NotePy" functionality)
118
+ if len (sys .argv ) > 1 :
119
+ self .__file = sys .argv [1 ]
120
+ self .__openFile (initial_open = True )
121
+
122
+ def __newWindow (self ):
123
+ os .system (f'python "{ __file__ } "' )
124
+
93
125
def __quitApplication (self ):
94
126
self .__root .destroy ()
95
127
96
128
def __showAbout (self ):
97
129
showinfo ("NotePy" , "Having trust issue with Microsoft Products? Here you are, a FREE and OPEN SOURCE Notepad created in Python! Created by: Long Do (http://longdo.pythonanywhere.com/)" )
98
130
99
- def __openFile (self ):
100
- self .__file = askopenfilename (defaultextension = ".txt" , filetypes = [("All Files" , "*.*" ), ("Text Documents" , "*.txt" )])
131
+ def __openFile (self , initial_open = False ):
132
+ if not initial_open :
133
+ self .__file = askopenfilename (defaultextension = ".txt" , filetypes = [("All Files" , "*.*" ), ("Text Documents" , "*.txt" )])
101
134
if self .__file :
102
135
self .__root .title (os .path .basename (self .__file ) + " - NotePy" )
103
136
self .__thisTextArea .delete (1.0 , tk .END )
@@ -113,12 +146,18 @@ def __newFile(self):
113
146
114
147
def __saveFile (self ):
115
148
if self .__file is None :
116
- self .__file = asksaveasfilename (initialfile = 'Untitled.txt' , defaultextension = ".txt" , filetypes = [("All Files" , "*.*" ), ("Text Documents" , "*.txt" )])
149
+ self .__saveAsFile ()
150
+ else :
151
+ with open (self .__file , "w" , encoding = self .__encoding ) as file :
152
+ file .write (self .__thisTextArea .get (1.0 , tk .END ))
153
+ self .__root .title (os .path .basename (self .__file ) + " - NotePy" )
154
+
155
+ def __saveAsFile (self ):
156
+ self .__file = asksaveasfilename (initialfile = 'Untitled.txt' , defaultextension = ".txt" , filetypes = [("All Files" , "*.*" ), ("Text Documents" , "*.txt" )])
117
157
if self .__file :
118
158
with open (self .__file , "w" , encoding = self .__encoding ) as file :
119
159
file .write (self .__thisTextArea .get (1.0 , tk .END ))
120
160
self .__root .title (os .path .basename (self .__file ) + " - NotePy" )
121
- self .__updateStatusBar ()
122
161
123
162
def __cut (self ):
124
163
self .__thisTextArea .event_generate ("<<Cut>>" )
@@ -130,70 +169,55 @@ def __paste(self):
130
169
self .__thisTextArea .event_generate ("<<Paste>>" )
131
170
132
171
def __undo (self ):
133
- self .__thisTextArea .event_generate ("<<Undo>>" )
172
+ try :
173
+ self .__thisTextArea .edit_undo ()
174
+ except tk .TclError :
175
+ pass
134
176
135
177
def __redo (self ):
136
- self .__thisTextArea .event_generate ("<<Redo>>" )
178
+ try :
179
+ self .__thisTextArea .edit_redo ()
180
+ except tk .TclError :
181
+ pass
137
182
138
183
def __findReplace (self ):
139
- findReplaceWindow = tk .Toplevel (self .__root )
140
- findReplaceWindow .title ("Find and Replace" )
141
- tk .Label (findReplaceWindow , text = "Find:" ).grid (row = 0 , column = 0 , sticky = 'e' )
142
- findEntry = tk .Entry (findReplaceWindow , width = 30 )
143
- findEntry .grid (row = 0 , column = 1 , padx = 5 , pady = 5 )
144
- tk .Label (findReplaceWindow , text = "Replace with:" ).grid (row = 1 , column = 0 , sticky = 'e' )
145
- replaceEntry = tk .Entry (findReplaceWindow , width = 30 )
146
- replaceEntry .grid (row = 1 , column = 1 , padx = 5 , pady = 5 )
147
- tk .Button (findReplaceWindow , text = "Find" , command = lambda : self .__find (findEntry .get ())).grid (row = 2 , column = 0 , padx = 5 , pady = 5 )
148
- tk .Button (findReplaceWindow , text = "Replace" , command = lambda : self .__replace (findEntry .get (), replaceEntry .get ())).grid (row = 2 , column = 1 , padx = 5 , pady = 5 )
149
- tk .Button (findReplaceWindow , text = "Cancel" , command = findReplaceWindow .destroy ).grid (row = 2 , column = 2 , padx = 5 , pady = 5 )
150
-
151
- def __find (self , search_text ):
152
- content = self .__thisTextArea .get (1.0 , tk .END )
153
- index = content .find (search_text )
154
- if index != - 1 :
155
- self .__thisTextArea .mark_set ("insert" , f"1.0+{ index } c" )
156
- self .__thisTextArea .see ("insert" )
157
-
158
- def __replace (self , search_text , replace_text ):
159
- content = self .__thisTextArea .get (1.0 , tk .END )
160
- new_content = content .replace (search_text , replace_text )
184
+ find_what = askstring ("Find" , "Enter text to find:" )
185
+ replace_with = askstring ("Replace" , "Replace with (leave empty to skip):" )
186
+
187
+ text_content = self .__thisTextArea .get (1.0 , tk .END )
161
188
self .__thisTextArea .delete (1.0 , tk .END )
162
- self .__thisTextArea .insert (1.0 , new_content )
189
+
190
+ if replace_with :
191
+ text_content = text_content .replace (find_what , replace_with )
192
+
193
+ self .__thisTextArea .insert (1.0 , text_content )
194
+ self .__updateStatusBar ()
195
+
196
+ def __selectAll (self ):
197
+ self .__thisTextArea .tag_add ('sel' , '1.0' , 'end' )
163
198
164
199
def __setFontSize (self ):
165
200
size = askstring ("Font Size" , "Enter font size:" )
166
- if size :
167
- try :
168
- size = int (size )
169
- current_font = self .__thisTextArea .cget ("font" )
170
- font_family , font_size = current_font .rsplit (' ' , 1 )
171
- self .__thisTextArea .config (font = (font_family , size ))
172
- except ValueError :
173
- showinfo ("Error" , "Invalid font size." )
201
+ if size and size .isdigit ():
202
+ self .__thisTextArea .config (font = ("Arial" , int (size )))
174
203
175
204
def __setFontStyle (self ):
176
- font_style = askstring ("Font Style" , "Enter font style (e.g., Arial, Courier):" )
177
- if font_style :
178
- current_font = self .__thisTextArea .cget ("font" )
179
- font_family , font_size = current_font .rsplit (' ' , 1 )
180
- self .__thisTextArea .config (font = (font_style , font_size ))
205
+ style = askstring ("Font Style" , "Enter font style (e.g., bold, italic):" )
206
+ if style :
207
+ current_font = self .__thisTextArea ['font' ].split ()
208
+ if len (current_font ) > 1 :
209
+ current_font [- 1 ] = style
210
+ else :
211
+ current_font .append (style )
212
+ self .__thisTextArea .config (font = " " .join (current_font ))
181
213
182
214
def __updateStatusBar (self , event = None ):
183
- content = self .__thisTextArea .get (1.0 , tk .END )
184
- num_lines = int (self .__thisTextArea .index ('end-1c' ).split ('.' )[0 ])
185
- num_cols = len (self .__thisTextArea .get (tk .INSERT + " linestart" , tk .INSERT ))
186
- num_words = len (content .split ())
187
-
188
- line , col = map (int , self .__thisTextArea .index (tk .INSERT ).split ('.' ))
189
- line_endings = self .__line_endings
190
- encoding = self .__encoding
191
-
192
- self .__statusBar .config (text = f"Ln { line } | Col { col } | { num_words } Words | { line_endings } | { encoding } " )
215
+ line , column = self .__thisTextArea .index (tk .INSERT ).split ('.' )
216
+ self .__statusBar .config (text = f"Ln { line } | Col { column } | 100% | { self .__line_endings } | { self .__encoding } " )
193
217
194
218
def run (self ):
195
219
self .__root .mainloop ()
196
220
197
- if __name__ == " __main__" :
198
- app = NotePy (width = 600 , height = 460 )
221
+ if __name__ == ' __main__' :
222
+ app = NotePy ()
199
223
app .run ()
0 commit comments