42using namespace std::string_literals;
115 std::list<std::shared_ptr<WebCommand>>
fCmds;
137 void ProcessData(
unsigned connid,
const std::string &arg);
166 void NewDisplay(const std::
string &where) final;
176 void Run(
double tm = 0.) final;
191 return std::make_unique<RCanvasPainter>(canv);
199 R__LOG_ERROR(CanvasPainerLog()) <<
"Generator is already set! Skipping second initialization.";
224 auto comp =
gEnv->GetValue(
"WebGui.JsonComp", -1);
246 item.fCallback(
false);
256 std::list<std::shared_ptr<WebCommand>> remainingCmds;
258 for (
auto &&cmd :
fCmds) {
259 if (!connid || (cmd->fConnId == connid)) {
260 cmd->CallBack(
false);
263 remainingCmds.emplace_back(std::move(cmd));
267 std::swap(
fCmds, remainingCmds);
275 uint64_t min_delivered = 0;
276 bool is_any_send =
true;
279 while (is_any_send && (++loopcnt < 10)) {
285 if (conn.fDelivered && (!min_delivered || (min_delivered < conn.fDelivered)))
286 min_delivered = conn.fDelivered;
289 bool need_send_snapshot = (conn.fSend !=
fCanvas.GetModified()) && (conn.fDelivered == conn.fSend);
292 if (need_send_snapshot && (loopcnt == 0))
293 if (std::find(conn.fSendQueue.begin(), conn.fSendQueue.end(),
""s) == conn.fSendQueue.end())
294 conn.fSendQueue.emplace_back(
""s);
297 if (!
fWindow->CanSend(conn.fConnId,
true))
303 ((
fCmds.front()->fConnId == 0) || (
fCmds.front()->fConnId == conn.fConnId))) {
305 auto &cmd =
fCmds.front();
307 cmd->fConnId = conn.fConnId;
313 }
else if (!conn.fSendQueue.empty()) {
315 buf = conn.fSendQueue.front().c_str();
316 conn.fSendQueue.pop_front();
319 if (!need_send_snapshot && (buf.
Length() == 0) && !conn.fSendQueue.empty()) {
320 buf = conn.fSendQueue.front().c_str();
321 conn.fSendQueue.pop_front();
325 if ((buf.
Length() == 0) && need_send_snapshot) {
335 conn.fSend =
fCanvas.GetModified();
355 if (item.fVersion > fSnapshotDelivered)
392 fWindow->WaitForTimed([
this, ver](
double) {
421 if (arg ==
"AddPanel") {
423 connid =
fWindow->GetConnectionId();
435 auto cmd = std::make_shared<WebCommand>(std::to_string(++
fCmdsCnt),
name, arg, callback, connid);
436 fCmds.emplace_back(cmd);
442 int res =
fWindow->WaitForTimed([
this, cmd](
double) {
444 R__LOG_DEBUG(0, CanvasPainerLog()) <<
"Command " << cmd->fName <<
" done";
445 return cmd->fResult ? 1 : -1;
449 if (!
fWindow->HasConnection(cmd->fConnId,
false))
459 R__LOG_ERROR(CanvasPainerLog()) <<
name <<
" fail with " << arg <<
" result = " << res;
468 auto len = fname.length();
469 bool is_json = (len > 4) && ((fname.compare(len-4,4,
".json") == 0) || (fname.compare(len-4,4,
".JSON") == 0));
481 std::ofstream
f(fname);
483 R__LOG_ERROR(CanvasPainerLog()) <<
"Fail to open file " << fname <<
" to store canvas snapshot";
486 R__LOG_INFO(CanvasPainerLog()) <<
"Store canvas in " << fname;
518 auto check_header = [&arg, &cdata](
const std::string &header) {
519 if (arg.compare(0, header.length(), header) != 0)
521 cdata = arg.substr(header.length());
528 if (check_header(
"READY")) {
530 }
else if (check_header(
"SNAPDONE:")) {
531 conn->fDelivered = (uint64_t)std::stoll(cdata);
532 }
else if (arg ==
"QUIT") {
536 }
else if (arg ==
"START_BROWSER") {
537 gROOT->ProcessLine(
"auto br = std::make_shared<ROOT::RBrowser>();br->ClearOnClose(br);");
539 }
else if (arg ==
"RELOAD") {
541 }
else if (arg ==
"INTERRUPT") {
542 gROOT->SetInterrupt();
543 }
else if (check_header(
"REPLY:")) {
544 const char *sid = cdata.c_str();
545 const char *separ = strchr(sid,
':');
548 id.append(sid, separ - sid);
550 R__LOG_ERROR(CanvasPainerLog()) <<
"Get REPLY without command";
552 R__LOG_ERROR(CanvasPainerLog()) <<
"Front command is not running when get reply";
553 }
else if (
fCmds.front()->fId !=
id) {
554 R__LOG_ERROR(CanvasPainerLog()) <<
"Mismatch with front command and ID in REPLY";
558 }
else if (check_header(
"SAVE:")) {
560 }
else if (check_header(
"PRODUCE:")) {
561 R__LOG_DEBUG(0, CanvasPainerLog()) <<
"Create file " << cdata;
570 }
else if (check_header(
"REQ:")) {
573 std::shared_ptr<RDrawable> drawable;
574 req->GetContext().SetCanvas(&
fCanvas);
575 if (req->GetId().empty() || (req->GetId() ==
"canvas")) {
576 req->GetContext().SetPad(
nullptr);
577 req->GetContext().SetDrawable(&
fCanvas, 0);
581 req->GetContext().SetPad(
const_cast<RPadBase *
>(subpad));
582 req->GetContext().SetDrawable(drawable.get(), 0);
585 req->GetContext().SetConnection(connid, conn ==
fWebConn.begin());
587 auto reply = req->Process();
589 if (req->ShouldBeReplyed()) {
591 reply = std::make_unique<RDrawableReply>();
593 reply->SetRequestId(req->GetRequestId());
596 conn->fSendQueue.emplace_back(
"REPL_REQ:"s + json.Data());
600 if (req->NeedCanvasUpdate())
604 R__LOG_ERROR(CanvasPainerLog()) <<
"Fail to parse RDrawableRequest";
606 }
else if (check_header(
"RESIZED:")) {
608 if (sz && sz->size() == 2) {
612 }
else if (check_header(
"CLEAR")) {
615 }
else if (check_header(
"SHOWURL:")) {
621 R__LOG_ERROR(CanvasPainerLog()) <<
"Got not recognized message" << arg;
636 fWindow->SetDefaultPage(
"file:rootui5sys/canv/canvas.html");
639 [
this](
unsigned connid) {
644 [
this](
unsigned connid,
const std::string &arg) {
ProcessData(connid, arg); },
646 [
this](
unsigned connid) {
666 int width =
fCanvas.GetWidth();
667 int height =
fCanvas.GetHeight();
671 if ((width > 10) && (height > 10)) {
698 return fWindow->NumConnections();
718 return fWindow->GetUrl(remote);
727 if (
gROOT->IsWebDisplayBatch())
731 R__LOG_ERROR(CanvasPainerLog()) <<
"Canvas not yet shown in AddPanel";
736 R__LOG_ERROR(CanvasPainerLog()) <<
"Canvas window was not shown to call AddPanel";
740 if (win->GetManager() !=
fWindow->GetManager()) {
741 R__LOG_ERROR(CanvasPainerLog()) <<
"Cannot embed window from other windows manager";
746 std::string addr = win->GetUrl(
false);
749 R__LOG_ERROR(CanvasPainerLog()) <<
"Cannot attach panel to canvas";
756 std::string cmd(
"ADDPANEL:");
772 fWindow->SetClearOnClose(handle);
782 auto canvitem = std::make_unique<RCanvasDisplayItem>();
784 fCanvas.DisplayPrimitives(*canvitem, ctxt);
786 canvitem->SetTitle(
fCanvas.GetTitle());
789 canvitem->BuildFullId(
"");
790 canvitem->SetObjectID(
"canvas");
795 static std::vector<const TClass *> exclude_classes = {
809 for (
auto cl : exclude_classes)
810 json.SetSkipClassInfo(cl);
814 return std::string(res.Data());
821std::shared_ptr<RDrawable>
824 std::string search = id;
825 size_t pos = search.find(
"#");
827 if (
pos != std::string::npos)
841 size_t pos = reply.find(
":");
842 if ((
pos == std::string::npos) || (
pos == 0)) {
843 R__LOG_ERROR(CanvasPainerLog()) <<
"SaveCreatedFile does not found ':' separator";
847 std::string fname(reply, 0,
pos);
848 reply.erase(0,
pos + 1);
850 Bool_t isSvg = (fname.length() > 4) && ((fname.rfind(
".svg") == fname.length()-4) || (fname.rfind(
".SVG") == fname.length()-4));
854 std::ofstream ofs(fname, std::ios::binary);
857 file_len = reply.length();
861 file_len = binary.
Length();
865 R__LOG_INFO(CanvasPainerLog()) <<
" Save file from GUI " << fname <<
" len " << file_len;
873 auto cmd =
fCmds.front();
880 if ((cmd->fName ==
"SVG") || (cmd->fName ==
"PNG") || (cmd->fName ==
"JPEG")) {
881 if (reply.length() == 0) {
882 R__LOG_ERROR(CanvasPainerLog()) <<
"Fail to produce image" << cmd->fArg;
885 std::ofstream ofs(cmd->fArg, std::ios::binary);
888 R__LOG_INFO(CanvasPainerLog()) << cmd->fName <<
" create file " << cmd->fArg <<
" length " << content.
Length();
891 }
else if (cmd->fName.find(
"ADDPANEL:") == 0) {
892 R__LOG_DEBUG(0, CanvasPainerLog()) <<
"get reply for ADDPANEL " << reply;
893 result = (reply ==
"true");
895 R__LOG_ERROR(CanvasPainerLog()) <<
"Unknown command " << cmd->fName;
898 cmd->fResult = result;
899 cmd->CallBack(result);
911 std::this_thread::sleep_for(std::chrono::milliseconds(
int(tm*1000)));
struct TNewCanvasPainterReg newCanvasPainterReg
#define R__LOG_ERROR(...)
#define R__LOG_DEBUG(DEBUGLEVEL,...)
bool Bool_t
Boolean (0=false, 1=true) (bool).
Abstract interface for painting a canvas.
static std::unique_ptr< Generator > & GetGenerator()
generator getter
static void ResetGlobalPainter()
Release the GeneratorImpl object.
~GeneratorImpl() override=default
std::unique_ptr< RVirtualCanvasPainter > Create(RCanvas &canv) const override
Create a new RCanvasPainter to paint the given RCanvas.
static void SetGlobalPainter()
Set RVirtualCanvasPainter::fgGenerator to a new GeneratorImpl object.
std::list< WebConn > fWebConn
!< configured display
std::list< std::shared_ptr< WebCommand > > fCmds
! list of submitted commands
uint64_t fCmdsCnt
! commands counter
uint64_t fSnapshotDelivered
! minimal version delivered to all connections
std::list< WebUpdate > fUpdatesLst
! list of callbacks for canvas update
void CancelCommands(unsigned connid=0)
Cancel command execution on provided connection All commands are cancelled, when connid === 0.
void SaveCreatedFile(std::string &reply)
Method called when GUI sends file to save on local disk File data coded with base64 coding beside SVG...
void CancelUpdates()
Cancel all pending Canvas::Update().
std::string GetWindowUrl(bool remote) final
Returns connection URL for web window.
bool ProduceBatchOutput(const std::string &fname, int width, int height) final
Produce batch output, using chrome headless mode with DOM dump.
std::shared_ptr< RDrawable > FindPrimitive(const RCanvas &can, const std::string &id, const RPadBase **subpad=nullptr)
Find drawable in the canvas with specified id Used to communicate with the clients,...
void DoWhenReady(const std::string &name, const std::string &arg, bool async, CanvasCallback_t callback) final
perform special action when drawing is ready
std::vector< Detail::RMenuItem > MenuItemsVector
std::shared_ptr< ROOT::RWebWindow > fWindow
std::string GetWindowAddr() const final
Returns web window name.
void Run(double tm=0.) final
Run canvas functionality for specified period of time Required when canvas used not from the main thr...
std::vector< std::unique_ptr< ROOT::RWebDisplayHandle > > fHelpHandles
! array of handles for help widgets
int NumDisplays() const final
Returns number of connected displays.
bool AddPanel(std::shared_ptr< ROOT::RWebWindow >) final
Add window as panel inside canvas window.
void FrontCommandReplied(const std::string &reply)
Process reply on the currently active command.
void ProcessData(unsigned connid, const std::string &arg)
Process data from the client.
int fJsonComp
! json compression for data send to client
void CanvasUpdated(uint64_t ver, bool async, CanvasCallback_t callback) final
Method invoked when canvas should be updated on the client side Depending from delivered status,...
void CheckDataToSend()
Check if canvas need to send data to the clients.
void CreateWindow()
Create web window for canvas.
std::string ProduceJSON() final
Produce JSON for the canvas.
bool IsCanvasModified(uint64_t id) const final
return true if canvas modified since last painting
RCanvasPainter & operator=(const RCanvasPainter &)=delete
Disable assignment.
std::string CreateSnapshot(RDrawable::RDisplayContext &ctxt)
Create JSON representation of data, which should be send to the clients Here server-side painting is ...
std::shared_ptr< ROOT::RWebWindow > GetWindow() final
Returns web window used for canvas display.
RCanvasPainter(const RCanvasPainter &)=delete
Disable copy construction.
~RCanvasPainter() override
Destructor.
void SetClearOnClose(const std::shared_ptr< void > &) final
Set handle to window which will be cleared when connection is closed.
void NewDisplay(const std::string &where) final
Create new display for the canvas See ROOT::RWebWindowsManager::Show() docu for more info.
RCanvas & fCanvas
! Canvas we are painting, *this will be owned by canvas
void SetConnection(unsigned connid, bool ismain)
Set connection id and ismain flag for connection.
Base class for graphic containers for RDrawable-s.
std::shared_ptr< RDrawable > FindPrimitiveByDisplayId(const std::string &display_id) const
Find primitive with unique id, produce for RDisplayItem Such id used for client-server identification...
const RPadBase * FindPadForPrimitiveWithDisplayId(const std::string &display_id) const
Find subpad which contains primitive with given display id.
A log configuration for a channel, e.g.
Holds different arguments for starting browser with RWebDisplayHandle::Display() method.
void SetStandalone(bool on=true)
Set standalone mode for running browser, default on When disabled, normal browser window (or just tab...
RWebDisplayArgs & SetWidgetKind(const std::string &kind)
set widget kind
RWebDisplayArgs & SetUrl(const std::string &url)
set window url
RWebDisplayArgs & SetWidth(int w=0)
set preferable web window width
RWebDisplayArgs & SetHeight(int h=0)
set preferable web window height
static bool ProduceImage(const std::string &fname, const std::string &json, int width=800, int height=600, const char *batch_file=nullptr)
Produce image file using JSON data as source Invokes JSROOT drawing functionality in headless browser...
static bool CanProduceImages(const std::string &browser="")
Returns true if image production for specified browser kind is supported If browser not specified - u...
static std::unique_ptr< RWebDisplayHandle > Display(const RWebDisplayArgs &args)
Create web display.
Represents web window, which can be shown in web browser or any other supported environment.
static std::shared_ptr< RWebWindow > Create()
Create new RWebWindow Using default RWebWindowsManager.
static bool EmbedFileDialog(const std::shared_ptr< RWebWindow > &window, unsigned connid, const std::string &args)
Create dialog instance to use as embedded dialog inside provided widget Loads libROOTBrowserv7 and tr...
static bool IsFileDialogMessage(const std::string &msg)
Check if this could be the message send by client to start new file dialog If returns true,...
static TString Decode(const char *data)
Decode a base64 string date into a generic TString.
Class for serializing object to and from JavaScript Object Notation (JSON) format.
static TString ToJSON(const T *obj, Int_t compact=0, const char *member_name=nullptr)
@ kNoSpaces
no new lines plus remove all spaces around "," and ":" symbols
static Bool_t FromJSON(T *&obj, const char *json)
void SetCompact(int level)
Set level of space/newline/array compression Lower digit of compact parameter define formatting rules...
static TClass * GetClass(const char *name, Bool_t load=kTRUE, Bool_t silent=kFALSE)
Static method returning pointer to TClass of the specified class name.
A file, usually with extension .root, that stores data and code in the form of serialized objects in ...
static TFile * Open(const char *name, Option_t *option="", const char *ftitle="", Int_t compress=ROOT::RCompressionSetting::EDefaults::kUseCompiledDefault, Int_t netopt=0)
Create / open a file.
const char * Data() const
static TString ULLtoa(ULong64_t value, Int_t base)
Converts a ULong64_t (twice the range of an Long64_t) to a TString with respect to the base specified...
TString & Append(const char *cs)
Namespace for ROOT features in testing.
std::function< void(bool)> CanvasCallback_t
unsigned fConnId
! connection id for the command, when 0 specified command will be submitted to any available connecti...
std::string fArg
! command arguments
bool fResult
! result of command execution
enum ROOT::Experimental::RCanvasPainter::WebCommand::@241142132062031213205143135006316151215046303277 sInit
! true when command submitted
std::string fName
! command name
CanvasCallback_t fCallback
! callback function associated with command
WebCommand(const std::string &id, const std::string &name, const std::string &arg, CanvasCallback_t callback, unsigned connid)
std::string fId
! command identifier
unsigned fConnId
! connection id
std::list< std::string > fSendQueue
! send queue for the connection
RDrawable::Version_t fDelivered
! indicates version confirmed from canvas
RDrawable::Version_t fSend
! indicates version send to connection
uint64_t fVersion
! canvas version
CanvasCallback_t fCallback
! callback function associated with the update
WebUpdate(uint64_t ver, CanvasCallback_t callback)