Exemplo n.º 1
0
class XCurrencySpinBox(QDoubleSpinBox):
    def __init__(self, parent):
        super(XCurrencySpinBox, self).__init__(parent)

        # define custom properties
        self._currency = 'USD'

        # set default values
        self.setSingleStep(10)

    def currency(self):
        """
        Returns the currency for this widget.
        
        :return     <str>
        """
        return self._currency

    @Slot(str)
    def setCurrency(self, currency):
        """
        Sets the currency for this widget.
        
        :param      currency | <str>
        """
        self._currency = currency
        self.setValue(self.value())

    def textFromValue(self, value):
        """
        Returns the text for this widgets value.
        
        :param      value | <float>
        
        :return     <str>
        """
        return projex.money.toString(value, self.currency())

    def valueFromText(self, text):
        """
        Returns the value for this widgets text.
        
        :param      text | <str>
        
        :return     <float>
        """
        value, currency = projex.money.fromString(text)

        return value

    x_currency = Property(str, currency, setCurrency)
Exemplo n.º 2
0
class XOrbGridEdit(QWidget):
    """ """
    __designer_group__ = 'ProjexUI - ORB'
    
    def __init__( self, parent = None ):
        super(XOrbGridEdit, self).__init__( parent )
        
        # load the user interface
        projexui.loadUi(__file__, self)
        
        # define custom properties
        self._queryWidget = XOrbQueryWidget(self)
        
        self.uiSaveBTN.hide()
        self.uiSearchTXT.setIconSize(QSize(28, 28))
        self.uiSearchTXT.addButton(self.uiQueryBTN)
        
        self.uiQueryBTN.setCentralWidget(self._queryWidget)
        self.uiQueryBTN.setDefaultAnchor(XPopupWidget.Anchor.TopRight)
        popup = self.uiQueryBTN.popupWidget()
        popup.setShowTitleBar(False)

        # set default properties
        self.uiRecordTREE.setUserGroupingEnabled(False)
        self.uiRecordTREE.setGroupingActive(False)
        self.uiRecordTREE.setEditable(False)
        self.uiRecordTREE.setPageSize(50)
        self.uiRecordTREE.setTabKeyNavigation(True)
        
        # create connections
        self.uiRefreshBTN.clicked.connect(self.refresh)
        self.uiSaveBTN.clicked.connect(self.commit)
        self.uiQueryBTN.popupAboutToShow.connect(self.loadQuery)
        self.uiQueryBTN.popupAccepted.connect(self.assignQuery)
        
        popup.resetRequested.connect(self._queryWidget.reset)
    
    def addWidget(self, widget, align=Qt.AlignLeft, before=None):
        """
        Adds a widget to the grid edit's toolbar with the given
        alignment.
        
        :param      widget | <QtGui.QWidget>
                    align  | <QtCore.Qt.Alignment>
        """
        # self.uiToolbarLAYOUT points to a deleted C/C++ object in PySide...
        layout = self.findChild(QHBoxLayout, 'uiToolbarLAYOUT')
        
        if before is not None:
            index = None
            for i in range(layout.count()):
                if layout.itemAt(i).widget() == before:
                    index = i
                    break
            
            if index is not None:
                layout.insertWidget(index, widget)
            else:
                layout.addWidget(widget)
            
        if align == Qt.AlignLeft:
            layout.insertWidget(0, widget)
        else:
            layout.addWidget(widget)
    
    def autoloadPages(self):
        """
        Returns whether or not to automatically load pages for this edit.
        
        :sa     XOrbTreeWidget.autoloadPages
        
        :return     <bool>
        """
        return self.uiRecordTREE.autoloadPages()
    
    def assignQuery(self):
        """
        Assigns the query from the query widget to the edit.
        """
        self.uiRecordTREE.setQuery(self._queryWidget.query(), autoRefresh=True)
    
    def currentRecord(self):
        """
        Returns the current record based on the user selection in the
        tree.
        
        :return     <orb.Table> || None
        """
        return self.uiRecordTREE.currentRecord()
    
    def commit(self):
        """
        Commits changes stored in the interface to the database.
        """
        self.uiRecordTREE.commit()
    
    def isEditable(self):
        """
        Returns whether or not this grid edit is editable.
        
        :return     <bool>
        """
        return self.uiRecordTREE.isEditable()
    
    def isPaged(self):
        """
        Returns whether or not to pages the results from the database query.
        
        :sa     XOrbTreeWidget.isPaged
        
        :param      state | <bool>
        """
        return self.uiRecordTREE.isPaged()
    
    def loadQuery(self):
        """
        Loads the query for the query widget when it is being shown.
        """
        self._queryWidget.setQuery(self.query())
    
    def pageSize(self):
        """
        Returns the number of records that should be loaded per page.
        
        :sa     XOrbTreeWidget.pageSize
        
        :return     <int>
        """
        return self.uiRecordTREE.pageSize()
    
    def query(self):
        """
        Returns the query that is being represented by the current results.
        
        :return     <orb.Query>
        """
        return self.uiRecordTREE.query()
    
    def records(self):
        """
        Returns the records that are currently assigned to this widget.
        
        :return     <orb.RecordSet>
        """
        return self.uiRecordTREE.records()
    
    def refresh(self):
        """
        Commits changes stored in the interface to the database.
        """
        table = self.tableType()
        if table:
            table.markTableCacheExpired()
        
        self.uiRecordTREE.searchRecords(self.uiSearchTXT.text())
    
    def restoreXml(self, xml):
        """
        Restores the settings for this edit from xml.
        
        :param      xml | <xml.etree.ElementTree>
        """
        self.uiRecordTREE.restoreXml(xml.find('tree'))
        
        # restore the query
        xquery = xml.find('query')
        if xquery is not None:
            self.setQuery(Q.fromXml(xquery[0]))
    
    def saveXml(self, xml):
        """
        Saves the settings for this edit to the xml parent.
        
        :param      xparent | <xml.etree.ElementTree>
        """
        # save grouping
        xtree = ElementTree.SubElement(xml, 'tree')
        self.uiRecordTREE.saveXml(xtree)
        
        # save the query
        query = self.query()
        if query:
            query.toXml(ElementTree.SubElement(xml, 'query'))
    
    def searchWidget(self):
        """
        Returns the search text edit for this grid edit.
        
        :return     <XLineEdit>
        """
        return self.uiSearchTXT
    
    def setAutoloadPages(self, state):
        """
        Sets whether or not to automatically load pages for this edit.
        
        :sa     XOrbTreeWidget.setAutoloadPages
        
        :param      state | <bool>
        """
        return self.uiRecordTREE.setAutoloadPages(state)
    
    def setCurrentRecord(self, record):
        """
        Sets the current record based on the user selection in the
        tree.
        
        :param      record | <orb.Table> || None
        """
        self.uiRecordTREE.setCurrentRecord(record)
    
    def setEditable(self, state):
        """
        Sets the editable state for this grid widget.
        
        :param      state | <bool>
        """
        self.uiRecordTREE.setEditable(state)
        self.uiSaveBTN.setVisible(state)
    
    def setQuery(self, query, autoRefresh=True):
        """
        Sets the query for this edit to the inputed query.
        
        :param      query | <orb.Query>
        """
        self.uiRecordTREE.setQuery(query, autoRefresh=autoRefresh)
    
    def setPaged(self, state):
        """
        Sets whether or not to pages the results from the database query.
        
        :sa     XOrbTreeWidget.setPaged
        
        :param      state | <bool>
        """
        return self.uiRecordTREE.setPaged(state)
    
    def setPageSize(self, size):
        """
        Sets the number of records that should be loaded per page.
        
        :sa     XOrbTreeWidget.setPageSize
        
        :param      size | <int>
        """
        return self.uiRecordTREE.setPageSize(size)
    
    def setRecords(self, records):
        """
        Sets the records for this widget to the inputed records.
        
        :param      records | [<orb.Table>, ..] || <orb.RecordSet>
        """
        self.uiRecordTREE.setRecords(records)
    
    def setTableType(self, tableType, autoRefresh=True):
        """
        Sets the table type associated with this edit.
        
        :param      tableType | <subclass of orb.Table>
        """
        self.uiRecordTREE.setTableType(tableType)
        self._queryWidget.setTableType(tableType)
        
        if autoRefresh:
            self.setQuery(Q())
    
    def setUserGroupingEnabled(self, state=True):
        """
        Sets whether or not to allow the user to manipulating grouping.
        
        :param      state | <bool>
        """
        self.uiRecordTREE.setUserGroupingEnabled(state)
    
    def tableType(self):
        """
        Returns the table type associated with this edit.
        
        :return     <subclass of orb.Table>
        """
        return self.uiRecordTREE.tableType()
    
    def treeWidget(self):
        """
        Returns the tree widget that is for editing records for this grid.
        
        :return     <XOrbTreeWidget>
        """
        return self.uiRecordTREE
    
    def userGroupingEnabled(self):
        """
        Returns whether or not user grouping is enabled.
        
        :return     <bool>
        """
        return self.uiRecordTREE.userGroupingEnabled()
    
    x_autoloadPages = Property(bool, autoloadPages, setAutoloadPages)
    x_paged = Property(bool, isPaged, setPaged)
    x_pageSize = Property(int, pageSize, setPageSize)
    x_editable = Property(bool, isEditable, setEditable)
    x_userGroupingEnabled = Property(bool, userGroupingEnabled,
                                           setUserGroupingEnabled)
Exemplo n.º 3
0
class XTextEdit(QTextEdit):
    focusEntered = Signal()
    focusChanged = Signal(bool)
    focusExited = Signal()
    returnPressed = Signal()
    textEntered = Signal(str)
    htmlEntered = Signal(str)
    
    def __init__(self, parent=None):
        super(XTextEdit, self).__init__(parent)
        
        # define custom properties
        self._autoResizeToContents = False
        self._hint = ''
        self._encoding = 'utf-8'
        self._tabsAsSpaces = False
        self._requireShiftForNewLine = False
        self._richTextEditEnabled = True
        
        palette = self.palette()
        self._hintColor = palette.color(palette.AlternateBase).darker(130)
    
    def acceptText(self):
        """
        Emits the editing finished signals for this widget.
        """
        if not self.signalsBlocked():
            self.textEntered.emit(self.toPlainText())
            self.htmlEntered.emit(self.toHtml())
            self.returnPressed.emit()
    
    def autoResizeToContents(self):
        """
        Returns whether or not this text edit should automatically resize
        itself to fit its contents.
        
        :return     <bool>
        """
        return self._autoResizeToContents
    
    @Slot()
    def clear(self):
        """
        Clears the text for this edit and resizes the toolbar information.
        """
        super(XTextEdit, self).clear()
        
        if self.autoResizeToContents():
            self.resizeToContents()
    
    def encoding(self):
        """
        Returns the encoding format that will be used for this text edit.  All
        text that is pasted into this edit will be automatically converted
        to this format.
        
        :return     <str>
        """
        return self._encoding
    
    def focusInEvent(self, event):
        """
        Processes when this widget recieves focus.
        
        :param      event | <QFocusEvent>
        """
        if not self.signalsBlocked():
            self.focusChanged.emit(True)
            self.focusEntered.emit()
        
        return super(XTextEdit, self).focusInEvent(event)
    
    def focusOutEvent(self, event):
        """
        Processes when this widget loses focus.
        
        :param      event | <QFocusEvent>
        """
        if not self.signalsBlocked():
            self.focusChanged.emit(False)
            self.focusExited.emit()
        
        return super(XTextEdit, self).focusOutEvent(event)
    
    def hint( self ):
        """
        Returns the hint that will be rendered for this tree if there are no
        items defined.
        
        :return     <str>
        """
        return self._hint
    
    def hintColor( self ):
        """
        Returns the color used for the hint rendering.
        
        :return     <QColor>
        """
        return self._hintColor
    
    def isRichTextEditEnabled(self):
        """
        Returns whether or not this widget should accept rich text or not.
        
        :return     <bool>
        """
        return self._richTextEditEnabled
    
    def keyPressEvent(self, event):
        """
        Processes user input when they enter a key.
        
        :param      event | <QKeyEvent>
        """
        # emit the return pressed signal for this widget
        if event.key() in (Qt.Key_Return, Qt.Key_Enter) and \
           event.modifiers() == Qt.ControlModifier:
            self.acceptText()
            event.accept()
            return
        
        elif event.key() == Qt.Key_Tab:
            if self.tabsAsSpaces():
                count = 4 - (self.textCursor().columnNumber() % 4)
                self.insertPlainText(' ' * count)
                event.accept()
                return
        
        elif event.key() == Qt.Key_V and event.modifiers() == Qt.ControlModifier:
            self.paste()
            event.accept()
            return
        
        super(XTextEdit, self).keyPressEvent(event)
        
        if self.autoResizeToContents():
            self.resizeToContents()
    
    def paintEvent(self, event):
        """
        Overloads the paint event to support rendering of hints if there are
        no items in the tree.
        
        :param      event | <QPaintEvent>
        """
        super(XTextEdit, self).paintEvent(event)
        
        if self.document().isEmpty() and self.hint():
            text    = self.hint()
            rect    = self.rect()
            
            # modify the padding on the rect
            rect.setX(4)
            rect.setY(4)
            align = int(Qt.AlignLeft | Qt.AlignTop)
            
            # setup the coloring options
            clr = self.hintColor()
            
            # paint the hint
            painter = QPainter(self.viewport())
            painter.setPen(clr)
            painter.drawText(rect, align | Qt.TextWordWrap, text)
    
    @Slot()
    def paste(self):
        """
        Pastes text from the clipboard into this edit.
        """
        html = QApplication.clipboard().text()
        if not self.isRichTextEditEnabled():
            self.insertPlainText(projex.text.toAscii(html))
        else:
            super(XTextEdit, self).paste()
    
    def requireShiftForNewLine(self):
        """
        Returns whether or not the shift modifier is required for new lines.
        When this is True, then Return/Enter key presses will not create
        new lines in the edit, but instead trigger the returnPressed,
        textEntered and htmlEntered signals.
        
        :return     <bool>
        """
        return self._requireShiftForNewLine
    
    def resizeEvent(self, event):
        """
        Processes when this edit has been resized.
        
        :param      event | <QResizeEvent>
        """
        super(XTextEdit, self).resizeEvent(event)
        
        if self.autoResizeToContents():
            self.resizeToContents()
    
    @Slot()
    def resizeToContents(self):
        """
        Resizes this widget to fit the contents of its text.
        """
        doc = self.document()
        h = doc.documentLayout().documentSize().height()
        self.setFixedHeight(h + 4)
    
    def setAutoResizeToContents(self, state):
        """
        Sets whether or not this text edit should automatically resize itself
        to fit its contents.
        
        :param      state | <bool>
        """
        self._autoResizeToContents = state
        if state:
            self.resizeToContents()
    
    def setEncoding(self, encoding):
        """
        Sets the encoding format that will be used for this text edit.  All
        text that is pasted into this edit will be automatically converted
        to this format.
        
        :param      encoding | <str>
        """
        self._encoding = encoding
    
    def setHint(self, hint):
        """
        Sets the hint text that will be rendered when no items are present.
        
        :param      hint | <str>
        """
        self._hint = hint
    
    def setHintColor(self, color):
        """
        Sets the color used for the hint rendering.
        
        :param      color | <QColor>
        """
        self._hintColor = QColor(color)
    
    def setRequireShiftForNewLine(self, state):
        """
        Sets whether or not the shift modifier is required for new lines.
        When this is True, then Return/Enter key presses will not create
        new lines in the edit, but instead trigger the returnPressed,
        textEntered and htmlEntered signals.
        
        :param     state | <bool>
        """
        self._requireShiftForNewLine = state
    
    def setRichTextEditEnabled(self, state):
        """
        Sets whether or not rich text editing is enabled for this editor.
        
        :param      state | <bool>
        """
        self._richTextEditEnabled = state
    
    def setTabsAsSpaces(self, state):
        """
        Sets whether or not tabs as spaces are used instead of tab characters.
        
        :param      state | <bool>
        """
        self._tabsAsSpaces = state
    
    def setText(self, text):
        """
        Sets the text for this instance to the inputed text.
        
        :param      text | <str>
        """
        super(XTextEdit, self).setText(projex.text.toAscii(text))
    
    def tabsAsSpaces(self):
        """
        Returns whether or not tabs as spaces are being used.
        
        :return     <bool>
        """
        return self._tabsAsSpaces
    
    @classmethod
    def getText(cls,
                parent=None,
                windowTitle='Get Text',
                label='',
                text='',
                plain=True,
                wrapped=True):
        """
        Prompts the user for a text entry using the text edit class.
        
        :param      parent | <QWidget>
                    windowTitle | <str>
                    label       | <str>
                    text        | <str>
                    plain       | <bool> | return plain text or not
        
        :return     (<str> text, <bool> accepted)
        """
        # create the dialog
        dlg = QDialog(parent)
        dlg.setWindowTitle(windowTitle)
        
        # create the layout
        layout = QVBoxLayout()
        
        # create the label
        if label:
            lbl = QLabel(dlg)
            lbl.setText(label)
            layout.addWidget(lbl)
        
        # create the widget
        widget = cls(dlg)
        widget.setText(text)
        
        if not wrapped:
            widget.setLineWrapMode(XTextEdit.NoWrap)
        
        layout.addWidget(widget)
        
        # create the buttons
        btns = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel,
                                Qt.Horizontal,
                                dlg)
        layout.addWidget(btns)
        
        dlg.setLayout(layout)
        dlg.adjustSize()
        
        # create connections
        btns.accepted.connect(dlg.accept)
        btns.rejected.connect(dlg.reject)
        
        if dlg.exec_():
            if plain:
                return (widget.toPlainText(), True)
            else:
                return (widget.toHtml(), True)
        else:
            return ('', False)
        
    
    x_autoResizeToContents = Property(bool,
                                      autoResizeToContents,
                                      setAutoResizeToContents)
    
    x_encoding = Property(str, encoding, setEncoding)
    
    x_requireShiftForNewLine = Property(bool,
                                      requireShiftForNewLine,
                                      setRequireShiftForNewLine)
    
    x_hint = Property(str, hint, setHint)
    x_tabsAsSpaces = Property(bool, tabsAsSpaces, setTabsAsSpaces)
    x_richTextEditEnabled = Property(bool, isRichTextEditEnabled, setRichTextEditEnabled)
Exemplo n.º 4
0
class XOrbBrowserWidget(QWidget):
    """ """
    __designer_group__ = 'ProjexUI - ORB'

    currentRecordChanged = Signal()
    queryChanged = Signal(PyObject)  # orb.Query
    recordDoubleClicked = Signal(PyObject)  # orb.Table

    GroupByAdvancedKey = '__ADVANCED__'
    Mode = enum('Detail', 'Card', 'Thumbnail')

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

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

        # define custom properties
        self._hint = ''
        self._query = Q()
        self._advancedGrouping = []
        self._records = RecordSet()
        self._groupBy = XOrbBrowserWidget.GroupByAdvancedKey
        self._factory = XOrbBrowserFactory()
        self._queryWidget = XOrbQueryWidget(self, self._factory)
        self._thumbnailSize = QSize(128, 128)

        # set default properties
        self.uiSearchTXT.addButton(self.uiQueryBTN)
        self.uiQueryBTN.setCentralWidget(self._queryWidget)
        self.uiThumbLIST.installEventFilter(self)

        self.uiQueryACT.setShortcutContext(Qt.WidgetWithChildrenShortcut)
        self.uiQueryBTN.setDefaultAction(self.uiQueryACT)

        self.uiViewModeWGT.addAction(self.uiDetailsACT)
        self.uiViewModeWGT.addAction(self.uiCardACT)
        self.uiViewModeWGT.addAction(self.uiThumbnailACT)

        # create connections
        self.uiGroupOptionsBTN.clicked.connect(self.showGroupMenu)
        self.uiSearchTXT.returnPressed.connect(self.refresh)
        self.queryChanged.connect(self.refresh)
        self.uiGroupBTN.toggled.connect(self.refreshResults)

        self.uiDetailsACT.triggered.connect(self.setDetailMode)
        self.uiCardACT.triggered.connect(self.setCardMode)
        self.uiThumbnailACT.triggered.connect(self.setThumbnailMode)

        self.uiQueryBTN.popupAboutToShow.connect(self.prepareQuery)
        self.uiQueryBTN.popupAccepted.connect(self.acceptQuery)
        self.uiQueryBTN.popupReset.connect(self.resetQuery)

        self.uiRefreshBTN.clicked.connect(self.refresh)

        self.uiRecordsTREE.itemDoubleClicked.connect(self.handleDetailDblClick)
        self.uiRecordsTREE.currentItemChanged.connect(
            self.emitCurrentRecordChanged)

        self.uiThumbLIST.itemDoubleClicked.connect(self.handleThumbDblClick)
        self.uiThumbLIST.currentItemChanged.connect(
            self.emitCurrentRecordChanged)

        self.uiCardTREE.itemDoubleClicked.connect(self.handleCardDblClick)
        self.uiCardTREE.currentItemChanged.connect(
            self.emitCurrentRecordChanged)

    def _loadCardGroup(self, groupName, records, parent=None):
        if (not groupName):
            groupName = 'None'

        cards = self.cardWidget()
        factory = self.factory()

        # create the group item
        group_item = QTreeWidgetItem(parent, [groupName])
        font = group_item.font(0)
        font.setBold(True)
        font.setPointSize(font.pointSize() + 2)
        group_item.setFont(0, font)
        group_item.setFlags(Qt.ItemIsEnabled)

        # load sub-groups
        if (type(records) == dict):
            for subgroup, records in sorted(records.items()):
                self._loadCardGroup(subgroup, records, group_item)
        else:
            for record in records:
                widget = factory.createCard(cards, record)
                if (not widget):
                    continue

                widget.adjustSize()

                # create the card item
                item = QTreeWidgetItem(group_item)
                item.setSizeHint(0, QSize(0, widget.height()))
                cards.setItemWidget(item, 0, widget)

        group_item.setExpanded(True)

    def _loadThumbnailGroup(self, groupName, records):
        if (not groupName):
            groupName = 'None'

        widget = self.thumbnailWidget()
        factory = self.factory()

        # create the group item
        GroupListWidgetItem(groupName, widget)

        # load sub-groups
        if (type(records) == dict):
            for subgroup, records in sorted(records.items()):
                self._loadThumbnailGroup(subgroup, records)
        else:
            # create the record items
            for record in records:
                thumbnail = factory.thumbnail(record)
                text = factory.thumbnailText(record)
                RecordListWidgetItem(thumbnail, text, record, widget)

    def acceptQuery(self):
        """
        Accepts the changes made from the query widget to the browser.
        """
        self.setQuery(self._queryWidget.query())

    def advancedGrouping(self):
        """
        Returns the advanced grouping options for this widget.
        
        :return     [<str> group level, ..]
        """
        return ['[lastName::slice(0, 1)]']
        return self._advancedGrouping

    def cardWidget(self):
        """
        Returns the card widget for this browser.
        
        :return     <QTreeWidget>
        """
        return self.uiCardTREE

    def controlsWidget(self):
        """
        Returns the controls widget for this browser.  This is the widget that
        contains the various control mechanisms.
        
        :return     <QWidget>
        """
        return self._controlsWidget

    def currentGrouping(self):
        """
        Returns the current grouping for this widget.
        
        :return     [<str> group level, ..]
        """
        groupBy = self.groupBy()
        if (groupBy == XOrbBrowserWidget.GroupByAdvancedKey):
            return self.advancedGrouping()
        else:
            table = self.tableType()
            if (not table):
                return []

            for column in table.schema().columns():
                if (column.displayName() == groupBy):
                    return [column.name()]

            return []

    def currentRecord(self):
        """
        Returns the current record from this browser.
        
        :return     <orb.Table> || None
        """
        if (self.currentMode() == XOrbBrowserWidget.Mode.Detail):
            return self.detailWidget().currentRecord()

        elif (self.currentMode() == XOrbBrowserWidget.Mode.Thumbnail):
            item = self.thumbnailWidget().currentItem()
            if (isinstance(item, RecordListWidgetItem)):
                return item.record()
            return None

        else:
            item = self.uiCardTREE.currentItem()
            widget = self.uiCardTREE.itemWidget(item, 0)
            if (isinstance(widget, XAbstractCardWidget)):
                return widget.record()

            return None

    def currentMode(self):
        """
        Returns the current mode for this widget.
        
        :return     <XOrbBrowserWidget.Mode>
        """
        if (self.uiCardACT.isChecked()):
            return XOrbBrowserWidget.Mode.Card
        elif (self.uiDetailsACT.isChecked()):
            return XOrbBrowserWidget.Mode.Detail
        else:
            return XOrbBrowserWidget.Mode.Thumbnail

    def detailWidget(self):
        """
        Returns the tree widget used by this browser.
        
        :return     <XOrbTreeWidget>
        """
        return self.uiRecordsTREE

    def emitCurrentRecordChanged(self):
        """
        Emits the current record changed signal.
        """
        if (not self.signalsBlocked()):
            self.currentRecordChanged.emit()

    def emitRecordDoubleClicked(self, record):
        """
        Emits the record double clicked signal.
        
        :param      record | <orb.Table>
        """
        if (not self.signalsBlocked()):
            self.recordDoubleClicked.emit(record)

    def enabledModes(self):
        """
        Returns the binary value of the enabled modes.
        
        :return     <XOrbBrowserWidget.Mode>
        """
        output = 0
        for i, action in enumerate(
            (self.uiDetailsACT, self.uiCardACT, self.uiThumbnailACT)):
            if (action.isEnabled()):
                output |= int(math.pow(2, i))
        return output

    def eventFilter(self, object, event):
        """
        Processes resize events on the thumbnail widget to update the group
        items to force a proper sizing.
        
        :param      object | <QObject>
                    event  | <QEvent>
        
        :return     <bool> | consumed
        """
        if ( event.type() == event.Resize and \
             self.currentMode() == XOrbBrowserWidget.Mode.Thumbnail and \
             self.isGroupingActive() ):
            size = QSize(event.size().width() - 20, 22)
            for row in range(object.count()):
                item = object.item(row)
                if (isinstance(item, GroupListWidgetItem)):
                    item.setSizeHint(size)
        return False

    def factory(self):
        """
        Returns the factory assigned to this browser for generating card and
        thumbnail information for records.
        
        :return     <XOrbBrowserFactory>
        """
        return self._factory

    def groupBy(self):
        """
        Returns the group by key for this widget.  If GroupByAdvancedKey
        is returned, then the advanced grouping options will be used.  
        Otherwise, a column will be used for grouping.
        
        :return     <str>
        """
        return self._groupBy

    def handleCardDblClick(self, item):
        """
        Handles when a card item is double clicked on.
        
        :param      item | <QTreeWidgetItem>
        """
        widget = self.uiCardTREE.itemWidget(item, 0)
        if (isinstance(widget, XAbstractCardWidget)):
            self.emitRecordDoubleClicked(widget.record())

    def handleDetailDblClick(self, item):
        """
        Handles when a detail item is double clicked on.
        
        :param      item | <QTreeWidgetItem>
        """
        if (isinstance(item, XOrbRecordItem)):
            self.emitRecordDoubleClicked(item.record())

    def handleThumbDblClick(self, item):
        """
        Handles when a thumbnail item is double clicked on.
        
        :param      item | <QListWidgetItem>
        """
        if (isinstance(item, RecordListWidgetItem)):
            self.emitRecordDoubleClicked(item.record())

    def hint(self):
        """
        Returns the hint for this widget.
        
        :return     <str>
        """
        return self._hint

    def isGroupingActive(self):
        """
        Returns if the grouping is currently on or not.
        
        :return     <bool>
        """
        return self.uiGroupBTN.isChecked()

    def isModeEnabled(self, mode):
        """
        Returns whether or not the inputed mode is enabled.
        
        :param      mode | <XOrbBrowserWidget.Mode>
        
        :return     <bool>
        """
        return (self.enabledModes() & mode) != 0

    def modeWidget(self):
        """
        Returns the mode widget for this instance.
        
        :return     <projexui.widgets.xactiongroupwidget.XActionGroupWidget>
        """
        return self.uiViewModeWGT

    def prepareQuery(self):
        """
        Prepares the popup widget with the query data.
        """
        self._queryWidget.setQuery(self.query())

    def query(self):
        """
        Returns the fixed query that is assigned via programmatic means.
        
        :return     <orb.Query> || None
        """
        return self._query

    def queryWidget(self):
        """
        Returns the query building widget.
        
        :return     <XOrbQueryWidget>
        """
        return self._queryWidget

    def records(self):
        """
        Returns the record set for the current settings of this browser.
        
        :return     <orb.RecordSet>
        """
        if (self.isGroupingActive()):
            self._records.setGroupBy(self.currentGrouping())
        else:
            self._records.setGroupBy(None)
        return self._records

    def refresh(self):
        """
        Refreshes the interface fully.
        """
        self.refreshRecords()
        self.refreshResults()

    def refreshRecords(self):
        """
        Refreshes the records being loaded by this browser.
        """
        table_type = self.tableType()
        if (not table_type):
            self._records = RecordSet()
            return False

        search = str(self.uiSearchTXT.text())

        query = self.query().copy()
        terms, search_query = Q.fromSearch(search)

        if (search_query):
            query &= search_query

        self._records = table_type.select(where=query).search(terms)
        return True

    def refreshResults(self):
        """
        Joins together the queries from the fixed system, the search, and the
        query builder to generate a query for the browser to display.
        """
        if (self.currentMode() == XOrbBrowserWidget.Mode.Detail):
            self.refreshDetails()
        elif (self.currentMode() == XOrbBrowserWidget.Mode.Card):
            self.refreshCards()
        else:
            self.refreshThumbnails()

    def refreshCards(self):
        """
        Refreshes the results for the cards view of the browser.
        """
        cards = self.cardWidget()
        factory = self.factory()

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

        cards.setUpdatesEnabled(False)
        cards.blockSignals(True)

        cards.clear()
        QApplication.instance().processEvents()

        if (self.isGroupingActive()):
            grouping = self.records().grouped()
            for groupName, records in sorted(grouping.items()):
                self._loadCardGroup(groupName, records, cards)

        else:
            for record in self.records():
                widget = factory.createCard(cards, record)
                if (not widget):
                    continue

                widget.adjustSize()

                # create the card item
                item = QTreeWidgetItem(cards)
                item.setSizeHint(0, QSize(0, widget.height()))
                cards.setItemWidget(item, 0, widget)

        cards.setUpdatesEnabled(True)
        cards.blockSignals(False)

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

    def refreshDetails(self):
        """
        Refreshes the results for the details view of the browser.
        """
        # start off by filtering based on the group selection
        tree = self.uiRecordsTREE
        tree.blockSignals(True)
        tree.setRecordSet(self.records())
        tree.blockSignals(False)

    def refreshThumbnails(self):
        """
        Refreshes the thumbnails view of the browser.
        """
        # clear existing items
        widget = self.thumbnailWidget()
        widget.setUpdatesEnabled(False)
        widget.blockSignals(True)

        widget.clear()
        widget.setIconSize(self.thumbnailSize())

        factory = self.factory()

        # load grouped thumbnails (only allow 1 level of grouping)
        if (self.isGroupingActive()):
            grouping = self.records().grouped()
            for groupName, records in sorted(grouping.items()):
                self._loadThumbnailGroup(groupName, records)

        # load ungrouped thumbnails
        else:
            # load the records into the thumbnail
            for record in self.records():
                thumbnail = factory.thumbnail(record)
                text = factory.thumbnailText(record)
                RecordListWidgetItem(thumbnail, text, record, widget)

        widget.setUpdatesEnabled(True)
        widget.blockSignals(False)

    def resetQuery(self):
        """
        Resets the popup query widget's query information
        """
        self._queryWidget.clear()

    def setCardMode(self):
        """
        Sets the mode for this widget to the Card mode.
        """
        self.setCurrentMode(XOrbBrowserWidget.Mode.Card)

    def setCurrentMode(self, mode):
        """
        Sets the current mode for this widget to the inputed mode.  This will
        check against the valid modes for this browser and return success.
        
        :param      mode | <XOrbBrowserWidget.Mode>
        
        :return     <bool> | success
        """
        if (not self.isModeEnabled(mode)):
            return False

        if (mode == XOrbBrowserWidget.Mode.Detail):
            self.uiModeSTACK.setCurrentIndex(0)
            self.uiDetailsACT.setChecked(True)
        elif (mode == XOrbBrowserWidget.Mode.Card):
            self.uiModeSTACK.setCurrentIndex(1)
            self.uiCardACT.setChecked(True)
        else:
            self.uiModeSTACK.setCurrentIndex(2)
            self.uiThumbnailACT.setChecked(True)

        self.refreshResults()

        return True

    def setCurrentRecord(self, record):
        """
        Sets the current record for this browser to the inputed record.
        
        :param      record | <orb.Table> || None
        """
        mode = self.currentMode()
        if (mode == XOrbBrowserWidget.Mode.Detail):
            self.detailWidget().setCurrentRecord(record)

        elif (mode == XOrbBrowserWidget.Mode.Thumbnail):
            thumbs = self.thumbnailWidget()
            for row in range(thumbs.count()):
                item = thumbs.item(row)
                if ( isinstance(item, RecordListWidgetItem) and \
                     item.record() == item ):
                    thumbs.setCurrentItem(item)
                    break

    def setDetailMode(self):
        """
        Sets the mode for this widget to the Detail mode.
        """
        self.setCurrentMode(XOrbBrowserWidget.Mode.Detail)

    def setFactory(self, factory):
        """
        Sets the factory assigned to this browser for generating card and
        thumbnail information for records.
        
        :param      factory | <XOrbBrowserFactory>
        """
        self._factory = factory
        self._queryWidget.setFactory(factory)

    def setGroupByAdvanced(self):
        """
        Sets the groupBy key for this widget to GroupByAdvancedKey signaling 
        that the advanced user grouping should be used.
        """
        self.setGroupBy(XOrbBrowserWidget.GroupByAdvancedKey)

    def setGroupBy(self, groupBy):
        """
        Sets the group by key for this widget.  This should correspond to a 
        display name for the columns, or the GroupByAdvancedKey keyword.  It is
        recommended to use setGroupByAdvanced for setting advanced groupings.
        
        :param      groupBy | <str>
        """
        self._groupBy = groupBy

    def setGroupingActive(self, state):
        """
        Sets whether or not the grouping should be enabled for the widget.
        
        :param      state | <bool>
        """
        self.uiGroupBTN.setChecked(state)

    def setHint(self, hint):
        """
        Sets the hint for this widget.
        
        :param      hint | <str>
        """
        self._hint = hint
        self.detailWidget().setHint(hint)

    def setModeEnabled(self, mode, state):
        """
        Sets whether or not the mode should be enabled.
        
        :param      mode  | <XOrbBrowserWidget.Mode>
                    state | <bool>
        """
        if (mode == XOrbBrowserWidget.Mode.Detail):
            self.uiDetailsACT.setEnabled(state)
        elif (mode == XOrbBrowserWidget.Mode.Card):
            self.uiCardACT.setEnabled(state)
        else:
            self.uiThumbnailACT.setEnabled(state)

    def setQuery(self, query):
        """
        Sets the fixed lookup query for this widget to the inputed query.
        
        :param      query | <orb.Query>
        """
        self._query = query
        if (not self.signalsBlocked()):
            self.queryChanged.emit(query)

    def setTableType(self, tableType):
        """
        Sets the table type for this widget to the inputed type.
        
        :param      tableType | <orb.Table>
        """
        self.detailWidget().setTableType(tableType)
        self.queryWidget().setTableType(tableType)

    def setThumbnailMode(self):
        """
        Sets the mode for this widget to the thumbnail mode.
        """
        self.setCurrentMode(XOrbBrowserWidget.Mode.Thumbnail)

    def setThumbnailSize(self, size):
        """
        Sets the size that will be used for the thumbnails in this widget.
        
        :param      size | <QSize>
        """
        self._thumbnailSize = QSize(size)

    def showGroupMenu(self):
        """
        Displays the group menu to the user for modification.
        """
        group_active = self.isGroupingActive()
        group_by = self.groupBy()

        menu = XMenu(self)
        menu.setTitle('Grouping Options')
        menu.setShowTitle(True)
        menu.addAction('Edit Advanced Grouping')

        menu.addSeparator()

        action = menu.addAction('No Grouping')
        action.setCheckable(True)
        action.setChecked(not group_active)

        action = menu.addAction('Advanced')
        action.setCheckable(True)
        action.setChecked(group_by == self.GroupByAdvancedKey and group_active)
        if (group_by == self.GroupByAdvancedKey):
            font = action.font()
            font.setBold(True)
            action.setFont(font)

        menu.addSeparator()

        # add dynamic options from the table schema
        tableType = self.tableType()
        if (tableType):
            columns = tableType.schema().columns()
            columns.sort(key=lambda x: x.displayName())
            for column in columns:
                action = menu.addAction(column.displayName())
                action.setCheckable(True)
                action.setChecked(group_by == column.displayName()
                                  and group_active)

                if (column.displayName() == group_by):
                    font = action.font()
                    font.setBold(True)
                    action.setFont(font)

        point = QPoint(0, self.uiGroupOptionsBTN.height())
        action = menu.exec_(self.uiGroupOptionsBTN.mapToGlobal(point))

        if (not action):
            return
        elif (action.text() == 'Edit Advanced Grouping'):
            print 'edit advanced grouping options'
        elif (action.text() == 'No Grouping'):
            self.setGroupingActive(False)

        elif (action.text() == 'Advanced'):
            self.uiGroupBTN.blockSignals(True)
            self.setGroupBy(self.GroupByAdvancedKey)
            self.setGroupingActive(True)
            self.uiGroupBTN.blockSignals(False)

            self.refreshResults()

        else:
            self.uiGroupBTN.blockSignals(True)
            self.setGroupBy(str(action.text()))
            self.setGroupingActive(True)
            self.uiGroupBTN.blockSignals(False)

            self.refreshResults()

    def stackWidget(self):
        """
        Returns the stack widget linked with this browser.  This contains the
        different views linked with the view mode.
        
        :return     <QStackWidget>
        """
        return self.uiModeSTACK

    def tableType(self):
        """
        Returns the table type for this widget.
        
        :return     <orb.Table>
        """
        return self.detailWidget().tableType()

    def thumbnailSize(self):
        """
        Returns the size that will be used for displaying thumbnails for this
        widget.
        
        :return     <QSize>
        """
        return self._thumbnailSize

    def thumbnailWidget(self):
        """
        Returns the thumbnail widget for this widget.
        
        :return     <QListWidget>
        """
        return self.uiThumbLIST

    x_hint = Property(str, hint, setHint)
