From cb007f8d63823fa30106bb2d5e4906e5c5c9344a Mon Sep 17 00:00:00 2001 From: David Koop Date: Wed, 16 Jul 2014 18:18:16 -0700 Subject: [PATCH] Have spreadsheet use FileMode subclasses In order to not duplicate code for spreadsheet cell dumps and FileMode outputs, make it possible for the spreadsheet to use the output module code when dumping cells to files. Next step would be doing this for thumbnails. --- vistrails/core/modules/output_modules.py | 106 +++++++++++------- vistrails/packages/matplotlib/bases.py | 1 + .../packages/spreadsheet/basic_widgets.py | 5 +- .../packages/spreadsheet/spreadsheet_cell.py | 78 +++++++++++-- vistrails/packages/tabledata/common.py | 1 + vistrails/packages/vtk/base_module.py | 1 + 6 files changed, 140 insertions(+), 52 deletions(-) diff --git a/vistrails/core/modules/output_modules.py b/vistrails/core/modules/output_modules.py index 8ee5f9acb..cd3c38f05 100644 --- a/vistrails/core/modules/output_modules.py +++ b/vistrails/core/modules/output_modules.py @@ -83,6 +83,10 @@ def get_all_fields(cls): fields.sort() return fields + @classmethod + def get_local_fields(cls): + return sorted(cls._fields) + @classmethod def get_default(cls, k): f = cls.get_field(k) @@ -246,6 +250,29 @@ def get_mode_tree(cls): for c in reversed(cls_list): c.ensure_mode_dict() + def get_mode_config(self, mode_cls): + mode_config_cls = mode_cls.config_cls + mode_config_dict = {} + configuration = self.force_get_input('configuration') + if configuration is not None: + # want to search through all mode classes in case we have + # base class settings that should trump + cls_list = [mode_config_cls] + mode_config_cls_list = [] + while len(cls_list) > 0: + c = cls_list.pop(0) + if issubclass(c, OutputModeConfig): + mode_config_cls_list.append(c) + cls_list.extend(c.__bases__) + mode_config_cls_list.reverse() + + for mode_config_cls in mode_config_cls_list: + for k, v in configuration.iteritems(): + if k == mode_config_cls.mode_type: + mode_config_dict.update(v) + mode_config = mode_config_cls(mode_config_dict) + return mode_config + def compute(self): mode_cls = None self.ensure_mode_dict() @@ -270,29 +297,8 @@ def compute(self): raise ModuleError(self, "No output mode is valid, output cannot " "be generated") + mode_config = self.get_mode_config(mode_cls) mode = mode_cls() - mode_config_cls = mode_cls.config_cls - mode_config = None - mode_config_dict = {} - configuration = self.force_get_input('configuration') - if configuration is not None: - # want to search through all mode classes in case we have - # base class settings that should trump - cls_list = [mode_config_cls] - mode_config_cls_list = [] - while len(cls_list) > 0: - c = cls_list.pop(0) - if issubclass(c, OutputModeConfig): - mode_config_cls_list.append(c) - cls_list.extend(c.__bases__) - mode_config_cls_list.reverse() - - for mode_config_cls in mode_config_cls_list: - for k, v in configuration.iteritems(): - if k == mode_config_cls.mode_type: - mode_config_dict.update(v) - mode_config = mode_config_cls(mode_config_dict) - self.annotate({"output_mode": mode.mode_type}) mode.compute_output(self, mode_config) @@ -319,12 +325,14 @@ class FileModeConfig(OutputModeConfig): ConfigField('series', False, bool), ConfigField('overwrite', True, bool), ConfigField('seriesPadding', 3, int), - ConfigField('seriesStart', 0, int)] + ConfigField('seriesStart', 0, int), + ConfigField('format', None, str)] class FileMode(OutputMode): mode_type = "file" priority = 1 config_cls = FileModeConfig + formats = [] # need to reset this after each execution! series_next = 0 @@ -333,11 +341,42 @@ class FileMode(OutputMode): def can_compute(cls): return True + @classmethod + def get_formats(cls): + formats = [] + cls_list = [cls] + while len(cls_list) > 0: + c = cls_list.pop(0) + if issubclass(c, FileMode): + if 'formats' in c.__dict__: + return c.formats + cls_list.extend(c.__bases__) + return [] + + def get_format(self, configuration=None): + format_map = {'png': 'png', + 'jpeg': 'jpg', + 'jpg': 'jpg', + 'tif': 'tif', + 'tiff': 'tif'} + if configuration is not None and 'format' in configuration: + conf_format = configuration['format'] + if conf_format.lower() in format_map: + return format_map[conf_format.lower()] + return conf_format + + # default is the first listed if it exists + format_list = self.get_formats() + if len(format_list) > 0: + return format_list[0] + return None + def get_series_num(self): retval = FileMode.series_next FileMode.series_next += 1 return retval - + + # FIXME should add format into this computation def get_filename(self, configuration, full_path=None, filename=None, dirname=None, basename=None, prefix=None, suffix=None, overwrite=True, series=False, series_padding=3): @@ -462,33 +501,19 @@ class FileOutput(OutputModule): class ImageFileModeConfig(FileModeConfig): mode_type = "imageFile" _fields = [ConfigField('width', 800, int), - ConfigField('height', 600, int), - ConfigField('format', None, str)] + ConfigField('height', 600, int)] class ImageFileMode(FileMode): config_cls = ImageFileModeConfig mode_type = "imageFile" - def get_format(self, configuration=None): - format_map = {'png': 'png', - 'jpeg': 'jpg', - 'jpg': 'jpg', - 'tif': 'tif', - 'tiff': 'tif'} - if configuration is not None: - img_format = configuration['format'] - if img_format.lower() in format_map: - return format_map[img_format.lower()] - return img_format - return 'png' - class RichTextOutput(OutputModule): # need specific spreadsheet richtext mode here pass _modules = [OutputModule, GenericOutput, FileOutput] -# need to put WebOutput, ImageOutput, RichTextOutput, SVGOutput, VTKOutput, MplOutput, etc. elsewhere +# need to put WebOutput, ImageOutput, RichTextOutput, SVGOutput, etc. elsewhere class TestOutputModeConfig(unittest.TestCase): def test_fields(self): @@ -519,7 +544,6 @@ def test_get_item(self): def test_get_default(self): self.assertEqual(FileModeConfig.get_default("seriesStart"), 0) - if __name__ == '__main__': import vistrails.core.application diff --git a/vistrails/packages/matplotlib/bases.py b/vistrails/packages/matplotlib/bases.py index 72dc88ad1..82ce70fbd 100644 --- a/vistrails/packages/matplotlib/bases.py +++ b/vistrails/packages/matplotlib/bases.py @@ -174,6 +174,7 @@ class MplQuadContourSet(MplContourSet): class MplFigureToFile(ImageFileMode): config_cls = ImageFileModeConfig + formats = ['pdf', 'png', 'jpg'] def compute_output(self, output_module, configuration=None): value = output_module.get_input('value') diff --git a/vistrails/packages/spreadsheet/basic_widgets.py b/vistrails/packages/spreadsheet/basic_widgets.py index 10bc974e1..7afe81198 100644 --- a/vistrails/packages/spreadsheet/basic_widgets.py +++ b/vistrails/packages/spreadsheet/basic_widgets.py @@ -310,7 +310,10 @@ def display_and_wait(self, output_module, configuration, cell_type, spreadsheetWindow = spreadsheetController.findSpreadsheetWindow() if spreadsheetWindow.echoMode == False: spreadsheetWindow.configShow(show=True) - return spreadsheetWindow.displayCellEvent(e) + cell = spreadsheetWindow.displayCellEvent(e) + if cell is not None: + cell.set_output_module(output_module, configuration) + return cell class SingleCellSheetReference(SheetReference): diff --git a/vistrails/packages/spreadsheet/spreadsheet_cell.py b/vistrails/packages/spreadsheet/spreadsheet_cell.py index 543ab2193..730c6aa02 100644 --- a/vistrails/packages/spreadsheet/spreadsheet_cell.py +++ b/vistrails/packages/spreadsheet/spreadsheet_cell.py @@ -49,6 +49,7 @@ import analogy_api from spreadsheet_config import configuration from vistrails.core.system import strftime +from vistrails.core.modules.output_modules import FileMode ################################################################################ @@ -79,6 +80,8 @@ def __init__(self, parent=None, flags=QtCore.Qt.WindowFlags()): # cell can be captured if it re-implements saveToPNG self._capturingEnabled = (not isinstance(self, QCellWidget) and hasattr(self, 'saveToPNG')) + self._output_module = None + self._output_configuration = None self.connect(self._playerTimer, QtCore.SIGNAL('timeout()'), self.playNextFrame) @@ -250,6 +253,40 @@ def saveToPDF(self, filename): painter.drawPixmap(0, 0, pixmap) painter.end() + def set_output_module(self, output_module, configuration=None): + self._output_module = output_module + self._output_configuration = configuration + + def has_file_output_mode(self): + # from vistrails.core.modules.output_modules import FileMode + if self._output_module is None: + return False + for mode in self._output_module.get_sorted_mode_list(): + if issubclass(mode, FileMode): + return True + return False + + def get_file_output_modes(self): + modes = [] + if self._output_module is not None: + for mode_cls in self._output_module.get_sorted_mode_list(): + if issubclass(mode_cls, FileMode): + modes.append(mode_cls) + return modes + + def get_conf_file_format(self): + if (self._output_configuration is not None and + 'format' in self._output_configuration): + return self._output_configuration['format'] + return None + + def save_via_file_output(self, filename, mode_cls, save_format=None): + mode_config = self._output_module.get_mode_config(mode_cls) + mode_config['file'] = filename + if save_format is not None: + mode_config['format'] = save_format + mode = mode_cls() + mode.compute_output(self._output_module, mode_config) ################################################################################ @@ -300,16 +337,37 @@ def addSaveCellAction(self): def exportCell(self, checked=False): cell = self.sheet.getCell(self.row, self.col) - if not cell.save_formats: - QtGui.QMessageBox.information( - self, "Export cell", - "This cell type doesn't provide any export option") - return - filename = QtGui.QFileDialog.getSaveFileName( - self, "Select a File to Export the Cell", - ".", ';;'.join(cell.save_formats)) - if filename: - cell.dumpToFile(filename) + if cell.has_file_output_mode(): + modes = cell.get_file_output_modes() + formats = [] + format_map = {} + for mode in modes: + for m_format in mode.get_formats(): + if m_format not in format_map: + formats.append(m_format) + format_map[m_format] = mode + selected_filter = None + if cell.get_conf_file_format() is not None: + selected_filter = '(*.%s)' % cell.get_conf_file_format() + (filename, save_format) = \ + QtGui.QFileDialog.getSaveFileNameAndFilter( + self, "Select a File to Export the Cell", + ".", ';;'.join(['(*.%s)' % f for f in formats]), + selected_filter) + if filename: + save_mode = format_map[save_format[3:-1]] + cell.save_via_file_output(filename, save_mode) + else: + if not cell.save_formats: + QtGui.QMessageBox.information( + self, "Export cell", + "This cell type doesn't provide any export option") + return + filename = QtGui.QFileDialog.getSaveFileName( + self, "Select a File to Export the Cell", + ".", ';;'.join(cell.save_formats)) + if filename: + cell.dumpToFile(filename) def createToolBar(self): """ createToolBar() -> None diff --git a/vistrails/packages/tabledata/common.py b/vistrails/packages/tabledata/common.py index 41f0f0d12..c57630347 100644 --- a/vistrails/packages/tabledata/common.py +++ b/vistrails/packages/tabledata/common.py @@ -272,6 +272,7 @@ def compute(self): ['converted_list'])) # names class TableToFileMode(FileMode): + formats = ['html'] def write_html(self, table): document = ['\n' '\n \n' diff --git a/vistrails/packages/vtk/base_module.py b/vistrails/packages/vtk/base_module.py index ed6086d2a..e3afa62e1 100644 --- a/vistrails/packages/vtk/base_module.py +++ b/vistrails/packages/vtk/base_module.py @@ -266,6 +266,7 @@ def ProgressEvent(obj, event): class vtkRendererToFile(ImageFileMode): config_cls = ImageFileModeConfig + formats = ['png', 'jpg', 'tif', 'pnm'] @classmethod def can_compute(cls):