// www.haokanbu.com
//
// Hack from PhotoNotes
// http://www.dustyd.net/projects/PhotoNotes/
//
// * Fix bug for note moving in multiple photo
// * Fix bug bounds check
// * Add readonly attribute to note, allow note container overwrite it
// * Add button text settings
// * Add author attribute to note

// Retrieve id from string which format like 'photo1234'
function get_id(prefix, str){
	var start = str.indexOf(prefix) + prefix.length;
	return str.substring(start);
}

$.fn.photonotes = function(photo_notes, options) {
	/*
	 * Sample html:
	 * ...
	 * <img id="photo63933" class="story_photo" src="..."/>
	 * <a id="add_note_button63933" href="#">Add Note</a>
	 * ...
	 * <img id="photo63934" class="story_photo" src="..."/>
	 * <a id="add_note_button63934" href="#">Add Note</a>
	 * ...
	 *
	 * Sample phto_notes:
	 * var photo_notes = {
	 * "63933": {"notes": [{"text": "7th brohter", "readonly": false, "id": "27168", "rect": {"width": 52, "top": 237, "height": 52, "left": 184}, "author": "\u4f7f\u7528inputking\u7684\u8682\u8681"}, {"text": "Hulu brothers", "readonly": false, "id": "27176", "rect": {"width": 251, "top": 8, "height": 90, "left": 6}, "author": "\u4f7f\u7528inputking\u7684\u8682\u8681"}], "vratio": 1, "hratio": 1}, "readonly": false,
	 * "63934": {"notes": [{"text": "nose", "readonly": false, "id": "27170", "rect": {"width": 63, "top": 81, "height": 68, "left": 312}, "author": "\u4f7f\u7528inputking\u7684\u8682\u8681"}], "vratio": 1, "hratio": 1}}
	 */
	var settings = {		
		prefix:'photo',
		add_prefix:'add_note_button',
		save_text: 'SAVE',
		cancel_text: 'CANCEL',
		delete_text: 'DELETE',
		new_note_text: 'Add new note here....',
		new_note_author: 'Anonymous',
		add_note_text: null,
		onsave: function (photo_id, id, left, right, width, height, text) { return 1; },
		ondelete: function (id) { return true; },
		save_error_msg: 'SAVE ERROR!',
		delete_error_msg: 'DELETE ERROR!'
	}
	if(options) $.extend(settings, options);
	g_fn_save_error_msg = settings.save_error_msg;
	g_fn_delete_error_msg = settings.delete_error_msg;

	var prefix = settings.prefix;

	this.each(function(){
			var photo_id = get_id(prefix, this.id);
			var me = $(this);

			// Create photo container
			var photo_container_id = 'PhotoContainer' + photo_id;
			var photo_container = $('<span class="fn-container" id="' + photo_container_id + '"></span>');
			me.wrap(photo_container);
			// Set container size to image size
			var dom_photo_container = document.getElementById(photo_container_id);
			// The fllowing code depends on image loading speed, width and height will lost when it's slow
			//dom_photo_container.style.width= this.offsetWidth + 'px';
			//dom_photo_container.style.height= this.offsetHeight + 'px';
			// So we just get the photo width and height from photo_notes
			if (photo_notes && photo_notes[photo_id]!=undefined && photo_notes[photo_id]['width']!=undefined)
			{
				dom_photo_container.style.width= photo_notes[photo_id]['width'] + 'px';
			}
			if (photo_notes && photo_notes[photo_id]!=undefined && photo_notes[photo_id]['height']!=undefined)
			{
				dom_photo_container.style.height= photo_notes[photo_id]['height'] + 'px';
			}

			/* create the Photo Note Container */
			var hratio = photo_notes && photo_notes[photo_id]!=undefined && photo_notes[photo_id]['hratio']!=undefined?photo_notes[photo_id]['hratio']:1;
			var vratio = photo_notes && photo_notes[photo_id]!=undefined && photo_notes[photo_id]['vratio']!=undefined?photo_notes[photo_id]['vratio']:1;
			var notes = new PhotoNoteContainer(document.getElementById(photo_container_id), {'save_text':settings.save_text, 'cancel_text':settings.cancel_text,'delete_text':settings.delete_text, 'hratio':hratio, 'vratio':vratio});

			// Create add note button
			var notes_readonly = photo_notes && photo_notes['readonly']!=undefined?photo_notes['readonly']:true;
			if (settings.add_note_text && !notes_readonly)
			{
				var add_note_id = settings.add_prefix + photo_id;
				$('#' + add_note_id).click(function(){
					var newNote = new PhotoNote({'id':-1, 'text':settings.new_note_text, 'readonly':false, 'author':settings.new_note_author});
					newNote.onsave = settings.onsave;
					newNote.ondelete = settings.ondelete;
					notes.AddNote(newNote);
					newNote.Select();
					return false;
				});
			}

			// Create notes for photos
			notes.setReadonly(notes_readonly);

			function new_note(note_array)
			{
				var note_array = note_array;
				var note = new PhotoNote(note_array);
				note.onsave = settings.onsave;
				note.ondelete = settings.ondelete;
				return note
			}

			if (photo_notes && photo_notes[photo_id]!=undefined && photo_notes[photo_id]['notes']!=undefined)
			{
				var note_arraries = photo_notes[photo_id]['notes'];
				for (var i=0;i<note_arraries.length;i++)
				{
					notes.AddNote(new_note(note_arraries[i]));
				}
			}
	});

	return this;
}


