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