<commit_before>/**
 * @file GUI
 * @author <NAME> <<EMAIL>>
 */



NGL.download = function( dataUrl, downloadName ){

    if( !dataUrl ){
        console.warn( "NGL.download: no dataUrl given" );
        return;
    }

    downloadName = downloadName || "download";

    var a = document.createElement( 'a' );
    document.body.appendChild( a );
    a.href = dataUrl;
    a.download = downloadName;
    a.target = "_blank";
    a.click();

    document.body.removeChild( a );

};


NGL.Widget = function(){

};

NGL.Widget.prototype = {

};


NGL.ViewportWidget = function( stage ){

    var viewer = stage.viewer;
    var renderer = viewer.renderer;

    var container = new UI.Panel();
    container.setPosition( 'absolute' );

    viewer.container = container.dom;
    container.dom.appendChild( renderer.domElement );

    // event handlers

    container.dom.addEventListener( 'dragover', function( e ){

        e.stopPropagation();
        e.preventDefault();
        e.dataTransfer.dropEffect = 'copy';

    }, false );

    container.dom.addEventListener( 'drop', function( e ){

        e.stopPropagation();
        e.preventDefault();

        var fileList = e.dataTransfer.files;
        var n = fileList.length;

        for( var i=0; i<n; ++i ){

            stage.loadFile( fileList[ i ] );

        }

    }, false );


    return container;

};


NGL.ToolbarWidget = function( stage ){

    var signals = stage.signals;
    var container = new UI.Panel();

    var messagePanel = new UI.Panel();

    signals.atomPicked.add( function( atom ){

        var name = "none";

        if( atom ){
            name = atom.qualifiedName() + 
                " (" + atom.residue.chain.model.structure.name + ")";
        }

        messagePanel
            .clear()
            .add( new UI.Text( "Picked: " + name ) );
        
    } );

    container.add( messagePanel );

    return container;

};


NGL.MenubarWidget = function( stage ){

    var container = new UI.Panel();

    container.add( new NGL.MenubarFileWidget( stage ) );
    container.add( new NGL.MenubarViewWidget( stage ) );
    container.add( new NGL.MenubarExampleWidget( stage ) );
    container.add( new NGL.MenubarHelpWidget( stage ) );

    return container;

};


NGL.MenubarFileWidget = function( stage ){

    var fileInput = document.createElement("input");
    fileInput.type = "file";
    fileInput.multiple = true;
    fileInput.style = "visibility:hidden";
    fileInput.addEventListener( 'change', function( e ){

        var fileList = e.target.files;
        var n = fileList.length;

        for( var i=0; i<n; ++i ){

            addFile( fileList[ i ] );

        }

    }, false );

    function addFile( path ){

        stage.loadFile( path );

    }

    // event handlers

    function onOpenOptionClick () {

        fileInput.dispatchEvent( new MouseEvent('click', {
            'view': window,
            'bubbles': true,
            'cancelable': true
        }));

    }

    function onImportOptionClick(){

        var dirWidget = new NGL.DirectoryListingWidget(

            stage, "Import file", [ "pdb", "gro", "obj", "ply" ],

            function( path ){

                var ext = path.path.split('.').pop().toLowerCase();

                if( ext == "pdb" || ext == "gro" || 
                    ext == "obj" || ext == "ply" ){

                    stage.loadFile( "../data/" + path.path );
                    
                }else{

                    console.log( "unknown filetype: " + ext );

                }

                dirWidget.dispose();

            }

        );

        dirWidget
            .setOpacity( "0.8" )
            .setLeft( "50px" )
            .setTop( "80px" )
            .attach();

    }

    function onExportImageOptionClick () {

        NGL.download( stage.viewer.getImage(), "screenshot.png" );

    }

    function onPdbInputKeyDown ( e ) {

        if( e.keyCode === 13 ){

            addFile( e.target.value );
            e.target.value = "";

        }

    }

    // configure menu contents

    var createOption = UI.MenubarHelper.createOption;
    var createInput = UI.MenubarHelper.createInput;
    var createDivider = UI.MenubarHelper.createDivider;

    var menuConfig = [
        createOption( 'Open...', onOpenOptionClick ),
        createOption( 'Import...', onImportOptionClick ),
        createInput( 'PDB', onPdbInputKeyDown ),
        createDivider(),
        createOption( 'Export image', onExportImageOptionClick ),
    ];

    var optionsPanel = UI.MenubarHelper.createOptionsPanel( menuConfig );

    return UI.MenubarHelper.createMenuContainer( 'File', optionsPanel );

};


NGL.MenubarViewWidget = function( stage ){

    function setTheme( value ) {

        document.getElementById( 'theme' ).href = value;

    }

    // event handlers

    function onLightThemeOptionClick () {

        setTheme( '../css/light.css' );
        stage.viewer.setBackground( "white" );
        // editor.config.setKey( 'theme', 'css/light.css' );

    }

    function onDarkThemeOptionClick () {

        setTheme( '../css/dark.css' );
        stage.viewer.setBackground( "black" );
        // editor.config.setKey( 'theme', 'css/dark.css' );

    }

    function onFullScreenOptionClick () {

        stage.viewer.fullscreen();

    }

    // configure menu contents

    var createOption = UI.MenubarHelper.createOption;
    var createDivider = UI.MenubarHelper.createDivider;

    var menuConfig = [
        createOption( 'Light theme', onLightThemeOptionClick ),
        createOption( 'Dark theme', onDarkThemeOptionClick ),
        createDivider(),
        createOption( 'Full screen', onFullScreenOptionClick, 'expand' )
    ];

    var optionsPanel = UI.MenubarHelper.createOptionsPanel( menuConfig );

    return UI.MenubarHelper.createMenuContainer( 'View', optionsPanel );

};


