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