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