Exemplo n.º 5
0
class XGroupBox(QGroupBox):
    """
    Extends the base QGroupBox class to support some additional features like
    setting collapsing on toggle.
    """

    __designer_icon__ = projexui.resources.find('img/ui/groupbox.png')
    __designer_container__ = True

    def __init__(self, *args):
        super(XGroupBox, self).__init__(*args)

        self._collapsible = False
        self._collapsedHeight = 18
        self._inverted = False

        self.toggled.connect(self.matchCollapsedState)

    def collapsedHeight(self):
        """
        Returns the collapsed height for this object.
        
        :return     <int>
        """
        return self._collapsedHeight

    def isCollapsed(self):
        """
        Returns whether or not this group box is collapsed.
        
        :return     <bool>
        """
        if not self.isCollapsible():
            return False

        if self._inverted:
            return self.isChecked()
        return not self.isChecked()

    def isCollapsible(self):
        """
        Returns whether or not this group box is collapsiible.
        
        :return     <bool>
        """
        return self._collapsible

    def isInverted(self):
        """
        Returns whether or not this widget is in an inverted state, by which
        unchecking the group box will force it collapsed.
        
        :return     <bool>
        """
        return self._inverted

    def paintEvent(self, event):
        """
        Overloads the paint event for this group box if it is currently
        collpased.
        
        :param      event | <QPaintEvent>
        """
        if (self.isCollapsed()):
            self.setFlat(True)

        elif (self.isCollapsible()):
            self.setFlat(False)

        super(XGroupBox, self).paintEvent(event)

    def matchCollapsedState(self):
        """
        Matches the collapsed state for this groupbox.
        """
        collapsed = not self.isChecked()
        if self._inverted:
            collapsed = not collapsed

        if (not self.isCollapsible() or not collapsed):
            for child in self.children():
                if (not isinstance(child, QWidget)):
                    continue

                child.show()

            self.setMaximumHeight(MAX_INT)
            self.adjustSize()

            if (self.parent()):
                self.parent().adjustSize()
        else:
            self.setMaximumHeight(self.collapsedHeight())

            for child in self.children():
                if (not isinstance(child, QWidget)):
                    continue

                child.hide()

    def setCollapsed(self, state):
        """
        Sets whether or not this group box is collapsed.
        
        :param      state | <bool>
        """
        self.setCollapsible(True)
        if not self._inverted:
            self.setChecked(not state)
        else:
            self.setChecked(state)

    def setCollapsible(self, state):
        """
        Sets whether or not this groupbox will be collapsible when toggled.
        
        :param      state | <bool>
        """
        self._collapsible = state
        self.matchCollapsedState()

    def setCollapsedHeight(self, height):
        """
        Sets the height that will be used when this group box is collapsed.
        
        :param      height | <int>
        """
        self._collapsedHeight = height
        self.matchCollapsedState()

    def setInverted(self, state):
        """
        Sets whether or not to invert the check state for collapsing.
        
        :param      state | <bool>
        """
        collapsed = self.isCollapsed()
        self._inverted = state
        if self.isCollapsible():
            self.setCollapsed(collapsed)

    # create Qt properties
    x_collapsible = Property(bool, isCollapsible, setCollapsible)
    x_collapsed = Property(bool, isCollapsed, setCollapsed)
    x_collapsedHeight = Property(int, collapsedHeight, setCollapsedHeight)
    x_inverted = Property(bool, isInverted, setInverted)
Exemplo n.º 6
0
class XCommentEdit(XTextEdit):
    attachmentRequested = Signal()
    
    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)
    
    def addAttachment(self, title, attachment):
        """
        Adds an attachment to this comment.
        
        :param      title      | <str>
                    attachment | <variant>
        """
        self._attachments[title] = attachment
        self.resizeToContents()
    
    def attachments(self):
        """
        Returns a list of attachments that have been linked to this widget.
        
        :return     {<str> title: <variant> attachment, ..}
        """
        return self._attachments.copy()
    
    def attachButton(self):
        """
        Returns the attach button from the toolbar for this widget.
        
        :return     <QToolButton>
        """
        return self._attachButton
    
    @Slot()
    def clear(self):
        """
        Clears out this widget and its attachments.
        """
        # clear the attachment list
        self._attachments.clear()
        
        super(XCommentEdit, self).clear()
    
    def isToolbarVisible(self):
        """
        Returns whether or not the toolbar for this comment edit is currently
        visible to the user.
        
        :return     <bool>
        """
        return self._toolbar.isVisible()
    
    def keyPressEvent(self, event):
        if event.key() == Qt.Key_Escape:
            self.clear()
            event.accept()
        else:
            super(XCommentEdit, self).keyPressEvent(event)
    
    @Slot()
    def pickAttachment(self):
        """
        Prompts the user to select an attachment to add to this edit.
        """
        filename = QFileDialog.getOpenFileName(self.window(),
                                               'Select Attachment',
                                               '',
                                               'All Files (*.*)')
        
        if type(filename) == tuple:
            filename = str(filename[0])
        
        filename = str(filename)
        if filename:
            self.addAttachment(os.path.basename(filename), filename)
    
    def removeAttachment(self, title):
        """
        Removes the attachment from the given title.
        
        :param      title | <str>
        
        :return     <variant> | attachment
        """
        attachment = self._attachments.pop(str(title), None)
        
        if attachment:
            self.resizeToContents()
        
        return attachment
    
    def resizeEvent(self, event):
        super(XCommentEdit, self).resizeEvent(event)
        
        self._toolbar.resize(self.width() - 4, 30)
        edit = self._attachmentsEdit
        edit.resize(self.width() - 4, edit.height())
    
    def resizeToContents(self):
        """
        Resizes this toolbar based on the contents of its text.
        """
        if self._toolbar.isVisible():
            doc = self.document()
            h = doc.documentLayout().documentSize().height()
            
            offset = 34
            
            # update the attachments edit
            edit = self._attachmentsEdit
            if self._attachments:
                edit.move(2, self.height() - edit.height() - 31)
                edit.setTags(sorted(self._attachments.keys()))
                edit.show()
                
                offset = 34 + edit.height()
            else:
                edit.hide()
                offset = 34
            
            self.setFixedHeight(h + offset)
            self._toolbar.move(2, self.height() - 32)
        else:
            super(XCommentEdit, self).resizeToContents()
    
    def setAttachments(self, attachments):
        """
        Sets the attachments for this widget to the inputed list of attachments.
        
        :param      attachments | {<str> title: <variant> attachment, ..}
        """
        self._attachments = attachments
        self.resizeToContents()
    
    def setSubmitText(self, text):
        """
        Sets the submission text for this edit.
        
        :param      text | <str>
        """
        self._submitButton.setText(text)

    def setShowAttachments(self, state):
        """
        Sets whether or not to show the attachments for this edit.
        
        :param      state | <bool>
        """
        self._showAttachments = state
        self._attachAction.setVisible(state)

    def setToolbarVisible(self, state):
        """
        Sets whether or not the toolbar is visible.
        
        :param      state | <bool>
        """
        self._toolbar.setVisible(state)
        
        self.resizeToContents()
    
    def showAttachments(self):
        """
        Returns whether or not to show the attachments for this edit.
        
        :return     <bool>
        """
        return self._showAttachments
    
    def submitButton(self):
        """
        Returns the submit button for this edit.
        
        :return     <QPushButton>
        """
        return self._submitButton
    
    def submitText(self):
        """
        Returns the submission text for this edit.
        
        :return     <str>
        """
        return self._submitButton.text()

    def toolbar(self):
        """
        Returns the toolbar widget for this comment edit.
        
        :return     <QToolBar>
        """
        return self._toolbar
    
    x_showAttachments = Property(bool, showAttachments, setShowAttachments)
    x_submitText = Property(str, submitText, setSubmitText)
Exemplo n.º 7
0
class XToolBar(QToolBar):
    collapseToggled = Signal(bool)

    def __init__(self, *args):
        super(XToolBar, self).__init__(*args)

        # set custom properties
        self._collapseButton = None
        self._collapsable = True
        self._collapsed = True
        self._collapsedSize = 14
        self._autoCollapsible = False
        self._precollapseSize = None
        self._shadowed = False
        self._colored = False

        # set standard options
        self.layout().setSpacing(0)
        self.layout().setContentsMargins(1, 1, 1, 1)
        self.setMovable(False)
        self.clear()
        self.setMouseTracking(True)
        self.setOrientation(Qt.Horizontal)
        self.setCollapsed(False)

    def autoCollapsible(self):
        """
        Returns whether or not this toolbar is auto-collapsible.  When
        True, it will enter its collapsed state when the user hovers out
        of the bar.
        
        :return     <bool>
        """
        return self._autoCollapsible

    def clear(self):
        """
        Clears out this toolbar from the system.
        """
        # preserve the collapse button
        super(XToolBar, self).clear()

        # clears out the toolbar
        if self.isCollapsable():
            self._collapseButton = QToolButton(self)
            self._collapseButton.setAutoRaise(True)
            self._collapseButton.setSizePolicy(QSizePolicy.Expanding,
                                               QSizePolicy.Expanding)

            self.addWidget(self._collapseButton)
            self.refreshButton()

            # create connection
            self._collapseButton.clicked.connect(self.toggleCollapsed)

        elif self._collapseButton:
            self._collapseButton.setParent(None)
            self._collapseButton.deleteLater()
            self._collapseButton = None

    def count(self):
        """
        Returns the number of actions linked with this toolbar.
        
        :return     <int>
        """
        return len(self.actions())

    def collapseButton(self):
        """
        Returns the collapsing button for this toolbar.
        
        :return     <QToolButton>
        """
        return self._collapseButton

    def isCollapsable(self):
        """
        Returns whether or not this toolbar is collapsable.
        
        :return     <bool>
        """
        return self._collapsable

    def isCollapsed(self):
        """
        Returns whether or not this toolbar is in a collapsed state.
        
        :return     <bool>
        """
        return self._collapsed and self.isCollapsable()

    def isColored(self):
        """
        Returns whether or not to colorize the buttons on the toolbar
        when they are highlighted.
        
        :return     <bool>
        """
        return self._colored

    def isShadowed(self):
        """
        Returns whether or not to show this toolbar with shadows.
        
        :return     <bool>
        """
        return self._shadowed

    def refreshButton(self):
        """
        Refreshes the button for this toolbar.
        """
        collapsed = self.isCollapsed()
        btn = self._collapseButton

        if not btn:
            return

        btn.setMaximumSize(MAX_SIZE, MAX_SIZE)

        # set up a vertical scrollbar
        if self.orientation() == Qt.Vertical:
            btn.setMaximumHeight(12)
        else:
            btn.setMaximumWidth(12)

        icon = ''

        # collapse/expand a vertical toolbar
        if self.orientation() == Qt.Vertical:
            if collapsed:
                self.setFixedWidth(self._collapsedSize)
                btn.setMaximumHeight(MAX_SIZE)
                btn.setArrowType(Qt.RightArrow)
            else:
                self.setMaximumWidth(MAX_SIZE)
                self._precollapseSize = None
                btn.setMaximumHeight(12)
                btn.setArrowType(Qt.LeftArrow)

        else:
            if collapsed:
                self.setFixedHeight(self._collapsedSize)
                btn.setMaximumWidth(MAX_SIZE)
                btn.setArrowType(Qt.DownArrow)
            else:
                self.setMaximumHeight(1000)
                self._precollapseSize = None
                btn.setMaximumWidth(12)
                btn.setArrowType(Qt.UpArrow)

        for index in range(1, self.layout().count()):
            item = self.layout().itemAt(index)
            if not item.widget():
                continue

            if collapsed:
                item.widget().setMaximumSize(0, 0)
            else:
                item.widget().setMaximumSize(MAX_SIZE, MAX_SIZE)

        if not self.isCollapsable():
            btn.hide()
        else:
            btn.show()

    def resizeEvent(self, event):
        super(XToolBar, self).resizeEvent(event)

        if not self._collapsed:
            if self.orientation() == Qt.Vertical:
                self._precollapseSize = self.width()
            else:
                self._precollapseSize = self.height()

    def setAutoCollapsible(self, state):
        """
        Sets whether or not this toolbar is auto-collapsible.
        
        :param      state | <bool>
        """
        self._autoCollapsible = state

    def setCollapsed(self, state):
        """
        Sets whether or not this toolbar is in a collapsed state.
        
        :return     <bool> changed
        """
        if state == self._collapsed:
            return False

        self._collapsed = state
        self.refreshButton()

        if not self.signalsBlocked():
            self.collapseToggled.emit(state)

        return True

    def setCollapsable(self, state):
        """
        Sets whether or not this toolbar is collapsable.
        
        :param      state | <bool>
        """
        if self._collapsable == state:
            return

        self._collapsable = state
        self.clear()

    def setOrientation(self, orientation):
        """
        Sets the orientation for this toolbar to the inputed value, and \
        updates the contents margins and collapse button based on the vaule.
        
        :param      orientation | <Qt.Orientation>
        """
        super(XToolBar, self).setOrientation(orientation)
        self.refreshButton()

    def setShadowed(self, state):
        """
        Sets whether or not this toolbar is shadowed.
        
        :param      state | <bool>
        """
        self._shadowed = state
        if state:
            self._colored = False

        for child in self.findChildren(XToolButton):
            child.setShadowed(state)

    def setColored(self, state):
        """
        Sets whether or not this toolbar is shadowed.
        
        :param      state | <bool>
        """
        self._colored = state
        if state:
            self._shadowed = False

        for child in self.findChildren(XToolButton):
            child.setColored(state)

    def toggleCollapsed(self):
        """
        Toggles the collapsed state for this toolbar.
        
        :return     <bool> changed
        """
        return self.setCollapsed(not self.isCollapsed())

    x_shadowed = Property(bool, isShadowed, setShadowed)
    x_colored = Property(bool, isColored, setColored)
Exemplo n.º 8
0
class XSplitButton(QWidget):
    """
    ~~>[img:widgets/xsplitbutton.png]

    The XSplitButton class provides a simple class for creating a 
    multi-checkable tool button based on QActions and QActionGroups.

    === Example Usage ===

    |>>> from projexui.widgets.xsplitbutton import XSplitButton
    |>>> import projexui
    |
    |>>> # create the widget
    |>>> widget = projexui.testWidget(XSplitButton)
    |
    |>>> # add some actions (can be text or a QAction)
    |>>> widget.addAction('Day')
    |>>> widget.addAction('Month')
    |>>> widget.addAction('Year')
    |
    |>>> # create connections
    |>>> def printAction(act): print act.text()
    |>>> widget.actionGroup().triggered.connect(printAction)
    """
    
    __designer_icon__ = projexui.resources.find('img/ui/multicheckbox.png')
    
    clicked                 = Signal()
    currentActionChanged    = Signal(object)
    hovered                 = Signal(object)
    triggered               = Signal(object)
    
    def __init__( self, parent = None ):
        super(XSplitButton, self).__init__( parent )
        
        # define custom properties
        self._actionGroup   = QActionGroup(self)
        self._padding       = 5
        self._cornerRadius  = 10
        #self._currentAction = None
        self._checkable     = True
        
        # set default properties
        layout = QBoxLayout(QBoxLayout.LeftToRight)
        layout.setContentsMargins(0, 0, 0, 0)
        layout.setSpacing(0)
        self.setSizePolicy(QSizePolicy.Preferred, QSizePolicy.Preferred)
        self.setLayout(layout)
        self.clear()
        
        # create connections
        self._actionGroup.hovered.connect(self.emitHovered)
        self._actionGroup.triggered.connect(self.emitTriggered)
    
    def actions(self):
        """
        Returns a list of the actions linked with this widget.
        
        :return     [<QAction>, ..]
        """
        return self._actionGroup.actions()
    
    def actionTexts(self):
        """
        Returns a list of the action texts for this widget.
        
        :return     [<str>, ..]
        """
        return map(lambda x: x.text(), self._actionGroup.actions())
    
    def actionGroup( self ):
        """
        Returns the action group linked with this widget.
        
        :return     <QActionGroup>
        """
        return self._actionGroup
    
    def addAction(self, action, checked=None, autoBuild=True):
        """
        Adds the inputed action to this widget's action group.  This will auto-\
        create a new group if no group is already defined.
        
        :param      action | <QAction> || <str>
        
        :return     <QAction>
        """
        # clear the holder
        actions = self._actionGroup.actions()
        if actions and actions[0].objectName() == 'place_holder':
            self._actionGroup.removeAction(actions[0])
            actions[0].deleteLater()
        
        # create an action from the name
        if not isinstance(action, QAction):
            action_name = str(action)
            action = QAction(action_name, self)
            action.setObjectName(action_name)
            action.setCheckable(self.isCheckable())
            
            # auto-check the first option
            if checked or (not self._actionGroup.actions() and checked is None):
                action.setChecked(True)
        
        self._actionGroup.addAction(action)
        
        if autoBuild:
            self.rebuild()
        
        return action
    
    def clear(self, autoBuild=True):
        """
        Clears the actions for this widget.
        """
        for action in self._actionGroup.actions():
            self._actionGroup.removeAction(action)
        
        action = QAction('', self)
        action.setObjectName('place_holder')
#        self._currentAction = None
        self._actionGroup.addAction(action)
        if autoBuild:
            self.rebuild()
    
    def cornerRadius( self ):
        """
        Returns the corner radius for this widget.
        
        :return     <int>
        """
        return self._cornerRadius
    
    def count(self):
        """
        Returns the number of actions associated with this button.
        
        :return     <int>
        """
        actions = self._actionGroup.actions()
        if len(actions) == 1 and actions[0].objectName() == 'place_holder':
            return 0
        
        return len(actions)
    
    def currentAction( self ):
        """
        Returns the action that is currently checked in the system.
        
        :return     <QAction> || None
        """
        return self._actionGroup.checkedAction()
    
    def direction( self ):
        """
        Returns the direction for this widget.
        
        :return     <QBoxLayout::Direction>
        """
        return self.layout().direction()
    
    def emitClicked(self):
        """
        Emits the clicked signal whenever any of the actions are clicked.
        """
        if not self.signalsBlocked():
            self.clicked.emit()
    
    def emitHovered(self, action):
        """
        Emits the hovered action for this widget.
        
        :param      action | <QAction>
        """
        if not self.signalsBlocked():
            self.hovered.emit(action)
    
    def emitTriggered(self, action):
        """
        Emits the triggered action for this widget.
        
        :param      action | <QAction>
        """
#        if action != self._currentAction:
#            self._currentAction = action
#            self.currentActionChanged.emit(action)
        
#        self._currentAction = action
        if not self.signalsBlocked():
            self.triggered.emit(action)
    
    def findAction( self, text ):
        """
        Looks up the action based on the inputed text.
        
        :return     <QAction> || None
        """
        for action in self.actionGroup().actions():
            if ( text in (action.objectName(), action.text()) ):
                return action
        return None
    
    def isCheckable(self):
        """
        Returns whether or not the actions within this button should be
        checkable.
        
        :return     <bool>
        """
        return self._checkable
    
    def padding( self ):
        """
        Returns the button padding amount for this widget.
        
        :return     <int>
        """
        return self._padding
    
    def rebuild( self ):
        """
        Rebuilds the user interface buttons for this widget.
        """
        self.setUpdatesEnabled(False)
        
        # sync up the toolbuttons with our actions
        actions = self._actionGroup.actions()
        btns    = self.findChildren(QToolButton)
        horiz   = self.direction() in (QBoxLayout.LeftToRight, 
                                       QBoxLayout.RightToLeft)
        
        # remove unnecessary buttons
        if len(actions) < len(btns):
            rem_btns = btns[len(actions)-1:]
            btns = btns[:len(actions)]
            
            for btn in rem_btns:
                btn.close()
                btn.setParent(None)
                btn.deleteLater()
        
        # create new buttons
        elif len(btns) < len(actions):
            for i in range(len(btns), len(actions)):
                btn = QToolButton(self)
                btn.setAutoFillBackground(True)
                btns.append(btn)
                self.layout().addWidget(btn)
                
                btn.clicked.connect(self.emitClicked)
        
        # determine coloring options
        palette      = self.palette()
        checked      = palette.color(palette.Highlight)
        checked_fg   = palette.color(palette.HighlightedText)
        unchecked    = palette.color(palette.Button)
        unchecked_fg = palette.color(palette.ButtonText)
        border       = palette.color(palette.Mid)
        
        # define the stylesheet options
        options = {}
        options['top_left_radius']      = 0
        options['top_right_radius']     = 0
        options['bot_left_radius']      = 0
        options['bot_right_radius']     = 0
        options['border_color']         = border.name()
        options['checked_fg']           = checked_fg.name()
        options['checked_bg']           = checked.name()
        options['checked_bg_alt']       = checked.darker(120).name()
        options['unchecked_fg']         = unchecked_fg.name()
        options['unchecked_bg']         = unchecked.name()
        options['unchecked_bg_alt']     = unchecked.darker(120).name()
        options['padding_top']          = 1
        options['padding_bottom']       = 1
        options['padding_left']         = 1
        options['padding_right']        = 1
        
        if horiz:
            options['x1'] = 0
            options['y1'] = 0
            options['x2'] = 0
            options['y2'] = 1
        else:
            options['x1'] = 0
            options['y1'] = 0
            options['x2'] = 1
            options['y2'] = 1
        
        # sync up the actions and buttons
        count = len(actions)
        palette = self.palette()
        font = self.font()
        for i, action in enumerate(actions):
            btn = btns[i]
            
            # assign the action for this button
            if btn.defaultAction() != action:
                # clear out any existing actions
                for act in btn.actions():
                    btn.removeAction(act)
                
                # assign the given action
                btn.setDefaultAction(action)
            
            options['top_left_radius']  = 1
            options['bot_left_radius']  = 1
            options['top_right_radius'] = 1
            options['bot_right_radius'] = 1
            
            if horiz:
                options['padding_left']     = self._padding
                options['padding_right']    = self._padding
            else:
                options['padding_top']      = self._padding
                options['padding_bottom']   = self._padding
            
            if not i:
                if horiz:
                    options['top_left_radius'] = self.cornerRadius()
                    options['bot_left_radius'] = self.cornerRadius()
                    options['padding_left']    += self.cornerRadius() / 3.0
                else:
                    options['top_left_radius'] = self.cornerRadius()
                    options['top_right_radius'] = self.cornerRadius()
                    options['padding_top']     += self.cornerRadius() / 3.0
                    
            if i == count - 1:
                if horiz:
                    options['top_right_radius'] = self.cornerRadius()
                    options['bot_right_radius'] = self.cornerRadius()
                    options['padding_right']    += self.cornerRadius() / 3.0
                else:
                    options['bot_left_radius']  = self.cornerRadius()
                    options['bot_right_radius'] = self.cornerRadius()
                    options['padding_bottom']   += self.cornerRadius() / 3.0
            
            btn.setFont(font)
            btn.setPalette(palette)
            btn.setStyleSheet(TOOLBUTTON_STYLE % options)
            if horiz:
                btn.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Preferred)
            else:
                btn.setSizePolicy(QSizePolicy.Preferred, QSizePolicy.Expanding)
        
        self.setUpdatesEnabled(True)
    
    def setActions(self, actions):
        """
        Sets the actions for this widget to th inputed list of actions.
        
        :param      [<QAction>, ..]
        """
        self.clear(autoBuild=False)
        for action in actions:
            self.addAction(action, autoBuild=False)
        self.rebuild()
    
    def setActionTexts(self, names):
        """
        Convenience method for auto-generating actions based on text names,
        sets the list of actions for this widget to the inputed list of names.
        
        :param      names | [<str>, ..]
        """
        self.setActions(names)
    
    def setActionGroup( self, actionGroup ):
        """
        Sets the action group for this widget to the inputed action group.
        
        :param      actionGroup | <QActionGroup>
        """
        self._actionGroup = actionGroup
        self.rebuild()
    
    def setCheckable(self, state):
        """
        Sets whether or not the actions within this button should be checkable.
        
        :param      state | <bool>
        """
        self._checkable = state
        for act in self._actionGroup.actions():
            act.setCheckable(state)
    
    def setCornerRadius( self, radius ):
        """
        Sets the corner radius value for this widget to the inputed radius.
        
        :param      radius | <int>
        """
        self._cornerRadius = radius
    
    def setCurrentAction(self, action):
        """
        Sets the current action for this button to the inputed action.
        
        :param      action | <QAction> || <str>
        """
        self._actionGroup.blockSignals(True)
        for act in self._actionGroup.actions():
            act.setChecked(act == action or act.text() == action)
        self._actionGroup.blockSignals(False)
    
    def setDirection( self, direction ):
        """
        Sets the direction that this group widget will face.
        
        :param      direction | <QBoxLayout::Direction>
        """
        self.layout().setDirection(direction)
        self.rebuild()
    
    def setFont(self, font):
        """
        Sets the font for this widget and propogates down to the buttons.
        
        :param      font | <QFont>
        """
        super(XSplitButton, self).setFont(font)
        self.rebuild()
    
    def setPadding( self, padding ):
        """
        Sets the padding amount for this widget's button set.
        
        :param      padding | <int>
        """
        self._padding = padding
        self.rebuild()
    
    def setPalette(self, palette):
        """
        Rebuilds the buttons for this widget since they use specific palette
        options.
        
        :param      palette | <QPalette>
        """
        super(XSplitButton, self).setPalette(palette)
        self.rebuild()
    
    def sizeHint(self):
        """
        Returns the base size hint for this widget.
        
        :return     <QSize>
        """
        return QSize(35, 22)
    
    x_actionTexts = Property(QStringList, actionTexts, setActionTexts)
    x_checkable   = Property(bool, isCheckable, setCheckable)
