diff --git a/diode/webclient/context_menu.js b/diode/webclient/context_menu.js index 8c558cec89..29693bdf22 100644 --- a/diode/webclient/context_menu.js +++ b/diode/webclient/context_menu.js @@ -42,6 +42,16 @@ class ContextMenu { }); } + addCheckableOption(name, checked, onselect, onhover=null) { + this._options.push({ + name: name, + checkbox: true, + checked: checked, + func: onselect, + onhover: onhover + }); + } + destroy() { if (!this._cmenu_elem) return; @@ -79,10 +89,21 @@ class ContextMenu { elem.addEventListener('click', x.func); elem.classList.add("context_menu_option"); - elem.innerText = x.name; + if (x.checkbox) { + let markelem = document.createElement('span'); + markelem.classList = x.checked ? 'checkmark_checked' : 'checkmark'; + elem.appendChild(markelem); + elem.innerHTML += x.name; + elem.addEventListener('click', elem => { + x.checked = !x.checked; + x.func(elem, x.checked); + }); + } else { + elem.innerText = x.name; + elem.addEventListener('click', x.func); + } cmenu_div.appendChild(elem); } - } else { cmenu_div.innerHTML = this._html_content; diff --git a/diode/webclient/renderer.js b/diode/webclient/renderer.js index 86ee9823e6..64c8137516 100644 --- a/diode/webclient/renderer.js +++ b/diode/webclient/renderer.js @@ -670,6 +670,9 @@ class SDFGRenderer { this.tooltip = null; this.tooltip_container = null; + // View options + this.inclusive_ranges = false; + // Mouse-related fields this.mousepos = null; // Last position of the mouse pointer (in canvas coordinates) this.realmousepos = null; // Last position of the mouse pointer (in pixel coordinates) @@ -693,6 +696,10 @@ class SDFGRenderer { } } + view_settings() { + return {inclusive_ranges: this.inclusive_ranges}; + } + // Initializes the DOM init_elements() { @@ -721,8 +728,9 @@ class SDFGRenderer { cmenu.addOption("Save view as PNG", x => that.save_as_png()); cmenu.addOption("Save view as PDF", x => that.save_as_pdf()); cmenu.addOption("Save all as PDF", x => that.save_as_pdf(true)); + cmenu.addCheckableOption("Inclusive ranges", that.inclusive_ranges, (x, checked) => {that.inclusive_ranges = checked;}); that.menu = cmenu; - cmenu.show(rect.left, rect.bottom); + that.menu.show(rect.left, rect.bottom); }; d.title = 'Menu'; this.toolbar.appendChild(d); diff --git a/diode/webclient/renderer_elements.js b/diode/webclient/renderer_elements.js index 747bc20ba2..14fa24daf9 100644 --- a/diode/webclient/renderer_elements.js +++ b/diode/webclient/renderer_elements.js @@ -199,7 +199,7 @@ class Edge extends SDFGElement { let style = this.strokeStyle(); if (style !== 'black') - renderer.tooltip = (c) => this.tooltip(c); + renderer.tooltip = (c) => this.tooltip(c, renderer); if (this.parent_id == null && style === 'black') { // Interstate edge style = 'blue'; } @@ -224,7 +224,8 @@ class Edge extends SDFGElement { ctx.strokeStyle = "black"; } - tooltip(container) { + tooltip(container, renderer) { + let dsettings = renderer.view_settings(); let attr = this.attributes(); // Memlet if (attr.subset !== undefined) { @@ -233,15 +234,15 @@ class Edge extends SDFGElement { return; } let contents = attr.data; - contents += sdfg_property_to_string(attr.subset); + contents += sdfg_property_to_string(attr.subset, dsettings); if (attr.other_subset) - contents += ' -> ' + sdfg_property_to_string(attr.other_subset); + contents += ' -> ' + sdfg_property_to_string(attr.other_subset, dsettings); if (attr.wcr) - contents += '
CR: ' + sdfg_property_to_string(attr.wcr) +''; + contents += '
CR: ' + sdfg_property_to_string(attr.wcr, dsettings) +''; - let num_accesses = sdfg_property_to_string(attr.num_accesses); + let num_accesses = sdfg_property_to_string(attr.num_accesses, dsettings); if (num_accesses == -1) num_accesses = "Dynamic"; @@ -324,13 +325,13 @@ class AccessNode extends Node { let nodedesc = this.sdfg.attributes._arrays[this.data.node.attributes.data]; // Streams have dashed edges - if (nodedesc.type === "Stream") { + if (nodedesc && nodedesc.type === "Stream") { ctx.setLineDash([5, 3]); } else { ctx.setLineDash([1, 0]); } - if (nodedesc.attributes.transient === false) { + if (nodedesc && nodedesc.attributes.transient === false) { ctx.lineWidth = 3.0; } else { ctx.lineWidth = 1.0; @@ -378,23 +379,54 @@ class ScopeNode extends Node { ctx.fill(); ctx.fillStyle = "black"; - let far_label = this.attributes().label; + let far_label = this.far_label(); + drawAdaptiveText(ctx, renderer, far_label, + this.close_label(renderer), this.x, this.y, + this.width, this.height, + SCOPE_LOD); + } + + far_label() { + let result = this.attributes().label; if (this.scopeend()) { // Get label from scope entry let entry = this.sdfg.nodes[this.parent_id].nodes[this.data.node.scope_entry]; if (entry !== undefined) - far_label = entry.attributes.label; + result = entry.attributes.label; else { - far_label = this.label(); - let ind = far_label.indexOf('['); + result = this.data.node.label; + let ind = result.indexOf('['); if (ind > 0) - far_label = far_label.substring(0, ind); + result = result.substring(0, ind); } } + return result; + } - drawAdaptiveText(ctx, renderer, far_label, - this.label(), this.x, this.y, - this.width, this.height, - SCOPE_LOD); + close_label(renderer) { + if (!renderer.inclusive_ranges) + return this.label(); + + let result = this.far_label(); + let attrs = this.attributes(); + if (this.scopeend()) { + let entry = this.sdfg.nodes[this.parent_id].nodes[this.data.node.scope_entry]; + if (entry !== undefined) + attrs = entry.attributes; + else + return this.label(); + } + result += ' ['; + + if (this instanceof ConsumeEntry || this instanceof ConsumeExit) { + result += attrs.pe_index + '=' + '0..' + (attrs.num_pes - 1).toString(); + } else { + for (let i = 0; i < attrs.params.length; ++i) { + result += attrs.params[i] + '='; + result += sdfg_range_elem_to_string(attrs.range.ranges[i], renderer.view_settings()) + ', '; + } + result = result.substring(0, result.length-2); // Remove trailing comma + } + return result + ']'; } } diff --git a/diode/webclient/sdfg_utils.js b/diode/webclient/sdfg_utils.js index 154659a498..7c2023ff8a 100644 --- a/diode/webclient/sdfg_utils.js +++ b/diode/webclient/sdfg_utils.js @@ -61,14 +61,43 @@ function stringify_sdfg(sdfg) { return JSON.stringify(sdfg, (name, val) => replacer(name, val, sdfg)); } +function sdfg_range_elem_to_string(range, settings=null) { + let preview = ''; + if (range.start == range.end && range.step == 1 && range.tile == 1) + preview += sdfg_property_to_string(range.start, settings); + else { + if (settings && settings.inclusive_ranges) { + preview += sdfg_property_to_string(range.start, settings) + '..' + + sdfg_property_to_string(range.end, settings); + } else { + let endp1 = sdfg_property_to_string(range.end, settings) + ' + 1'; + // Try to simplify using math.js + try { + endp1 = math.simplify(endp1).toString(); + } catch(e) {} + + preview += sdfg_property_to_string(range.start, settings) + ':' + + endp1; + } + if (range.step != 1) { + preview += ':' + sdfg_property_to_string(range.step, settings); + if (range.tile != 1) + preview += ':' + sdfg_property_to_string(range.tile, settings); + } else if (range.tile != 1) { + preview += '::' + sdfg_property_to_string(range.tile, settings); + } + } + return preview; +} + // Includes various properties and returns their string representation -function sdfg_property_to_string(prop) { +function sdfg_property_to_string(prop, settings=null) { if (prop === null) return prop; if (prop.type === "Indices" || prop.type === "subsets.Indices") { let indices = prop.indices; let preview = '['; for (let index of indices) { - preview += sdfg_property_to_string(index) + ', '; + preview += sdfg_property_to_string(index, settings) + ', '; } return preview.slice(0, -2) + ']'; } else if (prop.type === "Range" || prop.type === "subsets.Range") { @@ -77,26 +106,7 @@ function sdfg_property_to_string(prop) { // Generate string from range let preview = '['; for (let range of ranges) { - if (range.start == range.end && range.step == 1 && range.tile == 1) - preview += sdfg_property_to_string(range.start); - else { - let endp1 = sdfg_property_to_string(range.end) + ' + 1'; - // Try to simplify using math.js - try { - endp1 = math.simplify(endp1).toString(); - } catch(e) {} - - preview += sdfg_property_to_string(range.start) + ':' + - endp1; - if (range.step != 1) { - preview += ':' + sdfg_property_to_string(range.step); - if (range.tile != 1) - preview += ':' + sdfg_property_to_string(range.tile); - } else if (range.tile != 1) { - preview += '::' + sdfg_property_to_string(range.tile); - } - } - preview += ', '; + preview += sdfg_range_elem_to_string(range, settings) + ', '; } return preview.slice(0, -2) + ']'; } else if (prop.language !== undefined && prop.string_data !== undefined) { @@ -115,7 +125,7 @@ function sdfg_property_to_string(prop) { for (let subprop of prop) { if (!first) result += ', '; - result += sdfg_property_to_string(subprop); + result += sdfg_property_to_string(subprop, settings); first = false; } return result + ' ]'; diff --git a/diode/webclient/sdfv.css b/diode/webclient/sdfv.css index 301d725803..5f4721d0eb 100644 --- a/diode/webclient/sdfv.css +++ b/diode/webclient/sdfv.css @@ -37,6 +37,10 @@ h1,h2,h3,h4,h5,h6{font-family:"Segoe UI",Arial,sans-serif;font-weight:400;margin position: absolute; background-color: #fdfdfd; box-shadow: 0 4px 5px 3px rgba(0, 0, 0, 0.2); + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; } .context_menu_option { @@ -50,6 +54,14 @@ h1,h2,h3,h4,h5,h6{font-family:"Segoe UI",Arial,sans-serif;font-weight:400;margin background: rgba(0, 0, 0, 0.1); } +.context_menu_option input { + position: absolute; + opacity: 0; + cursor: pointer; + height: 0; + width: 0; +} + .tooltip { background: rgba(0, 0, 0, 0.8); color: white; @@ -57,7 +69,53 @@ h1,h2,h3,h4,h5,h6{font-family:"Segoe UI",Arial,sans-serif;font-weight:400;margin font-family: "Segoe UI", Arial, sans-serif; padding: 2px 5px 2px 5px; border-radius: 4px; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; user-select: none; position: absolute; display: none; } + +/* Checkmarks for context menu items */ +.checkmark { + position: absolute; + right: 5px; + height: 20px; + width: 20px; + background-color: #eee; +} + +.checkmark_checked { + position: absolute; + right: 5px; + height: 20px; + width: 20px; + background-color: #2196F3; +} + +.context_menu_option:hover .checkmark { + background-color: #ccc; +} + +.checkmark:after { + content: ""; + position: absolute; + display: none; +} + +.context_menu_option .checkmark_checked:after { + content: ""; + position: absolute; + display: block; + left: 6px; + top: 2px; + width: 5px; + height: 10px; + border: solid white; + border-width: 0 3px 3px 0; + -webkit-transform: rotate(45deg); + -ms-transform: rotate(45deg); + transform: rotate(45deg); +} + diff --git a/diode/webclient/sdfv.js b/diode/webclient/sdfv.js index 9aabcc7975..25137570f4 100644 --- a/diode/webclient/sdfv.js +++ b/diode/webclient/sdfv.js @@ -160,7 +160,7 @@ function mouse_event(evtype, event, mousepos, elements, renderer, elem) { for (let attr of Object.entries(elem.attributes())) { if (attr[0] === "layout" || attr[0] === "sdfg" || attr[0].startsWith("_meta_")) continue; html += "" + attr[0] + ":  "; - html += sdfg_property_to_string(attr[1], attr[0]) + "

"; + html += sdfg_property_to_string(attr[1], renderer.view_settings()) + "

"; } // If access node, add array information too @@ -170,7 +170,7 @@ function mouse_event(evtype, event, mousepos, elements, renderer, elem) { for (let attr of Object.entries(sdfg_array.attributes)) { if (attr[0] === "layout" || attr[0] === "sdfg" || attr[0].startsWith("_meta_")) continue; html += "" + attr[0] + ":  "; - html += sdfg_property_to_string(attr[1], attr[0]) + "

"; + html += sdfg_property_to_string(attr[1], renderer.view_settings()) + "

"; } }