class HelpDockPane(TraitsDockPane): """ A DockPane to view help for the current module """ #### TaskPane interface ############################################### id = 'edu.mit.synbio.cytoflowgui.help_pane' name = 'Help' # the Task that serves as the controller task = Instance(Task) view_plugins = List(IViewPlugin) op_plugins = List(IOperationPlugin) help_id = Str html = HTML("<b>Welcome to Cytoflow!</b>") traits_view = View( Item('html', editor=HTMLEditor(base_url=pathlib.Path(__file__).parent.joinpath( 'help').as_posix()), show_label=False)) def create_contents(self, parent): self.ui = self.edit_traits(kind='subpanel', parent=parent) layout = QtGui.QHBoxLayout() control = HintedWidget() layout.addWidget(self.ui.control) control.setLayout(layout) control.setParent(parent) parent.setWidget(control) return control @on_trait_change('help_id', post_init=True) def _on_help_id_changed(self): for plugin in self.view_plugins: if self.help_id == plugin.view_id: try: self.html = plugin.get_help() except AttributeError: pass finally: return for plugin in self.op_plugins: if self.help_id == plugin.operation_id: try: self.html = plugin.get_help() except AttributeError: pass finally: return
class ConsumerCheckAbout(HasTraits): about_render = HTML(about_html()) traits_view = View( Item('about_render', show_label=False), title="About", height=430, width=400, buttons=['OK'], )
class HTMLEditorDemo(HasTraits): """ Defines the main HTMLEditor demo class. """ # Define a HTML trait to view my_html_trait = HTML(sample_text) # Demo view traits_view = View( UItem( 'my_html_trait', # we specify the editor explicitly in order to set format_text: editor=HTMLEditor(format_text=True)), title='HTMLEditor', buttons=['OK'], width=800, height=600, resizable=True)
class HelpDockPane(TraitsDockPane): """ A DockPane to view help for the current module """ #### TaskPane interface ############################################### id = 'edu.mit.synbio.help_pane' name = 'Help' # the Task that serves as the controller task = Instance(Task) view_plugins = List(IViewPlugin) op_plugins = List(IOperationPlugin) help_id = Str html = HTML("<b>Welcome to Cytoflow!</b>") traits_view = View( Item('html', editor=HTMLEditor(base_url=pathlib.Path(__file__).parent.joinpath( 'help').as_posix()), show_label=False)) @on_trait_change('help_id', post_init=True) def _on_help_id_changed(self): for plugin in self.view_plugins: if self.help_id == plugin.view_id: try: self.html = plugin.get_help() except AttributeError: pass finally: return for plugin in self.op_plugins: if self.help_id == plugin.operation_id: try: self.html = plugin.get_help() except AttributeError: pass finally: return
class MainApp(HasStrictTraits): """ A dummy main app to show the demo. """ #: Information about the example. information = HTML() #: Information about the example. credentials = Instance(Credentials, ()) def _information_default(self): return """ <html> <head> <style media="screen" type="text/css"> {css} </style> </head> <body> <h1>Validating Dialog Example</h1> <p>This example shows how to dynamically validate a user's entries in a TraitsUI dialog. The example shows four things:</p> <ul> <li><p>how to enable/disable the 'OK' dialog button by tracking various error states and incrementing/decrementing the <code>ui.errors</code> count.</p></li> <li><p>how to perform additional checks when the user clicks the 'OK' dialog button, displaying an appropriate error message and returning control to the dialog on failure.</p></li> <li><p>setting an editor's 'invalid' state via a trait, which colors the textbox background to the error color.</p></li> <li><p>displaying feedback to the user in a number of additional ways, such as text explanations, alert icons, and icons with varying feedback levels.</p></li> </ul> </body> """.format(css=css)
class DemoFileBase(DemoTreeNodeObject): #: Parent of this file: parent = Any() #: Name of file system path to this file: path = Property(depends_on='parent.path,name') #: Name of the file: name = Str() #: UI form of the 'name': nice_name = Property() #: Files don't allow children: allows_children = Bool(False) #: Description of what the demo does: description = HTML() #: The base URL for links: base_url = Property(depends_on='path') #: The css file for this node. css_filename = Str("default.css") #: Log of all print messages displayed: log = Code() _nice_name = Str() def init(self): self.log = "" # ------------------------------------------------------------------------- # Implementation of the 'path' property: # ------------------------------------------------------------------------- def _get_path(self): return join(self.parent.path, self.name) def _get_base_url(self): if isdir(self.path): base_dir = self.path else: base_dir = dirname(self.path) return base_dir # ------------------------------------------------------------------------- # Implementation of the 'nice_name' property: # ------------------------------------------------------------------------- def _get_nice_name(self): if not self._nice_name: name, ext = splitext(self.name) self._nice_name = user_name_for(name) return self._nice_name def _set_nice_name(self, value): old = self.nice_name self._nice_name = value self.trait_property_changed("nice_name", old, value) # ------------------------------------------------------------------------- # Returns whether or not the object has children: # ------------------------------------------------------------------------- def has_children(self): """ Returns whether or not the object has children. """ return False def get_children(self): """ Gets the demo file's children. """ return []
class CiteOverlapGUI(HasTraits): """GUI for Citation Overlap.""" #: str: Default extractor option to prompt user to select an extractor. _DEFAULT_EXTRACTOR = "Select..." #: int: Default number of import views. _DEFAULT_NUM_IMPORTS = 3 #: OrderedDict[str, str]: Dictionary of separator descriptions to # separator characters. _EXPORT_SEPS = OrderedDict(( ('Tabs (.tsv)', '\t'), ('Comma (.csv)', ','), ('Bar (.csv)', '|'), ('Semi-colon (.csv)', ';'), )) # handler triggers selectSheetTab = Int(-1) # tab index to select renameSheetTab = Int(-1) # tab index to rename renameSheetName = Str # new tab name # CONTROL PANEL TRAITS # citation list import views importMedline = Instance(CiteImport) importEmbase = Instance(CiteImport) importScopus = Instance(CiteImport) importOther1 = Instance(CiteImport) importOther2 = Instance(CiteImport) importOther3 = Instance(CiteImport) importOther4 = Instance(CiteImport) _importAddBtn = Button('Add Sheet') # extractor drop-downs _extractorNames = Instance(TraitsList) _extractorAddBtn = Button('Add Extractor') # button to find overlaps and progress bar _overlapBtn = Button('Find Overlaps') _progBarPct = Int(0) _progBarMsg = Str('Awaiting overlaps') # table export _exportBtn = Button('Export Tables') _exportSep = Str _exportSepNames = Instance(TraitsList) _statusBarMsg = Str # HELP PANEL TRAITS pkgDir = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) docsDir = os.path.join(pkgDir, "docs") if getattr(sys, "frozen", False) and hasattr(sys, "_MEIPASS"): # detect frozen env using the PyInstaller-specific attributes # (currently works even without this setting through symlinks) docsDir = os.path.realpath(os.path.join(sys._MEIPASS, "docs")) with open(os.path.join(docsDir, "sidebar.html"), mode="r") as help_file: helpMsg = "".join(help_file.readlines()) _helpHtml = HTML(helpMsg) # SHEETS TRAITS # counter for number of "other citation" sheets _numCitOther = Int(0) _tabularArgs = { 'editable': True, 'auto_resize_rows': True, 'stretch_last_section': False } # Import view groups, which need an adapter set here to avoid sharing # the adapter across view instances # MEDLINE table _medlineAdapter = TableArrayAdapter() _medlineTable = TabularEditor(adapter=_medlineAdapter, **_tabularArgs) _medline = CiteSheet(adapter=_medlineAdapter) # Embase table _embaseAdapter = TableArrayAdapter() _embaseTable = TabularEditor(adapter=_embaseAdapter, **_tabularArgs) _embase = CiteSheet(adapter=_embaseAdapter) # Scopus table _scopusAdapter = TableArrayAdapter() _scopusTable = TabularEditor(adapter=_scopusAdapter, **_tabularArgs) _scopus = CiteSheet(adapter=_scopusAdapter) # Other 1 table _citOther1Adapter = TableArrayAdapter() _citOther1Table = TabularEditor(adapter=_citOther1Adapter, **_tabularArgs) _citOther1 = CiteSheet(adapter=_citOther1Adapter) # Other 2 table _citOther2Adapter = TableArrayAdapter() _citOther2Table = TabularEditor(adapter=_citOther2Adapter, **_tabularArgs) _citOther2 = CiteSheet(adapter=_citOther2Adapter) # Other 3 table _citOther3Adapter = TableArrayAdapter() _citOther3Table = TabularEditor(adapter=_citOther3Adapter, **_tabularArgs) _citOther3 = CiteSheet(adapter=_citOther3Adapter) # Other 4 table _citOther4Adapter = TableArrayAdapter() _citOther4Table = TabularEditor(adapter=_citOther4Adapter, **_tabularArgs) _citOther4 = CiteSheet(adapter=_citOther4Adapter) # Overlaps output table _overlapsAdapter = TableArrayAdapter() _outputTable = TabularEditor(adapter=_overlapsAdapter, **_tabularArgs) _overlaps = CiteSheet(adapter=_overlapsAdapter) # TRAITUI WIDGETS # controls panel _controlsPanel = VGroup( VGroup( Item('importMedline', show_label=False, style='custom'), Item('importEmbase', show_label=False, style='custom'), Item('importScopus', show_label=False, style='custom'), Item('importOther1', show_label=False, style='custom', visible_when='_numCitOther >= 1'), Item('importOther2', show_label=False, style='custom', visible_when='_numCitOther >= 2'), Item('importOther3', show_label=False, style='custom', visible_when='_numCitOther >= 3'), Item('importOther4', show_label=False, style='custom', visible_when='_numCitOther >= 4'), HGroup( Item('_importAddBtn', show_label=False, springy=True, enabled_when='_numCitOther <= object._DEFAULT_NUM_IMPORTS' ), Item('_extractorAddBtn', show_label=False, springy=True), ), label='Load Citation Files', ), VGroup( HGroup(Item('_overlapBtn', show_label=False, springy=True), ), Item('_progBarPct', show_label=False, editor=ProgressEditor(min=0, max=100, message_name='_progBarMsg')), HGroup( Item('_exportBtn', show_label=False, springy=True), Item("_exportSep", label="Separator", editor=CheckListEditor( name="object._exportSepNames.selections")), ), label='Detect Overlapping Citations', ), label="Import", ) # help panel _helpPanel = VGroup( Item("_helpHtml", editor=HTMLEditor(format_text=True), show_label=False), label="Help", ) # tabbed panel for sidebar _sidebarTabs = Tabbed( _controlsPanel, _helpPanel, ) # Tabbed viewers of tables # WORKAROUND: Because of an apparent limitation in adding tabs dynamically # in TraitsUI (https://github.com/enthought/traitsui/pull/1456), separate # views are created and toggled depending on the number of "other" sheets, # toggled by the "visible_when" flag # default tabbed viewer _tableView1 = Tabbed( Item('object._medline.data', editor=_medlineTable, show_label=False, width=1000), Item('object._embase.data', editor=_embaseTable, show_label=False), Item('object._scopus.data', editor=_scopusTable, show_label=False), Item('object._overlaps.data', editor=_outputTable, show_label=False), visible_when='_numCitOther == 0', ) # tabbed viewer of tables with one "other" sheet _tableView2 = Tabbed( Item('object._medline.data', editor=_medlineTable, show_label=False, width=1000), Item('object._embase.data', editor=_embaseTable, show_label=False), Item('object._scopus.data', editor=_scopusTable, show_label=False), Item('object._citOther1.data', editor=_citOther1Table, show_label=False), Item('object._overlaps.data', editor=_outputTable, show_label=False), visible_when='_numCitOther == 1', ) # tabbed viewer of tables with two "other" sheets _tableView3 = Tabbed( Item('object._medline.data', editor=_medlineTable, show_label=False, width=1000), Item('object._embase.data', editor=_embaseTable, show_label=False), Item('object._scopus.data', editor=_scopusTable, show_label=False), Item('object._citOther1.data', editor=_citOther1Table, show_label=False), Item('object._citOther2.data', editor=_citOther2Table, show_label=False), Item('object._overlaps.data', editor=_outputTable, show_label=False), visible_when='_numCitOther == 2', ) # tabbed viewer of tables with three "other" sheets _tableView4 = Tabbed( Item('object._medline.data', editor=_medlineTable, show_label=False, width=1000), Item('object._embase.data', editor=_embaseTable, show_label=False), Item('object._scopus.data', editor=_scopusTable, show_label=False), Item('object._citOther1.data', editor=_citOther1Table, show_label=False), Item('object._citOther2.data', editor=_citOther2Table, show_label=False), Item('object._citOther3.data', editor=_citOther2Table, show_label=False), Item('object._overlaps.data', editor=_outputTable, show_label=False), visible_when='_numCitOther == 3', ) # tabbed viewer of tables with four "other" sheets _tableView5 = Tabbed( Item('object._medline.data', editor=_medlineTable, show_label=False, width=1000), Item('object._embase.data', editor=_embaseTable, show_label=False), Item('object._scopus.data', editor=_scopusTable, show_label=False), Item('object._citOther1.data', editor=_citOther1Table, show_label=False), Item('object._citOther2.data', editor=_citOther2Table, show_label=False), Item('object._citOther3.data', editor=_citOther2Table, show_label=False), Item('object._citOther4.data', editor=_citOther2Table, show_label=False), Item('object._overlaps.data', editor=_outputTable, show_label=False), visible_when='_numCitOther == 4', ) # main view view = View( HSplit( _sidebarTabs, # only one table view should be displayed at a time Group( _tableView1, _tableView2, _tableView3, _tableView4, _tableView5, ), ), width=1300, # also influenced by _tableView width height=800, title='Citation Overlap', resizable=True, handler=CiteOverlapHandler(), statusbar="_statusBarMsg") def __init__(self): """Initialize the GUI.""" super().__init__() # set up import views self.importMedline = CiteImport(sheet=self._medline) self.importEmbase = CiteImport(sheet=self._embase) self.importScopus = CiteImport(sheet=self._scopus) self.importOther1 = CiteImport(sheet=self._citOther1) self.importOther2 = CiteImport(sheet=self._citOther2) self.importOther3 = CiteImport(sheet=self._citOther3) self.importOther4 = CiteImport(sheet=self._citOther4) self.importViews = ( self.importMedline, self.importEmbase, self.importScopus, self.importOther1, self.importOther2, self.importOther3, self.importOther4, ) # populate drop-down of available extractors from directory of # extractors, displaying only basename but keeping dict with full path extractor_paths = [] for extractor_dir in config.extractor_dirs: extractor_paths.extend(glob.glob(str(extractor_dir / "*"))) self._extractor_paths = { os.path.basename(f): f for f in extractor_paths } self._updateExtractorNames(True) for importer in self.importViews: importer.observe(self.renameTabEvent, "extractor") importer.observe(self.importFile, "path") importer.observe(self.clearSheet, "clearBtn") # populate drop-down of separators/delimiters self._exportSepNames = TraitsList() self._exportSepNames.selections = list(self._EXPORT_SEPS.keys()) self._exportSep = self._exportSepNames.selections[0] # extractor and overlaps thread instances self.dbExtractor = extractor.DbExtractor() self._overlapsThread = None # last opened directory self._save_dir = None def _updateExtractorNames(self, reset=False): """Update the list of extractor names shown in the combo boxes. Args: reset (bool): True to reset to default selections; defaults to False. """ if reset: # pick default selections for each extractor selections = [e.value for e in extractor.DefaultExtractors] numExtra = len(self.importViews) - len(selections) if numExtra > 0: selections.extend([self._DEFAULT_EXTRACTOR] * numExtra) else: # keep current selections selections = [v.extractor for v in self.importViews] # update combo box from extractor path keys self._extractorNames = TraitsList() extractorNames = [self._DEFAULT_EXTRACTOR] extractorNames.extend(self._extractor_paths.keys()) self._extractorNames.selections = extractorNames # assign default extractor selections for view, selection in zip(self.importViews, selections): view.extractorNames = self._extractorNames view.extractor = selection def renameTabEvent(self, event): """Handler to rename a spreadsheet tab. Args: event (:class:`traits.observation.events.TraitChangeEvent`): Event. """ self.renameSheetTab = self.importViews.index(event.object) self.renameSheetName = _displayExtractor(event.object.extractor) def clearSheet(self, event): """Clear the sheet associated with an import view. Args: event (:class:`traits.observation.events.TraitChangeEvent`): Event. """ event.object.sheet.data = np.empty((0, 0)) del self.dbExtractor.dbsParsed[event.object.dbName] event.object.path = '' @staticmethod def _df_to_cols(df): """Convert a data frame to table columns with widths adjusted to fit the column width up to a given max amount. Args: df (:obj:`pd.DataFrame`): Data frame to enter into table. Returns: dict[int, int], list[(str, Any)], :obj:`np.ndarray`: Dictionary of column indices to width, list of column tuples given as ``(col_name, col_ID)``, and data frame as a Numpy arry. """ colWidths = [] colsIDs = [] for i, col in enumerate(df.columns.values.tolist()): # get widths of all rows in column as well as header colWidth = df[col].astype(str).str.len().tolist() colWidth.append(len(col)) colWidths.append(colWidth) # use index as ID except for group/sub-group, where using a string # allows the col along with row to be accessed for individual cells colID = col.lower() if col in ('Group', 'Subgrp') else i colsIDs.append((col, colID)) # get max width for each col, taking log to slow the width increase # for wider strings and capping at a max width widths = { i: min((math.log1p(max(c)) * 40, TableArrayAdapter.MAX_WIDTH)) for i, c in enumerate(colWidths) } return widths, colsIDs, df.to_numpy() @on_trait_change('_importAddBtn') def addImport(self): """Add import fields and a new sheet.""" if self._numCitOther < len( self.importViews) - self._DEFAULT_NUM_IMPORTS: # trigger additional import view and tab with new sheet self._numCitOther += 1 @on_trait_change('_extractorAddBtn') def addExtractor(self): """Add an extractor to the combo box.""" try: # get path to extractor from file dialog path = self._getFileDialogPath() except FileNotFoundError: return # update combo box pathName = os.path.basename(path) self._extractor_paths[pathName] = path self._updateExtractorNames() def importFile(self, event): """Import a database file. Args: event (:class:`traits.observation.events.TraitChangeEvent`): Event. Returns: :obj:`pd.DataFrame`: Data frame of extracted file. """ path = event.object.path if not os.path.exists(path): if path: # file inaccessible, or manually edited, non-accessible path self._statusBarMsg = f'{path} could not be found, skipping' return None self._save_dir = os.path.dirname(path) try: # extract file extractorPath = self._extractor_paths[event.object.extractor] df, dbName = self.dbExtractor.extractDb(path, extractorPath) event.object.dbName = dbName try: self.dbExtractor.checkExtraction(df) self._statusBarMsg = f'Imported file from {path}' except SyntaxWarning as e: msg = \ f'WARNING: {str(e)}. Please check the selected file ' \ f'source and reload the citation file.' self._statusBarMsg = msg _logger.warning(msg) sheet = event.object.sheet if df is not None and sheet is not None: # output data frame to associated table sheet.adapter._widths, sheet.adapter.columns, sheet.data = \ self._df_to_cols(df) self.selectSheetTab = self.importViews.index(event.object) return df except (FileNotFoundError, SyntaxError) as e: self._statusBarMsg = str(e) return None def _updateProgBar(self, pct: int, msg: str): """"Update progress bar. Args: pct: Percentage complete, from 0-100. msg: Message to display. """ self._progBarPct = pct self._progBarMsg = msg def _overlapsHandler(self, result: Union[pd.DataFrame, str]): """Handle result from finding overlaps. Args: result: Data frame of overlaps or message. """ if isinstance(result, pd.DataFrame): self._progBarPct = 100 if result is None: # clear any existing data in sheet if no citation lists self._overlaps.data = np.empty((0, 0)) self._statusBarMsg = 'No citation lists found' return # populate overlaps sheet self._overlaps.adapter._widths, self._overlaps.adapter.columns, \ self._overlaps.data = self._df_to_cols(result) self.selectSheetTab = self._DEFAULT_NUM_IMPORTS + self._numCitOther self._statusBarMsg = 'Found overlaps across databases' elif isinstance(result, str): # show message self._statusBarMsg = result @on_trait_change('_overlapBtn') def findOverlaps(self): """Find overlaps in a thread.""" self._progBarPct = 0 self._overlapsThread = overlaps_thread.OverlapsThread( self.dbExtractor, self._overlapsHandler, self._updateProgBar) self._overlapsThread.start() def _getFileDialogPath(self, default_path='', mode='open'): """Get a path from the user through a file dialog. Args: default_path (str): Initial path to display in the dialog; defaults to an emptry string. If :attr:`_save_dir` is set, ``default_path`` will be joined to the save directory. mode (str): "open" for an open dialog, or "save as" for a save dialog. Returns: str: Chosen path. Raises: FileNotFoundError: User canceled file selection. """ if self._save_dir: # use directory of last chosen import file default_path = os.path.join(self._save_dir, default_path) # open a PyFace file dialog in save mode save_dialog = FileDialog(action=mode, default_path=default_path) if save_dialog.open() == OK: # get user selected path return save_dialog.path else: # user canceled file selection raise FileNotFoundError("User canceled file selection") @on_trait_change('_exportBtn') def exportTables(self): """Export tables to a files.""" self.dbExtractor.saveSep = self._EXPORT_SEPS[self._exportSep] try: # prompt user to select an output file path for the combined list; # save along with filtered folders in a separate dir there save_path = self._getFileDialogPath( self.dbExtractor.DEFAULT_OVERLAPS_PATH, "save as") self.dbExtractor.exportDataFrames(save_path) self._statusBarMsg = ( f'Saved combined table to "{save_path}" and filtered tables ' f'alongside in "{self.dbExtractor.DEFAULT_CLEANED_DIR_PATH}"') except FileNotFoundError: print("Skipping file save")
class MainWindow(HasTraits): title = String() date = String() category = Enum(['nus', 'travel', 'pics', 'food']) dirpath = Directory() codedir = Directory() html_text = String('') status = String('no connection') ftp_url = String('files.000webhost.com') ftp_user = String('maxinsingapore') ftp_dir = String('public_html/pictures') ftp_pw = String() upload_btn = Button('Upload') html_preview = HTML() preview_btn = Button('HTML preview') uploadthread = Instance(UploadThread) notuploading = Bool(True) html_intro_1 = '''<!DOCTYPE html><html><head><link href="main.css" rel="stylesheet"/> <title>Max in Singapore</title> </head> <body> <?php require("ground.php"); ?> <div class = "title"> <a href="''' html_intro_2 = '''.php"><figure><p>back</p</figure></a> </div> <div class="center">''' html_end = ''' </div> </div> </div> </body> </html>''' traits_view = View( HGroup('ftp_url', 'ftp_user', 'ftp_pw', 'ftp_dir'), HGroup('title', 'date', 'category'), HGroup(Item('html_text', editor=CodeEditor()), Item('html_preview', editor=HTMLEditor())), 'preview_btn', Item('dirpath', label='Photo Directory'), Item('codedir', label='Code Directory'), Item('status', style='readonly'), Item('upload_btn', enabled_when='notuploading')) def _preview_btn_fired(self): html_intro = self.html_intro_1 + self.category + self.html_intro_2 self.html_preview = html_intro + self.html_text + self.html_end def _upload_btn_fired(self): if self.dirpath != '' and self.codedir != '': self.notuploading = False self.uploadthread = UploadThread() self.uploadthread.wants_abort = False self.uploadthread.master = self self.uploadthread.start() else: self.status = "choose directories"
class Comparator(HasTraits): """ The main application. """ #### Configuration traits ################################################## # The root directory of the test suite. suitedir = Str() # Mapping of SVG basenames to their reference PNGs. Use None if there is no # reference PNG. svg_png = Dict() # The list of SVG file names. svg_files = List() # The name of the default PNG file to display when no reference PNG exists. default_png = Str(os.path.join(this_dir, 'images/default.png')) #### State traits ########################################################## # The currently selected SVG file. current_file = Str() abs_current_file = Property(depends_on=['current_file']) # The current XML ElementTree root Element and its XMLTree view model. current_xml = Any() current_xml_view = Any() # The profilers. profile_this = Instance(ProfileThis, args=()) #### GUI traits ############################################################ # The text showing the current mouse coordinates over any of the components. mouse_coords = Property(Str, depends_on=['ch_controller.svg_coords']) # Move forward and backward through the list of SVG files. move_forward = Button('>>') move_backward = Button('<<') # The description of the test. description = HTML() document = Instance(document.SVGDocument) # The components to view. kiva_component = ComponentTrait(klass=SVGComponent) ref_component = ComponentTrait(klass=ImageComponent, args=()) ch_controller = Instance(MultiController) # The profiler views. parsing_sike = Instance(Sike, args=()) drawing_sike = Instance(Sike, args=()) wx_doc_sike = Instance(Sike, args=()) kiva_doc_sike = Instance(Sike, args=()) traits_view = tui.View( tui.Tabbed( tui.VGroup( tui.HGroup( tui.Item('current_file', editor=tui.EnumEditor(name='svg_files'), style='simple', width=1.0, show_label=False), tui.Item( 'move_backward', show_label=False, enabled_when="svg_files.index(current_file) != 0"), tui.Item( 'move_forward', show_label=False, enabled_when= "svg_files.index(current_file) != len(svg_files)-1"), ), tui.VSplit( tui.HSplit( tui.Item('description', label='Description', show_label=False), tui.Item('current_xml_view', editor=xml_tree_editor, show_label=False), ), tui.HSplit( tui.Item('document', editor=SVGEditor(), show_label=False), tui.Item('kiva_component', show_label=False), tui.Item('ref_component', show_label=False), # TODO: tui.Item('agg_component', show_label=False), ), ), label='SVG', ), tui.Item('parsing_sike', style='custom', show_label=False, label='Parsing Profile'), tui.Item('drawing_sike', style='custom', show_label=False, label='Kiva Drawing Profile'), tui.Item('wx_doc_sike', style='custom', show_label=False, label='Creating WX document'), tui.Item('kiva_doc_sike', style='custom', show_label=False, label='Creating WX document'), ), width=1280, height=768, resizable=True, statusbar='mouse_coords', title='SVG Comparator', ) def __init__(self, **traits): super(Comparator, self).__init__(**traits) kiva_ch = activate_tool(self.kiva_component, Crosshair(self.kiva_component)) ref_ch = activate_tool(self.ref_component, Crosshair(self.ref_component)) self.ch_controller = MultiController(kiva_ch, ref_ch) @classmethod def fromsuitedir(cls, dirname, **traits): """ Find all SVG files and their related reference PNG files under a directory. This assumes that the SVGs are located under <dirname>/svg/ and the related PNGs under <dirname>/png/ and that there are no subdirectories. """ dirname = os.path.abspath(dirname) svgs = glob.glob(os.path.join(dirname, 'svg', '*.svg')) pngdir = os.path.join(dirname, 'png') d = {} for svg in svgs: png = None base = os.path.splitext(os.path.basename(svg))[0] for prefix in ('full-', 'basic-', 'tiny-', ''): fn = os.path.join(pngdir, prefix + base + '.png') if os.path.exists(fn): png = os.path.basename(fn) break d[os.path.basename(svg)] = png svgs = sorted(d) x = cls(suitedir=dirname, svg_png=d, svg_files=svgs, **traits) x.current_file = svgs[0] return x def display_reference_png(self, filename): """ Read the image file and shove its data into the display component. """ img = Image.open(filename) arr = np.array(img) self.ref_component.image = arr def display_test_description(self): """ Extract the test description for display. """ html = ET.Element('html') title = self.current_xml.find('.//{http://www.w3.org/2000/svg}title') if title is not None: title_text = title.text else: title_text = os.path.splitext(self.current_file)[0] p = ET.SubElement(html, 'p') b = ET.SubElement(p, 'b') b.text = 'Title: ' b.tail = title_text desc_text = None version_text = None desc = self.current_xml.find('.//{http://www.w3.org/2000/svg}desc') if desc is not None: desc_text = desc.text else: testcase = self.current_xml.find( './/{http://www.w3.org/2000/02/svg/testsuite/description/}SVGTestCase' ) if testcase is not None: desc_text = testcase.get('desc', None) version_text = testcase.get('version', None) if desc_text is not None: p = ET.SubElement(html, 'p') b = ET.SubElement(p, 'b') b.text = 'Description: ' b.tail = normalize_text(desc_text) if version_text is None: script = self.current_xml.find( './/{http://www.w3.org/2000/02/svg/testsuite/description/}OperatorScript' ) if script is not None: version_text = script.get('version', None) if version_text is not None: p = ET.SubElement(html, 'p') b = ET.SubElement(p, 'b') b.text = 'Version: ' b.tail = version_text paras = self.current_xml.findall( './/{http://www.w3.org/2000/02/svg/testsuite/description/}Paragraph' ) if len(paras) > 0: div = ET.SubElement(html, 'div') for para in paras: p = ET.SubElement(div, 'p') p.text = normalize_text(para.text) # Copy over any children elements like <a>. p[:] = para[:] tree = ET.ElementTree(html) f = StringIO() tree.write(f) text = f.getvalue() self.description = text def locate_file(self, name, kind): """ Find the location of the given file in the suite. Parameters ---------- name : str Path of the file relative to the suitedir. kind : either 'svg' or 'png' The kind of file. Returns ------- path : str The full path to the file. """ return os.path.join(self.suitedir, kind, name) def _kiva_component_default(self): return SVGComponent(profile_this=self.profile_this) def _move_backward_fired(self): idx = self.svg_files.index(self.current_file) idx = max(idx - 1, 0) self.current_file = self.svg_files[idx] def _move_forward_fired(self): idx = self.svg_files.index(self.current_file) idx = min(idx + 1, len(self.svg_files) - 1) self.current_file = self.svg_files[idx] def _get_abs_current_file(self): return self.locate_file(self.current_file, 'svg') def _current_file_changed(self, new): # Reset the warnings filters. While it's good to only get 1 warning per # file, we want to get the same warning again if a new file issues it. warnings.resetwarnings() self.profile_this.start('Parsing') self.current_xml = ET.parse(self.abs_current_file).getroot() self.current_xml_view = xml_to_tree(self.current_xml) resources = document.ResourceGetter.fromfilename(self.abs_current_file) self.profile_this.stop() try: self.profile_this.start('Creating WX document') self.document = document.SVGDocument(self.current_xml, resources=resources, renderer=WxRenderer) except: logger.exception('Error parsing document %s', new) self.document = None self.profile_this.stop() try: self.profile_this.start('Creating Kiva document') self.kiva_component.document = document.SVGDocument( self.current_xml, resources=resources, renderer=KivaRenderer) except Exception as e: logger.exception('Error parsing document %s', new) self.kiva_component.document self.profile_this.stop() png_file = self.svg_png.get(new, None) if png_file is None: png_file = self.default_png else: png_file = self.locate_file(png_file, 'png') self.display_test_description() self.display_reference_png(png_file) def _get_mouse_coords(self): if self.ch_controller is None: return '' else: return '%1.3g %1.3g' % self.ch_controller.svg_coords @on_trait_change('profile_this:profile_ended') def _update_profiling(self, new): if new is not None: name, p = new stats = pstats.Stats(p) if name == 'Parsing': self.parsing_sike.stats = stats elif name == 'Drawing': self.drawing_sike.stats = stats elif name == 'Creating WX document': self.wx_doc_sike.stats = stats elif name == 'Creating Kiva document': self.kiva_doc_sike.stats = stats