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