//------------------------------------------------------------------------------
/*
   PhotoNotes v2.0

   Copyright (c) 2007 Guillaume Texier - gig6@free.fr
   (Based on original PhotoNotes Copyright (c) 2006 Dusty Davidson - http://www.dustyd.net)

   - toXML() added
   - getTransfertObject() added
   - transfertObject now used
   - PhotoNoteContainer read-only mode added
   - PhotoNoteContainer maximum number of notes added
   - The note text is now only selected when edit mode start
   - The note text now keeps new line
   - The note text is now HTML proof
   - The note clickable zone is now the external area (user could clic on area border and get only dragresize funcions without note edition)
   - DragResize code is now in its own file (dragresize.js)
   - DragResize v1.0 now used (fix Internet explorer stack overflow bug when number of notes exeeds 15)

   Permission is hereby granted, free of charge, to any person obtaining
   a copy of this software and associated documentation files (the "Software"),
   to deal in the Software without restriction, including without limitation
   the rights to use, copy, modify, merge, publish, distribute, sublicense,
   and/or sell copies of the Software, and to permit persons to whom the Software
   is furnished to do so, subject to the following conditions:

   The above copyright notice and this permission notice shall be included in
   all copies or substantial portions of the Software.

   THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
   IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
   FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
   AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
   WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
   IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
   */


/*********************************************************/
/*** Photo Notes Container *******************************/
/*********************************************************/
function PhotoNoteContainer(element, config)
{
	var props = {
		element: element,
		dragresize: null,
		notes: new Array(),
		editing: false,
		maxNotes: 999,
		readonly: true,
		save_text: 'SAVE',
		cancel_text: 'CANCEL',
		delete_text: 'DELETE',
		hratio: 1,
		vratio: 1
	};

	for (var p in props)
	{
		this[p] = (!config || typeof config[p] == 'undefined') ? props[p] : config[p];
	}

};

PhotoNoteContainer.prototype.DeleteNote = function(note)
{

	note.UnSelect();

	/* remove from the DOM */
	this.element.removeChild(note.gui.ElementRect);
	this.element.removeChild(note.gui.ElementNote);

	/* remove from the array... */
	this.notes.remove(note);
}

PhotoNoteContainer.prototype.AddNote = function(note)
{
	if(!this.editing && this.notes.length<this.maxNotes)
	{
		/* add the note to the array of notes, and set its container */
		this.notes[this.notes.length] = note;
		note.container = this;
		note.SetRatio(this.hratio, this.vratio);
		note.CreateElements();

		/* add the note to the DOM */
		this.element.appendChild(note.gui.ElementRect);
		this.element.appendChild(note.gui.ElementNote);

		/* Overwrite note readonly attribute */
		if (this.readonly)
		{
			note.readonly = this.readonly;
		}
		return true
	}
	return false

};

/* hide all of the "text" parts of the notes. Primarily called when hovering a note, at which
   point we want to hide all of the other note texts */
PhotoNoteContainer.prototype.HideAllNoteTexts = function()
{
	for (var i = 0;i<this.notes.length;i++)
		this.notes[i].HideNoteText();
};


PhotoNoteContainer.prototype.DisableAllNotes = function()
{
	for (var i = 0;i<this.notes.length;i++)
		this.notes[i].DisableNote();
};

PhotoNoteContainer.prototype.HideAllNotes = function()
{
	for (var i = 0;i<this.notes.length;i++)
		this.notes[i].HideNote();
};

PhotoNoteContainer.prototype.ShowAllNotes = function()
{
	for (var i = 0;i<this.notes.length;i++)
		this.notes[i].ShowNote();
};

PhotoNoteContainer.prototype.EnableAllNotes = function()
{
	for (var i = 0;i<this.notes.length;i++)
		this.notes[i].EnableNote();
};

PhotoNoteContainer.prototype.setReadonly = function(readonly)
{
	this.readonly	= readonly
};

