root6 musrview could save the plot as png/pdf/svg/...; the ROOT7 web rewrite dropped that. Add a "Save graphics" toolbar entry: the server renders the classic layout (same code path as --png/--pdf/... batch export) into the current working directory and hands it to the browser as a download, so the browser's own save dialog/download folder decides where it lands -- a native OS folder picker isn't reachable from here, since the --embedded window is a Qt6 QWebEngineView owned by ROOT itself, which auto-accepts downloads with no dialog. Also fixes rasterized batch export (png/gif/jpg) coming out blank when triggered from an already-running interactive session: TCanvas decides at *construction* time, from gROOT->IsBatch(), whether to build a real GUI canvas or a headless one, so canvas.SetBatch(kTRUE) after the fact was too late. Vector formats (pdf/eps/svg) don't need pixel readback and were unaffected, which is why this went unnoticed. Fixed by toggling gROOT's batch flag for the export canvas's lifetime. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017cuZPrSNaKjG8xHPVNwV16
531 lines
20 KiB
C++
531 lines
20 KiB
C++
/***************************************************************************
|
|
|
|
musrview.cpp
|
|
|
|
Author: Andreas Suter
|
|
e-mail: andreas.suter@psi.ch
|
|
|
|
***************************************************************************/
|
|
|
|
/***************************************************************************
|
|
* Copyright (C) 2007-2026 by Andreas Suter *
|
|
* andreas.suter@psi.ch *
|
|
* *
|
|
* This program is free software; you can redistribute it and/or modify *
|
|
* it under the terms of the GNU General Public License as published by *
|
|
* the Free Software Foundation; either version 2 of the License, or *
|
|
* (at your option) any later version. *
|
|
* *
|
|
* This program is distributed in the hope that it will be useful, *
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
|
|
* GNU General Public License for more details. *
|
|
* *
|
|
* You should have received a copy of the GNU General Public License *
|
|
* along with this program; if not, write to the *
|
|
* Free Software Foundation, Inc., *
|
|
* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *
|
|
***************************************************************************/
|
|
|
|
#ifdef HAVE_CONFIG_H
|
|
#include "config.h"
|
|
#endif
|
|
|
|
#include <cstdio>
|
|
#include <cstdlib>
|
|
#include <cstring>
|
|
|
|
#include <chrono>
|
|
#include <iostream>
|
|
#include <memory>
|
|
#include <sstream>
|
|
#include <string>
|
|
#include <vector>
|
|
|
|
#include <TApplication.h>
|
|
#include <TROOT.h>
|
|
#include <TSAXParser.h>
|
|
#include <TSystem.h>
|
|
|
|
#include <ROOT/RWebWindowsManager.hxx>
|
|
|
|
#ifdef HAVE_GIT_REV_H
|
|
#include "git-revision.h"
|
|
#endif
|
|
|
|
#include "PMusr.h"
|
|
#include "PMsgBox.h"
|
|
#include "PMsrHandler.h"
|
|
#include "PMusrWebCanvas.h"
|
|
#include "PRunDataHandler.h"
|
|
#include "PRunListCollection.h"
|
|
#include "PStartupHandler.h"
|
|
|
|
//--------------------------------------------------------------------------
|
|
/**
|
|
* <p>Sends the usage description to the standard output.
|
|
*/
|
|
void musrview_syntax()
|
|
{
|
|
std::cout << std::endl << "usage: musrview <msr-file> [Options]";
|
|
std::cout << std::endl << " <msr-file>: msr/mlog input file";
|
|
std::cout << std::endl << " Options:";
|
|
std::cout << std::endl << " --help : display this help and exit.";
|
|
std::cout << std::endl << " --version : output version information and exit.";
|
|
std::cout << std::endl << " --show-dynamic-path : dumps the dynamic search paths and exit.";
|
|
std::cout << std::endl << " -f, --fourier: will directly present the Fourier transform of the <msr-file>.";
|
|
std::cout << std::endl << " -a, --avg: will directly present the averaged data/Fourier of the <msr-file>.";
|
|
std::cout << std::endl << " -1, --one_to_one: calculate theory points only at data points.";
|
|
std::cout << std::endl << " display options (musrview is web based, see the musrfit documentation):";
|
|
std::cout << std::endl << " --embedded : show the plots in an embedded browser window, i.e. musrview";
|
|
std::cout << std::endl << " behaves like a stand-alone application. This is the default";
|
|
std::cout << std::endl << " as long as ROOT provides the Qt6 web display.";
|
|
std::cout << std::endl << " --browser : show the plots in the standard web browser of the system.";
|
|
std::cout << std::endl << " --browser=<kind> : use a specific display, e.g. chrome, firefox, qt6, server.";
|
|
std::cout << std::endl << " 'server' only prints the URL and starts no browser at all,";
|
|
std::cout << std::endl << " which is what to use when musrfit runs on a remote machine.";
|
|
std::cout << std::endl << " --<graphic-format-extension>: ";
|
|
std::cout << std::endl << " will produce a graphics-output-file without opening a window.";
|
|
std::cout << std::endl << " the name is based on the <msr-file>, e.g. 3310.msr -> 3310_0.png";
|
|
std::cout << std::endl << " supported graphic-format-extension:";
|
|
std::cout << std::endl << " eps, pdf, gif, jpg, png, svg, xpm, root";
|
|
std::cout << std::endl << " example: musrview 3310.msr --png, will produce a files 3310_X.png";
|
|
std::cout << std::endl << " where 'X' stands for the plot number (starting form 0)";
|
|
std::cout << std::endl << " --ascii: ";
|
|
std::cout << std::endl << " will produce an ascii dump of the data and fit as plotted.";
|
|
std::cout << std::endl << " --timeout <timeout>: <timeout> given in seconds after which musrview terminates.";
|
|
std::cout << std::endl << " If <timeout> <= 0, no timeout will take place. Default <timeout> is 0.";
|
|
std::cout << std::endl << " -s, --show-errMsgBox: if this tag is defined, error message boxes are shown,";
|
|
std::cout << std::endl << " rather than only stderr output.";
|
|
std::cout << std::endl << std::endl;
|
|
}
|
|
|
|
//--------------------------------------------------------------------------
|
|
/**
|
|
* <p>Shows an error message in a message box.
|
|
*
|
|
* \param errMsg the message to be shown
|
|
*/
|
|
void musrview_error_msg(std::string errMsg)
|
|
{
|
|
int argc=0;
|
|
char **argv=nullptr;
|
|
TApplication app("musrviewErrorMsg", &argc, argv);
|
|
new PMsgBox(errMsg, gClient->GetRoot(), 600, 200);
|
|
app.Run();
|
|
}
|
|
|
|
//--------------------------------------------------------------------------
|
|
/**
|
|
* <p>Decides which web display musrview should use.
|
|
*
|
|
* <p>Unless the user asked for something specific, the embedded Qt6 WebEngine
|
|
* window is preferred, since it makes musrview appear as the stand-alone
|
|
* application it has always been. If ROOT was built without the Qt6 web
|
|
* display, the standard browser of the system is used instead.
|
|
*
|
|
* \param requested what the user asked for, empty if nothing was requested
|
|
*
|
|
* \return the display string to be handed to ROOT::RWebWindow::Show()
|
|
*/
|
|
std::string musrview_select_display(const std::string &requested)
|
|
{
|
|
if (!requested.empty())
|
|
return requested;
|
|
|
|
if (const char *env = gSystem->Getenv("MUSRVIEW_BROWSER"))
|
|
if (strlen(env) > 0)
|
|
return std::string(env);
|
|
|
|
// prefer the embedded window if ROOT provides it
|
|
gSystem->RedirectOutput("/dev/null", "a");
|
|
const Int_t loaded = gSystem->Load("libROOTQt6WebDisplay");
|
|
gSystem->RedirectOutput(nullptr);
|
|
|
|
if (loaded >= 0)
|
|
return "qt6";
|
|
|
|
std::cout << std::endl << ">> musrview **INFO** ROOT has no Qt6 web display, using the standard browser.";
|
|
std::cout << std::endl << ">> Rebuild ROOT with -Dqt6web=ON for an embedded window." << std::endl;
|
|
|
|
return std::string();
|
|
}
|
|
|
|
//--------------------------------------------------------------------------
|
|
/**
|
|
* <p>The musrview program is used to show muSR fit results in graphical form.
|
|
* From it also Fourier transforms, difference between data an theory can be formed.
|
|
*
|
|
* <p>Since version 1.13 musrview is based on the ROOT7 web graphics: the plots
|
|
* are rendered by JSROOT either in an embedded browser window or in the
|
|
* standard browser of the system, while the C++ side only holds the data.
|
|
*
|
|
* <b>return:</b>
|
|
* - PMUSR_SUCCESS if everthing went smooth
|
|
* - PMUSR_WRONG_STARTUP_SYNTAX if syntax error is encountered
|
|
* - line number if an error in the msr-file was encountered which cannot be handled.
|
|
*
|
|
* \param argc number of input arguments
|
|
* \param argv list of input arguments
|
|
*/
|
|
int main(int argc, char *argv[])
|
|
{
|
|
int result{PMUSR_SUCCESS};
|
|
bool show_syntax{false};
|
|
int status;
|
|
bool success{true};
|
|
std::string fileName{};
|
|
bool fourier{false};
|
|
bool avg{false};
|
|
bool theoAtData{false}; // theory points only at data points
|
|
bool graphicsOutput{false};
|
|
bool asciiOutput{false};
|
|
std::string graphicsExtension{};
|
|
std::string display{};
|
|
int timeout{0};
|
|
bool show_errMsgBox{false};
|
|
std::stringstream errMsg;
|
|
|
|
// add default shared library path /usr/local/lib if not already persent
|
|
const char *dsp = gSystem->GetDynamicPath();
|
|
if (strstr(dsp, "/usr/local/lib") == nullptr)
|
|
gSystem->AddDynamicPath("/usr/local/lib");
|
|
|
|
// check input arguments
|
|
if (argc == 1) {
|
|
musrview_syntax();
|
|
return PMUSR_SYNTAX_REQUEST;
|
|
}
|
|
for (int i=1; i<argc; i++) {
|
|
if (strstr(argv[i], ".msr") || strstr(argv[i], ".mlog")) {
|
|
if (fileName.empty()) {
|
|
fileName = argv[i];
|
|
} else {
|
|
std::cerr << std::endl << "**ERROR** only one file name allowed." << std::endl;
|
|
show_syntax = true;
|
|
break;
|
|
}
|
|
} else if (!strcmp(argv[i], "--version")) {
|
|
#ifdef HAVE_CONFIG_H
|
|
#ifdef HAVE_GIT_REV_H
|
|
std::cout << std::endl << "musrview version: " << PACKAGE_VERSION << ", git-branch: " << GIT_BRANCH << ", git-rev: " << GIT_CURRENT_SHA1 << " (" << BUILD_TYPE << "), ROOT version: " << ROOT_VERSION_USED << std::endl << std::endl;
|
|
#else
|
|
std::cout << std::endl << "musrview version: " << PACKAGE_VERSION << " (" << BUILD_TYPE << "), ROOT version: " << ROOT_VERSION_USED << std::endl << std::endl;
|
|
#endif
|
|
#else
|
|
#ifdef HAVE_GIT_REV_H
|
|
std::cout << std::endl << "musrview git-branch: " << GIT_BRANCH << ", git-rev: " << GIT_CURRENT_SHA1 << std::endl << std::endl;
|
|
#else
|
|
std::cout << std::endl << "musrview version: unknown." << std::endl << std::endl;
|
|
#endif
|
|
#endif
|
|
return PMUSR_SUCCESS;
|
|
} else if (!strcmp(argv[i], "--show-dynamic-path")) {
|
|
std::cout << std::endl << "musrview: internal dynamic search paths for shared libraries/root dictionaries:";
|
|
std::cout << std::endl << " '" << gSystem->GetDynamicPath() << "'" << std::endl << std::endl;
|
|
return PMUSR_SUCCESS;
|
|
} else if (!strcmp(argv[i], "--help")) {
|
|
show_syntax = true;
|
|
break;
|
|
} else if (!strcmp(argv[i], "-f") || !strcmp(argv[i], "--fourier")) {
|
|
fourier = true;
|
|
} else if (!strcmp(argv[i], "-a") || !strcmp(argv[i], "--avg")) {
|
|
avg = true;
|
|
} else if (!strcmp(argv[i], "-1") || !strcmp(argv[i], "--one_to_one")) {
|
|
theoAtData = true;
|
|
} else if (!strcmp(argv[i], "--embedded")) {
|
|
display = "qt6";
|
|
} else if (!strcmp(argv[i], "--browser")) {
|
|
display = "default";
|
|
} else if (!strncmp(argv[i], "--browser=", 10)) {
|
|
display = argv[i]+10;
|
|
if (display.empty()) {
|
|
show_syntax = true;
|
|
break;
|
|
}
|
|
} else if (!strcmp(argv[i], "--eps") || !strcmp(argv[i], "--pdf") || !strcmp(argv[i], "--gif") ||
|
|
!strcmp(argv[i], "--jpg") || !strcmp(argv[i], "--png") || !strcmp(argv[i], "--svg") ||
|
|
!strcmp(argv[i], "--xpm") || !strcmp(argv[i], "--root")) {
|
|
|
|
graphicsOutput = true;
|
|
graphicsExtension = argv[i]+2;
|
|
} else if (!strcmp(argv[i], "--ascii")) {
|
|
asciiOutput = true;
|
|
} else if (!strcmp(argv[i], "--timeout")) {
|
|
if (i+1 < argc) {
|
|
TString str(argv[i+1]);
|
|
if (str.IsFloat()) {
|
|
timeout = str.Atoi();
|
|
} else {
|
|
show_syntax = true;
|
|
break;
|
|
}
|
|
i++;
|
|
} else {
|
|
show_syntax = true;
|
|
break;
|
|
}
|
|
} else if (!strcmp(argv[i], "-s") || !strcmp(argv[i], "--show-errMsgBox")) {
|
|
show_errMsgBox = true;
|
|
} else {
|
|
show_syntax = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (show_syntax) {
|
|
musrview_syntax();
|
|
return PMUSR_WRONG_STARTUP_SYNTAX;
|
|
}
|
|
|
|
const bool batchMode = graphicsOutput || asciiOutput;
|
|
|
|
// read startup file
|
|
std::string startup_path_name{};
|
|
std::unique_ptr<TSAXParser> saxParser = std::make_unique<TSAXParser>();
|
|
std::unique_ptr<PStartupHandler> startupHandler = std::make_unique<PStartupHandler>();
|
|
if (!startupHandler->StartupFileFound()) {
|
|
std::cerr << std::endl << ">> musrview **WARNING** couldn't find " << startupHandler->GetStartupFilePath().Data();
|
|
std::cerr << std::endl;
|
|
} else {
|
|
startup_path_name = startupHandler->GetStartupFilePath().Data();
|
|
saxParser->ConnectToHandler("PStartupHandler", startupHandler.get());
|
|
// parsing the file via ParseFile() seems to lead to problems in certain
|
|
// environments; use parseXmlFile (see PStartupHandler.cpp) instead
|
|
status = parseXmlFile(saxParser.get(), startup_path_name.c_str());
|
|
if (status) { // error
|
|
std::cerr << std::endl << ">> musrview **WARNING** Reading/parsing musrfit_startup.xml failed.";
|
|
std::cerr << std::endl << ">> Any graph will appear with random symbols and colors!";
|
|
std::cerr << std::endl;
|
|
} else {
|
|
startupHandler->CheckLists();
|
|
}
|
|
}
|
|
|
|
// read msr-file
|
|
std::unique_ptr<PMsrHandler> msrHandler = std::make_unique<PMsrHandler>(fileName.c_str());
|
|
status = msrHandler->ReadMsrFile();
|
|
if (status != PMUSR_SUCCESS) {
|
|
errMsg << msrHandler->GetLastErrorMsg();
|
|
switch (status) {
|
|
case PMUSR_MSR_FILE_NOT_FOUND:
|
|
errMsg << "\n";
|
|
errMsg << ">> musrview **ERROR** couldn't find '" << fileName << "'\n\n";
|
|
break;
|
|
case PMUSR_MSR_SYNTAX_ERROR:
|
|
errMsg << "\n";
|
|
errMsg << ">> musrview **SYNTAX ERROR** in file " << fileName << ", full stop here.\n\n";
|
|
break;
|
|
default:
|
|
errMsg << "\n";
|
|
errMsg << ">> musrview **UNKNOWN ERROR** when trying to read the msr-file.\n\n";
|
|
break;
|
|
}
|
|
std::cerr << errMsg.str();
|
|
if (show_errMsgBox)
|
|
musrview_error_msg(errMsg.str());
|
|
return status;
|
|
}
|
|
|
|
// make a plot list vector
|
|
PMsrPlotList *msrPlotList = msrHandler->GetMsrPlotList();
|
|
PIntVector plotList;
|
|
bool runPresent;
|
|
for (unsigned int i=0; i<msrPlotList->size(); i++) {
|
|
for (unsigned int j=0; j<msrPlotList->at(i).fRuns.size(); j++) {
|
|
runPresent = false;
|
|
for (unsigned int k=0; k<plotList.size(); k++) {
|
|
if (msrPlotList->at(i).fRuns[j] == static_cast<int>(plotList[k])) {
|
|
runPresent = true;
|
|
break;
|
|
}
|
|
}
|
|
if (!runPresent)
|
|
plotList.push_back(static_cast<int>(msrPlotList->at(i).fRuns[j]));
|
|
}
|
|
}
|
|
|
|
// read all the necessary runs (raw data)
|
|
std::unique_ptr<PRunDataHandler> dataHandler;
|
|
if (startupHandler)
|
|
dataHandler = std::make_unique<PRunDataHandler>(msrHandler.get(), startupHandler->GetDataPathList());
|
|
else
|
|
dataHandler = std::make_unique<PRunDataHandler>(msrHandler.get());
|
|
|
|
dataHandler->ReadData();
|
|
|
|
success = dataHandler->IsAllDataAvailable();
|
|
if (!success) {
|
|
errMsg << ">> musrview **ERROR** Couldn't read all data files, will quit ...\n";
|
|
std::cerr << errMsg.str();
|
|
if (show_errMsgBox)
|
|
musrview_error_msg(errMsg.str());
|
|
result = PMUSR_DATA_FILE_READ_ERROR;
|
|
}
|
|
|
|
// generate the necessary histogramms for the view
|
|
std::unique_ptr<PRunListCollection> runListCollection;
|
|
if (result == PMUSR_SUCCESS) {
|
|
runListCollection = std::make_unique<PRunListCollection>(msrHandler.get(), dataHandler.get(), theoAtData);
|
|
for (unsigned int i=0; i<msrHandler->GetMsrRunList()->size(); i++) {
|
|
runPresent = false;
|
|
for (unsigned int j=0; j<plotList.size(); j++) {
|
|
if (static_cast<unsigned int>(plotList[j]) == i+1) {
|
|
runPresent = true;
|
|
break;
|
|
}
|
|
}
|
|
if (runPresent) {
|
|
success = runListCollection->Add(i, kView);
|
|
if (!success) {
|
|
errMsg << ">> musrview **ERROR** Couldn't handle run no " << i << " ";
|
|
errMsg << (*msrHandler->GetMsrRunList())[i].GetRunName()->Data() << "\n";
|
|
std::cerr << errMsg.str();
|
|
if (show_errMsgBox)
|
|
musrview_error_msg(errMsg.str());
|
|
result = PMUSR_MSR_RUN_ERROR;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (result != PMUSR_SUCCESS) {
|
|
plotList.clear();
|
|
return result;
|
|
}
|
|
|
|
// A ROOT application is needed so that the web display and the graphics
|
|
// export find a working environment.
|
|
//
|
|
// In the interactive case no TCanvas is created at all - the spectra travel
|
|
// to JSROOT as a list of drawables - hence ROOT never opens a window of its
|
|
// own. Only the graphics export builds a TCanvas, and that runs in batch.
|
|
std::vector<char*> args;
|
|
args.push_back(argv[0]);
|
|
if (batchMode)
|
|
args.push_back(const_cast<char*>("-b"));
|
|
int cc = static_cast<int>(args.size());
|
|
TApplication app("musrview", &cc, args.data());
|
|
|
|
std::vector<std::unique_ptr<musrview::PMusrWebCanvas>> canvasList;
|
|
|
|
bool ok = true;
|
|
for (unsigned int i=0; i<msrHandler->GetMsrPlotList()->size(); i++) {
|
|
auto canvas = std::make_unique<musrview::PMusrWebCanvas>(
|
|
static_cast<Int_t>(i), msrHandler->GetMsrTitle()->Data(),
|
|
1200, 900,
|
|
startupHandler ? startupHandler->GetFourierDefaults() : PMsrFourierStructure{},
|
|
startupHandler ? startupHandler->GetMarkerList() : PIntVector{},
|
|
startupHandler ? startupHandler->GetColorList() : PIntVector{},
|
|
batchMode, fourier, avg, theoAtData);
|
|
|
|
// ugly but rootcling cannot handle the spirit-parser framework
|
|
canvas->SetMsrHandler(msrHandler.get());
|
|
canvas->SetRunListCollection(runListCollection.get());
|
|
|
|
if (!canvas->Build()) {
|
|
errMsg << ">> musrview **ERROR** " << canvas->GetLastError() << "\n";
|
|
std::cerr << errMsg.str();
|
|
if (show_errMsgBox)
|
|
musrview_error_msg(errMsg.str());
|
|
ok = false;
|
|
result = PMUSR_MSR_RUN_ERROR;
|
|
break;
|
|
}
|
|
|
|
canvas->SetTimeout(timeout);
|
|
canvasList.push_back(std::move(canvas));
|
|
}
|
|
|
|
if (!ok) {
|
|
canvasList.clear();
|
|
plotList.clear();
|
|
return result;
|
|
}
|
|
|
|
if (graphicsOutput) {
|
|
for (auto &canvas : canvasList) {
|
|
if (!canvas->SaveGraphics(graphicsExtension)) {
|
|
std::cerr << std::endl << ">> musrview **ERROR** " << canvas->GetLastError() << std::endl;
|
|
result = PMUSR_MSR_RUN_ERROR;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (asciiOutput) {
|
|
// generate the export data file name
|
|
TString str(fileName.c_str());
|
|
str.Remove(str.Last('.'));
|
|
for (size_t i=0; i<canvasList.size(); i++) {
|
|
TString name(str);
|
|
if (canvasList.size() > 1) {
|
|
name += "_";
|
|
name += static_cast<Int_t>(i);
|
|
}
|
|
name += ".dat";
|
|
if (!canvasList[i]->ExportData(name.Data())) {
|
|
std::cerr << std::endl << ">> musrview **ERROR** " << canvasList[i]->GetLastError() << std::endl;
|
|
result = PMUSR_MSR_RUN_ERROR;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!batchMode) {
|
|
const std::string where = musrview_select_display(display);
|
|
|
|
// one window per PLOT block, exactly as before
|
|
ROOT::RWebWindowsManager::SetSingleConnMode(false);
|
|
|
|
for (auto &canvas : canvasList) {
|
|
if (!canvas->Show(where)) {
|
|
std::cerr << std::endl << ">> musrview **ERROR** " << canvas->GetLastError() << std::endl;
|
|
result = PMUSR_MSR_RUN_ERROR;
|
|
ok = false;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (ok) {
|
|
// run until every window has been closed, or until the timeout expired
|
|
const auto start = std::chrono::steady_clock::now();
|
|
while (true) {
|
|
gSystem->ProcessEvents();
|
|
gSystem->Sleep(20);
|
|
|
|
bool allDone = true;
|
|
for (auto &canvas : canvasList) {
|
|
if (!canvas->IsDone()) {
|
|
allDone = false;
|
|
break;
|
|
}
|
|
}
|
|
if (allDone)
|
|
break;
|
|
|
|
if (timeout > 0) {
|
|
const auto now = std::chrono::steady_clock::now();
|
|
if (std::chrono::duration_cast<std::chrono::seconds>(now-start).count() >= timeout) {
|
|
std::cout << std::endl << ">> musrview: timeout of " << timeout << " s reached, terminating." << std::endl;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// End the process here. Tearing down an RWebWindow which drives the
|
|
// embedded display blocks, because the Qt event loop ROOT started on our
|
|
// behalf is never stopped again. Everything of interest has been written by
|
|
// now, so leave immediately rather than hang after the last window closed.
|
|
std::cout.flush();
|
|
std::cerr.flush();
|
|
gSystem->Exit(result, kFALSE);
|
|
}
|
|
|
|
// only reached in batch mode, where no window was ever opened
|
|
canvasList.clear();
|
|
plotList.clear();
|
|
|
|
return result;
|
|
}
|