Exemplo n.º 9
0
class XTabWidget(QTabWidget):
    addRequested = Signal(QPoint)
    optionsRequested = Signal(QPoint)

    def __init__(self, *args):
        super(XTabWidget, self).__init__(*args)

        # create the tab bar
        self.setTabBar(XTabBar(self))

        # create custom properties
        self._showAddButton = True
        self._showOptionsButton = True

        # create the add button
        self._addButton = XTabButton(self)
        self._addButton.setIcon(QIcon(resources.find('img/tab/add.png')))
        self._addButton.setFixedSize(18, 18)
        self._addButton.setIconSize(QSize(10, 10))

        # create the option button
        self._optionsButton = XTabButton(self)
        self._optionsButton.setFixedSize(22, 18)
        self._optionsButton.setIcon(QIcon(resources.find('img/tab/gear.png')))
        self._optionsButton.setIconSize(QSize(10, 10))

        # create connection
        self.connect(self.tabBar(), SIGNAL('currentChanged(int)'),
                     self.adjustButtons)

        self.connect(self.tabBar(), SIGNAL('resized()'), self.adjustButtons)

        self.connect(self._optionsButton, SIGNAL('clicked()'),
                     self.emitOptionsRequested)

        self.connect(self._addButton, SIGNAL('clicked()'),
                     self.emitAddRequested)

    def __nonzero__(self):
        """
        At somepoint, QTabWidget's nonzero became linked to whether it had
        children vs. whether it was none.  This returns the original
        functionality.
        """
        return self is not None

    def adjustButtons(self):
        """
        Updates the position of the buttons based on the current geometry.
        """
        tabbar = self.tabBar()
        tabbar.adjustSize()

        w = self.width() - self._optionsButton.width() - 2
        self._optionsButton.move(w, 0)

        if self.count():
            need_update = self._addButton.property('alone') != False
            if need_update:
                self._addButton.setProperty('alone', False)

            self._addButton.move(tabbar.width(), 1)
            self._addButton.setFixedHeight(tabbar.height())
        else:
            need_update = self._addButton.property('alone') != True
            if need_update:
                self._addButton.setProperty('alone', True)

            self._addButton.move(tabbar.width() + 2, 1)

        self._addButton.stackUnder(self.currentWidget())

        # force refresh on the stylesheet (Qt limitation for updates)
        if need_update:
            app = QApplication.instance()
            app.setStyleSheet(app.styleSheet())

    def addButton(self):
        """
        Returns the add button linked with this tab widget.
        
        :return     <XTabButton>
        """
        return self._addButton

    def emitAddRequested(self, point=None):
        """
        Emitted when the option menu button is clicked provided the signals \
        are not being blocked for this widget.
        
        :param      point | <QPoint>
        """
        if self.signalsBlocked():
            return

        if not point:
            btn = self._addButton
            point = btn.mapToGlobal(QPoint(btn.width(), 0))

        self.addRequested.emit(point)

    def emitOptionsRequested(self, point=None):
        """
        Emitted when the option menu button is clicked provided the signals \
        are not being blocked for this widget.
        
        :param      point | <QPoint>
        """
        if self.signalsBlocked():
            return

        if not point:
            btn = self._optionsButton
            point = btn.mapToGlobal(QPoint(0, btn.height()))

        self.optionsRequested.emit(point)

    def optionsButton(self):
        """
        Returns the options button linked with this tab widget.
        
        :return     <XTabButton>
        """
        return self._optionsButton

    def paintEvent(self, event):
        if not self.count():
            return

        super(XTabWidget, self).paintEvent(event)

    def resizeEvent(self, event):
        """
        Updates the position of the additional buttons when this widget \
        resizes.
        
        :param      event | <QResizeEvet>
        """
        super(XTabWidget, self).resizeEvent(event)
        self.adjustButtons()

    def setShowAddButton(self, state):
        """
        Sets whether or not the add button is visible.
        
        :param      state | <bool>
        """
        self._showAddButton = state
        self._addButton.setVisible(state)

    def setShowOptionsButton(self, state):
        """
        Sets whether or not the option button is visible.
        
        :param          state   | <bool>
        """
        self._showOptionsButton = state
        self._optionsButton.setVisible(state)

    def showAddButton(self):
        """
        Returns whether or not the add button is visible.
        
        :return     <bool>
        """
        return self._showAddButton

    def showOptionsButton(self):
        """
        Returns whether or not the option button should be visible.
        
        :return     <bool>
        """
        return self._showOptionsButton

    x_showAddButton = Property(bool, showAddButton, setShowAddButton)
    x_showOptionsButton = Property(bool, showOptionsButton,
                                   setShowOptionsButton)
Exemplo n.º 10
0
class XMultiTagEdit(XListWidget):
    tagCreated = Signal(str)
    tagRemoved = Signal(str)

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

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

        # define custom properties
        self._createItem = None
        self._completer = None
        self._itemsRemovable = True
        self._unique = True
        self._highlightColor = QColor(181, 209, 244)
        self._tagColor = QColor(181, 209, 244, 50)
        self._borderColor = QColor(150, 178, 213)
        self._options = None
        self._insertAllowed = False
        self._editable = True

        # make sure the highlighting works
        palette = self.palette()
        palette.setColor(palette.Highlight, self._highlightColor)
        self.setPalette(palette)

        # setup default options
        self.setItemDelegate(XMultiTagDelegate(self))
        self.setMinimumHeight(28)
        self.setMovement(XListWidget.Static)
        self.setResizeMode(XListWidget.Adjust)
        self.setDragDropMode(XListWidget.DragOnly)  # handling drops internally
        self.setViewMode(XListWidget.IconMode)
        self.setEditTriggers(XListWidget.DoubleClicked | \
                             XListWidget.SelectedClicked | \
                             XListWidget.AnyKeyPressed )
        self.setSpacing(3)
        self.setAcceptDrops(True)
        self.clear()

        # track changes to items
        self.itemChanged.connect(self.handleTagChange)

    @Slot(str)
    def addTag(self, tag):
        """
        Adds a new tag to the edit.
        
        :param      tag | <str>
        
        :return     <bool>
        """
        if (not (tag and self.isTagValid(tag))):
            return False

        self.blockSignals(True)
        create_item = self.createItem()
        if create_item:
            self.insertItem(self.row(create_item), XMultiTagItem(tag, self))
            create_item.setText('')
        else:
            self.addItem(XMultiTagItem(tag, self))
        self.blockSignals(False)

        if (not self.signalsBlocked()):
            self.tagCreated.emit(tag)

        return False

    def borderColor(self):
        """
        Returns the color used for the tag border.
        
        :return     <QColor>
        """
        return self._borderColor

    def clear(self):
        """
        Clears the items for this edit.
        """
        super(XMultiTagEdit, self).clear()
        self._createItem = None

    def completer(self):
        """
        Returns the completer instance linked with this edit.
        
        :return     <QCompleter> || None
        """
        return self._completer

    def copy(self):
        """
        Copies the selected items to the clipboard.
        """
        text = []
        for item in self.selectedItems():
            text.append(str(item.text()))

        QApplication.clipboard().setText(','.join(text))

    def createItem(self):
        """
        Returns a reference to the create item that is used for this edit.
        
        :return     <XMultiTagCreateItem>
        """
        if not self.isEditable():
            return None

        if self._createItem is None:
            self.blockSignals(True)

            self._createItem = XMultiTagCreateItem(self)
            self.addItem(self._createItem)

            self.blockSignals(False)

        return self._createItem

    def dragEnterEvent(self, event):
        """
        Handles the drag enter event.
        
        :param      event | <QDragEvent>
        """
        tags = str(event.mimeData().text())

        if (event.source() == self):
            event.acceptProposedAction()
        elif (tags):
            event.acceptProposedAction()
        else:
            super(XMultiTagEdit, self).dragEnterEvent(event)

    def dragMoveEvent(self, event):
        """
        Handles the drag move event.
        
        :param      event | <QDragEvent>
        """
        tags = str(event.mimeData().text())

        if (event.source() == self):
            event.acceptProposedAction()
        elif (tags):
            event.acceptProposedAction()
        else:
            super(XMultiTagEdit, self).dragMoveEvent(event)

    def dropEvent(self, event):
        """
        Handles the drop event.
        
        :param      event | <QDropEvent>
        """
        tags = str(event.mimeData().text())

        # handle an internal move
        if event.source() == self:
            curr_item = self.selectedItems()[0]
            create_item = self.createItem()

            # don't allow moving of the creation item
            if curr_item == create_item:
                return

            targ_item = self.itemAt(event.pos())
            if not targ_item:
                targ_item = create_item

            curr_idx = self.row(curr_item)
            targ_idx = self.row(targ_item)
            if (targ_idx == self.count() - 1):
                targ_idx -= 1

            # don't bother moving the same item
            if (curr_idx == targ_idx):
                return

            self.takeItem(self.row(curr_item))
            self.insertItem(targ_idx, curr_item)
            self.setCurrentItem(curr_item)

        elif (tags):
            for tag in tags.split(','):
                tag = tag.strip()
                if (self.isTagValid(tag)):
                    self.addTag(tag)

        else:
            event.accept()

    def eventFilter(self, object, event):
        """
        Filters the key press event on the editor object to look for backspace
        key strokes on blank editors to remove previous tags.
        
        :param      object | <QObject>
                    event | <QEvent>
        
        :return     <bool> | consumed
        """
        if event.type() == event.KeyPress:
            if event.key() == Qt.Key_Backspace:
                is_line_edit = isinstance(object, QLineEdit)
                if not (is_line_edit and object.text()):
                    if self.count() > 1:
                        self.takeItem(self.count() - 2)
                        object.setFocus()

            elif event.key() in (Qt.Key_Return, Qt.Key_Enter):
                self.finishEditing(object.text())
                return True

        return False

    def finishEditing(self, tag):
        """
        Finishes editing the current item.
        """
        curr_item = self.currentItem()
        create_item = self.createItem()
        self.closePersistentEditor(curr_item)

        if curr_item == create_item:
            self.addTag(tag)

        elif self.isTagValid(tag):
            curr_item.setText(tag)

    def handleTagChange(self, item):
        """
        Handles the tag change information for this widget.
        
        :param      item | <QListWidgetItem>
        """
        # :note PySide == operator not defined for QListWidgetItem.  In this
        #       in this case, we're just checking if the object is the exact
        #       same, so 'is' works just fine.
        create_item = self.createItem()
        if item is create_item:
            self.addTag(create_item.text())

        elif self.isTagValid(item.text()):
            item.setText(item.text())

    def hasTag(self, tag):
        """
        Returns whether or not the inputed tag exists in this collection.
        
        :return     <bool>
        """
        return str(tag) in self.tags()

    def highlightColor(self):
        """
        Returns the highlight color for this edit.
        
        :return     <QColor>
        """
        return self._highlightColor

    def isEditable(self):
        """
        Returns whether or not the user can edit the items in the list by
        typing.
        
        :return     <bool>
        """
        return self._editable

    def isInsertAllowed(self):
        """
        Returns the whether or not a user is able to insert new tags besides
        the ones in the options.
        
        :return     <bool>
        """
        return self._insertAllowed

    def isTagValid(self, tag):
        """
        Checks to see if the inputed tag is valid or not.
        
        :param      tag | <str>
        
        :return     <bool>
        """
        if ( self._options is not None and \
             not str(tag) in self._options \
             and not self.isInsertAllowed() ):
            return False

        elif (self.isUnique() and self.hasTag(tag)):
            return False

        return True

    def isUnique(self):
        """
        Returns whether or not the tags for this edit should be unique.
        
        :return     <bool>
        """
        return self._unique

    def itemFromTag(self, tag):
        """
        Returns the item assigned to the given tag.
        
        :param      tag | <str>
        
        :return     <XMultiTagItem> || None
        """
        for row in range(self.count() - 1):
            item = self.item(row)
            if (item and item.text() == tag):
                return item
        return None

    def itemsRemovable(self):
        """
        Returns whether or not the items are able to be removed by the user.
        
        :return     <bool>
        """
        return self._itemsRemovable

    def keyPressEvent(self, event):
        """
        Handles the Ctrl+C/Ctrl+V events for copy & paste.
        
        :param      event | <QKeyEvent>
        """
        if ( event.key() == Qt.Key_C and \
             event.modifiers() == Qt.ControlModifier ):
            self.copy()
            event.accept()
            return

        elif ( event.key() == Qt.Key_V and \
             event.modifiers() == Qt.ControlModifier ):
            self.paste()
            event.accept()
            return

        elif (event.key() == Qt.Key_Delete):
            indexes = map(self.row, self.selectedItems())
            for index in reversed(sorted(indexes)):
                self.takeItem(index)

            event.accept()
            return

        elif event.key() == Qt.Key_Backspace:
            if self.count() > 1:
                self.takeItem(self.count() - 2)
                self.setFocus()

        super(XMultiTagEdit, self).keyPressEvent(event)

    def mimeData(self, items):
        """
        Creates the mime data for the different items.
        
        :param      items | [<QListWidgetItem>, ..]
        
        :return     <QMimeData>
        """
        text = []
        for item in items:
            text.append(str(item.text()))

        data = QMimeData()
        data.setText(','.join(text))
        return data

    def mousePressEvent(self, event):
        """
        Make sure on a mouse release event that we have a current item.  If
        no item is current, then our edit item will become current.
        
        :param      event | <QMouseReleaseEvent>
        """
        item = self.itemAt(event.pos())

        # set the tag creation item as active
        if item is None:
            create_item = self.createItem()
            if create_item:
                self.setCurrentItem(create_item)
                self.editItem(create_item)

        # check to see if we're removing a tag
        else:
            rect = self.visualItemRect(item)
            if (rect.right() - 14 < event.pos().x()):
                # make sure the item is allowed to be removed via the widget
                if (self.itemsRemovable()):
                    self.takeItem(self.row(item))

                # emit the removed signal
                if (not self.signalsBlocked()):
                    self.tagRemoved.emit(item.text())

                event.ignore()
                return

        super(XMultiTagEdit, self).mousePressEvent(event)

    def options(self):
        """
        Returns the list of options that are valid for this tag edit.
        
        :return     [<str>, ..]
        """
        if self._options is None:
            return []
        return self._options

    def paste(self):
        """
        Pastes text from the clipboard.
        """
        text = str(QApplication.clipboard().text())
        for tag in text.split(','):
            tag = tag.strip()
            if (self.isTagValid(tag)):
                self.addTag(tag)

    def resizeEvent(self, event):
        """
        Overloads the resize event to control if we are still editing.
        
        If we are resizing, then we are no longer editing.
        """
        curr_item = self.currentItem()
        self.closePersistentEditor(curr_item)

        super(XMultiTagEdit, self).resizeEvent(event)

    def setCompleter(self, completer):
        """
        Sets the text completer for this tag widget to the inputed completer.
        
        :param      completer | <QCompleter>
        """
        if (self._completer == completer):
            return
        elif (self._completer):
            self._completer.activated.disconnect(self.finishEditing)

        self._completer = completer

        if (completer):
            completer.activated.connect(self.finishEditing)

    def setEditable(self, state):
        """
        Sets whether or not the user can edit the items in the list by
        typing.
        
        :param     state | <bool>
        """
        self._editable = state
        if state:
            self.setEditTriggers(self.AllEditTriggers)
        else:
            self.setEditTriggers(self.NoEditTriggers)

    def setInsertAllowed(self, state):
        """
        Sets whether or not the insertion is allowed for tags not defined in 
        the options.
        
        :param      state | <bool>
        """
        self._insertAllowed = state

    def setItemsRemovable(self, state):
        """
        Sets whether or not the items should be allowed to be removed by the
        user.
        
        :param      state | <bool>
        """
        self._itemsRemovable = state

    def setOptions(self, options):
        """
        Sets the tag option list for this widget.  If used, tags need to be
        found within the list of options when added.
        
        :param      options | [<str>, ..]
        """
        self._options = map(str, options)

        if (options):
            completer = QCompleter(options, self)
            completer.setCompletionMode(QCompleter.InlineCompletion)
            self.setCompleter(completer)
        else:
            self.setCompleter(None)

    def setTags(self, tags):
        """
        Sets the tags assigned to this item to the inputed list of tags.
        
        :param      tags | [<str>, ..]
        """
        self.setUpdatesEnabled(False)
        self.blockSignals(True)

        self.clear()
        for tag in tags:
            self.addItem(XMultiTagItem(tag, self))

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

    def setUnique(self, state):
        """
        Sets whether or not the tags on this edit should be unique.
        
        :param      state | <bool>
        """
        self._unique = state

    def setViewMode(self, viewMode):
        """
        Sets the view mode for this widget to the inputed mode.
        
        :param      viewMode | <QListWidget.ViewMode>
        """
        ddrop_mode = self.dragDropMode()
        super(XMultiTagEdit, self).setViewMode(viewMode)
        self.setDragDropMode(ddrop_mode)

    def tagColor(self):
        """
        Returns the color used for the tags of this edit.
        
        :return     <QColor>
        """
        return self._tagColor

    def tagItems(self):
        """
        Returns a list of all the tag items assigned to this widget.
        
        :return     [<XMultiTagItem>, ..]
        """
        return [self.item(row) for row in range(self.count() - 1)]

    def tags(self):
        """
        Returns a list of all the tags assigned to this widget.
        
        :return     [<str>, ..]
        """
        item = self.item(self.count() - 1)
        count = self.count()
        if (item is self._createItem):
            count -= 1

        return [str(self.item(row).text()) for row in range(count)]

    def takeTag(self, tag):
        """
        Removes the inputed tag from the system.
        
        :param      tag | <str>
        
        :return     <XMultiTagItem> || None
        """
        for row in range(self.count() - 1):
            item = self.item(row)
            if (item and item.text() == tag):
                self.takeItem(row)
                if (not self.signalsBlocked()):
                    self.tagRemoved.emit(tag)
                return item
        return None

    x_editable = Property(bool, isEditable, setEditable)
    x_unique = Property(bool, isUnique, setUnique)
    x_insertAllowed = Property(bool, isInsertAllowed, setInsertAllowed)
    x_itemsRemovable = Property(bool, itemsRemovable, setItemsRemovable)
Exemplo n.º 11
0
class XRichTextEdit(QWidget):
    """ """
    copyAvailable = Signal(bool)
    currentCharFormatChanged = Signal(QTextCharFormat)
    cursorPositionChanged = Signal()
    redoAvailable = Signal(bool)
    selectionChanged = Signal()
    textChanged = Signal()
    undoAvailable = Signal(bool)
    
    def __init__(self, parent=None):
        super(XRichTextEdit, self).__init__( parent )
        
        # load the user interface
        projexui.loadUi(__file__, self)
        
        # define custom properties
        
        # set default properties
        self.setFocusProxy(self.uiEditTXT)
        self.uiFindWGT.setTextEdit(self.uiEditTXT)
        self.uiFindWGT.hide()
        
        self.editor().setTabStopWidth(16)
        self.editor().document().setIndentWidth(24)
        self.editor().installEventFilter(self)
        self.editor().setRichTextEditEnabled(True)
        
        # create the font picker widget
        self._fontPickerWidget = XFontPickerWidget(self)
        self.uiFontBTN.setDefaultAnchor(XPopupWidget.Anchor.TopLeft)
        self.uiFontBTN.setCentralWidget(self._fontPickerWidget)
        
        popup = self.uiFontBTN.popupWidget()
        popup.setResizable(False)
        popup.setShowTitleBar(False)
        
        self._fontPickerWidget.accepted.connect(popup.accept)
        
        # generate actions for this editor based on the toolbar buttons
        self._actions = {}
        for mapping in (('bold', self.uiBoldBTN, 'Ctrl+B'),
                        ('italic', self.uiItalicBTN, 'Ctrl+I'),
                        ('underline', self.uiUnderlineBTN, 'Ctrl+U'),
                        ('strikeOut', self.uiStrikeoutBTN, ''),
                        ('unordered', self.uiUnorderedBTN, ''),
                        ('ordered', self.uiOrderedBTN, ''),
                        ('table', self.uiTableBTN, ''),
                        ('align_left', self.uiAlignLeftBTN, ''),
                        ('align_right', self.uiAlignRightBTN, ''),
                        ('align_center', self.uiAlignCenterBTN, ''),
                        ('align_justify', self.uiAlignJustifyBTN, ''),
                        ('font_color', self.uiFontColorBTN, ''),
                        ('bg_color', self.uiBackgroundColorBTN, '')):
            
            name, btn, shortcut = mapping
            
            act = QAction(self)
            act.setObjectName(name)
            act.setToolTip(btn.toolTip())
            act.setIcon(btn.icon())
            
            act.setShortcut(QKeySequence(shortcut))
            act.setCheckable(btn.isCheckable())
            act.setChecked(btn.isChecked())
            
            act.setShortcutContext(Qt.WidgetWithChildrenShortcut)
            
            btn.setDefaultAction(act)
            
            self._actions[name] = act
            self.addAction(act)
        
        # create the action groupings
        popup.resetRequested.connect(self.updateFontPicker)
        popup.aboutToShow.connect(self.updateFontPicker)
        popup.accepted.connect(self.assignFont)
        
        align_group = QActionGroup(self)
        align_group.addAction(self._actions['align_left'])
        align_group.addAction(self._actions['align_right'])
        align_group.addAction(self._actions['align_center'])
        align_group.addAction(self._actions['align_justify'])
        
        align_group.triggered.connect(self.assignAlignment)
        
        self._actions['align_left'].setChecked(True)
        
        # create connections
        self._actions['bold'].toggled.connect(self.setFontBold)
        self._actions['italic'].toggled.connect(self.setFontItalic)
        self._actions['underline'].toggled.connect(self.setFontUnderline)
        self._actions['strikeOut'].toggled.connect(self.setFontStrikeOut)
        
        self._actions['ordered'].triggered.connect(self.insertOrderedList)
        self._actions['unordered'].triggered.connect(self.insertUnorderedList)
        self._actions['table'].triggered.connect(self.insertTable)
        
        self._actions['font_color'].triggered.connect(self.pickTextColor)
        self._actions['bg_color'].triggered.connect(self.pickTextBackgroundColor)
        
        # link signals from the editor to the system
        for signal in ('copyAvailable',
                       'currentCharFormatChanged',
                       'cursorPositionChanged',
                       'redoAvailable',
                       'selectionChanged',
                       'textChanged',
                       'undoAvailable'):
                           
            from_ = getattr(self.uiEditTXT, signal)
            to_   = getattr(self, signal)
            
            from_.connect(to_)
        
        self.cursorPositionChanged.connect(self.refreshAlignmentUi)
        self.currentCharFormatChanged.connect(self.refreshUi)
    
    def alignment(self):
        """
        Returns the current alignment for the editor.
        
        :return     <Qt.Alignment>
        """
        return self.editor().alignment()
    
    def assignAlignment(self, action):
        """
        Sets the current alignment for the editor.
        """
        if self._actions['align_left'] == action:
            self.setAlignment(Qt.AlignLeft)
        elif self._actions['align_right'] == action:
            self.setAlignment(Qt.AlignRight)
        elif self._actions['align_center'] == action:
            self.setAlignment(Qt.AlignHCenter)
        else:
            self.setAlignment(Qt.AlignJustify)
    
    def assignFont(self):
        """
        Assigns the font family and point size settings from the font picker
        widget.
        """
        font = self.currentFont()
        font.setFamily(self._fontPickerWidget.currentFamily())
        font.setPointSize(self._fontPickerWidget.pointSize())
        self.setCurrentFont(font)
    
    def blockSignals(self, state):
        """
        Propagates the block signals for this editor down to the text
        editor.
        
        :param      state | <bool>
        """
        super(XRichTextEdit, self).blockSignals(state)
        self.uiEditTXT.blockSignals(state)
    
    def currentFont(self):
        """
        Returns the current font for this editor.
        
        :return     <QFont>
        """
        return self.editor().currentFont()
    
    def document(self):
        """
        Returns the text document assigned to this edit.
        
        :return     <QTextDocument>
        """
        return self.editor().document()
    
    def documentMargin(self):
        """
        Returns the margins used for the document edges.
        
        :return     <int>
        """
        return self.document().documentMargin()
    
    def editor(self):
        """
        Returns the text editor that is linked with this rich text editor.
        
        :return     <XTextEdit>
        """
        return self.uiEditTXT
    
    def encoding(self):
        """
        Returns the text encoding type for this edit.
        
        :return     <str>
        """
        return self.uiEditTXT.encoding()
    
    def eventFilter(self, object, event):
        """
        Listens for tab/backtab to modify list information.
        
        :param      event | <QKeyPressEvent>
        """
        if event.type() != event.KeyPress:
            return super(XRichTextEdit, self).eventFilter(object, event)
        
        cursor    = object.textCursor()
        curr_list = cursor.currentList()
        
        # make sure we're in a current list
        if not curr_list:
            return super(XRichTextEdit, self).eventFilter(object, event)
        
        # unindent for Backtab (Shift+Tab)
        if event.key() == Qt.Key_Backtab:
            delta = -1
        
        # indent for Tab
        elif event.key() == Qt.Key_Tab:
            delta = 1
        
        # otherwise, don't bother calculating
        else:
            return super(XRichTextEdit, self).eventFilter(object, event)
        
        # look for the proper list to move to
        curr_block      = cursor.block()
        curr_indent     = curr_list.format().indent()
        curr_style      = curr_list.format().style()
        prev_block      = curr_block
        next_block      = curr_block
        add_to_list     = None
        add_to_indent   = curr_indent + delta
        
        while prev_block or next_block:
            if prev_block:
                prev_block = prev_block.previous()
                prev_list  = prev_block.textList()
                
                if not prev_list:
                    prev_block = None
                
                else:
                    prev_indent = prev_list.format().indent()
                    
                    if prev_indent == add_to_indent:
                        add_to_list = prev_list
                        break
                    
            if next_block:
                next_block = next_block.next()
                next_list  = next_block.textList()
                
                if not next_list:
                    next_block = None
                
                else:
                    next_indent = next_list.format().indent()
                    
                    if next_indent == add_to_indent:
                        add_to_list = next_list
                        break
        
        if add_to_list is None and 0 < delta:
            if curr_style in (QTextListFormat.ListCircle,
                              QTextListFormat.ListDisc,
                              QTextListFormat.ListSquare):
                self.insertUnorderedList()
            else:
                self.insertOrderedList()
        
        elif add_to_list:
            add_to_list.add(curr_block)
        
        return True
    
    def fontBold(self):
        """
        Returns whether or not the current text is bold.
        
        :return     <bool>
        """
        return self.editor().fontWeight() == QFont.Bold
    
    def fontFamily(self):
        """
        Returns the family name of the current font for this editor.
        
        :return     <str>
        """
        return self.editor().fontFamily()
    
    def fontItalic(self):
        """
        Returns whether or not the editor is currently in italics.
        
        :return     <bool>
        """
        return self.editor().fontItalic()
    
    def fontPointSize(self, pointSize):
        """
        Returns the current font's point size.
        
        :return     <int>
        """
        return self.editor().fontPointSize()
    
    def fontStrikeOut(self):
        """
        Returns whether or not the current font is in strike out mode.
        
        :return     <bool>
        """
        return self.currentFont().strikeOut()
    
    def fontUnderline(self):
        """
        Returns whether or not the editor is in underline state.
        
        :return     <bool>
        """
        return self.editor().fontUnderline()
    
    def fontWeight(self):
        """
        Returns the current font weight of the editor.
        
        :return     <QFont.Weight>
        """
        return self.editor().fontWeight()
    
    @Slot()
    def insertOrderedList(self):
        """
        Inserts an ordered list into the editor.
        """
        cursor = self.editor().textCursor()
        currlist = cursor.currentList()
        new_style = QTextListFormat.ListDecimal
        indent = 1
        
        if currlist:
            format = currlist.format()
            indent = format.indent() + 1
            style  = format.style()
            
            if style == QTextListFormat.ListDecimal:
                new_style = QTextListFormat.ListLowerRoman
            elif style == QTextListFormat.ListLowerRoman:
                new_style = QTextListFormat.ListUpperAlpha
            elif style == QTextListFormat.ListUpperAlpha:
                new_style = QTextListFormat.ListLowerAlpha
        
        new_format = QTextListFormat()
        new_format.setStyle(new_style)
        new_format.setIndent(indent)
        new_list = cursor.createList(new_format)
        
        self.editor().setFocus()
        
        return new_list
    
    @Slot()
    def insertTable(self):
        """
        Inserts a table into the editor.
        """
        self.editor().textCursor().insertTable(3, 3)
        self.editor().setFocus()
    
    @Slot()
    def insertUnorderedList(self):
        """
        Inserts an ordered list into the editor.
        """
        cursor = self.editor().textCursor()
        currlist = cursor.currentList()
        new_style = QTextListFormat.ListDisc
        indent = 1
        
        if currlist:
            format = currlist.format()
            indent = format.indent() + 1
            style  = format.style()
            
            if style == QTextListFormat.ListDisc:
                new_style = QTextListFormat.ListCircle
            elif style == QTextListFormat.ListCircle:
                new_style = QTextListFormat.ListSquare
        
        new_format = QTextListFormat()
        new_format.setStyle(new_style)
        new_format.setIndent(indent)
        new_list = cursor.createList(new_format)
        
        self.editor().setFocus()
        
        return new_list
    
    @Slot()
    def pickTextBackgroundColor(self):
        """
        Prompts the user to select a text color.
        """
        clr = QColorDialog.getColor(self.textBackgroundColor(),
                                    self.window(),
                                    'Pick Background Color')
        
        if clr.isValid():
            self.setTextBackgroundColor(clr)
    
    @Slot()
    def pickTextColor(self):
        """
        Prompts the user to select a text color.
        """
        clr = QColorDialog.getColor(self.textColor(),
                                    self.window(),
                                    'Pick Text Color')
        
        if clr.isValid():
            self.setTextColor(clr)
    
    def refreshUi(self):
        """
        Matches the UI state to the current cursor positioning.
        """
        font = self.currentFont()
        
        for name in ('underline', 'bold', 'italic', 'strikeOut'):
            getter = getattr(font, name)
            act = self._actions[name]
            act.blockSignals(True)
            act.setChecked(getter())
            act.blockSignals(False)
    
    def refreshAlignmentUi(self):
        """
        Refreshes the alignment UI information.
        """
        align = self.alignment()
        for name, value in (('align_left', Qt.AlignLeft),
                            ('align_right', Qt.AlignRight),
                            ('align_center', Qt.AlignHCenter),
                            ('align_justify', Qt.AlignJustify)):
            
            act = self._actions[name]
            act.blockSignals(True)
            act.setChecked(value == align)
            act.blockSignals(False)
    
    def setAlignment(self, align):
        """
        Sets the current alignment for this editor.
        
        :param      align | <Qt.Align>
        """
        self.blockSignals(True)
        self.editor().setAlignment(align)
        self.blockSignals(False)
        self.refreshAlignmentUi()
    
    @Slot(QFont)
    def setCurrentFont(self, font):
        """
        Sets the current font for the editor to the inputed font.
        
        :param      font | <QFont>
        """
        self.blockSignals(True)
        self.editor().setCurrentFont(font)
        self.blockSignals(False)
        self.refreshUi()
    
    @Slot(int)
    def setDocumentMargin(self, margin):
        """
        Sets the document margins for this editor.
        
        :param      margin | <int>
        """
        self.document().setDocumentMargin(margin)
    
    def setEncoding(self, encoding):
        """
        Sets the encoding type for this editor to the inputed encoding.
        
        :param      encoding | <str>
        """
        self.uiEditTXT.setEncoding(encoding)
    
    @Slot(bool)
    def setFontBold(self, state):
        """
        Toggles whether or not the text is currently bold.
        
        :param      state | <bool>
        """
        if state:
            weight = QFont.Bold
        else:
            weight = QFont.Normal
        
        self.setFontWeight(weight)
    
    @Slot(str)
    def setFontFamily(self, family):
        """
        Sets the current font family to the inputed family.
        
        :param      family | <str>
        """
        self.blockSignals(True)
        self.editor().setFontFamily(family)
        self.blockSignals(False)
    
    @Slot(bool)
    def setFontItalic(self, state):
        """
        Toggles whehter or not the text is currently italic.
        
        :param      state | <bool>
        """
        font = self.currentFont()
        font.setItalic(state)
        self.setCurrentFont(font)
    
    @Slot(int)
    def setFontPointSize(self, size):
        """
        Sets the point size of the current font to the inputed size.
        
        :param      size | <int>
        """
        self.blockSignals(True)
        self.editor().setFontPointSize(size)
        self.blockSignals(False)
    
    @Slot(bool)
    def setFontStrikeOut(self, strikeOut):
        """
        Sets whether or not this editor is currently striking out the text.
        
        :param      strikeOut | <bool>
        """
        font = self.currentFont()
        font.setStrikeOut(strikeOut)
        self.setCurrentFont(font)
    
    @Slot(bool)
    def setFontUnderline(self, state):
        """
        Sets whether or not this editor is currently in underline state.
        
        :param      state | <bool>
        """
        font = self.currentFont()
        font.setUnderline(state)
        self.setCurrentFont(font)
    
    @Slot(QFont.Weight)
    def setFontWeight(self, weight):
        """
        Sets the font weight for this editor to the inputed weight.
        
        :param      weight | <QFont.Weight>
        """
        font = self.currentFont()
        font.setWeight(weight)
        self.setCurrentFont(font)
    
    @Slot(str)
    def setText(self, text):
        """
        Sets the text for this editor.
        
        :param      text | <str>
        """
        self.editor().setText(text)
    
    @Slot(QColor)
    def setTextBackgroundColor(self, color):
        """
        Sets the text background color for this instance to the inputed color.
        
        :param      color | <QColor>
        """
        self.editor().setTextBackgroundColor(QColor(color))
    
    @Slot(QColor)
    def setTextColor(self, color):
        """
        Sets the text color for this instance to the inputed color.
        
        :param      color | <QColor>
        """
        self.editor().setTextColor(QColor(color))
    
    def textBackgroundColor(self):
        """
        Returns the background color that is current in the document.
        
        :return     <QColor>
        """
        return self.editor().textBackgroundColor()
    
    def textColor(self):
        """
        Returns the text color that is current in the document.
        
        :return     <QColor>
        """
        return self.editor().textColor()
    
    def toDiv(self, style='document'):
        """
        Returns the text as paragaphed HTML vs. a full HTML document page.
        
        :return     <str>
        """
        if not self.editor().toPlainText():
            return ''
        
        html = self.editor().document().toHtml(self.encoding())
        html = projex.text.encoded(html, self.encoding())
        html = html.replace('style="-qt-paragraph-type:empty',
                     'class="paragraph_empty" style="-qt-paragraph-type:empty')
        
        # strip out any existing style's because we want to control this
        # via style sheets
        results = re.findall(r'\<(\w+)\s+(style="[^"]*")', html)
        for tag, tag_style in results:
            # keep any span stylings as these are inline additions
            if tag == 'span':
                continue
            
            html = html.replace(tag_style, '')
        
        start = '<body '
        end = '</body>'
        
        start_i = html.find(start)
        end_i = html.find(end)
        
        stripped = html[start_i+len(start):end_i]
        
        return '<div class="%s" %s</div>' % (style, stripped)
    
    def toHtml(self):
        """
        Returns the text as HTML.
        
        :return     <str>
        """
        if self.editor().toPlainText():
            return self.editor().toHtml()
        return ''
    
    def toPlainText(self):
        """
        Returns the text as plain text.
        
        :return     <str>
        """
        return self.editor().toPlainText()
    
    def updateFontPicker(self):
        """
        Updates the font picker widget to the current font settings.
        """
        font = self.currentFont()
        self._fontPickerWidget.setPointSize(font.pointSize())
        self._fontPickerWidget.setCurrentFamily(font.family())
    
    x_encoding = Property(str, encoding, setEncoding)