PhotoNoteContainer.prototype.toggleReadonly = function()
{
	this.readonly	= !this.readonly
};

PhotoNoteContainer.prototype.isReadonly = function()
{
	return this.readonly
};


/*********************************************************/
/*** Photo Note ******************************************/
/*********************************************************/
function PhotoNote(transfertObject)
{
	// Declaration
	this.text		= '';
	this.id			= -1;
	this.rect		= {'left':10,'top':10,'width':50,'height':50};
	this.selected		= false;
	this.container		= null;
	this.dragresize		= null;
	this.oldText		= null;
	this.oldRect		= null;
	this.YOffset		= 10;
	this.XOffset		= 0;
	this.onsave		= null;
	this.ondelete		= null;
	this.gui		= null;
	this.readonly	= true;
	this.author		= null;
	this.hratio		= 1;
	this.vratio		= 1;

	// Initialization
	for (var p in transfertObject) {
		this[p] = transfertObject[p];
	}

	// Action
	//this.CreateElements();
}


PhotoNote.prototype.SetRatio = function(hratio, vratio)
{
	this.hratio		= hratio;
	this.vratio		= vratio;
	this.rect.left	= this.hratio * this.rect.left;
	this.rect.top	= this.vratio * this.rect.top;
	this.rect.width	= this.hratio * this.rect.width;
	this.rect.height	= this.vratio * this.rect.height;
}


PhotoNote.prototype.Select = function()
{
	if(!this.container.editing && !this.container.readonly && !this.readonly)
	{
		this.ShowNoteText();
		this.dragresize.select(this.gui.ElementRect);
		this.selected = true;
		this.SetEditable(true);
	}
}


PhotoNote.prototype.UnSelect = function()
{
	this.dragresize.deselect(true);
	this.selected = false;
	this.SetEditable(false);
	this.HideNoteText();
}

PhotoNote.prototype.p_encodeXMLChars = function(value)
{
	var xmlCharsMapping = new Array(["&","&amp;"],['"',"&quot;"],["<","&lt;"],[">","&gt;"],["'","&#39;"]);

	var res = value;
	for (var i=0; i< xmlCharsMapping.length; i++){
		var re = new RegExp("("+xmlCharsMapping[i][0]+")","g");
		res = res.replace(re,xmlCharsMapping[i][1]);
	}
	return res;
}

PhotoNote.prototype.Save = function()
{
	this.oldText = null;
	this.oldRect = null;
	this.gui.TextTitle.innerHTML = this.author + ':  ' + this.p_encodeXMLChars(this.gui.TextBox.value).replace(/\n/gi,'<br/>');
	this.UnSelect();
}

PhotoNote.prototype.Cancel = function()
{

	//if the note is still new, then we actually want to delete it, not cancel it..
	if(this.id < 0)
	{
		this.container.DeleteNote(this)
	}
	else
	{
		//reset the node to it's old position
		if(this.oldRect != null)
		{
			this.text	= this.oldText;
			this.rect	= this.oldRect;
			this.oldText	= null;
			this.oldRect	= null;
		}
		this.gui.TextBox.value = this.text;
		this.PositionNote();
		this.UnSelect();
	}

}


PhotoNote.prototype.ShowNoteText = function()
{
	if(!this.container.editing)
	{
		this.container.HideAllNoteTexts();
		this.container.DisableAllNotes();
		this.EnableNote();

		this.gui.ElementRect.style.border='1px solid #D4D82D';
		this.gui.ElementRect.style.margin='0';
		this.gui.ElementNote.style.display='block';
	}
}

PhotoNote.prototype.DisableNote = function ()
{
	this.dragresize.enabled=false;
}

PhotoNote.prototype.EnableNote = function ()
{
	if(!this.container.readonly)
	{
		this.dragresize.enabled=true;
	}
}

PhotoNote.prototype.HideNoteText = function ()
{
	this.gui.ElementRect.style.border='0px solid #D4D82D';
	this.gui.ElementRect.style.margin='1px';
	this.gui.ElementNote.style.display='none';
}


PhotoNote.prototype.HideNote = function ()
{
	this.gui.ElementRect.style.display='none';
	this.gui.ElementNote.style.display='none';
}


PhotoNote.prototype.ShowNote = function ()
{
	this.gui.ElementRect.style.display='block';
	this.gui.ElementNote.style.display='none';
}



PhotoNote.prototype.SetEditable = function(editable)
{
	this.container.editing = editable;

	if(editable)
	{
		//the first child of the note is the text
		this.gui.TextTitle.style.display = 'none';

		//the second child is the edit area...
		this.gui.EditArea.style.display = 'block';

		//if this is a "new" note, then hide the delete button
		if(this.id <= 0)
			this.gui.DeleteButton.style.display = 'none';
		else
			this.gui.DeleteButton.style.display = 'inline';

		// get the textarea and select the text...
		this.HighlightTextbox();
	}
	else
	{
		//the first child of the note is the text
		this.gui.TextTitle.style.display = 'block';

		//the second child is the edit area...
		this.gui.EditArea.style.display = 'none';
	}
}

