Пример #1
0
    def __init__(self, parent=None):
        super(XQueryBuilderWidget, self).__init__(parent)

        # load the user interface
        projexui.loadUi(__file__, self)

        self.setMinimumWidth(470)

        # define custom properties
        self._rules = {}
        self._defaultQuery = []
        self._completionTerms = []
        self._minimumCount = 1

        # set default properties
        self._container = QWidget(self)
        layout = QVBoxLayout()
        layout.setContentsMargins(0, 0, 0, 0)
        layout.setSpacing(2)
        layout.addStretch(1)
        self._container.setLayout(layout)
        self.uiQueryAREA.setWidget(self._container)

        # create connections
        self.uiResetBTN.clicked.connect(self.emitResetRequested)
        self.uiSaveBTN.clicked.connect(self.emitSaveRequested)
        self.uiCancelBTN.clicked.connect(self.emitCancelRequested)

        self.resetRequested.connect(self.reset)
Пример #2
0
 def __init__(self, parent=None):
     super(XCommentEdit, self).__init__(parent)
     
     # define custom properties
     self._attachments = {}
     self._showAttachments = True
     
     # create toolbar
     self._toolbar = QToolBar(self)
     self._toolbar.setMovable(False)
     self._toolbar.setFixedHeight(30)
     self._toolbar.setAutoFillBackground(True)
     self._toolbar.setFocusProxy(self)
     self._toolbar.hide()
     
     # create toolbar buttons
     self._attachButton = QToolButton(self)
     self._attachButton.setIcon(QIcon(resources.find('img/attach.png')))
     self._attachButton.setToolTip('Add Attachment')
     self._attachButton.setAutoRaise(True)
     self._attachButton.setIconSize(QSize(24, 24))
     self._attachButton.setFixedSize(26, 26)
     
     self._submitButton = QPushButton(self)
     self._submitButton.setText('Submit')
     self._submitButton.setFocusProxy(self)
     
     # create attachments widget
     self._attachmentsEdit = XMultiTagEdit(self)
     self._attachmentsEdit.setAutoResizeToContents(True)
     self._attachmentsEdit.setFrameShape(XMultiTagEdit.NoFrame)
     self._attachmentsEdit.setViewMode(XMultiTagEdit.ListMode)
     self._attachmentsEdit.setEditable(False)
     self._attachmentsEdit.setFocusProxy(self)
     self._attachmentsEdit.hide()
     
     # define toolbar layout
     spacer = QWidget(self)
     spacer.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Preferred)
     
     self._attachAction = self._toolbar.addWidget(self._attachButton)
     self._toolbar.addWidget(spacer)
     self._toolbar.addWidget(self._submitButton)
     
     # set standard properties
     self.setAutoResizeToContents(True)
     self.setHint('add comment')
     self.setFocusPolicy(Qt.StrongFocus)
     self.setRequireShiftForNewLine(True)
     
     # create connections
     self._attachButton.clicked.connect(self.attachmentRequested)
     self._submitButton.clicked.connect(self.acceptText)
     self._attachmentsEdit.tagRemoved.connect(self.removeAttachment)
     self.focusChanged.connect(self.setToolbarVisible)
Пример #3
0
    def __init__(self, parent=None):
        super(XCommentEdit, self).__init__(parent)

        # define custom properties
        self._attachments = {}
        self._showAttachments = True

        # create toolbar
        self._toolbar = QToolBar(self)
        self._toolbar.setMovable(False)
        self._toolbar.setFixedHeight(30)
        self._toolbar.setAutoFillBackground(True)
        self._toolbar.setFocusProxy(self)
        self._toolbar.hide()

        # create toolbar buttons
        self._attachButton = QToolButton(self)
        self._attachButton.setIcon(QIcon(resources.find('img/attach.png')))
        self._attachButton.setToolTip('Add Attachment')
        self._attachButton.setAutoRaise(True)
        self._attachButton.setIconSize(QSize(24, 24))
        self._attachButton.setFixedSize(26, 26)

        self._submitButton = QPushButton(self)
        self._submitButton.setText('Submit')
        self._submitButton.setFocusProxy(self)

        # create attachments widget
        self._attachmentsEdit = XMultiTagEdit(self)
        self._attachmentsEdit.setAutoResizeToContents(True)
        self._attachmentsEdit.setFrameShape(XMultiTagEdit.NoFrame)
        self._attachmentsEdit.setViewMode(XMultiTagEdit.ListMode)
        self._attachmentsEdit.setEditable(False)
        self._attachmentsEdit.setFocusProxy(self)
        self._attachmentsEdit.hide()

        # define toolbar layout
        spacer = QWidget(self)
        spacer.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Preferred)

        self._attachAction = self._toolbar.addWidget(self._attachButton)
        self._toolbar.addWidget(spacer)
        self._toolbar.addWidget(self._submitButton)

        # set standard properties
        self.setAutoResizeToContents(True)
        self.setHint('add comment')
        self.setFocusPolicy(Qt.StrongFocus)
        self.setRequireShiftForNewLine(True)

        # create connections
        self._attachButton.clicked.connect(self.attachmentRequested)
        self._submitButton.clicked.connect(self.acceptText)
        self._attachmentsEdit.tagRemoved.connect(self.removeAttachment)
        self.focusChanged.connect(self.setToolbarVisible)
Пример #4
0
 def __init__( self, parent = None ):
     super(XRolloutWidget, self).__init__( parent )
     
     # define custom properties
     self.setWidgetResizable(True)
     
     # set default properties
     widget = QWidget(self)
     layout = QVBoxLayout()
     layout.setContentsMargins(0, 0, 0, 0)
     layout.setSpacing(3)
     layout.addStretch(1)
     widget.setLayout(layout)
     
     self.setWidget(widget)
Пример #5
0
    def __init__(self, parent=None):
        super(XRolloutWidget, self).__init__(parent)

        # define custom properties
        self.setWidgetResizable(True)

        # set default properties
        widget = QWidget(self)
        layout = QVBoxLayout()
        layout.setContentsMargins(0, 0, 0, 0)
        layout.setSpacing(3)
        layout.addStretch(1)
        widget.setLayout(layout)

        self.setWidget(widget)
Пример #6
0
 def __init__( self, parent = None ):
     super(XQueryBuilderWidget, self).__init__( parent )
     
     # load the user interface
     projexui.loadUi(__file__, self)
     
     self.setMinimumWidth(470)
     
     # define custom properties
     self._rules           = {}
     self._defaultQuery    = []
     self._completionTerms = []
     self._minimumCount    = 1
     
     # set default properties
     self._container = QWidget(self)
     layout = QVBoxLayout()
     layout.setContentsMargins(0, 0, 0, 0)
     layout.setSpacing(2)
     layout.addStretch(1)
     self._container.setLayout(layout)
     self.uiQueryAREA.setWidget(self._container)
     
     # create connections
     self.uiResetBTN.clicked.connect(  self.emitResetRequested )
     self.uiSaveBTN.clicked.connect(   self.emitSaveRequested )
     self.uiCancelBTN.clicked.connect( self.emitCancelRequested )
     
     self.resetRequested.connect(      self.reset )
Пример #7
0
    def __init__(self, parent=None):
        super(XOrbQueryContainer, self).__init__(parent)

        # load the user interface
        projexui.loadUi(__file__, self)

        # define custom properties
        self._queryWidget = parent
        self._entryWidget = QWidget(self)
        self._currentJoiner = QueryCompound.Op.And

        layout = QVBoxLayout()
        layout.addStretch(1)
        layout.setSpacing(3)
        self._entryWidget.setLayout(layout)
        self.uiQueryAREA.setWidget(self._entryWidget)

        # set default properties
        self.setContextMenuPolicy(Qt.CustomContextMenu)

        # create connections (use old-style syntax or PySide errors)
        self.connect(self.uiBackBTN, SIGNAL('clicked()'), self.exitCompound)
        self.entriesUpdated.connect(self.refreshEntries)
Пример #8
0
 def __init__( self, parent = None ):
     super(XOrbQueryContainer, self).__init__( parent )
     
     # load the user interface
     projexui.loadUi(__file__, self)
     
     # define custom properties
     self._queryWidget   = parent
     self._entryWidget   = QWidget(self)
     self._currentJoiner = QueryCompound.Op.And
     
     layout = QVBoxLayout()
     layout.addStretch(1)
     layout.setSpacing(3)
     self._entryWidget.setLayout(layout)
     self.uiQueryAREA.setWidget(self._entryWidget)
     
     # set default properties
     self.setContextMenuPolicy(Qt.CustomContextMenu)
     
     # create connections (use old-style syntax or PySide errors)
     self.connect(self.uiBackBTN, SIGNAL('clicked()'), self.exitCompound)
     self.entriesUpdated.connect(self.refreshEntries)
