code: # coding: utf-8
  # /*##########################################################################
  #
  # Copyright (c) 2004-2020 European Synchrotron Radiation Facility
  #
  # Permission is hereby granted, free of charge, to any person obtaining a copy
  # of this software and associated documentation files (the "Software"), to deal
  # in the Software without restriction, including without limitation the rights
  # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  # copies of the Software, and to permit persons to whom the Software is
  # furnished to do so, subject to the following conditions:
  #
  # The above copyright notice and this permission notice shall be included in
  # all copies or substantial portions of the Software.
  #
  # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  # THE SOFTWARE.
  #
  # ###########################################################################*/
  """
  :mod:`silx.gui.plot.actions.io` provides a set of QAction relative of inputs
  and outputs for a :class:`.PlotWidget`.
  
  The following QAction are available:
  
  - :class:`CopyAction`
  - :class:`PrintAction`
  - :class:`SaveAction`
  """
  
  from __future__ import division
  
  __authors__ = ["V.A. Sole", "T. Vincent", "P. Knobel"]
  __license__ = "MIT"
  __date__ = "25/09/2020"
  
  from . import PlotAction
  from silx.io.utils import save1D, savespec, NEXUS_HDF5_EXT
  from silx.io.nxdata import save_NXdata
  import logging
  import sys
  import os.path
  from collections import OrderedDict
  import traceback
  import numpy
  from silx.utils.deprecation import deprecated
  from silx.gui import qt, printer
  from silx.gui.dialog.GroupDialog import GroupDialog
  from silx.third_party.EdfFile import EdfFile
  from silx.third_party.TiffIO import TiffIO
  from ...utils.image import convertArrayToQImage
  if sys.version_info[0] == 3:
      from io import BytesIO
  else:
      import cStringIO as _StringIO
      BytesIO = _StringIO.StringIO
  
  _logger = logging.getLogger(__name__)
  
  _NEXUS_HDF5_EXT_STR = ' '.join(['*' + ext for ext in NEXUS_HDF5_EXT])
  
  
  def selectOutputGroup(h5filename):
      """Open a dialog to prompt the user to select a group in
      which to output data.
  
      :param str h5filename: name of an existing HDF5 file
      :rtype: str
      :return: Name of output group, or None if the dialog was cancelled
      """
      dialog = GroupDialog()
      dialog.addFile(h5filename)
      dialog.setWindowTitle("Select an output group")
      if not dialog.exec_():
          return None
      return dialog.getSelectedDataUrl().data_path()
  
  
  class SaveAction(PlotAction):
      """QAction for saving Plot content.
  
      It opens a Save as... dialog.
  
      :param plot: :class:`.PlotWidget` instance on which to operate.
      :param parent: See :class:`QAction`.
      """
  
      SNAPSHOT_FILTER_SVG = 'Plot Snapshot as SVG (*.svg)'
      SNAPSHOT_FILTER_PNG = 'Plot Snapshot as PNG (*.png)'
  
      DEFAULT_ALL_FILTERS = (SNAPSHOT_FILTER_PNG, SNAPSHOT_FILTER_SVG)
  
      # Dict of curve filters with CSV-like format
      # Using ordered dict to guarantee filters order
      # Note: '%.18e' is numpy.savetxt default format
      CURVE_FILTERS_TXT = OrderedDict((
          ('Curve as Raw ASCII (*.txt)',
           {'fmt': '%.18e', 'delimiter': ' ', 'header': False}),
          ('Curve as ";"-separated CSV (*.csv)',
           {'fmt': '%.18e', 'delimiter': ';', 'header': True}),
          ('Curve as ","-separated CSV (*.csv)',
           {'fmt': '%.18e', 'delimiter': ',', 'header': True}),
          ('Curve as tab-separated CSV (*.csv)',
           {'fmt': '%.18e', 'delimiter': '\t', 'header': True}),
          ('Curve as OMNIC CSV (*.csv)',
           {'fmt': '%.7E', 'delimiter': ',', 'header': False}),
          ('Curve as SpecFile (*.dat)',
           {'fmt': '%.10g', 'delimiter': '', 'header': False})
      ))
  
      CURVE_FILTER_NPY = 'Curve as NumPy binary file (*.npy)'
  
      CURVE_FILTER_NXDATA = 'Curve as NXdata (%s)' % _NEXUS_HDF5_EXT_STR
  
      DEFAULT_CURVE_FILTERS = list(CURVE_FILTERS_TXT.keys()) + [
          CURVE_FILTER_NPY, CURVE_FILTER_NXDATA]
  
      DEFAULT_ALL_CURVES_FILTERS = ("All curves as SpecFile (*.dat)",)
  
      IMAGE_FILTER_EDF = 'Image data as EDF (*.edf)'
      IMAGE_FILTER_TIFF = 'Image data as TIFF (*.tif)'
      IMAGE_FILTER_NUMPY = 'Image data as NumPy binary file (*.npy)'
      IMAGE_FILTER_ASCII = 'Image data as ASCII (*.dat)'
      IMAGE_FILTER_CSV_COMMA = 'Image data as ,-separated CSV (*.csv)'
      IMAGE_FILTER_CSV_SEMICOLON = 'Image data as ;-separated CSV (*.csv)'
      IMAGE_FILTER_CSV_TAB = 'Image data as tab-separated CSV (*.csv)'
      IMAGE_FILTER_RGB_PNG = 'Image as PNG (*.png)'
      IMAGE_FILTER_NXDATA = 'Image as NXdata (%s)' % _NEXUS_HDF5_EXT_STR
  
      DEFAULT_IMAGE_FILTERS = (IMAGE_FILTER_EDF,
                               IMAGE_FILTER_TIFF,
                               IMAGE_FILTER_NUMPY,
                               IMAGE_FILTER_ASCII,
                               IMAGE_FILTER_CSV_COMMA,
                               IMAGE_FILTER_CSV_SEMICOLON,
                               IMAGE_FILTER_CSV_TAB,
                               IMAGE_FILTER_RGB_PNG,
                               IMAGE_FILTER_NXDATA)
  
      SCATTER_FILTER_NXDATA = 'Scatter as NXdata (%s)' % _NEXUS_HDF5_EXT_STR
      DEFAULT_SCATTER_FILTERS = (SCATTER_FILTER_NXDATA,)
  
      # filters for which we don't want an "overwrite existing file" warning
      DEFAULT_APPEND_FILTERS = (CURVE_FILTER_NXDATA, IMAGE_FILTER_NXDATA,
                                SCATTER_FILTER_NXDATA)
  
      def __init__(self, plot, parent=None):
          self._filters = {
              'all': OrderedDict(),
              'curve': OrderedDict(),
              'curves': OrderedDict(),
              'image': OrderedDict(),
              'scatter': OrderedDict()}
  
          self._appendFilters = list(self.DEFAULT_APPEND_FILTERS)
  
          # Initialize filters
          for nameFilter in self.DEFAULT_ALL_FILTERS:
              self.setFileFilter(
                  dataKind='all', nameFilter=nameFilter, func=self._saveSnapshot)
  
          for nameFilter in self.DEFAULT_CURVE_FILTERS:
              self.setFileFilter(
                  dataKind='curve', nameFilter=nameFilter, func=self._saveCurve)
  
          for nameFilter in self.DEFAULT_ALL_CURVES_FILTERS:
              self.setFileFilter(
                  dataKind='curves', nameFilter=nameFilter, func=self._saveCurves)
  
          for nameFilter in self.DEFAULT_IMAGE_FILTERS:
              self.setFileFilter(
                  dataKind='image', nameFilter=nameFilter, func=self._saveImage)
  
          for nameFilter in self.DEFAULT_SCATTER_FILTERS:
              self.setFileFilter(
                  dataKind='scatter', nameFilter=nameFilter, func=self._saveScatter)
  
          super(SaveAction, self).__init__(
              plot, icon='document-save', text='Save as...',
              tooltip='Save curve/image/plot snapshot dialog',
              triggered=self._actionTriggered,
              checkable=False, parent=parent)
          self.setShortcut(qt.QKeySequence.Save)
          self.setShortcutContext(qt.Qt.WidgetShortcut)
  
      @staticmethod
      def _errorMessage(informativeText='', parent=None):
          """Display an error message."""
          # TODO issue with QMessageBox size fixed and too small
          msg = qt.QMessageBox(parent)
          msg.setIcon(qt.QMessageBox.Critical)
          msg.setInformativeText(informativeText + ' ' + str(sys.exc_info()[1]))
          msg.setDetailedText(traceback.format_exc())
          msg.exec_()
  
      def _saveSnapshot(self, plot, filename, nameFilter):
          """Save a snapshot of the :class:`PlotWindow` widget.
  
          :param str filename: The name of the file to write
          :param str nameFilter: The selected name filter
          :return: False if format is not supported or save failed,
                   True otherwise.
          """
          if nameFilter == self.SNAPSHOT_FILTER_PNG:
              fileFormat = 'png'
          elif nameFilter == self.SNAPSHOT_FILTER_SVG:
              fileFormat = 'svg'
          else:  # Format not supported
              _logger.error(
                  'Saving plot snapshot failed: format not supported')
              return False
  
          plot.saveGraph(filename, fileFormat=fileFormat)
          return True
  
      def _getAxesLabels(self, item):
          # If curve has no associated label, get the default from the plot
          xlabel = item.getXLabel() or self.plot.getXAxis().getLabel()
          ylabel = item.getYLabel() or self.plot.getYAxis().getLabel()
          return xlabel, ylabel
  
      def _get1dData(self, item):
          "provide xdata, [ydata], xlabel, [ylabel] and manages error bars"
          xlabel, ylabel = self._getAxesLabels(item)
          x_data = item.getXData(copy=False)
          y_data = item.getYData(copy=False)
          x_err = item.getXErrorData(copy=False)
          y_err = item.getYErrorData(copy=False)
          labels = [ylabel]
          data = [y_data]
  
          if x_err is not None:
              if numpy.isscalar(x_err):
                  data.append(numpy.zeros_like(y_data) + x_err)
                  labels.append(xlabel + "_errors")
              elif x_err.ndim == 1:
                  data.append(x_err)
                  labels.append(xlabel + "_errors")
              elif x_err.ndim == 2:
                  data.append(x_err[0])
                  labels.append(xlabel + "_errors_below")
                  data.append(x_err[1])
                  labels.append(xlabel + "_errors_above")
  
          if y_err is not None:
              if numpy.isscalar(y_err):
                  data.append(numpy.zeros_like(y_data) + y_err)
                  labels.append(ylabel + "_errors")
              elif y_err.ndim == 1:
                  data.append(y_err)
                  labels.append(ylabel + "_errors")
              elif y_err.ndim == 2:
                  data.append(y_err[0])
                  labels.append(ylabel + "_errors_below")
                  data.append(y_err[1])
                  labels.append(ylabel + "_errors_above")
          return x_data, data, xlabel, labels
  
      @staticmethod
      def _selectWriteableOutputGroup(filename, parent):
          if os.path.exists(filename) and os.path.isfile(filename) \
                  and os.access(filename, os.W_OK):
              entryPath = selectOutputGroup(filename)
              if entryPath is None:
                  _logger.info("Save operation cancelled")
                  return None
              return entryPath
          elif not os.path.exists(filename):
              # create new entry in new file
              return "/entry"
          else:
              SaveAction._errorMessage('Save failed (file access issue)\n', parent=parent)
              return None
  
      def _saveCurveAsNXdata(self, curve, filename):
          entryPath = self._selectWriteableOutputGroup(filename, parent=self.plot)
          if entryPath is None:
              return False
  
          xlabel, ylabel = self._getAxesLabels(curve)
  
          return save_NXdata(
              filename,
              nxentry_name=entryPath,
              signal=curve.getYData(copy=False),
              axes=[curve.getXData(copy=False)],
              signal_name="y",
              axes_names=["x"],
              signal_long_name=ylabel,
              axes_long_names=[xlabel],
              signal_errors=curve.getYErrorData(copy=False),
              axes_errors=[curve.getXErrorData(copy=True)],
              title=self.plot.getGraphTitle())
  
      def _saveCurve(self, plot, filename, nameFilter):
          """Save a curve from the plot.
  
          :param str filename: The name of the file to write
          :param str nameFilter: The selected name filter
          :return: False if format is not supported or save failed,
                   True otherwise.
          """
          if nameFilter not in self.DEFAULT_CURVE_FILTERS:
              return False
  
          # Check if a curve is to be saved
          curve = plot.getActiveCurve()
          # before calling _saveCurve, if there is no selected curve, we
          # make sure there is only one curve on the graph
          if curve is None:
              curves = plot.getAllCurves()
              if not curves:
                  self._errorMessage("No curve to be saved", parent=self.plot)
                  return False
              curve = curves[0]
  
          if nameFilter in self.CURVE_FILTERS_TXT:
              filter_ = self.CURVE_FILTERS_TXT[nameFilter]
              fmt = filter_['fmt']
              csvdelim = filter_['delimiter']
              autoheader = filter_['header']
          else:
              # .npy or nxdata
              fmt, csvdelim, autoheader = ("", "", False)
  
          if nameFilter == self.CURVE_FILTER_NXDATA:
              return self._saveCurveAsNXdata(curve, filename)
  
          xdata, data, xlabel, labels = self._get1dData(curve)
  
          try:
              save1D(filename,
                     xdata, data,
                     xlabel, labels,
                     fmt=fmt, csvdelim=csvdelim,
                     autoheader=autoheader)
          except IOError:
              self._errorMessage('Save failed\n', parent=self.plot)
              return False
  
          return True
  
      def _saveCurves(self, plot, filename, nameFilter):
          """Save all curves from the plot.
  
          :param str filename: The name of the file to write
          :param str nameFilter: The selected name filter
          :return: False if format is not supported or save failed,
                   True otherwise.
          """
          if nameFilter not in self.DEFAULT_ALL_CURVES_FILTERS:
              return False
  
          curves = plot.getAllCurves()
          if not curves:
              self._errorMessage("No curves to be saved", parent=self.plot)
              return False
  
          curve = curves[0]
          scanno = 1
          try:
              xdata, data, xlabel, labels = self._get1dData(curve)
  
              specfile = savespec(filename,
                                  xdata, data,
                                  xlabel, labels,
                                  fmt="%.7g", scan_number=1, mode="w",
                                  write_file_header=True,
                                  close_file=False)
          except IOError:
              self._errorMessage('Save failed\n', parent=self.plot)
              return False
  
          for curve in curves[1:]:
              try:
                  scanno += 1
                  xdata, data, xlabel, labels = self._get1dData(curve)
                  specfile = savespec(specfile,
                                      xdata, data,
                                      xlabel, labels,
                                      fmt="%.7g", scan_number=scanno,
                                      write_file_header=False,
                                      close_file=False)
              except IOError:
                  self._errorMessage('Save failed\n', parent=self.plot)
                  return False
          specfile.close()
  
          return True
  
      def _saveImage(self, plot, filename, nameFilter):
          """Save an image from the plot.
  
          :param str filename: The name of the file to write
          :param str nameFilter: The selected name filter
          :return: False if format is not supported or save failed,
                   True otherwise.
          """
          if nameFilter not in self.DEFAULT_IMAGE_FILTERS:
              return False
  
          image = plot.getActiveImage()
          if image is None:
              qt.QMessageBox.warning(
                  plot, "No Data", "No image to be saved")
              return False
  
          data = image.getData(copy=False)
  
          # TODO Use silx.io for writing files
          if nameFilter == self.IMAGE_FILTER_EDF:
              edfFile = EdfFile(filename, access="w+")
              edfFile.WriteImage({}, data, Append=0)
              return True
  
          elif nameFilter == self.IMAGE_FILTER_TIFF:
              tiffFile = TiffIO(filename, mode='w')
              tiffFile.writeImage(data, software='silx')
              return True
  
          elif nameFilter == self.IMAGE_FILTER_NUMPY:
              try:
                  numpy.save(filename, data)
              except IOError:
                  self._errorMessage('Save failed\n', parent=self.plot)
                  return False
              return True
  
          elif nameFilter == self.IMAGE_FILTER_NXDATA:
              entryPath = self._selectWriteableOutputGroup(filename, parent=self.plot)
              if entryPath is None:
                  return False
              xorigin, yorigin = image.getOrigin()
              xscale, yscale = image.getScale()
              xaxis = xorigin + xscale * numpy.arange(data.shape[1])
              yaxis = yorigin + yscale * numpy.arange(data.shape[0])
              xlabel, ylabel = self._getAxesLabels(image)
              interpretation = "image" if len(data.shape) == 2 else "rgba-image"
  
              return save_NXdata(filename,
                                 nxentry_name=entryPath,
                                 signal=data,
                                 axes=[yaxis, xaxis],
                                 signal_name="image",
                                 axes_names=["y", "x"],
                                 axes_long_names=[ylabel, xlabel],
                                 title=plot.getGraphTitle(),
                                 interpretation=interpretation)
  
          elif nameFilter in (self.IMAGE_FILTER_ASCII,
                              self.IMAGE_FILTER_CSV_COMMA,
                              self.IMAGE_FILTER_CSV_SEMICOLON,
                              self.IMAGE_FILTER_CSV_TAB):
              csvdelim, filetype = {
                  self.IMAGE_FILTER_ASCII: (' ', 'txt'),
                  self.IMAGE_FILTER_CSV_COMMA: (',', 'csv'),
                  self.IMAGE_FILTER_CSV_SEMICOLON: (';', 'csv'),
                  self.IMAGE_FILTER_CSV_TAB: ('\t', 'csv'),
                  }[nameFilter]
  
              height, width = data.shape
              rows, cols = numpy.mgrid[0:height, 0:width]
              try:
                  save1D(filename, rows.ravel(), (cols.ravel(), data.ravel()),
                         filetype=filetype,
                         xlabel='row',
                         ylabels=['column', 'value'],
                         csvdelim=csvdelim,
                         autoheader=True)
  
              except IOError:
                  self._errorMessage('Save failed\n', parent=self.plot)
                  return False
              return True
  
          elif nameFilter == self.IMAGE_FILTER_RGB_PNG:
              # Get displayed image
              rgbaImage = image.getRgbaImageData(copy=False)
              # Convert RGB QImage
              qimage = convertArrayToQImage(rgbaImage[:, :, :3])
  
              if qimage.save(filename, 'PNG'):
                  return True
              else:
                  _logger.error('Failed to save image as %s', filename)
                  qt.QMessageBox.critical(
                      self.parent(),
                      'Save image as',
                      'Failed to save image')
  
          return False
  
      def _saveScatter(self, plot, filename, nameFilter):
          """Save an image from the plot.
  
          :param str filename: The name of the file to write
          :param str nameFilter: The selected name filter
          :return: False if format is not supported or save failed,
                   True otherwise.
          """
          if nameFilter not in self.DEFAULT_SCATTER_FILTERS:
              return False
  
          if nameFilter == self.SCATTER_FILTER_NXDATA:
              entryPath = self._selectWriteableOutputGroup(filename, parent=self.plot)
              if entryPath is None:
                  return False
              scatter = plot.getScatter()
  
              x = scatter.getXData(copy=False)
              y = scatter.getYData(copy=False)
              z = scatter.getValueData(copy=False)
  
              xerror = scatter.getXErrorData(copy=False)
              if isinstance(xerror, float):
                  xerror = xerror * numpy.ones(x.shape, dtype=numpy.float32)
  
              yerror = scatter.getYErrorData(copy=False)
              if isinstance(yerror, float):
                  yerror = yerror * numpy.ones(x.shape, dtype=numpy.float32)
  
              xlabel = plot.getGraphXLabel()
              ylabel = plot.getGraphYLabel()
  
              return save_NXdata(
                  filename,
                  nxentry_name=entryPath,
                  signal=z,
                  axes=[x, y],
                  signal_name="values",
                  axes_names=["x", "y"],
                  axes_long_names=[xlabel, ylabel],
                  axes_errors=[xerror, yerror],
                  title=plot.getGraphTitle())
  
      def setFileFilter(self, dataKind, nameFilter, func, index=None, appendToFile=False):
          """Set a name filter to add/replace a file format support
  
          :param str dataKind:
              The kind of data for which the provided filter is valid.
              One of: 'all', 'curve', 'curves', 'image', 'scatter'
          :param str nameFilter: The name filter in the QFileDialog.
              See :meth:`QFileDialog.setNameFilters`.
          :param callable func: The function to call to perform saving.
              Expected signature is:
              bool func(PlotWidget plot, str filename, str nameFilter)
          :param bool appendToFile: True to append the data into the selected
              file.
          :param integer index: Index of the filter in the final list (or None)
          """
          assert dataKind in ('all', 'curve', 'curves', 'image', 'scatter')
  
          if appendToFile:
              self._appendFilters.append(nameFilter)
  
          # first append or replace the new filter to prevent colissions
          self._filters[dataKind][nameFilter] = func
          if index is None:
              # we are already done
              return
  
          # get the current ordered list of keys
          keyList = list(self._filters[dataKind].keys())
  
          # deal with negative indices
          if index < 0:
              index = len(keyList) + index
              if index < 0:
                  index = 0
  
          if index >= len(keyList):
              # nothing to be done, already at the end
              txt = 'Requested index %d impossible, already at the end' % index
              _logger.info(txt)
              return
  
          # get the new ordered list
          oldIndex = keyList.index(nameFilter)
          del keyList[oldIndex]
          keyList.insert(index, nameFilter)
  
          # build the new filters
          newFilters = OrderedDict()
          for key in keyList:
              newFilters[key] = self._filters[dataKind][key]
  
          # and update the filters
          self._filters[dataKind] = newFilters
          return
  
      def getFileFilters(self, dataKind):
          """Returns the nameFilter and associated function for a kind of data.
  
          :param str dataKind:
              The kind of data for which the provided filter is valid.
              On of: 'all', 'curve', 'curves', 'image', 'scatter'
          :return: {nameFilter: function} associations.
          :rtype: collections.OrderedDict
          """
          assert dataKind in ('all', 'curve', 'curves', 'image', 'scatter')
  
          return self._filters[dataKind].copy()
  
      def _actionTriggered(self, checked=False):
          """Handle save action."""
          # Set-up filters
          filters = OrderedDict()
  
          # Add image filters if there is an active image
          if self.plot.getActiveImage() is not None:
              filters.update(self._filters['image'].items())
  
          # Add curve filters if there is a curve to save
          if (self.plot.getActiveCurve() is not None or
                  len(self.plot.getAllCurves()) == 1):
              filters.update(self._filters['curve'].items())
          if len(self.plot.getAllCurves()) >= 1:
              filters.update(self._filters['curves'].items())
  
          # Add scatter filters if there is a scatter
          # todo: CSV
          if self.plot.getScatter() is not None:
              filters.update(self._filters['scatter'].items())
  
          filters.update(self._filters['all'].items())
  
          # Create and run File dialog
          dialog = qt.QFileDialog(self.plot)
          dialog.setOption(dialog.DontUseNativeDialog)
          dialog.setWindowTitle("Output File Selection")
          dialog.setModal(1)
          dialog.setNameFilters(list(filters.keys()))
  
          dialog.setFileMode(dialog.AnyFile)
          dialog.setAcceptMode(dialog.AcceptSave)
  
          def onFilterSelection(filt_):
              # disable overwrite confirmation for NXdata types,
              # because we append the data to existing files
              if filt_ in self._appendFilters:
                  dialog.setOption(dialog.DontConfirmOverwrite)
              else:
                  dialog.setOption(dialog.DontConfirmOverwrite, False)
  
          dialog.filterSelected.connect(onFilterSelection)
  
          if not dialog.exec_():
              return False
  
          nameFilter = dialog.selectedNameFilter()
          filename = dialog.selectedFiles()[0]
          dialog.close()
  
          if '(' in nameFilter and ')' == nameFilter.strip()[-1]:
              # Check for correct file extension
              # Extract file extensions as .something
              extensions = [ext[ext.find('.'):] for ext in
                            nameFilter[nameFilter.find('(') + 1:-1].split()]
              for ext in extensions:
                  if (len(filename) > len(ext) and
                          filename[-len(ext):].lower() == ext.lower()):
                      break
              else:  # filename has no extension supported in nameFilter, add one
                  if len(extensions) >= 1:
                      filename += extensions[0]
  
          # Handle save
          func = filters.get(nameFilter, None)
          if func is not None:
              return func(self.plot, filename, nameFilter)
          else:
              _logger.error('Unsupported file filter: %s', nameFilter)
              return False
  
  
  def _plotAsPNG(plot):
      """Save a :class:`Plot` as PNG and return the payload.
  
      :param plot: The :class:`Plot` to save
      """
      pngFile = BytesIO()
      plot.saveGraph(pngFile, fileFormat='png')
      pngFile.flush()
      pngFile.seek(0)
      data = pngFile.read()
      pngFile.close()
      return data
  
  
  class PrintAction(PlotAction):
      """QAction for printing the plot.
  
      It opens a Print dialog.
  
      Current implementation print a bitmap of the plot area and not vector
      graphics, so printing quality is not great.
  
      :param plot: :class:`.PlotWidget` instance on which to operate.
      :param parent: See :class:`QAction`.
      """
  
      def __init__(self, plot, parent=None):
          super(PrintAction, self).__init__(
              plot, icon='document-print', text='Print...',
              tooltip='Open print dialog',
              triggered=self.printPlot,
              checkable=False, parent=parent)
          self.setShortcut(qt.QKeySequence.Print)
          self.setShortcutContext(qt.Qt.WidgetShortcut)
  
      def getPrinter(self):
          """The QPrinter instance used by the PrintAction.
  
          :rtype: QPrinter
          """
          return printer.getDefaultPrinter()
  
      @property
      @deprecated(replacement="getPrinter()", since_version="0.8.0")
      def printer(self):
          return self.getPrinter()
  
      def printPlotAsWidget(self):
          """Open the print dialog and print the plot.
  
          Use :meth:`QWidget.render` to print the plot
  
          :return: True if successful
          """
          dialog = qt.QPrintDialog(self.getPrinter(), self.plot)
          dialog.setWindowTitle('Print Plot')
          if not dialog.exec_():
              return False
  
          # Print a snapshot of the plot widget at the top of the page
          widget = self.plot.centralWidget()
  
          painter = qt.QPainter()
          if not painter.begin(self.getPrinter()):
              return False
  
          pageRect = self.getPrinter().pageRect()
          xScale = pageRect.width() / widget.width()
          yScale = pageRect.height() / widget.height()
          scale = min(xScale, yScale)
  
          painter.translate(pageRect.width() / 2., 0.)
          painter.scale(scale, scale)
          painter.translate(-widget.width() / 2., 0.)
          widget.render(painter)
          painter.end()
  
          return True
  
      def printPlot(self):
          """Open the print dialog and print the plot.
  
          Use :meth:`Plot.saveGraph` to print the plot.
  
          :return: True if successful
          """
          # Init printer and start printer dialog
          dialog = qt.QPrintDialog(self.getPrinter(), self.plot)
          dialog.setWindowTitle('Print Plot')
          if not dialog.exec_():
              return False
  
          # Save Plot as PNG and make a pixmap from it with default dpi
          pngData = _plotAsPNG(self.plot)
  
          pixmap = qt.QPixmap()
          pixmap.loadFromData(pngData, 'png')
  
          xScale = self.getPrinter().pageRect().width() / pixmap.width()
          yScale = self.getPrinter().pageRect().height() / pixmap.height()
          scale = min(xScale, yScale)
  
          # Draw pixmap with painter
          painter = qt.QPainter()
          if not painter.begin(self.getPrinter()):
              return False
  
          painter.drawPixmap(0, 0,
                             pixmap.width() * scale,
                             pixmap.height() * scale,
                             pixmap)
          painter.end()
  
          return True
  
  
  class CopyAction(PlotAction):
      """QAction to copy :class:`.PlotWidget` content to clipboard.
  
      :param plot: :class:`.PlotWidget` instance on which to operate
      :param parent: See :class:`QAction`
      """
  
      def __init__(self, plot, parent=None):
          super(CopyAction, self).__init__(
              plot, icon='edit-copy', text='Copy plot',
              tooltip='Copy a snapshot of the plot into the clipboard',
              triggered=self.copyPlot,
              checkable=False, parent=parent)
          self.setShortcut(qt.QKeySequence.Copy)
          self.setShortcutContext(qt.Qt.WidgetShortcut)
  
      def copyPlot(self):
          """Copy plot content to the clipboard as a bitmap."""
          # Save Plot as PNG and make a QImage from it with default dpi
          pngData = _plotAsPNG(self.plot)
          image = qt.QImage.fromData(pngData, 'png')
          qt.QApplication.clipboard().setImage(image)
  
apis: numpy.ones numpy.save numpy.zeros_like numpy.arange numpy.isscalar