PhotoNote.prototype.HighlightTextbox = function ()
{
	// get the textarea and select the text...
	if(this.gui.EditArea.style.display=='block')
	{
		var textfield = this.gui.TextBox;
		setTimeout(function() {
				try
				{
				textfield.focus();
				textfield.select();
				}
				catch(e) {}
				}, 200);
	}

}

var g_fn_save_error_msg = "error saving note";
var g_fn_delete_error_msg = "error deleting note";

PhotoNote.prototype.CreateElements = function()
{

	this.gui = new PhotoNoteGUI();

	var newArea = document.createElement('div');
	this.dragresize = new DragResize('dragresize', { allowBlur: false });
	/*
	   this.dragresize = new Object();

	   this.dragresize.enabled = true
	   this.dragresize.select = function () {return true}
	   this.dragresize.deselect = function () {return true}
	   */

	newArea.className = 'fn-area';
	newArea.id = 'fn-area-new';

	var newAreaBlack = document.createElement('div');
	newAreaBlack.className = 'fn-area-blackborder';
	var newAreaWhite = document.createElement('div');
	newAreaWhite.className = 'fn-area-whiteborder';


	var currentNote = this;


	var newAreaInner = document.createElement('div');
	newAreaInner.className = 'fn-area-inner';
	newAreaWhite.appendChild(newAreaInner);


	//attach mouse events to this element...
	addEvent(newAreaInner, 'mouseover', function() {
			currentNote.ShowNoteText();
			});
	addEvent(newAreaInner, 'mouseout', function() {

			if(!currentNote.selected)
			{
			setTimeout(function () {
				currentNote.HideNoteText();
				}, 250);

			}
			});

	addEvent(newArea, 'mousedown', function() {
			if(!currentNote.selected)
			{
			//window.status = 'mouseDown2!';
			currentNote.Select();
			}
			});


	newAreaBlack.appendChild(newAreaWhite);
	newArea.appendChild(newAreaBlack);

	// add the notes area
	var noteArea = document.createElement('div');
	noteArea.className = 'fn-note';

	var titleArea = document.createElement('div');
	titleArea.className = 'fn-note-text';
	//var t = document.createTextNode(this.author + ':  ' + this.text);
	var t = document.createTextNode(this.text);
	titleArea.appendChild(t);
	noteArea.appendChild(titleArea);

	var editArea = document.createElement('div');
	editArea.className = 'fn-note-edit';

	var editAreaText = document.createElement('div');
	editAreaText.className = 'fn-note-edit-text';

	var newTextbox = document.createElement('textarea');
	newTextbox.value = this.text;
	editAreaText.appendChild(newTextbox);
	editArea.appendChild(editAreaText);

	var buttonsDiv = document.createElement('div');
	var newButtonOK = document.createElement('input');
	newButtonOK.type='button';
	newButtonOK.className = 'Butt';
	newButtonOK.value=this.container.save_text;
	newButtonOK.onclick = function() {

		if(!currentNote.onsave) {
			alert("onsave must be implemented in order to *actually* save");
			currentNote.Cancel();
		} else {
			// Store the new text
			currentNote.text = currentNote.gui.TextBox.value
				// Call the abstract function
				var photo_id = get_id('PhotoContainer', currentNote.container.element.id);
				var res = currentNote.onsave(photo_id, currentNote.id, Math.floor(currentNote.rect.left/currentNote.hratio), Math.floor(currentNote.rect.top/currentNote.vratio), Math.floor(currentNote.rect.width/currentNote.hratio), Math.floor(currentNote.rect.height/currentNote.vratio), currentNote.text);
			// Save or cancel saving according to abstract saving function result
			if(res > 0) {
				currentNote.id = res;
				currentNote.Save();
			} else {
				alert(g_fn_save_error_msg);
				currentNote.Cancel();
			}
		}

	};
	buttonsDiv.appendChild(newButtonOK);

	var newButtonCancel = document.createElement('input');
	newButtonCancel.type='button';
	newButtonCancel.className = 'CancelButt';
	newButtonCancel.value=this.container.cancel_text;
	newButtonCancel.onclick = function() {
		currentNote.Cancel();

	};
	buttonsDiv.appendChild(newButtonCancel);

	var newButtonDelete = document.createElement('input');
	newButtonDelete.type='button';
	newButtonDelete.className = 'CancelButt';
	newButtonDelete.value=this.container.delete_text;
	newButtonDelete.onclick = function() {

		if(currentNote.ondelete)
		{
			var res = currentNote.ondelete(currentNote.id);
			if(res)
			{
				currentNote.container.DeleteNote(currentNote);
			}
			else
			{
				alert(g_fn_delete_error_msg);
			}
		}
		else
		{
			alert("ondelete must be implemented in order to *actually* delete");
		}
	};
	buttonsDiv.appendChild(newButtonDelete);

	editArea.appendChild(buttonsDiv);
	noteArea.appendChild(editArea);



	/********* DRAG & RESIZE EVENTS **********************/

	this.dragresize.isElement = function(elm)
	{
		if(currentNote.selected & elm.className == 'fn-area')
		{
			this.maxLeft = currentNote.container.element.offsetWidth;
			this.maxTop = currentNote.container.element.offsetHeight;
			return true;
		}
	};
	this.dragresize.isHandle = function(elm)
	{
		if(currentNote.selected & elm.className == 'fn-area')
			return true;
	};
	this.dragresize.ondragfocus = function()
	{
		currentNote.gui.ElementRect.style.cursor = 'move';
	};
	this.dragresize.ondragblur = function()
	{
		currentNote.gui.ElementRect.style.cursor = 'pointer';
	};
	this.dragresize.ondragstart = function()
	{
		if(currentNote.oldRect == null)
		{
			currentNote.oldText	= currentNote.text
				var r			= currentNote.rect;
			currentNote.oldRect	= new PhotoNoteRect(r.left,r.top,r.width,r.height);

		}
	};
	this.dragresize.ondragend = function()
	{
	};
	this.dragresize.ondragmove = function()
	{
		currentNote.rect.left = parseInt(this.element.style.left);
		currentNote.rect.top = parseInt(this.element.style.top);
		currentNote.rect.width = parseInt(this.element.style.width);
		currentNote.rect.height = parseInt(this.element.style.height);
		currentNote.PositionNote();
	};

	this.dragresize.apply(document);




	/* setup the GUI object */
	this.gui.ElementRect = newArea;
	this.gui.ElementNote = noteArea;
	this.gui.EditArea = editArea;
	this.gui.TextBox = newTextbox;
	this.gui.TextTitle = titleArea;
	this.gui.DeleteButton = newButtonDelete;

	/* position the note text below the note area */
	this.PositionNote();

}

