mirror of
https://bitbucket.org/wisemapping/wisemapping-frontend.git
synced 2024-12-23 12:03:49 +01:00
1370 lines
45 KiB
JavaScript
1370 lines
45 KiB
JavaScript
/*
|
|
* Copyright [2015] [wisemapping]
|
|
*
|
|
* Licensed under WiseMapping Public License, Version 1.0 (the "License").
|
|
* It is basically the Apache License, Version 2.0 (the "License") plus the
|
|
* "powered by wisemapping" text requirement on every single page;
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the license at
|
|
*
|
|
* http://www.wisemapping.org/license
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
const web2D = require('@wismapping/web2d');
|
|
const web2d = web2D();
|
|
const NodeGraph = require('./NodeGraph').default;
|
|
const { TopicShape } = require('./model/INodeModel');
|
|
const TopicStyle = require('./TopicStyle').default;
|
|
const TopicFeature = require('./TopicFeature').default;
|
|
const ConnectionLine = require('./ConnectionLine').default;
|
|
const IconGroup = require('./IconGroup').default;
|
|
const FadeEffect = require('./util/FadeEffect').default;
|
|
const EventBus = require('./layout/EventBus').default;
|
|
const ShirinkConnector = require('./ShrinkConnector').default;
|
|
const NoteEditor = require('./widget/NoteEditor').default;
|
|
const ActionDispatcher = require('./ActionDispatcher').default;
|
|
const LinkEditor = require('./widget/LinkEditor').default;
|
|
const TopicEventDispatcher = require('./TopicEventDispatcher').default;
|
|
const INodeModel = require('./model/INodeModel').default;
|
|
|
|
const Topic = new Class(
|
|
/** @lends Topic */ {
|
|
Extends: NodeGraph,
|
|
/**
|
|
* @extends mindplot.NodeGraph
|
|
* @constructs
|
|
* @param model
|
|
* @param options
|
|
*/
|
|
initialize: function (model, options) {
|
|
this.parent(model, options);
|
|
this._children = [];
|
|
this._parent = null;
|
|
this._relationships = [];
|
|
this._isInWorkspace = false;
|
|
this._buildTopicShape();
|
|
|
|
// Position a topic ....
|
|
var pos = model.getPosition();
|
|
if (pos != null && this.isCentralTopic()) {
|
|
this.setPosition(pos);
|
|
}
|
|
|
|
// Register events for the topic ...
|
|
if (!this.isReadOnly()) {
|
|
this._registerEvents();
|
|
}
|
|
},
|
|
|
|
_registerEvents: function () {
|
|
this.setMouseEventsEnabled(true);
|
|
|
|
// Prevent click on the topics being propagated ...
|
|
this.addEvent('click', function (event) {
|
|
event.stopPropagation();
|
|
});
|
|
var me = this;
|
|
this.addEvent('dblclick', function (event) {
|
|
me._getTopicEventDispatcher().show(me);
|
|
event.stopPropagation();
|
|
});
|
|
},
|
|
|
|
/**
|
|
* @param {String} type the topic shape type
|
|
* @see {@link mindplot.model.INodeModel}
|
|
*/
|
|
setShapeType: function (type) {
|
|
this._setShapeType(type, true);
|
|
},
|
|
|
|
/** @return {mindplot.Topic} parent topic */
|
|
getParent: function () {
|
|
return this._parent;
|
|
},
|
|
|
|
_setShapeType: function (type, updateModel) {
|
|
// Remove inner shape figure ...
|
|
var model = this.getModel();
|
|
if ($defined(updateModel) && updateModel) {
|
|
model.setShapeType(type);
|
|
}
|
|
|
|
var oldInnerShape = this.getInnerShape();
|
|
if (oldInnerShape != null) {
|
|
this._removeInnerShape();
|
|
|
|
// Create a new one ...
|
|
var innerShape = this.getInnerShape();
|
|
|
|
// Update figure size ...
|
|
var size = this.getSize();
|
|
this.setSize(size, true);
|
|
|
|
var group = this.get2DElement();
|
|
group.append(innerShape);
|
|
|
|
// Move text to the front ...
|
|
var text = this.getTextShape();
|
|
text.moveToFront();
|
|
|
|
//Move iconGroup to front ...
|
|
var iconGroup = this.getIconGroup();
|
|
if ($defined(iconGroup)) {
|
|
iconGroup.moveToFront();
|
|
}
|
|
|
|
//Move connector to front
|
|
var connector = this.getShrinkConnector();
|
|
if ($defined(connector)) {
|
|
connector.moveToFront();
|
|
}
|
|
}
|
|
},
|
|
|
|
/** @return {String} topic shape type */
|
|
getShapeType: function () {
|
|
var model = this.getModel();
|
|
var result = model.getShapeType();
|
|
if (!$defined(result)) {
|
|
result = TopicStyle.defaultShapeType(this);
|
|
}
|
|
return result;
|
|
},
|
|
|
|
_removeInnerShape: function () {
|
|
var group = this.get2DElement();
|
|
var innerShape = this.getInnerShape();
|
|
group.removeChild(innerShape);
|
|
this._innerShape = null;
|
|
return innerShape;
|
|
},
|
|
|
|
/** @return {web2d.Line|web2d.Rect|web2d.Image} inner shape of the topic */
|
|
getInnerShape: function () {
|
|
if (!$defined(this._innerShape)) {
|
|
// Create inner box.
|
|
this._innerShape = this._buildShape(
|
|
Topic.INNER_RECT_ATTRIBUTES,
|
|
this.getShapeType()
|
|
);
|
|
|
|
// Update bgcolor ...
|
|
var bgColor = this.getBackgroundColor();
|
|
this._setBackgroundColor(bgColor, false);
|
|
|
|
// Update border color ...
|
|
var brColor = this.getBorderColor();
|
|
this._setBorderColor(brColor, false);
|
|
|
|
// Define the pointer ...
|
|
if (!this.isCentralTopic() && !this.isReadOnly()) {
|
|
this._innerShape.setCursor('move');
|
|
} else {
|
|
this._innerShape.setCursor('default');
|
|
}
|
|
}
|
|
return this._innerShape;
|
|
},
|
|
|
|
_buildShape: function (attributes, shapeType) {
|
|
$assert(attributes, 'attributes can not be null');
|
|
$assert(shapeType, 'shapeType can not be null');
|
|
|
|
var result;
|
|
if (shapeType == TopicShape.RECTANGLE) {
|
|
result = new web2d.Rect(0, attributes);
|
|
} else if (shapeType == TopicShape.IMAGE) {
|
|
var model = this.getModel();
|
|
var url = model.getImageUrl();
|
|
var size = model.getImageSize();
|
|
|
|
result = new web2d.Image();
|
|
result.setHref(url);
|
|
result.setSize(size.width, size.height);
|
|
|
|
result.getSize = function () {
|
|
return model.getImageSize();
|
|
};
|
|
|
|
result.setPosition = function () {};
|
|
} else if (shapeType == TopicShape.ELLIPSE) {
|
|
result = new web2d.Rect(0.9, attributes);
|
|
} else if (shapeType == TopicShape.ROUNDED_RECT) {
|
|
result = new web2d.Rect(0.3, attributes);
|
|
} else if (shapeType == TopicShape.LINE) {
|
|
result = new web2d.Line({ strokeColor: '#495879', strokeWidth: 1 });
|
|
result.setSize = function (width, height) {
|
|
this.size = { width: width, height: height };
|
|
result.setFrom(0, height);
|
|
result.setTo(width, height);
|
|
|
|
// Lines will have the same color of the default connection lines...
|
|
var stokeColor = ConnectionLine.getStrokeColor();
|
|
result.setStroke(1, 'solid', stokeColor);
|
|
};
|
|
|
|
result.getSize = function () {
|
|
return this.size;
|
|
};
|
|
|
|
result.setPosition = function () {};
|
|
|
|
result.setFill = function () {};
|
|
|
|
result.setStroke = function () {};
|
|
} else {
|
|
$assert(false, 'Unsupported figure shapeType:' + shapeType);
|
|
}
|
|
result.setPosition(0, 0);
|
|
return result;
|
|
},
|
|
|
|
/** @param {String} type the cursor type, either 'pointer', 'default' or 'move' */
|
|
setCursor: function (type) {
|
|
var innerShape = this.getInnerShape();
|
|
innerShape.setCursor(type);
|
|
|
|
var outerShape = this.getOuterShape();
|
|
outerShape.setCursor(type);
|
|
|
|
var textShape = this.getTextShape();
|
|
textShape.setCursor(type);
|
|
},
|
|
|
|
/** @return outer shape */
|
|
getOuterShape: function () {
|
|
if (!$defined(this._outerShape)) {
|
|
var rect = this._buildShape(Topic.OUTER_SHAPE_ATTRIBUTES, TopicShape.ROUNDED_RECT);
|
|
rect.setPosition(-2, -3);
|
|
rect.setOpacity(0);
|
|
this._outerShape = rect;
|
|
}
|
|
|
|
return this._outerShape;
|
|
},
|
|
|
|
/** @return text shape */
|
|
getTextShape: function () {
|
|
if (!$defined(this._text)) {
|
|
this._text = this._buildTextShape(false);
|
|
|
|
// Set Text ...
|
|
var text = this.getText();
|
|
this._setText(text, false);
|
|
}
|
|
|
|
return this._text;
|
|
},
|
|
|
|
/** @return icon group */
|
|
getOrBuildIconGroup: function () {
|
|
if (!$defined(this._iconsGroup)) {
|
|
this._iconsGroup = this._buildIconGroup();
|
|
var group = this.get2DElement();
|
|
group.append(this._iconsGroup.getNativeElement());
|
|
this._iconsGroup.moveToFront();
|
|
}
|
|
return this._iconsGroup;
|
|
},
|
|
|
|
/** */
|
|
getIconGroup: function () {
|
|
return this._iconsGroup;
|
|
},
|
|
|
|
_buildIconGroup: function () {
|
|
var textHeight = this.getTextShape().getFontHeight();
|
|
var result = new IconGroup(this.getId(), textHeight);
|
|
var padding = TopicStyle.getInnerPadding(this);
|
|
result.setPosition(padding, padding);
|
|
|
|
// Load topic features ...
|
|
var model = this.getModel();
|
|
var featuresModel = model.getFeatures();
|
|
for (var i = 0; i < featuresModel.length; i++) {
|
|
var featureModel = featuresModel[i];
|
|
var icon = TopicFeature.createIcon(this, featureModel, this.isReadOnly());
|
|
result.addIcon(
|
|
icon,
|
|
featureModel.getType() == TopicFeature.Icon.id && !this.isReadOnly()
|
|
);
|
|
}
|
|
|
|
return result;
|
|
},
|
|
|
|
/**
|
|
* assigns the new feature model to the topic's node model and adds the respective icon
|
|
* @param {mindplot.model.FeatureModel} featureModel
|
|
* @return {mindplot.Icon} the icon corresponding to the feature model
|
|
*/
|
|
addFeature: function (featureModel) {
|
|
var iconGroup = this.getOrBuildIconGroup();
|
|
this.closeEditors();
|
|
|
|
// Update model ...
|
|
var model = this.getModel();
|
|
model.addFeature(featureModel);
|
|
|
|
var result = TopicFeature.createIcon(this, featureModel, this.isReadOnly());
|
|
iconGroup.addIcon(
|
|
result,
|
|
featureModel.getType() == TopicFeature.Icon.id && !this.isReadOnly()
|
|
);
|
|
|
|
this._adjustShapes();
|
|
return result;
|
|
},
|
|
|
|
/** */
|
|
findFeatureById: function (id) {
|
|
var model = this.getModel();
|
|
return model.findFeatureById(id);
|
|
},
|
|
|
|
/** */
|
|
removeFeature: function (featureModel) {
|
|
$assert(featureModel, 'featureModel could not be null');
|
|
|
|
//Removing the icon from MODEL
|
|
var model = this.getModel();
|
|
model.removeFeature(featureModel);
|
|
|
|
//Removing the icon from UI
|
|
var iconGroup = this.getIconGroup();
|
|
if ($defined(iconGroup)) {
|
|
iconGroup.removeIconByModel(featureModel);
|
|
}
|
|
this._adjustShapes();
|
|
},
|
|
|
|
/** */
|
|
addRelationship: function (relationship) {
|
|
this._relationships.push(relationship);
|
|
},
|
|
|
|
/** */
|
|
deleteRelationship: function (relationship) {
|
|
this._relationships.erase(relationship);
|
|
},
|
|
|
|
/** */
|
|
getRelationships: function () {
|
|
return this._relationships;
|
|
},
|
|
|
|
_buildTextShape: function (readOnly) {
|
|
var result = new web2d.Text();
|
|
var family = this.getFontFamily();
|
|
var size = this.getFontSize();
|
|
var weight = this.getFontWeight();
|
|
var style = this.getFontStyle();
|
|
result.setFont(family, size, style, weight);
|
|
|
|
var color = this.getFontColor();
|
|
result.setColor(color);
|
|
|
|
if (!readOnly) {
|
|
// Propagate mouse events ...
|
|
if (!this.isCentralTopic()) {
|
|
result.setCursor('move');
|
|
} else {
|
|
result.setCursor('default');
|
|
}
|
|
}
|
|
|
|
return result;
|
|
},
|
|
|
|
/** */
|
|
setFontFamily: function (value, updateModel) {
|
|
var textShape = this.getTextShape();
|
|
textShape.setFontFamily(value);
|
|
if ($defined(updateModel) && updateModel) {
|
|
var model = this.getModel();
|
|
model.setFontFamily(value);
|
|
}
|
|
this._adjustShapes(updateModel);
|
|
},
|
|
|
|
/** */
|
|
setFontSize: function (value, updateModel) {
|
|
var textShape = this.getTextShape();
|
|
textShape.setSize(value);
|
|
|
|
if ($defined(updateModel) && updateModel) {
|
|
var model = this.getModel();
|
|
model.setFontSize(value);
|
|
}
|
|
this._adjustShapes(updateModel);
|
|
},
|
|
|
|
/** */
|
|
setFontStyle: function (value, updateModel) {
|
|
var textShape = this.getTextShape();
|
|
textShape.setStyle(value);
|
|
if ($defined(updateModel) && updateModel) {
|
|
var model = this.getModel();
|
|
model.setFontStyle(value);
|
|
}
|
|
this._adjustShapes(updateModel);
|
|
},
|
|
|
|
/** */
|
|
setFontWeight: function (value, updateModel) {
|
|
var textShape = this.getTextShape();
|
|
textShape.setWeight(value);
|
|
if ($defined(updateModel) && updateModel) {
|
|
var model = this.getModel();
|
|
model.setFontWeight(value);
|
|
}
|
|
this._adjustShapes();
|
|
},
|
|
|
|
/** */
|
|
getFontWeight: function () {
|
|
var model = this.getModel();
|
|
var result = model.getFontWeight();
|
|
if (!$defined(result)) {
|
|
var font = TopicStyle.defaultFontStyle(this);
|
|
result = font.weight;
|
|
}
|
|
return result;
|
|
},
|
|
|
|
/** */
|
|
getFontFamily: function () {
|
|
var model = this.getModel();
|
|
var result = model.getFontFamily();
|
|
if (!$defined(result)) {
|
|
var font = TopicStyle.defaultFontStyle(this);
|
|
result = font.font;
|
|
}
|
|
return result;
|
|
},
|
|
|
|
/** */
|
|
getFontColor: function () {
|
|
var model = this.getModel();
|
|
var result = model.getFontColor();
|
|
if (!$defined(result)) {
|
|
var font = TopicStyle.defaultFontStyle(this);
|
|
result = font.color;
|
|
}
|
|
return result;
|
|
},
|
|
|
|
/** */
|
|
getFontStyle: function () {
|
|
var model = this.getModel();
|
|
var result = model.getFontStyle();
|
|
if (!$defined(result)) {
|
|
var font = TopicStyle.defaultFontStyle(this);
|
|
result = font.style;
|
|
}
|
|
return result;
|
|
},
|
|
|
|
/** */
|
|
getFontSize: function () {
|
|
var model = this.getModel();
|
|
var result = model.getFontSize();
|
|
if (!$defined(result)) {
|
|
var font = TopicStyle.defaultFontStyle(this);
|
|
result = font.size;
|
|
}
|
|
return result;
|
|
},
|
|
|
|
/** */
|
|
setFontColor: function (value, updateModel) {
|
|
var textShape = this.getTextShape();
|
|
textShape.setColor(value);
|
|
if ($defined(updateModel) && updateModel) {
|
|
var model = this.getModel();
|
|
model.setFontColor(value);
|
|
}
|
|
},
|
|
|
|
_setText: function (text, updateModel) {
|
|
var textShape = this.getTextShape();
|
|
textShape.setText(text == null ? TopicStyle.defaultText(this) : text);
|
|
|
|
if ($defined(updateModel) && updateModel) {
|
|
var model = this.getModel();
|
|
model.setText(text);
|
|
}
|
|
},
|
|
|
|
/** */
|
|
setText: function (text) {
|
|
// Avoid empty nodes ...
|
|
if (!text || $.trim(text).length == 0) {
|
|
text = null;
|
|
}
|
|
|
|
this._setText(text, true);
|
|
this._adjustShapes();
|
|
},
|
|
|
|
/** */
|
|
getText: function () {
|
|
var model = this.getModel();
|
|
var result = model.getText();
|
|
if (!$defined(result)) {
|
|
result = TopicStyle.defaultText(this);
|
|
}
|
|
return result;
|
|
},
|
|
|
|
/** */
|
|
setBackgroundColor: function (color) {
|
|
this._setBackgroundColor(color, true);
|
|
},
|
|
|
|
_setBackgroundColor: function (color, updateModel) {
|
|
var innerShape = this.getInnerShape();
|
|
innerShape.setFill(color);
|
|
|
|
var connector = this.getShrinkConnector();
|
|
if (connector) {
|
|
connector.setFill(color);
|
|
}
|
|
|
|
if ($defined(updateModel) && updateModel) {
|
|
var model = this.getModel();
|
|
model.setBackgroundColor(color);
|
|
}
|
|
},
|
|
|
|
/** */
|
|
getBackgroundColor: function () {
|
|
var model = this.getModel();
|
|
var result = model.getBackgroundColor();
|
|
if (!$defined(result)) {
|
|
result = TopicStyle.defaultBackgroundColor(this);
|
|
}
|
|
return result;
|
|
},
|
|
|
|
/** */
|
|
setBorderColor: function (color) {
|
|
this._setBorderColor(color, true);
|
|
},
|
|
|
|
_setBorderColor: function (color, updateModel) {
|
|
var innerShape = this.getInnerShape();
|
|
innerShape.setAttribute('strokeColor', color);
|
|
|
|
var connector = this.getShrinkConnector();
|
|
if (connector) {
|
|
connector.setAttribute('strokeColor', color);
|
|
}
|
|
|
|
if ($defined(updateModel) && updateModel) {
|
|
var model = this.getModel();
|
|
model.setBorderColor(color);
|
|
}
|
|
},
|
|
|
|
/** */
|
|
getBorderColor: function () {
|
|
var model = this.getModel();
|
|
var result = model.getBorderColor();
|
|
if (!$defined(result)) {
|
|
result = TopicStyle.defaultBorderColor(this);
|
|
}
|
|
return result;
|
|
},
|
|
|
|
_buildTopicShape: function () {
|
|
var groupAttributes = {
|
|
width: 100,
|
|
height: 100,
|
|
coordSizeWidth: 100,
|
|
coordSizeHeight: 100,
|
|
};
|
|
var group = new web2d.Group(groupAttributes);
|
|
this._set2DElement(group);
|
|
|
|
// Shape must be build based on the model width ...
|
|
var outerShape = this.getOuterShape();
|
|
var innerShape = this.getInnerShape();
|
|
var textShape = this.getTextShape();
|
|
|
|
// Add to the group ...
|
|
group.append(outerShape);
|
|
group.append(innerShape);
|
|
group.append(textShape);
|
|
|
|
// Update figure size ...
|
|
var model = this.getModel();
|
|
if (model.getFeatures().length != 0) {
|
|
this.getOrBuildIconGroup();
|
|
}
|
|
|
|
var shrinkConnector = this.getShrinkConnector();
|
|
if ($defined(shrinkConnector)) {
|
|
shrinkConnector.addToWorkspace(group);
|
|
}
|
|
|
|
// Register listeners ...
|
|
this._registerDefaultListenersToElement(group, this);
|
|
},
|
|
|
|
_registerDefaultListenersToElement: function (elem, topic) {
|
|
var mouseOver = function (event) {
|
|
if (topic.isMouseEventsEnabled()) {
|
|
topic.handleMouseOver(event);
|
|
}
|
|
};
|
|
elem.addEvent('mouseover', mouseOver);
|
|
|
|
var outout = function (event) {
|
|
if (topic.isMouseEventsEnabled()) {
|
|
topic.handleMouseOut(event);
|
|
}
|
|
};
|
|
elem.addEvent('mouseout', outout);
|
|
|
|
var me = this;
|
|
// Focus events ...
|
|
elem.addEvent('mousedown', function (event) {
|
|
if (!me.isReadOnly()) {
|
|
// Disable topic selection of readOnly mode ...
|
|
var value = true;
|
|
if (
|
|
(event.metaKey && Browser.Platform.mac) ||
|
|
(event.ctrlKey && !Browser.Platform.mac)
|
|
) {
|
|
value = !me.isOnFocus();
|
|
event.stopPropagation();
|
|
event.preventDefault();
|
|
}
|
|
topic.setOnFocus(value);
|
|
}
|
|
|
|
var eventDispatcher = me._getTopicEventDispatcher();
|
|
eventDispatcher.process(TopicEvent.CLICK, me);
|
|
event.stopPropagation();
|
|
});
|
|
},
|
|
|
|
/** */
|
|
areChildrenShrunken: function () {
|
|
var model = this.getModel();
|
|
return model.areChildrenShrunken() && !this.isCentralTopic();
|
|
},
|
|
|
|
/** */
|
|
isCollapsed: function () {
|
|
var result = false;
|
|
|
|
var current = this.getParent();
|
|
while (current && !result) {
|
|
result = current.areChildrenShrunken();
|
|
current = current.getParent();
|
|
}
|
|
return result;
|
|
},
|
|
|
|
/** */
|
|
setChildrenShrunken: function (value) {
|
|
// Update Model ...
|
|
var model = this.getModel();
|
|
model.setChildrenShrunken(value);
|
|
|
|
// Change render base on the state.
|
|
var shrinkConnector = this.getShrinkConnector();
|
|
if ($defined(shrinkConnector)) {
|
|
shrinkConnector.changeRender(value);
|
|
}
|
|
|
|
// Do some fancy animation ....
|
|
var elements = this._flatten2DElements(this);
|
|
var fade = new FadeEffect(elements, !value);
|
|
var me = this;
|
|
fade.addEvent('complete', function () {
|
|
// Set focus on the parent node ...
|
|
if (value) {
|
|
me.setOnFocus(true);
|
|
}
|
|
|
|
// Set focus in false for all the children ...
|
|
elements.forEach(function (elem) {
|
|
if (elem.setOnFocus) {
|
|
elem.setOnFocus(false);
|
|
}
|
|
});
|
|
});
|
|
fade.start();
|
|
|
|
EventBus.instance.fireEvent(EventBus.events.NodeShrinkEvent, model);
|
|
},
|
|
|
|
/** */
|
|
getShrinkConnector: function () {
|
|
var result = this._connector;
|
|
if (this._connector == null) {
|
|
this._connector = new ShirinkConnector(this);
|
|
this._connector.setVisibility(false);
|
|
result = this._connector;
|
|
}
|
|
return result;
|
|
},
|
|
|
|
/** */
|
|
handleMouseOver: function () {
|
|
var outerShape = this.getOuterShape();
|
|
outerShape.setOpacity(1);
|
|
},
|
|
|
|
/** */
|
|
handleMouseOut: function () {
|
|
var outerShape = this.getOuterShape();
|
|
if (!this.isOnFocus()) {
|
|
outerShape.setOpacity(0);
|
|
}
|
|
},
|
|
|
|
/** */
|
|
showTextEditor: function (text) {
|
|
this._getTopicEventDispatcher().show(this, { text: text });
|
|
},
|
|
|
|
/** */
|
|
showNoteEditor: function () {
|
|
var topicId = this.getId();
|
|
var model = this.getModel();
|
|
var editorModel = {
|
|
getValue: function () {
|
|
var notes = model.findFeatureByType(TopicFeature.Note.id);
|
|
var result;
|
|
if (notes.length > 0) result = notes[0].getText();
|
|
|
|
return result;
|
|
},
|
|
|
|
setValue: function (value) {
|
|
var dispatcher = ActionDispatcher.getInstance();
|
|
var notes = model.findFeatureByType(TopicFeature.Note.id);
|
|
if (!$defined(value)) {
|
|
var featureId = notes[0].getId();
|
|
dispatcher.removeFeatureFromTopic(topicId, featureId);
|
|
} else {
|
|
if (notes.length > 0) {
|
|
dispatcher.changeFeatureToTopic(topicId, notes[0].getId(), {
|
|
text: value,
|
|
});
|
|
} else {
|
|
dispatcher.addFeatureToTopic(topicId, TopicFeature.Note.id, {
|
|
text: value,
|
|
});
|
|
}
|
|
}
|
|
},
|
|
};
|
|
var editor = new NoteEditor(editorModel);
|
|
this.closeEditors();
|
|
editor.show();
|
|
},
|
|
|
|
/** opens a dialog where the user can enter or edit an existing link associated with this topic */
|
|
showLinkEditor: function () {
|
|
var topicId = this.getId();
|
|
var model = this.getModel();
|
|
var editorModel = {
|
|
getValue: function () {
|
|
//@param {mindplot.model.LinkModel[]} links
|
|
var links = model.findFeatureByType(TopicFeature.Link.id);
|
|
var result;
|
|
if (links.length > 0) result = links[0].getUrl();
|
|
|
|
return result;
|
|
},
|
|
|
|
setValue: function (value) {
|
|
var dispatcher = ActionDispatcher.getInstance();
|
|
var links = model.findFeatureByType(TopicFeature.Link.id);
|
|
if (!$defined(value)) {
|
|
var featureId = links[0].getId();
|
|
dispatcher.removeFeatureFromTopic(topicId, featureId);
|
|
} else {
|
|
if (links.length > 0) {
|
|
dispatcher.changeFeatureToTopic(topicId, links[0].getId(), {
|
|
url: value,
|
|
});
|
|
} else {
|
|
dispatcher.addFeatureToTopic(topicId, TopicFeature.Link.id, {
|
|
url: value,
|
|
});
|
|
}
|
|
}
|
|
},
|
|
};
|
|
|
|
this.closeEditors();
|
|
var editor = new LinkEditor(editorModel);
|
|
editor.show();
|
|
},
|
|
|
|
/** */
|
|
closeEditors: function () {
|
|
this._getTopicEventDispatcher().close(true);
|
|
},
|
|
|
|
_getTopicEventDispatcher: function () {
|
|
return TopicEventDispatcher.getInstance();
|
|
},
|
|
|
|
/**
|
|
* Point: references the center of the rect shape.!!!
|
|
*/
|
|
setPosition: function (point) {
|
|
$assert(point, 'position can not be null');
|
|
point.x = Math.ceil(point.x);
|
|
point.y = Math.ceil(point.y);
|
|
|
|
// Update model's position ...
|
|
var model = this.getModel();
|
|
model.setPosition(point.x, point.y);
|
|
|
|
// Elements are positioned in the center.
|
|
// All topic element must be positioned based on the innerShape.
|
|
var size = this.getSize();
|
|
|
|
var cx = point.x - size.width / 2;
|
|
var cy = point.y - size.height / 2;
|
|
|
|
// Update visual position.
|
|
this._elem2d.setPosition(cx, cy);
|
|
|
|
// Update connection lines ...
|
|
this._updateConnectionLines();
|
|
|
|
// Check object state.
|
|
this.invariant();
|
|
},
|
|
|
|
/** */
|
|
getOutgoingLine: function () {
|
|
return this._outgoingLine;
|
|
},
|
|
|
|
/** */
|
|
getIncomingLines: function () {
|
|
var result = [];
|
|
var children = this.getChildren();
|
|
for (var i = 0; i < children.length; i++) {
|
|
var node = children[i];
|
|
var line = node.getOutgoingLine();
|
|
if ($defined(line)) {
|
|
result.push(line);
|
|
}
|
|
}
|
|
return result;
|
|
},
|
|
|
|
/** */
|
|
getOutgoingConnectedTopic: function () {
|
|
var result = null;
|
|
var line = this.getOutgoingLine();
|
|
if ($defined(line)) {
|
|
result = line.getTargetTopic();
|
|
}
|
|
return result;
|
|
},
|
|
|
|
_updateConnectionLines: function () {
|
|
// Update this to parent line ...
|
|
var outgoingLine = this.getOutgoingLine();
|
|
if ($defined(outgoingLine)) {
|
|
outgoingLine.redraw();
|
|
}
|
|
|
|
// Update all the incoming lines ...
|
|
var incomingLines = this.getIncomingLines();
|
|
for (var i = 0; i < incomingLines.length; i++) {
|
|
incomingLines[i].redraw();
|
|
}
|
|
|
|
// Update relationship lines
|
|
for (var j = 0; j < this._relationships.length; j++) {
|
|
this._relationships[j].redraw();
|
|
}
|
|
},
|
|
|
|
/** */
|
|
setBranchVisibility: function (value) {
|
|
var current = this;
|
|
var parent = this;
|
|
while (parent != null && !parent.isCentralTopic()) {
|
|
current = parent;
|
|
parent = current.getParent();
|
|
}
|
|
current.setVisibility(value);
|
|
},
|
|
|
|
/** */
|
|
setVisibility: function (value) {
|
|
this._setTopicVisibility(value);
|
|
|
|
// Hide all children...
|
|
this._setChildrenVisibility(value);
|
|
|
|
// If there there are connection to the node, topic must be hidden.
|
|
this._setRelationshipLinesVisibility(value);
|
|
|
|
// If it's connected, the connection must be rendered.
|
|
var outgoingLine = this.getOutgoingLine();
|
|
if (outgoingLine) {
|
|
outgoingLine.setVisibility(value);
|
|
}
|
|
},
|
|
|
|
/** */
|
|
moveToBack: function () {
|
|
// Update relationship lines
|
|
for (var j = 0; j < this._relationships.length; j++) {
|
|
this._relationships[j].moveToBack();
|
|
}
|
|
var connector = this.getShrinkConnector();
|
|
if ($defined(connector)) {
|
|
connector.moveToBack();
|
|
}
|
|
|
|
this.get2DElement().moveToBack();
|
|
},
|
|
|
|
/** */
|
|
moveToFront: function () {
|
|
this.get2DElement().moveToFront();
|
|
var connector = this.getShrinkConnector();
|
|
if ($defined(connector)) {
|
|
connector.moveToFront();
|
|
}
|
|
// Update relationship lines
|
|
for (var j = 0; j < this._relationships.length; j++) {
|
|
this._relationships[j].moveToFront();
|
|
}
|
|
},
|
|
|
|
/** */
|
|
isVisible: function () {
|
|
var elem = this.get2DElement();
|
|
return elem.isVisible();
|
|
},
|
|
|
|
_setRelationshipLinesVisibility: function (value) {
|
|
_.each(this._relationships, function (relationship) {
|
|
var sourceTopic = relationship.getSourceTopic();
|
|
var targetTopic = relationship.getTargetTopic();
|
|
|
|
var targetParent = targetTopic.getModel().getParent();
|
|
var sourceParent = sourceTopic.getModel().getParent();
|
|
relationship.setVisibility(
|
|
value &&
|
|
(targetParent == null || !targetParent.areChildrenShrunken()) &&
|
|
(sourceParent == null || !sourceParent.areChildrenShrunken())
|
|
);
|
|
});
|
|
},
|
|
|
|
_setTopicVisibility: function (value) {
|
|
var elem = this.get2DElement();
|
|
elem.setVisibility(value);
|
|
|
|
if (this.getIncomingLines().length > 0) {
|
|
var connector = this.getShrinkConnector();
|
|
if ($defined(connector)) {
|
|
connector.setVisibility(value);
|
|
}
|
|
}
|
|
|
|
var textShape = this.getTextShape();
|
|
textShape.setVisibility(this.getShapeType() != TopicShape.IMAGE ? value : false);
|
|
},
|
|
|
|
/** */
|
|
setOpacity: function (opacity) {
|
|
var elem = this.get2DElement();
|
|
elem.setOpacity(opacity);
|
|
|
|
var connector = this.getShrinkConnector();
|
|
if ($defined(connector)) {
|
|
connector.setOpacity(opacity);
|
|
}
|
|
var textShape = this.getTextShape();
|
|
textShape.setOpacity(opacity);
|
|
},
|
|
|
|
_setChildrenVisibility: function (isVisible) {
|
|
// Hide all children.
|
|
var children = this.getChildren();
|
|
var model = this.getModel();
|
|
|
|
isVisible = isVisible ? !model.areChildrenShrunken() : isVisible;
|
|
for (var i = 0; i < children.length; i++) {
|
|
var child = children[i];
|
|
child.setVisibility(isVisible);
|
|
|
|
var outgoingLine = child.getOutgoingLine();
|
|
outgoingLine.setVisibility(isVisible);
|
|
}
|
|
},
|
|
|
|
/** */
|
|
invariant: function () {
|
|
var line = this._outgoingLine;
|
|
var model = this.getModel();
|
|
var isConnected = model.isConnected();
|
|
|
|
// Check consistency...
|
|
if ((isConnected && !line) || (!isConnected && line)) {
|
|
// $assert(false,'Illegal state exception.');
|
|
}
|
|
},
|
|
|
|
/** */
|
|
setSize: function (size, force) {
|
|
$assert(size, 'size can not be null');
|
|
$assert($defined(size.width), 'size seem not to be a valid element');
|
|
size = { width: Math.ceil(size.width), height: Math.ceil(size.height) };
|
|
|
|
var oldSize = this.getSize();
|
|
var hasSizeChanged = oldSize.width != size.width || oldSize.height != size.height;
|
|
if (hasSizeChanged || force) {
|
|
NodeGraph.prototype.setSize.call(this, size);
|
|
|
|
var outerShape = this.getOuterShape();
|
|
var innerShape = this.getInnerShape();
|
|
|
|
outerShape.setSize(size.width + 4, size.height + 6);
|
|
innerShape.setSize(size.width, size.height);
|
|
|
|
// Update the figure position(ej: central topic must be centered) and children position.
|
|
this._updatePositionOnChangeSize(oldSize, size);
|
|
|
|
if (hasSizeChanged) {
|
|
EventBus.instance.fireEvent(EventBus.events.NodeResizeEvent, {
|
|
node: this.getModel(),
|
|
size: size,
|
|
});
|
|
}
|
|
}
|
|
},
|
|
|
|
_updatePositionOnChangeSize: function () {
|
|
$assert(false, 'this method must be overwrited.');
|
|
},
|
|
|
|
/** */
|
|
disconnect: function (workspace) {
|
|
var outgoingLine = this.getOutgoingLine();
|
|
if ($defined(outgoingLine)) {
|
|
$assert(workspace, 'workspace can not be null');
|
|
|
|
this._outgoingLine = null;
|
|
|
|
// Disconnect nodes ...
|
|
var targetTopic = outgoingLine.getTargetTopic();
|
|
targetTopic.removeChild(this);
|
|
|
|
// Update model ...
|
|
var childModel = this.getModel();
|
|
childModel.disconnect();
|
|
|
|
this._parent = null;
|
|
|
|
// Remove graphical element from the workspace...
|
|
outgoingLine.removeFromWorkspace(workspace);
|
|
|
|
// Remove from workspace.
|
|
EventBus.instance.fireEvent(EventBus.events.NodeDisconnectEvent, this.getModel());
|
|
|
|
// Change text based on the current connection ...
|
|
var model = this.getModel();
|
|
if (!model.getText()) {
|
|
var text = this.getText();
|
|
this._setText(text, false);
|
|
}
|
|
if (!model.getFontSize()) {
|
|
var size = this.getFontSize();
|
|
this.setFontSize(size, false);
|
|
}
|
|
|
|
// Hide connection line?.
|
|
if (targetTopic.getChildren().length == 0) {
|
|
var connector = targetTopic.getShrinkConnector();
|
|
if ($defined(connector)) {
|
|
connector.setVisibility(false);
|
|
}
|
|
}
|
|
}
|
|
},
|
|
|
|
/** */
|
|
getOrder: function () {
|
|
var model = this.getModel();
|
|
return model.getOrder();
|
|
},
|
|
|
|
/** */
|
|
setOrder: function (value) {
|
|
var model = this.getModel();
|
|
model.setOrder(value);
|
|
},
|
|
|
|
/** */
|
|
connectTo: function (targetTopic, workspace) {
|
|
$assert(!this._outgoingLine, 'Could not connect an already connected node');
|
|
$assert(targetTopic != this, 'Circular connection are not allowed');
|
|
$assert(targetTopic, 'Parent Graph can not be null');
|
|
$assert(workspace, 'Workspace can not be null');
|
|
|
|
// Connect Graphical Nodes ...
|
|
targetTopic.append(this);
|
|
this._parent = targetTopic;
|
|
|
|
// Update model ...
|
|
var targetModel = targetTopic.getModel();
|
|
var childModel = this.getModel();
|
|
childModel.connectTo(targetModel);
|
|
|
|
// Create a connection line ...
|
|
var outgoingLine = new ConnectionLine(this, targetTopic);
|
|
outgoingLine.setVisibility(false);
|
|
|
|
this._outgoingLine = outgoingLine;
|
|
workspace.append(outgoingLine);
|
|
|
|
// Update figure is necessary.
|
|
this.updateTopicShape(targetTopic);
|
|
|
|
// Change text based on the current connection ...
|
|
var model = this.getModel();
|
|
if (!model.getText()) {
|
|
var text = this.getText();
|
|
this._setText(text, false);
|
|
}
|
|
if (!model.getFontSize()) {
|
|
var size = this.getFontSize();
|
|
this.setFontSize(size, false);
|
|
}
|
|
this.getTextShape();
|
|
|
|
// Display connection node...
|
|
var connector = targetTopic.getShrinkConnector();
|
|
if ($defined(connector)) {
|
|
connector.setVisibility(true);
|
|
}
|
|
|
|
// Redraw line ...
|
|
outgoingLine.redraw();
|
|
|
|
// Fire connection event ...
|
|
if (this.isInWorkspace()) {
|
|
EventBus.instance.fireEvent(EventBus.events.NodeConnectEvent, {
|
|
parentNode: targetTopic.getModel(),
|
|
childNode: this.getModel(),
|
|
});
|
|
}
|
|
},
|
|
|
|
/** */
|
|
append: function (child) {
|
|
var children = this.getChildren();
|
|
children.push(child);
|
|
},
|
|
|
|
/** */
|
|
removeChild: function (child) {
|
|
var children = this.getChildren();
|
|
children.erase(child);
|
|
},
|
|
|
|
/** */
|
|
getChildren: function () {
|
|
var result = this._children;
|
|
if (!$defined(result)) {
|
|
this._children = [];
|
|
result = this._children;
|
|
}
|
|
return result;
|
|
},
|
|
|
|
/** */
|
|
removeFromWorkspace: function (workspace) {
|
|
var elem2d = this.get2DElement();
|
|
workspace.removeChild(elem2d);
|
|
var line = this.getOutgoingLine();
|
|
if ($defined(line)) {
|
|
workspace.removeChild(line);
|
|
}
|
|
this._isInWorkspace = false;
|
|
EventBus.instance.fireEvent(EventBus.events.NodeRemoved, this.getModel());
|
|
},
|
|
|
|
/** */
|
|
addToWorkspace: function (workspace) {
|
|
var elem = this.get2DElement();
|
|
workspace.append(elem);
|
|
if (!this.isInWorkspace()) {
|
|
if (!this.isCentralTopic()) {
|
|
EventBus.instance.fireEvent(EventBus.events.NodeAdded, this.getModel());
|
|
}
|
|
|
|
if (this.getModel().isConnected())
|
|
EventBus.instance.fireEvent(EventBus.events.NodeConnectEvent, {
|
|
parentNode: this.getOutgoingConnectedTopic().getModel(),
|
|
childNode: this.getModel(),
|
|
});
|
|
}
|
|
this._isInWorkspace = true;
|
|
this._adjustShapes();
|
|
},
|
|
|
|
/** */
|
|
isInWorkspace: function () {
|
|
return this._isInWorkspace;
|
|
},
|
|
|
|
/** */
|
|
createDragNode: function (layoutManager) {
|
|
var result = this.parent(layoutManager);
|
|
|
|
// Is the node already connected ?
|
|
var targetTopic = this.getOutgoingConnectedTopic();
|
|
if ($defined(targetTopic)) {
|
|
result.connectTo(targetTopic);
|
|
result.setVisibility(false);
|
|
}
|
|
|
|
// If a drag node is create for it, let's hide the editor.
|
|
this._getTopicEventDispatcher().close();
|
|
|
|
return result;
|
|
},
|
|
|
|
_adjustShapes: function () {
|
|
if (this._isInWorkspace) {
|
|
var textShape = this.getTextShape();
|
|
if (this.getShapeType() != TopicShape.IMAGE) {
|
|
var textWidth = textShape.getWidth();
|
|
|
|
var textHeight = textShape.getHeight();
|
|
textHeight = textHeight != 0 ? textHeight : 20;
|
|
|
|
var topicPadding = TopicStyle.getInnerPadding(this);
|
|
|
|
// Adjust the icon size to the size of the text ...
|
|
var iconGroup = this.getOrBuildIconGroup();
|
|
var fontHeight = this.getTextShape().getFontHeight();
|
|
iconGroup.setPosition(topicPadding, topicPadding);
|
|
iconGroup.seIconSize(fontHeight, fontHeight);
|
|
|
|
// Add a extra padding between the text and the icons
|
|
var iconsWidth = iconGroup.getSize().width;
|
|
if (iconsWidth != 0) {
|
|
iconsWidth = iconsWidth + textHeight / 4;
|
|
}
|
|
|
|
var height = textHeight + topicPadding * 2;
|
|
var width = textWidth + iconsWidth + topicPadding * 2;
|
|
|
|
this.setSize({ width: width, height: height });
|
|
|
|
// Position node ...
|
|
textShape.setPosition(topicPadding + iconsWidth, topicPadding);
|
|
} else {
|
|
// In case of images, the size if fixed ...
|
|
var size = this.getModel().getImageSize();
|
|
this.setSize(size);
|
|
}
|
|
}
|
|
},
|
|
|
|
_flatten2DElements: function (topic) {
|
|
var result = [];
|
|
|
|
var children = topic.getChildren();
|
|
for (var i = 0; i < children.length; i++) {
|
|
var child = children[i];
|
|
result.push(child);
|
|
result.push(child.getOutgoingLine());
|
|
|
|
var relationships = child.getRelationships();
|
|
result = result.concat(relationships);
|
|
|
|
if (!child.areChildrenShrunken()) {
|
|
var innerChilds = this._flatten2DElements(child);
|
|
result = result.concat(innerChilds);
|
|
}
|
|
}
|
|
return result;
|
|
},
|
|
|
|
/**
|
|
* @param childTopic
|
|
* @return {Boolean} true if childtopic is a child topic of this topic or the topic itself
|
|
*/
|
|
isChildTopic: function (childTopic) {
|
|
var result = this.getId() == childTopic.getId();
|
|
if (!result) {
|
|
var children = this.getChildren();
|
|
for (var i = 0; i < children.length; i++) {
|
|
var parent = children[i];
|
|
result = parent.isChildTopic(childTopic);
|
|
if (result) {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
return result;
|
|
},
|
|
|
|
/** @return {Boolean} true if the topic is the central topic of the map */
|
|
isCentralTopic: function () {
|
|
return this.getModel().getType() == INodeModel.CENTRAL_TOPIC_TYPE;
|
|
},
|
|
}
|
|
);
|
|
|
|
/**
|
|
* @constant
|
|
* @type {Number}
|
|
* @default
|
|
*/
|
|
Topic.CONNECTOR_WIDTH = 6;
|
|
/**
|
|
* @constant
|
|
* @type {Object<String, Number>}
|
|
* @default
|
|
*/
|
|
Topic.OUTER_SHAPE_ATTRIBUTES = {
|
|
fillColor: 'rgb(252,235,192)',
|
|
stroke: '1 dot rgb(241,163,39)',
|
|
x: 0,
|
|
y: 0,
|
|
};
|
|
/**
|
|
* @constant
|
|
* @type {Object<String, Number>}
|
|
* @default
|
|
*/
|
|
Topic.OUTER_SHAPE_ATTRIBUTES_FOCUS = { fillColor: 'rgb(244,184,45)', x: 0, y: 0 };
|
|
/**
|
|
* @constant
|
|
* @type {Object<String>}
|
|
* @default
|
|
* */
|
|
Topic.INNER_RECT_ATTRIBUTES = { stroke: '2 solid' };
|
|
|
|
export default Topic;
|