NGL.MenubarExampleWidget = function( stage ){

    // event handlers

    function makeOnFileClick( file ) {

        return function(){

            stage.loadFile( '../data/__example__/' + file );

        }

    }

    function onTrajClick( file ) {

        NGL.Examples.load( "anim_trajectory", stage );

    }

    // configure menu contents

    var createOption = UI.MenubarHelper.createOption;
    var createDivider = UI.MenubarHelper.createDivider;

    var menuConfig = [

        createOption( '1r6a.pdb', makeOnFileClick( '1R6A.pdb' ) ),
        createOption( '1blu.pdb', makeOnFileClick( '1blu.pdb' ) ),
        createOption( '1crn.pdb', makeOnFileClick( '1crn.pdb' ) ),
        createOption( '1d66.pdb', makeOnFileClick( '1d66.pdb' ) ),
        createOption( '1jj2.pdb', makeOnFileClick( '1jj2.pdb' ) ),
        createOption( '1lvz.pdb', makeOnFileClick( '1LVZ.pdb' ) ),
        createOption( '1mf6.pdb', makeOnFileClick( '1MF6.pdb' ) ),
        createOption( '304d.pdb', makeOnFileClick( '304d.pdb' ) ),
        createOption( '3dqb.pdb', makeOnFileClick( '3dqb.pdb' ) ),
        createOption( '3l5q.pdb', makeOnFileClick( '3l5q.pdb' ) ),
        createOption( '3pqr.pdb', makeOnFileClick( '3pqr.pdb' ) ),
        createOption( '3sn6.pdb', makeOnFileClick( '3sn6.pdb' ) ),
        createOption( 'BaceCg.pdb', makeOnFileClick( 'BaceCg.pdb' ) ),
        createOption( 'hem.pdb', makeOnFileClick( 'hem.pdb' ) ),
        createOption( 'md.pdb', makeOnFileClick( 'md.pdb' ) ),

        createDivider(),

        createOption( 'md.gro', makeOnFileClick( 'md.gro' ) ),

        createDivider(),

        createOption( '1crn.obj', makeOnFileClick( '1crn.obj' ) ),
        createOption( '1crn.ply', makeOnFileClick( '1crn.ply' ) ),
        createOption( '3dqb.obj', makeOnFileClick( '3dqb.obj' ) ),

        createDivider(),

        createOption( 'trajectory', onTrajClick )

    ];

    var optionsPanel = UI.MenubarHelper.createOptionsPanel( menuConfig );

    return UI.MenubarHelper.createMenuContainer( 'Example', optionsPanel )
        .setWidth( "80px" );

};


NGL.MenubarHelpWidget = function( stage ){

    // event handlers

    function onRcsbPdbOptionClick () {

        window.open( 'http://www.rcsb.org', '_blank' );

    }

    // configure menu contents

    var createOption = UI.MenubarHelper.createOption;
    var createDivider = UI.MenubarHelper.createDivider;

    var menuConfig = [
        createOption( 'RCSB PDB', onRcsbPdbOptionClick )
    ];

    var optionsPanel = UI.MenubarHelper.createOptionsPanel( menuConfig );

    return UI.MenubarHelper.createMenuContainer( 'Help', optionsPanel );

};


NGL.SidebarWidget = function( stage ){

    var signals = stage.signals;
    var container = new UI.Panel();

    signals.componentAdded.add( function( component ){

        console.log( component );

        if( component instanceof NGL.StructureComponent ){

            container.add( new NGL.StructureComponentWidget( component, stage ) );

        }else if( component instanceof NGL.SurfaceComponent ){

            container.add( new NGL.SurfaceComponentWidget( component, stage ) );

        }else{

            console.warn( "NGL.SidebarWidget: component type unknown", component );

        }

    } );

    return container;

};


NGL.ComponentWidget = function( component, stage ){

    var container = new UI.Panel();

    

    return container;

};