Пример #9
0
class XQueryBuilderWidget(QWidget):
    """ """
    saveRequested = Signal()
    resetRequested = Signal()
    cancelRequested = Signal()

    def __init__(self, parent=None):
        super(XQueryBuilderWidget, self).__init__(parent)

        # load the user interface
        projexui.loadUi(__file__, self)

        self.setMinimumWidth(470)

        # define custom properties
        self._rules = {}
        self._defaultQuery = []
        self._completionTerms = []
        self._minimumCount = 1

        # set default properties
        self._container = QWidget(self)
        layout = QVBoxLayout()
        layout.setContentsMargins(0, 0, 0, 0)
        layout.setSpacing(2)
        layout.addStretch(1)
        self._container.setLayout(layout)
        self.uiQueryAREA.setWidget(self._container)

        # create connections
        self.uiResetBTN.clicked.connect(self.emitResetRequested)
        self.uiSaveBTN.clicked.connect(self.emitSaveRequested)
        self.uiCancelBTN.clicked.connect(self.emitCancelRequested)

        self.resetRequested.connect(self.reset)

    def addLineWidget(self, query=None):
        """
        Adds a new line widget to the system with the given values.
        
        :param      query | (<str> term, <str> operator, <str> vlaue) || None
        """
        widget = XQueryLineWidget(self)
        widget.setTerms(sorted(self._rules.keys()))
        widget.setQuery(query)

        index = self._container.layout().count() - 1
        self._container.layout().insertWidget(index, widget)

        widget.addRequested.connect(self.addLineWidget)
        widget.removeRequested.connect(self.removeLineWidget)

        # update the remove enabled options for these widgets
        self.updateRemoveEnabled()

    def addRule(self, rule):
        """
        Adds a rule to the system.
        
        :param      rule | <XQueryRule>
        """
        self._rules[rule.term()] = rule
        self.updateRules()

    def clear(self):
        """
        Clears out all the widgets from the system.
        """
        for lineWidget in self.lineWidgets():
            lineWidget.setParent(None)
            lineWidget.deleteLater()

    def completionTerms(self):
        """
        Returns the list of terms that will be used as a global override
        for completion terms when the query rule generates a QLineEdit instance.
        
        :return     [<str>, ..]
        """
        return self._completionTerms

    def count(self):
        """
        Returns the count of the line widgets in the system.
        
        :return     <int>
        """
        return len(self.lineWidgets())

    def currentQuery(self):
        """
        Returns the current query string for this widget.
        
        :return     [(<str> term, <str> operator, <str> value), ..]
        """
        widgets = self.lineWidgets()
        output = []
        for widget in widgets:
            output.append(widget.query())
        return output

    def defaultQuery(self):
        """
        Returns the default query for the system.
        
        :return     [(<str> term, <str> operator, <str> value), ..]
        """
        return self._defaultQuery

    def keyPressEvent(self, event):
        """
        Emits the save requested signal for this builder for when the enter
        or return press is clicked.
        
        :param      event | <QKeyEvent>
        """
        if (event.key() in (Qt.Key_Enter, Qt.Key_Return)):
            self.emitSaveRequested()

        super(XQueryBuilderWidget, self).keyPressEvent(event)

    def emitCancelRequested(self):
        """
        Emits the cancel requested signal.
        """
        if (not self.signalsBlocked()):
            self.cancelRequested.emit()

    def emitResetRequested(self):
        """
        Emits the reste requested signal.
        """
        if (not self.signalsBlocked()):
            self.resetRequested.emit()

    def emitSaveRequested(self):
        """
        Emits the save requested signal.
        """
        if (not self.signalsBlocked()):
            self.saveRequested.emit()

    def findRule(self, term):
        """
        Looks up a rule by the inputed term.
        
        :param      term | <str>
        
        :return     <XQueryRule> || None
        """
        return self._rules.get(nativestring(term))

    def removeLineWidget(self, widget):
        """
        Removes the line widget from the query.
        
        :param      widget | <XQueryLineWidget>
        """
        widget.setParent(None)
        widget.deleteLater()

        self.updateRemoveEnabled()

    def minimumCount(self):
        """
        Defines the minimum number of query widgets that are allowed.
        
        :return     <int>
        """
        return self._minimumCount

    def lineWidgets(self):
        """
        Returns a list of line widgets for this system.
        
        :return     [<XQueryLineWidget>, ..]
        """
        return self.findChildren(XQueryLineWidget)

    def reset(self):
        """
        Resets the system to the default query.
        """
        self.setCurrentQuery(self.defaultQuery())

    def setCompletionTerms(self, terms):
        """
        Sets the list of terms that will be used as a global override
        for completion terms when the query rule generates a QLineEdit instance.
        
        :param     terms | [<str>, ..]
        """
        self._completionTerms = terms

    def setCurrentQuery(self, query):
        """
        Sets the query for this system to the inputed query.
        
        :param      query | [(<str> term, <str> operator, <str> value), ..]
        """
        self.clear()

        for entry in query:
            self.addLineWidget(entry)

        # make sure we have the minimum number of widgets
        for i in range(self.minimumCount() - len(query)):
            self.addLineWidget()

    def setDefaultQuery(self, query):
        """
        Sets the default query that will be used when the user clicks on the \
        reset button or the reset method is called.
        
        :param      query | [(<str> term, <str> operator, <str> value), ..]
        """
        self._defaultQuery = query[:]

    def setMinimumCount(self, count):
        """
        Sets the minimum number of line widgets that are allowed at any \
        given time.
        
        :param      count | <int>
        """
        self._minimumCount = count

    def setRules(self, rules):
        """
        Sets all the rules for this builder.
        
        :param      rules | [<XQueryRule>, ..]
        """
        if (type(rules) in (list, tuple)):
            self._rules = dict([(x.term(), x) for x in rules])
            self.updateRules()
            return True

        elif (type(rules) == dict):
            self._rules = rules.copy()
            self.updateRules()
            return True

        else:
            return False

    def setTerms(self, terms):
        """
        Sets a simple rule list by accepting a list of strings for terms.  \
        This is a convenience method for the setRules method.
        
        :param      rules | [<str> term, ..]
        """
        return self.setRules([XQueryRule(term=term) for term in terms])

    def updateRemoveEnabled(self):
        """
        Updates the remove enabled baesd on the current number of line widgets.
        """
        lineWidgets = self.lineWidgets()
        count = len(lineWidgets)
        state = self.minimumCount() < count

        for widget in lineWidgets:
            widget.setRemoveEnabled(state)

    def updateRules(self):
        """
        Updates the query line items to match the latest rule options.
        """
        terms = sorted(self._rules.keys())
        for child in self.lineWidgets():
            child.setTerms(terms)
