コード例 #1
0
ファイル: owitemsets.py プロジェクト: r0b1n1983liu/o3env
class OWItemsets(widget.OWWidget):
    name = 'Frequent Itemsets'
    description = 'Explore sets of items that frequently appear together.'
    icon = 'icons/FrequentItemsets.svg'
    priority = 10

    inputs = [("Data", Table, 'set_data')]
    outputs = [(Output.DATA, Table)]

    minSupport = settings.Setting(30)
    maxItemsets = settings.Setting(10000)
    filterSearch = settings.Setting(True)
    autoFind = settings.Setting(False)
    autoSend = settings.Setting(True)
    filterKeywords = settings.Setting('')
    filterMinItems = settings.Setting(1)
    filterMaxItems = settings.Setting(10000)

    UserAdviceMessages = [
        widget.Message('Itemset are listed in item-sorted order, i.e. '
                       'an itemset containing A and B is only listed once, as '
                       'A > B (and not also B > A).',
                       'itemsets-order', widget.Message.Warning),
        widget.Message('To select all the itemsets that are descendants of '
                       '(include) some item X (i.e. the whole subtree), you '
                       'can fold the subtree at that item and then select it.',
                       'itemsets-order', widget.Message.Information)
    ]

    def __init__(self):
        self.tree = QTreeWidget(self.mainArea,
                                columnCount=2,
                                allColumnsShowFocus=True,
                                alternatingRowColors=True,
                                selectionMode=QTreeWidget.ExtendedSelection,
                                uniformRowHeights=True)
        self.tree.setHeaderLabels(["Itemsets", "Support", "%"])
        self.tree.header().setStretchLastSection(True)
        self.tree.itemSelectionChanged.connect(self.selectionChanged)
        self.mainArea.layout().addWidget(self.tree)

        box = gui.widgetBox(self.controlArea, "Info")
        self.nItemsets = self.nSelectedExamples = self.nSelectedItemsets = ''
        gui.label(box, self, "Number of itemsets: %(nItemsets)s")
        gui.label(box, self, "Selected itemsets: %(nSelectedItemsets)s")
        gui.label(box, self, "Selected examples: %(nSelectedExamples)s")
        hbox = gui.widgetBox(box, orientation='horizontal')
        gui.button(hbox, self, "Expand all", callback=self.tree.expandAll)
        gui.button(hbox, self, "Collapse all", callback=self.tree.collapseAll)

        box = gui.widgetBox(self.controlArea, 'Find itemsets')
        gui.hSlider(box, self, 'minSupport', minValue=1, maxValue=100,
                    label='Minimal support:', labelFormat="%d%%",
                    callback=lambda: self.find_itemsets())
        gui.hSlider(box, self, 'maxItemsets', minValue=10000, maxValue=100000, step=10000,
                    label='Max. number of itemsets:', labelFormat="%d",
                    callback=lambda: self.find_itemsets())
        gui.checkBox(box, self, 'filterSearch',
                     label='Apply below filters in search',
                     tooltip='If checked, the itemsets are filtered according '
                             'to below filter conditions already in the search '
                             'phase. \nIf unchecked, the only filters applied '
                             'during search are the ones above, '
                             'and the itemsets are \nfiltered afterwards only for '
                             'display, i.e. only the matching itemsets are shown.')
        self.button = gui.auto_commit(
            box, self, 'autoFind', 'Find itemsets', commit=self.find_itemsets)

        box = gui.widgetBox(self.controlArea, 'Filter itemsets')
        gui.lineEdit(box, self, 'filterKeywords', 'Contains:',
                     callback=self.filter_change, orientation='horizontal',
                     tooltip='A comma or space-separated list of regular '
                             'expressions.')
        hbox = gui.widgetBox(box, orientation='horizontal')
        gui.spin(hbox, self, 'filterMinItems', 1, 998, label='Min. items:',
                 callback=self.filter_change)
        gui.spin(hbox, self, 'filterMaxItems', 2, 999, label='Max. items:',
                 callback=self.filter_change)
        gui.rubber(hbox)

        gui.rubber(self.controlArea)
        gui.auto_commit(self.controlArea, self, 'autoSend', 'Send selection')

        self.filter_change()

    def sendReport(self):
        self.reportSettings("Itemset statistics",
                            [("Number of itemsets", self.nItemsets),
                             ("Selected itemsets", self.nSelectedItemsets),
                             ("Covered examples", self.nSelectedExamples),
                             ])
        self.reportSection("Itemsets")
        self.reportRaw(OWReport.reportTree(self.tree))

    ITEM_DATA_ROLE = Qt.UserRole + 1

    def selectionChanged(self):
        X = self.data.X
        mapping = self.onehot_mapping
        instances = set()
        where = np.where

        def whole_subtree(node):
            yield node
            for i in range(node.childCount()):
                yield from whole_subtree(node.child(i))

        def itemset(node):
            while node:
                yield node.data(0, self.ITEM_DATA_ROLE)
                node = node.parent()

        def selection_ranges(node):
            n_children = node.childCount()
            if n_children:
                yield (self.tree.indexFromItem(node.child(0)),
                       self.tree.indexFromItem(node.child(n_children - 1)))
            for i in range(n_children):
                yield from selection_ranges(node.child(i))

        nSelectedItemsets = 0
        item_selection = QItemSelection()
        for node in self.tree.selectedItems():
            nodes = (node,) if node.isExpanded() else whole_subtree(node)
            if not node.isExpanded():
                for srange in selection_ranges(node):
                    item_selection.select(*srange)
            for node in nodes:
                nSelectedItemsets += 1
                cols, vals = zip(*(mapping[i] for i in itemset(node)))
                instances.update(where((X[:, cols] == vals).all(axis=1))[0])
        self.tree.itemSelectionChanged.disconnect(self.selectionChanged)
        self.tree.selectionModel().select(item_selection,
                                          QItemSelectionModel.Select | QItemSelectionModel.Rows)
        self.tree.itemSelectionChanged.connect(self.selectionChanged)

        self.nSelectedExamples = len(instances)
        self.nSelectedItemsets = nSelectedItemsets
        self.output = self.data[sorted(instances)] or None
        self.commit()

    def commit(self):
        self.send(Output.DATA, self.output)

    def filter_change(self):
        isRegexMatch = self.isRegexMatch = re.compile(
            '|'.join(i.strip()
                     for i in re.split('(,|\s)+', self.filterKeywords.strip())
                     if i.strip())).search

        def hide(node, depth, has_kw):
            if not has_kw:
                has_kw = isRegexMatch(node.text(0))
            hidden = (sum(hide(node.child(i), depth + 1, has_kw)
                          for i in range(node.childCount())) == node.childCount()
                      if node.childCount() else
                      (not has_kw or
                       not self.filterMinItems <= depth <= self.filterMaxItems))
            node.setHidden(hidden)
            return hidden

        hide(self.tree.invisibleRootItem(), 0, False)

    class TreeWidgetItem(QTreeWidgetItem):
        def data(self, column, role):
            """Construct lazy tooltips"""
            if role != Qt.ToolTipRole:
                return super().data(column, role)
            tooltip = []
            while self:
                tooltip.append(self.text(0))
                self = self.parent()
            return ' '.join(reversed(tooltip))

    def find_itemsets(self):
        if self.data is None: return
        data = self.data
        self.tree.clear()
        self.tree.setUpdatesEnabled(False)
        self.tree.blockSignals(True)

        class ItemDict(dict):
            def __init__(self, item):
                self.item = item

        top = ItemDict(self.tree.invisibleRootItem())
        X, mapping = OneHot.encode(data)
        self.onehot_mapping = mapping
        names = {item: '{}={}'.format(var.name, val)
                 for item, var, val in OneHot.decode(mapping.keys(), data, mapping)}
        nItemsets = 0

        filterSearch = self.filterSearch
        filterMinItems, filterMaxItems = self.filterMinItems, self.filterMaxItems
        isRegexMatch = self.isRegexMatch

        # Find itemsets and populate the TreeView
        progress = gui.ProgressBar(self, self.maxItemsets + 1)
        for itemset, support in frequent_itemsets(X, self.minSupport / 100):

            if filterSearch and not filterMinItems <= len(itemset) <= filterMaxItems:
                continue

            parent = top
            first_new_item = None
            itemset_matches_filter = False

            for item in sorted(itemset):
                name = names[item]

                if filterSearch and not itemset_matches_filter:
                    itemset_matches_filter = isRegexMatch(name)

                child = parent.get(name)
                if child is None:
                    wi = self.TreeWidgetItem(parent.item, [name, str(support), '{:.1f}'.format(100 * support / len(data))])
                    wi.setData(0, self.ITEM_DATA_ROLE, item)
                    child = parent[name] = ItemDict(wi)

                    if first_new_item is None:
                        first_new_item = (parent, name)
                parent = child

            if filterSearch and not itemset_matches_filter:
                parent, name = first_new_item
                parent.item.removeChild(parent[name].item)
                del parent[name].item
                del parent[name]
            else:
                nItemsets += 1
                progress.advance()
            if nItemsets >= self.maxItemsets:
                break

        if not filterSearch:
            self.filter_change()
        self.nItemsets = nItemsets
        self.nSelectedItemsets = 0
        self.nSelectedExamples = 0
        self.tree.expandAll()
        for i in range(self.tree.columnCount()):
            self.tree.resizeColumnToContents(i)
        self.tree.setUpdatesEnabled(True)
        self.tree.blockSignals(False)
        progress.finish()

    def set_data(self, data):
        self.data = data
        if data is not None:
            self.warning(0, 'Data has continuous attributes which will be skipped.'
                            if data.domain.has_continuous_attributes() else None)
            self.error(1, 'Discrete features required but data has none.'
                          if not data.domain.has_discrete_attributes() else None)
            self.button.setDisabled(not data.domain.has_discrete_attributes())
        if self.autoFind:
            self.find_itemsets()