NGL.StructureComponentWidget = function( component, stage ){

    var signals = component.signals;
    var container = new UI.CollapsiblePanel();

    var reprContainer = new UI.Panel();
    var trajContainer = new UI.Panel();

    signals.representationAdded.add( function( repr ){

        reprContainer.add( new NGL.RepresentationWidget( repr, component ) );
        
    } );

    signals.trajectoryAdded.add( function( traj ){

        trajContainer.add( new NGL.TrajectoryWidget( traj, component ) );
        
    } );

    signals.visibilityChanged.add( function( value ){

        if( value ){
            toggle.removeClass( "eye-slash", "eye" ).addClass( "eye" );
        }else{
            toggle.removeClass( "eye", "eye-slash" ).addClass( "eye-slash" );
        }
        
    } );

    // Actions

    var toggle = new UI.Icon( "eye" )
        .setTitle( "hide/show" )
        .setMarginLeft( "25px" )
        .onClick( function(){

            if( toggle.hasClass( "eye" ) ){
                component.setVisibility( false );
            }else{
                component.setVisibility( true );
            }

        } );

    var center = new UI.Icon( "bullseye" )
        .setTitle( "center" )
        .setMarginLeft( "10px" )
        .onClick( function(){

            component.centerView( "backbone" );

        } );

    var dispose = new UI.Icon( "trash-o" )
        .setTitle( "delete" )
        .setMarginLeft( "10px" )
        .onClick( function(){

            if( dispose.getColor() === "rgb(178, 34, 34)" ){

                stage.removeComponent( component );
                container.dispose();

            }else{

                dispose.setColor( "rgb(178, 34, 34)" );

                setTimeout( function(){ 
                    dispose.setColor( "#888" );
                }, 1000);

            }

        } );

    // Selection for subset

    var seleRow = new UI.Panel();
    var sele = new UI.Input()
        .setWidth( '190px' ).onKeyDown( function( e ){
            
            if( e.keyCode === 13 ){

                component.changeSelection( sele.getValue() );

            }

        } );

    if( component.sele ){
        sele.setValue( component.sele );
    }
 
    seleRow.add( new UI.Text( 'Sele' ).setWidth( '45px' ).setMarginLeft( "20px" ) );
    seleRow.add( sele );

    container.add( seleRow );

    // Export PDB
    
    var pdb = new UI.Button( "export" ).onClick( function(){

        // https://github.com/eligrey/FileSaver.js/blob/master/FileSaver.js

        var blob = new Blob(
            [ component.structure.toPdb() ],
            { type: 'text/plain' }
        );

        NGL.download( URL.createObjectURL( blob ), "structure.pdb" );

        menuPanel.setDisplay( "none" );

    } );

    // Add representation

    var reprOptions = { "": "[ add ]" };
    for( var key in NGL.representationTypes ){
        reprOptions[ key ] = key;
    }

    var repr = new UI.Select()
        .setColor( '#444' )
        .setOptions( reprOptions )
        .onChange( function(){

            component.addRepresentation( repr.getValue() );
            repr.setValue( "" );
            menuPanel.setDisplay( "none" );

        } );

    // Import trajectory

    var traj = new UI.Button( "import" ).onClick( function(){

        menuPanel.setDisplay( "none" );

        var dirWidget = new NGL.DirectoryListingWidget(

            stage, "Import trajectory", [ "xtc" ],

            function( path ){

                var ext = path.path.split('.').pop().toLowerCase();

                if( ext == "xtc" ){

                    console.log( path );

                    component.addTrajectory( path.path );

                    dirWidget.dispose();
                    
                }else{

                    console.log( "unknown trajectory type: " + ext );

                }

            }

        );

        dirWidget
            .setOpacity( "0.8" )
            .setLeft( "50px" )
            .setTop( "80px" )
            .attach();

    });

    // Superpose

    function setSuperposeOptions(){

        var superposeOptions = { "": "[ structure ]" };
        stage.compList.forEach( function( o, i ){
            if( o instanceof NGL.StructureComponent && o !== component ){
                superposeOptions[ i ] = o.name;
            }
        } );
        superpose.setOptions( superposeOptions );

    }

    stage.signals.componentAdded.add( setSuperposeOptions );
    stage.signals.componentRemoved.add( setSuperposeOptions );

    var superpose = new UI.Select()
        .setColor( '#444' )
        .onChange( function(){

            var s1 = component.structure;
            var s2 = stage.compList[ superpose.getValue() ].structure;

            NGL.superpose( s1, s2, true );

            component.updateRepresentations();
            component.centerView();

            superpose.setValue( "" );
            menuPanel.setDisplay( "none" );

        } );

    setSuperposeOptions();

    // Menu

    var menuPanel = new UI.OverlayPanel()
        .add( new UI.Text( "PDB file" ).setWidth( "110px" ) )
        .add( pdb )
        .add( new UI.Break() )
        .add( new UI.Text( "Representation" ).setWidth( "110px" ) )
        .add( repr )
        .add( new UI.Break() )
        .add( new UI.Text( "Trajectory" ).setWidth( "110px" ) )
        .add( traj )
        .add( new UI.Break() )
        .add( new UI.Text( "Superpose" ).setWidth( "110px" ) )
        .add( superpose );

    var menu = new UI.Icon( "bars" )
        .setTitle( "menu" )
        .setMarginLeft( "47px" )
        .onClick( function(){

            if( menuPanel.getDisplay() === "block" ){

                menuPanel.setDisplay( "none" );
                return;

            }

            var box = menu.getBox();

            menuPanel
                .setRight( ( window.innerWidth - box.left + 10 ) + "px" )
                .setTop( box.top + "px" )
                .setDisplay( "block" )
                .attach();

        } );

    

    container.addStatic( new UI.Text( component.name ).setWidth( "100px" ) );
    container.addStatic( toggle );
    container.addStatic( center );
    container.addStatic( dispose );
    container.addStatic( menu );
    // container.addStatic( repr );

    // Fill container

    container.add( trajContainer );
    container.add( reprContainer );

    return container;

};


