/*  $Id: main.js 11436 2009-10-14 20:21:10Z rallj $
 * ===========================================================================
 *
 *                            PUBLIC DOMAIN NOTICE
 *               National Center for Biotechnology Information
 *
 *  This software/database is a "United States Government Work" under the
 *  terms of the United States Copyright Act.  It was written as part of
 *  the author's official duties as a United States Government employee and
 *  thus cannot be copyrighted.  This software/database is freely available
 *  to the public for use. The National Library of Medicine and the U.S.
 *  Government have not placed any restriction on its use or reproduction.
 *
 *  Although all reasonable efforts have been taken to ensure the accuracy
 *  and reliability of the software and data, the NLM and the U.S.
 *  Government do not and cannot warrant the performance or results that
 *  may be obtained by using this software or data. The NLM and the U.S.
 *  Government disclaim all warranties, express or implied, including
 *  warranties of performance, merchantability or fitness for any particular
 *  purpose.
 *
 *  Please cite the author in any work or product based on this material.
 *
 * ===========================================================================
 *
 * Authors:  Vlad Lebedev
 *
 * File Description:
 *
 */
 
Ext.QuickTips.init();
Ext.form.Field.prototype.msgTarget = 'title';
Ext.namespace('SeqApp');
Ext.namespace('Ext.ux');
Ext.namespace('Ext.ux.Plugin');

Ext.ux.NCBI = 'http://www.ncbi.nlm.nih.gov';
Ext.BLANK_IMAGE_URL = Ext.ux.NCBI + '/core/extjs/ext-2.3.0/resources/images/default/s.gif';  // 2.3
Ext.SSL_SECURE_URL  = Ext.ux.NCBI + '/core/extjs/ext-2.3.0/resources/images/default/s.gif'; 


var m_CGIPrefix = ""; // portal vs standalong (default)
var m_SeqApp = null; // main frame instance  
var m_GViews = [];
var m_GViewCounter = 0;

var m_AllViewParams;
var m_Portal = false;
var m_SliderMin = 43;
var m_SliderMax = 135;
var m_SliderActive = false; m_SliderActiveIdx = 0;
var m_ChunkWidth = 4000; // (pixels)default chunk size (can vary)
var m_Origin = 0;

var m_PreloadMargin = 300; // load next chunk when the current is with m_PreloadMargin to the edge
var m_PanHolderSize = 18; // fixed, do not change
var m_ShowGap = false; // (not used?)show/hide gap in the sequence panel
var m_NeedAlignmentView = false;

var m_PanoramaHeight = 0;
var m_PanoramaWidth;
var m_SeqLength = 0;
var m_CurIdeoElem = null; // ideogram selection

var m_Key = null; // NetCache Key for external data
var m_ThemeDlg; // global preferences (themes, caching and so on)
var m_SearchDlg; // search
var m_MonitorDlg; // performance monitor
var m_HelpDlg; // help
var m_SpacerHeight = 4; // pixels between the view panels

var m_Plugins; //registered plugins
var m_MainToolsMenu;
var m_ViewParams; // seqInfo results
var PAN_RIGHT = 1;
var PAN_LEFT = -1;
var m_Embedded = false;
var m_Init = true; // initializing

// Extension Points
m_RegisteredExtensions = {}
m_ValidExtensionEvents = {
   'panorama_image_loaded' : 0,
   'graphical_image_loaded' : 1,
   'marker_created' : 2,
   'marker_moved' : 3,
   'feature_clicked' : 4
};



// initial params:
m_Test = null, m_From = null, m_To = null; m_Markers = []; m_MarkersNames = []; m_ViewRanges = []; m_ViewThemes = []; m_ViewColors = []; m_ViewColors = []; m_ViewsFlip = []; m_ViewSelect = []; m_ViewSeq = []; 
m_ViewsCount = 0; m_ViewSearch = null; m_ViewMarkers = null; m_NAA = null; m_ViewLabels = null; m_DataRID = null; m_DataURL = null; m_SnpFilter = null; m_Tracks = null;
m_ViewsConfigContent = null; m_ViewsConfigColor = null; m_ViewsConfigDecor = null; m_ViewsConfigLayout = null; m_ViewsConfigSpacing = null; m_ViewsConfigAlnColor = null;
m_ViewsConfigLabel = null; m_ViewsConfigModel = null; 

// CGIs
var GI = null, theFeatSearchCGI, thePanoramaCGI, theGraphicCGI, theAlignmentCGI, theInfoCGI, theMoreDataCGI, theObjCoordsCGI, theSequencCGI, theLinkCGI, theObjInfoCGI, theSvDataCGI, theFeedbackCGI, theTinyURLCGI;

// search
//SeqApp.featureSearchTPL = new Ext.XTemplate('<tpl for="."><div class="x-combo-list-item"><span>{label}</span>({from}..{to})</div></tpl>');
// others
SeqApp.colorBarTPL = new Ext.Template('<div id="view_color_{idx}" style="width:15px;height:15px;background-color:#{color};border:1px black solid;">&nbsp;</div>');

// service
var can_drag = false; can_marker=false; did_pan_action=false;
var can_resize_left = false;
var can_resize_right = false;
var can_scroll = false;
var cur_locator, cur_locator_idx, cur_gv_target, cur_marker;

var gview_xy = [];
var panorama_xy = [];

SeqApp.registerExtension = function(event_name, function_name) {
   if ([event_name] in m_ValidExtensionEvents) m_RegisteredExtensions[event_name] = function_name;
   else alert('Unrecognized Event: ' + event_name);
}


SeqApp.remoteJS = function(config) {
   Ext.Ajax.request({url:m_CGIPrefix+'remoteJSON.cgi', params:config, success:function(val) {
      eval(val.responseText);
   }});   
}

Ext.ux.Plugin.RemoteJSONLoader = function (config) {
  var defaultType = config.xtype || 'panel';
  var callback = function(res) {
     this.container.add(Ext.ComponentMgr.create(Ext.decode(res.responseText), defaultType)).show();
	 this.container.doLayout();
  };
  return {
	init : function (container) {
        this.container = container;
        Ext.Ajax.request(Ext.apply(config, {success: callback, scope: this}));
    }
  }
};



