diff options
author | Willian Padovani Germano <wpgermano@gmail.com> | 2004-11-07 19:31:13 +0300 |
---|---|---|
committer | Willian Padovani Germano <wpgermano@gmail.com> | 2004-11-07 19:31:13 +0300 |
commit | c702b237d5f9a542c1d327e1c87a5b7e7ec81590 (patch) | |
tree | 99347ced1b1260843aff9c6f70670a2a9d45f7f8 /release/scripts/help_browser.py | |
parent | 23e8b982288699258c4bf4d7f5464f968b98d5cf (diff) |
Scripts:
-- adding help_browser.py to show help for installed scripts;
-- updated scripts to include basic doc info to be shown with above script:
script authors can / will / should update with more info, of course;
-- updated some scripts to newer versions: disp_paint, fixfromarmature, hotkeys, etc.
Diffstat (limited to 'release/scripts/help_browser.py')
-rw-r--r-- | release/scripts/help_browser.py | 753 |
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) |