コード例 #2
0
class OWItemsets(widget.OWWidget):
    name = 'Frequent Itemsets'
    description = 'Explore sets of items that frequently appear together.'
    icon = 'icons/FrequentItemsets.svg'
    priority = 10

    inputs = [("Data", Table, 'set_data')]
    outputs = [(Output.DATA, Table)]

    minSupport = settings.Setting(30)
    maxItemsets = settings.Setting(10000)
    filterSearch = settings.Setting(True)
    autoFind = settings.Setting(False)
    autoSend = settings.Setting(True)
    filterKeywords = settings.Setting('')
    filterMinItems = settings.Setting(1)
    filterMaxItems = settings.Setting(10000)

    UserAdviceMessages = [
        widget.Message(
            'Itemset are listed in item-sorted order, i.e. '
            'an itemset containing A and B is only listed once, as '
            'A > B (and not also B > A).', 'itemsets-order',
            widget.Message.Warning),
        widget.Message(
            'To select all the itemsets that are descendants of '
            '(include) some item X (i.e. the whole subtree), you '
            'can fold the subtree at that item and then select it.',
            'itemsets-order', widget.Message.Information)
    ]

    def __init__(self):
        self._is_running = False
        self.isRegexMatch = lambda x: True
        self.tree = QTreeWidget(self.mainArea,
                                columnCount=2,
                                allColumnsShowFocus=True,
                                alternatingRowColors=True,
                                selectionMode=QTreeWidget.ExtendedSelection,
                                uniformRowHeights=True)
        self.tree.setHeaderLabels(["Itemsets", "Support", "%"])
        self.tree.header().setStretchLastSection(True)
        self.tree.itemSelectionChanged.connect(self.selectionChanged)
        self.mainArea.layout().addWidget(self.tree)

        box = gui.widgetBox(self.controlArea, "Info")
        self.nItemsets = self.nSelectedExamples = self.nSelectedItemsets = ''
        gui.label(box, self, "Number of itemsets: %(nItemsets)s")
        gui.label(box, self, "Selected itemsets: %(nSelectedItemsets)s")
        gui.label(box, self, "Selected examples: %(nSelectedExamples)s")
        hbox = gui.widgetBox(box, orientation='horizontal')
        gui.button(hbox, self, "Expand all", callback=self.tree.expandAll)
        gui.button(hbox, self, "Collapse all", callback=self.tree.collapseAll)

        box = gui.widgetBox(self.controlArea, 'Find itemsets')
        gui.valueSlider(box,
                        self,
                        'minSupport',
                        values=[.0001, .0005, .001, .005, .01, .05, .1, .5] +
                        list(range(1, 101)),
                        label='Minimal support:',
                        labelFormat="%g%%",
                        callback=lambda: self.find_itemsets())
        gui.hSlider(box,
                    self,
                    'maxItemsets',
                    minValue=10000,
                    maxValue=100000,
                    step=10000,
                    label='Max. number of itemsets:',
                    labelFormat="%d",
                    callback=lambda: self.find_itemsets())
        self.button = gui.auto_commit(box,
                                      self,
                                      'autoFind',
                                      'Find itemsets',
                                      commit=self.find_itemsets)

        box = gui.widgetBox(self.controlArea, 'Filter itemsets')
        gui.lineEdit(box,
                     self,
                     'filterKeywords',
                     'Contains:',
                     callback=self.filter_change,
                     orientation='horizontal',
                     tooltip='A comma or space-separated list of regular '
                     'expressions.')
        hbox = gui.widgetBox(box, orientation='horizontal')
        gui.spin(hbox,
                 self,
                 'filterMinItems',
                 1,
                 998,
                 label='Min. items:',
                 callback=self.filter_change)
        gui.spin(hbox,
                 self,
                 'filterMaxItems',
                 2,
                 999,
                 label='Max. items:',
                 callback=self.filter_change)
        gui.checkBox(box,
                     self,
                     'filterSearch',
                     label='Apply these filters in search',
                     tooltip='If checked, the itemsets are filtered according '
                     'to these filter conditions already in the search '
                     'phase. \nIf unchecked, the only filters applied '
                     'during search are the ones above, '
                     'and the itemsets are \nfiltered afterwards only for '
                     'display, i.e. only the matching itemsets are shown.')

        gui.rubber(hbox)

        gui.rubber(self.controlArea)
        gui.auto_commit(self.controlArea, self, 'autoSend', 'Send selection')

        self.filter_change()

    ITEM_DATA_ROLE = Qt.UserRole + 1

    def selectionChanged(self):
        X = self.X
        mapping = self.onehot_mapping
        instances = set()
        where = np.where

        def whole_subtree(node):
            yield node
            for i in range(node.childCount()):
                yield from whole_subtree(node.child(i))

        def itemset(node):
            while node:
                yield node.data(0, self.ITEM_DATA_ROLE)
                node = node.parent()

        def selection_ranges(node):
            n_children = node.childCount()
            if n_children:
                yield (self.tree.indexFromItem(node.child(0)),
                       self.tree.indexFromItem(node.child(n_children - 1)))
            for i in range(n_children):
                yield from selection_ranges(node.child(i))

        nSelectedItemsets = 0
        item_selection = QItemSelection()
        for node in self.tree.selectedItems():
            nodes = (node, ) if node.isExpanded() else whole_subtree(node)
            if not node.isExpanded():
                for srange in selection_ranges(node):
                    item_selection.select(*srange)
            for node in nodes:
                nSelectedItemsets += 1
                cols, vals = zip(*(mapping[i] for i in itemset(node)))
                if issparse(X):
                    rows = (len(cols) == np.bincount(
                        (X[:, cols] != 0).indices,
                        minlength=X.shape[0])).nonzero()[0]
                else:
                    rows = where((X[:, cols] == vals).all(axis=1))[0]
                instances.update(rows)
        self.tree.itemSelectionChanged.disconnect(self.selectionChanged)
        self.tree.selectionModel().select(
            item_selection,
            QItemSelectionModel.Select | QItemSelectionModel.Rows)
        self.tree.itemSelectionChanged.connect(self.selectionChanged)

        self.nSelectedExamples = len(instances)
        self.nSelectedItemsets = nSelectedItemsets
        self.output = self.data[sorted(instances)] or None
        self.commit()

    def commit(self):
        self.send(Output.DATA, self.output)

    def filter_change(self):
        self.warning(9)
        try:
            isRegexMatch = self.isRegexMatch = re.compile(
                '|'.join(
                    i.strip()
                    for i in re.split('(,|\s)+', self.filterKeywords.strip())
                    if i.strip()), re.IGNORECASE).search
        except Exception as e:
            self.warning(9,
                         'Error in regular expression: {}'.format(e.args[0]))
            isRegexMatch = self.isRegexMatch = lambda x: True

        def hide(node, depth, has_kw):
            if not has_kw:
                has_kw = isRegexMatch(node.text(0))
            hidden = (
                sum(
                    hide(node.child(i), depth + 1, has_kw)
                    for i in range(node.childCount())) == node.childCount()
                if node.childCount() else
                (not has_kw
                 or not self.filterMinItems <= depth <= self.filterMaxItems))
            node.setHidden(hidden)
            return hidden

        hide(self.tree.invisibleRootItem(), 0, False)

    class TreeWidgetItem(QTreeWidgetItem):
        def data(self, column, role):
            """Construct lazy tooltips"""
            if role != Qt.ToolTipRole:
                return super().data(column, role)
            tooltip = []
            while self:
                tooltip.append(self.text(0))
                self = self.parent()
            return ' '.join(reversed(tooltip))

    def find_itemsets(self):
        if self.data is None:
            return
        if self._is_running:
            return
        self._is_running = True

        data = self.data
        self.tree.clear()
        self.tree.setUpdatesEnabled(False)
        self.tree.blockSignals(True)

        class ItemDict(dict):
            def __init__(self, item):
                self.item = item

        top = ItemDict(self.tree.invisibleRootItem())
        X, mapping = OneHot.encode(data)
        self.onehot_mapping = mapping
        ITEM_FMT = '{}' if issparse(data.X) else '{}={}'
        names = {
            item: ITEM_FMT.format(var.name, val)
            for item, var, val in OneHot.decode(mapping.keys(), data, mapping)
        }
        nItemsets = 0

        filterSearch = self.filterSearch
        filterMinItems, filterMaxItems = self.filterMinItems, self.filterMaxItems
        isRegexMatch = self.isRegexMatch

        # Find itemsets and populate the TreeView
        with self.progressBar(self.maxItemsets + 1) as progress:
            for itemset, support in frequent_itemsets(X,
                                                      self.minSupport / 100):

                if filterSearch and not filterMinItems <= len(
                        itemset) <= filterMaxItems:
                    continue

                parent = top
                first_new_item = None
                itemset_matches_filter = False

                for item in sorted(itemset):
                    name = names[item]

                    if filterSearch and not itemset_matches_filter:
                        itemset_matches_filter = isRegexMatch(name)

                    child = parent.get(name)
                    if child is None:
                        try:
                            wi = self.TreeWidgetItem(parent.item, [
                                name,
                                str(support), '{:.4g}'.format(
                                    100 * support / len(data))
                            ])
                        except RuntimeError:
                            # FIXME: When autoFind was in effect and the support
                            # slider was moved, this line excepted with:
                            #     RuntimeError: wrapped C/C++ object of type
                            #                   TreeWidgetItem has been deleted
                            return
                        wi.setData(0, self.ITEM_DATA_ROLE, item)
                        child = parent[name] = ItemDict(wi)

                        if first_new_item is None:
                            first_new_item = (parent, name)
                    parent = child

                if filterSearch and not itemset_matches_filter:
                    parent, name = first_new_item
                    parent.item.removeChild(parent[name].item)
                    del parent[name].item
                    del parent[name]
                else:
                    nItemsets += 1
                    progress.advance()
                if nItemsets >= self.maxItemsets:
                    break

        if not filterSearch:
            self.filter_change()
        self.nItemsets = nItemsets
        self.nSelectedItemsets = 0
        self.nSelectedExamples = 0
        self.tree.expandAll()
        for i in range(self.tree.columnCount()):
            self.tree.resizeColumnToContents(i)
        self.tree.setUpdatesEnabled(True)
        self.tree.blockSignals(False)
        self._is_running = False

    def set_data(self, data):
        self.data = data
        is_error = False
        if data is not None:
            self.warning(0)
            self.error(1)
            self.button.setDisabled(False)
            self.X = data.X
            if issparse(data.X):
                self.X = data.X.tocsc()
            else:
                if not data.domain.has_discrete_attributes():
                    self.error(
                        1, 'Discrete features required but data has none.')
                    is_error = True
                    self.button.setDisabled(True)
                elif data.domain.has_continuous_attributes():
                    self.warning(
                        0,
                        'Data has continuous attributes which will be skipped.'
                    )
        else:
            self.output = None
            self.commit()
        if self.autoFind and not is_error:
            self.find_itemsets()