NGL.SurfaceComponentWidget = function( component, stage ){

    var signals = component.signals;
    var container = new UI.CollapsiblePanel();

    signals.visibilityChanged.add( function( value ){

        if( value ){
            toggle.removeClass( "eye-slash", "eye" ).addClass( "eye" );
        }else{
            toggle.removeClass( "eye", "eye-slash" ).addClass( "eye-slash" );
        }
        
    } );

    // Actions

    var toggle = new UI.Icon( "eye" )
        .setTitle( "hide/show" )
        .setMarginLeft( "25px" )
        .onClick( function(){

            if( toggle.hasClass( "eye" ) ){
                component.setVisibility( false );
            }else{
                component.setVisibility( true );
            }

        } );

    var center = new UI.Icon( "bullseye" )
        .setTitle( "center" )
        .setMarginLeft( "10px" )
        .onClick( function(){

            component.centerView( "backbone" );

        } );

    var dispose = new UI.Icon( "trash-o" )
        .setTitle( "delete" )
        .setMarginLeft( "10px" )
        .onClick( function(){

            if( dispose.getColor() === "rgb(178, 34, 34)" ){

                stage.removeComponent( component );
                container.dispose();

            }else{

                dispose.setColor( "rgb(178, 34, 34)" );

                setTimeout( function(){ 
                    dispose.setColor( "#888" );
                }, 1000);

            }

        } );
    
    container.addStatic( new UI.Text( component.name ).setWidth( "100px" ) );
    container.addStatic( toggle );
    container.addStatic( center );
    container.addStatic( dispose );

    return container;

};


NGL.RepresentationWidget = function( repr, component ){

    var signals = repr.signals;

    var container = new UI.CollapsiblePanel()
        .setMarginLeft( "20px" );

    signals.visibilityChanged.add( function( value ){

        if( value ){
            toggle.removeClass( "eye-slash", "eye" ).addClass( "eye" );
        }else{
            toggle.removeClass( "eye", "eye-slash" ).addClass( "eye-slash" );
        }
        
    } );

    component.signals.representationRemoved.add( function( _repr ){

        if( repr === _repr ) container.dispose();
        
    } );

    // Actions

    var toggle = new UI.Icon( "eye" )
        .setTitle( "hide/show" )
        .setMarginLeft( "25px" )
        .onClick( function(){

            if( toggle.hasClass( "eye" ) ){
                repr.setVisibility( false );
            }else{
                repr.setVisibility( true );
            }

        } );

    var dispose = new UI.Icon( "trash-o" )
        .setTitle( "delete" )
        .setMarginLeft( "10px" )
        .onClick( function(){

            if( dispose.getColor() === "rgb(178, 34, 34)" ){

                component.removeRepresentation( repr );

            }else{

                dispose.setColor( "rgb(178, 34, 34)" );

                setTimeout( function(){ 
                    dispose.setColor( "#888" );
                }, 1000);
                
            }

        } );

    container.addStatic( new UI.Text( repr.name ).setWidth( "80px" ) );
    container.addStatic( toggle );
    container.addStatic( dispose );

    // Add sele

    var seleRow = new UI.Panel();
    var sele = new UI.Input()
        .setWidth( '170px' ).onKeyDown( function( e ){
            
            if( e.keyCode === 13 ){

                repr.changeSelection( sele.getValue() );

            }

        } );

    if( repr.selection ){
        sele.setValue( repr.selection.selectionStr );
    }
 
    seleRow.add( new UI.Text( 'Sele' ).setWidth( '45px' ).setMarginLeft( "20px" ) );
    seleRow.add( sele );

    container.add( seleRow );

    return container;

};