Exemplo n.º 12
0
class XConsoleEdit(XLoggerWidget):
    __designer_icon__ = projexui.resources.find('img/ui/console.png')

    executeRequested = Signal(str)

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

        # create custom properties
        self._scope = __main__.__dict__
        self._initialized = False
        self._completerTree = None
        self._commandStack = []
        self._history = []
        self._currentHistoryIndex = 0
        self._waitingForInput = False
        self._commandLineInteraction = False
        self._highlighter = XPythonHighlighter(self.document())

        # setup the look for the console
        self.setReadOnly(False)
        self.waitForInput()
        self.setConfigurable(False)

    def _information(self, msg):
        locker = QMutexLocker(self._mutex)

        msg = projex.text.nativestring(msg)
        self.moveCursor(QTextCursor.End)
        self.setCurrentMode(logging.INFO)
        self.insertPlainText(msg)
        self.scrollToEnd()

    def _error(self, msg):
        locker = QMutexLocker(self._mutex)

        msg = projex.text.nativestring(msg)
        self.moveCursor(QTextCursor.End)
        self.setCurrentMode(logging.ERROR)
        self.insertPlainText(msg)
        self.scrollToEnd()

        if not self._waitingForInput:
            self._waitingForInput = True
            QTimer.singleShot(50, self.waitForInput)

    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] in ('.', ' '):
                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.
        """
        # generate the command information
        cursor = self.textCursor()
        cursor.movePosition(cursor.EndOfLine)

        line = projex.text.nativestring(cursor.block().text())
        at_end = cursor.atEnd()
        modifiers = QApplication.instance().keyboardModifiers()
        mod_mode = at_end or modifiers == Qt.ShiftModifier

        # test the line for information
        if mod_mode and line.endswith(':'):
            cursor.movePosition(cursor.EndOfLine)

            line = re.sub('^>>> ', '', line)
            line = re.sub('^\.\.\. ', '', line)
            count = len(line) - len(line.lstrip()) + 4

            self.insertPlainText('\n... ' + count * ' ')
            return False

        elif mod_mode and line.startswith('...') and \
            (line.strip() != '...' or not at_end):
            cursor.movePosition(cursor.EndOfLine)
            line = re.sub('^\.\.\. ', '', line)
            count = len(line) - len(line.lstrip())
            self.insertPlainText('\n... ' + count * ' ')
            return False

        # if we're not at the end of the console, then add it to the end
        elif line.startswith('>>>') or line.startswith('...'):
            # move to the top of the command structure
            line = projex.text.nativestring(cursor.block().text())
            while line.startswith('...'):
                cursor.movePosition(cursor.PreviousBlock)
                line = projex.text.nativestring(cursor.block().text())

            # calculate the command
            cursor.movePosition(cursor.EndOfLine)
            line = projex.text.nativestring(cursor.block().text())
            ended = False
            lines = []

            while True:
                # add the new block
                lines.append(line)

                if cursor.atEnd():
                    ended = True
                    break

                # move to the next line
                cursor.movePosition(cursor.NextBlock)
                cursor.movePosition(cursor.EndOfLine)

                line = projex.text.nativestring(cursor.block().text())

                # check for a new command or the end of the command
                if not line.startswith('...'):
                    break

            command = '\n'.join(lines)

            # if we did not end up at the end of the command block, then
            # copy it for modification
            if not (ended and command):
                self.waitForInput()
                self.insertPlainText(command.replace('>>> ', ''))
                cursor.movePosition(cursor.End)
                return False

        else:
            self.waitForInput()
            return False

        self.executeCommand(command)
        return True

    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 commandLineInteraction(self):
        """
        Returns whether or not the console is using interaction like the
        command line.
        
        :return     <bool>
        """
        return self._commandLineInteraction

    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, Qt.Key_PageUp,
                             Qt.Key_PageDown):
            return False

        else:
            self.keyPressEvent(event)

            # update the completer
            cursor = self.textCursor()
            text = projex.text.nativestring(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.nativestring(item.text(0)).startswith(text):
                    self._completerTree.setCurrentItem(item)
                    break

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

            return True

    def executeCommand(self, command):
        """
        Executes the inputed command in the global scope.
        
        :param      command | <unicode>
        
        :return     <variant>
        """
        if not command.strip():
            return self.waitForInput()

        # store the current block
        self._history.append(command)
        self._currentHistoryIndex = len(self._history)

        lines = []
        for line in command.split('\n'):
            line = re.sub('^>>> ', '', line)
            line = re.sub('^\.\.\. ', '', line)
            lines.append(line)

        command = '\n'.join(lines)

        # ensure we are at the end
        self.moveCursor(QTextCursor.End)
        self.scrollToEnd()
        self.insertPlainText('\n')
        cmdresult = None

        try:
            cmdresult = eval(command, self.scope(), self.scope())

        except SyntaxError:
            exec(command) in self.scope(), self.scope()

        else:
            if cmdresult is not None:
                # check to see if the command we executed actually caused
                # the destruction of this object -- if it did, then
                # the commands below will error
                if self.isDestroyed():
                    return

                try:
                    result = projex.text.nativestring(repr(cmdresult))
                except:
                    result = '<<< error formatting result to utf-8 >>>'

                self.information(result)

        finally:
            self.waitForInput()

    def dragEnterEvent(self, event):
        if event.keyboardModifiers() == Qt.ShiftModifier:
            event.acceptProposedAction()
        else:
            super(XConsoleEdit, self).dragEnterEvent(event)

    def dragMoveEvent(self, event):
        if event.keyboardModifiers() == Qt.ShiftModifier:
            event.acceptProposedAction()
        else:
            super(XConsoleEdit, self).dragMoveEvent(event)

    def dropEvent(self, event):
        if event.keyboardModifiers() == Qt.ShiftModifier:
            self.insertPlainText('\n' + projexui.formatDropEvent(event))
        else:
            super(XConsoleEdit, self).dropEvent(event)

    def highlighter(self):
        """
        Returns the console highlighter for this widget.
        
        :return     <XPythonHighlighter>
        """
        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.nativestring(cursor.block().text())

        cursor.movePosition(QTextCursor.StartOfBlock, mode)
        if block.startswith('>>> '):
            cursor.movePosition(QTextCursor.Right, mode, 4)
        elif block.startswith('... '):
            match = re.match('...\s*', block)
            cursor.movePosition(QTextCursor.Right, mode, match.end())

        self.setTextCursor(cursor)

    def insertFromMimeData(self, source):
        """
        Inserts the information from the inputed source.
        
        :param      source | <QMimeData>
        """
        lines = projex.text.nativestring(source.text()).splitlines()
        for i in range(1, len(lines)):
            if not lines[i].startswith('... '):
                lines[i] = '... ' + lines[i]

        if len(lines) > 1:
            lines.append('... ')

        self.insertPlainText('\n'.join(lines))

    def insertNextCommand(self):
        """
        Inserts the previous command from history into the line.
        """
        self._currentHistoryIndex += 1
        if 0 <= self._currentHistoryIndex < len(self._history):
            cmd = self._history[self._currentHistoryIndex]
        else:
            cmd = '>>> '
            self._currentHistoryIndex = -1

        self.replaceCommand(cmd)

    def insertPreviousCommand(self):
        """
        Inserts the previous command from history into the line.
        """
        self._currentHistoryIndex -= 1
        if 0 <= self._currentHistoryIndex < len(self._history):
            cmd = self._history[self._currentHistoryIndex]
        else:
            cmd = '>>> '
            self._currentHistoryIndex = len(self._history)

        self.replaceCommand(cmd)

    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()
            event.accept()

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

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

            # update the completer
            cursor = self.textCursor()
            text = projex.text.nativestring(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 or \
             (Qt.Key_A <= event.key() <= Qt.Key_Z):
            super(XConsoleEdit, self).keyPressEvent(event)
            self.startCompletion(force=event.key() == Qt.Key_Period)

        # 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)

        # Ctrl+Up will load previous commands
        elif event.key() == Qt.Key_Up:
            if self.commandLineInteraction() or \
               event.modifiers() & Qt.ControlModifier:
                self.insertPreviousCommand()
                event.accept()
            else:
                super(XConsoleEdit, self).keyPressEvent(event)

        # Ctrl+Down will load future commands
        elif event.key() == Qt.Key_Down:
            if self.commandLineInteraction() or \
               event.modifiers() & Qt.ControlModifier:
                self.insertNextCommand()
                event.accept()
            else:
                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.nativestring(cursor.block().text())
        position = cursor.positionInBlock() - 1

        if not text:
            return (None, '')

        symbol = ''
        for match in re.finditer('[\w\.]+', text):
            if match.start() <= position <= match.end():
                symbol = match.group()
                break

        if not symbol:
            return (None, '')

        parts = symbol.split('.')
        if len(parts) == 1:
            return (self.scope(), parts[0])

        part = parts[0]
        obj = self.scope().get(part)
        for part in parts[1:-1]:
            try:
                obj = getattr(obj, part)
            except AttributeError:
                return (None, '')

        return (obj, parts[-1])

    def restoreSettings(self, settings):
        hist = []
        settings.beginGroup('console')
        for key in sorted(settings.childKeys()):
            hist.append(unwrapVariant(settings.value(key)))
        settings.endGroup()

        self._history = hist

    def saveSettings(self, settings):
        settings.beginGroup('console')
        for i, text in enumerate(self._history):
            settings.setValue('command_{0}'.format(i), wrapVariant(text))
        settings.endGroup()

    def showEvent(self, event):
        super(XConsoleEdit, self).showEvent(event)

        if not self._initialized:
            self._initialized = True

            # create connections
            if os.environ.get('XUI_DISABLE_CONSOLE') != '1':
                hook = XIOHook.instance()
                hook.printed.connect(self._information)
                hook.errored.connect(self._error)

            # setup the header
            opts = {'version': sys.version, 'platform': sys.platform}
            self.setText(HEADER.format(**opts))
            self.waitForInput()

    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 replaceCommand(self, cmd):
        # move to the top of the command structure
        self.moveCursor(QTextCursor.End)
        cursor = self.textCursor()

        line = projex.text.nativestring(cursor.block().text())
        while line.startswith('...'):
            cursor.movePosition(cursor.PreviousBlock)
            line = projex.text.nativestring(cursor.block().text())

        # calculate the command
        cursor.movePosition(cursor.StartOfLine)
        cursor.movePosition(cursor.End, cursor.KeepAnchor)
        cursor.removeSelectedText()
        cursor.insertText(cmd)
        self.moveCursor(cursor.End)

    def scope(self):
        """
        Returns the dictionary scope that will be used when working
        with this editor.
        
        :return     <dict>
        """
        return self._scope

    def setCommandLineInteraction(self, state=True):
        """
        Sets whether or not the interaction should follow command-line
        standards (Up/Down navigation) or not (CTRL+Up/Down).
        
        :param      state | <bool>
        """
        self._commandLineInteraction = state

    def setScope(self, scope):
        """
        Sets the scope that will be used for this editor.
        
        :param      scope | <dict>
        """
        self._scope = scope

    def startCompletion(self, force=False):
        """
        Starts a new completion popup for the current object.
        
        :return     <bool> success
        """
        # add the top level items
        tree = self.completerTree()
        if not force and tree.isVisible():
            return

        tree.clear()

        # make sure we have a valid object
        obj, remain = self.objectAtCursor()

        if obj is None:
            tree.hide()
            return

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

        # compare the ids since some things might overload the __eq__
        # comparator
        if id(obj) == id(self._scope):
            o_keys = obj.keys()
        elif obj is not None:
            o_keys = dir(obj)

        keys = [key for key in sorted(o_keys) if not key.startswith('_')]
        if id(obj) == id(self._scope):
            if not remain:
                return False
            else:
                keys = filter(lambda x: x.startswith(remain[0]), keys)

        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.
        """
        self._waitingForInput = False

        try:
            if self.isDestroyed() or self.isReadOnly():
                return
        except RuntimeError:
            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.nativestring(self.textCursor().block().text()):
            newln = '\n' + newln

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

        self._blankCache = ''

    x_commandLineInteraction = Property(bool, commandLineInteraction,
                                        setCommandLineInteraction)
Exemplo n.º 13
0
class XBoolComboBox(XComboBox):
    def __init__(self, parent=None):
        super(XBoolComboBox, self).__init__(parent)

        # setup properties
        self.addItem('True')
        self.addItem('False')

    def falseText(self):
        """
        Returns the text that will be shown for a false state.
        
        :return     <str>
        """
        return self.itemText(0)

    def isChecked(self):
        """
        Returns whether or not this combo box is in a checked (True) state.
        
        :return     <bool>
        """
        return self.currentIndex() == 0

    def setChecked(self, state):
        """
        Sets whether or not this combo box is in a checked (True) state.
        
        :param      state | <bool>
        """
        if state:
            index = 0
        else:
            index = 1

        self.setCurrentIndex(index)

    def setFalseText(self, text):
        """
        Sets the text that will be shown for a false state.
        
        :param      text | <str>
        """
        self.setItemText(1, text)

    def setTrueText(self, text):
        """
        Sets the text that will be shown for a true state.
        
        :param      text | <str>
        """
        self.setItemText(0, text)

    def trueText(self):
        """
        Returns the text that will be shown for a true state.
        
        :return     <str>
        """
        return self.itemText(0)

    x_checked = Property(bool, isChecked, setChecked)
    x_falseText = Property(str, falseText, setFalseText)
    x_trueText = Property(str, trueText, setTrueText)
Exemplo n.º 14
0
class XOrbQuickFilterWidget(QWidget):
    """ """
    __designer_group__ = 'ProjexUI - ORB'

    queryEntered = Signal(object)

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

        # define custom properties
        self._tableType = None
        self._plugins = []
        self._filterFormat = ''
        self._pluginFactory = XOrbQueryPluginFactory()

        self.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Preferred)
        layout = QVBoxLayout()
        layout.setContentsMargins(0, 3, 0, 3)
        self.setLayout(layout)
        self.setContextMenuPolicy(Qt.CustomContextMenu)

        self.customContextMenuRequested.connect(self.showMenu)

    def filterFormat(self):
        """
        Returns the text that defines the filtering options for this widget.
        
        :return     <str>
        """
        return self._filterFormat

    def pluginFactory(self):
        """
        Returns the plugin for this filter widget.
        
        :return     <XOrbQueryPluginFactory>
        """
        return self._pluginFactory

    def query(self):
        """
        Builds the query for this quick filter.
        
        :return     <orb.Query>
        """
        output = Query()
        for column, op, plugin, editor in self._plugins:
            query = Query(column)
            if plugin.setupQuery(query, op, editor):
                output &= query
        return output

    def keyPressEvent(self, event):
        """
        Listens for the enter event to check if the query is setup.
        """
        if event.key() in (Qt.Key_Enter, Qt.Key_Return):
            self.queryEntered.emit(self.query())

        super(XOrbQuickFilterWidget, self).keyPressEvent(event)

    def rebuild(self):
        """
        Rebuilds the data associated with this filter widget.
        """
        table = self.tableType()
        form = nativestring(self.filterFormat())

        if not table and form:
            if self.layout().count() == 0:
                self.layout().addWidget(QLabel(form, self))
            else:
                self.layout().itemAt(0).widget().setText(form)
            return

        elif not form:
            return

        for child in self.findChildren(QWidget):
            child.close()
            child.setParent(None)
            child.deleteLater()

        self.setUpdatesEnabled(False)
        schema = table.schema()

        vlayout = self.layout()
        for i in range(vlayout.count()):
            vlayout.takeAt(0)

        self._plugins = []
        for line in form.split('\n'):
            row = QHBoxLayout()
            row.setContentsMargins(0, 0, 0, 0)
            row.setSpacing(0)

            for label, lookup in FORMAT_SPLITTER.findall(line):
                # create the label
                lbl = QLabel(label, self)
                row.addWidget(lbl)

                # create the query plugin
                opts = lookup.split(':')
                if len(opts) == 1:
                    opts.append('is')

                column = schema.column(opts[0])
                if not column:
                    continue

                plugin = self.pluginFactory().plugin(column)
                if not plugin:
                    continue

                editor = plugin.createEditor(self, column, opts[1], None)
                if editor:
                    editor.setObjectName(opts[0])
                    row.addWidget(editor)
                    self._plugins.append((opts[0], opts[1], plugin, editor))

            row.addStretch(1)
            vlayout.addLayout(row)

        self.setUpdatesEnabled(True)
        self.adjustSize()

    def showMenu(self, point):
        """
        Displays the menu for this filter widget.
        """
        menu = QMenu(self)
        acts = {}
        acts['edit'] = menu.addAction('Edit quick filter...')

        trigger = menu.exec_(self.mapToGlobal(point))

        if trigger == acts['edit']:
            text, accepted = XTextEdit.getText(self.window(),
                                               'Edit Format',
                                               'Format:',
                                               self.filterFormat(),
                                               wrapped=False)

            if accepted:
                self.setFilterFormat(text)

    def setQuery(self, query):
        """
        Sets the query information for this filter widget.
        
        :param      query | <orb.Query> || None
        """
        if query is None:
            return

        count = {}

        for widget in self.findChildren(QWidget):
            column = nativestring(widget.objectName())
            count.setdefault(column, 0)
            count[column] += 1

            success, value, _ = query.findValue(column, count[column])
            if success:
                projexui.setWidgetValue(widget, value)

    def setPluginFactory(self, pluginFactory):
        """
        Sets the plugin factory for this widget to the inputed factory.
        
        :param      pluginFactory | <XOrbPluginFactory>
        """
        self._pluginFactory = pluginFactory

    def setFilterFormat(self, format):
        """
        Sets the text that defines the filtering options for this widget.
        
        :param      filterFormat | <str>
        """
        self._filterFormat = format
        self.rebuild()

    def setTableType(self, tableType):
        """
        Sets the table type associated with this filter widget.
        
        :param      tableType | <subclass of orb.TableType>
        """
        self._tableType = tableType
        self.rebuild()

    def tableType(self):
        """
        Returns the table type associated with this filter widget.
        
        :return     <subclass of orb.TableType>
        """
        return self._tableType

    x_filterFormat = Property(str, filterFormat, setFilterFormat)
Exemplo n.º 15
0
class XEnumBox(XComboBox):
    """ 
    Creates a widget for editing enumerated types easily.  By default, 
    this will be a single selection widget, but if you want a bit or operation 
    for the value, set the checkable state to True.
    
    This class inherits the [[./xcombobox-XComboBox|XComboBox]] class and 
    utilizes the enum class from the [[$API/projex/enum-enum|projex.enum]] module.
    
    == Example ==
    
    |>>> from projexui.widgets.xenumbox import XEnumBox
    |>>> import projexui
    |
    |>>> # create the enum box
    |>>> combo = projexui.testWidget(XEnumBox)
    |
    |>>> # create the enum type
    |>>> from projex.enum import enum
    |>>> Type = enum('Normal', 'Rounded', 'Smooth')
    |
    |>>> # link the enum to the combo
    |>>> combo.setEnum(Type)
    |
    |>>> # set the enum value
    |>>> combo.setCurrentValue(Type.Smooth)
    |
    |>>> # set the combobox enum values
    |>>> combo.setCheckable(True)
    |
    |>>> # set multiple values at once
    |>>> combo.setCurrentValue(Type.Smooth | Type.Rounded)
    |
    |>>> # retrieve the current value
    |>>> combo.currentValue()
    |4
    |
    |>>> # connect to signals
    |>>> def printValue(value): print value
    |>>> combo.valueChanged.connect(printValue)
    
    """
    valueChanged = Signal(int)

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

        # define custom properties
        self._enum = None
        self._required = False
        self._sortByKey = True

        # set default properties

        # create connections
        self.currentIndexChanged.connect(self.emitValueChanged)
        self.checkedIndexesChanged.connect(self.emitValueChanged)

    def currentValue(self):
        """
        Returns the current value for the widget.  If this widget is checkable
        then the bitor value for all checked items is returned, otherwise, the
        selected value is returned.
        
        :return     <int>
        """
        enum = self.enum()
        if (self.isCheckable()):
            value = 0
            for i in self.checkedIndexes():
                value |= enum[nativestring(self.itemText(i))]
            return value
        else:
            try:
                return enum[nativestring(self.itemText(self.currentIndex()))]
            except KeyError:
                return 0

    def enum(self):
        """
        Returns the enum type that is linked with this widget.
        
        :return     <projex.enum.enum>
        """
        return self._enum

    def emitValueChanged(self):
        """
        Emits the value changed signal with the current value if the signals
        for this widget aren't currently being blocked.
        """
        if not self.signalsBlocked():
            self.valueChanged.emit(self.currentValue())

    def isRequired(self):
        """
        Returns whether or not a value is required for this enumeration.
        
        :return     <bool>
        """
        return self._required

    def reload(self):
        """
        Reloads the contents for this box.
        """
        enum = self._enum
        if not enum:
            return

        self.clear()

        if not self.isRequired():
            self.addItem('')

        if self.sortByKey():
            self.addItems(sorted(enum.keys()))
        else:
            items = enum.items()
            items.sort(key=lambda x: x[1])

            self.addItems(map(lambda x: x[0], items))

    def setEnum(self, enum):
        """
        Sets the enum to the inputed enumerated value.
        
        :param      enum | <projex.enum.enum>
        """
        self._enum = enum
        self.reload()

    def setRequired(self, state):
        """
        Sets whether or not the value is required for this enumeration.
        
        :param      state | <bool>
        """
        self._required = state

    def setCurrentValue(self, value):
        """
        Sets the value for the combobox to the inputed value.  If the combobox 
        is in a checkable state, then the values will be checked, otherwise,
        the value will be selected.
        
        :param      value | <int>
        """
        enum = self.enum()
        if not enum:
            return

        if self.isCheckable():
            indexes = []
            for i in range(self.count()):
                try:
                    check_value = enum[nativestring(self.itemText(i))]
                except KeyError:
                    continue

                if check_value & value:
                    indexes.append(i)
            self.setCheckedIndexes(indexes)
        else:
            try:
                text = enum[value]
            except (AttributeError, KeyError):
                return

            self.setCurrentIndex(self.findText(text))

    def setSortByKey(self, state):
        """
        Returns whether or not this enum box should sort by key.  If False,
        then the values will be entered based on the value.
        
        :param     state | <bool>
        """
        self._sortByKey = state

    def sortByKey(self):
        """
        Returns whether or not this enum box should sort by key.  If False,
        then the values will be entered based on the value.
        
        :return     <bool>
        """
        return self._sortByKey

    x_required = Property(bool, isRequired, setRequired)
    x_sortByKey = Property(bool, sortByKey, setSortByKey)