Пример #10
0
class XOrbQueryContainer(QWidget):
    """ """
    entriesUpdated = Signal()
    enterCompoundRequested = Signal(object, object)
    exitCompoundRequested = Signal()
    
    def __init__( self, parent = None ):
        super(XOrbQueryContainer, self).__init__( parent )
        
        # load the user interface
        projexui.loadUi(__file__, self)
        
        # define custom properties
        self._queryWidget   = parent
        self._entryWidget   = QWidget(self)
        self._currentJoiner = QueryCompound.Op.And
        
        layout = QVBoxLayout()
        layout.addStretch(1)
        layout.setSpacing(3)
        self._entryWidget.setLayout(layout)
        self.uiQueryAREA.setWidget(self._entryWidget)
        
        # set default properties
        self.setContextMenuPolicy(Qt.CustomContextMenu)
        
        # create connections (use old-style syntax or PySide errors)
        self.connect(self.uiBackBTN, SIGNAL('clicked()'), self.exitCompound)
        self.entriesUpdated.connect(self.refreshEntries)
    
    def addEntry(self, query=None, entry=None):
        if query is None:
            query = Query()
        
        layout = self._entryWidget.layout()
        index = layout.count() - 1
        if entry:
            index = layout.indexOf(entry) + 1
        
        widget = XOrbQueryEntryWidget(self, self.tableType())
        layout.insertWidget(index, widget)
        
        widget.setQuery(query)
        
        if not self.signalsBlocked():
            self.entriesUpdated.emit()
        
        return widget
    
    def checkedEntries(self):
        """
        Returns the widgets that are checked for this widget.
        
        :return     [<XOrbQueryEntryWidget>, ..]
        """
        return [entry for entry in self.entries() if entry.isChecked()]
    
    def clear(self):
        """
        Clears out the widgets for this query builder.
        """
        layout = self._entryWidget.layout()
        for i in range(layout.count() - 1):
            widget = layout.itemAt(i).widget()
            widget.close()
    
    def createCompoundFromChecked(self):
        """
        Creates a new compound query from the checked entry list.
        
        :return     <orb.QueryCompound>
        """
        checked_entries = self.checkedEntries()
        
        if len(checked_entries) <= 1:
            return QueryCompound()
        
        self.setUpdatesEnabled(False)
        joiner = self.currentJoiner()
        query = Query()
        for entry in checked_entries:
            if joiner == QueryCompound.Op.And:
                query &= entry.query()
            else:
                query |= entry.query()
        
        # clear out the existing containers
        first = checked_entries[0]
        first.setQuery(query)
        first.setChecked(False)
        
        layout = self._entryWidget.layout()
        for i in range(len(checked_entries) - 1, 0, -1):
            w = checked_entries[i]
            layout.takeAt(layout.indexOf(w))
            w.close()
        
        self.refreshEntries()
        self.setUpdatesEnabled(True)
        
        if not self.signalsBlocked():
            self.enterCompound(first, query)
    
    def currentJoiner(self):
        return self._currentJoiner
    
    def enterCompound(self, entry, query):
        # enter an existing compound
        if QueryCompound.typecheck(query):
            self.enterCompoundRequested.emit(entry, query)
        
        # create a new compound from the checked entries
        else:
            self.createCompoundFromChecked()
    
    def entries(self):
        """
        Returns the entry widgets for this widget.
        
        :return     [<XOrbQueryEntryWidget>, ..]
        """
        layout = self._entryWidget.layout()
        output = []
        for i in range(layout.count() - 1):
            widget = layout.itemAt(i).widget()
            if not isinstance(widget, XOrbQueryEntryWidget):
                continue
            output.append(widget)
        return output
    
    def exitCompound(self):
        self.exitCompoundRequested.emit()
    
    def isNull(self):
        """
        Returns whether or not any widgets have been defined for this
        container yet.
        
        :return     <bool>
        """
        return self._entryWidget.layout().count() <= 1
    
    def moveDown(self, entry):
        """
        Moves the current query down one entry.
        """
        if not entry:
            return
        
        entries = self.entries()
        next = entries[entries.index(entry) + 1]
        
        entry_q = entry.query()
        next_q  = next.query()
        
        next.setQuery(entry_q)
        entry.setQuery(next_q)
    
    def moveUp(self, entry):
        """
        Moves the current query down up one entry.
        """
        if not entry:
            return
        
        entries = self.entries()
        next = entries[entries.index(entry) - 1]
        
        entry_q = entry.query()
        next_q  = next.query()
        
        next.setQuery(entry_q)
        entry.setQuery(next_q)
    
    def pluginFactory(self):
        """
        Returns the plugin factory for this widget.  You can define a custom
        factory for handling specific columns or column types based on your
        table type.
        
        :return     <XOrbQueryPluginFactory>
        """
        return self._queryWidget.pluginFactory()
    
    def query(self):
        """
        Returns the query that is defined by this current panel.
        
        :return     <orb.Query>
        """
        joiner = self.currentJoiner()
        
        query = Query()
        for entry in self.entries():
            if joiner == QueryCompound.Op.And:
                query &= entry.query()
            else:
                query |= entry.query()
        
        query.setName(self.uiNameTXT.text())
        return query
    
    def queryWidget(self):
        """
        Returns the query widget linked with this container.
        
        :return     <XOrbQueryWidget>
        """
        return self._queryWidget
    
    def removeEntry(self, entry):
        if not entry:
            return
        
        layout = self._entryWidget.layout()
        if layout.count() == 2:
            entry.setQuery(Query())
            return
        
        layout.takeAt(layout.indexOf(entry))
        entry.close()
        
        if not self.signalsBlocked():
            self.entriesUpdated.emit()
    
    def refreshEntries(self):
        layout = self._entryWidget.layout()
        for i in range(layout.count() - 1):
            widget = layout.itemAt(i).widget()
            widget.setFirst(i == 0)
            widget.setLast(i == (layout.count() - 2))
    
    def setCurrentJoiner(self, joiner):
        self._currentJoiner = joiner
        layout = self._entryWidget.layout()
        for i in range(layout.count() - 1):
            widget = layout.itemAt(i).widget()
            widget.setJoiner(joiner)
    
    def setQuery(self, query):
        """
        Sets the query for this wigdet to the inputed query instance.
        
        :param      query | <orb.Query> || <orb.QueryCompound>
        """
        if not self.isNull() and hash(query) == hash(self.query()):
            return
        
        # add entries
        table = self.tableType()
        
        self.setUpdatesEnabled(False)
        self.blockSignals(True)
        self.clear()
        
        if query is None or table is None:
            self.setEnabled(False)
            self.setUpdatesEnabled(True)
            self.blockSignals(False)
            return
        else:
            self.setEnabled(True)
        
        # load the queries for this item
        if QueryCompound.typecheck(query):
            queries = query.queries()
            self.setCurrentJoiner(query.operatorType())
        else:
            queries = [query]
        
        self.uiNameTXT.setText(query.name())
        
        layout = self._entryWidget.layout()
        for index, query in enumerate(queries):
            widget = self.addEntry(query)
            widget.setFirst(index == 0)
            widget.setLast(index == (len(queries) - 1))
            widget.setJoiner(self.currentJoiner())
        
        self.setUpdatesEnabled(True)
        self.blockSignals(False)
    
    def setShowBack(self, state):
        # check to see if we're working on the current query
        self.uiBackBTN.setVisible(state)
        self.uiNameTXT.setVisible(state)
    
    def tableType(self):
        """
        Returns the table type instance for this widget.
        
        :return     <subclass of orb.Table>
        """
        return self._queryWidget.tableType()
Пример #11
0
    def __init__(self, parent=None):
        super(XNavigationEdit, self).__init__(parent)

        # define custom properties
        self._separator = '/'
        self._partsEditingEnabled = True
        self._originalText = ''
        self._scrollWidget = QScrollArea(self)
        self._partsWidget = QWidget(self._scrollWidget)
        self._buttonGroup = QButtonGroup(self)
        self._scrollAmount = 0
        self._navigationModel = None

        # create the completer tree
        palette = self.palette()
        palette.setColor(palette.Base, palette.color(palette.Window))
        palette.setColor(palette.Text, palette.color(palette.WindowText))

        bg = palette.color(palette.Highlight)
        abg = bg.darker(115)
        fg = palette.color(palette.HighlightedText)
        sbg = 'rgb(%s, %s, %s)' % (bg.red(), bg.green(), bg.blue())
        sabg = 'rgb(%s, %s, %s)' % (abg.red(), abg.green(), abg.blue())
        sfg = 'rgb(%s, %s, %s)' % (fg.red(), fg.green(), fg.blue())
        style   = 'QTreeView::item:hover { '\
                  '     color: %s;'\
                  '     background: qlineargradient(x1:0,'\
                  '                                 y1:0,'\
                  '                                 x2:0,'\
                  '                                 y2:1,'\
                  '                                 stop: 0 %s,'\
                  '                                 stop: 1 %s);'\
                  '}' % (sfg, sbg, sabg)

        self._completerTree = QTreeView(self)
        self._completerTree.setStyleSheet(style)
        self._completerTree.header().hide()
        self._completerTree.setFrameShape(QTreeView.Box)
        self._completerTree.setFrameShadow(QTreeView.Plain)
        self._completerTree.setPalette(palette)
        self._completerTree.setEditTriggers(QTreeView.NoEditTriggers)
        self._completerTree.setWindowFlags(Qt.Popup)
        self._completerTree.installEventFilter(self)
        self._completerTree.setRootIsDecorated(False)
        self._completerTree.setItemsExpandable(False)

        # create the editing widget
        layout = QHBoxLayout()
        layout.setContentsMargins(0, 0, 0, 0)
        layout.setSpacing(0)
        layout.addStretch()

        self._scrollWidget.setFrameShape(QScrollArea.NoFrame)
        self._scrollWidget.setFocusPolicy(Qt.NoFocus)
        self._scrollWidget.setWidget(self._partsWidget)
        self._scrollWidget.setWidgetResizable(True)
        self._scrollWidget.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
        self._scrollWidget.setAlignment(Qt.AlignTop | Qt.AlignRight)
        self._scrollWidget.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
        self._scrollWidget.setContentsMargins(0, 0, 0, 0)
        self._scrollWidget.setViewportMargins(0, 0, 0, 0)
        self._scrollWidget.move(2, 2)

        self._partsWidget.setLayout(layout)
        self._partsWidget.setCursor(Qt.ArrowCursor)
        self._partsWidget.setAutoFillBackground(True)
        self._partsWidget.setFixedHeight(self.height() - 12)

        palette = self._partsWidget.palette()
        palette.setColor(palette.Background, palette.color(palette.Base))
        self._partsWidget.setPalette(palette)

        # create connections
        self._completerTree.clicked.connect(self.navigateToIndex)
        self._buttonGroup.buttonClicked.connect(self.handleButtonClick)
        self._scrollWidget.horizontalScrollBar().valueChanged.connect(
            self.scrollParts)