PhotoNote.prototype.PositionNote = function()
{
	/* outer most box */
	this.gui.ElementRect.style.left  = this.rect.left + 'px';
	this.gui.ElementRect.style.top  = this.rect.top + 'px';
	this.gui.ElementRect.style.width  = this.rect.width + 'px';
	this.gui.ElementRect.style.height  = this.rect.height + 'px';

	// black border
	this.gui.ElementRect.firstChild.style.width  = parseInt(this.gui.ElementRect.style.width) - 2 + 'px';
	this.gui.ElementRect.firstChild.style.height  = parseInt(this.gui.ElementRect.style.height) - 2 + 'px';

	// white border
	this.gui.ElementRect.firstChild.firstChild.style.width  = parseInt(this.gui.ElementRect.style.width) - 4 + 'px';
	this.gui.ElementRect.firstChild.firstChild.style.height  = parseInt(this.gui.ElementRect.style.height) - 4 + 'px';

	// inner box
	this.gui.ElementRect.firstChild.firstChild.firstChild.style.width  = parseInt(this.gui.ElementRect.style.width) - 6 + 'px';
	this.gui.ElementRect.firstChild.firstChild.firstChild.style.height  = parseInt(this.gui.ElementRect.style.height) - 6 + 'px';

	this.gui.ElementNote.style.left  = this.rect.left + this.XOffset + 'px';
	this.gui.ElementNote.style.top  = this.rect.top + this.YOffset + this.rect.height + 'px';
}

PhotoNote.prototype.toXML = function()
{

	return '<image:hasPart>'
		+'<image:Rectangle rdf:ID="'+this.id+'">'
		+'<image:points>'+this.rect.left+','+this.rect.top+' '+this.rect.width+','+this.rect.height+'</image:points>'
		//+'<dc:title>'+this.p_encodeXMLChars(this.title)+'</dc:title>'
		//+'<dc:description>'+this.p_encodeXMLChars(this.description)+'</dc:description>'
		+'<image:depicts rdf:parseType="Resource">'
		+'<dc:description>'+this.p_encodeXMLChars(this.text)+'</dc:description>'
		+'</image:depicts>'
		+'</image:Rectangle>'
		+'</image>'
}

