Integrated Freestyle to rendering pipeline
[blender.git] / release / scripts / help_browser.py
1 #!BPY
2
3 """
4 Name: 'Scripts Help Browser'
5 Blender: 234
6 Group: 'Help'
7 Tooltip: 'Show help information about a chosen installed script.'
8 """
9
10 __author__ = "Willian P. Germano"
11 __version__ = "0.3 01/21/09"
12 __email__ = ('scripts', 'Author, wgermano:ig*com*br')
13 __url__ = ('blender', 'blenderartists.org')
14
15 __bpydoc__ ="""\
16 This script shows help information for scripts registered in the menus.
17
18 Usage:
19
20 - Start Screen:
21
22 To read any script's "user manual" select a script from one of the
23 available category menus.  If the script has help information in the format
24 expected by this Help Browser, it will be displayed in the Script Help
25 Screen.  Otherwise you'll be offered the possibility of loading the chosen
26 script's source file in Blender's Text Editor.  The programmer(s) may have
27 written useful comments there for users.
28
29 Hotkeys:<br>
30    ESC or Q: [Q]uit
31
32 - Script Help Screen:
33
34 This screen shows the user manual page for the chosen script. If the text
35 doesn't fit completely on the screen, you can scroll it up or down with
36 arrow keys or a mouse wheel.  There may be link and email buttons that if
37 clicked should open your default web browser and email client programs for
38 further information or support.
39
40 Hotkeys:<br>
41    ESC: back to Start Screen<br>
42    Q:   [Q]uit<br>
43    S:   view script's [S]ource code in Text Editor<br>
44    UP, DOWN Arrows and mouse wheel: scroll text up / down
45 """
46
47 # $Id$
48 #
49 # --------------------------------------------------------------------------
50 # ***** BEGIN GPL LICENSE BLOCK *****
51 #
52 # Copyright (C) 2004: Willian P. Germano, wgermano _at_ ig.com.br
53 #
54 # This program is free software; you can redistribute it and/or
55 # modify it under the terms of the GNU General Public License
56 # as published by the Free Software Foundation; either version 2
57 # of the License, or (at your option) any later version.
58 #
59 # This program is distributed in the hope that it will be useful,
60 # but WITHOUT ANY WARRANTY; without even the implied warranty of
61 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
62 # GNU General Public License for more details.
63 #
64 # You should have received a copy of the GNU General Public License
65 # along with this program; if not, write to the Free Software Foundation,
66 # Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
67 #
68 # ***** END GPL LICENCE BLOCK *****
69 # --------------------------------------------------------------------------
70 # Thanks: Brendon Murphy (suggestion) and Kevin Morgan (implementation)
71 # for the "run" button; Jean-Michel Soler for pointing a parsing error
72 # with multilines using triple single quotes.
73
74 import Blender
75 from Blender import sys as bsys, Draw, Window, Registry
76
77 WEBBROWSER = True
78 try:
79         import webbrowser
80 except:
81         WEBBROWSER = False
82
83 DEFAULT_EMAILS = {
84         'scripts': ['Bf-scripts-dev', 'bf-scripts-dev@blender.org']
85 }
86
87 DEFAULT_LINKS = {
88         'blender': ["blender.org\'s Python forum", "http://www.blender.org/modules.php?op=modload&name=phpBB2&file=viewforum&f=9"]
89 }
90
91 PADDING = 15
92 COLUMNS = 1
93 TEXT_WRAP = 100
94 WIN_W = WIN_H = 200
95 SCROLL_DOWN = 0
96
97 def screen_was_resized():
98         global WIN_W, WIN_H
99
100         w, h = Window.GetAreaSize()
101         if WIN_W != w or WIN_H != h:
102                 WIN_W = w
103                 WIN_H = h
104                 return True
105         return False
106
107 def fit_on_screen():
108         global TEXT_WRAP, PADDING, WIN_W, WIN_H, COLUMNS
109
110         COLUMNS = 1
111         WIN_W, WIN_H = Window.GetAreaSize()
112         TEXT_WRAP = int((WIN_W - PADDING) / 6)
113         if TEXT_WRAP < 40:
114                 TEXT_WRAP = 40
115         elif TEXT_WRAP > 100:
116                 if TEXT_WRAP > 110:
117                         COLUMNS = 2
118                         TEXT_WRAP /= 2
119                 else: TEXT_WRAP = 100
120
121 def cut_point(text, length):
122         "Returns position of the last space found before 'length' chars"
123         l = length
124         c = text[l]
125         while c != ' ':
126                 l -= 1
127                 if l == 0: return length # no space found
128                 c = text[l]
129         return l
130
131 def text_wrap(text, length = None):
132         global TEXT_WRAP
133
134         wrapped = []
135         lines = text.split('<br>')
136         llen = len(lines)
137         if llen > 1:
138                 if lines[-1] == '': llen -= 1
139                 for i in range(llen - 1):
140                         lines[i] = lines[i].rstrip() + '<br>'
141                 lines[llen-1] = lines[llen-1].rstrip()
142
143         if not length: length = TEXT_WRAP
144
145         for l in lines:
146                 while len(l) > length:
147                         cpt = cut_point(l, length)
148                         line, l = l[:cpt], l[cpt + 1:]
149                         wrapped.append(line)
150                 wrapped.append(l)
151         return wrapped
152
153 def load_script_text(script):
154         global PATHS, SCRIPT_INFO
155
156         if script.userdir:
157                 path = PATHS['uscripts']
158         else:
159                 path = PATHS['scripts']
160
161         fname = bsys.join(path, script.fname)
162
163         source = Blender.Text.Load(fname)
164         if source:
165                 Draw.PupMenu("File loaded%%t|Please check the file \"%s\" in the Text Editor window" % source.name)
166
167
168 # for theme colors:
169 def float_colors(cols):
170         return map(lambda x: x / 255.0, cols)
171
172 # globals
173
174 SCRIPT_INFO = None
175
176 PATHS = {
177         'home': Blender.Get('homedir'),
178         'scripts': Blender.Get('scriptsdir'),
179         'uscripts': Blender.Get('uscriptsdir')
180 }
181
182 if not PATHS['home']:
183         errmsg = """
184 Can't find Blender's home dir and so can't find the
185 Bpymenus file automatically stored inside it, which
186 is needed by this script.  Please run the
187 Help -> System -> System Information script to get
188 information about how to fix this.
189 """
190         raise SystemError, errmsg
191
192 BPYMENUS_FILE = bsys.join(PATHS['home'], 'Bpymenus')
193
194 f = file(BPYMENUS_FILE, 'r')
195 lines = f.readlines()
196 f.close()
197
198 AllGroups = []
199
200 class Script:
201
202         def __init__(self, data):
203                 self.name = data[0]
204                 self.version = data[1]
205                 self.fname = data[2]
206                 self.userdir = data[3]
207                 self.tip = data[4]
208
209 # End of class Script
210
211
212 class Group:
213
214         def __init__(self, name):
215                 self.name = name
216                 self.scripts = []
217
218         def add_script(self, script):
219                 self.scripts.append(script)
220
221         def get_name(self):
222                 return self.name
223
224         def get_scripts(self):
225                 return self.scripts
226
227 # End of class Group
228
229
230 class BPy_Info:
231
232         def __init__(self, script, dict):
233
234                 self.script = script
235
236                 self.d = dict
237
238                 self.header = []
239                 self.len_header = 0
240                 self.content = []
241                 self.len_content = 0
242                 self.spaces = 0
243                 self.fix_urls()
244                 self.make_header()
245                 self.wrap_lines()
246
247         def make_header(self):
248
249                 sc = self.script
250                 d = self.d
251
252                 header = self.header
253
254                 title = "Script: %s" % sc.name
255                 version = "Version: %s for Blender %1.2f or newer" % (d['__version__'],
256                         sc.version / 100.0)
257
258                 if len(d['__author__']) == 1:
259                         asuffix = ':'
260                 else: asuffix = 's:'
261
262                 authors = "%s%s %s" % ("Author", asuffix, ", ".join(d['__author__']))
263
264                 header.append(title)
265                 header.append(version)
266                 header.append(authors)
267                 self.len_header = len(header)
268
269
270         def fix_urls(self):
271
272                 emails = self.d['__email__']
273                 fixed = []
274                 for a in emails:
275                         if a in DEFAULT_EMAILS.keys():
276                                 fixed.append(DEFAULT_EMAILS[a])
277                         else:
278                                 a = a.replace('*','.').replace(':','@')
279                                 ltmp = a.split(',')
280                                 if len(ltmp) != 2:
281                                         ltmp = [ltmp[0], ltmp[0]]
282                                 fixed.append(ltmp)
283
284                 self.d['__email__'] = fixed
285
286                 links = self.d['__url__']
287                 fixed = []
288                 for a in links:
289                         if a in DEFAULT_LINKS.keys():
290                                 fixed.append(DEFAULT_LINKS[a])
291                         else:
292                                 ltmp = a.split(',')
293                                 if len(ltmp) != 2:
294                                         ltmp = [ltmp[0], ltmp[0]]
295                                 fixed.append([ltmp[0].strip(), ltmp[1].strip()])
296
297                 self.d['__url__'] = fixed
298
299
300         def wrap_lines(self, reset = 0):
301
302                 lines = self.d['__bpydoc__'].split('\n')
303                 self.content = []
304                 newlines = []
305                 newline = []
306
307                 if reset:
308                         self.len_content = 0
309                         self.spaces = 0
310
311                 for l in lines:
312                         if l == '' and newline:
313                                 newlines.append(newline)
314                                 newline = []
315                                 newlines.append('')
316                         else: newline.append(l)
317                 if newline: newlines.append(newline)
318
319                 for lst in newlines:
320                         wrapped = text_wrap(" ".join(lst))
321                         for l in wrapped:
322                                 self.content.append(l)
323                                 if l: self.len_content += 1
324                                 else: self.spaces += 1
325
326                 if not self.content[-1]:
327                         self.len_content -= 1
328
329
330 # End of class BPy_Info
331
332 def parse_pyobj_close(closetag, lines, i):
333         i += 1
334         l = lines[i]
335         while l.find(closetag) < 0:
336                 i += 1
337                 l = "%s%s" % (l, lines[i])
338         return [l, i]
339
340 def parse_pyobj(var, lines, i):
341         "Bad code, was in a hurry for release"
342
343         l = lines[i].replace(var, '').replace('=','',1).strip()
344
345         i0 = i - 1
346
347         if l[0] == '"':
348                 if l[1:3] == '""': # """
349                         if l.find('"""', 3) < 0: # multiline
350                                 l2, i = parse_pyobj_close('"""', lines, i)
351                                 if l[-1] == '\\': l = l[:-1]
352                                 l = "%s%s" % (l, l2)
353                 elif l[-1] == '"' and l[-2] != '\\': # single line: "..."
354                         pass
355                 else:
356                         l = "ERROR"
357
358         elif l[0] == "'":
359                 if l[1:3] == "''": # '''
360                         if l.find("'''", 3) < 0: # multiline
361                                 l2, i = parse_pyobj_close("'''", lines, i)
362                                 if l[-1] == '\\': l = l[:-1]
363                                 l = "%s%s" % (l, l2)
364                 elif l[-1] == '\\':
365                         l2, i = parse_pyobj_close("'", lines, i)
366                         l = "%s%s" % (l, l2)
367                 elif l[-1] == "'" and l[-2] !=  '\\': # single line: '...'
368                         pass
369                 else:
370                         l = "ERROR"
371
372         elif l[0] == '(':
373                 if l[-1] != ')':
374                         l2, i = parse_pyobj_close(')', lines, i)
375                         l = "%s%s" % (l, l2)
376
377         elif l[0] == '[':
378                 if l[-1] != ']':
379                         l2, i = parse_pyobj_close(']', lines, i)
380                         l = "%s%s" % (l, l2)
381
382         return [l, i - i0]
383
384 # helper functions:
385
386 def parse_help_info(script):
387
388         global PATHS, SCRIPT_INFO
389
390         if script.userdir:
391                 path = PATHS['uscripts']
392         else:
393                 path = PATHS['scripts']
394
395         fname = bsys.join(path, script.fname)
396
397         if not bsys.exists(fname):
398                 Draw.PupMenu('IO Error: couldn\'t find script %s' % fname)
399                 return None
400
401         f = file(fname, 'r')
402         lines = f.readlines()
403         f.close()
404
405         # fix line endings:
406         if lines[0].find('\r'):
407                 unixlines = []
408                 for l in lines:
409                         unixlines.append(l.replace('\r',''))
410                 lines = unixlines
411
412         llen = len(lines)
413         has_doc = 0
414
415         doc_data = {
416                 '__author__': '',
417                 '__version__': '',
418                 '__url__': '',
419                 '__email__': '',
420                 '__bpydoc__': '',
421                 '__doc__': ''
422         }
423
424         i = 0
425         while i < llen:
426                 l = lines[i]
427                 incr = 1
428                 for k in doc_data.keys():
429                         if l.find(k, 0, 20) == 0:
430                                 value, incr = parse_pyobj(k, lines, i)
431                                 exec("doc_data['%s'] = %s" % (k, value))
432                                 has_doc = 1
433                                 break
434                 i += incr
435
436         # fix these to seqs, simplifies coding elsewhere
437         for w in ['__author__', '__url__', '__email__']:
438                 val = doc_data[w]
439                 if val and type(val) == str:
440                         doc_data[w] = [doc_data[w]]
441
442         if not doc_data['__bpydoc__']:
443                 if doc_data['__doc__']:
444                         doc_data['__bpydoc__'] = doc_data['__doc__']
445
446         if has_doc: # any data, maybe should confirm at least doc/bpydoc
447                 info = BPy_Info(script, doc_data)
448                 SCRIPT_INFO = info
449                 return True
450
451         else:
452                 return False
453
454
455 def parse_script_line(l):
456
457         tip = 'No tooltip'
458         try:
459                 pieces = l.split("'")
460                 name = pieces[1].replace('...','')
461                 data = pieces[2].strip().split()
462                 version = data[0]
463                 userdir = data[-1]
464                 fname = data[1]
465                 i = 1
466                 while not fname.endswith('.py'):
467                         i += 1
468                         fname = '%s %s' % (fname, data[i])
469                 if len(pieces) > 3: tip = pieces[3]
470         except:
471                 return None
472
473         return [name, int(version), fname, int(userdir), tip]
474
475
476 def parse_bpymenus(lines):
477
478         global AllGroups
479
480         llen = len(lines)
481
482         for i in range(llen):
483                 l = lines[i].strip()
484                 if not l: continue
485                 if l[-1] == '{':
486                         group = Group(l[:-2])
487                         AllGroups.append(group)
488                         i += 1
489                         l = lines[i].strip()
490                         while l != '}':
491                                 if l[0] != '|':
492                                         data = parse_script_line(l)
493                                         if data:
494                                                 script = Script(data)
495                                                 group.add_script(script)
496                                 i += 1
497                                 l = lines[i].strip()
498
499 #       AllGroups.reverse()
500
501
502 def create_group_menus():
503
504         global AllGroups
505         menus = []
506
507         for group in AllGroups:
508
509                 name = group.get_name()
510                 menu = []
511                 scripts = group.get_scripts()
512                 for s in scripts: menu.append(s.name)
513                 menu = "|".join(menu)
514                 menu = "%s%%t|%s" % (name, menu)
515                 menus.append([name, menu])
516
517         return menus
518
519
520 # Collecting data:
521 fit_on_screen()
522 parse_bpymenus(lines)
523 GROUP_MENUS = create_group_menus()
524
525
526 # GUI:
527
528 from Blender import BGL
529 from Blender.Window import Theme
530
531 # globals:
532
533 START_SCREEN  = 0
534 SCRIPT_SCREEN = 1
535
536 SCREEN = START_SCREEN
537
538 # gui buttons:
539 len_gmenus = len(GROUP_MENUS)
540
541 BUT_GMENU = range(len_gmenus)
542 for i in range(len_gmenus):
543         BUT_GMENU[i] = Draw.Create(0)
544
545 # events:
546 BEVT_LINK  = None # range(len(SCRIPT_INFO.links))
547 BEVT_EMAIL = None # range(len(SCRIPT_INFO.emails))
548 BEVT_GMENU = range(100, len_gmenus + 100)
549 BEVT_VIEWSOURCE = 1
550 BEVT_EXIT = 2
551 BEVT_BACK = 3
552 BEVT_EXEC = 4   # Executes Script
553
554 # gui callbacks:
555
556 def gui(): # drawing the screen
557
558         global SCREEN, START_SCREEN, SCRIPT_SCREEN
559         global SCRIPT_INFO, AllGroups, GROUP_MENUS
560         global BEVT_EMAIL, BEVT_LINK
561         global BEVT_VIEWSOURCE, BEVT_EXIT, BEVT_BACK, BEVT_GMENU, BUT_GMENU, BEVT_EXEC
562         global PADDING, WIN_W, WIN_H, SCROLL_DOWN, COLUMNS, FMODE
563
564         theme = Theme.Get()[0]
565         tui = theme.get('ui')
566         ttxt = theme.get('text')
567
568         COL_BG = float_colors(ttxt.back)
569         COL_TXT = ttxt.text
570         COL_TXTHI = ttxt.text_hi
571
572         BGL.glClearColor(COL_BG[0],COL_BG[1],COL_BG[2],COL_BG[3])
573         BGL.glClear(BGL.GL_COLOR_BUFFER_BIT)
574         BGL.glColor3ub(COL_TXT[0],COL_TXT[1], COL_TXT[2])
575
576         resize = screen_was_resized()
577         if resize: fit_on_screen()
578
579         if SCREEN == START_SCREEN:
580                 x = PADDING
581                 bw = 85
582                 bh = 25
583                 hincr = 50
584
585                 butcolumns = (WIN_W - 2*x)/ bw
586                 if butcolumns < 2: butcolumns = 2
587                 elif butcolumns > 7: butcolumns = 7
588
589                 len_gm = len(GROUP_MENUS)
590                 butlines = len_gm / butcolumns
591                 if len_gm % butcolumns: butlines += 1
592
593                 h = hincr * butlines + 20
594                 y = h + bh
595
596                 BGL.glColor3ub(COL_TXTHI[0],COL_TXTHI[1], COL_TXTHI[2])
597                 BGL.glRasterPos2i(x, y)
598                 Draw.Text('Scripts Help Browser')
599
600                 y -= bh
601
602                 BGL.glColor3ub(COL_TXT[0],COL_TXT[1], COL_TXT[2])
603
604                 i = 0
605                 j = 0
606                 for group_menu in GROUP_MENUS:
607                         BGL.glRasterPos2i(x, y)
608                         Draw.Text(group_menu[0]+':')
609                         BUT_GMENU[j] = Draw.Menu(group_menu[1], BEVT_GMENU[j],
610                                 x, y-bh-5, bw, bh, 0,
611                                 'Choose a script to read its help information')
612                         if i == butcolumns - 1:
613                                 x = PADDING
614                                 i = 0
615                                 y -= hincr
616                         else:
617                                 i += 1
618                                 x += bw + 3
619                         j += 1
620
621                 x = PADDING
622                 y = 10
623                 BGL.glRasterPos2i(x, y)
624                 Draw.Text('Select script for its help.  Press Q or ESC to leave.')
625
626         elif SCREEN == SCRIPT_SCREEN:
627                 if SCRIPT_INFO:
628
629                         if resize:
630                                 SCRIPT_INFO.wrap_lines(1)
631                                 SCROLL_DOWN = 0
632
633                         h = 18 * SCRIPT_INFO.len_content + 12 * SCRIPT_INFO.spaces
634                         x = PADDING
635                         y = WIN_H
636                         bw = 38
637                         bh = 16
638
639                         BGL.glColor3ub(COL_TXTHI[0],COL_TXTHI[1], COL_TXTHI[2])
640                         for line in SCRIPT_INFO.header:
641                                 y -= 18
642                                 BGL.glRasterPos2i(x, y)
643                                 size = Draw.Text(line)
644
645                         for line in text_wrap('Tooltip: %s' % SCRIPT_INFO.script.tip):
646                                 y -= 18
647                                 BGL.glRasterPos2i(x, y)
648                                 size = Draw.Text(line)
649
650                         i = 0
651                         y -= 28
652                         for data in SCRIPT_INFO.d['__url__']:
653                                 Draw.PushButton('link %d' % (i + 1), BEVT_LINK[i],
654                                         x + i*bw, y, bw, bh, data[0])
655                                 i += 1
656                         y -= bh + 1
657
658                         i = 0
659                         for data in SCRIPT_INFO.d['__email__']:
660                                 Draw.PushButton('email', BEVT_EMAIL[i], x + i*bw, y, bw, bh, data[0])
661                                 i += 1
662                         y -= 18
663
664                         y0 = y
665                         BGL.glColor3ub(COL_TXT[0],COL_TXT[1], COL_TXT[2])
666                         for line in SCRIPT_INFO.content[SCROLL_DOWN:]:
667                                 if line:
668                                         line = line.replace('<br>', '')
669                                         BGL.glRasterPos2i(x, y)
670                                         Draw.Text(line)
671                                         y -= 18
672                                 else: y -= 12
673                                 if y < PADDING + 20: # reached end, either stop or go to 2nd column
674                                         if COLUMNS == 1: break
675                                         elif x == PADDING: # make sure we're still in column 1
676                                                 x = 6*TEXT_WRAP + PADDING / 2
677                                                 y = y0
678
679                         x = PADDING
680                         Draw.PushButton('source', BEVT_VIEWSOURCE, x, 17, 45, bh,
681                                 'View this script\'s source code in the Text Editor (hotkey: S)')
682                         Draw.PushButton('exit', BEVT_EXIT, x + 45, 17, 45, bh,
683                                 'Exit from Scripts Help Browser (hotkey: Q)')
684                         if not FMODE: 
685                                 Draw.PushButton('back', BEVT_BACK, x + 2*45, 17, 45, bh,
686                                 'Back to scripts selection screen (hotkey: ESC)')
687                                 Draw.PushButton('run script', BEVT_EXEC, x + 3*45, 17, 60, bh, 'Run this script')
688
689                         BGL.glColor3ub(COL_TXTHI[0],COL_TXTHI[1], COL_TXTHI[2])
690                         BGL.glRasterPos2i(x, 5)
691                         Draw.Text('use the arrow keys or the mouse wheel to scroll text', 'small')
692
693 def fit_scroll():
694         global SCROLL_DOWN
695         if not SCRIPT_INFO:
696                 SCROLL_DOWN = 0
697                 return
698         max = SCRIPT_INFO.len_content + SCRIPT_INFO.spaces - 1
699         if SCROLL_DOWN > max: SCROLL_DOWN = max
700         if SCROLL_DOWN < 0: SCROLL_DOWN = 0
701
702
703 def event(evt, val): # input events
704
705         global SCREEN, START_SCREEN, SCRIPT_SCREEN
706         global SCROLL_DOWN, FMODE
707
708         if not val: return
709
710         if evt == Draw.ESCKEY:
711                 if SCREEN == START_SCREEN or FMODE: Draw.Exit()
712                 else:
713                         SCREEN = START_SCREEN
714                         SCROLL_DOWN = 0
715                         Draw.Redraw()
716                 return
717         elif evt == Draw.QKEY:
718                 Draw.Exit()
719                 return
720         elif evt in [Draw.DOWNARROWKEY, Draw.WHEELDOWNMOUSE] and SCREEN == SCRIPT_SCREEN:
721                 SCROLL_DOWN += 1
722                 fit_scroll()
723                 Draw.Redraw()
724                 return
725         elif evt in [Draw.UPARROWKEY, Draw.WHEELUPMOUSE] and SCREEN == SCRIPT_SCREEN:
726                 SCROLL_DOWN -= 1
727                 fit_scroll()
728                 Draw.Redraw()
729                 return
730         elif evt == Draw.SKEY:
731                 if SCREEN == SCRIPT_SCREEN and SCRIPT_INFO:
732                         load_script_text(SCRIPT_INFO.script)
733                         return
734
735 def button_event(evt): # gui button events
736
737         global SCREEN, START_SCREEN, SCRIPT_SCREEN
738         global BEVT_LINK, BEVT_EMAIL, BEVT_GMENU, BUT_GMENU, SCRIPT_INFO
739         global SCROLL_DOWN, FMODE
740
741         if evt >= 100: # group menus
742                 for i in range(len(BUT_GMENU)):
743                         if evt == BEVT_GMENU[i]:
744                                 group = AllGroups[i]
745                                 index = BUT_GMENU[i].val - 1
746                                 if index < 0: return # user didn't pick a menu entry
747                                 script = group.get_scripts()[BUT_GMENU[i].val - 1]
748                                 if parse_help_info(script):
749                                         SCREEN = SCRIPT_SCREEN
750                                         BEVT_LINK = range(20, len(SCRIPT_INFO.d['__url__']) + 20)
751                                         BEVT_EMAIL = range(50, len(SCRIPT_INFO.d['__email__']) + 50)
752                                         Draw.Redraw()
753                                 else:
754                                         res = Draw.PupMenu("No help available%t|View Source|Cancel")
755                                         if res == 1:
756                                                 load_script_text(script)
757         elif evt >= 20:
758                 if not WEBBROWSER:
759                         Draw.PupMenu('Missing standard Python module%t|You need module "webbrowser" to access the web')
760                         return
761
762                 if evt >= 50: # script screen email buttons
763                         email = SCRIPT_INFO.d['__email__'][evt - 50][1]
764                         webbrowser.open("mailto:%s" % email)
765                 else: # >= 20: script screen link buttons
766                         link = SCRIPT_INFO.d['__url__'][evt - 20][1]
767                         webbrowser.open(link)
768         elif evt == BEVT_VIEWSOURCE:
769                 if SCREEN == SCRIPT_SCREEN: load_script_text(SCRIPT_INFO.script)
770         elif evt == BEVT_EXIT:
771                 Draw.Exit()
772                 return
773         elif evt == BEVT_BACK:
774                 if SCREEN == SCRIPT_SCREEN and not FMODE:
775                         SCREEN = START_SCREEN
776                         SCRIPT_INFO = None
777                         SCROLL_DOWN = 0
778                         Draw.Redraw()
779         elif evt == BEVT_EXEC: # Execute script
780                 exec_line = ''
781                 if SCRIPT_INFO.script.userdir:
782                         exec_line = bsys.join(Blender.Get('uscriptsdir'), SCRIPT_INFO.script.fname)
783                 else:
784                         exec_line = bsys.join(Blender.Get('scriptsdir'), SCRIPT_INFO.script.fname)
785
786                 Blender.Run(exec_line)
787
788 keepon = True
789 FMODE = False # called by Blender.ShowHelp(name) API function ?
790
791 KEYNAME = '__help_browser'
792 rd = Registry.GetKey(KEYNAME)
793 if rd:
794         rdscript = rd['script']
795         keepon = False
796         Registry.RemoveKey(KEYNAME)
797         for group in AllGroups:
798                 for script in group.get_scripts():
799                         if rdscript == script.fname:
800                                 parseit = parse_help_info(script)
801                                 if parseit == True:
802                                         keepon = True
803                                         SCREEN = SCRIPT_SCREEN
804                                         BEVT_LINK = range(20, len(SCRIPT_INFO.d['__url__']) + 20)
805                                         BEVT_EMAIL = range(50, len(SCRIPT_INFO.d['__email__']) + 50)
806                                         FMODE = True
807                                 elif parseit == False:
808                                         Draw.PupMenu("ERROR: script doesn't have proper help data")
809                                 break
810
811 if not keepon:
812         Draw.PupMenu("ERROR: couldn't find script")
813 else:
814         Draw.Register(gui, event, button_event)