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