1""" help.py: Implement the Idle help menu. 2Contents are subject to revision at any time, without notice. 3 4 5Help => About IDLE: diplay About Idle dialog 6 7<to be moved here from aboutDialog.py> 8 9 10Help => IDLE Help: Display help.html with proper formatting. 11Doc/library/idle.rst (Sphinx)=> Doc/build/html/library/idle.html 12(help.copy_strip)=> Lib/idlelib/help.html 13 14HelpParser - Parse help.html and render to tk Text. 15 16HelpText - Display formatted help.html. 17 18HelpFrame - Contain text, scrollbar, and table-of-contents. 19(This will be needed for display in a future tabbed window.) 20 21HelpWindow - Display HelpFrame in a standalone window. 22 23copy_strip - Copy idle.html to help.html, rstripping each line. 24 25show_idlehelp - Create HelpWindow. Called in EditorWindow.help_dialog. 26""" 27from HTMLParser import HTMLParser 28from os.path import abspath, dirname, isdir, isfile, join 29from platform import python_version 30from Tkinter import Tk, Toplevel, Frame, Text, Scrollbar, Menu, Menubutton 31import tkFont as tkfont 32from idlelib.configHandler import idleConf 33 34use_ttk = False # until available to import 35if use_ttk: 36 from tkinter.ttk import Menubutton 37 38## About IDLE ## 39 40 41## IDLE Help ## 42 43class 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 158class 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 200class 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 224class 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 235def 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 267def 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 275if __name__ == '__main__': 276 from idlelib.idle_test.htest import run 277 run(show_idlehelp) 278