Exemplo n.º 16
0
class XToolButton(QtGui.QToolButton):
    def __init__(self, *args):
        super(XToolButton, self).__init__(*args)

        # define custom properties
        self._colored = False
        self._shadowed = False
        self._shadowRadius = 20
        self._clickable = True
        self._angle = 0
        self._flipVertical = False
        self._flipHorizontal = False
        self._movie = None
        self._blinking = False
        self._blinkInterval = 500  # msecs
        self._hoverable = False
        self._hoverIcon = None

        # assign this toolbutton on a XToolBar class
        if len(args) > 0:
            parent = args[0]
            if isinstance(parent, xtoolbar.XToolBar):
                palette = self.parent().palette()
                self.setPalette(palette)

                self.setToolButtonStyle(parent.toolButtonStyle())
                self.triggered.connect(parent.actionTriggered)
                self.setShadowed(parent.isShadowed())
                self.setColored(parent.isColored())

        # update the ui when it is toggled
        self.toggled.connect(self.updateUi)

    def _updateFrame(self):
        """
        Sets the icon for this button to the frame at the given number.
        """
        self.setIcon(QtGui.QIcon(self._movie.currentPixmap()))

    def angle(self):
        """
        Returns the angle that this button should be rotated.
        
        :return     <int>
        """
        return self._angle

    def blink(self, state=True):
        """
        Starts or stops the blinking state for this button.  This only
        works for when the toolbutton is in Shadowed or Colored mode.
        
        :param      state | <bool>
        
        :return     <bool> | success
        """
        if self._blinking == state:
            return True
        elif not self.graphicsEffect():
            return False
        else:
            self._blinking = state
            if state:
                self.startTimer(self.blinkInterval())

    def blinkInterval(self):
        """
        Returns the number of milliseconds that this button will blink for
        when it is in the blinking state.
        
        :return     <int>
        """
        return self._blinkInterval

    def cleanup(self):
        """
        Cleanup references to the movie when this button is destroyed.
        """
        if self._movie is not None:
            self._movie.frameChanged.disconnect(self._updateFrame)
            self._movie = None

    def enterEvent(self, event):
        if self.isHoverable():
            super(XToolButton, self).setIcon(self._hoverIcon)

        if self.isShadowed():
            if self.isClickable() and self.isEnabled():
                effect = self.graphicsEffect()
                palette = self.palette()
                clr = palette.color(palette.Shadow)
                effect.setColor(clr)

        elif self.isColored():
            if self.isClickable() and self.isEnabled():
                effect = self.graphicsEffect()
                effect.setStrength(1)

        else:
            super(XToolButton, self).enterEvent(event)

    def flipHorizontal(self):
        """
        Returns whether or not the button should be flipped horizontally.
        
        :return     <bool>
        """
        return self._flipHorizontal

    def flipVertical(self):
        """
        Returns whether or not the button should be flipped horizontally.
        
        :return     <bool>
        """
        return self._flipVertical

    def movie(self):
        """
        Returns the movie instance associated with this button.
        
        :return     <QtGui.QMovie> || None
        """
        return self._movie

    def isBlinking(self):
        """
        Returns whether or not this button is currently blinking.
        
        :return     <bool>
        """
        return self._blinking

    def isClickable(self):
        return self._clickable

    def isColored(self):
        return self._colored

    def isHoverable(self):
        """
        Returns whether or not this button should hide its icon when not hovered.

        :return     <bool>
        """
        return self._hoverable

    def isShadowed(self):
        return self._shadowed

    def leaveEvent(self, event):
        if self.isHoverable():
            super(XToolButton, self).setIcon(QtGui.QIcon())

        if self.isShadowed() or self.isColored():
            self.updateUi()
        else:
            super(XToolButton, self).leaveEvent(event)

    def paintEvent(self, event):
        """
        Overloads the paint even to render this button.
        """
        if self.isHoverable() and self.icon().isNull():
            return

        # initialize the painter
        painter = QtGui.QStylePainter()
        painter.begin(self)
        try:
            option = QtGui.QStyleOptionToolButton()
            self.initStyleOption(option)

            # generate the scaling and rotating factors
            x_scale = 1
            y_scale = 1

            if self.flipHorizontal():
                x_scale = -1
            if self.flipVertical():
                y_scale = -1

            center = self.rect().center()
            painter.translate(center.x(), center.y())
            painter.rotate(self.angle())
            painter.scale(x_scale, y_scale)
            painter.translate(-center.x(), -center.y())

            painter.drawComplexControl(QtGui.QStyle.CC_ToolButton, option)
        finally:
            painter.end()

    def setAngle(self, angle):
        """
        Sets the angle that this button should be rotated.
        
        :param      angle | <int>
        """
        self._angle = angle

    def setBlinkInterval(self, msecs):
        """
        Sets the number of milliseconds that this button will blink for
        when it is in the blinking state.
        
        :param      msecs | <int>
        """
        self._blinkInterval = msecs

    def setClickable(self, state):
        self._clickable = state

        if not state:
            self.setStyleSheet(UNCLICKABLE_SHEET)
        elif self.isShadowed() or self.isColored():
            self.setStyleSheet(CLICKABLE_SHEET)
        else:
            self.setStyleSheet('')

    def setColored(self, state):
        self._colored = state
        if state:
            self._shadowed = False
            palette = self.palette()

            effect = QtGui.QGraphicsColorizeEffect(self)
            effect.setStrength(0)
            effect.setColor(palette.color(palette.Highlight))

            self.setGraphicsEffect(effect)
            if self.isClickable():
                self.setStyleSheet(CLICKABLE_SHEET)
            else:
                self.setStyleSheet(UNCLICKABLE_SHEET)
            self.updateUi()
        else:
            self.setStyleSheet('')
            self.setGraphicsEffect(None)
            self.blink(False)

    def setHoverable(self, state):
        """
        Sets whether or not this is a hoverable button.  When in a hoverable state, the icon will only
        be visible when the button is hovered on.

        :param      state | <bool>
        """
        self._hoverable = state
        self._hoverIcon = self.icon()

    def setIcon(self, icon):
        super(XToolButton, self).setIcon(icon)

        if self.isHoverable():
            self._hoverIcon = icon

    def setEnabled(self, state):
        """
        Updates the drop shadow effect for this widget on enable/disable
        state change.
        
        :param      state | <bool>
        """
        super(XToolButton, self).setEnabled(state)

        self.updateUi()

    def setFlipHorizontal(self, state):
        """
        Sets whether or not the button should be flipped horizontally.
        
        :param      state | <bool>
        """
        self._flipHorizontal = state

    def setFlipVertical(self, state):
        """
        Sets whether or not the button should be flipped vertically.
        
        :param      state | <bool>
        """
        self._flipVertical = state

    def setMovie(self, movie):
        """
        Sets the movie instance for this button.
        
        :param      movie | <QtGui.QMovie>
        """
        if self._movie is not None:
            self._movie.frameChanged.disconnect(self._updateFrame)

        self._movie = movie

        if movie is not None:
            self._updateFrame()
            self._movie.frameChanged.connect(self._updateFrame)
            self.destroyed.connect(self.cleanup)

    def setPalette(self, palette):
        """
        Sets the palette for this button to the inputed palette.  This will
        update the drop shadow to the palette's Shadow color property if
        the shadowed mode is on.
        
        :param      palette | <QtGui.QPalette>
        """
        super(XToolButton, self).setPalette(palette)
        self.updateUi()

    def setShadowRadius(self, radius):
        self._shadowRadius = radius

    def setShadowed(self, state):
        self._shadowed = state
        if state:
            self._colored = False

            effect = QtGui.QGraphicsDropShadowEffect(self)
            effect.setColor(QtGui.QColor(0, 0, 0, 0))
            effect.setOffset(0, 0)
            effect.setBlurRadius(self.shadowRadius())

            self.setGraphicsEffect(effect)
            if self.isClickable():
                self.setStyleSheet(CLICKABLE_SHEET)
            else:
                self.setStyleSheet(UNCLICKABLE_SHEET)
            self.updateUi()
        else:
            self.setStyleSheet('')
            self.setGraphicsEffect(None)
            self.blink(False)

    def shadowRadius(self):
        return self._shadowRadius

    def showEvent(self, event):
        super(XToolButton, self).showEvent(event)

        if self.isHoverable():
            super(XToolButton, self).setIcon(QtGui.QIcon())

    def timerEvent(self, event):
        effect = self.graphicsEffect()
        if not (effect and self.isBlinking()):
            self.killTimer(event.timerId())

        elif isinstance(effect, QtGui.QGraphicsDropShadowEffect):
            palette = self.palette()
            transparent = QtGui.QColor(0, 0, 0, 0)
            clr = palette.color(palette.Shadow)
            if effect.color() == transparent:
                effect.setColor(clr)
            else:
                effect.setColor(transparent)

        elif isinstance(effect, QtGui.QGraphicsColorizeEffect):
            effect.setStrength(int(not effect.strength()))

    def updateUi(self):
        if not self.isClickable():
            return

        effect = self.graphicsEffect()

        if isinstance(effect, QtGui.QGraphicsDropShadowEffect):
            palette = self.palette()
            transparent = QtGui.QColor(0, 0, 0, 0)
            clr = palette.color(palette.Shadow)
            show = self.isChecked() and self.isEnabled()
            effect.setColor(transparent if not show else clr)

        elif isinstance(effect, QtGui.QGraphicsColorizeEffect):
            effect.setStrength(1 if self.isChecked() else 0)

    # define properties
    x_angle = Property(int, angle, setAngle)
    x_clickable = Property(bool, isClickable, setClickable)
    x_colored = Property(bool, isColored, setColored)
    x_flipHorizontal = Property(bool, flipHorizontal, setFlipHorizontal)
    x_flipVertical = Property(bool, flipVertical, setFlipVertical)
    x_shadowRadius = Property(int, shadowRadius, setShadowRadius)
    x_shadowed = Property(bool, isShadowed, setShadowed)
Exemplo n.º 17
0
class XUrlWidget(QWidget):
    urlChanged = Signal(str)
    urlEdited = Signal()

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

        # define the interface
        self._urlEdit = XLineEdit(self)
        self._urlButton = QToolButton(self)

        self._urlButton.setAutoRaise(True)
        self._urlButton.setIcon(QIcon(resources.find('img/web.png')))
        self._urlButton.setToolTip('Browse Link')
        self._urlButton.setFocusPolicy(Qt.NoFocus)

        self._urlEdit.setHint('http://')

        layout = QHBoxLayout()
        layout.setContentsMargins(0, 0, 0, 0)
        layout.setSpacing(0)
        layout.addWidget(self._urlEdit)
        layout.addWidget(self._urlButton)

        self.setLayout(layout)
        self.setFocusPolicy(Qt.StrongFocus)

        # create connections
        self._urlEdit.textChanged.connect(self.urlChanged)
        self._urlEdit.textEdited.connect(self.urlEdited)
        self._urlButton.clicked.connect(self.browse)

    def blockSignals(self, state):
        """
        Blocks the signals for this widget and its sub-parts.
        
        :param      state | <bool>
        """
        super(XUrlWidget, self).blockSignals(state)
        self._urlEdit.blockSignals(state)
        self._urlButton.blockSignals(state)

    def browse(self):
        """
        Brings up a web browser with the address in a Google map.
        """
        webbrowser.open(self.url())

    def hint(self):
        """
        Returns the hint associated with this widget.
        
        :return     <str>
        """
        return self._urlEdit.hint()

    def lineEdit(self):
        """
        Returns the line edit linked with this widget.
        
        :return     <XLineEdit>
        """
        return self._urlEdit

    def setFocus(self):
        """
        Sets the focus for this widget on its line edit.
        """
        self._urlEdit.setFocus()

    @Slot(str)
    def setHint(self, hint):
        """
        Sets the hint associated with this widget.
        
        :param      hint | <str>
        """
        self._urlEdit.setHint(hint)

    @Slot(str)
    def setUrl(self, url):
        """
        Sets the url for this widget to the inputed url.
        
        :param      url | <str>
        """
        self._urlEdit.setText(nativestring(url))

    def url(self):
        """
        Returns the current url from the edit.
        
        :return     <str>
        """
        return nativestring(self._urlEdit.text())

    x_hint = Property(str, hint, setHint)
    x_url = Property(str, url, setUrl)
Exemplo n.º 18
0
class XOrbColumnEdit(QWidget):
    """ """
    __designer_group__ = 'ProjexUI - ORB'
    
    def __init__( self, parent = None ):
        super(XOrbColumnEdit, self).__init__( parent )
        
        # define custom properties
        
        # set default properties
        self._columnType = None
        self._columnName = ''
        self._options    = None
        self._editor     = None
        
        # set the layout for this object
        layout = QHBoxLayout()
        layout.setContentsMargins(0, 0, 0, 0)
        self.setLayout(layout)
        
        if ( ColumnType ):
            self.setColumnType(ColumnType.String)
        
        # create connections
    
    def columnName( self ):
        """
        Returns the column name that will be used for this record edit.
        
        :return     <str>
        """
        return self._columnName
    
    def columnType( self ):
        """
        Returns the column type that is linked with this widget.
        
        :return     <orb.ColumnType>
        """
        return self._columnType
    
    def columnTypeText( self ):
        """
        Returns the string representation of the current column type.
        
        :return     <str>
        """
        if ( ColumnType ):
            try:
                return ColumnType[self._columnType]
            except KeyError:
                return 0
        return 0
    
    def editor( self ):
        """
        Returns the editor instance being used for this widget.
        
        :return     <QWidget> || None
        """
        return self._editor
    
    def label( self ):
        """
        Returns the label for this widget.  Varies per type, not all
        types have labels.
        
        :return     <str>
        """
        if ( self._editor and hasattr(self._editor, 'label') ):
            return self._editor.label()
        return ''
    
    def isReadOnly( self ):
        """
        Returns the read only for this widget from the editor.
        Differs per type, not all types support read only.
        
        :param      text | <str>
        """
        if ( self._editor and hasattr(self._editor, 'isReadOnly') ):
            return self._editor.isReadOnly()
        return False
    
    def rebuild( self ):
        """
        Clears out all the child widgets from this widget and creates the 
        widget that best matches the column properties for this edit.
        """
        plugins.init()
        
        self.blockSignals(True)
        self.setUpdatesEnabled(False)
        
        # clear the old editor
        if ( self._editor ):
            self._editor.close()
            self._editor.setParent(None)
            self._editor.deleteLater()
            self._editor = None
        
        # create a new widget
        plugin_class = plugins.widgets.get(self._columnType)
        if ( plugin_class ):
            self._editor = plugin_class(self)
            self.layout().addWidget(self._editor)
        
        self.blockSignals(False)
        self.setUpdatesEnabled(True)
    
    def setColumn( self, column ):
        """
        Sets the column instance for this edit to the given column.
        
        :param      column | <orb.Column>
        """
        if ( not column ):
            return
        
        self._columnName = column.name()
        
        if ( column.columnType() != ColumnType.ForeignKey ):
            return
        
        if ( self._editor ):
            self._editor.setTableType(column.referenceModel())
            self._editor.setRequired(column.required())
    
    def setColumnName( self, columnName ):
        """
        Returns the column name that will be used for this edit.
        
        :param      columnName | <str>
        """
        self._columnName = str(columnName)
    
    @Slot(int)
    def setColumnType( self, columnType ):
        """
        Sets the column type for this widget to the inputed type value.
        This will reset the widget to use one of the plugins for editing the
        value of the column.
        
        :param      columnType | <orb.ColumnType>
        """
        if ( columnType == self._columnType ):
            return False
        
        self._columnType = columnType
        self.rebuild()
        return True
    
    @Slot(str)
    def setColumnTypeText( self, columnTypeText ):
        """
        Sets the column type for this widget based on the inputed text.
        
        :param      columnTypeText | <str>
        """
        if ( not ColumnType ):
            return False
        
        try:
            columnType = ColumnType[str(columnTypeText)]
        except KeyError:
            return False
        
        return self.setColumnType(columnType)
    
    def setLabel( self, text ):
        """
        Sets the label for this widget to the inputed text.  Differs per type.
        
        :param      text | <str>
        """
        if ( self._editor and hasattr(self._editor, 'setLabel') ):
            self._editor.setLabel(text)
            return True
        return False
    
    def setReadOnly( self, state ):
        """
        Sets the read only for this widget to the inputed state.  
        Differs per type, not all types support read only.
        
        :param      text | <str>
        """
        if ( self._editor and hasattr(self._editor, 'setReadOnly') ):
            self._editor.setReadOnly(state)
            return True
        return False
    
    @Slot(PyObject)
    def setValue( self, value ):
        """
        Sets the value for this edit to the inputed value.
        
        :param      value | <variant>
        """
        if ( self._editor ):
            self._editor.setValue(value)
            return True
        return False
    
    def value( self ):
        """
        Returns the current value for this widget.
        
        :return     <variant>
        """
        if ( self._editor ):
            return self._editor.value()
        return None
    
    x_columnTypeText = Property(str, columnTypeText, setColumnTypeText)
    x_columnName     = Property(str, columnName, setColumnName)
    x_label          = Property(str, label, setLabel)
    x_readOnly       = Property(bool, isReadOnly, setReadOnly)
Exemplo n.º 19
0
class XLineEdit(QLineEdit):
    """
    Creates a new QLineEdit that allows the user to define a grayed out text
    hint that will be drawn when there is no text assigned to the widget.
    """

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

    hintChanged = Signal(str)
    textEntered = Signal(str)

    State = enum('Normal', 'Passed', 'Failed')

    InputFormat = enum('Normal', 'CamelHump', 'Underscore', 'Dash',
                       'ClassName', 'NoSpaces', 'Capitalize', 'Uppercase',
                       'Lowercase', 'Pretty', 'Package')

    def __init__(self, *args):
        super(XLineEdit, self).__init__(*args)

        palette = self.palette()
        hint_clr = palette.color(palette.Disabled, palette.Text)

        # set the hint property
        self._hint = ''
        self._hintPrefix = ''
        self._hintSuffix = ''
        self._spacer = '_'
        self._encoding = 'utf-8'
        self._hintColor = hint_clr
        self._buttonWidth = 0
        self._cornerRadius = 0
        self._currentState = XLineEdit.State.Normal
        self._inputFormat = XLineEdit.InputFormat.Normal
        self._selectAllOnFocus = False
        self._focusedIn = False
        self._useHintValue = True

        self._icon = QIcon()
        self._iconSize = QSize(14, 14)
        self._buttons = {}

        self.textChanged.connect(self.adjustText)
        self.returnPressed.connect(self.emitTextEntered)

    def adjustText(self):
        """
        Updates the text based on the current format options.
        """
        pos = self.cursorPosition()
        self.blockSignals(True)
        super(XLineEdit, self).setText(self.formatText(self.text()))
        self.setCursorPosition(pos)
        self.blockSignals(False)

    def addButton(self, button, alignment=None):
        """
        Adds a button the edit.  All the buttons will be layed out at the \
        end of the widget.
        
        :param      button      | <QToolButton>
                    alignment   | <Qt.Alignment>
        
        :return     <bool> | success
        """
        if alignment == None:
            if button.pos().x() < self.pos().x():
                alignment = Qt.AlignLeft
            else:
                alignment = Qt.AlignRight

        all_buttons = self.buttons()
        if button in all_buttons:
            return False

        # move the button to this edit
        button.setParent(self)
        button.setAutoRaise(True)
        button.setIconSize(self.iconSize())
        button.setCursor(Qt.ArrowCursor)
        button.setFixedSize(QSize(self.height() - 2, self.height() - 2))

        self._buttons.setdefault(alignment, [])
        self._buttons[alignment].append(button)
        self.adjustButtons()
        return True

    def adjustButtons(self):
        """
        Adjusts the placement of the buttons for this line edit.
        """
        y = 1

        for btn in self.buttons():
            btn.setIconSize(self.iconSize())
            btn.setFixedSize(QSize(self.height() - 2, self.height() - 2))

        # adjust the location for the left buttons
        left_buttons = self._buttons.get(Qt.AlignLeft, [])
        x = (self.cornerRadius() / 2.0) + 2

        for btn in left_buttons:
            btn.move(x, y)
            x += btn.width()

        # adjust the location for the right buttons
        right_buttons = self._buttons.get(Qt.AlignRight, [])

        w = self.width()
        bwidth = sum([btn.width() for btn in right_buttons])
        bwidth += (self.cornerRadius() / 2.0) + 1

        for btn in right_buttons:
            btn.move(w - bwidth, y)
            bwidth -= btn.width()

        self._buttonWidth = sum([btn.width() for btn in self.buttons()])
        self.adjustTextMargins()

    def adjustTextMargins(self):
        """
        Adjusts the margins for the text based on the contents to be displayed.
        """
        left_buttons = self._buttons.get(Qt.AlignLeft, [])

        if left_buttons:
            bwidth = left_buttons[-1].pos().x() + left_buttons[-1].width() - 4
        else:
            bwidth = 0 + (max(8, self.cornerRadius()) - 8)

        ico = self.icon()
        if ico and not ico.isNull():
            bwidth += self.iconSize().width()

        self.setTextMargins(bwidth, 0, 0, 0)

    def adjustStyleSheet(self):
        """
        Adjusts the stylesheet for this widget based on whether it has a \
        corner radius and/or icon.
        """
        radius = self.cornerRadius()
        icon = self.icon()

        if not self.objectName():
            self.setStyleSheet('')
        elif not (radius or icon):
            self.setStyleSheet('')
        else:
            palette = self.palette()

            options = {}
            options['corner_radius'] = radius
            options['padding'] = 5
            options['objectName'] = self.objectName()

            if icon and not icon.isNull():
                options['padding'] += self.iconSize().width() + 2

            self.setStyleSheet(LINEEDIT_STYLE % options)

    def buttons(self):
        """
        Returns all the buttons linked to this edit.
        
        :return     [<QToolButton>, ..]
        """
        all_buttons = []
        for buttons in self._buttons.values():
            all_buttons += buttons
        return all_buttons

    def clear(self):
        """
        Clears the text from the edit.
        """
        super(XLineEdit, self).clear()

        self.textEntered.emit('')
        self.textChanged.emit('')
        self.textEdited.emit('')

    def cornerRadius(self):
        """
        Returns the rounding radius for this widget's corner, allowing a \
        developer to round the edges for a line edit on the fly.
        
        :return     <int>
        """
        return self._cornerRadius

    def currentState(self):
        """
        Returns the current state for this line edit.
        
        :return     <XLineEdit.State>
        """
        return self._currentState

    def currentText(self):
        """
        Returns the text that is available currently, \
        if the user has set standard text, then that \
        is returned, otherwise the hint is returned.
        
        :return     <str>
        """
        text = nativestring(self.text())
        if text or not self.useHintValue():
            return text
        return self.hint()

    def emitTextEntered(self):
        """
        Emits the text entered signal for this line edit, provided the
        signals are not being blocked.
        """
        if not self.signalsBlocked():
            self.textEntered.emit(self.text())

    def encoding(self):
        return self._encoding

    def focusInEvent(self, event):
        """
        Updates the focus in state for this edit.
        
        :param      event | <QFocusEvent>
        """
        super(XLineEdit, self).focusInEvent(event)

        self._focusedIn = True

    def focusOutEvent(self, event):
        """
        Updates the focus in state for this edit.
        
        :param      event | <QFocusEvent>
        """
        super(XLineEdit, self).focusOutEvent(event)

        self._focusedIn = False

    def formatText(self, text):
        """
        Formats the inputed text based on the input format assigned to this
        line edit.
        
        :param      text | <str>
        
        :return     <str> | frormatted text
        """
        format = self.inputFormat()
        if format == XLineEdit.InputFormat.Normal:
            return text

        text = projex.text.nativestring(text)
        if format == XLineEdit.InputFormat.CamelHump:
            return projex.text.camelHump(text)

        elif format == XLineEdit.InputFormat.Pretty:
            return projex.text.pretty(text)

        elif format == XLineEdit.InputFormat.Underscore:
            return projex.text.underscore(text)

        elif format == XLineEdit.InputFormat.Dash:
            return projex.text.dashed(text)

        elif format == XLineEdit.InputFormat.ClassName:
            return projex.text.classname(text)

        elif format == XLineEdit.InputFormat.NoSpaces:
            return projex.text.joinWords(text, self.spacer())

        elif format == XLineEdit.InputFormat.Capitalize:
            return text.capitalize()

        elif format == XLineEdit.InputFormat.Uppercase:
            return text.upper()

        elif format == XLineEdit.InputFormat.Lowercase:
            return text.lower()

        elif format == XLineEdit.InputFormat.Package:
            return '.'.join(
                map(lambda x: x.lower(),
                    map(projex.text.classname, text.split('.'))))

        return text

    def hint(self):
        """
        Returns the hint value for this line edit.
        
        :return     <str>
        """
        parts = (self._hintPrefix, self._hint, self._hintSuffix)
        return ''.join(map(projex.text.nativestring, parts))

    def hintPrefix(self):
        """
        Returns the default prefix for the hint.
        
        :return     <str>
        """
        return self._hintPrefix

    def hintSuffix(self):
        """
        Returns the default suffix for the hint.
        
        :return     <str>
        """
        return self._hintSuffix

    def hintColor(self):
        """
        Returns the hint color for this text item.
        
        :return     <QColor>
        """
        return self._hintColor

    def icon(self):
        """
        Returns the icon instance that is being used for this widget.
        
        :return     <QIcon> || None
        """
        return self._icon

    def iconSize(self):
        """
        Returns the icon size that will be used for this widget.
        
        :return     <QSize>
        """
        return self._iconSize

    def inputFormat(self):
        """
        Returns the input format for this widget.
        
        :return     <int>
        """
        return self._inputFormat

    def inputFormatText(self):
        """
        Returns the input format as a text value for this widget.
        
        :return     <str>
        """
        return XLineEdit.InputFormat[self.inputFormat()]

    def mousePressEvent(self, event):
        """
        Selects all the text if the property is set after this widget
        first gains focus.
        
        :param      event | <QMouseEvent>
        """
        super(XLineEdit, self).mousePressEvent(event)

        if self._focusedIn and self.selectAllOnFocus():
            self.selectAll()
            self._focusedIn = False

    def paintEvent(self, event):
        """
        Overloads the paint event to paint additional \
        hint information if no text is set on the \
        editor.
        
        :param      event      | <QPaintEvent>
        """
        super(XLineEdit, self).paintEvent(event)

        # paint the hint text if not text is set
        if self.text() and not (self.icon() and not self.icon().isNull()):
            return

        # paint the hint text
        with XPainter(self) as painter:
            painter.setPen(self.hintColor())

            icon = self.icon()
            left, top, right, bottom = self.getTextMargins()

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

            w -= (right + left)
            h -= (bottom + top)

            if icon and not icon.isNull():
                size = icon.actualSize(self.iconSize())
                x = self.cornerRadius() + 2
                y = (self.height() - size.height()) / 2.0

                painter.drawPixmap(x, y,
                                   icon.pixmap(size.width(), size.height()))

                w -= size.width() - 2
            else:
                x = 6 + left

            w -= self._buttonWidth
            y = 2 + top

            # create the elided hint
            if not self.text() and self.hint():
                rect = self.cursorRect()
                metrics = QFontMetrics(self.font())
                hint = metrics.elidedText(self.hint(), Qt.ElideRight, w)
                align = self.alignment()

                if align & Qt.AlignHCenter:
                    x = 0
                else:
                    x = rect.center().x()

                painter.drawText(x, y, w, h, align, hint)

    def resizeEvent(self, event):
        """
        Overloads the resize event to handle updating of buttons.
        
        :param      event | <QResizeEvent>
        """
        super(XLineEdit, self).resizeEvent(event)
        self.adjustButtons()

    def selectAllOnFocus(self):
        """
        Returns whether or not this edit will select all its contents on
        focus in.
        
        :return     <bool>
        """
        return self._selectAllOnFocus

    def setCornerRadius(self, radius):
        """
        Sets the corner radius for this widget tot he inputed radius.
        
        :param      radius | <int>
        """
        self._cornerRadius = radius

        self.adjustStyleSheet()

    def setCurrentState(self, state):
        """
        Sets the current state for this edit to the inputed state.
        
        :param      state | <XLineEdit.State>
        """
        self._currentState = state

        palette = self.palette()
        if state == XLineEdit.State.Normal:
            palette = QApplication.instance().palette()

        elif state == XLineEdit.State.Failed:
            palette.setColor(palette.Base, QColor('#ffc9bc'))
            palette.setColor(palette.Text, QColor('#aa2200'))
            palette.setColor(palette.Disabled, palette.Text, QColor('#e58570'))

        elif state == XLineEdit.State.Passed:
            palette.setColor(palette.Base, QColor('#d1ffd1'))
            palette.setColor(palette.Text, QColor('#00aa00'))
            palette.setColor(palette.Disabled, palette.Text, QColor('#75e575'))

        self._hintColor = palette.color(palette.Disabled, palette.Text)
        self.setPalette(palette)

    def setEncoding(self, enc):
        self._encoding = enc

    @Slot(str)
    def setHint(self, hint):
        """
        Sets the hint text to the inputed value.
        
        :param      hint       | <str>
        """
        self._hint = self.formatText(hint)
        self.update()
        self.hintChanged.emit(self.hint())

    def setHintColor(self, clr):
        """
        Sets the color for the hint for this edit.
        
        :param      clr     | <QColor>
        """
        self._hintColor = clr

    def setHintPrefix(self, prefix):
        """
        Ses the default prefix for the hint.
        
        :param     prefix | <str>
        """
        self._hintPrefix = str(prefix)

    def setHintSuffix(self, suffix):
        """
        Sets the default suffix for the hint.
        
        :param     suffix | <str>
        """
        self._hintSuffix = str(suffix)

    def setIcon(self, icon):
        """
        Sets the icon that will be used for this widget to the inputed icon.
        
        :param      icon | <QIcon> || None
        """
        self._icon = QIcon(icon)

        self.adjustStyleSheet()

    def setIconSize(self, size):
        """
        Sets the icon size that will be used for this edit.
        
        :param      size | <QSize>
        """
        self._iconSize = size
        self.adjustTextMargins()

    def setInputFormat(self, inputFormat):
        """
        Sets the input format for this text.
        
        :param      inputFormat | <int>
        """
        self._inputFormat = inputFormat

    def setInputFormatText(self, text):
        """
        Sets the input format text for this widget to the given value.
        
        :param      text | <str>
        """
        try:
            self._inputFormat = XLineEdit.InputFormat[nativestring(text)]
        except KeyError:
            pass

    def setObjectName(self, objectName):
        """
        Updates the style sheet for this line edit when the name changes.
        
        :param      objectName | <str>
        """
        super(XLineEdit, self).setObjectName(objectName)
        self.adjustStyleSheet()

    def setSelectAllOnFocus(self, state):
        """
        Returns whether or not this edit will select all its contents on
        focus in.
        
        :param      state | <bool>
        """
        self._selectAllOnFocus = state

    def setSpacer(self, spacer):
        """
        Sets the spacer that will be used for this line edit when replacing
        NoSpaces input formats.
        
        :param      spacer | <str>
        """
        self._spacer = spacer

    def setUseHintValue(self, state):
        """
        This method sets whether or not the value for this line edit should
        use the hint value if no text is found (within the projexui.xwidgetvalue
        plugin system).  When set to True, the value returned will first look
        at the text of the widget, and if it is blank, will then return the
        hint value.  If it is False, only the text value will be returned.
        
        :param      state | <bool>
        """
        self._useHintValue = state

    def setText(self, text):
        """
        Sets the text for this widget to the inputed text, converting it based \
        on the current input format if necessary.
        
        :param      text | <str>
        """
        if text is None:
            text = ''

        super(XLineEdit, self).setText(
            projex.text.encoded(self.formatText(text), self.encoding()))

    def setVisible(self, state):
        """
        Sets the visible state for this line edit.
        
        :param      state | <bool>
        """
        super(XLineEdit, self).setVisible(state)

        self.adjustStyleSheet()
        self.adjustTextMargins()

    def spacer(self):
        """
        Returns the spacer that is used to replace spaces when the NoSpaces
        input format is used.
        
        :return     <str>
        """
        return self._spacer

    def useHintValue(self):
        """
        This method returns whether or not the value for this line edit should
        use the hint value if no text is found (within the projexui.xwidgetvalue
        plugin system).  When set to True, the value returned will first look
        at the text of the widget, and if it is blank, will then return the
        hint value.  If it is False, only the text value will be returned.
        
        :return     <bool>
        """
        return self._useHintValue

    # create Qt properties
    x_hint = Property(str, hint, setHint)
    x_hintPrefix = Property(str, hintPrefix, setHintPrefix)
    x_hintSuffix = Property(str, hintSuffix, setHintSuffix)
    x_icon = Property('QIcon', icon, setIcon)
    x_iconSize = Property(QSize, iconSize, setIconSize)
    x_hintColor = Property('QColor', hintColor, setHintColor)
    x_cornerRadius = Property(int, cornerRadius, setCornerRadius)
    x_encoding = Property(str, encoding, setEncoding)
    x_inputFormatText = Property(str, inputFormatText, setInputFormatText)
    x_spacer = Property(str, spacer, setSpacer)
    x_selectAllOnFocus = Property(bool, selectAllOnFocus, setSelectAllOnFocus)
    x_useHintValue = Property(bool, useHintValue, setUseHintValue)

    # hack for qt
    setX_icon = setIcon
