
// viewmode change: swap show/hide

// credit: denis howlett 
// http://www.isocra.com/

// config
var imagePath = '/App_Themes/DiWA.LayerManager/images/';
//Colors=======================
var cDefault = '#000000';
var cDefaultActive = '#000000';
var cHidden = '#c0c0c0';
var cHiddenActive = '#c0c0c0';
//Maximum of opend layers on one client
var maxLayerCount = 10;

/** Keep hold of the current table being dragged */
var currenttable = null;

/** 
*   Capture the onmousemove so that we can see if a row from the current
*   table if any is being dragged.
*   @param ev the event (for Firefox and Safari, otherwise we use window.event for IE)
*/
document.onmousemove = function(ev) {
  if (currenttable && currenttable.dragObject) {
    ev = ev || window.event;
    var mousePos = currenttable.mouseCoords(ev);
    var y = mousePos.y - currenttable.mouseOffset.y;
    if (y != currenttable.oldY) {
      // work out if we're going up or down...
      var movingDown = y > currenttable.oldY;
      // update the old value
      currenttable.oldY = y;
      // update the style to show we're dragging
     
      //currenttable.dragObject.className = 'drag';
      currenttable.activateLayer(currenttable.dragObject);
      // If we're over a row then move the dragged row to there so that the user sees the
      // effect dynamically
      var currentRow = currenttable.findDropTargetRow(y);
      if (currentRow) {
        if (movingDown && currenttable.dragObject != currentRow) {
          currenttable.dragObject.parentNode.insertBefore(currenttable.dragObject, currentRow.nextSibling);
        } else if (! movingDown && currenttable.dragObject != currentRow) {
          currenttable.dragObject.parentNode.insertBefore(currenttable.dragObject, currentRow);
        }
      }
    }
    return false;
  }
}

// Similarly for the mouseup
document.onmouseup = function(ev) {
  if (currenttable && currenttable.dragObject) {
    var droppedRow = currenttable.dragObject;
    // If we have a dragObject, then we need to release it,
    // The row will already have been moved to the right place so we just reset stuff
    
    currenttable.dragObject = null;
    
    var tBody = currenttable.table.tBodies[0];
    var layers = tBody.rows;
    
    for (var i = 1; i < layers.length; i++) {
      if (layers[i].layerIndex != (layers.length - i - 1)) {
        if (droppedRow.layerIndex > layers[i].layerIndex) {
          // found layer moved up while dragged layer was moved down; go on searching
        } else {
          currenttable.reIndexLayers();
          currenttable.onLayerIndexChanged(droppedRow);
          break;
        }
      } else {
        if (droppedRow.layerIndex == layers[i].layerIndex) {
          // just clicked
          currenttable.activateLayer(droppedRow);
        }
      }
    }
    currenttable = null; // let go of the table too
  }
}

/** get the source element from an event in a way that works for IE and Firefox and Safari
* @param evt the source event for Firefox (but not IE--IE uses window.event) */
function getEventSource(evt) {
  if (window.event) {
    evt = window.event; // For IE
    return evt.srcElement;
  } else {
    return evt.target; // For Firefox
  }
}

