1 /*
2  * RGraphicsPlotManager.cpp
3  *
4  * Copyright (C) 2021 by RStudio, PBC
5  *
6  * Unless you have received this program directly from RStudio pursuant
7  * to the terms of a commercial license agreement with RStudio, then
8  * this program is licensed to you under the terms of version 3 of the
9  * GNU Affero General Public License. This program is distributed WITHOUT
10  * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
11  * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
12  * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
13  *
14  */
15 
16 #include "RGraphicsPlotManager.hpp"
17 
18 #include <algorithm>
19 #include <gsl/gsl>
20 
21 #include <boost/function.hpp>
22 #include <boost/format.hpp>
23 #include <boost/lexical_cast.hpp>
24 #include <boost/bind/bind.hpp>
25 
26 #include <core/Log.hpp>
27 #include <shared_core/Error.hpp>
28 #include <core/FileSerializer.hpp>
29 #include <core/RegexUtils.hpp>
30 
31 #include <r/RExec.hpp>
32 #include <r/RUtil.hpp>
33 #include <r/ROptions.hpp>
34 #include <r/RErrorCategory.hpp>
35 #include <r/session/RSessionUtils.hpp>
36 
37 #include "RGraphicsUtils.hpp"
38 #include "RGraphicsDevice.hpp"
39 #include "RGraphicsPlotManipulatorManager.hpp"
40 
41 using namespace rstudio::core;
42 using namespace boost::placeholders;
43 
44 namespace rstudio {
45 namespace r {
46 namespace session {
47 namespace graphics {
48 
49 namespace {
50 
pixelsToInches(int pixels)51 double pixelsToInches(int pixels)
52 {
53    return (double)pixels / 96.0;
54 }
55 
56 } // anonymous namespace
57 
58 const char * const kPngFormat = "png";
59 const char * const kJpegFormat = "jpeg";
60 const char * const kBmpFormat = "bmp";
61 const char * const kTiffFormat = "tiff";
62 const char * const kMetafileFormat = "emf";
63 const char * const kSvgFormat = "svg";
64 const char * const kPostscriptFormat = "eps";
65 
66 // satisfy r::session::graphics::Display singleton
display()67 Display& display()
68 {
69    return graphics::plotManager();
70 }
71 
plotManager()72 PlotManager& plotManager()
73 {
74    static PlotManager instance;
75    return instance;
76 }
77 
PlotManager()78 PlotManager::PlotManager()
79    :  displayHasChanges_(false),
80       lastChange_(boost::posix_time::not_a_date_time),
81       suppressDeviceEvents_(false),
82       activePlot_(-1),
83       plotInfoRegex_("([A-Za-z0-9\\-]+):([0-9]+),([0-9]+)")
84 {
85    plots_.set_capacity(100);
86 }
87 
initialize(const FilePath & graphicsPath,const GraphicsDeviceFunctions & graphicsDevice,GraphicsDeviceEvents * pEvents)88 Error PlotManager::initialize(const FilePath& graphicsPath,
89                               const GraphicsDeviceFunctions& graphicsDevice,
90                               GraphicsDeviceEvents* pEvents)
91 {
92    // initialize plot manipulator manager
93    Error error = plotManipulatorManager().initialize(graphicsDevice.convert);
94    if (error)
95       return error;
96 
97    // save reference to graphics path and make sure it exists
98    graphicsPath_ = graphicsPath;
99    error = graphicsPath_.ensureDirectory();
100    if (error)
101       return error;
102 
103    // save reference to plots state file
104    plotsStateFile_ = graphicsPath_.completePath("INDEX");
105 
106    // save reference to graphics device functions
107    graphicsDevice_ = graphicsDevice;
108 
109    // sign up for graphics device events
110    using boost::bind;
111    pEvents->onNewPage.connect(bind(&PlotManager::onDeviceNewPage, this, _1));
112    pEvents->onDrawing.connect(bind(&PlotManager::onDeviceDrawing, this));
113    pEvents->onResized.connect(bind(&PlotManager::onDeviceResized, this));
114    pEvents->onClosed.connect(bind(&PlotManager::onDeviceClosed, this));
115 
116    return Success();
117 }
118 
119 
plotCount() const120 int PlotManager::plotCount() const
121 {
122    return gsl::narrow_cast<int>(plots_.size());
123 }
124 
plotImageFilename(int index,std::string * pImageFilename) const125 Error PlotManager::plotImageFilename(int index,
126                                      std::string* pImageFilename) const
127 {
128    if (!isValidPlotIndex(index))
129    {
130       return plotIndexError(index, ERROR_LOCATION);
131    }
132    else
133    {
134       *pImageFilename = plots_[index]->imageFilename();
135       return Success();
136    }
137 }
138 
activePlotIndex() const139 int PlotManager::activePlotIndex() const
140 {
141    return activePlot_;
142 }
143 
144 // NOTE: returns an error if the plot index is invalid. Otherwise will always
145 // successfully update the active plot state. If any file or rendering errors
146 // occur while setting the active plot they will be reported but will not
147 // cause the method to return an error.
setActivePlot(int index)148 Error PlotManager::setActivePlot(int index)
149 {
150    if (!isValidPlotIndex(index))
151       return plotIndexError(index, ERROR_LOCATION);
152 
153    if (activePlot_ != index)
154    {
155       // if there is already a plot active then release its
156       // in-memory resources
157       if (hasPlot())
158          activePlot().purgeInMemoryResources();
159 
160       // set index
161       activePlot_ = index;
162 
163       // render it
164       renderActivePlotToDisplay();
165 
166       // trip changes flag
167       setDisplayHasChanges(true);
168    }
169 
170    // return success
171    return Success();
172 }
173 
174 
175 // NOTE: returns an error if the plot index is invalid. Otherwise it
176 // is guaranteed to have removed the plot. Rendering or file errors which
177 // occur during the removal or transformation to a new graphics state are
178 // reported to the user and logged but are not returned (because in these
179 // cases the actual removal succeeded)
removePlot(int index)180 Error PlotManager::removePlot(int index)
181 {
182    if (!isValidPlotIndex(index))
183       return plotIndexError(index, ERROR_LOCATION);
184 
185    // remove the plot files
186    Error removeError = plots_[index]->removeFiles();
187    if (removeError)
188       logAndReportError(removeError, ERROR_LOCATION);
189 
190    // erase the plot from the internal list
191    plots_.erase(plots_.begin() + index);
192 
193    // trip changes flag (removing a plot will affect the number of plots
194    // and the active plot index so we need a new changed event)
195    setDisplayHasChanges(true);
196 
197    // fixup active plot as necessary
198 
199    // case: we just removed the active plot
200    if (index == activePlot_)
201    {
202       // clear active plot
203       activePlot_ = -1;
204 
205       // try to select the plot after the one removed
206       if (isValidPlotIndex(index))
207       {
208          Error error = setActivePlot(index);
209          if (error)
210             logAndReportError(error, ERROR_LOCATION);
211       }
212       // try to select the plot prior to the one removed
213       else if (isValidPlotIndex(index-1))
214       {
215          Error error = setActivePlot(index-1);
216          if (error)
217             logAndReportError(error, ERROR_LOCATION);
218       }
219    }
220    // case: we removed a plot *prior to* the active plot. this means
221    // that the list shrunk by 1 so the active plot's index needs to
222    // shrink by 1 as well
223    else if (index < activePlot_)
224    {
225       --activePlot_;
226    }
227 
228 
229    return Success();
230 }
231 
savePlotAsFile(const boost::function<Error ()> & deviceCreationFunction)232 Error PlotManager::savePlotAsFile(const boost::function<Error()>&
233                                      deviceCreationFunction)
234 {
235    if (!hasPlot())
236       return Error(errc::NoActivePlot, ERROR_LOCATION);
237 
238    // restore previous device after invoking file device
239    RestorePreviousGraphicsDeviceScope restoreScope;
240 
241    // create the target device
242    Error error = deviceCreationFunction();
243    if (error)
244       return error;
245 
246    // copy the current contents of the graphics device to the target device
247    graphicsDevice_.copyToActiveDevice();
248 
249    // close the target device to save the file
250    return r::exec::RFunction("dev.off").call();
251 }
252 
savePlotAsFile(const std::string & deviceCreationCode)253 Error PlotManager::savePlotAsFile(const std::string& deviceCreationCode)
254 {
255    return savePlotAsFile(
256          boost::bind(r::exec::executeString, deviceCreationCode));
257 }
258 
savePlotAsImage(const FilePath & filePath,const std::string & format,int widthPx,int heightPx,bool useDevicePixelRatio)259 Error PlotManager::savePlotAsImage(const FilePath& filePath,
260                                    const std::string& format,
261                                    int widthPx,
262                                    int heightPx,
263                                    bool useDevicePixelRatio)
264 {
265    double pixelRatio = useDevicePixelRatio ?
266                          r::session::graphics::device::devicePixelRatio() : 1;
267    return savePlotAsImage(filePath, format, widthPx, heightPx, pixelRatio);
268 }
269 
270 
savePlotAsImage(const FilePath & filePath,const std::string & format,int widthPx,int heightPx,double pixelRatio)271 Error PlotManager::savePlotAsImage(const FilePath& filePath,
272                                    const std::string& format,
273                                    int widthPx,
274                                    int heightPx,
275                                    double pixelRatio)
276 {
277    if (format == kPngFormat ||
278        format == kBmpFormat ||
279        format == kJpegFormat ||
280        format == kTiffFormat)
281    {
282       return savePlotAsBitmapFile(filePath, format, widthPx, heightPx, pixelRatio);
283    }
284    else if (format == kSvgFormat)
285    {
286       return savePlotAsSvg(filePath, widthPx, heightPx);
287    }
288    else if (format == kMetafileFormat)
289    {
290       return savePlotAsMetafile(filePath, widthPx, heightPx);
291    }
292    else if (format == kPostscriptFormat)
293    {
294       return savePlotAsPostscript(filePath, widthPx, heightPx);
295    }
296    else
297    {
298       return systemError(boost::system::errc::invalid_argument, ERROR_LOCATION);
299    }
300 }
301 
savePlotAsBitmapFile(const FilePath & targetPath,const std::string & bitmapFileType,int width,int height,double pixelRatio)302 Error PlotManager::savePlotAsBitmapFile(const FilePath& targetPath,
303                                         const std::string& bitmapFileType,
304                                         int width,
305                                         int height,
306                                         double pixelRatio)
307 {
308    // default res
309    int res = 96;
310 
311    // adjust for device pixel ratio
312    width = gsl::narrow_cast<int>(width * pixelRatio);
313    height = gsl::narrow_cast<int>(height * pixelRatio);
314    res = gsl::narrow_cast<int>(res * pixelRatio);
315 
316    // handle ragg specially
317    std::string backend = getDefaultBackend();
318    if (backend == "ragg" &&
319        (bitmapFileType == kPngFormat ||
320         bitmapFileType == kJpegFormat ||
321         bitmapFileType == kTiffFormat))
322    {
323       auto deviceFunction = [=]() -> core::Error
324       {
325          if (bitmapFileType == kPngFormat)
326          {
327             return r::exec::RFunction("ragg:::agg_png")
328                   .addParam("filename", targetPath.getAbsolutePath())
329                   .addParam("width", width)
330                   .addParam("height", height)
331                   .addParam("res", res)
332                   .call();
333          }
334          else if (bitmapFileType == kJpegFormat)
335          {
336             return r::exec::RFunction("ragg:::agg_jpeg")
337                   .addParam("filename", targetPath.getAbsolutePath())
338                   .addParam("width", width)
339                   .addParam("height", height)
340                   .addParam("res", res)
341                   .addParam("quality", 100)
342                   .call();
343          }
344          else if (bitmapFileType == kTiffFormat)
345          {
346             return r::exec::RFunction("ragg:::agg_tiff")
347                   .addParam("filename", targetPath.getAbsolutePath())
348                   .addParam("width", width)
349                   .addParam("height", height)
350                   .addParam("res", res)
351                   .call();
352          }
353          else
354          {
355             return Error(
356                      boost::system::errc::not_supported,
357                      ERROR_LOCATION);
358          }
359       };
360 
361       return savePlotAsFile(deviceFunction);
362    }
363 
364    // optional format specific extra params
365    std::string extraParams;
366 
367    // add extra quality parameter for jpegs
368    if (bitmapFileType == kJpegFormat)
369       extraParams = ", quality = 100";
370 
371    // add extra bitmap params
372    extraParams += r::session::graphics::extraBitmapParams();
373 
374    // generate code for creating bitmap file device
375    boost::format fmt(
376       "{ require(grDevices, quietly=TRUE); "
377       "  %1%(filename=\"%2%\", width=%3%, height=%4%, res = %5% %6%); }");
378    std::string deviceCreationCode = boost::str(fmt % bitmapFileType %
379                                                string_utils::utf8ToSystem(targetPath.getAbsolutePath()) %
380                                                width %
381                                                height %
382                                                res %
383                                                extraParams);
384 
385    // save the file
386    return savePlotAsFile(deviceCreationCode);
387 }
388 
savePlotAsPdf(const FilePath & filePath,double widthInches,double heightInches,bool useCairoPdf)389 Error PlotManager::savePlotAsPdf(const FilePath& filePath,
390                                  double widthInches,
391                                  double heightInches,
392                                  bool useCairoPdf)
393 {
394    // generate code for creating pdf file device
395    std::string code("{ require(grDevices, quietly=TRUE); ");
396    if (useCairoPdf)
397       code += "cairo_pdf(file=\"%1%\", width=%2%, height=%3%); }";
398    else
399       code += " pdf(file=\"%1%\", width=%2%, height=%3%, "
400              "      useDingbats=FALSE); }";
401    boost::format fmt(code);
402    std::string deviceCreationCode = boost::str(fmt % string_utils::utf8ToSystem(filePath.getAbsolutePath()) %
403                                                widthInches %
404                                                heightInches);
405 
406    // save the file
407    return savePlotAsFile(deviceCreationCode);
408 }
409 
savePlotAsSvg(const FilePath & targetPath,int width,int height)410 Error PlotManager::savePlotAsSvg(const FilePath& targetPath,
411                                  int width,
412                                  int height)
413 {
414    // calculate size in inches
415    double widthInches = pixelsToInches(width);
416    double heightInches = pixelsToInches(height);
417 
418    // generate code for creating svg device
419    boost::format fmt("{ require(grDevices, quietly=TRUE); "
420                      "  svg(filename=\"%1%\", width=%2%, height=%3%, "
421                      "      antialias = \"subpixel\"); }");
422    std::string deviceCreationCode = boost::str(fmt % string_utils::utf8ToSystem(targetPath.getAbsolutePath()) %
423                                                widthInches %
424                                                heightInches);
425 
426    return savePlotAsFile(deviceCreationCode);
427 }
428 
savePlotAsPostscript(const FilePath & targetPath,int width,int height)429 Error PlotManager::savePlotAsPostscript(const FilePath& targetPath,
430                                         int width,
431                                         int height)
432 {
433    // calculate size in inches
434    double widthInches = pixelsToInches(width);
435    double heightInches = pixelsToInches(height);
436 
437    // generate code for creating postscript device
438    boost::format fmt("{ require(grDevices, quietly=TRUE); "
439                      "  postscript(file=\"%1%\", width=%2%, height=%3%, "
440                      "             onefile = FALSE, "
441                      "             paper = \"special\", "
442                      "             horizontal = FALSE); }");
443    std::string deviceCreationCode = boost::str(fmt % string_utils::utf8ToSystem(targetPath.getAbsolutePath()) %
444                                                widthInches %
445                                                heightInches);
446 
447    return savePlotAsFile(deviceCreationCode);
448 }
449 
450 
savePlotAsMetafile(const core::FilePath & filePath,int widthPx,int heightPx)451 Error PlotManager::savePlotAsMetafile(const core::FilePath& filePath,
452                                       int widthPx,
453                                       int heightPx)
454 {
455 #ifdef _WIN32
456    // calculate size in inches
457    double widthInches = pixelsToInches(widthPx);
458    double heightInches = pixelsToInches(heightPx);
459 
460    // generate code for creating metafile device
461    boost::format fmt("{ require(grDevices, quietly=TRUE); "
462                      "  win.metafile(filename=\"%1%\", width=%2%, height=%3%, "
463                      "               restoreConsole=FALSE); }");
464    std::string deviceCreationCode = boost::str(fmt % string_utils::utf8ToSystem(filePath.getAbsolutePath()) %
465                                                      widthInches %
466                                                      heightInches);
467 
468    return savePlotAsFile(deviceCreationCode);
469 
470 #else
471    return systemError(boost::system::errc::not_supported, ERROR_LOCATION);
472 #endif
473 }
474 
475 
hasOutput() const476 bool PlotManager::hasOutput() const
477 {
478    return hasPlot();
479 }
480 
hasChanges() const481 bool PlotManager::hasChanges() const
482 {
483    return displayHasChanges_;
484 }
485 
isActiveDevice() const486 bool PlotManager::isActiveDevice() const
487 {
488    return graphicsDevice_.isActive();
489 }
490 
lastChange() const491 boost::posix_time::ptime PlotManager::lastChange() const
492 {
493    return lastChange_;
494 }
495 
render(boost::function<void (DisplayState)> outputFunction)496 void PlotManager::render(boost::function<void(DisplayState)> outputFunction)
497 {
498    // make sure the graphics path exists (may have been blown away
499    // by call to dev.off or other call to removeAllPlots)
500    Error error = graphicsPath_.ensureDirectory();
501    if (error)
502    {
503       Error graphicsError(errc::PlotFileError, error, ERROR_LOCATION);
504       logAndReportError(graphicsError, ERROR_LOCATION);
505       return;
506    }
507 
508    // clear changes flag
509    setDisplayHasChanges(false);
510 
511    // optional manipulator structure
512    json::Value plotManipulatorJson;
513 
514    if (hasPlot()) // write image for active plot
515    {
516       // copy current contents of the display to the active plot files
517       Error error = activePlot().renderFromDisplay();
518       if (error)
519       {
520          // no such file error expected in the case of an invalid graphics
521          // context (because generation of the PNG would have failed)
522          bool pngNotFound = error.hasCause() && isPathNotFoundError(error.getCause());
523 
524          // only log if this wasn't png not found
525          if (!pngNotFound)
526          {
527             // for r code execution errors we just report them
528             if (r::isCodeExecutionError(error))
529                reportError(error);
530             else
531                logAndReportError(error, ERROR_LOCATION);
532          }
533 
534          return;
535       }
536 
537       // get manipulator
538       activePlot().manipulatorAsJson(&plotManipulatorJson);
539    }
540    else  // write "empty" image
541    {
542       // create an empty file
543       FilePath emptyImageFilePath = graphicsPath_.completePath(emptyImageFilename());
544       error = writeStringToFile(emptyImageFilePath, std::string());
545       if (error)
546       {
547          Error graphicsError(errc::PlotRenderingError, error, ERROR_LOCATION);
548          logAndReportError(graphicsError, ERROR_LOCATION);
549          return;
550       }
551    }
552 
553    // call output function
554    DisplayState currentState(imageFilename(),
555                              plotManipulatorJson,
556                              r::session::graphics::device::getWidth(),
557                              r::session::graphics::device::getHeight(),
558                              activePlotIndex(),
559                              plotCount());
560    outputFunction(currentState);
561 }
562 
imageFilename() const563 std::string PlotManager::imageFilename() const
564 {
565    if (hasPlot())
566    {
567       return plots_[activePlot_]->imageFilename();
568    }
569    else
570    {
571       return emptyImageFilename();
572    }
573 }
574 
refresh()575 void PlotManager::refresh()
576 {
577    invalidateActivePlot();
578 }
579 
imagePath(const std::string & imageFilename) const580 FilePath PlotManager::imagePath(const std::string& imageFilename) const
581 {
582    return graphicsPath_.completePath(imageFilename);
583 }
584 
clear()585 void PlotManager::clear()
586 {
587    graphicsDevice_.close();
588 }
589 
590 
591 
onShowManipulator()592 RSTUDIO_BOOST_SIGNAL<void ()>& PlotManager::onShowManipulator()
593 {
594    return plotManipulatorManager().onShowManipulator();
595 }
596 
597 
598 
setPlotManipulatorValues(const json::Object & values)599 void PlotManager::setPlotManipulatorValues(const json::Object& values)
600 {
601    return plotManipulatorManager().setPlotManipulatorValues(values);
602 }
603 
manipulatorPlotClicked(int x,int y)604 void PlotManager::manipulatorPlotClicked(int x, int y)
605 {
606    plotManipulatorManager().manipulatorPlotClicked(x, y);
607 }
608 
609 
onBeforeExecute()610 void PlotManager::onBeforeExecute()
611 {
612    graphicsDevice_.onBeforeExecute();
613 }
614 
savePlotsState()615 Error PlotManager::savePlotsState()
616 {
617    // exit if we don't have a graphics path
618    if (!graphicsPath_.exists())
619       return Success();
620 
621    // list to write
622    std::vector<std::string> plots;
623 
624     // write the storage id of the active plot
625    if (hasPlot())
626       plots.push_back(activePlot().storageUuid());
627 
628    // build sequence of plot info (id:width,height)
629    for (boost::circular_buffer<PtrPlot>::const_iterator it = plots_.begin();
630         it != plots_.end();
631         ++it)
632    {
633       const Plot& plot = *(it->get());
634 
635       boost::format fmt("%1%:%2%,%3%");
636       std::string plotInfo = boost::str(fmt % plot.storageUuid() %
637                                               plot.renderedSize().width %
638                                               plot.renderedSize().height);
639       plots.push_back(plotInfo);
640    }
641 
642    // suppres all device events after suspend
643    suppressDeviceEvents_ = true;
644 
645    // write plot list
646    return writeStringVectorToFile(plotsStateFile_, plots);
647 }
648 
restorePlotsState()649 Error PlotManager::restorePlotsState()
650 {
651    // exit if we don't have a plot list
652    if (!plotsStateFile_.exists())
653       return Success();
654 
655    // read plot list from file
656    std::vector<std::string> plots;
657    Error error = readStringVectorFromFile(plotsStateFile_, &plots);
658    if (error)
659       return error;
660 
661    // if it is empty then return succes
662    if (plots.empty())
663       return Success();
664 
665    // read the storage id of the active plot them remove it from the list
666    std::string activePlotStorageId;
667    if (!plots.empty())
668    {
669       activePlotStorageId = plots[0];
670       plots.erase(plots.begin());
671    }
672 
673    // initialize plot list
674    std::string plotInfo;
675    for (int i=0; i<(int)plots.size(); ++i)
676    {
677       std::string plotStorageId;
678       DisplaySize renderedSize(0,0);
679 
680       // extract the id, width, and height
681       plotInfo = plots[i];
682       boost::cmatch matches;
683       if (regex_utils::match(plotInfo.c_str(), matches, plotInfoRegex_) &&
684           (matches.size() > 3) )
685       {
686          plotStorageId = matches[1];
687          renderedSize.width = boost::lexical_cast<int>(matches[2]);
688          renderedSize.height = boost::lexical_cast<int>(matches[3]);
689       }
690 
691       // create next plot
692       PtrPlot ptrPlot(new Plot(graphicsDevice_,
693                                graphicsPath_,
694                                plotStorageId,
695                                renderedSize));
696 
697       // ensure it actually exists on disk before we add it
698       if (ptrPlot->hasValidStorage())
699       {
700          plots_.push_back(ptrPlot);
701 
702          // set it as active if necessary
703          if (plotStorageId == activePlotStorageId)
704             activePlot_ = i;
705       }
706    }
707 
708    // if we didn't find the active plot or if it exceeds the size
709    // of the circular buffer (would happen when migrating from a
710    // suspended session that allowed more plots)
711    if ((activePlot_ == -1) ||
712        (activePlot_ > (gsl::narrow_cast<int>(plots_.size()) - 1)))
713    {
714       activePlot_ = gsl::narrow_cast<int>(plots_.size()) - 1;
715    }
716 
717    // restore snapshot for the active plot
718    if (hasPlot())
719       renderActivePlotToDisplay();
720 
721    return Success();
722 }
723 
724 namespace {
725 
copyDirectory(const FilePath & srcDir,const FilePath & targetDir)726 Error copyDirectory(const FilePath& srcDir, const FilePath& targetDir)
727 {
728    Error error = targetDir.removeIfExists();
729    if (error)
730       return error;
731 
732    error = targetDir.ensureDirectory();
733    if (error)
734       return error;
735 
736    std::vector<FilePath> srcFiles;
737    error = srcDir.getChildren(srcFiles);
738    if (error)
739       return error;
740    for (const FilePath& srcFile : srcFiles)
741    {
742       FilePath targetFile = targetDir.completePath(srcFile.getFilename());
743       Error error = srcFile.copy(targetFile);
744       if (error)
745          return error;
746    }
747 
748    return Success();
749 }
750 
751 } // anonymous namespace
752 
serialize(const FilePath & saveToPath)753 Error PlotManager::serialize(const FilePath& saveToPath)
754 {
755    // save plots state
756    Error error = savePlotsState();
757    if (error)
758       return error;
759 
760    // copy the plots dir to the save to path
761    return copyDirectory(graphicsPath_, saveToPath);
762 }
763 
deserialize(const FilePath & restoreFromPath)764 Error PlotManager::deserialize(const FilePath& restoreFromPath)
765 {
766    // copy the restoreFromPath to the graphics path
767    Error error = copyDirectory(restoreFromPath, graphicsPath_);
768    if (error)
769       return error;
770 
771    // restore plots state
772    return restorePlotsState();
773 }
774 
775 
onDeviceNewPage(SEXP previousPageSnapshot)776 void PlotManager::onDeviceNewPage(SEXP previousPageSnapshot)
777 {
778    if (suppressDeviceEvents_)
779       return;
780 
781    // make sure the graphics path exists (may have been blown away
782    // by call to dev.off or other call to removeAllPlots)
783    Error error = graphicsPath_.ensureDirectory();
784    if (error)
785    {
786       Error graphicsError(errc::PlotFileError, error, ERROR_LOCATION);
787       logAndReportError(graphicsError, ERROR_LOCATION);
788       return;
789    }
790 
791    // if we have a plot with unrendered changes then save the previous snapshot
792    if (hasPlot() && hasChanges())
793    {
794       if (previousPageSnapshot != R_NilValue)
795       {
796          r::sexp::Protect protectSnapshot(previousPageSnapshot);
797          Error error = activePlot().renderFromDisplaySnapshot(
798                                                          previousPageSnapshot);
799          if (error)
800             logAndReportError(error, ERROR_LOCATION);
801       }
802       else
803       {
804          LOG_WARNING_MESSAGE(
805                      "onDeviceNewPage was not passed a previousPageSnapshot");
806       }
807    }
808 
809    // if we are replaying a manipulator then this plot replaces the
810    // existing plot
811    if (hasPlot() &&
812        plotManipulatorManager().replayingManipulator())
813    {
814       // create plot using the active plot's manipulator
815       PtrPlot ptrPlot(new Plot(graphicsDevice_,
816                                graphicsPath_,
817                                activePlot().manipulatorSEXP()));
818 
819       // replace active plot
820       plots_[activePlotIndex()] = ptrPlot;
821    }
822    else
823    {
824       // if there is already a plot active then release its
825       // in-memory resources
826       if (hasPlot())
827          activePlot().purgeInMemoryResources();
828 
829       // create new plot (use pending manipulator, if any)
830       PtrPlot ptrPlot(new Plot(graphicsDevice_,
831                                graphicsPath_,
832                                plotManipulatorManager().pendingManipulatorSEXP()));
833 
834       // if we're full then remove the first plot's files before adding a new one
835       if (plots_.full())
836       {
837          Error error = plots_.front()->removeFiles();
838          if (error)
839             LOG_ERROR(error);
840       }
841 
842       // add the plot
843       plots_.push_back(ptrPlot);
844       activePlot_ = gsl::narrow_cast<int>(plots_.size()) - 1;
845    }
846 
847    // once we render the new plot we always reset pending manipulator state
848    plotManipulatorManager().clearPendingManipulatorState();
849 
850    // ensure updates
851    invalidateActivePlot();
852 }
853 
onDeviceDrawing()854 void PlotManager::onDeviceDrawing()
855 {
856    if (suppressDeviceEvents_)
857       return;
858 
859    invalidateActivePlot();
860 }
861 
onDeviceResized()862 void PlotManager::onDeviceResized()
863 {
864    if (suppressDeviceEvents_)
865       return;
866 
867    invalidateActivePlot();
868 }
869 
onDeviceClosed()870 void PlotManager::onDeviceClosed()
871 {
872    if (suppressDeviceEvents_)
873       return;
874 
875    // clear plots
876    activePlot_ = -1;
877    plots_.clear();
878 
879    // trip changes flag to ensure repaint
880    setDisplayHasChanges(true);
881 
882    // remove all files
883    Error error = plotsStateFile_.removeIfExists();
884    if (error)
885       LOG_ERROR(error);
886 
887    error = graphicsPath_.removeIfExists();
888    if (error)
889       LOG_ERROR(error);
890 }
891 
activePlot() const892 Plot& PlotManager::activePlot() const
893 {
894    return *(plots_[activePlot_]);
895 }
896 
isValidPlotIndex(int index) const897 bool PlotManager::isValidPlotIndex(int index) const
898 {
899    return (index >= 0) && (index < (int)plots_.size());
900 }
901 
hasPlot() const902 bool PlotManager::hasPlot() const
903 {
904    return activePlot_ >= 0;
905 }
906 
setDisplayHasChanges(bool hasChanges)907 void PlotManager::setDisplayHasChanges(bool hasChanges)
908 {
909    displayHasChanges_ = hasChanges;
910 
911    if (hasChanges)
912       lastChange_ = boost::posix_time::microsec_clock::universal_time();
913    else
914       lastChange_ = boost::posix_time::not_a_date_time;
915 }
916 
917 
invalidateActivePlot()918 void PlotManager::invalidateActivePlot()
919 {
920    setDisplayHasChanges(true);
921 
922    if (hasPlot())
923       activePlot().invalidate();
924 }
925 
926 // render active plot to display (used in setActivePlot and onSessionResume)
renderActivePlotToDisplay()927 void PlotManager::renderActivePlotToDisplay()
928 {
929    suppressDeviceEvents_ = true;
930 
931    // attempt to render the active plot -- notify end user if there is an error
932    Error error = activePlot().renderToDisplay();
933    if (error)
934       reportError(error);
935 
936    suppressDeviceEvents_ = false;
937 
938 }
939 
940 
plotIndexError(int index,const ErrorLocation & location) const941 Error PlotManager::plotIndexError(int index, const ErrorLocation& location)
942                                                                         const
943 {
944    Error error(errc::InvalidPlotIndex, location);
945    error.addProperty("index", index);
946    return error;
947 }
948 
949 
emptyImageFilename() const950 std::string PlotManager::emptyImageFilename() const
951 {
952    return "empty." + graphicsDevice_.imageFileExtension();
953 }
954 
955 } // namespace graphics
956 } // namespace session
957 } // namespace r
958 } // namespace rstudio
959 
960 
961 
962