Exemplo n.º 20
0
class XOrbGridEdit(QWidget):
    """ """
    __designer_group__ = 'ProjexUI - ORB'

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

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

        # define custom properties
        self._queryWidget = XOrbQueryWidget(self)

        self.uiSearchTXT.setIconSize(QSize(28, 28))
        self.uiSearchTXT.addButton(self.uiQueryBTN)

        self.uiQueryBTN.setCentralWidget(self._queryWidget)
        self.uiQueryBTN.setDefaultAnchor(XPopupWidget.Anchor.TopRight)
        popup = self.uiQueryBTN.popupWidget()
        popup.setShowTitleBar(False)

        # set default properties
        self.uiRecordTREE.setGroupingEnabled(False)
        self.uiRecordTREE.setEditable(True)
        self.uiRecordTREE.setPageSize(50)
        self.uiRecordTREE.setTabKeyNavigation(True)

        # create connections
        self.uiRefreshBTN.clicked.connect(self.refresh)
        self.uiSaveBTN.clicked.connect(self.commit)
        self.uiQueryBTN.popupAboutToShow.connect(self.loadQuery)
        self.uiQueryBTN.popupAccepted.connect(self.assignQuery)
        self.uiRecordTREE.headerMenuAboutToShow.connect(self.updateMenu)

        popup.resetRequested.connect(self._queryWidget.reset)

    def autoloadPages(self):
        """
        Returns whether or not to automatically load pages for this edit.
        
        :sa     XOrbTreeWidget.autoloadPages
        
        :return     <bool>
        """
        return self.uiRecordTREE.autoloadPages()

    def assignQuery(self):
        """
        Assigns the query from the query widget to the edit.
        """
        self.uiRecordTREE.setQuery(self._queryWidget.query(), autoRefresh=True)

    def commit(self):
        """
        Commits changes stored in the interface to the database.
        """
        self.uiRecordTREE.commit()

    def disableGrouping(self):
        """
        Disables the grouping component for this edit.
        """
        self.uiRecordTREE.disableGrouping()
        self.refresh()

    def enableGrouping(self):
        """
        Disables the grouping component for this edit.
        """
        self.uiRecordTREE.enableGrouping()
        self.refresh()

    def groupByHeaderIndex(self):
        """
        Groups the records based on the last selected header index.
        """
        self.uiRecordTREE.groupByHeaderIndex()
        self.refresh()

    def isEditable(self):
        """
        Returns whether or not this grid edit is editable.
        
        :return     <bool>
        """
        return self.uiRecordTREE.isEditable()

    def isPaged(self):
        """
        Returns whether or not to pages the results from the database query.
        
        :sa     XOrbTreeWidget.isPaged
        
        :param      state | <bool>
        """
        return self.uiRecordTREE.isPaged()

    def loadQuery(self):
        """
        Loads the query for the query widget when it is being shown.
        """
        self._queryWidget.setQuery(self.query())

    def pageSize(self):
        """
        Returns the number of records that should be loaded per page.
        
        :sa     XOrbTreeWidget.pageSize
        
        :return     <int>
        """
        return self.uiRecordTREE.pageSize()

    def query(self):
        """
        Returns the query that is being represented by the current results.
        
        :return     <orb.Query>
        """
        return self.uiRecordTREE.query()

    def records(self):
        """
        Returns the records that are currently assigned to this widget.
        
        :return     <orb.RecordSet>
        """
        return self.uiRecordTREE.records()

    def refresh(self):
        """
        Commits changes stored in the interface to the database.
        """
        self.uiRecordTREE.refresh(reloadData=True)

    def restoreXml(self, xml):
        """
        Restores the settings for this edit from xml.
        
        :param      xml | <xml.etree.ElementTree>
        """
        self.uiRecordTREE.restoreXml(xml.find('tree'))

        # restore the query
        xquery = xml.find('query')
        if xquery is not None:
            self.setQuery(Q.fromXml(xquery[0]))

    def saveXml(self, xml):
        """
        Saves the settings for this edit to the xml parent.
        
        :param      xparent | <xml.etree.ElementTree>
        """
        # save grouping
        xtree = ElementTree.SubElement(xml, 'tree')
        self.uiRecordTREE.saveXml(xtree)

        # save the query
        query = self.query()
        if query:
            query.toXml(ElementTree.SubElement(xml, 'query'))

    def searchWidget(self):
        """
        Returns the search text edit for this grid edit.
        
        :return     <XLineEdit>
        """
        return self.uiSearchTXT

    def setAutoloadPages(self, state):
        """
        Sets whether or not to automatically load pages for this edit.
        
        :sa     XOrbTreeWidget.setAutoloadPages
        
        :param      state | <bool>
        """
        return self.uiRecordTREE.setAutoloadPages(state)

    def setEditable(self, state):
        """
        Sets the editable state for this grid widget.
        
        :param      state | <bool>
        """
        self.uiRecordTREE.setEditable(state)
        self.uiSaveBTN.setVisible(state)

    def setQuery(self, query, autoRefresh=True):
        """
        Sets the query for this edit to the inputed query.
        
        :param      query | <orb.Query>
        """
        self.uiRecordTREE.setQuery(query, autoRefresh=autoRefresh)

    def setPaged(self, state):
        """
        Sets whether or not to pages the results from the database query.
        
        :sa     XOrbTreeWidget.setPaged
        
        :param      state | <bool>
        """
        return self.uiRecordTREE.setPaged(state)

    def setPageSize(self, size):
        """
        Sets the number of records that should be loaded per page.
        
        :sa     XOrbTreeWidget.setPageSize
        
        :param      size | <int>
        """
        return self.uiRecordTREE.setPageSize(size)

    def setRecords(self, records):
        """
        Sets the records for this widget to the inputed records.
        
        :param      records | [<orb.Table>, ..] || <orb.RecordSet>
        """
        self.uiRecordTREE.setRecords(records)

    def setTableType(self, tableType, autoRefresh=True):
        """
        Sets the table type associated with this edit.
        
        :param      tableType | <subclass of orb.Table>
        """
        self.uiRecordTREE.setTableType(tableType)
        self._queryWidget.setTableType(tableType)

        if autoRefresh:
            self.setQuery(Q())

    def showAdvancedGroupingOptions(self):
        """
        Shows the advanced grouping options for the grid edit.
        """
        pass

    def tableType(self):
        """
        Returns the table type associated with this edit.
        
        :return     <subclass of orb.Table>
        """
        return self.uiRecordTREE.tableType()

    def treeWidget(self):
        """
        Returns the tree widget that is for editing records for this grid.
        
        :return     <XOrbTreeWidget>
        """
        return self.uiRecordTREE

    def updateMenu(self, menu, index):
        tree = self.uiRecordTREE

        first_action = menu.actions()[1]
        column = tree.columnOf(index)

        enable_action = QAction(menu)
        enable_action.setText('Enable Grouping')

        disable_action = QAction(menu)
        disable_action.setText('Disable Grouping')

        quick_action = QAction(menu)
        quick_action.setText('Group by "%s"' % column)

        adv_action = QAction(menu)
        adv_action.setText('More Grouping Options...')

        menu.insertSeparator(first_action)
        menu.insertAction(first_action, enable_action)
        menu.insertAction(first_action, disable_action)
        menu.insertSeparator(first_action)
        menu.insertAction(first_action, quick_action)
        menu.insertAction(first_action, adv_action)

        quick_action.triggered.connect(self.groupByHeaderIndex)
        adv_action.triggered.connect(self.showAdvancedGroupingOptions)
        enable_action.triggered.connect(self.enableGrouping)
        disable_action.triggered.connect(self.disableGrouping)

    x_autoloadPages = Property(bool, autoloadPages, setAutoloadPages)
    x_paged = Property(bool, isPaged, setPaged)
    x_pageSize = Property(int, pageSize, setPageSize)
    x_editable = Property(bool, isEditable, setEditable)
Exemplo n.º 21
0
class XComboBox(QComboBox):
    """
    ~~>[img:widgets/xcombobox.png]
    The XComboBox class is a simple extension to the standard QComboBox
    that provides a couple enhancement features, namely the ability to 
    add a hint to the line edit and supporting multi-selection via checkable
    items.
    
    == Example ==
    
    |>>> from projexui.widgets.xcombobox import XComboBox
    |>>> import projexui
    |
    |>>> # create the combobox
    |>>> combo = projexui.testWidget(XComboBox)
    |
    |>>> # set the hint
    |>>> combo.setHint('select type')
    |
    |>>> # create items, make checkable
    |>>> combo.addItems(['A', 'B', 'C'])
    |>>> combo.setCheckable(True)
    |
    |>>> # set the checked items
    |>>> combo.setCheckedItems(['C'])
    |>>> combo.setCheckedIndexes([0, 2])
    |
    |>>> # retrieve checked items
    |>>> combo.checkedItems()
    |['A', 'C']
    |>>> combo.checkedIndexes()
    |[0, 2]
    |
    |>>> # connect to signals
    |>>> def printChecked(): print checked.checkedItems()
    |>>> combo.checkedIndexesChanged.connect(printChecked)
    |
    |>>> # modify selection and see the output
    """
    __designer_icon__ = resources.find('img/ui/combobox.png')

    checkedIndexesChanged = Signal(list)
    checkedItemsChanged = Signal(list)

    def __init__(self, parent=None):
        # define custom properties
        self._checkable = False
        self._hint = ''
        self._separator = ','
        self._autoRaise = False
        self._hovered = False
        self._lineEdit = None

        # setup the checkable popup widget
        self._checkablePopup = None

        # set default properties
        super(XComboBox, self).__init__(parent)
        self.setLineEdit(XLineEdit(self))

    def autoRaise(self):
        """
        Returns whether or not this combo box should automatically
        raise up.
        
        :return     <bool>
        """
        return self._autoRaise

    def adjustCheckState(self):
        """
        Updates when new items are added to the system.
        """
        if self.isCheckable():
            self.updateCheckState()

    def checkablePopup(self):
        """
        Returns the popup if this widget is checkable.
        
        :return     <QListView> || None
        """
        if not self._checkablePopup and self.isCheckable():
            popup = QListView(self)
            popup.setSelectionMode(QListView.NoSelection)
            popup.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
            popup.setWindowFlags(Qt.Popup)
            popup.installEventFilter(self)
            popup.doubleClicked.connect(self.checkModelIndex)
            self._checkablePopup = popup

        return self._checkablePopup

    def checkModelIndex(self, modelIndex):
        """
        Sets the current index as the checked index.
        
        :param      modelIndex | <QModelIndex>
        """
        self.checkablePopup().hide()

        if not self.isCheckable():
            return

        self.setCheckedIndexes([modelIndex.row()])

    def currentText(self):
        """
        Returns the current text for this combobox, including the hint option \
        if no text is set.
        """
        lineEdit = self.lineEdit()
        if lineEdit:
            return lineEdit.currentText()
        text = nativestring(super(XComboBox, self).currentText())
        if not text:
            return self._hint
        return text

    def checkedIndexes(self):
        """
        Returns a list of checked indexes for this combobox.
        
        :return     [<int>, ..]
        """
        if (not self.isCheckable()):
            return []

        model = self.model()
        return [i for i in range(self.count()) if model.item(i).checkState()]

    def checkedItems(self):
        """
        Returns the checked items for this combobox.
        
        :return     [<str>, ..]
        """
        if not self.isCheckable():
            return []

        return [nativestring(self.itemText(i)) for i in self.checkedIndexes()]

    def enterEvent(self, event):
        self._hovered = True
        super(XComboBox, self).enterEvent(event)

        if self.autoRaise():
            try:
                self.lineEdit().show()
            except AttributeError:
                pass

    def eventFilter(self, object, event):
        """
        Filters events for the popup widget.
        
        :param      object | <QObject>
                    event  | <QEvent>
        """
        # popup the editor when clicking in the line edit for a checkable state
        if object == self.lineEdit() and self.isEnabled():
            if not self.isCheckable():
                return super(XComboBox, self).eventFilter(object, event)

            # show the popup when the user clicks on it
            elif event.type() == event.MouseButtonPress:
                self.showPopup()

            # eat the wheel event when the user is scrolling
            elif event.type() == event.Wheel:
                return True

        # make sure we're looking for the checkable popup
        elif object == self._checkablePopup:
            if event.type() == event.KeyPress and \
                 event.key() in (Qt.Key_Escape, Qt.Key_Return, Qt.Key_Enter):
                object.close()

            elif event.type() == event.MouseButtonPress:
                if not object.geometry().contains(event.pos()):
                    object.close()

        return super(XComboBox, self).eventFilter(object, event)

    def hint(self):
        """
        Returns the hint for this combobox.
        
        :return     <str>
        """
        return self._hint

    def hintColor(self):
        """
        Returns the hint color for this combo box provided its line edit is
        an XLineEdit instance.
        
        :return     <QColor>
        """
        lineEdit = self.lineEdit()
        if isinstance(lineEdit, XLineEdit):
            return lineEdit.hintColor()
        return QColor()

    def isCheckable(self):
        """
        Returns whether or not this combobox has checkable options.
        
        :return     <bool>
        """
        try:
            return self._checkable
        except AttributeError:
            return False

    def items(self):
        """
        Returns the labels for the different items in this combo box.
        
        :return     [<str>, ..]
        """
        return [self.itemText(i) for i in range(self.count())]

    def leaveEvent(self, event):
        self._hovered = False
        super(XComboBox, self).leaveEvent(event)

        if self.autoRaise():
            try:
                self.lineEdit().hide()
            except AttributeError:
                pass

    def lineEdit(self):
        """
        Returns the line editor associated with this combobox.  This will
        return the object stored at the reference for the editor since
        sometimes the internal Qt process will raise a RuntimeError that
        the C/C++ object has been deleted.
        
        :return     <XLineEdit> || None
        """
        try:
            edit = self._lineEdit()
        except TypeError:
            edit = None

        if edit is None:
            self._edit = None
        return edit

    def paintEvent(self, event):
        """
        Paints this combobox based on whether or not it is visible.
        
        :param      event | <QPaintEvent>
        """
        if not self.autoRaise() or (self._hovered and self.isEnabled()):
            super(XComboBox, self).paintEvent(event)

            text = QComboBox.currentText(self)
            if not text and self._hint and not self.lineEdit():
                text = self._hint
                palette = self.palette()
                with XPainter(self) as painter:
                    painter.setPen(
                        palette.color(palette.Disabled, palette.Text))
                    painter.drawText(5, 0, self.width(), self.height(),
                                     Qt.AlignLeft | Qt.AlignVCenter,
                                     self.currentText())

        else:
            palette = self.palette()

            with XPainter(self) as painter:
                text = QComboBox.currentText(self)
                if not text:
                    text = self.hint()
                    painter.setPen(
                        palette.color(palette.Disabled, palette.WindowText))

                painter.drawText(5, 0, self.width(), self.height(),
                                 Qt.AlignLeft | Qt.AlignVCenter, text)

                x = self.width() - 15
                y = 4
                pixmap = QPixmap(
                    resources.find('img/treeview/triangle_down.png'))
                painter.drawPixmap(x, y, pixmap)

    def separator(self):
        """
        Returns the separator that will be used for joining together 
        the options when in checked mode.  By default, this will be a comma.
        
        :return     <str>
        """
        return self._separator

    def setAutoRaise(self, state):
        """
        Sets whether or not this combo box should automatically
        raise up.
        
        :param      state | <bool>
        """
        self._autoRaise = state
        self.setMouseTracking(state)
        try:
            self.lineEdit().setVisible(not state)
        except AttributeError:
            pass

    def setCheckedIndexes(self, indexes):
        """
        Sets a list of checked indexes for this combobox.
        
        :param      indexes | [<int>, ..]
        """
        if not self.isCheckable():
            return

        model = self.model()
        model.blockSignals(True)
        for i in range(self.count()):
            if not self.itemText(i):
                continue

            item = model.item(i)

            if i in indexes:
                state = Qt.Checked
            else:
                state = Qt.Unchecked

            item.setCheckState(state)
        model.blockSignals(False)
        self.updateCheckedText()

    def setCheckedItems(self, items):
        """
        Returns the checked items for this combobox.
        
        :return     items | [<str>, ..]
        """
        if not self.isCheckable():
            return

        model = self.model()
        for i in range(self.count()):
            item_text = self.itemText(i)
            if not item_text:
                continue

            if nativestring(item_text) in items:
                state = Qt.Checked
            else:
                state = Qt.Unchecked

            model.item(i).setCheckState(state)

    def setCheckable(self, state):
        """
        Sets whether or not this combobox stores checkable items.
        
        :param      state | <bool>
        """
        self._checkable = state

        # need to be editable to be checkable
        edit = self.lineEdit()
        if state:
            self.setEditable(True)
            edit.setReadOnly(True)

            # create connections
            model = self.model()
            model.rowsInserted.connect(self.adjustCheckState)
            model.dataChanged.connect(self.updateCheckedText)

        elif edit:
            edit.setReadOnly(False)

        self.updateCheckState()
        self.updateCheckedText()

    def setEditable(self, state):
        """
        Sets whether or not this combobox will be editable, updating its \
        line edit to an XLineEdit if necessary.
        
        :param      state | <bool>
        """
        super(XComboBox, self).setEditable(state)

        if state:
            edit = self.lineEdit()
            if edit and isinstance(edit, XLineEdit):
                return
            elif edit:
                edit.setParent(None)
                edit.deleteLater()

            edit = XLineEdit(self)
            edit.setHint(self.hint())
            self.setLineEdit(edit)

    def setLineEdit(self, edit):
        """
        Sets the line edit for this widget.
        
        :warning    If the inputed edit is NOT an instance of XLineEdit, \
                    this method will destroy the edit and create a new \
                    XLineEdit instance.
        
        :param      edit | <XLineEdit>
        """
        if edit and not isinstance(edit, XLineEdit):
            edit.setParent(None)
            edit.deleteLater()

            edit = XLineEdit(self)

        if edit is not None:
            edit.installEventFilter(self)
            self._lineEdit = weakref.ref(edit)
        else:
            self._lineEdit = None

        super(XComboBox, self).setLineEdit(edit)

    def setHint(self, hint):
        """
        Sets the hint for this line edit that will be displayed when in \
        editable mode.
        
        :param      hint | <str>
        """
        self._hint = hint

        lineEdit = self.lineEdit()
        if isinstance(lineEdit, XLineEdit):
            lineEdit.setHint(hint)

    def setHintColor(self, color):
        """
        Sets the hint color for this combo box provided its line edit is
        an XLineEdit instance.
        
        :param      color | <QColor>
        """
        lineEdit = self.lineEdit()
        if isinstance(lineEdit, XLineEdit):
            lineEdit.setHintColor(color)

    @Slot(str)
    def setSeparator(self, separator):
        """
        Sets the separator that will be used when joining the checked items
        for this combo in the display.
        
        :param      separator | <str>
        """
        self._separator = nativestring(separator)
        self.updateCheckedText()

    def showPopup(self):
        """
        Displays a custom popup widget for this system if a checkable state \
        is setup.
        """
        if not self.isCheckable():
            return super(XComboBox, self).showPopup()

        if not self.isVisible():
            return

        # update the checkable widget popup
        point = self.mapToGlobal(QPoint(0, self.height() - 1))
        popup = self.checkablePopup()
        popup.setModel(self.model())
        popup.move(point)
        popup.setFixedWidth(self.width())

        height = (self.count() * 19) + 2
        if height > 400:
            height = 400
            popup.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOn)
        else:
            popup.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff)

        popup.setFixedHeight(height)
        popup.show()
        popup.raise_()

    def updateCheckState(self):
        """
        Updates the items to reflect the current check state system.
        """
        checkable = self.isCheckable()
        model = self.model()
        flags = Qt.ItemIsSelectable | Qt.ItemIsEnabled

        for i in range(self.count()):
            item = model.item(i)

            if not (checkable and item.text()):
                item.setCheckable(False)
                item.setFlags(flags)

            # only allow checking for items with text
            else:
                item.setCheckable(True)
                item.setFlags(flags | Qt.ItemIsUserCheckable)

    def updateCheckedText(self):
        """
        Updates the text in the editor to reflect the latest state.
        """
        if not self.isCheckable():
            return

        indexes = self.checkedIndexes()
        items = self.checkedItems()

        if len(items) < 2 or self.separator():
            self.lineEdit().setText(self.separator().join(items))
        else:
            self.lineEdit().setText('{0} items selected'.format(len(items)))

        if not self.signalsBlocked():
            self.checkedItemsChanged.emit(items)
            self.checkedIndexesChanged.emit(indexes)

    def toggleModelIndex(self, modelIndex):
        """
        Toggles the index's check state.
        
        :param      modelIndex | <QModelIndex>
        """
        if not self.isCheckable():
            return

        item = self.model().item(modelIndex.row())
        if item.checkState() == Qt.Checked:
            state = Qt.Unchecked
        else:
            state = Qt.Checked

        item.setCheckState(state)

    # define qt properties
    x_hint = Property(str, hint, setHint)
    x_checkable = Property(bool, isCheckable, setCheckable)
    x_separator = Property(str, separator, setSeparator)
    x_autoRaise = Property(bool, autoRaise, setAutoRaise)
Exemplo n.º 22
0
class XStackedWidget(QStackedWidget):
    __designer_container__ = True
    __designer_xml__ = """\
<widget class="XStackedWidget" name="stackedWidget">
    <property name="geometry">
        <rect>
            <x>100</x>
            <y>60</y>
            <width>321</width>
            <height>391</height>
        </rect>
    </property>
    <widget class="QWidget" name="page"/>
    <widget class="QWidget" name="page_2"/>
</widget>"""

    animationFinished = Signal()

    Direction = enum('LeftToRight', 'RightToLeft', 'TopToBottom',
                     'BottomToTop', 'Automatic')

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

        # define custom properties
        self._animationType = QEasingCurve.Linear
        self._vertical = False
        self._wrap = False
        self._active = False
        self._speed = 250
        self._nextIndex = 0
        self._lastIndex = 0
        self._lastPoint = None

    def _finishAnimation(self):
        """
        Cleans up post-animation.
        """
        self.setCurrentIndex(self._nextIndex)
        self.widget(self._lastIndex).hide()
        self.widget(self._lastIndex).move(self._lastPoint)
        self._active = False

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

    def animationType(self):
        """
        Returns the animation curve type for this widget.
        
        :return     <QEasingCurve.Type>
        """
        return self._animationType

    def clear(self):
        """
        Clears out the widgets from this stack.
        """
        for i in range(self.count() - 1, -1, -1):
            w = self.widget(i)
            if w:
                self.removeWidget(w)
                w.close()
                w.deleteLater()

    def isVerticalMode(self):
        """
        Returns whether or not the animation will play vertically or not.
        
        :return     <bool>
        """
        return self._vertical

    @Slot(QEasingCurve.Type)
    def setAnimationType(self, animationType):
        """
        Sets the animation curve type for this widget.
        
        :param      animationType | <QEasingCurve.Type>
        """
        self._animationType = animationType

    @Slot(int)
    def setSpeed(self, speed):
        """
        Sets the speed for this widget.
        
        :param      speed | <int>
        """
        self._speed = speed

    @Slot(bool)
    def setVerticalMode(self, state=True):
        """
        Sets whether or not the animation will play vertically or not.
        
        :param      state | <bool>
        """
        self._vertical = state

    @Slot(bool)
    def setWrap(self, state=True):
        """
        Sets whether or not the stacked widget will wrap during the animation.
        
        :param      state | <bool>
        """
        self._wrap = state

    @Slot(int)
    def slideIn(self, index, direction=Direction.Automatic):
        """
        Slides in the panel at the inputed index in the given
        direction for this widget.
        
        :param      index | <int>
                    direction | <XStackedWidget.Direction>
        
        :return     <bool> | success
        """
        # do not allow multiple slides while it is active
        if self._active:
            return False

        # determine the proper index to calculate
        invert = False
        if self.count() <= index:
            if not self.wrap():
                return False
            index = self.count() % index
            invert = True
        elif index < 0:
            if not self.wrap():
                return False
            index = self.count() + index
            invert = True

        # define the direction information
        if index == self.currentIndex():
            return False
        elif self.currentIndex() < index:
            if direction == XStackedWidget.Direction.Automatic:
                if self.isVerticalMode():
                    direction = XStackedWidget.Direction.BottomToTop
                else:
                    direction = XStackedWidget.Direction.RightToLeft
        else:
            if direction == XStackedWidget.Direction.Automatic:
                if self.isVerticalMode():
                    direction = XStackedWidget.Direction.TopToBottom
                else:
                    direction = XStackedWidget.Direction.LeftToRight

        # invert the animation if we are wrapping
        if invert:
            if direction == XStackedWidget.Direction.BottomToTop:
                direction = XStackedWidget.Direction.TopToBottom
            elif direction == XStackedWidget.Direction.TopToBottom:
                direction = XStackedWidget.Direction.BottomToTop
            elif direction == XStackedWidget.Direction.LeftToRight:
                direction = XStackedWidget.Direction.RightToLeft
            else:
                direction = XStackedWidget.Direction.LeftToRight

        self._active = True
        offset_x = self.frameRect().width()
        offset_y = self.frameRect().height()

        next_widget = self.widget(index)
        curr_widget = self.widget(self.currentIndex())

        next_widget.setGeometry(0, 0, offset_x, offset_y)

        if direction == XStackedWidget.Direction.BottomToTop:
            offset_x = 0
            offset_y = -offset_y
        elif direction == XStackedWidget.Direction.TopToBottom:
            offset_x = 0
        elif direction == XStackedWidget.Direction.RightToLeft:
            offset_x = -offset_x
            offset_y = 0
        elif direction == XStackedWidget.Direction.LeftToRight:
            offset_y = 0

        next_point = next_widget.pos()
        curr_point = curr_widget.pos()

        self._nextIndex = index
        self._lastIndex = self.currentIndex()
        self._lastPoint = QPoint(curr_point)

        next_widget.move(next_point.x() - offset_x, next_point.y() - offset_y)
        next_widget.raise_()
        next_widget.show()

        curr_anim = QPropertyAnimation(curr_widget, 'pos')
        curr_anim.setDuration(self.speed())
        curr_anim.setEasingCurve(self.animationType())
        curr_anim.setStartValue(curr_point)
        curr_anim.setEndValue(
            QPoint(curr_point.x() + offset_x,
                   curr_point.y() + offset_y))

        next_anim = QPropertyAnimation(next_widget, 'pos')
        next_anim.setDuration(self.speed())
        next_anim.setEasingCurve(self.animationType())
        next_anim.setStartValue(
            QPoint(next_point.x() - offset_x,
                   next_point.y() - offset_y))
        next_anim.setEndValue(next_point)

        anim_group = QParallelAnimationGroup(self)
        anim_group.addAnimation(curr_anim)
        anim_group.addAnimation(next_anim)

        anim_group.finished.connect(self._finishAnimation)
        anim_group.finished.connect(anim_group.deleteLater)
        anim_group.start()

        return True

    @Slot()
    def slideInNext(self):
        """
        Slides in the next slide for this widget.
        
        :return     <bool> | success
        """
        return self.slideIn(self.currentIndex() + 1)

    @Slot()
    def slideInPrev(self):
        """
        Slides in the previous slide for this widget.
        
        :return     <bool> | success
        """
        return self.slideIn(self.currentIndex() - 1)

    def speed(self):
        """
        Returns the speed property for this stacked widget.
        
        :return     <int>
        """
        return self._speed

    def wrap(self):
        """
        Returns whether or not the stacked widget will wrap during the
        animation.
        
        :return     <bool>
        """
        return self._wrap

    x_animationType = Property(QEasingCurve.Type, animationType,
                               setAnimationType)

    x_speed = Property(int, speed, setSpeed)
    x_verticalMode = Property(bool, isVerticalMode, setVerticalMode)
    x_wrap = Property(bool, wrap, setWrap)