コード例 #3
0
class XConsoleEdit(XLoggerWidget):
    __designer_icon__ = projexui.resources.find('img/ui/console.png')
    
    def __init__( self, parent ):
        super(XConsoleEdit, self).__init__(parent)
        
        self.setLogger(logging.getLogger())
        
        # create custom properties
        self._completerTree = None
        self._commandStack = []
        self._highlighter = XConsoleHighlighter(self.document())
        
        # set properties
        font = QFont('Courier New')
        font.setPointSize(9)
        self.setFont(font)
        self.setReadOnly(False)
        self.waitForInput()
        metrics = QFontMetrics(font)
        self.setTabStopWidth(4 * metrics.width(' '))
        
        # create connections
        XStream.stdout().messageWritten.connect( self.information )
        XStream.stderr().messageWritten.connect( self.error )
    
    def __del__( self ):
        XStream.stdout().messageWritten.disconnect( self.information )
        XStream.stderr().messageWritten.disconnect( self.error )
    
    def acceptCompletion( self ):
        """
        Accepts the current completion and inserts the code into the edit.
        
        :return     <bool> accepted
        """
        tree = self._completerTree
        if not tree:
            return False
            
        tree.hide()
        
        item = tree.currentItem()
        if not item:
            return False
        
        # clear the previously typed code for the block
        cursor  = self.textCursor()
        text    = cursor.block().text()
        col     = cursor.columnNumber()
        end     = col
        
        while col:
            col -= 1
            if text[col] == '.':
                col += 1
                break
        
        # insert the current text
        cursor.setPosition(cursor.position() - (end-col), cursor.KeepAnchor)
        cursor.removeSelectedText()
        self.insertPlainText(item.text(0))
        return True
    
    def applyCommand( self ):
        """
        Applies the current line of code as an interactive python command.
        """
        # grab the command from the current line
        block   = self.textCursor().block().text()
        match   = INPUT_EXPR.match(projex.text.toUtf8(block))
        
        # if there is no command, then wait for the input
        if not match:
            self.waitForInput()
            return
        
        self.executeCommand(match.group(2).rstrip(), match.group(1).strip())
    
    def cancelCompletion( self ):
        """
        Cancels the current completion.
        """
        if ( self._completerTree ):
            self._completerTree.hide()
    
    def clear( self ):
        """
        Clears the current text and starts a new input line.
        """
        super(XConsoleEdit, self).clear()
        self.waitForInput()
    
    def closeEvent( self, event ):
        """
        Disconnects from the stderr/stdout on close.
        
        :param      event | <QCloseEvent>
        """
        if ( self.clearOnClose() ):
            XStream.stdout().messageWritten.disconnect( self.information )
            XStream.stderr().messageWritten.disconnect( self.error )
        
        super(XConsoleEdit, self).closeEvent(event)
    
    def completerTree( self ):
        """
        Returns the completion tree for this instance.
        
        :return     <QTreeWidget>
        """
        if ( not self._completerTree ):
            self._completerTree = QTreeWidget(self)
            self._completerTree.setWindowFlags(Qt.Popup)
            self._completerTree.setAlternatingRowColors( True )
            self._completerTree.installEventFilter(self)
            self._completerTree.itemClicked.connect( self.acceptCompletion )
            self._completerTree.setRootIsDecorated(False)
            self._completerTree.header().hide()
            
        return self._completerTree
    
    def eventFilter( self, obj, event ):
        """
        Filters particular events for a given QObject through this class. \
        Will use this to intercept events to the completer tree widget while \
        filtering.
        
        :param      obj     | <QObject>
                    event   | <QEvent>
        
        :return     <bool> consumed
        """
        if ( not obj == self._completerTree ):
            return False
        
        if ( event.type() != event.KeyPress ):
            return False
            
        if ( event.key() == Qt.Key_Escape ):
            QToolTip.hideText()
            self.cancelCompletion()
            return False
        
        elif ( event.key() in (Qt.Key_Enter, Qt.Key_Return, Qt.Key_Tab) ):
            self.acceptCompletion()
            return False
        
        elif ( event.key() in (Qt.Key_Up, 
                               Qt.Key_Down) ):
            
            return False
        
        else:
            self.keyPressEvent(event)
            
            # update the completer
            cursor   = self.textCursor()
            text     = projex.text.toUtf8(cursor.block().text())
            text     = text[:cursor.columnNumber()].split(' ')[-1]
            text     = text.split('.')[-1]
            
            self._completerTree.blockSignals(True)
            self._completerTree.setUpdatesEnabled(False)
            
            self._completerTree.setCurrentItem(None)
            
            for i in range(self._completerTree.topLevelItemCount()):
                item = self._completerTree.topLevelItem(i)
                if projex.text.toUtf8(item.text(0)).startswith(text):
                    self._completerTree.setCurrentItem(item)
                    break
            
            self._completerTree.blockSignals(False)
            self._completerTree.setUpdatesEnabled(True)
            
            return True
    
    def executeCommand(self, command, mode='>>>'):
        """
        Executes the inputed command in the global scope.
        
        :param      command | <unicode>
        
        :return     <variant>
        """
        # check to see if we're starting a new line
        if mode == '...' and not command.strip():
            command = '\n'.join(self._commandStack)
        
        elif command.endswith(':') or mode == '...':
            self._commandStack.append(command)
            self.insertPlainText('\n... ')
            return
        
        # if we're not at the end of the console, then add it to the end
        elif not self.textCursor().atEnd():
            self.waitForInput()
            self.insertPlainText(command)
            return
        
        self._commandStack = []
        
        # insert a new line
        self.insertPlainText('\n')
        cmdresult = None
        
        try:
            cmdresult = eval(command, __main__.__dict__, __main__.__dict__)
        except SyntaxError:
            exec(command) in __main__.__dict__, __main__.__dict__
        
        # print the resulting commands
        if cmdresult != None:
            self.information(projex.text.toUtf8(cmdresult))
        
        self.waitForInput()
    
    def highlighter(self):
        """
        Returns the console highlighter for this widget.
        
        :return     <XConsoleHighlighter>
        """
        return self._highlighter
    
    def gotoHome( self ):
        """
        Navigates to the home position for the edit.
        """
        mode = QTextCursor.MoveAnchor
        
        # select the home
        if QApplication.instance().keyboardModifiers() == Qt.ShiftModifier:
            mode = QTextCursor.KeepAnchor
        
        cursor = self.textCursor()
        block  = projex.text.toUtf8(cursor.block().text())
        
        cursor.movePosition( QTextCursor.StartOfBlock, mode )
        if ( block.startswith('>>> ') ):
            cursor.movePosition( QTextCursor.Right, mode, 4 )
            
        self.setTextCursor(cursor)
    
    def keyPressEvent( self, event ):
        """
        Overloads the key press event to control keystroke modifications for \
        the console widget.
        
        :param      event | <QKeyEvent>
        """
        # enter || return keys will apply the command
        if event.key() in (Qt.Key_Return, Qt.Key_Enter):
            self.applyCommand()
        
        # home key will move the cursor to the home position
        elif event.key() == Qt.Key_Home:
            self.gotoHome()
        
        elif event.key() in (Qt.Key_Backspace, Qt.Key_Delete):
            super(XConsoleEdit, self).keyPressEvent(event)
            
            # update the completer
            cursor   = self.textCursor()
            text     = projex.text.toUtf8(cursor.block().text())
            text     = text[:cursor.columnNumber()].split(' ')[-1]
            
            if not '.' in text:
                self.cancelCompletion()
            
        # period key will trigger a completion popup
        elif event.key() == Qt.Key_Period:
            self.startCompletion()
            super(XConsoleEdit, self).keyPressEvent(event)
        
        # space, tab, backspace and delete will cancel the completion
        elif event.key() == Qt.Key_Space:
            self.cancelCompletion()
            super(XConsoleEdit, self).keyPressEvent(event)
        
        # left parenthesis will start method help
        elif event.key() == Qt.Key_ParenLeft:
            self.cancelCompletion()
            self.showMethodToolTip()
            super(XConsoleEdit, self).keyPressEvent(event)
        
        # otherwise, handle the event like normal
        else:
            super(XConsoleEdit, self).keyPressEvent(event)
    
    def objectAtCursor( self ):
        """
        Returns the python object that the text is representing.
        
        :return     <object> || None
        """
        
        # determine the text block
        cursor = self.textCursor()
        text    = projex.text.toUtf8(cursor.block().text())
        col     = cursor.columnNumber()
        end     = col
        
        while ( col ):
            col -= 1
            if ( re.match('[^a-zA-Z\._\(\)]', text[col]) ):
                break
        
        symbol = text[col:end].rstrip('.(')
        try:
            return eval(symbol, __main__.__dict__, __main__.__dict__)
        except:
            return None
    
    def showMethodToolTip( self ):
        """
        Pops up a tooltip message with the help for the object under the \
        cursor.
        
        :return     <bool> success
        """
        self.cancelCompletion()
        
        obj = self.objectAtCursor()
        if ( not obj ):
            return False
        
        docs = inspect.getdoc(obj)
        if ( not docs ):
            return False
        
        # determine the cursor position
        rect   = self.cursorRect()
        cursor = self.textCursor()
        point  = QPoint(rect.left(), rect.top() + 18)
        
        QToolTip.showText( self.mapToGlobal(point), docs, self )
        
        return True
    
    def startCompletion( self ):
        """
        Starts a new completion popup for the current object.
        
        :return     <bool> success
        """
        # add the top level items
        tree = self.completerTree()
        tree.clear()
        
        # make sure we have a valid object
        obj = self.objectAtCursor()
        if ( obj is None ):
            return False
            
        # determine the cursor position
        rect   = self.cursorRect()
        cursor = self.textCursor()
        point  = QPoint(rect.left(), rect.top() + 18)
        
        try:
            o_keys = obj.__dir__()
        except (AttributeError, TypeError):
            o_keys = dir(obj)
        
        keys = [key for key in sorted(o_keys) if not key.startswith('_')]
        if ( not keys ):
            return False
        
        for key in keys:
            tree.addTopLevelItem(QTreeWidgetItem([key]))
        
        tree.move(self.mapToGlobal(point))
        tree.show()
        
        return True
    
    def waitForInput( self ):
        """
        Inserts a new input command into the console editor.
        """
        if ( self.isReadOnly() ):
            return
            
        self.moveCursor( QTextCursor.End )
        
        if ( self.textCursor().block().text() == '>>> ' ):
            return
        
        # if there is already text on the line, then start a new line
        newln = '>>> '
        if projex.text.toUtf8(self.textCursor().block().text()):
            newln = '\n' + newln
        
        # insert the text
        self.setCurrentMode('standard')
        self.insertPlainText(newln)
        self.scrollToEnd()
        
        self._blankCache = ''
