1var FCKDragTableHandler =
2{
3	"_DragState" : 0,
4	"_LeftCell" : null,
5	"_RightCell" : null,
6	"_MouseMoveMode" : 0,	// 0 - find candidate cells for resizing, 1 - drag to resize
7	"_ResizeBar" : null,
8	"_OriginalX" : null,
9	"_MinimumX" : null,
10	"_MaximumX" : null,
11	"_LastX" : null,
12	"_TableMap" : null,
13	"_doc" : document,
14	"_IsInsideNode" : function( w, domNode, pos )
15	{
16		var myCoords = FCKTools.GetWindowPosition( w, domNode ) ;
17		var xMin = myCoords.x ;
18		var yMin = myCoords.y ;
19		var xMax = parseInt( xMin, 10 ) + parseInt( domNode.offsetWidth, 10 ) ;
20		var yMax = parseInt( yMin, 10 ) + parseInt( domNode.offsetHeight, 10 ) ;
21		if ( pos.x >= xMin && pos.x <= xMax && pos.y >= yMin && pos.y <= yMax )
22			return true;
23		return false;
24	},
25	"_GetBorderCells" : function( w, tableNode, tableMap, mouse )
26	{
27		// Enumerate all the cells in the table.
28		var cells = [] ;
29		for ( var i = 0 ; i < tableNode.rows.length ; i++ )
30		{
31			var r = tableNode.rows[i] ;
32			for ( var j = 0 ; j < r.cells.length ; j++ )
33				cells.push( r.cells[j] ) ;
34		}
35
36		if ( cells.length < 1 )
37			return null ;
38
39		// Get the cells whose right or left border is nearest to the mouse cursor's x coordinate.
40		var minRxDist = null ;
41		var lxDist = null ;
42		var minYDist = null ;
43		var rbCell = null ;
44		var lbCell = null ;
45		for ( var i = 0 ; i < cells.length ; i++ )
46		{
47			var pos = FCKTools.GetWindowPosition( w, cells[i] ) ;
48			var rightX = pos.x + parseInt( cells[i].clientWidth, 10 ) ;
49			var rxDist = mouse.x - rightX ;
50			var yDist = mouse.y - ( pos.y + ( cells[i].clientHeight / 2 ) ) ;
51			if ( minRxDist == null ||
52					( Math.abs( rxDist ) <= Math.abs( minRxDist ) &&
53					  ( minYDist == null || Math.abs( yDist ) <= Math.abs( minYDist ) ) ) )
54			{
55				minRxDist = rxDist ;
56				minYDist = yDist ;
57				rbCell = cells[i] ;
58			}
59		}
60		/*
61		var rowNode = FCKTools.GetElementAscensor( rbCell, "tr" ) ;
62		var cellIndex = rbCell.cellIndex + 1 ;
63		if ( cellIndex >= rowNode.cells.length )
64			return null ;
65		lbCell = rowNode.cells.item( cellIndex ) ;
66		*/
67		var rowIdx = rbCell.parentNode.rowIndex ;
68		var colIdx = FCKTableHandler._GetCellIndexSpan( tableMap, rowIdx, rbCell ) ;
69		var colSpan = isNaN( rbCell.colSpan ) ? 1 : rbCell.colSpan ;
70		lbCell = tableMap[rowIdx][colIdx + colSpan] ;
71
72		if ( ! lbCell )
73			return null ;
74
75		// Abort if too far from the border.
76		lxDist = mouse.x - FCKTools.GetWindowPosition( w, lbCell ).x ;
77		if ( lxDist < 0 && minRxDist < 0 && minRxDist < -2 )
78			return null ;
79		if ( lxDist > 0 && minRxDist > 0 && lxDist > 3 )
80			return null ;
81
82		return { "leftCell" : rbCell, "rightCell" : lbCell } ;
83	},
84	"_GetResizeBarPosition" : function()
85	{
86		var row = FCKTools.GetElementAscensor( this._RightCell, "tr" ) ;
87		return FCKTableHandler._GetCellIndexSpan( this._TableMap, row.rowIndex, this._RightCell ) ;
88	},
89	"_ResizeBarMouseDownListener" : function( evt )
90	{
91		if ( FCKDragTableHandler._LeftCell )
92			FCKDragTableHandler._MouseMoveMode = 1 ;
93		if ( FCKBrowserInfo.IsIE )
94			FCKDragTableHandler._ResizeBar.filters.item("DXImageTransform.Microsoft.Alpha").opacity = 50 ;
95		else
96			FCKDragTableHandler._ResizeBar.style.opacity = 0.5 ;
97		FCKDragTableHandler._OriginalX = evt.clientX ;
98
99		// Calculate maximum and minimum x-coordinate delta.
100		var borderIndex = FCKDragTableHandler._GetResizeBarPosition() ;
101		var offset = FCKDragTableHandler._GetIframeOffset();
102		var table = FCKTools.GetElementAscensor( FCKDragTableHandler._LeftCell, "table" );
103		var minX = null ;
104		var maxX = null ;
105		for ( var r = 0 ; r < FCKDragTableHandler._TableMap.length ; r++ )
106		{
107			var leftCell = FCKDragTableHandler._TableMap[r][borderIndex - 1] ;
108			var rightCell = FCKDragTableHandler._TableMap[r][borderIndex] ;
109			var leftPosition = FCKTools.GetWindowPosition( FCK.EditorWindow, leftCell ) ;
110			var rightPosition = FCKTools.GetWindowPosition( FCK.EditorWindow, rightCell ) ;
111			var leftPadding = FCKDragTableHandler._GetCellPadding( table, leftCell ) ;
112			var rightPadding = FCKDragTableHandler._GetCellPadding( table, rightCell ) ;
113			if ( minX == null || leftPosition.x + leftPadding > minX )
114				minX = leftPosition.x + leftPadding ;
115			if ( maxX == null || rightPosition.x + rightCell.clientWidth - rightPadding < maxX )
116				maxX = rightPosition.x + rightCell.clientWidth - rightPadding ;
117		}
118
119		FCKDragTableHandler._MinimumX = minX + offset.x ;
120		FCKDragTableHandler._MaximumX = maxX + offset.x ;
121		FCKDragTableHandler._LastX = null ;
122
123		if (evt.preventDefault)
124			evt.preventDefault();
125		else
126			evt.returnValue = false;
127	},
128	"_ResizeBarMouseUpListener" : function( evt )
129	{
130		FCKDragTableHandler._MouseMoveMode = 0 ;
131		FCKDragTableHandler._HideResizeBar() ;
132
133		if ( FCKDragTableHandler._LastX == null )
134			return ;
135
136		// Calculate the delta value.
137		var deltaX = FCKDragTableHandler._LastX - FCKDragTableHandler._OriginalX ;
138
139		// Then, build an array of current column width values.
140		// This algorithm can be very slow if the cells have insane colSpan values. (e.g. colSpan=1000).
141		var table = FCKTools.GetElementAscensor( FCKDragTableHandler._LeftCell, "table" ) ;
142		var colArray = [] ;
143		var tableMap = FCKDragTableHandler._TableMap ;
144		for ( var i = 0 ; i < tableMap.length ; i++ )
145		{
146			for ( var j = 0 ; j < tableMap[i].length ; j++ )
147			{
148				var cell = tableMap[i][j] ;
149				var width = FCKDragTableHandler._GetCellWidth( table, cell ) ;
150				var colSpan = isNaN( cell.colSpan) ? 1 : cell.colSpan ;
151				if ( colArray.length <= j )
152					colArray.push( { width : width / colSpan, colSpan : colSpan } ) ;
153				else
154				{
155					var guessItem = colArray[j] ;
156					if ( guessItem.colSpan > colSpan )
157					{
158						guessItem.width = width / colSpan ;
159						guessItem.colSpan = colSpan ;
160					}
161				}
162			}
163		}
164
165		// Find out the equivalent column index of the two cells selected for resizing.
166		colIndex = FCKDragTableHandler._GetResizeBarPosition() ;
167
168		// Note that colIndex must be at least 1 here, so it's safe to subtract 1 from it.
169		colIndex-- ;
170
171		// Modify the widths in the colArray according to the mouse coordinate delta value.
172		colArray[colIndex].width += deltaX ;
173		colArray[colIndex + 1].width -= deltaX ;
174
175		// Clear all cell widths, delete all <col> elements from the table.
176		for ( var r = 0 ; r < table.rows.length ; r++ )
177		{
178			var row = table.rows.item( r ) ;
179			for ( var c = 0 ; c < row.cells.length ; c++ )
180			{
181				var cell = row.cells.item( c ) ;
182				cell.width = "" ;
183				cell.style.width = "" ;
184			}
185		}
186		var colElements = table.getElementsByTagName( "col" ) ;
187		for ( var i = colElements.length - 1 ; i >= 0 ; i-- )
188			colElements[i].parentNode.removeChild( colElements[i] ) ;
189
190		// Set new cell widths.
191		var processedCells = [] ;
192		for ( var i = 0 ; i < tableMap.length ; i++ )
193		{
194			for ( var j = 0 ; j < tableMap[i].length ; j++ )
195			{
196				var cell = tableMap[i][j] ;
197				if ( cell._Processed )
198					continue ;
199				if ( tableMap[i][j-1] != cell )
200					cell.width = colArray[j].width ;
201				else
202					cell.width = parseInt( cell.width, 10 ) + parseInt( colArray[j].width, 10 ) ;
203				if ( tableMap[i][j+1] != cell )
204				{
205					processedCells.push( cell ) ;
206					cell._Processed = true ;
207				}
208			}
209		}
210		for ( var i = 0 ; i < processedCells.length ; i++ )
211		{
212			if ( FCKBrowserInfo.IsIE )
213				processedCells[i].removeAttribute( '_Processed' ) ;
214			else
215				delete processedCells[i]._Processed ;
216		}
217
218		FCKDragTableHandler._LastX = null ;
219	},
220	"_ResizeBarMouseMoveListener" : function( evt )
221	{
222		if ( FCKDragTableHandler._MouseMoveMode == 0 )
223			return FCKDragTableHandler._MouseFindHandler( FCK, evt ) ;
224		else
225			return FCKDragTableHandler._MouseDragHandler( FCK, evt ) ;
226	},
227	// Calculate the padding of a table cell.
228	// It returns the value of paddingLeft + paddingRight of a table cell.
229	// This function is used, in part, to calculate the width parameter that should be used for setting cell widths.
230	// The equation in question is clientWidth = paddingLeft + paddingRight + width.
231	// So that width = clientWidth - paddingLeft - paddingRight.
232	// The return value of this function must be pixel accurate acorss all supported browsers, so be careful if you need to modify it.
233	"_GetCellPadding" : function( table, cell )
234	{
235		var attrGuess = parseInt( table.cellPadding, 10 ) * 2 ;
236		var cssGuess = null ;
237		if ( typeof( window.getComputedStyle ) == "function" )
238		{
239			var styleObj = window.getComputedStyle( cell, null ) ;
240			cssGuess = parseInt( styleObj.getPropertyValue( "padding-left" ), 10 ) +
241				parseInt( styleObj.getPropertyValue( "padding-right" ), 10 ) ;
242		}
243		else
244			cssGuess = parseInt( cell.currentStyle.paddingLeft, 10 ) + parseInt (cell.currentStyle.paddingRight, 10 ) ;
245
246		var cssRuntime = cell.style.padding ;
247		if ( isFinite( cssRuntime ) )
248			cssGuess = parseInt( cssRuntime, 10 ) * 2 ;
249		else
250		{
251			cssRuntime = cell.style.paddingLeft ;
252			if ( isFinite( cssRuntime ) )
253				cssGuess = parseInt( cssRuntime, 10 ) ;
254			cssRuntime = cell.style.paddingRight ;
255			if ( isFinite( cssRuntime ) )
256				cssGuess += parseInt( cssRuntime, 10 ) ;
257		}
258
259		attrGuess = parseInt( attrGuess, 10 ) ;
260		cssGuess = parseInt( cssGuess, 10 ) ;
261		if ( isNaN( attrGuess ) )
262			attrGuess = 0 ;
263		if ( isNaN( cssGuess ) )
264			cssGuess = 0 ;
265		return Math.max( attrGuess, cssGuess ) ;
266	},
267	// Calculate the real width of the table cell.
268	// The real width of the table cell is the pixel width that you can set to the width attribute of the table cell and after
269	// that, the table cell should be of exactly the same width as before.
270	// The real width of a table cell can be calculated as:
271	// width = clientWidth - paddingLeft - paddingRight.
272	"_GetCellWidth" : function( table, cell )
273	{
274		var clientWidth = cell.clientWidth ;
275		if ( isNaN( clientWidth ) )
276			clientWidth = 0 ;
277		return clientWidth - this._GetCellPadding( table, cell ) ;
278	},
279	"MouseMoveListener" : function( FCK, evt )
280	{
281		if ( FCKDragTableHandler._MouseMoveMode == 0 )
282			return FCKDragTableHandler._MouseFindHandler( FCK, evt ) ;
283		else
284			return FCKDragTableHandler._MouseDragHandler( FCK, evt ) ;
285	},
286	"_MouseFindHandler" : function( FCK, evt )
287	{
288		if ( FCK.MouseDownFlag )
289			return ;
290		var node = evt.srcElement || evt.target ;
291		try
292		{
293			if ( ! node || node.nodeType != 1 )
294			{
295				this._HideResizeBar() ;
296				return ;
297			}
298		}
299		catch ( e )
300		{
301			this._HideResizeBar() ;
302			return ;
303		}
304
305		// Since this function might be called from the editing area iframe or the outer fckeditor iframe,
306		// the mouse point coordinates from evt.clientX/Y can have different reference points.
307		// We need to resolve the mouse pointer position relative to the editing area iframe.
308		var mouseX = evt.clientX ;
309		var mouseY = evt.clientY ;
310		if ( FCKTools.GetElementDocument( node ) == document )
311		{
312			var offset = this._GetIframeOffset() ;
313			mouseX -= offset.x ;
314			mouseY -= offset.y ;
315		}
316
317
318		if ( this._ResizeBar && this._LeftCell )
319		{
320			var leftPos = FCKTools.GetWindowPosition( FCK.EditorWindow, this._LeftCell ) ;
321			var rightPos = FCKTools.GetWindowPosition( FCK.EditorWindow, this._RightCell ) ;
322			var rxDist = mouseX - ( leftPos.x + this._LeftCell.clientWidth ) ;
323			var lxDist = mouseX - rightPos.x ;
324			var inRangeFlag = false ;
325			if ( lxDist >= 0 && rxDist <= 0 )
326				inRangeFlag = true ;
327			else if ( rxDist > 0 && lxDist <= 3 )
328				inRangeFlag = true ;
329			else if ( lxDist < 0 && rxDist >= -2 )
330				inRangeFlag = true ;
331			if ( inRangeFlag )
332			{
333				this._ShowResizeBar( FCK.EditorWindow,
334					FCKTools.GetElementAscensor( this._LeftCell, "table" ),
335					{ "x" : mouseX, "y" : mouseY } ) ;
336				return ;
337			}
338		}
339
340		var tagName = node.tagName.toLowerCase() ;
341		if ( tagName != "table" && tagName != "td" && tagName != "th" )
342		{
343			if ( this._LeftCell )
344				this._LeftCell = this._RightCell = this._TableMap = null ;
345			this._HideResizeBar() ;
346			return ;
347		}
348		node = FCKTools.GetElementAscensor( node, "table" ) ;
349		var tableMap = FCKTableHandler._CreateTableMap( node ) ;
350		var cellTuple = this._GetBorderCells( FCK.EditorWindow, node, tableMap, { "x" : mouseX, "y" : mouseY } ) ;
351
352		if ( cellTuple == null )
353		{
354			if ( this._LeftCell )
355				this._LeftCell = this._RightCell = this._TableMap = null ;
356			this._HideResizeBar() ;
357		}
358		else
359		{
360			this._LeftCell = cellTuple["leftCell"] ;
361			this._RightCell = cellTuple["rightCell"] ;
362			this._TableMap = tableMap ;
363			this._ShowResizeBar( FCK.EditorWindow,
364					FCKTools.GetElementAscensor( this._LeftCell, "table" ),
365					{ "x" : mouseX, "y" : mouseY } ) ;
366		}
367	},
368	"_MouseDragHandler" : function( FCK, evt )
369	{
370		var mouse = { "x" : evt.clientX, "y" : evt.clientY } ;
371
372		// Convert mouse coordinates in reference to the outer iframe.
373		var node = evt.srcElement || evt.target ;
374		if ( FCKTools.GetElementDocument( node ) == FCK.EditorDocument )
375		{
376			var offset = this._GetIframeOffset() ;
377			mouse.x += offset.x ;
378			mouse.y += offset.y ;
379		}
380
381		// Calculate the mouse position delta and see if we've gone out of range.
382		if ( mouse.x >= this._MaximumX - 5 )
383			mouse.x = this._MaximumX - 5 ;
384		if ( mouse.x <= this._MinimumX + 5 )
385			mouse.x = this._MinimumX + 5 ;
386
387		var docX = mouse.x + FCKTools.GetScrollPosition( window ).X ;
388		this._ResizeBar.style.left = ( docX - this._ResizeBar.offsetWidth / 2 ) + "px" ;
389		this._LastX = mouse.x ;
390	},
391	"_ShowResizeBar" : function( w, table, mouse )
392	{
393		if ( this._ResizeBar == null )
394		{
395			this._ResizeBar = this._doc.createElement( "div" ) ;
396			var paddingBar = this._ResizeBar ;
397			var paddingStyles = { 'position' : 'absolute', 'cursor' : 'e-resize' } ;
398			if ( FCKBrowserInfo.IsIE )
399				paddingStyles.filter = "progid:DXImageTransform.Microsoft.Alpha(opacity=10,enabled=true)" ;
400			else
401				paddingStyles.opacity = 0.10 ;
402			FCKDomTools.SetElementStyles( paddingBar, paddingStyles ) ;
403			this._avoidStyles( paddingBar );
404			paddingBar.setAttribute('_fcktemp', true);
405			this._doc.body.appendChild( paddingBar ) ;
406			FCKTools.AddEventListener( paddingBar, "mousemove", this._ResizeBarMouseMoveListener ) ;
407			FCKTools.AddEventListener( paddingBar, "mousedown", this._ResizeBarMouseDownListener ) ;
408			FCKTools.AddEventListener( document, "mouseup", this._ResizeBarMouseUpListener ) ;
409			FCKTools.AddEventListener( FCK.EditorDocument, "mouseup", this._ResizeBarMouseUpListener ) ;
410
411			// IE doesn't let the tranparent part of the padding block to receive mouse events unless there's something inside.
412			// So we need to create a spacer image to fill the block up.
413			var filler = this._doc.createElement( "img" ) ;
414			filler.setAttribute('_fcktemp', true);
415			filler.border = 0 ;
416			filler.src = FCKConfig.BasePath + "images/spacer.gif" ;
417			filler.style.position = "absolute" ;
418			paddingBar.appendChild( filler ) ;
419
420			// Disable drag and drop, and selection for the filler image.
421			var disabledListener = function( evt )
422			{
423				if ( evt.preventDefault )
424					evt.preventDefault() ;
425				else
426					evt.returnValue = false ;
427			}
428			FCKTools.AddEventListener( filler, "dragstart", disabledListener ) ;
429			FCKTools.AddEventListener( filler, "selectstart", disabledListener ) ;
430		}
431
432		var paddingBar = this._ResizeBar ;
433		var offset = this._GetIframeOffset() ;
434		var tablePos = this._GetTablePosition( w, table ) ;
435		var barHeight = table.offsetHeight ;
436		var barTop = offset.y + tablePos.y ;
437		// Do not let the resize bar intrude into the toolbar area.
438		if ( tablePos.y < 0 )
439		{
440			barHeight += tablePos.y ;
441			barTop -= tablePos.y ;
442		}
443		var bw = parseInt( table.border, 10 ) ;
444		if ( isNaN( bw ) )
445			bw = 0 ;
446		var cs = parseInt( table.cellSpacing, 10 ) ;
447		if ( isNaN( cs ) )
448			cs = 0 ;
449		var barWidth = Math.max( bw+100, cs+100 ) ;
450		var paddingStyles =
451		{
452			'top'		: barTop + 'px',
453			'height'	: barHeight + 'px',
454			'width'		: barWidth + 'px',
455			'left'		: ( offset.x + mouse.x + FCKTools.GetScrollPosition( w ).X - barWidth / 2 ) + 'px'
456		} ;
457		if ( FCKBrowserInfo.IsIE )
458			paddingBar.filters.item("DXImageTransform.Microsoft.Alpha").opacity = 10 ;
459		else
460			paddingStyles.opacity = 0.1 ;
461
462		FCKDomTools.SetElementStyles( paddingBar, paddingStyles ) ;
463		var filler = paddingBar.getElementsByTagName( "img" )[0] ;
464
465		FCKDomTools.SetElementStyles( filler,
466			{
467				width	: paddingBar.offsetWidth + 'px',
468				height	: barHeight + 'px'
469			} ) ;
470
471		barWidth = Math.max( bw, cs, 3 ) ;
472		var visibleBar = null ;
473		if ( paddingBar.getElementsByTagName( "div" ).length < 1 )
474		{
475			visibleBar = this._doc.createElement( "div" ) ;
476			this._avoidStyles( visibleBar );
477			visibleBar.setAttribute('_fcktemp', true);
478			paddingBar.appendChild( visibleBar ) ;
479		}
480		else
481			visibleBar = paddingBar.getElementsByTagName( "div" )[0] ;
482
483		FCKDomTools.SetElementStyles( visibleBar,
484			{
485				position		: 'absolute',
486				backgroundColor	: 'blue',
487				width			: barWidth + 'px',
488				height			: barHeight + 'px',
489				left			: '50px',
490				top				: '0px'
491			} ) ;
492	},
493	"_HideResizeBar" : function()
494	{
495		if ( this._ResizeBar )
496			// IE bug: display : none does not hide the resize bar for some reason.
497			// so set the position to somewhere invisible.
498			FCKDomTools.SetElementStyles( this._ResizeBar,
499				{
500					top		: '-100000px',
501					left	: '-100000px'
502				} ) ;
503	},
504	"_GetIframeOffset" : function ()
505	{
506		return FCKTools.GetDocumentPosition( window, FCK.EditingArea.IFrame ) ;
507	},
508	"_GetTablePosition" : function ( w, table )
509	{
510		return FCKTools.GetWindowPosition( w, table ) ;
511	},
512	"_avoidStyles" : function( element )
513	{
514		FCKDomTools.SetElementStyles( element,
515			{
516				padding		: '0',
517				backgroundImage	: 'none',
518				border		: '0'
519			} ) ;
520	},
521	"Reset" : function()
522	{
523		FCKDragTableHandler._LeftCell = FCKDragTableHandler._RightCell = FCKDragTableHandler._TableMap = null ;
524	}
525
526};
527
528FCK.Events.AttachEvent( "OnMouseMove", FCKDragTableHandler.MouseMoveListener ) ;
529FCK.Events.AttachEvent( "OnAfterSetHTML", FCKDragTableHandler.Reset ) ;
530