Пример #12
0
class XNavigationEdit(XLineEdit):
    """ """
    navigationChanged = Signal()

    __designer_icon__ = projexui.resources.find('img/ui/navigate.png')

    def __init__(self, parent=None):
        super(XNavigationEdit, self).__init__(parent)

        # define custom properties
        self._separator = '/'
        self._partsEditingEnabled = True
        self._originalText = ''
        self._scrollWidget = QScrollArea(self)
        self._partsWidget = QWidget(self._scrollWidget)
        self._buttonGroup = QButtonGroup(self)
        self._scrollAmount = 0
        self._navigationModel = None

        # create the completer tree
        palette = self.palette()
        palette.setColor(palette.Base, palette.color(palette.Window))
        palette.setColor(palette.Text, palette.color(palette.WindowText))

        bg = palette.color(palette.Highlight)
        abg = bg.darker(115)
        fg = palette.color(palette.HighlightedText)
        sbg = 'rgb(%s, %s, %s)' % (bg.red(), bg.green(), bg.blue())
        sabg = 'rgb(%s, %s, %s)' % (abg.red(), abg.green(), abg.blue())
        sfg = 'rgb(%s, %s, %s)' % (fg.red(), fg.green(), fg.blue())
        style   = 'QTreeView::item:hover { '\
                  '     color: %s;'\
                  '     background: qlineargradient(x1:0,'\
                  '                                 y1:0,'\
                  '                                 x2:0,'\
                  '                                 y2:1,'\
                  '                                 stop: 0 %s,'\
                  '                                 stop: 1 %s);'\
                  '}' % (sfg, sbg, sabg)

        self._completerTree = QTreeView(self)
        self._completerTree.setStyleSheet(style)
        self._completerTree.header().hide()
        self._completerTree.setFrameShape(QTreeView.Box)
        self._completerTree.setFrameShadow(QTreeView.Plain)
        self._completerTree.setPalette(palette)
        self._completerTree.setEditTriggers(QTreeView.NoEditTriggers)
        self._completerTree.setWindowFlags(Qt.Popup)
        self._completerTree.installEventFilter(self)
        self._completerTree.setRootIsDecorated(False)
        self._completerTree.setItemsExpandable(False)

        # create the editing widget
        layout = QHBoxLayout()
        layout.setContentsMargins(0, 0, 0, 0)
        layout.setSpacing(0)
        layout.addStretch()

        self._scrollWidget.setFrameShape(QScrollArea.NoFrame)
        self._scrollWidget.setFocusPolicy(Qt.NoFocus)
        self._scrollWidget.setWidget(self._partsWidget)
        self._scrollWidget.setWidgetResizable(True)
        self._scrollWidget.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
        self._scrollWidget.setAlignment(Qt.AlignTop | Qt.AlignRight)
        self._scrollWidget.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
        self._scrollWidget.setContentsMargins(0, 0, 0, 0)
        self._scrollWidget.setViewportMargins(0, 0, 0, 0)
        self._scrollWidget.move(2, 2)

        self._partsWidget.setLayout(layout)
        self._partsWidget.setCursor(Qt.ArrowCursor)
        self._partsWidget.setAutoFillBackground(True)
        self._partsWidget.setFixedHeight(self.height() - 12)

        palette = self._partsWidget.palette()
        palette.setColor(palette.Background, palette.color(palette.Base))
        self._partsWidget.setPalette(palette)

        # create connections
        self._completerTree.clicked.connect(self.navigateToIndex)
        self._buttonGroup.buttonClicked.connect(self.handleButtonClick)
        self._scrollWidget.horizontalScrollBar().valueChanged.connect(
            self.scrollParts)

    def acceptEdit(self):
        """
        Accepts the current text and rebuilds the parts widget.
        """

        if (self._partsWidget.isVisible()):
            return False

        use_completion = self.completer().popup().isVisible()
        completion = self.completer().currentCompletion()

        self._completerTree.hide()
        self.completer().popup().hide()

        if (use_completion):
            self.setText(completion)
        else:
            self.rebuild()

        return True

    def cancelEdit(self):
        """
        Rejects the current edit and shows the parts widget.
        """

        if (self._partsWidget.isVisible()):
            return False

        self._completerTree.hide()
        self.completer().popup().hide()

        self.setText(self._originalText)
        return True

    def currentItem(self):
        """
        Returns the current navigation item from the current path.
        
        :return     <XNavigationItem> || None
        """
        model = self.navigationModel()
        if (not model):
            return None

        return model.itemByPath(self.text())

    def eventFilter(self, object, event):
        """
        Filters the events for the inputed object through this edit.
        
        :param      object | <QObject>
                    event  | <QEvent>
        
        :return     <bool> | consumed
        """
        if (event.type() == event.KeyPress):
            if (event.key() == Qt.Key_Escape):
                self._completerTree.hide()
                self.completer().popup().hide()

                self.cancelEdit()

            elif (event.key() in (Qt.Key_Return, Qt.Key_Enter)):
                self.acceptEdit()
                return True

            elif (event.key() == Qt.Key_Tab):
                if (self.completer().popup().isVisible()):
                    text = nativestring(self.completer().currentCompletion())
                    super(XNavigationEdit, self).setText(text)
                    return True
                else:
                    self.acceptEdit()
                    return False

        elif (event.type() == event.MouseButtonPress):
            if (not self._completerTree.rect().contains(event.pos())):
                self._completerTree.hide()
                self.completer().popup().hide()

                self.cancelEdit()

        return False

    def focusOutEvent(self, event):
        """
        Overloads the focus out event to cancel editing when the widget loses
        focus.
        
        :param      event | <QFocusEvent>
        """
        super(XNavigationEdit, self).focusOutEvent(event)

        self.cancelEdit()

    def handleButtonClick(self, button):
        """
        Handle the event when a user clicks on one of the part buttons.
        
        :param      button | <QToolButton>
        """
        path = button.property('path')
        is_completer = button.property('is_completer')

        # popup a completion menu
        if (unwrapVariant(is_completer)):
            model = self.navigationModel()
            if (not model):
                return

            sep = self.separator()
            path = nativestring(unwrapVariant(path))
            item = model.itemByPath(path, includeRoot=True)
            if (not item):
                return

            curr_path = nativestring(self.text()).strip(self.separator())
            curr_path = curr_path.replace(path, '').strip(self.separator())

            child_name = ''
            if (curr_path):
                child_name = curr_path.split(self.separator())[0]

            index = model.indexFromItem(item)

            self._completerTree.move(QCursor.pos())
            self._completerTree.setRootIndex(index)
            self._completerTree.verticalScrollBar().setValue(0)

            if (child_name):
                child_item = None
                for i in range(item.rowCount()):
                    child = item.child(i)
                    if (child.text() == child_name):
                        child_item = child
                        break

                if (child_item):
                    child_index = model.indexFromItem(child_item)
                    self._completerTree.setCurrentIndex(child_index)
                    self._completerTree.scrollTo(child_index)

            self._completerTree.show()
            self._completerTree.setUpdatesEnabled(True)
        else:
            self.setText(unwrapVariant(path))

    def keyPressEvent(self, event):
        """
        Overloads the key press event to listen for escape calls to cancel the
        parts editing.
        
        :param      event | <QKeyPressEvent>
        """
        if (self.scrollWidget().isHidden()):
            if (event.key() == Qt.Key_Escape):
                self.cancelEdit()
                return

            elif (event.key() in (Qt.Key_Return, Qt.Key_Enter)):
                self.acceptEdit()
                return

        elif (event.key() == Qt.Key_A
              and event.modifiers() == Qt.ControlModifier):
            self.startEdit()

        super(XNavigationEdit, self).keyPressEvent(event)

    def mouseDoubleClickEvent(self, event):
        """
        Overloads the system to enable editing when a user double clicks.
        
        :param      event | <QMouseEvent>
        """
        super(XNavigationEdit, self).mouseDoubleClickEvent(event)

        self.startEdit()

    def navigationModel(self):
        """
        Returns the navigation model linked with this edit.
        
        :return     <XNavigationModel> || None
        """
        return self._navigationModel

    def navigateToIndex(self, index):
        """
        Navigates to the inputed action's path.
        
        :param      action | <QAction>
        """
        self._completerTree.hide()
        item = self._navigationModel.itemFromIndex(index)
        self.setText(self._navigationModel.itemPath(item))

    def parts(self):
        """
        Returns the parts that are used for this system.
        
        :return     [<str>, ..]
        """
        path = nativestring(self.text()).strip(self.separator())
        if (not path):
            return []
        return path.split(self.separator())

    def partsWidget(self):
        """
        Returns the widget that contains the parts system.
        
        :return     <QScrollArea>
        """
        return self._partsWidget

    def startEdit(self):
        """
        Rebuilds the pathing based on the parts.
        """
        self._originalText = self.text()
        self.scrollWidget().hide()
        self.setFocus()
        self.selectAll()

    def rebuild(self):
        """
        Rebuilds the parts widget with the latest text.
        """
        navitem = self.currentItem()
        if (navitem):
            navitem.initialize()

        self.setUpdatesEnabled(False)
        self.scrollWidget().show()
        self._originalText = ''

        partsw = self.partsWidget()
        for button in self._buttonGroup.buttons():
            self._buttonGroup.removeButton(button)
            button.close()
            button.setParent(None)
            button.deleteLater()

        # create the root button
        layout = partsw.layout()
        parts = self.parts()

        button = QToolButton(partsw)
        button.setAutoRaise(True)
        button.setMaximumWidth(12)
        button.setArrowType(Qt.RightArrow)

        button.setProperty('path', wrapVariant(''))
        button.setProperty('is_completer', wrapVariant(True))
        last_button = button

        self._buttonGroup.addButton(button)
        layout.insertWidget(0, button)

        # check to see if we have a navigation model setup
        if (self._navigationModel):
            last_item = self._navigationModel.itemByPath(self.text())
            show_last = last_item and last_item.rowCount() > 0
        else:
            show_last = False

        # load the navigation system
        count = len(parts)
        for i, part in enumerate(parts):
            path = self.separator().join(parts[:i + 1])

            button = QToolButton(partsw)
            button.setAutoRaise(True)
            button.setText(part)

            if (self._navigationModel):
                item = self._navigationModel.itemByPath(path)
                if (item):
                    button.setIcon(item.icon())
                    button.setToolButtonStyle(Qt.ToolButtonTextBesideIcon)

            button.setProperty('path', wrapVariant(path))
            button.setProperty('is_completer', wrapVariant(False))

            self._buttonGroup.addButton(button)
            layout.insertWidget((i * 2) + 1, button)

            # determine if we should show the final button
            if (show_last or i < (count - 1)):
                button = QToolButton(partsw)
                button.setAutoRaise(True)
                button.setMaximumWidth(12)
                button.setArrowType(Qt.RightArrow)

                button.setProperty('path', wrapVariant(path))
                button.setProperty('is_completer', wrapVariant(True))

                self._buttonGroup.addButton(button)
                layout.insertWidget((i * 2) + 2, button)

                last_button = button

        if (self.scrollWidget().width() < partsw.width()):
            self.scrollParts(partsw.width() - self.scrollWidget().width())

        self.setUpdatesEnabled(True)
        self.navigationChanged.emit()

    def resizeEvent(self, event):
        """
        Resizes the current widget and its parts widget.
        
        :param      event | <QResizeEvent>
        """
        super(XNavigationEdit, self).resizeEvent(event)

        w = self.width()
        h = self.height()

        self._scrollWidget.resize(w - 4, h - 4)

        if (self._scrollWidget.width() < self._partsWidget.width()):
            self.scrollParts(self._partsWidget.width() -
                             self._scrollWidget.width())

    def scrollParts(self, amount):
        """
        Scrolls the parts to offset the scrolling amount.
        
        :param      amount | <int>
        """
        change = self._scrollAmount - amount
        self._partsWidget.scroll(change, 0)
        self._scrollAmount = amount

    def scrollWidget(self):
        """
        Returns the scrolling widget.
        
        :return     <QScrollArea>
        """
        return self._scrollWidget

    def separator(self):
        """
        Returns the separation character that is used for this edit.
        
        :return     <str>
        """
        return self._separator

    def setTopLevelItems(self, items):
        """
        Initializes the navigation system to start with the inputed root \
        item.
        
        :param      item | <XNavigationItem>
        """
        if (not self._navigationModel):
            self.setNavigationModel(XNavigationModel(self))

        self._navigationModel.setTopLevelItems(items)

    def setNavigationModel(self, model):
        """
        Sets the navigation model for this edit.
        
        :param      model | <XNavigationModel>
        """
        self._navigationModel = model
        self._completerTree.setModel(model)

        if (model):
            model.setSeparator(self.separator())
            completer = XNavigationCompleter(model, self)
            self.setCompleter(completer)
            completer.popup().installEventFilter(self)
        else:
            self.setCompleter(None)

        self.rebuild()

    def setParts(self, parts):
        """
        Sets the path for this edit widget by providing the parts to the path.
        
        :param      parts | [<str>, ..]
        """
        self.setText(self.separator().join(map(str, parts)))

    def setSeparator(self, separator):
        """
        Sets the separator to the inputed character.
        
        :param      separator | <str>
        """
        self._separator = separator
        if (self._navigationModel):
            self._navigationModel.setSeparator(separator)
        self.rebuild()

    def setText(self, text):
        """
        Sets the text for this edit to the inputed text.
        
        :param      text | <str>
        """
        super(XNavigationEdit, self).setText(text)

        self.scrollWidget().show()
        if (text == '' or self._originalText != text):
            self.rebuild()