コード例 #4
0
class VersionSelectDialog( QDialog ):
    def __init__( self, parent ):
        # initialize the super class
        super( VersionSelectDialog, self ).__init__( parent )
        
        # create the tree
        self.uiVersionsTREE = QTreeWidget(self)
        self.uiVersionsTREE.setAlternatingRowColors(True)
        self.uiVersionsTREE.setRootIsDecorated(False)
        self.uiVersionsTREE.setSelectionMode( self.uiVersionsTREE.NoSelection )
        
        header = self.uiVersionsTREE.header()
        header.setVisible(False)
        
        # create the layout
        layout = QVBoxLayout()
        layout.addWidget(self.uiVersionsTREE)
        layout.setContentsMargins(0,0,0,0)
        
        # inherit the highlight palette
        palette = self.palette()
        palette.setColor(palette.Highlight,parent.palette().color(palette.Highlight))
        self.setPalette(palette)
        
        # set dialog information
        self.setLayout(layout)
        self.setWindowFlags( Qt.Popup )
        self.resize(500,250)
        
        # create connections
        self.uiVersionsTREE.itemClicked.connect( self.acceptItem )
    
    def closeEvent( self, event ):
        # update all the items for this
        for i in range( self.uiVersionsTREE.topLevelItemCount() ):
            item    = self.uiVersionsTREE.topLevelItem(i)
            widget  = item.versionWidget()
            version = widget.version()
            
            # match the active state
            version.setActive(widget.isActive())
        
        super(VersionSelectDialog,self).closeEvent(event)
    
    def acceptItem( self, item ):
        # handle version change information
        widget = item.versionWidget()
        widget.toggleActive()
        
        # accept the dialog
        self.close()
    
    def popup( self, versions ):
        self.uiVersionsTREE.setUpdatesEnabled(False)
        self.uiVersionsTREE.blockSignals(True)
        
        self.uiVersionsTREE.clear()
        for version in versions:
            item = VersionItem(self.uiVersionsTREE, version)
            self.uiVersionsTREE.addTopLevelItem( item )
            self.uiVersionsTREE.setItemWidget( item, 0, item.versionWidget() )
        
        # reset the scrolling
        self.uiVersionsTREE.verticalScrollBar().setValue(0)
        
        self.uiVersionsTREE.setUpdatesEnabled(True)
        self.uiVersionsTREE.blockSignals(False)
        
        return self.exec_()
