Home | History | Annotate | Download | only in idlelib
      1 """ help.py: Implement the Idle help menu.
      2 Contents are subject to revision at any time, without notice.
      3 
      4 
      5 Help => About IDLE: diplay About Idle dialog
      6 
      7 <to be moved here from aboutDialog.py>
      8 
      9 
     10 Help => IDLE Help: Display help.html with proper formatting.
     11 Doc/library/idle.rst (Sphinx)=> Doc/build/html/library/idle.html
     12 (help.copy_strip)=> Lib/idlelib/help.html
     13 
     14 HelpParser - Parse help.html and render to tk Text.
     15 
     16 HelpText - Display formatted help.html.
     17 
     18 HelpFrame - Contain text, scrollbar, and table-of-contents.
     19 (This will be needed for display in a future tabbed window.)
     20 
     21 HelpWindow - Display HelpFrame in a standalone window.
     22 
     23 copy_strip - Copy idle.html to help.html, rstripping each line.
     24 
     25 show_idlehelp - Create HelpWindow.  Called in EditorWindow.help_dialog.
     26 """
     27 from HTMLParser import HTMLParser
     28 from os.path import abspath, dirname, isdir, isfile, join
     29 from platform import python_version
     30 from Tkinter import Tk, Toplevel, Frame, Text, Scrollbar, Menu, Menubutton
     31 import tkFont as tkfont
     32 from idlelib.configHandler import idleConf
     33 
     34 use_ttk = False # until available to import
     35 if use_ttk:
     36     from tkinter.ttk import Menubutton
     37 
     38 ## About IDLE ##
     39 
     40 
     41 ## IDLE Help ##
     42 
     43 class HelpParser(HTMLParser):
     44     """Render help.html into a text widget.
     45 
     46     The overridden handle_xyz methods handle a subset of html tags.
     47     The supplied text should have the needed tag configurations.
     48     The behavior for unsupported tags, such as table, is undefined.
     49     If the tags generated by Sphinx change, this class, especially
     50     the handle_starttag and handle_endtags methods, might have to also.
     51     """
     52     def __init__(self, text):
     53         HTMLParser.__init__(self)
     54         self.text = text         # text widget we're rendering into
     55         self.tags = ''           # current block level text tags to apply
     56         self.chartags = ''       # current character level text tags
     57         self.show = False        # used so we exclude page navigation
     58         self.hdrlink = False     # used so we don't show header links
     59         self.level = 0           # indentation level
     60         self.pre = False         # displaying preformatted text
     61         self.hprefix = ''        # prefix such as '25.5' to strip from headings
     62         self.nested_dl = False   # if we're in a nested <dl>
     63         self.simplelist = False  # simple list (no double spacing)
     64         self.toc = []            # pair headers with text indexes for toc
     65         self.header = ''         # text within header tags for toc
     66 
     67     def indent(self, amt=1):
     68         self.level += amt
     69         self.tags = '' if self.level == 0 else 'l'+str(self.level)
     70 
     71     def handle_starttag(self, tag, attrs):
     72         "Handle starttags in help.html."
     73         class_ = ''
     74         for a, v in attrs:
     75             if a == 'class':
     76                 class_ = v
     77         s = ''
     78         if tag == 'div' and class_ == 'section':
     79             self.show = True    # start of main content
     80         elif tag == 'div' and class_ == 'sphinxsidebar':
     81             self.show = False   # end of main content
     82         elif tag == 'p' and class_ != 'first':
     83             s = '\n\n'
     84         elif tag == 'span' and class_ == 'pre':
     85             self.chartags = 'pre'
     86         elif tag == 'span' and class_ == 'versionmodified':
     87             self.chartags = 'em'
     88         elif tag == 'em':
     89             self.chartags = 'em'
     90         elif tag in ['ul', 'ol']:
     91             if class_.find('simple') != -1:
     92                 s = '\n'
     93                 self.simplelist = True
     94             else:
     95                 self.simplelist = False
     96             self.indent()
     97         elif tag == 'dl':
     98             if self.level > 0:
     99                 self.nested_dl = True
    100         elif tag == 'li':
    101             s = '\n* ' if self.simplelist else '\n\n* '
    102         elif tag == 'dt':
    103             s = '\n\n' if not self.nested_dl else '\n'  # avoid extra line
    104             self.nested_dl = False
    105         elif tag == 'dd':
    106             self.indent()
    107             s = '\n'
    108         elif tag == 'pre':
    109             self.pre = True
    110             if self.show:
    111                 self.text.insert('end', '\n\n')
    112             self.tags = 'preblock'
    113         elif tag == 'a' and class_ == 'headerlink':
    114             self.hdrlink = True
    115         elif tag == 'h1':
    116             self.tags = tag
    117         elif tag in ['h2', 'h3']:
    118             if self.show:
    119                 self.header = ''
    120                 self.text.insert('end', '\n\n')
    121             self.tags = tag
    122         if self.show:
    123             self.text.insert('end', s, (self.tags, self.chartags))
    124 
    125     def handle_endtag(self, tag):
    126         "Handle endtags in help.html."
    127         if tag in ['h1', 'h2', 'h3']:
    128             self.indent(0)  # clear tag, reset indent
    129             if self.show:
    130                 self.toc.append((self.header, self.text.index('insert')))
    131         elif tag in ['span', 'em']:
    132             self.chartags = ''
    133         elif tag == 'a':
    134             self.hdrlink = False
    135         elif tag == 'pre':
    136             self.pre = False
    137             self.tags = ''
    138         elif tag in ['ul', 'dd', 'ol']:
    139             self.indent(amt=-1)
    140 
    141     def handle_data(self, data):
    142         "Handle date segments in help.html."
    143         if self.show and not self.hdrlink:
    144             d = data if self.pre else data.replace('\n', ' ')
    145             if self.tags == 'h1':
    146                 self.hprefix = d[0:d.index(' ')]
    147             if self.tags in ['h1', 'h2', 'h3'] and self.hprefix != '':
    148                 if d[0:len(self.hprefix)] == self.hprefix:
    149                     d = d[len(self.hprefix):].strip()
    150                 self.header += d
    151             self.text.insert('end', d, (self.tags, self.chartags))
    152 
    153     def handle_charref(self, name):
    154         if self.show:
    155             self.text.insert('end', unichr(int(name)))
    156 
    157 
    158 class HelpText(Text):
    159     "Display help.html."
    160     def __init__(self, parent, filename):
    161         "Configure tags and feed file to parser."
    162         uwide = idleConf.GetOption('main', 'EditorWindow', 'width', type='int')
    163         uhigh = idleConf.GetOption('main', 'EditorWindow', 'height', type='int')
    164         uhigh = 3 * uhigh // 4  # lines average 4/3 of editor line height
    165         Text.__init__(self, parent, wrap='word', highlightthickness=0,
    166                       padx=5, borderwidth=0, width=uwide, height=uhigh)
    167 
    168         normalfont = self.findfont(['TkDefaultFont', 'arial', 'helvetica'])
    169         fixedfont = self.findfont(['TkFixedFont', 'monaco', 'courier'])
    170         self['font'] = (normalfont, 12)
    171         self.tag_configure('em', font=(normalfont, 12, 'italic'))
    172         self.tag_configure('h1', font=(normalfont, 20, 'bold'))
    173         self.tag_configure('h2', font=(normalfont, 18, 'bold'))
    174         self.tag_configure('h3', font=(normalfont, 15, 'bold'))
    175         self.tag_configure('pre', font=(fixedfont, 12), background='#f6f6ff')
    176         self.tag_configure('preblock', font=(fixedfont, 10), lmargin1=25,
    177                 borderwidth=1, relief='solid', background='#eeffcc')
    178         self.tag_configure('l1', lmargin1=25, lmargin2=25)
    179         self.tag_configure('l2', lmargin1=50, lmargin2=50)
    180         self.tag_configure('l3', lmargin1=75, lmargin2=75)
    181         self.tag_configure('l4', lmargin1=100, lmargin2=100)
    182 
    183         self.parser = HelpParser(self)
    184         with open(filename) as f:
    185             contents = f.read().decode(encoding='utf-8')
    186         self.parser.feed(contents)
    187         self['state'] = 'disabled'
    188 
    189     def findfont(self, names):
    190         "Return name of first font family derived from names."
    191         for name in names:
    192             if name.lower() in (x.lower() for x in tkfont.names(root=self)):
    193                 font = tkfont.Font(name=name, exists=True, root=self)
    194                 return font.actual()['family']
    195             elif name.lower() in (x.lower()
    196                                   for x in tkfont.families(root=self)):
    197                 return name
    198 
    199 
    200 class HelpFrame(Frame):
    201     "Display html text, scrollbar, and toc."
    202     def __init__(self, parent, filename):
    203         Frame.__init__(self, parent)
    204         text = HelpText(self, filename)
    205         self['background'] = text['background']
    206         scroll = Scrollbar(self, command=text.yview)
    207         text['yscrollcommand'] = scroll.set
    208         self.rowconfigure(0, weight=1)
    209         self.columnconfigure(1, weight=1)  # text
    210         self.toc_menu(text).grid(column=0, row=0, sticky='nw')
    211         text.grid(column=1, row=0, sticky='nsew')
    212         scroll.grid(column=2, row=0, sticky='ns')
    213 
    214     def toc_menu(self, text):
    215         "Create table of contents as drop-down menu."
    216         toc = Menubutton(self, text='TOC')
    217         drop = Menu(toc, tearoff=False)
    218         for lbl, dex in text.parser.toc:
    219             drop.add_command(label=lbl, command=lambda dex=dex:text.yview(dex))
    220         toc['menu'] = drop
    221         return toc
    222 
    223 
    224 class HelpWindow(Toplevel):
    225     "Display frame with rendered html."
    226     def __init__(self, parent, filename, title):
    227         Toplevel.__init__(self, parent)
    228         self.wm_title(title)
    229         self.protocol("WM_DELETE_WINDOW", self.destroy)
    230         HelpFrame(self, filename).grid(column=0, row=0, sticky='nsew')
    231         self.grid_columnconfigure(0, weight=1)
    232         self.grid_rowconfigure(0, weight=1)
    233 
    234 
    235 def copy_strip():
    236     """Copy idle.html to idlelib/help.html, stripping trailing whitespace.
    237 
    238     Files with trailing whitespace cannot be pushed to the hg cpython
    239     repository.  For 3.x (on Windows), help.html is generated, after
    240     editing idle.rst in the earliest maintenance version, with
    241       sphinx-build -bhtml . build/html
    242       python_d.exe -c "from idlelib.help import copy_strip; copy_strip()"
    243     After refreshing TortoiseHG workshop to generate a diff,
    244     check  both the diff and displayed text.  Push the diff along with
    245     the idle.rst change and merge both into default (or an intermediate
    246     maintenance version).
    247 
    248     When the 'earlist' version gets its final maintenance release,
    249     do an update as described above, without editing idle.rst, to
    250     rebase help.html on the next version of idle.rst.  Do not worry
    251     about version changes as version is not displayed.  Examine other
    252     changes and the result of Help -> IDLE Help.
    253 
    254     If maintenance and default versions of idle.rst diverge, and
    255     merging does not go smoothly, then consider generating
    256     separate help.html files from separate idle.htmls.
    257     """
    258     src = join(abspath(dirname(dirname(dirname(__file__)))),
    259                'Doc', 'build', 'html', 'library', 'idle.html')
    260     dst = join(abspath(dirname(__file__)), 'help.html')
    261     with open(src, 'r') as inn,\
    262          open(dst, 'w') as out:
    263         for line in inn:
    264             out.write(line.rstrip() + '\n')
    265     print('idle.html copied to help.html')
    266 
    267 def show_idlehelp(parent):
    268     "Create HelpWindow; called from Idle Help event handler."
    269     filename = join(abspath(dirname(__file__)), 'help.html')
    270     if not isfile(filename):
    271         # try copy_strip, present message
    272         return
    273     HelpWindow(parent, filename, 'IDLE Help (%s)' % python_version())
    274 
    275 if __name__ == '__main__':
    276     from idlelib.idle_test.htest import run
    277     run(show_idlehelp)
    278