Logo ROOT  
Reference Guide
 
Loading...
Searching...
No Matches
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<ROOT::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<ROOT::RWebWindow>) final;
171
172 void SetClearOnClose(const std::shared_ptr<void> &) final;
173
174 /** \class CanvasPainterGenerator
175 Creates RCanvasPainter objects.
176 */
177
178 class GeneratorImpl : public Generator {
179 public:
180 /// Create a new RCanvasPainter to paint the given RCanvas.
181 std::unique_ptr<RVirtualCanvasPainter> Create(RCanvas &canv) const override
182 {
183 return std::make_unique<RCanvasPainter>(canv);
184 }
185 ~GeneratorImpl() = default;
186
187 /// Set RVirtualCanvasPainter::fgGenerator to a new GeneratorImpl object.
188 static void SetGlobalPainter()
189 {
190 if (GetGenerator()) {
191 R__LOG_ERROR(CanvasPainerLog()) << "Generator is already set! Skipping second initialization.";
192 return;
193 }
194 GetGenerator().reset(new GeneratorImpl());
195 }
196
197 /// Release the GeneratorImpl object.
198 static void ResetGlobalPainter() { GetGenerator().reset(); }
199 };
200};
201
202} // namespace Experimental
203} // namespace ROOT
204
209
210
211////////////////////////////////////////////////////////////////////////////////
212/// Constructor
213
214RCanvasPainter::RCanvasPainter(RCanvas &canv) : fCanvas(canv)
215{
216 auto comp = gEnv->GetValue("WebGui.JsonComp", -1);
217 if (comp >= 0) fJsonComp = comp;
218}
219
220////////////////////////////////////////////////////////////////////////////////
221/// Destructor
222
224{
227 if (fWindow)
228 fWindow->CloseConnections();
229}
230
231////////////////////////////////////////////////////////////////////////////////
232/// Cancel all pending Canvas::Update()
233
235{
237 for (auto &item: fUpdatesLst)
238 item.fCallback(false);
239 fUpdatesLst.clear();
240}
241
242////////////////////////////////////////////////////////////////////////////////
243/// Cancel command execution on provided connection
244/// All commands are cancelled, when connid === 0
245
247{
248 std::list<std::shared_ptr<WebCommand>> remainingCmds;
249
250 for (auto &&cmd : fCmds) {
251 if (!connid || (cmd->fConnId == connid)) {
252 cmd->CallBack(false);
253 cmd->fState = WebCommand::sReady;
254 } else {
255 remainingCmds.emplace_back(std::move(cmd));
256 }
257 }
258
259 std::swap(fCmds, remainingCmds);
260}
261
262////////////////////////////////////////////////////////////////////////////////
263/// Check if canvas need to send data to the clients
264
266{
267 uint64_t min_delivered = 0;
268 bool is_any_send = true;
269 int loopcnt = 0;
270
271 while (is_any_send && (++loopcnt < 10)) {
272
273 is_any_send = false;
274
275 for (auto &conn : fWebConn) {
276
277 if (conn.fDelivered && (!min_delivered || (min_delivered < conn.fDelivered)))
278 min_delivered = conn.fDelivered;
279
280 // flag indicates that next version of canvas has to be send to that client
281 bool need_send_snapshot = (conn.fSend != fCanvas.GetModified()) && (conn.fDelivered == conn.fSend);
282
283 // ensure place in the queue for the send snapshot operation
284 if (need_send_snapshot && (loopcnt == 0))
285 if (std::find(conn.fSendQueue.begin(), conn.fSendQueue.end(), ""s) == conn.fSendQueue.end())
286 conn.fSendQueue.emplace_back(""s);
287
288 // check if direct data sending is possible
289 if (!fWindow->CanSend(conn.fConnId, true))
290 continue;
291
292 TString buf;
293
294 if (conn.fDelivered && !fCmds.empty() && (fCmds.front()->fState == WebCommand::sInit) &&
295 ((fCmds.front()->fConnId == 0) || (fCmds.front()->fConnId == conn.fConnId))) {
296
297 auto &cmd = fCmds.front();
298 cmd->fState = WebCommand::sRunning;
299 cmd->fConnId = conn.fConnId; // assign command to the connection
300 buf = "CMD:";
301 buf.Append(cmd->fId);
302 buf.Append(":");
303 buf.Append(cmd->fName);
304
305 } else if (!conn.fSendQueue.empty()) {
306
307 buf = conn.fSendQueue.front().c_str();
308 conn.fSendQueue.pop_front();
309
310 // empty string reserved for sending snapshot, if it no longer required process next entry
311 if (!need_send_snapshot && (buf.Length() == 0) && !conn.fSendQueue.empty()) {
312 buf = conn.fSendQueue.front().c_str();
313 conn.fSendQueue.pop_front();
314 }
315 }
316
317 if ((buf.Length() == 0) && need_send_snapshot) {
318 buf = "SNAP:";
319 buf += TString::ULLtoa(fCanvas.GetModified(), 10);
320 buf += ":";
321
322 RDrawable::RDisplayContext ctxt(&fCanvas, &fCanvas, conn.fSend);
323 ctxt.SetConnection(conn.fConnId, (conn.fConnId == fWebConn.begin()->fConnId));
324
325 buf += CreateSnapshot(ctxt);
326
327 conn.fSend = fCanvas.GetModified();
328 }
329
330 if (buf.Length() > 0) {
331 // sending of data can be moved into separate thread - not to block user code
332 fWindow->Send(conn.fConnId, buf.Data());
333 is_any_send = true;
334 }
335 }
336 }
337
338 // if there are updates submitted, but all connections disappeared - cancel all updates
339 if (fWebConn.empty() && fSnapshotDelivered)
340 return CancelUpdates();
341
342 if (fSnapshotDelivered != min_delivered) {
343 fSnapshotDelivered = min_delivered;
344
345 if (fUpdatesLst.size() > 0)
346 fUpdatesLst.erase(std::remove_if(fUpdatesLst.begin(), fUpdatesLst.end(), [this](WebUpdate &item) {
347 if (item.fVersion > fSnapshotDelivered)
348 return false;
349 item.CallBack(true);
350 return true;
351 }));
352 }
353}
354
355////////////////////////////////////////////////////////////////////////////////
356/// Method invoked when canvas should be updated on the client side
357/// Depending from delivered status, each client will received new data
358
359void RCanvasPainter::CanvasUpdated(uint64_t ver, bool async, CanvasCallback_t callback)
360{
361 if (fWindow)
362 fWindow->Sync();
363
364 if (ver && fSnapshotDelivered && (ver <= fSnapshotDelivered)) {
365 // if given canvas version was already delivered to clients, can return immediately
366 if (callback)
367 callback(true);
368 return;
369 }
370
371 if (!fWindow || !fWindow->HasConnection(0, false)) {
372 if (callback)
373 callback(false);
374 return;
375 }
376
378
379 if (callback)
380 fUpdatesLst.emplace_back(ver, callback);
381
382 // wait that canvas is painted
383 if (!async) {
384 fWindow->WaitForTimed([this, ver](double) {
385
386 if (fSnapshotDelivered >= ver)
387 return 1;
388
389 // all connections are gone
390 if (fWebConn.empty() && !fWindow->HasConnection(0, false))
391 return -2;
392
393 // time is not important - timeout handle before
394 // if (tm > 100) return -3;
395
396 // continue waiting
397 return 0;
398 });
399 }
400}
401
402////////////////////////////////////////////////////////////////////////////////
403/// Perform special action when drawing is ready
404
405void RCanvasPainter::DoWhenReady(const std::string &name, const std::string &arg, bool async,
406 CanvasCallback_t callback)
407{
408 // ensure that window exists
409 CreateWindow();
410
411 unsigned connid = 0;
412
413 if (arg == "AddPanel") {
414 // take first connection to add panel
415 connid = fWindow->GetConnectionId();
416 } else {
417 // create batch job to execute action
418 // connid = fWindow->MakeBatch();
419 }
420
421 if (!connid) {
422 if (callback)
423 callback(false);
424 return;
425 }
426
427 auto cmd = std::make_shared<WebCommand>(std::to_string(++fCmdsCnt), name, arg, callback, connid);
428 fCmds.emplace_back(cmd);
429
431
432 if (async) return;
433
434 int res = fWindow->WaitForTimed([this, cmd](double) {
435 if (cmd->fState == WebCommand::sReady) {
436 R__LOG_DEBUG(0, CanvasPainerLog()) << "Command " << cmd->fName << " done";
437 return cmd->fResult ? 1 : -1;
438 }
439
440 // connection is gone
441 if (!fWindow->HasConnection(cmd->fConnId, false))
442 return -2;
443
444 // time is not important - timeout handle before
445 // if (tm > 100.) return -3;
446
447 return 0;
448 });
449
450 if (res <= 0)
451 R__LOG_ERROR(CanvasPainerLog()) << name << " fail with " << arg << " result = " << res;
452}
453
454
455////////////////////////////////////////////////////////////////////////////////
456/// Produce batch output, using chrome headless mode with DOM dump
457
458bool RCanvasPainter::ProduceBatchOutput(const std::string &fname, int width, int height)
459{
460 auto len = fname.length();
461 bool is_json = (len > 4) && ((fname.compare(len-4,4,".json") == 0) || (fname.compare(len-4,4,".JSON") == 0));
462
463 // do not try to produce image if current settings not allowing this
464 if (!is_json && !RWebDisplayHandle::CanProduceImages())
465 return false;
466
468 ctxt.SetConnection(1, true);
469
470 auto snapshot = CreateSnapshot(ctxt);
471
472 if (is_json) {
473 std::ofstream f(fname);
474 if (!f) {
475 R__LOG_ERROR(CanvasPainerLog()) << "Fail to open file " << fname << " to store canvas snapshot";
476 return false;
477 }
478 R__LOG_INFO(CanvasPainerLog()) << "Store canvas in " << fname;
479 f << snapshot;
480 return true;
481 }
482
483 return RWebDisplayHandle::ProduceImage(fname, snapshot, width, height);
484}
485
486////////////////////////////////////////////////////////////////////////////////
487/// Produce JSON for the canvas
488
490{
492 ctxt.SetConnection(1, true);
493
494 return CreateSnapshot(ctxt);
495}
496
497////////////////////////////////////////////////////////////////////////////////
498/// Process data from the client
499
500void RCanvasPainter::ProcessData(unsigned connid, const std::string &arg)
501{
502 auto conn =
503 std::find_if(fWebConn.begin(), fWebConn.end(), [connid](WebConn &item) { return item.fConnId == connid; });
504
505 if (conn == fWebConn.end())
506 return; // no connection found
507
508 std::string cdata;
509
510 auto check_header = [&arg, &cdata](const std::string &header) {
511 if (arg.compare(0, header.length(), header) != 0)
512 return false;
513 cdata = arg.substr(header.length());
514 return true;
515 };
516
517 // R__LOG_DEBUG(0, CanvasPainerLog()) << "from client " << connid << " got data len:" << arg.length() << " val:" <<
518 // arg.substr(0,30);
519
520 if (check_header("READY")) {
521
522 } else if (check_header("SNAPDONE:")) {
523 conn->fDelivered = (uint64_t)std::stoll(cdata); // delivered version of the snapshot
524 } else if (arg == "QUIT") {
525 // use window manager to correctly terminate http server and ROOT session
526 fWindow->TerminateROOT();
527 return;
528 } else if (arg == "START_BROWSER") {
529 gROOT->ProcessLine("auto br = std::make_shared<ROOT::RBrowser>();br->ClearOnClose(br);");
530
531 } else if (arg == "RELOAD") {
532 conn->fSend = 0; // reset send version, causes new data sending
533 } else if (arg == "INTERRUPT") {
534 gROOT->SetInterrupt();
535 } else if (check_header("REPLY:")) {
536 const char *sid = cdata.c_str();
537 const char *separ = strchr(sid, ':');
538 std::string id;
539 if (separ)
540 id.append(sid, separ - sid);
541 if (fCmds.empty()) {
542 R__LOG_ERROR(CanvasPainerLog()) << "Get REPLY without command";
543 } else if (fCmds.front()->fState != WebCommand::sRunning) {
544 R__LOG_ERROR(CanvasPainerLog()) << "Front command is not running when get reply";
545 } else if (fCmds.front()->fId != id) {
546 R__LOG_ERROR(CanvasPainerLog()) << "Mismatch with front command and ID in REPLY";
547 } else {
548 FrontCommandReplied(separ + 1);
549 }
550 } else if (check_header("SAVE:")) {
551 SaveCreatedFile(cdata);
552 } else if (check_header("PRODUCE:")) {
553 R__LOG_DEBUG(0, CanvasPainerLog()) << "Create file " << cdata;
554
555 TFile *f = TFile::Open(cdata.c_str(), "RECREATE");
556 f->WriteObject(&fCanvas, "Canvas");
557 delete f;
559
561
562 } else if (check_header("REQ:")) {
563 auto req = TBufferJSON::FromJSON<RDrawableRequest>(cdata);
564 if (req) {
565 std::shared_ptr<RDrawable> drawable;
566 req->GetContext().SetCanvas(&fCanvas);
567 if (req->GetId().empty() || (req->GetId() == "canvas")) {
568 req->GetContext().SetPad(nullptr); // no subpad for the canvas
569 req->GetContext().SetDrawable(&fCanvas, 0); // drawable is canvas itself
570 } else {
571 const RPadBase *subpad = nullptr;
572 drawable = FindPrimitive(fCanvas, req->GetId(), &subpad);
573 req->GetContext().SetPad(const_cast<RPadBase *>(subpad));
574 req->GetContext().SetDrawable(drawable.get(), 0);
575 }
576
577 req->GetContext().SetConnection(connid, conn == fWebConn.begin());
578
579 auto reply = req->Process();
580
581 if (req->ShouldBeReplyed()) {
582 if (!reply)
583 reply = std::make_unique<RDrawableReply>();
584
585 reply->SetRequestId(req->GetRequestId());
586
588 conn->fSendQueue.emplace_back("REPL_REQ:"s + json.Data());
589 }
590
591 // real update will be performed by CheckDataToSend()
592 if (req->NeedCanvasUpdate())
594
595 } else {
596 R__LOG_ERROR(CanvasPainerLog()) << "Fail to parse RDrawableRequest";
597 }
598 } else if (check_header("RESIZED:")) {
599 auto sz = TBufferJSON::FromJSON<std::vector<int>>(cdata);
600 if (sz && sz->size() == 2) {
601 fCanvas.SetWidth(sz->at(0));
602 fCanvas.SetHeight(sz->at(1));
603 }
604 } else if (check_header("CLEAR")) {
605 fCanvas.Wipe();
607 } else {
608 R__LOG_ERROR(CanvasPainerLog()) << "Got not recognized message" << arg;
609 }
610
612}
613
614////////////////////////////////////////////////////////////////////////////////
615/// Create web window for canvas
616
618{
619 if (fWindow) return;
620
622 fWindow->SetConnLimit(0); // allow any number of connections
623 fWindow->SetDefaultPage("file:rootui5sys/canv/canvas.html");
624 fWindow->SetCallBacks(
625 // connect
626 [this](unsigned connid) {
627 fWebConn.emplace_back(connid);
629 },
630 // data
631 [this](unsigned connid, const std::string &arg) { ProcessData(connid, arg); },
632 // disconnect
633 [this](unsigned connid) {
634 auto conn =
635 std::find_if(fWebConn.begin(), fWebConn.end(), [connid](WebConn &item) { return item.fConnId == connid; });
636
637 if (conn != fWebConn.end()) {
638 fWebConn.erase(conn);
639 CancelCommands(connid);
640 }
641 });
642 // fWindow->SetGeometry(500,300);
643}
644
645////////////////////////////////////////////////////////////////////////////////
646/// Create new display for the canvas
647/// See ROOT::RWebWindowsManager::Show() docu for more info
648
649void RCanvasPainter::NewDisplay(const std::string &where)
650{
651 CreateWindow();
652
653 int width = fCanvas.GetWidth();
654 int height = fCanvas.GetHeight();
655
656 RWebDisplayArgs args(where);
657
658 if ((width > 10) && (height > 10)) {
659 // extra size of browser window header + ui5 menu
660 args.SetWidth(width + 4);
661 args.SetHeight(height + 36);
662 }
663
664 args.SetWidgetKind("RCanvas");
665
666 fWindow->Show(args);
667}
668
669////////////////////////////////////////////////////////////////////////////////
670/// Returns number of connected displays
671
673{
674 if (!fWindow) return 0;
675
676 return fWindow->NumConnections();
677}
678
679////////////////////////////////////////////////////////////////////////////////
680/// Returns web window name
681
683{
684 if (!fWindow) return "";
685
686 return fWindow->GetAddr();
687}
688
689////////////////////////////////////////////////////////////////////////////////
690/// Add window as panel inside canvas window
691
692bool RCanvasPainter::AddPanel(std::shared_ptr<ROOT::RWebWindow> win)
693{
694 if (gROOT->IsWebDisplayBatch())
695 return false;
696
697 if (!fWindow) {
698 R__LOG_ERROR(CanvasPainerLog()) << "Canvas not yet shown in AddPanel";
699 return false;
700 }
701
702 if (!fWindow->IsShown()) {
703 R__LOG_ERROR(CanvasPainerLog()) << "Canvas window was not shown to call AddPanel";
704 return false;
705 }
706
707 std::string addr = fWindow->GetRelativeAddr(win);
708
709 if (addr.length() == 0) {
710 R__LOG_ERROR(CanvasPainerLog()) << "Cannot attach panel to canvas";
711 return false;
712 }
713
714 // connection is assigned, but can be refused by the client later
715 // therefore handle may be removed later
716
717 std::string cmd("ADDPANEL:");
718 cmd.append(addr);
719
720 /// one could use async mode
721 DoWhenReady(cmd, "AddPanel", true, nullptr);
722
723 return true;
724}
725
726////////////////////////////////////////////////////////////////////////////////
727/// Set handle to window which will be cleared when connection is closed
728
729void RCanvasPainter::SetClearOnClose(const std::shared_ptr<void> &handle)
730{
731 if (fWindow)
732 fWindow->SetClearOnClose(handle);
733}
734
735////////////////////////////////////////////////////////////////////////////////
736/// Create JSON representation of data, which should be send to the clients
737/// Here server-side painting is performed - each drawable adds own elements in
738/// so-called display list, which transferred to the clients
739
741{
742 auto canvitem = std::make_unique<RCanvasDisplayItem>();
743
744 fCanvas.DisplayPrimitives(*canvitem, ctxt);
745
746 canvitem->SetTitle(fCanvas.GetTitle());
747 canvitem->SetWindowSize(fCanvas.GetWidth(), fCanvas.GetHeight());
748
749 canvitem->BuildFullId(""); // create object id which unique identify it via pointer and position in subpads
750 canvitem->SetObjectID("canvas"); // for canvas itself use special id
751
754
755 static std::vector<const TClass *> exclude_classes = {
756 TClass::GetClass<RAttrMap::NoValue_t>(),
757 TClass::GetClass<RAttrMap::BoolValue_t>(),
758 TClass::GetClass<RAttrMap::IntValue_t>(),
759 TClass::GetClass<RAttrMap::DoubleValue_t>(),
760 TClass::GetClass<RAttrMap::StringValue_t>(),
761 TClass::GetClass<RAttrMap>(),
762 TClass::GetClass<RStyle::Block_t>(),
763 TClass::GetClass<RPadPos>(),
764 TClass::GetClass<RPadLength>(),
765 TClass::GetClass<RPadExtent>(),
766 TClass::GetClass<std::unordered_map<std::string,RAttrMap::Value_t*>>()
767 };
768
769 for (auto cl : exclude_classes)
770 json.SetSkipClassInfo(cl);
771
772 auto res = json.StoreObject(canvitem.get(), TClass::GetClass<RCanvasDisplayItem>());
773
774 return std::string(res.Data());
775}
776
777////////////////////////////////////////////////////////////////////////////////
778/// Find drawable in the canvas with specified id
779/// Used to communicate with the clients, which does not have any pointer
780
781std::shared_ptr<RDrawable>
782RCanvasPainter::FindPrimitive(const RCanvas &can, const std::string &id, const RPadBase **subpad)
783{
784 std::string search = id;
785 size_t pos = search.find("#");
786 // exclude extra specifier, later can be used for menu and commands execution
787 if (pos != std::string::npos)
788 search.resize(pos);
789
790 if (subpad) *subpad = can.FindPadForPrimitiveWithDisplayId(search);
791
792 return can.FindPrimitiveByDisplayId(search);
793}
794
795////////////////////////////////////////////////////////////////////////////////
796/// Method called when GUI sends file to save on local disk
797/// File data coded with base64 coding beside SVG format
798
799void RCanvasPainter::SaveCreatedFile(std::string &reply)
800{
801 size_t pos = reply.find(":");
802 if ((pos == std::string::npos) || (pos == 0)) {
803 R__LOG_ERROR(CanvasPainerLog()) << "SaveCreatedFile does not found ':' separator";
804 return;
805 }
806
807 std::string fname(reply, 0, pos);
808 reply.erase(0, pos + 1);
809
810 Bool_t isSvg = (fname.length() > 4) && ((fname.rfind(".svg") == fname.length()-4) || (fname.rfind(".SVG") == fname.length()-4));
811
812 int file_len = 0;
813
814 std::ofstream ofs(fname, std::ios::binary);
815 if (isSvg) {
816 ofs << reply;
817 file_len = reply.length();
818 } else {
819 TString binary = TBase64::Decode(reply.c_str());
820 ofs.write(binary.Data(), binary.Length());
821 file_len = binary.Length();
822 }
823 ofs.close();
824
825 R__LOG_INFO(CanvasPainerLog()) << " Save file from GUI " << fname << " len " << file_len;
826}
827
828////////////////////////////////////////////////////////////////////////////////
829/// Process reply on the currently active command
830
831void RCanvasPainter::FrontCommandReplied(const std::string &reply)
832{
833 auto cmd = fCmds.front();
834 fCmds.pop_front();
835
836 cmd->fState = WebCommand::sReady;
837
838 bool result = false;
839
840 if ((cmd->fName == "SVG") || (cmd->fName == "PNG") || (cmd->fName == "JPEG")) {
841 if (reply.length() == 0) {
842 R__LOG_ERROR(CanvasPainerLog()) << "Fail to produce image" << cmd->fArg;
843 } else {
844 TString content = TBase64::Decode(reply.c_str());
845 std::ofstream ofs(cmd->fArg, std::ios::binary);
846 ofs.write(content.Data(), content.Length());
847 ofs.close();
848 R__LOG_INFO(CanvasPainerLog()) << cmd->fName << " create file " << cmd->fArg << " length " << content.Length();
849 result = true;
850 }
851 } else if (cmd->fName.find("ADDPANEL:") == 0) {
852 R__LOG_DEBUG(0, CanvasPainerLog()) << "get reply for ADDPANEL " << reply;
853 result = (reply == "true");
854 } else {
855 R__LOG_ERROR(CanvasPainerLog()) << "Unknown command " << cmd->fName;
856 }
857
858 cmd->fResult = result;
859 cmd->CallBack(result);
860}
861
862////////////////////////////////////////////////////////////////////////////////
863/// Run canvas functionality for specified period of time
864/// Required when canvas used not from the main thread
865
866void RCanvasPainter::Run(double tm)
867{
868 if (fWindow) {
869 fWindow->Run(tm);
870 } else if (tm>0) {
871 std::this_thread::sleep_for(std::chrono::milliseconds(int(tm*1000)));
872 }
873}
nlohmann::json json
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:407
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
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()
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...
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 ...
RCanvasPainter(const RCanvasPainter &)=delete
Disable copy construction.
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
A window's topmost RPad.
Definition RCanvas.hxx:47
const std::string & GetTitle() const
Get the canvas's title.
Definition RCanvas.hxx:180
int GetHeight() const
Get canvas height.
Definition RCanvas.hxx:114
uint64_t GetModified() const
Get modify counter.
Definition RCanvas.hxx:143
void SetHeight(int height)
Set canvas height.
Definition RCanvas.hxx:108
void SetWidth(int width)
Set canvas width.
Definition RCanvas.hxx:105
int GetWidth() const
Get canvas width.
Definition RCanvas.hxx:111
void SetConnection(unsigned connid, bool ismain)
Set connection id and ismain flag for connection.
const std::string & GetId() const
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 & SetWidgetKind(const std::string &kind)
set widget kind
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...
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.
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
void SetCompact(int level)
Set level of space/newline/array compression Lower digit of compact parameter define formatting rules...
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 composed of a header, followed by consecutive data records (TKey instances) with a wel...
Definition TFile.h:53
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:4075
Basic string class.
Definition TString.h:139
Ssiz_t Length() const
Definition TString.h:421
const char * Data() const
Definition TString.h:380
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:2149
TString & Append(const char *cs)
Definition TString.h:576
std::function< void(bool)> CanvasCallback_t
This file contains a specialised ROOT message handler to test for diagnostic in unit tests.
unsigned fConnId
! connection id for the command, when 0 specified command will be submitted to any available connecti...
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)
enum ROOT::Experimental::RCanvasPainter::WebCommand::@64 sInit
! true when command submitted
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)