blob: 24665a82e496464912e50a1ba399061d2cffb623 [file] [log] [blame]
Terry Jan Reedycba1a1a2015-09-21 22:36:42 -04001""" help.py: Implement the Idle help menu.
2Contents are subject to revision at any time, without notice.
Terry Jan Reedy5d46ab12015-09-20 19:57:13 -04003
Terry Jan Reedy5d46ab12015-09-20 19:57:13 -04004
5Help => About IDLE: diplay About Idle dialog
6
7<to be moved here from aboutDialog.py>
8
Terry Jan Reedy5d46ab12015-09-20 19:57:13 -04009
Terry Jan Reedycba1a1a2015-09-21 22:36:42 -040010Help => 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
Terry Jan Reedy5d46ab12015-09-20 19:57:13 -040013
Martin Panter96a4f072016-02-10 01:17:51 +000014HelpParser - Parse help.html and render to tk Text.
Terry Jan Reedy5d46ab12015-09-20 19:57:13 -040015
Terry Jan Reedycba1a1a2015-09-21 22:36:42 -040016HelpText - Display formatted help.html.
17
18HelpFrame - Contain text, scrollbar, and table-of-contents.
Terry Jan Reedy5d46ab12015-09-20 19:57:13 -040019(This will be needed for display in a future tabbed window.)
20
Terry Jan Reedycba1a1a2015-09-21 22:36:42 -040021HelpWindow - Display HelpFrame in a standalone window.
22
23copy_strip - Copy idle.html to help.html, rstripping each line.
Terry Jan Reedy5d46ab12015-09-20 19:57:13 -040024
25show_idlehelp - Create HelpWindow. Called in EditorWindow.help_dialog.
26"""
27from html.parser import HTMLParser
28from os.path import abspath, dirname, isdir, isfile, join
29from tkinter import Tk, Toplevel, Frame, Text, Scrollbar, Menu, Menubutton
30from tkinter import font as tkfont
Terry Jan Reedy52736dd2015-09-25 00:49:18 -040031from idlelib.configHandler import idleConf
Terry Jan Reedy5d46ab12015-09-20 19:57:13 -040032
33use_ttk = False # until available to import
34if use_ttk:
35 from tkinter.ttk import Menubutton
36
37## About IDLE ##
38
39
40## IDLE Help ##
41
42class HelpParser(HTMLParser):
Terry Jan Reedycba1a1a2015-09-21 22:36:42 -040043 """Render help.html into a text widget.
Terry Jan Reedy5d46ab12015-09-20 19:57:13 -040044
45 The overridden handle_xyz methods handle a subset of html tags.
46 The supplied text should have the needed tag configurations.
47 The behavior for unsupported tags, such as table, is undefined.
48 """
49 def __init__(self, text):
50 HTMLParser.__init__(self, convert_charrefs=True)
51 self.text = text # text widget we're rendering into
Terry Jan Reedy974a2712015-09-24 17:32:01 -040052 self.tags = '' # current block level text tags to apply
53 self.chartags = '' # current character level text tags
Terry Jan Reedy5d46ab12015-09-20 19:57:13 -040054 self.show = False # used so we exclude page navigation
55 self.hdrlink = False # used so we don't show header links
56 self.level = 0 # indentation level
57 self.pre = False # displaying preformatted text
Terry Jan Reedy28670d12015-09-27 04:40:08 -040058 self.hprefix = '' # prefix such as '25.5' to strip from headings
Terry Jan Reedy5d46ab12015-09-20 19:57:13 -040059 self.nested_dl = False # if we're in a nested <dl>
60 self.simplelist = False # simple list (no double spacing)
Terry Jan Reedy28670d12015-09-27 04:40:08 -040061 self.toc = [] # pair headers with text indexes for toc
62 self.header = '' # text within header tags for toc
Terry Jan Reedy5d46ab12015-09-20 19:57:13 -040063
64 def indent(self, amt=1):
65 self.level += amt
66 self.tags = '' if self.level == 0 else 'l'+str(self.level)
67
68 def handle_starttag(self, tag, attrs):
Terry Jan Reedycba1a1a2015-09-21 22:36:42 -040069 "Handle starttags in help.html."
Terry Jan Reedy5d46ab12015-09-20 19:57:13 -040070 class_ = ''
71 for a, v in attrs:
72 if a == 'class':
73 class_ = v
74 s = ''
75 if tag == 'div' and class_ == 'section':
76 self.show = True # start of main content
77 elif tag == 'div' and class_ == 'sphinxsidebar':
78 self.show = False # end of main content
79 elif tag == 'p' and class_ != 'first':
80 s = '\n\n'
81 elif tag == 'span' and class_ == 'pre':
Terry Jan Reedy974a2712015-09-24 17:32:01 -040082 self.chartags = 'pre'
Terry Jan Reedy5d46ab12015-09-20 19:57:13 -040083 elif tag == 'span' and class_ == 'versionmodified':
Terry Jan Reedy974a2712015-09-24 17:32:01 -040084 self.chartags = 'em'
Terry Jan Reedy5d46ab12015-09-20 19:57:13 -040085 elif tag == 'em':
Terry Jan Reedy974a2712015-09-24 17:32:01 -040086 self.chartags = 'em'
Terry Jan Reedy5d46ab12015-09-20 19:57:13 -040087 elif tag in ['ul', 'ol']:
88 if class_.find('simple') != -1:
89 s = '\n'
90 self.simplelist = True
91 else:
92 self.simplelist = False
93 self.indent()
94 elif tag == 'dl':
95 if self.level > 0:
96 self.nested_dl = True
97 elif tag == 'li':
98 s = '\n* ' if self.simplelist else '\n\n* '
99 elif tag == 'dt':
100 s = '\n\n' if not self.nested_dl else '\n' # avoid extra line
101 self.nested_dl = False
102 elif tag == 'dd':
103 self.indent()
104 s = '\n'
105 elif tag == 'pre':
106 self.pre = True
107 if self.show:
108 self.text.insert('end', '\n\n')
109 self.tags = 'preblock'
110 elif tag == 'a' and class_ == 'headerlink':
111 self.hdrlink = True
112 elif tag == 'h1':
Terry Jan Reedy5d46ab12015-09-20 19:57:13 -0400113 self.tags = tag
114 elif tag in ['h2', 'h3']:
115 if self.show:
Terry Jan Reedy28670d12015-09-27 04:40:08 -0400116 self.header = ''
Terry Jan Reedy5d46ab12015-09-20 19:57:13 -0400117 self.text.insert('end', '\n\n')
118 self.tags = tag
119 if self.show:
Terry Jan Reedy974a2712015-09-24 17:32:01 -0400120 self.text.insert('end', s, (self.tags, self.chartags))
Terry Jan Reedy5d46ab12015-09-20 19:57:13 -0400121
122 def handle_endtag(self, tag):
Terry Jan Reedycba1a1a2015-09-21 22:36:42 -0400123 "Handle endtags in help.html."
Terry Jan Reedy974a2712015-09-24 17:32:01 -0400124 if tag in ['h1', 'h2', 'h3']:
Terry Jan Reedy5d46ab12015-09-20 19:57:13 -0400125 self.indent(0) # clear tag, reset indent
Terry Jan Reedy28670d12015-09-27 04:40:08 -0400126 if self.show:
127 self.toc.append((self.header, self.text.index('insert')))
Terry Jan Reedy974a2712015-09-24 17:32:01 -0400128 elif tag in ['span', 'em']:
129 self.chartags = ''
Terry Jan Reedy5d46ab12015-09-20 19:57:13 -0400130 elif tag == 'a':
131 self.hdrlink = False
132 elif tag == 'pre':
133 self.pre = False
134 self.tags = ''
135 elif tag in ['ul', 'dd', 'ol']:
136 self.indent(amt=-1)
137
138 def handle_data(self, data):
Terry Jan Reedycba1a1a2015-09-21 22:36:42 -0400139 "Handle date segments in help.html."
Terry Jan Reedy5d46ab12015-09-20 19:57:13 -0400140 if self.show and not self.hdrlink:
141 d = data if self.pre else data.replace('\n', ' ')
142 if self.tags == 'h1':
143 self.hprefix = d[0:d.index(' ')]
144 if self.tags in ['h1', 'h2', 'h3'] and self.hprefix != '':
145 if d[0:len(self.hprefix)] == self.hprefix:
146 d = d[len(self.hprefix):].strip()
Terry Jan Reedy28670d12015-09-27 04:40:08 -0400147 self.header += d
Terry Jan Reedy974a2712015-09-24 17:32:01 -0400148 self.text.insert('end', d, (self.tags, self.chartags))
Terry Jan Reedy5d46ab12015-09-20 19:57:13 -0400149
150
151class HelpText(Text):
Terry Jan Reedycba1a1a2015-09-21 22:36:42 -0400152 "Display help.html."
Terry Jan Reedy5d46ab12015-09-20 19:57:13 -0400153 def __init__(self, parent, filename):
154 "Configure tags and feed file to parser."
Terry Jan Reedy52736dd2015-09-25 00:49:18 -0400155 uwide = idleConf.GetOption('main', 'EditorWindow', 'width', type='int')
156 uhigh = idleConf.GetOption('main', 'EditorWindow', 'height', type='int')
157 uhigh = 3 * uhigh // 4 # lines average 4/3 of editor line height
Terry Jan Reedy5d46ab12015-09-20 19:57:13 -0400158 Text.__init__(self, parent, wrap='word', highlightthickness=0,
Terry Jan Reedy52736dd2015-09-25 00:49:18 -0400159 padx=5, borderwidth=0, width=uwide, height=uhigh)
Terry Jan Reedy5d46ab12015-09-20 19:57:13 -0400160
161 normalfont = self.findfont(['TkDefaultFont', 'arial', 'helvetica'])
162 fixedfont = self.findfont(['TkFixedFont', 'monaco', 'courier'])
163 self['font'] = (normalfont, 12)
164 self.tag_configure('em', font=(normalfont, 12, 'italic'))
165 self.tag_configure('h1', font=(normalfont, 20, 'bold'))
166 self.tag_configure('h2', font=(normalfont, 18, 'bold'))
167 self.tag_configure('h3', font=(normalfont, 15, 'bold'))
Terry Jan Reedy974a2712015-09-24 17:32:01 -0400168 self.tag_configure('pre', font=(fixedfont, 12), background='#f6f6ff')
Terry Jan Reedy5d46ab12015-09-20 19:57:13 -0400169 self.tag_configure('preblock', font=(fixedfont, 10), lmargin1=25,
170 borderwidth=1, relief='solid', background='#eeffcc')
171 self.tag_configure('l1', lmargin1=25, lmargin2=25)
172 self.tag_configure('l2', lmargin1=50, lmargin2=50)
173 self.tag_configure('l3', lmargin1=75, lmargin2=75)
174 self.tag_configure('l4', lmargin1=100, lmargin2=100)
175
176 self.parser = HelpParser(self)
177 with open(filename, encoding='utf-8') as f:
178 contents = f.read()
179 self.parser.feed(contents)
180 self['state'] = 'disabled'
181
182 def findfont(self, names):
183 "Return name of first font family derived from names."
184 for name in names:
185 if name.lower() in (x.lower() for x in tkfont.names(root=self)):
186 font = tkfont.Font(name=name, exists=True, root=self)
187 return font.actual()['family']
188 elif name.lower() in (x.lower()
189 for x in tkfont.families(root=self)):
190 return name
191
192
193class HelpFrame(Frame):
Terry Jan Reedycba1a1a2015-09-21 22:36:42 -0400194 "Display html text, scrollbar, and toc."
Terry Jan Reedy5d46ab12015-09-20 19:57:13 -0400195 def __init__(self, parent, filename):
196 Frame.__init__(self, parent)
197 text = HelpText(self, filename)
198 self['background'] = text['background']
199 scroll = Scrollbar(self, command=text.yview)
200 text['yscrollcommand'] = scroll.set
Terry Jan Reedy28670d12015-09-27 04:40:08 -0400201 self.rowconfigure(0, weight=1)
202 self.columnconfigure(1, weight=1) # text
203 self.toc_menu(text).grid(column=0, row=0, sticky='nw')
Terry Jan Reedy5d46ab12015-09-20 19:57:13 -0400204 text.grid(column=1, row=0, sticky='nsew')
205 scroll.grid(column=2, row=0, sticky='ns')
Terry Jan Reedy5d46ab12015-09-20 19:57:13 -0400206
Terry Jan Reedy28670d12015-09-27 04:40:08 -0400207 def toc_menu(self, text):
208 "Create table of contents as drop-down menu."
Terry Jan Reedy5d46ab12015-09-20 19:57:13 -0400209 toc = Menubutton(self, text='TOC')
210 drop = Menu(toc, tearoff=False)
Terry Jan Reedy28670d12015-09-27 04:40:08 -0400211 for lbl, dex in text.parser.toc:
212 drop.add_command(label=lbl, command=lambda dex=dex:text.yview(dex))
Terry Jan Reedy5d46ab12015-09-20 19:57:13 -0400213 toc['menu'] = drop
214 return toc
215
216
217class HelpWindow(Toplevel):
Terry Jan Reedycba1a1a2015-09-21 22:36:42 -0400218 "Display frame with rendered html."
Terry Jan Reedy5d46ab12015-09-20 19:57:13 -0400219 def __init__(self, parent, filename, title):
220 Toplevel.__init__(self, parent)
221 self.wm_title(title)
222 self.protocol("WM_DELETE_WINDOW", self.destroy)
223 HelpFrame(self, filename).grid(column=0, row=0, sticky='nsew')
224 self.grid_columnconfigure(0, weight=1)
225 self.grid_rowconfigure(0, weight=1)
226
227
Terry Jan Reedycba1a1a2015-09-21 22:36:42 -0400228def copy_strip():
229 "Copy idle.html to idlelib/help.html, stripping trailing whitespace."
230 src = join(abspath(dirname(dirname(dirname(__file__)))),
231 'Doc', 'build', 'html', 'library', 'idle.html')
232 dst = join(abspath(dirname(__file__)), 'help.html')
233 with open(src, 'rb') as inn,\
234 open(dst, 'wb') as out:
235 for line in inn:
Terry Jan Reedy6f5cdfe2015-09-23 03:45:13 -0400236 out.write(line.rstrip() + b'\n')
Terry Jan Reedycba1a1a2015-09-21 22:36:42 -0400237 print('idle.html copied to help.html')
238
Terry Jan Reedy5d46ab12015-09-20 19:57:13 -0400239def show_idlehelp(parent):
Terry Jan Reedycba1a1a2015-09-21 22:36:42 -0400240 "Create HelpWindow; called from Idle Help event handler."
241 filename = join(abspath(dirname(__file__)), 'help.html')
Terry Jan Reedy5d46ab12015-09-20 19:57:13 -0400242 if not isfile(filename):
Terry Jan Reedycba1a1a2015-09-21 22:36:42 -0400243 # try copy_strip, present message
Terry Jan Reedy364d6e12015-09-21 22:42:32 -0400244 return
Terry Jan Reedy5d46ab12015-09-20 19:57:13 -0400245 HelpWindow(parent, filename, 'IDLE Help')
246
247if __name__ == '__main__':
248 from idlelib.idle_test.htest import run
249 run(show_idlehelp)