コード例 #5
0
class OWItemsets(widget.OWWidget):
    name = "Frequent Itemsets"
    description = "Explore sets of items that frequently appear together."
    icon = "icons/FrequentItemsets.svg"
    priority = 10

    inputs = [("Data", Table, "set_data")]
    outputs = [(Output.DATA, Table)]

    minSupport = settings.Setting(30)
    maxItemsets = settings.Setting(10000)
    filterSearch = settings.Setting(True)
    autoFind = settings.Setting(False)
    autoSend = settings.Setting(True)
    filterKeywords = settings.Setting("")
    filterMinItems = settings.Setting(1)
    filterMaxItems = settings.Setting(10000)

    UserAdviceMessages = [
        widget.Message(
            "Itemset are listed in item-sorted order, i.e. "
            "an itemset containing A and B is only listed once, as "
            "A > B (and not also B > A).",
            "itemsets-order",
            widget.Message.Warning,
        ),
        widget.Message(
            "To select all the itemsets that are descendants of "
            "(include) some item X (i.e. the whole subtree), you "
            "can fold the subtree at that item and then select it.",
            "itemsets-order",
            widget.Message.Information,
        ),
    ]

    def __init__(self):
        self._is_running = False
        self.isRegexMatch = lambda x: True
        self.tree = QTreeWidget(
            self.mainArea,
            columnCount=2,
            allColumnsShowFocus=True,
            alternatingRowColors=True,
            selectionMode=QTreeWidget.ExtendedSelection,
            uniformRowHeights=True,
        )
        self.tree.setHeaderLabels(["Itemsets", "Support", "%"])
        self.tree.header().setStretchLastSection(True)
        self.tree.itemSelectionChanged.connect(self.selectionChanged)
        self.mainArea.layout().addWidget(self.tree)

        box = gui.widgetBox(self.controlArea, "Info")
        self.nItemsets = self.nSelectedExamples = self.nSelectedItemsets = ""
        gui.label(box, self, "Number of itemsets: %(nItemsets)s")
        gui.label(box, self, "Selected itemsets: %(nSelectedItemsets)s")
        gui.label(box, self, "Selected examples: %(nSelectedExamples)s")
        hbox = gui.widgetBox(box, orientation="horizontal")
        gui.button(hbox, self, "Expand all", callback=self.tree.expandAll)
        gui.button(hbox, self, "Collapse all", callback=self.tree.collapseAll)

        box = gui.widgetBox(self.controlArea, "Find itemsets")
        gui.valueSlider(
            box,
            self,
            "minSupport",
            values=[0.0001, 0.0005, 0.001, 0.005, 0.01, 0.05, 0.1, 0.5] + list(range(1, 101)),
            label="Minimal support:",
            labelFormat="%g%%",
            callback=lambda: self.find_itemsets(),
        )
        gui.hSlider(
            box,
            self,
            "maxItemsets",
            minValue=10000,
            maxValue=100000,
            step=10000,
            label="Max. number of itemsets:",
            labelFormat="%d",
            callback=lambda: self.find_itemsets(),
        )
        self.button = gui.auto_commit(box, self, "autoFind", "Find itemsets", commit=self.find_itemsets)

        box = gui.widgetBox(self.controlArea, "Filter itemsets")
        gui.lineEdit(
            box,
            self,
            "filterKeywords",
            "Contains:",
            callback=self.filter_change,
            orientation="horizontal",
            tooltip="A comma or space-separated list of regular " "expressions.",
        )
        hbox = gui.widgetBox(box, orientation="horizontal")
        gui.spin(hbox, self, "filterMinItems", 1, 998, label="Min. items:", callback=self.filter_change)
        gui.spin(hbox, self, "filterMaxItems", 2, 999, label="Max. items:", callback=self.filter_change)
        gui.checkBox(
            box,
            self,
            "filterSearch",
            label="Apply these filters in search",
            tooltip="If checked, the itemsets are filtered according "
            "to these filter conditions already in the search "
            "phase. \nIf unchecked, the only filters applied "
            "during search are the ones above, "
            "and the itemsets are \nfiltered afterwards only for "
            "display, i.e. only the matching itemsets are shown.",
        )

        gui.rubber(hbox)

        gui.rubber(self.controlArea)
        gui.auto_commit(self.controlArea, self, "autoSend", "Send selection")

        self.filter_change()

    ITEM_DATA_ROLE = Qt.UserRole + 1

    def selectionChanged(self):
        X = self.X
        mapping = self.onehot_mapping
        instances = set()
        where = np.where

        def whole_subtree(node):
            yield node
            for i in range(node.childCount()):
                yield from whole_subtree(node.child(i))

        def itemset(node):
            while node:
                yield node.data(0, self.ITEM_DATA_ROLE)
                node = node.parent()

        def selection_ranges(node):
            n_children = node.childCount()
            if n_children:
                yield (self.tree.indexFromItem(node.child(0)), self.tree.indexFromItem(node.child(n_children - 1)))
            for i in range(n_children):
                yield from selection_ranges(node.child(i))

        nSelectedItemsets = 0
        item_selection = QItemSelection()
        for node in self.tree.selectedItems():
            nodes = (node,) if node.isExpanded() else whole_subtree(node)
            if not node.isExpanded():
                for srange in selection_ranges(node):
                    item_selection.select(*srange)
            for node in nodes:
                nSelectedItemsets += 1
                cols, vals = zip(*(mapping[i] for i in itemset(node)))
                if issparse(X):
                    rows = (len(cols) == np.bincount((X[:, cols] != 0).indices, minlength=X.shape[0])).nonzero()[0]
                else:
                    rows = where((X[:, cols] == vals).all(axis=1))[0]
                instances.update(rows)
        self.tree.itemSelectionChanged.disconnect(self.selectionChanged)
        self.tree.selectionModel().select(item_selection, QItemSelectionModel.Select | QItemSelectionModel.Rows)
        self.tree.itemSelectionChanged.connect(self.selectionChanged)

        self.nSelectedExamples = len(instances)
        self.nSelectedItemsets = nSelectedItemsets
        self.output = self.data[sorted(instances)] or None
        self.commit()

    def commit(self):
        self.send(Output.DATA, self.output)

    def filter_change(self):
        self.warning(9)
        try:
            isRegexMatch = self.isRegexMatch = re.compile(
                "|".join(i.strip() for i in re.split("(,|\s)+", self.filterKeywords.strip()) if i.strip()),
                re.IGNORECASE,
            ).search
        except Exception as e:
            self.warning(9, "Error in regular expression: {}".format(e.args[0]))
            isRegexMatch = self.isRegexMatch = lambda x: True

        def hide(node, depth, has_kw):
            if not has_kw:
                has_kw = isRegexMatch(node.text(0))
            hidden = (
                sum(hide(node.child(i), depth + 1, has_kw) for i in range(node.childCount())) == node.childCount()
                if node.childCount()
                else (not has_kw or not self.filterMinItems <= depth <= self.filterMaxItems)
            )
            node.setHidden(hidden)
            return hidden

        hide(self.tree.invisibleRootItem(), 0, False)

    class TreeWidgetItem(QTreeWidgetItem):
        def data(self, column, role):
            """Construct lazy tooltips"""
            if role != Qt.ToolTipRole:
                return super().data(column, role)
            tooltip = []
            while self:
                tooltip.append(self.text(0))
                self = self.parent()
            return " ".join(reversed(tooltip))

    def find_itemsets(self):
        if self.data is None:
            return
        if self._is_running:
            return
        self._is_running = True

        data = self.data
        self.tree.clear()
        self.tree.setUpdatesEnabled(False)
        self.tree.blockSignals(True)

        class ItemDict(dict):
            def __init__(self, item):
                self.item = item

        top = ItemDict(self.tree.invisibleRootItem())
        X, mapping = OneHot.encode(data)
        self.onehot_mapping = mapping
        ITEM_FMT = "{}" if issparse(data.X) else "{}={}"
        names = {
            item: ITEM_FMT.format(var.name, val) for item, var, val in OneHot.decode(mapping.keys(), data, mapping)
        }
        nItemsets = 0

        filterSearch = self.filterSearch
        filterMinItems, filterMaxItems = self.filterMinItems, self.filterMaxItems
        isRegexMatch = self.isRegexMatch

        # Find itemsets and populate the TreeView
        with self.progressBar(self.maxItemsets + 1) as progress:
            for itemset, support in frequent_itemsets(X, self.minSupport / 100):

                if filterSearch and not filterMinItems <= len(itemset) <= filterMaxItems:
                    continue

                parent = top
                first_new_item = None
                itemset_matches_filter = False

                for item in sorted(itemset):
                    name = names[item]

                    if filterSearch and not itemset_matches_filter:
                        itemset_matches_filter = isRegexMatch(name)

                    child = parent.get(name)
                    if child is None:
                        try:
                            wi = self.TreeWidgetItem(
                                parent.item, [name, str(support), "{:.4g}".format(100 * support / len(data))]
                            )
                        except RuntimeError:
                            # FIXME: When autoFind was in effect and the support
                            # slider was moved, this line excepted with:
                            #     RuntimeError: wrapped C/C++ object of type
                            #                   TreeWidgetItem has been deleted
                            return
                        wi.setData(0, self.ITEM_DATA_ROLE, item)
                        child = parent[name] = ItemDict(wi)

                        if first_new_item is None:
                            first_new_item = (parent, name)
                    parent = child

                if filterSearch and not itemset_matches_filter:
                    parent, name = first_new_item
                    parent.item.removeChild(parent[name].item)
                    del parent[name].item
                    del parent[name]
                else:
                    nItemsets += 1
                    progress.advance()
                if nItemsets >= self.maxItemsets:
                    break

        if not filterSearch:
            self.filter_change()
        self.nItemsets = nItemsets
        self.nSelectedItemsets = 0
        self.nSelectedExamples = 0
        self.tree.expandAll()
        for i in range(self.tree.columnCount()):
            self.tree.resizeColumnToContents(i)
        self.tree.setUpdatesEnabled(True)
        self.tree.blockSignals(False)
        self._is_running = False

    def set_data(self, data):
        self.data = data
        is_error = False
        if data is not None:
            self.warning(0)
            self.error(1)
            self.button.setDisabled(False)
            self.X = data.X
            if issparse(data.X):
                self.X = data.X.tocsc()
            else:
                if not data.domain.has_discrete_attributes():
                    self.error(1, "Discrete features required but data has none.")
                    is_error = True
                    self.button.setDisabled(True)
                elif data.domain.has_continuous_attributes():
                    self.warning(0, "Data has continuous attributes which will be skipped.")
        else:
            self.output = None
            self.commit()
        if self.autoFind and not is_error:
            self.find_itemsets()
