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