Ext.onReady(function() {   
    SeqApp.loadAccession = function(gi) {
       GI = gi;
       for(m in m_AllMarkers) { if (m_AllMarkers[m]) m_AllMarkers[m].deleteMarker(); } // delete markers
       m_MarkerColorIdx = 0;

       for (v in m_GViews) {
          config = m_GViews[v];
          if (config['spacer']) m_SeqApp.remove(config['spacer'], true);
          if (config['view']) m_SeqApp.remove(config['view'], true);
       } 
       m_GViews = [];
       m_AllMarkersRefs = [];
       m_GViewCounter = 0;
       m_MarkerStart = 1;
       
       m_CGIPrefix = m_Test ? '' : '/projects/sviewer/';// m_Portal ? '/projects/sviewer/' : '';
       
       theFeatSearchCGI = m_CGIPrefix + 'featsearch.cgi';//'?id=' + GI;
       /*if (m_Test) {
          thePanoramaCGI = m_CGIPrefix + 'seqgraphic_test.cgi?id=' + GI;
          theGraphicCGI = m_CGIPrefix + 'seqgraphic_test.cgi?id=' + GI + '&minheight=220';
       } else {*/
          thePanoramaCGI = m_CGIPrefix + 'seqgraphic.cgi?id=' + GI;
          theGraphicCGI = m_CGIPrefix + 'seqgraphic.cgi?id=' + GI + '&minheight=220';
          theAlignmentCGI = m_CGIPrefix + 'alnmulti.cgi?id=' + GI + '&minheight=220';
       //} 
       if (m_ViewLabels) theGraphicCGI += "&markers=" + m_ViewLabels;
       if (m_SnpFilter) theGraphicCGI += "&snp_filter=" + m_SnpFilter;
       
       theInfoCGI = m_CGIPrefix + 'seqinfo.cgi?id=' + GI;
       theMoreDataCGI = m_CGIPrefix + 'more_data.cgi?id=' + GI;
       theObjCoordsCGI = m_CGIPrefix + 'objcoords.cgi?id=' + GI;
       theSequencCGI = m_CGIPrefix + 'seqfeat.cgi?opts=seq&id=' + GI;
       theLinkCGI = m_CGIPrefix + 'link.cgi';
       theObjInfoCGI = m_CGIPrefix + 'objinfo.cgi';
       theSvDataCGI = m_CGIPrefix + 'sv_data.cgi';
       theFeedbackCGI =  m_CGIPrefix + 'feedback.cgi';
       theTinyURLCGI =  m_CGIPrefix + 'tinyURL.cgi';
       if (m_Key) { // add key if supplied in URL
          theInfoCGI += '&key='+m_Key;
          theLinkCGI += '?key='+m_Key;
       }
              
       Ext.Ajax.request({url:theInfoCGI, success:function(res) { m_ViewParams = Ext.decode(res.responseText); SeqApp.infoLoaded(); }});
       // load plugins (and populate the menu)
       /*Ext.Ajax.request({url:'json/Plugins.js', success:function(res) {
          m_Plugins = Ext.decode(res.responseText); 
          for (i = 0; i != m_Plugins['plugins'].length; i++) {
             the_item = new Ext.menu.Item({xtype:'tbtext', idx:i, text:m_Plugins['plugins'][i]['text'], handler:function() { SeqApp.showToolsDlg(this.idx); } });
             m_MainToolsMenu.insert(2+i, the_item);
          }          
       }});*/
    }
    
    SeqApp.processUrl = function() {
        m_AllViewParams = document.location.href.split("?")[1];
        if (m_AllViewParams) m_AllViewParams = m_AllViewParams.replace(/#/, ''); // safety
        
        rel = Ext.get('SeqViewer');
        rel_attr = rel.dom.getAttribute('rel');
        if (rel_attr != null) {
           m_AllViewParams = m_AllViewParams == undefined ? rel_attr : m_AllViewParams + '&' + rel_attr; // merge them all
           m_Embedded = rel_attr.indexOf('embedded') != -1;
           m_Portal = !m_Embedded;
        }
        
        // add links
        var tmpl = new Ext.Template(
             '<a onClick="SeqApp.showLinkURLDlg();" href="#">Link To This Page</a> | ',
             '<a onClick="SeqApp.showHelpDlg();" href="#">Help</a> | ',
             '<a onClick="SeqApp.showFeedbackDlg();" href="#">Feedback</a> | ',
             '<a onClick="SeqApp.showPrintPageDlg();" href="#">Printer-Friendly Page</a>'
        );
        
        if (m_Embedded) cfg = [{tag: 'div', id:'SeqViewerJS'}]
        else cfg = [{tag: 'div', id: 'SeqViewerControls', html:tmpl.apply()}, {tag:'br'}, {tag: 'div', id:'SeqViewerJS'}]
        Ext.DomHelper.append('SeqViewer', cfg);
        
                   
        if (m_AllViewParams) {
           //m_AllViewParams = url_params[1]
           //console.log('m_AllViewParams: ' + m_AllViewParams)
           p_array = m_AllViewParams.split('&');
           
           for (i = 0; i != p_array.length; i++) {
              pair = p_array[i]
              p = pair.split("=");
              the_key = p[0].toLowerCase();
              
              val = unescape(p[1]);
              if (the_key != 'key'  &&
                  the_key != 'naa'  &&
                  the_key != 'tracks' &&
                  the_key != 'mn'  &&
                  the_key != 'url'  &&
                  the_key != 'rid'  &&
                  the_key != 'snp_filter' &&
                  the_key != 'theme') val = val.toLowerCase();
              
              if (the_key=='id') GI = val;
              if (the_key=='f') m_From = val;
              if (the_key=='t') m_To = val;
              if (the_key=='from') m_From = val;
              if (the_key=='to') m_To = val;
              if (the_key=='test') m_Test = "true";
              if (the_key=='_gene' || the_key=='gene') m_ViewSearch = val;
              if (the_key=='m') m_Markers = val.split(','); // like: m=249,276
              if (the_key=='mn') m_MarkersNames = val.split(','); // like: mn=Marker 1,Marker 2
              if (the_key=='v') m_ViewRanges = val.split(','); // like: v=1000:6000,8000:18723
              if (the_key=='c') m_ViewColors = val.split(','); // like: color=0000FF,FFFF99
              if (the_key=='theme') m_ViewThemes = val.split(','); // like: theme:NCBI Compact,NCBI Details
              if (the_key=='flip') m_ViewsFlip = val.split(','); // like: flip=true,false
              if (the_key=='strand') m_ViewsFlip = val.split(','); // like: flip=true,false
              if (the_key=='select') m_ViewSelect = val.split(','); // like: select=
              if (the_key=='seq') m_ViewSeq = val.split(':'); // like: seq=2000:7000
              if (the_key=='search') m_ViewSearch = val; // like: search=feature_name
              if (the_key=='vm') m_ViewMarkers = true; // like: vm=true
              if (the_key=='key') m_Key = val; // like: db=nucleotide
              if (the_key=='naa') m_NAA = val; // like: naa=NA000000003,NA000000004, NA000000004:renderer_name
              if (the_key=='tracks') m_Tracks = val;
              if (the_key=='rid') m_DataRID = val; // like: rid=1175088155-31624-126008617054
              if (the_key=='url') m_DataURL = val; // like: url=www.ncbi.nlm.nih.gov/data.txt
              
              
              if (the_key=='origin') m_Origin = parseInt(val); // like: origin=10000
              if (the_key=='content') m_ViewsConfigContent = val.split(',');  // like:  content = 1,3
              if (the_key=='color') m_ViewsConfigColor = val.split(','); 
              if (the_key=='label') m_ViewsConfigLabel = val.split(','); 
              if (the_key=='genemodel') m_ViewsConfigModel = val.split(','); 
              if (the_key=='decor') m_ViewsConfigDecor = val.split(',');
              if (the_key=='layout') m_ViewsConfigLayout = val.split(',');
              if (the_key=='spacing') m_ViewsConfigSpacing = val.split(',');
              if (the_key=='alncolor') m_ViewsConfigAlnColor = val.split(',');
              if (the_key=='labels') m_ViewLabels = val;
              if (the_key=='embedded') m_Embedded = true;
              if (the_key=='snp_filter') m_SnpFilter = val; // like: Validated|000010000%20-1%20-1%20-1%20_%20_%20_%2043%20_%20_%20
           } // for
        } // view
    }

    // Start here:
    SeqApp.processUrl();
    
    // Start   
    if (GI) {
        /*tools = [
          {id:'help', qtip:'Help', handler:function() { SeqApp.showHelpDlg(); } },
          {id:'print', qtip:'Printer-Friendly Page', handler:function() { SeqApp.showPrintPageDlg(); } },
          {id:'gear', qtip:'Feedback', handler:function(e) { e.shiftKey ? SeqApp.showMonitorDlg() : SeqApp.showFeedbackDlg(); } }
        ];*/
           
        m_MainToolsMenu = new Ext.menu.Menu({items:[
             {text:'BLAST Search (Whole Sequence)', handler:function() { SeqApp.doBlastSearch(); } },
             '-',
             {text:'GenBank view', handler:function() {  window.open(Ext.ux.NCBI + '/nuccore/'+GI+'?report=genbank'); } },
             {text:'FASTA View', handler:function() { window.open(Ext.ux.NCBI + '/nuccore/'+GI+'?report=fasta'); } },
             '-',
             {text:'Load External Data', iconCls:'ext_data_load', handler:function() { SeqApp.loadExternalDataDlg(); } },
             '-',
             {text:'Clear Tool Results', iconCls:'ext_data_clear', handler:function() { SeqApp.clearExternalData(); } }
        ]});
      
      
        view_tbar = [
            {iconCls:'new_view', tooltip:'Create New Graphical View', handler:function() { cfg = SeqApp.getDefaultGraphicalConfig(); SeqApp.createGraphical(cfg); } }, '-',
            {iconCls:'new_fasta', text:'Sequence', tooltip:'Create New Sequence Text View', handler:function() { SeqApp.createSequenceText(-1); } }, '-',
            {text:'Load Accession', hidden:m_Portal, iconCls:'load-accession', handler:function() { SeqApp.loadAccessionDlg(); } }, 
            {text:'Set Origin', iconCls:'origin', handler:function() { SeqApp.setOriginDlg(null); } }, '-',
            {text:'Views & Tools', tooltip:'Views and Tools', iconCls:'views_tools', menu:m_MainToolsMenu},//'-',
            //{tooltip:'Load External Data', iconCls:'ext_data_load', handler:function() { SeqApp.loadExternalDataDlg(); } },'-',
            {iconCls:'connect', tooltip:'Additional Data', id:'additional_menu', hidden:true, menu:new Ext.menu.Menu({items:[]}) },
            '->', 
            //{tooltip:'URL Link To This Page', iconCls:'link_to_page', handler:function() { SeqApp.showLinkURLDlg(); }}, '-',
            //{tooltip:'Printer-Friendly Page', iconCls:'printer', handler:function() { SeqApp.showPrintPageDlg(); }}, '-',
            {text:'Markers', iconCls:'markers', handler:function() { SeqApp.showMarkersDlg(); }}, '-',
            {
                 xtype:'textfield',
                 id:'seqapp-search-box',
                 emptyText:'Find gene...',
                 listeners:{ 
                    'specialkey': function(f, e) { if (e.getKey() == Ext.EventObject.ENTER) { SeqApp.showSearchDlg(); e.stopEvent(); }}
                 },
                 width: 250
            }
        ];
        
        if (!m_Embedded) {
           view_config = {
                 title: 'Loading...',
                 collapsible:true,
                 renderTo: 'SeqViewerJS',
                 bodyStyle:'padding:3px;',
                 //tools:tools,
                 html:'<span id="string_ruler_unit"></span>',
                 tbar:view_tbar
           };
        } else {
           view_config = {
                 renderTo: 'SeqViewerJS',
                 border:false,
                 html:'<span id="string_ruler_unit"></span>'
           };
        }
        m_SeqApp = new Ext.Panel(view_config);
        SeqApp.loadAccession(GI);// ? GI : 'NT_011515'); // First Load
    } else { // show home page
       Ext.get('app-frontpage').setStyle('display', 'block');
       
       var button = Ext.get('mainshow-btn');
       button.on('click', function() { SeqApp.loadAccessionDlg();  }); 
    }
    // done
    
    SeqApp.x_UpdateDeveloperConsole = function(text, from_cgi) {
       if (!m_MonitorDlg) return;
       
       size = text.length;
       Ext.getCmp('json-size').setValue(size);
       Ext.getCmp('Render_Mesa_Elapsed').setValue( from_cgi['Render_Mesa_Elapsed'] );
       Ext.getCmp('Render_Config_Elapsed').setValue( from_cgi['Render_Config_Elapsed'] );
       Ext.getCmp('RenderActiveAreas_Elapsed').setValue( from_cgi['RenderActiveAreas_Elapsed'] );
       Ext.getCmp('Render_Image_Elapsed').setValue( from_cgi['Render_Image_Elapsed'] );
       Ext.getCmp('Render_Total_Elapsed').setValue( from_cgi['Render_Total_Elapsed'] );
       Ext.getCmp('Image_Cache_Elapsed').setValue( from_cgi['Image_Cache_Elapsed'] );
       Ext.getCmp('Render_Data_Elapsed').setValue( from_cgi['Render_Data_Elapsed'] );
       Ext.getCmp('Total_Elapsed').setValue( from_cgi['Total_Elapsed'] );
       Ext.getCmp('Total_Label').setVisible( from_cgi['Total_Elapsed']>2.0 ); // more than 2 seconds is bad!
    }
    
    SeqApp.showMonitorDlg = function() {
       SeqApp.remoteJS({name:'json/MonitorDlg.js' });
    }
    
    SeqApp.showFeedbackDlg = function() {
       SeqApp.remoteJS({name:'json/FeedbackDlg.js' });
    }
    
    SeqApp.doBlastSearch = function() {
      window.open(Ext.ux.NCBI + SeqApp.x_GetBlastSearchURL());
    }
    
    SeqApp.doBlastSearchRange = function(idx) {
       config = m_GViews[idx];
       from = config['vis_from_seq'] + 1;
       len = config['vis_len_seq'];
       to = from + len;
       
       blast_url = SeqApp.x_GetBlastSearchURL();
       blast_url += "&QUERY_FROM="+from;
       blast_url += "&QUERY_TO="+to;
       //window.open('http://www.ncbi.nlm.nih.gov'+blast_url);
       window.open(blast_url);
    }
    
    SeqApp.x_GetBlastSearchURL = function() {
        if (m_ViewParams['acc_type']=='protein') return "/blast/Blast.cgi?QUERY="+GI+"&PROGRAM=blastp&BLAST_PROGRAMS=blastp&PAGE_TYPE=BlastSearch&SHOW_DEFAULTS=on";
        else return "/blast/Blast.cgi?QUERY="+GI+"&PAGE=Nucleotides&PROGRAM=blastn&MEGABLAST=on&BLAST_PROGRAMS=megaBlast&PAGE_TYPE=BlastSearch&SHOW_DEFAULTS=on";
    }
    
    
    SeqApp.showToolsDlg = function(idx) {
      plugin_data = m_Plugins['plugins'][idx];
      dlg = new Ext.Window({
          layout:'fit', 
          tools:[
             {id:'help', qtip:'Help', handler:function() { alert('Help Dialog Here') } }
          ],
          title:plugin_data['text'] + ': ' + m_ViewParams['id'],
          width:plugin_data['width'], height:plugin_data['height'], modal:true,
          constrain:true, resizable:plugin_data['resizable'],
          bodyStyle:'padding:10px;',
          closeAction:'close',
          plugins: new Ext.ux.Plugin.RemoteJSONLoader({ url:m_CGIPrefix+'remoteJSON.cgi', params:{name:plugin_data['file'], gi:GI}, text:'Loading Search'}),
          buttons: [
            {text: 'Submit Search', handler: function() { 
               form = dlg.items.items[0].getForm();
               if (form.isValid()) {
                 form.submit({success:function(form, action) { 
                    dlg.close();
                    Ext.MessageBox.show({title:'Search', msg:'Form submit...', buttons:Ext.MessageBox.OK, icon:Ext.MessageBox.INFO});
                 }});
               }
            }},
            {text: 'Cancel', handler: function() {dlg.close(); } }
          ]
       });
       dlg.show();
    }
    
    
    SeqApp.showHelpDlg = function() {
       SeqApp.remoteJS({name:'json/HelpDlg.js' });
    }
    
    SeqApp.loadHelpTopic = function(file, topic) {
       tree = Ext.getCmp('seq-viewer-help-tree');
       tree.getSelectionModel().select( tree.getNodeById(file));
        
       panel = Ext.getCmp('cur-topic');
       panel.getUpdater().update({url:m_CGIPrefix+ './help/'+file, callback:function(el, success, res, options) {
          el.update( res.responseText );
          panel.setTitle('Current Topic: ' + topic);
       }});
    }
    
    SeqApp.showPrintPageDlg = function() {
       SeqApp.remoteJS({name:'json/PrintPageDlg.js' });
    }
     
    SeqApp.showLinkURLDlg = function() {
       SeqApp.remoteJS({name:'json/LinksURLDlg.js' });
    }
    
    
    SeqApp.themeConfigDlg = function(idx) {
       SeqApp.remoteJS({name:'json/ThemeConfigDlg.js' });
    }
   

   SeqApp.setNewMarkerDlg = function(cfg, x_pos) {
      pix = Math.abs(cfg['scroll_pix']) + x_pos;
      seq_pos = Math.round( SeqApp.pix2SeqG(cfg, pix) ) + 1;
      if (m_Origin != 0) seq_pos -= m_Origin;
      
      //return;
      setMarkerDlg = new Ext.Window({
             layout:'form', modal:true, title:'Set New Marker', width:270, autoHeight:true,
             collapsible:false, constrain:true, resizable:false, closeAction:'close', iconCls:'markers',
             items:[{
                   xtype:'form',
                   bodyStyle:'padding:5px;',
                   labelWidth: 70,
                   frame:true,
                   labelAlign:'right',
                   items:[
                      {xtype:'textfield', name:'name', width:'90%', fieldLabel:'Name', value:Marker.getNextName() },
                      {xtype:'textfield', name:'position', width:'90%', fieldLabel:'Position', value:seq_pos },
                      {xtype:'checkbox',  name:'lock', height:24, labelSeparator:'', boxLabel:'Lock Marker', checked:true }
                   ]
             }],
             buttons:[
               {text:'Ok', handler: function() {
                  values = setMarkerDlg.items.items[0].getForm().getValues();
                  name = values['name'];
                  position = values['position'].replace(/k/, '000').replace(/m/, '000000');
                  new_position = parseInt(position);
                  if (isNaN(new_position) || new_position < 0 || new_position>m_SeqLength) {
                     Ext.MessageBox.show({title:'Set New Marker', msg:'Invalid sequence position.', buttons:Ext.MessageBox.OK, icon:Ext.MessageBox.ERROR});
                  } else if (name.length==0 || name.length>50 || name.indexOf(',')!=-1) {
                     Ext.MessageBox.show({title:'Set New Marker', msg:'Invalid marker name.', buttons:Ext.MessageBox.OK, icon:Ext.MessageBox.ERROR});
                  } else {
                     setMarkerDlg.close();
                     seq_pos = m_Origin != 0 ? new_position + m_Origin - 1 : new_position - 1;
                     m_AllMarkersRefs.push( new Marker(cfg, seq_pos, name, values['lock']=='on') );
                     SeqApp.updateMarkersInfo();
                  }
               }},
               {text:'Cancel', handler: function() {setMarkerDlg.close(); } }
             ]
      });
      setMarkerDlg.show();
   }
   
   SeqApp.clearOrigin = function() {
      Ext.MessageBox.confirm('Confirm', 'Reset Sequence Origin?', function(btn) {
         if (btn!='yes') return;
         m_Origin = 0;         
         // reload all views
         SeqApp.loadPanoramaImage();
         SeqApp.reloadAllGViews();
      });
   }
   
   SeqApp.setOriginDlg = function(pos) {
      Ext.MessageBox.prompt('Set Origin', 'Please enter new origin:', function(btn, text) {
         if (btn!='ok'  || text.length==0) return;
         if (!SeqApp.IsNumeric(text) || parseInt(text)>m_SeqLength) {
            Ext.MessageBox.show({title: 'Set Origin',msg: 'Invalid sequence origin.',buttons: Ext.MessageBox.OK,icon:Ext.MessageBox.ERROR});
            return; 
         }
                
         m_Origin = parseInt(text);
         // reload all views
         SeqApp.loadPanoramaImage();
         SeqApp.reloadAllGViews();
      }, this, false, pos ? pos : m_Origin);
   }
   
   
   SeqApp.loadAccessionDlg = function() {
      Ext.MessageBox.prompt('Load Accession', 'Please enter accession or GI:', function(btn, text) {
          if (btn!='ok'  || text.length==0) return;
          Ext.Ajax.request({url:m_CGIPrefix + 'seqinfo.cgi?id=' + text, success:function(res) { 
              m_ViewParams = Ext.decode(res.responseText); 
              if (m_ViewParams['error']) {
                 Ext.MessageBox.show({title: 'Sequence Viewer', msg:m_ViewParams['error'], buttons: Ext.MessageBox.OK, icon:Ext.MessageBox.ERROR});
              } else {
                 view_url = document.location.href.split("?")[0];
                 document.location.href = view_url + '?id=' + text;
              }
          }});
      }, this, false, GI);
   }
      
   SeqApp.infoLoaded = function() {
      if (m_ViewParams['error']) {
          m_SeqApp.setTitle('Error');
          m_SeqApp.getEl().insertHtml("afterBegin", m_ViewParams['error']);
          return;
      }
      document.title = m_ViewParams['id'] + ': ' + m_ViewParams['title'];
      
      title_place = Ext.get('SeqViewerTitle');
      if (title_place) title_place.update(m_ViewParams['title']);
      
      title_id = Ext.get('SeqViewerTitleID');
      if (title_id) title_id.update(m_ViewParams['id_full']);
      
      m_SeqApp.setTitle(m_ViewParams['id']);
      
      m_SeqLength = m_ViewParams['length'];
      views = m_ViewParams['views'];
      //for (i = 0; i != views.length; i++) SeqApp.createView(views[i]);
      suggested_ranges = [];
      for (i = 0; i != views.length; i++) {
        sp = views[i].split(':');
        if (sp[0]=='multialign') m_NeedAlignmentView = true;
        if (sp[0]=='graphical'  &&  sp[1]) {
            suggested_ranges = sp[1].split('-');
        }
        views[i] = sp[0];
      }

      
      //SeqApp.createIdeogram();
      SeqApp.createPanorama();
      
      if (m_From && (m_From=="begin"  ||  m_From=="begining")) m_From = null;
      if (m_To && m_To=="end") m_To = null;
      if ( !m_From  &&  !m_To  &&  suggested_ranges[0]) {
        m_From = suggested_ranges[0];
        m_To   = suggested_ranges[1];
      }

      from = m_From ? m_From.replace(/k/, '000').replace(/m/, '000000').replace(/,/g, '') : 0;
      to   = m_To   ? m_To.replace(/k/, '000').replace(/m/, '000000').replace(/,/g, '')   : (m_SeqLength < 10000 ? m_SeqLength : m_SeqLength > 500000 ? 75000 : m_SeqLength / 10);      
      
      // fix for old Portal format
      if (m_ViewRanges.length==1 && m_ViewRanges[0].indexOf('begin') != -1) m_ViewRanges = []; // ignore cases such us begin..end
      
      
      if (m_ViewRanges.length == 0) {
         m_ViewsCount = 1;

         cfg = SeqApp.getDefaultGraphicalConfig();
         cfg['from_seq'] = from;
         cfg['len_seq'] = to - from;
         
         cfg['url_from'] = m_From ? from : null; // save for the first title update
         cfg['url_to']   = m_To ? to : null;
         
         if (m_ViewsFlip.length  > 0) cfg['flip']  = (m_ViewsFlip[0] == 'true');
         if (m_ViewThemes.length > 0) cfg['theme'] = m_ViewThemes[0];
         if (m_ViewSelect.length > 0) cfg['selected_sig'] = m_ViewSelect[0];

         
         if (m_ViewsConfigContent && m_ViewsConfigContent.length > 0)  cfg['config_content']  = m_ViewParams['controls']['content'][ m_ViewsConfigContent[0] ];
         if (m_ViewsConfigLabel   && m_ViewsConfigLabel.length > 0  )  cfg['config_label'  ]  = m_ViewParams['controls']['label'  ][ m_ViewsConfigLabel  [0] ];
         if (m_ViewsConfigColor   && m_ViewsConfigColor.length > 0  )  cfg['config_color'  ]  = m_ViewParams['controls']['color'  ][ m_ViewsConfigColor  [0] ];
         if (m_ViewsConfigDecor   && m_ViewsConfigDecor.length > 0  )  cfg['config_decor'  ]  = m_ViewParams['controls']['decor'  ][ m_ViewsConfigDecor  [0] ];
         if (m_ViewsConfigLayout  && m_ViewsConfigLayout.length > 0 )  cfg['config_layout' ]  = m_ViewParams['controls']['layout' ][ m_ViewsConfigLayout [0] ];
         if (m_ViewsConfigSpacing && m_ViewsConfigSpacing.length > 0)  cfg['config_spacing']  = m_ViewParams['controls']['spacing'][ m_ViewsConfigSpacing[0] ];            
         if (m_ViewsConfigModel   && m_ViewsConfigModel.length > 0  )  cfg['config_geneModel']= m_ViewParams['controls']['geneModel'][m_ViewsConfigModel [0] ];
        
         SeqApp.createGraphical(cfg); // default
         
         if (m_NeedAlignmentView && ! m_Embedded) {
            cfg_aln = SeqApp.getDefaultGraphicalConfig();
            cfg_aln['from_seq'] = from;
            cfg_aln['len_seq'] = to - from;
            SeqApp.createAlignment(cfg_aln);
         }
         
         
      } else { // create views from URL
         m_ViewsCount = m_ViewRanges.length; // for marker creation (create markers when the last view is loaded)
         for (v = 0; v != m_ViewRanges.length; v++) { // 100:5000
            the_r = m_ViewRanges[v];
            
            if (the_r.indexOf(':') != -1) range = the_r.split(':');
            if (the_r.indexOf('..') != -1) range = the_r.split('..');
            if (the_r.indexOf('-') != -1) range = the_r.split('-');
            
            range = SeqApp.decodeRange(range);
              
            cfg = SeqApp.getDefaultGraphicalConfig();
            cfg['from_seq'] = range[0];
            cfg['len_seq']  = range[1] - range[0];
            
            cfg['url_from'] = range[0] ? range[0] : null;
            cfg['url_to']   = range[1] ? range[1] : null;
            

            if (m_ViewColors.length > 0) cfg['color'] = m_ViewColors[v];
            if (m_ViewThemes.length > 0) cfg['theme'] = m_ViewThemes[v];
            if (m_ViewsFlip.length  > 0) cfg['flip']  = (m_ViewsFlip[v] == 'true');
            if (m_ViewSelect.length > 0  && m_ViewSelect[v] != 'null') cfg['selected_sig'] = m_ViewSelect[v];
            
            if (m_ViewsConfigContent) {
                if (m_ViewsConfigContent[v] >0  && m_ViewsConfigContent [v] < m_ViewParams['controls']['content'].length)
                    cfg['config_content'] = m_ViewParams['controls']['content'][m_ViewsConfigContent[v]];
            } // content
            
            if (m_ViewsConfigLabel) {
                if (m_ViewsConfigLabel[v] >0  && m_ViewsConfigLabel [v] < m_ViewParams['controls']['label'].length)
                    cfg['config_label'] = m_ViewParams['controls']['label'][m_ViewsConfigLabel[v] ];
            } // label
            
            if (m_ViewsConfigModel) {
                if (m_ViewsConfigModel[v] >0  && m_ViewsConfigModel [v] < m_ViewParams['controls']['geneModel'].length)
                    cfg['config_geneModel'] = m_ViewParams['controls']['geneModel'][m_ViewsConfigModel[v] ];
            } // geneModel
            
            if (m_ViewsConfigColor) {
                if (m_ViewsConfigColor[v] >0  && m_ViewsConfigColor [v] < m_ViewParams['controls']['color'].length)
                    cfg['config_color'] = m_ViewParams['controls']['color'][m_ViewsConfigColor[v] ];
            } // color
            
            if (m_ViewsConfigDecor) {
                if (m_ViewsConfigDecor[v] >0  && m_ViewsConfigDecor [v] < m_ViewParams['controls']['decor'].length)
                    cfg['config_decor'  ]  = m_ViewParams['controls']['decor'  ][ m_ViewsConfigDecor  [v] ];
            } // decor
            
            if (m_ViewsConfigLayout) {
                if (m_ViewsConfigLayout[v] >0  && m_ViewsConfigLayout [v] < m_ViewParams['controls']['layout'].length)
                    cfg['config_layout'] = m_ViewParams['controls']['layout'][ m_ViewsConfigLayout[v] ];
            } // layout
            
            if (m_ViewsConfigSpacing)  cfg['config_spacing']  = m_ViewParams['controls']['spacing'][ m_ViewsConfigSpacing[v] ];            
            if (m_ViewsConfigAlnColor) cfg['config_alncolor'] = m_ViewsConfigAlnColor[v];
            
            SeqApp.createGraphical(cfg);
         }
      }
      
      if (m_ViewSeq.length > 0) {
          m_SeqFlipStrand = (m_ViewsFlip.length > 0  &&  m_ViewsFlip[0] == "true");
          SeqApp.displaySequenceTextDlg(parseInt(m_ViewSeq[0]), parseInt(m_ViewSeq[1]));
      }
      if (m_ViewSearch) { Ext.getCmp('seqapp-search-box').setValue(m_ViewSearch); SeqApp.showSearchDlg(); }
   }
   
   
   SeqApp.decodeRange = function(range) {
      from = range[0].replace(/k/, '000').replace(/m/, '000000')
      to   = range[1].replace(/k/, '000').replace(/m/, '000000')
      to = Math.min(to, m_SeqLength - 2);
      
      return [from, to];
   }
   /*SeqApp.createView = function(type) {
      switch(type) {
         case 'panorama': SeqApp.createPanorama(); break;
         case 'graphical': SeqApp.createGraphical(); break;
         case 'alignment': SeqApp.createAlignment(); break;
         case 'sequence':  SeqApp.createSequenceText(-1); break;
      }
   }*/

   
   SeqApp.createIdeogram = function() {
      m_GViewer = m_SeqApp.add({hidden:m_Embedded, html:'<div id="ideogram_div" class="ideogram_div">', bodyStyle:'margin-bottom:4px;'  });
      m_SeqApp.doLayout();
         Ext.get('ideogram_div').on({
              'mousedown' : SeqApp.onGViewMouseDown,
              //'mouseup' : SeqApp.onGViewMouseUp,
              'mousemove' : SeqApp.onGViewMouseMove
              //'mouseout':SeqApp.onGViewMouseOut,
              //'contextmenu':SeqApp.onGViewContextMenu
         });
      SeqApp.loadIdeogramImage();
   }
   
   SeqApp.createPanorama = function() {
      idx = m_GViewCounter;
      the_id = 'panorama_id';// + idx;
      m_GViews[idx] = {
         type:'panorama',
         idx: m_GViewCounter,
         loading:true,
         theme:'Overview',
         initializing:true,
         config_geneModel:'', config_label:'', config_content:'', config_decor:'', config_color:'', 
         config_layout:'', config_spacing:'', config_alncolor:'on', 
         top_offset:m_PanHolderSize,
         scroll_pix:0,
         from_seq:0,
         the_id:the_id,
         from_cgi:null
      }
  		
      m_GViews[idx]['view'] = m_SeqApp.add( {hidden:m_Embedded, html:'<div id="'+the_id+'" class="panorama_div"><div class="pan-holder"/>' } );
      m_SeqApp.doLayout();
      
      Ext.get(the_id).on({
           'mousedown' : SeqApp.onGViewMouseDown,
           'mouseup' : SeqApp.onGViewMouseUp,
           'mousemove' : SeqApp.onGViewMouseMove,
           'mouseout':SeqApp.onGViewMouseOut,
           'contextmenu':SeqApp.onGViewContextMenu,
           scope: this
      });
      m_GViewCounter++;
      SeqApp.loadPanoramaImage();
      //SeqApp.checkForAdditionalData();
   }
   
   
   SeqApp.reloadGView = function(config, keep_len, res_left, res_right) {
      if (!config) return;
      locator = config['locator'];
      
      if (res_right) vis_from = config['vis_from_seq'];
      else vis_from = SeqApp.toSeqP( locator.getElement().getLeft(true) );
      
      if (keep_len) vis_to = vis_from + config['vis_len_seq']; // keep length
      else vis_to = SeqApp.toSeqP( locator.getElement().getLeft(true) + locator.getElement().getWidth()+3 );      

      if (config['type']=='alignment') {
         SeqApp.loadAlignmentImage(config, vis_from, vis_to - vis_from);
      } else {
         config['loading'] = true; // start loading
         SeqApp.clearCache(config);
         SeqApp.loadGraphicalImage(config, vis_from, vis_to - vis_from);
      }
   }
   
   
   /* Graphical Views */
   SeqApp.onRefreshView= function(idx) {
      SeqApp.refreshGView(idx);
   }


   SeqApp.clearCache = function(config) {
       config['prev_cgi'] = null; // clear next/prev cache 
       config['next_cgi'] = null;
       config['url_from'] = null;
       config['url_to'] = null;
   }
   
   
   SeqApp.createGraphical = function(new_config) {
      idx = m_GViewCounter;
      the_id = 'graphical_id' + idx;

      menu_id = 'theme-id-' + idx;
      
      if (!new_config['theme']) new_config['theme'] = m_ViewParams['default_theme'];
      cur_theme = new_config['theme'];
      
      menu_items = [];
      for (i=0; i!=m_ViewParams['controls']['theme'].length; i++) {
         the_t = m_ViewParams['controls']['theme'][i];
         t_name = the_t['name'];
         if (t_name.indexOf('NCBI')==0) t_name = t_name.substr(5); // remove NCBI from theme name
         
         
         if (t_name==cur_theme) new_config['def_theme'] = the_t;
         
         menu_items.push({
            text:t_name, val:t_name, group:true, checked:(cur_theme == t_name), 
            color:the_t['color'], decor:the_t['decor'], content:the_t['content'], layout:the_t['layout'], 
            spacing:the_t['spacing'], label:the_t['label'], geneModel:the_t['geneModel']
         });
      }
      
      theme_menu = new Ext.menu.Menu({idx:idx, menu_id:menu_id, items:menu_items, 
         listeners:{'itemclick': function(baseItem, e) { 
            baseItem.setChecked(true);
            Ext.getCmp(this.menu_id).setText(baseItem.text);
            config = m_GViews[this.idx];
            config['theme'] = baseItem.val;
            
            config['config_color']   = m_ViewParams['controls']['color'][baseItem.color];
            config['config_label']   = m_ViewParams['controls']['label'][baseItem.label];
            config['config_decor']   = m_ViewParams['controls']['decor'][baseItem.decor];
            config['config_content'] = m_ViewParams['controls']['content'][baseItem.content];
            config['config_layout']  = m_ViewParams['controls']['layout'][baseItem.layout];
            config['config_spacing'] = m_ViewParams['controls']['spacing'][baseItem.spacing];
            config['config_geneModel'] = m_ViewParams['controls']['geneModel'][baseItem.geneModel];
            SeqApp.refreshGView(this.idx);
         } 
      }});
      
      spacer = m_SeqApp.add({border:false, height:m_SpacerHeight}); // spacer
      
      var cp = new Ext.ColorPalette();
      cur_color = cp.colors[Math.round( Math.random() * cp.colors.length)];

      if (!new_config['color']) new_config['color'] = cur_color; // view color (from url or at random)
      else cur_color = new_config['color'];
      
      tbar = [
         {text:SeqApp.colorBarTPL.apply({idx:idx, color:cur_color}), tooltip:'View Color', menu:new Ext.menu.ColorMenu({idx:idx, listeners: {'select': function(cm, color) {
               Ext.get('view_color_'+this.idx).setStyle('background-color', '#'+color);
               m_GViews[this.idx]['locator'].setColor(color);
               m_GViews[this.idx]['color'] = color;
               Reflection.reCreateReflections();
         }}}), hidden:m_Embedded}, 
         m_Embedded ? {hidden:m_Embedded} : '-',
         {iconCls:'new_fasta', hidden:m_Embedded, text:'Sequence', tooltip:'Create New Sequence Text View', idx:idx, handler:function() { SeqApp.createSequenceText(this.idx); } }, 
         m_Embedded ? {hidden:m_Embedded} : '-',
         {text:'Flip Strands', id:'flip-button-'+idx, tooltip:'Flip Sequence Strands', iconCls:'flip-strands', pressed:(new_config['flip']==true), enableToggle:true, idx:idx, handler:function() {SeqApp.flipStrand(this.idx);} }, 
         '-',
         {iconCls:'zoom_plus', tooltip:'Zoom In', idx:idx, handler:function() {SeqApp.zoomIn(this.idx);} }, 
         {iconCls:'zoom_minus', tooltip:'Zoom Out', idx:idx, handler:function() {SeqApp.zoomOut(this.idx);} }, 
         //{iconCls:'zoom_range', tooltip:'Zoom On Range', idx:idx, handler:function() {SeqApp.zoomRange(this.idx);} }, 
         {iconCls:'zoom_seq', tooltip:'Zoom To Sequence', idx:idx, handler:function() {SeqApp.zoomSeq(this.idx);} },
         '-',
         {tooltip:'Go To Position/Range', iconCls:'goto_position', idx:idx, handler:function() {SeqApp.gotoPositionDlg(this.idx);} },
         m_Embedded ? {hidden:m_Embedded} : '-', 
         {text:'Tools', tooltip:'Tools', iconCls:'views_tools', menu:new Ext.menu.Menu({items:[
             {text:'BLAST Search (Visible Range)', idx:idx, handler:function() { SeqApp.doBlastSearchRange(this.idx); } }
         ]}), hidden:m_Embedded},
         /*
         {text:'Previous', xtype: 'tbsplit', tooltip:'Previous feature', iconCls:'prev', menu:new Ext.menu.Menu({items:[
             {text:'Gene', idx:idx, handler:function() {  } },
             {text:'SNP', idx:idx, handler:function() {  } },
             {text:'Marker', idx:idx, handler:function() {  } },
             {text:'Featrue', idx:idx, handler:function() {  } },
             {text:'Blast Hit', idx:idx, handler:function() {  } }
         ]}), hidden:m_Embedded, handler:function() { 
            config = m_GViews[idx];
            Ext.Msg.alert('prev', config.selected_sig);} 
         },
         {text:'Next', xtype: 'tbsplit', tooltip:'Next feature', iconCls:'next', menu:new Ext.menu.Menu({items:[
             {text:'Gene', idx:idx, handler:function() {  } },
             {text:'SNP', idx:idx, handler:function() {  } },
             {text:'Marker', idx:idx, handler:function() {  } },
             {text:'Featrue', idx:idx, handler:function() {  } },
             {text:'Blast Hit', idx:idx, handler:function() {  } }
         ]}), hidden:m_Embedded},
         */
         {id:'seq-view-sync'+idx, hidden:m_NeedAlignmentView==false, text:'Sync Alignment View', idx:idx, handler:function(){SeqApp.onSyncAlignView(this.idx);} },
         
         '->',
         {iconCls:'x-tbar-loading', id:'seq-view-loading-'+idx, tooltip:'Reload View', idx:idx, handler:function(){SeqApp.onRefreshView(this.idx);} },
         '-',
         {text:'Markers', hidden:m_Embedded, iconCls:'markers', handler:function() { SeqApp.showMarkersDlg(); }}, 
         m_Embedded ? {hidden:m_Embedded} : '-',
         {text:cur_theme, id:menu_id, menuAlign:'tr-br?', menu:theme_menu},
         {text:'Options', iconCls:'configure', tooltip:'Configure Theme', idx:idx, handler:function() { SeqApp.themeConfigDlg(this.idx); } }
      ];
      
      tools = [
         {id:'close', qtip:'Close View', hidden:m_Embedded, handler:function(e, target, panel) { 
            idx = panel.idx;
            config = m_GViews[idx];
            config['locator'].getElement().remove(); // remove locator form panoram

            panel.ownerCt.remove(config['spacer'], true);
            panel.ownerCt.remove(panel, true);
            m_GViews[idx] = null;
            Reflection.reCreateReflections();
         } }
      ];
      
      view = m_SeqApp.add( {collapsible:true, title:'Loading...',
         tools:tools, idx:idx, tbar:tbar, html:'<div class="graphical_div" id="'+the_id+'"/>'}// + map_ctrl} 
      );
      m_SeqApp.doLayout();
      // add zoom controls
      tmpl = new Ext.Template(
         '<div class="the_map_control" '+ (m_Embedded ? 'style="visibility:hidden;"': '') +'/>',
         '<div id="left_ctrl_{idx}" class="map_ctrl_left" ext:qtip="Pan left"></div>',
         '<div id="drag_ctrl_{idx}" class="map_ctrl_drag" ext:qtip="Move Map Control"></div>',
         '<div id="right_ctrl_{idx}" class="map_ctrl_right" ext:qtip="Pan right"></div>',
         '<div id="zoomin_ctrl_{idx}" class="map_ctrl_zoomin" ext:qtip="Zoom In"></div>',
         '<div id="zoomout_ctrl_{idx}" class="map_ctrl_zoomout" ext:qtip="Zoom Out"></div>',
         '<div id="map_bar_{idx}" class="map_ctrl_bar" ext:qtip="Click To Zoom"></div>',
         '<div id="map_slider_{idx}" class="map_ctrl_slider" ext:qtip="Zoom Level"></div>'
      );

      new_config['slider'] = tmpl.append(Ext.get(the_id), {idx:idx}, true);
      
      slider_dd = new Ext.dd.DD( new_config['slider'] );
      slider_dd.setOuterHandleElId( Ext.get('drag_ctrl_'+idx) );
      
      Ext.get('left_ctrl_'+idx).on({ 'click': SeqApp.onLeftCtrl });
      Ext.get('right_ctrl_'+idx).on({ 'click': SeqApp.onRightCtrl });
      Ext.get('zoomin_ctrl_'+idx).on({ 'click': SeqApp.onZoomInCtrl });
      Ext.get('zoomout_ctrl_'+idx).on({ 'click': SeqApp.onZoomOutCtrl });
      Ext.get('map_bar_'+idx).on({ 'click': SeqApp.onSetZoomCtrl });
      
      
      Ext.get(the_id).on({ 'mousedown':SeqApp.onGViewMouseDown, 'mouseup':SeqApp.onGViewMouseUp, 'dblclick':SeqApp.onGViewDblClick,
         'mousemove':SeqApp.onGViewMouseMove, 'contextmenu':SeqApp.onGViewContextMenu, 'mouseout':SeqApp.onGViewMouseOut
      });
      //
      // Init view data
      
      new_config['idx']    = idx,
      new_config['view']   = view;
      new_config['spacer'] = spacer;
      new_config['the_id'] = the_id;
      
      m_GViews[idx] = new_config;
      m_GViews[idx]['locator'] = new Locator(idx, cur_color, true);

      m_GViewCounter++;
      SeqApp.loadGraphicalImage(new_config, new_config['from_seq'], new_config['len_seq']);
      Reflection.reCreateReflections(); // show reflections
   } // SeqApp.createGraphical


   SeqApp.getDefaultGraphicalConfig = function() {
      cfg = {
         type:'graphical',
         width_pix:0,
         theme:'', config_content:'', config_decor:'', config_color:'', config_label:'', 
         config_layout:'', config_spacing:'', config_alncolor:'on', config_geneModel:'',
         prev_cgi:null, 
         next_cgi:null, // image caching
         from_seq:0, 
         initializing:true,
         len_seq:m_SeqLength / 10, 
         vis_from_seq:0, vis_len_seq:0,
         loading:true, 
         scroll_pix:0,
         top_offset:0,
         flip:false,
         selection:null,
         reflections:null,
         selected_sig:null,
         scroll_pix_before_load:null
      }
      return cfg;
   }
   
   
   SeqApp.onFeatSearchSelect = function(record) {
        config = m_GViews[1];
        config['selected_sig'] = record.data.object_id;
        //seq_5pix = SeqApp.pixWidth2Seq(config, 5); // 5 pixels border
        
        from = record.data.from;// - seq_5pix;
        len = record.data.to - record.data.from;// + seq_5pix * 2;
        
        config['prev_cgi'] = null; // clear cache
        config['next_cgi'] = null;
        SeqApp.loadGraphicalImage(config, from, len);
   }
   
   SeqApp.onCenterPos = function(seq_pos, idx) {
        config = idx == undefined ? m_GViews[1] : m_GViews[idx];
        if (m_Origin != 0) seq_pos += m_Origin;
        
        SeqApp.clearCache(config);        

        new_len  = config['vis_len_seq'];
        new_from = seq_pos - config['vis_len_seq'] / 2;
        
        if (new_from < 0) new_from = 0;
        if (new_from + new_len > m_SeqLength) new_len = m_SeqLength - new_from;
        SeqApp.loadGraphicalImage(config, new_from, new_len);
   }
   

   SeqApp.onGViewClick = function(e) {
      elem = e.getTarget().id;
      idx = elem.substr(elem.length-1, 1);
      
      areas = SeqApp.hitTest(idx, e.getXY());
      if (areas /*&& e.shiftKey*/) { // set selection
         area = areas[ areas.length-1 ]; // [0]
         cur_sig = m_GViews[idx]['selected_sig'] ? m_GViews[idx]['selected_sig'].split(';') : [];
         new_sig = area['signature'];
         if ( e.ctrlKey ) {
            s_idx = cur_sig.indexOf(new_sig);
            if (s_idx != -1) { 
                cur_sig.splice(s_idx,1);
            } else { 
                cur_sig.push(new_sig); 
            }
            m_GViews[idx]['selected_sig'] = cur_sig.join(';');
         } else {
            m_GViews[idx]['selected_sig'] = new_sig;
         }
         /*
         if ( !e.ctrlKey || (cur_sig && cur_sig.indexOf(new_sig) != -1)) {
             m_GViews[idx]['selected_sig'] = new_sig;
         } else {
            m_GViews[idx]['selected_sig'] = m_GViews[idx]['selected_sig'] + ";" + new_sig;
         }*/
         SeqApp.refreshGView(idx);
         SeqApp.doExtensionPoint('feature_clicked', m_GViews[idx], area);
      }
   }
   
   SeqApp.onGViewDblClick = function(e) {
      e.stopPropagation();
      e.preventDefault();
      
      var the_elem = e.getTarget().id;
      if (the_elem.indexOf('graphical_id') == -1  && the_elem.indexOf('selection_id') == -1) return;
      
      idx = the_elem.substr(the_elem.length-1, 1);
      areas = SeqApp.hitTest(idx, e.getXY());
      
      if (the_elem.indexOf('right_ctrl_') == 0 || the_elem.indexOf('left_ctrl_') == 0 ||
          the_elem.indexOf('zoomin_ctrl_') == 0 || the_elem.indexOf('zoomout_ctrl_') == 0) {
         return;
      }
      
      if (areas) {
         area = areas[0];
         config = m_GViews[idx];
         //config['selected_sig'] = area['signature'];
         range = area['range'];
         
         new_from  = (range[0] < range[1]) ? range[0] : range[1];
         new_to    = (range[0] < range[1]) ? range[1] : range[0];
         
         new_len = new_to - new_from; // before offset
         l_offset = new_len / 100 * 6;  // calc. 5% offset
         new_from -= l_offset;
         new_to += l_offset;
         new_len = new_to - new_from; // after offset

        SeqApp.clearCache(config);
        SeqApp.loadGraphicalImage(config, new_from, new_len);
      } else { // zoom in to mouse pos 
         config = m_GViews[idx];
         SeqApp.clearCache(config);
         
         the_elem_xy = Ext.get( config['the_id'] ).getXY();
         xx = e.getXY()[0] - the_elem_xy[0] - config['scroll_pix'];
         
         pix = Math.abs(xx);//config['scroll_pix']) + e.getXY()[0] + the_elem_xy[0];
         seq_pos = SeqApp.pix2SeqG(config, pix);
         
         SeqApp.zoomInSeqPos(idx, seq_pos);
      }
   }
   
   SeqApp.onGViewMouseUp = function(e) {
      elem = e.getTarget().id;

      if (m_ReflectionActive) {
         m_ReflectionActive = false;
         cur_gv_target.setStyle('cursor', 'pointer');

         refl = m_AllReflections[m_ReflectionActiveIdx];
         this_config = m_GViews[refl.idx];
         flip = this_config['flip'];
         
         pix_from = Math.abs(this_config['scroll_pix']) + cur_gv_target.getLeft(true);
         pix_to   = pix_from + cur_gv_target.getWidth();
                  
         from_seq = Math.round( SeqApp.pix2SeqG(this_config, flip ? pix_to : pix_from) );
         to_seq   = Math.round( SeqApp.pix2SeqG(this_config, flip ? pix_from : pix_to) );

         if (refl.show_idx) {
            show_config = m_GViews[refl.show_idx];      
            show_config['loading'] = true; // start loading
            SeqApp.clearCache(show_config);
            SeqApp.loadGraphicalImage(show_config, from_seq, to_seq - from_seq);
         } else {
            m_FromSequence = from_seq;
            m_ToSequence = to_seq;
            Ext.getCmp('seq-panel').getUpdater().update({url:SeqApp.getSeqTextURL(), callback:SeqApp.seqLoadCallback});
         }
      } else if (m_SliderActive) {
         m_SliderActive = false;
         cur_gv_target.setStyle('cursor', 'pointer');
         config = m_GViews[m_SliderActiveIdx];

         slider_range = m_SliderMax - m_SliderMin;
         slider_val = cur_gv_target.getTop(true) - m_SliderMin;
                  
         vis_range = SeqApp.toSeqG(config);
         zoom_point = vis_range[0] + vis_range[2] / 2;
                  
         max_bases_to_show = m_SeqLength;
         min_bases_to_show = m_PanoramaWidth / 10;
                  
         var ee = Math.log(max_bases_to_show / min_bases_to_show);
         
         slider_val = Math.min(slider_range, Math.max(0, slider_val));
         
         len = min_bases_to_show * Math.exp(ee * slider_val / slider_range);
         from = zoom_point - len / 2;

         len = Math.min(m_SeqLength, Math.round(len));
         from = Math.round( Math.max(0, from) );
         if (from + len > m_SeqLength) {
            var extra = from + len - m_SeqLength;
            from -= extra;
         }
         
         SeqApp.clearCache(config);
         SeqApp.loadGraphicalImage(config, from, len);
      } else if (can_marker) { // if (elem.indexOf('marker_') == 0) { 
         cur_gv_target.setCursor(cur_marker, 'pointer'); 
         SeqApp.updateMarkersInfo(); // reload info
      } else if (can_drag) { //if (elem.indexOf('graphical_id' == 0)) {
         cur_gv_target.setStyle('cursor', 'default');
      } 
      
      if (did_pan_action) {
         config = m_GViews[cur_locator_idx];
         if (cur_locator_idx=='S') SeqApp.reloadSeqTextView();
         else if (config['type']!='panorama') SeqApp.reloadGView( config, can_scroll, can_resize_left, can_resize_right  );
      }
      
      can_resize_left = false; 
      can_resize_right = false; 
      can_scroll = false; 
      can_drag=false; 
      can_marker=false;
      cur_locator = null;
   }
   
   SeqApp.onGViewMouseDown = function(e) { 
      if (e.button == 0) {
         elem = e.getTarget().id;
         gview_xy=e.getXY(); 
         panorama_xy=e.getXY();
         did_pan_action = false;
         
         if (elem.indexOf('ideogram-id') == 0) {
             acc = elem.split('-')[2];
             view_url = document.location.href.split("?")[0];
             document.location.href = view_url + '?id=' + acc;
         } else if (elem.indexOf('reflection_id_') == 0) {
            m_ReflectionActive = true;
            cur_gv_target = Ext.get(elem);
            m_ReflectionActiveIdx = cur_gv_target.id.split('_')[2];
            cur_gv_target.setStyle('cursor', 'move');
         } else if (elem.indexOf('map_slider_') == 0) {
            m_SliderActive=true;
            cur_gv_target = Ext.get(elem);
            m_SliderActiveIdx = cur_gv_target.id.substr(elem.length-1, 1);
            cur_gv_target.setStyle('cursor', 'move');
         } else if (elem.indexOf('marker_') == 0  &&  !m_Embedded) {
            elem = elem.replace(/_label/, '');
            cur_gv_target = m_AllMarkers[elem];
            if (cur_gv_target.lock) return;
            can_marker=true; 
            cur_marker = elem;
            cur_gv_target.setCursor(cur_marker, 'move'); 
         } else if (elem.indexOf('graphical_id') == 0) {
            can_drag=true; 
            cur_gv_target = Ext.get(elem);
            cur_gv_target.setStyle('cursor', 'move');
         } else if (elem.indexOf('selection_id') == 0) {
            can_drag=true;
            cur_gv_target = Ext.get(elem);
            cur_gv_target.setStyle('cursor', 'move');
         } else if (elem.indexOf('left_resizer') == 0) {
            can_resize_left = true;
         } else if (elem.indexOf('right_resizer') == 0) {
            can_resize_right = true;
         } else if (elem.indexOf('pan_scroller') == 0) {
            can_scroll = true;
         } else return;

         if (can_scroll || can_resize_left || can_resize_right) { // clicked on locator
            cur_locator_idx = elem.substr(elem.length-1, 1);

            if (cur_locator_idx=='S') cur_locator = m_TextLocator.getElement();
            else cur_locator = m_GViews[cur_locator_idx]['locator'].getElement();
         }
      } 
   } // SeqApp.onGViewMouseDown
   
   SeqApp.showSearchDlg = function() {
      SeqApp.remoteJS({name:'json/SearchDlg.js' });
   }
   
   SeqApp.showPropertiesDlg = function(sig) {      
      link_url = theLinkCGI+'?id='+sig;
      if (m_NAA) link_url += '&naa='+m_NAA;
      if (m_Tracks) link_url += '&tracks='+m_Tracks;
      Ext.Ajax.request({url:link_url, success:function(res) { 
         links = Ext.decode(res.responseText); 
         html_links = '';
         if (links.length > 0) {
             html_links += "<br><br>Links & Tools<br>"
             for (i = 0; i != links.length; i++) {
                //html_links += ('<a href="http://www.ncbi.nlm.nih.gov' + links[i]['link'] + '" target="_blank">' + links[i]['label'] + '</a><br>');
                html_links += ('<a href="' + links[i]['link'] + '" target="_blank">' + links[i]['label'] + '</a><br>');
             }
         }
         url = theObjInfoCGI+'?id='+sig;
         if (m_Key) url += '&key=' + m_Key;
         if (m_NAA) url += '&naa='+m_NAA;
         if (m_Tracks) url += '&tracks='+m_Tracks;
         Ext.Ajax.request({url:url+'&fmt=flat', success:function(res) {          
            info = Ext.decode(res.responseText); 
            if (info.length > 0) info = info[0];
            Ext.Ajax.request({url:url, success:function(res) { 
               tip = Ext.decode(res.responseText); 
               if (tip.length > 0) tip = tip[0];
               propDlg = new Ext.Window({
                   layout:'fit',
                   title:'Properties',
                   iconCls:'properties',
                   minWidth:480, width:700, height:300,
                   constrain:true,
                   plain: true,
                   items:[{
                        xtype:'panel',
                        autoScroll:true,
                        html:'<pre><div style="padding:5px;font-family:courier, monospace;">' + tip['text'] + '<br><br>' + info['text'] + html_links + '<br>' + '</div></pre>' 
                   }],
                   buttons: [{text: 'Close', handler: function() {propDlg.close(); } }]
               });
               propDlg.show();
            }}); // Ext.Ajax
         }}); // Ext.Ajax
      }}); // Ext.Ajax
   }
      
   SeqApp.onGViewContextMenu = function(e) {
      e.preventDefault();  // this prevents the default contextmenu to open in Firefox (linux)
      e.stopPropagation();
      elem = e.getTarget().id;
      
      menu = new Ext.menu.Menu();
      if (elem.indexOf('marker_') == 0  &&  !m_Embedded) {
         elem = elem.replace(/_label/, '');
         idx = elem.split('_')[1];
         marker = m_AllMarkers[elem];
         menu.add(marker.marker_name);
         menu.add('-');
         menu.add({text:'Set To Position...', num:marker.marker_num, handler:function() { SeqApp.setMarkerPosition(this.num); } });
         menu.add({text:'Rename...', num:marker.marker_num, handler:function() { SeqApp.renameMarker(this.num); } });
         //menu.add({iconCls:'new_fasta', scope:this, text:'Open Sequence View at Marker', handler:function() { marker.showSequence(); } });
         open_views_menu = new Ext.menu.Menu();
         for (i = 0; i != m_GViews.length; i++) {
            config = m_GViews[i];
            if (config && config['type']=='graphical') {
               range = SeqApp.toSeqG(config);
               vis_range = Math.round(range[0]+1) + '&nbsp;-&nbsp;' + Math.round(range[1]+1);
               text = 'Graphical View (' + vis_range + ')';
               open_views_menu.add({text:text, idx:config['idx'], iconCls:'color_rect_'+config['color'], handler:function() { marker.centerInView(this.idx); } });
            }
         }
         open_views_menu.add('-');
         open_views_menu.add({iconCls:'new_fasta', text:'Sequence View', handler:function() { marker.showSequence(); } });
           
         menu.add({scope:this, text:'Reveal Marker in...', menu:open_views_menu });
         menu.add('-');
         menu.add({scope:this, text:marker.lock?'Unlock Marker':'Lock Marker', handler:function() { marker.lockMarker(); } });         
         menu.add('-');
         menu.add({iconCls:'marker-remove', num:marker.marker_num, text:'Remove Marker', handler:function() { SeqApp.deleteMarker(this.num); } });
         if (idx != 0) {
            menu.add('-');
            menu.add({iconCls:'zoom_seq', idx:idx, text:'Zoom To Sequence At Marker', handler:function() { marker.zoomSeqMarker(this.idx); } });
         }
         menu.add('-');
         menu.add({iconCls:'markers', text:'Marker Details', handler:function() { SeqApp.showMarkersDlg(); } });
      } else if (elem.length>0) {
         idx = elem.substr(elem.length-1, 1);
         if (elem.indexOf('selection_id') != 0) {
            x_pos = e.getPageX() - Ext.get(elem).getX(); // translate from page to element
            cfg = idx == 'd' ? SeqApp.getPanorama() : m_GViews[idx];
            if (!m_Embedded) {
               menu.add({text:'Set New Marker At Position', iconCls:'markers', scope:this, cfg:cfg, handler:function() { SeqApp.setNewMarkerDlg(cfg, x_pos); } });
               menu.add('-');
            }
            if (m_Origin) menu.add({text:'Reset Sequence Origin', iconCls:'origin', scope:this, cfg:cfg, handler:function() { SeqApp.clearOrigin(); } });
            else menu.add({text:'Set Sequence Origin At Position', iconCls:'origin', scope:this, cfg:cfg, handler:function() { pix = Math.abs(cfg['scroll_pix']) + x_pos; seq_pos = Math.round( SeqApp.pix2SeqG(cfg, pix) ); SeqApp.setOriginDlg(seq_pos); } });
         }
         
         if (elem.indexOf('panorama') != 0) {
            menu.add({iconCls:'zoom_plus', text:'Zoom In', idx:idx, handler:function() {SeqApp.zoomIn(this.idx);} });
            menu.add({iconCls:'zoom_minus', text:'Zoom Out', idx:idx, handler:function() {SeqApp.zoomOut(this.idx);} });
            menu.add({iconCls:'zoom_range', text:'Zoom On Range', idx:idx, handler:function() {SeqApp.zoomRange(this.idx);} });
            menu.add({iconCls:'zoom_seq', text:'Zoom To Sequence', idx:idx, handler:function() {SeqApp.zoomSeq(this.idx);} });
         }
      }

      if (elem.indexOf('selection_id') == 0) {
         menu.add('-');
         idx = elem.substr(elem.length-1, 1);
         areas = SeqApp.hitTest(idx, e.getXY());
         area = areas[0]
         
         config = m_GViews[idx];
         range = area['range'];
         seq_pos = range[0];
         
         // Properties
         menu.add({text:'Set Sequence Origin At Feature', iconCls:'origin', seq_pos:seq_pos, handler:function() { SeqApp.setOriginDlg(this.seq_pos); } });
         menu.add('-');
         
         if (areas.length==2) { // two features overlayed
            prop_menu = new Ext.menu.Menu();
            prop_menu.add( {text:'Properties: mRNA', iconCls:'properties', sig:areas[0]['signature'], handler:function() { SeqApp.showPropertiesDlg(this.sig); } } );
            prop_menu.add( {text:'Properties: CDS', iconCls:'properties',  sig:areas[1]['signature'], handler:function() { SeqApp.showPropertiesDlg(this.sig); } } );
            menu.add({text:'Properties', iconCls:'properties', menu:prop_menu });
         } else {
            menu.add({text:'Properties', iconCls:'properties', sig:area['signature'], handler:function() { SeqApp.showPropertiesDlg(this.sig); } });
         }
         
         if (areas.length==2) { // two features overlayed
            tools_menu = new Ext.menu.Menu();
            area_0 = areas[0];
            area_1 = areas[1];
            // Links
            if (area_0['link_menu_mrna']) {
               tools_menu.add( {text:'Views & Tools: mRNA', iconCls:'views_tools',  menu:area_0['link_menu_mrna'] } );
            } else {
               link_url = theLinkCGI+'?id='+area_0['signature'];
               if (m_NAA) link_url += '&naa=' + m_NAA;
               if (m_Tracks) link_url += '&tracks='+m_Tracks;
               Ext.Ajax.request({url:link_url, scope:this, success:function(res) { 
                  links = Ext.decode(res.responseText); 
                  area_0['link_menu_mrna'] = new Ext.menu.Menu();
                  for (i = 0; i != links.length; i++) {
                     //area_0['link_menu_mrna'].add({text:links[i]['label'], url:links[i]['link'], handler:function(conf) { window.open('http://www.ncbi.nlm.nih.gov'+conf.url); } });
                     area_0['link_menu_mrna'].add({text:links[i]['label'], url:links[i]['link'], handler:function(conf) { window.open(conf.url); } });
                  }
                  tools_menu.add( {text:'Views & Tools: mRNA', iconCls:'views_tools',  menu:area_0['link_menu_mrna'] } );
               }}); // Ext.Ajax
            }
            if (area_1['link_menu_cds']) {
               tools_menu.add( {text:'Views & Tools: CDS', iconCls:'views_tools',  menu:area_1['link_menu_cds'] } );
            } else {
               link_url = theLinkCGI+'?id='+area_1['signature'];
               if (m_NAA) link_url += '&naa=' + m_NAA;
               if (m_Tracks) link_url += '&tracks='+m_Tracks;
               Ext.Ajax.request({url:link_url, scope:this, success:function(res) { 
                  links = Ext.decode(res.responseText); 
                  area_1['link_menu_cds'] = new Ext.menu.Menu();
                  for (i = 0; i != links.length; i++) {
                     //area_1['link_menu_cds'].add({text:links[i]['label'], url:links[i]['link'], handler:function(conf) { window.open('http://www.ncbi.nlm.nih.gov'+conf.url); } });
                     area_1['link_menu_cds'].add({text:links[i]['label'], url:links[i]['link'], handler:function(conf) { window.open(conf.url); } });
                  }
                  tools_menu.add( {text:'Views & Tools: CDS', iconCls:'views_tools',  menu:area_1['link_menu_cds'] } );
               }}); // Ext.Ajax
            }
            menu.add('-');
            menu.add({text:'Views & Tools', iconCls:'views_tools', menu:tools_menu });
         } else {
            // Links
            if (area['link_menu']) {
               menu.add('-');
               menu.add({iconCls:'views_tools', text:'Views & Tools', menu:area['link_menu'] });
            } else {
               link_url = theLinkCGI+'?id='+area['signature'];
               if (m_NAA) link_url += '&naa=' + m_NAA;
               if (m_Tracks) link_url += '&tracks='+m_Tracks;
               Ext.Ajax.request({url:link_url, scope:this, success:function(res) { 
                  links = Ext.decode(res.responseText); 
                  if (links.length > 0) {
                      area['link_menu'] = new Ext.menu.Menu();
                      for (i = 0; i != links.length; i++) {
                         //area['link_menu'].add({text:links[i]['label'], url:links[i]['link'], handler:function(conf) { window.open('http://www.ncbi.nlm.nih.gov'+conf.url); } });
                         area['link_menu'].add({text:links[i]['label'], url:links[i]['link'], handler:function(conf) { window.open(conf.url); } });
                      }
                      menu.add('-');
                      menu.add({iconCls:'views_tools', text:'Views & Tools', menu:area['link_menu'] });
                  } // links not empty (like for alignments)
               }}); // Ext.Ajax
            }
            
         }
         
      }
      if (menu.items.length>0) menu.showAt(e.getXY());
   }


   SeqApp.onGViewMouseOut = function(e) {
      if (can_scroll || can_drag || can_marker) {
         s_x = m_SeqApp.getEl().getX() + 4; // our margin
         s_w = m_SeqApp.getEl().getWidth();
         
         if (e.getPageX() <= s_x || e.getPageX() >= s_w) { // GView
            can_scroll = false; 
            can_drag=false;
            can_marker=false;
         }
      }
   }

   
   SeqApp.onGViewMouseMove = function (e) { 
      if (can_marker  ||  can_drag  ||  m_SliderActive  ||  m_ReflectionActive) {
         elem = e.getTarget().id;
         delta_x = gview_xy[0] - e.getXY()[0];
         delta_y = gview_xy[1] - e.getXY()[1];
                  
         if (can_marker) { // Markers
            cur_gv_target.movePix(cur_marker, delta_x, true);
            gview_xy = e.getXY(); // save new values
         } else if (m_ReflectionActive) {
            new_left = cur_gv_target.getLeft(true) - delta_x;
            cur_gv_target.setLeft(new_left);
            gview_xy = e.getXY(); // save new values
         } else if (m_SliderActive) {
            new_top = cur_gv_target.getTop(true) - delta_y;
            new_top = Math.min(new_top, m_SliderMax);
            new_top = Math.max(new_top, m_SliderMin);

            cur_gv_target.setTop(new_top);
            gview_xy = e.getXY(); // save new values
         } else if (can_drag  &&  (elem.indexOf('graphical_id') == 0 || elem.indexOf('selection_id') == 0)) { // GView
            idx = elem.substr(elem.length-1, 1);
            config = m_GViews[idx];
         
            cur_x = config['scroll_pix'];
            cur_x = cur_x - delta_x;
            gview_xy = e.getXY(); // save new values
            SeqApp.scrollViewG(idx, cur_x, delta_x > 0 ? PAN_RIGHT : PAN_LEFT);
         }
      } else {
         elem = e.getTarget().id;
         if (elem.indexOf('graphical_id') == 0) { // GView
             idx = elem.substr(elem.length-1, 1);
             SeqApp.highlightElement(idx, e.getXY());
         } else if (elem.indexOf('ideogram-id') == 0) {
             m_CurIdeoElem = Ext.get(elem);
             if (m_CurIdeoElem) m_CurIdeoElem.addClass('ideogram_chromosome_over');
         } else {
             if (m_CurIdeoElem) m_CurIdeoElem.removeClass('ideogram_chromosome_over');
         }
      }
      
      SeqApp.x_ClearBrowserSelection(); // clear selection
      
      delta = panorama_xy[0] - e.getXY()[0];
      if (can_scroll) {
         //alert(cur_locator.getWidth() + '   ' + m_PanoramaWidth);
         new_left = Math.max(0, cur_locator.getLeft(true) - delta);
         new_left = Math.min(m_PanoramaWidth - 2 - cur_locator.getWidth(), new_left);
         cur_locator.setLeft(new_left);
         did_pan_action = true;         
      } else if (can_resize_left) {
         old_left = cur_locator.getLeft(true);
         new_left = Math.max(0, old_left - delta);
         new_left = Math.min(cur_locator.getRight(true) - 2, new_left);
         cur_locator.setLeft(new_left);
         cur_locator.setWidth(cur_locator.getWidth() + (old_left - new_left));
         did_pan_action = true;
      } else if (can_resize_right) {
         new_width = Math.max(2, cur_locator.getWidth()-delta);
         new_width = Math.min(m_PanoramaWidth - 2 - cur_locator.getLeft(true), new_width);
         cur_locator.setWidth(new_width);
         did_pan_action = true;
      } /*else {
         panorama = SeqApp.getPanorama();
         SeqApp.highlightElement(panorama['idx'], e.getXY());
      }*/
      panorama_xy = e.getXY(); // save new values
      
      
      
   }
   
   SeqApp.hitTest = function(idx, page_xy) {
      the_area = null;
      config = m_GViews[idx];

      elem_xy = Ext.get( config['the_id'] ).getXY();
      xx = page_xy[0] - elem_xy[0] - config['scroll_pix'];
      yy = page_xy[1] - elem_xy[1] - config['top_offset'];
      for (a in config['from_cgi']['areas']) {
         area = config['from_cgi']['areas'][a];
         sig = area['signature'];
         if (!sig || sig.length==0) continue;
         bounds = area['bounds'];
         
         left  = config['flip'] ? bounds['r'] : bounds['l'];
         right = config['flip'] ? bounds['l'] : bounds['r'];
         
         if (xx >= left-1 && xx <= right+1 && yy >= bounds['t']-1 && yy <= bounds['b']+1) {
            if (!the_area) the_area = [];
            the_area.push(area);
         }
      }
      return the_area;
   }
   
   SeqApp.highlightElement = function(idx, page_xy) {
      config = m_GViews[idx];
      areas = SeqApp.hitTest(idx, page_xy);
      
      if (areas && !config['selection']) {
         config['selection'] = new Selection(idx, areas);
      } else if (config['selection']) {
        //console.log(areas);
         if (config['selection'].qtip) {
            config['selection'].qtip.destroy()
         }
         config['selection'].getElement().remove();
         config['selection'] = null;
      }
   }
   
   SeqApp.zoomInSeqPos = function(idx, seq_pos) {
      config = m_GViews[idx];
      SeqApp.clearCache(config);

      config['vis_from_seq'] = Math.max(0, seq_pos - config['vis_len_seq'] / 2);            
      new_len  = config['vis_len_seq'] / 2;
      new_from = config['vis_from_seq'] + new_len / 2;
      if (new_from + new_len > m_SeqLength) new_len = m_SeqLength - new_from;

      SeqApp.loadGraphicalImage(config, new_from, new_len);
   }
   
   SeqApp.zoomRange = function(idx) {
      config = m_GViews[idx];
      
      chooseRangeDlg = new Ext.Window({
             layout:'fit', modal:true, title:'Choose sequence range', width:270, height:140,
             collapsible:false, constrain:true, resizable:false, closeAction:'close', iconCls:'zoom_range',
             items:[{
                   xtype:'form',
                   bodyStyle:'padding:5px;',
                   labelWidth: 70,
                   frame:true,
                   labelAlign:'right',
                   items:[
                      {xtype:'textfield', name:'from', width:'90%', fieldLabel:'From', 
                            value:(config['url_from'] ? config['url_from'] : config['vis_from_seq'] + 1) },
                      {xtype:'textfield', name:'to', width:'90%', fieldLabel:'To', 
                            value:(config['url_to'] ? config['url_to'] : config['vis_from_seq'] + config['vis_len_seq'] + 1) }
                   ]
             }],
             buttons:[
               {text:'Zoom on range', handler: function() {
                  values = chooseRangeDlg.items.items[0].getForm().getValues();
                  from = values['from'].replace(/k/, '000').replace(/m/, '000000');
                  to = values['to'].replace(/k/, '000').replace(/m/, '000000');
                  new_from = parseInt(from);
                  new_to   = parseInt(to);
                  if (isNaN(new_from)  ||  isNaN(new_to)  ||  new_from >= new_to  ||
                            new_from >= m_SeqLength || new_to >= m_SeqLength) {
                     Ext.MessageBox.show({title:'Zoom On Range', msg:'The range you provided is not valid.', buttons:Ext.MessageBox.OK, icon:Ext.MessageBox.ERROR});
                  } else {
                     SeqApp.clearCache(config);
                     config['url_from'] = new_from;
                     config['url_to'] = new_to;
                     //console.log(new_from + ' / ' + new_to)
                     chooseRangeDlg.close();
                     
                     SeqApp.loadGraphicalImage(config, new_from, new_to - new_from);
                  }
               }},
               {text:'Cancel', handler: function() {chooseRangeDlg.close(); } }
             ]
      });
      chooseRangeDlg.show();
   }
   
   
   SeqApp.zoomIn = function(idx) {
      config = m_GViews[idx];
      SeqApp.clearCache(config);
      
      new_len  = config['vis_len_seq'] / 2;
      new_from = config['vis_from_seq'] + new_len / 2;

      SeqApp.loadGraphicalImage(config, new_from, new_len);
   }
   
   SeqApp.zoomSeq = function(idx) {
      config = m_GViews[idx];
      SeqApp.clearCache(config);
      
      new_len  = 100;//config['vis_len_seq'] / 2000;
      new_from = (config['vis_from_seq'] + config['vis_len_seq']/2) - new_len / 2;
   
      SeqApp.loadGraphicalImage(config, new_from, new_len);
   }
   
   
   SeqApp.zoomOut = function(idx) {
      config = m_GViews[idx];
      SeqApp.clearCache(config);
      
      
      new_len  = config['vis_len_seq'] * 2;
      new_from = config['vis_from_seq'] - config['vis_len_seq'] / 2;
      
      new_from = Math.max(0, new_from); // not to exteed the sequence range
      if (new_from + new_len > m_SeqLength) new_len = m_SeqLength - new_from;
      SeqApp.loadGraphicalImage(config, new_from, new_len);
   }
   
   SeqApp.flipStrand = function(idx) {
      config = m_GViews[idx];
      config['flip'] = !config['flip'];
      SeqApp.clearCache(config);
      
      SeqApp.loadGraphicalImage(config, config['vis_from_seq'], config['vis_len_seq']);
   }
   
   

   SeqApp.loadGraphicalImage = function(config, from, len) {
      var req_from = from;
      var req_len = len;
      
      //console.log('from: ' + req_from + ' / len: ' + req_len)
      chunk_width = m_ChunkWidth; // start with default chunk width
      screen_width = config['view'].getInnerWidth() - 2; // our panel width in pixels
      off_pix = (m_ChunkWidth - screen_width ) / 2; // desired image overhang 
      bpp = len / screen_width; // bases per pixel
      from -= off_pix * bpp; // apply overhang to from
      len += off_pix * bpp * 2;  // apply overhang to len

      // our safety validations
      if (from < 0) { // less than sequence start?
         chunk_width = chunk_width + from / bpp; // reduce chunk width
         len = len + from; // reduce length
         from = 0; // sequence start
         off_pix = req_from / bpp; // scrolling offset equals original from (req_from)
      }
      if (from + len > m_SeqLength) { // exeed sequence length?
         ex = m_SeqLength - (from + len);
         chunk_width = chunk_width + ex / bpp; // reduce chunk width
         len = len + ex; // reduce length
      }
      
      from = Math.round(from);
      len = Math.round(len);
      
      len = Math.max(len, Math.min(m_SeqLength, 250));
      config['off_pix'] = off_pix; // offset before load
      
      // go for it!!
      url = theGraphicCGI + '&client=seqviewer&width='+chunk_width+'&from='+from+'&len='+len
      if (config['selected_sig']) url = url + '&select=' + config['selected_sig'];
      if (config['flip']) url += '&flip=true';

      url = url + SeqApp.addUrlVisualConfig(config);
      if (m_Key) url += '&key=' + m_Key;
      if (m_NAA) url += '&naa=' + m_NAA;
      if (m_Tracks) url += '&tracks='+m_Tracks;
      if (m_Origin !=0 ) url += '&origin=' + m_Origin;
      
      Ext.getCmp('seq-view-loading-'+config['idx']).disable();
      Ext.Ajax.request({url:url,  autoAbort:!config['initializing'], success:function(res) { 
         config['initializing'] = false;
         from_cgi = Ext.decode(res.responseText);
         SeqApp.x_UpdateDeveloperConsole(res.responseText, from_cgi);
         Ext.getCmp('seq-view-loading-'+config['idx']).enable();
         if (config['selected_sig'] && config['selection']) { config['selection'].getElement().remove(); config['selection'] = null; } // clear floating selection
         
         if (from_cgi['error']) {
            //Ext.MessageBox.show({title:'Image loading error', msg:from_cgi['error'], buttons:Ext.MessageBox.OK, icon:Ext.MessageBox.INFO});
            config['view'].setTitle('Image loading error: ' + from_cgi['error']);
         } else {
            SeqApp.applyGraphicalResults(config, from_cgi);
            SeqApp.doExtensionPoint('graphical_image_loaded', config, '');
         }
         
         SeqApp.loadRID_or_URL();
      }}); // Ext.Ajax
   }
   
   SeqApp.loadRID_or_URL = function() {
      if (m_DataRID  ||  m_DataURL) {
         config = {data_action:'uploading', key:m_Key, accession:GI };
         if (m_DataRID) { config['rid'] = m_DataRID; config['format'] = 'rid'; }
         else if (m_DataURL) { config['url'] = m_DataURL; config['format'] = 'url'; }
         
         Ext.Ajax.request({url:theSvDataCGI, params:config, success:function(res) { 
            from_cgi = Ext.decode(res.responseText);
            m_Key = from_cgi['key'];
            m_DataRID = null;
            m_DataURL = null;
            SeqApp.reloadAllGViews();
         }}); // Ext.Ajax
      }
   }
   
   SeqApp.addUrlVisualConfig = function(config) {
      url = '&theme=NCBI ' + config['theme'];
      
      def_theme = config['def_theme']

      if (config['config_content'] && config['config_content'].length>0) url += '&content=' + config['config_content'];
      else if (def_theme) url += '&content=' + m_ViewParams['controls']['content'][ def_theme['content'] ]
      
      if (config['config_decor']   && config['config_decor'].length>0)   url += '&decor='   + config['config_decor'];
      else if (def_theme) url += '&decor=' + m_ViewParams['controls']['decor'][ def_theme['decor'] ]
      
      if (config['config_color']   && config['config_color'].length>0)   url += '&color='   + config['config_color'];
      else if (def_theme) url += '&color=' + m_ViewParams['controls']['color'][ def_theme['color'] ]
      
      if (config['config_label']   && config['config_label'].length>0)   url += '&label='   + config['config_label'];
      else if (def_theme) url += '&label=' + m_ViewParams['controls']['label'][ def_theme['label'] ]
      
      if (config['config_geneModel']   && config['config_geneModel'].length>0)   url += '&geneModel='   + config['config_geneModel'];
      else if (def_theme) url += '&geneModel=' + m_ViewParams['controls']['geneModel'][ def_theme['geneModel'] ]
      
      if (config['config_layout']  && config['config_layout'].length>0)  url += '&layout='  + config['config_layout'];
      else if (def_theme) url += '&layout=' + m_ViewParams['controls']['layout'][ def_theme['layout'] ]
      
      if (config['config_spacing'] && config['config_spacing'].length>0) url += '&spacing=' + config['config_spacing'];
      else if (def_theme) url += '&spacing=' + m_ViewParams['controls']['spacing'][ def_theme['spacing'] ]
      
      url += '&alncolor=' + config['config_alncolor'];
      return url;
   }
    
   SeqApp.loadPanoramaImage = function() {
      panorama = SeqApp.getPanorama();
      if (!panorama) return; // still initializing the viewer (first load)
      
      m_PanoramaWidth = panorama['view'].getInnerWidth() - 2;
      SeqApp.resizeWatcher();
      
      url = thePanoramaCGI + '&width=' + m_PanoramaWidth;
      url = url + SeqApp.addUrlVisualConfig(panorama);
      if (m_Origin !=0 ) url += '&origin='+m_Origin;
      if (m_Key) url += '&key='+m_Key;
      if (m_NAA) url += '&naa=' + m_NAA;
      if (m_Tracks) url += '&tracks='+m_Tracks;
      
      Ext.Ajax.request({url:url, autoAbort:!config['initializing'],  success:function(res) { 
         panorama['initializing'] = false;
         the_div = Ext.get(panorama['the_id']);
         from_cgi = Ext.decode(res.responseText);
         panorama['from_cgi'] = from_cgi;
         m_PanoramaHeight = from_cgi['h'] + m_PanHolderSize + 1;

         the_div.setStyle('background-image', 'url(' + from_cgi['img_url'] + ')'); 
         the_div.setStyle('height', m_PanoramaHeight+'px' ); 

         panorama['view'].setHeight(m_PanoramaHeight + 2);
         
         SeqApp.doExtensionPoint('panorama_image_loaded', panorama, '');
         
         SeqApp.updateMarkersSize(panorama); // update markers
         SeqApp.updateLocatorHeights(); // update locators
         //for(idx = 0; idx != m_GViewCounter; idx++) SeqApp.updateTitle( m_GViews[idx] ); // update title
      }}); // Ext.Ajax
   }    

   var chromorder=function(a,b) {
       return(a['chrom']['order'] < b['chrom']['order'])? -1:((a['chrom']['order'] > b['chrom']['order'])? 1:0);
   };
   
   /*SeqApp.loadIdeogramImage = function() {
         url = 'rasterideo.cgi';
         Ext.Ajax.request({url:url, success:function(res) { 
            the_div = Ext.get('ideogram_div');
            from_cgi = Ext.decode(res.responseText);
            image = from_cgi['image'];
            img_url = "http://mwebdev.ncbi.nlm.nih.gov/projects/mapview.dev/ncFetch.cgi?type=png&key=" + image['nc_key'];
            
            the_div.setStyle('height', (image['dimensions']['height']+18)+'px' ); 
            layout = from_cgi['layout']['layout'];
            ideograms = from_cgi['karyotype']['ideograms'];
            
            for (i = 0;  i != layout.length-2;  i++) ideograms[i]['layout'] = layout[i];
            ideograms.sort(chromorder);
            
            total = layout.length - 2;
            str = '';
            for (i = 0;  i != total;  i++) {
                layout = ideograms[i]['layout'];
                height = layout['position']['height'];
                width  = layout['position']['width'];
                left   = layout['position']['left'];
                top    = layout['position']['top'];
                
                ff = ideograms[i]['chrom'];

                acc = ff['seq'] ? ff['seq']['accession']['other']['accession'] : '';
                
                spacer_width = (m_PanoramaWidth - total * (width+3)) / total;
                spacer_width = Math.floor(spacer_width)                
                
                label = ff['chrom'];
                
                str += '<div id="ideogram-id-'+acc+'" class="ideogram_chromosome" style="background-position:-'+left+'px -'+top+'px;background-image:url('+img_url+
                       ');width:'+width+'px;height:'+height+'px;"><div class="ideogram_label">'+label+
                       '</div></div><div class="ideogram_spacer" style="width:'+spacer_width+'px;">&nbsp;</div>';
            }
            tmpl = new Ext.Template(str);
            tmpl.overwrite(Ext.get(the_div), true);
         }}); // Ext.Ajax
   }*/
   
   SeqApp.checkForAdditionalData = function() {
      Ext.Ajax.request({url:theMoreDataCGI, success:function(res) { 
         out = Ext.decode(res.responseText); 
         if (out.length>0) {
            the_button = Ext.getCmp('additional_menu');
            items=[];
            for (i=0; i!=out.length;i++) {
               acc = out[i];
               items.push(  {text:'<a href="#" ext:qtip="'+acc['naa']+'">'+acc['name']+'</a>', naa:acc['naa'], checked:false}  );
            }
            the_button.menu = new Ext.menu.Menu({items:items, listeners:{'itemclick': function(baseItem, e) { 
               e_array = m_NAA && m_NAA.length > 0 ? m_NAA.split(',') : [];
               loaded = false;

               for (i = 0;  i != e_array.length;  i++) {
                  if (e_array[i]==baseItem.naa) {
                     loaded = true;
                     delete e_array[i];
                     break;
                  }
               }

               if (!loaded) e_array.push( baseItem.naa );
               m_NAA = e_array.join(',');
               SeqApp.loadPanoramaImage();
               SeqApp.reloadAllGViews();
            }} });
            the_button.setVisible(true);
         } // length>0
      }});
   }

     
   SeqApp.getPanorama = function() {
      for (v in m_GViews) {
         config = m_GViews[v];
         if (config && config['type'] == 'panorama') return config;//['view'];
      }
      return null;
   }
   
   
   SeqApp.reloadAllGViews = function() {
      for(idx = 0; idx != m_GViewCounter; idx++) SeqApp.refreshGView(idx);
   }
   
   SeqApp.refreshGView = function(idx) {
      config = m_GViews[idx];
      if (config['type']=='panorama')  return;
      
      config['loading'] = true; // start loading
      SeqApp.clearCache(config);
      
      SeqApp.loadGraphicalImage(config, config['vis_from_seq'], config['vis_len_seq']); // reload using current visible range
   }
   
   SeqApp.gotoPositionDlg = function(idx) {
      Ext.MessageBox.prompt('Go to position/range', 'Please enter sequence position or range:', function(btn, text) {
         text = text.replace(/[, ]/g,'');
         var pattern = /^[0-9]+(\.[0-9]*)?[km]?$/i;

         if (btn!='ok'  || text.length==0) return;
         
         var splits = text.split(/:|\.\.+|-|to/i);
         
         if (splits == null || splits.length < 1 || splits.length > 2) {
            Ext.MessageBox.alert('Go to position/range', 'Invalid position/range.');
            return;
         }
         for (var i = 0; i < splits.length; ++i) {
            if (!pattern.test(splits[i])) {
                Ext.MessageBox.alert('Go to position/range', '"' + splits[i] + '" is not a number.');
                return;
            }
            var val = 1;
            var last_char = splits[i].charAt(splits[i].length - 1).toUpperCase();
            if (last_char == 'K' || last_char == 'M') {
                splits[i] = splits[i].substr(0, splits[i].length - 1);
                if (last_char == 'K') {
                   val = 1000;
                } else {
                   val = 1000000; 
                }
            }
            splits[i] = val * splits[i];
         }

         if (splits.length == 2) { // range is specified
                //Does this handle shifted origins?
               new_from = parseInt(splits[0]);
               new_to   = parseInt(splits[1]);
               if (isNaN(new_from)  ||  isNaN(new_to)  ||  new_from >= new_to  ||
                      new_from >= m_SeqLength || new_to >= m_SeqLength) {
                  Ext.MessageBox.show({title:'Go to position/range', msg:'The range you provided is not valid.', buttons:Ext.MessageBox.OK, icon:Ext.MessageBox.ERROR});
               } else {
                  config = m_GViews[idx];
                  SeqApp.clearCache(config);
                  SeqApp.loadGraphicalImage(config, new_from, new_to - new_from);
               }
               
         } else { // single position             
            new_from = splits[0] - config['vis_len_seq'] / 2;
            if (m_Origin != 0)  new_from += m_Origin;
            config = m_GViews[idx];
            SeqApp.clearCache(config);

            if (new_from < 0) new_from = 0;
            if (new_from + config['vis_len_seq'] > m_SeqLength) new_from = m_SeqLength - config['vis_len_seq'];
            
            SeqApp.loadGraphicalImage(config, new_from, config['vis_len_seq']);
         } // end single position
         
      }, this, false); 
   }
   
   SeqApp.onWindowResize = function() {
      if (m_SeqApp) {
         SeqApp.loadPanoramaImage(); // reload panorama and all the views
         SeqApp.reloadAlignmentImage();
         //SeqApp.loadIdeogramImage();
      }
      //SeqApp.reloadAllGViews();
   }
   
   SeqApp.resizeWatcher = function() {
      if (m_SeqApp && 
           m_PanoramaWidth != panorama['view'].getInnerWidth() - 2) {
          SeqApp.onWindowResize();
      }
      SeqApp.resizeWatcher.defer(500, this);
   }
   
   /* window resize */
   Ext.EventManager.onWindowResize(SeqApp.onWindowResize);
   
   // coordinates P
   SeqApp.toSeqP = function(x) { // screen 2 sequence (panorama)
      return m_SeqLength * x / m_PanoramaWidth;
   }

   SeqApp.toPixP = function(x) { // sequence 2 screen (panorama)
      return x * m_PanoramaWidth / m_SeqLength;
   }

   // coordinates G
   SeqApp.pixWidth2Seq = function(config, width_pix) {
      view_width = config['view'].getInnerWidth() - 2;
      img_width = config['from_cgi'] ? config['from_cgi']['w'] : m_ChunkWidth;
      
      bpp = config['len_seq'] / img_width;
      return bpp * width_pix;
   }
      
   SeqApp.seq2PixG = function(config, seq_pos) { // sequence 2 screen (gview)
      if (config['type']=='panorama') return SeqApp.toPixP(seq_pos);
      
      view_width = config['view'].getInnerWidth() - 2;
      img_width = config['from_cgi'] ? config['from_cgi']['w'] : m_ChunkWidth;
      
      bpp = config['len_seq'] / img_width;
      if (config['flip']) {
         return (config['len_seq'] - (seq_pos - config['from_seq'])) / bpp;
      } else {
         return (seq_pos - config['from_seq']) / bpp;
      }
   }
   
   SeqApp.pix2SeqG = function(config, pix_pos) { // screen 2 sequence (gview)
      if (config['type']=='panorama') return SeqApp.toSeqP(pix_pos);
      
      view_width = m_PanoramaWidth;//config['view'].getInnerWidth() - 2;
      img_width = config['from_cgi'] ? config['from_cgi']['w'] : m_ChunkWidth;

      bpp = config['len_seq'] / img_width;

      //console.log('seq: ' + (config['from_seq'] + Math.abs(pix_pos) * bpp))      
      if (config['flip']) pix_pos = img_width - pix_pos;

      return config['from_seq'] + pix_pos * bpp; // (config['from_seq'] + Math.abs(pix_pos) * bpp - 0.5);
   }
   
   
   SeqApp.toSeqG = function(config) { // screen 2 sequence range (more precise calculation required!!!)
      flip = config['flip'];
      
      view_width = config['view'].getInnerWidth() - 2;
      img_width = config['from_cgi'] ? config['from_cgi']['w'] : m_ChunkWidth;
      
      bpp = config['len_seq'] / img_width;
      if (flip) {
         left = config['from_seq'] + config['len_seq']
         the_from = left - (Math.abs(config['scroll_pix']) + view_width) * bpp;
         the_to = left - Math.abs(config['scroll_pix']) * bpp;
      } else {
         the_from = config['from_seq'] + Math.abs(config['scroll_pix']) * bpp;
         the_to = config['from_seq'] + (Math.abs(config['scroll_pix']) + view_width) * bpp;
      }
      return [the_from, the_to, the_to - the_from]; // from, to, len
   }

   SeqApp.onLeftCtrl = function(e) {
      elem = e.getTarget().id;
      if (elem.indexOf('left_ctrl_') != 0) return;
      idx = elem.substr(elem.length-1, 1);
      
      SeqApp.scrollViewG(idx, m_GViews[idx]['scroll_pix'] + 100, PAN_LEFT);
   }
   
   SeqApp.onRightCtrl = function(e) {
      elem = e.getTarget().id;
      if (elem.indexOf('right_ctrl_') != 0) return;
      idx = elem.substr(elem.length-1, 1);
      
      SeqApp.scrollViewG(idx, m_GViews[idx]['scroll_pix'] - 100, PAN_RIGHT);
   }
   
   SeqApp.onZoomInCtrl = function(e) {
      elem = e.getTarget().id;
      if (elem.indexOf('zoomin_ctrl_') != 0) return;
      idx = elem.substr(elem.length-1, 1);
      SeqApp.zoomIn(idx);
   }
   
   SeqApp.onZoomOutCtrl = function(e) {
      elem = e.getTarget().id;
      if (elem.indexOf('zoomout_ctrl_') != 0) return;
      idx = elem.substr(elem.length-1, 1);
      SeqApp.zoomOut(idx);
   }
   
   SeqApp.onSetZoomCtrl = function(e) {
      elem = e.getTarget().id;
      if (elem.indexOf('map_bar_') != 0) return;
      
      idx = elem.substr(elem.length-1, 1);      
      cfg = m_GViews[idx]; 
      slider_range = m_SliderMax - m_SliderMin;
      elem_xy = Ext.get( cfg['the_id'] ).getXY();
      yy = e.getXY()[1] - elem_xy[1];
      

      slider_top = cfg['slider'].getTop(true);      
      slider_val = yy - m_SliderMin - slider_top;
      slider_val = Math.max(0, slider_val);
      slider_val = Math.min(m_SliderMax-m_SliderMin, slider_val);
      
      vis_range = SeqApp.toSeqG(config);
      zoom_point = vis_range[0] + vis_range[2] / 2;
      
      max_bpp = m_SeqLength / m_PanoramaWidth;
      min_bpp = 10 / m_PanoramaWidth;
      
      k = (max_bpp - min_bpp) / slider_range;
      cur_bpp = slider_val * k + min_bpp;
      
      len = m_PanoramaWidth * cur_bpp;
      from = zoom_point - len / 2;

      len = Math.round(len);
      from = Math.round( Math.max(0, from) );
      
      SeqApp.clearCache(config);
      SeqApp.loadGraphicalImage(config, from, len);
   }

   SeqApp.scrollViewG = function(idx, scroll, dir) {
      config = m_GViews[idx]; 
      config['url_from'] = null;
      config['url_to'] = null;
      screen_width = config['view'].getInnerWidth() - 2;
   
      scroll = Math.min(0, scroll);                                // 
      scroll = Math.max(scroll, screen_width-config['width_pix']); // 
      
      delta = config['scroll_pix'] - scroll;
      SeqApp.scrollReflections(config, delta);
      SeqApp.scrollMarkers(config, delta);
      SeqApp.scrollSelection(config, delta);
      
      the_div = Ext.get( config['the_id'] ); 
      the_div.setStyle('background-position', scroll+'px 0px'); 
      config['scroll_pix'] = scroll;
      SeqApp.updateTitle(config); // and save visible range
            
      if ( (scroll < screen_width - config['width_pix'] + m_PreloadMargin  &&  !config['loading'] && dir==PAN_RIGHT) ||
            (scroll > -m_PreloadMargin  &&  !config['loading'] && dir==PAN_LEFT)) {
         vis_range = SeqApp.toSeqG(config);
         bpp = config['len_seq'] / config['width_pix'];

         if (dir==PAN_RIGHT) { // moving right
            if (config['flip'] && config['from_seq']==0) return; // start of sequence reached - no loading
            if (vis_range[1] >= m_SeqLength - m_PreloadMargin * bpp) return; // end of sequence reached - no loading (to >= seq_len minus preload margin)
            
            new_from = vis_range[0];// + vis_range[2] / 2;
            new_len = vis_range[2];
            config['loading'] = true;
         } else if (dir==PAN_LEFT) { // moving left
            if (!config['flip'] && config['from_seq']==0) return; // start of sequence reached - no loading
            if (config['flip'] && vis_range[1] >= m_SeqLength - m_PreloadMargin * bpp) return; // end of sequence reached - no loading (to >= seq_len minus preload margin)
            
            new_from = vis_range[0];// - vis_range[2] / 2;
            new_len = vis_range[2];
            config['loading'] = true;
         }
         
         // caching
         if (config['prev_cgi'] && dir==PAN_LEFT) {
            config['next_cgi'] = config['from_cgi'];
            SeqApp.applyGraphicalResults(config, config['prev_cgi']);
            config['prev_cgi'] = null;
            //console.log('prev_cgi');
         } else if (config['next_cgi'] && dir==PAN_RIGHT) {
            config['prev_cgi'] = config['from_cgi'];
            SeqApp.applyGraphicalResults(config, config['next_cgi']);
            config['next_cgi'] = null;
            //console.log('next_cgi');
         } else { // time to load
            if (dir==PAN_LEFT) config['next_cgi'] = config['from_cgi'];
            if (dir==PAN_RIGHT) config['prev_cgi'] = config['from_cgi'];
            config['scroll_pix_before_load'] = config['scroll_pix']; // save scroll offset at the time of load request
            SeqApp.loadGraphicalImage(config, new_from, new_len);
         }
      }
   }
   
   SeqApp.applyGraphicalResults = function(config, from_cgi) {
      config['from_cgi'] = from_cgi;
      config['from_seq'] = from_cgi['from'];
      config['len_seq'] = from_cgi['len'];
      config['width_pix'] = from_cgi['w'];
      config['loading'] = false; // loaded
      SeqApp.updateMarkersSize(config); // Do that before setting the size of the panel
      
      the_div = Ext.get( config['the_id'] );
      the_div.setStyle('background-image', 'url(' + from_cgi['img_url'] + ')'); 
      the_div.setStyle('height', from_cgi['h']+'px' ); 

      screen_width = config['view'].getInnerWidth() - 2;
      if (config['flip']) config['off_pix'] = config['width_pix'] - config['off_pix'] - screen_width;

      if (config['scroll_pix_before_load']) {
         diff = config['scroll_pix_before_load'] - config['scroll_pix'] // scrolling that happend during request
         config['scroll_pix_before_load'] = null; // reset untill next reload
         
         config['scroll_pix'] = -config['off_pix'];
         config['scroll_pix'] = config['scroll_pix'] - diff; // add this additional scroll offset
      } else {
         config['scroll_pix'] = -config['off_pix'];
      }

      the_div.setStyle('background-position', config['scroll_pix']+'px 0px');
      SeqApp.updateTitle(config);
      SeqApp.updateMarkersPos(config);
      
      SeqApp.updateZoomIndicator(config);
      
      m_ViewsCount = m_ViewsCount - 1;
      if (m_AllMarkersRefs.length == 0  &&  m_ViewsCount == 0) { // create markers from URL (when the last view created)
         for (m = 0; m != m_Markers.length; m++) {
            m_val = m_Markers[m].replace(/k/, '000').replace(/m/, '000000')
            m_name = m_MarkersNames[m];
            marker_seq = parseInt( m_val )-1
            m_lock = false; // locked?
            if (m_val.indexOf('!') != -1) {
                m_lock = true;
                m_val.replace(/!/, '');
            }
            m_AllMarkersRefs.push( new Marker(m_GViews[0], marker_seq, m_name, m_lock) ); 
         }
      } // create markers from URL
      if (m_ViewMarkers) { // show marker info dialog?
          m_ViewMarkers = false;
          SeqApp.showMarkersDlg();
      }
      
      Reflection.reCreateReflections(); // show reflections
   }

   SeqApp.updateZoomIndicator = function(config)
   {
      slider = Ext.get('map_slider_'+config['idx']);
      slider_range = m_SliderMax - m_SliderMin; // 92

      range = SeqApp.toSeqG(config);
      vis_len = range[2];
                  
      max_bases_to_show = m_SeqLength;
      min_bases_to_show = m_PanoramaWidth / 10;
                  
      var ee = Math.log(max_bases_to_show / min_bases_to_show);
         
      slider_val = slider_range * Math.log(vis_len / min_bases_to_show) / ee;

      slider.setTop(m_SliderMin + slider_val);
   }
   

   SeqApp.openFullView = function(e) {
      e.preventDefault();
      e.stopPropagation();
      new_params = m_AllViewParams.replace(/embedded/, 'from_emb')
      window.open('/projects/sviewer/?'+new_params);
   }
   
   SeqApp.getLinkToThisPageURL = function() {
       the_link = document.location.href.split("?")[0];

       //the_link += ('?id=' + GI + '&uid=' + GI);        
       if (m_Portal) the_link += '?report=graph';
       else the_link += ('?id=' + GI);

       // origin
       if (m_Origin != 0) the_link += '&origin=' + m_Origin;

       // NAAs
       if (m_NAA) the_link += '&naa='+m_NAA;
       // SNP Filter
       if (m_SnpFilter) the_link += '&snp_filter='+m_SnpFilter;
       // Tracks
       if (m_Tracks) the_link += '&tracks='+m_Tracks;
       
       // add markers
       markers = "";
       markers_names = "";
       for (i=0; i != m_AllMarkersRefs.length; i++) {
          m = m_AllMarkersRefs[i];
          if (!m.deleted) {
             if (markers.length > 0) { markers += ','; markers_names += ',';}
             else { markers += '&m='; markers_names += '&mn='; }
             markers += (m.seq_pos + 1);
             if (m.lock) markers += '!';
             markers_names += m.marker_name;
          }
       }
       the_link += markers;
       the_link += markers_names;

       // add views
       view = '&v=';
       color = '&c=';
       the_theme = '&theme=';
       flip = '&flip=';
       select = '&select=';

       conf_content = '&content=';
       conf_color   = '&color=';
       conf_label   = '&label=';
       conf_geneModel= '&geneModel=';
       conf_decor   = '&decor=';
       conf_layout  = '&layout=';
       conf_spacing = '&spacing=';
       conf_alncolor = '&alncolor=';


       for (i = 0, c = 0; i != m_GViews.length; i++) {
          config = m_GViews[i];
          if (config && config['type'] !='panorama' && config['type'] !='alignment') {
             def_theme = config['def_theme']; // default theme
             if (c!=0) { 
                view += ','; color += ','; the_theme += ','; flip += ','; select += ',';
                conf_alncolor += ','; conf_content += ','; conf_color += ','; conf_decor += ','; 
                conf_layout += ','; conf_spacing += ','; conf_label += ','; conf_geneModel += ',';
             }
             
             if (config['url_from'] && config['url_to']) { // if we have presise positions from URL, use them. Otherwise - calculate from visible range
                 view += config['url_from'] + ':' + config['url_to'];
             } else {
                 view += ( Math.round(config['vis_from_seq'] + 1) + ':' + Math.round(config['vis_from_seq'] + config['vis_len_seq'] + 1) );
             }

             color += config['color'];
             the_theme += config['theme'];
             flip += config['flip'];
             select += (config['selected_sig'] ? config['selected_sig'] :  'null');


             content_idx = config['config_content'].length>0 ? m_ViewParams['controls']['content'].indexOf( config['config_content']) : def_theme['content'];
             color_idx   = config['config_color'].length>0   ? m_ViewParams['controls']['color'].indexOf  ( config['config_color']  ) : def_theme['color'];
             
             label_idx   = config['config_label'].length>0   ? m_ViewParams['controls']['label'].indexOf  ( config['config_label']  ) : def_theme['label'];
             geneModel_idx   = config['config_geneModel'].length>0   ? m_ViewParams['controls']['geneModel'].indexOf  ( config['config_geneModel']  ) : def_theme['geneModel'];
             
             decor_idx   = config['config_decor'].length>0   ? m_ViewParams['controls']['decor'].indexOf  ( config['config_decor']  ) : def_theme['decor'];
             layout_idx  = config['config_layout'].length>0  ? m_ViewParams['controls']['layout'].indexOf ( config['config_layout'] ) : def_theme['layout'];
             spacing_idx = config['config_spacing'].length>0 ? m_ViewParams['controls']['spacing'].indexOf( config['config_spacing']) : def_theme['spacing'];


             conf_content += (content_idx == -1 ? 0 : content_idx);
             conf_color   += (color_idx   == -1 ? 0 : color_idx);
             conf_label   += (label_idx   == -1 ? 0 : label_idx);
             conf_geneModel+= (geneModel_idx   == -1 ? 0 : geneModel_idx);
             conf_decor   += (decor_idx   == -1 ? 0 : decor_idx);
             conf_layout  += (layout_idx  == -1 ? 0 : layout_idx);
             conf_spacing += (spacing_idx == -1 ? 0 : spacing_idx);
             conf_alncolor += config['config_alncolor'];

             c++;
          }
       }

       the_link += (view + color + the_theme + flip + select + conf_content + conf_color + conf_label + conf_geneModel + conf_decor + conf_layout + conf_spacing + conf_alncolor);

       if (Ext.get('seq-dlg')) the_link += ('&seq=' + m_FromSequence + ':' + m_ToSequence);
       if (m_MarkersDlg && m_MarkersDlg.isVisible()) the_link += '&vm=true';
       if (m_SearchDlg && m_SearchDlg.isVisible()) the_link += ('&search=' + Ext.getCmp('seqapp-search-box').getValue());
       
       return the_link;
   }
   
   SeqApp.updateTitle = function(config)
   {
      if (!config) return;
      range = SeqApp.toSeqG(config);
      
      // use URL values for hi-res
       if (config['url_from'] && range[2] > 250) range[0] = config['url_from'];
       else range[0] += 1;
       
       if (config['url_to']   && range[2] > 250) range[1] = config['url_to']
       else range[1] += 1;
       
       //config['url_from'] = null;
       //config['url_to'] = null;
       r_range_from = Math.round(range[0]);
       r_range_to   = Math.round(range[1]);
      
      if (config['type']=='alignment') {
         title = 'Alignment View: ';
         title += config['vis_from_seq'] + ' - ' + config['vis_to_seq'] + ' (' + config['seq_length'] + ' bases shown' + ')';
         config['seq_length']
         config['view'].setTitle(title);
      } else if (config['type']=='graphical') {
         title = m_Embedded ? "<a href='#' id='new_view_link' style='float:right;padding-right:7px;'>Open Full View</a>" : '';
         
         main_title = m_ViewParams['id'] + ' (' + m_SeqLength + ' bases)';

         
         if (m_Origin != 0) {
            m_SeqApp.setTitle(main_title + '&nbsp;&nbsp;(Sequence origin: ' + m_Origin +')' );
            title += Math.round(range[0] - m_Origin) + '&nbsp;:&nbsp;' + Math.round(range[1]-m_Origin);
         } else {
            m_SeqApp.setTitle(main_title);
            title += r_range_from + '&nbsp;-&nbsp;' + r_range_to;
         }
         
         title += ' ('+ (r_range_to - r_range_from) +' ';
         title += (m_ViewParams['acc_type']=='protein' ? 'residues' : 'bases');
         title += ' shown, ';

         
         if (config['flip']) title += ' negative strand)'
         else title += ' positive strand)'
         
         config['view'].setTitle(title);
         vis_range = SeqApp.toSeqG(config);
         config['vis_from_seq'] = Math.round(vis_range[0]);
         config['vis_len_seq'] = Math.round(vis_range[2]);

         if (m_Embedded) Ext.get('new_view_link').on({'click' : SeqApp.openFullView });
      }
      SeqApp.updatePanorama(config);
   }
   
   SeqApp.removeFromMarkers = function(config) {
      for (i=0;i!=m_AllMarkersRefs.length;i++) m_AllMarkersRefs[i].removeView(config);
   }
   
   SeqApp.updateMarkersSize = function(config) {
      for (i=0;i!=m_AllMarkersRefs.length;i++) m_AllMarkersRefs[i].updateMarkerSize(config);
   }
   
   SeqApp.updateMarkersPos = function(config) {
      for (i=0;i!=m_AllMarkersRefs.length;i++) m_AllMarkersRefs[i].updateMarkerPos(config);
   }
   
   SeqApp.scrollMarkers = function(config, delta) {
      for (i=0;i!=m_AllMarkersRefs.length;i++) m_AllMarkersRefs[i].scrollPix(config, delta);
   }
   
   SeqApp.scrollReflections = function(config, delta) {
      for (i=0;i!=m_AllReflections.length;i++) m_AllReflections[i].scrollPix(config, delta);
      //Reflection.reCreateReflections();
   }
   
   SeqApp.scrollSelection = function(config, delta) {
      sel = config['selection'];
      if (sel) sel.movePix(delta);
   }
   
   SeqApp.updateLocatorHeights = function() {
      for (i = 0; i != m_GViews.length; i++) {
         config = m_GViews[i];
         if (!config) continue;
         locator = config['locator'];
         if (locator) locator.setHeight( m_PanoramaHeight-2 );
      }
   }
   
   SeqApp.updatePanorama = function(config) {
      // do not upadate locators if the resize is in progress
      if (did_pan_action || can_resize_left || can_resize_right) return;
   
      if (config['type']=='alignment') range = [config['vis_from_seq'], config['vis_to_seq']];
      else range = SeqApp.toSeqG(config);
      
      locator = config['locator'];
      if (locator) {
         locator.getElement().setLeft( SeqApp.toPixP(range[0]) );
         locator.getElement().setWidth( SeqApp.toPixP(range[1]-range[0])-2 ); // 2px if inner width (inclusive range)
         locator.setHeight( m_PanoramaHeight-2 );
      }
   }
   
   SeqApp.doExtensionPoint = function(event_name, config, param) {
      if (event_name in m_RegisteredExtensions) m_RegisteredExtensions[event_name](config, param);
   }
   
   String.prototype.visualLength = function()
   {
       var ruler = document.getElementById('string_ruler_unit');
       ruler.innerHTML = this;
       return ruler.offsetWidth;
   }
   
   String.prototype.trimToPix = function(length)
   {
       var tmp = this;
       var trimmed = this;
       if (tmp.visualLength() > length)
       {
           trimmed += "...";
           while (trimmed.visualLength() > length)
           {
               tmp = tmp.substring(0, tmp.length-1);
               trimmed = tmp + "...";
           }
       }
       return trimmed;
   }
   /* That's all, folks! */
});



// patching core (to add sync Ajax support). Note: Ext JS 2.2 
Ext.lib.Ajax.request = function(method, uri, cb, data, options) {
    if(options){
        var hs = options.headers;
        if(hs) for (var h in hs) if (hs.hasOwnProperty(h)) this.initHeader(h, hs[h], false);
        if(options.xmlData){
            this.initHeader('Content-Type', 'text/xml', false);
            method = 'POST';
            data = options.xmlData;
        }else if(options.jsonData){
            this.initHeader('Content-Type', 'text/javascript', false);
            method = 'POST';
            data = typeof options.jsonData == 'object' ? Ext.encode(options.jsonData) : options.jsonData;
        }
        if (options.sync) {
            return this.syncRequest(method, uri, cb, data);
        }
    }
    return this.asyncRequest(method, uri, cb, data);
};


Ext.lib.Ajax.syncRequest = function(method, uri, callback, postData)
{
    var o = this.getConnectionObject();
    if (!o) return null;
    else {
        o.conn.open(method, uri, false);

        if (this.useDefaultXhrHeader) {
            if (!this.defaultHeaders['X-Requested-With']) {
                this.initHeader('X-Requested-With', this.defaultXhrHeader, true);
            }
        }

        if (postData && this.useDefaultHeader) this.initHeader('Content-Type', this.defaultPostHeader);
        if (this.hasDefaultHeaders || this.hasHeaders) this.setHeader(o);

        o.conn.send(postData || null);
        this.handleTransactionResponse(o, callback);
        return o;
    }
};


// Sets a value of combo box that has separate display value and item value but uses remote data source
Ext.ux.SetComboValue = function ( combo, value ) {

    var params = {}
    params[combo.valueField] = value;
    combo.setValue(value);
    combo.store.load({params:params});
    
    var setInitValue = function(store, records, options) {
        combo.store.un('load', setInitValue);
        combo.setValue(value);
    }

    combo.store.on('load', setInitValue);
}
