Logo ROOT  
Reference Guide
RCanvasPainter.cxx
Go to the documentation of this file.
1// Author: Axel Naumann <axel@cern.ch>
2// Date: 2017-05-31
3// Warning: This is part of the ROOT 7 prototype! It will change without notice. It might trigger earthquakes. Feedback is welcome!
4
5/*************************************************************************
6 * Copyright (C) 1995-2017, Rene Brun and Fons Rademakers. *
7 * All rights reserved. *
8 * *
9 * For the licensing terms see $ROOTSYS/LICENSE. *
10 * For the list of contributors see $ROOTSYS/README/CREDITS. *
11 *************************************************************************/
12
14#include "ROOT/RCanvas.hxx"
15#include <ROOT/RLogger.hxx>
16#include <ROOT/RDisplayItem.hxx>
18#include <ROOT/RMenuItems.hxx>
21#include <ROOT/RWebWindow.hxx>
22
23#include <memory>
24#include <string>
25#include <vector>
26#include <list>
27#include <thread>
28#include <chrono>
29#include <fstream>
30#include <algorithm>
31#include <cstdlib>
32#include <regex>
33
34#include "TList.h"
35#include "TEnv.h"
36#include "TROOT.h"
37#include "TFile.h"
38#include "TClass.h"
39#include "TBufferJSON.h"
40#include "TBase64.h"
41
42using namespace std::string_literals;
43using namespace ROOT::Experimental;
44
45namespace {
46RLogChannel &CanvasPainerLog() {
47 static RLogChannel sLog("ROOT.CanvasPainer");
48 return sLog;
49}
50}
51
52/** \class RCanvasPainter
53\ingroup webdisplay
54New implementation of canvas painter, using RWebWindow
55*/
56
57namespace ROOT {
58namespace Experimental {
59
61private:
62 struct WebConn {
63 unsigned fConnId{0}; ///<! connection id
64 std::list<std::string> fSendQueue; ///<! send queue for the connection
65 RDrawable::Version_t fSend{0}; ///<! indicates version send to connection
66 RDrawable::Version_t fDelivered{0}; ///<! indicates version confirmed from canvas
67 WebConn() = default;
68 WebConn(unsigned connid) : fConnId(connid) {}
69 };
70
71 struct WebCommand {
72 std::string fId; ///<! command identifier
73 std::string fName; ///<! command name
74 std::string fArg; ///<! command arguments
75 enum { sInit, sRunning, sReady } fState{sInit}; ///<! true when command submitted
76 bool fResult{false}; ///<! result of command execution
77 CanvasCallback_t fCallback{nullptr}; ///<! callback function associated with command
78 unsigned fConnId{0}; ///<! connection id for the command, when 0 specified command will be submitted to any available connection
79 WebCommand() = default;
80 WebCommand(const std::string &id, const std::string &name, const std::string &arg, CanvasCallback_t callback,
81 unsigned connid)
82 : fId(id), fName(name), fArg(arg), fCallback(callback), fConnId(connid)
83 {
84 }
85 void CallBack(bool res)
86 {
87 if (fCallback)
88 fCallback(res);
89 fCallback = nullptr;
90 }
91 };
92
93 struct WebUpdate {
94 uint64_t fVersion{0}; ///<! canvas version
95 CanvasCallback_t fCallback{nullptr}; ///<! callback function associated with the update
96 WebUpdate() = default;
97 WebUpdate(uint64_t ver, CanvasCallback_t callback) : fVersion(ver), fCallback(callback) {}
98 void CallBack(bool res)
99 {
100 if (fCallback)
101 fCallback(res);
102 fCallback = nullptr;
103 }
104 };
105
106 typedef std::vector<Detail::RMenuItem> MenuItemsVector;
107
108 RCanvas &fCanvas; ///<! Canvas we are painting, *this will be owned by canvas
109
110 std::shared_ptr<RWebWindow> fWindow; ///!< configured display
111
112 std::list<WebConn> fWebConn; ///<! connections list
113 std::list<std::shared_ptr<WebCommand>> fCmds; ///<! list of submitted commands
114 uint64_t fCmdsCnt{0}; ///<! commands counter
115
116 uint64_t fSnapshotDelivered{0}; ///<! minimal version delivered to all connections
117 std::list<WebUpdate> fUpdatesLst; ///<! list of callbacks for canvas update
118
119 int fJsonComp{23}; ///<! json compression for data send to client
120
121 /// Disable copy construction.
123
124 /// Disable assignment.
126
127 void CancelUpdates();
128
129 void CancelCommands(unsigned connid = 0);
130
131 void CheckDataToSend();
132
133 void ProcessData(unsigned connid, const std::string &arg);
134
136
137 std::shared_ptr<RDrawable> FindPrimitive(const RCanvas &can, const std::string &id, const RPadBase **subpad = nullptr);
138
139 void CreateWindow();
140
141 void SaveCreatedFile(std::string &reply);
142
143 void FrontCommandReplied(const std::string &reply);
144
145public:
146 RCanvasPainter(RCanvas &canv);
147
148 virtual ~RCanvasPainter();
149
150 void CanvasUpdated(uint64_t ver, bool async, CanvasCallback_t callback) final;
151
152 /// return true if canvas modified since last painting
153 bool IsCanvasModified(uint64_t id) const final { return fSnapshotDelivered != id; }
154
155 /// perform special action when drawing is ready
156 void DoWhenReady(const std::string &name, const std::string &arg, bool async, CanvasCallback_t callback) final;
157
158 bool ProduceBatchOutput(const std::string &fname, int width, int height) final;
159
160 std::string ProduceJSON() final;
161
162 void NewDisplay(const std::string &where) final;
163
164 int NumDisplays() const final;
165
166 std::string GetWindowAddr() const final;
167
168 void Run(double tm = 0.) final;
169
170 bool AddPanel(std::shared_ptr<RWebWindow>) final;
171
172 /** \class CanvasPainterGenerator
173 Creates RCanvasPainter objects.
174 */
175
176 class GeneratorImpl : public Generator {
177 public:
178 /// Create a new RCanvasPainter to paint the given RCanvas.
179 std::unique_ptr<RVirtualCanvasPainter> Create(RCanvas &canv) const override
180 {
181 return std::make_unique<RCanvasPainter>(canv);
182 }
183 ~GeneratorImpl() = default;
184
185 /// Set RVirtualCanvasPainter::fgGenerator to a new GeneratorImpl object.
186 static void SetGlobalPainter()
187 {
188 if (GetGenerator()) {
189 R__LOG_ERROR(CanvasPainerLog()) << "Generator is already set! Skipping second initialization.";
190 return;
191 }
192 GetGenerator().reset(new GeneratorImpl());
193 }
194
195 /// Release the GeneratorImpl object.
196 static void ResetGlobalPainter() { GetGenerator().reset(); }
197 };
198};
199
200} // namespace Experimental
201} // namespace ROOT
202
204 TNewCanvasPainterReg() { RCanvasPainter::GeneratorImpl::SetGlobalPainter(); }
205 ~TNewCanvasPainterReg() { RCanvasPainter::GeneratorImpl::ResetGlobalPainter(); }
207
208
209////////////////////////////////////////////////////////////////////////////////
210/// Constructor
211
212RCanvasPainter::RCanvasPainter(RCanvas &canv) : fCanvas(canv)
213{
214 auto comp = gEnv->GetValue("WebGui.JsonComp", -1);
215 if (comp >= 0) fJsonComp = comp;
216}
217
218////////////////////////////////////////////////////////////////////////////////
219/// Destructor
220
222{
225 if (fWindow)
226 fWindow->CloseConnections();
227}
228
229////////////////////////////////////////////////////////////////////////////////
230/// Cancel all pending Canvas::Update()
231
233{
235 for (auto &item: fUpdatesLst)
236 item.fCallback(false);
237 fUpdatesLst.clear();
238}
239
240////////////////////////////////////////////////////////////////////////////////
241/// Cancel command execution on provided connection
242/// All commands are cancelled, when connid === 0
243
245{
246 std::list<std::shared_ptr<WebCommand>> remainingCmds;
247
248 for (auto &&cmd : fCmds) {
249 if (!connid || (cmd->fConnId == connid)) {
250 cmd->CallBack(false);
251 cmd->fState = WebCommand::sReady;
252 } else {
253 remainingCmds.emplace_back(std::move(cmd));
254 }
255 }
256
257 std::swap(fCmds, remainingCmds);
258}
259
260////////////////////////////////////////////////////////////////////////////////
261/// Check if canvas need to send data to the clients
262
264{
265 uint64_t min_delivered = 0;
266 bool is_any_send = true;
267 int loopcnt = 0;
268
269 while (is_any_send && (++loopcnt < 10)) {
270
271 is_any_send = false;
272
273 for (auto &conn : fWebConn) {
274
275 if (conn.fDelivered && (!min_delivered || (min_delivered < conn.fDelivered)))
276 min_delivered = conn.fDelivered;
277
278 // flag indicates that next version of canvas has to be send to that client
279 bool need_send_snapshot = (conn.fSend != fCanvas.GetModified()) && (conn.fDelivered == conn.fSend);
280
281 // ensure place in the queue for the send snapshot operation
282 if (need_send_snapshot && (loopcnt == 0))
283 if (std::find(conn.fSendQueue.begin(), conn.fSendQueue.end(), ""s) == conn.fSendQueue.end())
284 conn.fSendQueue.emplace_back(""s);
285
286 // check if direct data sending is possible
287 if (!fWindow->CanSend(conn.fConnId, true))
288 continue;
289
290 TString buf;
291
292 if (conn.fDelivered && !fCmds.empty() && (fCmds.front()->fState == WebCommand::sInit) &&
293 ((fCmds.front()->fConnId == 0) || (fCmds.front()->fConnId == conn.fConnId))) {
294
295 auto &cmd = fCmds.front();
296 cmd->fState = WebCommand::sRunning;
297 cmd->fConnId = conn.fConnId; // assign command to the connection
298 buf = "CMD:";
299 buf.Append(cmd->fId);
300 buf.Append(":");
301 buf.Append(cmd->fName);
302
303 } else if (!conn.fSendQueue.empty()) {
304
305 buf = conn.fSendQueue.front().c_str();
306 conn.fSendQueue.pop_front();
307
308 // empty string reserved for sending snapshot, if it no longer required process next entry
309 if (!need_send_snapshot && (buf.Length() == 0) && !conn.fSendQueue.empty()) {
310 buf = conn.fSendQueue.front().c_str();
311 conn.fSendQueue.pop_front();
312 }
313 }
314
315 if ((buf.Length() == 0) && need_send_snapshot) {
316 buf = "SNAP:";
317 buf += TString::ULLtoa(fCanvas.GetModified(), 10);
318 buf += ":";
319
320 RDrawable::RDisplayContext ctxt(&fCanvas, &fCanvas, conn.fSend);
321 ctxt.SetConnection(conn.fConnId, (conn.fConnId == fWebConn.begin()->fConnId));
322
323 buf += CreateSnapshot(ctxt);
324
325 conn.fSend = fCanvas.GetModified();
326 }
327
328 if (buf.Length() > 0) {
329 // sending of data can be moved into separate thread - not to block user code
330 fWindow->Send(conn.fConnId, buf.Data());
331 is_any_send = true;
332 }
333 }
334 }
335
336 // if there are updates submitted, but all connections disappeared - cancel all updates
337 if (fWebConn.empty() && fSnapshotDelivered)
338 return CancelUpdates();
339
340 if (fSnapshotDelivered != min_delivered) {
341 fSnapshotDelivered = min_delivered;
342
343 if (fUpdatesLst.size() > 0)
344 fUpdatesLst.erase(std::remove_if(fUpdatesLst.begin(), fUpdatesLst.end(), [this](WebUpdate &item) {
345 if (item.fVersion > fSnapshotDelivered)
346 return false;
347 item.CallBack(true);
348 return true;
349 }));
350 }
351}
352
353////////////////////////////////////////////////////////////////////////////////
354/// Method invoked when canvas should be updated on the client side
355/// Depending from delivered status, each client will received new data
356
357void RCanvasPainter::CanvasUpdated(uint64_t ver, bool async, CanvasCallback_t callback)
358{
359 if (fWindow)
360 fWindow->Sync();
361
362 if (ver && fSnapshotDelivered && (ver <= fSnapshotDelivered)) {
363 // if given canvas version was already delivered to clients, can return immediately
364 if (callback)
365 callback(true);
366 return;
367 }
368
369 if (!fWindow || !fWindow->HasConnection(0, false)) {
370 if (callback)
371 callback(false);
372 return;
373 }
374
376
377 if (callback)
378 fUpdatesLst.emplace_back(ver, callback);
379
380 // wait that canvas is painted
381 if (!async) {
382 fWindow->WaitForTimed([this, ver](double) {
383
384 if (fSnapshotDelivered >= ver)
385 return 1;
386
387 // all connections are gone
388 if (fWebConn.empty() && !fWindow->HasConnection(0, false))
389 return -2;
390
391 // time is not important - timeout handle before
392 // if (tm > 100) return -3;
393
394 // continue waiting
395 return 0;
396 });
397 }
398}
399
400////////////////////////////////////////////////////////////////////////////////
401/// Perform special action when drawing is ready
402
403void RCanvasPainter::DoWhenReady(const std::string &name, const std::string &arg, bool async,
404 CanvasCallback_t callback)
405{
406 // ensure that window exists
407 CreateWindow();
408
409 unsigned connid = 0;
410
411 if (arg == "AddPanel") {
412 // take first connection to add panel
413 connid = fWindow->GetConnectionId();
414 } else {
415 // create batch job to execute action
416 // connid = fWindow->MakeBatch();
417 }
418
419 if (!connid) {
420 if (callback)
421 callback(false);
422 return;
423 }
424
425 auto cmd = std::make_shared<WebCommand>(std::to_string(++fCmdsCnt), name, arg, callback, connid);
426 fCmds.emplace_back(cmd);
427
429
430 if (async) return;
431
432 int res = fWindow->WaitForTimed([this, cmd](double) {
433 if (cmd->fState == WebCommand::sReady) {
434 R__LOG_DEBUG(0, CanvasPainerLog()) << "Command " << cmd->fName << " done";
435 return cmd->fResult ? 1 : -1;
436 }
437
438 // connection is gone
439 if (!fWindow->HasConnection(cmd->fConnId, false))
440 return -2;
441
442 // time is not important - timeout handle before
443 // if (tm > 100.) return -3;
444
445 return 0;
446 });
447
448 if (res <= 0)
449 R__LOG_ERROR(CanvasPainerLog()) << name << " fail with " << arg << " result = " << res;
450}
451
452
453////////////////////////////////////////////////////////////////////////////////
454/// Produce batch output, using chrome headless mode with DOM dump
455
456bool RCanvasPainter::ProduceBatchOutput(const std::string &fname, int width, int height)
457{
459 ctxt.SetConnection(1, true);
460
461 auto snapshot = CreateSnapshot(ctxt);
462
463 auto len = fname.length();
464 if ((len > 4) && ((fname.compare(len-4,4,".json") == 0) || (fname.compare(len-4,4,".JSON") == 0))) {
465 std::ofstream f(fname);
466 if (!f) {
467 R__LOG_ERROR(CanvasPainerLog()) << "Fail to open file " << fname << " to store canvas snapshot";
468 return false;
469 }
470 R__LOG_INFO(CanvasPainerLog()) << "Store canvas in " << fname;
471 f << snapshot;
472 return true;
473 }
474
475 return RWebDisplayHandle::ProduceImage(fname, snapshot, width, height);
476}
477
478////////////////////////////////////////////////////////////////////////////////
479/// Produce JSON for the canvas
480
482{
484 ctxt.SetConnection(1, true);
485
486 return CreateSnapshot(ctxt);
487}
488
489////////////////////////////////////////////////////////////////////////////////
490/// Process data from the client
491
492void RCanvasPainter::ProcessData(unsigned connid, const std::string &arg)
493{
494 auto conn =
495 std::find_if(fWebConn.begin(), fWebConn.end(), [connid](WebConn &item) { return item.fConnId == connid; });
496
497 if (conn == fWebConn.end())
498 return; // no connection found
499
500 std::string cdata;
501
502 auto check_header = [&arg, &cdata](const std::string &header) {
503 if (arg.compare(0, header.length(), header) != 0)
504 return false;
505 cdata = arg.substr(header.length());
506 return true;
507 };
508
509 // R__LOG_DEBUG(0, CanvasPainerLog()) << "from client " << connid << " got data len:" << arg.length() << " val:" <<
510 // arg.substr(0,30);
511
512 if (check_header("READY")) {
513
514 } else if (check_header("SNAPDONE:")) {
515 conn->fDelivered = (uint64_t)std::stoll(cdata); // delivered version of the snapshot
516 } else if (arg == "QUIT") {
517 // use window manager to correctly terminate http server and ROOT session
518 fWindow->TerminateROOT();
519 return;
520 } else if (arg == "RELOAD") {
521 conn->fSend = 0; // reset send version, causes new data sending
522 } else if (arg == "INTERRUPT") {
523 gROOT->SetInterrupt();
524 } else if (check_header("REPLY:")) {
525 const char *sid = cdata.c_str();
526 const char *separ = strchr(sid, ':');
527 std::string id;
528 if (separ)
529 id.append(sid, separ - sid);
530 if (fCmds.empty()) {
531 R__LOG_ERROR(CanvasPainerLog()) << "Get REPLY without command";
532 } else if (fCmds.front()->fState != WebCommand::sRunning) {
533 R__LOG_ERROR(CanvasPainerLog()) << "Front command is not running when get reply";
534 } else if (fCmds.front()->fId != id) {
535 R__LOG_ERROR(CanvasPainerLog()) << "Mismatch with front command and ID in REPLY";
536 } else {
537 FrontCommandReplied(separ + 1);
538 }
539 } else if (check_header("SAVE:")) {
540 SaveCreatedFile(cdata);
541 } else if (check_header("PRODUCE:")) {
542 R__LOG_DEBUG(0, CanvasPainerLog()) << "Create file " << cdata;
543
544 TFile *f = TFile::Open(cdata.c_str(), "RECREATE");
545 f->WriteObject(&fCanvas, "Canvas");
546 delete f;
547 } else if (check_header("REQ:")) {
548 auto req = TBufferJSON::FromJSON<RDrawableRequest>(cdata);
549 if (req) {
550 std::shared_ptr<RDrawable> drawable;
551 req->GetContext().SetCanvas(&fCanvas);
552 if (req->GetId().empty() || (req->GetId() == "canvas")) {
553 req->GetContext().SetPad(nullptr); // no subpad for the canvas
554 req->GetContext().SetDrawable(&fCanvas, 0); // drawable is canvas itself
555 } else {
556 const RPadBase *subpad = nullptr;
557 drawable = FindPrimitive(fCanvas, req->GetId(), &subpad);
558 req->GetContext().SetPad(const_cast<RPadBase *>(subpad));
559 req->GetContext().SetDrawable(drawable.get(), 0);
560 }
561
562 req->GetContext().SetConnection(connid, conn == fWebConn.begin());
563
564 auto reply = req->Process();
565
566 if (req->ShouldBeReplyed()) {
567 if (!reply)
568 reply = std::make_unique<RDrawableReply>();
569
570 reply->SetRequestId(req->GetRequestId());
571
573 conn->fSendQueue.emplace_back("REPL_REQ:"s + json.Data());
574 }
575
576 // real update will be performed by CheckDataToSend()
577 if (req->NeedCanvasUpdate())
579
580 } else {
581 R__LOG_ERROR(CanvasPainerLog()) << "Fail to parse RDrawableRequest";
582 }
583 } else if (check_header("CLEAR")) {
584
585 fCanvas.Wipe();
587 } else {
588 R__LOG_ERROR(CanvasPainerLog()) << "Got not recognized message" << arg;
589 }
590
592}
593
594////////////////////////////////////////////////////////////////////////////////
595/// Create web window for canvas
596
598{
599 if (fWindow) return;
600
602 fWindow->SetConnLimit(0); // allow any number of connections
603 fWindow->SetDefaultPage("file:rootui5sys/canv/canvas.html");
604 fWindow->SetCallBacks(
605 // connect
606 [this](unsigned connid) {
607 fWebConn.emplace_back(connid);
609 },
610 // data
611 [this](unsigned connid, const std::string &arg) { ProcessData(connid, arg); },
612 // disconnect
613 [this](unsigned connid) {
614 auto conn =
615 std::find_if(fWebConn.begin(), fWebConn.end(), [connid](WebConn &item) { return item.fConnId == connid; });
616
617 if (conn != fWebConn.end()) {
618 fWebConn.erase(conn);
619 CancelCommands(connid);
620 }
621 });
622 // fWindow->SetGeometry(500,300);
623}
624
625////////////////////////////////////////////////////////////////////////////////
626/// Create new display for the canvas
627/// See RWebWindowsManager::Show() docu for more info
628
629void RCanvasPainter::NewDisplay(const std::string &where)
630{
631 CreateWindow();
632
633 int width = fCanvas.GetWidth();
634 int height = fCanvas.GetHeight();
635
636 RWebDisplayArgs args(where);
637
638 if ((width > 10) && (height > 10)) {
639 // extra size of browser window header + ui5 menu
640 args.SetWidth(width + 4);
641 args.SetHeight(height + 36);
642 }
643
644 args.SetWidgetKind("RCanvas");
645
646 fWindow->Show(args);
647}
648
649////////////////////////////////////////////////////////////////////////////////
650/// Returns number of connected displays
651
653{
654 if (!fWindow) return 0;
655
656 return fWindow->NumConnections();
657}
658
659////////////////////////////////////////////////////////////////////////////////
660/// Returns web window name
661
663{
664 if (!fWindow) return "";
665
666 return fWindow->GetAddr();
667}
668
669////////////////////////////////////////////////////////////////////////////////
670/// Add window as panel inside canvas window
671
672bool RCanvasPainter::AddPanel(std::shared_ptr<RWebWindow> win)
673{
674 if (gROOT->IsWebDisplayBatch())
675 return false;
676
677 if (!fWindow) {
678 R__LOG_ERROR(CanvasPainerLog()) << "Canvas not yet shown in AddPanel";
679 return false;
680 }
681
682 if (!fWindow->IsShown()) {
683 R__LOG_ERROR(CanvasPainerLog()) << "Canvas window was not shown to call AddPanel";
684 return false;
685 }
686
687 std::string addr = fWindow->GetRelativeAddr(win);
688
689 if (addr.length() == 0) {
690 R__LOG_ERROR(CanvasPainerLog()) << "Cannot attach panel to canvas";
691 return false;
692 }
693
694 // connection is assigned, but can be refused by the client later
695 // therefore handle may be removed later
696
697 std::string cmd("ADDPANEL:");
698 cmd.append(addr);
699
700 /// one could use async mode
701 DoWhenReady(cmd, "AddPanel", true, nullptr);
702
703 return true;
704}
705
706////////////////////////////////////////////////////////////////////////////////
707/// Create JSON representation of data, which should be send to the clients
708/// Here server-side painting is performed - each drawable adds own elements in
709/// so-called display list, which transferred to the clients
710
712{
713 auto canvitem = std::make_unique<RCanvasDisplayItem>();
714
715 fCanvas.DisplayPrimitives(*canvitem, ctxt);
716
717 canvitem->SetTitle(fCanvas.GetTitle());
718 canvitem->SetWindowSize(fCanvas.GetWidth(), fCanvas.GetHeight());
719
720 canvitem->BuildFullId(""); // create object id which unique identify it via pointer and position in subpads
721 canvitem->SetObjectID("canvas"); // for canvas itself use special id
722
724 json.SetCompact(fJsonComp);
725
726 static std::vector<const TClass *> exclude_classes = {
727 TClass::GetClass<RAttrMap::NoValue_t>(),
728 TClass::GetClass<RAttrMap::BoolValue_t>(),
729 TClass::GetClass<RAttrMap::IntValue_t>(),
730 TClass::GetClass<RAttrMap::DoubleValue_t>(),
731 TClass::GetClass<RAttrMap::StringValue_t>(),
732 TClass::GetClass<RAttrMap>(),
733 TClass::GetClass<RStyle::Block_t>(),
734 TClass::GetClass<RPadPos>(),
735 TClass::GetClass<RPadLength>(),
736 TClass::GetClass<RPadExtent>(),
737 TClass::GetClass<std::unordered_map<std::string,RAttrMap::Value_t*>>()
738 };
739
740 for (auto cl : exclude_classes)
741 json.SetSkipClassInfo(cl);
742
743 auto res = json.StoreObject(canvitem.get(), TClass::GetClass<RCanvasDisplayItem>());
744
745 return std::string(res.Data());
746}
747
748////////////////////////////////////////////////////////////////////////////////
749/// Find drawable in the canvas with specified id
750/// Used to communicate with the clients, which does not have any pointer
751
752std::shared_ptr<RDrawable>
753RCanvasPainter::FindPrimitive(const RCanvas &can, const std::string &id, const RPadBase **subpad)
754{
755 std::string search = id;
756 size_t pos = search.find("#");
757 // exclude extra specifier, later can be used for menu and commands execution
758 if (pos != std::string::npos)
759 search.resize(pos);
760
761 if (subpad) *subpad = can.FindPadForPrimitiveWithDisplayId(search);
762
763 return can.FindPrimitiveByDisplayId(search);
764}
765
766////////////////////////////////////////////////////////////////////////////////
767/// Method called when GUI sends file to save on local disk
768/// File data coded with base64 coding beside SVG format
769
770void RCanvasPainter::SaveCreatedFile(std::string &reply)
771{
772 size_t pos = reply.find(":");
773 if ((pos == std::string::npos) || (pos == 0)) {
774 R__LOG_ERROR(CanvasPainerLog()) << "SaveCreatedFile does not found ':' separator";
775 return;
776 }
777
778 std::string fname(reply, 0, pos);
779 reply.erase(0, pos + 1);
780
781 Bool_t isSvg = (fname.length() > 4) && ((fname.rfind(".svg") == fname.length()-4) || (fname.rfind(".SVG") == fname.length()-4));
782
783 int file_len = 0;
784
785 std::ofstream ofs(fname, std::ios::binary);
786 if (isSvg) {
787 ofs << reply;
788 file_len = reply.length();
789 } else {
790 TString binary = TBase64::Decode(reply.c_str());
791 ofs.write(binary.Data(), binary.Length());
792 file_len = binary.Length();
793 }
794 ofs.close();
795
796 R__LOG_INFO(CanvasPainerLog()) << " Save file from GUI " << fname << " len " << file_len;
797}
798
799////////////////////////////////////////////////////////////////////////////////
800/// Process reply on the currently active command
801
802void RCanvasPainter::FrontCommandReplied(const std::string &reply)
803{
804 auto cmd = fCmds.front();
805 fCmds.pop_front();
806
807 cmd->fState = WebCommand::sReady;
808
809 bool result = false;
810
811 if ((cmd->fName == "SVG") || (cmd->fName == "PNG") || (cmd->fName == "JPEG")) {
812 if (reply.length() == 0) {
813 R__LOG_ERROR(CanvasPainerLog()) << "Fail to produce image" << cmd->fArg;
814 } else {
815 TString content = TBase64::Decode(reply.c_str());
816 std::ofstream ofs(cmd->fArg, std::ios::binary);
817 ofs.write(content.Data(), content.Length());
818 ofs.close();
819 R__LOG_INFO(CanvasPainerLog()) << cmd->fName << " create file " << cmd->fArg << " length " << content.Length();
820 result = true;
821 }
822 } else if (cmd->fName.find("ADDPANEL:") == 0) {
823 R__LOG_DEBUG(0, CanvasPainerLog()) << "get reply for ADDPANEL " << reply;
824 result = (reply == "true");
825 } else {
826 R__LOG_ERROR(CanvasPainerLog()) << "Unknown command " << cmd->fName;
827 }
828
829 cmd->fResult = result;
830 cmd->CallBack(result);
831}
832
833////////////////////////////////////////////////////////////////////////////////
834/// Run canvas functionality for specified period of time
835/// Required when canvas used not from the main thread
836
837void RCanvasPainter::Run(double tm)
838{
839 if (fWindow) {
840 fWindow->Run(tm);
841 } else if (tm>0) {
842 std::this_thread::sleep_for(std::chrono::milliseconds(int(tm*1000)));
843 }
844}
struct TNewCanvasPainterReg newCanvasPainterReg
#define R__LOG_ERROR(...)
Definition: RLogger.hxx:362
#define R__LOG_DEBUG(DEBUGLEVEL,...)
Definition: RLogger.hxx:365
#define R__LOG_INFO(...)
Definition: RLogger.hxx:364
#define f(i)
Definition: RSha256.hxx:104
R__EXTERN TEnv * gEnv
Definition: TEnv.h:170
Option_t Option_t TPoint TPoint const char GetTextMagnitude GetFillStyle GetLineColor GetLineWidth GetMarkerStyle GetTextAlign GetTextColor GetTextSize void char Point_t Rectangle_t WindowAttributes_t Float_t Float_t Float_t Int_t Int_t UInt_t UInt_t Rectangle_t result
Option_t Option_t TPoint TPoint const char GetTextMagnitude GetFillStyle GetLineColor GetLineWidth GetMarkerStyle GetTextAlign GetTextColor GetTextSize id
Option_t Option_t TPoint TPoint const char GetTextMagnitude GetFillStyle GetLineColor GetLineWidth GetMarkerStyle GetTextAlign GetTextColor GetTextSize void char Point_t Rectangle_t WindowAttributes_t Float_t Float_t Float_t Int_t Int_t UInt_t UInt_t Rectangle_t Int_t Int_t Window_t TString Int_t GCValues_t GetPrimarySelectionOwner GetDisplay GetScreen GetColormap GetNativeEvent const char const char dpyName wid window const char font_name cursor keysym reg const char only_if_exist regb h Point_t winding char text const char depth char const char Int_t count const char ColorStruct_t color const char Pixmap_t Pixmap_t PictureAttributes_t attr const char char ret_data h unsigned char height h Atom_t Int_t ULong_t ULong_t unsigned char prop_list Atom_t Atom_t Atom_t Time_t UChar_t len
Option_t Option_t TPoint TPoint const char GetTextMagnitude GetFillStyle GetLineColor GetLineWidth GetMarkerStyle GetTextAlign GetTextColor GetTextSize void char Point_t Rectangle_t WindowAttributes_t Float_t Float_t Float_t Int_t Int_t UInt_t UInt_t Rectangle_t Int_t Int_t Window_t win
Option_t Option_t width
Option_t Option_t TPoint TPoint const char GetTextMagnitude GetFillStyle GetLineColor GetLineWidth GetMarkerStyle GetTextAlign GetTextColor GetTextSize void char Point_t Rectangle_t height
char name[80]
Definition: TGX11.cxx:110
#define gROOT
Definition: TROOT.h:404
Abstract interface for painting a canvas.
static std::unique_ptr< Generator > & GetGenerator()
generator getter
static void ResetGlobalPainter()
Release the GeneratorImpl object.
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
bool AddPanel(std::shared_ptr< RWebWindow >) final
Add window as panel inside canvas window.
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...
std::shared_ptr< RWebWindow > fWindow
void CancelUpdates()
Cancel all pending Canvas::Update()
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::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...
int NumDisplays() const final
Returns number of connected displays.
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 ...
RCanvasPainter(const RCanvasPainter &)=delete
Disable copy construction.
void NewDisplay(const std::string &where) final
Create new display for the canvas See RWebWindowsManager::Show() docu for more info.
RCanvas & fCanvas
! Canvas we are painting, *this will be owned by canvas
A window's topmost RPad.
Definition: RCanvas.hxx:47
const std::string & GetTitle() const
Get the canvas's title.
Definition: RCanvas.hxx:166
int GetHeight() const
Get canvas height.
Definition: RCanvas.hxx:111
uint64_t GetModified() const
Get modify counter.
Definition: RCanvas.hxx:137
int GetWidth() const
Get canvas width.
Definition: RCanvas.hxx:108
void SetConnection(unsigned connid, bool ismain)
Set connection id and ismain flag for connection.
Definition: RDrawable.hxx:154
const std::string & GetId() const
Definition: RDrawable.hxx:221
A log configuration for a channel, e.g.
Definition: RLogger.hxx:101
Base class for graphic containers for RDrawable-s.
Definition: RPadBase.hxx:37
void DisplayPrimitives(RPadBaseDisplayItem &paditem, RDisplayContext &ctxt)
Create display items for all primitives in the pad Each display item gets its special id,...
Definition: RPadBase.cxx:112
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...
Definition: RPadBase.cxx:64
const RPadBase * FindPadForPrimitiveWithDisplayId(const std::string &display_id) const
Find subpad which contains primitive with given display id.
Definition: RPadBase.cxx:87
void Wipe()
Wipe the pad by clearing the list of primitives.
Definition: RPadBase.hxx:190
Holds different arguments for starting browser with RWebDisplayHandle::Display() method.
RWebDisplayArgs & SetHeight(int h=0)
set preferable web window height
RWebDisplayArgs & SetWidgetKind(const std::string &kind)
set widget kind
RWebDisplayArgs & SetWidth(int w=0)
set preferable web window width
static bool ProduceImage(const std::string &fname, const std::string &json, int width=800, int height=600)
Produce image file using JSON data as source Invokes JSROOT drawing functionality in headless browser...
Represents web window, which can be shown in web browser or any other supported environment.
Definition: RWebWindow.hxx:53
static std::shared_ptr< RWebWindow > Create()
Create new RWebWindow Using default RWebWindowsManager.
static TString Decode(const char *data)
Decode a base64 string date into a generic TString.
Definition: TBase64.cxx:131
Class for serializing object to and from JavaScript Object Notation (JSON) format.
Definition: TBufferJSON.h:30
static TString ToJSON(const T *obj, Int_t compact=0, const char *member_name=nullptr)
Definition: TBufferJSON.h:75
@ kNoSpaces
no new lines plus remove all spaces around "," and ":" symbols
Definition: TBufferJSON.h:39
virtual Int_t GetValue(const char *name, Int_t dflt) const
Returns the integer value for a resource.
Definition: TEnv.cxx:491
A ROOT file is a suite of consecutive data records (TKey instances) with a well defined format.
Definition: TFile.h:54
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.
Definition: TFile.cxx:4053
Basic string class.
Definition: TString.h:136
Ssiz_t Length() const
Definition: TString.h:410
const char * Data() const
Definition: TString.h:369
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...
Definition: TString.cxx:2138
TString & Append(const char *cs)
Definition: TString.h:564
void swap(RDirectoryEntry &e1, RDirectoryEntry &e2) noexcept
std::function< void(bool)> CanvasCallback_t
This file contains a specialised ROOT message handler to test for diagnostic in unit tests.
static constexpr double s
basic_json<> json
Definition: REveElement.hxx:62
unsigned fConnId
! connection id for the command, when 0 specified command will be submitted to any available connecti...
enum ROOT::Experimental::RCanvasPainter::WebCommand::@65 sInit
! true when command submitted
bool fResult
! result of command execution
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::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
CanvasCallback_t fCallback
! callback function associated with the update
WebUpdate(uint64_t ver, CanvasCallback_t callback)