コード例 #6
0
class VersionSelectDialog(QDialog):
    def __init__(self, parent):
        # initialize the super class
        super(VersionSelectDialog, self).__init__(parent)

        # create the tree
        self.uiVersionsTREE = QTreeWidget(self)
        self.uiVersionsTREE.setAlternatingRowColors(True)
        self.uiVersionsTREE.setRootIsDecorated(False)
        self.uiVersionsTREE.setSelectionMode(self.uiVersionsTREE.NoSelection)

        header = self.uiVersionsTREE.header()
        header.setVisible(False)

        # create the layout
        layout = QVBoxLayout()
        layout.addWidget(self.uiVersionsTREE)
        layout.setContentsMargins(0, 0, 0, 0)

        # inherit the highlight palette
        palette = self.palette()
        palette.setColor(palette.Highlight,
                         parent.palette().color(palette.Highlight))
        self.setPalette(palette)

        # set dialog information
        self.setLayout(layout)
        self.setWindowFlags(Qt.Popup)
        self.resize(500, 250)

        # create connections
        self.uiVersionsTREE.itemClicked.connect(self.acceptItem)

    def closeEvent(self, event):
        # update all the items for this
        for i in range(self.uiVersionsTREE.topLevelItemCount()):
            item = self.uiVersionsTREE.topLevelItem(i)
            widget = item.versionWidget()
            version = widget.version()

            # match the active state
            version.setActive(widget.isActive())

        super(VersionSelectDialog, self).closeEvent(event)

    def acceptItem(self, item):
        # handle version change information
        widget = item.versionWidget()
        widget.toggleActive()

        # accept the dialog
        self.close()

    def popup(self, versions):
        self.uiVersionsTREE.setUpdatesEnabled(False)
        self.uiVersionsTREE.blockSignals(True)

        self.uiVersionsTREE.clear()
        for version in versions:
            item = VersionItem(self.uiVersionsTREE, version)
            self.uiVersionsTREE.addTopLevelItem(item)
            self.uiVersionsTREE.setItemWidget(item, 0, item.versionWidget())

        # reset the scrolling
        self.uiVersionsTREE.verticalScrollBar().setValue(0)

        self.uiVersionsTREE.setUpdatesEnabled(True)
        self.uiVersionsTREE.blockSignals(False)

        return self.exec_()