PhotoNote.prototype.getTransfertObject = function()
{
	// Declaration
	var transfertObject = new Object()

		// Initialization
		transfertObject.id		= this.id
		//transfertObject.title		= this.title
		transfertObject.text		= this.text
		transfertObject.rect		= new Object()
		transfertObject.rect.left	= this.rect.left
		transfertObject.rect.top	= this.rect.top
		transfertObject.rect.width	= this.rect.width
		transfertObject.rect.height	= this.rect.height

		// Everything is OK !
		return transfertObject

}

PhotoNote.prototype.p_genUUID = function()
{
	var result, i, j;
	result = '';
	for(j=0; j<32; j++)
	{
		if( j == 8 || j == 12|| j == 16|| j == 20)
			result = result + '-';
		i = Math.floor(Math.random()*16).toString(16).toUpperCase();
		result = result + i;
	}
	return result
}

/*********************************************************/
/*** Photo Note GUI Object *******************************/
/*********************************************************/
function PhotoNoteGUI()
{
	this.ElementRect = null;

	// the note text area...
	this.ElementNote = null;
	this.TextTitle = null;
	this.EditArea = null;
	this.TextBox = null;

	// buttons
	this.DeleteButton = null;
}



/*********************************************************/
/*** Rectangle *******************************************/
/*********************************************************/
function PhotoNoteRect(left,top,width,height)
{
	this.left = left;
	this.top = top;
	this.width = width;
	this.height = height;
}

/* for debugging purposes */
PhotoNoteRect.prototype.toString = function()
{
	return 'left: ' + this.left + ', top: ' + this.top + ', width: ' + this.width + ', height: ' + this.height;
}


//------------------------------------------------------------------------------
/* Extend the Array object with some useful features 
http://www.ditchnet.org/wp/?p=8
*/

Array.prototype.clear = function () {
	this.length = 0;
};

Array.prototype.remove = function (element) {
	var result = false;
	var array = [];
	for (var i = 0; i < this.length; i++) {
		if (this[i] == element) {
			result = true;
		} else {
			array.push(this[i]);
		}
	}
	this.length = 0;
	for (var i = 0; i < array.length; i++) {
		this.push(array[i]);
	}
	array = null;
	return result;
};

//------------------------------------------------------------------------------
/*

   DragResize v1.0
   (c) 2005-2006 Angus Turnbull, TwinHelix Designs http://www.twinhelix.com

   Licensed under the CC-GNU LGPL, version 2.1 or later:
http://creativecommons.org/licenses/LGPL/2.1/
This is distributed WITHOUT ANY WARRANTY; without even the implied
warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

*/


// Common API code.

if (typeof addEvent != 'function')
{
	var addEvent = function(o, t, f, l)
	{
		var d = 'addEventListener', n = 'on' + t, rO = o, rT = t, rF = f, rL = l;
		if (o[d] && !l) return o[d](t, f, false);
		if (!o._evts) o._evts = {};
		if (!o._evts[t])
		{
			o._evts[t] = o[n] ? { b: o[n] } : {};
			o[n] = new Function('e',
					'var r = true, o = this, a = o._evts["' + t + '"], i; for (i in a) {' +
					'o._f = a[i]; r = o._f(e||window.event) != false && r; o._f = null;' +
					'} return r');
			if (t != 'unload') addEvent(window, 'unload', function() {
					removeEvent(rO, rT, rF, rL);
					});
		}
		if (!f._i) f._i = addEvent._i++;
		o._evts[t][f._i] = f;
	};
	addEvent._i = 1;
	var removeEvent = function(o, t, f, l)
	{
		var d = 'removeEventListener';
		if (o[d] && !l) return o[d](t, f, false);
		if (o._evts && o._evts[t] && f._i) delete o._evts[t][f._i];
	};
}


function cancelEvent(e, c)
{
	e.returnValue = false;
	if (e.preventDefault) e.preventDefault();
	if (c)
	{
		e.cancelBubble = true;
		if (e.stopPropagation) e.stopPropagation();
	}
};

// *** DRAG/RESIZE CODE ***

function DragResize(myName, config)
{
	var props = {
myName: myName,                  // Name of the object.
		enabled: true,                   // Global toggle of drag/resize.
		handles: ['tl', 'tm', 'tr',
		'ml', 'mr', 'bl', 'bm', 'br'], // Array of drag handles: top/mid/bot/right.
		isElement: null,                 // Function ref to test for an element.
		isHandle: null,                  // Function ref to test for move handle.
		element: null,                   // The currently selected element.
		handle: null,                  // Active handle reference of the element.
		minWidth: 10, minHeight: 10,     // Minimum pixel size of elements.
		minLeft: 0, maxLeft: 9999,       // Bounding box area, in pixels.
		minTop: 0, maxTop: 9999,
		zIndex: 1,                       // The highest Z-Index yet allocated.
		mouseX: 0, mouseY: 0,            // Current mouse position, recorded live.
		lastMouseX: 0, lastMouseY: 0,    // Last processed mouse positions.
		mOffX: 0, mOffY: 0,              // A known offset between position & mouse.
		elmX: 0, elmY: 0,                // Element position.
		elmW: 0, elmH: 0,                // Element size.
		allowBlur: true,                 // Whether to allow automatic blur onclick.
		ondragfocus: null,               // Event handler functions.
		ondragstart: null,
		ondragmove: null,
		ondragend: null,
		ondragblur: null
	};

	for (var p in props)
		this[p] = (typeof config[p] == 'undefined') ? props[p] : config[p];
};


