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