17db96d56Sopenharmony_ci"""Simple text browser for IDLE 27db96d56Sopenharmony_ci 37db96d56Sopenharmony_ci""" 47db96d56Sopenharmony_cifrom tkinter import Toplevel, Text, TclError,\ 57db96d56Sopenharmony_ci HORIZONTAL, VERTICAL, NS, EW, NSEW, NONE, WORD, SUNKEN 67db96d56Sopenharmony_cifrom tkinter.ttk import Frame, Scrollbar, Button 77db96d56Sopenharmony_cifrom tkinter.messagebox import showerror 87db96d56Sopenharmony_ci 97db96d56Sopenharmony_cifrom idlelib.colorizer import color_config 107db96d56Sopenharmony_ci 117db96d56Sopenharmony_ci 127db96d56Sopenharmony_ciclass AutoHideScrollbar(Scrollbar): 137db96d56Sopenharmony_ci """A scrollbar that is automatically hidden when not needed. 147db96d56Sopenharmony_ci 157db96d56Sopenharmony_ci Only the grid geometry manager is supported. 167db96d56Sopenharmony_ci """ 177db96d56Sopenharmony_ci def set(self, lo, hi): 187db96d56Sopenharmony_ci if float(lo) > 0.0 or float(hi) < 1.0: 197db96d56Sopenharmony_ci self.grid() 207db96d56Sopenharmony_ci else: 217db96d56Sopenharmony_ci self.grid_remove() 227db96d56Sopenharmony_ci super().set(lo, hi) 237db96d56Sopenharmony_ci 247db96d56Sopenharmony_ci def pack(self, **kwargs): 257db96d56Sopenharmony_ci raise TclError(f'{self.__class__.__name__} does not support "pack"') 267db96d56Sopenharmony_ci 277db96d56Sopenharmony_ci def place(self, **kwargs): 287db96d56Sopenharmony_ci raise TclError(f'{self.__class__.__name__} does not support "place"') 297db96d56Sopenharmony_ci 307db96d56Sopenharmony_ci 317db96d56Sopenharmony_ciclass ScrollableTextFrame(Frame): 327db96d56Sopenharmony_ci """Display text with scrollbar(s).""" 337db96d56Sopenharmony_ci 347db96d56Sopenharmony_ci def __init__(self, master, wrap=NONE, **kwargs): 357db96d56Sopenharmony_ci """Create a frame for Textview. 367db96d56Sopenharmony_ci 377db96d56Sopenharmony_ci master - master widget for this frame 387db96d56Sopenharmony_ci wrap - type of text wrapping to use ('word', 'char' or 'none') 397db96d56Sopenharmony_ci 407db96d56Sopenharmony_ci All parameters except for 'wrap' are passed to Frame.__init__(). 417db96d56Sopenharmony_ci 427db96d56Sopenharmony_ci The Text widget is accessible via the 'text' attribute. 437db96d56Sopenharmony_ci 447db96d56Sopenharmony_ci Note: Changing the wrapping mode of the text widget after 457db96d56Sopenharmony_ci instantiation is not supported. 467db96d56Sopenharmony_ci """ 477db96d56Sopenharmony_ci super().__init__(master, **kwargs) 487db96d56Sopenharmony_ci 497db96d56Sopenharmony_ci text = self.text = Text(self, wrap=wrap) 507db96d56Sopenharmony_ci text.grid(row=0, column=0, sticky=NSEW) 517db96d56Sopenharmony_ci self.grid_rowconfigure(0, weight=1) 527db96d56Sopenharmony_ci self.grid_columnconfigure(0, weight=1) 537db96d56Sopenharmony_ci 547db96d56Sopenharmony_ci # vertical scrollbar 557db96d56Sopenharmony_ci self.yscroll = AutoHideScrollbar(self, orient=VERTICAL, 567db96d56Sopenharmony_ci takefocus=False, 577db96d56Sopenharmony_ci command=text.yview) 587db96d56Sopenharmony_ci self.yscroll.grid(row=0, column=1, sticky=NS) 597db96d56Sopenharmony_ci text['yscrollcommand'] = self.yscroll.set 607db96d56Sopenharmony_ci 617db96d56Sopenharmony_ci # horizontal scrollbar - only when wrap is set to NONE 627db96d56Sopenharmony_ci if wrap == NONE: 637db96d56Sopenharmony_ci self.xscroll = AutoHideScrollbar(self, orient=HORIZONTAL, 647db96d56Sopenharmony_ci takefocus=False, 657db96d56Sopenharmony_ci command=text.xview) 667db96d56Sopenharmony_ci self.xscroll.grid(row=1, column=0, sticky=EW) 677db96d56Sopenharmony_ci text['xscrollcommand'] = self.xscroll.set 687db96d56Sopenharmony_ci else: 697db96d56Sopenharmony_ci self.xscroll = None 707db96d56Sopenharmony_ci 717db96d56Sopenharmony_ci 727db96d56Sopenharmony_ciclass ViewFrame(Frame): 737db96d56Sopenharmony_ci "Display TextFrame and Close button." 747db96d56Sopenharmony_ci def __init__(self, parent, contents, wrap='word'): 757db96d56Sopenharmony_ci """Create a frame for viewing text with a "Close" button. 767db96d56Sopenharmony_ci 777db96d56Sopenharmony_ci parent - parent widget for this frame 787db96d56Sopenharmony_ci contents - text to display 797db96d56Sopenharmony_ci wrap - type of text wrapping to use ('word', 'char' or 'none') 807db96d56Sopenharmony_ci 817db96d56Sopenharmony_ci The Text widget is accessible via the 'text' attribute. 827db96d56Sopenharmony_ci """ 837db96d56Sopenharmony_ci super().__init__(parent) 847db96d56Sopenharmony_ci self.parent = parent 857db96d56Sopenharmony_ci self.bind('<Return>', self.ok) 867db96d56Sopenharmony_ci self.bind('<Escape>', self.ok) 877db96d56Sopenharmony_ci self.textframe = ScrollableTextFrame(self, relief=SUNKEN, height=700) 887db96d56Sopenharmony_ci 897db96d56Sopenharmony_ci text = self.text = self.textframe.text 907db96d56Sopenharmony_ci text.insert('1.0', contents) 917db96d56Sopenharmony_ci text.configure(wrap=wrap, highlightthickness=0, state='disabled') 927db96d56Sopenharmony_ci color_config(text) 937db96d56Sopenharmony_ci text.focus_set() 947db96d56Sopenharmony_ci 957db96d56Sopenharmony_ci self.button_ok = button_ok = Button( 967db96d56Sopenharmony_ci self, text='Close', command=self.ok, takefocus=False) 977db96d56Sopenharmony_ci self.textframe.pack(side='top', expand=True, fill='both') 987db96d56Sopenharmony_ci button_ok.pack(side='bottom') 997db96d56Sopenharmony_ci 1007db96d56Sopenharmony_ci def ok(self, event=None): 1017db96d56Sopenharmony_ci """Dismiss text viewer dialog.""" 1027db96d56Sopenharmony_ci self.parent.destroy() 1037db96d56Sopenharmony_ci 1047db96d56Sopenharmony_ci 1057db96d56Sopenharmony_ciclass ViewWindow(Toplevel): 1067db96d56Sopenharmony_ci "A simple text viewer dialog for IDLE." 1077db96d56Sopenharmony_ci 1087db96d56Sopenharmony_ci def __init__(self, parent, title, contents, modal=True, wrap=WORD, 1097db96d56Sopenharmony_ci *, _htest=False, _utest=False): 1107db96d56Sopenharmony_ci """Show the given text in a scrollable window with a 'close' button. 1117db96d56Sopenharmony_ci 1127db96d56Sopenharmony_ci If modal is left True, users cannot interact with other windows 1137db96d56Sopenharmony_ci until the textview window is closed. 1147db96d56Sopenharmony_ci 1157db96d56Sopenharmony_ci parent - parent of this dialog 1167db96d56Sopenharmony_ci title - string which is title of popup dialog 1177db96d56Sopenharmony_ci contents - text to display in dialog 1187db96d56Sopenharmony_ci wrap - type of text wrapping to use ('word', 'char' or 'none') 1197db96d56Sopenharmony_ci _htest - bool; change box location when running htest. 1207db96d56Sopenharmony_ci _utest - bool; don't wait_window when running unittest. 1217db96d56Sopenharmony_ci """ 1227db96d56Sopenharmony_ci super().__init__(parent) 1237db96d56Sopenharmony_ci self['borderwidth'] = 5 1247db96d56Sopenharmony_ci # Place dialog below parent if running htest. 1257db96d56Sopenharmony_ci x = parent.winfo_rootx() + 10 1267db96d56Sopenharmony_ci y = parent.winfo_rooty() + (10 if not _htest else 100) 1277db96d56Sopenharmony_ci self.geometry(f'=750x500+{x}+{y}') 1287db96d56Sopenharmony_ci 1297db96d56Sopenharmony_ci self.title(title) 1307db96d56Sopenharmony_ci self.viewframe = ViewFrame(self, contents, wrap=wrap) 1317db96d56Sopenharmony_ci self.protocol("WM_DELETE_WINDOW", self.ok) 1327db96d56Sopenharmony_ci self.button_ok = button_ok = Button(self, text='Close', 1337db96d56Sopenharmony_ci command=self.ok, takefocus=False) 1347db96d56Sopenharmony_ci self.viewframe.pack(side='top', expand=True, fill='both') 1357db96d56Sopenharmony_ci 1367db96d56Sopenharmony_ci self.is_modal = modal 1377db96d56Sopenharmony_ci if self.is_modal: 1387db96d56Sopenharmony_ci self.transient(parent) 1397db96d56Sopenharmony_ci self.grab_set() 1407db96d56Sopenharmony_ci if not _utest: 1417db96d56Sopenharmony_ci self.wait_window() 1427db96d56Sopenharmony_ci 1437db96d56Sopenharmony_ci def ok(self, event=None): 1447db96d56Sopenharmony_ci """Dismiss text viewer dialog.""" 1457db96d56Sopenharmony_ci if self.is_modal: 1467db96d56Sopenharmony_ci self.grab_release() 1477db96d56Sopenharmony_ci self.destroy() 1487db96d56Sopenharmony_ci 1497db96d56Sopenharmony_ci 1507db96d56Sopenharmony_cidef view_text(parent, title, contents, modal=True, wrap='word', _utest=False): 1517db96d56Sopenharmony_ci """Create text viewer for given text. 1527db96d56Sopenharmony_ci 1537db96d56Sopenharmony_ci parent - parent of this dialog 1547db96d56Sopenharmony_ci title - string which is the title of popup dialog 1557db96d56Sopenharmony_ci contents - text to display in this dialog 1567db96d56Sopenharmony_ci wrap - type of text wrapping to use ('word', 'char' or 'none') 1577db96d56Sopenharmony_ci modal - controls if users can interact with other windows while this 1587db96d56Sopenharmony_ci dialog is displayed 1597db96d56Sopenharmony_ci _utest - bool; controls wait_window on unittest 1607db96d56Sopenharmony_ci """ 1617db96d56Sopenharmony_ci return ViewWindow(parent, title, contents, modal, wrap=wrap, _utest=_utest) 1627db96d56Sopenharmony_ci 1637db96d56Sopenharmony_ci 1647db96d56Sopenharmony_cidef view_file(parent, title, filename, encoding, modal=True, wrap='word', 1657db96d56Sopenharmony_ci _utest=False): 1667db96d56Sopenharmony_ci """Create text viewer for text in filename. 1677db96d56Sopenharmony_ci 1687db96d56Sopenharmony_ci Return error message if file cannot be read. Otherwise calls view_text 1697db96d56Sopenharmony_ci with contents of the file. 1707db96d56Sopenharmony_ci """ 1717db96d56Sopenharmony_ci try: 1727db96d56Sopenharmony_ci with open(filename, encoding=encoding) as file: 1737db96d56Sopenharmony_ci contents = file.read() 1747db96d56Sopenharmony_ci except OSError: 1757db96d56Sopenharmony_ci showerror(title='File Load Error', 1767db96d56Sopenharmony_ci message=f'Unable to load file {filename!r} .', 1777db96d56Sopenharmony_ci parent=parent) 1787db96d56Sopenharmony_ci except UnicodeDecodeError as err: 1797db96d56Sopenharmony_ci showerror(title='Unicode Decode Error', 1807db96d56Sopenharmony_ci message=str(err), 1817db96d56Sopenharmony_ci parent=parent) 1827db96d56Sopenharmony_ci else: 1837db96d56Sopenharmony_ci return view_text(parent, title, contents, modal, wrap=wrap, 1847db96d56Sopenharmony_ci _utest=_utest) 1857db96d56Sopenharmony_ci return None 1867db96d56Sopenharmony_ci 1877db96d56Sopenharmony_ci 1887db96d56Sopenharmony_ciif __name__ == '__main__': 1897db96d56Sopenharmony_ci from unittest import main 1907db96d56Sopenharmony_ci main('idlelib.idle_test.test_textview', verbosity=2, exit=False) 1917db96d56Sopenharmony_ci 1927db96d56Sopenharmony_ci from idlelib.idle_test.htest import run 1937db96d56Sopenharmony_ci run(ViewWindow) 194