DragResize.prototype.apply = function(node)
{
	// Adds object event handlers to the specified DOM node.

	var obj = this;
	addEvent(node, 'mousedown', function(e) { obj.mouseDown(e) } );
	addEvent(node, 'mousemove', function(e) { obj.mouseMove(e) } );
	addEvent(node, 'mouseup', function(e) { obj.mouseUp(e) } );
};


DragResize.prototype.select = function(newElement) { with (this)
	{
		// Selects an element for dragging.

		if (!document.getElementById || !enabled) return;

		// Activate and record our new dragging element.
		if (newElement && (newElement != element) && enabled)
		{
			element = newElement;
			// Elevate it and give it resize handles.
			element.style.zIndex = ++zIndex;
			if (this.resizeHandleSet) this.resizeHandleSet(element, true);
			// Record element attributes for mouseMove().
			elmX = parseInt(element.style.left);
			elmY = parseInt(element.style.top);
			elmW = element.offsetWidth;
			elmH = element.offsetHeight;
			if (ondragfocus) this.ondragfocus();
		}
	}};


DragResize.prototype.deselect = function(delHandles) { with (this)
	{
		// Immediately stops dragging an element. If 'delHandles' is true, this
		// remove the handles from the element and clears the element flag,
		// completely resetting the .

		if (!document.getElementById || !enabled) return;

		if (delHandles)
		{
			if (ondragblur) this.ondragblur();
			if (this.resizeHandleSet) this.resizeHandleSet(element, false);
			element = null;
		}

		handle = null;
		mOffX = 0;
		mOffY = 0;
	}};


DragResize.prototype.mouseDown = function(e) { with (this)
	{
		// Suitable elements are selected for drag/resize on mousedown.
		// We also initialise the resize boxes, and drag parameters like mouse position etc.
		if (!document.getElementById || !enabled) return true;

		var elm = e.target || e.srcElement,
			newElement = null,
			newHandle = null,
			hRE = new RegExp(myName + '-([trmbl]{2})', '');

		while (elm)
		{
			// Loop up the DOM looking for matching elements. Remember one if found.
			if (elm.className)
			{
				if (!newHandle && (hRE.test(elm.className) || isHandle(elm))) newHandle = elm;
				if (isElement(elm)) { newElement = elm; break }
			}
			elm = elm.parentNode;
		}

		// If this isn't on the last dragged element, call deselect(),
		// which will hide its handles and clear element.
		if (element && (element != newElement) && allowBlur) deselect(true);

		// If we have a new matching element, call select().
		if (newElement && (!element || (newElement == element)))
		{
			// Stop mouse selections if we're dragging a handle.
			if (newHandle) cancelEvent(e);
			select(newElement, newHandle);
			handle = newHandle;
			if (handle && ondragstart) this.ondragstart(hRE.test(handle.className));
		}
	}};


