\
-
\
+
\
+
\
\
\
@@ -1078,10 +1135,11 @@ joint.shapes.ice.InfoView = joint.shapes.ice.ModelView.extend({
this.updateBox();
this.updating = false;
- var selector = this.$box.find('#' + editorLabel);
+ this.textSelector = this.$box.find('#' + textLabel);
+ this.editorSelector = this.$box.find('#' + editorLabel);
// Prevent paper from handling pointerdown.
- selector.on('mousedown click', function(event) { event.stopPropagation(); });
+ this.editorSelector.on('mousedown click', function(event) { event.stopPropagation(); });
this.deltas = [];
this.counter = 0;
@@ -1089,7 +1147,7 @@ joint.shapes.ice.InfoView = joint.shapes.ice.ModelView.extend({
var undoGroupingInterval = 200;
var self = this;
- this.editor = ace.edit(selector[0]);
+ this.editor = ace.edit(this.editorSelector[0]);
this.editor.$blockScrolling = Infinity;
this.editor.commands.removeCommand('undo');
this.editor.commands.removeCommand('redo');
@@ -1117,6 +1175,16 @@ joint.shapes.ice.InfoView = joint.shapes.ice.ModelView.extend({
});
this.editor.on('focus', function() {
$(document).trigger('disableSelected');
+ // Show cursor
+ self.editor.renderer.$cursorLayer.element.style.opacity = 1;
+ });
+ this.editor.on('blur', function() {
+ var selection = self.editor.session.selection;
+ if (selection) {
+ selection.clearSelection();
+ }
+ // Hide cursor
+ self.editor.renderer.$cursorLayer.element.style.opacity = 0;
});
this.editor.on('paste', function(e) {
if (e.text.startsWith('{"icestudio":')) {
@@ -1156,7 +1224,6 @@ joint.shapes.ice.InfoView = joint.shapes.ice.ModelView.extend({
}
break;
case 'data':
-
break;
default:
break;
@@ -1173,8 +1240,41 @@ joint.shapes.ice.InfoView = joint.shapes.ice.ModelView.extend({
}, 10, this);
},
+ applyReadonly: function() {
+ var readonly = this.model.get('data').readonly;
+ if (readonly) {
+ this.$box.addClass('info-block-readonly');
+ this.textSelector.removeClass('hidden');
+ this.editorSelector.addClass('hidden');
+ this.disableResizer();
+ // Hide cursor
+ this.editor.renderer.$cursorLayer.element.style.opacity = 0;
+ // Clear selection
+ var selection = this.editor.session.selection;
+ if (selection) {
+ selection.clearSelection();
+ }
+ }
+ else {
+ this.$box.removeClass('info-block-readonly');
+ this.textSelector.addClass('hidden');
+ this.editorSelector.removeClass('hidden');
+ this.enableResizer();
+ // Show cursor
+ this.editor.renderer.$cursorLayer.element.style.opacity = 1;
+ }
+ },
+
+ applyText: function() {
+ var data = this.model.get('data');
+ this.textSelector.children().html(data.text || '');
+ },
+
apply: function(opt) {
this.applyValue(opt);
+ this.applyReadonly();
+ this.applyText();
+ this.updateBox();
},
render: function() {
@@ -1192,14 +1292,27 @@ joint.shapes.ice.InfoView = joint.shapes.ice.ModelView.extend({
updateBox: function() {
var bbox = this.model.getBBox();
var state = this.model.get('state');
+ var data = this.model.get('data');
- // Set font size
- if (this.editor) {
- this.editor.setFontSize(aceFontSize * state.zoom);
+ if (data.readonly) {
+ this.$box.find('.info-text').css({
+ margin: 8 * state.zoom,
+ 'border-radius': 5 * state.zoom,
+ fontSize: Math.round(aceFontSize * state.zoom)
+ });
+ }
+ else if (this.editor) {
+ this.$box.find('.info-editor').css({
+ margin: 8 * state.zoom,
+ 'border-radius': 5 * state.zoom
+ });
+ this.editor.setFontSize(Math.round(aceFontSize * state.zoom));
this.editor.resize();
}
- this.$box.find('.info-editor').css('margin', 8 * state.zoom);
+ this.$box.css({
+ 'border-radius': 5 * state.zoom
+ });
this.$box.css({ width: bbox.width * state.zoom,
height: bbox.height * state.zoom,
left: bbox.x * state.zoom + state.pan.x,
@@ -1245,7 +1358,7 @@ joint.shapes.ice.Wire = joint.dia.Link.extend({
arrowheadMarkup: [
'
',
- '',
+ '',
''
].join(''),
@@ -1273,20 +1386,18 @@ joint.shapes.ice.Wire = joint.dia.Link.extend({
type: 'ice.Wire',
- labels: [
- {
- position: 0.5,
- attrs: {
- text: {
- text: '',
- 'font-weight': 'bold',
- 'font-size': '13px',
- 'text-anchor': 'middle',
- 'y': '4px'
- }
+ labels: [{
+ position: 0.5,
+ attrs: {
+ text: {
+ text: '',
+ y: '4px',
+ 'font-weight': 'bold',
+ 'font-size': '13px',
+ 'text-anchor': 'middle'
}
}
- ],
+ }],
attrs: {
'.connection': {
@@ -1304,6 +1415,12 @@ joint.shapes.ice.Wire = joint.dia.Link.extend({
joint.shapes.ice.WireView = joint.dia.LinkView.extend({
+ options: {
+ shortLinkLength: 100,
+ longLinkLength: 160,
+ linkToolsOffset: 40,
+ },
+
initialize: function() {
joint.dia.LinkView.prototype.initialize.apply(this, arguments);
@@ -1399,6 +1516,29 @@ joint.shapes.ice.WireView = joint.dia.LinkView.extend({
return this;
},
+ updateToolsPosition: function() {
+ if (!this._V.linkTools) {
+ return this;
+ }
+
+ var scale = '';
+ var offset = this.options.linkToolsOffset;
+ var connectionLength = this.getConnectionLength();
+
+ if (!_.isNaN(connectionLength)) {
+ // If the link is too short, make the tools half the size and the offset twice as low.
+ if (connectionLength < this.options.shortLinkLength) {
+ scale = 'scale(.5)';
+ offset /= 2;
+ }
+
+ var toolPosition = this.getPointAtLength(connectionLength - offset);
+ this._toolCache.attr('transform', 'translate(' + toolPosition.x + ', ' + toolPosition.y + ') ' + scale);
+ }
+
+ return this;
+ },
+
updateWireProperties: function(size) {
if (size > 1) {
this.$('.connection').css('stroke-width', WIRE_WIDTH * 3);
diff --git a/app/scripts/services/blocks.js b/app/scripts/services/blocks.js
index 276ce8a85..0d6b26707 100644
--- a/app/scripts/services/blocks.js
+++ b/app/scripts/services/blocks.js
@@ -19,29 +19,29 @@ angular.module('icestudio')
//-- New
- function newBasic(type, addCellCallback) {
+ function newBasic(type, addCellsCallback) {
switch(type) {
case 'basic.input':
- newBasicInput(addCellCallback);
+ newBasicInput(addCellsCallback);
break;
case 'basic.output':
- newBasicOutput(addCellCallback);
+ newBasicOutput(addCellsCallback);
break;
case 'basic.constant':
- newBasicConstant(addCellCallback);
+ newBasicConstant(addCellsCallback);
break;
case 'basic.code':
- newBasicCode(addCellCallback);
+ newBasicCode(addCellsCallback);
break;
case 'basic.info':
- newBasicInfo(addCellCallback);
+ newBasicInfo(addCellsCallback);
break;
default:
break;
}
}
- function newBasicInput(addCellCallback) {
+ function newBasicInput(addCellsCallback) {
var blockInstance = {
id: null,
data: {},
@@ -82,6 +82,7 @@ angular.module('icestudio')
}
}
// Create blocks
+ var cells = [];
for (var p in portInfos) {
portInfo = portInfos[p];
var pins = getPins(portInfo);
@@ -92,16 +93,17 @@ angular.module('icestudio')
virtual: virtual,
clock: clock
};
- if (addCellCallback) {
- addCellCallback(loadBasic(blockInstance));
- }
+ cells.push(loadBasic(blockInstance));
// Next block position
blockInstance.position.y += (virtual ? 10 : (6 + 4 * pins.length)) * gridsize;
}
+ if (addCellsCallback) {
+ addCellsCallback(cells);
+ }
});
}
- function newBasicOutput(addCellCallback) {
+ function newBasicOutput(addCellsCallback) {
var blockInstance = {
id: null,
data: {},
@@ -139,6 +141,7 @@ angular.module('icestudio')
}
}
// Create blocks
+ var cells = [];
for (var p in portInfos) {
portInfo = portInfos[p];
var pins = getPins(portInfo);
@@ -148,12 +151,13 @@ angular.module('icestudio')
pins: pins,
virtual: virtual
};
- if (addCellCallback) {
- addCellCallback(loadBasic(blockInstance));
- }
+ cells.push(loadBasic(blockInstance));
// Next block position
blockInstance.position.y += (virtual ? 10 : (6 + 4 * pins.length)) * gridsize;
}
+ if (addCellsCallback) {
+ addCellsCallback(cells);
+ }
});
}
@@ -170,7 +174,7 @@ angular.module('icestudio')
return pins;
}
- function newBasicConstant(addCellCallback) {
+ function newBasicConstant(addCellsCallback) {
var blockInstance = {
id: null,
data: {},
@@ -208,6 +212,7 @@ angular.module('icestudio')
}
}
// Create blocks
+ var cells = [];
for (var p in paramInfos) {
paramInfo = paramInfos[p];
blockInstance.data = {
@@ -215,15 +220,16 @@ angular.module('icestudio')
value: '',
local: local
};
- if (addCellCallback) {
- addCellCallback(loadBasicConstant(blockInstance));
- }
+ cells.push(loadBasicConstant(blockInstance));
blockInstance.position.x += 15 * gridsize;
}
+ if (addCellsCallback) {
+ addCellsCallback(cells);
+ }
});
}
- function newBasicCode(addCellCallback, block) {
+ function newBasicCode(addCellsCallback, block) {
var blockInstance = {
id: null,
data: {
@@ -361,8 +367,8 @@ angular.module('icestudio')
if (numNames === $.unique(allNames).length) {
evt.cancel = false;
// Create block
- if (addCellCallback) {
- addCellCallback(loadBasicCode(blockInstance));
+ if (addCellsCallback) {
+ addCellsCallback([loadBasicCode(blockInstance)]);
}
}
else {
@@ -372,16 +378,16 @@ angular.module('icestudio')
});
}
- function newBasicInfo(addCellCallback) {
+ function newBasicInfo(addCellsCallback) {
var blockInstance = {
id: null,
- data: { info: '' },
+ data: { info: '', readonly: false },
type: 'basic.info',
position: { x: 40 * gridsize, y: 36 * gridsize },
size: { width: 192, height: 128 }
};
- if (addCellCallback) {
- addCellCallback(loadBasicInfo(blockInstance));
+ if (addCellsCallback) {
+ addCellsCallback([loadBasicInfo(blockInstance)]);
}
}
@@ -537,6 +543,10 @@ angular.module('icestudio')
}
function loadBasicInfo(instance, disabled) {
+ // Translate info content
+ if (instance.data.info && instance.data.readonly) {
+ instance.data.text = gettextCatalog.getString(instance.data.info);
+ }
var cell = new joint.shapes.ice.Info({
id: instance.id,
blockType: instance.type,
@@ -624,7 +634,7 @@ angular.module('icestudio')
pullup: block.design.pullup,
image: blockImage,
label: blockLabel,
- tooltip: gettextCatalog.getString(block.package.description), // TODO: update on change language
+ tooltip: gettextCatalog.getString(block.package.description),
position: instance.position,
size: size,
disabled: disabled,
@@ -685,6 +695,9 @@ angular.module('icestudio')
case 'basic.code':
editBasicCode(cellView, addCellCallback);
break;
+ case 'basic.info':
+ editBasicInfo(cellView);
+ break;
default:
break;
}
@@ -890,25 +903,28 @@ angular.module('icestudio')
position: block.position,
size: block.size
};
- newBasicCode(function(cell) {
+ newBasicCode(function(cells) {
if (addCellCallback) {
- var connectedWires = graph.getConnectedLinks(cellView.model);
- graph.startBatch('change');
- cellView.model.remove();
- addCellCallback(cell);
- // Restore previous connections
- for (var w in connectedWires) {
- var wire = connectedWires[w];
- var source = wire.get('source');
- var target = wire.get('target');
- if ((source.id === cell.id && containsPort(source.port, cell.get('rightPorts'))) ||
- (target.id === cell.id && containsPort(target.port, cell.get('leftPorts')) && source.port !== 'constant-out') ||
- (target.id === cell.id && containsPort(target.port, cell.get('topPorts')) && source.port === 'constant-out')) {
- graph.addCell(wire);
+ var cell = cells[0];
+ if (cell) {
+ var connectedWires = graph.getConnectedLinks(cellView.model);
+ graph.startBatch('change');
+ cellView.model.remove();
+ addCellCallback(cell);
+ // Restore previous connections
+ for (var w in connectedWires) {
+ var wire = connectedWires[w];
+ var source = wire.get('source');
+ var target = wire.get('target');
+ if ((source.id === cell.id && containsPort(source.port, cell.get('rightPorts'))) ||
+ (target.id === cell.id && containsPort(target.port, cell.get('leftPorts')) && source.port !== 'constant-out') ||
+ (target.id === cell.id && containsPort(target.port, cell.get('topPorts')) && source.port === 'constant-out')) {
+ graph.addCell(wire);
+ }
}
+ graph.stopBatch('change');
+ alertify.success(gettextCatalog.getString('Block updated'));
}
- graph.stopBatch('change');
- alertify.success(gettextCatalog.getString('Block updated'));
}
}, blockInstance);
}
@@ -924,4 +940,25 @@ angular.module('icestudio')
return found;
}
+ function editBasicInfo(cellView) {
+ var block = cellView.model.attributes;
+ utils.checkboxprompt([
+ gettextCatalog.getString('Read only')
+ ], [
+ block.data.readonly || false,
+ ],
+ function(evt, values) {
+ var readonly = values[0];
+ var data = utils.clone(block.data);
+ data.readonly = readonly;
+ // Translate info content
+ if (data.info && data.readonly) {
+ data.text = gettextCatalog.getString(data.info);
+ }
+ cellView.model.set('data', data);
+ cellView.apply();
+ alertify.success(gettextCatalog.getString('Block updated'));
+ });
+ }
+
});
diff --git a/app/scripts/services/boards.js b/app/scripts/services/boards.js
index 3e8ed6ff7..00df50ce2 100644
--- a/app/scripts/services/boards.js
+++ b/app/scripts/services/boards.js
@@ -7,9 +7,8 @@ angular.module('icestudio')
nodePath) {
const DEFAULT = 'icezum';
- this.currentBoards = loadBoards(nodePath.join('resources', 'boards'));
-
- function loadBoards(path) {
+ this.loadBoards = function(path) {
+ path = path || nodePath.join('resources', 'boards');
var boards = [];
var contents = nodeFs.readdirSync(path);
contents.forEach(function (content) {
@@ -28,8 +27,8 @@ angular.module('icestudio')
}
}
});
- return boards;
- }
+ common.boards = _.sortBy(boards, 'info.label');
+ };
function readJSONFile(filepath, filename) {
var ret = {};
@@ -41,27 +40,21 @@ angular.module('icestudio')
return ret;
}
- var self = this;
-
- $(document).on('boardChanged', function(evt, name) {
- self.selectBoard(name);
- });
-
this.selectBoard = function(name) {
name = name || DEFAULT;
var i;
var selectedBoard = null;
- for (i in this.currentBoards) {
- if (this.currentBoards[i].name === name) {
- selectedBoard = this.currentBoards[i];
+ for (i in common.boards) {
+ if (common.boards[i].name === name) {
+ selectedBoard = common.boards[i];
break;
}
}
if (selectedBoard === null) {
// Board not found: select default board
- for (i in this.currentBoards) {
- if (this.currentBoards[i].name === DEFAULT) {
- selectedBoard = this.currentBoards[i];
+ for (i in common.boards) {
+ if (common.boards[i].name === DEFAULT) {
+ selectedBoard = common.boards[i];
break;
}
}
@@ -70,13 +63,13 @@ angular.module('icestudio')
common.pinoutInputHTML = generateHTMLOptions(common.selectedBoard.pinout, 'input');
common.pinoutOutputHTML = generateHTMLOptions(common.selectedBoard.pinout, 'output');
utils.rootScopeSafeApply();
- return common.selectedBoard.name;
+ return common.selectedBoard;
};
this.boardLabel = function(name) {
- for (var i in this.currentBoards) {
- if (this.currentBoards[i].name === name) {
- return this.currentBoards[i].info.label;
+ for (var i in common.boards) {
+ if (common.boards[i].name === name) {
+ return common.boards[i].info.label;
}
}
return name;
diff --git a/app/scripts/services/resources.js b/app/scripts/services/collections.js
similarity index 66%
rename from app/scripts/services/resources.js
rename to app/scripts/services/collections.js
index fbdf161c5..41cf266bf 100644
--- a/app/scripts/services/resources.js
+++ b/app/scripts/services/collections.js
@@ -1,31 +1,29 @@
'use strict';
angular.module('icestudio')
- .service('resources', function(utils,
- nodePath) {
+ .service('collections', function(utils,
+ common,
+ nodePath) {
const DEFAULT = '';
- this.collections = [];
- this.selectedCollection = null;
-
this.loadCollections = function() {
- this.collections = [];
+ common.collections = [];
// Add Default collection
var defaultPath = nodePath.join('resources', 'collection');
var defaultData = {
'name': DEFAULT,
'path': nodePath.resolve(defaultPath),
- 'children': utils.getFilesRecursive(defaultPath)
+ 'children': utils.getFilesRecursive(nodePath.resolve(defaultPath))
};
var defaultCollection = getCollection(defaultData);
- this.collections.push(defaultCollection);
+ common.collections.push(defaultCollection);
// Add installed collections
- var data = utils.getFilesRecursive(utils.COLLECTIONS_DIR);
+ var data = utils.getFilesRecursive(common.COLLECTIONS_DIR);
for (var i in data) {
var collection = getCollection(data[i]);
if (collection) {
- this.collections.push(collection);
+ common.collections.push(collection);
}
}
};
@@ -37,7 +35,8 @@ angular.module('icestudio')
content: {
blocks: [],
examples: [],
- package: {}
+ package: {},
+ readme: ''
}
};
for (var i in data.children) {
@@ -59,27 +58,30 @@ angular.module('icestudio')
//collection.content.package = require(child.path);
}
break;
+ case ('README'):
+ if (!child.children) {
+ collection.content.readme = child.path;
+ }
+ break;
}
}
return collection;
}
- this.loadCollections();
-
this.selectCollection = function(name) {
name = name || DEFAULT;
var selectedCollection = null;
- for (var i in this.collections) {
- if (this.collections[i].name === name) {
- selectedCollection = this.collections[i];
+ for (var i in common.collections) {
+ if (common.collections[i].name === name) {
+ selectedCollection = common.collections[i];
break;
}
}
if (selectedCollection === null) {
// Collection not found: select default collection
- selectedCollection = this.collections[0];
+ selectedCollection = common.collections[0];
}
- this.selectedCollection = selectedCollection;
+ common.selectedCollection = selectedCollection;
return selectedCollection.name;
};
diff --git a/app/scripts/services/common.js b/app/scripts/services/common.js
index b8f8874d6..06e2b0048 100644
--- a/app/scripts/services/common.js
+++ b/app/scripts/services/common.js
@@ -1,7 +1,7 @@
'use strict';
angular.module('icestudio')
- .service('common', function() {
+ .service('common', function(nodePath) {
// Project version
this.VERSION = '1.1';
@@ -10,10 +10,70 @@ angular.module('icestudio')
this.allDependencies = {};
// Selected board
+ this.boards = [];
this.selectedBoard = null;
this.pinoutInputHTML = '';
this.pinoutOutputHTML = '';
- // TODO: move all constants
+ // Selected collection
+ this.collections = [];
+ this.selectedCollection = null;
+
+ // OS
+ this.LINUX = Boolean(process.platform.indexOf('linux') > -1);
+ this.WIN32 = Boolean(process.platform.indexOf('win32') > -1);
+ this.DARWIN = Boolean(process.platform.indexOf('darwin') > -1);
+
+ // Paths
+ this.LOCALE_DIR = nodePath.join('resources', 'locale');
+ this.SAMPLE_DIR = nodePath.join('resources', 'sample');
+
+ this.BASE_DIR = process.env.HOME || process.env.USERPROFILE;
+ this.ICESTUDIO_DIR = safeDir(nodePath.join(this.BASE_DIR, '.icestudio'), this);
+ this.COLLECTIONS_DIR = nodePath.join(this.ICESTUDIO_DIR, 'collections');
+ this.APIO_HOME_DIR = nodePath.join(this.ICESTUDIO_DIR, 'apio');
+ this.PROFILE_PATH = nodePath.join(this.ICESTUDIO_DIR, 'profile.json');
+ this.CACHE_DIR = nodePath.join(this.ICESTUDIO_DIR, '.cache');
+ this.BUILD_DIR = nodePath.join(this.ICESTUDIO_DIR, '.build');
+
+ this.VENV = 'virtualenv-15.0.1';
+ this.VENV_DIR = nodePath.join(this.CACHE_DIR, this.VENV);
+ this.VENV_TARGZ = nodePath.join('resources', 'virtualenv', this.VENV + '.tar.gz');
+
+ this.APP_DIR = nodePath.dirname(process.execPath);
+ this.TOOLCHAIN_DIR = nodePath.join(this.APP_DIR, 'toolchain');
+
+ this.DEFAULT_APIO = 'default-apio';
+ this.DEFAULT_APIO_DIR = nodePath.join(this.CACHE_DIR, this.DEFAULT_APIO);
+ this.DEFAULT_APIO_TARGZ = nodePath.join(this.TOOLCHAIN_DIR, this.DEFAULT_APIO + '.tar.gz');
+
+ this.DEFAULT_APIO_PACKAGES = 'default-apio-packages';
+ this.DEFAULT_APIO_PACKAGES_TARGZ = nodePath.join(this.TOOLCHAIN_DIR, this.DEFAULT_APIO_PACKAGES + '.tar.gz');
+
+ this.ENV_DIR = nodePath.join(this.ICESTUDIO_DIR, 'venv');
+ this.ENV_BIN_DIR = nodePath.join(this.ENV_DIR, this.WIN32 ? 'Scripts' : 'bin');
+ this.ENV_PIP = nodePath.join(this.ENV_BIN_DIR, 'pip');
+ this.ENV_APIO = nodePath.join(this.ENV_BIN_DIR, this.WIN32 ? 'apio.exe' : 'apio');
+ this.APIO_CMD = (this.WIN32 ? 'set' : 'export') + ' APIO_HOME_DIR=' + this.APIO_HOME_DIR + (this.WIN32 ? '& ' : '; ') + '"' + this.ENV_APIO + '"';
+ this.SYSTEM_APIO = '/usr/bin/apio';
+
+ function safeDir(_dir, self) {
+ if (self.WIN32) {
+ // Put the env directory to the root of the current local disk when
+ // default path contains non-ASCII characters. Virtualenv will fail to
+ for (var i in _dir) {
+ if (_dir[i].charCodeAt(0) > 127) {
+ const _dirFormat = nodePath.parse(_dir);
+ return nodePath.format({
+ root: _dirFormat.root,
+ dir: _dirFormat.root,
+ base: '.icestudio',
+ name: '.icestudio',
+ });
+ }
+ }
+ }
+ return _dir;
+ }
});
diff --git a/app/scripts/services/compiler.js b/app/scripts/services/compiler.js
index 11ca86f7d..7e6b9c95f 100644
--- a/app/scripts/services/compiler.js
+++ b/app/scripts/services/compiler.js
@@ -2,6 +2,7 @@
angular.module('icestudio')
.service('compiler', function(common,
+ utils,
nodeSha1,
_package) {
@@ -40,13 +41,6 @@ angular.module('icestudio')
return header;
}
- function digestId(id) {
- if (id.indexOf('-') !== -1) {
- id = nodeSha1(id).toString();
- }
- return 'v' + id.substring(0, 6);
- }
-
function module(data) {
var code = '';
if (data && data.name && data.ports) {
@@ -119,7 +113,7 @@ angular.module('icestudio')
var block = graph.blocks[i];
if (block.type === 'basic.constant') {
params.push({
- name: digestId(block.id),
+ name: utils.digestId(block.id),
value: block.data.value
});
}
@@ -139,13 +133,13 @@ angular.module('icestudio')
var block = graph.blocks[i];
if (block.type === 'basic.input') {
ports.in.push({
- name: digestId(block.id),
+ name: utils.digestId(block.id),
range: block.data.range ? block.data.range : ''
});
}
else if (block.type === 'basic.output') {
ports.out.push({
- name: digestId(block.id),
+ name: utils.digestId(block.id),
range: block.data.range ? block.data.range : ''
});
}
@@ -169,7 +163,7 @@ angular.module('icestudio')
if (wire.source.port === 'constant-out') {
// Local Parameters
var constantBlock = findBlock(wire.source.block, graph);
- var paramValue = digestId(constantBlock.id);
+ var paramValue = utils.digestId(constantBlock.id);
if (paramValue) {
connections.localparam.push('localparam p' + w + ' = ' + paramValue + ';');
}
@@ -184,7 +178,7 @@ angular.module('icestudio')
var block = graph.blocks[i];
if (block.type === 'basic.input') {
if (wire.source.block === block.id) {
- connections.assign.push('assign w' + w + ' = ' + digestId(block.id) + ';');
+ connections.assign.push('assign w' + w + ' = ' + utils.digestId(block.id) + ';');
}
}
else if (block.type === 'basic.output') {
@@ -193,7 +187,7 @@ angular.module('icestudio')
// connections.assign.push('assign ' + digestId(block.id) + ' = p' + w + ';');
}
else {
- connections.assign.push('assign ' + digestId(block.id) + ' = w' + w + ';');
+ connections.assign.push('assign ' + utils.digestId(block.id) + ' = w' + w + ';');
}
}
}
@@ -243,10 +237,10 @@ angular.module('icestudio')
var instance;
if (block.type === 'basic.code') {
- instance = name + '_' + digestId(block.id);
+ instance = name + '_' + utils.digestId(block.id);
}
else {
- instance = digestId(block.type);
+ instance = utils.digestId(block.type);
}
//-- Parameters
@@ -258,7 +252,7 @@ angular.module('icestudio')
(wire.source.port === 'constant-out')) {
var paramName = wire.target.port;
if (block.type !== 'basic.code') {
- paramName = digestId(paramName);
+ paramName = utils.digestId(paramName);
}
var param = '';
param += ' .' + paramName;
@@ -273,7 +267,7 @@ angular.module('icestudio')
//-- Instance name
- instance += ' ' + digestId(block.id);
+ instance += ' ' + utils.digestId(block.id);
//-- Ports
@@ -302,7 +296,7 @@ angular.module('icestudio')
function connectPort(portName, portsNames, ports, block) {
if (portName) {
if (block.type !== 'basic.code') {
- portName = digestId(portName);
+ portName = utils.digestId(portName);
}
if (portsNames.indexOf(portName) === -1) {
portsNames.push(portName);
@@ -504,7 +498,7 @@ angular.module('icestudio')
// Dependencies modules
for (var d in dependencies) {
- code += verilogCompiler(digestId(d), dependencies[d]);
+ code += verilogCompiler(utils.digestId(d), dependencies[d]);
}
// Code modules
@@ -514,10 +508,10 @@ angular.module('icestudio')
if (block) {
if (block.type === 'basic.code') {
data = {
- name: name + '_' + digestId(block.id),
+ name: name + '_' + utils.digestId(block.id),
params: block.data.params,
ports: block.data.ports,
- content: block.data.code.replace(/\n+/g, '\n').replace(/\n$/g, '')
+ content: block.data.code //.replace(/\n+/g, '\n').replace(/\n$/g, '')
};
code += module(data);
}
@@ -543,7 +537,7 @@ angular.module('icestudio')
pin = block.data.pins[p];
value = block.data.virtual ? '' : pin.value;
code += 'set_io ';
- code += digestId(block.id);
+ code += utils.digestId(block.id);
code += '[' + pin.index + '] ';
code += value;
code += '\n';
@@ -553,7 +547,7 @@ angular.module('icestudio')
pin = block.data.pins[0];
value = block.data.virtual ? '' : pin.value;
code += 'set_io ';
- code += digestId(block.id);
+ code += utils.digestId(block.id);
code += ' ';
code += value;
code += '\n';
@@ -747,14 +741,14 @@ angular.module('icestudio')
if (block.type === 'basic.input') {
if (block.data.name) {
input.push({
- id: digestId(block.id),
+ id: utils.digestId(block.id),
name: block.data.name.replace(/ /g, '_'),
range: block.data.range
});
}
else {
input.push({
- id: digestId(block.id),
+ id: utils.digestId(block.id),
name: inputUnnamed.toString(),
});
inputUnnamed += 1;
@@ -763,14 +757,14 @@ angular.module('icestudio')
else if (block.type === 'basic.output') {
if (block.data.name) {
output.push({
- id: digestId(block.id),
+ id: utils.digestId(block.id),
name: block.data.name.replace(/ /g, '_'),
range: block.data.range
});
}
else {
output.push({
- id: digestId(block.id),
+ id: utils.digestId(block.id),
name: outputUnnamed.toString()
});
outputUnnamed += 1;
@@ -794,14 +788,14 @@ angular.module('icestudio')
if (!block.data.local) {
if (block.data.name) {
params.push({
- id: digestId(block.id),
+ id: utils.digestId(block.id),
name: 'constant_' + block.data.name.replace(/ /g, '_'),
value: block.data.value
});
}
else {
params.push({
- id: digestId(block.id),
+ id: utils.digestId(block.id),
name: 'constant_' + paramsUnnamed.toString(),
value: block.data.value
});
diff --git a/app/scripts/services/drivers.js b/app/scripts/services/drivers.js
new file mode 100644
index 000000000..cf1727f85
--- /dev/null
+++ b/app/scripts/services/drivers.js
@@ -0,0 +1,229 @@
+'use strict';
+
+angular.module('icestudio')
+ .service('drivers', function(gettextCatalog,
+ profile,
+ common,
+ gui,
+ nodePath,
+ nodeSudo,
+ nodeChildProcess) {
+
+ this.enable = function() {
+ if (common.WIN32) {
+ enableWindowsDrivers();
+ }
+ else if (common.DARWIN) {
+ enableDarwinDrivers();
+ }
+ else {
+ linuxDrivers(true);
+ }
+ };
+
+ this.disable = function() {
+ if (common.WIN32) {
+ disableWindowsDrivers();
+ }
+ else if (common.DARWIN) {
+ disableDarwinDrivers();
+ }
+ else {
+ linuxDrivers(false);
+ }
+ };
+
+ this.preUpload = function(callback) {
+ if (common.DARWIN) {
+ preUploadDarwin(callback);
+ }
+ else {
+ if (callback) {
+ callback();
+ }
+ }
+ };
+
+ this.postUpload = function() {
+ if (common.DARWIN) {
+ postUploadDarwin();
+ }
+ };
+
+ function linuxDrivers(enable) {
+ var commands;
+ if (enable) {
+ commands = [
+ 'cp ' + nodePath.resolve('resources/config/80-icestick.rules') + ' /etc/udev/rules.d/80-icestick.rules',
+ 'service udev restart'
+ ];
+ }
+ else {
+ commands = [
+ 'rm /etc/udev/rules.d/80-icestick.rules',
+ 'service udev restart'
+ ];
+ }
+ var command = 'sh -c "' + commands.join('; ') + '"';
+
+ beginLazyProcess();
+ nodeSudo.exec(command, {name: 'Icestudio'}, function(error/*, stdout, stderr*/) {
+ // console.log(error, stdout, stderr);
+ endLazyProcess();
+ if (!error) {
+ if (enable) {
+ alertify.success(gettextCatalog.getString('Drivers enabled'));
+ }
+ else {
+ alertify.warning(gettextCatalog.getString('Drivers disabled'));
+ }
+ setTimeout(function() {
+ alertify.message(gettextCatalog.getString('
Unplug and
reconnect the board'), 5);
+ }, 1000);
+ }
+ });
+ }
+
+ function enableDarwinDrivers() {
+ var brewCommands = [
+ '/usr/local/bin/brew update',
+ '/usr/local/bin/brew install --force libftdi',
+ '/usr/local/bin/brew unlink libftdi',
+ '/usr/local/bin/brew link --force libftdi',
+ '/usr/local/bin/brew install --force libffi',
+ '/usr/local/bin/brew unlink libffi',
+ '/usr/local/bin/brew link --force libffi'
+ ];
+ beginLazyProcess();
+ nodeChildProcess.exec(brewCommands.join('; '), function(error, stdout, stderr) {
+ // console.log(error, stdout, stderr);
+ if (error) {
+ if ((stderr.indexOf('brew: command not found') !== -1) ||
+ (stderr.indexOf('brew: No such file or directory') !== -1)) {
+ alertify.warning(gettextCatalog.getString('{{app}} is required.', { app: '
Homebrew' }) + '
' +
+ '
' + gettextCatalog.getString('Click here to install it') + '', 30)
+ .callback = function(isClicked) {
+ if (isClicked) {
+ gui.Shell.openExternal('https://brew.sh');
+ }
+ };
+ }
+ else if (stderr.indexOf('Error: Failed to download') !== -1) {
+ alertify.error(gettextCatalog.getString('Internet connection required'), 30);
+ }
+ else {
+ alertify.error(stderr, 30);
+ }
+ }
+ else {
+ profile.set('macosDrivers', true);
+ alertify.success(gettextCatalog.getString('Drivers enabled'));
+ }
+ endLazyProcess();
+ });
+ }
+
+ function disableDarwinDrivers() {
+ profile.set('macosDrivers', false);
+ alertify.warning(gettextCatalog.getString('Drivers disabled'));
+ }
+
+ var driverC = '';
+
+ function preUploadDarwin(callback) {
+ if (profile.get('macosDrivers')) {
+ // Check and unload the Drivers
+ var driverA = 'com.FTDI.driver.FTDIUSBSerialDriver';
+ var driverB = 'com.apple.driver.AppleUSBFTDI';
+ if (checkDriverDarwin(driverA)) {
+ driverC = driverA;
+ processDriverDarwin(driverA, false, callback);
+ }
+ else if (checkDriverDarwin(driverB)) {
+ driverC = driverB;
+ processDriverDarwin(driverB, false, callback);
+ }
+ else {
+ driverC = '';
+ if (callback) {
+ callback();
+ }
+ }
+ }
+ else {
+ if (callback) {
+ callback();
+ }
+ }
+ }
+
+ function postUploadDarwin() {
+ if (profile.get('macosDrivers')) {
+ processDriverDarwin(driverC, true);
+ }
+ }
+
+ function checkDriverDarwin(driver) {
+ var output = nodeChildProcess.execSync('kextstat').toString();
+ return output.indexOf(driver) > -1;
+ }
+
+ function processDriverDarwin(driver, load, callback) {
+ if (driver) {
+ var command = (load ? 'kextload' : 'kextunload') + ' -b ' + driver;
+ nodeSudo.exec(command, {name: 'Icestudio'}, function(/*error, stdout, stderr*/) {
+ //console.log(error, stdout, stderr);
+ if (callback) {
+ callback();
+ }
+ });
+ }
+ else {
+ if (callback) {
+ callback();
+ }
+ }
+ }
+
+ function enableWindowsDrivers() {
+ alertify.confirm(gettextCatalog.getString('
FTDI driver installation instructions
- Connect the FPGA board
- Replace the (Interface 0) driver of the board by libusbK
- Unplug and reconnect the board
') +
+ gettextCatalog.getString('It is recommended to use
USB 2.0 ports'),
+ function() {
+ beginLazyProcess();
+ nodeSudo.exec([common.APIO_CMD, 'drivers', '--enable'].join(' '), {name: 'Icestudio'}, function(error, stdout, stderr) {
+ // console.log(error, stdout, stderr);
+ endLazyProcess();
+ if (stderr) {
+ alertify.error(gettextCatalog.getString('Toolchain not installed. Please, install the toolchain'), 30);
+ }
+ else if (!error) {
+ alertify.message(gettextCatalog.getString('
Unplug and
reconnect the board'), 5);
+ }
+ });
+ });
+ }
+
+ function disableWindowsDrivers() {
+ alertify.confirm(gettextCatalog.getString('
FTDI driver uninstallation instructions
- Find the FPGA USB Device
- Select the board interface and uninstall the driver
'), function() {
+ beginLazyProcess();
+ nodeChildProcess.exec([common.APIO_CMD, 'drivers', '--disable'].join(' '), function(error, stdout, stderr) {
+ // console.log(error, stdout, stderr);
+ endLazyProcess();
+ if (stderr) {
+ alertify.error(gettextCatalog.getString('Toolchain not installed. Please, install the toolchain'), 30);
+ }
+ });
+ });
+ }
+
+ function beginLazyProcess() {
+ $('body').addClass('waiting');
+ angular.element('#menu').addClass('disable-menu');
+ }
+
+ function endLazyProcess() {
+ $('body').removeClass('waiting');
+ angular.element('#menu').removeClass('disable-menu');
+ }
+
+ });
diff --git a/app/scripts/services/graph.js b/app/scripts/services/graph.js
index 27be97512..7704f98f1 100644
--- a/app/scripts/services/graph.js
+++ b/app/scripts/services/graph.js
@@ -12,23 +12,25 @@ angular.module('icestudio')
window) {
// Variables
- var z = {
- index: 100
- };
-
+ var z = { index: 100 };
var graph = null;
var paper = null;
var selection = null;
var selectionView = null;
var commandManager = null;
var mousePosition = { x: 0, y: 0 };
-
- this.breadcrumbs = [{ name: '', type: '' }];
-
+ var menuHeight = 51;
var gridsize = 8;
var state = { pan: { x: 0, y: 0 }, zoom: 1.0 };
- const ZOOM_MAX = 2.0;
+
+ var self = this;
+
+ const ZOOM_MAX = 2.1;
const ZOOM_MIN = 0.3;
+ const ZOOM_SENS = 0.3;
+
+ this.breadcrumbs = [{ name: '', type: '' }];
+ this.addingDraggableBlock = false;
// Functions
@@ -81,8 +83,8 @@ angular.module('icestudio')
else {
scale = tbox.width / sbox.width;
}
- if (state.zoom * scale > ZOOM_MAX) {
- scale = ZOOM_MAX / state.zoom;
+ if (state.zoom * scale > 1) {
+ scale = 1 / state.zoom;
}
var target = {
x: tbox.x + tbox.width / 2,
@@ -118,6 +120,7 @@ angular.module('icestudio')
height: 1000,
model: graph,
gridSize: gridsize,
+ clickThreshold: 6,
snapLinks: { radius: 16 },
linkPinning: false,
embeddingMode: false,
@@ -158,26 +161,27 @@ angular.module('icestudio')
var i;
var links = graph.getLinks();
for (i in links) {
- var linkIView = links[i].findView(paper);
+ var link = links[i];
+ var linkIView = link.findView(paper);
if (linkView === linkIView) {
//Skip the wire the user is drawing
continue;
}
// Prevent multiple input links
- if ((cellViewT.model.id === links[i].get('target').id) &&
- (magnetT.getAttribute('port') === links[i].get('target').port)) {
+ if ((cellViewT.model.id === link.get('target').id) &&
+ (magnetT.getAttribute('port') === link.get('target').port)) {
warning(gettextCatalog.getString('Invalid multiple input connections'));
return false;
}
// Prevent to connect a pull-up if other blocks are connected
if ((cellViewT.model.get('pullup')) &&
- (cellViewS.model.id === links[i].get('source').id)) {
+ (cellViewS.model.id === link.get('source').id)) {
warning(gettextCatalog.getString('Invalid
Pull up connection:
block already connected'));
return false;
}
// Prevent to connect other blocks if a pull-up is connected
if ((linkIView.targetView.model.get('pullup')) &&
- (cellViewS.model.id === links[i].get('source').id)) {
+ (cellViewS.model.id === link.get('source').id)) {
warning(gettextCatalog.getString('Invalid block connection:
Pull up already connected'));
return false;
}
@@ -252,7 +256,7 @@ angular.module('icestudio')
center: false,
zoomEnabled: true,
panEnabled: false,
- zoomScaleSensitivity: 0.1,
+ zoomScaleSensitivity: ZOOM_SENS,
dblClickZoomEnabled: false,
minZoom: ZOOM_MIN,
maxZoom: ZOOM_MAX,
@@ -290,84 +294,122 @@ angular.module('icestudio')
});
}
- // Events
+ // Events
- this.mousedown = false;
- $(document).on('mouseup', function() { self.mousedown = false; });
- $(document).on('mousedown', function() { self.mousedown = true; });
+ $('body').mousemove(function(event) {
+ mousePosition = {
+ x: event.pageX,
+ y: event.pageY
+ };
+ });
- var self = this;
- $('#paper').mousemove(function(event) {
- mousePosition = {
- x: event.offsetX,
- y: event.offsetY
- };
- });
+ selectionView.on('selection-box:pointerdown', function(/*evt*/) {
+ // Move selection to top view
+ if (selection) {
+ selection.each(function(cell) {
+ var cellView = paper.findViewByModel(cell);
+ if (!cellView.model.isLink()) {
+ if (cellView.$box.css('z-index') < z.index) {
+ cellView.$box.css('z-index', ++z.index);
+ }
+ }
+ });
+ }
+ });
- selectionView.on('selection-box:pointerdown', function(evt) {
- // Selection to top view
- if (selection) {
- selection.each(function(cell) {
- var cellView = paper.findViewByModel(cell);
- if (!cellView.model.isLink()) {
- if (cellView.$box.css('z-index') < z.index) {
- cellView.$box.css('z-index', ++z.index);
- }
- }
- });
- }
- // Toggle selection
- if (evt.which === 3) {
- var cell = selection.get($(evt.target).data('model'));
- selection.reset(selection.without(cell));
- selectionView.destroySelectionBox(paper.findViewByModel(cell));
- }
- });
+ selectionView.on('selection-box:pointerclick', function(evt) {
+ if (self.addingDraggableBlock) {
+ // Set new block position
+ self.addingDraggableBlock = false;
+ disableSelected();
+ updateWiresOnObstacles();
+ }
+ else {
+ // Toggle selected cell
+ if (utils.hasShift(evt)) {
+ var cell = selection.get($(evt.target).data('model'));
+ selection.reset(selection.without(cell));
+ selectionView.destroySelectionBox(cell);
+ }
+ }
+ });
- paper.on('cell:pointerup', function(cellView, evt/*, x, y*/) {
- if (paper.options.enabled) {
- if (!cellView.model.isLink()) {
- if (evt.which === 3) {
- // Disable current focus
- document.activeElement.blur();
- // Right button
- selection.add(cellView.model);
- selectionView.createSelectionBox(cellView);
- unhighlight(cellView);
- }
- updateWiresOnObstacles();
- }
- }
- });
+ var pointerDown = false;
+ var dblClickCell = false;
- paper.on('cell:pointerdown', function(cellView) {
- if (paper.options.enabled) {
- if (cellView.model.isLink()) {
- // Unhighlight source block of the wire
- unhighlight(paper.findViewByModel(cellView.model.get('source').id));
- }
- }
- });
+ paper.on('cell:pointerclick', function(cellView, evt/*, x, y*/) {
+ if (utils.hasShift(evt)) {
+ // If Shift is pressed process the click (no Shift+dblClick allowed)
+ processCellClick(cellView, evt);
+ }
+ else {
+ // If not, wait 150ms to ensure that it's not a dblclick
+ var ensureTime = 150;
+ pointerDown = false;
+ setTimeout(function() {
+ if (!dblClickCell && !pointerDown) {
+ processCellClick(cellView, evt);
+ }
+ }, ensureTime);
+ }
- paper.on('cell:pointerdblclick', function(cellView/*, evt, x, y*/) {
- var type = cellView.model.get('blockType');
- if (type.indexOf('basic.') !== -1) {
+ function processCellClick(cellView, evt) {
if (paper.options.enabled) {
- blocks.editBasic(type, cellView, function(cell) {
- addCell(cell);
- });
+ if (!cellView.model.isLink()) {
+ // Disable current focus
+ document.activeElement.blur();
+ if (utils.hasLeftButton(evt)) {
+ if (!utils.hasShift(evt)) {
+ // Cancel previous selection
+ disableSelected();
+ }
+ // Add cell to selection
+ selection.add(cellView.model);
+ selectionView.createSelectionBox(cellView.model);
+ //unhighlight(cellView);
+ }
+ }
}
}
- else if (common.allDependencies[type]) {
- z.index = 1;
- var project = common.allDependencies[type];
- var breadcrumbsLength = self.breadcrumbs.length;
- $rootScope.$broadcast('navigateProject', {
- update: breadcrumbsLength === 1,
- project: project
- });
- self.breadcrumbs.push({ name: project.package.name || '#', type: type });
- utils.rootScopeSafeApply();
+ });
+
+ paper.on('cell:pointerdown', function(/*cellView, evt, x, y*/) {
+ if (paper.options.enabled) {
+ pointerDown = true;
+ }
+ });
+
+ paper.on('cell:pointerup', function(/*cellView, evt, x, y*/) {
+ if (paper.options.enabled) {
+ updateWiresOnObstacles();
+ }
+ });
+
+ paper.on('cell:pointerdblclick', function(cellView, evt/*, x, y*/) {
+ if (!utils.hasShift(evt)) {
+ // Allow dblClick if Shift is not pressed
+ dblClickCell = true;
+ var type = cellView.model.get('blockType');
+ if (type.indexOf('basic.') !== -1) {
+ if (paper.options.enabled) {
+ blocks.editBasic(type, cellView, function(cell) {
+ addCell(cell);
+ });
+ }
+ }
+ else if (common.allDependencies[type]) {
+ z.index = 1;
+ var project = common.allDependencies[type];
+ var breadcrumbsLength = self.breadcrumbs.length;
+ $rootScope.$broadcast('navigateProject', {
+ update: breadcrumbsLength === 1,
+ project: project
+ });
+ self.breadcrumbs.push({ name: project.package.name || '#', type: type });
+ utils.rootScopeSafeApply();
+ }
+ // Enable click event
+ setTimeout(function() { dblClickCell = false; }, 200);
}
});
@@ -375,26 +417,32 @@ angular.module('icestudio')
// Disable current focus
document.activeElement.blur();
- if (evt.which === 3) {
- // Right button
- if (paper.options.enabled) {
+ if (utils.hasLeftButton(evt)) {
+ if (utils.hasCtrl(evt)) {
+ if (!self.isEmpty()) {
+ self.panAndZoom.enablePan();
+ }
+ }
+ else if (paper.options.enabled) {
selectionView.startSelecting(evt, x, y);
}
}
- else if (evt.which === 1) {
- // Left button
- self.panAndZoom.enablePan();
+ else if (utils.hasRightButton(evt)) {
+ if (!self.isEmpty()) {
+ self.panAndZoom.enablePan();
+ }
}
});
- paper.on('cell:pointerup blank:pointerup', function(/*cellView, evt*/) {
+ paper.on('blank:pointerup', function(/*cellView, evt*/) {
self.panAndZoom.disablePan();
});
- paper.on('cell:mouseover', function(cellView/*, evt*/) {
- if (!self.mousedown) {
+ paper.on('cell:mouseover', function(cellView, evt) {
+ // Move selection to top view if !mousedown
+ if (!utils.hasButtonPressed(evt)) {
if (!cellView.model.isLink()) {
- highlight(cellView);
+ //highlight(cellView);
if (cellView.$box.css('z-index') < z.index) {
cellView.$box.css('z-index', ++z.index);
}
@@ -402,27 +450,30 @@ angular.module('icestudio')
}
});
- paper.on('cell:mouseout', function(cellView/*, evt*/) {
- if (!self.mousedown) {
+ /*paper.on('cell:mouseout', function(cellView, evt) {
+ if (!utils.hasButtonPressed(evt)) {
if (!cellView.model.isLink()) {
unhighlight(cellView);
}
}
- });
+ });*/
- graph.on('change:position', function(/*cell*/) {
- /*if (!selectionView.isTranslating()) {
- // Update wires on obstacles motion
- var cells = graph.getCells();
- for (var i in cells) {
- var cell = cells[i];
- if (cell.isLink()) {
- paper.findViewByModel(cell).update();
- }
+ /*paper.on('cell:pointerdown', function(cellView) {
+ if (paper.options.enabled) {
+ if (cellView.model.isLink()) {
+ // Unhighlight source block of the wire
+ unhighlight(paper.findViewByModel(cellView.model.get('source').id));
}
- }*/
+ }
});
+ graph.on('change:position', function(cell) {
+ if (!selectionView.isTranslating()) {
+ // Update wires on obstacles motion
+ updateWiresOnObstacles();
+ }
+ });*/
+
graph.on('add change:source change:target', function(cell) {
if (cell.isLink() && cell.get('source').id) {
// Link connected
@@ -478,12 +529,11 @@ angular.module('icestudio')
function updateWiresOnObstacles() {
var cells = graph.getCells();
- for (var i in cells) {
- var cell = cells[i];
+ _.each(cells, function(cell) {
if (cell.isLink()) {
paper.findViewByModel(cell).update();
}
- }
+ });
}
this.setBoardRules = function(value) {
@@ -528,10 +578,10 @@ angular.module('icestudio')
angular.element('#banner').removeClass('hidden');
}
var cells = graph.getCells();
- for (var i in cells) {
- var cellView = paper.findViewByModel(cells[i].id);
+ _.each(cells, function(cell) {
+ var cellView = paper.findViewByModel(cell.id);
cellView.options.interactive = value;
- if (cells[i].get('type') !== 'ice.Generic') {
+ if (cell.get('type') !== 'ice.Generic') {
if (value) {
cellView.$el.removeClass('disable-graph');
}
@@ -539,7 +589,7 @@ angular.module('icestudio')
cellView.$el.addClass('disable-graph');
}
}
- else if (cells[i].get('type') !== 'ice.Wire') {
+ else if (cell.get('type') !== 'ice.Wire') {
if (value) {
cellView.$el.find('.port-body').removeClass('disable-graph');
}
@@ -547,21 +597,62 @@ angular.module('icestudio')
cellView.$el.find('.port-body').addClass('disable-graph');
}
}
- }
+ });
};
this.createBlock = function(type, block) {
blocks.newGeneric(type, block, function(cell) {
- addCell(cell);
+ self.addDraggableCell(cell);
});
};
this.createBasicBlock = function(type) {
- blocks.newBasic(type, function(cell) {
- addCell(cell);
+ blocks.newBasic(type, function(cells) {
+ self.addDraggableCells(cells);
});
};
+ this.addDraggableCell = function(cell) {
+ this.addingDraggableBlock = true;
+ cell.attributes.position = {
+ x: Math.round(((mousePosition.x - state.pan.x) / state.zoom - cell.attributes.size.width/2) / gridsize) * gridsize,
+ y: Math.round(((mousePosition.y - state.pan.y - menuHeight) / state.zoom - cell.attributes.size.height/2) / gridsize) * gridsize,
+ };
+ graph.trigger('batch:start');
+ addCell(cell);
+ disableSelected();
+ var opt = { transparent: true };
+ var noBatch = true;
+ selection.add(cell);
+ selectionView.createSelectionBox(cell, opt);
+ selectionView.startTranslatingSelection({ clientX: mousePosition.x, clientY: mousePosition.y }, noBatch);
+ };
+
+ this.addDraggableCells = function(cells) {
+ this.addingDraggableBlock = true;
+ if (cells.length > 0) {
+ var firstCellAttrs = cells[0].attributes;
+ var offset = {
+ x: Math.round(((mousePosition.x - state.pan.x) / state.zoom - firstCellAttrs.size.width/2) / gridsize) * gridsize - firstCellAttrs.position.x,
+ y: Math.round(((mousePosition.y - state.pan.y - menuHeight) / state.zoom - firstCellAttrs.size.height/2) / gridsize) * gridsize - firstCellAttrs.position.y,
+ };
+ _.each(cells, function(cell) {
+ cell.attributes.position.x += offset.x;
+ cell.attributes.position.y += offset.y;
+ });
+ graph.trigger('batch:start');
+ addCells(cells);
+ disableSelected();
+ var opt = { transparent: true };
+ var noBatch = true;
+ _.each(cells, function(cell) {
+ selection.add(cell);
+ selectionView.createSelectionBox(cell, opt);
+ });
+ selectionView.startTranslatingSelection({ clientX: mousePosition.x, clientY: mousePosition.y }, noBatch);
+ }
+ };
+
this.toJSON = function() {
return graph.toJSON();
};
@@ -574,27 +665,39 @@ angular.module('icestudio')
graph.attributes.cells.models = cells;
};
- this.selectBoard = function(boardName) {
+ this.selectBoard = function(board) {
graph.startBatch('change');
// Trigger board event
var data = {
- previous: common.selectedBoard.name,
- next: boardName
+ previous: common.selectedBoard,
+ next: board
};
graph.trigger('board', { data: data });
- boardName = boards.selectBoard(boardName);
+ var newBoard = boards.selectBoard(board.name);
resetBlocks();
graph.stopBatch('change');
- return boardName;
+ return newBoard;
+ };
+
+ this.selectLanguage = function(language) {
+ graph.startBatch('change');
+ // Trigger lang event
+ var data = {
+ previous: profile.get('language'),
+ next: language
+ };
+ graph.trigger('lang', { data: data });
+ language = utils.setLocale(language);
+ graph.stopBatch('change');
+ return language;
};
function resetBlocks() {
var data;
var cells = graph.getCells();
- for (var i in cells) {
- var cell = cells[i];
+ _.each(cells, function(cell) {
if (cell.isLink()) {
- break;
+ return;
}
var type = cell.get('blockType');
if (type === 'basic.input' || type === 'basic.output') {
@@ -620,7 +723,7 @@ angular.module('icestudio')
// Reset rules in Generic block ports
var block = common.allDependencies[type];
data = { ports: { in: [] }};
- for (i in block.design.graph.blocks) {
+ for (var i in block.design.graph.blocks) {
var item = block.design.graph.blocks[i];
if (item.type === 'basic.input') {
if (!item.data.range) {
@@ -634,7 +737,7 @@ angular.module('icestudio')
cell.set('data', data);
paper.findViewByModel(cell.id).updateBox();
}
- }
+ });
}
this.resetCommandStack = function() {
@@ -655,14 +758,15 @@ angular.module('icestudio')
};
this.pasteSelected = function() {
- var self = this;
- utils.pasteFromClipboard(function(object) {
- if (object.version === common.VERSION &&
- (document.activeElement.tagName === 'A' ||
- document.activeElement.tagName === 'BODY')) {
- self.appendDesign(object.design, object.dependencies);
- }
- });
+ if (document.activeElement.tagName === 'A' ||
+ document.activeElement.tagName === 'BODY')
+ {
+ utils.pasteFromClipboard(function(object) {
+ if (object.version === common.VERSION) {
+ self.appendDesign(object.design, object.dependencies);
+ }
+ });
+ }
};
this.selectAll = function() {
@@ -670,15 +774,14 @@ angular.module('icestudio')
var cells = graph.getCells();
_.each(cells, function(cell) {
if (!cell.isLink()) {
- var cellView = paper.findViewByModel(cell);
selection.add(cell);
- selectionView.createSelectionBox(cellView);
- unhighlight(cellView);
+ selectionView.createSelectionBox(cell);
+ //unhighlight(cellView);
}
});
};
- function highlight(cellView) {
+ /*function highlight(cellView) {
if (cellView) {
switch(cellView.model.get('type')) {
case 'ice.Input':
@@ -742,7 +845,7 @@ angular.module('icestudio')
break;
}
}
- }
+ }*/
function hasSelection() {
return selection && selection.length > 0;
@@ -856,8 +959,6 @@ angular.module('icestudio')
design.graph.blocks &&
design.graph.wires) {
- var self = this;
-
opt = opt || {};
$('body').addClass('waiting');
@@ -871,6 +972,7 @@ angular.module('icestudio')
self.clearAll();
var cells = graphToCells(design.graph, opt);
+
graph.addCells(cells);
self.appEnable(!opt.disabled);
@@ -990,7 +1092,7 @@ angular.module('icestudio')
reset: design.board !== common.selectedBoard.name,
offset: {
x: Math.round(((mousePosition.x - state.pan.x) / state.zoom - origin.x) / gridsize) * gridsize,
- y: Math.round(((mousePosition.y - state.pan.y) / state.zoom - origin.y) / gridsize) * gridsize,
+ y: Math.round(((mousePosition.y - state.pan.y - menuHeight) / state.zoom - origin.y) / gridsize) * gridsize,
}
};
var cells = graphToCells(design.graph, opt);
@@ -1000,9 +1102,12 @@ angular.module('icestudio')
_.each(cells, function(cell) {
if (!cell.isLink()) {
var cellView = paper.findViewByModel(cell);
+ if (cellView.$box.css('z-index') < z.index) {
+ cellView.$box.css('z-index', ++z.index);
+ }
selection.add(cell);
- selectionView.createSelectionBox(cellView);
- unhighlight(cellView);
+ selectionView.createSelectionBox(cell);
+ //unhighlight(cellView);
}
});
}
@@ -1039,4 +1144,58 @@ angular.module('icestudio')
}
}
+ function addCells(cells) {
+ _.each(cells, function(cell) {
+ updateCellAttributes(cell);
+ });
+ graph.addCells(cells);
+ _.each(cells, function(cell) {
+ if (!cell.isLink()) {
+ var cellView = paper.findViewByModel(cell);
+ if (cellView.$box.css('z-index') < z.index) {
+ cellView.$box.css('z-index', ++z.index);
+ }
+ }
+ });
+ }
+
+ this.resetCodeErrors = function() {
+ var cells = graph.getCells();
+ _.each(cells, function(cell) {
+ var cellView;
+ if (cell.attributes.type === 'ice.Code') {
+ cellView = paper.findViewByModel(cell);
+ cellView.clearAnnotations();
+ }
+ else if (cell.attributes.type === 'ice.Generic') {
+ cellView = paper.findViewByModel(cell);
+ }
+ if (cellView) {
+ cellView.$box.removeClass('highlight-error');
+ }
+ });
+ };
+
+ $(document).on('codeError', function(evt, codeError) {
+ var cells = graph.getCells();
+ _.each(cells, function(cell) {
+ var blockId, cellView;
+ if (codeError.blockType === 'code' && cell.attributes.type === 'ice.Code') {
+ blockId = utils.digestId(cell.id);
+ if (codeError.blockId === blockId) {
+ cellView = paper.findViewByModel(cell);
+ cellView.$box.addClass('highlight-error');
+ cellView.setAnnotation(codeError);
+ }
+ }
+ else if (codeError.blockType === 'generic' && cell.attributes.type === 'ice.Generic') {
+ blockId = utils.digestId(cell.attributes.blockType);
+ if (codeError.blockId === blockId) {
+ cellView = paper.findViewByModel(cell);
+ cellView.$box.addClass('highlight-error');
+ }
+ }
+ });
+ });
+
});
diff --git a/app/scripts/services/profile.js b/app/scripts/services/profile.js
index 03fc01b0f..280b610ca 100644
--- a/app/scripts/services/profile.js
+++ b/app/scripts/services/profile.js
@@ -2,6 +2,7 @@
angular.module('icestudio')
.service('profile', function(utils,
+ common,
nodeFs) {
this.data = {
@@ -12,17 +13,24 @@ angular.module('icestudio')
'boardRules': true
};
+ if (common.DARWIN) {
+ this.data['macosDrivers'] = false;
+ }
+
this.load = function(callback) {
var self = this;
- utils.readFile(utils.PROFILE_PATH, function(data) {
+ utils.readFile(common.PROFILE_PATH, function(data) {
if (data) {
self.data = {
'language': data.language || '',
'remoteHostname': data.remoteHostname || '',
'collection': data.collection || '',
'board': data.board || '',
- 'boardRules': data.remoteHostname || true
+ 'boardRules': data.boardRules !== false
};
+ if (common.DARWIN) {
+ self.data['macosDrivers'] = data.macosDrivers || false;
+ }
}
if (callback) {
callback();
@@ -42,10 +50,10 @@ angular.module('icestudio')
};
this.save = function() {
- if (!nodeFs.existsSync(utils.ICESTUDIO_DIR)) {
- nodeFs.mkdirSync(utils.ICESTUDIO_DIR);
+ if (!nodeFs.existsSync(common.ICESTUDIO_DIR)) {
+ nodeFs.mkdirSync(common.ICESTUDIO_DIR);
}
- utils.saveFile(utils.PROFILE_PATH, this.data, function() {
+ utils.saveFile(common.PROFILE_PATH, this.data, function() {
// Success
}, true);
};
diff --git a/app/scripts/services/project.js b/app/scripts/services/project.js
index c6755c693..f4afee9ad 100644
--- a/app/scripts/services/project.js
+++ b/app/scripts/services/project.js
@@ -14,6 +14,7 @@ angular.module('icestudio')
this.name = ''; // Used in File dialogs
this.path = ''; // Used in Save / Save as
+ this.filepath = ''; // Used to find external resources (.v, .vh, .list)
this.changed = false;
var project = _default();
@@ -83,6 +84,7 @@ angular.module('icestudio')
this.open = function(filepath, emptyPath) {
var self = this;
this.path = emptyPath ? '' : filepath;
+ this.filepath = filepath;
utils.readFile(filepath, function(data) {
if (data) {
var name = utils.basename(filepath);
@@ -119,11 +121,12 @@ angular.module('icestudio')
var opt = { reset: reset || false, disabled: false };
var ret = graph.loadDesign(project.design, opt, function() {
graph.resetCommandStack();
+ graph.fitContent();
alertify.success(gettextCatalog.getString('Project {{name}} loaded', { name: utils.bold(name) }));
});
if (ret) {
- profile.set('board', boards.selectBoard(project.design.board));
+ profile.set('board', boards.selectBoard(project.design.board).name);
self.updateTitle(name);
}
else {
@@ -286,14 +289,52 @@ angular.module('icestudio')
this.save = function(filepath) {
var name = utils.basename(filepath);
- this.path = filepath;
this.updateTitle(name);
-
sortGraph();
this.update();
- utils.saveFile(filepath, pruneProject(project), function() {
- alertify.success(gettextCatalog.getString('Project {{name}} saved', { name: utils.bold(name) }));
- }, true);
+
+ // Copy included files if the previous filepath
+ // is different from the new filepath
+ if (this.filepath !== filepath) {
+ var origPath = utils.dirname(this.filepath);
+ var destPath = utils.dirname(filepath);
+ // 1. Parse and find included files
+ var code = compiler.generate('verilog', project);
+ var files = utils.findIncludedFiles(code);
+ // Are there included files?
+ if (files.length > 0) {
+ // 2. Check project's directory
+ if (filepath) {
+ // 3. Copy the included files
+ copyIncludedFiles(files, origPath, destPath, function(success) {
+ if (success) {
+ // 4. Success: save project
+ doSaveProject();
+ }
+ });
+ }
+ }
+ else {
+ // No included files to copy
+ // 4. Save project
+ doSaveProject();
+ }
+ }
+ else {
+ // Same filepath
+ // 4. Save project
+ doSaveProject();
+ }
+
+ this.path = filepath;
+ this.filepath = filepath;
+
+ function doSaveProject() {
+ utils.saveFile(filepath, pruneProject(project), function() {
+ alertify.success(gettextCatalog.getString('Project {{name}} saved', { name: utils.bold(name) }));
+ }, true);
+ }
+
};
function sortGraph() {
@@ -316,7 +357,7 @@ angular.module('icestudio')
graph.setCells(cells);
}
- this.addAsBlock = function(filepath) {
+ this.addBlockFile = function(filepath, notification) {
var self = this;
utils.readFile(filepath, function(data) {
if (data.version !== common.VERSION) {
@@ -325,19 +366,20 @@ angular.module('icestudio')
var name = utils.basename(filepath);
var block = _safeLoad(data, name);
if (block) {
- var path = utils.dirname(filepath);
+ var origPath = utils.dirname(filepath);
+ var destPath = utils.dirname(self.path);
// 1. Parse and find included files
- var code = JSON.stringify(block);
+ var code = compiler.generate('verilog', block);
var files = utils.findIncludedFiles(code);
// Are there included files?
if (files.length > 0) {
// 2. Check project's directory
if (self.path) {
// 3. Copy the included files
- copyIncludedFiles(function(success) {
+ copyIncludedFiles(files, origPath, destPath, function(success) {
if (success) {
// 4. Success: import block
- doImportBlock(block);
+ doImportBlock();
}
});
}
@@ -347,10 +389,10 @@ angular.module('icestudio')
$rootScope.$emit('saveProjectAs', function() {
setTimeout(function() {
// 3. Copy the included files
- copyIncludedFiles(function(success) {
+ copyIncludedFiles(files, origPath, destPath, function(success) {
if (success) {
// 4. Success: import block
- doImportBlock(block);
+ doImportBlock();
}
});
}, 500);
@@ -361,66 +403,68 @@ angular.module('icestudio')
else {
// No included files to copy
// 4. Import block
- doImportBlock(block);
+ doImportBlock();
}
}
- function copyIncludedFiles(callback) {
- var success = true;
- async.eachSeries(files, function(filename, next) {
- setTimeout(function() {
- var origPath = nodePath.join(path, filename);
- var destPath = nodePath.join(utils.dirname(self.path), filename);
- if (origPath !== destPath) {
- if (nodeFs.existsSync(destPath)) {
- alertify.confirm(gettextCatalog.getString('File {{file}} already exists in the project path. Do you want to replace it?', { file: utils.bold(filename) }),
- function() {
- success = success && doCopySync(origPath, destPath, filename);
- if (!success) {
- return next(); // break
- }
- next();
- },
- function() {
- next();
- });
- }
- else {
- success = success && doCopySync(origPath, destPath, filename);
- if (!success) {
- return next(); // break
- }
- next();
+ function doImportBlock() {
+ self.addBlock(block);
+ if (notification) {
+ alertify.success(gettextCatalog.getString('Block {{name}} imported', { name: utils.bold(block.package.name) }));
+ }
+ }
+ });
+ };
+
+ function copyIncludedFiles(files, origPath, destPath, callback) {
+ var success = true;
+ async.eachSeries(files, function(filename, next) {
+ setTimeout(function() {
+ if (origPath !== destPath) {
+ if (nodeFs.existsSync(nodePath.join(destPath, filename))) {
+ alertify.confirm(gettextCatalog.getString('File {{file}} already exists in the project path. Do you want to replace it?', { file: utils.bold(filename) }),
+ function() {
+ success = success && doCopySync(origPath, destPath, filename);
+ if (!success) {
+ return next(); // break
}
- }
- else {
+ next();
+ },
+ function() {
+ next();
+ });
+ }
+ else {
+ success = success && doCopySync(origPath, destPath, filename);
+ if (!success) {
return next(); // break
}
- }, 0);
- }, function(/*result*/) {
- return callback(success);
- });
- }
-
- function doCopySync(orig, dest, filename) {
- var success = utils.copySync(orig, dest);
- if (success) {
- alertify.message(gettextCatalog.getString('File {{file}} imported', { file: utils.bold(filename) }), 5);
+ next();
+ }
}
else {
- alertify.error(gettextCatalog.getString('Original file {{file}} does not exist', { file: utils.bold(filename) }), 30);
+ return next(); // break
}
- return success;
- }
-
- function doImportBlock(block) {
- self.addBlock(block);
- alertify.success(gettextCatalog.getString('Block {{name}} imported', { name: utils.bold(block.package.name) }));
- }
+ }, 0);
+ }, function(/*result*/) {
+ return callback(success);
});
- };
+ }
- function pruneProject (project) {
+ function doCopySync(origPath, destPath, filename) {
+ var orig = nodePath.join(origPath, filename);
+ var dest = nodePath.join(destPath, filename);
+ var success = utils.copySync(orig, dest);
+ if (success) {
+ alertify.message(gettextCatalog.getString('File {{file}} imported', { file: utils.bold(filename) }), 5);
+ }
+ else {
+ alertify.error(gettextCatalog.getString('Original file {{file}} does not exist', { file: utils.bold(filename) }), 30);
+ }
+ return success;
+ }
+
+ function pruneProject(project) {
var _project = utils.clone(project);
_prune(_project);
@@ -435,7 +479,9 @@ angular.module('icestudio')
case 'basic.input':
case 'basic.output':
case 'basic.constant':
+ break;
case 'basic.info':
+ delete block.data.text;
break;
case 'basic.code':
for (var j in block.data.ports.in) {
@@ -496,26 +542,13 @@ angular.module('icestudio')
graph.createBasicBlock(type);
};
- this.addBlock = function(arg) {
- if (typeof arg === 'string') {
- // arg is a filepath
- utils.readFile(arg, function(block) {
- _addBlock(block);
- });
- }
- else {
- // arg is a block
- _addBlock(arg);
- }
-
- function _addBlock(block) {
- if (block) {
- block = _safeLoad(block);
- block = pruneBlock(block);
- var type = utils.dependencyID(block);
- utils.mergeDependencies(type, block);
- graph.createBlock(type, block);
- }
+ this.addBlock = function(block) {
+ if (block) {
+ block = _safeLoad(block);
+ block = pruneBlock(block);
+ var type = utils.dependencyID(block);
+ utils.mergeDependencies(type, block);
+ graph.createBlock(type, block);
}
};
diff --git a/app/scripts/services/shortcuts.js b/app/scripts/services/shortcuts.js
index 08ac8e53e..eff553569 100644
--- a/app/scripts/services/shortcuts.js
+++ b/app/scripts/services/shortcuts.js
@@ -6,7 +6,7 @@ angular.module('icestudio')
return shortcuts.label(action);
};
})
- .service('shortcuts', function(utils) {
+ .service('shortcuts', function(common) {
this.method = function(action, method) {
// Configure shortcut method
@@ -23,7 +23,7 @@ angular.module('icestudio')
var action = '';
var method = null;
- var system = utils.DARWIN ? 'mac' : 'linux';
+ var system = common.DARWIN ? 'mac' : 'linux';
var ret = { preventDefault: false };
for (action in shortcuts) {
var options = shortcuts[action].opt || {};
@@ -50,7 +50,7 @@ angular.module('icestudio')
// Return shortcut label
var label = '';
if (action in shortcuts) {
- if (utils.DARWIN) {
+ if (common.DARWIN) {
label = shortcuts[action].mac.label;
}
else {
@@ -154,7 +154,7 @@ angular.module('icestudio')
linux: { label: 'Supr', key: 46 },
mac: { label: 'Fn+Delete', key: 46 },
},
- breadcrumbsBack: {
+ back: {
linux: { label: 'Back', key: 8 },
mac: { label: 'Delete', key: 8 },
opt: { disabled: true }
diff --git a/app/scripts/services/tools.js b/app/scripts/services/tools.js
index 5e2dd4e95..0d698e21d 100644
--- a/app/scripts/services/tools.js
+++ b/app/scripts/services/tools.js
@@ -4,7 +4,8 @@ angular.module('icestudio')
.service('tools', function(project,
compiler,
profile,
- resources,
+ collections,
+ drivers,
utils,
common,
gettextCatalog,
@@ -29,7 +30,7 @@ angular.module('icestudio')
checkToolchain();
// Remove build directory on start
- nodeFse.removeSync(utils.BUILD_DIR);
+ nodeFse.removeSync(common.BUILD_DIR);
this.verifyCode = function() {
this.apio(['verify']);
@@ -60,13 +61,14 @@ angular.module('icestudio')
gettext('start_build');
/// Start uploading ...
gettext('start_upload');
- var message = 'start_' + commands[0];
+ var label = commands[0];
+ var message = 'start_' + label;
currentAlert = alertify.message(gettextCatalog.getString(message), 100000);
$('body').addClass('waiting');
check = this.syncResources(code);
try {
if (check) {
- execute(commands, commands[0], currentAlert, function() {
+ execute(commands, label, code, currentAlert, function() {
if (currentAlert) {
setTimeout(function() {
angular.element('#menu').removeClass('disable-menu');
@@ -115,7 +117,7 @@ angular.module('icestudio')
toolchain.installed = toolchain.apio >= _package.apio.min &&
toolchain.apio < _package.apio.max;
if (toolchain.installed) {
- nodeChildProcess.exec([apio, 'clean', '-p', utils.SAMPLE_DIR].join(' '), function(error/*, stdout, stderr*/) {
+ nodeChildProcess.exec([apio, 'clean', '-p', common.SAMPLE_DIR].join(' '), function(error/*, stdout, stderr*/) {
toolchain.installed = !error;
if (callback) {
callback();
@@ -133,8 +135,8 @@ angular.module('icestudio')
}
this.generateCode = function() {
- if (!nodeFs.existsSync(utils.BUILD_DIR)) {
- nodeFs.mkdirSync(utils.BUILD_DIR);
+ if (!nodeFs.existsSync(common.BUILD_DIR)) {
+ nodeFs.mkdirSync(common.BUILD_DIR);
}
project.update();
var opt = { boardRules: profile.get('boardRules') };
@@ -144,8 +146,8 @@ angular.module('icestudio')
}
var verilog = compiler.generate('verilog', project.get(), opt);
var pcf = compiler.generate('pcf', project.get(), opt);
- nodeFs.writeFileSync(nodePath.join(utils.BUILD_DIR, 'main.v'), verilog, 'utf8');
- nodeFs.writeFileSync(nodePath.join(utils.BUILD_DIR, 'main.pcf'), pcf, 'utf8');
+ nodeFs.writeFileSync(nodePath.join(common.BUILD_DIR, 'main.v'), verilog, 'utf8');
+ nodeFs.writeFileSync(nodePath.join(common.BUILD_DIR, 'main.pcf'), pcf, 'utf8');
return verilog;
};
@@ -156,11 +158,11 @@ angular.module('icestudio')
nodeFse.removeSync('!(main.*)');
// Sync included files
- ret = this.syncFiles(/@include\s(.*?)(\\n|\n|\s)/g, code);
+ ret = this.syncFiles(/(\n|\s)\/\/\s*@include\s+([^\s]*\.(v|vh))(\n|\s)/g, code);
// Sync list files
if (ret) {
- ret = this.syncFiles(/\"(.*\.list?)\"/g, code);
+ ret = this.syncFiles(/(\n|\s)[^\/]?\"(.*\.list?)\"/g, code);
}
return ret;
@@ -170,9 +172,9 @@ angular.module('icestudio')
var ret = true;
var match;
while (match = pattern.exec(code)) {
- var file = match[1];
- var destPath = nodePath.join(utils.BUILD_DIR, file);
- var origPath = nodePath.join(utils.dirname(project.path), file);
+ var file = match[2];
+ var destPath = nodePath.join(common.BUILD_DIR, file);
+ var origPath = nodePath.join(utils.dirname(project.filepath), file);
// Copy included file
var copySuccess = utils.copySync(origPath, destPath);
@@ -186,13 +188,13 @@ angular.module('icestudio')
return ret;
};
- function execute(commands, label, currentAlert, callback) {
+ function execute(commands, label, code, currentAlert, callback) {
var remoteHostname = profile.get('remoteHostname');
if (remoteHostname) {
currentAlert.setContent(gettextCatalog.getString('Synchronize remote files ...'));
nodeRSync({
- src: utils.BUILD_DIR + '/',
+ src: common.BUILD_DIR + '/',
dest: remoteHostname + ':.build/',
ssh: true,
recursive: true,
@@ -204,31 +206,49 @@ angular.module('icestudio')
currentAlert.setContent(gettextCatalog.getString('Execute remote {{label}} ...', { label: label }));
nodeSSHexec((['apio'].concat(commands).concat(['-p', '.build'])).join(' '), remoteHostname,
function (error, stdout, stderr) {
- processExecute(label, callback, error, stdout, stderr);
+ processExecute(label, code, callback, error, stdout, stderr);
});
}
else {
- processExecute(label, callback, error, stdout, stderr);
+ processExecute(label, code, callback, error, stdout, stderr);
}
});
}
else {
+ if (commands[0] === 'upload') {
+ drivers.preUpload(function() {
+ _execute();
+ });
+ }
+ else {
+ _execute();
+ }
+ }
+
+ function _execute() {
var apio = utils.getApioExecutable();
toolchain.disabled = utils.toolchainDisabled;
- nodeChildProcess.exec(([apio].concat(commands).concat(['-p', utils.coverPath(utils.BUILD_DIR)])).join(' '), { maxBuffer: 5000 * 1024 },
+ nodeChildProcess.exec(([apio].concat(commands).concat(['-p', utils.coverPath(common.BUILD_DIR)])).join(' '), { maxBuffer: 5000 * 1024 },
function(error, stdout, stderr) {
- processExecute(label, callback, error, stdout, stderr);
+ if (!error && !stderr) {
+ if (commands[0] === 'upload') {
+ drivers.postUpload();
+ }
+ }
+ processExecute(label, code, callback, error, stdout, stderr);
});
}
}
- function processExecute(label, callback, error, stdout, stderr) {
+ function processExecute(label, code, callback, error, stdout, stderr) {
if (callback) {
callback();
}
+ //console.log(label, error, stdout, stderr)
if (label) {
if (error || stderr) {
if (stdout) {
+ // - Apio errors
if (stdout.indexOf('[upload] Error') !== -1 ||
stdout.indexOf('Error: board not detected') !== -1) {
alertify.error(gettextCatalog.getString('Board {{name}} not detected', { name: utils.bold(common.selectedBoard.info.label) }), 30);
@@ -236,33 +256,110 @@ angular.module('icestudio')
else if (stdout.indexOf('Error: unkown board') !== -1) {
alertify.error(gettextCatalog.getString('Unknown board'), 30);
}
- else if (stdout.indexOf('set_io: too few arguments') !== -1) {
- alertify.error(gettextCatalog.getString('FPGA I/O ports not defined'), 30);
- }
- else if (stdout.indexOf('error: unknown pin') !== -1) {
+ // - Arachne-pnr errors
+ else if (stdout.indexOf('set_io: too few arguments') !== -1 ||
+ stdout.indexOf('fatal error: unknown pin') !== -1) {
alertify.error(gettextCatalog.getString('FPGA I/O ports not defined'), 30);
}
- else if (stdout.indexOf('error: duplicate pin constraints') !== -1) {
+ else if (stdout.indexOf('fatal error: duplicate pin constraints') !== -1) {
alertify.error(gettextCatalog.getString('Duplicated FPGA I/O ports'), 30);
}
else {
- var stdoutError = stdout.split('\n').filter(function (line) {
- return (line.indexOf('syntax error') !== -1 ||
- line.indexOf('not installed') !== -1 ||
- line.indexOf('error: ') !== -1 ||
- line.indexOf('ERROR: ') !== -1 ||
- line.indexOf('Error: ') !== -1 ||
- line.indexOf('already declared') !== -1);
- });
- if (stdoutError.length > 0) {
- alertify.error(stdoutError[0], 30);
+ var re, matchError, codeErrors = [];
+
+ // - Iverilog errors & warnings
+ // main.v:#: error: ...
+ // main.v:#: warning: ...
+ // main.v:#: syntax error
+ re = /main.v:([0-9]+):\s(error|warning):\s(.*?)\n/g;
+ while (matchError = re.exec(stdout)) {
+ codeErrors.push({
+ line: parseInt(matchError[1]),
+ msg: matchError[3],
+ type: matchError[2]
+ });
+ }
+ re = /main.v:([0-9]+):\ssyntax\serror\n/g;
+ while (matchError = re.exec(stdout)) {
+ codeErrors.push({
+ line: parseInt(matchError[1]),
+ msg: 'Syntax error',
+ type: 'error'
+ });
+ }
+
+ // - Yosys errors
+ // ERROR: ... main.v:#...\n
+ // Warning: ... main.v:#...\n
+ re = /(ERROR|Warning):\s(.*?)\smain\.v:([0-9]+)(.*?)\n/g;
+ while (matchError = re.exec(stdout)) {
+ var msg = '';
+ var line = parseInt(matchError[3]);
+ var type = matchError[1].toLowerCase();
+ var preContent = matchError[2];
+ var postContent = matchError[4];
+ // Process error
+ if (preContent === 'Parser error in line') {
+ postContent = postContent.substring(2); // remove :\s
+ if (postContent.startsWith('syntax error')) {
+ postContent = 'Syntax error';
+ }
+ msg = postContent;
+ }
+ else if (preContent.endsWith(' in line ')) {
+ msg = preContent.replace(/\sin\sline\s$/, ' ') + postContent;
+ }
+ else {
+ preContent = preContent.replace(/\sat\s$/, '');
+ preContent = preContent.replace(/\sin\s$/, '');
+ msg = preContent;
+ }
+ codeErrors.push({
+ line: line,
+ msg: msg,
+ type: type
+ });
+ }
+
+ // Extract modules map from code
+ var modules = mapCodeModules(code);
+
+ for (var i in codeErrors) {
+ var codeError = normalizeCodeError(codeErrors[i], modules);
+ if (codeError) {
+ // Launch codeError event
+ $(document).trigger('codeError', [codeError]);
+ }
+ }
+
+ if (codeErrors.length !== 0) {
+ alertify.error(gettextCatalog.getString('Errors detected in the code'), 5);
}
else {
- alertify.error(stdout, 30);
+ var stdoutWarning = stdout.split('\n').filter(function (line) {
+ line = line.toLowerCase();
+ return (line.indexOf('warning: ') !== -1);
+ });
+ var stdoutError = stdout.split('\n').filter(function (line) {
+ line = line.toLowerCase();
+ return (line.indexOf('error: ') !== -1 ||
+ line.indexOf('not installed') !== -1 ||
+ line.indexOf('already declared') !== -1);
+ });
+ if (stdoutWarning.length > 0) {
+ alertify.warning(stdoutWarning[0]);
+ }
+ if (stdoutError.length > 0) {
+ alertify.error(stdoutError[0], 30);
+ }
+ else {
+ alertify.error(stdout, 30);
+ }
}
}
}
else if (stderr) {
+ // Remote hostname errors
if (stderr.indexOf('Could not resolve hostname') !== -1 ||
stderr.indexOf('Connection refused') !== -1) {
alertify.error(gettextCatalog.getString('Wrong remote hostname {{name}}', { name: profile.get('remoteHostname') }), 30);
@@ -313,6 +410,68 @@ angular.module('icestudio')
}
}
+ function mapCodeModules(code) {
+ var codelines = code.split('\n');
+ var match, module = {}, modules = [];
+ // Find begin/end lines of the modules
+ for (var i in codelines) {
+ var codeline = codelines[i];
+ // Get the module name
+ if (!module.name) {
+ match = /module\s+(.*?)[\s|\(|$]/.exec(codeline);
+ if (match) {
+ module.name = match[1];
+ continue;
+ }
+ }
+ // Get the begin of the module code
+ if (!module.begin) {
+ match = /;/.exec(codeline);
+ if (match) {
+ module.begin = parseInt(i) + 1;
+ continue;
+ }
+ }
+ // Get the end of the module code
+ if (!module.end) {
+ match = /endmodule/.exec(codeline);
+ if (match) {
+ module.end = parseInt(i) + 1;
+ modules.push(module);
+ module = {};
+ }
+ }
+ }
+ return modules;
+ }
+
+ function normalizeCodeError(codeError, modules) {
+ var newCodeError;
+ // Find the module with the error
+ for (var i in modules) {
+ var module = modules[i];
+ if ((codeError.line > module.begin) && (codeError.line <= module.end)) {
+ newCodeError = {
+ type: codeError.type,
+ line: codeError.line - module.begin - ((codeError.line === module.end) ? 1 : 0),
+ msg: (codeError.msg.length > 2) ? codeError.msg[0].toUpperCase() + codeError.msg.substring(1) : codeError.msg
+ };
+ if (module.name.startsWith('main_')) {
+ // Code block
+ newCodeError.blockId = module.name.split('_')[1];
+ newCodeError.blockType = 'code';
+ }
+ else {
+ // Generic block
+ newCodeError.blockId = module.name.split('_')[0];
+ newCodeError.blockType = 'generic';
+ }
+ break;
+ }
+ }
+ return newCodeError;
+ }
+
this.installToolchain = function() {
utils.removeToolchain();
if (utils.checkDefaultToolchain()) {
@@ -342,7 +501,7 @@ angular.module('icestudio')
});
}
else {
- alertify.alert(gettextCatalog.getString('Error: default toolchain not found in \'{{dir}}\'', { dir: utils.TOOLCHAIN_DIR}));
+ alertify.alert(gettextCatalog.getString('Error: default toolchain not found in \'{{dir}}\'', { dir: common.TOOLCHAIN_DIR}));
}
};
@@ -357,11 +516,11 @@ angular.module('icestudio')
};
this.enableDrivers = function() {
- utils.enableDrivers();
+ drivers.enable();
};
this.disableDrivers = function() {
- utils.disableDrivers();
+ drivers.disable();
};
function installDefaultToolchain() {
@@ -512,7 +671,7 @@ angular.module('icestudio')
}
function apioInstallDrivers(callback) {
- if (utils.WIN32) {
+ if (common.WIN32) {
updateProgress('apio install drivers', 80);
utils.apioInstall('drivers', callback);
}
@@ -575,110 +734,164 @@ angular.module('icestudio')
// Collections management
- this.addCollections = function(filepath) {
- alertify.message(gettextCatalog.getString('Load {{name}} ...', { name: utils.bold(utils.basename(filepath) + '.zip') }));
+ this.addCollections = function(filepaths) {
+ // Load zip file
+ async.eachSeries(filepaths, function(filepath, nextzip) {
+ //alertify.message(gettextCatalog.getString('Load {{name}} ...', { name: utils.bold(utils.basename(filepath)) }));
+ var zipData = nodeAdmZip(filepath);
+ var _collections = getCollections(zipData);
- var collections = {};
- var zip = nodeAdmZip(filepath);
- var zipEntries = zip.getEntries();
+ async.eachSeries(_collections, function(collection, next) {
+ setTimeout(function() {
+ if (collection.package && (collection.blocks || collection.examples)) {
+
+ alertify.prompt(gettextCatalog.getString('Edit the collection name'), collection.origName,
+ function(evt, name) {
+ if (!name) {
+ return false;
+ }
+ collection.name = name;
+
+ var destPath = nodePath.join(common.COLLECTIONS_DIR, name);
+ if (nodeFs.existsSync(destPath)) {
+ alertify.confirm(
+ gettextCatalog.getString('The collection {{name}} already exists.', { name: utils.bold(name) }) + '
' +
+ gettextCatalog.getString('Do you want to replace it?'),
+ function() {
+ utils.deleteFolderRecursive(destPath);
+ installCollection(collection, zipData);
+ alertify.success(gettextCatalog.getString('Collection {{name}} replaced', { name: utils.bold(name) }));
+ next(name);
+ },
+ function() {
+ alertify.warning(gettextCatalog.getString('Collection {{name}} not replaced', { name: utils.bold(name) }));
+ next(name);
+ });
+ }
+ else {
+ installCollection(collection, zipData);
+ alertify.success(gettextCatalog.getString('Collection {{name}} added', { name: utils.bold(name) }));
+ next(name);
+ }
+ });
+ }
+ else {
+ alertify.warning(gettextCatalog.getString('Invalid collection {{name}}', { name: utils.bold(name) }));
+ }
+ }, 0);
+ }, function(name) {
+ collections.loadCollections();
+ // If the selected collection is replaced, load it again
+ if (common.selectedCollection.name === name) {
+ collections.selectCollection(name);
+ }
+ utils.rootScopeSafeApply();
+ nextzip();
+ });
+ });
+ };
+
+ function getCollections(zipData) {
+ var data = '';
+ var _collections = {};
+ var zipEntries = zipData.getEntries();
// Validate collections
zipEntries.forEach(function(zipEntry) {
- var name = zipEntry.entryName.match(/^([^\/]+)\/$/);
- if (name) {
- collections[name[1]] = {
- name: name[1], blocks: [], examples: [], locale: [], package: ''
+ data = zipEntry.entryName.match(/^([^\/]+)\/$/);
+ if (data) {
+ _collections[data[1]] = {
+ origName: data[1], blocks: [], examples: [], locale: [], package: ''
};
}
- name = zipEntry.entryName.match(/^([^\/]+)\/blocks\/.*\.ice$/);
- if (name) {
- collections[name[1]].blocks.push(zipEntry.entryName);
+ data = zipEntry.entryName.match(/^([^\/]+)\/blocks\/.*\.ice$/);
+ if (data) {
+ _collections[data[1]].blocks.push(zipEntry.entryName);
}
- name = zipEntry.entryName.match(/^([^\/]+)\/examples\/.*\.ice$/);
- if (name) {
- collections[name[1]].examples.push(zipEntry.entryName);
+ data = zipEntry.entryName.match(/^([^\/]+)\/examples\/.*\.ice$/);
+ if (data) {
+ _collections[data[1]].examples.push(zipEntry.entryName);
}
- name = zipEntry.entryName.match(/^([^\/]+)\/locale\/.*\.po$/);
- if (name) {
- collections[name[1]].locale.push(zipEntry.entryName);
+ data = zipEntry.entryName.match(/^([^\/]+)\/examples\/.*\.v$/);
+ if (data) {
+ _collections[data[1]].examples.push(zipEntry.entryName);
}
- name = zipEntry.entryName.match(/^([^\/]+)\/package\.json$/);
- if (name) {
- collections[name[1]].package = zipEntry.entryName;
+ data = zipEntry.entryName.match(/^([^\/]+)\/examples\/.*\.vh$/);
+ if (data) {
+ _collections[data[1]].examples.push(zipEntry.entryName);
+ }
+ data = zipEntry.entryName.match(/^([^\/]+)\/examples\/.*\.list$/);
+ if (data) {
+ _collections[data[1]].examples.push(zipEntry.entryName);
+ }
+ data = zipEntry.entryName.match(/^([^\/]+)\/locale\/.*\.po$/);
+ if (data) {
+ _collections[data[1]].locale.push(zipEntry.entryName);
+ }
+ data = zipEntry.entryName.match(/^([^\/]+)\/package\.json$/);
+ if (data) {
+ _collections[data[1]].package = zipEntry.entryName;
+ }
+ data = zipEntry.entryName.match(/^([^\/]+)\/README\.md$/);
+ if (data) {
+ _collections[data[1]].readme = zipEntry.entryName;
}
});
- async.eachSeries(collections, function(collection, next) {
- setTimeout(function() {
- if (collection.package && (collection.blocks || collection.examples)) {
- var destPath = nodePath.join(utils.COLLECTIONS_DIR, collection.name);
- if (nodeFs.existsSync(destPath)) {
- alertify.confirm(
- gettextCatalog.getString('The collection {{name}} already exists.', { name: utils.bold(collection.name) }) + '
' +
- gettextCatalog.getString('Do you want to replace it?'),
- function() {
- utils.deleteFolderRecursive(destPath);
- installCollection(collection, zip);
- alertify.success(gettextCatalog.getString('Collection {{name}} replaced', { name: utils.bold(collection.name) }));
- next();
- },
- function() {
- alertify.warning(gettextCatalog.getString('Collection {{name}} not replaced', { name: utils.bold(collection.name) }));
- next();
- });
- }
- else {
- installCollection(collection, zip);
- alertify.success(gettextCatalog.getString('Collection {{name}} added', { name: utils.bold(collection.name) }));
- next();
- }
- }
- else {
- alertify.warning(gettextCatalog.getString('Invalid collection {{name}}', { name: utils.bold(collection.name) }));
- }
- }, 0);
- }, function() {
- resources.loadCollections();
- });
- };
+
+ return _collections;
+ }
function installCollection(collection, zip) {
- for (var b in collection.blocks) {
- safeExtract(collection.blocks[b], zip);
+ var i, dest = '';
+ var pattern = RegExp('^' + collection.origName);
+ for (i in collection.blocks) {
+ dest = collection.blocks[i].replace(pattern, collection.name);
+ safeExtract(collection.blocks[i], dest, zip);
}
- for (var e in collection.examples) {
- safeExtract(collection.examples[e], zip);
+ for (i in collection.examples) {
+ dest = collection.examples[i].replace(pattern, collection.name);
+ safeExtract(collection.examples[i], dest, zip);
}
- for (var l in collection.locale) {
- safeExtract(collection.locale[l], zip);
+ for (i in collection.locale) {
+ dest = collection.locale[i].replace(pattern, collection.name);
+ safeExtract(collection.locale[i], dest, zip);
// Generate locale JSON files
var compiler = new nodeGettext.Compiler({ format: 'json' });
- var sourcePath = nodePath.join(utils.COLLECTIONS_DIR, collection.locale[l]);
- var targetPath = nodePath.join(utils.COLLECTIONS_DIR, collection.locale[l].replace(/\.po$/, '.json'));
+ var sourcePath = nodePath.join(common.COLLECTIONS_DIR, dest);
+ var targetPath = nodePath.join(common.COLLECTIONS_DIR, dest.replace(/\.po$/, '.json'));
var content = nodeFs.readFileSync(sourcePath).toString();
var json = compiler.convertPo([content]);
nodeFs.writeFileSync(targetPath, json);
- // Add string to gettext
+ // Add strings to gettext
gettextCatalog.loadRemote(targetPath);
}
- safeExtract(collection.package, zip);
+ if (collection.package) {
+ dest = collection.package.replace(pattern, collection.name);
+ safeExtract(collection.package, dest, zip);
+ }
+ if (collection.readme) {
+ dest = collection.readme.replace(pattern, collection.name);
+ safeExtract(collection.readme, dest, zip);
+ }
}
- function safeExtract(entry, zip) {
+ function safeExtract(entry, dest, zip) {
try {
- zip.extractEntryTo(entry, utils.COLLECTIONS_DIR);
+ var newPath = nodePath.join(common.COLLECTIONS_DIR, dest);
+ zip.extractEntryTo(entry, utils.dirname(newPath), /*maintainEntryPath*/false);
}
catch(e) {}
}
this.removeCollection = function(collection) {
utils.deleteFolderRecursive(collection.path);
- resources.loadCollections();
+ collections.loadCollections();
alertify.success(gettextCatalog.getString('Collection {{name}} removed', { name: utils.bold(collection.name) }));
};
this.removeAllCollections = function() {
utils.removeCollections();
- resources.loadCollections();
+ collections.loadCollections();
alertify.success(gettextCatalog.getString('All collections removed'));
};
diff --git a/app/scripts/services/utils.js b/app/scripts/services/utils.js
index 5eb98ab2e..16e547cb9 100644
--- a/app/scripts/services/utils.js
+++ b/app/scripts/services/utils.js
@@ -12,82 +12,21 @@ angular.module('icestudio')
nodeChildProcess,
nodeTarball,
nodeZlib,
- nodeSudo,
nodeOnline,
nodeGlob,
nodeSha1,
nodeCP,
+ nodeGetOS,
+ nodeLangInfo,
SVGO) {
- const WIN32 = Boolean(process.platform.indexOf('win32') > -1);
- this.WIN32 = WIN32;
- const DARWIN = Boolean(process.platform.indexOf('darwin') > -1);
- this.DARWIN = DARWIN;
-
- const LOCALE_DIR = nodePath.join('resources', 'locale');
- const SAMPLE_DIR = nodePath.join('resources', 'sample');
- this.SAMPLE_DIR = SAMPLE_DIR;
-
- const BASE_DIR = process.env.HOME || process.env.USERPROFILE;
- const ICESTUDIO_DIR = safeDir(nodePath.join(BASE_DIR, '.icestudio'));
- this.ICESTUDIO_DIR = ICESTUDIO_DIR;
- const COLLECTIONS_DIR = nodePath.join(ICESTUDIO_DIR, 'collections');
- this.COLLECTIONS_DIR = COLLECTIONS_DIR;
- const APIO_HOME_DIR = nodePath.join(ICESTUDIO_DIR, 'apio');
- const PROFILE_PATH = nodePath.join(ICESTUDIO_DIR, 'profile.json');
- this.PROFILE_PATH = PROFILE_PATH;
- const CACHE_DIR = nodePath.join(ICESTUDIO_DIR, '.cache');
- const BUILD_DIR = nodePath.join(ICESTUDIO_DIR, '.build');
- this.BUILD_DIR = BUILD_DIR;
-
- const VENV = 'virtualenv-15.0.1';
- const VENV_DIR = nodePath.join(CACHE_DIR, VENV);
- const VENV_TARGZ = nodePath.join('resources', 'virtualenv', VENV + '.tar.gz');
-
- const APP_DIR = nodePath.dirname(process.execPath);
- const TOOLCHAIN_DIR = nodePath.join(APP_DIR, 'toolchain');
- this.TOOLCHAIN_DIR = TOOLCHAIN_DIR;
-
- const DEFAULT_APIO = 'default-apio';
- const DEFAULT_APIO_DIR = nodePath.join(CACHE_DIR, DEFAULT_APIO);
- const DEFAULT_APIO_TARGZ = nodePath.join(TOOLCHAIN_DIR, DEFAULT_APIO + '.tar.gz');
-
- const DEFAULT_APIO_PACKAGES = 'default-apio-packages';
- const DEFAULT_APIO_PACKAGES_TARGZ = nodePath.join(TOOLCHAIN_DIR, DEFAULT_APIO_PACKAGES + '.tar.gz');
-
- const ENV_DIR = nodePath.join(ICESTUDIO_DIR, 'venv');
- const ENV_BIN_DIR = nodePath.join(ENV_DIR, WIN32 ? 'Scripts' : 'bin');
- const ENV_PIP = nodePath.join(ENV_BIN_DIR, 'pip');
- const ENV_APIO = nodePath.join(ENV_BIN_DIR, WIN32 ? 'apio.exe' : 'apio');
- const APIO_CMD = (WIN32 ? 'set' : 'export') + ' APIO_HOME_DIR=' + APIO_HOME_DIR + (WIN32 ? '& ' : '; ') + coverPath(ENV_APIO);
- const SYSTEM_APIO = '/usr/bin/apio';
-
- function safeDir(_dir) {
- if (WIN32) {
- // Put the env directory to the root of the current local disk when
- // default path contains non-ASCII characters. Virtualenv will fail to
- for (var i in _dir) {
- if (_dir[i].charCodeAt(0) > 127) {
- const _dirFormat = nodePath.parse(_dir);
- return nodePath.format({
- root: _dirFormat.root,
- dir: _dirFormat.root,
- base: '.icestudio',
- name: '.icestudio',
- });
- }
- }
- }
- return _dir;
- }
-
var _pythonExecutableCached = null;
// Get the system executable
this.getPythonExecutable = function() {
if (!_pythonExecutableCached) {
const possibleExecutables = [];
- if (WIN32) {
+ if (common.WIN32) {
possibleExecutables.push('python.exe');
possibleExecutables.push('C:\\Python27\\python.exe');
} else {
@@ -129,7 +68,7 @@ angular.module('icestudio')
};
this.extractVirtualEnv = function(callback) {
- this.extractTargz(VENV_TARGZ, CACHE_DIR, callback);
+ this.extractTargz(common.VENV_TARGZ, common.CACHE_DIR, callback);
};
function disableClick(e) {
@@ -172,15 +111,15 @@ angular.module('icestudio')
};
this.makeVenvDirectory = function(callback) {
- if (!nodeFs.existsSync(ICESTUDIO_DIR)) {
- nodeFs.mkdirSync(ICESTUDIO_DIR);
+ if (!nodeFs.existsSync(common.ICESTUDIO_DIR)) {
+ nodeFs.mkdirSync(common.ICESTUDIO_DIR);
}
- if (!nodeFs.existsSync(ENV_DIR)) {
- nodeFs.mkdirSync(ENV_DIR);
+ if (!nodeFs.existsSync(common.ENV_DIR)) {
+ nodeFs.mkdirSync(common.ENV_DIR);
this.executeCommand(
[this.getPythonExecutable(),
- coverPath(nodePath.join(VENV_DIR, 'virtualenv.py')),
- coverPath(ENV_DIR)], callback);
+ coverPath(nodePath.join(common.VENV_DIR, 'virtualenv.py')),
+ coverPath(common.ENV_DIR)], callback);
}
else {
callback();
@@ -190,7 +129,7 @@ angular.module('icestudio')
this.checkDefaultToolchain = function() {
try {
// TODO: use tar.gz with sha1
- return nodeFs.statSync(TOOLCHAIN_DIR).isDirectory();
+ return nodeFs.statSync(common.TOOLCHAIN_DIR).isDirectory();
}
catch (err) {
return false;
@@ -198,21 +137,21 @@ angular.module('icestudio')
};
this.extractDefaultApio = function(callback) {
- this.extractTargz(DEFAULT_APIO_TARGZ, DEFAULT_APIO_DIR, callback);
+ this.extractTargz(common.DEFAULT_APIO_TARGZ, common.DEFAULT_APIO_DIR, callback);
};
this.installDefaultApio = function(callback) {
var self = this;
- nodeGlob(nodePath.join(DEFAULT_APIO_DIR, '*.*'), {}, function (error, files) {
+ nodeGlob(nodePath.join(common.DEFAULT_APIO_DIR, '*.*'), {}, function (error, files) {
if (!error) {
files = files.map(function(item) { return coverPath(item); });
- self.executeCommand([coverPath(ENV_PIP), 'install', '-U', '--no-deps'].concat(files), callback);
+ self.executeCommand([coverPath(common.ENV_PIP), 'install', '-U', '--no-deps'].concat(files), callback);
}
});
};
this.extractDefaultApioPackages = function(callback) {
- this.extractTargz(DEFAULT_APIO_PACKAGES_TARGZ, APIO_HOME_DIR, callback);
+ this.extractTargz(common.DEFAULT_APIO_PACKAGES_TARGZ, common.APIO_HOME_DIR, callback);
};
this.isOnline = function(callback, error) {
@@ -230,17 +169,17 @@ angular.module('icestudio')
};
this.installOnlineApio = function(callback) {
- this.executeCommand([coverPath(ENV_PIP), 'install', '-U', 'apio">=' + _package.apio.min + ',<' + _package.apio.max + '"'], callback);
+ this.executeCommand([coverPath(common.ENV_PIP), 'install', '-U', 'apio">=' + _package.apio.min + ',<' + _package.apio.max + '"'], callback);
};
this.apioInstall = function(_package, callback) {
- this.executeCommand([APIO_CMD, 'install', _package], callback);
+ this.executeCommand([common.APIO_CMD, 'install', _package], callback);
};
this.toolchainDisabled = false;
this.getApioExecutable = function() {
- var candidateApio = process.env.ICESTUDIO_APIO ? process.env.ICESTUDIO_APIO : SYSTEM_APIO;
+ var candidateApio = process.env.ICESTUDIO_APIO ? process.env.ICESTUDIO_APIO : common.SYSTEM_APIO;
if (nodeFs.existsSync(candidateApio)) {
if (!this.toolchainDisabled) {
// Show message only on start
@@ -250,17 +189,17 @@ angular.module('icestudio')
return coverPath(candidateApio);
}
this.toolchainDisabled = false;
- return APIO_CMD;
+ return common.APIO_CMD;
};
this.removeToolchain = function() {
- deleteFolderRecursive(ENV_DIR);
- deleteFolderRecursive(CACHE_DIR);
- deleteFolderRecursive(APIO_HOME_DIR);
+ deleteFolderRecursive(common.ENV_DIR);
+ deleteFolderRecursive(common.CACHE_DIR);
+ deleteFolderRecursive(common.APIO_HOME_DIR);
};
this.removeCollections = function() {
- deleteFolderRecursive(COLLECTIONS_DIR);
+ deleteFolderRecursive(common.COLLECTIONS_DIR);
};
this.deleteFolderRecursive = deleteFolderRecursive;
@@ -284,7 +223,8 @@ angular.module('icestudio')
this.basename = basename;
function basename(filepath) {
- return nodePath.basename(filepath).split('.')[0];
+ var b = nodePath.basename(filepath);
+ return b.substr(0, b.lastIndexOf('.'));
}
this.dirname = function(filepath) {
@@ -374,7 +314,7 @@ angular.module('icestudio')
function getFilesRecursive(folder) {
var fileTree = [];
- var validator = /.*\.(ice|json)$/;
+ var validator = /.*\.(ice|json|md)$/;
if (nodeFs.existsSync(folder)) {
var fileContents = nodeFs.readdirSync(folder);
@@ -401,168 +341,7 @@ angular.module('icestudio')
return fileTree;
}
- this.enableDrivers = function() {
- if (WIN32) {
- enableWindowsDrivers();
- }
- else if (DARWIN) {
- enableDarwinDrivers();
- }
- else {
- linuxDrivers(true);
- }
- };
-
- this.disableDrivers = function() {
- if (WIN32) {
- disableWindowsDrivers();
- }
- else if (DARWIN) {
- disableDarwinDrivers();
- }
- else {
- linuxDrivers(false);
- }
- };
-
- function linuxDrivers(enable) {
- var commands;
- if (enable) {
- commands = [
- 'cp ' + nodePath.resolve('resources/config/80-icestick.rules') + ' /etc/udev/rules.d/80-icestick.rules',
- 'service udev restart'
- ];
- }
- else {
- commands = [
- 'rm /etc/udev/rules.d/80-icestick.rules',
- 'service udev restart'
- ];
- }
- var command = 'sh -c "' + commands.join('; ') + '"';
-
- beginLazyProcess();
- nodeSudo.exec(command, {name: 'Icestudio'}, function(error/*, stdout, stderr*/) {
- // console.log(error, stdout, stderr);
- endLazyProcess();
- if (!error) {
- if (enable) {
- alertify.success(gettextCatalog.getString('Drivers enabled'));
- }
- else {
- alertify.warning(gettextCatalog.getString('Drivers disabled'));
- }
- setTimeout(function() {
- alertify.message(gettextCatalog.getString('
Unplug and
reconnect the board'), 5);
- }, 1000);
- }
- });
- }
-
- function enableDarwinDrivers() {
- var commands = [
- 'kextunload -b com.FTDI.driver.FTDIUSBSerialDriver -q || true',
- 'kextunload -b com.apple.driver.AppleUSBFTDI -q || true'
- ];
- var command = 'sh -c "' + commands.join('; ') + '"';
-
- beginLazyProcess();
- nodeSudo.exec(command, {name: 'Icestudio'}, function(error/*, stdout, stderr*/) {
- // console.log(error, stdout, stderr);
- if (error) {
- endLazyProcess();
- }
- else {
- var brewCommands = [
- '/usr/local/bin/brew update',
- '/usr/local/bin/brew install --force libftdi',
- '/usr/local/bin/brew unlink libftdi',
- '/usr/local/bin/brew link --force libftdi',
- '/usr/local/bin/brew install --force libffi',
- '/usr/local/bin/brew unlink libffi',
- '/usr/local/bin/brew link --force libffi'
- ];
- nodeChildProcess.exec(brewCommands.join('; '), function(error, stdout, stderr) {
- // console.log(error, stdout, stderr);
- endLazyProcess();
- if (error) {
- if ((stderr.indexOf('brew: command not found') !== -1) ||
- (stderr.indexOf('brew: No such file or directory') !== -1)) {
- alertify.error(gettextCatalog.getString('Homebrew is required'), 30);
- // TODO: open web browser with Homebrew website on click
- }
- else if (stderr.indexOf('Error: Failed to download') !== -1) {
- alertify.error(gettextCatalog.getString('Internet connection required'), 30);
- }
- else {
- alertify.error(stderr, 30);
- }
- }
- else {
- alertify.success(gettextCatalog.getString('Drivers enabled'));
- }
- });
- }
- });
- }
-
- function disableDarwinDrivers() {
- var commands = [
- 'kextload -b com.FTDI.driver.FTDIUSBSerialDriver -q || true',
- 'kextload -b com.apple.driver.AppleUSBFTDI -q || true'
- ];
- var command = 'sh -c "' + commands.join('; ') + '"';
-
- beginLazyProcess();
- nodeSudo.exec(command, {name: 'Icestudio'}, function(error/*, stdout, stderr*/) {
- // console.log(error, stdout, stderr);
- endLazyProcess();
- if (!error) {
- alertify.warning(gettextCatalog.getString('Drivers disabled'));
- }
- });
- }
-
- function enableWindowsDrivers() {
- alertify.confirm(gettextCatalog.getString('
FTDI driver installation instructions
- Connect the FPGA board
- Replace the (Interface 0) driver of the board by libusbK
- Unplug and reconnect the board
'), function() {
- beginLazyProcess();
- nodeSudo.exec([APIO_CMD, 'drivers', '--enable'].join(' '), {name: 'Icestudio'}, function(error, stdout, stderr) {
- // console.log(error, stdout, stderr);
- endLazyProcess();
- if (stderr) {
- alertify.error(gettextCatalog.getString('Toolchain not installed. Please, install the toolchain'), 30);
- }
- if (!error) {
- alertify.message(gettextCatalog.getString('
Unplug and
reconnect the board'), 5);
- }
- });
- });
- }
-
- function disableWindowsDrivers() {
- alertify.confirm(gettextCatalog.getString('
FTDI driver uninstallation instructions
- Find the FPGA USB Device
- Select the board interface and uninstall the driver
'), function() {
- beginLazyProcess();
- nodeChildProcess.exec([APIO_CMD, 'drivers', '--disable'].join(' '), function(error, stdout, stderr) {
- // console.log(error, stdout, stderr);
- endLazyProcess();
- if (stderr) {
- alertify.error(gettextCatalog.getString('Toolchain not installed. Please, install the toolchain'), 30);
- }
- });
- });
- }
-
- function beginLazyProcess() {
- $('body').addClass('waiting');
- angular.element('#menu').addClass('disable-menu');
- }
-
- function endLazyProcess() {
- $('body').removeClass('waiting');
- angular.element('#menu').removeClass('disable-menu');
- }
-
- this.setLocale = function(locale, collections) {
+ this.setLocale = function(locale) {
// Update current locale format
locale = splitLocale(locale);
// Load supported languages
@@ -571,16 +350,16 @@ angular.module('icestudio')
var bestLang = bestLocale(locale, supported);
gettextCatalog.setCurrentLanguage(bestLang);
// Application strings
- gettextCatalog.loadRemote(nodePath.join(LOCALE_DIR, bestLang, bestLang + '.json'));
+ gettextCatalog.loadRemote(nodePath.join(common.LOCALE_DIR, bestLang, bestLang + '.json'));
// Collections strings
- for (var c in collections) {
- var collection = collections[c];
+ for (var c in common.collections) {
+ var collection = common.collections[c];
var filepath = nodePath.join(collection.path, 'locale', bestLang, bestLang + '.json');
if (nodeFs.existsSync(filepath)) {
gettextCatalog.loadRemote(filepath);
}
}
- // COLLECTIONS_DIR
+ // Return the best language
return bestLang;
};
@@ -598,8 +377,8 @@ angular.module('icestudio')
function getSupportedLanguages() {
var supported = [];
- nodeFs.readdirSync(LOCALE_DIR).forEach(function(element/*, index*/) {
- var curPath = nodePath.join(LOCALE_DIR, element);
+ nodeFs.readdirSync(common.LOCALE_DIR).forEach(function(element/*, index*/) {
+ var curPath = nodePath.join(common.LOCALE_DIR, element);
if (nodeFs.lstatSync(curPath).isDirectory()) {
supported.push(splitLocale(element));
}
@@ -660,11 +439,31 @@ angular.module('icestudio')
});
};
+ this.checkboxprompt = function(messages, values, callback) {
+ var content = [];
+ content.push('
');
+ content.push('
');
+ content.push('
');
+ // Restore values
+ $('#check').prop('checked', values[0]);
+
+ alertify.confirm(content.join('\n'))
+ .set('onok', function(evt) {
+ var values = [];
+ values.push($('#check').prop('checked'));
+ if (callback) {
+ callback(evt, values);
+ }
+ })
+ .set('oncancel', function(/*evt*/) {
+ });
+ };
+
this.inputcheckboxprompt = function(messages, values, callback) {
var content = [];
content.push('
');
@@ -683,13 +482,17 @@ angular.module('icestudio')
})
.set('oncancel', function(/*evt*/) {
});
+
+ setTimeout(function(){
+ $('#label').select();
+ }, 50);
};
this.inputcheckbox2prompt = function(messages, values, callback) {
var content = [];
content.push('
');
content.push('
' + messages[0] + '
');
- content.push('
');
+ content.push('
');
content.push('
');
content.push('
');
content.push('
');
@@ -711,6 +514,10 @@ angular.module('icestudio')
})
.set('oncancel', function(/*evt*/) {
});
+
+ setTimeout(function(){
+ $('#label').select();
+ }, 50);
};
this.projectinfoprompt = function(values, callback) {
@@ -731,7 +538,7 @@ angular.module('icestudio')
//content.push('
');
}
content.push('
' + messages[i] + '
');
- content.push('
');
+ content.push('
');
}
content.push('
' + gettextCatalog.getString('Image') + '
');
content.push('
');
@@ -769,12 +576,12 @@ angular.module('icestudio')
chooserOpen.unbind('change');
chooserOpen.change(function(/*evt*/) {
var filepath = $(this).val();
- var svgo = new SVGO();
+
nodeFs.readFile(filepath, 'utf8', function(err, data) {
if (err) {
throw err;
}
- svgo.optimize(data, function(result) {
+ optimizeSVG(data, function(result) {
image = encodeURI(result.data);
registerSave();
$('#preview-svg').attr('src', 'data:image/svg+xml,' + image);
@@ -784,6 +591,14 @@ angular.module('icestudio')
});
}
+ function optimizeSVG(data, callback) {
+ SVGO.optimize(data, function(result) {
+ if (callback) {
+ callback(result);
+ }
+ });
+ }
+
function registerSave() {
// Save SVG
var label = $('#save-svg');
@@ -861,13 +676,13 @@ angular.module('icestudio')
this.findIncludedFiles = function(code) {
var ret = [];
var patterns = [
- /@include\s(.*?)(\\n|\n|\s)/g,
- /\\"(.*\.list?)\\"/g
+ /(\n|\s)\/\/\s*@include\s+([^\s]*\.(v|vh))(\n|\s)/g,
+ /(\n|\s)[^\/]?\"(.*\.list?)\"/g
];
for (var p in patterns) {
var match;
while (match = patterns[p].exec(code)) {
- var file = match[1].replace(/ /g, '');
+ var file = match[2].replace(/ /g, '');
if (ret.indexOf(file) === -1) {
ret.push(file);
}
@@ -1024,15 +839,50 @@ angular.module('icestudio')
};
this.pasteFromClipboard = function(callback) {
- nodeCP.paste(function(a, text) {
- try {
+ nodeCP.paste(function(err, text) {
+ if (err) {
+ if (common.LINUX) {
+ // xclip installation message
+ var cmd = '';
+ var message = gettextCatalog.getString('{{app}} is required.', { app: '
xclip' });
+ nodeGetOS(function(e, os) {
+ if (!e) {
+ if (os.dist.indexOf('Debian') !== -1 ||
+ os.dist.indexOf('Ubuntu Linux') !== -1 ||
+ os.dist.indexOf('Linux Mint') !== -1)
+ {
+ cmd = 'sudo apt-get install xclip';
+ }
+ else if (os.dist.indexOf('Fedora'))
+ {
+ cmd = 'sudo dnf install xclip';
+ }
+ else if (os.dist.indexOf('RHEL') !== -1 ||
+ os.dist.indexOf('RHAS') !== -1 ||
+ os.dist.indexOf('Centos') !== -1 ||
+ os.dist.indexOf('Red Hat Linux') !== -1)
+ {
+ cmd = 'sudo yum install xclip';
+ }
+ else if (os.dist.indexOf('Arch Linux') !== -1)
+ {
+ cmd = 'sudo pacman install xclip';
+ }
+ if (cmd) {
+ message += ' ' + gettextCatalog.getString('Please run: {{cmd}}', { cmd: '
' + cmd + '
' });
+ }
+ }
+ alertify.warning(message, 30);
+ });
+ }
+ }
+ else {
// Parse the global clipboard
var clipboard = JSON.parse(text);
if (callback && clipboard && clipboard.icestudio) {
callback(clipboard.icestudio);
}
}
- catch (e) { }
});
};
@@ -1160,4 +1010,53 @@ angular.module('icestudio')
return _.clone(_default);
};
+ this.hasLeftButton = function(evt) {
+ return evt.which === 1;
+ };
+
+ this.hasMiddleButton = function(evt) {
+ return evt.which === 2;
+ };
+
+ this.hasRightButton = function(evt) {
+ return evt.which === 3;
+ };
+
+ this.hasButtonPressed = function(evt) {
+ return evt.which !== 0;
+ };
+
+ this.hasShift = function(evt) {
+ return evt.shiftKey;
+ };
+
+ this.hasCtrl = function(evt) {
+ return evt.ctrlKey;
+ };
+
+ this.loadLanguage = function(profile) {
+ var self = this;
+ profile.load(function() {
+ var lang = profile.get('language');
+ if (lang) {
+ self.setLocale(lang);
+ }
+ else {
+ // If lang is empty, use the system language
+ nodeLangInfo(function(err, sysLang) {
+ if (!err) {
+ profile.set('language', self.setLocale(sysLang));
+ }
+ });
+ }
+ });
+ };
+
+ this.digestId = function(id) {
+ if (id.indexOf('-') !== -1) {
+ id = nodeSha1(id).toString();
+ }
+ return 'v' + id.substring(0, 6);
+ };
+
});
diff --git a/app/styles/design.css b/app/styles/design.css
index 004b23cac..95c6cbb53 100644
--- a/app/styles/design.css
+++ b/app/styles/design.css
@@ -100,10 +100,6 @@
.generic-block .clock {
position: absolute;
- top: 3px;
- left: -1px;
- font-size: 18px;
- color: #777;
}
.generic-block .tooltiptext {
@@ -342,6 +338,7 @@
left: -2px;
right: -2px;
bottom: -2px;
+ margin: 8px;
border-radius: 5px;
border: 1px solid #BBB;
pointer-events: auto;
@@ -358,12 +355,32 @@
z-index: 0;
}
+.info-block-readonly {
+ background: transparent;
+ border: 1px solid transparent;
+}
+
+.info-block .info-text {
+ position: absolute;
+ overflow: visible;
+ background: transparent;
+ top: -2px;
+ left: -2px;
+ right: -2px;
+ bottom: -2px;
+ margin: 8px;
+ border-radius: 5px;
+ border: 1px solid transparent;
+ pointer-events: none;
+}
+
.info-block .info-editor {
position: absolute;
top: -2px;
left: -2px;
right: -2px;
bottom: -2px;
+ margin: 8px;
border-radius: 5px;
border: 1px solid #BBB;
pointer-events: auto;
@@ -376,6 +393,10 @@
display: none;
}
+.highlight-error {
+ background: #ff8084;
+}
+
.highlight-yellow {
box-shadow: 0 0 8px 8px rgba(248, 248, 160, 0.5);
}
@@ -396,26 +417,26 @@
box-shadow: 0 0 8px 8px rgba(221, 221, 221, 0.5);
}
-.selectionarea {
- position: absolute;
- border: 1px solid rgba(42, 118, 198, 1);
- background-color: rgba(42, 118, 198, 0.2);
- overflow: visible;
- z-index: 1500;
-}
-
-.selectionarea.selected {
+.selection.selected {
background-color: transparent;
border: none;
opacity: 1;
cursor: move;
- /* Position the selection rectangle static so that the selection-boxe are contained within
+ /* Position the selection rectangle static so that the selection-boxes are contained within
the paper container (which is supposed to be positioned relative). The height 0 !important
makes sure the selection rectangle is not-visible, only the selection-boxes inside it (thanks to overflow: visible). */
position: static;
height: 0 !important;
}
+.selection-area {
+ position: absolute;
+ border: 1px solid rgba(42, 118, 198, 1);
+ background-color: rgba(42, 118, 198, 0.2);
+ overflow: visible;
+ z-index: 1500;
+}
+
.selection-box {
position: absolute;
border: 1px dotted rgba(42, 118, 198, 1);
@@ -424,6 +445,12 @@
z-index: 1500;
}
+.selection-box-transparent {
+ position: absolute;
+ border-radius: 5px;
+ z-index: 1500;
+}
+
/* Wires makers */
.joint-link.joint-theme-default .connection-wrap {
diff --git a/app/views/languages.html b/app/views/languages.html
new file mode 100644
index 000000000..fcf7874d7
--- /dev/null
+++ b/app/views/languages.html
@@ -0,0 +1,37 @@
+
+
+
+ {{ 'English' | translate }}
+
+
+
+
+
+ {{ 'Spanish' | translate }}
+
+
+
+
+
+ {{ 'Galician' | translate }}
+
+
+
+
+
+ {{ 'Basque' | translate }}
+
+
+
+
+
+ {{ 'French' | translate }}
+
+
+
+
+
+ {{ 'Catalan' | translate }}
+
+
+
diff --git a/app/views/menu.html b/app/views/menu.html
index 7e9650f1a..72da9f14d 100644
--- a/app/views/menu.html
+++ b/app/views/menu.html
@@ -1,7 +1,7 @@
-