Exemplo n.º 23
0
class XOrbRecordBox(XComboBox):
    __designer_group__ = 'ProjexUI - ORB'
    """ Defines a combo box that contains records from the ORB system. """
    loadRequested = Signal(object)

    loadingStarted = Signal()
    loadingFinished = Signal()
    currentRecordChanged = Signal(object)
    currentRecordEdited = Signal(object)
    initialized = Signal()

    def __init__(self, parent=None):
        # needs to be defined before the base class is initialized or the
        # event filter won't work
        self._treePopupWidget = None

        super(XOrbRecordBox, self).__init__(parent)

        # define custom properties
        self._currentRecord = None  # only used while loading
        self._changedRecord = -1

        self._tableTypeName = ''
        self._tableLookupIndex = ''
        self._baseHints = ('', '')
        self._tableType = None
        self._order = None
        self._query = None
        self._iconMapper = None
        self._labelMapper = str
        self._required = True
        self._loaded = False
        self._showTreePopup = False
        self._autoInitialize = False
        self._threadEnabled = True
        self._specifiedColumns = None
        self._specifiedColumnsOnly = False

        # create threading options
        self._worker = XOrbLookupWorker()
        self._workerThread = QThread()
        self._worker.moveToThread(self._workerThread)
        self._worker.setBatched(False)
        self._workerThread.start()

        # create connections
        self.loadRequested.connect(self._worker.loadRecords)
        self.lineEdit().textEntered.connect(self.assignCurrentRecord)
        self.lineEdit().editingFinished.connect(self.emitCurrentRecordEdited)
        self.lineEdit().returnPressed.connect(self.emitCurrentRecordEdited)

        self._worker.loadingStarted.connect(self.markLoadingStarted)
        self._worker.loadingFinished.connect(self.markLoadingFinished)
        self._worker.loadedRecords.connect(self.addRecordsFromThread)

        self.currentIndexChanged.connect(self.emitCurrentRecordChanged)
        QApplication.instance().aboutToQuit.connect(self.__cleanupWorker)

    def __del__(self):
        self.__cleanupWorker()

    def __cleanupWorker(self):
        if not self._workerThread:
            return

        thread = self._workerThread
        worker = self._worker

        self._workerThread = None
        self._worker = None

        worker.deleteLater()

        thread.finished.connect(thread.deleteLater)
        thread.quit()
        thread.wait()

    def addRecord(self, record):
        """
        Adds the given record to the system.
        
        :param      record | <str>
        """
        label_mapper = self.labelMapper()
        icon_mapper = self.iconMapper()

        self.addItem(label_mapper(record))
        self.setItemData(self.count() - 1, wrapVariant(record), Qt.UserRole)

        # load icon
        if icon_mapper:
            self.setItemIcon(self.count() - 1, icon_mapper(record))

        if self.showTreePopup():
            XOrbRecordItem(self.treePopupWidget(), record)

    def addRecords(self, records):
        """
        Adds the given record to the system.
        
        :param      records | [<orb.Table>, ..]
        """
        label_mapper = self.labelMapper()
        icon_mapper = self.iconMapper()

        # create the items to display
        tree = None
        if self.showTreePopup():
            tree = self.treePopupWidget()
            tree.blockSignals(True)
            tree.setUpdatesEnabled(False)

        # add the items to the list
        start = self.count()
        self.addItems(map(label_mapper, records))

        # update the item information
        for i, record in enumerate(records):
            index = start + i

            self.setItemData(index, wrapVariant(record), Qt.UserRole)

            if icon_mapper:
                self.setItemIcon(index, icon_mapper(record))

            if tree:
                XOrbRecordItem(tree, record)

        if tree:
            tree.blockSignals(False)
            tree.setUpdatesEnabled(True)

    def addRecordsFromThread(self, records):
        """
        Adds the given record to the system.
        
        :param      records | [<orb.Table>, ..]
        """
        label_mapper = self.labelMapper()
        icon_mapper = self.iconMapper()

        tree = None
        if self.showTreePopup():
            tree = self.treePopupWidget()

        # add the items to the list
        start = self.count()

        # update the item information
        blocked = self.signalsBlocked()
        self.blockSignals(True)
        for i, record in enumerate(records):
            index = start + i
            self.addItem(label_mapper(record))
            self.setItemData(index, wrapVariant(record), Qt.UserRole)

            if icon_mapper:
                self.setItemIcon(index, icon_mapper(record))

            if record == self._currentRecord:
                self.setCurrentIndex(self.count() - 1)

            if tree:
                XOrbRecordItem(tree, record)
        self.blockSignals(blocked)

    def acceptRecord(self, item):
        """
        Closes the tree popup and sets the current record.
        
        :param      record | <orb.Table>
        """
        record = item.record()
        self.treePopupWidget().close()
        self.setCurrentRecord(record)

    def assignCurrentRecord(self, text):
        """
        Assigns the current record from the inputed text.
        
        :param      text | <str>
        """
        if self.showTreePopup():
            item = self._treePopupWidget.currentItem()
            if item:
                self._currentRecord = item.record()
            else:
                self._currentRecord = None
            return

        # look up the record for the given text
        if text:
            index = self.findText(text)
        elif self.isRequired():
            index = 0
        else:
            index = -1

        # determine new record to look for
        record = self.recordAt(index)
        if record == self._currentRecord:
            return

        # set the current index and record for any changes
        self._currentRecord = record
        self.setCurrentIndex(index)

    def autoInitialize(self):
        """
        Returns whether or not this record box should auto-initialize its
        records.
        
        :return     <bool>
        """
        return self._autoInitialize

    def batchSize(self):
        """
        Returns the batch size to use when processing this record box's list
        of entries.
        
        :return     <int>
        """
        return self._worker.batchSize()

    def checkedRecords(self):
        """
        Returns a list of the checked records from this combo box.
        
        :return     [<orb.Table>, ..]
        """
        indexes = self.checkedIndexes()
        return map(self.recordAt, indexes)

    def currentRecord(self):
        """
        Returns the record found at the current index for this combo box.
        
        :rerturn        <orb.Table> || None
        """
        if self._currentRecord is None and self.isRequired():
            self._currentRecord = self.recordAt(self.currentIndex())
        return self._currentRecord

    def dragEnterEvent(self, event):
        """
        Listens for query's being dragged and dropped onto this tree.
        
        :param      event | <QDragEnterEvent>
        """
        data = event.mimeData()
        if data.hasFormat('application/x-orb-table') and \
           data.hasFormat('application/x-orb-query'):
            tableName = self.tableTypeName()
            if str(data.data('application/x-orb-table')) == tableName:
                event.acceptProposedAction()
                return
        elif data.hasFormat('application/x-orb-records'):
            event.acceptProposedAction()
            return

        super(XOrbRecordBox, self).dragEnterEvent(event)

    def dragMoveEvent(self, event):
        """
        Listens for query's being dragged and dropped onto this tree.
        
        :param      event | <QDragEnterEvent>
        """
        data = event.mimeData()
        if data.hasFormat('application/x-orb-table') and \
           data.hasFormat('application/x-orb-query'):
            tableName = self.tableTypeName()
            if str(data.data('application/x-orb-table')) == tableName:
                event.acceptProposedAction()
                return
        elif data.hasFormat('application/x-orb-records'):
            event.acceptProposedAction()
            return

        super(XOrbRecordBox, self).dragMoveEvent(event)

    def dropEvent(self, event):
        """
        Listens for query's being dragged and dropped onto this tree.
        
        :param      event | <QDropEvent>
        """
        # overload the current filtering options
        data = event.mimeData()
        if data.hasFormat('application/x-orb-table') and \
           data.hasFormat('application/x-orb-query'):
            tableName = self.tableTypeName()
            if str(data.data('application/x-orb-table')) == tableName:
                data = str(data.data('application/x-orb-query'))
                query = Q.fromXmlString(data)
                self.setQuery(query)
                return

        elif self.tableType() and data.hasFormat('application/x-orb-records'):
            from projexui.widgets.xorbtreewidget import XOrbTreeWidget
            records = XOrbTreeWidget.dataRestoreRecords(data)

            for record in records:
                if isinstance(record, self.tableType()):
                    self.setCurrentRecord(record)
                    return

        super(XOrbRecordBox, self).dropEvent(event)

    def emitCurrentRecordChanged(self):
        """
        Emits the current record changed signal for this combobox, provided \
        the signals aren't blocked.
        """
        record = unwrapVariant(self.itemData(self.currentIndex(), Qt.UserRole))
        if not Table.recordcheck(record):
            record = None

        self._currentRecord = record
        if not self.signalsBlocked():
            self._changedRecord = record
            self.currentRecordChanged.emit(record)

    def emitCurrentRecordEdited(self):
        """
        Emits the current record edited signal for this combobox, provided the
        signals aren't blocked and the record has changed since the last time.
        """
        if self._changedRecord == -1:
            return

        if self.signalsBlocked():
            return

        record = self._changedRecord
        self._changedRecord = -1
        self.currentRecordEdited.emit(record)

    def eventFilter(self, object, event):
        """
        Filters events for the popup tree widget.
        
        :param      object | <QObject>
                    event  | <QEvent>
        
        :retuen     <bool> | consumed
        """
        if not (object and object == self._treePopupWidget):
            return super(XOrbRecordBox, self).eventFilter(object, event)

        elif event.type() == event.KeyPress:
            # accept lookup
            if event.key() in (Qt.Key_Enter, Qt.Key_Return, Qt.Key_Tab,
                               Qt.Key_Backtab):

                item = object.currentItem()
                text = self.lineEdit().text()

                if not text:
                    record = None
                    item = None

                elif isinstance(item, XOrbRecordItem):
                    record = item.record()

                if record and item.isSelected() and not item.isHidden():
                    self.hidePopup()
                    self.setCurrentRecord(record)
                    event.accept()
                    return True

                else:
                    self.setCurrentRecord(None)
                    self.hidePopup()
                    self.lineEdit().setText(text)
                    self.lineEdit().keyPressEvent(event)
                    event.accept()
                    return True

            # cancel lookup
            elif event.key() == Qt.Key_Escape:
                text = self.lineEdit().text()
                self.setCurrentRecord(None)
                self.lineEdit().setText(text)
                self.hidePopup()
                event.accept()
                return True

            # update the search info
            else:
                self.lineEdit().keyPressEvent(event)

        elif event.type() == event.Show:
            object.resizeToContents()
            object.horizontalScrollBar().setValue(0)

        elif event.type() == event.KeyRelease:
            self.lineEdit().keyReleaseEvent(event)

        elif event.type() == event.MouseButtonPress:
            local_pos = object.mapFromGlobal(event.globalPos())
            in_widget = object.rect().contains(local_pos)

            if not in_widget:
                text = self.lineEdit().text()
                self.setCurrentRecord(None)
                self.lineEdit().setText(text)
                self.hidePopup()
                event.accept()
                return True

        return super(XOrbRecordBox, self).eventFilter(object, event)

    def focusNextChild(self, event):
        if not self.isLoading():
            self.assignCurrentRecord(self.lineEdit().text())

        return super(XOrbRecordBox, self).focusNextChild(event)

    def focusNextPrevChild(self, event):
        if not self.isLoading():
            self.assignCurrentRecord(self.lineEdit().text())

        return super(XOrbRecordBox, self).focusNextPrevChild(event)

    def focusInEvent(self, event):
        """
        When this widget loses focus, try to emit the record changed event
        signal.
        """
        self._changedRecord = -1
        super(XOrbRecordBox, self).focusInEvent(event)

    def focusOutEvent(self, event):
        """
        When this widget loses focus, try to emit the record changed event
        signal.
        """
        if not self.isLoading():
            self.assignCurrentRecord(self.lineEdit().text())

        super(XOrbRecordBox, self).focusOutEvent(event)

    def hidePopup(self):
        """
        Overloads the hide popup method to handle when the user hides
        the popup widget.
        """
        if self._treePopupWidget and self.showTreePopup():
            self._treePopupWidget.close()

        super(XOrbRecordBox, self).hidePopup()

    def iconMapper(self):
        """
        Returns the icon mapping method to be used for this combobox.
        
        :return     <method> || None
        """
        return self._iconMapper

    def isLoading(self):
        """
        Returns whether or not this combobox is loading records.
        
        :return     <bool>
        """
        return self._worker.isRunning()

    def isRequired(self):
        """
        Returns whether or not this combo box requires the user to pick a
        selection.
        
        :return     <bool>
        """
        return self._required

    def isThreadEnabled(self):
        """
        Returns whether or not threading is enabled for this combo box.
        
        :return     <bool>
        """
        return self._threadEnabled

    def labelMapper(self):
        """
        Returns the label mapping method to be used for this combobox.
        
        :return     <method> || None
        """
        return self._labelMapper

    @Slot(object)
    def lookupRecords(self, record):
        """
        Lookups records based on the inputed record.  This will use the 
        tableLookupIndex property to determine the Orb Index method to
        use to look up records.  That index method should take the inputed
        record as an argument, and return a list of records.
        
        :param      record | <orb.Table>
        """
        table_type = self.tableType()
        if not table_type:
            return

        index = getattr(table_type, self.tableLookupIndex(), None)
        if not index:
            return

        self.setRecords(index(record))

    def markLoadingStarted(self):
        """
        Marks this widget as loading records.
        """
        if self.isThreadEnabled():
            XLoaderWidget.start(self)

        if self.showTreePopup():
            tree = self.treePopupWidget()
            tree.setCursor(Qt.WaitCursor)
            tree.clear()
            tree.setUpdatesEnabled(False)
            tree.blockSignals(True)

            self._baseHints = (self.hint(), tree.hint())
            tree.setHint('Loading records...')
            self.setHint('Loading records...')
        else:
            self._baseHints = (self.hint(), '')
            self.setHint('Loading records...')

        self.setCursor(Qt.WaitCursor)
        self.blockSignals(True)
        self.setUpdatesEnabled(False)

        # prepare to load
        self.clear()
        use_dummy = not self.isRequired() or self.isCheckable()
        if use_dummy:
            self.addItem('')

        self.loadingStarted.emit()

    def markLoadingFinished(self):
        """
        Marks this widget as finished loading records.
        """
        XLoaderWidget.stop(self, force=True)

        hint, tree_hint = self._baseHints
        self.setHint(hint)

        # set the tree widget
        if self.showTreePopup():
            tree = self.treePopupWidget()
            tree.setHint(tree_hint)
            tree.unsetCursor()
            tree.setUpdatesEnabled(True)
            tree.blockSignals(False)

        self.unsetCursor()
        self.blockSignals(False)
        self.setUpdatesEnabled(True)
        self.loadingFinished.emit()

    def order(self):
        """
        Returns the ordering for this widget.
        
        :return     [(<str> column, <str> asc|desc, ..] || None
        """
        return self._order

    def query(self):
        """
        Returns the query used when querying the database for the records.
        
        :return     <Query> || None
        """
        return self._query

    def records(self):
        """
        Returns the record list that ist linked with this combo box.
        
        :return     [<orb.Table>, ..]
        """
        records = []
        for i in range(self.count()):
            record = self.recordAt(i)
            if record:
                records.append(record)
        return records

    def recordAt(self, index):
        """
        Returns the record at the inputed index.
        
        :return     <orb.Table> || None
        """
        return unwrapVariant(self.itemData(index, Qt.UserRole))

    def refresh(self, records):
        """
        Refreshs the current user interface to match the latest settings.
        """
        self._loaded = True

        if self.isLoading():
            return

        # load the information
        if RecordSet.typecheck(records):
            table = records.table()
            self.setTableType(table)

            if self.order():
                records.setOrder(self.order())

            # load specific data for this record box
            if self.specifiedColumnsOnly():
                records.setColumns(
                    map(lambda x: x.name(), self.specifiedColumns()))

            # load the records asynchronously
            if self.isThreadEnabled() and \
               table and \
               table.getDatabase().isThreadEnabled():
                # assign ordering based on tree table
                if self.showTreePopup():
                    tree = self.treePopupWidget()
                    if tree.isSortingEnabled():
                        col = tree.sortColumn()
                        colname = tree.headerItem().text(col)
                        column = table.schema().column(colname)

                        if column:
                            if tree.sortOrder() == Qt.AscendingOrder:
                                sort_order = 'asc'
                            else:
                                sort_order = 'desc'

                            records.setOrder([(column.name(), sort_order)])

                self.loadRequested.emit(records)
                return

        # load the records synchronously
        self.loadingStarted.emit()
        curr_record = self.currentRecord()
        self.blockSignals(True)
        self.setUpdatesEnabled(False)
        self.clear()
        use_dummy = not self.isRequired() or self.isCheckable()
        if use_dummy:
            self.addItem('')
        self.addRecords(records)
        self.setUpdatesEnabled(True)
        self.blockSignals(False)
        self.setCurrentRecord(curr_record)
        self.loadingFinished.emit()

    def setAutoInitialize(self, state):
        """
        Sets whether or not this combo box should auto initialize itself
        when it is shown.
        
        :param      state | <bool>
        """
        self._autoInitialize = state

    def setBatchSize(self, size):
        """
        Sets the batch size of records to look up for this record box.
        
        :param      size | <int>
        """
        self._worker.setBatchSize(size)

    def setCheckedRecords(self, records):
        """
        Sets the checked off records to the list of inputed records.
        
        :param      records | [<orb.Table>, ..]
        """
        QApplication.sendPostedEvents(self, -1)
        indexes = []

        for i in range(self.count()):
            record = self.recordAt(i)
            if record is not None and record in records:
                indexes.append(i)

        self.setCheckedIndexes(indexes)

    def setCurrentRecord(self, record, autoAdd=False):
        """
        Sets the index for this combobox to the inputed record instance.
        
        :param      record      <orb.Table>
        
        :return     <bool> success
        """
        if record is not None and not Table.recordcheck(record):
            return False

        # don't reassign the current record
        # clear the record
        if record is None:
            self._currentRecord = None
            blocked = self.signalsBlocked()
            self.blockSignals(True)
            self.setCurrentIndex(-1)
            self.blockSignals(blocked)

            if not blocked:
                self.currentRecordChanged.emit(None)

            return True

        elif record == self.currentRecord():
            return False

        self._currentRecord = record
        found = False

        blocked = self.signalsBlocked()
        self.blockSignals(True)
        for i in range(self.count()):
            stored = unwrapVariant(self.itemData(i, Qt.UserRole))
            if stored == record:
                self.setCurrentIndex(i)
                found = True
                break

        if not found and autoAdd:
            self.addRecord(record)
            self.setCurrentIndex(self.count() - 1)

        self.blockSignals(blocked)

        if not blocked:
            self.currentRecordChanged.emit(record)
        return False

    def setIconMapper(self, mapper):
        """
        Sets the icon mapping method for this combobox to the inputed mapper. \
        The inputed mapper method should take a orb.Table instance as input \
        and return a QIcon as output.
        
        :param      mapper | <method> || None
        """
        self._iconMapper = mapper

    def setLabelMapper(self, mapper):
        """
        Sets the label mapping method for this combobox to the inputed mapper.\
        The inputed mapper method should take a orb.Table instance as input \
        and return a string as output.
        
        :param      mapper | <method>
        """
        self._labelMapper = mapper

    def setOrder(self, order):
        """
        Sets the order for this combo box to the inputed order.  This will
        be used in conjunction with the query when loading records to the
        combobox.
        
        :param      order | [(<str> column, <str> asc|desc), ..] || None
        """
        self._order = order

    def setQuery(self, query, autoRefresh=True):
        """
        Sets the query for this record box for generating records.
        
        :param      query | <Query> || None
        """
        self._query = query

        tableType = self.tableType()
        if not tableType:
            return False

        if autoRefresh:
            self.refresh(tableType.select(where=query))

        return True

    def setRecords(self, records):
        """
        Sets the records on this combobox to the inputed record list.
        
        :param      records | [<orb.Table>, ..]
        """
        self.refresh(records)

    def setRequired(self, state):
        """
        Sets the required state for this combo box.  If the column is not
        required, a blank record will be included with the choices.
        
        :param      state | <bool>
        """
        self._required = state

    def setShowTreePopup(self, state):
        """
        Sets whether or not to use an ORB tree widget in the popup for this
        record box.
        
        :param      state | <bool>
        """
        self._showTreePopup = state

    def setSpecifiedColumns(self, columns):
        """
        Sets the specified columns for this combobox widget.
        
        :param      columns | [<orb.Column>, ..] || [<str>, ..] || None
        """
        self._specifiedColumns = columns
        self._specifiedColumnsOnly = columns is not None

    def setSpecifiedColumnsOnly(self, state):
        """
        Sets whether or not only specified columns should be
        loaded for this record box.
        
        :param      state | <bool>
        """
        self._specifiedColumnsOnly = state

    def setTableLookupIndex(self, index):
        """
        Sets the name of the index method that will be used to lookup
        records for this combo box.
        
        :param    index | <str>
        """
        self._tableLookupIndex = str(index)

    def setTableType(self, tableType):
        """
        Sets the table type for this record box to the inputed table type.
        
        :param      tableType | <orb.Table>
        """
        self._tableType = tableType

        if tableType:
            self._tableTypeName = tableType.schema().name()
        else:
            self._tableTypeName = ''

    def setTableTypeName(self, name):
        """
        Sets the table type name for this record box to the inputed name.
        
        :param      name | <str>
        """
        self._tableTypeName = str(name)
        self._tableType = None

    def setThreadEnabled(self, state):
        """
        Sets whether or not threading should be enabled for this widget.  
        Actual threading will be determined by both this property, and whether
        or not the active ORB backend supports threading.
        
        :param      state | <bool>
        """
        self._threadEnabled = state

    def setVisible(self, state):
        """
        Sets the visibility for this record box.
        
        :param      state | <bool>
        """
        super(XOrbRecordBox, self).setVisible(state)

        if state and not self._loaded:
            if self.autoInitialize():
                table = self.tableType()
                if not table:
                    return

                self.setRecords(table.select(where=self.query()))
            else:
                self.initialized.emit()

    def showPopup(self):
        """
        Overloads the popup method from QComboBox to display an ORB tree widget
        when necessary.
        
        :sa     setShowTreePopup
        """
        if not self.showTreePopup():
            return super(XOrbRecordBox, self).showPopup()

        tree = self.treePopupWidget()

        if tree and not tree.isVisible():
            tree.move(self.mapToGlobal(QPoint(0, self.height())))
            tree.resize(self.width(), 250)
            tree.resizeToContents()
            tree.filterItems('')
            tree.setFilteredColumns(range(tree.columnCount()))
            tree.show()

    def showTreePopup(self):
        """
        Sets whether or not to use an ORB tree widget in the popup for this
        record box.
        
        :return     <bool>
        """
        return self._showTreePopup

    def specifiedColumns(self):
        """
        Returns the list of columns that are specified based on the column
        view for this widget.
        
        :return     [<orb.Column>, ..]
        """
        columns = []
        table = self.tableType()
        tree = self.treePopupWidget()
        schema = table.schema()

        if self._specifiedColumns is not None:
            colnames = self._specifiedColumns
        else:
            colnames = tree.columns()

        for colname in colnames:
            if isinstance(colname, Column):
                columns.append(colname)
            else:
                col = schema.column(colname)
                if col and not col.isProxy():
                    columns.append(col)

        return columns

    def specifiedColumnsOnly(self):
        """
        Returns whether or not only specified columns should be loaded
        for this record box.
        
        :return     <int>
        """
        return self._specifiedColumnsOnly

    def tableLookupIndex(self):
        """
        Returns the name of the index method that will be used to lookup
        records for this combo box.
        
        :return     <str>
        """
        return self._tableLookupIndex

    def tableType(self):
        """
        Returns the table type for this instance.
        
        :return     <subclass of orb.Table> || None
        """
        if not self._tableType:
            if self._tableTypeName:
                self._tableType = Orb.instance().model(str(
                    self._tableTypeName))

        return self._tableType

    def tableTypeName(self):
        """
        Returns the table type name that is set for this combo box.
        
        :return     <str>
        """
        return self._tableTypeName

    def treePopupWidget(self):
        """
        Returns the popup widget for this record box when it is supposed to
        be an ORB tree widget.
        
        :return     <XTreeWidget>
        """
        if not self._treePopupWidget:
            # create the treewidget
            tree = XTreeWidget(self)
            tree.setWindowFlags(Qt.Popup)
            tree.setFocusPolicy(Qt.StrongFocus)
            tree.installEventFilter(self)
            tree.setAlternatingRowColors(True)
            tree.setShowGridColumns(False)
            tree.setRootIsDecorated(False)
            tree.setVerticalScrollMode(tree.ScrollPerPixel)

            # create connections
            tree.itemClicked.connect(self.acceptRecord)

            self.lineEdit().textEdited.connect(tree.filterItems)
            self.lineEdit().textEdited.connect(self.showPopup)

            self._treePopupWidget = tree

        return self._treePopupWidget

    def worker(self):
        """
        Returns the worker object for loading records for this record box.
        
        :return     <XOrbLookupWorker>
        """
        return self._worker

    x_batchSize = Property(int, batchSize, setBatchSize)
    x_required = Property(bool, isRequired, setRequired)
    x_tableTypeName = Property(str, tableTypeName, setTableTypeName)
    x_tableLookupIndex = Property(str, tableLookupIndex, setTableLookupIndex)
    x_showTreePopup = Property(bool, showTreePopup, setShowTreePopup)
    x_threadEnabled = Property(bool, isThreadEnabled, setThreadEnabled)
Exemplo n.º 24
0
class XPushButton(QPushButton):
    def __init__(self, *args, **kwds):
        super(XPushButton, self).__init__(*args, **kwds)
        
        # sets whether or not this button will display rich text
        self._showRichText  = False
        self._richTextLabel = None
        self._text          = ''
    
    def eventFilter(self, object, event):
        """
        Ignore all events for the text label.
        
        :param      object | <QObject>
                    event  | <QEvent>
        """
        if object == self._richTextLabel:
            if event.type() in (event.MouseButtonPress,
                                event.MouseMove,
                                event.MouseButtonRelease,
                                event.MouseButtonDblClick):
                event.ignore()
                return True
        return False
    
    def resizeEvent(self, event):
        """
        Overloads the resize event to auto-resize the rich text label to the
        size of this QPushButton.
        
        :param      event | <QResizeEvent>
        """
        super(XPushButton, self).resizeEvent(event)
        
        if self._richTextLabel:
            self._richTextLabel.resize(event.size())
        
    def richTextLabel(self):
        """
        Returns the label that is used for drawing the rich text to this button.
        
        :return     <QLabel>
        """
        if not self._richTextLabel:
            self._richTextLabel = QLabel(self)
            self._richTextLabel.installEventFilter(self)
            self._richTextLabel.setMargin(10)
        return self._richTextLabel
    
    def setShowRichText(self, state):
        """
        Sets whether or not to display rich text for this button.
        
        :param      state | <bool>
        """
        self._showRichText = state
        text = self.text()
        
        if state:
            label = self.richTextLabel()
            label.setText(text)
            label.show()
            super(XPushButton, self).setText('')
        else:
            if self._richTextLabel:
                self._richTextLabel.hide()
            
            super(XPushButton, self).setText(text)
    
    def setText(self, text):
        """
        Sets the text for this button.  If it is set to show rich text, then
        it will update the label text, leaving the root button text blank, 
        otherwise it will update the button.
        
        :param      text | <str>
        """
        self._text = nativestring(text)
        if self.showRichText():
            self.richTextLabel().setText(text)
        else:
            super(XPushButton, self).setText(text)
    
    def showRichText(self):
        """
        Returns whether or not rich text is visible for this button.
        
        :return     <bool>
        """
        return self._showRichText
    
    def text(self):
        """
        Returns the source text for this button.
        
        :return     <str>
        """
        return self._text

    x_showRichText = Property(bool, showRichText, setShowRichText)
Exemplo n.º 25
0
class XLabel(QLabel):
    aboutToEdit = Signal()
    editingCancelled = Signal()
    editingFinished = Signal(str)

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

        self._editable = False
        self._lineEdit = None
        self._editText = None

    @Slot()
    def acceptEdit(self):
        """
        Accepts the current edit for this label.
        """
        if not self._lineEdit:
            return

        self.setText(self._lineEdit.text())
        self._lineEdit.hide()

        if not self.signalsBlocked():
            self.editingFinished.emit(self._lineEdit.text())

    def beginEdit(self):
        """
        Begins editing for the label.
        """
        if not self._lineEdit:
            return

        self.aboutToEdit.emit()
        self._lineEdit.setText(self.editText())
        self._lineEdit.show()
        self._lineEdit.selectAll()
        self._lineEdit.setFocus()

    def editText(self):
        """
        Returns the edit text for this label.  This will be the text displayed
        in the editing field when editable.  By default, it will be the
        text from the label itself.
        
        :return     <str>
        """
        if self._editText is not None:
            return self._editText
        return self.text()

    def eventFilter(self, object, event):
        """
        Filters the event for the inputed object looking for escape keys.
        
        :param      object | <QObject>
                    event  | <QEvent>
        
        :return     <bool>
        """
        if event.type() == event.KeyPress:
            if event.key() == Qt.Key_Escape:
                self.rejectEdit()
                return True

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

        elif event.type() == event.FocusOut:
            self.acceptEdit()

        return False

    def isEditable(self):
        """
        Returns if this label is editable or not.
        
        :return     <bool>
        """
        return self._editable

    def lineEdit(self):
        """
        Returns the line edit instance linked with this label.  This will be
        null if the label is not editable.
        
        :return     <QLineEdit>
        """
        return self._lineEdit

    def mouseDoubleClickEvent(self, event):
        """
        Prompts the editing process if the label is editable.
        
        :param      event | <QMouseDoubleClickEvent>
        """
        if self.isEditable():
            self.beginEdit()

        super(XLabel, self).mouseDoubleClickEvent(event)

    def rejectEdit(self):
        """
        Cancels the edit for this label.
        """
        if self._lineEdit:
            self._lineEdit.hide()
            self.editingCancelled.emit()

    def resizeEvent(self, event):
        """
        Resize the label and the line edit for this label.
        
        :param      event | <QResizeEvent>
        """
        super(XLabel, self).resizeEvent(event)

        if self._lineEdit:
            self._lineEdit.resize(self.size())

    def setEditable(self, state):
        """
        Sets whether or not this label should be editable or not.
        
        :param      state | <bool>
        """
        self._editable = state

        if state and not self._lineEdit:
            self.setLineEdit(QLineEdit(self))

        elif not state and self._lineEdit:
            self._lineEdit.close()
            self._lineEdit.setParent(None)
            self._lineEdit.deleteLater()
            self._lineEdit = None

    def setEditText(self, text):
        """
        Sets the text to be used while editing.
        
        :param      text | <str> || None
        """
        self._editText = text

    def setLineEdit(self, lineEdit):
        """
        Sets the line edit instance for this label.
        
        :param      lineEdit | <QLineEdit>
        """
        self._lineEdit = lineEdit
        if lineEdit:
            lineEdit.setFont(self.font())
            lineEdit.installEventFilter(self)
            lineEdit.resize(self.size())
            lineEdit.hide()

    x_editable = Property(bool, isEditable, setEditable)
Exemplo n.º 26
0
class XLocationWidget(QWidget):
    locationChanged = Signal(str)
    locationEdited = Signal()

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

        # define the interface
        self._locationEdit = XLineEdit(self)
        self._locationButton = QToolButton(self)
        self._urlTemplate = 'http://maps.google.com/maps?%(params)s'
        self._urlQueryKey = 'q'

        self._locationButton.setAutoRaise(True)
        self._locationButton.setIcon(QIcon(resources.find('img/map.png')))

        self._locationEdit.setHint('no location set')

        layout = QHBoxLayout()
        layout.setContentsMargins(0, 0, 0, 0)
        layout.setSpacing(0)
        layout.addWidget(self._locationEdit)
        layout.addWidget(self._locationButton)

        self.setLayout(layout)

        # create connections
        self._locationEdit.textChanged.connect(self.locationChanged)
        self._locationEdit.textEdited.connect(self.locationEdited)
        self._locationButton.clicked.connect(self.browseMaps)

    def blockSignals(self, state):
        """
        Blocks the signals for this widget and its sub-parts.
        
        :param      state | <bool>
        """
        super(XLocationWidget, self).blockSignals(state)
        self._locationEdit.blockSignals(state)
        self._locationButton.blockSignals(state)

    def browseMaps(self):
        """
        Brings up a web browser with the address in a Google map.
        """
        url = self.urlTemplate()
        params = urllib.urlencode({self.urlQueryKey(): self.location()})

        url = url % {'params': params}
        webbrowser.open(url)

    def hint(self):
        """
        Returns the hint associated with this widget.
        
        :return     <str>
        """
        return self._locationEdit.hint()

    def lineEdit(self):
        """
        Returns the line edit linked with this widget.
        
        :return     <XLineEdit>
        """
        return self._locationEdit

    def location(self):
        """
        Returns the current location from the edit.
        
        :return     <str>
        """
        return nativestring(self._locationEdit.text())

    @Slot(str)
    def setHint(self, hint):
        """
        Sets the hint associated with this widget.
        
        :param      hint | <str>
        """
        self._locationEdit.setHint(hint)

    @Slot(str)
    def setLocation(self, location):
        """
        Sets the location for this widget to the inputed location.
        
        :param      location | <str>
        """
        self._locationEdit.setText(nativestring(location))

    def setUrlQueryKey(self, key):
        """
        Sets the key for the URL to the inputed key.
        
        :param      key | <str>
        """
        self._urlQueryKey = nativestring(key)

    def setUrlTemplate(self, templ):
        """
        Sets the URL path template that will be used when looking up locations
        on the web.
        
        :param      templ | <str>
        """
        self._urlQueryTemplate = nativestring(templ)

    def urlQueryKey(self):
        """
        Returns the query key that will be used for this location.
        
        :return     <str>
        """
        return self._urlQueryKey

    def urlTemplate(self):
        """
        Returns the url template that will be used when mapping this location.
        
        :return     <str>
        """
        return self._urlTemplate

    x_hint = Property(str, hint, setHint)
    x_location = Property(str, location, setLocation)
    x_urlQueryKey = Property(str, urlQueryKey, setUrlQueryKey)
    x_urlTemplate = Property(str, urlTemplate, setUrlTemplate)
