763 lines
19 KiB
JavaScript
763 lines
19 KiB
JavaScript
/**
|
|
* Copyright (c) 2006-2015, JGraph Ltd
|
|
* Copyright (c) 2006-2015, Gaudenz Alder
|
|
*/
|
|
/**
|
|
* Class: mxOutline
|
|
*
|
|
* Implements an outline (aka overview) for a graph. Set <updateOnPan> to true
|
|
* to enable updates while the source graph is panning.
|
|
*
|
|
* Example:
|
|
*
|
|
* (code)
|
|
* var outline = new mxOutline(graph, div);
|
|
* (end)
|
|
*
|
|
* If an outline is used in an <mxWindow> in IE8 standards mode, the following
|
|
* code makes sure that the shadow filter is not inherited and that any
|
|
* transparent elements in the graph do not show the page background, but the
|
|
* background of the graph container.
|
|
*
|
|
* (code)
|
|
* if (document.documentMode == 8)
|
|
* {
|
|
* container.style.filter = 'progid:DXImageTransform.Microsoft.alpha(opacity=100)';
|
|
* }
|
|
* (end)
|
|
*
|
|
* To move the graph to the top, left corner the following code can be used.
|
|
*
|
|
* (code)
|
|
* var scale = graph.view.scale;
|
|
* var bounds = graph.getGraphBounds();
|
|
* graph.view.setTranslate(-bounds.x / scale, -bounds.y / scale);
|
|
* (end)
|
|
*
|
|
* To toggle the suspended mode, the following can be used.
|
|
*
|
|
* (code)
|
|
* outline.suspended = !outln.suspended;
|
|
* if (!outline.suspended)
|
|
* {
|
|
* outline.update(true);
|
|
* }
|
|
* (end)
|
|
*
|
|
* Constructor: mxOutline
|
|
*
|
|
* Constructs a new outline for the specified graph inside the given
|
|
* container.
|
|
*
|
|
* Parameters:
|
|
*
|
|
* source - <mxGraph> to create the outline for.
|
|
* container - DOM node that will contain the outline.
|
|
*/
|
|
function mxOutline(source, container)
|
|
{
|
|
this.source = source;
|
|
|
|
if (container != null)
|
|
{
|
|
this.init(container);
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Function: source
|
|
*
|
|
* Reference to the source <mxGraph>.
|
|
*/
|
|
mxOutline.prototype.source = null;
|
|
|
|
/**
|
|
* Function: outline
|
|
*
|
|
* Reference to the <mxGraph> that renders the outline.
|
|
*/
|
|
mxOutline.prototype.outline = null;
|
|
|
|
/**
|
|
* Function: graphRenderHint
|
|
*
|
|
* Renderhint to be used for the outline graph. Default is faster.
|
|
*/
|
|
mxOutline.prototype.graphRenderHint = mxConstants.RENDERING_HINT_FASTER;
|
|
|
|
/**
|
|
* Variable: enabled
|
|
*
|
|
* Specifies if events are handled. Default is true.
|
|
*/
|
|
mxOutline.prototype.enabled = true;
|
|
|
|
/**
|
|
* Variable: showViewport
|
|
*
|
|
* Specifies a viewport rectangle should be shown. Default is true.
|
|
*/
|
|
mxOutline.prototype.showViewport = true;
|
|
|
|
/**
|
|
* Variable: border
|
|
*
|
|
* Border to be added at the bottom and right. Default is 10.
|
|
*/
|
|
mxOutline.prototype.border = 10;
|
|
|
|
/**
|
|
* Variable: enabled
|
|
*
|
|
* Specifies the size of the sizer handler. Default is 8.
|
|
*/
|
|
mxOutline.prototype.sizerSize = 8;
|
|
|
|
/**
|
|
* Variable: labelsVisible
|
|
*
|
|
* Specifies if labels should be visible in the outline. Default is false.
|
|
*/
|
|
mxOutline.prototype.labelsVisible = false;
|
|
|
|
/**
|
|
* Variable: updateOnPan
|
|
*
|
|
* Specifies if <update> should be called for <mxEvent.PAN> in the source
|
|
* graph. Default is false.
|
|
*/
|
|
mxOutline.prototype.updateOnPan = false;
|
|
|
|
/**
|
|
* Variable: sizerImage
|
|
*
|
|
* Optional <mxImage> to be used for the sizer. Default is null.
|
|
*/
|
|
mxOutline.prototype.sizerImage = null;
|
|
|
|
/**
|
|
* Variable: minScale
|
|
*
|
|
* Minimum scale to be used. Default is 0.0001.
|
|
*/
|
|
mxOutline.prototype.minScale = 0.0001;
|
|
|
|
/**
|
|
* Variable: suspended
|
|
*
|
|
* Optional boolean flag to suspend updates. Default is false. This flag will
|
|
* also suspend repaints of the outline. To toggle this switch, use the
|
|
* following code.
|
|
*
|
|
* (code)
|
|
* nav.suspended = !nav.suspended;
|
|
*
|
|
* if (!nav.suspended)
|
|
* {
|
|
* nav.update(true);
|
|
* }
|
|
* (end)
|
|
*/
|
|
mxOutline.prototype.suspended = false;
|
|
|
|
/**
|
|
* Variable: forceVmlHandles
|
|
*
|
|
* Specifies if VML should be used to render the handles in this control. This
|
|
* is true for IE8 standards mode and false for all other browsers and modes.
|
|
* This is a workaround for rendering issues of HTML elements over elements
|
|
* with filters in IE 8 standards mode.
|
|
*/
|
|
mxOutline.prototype.forceVmlHandles = document.documentMode == 8;
|
|
|
|
/**
|
|
* Function: createGraph
|
|
*
|
|
* Creates the <mxGraph> used in the outline.
|
|
*/
|
|
mxOutline.prototype.createGraph = function(container)
|
|
{
|
|
var graph = new mxGraph(container, this.source.getModel(), this.graphRenderHint, this.source.getStylesheet());
|
|
graph.foldingEnabled = false;
|
|
graph.autoScroll = false;
|
|
|
|
return graph;
|
|
};
|
|
|
|
/**
|
|
* Function: init
|
|
*
|
|
* Initializes the outline inside the given container.
|
|
*/
|
|
mxOutline.prototype.init = function(container)
|
|
{
|
|
this.outline = this.createGraph(container);
|
|
|
|
// Do not repaint when suspended
|
|
var outlineGraphModelChanged = this.outline.graphModelChanged;
|
|
this.outline.graphModelChanged = mxUtils.bind(this, function(changes)
|
|
{
|
|
if (!this.suspended && this.outline != null)
|
|
{
|
|
outlineGraphModelChanged.apply(this.outline, arguments);
|
|
}
|
|
});
|
|
|
|
// Enables faster painting in SVG
|
|
if (mxClient.IS_SVG)
|
|
{
|
|
var node = this.outline.getView().getCanvas().parentNode;
|
|
node.setAttribute('shape-rendering', 'optimizeSpeed');
|
|
node.setAttribute('image-rendering', 'optimizeSpeed');
|
|
}
|
|
|
|
// Hides cursors and labels
|
|
this.outline.labelsVisible = this.labelsVisible;
|
|
this.outline.setEnabled(false);
|
|
|
|
this.updateHandler = mxUtils.bind(this, function(sender, evt)
|
|
{
|
|
if (!this.suspended && !this.active)
|
|
{
|
|
this.update();
|
|
}
|
|
});
|
|
|
|
// Updates the scale of the outline after a change of the main graph
|
|
this.source.getModel().addListener(mxEvent.CHANGE, this.updateHandler);
|
|
this.outline.addMouseListener(this);
|
|
|
|
// Adds listeners to keep the outline in sync with the source graph
|
|
var view = this.source.getView();
|
|
view.addListener(mxEvent.SCALE, this.updateHandler);
|
|
view.addListener(mxEvent.TRANSLATE, this.updateHandler);
|
|
view.addListener(mxEvent.SCALE_AND_TRANSLATE, this.updateHandler);
|
|
view.addListener(mxEvent.DOWN, this.updateHandler);
|
|
view.addListener(mxEvent.UP, this.updateHandler);
|
|
|
|
// Updates blue rectangle on scroll
|
|
mxEvent.addListener(this.source.container, 'scroll', this.updateHandler);
|
|
|
|
this.panHandler = mxUtils.bind(this, function(sender)
|
|
{
|
|
if (this.updateOnPan)
|
|
{
|
|
this.updateHandler.apply(this, arguments);
|
|
}
|
|
});
|
|
this.source.addListener(mxEvent.PAN, this.panHandler);
|
|
|
|
// Refreshes the graph in the outline after a refresh of the main graph
|
|
this.refreshHandler = mxUtils.bind(this, function(sender)
|
|
{
|
|
this.outline.setStylesheet(this.source.getStylesheet());
|
|
this.outline.refresh();
|
|
});
|
|
this.source.addListener(mxEvent.REFRESH, this.refreshHandler);
|
|
|
|
// Creates the blue rectangle for the viewport
|
|
this.bounds = new mxRectangle(0, 0, 0, 0);
|
|
this.selectionBorder = new mxRectangleShape(this.bounds, null,
|
|
mxConstants.OUTLINE_COLOR, mxConstants.OUTLINE_STROKEWIDTH);
|
|
this.selectionBorder.dialect = this.outline.dialect;
|
|
|
|
if (this.forceVmlHandles)
|
|
{
|
|
this.selectionBorder.isHtmlAllowed = function()
|
|
{
|
|
return false;
|
|
};
|
|
}
|
|
|
|
this.selectionBorder.init(this.outline.getView().getOverlayPane());
|
|
|
|
// Handles event by catching the initial pointer start and then listening to the
|
|
// complete gesture on the event target. This is needed because all the events
|
|
// are routed via the initial element even if that element is removed from the
|
|
// DOM, which happens when we repaint the selection border and zoom handles.
|
|
var handler = mxUtils.bind(this, function(evt)
|
|
{
|
|
var t = mxEvent.getSource(evt);
|
|
|
|
var redirect = mxUtils.bind(this, function(evt)
|
|
{
|
|
this.outline.fireMouseEvent(mxEvent.MOUSE_MOVE, new mxMouseEvent(evt));
|
|
});
|
|
|
|
var redirect2 = mxUtils.bind(this, function(evt)
|
|
{
|
|
mxEvent.removeGestureListeners(t, null, redirect, redirect2);
|
|
this.outline.fireMouseEvent(mxEvent.MOUSE_UP, new mxMouseEvent(evt));
|
|
});
|
|
|
|
mxEvent.addGestureListeners(t, null, redirect, redirect2);
|
|
this.outline.fireMouseEvent(mxEvent.MOUSE_DOWN, new mxMouseEvent(evt));
|
|
});
|
|
|
|
mxEvent.addGestureListeners(this.selectionBorder.node, handler);
|
|
|
|
// Creates a small blue rectangle for sizing (sizer handle)
|
|
this.sizer = this.createSizer();
|
|
|
|
if (this.forceVmlHandles)
|
|
{
|
|
this.sizer.isHtmlAllowed = function()
|
|
{
|
|
return false;
|
|
};
|
|
}
|
|
|
|
this.sizer.init(this.outline.getView().getOverlayPane());
|
|
|
|
if (this.enabled)
|
|
{
|
|
this.sizer.node.style.cursor = 'nwse-resize';
|
|
}
|
|
|
|
mxEvent.addGestureListeners(this.sizer.node, handler);
|
|
|
|
this.selectionBorder.node.style.display = (this.showViewport) ? '' : 'none';
|
|
this.sizer.node.style.display = this.selectionBorder.node.style.display;
|
|
this.selectionBorder.node.style.cursor = 'move';
|
|
|
|
this.update(false);
|
|
};
|
|
|
|
/**
|
|
* Function: isEnabled
|
|
*
|
|
* Returns true if events are handled. This implementation
|
|
* returns <enabled>.
|
|
*/
|
|
mxOutline.prototype.isEnabled = function()
|
|
{
|
|
return this.enabled;
|
|
};
|
|
|
|
/**
|
|
* Function: setEnabled
|
|
*
|
|
* Enables or disables event handling. This implementation
|
|
* updates <enabled>.
|
|
*
|
|
* Parameters:
|
|
*
|
|
* value - Boolean that specifies the new enabled state.
|
|
*/
|
|
mxOutline.prototype.setEnabled = function(value)
|
|
{
|
|
this.enabled = value;
|
|
};
|
|
|
|
/**
|
|
* Function: setZoomEnabled
|
|
*
|
|
* Enables or disables the zoom handling by showing or hiding the respective
|
|
* handle.
|
|
*
|
|
* Parameters:
|
|
*
|
|
* value - Boolean that specifies the new enabled state.
|
|
*/
|
|
mxOutline.prototype.setZoomEnabled = function(value)
|
|
{
|
|
this.sizer.node.style.visibility = (value) ? 'visible' : 'hidden';
|
|
};
|
|
|
|
/**
|
|
* Function: refresh
|
|
*
|
|
* Invokes <update> and revalidate the outline. This method is deprecated.
|
|
*/
|
|
mxOutline.prototype.refresh = function()
|
|
{
|
|
this.update(true);
|
|
};
|
|
|
|
/**
|
|
* Function: createSizer
|
|
*
|
|
* Creates the shape used as the sizer.
|
|
*/
|
|
mxOutline.prototype.createSizer = function()
|
|
{
|
|
if (this.sizerImage != null)
|
|
{
|
|
var sizer = new mxImageShape(new mxRectangle(0, 0, this.sizerImage.width, this.sizerImage.height), this.sizerImage.src);
|
|
sizer.dialect = this.outline.dialect;
|
|
|
|
return sizer;
|
|
}
|
|
else
|
|
{
|
|
var sizer = new mxRectangleShape(new mxRectangle(0, 0, this.sizerSize, this.sizerSize),
|
|
mxConstants.OUTLINE_HANDLE_FILLCOLOR, mxConstants.OUTLINE_HANDLE_STROKECOLOR);
|
|
sizer.dialect = this.outline.dialect;
|
|
|
|
return sizer;
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Function: getSourceContainerSize
|
|
*
|
|
* Returns the size of the source container.
|
|
*/
|
|
mxOutline.prototype.getSourceContainerSize = function()
|
|
{
|
|
return new mxRectangle(0, 0, this.source.container.scrollWidth, this.source.container.scrollHeight);
|
|
};
|
|
|
|
/**
|
|
* Function: getOutlineOffset
|
|
*
|
|
* Returns the offset for drawing the outline graph.
|
|
*/
|
|
mxOutline.prototype.getOutlineOffset = function(scale)
|
|
{
|
|
return null;
|
|
};
|
|
|
|
/**
|
|
* Function: getSourceGraphBounds
|
|
*
|
|
* Returns the graph bound boxing of the source.
|
|
*/
|
|
mxOutline.prototype.getSourceGraphBounds = function()
|
|
{
|
|
return this.source.getGraphBounds();
|
|
};
|
|
|
|
/**
|
|
* Function: update
|
|
*
|
|
* Updates the outline.
|
|
*/
|
|
mxOutline.prototype.update = function(revalidate)
|
|
{
|
|
if (this.source != null && this.source.container != null &&
|
|
this.outline != null && this.outline.container != null)
|
|
{
|
|
var sourceScale = this.source.view.scale;
|
|
var scaledGraphBounds = this.getSourceGraphBounds();
|
|
var unscaledGraphBounds = new mxRectangle(scaledGraphBounds.x / sourceScale + this.source.panDx,
|
|
scaledGraphBounds.y / sourceScale + this.source.panDy, scaledGraphBounds.width / sourceScale,
|
|
scaledGraphBounds.height / sourceScale);
|
|
|
|
var unscaledFinderBounds = new mxRectangle(0, 0,
|
|
this.source.container.clientWidth / sourceScale,
|
|
this.source.container.clientHeight / sourceScale);
|
|
|
|
var union = unscaledGraphBounds.clone();
|
|
union.add(unscaledFinderBounds);
|
|
|
|
// Zooms to the scrollable area if that is bigger than the graph
|
|
var size = this.getSourceContainerSize();
|
|
var completeWidth = Math.max(size.width / sourceScale, union.width);
|
|
var completeHeight = Math.max(size.height / sourceScale, union.height);
|
|
|
|
var availableWidth = Math.max(0, this.outline.container.clientWidth - this.border);
|
|
var availableHeight = Math.max(0, this.outline.container.clientHeight - this.border);
|
|
|
|
var outlineScale = Math.min(availableWidth / completeWidth, availableHeight / completeHeight);
|
|
var scale = (isNaN(outlineScale)) ? this.minScale : Math.max(this.minScale, outlineScale);
|
|
|
|
if (scale > 0)
|
|
{
|
|
if (this.outline.getView().scale != scale)
|
|
{
|
|
this.outline.getView().scale = scale;
|
|
revalidate = true;
|
|
}
|
|
|
|
var navView = this.outline.getView();
|
|
|
|
if (navView.currentRoot != this.source.getView().currentRoot)
|
|
{
|
|
navView.setCurrentRoot(this.source.getView().currentRoot);
|
|
}
|
|
|
|
var t = this.source.view.translate;
|
|
var tx = t.x + this.source.panDx;
|
|
var ty = t.y + this.source.panDy;
|
|
|
|
var off = this.getOutlineOffset(scale);
|
|
|
|
if (off != null)
|
|
{
|
|
tx += off.x;
|
|
ty += off.y;
|
|
}
|
|
|
|
if (unscaledGraphBounds.x < 0)
|
|
{
|
|
tx = tx - unscaledGraphBounds.x;
|
|
}
|
|
if (unscaledGraphBounds.y < 0)
|
|
{
|
|
ty = ty - unscaledGraphBounds.y;
|
|
}
|
|
|
|
if (navView.translate.x != tx || navView.translate.y != ty)
|
|
{
|
|
navView.translate.x = tx;
|
|
navView.translate.y = ty;
|
|
revalidate = true;
|
|
}
|
|
|
|
// Prepares local variables for computations
|
|
var t2 = navView.translate;
|
|
scale = this.source.getView().scale;
|
|
var scale2 = scale / navView.scale;
|
|
var scale3 = 1.0 / navView.scale;
|
|
var container = this.source.container;
|
|
|
|
// Updates the bounds of the viewrect in the navigation
|
|
this.bounds = new mxRectangle(
|
|
(t2.x - t.x - this.source.panDx) / scale3,
|
|
(t2.y - t.y - this.source.panDy) / scale3,
|
|
(container.clientWidth / scale2),
|
|
(container.clientHeight / scale2));
|
|
|
|
// Adds the scrollbar offset to the finder
|
|
this.bounds.x += this.source.container.scrollLeft * navView.scale / scale;
|
|
this.bounds.y += this.source.container.scrollTop * navView.scale / scale;
|
|
|
|
var b = this.selectionBorder.bounds;
|
|
|
|
if (b.x != this.bounds.x || b.y != this.bounds.y || b.width != this.bounds.width || b.height != this.bounds.height)
|
|
{
|
|
this.selectionBorder.bounds = this.bounds;
|
|
this.selectionBorder.redraw();
|
|
}
|
|
|
|
// Updates the bounds of the zoom handle at the bottom right
|
|
var b = this.sizer.bounds;
|
|
var b2 = new mxRectangle(this.bounds.x + this.bounds.width - b.width / 2,
|
|
this.bounds.y + this.bounds.height - b.height / 2, b.width, b.height);
|
|
|
|
if (b.x != b2.x || b.y != b2.y || b.width != b2.width || b.height != b2.height)
|
|
{
|
|
this.sizer.bounds = b2;
|
|
|
|
// Avoids update of visibility in redraw for VML
|
|
if (this.sizer.node.style.visibility != 'hidden')
|
|
{
|
|
this.sizer.redraw();
|
|
}
|
|
}
|
|
|
|
if (revalidate)
|
|
{
|
|
this.outline.view.revalidate();
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Function: mouseDown
|
|
*
|
|
* Handles the event by starting a translation or zoom.
|
|
*/
|
|
mxOutline.prototype.mouseDown = function(sender, me)
|
|
{
|
|
if (this.enabled && this.showViewport)
|
|
{
|
|
var tol = (!mxEvent.isMouseEvent(me.getEvent())) ? this.source.tolerance : 0;
|
|
var hit = (this.source.allowHandleBoundsCheck && (mxClient.IS_IE || tol > 0)) ?
|
|
new mxRectangle(me.getGraphX() - tol, me.getGraphY() - tol, 2 * tol, 2 * tol) : null;
|
|
this.zoom = me.isSource(this.sizer) || (hit != null && mxUtils.intersects(shape.bounds, hit));
|
|
this.startX = me.getX();
|
|
this.startY = me.getY();
|
|
this.active = true;
|
|
|
|
if (this.source.useScrollbarsForPanning && mxUtils.hasScrollbars(this.source.container))
|
|
{
|
|
this.dx0 = this.source.container.scrollLeft;
|
|
this.dy0 = this.source.container.scrollTop;
|
|
}
|
|
else
|
|
{
|
|
this.dx0 = 0;
|
|
this.dy0 = 0;
|
|
}
|
|
}
|
|
|
|
me.consume();
|
|
};
|
|
|
|
/**
|
|
* Function: mouseMove
|
|
*
|
|
* Handles the event by previewing the viewrect in <graph> and updating the
|
|
* rectangle that represents the viewrect in the outline.
|
|
*/
|
|
mxOutline.prototype.mouseMove = function(sender, me)
|
|
{
|
|
if (this.active)
|
|
{
|
|
this.selectionBorder.node.style.display = (this.showViewport) ? '' : 'none';
|
|
this.sizer.node.style.display = this.selectionBorder.node.style.display;
|
|
|
|
var delta = this.getTranslateForEvent(me);
|
|
var dx = delta.x;
|
|
var dy = delta.y;
|
|
var bounds = null;
|
|
|
|
if (!this.zoom)
|
|
{
|
|
// Previews the panning on the source graph
|
|
var scale = this.outline.getView().scale;
|
|
bounds = new mxRectangle(this.bounds.x + dx,
|
|
this.bounds.y + dy, this.bounds.width, this.bounds.height);
|
|
this.selectionBorder.bounds = bounds;
|
|
this.selectionBorder.redraw();
|
|
dx /= scale;
|
|
dx *= this.source.getView().scale;
|
|
dy /= scale;
|
|
dy *= this.source.getView().scale;
|
|
this.source.panGraph(-dx - this.dx0, -dy - this.dy0);
|
|
}
|
|
else
|
|
{
|
|
// Does *not* preview zooming on the source graph
|
|
var container = this.source.container;
|
|
var viewRatio = container.clientWidth / container.clientHeight;
|
|
dy = dx / viewRatio;
|
|
bounds = new mxRectangle(this.bounds.x,
|
|
this.bounds.y,
|
|
Math.max(1, this.bounds.width + dx),
|
|
Math.max(1, this.bounds.height + dy));
|
|
this.selectionBorder.bounds = bounds;
|
|
this.selectionBorder.redraw();
|
|
}
|
|
|
|
// Updates the zoom handle
|
|
var b = this.sizer.bounds;
|
|
this.sizer.bounds = new mxRectangle(
|
|
bounds.x + bounds.width - b.width / 2,
|
|
bounds.y + bounds.height - b.height / 2,
|
|
b.width, b.height);
|
|
|
|
// Avoids update of visibility in redraw for VML
|
|
if (this.sizer.node.style.visibility != 'hidden')
|
|
{
|
|
this.sizer.redraw();
|
|
}
|
|
|
|
me.consume();
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Function: getTranslateForEvent
|
|
*
|
|
* Gets the translate for the given mouse event. Here is an example to limit
|
|
* the outline to stay within positive coordinates:
|
|
*
|
|
* (code)
|
|
* outline.getTranslateForEvent = function(me)
|
|
* {
|
|
* var pt = new mxPoint(me.getX() - this.startX, me.getY() - this.startY);
|
|
*
|
|
* if (!this.zoom)
|
|
* {
|
|
* var tr = this.source.view.translate;
|
|
* pt.x = Math.max(tr.x * this.outline.view.scale, pt.x);
|
|
* pt.y = Math.max(tr.y * this.outline.view.scale, pt.y);
|
|
* }
|
|
*
|
|
* return pt;
|
|
* };
|
|
* (end)
|
|
*/
|
|
mxOutline.prototype.getTranslateForEvent = function(me)
|
|
{
|
|
return new mxPoint(me.getX() - this.startX, me.getY() - this.startY);
|
|
};
|
|
|
|
/**
|
|
* Function: mouseUp
|
|
*
|
|
* Handles the event by applying the translation or zoom to <graph>.
|
|
*/
|
|
mxOutline.prototype.mouseUp = function(sender, me)
|
|
{
|
|
if (this.active)
|
|
{
|
|
var delta = this.getTranslateForEvent(me);
|
|
var dx = delta.x;
|
|
var dy = delta.y;
|
|
|
|
if (Math.abs(dx) > 0 || Math.abs(dy) > 0)
|
|
{
|
|
if (!this.zoom)
|
|
{
|
|
// Applies the new translation if the source
|
|
// has no scrollbars
|
|
if (!this.source.useScrollbarsForPanning ||
|
|
!mxUtils.hasScrollbars(this.source.container))
|
|
{
|
|
this.source.panGraph(0, 0);
|
|
dx /= this.outline.getView().scale;
|
|
dy /= this.outline.getView().scale;
|
|
var t = this.source.getView().translate;
|
|
this.source.getView().setTranslate(t.x - dx, t.y - dy);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Applies the new zoom
|
|
var w = this.selectionBorder.bounds.width;
|
|
var scale = this.source.getView().scale;
|
|
this.source.zoomTo(Math.max(this.minScale, scale - (dx * scale) / w), false);
|
|
}
|
|
|
|
this.update();
|
|
me.consume();
|
|
}
|
|
|
|
// Resets the state of the handler
|
|
this.index = null;
|
|
this.active = false;
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Function: destroy
|
|
*
|
|
* Destroy this outline and removes all listeners from <source>.
|
|
*/
|
|
mxOutline.prototype.destroy = function()
|
|
{
|
|
if (this.source != null)
|
|
{
|
|
this.source.removeListener(this.panHandler);
|
|
this.source.removeListener(this.refreshHandler);
|
|
this.source.getModel().removeListener(this.updateHandler);
|
|
this.source.getView().removeListener(this.updateHandler);
|
|
mxEvent.removeListener(this.source.container, 'scroll', this.updateHandler);
|
|
this.source = null;
|
|
}
|
|
|
|
if (this.outline != null)
|
|
{
|
|
this.outline.removeMouseListener(this);
|
|
this.outline.destroy();
|
|
this.outline = null;
|
|
}
|
|
|
|
if (this.selectionBorder != null)
|
|
{
|
|
this.selectionBorder.destroy();
|
|
this.selectionBorder = null;
|
|
}
|
|
|
|
if (this.sizer != null)
|
|
{
|
|
this.sizer.destroy();
|
|
this.sizer = null;
|
|
}
|
|
};
|