Example #1
0
class XColorButton(QPushButton):
    """
    The XColorButton class is a simple extension to the standard QPushButton
    that will control color settings.  When teh user clicks on the button, the
    QColorDialog will be displayed, prompting the user to select a new color.
    Colors are stored internally can can be accessed by etter and setter 
    methods, as well as the colorChanged signal.
    
    As the color is modified, either through code or by a user, the background
    color for the button will automatically update to match.
    
    == Example ==
    
    |>>> from projexui.widgets.xcolorbutton import XColorButton
    |>>> import projexui
    |
    |>>> # create the widget
    |>>> btn = projexui.testWidget(XColorButton)
    |
    |>>> # click around, change the color
    |>>> from PyQt4.QtGui import QColor
    |>>> print btn.color().red(), btn.color().green(), btn.color().blue()
    |255 170 0
    |>>> btn.setColor(QColor('red'))
    |
    |>>> # create connections
    |>>> def printColor(clr): print clr.red(), clr.green(), clr.blue()
    |>>> btn.colorChanged.connect(printColor)
    |
    |>>> # prompt the user to select a color for that button
    |>>> btn.pickColor()
    """
    colorChanged = qt.Signal(QColor)

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

        # initialize the color
        color = QColor('black')
        self._color = color
        palette = self.palette()
        palette.setColor(palette.Button, color)
        self.setPalette(palette)

        # create connections
        self.clicked.connect(self.pickColor)

    def color(self):
        """
        Returns the color value for this button.
        
        :return     <QColor>
        """
        return self._color

    def pickColor(self):
        """
        Prompts the user to select a color for this button.
        """
        color = QColorDialog.getColor(self.color(), self)

        if (color.isValid()):
            self.setColor(color)

    def setColor(self, color):
        """
        Sets the color value for this button to the given color.
        
        :param      color | <QColor>
        """
        self._color = color
        palette = self.palette()
        palette.setColor(palette.Button, color)
        self.setPalette(palette)

        if (not self.signalsBlocked()):
            self.colorChanged.emit(color)

    x_color = qt.Property(QColor, color, setColor)
Example #2
0
class XUrlWidget(QWidget):
    urlChanged = qt.Signal(str)
    urlEdited = qt.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()

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

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

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

    x_hint = qt.Property(str, hint, setHint)
    x_url = qt.Property(str, url, setUrl)
Example #3
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 = qt.Property(bool, isChecked, setChecked)
    x_falseText = qt.Property(str, falseText, setFalseText)
    x_trueText = qt.Property(str, trueText, setTrueText)
