diff --git a/app/scripts/controllers/design.js b/app/scripts/controllers/design.js index c96fcc2b9..f32245ef4 100644 --- a/app/scripts/controllers/design.js +++ b/app/scripts/controllers/design.js @@ -140,9 +140,14 @@ angular } }; + function loadSelectedGraph() { + utils.beginBlockingTask(); + setTimeout(function(){ + _decoupledLoadSelectedGraph(); + },500); + } + function _decoupledLoadSelectedGraph() { - - var n = graph.breadcrumbs.length; var opt = { disabled: true }; var design = false; @@ -166,8 +171,7 @@ angular graph.resetView(); graph.loadDesign(design, opt, function () { $scope.isNavigating = false; - graph.fitContent(); - + utils.endBlockingTask(); }); $scope.topModule = true; } @@ -191,24 +195,13 @@ angular graph.fitContent(); graph.resetView(); graph.loadDesign(dependency.design, opt, function () { - graph.fitContent(); $scope.isNavigating = false; - + utils.endBlockingTask(); }); $scope.information = dependency.package; } } - - - function loadSelectedGraph() { - - utils.beginBlockingTask(); - setTimeout(function(){ - _decoupledLoadSelectedGraph(); - },500); - } - $rootScope.$on('navigateProject', function (event, args) { var opt = { disabled: true }; if (typeof args.submodule !== 'undefined') { @@ -230,9 +223,7 @@ angular graph.resetView(); project.update({ deps: false }, function () { graph.loadDesign(args.project.design, opt, function () { - utils.endBlockingTask(); - }); }); diff --git a/app/scripts/controllers/menu.js b/app/scripts/controllers/menu.js index bd993a80e..44de30bbe 100644 --- a/app/scripts/controllers/menu.js +++ b/app/scripts/controllers/menu.js @@ -1027,6 +1027,17 @@ angular } }; + $scope.toggleInoutPorts = function () { + const newState = !profile.get("allowInoutPorts"); + profile.set("allowInoutPorts", newState); + if (newState) { + alertify.success(gettextCatalog.getString("Tri-state connections (inout ports) enabled")); + } else { + common.allowProjectInoutPorts = true; // if tri-state in current design, keep behaviour unchanged + alertify.success(gettextCatalog.getString("Tri-state connections (inout ports) disabled")); + } + }; + $(document).on("langChanged", function (evt, lang) { $scope.selectLanguage(lang); }); diff --git a/app/scripts/services/blockforms.js b/app/scripts/services/blockforms.js index add76e966..cd57be133 100644 --- a/app/scripts/services/blockforms.js +++ b/app/scripts/services/blockforms.js @@ -54,16 +54,18 @@ angular.module('icestudio') //-- the OK button and all the data is ok. //-- -cells: Array of blocks passed as arguments //------------------------------------------------------------------------- - function newBasic(type, callback) { + function newBasic(type, allowInoutPorts, callback) { + let name = ''; //-- Port name by default + let clock = false; //-- Clock checkbox not checked by default + let inoutDefault; let form; //-- If inside a module, the FPGA-pin option is disabled - //-- The pins are always virtual let disabled = common.isEditingSubmodule; + + //-- The pins are always virtual let virtual = disabled; - let clock = false; //-- Clock checkbox no checked by default - let name = ''; //-- Port name by default //-- Create the block by calling the corresponding function //-- according to the given type @@ -72,14 +74,24 @@ angular.module('icestudio') //-- Input port case blocks.BASIC_INPUT: - form = new forms.FormBasicInput(name, virtual, clock, disabled); + //-- InOut-pin option is present or not, and if present, it defaults to false + if (allowInoutPorts) { + inoutDefault = false; + } + + form = new forms.FormBasicInput(name, virtual, clock, disabled, inoutDefault); newBasicPort(form, callback); break; //-- Output port case blocks.BASIC_OUTPUT: - form = new forms.FormBasicOutput(name, virtual, disabled); + //-- InOut-pin option is present or not, and if present, it defaults to false + if (allowInoutPorts) { + inoutDefault = false; + } + + form = new forms.FormBasicOutput(name, virtual, disabled, inoutDefault); newBasicPort(form, callback); break; @@ -115,7 +127,12 @@ angular.module('icestudio') //-- Code block case blocks.BASIC_CODE: - form = new forms.FormBasicCode(); + //-- Inout ports are present or not, and if present, the value is just initialized to empty string + if (allowInoutPorts) { + inoutDefault = ''; + } + + form = new forms.FormBasicCode('', '', '', inoutDefault, inoutDefault); newBasicCode(form, callback); break; @@ -720,6 +737,7 @@ angular.module('icestudio') } for (o in instance.data.ports.inoutRight) { port = instance.data.ports.inoutRight[o]; + // (there's no port default rule for the right side, output) rightPorts.push({ id: port.name, name: port.name, @@ -912,7 +930,7 @@ angular.module('icestudio') //-- * cellView: Access to the graphics library //-- * callback: Function to call when the block is Edited //----------------------------------------------------------------------- - function editBasic(type, cellView, callback) { + function editBasic(type, allowInoutPorts, cellView, callback) { //-- Get information from the joint graphics library let block = cellView.model.attributes; @@ -921,7 +939,7 @@ angular.module('icestudio') let name = block.data.name + (block.data.range || ''); let virtual = block.data.virtual; let clock = block.data.clock; - let inout = (typeof block.data.inout === 'undefined') ? false : block.data.inout; + let inoutValue; let form; let color = block.data.blockColor; @@ -933,6 +951,11 @@ angular.module('icestudio') virtual = true; } + //-- InOut-pin option is present or not + if (allowInoutPorts) { + inoutValue = (typeof block.data.inout === 'undefined') ? false : !!block.data.inout; + } + //-- Call the corresponding function depending on the type of block switch (type) { @@ -940,7 +963,7 @@ angular.module('icestudio') case blocks.BASIC_INPUT: //-- Build the form, and pass the actual block data - form = new forms.FormBasicInput(name, virtual, clock, disabled, inout); + form = new forms.FormBasicInput(name, virtual, clock, disabled, inoutValue); editBasicPort(form, cellView, callback); break; @@ -948,7 +971,7 @@ angular.module('icestudio') case blocks.BASIC_OUTPUT: //-- Build the form, and pass the actual block data - form = new forms.FormBasicOutput(name, virtual, disabled, inout); + form = new forms.FormBasicOutput(name, virtual, disabled, inoutValue); editBasicPort(form, cellView, callback); break; @@ -977,7 +1000,7 @@ angular.module('icestudio') break; case blocks.BASIC_CODE: - editBasicCode(cellView, callback); + editBasicCode(allowInoutPorts, cellView, callback); break; case blocks.BASIC_INFO: @@ -1380,33 +1403,43 @@ angular.module('icestudio') } - function editBasicCode(cellView, callback) { + function editBasicCode(allowInoutPorts, cellView, callback) { + + let inPortNames = ''; + let outPortNames = ''; + let inoutLeftPortNames; + let inoutRightPortNames; //-- Get information from the joint graphics library let block = cellView.model.attributes; - // Backward compatibility + // Compatibility between tri-state/non-tri-state project formats if (typeof block.data.ports.inoutLeft === 'undefined') { block.data.ports.inoutLeft = []; block.data.ports.inoutRight = []; } - //-- Get the input port names as a string - let inPortNames = blocks.portsInfo2Str(block.data.ports.in); + //-- Get the input port names as a string + if (block.data.ports.in) { + inPortNames = blocks.portsInfo2Str(block.data.ports.in); + } //-- Get the output port names as a string - let outPortNames = blocks.portsInfo2Str(block.data.ports.out); + if (block.data.ports.out) { + outPortNames = blocks.portsInfo2Str(block.data.ports.out); + } //-- Get the input param names as a string let inParamNames = blocks.portsInfo2Str(block.data.params); + //-- Get the optional left/right InputOutput port names as strings + //-- InputOutput port name fields are present or not, and if present, are initialized to strings + if (allowInoutPorts) { + //-- Get the left side InputOutput port names as a string + inoutLeftPortNames = blocks.portsInfo2Str(block.data.ports.inoutLeft); - - //-- Get the input/output port names at left side of block as a string - let inoutLeftPortNames = blocks.portsInfo2Str(block.data.ports.inoutLeft); - - //-- Get the input/output port names at right side of block as a string - let inoutRightPortNames = blocks.portsInfo2Str(block.data.ports.inoutRight); - + //-- Get the right side InputOutput port names as a string + inoutRightPortNames = blocks.portsInfo2Str(block.data.ports.inoutRight); + } //-- Create the form let form = new forms.FormBasicCode( @@ -1467,7 +1500,7 @@ angular.module('icestudio') //-- Get all the wires of the current block let connectedWires = graph.getConnectedLinks(cellView.model); - //---------- Chage the block + //---------- Change the block graph.startBatch('change'); //-- Remove the current block diff --git a/app/scripts/services/blocks.js b/app/scripts/services/blocks.js index edac33263..7f62e9b1a 100644 --- a/app/scripts/services/blocks.js +++ b/app/scripts/services/blocks.js @@ -107,7 +107,10 @@ angular.module('icestudio') //-- * Particular information: //-- -clock: (bool). If the port is a clock or not //-- * true: It is a clock signal - //-- * False: Normal signal + //-- * false: Normal signal + //-- -inout: (bool). If the port is inout or normal + //-- * true: It is tri-state + //-- * false: It is normal two-state //------------------------------------------------------------------------- class InputPortBlock extends PortBlock { @@ -118,7 +121,6 @@ angular.module('icestudio') //-- Particular information this.data.clock = clock; //-- Optional. Is the port a clock input? - this.data.inout = inout; } } @@ -128,7 +130,10 @@ angular.module('icestudio') //-- Class: Output port. The information goes from the FPGA to the //-- outside. Or from one block to another the upper level //-- - //-- NO particular information + //-- * Particular information: + //-- -inout: (bool). If the port is inout or normal + //-- * true: It is tri-state + //-- * false: It is normal two-state //------------------------------------------------------------------------- class OutputPortBlock extends PortBlock { @@ -230,6 +235,8 @@ angular.module('icestudio') //-- * inPortsInfo: Array of PortInfos //-- * outPortsInfo: Array of PortInfos //-- * inParamsInfo: Array of PortInfos + //-- * inoutLeftPortsInfo: Optional Array of PortInfos + //-- * inoutRightPortsInfo: Optional Array of PortInfos //-- //-- PortInfos: //-- * name: String @@ -297,28 +304,33 @@ angular.module('icestudio') this.data.params.push(info); }); - //-- Insert the InputOutput portInfo - inoutLeftPortsInfo.forEach(portInfo => { - let info = { - name: portInfo.name, - range: portInfo.rangestr, - size: portInfo.size > 1 ? portInfo.size : undefined - }; + //-- Insert the InputOutput portInfo, left and/or right + if (inoutLeftPortsInfo) { + inoutLeftPortsInfo.forEach(portInfo => { - this.data.ports.inoutLeft.push(info); - }); - //-- Insert the InputOutput portInfo - inoutRightPortsInfo.forEach(portInfo => { + let info = { + name: portInfo.name, + range: portInfo.rangestr, + size: portInfo.size > 1 ? portInfo.size : undefined + }; - let info = { - name: portInfo.name, - range: portInfo.rangestr, - size: portInfo.size > 1 ? portInfo.size : undefined - }; + this.data.ports.inoutLeft.push(info); + }); + } - this.data.ports.inoutRight.push(info); - }); + if (inoutRightPortsInfo) { + inoutRightPortsInfo.forEach(portInfo => { + + let info = { + name: portInfo.name, + range: portInfo.rangestr, + size: portInfo.size > 1 ? portInfo.size : undefined + }; + + this.data.ports.inoutRight.push(info); + }); + } } } diff --git a/app/scripts/services/common.js b/app/scripts/services/common.js index 9cc4087f2..5d828fd5d 100644 --- a/app/scripts/services/common.js +++ b/app/scripts/services/common.js @@ -31,6 +31,9 @@ angular // Project status: Has it change from the previous build or not? this.hasChangesSinceBuild = false; + // Tri-state ports: Are they present in any opened designs or blocks, and is this approved? + // (User profile "allowInoutPorts" is false) + this.allowProjectInoutPorts = false; // All project dependencies this.allDependencies = {}; diff --git a/app/scripts/services/forms.js b/app/scripts/services/forms.js index 051ebdb79..491395b6e 100644 --- a/app/scripts/services/forms.js +++ b/app/scripts/services/forms.js @@ -834,7 +834,7 @@ angular.module('icestudio') //-- Field 0: Text input let field0 = new TextField( msg, //-- Top message - name, //-- Default value + name, //-- Default value 0 //-- Field id ); @@ -974,14 +974,16 @@ angular.module('icestudio') //-- | //-- [✅️] FPGA pin | //-- [ ] Show clock | - //-- [ ] InOut pin | + //-- [ ] InOut pin (*Optional) | //----------------------------------------+ //-- INPUTS: //-- * name: Default port name //-- * virtual: Is this a virtual or real port? - //-- * Clock: The input pin carries a clock signal + //-- * clock: The input pin carries a clock signal //-- * disabled: FPGA-pin checkbox disabled - constructor(name = '', virtual = false, clock = false, disabled = false, inout = false) { + //-- * inoutValue: If undefined, InOut-pin checkbox is hidden + // Otherwise, it is a boolean value to initialize the checkbox + constructor(name = '', virtual = false, clock = false, disabled = false, inoutValue = undefined) { //-- Create a blank BasicPortForm (calling the upper Class) super(gettextCatalog.getString('Input port name:'), @@ -1005,17 +1007,17 @@ angular.module('icestudio') //-- Add the fields to the form this.addField(field2); - //-- Field 3: Checkbox for configuring the pi as inout - - let field3 = new CheckboxField( - gettextCatalog.getString('InOut pin'), - inout, //-- Default value - 3 //-- Field id - ); - - //-- Add the fields to the form - this.addField(field3); + //-- Field 3: Checkbox for configuring the pin as inout + if (inoutValue !== undefined) { + let field3 = new CheckboxField( + gettextCatalog.getString('InOut pin'), + inoutValue, //-- Default value + 3 //-- Field id + ); + //-- Add the fields to the form + this.addField(field3); + } //-- Store the initial values //-- (For comparing it later with the new ones and detect if @@ -1023,7 +1025,9 @@ angular.module('icestudio') this.nameIni = name; this.virtualIni = virtual; this.clockIni = clock; - this.inoutIni = inout; + if (inoutValue !== undefined) { + this.inoutIni = inoutValue; + } } //------------------------------------------------ @@ -1040,11 +1044,10 @@ angular.module('icestudio') //-- it indicates if this is a clock input this.clock = this.values[2]; - //-- Input port have the inout property - //-- it indicates if this pin is an inout pin + //-- There may be no inout field in Values (undefined) + //-- If inout field is present, it indicates if this pin is an inout type this.inout = this.values[3]; - //-- Check all ports again... There could be no data buses defined //-- as clocks (it is only for 1-wire ports) for (let portInfo of this.portInfos) { @@ -1065,10 +1068,10 @@ angular.module('icestudio') //-- There have been no errors. Detect if there have been some //-- changes in the values - this.changed = (this.nameIni !== this.values[0] || + this.changed = this.nameIni !== this.values[0] || this.virtualIni !== this.virtual || - this.inoutIni !== this.inout || - this.clockIni !== this.clock); + this.clockIni !== this.clock || + this.hasOwnProperty('inoutIni') && this.inoutIni !== this.inout; } //------------------------------------------------------------- @@ -1118,13 +1121,15 @@ angular.module('icestudio') //-- +--------------------------+ | //-- | //-- [✅️] FPGA pin | - //-- [ ] InOut pin | + //-- [ ] InOut pin (*Optional) | //----------------------------------------+ //-- INPUTS: - //-- * msg: Message above the text box //-- * name: Default port name //-- * virtual: Is this a virtual or real port? - constructor(name = '', virtual = false, disabled = false, inout = false) { + //-- * disabled: FPGA-pin checkbox disabled + //-- * inoutValue: If undefined, InOut-pin checkbox is hidden + // Otherwise, it is a boolean value to initialize the checkbox + constructor(name = '', virtual = false, disabled = false, inoutValue = undefined) { //-- Create a blank BasicPortForm (calling the upper Class) super(gettextCatalog.getString('Output port name'), @@ -1135,20 +1140,28 @@ angular.module('icestudio') //-- Store the type of block associated with the Form this.type = blocks.BASIC_OUTPUT; - let field2 = new CheckboxField( - gettextCatalog.getString('InOut pin'), - inout, //-- Default value - 2 //-- Field id - ); + //-------- Add the particular fields + + //-- Field 2: Checkbox for configuring the pin as inout + if (inoutValue !== undefined) { + let field2 = new CheckboxField( + gettextCatalog.getString('InOut pin'), + inoutValue, //-- Default value + 2 //-- Field id + ); + + //-- Add the field to the form + this.addField(field2); + } - //-- Add the fields to the form - this.addField(field2); //-- Store the initial values //-- (For comparing it later with the new ones and detect if //-- there have been changes) this.nameIni = name; this.virtualIni = virtual; - this.inoutIni = inout; + if (inoutValue !== undefined) { + this.inoutIni = inoutValue; + } } process(evt) { @@ -1156,15 +1169,15 @@ angular.module('icestudio') //-- Process the form as an BasicPort super.process(evt); - //-- Input port have the inout property - //-- it indicates if this pin is an inout pin + //-- There may be no inout field in Values (undefined) + //-- If inout field is present, it indicates if this pin is an inout type this.inout = this.values[2]; //-- There have been no errors. Detect if there have been some //-- changes in the values - this.changed = (this.nameIni !== this.values[0] || + this.changed = this.nameIni !== this.values[0] || this.virtualIni !== this.virtual || - this.inoutIni !== this.inout); + this.hasOwnProperty('inoutIni') && this.inoutIni !== this.inout; } //------------------------------------------------------------- @@ -1584,9 +1597,13 @@ angular.module('icestudio') //-- INPUTS: //-- * portsIn: Input port names (separated by commas) //-- * portsOut: Output port names (separated by commas) - //-- * paramsIn: Input parameters names (separataed by commas) + //-- * paramsIn: Input parameters names (separated by commas) + //-- * portsInOutLeft: If undefined, InputOutput port names field is hidden + // Otherwise, it is string of port names (separated by commas) to populate the field + //-- * portsInOutRight: If undefined, InputOutput port names field is hidden + // Otherwise, it is string of port names (separated by commas) to populate the field //----------------------------------------------------------------- - constructor(portsIn = '', portsOut = '', paramsIn = '', portsInOutLeft = '', portsInOutRight = '') { + constructor(portsIn = '', portsOut = '', paramsIn = '', portsInOutLeft = undefined, portsInOutRight = undefined) { //-- Create a blank Form (calling the upper Class) super(); @@ -1614,25 +1631,37 @@ angular.module('icestudio') 2 ); - //-- Field 3: InputOutput port names at the left - let field3 = new TextField( - gettextCatalog.getString('InOut Left ports'), //-- Top message - portsInOutLeft, //-- Default InputOutput port names at the left - 3 //-- Field id - ); - - //-- Field 4: InputOutput port names at the right - let field4 = new TextField( - gettextCatalog.getString('InOut Right ports'), //-- Top message - portsInOutRight, //-- Default InputOutput port names at the right - 4 //-- Field id - ); - //-- Add the fields to the form this.addField(field0); this.addField(field1); this.addField(field2); - this.addField(field3); - this.addField(field4); + + //-- Optional fields + let field3; + let field4; + + //-- Field 3: InputOutput port names at the left + if (portsInOutLeft !== undefined) { + field3 = new TextField( + gettextCatalog.getString('InOut Left ports'), //-- Top message + portsInOutLeft, //-- Default InputOutput port names at the left + 3 //-- Field id + ); + + //-- Add the field to the form + this.addField(field3); + } + + //-- Field 4: InputOutput port names at the right + if (portsInOutRight !== undefined) { + field4 = new TextField( + gettextCatalog.getString('InOut Right ports'), //-- Top message + portsInOutRight, //-- Default InputOutput port names at the right + 4 //-- Field id + ); + + //-- Add the field to the form + this.addField(field4); + } //-- Control the notifications generated by //-- the errors when processing the form @@ -1640,12 +1669,14 @@ angular.module('icestudio') //-- Store the initial values used for creating the form //-- They will be used later for detecting a change in - //-- the vaues introduced by the user + //-- the values introduced by the user this.iniPortsIn = portsIn; this.iniPortsOut = portsOut; this.iniParamsIn = paramsIn; - this.iniPortsInOutLeft = portsInOutLeft; - this.iniPortsInOutRight = portsInOutRight; + if (portsInOutLeft !== undefined && portsInOutRight !== undefined) { + this.iniPortsInOutLeft = portsInOutLeft; + this.iniPortsInOutRight = portsInOutRight; + } } //----------------------------------------------------------------------- @@ -1714,14 +1745,17 @@ angular.module('icestudio') //-- Parse the input parameters this.inParams = Form.parseNames(this.values[2]); - //-- Values[3]: Input port names at the right of the block - //-- Parse the input/output port names - this.inoutLeftPorts = Form.parseNames(this.values[3]); - - //-- Values[4]: input/Output port names at the right of the block - //-- Parse the input/output port names - this.inoutRightPorts = Form.parseNames(this.values[4]); + //-- Values[3]: InputOutput port names at the left of the block + //-- If field is present in Values, then parse the inout port names + if (this.values[3]) { + this.inoutLeftPorts = Form.parseNames(this.values[3]); + } + //-- Values[4]: InputOutput port names at the right of the block + //-- If field is present in Values, then parse the inout port names + if (this.values[4]) { + this.inoutRightPorts = Form.parseNames(this.values[4]); + } } @@ -1742,8 +1776,12 @@ angular.module('icestudio') this.inPortsInfo = this.getPortInfo(this.inPorts, evt); this.outPortsInfo = this.getPortInfo(this.outPorts, evt); this.inParamsInfo = this.getPortInfo(this.inParams, evt); - this.inoutLeftPortsInfo = this.getPortInfo(this.inoutLeftPorts, evt); - this.inoutRightPortsInfo = this.getPortInfo(this.inoutRightPorts, evt); + if (this.hasOwnProperty('inoutLeftPorts')) { + this.inoutLeftPortsInfo = this.getPortInfo(this.inoutLeftPorts, evt); + } + if (this.hasOwnProperty('inoutRightPorts')) { + this.inoutRightPortsInfo = this.getPortInfo(this.inoutRightPorts, evt); + } //-- Validate values entered by the user //-- There cannot be inputs, outputs and params with the same name @@ -1752,12 +1790,17 @@ angular.module('icestudio') //-- Array for storing all the port names created let allPortnames = []; - //-- Array with the input/output given by the user - let userPorts = this.inPortsInfo.concat(this.outPortsInfo, this.inoutLeftPortsInfo, this.inoutRightPortsInfo); + //-- Array with the inputs and outputs given by the user + let userPorts = this.inPortsInfo.concat(this.outPortsInfo); //-- Add the array with the input parameters userPorts = userPorts.concat(this.inParamsInfo); + //-- Add the optional arrays with the left/right InputOutput ports + if (this.inoutLeftPortsInfo && this.inoutRightPortsInfo) { + userPorts = userPorts.concat(this.inoutLeftPortsInfo, this.inoutRightPortsInfo); + } + //-- Analyze all the port names, one by one for (let portInfo of userPorts) { @@ -1804,21 +1847,24 @@ angular.module('icestudio') //-- Get the input param names as a string let inParamNames = blocks.portsInfo2Str(this.inParamsInfo); - //-- Get the input/output port names as a string - let inoutLeftPortNames = blocks.portsInfo2Str(this.inoutLeftPortsInfo); + //-- Get the optional left/right InputOutput port names as strings + let inoutLeftPortNames; + let inoutRightPortNames; - //-- Get the input/output port names as a string - let inoutRightPortNames = blocks.portsInfo2Str(this.inoutRightPortsInfo); + if (this.inoutLeftPortsInfo && this.inoutRightPortsInfo) { + inoutLeftPortNames = blocks.portsInfo2Str(this.inoutLeftPortsInfo); + inoutRightPortNames = blocks.portsInfo2Str(this.inoutRightPortsInfo); + } //-- Compare these values with the initial ones //-- to detec if there has been a change //-- All the items compared are Strings - let changed = inPortNames !== this.iniPortsIn || - outPortNames !== this.iniPortsOut || - inoutLeftPortNames !== this.iniPortsInOutLeft || - inoutRightPortNames !== this.iniPortsInOutRight || - inParamNames !== this.iniParamsIn; + let changed = this.iniPortsIn !== inPortNames || + this.iniPortsOut !== outPortNames || + this.iniParamsIn !== inParamNames || + this.hasOwnProperty('iniPortsInOutLeft') && this.iniPortsInOutLeft !== inoutLeftPortNames || + this.hasOwnProperty('iniPortsInOutRight') && this.iniPortsInOutRight !== inoutRightPortNames; //-- Return a boolean value return changed; @@ -1839,8 +1885,8 @@ angular.module('icestudio') //-- Field 0: Memory block names let field0 = new TextField( gettextCatalog.getString('Memory blocks'), //-- Top message - names, //-- Default Input port names - 0 //-- Field id + names, //-- Default names + 0 //-- Field id ); //-------- Field 1: Combobox @@ -2058,7 +2104,7 @@ angular.module('icestudio') 0 //-- Field id ); - //-- Field 2: Checkbox for selecting if the constant is a + //-- Field 1: Checkbox for selecting if the constant is a //-- local parameter or not let field1 = new CheckboxField( gettextCatalog.getString('Local parameter'), diff --git a/app/scripts/services/graph.js b/app/scripts/services/graph.js index a44b15155..bb0d58c4f 100644 --- a/app/scripts/services/graph.js +++ b/app/scripts/services/graph.js @@ -522,8 +522,8 @@ angular.module('icestudio') if (type.indexOf('basic.') !== -1) { // Edit basic blocks if (paper.options.enabled) { - blockforms.editBasic(type, cellView, addCell); - + const allowInoutPorts = profile.get('allowInoutPorts') || common.allowProjectInoutPorts; + blockforms.editBasic(type, allowInoutPorts, cellView, addCell); } } else if (common.allDependencies[type]) { @@ -1041,7 +1041,8 @@ angular.module('icestudio') }; this.createBasicBlock = function (type) { - blockforms.newBasic(type, function (cells) { + const allowInoutPorts = profile.get('allowInoutPorts') || common.allowProjectInoutPorts; + blockforms.newBasic(type, allowInoutPorts, function (cells) { self.addDraggableCells(cells); }); }; @@ -1265,57 +1266,51 @@ angular.module('icestudio') this.pasteSelected = function () { if (document.activeElement.tagName === 'A' || document.activeElement.tagName === 'BODY') { - utils.pasteFromClipboard(function (object) { - if (object.version === common.VERSION) { - self.appendDesign(object.design, object.dependencies); - } + utils.pasteFromClipboard(profile, function (object) { + self.appendDesign(object.design, object.dependencies); }); } }; this.pasteAndCloneSelected = function () { if (document.activeElement.tagName === 'A' || document.activeElement.tagName === 'BODY') { - utils.pasteFromClipboard(function (object) { - if (object.version === common.VERSION) { - - let hash = {}; - // We will clone all dependencies - if (typeof object.dependencies !== false && - object.dependencies !== false && - object.dependencies !== null) { - - var dependencies = utils.clone(object.dependencies); - object.dependencies = {}; - let hId = false; - let dep = false; - let dat = false; - let seq = false; - let oldversion = false; - - for (dep in dependencies) { - dependencies[dep].package.name = dependencies[dep].package.name + ' CLONE'; - dat = new Date(); - seq = dat.getTime(); - oldversion = dependencies[dep].package.version.replace(/(.*)(-c\d*)/, '$1'); - dependencies[dep].package.version = oldversion + '-c' + seq; - - hId = utils.dependencyID(dependencies[dep]); - object.dependencies[hId] = dependencies[dep]; - hash[dep] = hId; - } - - //reassign dependencies - - object.design.graph.blocks = object.design.graph.blocks.map(function (e) { - if (typeof e.type !== 'undefined' && - typeof hash[e.type] !== 'undefined') { - e.type = hash[e.type]; - } - return e; - }); + utils.pasteFromClipboard(profile, function (object) { + let hash = {}; + // We will clone all dependencies + if (typeof object.dependencies !== false && + object.dependencies !== false && + object.dependencies !== null) { + + var dependencies = utils.clone(object.dependencies); + object.dependencies = {}; + let hId = false; + let dep = false; + let dat = false; + let seq = false; + let oldversion = false; + + for (dep in dependencies) { + dependencies[dep].package.name = dependencies[dep].package.name + ' CLONE'; + dat = new Date(); + seq = dat.getTime(); + oldversion = dependencies[dep].package.version.replace(/(.*)(-c\d*)/, '$1'); + dependencies[dep].package.version = oldversion + '-c' + seq; + + hId = utils.dependencyID(dependencies[dep]); + object.dependencies[hId] = dependencies[dep]; + hash[dep] = hId; } - self.appendDesign(object.design, object.dependencies); + + //reassign dependencies + object.design.graph.blocks = object.design.graph.blocks.map(function (e) { + if (typeof e.type !== 'undefined' && + typeof hash[e.type] !== 'undefined') { + e.type = hash[e.type]; + } + return e; + }); } + self.appendDesign(object.design, object.dependencies); }); } }; @@ -1323,9 +1318,7 @@ angular.module('icestudio') this.duplicateSelected = function() { if (hasSelection()) { utils.duplicateSelected(selection, graph, function (object) { - if (object.version === common.VERSION) { - self.appendDesign(object.design, object.dependencies); - } + self.appendDesign(object.design, object.dependencies); }); } }; @@ -1458,7 +1451,7 @@ angular.module('icestudio') let cells = graphToCells(design.graph, opt); - self.fitContent(); + self.fitContent(); graph.addCells(cells); @@ -1470,23 +1463,14 @@ angular.module('icestudio') } if (callback) { - - callback(); - - utils.endBlockingTask(); - - self.fitContent(); - } else { - - utils.endBlockingTask(); - - self.fitContent(); - } - + + self.fitContent(); return true; } + + return false; }; function graphToCells(_graph, opt) { @@ -1656,7 +1640,7 @@ angular.module('icestudio') }); if (isMigrated) { - alertify.warning(gettextCatalog.getString("If you see blank IN/OUT pins, it is because equivalent pins do not exist on this board")); + alertify.warning(gettextCatalog.getString('If you see blank IN/OUT pins, it is because equivalent pins do not exist on this board')); } diff --git a/app/scripts/services/profile.js b/app/scripts/services/profile.js index aad728e72..ef32eecd9 100644 --- a/app/scripts/services/profile.js +++ b/app/scripts/services/profile.js @@ -20,6 +20,7 @@ angular.module('icestudio') 'board': '', //-- Selected board 'boardRules': true, //-- Boardrules (active by default) + 'allowInoutPorts': false, //-- Tri-state (inout ports) available (not included by default) 'collection': '', //-- Selected collection 'externalCollections': '', //-- Path for the external collections 'externalPlugins': '', //-- Path for the external paths @@ -54,6 +55,7 @@ angular.module('icestudio') self.data = { 'board': data.board || '', 'boardRules': data.boardRules !== false, + 'allowInoutPorts': data.allowInoutPorts === true, 'collection': data.collection || '', 'language': data.language || 'en', 'uiTheme': data.uiTheme || 'dark', diff --git a/app/scripts/services/project.js b/app/scripts/services/project.js index b0b857ba3..327c3f08c 100644 --- a/app/scripts/services/project.js +++ b/app/scripts/services/project.js @@ -55,22 +55,17 @@ angular.module('icestudio') project[key] = obj; } }; - this.open = function (filepath, emptyPath) { - + this.open = function (filepath, emptyPath) { let _this=this; utils.beginBlockingTask(); setTimeout(function(){ - _this._decoupledOpen(filepath,emptyPath); + _this._decoupledOpen(filepath,emptyPath); },200); - }; - this._decoupledOpen = function(filepath,emptyPath){ - - + }; - + this._decoupledOpen = function(filepath,emptyPath){ var self = this; - self.path = emptyPath ? '' : filepath; self.filepath = filepath; utils.readFile(filepath) @@ -84,42 +79,51 @@ angular.module('icestudio') .catch(function () { alertify.error(gettextCatalog.getString('Invalid project format'), 30); }); - }; + }; this.load = function (name, data) { var self = this; if (!checkVersion(data.version)) { + utils.endBlockingTask(); return; } - project = _safeLoad(data, name); - if (project.design.board !== common.selectedBoard.name) { - var projectBoard = boards.boardLabel(project.design.board); - alertify.set('confirm', 'labels', { - 'ok': gettextCatalog.getString('Load'), - 'cancel': gettextCatalog.getString('Convert') - }); - alertify.confirm( - gettextCatalog.getString('This project is designed for the {{name}} board.', { name: utils.bold(projectBoard) }) + '
' + - gettextCatalog.getString('You can load it as it is or convert it to use the {{name}} board.', { name: utils.bold(common.selectedBoard.info.label) }), - function () { - // Load - setTimeout(function(){ - _load(); - },100); - }, - function () { - - setTimeout(function(){ - // Convert - project.design.board = common.selectedBoard.name; - - _load(true, boardMigration(projectBoard, common.selectedBoard.name)); - },100); + + project = _safeUpgradeVersion(data, name); + utils.approveProjectBlock(profile, project, true).then((result) => { + if (result === 'cancel') { + console.log('cancelLoad'); + utils.endBlockingTask(); + return; + } + + if (project.design.board !== common.selectedBoard.name) { + var projectBoard = boards.boardLabel(project.design.board); + alertify.set('confirm', 'labels', { + 'ok': gettextCatalog.getString('Load'), + 'cancel': gettextCatalog.getString('Convert') }); - } - else { - _load(); - } + alertify.confirm( + gettextCatalog.getString('This project is designed for the {{name}} board.', { name: utils.bold(projectBoard) }) + '
' + + gettextCatalog.getString('You can load it as it is or convert it to use the {{name}} board.', { name: utils.bold(common.selectedBoard.info.label) }), + function () { + // Load + setTimeout(function(){ + _load(); + },100); + }, + function () { + // Convert + setTimeout(function(){ + project.design.board = common.selectedBoard.name; + + _load(true, boardMigration(projectBoard, common.selectedBoard.name)); + },100); + }); + } + else { + _load(); + } + }); function _load(reset, originalBoard) { common.allDependencies = project.dependencies; @@ -137,11 +141,10 @@ angular.module('icestudio') } var ret = graph.loadDesign(project.design, opt, function () { - graph.resetCommandStack(); - graph.fitContent(); alertify.success(gettextCatalog.getString('Project {{name}} loaded', { name: utils.bold(name) })); common.hasChangesSinceBuild = true; + utils.endBlockingTask(); }); if (ret) { @@ -191,7 +194,7 @@ angular.module('icestudio') return true; } - function _safeLoad(data, name) { + function _safeUpgradeVersion(data, name) { // Backwards compatibility var project = {}; switch (data.version) { @@ -241,6 +244,7 @@ angular.module('icestudio') } // Add current dependency block = pruneBlock(block); + delete block.design.deps; block.package.name = block.package.name || key; block.package.description = block.package.description || key; @@ -451,10 +455,10 @@ angular.module('icestudio') if (!checkVersion(data.version)) { return; } + var name = utils.basename(filepath); - - var block = _safeLoad(data, name); - if (block) { + var block = _safeUpgradeVersion(data, name); + if (block) { var origPath = utils.dirname(filepath); var destPath = utils.dirname(self.path); // 1. Parse and find included files @@ -467,43 +471,31 @@ angular.module('icestudio') if (files.length > 0) { // 2. Check project's directory if (self.path) { - // 3. Copy the included files - copyIncludedFiles(files, origPath, destPath, function (success) { - if (success) { - // 4. Success: import block - doImportBlock(); - } - }); + // 3. Block will be imported if copying the included files is successful + doImportBlock(files, origPath, destPath); } else { alertify.confirm(gettextCatalog.getString('This import operation requires a project path. You need to save the current project. Do you want to continue?'), function () { $rootScope.$emit('saveProjectAs', function () { - - // 3. Copy the included files - - copyIncludedFiles(files, origPath, destPath, function (success) { - if (success) { - // 4. Success: import block - doImportBlock(); - } - }); + // 3. Block will be imported if copying the included files is successful + doImportBlock(files, origPath, destPath); }); }); } } else { // No included files to copy - // 4. Import block doImportBlock(); } } - function doImportBlock() { - self.addBlock(block); - if (notification) { - alertify.success(gettextCatalog.getString('Block {{name}} imported', { name: utils.bold(block.package.name) })); - } + function doImportBlock(files, origPath, destPath) { + self.addBlock(block, files, origPath, destPath).then(() => { + if (notification) { + alertify.success(gettextCatalog.getString('Block {{name}} imported', { name: utils.bold(block.package.name) })); + } + }); } }) .catch(function () { @@ -571,7 +563,15 @@ angular.module('icestudio') var block = _project.design.graph.blocks[i]; switch (block.type) { case blocks.BASIC_INPUT: + if (block.data.inout === false) { + delete block.data.inout; + } + break; case blocks.BASIC_OUTPUT: + if (block.data.inout === false) { + delete block.data.inout; + } + break; case blocks.BASIC_OUTPUT_LABEL: case blocks.BASIC_INPUT_LABEL: case blocks.BASIC_CONSTANT: @@ -581,6 +581,12 @@ angular.module('icestudio') for (var j in block.data.ports.in) { delete block.data.ports.in[j].default; } + if (block.data.ports.inoutLeft && !block.data.ports.inoutLeft.length) { + delete block.data.ports.inoutLeft; + } + if (block.data.ports.inoutRight && !block.data.ports.inoutRight.length) { + delete block.data.ports.inoutRight; + } break; case blocks.BASIC_INFO: delete block.data.text; @@ -650,19 +656,40 @@ angular.module('icestudio') graph.createBasicBlock(type); }; - this.addBlock = function (block) { - if (block) { - block = _safeLoad(block); - block = pruneBlock(block); - if (block.package.name.toLowerCase().indexOf('generic-') === 0) { - var dat = new Date(); - var seq = dat.getTime(); - block.package.otid = seq; - } - var type = utils.dependencyID(block); - utils.mergeDependencies(type, block); - graph.createBlock(type, block); - } + this.addBlock = function (block, files, origPath, destPath) { + return new Promise((resolve, reject) => { + return utils.approveProjectBlock(profile, block).then((result) => { + if (result === 'cancel') { + reject('cancelImport'); + return; + } + + if (files) { + copyIncludedFiles(files, origPath, destPath, function (success) { + if (success) { + _createBlock(); + } else { + reject('copyIncludedFiles'); + } + }); + } else { + _createBlock(); + } + + function _createBlock() { + block = pruneBlock(block); + if (block.package.name.toLowerCase().indexOf('generic-') === 0) { + var dat = new Date(); + var seq = dat.getTime(); + block.package.otid = seq; + } + var type = utils.dependencyID(block); + utils.mergeDependencies(type, block); + graph.createBlock(type, block); + resolve(); + } + }); + }); }; function pruneBlock(block) { diff --git a/app/scripts/services/utils.js b/app/scripts/services/utils.js index c7bf0e448..a28926fc1 100644 --- a/app/scripts/services/utils.js +++ b/app/scripts/services/utils.js @@ -4,6 +4,7 @@ angular.module('icestudio') .service('utils', function ($rootScope, gettextCatalog, common, + blocks, forms, _package, window, @@ -1146,7 +1147,8 @@ angular.module('icestudio') }); }; - this.pasteFromClipboard = function (callback) { + this.pasteFromClipboard = function (profile, callback) { + var _this = this; nodeCP.paste(function (err, text) { if (err) { if (common.LINUX) { @@ -1184,7 +1186,19 @@ angular.module('icestudio') // Parse the global clipboard var clipboard = JSON.parse(text); if (callback && clipboard && clipboard.icestudio) { - callback(clipboard.icestudio); + const block = clipboard.icestudio; + if (block.version === common.VERSION) { + _this.approveProjectBlock(profile, block).then((result) => { + if (result === 'cancel') { + console.log('cancelPaste'); + return; + } + + callback(block); + }); + } else { + alertify.error(gettextCatalog.getString('Cannot paste from a different project format ({{version}})', { version: block.version }), 5); + } } } }); @@ -1232,7 +1246,7 @@ angular.module('icestudio') // - design.graph // - dependencies - var blocks = []; + var _blocks = []; var wires = []; var p = { version: common.VERSION, @@ -1263,7 +1277,7 @@ angular.module('icestudio') cell.type === 'ice.Memory') { block.size = cell.size; } - blocks.push(block); + _blocks.push(block); } else if (cell.type === 'ice.Wire') { var wire = {}; wire.source = { @@ -1282,7 +1296,7 @@ angular.module('icestudio') p.design.board = common.selectedBoard.name; p.design.graph = { - blocks: blocks, + blocks: _blocks, wires: wires }; @@ -1300,9 +1314,8 @@ angular.module('icestudio') this.findSubDependencies = function (dependency) { var subDependencies = []; if (dependency) { - var blocks = dependency.design.graph.blocks; - for (var i in blocks) { - var type = blocks[i].type; + for (var i in dependency.design.graph.blocks) { + var type = dependency.design.graph.blocks[i].type; if (type.indexOf('basic.') === -1) { subDependencies.push(type); var newSubDependencies = this.findSubDependencies(common.allDependencies[type]); @@ -1314,6 +1327,103 @@ angular.module('icestudio') return subDependencies; }; + // Check for Advanced block being opened or imported: If it has tri-state, and user does not have + // Advanced profile setting for tri-state, user needs to approve or cancel + // + // Return 'cancel' or return 'ok' or a variant of 'ok' + this.approveProjectBlock = function (profile, block, isLoad) { + if (profile.get('allowInoutPorts') || common.allowProjectInoutPorts) { + return Promise.resolve('ok'); + } + + const hasInoutPorts = checkIsAnyInout(block); + if (!hasInoutPorts) { + return Promise.resolve('ok'); + } + + // user can approve by either updating profile 'allowInoutPorts' or setting + // flag common.allowProjectInoutPorts + const prompt = (isLoad ? + gettextCatalog.getString('You are loading a design that uses "tri-state".') : + gettextCatalog.getString('You are importing a block that uses "tri-state".')) + + ' ' + + gettextCatalog.getString('Tri-state (aka high-Z, bidirectional, or inout) ports are not recommended in standard designs.

You will be asked to update your Preferences (Advanced user setting) or you can just open this design on a preview basis.

Continue?'); + return new Promise((resolve) => { + alertify.confirm(prompt, () => { + resolve('ok'); + }, () => { + resolve('cancel'); + }); + }) + .then((result) => { + if (result === 'cancel') { + return result; + } + + return new Promise((resolve) => { + alertify.set('confirm', 'defaultFocus', 'cancel'); + alertify.confirm(gettextCatalog.getString('Click "Yes" to allow tri-state and update Preferences:
   Advanced features -> Allow tri-state connections

Click "This time" to view tri-state for this design only.'), () => { + profile.set('allowInoutPorts', true); + alertify.warning(gettextCatalog.getString('Changed Preferences: Allow tri-state connections')); + resolve('ok_advanced'); + }, () => { + common.allowProjectInoutPorts = true; + alertify.warning(gettextCatalog.getString('Viewing tri-state')); + resolve('ok_this_time'); + }).set('labels', { + ok: gettextCatalog.getString('Yes'), + cancel: gettextCatalog.getString('This time') + }); + }) + .then((result) => { + alertify.set('confirm', 'defaultFocus', 'ok'); + alertify.set('confirm', 'labels', { + ok: gettextCatalog.getString('OK'), + cancel: gettextCatalog.getString('Cancel') + }); + return result; + }); + }); + }; + + function checkIsAnyInout(project) { + if (_checkIsAnyInout(project)) { + return true; + } + for (var d in project.dependencies) { + if (_checkIsAnyInout(project.dependencies[d])) { + return true; + } + } + + function _checkIsAnyInout(_project) { + for (var i in _project.design.graph.blocks) { + var block = _project.design.graph.blocks[i]; + switch (block.type) { + case blocks.BASIC_INPUT: + case blocks.BASIC_OUTPUT: + if (block.data.inout) { + return true; + } + break; + case blocks.BASIC_CODE: + if (block.data.ports.inoutLeft && block.data.ports.inoutLeft.length) { + return true; + } + if (block.data.ports.inoutRight && block.data.ports.inoutRight.length) { + return true; + } + break; + default: + // Generic block + break; + } + } + return false; + } + return false; + } + this.hasInputRule = function (port, apply) { apply = (apply === undefined) ? true : apply; var _default; diff --git a/app/views/advanced.html b/app/views/advanced.html new file mode 100644 index 000000000..130ed4f5d --- /dev/null +++ b/app/views/advanced.html @@ -0,0 +1,52 @@ + + + + + + + + +
  • + + + {{ 'Board rules' | translate }}  + + + +
  • + +
  • + + + {{ 'Allow tri-state connections' | translate }}  + + + +
  • + + +
  • + + {{ 'External plugins' | translate }} +
  • + + +
  • + + {{ 'Python environment' | translate }} +
  • + + +
  • + + {{ 'Remote hostname' | translate }}  + + + +
  • diff --git a/app/views/menu.html b/app/views/menu.html index a2ed55044..3ff694b06 100644 --- a/app/views/menu.html +++ b/app/views/menu.html @@ -378,44 +378,26 @@
  • - - -
  • - - {{ 'Board rules' | translate }}  - + +
  • + +
  • +
  • {{ 'External collections' | translate }}
  • - -
  • - - {{ 'External plugins' | translate }} -
  • - - -
  • - - {{ 'Python environment' | translate }} -
  • - - -
  • - - {{ 'Remote hostname' | translate }} - - -
  • -
  • diff --git a/app/views/uithemes.html b/app/views/uithemes.html index 544bafffe..39e13a240 100644 --- a/app/views/uithemes.html +++ b/app/views/uithemes.html @@ -11,7 +11,7 @@
  • - + {{ 'Dark (default)' | translate }}  @@ -20,7 +20,7 @@
  • - + {{ 'Light' | translate }}