Logo ROOT  
Reference Guide
 
Loading...
Searching...
No Matches
RFileDialog.cxx
Go to the documentation of this file.
1// Author: Sergey Linev <S.Linev@gsi.de>
2// Date: 2019-10-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-2019, 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
13#include <ROOT/RFileDialog.hxx>
14
17
18#include <ROOT/RLogger.hxx>
19
20#include "TBufferJSON.h"
21
22#include <sstream>
23#include <iostream>
24#include <algorithm>
25#include <memory>
26#include <mutex>
27#include <thread>
28#include <fstream>
29
30using namespace ROOT;
31using namespace std::string_literals;
32
33/** \class ROOT::RFileDialog
34\ingroup rbrowser
35\ingroup webwidgets
36
37\brief Web-based FileDialog.
38*/
39
40//////////////////////////////////////////////////////////////////////////////////////////////
41/// constructor
42/// When title not specified, default will be used
43
44RFileDialog::RFileDialog(EDialogTypes kind, const std::string &title, const std::string &fname)
45{
46 fKind = kind;
47 fTitle = title;
48
49 if (fTitle.empty())
50 switch (fKind) {
51 case kOpenFile: fTitle = "Open file"; break;
52 case kSaveAs: fTitle = "Save as file"; break;
53 case kNewFile: fTitle = "New file"; break;
54 }
55
56 fSelect = fname;
57
58 auto separ = fSelect.rfind("/");
59 if (separ == std::string::npos)
60 separ = fSelect.rfind("\\");
61
62 std::string workdir;
63
64 if (separ != std::string::npos) {
65 workdir = fSelect.substr(0, separ);
66 fSelect = fSelect.substr(separ+1);
67 }
68
69 auto comp = std::make_shared<Browsable::RGroup>("top", "Top file dialog element");
70 auto workpath = Browsable::RSysFile::ProvideTopEntries(comp, workdir);
72 fBrowsable.SetWorkingPath(workpath);
73
75
76 // when dialog used in standalone mode, ui5 panel will be loaded
77 fWebWindow->SetPanelName("rootui5.browser.view.FileDialog");
78
79 // this is call-back, invoked when message received via websocket
80 fWebWindow->SetCallBacks([this](unsigned connid) { SendInitMsg(connid); },
81 [this](unsigned connid, const std::string &arg) { ProcessMsg(connid, arg); },
82 [this](unsigned) { InvokeCallBack(); });
83 fWebWindow->SetGeometry(800, 600); // configure predefined window geometry
84 fWebWindow->SetConnLimit(1); // the only connection is allowed
85 fWebWindow->SetMaxQueueLength(30); // number of allowed entries in the window queue
86}
87
88//////////////////////////////////////////////////////////////////////////////////////////////
89/// destructor
90
92{
93 InvokeCallBack(); // invoke callback if not yet performed
94 R__LOG_DEBUG(0, BrowserLog()) << "RFileDialog destructor";
95}
96
97//////////////////////////////////////////////////////////////////////////////////////////////
98/// Assign callback.
99/// Argument of callback is selected file name.
100/// If file was already selected, immediately call it
101
103{
104 fCallback = callback;
105 if (fDidSelect)
107}
108
109/////////////////////////////////////////////////////////////////////////////////
110/// Show or update RFileDialog in web window
111/// If web window already started - just refresh it like "reload" button does
112/// Reset result of file selection (if any)
113
115{
116 fDidSelect = false;
117
118 if (fWebWindow->NumConnections() == 0) {
120 } else {
121 SendInitMsg(0);
122 }
123}
124
125///////////////////////////////////////////////////////////////////////////////////////////////////////
126/// Hide ROOT Browser
127
129{
130 fWebWindow->CloseConnections();
131}
132
133///////////////////////////////////////////////////////////////////////////////////////////////////////
134/// Returns dialog type as string
135/// String value used for configuring JS-side
136
138{
139 switch(kind) {
140 case kOpenFile: return "OpenFile"s;
141 case kSaveAs: return "SaveAs"s;
142 case kNewFile: return "NewFile"s;
143 }
144
145 return ""s;
146}
147
148//////////////////////////////////////////////////////////////////////////////////////////////
149/// Configure selected filter
150/// Has to be one of the string from NameFilters entry
151
152void RFileDialog::SetSelectedFilter(const std::string &name)
153{
155}
156
157//////////////////////////////////////////////////////////////////////////////////////////////
158/// Returns selected filter
159/// Can differ from specified value - if it does not match to existing entry in NameFilters
160
162{
163 if (fNameFilters.size() == 0)
164 return fSelectedFilter;
165
166 std::string lastname, allname;
167
168 for (auto &entry : fNameFilters) {
169 auto pp = entry.find(" (");
170 if (pp == std::string::npos) continue;
171 auto name = entry.substr(0, pp);
172
173 if (name == fSelectedFilter)
174 return name;
175
176 if (allname.empty() && GetRegexp(name).empty())
177 allname = name;
178
179 lastname = name;
180 }
181
182 if (!allname.empty()) return allname;
183 if (!lastname.empty()) return lastname;
184
185 return ""s;
186}
187
188//////////////////////////////////////////////////////////////////////////////////////////////
189/// Returns regexp for selected filter
190/// String should have form "Filter name (*.ext1 *.ext2 ...)
191
192std::string RFileDialog::GetRegexp(const std::string &fname) const
193{
194 if (!fname.empty())
195 for (auto &entry : fNameFilters) {
196 if (entry.compare(0, fname.length(), fname) == 0) {
197 auto pp = entry.find("(", fname.length());
198
199 std::string res;
200
201 while (pp != std::string::npos) {
202 pp = entry.find("*.", pp);
203 if (pp == std::string::npos) break;
204
205 auto pp2 = entry.find_first_of(" )", pp+2);
206 if (pp2 == std::string::npos) break;
207
208 if (res.empty()) res = "^(.*\\.(";
209 else res.append("|");
210
211 res.append(entry.substr(pp+2, pp2 - pp - 2));
212
213 pp = pp2;
214 }
215
216 if (!res.empty()) res.append(")$)");
217
218 return res;
219
220 }
221 }
222
223 return ""s;
224}
225
226
227//////////////////////////////////////////////////////////////////////////////////////////////
228/// Sends initial message to the client
229
230void RFileDialog::SendInitMsg(unsigned connid)
231{
232 auto filter = GetSelectedFilter();
233 RBrowserRequest req;
234 req.sort = "alphabetical";
235 req.regex = GetRegexp(filter);
236
237 auto jtitle = TBufferJSON::ToJSON(&fTitle);
239 auto jfname = TBufferJSON::ToJSON(&fSelect);
240 auto jfilters = TBufferJSON::ToJSON(&fNameFilters);
241 auto jfilter = TBufferJSON::ToJSON(&filter);
242
243 fWebWindow->Send(connid, "INMSG:{\"kind\" : \""s + TypeAsString(fKind) + "\", "s +
244 "\"title\" : "s + jtitle.Data() + ","s +
245 "\"path\" : "s + jpath.Data() + ","s +
246 "\"can_change_path\" : "s + (GetCanChangePath() ? "true"s : "false"s) + ","s +
247 "\"filter\" : "s + jfilter.Data() + ","s +
248 "\"filters\" : "s + jfilters.Data() + ","s +
249 "\"fname\" : "s + jfname.Data() + ","s +
250 "\"brepl\" : "s + fBrowsable.ProcessRequest(req) + " }"s);
251}
252
253//////////////////////////////////////////////////////////////////////////////////////////////
254/// Sends new data after change current directory
255
256void RFileDialog::SendChPathMsg(unsigned connid)
257{
258 RBrowserRequest req;
259 req.sort = "alphabetical";
261
263
264 fWebWindow->Send(connid, "CHMSG:{\"path\" : "s + jpath.Data() +
265 ", \"brepl\" : "s + fBrowsable.ProcessRequest(req) + " }"s);
266}
267
268//////////////////////////////////////////////////////////////////////////////////////////////
269/// Process received data from client
270
271void RFileDialog::ProcessMsg(unsigned connid, const std::string &arg)
272{
273 if (arg.compare(0, 7, "CHPATH:") == 0) {
274 if (GetCanChangePath()) {
275 auto path = TBufferJSON::FromJSON<Browsable::RElementPath_t>(arg.substr(7));
276 if (path) fBrowsable.SetWorkingPath(*path);
277 }
278
279 SendChPathMsg(connid);
280
281 } else if (arg.compare(0, 6, "CHEXT:") == 0) {
282
283 SetSelectedFilter(arg.substr(6));
284
285 SendChPathMsg(connid);
286
287 } else if (arg.compare(0, 10, "DLGSELECT:") == 0) {
288 // selected file name, if file exists - send request for confirmation
289
290 auto path = TBufferJSON::FromJSON<Browsable::RElementPath_t>(arg.substr(10));
291
292 if (!path) {
293 R__LOG_ERROR(BrowserLog()) << "Fail to decode JSON " << arg.substr(10);
294 return;
295 }
296
297 // check if element exists
298 auto elem = fBrowsable.GetElementFromTop(*path);
299 if (elem)
300 fSelect = elem->GetContent("filename");
301 else
302 fSelect.clear();
303
304 bool need_confirm = false;
305
306 if ((GetType() == kSaveAs) || (GetType() == kNewFile)) {
307 if (elem) {
308 need_confirm = true;
309 } else {
310 std::string fname = path->back();
311 path->pop_back();
312 auto direlem = fBrowsable.GetElementFromTop(*path);
313 if (direlem)
314 fSelect = direlem->GetContent("filename") + "/"s + fname;
315 }
316 }
317
318 if (need_confirm) {
319 fWebWindow->Send(connid, "NEED_CONFIRM"s); // sending request for confirmation
320 } else {
321 fWebWindow->Send(connid, "SELECT_CONFIRMED:"s + fSelect); // sending select confirmation with fully qualified file name
322 fDidSelect = true;
323 }
324 } else if (arg == "DLGNOSELECT") {
325 fSelect.clear();
326 fDidSelect = true;
327 fWebWindow->Send(connid, "NOSELECT_CONFIRMED"s); // sending confirmation of NOSELECT
328 } else if (arg == "DLG_CONFIRM_SELECT") {
329 fDidSelect = true;
330 fWebWindow->Send(connid, "SELECT_CONFIRMED:"s + fSelect);
331 }
332}
333
334//////////////////////////////////////////////////////////////////////////////////////////////
335/// Change current working path of file dialog
336/// If dialog already shown, change will be immediately applied
337
338void RFileDialog::SetWorkingPath(const std::string &path)
339{
341 auto elem = fBrowsable.GetSubElement(p);
342 if (elem) {
344 if (fWebWindow->NumConnections() > 0)
345 SendChPathMsg(0);
346 }
347}
348
349//////////////////////////////////////////////////////////////////////////////////////////////
350/// Returns current working path
351
353{
354 auto path = fBrowsable.GetWorkingPath();
356}
357
358//////////////////////////////////////////////////////////////////////////////////////////////
359/// Invoke specified callback
360
362{
363 if (fCallback) {
364 auto func = fCallback;
365 // reset callback to release associated with lambda resources
366 // reset before invoking callback to avoid multiple calls
367 fCallback = nullptr;
368 func(fSelect);
369 }
370}
371
372//////////////////////////////////////////////////////////////////////////////////////////////
373/// Start specified dialog type
374
375std::string RFileDialog::Dialog(EDialogTypes kind, const std::string &title, const std::string &fname)
376{
377 RFileDialog dlg(kind, title, fname);
378
379 dlg.Show();
380
381 dlg.fWebWindow->WaitForTimed([&](double) {
382 if (dlg.fDidSelect) return 1;
383
384 return 0; // continue waiting
385 });
386
387 return dlg.fSelect;
388}
389
390/////////////////////////////////////////////////////////////////////////////////////
391/// Start OpenFile dialog.
392/// Blocks until file name is selected or Cancel button is pressed
393/// Returns selected file name (or empty string)
394
395std::string RFileDialog::OpenFile(const std::string &title, const std::string &fname)
396{
397 return Dialog(kOpenFile, title, fname);
398}
399
400/////////////////////////////////////////////////////////////////////////////////////
401/// Start SaveAs dialog.
402/// Blocks until file name is selected or Cancel button is pressed
403/// Returns selected file name (or empty string)
404
405std::string RFileDialog::SaveAs(const std::string &title, const std::string &fname)
406{
407 return Dialog(kSaveAs, title, fname);
408}
409
410/////////////////////////////////////////////////////////////////////////////////////
411/// Start NewFile dialog.
412/// Blocks until file name is selected or Cancel button is pressed
413/// Returns selected file name (or empty string)
414
415std::string RFileDialog::NewFile(const std::string &title, const std::string &fname)
416{
417 return Dialog(kNewFile, title, fname);
418}
419
420/////////////////////////////////////////////////////////////////////////////////////
421/// Check if this could be the message send by client to start new file dialog
422/// If returns true, one can call RFileDialog::Embedded() to really create file dialog
423/// instance inside existing widget
424
425bool RFileDialog::IsMessageToStartDialog(const std::string &msg)
426{
427 return msg.compare(0, 11, "FILEDIALOG:") == 0;
428}
429
430/////////////////////////////////////////////////////////////////////////////////////
431/// Create dialog instance to use as embedded dialog inside other widget
432/// Embedded dialog started on the client side where FileDialogController.SaveAs() method called
433/// Such method immediately send message with "FILEDIALOG:" prefix
434/// On the server side widget should detect such message and call RFileDialog::Embed()
435/// providing received string as second argument.
436/// Returned instance of shared_ptr<RFileDialog> may be used to assign callback when file is selected
437
438std::shared_ptr<RFileDialog> RFileDialog::Embed(const std::shared_ptr<RWebWindow> &window, unsigned connid, const std::string &args)
439{
440 if (!IsMessageToStartDialog(args))
441 return nullptr;
442
443 auto arr = TBufferJSON::FromJSON<std::vector<std::string>>(args.substr(11));
444
445 if (!arr || (arr->size() < 3)) {
446 R__LOG_ERROR(BrowserLog()) << "Embedded FileDialog failure - argument should have at least three strings" << args.substr(11);
447 return nullptr;
448 }
449
450 auto kind = kSaveAs;
451
452 if (TypeAsString(kOpenFile) == arr->at(0))
453 kind = kOpenFile;
454 else if (TypeAsString(kNewFile) == arr->at(0))
455 kind = kNewFile;
456
457 auto dialog = std::make_shared<RFileDialog>(kind, "", arr->at(1));
458
459 auto chid = std::stoi(arr->at(2));
460
461 if ((arr->size() > 3) && (arr->at(3) == "__cannotChangePath__"s)) {
462 dialog->SetCanChangePath(false);
463 arr->erase(arr->begin() + 3, arr->begin() + 4); // erase element 3
464 } else if ((arr->size() > 3) && (arr->at(3) == "__canChangePath__"s)) {
465 dialog->SetCanChangePath(true);
466 arr->erase(arr->begin() + 3, arr->begin() + 4); // erase element 3
467 }
468
469 if ((arr->size() > 4) && (arr->at(3) == "__workingPath__"s)) {
470 dialog->SetWorkingPath(arr->at(4));
471 arr->erase(arr->begin() + 3, arr->begin() + 5); // erase two elements used to set working path
472 }
473
474 if (arr->size() > 4) {
475 dialog->SetSelectedFilter(arr->at(3));
476 arr->erase(arr->begin(), arr->begin() + 4); // erase 4 elements, keep only list of filters
477 dialog->SetNameFilters(*arr);
478 }
479
480 dialog->Show({window, connid, chid});
481
482 // use callback to release pointer, actually not needed but just to avoid compiler warning
483 dialog->SetCallback([dialog](const std::string &) mutable { dialog.reset(); });
484
485 return dialog;
486}
487
488/////////////////////////////////////////////////////////////////////////////////////
489/// Set start dialog function for RWebWindow
490
492{
493 if (on)
494 RWebWindow::SetStartDialogFunc([](const std::shared_ptr<RWebWindow> &window, unsigned connid, const std::string &args) -> bool {
495 auto res = RFileDialog::Embed(window, connid, args);
496 return res ? true : false;
497 });
498 else
500}
501
502
503/////////////////////////////////////////////////////////////////////////////////////
504
505namespace ROOT {
506namespace Details {
507
509public:
511
514
515}
516}
517
#define R__LOG_ERROR(...)
Definition RLogger.hxx:362
#define R__LOG_DEBUG(DEBUGLEVEL,...)
Definition RLogger.hxx:365
winID h TVirtualViewer3D TVirtualGLPainter p
Option_t Option_t TPoint TPoint const char GetTextMagnitude GetFillStyle GetLineColor GetLineWidth GetMarkerStyle GetTextAlign GetTextColor GetTextSize void on
char name[80]
Definition TGX11.cxx:110
static std::string GetPathAsString(const RElementPath_t &path)
Converts element path back to string.
Definition RElement.cxx:160
static RElementPath_t ParsePath(const std::string &str)
Parse string path to produce RElementPath_t One should avoid to use string pathes as much as possible...
Definition RElement.cxx:116
static RElementPath_t ProvideTopEntries(std::shared_ptr< RGroup > &comp, const std::string &workdir="")
Provide top entries for file system On windows it is list of existing drivers, on Linux it is "File s...
Definition RSysFile.cxx:533
void SetTopElement(std::shared_ptr< Browsable::RElement > elem)
set top element for browsing
std::shared_ptr< Browsable::RElement > GetSubElement(const Browsable::RElementPath_t &path)
Returns sub-element starting from top, using cached data.
std::shared_ptr< Browsable::RElement > GetElementFromTop(const Browsable::RElementPath_t &path)
Returns element with path, specified as Browsable::RElementPath_t.
std::string ProcessRequest(const RBrowserRequest &request)
Process browser request, returns string with JSON of RBrowserReply data.
void SetWorkingPath(const Browsable::RElementPath_t &path)
set working directory relative to top element
const Browsable::RElementPath_t & GetWorkingPath() const
Request send from client to get content of path element.
std::string sort
kind of sorting
std::string regex
applied regex
Web-based FileDialog.
std::vector< std::string > fNameFilters
! name filters
static void SetStartFunc(bool on)
Set start dialog function for RWebWindow.
static std::string Dialog(EDialogTypes kind, const std::string &title, const std::string &fname)
Start specified dialog type.
std::shared_ptr< RWebWindow > fWebWindow
! web window for file dialog
EDialogTypes fKind
! dialog kind OpenFile, SaveAs, NewFile
bool GetCanChangePath() const
Returns true if working path can be change with gui elements.
RFileDialogCallback_t fCallback
! function receiving result, called once
void Show(const RWebDisplayArgs &args="")
Show or update RFileDialog in web window If web window already started - just refresh it like "reload...
void InvokeCallBack()
Invoke specified callback.
static std::string SaveAs(const std::string &title="", const std::string &fname="")
Start SaveAs dialog.
std::string GetSelectedFilter() const
Returns selected filter Can differ from specified value - if it does not match to existing entry in N...
void ProcessMsg(unsigned connid, const std::string &arg)
Process received data from client.
bool fDidSelect
! true when dialog is selected or closed
static std::string OpenFile(const std::string &title="", const std::string &fname="")
Start OpenFile dialog.
void Hide()
Hide ROOT Browser.
std::string GetRegexp(const std::string &name) const
Returns regexp for selected filter String should have form "Filter name (*.ext1 *....
std::string fSelectedFilter
! name of selected filter
void SendChPathMsg(unsigned connid)
Sends new data after change current directory.
static bool IsMessageToStartDialog(const std::string &msg)
Check if this could be the message send by client to start new file dialog If returns true,...
std::string GetWorkingPath() const
Returns current working path.
static std::string TypeAsString(EDialogTypes kind)
Returns dialog type as string String value used for configuring JS-side.
static std::string NewFile(const std::string &title="", const std::string &fname="")
Start NewFile dialog.
void SetSelectedFilter(const std::string &name)
Configure selected filter Has to be one of the string from NameFilters entry.
RFileDialog(EDialogTypes kind=kOpenFile, const std::string &title="", const std::string &fname="")
constructor When title not specified, default will be used
static std::shared_ptr< RFileDialog > Embed(const std::shared_ptr< RWebWindow > &window, unsigned connid, const std::string &args)
Create dialog instance to use as embedded dialog inside other widget Embedded dialog started on the c...
void SetCallback(RFileDialogCallback_t callback)
Assign callback.
RBrowserData fBrowsable
! central browsing element
std::string fTitle
! title, when not specified default will be used
void SendInitMsg(unsigned connid)
Sends initial message to the client.
const EDialogTypes & GetType() const
void SetWorkingPath(const std::string &)
Change current working path of file dialog If dialog already shown, change will be immediately applie...
virtual ~RFileDialog()
destructor
std::string fSelect
! result of file selection
Holds different arguments for starting browser with RWebDisplayHandle::Display() method.
static std::shared_ptr< RWebWindow > Create()
Create new RWebWindow Using default RWebWindowsManager.
static unsigned ShowWindow(std::shared_ptr< RWebWindow > window, const RWebDisplayArgs &args="")
Static method to show web window Has to be used instead of RWebWindow::Show() when window potentially...
static void SetStartDialogFunc(std::function< bool(const std::shared_ptr< RWebWindow > &, unsigned, const std::string &)>)
Configure func which has to be used for starting dialog.
static TString ToJSON(const T *obj, Int_t compact=0, const char *member_name=nullptr)
Definition TBufferJSON.h:75
class ROOT::Details::RWebWindowPlugin sRWebWindowPlugin
tbb::task_arena is an alias of tbb::interface7::task_arena, which doesn't allow to forward declare tb...
std::function< void(const std::string &)> RFileDialogCallback_t
function signature for file dialog call-backs argument is selected file name
ROOT::Experimental::RLogChannel & BrowserLog()
Log channel for Browser diagnostics.