Logo ROOT  
Reference Guide
 
All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Friends Macros Modules Pages
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 {
47{
48 static ROOT::RLogChannel sLog("ROOT.CanvasPainer");
49 return sLog;
50}
51}
52
53/** \class ROOT::Experimental::RCanvasPainter
54\ingroup webwidgets
55
56\brief Implementation of painter for ROOT::Experimental::RCanvas, using RWebWindow
57*/
58
59namespace ROOT {
60namespace Experimental {
61
63private:
64 struct WebConn {
65 unsigned fConnId{0}; ///<! connection id
66 std::list<std::string> fSendQueue; ///<! send queue for the connection
67 RDrawable::Version_t fSend{0}; ///<! indicates version send to connection
68 RDrawable::Version_t fDelivered{0}; ///<! indicates version confirmed from canvas
69 WebConn() = default;
70 WebConn(unsigned connid) : fConnId(connid) {}
71 };
72
73 struct WebCommand {
74 std::string fId; ///<! command identifier
75 std::string fName; ///<! command name
76 std::string fArg; ///<! command arguments
77 enum { sInit, sRunning, sReady } fState{sInit}; ///<! true when command submitted
78 bool fResult{false}; ///<! result of command execution
79 CanvasCallback_t fCallback{nullptr}; ///<! callback function associated with command
80 unsigned fConnId{0}; ///<! connection id for the command, when 0 specified command will be submitted to any available connection
81 WebCommand() = default;
82 WebCommand(const std::string &id, const std::string &name, const std::string &arg, CanvasCallback_t callback,
83 unsigned connid)
84 : fId(id), fName(name), fArg(arg), fCallback(callback), fConnId(connid)
85 {
86 }
87 void CallBack(bool res)
88 {
89 if (fCallback)
90 fCallback(res);
91 fCallback = nullptr;
92 }
93 };
94
95 struct WebUpdate {
96 uint64_t fVersion{0}; ///<! canvas version
97 CanvasCallback_t fCallback{nullptr}; ///<! callback function associated with the update
98 WebUpdate() = default;
99 WebUpdate(uint64_t ver, CanvasCallback_t callback) : fVersion(ver), fCallback(callback) {}
100 void CallBack(bool res)
101 {
102 if (fCallback)
103 fCallback(res);
104 fCallback = nullptr;
105 }
106 };
107
108 typedef std::vector<Detail::RMenuItem> MenuItemsVector;
109
110 RCanvas &fCanvas; ///<! Canvas we are painting, *this will be owned by canvas
111
112 std::shared_ptr<ROOT::RWebWindow> fWindow; ///!< configured display
113
114 std::list<WebConn> fWebConn; ///<! connections list
115 std::list<std::shared_ptr<WebCommand>> fCmds; ///<! list of submitted commands
116 uint64_t fCmdsCnt{0}; ///<! commands counter
117
118 uint64_t fSnapshotDelivered{0}; ///<! minimal version delivered to all connections
119 std::list<WebUpdate> fUpdatesLst; ///<! list of callbacks for canvas update
120
121 int fJsonComp{23}; ///<! json compression for data send to client
122
123 std::vector<std::unique_ptr<ROOT::RWebDisplayHandle>> fHelpHandles; ///<! array of handles for help widgets
124
125 /// Disable copy construction.
127
128 /// Disable assignment.
130
131 void CancelUpdates();
132
133 void CancelCommands(unsigned connid = 0);
134
135 void CheckDataToSend();
136
137 void ProcessData(unsigned connid, const std::string &arg);
138
140
141 std::shared_ptr<RDrawable> FindPrimitive(const RCanvas &can, const std::string &id, const RPadBase **subpad = nullptr);
142
143 void CreateWindow();
144
145 void SaveCreatedFile(std::string &reply);
146
147 void FrontCommandReplied(const std::string &reply);
148
149public:
151
152 ~RCanvasPainter() override;
153
154 void CanvasUpdated(uint64_t ver, bool async, CanvasCallback_t callback) final;
155
156 /// return true if canvas modified since last painting
157 bool IsCanvasModified(uint64_t id) const final { return fSnapshotDelivered != id; }
158
159 /// perform special action when drawing is ready
160 void DoWhenReady(const std::string &name, const std::string &arg, bool async, CanvasCallback_t callback) final;
161
162 bool ProduceBatchOutput(const std::string &fname, int width, int height) final;
163
164 std::string ProduceJSON() final;
165
166 void NewDisplay(const std::string &where) final;
167
168 int NumDisplays() const final;
169
170 std::shared_ptr<ROOT::RWebWindow> GetWindow() final;
171
172 std::string GetWindowAddr() const final;
173
174 std::string GetWindowUrl(bool remote) final;
175
176 void Run(double tm = 0.) final;
177
178 bool AddPanel(std::shared_ptr<ROOT::RWebWindow>) final;
179
180 void SetClearOnClose(const std::shared_ptr<void> &) final;
181
182 /** \class CanvasPainterGenerator
183 Creates RCanvasPainter objects.
184 */
185
187 public:
188 /// Create a new RCanvasPainter to paint the given RCanvas.
189 std::unique_ptr<RVirtualCanvasPainter> Create(RCanvas &canv) const override
190 {
191 return std::make_unique<RCanvasPainter>(canv);
192 }
193 ~GeneratorImpl() override = default;
194
195 /// Set RVirtualCanvasPainter::fgGenerator to a new GeneratorImpl object.
196 static void SetGlobalPainter()
197 {
198 if (GetGenerator()) {
199 R__LOG_ERROR(CanvasPainerLog()) << "Generator is already set! Skipping second initialization.";
200 return;
201 }
202 GetGenerator().reset(new GeneratorImpl());
203 }
204
205 /// Release the GeneratorImpl object.
206 static void ResetGlobalPainter() { GetGenerator().reset(); }
207 };
208};
209
210} // namespace Experimental
211} // namespace ROOT
212
217
218
219////////////////////////////////////////////////////////////////////////////////
220/// Constructor
221
223{
224 auto comp = gEnv->GetValue("WebGui.JsonComp", -1);
225 if (comp >= 0) fJsonComp = comp;
226}
227
228////////////////////////////////////////////////////////////////////////////////
229/// Destructor
230
232{
235 if (fWindow)
236 fWindow->Reset();
237}
238
239////////////////////////////////////////////////////////////////////////////////
240/// Cancel all pending Canvas::Update()
241
243{
245 for (auto &item: fUpdatesLst)
246 item.fCallback(false);
247 fUpdatesLst.clear();
248}
249
250////////////////////////////////////////////////////////////////////////////////
251/// Cancel command execution on provided connection
252/// All commands are cancelled, when connid === 0
253
255{
256 std::list<std::shared_ptr<WebCommand>> remainingCmds;
257
258 for (auto &&cmd : fCmds) {
259 if (!connid || (cmd->fConnId == connid)) {
260 cmd->CallBack(false);
261 cmd->fState = WebCommand::sReady;
262 } else {
263 remainingCmds.emplace_back(std::move(cmd));
264 }
265 }
266
267 std::swap(fCmds, remainingCmds);
268}
269
270////////////////////////////////////////////////////////////////////////////////
271/// Check if canvas need to send data to the clients
272
274{
275 uint64_t min_delivered = 0;
276 bool is_any_send = true;
277 int loopcnt = 0;
278
279 while (is_any_send && (++loopcnt < 10)) {
280
281 is_any_send = false;
282
283 for (auto &conn : fWebConn) {
284
285 if (conn.fDelivered && (!min_delivered || (min_delivered < conn.fDelivered)))
286 min_delivered = conn.fDelivered;
287
288 // flag indicates that next version of canvas has to be send to that client
289 bool need_send_snapshot = (conn.fSend != fCanvas.GetModified()) && (conn.fDelivered == conn.fSend);
290
291 // ensure place in the queue for the send snapshot operation
292 if (need_send_snapshot && (loopcnt == 0))
293 if (std::find(conn.fSendQueue.begin(), conn.fSendQueue.end(), ""s) == conn.fSendQueue.end())
294 conn.fSendQueue.emplace_back(""s);
295
296 // check if direct data sending is possible
297 if (!fWindow->CanSend(conn.fConnId, true))
298 continue;
299
300 TString buf;
301
302 if (conn.fDelivered && !fCmds.empty() && (fCmds.front()->fState == WebCommand::sInit) &&
303 ((fCmds.front()->fConnId == 0) || (fCmds.front()->fConnId == conn.fConnId))) {
304
305 auto &cmd = fCmds.front();
306 cmd->fState = WebCommand::sRunning;
307 cmd->fConnId = conn.fConnId; // assign command to the connection
308 buf = "CMD:";
309 buf.Append(cmd->fId);
310 buf.Append(":");
311 buf.Append(cmd->fName);
312
313 } else if (!conn.fSendQueue.empty()) {
314
315 buf = conn.fSendQueue.front().c_str();
316 conn.fSendQueue.pop_front();
317
318 // empty string reserved for sending snapshot, if it no longer required process next entry
319 if (!need_send_snapshot && (buf.Length() == 0) && !conn.fSendQueue.empty()) {
320 buf = conn.fSendQueue.front().c_str();
321 conn.fSendQueue.pop_front();
322 }
323 }
324
325 if ((buf.Length() == 0) && need_send_snapshot) {
326 buf = "SNAP:";
327 buf += TString::ULLtoa(fCanvas.GetModified(), 10);
328 buf += ":";
329
331 ctxt.SetConnection(conn.fConnId, (conn.fConnId == fWebConn.begin()->fConnId));
332
333 buf += CreateSnapshot(ctxt);
334
335 conn.fSend = fCanvas.GetModified();
336 }
337
338 if (buf.Length() > 0) {
339 // sending of data can be moved into separate thread - not to block user code
340 fWindow->Send(conn.fConnId, buf.Data());
341 is_any_send = true;
342 }
343 }
344 }
345
346 // if there are updates submitted, but all connections disappeared - cancel all updates
347 if (fWebConn.empty() && fSnapshotDelivered)
348 return CancelUpdates();
349
352
353 if (fUpdatesLst.size() > 0)
354 fUpdatesLst.erase(std::remove_if(fUpdatesLst.begin(), fUpdatesLst.end(), [this](WebUpdate &item) {
355 if (item.fVersion > fSnapshotDelivered)
356 return false;
357 item.CallBack(true);
358 return true;
359 }));
360 }
361}
362
363////////////////////////////////////////////////////////////////////////////////
364/// Method invoked when canvas should be updated on the client side
365/// Depending from delivered status, each client will received new data
366
368{
369 if (fWindow)
370 fWindow->Sync();
371
373 // if given canvas version was already delivered to clients, can return immediately
374 if (callback)
375 callback(true);
376 return;
377 }
378
379 if (!fWindow || !fWindow->HasConnection(0, false)) {
380 if (callback)
381 callback(false);
382 return;
383 }
384
386
387 if (callback)
388 fUpdatesLst.emplace_back(ver, callback);
389
390 // wait that canvas is painted
391 if (!async) {
392 fWindow->WaitForTimed([this, ver](double) {
393
394 if (fSnapshotDelivered >= ver)
395 return 1;
396
397 // all connections are gone
398 if (fWebConn.empty() && !fWindow->HasConnection(0, false))
399 return -2;
400
401 // time is not important - timeout handle before
402 // if (tm > 100) return -3;
403
404 // continue waiting
405 return 0;
406 });
407 }
408}
409
410////////////////////////////////////////////////////////////////////////////////
411/// Perform special action when drawing is ready
412
413void RCanvasPainter::DoWhenReady(const std::string &name, const std::string &arg, bool async,
414 CanvasCallback_t callback)
415{
416 // ensure that window exists
417 CreateWindow();
418
419 unsigned connid = 0;
420
421 if (arg == "AddPanel") {
422 // take first connection to add panel
423 connid = fWindow->GetConnectionId();
424 } else {
425 // create batch job to execute action
426 // connid = fWindow->MakeBatch();
427 }
428
429 if (!connid) {
430 if (callback)
431 callback(false);
432 return;
433 }
434
435 auto cmd = std::make_shared<WebCommand>(std::to_string(++fCmdsCnt), name, arg, callback, connid);
436 fCmds.emplace_back(cmd);
437
439
440 if (async) return;
441
442 int res = fWindow->WaitForTimed([this, cmd](double) {
443 if (cmd->fState == WebCommand::sReady) {
444 R__LOG_DEBUG(0, CanvasPainerLog()) << "Command " << cmd->fName << " done";
445 return cmd->fResult ? 1 : -1;
446 }
447
448 // connection is gone
449 if (!fWindow->HasConnection(cmd->fConnId, false))
450 return -2;
451
452 // time is not important - timeout handle before
453 // if (tm > 100.) return -3;
454
455 return 0;
456 });
457
458 if (res <= 0)
459 R__LOG_ERROR(CanvasPainerLog()) << name << " fail with " << arg << " result = " << res;
460}
461
462
463////////////////////////////////////////////////////////////////////////////////
464/// Produce batch output, using chrome headless mode with DOM dump
465
466bool RCanvasPainter::ProduceBatchOutput(const std::string &fname, int width, int height)
467{
468 auto len = fname.length();
469 bool is_json = (len > 4) && ((fname.compare(len-4,4,".json") == 0) || (fname.compare(len-4,4,".JSON") == 0));
470
471 // do not try to produce image if current settings not allowing this
473 return false;
474
476 ctxt.SetConnection(1, true);
477
478 auto snapshot = CreateSnapshot(ctxt);
479
480 if (is_json) {
481 std::ofstream f(fname);
482 if (!f) {
483 R__LOG_ERROR(CanvasPainerLog()) << "Fail to open file " << fname << " to store canvas snapshot";
484 return false;
485 }
486 R__LOG_INFO(CanvasPainerLog()) << "Store canvas in " << fname;
487 f << snapshot;
488 return true;
489 }
490
492}
493
494////////////////////////////////////////////////////////////////////////////////
495/// Produce JSON for the canvas
496
498{
500 ctxt.SetConnection(1, true);
501
502 return CreateSnapshot(ctxt);
503}
504
505////////////////////////////////////////////////////////////////////////////////
506/// Process data from the client
507
508void RCanvasPainter::ProcessData(unsigned connid, const std::string &arg)
509{
510 auto conn =
511 std::find_if(fWebConn.begin(), fWebConn.end(), [connid](WebConn &item) { return item.fConnId == connid; });
512
513 if (conn == fWebConn.end())
514 return; // no connection found
515
516 std::string cdata;
517
518 auto check_header = [&arg, &cdata](const std::string &header) {
519 if (arg.compare(0, header.length(), header) != 0)
520 return false;
521 cdata = arg.substr(header.length());
522 return true;
523 };
524
525 // R__LOG_DEBUG(0, CanvasPainerLog()) << "from client " << connid << " got data len:" << arg.length() << " val:" <<
526 // arg.substr(0,30);
527
528 if (check_header("READY")) {
529
530 } else if (check_header("SNAPDONE:")) {
531 conn->fDelivered = (uint64_t)std::stoll(cdata); // delivered version of the snapshot
532 } else if (arg == "QUIT") {
533 // use window manager to correctly terminate http server and ROOT session
534 fWindow->TerminateROOT();
535 return;
536 } else if (arg == "START_BROWSER") {
537 gROOT->ProcessLine("auto br = std::make_shared<ROOT::RBrowser>();br->ClearOnClose(br);");
538
539 } else if (arg == "RELOAD") {
540 conn->fSend = 0; // reset send version, causes new data sending
541 } else if (arg == "INTERRUPT") {
542 gROOT->SetInterrupt();
543 } else if (check_header("REPLY:")) {
544 const char *sid = cdata.c_str();
545 const char *separ = strchr(sid, ':');
546 std::string id;
547 if (separ)
548 id.append(sid, separ - sid);
549 if (fCmds.empty()) {
550 R__LOG_ERROR(CanvasPainerLog()) << "Get REPLY without command";
551 } else if (fCmds.front()->fState != WebCommand::sRunning) {
552 R__LOG_ERROR(CanvasPainerLog()) << "Front command is not running when get reply";
553 } else if (fCmds.front()->fId != id) {
554 R__LOG_ERROR(CanvasPainerLog()) << "Mismatch with front command and ID in REPLY";
555 } else {
557 }
558 } else if (check_header("SAVE:")) {
560 } else if (check_header("PRODUCE:")) {
561 R__LOG_DEBUG(0, CanvasPainerLog()) << "Create file " << cdata;
562
563 TFile *f = TFile::Open(cdata.c_str(), "RECREATE");
564 f->WriteObject(&fCanvas, "Canvas");
565 delete f;
567
569
570 } else if (check_header("REQ:")) {
571 auto req = TBufferJSON::FromJSON<RDrawableRequest>(cdata);
572 if (req) {
573 std::shared_ptr<RDrawable> drawable;
574 req->GetContext().SetCanvas(&fCanvas);
575 if (req->GetId().empty() || (req->GetId() == "canvas")) {
576 req->GetContext().SetPad(nullptr); // no subpad for the canvas
577 req->GetContext().SetDrawable(&fCanvas, 0); // drawable is canvas itself
578 } else {
579 const RPadBase *subpad = nullptr;
580 drawable = FindPrimitive(fCanvas, req->GetId(), &subpad);
581 req->GetContext().SetPad(const_cast<RPadBase *>(subpad));
582 req->GetContext().SetDrawable(drawable.get(), 0);
583 }
584
585 req->GetContext().SetConnection(connid, conn == fWebConn.begin());
586
587 auto reply = req->Process();
588
589 if (req->ShouldBeReplyed()) {
590 if (!reply)
591 reply = std::make_unique<RDrawableReply>();
592
593 reply->SetRequestId(req->GetRequestId());
594
596 conn->fSendQueue.emplace_back("REPL_REQ:"s + json.Data());
597 }
598
599 // real update will be performed by CheckDataToSend()
600 if (req->NeedCanvasUpdate())
602
603 } else {
604 R__LOG_ERROR(CanvasPainerLog()) << "Fail to parse RDrawableRequest";
605 }
606 } else if (check_header("RESIZED:")) {
607 auto sz = TBufferJSON::FromJSON<std::vector<int>>(cdata);
608 if (sz && sz->size() == 2) {
609 fCanvas.SetWidth(sz->at(0));
610 fCanvas.SetHeight(sz->at(1));
611 }
612 } else if (check_header("CLEAR")) {
613 fCanvas.Wipe();
615 } else if (check_header("SHOWURL:")) {
617 args.SetUrl(cdata);
618 args.SetStandalone(false);
620 } else {
621 R__LOG_ERROR(CanvasPainerLog()) << "Got not recognized message" << arg;
622 }
623
625}
626
627////////////////////////////////////////////////////////////////////////////////
628/// Create web window for canvas
629
631{
632 if (fWindow) return;
633
635 fWindow->SetConnLimit(0); // allow any number of connections
636 fWindow->SetDefaultPage("file:rootui5sys/canv/canvas.html");
637 fWindow->SetCallBacks(
638 // connect
639 [this](unsigned connid) {
640 fWebConn.emplace_back(connid);
642 },
643 // data
644 [this](unsigned connid, const std::string &arg) { ProcessData(connid, arg); },
645 // disconnect
646 [this](unsigned connid) {
647 auto conn =
648 std::find_if(fWebConn.begin(), fWebConn.end(), [connid](WebConn &item) { return item.fConnId == connid; });
649
650 if (conn != fWebConn.end()) {
651 fWebConn.erase(conn);
652 CancelCommands(connid);
653 }
654 });
655 // fWindow->SetGeometry(500,300);
656}
657
658////////////////////////////////////////////////////////////////////////////////
659/// Create new display for the canvas
660/// See ROOT::RWebWindowsManager::Show() docu for more info
661
662void RCanvasPainter::NewDisplay(const std::string &where)
663{
664 CreateWindow();
665
666 int width = fCanvas.GetWidth();
667 int height = fCanvas.GetHeight();
668
670
671 if ((width > 10) && (height > 10)) {
672 // extra size of browser window header + ui5 menu
673 args.SetWidth(width + 4);
674 args.SetHeight(height + 36);
675 }
676
677 args.SetWidgetKind("RCanvas");
678
679 fWindow->Show(args);
680}
681
682////////////////////////////////////////////////////////////////////////////////
683/// Returns web window used for canvas display
684
685std::shared_ptr<ROOT::RWebWindow> RCanvasPainter::GetWindow()
686{
687 return fWindow;
688}
689
690
691////////////////////////////////////////////////////////////////////////////////
692/// Returns number of connected displays
693
695{
696 if (!fWindow) return 0;
697
698 return fWindow->NumConnections();
699}
700
701////////////////////////////////////////////////////////////////////////////////
702/// Returns web window name
703
705{
706 if (!fWindow) return "";
707
708 return fWindow->GetAddr();
709}
710
711////////////////////////////////////////////////////////////////////////////////
712/// Returns connection URL for web window
713
715{
716 if (!fWindow) return "";
717
718 return fWindow->GetUrl(remote);
719}
720
721
722////////////////////////////////////////////////////////////////////////////////
723/// Add window as panel inside canvas window
724
725bool RCanvasPainter::AddPanel(std::shared_ptr<ROOT::RWebWindow> win)
726{
727 if (gROOT->IsWebDisplayBatch())
728 return false;
729
730 if (!fWindow) {
731 R__LOG_ERROR(CanvasPainerLog()) << "Canvas not yet shown in AddPanel";
732 return false;
733 }
734
735 if (!fWindow->IsShown()) {
736 R__LOG_ERROR(CanvasPainerLog()) << "Canvas window was not shown to call AddPanel";
737 return false;
738 }
739
740 if (win->GetManager() != fWindow->GetManager()) {
741 R__LOG_ERROR(CanvasPainerLog()) << "Cannot embed window from other windows manager";
742 return false;
743 }
744
745 // request URL only as for local connection - without full server
746 std::string addr = win->GetUrl(false);
747
748 if (addr.empty()) {
749 R__LOG_ERROR(CanvasPainerLog()) << "Cannot attach panel to canvas";
750 return false;
751 }
752
753 // connection is assigned, but can be refused by the client later
754 // therefore handle may be removed later
755
756 std::string cmd("ADDPANEL:");
757 cmd.append("..");
758 cmd.append(addr);
759
760 /// one could use async mode
761 DoWhenReady(cmd, "AddPanel", true, nullptr);
762
763 return true;
764}
765
766////////////////////////////////////////////////////////////////////////////////
767/// Set handle to window which will be cleared when connection is closed
768
769void RCanvasPainter::SetClearOnClose(const std::shared_ptr<void> &handle)
770{
771 if (fWindow)
772 fWindow->SetClearOnClose(handle);
773}
774
775////////////////////////////////////////////////////////////////////////////////
776/// Create JSON representation of data, which should be send to the clients
777/// Here server-side painting is performed - each drawable adds own elements in
778/// so-called display list, which transferred to the clients
779
781{
782 auto canvitem = std::make_unique<RCanvasDisplayItem>();
783
785
786 canvitem->SetTitle(fCanvas.GetTitle());
787 canvitem->SetWindowSize(fCanvas.GetWidth(), fCanvas.GetHeight());
788
789 canvitem->BuildFullId(""); // create object id which unique identify it via pointer and position in subpads
790 canvitem->SetObjectID("canvas"); // for canvas itself use special id
791
794
795 static std::vector<const TClass *> exclude_classes = {
796 TClass::GetClass<RAttrMap::NoValue_t>(),
797 TClass::GetClass<RAttrMap::BoolValue_t>(),
798 TClass::GetClass<RAttrMap::IntValue_t>(),
799 TClass::GetClass<RAttrMap::DoubleValue_t>(),
800 TClass::GetClass<RAttrMap::StringValue_t>(),
801 TClass::GetClass<RAttrMap>(),
802 TClass::GetClass<RStyle::Block_t>(),
803 TClass::GetClass<RPadPos>(),
804 TClass::GetClass<RPadLength>(),
805 TClass::GetClass<RPadExtent>(),
806 TClass::GetClass<std::unordered_map<std::string,RAttrMap::Value_t*>>()
807 };
808
809 for (auto cl : exclude_classes)
810 json.SetSkipClassInfo(cl);
811
812 auto res = json.StoreObject(canvitem.get(), TClass::GetClass<RCanvasDisplayItem>());
813
814 return std::string(res.Data());
815}
816
817////////////////////////////////////////////////////////////////////////////////
818/// Find drawable in the canvas with specified id
819/// Used to communicate with the clients, which does not have any pointer
820
821std::shared_ptr<RDrawable>
822RCanvasPainter::FindPrimitive(const RCanvas &can, const std::string &id, const RPadBase **subpad)
823{
824 std::string search = id;
825 size_t pos = search.find("#");
826 // exclude extra specifier, later can be used for menu and commands execution
827 if (pos != std::string::npos)
828 search.resize(pos);
829
830 if (subpad) *subpad = can.FindPadForPrimitiveWithDisplayId(search);
831
832 return can.FindPrimitiveByDisplayId(search);
833}
834
835////////////////////////////////////////////////////////////////////////////////
836/// Method called when GUI sends file to save on local disk
837/// File data coded with base64 coding beside SVG format
838
840{
841 size_t pos = reply.find(":");
842 if ((pos == std::string::npos) || (pos == 0)) {
843 R__LOG_ERROR(CanvasPainerLog()) << "SaveCreatedFile does not found ':' separator";
844 return;
845 }
846
847 std::string fname(reply, 0, pos);
848 reply.erase(0, pos + 1);
849
850 Bool_t isSvg = (fname.length() > 4) && ((fname.rfind(".svg") == fname.length()-4) || (fname.rfind(".SVG") == fname.length()-4));
851
852 int file_len = 0;
853
854 std::ofstream ofs(fname, std::ios::binary);
855 if (isSvg) {
856 ofs << reply;
857 file_len = reply.length();
858 } else {
860 ofs.write(binary.Data(), binary.Length());
861 file_len = binary.Length();
862 }
863 ofs.close();
864
865 R__LOG_INFO(CanvasPainerLog()) << " Save file from GUI " << fname << " len " << file_len;
866}
867
868////////////////////////////////////////////////////////////////////////////////
869/// Process reply on the currently active command
870
872{
873 auto cmd = fCmds.front();
874 fCmds.pop_front();
875
876 cmd->fState = WebCommand::sReady;
877
878 bool result = false;
879
880 if ((cmd->fName == "SVG") || (cmd->fName == "PNG") || (cmd->fName == "JPEG")) {
881 if (reply.length() == 0) {
882 R__LOG_ERROR(CanvasPainerLog()) << "Fail to produce image" << cmd->fArg;
883 } else {
885 std::ofstream ofs(cmd->fArg, std::ios::binary);
886 ofs.write(content.Data(), content.Length());
887 ofs.close();
888 R__LOG_INFO(CanvasPainerLog()) << cmd->fName << " create file " << cmd->fArg << " length " << content.Length();
889 result = true;
890 }
891 } else if (cmd->fName.find("ADDPANEL:") == 0) {
892 R__LOG_DEBUG(0, CanvasPainerLog()) << "get reply for ADDPANEL " << reply;
893 result = (reply == "true");
894 } else {
895 R__LOG_ERROR(CanvasPainerLog()) << "Unknown command " << cmd->fName;
896 }
897
898 cmd->fResult = result;
899 cmd->CallBack(result);
900}
901
902////////////////////////////////////////////////////////////////////////////////
903/// Run canvas functionality for specified period of time
904/// Required when canvas used not from the main thread
905
907{
908 if (fWindow) {
909 fWindow->Run(tm);
910 } else if (tm>0) {
911 std::this_thread::sleep_for(std::chrono::milliseconds(int(tm*1000)));
912 }
913}
nlohmann::json json
struct TNewCanvasPainterReg newCanvasPainterReg
#define R__LOG_ERROR(...)
Definition RLogger.hxx:357
#define R__LOG_DEBUG(DEBUGLEVEL,...)
Definition RLogger.hxx:360
#define R__LOG_INFO(...)
Definition RLogger.hxx:359
#define f(i)
Definition RSha256.hxx:104
ROOT::Detail::TRangeCast< T, true > TRangeDynCast
TRangeDynCast is an adapter class that allows the typed iteration through a TCollection.
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:406
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.
Implementation of painter for ROOT::Experimental::RCanvas, using RWebWindow.
std::list< WebConn > fWebConn
!< configured display
std::list< std::shared_ptr< WebCommand > > fCmds
! list of submitted commands
uint64_t fCmdsCnt
! commands counter
uint64_t fSnapshotDelivered
! minimal version delivered to all connections
std::list< WebUpdate > fUpdatesLst
! list of callbacks for canvas update
void CancelCommands(unsigned connid=0)
Cancel command execution on provided connection All commands are cancelled, when connid === 0.
void SaveCreatedFile(std::string &reply)
Method called when GUI sends file to save on local disk File data coded with base64 coding beside SVG...
void CancelUpdates()
Cancel all pending Canvas::Update()
std::string GetWindowUrl(bool remote) final
Returns connection URL for web window.
bool ProduceBatchOutput(const std::string &fname, int width, int height) final
Produce batch output, using chrome headless mode with DOM dump.
std::shared_ptr< RDrawable > FindPrimitive(const RCanvas &can, const std::string &id, const RPadBase **subpad=nullptr)
Find drawable in the canvas with specified id Used to communicate with the clients,...
void DoWhenReady(const std::string &name, const std::string &arg, bool async, CanvasCallback_t callback) final
perform special action when drawing is ready
std::vector< Detail::RMenuItem > MenuItemsVector
std::shared_ptr< ROOT::RWebWindow > fWindow
std::string GetWindowAddr() const final
Returns web window name.
void Run(double tm=0.) final
Run canvas functionality for specified period of time Required when canvas used not from the main thr...
std::vector< std::unique_ptr< ROOT::RWebDisplayHandle > > fHelpHandles
! array of handles for help widgets
int NumDisplays() const final
Returns number of connected displays.
bool AddPanel(std::shared_ptr< ROOT::RWebWindow >) final
Add window as panel inside canvas window.
void FrontCommandReplied(const std::string &reply)
Process reply on the currently active command.
void ProcessData(unsigned connid, const std::string &arg)
Process data from the client.
int fJsonComp
! json compression for data send to client
void CanvasUpdated(uint64_t ver, bool async, CanvasCallback_t callback) final
Method invoked when canvas should be updated on the client side Depending from delivered status,...
void CheckDataToSend()
Check if canvas need to send data to the clients.
void CreateWindow()
Create web window for canvas.
std::string ProduceJSON() final
Produce JSON for the canvas.
bool IsCanvasModified(uint64_t id) const final
return true if canvas modified since last painting
RCanvasPainter & operator=(const RCanvasPainter &)=delete
Disable assignment.
std::string CreateSnapshot(RDrawable::RDisplayContext &ctxt)
Create JSON representation of data, which should be send to the clients Here server-side painting is ...
std::shared_ptr< ROOT::RWebWindow > GetWindow() final
Returns web window used for canvas display.
RCanvasPainter(const RCanvasPainter &)=delete
Disable copy construction.
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:187
int GetHeight() const
Get canvas height.
Definition RCanvas.hxx:114
uint64_t GetModified() const
Get modify counter.
Definition RCanvas.hxx:150
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
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
void Wipe()
Wipe the pad by clearing the list of primitives.
Definition RPadBase.hxx:190
A log configuration for a channel, e.g.
Definition RLogger.hxx:98
Holds different arguments for starting browser with RWebDisplayHandle::Display() method.
void SetStandalone(bool on=true)
Set standalone mode for running browser, default on When disabled, normal browser window (or just tab...
RWebDisplayArgs & SetWidgetKind(const std::string &kind)
set widget kind
RWebDisplayArgs & SetUrl(const std::string &url)
set window url
RWebDisplayArgs & SetWidth(int w=0)
set preferable web window width
RWebDisplayArgs & SetHeight(int h=0)
set preferable web window height
static bool ProduceImage(const std::string &fname, const std::string &json, int width=800, int height=600, const char *batch_file=nullptr)
Produce image file using JSON data as source Invokes JSROOT drawing functionality in headless browser...
static bool CanProduceImages(const std::string &browser="")
Returns true if image production for specified browser kind is supported If browser not specified - u...
static std::unique_ptr< RWebDisplayHandle > Display(const RWebDisplayArgs &args)
Create web display.
Represents web window, which can be shown in web browser or any other supported environment.
static std::shared_ptr< RWebWindow > Create()
Create new RWebWindow Using default RWebWindowsManager.
static bool EmbedFileDialog(const std::shared_ptr< RWebWindow > &window, unsigned connid, const std::string &args)
Create dialog instance to use as embedded dialog inside provided widget Loads libROOTBrowserv7 and tr...
static bool IsFileDialogMessage(const std::string &msg)
Check if this could be the message send by client to start new file dialog If returns true,...
static TString Decode(const char *data)
Decode a base64 string date into a generic TString.
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:131
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:4131
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)