Welcome to mirror list, hosted at ThFree Co, Russian Federation.

git.blender.org/blender.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'release/scripts/help_browser.py')
-rw-r--r--release/scripts/help_browser.py753
1 files changed, 753 insertions, 0 deletions
diff --git a/release/scripts/help_browser.py b/release/scripts/help_browser.py
new file mode 100644
index 00000000000..550a494c97b
--- /dev/null
+++ b/release/scripts/help_browser.py
@@ -0,0 +1,753 @@
+#!BPY
+
+"""
+Name: 'Scripts Help Browser'
+Blender: 234
+Group: 'Help'
+Tooltip: 'Show help information about a chosen installed script.'
+"""
+
+__author__ = "Willian P. Germano"
+__version__ = "0.1 11/02/04"
+__email__ = ('scripts', 'Author, wgermano:ig*com*br')
+__url__ = ('blender', 'elysiun')
+
+__bpydoc__ ="""\
+This script shows help information for scripts registered in the menus.
+
+Usage:
+
+- Start Screen:
+
+To read any script's "user manual" select a script from one of the
+available category menus. If the script has help information in the format
+expected by this Help Browser, it will be displayed in the Script Help
+Screen. Otherwise you'll be offered the possibility of loading the chosen
+script's source file in Blender's Text Editor. The programmer(s) may have
+written useful comments there for users.
+
+Hotkeys:<br>
+ ESC or Q: [Q]uit
+
+- Script Help Screen:
+
+This screen shows the user manual page for the chosen script. If the text
+doesn't fit completely on the screen, you can scroll it up or down with
+arrow keys or a mouse wheel. There may be link and email buttons that if
+clicked should open your default web browser and email client programs for
+further information or support.
+
+Hotkeys:<br>
+ ESC: back to Start Screen<br>
+ Q: [Q]uit<br>
+ S: view script's [S]ource code in Text Editor<br>
+ UP, DOWN Arrows and mouse wheel: scroll text up / down
+"""
+
+# $Id$
+#
+# --------------------------------------------------------------------------
+# sysinfo.py version 0.1 Jun 09, 2004
+# --------------------------------------------------------------------------
+# ***** BEGIN GPL LICENSE BLOCK *****
+#
+# Copyright (C) 2004: Willian P. Germano, wgermano _at_ ig.com.br
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software Foundation,
+# Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+#
+# ***** END GPL LICENCE BLOCK *****
+# --------------------------------------------------------------------------
+
+import Blender
+from Blender import sys as bsys, Draw, Window
+
+WEBBROWSER = True
+try:
+ import webbrowser
+except:
+ WEBBROWSER = False
+
+DEFAULT_EMAILS = {
+ 'scripts': ['Bf-scripts-dev', 'bf-scripts-dev@blender.org']
+}
+
+DEFAULT_LINKS = {
+ 'blender': ["blender.org\'s Python forum", "http://www.blender.org/modules.php?op=modload&name=phpBB2&file=viewforum&f=9"],
+ 'elysiun': ["elYsiun\'s Python and Plugins forum", "http://www.elysiun.com/forum/viewforum.php?f=5"]
+}
+
+PADDING = 15
+COLUMNS = 1
+TEXT_WRAP = 100
+WIN_W = WIN_H = 200
+SCROLL_DOWN = 0
+
+def screen_was_resized():
+ global WIN_W, WIN_H
+
+ w, h = Window.GetAreaSize()
+ if WIN_W != w or WIN_H != h:
+ WIN_W = w
+ WIN_H = h
+ return True
+ return False
+
+def fit_on_screen():
+ global TEXT_WRAP, PADDING, WIN_W, WIN_H, COLUMNS
+
+ COLUMNS = 1
+ WIN_W, WIN_H = Window.GetAreaSize()
+ TEXT_WRAP = int((WIN_W - PADDING) / 6)
+ if TEXT_WRAP < 40:
+ TEXT_WRAP = 40
+ elif TEXT_WRAP > 100:
+ if TEXT_WRAP > 110:
+ COLUMNS = 2
+ TEXT_WRAP /= 2
+ else: TEXT_WRAP = 100
+
+def cut_point(text, length):
+ "Returns position of the last space found before 'length' chars"
+ l = length
+ c = text[l]
+ while c != ' ':
+ l -= 1
+ if l == 0: return length # no space found
+ c = text[l]
+ return l
+
+def text_wrap(text, length = None):
+ global TEXT_WRAP
+
+ wrapped = []
+ lines = text.split('<br>')
+ llen = len(lines)
+ if llen > 1:
+ if lines[-1] == '': llen -= 1
+ for i in range(llen - 1):
+ lines[i] = lines[i].rstrip() + '<br>'
+ lines[llen-1] = lines[llen-1].rstrip()
+
+ if not length: length = TEXT_WRAP
+
+ for l in lines:
+ while len(l) > length:
+ cpt = cut_point(l, length)
+ line, l = l[:cpt], l[cpt + 1:]
+ wrapped.append(line)
+ wrapped.append(l)
+ return wrapped
+
+def load_script_text(script):
+ global PATHS, SCRIPT_INFO
+
+ if script.userdir:
+ path = PATHS['uscripts']
+ else:
+ path = PATHS['scripts']
+
+ fname = bsys.join(path, script.fname)
+
+ source = Blender.Text.Load(fname)
+ if source:
+ Draw.PupMenu("File loaded%%t|Please check the file \"%s\" in the Text Editor window" % source.name)
+
+
+# for theme colors:
+def float_colors(cols):
+ return map(lambda x: x / 255.0, cols)
+
+# globals
+
+SCRIPT_INFO = None
+
+PATHS = {
+ 'home': Blender.Get('homedir'),
+ 'scripts': Blender.Get('scriptsdir'),
+ 'uscripts': Blender.Get('uscriptsdir')
+}
+
+BPYMENUS_FILE = bsys.join(PATHS['home'], 'Bpymenus')
+
+f = file(BPYMENUS_FILE, 'r')
+lines = f.readlines()
+f.close()
+
+AllGroups = []
+
+class Script:
+
+ def __init__(self, data):
+ self.name = data[0]
+ self.version = data[1]
+ self.fname = data[2]
+ self.userdir = data[3]
+ self.tip = data[4]
+
+# End of class Script
+
+
+class Group:
+
+ def __init__(self, name):
+ self.name = name
+ self.scripts = []
+
+ def add_script(self, script):
+ self.scripts.append(script)
+
+ def get_name(self):
+ return self.name
+
+ def get_scripts(self):
+ return self.scripts
+
+# End of class Group
+
+
+class BPy_Info:
+
+ def __init__(self, script, dict):
+
+ self.script = script
+
+ self.d = dict
+
+ self.header = []
+ self.len_header = 0
+ self.content = []
+ self.len_content = 0
+ self.spaces = 0
+ self.fix_urls()
+ self.make_header()
+ self.wrap_lines()
+
+ def make_header(self):
+
+ sc = self.script
+ d = self.d
+
+ header = self.header
+
+ title = "Script: %s" % sc.name
+ version = "Version: %s for Blender %1.2f or newer" % (d['__version__'],
+ sc.version / 100.0)
+
+ if len(d['__author__']) == 1:
+ asuffix = ':'
+ else: asuffix = 's:'
+
+ authors = "%s%s %s" % ("Author", asuffix, ", ".join(d['__author__']))
+
+ header.append(title)
+ header.append(version)
+ header.append(authors)
+ self.len_header = len(header)
+
+
+ def fix_urls(self):
+
+ emails = self.d['__email__']
+ fixed = []
+ for a in emails:
+ if a in DEFAULT_EMAILS.keys():
+ fixed.append(DEFAULT_EMAILS[a])
+ else:
+ a = a.replace('*','.').replace(':','@')
+ ltmp = a.split(',')
+ if len(ltmp) != 2:
+ ltmp = [ltmp[0], ltmp[0]]
+ fixed.append(ltmp)
+
+ self.d['__email__'] = fixed
+
+ links = self.d['__url__']
+ fixed = []
+ for a in links:
+ if a in DEFAULT_LINKS.keys():
+ fixed.append(DEFAULT_LINKS[a])
+ else:
+ ltmp = a.split(',')
+ if len(ltmp) != 2:
+ ltmp = [ltmp[0], ltmp[0]]
+ fixed.append([ltmp[0].strip(), ltmp[1].strip()])
+
+ self.d['__url__'] = fixed
+
+
+ def wrap_lines(self, reset = 0):
+
+ lines = self.d['__bpydoc__'].split('\n')
+ self.content = []
+ newlines = []
+ newline = []
+
+ if reset:
+ self.len_content = 0
+ self.spaces = 0
+
+ for l in lines:
+ if l == '' and newline:
+ newlines.append(newline)
+ newline = []
+ newlines.append('')
+ else: newline.append(l)
+ if newline: newlines.append(newline)
+
+ for lst in newlines:
+ wrapped = text_wrap(" ".join(lst))
+ for l in wrapped:
+ self.content.append(l)
+ if l: self.len_content += 1
+ else: self.spaces += 1
+
+ if not self.content[-1]:
+ self.len_content -= 1
+
+
+# End of class BPy_Info
+
+def parse_pyobj_close(closetag, lines, i):
+ i += 1
+ l = lines[i]
+ while l.find(closetag) < 0:
+ i += 1
+ l = "%s%s" % (l, lines[i])
+ return [l, i]
+
+def parse_pyobj(var, lines, i):
+ "Bad code, was in a hurry for release"
+
+ l = lines[i].replace(var, '').replace('=','',1).strip()
+
+ i0 = i - 1
+
+ if l[0] == '"':
+ if l[1:3] == '""': # """
+ if l.find('"""', 3) < 0: # multiline
+ l2, i = parse_pyobj_close('"""', lines, i)
+ if l[-1] == '\\': l = l[:-1]
+ l = "%s%s" % (l, l2)
+ elif l[-1] == '"' and l[-2] != '\\': # single line: "..."
+ pass
+ else:
+ l = "ERROR"
+
+ elif l[0] == "'":
+ if l[-1] == '\\':
+ l2, i = parse_pyobj_close("'", lines, i)
+ l = "%s%s" % (l, l2)
+ elif l[-1] == "'" and l[-2] != '\\': # single line: '...'
+ pass
+ else:
+ l = "ERROR"
+
+ elif l[0] == '(':
+ if l[-1] != ')':
+ l2, i = parse_pyobj_close(')', lines, i)
+ l = "%s%s" % (l, l2)
+
+ elif l[0] == '[':
+ if l[-1] != ']':
+ l2, i = parse_pyobj_close(']', lines, i)
+ l = "%s%s" % (l, l2)
+
+ return [l, i - i0]
+
+# helper functions:
+
+def parse_help_info(script):
+
+ global PATHS, SCRIPT_INFO
+
+ if script.userdir:
+ path = PATHS['uscripts']
+ else:
+ path = PATHS['scripts']
+
+ fname = bsys.join(path, script.fname)
+
+ if not bsys.exists(fname):
+ Draw.PupMenu('IO Error|Couldn\'t find script %s' % fname)
+ return None
+
+ f = file(fname, 'r')
+ lines = f.readlines()
+ f.close()
+
+ # fix line endings:
+ if lines[0].find('\r'):
+ unixlines = []
+ for l in lines:
+ unixlines.append(l.replace('\r',''))
+ lines = unixlines
+
+ llen = len(lines)
+ has_doc = 0
+
+ doc_data = {
+ '__author__': '',
+ '__version__': '',
+ '__url__': '',
+ '__email__': '',
+ '__bpydoc__': '',
+ '__doc__': ''
+ }
+
+ i = 0
+ while i < llen:
+ l = lines[i]
+ incr = 1
+ for k in doc_data.keys():
+ if l.find(k, 0, 20) == 0:
+ value, incr = parse_pyobj(k, lines, i)
+ exec("doc_data['%s'] = %s" % (k, value))
+ has_doc = 1
+ break
+ i += incr
+
+ # fix these to seqs, simplifies coding elsewhere
+ for w in ['__author__', '__url__', '__email__']:
+ val = doc_data[w]
+ if val and type(val) == str:
+ doc_data[w] = [doc_data[w]]
+
+ if not doc_data['__bpydoc__']:
+ if doc_data['__doc__']:
+ doc_data['__bpydoc__'] = doc_data['__doc__']
+
+ if has_doc: # any data, maybe should confirm at least doc/bpydoc
+ info = BPy_Info(script, doc_data)
+ SCRIPT_INFO = info
+ return True
+
+ else:
+ return False
+
+
+def parse_script_line(l):
+
+ try:
+ pieces = l.split("'")
+ name = pieces[1].replace('...','')
+ version, fname, userdir = pieces[2].strip().split()
+ tip = pieces[3]
+ except:
+ return None
+
+ return [name, int(version), fname, int(userdir), tip]
+
+
+def parse_bpymenus(lines):
+
+ global AllGroups
+
+ llen = len(lines)
+
+ for i in range(llen):
+ l = lines[i].strip()
+ if not l: continue
+ if l[-1] == '{':
+ group = Group(l[:-2])
+ AllGroups.append(group)
+ i += 1
+ l = lines[i].strip()
+ while l != '}':
+ if l[0] != '|':
+ data = parse_script_line(l)
+ if data:
+ script = Script(data)
+ group.add_script(script)
+ i += 1
+ l = lines[i].strip()
+
+ AllGroups.reverse()
+
+
+def create_group_menus():
+
+ global AllGroups
+ menus = []
+
+ for group in AllGroups:
+
+ name = group.get_name()
+ menu = []
+ scripts = group.get_scripts()
+ for s in scripts: menu.append(s.name)
+ menu = "|".join(menu)
+ menu = "%s%%t|%s" % (name, menu)
+ menus.append([name, menu])
+
+ return menus
+
+
+# Collecting data:
+fit_on_screen()
+parse_bpymenus(lines)
+GROUP_MENUS = create_group_menus()
+
+
+# GUI:
+
+from Blender import BGL
+from Blender.Window import Theme
+
+# globals:
+
+START_SCREEN = 0
+SCRIPT_SCREEN = 1
+
+SCREEN = START_SCREEN
+
+# gui buttons:
+len_gmenus = len(GROUP_MENUS)
+
+BUT_GMENU = range(len_gmenus)
+for i in range(len_gmenus):
+ BUT_GMENU[i] = Draw.Create(0)
+
+# events:
+BEVT_LINK = None # range(len(SCRIPT_INFO.links))
+BEVT_EMAIL = None # range(len(SCRIPT_INFO.emails))
+BEVT_GMENU = range(100, len_gmenus + 100)
+BEVT_VIEWSOURCE = 1
+BEVT_EXIT = 2
+BEVT_BACK = 3
+
+# gui callbacks:
+
+def gui(): # drawing the screen
+
+ global SCREEN, START_SCREEN, SCRIPT_SCREEN
+ global SCRIPT_INFO, AllGroups, GROUP_MENUS
+ global BEVT_EMAIL, BEVT_LINK
+ global BEVT_VIEWSOURCE, BEVT_EXIT, BEVT_BACK, BEVT_GMENU, BUT_GMENU
+ global PADDING, WIN_W, WIN_H, SCROLL_DOWN, COLUMNS
+
+ theme = Theme.Get()[0]
+ tui = theme.get('ui')
+ ttxt = theme.get('text')
+
+ COL_BG = float_colors(ttxt.back)
+ COL_TXT = ttxt.text
+ COL_TXTHI = ttxt.text_hi
+
+ BGL.glClearColor(COL_BG[0],COL_BG[1],COL_BG[2],COL_BG[3])
+ BGL.glClear(BGL.GL_COLOR_BUFFER_BIT)
+ BGL.glColor3ub(COL_TXT[0],COL_TXT[1], COL_TXT[2])
+
+ resize = screen_was_resized()
+ if resize: fit_on_screen()
+
+ if SCREEN == START_SCREEN:
+ x = PADDING
+ bw = 70
+ bh = 25
+ hincr = 50
+
+ butcolumns = (WIN_W - 2*x)/ bw
+ if butcolumns < 2: butcolumns = 2
+ elif butcolumns > 7: butcolumns = 7
+
+ len_gm = len(GROUP_MENUS)
+ butlines = len_gm / butcolumns
+ if len_gm % butcolumns: butlines += 1
+
+ h = hincr * butlines + 20
+ y = h + bh
+
+ BGL.glColor3ub(COL_TXTHI[0],COL_TXTHI[1], COL_TXTHI[2])
+ BGL.glRasterPos2i(x, y)
+ Draw.Text('Scripts Help Browser')
+
+ y -= bh
+
+ BGL.glColor3ub(COL_TXT[0],COL_TXT[1], COL_TXT[2])
+
+ i = 0
+ j = 0
+ for group_menu in GROUP_MENUS:
+ BGL.glRasterPos2i(x, y)
+ Draw.Text(group_menu[0]+':')
+ BUT_GMENU[j] = Draw.Menu(group_menu[1], BEVT_GMENU[j],
+ x, y-bh-5, bw, bh, 0,
+ 'Choose a script to read its help information')
+ if i == butcolumns - 1:
+ x = PADDING
+ i = 0
+ y -= hincr
+ else:
+ i += 1
+ x += bw + 3
+ j += 1
+
+ x = PADDING
+ y = 10
+ BGL.glRasterPos2i(x, y)
+ Draw.Text('Select script for its help. Press Q or ESC to leave.')
+
+ elif SCREEN == SCRIPT_SCREEN:
+ if SCRIPT_INFO:
+
+ if resize:
+ SCRIPT_INFO.wrap_lines(1)
+ SCROLL_DOWN = 0
+
+ h = 18 * SCRIPT_INFO.len_content + 12 * SCRIPT_INFO.spaces
+ x = PADDING
+ y = WIN_H
+ bw = 38
+ bh = 16
+
+ BGL.glColor3ub(COL_TXTHI[0],COL_TXTHI[1], COL_TXTHI[2])
+ for line in SCRIPT_INFO.header:
+ y -= 18
+ BGL.glRasterPos2i(x, y)
+ size = Draw.Text(line)
+
+ for line in text_wrap('Tooltip: %s' % SCRIPT_INFO.script.tip):
+ y -= 18
+ BGL.glRasterPos2i(x, y)
+ size = Draw.Text(line)
+
+ i = 0
+ y -= 28
+ for data in SCRIPT_INFO.d['__url__']:
+ Draw.PushButton('link %d' % (i + 1), BEVT_LINK[i],
+ x + i*bw, y, bw, bh, data[0])
+ i += 1
+ y -= bh + 1
+
+ i = 0
+ for data in SCRIPT_INFO.d['__email__']:
+ Draw.PushButton('email', BEVT_EMAIL[i], x + i*bw, y, bw, bh, data[0])
+ i += 1
+ y -= 18
+
+ y0 = y
+ BGL.glColor3ub(COL_TXT[0],COL_TXT[1], COL_TXT[2])
+ for line in SCRIPT_INFO.content[SCROLL_DOWN:]:
+ if line:
+ line = line.replace('<br>', '')
+ BGL.glRasterPos2i(x, y)
+ Draw.Text(line)
+ y -= 18
+ else: y -= 12
+ if y < PADDING + 20: # reached end, either stop or go to 2nd column
+ if COLUMNS == 1: break
+ elif x == PADDING: # make sure we're still in column 1
+ x = 6*TEXT_WRAP + PADDING / 2
+ y = y0
+
+ x = PADDING
+ Draw.PushButton('source', BEVT_VIEWSOURCE, x, 17, 45, bh,
+ 'View this script\'s source code in the Text Editor (hotkey: S)')
+ Draw.PushButton('exit', BEVT_EXIT, x + 45, 17, 45, bh,
+ 'Exit from Scripts Help Browser (hotkey: Q)')
+ Draw.PushButton('back', BEVT_BACK, x + 2*45, 17, 45, bh,
+ 'Back to scripts selection screen (hotkey: ESC)')
+ BGL.glColor3ub(COL_TXTHI[0],COL_TXTHI[1], COL_TXTHI[2])
+ BGL.glRasterPos2i(x, 5)
+ Draw.Text('use the arrow keys or the mouse wheel to scroll text', 'small')
+
+def fit_scroll():
+ global SCROLL_DOWN
+ if not SCRIPT_INFO:
+ SCROLL_DOWN = 0
+ return
+ max = SCRIPT_INFO.len_content + SCRIPT_INFO.spaces - 1
+ if SCROLL_DOWN > max: SCROLL_DOWN = max
+ if SCROLL_DOWN < 0: SCROLL_DOWN = 0
+
+
+def event(evt, val): # input events
+
+ global SCREEN, START_SCREEN, SCRIPT_SCREEN
+ global SCROLL_DOWN
+
+ if not val: return
+
+ if evt == Draw.ESCKEY:
+ if SCREEN == START_SCREEN: Draw.Exit()
+ else:
+ SCREEN = START_SCREEN
+ SCROLL_DOWN = 0
+ Draw.Redraw()
+ return
+ elif evt == Draw.QKEY:
+ Draw.Exit()
+ return
+ elif evt in [Draw.DOWNARROWKEY, Draw.WHEELDOWNMOUSE] and SCREEN == SCRIPT_SCREEN:
+ SCROLL_DOWN += 1
+ fit_scroll()
+ Draw.Redraw()
+ return
+ elif evt in [Draw.UPARROWKEY, Draw.WHEELUPMOUSE] and SCREEN == SCRIPT_SCREEN:
+ SCROLL_DOWN -= 1
+ fit_scroll()
+ Draw.Redraw()
+ return
+ elif evt == Draw.SKEY:
+ if SCREEN == SCRIPT_SCREEN and SCRIPT_INFO:
+ load_script_text(SCRIPT_INFO.script)
+ return
+
+def button_event(evt): # gui button events
+
+ global SCREEN, START_SCREEN, SCRIPT_SCREEN
+ global BEVT_LINK, BEVT_EMAIL, BEVT_GMENU, BUT_GMENU, SCRIPT_INFO
+ global SCROLL_DOWN
+
+ if evt >= 100: # group menus
+ for i in range(len(BUT_GMENU)):
+ if evt == BEVT_GMENU[i]:
+ group = AllGroups[i]
+ index = BUT_GMENU[i].val - 1
+ if index < 0: return # user didn't pick a menu entry
+ script = group.get_scripts()[BUT_GMENU[i].val - 1]
+ if parse_help_info(script):
+ SCREEN = SCRIPT_SCREEN
+ BEVT_LINK = range(20, len(SCRIPT_INFO.d['__url__']) + 20)
+ BEVT_EMAIL = range(50, len(SCRIPT_INFO.d['__email__']) + 50)
+ Draw.Redraw()
+ else:
+ res = Draw.PupMenu("No help available%t|View Source|Cancel")
+ if res == 1:
+ load_script_text(script)
+ elif evt >= 20:
+ if not WEBBROWSER:
+ Draw.PupMenu('Missing standard Python module%t|You need module "webbrowser" to access the web')
+ return
+
+ if evt >= 50: # script screen email buttons
+ email = SCRIPT_INFO.d['__email__'][evt - 50][1]
+ webbrowser.open("mailto:%s" % email)
+ else: # >= 20: script screen link buttons
+ link = SCRIPT_INFO.d['__url__'][evt - 20][1]
+ webbrowser.open(link)
+ elif evt == BEVT_VIEWSOURCE:
+ if SCREEN == SCRIPT_SCREEN: load_script_text(SCRIPT_INFO.script)
+ elif evt == BEVT_EXIT:
+ Draw.Exit()
+ return
+ elif evt == BEVT_BACK:
+ if SCREEN == SCRIPT_SCREEN:
+ SCREEN = START_SCREEN
+ SCRIPT_INFO = None
+ SCROLL_DOWN = 0
+ Draw.Redraw()
+
+Draw.Register(gui, event, button_event)