|
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://.com/PySimpleGUI/PySimpleGUI/blob/master/DemoPrograms/Demo_Design_Pattern_Multiple_Windows2.py |
150 |
| -# https://.com/PySimpleGUI/PySimpleGUI/blob/master/DemoPrograms/Demo_Design_Pattern_Multiple_Windows.py |
151 |
| -# https://.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://.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\nclick 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\nwithout 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