|
| 1 | +from __future__ import annotations |
| 2 | + |
| 3 | +import inspect |
| 4 | +import sys |
| 5 | +import traceback |
| 6 | + |
| 7 | + |
| 8 | +def _create_error_message(): |
| 9 | + """ |
| 10 | + Creates an error message containing the filename and line number of the users |
| 11 | + code that made the call into PySimpleGUI |
| 12 | + :return: Error string to display with file, line number, and line of code |
| 13 | + :rtype: str |
| 14 | + """ |
| 15 | + |
| 16 | + called_func = inspect.stack()[1].function |
| 17 | + trace_details = traceback.format_stack() |
| 18 | + error_message = '' |
| 19 | + file_info_pysimplegui = trace_details[-1].split(',')[0] |
| 20 | + for line in reversed(trace_details): |
| 21 | + if line.split(',')[0] != file_info_pysimplegui: |
| 22 | + error_message = line |
| 23 | + break |
| 24 | + if error_message != '': |
| 25 | + error_parts = error_message.split(', ') |
| 26 | + if len(error_parts) < 4: |
| 27 | + error_message = error_parts[0] + '\n' + error_parts[1] + '\n' + ''.join(error_parts[2:]) |
| 28 | + return 'The PySimpleGUI internal reporting function is ' + called_func + '\n' + 'The error originated from:\n' + error_message |
| 29 | + |
| 30 | + |
| 31 | +def _error_popup_with_traceback(title, *args, emoji=None): |
| 32 | + if SUPPRESS_ERROR_POPUPS: |
| 33 | + return |
| 34 | + trace_details = traceback.format_stack() |
| 35 | + error_message = '' |
| 36 | + file_info_pysimplegui = None |
| 37 | + for line in reversed(trace_details): |
| 38 | + if __file__ not in line: |
| 39 | + file_info_pysimplegui = line.split(',')[0] |
| 40 | + error_message = line |
| 41 | + break |
| 42 | + if file_info_pysimplegui is None: |
| 43 | + _error_popup_with_code(title, None, None, 'Did not find your traceback info', *args, emoji=emoji) |
| 44 | + return |
| 45 | + |
| 46 | + error_parts = None |
| 47 | + if error_message != '': |
| 48 | + error_parts = error_message.split(', ') |
| 49 | + if len(error_parts) < 4: |
| 50 | + error_message = error_parts[0] + '\n' + error_parts[1] + '\n' + ''.join(error_parts[2:]) |
| 51 | + if error_parts is None: |
| 52 | + print('*** Error popup attempted but unable to parse error details ***') |
| 53 | + print(trace_details) |
| 54 | + return |
| 55 | + filename = error_parts[0][error_parts[0].index('File ') + 5 :] |
| 56 | + line_num = error_parts[1][error_parts[1].index('line ') + 5 :] |
| 57 | + _error_popup_with_code(title, filename, line_num, error_message, *args, emoji=emoji) |
| 58 | + |
| 59 | + |
| 60 | +def _error_popup_with_code(title, filename, line_num, *args, emoji=None): |
| 61 | + """ |
| 62 | + Makes the error popup window |
| 63 | +
|
| 64 | + :param title: The title that will be shown in the popup's titlebar and in the first line of the window |
| 65 | + :type title: str |
| 66 | + :param filename: The filename to show.. may not be the filename that actually encountered the exception! |
| 67 | + :type filename: str |
| 68 | + :param line_num: Line number within file with the error |
| 69 | + :type line_num: int | str |
| 70 | + :param args: A variable number of lines of messages |
| 71 | + :type args: *Any |
| 72 | + :param emoji: An optional BASE64 Encoded image to shows in the error window |
| 73 | + :type emoji: bytes |
| 74 | + """ |
| 75 | + editor_filename = execute_get_editor() |
| 76 | + emoji_data = emoji if emoji is not None else _random_error_emoji() |
| 77 | + layout = [[Text('ERROR'), Text(title)], [Image(data=emoji_data)]] |
| 78 | + lines = [] |
| 79 | + for msg in args: |
| 80 | + if isinstance(msg, Exception): |
| 81 | + lines += [[f'Additional Exception info pased in by PySimpleGUI or user: Error type is: {type(msg).__name__}']] |
| 82 | + lines += [[f'In file {__file__} Line number {msg.__traceback__.tb_lineno}']] |
| 83 | + lines += [[f'{msg}']] |
| 84 | + else: |
| 85 | + lines += [str(msg).split('\n')] |
| 86 | + max_line_len = 0 |
| 87 | + for line in lines: |
| 88 | + max_line_len = max(max_line_len, max([len(s) for s in line])) |
| 89 | + |
| 90 | + layout += [[Text(''.join(line), size=(min(max_line_len, 90), None))] for line in lines] |
| 91 | + layout += [ |
| 92 | + [ |
| 93 | + Button('Close'), |
| 94 | + Button('Take me to error', disabled=True if not editor_filename else False), |
| 95 | + Button('Kill Application', button_color='white on red'), |
| 96 | + ] |
| 97 | + ] |
| 98 | + if not editor_filename: |
| 99 | + layout += [[Text('Configure editor in the Global settings to enable "Take me to error" feature')]] |
| 100 | + window = Window(title, layout, keep_on_top=True) |
| 101 | + |
| 102 | + while True: |
| 103 | + event, values = window.read() |
| 104 | + if event in ('Close', WIN_CLOSED): |
| 105 | + break |
| 106 | + if event == 'Kill Application': |
| 107 | + window.close() |
| 108 | + popup_quick_message( |
| 109 | + 'KILLING APP! BYE!', |
| 110 | + font='_ 18', |
| 111 | + keep_on_top=True, |
| 112 | + text_color='white', |
| 113 | + background_color='red', |
| 114 | + non_blocking=False, |
| 115 | + ) |
| 116 | + sys.exit() |
| 117 | + if event == 'Take me to error' and filename is not None and line_num is not None: |
| 118 | + execute_editor(filename, line_num) |
| 119 | + |
| 120 | + window.close() |
| 121 | + |
| 122 | + |
| 123 | +def _exit_mainloop(exiting_window): |
| 124 | + if exiting_window == Window._window_running_mainloop or Window._root_running_mainloop == Window.hidden_master_root: |
| 125 | + Window._window_that_exited = exiting_window |
| 126 | + if Window._root_running_mainloop is not None: |
| 127 | + Window._root_running_mainloop.quit() |
| 128 | + # print('** Exited window mainloop **') |
| 129 | + |
| 130 | + |
| 131 | +from FreeSimpleGUI import _random_error_emoji |
| 132 | +from FreeSimpleGUI import execute_editor |
| 133 | +from FreeSimpleGUI import execute_get_editor |
| 134 | +from FreeSimpleGUI import popup_quick_message |
| 135 | +from FreeSimpleGUI import SUPPRESS_ERROR_POPUPS |
| 136 | +from FreeSimpleGUI import WIN_CLOSED |
| 137 | +from FreeSimpleGUI.elements.button import Button |
| 138 | +from FreeSimpleGUI.elements.image import Image |
| 139 | +from FreeSimpleGUI.elements.text import Text |
| 140 | +from FreeSimpleGUI.window import Window |
0 commit comments