class XViewProfileManager(QWidget): currentProfileChanged = Signal(PyObject) optionsMenuRequested = Signal(QPoint) def __init__(self, parent=None): super(XViewProfileManager, self).__init__(parent) # define custom properties self._profiles = [] self._optionsMenuPolicy = Qt.DefaultContextMenu self._viewWidget = None # define the interface self._profileCombo = QComboBox(self) self._optionsButton = QToolButton(self) self._optionsButton.setAutoRaise(True) self._optionsButton.setToolTip('Advanced Options') self._optionsButton.setIcon(QIcon(resources.find('img/advanced.png'))) layout = QHBoxLayout() layout.setSpacing(0) layout.setContentsMargins(0, 0, 0, 0) layout.addWidget(self._profileCombo) layout.addWidget(self._optionsButton) self.setLayout(layout) # create connections self._profileCombo.currentIndexChanged.connect(self.handleProfileChange) self._optionsButton.clicked.connect(self.showOptionsMenu) def addProfile(self, profile): """ Adds the inputed profile to the system. :param profile | <XViewProfile> """ if ( profile in self._profiles ): return self._profiles.append(profile) self._profileCombo.blockSignals(True) self._profileCombo.addItem(profile.name()) self._profileCombo.setCurrentIndex(self._profileCombo.count()-1) self._profileCombo.blockSignals(False) def currentProfile(self): """ Returns the currently selected profile from the system. :return <XViewProfile> """ index = self._profileCombo.currentIndex() if 0 <= index and index < len(self._profiles): return self._profiles[index] return None def handleProfileChange(self): """ Emits that the current profile has changed. """ # restore the profile settings prof = self.currentProfile() vwidget = self.viewWidget() if vwidget: prof.restore(vwidget) if not self.signalsBlocked(): self.currentProfileChanged.emit(self.currentProfile()) def optionsMenuPolicy(self): """ Returns the option menu policy for this widget. :return <Qt.MenuPolicy> """ return self._optionsMenuPolicy def profiles(self): """ Returns a list of all the profiles for this system. :return [<XViewProfile>, ..] """ return self._profiles def removeProfile(self, profile): """ Adds the inputed profile to the system. :param profile | <XViewProfile> """ if not profile in self._profiles: return index = self._profiles.index(profile) self._profiles.remove(profile) self._profileCombo.blockSignals(True) self._profileCombo.takeItem(index) self._profileCombo.blockSignals(False) def restoreSettings(self, settings): """ Restores settings from the application. :param settings | <QSettings> """ settings.beginGroup(self.objectName()) curr_prof = None curr_name = unwrapVariant(settings.value('current')) profiles = [] for prof_name in settings.childGroups(): settings.beginGroup(prof_name) prof_str = unwrapVariant(settings.value('profile')) profile = XViewProfile.fromString(prof_str) profile.setName(prof_name) if prof_name == curr_name: curr_prof = profile profiles.append(profile) settings.endGroup() self.blockSignals(True) self._profileCombo.blockSignals(True) self.setProfiles(profiles) if curr_prof: self.setCurrentProfile(curr_prof) self._profileCombo.blockSignals(False) self.blockSignals(False) settings.endGroup() def saveSettings(self, settings): """ Saves the settings for this widget to the application :param settings | <QSettings> """ settings.beginGroup(self.objectName()) curr_prof = self.currentProfile() if curr_prof: settings.setValue('current', curr_prof.name()) for profile in self.profiles(): settings.beginGroup(profile.name()) settings.setValue('profile', wrapVariant(profile.toString())) settings.endGroup() settings.endGroup() def setCurrentProfile(self, profile): """ Sets the current profile to the inputed profile. :param profile | <XViewProfile> """ try: index = self._profiles.index(profile) except ValueError: index = -1 self._profileCombo.setCurrentIndex(index) def setOptionsMenuPolicy(self, menuPolicy): """ Sets the options menu policy for this item. :param menuPolicy | <Qt.MenuPolicy> """ self._optionsMenuPolicy = menuPolicy def setProfiles(self, profiles): """ Sets a list of profiles to be the options for the manager. :param profiles | [<XViewProfile>, ..] """ self.blockSignals(True) self._profileCombo.blockSignals(True) self._profiles = profiles[:] self._profileCombo.clear() self._profileCombo.addItems(map(lambda x: x.name(), profiles)) self._profileCombo.blockSignals(False) self.blockSignals(False) def setViewWidget(self, viewWidget): """ Sets the view widget instance linked with this manager. :param viewWidget | <XViewWidget> """ self._viewWidget = viewWidget def showOptionsMenu(self): """ Displays the options menu. If the option menu policy is set to CustomContextMenu, then the optionMenuRequested signal will be emitted, otherwise the default context menu will be displayed. """ point = QPoint(0, self._optionsButton.height()) global_point = self._optionsButton.mapToGlobal(point) # prompt the custom context menu if self.optionsMenuPolicy() == Qt.CustomContextMenu: if not self.signalsBlocked(): self.optionsMenuRequested.emit(global_point) return # use the default context menu menu = XViewProfileManagerMenu(self) menu.exec_(global_point) def viewWidget(self): """ Returns the view widget that is associated with this manager. :return <XViewWidget> """ return self._viewWidget
class XUrlWidget(QWidget): urlChanged = Signal(str) urlEdited = Signal() def __init__(self, parent): super(XUrlWidget, self).__init__(parent) # define the interface self._urlEdit = XLineEdit(self) self._urlButton = QToolButton(self) self._urlButton.setAutoRaise(True) self._urlButton.setIcon(QIcon(resources.find('img/web.png'))) self._urlButton.setToolTip('Browse Link') self._urlButton.setFocusPolicy(Qt.NoFocus) self._urlEdit.setHint('http://') layout = QHBoxLayout() layout.setContentsMargins(0, 0, 0, 0) layout.setSpacing(0) layout.addWidget(self._urlEdit) layout.addWidget(self._urlButton) self.setLayout(layout) self.setFocusPolicy(Qt.StrongFocus) # create connections self._urlEdit.textChanged.connect(self.urlChanged) self._urlEdit.textEdited.connect(self.urlEdited) self._urlButton.clicked.connect(self.browse) def blockSignals(self, state): """ Blocks the signals for this widget and its sub-parts. :param state | <bool> """ super(XUrlWidget, self).blockSignals(state) self._urlEdit.blockSignals(state) self._urlButton.blockSignals(state) def browse(self): """ Brings up a web browser with the address in a Google map. """ webbrowser.open(self.url()) def hint(self): """ Returns the hint associated with this widget. :return <str> """ return self._urlEdit.hint() def lineEdit(self): """ Returns the line edit linked with this widget. :return <XLineEdit> """ return self._urlEdit def setFocus(self): """ Sets the focus for this widget on its line edit. """ self._urlEdit.setFocus() @Slot(str) def setHint(self, hint): """ Sets the hint associated with this widget. :param hint | <str> """ self._urlEdit.setHint(hint) @Slot(str) def setUrl(self, url): """ Sets the url for this widget to the inputed url. :param url | <str> """ self._urlEdit.setText(nativestring(url)) def url(self): """ Returns the current url from the edit. :return <str> """ return nativestring(self._urlEdit.text()) x_hint = Property(str, hint, setHint) x_url = Property(str, url, setUrl)
class XPagesWidget(QWidget): """ """ currentPageChanged = Signal(int) pageSizeChanged = Signal(int) pageCountChanged = Signal(int) def __init__(self, parent=None): super(XPagesWidget, self).__init__(parent) # define custom properties self._currentPage = 1 self._pageCount = 10 self._itemCount = 0 self._pageSize = 50 self._itemsTitle = 'items' self._pagesSpinner = QSpinBox() self._pagesSpinner.setMinimum(1) self._pagesSpinner.setMaximum(10) self._pageSizeCombo = XComboBox(self) self._pageSizeCombo.setHint('all') self._pageSizeCombo.addItems(['', '25', '50', '75', '100']) self._pageSizeCombo.setCurrentIndex(2) self._nextButton = QToolButton(self) self._nextButton.setAutoRaise(True) self._nextButton.setArrowType(Qt.RightArrow) self._nextButton.setFixedWidth(16) self._prevButton = QToolButton(self) self._prevButton.setAutoRaise(True) self._prevButton.setArrowType(Qt.LeftArrow) self._prevButton.setFixedWidth(16) self._prevButton.setEnabled(False) self._pagesLabel = QLabel('of 10 for ', self) self._itemsLabel = QLabel(' items per page', self) # define the interface layout = QHBoxLayout() layout.addWidget(QLabel('Page', self)) layout.addWidget(self._prevButton) layout.addWidget(self._pagesSpinner) layout.addWidget(self._nextButton) layout.addWidget(self._pagesLabel) layout.addWidget(self._pageSizeCombo) layout.addWidget(self._itemsLabel) layout.addStretch(1) layout.setContentsMargins(0, 0, 0, 0) layout.setSpacing(0) self.setLayout(layout) self.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Preferred) # create connections self._pageSizeCombo.currentIndexChanged.connect(self.pageSizePicked) self._nextButton.clicked.connect(self.gotoNext) self._prevButton.clicked.connect(self.gotoPrevious) self._pagesSpinner.editingFinished.connect(self.assignCurrentPage) def assignCurrentPage(self): """ Assigns the page for the spinner to be current. """ self.setCurrentPage(self._pagesSpinner.value()) def currentPage(self): """ Reutrns the current page for this widget. :return <int> """ return self._currentPage @Slot() def gotoFirst(self): """ Goes to the first page. :sa setCurrentPage """ self.setCurrentPage(1) @Slot() def gotoLast(self): """ Goes to the last page. :sa setCurrentPage """ self.setCurrentPage(self.pageCount()) @Slot() def gotoNext(self): """ Goes to the next page. :sa setCurrentPage """ next_page = self.currentPage() + 1 if (next_page > self.pageCount()): return self.setCurrentPage(next_page) @Slot() def gotoPrevious(self): """ Goes to the previous page. :sa setCurrentPage """ prev_page = self.currentPage() - 1 if (prev_page == 0): return self.setCurrentPage(prev_page) def itemCount(self): """ Returns the total number of items this widget holds. If no item count is defined, it will not be displayed in the label, otherwise it will show. :return <int> """ return self._itemCount def itemsTitle(self): """ Returns the items title for this instance. :return <str> """ return self._itemsTitle def pageCount(self): """ Returns the number of pages that this widget holds. :return <int> """ return self._pageCount def pageSize(self): """ Returns the number of items that should be visible in a page. :return <int> """ return self._pageSize def pageSizeOptions(self): """ Returns the list of options that will be displayed for this default size options. :return [<str>, ..] """ return map(str, self._pageSizeCombo.items()) def pageSizePicked(self, pageSize): """ Updates when the user picks a page size. :param pageSize | <str> """ try: pageSize = int(self._pageSizeCombo.currentText()) except ValueError: pageSize = 0 self.setPageSize(pageSize) self.pageSizeChanged.emit(pageSize) def refreshLabels(self): """ Refreshes the labels to display the proper title and count information. """ itemCount = self.itemCount() title = self.itemsTitle() if (not itemCount): self._itemsLabel.setText(' %s per page' % title) else: msg = ' %s per page, %i %s total' % (title, itemCount, title) self._itemsLabel.setText(msg) @Slot(int) def setCurrentPage(self, pageno): """ Sets the current page for this widget to the inputed page. :param pageno | <int> """ if (pageno == self._currentPage): return if (pageno <= 0): pageno = 1 self._currentPage = pageno self._prevButton.setEnabled(pageno > 1) self._nextButton.setEnabled(pageno < self.pageCount()) self._pagesSpinner.blockSignals(True) self._pagesSpinner.setValue(pageno) self._pagesSpinner.blockSignals(False) if (not self.signalsBlocked()): self.currentPageChanged.emit(pageno) @Slot(int) def setItemCount(self, itemCount): """ Sets the item count for this page to the inputed value. :param itemCount | <int> """ self._itemCount = itemCount self.refreshLabels() @Slot(str) def setItemsTitle(self, title): """ Sets the title that will be displayed when the items labels are rendered :param title | <str> """ self._itemsTitle = nativestring(title) self.refreshLabels() @Slot(int) def setPageCount(self, pageCount): """ Sets the number of pages that this widget holds. :param pageCount | <int> """ if (pageCount == self._pageCount): return pageCount = max(1, pageCount) self._pageCount = pageCount self._pagesSpinner.setMaximum(pageCount) self._pagesLabel.setText('of %i for ' % pageCount) if (pageCount and self.currentPage() <= 0): self.setCurrentPage(1) elif (pageCount < self.currentPage()): self.setCurrentPage(pageCount) if (not self.signalsBlocked()): self.pageCountChanged.emit(pageCount) self._prevButton.setEnabled(self.currentPage() > 1) self._nextButton.setEnabled(self.currentPage() < pageCount) @Slot(int) def setPageSize(self, pageSize): """ Sets the number of items that should be visible in a page. Setting the value to 0 will use all sizes :return <int> """ if self._pageSize == pageSize: return self._pageSize = pageSize # update the display size ssize = nativestring(pageSize) if (ssize == '0'): ssize = '' self._pageSizeCombo.blockSignals(True) index = self._pageSizeCombo.findText(ssize) self._pageSizeCombo.setCurrentIndex(index) self._pageSizeCombo.blockSignals(False) def setPageSizeOptions(self, options): """ Sets the options that will be displayed for this default size. :param options | [<str>,. ..] """ self._pageSizeCombo.blockSignals(True) self._pageSizeCombo.addItems(options) ssize = nativestring(self.pageSize()) if (ssize == '0'): ssize = '' index = self._pageSizeCombo.findText() self._pageSizeCombo.setCurrentIndex(index) self._pageSizeCombo.blockSignals(False) x_itemsTitle = Property(str, itemsTitle, setItemsTitle) x_pageCount = Property(int, pageCount, setPageCount) x_pageSize = Property(int, pageSize, setPageSize) x_pageSizeOptions = Property(list, pageSizeOptions, setPageSizeOptions)
def rebuild(self): """ Rebuilds the parts widget with the latest text. """ navitem = self.currentItem() if (navitem): navitem.initialize() self.setUpdatesEnabled(False) self.scrollWidget().show() self._originalText = '' partsw = self.partsWidget() for button in self._buttonGroup.buttons(): self._buttonGroup.removeButton(button) button.close() button.setParent(None) button.deleteLater() # create the root button layout = partsw.layout() parts = self.parts() button = QToolButton(partsw) button.setAutoRaise(True) button.setMaximumWidth(12) button.setArrowType(Qt.RightArrow) button.setProperty('path', wrapVariant('')) button.setProperty('is_completer', wrapVariant(True)) last_button = button self._buttonGroup.addButton(button) layout.insertWidget(0, button) # check to see if we have a navigation model setup if (self._navigationModel): last_item = self._navigationModel.itemByPath(self.text()) show_last = last_item and last_item.rowCount() > 0 else: show_last = False # load the navigation system count = len(parts) for i, part in enumerate(parts): path = self.separator().join(parts[:i + 1]) button = QToolButton(partsw) button.setAutoRaise(True) button.setText(part) if (self._navigationModel): item = self._navigationModel.itemByPath(path) if (item): button.setIcon(item.icon()) button.setToolButtonStyle(Qt.ToolButtonTextBesideIcon) button.setProperty('path', wrapVariant(path)) button.setProperty('is_completer', wrapVariant(False)) self._buttonGroup.addButton(button) layout.insertWidget((i * 2) + 1, button) # determine if we should show the final button if (show_last or i < (count - 1)): button = QToolButton(partsw) button.setAutoRaise(True) button.setMaximumWidth(12) button.setArrowType(Qt.RightArrow) button.setProperty('path', wrapVariant(path)) button.setProperty('is_completer', wrapVariant(True)) self._buttonGroup.addButton(button) layout.insertWidget((i * 2) + 2, button) last_button = button if (self.scrollWidget().width() < partsw.width()): self.scrollParts(partsw.width() - self.scrollWidget().width()) self.setUpdatesEnabled(True) self.navigationChanged.emit()
class XViewProfileManager(QWidget): currentProfileChanged = Signal(PyObject) optionsMenuRequested = Signal(QPoint) def __init__(self, parent=None): super(XViewProfileManager, self).__init__(parent) # define custom properties self._profiles = [] self._optionsMenuPolicy = Qt.DefaultContextMenu self._viewWidget = None # define the interface self._profileCombo = QComboBox(self) self._optionsButton = QToolButton(self) self._optionsButton.setAutoRaise(True) self._optionsButton.setToolTip('Advanced Options') self._optionsButton.setIcon(QIcon(resources.find('img/advanced.png'))) layout = QHBoxLayout() layout.setSpacing(0) layout.setContentsMargins(0, 0, 0, 0) layout.addWidget(self._profileCombo) layout.addWidget(self._optionsButton) self.setLayout(layout) # create connections self._profileCombo.currentIndexChanged.connect( self.handleProfileChange) self._optionsButton.clicked.connect(self.showOptionsMenu) def addProfile(self, profile): """ Adds the inputed profile to the system. :param profile | <XViewProfile> """ if (profile in self._profiles): return self._profiles.append(profile) self._profileCombo.blockSignals(True) self._profileCombo.addItem(profile.name()) self._profileCombo.setCurrentIndex(self._profileCombo.count() - 1) self._profileCombo.blockSignals(False) def currentProfile(self): """ Returns the currently selected profile from the system. :return <XViewProfile> """ index = self._profileCombo.currentIndex() if 0 <= index and index < len(self._profiles): return self._profiles[index] return None def handleProfileChange(self): """ Emits that the current profile has changed. """ # restore the profile settings prof = self.currentProfile() vwidget = self.viewWidget() if vwidget: prof.restore(vwidget) if not self.signalsBlocked(): self.currentProfileChanged.emit(self.currentProfile()) def optionsMenuPolicy(self): """ Returns the option menu policy for this widget. :return <Qt.MenuPolicy> """ return self._optionsMenuPolicy def profiles(self): """ Returns a list of all the profiles for this system. :return [<XViewProfile>, ..] """ return self._profiles def removeProfile(self, profile): """ Adds the inputed profile to the system. :param profile | <XViewProfile> """ if not profile in self._profiles: return index = self._profiles.index(profile) self._profiles.remove(profile) self._profileCombo.blockSignals(True) self._profileCombo.takeItem(index) self._profileCombo.blockSignals(False) def restoreSettings(self, settings): """ Restores settings from the application. :param settings | <QSettings> """ settings.beginGroup(self.objectName()) curr_prof = None curr_name = unwrapVariant(settings.value('current')) profiles = [] for prof_name in settings.childGroups(): settings.beginGroup(prof_name) prof_str = unwrapVariant(settings.value('profile')) profile = XViewProfile.fromString(prof_str) profile.setName(prof_name) if prof_name == curr_name: curr_prof = profile profiles.append(profile) settings.endGroup() self.blockSignals(True) self._profileCombo.blockSignals(True) self.setProfiles(profiles) if curr_prof: self.setCurrentProfile(curr_prof) self._profileCombo.blockSignals(False) self.blockSignals(False) settings.endGroup() def saveSettings(self, settings): """ Saves the settings for this widget to the application :param settings | <QSettings> """ settings.beginGroup(self.objectName()) curr_prof = self.currentProfile() if curr_prof: settings.setValue('current', curr_prof.name()) for profile in self.profiles(): settings.beginGroup(profile.name()) settings.setValue('profile', wrapVariant(profile.toString())) settings.endGroup() settings.endGroup() def setCurrentProfile(self, profile): """ Sets the current profile to the inputed profile. :param profile | <XViewProfile> """ try: index = self._profiles.index(profile) except ValueError: index = -1 self._profileCombo.setCurrentIndex(index) def setOptionsMenuPolicy(self, menuPolicy): """ Sets the options menu policy for this item. :param menuPolicy | <Qt.MenuPolicy> """ self._optionsMenuPolicy = menuPolicy def setProfiles(self, profiles): """ Sets a list of profiles to be the options for the manager. :param profiles | [<XViewProfile>, ..] """ self.blockSignals(True) self._profileCombo.blockSignals(True) self._profiles = profiles[:] self._profileCombo.clear() self._profileCombo.addItems(map(lambda x: x.name(), profiles)) self._profileCombo.blockSignals(False) self.blockSignals(False) def setViewWidget(self, viewWidget): """ Sets the view widget instance linked with this manager. :param viewWidget | <XViewWidget> """ self._viewWidget = viewWidget def showOptionsMenu(self): """ Displays the options menu. If the option menu policy is set to CustomContextMenu, then the optionMenuRequested signal will be emitted, otherwise the default context menu will be displayed. """ point = QPoint(0, self._optionsButton.height()) global_point = self._optionsButton.mapToGlobal(point) # prompt the custom context menu if self.optionsMenuPolicy() == Qt.CustomContextMenu: if not self.signalsBlocked(): self.optionsMenuRequested.emit(global_point) return # use the default context menu menu = XViewProfileManagerMenu(self) menu.exec_(global_point) def viewWidget(self): """ Returns the view widget that is associated with this manager. :return <XViewWidget> """ return self._viewWidget
class XLocationWidget(QWidget): locationChanged = Signal(str) locationEdited = Signal() def __init__(self, parent): super(XLocationWidget, self).__init__(parent) # define the interface self._locationEdit = XLineEdit(self) self._locationButton = QToolButton(self) self._urlTemplate = 'http://maps.google.com/maps?%(params)s' self._urlQueryKey = 'q' self._locationButton.setAutoRaise(True) self._locationButton.setIcon(QIcon(resources.find('img/map.png'))) self._locationEdit.setHint('no location set') layout = QHBoxLayout() layout.setContentsMargins(0, 0, 0, 0) layout.setSpacing(0) layout.addWidget(self._locationEdit) layout.addWidget(self._locationButton) self.setLayout(layout) # create connections self._locationEdit.textChanged.connect(self.locationChanged) self._locationEdit.textEdited.connect(self.locationEdited) self._locationButton.clicked.connect(self.browseMaps) def blockSignals(self, state): """ Blocks the signals for this widget and its sub-parts. :param state | <bool> """ super(XLocationWidget, self).blockSignals(state) self._locationEdit.blockSignals(state) self._locationButton.blockSignals(state) def browseMaps(self): """ Brings up a web browser with the address in a Google map. """ url = self.urlTemplate() params = urllib.urlencode({self.urlQueryKey(): self.location()}) url = url % {'params': params} webbrowser.open(url) def hint(self): """ Returns the hint associated with this widget. :return <str> """ return self._locationEdit.hint() def lineEdit(self): """ Returns the line edit linked with this widget. :return <XLineEdit> """ return self._locationEdit def location(self): """ Returns the current location from the edit. :return <str> """ return nativestring(self._locationEdit.text()) @Slot(str) def setHint(self, hint): """ Sets the hint associated with this widget. :param hint | <str> """ self._locationEdit.setHint(hint) @Slot(str) def setLocation(self, location): """ Sets the location for this widget to the inputed location. :param location | <str> """ self._locationEdit.setText(nativestring(location)) def setUrlQueryKey(self, key): """ Sets the key for the URL to the inputed key. :param key | <str> """ self._urlQueryKey = nativestring(key) def setUrlTemplate(self, templ): """ Sets the URL path template that will be used when looking up locations on the web. :param templ | <str> """ self._urlQueryTemplate = nativestring(templ) def urlQueryKey(self): """ Returns the query key that will be used for this location. :return <str> """ return self._urlQueryKey def urlTemplate(self): """ Returns the url template that will be used when mapping this location. :return <str> """ return self._urlTemplate x_hint = Property(str, hint, setHint) x_location = Property(str, location, setLocation) x_urlQueryKey = Property(str, urlQueryKey, setUrlQueryKey) x_urlTemplate = Property(str, urlTemplate, setUrlTemplate)
class XUrlWidget(QWidget): urlChanged = Signal(str) urlEdited = Signal() def __init__( self, parent ): super(XUrlWidget, self).__init__(parent) # define the interface self._urlEdit = XLineEdit(self) self._urlButton = QToolButton(self) self._urlButton.setAutoRaise(True) self._urlButton.setIcon(QIcon(resources.find('img/web.png'))) self._urlButton.setToolTip('Browse Link') self._urlButton.setFocusPolicy(Qt.NoFocus) self._urlEdit.setHint('http://') layout = QHBoxLayout() layout.setContentsMargins(0, 0, 0, 0) layout.setSpacing(0) layout.addWidget(self._urlEdit) layout.addWidget(self._urlButton) self.setLayout(layout) self.setFocusPolicy(Qt.StrongFocus) # create connections self._urlEdit.textChanged.connect(self.urlChanged) self._urlEdit.textEdited.connect(self.urlEdited) self._urlButton.clicked.connect(self.browse) def blockSignals( self, state ): """ Blocks the signals for this widget and its sub-parts. :param state | <bool> """ super(XUrlWidget, self).blockSignals(state) self._urlEdit.blockSignals(state) self._urlButton.blockSignals(state) def browse( self ): """ Brings up a web browser with the address in a Google map. """ webbrowser.open(self.url()) def hint( self ): """ Returns the hint associated with this widget. :return <str> """ return self._urlEdit.hint() def lineEdit( self ): """ Returns the line edit linked with this widget. :return <XLineEdit> """ return self._urlEdit def setFocus(self): """ Sets the focus for this widget on its line edit. """ self._urlEdit.setFocus() @Slot(str) def setHint( self, hint ): """ Sets the hint associated with this widget. :param hint | <str> """ self._urlEdit.setHint(hint) @Slot(str) def setUrl( self, url ): """ Sets the url for this widget to the inputed url. :param url | <str> """ self._urlEdit.setText(nativestring(url)) def url( self ): """ Returns the current url from the edit. :return <str> """ return nativestring(self._urlEdit.text()) x_hint = Property(str, hint, setHint) x_url = Property(str, url, setUrl)
class XSplitterHandle(QSplitterHandle): CollapseDirection = enum('After', 'Before') def __init__(self, orientation, parent): super(XSplitterHandle, self).__init__(orientation, parent) # create a layout for the different buttons self._collapsed = False self._storedSizes = None self._collapseBefore = QToolButton(self) self._resizeGrip = QLabel(self) self._collapseAfter = QToolButton(self) self._collapseBefore.setAutoRaise(True) self._collapseAfter.setAutoRaise(True) self._collapseBefore.setCursor(Qt.ArrowCursor) self._collapseAfter.setCursor(Qt.ArrowCursor) # define the layout layout = QBoxLayout(QBoxLayout.LeftToRight, self) layout.setContentsMargins(0, 0, 0, 0) layout.addStretch(1) layout.addWidget(self._collapseBefore) layout.addWidget(self._resizeGrip) layout.addWidget(self._collapseAfter) layout.addStretch(1) self.setLayout(layout) # set the orientation to start with self.setOrientation(orientation) # create connections self._collapseAfter.clicked.connect(self.toggleCollapseAfter) self._collapseBefore.clicked.connect(self.toggleCollapseBefore) def collapse(self, direction): """ Collapses this splitter handle before or after other widgets based on \ the inputed CollapseDirection. :param direction | <XSplitterHandle.CollapseDirection> :return <bool> | success """ if (self.isCollapsed()): return False splitter = self.parent() if (not splitter): return False sizes = splitter.sizes() handles = [splitter.handle(i) for i in range(len(sizes))] index = handles.index(self) self.markCollapsed(direction, sizes) # determine the sizes to use based on the direction if (direction == XSplitterHandle.CollapseDirection.Before): sizes = [0 for i in range(i)] + sizes[i + 1:] else: sizes = sizes[:i] + [0 for i in range(i, len(sizes))] splitter.setSizes(sizes) return True def collapseAfter(self, handle): """ Collapses the splitter after the inputed handle. :param handle | <XSplitterHandle> """ self.setUpdatesEnabled(False) # collapse all items after the current handle if (handle.isCollapsed()): self.setSizes(handle.restoreSizes()) found = False sizes = self.sizes() handle.storeSizes(sizes) for c in range(self.count()): if (self.handle(c) == handle): found = True if (found): sizes[c] = 0 self.setSizes(sizes) self.update() self.setUpdatesEnabled(True) def collapseBefore(self, handle): """ Collapses the splitter before the inputed handle. :param handle | <XSplitterHandle> """ self.setUpdatesEnabled(False) # collapse all items after the current handle if (handle.isCollapsed()): self.setSizes(handle.restoreSizes()) # collapse all items before the current handle found = False sizes = self.sizes() handle.storeSizes(sizes) for c in range(self.count()): if (self.handle(c) == handle): break sizes[c] = 0 self.setSizes(sizes) self.setUpdatesEnabled(True) def isCollapsed(self): """ Returns whether or not this widget is collapsed. :return <bool> """ return self._collapsed def markCollapsed(self, direction, sizes): """ Updates the interface to reflect that the splitter is collapsed. :param direction | <XSplitterHandle.CollapseDirection> sizes | [<int>, ..] """ self._collapsed = True self._storedSizes = sizes[:] if (direction == XSplitterHandle.CollapseDirection.Before): if (self.orientation() == Qt.Horizontal): self._collapseAfter.setArrowType(Qt.RightArrow) self._collapseBefore.setArrowType(Qt.RightArrow) else: self._collapseAfter.setArrowType(Qt.DownArrow) self._collapseBefore.setArrowType(Qt.DownArrow) else: if (self.orientation() == Qt.Horizontal): self._collapseAfter.setArrowType(Qt.LeftArrow) self._collapseBefore.setArrowType(Qt.LeftArrow) else: self._collapseAfter.setArrowType(Qt.UpArrow) self._collapseAfter.setArrowType(Qt.UpArrow) def paintEvent(self, event): """ Overloads the paint event to handle drawing the splitter lines. :param event | <QPaintEvent> """ lines = [] count = 20 # calculate the lines if (self.orientation() == Qt.Vertical): x = self._resizeGrip.pos().x() h = self.height() spacing = int(float(self._resizeGrip.width()) / count) for i in range(count): lines.append(QLine(x, 0, x, h)) x += spacing else: y = self._resizeGrip.pos().y() w = self.width() spacing = int(float(self._resizeGrip.height()) / count) for i in range(count): lines.append(QLine(0, y, w, y)) y += spacing # draw the lines with XPainter(self) as painter: pal = self.palette() painter.setPen(pal.color(pal.Window).darker(120)) painter.drawLines(lines) def setOrientation(self, orientation): """ Sets the orientation for this handle and updates the widgets linked \ with it. :param orientation | <Qt.Orientation> """ super(XSplitterHandle, self).setOrientation(orientation) if (orientation == Qt.Vertical): self.layout().setDirection(QBoxLayout.LeftToRight) # update the widgets self._collapseBefore.setFixedSize(30, 10) self._collapseAfter.setFixedSize(30, 10) self._resizeGrip.setFixedSize(60, 10) self._collapseBefore.setArrowType(Qt.UpArrow) self._collapseAfter.setArrowType(Qt.DownArrow) elif (orientation == Qt.Horizontal): self.layout().setDirection(QBoxLayout.TopToBottom) # update the widgets self._collapseBefore.setFixedSize(10, 30) self._collapseAfter.setFixedSize(10, 30) self._resizeGrip.setFixedSize(10, 60) self._collapseBefore.setArrowType(Qt.LeftArrow) self._collapseAfter.setArrowType(Qt.RightArrow) def uncollapse(self): """ Uncollapses the splitter this handle is associated with by restoring \ its sizes from before the collapse occurred. :return <bool> | changed """ if (not self.isCollapsed()): return False self.parent().setSizes(self._storedSizes) self.unmarkCollapsed() return True def unmarkCollapsed(self): """ Unmarks this splitter as being in a collapsed state, clearing any \ collapsed information. """ if (not self.isCollapsed()): return self._collapsed = False self._storedSizes = None if (self.orientation() == Qt.Vertical): self._collapseBefore.setArrowType(Qt.UpArrow) self._collapseAfter.setArrowType(Qt.DownArrow) else: self._collapseBefore.setArrowType(Qt.LeftArrow) self._collapseAfter.setArrowType(Qt.RightArrow) def toggleCollapseAfter(self): """ Collapses the splitter after this handle. """ if (self.isCollapsed()): self.uncollapse() else: self.collapse(XSplitterHandle.CollapseDirection.After) def toggleCollapseBefore(self): """ Collapses the splitter before this handle. """ if (self.isCollapsed()): self.uncollapse() else: self.collapse(XSplitterHandle.CollapseDirection.Before)
class XPagesWidget(QWidget): """ """ currentPageChanged = Signal(int) pageSizeChanged = Signal(int) pageCountChanged = Signal(int) def __init__( self, parent = None ): super(XPagesWidget, self).__init__( parent ) # define custom properties self._currentPage = 1 self._pageCount = 10 self._itemCount = 0 self._pageSize = 50 self._itemsTitle = 'items' self._pagesSpinner = QSpinBox() self._pagesSpinner.setMinimum(1) self._pagesSpinner.setMaximum(10) self._pageSizeCombo = XComboBox(self) self._pageSizeCombo.setHint('all') self._pageSizeCombo.addItems(['', '25', '50', '75', '100']) self._pageSizeCombo.setCurrentIndex(2) self._nextButton = QToolButton(self) self._nextButton.setAutoRaise(True) self._nextButton.setArrowType(Qt.RightArrow) self._nextButton.setFixedWidth(16) self._prevButton = QToolButton(self) self._prevButton.setAutoRaise(True) self._prevButton.setArrowType(Qt.LeftArrow) self._prevButton.setFixedWidth(16) self._prevButton.setEnabled(False) self._pagesLabel = QLabel('of 10 for ', self) self._itemsLabel = QLabel(' items per page', self) # define the interface layout = QHBoxLayout() layout.addWidget(QLabel('Page', self)) layout.addWidget(self._prevButton) layout.addWidget(self._pagesSpinner) layout.addWidget(self._nextButton) layout.addWidget(self._pagesLabel) layout.addWidget(self._pageSizeCombo) layout.addWidget(self._itemsLabel) layout.addStretch(1) layout.setContentsMargins(0, 0, 0, 0) layout.setSpacing(0) self.setLayout(layout) self.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Preferred) # create connections self._pageSizeCombo.currentIndexChanged.connect(self.pageSizePicked) self._nextButton.clicked.connect(self.gotoNext) self._prevButton.clicked.connect(self.gotoPrevious) self._pagesSpinner.editingFinished.connect(self.assignCurrentPage) def assignCurrentPage( self ): """ Assigns the page for the spinner to be current. """ self.setCurrentPage(self._pagesSpinner.value()) def currentPage( self ): """ Reutrns the current page for this widget. :return <int> """ return self._currentPage @Slot() def gotoFirst( self ): """ Goes to the first page. :sa setCurrentPage """ self.setCurrentPage(1) @Slot() def gotoLast( self ): """ Goes to the last page. :sa setCurrentPage """ self.setCurrentPage(self.pageCount()) @Slot() def gotoNext( self ): """ Goes to the next page. :sa setCurrentPage """ next_page = self.currentPage() + 1 if ( next_page > self.pageCount() ): return self.setCurrentPage(next_page) @Slot() def gotoPrevious( self ): """ Goes to the previous page. :sa setCurrentPage """ prev_page = self.currentPage() - 1 if ( prev_page == 0 ): return self.setCurrentPage(prev_page) def itemCount( self ): """ Returns the total number of items this widget holds. If no item count is defined, it will not be displayed in the label, otherwise it will show. :return <int> """ return self._itemCount def itemsTitle( self ): """ Returns the items title for this instance. :return <str> """ return self._itemsTitle def pageCount( self ): """ Returns the number of pages that this widget holds. :return <int> """ return self._pageCount def pageSize( self ): """ Returns the number of items that should be visible in a page. :return <int> """ return self._pageSize def pageSizeOptions( self ): """ Returns the list of options that will be displayed for this default size options. :return [<str>, ..] """ return map(str, self._pageSizeCombo.items()) def pageSizePicked( self, pageSize ): """ Updates when the user picks a page size. :param pageSize | <str> """ try: pageSize = int(self._pageSizeCombo.currentText()) except ValueError: pageSize = 0 self.setPageSize(pageSize) self.pageSizeChanged.emit(pageSize) def refreshLabels( self ): """ Refreshes the labels to display the proper title and count information. """ itemCount = self.itemCount() title = self.itemsTitle() if ( not itemCount ): self._itemsLabel.setText(' %s per page' % title) else: msg = ' %s per page, %i %s total' % (title, itemCount, title) self._itemsLabel.setText(msg) @Slot(int) def setCurrentPage( self, pageno ): """ Sets the current page for this widget to the inputed page. :param pageno | <int> """ if ( pageno == self._currentPage ): return if ( pageno <= 0 ): pageno = 1 self._currentPage = pageno self._prevButton.setEnabled(pageno > 1) self._nextButton.setEnabled(pageno < self.pageCount()) self._pagesSpinner.blockSignals(True) self._pagesSpinner.setValue(pageno) self._pagesSpinner.blockSignals(False) if ( not self.signalsBlocked() ): self.currentPageChanged.emit(pageno) @Slot(int) def setItemCount( self, itemCount ): """ Sets the item count for this page to the inputed value. :param itemCount | <int> """ self._itemCount = itemCount self.refreshLabels() @Slot(str) def setItemsTitle( self, title ): """ Sets the title that will be displayed when the items labels are rendered :param title | <str> """ self._itemsTitle = nativestring(title) self.refreshLabels() @Slot(int) def setPageCount( self, pageCount ): """ Sets the number of pages that this widget holds. :param pageCount | <int> """ if ( pageCount == self._pageCount ): return pageCount = max(1, pageCount) self._pageCount = pageCount self._pagesSpinner.setMaximum(pageCount) self._pagesLabel.setText('of %i for ' % pageCount) if ( pageCount and self.currentPage() <= 0 ): self.setCurrentPage(1) elif ( pageCount < self.currentPage() ): self.setCurrentPage(pageCount) if ( not self.signalsBlocked() ): self.pageCountChanged.emit(pageCount) self._prevButton.setEnabled(self.currentPage() > 1) self._nextButton.setEnabled(self.currentPage() < pageCount) @Slot(int) def setPageSize( self, pageSize ): """ Sets the number of items that should be visible in a page. Setting the value to 0 will use all sizes :return <int> """ if self._pageSize == pageSize: return self._pageSize = pageSize # update the display size ssize = nativestring(pageSize) if ( ssize == '0' ): ssize = '' self._pageSizeCombo.blockSignals(True) index = self._pageSizeCombo.findText(ssize) self._pageSizeCombo.setCurrentIndex(index) self._pageSizeCombo.blockSignals(False) def setPageSizeOptions( self, options ): """ Sets the options that will be displayed for this default size. :param options | [<str>,. ..] """ self._pageSizeCombo.blockSignals(True) self._pageSizeCombo.addItems(options) ssize = nativestring(self.pageSize()) if ( ssize == '0' ): ssize = '' index = self._pageSizeCombo.findText() self._pageSizeCombo.setCurrentIndex(index) self._pageSizeCombo.blockSignals(False) x_itemsTitle = Property(str, itemsTitle, setItemsTitle) x_pageCount = Property(int, pageCount, setPageCount) x_pageSize = Property(int, pageSize, setPageSize) x_pageSizeOptions = Property(list, pageSizeOptions, setPageSizeOptions)
def rebuild( self ): """ Rebuilds the parts widget with the latest text. """ navitem = self.currentItem() if ( navitem ): navitem.initialize() self.setUpdatesEnabled(False) self.scrollWidget().show() self._originalText = '' partsw = self.partsWidget() for button in self._buttonGroup.buttons(): self._buttonGroup.removeButton(button) button.close() button.setParent(None) button.deleteLater() # create the root button layout = partsw.layout() parts = self.parts() button = QToolButton(partsw) button.setAutoRaise(True) button.setMaximumWidth(12) button.setArrowType(Qt.RightArrow) button.setProperty('path', wrapVariant('')) button.setProperty('is_completer', wrapVariant(True)) last_button = button self._buttonGroup.addButton(button) layout.insertWidget(0, button) # check to see if we have a navigation model setup if ( self._navigationModel ): last_item = self._navigationModel.itemByPath(self.text()) show_last = last_item and last_item.rowCount() > 0 else: show_last = False # load the navigation system count = len(parts) for i, part in enumerate(parts): path = self.separator().join(parts[:i+1]) button = QToolButton(partsw) button.setAutoRaise(True) button.setText(part) if ( self._navigationModel ): item = self._navigationModel.itemByPath(path) if ( item ): button.setIcon(item.icon()) button.setToolButtonStyle(Qt.ToolButtonTextBesideIcon) button.setProperty('path', wrapVariant(path)) button.setProperty('is_completer', wrapVariant(False)) self._buttonGroup.addButton(button) layout.insertWidget((i * 2) + 1, button) # determine if we should show the final button if ( show_last or i < (count - 1) ): button = QToolButton(partsw) button.setAutoRaise(True) button.setMaximumWidth(12) button.setArrowType(Qt.RightArrow) button.setProperty('path', wrapVariant(path)) button.setProperty('is_completer', wrapVariant(True)) self._buttonGroup.addButton(button) layout.insertWidget((i * 2) + 2, button) last_button = button if ( self.scrollWidget().width() < partsw.width() ): self.scrollParts(partsw.width() - self.scrollWidget().width()) self.setUpdatesEnabled(True) self.navigationChanged.emit()
class XToolBar(QToolBar): collapseToggled = Signal(bool) def __init__(self, *args): super(XToolBar, self).__init__(*args) # set custom properties self._collapseButton = None self._collapsable = True self._collapsed = True self._collapsedSize = 14 self._autoCollapsible = False self._precollapseSize = None self._shadowed = False self._colored = False # set standard options self.layout().setSpacing(0) self.layout().setContentsMargins(1, 1, 1, 1) self.setMovable(False) self.clear() self.setMouseTracking(True) self.setOrientation(Qt.Horizontal) self.setCollapsed(False) def autoCollapsible(self): """ Returns whether or not this toolbar is auto-collapsible. When True, it will enter its collapsed state when the user hovers out of the bar. :return <bool> """ return self._autoCollapsible def clear(self): """ Clears out this toolbar from the system. """ # preserve the collapse button super(XToolBar, self).clear() # clears out the toolbar if self.isCollapsable(): self._collapseButton = QToolButton(self) self._collapseButton.setAutoRaise(True) self._collapseButton.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) self.addWidget(self._collapseButton) self.refreshButton() # create connection self._collapseButton.clicked.connect(self.toggleCollapsed) elif self._collapseButton: self._collapseButton.setParent(None) self._collapseButton.deleteLater() self._collapseButton = None def count(self): """ Returns the number of actions linked with this toolbar. :return <int> """ return len(self.actions()) def collapseButton(self): """ Returns the collapsing button for this toolbar. :return <QToolButton> """ return self._collapseButton def isCollapsable(self): """ Returns whether or not this toolbar is collapsable. :return <bool> """ return self._collapsable def isCollapsed( self ): """ Returns whether or not this toolbar is in a collapsed state. :return <bool> """ return self._collapsed and self.isCollapsable() def isColored(self): """ Returns whether or not to colorize the buttons on the toolbar when they are highlighted. :return <bool> """ return self._colored def isShadowed(self): """ Returns whether or not to show this toolbar with shadows. :return <bool> """ return self._shadowed def refreshButton(self): """ Refreshes the button for this toolbar. """ collapsed = self.isCollapsed() btn = self._collapseButton if not btn: return btn.setMaximumSize(MAX_SIZE, MAX_SIZE) # set up a vertical scrollbar if self.orientation() == Qt.Vertical: btn.setMaximumHeight(12) else: btn.setMaximumWidth(12) icon = '' # collapse/expand a vertical toolbar if self.orientation() == Qt.Vertical: if collapsed: self.setFixedWidth(self._collapsedSize) btn.setMaximumHeight(MAX_SIZE) btn.setArrowType(Qt.RightArrow) else: self.setMaximumWidth(MAX_SIZE) self._precollapseSize = None btn.setMaximumHeight(12) btn.setArrowType(Qt.LeftArrow) else: if collapsed: self.setFixedHeight(self._collapsedSize) btn.setMaximumWidth(MAX_SIZE) btn.setArrowType(Qt.DownArrow) else: self.setMaximumHeight(1000) self._precollapseSize = None btn.setMaximumWidth(12) btn.setArrowType(Qt.UpArrow) for index in range(1, self.layout().count()): item = self.layout().itemAt(index) if not item.widget(): continue if collapsed: item.widget().setMaximumSize(0, 0) else: item.widget().setMaximumSize(MAX_SIZE, MAX_SIZE) if not self.isCollapsable(): btn.hide() else: btn.show() def resizeEvent(self, event): super(XToolBar, self).resizeEvent(event) if not self._collapsed: if self.orientation() == Qt.Vertical: self._precollapseSize = self.width() else: self._precollapseSize = self.height() def setAutoCollapsible(self, state): """ Sets whether or not this toolbar is auto-collapsible. :param state | <bool> """ self._autoCollapsible = state def setCollapsed(self, state): """ Sets whether or not this toolbar is in a collapsed state. :return <bool> changed """ if state == self._collapsed: return False self._collapsed = state self.refreshButton() if not self.signalsBlocked(): self.collapseToggled.emit(state) return True def setCollapsable(self, state): """ Sets whether or not this toolbar is collapsable. :param state | <bool> """ if self._collapsable == state: return self._collapsable = state self.clear() def setOrientation( self, orientation ): """ Sets the orientation for this toolbar to the inputed value, and \ updates the contents margins and collapse button based on the vaule. :param orientation | <Qt.Orientation> """ super(XToolBar, self).setOrientation(orientation) self.refreshButton() def setShadowed(self, state): """ Sets whether or not this toolbar is shadowed. :param state | <bool> """ self._shadowed = state if state: self._colored = False for child in self.findChildren(XToolButton): child.setShadowed(state) def setColored(self, state): """ Sets whether or not this toolbar is shadowed. :param state | <bool> """ self._colored = state if state: self._shadowed = False for child in self.findChildren(XToolButton): child.setColored(state) def toggleCollapsed( self ): """ Toggles the collapsed state for this toolbar. :return <bool> changed """ return self.setCollapsed(not self.isCollapsed()) x_shadowed = Property(bool, isShadowed, setShadowed) x_colored = Property(bool, isColored, setColored)
class XZoomSlider(QWidget): zoomAmountChanged = Signal(int) def __init__(self, parent=None): super(XZoomSlider, self).__init__(parent) # define the interface in_icon = projexui.resources.find('img/zoom_in.png') out_icon = projexui.resources.find('img/zoom_out.png') self._zoomInButton = QToolButton(self) self._zoomInButton.setAutoRaise(True) self._zoomInButton.setToolTip('Zoom In') self._zoomInButton.setIcon(QIcon(in_icon)) self._zoomOutButton = QToolButton(self) self._zoomOutButton.setAutoRaise(True) self._zoomOutButton.setToolTip('Zoom Out') self._zoomOutButton.setIcon(QIcon(out_icon)) self._zoomSlider = QSlider(Qt.Horizontal, self) self._zoomSlider.setRange(10, 100) self._zoomSlider.setValue(100) # define the layout layout = QHBoxLayout() layout.setContentsMargins(0, 0, 0, 0) layout.setSpacing(0) layout.addWidget(self._zoomOutButton) layout.addWidget(self._zoomSlider) layout.addWidget(self._zoomInButton) self.setLayout(layout) # create connections self._zoomSlider.valueChanged.connect(self.emitZoomAmountChanged) self._zoomInButton.clicked.connect(self.zoomIn) self._zoomOutButton.clicked.connect(self.zoomOut) def emitZoomAmountChanged(self): """ Emits the current zoom amount, provided the signals are not being blocked. """ if not self.signalsBlocked(): self.zoomAmountChanged.emit(self.zoomAmount()) def maximum(self): """ Returns the maximum zoom level for this widget. :return <int> """ return self._zoomSlider.maximum() def minimum(self): """ Returns the minimum zoom level for this widget. :return <int> """ return self._zoomSlider.minimum() @Slot(int) def setMaximum(self, maximum): """ Sets the maximum zoom level for this widget. :param maximum | <int> """ self._zoomSlider.setMaximum(maximum) @Slot(int) def setMinimum(self, minimum): """ Sets the minimum zoom level for this widget. :param minimum | <int> """ self._zoomSlider.setMinimum(minimum) def setZoomStep(self, amount): """ Sets how much a single step for the zoom in/out will be. :param amount | <int> """ self._zoomSlider.setPageStep(amount) @Slot(int) def setZoomAmount(self, amount): """ Sets the zoom amount for this widget to the inputed amount. :param amount | <int> """ self._zoomSlider.setValue(amount) def zoomAmount(self): """ Returns the current zoom amount for this widget. :return <int> """ return self._zoomSlider.value() @Slot() def zoomIn(self): """ Zooms in by a single page step. """ self._zoomSlider.triggerAction(QSlider.SliderPageStepAdd) def zoomInButton(self): """ Returns the zoom in button from the left side of this widget. :return <QToolButton> """ return self._zoomInButton @Slot() def zoomOut(self): """ Zooms out by a single page step. """ self._zoomSlider.triggerAction(QSlider.SliderPageStepSub) def zoomOutButton(self): """ Returns the zoom out button from the right side of this widget. :return <QToolButton> """ return self._zoomOutButton def zoomSlider(self): """ Returns the slider widget of this zoom slider. :return <QSlider> """ return self._zoomSlider def zoomStep(self): """ Returns the amount for a single step when the user clicks the zoom in/ out amount. :return <int> """ return self._zoomSlider.pageStep() x_maximum = Property(int, maximum, setMaximum) x_minimum = Property(int, minimum, setMinimum) z_zoomStep = Property(int, zoomStep, setZoomStep)
class XCommentEdit(XTextEdit): attachmentRequested = Signal() def __init__(self, parent=None): super(XCommentEdit, self).__init__(parent) # define custom properties self._attachments = {} self._showAttachments = True # create toolbar self._toolbar = QToolBar(self) self._toolbar.setMovable(False) self._toolbar.setFixedHeight(30) self._toolbar.setAutoFillBackground(True) self._toolbar.setFocusProxy(self) self._toolbar.hide() # create toolbar buttons self._attachButton = QToolButton(self) self._attachButton.setIcon(QIcon(resources.find('img/attach.png'))) self._attachButton.setToolTip('Add Attachment') self._attachButton.setAutoRaise(True) self._attachButton.setIconSize(QSize(24, 24)) self._attachButton.setFixedSize(26, 26) self._submitButton = QPushButton(self) self._submitButton.setText('Submit') self._submitButton.setFocusProxy(self) # create attachments widget self._attachmentsEdit = XMultiTagEdit(self) self._attachmentsEdit.setAutoResizeToContents(True) self._attachmentsEdit.setFrameShape(XMultiTagEdit.NoFrame) self._attachmentsEdit.setViewMode(XMultiTagEdit.ListMode) self._attachmentsEdit.setEditable(False) self._attachmentsEdit.setFocusProxy(self) self._attachmentsEdit.hide() # define toolbar layout spacer = QWidget(self) spacer.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Preferred) self._attachAction = self._toolbar.addWidget(self._attachButton) self._toolbar.addWidget(spacer) self._toolbar.addWidget(self._submitButton) # set standard properties self.setAutoResizeToContents(True) self.setHint('add comment') self.setFocusPolicy(Qt.StrongFocus) self.setRequireShiftForNewLine(True) # create connections self._attachButton.clicked.connect(self.attachmentRequested) self._submitButton.clicked.connect(self.acceptText) self._attachmentsEdit.tagRemoved.connect(self.removeAttachment) self.focusChanged.connect(self.setToolbarVisible) def addAttachment(self, title, attachment): """ Adds an attachment to this comment. :param title | <str> attachment | <variant> """ self._attachments[title] = attachment self.resizeToContents() def attachments(self): """ Returns a list of attachments that have been linked to this widget. :return {<str> title: <variant> attachment, ..} """ return self._attachments.copy() def attachButton(self): """ Returns the attach button from the toolbar for this widget. :return <QToolButton> """ return self._attachButton @Slot() def clear(self): """ Clears out this widget and its attachments. """ # clear the attachment list self._attachments.clear() super(XCommentEdit, self).clear() def isToolbarVisible(self): """ Returns whether or not the toolbar for this comment edit is currently visible to the user. :return <bool> """ return self._toolbar.isVisible() def keyPressEvent(self, event): if event.key() == Qt.Key_Escape: self.clear() event.accept() else: super(XCommentEdit, self).keyPressEvent(event) @Slot() def pickAttachment(self): """ Prompts the user to select an attachment to add to this edit. """ filename = QFileDialog.getOpenFileName(self.window(), 'Select Attachment', '', 'All Files (*.*)') if type(filename) == tuple: filename = nativestring(filename[0]) filename = nativestring(filename) if filename: self.addAttachment(os.path.basename(filename), filename) def removeAttachment(self, title): """ Removes the attachment from the given title. :param title | <str> :return <variant> | attachment """ attachment = self._attachments.pop(nativestring(title), None) if attachment: self.resizeToContents() return attachment def resizeEvent(self, event): super(XCommentEdit, self).resizeEvent(event) self._toolbar.resize(self.width() - 4, 30) edit = self._attachmentsEdit edit.resize(self.width() - 4, edit.height()) def resizeToContents(self): """ Resizes this toolbar based on the contents of its text. """ if self._toolbar.isVisible(): doc = self.document() h = doc.documentLayout().documentSize().height() offset = 34 # update the attachments edit edit = self._attachmentsEdit if self._attachments: edit.move(2, self.height() - edit.height() - 31) edit.setTags(sorted(self._attachments.keys())) edit.show() offset = 34 + edit.height() else: edit.hide() offset = 34 self.setFixedHeight(h + offset) self._toolbar.move(2, self.height() - 32) else: super(XCommentEdit, self).resizeToContents() def setAttachments(self, attachments): """ Sets the attachments for this widget to the inputed list of attachments. :param attachments | {<str> title: <variant> attachment, ..} """ self._attachments = attachments self.resizeToContents() def setSubmitText(self, text): """ Sets the submission text for this edit. :param text | <str> """ self._submitButton.setText(text) def setShowAttachments(self, state): """ Sets whether or not to show the attachments for this edit. :param state | <bool> """ self._showAttachments = state self._attachAction.setVisible(state) def setToolbarVisible(self, state): """ Sets whether or not the toolbar is visible. :param state | <bool> """ self._toolbar.setVisible(state) self.resizeToContents() def showAttachments(self): """ Returns whether or not to show the attachments for this edit. :return <bool> """ return self._showAttachments def submitButton(self): """ Returns the submit button for this edit. :return <QPushButton> """ return self._submitButton def submitText(self): """ Returns the submission text for this edit. :return <str> """ return self._submitButton.text() def toolbar(self): """ Returns the toolbar widget for this comment edit. :return <QToolBar> """ return self._toolbar x_showAttachments = Property(bool, showAttachments, setShowAttachments) x_submitText = Property(str, submitText, setSubmitText)
class XToolBar(QToolBar): collapseToggled = Signal(bool) def __init__(self, *args): super(XToolBar, self).__init__(*args) # set custom properties self._collapseButton = None self._collapsable = True self._collapsed = True self._collapsedSize = 14 self._autoCollapsible = False self._precollapseSize = None self._shadowed = False self._colored = False # set standard options self.layout().setSpacing(0) self.layout().setContentsMargins(1, 1, 1, 1) self.setMovable(False) self.clear() self.setMouseTracking(True) self.setOrientation(Qt.Horizontal) self.setCollapsed(False) def autoCollapsible(self): """ Returns whether or not this toolbar is auto-collapsible. When True, it will enter its collapsed state when the user hovers out of the bar. :return <bool> """ return self._autoCollapsible def clear(self): """ Clears out this toolbar from the system. """ # preserve the collapse button super(XToolBar, self).clear() # clears out the toolbar if self.isCollapsable(): self._collapseButton = QToolButton(self) self._collapseButton.setAutoRaise(True) self._collapseButton.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) self.addWidget(self._collapseButton) self.refreshButton() # create connection self._collapseButton.clicked.connect(self.toggleCollapsed) elif self._collapseButton: self._collapseButton.setParent(None) self._collapseButton.deleteLater() self._collapseButton = None def count(self): """ Returns the number of actions linked with this toolbar. :return <int> """ return len(self.actions()) def collapseButton(self): """ Returns the collapsing button for this toolbar. :return <QToolButton> """ return self._collapseButton def isCollapsable(self): """ Returns whether or not this toolbar is collapsable. :return <bool> """ return self._collapsable def isCollapsed(self): """ Returns whether or not this toolbar is in a collapsed state. :return <bool> """ return self._collapsed and self.isCollapsable() def isColored(self): """ Returns whether or not to colorize the buttons on the toolbar when they are highlighted. :return <bool> """ return self._colored def isShadowed(self): """ Returns whether or not to show this toolbar with shadows. :return <bool> """ return self._shadowed def refreshButton(self): """ Refreshes the button for this toolbar. """ collapsed = self.isCollapsed() btn = self._collapseButton if not btn: return btn.setMaximumSize(MAX_SIZE, MAX_SIZE) # set up a vertical scrollbar if self.orientation() == Qt.Vertical: btn.setMaximumHeight(12) else: btn.setMaximumWidth(12) icon = '' # collapse/expand a vertical toolbar if self.orientation() == Qt.Vertical: if collapsed: self.setFixedWidth(self._collapsedSize) btn.setMaximumHeight(MAX_SIZE) btn.setArrowType(Qt.RightArrow) else: self.setMaximumWidth(MAX_SIZE) self._precollapseSize = None btn.setMaximumHeight(12) btn.setArrowType(Qt.LeftArrow) else: if collapsed: self.setFixedHeight(self._collapsedSize) btn.setMaximumWidth(MAX_SIZE) btn.setArrowType(Qt.DownArrow) else: self.setMaximumHeight(1000) self._precollapseSize = None btn.setMaximumWidth(12) btn.setArrowType(Qt.UpArrow) for index in range(1, self.layout().count()): item = self.layout().itemAt(index) if not item.widget(): continue if collapsed: item.widget().setMaximumSize(0, 0) else: item.widget().setMaximumSize(MAX_SIZE, MAX_SIZE) if not self.isCollapsable(): btn.hide() else: btn.show() def resizeEvent(self, event): super(XToolBar, self).resizeEvent(event) if not self._collapsed: if self.orientation() == Qt.Vertical: self._precollapseSize = self.width() else: self._precollapseSize = self.height() def setAutoCollapsible(self, state): """ Sets whether or not this toolbar is auto-collapsible. :param state | <bool> """ self._autoCollapsible = state def setCollapsed(self, state): """ Sets whether or not this toolbar is in a collapsed state. :return <bool> changed """ if state == self._collapsed: return False self._collapsed = state self.refreshButton() if not self.signalsBlocked(): self.collapseToggled.emit(state) return True def setCollapsable(self, state): """ Sets whether or not this toolbar is collapsable. :param state | <bool> """ if self._collapsable == state: return self._collapsable = state self.clear() def setOrientation(self, orientation): """ Sets the orientation for this toolbar to the inputed value, and \ updates the contents margins and collapse button based on the vaule. :param orientation | <Qt.Orientation> """ super(XToolBar, self).setOrientation(orientation) self.refreshButton() def setShadowed(self, state): """ Sets whether or not this toolbar is shadowed. :param state | <bool> """ self._shadowed = state if state: self._colored = False for child in self.findChildren(XToolButton): child.setShadowed(state) def setColored(self, state): """ Sets whether or not this toolbar is shadowed. :param state | <bool> """ self._colored = state if state: self._shadowed = False for child in self.findChildren(XToolButton): child.setColored(state) def toggleCollapsed(self): """ Toggles the collapsed state for this toolbar. :return <bool> changed """ return self.setCollapsed(not self.isCollapsed()) x_shadowed = Property(bool, isShadowed, setShadowed) x_colored = Property(bool, isColored, setColored)
class XSplitterHandle(QSplitterHandle): CollapseDirection = enum('After', 'Before') def __init__( self, orientation, parent ): super(XSplitterHandle, self).__init__( orientation, parent ) # create a layout for the different buttons self._collapsed = False self._storedSizes = None self._collapseBefore = QToolButton(self) self._resizeGrip = QLabel(self) self._collapseAfter = QToolButton(self) self._collapseBefore.setAutoRaise(True) self._collapseAfter.setAutoRaise(True) self._collapseBefore.setCursor(Qt.ArrowCursor) self._collapseAfter.setCursor(Qt.ArrowCursor) # define the layout layout = QBoxLayout(QBoxLayout.LeftToRight, self) layout.setContentsMargins(0, 0, 0, 0) layout.addStretch(1) layout.addWidget(self._collapseBefore) layout.addWidget(self._resizeGrip) layout.addWidget(self._collapseAfter) layout.addStretch(1) self.setLayout(layout) # set the orientation to start with self.setOrientation(orientation) # create connections self._collapseAfter.clicked.connect( self.toggleCollapseAfter ) self._collapseBefore.clicked.connect( self.toggleCollapseBefore ) def collapse( self, direction ): """ Collapses this splitter handle before or after other widgets based on \ the inputed CollapseDirection. :param direction | <XSplitterHandle.CollapseDirection> :return <bool> | success """ if ( self.isCollapsed() ): return False splitter = self.parent() if ( not splitter ): return False sizes = splitter.sizes() handles = [splitter.handle(i) for i in range(len(sizes))] index = handles.index(self) self.markCollapsed(direction, sizes) # determine the sizes to use based on the direction if ( direction == XSplitterHandle.CollapseDirection.Before ): sizes = [0 for i in range(i)] + sizes[i+1:] else: sizes = sizes[:i] + [0 for i in range(i, len(sizes))] splitter.setSizes(sizes) return True def collapseAfter( self, handle ): """ Collapses the splitter after the inputed handle. :param handle | <XSplitterHandle> """ self.setUpdatesEnabled(False) # collapse all items after the current handle if ( handle.isCollapsed() ): self.setSizes(handle.restoreSizes()) found = False sizes = self.sizes() handle.storeSizes(sizes) for c in range(self.count()): if ( self.handle(c) == handle ): found = True if ( found ): sizes[c] = 0 self.setSizes(sizes) self.update() self.setUpdatesEnabled(True) def collapseBefore( self, handle ): """ Collapses the splitter before the inputed handle. :param handle | <XSplitterHandle> """ self.setUpdatesEnabled(False) # collapse all items after the current handle if ( handle.isCollapsed() ): self.setSizes(handle.restoreSizes()) # collapse all items before the current handle found = False sizes = self.sizes() handle.storeSizes(sizes) for c in range(self.count()): if ( self.handle(c) == handle ): break sizes[c] = 0 self.setSizes(sizes) self.setUpdatesEnabled(True) def isCollapsed( self ): """ Returns whether or not this widget is collapsed. :return <bool> """ return self._collapsed def markCollapsed( self, direction, sizes ): """ Updates the interface to reflect that the splitter is collapsed. :param direction | <XSplitterHandle.CollapseDirection> sizes | [<int>, ..] """ self._collapsed = True self._storedSizes = sizes[:] if ( direction == XSplitterHandle.CollapseDirection.Before ): if ( self.orientation() == Qt.Horizontal ): self._collapseAfter.setArrowType( Qt.RightArrow ) self._collapseBefore.setArrowType( Qt.RightArrow ) else: self._collapseAfter.setArrowType( Qt.DownArrow ) self._collapseBefore.setArrowType( Qt.DownArrow ) else: if ( self.orientation() == Qt.Horizontal ): self._collapseAfter.setArrowType( Qt.LeftArrow ) self._collapseBefore.setArrowType( Qt.LeftArrow ) else: self._collapseAfter.setArrowType( Qt.UpArrow ) self._collapseAfter.setArrowType( Qt.UpArrow ) def paintEvent( self, event ): """ Overloads the paint event to handle drawing the splitter lines. :param event | <QPaintEvent> """ lines = [] count = 20 # calculate the lines if ( self.orientation() == Qt.Vertical ): x = self._resizeGrip.pos().x() h = self.height() spacing = int(float(self._resizeGrip.width()) / count) for i in range(count): lines.append(QLine(x, 0, x, h)) x += spacing else: y = self._resizeGrip.pos().y() w = self.width() spacing = int(float(self._resizeGrip.height()) / count) for i in range(count): lines.append(QLine(0, y, w, y)) y += spacing # draw the lines with XPainter(self) as painter: pal = self.palette() painter.setPen(pal.color(pal.Window).darker(120)) painter.drawLines(lines) def setOrientation( self, orientation ): """ Sets the orientation for this handle and updates the widgets linked \ with it. :param orientation | <Qt.Orientation> """ super(XSplitterHandle, self).setOrientation(orientation) if ( orientation == Qt.Vertical ): self.layout().setDirection( QBoxLayout.LeftToRight ) # update the widgets self._collapseBefore.setFixedSize(30, 10) self._collapseAfter.setFixedSize(30, 10) self._resizeGrip.setFixedSize(60, 10) self._collapseBefore.setArrowType(Qt.UpArrow) self._collapseAfter.setArrowType(Qt.DownArrow) elif ( orientation == Qt.Horizontal ): self.layout().setDirection( QBoxLayout.TopToBottom ) # update the widgets self._collapseBefore.setFixedSize(10, 30) self._collapseAfter.setFixedSize(10, 30) self._resizeGrip.setFixedSize(10, 60) self._collapseBefore.setArrowType(Qt.LeftArrow) self._collapseAfter.setArrowType(Qt.RightArrow) def uncollapse( self ): """ Uncollapses the splitter this handle is associated with by restoring \ its sizes from before the collapse occurred. :return <bool> | changed """ if ( not self.isCollapsed() ): return False self.parent().setSizes(self._storedSizes) self.unmarkCollapsed() return True def unmarkCollapsed( self ): """ Unmarks this splitter as being in a collapsed state, clearing any \ collapsed information. """ if ( not self.isCollapsed() ): return self._collapsed = False self._storedSizes = None if ( self.orientation() == Qt.Vertical ): self._collapseBefore.setArrowType( Qt.UpArrow ) self._collapseAfter.setArrowType( Qt.DownArrow ) else: self._collapseBefore.setArrowType( Qt.LeftArrow ) self._collapseAfter.setArrowType( Qt.RightArrow ) def toggleCollapseAfter( self ): """ Collapses the splitter after this handle. """ if ( self.isCollapsed() ): self.uncollapse() else: self.collapse( XSplitterHandle.CollapseDirection.After ) def toggleCollapseBefore( self ): """ Collapses the splitter before this handle. """ if ( self.isCollapsed() ): self.uncollapse() else: self.collapse( XSplitterHandle.CollapseDirection.Before )
class XLocationWidget(QWidget): locationChanged = Signal(str) locationEdited = Signal() def __init__( self, parent ): super(XLocationWidget, self).__init__(parent) # define the interface self._locationEdit = XLineEdit(self) self._locationButton = QToolButton(self) self._urlTemplate = 'http://maps.google.com/maps?%(params)s' self._urlQueryKey = 'q' self._locationButton.setAutoRaise(True) self._locationButton.setIcon(QIcon(resources.find('img/map.png'))) self._locationEdit.setHint('no location set') layout = QHBoxLayout() layout.setContentsMargins(0, 0, 0, 0) layout.setSpacing(0) layout.addWidget(self._locationEdit) layout.addWidget(self._locationButton) self.setLayout(layout) # create connections self._locationEdit.textChanged.connect(self.locationChanged) self._locationEdit.textEdited.connect(self.locationEdited) self._locationButton.clicked.connect(self.browseMaps) def blockSignals( self, state ): """ Blocks the signals for this widget and its sub-parts. :param state | <bool> """ super(XLocationWidget, self).blockSignals(state) self._locationEdit.blockSignals(state) self._locationButton.blockSignals(state) def browseMaps( self ): """ Brings up a web browser with the address in a Google map. """ url = self.urlTemplate() params = urllib.urlencode({self.urlQueryKey(): self.location()}) url = url % {'params': params} webbrowser.open(url) def hint( self ): """ Returns the hint associated with this widget. :return <str> """ return self._locationEdit.hint() def lineEdit( self ): """ Returns the line edit linked with this widget. :return <XLineEdit> """ return self._locationEdit def location( self ): """ Returns the current location from the edit. :return <str> """ return nativestring(self._locationEdit.text()) @Slot(str) def setHint( self, hint ): """ Sets the hint associated with this widget. :param hint | <str> """ self._locationEdit.setHint(hint) @Slot(str) def setLocation( self, location ): """ Sets the location for this widget to the inputed location. :param location | <str> """ self._locationEdit.setText(nativestring(location)) def setUrlQueryKey( self, key ): """ Sets the key for the URL to the inputed key. :param key | <str> """ self._urlQueryKey = nativestring(key) def setUrlTemplate( self, templ ): """ Sets the URL path template that will be used when looking up locations on the web. :param templ | <str> """ self._urlQueryTemplate = nativestring(templ) def urlQueryKey( self ): """ Returns the query key that will be used for this location. :return <str> """ return self._urlQueryKey def urlTemplate( self ): """ Returns the url template that will be used when mapping this location. :return <str> """ return self._urlTemplate x_hint = Property(str, hint, setHint) x_location = Property(str, location, setLocation) x_urlQueryKey = Property(str, urlQueryKey, setUrlQueryKey) x_urlTemplate = Property(str, urlTemplate, setUrlTemplate)
class XFindWidget(QWidget): """ """ __designer_icon__ = resources.find('img/search.png') def __init__( self, parent = None ): super(XFindWidget, self).__init__( parent ) # define custom properties self._textEdit = None self._webView = None self._lastCursor = QTextCursor() self._lastText = '' self._closeButton = QToolButton(self) self._closeButton.setIcon(QIcon(resources.find('img/close.png'))) self._closeButton.setAutoRaise(True) self._closeButton.setToolTip('Hide the Find Field.') self._searchEdit = XLineEdit(self) self._searchEdit.setHint('search for...') self._previousButton = QToolButton(self) self._previousButton.setIcon(QIcon(resources.find('img/back.png'))) self._previousButton.setAutoRaise(True) self._previousButton.setToolTip('Find Previous') self._nextButton = QToolButton(self) self._nextButton.setIcon(QIcon(resources.find('img/forward.png'))) self._nextButton.setAutoRaise(True) self._nextButton.setToolTip('Find Next') self._caseSensitiveCheckbox = QCheckBox(self) self._caseSensitiveCheckbox.setText('Case Sensitive') self._wholeWordsCheckbox = QCheckBox(self) self._wholeWordsCheckbox.setText('Whole Words Only') self._regexCheckbox = QCheckBox(self) self._regexCheckbox.setText('Use Regex') self._findAction = QAction(self) self._findAction.setText('Find...') self._findAction.setIcon(QIcon(resources.find('img/search.png'))) self._findAction.setToolTip('Find in Text') self._findAction.setShortcut(QKeySequence('Ctrl+F')) self._findAction.setShortcutContext(Qt.WidgetWithChildrenShortcut) # layout the widgets layout = QHBoxLayout() layout.setContentsMargins(0, 0, 0, 0) layout.addWidget( self._closeButton ) layout.addWidget( self._searchEdit ) layout.addWidget( self._previousButton ) layout.addWidget( self._nextButton ) layout.addWidget( self._caseSensitiveCheckbox ) layout.addWidget( self._wholeWordsCheckbox ) layout.addWidget( self._regexCheckbox ) self.setLayout(layout) # create connections self._findAction.triggered.connect( self.show ) self._searchEdit.textChanged.connect( self.findNext ) self._closeButton.clicked.connect( self.hide ) self._previousButton.clicked.connect( self.findPrev ) self._nextButton.clicked.connect( self.findNext ) self._caseSensitiveCheckbox.clicked.connect( self.findNext ) self._wholeWordsCheckbox.clicked.connect( self.findNext ) self._searchEdit.returnPressed.connect( self.findNext ) self._regexCheckbox.clicked.connect( self.findNext ) def find( self, flags = 0 ): """ Looks throught the text document based on the current criteria. The \ inputed flags will be merged with the generated search flags. :param flags | <QTextDocument.FindFlag> :return <bool> | success """ # check against the web and text views if ( not (self._textEdit or self._webView) ): fg = QColor('darkRed') bg = QColor('red').lighter(180) palette = self.palette() palette.setColor(palette.Text, fg) palette.setColor(palette.Base, bg) self._searchEdit.setPalette(palette) self._searchEdit.setToolTip( 'No Text Edit is linked.' ) return False if ( self._caseSensitiveCheckbox.isChecked() ): flags |= QTextDocument.FindCaseSensitively if ( self._textEdit and self._wholeWordsCheckbox.isChecked() ): flags |= QTextDocument.FindWholeWords terms = self._searchEdit.text() if ( terms != self._lastText ): self._lastCursor = QTextCursor() if ( self._regexCheckbox.isChecked() ): terms = QRegExp(terms) palette = self.palette() # search on a text edit if ( self._textEdit ): cursor = self._textEdit.document().find(terms, self._lastCursor, QTextDocument.FindFlags(flags)) found = not cursor.isNull() self._lastCursor = cursor self._textEdit.setTextCursor(cursor) elif ( QWebPage ): flags = QWebPage.FindFlags(flags) flags |= QWebPage.FindWrapsAroundDocument found = self._webView.findText(terms, flags) self._lastText = self._searchEdit.text() if ( not terms or found ): fg = palette.color(palette.Text) bg = palette.color(palette.Base) else: fg = QColor('darkRed') bg = QColor('red').lighter(180) palette.setColor(palette.Text, fg) palette.setColor(palette.Base, bg) self._searchEdit.setPalette(palette) return found def findNext( self ): """ Looks for the search terms that come up next based on the criteria. :return <bool> | success """ return self.find() def findPrev( self ): """ Looks for the search terms that come up last based on the criteria. :return <bool> | success """ return self.find(QTextDocument.FindBackward) def setTextEdit( self, textEdit ): """ Sets the text edit that this find widget will use to search. :param textEdit | <QTextEdit> """ if ( self._textEdit ): self._textEdit.removeAction(self._findAction) self._textEdit = textEdit if ( textEdit ): textEdit.addAction(self._findAction) def setWebView( self, webView ): """ Sets the web view edit that this find widget will use to search. :param webView | <QWebView> """ if ( self._webView ): self._webView.removeAction(self._findAction) self._webView = webView if ( webView ): webView.addAction(self._findAction) def show( self ): """ Sets this widget visible and then makes the find field have focus. """ super(XFindWidget, self).show() self._searchEdit.setFocus() def textEdit( self ): """ Returns the text edit linked with this find widget. :return <QTextEdit> """ return self._textEdit def webView( self ): """ Returns the text edit linked with this find widget. :return <QWebView> """ return self._webView
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 = 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._filepathEdit.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) 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() @Slot() def clearFilepath( self ): """ Clears the filepath contents for this path. """ self.setFilepath('') @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 nativestring(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 = nativestring(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 = nativestring(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(nativestring(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) @Slot(int) def setFilepathMode( self, mode ): """ Sets the filepath mode for this widget to the inputed mode. :param mode | <XFilepathEdit.Mode> """ self._filepathMode = mode @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[nativestring(text)]) return True except KeyError: return False @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 @Slot(str) def setFilepath(self, filepath): """ Sets the filepath text for this widget to the inputed path. :param filepath | <str> """ if not filepath: self._filepathEdit.setText('') return if self.normalizePath(): filepath = os.path.normpath(nativestring(filepath)) else: filepath = os.path.normpath(nativestring(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(nativestring(hint)) else: filepath = os.path.normpath(nativestring(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 @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) @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 = Property(bool, autoRaise, setAutoRaise) x_filepathTypes = Property(str, filepathTypes, setFilepathTypes) x_filepath = Property(str, filepath, setFilepath) x_readOnly = Property(bool, isReadOnly, setReadOnly) x_validated = Property(bool, isValidated, setValidated) x_hint = Property(str, hint, setHint) x_icon = Property('QIcon', icon, setIcon) x_normalizePath = Property(bool, normalizePath, setNormalizePath) x_invalidForeground = Property('QColor', invalidForeground, setInvalidForeground) x_invalidBackground = Property('QColor', invalidBackground, setInvalidBackground) x_validForeground = Property('QColor', validForeground, setValidForeground) x_validBackground = Property('QColor', validBackground, setValidBackground) x_filepathModeText = Property(str, filepathModeText, setFilepathModeText)