2
2
# pylint: disable=no-member
3
3
# pylint: disable=invalid-name
4
4
5
+ import os
5
6
import shlex
6
7
from tkinter import Tk
7
8
import wx
15
16
wx_app = [] # pylint: disable=unused-variable
16
17
wx_app = wx .App (None )
17
18
19
+ # get the current working directory.
20
+ CURRENT_WORKING_DIRECTORY = os .path .dirname (os .path .realpath (__file__ ))
21
+ APPLICATION_ICON = CURRENT_WORKING_DIRECTORY + '\\ notepad.ico'
18
22
19
23
# change the default theme.
20
- # sg.theme('dark grey 9')
24
+ sg .theme ('dark grey 9' )
21
25
22
26
WINDOW_WIDTH : int = 90
23
27
WINDOW_HEIGHT : int = 25
24
28
FILE_NAME : str = None
25
29
DEFAULT_FONT_NAME : str = 'Times New Roman'
26
30
APP_NAME : str = 'NotepadPy+'
27
31
SELECTED_THEME : str = ''
32
+ text_to_save : str = ''
33
+ # this is needed to control the displaying of the user prompt while closing.
34
+ # If the user closes the document just before closing the document,
35
+ # we do not want to display the prompt for save changes.
36
+ text_last_saved_manually : str = ''
37
+
38
+
39
+ # initialize the print data and set some default values
40
+ pdata = wx .PrintData ()
41
+ pdata .SetPaperId (wx .PAPER_A3 )
42
+ pdata .SetOrientation (wx .PORTRAIT )
43
+ margins = (wx .Point (15 , 15 ), wx .Point (15 , 15 ))
28
44
29
45
def ShowFontDialog ():
30
46
'''Get a font dialog to display and return all the
@@ -84,32 +100,49 @@ def ShowPrintDialog():
84
100
data .SetMinPage (1 )
85
101
data .SetMaxPage (10 )
86
102
87
- dialog = wx .PrintDialog (None , data )
88
-
89
103
text_to_print = VALUES ['-BODY-' ]
104
+ # lines_to_print = text_to_print.split('\n')
105
+
106
+ dialog = wx .PrintDialog (None , data )
90
107
if dialog .ShowModal () == wx .ID_OK :
91
108
data = dialog .GetPrintDialogData ()
92
- dc = dialog . GetPrintDC ( )
109
+ data . GetPrintData (). SetPaperId ( wx . PAPER_A3 )
93
110
111
+ dc = dialog .GetPrintDC ()
94
112
dc .StartDoc ("MyDoc" )
95
113
dc .StartPage ()
96
114
dc .SetMapMode (wx .MM_POINTS )
97
115
98
116
dc .SetTextForeground ("black" )
99
- dc .DrawText (text_to_print , 50 , 100 )
117
+ dc .DrawText (text_to_print , margins [ 0 ][ 0 ], margins [ 1 ][ 0 ] )
100
118
101
119
dc .EndPage ()
102
120
dc .EndDoc ()
103
121
del dc
104
122
105
- printer = wx .Printer (data )
106
- printer_config = wx .PrintData (printer .GetPrintDialogData ().GetPrintData ())
107
-
108
- # print('GetAllPages: %d\n' % data.GetAllPages())
109
-
110
-
123
+ # printer = wx.Printer(data)
111
124
dialog .Destroy ()
112
125
126
+ def ShowPageSetupDialog ():
127
+ '''display the page setup dialog.'''
128
+ global pdata
129
+ global margins
130
+ data = wx .PageSetupDialogData ()
131
+ data .SetPrintData (pdata )
132
+
133
+ data .SetDefaultMinMargins (True )
134
+ data .SetMarginTopLeft (margins [0 ])
135
+ data .SetMarginBottomRight (margins [1 ])
136
+
137
+ dlg = wx .PageSetupDialog (None , data )
138
+ if dlg .ShowModal () == wx .ID_OK :
139
+ data = dlg .GetPageSetupData ()
140
+ pdata = wx .PrintData (data .GetPrintData ()) # force a copy
141
+ pdata .SetPaperId (data .GetPaperId ())
142
+ margins = (data .GetMarginTopLeft (), data .GetMarginBottomRight ())
143
+
144
+ dlg .Destroy ()
145
+
113
146
def rgb2hex (r , g , b ):
114
147
'''Convert RGB to hex values.'''
115
148
return "#{:02x}{:02x}{:02x}" .format (r , g , b )
@@ -127,7 +160,7 @@ def rgb2hex(r, g, b):
127
160
edit_delete : str = 'Delete Del'
128
161
129
162
130
- menu_layout : list = [['&File' , [file_new , file_open , file_save , 'Save As' , '______________________' , file_print , '______________________' , 'Exit' ]],
163
+ menu_layout : list = [['&File' , [file_new , file_open , file_save , 'Save As' , '______________________' , 'Page Setup' , file_print , '______________________' , 'Exit' ]],
131
164
['&Edit' , [edit_cut , edit_copy , edit_paste , edit_delete ]],
132
165
['&Statistics' , ['Word Count' , 'Line Count' , 'Character With Spaces' , 'Character Without Spaces' , ]],
133
166
['F&ormat' , ['Font' , ]],
@@ -140,31 +173,18 @@ def rgb2hex(r, g, b):
140
173
size = (WINDOW_WIDTH , WINDOW_HEIGHT ), key = '-BODY-' , reroute_cprint = True )]]
141
174
142
175
WINDOW = sg .Window ('untitled - ' + APP_NAME , layout = layout , margins = (0 , 0 ),
143
- resizable = True , return_keyboard_events = True , finalize = True )
144
- # WINDOW.read(timeout=1)
145
- WINDOW .maximize ()
146
- WINDOW ['-BODY-' ].expand (expand_x = True , expand_y = True )
176
+ resizable = True , return_keyboard_events = True , icon = APPLICATION_ICON , finalize = True )
147
177
148
- # APPLICATION THEME CHANGING DIALOG - A good place to refer are the following resources -
149
- # https://github.com/PySimpleGUI/PySimpleGUI/blob/master/DemoPrograms/Demo_Design_Pattern_Multiple_Windows2.py
150
- # https://github.com/PySimpleGUI/PySimpleGUI/blob/master/DemoPrograms/Demo_Design_Pattern_Multiple_Windows.py
151
- # https://github.com/PySimpleGUI/PySimpleGUI/blob/master/DemoPrograms/Demo_Design_Pattern_Multiple_Windows1.py
178
+ # redefine the callback for window close button by using tkinter code.
179
+ # this is required to delay the event of closing the main window incase
180
+ # the text is not saved before closing.
181
+ # more details @ https://github.com/PySimpleGUI/PySimpleGUI/issues/3650
182
+ WINDOW .TKroot .protocol ("WM_DESTROY_WINDOW" , lambda :WINDOW .write_event_value ("WIN_CLOSE" , ()))
183
+ WINDOW .TKroot .protocol ("WM_DELETE_WINDOW" , lambda :WINDOW .write_event_value ("WIN_CLOSE" , ()))
152
184
153
- def create_theme_browser ():
154
- '''Creates a GUI Theme browser dialog to select
155
- and apply the application theme.'''
156
-
157
- theme_window_layout = [[sg .Text ('Select a theme from the list below and\n click on Apply for changes to take effect.' )],
158
- [sg .Listbox (values = sg .theme_list (), size = (20 , 12 ), key = '-THEMELIST-' , enable_events = True )],
159
- [sg .Button ('Apply' , tooltip = "Applies the selected theme." , key = '-APPLYTHEME-' ),
160
- sg .Button ('Exit' , key = '-EXITTHEME-' )]]
161
-
162
- # Define the second window
163
- # Link it to the first window (master=window)
164
- # Assign a key to the window so that it can be easily referenced
165
- theme_window = sg .Window (title = 'Theme Browser' , layout = theme_window_layout , finalize = True , modal = True )
166
-
167
- return theme_window
185
+ WINDOW .read (timeout = 1 )
186
+ WINDOW .maximize ()
187
+ WINDOW ['-BODY-' ].expand (expand_x = True , expand_y = True )
168
188
169
189
def new_file () -> str :
170
190
''' Reset body and info bar, and clear FILE_NAME variable '''
@@ -185,35 +205,58 @@ def open_file() -> str:
185
205
186
206
def save_file (file_name : str ):
187
207
''' Save file instantly if already open; otherwise display `save-as` popup '''
188
-
208
+ global text_last_saved_manually
189
209
# Get the filename if already saved in the same session.
190
210
file_name = WINDOW ['-FILE_INFO-' ].DisplayText
191
211
if file_name not in (None , '' , 'New File:' ):
192
212
with open (file_name , 'w' ) as f :
193
- f .write (VALUES .get ('-BODY-' ))
194
- WINDOW ['-FILE_INFO-' ].update (value = file_name )
213
+ if VALUES is not None :
214
+ f .write (VALUES .get ('-BODY-' ))
215
+ WINDOW ['-FILE_INFO-' ].update (value = file_name )
216
+ else :
217
+ f .write (text_to_save )
218
+
219
+ # this is needed to control the displaying of the user prompt while closing.
220
+ # If the user closes the document just before closing the document,
221
+ # we do not want to display the prompt for save changes.
222
+ text_last_saved_manually = text_to_save
195
223
else :
196
224
file_name = save_as ()
197
- WINDOW .set_title (file_name + ' - ' + APP_NAME )
225
+
226
+
227
+ # We will skip this line while closing the dialog.
228
+ if VALUES is not None :
229
+ WINDOW .set_title (file_name + ' - ' + APP_NAME )
198
230
199
231
def save_as () -> str :
200
232
''' Save new file or save existing file with another name '''
233
+ global text_last_saved_manually
201
234
try :
202
- file_name : str = sg .popup_get_file ('Save As' , save_as = True , no_window = True )
235
+ file_name : str = sg .popup_get_file ('Save As' , save_as = True , no_window = True ,
236
+ file_types = (('Text Documents' , '*.txt' ), ('ALL Files' , '*.*' ),),
237
+ modal = True , default_path = "*.txt" ,
238
+ icon = APPLICATION_ICON )
203
239
except : # pylint: disable=bare-except
204
240
return ''
205
- if file_name not in (None , '' )and not isinstance ( FILE_NAME , tuple ) :
241
+ if file_name not in (None , '' ):
206
242
with open (file_name , 'w' ) as f :
207
- f .write (VALUES .get ('-BODY-' ))
208
- WINDOW ['-FILE_INFO-' ].update (value = file_name )
243
+ if VALUES is not None :
244
+ f .write (VALUES .get ('-BODY-' ))
245
+ WINDOW ['-FILE_INFO-' ].update (value = file_name )
246
+ else :
247
+ f .write (text_to_save )
248
+ # this is needed to control the displaying of the user prompt while closing.
249
+ # If the user closes the document just before closing the document,
250
+ # we do not want to display the prompt for save changes.
251
+ text_last_saved_manually = text_to_save
209
252
return file_name
210
253
211
254
def get_word_count ():
212
255
''' Get the estimated word count '''
213
256
total_words : int = 0
214
257
if not validate_text ():
215
258
sg .PopupQuick ('Enter some text to calculate the number of words.' ,
216
- title = 'Text Not Found' , auto_close = False )
259
+ title = 'Text Not Found' , auto_close = False , modal = True )
217
260
return 0
218
261
219
262
lines : list = VALUES ['-BODY-' ].splitlines ()
@@ -238,7 +281,7 @@ def character_count():
238
281
239
282
if not validate_text ():
240
283
sg .PopupQuick ('Enter some text to calculate the number of characters.' ,
241
- title = 'Text Not Found' , auto_close = False )
284
+ title = 'Text Not Found' , auto_close = False , modal = True )
242
285
return 0
243
286
244
287
chars = len (VALUES ['-BODY-' ]) - 1
@@ -249,7 +292,7 @@ def characters_without_spaces():
249
292
250
293
if not validate_text ():
251
294
sg .PopupQuick ('Enter some text to calculate the number of characters\n without spaces.' ,
252
- title = 'Text Not Found' , auto_close = False )
295
+ title = 'Text Not Found' , auto_close = False , modal = True )
253
296
return 0
254
297
255
298
chars_without_spaces : int = 0
@@ -268,7 +311,7 @@ def get_line_count():
268
311
269
312
if not validate_text ():
270
313
sg .PopupQuick ('Enter some text to calculate the number of lines.' ,
271
- title = 'Text Not Found' , auto_close = False )
314
+ title = 'Text Not Found' , auto_close = False , modal = True )
272
315
return 0
273
316
274
317
text : str = VALUES ['-BODY-' ]
@@ -286,21 +329,36 @@ def about():
286
329
'''About the application'''
287
330
288
331
sg .PopupQuick ('A simple Notepad like application created using\
289
- PySimpleGUI framework.' , auto_close = False )
332
+ PySimpleGUI framework.' , auto_close = False , modal = True )
290
333
291
- window1 , window2 = WINDOW (), None
292
334
# read the events and take appropriate actions.
293
335
while True :
294
336
295
- WIN , EVENT , VALUES = sg .read_all_windows () #WINDOW.read()
337
+ EVENT , VALUES = WINDOW .read ()
338
+
339
+ if EVENT in (sg .WINDOW_CLOSED , 'Exit' , "WIN_CLOSE" ):
340
+ # Get the filename if already saved in the same session.
341
+ file_name = WINDOW ['-FILE_INFO-' ].DisplayText
342
+
343
+ if file_name not in (None , '' ) and text_to_save .rstrip () != '' and text_last_saved_manually != text_to_save :
344
+ # display a user prompt incase the note is not yet saved asking the
345
+ # user 'Do you want to save changes to Untitled?'
346
+ user_prompt_msg : str = ''
347
+ if file_name == 'New File:' :
348
+ user_prompt_msg = 'Untitled'
349
+ else :
350
+ user_prompt_msg = file_name
351
+ user_prompt_action = sg .popup_yes_no ('Do you want to save changes to ' + user_prompt_msg + "?" ,
352
+ title = 'NotepayPy+' , modal = True ,
353
+ icon = APPLICATION_ICON )
296
354
297
- if EVENT in ( sg . WINDOW_CLOSED , 'Exit' , '-EXITTHEME-' ) :
298
- # exit out of the application is close or exit clicked.
299
- WIN . close ()
300
- if WIN == window2 : # if closing win 2, mark as closed
301
- window2 = None
302
- else : # if closing win 1, exit program
303
- break
355
+ if user_prompt_action == 'Yes' :
356
+ save_file ( FILE_NAME )
357
+ elif user_prompt_action == 'No' :
358
+ break
359
+
360
+ # finally breakout of the event loop and end the application.
361
+ break
304
362
305
363
# file menu events.
306
364
if EVENT in (file_new , 'n:78' ):
@@ -313,6 +371,8 @@ def about():
313
371
FILE_NAME = save_as ()
314
372
if EVENT in (file_print , 'p:80' ):
315
373
ShowPrintDialog ()
374
+ if EVENT == 'Page Setup' :
375
+ ShowPageSetupDialog ()
316
376
317
377
# edit menu events.
318
378
if EVENT == edit_cut :
@@ -338,23 +398,31 @@ def about():
338
398
if EVENT in ('Word Count' ,):
339
399
WORDS = get_word_count ()
340
400
if WORDS != 0 :
341
- sg .PopupQuick ('Word Count: {:,d}' .format (WORDS ), auto_close = False )
401
+ sg .PopupQuick ('Word Count: {:,d}' .format (WORDS ), auto_close = False , modal = True )
342
402
if EVENT in ('Line Count' ,):
343
403
LINES = get_line_count ()
344
404
if LINES != 0 :
345
- sg .PopupQuick ('Line Count: {:,d}' .format (LINES ), auto_close = False )
405
+ sg .PopupQuick ('Line Count: {:,d}' .format (LINES ), auto_close = False , modal = True )
346
406
if EVENT in ('Character With Spaces' ,):
347
407
CHARS = character_count ()
348
408
if CHARS != 0 :
349
- sg .PopupQuick ('Characters With Spaces: {:,d}' .format (CHARS ), auto_close = False )
409
+ sg .PopupQuick ('Characters With Spaces: {:,d}' .format (CHARS ),
410
+ auto_close = False , modal = True )
350
411
if EVENT in ('Character Without Spaces' ,):
351
412
CHAR_WITHOUT_SPACES = characters_without_spaces ()
352
413
if CHAR_WITHOUT_SPACES != 0 :
353
414
sg .PopupQuick ('Characters Without Spaces: {:,d}' .format (CHAR_WITHOUT_SPACES ),
354
- auto_close = False )
415
+ auto_close = False , modal = True )
355
416
if EVENT in ('About' ,):
356
417
about ()
357
418
358
419
# Format Menu
359
420
if EVENT in ('Font' ,):
360
421
ShowFontDialog ()
422
+
423
+ # record the text after each event to ensure the
424
+ # file/text is saved.
425
+ try :
426
+ text_to_save = VALUES ['-BODY-' ]
427
+ except :
428
+ pass
0 commit comments