NGL.TrajectoryWidget = function( traj, component ){

    var signals = traj.signals;

    var container = new UI.CollapsiblePanel()
        .setMarginLeft( "20px" );

    component.signals.trajectoryRemoved.add( function( _traj ){

        if( traj === _traj ) container.dispose();
        
    } );

    var numframes = new UI.Panel()
        .setMarginLeft( "10px" )
        .setDisplay( "inline" )
        .add( new UI.Icon( "spinner" ).addClass( "spin" ) );

    var numcache = new UI.Text( "0" )
        .setMarginLeft( "10px" );

    signals.gotNumframes.add( function( value ){

        numframes.clear().add( new UI.Text( value ) );
        frame.setRange( 0, value - 1 );
        frame2.setRange( 0, value - 1 );

        step.setValue( Math.ceil( ( value + 1 ) / 100 ) );
        
    } );

    //1000 = n / step 

    signals.frameChanged.add( function( value ){

        frame.setValue( value );
        frame2.setValue( value );

        numcache.setValue( traj.frameCacheSize );

        inProgress = false;
        
    } );

    container.addStatic( new UI.Text( "Trajectory" ) );
    container.addStatic( numframes );
    container.addStatic( new UI.Text( "Cache" ).setMarginLeft( "20px" ) );
    container.addStatic( numcache );

    // frames

    var frameRow = new UI.Panel();

    var frame = new UI.Integer( -1 )
        .setMarginLeft( "5px" )
        .setWidth( "70px" )
        .setRange( -1, -1 )
        .onChange( function( e ){

            traj.setFrame( frame.getValue() );

        } );
<commit_msg>CHANGE:  trajectory ui simpler<commit_after>/**
 * @file GUI
 * @author <NAME> <<EMAIL>>
 */



NGL.download = function( dataUrl, downloadName ){

    if( !dataUrl ){
        console.warn( "NGL.download: no dataUrl given" );
        return;
    }

    downloadName = downloadName || "download";

    var a = document.createElement( 'a' );
    document.body.appendChild( a );
    a.href = dataUrl;
    a.download = downloadName;
    a.target = "_blank";
    a.click();

    document.body.removeChild( a );

};


NGL.Widget = function(){

};

NGL.Widget.prototype = {

};


NGL.ViewportWidget = function( stage ){

    var viewer = stage.viewer;
    var renderer = viewer.renderer;

    var container = new UI.Panel();
    container.setPosition( 'absolute' );

    viewer.container = container.dom;
    container.dom.appendChild( renderer.domElement );

    // event handlers

    container.dom.addEventListener( 'dragover', function( e ){

        e.stopPropagation();
        e.preventDefault();
        e.dataTransfer.dropEffect = 'copy';

    }, false );

    container.dom.addEventListener( 'drop', function( e ){

        e.stopPropagation();
        e.preventDefault();

        var fileList = e.dataTransfer.files;
        var n = fileList.length;

        for( var i=0; i<n; ++i ){

            stage.loadFile( fileList[ i ] );

        }

    }, false );


    return container;

};


NGL.ToolbarWidget = function( stage ){

    var signals = stage.signals;
    var container = new UI.Panel();

    var messagePanel = new UI.Panel();

    signals.atomPicked.add( function( atom ){

        var name = "none";

        if( atom ){
            name = atom.qualifiedName() + 
                " (" + atom.residue.chain.model.structure.name + ")";
        }

        messagePanel
            .clear()
            .add( new UI.Text( "Picked: " + name ) );
        
    } );

    container.add( messagePanel );

    return container;

};


NGL.MenubarWidget = function( stage ){

    var container = new UI.Panel();

    container.add( new NGL.MenubarFileWidget( stage ) );
    container.add( new NGL.MenubarViewWidget( stage ) );
    container.add( new NGL.MenubarExampleWidget( stage ) );
    container.add( new NGL.MenubarHelpWidget( stage ) );

    return container;

};


NGL.MenubarFileWidget = function( stage ){

    var fileInput = document.createElement("input");
    fileInput.type = "file";
    fileInput.multiple = true;
    fileInput.style = "visibility:hidden";
    fileInput.addEventListener( 'change', function( e ){

        var fileList = e.target.files;
        var n = fileList.length;

        for( var i=0; i<n; ++i ){

            addFile( fileList[ i ] );

        }

    }, false );

    function addFile( path ){

        stage.loadFile( path );

    }

    // event handlers

    function onOpenOptionClick () {

        fileInput.dispatchEvent( new MouseEvent('click', {
            'view': window,
            'bubbles': true,
            'cancelable': true
        }));

    }

    function onImportOptionClick(){

        var dirWidget = new NGL.DirectoryListingWidget(

            stage, "Import file", [ "pdb", "gro", "obj", "ply" ],

            function( path ){

                var ext = path.path.split('.').pop().toLowerCase();

                if( ext == "pdb" || ext == "gro" || 
                    ext == "obj" || ext == "ply" ){

                    stage.loadFile( "../data/" + path.path );
                    
                }else{

                    console.log( "unknown filetype: " + ext );

                }

                dirWidget.dispose();

            }

        );

        dirWidget
            .setOpacity( "0.8" )
            .setLeft( "50px" )
            .setTop( "80px" )
            .attach();

    }

    function onExportImageOptionClick () {

        NGL.download( stage.viewer.getImage(), "screenshot.png" );

    }

    function onPdbInputKeyDown ( e ) {

        if( e.keyCode === 13 ){

            addFile( e.target.value );
            e.target.value = "";

        }

    }

    // configure menu contents

    var createOption = UI.MenubarHelper.createOption;
    var createInput = UI.MenubarHelper.createInput;
    var createDivider = UI.MenubarHelper.createDivider;

    var menuConfig = [
        createOption( 'Open...', onOpenOptionClick ),
        createOption( 'Import...', onImportOptionClick ),
        createInput( 'PDB', onPdbInputKeyDown ),
        createDivider(),
        createOption( 'Export image', onExportImageOptionClick ),
    ];

    var optionsPanel = UI.MenubarHelper.createOptionsPanel( menuConfig );

    return UI.MenubarHelper.createMenuContainer( 'File', optionsPanel );

};


NGL.MenubarViewWidget = function( stage ){

    function setTheme( value ) {

        document.getElementById( 'theme' ).href = value;

    }

    // event handlers

    function onLightThemeOptionClick () {

        setTheme( '../css/light.css' );
        stage.viewer.setBackground( "white" );
        // editor.config.setKey( 'theme', 'css/light.css' );

    }

    function onDarkThemeOptionClick () {

        setTheme( '../css/dark.css' );
        stage.viewer.setBackground( "black" );
        // editor.config.setKey( 'theme', 'css/dark.css' );

    }

    function onFullScreenOptionClick () {

        stage.viewer.fullscreen();

    }

    // configure menu contents

    var createOption = UI.MenubarHelper.createOption;
    var createDivider = UI.MenubarHelper.createDivider;

    var menuConfig = [
        createOption( 'Light theme', onLightThemeOptionClick ),
        createOption( 'Dark theme', onDarkThemeOptionClick ),
        createDivider(),
        createOption( 'Full screen', onFullScreenOptionClick, 'expand' )
    ];

    var optionsPanel = UI.MenubarHelper.createOptionsPanel( menuConfig );

    return UI.MenubarHelper.createMenuContainer( 'View', optionsPanel );

};


NGL.MenubarExampleWidget = function( stage ){

    // event handlers

    function makeOnFileClick( file ) {

        return function(){

            stage.loadFile( '../data/__example__/' + file );

        }

    }

    function onTrajClick( file ) {

        NGL.Examples.load( "anim_trajectory", stage );

    }

    // configure menu contents

    var createOption = UI.MenubarHelper.createOption;
    var createDivider = UI.MenubarHelper.createDivider;

    var menuConfig = [

        createOption( '1r6a.pdb', makeOnFileClick( '1R6A.pdb' ) ),
        createOption( '1blu.pdb', makeOnFileClick( '1blu.pdb' ) ),
        createOption( '1crn.pdb', makeOnFileClick( '1crn.pdb' ) ),
        createOption( '1d66.pdb', makeOnFileClick( '1d66.pdb' ) ),
        createOption( '1jj2.pdb', makeOnFileClick( '1jj2.pdb' ) ),
        createOption( '1lvz.pdb', makeOnFileClick( '1LVZ.pdb' ) ),
        createOption( '1mf6.pdb', makeOnFileClick( '1MF6.pdb' ) ),
        createOption( '304d.pdb', makeOnFileClick( '304d.pdb' ) ),
        createOption( '3dqb.pdb', makeOnFileClick( '3dqb.pdb' ) ),
        createOption( '3l5q.pdb', makeOnFileClick( '3l5q.pdb' ) ),
        createOption( '3pqr.pdb', makeOnFileClick( '3pqr.pdb' ) ),
        createOption( '3sn6.pdb', makeOnFileClick( '3sn6.pdb' ) ),
        createOption( 'BaceCg.pdb', makeOnFileClick( 'BaceCg.pdb' ) ),
        createOption( 'hem.pdb', makeOnFileClick( 'hem.pdb' ) ),
        createOption( 'md.pdb', makeOnFileClick( 'md.pdb' ) ),

        createDivider(),

        createOption( 'md.gro', makeOnFileClick( 'md.gro' ) ),

        createDivider(),

        createOption( '1crn.obj', makeOnFileClick( '1crn.obj' ) ),
        createOption( '1crn.ply', makeOnFileClick( '1crn.ply' ) ),
        createOption( '3dqb.obj', makeOnFileClick( '3dqb.obj' ) ),

        createDivider(),

        createOption( 'trajectory', onTrajClick )

    ];

    var optionsPanel = UI.MenubarHelper.createOptionsPanel( menuConfig );

    return UI.MenubarHelper.createMenuContainer( 'Example', optionsPanel )
        .setWidth( "80px" );

};


NGL.MenubarHelpWidget = function( stage ){

    // event handlers

    function onRcsbPdbOptionClick () {

        window.open( 'http://www.rcsb.org', '_blank' );

    }

    // configure menu contents

    var createOption = UI.MenubarHelper.createOption;
    var createDivider = UI.MenubarHelper.createDivider;

    var menuConfig = [
        createOption( 'RCSB PDB', onRcsbPdbOptionClick )
    ];

    var optionsPanel = UI.MenubarHelper.createOptionsPanel( menuConfig );

    return UI.MenubarHelper.createMenuContainer( 'Help', optionsPanel );

};


NGL.SidebarWidget = function( stage ){

    var signals = stage.signals;
    var container = new UI.Panel();

    signals.componentAdded.add( function( component ){

        console.log( component );

        if( component instanceof NGL.StructureComponent ){

            container.add( new NGL.StructureComponentWidget( component, stage ) );

        }else if( component instanceof NGL.SurfaceComponent ){

            container.add( new NGL.SurfaceComponentWidget( component, stage ) );

        }else{

            console.warn( "NGL.SidebarWidget: component type unknown", component );

        }

    } );

    return container;

};


NGL.ComponentWidget = function( component, stage ){

    var container = new UI.Panel();

    

    return container;

};


NGL.StructureComponentWidget = function( component, stage ){

    var signals = component.signals;
    var container = new UI.CollapsiblePanel();

    var reprContainer = new UI.Panel();
    var trajContainer = new UI.Panel();

    signals.representationAdded.add( function( repr ){

        reprContainer.add( new NGL.RepresentationWidget( repr, component ) );
        
    } );

    signals.trajectoryAdded.add( function( traj ){

        trajContainer.add( new NGL.TrajectoryWidget( traj, component ) );
        
    } );

    signals.visibilityChanged.add( function( value ){

        if( value ){
            toggle.removeClass( "eye-slash", "eye" ).addClass( "eye" );
        }else{
            toggle.removeClass( "eye", "eye-slash" ).addClass( "eye-slash" );
        }
        
    } );

    // Actions

    var toggle = new UI.Icon( "eye" )
        .setTitle( "hide/show" )
        .setMarginLeft( "25px" )
        .onClick( function(){

            if( toggle.hasClass( "eye" ) ){
                component.setVisibility( false );
            }else{
                component.setVisibility( true );
            }

        } );

    var center = new UI.Icon( "bullseye" )
        .setTitle( "center" )
        .setMarginLeft( "10px" )
        .onClick( function(){

            component.centerView( "backbone" );

        } );

    var dispose = new UI.Icon( "trash-o" )
        .setTitle( "delete" )
        .setMarginLeft( "10px" )
        .onClick( function(){

            if( dispose.getColor() === "rgb(178, 34, 34)" ){

                stage.removeComponent( component );
                container.dispose();

            }else{

                dispose.setColor( "rgb(178, 34, 34)" );

                setTimeout( function(){ 
                    dispose.setColor( "#888" );
                }, 1000);

            }

        } );

    // Selection for subset

    var seleRow = new UI.Panel();
    var sele = new UI.Input()
        .setWidth( '190px' ).onKeyDown( function( e ){
            
            if( e.keyCode === 13 ){

                component.changeSelection( sele.getValue() );

            }

        } );

    if( component.sele ){
        sele.setValue( component.sele );
    }
 
    seleRow.add( new UI.Text( 'Sele' ).setWidth( '45px' ).setMarginLeft( "20px" ) );
    seleRow.add( sele );

    container.add( seleRow );

    // Export PDB
    
    var pdb = new UI.Button( "export" ).onClick( function(){

        // https://github.com/eligrey/FileSaver.js/blob/master/FileSaver.js

        var blob = new Blob(
            [ component.structure.toPdb() ],
            { type: 'text/plain' }
        );

        NGL.download( URL.createObjectURL( blob ), "structure.pdb" );

        menuPanel.setDisplay( "none" );

    } );

    // Add representation

    var reprOptions = { "": "[ add ]" };
    for( var key in NGL.representationTypes ){
        reprOptions[ key ] = key;
    }

    var repr = new UI.Select()
        .setColor( '#444' )
        .setOptions( reprOptions )
        .onChange( function(){

            component.addRepresentation( repr.getValue() );
            repr.setValue( "" );
            menuPanel.setDisplay( "none" );

        } );

    // Import trajectory

    var traj = new UI.Button( "import" ).onClick( function(){

        menuPanel.setDisplay( "none" );

        var dirWidget = new NGL.DirectoryListingWidget(

            stage, "Import trajectory", [ "xtc" ],

            function( path ){

                var ext = path.path.split('.').pop().toLowerCase();

                if( ext == "xtc" ){

                    console.log( path );

                    component.addTrajectory( path.path );

                    dirWidget.dispose();
                    
                }else{

                    console.log( "unknown trajectory type: " + ext );

                }

            }

        );

        dirWidget
            .setOpacity( "0.8" )
            .setLeft( "50px" )
            .setTop( "80px" )
            .attach();

    });

    // Superpose

    function setSuperposeOptions(){

        var superposeOptions = { "": "[ structure ]" };
        stage.compList.forEach( function( o, i ){
            if( o instanceof NGL.StructureComponent && o !== component ){
                superposeOptions[ i ] = o.name;
            }
        } );
        superpose.setOptions( superposeOptions );

    }

    stage.signals.componentAdded.add( setSuperposeOptions );
    stage.signals.componentRemoved.add( setSuperposeOptions );

    var superpose = new UI.Select()
        .setColor( '#444' )
        .onChange( function(){

            var s1 = component.structure;
            var s2 = stage.compList[ superpose.getValue() ].structure;

            NGL.superpose( s1, s2, true );

            component.updateRepresentations();
            component.centerView();

            superpose.setValue( "" );
            menuPanel.setDisplay( "none" );

        } );

    setSuperposeOptions();

    // Menu

    var menuPanel = new UI.OverlayPanel()
        .add( new UI.Text( "PDB file" ).setWidth( "110px" ) )
        .add( pdb )
        .add( new UI.Break() )
        .add( new UI.Text( "Representation" ).setWidth( "110px" ) )
        .add( repr )
        .add( new UI.Break() )
        .add( new UI.Text( "Trajectory" ).setWidth( "110px" ) )
        .add( traj )
        .add( new UI.Break() )
        .add( new UI.Text( "Superpose" ).setWidth( "110px" ) )
        .add( superpose );

    var menu = new UI.Icon( "bars" )
        .setTitle( "menu" )
        .setMarginLeft( "47px" )
        .onClick( function(){

            if( menuPanel.getDisplay() === "block" ){

                menuPanel.setDisplay( "none" );
                return;

            }

            var box = menu.getBox();

            menuPanel
                .setRight( ( window.innerWidth - box.left + 10 ) + "px" )
                .setTop( box.top + "px" )
                .setDisplay( "block" )
                .attach();

        } );

    

    container.addStatic( new UI.Text( component.name ).setWidth( "100px" ) );
    container.addStatic( toggle );
    container.addStatic( center );
    container.addStatic( dispose );
    container.addStatic( menu );
    // container.addStatic( repr );

    // Fill container

    container.add( trajContainer );
    container.add( reprContainer );

    return container;

};


NGL.SurfaceComponentWidget = function( component, stage ){

    var signals = component.signals;
    var container = new UI.CollapsiblePanel();

    signals.visibilityChanged.add( function( value ){

        if( value ){
            toggle.removeClass( "eye-slash", "eye" ).addClass( "eye" );
        }else{
            toggle.removeClass( "eye", "eye-slash" ).addClass( "eye-slash" );
        }
        
    } );

    // Actions

    var toggle = new UI.Icon( "eye" )
        .setTitle( "hide/show" )
        .setMarginLeft( "25px" )
        .onClick( function(){

            if( toggle.hasClass( "eye" ) ){
                component.setVisibility( false );
            }else{
                component.setVisibility( true );
            }

        } );

    var center = new UI.Icon( "bullseye" )
        .setTitle( "center" )
        .setMarginLeft( "10px" )
        .onClick( function(){

            component.centerView( "backbone" );

        } );

    var dispose = new UI.Icon( "trash-o" )
        .setTitle( "delete" )
        .setMarginLeft( "10px" )
        .onClick( function(){

            if( dispose.getColor() === "rgb(178, 34, 34)" ){

                stage.removeComponent( component );
                container.dispose();

            }else{

                dispose.setColor( "rgb(178, 34, 34)" );

                setTimeout( function(){ 
                    dispose.setColor( "#888" );
                }, 1000);

            }

        } );
    
    container.addStatic( new UI.Text( component.name ).setWidth( "100px" ) );
    container.addStatic( toggle );
    container.addStatic( center );
    container.addStatic( dispose );

    return container;

};


NGL.RepresentationWidget = function( repr, component ){

    var signals = repr.signals;

    var container = new UI.CollapsiblePanel()
        .setMarginLeft( "20px" );

    signals.visibilityChanged.add( function( value ){

        if( value ){
            toggle.removeClass( "eye-slash", "eye" ).addClass( "eye" );
        }else{
            toggle.removeClass( "eye", "eye-slash" ).addClass( "eye-slash" );
        }
        
    } );

    component.signals.representationRemoved.add( function( _repr ){

        if( repr === _repr ) container.dispose();
        
    } );

    // Actions

    var toggle = new UI.Icon( "eye" )
        .setTitle( "hide/show" )
        .setMarginLeft( "25px" )
        .onClick( function(){

            if( toggle.hasClass( "eye" ) ){
                repr.setVisibility( false );
            }else{
                repr.setVisibility( true );
            }

        } );

    var dispose = new UI.Icon( "trash-o" )
        .setTitle( "delete" )
        .setMarginLeft( "10px" )
        .onClick( function(){

            if( dispose.getColor() === "rgb(178, 34, 34)" ){

                component.removeRepresentation( repr );

            }else{

                dispose.setColor( "rgb(178, 34, 34)" );

                setTimeout( function(){ 
                    dispose.setColor( "#888" );
                }, 1000);
                
            }

        } );

    container.addStatic( new UI.Text( repr.name ).setWidth( "80px" ) );
    container.addStatic( toggle );
    container.addStatic( dispose );

    // Add sele

    var seleRow = new UI.Panel();
    var sele = new UI.Input()
        .setWidth( '170px' ).onKeyDown( function( e ){
            
            if( e.keyCode === 13 ){

                repr.changeSelection( sele.getValue() );

            }

        } );

    if( repr.selection ){
        sele.setValue( repr.selection.selectionStr );
    }
 
    seleRow.add( new UI.Text( 'Sele' ).setWidth( '45px' ).setMarginLeft( "20px" ) );
    seleRow.add( sele );

    container.add( seleRow );

    return container;

};


NGL.TrajectoryWidget = function( traj, component ){

    var signals = traj.signals;

    var container = new UI.CollapsiblePanel()
        .setMarginLeft( "20px" );

    component.signals.trajectoryRemoved.add( function( _traj ){

        if( traj === _traj ) container.dispose();
        
    } );

    var numframes = new UI.Panel()
        .setMarginLeft( "10px" )
        .setDisplay( "inline" )
        .add( new UI.Icon( "spinner" ).addClass( "spin" ) );

    signals.gotNumframes.add( function( value ){

        numframes.clear().add( new UI.Text( "#" + value ) );
        frame.setRange( 0, value - 1 );
        frame2.setRange( 0, value - 1 );

        step.setValue( Math.ceil( ( value + 1 ) / 100 ) );
        
    } );

    //1000 = n / step 

    signals.frameChanged.add( function( value ){

        frame.setValue( value );
        frame2.setValue( value );

        numframes.clear().add( new UI.Text(
            "#" + traj.numframes + " (" + traj.frameCacheSize + ")"
        ) );

        inProgress = false;
        
    } );

    container.addStatic( new UI.Text( "Trajectory" ) );
    container.addStatic( numframes );

    // frames

    var frameRow = new UI.Panel();

    var frame = new UI.Integer( -1 )
        .setMarginLeft( "5px" )
        .setWidth( "70px" )
        .setRange( -1, -1 )
        .onChange( function( e ){

            traj.setFrame( frame.getValue() );

        } );

{'fraction_non_alphanumeric': 0.13867285649333558, 'fraction_numerical': 0.010792313766780732, 'mean_word_length': 1.8916412953224466, 'pattern_counts': {'":': 4, '<': 13, '<?xml version=': 0, '>': 9, 'https://': 2, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 20, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '6907486', 'n_tokens_mistral': 13403, 'n_tokens_neox': 12403, 'n_words': 2625}