diff --git a/etc/http/changes.md b/etc/http/changes.md index dbf9318f0b25d..db120b17a1591 100644 --- a/etc/http/changes.md +++ b/etc/http/changes.md @@ -8,6 +8,9 @@ 4. Refactor JSROOT scripts structure - extract histograms and hierarchy painters into separate scripts 5. Use latest three.js r86 with improved Projector and CanvasRenderer Still use own SVGRenderer which supported direct text dump +6. Provide text info when geometry drawing takes too long +7. Preliminary support of TEfficiency +8. Automatic title positioning of vertical axis when fTitleOffset==0 ## Changes in 5.2.0 diff --git a/etc/http/scripts/JSRootCore.js b/etc/http/scripts/JSRootCore.js index 009a5dc61d3a6..ea883a6aeecbe 100644 --- a/etc/http/scripts/JSRootCore.js +++ b/etc/http/scripts/JSRootCore.js @@ -93,7 +93,7 @@ } } (function(JSROOT) { - JSROOT.version = "dev 26/07/2017"; + JSROOT.version = "dev 27/07/2017"; JSROOT.source_dir = ""; JSROOT.source_min = false; diff --git a/etc/http/scripts/JSRootPainter.hierarchy.js b/etc/http/scripts/JSRootPainter.hierarchy.js index ea06b09dd46d7..1c36155dbf888 100644 --- a/etc/http/scripts/JSRootPainter.hierarchy.js +++ b/etc/http/scripts/JSRootPainter.hierarchy.js @@ -2173,6 +2173,8 @@ if (painter.batch_mode) JSROOT.BatchMode = true; else JSROOT.RegisterForResize(painter); + if (window) window.onbeforeunload = painter.WindowBeforeUnloadHanlder.bind(painter); + return; } diff --git a/etc/http/scripts/JSRootPainter.hist.js b/etc/http/scripts/JSRootPainter.hist.js index d8e09fd6f6503..8e5889b4f3a82 100644 --- a/etc/http/scripts/JSRootPainter.hist.js +++ b/etc/http/scripts/JSRootPainter.hist.js @@ -1315,7 +1315,7 @@ shift_y = Math.round(center ? h/2 : (reverse ? h : 0)); - this.DrawText((center ? "middle" : (myxor ? "begin" : "end" ))+ ";middle", + this.DrawText((center ? "middle" : (myxor ? "begin" : "end" )) + ";middle", 0, 0, 0, (rotate<0 ? -90 : -270), axis.fTitle, title_color, 1, title_g); } else { @@ -1328,11 +1328,22 @@ axis.fTitle, title_color, 1, title_g); } - this.FinishTextDrawing(title_g); + var axis_rect = null; + if (vertical && (axis.fTitleOffset == 0) && ('getBoundingClientRect' in axis_g.node())) + axis_rect = axis_g.node().getBoundingClientRect(); + + this.FinishTextDrawing(title_g, function() { + if (axis_rect) { + var title_rect = title_g.node().getBoundingClientRect(); + shift_x = (side>0) ? Math.round(axis_rect.left - title_rect.right - title_fontsize*0.3) : + Math.round(axis_rect.right - title_rect.left + title_fontsize*0.3); + } + + title_g.attr('transform', 'translate(' + shift_x + ',' + shift_y + ')') + .property('shift_x', shift_x) + .property('shift_y', shift_y); + }); - title_g.attr('transform', 'translate(' + shift_x + ',' + shift_y + ')') - .property('shift_x',shift_x) - .property('shift_y',shift_y); this.AddTitleDrag(title_g, vertical, title_offest_k, reverse, vertical ? h : w); } diff --git a/etc/http/scripts/JSRootPainter.js b/etc/http/scripts/JSRootPainter.js index faeef5266ef5f..4e4d4755d080d 100644 --- a/etc/http/scripts/JSRootPainter.js +++ b/etc/http/scripts/JSRootPainter.js @@ -97,17 +97,18 @@ } }; + // ========================================================================================== - JSROOT.DrawOptions = function(opt) { + var DrawOptions = function(opt) { this.opt = opt && (typeof opt=="string") ? opt.toUpperCase().trim() : ""; this.part = ""; } - JSROOT.DrawOptions.prototype.empty = function() { + DrawOptions.prototype.empty = function() { return this.opt.length === 0; } - JSROOT.DrawOptions.prototype.check = function(name,postpart) { + DrawOptions.prototype.check = function(name,postpart) { var pos = this.opt.indexOf(name); if (pos < 0) return false; this.opt = this.opt.substr(0, pos) + this.opt.substr(pos + name.length); @@ -123,33 +124,364 @@ return true; } - JSROOT.DrawOptions.prototype.partAsInt = function(offset, dflt) { + DrawOptions.prototype.partAsInt = function(offset, dflt) { var val = this.part.replace( /^\D+/g, ''); val = val ? parseInt(val,10) : Number.NaN; return isNaN(val) ? (dflt || 0) : val + (offset || 0); } - /** - * @class JSROOT.Painter Holder of different functions and classes for drawing - */ - JSROOT.Painter = {}; + /** @class JSROOT.Painter Holder of different functions and classes for drawing */ + var Painter = { + Coord: { + kCARTESIAN : 1, + kPOLAR : 2, + kCYLINDRICAL : 3, + kSPHERICAL : 4, + kRAPIDITY : 5 + }, + root_colors: [], + root_line_styles: ["", "", "3,3", "1,2", + "3,4,1,4", "5,3,1,3", "5,3,1,3,1,3,1,3", "5,5", + "5,3,1,3,1,3", "20,5", "20,10,1,10", "1,3"], + root_markers: [ 0, 100, 8, 7, 0, // 0..4 + 9, 100, 100, 100, 100, // 5..9 + 100, 100, 100, 100, 100, // 10..14 + 100, 100, 100, 100, 100, // 15..19 + 100, 103, 105, 104, 0, // 20..24 + 3, 4, 2, 1, 106, // 25..29 + 6, 7, 5, 102, 101], // 30..34 + root_fonts: ['Arial', 'Times New Roman', + 'bTimes New Roman', 'biTimes New Roman', 'Arial', + 'oArial', 'bArial', 'boArial', 'Courier New', + 'oCourier New', 'bCourier New', 'boCourier New', + 'Symbol', 'Times New Roman', 'Wingdings', 'Symbol', 'Verdana'], + superscript_symbols_map: { + '1': '\xB9', + '2': '\xB2', + '3': '\xB3', + 'o': '\xBA', + '0': '\u2070', + 'i': '\u2071', + '4': '\u2074', + '5': '\u2075', + '6': '\u2076', + '7': '\u2077', + '8': '\u2078', + '9': '\u2079', + '+': '\u207A', + '-': '\u207B', + '=': '\u207C', + '(': '\u207D', + ')': '\u207E', + 'n': '\u207F', + 'a': '\xAA', + 'v': '\u2C7D', + 'h': '\u02B0', + 'j': '\u02B2', + 'r': '\u02B3', + 'w': '\u02B7', + 'y': '\u02B8', + 'l': '\u02E1', + 's': '\u02E2', + 'x': '\u02E3' + }, + subscript_symbols_map: { + '0': '\u2080', + '1': '\u2081', + '2': '\u2082', + '3': '\u2083', + '4': '\u2084', + '5': '\u2085', + '6': '\u2086', + '7': '\u2087', + '8': '\u2088', + '9': '\u2089', + '+': '\u208A', + '-': '\u208B', + '=': '\u208C', + '(': '\u208D', + ')': '\u208E', + 'a': '\u2090', + 'e': '\u2091', + 'o': '\u2092', + 'x': '\u2093', + 'ə': '\u2094', + 'h': '\u2095', + 'k': '\u2096', + 'l': '\u2097', + 'm': '\u2098', + 'n': '\u2099', + 'p': '\u209A', + 's': '\u209B', + 't': '\u209C', + 'j': '\u2C7C' + }, + symbols_map: { + // greek letters + '#alpha': '\u03B1', + '#beta': '\u03B2', + '#chi': '\u03C7', + '#delta': '\u03B4', + '#varepsilon': '\u03B5', + '#phi': '\u03C6', + '#gamma': '\u03B3', + '#eta': '\u03B7', + '#iota': '\u03B9', + '#varphi': '\u03C6', + '#kappa': '\u03BA', + '#lambda': '\u03BB', + '#mu': '\u03BC', + '#nu': '\u03BD', + '#omicron': '\u03BF', + '#pi': '\u03C0', + '#theta': '\u03B8', + '#rho': '\u03C1', + '#sigma': '\u03C3', + '#tau': '\u03C4', + '#upsilon': '\u03C5', + '#varomega': '\u03D6', + '#omega': '\u03C9', + '#xi': '\u03BE', + '#psi': '\u03C8', + '#zeta': '\u03B6', + '#Alpha': '\u0391', + '#Beta': '\u0392', + '#Chi': '\u03A7', + '#Delta': '\u0394', + '#Epsilon': '\u0395', + '#Phi': '\u03A6', + '#Gamma': '\u0393', + '#Eta': '\u0397', + '#Iota': '\u0399', + '#vartheta': '\u03D1', + '#Kappa': '\u039A', + '#Lambda': '\u039B', + '#Mu': '\u039C', + '#Nu': '\u039D', + '#Omicron': '\u039F', + '#Pi': '\u03A0', + '#Theta': '\u0398', + '#Rho': '\u03A1', + '#Sigma': '\u03A3', + '#Tau': '\u03A4', + '#Upsilon': '\u03A5', + '#varsigma': '\u03C2', + '#Omega': '\u03A9', + '#Xi': '\u039E', + '#Psi': '\u03A8', + '#Zeta': '\u0396', + '#varUpsilon': '\u03D2', + '#epsilon': '\u03B5', + // math symbols + + '#sqrt': '\u221A', + + // from TLatex tables #2 & #3 + '#leq': '\u2264', + '#/': '\u2044', + '#infty': '\u221E', + '#voidb': '\u0192', + '#club': '\u2663', + '#diamond': '\u2666', + '#heart': '\u2665', + '#spade': '\u2660', + '#leftrightarrow': '\u2194', + '#leftarrow': '\u2190', + '#uparrow': '\u2191', + '#rightarrow': '\u2192', + '#downarrow': '\u2193', + '#circ': '\u02C6', // ^ + '#pm': '\xB1', + '#doublequote': '\u2033', + '#geq': '\u2265', + '#times': '\xD7', + '#propto': '\u221D', + '#partial': '\u2202', + '#bullet': '\u2022', + '#divide': '\xF7', + '#neq': '\u2260', + '#equiv': '\u2261', + '#approx': '\u2248', // should be \u2245 ? + '#3dots': '\u2026', + '#cbar': '\u007C', + '#topbar': '\xAF', + '#downleftarrow': '\u21B5', + '#aleph': '\u2135', + '#Jgothic': '\u2111', + '#Rgothic': '\u211C', + '#voidn': '\u2118', + '#otimes': '\u2297', + '#oplus': '\u2295', + '#oslash': '\u2205', + '#cap': '\u2229', + '#cup': '\u222A', + '#supseteq': '\u2287', + '#supset': '\u2283', + '#notsubset': '\u2284', + '#subseteq': '\u2286', + '#subset': '\u2282', + '#int': '\u222B', + '#in': '\u2208', + '#notin': '\u2209', + '#angle': '\u2220', + '#nabla': '\u2207', + '#oright': '\xAE', + '#ocopyright': '\xA9', + '#trademark': '\u2122', + '#prod': '\u220F', + '#surd': '\u221A', + '#upoint': '\u22C5', + '#corner': '\xAC', + '#wedge': '\u2227', + '#vee': '\u2228', + '#Leftrightarrow': '\u21D4', + '#Leftarrow': '\u21D0', + '#Uparrow': '\u21D1', + '#Rightarrow': '\u21D2', + '#Downarrow': '\u21D3', + '#LT': '\x3C', + '#void1': '\xAE', + '#copyright': '\xA9', + '#void3': '\u2122', + '#sum': '\u2211', + '#arctop': '', + '#lbar': '', + '#arcbottom': '', + '#void8': '', + '#bottombar': '\u230A', + '#arcbar': '', + '#ltbar': '', + '#AA': '\u212B', + '#aa': '\u00E5', + '#void06': '', + '#GT': '\x3E', + '#forall': '\u2200', + '#exists': '\u2203', + '#bar': '', + '#vec': '', + '#dot': '\u22C5', + '#hat': '\xB7', + '#ddot': '', + '#acute': '\acute', + '#grave': '', + '#check': '\u2713', + '#tilde': '\u02DC', + '#slash': '\u2044', + '#hbar': '\u0127', + '#box': '', + '#Box': '', + '#parallel': '', + '#perp': '\u22A5', + '#odot': '', + '#left': '', + '#right': '' + }, + math_symbols_map: { + '#LT':"\\langle", + '#GT':"\\rangle", + '#club':"\\clubsuit", + '#spade':"\\spadesuit", + '#heart':"\\heartsuit", + '#diamond':"\\diamondsuit", + '#voidn':"\\wp", + '#voidb':"f", + '#copyright':"(c)", + '#ocopyright':"(c)", + '#trademark':"TM", + '#void3':"TM", + '#oright':"R", + '#void1':"R", + '#3dots':"\\ldots", + '#lbar':"\\mid", + '#void8':"\\mid", + '#divide':"\\div", + '#Jgothic':"\\Im", + '#Rgothic':"\\Re", + '#doublequote':"\"", + '#plus':"+", + '#diamond':"\\diamondsuit", + '#voidn':"\\wp", + '#voidb':"f", + '#copyright':"(c)", + '#ocopyright':"(c)", + '#trademark':"TM", + '#void3':"TM", + '#oright':"R", + '#void1':"R", + '#3dots':"\\ldots", + '#lbar':"\\mid", + '#void8':"\\mid", + '#divide':"\\div", + '#Jgothic':"\\Im", + '#Rgothic':"\\Re", + '#doublequote':"\"", + '#plus':"+", + '#minus':"-", + '#\/':"/", + '#upoint':".", + '#aa':"\\mathring{a}", + '#AA':"\\mathring{A}", + '#omicron':"o", + '#Alpha':"A", + '#Beta':"B", + '#Epsilon':"E", + '#Zeta':"Z", + '#Eta':"H", + '#Iota':"I", + '#Kappa':"K", + '#Mu':"M", + '#Nu':"N", + '#Omicron':"O", + '#Rho':"P", + '#Tau':"T", + '#Chi':"X", + '#varomega':"\\varpi", + '#corner':"?", + '#ltbar':"?", + '#bottombar':"?", + '#notsubset':"?", + '#arcbottom':"?", + '#cbar':"?", + '#arctop':"?", + '#topbar':"?", + '#arcbar':"?", + '#downleftarrow':"?", + '#splitline':"\\genfrac{}{}{0pt}{}", + '#it':"\\textit", + '#bf':"\\textbf", + '#frac':"\\frac", + '#left{':"\\lbrace", + '#right}':"\\rbrace", + '#left\\[':"\\lbrack", + '#right\\]':"\\rbrack", + '#\\[\\]{':"\\lbrack", + ' } ':"\\rbrack", + '#\\[':"\\lbrack", + '#\\]':"\\rbrack", + '#{':"\\lbrace", + '#}':"\\rbrace", + ' ':"\\;" + } + }; + + JSROOT.Painter = Painter; // export here to avoid ambiguity - JSROOT.Painter.createMenu = function(painter, maincallback) { + Painter.createMenu = function(painter, maincallback) { // dummy functions, forward call to the jquery function document.body.style.cursor = 'wait'; JSROOT.AssertPrerequisites('hierarchy;jq2d;openui5;', function() { document.body.style.cursor = 'auto'; - JSROOT.Painter.createMenu(painter, maincallback); + Painter.createMenu(painter, maincallback); }); } - JSROOT.Painter.closeMenu = function(menuname) { + Painter.closeMenu = function(menuname) { var x = document.getElementById(menuname || 'root_ctx_menu'); if (x) { x.parentNode.removeChild(x); return true; } return false; } - JSROOT.Painter.readStyleFromURL = function(url) { + Painter.readStyleFromURL = function(url) { var optimize = JSROOT.GetUrlOption("optimize", url); if (optimize=="") JSROOT.gStyle.OptimizeDraw = 2; else if (optimize!==null) { @@ -207,18 +539,8 @@ if (geocomp!==null) JSROOT.gStyle.GeoCompressComp = (geocomp!=='0') && (geocomp!=='false'); } - JSROOT.Painter.Coord = { - kCARTESIAN : 1, - kPOLAR : 2, - kCYLINDRICAL : 3, - kSPHERICAL : 4, - kRAPIDITY : 5 - } - /** Function that generates all root colors */ - JSROOT.Painter.root_colors = []; - - JSROOT.Painter.createRootColors = function() { + Painter.createRootColors = function() { var colorMap = ['white','black','red','green','blue','yellow','magenta','cyan','rgb(89,212,84)','rgb(89,84,217)', 'white']; colorMap[110] = 'white'; @@ -242,10 +564,10 @@ } } - JSROOT.Painter.root_colors = colorMap; + Painter.root_colors = colorMap; } - JSROOT.Painter.MakeColorRGB = function(col) { + Painter.MakeColorRGB = function(col) { if ((col==null) || (col._typename != 'TColor')) return null; var rgb = Math.round(col.fRed*255) + "," + Math.round(col.fGreen*255) + "," + Math.round(col.fBlue*255); if ((col.fAlpha === undefined) || (col.fAlpha == 1.)) @@ -266,7 +588,7 @@ return rgb; } - JSROOT.Painter.adoptRootColors = function(objarr) { + Painter.adoptRootColors = function(objarr) { if (!objarr || !objarr.arr) return; for (var n = 0; n < objarr.arr.length; ++n) { @@ -276,32 +598,18 @@ var num = col.fNumber; if ((num<0) || (num>4096)) continue; - var rgb = JSROOT.Painter.MakeColorRGB(col); + var rgb = Painter.MakeColorRGB(col); if (rgb == null) continue; - if (JSROOT.Painter.root_colors[num] != rgb) - JSROOT.Painter.root_colors[num] = rgb; + if (Painter.root_colors[num] != rgb) + Painter.root_colors[num] = rgb; } } - JSROOT.Painter.root_line_styles = ["", "", "3,3", "1,2", - "3,4,1,4", "5,3,1,3", "5,3,1,3,1,3,1,3", "5,5", - "5,3,1,3,1,3", "20,5", "20,10,1,10", "1,3"]; - - // Initialize ROOT markers - JSROOT.Painter.root_markers = - [ 0, 100, 8, 7, 0, // 0..4 - 9, 100, 100, 100, 100, // 5..9 - 100, 100, 100, 100, 100, // 10..14 - 100, 100, 100, 100, 100, // 15..19 - 100, 103, 105, 104, 0, // 20..24 - 3, 4, 2, 1, 106, // 25..29 - 6, 7, 5, 102, 101]; // 30..34 - /** Function returns the ready to use marker for drawing */ - JSROOT.Painter.createAttMarker = function(attmarker, style) { + Painter.createAttMarker = function(attmarker, style) { - var marker_color = JSROOT.Painter.root_colors[attmarker.fMarkerColor]; + var marker_color = Painter.root_colors[attmarker.fMarkerColor]; if (!style || (style<0)) style = attmarker.fMarkerStyle; @@ -338,7 +646,7 @@ return true; } - var marker_kind = ((this.style>0) && (this.style 0) && (this.style < Painter.root_markers.length)) ? Painter.root_markers[this.style] : 100; var shape = marker_kind % 100; this.fill = (marker_kind>=100); @@ -430,20 +738,19 @@ return res; } - JSROOT.Painter.createAttLine = function(attline, borderw, can_excl) { - + Painter.createAttLine = function(attline, borderw, can_excl) { var color = 'black', _width = 0, style = 0; if (typeof attline == 'string') { color = attline; if (color!=='none') _width = 1; } else if (typeof attline == 'object') { - if ('fLineColor' in attline) color = JSROOT.Painter.root_colors[attline.fLineColor]; + if ('fLineColor' in attline) color = Painter.root_colors[attline.fLineColor]; if ('fLineWidth' in attline) _width = attline.fLineWidth; if ('fLineStyle' in attline) style = attline.fLineStyle; } else - if ((attline!==undefined) && !isNaN(attline)) { - color = JSROOT.Painter.root_colors[attline]; + if ((attline !== undefined) && !isNaN(attline)) { + color = Painter.root_colors[attline]; } if (borderw!==undefined) _width = borderw; @@ -452,7 +759,7 @@ used: true, // can mark object if it used or not, color: color, width: _width, - dash: JSROOT.Painter.root_line_styles[style] + dash: Painter.root_line_styles[style] }; if (_width==0) line.color = 'none'; @@ -503,25 +810,19 @@ return line; } - JSROOT.Painter.clearCuts = function(chopt) { + Painter.clearCuts = function(chopt) { /* decode string "chopt" and remove graphical cuts */ - var left = chopt.indexOf('['); - var right = chopt.indexOf(']'); + var left = chopt.indexOf('['), + right = chopt.indexOf(']'); if ((left>=0) && (right>=0) && (left 0) { if (fontName[0]==='b') res.weight = "bold"; else @@ -566,7 +867,7 @@ return res; } - JSROOT.Painter.chooseTimeFormat = function(awidth, ticks) { + Painter.chooseTimeFormat = function(awidth, ticks) { if (awidth < .5) return ticks ? "%S.%L" : "%M:%S.%L"; if (awidth < 30) return ticks ? "%Mm%S" : "%H:%M:%S"; awidth /= 60; if (awidth < 30) return ticks ? "%Hh%M" : "%d/%m %H:%M"; @@ -577,13 +878,13 @@ return "%Y"; } - JSROOT.Painter.getTimeFormat = function(axis) { + Painter.getTimeFormat = function(axis) { var idF = axis.fTimeFormat.indexOf('%F'); if (idF >= 0) return axis.fTimeFormat.substr(0, idF); return axis.fTimeFormat; } - JSROOT.Painter.getTimeOffset = function(axis) { + Painter.getTimeOffset = function(axis) { var idF = axis.fTimeFormat.indexOf('%F'); if (idF < 0) return JSROOT.gStyle.fTimeOffset*1000; var sof = axis.fTimeFormat.substr(idF + 2); @@ -614,273 +915,49 @@ return dt.getTime(); } - JSROOT.Painter.superscript_symbols_map = { - '1': '\xB9', - '2': '\xB2', - '3': '\xB3', - 'o': '\xBA', - '0': '\u2070', - 'i': '\u2071', - '4': '\u2074', - '5': '\u2075', - '6': '\u2076', - '7': '\u2077', - '8': '\u2078', - '9': '\u2079', - '+': '\u207A', - '-': '\u207B', - '=': '\u207C', - '(': '\u207D', - ')': '\u207E', - 'n': '\u207F', - 'a': '\xAA', - 'v': '\u2C7D', - 'h': '\u02B0', - 'j': '\u02B2', - 'r': '\u02B3', - 'w': '\u02B7', - 'y': '\u02B8', - 'l': '\u02E1', - 's': '\u02E2', - 'x': '\u02E3' - } - - JSROOT.Painter.subscript_symbols_map = { - '0': '\u2080', - '1': '\u2081', - '2': '\u2082', - '3': '\u2083', - '4': '\u2084', - '5': '\u2085', - '6': '\u2086', - '7': '\u2087', - '8': '\u2088', - '9': '\u2089', - '+': '\u208A', - '-': '\u208B', - '=': '\u208C', - '(': '\u208D', - ')': '\u208E', - 'a': '\u2090', - 'e': '\u2091', - 'o': '\u2092', - 'x': '\u2093', - 'ə': '\u2094', - 'h': '\u2095', - 'k': '\u2096', - 'l': '\u2097', - 'm': '\u2098', - 'n': '\u2099', - 'p': '\u209A', - 's': '\u209B', - 't': '\u209C', - 'j': '\u2C7C' - } - - JSROOT.Painter.translateSuperscript = function(_exp) { + Painter.translateSuperscript = function(_exp) { var res = ""; for (var n=0;n<_exp.length;++n) res += (this.superscript_symbols_map[_exp[n]] || _exp[n]); return res; } - JSROOT.Painter.translateSubscript = function(_sub) { + Painter.translateSubscript = function(_sub) { var res = ""; for (var n=0;n<_sub.length;++n) res += (this.subscript_symbols_map[_sub[n]] || _sub[n]); return res; } - JSROOT.Painter.formatExp = function(label) { + Painter.formatExp = function(label) { var str = label.toLowerCase().replace('e+', 'x10@').replace('e-', 'x10@-'), pos = str.indexOf('@'), - exp = JSROOT.Painter.translateSuperscript(str.substr(pos+1)), + exp = Painter.translateSuperscript(str.substr(pos+1)), str = str.substr(0, pos); return ((str === "1x10") ? "10" : str) + exp; } - JSROOT.Painter.symbols_map = { - // greek letters - '#alpha': '\u03B1', - '#beta': '\u03B2', - '#chi': '\u03C7', - '#delta': '\u03B4', - '#varepsilon': '\u03B5', - '#phi': '\u03C6', - '#gamma': '\u03B3', - '#eta': '\u03B7', - '#iota': '\u03B9', - '#varphi': '\u03C6', - '#kappa': '\u03BA', - '#lambda': '\u03BB', - '#mu': '\u03BC', - '#nu': '\u03BD', - '#omicron': '\u03BF', - '#pi': '\u03C0', - '#theta': '\u03B8', - '#rho': '\u03C1', - '#sigma': '\u03C3', - '#tau': '\u03C4', - '#upsilon': '\u03C5', - '#varomega': '\u03D6', - '#omega': '\u03C9', - '#xi': '\u03BE', - '#psi': '\u03C8', - '#zeta': '\u03B6', - '#Alpha': '\u0391', - '#Beta': '\u0392', - '#Chi': '\u03A7', - '#Delta': '\u0394', - '#Epsilon': '\u0395', - '#Phi': '\u03A6', - '#Gamma': '\u0393', - '#Eta': '\u0397', - '#Iota': '\u0399', - '#vartheta': '\u03D1', - '#Kappa': '\u039A', - '#Lambda': '\u039B', - '#Mu': '\u039C', - '#Nu': '\u039D', - '#Omicron': '\u039F', - '#Pi': '\u03A0', - '#Theta': '\u0398', - '#Rho': '\u03A1', - '#Sigma': '\u03A3', - '#Tau': '\u03A4', - '#Upsilon': '\u03A5', - '#varsigma': '\u03C2', - '#Omega': '\u03A9', - '#Xi': '\u039E', - '#Psi': '\u03A8', - '#Zeta': '\u0396', - '#varUpsilon': '\u03D2', - '#epsilon': '\u03B5', - // math symbols - - '#sqrt': '\u221A', - - // from TLatex tables #2 & #3 - '#leq': '\u2264', - '#/': '\u2044', - '#infty': '\u221E', - '#voidb': '\u0192', - '#club': '\u2663', - '#diamond': '\u2666', - '#heart': '\u2665', - '#spade': '\u2660', - '#leftrightarrow': '\u2194', - '#leftarrow': '\u2190', - '#uparrow': '\u2191', - '#rightarrow': '\u2192', - '#downarrow': '\u2193', - '#circ': '\u02C6', // ^ - '#pm': '\xB1', - '#doublequote': '\u2033', - '#geq': '\u2265', - '#times': '\xD7', - '#propto': '\u221D', - '#partial': '\u2202', - '#bullet': '\u2022', - '#divide': '\xF7', - '#neq': '\u2260', - '#equiv': '\u2261', - '#approx': '\u2248', // should be \u2245 ? - '#3dots': '\u2026', - '#cbar': '\u007C', - '#topbar': '\xAF', - '#downleftarrow': '\u21B5', - '#aleph': '\u2135', - '#Jgothic': '\u2111', - '#Rgothic': '\u211C', - '#voidn': '\u2118', - '#otimes': '\u2297', - '#oplus': '\u2295', - '#oslash': '\u2205', - '#cap': '\u2229', - '#cup': '\u222A', - '#supseteq': '\u2287', - '#supset': '\u2283', - '#notsubset': '\u2284', - '#subseteq': '\u2286', - '#subset': '\u2282', - '#int': '\u222B', - '#in': '\u2208', - '#notin': '\u2209', - '#angle': '\u2220', - '#nabla': '\u2207', - '#oright': '\xAE', - '#ocopyright': '\xA9', - '#trademark': '\u2122', - '#prod': '\u220F', - '#surd': '\u221A', - '#upoint': '\u22C5', - '#corner': '\xAC', - '#wedge': '\u2227', - '#vee': '\u2228', - '#Leftrightarrow': '\u21D4', - '#Leftarrow': '\u21D0', - '#Uparrow': '\u21D1', - '#Rightarrow': '\u21D2', - '#Downarrow': '\u21D3', - '#LT': '\x3C', - '#void1': '\xAE', - '#copyright': '\xA9', - '#void3': '\u2122', - '#sum': '\u2211', - '#arctop': '', - '#lbar': '', - '#arcbottom': '', - '#void8': '', - '#bottombar': '\u230A', - '#arcbar': '', - '#ltbar': '', - '#AA': '\u212B', - '#aa': '\u00E5', - '#void06': '', - '#GT': '\x3E', - '#forall': '\u2200', - '#exists': '\u2203', - '#bar': '', - '#vec': '', - '#dot': '\u22C5', - '#hat': '\xB7', - '#ddot': '', - '#acute': '\acute', - '#grave': '', - '#check': '\u2713', - '#tilde': '\u02DC', - '#slash': '\u2044', - '#hbar': '\u0127', - '#box': '', - '#Box': '', - '#parallel': '', - '#perp': '\u22A5', - '#odot': '', - '#left': '', - '#right': '' - }; - - JSROOT.Painter.translateLaTeX = function(_string) { + Painter.translateLaTeX = function(_string) { var str = _string, i; var lstr = str.match(/\^{(.*?)}/gi); if (lstr) for (i = 0; i < lstr.length; ++i) - str = str.replace(lstr[i], JSROOT.Painter.translateSuperscript(lstr[i].substr(2, lstr[i].length-3))); + str = str.replace(lstr[i], Painter.translateSuperscript(lstr[i].substr(2, lstr[i].length-3))); lstr = str.match(/\_{(.*?)}/gi); if (lstr) for (i = 0; i < lstr.length; ++i) - str = str.replace(lstr[i], JSROOT.Painter.translateSubscript(lstr[i].substr(2, lstr[i].length-3))); + str = str.replace(lstr[i], Painter.translateSubscript(lstr[i].substr(2, lstr[i].length-3))); lstr = str.match(/\#sqrt{(.*?)}/gi); if (lstr) for (i = 0; i < lstr.length; ++i) str = str.replace(lstr[i], lstr[i].replace(' ', '').replace('#sqrt{', '#sqrt').replace('}', '')); - for (i in JSROOT.Painter.symbols_map) - str = str.replace(new RegExp(i,'g'), JSROOT.Painter.symbols_map[i]); + for (i = 0; i < Painter.symbols_map.length; ++i) + str = str.replace(new RegExp(i,'g'), Painter.symbols_map[i]); // simple workaround for simple #splitline{first_line}{second_line} if ((str.indexOf("#splitline{")==0) && (str[str.length-1]=="}")) { @@ -892,105 +969,18 @@ return str.replace(/\^2/gi,'\xB2').replace(/\^3/gi,'\xB3'); } - JSROOT.Painter.isAnyLatex = function(str) { + Painter.isAnyLatex = function(str) { return (str.indexOf("#")>=0) || (str.indexOf("\\")>=0) || (str.indexOf("{")>=0); } - JSROOT.Painter.math_symbols_map = { - '#LT':"\\langle", - '#GT':"\\rangle", - '#club':"\\clubsuit", - '#spade':"\\spadesuit", - '#heart':"\\heartsuit", - '#diamond':"\\diamondsuit", - '#voidn':"\\wp", - '#voidb':"f", - '#copyright':"(c)", - '#ocopyright':"(c)", - '#trademark':"TM", - '#void3':"TM", - '#oright':"R", - '#void1':"R", - '#3dots':"\\ldots", - '#lbar':"\\mid", - '#void8':"\\mid", - '#divide':"\\div", - '#Jgothic':"\\Im", - '#Rgothic':"\\Re", - '#doublequote':"\"", - '#plus':"+", - '#diamond':"\\diamondsuit", - '#voidn':"\\wp", - '#voidb':"f", - '#copyright':"(c)", - '#ocopyright':"(c)", - '#trademark':"TM", - '#void3':"TM", - '#oright':"R", - '#void1':"R", - '#3dots':"\\ldots", - '#lbar':"\\mid", - '#void8':"\\mid", - '#divide':"\\div", - '#Jgothic':"\\Im", - '#Rgothic':"\\Re", - '#doublequote':"\"", - '#plus':"+", - '#minus':"-", - '#\/':"/", - '#upoint':".", - '#aa':"\\mathring{a}", - '#AA':"\\mathring{A}", - '#omicron':"o", - '#Alpha':"A", - '#Beta':"B", - '#Epsilon':"E", - '#Zeta':"Z", - '#Eta':"H", - '#Iota':"I", - '#Kappa':"K", - '#Mu':"M", - '#Nu':"N", - '#Omicron':"O", - '#Rho':"P", - '#Tau':"T", - '#Chi':"X", - '#varomega':"\\varpi", - '#corner':"?", - '#ltbar':"?", - '#bottombar':"?", - '#notsubset':"?", - '#arcbottom':"?", - '#cbar':"?", - '#arctop':"?", - '#topbar':"?", - '#arcbar':"?", - '#downleftarrow':"?", - '#splitline':"\\genfrac{}{}{0pt}{}", - '#it':"\\textit", - '#bf':"\\textbf", - '#frac':"\\frac", - '#left{':"\\lbrace", - '#right}':"\\rbrace", - '#left\\[':"\\lbrack", - '#right\\]':"\\rbrack", - '#\\[\\]{':"\\lbrack", - ' } ':"\\rbrack", - '#\\[':"\\lbrack", - '#\\]':"\\rbrack", - '#{':"\\lbrace", - '#}':"\\rbrace", - ' ':"\\;" - }; - - JSROOT.Painter.translateMath = function(str, kind, color) { + Painter.translateMath = function(str, kind, color) { // function translate ROOT TLatex into MathJax format if (kind!=2) { - for (var x in JSROOT.Painter.math_symbols_map) - str = str.replace(new RegExp(x,'g'), JSROOT.Painter.math_symbols_map[x]); + for (var x in Painter.math_symbols_map) + str = str.replace(new RegExp(x,'g'), Painter.math_symbols_map[x]); - for (var x in JSROOT.Painter.symbols_map) + for (var x in Painter.symbols_map) str = str.replace(new RegExp(x,'g'), "\\" + x.substr(1)); } else { str = str.replace(/\\\^/g, "\\hat"); @@ -1006,7 +996,7 @@ return "\\(\\color{" + color + '}' + str + "\\)"; } - JSROOT.Painter.BuildSvgPath = function(kind, bins, height, ndig) { + Painter.BuildSvgPath = function(kind, bins, height, ndig) { // function used to provide svg:path for the smoothed curves // reuse code from d3.js. Used in TH1, TF1 and TGraph painters // kind should contain "bezier" or "line". @@ -2439,7 +2429,7 @@ } - JSROOT.LongPollSocket = function(addr) { + var LongPollSocket = function(addr) { this.path = addr; this.connid = null; @@ -2517,7 +2507,7 @@ return this; } - JSROOT.Cef3QuerySocket = function(addr) { + var Cef3QuerySocket = function(addr) { // make very similar to longpoll // create persistent CEF requests which could be use from client application at eny time @@ -2526,6 +2516,7 @@ this.path = addr; this.connid = null; + this.nextrequest = function(data, kind) { var req = { request: "", persistent: false }; if (kind === "connect") { @@ -2552,7 +2543,9 @@ req.onSuccess = this.onSuccess.bind(this); req.onFailure = this.onFailure.bind(this); - window.cefQuery(req); // equvalent to req.send + this.cefid = window.cefQuery(req); // equvalent to req.send + + return this; } this.onFailure = function(error_code, error_message) { @@ -2579,13 +2572,26 @@ this.send = function(str) { this.nextrequest(str); } - this.close = function() { this.nextrequest("", "close"); } + this.close = function() { + this.nextrequest("", "close"); + if (this.cefid) window.cefQueryCancel(this.cefid); + delete this.cefid; + } this.nextrequest("","connect"); return this; } + TObjectPainter.prototype.CloseWebsocket = function() { + if (this._websocket && this._websocket_opened) { + this._websocket_opened = false; + this._websocket.close(); + delete this._websocket; + if (typeof this.OnWebsocketClosed == 'function') + this.OnWebsocketClosed(); + } + } TObjectPainter.prototype.OpenWebsocket = function(socket_kind) { // create websocket for current object (canvas) @@ -2617,7 +2623,7 @@ if (path.indexOf("rootscheme://rootserver")==0) path = path.substr(23); console.log('configure cefquery ' + path); - conn = new JSROOT.Cef3QuerySocket(path); + conn = new Cef3QuerySocket(path); } else if ((pthis._websocket_kind !== 'longpoll') && first_time) { path = path.replace("http://", "ws://"); @@ -2632,7 +2638,7 @@ if (pos < 0) return; path = path.substr(0,pos) + "root.longpoll"; console.log('configure longpoll ' + path); - conn = new JSROOT.LongPollSocket(path); + conn = new LongPollSocket(path); } if (!conn) return; @@ -2642,95 +2648,16 @@ conn.onopen = function() { console.log('websocket initialized'); pthis._websocket_opened = true; - conn.send('READY'); // indicate that we are ready to recieve JSON code (or any other big piece) + if (typeof pthis.OnWebsocketOpened == 'function') + pthis.OnWebsocketOpened(conn); } conn.onmessage = function (e) { - var d = e.data; - if (typeof d != 'string') return console.log("msg",d); - - if (d.substr(0,4)=='SNAP') { - var snap = JSROOT.parse(d.substr(4)); - - if (typeof pthis.RedrawPadSnap === 'function') { - pthis.RedrawPadSnap(snap, function() { - var reply = pthis.GetAllRanges(); - if (reply) console.log("ranges: " + reply); - conn.send(reply ? "RREADY:" + reply : "RREADY:" ); // send ready message back when drawing completed - }); - } else { - conn.send('READY'); // send ready message back - } - - } else - if (d.substr(0,4)=='JSON') { - var obj = JSROOT.parse(d.substr(4)); - // console.log("get JSON ", d.length-4, obj._typename); - var tm1 = new Date().getTime(); - pthis.RedrawObject(obj); - var tm2 = new Date().getTime(); - sum1+=1; - sum2+=(tm2-tm1); - if (sum1>10) { console.log('Redraw ', Math.round(sum2/sum1)); sum1=sum2=0; } - - conn.send('READY'); // send ready message back - // if (++cnt > 10) conn.close(); - - } else - if (d.substr(0,4)=='MENU') { - var lst = JSROOT.parse(d.substr(4)); - console.log("get MENUS ", typeof lst, 'nitems', lst.length, d.length-4); - conn.send('READY'); // send ready message back - if (typeof pthis._getmenu_callback == 'function') - pthis._getmenu_callback(lst); - } else - if (d.substr(0,4)=='SVG:') { - var fname = d.substr(4); - console.log('get request for SVG image ' + fname); - - var res = "anything_else"; - - if (pthis.CreateSvg) res = pthis.CreateSvg(); - - console.log('SVG size = ' + res.length); - - conn.send("DONESVG:" + fname + ":" + res); - } else - if (d.substr(0,4)=='PNG:') { - var fname = d.substr(4); - console.log('get request for PNG image ' + fname); - - pthis.ProduceImage(true, 'any.png', function(can) { - var res = can.toDataURL('image/png'); - console.log('PNG size = ' + res.length); - var separ = res.indexOf("base64,"); - if (separ>0) - conn.send("DONEPNG:" + fname + ":" + res.substr(separ+7)); - else - conn.send("DONEPNG:" + fname); - }); - - } else - - if (d.substr(0,7)=='GETIMG:') { - // obsolete, can be removed - - console.log('d',d); - - d = d.substr(7); - var p = d.indexOf(":"), - id = d.substr(0,p), - fname = d.substr(p+1); - conn.send('READY'); // send ready message back + var msg = e.data; + if (typeof msg != 'string') return console.log("unsupported message kind: " + (typeof msg)); - console.log('GET REQUEST FOR SVG FILE', fname, id); - - var painter = pthis.FindSnap(id); - if (!painter) console.log('not find snap ' + id); - - } else { - if (d) console.log("unrecognized msg",d); - } + if (typeof pthis.OnWebsocketMsg == 'function') + pthis.OnWebsocketMsg(conn, msg); } conn.onclose = function() { @@ -2738,7 +2665,8 @@ delete pthis._websocket; if (pthis._websocket_opened) { pthis._websocket_opened = false; - window.close(); // close window when socked disapper + if (typeof pthis.OnWebsocketClosed == 'function') + pthis.OnWebsocketClosed(); } } @@ -4002,7 +3930,7 @@ hintsg.property('startx', posx); } - JSROOT.Painter.drawFrame = function(divid, obj) { + Painter.drawFrame = function(divid, obj) { var p = new TFramePainter(obj); p.SetDivId(divid, 2); p.Redraw(); @@ -4891,6 +4819,103 @@ // show we redraw all other painters without snapid? } + TPadPainter.prototype.OnWebsocketOpened = function(conn) { + // indicate that we are ready to recieve any following commands + conn.send('READY'); + } + + TPadPainter.prototype.OnWebsocketMsg = function(conn, msg) { + + if (msg.substr(0,5)=='SNAP:') { + + msg = msg.substr(5); + var p1 = msg.indexOf(":"), + snapid = msg.substr(0,p1), + snap = JSROOT.parse(msg.substr(p1+1)); + + if (typeof this.RedrawPadSnap === 'function') { + var pthis = this; + this.RedrawPadSnap(snap, function() { + conn.send("SNAPDONE:" + snapid); // send ready message back when drawing completed + }); + } else { + conn.send('READY'); // send ready message back + } + } else if (msg.substr(0,4)=='SNAP') { + // older version, used with ROOT6 implementation, should be removed soon + + var snap = JSROOT.parse(msg.substr(p1+1)); + + if (typeof this.RedrawPadSnap === 'function') { + var pthis = this; + this.RedrawPadSnap(snap, function() { + var reply = pthis.GetAllRanges(); + if (reply) console.log("ranges: " + reply); + conn.send(reply ? "RREADY:" + reply : "RREADY:" ); // send ready message back when drawing completed + }); + } else { + conn.send('READY'); // send ready message back + } + + } else if (msg.substr(0,4)=='JSON') { + var obj = JSROOT.parse(msg.substr(4)); + // console.log("get JSON ", msg.length-4, obj._typename); + var tm1 = new Date().getTime(); + this.RedrawObject(obj); + var tm2 = new Date().getTime(); + sum1+=1; + sum2+=(tm2-tm1); + if (sum1>10) { console.log('Redraw ', Math.round(sum2/sum1)); sum1=sum2=0; } + + conn.send('READY'); // send ready message back + // if (++cnt > 10) conn.close(); + + } else if (msg.substr(0,4)=='MENU') { + var lst = JSROOT.parse(msg.substr(4)); + console.log("get MENUS ", typeof lst, 'nitems', lst.length, msg.length-4); + conn.send('READY'); // send ready message back + if (typeof this._getmenu_callback == 'function') + this._getmenu_callback(lst); + } else if (msg.substr(0,4)=='CMD:') { + msg = msg.substr(4); + var p1 = msg.indexOf(":"), + cmdid = msg.substr(0,p1), + cmd = msg.substr(p1+1), + reply = "REPLY:" + cmdid + ":"; + if (cmd == "SVG") { + var res = ""; + if (this.CreateSvg) res = this.CreateSvg(); + console.log('SVG size = ' + res.length); + conn.send(reply + res); + } else if (cmd == "PNG") { + this.ProduceImage(true, 'any.png', function(can) { + var res = can.toDataURL('image/png'), + separ = res.indexOf("base64,"); + if (separ>0) + conn.send(reply + res.substr(separ+7)); + else + conn.send(reply); + }); + } else { + console.log('Urecognized command ' + cmd); + conn.send(reply); + } + + } else { + console.log("unrecognized msg " + msg); + } + } + + TPadPainter.prototype.OnWebsocketClosed = function() { + if (window) window.close(); // close window when socket disapper + } + + TPadPainter.prototype.WindowBeforeUnloadHanlder = function() { + // when window closed, close socket + this.CloseWebsocket(); + return null; // one could block close, but not now + } + TPadPainter.prototype.GetAllRanges = function() { var res = ""; @@ -5273,7 +5298,7 @@ if (d.check('TICK')) pad.fTickx = pad.fTicky = 1; } - JSROOT.Painter.drawCanvas = function(divid, can, opt) { + Painter.drawCanvas = function(divid, can, opt) { var nocanvas = (can===null); if (nocanvas) can = JSROOT.Create("TCanvas"); @@ -5299,7 +5324,7 @@ return painter; } - JSROOT.Painter.drawPad = function(divid, pad, opt) { + Painter.drawPad = function(divid, pad, opt) { var painter = new TPadPainter(pad, false); painter.DecodeOptions(opt); @@ -5340,7 +5365,7 @@ // ================= painter of raw text ======================================== - JSROOT.Painter.drawRawText = function(divid, txt, opt) { + Painter.drawRawText = function(divid, txt, opt) { var painter = new TBasePainter(); painter.txt = txt; @@ -5920,8 +5945,11 @@ } } - JSROOT.Painter.createRootColors(); + Painter.createRootColors(); + JSROOT.LongPollSocket = LongPollSocket; + JSROOT.Cef3QuerySocket = Cef3QuerySocket; + JSROOT.DrawOptions = DrawOptions; JSROOT.TBasePainter = TBasePainter; JSROOT.TObjectPainter = TObjectPainter; JSROOT.TFramePainter = TFramePainter; diff --git a/graf2d/gpad/v7/inc/ROOT/TCanvas.hxx b/graf2d/gpad/v7/inc/ROOT/TCanvas.hxx index c39d90006538a..5be2df35eb367 100644 --- a/graf2d/gpad/v7/inc/ROOT/TCanvas.hxx +++ b/graf2d/gpad/v7/inc/ROOT/TCanvas.hxx @@ -16,15 +16,15 @@ #ifndef ROOT7_TCanvas #define ROOT7_TCanvas +#include "ROOT/TDrawable.hxx" +#include "ROOT/TypeTraits.hxx" +#include "ROOT/TVirtualCanvasPainter.hxx" + #include #include #include #include -#include "ROOT/TDrawable.hxx" -#include "ROOT/TypeTraits.hxx" -#include "ROOT/TVirtualCanvasPainter.hxx" - namespace ROOT { namespace Experimental { @@ -48,8 +48,7 @@ private: /// Title of the canvas. std::string fTitle; - /// If canvas modified. - bool fModified; + uint64_t fModified; //(what), options)); + Modified(); } /// Remove an object from the list of primitives. // TODO: void Wipe(); - void Modified() { fModified = true; } + /// Display the canvas. + void Show(const std::string &where = ""); - /// Actually display the canvas. - void Show(); + // Indicates that primitives list was changed or any primitive was modified + void Modified() { fModified++; } - /// Save canvas in image file - void SaveAs(const std::string &filename); + // Return if canvas was modified and not yet updated + bool IsModified() const; /// update drawing - void Update(); + void Update(bool async = false, CanvasCallback_t callback = nullptr); + + /// Save canvas in image file + void SaveAs(const std::string &filename, bool async = false, CanvasCallback_t callback = nullptr); /// Get the canvas's title. const std::string &GetTitle() const { return fTitle; } diff --git a/graf2d/gpad/v7/inc/ROOT/TVirtualCanvasPainter.hxx b/graf2d/gpad/v7/inc/ROOT/TVirtualCanvasPainter.hxx index 13a3c141d0e41..fdc4f5047ed62 100644 --- a/graf2d/gpad/v7/inc/ROOT/TVirtualCanvasPainter.hxx +++ b/graf2d/gpad/v7/inc/ROOT/TVirtualCanvasPainter.hxx @@ -16,13 +16,16 @@ #ifndef ROOT7_TVirtualCanvasPainter #define ROOT7_TVirtualCanvasPainter -#include - #include "ROOT/TDisplayItem.hxx" +#include +#include + namespace ROOT { namespace Experimental { +using CanvasCallback_t = std::function; + class TCanvas; namespace Internal { @@ -53,8 +56,16 @@ public: /// add display item to the canvas virtual void AddDisplayItem(TDisplayItem *item) = 0; + /// indicate that canvas changed, provides current version of the canvas + virtual void CanvasUpdated(uint64_t, bool, CanvasCallback_t) = 0; + + /// return true if canvas modified since last painting + virtual bool IsCanvasModified(uint64_t) const = 0; + /// perform special action when drawing is ready - virtual void DoWhenReady(const std::string &, const std::string &) = 0; + virtual void DoWhenReady(const std::string &, const std::string &, bool, CanvasCallback_t) = 0; + + virtual void NewDisplay(const std::string &where) = 0; /// Loads the plugin that implements this class. static std::unique_ptr Create(const TCanvas &canv, bool batch_mode = false); diff --git a/graf2d/gpad/v7/src/TCanvas.cxx b/graf2d/gpad/v7/src/TCanvas.cxx index 42a2206f23f64..fd00696d70bc7 100644 --- a/graf2d/gpad/v7/src/TCanvas.cxx +++ b/graf2d/gpad/v7/src/TCanvas.cxx @@ -43,8 +43,16 @@ const std::vector> &ROOT::Experimen // } // } -void ROOT::Experimental::TCanvas::Update() +bool ROOT::Experimental::TCanvas::IsModified() const { + return fPainter ? fPainter->IsCanvasModified(fModified) : fModified; +} + +void ROOT::Experimental::TCanvas::Update(bool async, CanvasCallback_t callback) +{ + if (fPainter) + fPainter->CanvasUpdated(fModified, async, callback); + // SnapshotList_t lst; // for (auto&& drw: fPrimitives) { // TSnapshot *snap = drw->CreateSnapshot(*this); @@ -60,20 +68,47 @@ std::shared_ptr ROOT::Experimental::TCanvas::Create return pCanvas; } -void ROOT::Experimental::TCanvas::Show() +////////////////////////////////////////////////////////////////////////// +/// Create new display for the canvas +/// Parameter \par where specified which program could be used for display creation +/// Possible values: +/// +/// cef - Chromium Embeded Framework, local display, local communication +/// qt5 - Qt5 WebEngine (when running via rootqt5), local display, local communication +/// browser - default system web-browser, communication via random http port from range 8800 - 9800 +/// - any program name which will be started instead of default browser, like firefox or /usr/bin/opera +/// one could also specify $url in program name, which will be replaced with canvas URL +/// native - either any available local display or default browser +/// +/// Canvas can be displayed in several different places + +void ROOT::Experimental::TCanvas::Show(const std::string &where) { - if (fPainter) return; + if (fPainter) { + if (!where.empty()) + fPainter->NewDisplay(where); + return; + } + bool batch_mode = gROOT->IsBatch(); + if (!fModified) + fModified = 1; // 0 is special value, means no changes and no drawings + fPainter = Internal::TVirtualCanvasPainter::Create(*this, batch_mode); + if (fPainter) { + fPainter->NewDisplay(where); + fPainter->CanvasUpdated(fModified, true, nullptr); // trigger async display + } } -void ROOT::Experimental::TCanvas::SaveAs(const std::string &filename) +void ROOT::Experimental::TCanvas::SaveAs(const std::string &filename, bool async, CanvasCallback_t callback) { - if (!fPainter) fPainter = Internal::TVirtualCanvasPainter::Create(*this, true); + if (!fPainter) + fPainter = Internal::TVirtualCanvasPainter::Create(*this, true); if (filename.find(".svg") != std::string::npos) - fPainter->DoWhenReady("SVG", filename); + fPainter->DoWhenReady("SVG", filename, async, callback); else if (filename.find(".png") != std::string::npos) - fPainter->DoWhenReady("PNG", filename); + fPainter->DoWhenReady("PNG", filename, async, callback); } // TODO: removal from GetHeldCanvases(). diff --git a/gui/canvaspainter/v7/cef/base_handler.cxx b/gui/canvaspainter/v7/cef/base_handler.cxx index ad4164c2d713a..ea850c1da0bbb 100644 --- a/gui/canvaspainter/v7/cef/base_handler.cxx +++ b/gui/canvaspainter/v7/cef/base_handler.cxx @@ -47,7 +47,11 @@ class TCefWSEngine : public THttpWSEngine { { } - virtual ~TCefWSEngine() {} + virtual ~TCefWSEngine() + { + if (fCallback) + fCallback->Failure(0, "close"); + } virtual UInt_t GetId() const { @@ -55,7 +59,7 @@ class TCefWSEngine : public THttpWSEngine { return TString::Hash((void *)&ptr, sizeof(void *)); } - virtual void ClearHandle() { fCallback->Failure(0, "close"); } + virtual void ClearHandle() { fCallback = NULL; } virtual void Send(const void * /*buf*/, int /*len*/) { @@ -64,8 +68,9 @@ class TCefWSEngine : public THttpWSEngine { virtual void SendCharStar(const char *buf) { - // printf("CEF sends message to client %s\n", buf); - fCallback->Success(buf); // send next message to JS + // printf("CEF sends message to client %d\n", strlen(buf)); + if (fCallback) + fCallback->Success(buf); // send next message to JS } virtual Bool_t PreviewData(THttpCallArg *arg) @@ -96,17 +101,21 @@ class TCefWsCallArg : public THttpCallArg { virtual void HttpReplied() { - if (fCallback == NULL) return; + if (!fCallback) + return; if (Is404()) { fCallback->Failure(0, "error"); } else { std::string reply; - if (GetContentLength() > 0) reply.append((const char *)GetContent(), GetContentLength()); + if (GetContentLength() > 0) + reply.append((const char *)GetContent(), GetContentLength()); fCallback->Success(reply); } fCallback = NULL; } + + void ClearCallBack() { fCallback = NULL; } }; // Handle messages in the browser process. @@ -117,27 +126,22 @@ class RootMessageHandler : public CefMessageRouterBrowserSide::Handler { public: explicit RootMessageHandler(THttpServer *serv = 0) : fServer(serv) {} - // Called due to cefQuery execution in message_router.html. + // Called due to cefQuery execution bool OnQuery(CefRefPtr browser, CefRefPtr frame, int64 query_id, const CefString &request, bool persistent, CefRefPtr callback) OVERRIDE { std::string message = request; - if (message == "init_jsroot_done") { - printf("Get message %s\n", message.c_str()); - std::string result = "confirm from ROOT"; - callback->Success(result); - return true; // processed - } - - if (!fServer) return false; + if (!fServer) + return false; // message format // ::connect, replied with ws handler id // ::::post:: or ::::close int pos = message.find("::"); - if (pos == std::string::npos) return false; + if (pos == std::string::npos) + return false; std::string url = message.substr(0, pos); message.erase(0, pos + 2); @@ -154,7 +158,8 @@ class RootMessageHandler : public CefMessageRouterBrowserSide::Handler { printf("Create CEF WS engine with id %u\n", ws->GetId()); } else { pos = message.find("::"); - if (pos == std::string::npos) return false; + if (pos == std::string::npos) + return false; std::string sid = message.substr(0, pos); message.erase(0, pos + 2); unsigned wsid = 0; @@ -162,13 +167,16 @@ class RootMessageHandler : public CefMessageRouterBrowserSide::Handler { arg->SetWSId(wsid); if (message == "close") { arg->SetMethod("WS_CLOSE"); + arg->ClearCallBack(); } else { arg->SetMethod("WS_DATA"); - if (message.length() > 6) arg->SetPostData((void *)(message.c_str() + 6), message.length() - 6, kTRUE); + if (message.length() > 6) + arg->SetPostData((void *)(message.c_str() + 6), message.length() - 6, kTRUE); } } - if (fServer->SubmitHttp(arg, kTRUE)) arg->HttpReplied(); // message processed and can be replied + if (fServer->SubmitHttp(arg, kTRUE)) + arg->HttpReplied(); // message processed and can be replied return true; } @@ -288,7 +296,8 @@ void BaseHandler::OnLoadError(CefRefPtr browser, CefRefPtr CEF_REQUIRE_UI_THREAD(); // Don't display an error for downloaded files. - if (errorCode == ERR_ABORTED) return; + if (errorCode == ERR_ABORTED) + return; // Display a load error message. std::stringstream ss; @@ -307,7 +316,8 @@ void BaseHandler::CloseAllBrowsers(bool force_close) return; } - if (browser_list_.empty()) return; + if (browser_list_.empty()) + return; BrowserList::const_iterator it = browser_list_.begin(); for (; it != browser_list_.end(); ++it) (*it)->GetHost()->CloseBrowser(force_close); diff --git a/gui/canvaspainter/v7/src/TCanvasPainter.cxx b/gui/canvaspainter/v7/src/TCanvasPainter.cxx index b3edb3c6acf72..085c5808b9bf7 100644 --- a/gui/canvaspainter/v7/src/TCanvasPainter.cxx +++ b/gui/canvaspainter/v7/src/TCanvasPainter.cxx @@ -164,15 +164,36 @@ class TCanvasPainter : public THttpWSHandler, private: struct WebConn { THttpWSEngine *fHandle; /// WebConnList; + typedef std::list WebCommandsList; + + typedef std::list WebUpdatesList; + typedef std::vector MenuItemsVector; /// The canvas we are painting. It might go out of existence while painting. @@ -182,7 +203,14 @@ class TCanvasPainter : public THttpWSHandler, WebConnList fWebConn; ///Register("/web7gui", this); - PopupBrowser(); + } + + virtual ~TCanvasPainter() + { + CancelCommands(true); + CancelUpdates(); } virtual bool IsBatchMode() const { return fBatchMode; } virtual void AddDisplayItem(ROOT::Experimental::TDisplayItem *item) final; + virtual void CanvasUpdated(uint64_t, bool, ROOT::Experimental::CanvasCallback_t) override; + + virtual bool IsCanvasModified(uint64_t) const override; + /// perform special action when drawing is ready - virtual void DoWhenReady(const std::string &cmd, const std::string &arg) final; + virtual void DoWhenReady(const std::string &cmd, const std::string &arg, bool async, + ROOT::Experimental::CanvasCallback_t callback) final; + + // open new display for the canvas + virtual void NewDisplay(const std::string &where) override; // void ReactToSocketNews(...) override { SendCanvas(); } @@ -289,34 +341,53 @@ void TCanvasPainter::CreateHttpServer(Bool_t with_http) gServer->CreateEngine(TString::Format("http:%s?websocket_timeout=10000", port).Data()); } -void TCanvasPainter::PopupBrowser() +////////////////////////////////////////////////////////////////////////// +/// Create new display for the canvas +/// Parameter \par where specified which program could be used for display creation +/// Possible values: +/// +/// cef - Chromium Embeded Framework, local display, local communication +/// qt5 - Qt5 WebEngine (when running via rootqt5), local display, local communication +/// browser - default system web-browser, communication via random http port from range 8800 - 9800 +/// - any program name which will be started instead of default browser, like firefox or /usr/bin/opera +/// one could also specify $url in program name, which will be replaced with canvas URL +/// native - either any available local display or default browser +/// +/// Canvas can be displayed in several different places + +void TCanvasPainter::NewDisplay(const std::string &where) { TString addr; + bool is_native = where.empty() || (where == "native"), is_qt5 = (where == "qt5"), ic_cef = (where == "cef"); + Func_t symbol_qt5 = gSystem->DynFindSymbol("*", "webgui_start_browser_in_qt5"); - if (symbol_qt5) { + + if (symbol_qt5 && (is_native || is_qt5)) { typedef void (*FunctionQt5)(const char *, void *, bool); addr.Form("://dummy:8080/web7gui/%s/draw.htm?longpollcanvas%s", GetName(), (IsBatchMode() ? "&batch_mode" : "")); // addr.Form("example://localhost:8080/Canvases/%s/draw.htm", Canvas()->GetName()); - Info("PopupBrowser", "Show canvas in Qt5 window: %s", addr.Data()); + Info("NewDisplay", "Show canvas in Qt5 window: %s", addr.Data()); FunctionQt5 func = (FunctionQt5)symbol_qt5; func(addr.Data(), gServer, IsBatchMode()); return; } + // TODO: one should try to load CEF libraries only when really needed + // probably, one should create separate DLL with CEF-related code Func_t symbol_cef = gSystem->DynFindSymbol("*", "webgui_start_browser_in_cef3"); const char *cef_path = gSystem->Getenv("CEF_PATH"); const char *rootsys = gSystem->Getenv("ROOTSYS"); - if (symbol_cef && cef_path && !gSystem->AccessPathName(cef_path) && rootsys) { + if (symbol_cef && cef_path && !gSystem->AccessPathName(cef_path) && rootsys && (is_native || ic_cef)) { typedef void (*FunctionCef3)(const char *, void *, bool, const char *, const char *); // addr.Form("/web7gui/%s/draw.htm?cef_canvas%s", GetName(), (IsBatchMode() ? "&batch_mode" : "")); addr.Form("/web7gui/%s/draw.htm?cef_canvas%s", GetName(), (IsBatchMode() ? "&batch_mode" : "")); - Info("PopupBrowser", "Show canvas in CEF window: %s", addr.Data()); + Info("NewDisplay", "Show canvas in CEF window: %s", addr.Data()); FunctionCef3 func = (FunctionCef3)symbol_cef; func(addr.Data(), gServer, IsBatchMode(), rootsys, cef_path); @@ -330,20 +401,203 @@ void TCanvasPainter::PopupBrowser() TString exec; - if (gSystem->InheritsFrom("TMacOSXSystem")) + if (!is_native && !ic_cef && !is_qt5 && (where != "browser")) { + if (where.find("$url") != std::string::npos) { + exec = where.c_str(); + exec.ReplaceAll("$url", addr); + } else { + exec.Form("%s %s", where.c_str(), addr.Data()); + } + } else if (gSystem->InheritsFrom("TMacOSXSystem")) exec.Form("open %s", addr.Data()); else exec.Form("xdg-open %s &", addr.Data()); - Info("PopupBrowser", "Show canvas in browser with cmd: %s", exec.Data()); + Info("NewDisplay", "Show canvas in browser with cmd: %s", exec.Data()); gSystem->Exec(exec); } -void TCanvasPainter::DoWhenReady(const std::string &cmd, const std::string &arg) +void TCanvasPainter::CanvasUpdated(uint64_t ver, bool async, ROOT::Experimental::CanvasCallback_t callback) +{ + if (ver && fSnapshotDelivered && (ver <= fSnapshotDelivered)) { + // if given canvas version was already delivered to clients, can return immediately + if (callback) + callback(true); + return; + } + + fSnapshotVersion = ver; + fSnapshot = CreateSnapshot(fCanvas); + + CheckDataToSend(); + + if (callback) { + WebUpdate item; + item.fVersion = ver; + item.fCallback = callback; + fUpdatesLst.push_back(item); + } + + if (!async) + WaitWhenCanvasPainted(ver); +} + +bool TCanvasPainter::WaitWhenCanvasPainted(uint64_t ver) +{ + // simple polling loop until specified version delivered to the clients + + uint64_t cnt = 0; + bool had_connection = false; + + while (true) { + if (fWebConn.size() > 0) + had_connection = true; + if ((fWebConn.size() == 0) && (had_connection || (cnt > 1000))) + return false; // wait ~1 min if no new connection established + if (fSnapshotDelivered >= ver) { + printf("PAINT READY!!!\n"); + return true; + } + gSystem->ProcessEvents(); + gSystem->Sleep((++cnt < 500) ? 1 : 100); // increase sleep interval when do very often + } + + return false; +} + +void TCanvasPainter::DoWhenReady(const std::string &name, const std::string &arg, bool async, + ROOT::Experimental::CanvasCallback_t callback) +{ + if (!async && !fWaitingCmdId.empty()) { + Error("DoWhenReady", "Fail to submit sync command when previous is still awaited - use async"); + async = true; + } + + WebCommand cmd; + cmd.fId = TString::ULLtoa(++fCmdsCnt, 10); + cmd.fName = name; + cmd.fArg = arg; + cmd.fRunning = false; + cmd.fCallback = callback; + fCmds.push_back(cmd); + + if (!async) + fWaitingCmdId = cmd.fId; + + CheckDataToSend(); + + if (async) + return; + + uint64_t cnt = 0; + bool had_connection = false; + + while (true) { + if (fWebConn.size() > 0) + had_connection = true; + if ((fWebConn.size() == 0) && (had_connection || (cnt > 1000))) + return; // wait ~1 min if no new connection established + if (fWaitingCmdId.empty()) { + printf("Command %s waiting READY!!!\n", name.c_str()); + return; + } + gSystem->ProcessEvents(); + gSystem->Sleep((++cnt < 500) ? 1 : 100); // increase sleep interval when do very often + } +} + +///////////////////////////////////////////////////////////////////////////////////////////// +/// Remove front command from the command queue +/// If necessary, configured call-back will be invoked + +void TCanvasPainter::PopFrontCommand(bool result) +{ + if (fCmds.size() == 0) + return; + + // simple condition, which will be checked in waiting loop + if (!fWaitingCmdId.empty() && (fWaitingCmdId == fCmds.front().fId)) + fWaitingCmdId.clear(); + + if (fCmds.front().fCallback) + fCmds.front().fCallback(result); + + fCmds.pop_front(); +} + +///////////////////////////////////////////////////////////////////////////////////////////// +/// Cancel commands for given connection ID +/// Invoke all callbacks + +void TCanvasPainter::CancelCommands(bool cancel_all, UInt_t connid) { - fNextCmd = cmd + ":" + arg; - CheckModifiedFlag(); + auto iter = fCmds.begin(); + while (iter != fCmds.end()) { + auto next = iter; + next++; + if (cancel_all || (iter->fConnId == connid)) { + if (fWaitingCmdId == iter->fId) + fWaitingCmdId.clear(); + iter->fCallback(false); + fCmds.erase(iter); + } + } +} + +///////////////////////////////////////////////////////////////////////////////////////////// +/// Cancel all pending Canvas::Update() + +void TCanvasPainter::CancelUpdates() +{ + fSnapshotDelivered = 0; + auto iter = fUpdatesLst.begin(); + while (iter != fUpdatesLst.end()) { + auto curr = iter; + iter++; + curr->fCallback(false); + fUpdatesLst.erase(curr); + } +} + +///////////////////////////////////////////////////////////////////////////////////////////// +/// Process reply of first command in the queue +/// For the moment commands use to create image files + +bool TCanvasPainter::FrontCommandReplied(const std::string &reply) +{ + WebCommand &cmd = fCmds.front(); + + cmd.fRunning = false; + + bool result = false; + + if (cmd.fName == "SVG") { + if (reply.length() == 0) { + Error("FrontCommandReplied", "Fail to produce SVG image %s", cmd.fArg.c_str()); + } else { + std::ofstream ofs(cmd.fArg); + ofs.write(reply.c_str(), reply.length()); + ofs.close(); + Info("FrontCommandReplied", "Create SVG file %s len %d", cmd.fArg.c_str(), (int)reply.length()); + result = true; + } + } else if (cmd.fName == "PNG") { + if (reply.length() == 0) { + Error("FrontCommandReplied", "Fail to produce PNG image %s", cmd.fArg.c_str()); + } else { + std::string png = base64_decode(reply); + std::ofstream ofs(cmd.fArg); + ofs.write(png.c_str(), png.length()); + ofs.close(); + Info("FrontCommandReplied", "Create PNG file %s len %d", cmd.fArg.c_str(), (int)png.length()); + result = true; + } + } else { + Error("FrontCommandReplied", "Unknown command %s", cmd.fName.c_str()); + } + + return result; } Bool_t TCanvasPainter::ProcessWS(THttpCallArg *arg) @@ -377,10 +631,11 @@ Bool_t TCanvasPainter::ProcessWS(THttpCallArg *arg) WebConn newconn; newconn.fHandle = wshandle; - newconn.fModified = kTRUE; fWebConn.push_back(newconn); - printf("socket is ready\n"); + // printf("socket is ready %d\n", fWebConn.back().fReady); + + CheckDataToSend(); return kTRUE; } @@ -388,7 +643,12 @@ Bool_t TCanvasPainter::ProcessWS(THttpCallArg *arg) if (strcmp(arg->GetMethod(), "WS_CLOSE") == 0) { // connection is closed, one can remove handle + printf("Connection closed\n"); + + UInt_t connid = 0; + if (conn && conn->fHandle) { + connid = conn->fHandle->GetId(); conn->fHandle->ClearHandle(); delete conn->fHandle; conn->fHandle = 0; @@ -396,6 +656,12 @@ Bool_t TCanvasPainter::ProcessWS(THttpCallArg *arg) if (conn) fWebConn.erase(iter); + + // if there are no other connections - cancel all submitted commands + CancelCommands((fWebConn.size() == 0), connid); + + CheckDataToSend(); // check if data should be send via other connections + return kTRUE; } @@ -416,50 +682,42 @@ Bool_t TCanvasPainter::ProcessWS(THttpCallArg *arg) if (strncmp(cdata, "READY", 5) == 0) { conn->fReady = kTRUE; - CheckModifiedFlag(); + CheckDataToSend(); + } else if (strncmp(cdata, "SNAPDONE:", 9) == 0) { + conn->fReady = kTRUE; + conn->fDrawReady = kTRUE; // at least first drawing is performed + conn->fDelivered = (uint64_t)TString(cdata + 9).Atoll(); // delivered version of the snapshot + CheckDataToSend(); } else if (strncmp(cdata, "RREADY:", 7) == 0) { conn->fReady = kTRUE; conn->fDrawReady = kTRUE; // at least first drawing is performed - CheckModifiedFlag(); + CheckDataToSend(); } else if (strncmp(cdata, "GETMENU:", 8) == 0) { conn->fReady = kTRUE; conn->fGetMenu = cdata + 8; - CheckModifiedFlag(); + CheckDataToSend(); } else if (strncmp(cdata, "GEXE:", 5) == 0) { // TODO: temporary solution, should be removed later // used now to terminate ROOT session gROOT->ProcessLine(cdata + 5); - } else if (strncmp(cdata, "DONESVG:", 8) == 0) { - TString buf(cdata + 8); - Int_t pos = buf.First(':'); - if (pos > 0) { - TString fname = buf(0, pos); - buf.Remove(0, pos + 1); - std::ofstream ofs(fname.Data()); - ofs.write(buf.Data(), buf.Length()); - ofs.close(); - printf("Create SVG file %s len %d\n", fname.Data(), buf.Length()); - } - conn->fReady = kTRUE; - CheckModifiedFlag(); - } else if (strncmp(cdata, "DONEPNG:", 8) == 0) { - TString buf(cdata + 8); - Int_t pos = buf.First(':'); - if (pos > 0) { - TString fname = buf(0, pos); - buf.Remove(0, pos + 1); - - std::string png = base64_decode(buf.Data()); - - std::ofstream ofs(fname.Data()); - ofs.write(png.c_str(), png.length()); - ofs.close(); - printf("Create PNG file %s len %d\n", fname.Data(), (int)png.length()); + } else if (strncmp(cdata, "REPLY:", 6) == 0) { + const char *sid = cdata + 6; + const char *separ = strchr(sid, ':'); + std::string id; + if (separ) + id.append(sid, separ - sid); + if (fCmds.size() == 0) { + Error("ProcessWS", "Get REPLY without command"); + } else if (!fCmds.front().fRunning) { + Error("ProcessWS", "Front command is not running when get reply"); + } else if (fCmds.front().fId != id) { + Error("ProcessWS", "Mismatch with front command and ID in REPLY"); } else { - printf("Error when producing PNG image %s\n", buf.Data()); + bool res = FrontCommandReplied(separ + 1); + PopFrontCommand(res); } conn->fReady = kTRUE; - CheckModifiedFlag(); + CheckDataToSend(); } else if (strncmp(cdata, "OBJEXEC:", 8) == 0) { TString buf(cdata + 8); Int_t pos = buf.First(':'); @@ -478,20 +736,30 @@ Bool_t TCanvasPainter::ProcessWS(THttpCallArg *arg) return kTRUE; } -void TCanvasPainter::CheckModifiedFlag() +void TCanvasPainter::CheckDataToSend() { + uint64_t min_delivered = 0; + for (WebConnList::iterator citer = fWebConn.begin(); citer != fWebConn.end(); ++citer) { WebConn &conn = *citer; + if (conn.fDelivered && (!min_delivered || (min_delivered < conn.fDelivered))) + min_delivered = conn.fDelivered; + if (!conn.fReady || !conn.fHandle) continue; TString buf; - if (conn.fDrawReady && !fNextCmd.empty()) { - buf = fNextCmd; - fNextCmd.clear(); + if (conn.fDrawReady && (fCmds.size() > 0) && !fCmds.front().fRunning) { + WebCommand &cmd = fCmds.front(); + cmd.fRunning = true; + buf = "CMD:"; + buf.Append(cmd.fId); + buf.Append(":"); + buf.Append(cmd.fName); + cmd.fConnId = conn.fHandle->GetId(); } else if (!conn.fGetMenu.empty()) { ROOT::Experimental::Internal::TDrawable *drawable = FindDrawable(fCanvas, conn.fGetMenu); @@ -509,13 +777,15 @@ void TCanvasPainter::CheckModifiedFlag() } conn.fGetMenu = ""; - } else if (conn.fModified) { + } else if (conn.fSend != fSnapshotVersion) { // buf = "JSON"; // buf += TBufferJSON::ConvertToJSON(Canvas(), 3); - buf = "SNAP"; - buf += CreateSnapshot(fCanvas); - conn.fModified = kFALSE; + conn.fSend = fSnapshotVersion; + buf = "SNAP:"; + buf += TString::ULLtoa(fSnapshotVersion, 10); + buf += ":"; + buf += fSnapshot; } if (buf.Length() > 0) { @@ -524,6 +794,29 @@ void TCanvasPainter::CheckModifiedFlag() conn.fHandle->SendCharStar(buf.Data()); } } + + // if there are updates submitted, but all connections disappeared - cancel all updates + if ((fWebConn.size() == 0) && fSnapshotDelivered) + return CancelUpdates(); + + if (fSnapshotDelivered != min_delivered) { + fSnapshotDelivered = min_delivered; + + auto iter = fUpdatesLst.begin(); + while (iter != fUpdatesLst.end()) { + auto curr = iter; + iter++; + if (curr->fVersion <= fSnapshotDelivered) { + curr->fCallback(true); + fUpdatesLst.erase(curr); + } + } + } +} + +bool TCanvasPainter::IsCanvasModified(uint64_t id) const +{ + return fSnapshotDelivered != id; } void TCanvasPainter::AddDisplayItem(ROOT::Experimental::TDisplayItem *item) @@ -551,7 +844,7 @@ std::string TCanvasPainter::CreateSnapshot(const ROOT::Experimental::TCanvas &ca fDisplayList.SetObjectIDAsPtr((void *)&can); - TPad *dummy = new TPad(); // just to keep information we need for different ranges now + TPad *dummy = new TPad(); // just provide old class where all kind of info (size, ranges) already provided auto *snap = new ROOT::Experimental::TUniqueDisplayItem(dummy); snap->SetObjectIDAsPtr((void *)&can); diff --git a/tutorials/v7/draw_v6.cxx b/tutorials/v7/draw_v6.cxx index 3782090b015d0..39e408baa9b50 100644 --- a/tutorials/v7/draw_v6.cxx +++ b/tutorials/v7/draw_v6.cxx @@ -35,7 +35,26 @@ void draw_v6() auto canvas = Experimental::TCanvas::Create("v7 TCanvas showing a v6 TGraph"); canvas->Draw(gr); - canvas->Show(); + canvas->Show(); // new window should popup and async update will be triggered - canvas->SaveAs("draw.png"); // only .svg and .png are supported for the moment, asynchron + // canvas->Show("/usr/bin/opera"); // one could specify program name which should show canvas + // canvas->Show("firefox"); // it could be firefox, opera, chromium; canvas can be shown several times + + // synchronous, wait until painting is finished + canvas->Update(false, + [](bool res) { std::cout << "First Update done = " << (res ? "true" : "false") << std::endl; }); + + // call again, should return immediately + canvas->Update(false, + [](bool res) { std::cout << "Second Update done = " << (res ? "true" : "false") << std::endl; }); + + // request to create PNG file in asynchronous mode and specify lambda function as callback + // when request processed by the client, callback called with result value + canvas->SaveAs("draw.png", true, [](bool res) { + std::cout << "Producing PNG done res = " << (res ? "true" : "false") << std::endl; + }); // asynchronous + + // this function executed in synchronous mode (async = false is default), + // mean previous file saving will be completed as well at this point + canvas->SaveAs("draw.svg"); // asynchronous }