/**
* Encapsulate table Drag and Drop in a class. We'll have this as a Singleton
* so we don't get scoping problems.
*/
function LayerManager() {
  this.objectId = null; // self-awareness *g
  this.table = null;
  this.confirmRemoveLayer = false;
  this.maxLayerId = 0;
  this.viewMode = 0; // values: 0 = single, 1 = split (left), 2 = split (right)
  this.swapTarget = null;
  this.ecwView = null;
  
  // master gis properties
  this.projection = null;
  this.datum = null;
  this.cellUnits = null;
  this.cellSize = null;
  
  this.dragObject = null;
  this.mouseOffset = null;
  
  /** Remember the old value of Y so that we don't do too much processing */
  this.oldY = 0;

  /** Initialise the drag and drop by capturing mouse move events */
  this.init = function(objectId, table, confirmRemoveLayer, viewMode, swapTarget, ecwView) {
      this.objectId = objectId;
      this.table = table;
      this.confirmRemoveLayer = confirmRemoveLayer;
      this.viewMode = viewMode;
      this.swapTarget = swapTarget;
      this.ecwView = ecwView;
      var tBody = this.table.tBodies[0];
      var newHeader = document.createElement('tr');
      newHeader.noDrop = true;
      newHeader.noDrag = true;

      // caption
      var newTD = document.createElement('td');
      newTD.innerHTML = 'Karten';
      newTD.className = 'header default';
      newTD.style.width = '100%'
      newHeader.appendChild(newTD);

      // legend icon column
      newTD = document.createElement('td');
      newTD.innerHTML = '&nbsp;';
      newTD.className = 'header default';
      newHeader.appendChild(newTD);

      // tag icon column
      newTD = document.createElement('td');
      newTD.innerHTML = '<a href="javascript:void(0);" style="vertical-align:middle;white-space:nowrap; text-transform:capitalize;" onclick="' + this.objectId + '.onLayerAdding();"><img src="' + imagePath + 'add.png" title="Karte hinzuf' + unescape('%FC') + 'gen..."/>Auswahl</a>';
      newTD.className = 'header default';
      newHeader.appendChild(newTD);

      // swap icon
      newTD = document.createElement('td');
      newTD.innerHTML = '&nbsp;';
      newTD.className = 'header default';
      if (this.viewMode == 0) {
          newTD.style.display = 'none';
      }
      newHeader.appendChild(newTD);

      // add/delete icon
      newTD = document.createElement('td');
      newTD.innerHTML = '<a href="javascript:void(0);" onclick="' + this.objectId + '.clear(' + this.confirmRemoveLayer + ');"><img src="' + imagePath + 'delete.png" title="Alle Karten entfernen"/></a>';
      newTD.className = 'header default';
      newHeader.appendChild(newTD);

      tBody.appendChild(newHeader);
  }

  // layers collection
  this.layers = function() {
    // use 1-based to ignore caption row
    var tBody = this.table.tBodies[0];
    return(tBody.rows);
  }
  
  // events, methods, properties
  this.onLayerAdding = function() {
  
  }
  
  this.onLayerAdded = function(layer) {

  }
  
  this.onLayerActivated = function(layer) {
    
  }
  
  this.onLayerVisibilityChanged = function(layer) {
  
  }
  
  this.onLayerIndexChanged = function(layer) {

  }
  
  this.onLayerRemoved = function(layerID) {

  }
  
  this.showLayerDetails = function(layerID) {
  
  }
  
  this.showLayerLegend = function(layerID) {
  
  }
  
  this.setViewMode = function(viewMode) {
    if (this.viewMode != viewMode) {
      this.viewMode = viewMode;
      
      var tBody = this.table.tBodies[0];
      var layers = tBody.rows;
      
      for (i = 0; i < layers.length; i++) {
        var tds = layers[i].getElementsByTagName("td");
        if (this.viewMode == 0) {
          tds[2].style.display = 'none'; 
        } else {
          tds[2].style.display = ''; 
        }
      }
      
    }
  }

  this.clear = function(confirmRemoveLayer) {
    if (confirmRemoveLayer) {
      if (!confirm('Wollen Sie wirklich alle Karten entfernen?')) {
        return (0);
      }
    }

    if (this.ecwView.GetLayerIndex('_crosshair') > -1) {
      this.ecwView.DeleteLayer('_crosshair');
    }
    
    var tBody = this.table.tBodies[0];
    var layers = tBody.rows;

    for (i = 1; i < layers.length; i++) {
      this.removeLayer(layers[i].id, false, true);
    }
    
  }

  this.addLayer = function(mapID, layerAtlas, layerContent, layerTitle, layerLegend, layerTagLemma, layerSatz, url0, url1, linkID,kartennummer) {
    var tBody = this.table.tBodies[0];
    var layers = tBody.rows;
    if (layers.length - 1 >= maxLayerCount) {
      alert('Die Karte kann nicht hinzugef' + unescape('%FC') + 'gt werden: Maximale Kartenanzahl (' + maxLayerCount + ') erreicht.');
      return (0);
    }

    var newLayer = document.createElement('tr');

    this.maxLayerId++;
    newLayer.id = this.maxLayerId;  // unique id
    newLayer.layerIndex = -1;       // layerindex as seen by user
    newLayer.active = false;
    newLayer.visible = true;
    newLayer.opacity = 1.0;
    newLayer.brightness = 1.0;
    newLayer.contrast = 1.0;

    newLayer.layerCaption = '';
    newLayer.layerAtlas = layerAtlas;
    newLayer.layerContent = layerContent;
    newLayer.layerTitle = layerTitle;
    newLayer.layerLegend = layerLegend;
    newLayer.layerTagLemma = layerTagLemma;
    newLayer.layerSatz = layerSatz;
    newLayer.title = '';    // tooltip
    newLayer.mapID = mapID;
    newLayer.linkID = linkID;
    newLayer.kartennummer = kartennummer;
//url0 = RasterURL

    newLayer.url0 = null;
    newLayer.id0 = 0;
    if (url0 != null && url0 != '') {
      newLayer.url0 = url0;
      newLayer.id0 = newLayer.id + '-0';  // unique sub-id used as layername in ecwview
      this.ecwView.AddLayer('ECW', ECWP_BASEURL + String(newLayer.url0), newLayer.id0, '');
      this.ecwView.SetLayerGDTDataDownloading(newLayer.id0, true);

      if (this.projection == null) {
        this.projection = this.ecwView.GetLayerProjection(newLayer.id0);
        this.datum = this.ecwView.GetLayerDatum(newLayer.id0);
        this.cellUnits = this.ecwView.GetLayerCellUnits(newLayer.id0);
        // = CU_INVALID, CU_METERS, CU_DEGREES, CU_FEET
        this.cellSize = this.ecwView.GetLayerCellSizeY(newLayer.id0);
      }

    }
    //url1 = OverlayURL

    newLayer.url1 = null;
    newLayer.id1 = 0;

    if (url1 != null) {
      newLayer.url1 = url1;
      newLayer.id1 = newLayer.id + '-1';  // unique sub-id used as layername in ecwview

      this.ecwView.AddLayer('GISOVERLAY', '', newLayer.id1, this.getLayerOverlayParams(newLayer));
      this.ecwView.SetLayerTransparency(newLayer.id1, TRANSPARENTBACKGROUND_COLOR, 0.0);    // Background
    }

    // caption
    newTD = document.createElement('td');
    newTD.className = 'caption';

    if (newLayer.layerContent != '') {
      newTD.innerHTML = newLayer.layerContent;
      if ((newLayer.layerTitle != '') && (newLayer.layerTitle != newLayer.layerContent)) {
        newLayer.title = newLayer.layerTitle; // tooltip
      }
    } else {
      newTD.innerHTML = newLayer.layerTitle;
    }

    newLayer.layerCaption = removeHTMLTags(newTD.innerHTML) + ' (' + newLayer.id + ')';

    if (layerAtlas != '') {
      newTD.innerHTML = newLayer.layerAtlas + ': ' + newTD.innerHTML;
    }

    newTD.innerHTML += ' (' + newLayer.id + ')';

    newLayer.appendChild(newTD);

    // legend icon
    newTD = document.createElement('td');
    if (newLayer.layerLegend != '') {
      newTD.innerHTML = '<a href="javascript:void(0);" onclick="' + this.objectId + '.showLayerLegend(' + newLayer.id + ');"><img src="' + imagePath + 'legend.png" title="Legende"/></a>';
    } else {
      newTD.innerHTML = '&nbsp;'
    }
    newLayer.appendChild(newTD);

    // tag icon
    newTD = document.createElement('td');
    newTD.style.textAlign = 'right'
    newTD.innerHTML = '<a href="javascript:void(0);" onclick="' + this.objectId + '.showLayerDetails(' + newLayer.id + ');"><img src="' + imagePath + 'tag.png" title="Details"/></a>';
    newLayer.appendChild(newTD);

    // swap icon
    newTD = document.createElement('td');
    switch (this.viewMode) {
      case 0: case 1:
        newTD.innerHTML = '<a href="javascript:void(0);" onclick="' + this.objectId + '.swapLayer(' + newLayer.id + ');"><img src="' + imagePath + 'indent.png" title="Karte nach rechts verschieben"/></a>';
        break;
      case 2:
        newTD.innerHTML = '<a href="javascript:void(0);" onclick="' + this.objectId + '.swapLayer(' + newLayer.id + ');"><img src="' + imagePath + 'outdent.png" title="Karte nach links verschieben"/></a>';
        break;
    }
    if (this.viewMode == 0) {
      newTD.style.display = 'none';
    }
    newLayer.appendChild(newTD);

    // delete icon
    newTD = document.createElement('td');
    newTD.className = 'removeicon';
    newTD.innerHTML = '<a href="javascript:void(0);" onclick="' + this.objectId + '.removeLayer(' + newLayer.id + ', ' + this.confirmRemoveLayer + ', false);"><img src="' + imagePath + 'deleteItem.png" title="Karte entfernen"/></a>';
    newLayer.appendChild(newTD);

    if (tBody.rows.length == 1) {
      tBody.appendChild(newLayer);
      this.activateLayer(newLayer);
    } else {
      var topLayer = tBody.rows[1];
      tBody.insertBefore(newLayer, topLayer);
    }

    this.reIndexLayers();

    this.makeDraggable(newLayer);
    this.activateLayer(newLayer);

    this.onLayerAdded(newLayer);
  }

    /**
    *   Removes a layer from the view.
    *   TODO this should be a controller class, so the confiramtion should be removed and placed in the GUI. 
    *   TODO the clearing-flag can be removed too if the clearing method removes the activation-listners herself or the last layer is set as active so the activate method is called only once.
    *   @param id The id of the layer which has to be removed.
    *   @param confirmRemoveLayer if true and not clearing then a confirmation dialog is shown
    *   @param clearing if true there is no ask for confirmation and no layer is marked as activated
    */
  this.removeLayer = function(id, confirmRemoveLayer, clearing) {
    var tBody = this.table.tBodies[0];
    var layers = tBody.rows;
    var activeLayerRemoved = false;
    for (i = 1; i < layers.length; i++) {
      if (layers[i].id == id) {
        var doRemove = true;     
        if (! clearing) {
          if (confirmRemoveLayer) {
            if (! confirm('Wollen Sie die Karte "' + layers[i].layerCaption + '" wirklich entfernen?')) {
              doRemove = false;
            }
          }
        }
        if (doRemove) {
          if (layers[i].active) {
            activeLayerRemoved = true;
          }
          
          if (layers[i].url0 != null) {//
            this.ecwView.DeleteLayer(layers[i].id0);
          }
          if (layers[i].url1 != null) {//Remove GISOVERLAY-Layer
            this.ecwView.DeleteLayer(layers[i].id1);
          }
          
          tBody.removeChild(layers[i]);
          this.onLayerRemoved(id);
          this.reIndexLayers();
          
          if ((! clearing) && (activeLayerRemoved)) {
            if (layers.length > 1) {
              this.activateLayer(layers[1]);
            }
          }
        }
        break;
      }
    }
  }
  
  this.swapLayer = function(id) {
    var tBody = this.table.tBodies[0];
    var layers = tBody.rows;
    for (i = 1; i < layers.length; i++) {
      if (layers[i].id == id) {
        this.swapTarget.addLayer(layers[i].id, layers[i].layerAtlas, layers[i].layerContent, layers[i].layerTitle, layers[i].layerLegend, layers[i].url0, layers[i].url1, layers[i].linkID);
        this.removeLayer(id, false, false);
        break;
      }
    }
  }

  this.activateLayer = function(layer) {
    if (layer.active) return;
    // if (! layer.visible) return;

    var tBody = this.table.tBodies[0];
    var layers = tBody.rows;
    for (i = 1; i < layers.length; i++) {
      layers[i].active = false;
      layers[i].className = '';
    }
    layer.active = true;
    layer.className = 'drag';
    this.onLayerActivated(layer);
  }
  
  this.reIndexLayers = function() {
    var tBody = this.table.tBodies[0];
    var layers = tBody.rows;
    var zindex = 0;
    
    for (i = layers.length - 1; i >= 1; i--) {
      // ecw plugin requires bottom layer = 0, top layer = GetNumberLayers() - 1
      layers[i].layerIndex = layers.length - i - 1;
      
      if (layers[i].url0 != null) {
        oldzindex = this.ecwView.GetLayerIndex(layers[i].id0);
        if (oldzindex != zindex) {
          this.ecwView.MoveLayer(oldzindex, zindex);
        }
        zindex++;
      }
      
      if (layers[i].url1 != null) {
        oldzindex = this.ecwView.GetLayerIndex(layers[i].id1);
        if (oldzindex != zindex) {
          this.ecwView.MoveLayer(oldzindex, zindex);
        }
        zindex++;
      }

      if (this.ecwView.GetLayerIndex('_crosshair') != -1) {
        // crosshair always on top
        this.ecwView.MoveLayer(this.ecwView.GetLayerIndex('_crosshair'), this.ecwView.GetNumberLayers() - 1);
      }
    }
  }
  
  this.getActiveLayer = function() {
    var tBody = this.table.tBodies[0];
    var layers = tBody.rows;
    
    if (layers.length <= 1) {
      return(null);
    } else {
      for (i = 1; i < layers.length; i++) {
        if (layers[i].active == true) {
          return(layers[i]);
          break;
        }
      }
    }
  }
  
  this.getLayerByID = function(layerID) {
    var tBody = this.table.tBodies[0];
    var layers = tBody.rows;
    
    if (layers.length <= 1) {
      return(null);
    } else {
      for (i = 1; i < layers.length; i++) {
        if (layers[i].id == layerID) {
          return(layers[i]);
          break;
        }
      }
    }
  }
  
  // *** layer properties
  this.setLayerVisiblity = function(layer, isVisible) {
    if (layer.visible == isVisible) return;
    
    layer.visible = isVisible;
    if (layer.visible) {
      if (layer.active) {
        layer.style.color = cDefaultActive;
      } else {
        layer.style.color = cDefault;
      }
    } else {
      if (layer.active) {
        layer.style.color = cHiddenActive;
      } else {
        layer.style.color = cHidden;
      }
    }
    
    if (layer.url0 != null) {
      this.ecwView.SetLayerVisible(layer.id0, layer.visible);
    }
    if (layer.url1 != null) {
      this.ecwView.SetLayerVisible(layer.id1, layer.visible);
    }
    
    this.onLayerVisibilityChanged(layer);
  }
  
  this.setLayerOpacity = function(layer, value) {
    layer.opacity = value;
    if (layer.url0 != null) {
      this.ecwView.SetLayerTransparency(layer.id0, '#', value);
    }
    if (layer.url1 != null) {
      this.ecwView.SetLayerTransparency(layer.id1, '#', value);
    }
    
  }
  
  this.setLayerBrightness = function(layer, value) {
    layer.brightness = value;
    this.ecwView.SetLayerParameter(layer.id0, 'brightness=' + value);
  }
  
  this.setLayerContrast = function(layer, value) {
    layer.contrast = value;
    this.ecwView.SetLayerParameter(layer.id0, 'contrast=' + value);
  }

  this.zoomToLayer = function(layer) {
    if (layer.url0) {
		this.ecwView.SetExtents(this.ecwView.GetLayerImageTopLeftWorldCoordinateX(layer.id0),
        this.ecwView.GetLayerImageTopLeftWorldCoordinateY(layer.id0),
        this.ecwView.GetLayerImageBottomRightWorldCoordinateX(layer.id0),
        this.ecwView.GetLayerImageBottomRightWorldCoordinateY(layer.id0));
    }
  }
  
  /*
  * Returns the overlay parameters.
  * As a string with the following attributes:
  * mode; body; action; worldTLX; worldTLY; worldBRX; worldBRY; url
  */
  this.getLayerOverlayParams = function(layer, w, h, worldTLX, worldTLY, worldBRX, worldBRY) {
    if (w == null || h == null || worldTLX == null || worldTLY == null || worldBRX == null ||worldBRY == null) {
      var w = this.ecwView.GetViewWidth();
      var h = this.ecwView.GetViewHeight();
      
      var worldTLX = this.ecwView.GetTopLeftWorldCoordinateX();
      var worldTLY = this.ecwView.GetTopLeftWorldCoordinateY();
      var worldBRX = this.ecwView.GetBottomRightWorldCoordinateX();
      var worldBRY = this.ecwView.GetBottomRightWorldCoordinateY();
    }
    
    if (layer.url1.indexOf('mapserv.exe') > -1) {
      // umn mapserver
			// in conic (lcc) projection, vector overlay and raster image do not align with
			// xmin ymin xmax ymax = Lon(TLX, BRY) Lat(TLX, BRY) Lon(BRX, TLY) Lat(BRX, BRY),
			// so we must use the 'inner' coordinate bounds:
			
			// Use largest left long for x0, found in ul or ll corner:
			if (this.ecwView.GetCoordLongitude(worldTLX, worldTLY) > this.ecwView.GetCoordLongitude(worldTLX, worldBRY)) {
				x0 = this.ecwView.GetCoordLongitude(worldTLX, worldTLY);
			} else {
				x0 = this.ecwView.GetCoordLongitude(worldTLX, worldBRY);
			}
			
			// Use smallest right long for x1, found in lr or ur corner:
			if (this.ecwView.GetCoordLongitude(worldBRX, worldBRY) < this.ecwView.GetCoordLongitude(worldBRX, worldTLY)) {
				x1 = this.ecwView.GetCoordLongitude(worldBRX, worldBRY);
			} else {
				x1 = this.ecwView.GetCoordLongitude(worldBRX, worldTLY);
			}
			
			// Use largest lower lat for y0, found by partitioning x axis:
			a = worldTLX;
			b = (worldTLX + worldBRX) / 2;
			c = worldBRX;
			tx0 = (a + b) / 2;
			tx1 = (b + c) / 2;
			
			while (Math.abs(this.ecwView.GetCoordLatitude(tx1, worldBRY) - this.ecwView.GetCoordLatitude(tx0, worldBRY)) > 0.000001) {
				if (this.ecwView.GetCoordLatitude(tx0, worldBRY) > this.ecwView.GetCoordLatitude(tx1, worldBRY)) {
					a = a;
					c = b;
					b = tx0;
				} else {
					a = b;
					c = c;
					b = tx1;
				}
				tx0 = (a + b) / 2;
				tx1 = (b + c) / 2;
			}

			y0 = this.ecwView.GetCoordLatitude(tx0, worldBRY);
			
			// Use smallest upper lat for y1, found in ul or ur corner:
			if (this.ecwView.GetCoordLatitude(worldTLX, worldTLY) < this.ecwView.GetCoordLatitude(worldBRX, worldTLY)) {
				y1 = this.ecwView.GetCoordLatitude(worldTLX, worldTLY);
			} else {
				y1 = this.ecwView.GetCoordLatitude(worldBRX, worldTLY);
			}
			
			url = layer.url1
			  + '&mode=map'
			  + '&mapext=' + x0 + '%20' + y0 + '%20' + x1 + '%20' + y1
			  + '&imgsize=' + w + '%20' + h; 
					
    } else {
      url = layer.url1
        + '?w=' + w
        + '&h=' + h
        + '&worldtlx=' + worldTLX 
        + '&worldtly=' + worldTLY 
        + '&worldbrx=' + worldBRX 
        + '&worldbry=' + worldBRY
        + '&mapid=' + layer.linkID;
    }
      
    sParams = 'mode=WORLD;body=;action=GET' 
      + ';worldTLX=' + worldTLX 
      + ';worldTLY=' + worldTLY 
      + ';worldBRX=' + worldBRX
      + ';worldBRY=' + worldBRY
      + ';url=' + url;
    
    return(sParams);
  }
  
  this.updateVectorOverlays = function(w, h, worldTLX, worldTLY, worldBRX, worldBRY) {
    var tBody = this.table.tBodies[0];
    var layers = tBody.rows;
    
    if (layers.length <= 1) {
      return(null);
    } else {
      for (var i = 1; i < layers.length; i++) {
        if (layers[i].url1 != null) {
          this.ecwView.SetLayerParameter(layers[i].id1, this.getLayerOverlayParams(layers[i], w, h, worldTLX, worldTLY, worldBRX, worldBRY));
        }
      }
    }
  }
  
  

  
// ***** table dnd utilities *****  
  this.getPosition = function(e) {
    var left = 0;
    var top  = 0;
    /** Safari fix -- thanks to Luis Chato for this! */
    if (e.offsetHeight == 0) {
      /** Safari 2 doesn't correctly grab the offsetTop of a table row
      this is detailed here:
      http://jacob.peargrove.com/blog/2006/technical/table-row-offsettop-bug-in-safari/
      the solution is likewise noted there, grab the offset of a table cell in the row - the firstChild.
      note that firefox will return a text node as a first child, so designing a more thorough
      solution may need to take that into account, for now this seems to work in firefox, safari, ie */
      e = e.firstChild; // table cell
    }

    while (e.offsetParent) {
      left += e.offsetLeft;
      top  += e.offsetTop;
      e     = e.offsetParent;
    }

    left += e.offsetLeft;
    top  += e.offsetTop;

    return {x:left, y:top};
  }

  this.mouseCoords = function(ev) {
    if (ev.pageX || ev.pageY) {
      return {
        x:ev.pageX, 
        y:ev.pageY
      };
    }
    return {
      x:ev.clientX + document.body.scrollLeft - document.body.clientLeft,
      y:ev.clientY + document.body.scrollTop  - document.body.clientTop
    };
  }

  this.getMouseOffset = function(target, ev) {
    ev = ev || window.event;

    var docPos    = this.getPosition(target);
    var mousePos  = this.mouseCoords(ev);
    return {
      x:mousePos.x - docPos.x, 
      y:mousePos.y - docPos.y
    };
  }

  this.makeDraggable = function(item) {
    if (!item) return;
    var self = this; // keep context of LayerManager inside function
    item.onmousedown = function(ev) { 
      var target = getEventSource(ev);
      if (target.tagName == 'A' || target.tagName == 'IMG' || target.tagName == 'INPUT' || target.tagName == 'SELECT') return true;
      currenttable = self;
      self.dragObject  = this;
      self.mouseOffset = self.getMouseOffset(this, ev);
      item.style.cursor = "move";
      return false;
    };   
    item.onmouseup = function(ev) {
      item.style.cursor = "default";
      return false;
    };
  }

  this.findDropTargetRow = function(y) {
    var rows = this.table.tBodies[0].rows;
    for (var i = 1; i < rows.length; i++) {
      var row = rows[i];
      var nodrop = row.getAttribute("noDrop");
      if (nodrop == null || nodrop == "undefined") {  
        var rowY    = this.getPosition(row).y;
        var rowHeight = parseInt(row.offsetHeight)/2;
        if (row.offsetHeight == 0) {
          rowY = this.getPosition(row.firstChild).y;
          rowHeight = parseInt(row.firstChild.offsetHeight)/2;
        }
    
        if ((y > rowY - rowHeight) && (y < (rowY + rowHeight))) {
          return row;
        }
      }
    }
    return null;
  }
}


