<!--[if IE]><meta http-equiv="X-UA-Compatible" content="IE=5,IE=9" ><![endif]-->
<!--
  Copyright (c) 2006-2013, JGraph Ltd
  
  Touch example for mxGraph. This example demonstrates handling of touch,
  mouse and pointer events.
-->
<!DOCTYPE html>
<html>
<head>
	<title>Touch example for mxGraph</title>
	<meta name="viewport" content="width=device-width, initial-scale=1, minimum-scale=1, maximum-scale=1, user-scalabale=no">

	<!-- Increases size of popup menu entries. See menustyle.html for more styling options. -->
	<style type="text/css">
		body div.mxPopupMenu {
			position: absolute;
			padding: 3px;
		}
		body table.mxPopupMenu {
			border-collapse: collapse;
			margin: 0px;
		}
		body tr.mxPopupMenuItem {
			cursor: default;
		}
		body td.mxPopupMenuItem {
			padding: 10px 60px 10px 30px;
			font-family: Arial;
			font-size: 9pt;
		}
		body td.mxPopupMenuIcon {
			padding: 0px;
		}
		table.mxPopupMenu hr {
			border-top: solid 1px #cccccc;
		}
		table.mxPopupMenu tr {
			font-size: 4pt;
		}
	</style>

	<!-- Sets the basepath for the library if not in same directory -->
	<script type="text/javascript">
		mxBasePath = '../src';
	</script>

	<!-- Loads and initializes the library -->
	<script type="text/javascript" src="../src/js/mxClient.js"></script>

	<!-- Example code -->
	<script type="text/javascript">
		// Program starts here. Creates a sample graph in the
		// DOM node with the specified ID. This function is invoked
		// from the onLoad event handler of the document (see below).
		function main(container)
		{
			// Checks if the browser is supported
			if (!mxClient.isBrowserSupported())
			{
				// Displays an error message if the browser is not supported.
				mxUtils.error('Browser is not supported!', 200, false);
			}
			else
			{
				// To detect if touch events are actually supported, the following condition is recommended:
				// mxClient.IS_TOUCH || navigator.maxTouchPoints > 0 || navigator.msMaxTouchPoints > 0
				
				// Disables built-in text selection and context menu while not editing text
				var textEditing =  mxUtils.bind(this, function(evt)
				{
					return graph.isEditing();
				});

				container.onselectstart = textEditing;
				container.onmousedown = textEditing;

				if (mxClient.IS_IE && (typeof(document.documentMode) === 'undefined' || document.documentMode < 9))
				{
					mxEvent.addListener(container, 'contextmenu', textEditing);
				}
				else
				{
					container.oncontextmenu = textEditing;
				}

				// Creates the graph inside the given container
				var graph = new mxGraph(container);
				graph.centerZoom = false;
				graph.setConnectable(true);
				graph.setPanning(true);
				
				// Creates rubberband selection
			    var rubberband = new mxRubberband(graph);
				
				graph.popupMenuHandler.autoExpand = true;
				
				graph.popupMenuHandler.isSelectOnPopup = function(me)
				{
					return mxEvent.isMouseEvent(me.getEvent());
				};
				
			    // Installs context menu
				graph.popupMenuHandler.factoryMethod = function(menu, cell, evt)
				{
					menu.addItem('Item 1', null, function()
				    {
						alert('Item 1');
				    });

					menu.addSeparator();
					
					var submenu1 = menu.addItem('Submenu 1', null, null);
					
					menu.addItem('Subitem 1', null, function()
				    {
						alert('Subitem 1');
				    }, submenu1);
					menu.addItem('Subitem 1', null, function()
				    {
						alert('Subitem 2');
				    }, submenu1);
				};

				// Context menu trigger implementation depending on current selection state
				// combined with support for normal popup trigger.
				var cellSelected = false;
				var selectionEmpty = false;
				var menuShowing = false;
				
				graph.fireMouseEvent = function(evtName, me, sender)
				{
					if (evtName == mxEvent.MOUSE_DOWN)
					{
						// For hit detection on edges
						me = this.updateMouseEvent(me);
						
						cellSelected = this.isCellSelected(me.getCell());
						selectionEmpty = this.isSelectionEmpty();
						menuShowing = graph.popupMenuHandler.isMenuShowing();
					}
					
					mxGraph.prototype.fireMouseEvent.apply(this, arguments);
				};
				
				// Shows popup menu if cell was selected or selection was empty and background was clicked
				graph.popupMenuHandler.mouseUp = function(sender, me)
				{
					this.popupTrigger = !graph.isEditing() && (this.popupTrigger || (!menuShowing &&
							!graph.isEditing() && !mxEvent.isMouseEvent(me.getEvent()) &&
							((selectionEmpty && me.getCell() == null && graph.isSelectionEmpty()) ||
							(cellSelected && graph.isCellSelected(me.getCell())))));
					mxPopupMenuHandler.prototype.mouseUp.apply(this, arguments);
				};

				// Tap and hold on background starts rubberband for multiple selected
				// cells the cell associated with the event is deselected
				graph.addListener(mxEvent.TAP_AND_HOLD, function(sender, evt)
				{
					if (!mxEvent.isMultiTouchEvent(evt))
					{
						var me = evt.getProperty('event');
						var cell = evt.getProperty('cell');
						
						if (cell == null)
						{
							var pt = mxUtils.convertPoint(this.container,
									mxEvent.getClientX(me), mxEvent.getClientY(me));
							rubberband.start(pt.x, pt.y);
						}
						else if (graph.getSelectionCount() > 1 && graph.isCellSelected(cell))
						{
							graph.removeSelectionCell(cell);
						}
						
						// Blocks further processing of the event
						evt.consume();
					}
				});
				
				// Adds mouse wheel handling for zoom
				mxEvent.addMouseWheelListener(function(evt, up)
				{
					if (up)
					{
						graph.zoomIn();
					}
					else
					{
						graph.zoomOut();
					}

					mxEvent.consume(evt);
				});

				// Gets the default parent for inserting new cells. This
				// is normally the first child of the root (ie. layer 0).
				var parent = graph.getDefaultParent();
								
				// Adds cells to the model in a single step
				graph.getModel().beginUpdate();
				try
				{
					var v1 = graph.insertVertex(parent, null, 'Hello,', 20, 20, 80, 30);
					var v2 = graph.insertVertex(parent, null, 'World!', 200, 150, 80, 30);
					var e1 = graph.insertEdge(parent, null, '', v1, v2);
				}
				finally
				{
					// Updates the display
					graph.getModel().endUpdate();
				}

				// Disables new connections via "hotspot"
				graph.connectionHandler.marker.isEnabled = function()
				{
					return this.graph.connectionHandler.first != null;
				};

				// Adds custom hit detection if native hit detection found no cell
				graph.updateMouseEvent = function(me)
				{
					var me = mxGraph.prototype.updateMouseEvent.apply(this, arguments);

					if (me.getState() == null)
					{
						var cell = this.getCellAt(me.graphX, me.graphY);

						if (cell != null && this.isSwimlane(cell) && this.hitsSwimlaneContent(cell, me.graphX, me.graphY))
						{
							cell = null;
						}
						else
						{
							me.state = this.view.getState(cell);
							
							if (me.state != null && me.state.shape != null)
							{
								this.container.style.cursor = me.state.shape.node.style.cursor;
							}
						}
					}
					
					if (me.getState() == null)
					{
						this.container.style.cursor = 'default';
					}
					
					return me;
				};
			}
		};
		
		(function()
		{
			// Enables rotation handle
			mxVertexHandler.prototype.rotationEnabled = true;
			
			// Enables managing of sizers
			mxVertexHandler.prototype.manageSizers = true;
			
			// Enables live preview
			mxVertexHandler.prototype.livePreview = true;
	
			// Sets constants for touch style
			mxConstants.HANDLE_SIZE = 16;
			mxConstants.LABEL_HANDLE_SIZE = 7;
	
			// Larger tolerance and grid for real touch devices
			if (mxClient.IS_TOUCH || navigator.maxTouchPoints > 0 || navigator.msMaxTouchPoints > 0)
			{
				mxShape.prototype.svgStrokeTolerance = 18;
				mxVertexHandler.prototype.tolerance = 12;
				mxEdgeHandler.prototype.tolerance = 12;
				mxGraph.prototype.tolerance = 12;
			}
			
			// One finger pans (no rubberband selection) must start regardless of mouse button
			mxPanningHandler.prototype.isPanningTrigger = function(me)
			{
				var evt = me.getEvent();
				
			 	return (me.getState() == null && !mxEvent.isMouseEvent(evt)) ||
			 		(mxEvent.isPopupTrigger(evt) && (me.getState() == null ||
			 		mxEvent.isControlDown(evt) || mxEvent.isShiftDown(evt)));
			};
	
			// Don't clear selection if multiple cells selected
			var graphHandlerMouseDown = mxGraphHandler.prototype.mouseDown;
			mxGraphHandler.prototype.mouseDown = function(sender, me)
			{
				graphHandlerMouseDown.apply(this, arguments);
	
				if (this.graph.isCellSelected(me.getCell()) && this.graph.getSelectionCount() > 1)
				{
					this.delayedSelection = false;
				}
			};
	
			// On connect the target is selected and we clone the cell of the preview edge for insert
			mxConnectionHandler.prototype.selectCells = function(edge, target)
			{
				if (target != null)
				{
					this.graph.setSelectionCell(target);
				}
				else
				{
					this.graph.setSelectionCell(edge);
				}
			};
	
			// Overrides double click handling to use the tolerance
			var graphDblClick = mxGraph.prototype.dblClick;
			mxGraph.prototype.dblClick = function(evt, cell)
			{
				if (cell == null)
				{
					var pt = mxUtils.convertPoint(this.container,
						mxEvent.getClientX(evt), mxEvent.getClientY(evt));
					cell = this.getCellAt(pt.x, pt.y);
				}
	
				graphDblClick.call(this, evt, cell);
			};
	
			// Rounded edge and vertex handles
			var touchHandle = new mxImage('images/handle-main.png', 17, 17);
			mxVertexHandler.prototype.handleImage = touchHandle;
			mxEdgeHandler.prototype.handleImage = touchHandle;
			mxOutline.prototype.sizerImage = touchHandle;
			
			// Pre-fetches touch handle
			new Image().src = touchHandle.src;
	
			// Adds connect icon to selected vertex
			var connectorSrc = 'images/handle-connect.png';
	
			var vertexHandlerInit = mxVertexHandler.prototype.init;
			mxVertexHandler.prototype.init = function()
			{
				// TODO: Use 4 sizers, move outside of shape
				//this.singleSizer = this.state.width < 30 && this.state.height < 30;
				vertexHandlerInit.apply(this, arguments);
	
				// Only show connector image on one cell and do not show on containers
				if (this.graph.connectionHandler.isEnabled() &&
					this.graph.isCellConnectable(this.state.cell) &&
					this.graph.getSelectionCount() == 1)
				{
					this.connectorImg = mxUtils.createImage(connectorSrc);
					this.connectorImg.style.cursor = 'pointer';
					this.connectorImg.style.width = '29px';
					this.connectorImg.style.height = '29px';
					this.connectorImg.style.position = 'absolute';
					
					if (!mxClient.IS_TOUCH)
					{
						this.connectorImg.setAttribute('title', mxResources.get('connect'));
						mxEvent.redirectMouseEvents(this.connectorImg, this.graph, this.state);
					}
	
					// Starts connecting on touch/mouse down
					mxEvent.addGestureListeners(this.connectorImg,
						mxUtils.bind(this, function(evt)
						{
							this.graph.popupMenuHandler.hideMenu();
							this.graph.stopEditing(false);
							
							var pt = mxUtils.convertPoint(this.graph.container,
									mxEvent.getClientX(evt), mxEvent.getClientY(evt));
							this.graph.connectionHandler.start(this.state, pt.x, pt.y);
							this.graph.isMouseDown = true;
							this.graph.isMouseTrigger = mxEvent.isMouseEvent(evt);
							mxEvent.consume(evt);
						})
					);
	
					this.graph.container.appendChild(this.connectorImg);
				}
	
				this.redrawHandles();
			};
			
			var vertexHandlerHideSizers = mxVertexHandler.prototype.hideSizers;
			mxVertexHandler.prototype.hideSizers = function()
			{
				vertexHandlerHideSizers.apply(this, arguments);
				
				if (this.connectorImg != null)
				{
					this.connectorImg.style.visibility = 'hidden';
				}
			};
			
			var vertexHandlerReset = mxVertexHandler.prototype.reset;
			mxVertexHandler.prototype.reset = function()
			{
				vertexHandlerReset.apply(this, arguments);
				
				if (this.connectorImg != null)
				{
					this.connectorImg.style.visibility = '';
				}
			};
			
			var vertexHandlerRedrawHandles = mxVertexHandler.prototype.redrawHandles;
			mxVertexHandler.prototype.redrawHandles = function()
			{
				vertexHandlerRedrawHandles.apply(this);
	
				if (this.state != null && this.connectorImg != null)
				{
					var pt = new mxPoint();
					var s = this.state;
					
					// Top right for single-sizer
					if (mxVertexHandler.prototype.singleSizer)
					{
						pt.x = s.x + s.width - this.connectorImg.offsetWidth / 2;
						pt.y = s.y - this.connectorImg.offsetHeight / 2;
					}
					else
					{
						pt.x = s.x + s.width + mxConstants.HANDLE_SIZE / 2 + 4 + this.connectorImg.offsetWidth / 2;
						pt.y = s.y + s.height / 2;
					}
					
					var alpha = mxUtils.toRadians(mxUtils.getValue(s.style, mxConstants.STYLE_ROTATION, 0));
					
					if (alpha != 0)
					{
						var cos = Math.cos(alpha);
						var sin = Math.sin(alpha);
						
						var ct = new mxPoint(s.getCenterX(), s.getCenterY());
						pt = mxUtils.getRotatedPoint(pt, cos, sin, ct);
					}
					
					this.connectorImg.style.left = (pt.x - this.connectorImg.offsetWidth / 2) + 'px';
					this.connectorImg.style.top = (pt.y - this.connectorImg.offsetHeight / 2) + 'px';
				}
			};
			
			var vertexHandlerDestroy = mxVertexHandler.prototype.destroy;
			mxVertexHandler.prototype.destroy = function(sender, me)
			{
				vertexHandlerDestroy.apply(this, arguments);
	
				if (this.connectorImg != null)
				{
					this.connectorImg.parentNode.removeChild(this.connectorImg);
					this.connectorImg = null;
				}
			};
			
			// Pre-fetches touch connector
			new Image().src = connectorSrc;
		})();
	</script>
</head>

<!-- Page passes the container for the graph to the program -->
<body onload="main(document.getElementById('graphContainer'))">

	<!-- Creates a container for the graph with a grid wallpaper -->
	<div id="graphContainer"
		style="position:relative;overflow:hidden;width:640px;height:480px;background:url('editors/images/grid.gif');cursor:default;">
	</div>
</body>
</html>