Пример #13
0
class XOrbQueryContainer(QWidget):
    """ """
    entriesUpdated = Signal()
    enterCompoundRequested = Signal(object, object)
    exitCompoundRequested = Signal()

    def __init__(self, parent=None):
        super(XOrbQueryContainer, self).__init__(parent)

        # load the user interface
        projexui.loadUi(__file__, self)

        # define custom properties
        self._queryWidget = parent
        self._entryWidget = QWidget(self)
        self._currentJoiner = QueryCompound.Op.And

        layout = QVBoxLayout()
        layout.addStretch(1)
        layout.setSpacing(3)
        self._entryWidget.setLayout(layout)
        self.uiQueryAREA.setWidget(self._entryWidget)

        # set default properties
        self.setContextMenuPolicy(Qt.CustomContextMenu)

        # create connections (use old-style syntax or PySide errors)
        self.connect(self.uiBackBTN, SIGNAL('clicked()'), self.exitCompound)
        self.entriesUpdated.connect(self.refreshEntries)

    def addEntry(self, query=None, entry=None):
        if query is None:
            query = Query()

        layout = self._entryWidget.layout()
        index = layout.count() - 1
        if entry:
            index = layout.indexOf(entry) + 1

        widget = XOrbQueryEntryWidget(self, self.tableType())
        layout.insertWidget(index, widget)

        widget.setQuery(query)

        if not self.signalsBlocked():
            self.entriesUpdated.emit()

        return widget

    def checkedEntries(self):
        """
        Returns the widgets that are checked for this widget.
        
        :return     [<XOrbQueryEntryWidget>, ..]
        """
        return [entry for entry in self.entries() if entry.isChecked()]

    def clear(self):
        """
        Clears out the widgets for this query builder.
        """
        layout = self._entryWidget.layout()
        for i in range(layout.count() - 1):
            widget = layout.itemAt(i).widget()
            widget.close()

    def createCompoundFromChecked(self):
        """
        Creates a new compound query from the checked entry list.
        
        :return     <orb.QueryCompound>
        """
        checked_entries = self.checkedEntries()

        if len(checked_entries) <= 1:
            return QueryCompound()

        self.setUpdatesEnabled(False)
        joiner = self.currentJoiner()
        query = Query()
        for entry in checked_entries:
            if joiner == QueryCompound.Op.And:
                query &= entry.query()
            else:
                query |= entry.query()

        # clear out the existing containers
        first = checked_entries[0]
        first.setQuery(query)
        first.setChecked(False)

        layout = self._entryWidget.layout()
        for i in range(len(checked_entries) - 1, 0, -1):
            w = checked_entries[i]
            layout.takeAt(layout.indexOf(w))
            w.close()

        self.refreshEntries()
        self.setUpdatesEnabled(True)

        if not self.signalsBlocked():
            self.enterCompound(first, query)

    def currentJoiner(self):
        return self._currentJoiner

    def enterCompound(self, entry, query):
        # enter an existing compound
        if QueryCompound.typecheck(query):
            self.enterCompoundRequested.emit(entry, query)

        # create a new compound from the checked entries
        else:
            self.createCompoundFromChecked()

    def entries(self):
        """
        Returns the entry widgets for this widget.
        
        :return     [<XOrbQueryEntryWidget>, ..]
        """
        layout = self._entryWidget.layout()
        output = []
        for i in range(layout.count() - 1):
            widget = layout.itemAt(i).widget()
            if not isinstance(widget, XOrbQueryEntryWidget):
                continue
            output.append(widget)
        return output

    def exitCompound(self):
        self.exitCompoundRequested.emit()

    def isNull(self):
        """
        Returns whether or not any widgets have been defined for this
        container yet.
        
        :return     <bool>
        """
        return self._entryWidget.layout().count() <= 1

    def moveDown(self, entry):
        """
        Moves the current query down one entry.
        """
        if not entry:
            return

        entries = self.entries()
        next = entries[entries.index(entry) + 1]

        entry_q = entry.query()
        next_q = next.query()

        next.setQuery(entry_q)
        entry.setQuery(next_q)

    def moveUp(self, entry):
        """
        Moves the current query down up one entry.
        """
        if not entry:
            return

        entries = self.entries()
        next = entries[entries.index(entry) - 1]

        entry_q = entry.query()
        next_q = next.query()

        next.setQuery(entry_q)
        entry.setQuery(next_q)

    def pluginFactory(self):
        """
        Returns the plugin factory for this widget.  You can define a custom
        factory for handling specific columns or column types based on your
        table type.
        
        :return     <XOrbQueryPluginFactory>
        """
        return self._queryWidget.pluginFactory()

    def query(self):
        """
        Returns the query that is defined by this current panel.
        
        :return     <orb.Query>
        """
        joiner = self.currentJoiner()

        query = Query()
        for entry in self.entries():
            if joiner == QueryCompound.Op.And:
                query &= entry.query()
            else:
                query |= entry.query()

        query.setName(self.uiNameTXT.text())
        return query

    def queryWidget(self):
        """
        Returns the query widget linked with this container.
        
        :return     <XOrbQueryWidget>
        """
        return self._queryWidget

    def removeEntry(self, entry):
        if not entry:
            return

        layout = self._entryWidget.layout()
        if layout.count() == 2:
            entry.setQuery(Query())
            return

        layout.takeAt(layout.indexOf(entry))
        entry.close()

        if not self.signalsBlocked():
            self.entriesUpdated.emit()

    def refreshEntries(self):
        layout = self._entryWidget.layout()
        for i in range(layout.count() - 1):
            widget = layout.itemAt(i).widget()
            widget.setFirst(i == 0)
            widget.setLast(i == (layout.count() - 2))

    def setCurrentJoiner(self, joiner):
        self._currentJoiner = joiner
        layout = self._entryWidget.layout()
        for i in range(layout.count() - 1):
            widget = layout.itemAt(i).widget()
            widget.setJoiner(joiner)

    def setQuery(self, query):
        """
        Sets the query for this wigdet to the inputed query instance.
        
        :param      query | <orb.Query> || <orb.QueryCompound>
        """
        if not self.isNull() and hash(query) == hash(self.query()):
            return

        # add entries
        table = self.tableType()

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

        if query is None or table is None:
            self.setEnabled(False)
            self.setUpdatesEnabled(True)
            self.blockSignals(False)
            return
        else:
            self.setEnabled(True)

        # load the queries for this item
        if QueryCompound.typecheck(query):
            queries = query.queries()
            self.setCurrentJoiner(query.operatorType())
        else:
            queries = [query]

        self.uiNameTXT.setText(query.name())

        layout = self._entryWidget.layout()
        for index, query in enumerate(queries):
            widget = self.addEntry(query)
            widget.setFirst(index == 0)
            widget.setLast(index == (len(queries) - 1))
            widget.setJoiner(self.currentJoiner())

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

    def setShowBack(self, state):
        # check to see if we're working on the current query
        self.uiBackBTN.setVisible(state)
        self.uiNameTXT.setVisible(state)

    def tableType(self):
        """
        Returns the table type instance for this widget.
        
        :return     <subclass of orb.Table>
        """
        return self._queryWidget.tableType()
