Requirements
This is fun little app requires the Graphviz + GraphvizMax2D modules.
What does it do?
Given a module name, it displays a scrollable, zoomable, clickable Type hierarchy of said module in a window, also indicating any external types it uses.
Usage
Right-mouse drag - move view
Scrollwheel - zoom in/out
Left-mouse click - select node.
Possible Improvements
When a node is clicked on, it would be nice to see the bbdoc for it ;-)
Anyhoo... here it is.
This is fun little app requires the Graphviz + GraphvizMax2D modules.
What does it do?
Given a module name, it displays a scrollable, zoomable, clickable Type hierarchy of said module in a window, also indicating any external types it uses.
Usage
Right-mouse drag - move view
Scrollwheel - zoom in/out
Left-mouse click - select node.
Possible Improvements
When a node is clicked on, it would be nice to see the bbdoc for it ;-)
Anyhoo... here it is.
SuperStrict Framework BaH.Graphviz Import BaH.GraphvizMax2D Import BRL.StandardIO Import BRL.glmax2d Import BRL.MaxUtil Global nodeTypes:String[] = [ "/", "Module","Type", "Const","Field","Global","Method","Function","Keyword"] ' we won't show these babies! Global ignoreMods:String[] = [ "brl.mod", "pub.mod" ] ' Change this for you favourite module Local file:String = ModuleSource("bah.graphviz") processFile(file) doVisuals() End Function processFile(file:String) Local text:String = LoadText(file) Local inrem:Int Local docPath:String = file Local typePath:String For Local line:String = EachIn text.split("~n") line = line.Trim() Local low:String=line.ToLower() Local i:Int Local id:String = ParseIdent( low,i ) If id="end" id:+ ParseIdent( low,i ) If i< low.length And low[i]=Asc(":") id:+":" i:+1 EndIf If inrem If id="endrem" inrem=False End If Else If id="rem" inrem=True Else If id="endtype" If typePath docPath=typePath typePath="" EndIf Else If id="import" Or id="include" Local parsed:String = ParseString( line,i ) If parsed Then Local p$=ExtractDir( file )+"/"+parsed ' If ExtractExt( p ).ToLower()="bmx" processFile p EndIf Else Local imp:String = line[i..].Trim().ToLower() If FindModule(imp) Then Local src:String = ModuleSource(imp) Local ig:Int = False For Local ignore:String = EachIn ignoreMods If src.find(ignore) >= 0 Then ig = True End If Next If Not Ig Then If Not TNode._pathMap.ValueForKey( ModuleSource(imp) ) ProcessFile(ModuleSource(imp)) End If End If End If End If Else Local kind:String, proto:String For Local t$=EachIn nodeTypes If id<>t.ToLower() Continue kind=t proto=line id=ParseIdent( line,i ) Exit Next If kind Local path$ Select kind Case "Type" If typePath Throw "Type path already set" typePath=docPath docPath:+"/"+id path=docPath Case "Module" path=docPath Default If Not docPath Throw "No doc path" path=docPath+"/"+id End Select Local i:Int=proto.Find( ")=" ) If i<>-1 proto=proto[..i+1] If id.StartsWith( "Sort" ) proto:+" )" 'lazy!!!!! EndIf i=proto.Find( "=New" ) If i<>-1 proto=proto[..i] EndIf Local node:TNode=TNode.Create( id,path,kind ) If Not node Return End If node.setProto(proto) End If End If Next End Function Function FindModule:Int(impMod:String) Global mods:TList = EnumModules() impMod = impMod.ToLower() For Local modid$=EachIn mods If impMod = modid Then Return True End If Next Return False End Function Function IsAlphaChar:Int( char:Int ) Return (char>=Asc("A") And char<=Asc("Z")) Or (char>=Asc("a") And char<=Asc("z")) Or (char=Asc("_")) End Function Function IsDecChar:Int( char:Int ) Return (char>=Asc("0") And char<=Asc("9")) End Function Function IsIdentChar:Int( char:Int ) Return IsAlphaChar( char ) Or IsDecChar( char ) End Function Function IsHexChar:Int( char:Int ) Return IsDecChar( char ) Or (char>=Asc("A") And char<=Asc("F")) Or (char>=Asc("a") And char<=Asc("f")) End Function Function IsBinChar:Int( char:Int ) Return (char>=Asc("0") And char<=Asc("1")) End Function Function IsPunctChar:Int( char:Int ) Select char Case Asc("."),Asc(","),Asc(";"),Asc(":" ),Asc("!"),Asc("?") Return True End Select End Function Function ParseWS:String( t:String,i:Int Var ) Local i0:Int=i While i<t.length And t[i]<=32 i:+1 Wend Return t[i0..i] End Function Function ParseIdent:String( t:String, i:Int Var ) ParseWS t,i Local i0:Int=i If i<t.length And IsAlphaChar( t[i] ) i:+1 While i<t.length And (IsIdentChar( t[i] ) Or t[i]=Asc(".")) i:+1 Wend EndIf Return t[i0..i] End Function Function ParseString:String( t:String,i:Int Var ) ParseWS t,i If i=t.length Or t[i]<>Asc("~q") Return Null i:+1 Local i0:Int=i While i<t.length And t[i]<>Asc("~q" ) i:+1 Wend Local q$=t[i0..i] If i<t.length i:+1 Return q End Function Type TNode Field id$ 'eg: BRL.Audio Field path$ 'eg: Modules/Audio/Audio" Field kind$ 'eg: "Module", "Function", "Type" etc Field proto$ 'eg: Function LoadImage(...) Field bbdoc$ 'eg: Load an image (shortdesc?) Field returns$ 'eg: A new image Field about$ 'eg: blah etc blah (longdesc?) Field params:TList 'eg: [x - the x coord, y - the y coord] Field docDir$ 'eg: ../mod/brl.mod/max2d.mod/doc Field example$ 'eg: LoadImage.bmx (path) Field children:TList=New TList Field superType:TNode Field superTypeName:String Function Create:TNode( id$,path$,kind$ ) Local t:TNode=TNode( _pathMap.ValueForKey( path ) ) If t If t.kind<>"/" Return Null Else t=New TNode _pathMap.Insert path,t EndIf t.id=id t.path=path t.kind=kind Local q:TNode=t Repeat If path="/" Exit path=ExtractDir( path ) Local p:TNode=TNode( _pathMap.ValueForKey( path ) ) If Not p p=New TNode p.id=StripDir(path) p.path=path p.kind="/" p.children.AddLast q _pathMap.Insert path,p q=p Else ' is there a mod on this level? rather add it to the module... Local done:Int For Local m:TNode = EachIn p.children If m.kind = "Module" Then m.children.AddLast q done = True Exit End If Next If Not done p.children.AddLast q End If Exit EndIf Forever Return t End Function Method setProto(p:String) proto = p If kind = "Type" Then Local i:Int = p.find("Extends") If i > 0 Then superTypeName = p[i..].split(" ")[1] _needSuper.insert(path, Self) End If End If End Method Global _pathMap:TMap=New TMap Global _needSuper:TMap = New TMap End Type Function processDeep(sub:TGVSubGraph, parent:TNode) For Local subNode:TNode = EachIn parent.children If subNode.kind = "Type" Then Local gNode:TGVNode = sub.addNode(subNode.id) gNode.setAttr(ATTR_NODE_SHAPE, "box") gNode.setAttr("style", "filled") gNode.setAttr(ATTR_NODE_FILLCOLOR, "lemonchiffon") If subNode.id = "wxEvent" Then gNode.setAttr("rank", "min") End If If subNode.id = "wxFontMapper" Then gNode.setAttr("rank", "max") End If End If If Not subNode.children.isEmpty() Then processDeep(sub, subNode) End If Next End Function Function doVisuals() Local renderer:TGVGraphviz = TGVGraphviz.Create(1000, 600) Local graph:TGVGraph = TGVGraph.Create() graph.setAttr("clusterrank", "local") 'graph.setAttr("rankdir", "LR") 'graph.setAttr("overlap", "false") 'graph.setAttr("sep", "1") 'graph.setAttr("size", "6,6") ' modules first... For Local node:TNode = EachIn TNode._pathMap.Values() If node.kind = "Module" Then Local sub:TGVSubGraph = graph.addSubGraph("cluster_" + node.id) sub.setAttr(ATTR_LABEL, node.id) sub.setAttr("rank", "sink") sub.setAttr("style", "filled") sub.setAttr(ATTR_NODE_FILLCOLOR, "honeydew") processDeep(sub, node) End If Next ' initial type load... For Local node:TNode = EachIn TNode._pathMap.Values() If node.kind = "Type" Then Local subType:TGVNode = graph.findNode(node.id) If Not subType Then Local gNode:TGVNode = graph.addNode(node.id) gNode.setAttr(ATTR_NODE_SHAPE, "box") gNode.setAttr("style", "filled") gNode.setAttr(ATTR_NODE_FILLCOLOR, "lemonchiffon") End If End If Next ' Any super types? For Local node:TNode = EachIn TNode._needSuper.Values() If node.kind = "Type" Then Local subType:TGVNode = graph.findNode(node.id) Local superType:TGVNode = graph.findNode(node.superTypeName) If Not superType Then superType = graph.AddNode(node.superTypeName) superType.setAttr(ATTR_FONTCOLOR, "red") superType.setAttr(ATTR_NODE_SHAPE, "box") End If ' make the join Local edge:TGVEdge = graph.addEdge(superType, subType) edge.setAttr("dir", "back") edge.setAttr("arrowtail", "empty") End If Next ' now to construct the graph.. renderer.buildGraph(graph) ' and display it.. showGraph(renderer) End Function Function showGraph(renderer:TGVGraphviz) Graphics 1000, 600, 0 SetBlend alphablend renderer.layout("dot") renderer.fit(1000, 600) Local mx:Int Local my:Int Local mz:Int Local oldZ:Int Local buttons:Int[] = New Int[3] While Not KeyDown(KEY_ESCAPE) Cls SetColor(255, 255, 255) mx = MouseX() my = MouseY() mz = MouseZ() ' tell graphviz we've moved the mouse renderer.mouseMove(mx, my) For Local i:Int = 1 To 3 If MouseDown(i) If Not buttons[i - 1] Then renderer.MouseDown(mx, my, i) ' mouse button press buttons[i - 1] = 1 End If Else If buttons[i - 1] Then buttons[i - 1] = 0 renderer.mouseUp(mx, my, i) ' mouse button release End If End If Next If mz <> oldZ Then If mz < oldZ Then renderer.mouseScroll(mx, my, -1) ' scroll up - zoom in Else renderer.mouseScroll(mx, my, 1) ' scroll down - zoom out End If oldZ = mz End If ' refresh/redraw the graph renderer.refresh() ' draw the current tooltip under the mouse - if there is one! renderer.drawTooltip(mx, my + 8) ' get data for selected "node" (for anything else, returns Null) Local obj:TGViewObject = renderer.selectedObject(GV_VIEW_NODE) If obj Then SetImageFont(Null) SetColor 100, 100, 255 DrawText obj.name, 400, 0 DrawText obj.skind, 400, 15 For Local i:Int = 0 Until obj.attributes.length DrawText obj.attributes[i].name + " : " + obj.attributes[i].value, 400, 60 + i * 15 Next End If Flip Wend End Function