DragResize.prototype.mouseMove = function(e) { with (this)
	{
		// This continually offsets the dragged element by the difference between the
		// last recorded mouse position (mouseX/Y) and the current mouse position.
		if (!document.getElementById || !enabled) return true;

		// We always record the current mouse position.
		mouseX = e.pageX || e.clientX + document.documentElement.scrollLeft;
		mouseY = e.pageY || e.clientY + document.documentElement.scrollTop;
		// Record the relative mouse movement, in case we're dragging.
		// Add any previously stored & ignored offset to the calculations.
		var diffX = mouseX - lastMouseX + mOffX;
		var diffY = mouseY - lastMouseY + mOffY;
		mOffX = mOffY = 0;
		// Update last processed mouse positions.
		lastMouseX = mouseX;
		lastMouseY = mouseY;

		// That's all we do if we're not dragging anything.
		if (!handle) return true;

		// If included in the script, run the resize handle drag routine.
		// Let it create an object representing the drag offsets.
		var isResize = false;
		if (this.resizeHandleDrag && this.resizeHandleDrag(diffX, diffY))
		{
			isResize = true;
		}
		else
		{
			// If the resize drag handler isn't set or returns fase (to indicate the drag was
			// not on a resize handle), we must be dragging the whole element, so move that.
			// Bounds check left-right...
			var dX = diffX, dY = diffY;
			if (elmX + dX < minLeft) mOffX = (dX - (diffX = minLeft - elmX));
			else if (elmX + elmW + dX > maxLeft) mOffX = (dX - (diffX = maxLeft - elmX - elmW));
			// ...and up-down.
			if (elmY + dY < minTop) mOffY = (dY - (diffY = minTop - elmY));
			else if (elmY + elmH + dY > maxTop) mOffY = (dY - (diffY = maxTop - elmY - elmH));
			elmX += diffX;
			elmY += diffY;
		}

		// Assign new info back to the element, with minimum dimensions.
		with (element.style)
		{
			left =   elmX + 'px';
			width =  elmW + 'px';
			top =    elmY + 'px';
			height = elmH + 'px';
		}

		// Evil, dirty, hackish Opera select-as-you-drag fix.
		if (window.opera && document.documentElement)
		{
			var oDF = document.getElementById('op-drag-fix');
			if (!oDF)
			{
				var oDF = document.createElement('input');
				oDF.id = 'op-drag-fix';
				oDF.style.display = 'none';
				document.body.appendChild(oDF);
			}
			oDF.focus();
		}

		if (ondragmove) this.ondragmove(isResize);

		// Stop a normal drag event.
		cancelEvent(e);
	}};


DragResize.prototype.mouseUp = function(e) { with (this)
	{
		// On mouseup, stop dragging, but don't reset handler visibility.
		if (!document.getElementById || !enabled) return;

		var hRE = new RegExp(myName + '-([trmbl]{2})', '');
		if (handle && ondragend) this.ondragend(hRE.test(handle.className));
		deselect(false);
	}};



/* Resize Code -- can be deleted if you're not using it. */

DragResize.prototype.resizeHandleSet = function(elm, show) { with (this)
	{
		// Either creates, shows or hides the resize handles within an element.

		// If we're showing them, and no handles have been created, create 4 new ones.
		if (!elm._handle_tr)
		{
			for (var h = 0; h < handles.length; h++)
			{
				// Create 4 news divs, assign each a generic + specific class.
				var hDiv = document.createElement('div');
				hDiv.className = myName + ' ' +  myName + '-' + handles[h];
				elm['_handle_' + handles[h]] = elm.appendChild(hDiv);
			}
		}

		// We now have handles. Find them all and show/hide.
		for (var h = 0; h < handles.length; h++)
		{
			elm['_handle_' + handles[h]].style.visibility = show ? 'inherit' : 'hidden';
		}
	}};


DragResize.prototype.resizeHandleDrag = function(diffX, diffY) { with (this)
	{
		// Passed the mouse movement amounts. This function checks to see whether the
		// drag is from a resize handle created above; if so, it changes the stored
		// elm* dimensions and mOffX/Y.

		var hClass = handle && handle.className &&
			handle.className.match(new RegExp(myName + '-([tmblr]{2})')) ? RegExp.$1 : '';

		// If the hClass is one of the resize handles, resize one or two dimensions.
		// Bounds checking is the hard bit -- basically for each edge, check that the
		// element doesn't go under minimum size, and doesn't go beyond its boundary.
		var dY = diffY, dX = diffX, processed = false;
		if (hClass.indexOf('t') >= 0)
		{
			rs = 1;
			if (elmH - dY < minHeight) mOffY = (dY - (diffY = elmH - minHeight));
			else if (elmY + dY < minTop) mOffY = (dY - (diffY = minTop - elmY));
			elmY += diffY;
			elmH -= diffY;
			processed = true;
		}
		if (hClass.indexOf('b') >= 0)
		{
			rs = 1;
			if (elmH + dY < minHeight) mOffY = (dY - (diffY = minHeight - elmH));
			else if (elmY + elmH + dY > maxTop) mOffY = (dY - (diffY = maxTop - elmY - elmH));
			elmH += diffY;
			processed = true;
		}
		if (hClass.indexOf('l') >= 0)
		{
			rs = 1;
			if (elmW - dX < minWidth) mOffX = (dX - (diffX = elmW - minWidth));
			else if (elmX + dX < minLeft) mOffX = (dX - (diffX = minLeft - elmX));
			elmX += diffX;
			elmW -= diffX;
			processed = true;
		}
		if (hClass.indexOf('r') >= 0)
		{
			rs = 1;
			if (elmW + dX < minWidth) mOffX = (dX - (diffX = minWidth - elmW));
			else if (elmX + elmW + dX > maxLeft) mOffX = (dX - (diffX = maxLeft - elmX - elmW));
			elmW += diffX;
			processed = true;
		}

		return processed;
	}};