Example #4
0
class XTabWidget(QTabWidget):
    addRequested = qt.Signal(QPoint)
    optionsRequested = qt.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 = QPushButton(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 = QPushButton(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():
            if self.currentIndex() == self.count() - 1:
                self._addButton.move(tabbar.width() - 2, -1)
                self._addButton.setFixedHeight(tabbar.height() + 2)
            else:
                self._addButton.move(tabbar.width() - 4, 1)
                self._addButton.setFixedHeight(tabbar.height())
        else:
            self._addButton.move(tabbar.width() + 2, 1)

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

    def addButton(self):
        """
        Returns the add button linked with this tab widget.
        
        :return     <QPushButton>
        """
        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:
            point = QCursor.pos()

        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:
            point = QCursor.pos()

        self.optionsRequested.emit(point)

    def optionsButton(self):
        """
        Returns the options button linked with this tab widget.
        
        :return     <QPushButton>
        """
        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 = qt.Property(bool, showAddButton, setShowAddButton)
    x_showOptionsButton = qt.Property(bool, showOptionsButton,
                                      setShowOptionsButton)
Example #5
0
class XLocationWidget(QWidget):
    locationChanged = qt.Signal(str)
    locationEdited = qt.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 str(self._locationEdit.text())

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

    @qt.Slot(str)
    def setLocation(self, location):
        """
        Sets the location for this widget to the inputed location.
        
        :param      location | <str>
        """
        self._locationEdit.setText(str(location))

    def setUrlQueryKey(self, key):
        """
        Sets the key for the URL to the inputed key.
        
        :param      key | <str>
        """
        self._urlQueryKey = str(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 = str(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 = qt.Property(str, hint, setHint)
    x_location = qt.Property(str, location, setLocation)
    x_urlQueryKey = qt.Property(str, urlQueryKey, setUrlQueryKey)
    x_urlTemplate = qt.Property(str, urlTemplate, setUrlTemplate)
Example #6
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__ = projexui.resources.find('img/ui/combobox.png')

    checkedIndexesChanged = qt.Signal(list)
    checkedItemsChanged = qt.Signal(list)

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

        # define custom properties
        self._checkable = False
        self._hint = ''
        self._separator = ','

        # setup the checkable popup widget
        self._checkablePopup = None

        # set default properties
        self.setLineEdit(XLineEdit(self))

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

        return super(XComboBox, self).currentText()

    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 [str(self.itemText(i)) for i in self.checkedIndexes()]

    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>
        """
        return self._checkable

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

        model = self.model()
        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)

    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 (str(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)

        edit.installEventFilter(self)
        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)

    @qt.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 = str(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

        self.lineEdit().setText(self.separator().join(self.checkedItems()))

    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 = qt.Property(str, hint, setHint)
    x_checkable = qt.Property(bool, isCheckable, setCheckable)
    x_separator = qt.Property(str, separator, setSeparator)
Example #7
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')

    textEntered = qt.Signal(str)

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

    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._spacer = '_'
        self._hintColor = hint_clr
        self._cornerRadius = 0
        self._inputFormat = XLineEdit.InputFormat.Normal
        self._selectAllOnFocus = False
        self._focusedIn = False
        self._useHintValue = False

        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.setAutoRaise(True)
        button.setParent(self)
        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.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)

        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 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 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 = str(self.text())
        if (text):
            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 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.toUtf8(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()

        return text

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

    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
        painter = QPainter(self)
        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 = 5 + left
            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 -= sum([btn.width() for btn in self.buttons()])
        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()

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

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

    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[str(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(self.formatText(text))

    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 = qt.Property(str, hint, setHint)
    x_icon = qt.Property('QIcon', icon, setIcon)
    x_iconSize = qt.Property(QSize, iconSize, setIconSize)
    x_hintColor = qt.Property('QColor', hintColor, setHintColor)
    x_cornerRadius = qt.Property(int, cornerRadius, setCornerRadius)
    x_inputFormatText = qt.Property(str, inputFormatText, setInputFormatText)
    x_spacer = qt.Property(str, spacer, setSpacer)
    x_selectAllOnFocus = qt.Property(bool, selectAllOnFocus,
                                     setSelectAllOnFocus)
    x_useHintValue = qt.Property(bool, useHintValue, setUseHintValue)

    # hack for qt
    setX_icon = setIcon
Example #8
0
class XFilepathEdit(QWidget):
    """
    The XFilepathEdit class provides a common interface to prompt the user to
    select a filepath from the filesystem.  It can be configured to load
    directories, point to a save file path location, or to an open file path
    location.  It can also be setup to color changed based on the validity
    of the existance of the filepath.
    
    == Example ==
    
    |>>> from projexui.widgets.xfilepathedit import XFilepathEdit
    |>>> import projexui
    |
    |>>> # create the edit
    |>>> edit = projexui.testWidget(XFilepathEdit)
    |
    |>>> # set the filepath
    |>>> edit.setFilepath('/path/to/file')
    |
    |>>> # prompt the user to select the filepath
    |>>> edit.pickFilepath()
    |
    |>>> # enable the coloring validation
    |>>> edit.setValidated(True)
    """

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

    Mode = enum('OpenFile', 'SaveFile', 'Path', 'OpenFiles')

    filepathChanged = qt.Signal(str)

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

        # define custom properties
        self._validated = False
        self._validForeground = QColor(0, 120, 0)
        self._validBackground = QColor(0, 120, 0, 100)
        self._invalidForeground = QColor(255, 0, 0)
        self._invalidBackground = QColor(255, 0, 0, 100)
        self._normalizePath = False

        self._filepathMode = XFilepathEdit.Mode.OpenFile
        self._filepathEdit = XLineEdit(self)
        self._filepathButton = QToolButton(self)
        self._filepathTypes = 'All Files (*.*)'

        # set default properties
        ico = projexui.resources.find('img/folder.png')
        self._filepathEdit.setReadOnly(False)
        self._filepathButton.setText('...')
        self._filepathButton.setFixedSize(25, 23)
        self._filepathButton.setAutoRaise(True)
        self._filepathButton.setIcon(QIcon(ico))
        self._filepathEdit.setContextMenuPolicy(Qt.CustomContextMenu)

        self.setWindowTitle('Load File')
        self.setAcceptDrops(True)

        # define the layout
        layout = QHBoxLayout()
        layout.setContentsMargins(0, 0, 0, 0)
        layout.setSpacing(0)
        layout.addWidget(self._filepathEdit)
        layout.addWidget(self._filepathButton)
        self.setLayout(layout)

        # create connections
        self._filepathEdit.installEventFilter(self)

        self._filepathButton.clicked.connect(self.pickFilepath)
        self._filepathEdit.textChanged.connect(self.emitFilepathChanged)
        self._filepathEdit.textChanged.connect(self.validateFilepath)
        self._filepathEdit.customContextMenuRequested.connect(self.showMenu)

    def autoRaise(self):
        """
        Returns whether or not the tool button will auto raise.
        
        :return     <bool>
        """
        return self._filepathButton.autoRaise()

    @qt.Slot()
    def clearFilepath(self):
        """
        Clears the filepath contents for this path.
        """
        self.setFilepath('')

    @qt.Slot()
    def copyFilepath(self):
        """
        Copies the current filepath contents to the current clipboard.
        """
        clipboard = QApplication.instance().clipboard()
        clipboard.setText(self.filepath())
        clipboard.setText(self.filepath(), clipboard.Selection)

    def dragEnterEvent(self, event):
        """
        Processes drag enter events.
        
        :param      event | <QDragEvent>
        """
        if (event.mimeData().hasUrls()):
            event.acceptProposedAction()

    def dragMoveEvent(self, event):
        """
        Processes drag move events.
        
        :param      event | <QDragEvent>
        """
        if (event.mimeData().hasUrls()):
            event.acceptProposedAction()

    def dropEvent(self, event):
        """
        Processes drop event.
        
        :param      event | <QDropEvent>
        """
        if (event.mimeData().hasUrls()):
            url = event.mimeData().urls()[0]
            filepath = url.toLocalFile()
            if (filepath):
                self.setFilepath(filepath)

    def emitFilepathChanged(self):
        """
        Emits the filepathChanged signal for this widget if the signals are \
        not being blocked.
        """
        if (not self.signalsBlocked()):
            self.filepathChanged.emit(self.filepath())

    def eventFilter(self, object, event):
        """
        Overloads the eventFilter to look for click events on the line edit.
        
        :param      object | <QObject>
                    event  | <QEvent>
        """
        if ( object == self._filepathEdit and \
             self._filepathEdit.isReadOnly() and \
             event.type() == event.MouseButtonPress and \
             event.button() == Qt.LeftButton ):
            self.pickFilepath()

        return False

    def filepath(self, validated=False):
        """
        Returns the filepath for this widget.  If the validated flag is set \
        then this method will only return if the file or folder actually \
        exists for this path.  In the case of a SaveFile, only the base folder \
        needs to exist on the system, in other modes the actual filepath must \
        exist.  If not validated, the text will return whatever is currently \
        entered.
        
        :return     <str>
        """
        paths = self.filepaths()
        if not paths:
            return ''

        if not validated or self.isValid():
            return paths[0]
        return ''

    def filepaths(self):
        """
        Returns a list of the filepaths for this edit.
        
        :return     [<str>, ..]
        """
        return str(self._filepathEdit.text()).split(os.path.pathsep)

    def filepathMode(self):
        """
        Returns the filepath mode for this widget.
        
        :return     <XFilepathEdit.Mode>
        """
        return self._filepathMode

    def filepathModeText(self):
        """
        Returns the text representation for this filepath mode.
        
        :return     <str>
        """
        return XFilepathEdit.Mode[self._filepathMode]

    def filepathTypes(self):
        """
        Returns the filepath types that will be used for this widget.
        
        :return     <str>
        """
        return self._filepathTypes

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

    def icon(self):
        """
        Returns the icon that is used for this filepath widget.
        
        :return     <QIcon>
        """
        return self._filepathButton.icon()

    def invalidBackground(self):
        """
        Returns the invalid background color for this widget.
        
        :return     <QColor>
        """
        return self._invalidBackground

    def invalidForeground(self):
        """
        Returns the invalid foreground color for this widget.
        
        :return     <QColor>
        """
        return self._invalidForeground

    def isValid(self):
        """
        Returns whether or not the filepath exists on the system. \
        In the case of a SaveFile, only the base folder \
        needs to exist on the system, in other modes the actual filepath must \
        exist.
        
        :return     <bool>
        """
        check = str(self._filepathEdit.text()).split(os.path.pathsep)[0]
        if (self.filepathMode() == XFilepathEdit.Mode.SaveFile):
            check = os.path.dirname(check)

        return os.path.exists(check)

    def isValidated(self):
        """
        Set whether or not to validate the filepath as the user is working \
        with it.
        
        :return     <bool>
        """
        return self._validated

    def isReadOnly(self):
        """
        Returns if the widget is read only for text editing or not.
        
        :return     <bool>
        """
        return self._filepathEdit.isReadOnly()

    def normalizePath(self):
        """
        Returns whether or not the path should be normalized for the current
        operating system.  When off, it will be defaulted to forward slashes
        (/).
        
        :return     <bool>
        """
        return self._normalizePath

    def pickFilepath(self):
        """
        Prompts the user to select a filepath from the system based on the \
        current filepath mode.
        """
        mode = self.filepathMode()

        filepath = ''
        filepaths = []
        curr_dir = str(self._filepathEdit.text())
        if (not curr_dir):
            curr_dir = QDir.currentPath()

        if mode == XFilepathEdit.Mode.SaveFile:
            filepath = QFileDialog.getSaveFileName(self, self.windowTitle(),
                                                   curr_dir,
                                                   self.filepathTypes())

        elif mode == XFilepathEdit.Mode.OpenFile:
            filepath = QFileDialog.getOpenFileName(self, self.windowTitle(),
                                                   curr_dir,
                                                   self.filepathTypes())

        elif mode == XFilepathEdit.Mode.OpenFiles:
            filepaths = QFileDialog.getOpenFileNames(self, self.windowTitle(),
                                                     curr_dir,
                                                     self.filepathTypes())

        else:
            filepath = QFileDialog.getExistingDirectory(
                self, self.windowTitle(), curr_dir)

        if filepath:
            if type(filepath) == tuple:
                filepath = filepath[0]
            self.setFilepath(str(filepath))
        elif filepaths:
            self.setFilepaths(map(str, filepaths))

    def setAutoRaise(self, state):
        """
        Sets whether or not the tool button will auto raise.
        
        :param      state | <bool>
        """
        self._filepathButton.setAutoRaise(state)

    @qt.Slot(int)
    def setFilepathMode(self, mode):
        """
        Sets the filepath mode for this widget to the inputed mode.
        
        :param      mode | <XFilepathEdit.Mode>
        """
        self._filepathMode = mode

    @qt.Slot(str)
    def setFilepathModeText(self, text):
        """
        Sets the filepath mode for this widget based on the inputed text.
        
        :param      text | <str>
        
        :return     <bool> | success
        """
        try:
            self.setFilepathMode(XFilepathEdit.Mode[str(text)])
            return True
        except KeyError:
            return False

    @qt.Slot(str)
    def setFilepathTypes(self, filepathTypes):
        """
        Sets the filepath type string that will be used when looking up \
        filepaths.
        
        :param      filepathTypes | <str>
        """
        self._filepathTypes = filepathTypes

    @qt.Slot(str)
    def setFilepath(self, filepath):
        """
        Sets the filepath text for this widget to the inputed path.
        
        :param      filepath | <str>
        """
        if self.normalizePath():
            filepath = os.path.normpath(str(filepath))
        else:
            filepath = os.path.normpath(str(filepath)).replace('\\', '/')

        self._filepathEdit.setText(filepath)

    def setFilepaths(self, filepaths):
        """
        Sets the list of the filepaths for this widget to the inputed paths.
        
        :param      filepaths | [<str>, ..]
        """
        self.setFilepath(os.path.pathsep.join(filepaths))

    def setHint(self, hint):
        """
        Sets the hint for this filepath.
        
        :param      hint | <str>
        """
        if self.normalizePath():
            filepath = os.path.normpath(str(hint))
        else:
            filepath = os.path.normpath(str(hint)).replace('\\', '/')

        self._filepathEdit.setHint(hint)

    def setIcon(self, icon):
        """
        Sets the icon that will be used for this widget's tool button.
        
        :param      icon | <QIcon> || <str>
        """
        self._filepathButton.setIcon(QIcon(icon))

    def setInvalidBackground(self, bg):
        """
        Sets the invalid background color for this widget to the inputed widget.
        
        :param      bg | <QColor>
        """
        self._invalidBackground = QColor(bg)

    def setInvalidForeground(self, fg):
        """
        Sets the invalid foreground color for this widget to the inputed widget.
        
        :param      fg | <QColor>
        """
        self._invalidForeground = QColor(fg)

    def setNormalizePath(self, state):
        """
        Sets whether or not the path should be normalized for the current
        operating system.  When off, it will be defaulted to forward slashes
        (/).
        
        :param      state | <bool>
        """
        self._normalizePath = state

    @qt.Slot(bool)
    def setReadOnly(self, state):
        """
        Sets whether or not this filepath widget is readonly in the text edit.
        
        :param      state | <bool>
        """
        self._filepathEdit.setReadOnly(state)

    @qt.Slot(bool)
    def setValidated(self, state):
        """
        Set whether or not to validate the path as the user edits it.
        
        :param      state | <bool>
        """
        self._validated = state
        palette = self.palette()

        # reset the palette to default, revalidate
        self._filepathEdit.setPalette(palette)
        self.validate()

    def setValidBackground(self, bg):
        """
        Sets the valid background color for this widget to the inputed color.
        
        :param      bg | <QColor>
        """
        self._validBackground = QColor(bg)

    def setValidForeground(self, fg):
        """
        Sets the valid foreground color for this widget to the inputed color.
        
        :param      fg | <QColor>
        """
        self._validForeground = QColor(fg)

    def showMenu(self, pos):
        """
        Popups a menu for this widget.
        """
        menu = QMenu(self)
        menu.setAttribute(Qt.WA_DeleteOnClose)
        menu.addAction('Clear').triggered.connect(self.clearFilepath)
        menu.addSeparator()
        menu.addAction('Copy Filepath').triggered.connect(self.copyFilepath)

        menu.exec_(self.mapToGlobal(pos))

    def validBackground(self):
        """
        Returns the valid background color for this widget.
        
        :return     <QColor>
        """
        return self._validBackground

    def validForeground(self):
        """
        Returns the valid foreground color for this widget.
        
        :return     <QColor>
        """
        return self._validForeground

    def validateFilepath(self):
        """
        Alters the color scheme based on the validation settings.
        """
        if (not self.isValidated()):
            return

        valid = self.isValid()
        if (not valid):
            fg = self.invalidForeground()
            bg = self.invalidBackground()
        else:
            fg = self.validForeground()
            bg = self.validBackground()

        palette = self.palette()
        palette.setColor(palette.Base, bg)
        palette.setColor(palette.Text, fg)
        self._filepathEdit.setPalette(palette)

    # map Qt properties
    x_autoRaise = qt.Property(bool, autoRaise, setAutoRaise)
    x_filepathTypes = qt.Property(str, filepathTypes, setFilepathTypes)
    x_filepath = qt.Property(str, filepath, setFilepath)
    x_readOnly = qt.Property(bool, isReadOnly, setReadOnly)
    x_validated = qt.Property(bool, isValidated, setValidated)
    x_hint = qt.Property(str, hint, setHint)
    x_icon = qt.Property('QIcon', icon, setIcon)
    x_normalizePath = qt.Property(bool, normalizePath, setNormalizePath)

    x_invalidForeground = qt.Property('QColor', invalidForeground,
                                      setInvalidForeground)

    x_invalidBackground = qt.Property('QColor', invalidBackground,
                                      setInvalidBackground)

    x_validForeground = qt.Property('QColor', validForeground,
                                    setValidForeground)

    x_validBackground = qt.Property('QColor', validBackground,
                                    setValidBackground)

    x_filepathModeText = qt.Property(str, filepathModeText,
                                     setFilepathModeText)
Example #9
0
class XIconButton(QPushButton):
    """ """
    __designer_icon__ = projexui.resources.find('img/ui/icon.png')

    filepathChanged = qt.Signal(str)

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

        # define custom properties
        self._filepath = ''
        self._fileTypes = 'PNG Files (*.png);;All Files (*.*)'

        # set default properties
        self.setFixedWidth(64)
        self.setFixedHeight(64)
        self.setAcceptDrops(True)

        # create connections
        self.clicked.connect(self.pickFilepath)

    def filepath(self):
        """
        Returns the filepath for this button.
        
        :return     <str>
        """
        return self._filepath

    def fileTypes(self):
        """
        Returns the file types that will be used to filter this button.
        
        :return     <str>
        """
        return self._fileTypes

    def dragEnterEvent(self, event):
        """
        Handles a drag enter event.
        
        :param      event | <QEvent>
        """
        if (event.mimeData().hasUrls()):
            event.acceptProposedAction()

    def dragMoveEvent(self, event):
        """
        Handles a drag move event.
        
        :param      event | <QEvent>
        """
        if (event.mimeData().hasUrls()):
            event.acceptProposedAction()

    def dropEvent(self, event):
        """
        Handles a drop event.
        """
        url = event.mimeData().urls()[0]
        url_path = str(url.toString())

        # download an icon from the web
        if (not url_path.startswith('file:')):
            filename = os.path.basename(url_path)
            temp_path = os.path.join(str(QDir.tempPath()), filename)

            try:
                urllib.urlretrieve(url_path, temp_path)
            except IOError:
                return

            self.setFilepath(temp_path)
        else:
            self.setFilepath(url_path.replace('file://', ''))

    def pickFilepath(self):
        """
        Picks the image file to use for this icon path.
        """
        filepath = QFileDialog.getOpenFileName(self, 'Select Image File',
                                               QDir.currentPath(),
                                               self.fileTypes())

        if type(filepath) == tuple:
            filepath = str(filepath[0])

        if (filepath):
            self.setFilepath(filepath)

    def setFilepath(self, filepath):
        """
        Sets the filepath for this button to the inputed path.
        
        :param      filepath | <str>
        """
        self._filepath = str(filepath)
        self.setIcon(QIcon(filepath))
        if (not self.signalsBlocked()):
            self.filepathChanged.emit(filepath)

    def setFileTypes(self, fileTypes):
        """
        Sets the filetypes for this button to the inputed types.
        
        :param      fileTypes | <str>
        """
        self._fileTypes = fileTypes

    @staticmethod
    def buildIcon(icon):
        """
        Builds an icon from the inputed information.
        
        :param      icon | <variant>
        """
        if icon is None:
            return QIcon()

        if type(icon) == buffer:
            try:
                icon = QIcon(projexui.generatePixmap(icon))
            except:
                icon = QIcon()
        else:
            try:
                icon = QIcon(icon)
            except:
                icon = QIcon()

        return icon

    x_filepath = qt.Property(str, filepath, setFilepath)
    x_fileTypes = qt.Property(str, fileTypes, setFileTypes)
Example #10
0
        :return     <str>
        """
        return ElementTree.tostring(self.toXml())

    def toXml(self):
        """
        Saves this profile toolbar as XML information.
        
        :return     <xml.etree.ElementTree.Element>
        """
        xtoolbar = ElementTree.Element('toolbar')

        act = self._profileGroup.checkedAction()
        if (act):
            xtoolbar.set('current', act.profile().name())

        for profile in self.profiles():
            profile.toXml(xtoolbar)

        return xtoolbar

    def viewWidget(self):
        """
        Returns the view widget linked with this toolbar.
        
        :return     <projexui.widgets.xviewwidget.XViewWidget>
        """
        return self._viewWidget

    x_editingEnabled = qt.Property(bool, isEditingEnabled, setEditingEnabled)
Example #11
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 = str(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 = qt.Property(bool, showRichText, setShowRichText)
Example #12
0
class XNodeWidget(QGraphicsView):
    """ Defines the main widget for creating node graph views. """
    __designer_icon__ = projexui.resources.find('img/ui/node.png')

    zoomAmountChanged = qt.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.setScene(sceneClass(self))
        self.setViewportUpdateMode(QGraphicsView.FullViewportUpdate)
        self.setContextMenuPolicy(Qt.CustomContextMenu)
        self.setBaseSize(QSize(300, 250))

    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

    @qt.Slot()
    def autoLayout(self):
        """
        Auto-lays out the whole scene.
        """
        self.scene().autoLayout()

    @qt.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)

    @qt.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)

    @qt.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

    @qt.Slot(bool)
    def setIsolationMode(self, state):
        """
        Sets whether or not the widget is in isolation mode.
        
        :param      state | <bool>
        """
        self.scene().setIsolationMode(state)

    @qt.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 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()

    @qt.Slot()
    def zoomExtents(self):
        """
        Fits all the nodes in the view.
        """
        rect = self.scene().visibleItemsBoundingRect()
        vrect = self.viewportRect()
        if rect.width():
            if rect.width() < vrect.width() and rect.height() < vrect.height():
                self.centerOn(rect.center())
            else:
                self.fitInView(rect, Qt.KeepAspectRatio)

        if not self.signalsBlocked():
            self.zoomAmountChanged.emit(self.zoomAmount())

    @qt.Slot()
    def zoomIn(self):
        """
        Zooms in for this widget by the scene's zoom step amount.
        """
        self.scene().zoomIn()

    @qt.Slot()
    def zoomOut(self):
        """
        Zooms out for this widget by the scene's zoom step amount.
        """
        self.scene().zoomOut()

    x_isolationMode = qt.Property(bool, isolationMode, setIsolationMode)
    x_cleanupOnClose = qt.Property(bool, cleanupOnClose, setCleanupOnClose)
Example #13
0
class XChart(QFrame):
    """ """
    middleClicked = qt.Signal(QPoint)

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

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

        # define custom properties
        self._renderer = None
        self._chartTitle = ''
        self._axes = []
        self._datasets = []
        self._horizontalAxis = None
        self._verticalAxis = None
        self._showDatasetToolbar = True
        self._showTypeButton = True
        self._dataChanged = False
        self._showGrid = True
        self._showRows = True
        self._showColumns = True
        self._showXAxis = True
        self._showYAxis = True

        # set default properties
        self.uiChartVIEW.setScene(XChartScene(self))
        self.uiXAxisVIEW.setScene(XChartScene(self))
        self.uiYAxisVIEW.setScene(XChartScene(self))
        self.uiChartVIEW.setAlignment(Qt.AlignTop | Qt.AlignLeft)
        self.uiXAxisVIEW.setAlignment(Qt.AlignTop | Qt.AlignLeft)
        self.uiYAxisVIEW.setAlignment(Qt.AlignTop | Qt.AlignLeft)

        self.uiDatasetTBAR.setFixedHeight(32)

        self.uiXAxisVIEW.hide()
        self.uiYAxisVIEW.hide()

        self.setAutoFillBackground(True)
        self.setBackgroundRole(QPalette.Base)
        self.setStyleSheet(STYLE)
        self.uiChartVIEW.setMouseTracking(True)
        self.uiDatasetTBAR.setStyleSheet(TOOLBAR_STYLE)

        # load renderers
        for renderer in XChartRenderer.plugins():
            act = QAction('%s Chart' % renderer, self)
            ico = 'img/chart/%s.png' % renderer.lower()
            act.setIcon(QIcon(resources.find(ico)))
            self.uiTypeBTN.addAction(act)

            # assign the default renderer
            if not self.uiTypeBTN.defaultAction():
                self._renderer = XChartRenderer.plugin(renderer)
                self.uiTypeBTN.setDefaultAction(act)

        # create connections
        chart_hbar = self.uiChartVIEW.horizontalScrollBar()
        chart_vbar = self.uiChartVIEW.verticalScrollBar()

        x_bar = self.uiXAxisVIEW.horizontalScrollBar()
        y_bar = self.uiYAxisVIEW.verticalScrollBar()

        chart_hbar.valueChanged.connect(self.syncScrollbars)
        chart_hbar.rangeChanged.connect(self.syncScrollbars)
        y_bar.valueChanged.connect(self.syncScrollbars)
        y_bar.rangeChanged.connect(self.syncScrollbars)
        self.uiTypeBTN.triggered.connect(self.assignRenderer)

    def _addDatasetAction(self, dataset):
        """
        Adds an action for the inputed dataset to the toolbar
        
        :param      dataset | <XChartDataset>
        """
        # create the toolbar action
        action = QAction(dataset.name(), self)
        action.setIcon(XColorIcon(dataset.color()))
        action.setCheckable(True)
        action.setChecked(True)
        action.setData(qt.wrapVariant(dataset))
        action.toggled.connect(self.toggleDataset)

        self.uiDatasetTBAR.addAction(action)

    def _drawBackground(self, scene, painter, rect):
        """
        Draws the backgroud for a particular scene within the charts.
        
        :param      scene   | <XChartScene>
                    painter | <QPainter>
                    rect    | <QRectF>
        """
        rect = scene.sceneRect()
        if scene == self.uiChartVIEW.scene():
            self.renderer().drawGrid(painter, rect, self.showGrid(),
                                     self.showColumns(), self.showRows())
        elif scene == self.uiXAxisVIEW.scene():
            self.renderer().drawAxis(painter, rect, self.horizontalAxis())
        elif scene == self.uiYAxisVIEW.scene():
            self.renderer().drawAxis(painter, rect, self.verticalAxis())

    def addAxis(self, axis):
        """
        Adds a new axis for this chart.  Axis can define X & Y data
        including notch and value processing, as well as define
        individual lines within the chart that any chart items can
        reference or use.
        
        :param      axis | <projexui.widgets.xchart.XChartAxis>
        """
        self._axes.append(axis)

    def addDataset(self, dataset):
        """
        Adds the given data set to this chart widget.
        
        :param      dataSet | <XChartDataset>
        """
        self._datasets.append(dataset)
        self._dataChanged = True
        self._addDatasetAction(dataset)

    def addToolbarWidget(self, widget):
        """
        Adds a new widget to the toolbar layout for the chart.
        
        :param      widget | <QWidget>
        """
        self.uiToolbarHBOX.addWidget(widget)

    def assignRenderer(self, action):
        """
        Assigns the renderer for this chart to the current selected
        renderer.
        """
        name = str(action.text()).split(' ')[0]
        self._renderer = XChartRenderer.plugin(name)
        self.uiTypeBTN.setDefaultAction(action)
        self.recalculate()

    def axes(self):
        """
        Returns all the axes that have been defined for this chart.
        
        :return     [<projexui.widgets.xchart.XChartAxis>, ..]
        """
        out = self._axes[:]
        if self._horizontalAxis:
            out.append(self._horizontalAxis)
        if self._verticalAxis:
            out.append(self._verticalAxis)
        return out

    def axis(self, name):
        """
        Looks up an axis for this chart by the given name.
        
        :return     <projexui.widgets.xchart.XChartAxis> || None
        """
        for axis in self.axes():
            if axis.name() == name:
                return axis
        return None

    def clear(self):
        """
        Clears out all the dataset information from the chart.
        """
        self.clearAxes()
        self.clearDatasets()

    def clearAxes(self):
        self._axes = []
        self._verticalAxis = None
        self._horizontalAxis = None

    def clearDatasets(self):
        self._datasets = []
        for act in self.uiDatasetTBAR.actions():
            act.deleteLater()

        self.uiChartVIEW.scene().clear()

    def chartTitle(self):
        """
        Returns the title for this plot.
        
        :return     <str>
        """
        return self._title

    def compareValues(self, a, b):
        """
        Compares two values based on their values for this axis.
        
        :param      a | <variant>
                    b | <variant>
        """
        values = self.values()
        try:
            return cmp(values.index(a), values.index(b))
        except ValueError:
            return cmp(a, b)

    def datasets(self, visible=True):
        """
        Returns a list of the data sets that are assigned with this
        chart widget.
        
        :param      visible | <bool>
        
        :return     [<XChartDataSet>, ..]
        """
        if visible is not None:
            return filter(lambda x: x.isVisible(), self._datasets)
        return self._datasets[:]

    def showEvent(self, event):
        super(XChart, self).showEvent(event)
        self.recalculate()

    def findRenderer(self, name):
        """
        Returns the renderer based on the inputed name.
        
        :return     <str>
        """
        return XChartRenderer.plugin(name)

    def horizontalAxis(self):
        """
        Returns the axis that is assigned to the horizontal direction for this
        chart.
        
        :return     <XChartAxis>
        """
        return self._horizontalAxis

    def insertToolbarWidget(self, index, widget):
        """
        Inserts a new widget to the toolbar layout for the chart.
        
        :param      index  | <int>
                    widget | <QWidget>
        """
        self.uiToolbarHBOX.insertWidget(index, widget)

    def pointAt(self, **axis_values):
        """
        Returns the point on the chart where the inputed values are located.
        
        :return     <QPointF>
        """
        scene_point = self.renderer().pointAt(self.axes(), axis_values)
        chart_point = self.uiChartVIEW.mapFromScene(scene_point)
        return self.uiChartVIEW.mapToParent(chart_point)

    def mousePressEvent(self, event):
        if event.button() == Qt.MidButton:
            self.middleClicked.emit(event.pos())

        super(XChart, self).mousePressEvent(event)

    def recalculate(self):
        """
        Recalculates the information for this chart.
        """
        if not (self.isVisible() and self.renderer()):
            return

        # update dynamic range
        if self._dataChanged:
            for axis in self.axes():
                if axis.useDynamicRange():
                    axis.calculateRange(self.values(axis.name()))

            self._dataChanged = False

        # recalculate the main grid
        xaxis = self.horizontalAxis()
        yaxis = self.verticalAxis()
        renderer = self.renderer()

        xvisible = xaxis is not None and self.showXAxis(
        ) and renderer.showXAxis()
        yvisible = yaxis is not None and self.showYAxis(
        ) and renderer.showYAxis()

        self.uiXAxisVIEW.setVisible(xvisible)
        self.uiYAxisVIEW.setVisible(yvisible)

        # calculate the main view
        view = self.uiChartVIEW
        chart_scene = view.scene()
        chart_scene.setSceneRect(0, 0, view.width() - 2, view.height() - 2)
        rect = renderer.calculate(chart_scene, xaxis, yaxis)

        # recalculate the xaxis
        if xaxis and self.showXAxis() and renderer.showXAxis():
            view = self.uiXAxisVIEW
            scene = view.scene()
            scene.setSceneRect(0, 0, rect.width(), view.height())
            scene.invalidate()

        # render the yaxis
        if yaxis and self.showYAxis() and renderer.showYAxis():
            view = self.uiYAxisVIEW
            scene = view.scene()
            scene.setSceneRect(0, 0, view.width(), rect.height())
            scene.invalidate()

        # recalculate the items
        renderer.calculateDatasets(chart_scene, self.axes(), self.datasets())

        chart_scene.invalidate()

    def removeAxis(self, axis):
        """
        Removes an axis from this chart either by direct reference or by
        name.
        
        :param      axis | <projexui.widgets.XChartAxis> || <str>
        """
        if not isinstance(axis, XChartAxis):
            axis = self.axis(str(axis))

        try:
            self._axes.remove(axis)
        except ValueError:
            pass

    def renderer(self):
        """
        Returns the current renderer associated with this plot.
        
        :return     <projexui.widgets.xchart.XChartRenderer>
        """
        return self._renderer

    def renderers(self):
        """
        Returns the renderer instances associated with this chart.
        
        :return     [<XChartRenderer>, ..]
        """
        return map(XChartRenderer.plugin, XChartRenderer.plugins())

    def resizeEvent(self, event):
        """
        Recalculates the chart information when the widget resizes.
        
        :param      event | <QResizeEvent>
        """
        super(XChart, self).resizeEvent(event)

        if self.isVisible():
            self.recalculate()

    def restoreXml(self, xchart):
        """
        Restores the xml information for this chart.
        
        :param      xparent | <xml.etree.ElementTree.Element>
        """
        if xchart is None:
            return

        self.setRenderer(xchart.get('renderer', 'Bar'))

    def saveXml(self, xchart):
        """
        Saves the xml information for this chart to the inputed xml.
        
        :param      xchart | <xml.etree.ElementTree.Element>
        """
        if xchart is None:
            return

        xchart.set('renderer', self.renderer().name())

    def setRenderer(self, renderer):
        """
        Sets the current renderer associated with this plot.
        
        :param      renderer | <XChartRenderer> || <str>
        
        :return     <bool> | success
        """
        if not isinstance(renderer, XChartRenderer):
            renderer = XChartRenderer.plugin(str(renderer))

        if renderer is None:
            return False

        self._renderer = renderer

        for act in self.uiTypeBTN.actions():
            if act.text() == '%s Chart' % renderer.name():
                self.uiTypeBTN.setDefaultAction(act)
                break

        return True

    def setAxes(self, axes):
        """
        Sets the axes for this chart to the inputed list of axes.
        
        :param      axes | [<projexui.widgets.xchart.XChartAxis>, ..]
        """
        self._axes = axes

    def setDatasets(self, datasets):
        """
        Sets the dataset list for this chart to the inputed data.
        
        :param      datasets | [<XChartDataset>, ..]
        """
        self.clearDatasets()
        self._datasets = datasets

        for dataset in datasets:
            self._addDatasetAction(dataset)

        self._dataChanged = True
        self.recalculate()

    def setChartTitle(self, title):
        """
        Sets the title for the plot to the inputed title.
        
        :param      title | <str>
        """
        self._chartTitle = title

    def setHorizontalAxis(self, axis):
        """
        Sets the horizontal axis for this chart.
        
        :param      axis | <XChartAxis>
        """
        self._horizontalAxis = axis
        if axis:
            axis.setOrientation(Qt.Horizontal)
            self.uiXAxisVIEW.setFixedHeight(axis.minimumLabelHeight() + 5)

        self.uiXAxisVIEW.setVisible(axis is not None)

    def setVerticalAxis(self, axis):
        """
        Sets the vertical axis for this chart.
        
        :param      axis | <XChartAxis>
        """
        self._verticalAxis = axis
        if axis:
            axis.setOrientation(Qt.Vertical)
            self.uiYAxisVIEW.setFixedWidth(axis.minimumLabelWidth() + 15)

        self.uiYAxisVIEW.setVisible(axis is not None)

    def setShowColumns(self, state):
        """
        Sets the whether or not this renderer should draw the grid.
        
        :param      state | <bool>
        """
        self._showColumns = state

    def setShowDatasetToolbar(self, state):
        """
        Sets whether or not the dataset toolbar is visible.
        
        :param      state | <bool>
        """
        self._showDatasetToolbar = state
        if not state:
            self.uiDatasetTBAR.hide()
        else:
            self.uiDatasetTBAR.show()

    def setShowGrid(self, state):
        """
        Sets the whether or not this renderer should draw the grid.
        
        :param      state | <bool>
        """
        self._showGrid = state

    def setShowRows(self, state):
        """
        Sets the whether or not this renderer should draw the grid.
        
        :param      state | <bool>
        """
        self._showRows = state

    def setShowTypeButton(self, state):
        """
        Sets whether or not the type button is visible.
        
        :param      state | <bool>
        """
        self._showTypeButton = state
        if not state:
            self.uiTypeBTN.hide()
        else:
            self.uiTypeBTN.show()

    def setShowXAxis(self, state):
        """
        Sets the whether or not this renderer should draw the x-axis.
        
        :param      state | <bool>
        """
        self._showXAxis = state

    def setShowYAxis(self, state):
        """
        Sets the whether or not this renderer should draw the y-axis.
        
        :param      state | <bool>
        """
        self._showYAxis = state

    def showColumns(self):
        """
        Returns whether or not this renderer should draw the grid.
        
        :return     <bool>
        """
        return self._showColumns and self.showXAxis()

    def showDatasetToolbar(self):
        """
        Returns whether or not the dataset toolbar is visible.
        
        :return     <bool>
        """
        return self._showDatasetToolbar

    def showGrid(self):
        """
        Returns whether or not this renderer should draw the grid.
        
        :return     <bool>
        """
        return self._showGrid

    def showRows(self):
        """
        Returns whether or not this renderer should draw the grid.
        
        :return     <bool>
        """
        return self._showRows and self.showYAxis()

    def showTypeButton(self):
        """
        Returns whether or not the type button is visible.
        
        :return     <bool>
        """
        return self._showTypeButton

    def showXAxis(self):
        """
        Returns whether or not this renderer should draw the x-axis.
        
        :return     <bool>
        """
        return self._showXAxis

    def showYAxis(self):
        """
        Returns whether or not this renderer should draw the y-axis.
        
        :return     <bool>
        """
        return self._showYAxis

    def sizeHint(self):
        """
        Returns the size hint for this chart.
        """
        return QSize(300, 300)

    def syncScrollbars(self):
        """
        Synchronizes the various scrollbars within this chart.
        """
        chart_hbar = self.uiChartVIEW.horizontalScrollBar()
        chart_vbar = self.uiChartVIEW.verticalScrollBar()

        x_hbar = self.uiXAxisVIEW.horizontalScrollBar()
        x_vbar = self.uiXAxisVIEW.verticalScrollBar()

        y_hbar = self.uiYAxisVIEW.horizontalScrollBar()
        y_vbar = self.uiYAxisVIEW.verticalScrollBar()

        x_hbar.setRange(chart_hbar.minimum(), chart_hbar.maximum())
        x_hbar.setValue(chart_hbar.value())
        x_vbar.setValue(0)

        chart_vbar.setRange(y_vbar.minimum(), y_vbar.maximum())
        chart_vbar.setValue(y_vbar.value())

        y_hbar.setValue(4)

    def toggleDataset(self, state, dataset=None):
        """
        Toggles the dataset based on the given action or dataset.
        
        :param      state | <bool>
                    dataset | <XChartDataset>
        """
        if dataset is None and self.sender():
            dataset = qt.unwrapVariant(self.sender().data())

        dataset.setVisible(state)
        self._dataChanged = True
        self.recalculate()

    def valueAt(self, point):
        """
        Returns the value within the chart for the given point.
        
        :param      point | <QPoint>
        
        :return     {<str> axis name: <variant> value, ..}
        """
        chart_point = self.uiChartVIEW.mapFromParent(point)
        scene_point = self.uiChartVIEW.mapToScene(chart_point)
        return self.renderer().valueAt(self.axes(), scene_point)

    def values(self, axis):
        """
        Returns the values of the given axis from all the datasets within
        this chart.
        
        :param      axis | <str>
        
        :return     [<variant>, ..]
        """
        output = []
        for dataset in self.datasets():
            output += dataset.values(axis)

        return output

    def verticalAxis(self):
        """
        Returns the axis that is used for the vertical view for
        this graph.
        
        :return     <XChartAxis>
        """
        return self._verticalAxis

    x_showColumns = qt.Property(bool, showColumns, setShowColumns)
    x_showRows = qt.Property(bool, showRows, setShowRows)
    x_showGrid = qt.Property(bool, showGrid, setShowGrid)
    x_showYAxis = qt.Property(bool, showYAxis, setShowYAxis)
    x_showXAxis = qt.Property(bool, showXAxis, setShowXAxis)

    x_showDatasetToolbar = qt.Property(bool, showDatasetToolbar,
                                       setShowDatasetToolbar)

    x_showTypeButton = qt.Property(bool, showTypeButton, setShowTypeButton)