Exemplo n.º 27
0
class XNodeWidget(QGraphicsView):
    """ Defines the main widget for creating node graph views. """
    __designer_icon__ = projexui.resources.find('img/ui/node.png')

    maxZoomAmountChanged = Signal(int)
    minZoomAmountChanged = Signal(int)
    zoomAmountChanged = Signal(int)

    def __init__(self, parent, sceneClass=None):
        # initialize the super class
        super(XNodeWidget, self).__init__(parent)

        # set the scene
        if not sceneClass:
            sceneClass = XNodeScene

        self._cleanupOnClose = True
        self._initialized = False

        self.setScene(sceneClass(self))
        self.setViewportUpdateMode(QGraphicsView.FullViewportUpdate)
        self.setContextMenuPolicy(Qt.CustomContextMenu)

    def __dir__(self):
        out = set(self.__dict__.keys())
        out.update(dir(self.scene()))
        return list(out)

    def __getattr__(self, key):
        return getattr(self.scene(), key)

    def _runLayoutTest(self, layoutName):
        """
        Runs a layout test for this widget for the inputed layout plugin
        name.
        
        :param      layoutName | <str>
        
        :return     <bool> | success
        """
        layout = XNodeLayout.plugin(layoutName)
        if not layout:
            return False

        layout.runTest(self.scene())
        return True

    @Slot()
    def autoLayout(self):
        """
        Auto-lays out the whole scene.
        """
        self.scene().autoLayout()

    @Slot()
    def autoLayoutSelected(self):
        """
        Auto-lays out the selected items.
        """
        self.scene().autoLayoutSelected()

    def centerOn(self, *args):
        """
        Updates the center on method to ensure the viewport is updated.
        
        :param      *args | <variant>
        """
        super(XNodeWidget, self).centerOn(*args)

        for con in self.connections():
            con.setPath(con.rebuild())
            con.update()

    def centerOnAnimated(self, centerOn, animate=0):
        """
        Animates the centering options over a given number of seconds.
        
        :param      centerOn | <QRectF> | <QPointF> | <XNode>
                    animate  | <float> | seconds
        """
        if isinstance(centerOn, XNode):
            center = centerOn.sceneRect().center()
        elif isinstance(centerOn, QRectF):
            center = centerOn.center()
        elif isinstance(centerOn, QPointF):
            center = centerOn
        else:
            return

        anim = XObjectAnimation(self, 'centerOn', self)
        anim.setStartValue(self.viewportRect().center())
        anim.setEndValue(center)
        anim.setDuration(1000 * animate)
        anim.start()
        anim.finished.connect(anim.deleteLater)

    def centerOnItems(self, items=None):
        """
        Centers on the given items, if no items are supplied, then all
        items will be centered on.
        
        :param      items | [<QGraphicsItem>, ..]
        """
        if not items:
            rect = self.scene().visibleItemsBoundingRect()
            if not rect.width():
                rect = self.scene().sceneRect()

            self.centerOn(rect.center())
        else:
            self.centerOn(self.scene().calculateBoundingRect(items).center())

    def centerOnSelection(self):
        """
        Centers on the selected items.
        
        :sa     centerOnItems
        """
        self.centerOnItems(self.scene().selectedItems())

    def cleanupOnClose(self):
        """
        Sets whether or not this widget should clean up its scene before
        closing.
        
        :return     <bool>
        """
        return self._cleanupOnClose

    def closeEvent(self, event):
        """
        Cleans up the scene before closing.
        
        :param      event | <QEvent>
        """
        if (self.cleanupOnClose()):
            scene = self.scene()
            scene.cleanup()
            self.setScene(None)

        super(XNodeWidget, self).closeEvent(event)

    @Slot()
    def disableViewMode(self):
        """
        Sets the node widget into selection mode which allows the user to select
        vs. pan and zoom.
        """
        self.scene().setViewMode(False)

    @Slot()
    def enableViewMode(self):
        """
        Sets the node widget into view mode which allows the user to pan
        and zoom vs. select.
        """
        self.scene().setViewMode(True)

    def findNodeByRegex(self, nodeRegex):
        """
        Returns the first node that matches the inputed regular expression.
        
        :param      nodeRegex | <str>
        
        :return     <XNode> || None
        """
        return self.scene().findNodeByRegex(nodeRegex)

    def findNode(self, nodeName):
        """
        Returns the node for the given node name.
        
        :param     nodeName | <str>
        
        :return     <XNode> || None
        """
        return self.scene().findNode(nodeName)

    def isolationMode(self):
        """
        Returns whether or not this widget is in isolation mode.
        
        :return     <bool>
        """
        return self.scene().isolationMode()

    def setCleanupOnClose(self, state):
        """
        Sets whether or not the scene should be cleaned up before closing.
        
        :param      state | <bool>
        """
        self._cleanupOnClose = state

    @Slot(bool)
    def setIsolationMode(self, state):
        """
        Sets whether or not the widget is in isolation mode.
        
        :param      state | <bool>
        """
        self.scene().setIsolationMode(state)

    @Slot(int)
    def setZoomAmount(self, amount):
        """
        Sets the zoom amount for this widget to the inputed amount.
        
        :param      amount | <int>
        """
        self.scene().setZoomAmount(amount)

    def showEvent(self, event):
        super(XNodeWidget, self).showEvent(event)

        if not self._initialized:
            self._initialized = True
            self.centerOnItems()

    def viewportRect(self):
        """
        Returns the QRectF that represents the visible viewport rect for the
        current view.
        
        :return     <QRectF>
        """
        w = self.width()
        h = self.height()

        vbar = self.verticalScrollBar()
        hbar = self.horizontalScrollBar()

        if vbar.isVisible():
            w -= vbar.width()
        if hbar.isVisible():
            h -= hbar.height()

        top_l = self.mapToScene(QPoint(0, 0))
        bot_r = self.mapToScene(QPoint(w, h))

        return QRectF(top_l.x(), top_l.y(),
                      bot_r.x() - top_l.x(),
                      bot_r.y() - top_l.y())

    def zoomAmount(self):
        """
        Returns the zoom amount for this widget to the inputed amount.
        
        :param      amount | <int>
        """
        return self.scene().zoomAmount()

    @Slot()
    def zoomExtents(self):
        """
        Fits all the nodes in the view.
        """
        rect = self.scene().visibleItemsBoundingRect()
        vrect = self.viewportRect()

        if rect.width():
            changed = False
            scene_rect = self.scene().sceneRect()

            if scene_rect.width() < rect.width():
                scene_rect.setWidth(rect.width() + 150)
                scene_rect.setX(-scene_rect.width() / 2.0)
                changed = True

            if scene_rect.height() < rect.height():
                scene_rect.setHeight(rect.height() + 150)
                scene_rect.setY(-scene_rect.height() / 2.0)
                changed = True

            if changed:
                self.scene().setSceneRect(scene_rect)

            self.fitInView(rect, Qt.KeepAspectRatio)

        if not self.signalsBlocked():
            self.zoomAmountChanged.emit(self.zoomAmount())

    @Slot()
    def zoomIn(self):
        """
        Zooms in for this widget by the scene's zoom step amount.
        """
        self.scene().zoomIn()

    @Slot()
    def zoomOut(self):
        """
        Zooms out for this widget by the scene's zoom step amount.
        """
        self.scene().zoomOut()

    x_isolationMode = Property(bool, isolationMode, setIsolationMode)
    x_cleanupOnClose = Property(bool, cleanupOnClose, setCleanupOnClose)
Exemplo n.º 28
0
class XPagesWidget(QWidget):
    """ """
    currentPageChanged = Signal(int)
    pageSizeChanged = Signal(int)
    pageCountChanged = Signal(int)

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

        # define custom properties
        self._currentPage = 1
        self._pageCount = 10
        self._itemCount = 0
        self._pageSize = 50
        self._itemsTitle = 'items'

        self._pagesSpinner = QSpinBox()
        self._pagesSpinner.setMinimum(1)
        self._pagesSpinner.setMaximum(10)

        self._pageSizeCombo = XComboBox(self)
        self._pageSizeCombo.setHint('all')
        self._pageSizeCombo.addItems(['', '25', '50', '75', '100'])
        self._pageSizeCombo.setCurrentIndex(2)

        self._nextButton = QToolButton(self)
        self._nextButton.setAutoRaise(True)
        self._nextButton.setArrowType(Qt.RightArrow)
        self._nextButton.setFixedWidth(16)

        self._prevButton = QToolButton(self)
        self._prevButton.setAutoRaise(True)
        self._prevButton.setArrowType(Qt.LeftArrow)
        self._prevButton.setFixedWidth(16)
        self._prevButton.setEnabled(False)

        self._pagesLabel = QLabel('of 10 for ', self)
        self._itemsLabel = QLabel(' items per page', self)

        # define the interface
        layout = QHBoxLayout()
        layout.addWidget(QLabel('Page', self))
        layout.addWidget(self._prevButton)
        layout.addWidget(self._pagesSpinner)
        layout.addWidget(self._nextButton)
        layout.addWidget(self._pagesLabel)
        layout.addWidget(self._pageSizeCombo)
        layout.addWidget(self._itemsLabel)
        layout.addStretch(1)

        layout.setContentsMargins(0, 0, 0, 0)
        layout.setSpacing(0)

        self.setLayout(layout)
        self.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Preferred)

        # create connections
        self._pageSizeCombo.currentIndexChanged.connect(self.pageSizePicked)
        self._nextButton.clicked.connect(self.gotoNext)
        self._prevButton.clicked.connect(self.gotoPrevious)
        self._pagesSpinner.editingFinished.connect(self.assignCurrentPage)

    def assignCurrentPage(self):
        """
        Assigns the page for the spinner to be current.
        """
        self.setCurrentPage(self._pagesSpinner.value())

    def currentPage(self):
        """
        Reutrns the current page for this widget.
        
        :return     <int>
        """
        return self._currentPage

    @Slot()
    def gotoFirst(self):
        """
        Goes to the first page.
        
        :sa     setCurrentPage
        """
        self.setCurrentPage(1)

    @Slot()
    def gotoLast(self):
        """
        Goes to the last page.
        
        :sa     setCurrentPage
        """
        self.setCurrentPage(self.pageCount())

    @Slot()
    def gotoNext(self):
        """
        Goes to the next page.
        
        :sa     setCurrentPage
        """
        next_page = self.currentPage() + 1
        if (next_page > self.pageCount()):
            return

        self.setCurrentPage(next_page)

    @Slot()
    def gotoPrevious(self):
        """
        Goes to the previous page.
        
        :sa     setCurrentPage
        """
        prev_page = self.currentPage() - 1
        if (prev_page == 0):
            return

        self.setCurrentPage(prev_page)

    def itemCount(self):
        """
        Returns the total number of items this widget holds.  If no item count
        is defined, it will not be displayed in the label, otherwise it will
        show.
        
        :return     <int>
        """
        return self._itemCount

    def itemsTitle(self):
        """
        Returns the items title for this instance.
        
        :return     <str>
        """
        return self._itemsTitle

    def pageCount(self):
        """
        Returns the number of pages that this widget holds.
        
        :return     <int>
        """
        return self._pageCount

    def pageSize(self):
        """
        Returns the number of items that should be visible in a page.
        
        :return     <int>
        """
        return self._pageSize

    def pageSizeOptions(self):
        """
        Returns the list of options that will be displayed for this default
        size options.
        
        :return     [<str>, ..]
        """
        return map(str, self._pageSizeCombo.items())

    def pageSizePicked(self, pageSize):
        """
        Updates when the user picks a page size.
        
        :param      pageSize | <str>
        """
        try:
            pageSize = int(self._pageSizeCombo.currentText())
        except ValueError:
            pageSize = 0

        self.setPageSize(pageSize)
        self.pageSizeChanged.emit(pageSize)

    def refreshLabels(self):
        """
        Refreshes the labels to display the proper title and count information.
        """
        itemCount = self.itemCount()
        title = self.itemsTitle()

        if (not itemCount):
            self._itemsLabel.setText(' %s per page' % title)
        else:
            msg = ' %s per page, %i %s total' % (title, itemCount, title)
            self._itemsLabel.setText(msg)

    @Slot(int)
    def setCurrentPage(self, pageno):
        """
        Sets the current page for this widget to the inputed page.
        
        :param      pageno | <int>
        """
        if (pageno == self._currentPage):
            return

        if (pageno <= 0):
            pageno = 1

        self._currentPage = pageno

        self._prevButton.setEnabled(pageno > 1)
        self._nextButton.setEnabled(pageno < self.pageCount())

        self._pagesSpinner.blockSignals(True)
        self._pagesSpinner.setValue(pageno)
        self._pagesSpinner.blockSignals(False)

        if (not self.signalsBlocked()):
            self.currentPageChanged.emit(pageno)

    @Slot(int)
    def setItemCount(self, itemCount):
        """
        Sets the item count for this page to the inputed value.
        
        :param      itemCount | <int>
        """
        self._itemCount = itemCount
        self.refreshLabels()

    @Slot(str)
    def setItemsTitle(self, title):
        """
        Sets the title that will be displayed when the items labels are rendered
        
        :param      title | <str>
        """
        self._itemsTitle = nativestring(title)
        self.refreshLabels()

    @Slot(int)
    def setPageCount(self, pageCount):
        """
        Sets the number of pages that this widget holds.
        
        :param      pageCount | <int>
        """
        if (pageCount == self._pageCount):
            return

        pageCount = max(1, pageCount)

        self._pageCount = pageCount
        self._pagesSpinner.setMaximum(pageCount)
        self._pagesLabel.setText('of %i for ' % pageCount)

        if (pageCount and self.currentPage() <= 0):
            self.setCurrentPage(1)

        elif (pageCount < self.currentPage()):
            self.setCurrentPage(pageCount)

        if (not self.signalsBlocked()):
            self.pageCountChanged.emit(pageCount)

        self._prevButton.setEnabled(self.currentPage() > 1)
        self._nextButton.setEnabled(self.currentPage() < pageCount)

    @Slot(int)
    def setPageSize(self, pageSize):
        """
        Sets the number of items that should be visible in a page.  Setting the
        value to 0 will use all sizes
        
        :return     <int>
        """
        if self._pageSize == pageSize:
            return

        self._pageSize = pageSize

        # update the display size
        ssize = nativestring(pageSize)
        if (ssize == '0'):
            ssize = ''

        self._pageSizeCombo.blockSignals(True)
        index = self._pageSizeCombo.findText(ssize)
        self._pageSizeCombo.setCurrentIndex(index)
        self._pageSizeCombo.blockSignals(False)

    def setPageSizeOptions(self, options):
        """
        Sets the options that will be displayed for this default size.
        
        :param      options | [<str>,. ..]
        """
        self._pageSizeCombo.blockSignals(True)
        self._pageSizeCombo.addItems(options)

        ssize = nativestring(self.pageSize())
        if (ssize == '0'):
            ssize = ''

        index = self._pageSizeCombo.findText()
        self._pageSizeCombo.setCurrentIndex(index)
        self._pageSizeCombo.blockSignals(False)

    x_itemsTitle = Property(str, itemsTitle, setItemsTitle)
    x_pageCount = Property(int, pageCount, setPageCount)
    x_pageSize = Property(int, pageSize, setPageSize)
    x_pageSizeOptions = Property(list, pageSizeOptions, setPageSizeOptions)
Exemplo n.º 29
0
class XSerialEdit(QtGui.QWidget):
    returnPressed = Signal()

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

        # define custom properties
        self._sectionLength = 5
        self._readOnly = False
        self._editorHandlingBlocked = False

        # set standard values
        layout = QtGui.QHBoxLayout()
        layout.setContentsMargins(0, 0, 0, 0)
        layout.setSpacing(4)
        self.setLayout(layout)
        self.setSectionCount(4)
        self.setSizePolicy(QtGui.QSizePolicy.Expanding,
                           QtGui.QSizePolicy.Fixed)

    def blockEditorHandling(self, state):
        self._editorHandlingBlocked = state

    def clearSelection(self):
        """
        Clears the selected text for this edit.
        """
        first = None
        editors = self.editors()
        for editor in editors:
            if not editor.selectedText():
                continue

            first = first or editor
            editor.backspace()

        for editor in editors:
            editor.setFocus()

        if first:
            first.setFocus()

    @Slot()
    def copyAll(self):
        """
        Copies all of the text to the clipboard.
        """
        QtGui.QApplication.clipboard().setText(self.text())

    @Slot()
    def copy(self):
        """
        Copies the text from the serial to the clipboard.
        """
        QtGui.QApplication.clipboard().setText(self.selectedText())

    @Slot()
    def cut(self):
        """
        Cuts the text from the serial to the clipboard.
        """
        text = self.selectedText()
        for editor in self.editors():
            editor.cut()

        QtGui.QApplication.clipboard().setText(text)

    def currentEditor(self):
        """
        Returns the current editor or this widget based on the focusing.
        
        :return     <QtGui.QLineEdit>
        """
        for editor in self.editors():
            if editor.hasFocus():
                return editor
        return None

    def editors(self):
        """
        Returns the editors that are associated with this edit.
        
        :return     [<XLineEdit>, ..]
        """
        lay = self.layout()
        return [lay.itemAt(i).widget() for i in range(lay.count())]

    def editorAt(self, index):
        """
        Returns the editor at the given index.
        
        :param      index | <int>
        
        :return     <XLineEdit> || None
        """
        try:
            return self.layout().itemAt(index).widget()
        except AttributeError:
            return None

    def eventFilter(self, object, event):
        """
        Filters the events for the editors to control how the cursor
        flows between them.
        
        :param      object | <QtCore.QObject>
                    event  | <QtCore.QEvent>

        :return     <bool> | consumed
        """
        index = self.indexOf(object)
        pressed = event.type() == event.KeyPress
        released = event.type() == event.KeyRelease

        if index == -1 or \
           not (pressed or released) or \
           self.isEditorHandlingBlocked():
            return super(XSerialEdit, self).eventFilter(object, event)

        text = nativestring(event.text()).strip()

        # handle Ctrl+C (copy)
        if event.key() == QtCore.Qt.Key_C and \
           event.modifiers() == QtCore.Qt.ControlModifier and \
           pressed:
            self.copy()
            return True

        # handle Ctrl+X (cut)
        elif event.key() == QtCore.Qt.Key_X and \
             event.modifiers() == QtCore.Qt.ControlModifier and \
             pressed:
            if not self.isReadOnly():
                self.cut()
            return True

        # handle Ctrl+A (select all)
        elif event.key() == QtCore.Qt.Key_A and \
             event.modifiers() == QtCore.Qt.ControlModifier and \
             pressed:
            self.selectAll()
            return True

        # handle Ctrl+V (paste)
        elif event.key() == QtCore.Qt.Key_V and \
             event.modifiers() == QtCore.Qt.ControlModifier and \
             pressed:
            if not self.isReadOnly():
                self.paste()
            return True

        # ignore tab movements
        elif event.key() in (QtCore.Qt.Key_Tab, QtCore.Qt.Key_Backtab):
            pass

        # delete all selected text
        elif event.key() == QtCore.Qt.Key_Backspace:
            sel_text = self.selectedText()
            if sel_text and not self.isReadOnly():
                self.clearSelection()
                return True

        # ignore modified keys
        elif not released:
            return super(XSerialEdit, self).eventFilter(object, event)

        # move to the previous editor
        elif object.cursorPosition() == 0:
            if event.key() in (QtCore.Qt.Key_Backspace, QtCore.Qt.Key_Left):
                self.goBack()

        # move to next editor
        elif object.cursorPosition() == object.maxLength():
            valid_chars = string.ascii_letters + string.digits
            valid_text = text != '' and text in valid_chars

            if valid_text or event.key() == QtCore.Qt.Key_Right:
                self.goForward()

        return super(XSerialEdit, self).eventFilter(object, event)

    def goBack(self):
        """
        Moves the cursor to the end of the previous editor
        """
        index = self.indexOf(self.currentEditor())
        if index == -1:
            return

        previous = self.editorAt(index - 1)
        if previous:
            previous.setFocus()
            previous.setCursorPosition(self.sectionLength())

    def goForward(self):
        """
        Moves the cursor to the beginning of the next editor.
        """
        index = self.indexOf(self.currentEditor())
        if index == -1:
            return

        next = self.editorAt(index + 1)
        if next:
            next.setFocus()
            next.setCursorPosition(0)

    def hint(self):
        """
        Returns the hint that is used for the editors in this widget.
        
        :return     <str>
        """
        texts = []
        for editor in self.editors():
            text = editor.hint()
            if text:
                texts.append(nativestring(text))

        return ' '.join(texts)

    def indexOf(self, editor):
        """
        Returns the index of the inputed editor, or -1 if not found.
        
        :param      editor | <QtGui.QWidget>
        
        :return     <int>
        """
        lay = self.layout()
        for i in range(lay.count()):
            if lay.itemAt(i).widget() == editor:
                return i
        return -1

    def isEditorHandlingBlocked(self):
        return self._editorHandlingBlocked

    def isReadOnly(self):
        """
        Returns whether or not this edit is readonly.
        
        :return     <bool>
        """
        return self._readOnly

    @Slot()
    def paste(self):
        """
        Pastes text from the clipboard into the editors.
        """
        self.setText(QtGui.QApplication.clipboard().text())

    def showEvent(self, event):
        for editor in self.editors():
            editor.setFont(self.font())

        super(XSerialEdit, self).showEvent(event)

    def sectionCount(self):
        """
        Returns the number of editors that are a part of this serial edit.
        
        :return     <int>
        """
        return self.layout().count()

    def sectionLength(self):
        """
        Returns the number of characters available for each editor.
        
        :return     <int>
        """
        return self._sectionLength

    def selectedText(self):
        """
        Returns the selected text from the editors.
        
        :return     <str>
        """
        texts = []
        for editor in self.editors():
            text = editor.selectedText()
            if text:
                texts.append(nativestring(text))

        return ' '.join(texts)

    @Slot()
    def selectAll(self):
        """
        Selects the text within all the editors.
        """
        self.blockEditorHandling(True)
        for editor in self.editors():
            editor.selectAll()
        self.blockEditorHandling(False)

    def setHint(self, text):
        """
        Sets the hint to the inputed text.   The same hint will be used for
        all editors in this widget.
        
        :param      text | <str>
        """
        texts = nativestring(text).split(' ')

        for i, text in enumerate(texts):
            editor = self.editorAt(i)
            if not editor:
                break

            editor.setHint(text)

    def setReadOnly(self, state):
        """
        Sets whether or not this edit is read only.
        
        :param      state | <bool>
        """
        self._readOnly = state

        for editor in self.editors():
            editor.setReadOnly(state)

    def setSectionCount(self, count):
        """
        Sets the number of editors that the serial widget should have.
        
        :param      count | <int>
        """
        # cap the sections at 10
        count = max(1, min(count, 10))

        # create additional editors
        while self.layout().count() < count:
            editor = XLineEdit(self)
            editor.setFont(self.font())
            editor.setReadOnly(self.isReadOnly())
            editor.setHint(self.hint())
            editor.setAlignment(QtCore.Qt.AlignCenter)
            editor.installEventFilter(self)
            editor.setSizePolicy(QtGui.QSizePolicy.Expanding,
                                 QtGui.QSizePolicy.Expanding)
            editor.setMaxLength(self.sectionLength())
            editor.returnPressed.connect(self.returnPressed)
            self.layout().addWidget(editor)

        # remove unnecessary editors
        while count < self.layout().count():
            widget = self.layout().itemAt(0).widget()
            widget.close()
            widget.setParent(None)
            widget.deleteLater()

    def setSectionLength(self, length):
        """
        Sets the number of characters per section that are allowed.
        
        :param      length | <int>
        """
        self._sectionLength = length
        for editor in self.editors():
            editor.setMaxLength(length)

    @Slot()
    def setText(self, text):
        """
        Sets the text for this serial edit to the inputed text.
        
        :param      text | <str>
        """
        texts = nativestring(text).split(' ')

        for i, text in enumerate(texts):
            editor = self.editorAt(i)
            if not editor:
                break

            editor.setText(text)

    def text(self):
        """
        Returns the text from all the serials as text separated by a spacer.
        
        :return     <str>
        """
        texts = []
        for editor in self.editors():
            text = editor.text()
            if text:
                texts.append(nativestring(text))

        return ' '.join(texts)

    x_readOnly = Property(bool, isReadOnly, setReadOnly)
    x_sectionCount = Property(int, sectionCount, setSectionCount)
    x_sectionLength = Property(int, sectionLength, setSectionLength)
    x_hint = Property(str, hint, setHint)
    x_text = Property(str, text, setText)
Exemplo n.º 30
0
class XOrbQueryWidget(XStackedWidget):
    """ """
    __designer_group__ = 'ProjexUI - ORB'

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

        # define custom properties
        self._pluginFactory = XOrbQueryPluginFactory()
        self._loadQuery = None
        self._tableType = None
        self._compoundStack = []
        self._initialized = False
        self._showReferencePlugins = True

        self.clear()
        self.setMinimumWidth(575)
        self.setMinimumHeight(145)

        # create connections
        self.animationFinished.connect(self.cleanupContainers)

    def addContainer(self, query):
        """
        Creates a new query container widget object and slides it into
        the frame.
        
        :return     <XOrbQueryContainer>
        """
        self.setUpdatesEnabled(False)
        self.blockSignals(True)
        container = XOrbQueryContainer(self)

        # setup properties
        container.setShowBack(self.count() > 0)

        # create connections
        container.enterCompoundRequested.connect(self.enterContainer)
        container.exitCompoundRequested.connect(self.exitContainer)

        # show the widget
        self.addWidget(container)
        self.setUpdatesEnabled(True)
        self.blockSignals(False)

        container.setQuery(query)
        self.slideInNext()
        return container

    def clear(self):
        """
        Clears all the container for this query widget.
        """
        for i in range(self.count()):
            widget = self.widget(i)
            if widget is not None:
                widget.close()
                widget.setParent(None)
                widget.deleteLater()

    def cleanupContainers(self):
        """
        Cleans up all containers to the right of the current one.
        """
        for i in range(self.count() - 1, self.currentIndex(), -1):
            widget = self.widget(i)
            widget.close()
            widget.setParent(None)
            widget.deleteLater()

    def containerFor(self, entry):
        """
        Returns a container for the inputed entry widget.
        
        :param      entry | <XOrbQueryEntryWidget>
        
        :return     <XOrbQueryContainer> || None
        """
        try:
            index = self._compoundStack.index(entry)
        except ValueError:
            return None

        return self.widget(index + 1)

    def currentContainer(self):
        """
        Returns the current query container.
        
        :return     <XOrbQueryContainer>
        """
        return self.currentWidget()

    def currentQuery(self):
        """
        Returns the current query for the active container.  This will reflect
        what is currently visible to the user.
        
        :return     <orb.Query>
        """
        container = self.currentContainer()
        if container:
            return container.query()
        return Query()

    def enterContainer(self, entry, query):
        """
        Enters a new container for the given entry widget.
        
        :param      entry | <XOrbQueryEntryWidget> || None
        """
        self._compoundStack.append(entry)
        self.addContainer(query)

    def exitContainer(self):
        """
        Removes the current query container.
        """
        try:
            entry = self._compoundStack.pop()
        except IndexError:
            return

        container = self.currentContainer()
        entry.setQuery(container.query())

        self.slideInPrev()

    def query(self):
        """
        Returns the full query for this widget.  This will reflect the complete
        combined query for all containers within this widget.
        
        :return     <orb.Query>
        """
        if self._loadQuery is not None:
            return self._loadQuery

        container = self.widget(0)
        if container:
            query = container.query()
        else:
            query = Query()

        return query

    def pluginFactory(self):
        """
        Returns the plugin factory that will be used to generate plugins for
        the query selector.  You can subclass the XOrbQueryPlugin and
        XOrbQueryPluginFactory to create custom plugins for schemas and widgets.
        
        :return     <XOrbQueryPluginFactory>
        """
        return self._pluginFactory

    def reset(self):
        """
        Resets this query builder to a blank query.
        """
        self.setQuery(Query())

    def setCurrentQuery(self, query):
        """
        Sets the query for the current container widget.  This will only change
        the active container, not parent containers.  You should use the
        setQuery method to completely assign a query to this widget.
        
        :param      query | <orb.Query>
        """
        container = self.currentContainer()
        if container:
            container.setQuery(query)

    def setQuery(self, query):
        """
        Sets the query for this widget to the inputed query.  This will clear
        completely the current inforamation and reest all containers to the
        inputed query information.
        
        :param      query | <orb.Query>
        """
        if not self.isVisible():
            self._loadQuery = query
        else:
            self._loadQuery = None

            if self._initialized and hash(query) == hash(self.query()):
                return

            self._initialized = True
            self.clear()
            self.addContainer(query)

    def setPluginFactory(self, factory):
        """
        Assigns the plugin factory for this query widget to the inputed factory.
        You can use this to create custom handlers for columns when your schema
        is being edited.
        
        :param      factory | <XOrbQueryPluginFactory>
        """
        self._pluginFactory = factory

    def setShowReferencePlugins(self, state=True):
        """
        Sets whether or not reference based plugins will be displayed to the
        user.
        
        :param      state | <bool>
        """
        self._showReferencePlugins = state

    def showReferencePlugins(self):
        """
        Returns whether or not reference based plugins will be displayed to the
        user.
        
        :return     <bool>
        """
        return self._showReferencePlugins

    def showEvent(self, event):
        if self._loadQuery is not None:
            self.setQuery(self._loadQuery)
        elif not self._initialized:
            self.setQuery(Query())

    def setTableType(self, tableType):
        """
        Sets the table type for this instance to the given type.
        
        :param      tableType | <orb.Table>
        """
        if tableType == self._tableType:
            return

        self._initialized = False
        self._tableType = tableType
        self.setQuery(Query())

    def tableType(self):
        """
        Returns the table type instance for this widget.
        
        :return     <subclass of orb.Table>
        """
        return self._tableType

    x_showReferencePlugins = Property(bool, showReferencePlugins,
                                      setShowReferencePlugins)