1"""Simple text browser for IDLE 2 3""" 4from tkinter import Toplevel, Text, TclError,\ 5 HORIZONTAL, VERTICAL, NS, EW, NSEW, NONE, WORD, SUNKEN 6from tkinter.ttk import Frame, Scrollbar, Button 7from tkinter.messagebox import showerror 8 9from idlelib.colorizer import color_config 10 11 12class AutoHideScrollbar(Scrollbar): 13 """A scrollbar that is automatically hidden when not needed. 14 15 Only the grid geometry manager is supported. 16 """ 17 def set(self, lo, hi): 18 if float(lo) > 0.0 or float(hi) < 1.0: 19 self.grid() 20 else: 21 self.grid_remove() 22 super().set(lo, hi) 23 24 def pack(self, **kwargs): 25 raise TclError(f'{self.__class__.__name__} does not support "pack"') 26 27 def place(self, **kwargs): 28 raise TclError(f'{self.__class__.__name__} does not support "place"') 29 30 31class ScrollableTextFrame(Frame): 32 """Display text with scrollbar(s).""" 33 34 def __init__(self, master, wrap=NONE, **kwargs): 35 """Create a frame for Textview. 36 37 master - master widget for this frame 38 wrap - type of text wrapping to use ('word', 'char' or 'none') 39 40 All parameters except for 'wrap' are passed to Frame.__init__(). 41 42 The Text widget is accessible via the 'text' attribute. 43 44 Note: Changing the wrapping mode of the text widget after 45 instantiation is not supported. 46 """ 47 super().__init__(master, **kwargs) 48 49 text = self.text = Text(self, wrap=wrap) 50 text.grid(row=0, column=0, sticky=NSEW) 51 self.grid_rowconfigure(0, weight=1) 52 self.grid_columnconfigure(0, weight=1) 53 54 # vertical scrollbar 55 self.yscroll = AutoHideScrollbar(self, orient=VERTICAL, 56 takefocus=False, 57 command=text.yview) 58 self.yscroll.grid(row=0, column=1, sticky=NS) 59 text['yscrollcommand'] = self.yscroll.set 60 61 # horizontal scrollbar - only when wrap is set to NONE 62 if wrap == NONE: 63 self.xscroll = AutoHideScrollbar(self, orient=HORIZONTAL, 64 takefocus=False, 65 command=text.xview) 66 self.xscroll.grid(row=1, column=0, sticky=EW) 67 text['xscrollcommand'] = self.xscroll.set 68 else: 69 self.xscroll = None 70 71 72class ViewFrame(Frame): 73 "Display TextFrame and Close button." 74 def __init__(self, parent, contents, wrap='word'): 75 """Create a frame for viewing text with a "Close" button. 76 77 parent - parent widget for this frame 78 contents - text to display 79 wrap - type of text wrapping to use ('word', 'char' or 'none') 80 81 The Text widget is accessible via the 'text' attribute. 82 """ 83 super().__init__(parent) 84 self.parent = parent 85 self.bind('<Return>', self.ok) 86 self.bind('<Escape>', self.ok) 87 self.textframe = ScrollableTextFrame(self, relief=SUNKEN, height=700) 88 89 text = self.text = self.textframe.text 90 text.insert('1.0', contents) 91 text.configure(wrap=wrap, highlightthickness=0, state='disabled') 92 color_config(text) 93 text.focus_set() 94 95 self.button_ok = button_ok = Button( 96 self, text='Close', command=self.ok, takefocus=False) 97 self.textframe.pack(side='top', expand=True, fill='both') 98 button_ok.pack(side='bottom') 99 100 def ok(self, event=None): 101 """Dismiss text viewer dialog.""" 102 self.parent.destroy() 103 104 105class ViewWindow(Toplevel): 106 "A simple text viewer dialog for IDLE." 107 108 def __init__(self, parent, title, contents, modal=True, wrap=WORD, 109 *, _htest=False, _utest=False): 110 """Show the given text in a scrollable window with a 'close' button. 111 112 If modal is left True, users cannot interact with other windows 113 until the textview window is closed. 114 115 parent - parent of this dialog 116 title - string which is title of popup dialog 117 contents - text to display in dialog 118 wrap - type of text wrapping to use ('word', 'char' or 'none') 119 _htest - bool; change box location when running htest. 120 _utest - bool; don't wait_window when running unittest. 121 """ 122 super().__init__(parent) 123 self['borderwidth'] = 5 124 # Place dialog below parent if running htest. 125 x = parent.winfo_rootx() + 10 126 y = parent.winfo_rooty() + (10 if not _htest else 100) 127 self.geometry(f'=750x500+{x}+{y}') 128 129 self.title(title) 130 self.viewframe = ViewFrame(self, contents, wrap=wrap) 131 self.protocol("WM_DELETE_WINDOW", self.ok) 132 self.button_ok = button_ok = Button(self, text='Close', 133 command=self.ok, takefocus=False) 134 self.viewframe.pack(side='top', expand=True, fill='both') 135 136 self.is_modal = modal 137 if self.is_modal: 138 self.transient(parent) 139 self.grab_set() 140 if not _utest: 141 self.wait_window() 142 143 def ok(self, event=None): 144 """Dismiss text viewer dialog.""" 145 if self.is_modal: 146 self.grab_release() 147 self.destroy() 148 149 150def view_text(parent, title, contents, modal=True, wrap='word', _utest=False): 151 """Create text viewer for given text. 152 153 parent - parent of this dialog 154 title - string which is the title of popup dialog 155 contents - text to display in this dialog 156 wrap - type of text wrapping to use ('word', 'char' or 'none') 157 modal - controls if users can interact with other windows while this 158 dialog is displayed 159 _utest - bool; controls wait_window on unittest 160 """ 161 return ViewWindow(parent, title, contents, modal, wrap=wrap, _utest=_utest) 162 163 164def view_file(parent, title, filename, encoding, modal=True, wrap='word', 165 _utest=False): 166 """Create text viewer for text in filename. 167 168 Return error message if file cannot be read. Otherwise calls view_text 169 with contents of the file. 170 """ 171 try: 172 with open(filename, 'r', encoding=encoding) as file: 173 contents = file.read() 174 except OSError: 175 showerror(title='File Load Error', 176 message=f'Unable to load file {filename!r} .', 177 parent=parent) 178 except UnicodeDecodeError as err: 179 showerror(title='Unicode Decode Error', 180 message=str(err), 181 parent=parent) 182 else: 183 return view_text(parent, title, contents, modal, wrap=wrap, 184 _utest=_utest) 185 return None 186 187 188if __name__ == '__main__': 189 from unittest import main 190 main('idlelib.idle_test.test_textview', verbosity=2, exit=False) 191 192 from idlelib.idle_test.htest import run 193 run(ViewWindow) 194