Пример #14
0
 def __init__( self, parent = None ):
     super(XNavigationEdit, self).__init__( parent )
     
     # define custom properties
     self._separator             = '/'
     self._partsEditingEnabled   = True
     self._originalText          = ''
     self._scrollWidget          = QScrollArea(self)
     self._partsWidget           = QWidget(self._scrollWidget)
     self._buttonGroup           = QButtonGroup(self)
     self._scrollAmount          = 0
     self._navigationModel       = None
     
     # create the completer tree
     palette = self.palette()
     palette.setColor(palette.Base, palette.color(palette.Window))
     palette.setColor(palette.Text, palette.color(palette.WindowText))
     
     bg      = palette.color(palette.Highlight)
     abg     = bg.darker(115)
     fg      = palette.color(palette.HighlightedText)
     sbg     = 'rgb(%s, %s, %s)' % (bg.red(), bg.green(), bg.blue())
     sabg    = 'rgb(%s, %s, %s)' % (abg.red(), abg.green(), abg.blue())
     sfg     = 'rgb(%s, %s, %s)' % (fg.red(), fg.green(), fg.blue())
     style   = 'QTreeView::item:hover { '\
               '     color: %s;'\
               '     background: qlineargradient(x1:0,'\
               '                                 y1:0,'\
               '                                 x2:0,'\
               '                                 y2:1,'\
               '                                 stop: 0 %s,'\
               '                                 stop: 1 %s);'\
               '}' % (sfg, sbg, sabg)
     
     self._completerTree = QTreeView(self)
     self._completerTree.setStyleSheet(style)
     self._completerTree.header().hide()
     self._completerTree.setFrameShape(QTreeView.Box)
     self._completerTree.setFrameShadow(QTreeView.Plain)
     self._completerTree.setPalette(palette)
     self._completerTree.setEditTriggers(QTreeView.NoEditTriggers)
     self._completerTree.setWindowFlags(Qt.Popup)
     self._completerTree.installEventFilter(self)
     self._completerTree.setRootIsDecorated(False)
     self._completerTree.setItemsExpandable(False)
     
     # create the editing widget
     layout = QHBoxLayout()
     layout.setContentsMargins(0, 0, 0, 0)
     layout.setSpacing(0)
     layout.addStretch()
     
     self._scrollWidget.setFrameShape( QScrollArea.NoFrame )
     self._scrollWidget.setFocusPolicy(Qt.NoFocus)
     self._scrollWidget.setWidget(self._partsWidget)
     self._scrollWidget.setWidgetResizable(True)
     self._scrollWidget.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
     self._scrollWidget.setAlignment(Qt.AlignTop | Qt.AlignRight)
     self._scrollWidget.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
     self._scrollWidget.setContentsMargins(0, 0, 0, 0)
     self._scrollWidget.setViewportMargins(0, 0, 0, 0)
     self._scrollWidget.move(2, 2)
     
     self._partsWidget.setLayout(layout)
     self._partsWidget.setCursor(Qt.ArrowCursor)
     self._partsWidget.setAutoFillBackground(True)
     self._partsWidget.setFixedHeight(self.height() - 12)
     
     palette = self._partsWidget.palette()
     palette.setColor(palette.Background, palette.color(palette.Base))
     self._partsWidget.setPalette(palette)
     
     # create connections
     self._completerTree.clicked.connect( self.navigateToIndex )
     self._buttonGroup.buttonClicked.connect( self.handleButtonClick )
     self._scrollWidget.horizontalScrollBar().valueChanged.connect( 
                                                     self.scrollParts )