コード例 #7
0
class XConsoleEdit(XLoggerWidget):
    __designer_icon__ = projexui.resources.find('img/ui/console.png')

    def __init__(self, parent):
        super(XConsoleEdit, self).__init__(parent)

        self.setLogger(logging.getLogger())

        # create custom properties
        self._completerTree = None
        self._commandStack = []
        self._highlighter = XConsoleHighlighter(self.document())

        # set properties
        font = QFont('Courier New')
        font.setPointSize(9)
        self.setFont(font)
        self.setReadOnly(False)
        self.waitForInput()
        metrics = QFontMetrics(font)
        self.setTabStopWidth(4 * metrics.width(' '))

        # create connections
        XStream.stdout().messageWritten.connect(self.information)
        XStream.stderr().messageWritten.connect(self.error)

    def __del__(self):
        XStream.stdout().messageWritten.disconnect(self.information)
        XStream.stderr().messageWritten.disconnect(self.error)

    def acceptCompletion(self):
        """
        Accepts the current completion and inserts the code into the edit.
        
        :return     <bool> accepted
        """
        tree = self._completerTree
        if not tree:
            return False

        tree.hide()

        item = tree.currentItem()
        if not item:
            return False

        # clear the previously typed code for the block
        cursor = self.textCursor()
        text = cursor.block().text()
        col = cursor.columnNumber()
        end = col

        while col:
            col -= 1
            if text[col] == '.':
                col += 1
                break

        # insert the current text
        cursor.setPosition(cursor.position() - (end - col), cursor.KeepAnchor)
        cursor.removeSelectedText()
        self.insertPlainText(item.text(0))
        return True

    def applyCommand(self):
        """
        Applies the current line of code as an interactive python command.
        """
        # grab the command from the current line
        block = self.textCursor().block().text()
        match = INPUT_EXPR.match(projex.text.toUtf8(block))

        # if there is no command, then wait for the input
        if not match:
            self.waitForInput()
            return

        self.executeCommand(match.group(2).rstrip(), match.group(1).strip())

    def cancelCompletion(self):
        """
        Cancels the current completion.
        """
        if (self._completerTree):
            self._completerTree.hide()

    def clear(self):
        """
        Clears the current text and starts a new input line.
        """
        super(XConsoleEdit, self).clear()
        self.waitForInput()

    def closeEvent(self, event):
        """
        Disconnects from the stderr/stdout on close.
        
        :param      event | <QCloseEvent>
        """
        if (self.clearOnClose()):
            XStream.stdout().messageWritten.disconnect(self.information)
            XStream.stderr().messageWritten.disconnect(self.error)

        super(XConsoleEdit, self).closeEvent(event)

    def completerTree(self):
        """
        Returns the completion tree for this instance.
        
        :return     <QTreeWidget>
        """
        if (not self._completerTree):
            self._completerTree = QTreeWidget(self)
            self._completerTree.setWindowFlags(Qt.Popup)
            self._completerTree.setAlternatingRowColors(True)
            self._completerTree.installEventFilter(self)
            self._completerTree.itemClicked.connect(self.acceptCompletion)
            self._completerTree.setRootIsDecorated(False)
            self._completerTree.header().hide()

        return self._completerTree

    def eventFilter(self, obj, event):
        """
        Filters particular events for a given QObject through this class. \
        Will use this to intercept events to the completer tree widget while \
        filtering.
        
        :param      obj     | <QObject>
                    event   | <QEvent>
        
        :return     <bool> consumed
        """
        if (not obj == self._completerTree):
            return False

        if (event.type() != event.KeyPress):
            return False

        if (event.key() == Qt.Key_Escape):
            QToolTip.hideText()
            self.cancelCompletion()
            return False

        elif (event.key() in (Qt.Key_Enter, Qt.Key_Return, Qt.Key_Tab)):
            self.acceptCompletion()
            return False

        elif (event.key() in (Qt.Key_Up, Qt.Key_Down)):

            return False

        else:
            self.keyPressEvent(event)

            # update the completer
            cursor = self.textCursor()
            text = projex.text.toUtf8(cursor.block().text())
            text = text[:cursor.columnNumber()].split(' ')[-1]
            text = text.split('.')[-1]

            self._completerTree.blockSignals(True)
            self._completerTree.setUpdatesEnabled(False)

            self._completerTree.setCurrentItem(None)

            for i in range(self._completerTree.topLevelItemCount()):
                item = self._completerTree.topLevelItem(i)
                if projex.text.toUtf8(item.text(0)).startswith(text):
                    self._completerTree.setCurrentItem(item)
                    break

            self._completerTree.blockSignals(False)
            self._completerTree.setUpdatesEnabled(True)

            return True

    def executeCommand(self, command, mode='>>>'):
        """
        Executes the inputed command in the global scope.
        
        :param      command | <unicode>
        
        :return     <variant>
        """
        # check to see if we're starting a new line
        if mode == '...' and not command.strip():
            command = '\n'.join(self._commandStack)

        elif command.endswith(':') or mode == '...':
            self._commandStack.append(command)
            self.insertPlainText('\n... ')
            return

        # if we're not at the end of the console, then add it to the end
        elif not self.textCursor().atEnd():
            self.waitForInput()
            self.insertPlainText(command)
            return

        self._commandStack = []

        # insert a new line
        self.insertPlainText('\n')
        cmdresult = None

        try:
            cmdresult = eval(command, __main__.__dict__, __main__.__dict__)
        except SyntaxError:
            exec(command) in __main__.__dict__, __main__.__dict__

        # print the resulting commands
        if cmdresult != None:
            self.information(projex.text.toUtf8(cmdresult))

        self.waitForInput()

    def highlighter(self):
        """
        Returns the console highlighter for this widget.
        
        :return     <XConsoleHighlighter>
        """
        return self._highlighter

    def gotoHome(self):
        """
        Navigates to the home position for the edit.
        """
        mode = QTextCursor.MoveAnchor

        # select the home
        if QApplication.instance().keyboardModifiers() == Qt.ShiftModifier:
            mode = QTextCursor.KeepAnchor

        cursor = self.textCursor()
        block = projex.text.toUtf8(cursor.block().text())

        cursor.movePosition(QTextCursor.StartOfBlock, mode)
        if (block.startswith('>>> ')):
            cursor.movePosition(QTextCursor.Right, mode, 4)

        self.setTextCursor(cursor)

    def keyPressEvent(self, event):
        """
        Overloads the key press event to control keystroke modifications for \
        the console widget.
        
        :param      event | <QKeyEvent>
        """
        # enter || return keys will apply the command
        if event.key() in (Qt.Key_Return, Qt.Key_Enter):
            self.applyCommand()

        # home key will move the cursor to the home position
        elif event.key() == Qt.Key_Home:
            self.gotoHome()

        elif event.key() in (Qt.Key_Backspace, Qt.Key_Delete):
            super(XConsoleEdit, self).keyPressEvent(event)

            # update the completer
            cursor = self.textCursor()
            text = projex.text.toUtf8(cursor.block().text())
            text = text[:cursor.columnNumber()].split(' ')[-1]

            if not '.' in text:
                self.cancelCompletion()

        # period key will trigger a completion popup
        elif event.key() == Qt.Key_Period:
            self.startCompletion()
            super(XConsoleEdit, self).keyPressEvent(event)

        # space, tab, backspace and delete will cancel the completion
        elif event.key() == Qt.Key_Space:
            self.cancelCompletion()
            super(XConsoleEdit, self).keyPressEvent(event)

        # left parenthesis will start method help
        elif event.key() == Qt.Key_ParenLeft:
            self.cancelCompletion()
            self.showMethodToolTip()
            super(XConsoleEdit, self).keyPressEvent(event)

        # otherwise, handle the event like normal
        else:
            super(XConsoleEdit, self).keyPressEvent(event)

    def objectAtCursor(self):
        """
        Returns the python object that the text is representing.
        
        :return     <object> || None
        """

        # determine the text block
        cursor = self.textCursor()
        text = projex.text.toUtf8(cursor.block().text())
        col = cursor.columnNumber()
        end = col

        while (col):
            col -= 1
            if (re.match('[^a-zA-Z\._\(\)]', text[col])):
                break

        symbol = text[col:end].rstrip('.(')
        try:
            return eval(symbol, __main__.__dict__, __main__.__dict__)
        except:
            return None

    def showMethodToolTip(self):
        """
        Pops up a tooltip message with the help for the object under the \
        cursor.
        
        :return     <bool> success
        """
        self.cancelCompletion()

        obj = self.objectAtCursor()
        if (not obj):
            return False

        docs = inspect.getdoc(obj)
        if (not docs):
            return False

        # determine the cursor position
        rect = self.cursorRect()
        cursor = self.textCursor()
        point = QPoint(rect.left(), rect.top() + 18)

        QToolTip.showText(self.mapToGlobal(point), docs, self)

        return True

    def startCompletion(self):
        """
        Starts a new completion popup for the current object.
        
        :return     <bool> success
        """
        # add the top level items
        tree = self.completerTree()
        tree.clear()

        # make sure we have a valid object
        obj = self.objectAtCursor()
        if (obj is None):
            return False

        # determine the cursor position
        rect = self.cursorRect()
        cursor = self.textCursor()
        point = QPoint(rect.left(), rect.top() + 18)

        try:
            o_keys = obj.__dir__()
        except (AttributeError, TypeError):
            o_keys = dir(obj)

        keys = [key for key in sorted(o_keys) if not key.startswith('_')]
        if (not keys):
            return False

        for key in keys:
            tree.addTopLevelItem(QTreeWidgetItem([key]))

        tree.move(self.mapToGlobal(point))
        tree.show()

        return True

    def waitForInput(self):
        """
        Inserts a new input command into the console editor.
        """
        if (self.isReadOnly()):
            return

        self.moveCursor(QTextCursor.End)

        if (self.textCursor().block().text() == '>>> '):
            return

        # if there is already text on the line, then start a new line
        newln = '>>> '
        if projex.text.toUtf8(self.textCursor().block().text()):
            newln = '\n' + newln

        # insert the text
        self.setCurrentMode('standard')
        self.insertPlainText(newln)
        self.scrollToEnd()

        self._blankCache = ''