def test_clear_string(self): """Does clear set stored string to None?""" seg = Input('test3') seg.clear() self.assertEqual(Segmentation.get_data(-1), None, msg="clear doesn't set stored string to None!")
def test_clear_string(self): """Does clear set stored string to None?""" seg = Input('test3') seg.clear() self.assertEqual( Segmentation.get_data(-1), None, msg="clear doesn't set stored string to None!" )
class OWTextableTextField(OWTextableBaseWidget): """Orange widget for typing text data""" name = "Text Field" description = "Import text data from keyboard input" icon = "icons/TextField.png" priority = 1 # Input and output channels... inputs = [('Text data', Segmentation, "inputTextData", widget.Single)] outputs = [('Text data', Segmentation)] settingsHandler = VersionedSettingsHandler( version=__version__.rsplit(".", 1)[0]) # Settings ... textFieldContent = settings.Setting(u''.encode('utf-8')) encoding = settings.Setting(u'utf-8') want_main_area = False def __init__(self): """Initialize a Text File widget""" super().__init__() # Other attributes... self.infoBox = InfoBox(widget=self.controlArea) self.sendButton = SendButton( widget=self.controlArea, master=self, callback=self.sendData, infoBoxAttribute='infoBox', ) # LTTL.Input object (token that will be sent). self.segmentation = Input(text=u'') # GUI... # Text Field... gui.separator( widget=self.controlArea, height=3, ) self.editor = QPlainTextEdit() self.editor.setPlainText(self.textFieldContent.decode('utf-8')) self.controlArea.layout().addWidget(self.editor) self.editor.textChanged.connect(self.sendButton.settingsChanged) gui.separator( widget=self.controlArea, height=3, ) self.setMinimumWidth(250) # Send button... self.sendButton.draw() # Info box... self.infoBox.draw() self.sendButton.sendIf() def inputTextData(self, segmentation): """Handle text data on input connection""" if not segmentation: return self.editor.setPlainText(''.join( [s.get_content() for s in segmentation])) self.sendButton.settingsChanged() def sendData(self): """Normalize content, then create and send segmentation""" textFieldContent = self.editor.toPlainText() self.textFieldContent = textFieldContent.encode('utf-8') textFieldContent \ = textFieldContent.replace('\r\n', '\n').replace('\r', '\n') textFieldContent = normalize('NFC', textFieldContent) # Check that text field is not empty... if not self.textFieldContent: self.infoBox.setText( message=u'Please type or paste some text above.', state='warning', ) self.send('Text data', None, self) return # TODO: remove message 'No label was provided.' from docs # Set status to OK... message = u'1 segment (%i character@p) sent to output.' % \ len(textFieldContent) message = pluralize(message, len(textFieldContent)) self.infoBox.setText(message) # Update segmentation. self.segmentation.update(textFieldContent, label=self.captionTitle) # Send token... self.send('Text data', self.segmentation, self) self.sendButton.resetSettingsChangedFlag() def setCaption(self, title): if 'captionTitle' in dir(self): changed = title != self.captionTitle super().setCaption(title) if changed: self.sendButton.settingsChanged() else: super().setCaption(title) def onDeleteWidget(self): self.segmentation.clear() self.segmentation.__del__()
class OWTextableConvert(OWTextableBaseWidget): """Orange widget for converting a Textable table to an Orange table""" name = "Convert" description = "Convert, transform, or export Orange Textable tables." icon = "icons/Convert.png" priority = 10001 inputs = [('Textable table', Table, "inputData", widget.Single)] outputs = [('Orange table', Orange.data.Table, widget.Default), ('Textable table', Table, widget.Dynamic), ('Segmentation', Segmentation)] settingsHandler = VersionedSettingsHandler( version=__version__.rsplit(".", 1)[0]) # Settings... exportEncoding = settings.Setting('utf8') colDelimiter_idx = settings.Setting(0) includeOrangeHeaders = settings.Setting(False) sortRows = settings.Setting(False) sortRowsReverse = settings.Setting(False) sortCols = settings.Setting(False) sortColsReverse = settings.Setting(False) normalize = settings.Setting(False) normalizeMode = settings.Setting('rows') normalizeType = settings.Setting('l1') convert = settings.Setting(False) conversionType = settings.Setting('association matrix') associationBias = settings.Setting('none') transpose = settings.Setting(False) reformat = settings.Setting(False) unweighted = settings.Setting(False) lastLocation = settings.Setting('.') displayAdvancedSettings = settings.Setting(False) # Predefined list of available encodings... encodings = getPredefinedEncodings() want_main_area = False @property def colDelimiter(self): _, delimiter = ColumnDelimiters[self.colDelimiter_idx] return delimiter def __init__(self, *args, **kwargs): """Initialize a Convert widget""" super().__init__(*args, **kwargs) # Other attributes... self.sortRowsKeyId = 0 # None self.sortColsKeyId = 0 # None self.table = None self.segmentation = None self.infoBox = InfoBox(widget=self.controlArea) self.sendButton = SendButton( widget=self.controlArea, master=self, callback=self.sendData, infoBoxAttribute='infoBox', sendIfPreCallback=self.updateGUI, ) self.advancedSettings = AdvancedSettings( widget=self.controlArea, master=self, callback=self.sendButton.settingsChanged, ) # GUI... # Advanced settings checkbox... self.advancedSettings.draw() # Transform box self.transformBox = gui.widgetBox( widget=self.controlArea, box=u'Transform', orientation='vertical', addSpace=False, ) self.transformBoxLine1 = gui.widgetBox( widget=self.transformBox, orientation='horizontal', ) gui.checkBox( widget=self.transformBoxLine1, master=self, value='sortRows', label=u'Sort rows by column:', labelWidth=180, callback=self.sendButton.settingsChanged, tooltip=(u"Sort table rows."), ) self.sortRowsKeyIdCombo = gui.comboBox( widget=self.transformBoxLine1, master=self, value='sortRowsKeyId', items=list(), callback=self.sendButton.settingsChanged, tooltip=(u"Column whose values will be used for sorting rows."), ) self.sortRowsKeyIdCombo.setMinimumWidth(150) gui.separator(widget=self.transformBoxLine1, width=5) self.sortRowsReverseCheckBox = gui.checkBox( widget=self.transformBoxLine1, master=self, value='sortRowsReverse', label=u'Reverse', callback=self.sendButton.settingsChanged, tooltip=(u"Sort rows in reverse (i.e. decreasing) order."), ) gui.separator(widget=self.transformBox, height=3) self.transformBoxLine2 = gui.widgetBox( widget=self.transformBox, orientation='horizontal', ) gui.checkBox( widget=self.transformBoxLine2, master=self, value='sortCols', label=u'Sort columns by row:', labelWidth=180, callback=self.sendButton.settingsChanged, tooltip=(u"Sort table columns."), ) self.sortColsKeyIdCombo = gui.comboBox( widget=self.transformBoxLine2, master=self, value='sortColsKeyId', items=list(), callback=self.sendButton.settingsChanged, tooltip=(u"Row whose values will be used for sorting columns."), ) self.sortColsKeyIdCombo.setMinimumWidth(150) gui.separator(widget=self.transformBoxLine2, width=5) self.sortColsReverseCheckBox = gui.checkBox( widget=self.transformBoxLine2, master=self, value='sortColsReverse', label=u'Reverse', callback=self.sendButton.settingsChanged, tooltip=(u"Sort columns in reverse (i.e. decreasing) order."), ) gui.separator(widget=self.transformBox, height=3) self.transposeCheckBox = gui.checkBox( widget=self.transformBox, master=self, value='transpose', label=u'Transpose', callback=self.sendButton.settingsChanged, tooltip=(u"Transpose table (i.e. exchange rows and columns)."), ) gui.separator(widget=self.transformBox, height=3) self.transformBoxLine4 = gui.widgetBox( widget=self.transformBox, orientation='horizontal', ) gui.checkBox( widget=self.transformBoxLine4, master=self, value='normalize', label=u'Normalize:', labelWidth=180, callback=self.sendButton.settingsChanged, tooltip=(u"Normalize table."), ) self.normalizeModeCombo = gui.comboBox( widget=self.transformBoxLine4, master=self, value='normalizeMode', items=[ u'rows', u'columns', u'quotients', u'TF-IDF', u'presence/absence' ], sendSelectedValue=True, callback=self.sendButton.settingsChanged, tooltip=(u"Normalization mode:\n\n" u"Row: L1 or L2 normalization by rows.\n\n" u"Column: L1 or L2 normalization by columns.\n\n" u"Quotients: the count stored in each cell is\n" u"divided by the corresponding theoretical count\n" u"under independence: the result is greater than 1\n" u"in case of attraction between line and column,\n" u"lesser than 1 in case of repulsion, and 1 if\n" u"there is no specific interaction between them.\n\n" u"TF-IDF: the count stored in each cell is multiplied\n" u"by the natural log of the ratio of the number of\n" u"rows (i.e. contexts) having nonzero count for this\n" u"column (i.e. unit) to the total number of rows.\n\n" u"Presence/absence: counts greater than 0 are\n" u"replaced with 1."), ) self.normalizeModeCombo.setMinimumWidth(150) gui.separator(widget=self.transformBoxLine4, width=5) self.normalizeTypeCombo = gui.comboBox( widget=self.transformBoxLine4, master=self, orientation='horizontal', value='normalizeType', items=[u'L1', u'L2'], sendSelectedValue=True, label=u'Norm:', labelWidth=40, callback=self.sendButton.settingsChanged, tooltip=(u"Norm type.\n\n" u"L1: divide each value by the sum of the enclosing\n" u"normalization unit (row/column)\n\n" u"L2: divide each value by the sum of squares of the\n" u"enclosing normalization unit, then take square root."), ) self.normalizeTypeCombo.setMinimumWidth(70) gui.separator(widget=self.transformBox, height=3) self.transformBoxLine5 = gui.widgetBox( widget=self.transformBox, orientation='horizontal', ) gui.checkBox( widget=self.transformBoxLine5, master=self, value='convert', label=u'Convert to:', labelWidth=180, callback=self.sendButton.settingsChanged, tooltip=(u"Apply crosstab conversions."), ) self.conversionTypeCombo = gui.comboBox( widget=self.transformBoxLine5, master=self, value='conversionType', items=[ 'document frequency', 'association matrix', ], sendSelectedValue=True, callback=self.sendButton.settingsChanged, tooltip=(u"Crosstab conversions.\n\n" u"'document frequency': based on a pivot crosstab,\n" u"return a new crosstab giving, for each column,\n" u"the number of distinct rows that have nonzero\n" u"frequency (hence the resulting crosstab contains\n" u"a single row).\n\n" u"'association matrix': based on a pivot crosstab,\n" u"return a symmetric table with a measure of\n" u"associativity between each pair of columns of the\n" u"original table (see also the effect of the 'bias'\n" u"parameter)."), ) self.conversionTypeCombo.setMinimumWidth(150) gui.separator(widget=self.transformBoxLine5, width=5) self.associationBiasCombo = gui.comboBox( widget=self.transformBoxLine5, master=self, orientation='horizontal', value='associationBias', items=[u'frequent', u'none', u'rare'], sendSelectedValue=True, label=u'Bias:', labelWidth=40, callback=self.sendButton.settingsChanged, tooltip=(u"Association bias (alpha parameter in Deneulin,\n" u"Gautier, Le Fur, & Bavaud 2014).\n\n" u"'frequent': emphasizes strong associations\n" u"between frequent units (alpha=1).\n\n" u"'none': balanced compromise between\n" u"frequent and rare units (alpha=0.5).\n\n" u"'rare': emphasizes strong associations\n" u"between rare units (alpha=0). Note that in this\n" u"particular case, values greater than 1 express an\n" u"attraction and values lesser than 1 a repulsion."), ) self.associationBiasCombo.setMinimumWidth(70) gui.separator(widget=self.transformBox, height=3) self.transformBoxLine6 = gui.widgetBox( widget=self.transformBox, orientation='vertical', ) self.reformatCheckbox = gui.checkBox( widget=self.transformBoxLine6, master=self, value='reformat', label=u'Reformat to sparse crosstab', callback=self.sendButton.settingsChanged, tooltip=(u"Reformat a crosstab to sparse format, where each\n" u"row corresponds to a pair 'row-column' of the\n" u"original crosstab."), ) gui.separator(widget=self.transformBoxLine6, height=3) iBox = gui.indentedBox(widget=self.transformBoxLine6, ) self.unweightedCheckbox = gui.checkBox( widget=iBox, master=self, value='unweighted', label=u'Encode counts by repeating rows', callback=self.sendButton.settingsChanged, tooltip=(u"This option (only available for crosstabs with\n" u"integer values) specifies that values will be\n" u"encoded in the sparse matrix by the number of\n" u"times each row (i.e. each pair row-column of the\n" u"original crosstab) is repeated. Otherwise each\n" u"row-column pair will appear only once and the\n" u"corresponding value will be stored explicitely\n" u"in a separate column with label '__weight__'.\n"), ) gui.separator(widget=self.transformBox, height=3) self.advancedSettings.advancedWidgets.append(self.transformBox) self.advancedSettings.advancedWidgetsAppendSeparator() # This "dummy" box is the reason why an extra (and unwanted) pixel # appears just below the Advanced Settings checkbox. It is necessary # for the widget's size to adjust properly when switching between # modes... dummyBox = gui.widgetBox( widget=self.controlArea, addSpace=False, ) self.advancedSettings.basicWidgets.append(dummyBox) # Conversion box encodingBox = gui.widgetBox( widget=self.controlArea, box=u'Encoding', orientation='horizontal', addSpace=False, ) gui.widgetLabel( widget=encodingBox, labelWidth=180, label=u'Output file:', ) conversionEncodingCombo = gui.comboBox( widget=encodingBox, master=self, value='exportEncoding', items=type(self).encodings, sendSelectedValue=True, callback=self.sendButton.settingsChanged, orientation='horizontal', tooltip=(u"Select the encoding of the table that can be\n" u"saved to a file by clicking the 'Export' button\n" u"below.\n\n" u"Note that the table that is copied to the\n" u"clipboard by clicking the 'Copy to clipboard'\n" u"button below is always encoded in utf-8."), ) conversionEncodingCombo.setMinimumWidth(150) addSeparatorAfterDefaultEncodings(conversionEncodingCombo) gui.separator(widget=encodingBox, width=5) gui.widgetLabel( widget=encodingBox, label='', ) # Export box exportBox = gui.widgetBox( widget=self.controlArea, box=u'Export', orientation='vertical', addSpace=False, ) exportBoxLine2 = gui.widgetBox( widget=exportBox, orientation='horizontal', ) gui.widgetLabel( widget=exportBoxLine2, labelWidth=180, label=u'Column delimiter:', ) colDelimiterCombo = gui.comboBox( widget=exportBoxLine2, master=self, value='colDelimiter_idx', callback=self.sendButton.settingsChanged, orientation='horizontal', items=[text for text, _ in ColumnDelimiters], tooltip=(u"Select the character used for delimiting columns."), ) colDelimiterCombo.setMinimumWidth(150) gui.separator(widget=exportBoxLine2, width=5) dummyLabel = gui.widgetLabel( widget=exportBoxLine2, label='', ) gui.separator(widget=exportBox, height=2) gui.checkBox( widget=exportBox, master=self, value='includeOrangeHeaders', label=u'Include Orange headers', tooltip=(u"Include Orange table headers in output file."), ) gui.separator(widget=exportBox, height=2) exportBoxLine3 = gui.widgetBox( widget=exportBox, orientation='horizontal', ) self.exportButton = gui.button( widget=exportBoxLine3, master=self, label=u'Export to file', callback=self.exportFile, tooltip=(u"Open a dialog for selecting the output file to\n" u"which the table will be saved."), ) self.copyButton = gui.button( widget=exportBoxLine3, master=self, label=u'Copy to clipboard', callback=self.copyToClipboard, tooltip=(u"Copy table to clipboard, in order to paste it in\n" u"another application (typically in a spreadsheet)." u"\n\nNote that the only possible encoding is utf-8."), ) gui.separator(widget=exportBox, height=2) self.advancedSettings.advancedWidgets.append(exportBox) # Export box basicExportBox = gui.widgetBox( widget=self.controlArea, box=u'Export', orientation='vertical', addSpace=True, ) basicExportBoxLine1 = gui.widgetBox( widget=basicExportBox, orientation='horizontal', ) self.basicExportButton = gui.button( widget=basicExportBoxLine1, master=self, label=u'Export to file', callback=self.exportFile, tooltip=(u"Open a dialog for selecting the output file to\n" u"which the table will be saved."), ) self.basicCopyButton = gui.button( widget=basicExportBoxLine1, master=self, label=u'Copy to clipboard', callback=self.copyToClipboard, tooltip=(u"Copy table to clipboard, in order to paste it in\n" u"another application (typically in a spreadsheet)." u"\n\nNote that the only possible encoding is utf-8."), ) gui.separator(widget=basicExportBox, height=2) self.advancedSettings.basicWidgets.append(basicExportBox) gui.rubber(self.controlArea) # Send button... self.sendButton.draw() # Info box... self.infoBox.draw() self.sendButton.sendIf() self.adjustSizeWithTimer() def inputData(self, newInput): """Process incoming data.""" self.table = newInput self.infoBox.inputChanged() self.sendButton.sendIf() def sendData(self): """Convert and send table""" # Check that there is something on input... if not self.table: self.infoBox.setText(u'Widget needs input.', 'warning') self.send('Orange table', None) self.send('Textable table', None) self.send('Segmentation', None, self) if self.segmentation is not None: self.segmentation.clear() self.segmentation = None return transformed_table = self.table if self.displayAdvancedSettings: # Precompute number of iterations... numIterations = 0 if self.transpose: num_cols = len(transformed_table.row_ids) num_rows = len(transformed_table.col_ids) else: num_rows = len(transformed_table.row_ids) num_cols = len(transformed_table.col_ids) if self.normalize: if self.normalizeMode == 'rows': numIterations += num_rows elif self.normalizeMode == 'columns': numIterations += num_cols elif self.normalizeMode == 'presence/absence': numIterations += num_cols * num_rows elif self.normalizeMode == 'quotients': numIterations += num_cols * (num_rows + 1) elif self.normalizeMode == 'TF-IDF': numIterations += num_cols elif self.convert: numIterations += num_cols if self.reformat: numIterations += num_rows self.controlArea.setDisabled(True) self.infoBox.setText(u"Processing, please wait...", "warning") progressBar = ProgressBar(self, numIterations) # Sort if needed... if self.sortRows or self.sortCols: if self.sortRows: if self.sortRowsKeyId == 0: key_col_id = transformed_table.header_col_id else: key_col_id = transformed_table.col_ids[ self.sortRowsKeyId - 1] else: key_col_id = None if self.sortCols: if self.sortColsKeyId == 0: key_row_id = transformed_table.header_row_id else: key_row_id = transformed_table.row_ids[ self.sortColsKeyId - 1] else: key_row_id = None transformed_table = transformed_table.to_sorted( key_col_id, self.sortRowsReverse, key_row_id, self.sortColsReverse, ) # Transpose if needed... if self.transpose: transformed_table = transformed_table.to_transposed() # Normalize if needed... if self.normalize: transformed_table = transformed_table.to_normalized( self.normalizeMode, self.normalizeType.lower(), progressBar.advance) # Convert if needed... elif self.convert: if self.conversionType == 'document frequency': transformed_table = transformed_table.to_document_frequency( progress_callback=progressBar.advance) elif self.conversionType == 'association matrix': transformed_table = transformed_table.to_association_matrix( bias=self.associationBias, progress_callback=progressBar.advance) # Reformat if needed... if self.reformat: if self.unweighted: transformed_table = transformed_table.to_flat( progress_callback=progressBar.advance) else: transformed_table = transformed_table.to_weighted_flat( progress_callback=progressBar.advance) progressBar.finish() self.controlArea.setDisabled(False) self.transformed_table = transformed_table orangeTable = transformed_table.to_orange_table() self.send('Orange table', orangeTable) self.send('Textable table', transformed_table) if self.displayAdvancedSettings: colDelimiter = self.colDelimiter includeOrangeHeaders = self.includeOrangeHeaders else: colDelimiter = '\t' includeOrangeHeaders = False outputString = transformed_table.to_string( output_orange_headers=includeOrangeHeaders, col_delimiter=colDelimiter, ) if self.segmentation is None: self.segmentation = Input(label=u'table', text=outputString) else: self.segmentation.update(outputString, label=u'table') self.send('Segmentation', self.segmentation, self) message = 'Table with %i row@p' % len(transformed_table.row_ids) message = pluralize(message, len(transformed_table.row_ids)) message += ' and %i column@p ' % (len(transformed_table.col_ids) + 1) message = pluralize(message, len(transformed_table.col_ids) + 1) message += 'sent to output.' self.infoBox.setText(message) self.sendButton.resetSettingsChangedFlag() def exportFile(self): """Display a FileDialog and save table to file""" if getattr(self, self.sendButton.changedFlag): QMessageBox.warning( None, 'Textable', 'Input data and/or settings have changed.\nPlease click ' "'Send' or check 'Send automatically' before proceeding.", QMessageBox.Ok) return filePath, _ = QFileDialog.getSaveFileName( self, u'Export Table to File', self.lastLocation, ) if filePath: self.lastLocation = os.path.dirname(filePath) encoding = re.sub(r"[ ]\(.+", "", self.exportEncoding) outputFile = codecs.open( filePath, encoding=encoding, mode='w', errors='xmlcharrefreplace', ) outputFile.write(self.segmentation[0].get_content()) outputFile.close() QMessageBox.information(None, 'Textable', 'Table successfully exported to file.', QMessageBox.Ok) def copyToClipboard(self): """Copy output table to clipboard""" if getattr(self, self.sendButton.changedFlag): QMessageBox.warning( None, 'Textable', 'Input data and/or settings have changed.\nPlease click ' "'Send' or check 'Send automatically' before proceeding.", QMessageBox.Ok) return QApplication.clipboard().setText(self.segmentation[0].get_content()) QMessageBox.information(None, 'Textable', 'Table successfully copied to clipboard.', QMessageBox.Ok) def updateGUI(self): """Update GUI state""" if not self.table: if self.displayAdvancedSettings: self.transformBox.setDisabled(True) self.exportButton.setDisabled(True) self.copyButton.setDisabled(True) else: self.basicExportButton.setDisabled(True) self.basicCopyButton.setDisabled(True) else: if self.displayAdvancedSettings: self.transformBox.setDisabled(False) self.exportButton.setDisabled(False) self.copyButton.setDisabled(False) else: self.basicExportButton.setDisabled(False) self.basicCopyButton.setDisabled(False) if self.displayAdvancedSettings: self.normalizeTypeCombo.setDisabled(True) self.associationBiasCombo.setDisabled(True) if self.sortRows: self.sortRowsKeyIdCombo.clear() self.sortRowsKeyIdCombo.addItem( str(self.table.header_col_id)) if isinstance(self.table.col_ids[0], int): tableColIds = [str(i) for i in self.table.col_ids] else: tableColIds = self.table.col_ids for col_id in tableColIds: self.sortRowsKeyIdCombo.addItem(str(col_id)) self.sortRowsKeyId = self.sortRowsKeyId or 0 self.sortRowsKeyIdCombo.setDisabled(False) self.sortRowsReverseCheckBox.setDisabled(False) else: self.sortRowsKeyIdCombo.setDisabled(True) self.sortRowsKeyIdCombo.clear() self.sortRowsReverseCheckBox.setDisabled(True) if self.sortCols: self.sortColsKeyIdCombo.clear() self.sortColsKeyIdCombo.addItem( str(self.table.header_row_id)) if isinstance(self.table.row_ids[0], int): tableRowIds = [str(i) for i in self.table.row_ids] else: tableRowIds = self.table.row_ids for row_id in tableRowIds: self.sortColsKeyIdCombo.addItem(str(row_id)) self.sortColsKeyId = self.sortColsKeyId or 0 self.sortColsKeyIdCombo.setDisabled(False) self.sortColsReverseCheckBox.setDisabled(False) else: self.sortColsKeyIdCombo.setDisabled(True) self.sortColsKeyIdCombo.clear() self.sortColsReverseCheckBox.setDisabled(True) # Crosstab... if isinstance(self.table, Crosstab): self.transposeCheckBox.setDisabled(False) self.transformBoxLine4.setDisabled(False) self.transformBoxLine5.setDisabled(False) self.transformBoxLine6.setDisabled(False) self.normalizeModeCombo.setDisabled(True) self.normalizeTypeCombo.setDisabled(True) self.conversionTypeCombo.setDisabled(True) self.associationBiasCombo.setDisabled(True) self.reformatCheckbox.setDisabled(False) self.unweightedCheckbox.setDisabled(False) # IntPivotCrosstab... if isinstance(self.table, IntPivotCrosstab): # Normalize... if self.normalize: self.normalizeModeCombo.setDisabled(False) self.transformBoxLine5.setDisabled(True) self.convert = False self.unweightedCheckbox.setDisabled(True) self.unweighted = False if (self.normalizeMode == u'rows' or self.normalizeMode == u'columns'): self.normalizeTypeCombo.setDisabled(False) # Convert... elif self.convert: self.conversionTypeCombo.setDisabled(False) self.transformBoxLine4.setDisabled(True) self.normalize = False self.unweightedCheckbox.setDisabled(True) self.unweighted = False if self.conversionType == 'association matrix': self.associationBiasCombo.setDisabled(False) # Reformat... if self.reformat: # Flat crosstab if self.unweighted: self.transformBoxLine4.setDisabled(True) self.normalize = False self.transformBoxLine5.setDisabled(True) self.convert = False else: self.unweightedCheckbox.setDisabled(True) # Not IntPivotCrosstab... else: self.transformBoxLine4.setDisabled(True) self.normalize = False self.transformBoxLine5.setDisabled(True) self.convert = False self.unweightedCheckbox.setDisabled(True) # PivotCrosstab... if isinstance(self.table, PivotCrosstab): self.transposeCheckBox.setDisabled(False) # Not Crosstab... else: self.transposeCheckBox.setDisabled(True) self.transpose = False self.transformBoxLine4.setDisabled(True) self.normalize = False self.transformBoxLine5.setDisabled(True) self.convert = False self.transformBoxLine6.setDisabled(True) self.reformat = False self.advancedSettings.setVisible(self.displayAdvancedSettings) def onDeleteWidget(self): if self.segmentation is not None: self.segmentation.clear()
class OWTextableDisplay(OWTextableBaseWidget): """A widget for displaying segmentations""" name = "Display" description = "Display or export the details of a segmentation" icon = "icons/Display.png" priority = 6001 inputs = [('Segmentation', Segmentation, "inputData", widget.Single)] outputs = [('Bypassed segmentation', Segmentation, widget.Default), ('Displayed segmentation', Segmentation)] settingsHandler = VersionedSettingsHandler( version=__version__.rsplit(".", 1)[0]) # Settings... displayAdvancedSettings = settings.Setting(False) customFormatting = settings.Setting(False) limitNumberOfSegments = settings.Setting(True) customFormat = settings.Setting(u'%(__content__)s') segmentDelimiter = settings.Setting(u'\\n') header = settings.Setting(u'') footer = settings.Setting(u'') encoding = settings.Setting('utf8') lastLocation = settings.Setting('.') basicFormatHTML = settings.Setting(True) # Predefined list of available encodings... encodings = getPredefinedEncodings() want_main_area = True def __init__(self, *args, **kwargs): """Initialize a Display widget""" super().__init__(*args, **kwargs) # Current general warning and error messages (as submited # through self.error(text) and self.warning(text) self._currentErrorMessage = "" self._currentWarningMessage = "" self.segmentation = None self.displayedSegmentation = Input(label=u'displayed_segmentation', text=u'') self.goto = 0 self.browser = QTextBrowser() self.infoBox = InfoBox(widget=self.mainArea) self.sendButton = SendButton( widget=self.controlArea, master=self, callback=self.sendData, sendIfPreCallback=self.updateGUI, infoBoxAttribute='infoBox', ) # GUI... # NB: These are "custom" advanced settings, not those provided by # TextableUtils. Note also that there are two copies of the checkbox # controlling the same attribute, to simulate its moving from one # side of the widget to the other... self.advancedSettingsCheckBoxLeft = gui.checkBox( widget=self.controlArea, master=self, value='displayAdvancedSettings', label=u'Advanced settings', callback=self.sendButton.settingsChanged, tooltip=(u"Toggle advanced settings on and off."), ) gui.separator(widget=self.controlArea, height=3) # Options box... optionsBox = gui.widgetBox( widget=self.controlArea, box=u'Options', orientation='vertical', addSpace=True, ) gui.checkBox( widget=optionsBox, master=self, value='limitNumberOfSegments', label=u'Limit number of displayed segments', callback=self.sendButton.settingsChanged, tooltip=( "By default, the number of displayed segments is limited to\n" "%i, to keep the widget's execution time reasonably short.\n" "Unchecking this box makes it possible to display all the\n" "segments. Use with caution, as execution time can become\n" "prohibitively long for segmentations with more than a few\n" "thousand segments." % LTTL.Segmentation.MAX_SEGMENT_STRING), ) # Custom formatting box... formattingBox = gui.widgetBox( widget=self.controlArea, box=u'Formatting', orientation='vertical', addSpace=True, ) gui.checkBox( widget=formattingBox, master=self, value='customFormatting', label=u'Apply custom formatting', callback=self.sendButton.settingsChanged, tooltip=(u"Check this box to apply custom formatting."), ) gui.separator(widget=formattingBox, height=3) self.formattingIndentedBox = gui.indentedBox(widget=formattingBox, ) headerLineEdit = gui.lineEdit( widget=self.formattingIndentedBox, master=self, value='header', label=u'Header:', labelWidth=131, orientation='horizontal', callback=self.sendButton.settingsChanged, tooltip=(u"String that will be appended at the beginning of\n" u"the formatted segmentation."), ) headerLineEdit.setMinimumWidth(200) gui.separator(widget=self.formattingIndentedBox, height=3) gui.lineEdit( widget=self.formattingIndentedBox, master=self, value='customFormat', label=u'Format:', labelWidth=131, orientation='horizontal', callback=self.sendButton.settingsChanged, tooltip=(u"String specifying how to format the segmentation.\n\n" u"See user guide for detailed instructions."), ) gui.separator(widget=self.formattingIndentedBox, height=3) gui.lineEdit( widget=self.formattingIndentedBox, master=self, value='segmentDelimiter', label=u'Segment delimiter:', labelWidth=131, orientation='horizontal', callback=self.sendButton.settingsChanged, tooltip=(u"Delimiter that will be inserted between segments.\n\n" u"Note that '\\n' stands for carriage return and\n" u"'\\t' for tabulation."), ) gui.separator(widget=self.formattingIndentedBox, height=3) gui.lineEdit( widget=self.formattingIndentedBox, master=self, value='footer', label=u'Footer:', labelWidth=131, orientation='horizontal', callback=self.sendButton.settingsChanged, tooltip=(u"String that will be appended at the end of the\n" u"formatted segmentation."), ) headerLineEdit.setMinimumWidth(200) gui.separator(widget=self.formattingIndentedBox, height=3) # Advanced export box self.advancedExportBox = gui.widgetBox( widget=self.controlArea, box=u'Export', orientation='vertical', addSpace=True, ) encodingCombo = gui.comboBox( widget=self.advancedExportBox, master=self, value='encoding', items=type(self).encodings, sendSelectedValue=True, orientation='horizontal', label=u'File encoding:', labelWidth=151, tooltip=(u"Select the encoding of the file into which a\n" u"displayed segmentation can be saved by clicking\n" u"the 'Export' button below.\n\n" u"Note that the displayed segmentation that is\n" u"copied to the clipboard by clicking the 'Copy\n" u"to clipboard' button below is always encoded\n" u"in utf-8."), ) addSeparatorAfterDefaultEncodings(encodingCombo) gui.separator(widget=self.advancedExportBox, height=3) exportBoxLine2 = gui.widgetBox( widget=self.advancedExportBox, orientation='horizontal', ) gui.button( widget=exportBoxLine2, master=self, label=u'Export to file', callback=self.exportFile, tooltip=(u"Open a dialog for selecting the output file to\n" u"which the displayed segmentation will be saved."), ) gui.button( widget=exportBoxLine2, master=self, label=u'Copy to clipboard', callback=self.copyToClipboard, tooltip=(u"Copy the displayed segmentation to clipboard, in\n" u"order to paste it in another application." u"\n\nNote that the only possible encoding is utf-8."), ) gui.rubber(self.controlArea) # Send button and checkbox self.sendButton.draw() # Main area # NB: This is the second copy of the advanced settings checkbox, # see above... self.advancedSettingsRightBox = gui.widgetBox( widget=self.mainArea, orientation='vertical', ) self.advancedSettingsCheckBoxRight = gui.checkBox( widget=self.advancedSettingsRightBox, master=self, value='displayAdvancedSettings', label=u'Advanced settings', callback=self.sendButton.settingsChanged, tooltip=(u"Toggle advanced settings on and off."), ) gui.separator(widget=self.advancedSettingsRightBox, height=3) self.advancedSettingsCheckBoxRightPlaceholder = gui.separator( widget=self.mainArea, height=25, ) self.basicFormatBox = gui.widgetBox( widget=self.mainArea, orientation='vertical', box=u'Format', addSpace=False, ) gui.checkBox( widget=self.basicFormatBox, master=self, value='basicFormatHTML', label=u'Display segmentation in rich text format (HTML)', callback=self.sendButton.settingsChanged, tooltip=(u"TODO."), ) self.navigationBox = gui.widgetBox( widget=self.mainArea, orientation='vertical', box=u'Navigation', addSpace=True, ) self.gotoSpin = gui.spin( widget=self.navigationBox, master=self, value='goto', minv=1, maxv=1, orientation='horizontal', label=u'Go to segment:', labelWidth=180, callback=self.gotoSegment, tooltip=(u"Jump to a specific segment number."), ) self.mainArea.layout().addWidget(self.browser) # Advanced export box gui.separator(widget=self.mainArea, height=3) self.basicExportBox = gui.widgetBox( widget=self.mainArea, box=u'Export', orientation='horizontal', addSpace=True, ) gui.button( widget=self.basicExportBox, master=self, label=u'Save to file', callback=self.exportFile, tooltip=(u"Open a dialog for selecting the output file to\n" u"which the displayed segmentation will be saved."), ) gui.button( widget=self.basicExportBox, master=self, label=u'Copy to clipboard', callback=self.copyToClipboard, tooltip=(u"Copy the displayed segmentation to clipboard, in\n" u"order to paste it in another application."), ) # Info box... self.infoBox.draw() self.sendButton.sendIf() def inputData(self, newInput): """Process incoming data.""" self.segmentation = newInput self.infoBox.inputChanged() self.sendButton.sendIf() def sendData(self): """Send segmentation to output""" if not self.segmentation: self.infoBox.setText(u'Widget needs input.', 'warning') self.send('Bypassed segmentation', None, self) self.send('Displayed segmentation', None, self) return self.send('Bypassed segmentation', Segmenter.bypass(self.segmentation, self.captionTitle), self) # TODO: Check if this is correct replacement for textable v1.*, v2.* if 'format' in self._currentWarningMessage or \ 'format' in self._currentErrorMessage: self.send('Displayed segmentation', None, self) return if len(self.displayedSegmentation[0].get_content()) > 0: self.send('Displayed segmentation', self.displayedSegmentation, self) else: self.send('Displayed segmentation', None, self) # TODO: Differes only in capitalization with a check before # Is this intentional? if "Format" not in self._currentErrorMessage: message = u'%i segment@p sent to output.' % len(self.segmentation) message = pluralize(message, len(self.segmentation)) self.infoBox.setText(message) self.sendButton.resetSettingsChangedFlag() def updateGUI(self): """Update GUI state""" self.controlArea.setVisible(self.displayAdvancedSettings) self.advancedSettingsCheckBoxRightPlaceholder.setVisible( self.displayAdvancedSettings) self.advancedSettingsCheckBoxLeft.setVisible( self.displayAdvancedSettings) self.advancedSettingsRightBox.setVisible( not self.displayAdvancedSettings) self.basicFormatBox.setVisible(not self.displayAdvancedSettings) self.basicExportBox.setVisible(not self.displayAdvancedSettings) self.browser.clear() if self.segmentation: if self.displayAdvancedSettings: customFormatting = self.customFormatting else: customFormatting = False self.autoSend = True self.controlArea.setDisabled(True) self.mainArea.setDisabled(True) self.infoBox.setText(u"Processing, please wait...", "warning") if len(self.segmentation) <= LTTL.Segmentation.MAX_SEGMENT_STRING: display_all = None else: display_all = not self.limitNumberOfSegments if customFormatting: self.navigationBox.setVisible(False) self.navigationBox.setDisabled(True) self.advancedExportBox.setDisabled(True) self.formattingIndentedBox.setDisabled(False) displayedString = u'' progressBar = ProgressBar(self, iterations=len(self.segmentation)) try: displayedString = self.segmentation.to_string( codecs.decode(self.customFormat, 'unicode_escape'), codecs.decode(self.segmentDelimiter, 'unicode_escape'), codecs.decode(self.header, 'unicode_escape'), codecs.decode(self.footer, 'unicode_escape'), True, progress_callback=progressBar.advance, display_all=display_all, ) self.infoBox.settingsChanged() self.advancedExportBox.setDisabled(False) self.warning() self.error() except TypeError as type_error: try: self.infoBox.setText(type_error.message, 'error') except AttributeError: message = "Please enter a valid format (type error)." self.infoBox.setText(message, 'error') except KeyError: message = "Please enter a valid format (error: missing name)." self.infoBox.setText(message, 'error') except ValueError: message = "Please enter a valid format (error: missing " \ + "variable type)." self.infoBox.setText(message, 'error') self.browser.append(displayedString) self.displayedSegmentation.update( displayedString, label=self.captionTitle, ) progressBar.finish() else: self.navigationBox.setVisible(self.basicFormatHTML) self.formattingIndentedBox.setDisabled(True) self.warning() self.error() progressBar = ProgressBar(self, iterations=len(self.segmentation)) if self.displayAdvancedSettings: displayedString, summarized = self.segmentation.to_html( True, progressBar.advance, display_all, ) self.navigationBox.setEnabled( len(self.segmentation) > 1 and not summarized) elif self.basicFormatHTML: displayedString, summarized = self.segmentation.to_html( True, progressBar.advance, ) self.navigationBox.setEnabled( len(self.segmentation) > 1 and not summarized) else: displayedString = self.segmentation.to_string( formatting="%(__content__)s", segment_delimiter="\n", progress_callback=progressBar.advance, ) self.browser.append(displayedString) self.displayedSegmentation.update( displayedString, label=self.captionTitle, ) self.gotoSpin.setRange(1, len(self.segmentation)) if self.goto: self.browser.setSource(QUrl("#%i" % self.goto)) else: self.browser.setSource(QUrl("#top")) self.advancedExportBox.setDisabled(False) self.infoBox.settingsChanged() progressBar.finish() self.controlArea.setDisabled(False) self.mainArea.setDisabled(False) else: self.goto = 0 self.gotoSpin.setRange(0, 1) self.advancedExportBox.setDisabled(True) self.navigationBox.setVisible(True) self.navigationBox.setEnabled(False) self.formattingIndentedBox.setDisabled(True) def gotoSegment(self): if self.goto: self.browser.setSource(QUrl("#%i" % self.goto)) else: self.browser.setSource(QUrl("#top")) def exportFile(self): """Display a FileDialog and export segmentation to file""" filePath, _ = QFileDialog.getSaveFileName( self, u'Export segmentation to File', self.lastLocation, ) if filePath: self.lastLocation = os.path.dirname(filePath) if self.displayAdvancedSettings: encoding = re.sub(r"[ ]\(.+", "", self.encoding) else: encoding = "utf8" outputFile = codecs.open( filePath, encoding=encoding, mode='w', errors='xmlcharrefreplace', ) outputFile.write( #normalizeCarriageReturns( self.displayedSegmentation[0].get_content() #) ) outputFile.close() QMessageBox.information(None, 'Textable', 'Segmentation correctly exported', QMessageBox.Ok) def copyToClipboard(self): """Copy displayed segmentation to clipboard""" QApplication.clipboard().setText( #normalizeCarriageReturns( self.displayedSegmentation[0].get_content() #) ) QMessageBox.information(None, 'Textable', 'Segmentation correctly copied to clipboard', QMessageBox.Ok) def onDeleteWidget(self): if self.displayedSegmentation is not None: self.displayedSegmentation.clear() def setCaption(self, title): if 'captionTitle' in dir(self): changed = title != self.captionTitle super().setCaption(title) if changed: self.sendButton.settingsChanged() else: super().setCaption(title) def error(self, *args, **kwargs): # Reimplemented to track the current active error message if args: text_or_id = args[0] else: text_or_id = kwargs.get("text_or_id", None) if isinstance(text_or_id, str) or text_or_id is None: self._currentErrorMessage = text_or_id or "" return super().error(*args, **kwargs) def warning(self, *args, **kwargs): # Reimplemented to track the current active warning message if args: text_or_id = args[0] else: text_or_id = kwargs.get("text_or_id", None) if isinstance(text_or_id, str) or text_or_id is None: self._currentWarningMessage = text_or_id or "" return super().warning(*args, **kwargs)