Пример #15
0
class XNavigationEdit(XLineEdit):
    """ """
    navigationChanged = Signal()
    
    __designer_icon__ = projexui.resources.find('img/ui/navigate.png')
    
    def __init__( self, parent = None ):
        super(XNavigationEdit, self).__init__( parent )
        
        # define custom properties
        self._separator             = '/'
        self._partsEditingEnabled   = True
        self._originalText          = ''
        self._scrollWidget          = QScrollArea(self)
        self._partsWidget           = QWidget(self._scrollWidget)
        self._buttonGroup           = QButtonGroup(self)
        self._scrollAmount          = 0
        self._navigationModel       = None
        
        # create the completer tree
        palette = self.palette()
        palette.setColor(palette.Base, palette.color(palette.Window))
        palette.setColor(palette.Text, palette.color(palette.WindowText))
        
        bg      = palette.color(palette.Highlight)
        abg     = bg.darker(115)
        fg      = palette.color(palette.HighlightedText)
        sbg     = 'rgb(%s, %s, %s)' % (bg.red(), bg.green(), bg.blue())
        sabg    = 'rgb(%s, %s, %s)' % (abg.red(), abg.green(), abg.blue())
        sfg     = 'rgb(%s, %s, %s)' % (fg.red(), fg.green(), fg.blue())
        style   = 'QTreeView::item:hover { '\
                  '     color: %s;'\
                  '     background: qlineargradient(x1:0,'\
                  '                                 y1:0,'\
                  '                                 x2:0,'\
                  '                                 y2:1,'\
                  '                                 stop: 0 %s,'\
                  '                                 stop: 1 %s);'\
                  '}' % (sfg, sbg, sabg)
        
        self._completerTree = QTreeView(self)
        self._completerTree.setStyleSheet(style)
        self._completerTree.header().hide()
        self._completerTree.setFrameShape(QTreeView.Box)
        self._completerTree.setFrameShadow(QTreeView.Plain)
        self._completerTree.setPalette(palette)
        self._completerTree.setEditTriggers(QTreeView.NoEditTriggers)
        self._completerTree.setWindowFlags(Qt.Popup)
        self._completerTree.installEventFilter(self)
        self._completerTree.setRootIsDecorated(False)
        self._completerTree.setItemsExpandable(False)
        
        # create the editing widget
        layout = QHBoxLayout()
        layout.setContentsMargins(0, 0, 0, 0)
        layout.setSpacing(0)
        layout.addStretch()
        
        self._scrollWidget.setFrameShape( QScrollArea.NoFrame )
        self._scrollWidget.setFocusPolicy(Qt.NoFocus)
        self._scrollWidget.setWidget(self._partsWidget)
        self._scrollWidget.setWidgetResizable(True)
        self._scrollWidget.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
        self._scrollWidget.setAlignment(Qt.AlignTop | Qt.AlignRight)
        self._scrollWidget.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
        self._scrollWidget.setContentsMargins(0, 0, 0, 0)
        self._scrollWidget.setViewportMargins(0, 0, 0, 0)
        self._scrollWidget.move(2, 2)
        
        self._partsWidget.setLayout(layout)
        self._partsWidget.setCursor(Qt.ArrowCursor)
        self._partsWidget.setAutoFillBackground(True)
        self._partsWidget.setFixedHeight(self.height() - 12)
        
        palette = self._partsWidget.palette()
        palette.setColor(palette.Background, palette.color(palette.Base))
        self._partsWidget.setPalette(palette)
        
        # create connections
        self._completerTree.clicked.connect( self.navigateToIndex )
        self._buttonGroup.buttonClicked.connect( self.handleButtonClick )
        self._scrollWidget.horizontalScrollBar().valueChanged.connect( 
                                                        self.scrollParts )
    
    def acceptEdit( self ):
        """
        Accepts the current text and rebuilds the parts widget.
        """
        
        if ( self._partsWidget.isVisible() ):
            return False
        
        use_completion = self.completer().popup().isVisible()
        completion     = self.completer().currentCompletion()
        
        self._completerTree.hide()
        self.completer().popup().hide()
        
        if ( use_completion ):
            self.setText(completion)
        else:
            self.rebuild()
            
        return True
    
    def cancelEdit( self ):
        """
        Rejects the current edit and shows the parts widget.
        """
        
        if ( self._partsWidget.isVisible() ):
            return False
            
        self._completerTree.hide()
        self.completer().popup().hide()
        
        self.setText(self._originalText)
        return True
    
    def currentItem( self ):
        """
        Returns the current navigation item from the current path.
        
        :return     <XNavigationItem> || None
        """
        model = self.navigationModel()
        if ( not model ):
            return None
        
        return model.itemByPath(self.text())
    
    def eventFilter( self, object, event ):
        """
        Filters the events for the inputed object through this edit.
        
        :param      object | <QObject>
                    event  | <QEvent>
        
        :return     <bool> | consumed
        """
        if ( event.type() == event.KeyPress ):
            if ( event.key() == Qt.Key_Escape ):
                self._completerTree.hide()
                self.completer().popup().hide()
                
                self.cancelEdit()
                
            elif ( event.key() in (Qt.Key_Return, Qt.Key_Enter) ):
                self.acceptEdit()
                return True
                
            elif ( event.key() == Qt.Key_Tab ):
                if ( self.completer().popup().isVisible() ):
                    text   = nativestring(self.completer().currentCompletion())
                    super(XNavigationEdit, self).setText(text)
                    return True
                else:
                    self.acceptEdit()
                    return False
            
        elif ( event.type() == event.MouseButtonPress ):
            if ( not self._completerTree.rect().contains(event.pos()) ):
                self._completerTree.hide()
                self.completer().popup().hide()
                
                self.cancelEdit()
        
        return False
    
    def focusOutEvent( self, event ):
        """
        Overloads the focus out event to cancel editing when the widget loses
        focus.
        
        :param      event | <QFocusEvent>
        """
        super(XNavigationEdit, self).focusOutEvent(event)
        
        self.cancelEdit()
    
    def handleButtonClick( self, button ):
        """
        Handle the event when a user clicks on one of the part buttons.
        
        :param      button | <QToolButton>
        """
        path            = button.property('path')
        is_completer    = button.property('is_completer')
        
        # popup a completion menu
        if ( unwrapVariant(is_completer) ):
            model = self.navigationModel()
            if ( not model ):
                return
            
            sep  = self.separator()
            path = nativestring(unwrapVariant(path))
            item = model.itemByPath(path, includeRoot = True)
            if ( not item ):
                return
            
            curr_path = nativestring(self.text()).strip(self.separator())
            curr_path = curr_path.replace(path, '').strip(self.separator())
            
            child_name = ''
            if ( curr_path ):
                child_name = curr_path.split(self.separator())[0]
            
            index = model.indexFromItem(item)
            
            self._completerTree.move(QCursor.pos())
            self._completerTree.setRootIndex(index)
            self._completerTree.verticalScrollBar().setValue(0)
            
            if ( child_name ):
                child_item = None
                for i in range(item.rowCount()):
                    child = item.child(i)
                    if ( child.text() == child_name ):
                        child_item = child
                        break
                
                if ( child_item ):
                    child_index = model.indexFromItem(child_item)
                    self._completerTree.setCurrentIndex(child_index)
                    self._completerTree.scrollTo(child_index)
            
            self._completerTree.show()
            self._completerTree.setUpdatesEnabled(True)
        else:
            self.setText(unwrapVariant(path))
    
    def keyPressEvent( self, event ):
        """
        Overloads the key press event to listen for escape calls to cancel the
        parts editing.
        
        :param      event | <QKeyPressEvent>
        """
        if ( self.scrollWidget().isHidden() ):
            if ( event.key() == Qt.Key_Escape ):
                self.cancelEdit()
                return
                
            elif ( event.key() in (Qt.Key_Return, Qt.Key_Enter) ):
                self.acceptEdit()
                return
            
        elif ( event.key() == Qt.Key_A and 
               event.modifiers() == Qt.ControlModifier ):
            self.startEdit()
        
        super(XNavigationEdit, self).keyPressEvent(event)
    
    def mouseDoubleClickEvent( self, event ):
        """
        Overloads the system to enable editing when a user double clicks.
        
        :param      event | <QMouseEvent>
        """
        super(XNavigationEdit, self).mouseDoubleClickEvent(event)
        
        self.startEdit()
    
    def navigationModel( self ):
        """
        Returns the navigation model linked with this edit.
        
        :return     <XNavigationModel> || None
        """
        return self._navigationModel
    
    def navigateToIndex( self, index ):
        """
        Navigates to the inputed action's path.
        
        :param      action | <QAction>
        """
        self._completerTree.hide()
        item = self._navigationModel.itemFromIndex(index)
        self.setText(self._navigationModel.itemPath(item))
    
    def parts( self ):
        """
        Returns the parts that are used for this system.
        
        :return     [<str>, ..]
        """
        path = nativestring(self.text()).strip(self.separator())
        if ( not path ):
            return []
        return path.split(self.separator())
    
    def partsWidget( self ):
        """
        Returns the widget that contains the parts system.
        
        :return     <QScrollArea>
        """
        return self._partsWidget
    
    def startEdit( self ):
        """
        Rebuilds the pathing based on the parts.
        """
        self._originalText = self.text()
        self.scrollWidget().hide()
        self.setFocus()
        self.selectAll()
    
    def rebuild( self ):
        """
        Rebuilds the parts widget with the latest text.
        """
        navitem = self.currentItem()
        if ( navitem ):
            navitem.initialize()
            
        self.setUpdatesEnabled(False)
        self.scrollWidget().show()
        self._originalText = ''
        
        partsw = self.partsWidget()
        for button in self._buttonGroup.buttons():
            self._buttonGroup.removeButton(button)
            button.close()
            button.setParent(None)
            button.deleteLater()
        
        # create the root button
        layout = partsw.layout()
        parts  = self.parts()
        
        button = QToolButton(partsw)
        button.setAutoRaise(True)
        button.setMaximumWidth(12)
        button.setArrowType(Qt.RightArrow)
        
        button.setProperty('path',          wrapVariant(''))
        button.setProperty('is_completer',  wrapVariant(True))
        last_button = button
            
        self._buttonGroup.addButton(button)
        layout.insertWidget(0, button)
        
        # check to see if we have a navigation model setup
        if ( self._navigationModel ):
            last_item = self._navigationModel.itemByPath(self.text())
            show_last =  last_item and last_item.rowCount() > 0
        else:
            show_last = False
        
        # load the navigation system
        count = len(parts)
        for i, part in enumerate(parts):
            path = self.separator().join(parts[:i+1])
            
            button = QToolButton(partsw)
            button.setAutoRaise(True)
            button.setText(part)
            
            if ( self._navigationModel ):
                item = self._navigationModel.itemByPath(path)
                if ( item ):
                    button.setIcon(item.icon())
                    button.setToolButtonStyle(Qt.ToolButtonTextBesideIcon)
            
            button.setProperty('path',         wrapVariant(path))
            button.setProperty('is_completer', wrapVariant(False))
            
            self._buttonGroup.addButton(button)
            layout.insertWidget((i * 2) + 1, button)
            
            # determine if we should show the final button
            if ( show_last or i < (count - 1) ):
                button = QToolButton(partsw)
                button.setAutoRaise(True)
                button.setMaximumWidth(12)
                button.setArrowType(Qt.RightArrow)
                
                button.setProperty('path',          wrapVariant(path))
                button.setProperty('is_completer',  wrapVariant(True))
            
                self._buttonGroup.addButton(button)
                layout.insertWidget((i * 2) + 2, button)
                
                last_button = button
        
        if ( self.scrollWidget().width() < partsw.width() ):
            self.scrollParts(partsw.width() - self.scrollWidget().width())
            
        self.setUpdatesEnabled(True)
        self.navigationChanged.emit()
    
    def resizeEvent( self, event ):
        """
        Resizes the current widget and its parts widget.
        
        :param      event | <QResizeEvent>
        """
        super(XNavigationEdit, self).resizeEvent(event)
        
        w = self.width()
        h = self.height()
        
        self._scrollWidget.resize(w - 4, h - 4)
        
        if ( self._scrollWidget.width() < self._partsWidget.width() ):
           self.scrollParts( self._partsWidget.width() - self._scrollWidget.width() )
    
    def scrollParts( self, amount ):
        """
        Scrolls the parts to offset the scrolling amount.
        
        :param      amount | <int>
        """
        change = self._scrollAmount - amount
        self._partsWidget.scroll(change, 0)
        self._scrollAmount = amount
    
    def scrollWidget( self ):
        """
        Returns the scrolling widget.
        
        :return     <QScrollArea>
        """
        return self._scrollWidget
    
    def separator( self ):
        """
        Returns the separation character that is used for this edit.
        
        :return     <str>
        """
        return self._separator
    
    def setTopLevelItems( self, items ):
        """
        Initializes the navigation system to start with the inputed root \
        item.
        
        :param      item | <XNavigationItem>
        """
        if ( not self._navigationModel ):
            self.setNavigationModel(XNavigationModel(self))
        
        self._navigationModel.setTopLevelItems(items)
    
    def setNavigationModel( self, model ):
        """
        Sets the navigation model for this edit.
        
        :param      model | <XNavigationModel>
        """
        self._navigationModel = model
        self._completerTree.setModel(model)
        
        if ( model ):
            model.setSeparator(self.separator())
            completer = XNavigationCompleter(model, self)
            self.setCompleter(completer)
            completer.popup().installEventFilter(self)
        else:
            self.setCompleter(None)
        
        self.rebuild()
    
    def setParts( self, parts ):
        """
        Sets the path for this edit widget by providing the parts to the path.
        
        :param      parts | [<str>, ..]
        """
        self.setText(self.separator().join(map(str, parts)))
    
    def setSeparator( self, separator ):
        """
        Sets the separator to the inputed character.
        
        :param      separator | <str>
        """
        self._separator = separator
        if ( self._navigationModel ):
            self._navigationModel.setSeparator(separator)
        self.rebuild()
    
    def setText( self, text ):
        """
        Sets the text for this edit to the inputed text.
        
        :param      text | <str>
        """
        super(XNavigationEdit, self).setText(text)
        
        self.scrollWidget().show()
        if ( text == '' or self._originalText != text ):
            self.rebuild()
