コード例 #1
0
ファイル: test_input.py プロジェクト: Python3pkg/LTTL
 def test_update_string(self):
     """Does update modify stored string?"""
     seg = Input('test2')
     seg.update('modified')
     self.assertEqual(Segmentation.get_data(-1)[:],
                      'modified',
                      msg="update doesn't modify stored string!")
コード例 #2
0
ファイル: test_input.py プロジェクト: ArcaniteSolutions/LTTL
 def test_update_string(self):
     """Does update modify stored string?"""
     seg = Input(u'test2')
     seg.update(u'modified')
     self.assertEqual(
         Segmentation.get_data(-1)[:],
         u'modified',
         msg="update doesn't modify stored string!"
     )
コード例 #3
0
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__()
コード例 #4
0
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()
コード例 #5
0
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)