||| Copyright (C) 1998-2007, Sumisho Computer Systems Corp.  All Rights Reserved.
|||
||| Maintained by: Curl Solutions

{curl 6.0 applet}
{applet 
    manifest = "manifest.mcurl",
    {compiler-directives
        stringent? = true
    }
}

{import * from CURL.GRAPHICS.SCENE}
{import * from CURL.GRAPHICS.RENDERER3D}
{import * from COM.CURL.CSK.EXTRAS.SHAPES}


{title heading?=false, Molecular Modeling in Curl}

||-- This is the holder for the externally spawned view
{let view:#View}

{let trusted?:bool = {process-privileged?}}

||-- This color spectrum is based on the 3D stereoscopic
||-- color process described at www.chromatek.com.
{let stereoscopic:Spectrum =
    {Spectrum.from-envelope
        "red", 0.0,
        "orange",0.25,
        "yellow",0.5,
        "green",0.75,
        "blue", 1.0
    }
}
{let color-mode-3d?:bool = false}

{let constant colors:{HashTable-of String,Color} =
    {{HashTable-of String,Color}
        "C  ", {Color.from-string "#c8c8c8"},
        "H  ", {Color.from-string "#ffffff"},
        "N  ", {Color.from-string "#8f8fff"},
        "O  ", {Color.from-string "#ff0000"},
        "P  ", {Color.from-string "#ffa500"},
        "Fe ", {Color.from-string "#ffa500"},
        "S  ", {Color.from-string "#ffc832"},
        "Mg ", {Color.from-string "#228b22"},
        "Mn ", {Color.from-string "#808090"},
        "Ca ", {Color.from-string "#808090"},
        "Cl ", {Color.from-string "#00ff00"},
        "B  ", {Color.from-string "#00ff00"},
        "Zn ", {Color.from-string "#a52a2a"},
        "Cu ", {Color.from-string "#a52a2a"},
        "Na ", {Color.from-string "#0000ff"},
        "F  ", {Color.from-string "#daa520"},
        "Si ", {Color.from-string "#daa520"}
    }
}

{let constant color-unknown:Color = {Color.from-string "#ff1493"}}
{let constant highlight-color:Color  = {Palette.get-white}}
{let constant bond-color:Color  = {Color.from-string "#dddddd"}}

{define-proc public {get-colors z:float, atom-symbol:String}:(diffuse:Color, specular:Color)
    {if color-mode-3d? then
        let shade:float = (z - min-z) / delta-z
        let c:Color = {stereoscopic.get-color shade}

        || in stereo mode, we need the highlight to be the same color.
        {return c, c}
     else
        let (color:Color, found?:bool) =
            {colors.get-if-exists atom-symbol}
        {if found? then
            {return color, highlight-color}
         else
            {return color-unknown, highlight-color}
        }
    }
}

{define-proc {generate-color-key}:View
    let key-table:Table =
        {Table {bold Atomic{br}Symbol}, {bold Color}}
    {for color:Color key atom:String in colors do
        {key-table.add 
            {row-prototype atom, 
                {Fill border-color="black", border-width=1pt, 
                    background = color}}}
    }
    {key-table.add {row-prototype {italic default}, 
                       {Fill border-color="black", border-width=1pt, 
                           background = color-unknown}}
    }
    {let color-key:View = {View key-table}}
    set color-key.title = "Key"
    
    {return color-key}
}


{let atom-objects:{Array-of SceneObject} = {{Array-of SceneObject}}}
{let bond-objects:{Array-of SceneObject} = {{Array-of SceneObject}}}
{let bond-thickness:int = 0}

{let max-z:float = min-float}
{let min-z:float = max-float}
{let delta-z:float}

{define-proc public {compare-sphere-depths
                        so1:SceneObject,
                        so2:SceneObject}:bool

    let sphere1:Sphere = so1 asa Sphere
    let sphere2:Sphere = so2 asa Sphere
    {return sphere1.depth >= sphere2.depth}
}


{define-class public final CustomScene {inherits Scene}
  field public frame-count:int = 0

  {constructor public {default ...}
    {with-compiler-directives allow-slow-spliced-arguments? = true do
        {construct-super ...}
    }
  }

  {method public {paint 
                     renderer:Renderer3d,
                     viewport-width:Distance,
                     viewport-height:Distance
                 }:void

    {inc self.frame-count}
    ||    {output {format" %10d scene paint cycles ", {count-cycles num-iterations = 1, {do
    
    {super.paint renderer, viewport-width, viewport-height}
    || }}}}
  }
}

{define-class public final AtomGroup {inherits SceneGroup}
  {method public {paint 
                     renderer:Renderer3d,
                     viewport-width:Distance,
                     viewport-height:Distance
                 }:void
    ||    {output {format" %10d sphere projection & sort", {count-cycles num-iterations = 1, {do
    
    set max-z = min-float
    set min-z = max-float
    
    ||-- First Pass: calculate max/min 2-D Z values
    let camera:Camera = self.scene.camera
    let projection-matrix:Matrix3d = {renderer.projection-matrix.top-clone}
    let viewport-width-f:FloatDistance = viewport-width asa FloatDistance
    let viewport-height-f:FloatDistance = viewport-height asa FloatDistance
    
    {for obj in atom-objects do
        let sphere:Sphere = obj asa Sphere
        let z:float =
            {sphere.compute-z
                camera,
                projection-matrix,
                viewport-width-f,
                viewport-height-f
            }
        
        {if z > max-z then
            set max-z = z
        }
        
        {if z < min-z then
            set min-z = z
        }        
    }
    
    set delta-z = max-z - min-z
    
    {self.objects.sort comparison-proc = compare-sphere-depths}
    
    ||-- Second Pass: assign atom colors based on 2-D Z values
    let white:Texture = {Palette.get-white}
    {for obj in atom-objects do
        let sphere:Sphere = obj asa Sphere
        let (diffuse:Color, specular:Color) =
            {get-colors sphere.depth, sphere.atom-symbol}
        {sphere.set-colors diffuse, specular-color = specular}
    }
    
    ||-- Third Pass: assign line endpoint colors based on 2-D Z values
    {for bond in bond-objects do
        let line:Line = bond asa Line
        {if color-mode-3d? then
            let (start-diffuse:Color, start-specular:Color) =
                {line.start-object.get-colors}
            let (end-diffuse:Color, end-specular:Color) =
                {line.end-object.get-colors}
            || TODO: possibly average specular values.  They'll probably
            || be close, but it might look weird.
            {line.set-colors
                start-diffuse,
                end-diffuse,
                specular-color = start-specular
            }
         else
            {line.set-colors
                bond-color,
                bond-color
            }
        }
    }
    
    || }}}}
    ||    {output {format" %10d sphere rendering", {count-cycles num-iterations = 1, {do
    {super.paint renderer, viewport-width, viewport-height}
    || }}}}
  }
}

||||
|| The SceneGraph
||||
{let group:SceneGroup   = {SceneGroup}}
{let atom-group:SceneGroup = {AtomGroup}}
{let bond-group:SceneGroup = {SceneGroup}}

|| atom-group MUST come first!  The initial pass of calculating min/max z
|| and colors happens there.
{group.add-object atom-group}
{group.add-object bond-group}

{let scene:CustomScene =
    {CustomScene
        group,
        camera =
            {Camera
                projection = Projection.perspective,
                field-of-view = 30deg,
                near-clipping-plane = 5cm,
                far-clipping-plane = 75cm,
                position = {Distance3d 0cm, 0cm, 35cm}
            }
    }
}

{let sg:SceneGraphic =
    {SceneGraphic
        scene,
        width={make-elastic}, height={make-elastic},
        camera-motion-axis="none",
        background = {Color.from-rgb 0,0,0}
    }
}
{sg.add-event-handler
    {on e:PointerPress do
        || no-op if we already have it
        {{get-gui-manager}.grab-pointer sg}
        ||ram
        {after 0s do || Don't update unless necessary
            {SwitchGroup.selectAll
                atom-group,
                {if     atom-objects.size < 8  then 0
                 elseif atom-objects.size < 25 then 1
                 elseif atom-objects.size < 64 then 2
                 elseif bond-thickness == 2    then 2
                 else 3
                }
            }
        }
    }
}
{sg.add-event-handler
    {on e:PointerMotion do
        {if e.state-mask.buttons == 0 then
            {{get-gui-manager}.release-pointer-grab sg}
        }
    }
}
{sg.add-event-handler
    {on e:PointerRelease do
        {if e.state-mask.buttons == 0 then
            {{get-gui-manager}.release-pointer-grab sg}
        }
        {SwitchGroup.selectAll atom-group,0}
        {sg.update-drawable}
    }
}


{let atom-width:Distance = .25cm}
{let sphere-faces:int = 100}

{let MDL-filename:Url = {url "caffeine.mol"}}    ||-- use this as the default
{let molecule-name:TextFlowBox = {TextFlowBox}}
{let molecule-image:Graphic = {VBox
                                  {italic no image},
                                  {italic available}
                              }
}




{do
    {set scene.ambient-light-color = {Color.from-rgb .3,.3,.3}}
    {scene.add-object
        {DirectionalLight
            eye-space?     = true,
            direction      = {Direction3d  -1, -1, -1},
            diffuse-color  = {Color.from-rgb 1.0, 1.0, 1.0},
            specular-color = {Color.from-rgb  .8,  .8,  .8}
        }
    }
}

{let axis:SceneGroup   = {SceneGroup}}
{let axis-displayed?:bool = false}
{axis.add-object
    {ThinLine 
        {Distance3d 0cm, 0cm, 0cm},
        {Distance3d 25cm, 0cm, 0cm},
        fill-pattern = {Palette.get-blue}
    }
}
{axis.add-object
    {ThinLine 
        {Distance3d 0cm, 0cm, 0cm},
        {Distance3d 0cm, 25cm, 0cm},
        fill-pattern = {Palette.get-red}
    }    
}
{axis.add-object
    {ThinLine 
        {Distance3d 0cm, 0cm, 0cm},
        {Distance3d 0cm, 0cm, 25cm},
        fill-pattern = {Palette.get-white}
    }    
}
{if axis-displayed? then
    {group.add-object axis}
}


{define-proc public {parse-molecule-file filename:Url}:void
    let failed:bool = false
    let intext:#TextInputStream
    
    {try
        || Instantiate the input stream from results of read-open
        set intext = {read-open filename}
        || Code to read from input stream
     catch err:MissingFileException do
        || Oops! Handle not finding the file
        {popup-message title = "Error", "File Not Found"}
        set failed = true
     catch err2:PermissionDeniedFileException do
        {popup-message title = "Error",
            "You do not have permission to read the file " & filename & " ."}
        set failed = true
     catch err3:Exception do
        || Oops! Handle not finding the file
        {popup-message title = "Error", "File Not Found"}
        set failed = true
    }

    {if failed then
        {return}
    }
    
||--    {dump filename}

    ||-- delete any existing atom structures
    {for obj in atom-objects do
        {atom-group.remove-object obj}
    }

    {atom-objects.clear}

    ||-- Parse the header ... 3 lines
    ||-- Line 1: molecule name
    ||-- Line 2 (not implemented)
    ||-- Line 3 (not implemented)
    let (buf:StringBuf, bytes-read:int) = {intext.read-one-line}
    {molecule-name.clear}
    {molecule-name.add buf}

    let title:String = {molecule-name.get-text}
    {if-non-null view then
        set view.title = {title.substr 0, (title.size - 2)}
    }
    
    ||-- skip lines 2 and 3
    set (buf, bytes-read) = {intext.read-one-line}
    set (buf, bytes-read) = {intext.read-one-line}
    
    ||-- Read the Counts line
    ||-- format:  aaabbblllfffcccsssxxxrrrpppiiimmmvvvvv
    ||-- aaa = number of atoms
    ||-- bbb = number of bonds
    ||-- see the docs for the rest ... all other are unimplemented for this importer
    set (buf, bytes-read) = {intext.read-one-line}
    let number-of-atoms:int = {{buf.substr 0, 3}.to-int}
||--    {dump number-of-atoms}
    let number-of-bonds:int = {{buf.substr 3, 3}.to-int}
||--    {dump number-of-bonds}    
    {set Sphere.disable-label = number-of-atoms>64} || too busy.
    {let zmin:Distance = 100m, zmax:Distance=-100m} || some models weren't centered.
    {for a:int = 0 below number-of-atoms do
        set (buf, bytes-read) = {intext.read-one-line}
        let x:Distance = {{buf.substr 0, 10}.to-double} * 1cm
        let y:Distance = {{buf.substr 10, 10}.to-double} * 1cm
        let z:Distance = {{buf.substr 20, 10}.to-double} * 1cm
        let atom-symbol:String = {buf.substr 31, 3}
||--        {dump x, y, z, atom-symbol}

        let new-atom:Sphere =
            {Sphere
                {if number-of-atoms < 64 or sphere-faces < 20 then
                    sphere-faces
                 else
                    20
                },
                rad = atom-width,
                atom-symbol = atom-symbol
            }

        let (diffuse:Color, specular:Color) =
            {get-colors (z/1cm) asa float, atom-symbol}
        {new-atom.set-colors diffuse, specular-color = specular}
        set new-atom.world-position = {Distance3d x, y, z}

        {if z < zmin then set zmin = z}
        {if z > zmax then set zmax = z}

        {atom-objects.append new-atom}
    }
    {let zoffset:Distance = -(zmax+zmin)/2}
    
    {for obj in atom-objects do
        {obj.translate 0m,0m,zoffset}
        {atom-group.add-object obj}
    }

    {for bond in bond-objects do
        {bond-group.remove-object bond}
    }

    {bond-objects.clear}

    ||-- Parse the .mol Bond block   
    {for b:int = 0 below number-of-bonds do
        set (buf, bytes-read) = {intext.read-one-line}
        let start:int = {{buf.substr 0, 3}.to-int} - 1
        let end:int = {{buf.substr 3, 3}.to-int} - 1
        let bond-type:int = {{buf.substr 6, 3}.to-int}
        {bond-objects.append
            {Line 
                atom-objects[start].world-position,
                atom-objects[end].world-position,
                atom-objects[start] asa Sphere,
                atom-objects[end] asa Sphere
            }
            || TODO: assign a color here!
        }
    }

    {for bond in bond-objects do
        {bond-group.add-object bond}
    }

    {intext.close}
    {sg.update-drawable}
    
    ||-- Try to update the image file.
    ||-- First, set it back to the default.
    {molecule-image.clear}
    {molecule-image.add {VBox
                            {italic no image},
                            {italic available}
                        }
    }
    
    ||-- Try to open a .gif of the file
    let image-url:Url = {filename.set-extension ".gif"}        
    let error-found:bool = false

    let mol-image:any

    {try 
        set mol-image = 
            {image blocking?=true, 
                ignore-exceptions?=false, source=image-url}
     catch err:MissingFileException do
        set error-found = true
     catch err2:ImageException do
        set error-found = true
     catch err3:Exception do
        set error-found = true
    }
    
    {if error-found then
        set image-url = {filename.set-extension ".jpg"}        
        set error-found = false
        {try 
            set mol-image = 
                {image blocking?=true, 
                    ignore-exceptions?=false, source=image-url}
         catch err:MissingFileException do
            set error-found = true
         catch err2:ImageException do
            set error-found = true
         catch err3:Exception do
            set error-found = true
        }
    }
    
    {if not error-found then
        {molecule-image.clear}
        {molecule-image.add mol-image}
    }    
    {SwitchGroup.selectAll bond-group, bond-thickness}
}

{let scroller:Slider =
    {Slider width = {make-elastic},

        orientation=Orientation.horizontal,
        snap-to-ticks? = false,
        show-labels? = false,
        show-ticks? = false,
        domain = {StandardDoubleDomain
                     default-value = 0.0,
                     min-allowable = 0.0,
                     max-allowable = 200.0
                 },
        {on e:ValueChanged do
            || xxx -- ram start
            || {parse-molecule-file MDL-filename}
            {let sf:double = (scroller.value asa double +1) * .01cm / atom-width}
            {for atom in atom-objects do
                {atom.scale sf,sf,sf}
            }
            {SwitchGroup.selectAll atom-group,
                {if   atom-objects.size < 64 then 1
                 else 2
                }
            }
            {sg.update-drawable}

            || xxx -- ram end
            set atom-width = (scroller.value asa double +1) * .01cm
        },
        {on e:ValueFinished do
            {SwitchGroup.selectAll atom-group, 0}
            {sg.update-drawable}
        }
    }
}
{parse-molecule-file MDL-filename}
{set scroller.value = 30}

{let attached?:bool = true}
{let molecule-box:VBox =
    {VBox
        halign = "center",
        width={make-elastic minimum-size=4.5in},
        height={make-elastic minimum-size=4.5in},
        sg,
        {HBox
            valign = "center",
            spacing = 2mm,
            {text Atom width (nm)},
            scroller
        },
        {value
            let perf-display:TextDisplay = {TextDisplay}
            let perf-full-size:CheckButton = {CheckButton label = "use full detail?", value = false}
            {HBox
                {CommandButton
                    label = "Test Performance",
                    {on Action at button:CommandButton do
                        set button.enabled? = false
                        set perf-display.value = ""

                        {SwitchGroup.selectAll
                            atom-group,
                            {if not (perf-full-size.value asa bool) then
                                {if     atom-objects.size < 8  then 0
                                 elseif atom-objects.size < 25 then 1
                                 elseif atom-objects.size < 64 then 2
                                 elseif bond-thickness == 2    then 2
                                 else 3
                                }
                             else
                                0
                            }
                        }
                        
                        set scene.frame-count = 0
                        let start-time:DateTime = {DateTime}
                        let constant profile-time:FloatTime = 5f(s)
                        let timer:Timer =
                            {Timer
                                frequency = 1000fps,
                                {on TimerEvent do
                                    let elapsed:FloatTime = {start-time.elapsed} asa FloatTime
                                    {if elapsed < profile-time then
                                        {sg.update-drawable}
                                     else
                                        set timer.enabled? = false
                                        
                                        let frames:int = scene.frame-count
                                        let fps:FloatFrequency = frames / elapsed

                                        {SwitchGroup.selectAll atom-group,0}
                                        {sg.update-drawable}
                                        
                                        set perf-display.value = (fps / 1Hz) & " fps"

                                        set button.enabled? = true
                                    }
                                }
                            }
                    }
                },
                {Fill width = 0.125in},
                perf-full-size,
                {Fill width = 0.125in},
                perf-display
            }
        }
    }
}
{let molecule-3d-frame:Frame =
    {Frame
        molecule-box
    }
}

{let display-width:Distance, display-height:Distance}
{do
    let (left:int, top:int, width:int, height:int) =
        {{get-default-display-context}.get-bounds-in-pixels}
    set display-width =
        width * {get-default-display-context}.device-pixel-size
    set display-height =
        height * {get-default-display-context}.device-pixel-size    
}

{let detached-view-menu:MenuAction =
    {MenuAction
        label="Detach 3D View",
        {on Action do
            {if attached? then
                ||-- detach it
                set view =
                    {View
                        width = display-width * .5,
                        height = display-height * .5,
                        center? = true,
                        {Frame
                            molecule-box
                        },
                        {on WindowClose do
                            {molecule-3d-frame.add molecule-box}
                            set detached-view-menu.label = "Detach 3D View"
                            set attached? = true
                            set view = null
                        }
                    }
                let title:String = {molecule-name.get-text}
                set view.title = {title.substr 0, (title.size - 2)}
                {view.show}
                set detached-view-menu.label = "Reattach 3D View"
                set attached? = false
             else
                {view.close}
                set view = null
                set attached? = true
            }            
        }
    }
}

{define-proc {menu-item on?:bool, label:String}:HBox
    {return
        {HBox
            valign = "center",
            spacing = 1mm,
            {if on? then
                {RectangleGraphic width=2.5mm, fill-color="green"}
             else
                {Fill width=2mm}
            },
            {text {value label}}
        }
    }
}

{let axis-off-menu:#MenuAction} 
{let axis-on-menu:MenuAction =
    {MenuAction 
        label = {menu-item false, "On"},
        {on Action do
            {if axis-displayed? != true then
                set axis-displayed? = true
                {group.add-object axis}
                {sg.update-drawable}
                set axis-on-menu.label = {menu-item true, "On"}
                set axis-off-menu.label = {menu-item false, "Off"}
            }
        }
    }
}
{set axis-off-menu =
    {MenuAction 
        label = {menu-item true, "Off"},
        {on Action do
            {if axis-displayed? != false then
                set axis-displayed? = false
                {group.remove-object axis}
                {sg.update-drawable}
                set axis-on-menu.label = {menu-item false, "On"}
                set axis-off-menu.label = {menu-item true, "Off"}
            }
        }
    }
}
{let axis-submenu:SubMenu =
    {SubMenu
        label="Axis Display",
        axis-on-menu,
        axis-off-menu
    }
}

{let sphere-medium:#MenuAction}
{let sphere-high:#MenuAction}
{let sphere-low:MenuAction =
    {MenuAction
        label = {menu-item false, "Low"},
        {on Action at m:MenuAction do
            set sphere-faces = 10
            {parse-molecule-file MDL-filename}
            set sphere-low.label = {menu-item true, "Low"}
            set sphere-medium.label = {menu-item false, "Medium"}
            set sphere-high.label = {menu-item false, "High"}            
        }
    }
}
{set sphere-medium =
    {MenuAction
        label = {menu-item false, "Medium"},
        {on Action at m:MenuAction do
            set sphere-faces = 25
            {parse-molecule-file MDL-filename}
            set sphere-low.label = {menu-item false, "Low"}
            set sphere-medium.label = {menu-item true, "Medium"}
            set sphere-high.label = {menu-item false, "High"}             
        }
    }
}
{set sphere-high =
    {MenuAction
        label = {menu-item true, "High"},
        {on Action do
            set sphere-faces = 100
            {parse-molecule-file MDL-filename}
            set sphere-low.label = {menu-item false, "Low"}
            set sphere-medium.label = {menu-item false, "Medium"}
            set sphere-high.label = {menu-item true, "High"}             
        }
    }
}
{let sphere-submenu:SubMenu =
    {SubMenu
        label="Sphere Resolution",
        sphere-low,
        sphere-medium,
        sphere-high
    }
}





{let bond-none:#MenuAction}
{let bond-thin:#MenuAction}
{let bond-thick:#MenuAction}

{set bond-none =
    {MenuAction
        label = {menu-item false, "None"},
        {on Action do
            set bond-thickness=2
            {SwitchGroup.selectAll bond-group,2}
            set bond-none.label  = {menu-item true, "None"}
            set bond-thin.label  = {menu-item false, "Thin"}
            set bond-thick.label = {menu-item false, "Thick"}
            {sg.update-drawable}
        }
    }
}
{set bond-thin =
    {MenuAction
        label = {menu-item false, "Thin"},
        {on Action do
            set bond-thickness=1
            {SwitchGroup.selectAll bond-group,1}
            set bond-none.label  = {menu-item false, "None"}
            set bond-thin.label  = {menu-item true, "Thin"}
            set bond-thick.label = {menu-item false, "Thick"}
            {sg.update-drawable}
        }
    }
}
{set bond-thick =
    {MenuAction
        label = {menu-item true, "Thick"},
        {on Action do
            {SwitchGroup.selectAll bond-group,0}
            set bond-thickness=0
            set bond-none.label  = {menu-item false, "None"}
            set bond-thin.label  = {menu-item false, "Thin"}
            set bond-thick.label = {menu-item true, "Thick"}
            {sg.update-drawable}
        }
    }
}
{let bond-submenu:SubMenu =
    {SubMenu
        label="Bond Type",
        bond-none,
        bond-thin,
        bond-thick
    }
}
{let color-stereo:#MenuAction}
{let color-coded:MenuAction =
    {MenuAction
        label = {menu-item true, "Color Coded"},
        {on Action do
            set color-mode-3d? = false
            {sg.update-drawable}
            set color-coded.label = {menu-item true, "Color Coded"}      
            set color-stereo.label = {menu-item false, "Stereoscopic"}                        
        }
    }
}
{set color-stereo =
    {MenuAction
        label = {menu-item false, "Stereoscopic"},
        {on Action do
            set color-mode-3d? = true
            {sg.update-drawable}
            set color-coded.label = {menu-item false, "Color Coded"}      
            set color-stereo.label = {menu-item true, "Stereoscopic"}            
        }
    }
}
{let color-mode-submenu:SubMenu =
    {SubMenu
        label="Color Mode",
        color-coded,
        color-stereo
    }
}


{let mol-filter:{Array-of FileDialogFilter} =
    {new {Array-of FileDialogFilter},
        {FileDialogFilter
            "Molecule files",
            {new {Array-of FileDialogTypeFilter},
                {FileDialogTypeFilter "mol"}
            }
        }
    }
}

{define-proc {mol-choice name:String}:MenuAction
    {return
        {MenuAction
            label=name,
            {on Action do
                set MDL-filename = {url name}
                {parse-molecule-file MDL-filename}
            }
        }
    }
}

{define-proc {remote-mol-choice name:String}:MenuAction
    {return
        {MenuAction
            label=name,
            {on Action do
                {if trusted? then
                    set MDL-filename = {url name}
                    {parse-molecule-file MDL-filename}
                 else || need to get PrivilegedUrl from user
                    {if-non-null mol-url = {choose-location
                                               filters = mol-filter,
                                               title="Choose .mol file at another site",
                                               label="Please copy " & name & " into the field below."}
                     then

                        set MDL-filename = mol-url
                        {parse-molecule-file MDL-filename}
                    }
                }
            }
        }
    }
}

{let menubar:MenuBar =
    {MenuBar
        font-size=11pt,
        {SubMenu label="File",
            {mol-choice "caffeine.mol"},
            {mol-choice "nicotine.mol"},
            {mol-choice "dna.mol"},

            {menu-separator},

            {SubMenu label="At other sites",
                {remote-mol-choice "http://www.pharmis.org/database/structure/c_group/cocaine_hydrochloride.mol"},
                {remote-mol-choice "http://www.pharmis.org/database/structure/a_group/aspirin_aluminum.mol"},
                {remote-mol-choice "http://www.pharmis.org/database/structure/i_group/ibuprofen_piconol.mol"}
                
                || NOW also DEAD
||--                {remote-mol-choice "http://wngr343a.chem.orst.edu/~gablek/CH336/Chapter21/morphine.mol"},
||--                {remote-mol-choice "http://wngr343a.chem.orst.edu/~gablek/CH336/Chapter21/codeine.mol"},
||--                {remote-mol-choice "http://wngr343a.chem.orst.edu/~gablek/CH336/Chapter21/heroin.mol"},
||--                {remote-mol-choice "http://wngr343a.chem.orst.edu/~gablek/CH336/Chapter21/cocaine.mol"}
                || dead site {remote-mol-choice "http://ntp-server.niehs.nih.gov/htdocs/structures/3d/TR004.mol"}
            },
            
            {menu-separator},  

            {MenuAction
                label="Enter a URL",
                {on Action do
                    {if-non-null mol-url = {choose-location filters = mol-filter,
                                               title="Enter a URL for a .mol file:"}
                     then
                        set MDL-filename = mol-url
                        {parse-molecule-file MDL-filename}
                    }
                }
            }
        },
        {SubMenu label="Options",
            detached-view-menu,
            sphere-submenu,
            bond-submenu,
            axis-submenu,
            color-mode-submenu
        },
        {SubMenu
            label = "Help",
            {MenuAction
                label = "About the Demo",
                {on Action do
                    {popup-message title="Molecular Modeling in Curl",
                        max-width = 3in,
                        {paragraph
                            Rotate the molecular structure by
                            holding down the left mouse button.
                            Zoom in and out by holding down the
                            right mouse button.

                            The axes (when displayed) are color
                            coded with X blue, Y red, and Z
                            white.

                            Authors: Paul Metzger, Damian Frank, Ron Mayer{br}
                            October, 2001{br}
                            Copyright {copyright} 2001-2006, Sumisho Computer Systems Corp.,
                            All Rights Reserved.
                        }
                    }
                }
            },

            {MenuAction
                label = "Color Code",
                {on Action do
                    {{generate-color-key}.show}
                }
            }
        }
    }    
}

{text-width-display menubar}

{VBox
    halign = "center",

    ||-- this is from the .mol Header block
    {big {bold {value molecule-name}}},

    {HBox 
        valign = "center",
        border-style="raised",
        border-color={Color.from-rgb .2,.2,.9},
        border-width=5pt,
        molecule-3d-frame,
        {Fill width=.25in},
        molecule-image,
        {Fill width=.25in}
    }
}
