4cd626ef49
Most of the DOM manipulations can be done before doing the AJAX call. This leaves just the insertion of the MathML nodes in the mrow for the AJAX callback function. Also, make the stroke-width for the connector tool default to 2.
553 lines
15 KiB
JavaScript
553 lines
15 KiB
JavaScript
/*
|
|
* ext-connector.js
|
|
*
|
|
* Licensed under the Apache License, Version 2
|
|
*
|
|
* Copyright(c) 2010 Alexis Deveria
|
|
*
|
|
*/
|
|
|
|
$(function() {
|
|
svgCanvas.addExtension("Connector", function(S) {
|
|
var svgcontent = S.svgcontent,
|
|
svgroot = S.svgroot,
|
|
getNextId = S.getNextId,
|
|
getElem = S.getElem,
|
|
addElem = S.addSvgElementFromJson,
|
|
selManager = S.selectorManager,
|
|
started = false,
|
|
start_x,
|
|
start_y,
|
|
cur_line,
|
|
start_elem,
|
|
end_elem,
|
|
connections = [],
|
|
conn_sel = ".se_connector",
|
|
se_ns,
|
|
// connect_str = "-SE_CONNECT-",
|
|
selElems = [];
|
|
|
|
var lang_list = {
|
|
"en":[
|
|
{"id": "mode_connect", "title": "Connect two objects" }
|
|
],
|
|
"fr":[
|
|
{"id": "mode_connect", "title": "Connecter deux objets"}
|
|
]
|
|
};
|
|
|
|
function showPanel(on) {
|
|
var conn_rules = $('#connector_rules');
|
|
if(!conn_rules.length) {
|
|
conn_rules = $('<style id="connector_rules"><\/style>').appendTo('head');
|
|
}
|
|
conn_rules.text(!on?"":"#tool_clone, #tool_topath, #tool_angle, #xy_panel { display: none !important; }");
|
|
$('#connector_panel').toggle(on);
|
|
}
|
|
|
|
function setPoint(elem, pos, x, y, setMid) {
|
|
var pts = elem.points;
|
|
var pt = svgroot.createSVGPoint();
|
|
pt.x = x;
|
|
pt.y = y;
|
|
if(pos === 'end') pos = pts.numberOfItems-1;
|
|
// TODO: Test for this on init, then use alt only if needed
|
|
try {
|
|
pts.replaceItem(pt, pos);
|
|
} catch(err) {
|
|
// Should only occur in FF which formats points attr as "n,n n,n", so just split
|
|
var pt_arr = elem.getAttribute("points").split(" ");
|
|
for(var i=0; i< pt_arr.length; i++) {
|
|
if(i == pos) {
|
|
pt_arr[i] = x + ',' + y;
|
|
}
|
|
}
|
|
elem.setAttribute("points",pt_arr.join(" "));
|
|
}
|
|
|
|
if(setMid) {
|
|
// Add center point
|
|
var pt_start = pts.getItem(0);
|
|
var pt_end = pts.getItem(pts.numberOfItems-1);
|
|
setPoint(elem, 1, (pt_end.x + pt_start.x)/2, (pt_end.y + pt_start.y)/2);
|
|
}
|
|
}
|
|
|
|
function findConnectors() {
|
|
var elems = selElems;
|
|
var connectors = $(svgcontent).find(conn_sel);
|
|
connections = [];
|
|
|
|
// Loop through connectors to see if one is connected to the element
|
|
connectors.each(function() {
|
|
var start = $(this).data("c_start");
|
|
var end = $(this).data("c_end");
|
|
|
|
var parts = [getElem(start), getElem(end)];
|
|
for(var i=0; i<2; i++) {
|
|
var c_elem = parts[i];
|
|
var add_this = false;
|
|
// The connected element might be part of a selected group
|
|
$(c_elem).parents().each(function() {
|
|
if($.inArray(this, elems) !== -1) {
|
|
// Pretend this element is selected
|
|
add_this = true;
|
|
}
|
|
});
|
|
|
|
if(!c_elem || !c_elem.parentNode) {
|
|
$(this).remove();
|
|
continue;
|
|
}
|
|
|
|
if($.inArray(c_elem, elems) !== -1 || add_this) {
|
|
var bb = svgCanvas.getStrokedBBox([c_elem]);
|
|
connections.push({
|
|
elem: c_elem,
|
|
connector: this,
|
|
is_start: (i === 0),
|
|
start_x: bb.x,
|
|
start_y: bb.y
|
|
});
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
function updateConnectors() {
|
|
// Updates connector lines based on selected elements
|
|
// Is not used on mousemove, as it runs getStrokedBBox every time,
|
|
// which isn't necessary there.
|
|
findConnectors();
|
|
if(connections.length) {
|
|
// Update line with element
|
|
var i = connections.length;
|
|
while(i--) {
|
|
var conn = connections[i];
|
|
var line = conn.connector;
|
|
var elem = conn.elem;
|
|
|
|
var sw = line.getAttribute('stroke-width');
|
|
var pre = conn.is_start?'start':'end';
|
|
|
|
// Update bbox for this element
|
|
var bb = svgCanvas.getStrokedBBox([elem]);
|
|
bb.x = conn.start_x;
|
|
bb.y = conn.start_y;
|
|
$(line).data(pre+'_bb', bb);
|
|
var add_offset = $(line).data(pre+'_off');
|
|
|
|
var alt_pre = conn.is_start?'end':'start';
|
|
|
|
// Get center pt of connected element
|
|
var bb2 = $(line).data(alt_pre+'_bb');
|
|
var src_x = bb2.x + bb2.width/2;
|
|
var src_y = bb2.y + bb2.height/2;
|
|
|
|
// Set point of element being moved
|
|
var pt = getBBintersect(src_x, src_y, bb, add_offset?sw:0);
|
|
setPoint(line, conn.is_start?0:'end', pt.x, pt.y, true);
|
|
|
|
// Set point of connected element
|
|
var pt2 = getBBintersect(pt.x, pt.y, $(line).data(alt_pre + '_bb'), $(line).data(alt_pre + '_off')?sw:0);
|
|
setPoint(line, conn.is_start?'end':0, pt2.x, pt2.y, true);
|
|
|
|
// Update points attribute manually for webkit
|
|
if(navigator.userAgent.indexOf('AppleWebKit') != -1) {
|
|
var pts = line.points;
|
|
var len = pts.numberOfItems;
|
|
var pt_arr = Array(len);
|
|
for(var j=0; j< len; j++) {
|
|
var pt = pts.getItem(j);
|
|
pt_arr[j] = pt.x + ',' + pt.y;
|
|
}
|
|
line.setAttribute("points",pt_arr.join(" "));
|
|
}
|
|
|
|
}
|
|
}
|
|
}
|
|
|
|
function getBBintersect(x, y, bb, offset) {
|
|
if(offset) {
|
|
offset -= 0;
|
|
bb = $.extend({}, bb);
|
|
bb.width += offset;
|
|
bb.height += offset;
|
|
bb.x -= offset/2;
|
|
bb.y -= offset/2;
|
|
}
|
|
|
|
var mid_x = bb.x + bb.width/2;
|
|
var mid_y = bb.y + bb.height/2;
|
|
var len_x = x - mid_x;
|
|
var len_y = y - mid_y;
|
|
|
|
var slope = Math.abs(len_y/len_x);
|
|
|
|
var ratio;
|
|
|
|
if(slope < bb.height/bb.width) {
|
|
ratio = (bb.width/2) / Math.abs(len_x);
|
|
} else {
|
|
ratio = (bb.height/2) / Math.abs(len_y);
|
|
}
|
|
|
|
|
|
return {
|
|
x: mid_x + len_x * ratio,
|
|
y: mid_y + len_y * ratio
|
|
}
|
|
}
|
|
|
|
// Do once
|
|
(function() {
|
|
var gse = svgCanvas.groupSelectedElements;
|
|
|
|
svgCanvas.groupSelectedElements = function() {
|
|
svgCanvas.removeFromSelection($(conn_sel).toArray());
|
|
gse();
|
|
}
|
|
|
|
var mse = svgCanvas.moveSelectedElements;
|
|
|
|
svgCanvas.moveSelectedElements = function() {
|
|
svgCanvas.removeFromSelection($(conn_sel).toArray());
|
|
mse.apply(this, arguments);
|
|
updateConnectors();
|
|
}
|
|
|
|
se_ns = svgCanvas.getEditorNS();
|
|
}());
|
|
|
|
// Do on reset
|
|
function init() {
|
|
// Make sure all connectors have data set
|
|
$(svgcontent).find('*').each(function() {
|
|
var conn = this.getAttributeNS(se_ns, "connector");
|
|
if(conn) {
|
|
this.setAttribute('class', conn_sel.substr(1));
|
|
var conn_data = conn.split(' ');
|
|
$(this).data('c_start',conn_data[0])
|
|
.data('c_end',conn_data[1]);
|
|
svgCanvas.getEditorNS(true);
|
|
}
|
|
});
|
|
// updateConnectors();
|
|
}
|
|
|
|
// $(svgroot).parent().mousemove(function(e) {
|
|
// // if(started
|
|
// // || svgCanvas.getMode() != "connector"
|
|
// // || e.target.parentNode.parentNode != svgcontent) return;
|
|
//
|
|
// console.log('y')
|
|
// // if(e.target.parentNode.parentNode === svgcontent) {
|
|
// //
|
|
// // }
|
|
// });
|
|
|
|
return {
|
|
name: "Connector",
|
|
svgicons: "images/conn.svg",
|
|
buttons: [{
|
|
id: "mode_connect",
|
|
type: "mode",
|
|
icon: "images/cut.png",
|
|
title: "Connect two objects",
|
|
key: "Shift+3",
|
|
includeWith: {
|
|
button: '#tool_line',
|
|
isDefault: false,
|
|
position: 1
|
|
},
|
|
events: {
|
|
'click': function() {
|
|
svgCanvas.setMode("connector");
|
|
}
|
|
}
|
|
}],
|
|
addLangData: function(lang) {
|
|
return {
|
|
data: lang_list[lang]
|
|
};
|
|
},
|
|
mouseDown: function(opts) {
|
|
var e = opts.event;
|
|
start_x = opts.start_x,
|
|
start_y = opts.start_y;
|
|
var mode = svgCanvas.getMode();
|
|
|
|
if(mode == "connector") {
|
|
|
|
if(started) return;
|
|
|
|
var mouse_target = e.target;
|
|
|
|
var parents = $(mouse_target).parents();
|
|
|
|
if($.inArray(svgcontent, parents) != -1) {
|
|
// Connectable element
|
|
|
|
// If child of foreignObject, use parent
|
|
var fo = $(mouse_target).closest("foreignObject");
|
|
start_elem = fo.length ? fo[0] : mouse_target;
|
|
|
|
// Get center of source element
|
|
var bb = svgCanvas.getStrokedBBox([start_elem]);
|
|
var x = bb.x + bb.width/2;
|
|
var y = bb.y + bb.height/2;
|
|
|
|
started = true;
|
|
cur_line = addElem({
|
|
"element": "polyline",
|
|
"attr": {
|
|
"id": getNextId(),
|
|
"points": (x+','+y+' '+x+','+y+' '+start_x+','+start_y),
|
|
"stroke": '#000',
|
|
"stroke-width": 2,
|
|
"fill": "none",
|
|
"opacity": .5,
|
|
"style": "pointer-events:none"
|
|
}
|
|
});
|
|
|
|
$(cur_line).data('start_bb', bb);
|
|
}
|
|
return {
|
|
started: true
|
|
};
|
|
} else if(mode == "select") {
|
|
findConnectors();
|
|
}
|
|
},
|
|
mouseMove: function(opts) {
|
|
var zoom = svgCanvas.getZoom();
|
|
var e = opts.event;
|
|
var x = opts.mouse_x/zoom;
|
|
var y = opts.mouse_y/zoom;
|
|
|
|
var diff_x = x - start_x,
|
|
diff_y = y - start_y;
|
|
|
|
var mode = svgCanvas.getMode();
|
|
|
|
if(mode == "connector" && started) {
|
|
|
|
// Set start point (adjusts based on bb)
|
|
var pt = getBBintersect(x, y, $(cur_line).data('start_bb'));
|
|
start_x = pt.x;
|
|
start_y = pt.y;
|
|
|
|
setPoint(cur_line, 0, pt.x, pt.y, true);
|
|
|
|
// Set end point
|
|
setPoint(cur_line, 'end', x, y, true);
|
|
} else if(mode == "select") {
|
|
var slen = selElems.length;
|
|
|
|
while(slen--) {
|
|
var elem = selElems[slen];
|
|
// Look for selected connector elements
|
|
if(elem && $(elem).data('c_start')) {
|
|
// Remove the "translate" transform given to move
|
|
svgCanvas.removeFromSelection([elem]);
|
|
svgCanvas.getTransformList(elem).clear();
|
|
|
|
}
|
|
}
|
|
if(connections.length) {
|
|
// Update line with element
|
|
var i = connections.length;
|
|
while(i--) {
|
|
var conn = connections[i];
|
|
var line = conn.connector;
|
|
var elem = conn.elem;
|
|
|
|
var pre = conn.is_start?'start':'end';
|
|
var sw = line.getAttribute('stroke-width');
|
|
|
|
// Update bbox for this element
|
|
var bb = $(line).data(pre+'_bb');
|
|
bb.x = conn.start_x + diff_x;
|
|
bb.y = conn.start_y + diff_y;
|
|
$(line).data(pre+'_bb', bb);
|
|
|
|
var alt_pre = conn.is_start?'end':'start';
|
|
|
|
// Get center pt of connected element
|
|
var bb2 = $(line).data(alt_pre+'_bb');
|
|
var src_x = bb2.x + bb2.width/2;
|
|
var src_y = bb2.y + bb2.height/2;
|
|
|
|
// Set point of element being moved
|
|
var pt = getBBintersect(src_x, src_y, bb, $(line).data(pre+'_off')?sw:0);
|
|
setPoint(line, conn.is_start?0:'end', pt.x, pt.y, true);
|
|
|
|
// Set point of connected element
|
|
var pt2 = getBBintersect(pt.x, pt.y, $(line).data(alt_pre + '_bb'), $(line).data(alt_pre+'_off')?sw:0);
|
|
setPoint(line, conn.is_start?'end':0, pt2.x, pt2.y, true);
|
|
|
|
}
|
|
|
|
}
|
|
}
|
|
},
|
|
mouseUp: function(opts) {
|
|
var zoom = svgCanvas.getZoom();
|
|
var e = opts.event,
|
|
x = opts.mouse_x/zoom,
|
|
y = opts.mouse_y/zoom,
|
|
mouse_target = e.target;
|
|
|
|
if(svgCanvas.getMode() == "connector") {
|
|
var fo = $(mouse_target).closest("foreignObject");
|
|
if(fo.length) mouse_target = fo[0];
|
|
if(mouse_target.parentNode.parentNode != svgcontent) {
|
|
// Not a valid target element, so remove line
|
|
$(cur_line).remove();
|
|
started = false;
|
|
return {
|
|
keep: false,
|
|
element: null,
|
|
started: started
|
|
}
|
|
} else if(mouse_target == start_elem) {
|
|
// Start line through click
|
|
started = true;
|
|
return {
|
|
keep: true,
|
|
element: null,
|
|
started: started
|
|
}
|
|
} else {
|
|
// Valid end element
|
|
end_elem = mouse_target;
|
|
|
|
var start_id = start_elem.id, end_id = end_elem.id;
|
|
var conn_str = start_id + " " + end_id;
|
|
var alt_str = end_id + " " + start_id;
|
|
// Don't create connector if one already exists
|
|
var dupe = $(svgcontent).find(conn_sel).filter(function() {
|
|
var conn = this.getAttributeNS(se_ns, "connector");
|
|
if(conn == conn_str || conn == alt_str) return true;
|
|
});
|
|
if(dupe.length) {
|
|
$(cur_line).remove();
|
|
return {
|
|
keep: false,
|
|
element: null,
|
|
started: false
|
|
}
|
|
}
|
|
|
|
var bb = svgCanvas.getStrokedBBox([end_elem]);
|
|
|
|
var pt = getBBintersect(start_x, start_y, bb);
|
|
setPoint(cur_line, 'end', pt.x, pt.y, true);
|
|
$(cur_line)
|
|
.data("c_start", start_id)
|
|
.data("c_end", end_id)
|
|
.data("end_bb", bb);
|
|
se_ns = svgCanvas.getEditorNS(true);
|
|
cur_line.setAttributeNS(se_ns, "se:connector", conn_str);
|
|
cur_line.setAttribute('class', conn_sel.substr(1));
|
|
cur_line.setAttribute('opacity', 1);
|
|
svgCanvas.addToSelection([cur_line]);
|
|
svgCanvas.moveToBottomSelectedElement();
|
|
selManager.requestSelector(cur_line).showGrips(false);
|
|
started = false;
|
|
return {
|
|
keep: true,
|
|
element: cur_line,
|
|
started: started
|
|
}
|
|
}
|
|
}
|
|
},
|
|
selectedChanged: function(opts) {
|
|
|
|
// Use this to update the current selected elements
|
|
selElems = opts.elems;
|
|
|
|
var i = selElems.length;
|
|
|
|
while(i--) {
|
|
var elem = selElems[i];
|
|
if(elem && $(elem).data('c_start')) {
|
|
selManager.requestSelector(elem).showGrips(false);
|
|
if(opts.selectedElement && !opts.multiselected) {
|
|
// TODO: Set up context tools and hide most regular line tools
|
|
showPanel(true);
|
|
} else {
|
|
showPanel(false);
|
|
}
|
|
} else {
|
|
showPanel(false);
|
|
}
|
|
}
|
|
},
|
|
elementChanged: function(opts) {
|
|
var elem = opts.elems[0];
|
|
if (elem && elem.tagName == 'svg' && elem.id == "svgcontent") {
|
|
// Update svgcontent (can change on import)
|
|
svgcontent = elem;
|
|
init();
|
|
}
|
|
|
|
// Has marker, so change offset
|
|
if(elem && (
|
|
elem.getAttribute("marker-start") ||
|
|
elem.getAttribute("marker-mid") ||
|
|
elem.getAttribute("marker-end")
|
|
)) {
|
|
var start = elem.getAttribute("marker-start");
|
|
var mid = elem.getAttribute("marker-mid");
|
|
var end = elem.getAttribute("marker-end");
|
|
cur_line = elem;
|
|
$(elem)
|
|
.data("start_off", !!start)
|
|
.data("end_off", !!end);
|
|
|
|
if(elem.tagName == "line" && mid) {
|
|
// Convert to polyline to accept mid-arrow
|
|
|
|
var x1 = elem.getAttribute('x1')-0;
|
|
var x2 = elem.getAttribute('x2')-0;
|
|
var y1 = elem.getAttribute('y1')-0;
|
|
var y2 = elem.getAttribute('y2')-0;
|
|
var id = elem.id;
|
|
|
|
var mid_pt = (' '+((x1+x2)/2)+','+((y1+y2)/2) + ' ');
|
|
var pline = addElem({
|
|
"element": "polyline",
|
|
"attr": {
|
|
"points": (x1+','+y1+ mid_pt +x2+','+y2),
|
|
"stroke": elem.getAttribute('stroke'),
|
|
"stroke-width": elem.getAttribute('stroke-width'),
|
|
"marker-mid": mid,
|
|
"fill": "none",
|
|
"opacity": elem.getAttribute('opacity') || 1
|
|
}
|
|
});
|
|
$(elem).after(pline).remove();
|
|
svgCanvas.clearSelection();
|
|
pline.id = id;
|
|
svgCanvas.addToSelection([pline]);
|
|
}
|
|
|
|
}
|
|
updateConnectors();
|
|
},
|
|
toolButtonStateUpdate: function(opts) {
|
|
if(opts.nostroke) {
|
|
if ($('#mode_connect').hasClass('tool_button_current')) {
|
|
clickSelect();
|
|
}
|
|
}
|
|
$('#mode_connect')
|
|
.toggleClass('disabled',opts.nostroke);
|
|
}
|
|
};
|
|
});
|
|
}); |