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	"_IsInsideNode" : function( w, domNode, pos )
14	{
15		var myCoords = FCKTools.GetWindowPosition( w, domNode ) ;
16		var xMin = myCoords.x ;
17		var yMin = myCoords.y ;
18		var xMax = parseInt( xMin, 10 ) + parseInt( domNode.offsetWidth, 10 ) ;
19		var yMax = parseInt( yMin, 10 ) + parseInt( domNode.offsetHeight, 10 ) ;
20		if ( pos.x >= xMin && pos.x <= xMax && pos.y >= yMin && pos.y <= yMax )
21			return true;
22		return false;
23	},
24	"_GetBorderCells" : function( w, tableNode, tableMap, mouse )
25	{
26		// Enumerate all the cells in the table.
27		var cells = [] ;
28		for ( var i = 0 ; i < tableNode.rows.length ; i++ )
29		{
30			var r = tableNode.rows[i] ;
31			for ( var j = 0 ; j < r.cells.length ; j++ )
32				cells.push( r.cells[j] ) ;
33		}
34
35		if ( cells.length < 1 )
36			return null ;
37
38		// Get the cells whose right or left border is nearest to the mouse cursor's x coordinate.
39		var minRxDist = null ;
40		var lxDist = null ;
41		var minYDist = null ;
42		var rbCell = null ;
43		var lbCell = null ;
44		for ( var i = 0 ; i < cells.length ; i++ )
45		{
46			var pos = FCKTools.GetWindowPosition( w, cells[i] ) ;
47			var rightX = pos.x + parseInt( cells[i].clientWidth, 10 ) ;
48			var rxDist = mouse.x - rightX ;
49			var yDist = mouse.y - ( pos.y + ( cells[i].clientHeight / 2 ) ) ;
50			if ( minRxDist == null ||
51					( Math.abs( rxDist ) <= Math.abs( minRxDist ) &&
52					  ( minYDist == null || Math.abs( yDist ) <= Math.abs( minYDist ) ) ) )
53			{
54				minRxDist = rxDist ;
55				minYDist = yDist ;
56				rbCell = cells[i] ;
57			}
58		}
59		/*
60		var rowNode = FCKTools.GetElementAscensor( rbCell, "tr" ) ;
61		var cellIndex = rbCell.cellIndex + 1 ;
62		if ( cellIndex >= rowNode.cells.length )
63			return null ;
64		lbCell = rowNode.cells.item( cellIndex ) ;
65		*/
66		var rowIdx = rbCell.parentNode.rowIndex ;
67		var colIdx = FCKTableHandler._GetCellIndexSpan( tableMap, rowIdx, rbCell ) ;
68		var colSpan = isNaN( rbCell.colSpan ) ? 1 : rbCell.colSpan ;
69		lbCell = tableMap[rowIdx][colIdx + colSpan] ;
70
71		if ( ! lbCell )
72			return null ;
73
74		// Abort if too far from the border.
75		lxDist = mouse.x - FCKTools.GetWindowPosition( w, lbCell ).x ;
76		if ( lxDist < 0 && minRxDist < 0 && minRxDist < -2 )
77			return null ;
78		if ( lxDist > 0 && minRxDist > 0 && lxDist > 3 )
79			return null ;
80
81		return { "leftCell" : rbCell, "rightCell" : lbCell } ;
82	},
83	"_GetResizeBarPosition" : function()
84	{
85		var row = FCKTools.GetElementAscensor( this._RightCell, "tr" ) ;
86		return FCKTableHandler._GetCellIndexSpan( this._TableMap, row.rowIndex, this._RightCell ) ;
87	},
88	"_ResizeBarMouseDownListener" : function( evt )
89	{
90		if ( ! evt )
91			evt = window.event ;
92		if ( FCKDragTableHandler._LeftCell )
93			FCKDragTableHandler._MouseMoveMode = 1 ;
94		if ( FCKBrowserInfo.IsIE )
95			FCKDragTableHandler._ResizeBar.filters.item("DXImageTransform.Microsoft.Alpha").opacity = 50 ;
96		else
97			FCKDragTableHandler._ResizeBar.style.opacity = 0.5 ;
98		FCKDragTableHandler._OriginalX = evt.clientX ;
99
100		// Calculate maximum and minimum x-coordinate delta.
101		var borderIndex = FCKDragTableHandler._GetResizeBarPosition() ;
102		var offset = FCKTools.GetDocumentPosition( window, FCK.EditingArea.IFrame ) ;
103		var table = FCKTools.GetElementAscensor( FCKDragTableHandler._LeftCell, "table" );
104		var minX = null ;
105		var maxX = null ;
106		for ( var r = 0 ; r < FCKDragTableHandler._TableMap.length ; r++ )
107		{
108			var leftCell = FCKDragTableHandler._TableMap[r][borderIndex - 1] ;
109			var rightCell = FCKDragTableHandler._TableMap[r][borderIndex] ;
110			var leftPosition = FCKTools.GetWindowPosition( FCK.EditorWindow, leftCell ) ;
111			var rightPosition = FCKTools.GetWindowPosition( FCK.EditorWindow, rightCell ) ;
112			var leftPadding = FCKDragTableHandler._GetCellPadding( table, leftCell ) ;
113			var rightPadding = FCKDragTableHandler._GetCellPadding( table, rightCell ) ;
114			if ( minX == null || leftPosition.x + leftPadding > minX )
115				minX = leftPosition.x + leftPadding ;
116			if ( maxX == null || rightPosition.x + rightCell.clientWidth - rightPadding < maxX )
117				maxX = rightPosition.x + rightCell.clientWidth - rightPadding ;
118		}
119
120		FCKDragTableHandler._MinimumX = minX + offset.x ;
121		FCKDragTableHandler._MaximumX = maxX + offset.x ;
122		FCKDragTableHandler._LastX = null ;
123	},
124	"_ResizeBarMouseUpListener" : function( evt )
125	{
126		if ( ! evt )
127			evt = window.event ;
128		FCKDragTableHandler._MouseMoveMode = 0 ;
129		FCKDragTableHandler._HideResizeBar() ;
130
131		if ( FCKDragTableHandler._LastX == null )
132			return ;
133
134		// Calculate the delta value.
135		var deltaX = FCKDragTableHandler._LastX - FCKDragTableHandler._OriginalX ;
136
137		// Then, build an array of current column width values.
138		// This algorithm can be very slow if the cells have insane colSpan values. (e.g. colSpan=1000).
139		var table = FCKTools.GetElementAscensor( FCKDragTableHandler._LeftCell, "table" ) ;
140		var colArray = [] ;
141		var tableMap = FCKDragTableHandler._TableMap ;
142		for ( var i = 0 ; i < tableMap.length ; i++ )
143		{
144			for ( var j = 0 ; j < tableMap[i].length ; j++ )
145			{
146				var cell = tableMap[i][j] ;
147				var width = FCKDragTableHandler._GetCellWidth( table, cell ) ;
148				var colSpan = isNaN( cell.colSpan) ? 1 : cell.colSpan ;
149				if ( colArray.length <= j )
150					colArray.push( { width : width / colSpan, colSpan : colSpan } ) ;
151				else
152				{
153					var guessItem = colArray[j] ;
154					if ( guessItem.colSpan > colSpan )
155					{
156						guessItem.width = width / colSpan ;
157						guessItem.colSpan = colSpan ;
158					}
159				}
160			}
161		}
162
163		// Find out the equivalent column index of the two cells selected for resizing.
164		colIndex = FCKDragTableHandler._GetResizeBarPosition() ;
165
166		// Note that colIndex must be at least 1 here, so it's safe to subtract 1 from it.
167		colIndex-- ;
168
169		// Modify the widths in the colArray according to the mouse coordinate delta value.
170		colArray[colIndex].width += deltaX ;
171		colArray[colIndex + 1].width -= deltaX ;
172
173		// Clear all cell widths, delete all <col> elements from the table.
174		for ( var r = 0 ; r < table.rows.length ; r++ )
175		{
176			var row = table.rows.item( r ) ;
177			for ( var c = 0 ; c < row.cells.length ; c++ )
178			{
179				var cell = row.cells.item( c ) ;
180				cell.width = "" ;
181				cell.style.width = "" ;
182			}
183		}
184		var colElements = table.getElementsByTagName( "col" ) ;
185		for ( var i = colElements.length - 1 ; i >= 0 ; i-- )
186			colElements[i].parentNode.removeChild( colElements[i] ) ;
187
188		// Set new cell widths.
189		var processedCells = [] ;
190		for ( var i = 0 ; i < tableMap.length ; i++ )
191		{
192			for ( var j = 0 ; j < tableMap[i].length ; j++ )
193			{
194				var cell = tableMap[i][j] ;
195				if ( cell._Processed )
196					continue ;
197				if ( tableMap[i][j-1] != cell )
198					cell.width = colArray[j].width ;
199				else
200					cell.width = parseInt( cell.width, 10 ) + parseInt( colArray[j].width, 10 ) ;
201				if ( tableMap[i][j+1] != cell )
202				{
203					processedCells.push( cell ) ;
204					cell._Processed = true ;
205				}
206			}
207		}
208		for ( var i = 0 ; i < processedCells.length ; i++ )
209		{
210			if ( FCKBrowserInfo.IsIE )
211				processedCells[i].removeAttribute( '_Processed' ) ;
212			else
213				delete processedCells[i]._Processed ;
214		}
215
216		FCKDragTableHandler._LastX = null ;
217	},
218	"_ResizeBarMouseMoveListener" : function( evt )
219	{
220		if ( ! evt )
221			evt = window.event ;
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 ( node.ownerDocument == document )
311		{
312			var offset = FCKTools.GetDocumentPosition( window, FCK.EditingArea.IFrame ) ;
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 ( node.ownerDocument == FCK.EditorDocument )
375		{
376			var offset = FCKTools.GetDocumentPosition( window, FCK.EditingArea.IFrame ) ;
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 = document.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			document.body.appendChild( paddingBar ) ;
404			FCKTools.AddEventListener( paddingBar, "mousemove", this._ResizeBarMouseMoveListener ) ;
405			FCKTools.AddEventListener( paddingBar, "mousedown", this._ResizeBarMouseDownListener ) ;
406			FCKTools.AddEventListener( document, "mouseup", this._ResizeBarMouseUpListener ) ;
407			FCKTools.AddEventListener( FCK.EditorDocument, "mouseup", this._ResizeBarMouseUpListener ) ;
408
409			// IE doesn't let the tranparent part of the padding block to receive mouse events unless there's something inside.
410			// So we need to create a spacer image to fill the block up.
411			var filler = document.createElement( "img" ) ;
412			filler.border = 0 ;
413			filler.src = FCKConfig.BasePath + "images/spacer.gif" ;
414			filler.style.position = "absolute" ;
415			paddingBar.appendChild( filler ) ;
416
417			// Disable drag and drop, and selection for the filler image.
418			var disabledListener = function( evt )
419			{
420				if ( ! evt )
421					evt = window.event ;
422				if ( evt.preventDefault )
423					evt.preventDefault() ;
424				else
425					evt.returnValue = false ;
426			}
427			FCKTools.AddEventListener( filler, "dragstart", disabledListener ) ;
428			FCKTools.AddEventListener( filler, "selectstart", disabledListener ) ;
429		}
430
431		var paddingBar = this._ResizeBar ;
432		var offset = FCKTools.GetDocumentPosition( window, FCK.EditingArea.IFrame ) ;
433		var tablePos = FCKTools.GetWindowPosition( w, table ) ;
434		var barHeight = table.offsetHeight ;
435		var barTop = offset.y + tablePos.y ;
436		// Do not let the resize bar intrude into the toolbar area.
437		if ( tablePos.y < 0 )
438		{
439			barHeight += tablePos.y ;
440			barTop -= tablePos.y ;
441		}
442		var bw = parseInt( table.border, 10 ) ;
443		if ( isNaN( bw ) )
444			bw = 0 ;
445		var cs = parseInt( table.cellSpacing, 10 ) ;
446		if ( isNaN( cs ) )
447			cs = 0 ;
448		var barWidth = Math.max( bw+100, cs+100 ) ;
449		var paddingStyles =
450		{
451			'top'		: barTop + 'px',
452			'height'	: barHeight + 'px',
453			'width'		: barWidth + 'px',
454			'left'		: ( offset.x + mouse.x + FCKTools.GetScrollPosition( w ).X - barWidth / 2 ) + 'px'
455		} ;
456		if ( FCKBrowserInfo.IsIE )
457			paddingBar.filters.item("DXImageTransform.Microsoft.Alpha").opacity = 10 ;
458		else
459			paddingStyles.opacity = 0.1 ;
460
461		FCKDomTools.SetElementStyles( paddingBar, paddingStyles ) ;
462		var filler = paddingBar.getElementsByTagName( "img" )[0] ;
463
464		FCKDomTools.SetElementStyles( filler,
465			{
466				width	: paddingBar.offsetWidth + 'px',
467				height	: barHeight + 'px'
468			} ) ;
469
470		barWidth = Math.max( bw, cs, 3 ) ;
471		var visibleBar = null ;
472		if ( paddingBar.getElementsByTagName( "div" ).length < 1 )
473		{
474			visibleBar = document.createElement( "div" ) ;
475			paddingBar.appendChild( visibleBar ) ;
476		}
477		else
478			visibleBar = paddingBar.getElementsByTagName( "div" )[0] ;
479
480		FCKDomTools.SetElementStyles( visibleBar,
481			{
482				position		: 'absolute',
483				backgroundColor	: 'blue',
484				width			: barWidth + 'px',
485				height			: barHeight + 'px',
486				left			: '50px',
487				top				: '0px'
488			} ) ;
489	},
490	"_HideResizeBar" : function()
491	{
492		if ( this._ResizeBar )
493			// IE bug: display : none does not hide the resize bar for some reason.
494			// so set the position to somewhere invisible.
495			FCKDomTools.SetElementStyles( this._ResizeBar,
496				{
497					top		: '-100000px',
498					left	: '-100000px'
499				} ) ;
500	}
501};
502
503FCK.Events.AttachEvent( "OnMouseMove", FCKDragTableHandler.MouseMoveListener ) ;
504