Пример #16
0
class XQueryBuilderWidget(QWidget):
    """ """
    saveRequested   = Signal()
    resetRequested  = Signal()
    cancelRequested = Signal()
    
    def __init__( self, parent = None ):
        super(XQueryBuilderWidget, self).__init__( parent )
        
        # load the user interface
        projexui.loadUi(__file__, self)
        
        self.setMinimumWidth(470)
        
        # define custom properties
        self._rules           = {}
        self._defaultQuery    = []
        self._completionTerms = []
        self._minimumCount    = 1
        
        # set default properties
        self._container = QWidget(self)
        layout = QVBoxLayout()
        layout.setContentsMargins(0, 0, 0, 0)
        layout.setSpacing(2)
        layout.addStretch(1)
        self._container.setLayout(layout)
        self.uiQueryAREA.setWidget(self._container)
        
        # create connections
        self.uiResetBTN.clicked.connect(  self.emitResetRequested )
        self.uiSaveBTN.clicked.connect(   self.emitSaveRequested )
        self.uiCancelBTN.clicked.connect( self.emitCancelRequested )
        
        self.resetRequested.connect(      self.reset )
    
    def addLineWidget( self, query = None ):
        """
        Adds a new line widget to the system with the given values.
        
        :param      query | (<str> term, <str> operator, <str> vlaue) || None
        """
        widget = XQueryLineWidget(self)
        widget.setTerms(sorted(self._rules.keys()))
        widget.setQuery(query)
        
        index = self._container.layout().count() - 1
        self._container.layout().insertWidget(index, widget)
        
        widget.addRequested.connect(     self.addLineWidget )
        widget.removeRequested.connect(  self.removeLineWidget )
        
        # update the remove enabled options for these widgets
        self.updateRemoveEnabled()
    
    def addRule( self, rule ):
        """
        Adds a rule to the system.
        
        :param      rule | <XQueryRule>
        """
        self._rules[rule.term()] = rule
        self.updateRules()
    
    def clear( self ):
        """
        Clears out all the widgets from the system.
        """
        for lineWidget in self.lineWidgets():
            lineWidget.setParent(None)
            lineWidget.deleteLater()
    
    def completionTerms( self ):
        """
        Returns the list of terms that will be used as a global override
        for completion terms when the query rule generates a QLineEdit instance.
        
        :return     [<str>, ..]
        """
        return self._completionTerms
    
    def count( self ):
        """
        Returns the count of the line widgets in the system.
        
        :return     <int>
        """
        return len(self.lineWidgets())
    
    def currentQuery( self ):
        """
        Returns the current query string for this widget.
        
        :return     [(<str> term, <str> operator, <str> value), ..]
        """
        widgets = self.lineWidgets()
        output = []
        for widget in widgets:
            output.append(widget.query())
        return output
    
    def defaultQuery( self ):
        """
        Returns the default query for the system.
        
        :return     [(<str> term, <str> operator, <str> value), ..]
        """
        return self._defaultQuery
    
    def keyPressEvent( self, event ):
        """
        Emits the save requested signal for this builder for when the enter
        or return press is clicked.
        
        :param      event | <QKeyEvent>
        """
        if ( event.key() in (Qt.Key_Enter, Qt.Key_Return) ):
            self.emitSaveRequested()
        
        super(XQueryBuilderWidget, self).keyPressEvent(event)
    
    def emitCancelRequested( self ):
        """
        Emits the cancel requested signal.
        """
        if ( not self.signalsBlocked() ):
            self.cancelRequested.emit()
            
    def emitResetRequested( self ):
        """
        Emits the reste requested signal.
        """
        if ( not self.signalsBlocked() ):
            self.resetRequested.emit()
            
    def emitSaveRequested( self ):
        """
        Emits the save requested signal.
        """
        if ( not self.signalsBlocked() ):
            self.saveRequested.emit()
    
    def findRule( self, term ):
        """
        Looks up a rule by the inputed term.
        
        :param      term | <str>
        
        :return     <XQueryRule> || None
        """
        return self._rules.get(nativestring(term))
    
    def removeLineWidget( self, widget ):
        """
        Removes the line widget from the query.
        
        :param      widget | <XQueryLineWidget>
        """
        widget.setParent(None)
        widget.deleteLater()
        
        self.updateRemoveEnabled()
    
    def minimumCount( self ):
        """
        Defines the minimum number of query widgets that are allowed.
        
        :return     <int>
        """
        return self._minimumCount
    
    def lineWidgets( self ):
        """
        Returns a list of line widgets for this system.
        
        :return     [<XQueryLineWidget>, ..]
        """
        return self.findChildren(XQueryLineWidget)
    
    def reset( self ):
        """
        Resets the system to the default query.
        """
        self.setCurrentQuery(self.defaultQuery())
    
    def setCompletionTerms( self, terms ):
        """
        Sets the list of terms that will be used as a global override
        for completion terms when the query rule generates a QLineEdit instance.
        
        :param     terms | [<str>, ..]
        """
        self._completionTerms = terms
    
    def setCurrentQuery( self, query ):
        """
        Sets the query for this system to the inputed query.
        
        :param      query | [(<str> term, <str> operator, <str> value), ..]
        """
        self.clear()
        
        for entry in query:
            self.addLineWidget(entry)
        
        # make sure we have the minimum number of widgets
        for i in range(self.minimumCount() - len(query)):
            self.addLineWidget()
    
    def setDefaultQuery( self, query ):
        """
        Sets the default query that will be used when the user clicks on the \
        reset button or the reset method is called.
        
        :param      query | [(<str> term, <str> operator, <str> value), ..]
        """
        self._defaultQuery = query[:]
    
    def setMinimumCount( self, count ):
        """
        Sets the minimum number of line widgets that are allowed at any \
        given time.
        
        :param      count | <int>
        """
        self._minimumCount = count
    
    def setRules( self, rules ):
        """
        Sets all the rules for this builder.
        
        :param      rules | [<XQueryRule>, ..]
        """
        if ( type(rules) in (list, tuple) ):
            self._rules = dict([(x.term(), x) for x in rules])
            self.updateRules()
            return True
            
        elif ( type(rules) == dict ):
            self._rules = rules.copy()
            self.updateRules()
            return True
            
        else:
            return False
    
    def setTerms( self, terms ):
        """
        Sets a simple rule list by accepting a list of strings for terms.  \
        This is a convenience method for the setRules method.
        
        :param      rules | [<str> term, ..]
        """
        return self.setRules([XQueryRule(term = term) for term in terms])
    
    def updateRemoveEnabled( self ):
        """
        Updates the remove enabled baesd on the current number of line widgets.
        """
        lineWidgets = self.lineWidgets()
        count       = len(lineWidgets)
        state       = self.minimumCount() < count
        
        for widget in lineWidgets:
            widget.setRemoveEnabled(state)
    
    def updateRules( self ):
        """
        Updates the query line items to match the latest rule options.
        """
        terms = sorted(self._rules.keys())
        for child in self.lineWidgets():
            child.setTerms(terms)