class XViewTabMenu(XViewBaseMenu): def __init__(self, parent): self._pluginMenu = None self._groupingMenu = None self._panelGroup = QActionGroup(parent) self._groupingGroup = QActionGroup(parent) self._hideAction = None super(XViewTabMenu, self).__init__(parent) def setupActions(self): viewWidget = self.viewWidget() self.addSection('Views') self._pluginMenu = XViewPluginMenu(self.viewWidget()) self._pluginMenu.setTitle('Switch View') self.addMenu(self._pluginMenu) self._pluginMenu.triggered.disconnect(self._pluginMenu.addView) self._pluginMenu.setIcon(QIcon(resources.find('img/view/switch.png'))) self._pluginMenu.triggered.connect(self.swapTabType) # create view grouping options self._groupingMenu = self.addMenu('Set Group') icon = QIcon(resources.find('img/view/group.png')) self._groupingMenu.setIcon(icon) act = self._groupingMenu.addAction('No Grouping') act.setData(wrapVariant(0)) act.setCheckable(True) self._groupingMenu.addSeparator() self._groupingGroup.addAction(act) for i in range(1, 6): act = self._groupingMenu.addAction('Group %i' % i) act.setData(wrapVariant(i)) act.setCheckable(True) self._groupingGroup.addAction(act) self._groupingMenu.triggered.connect(self.assignGroup) self.setTitle('Tabs') self.addSection('Tabs') act = self.addAction('Rename Tab') act.setIcon(QIcon(resources.find('img/edit.png'))) act.triggered.connect(self.renamePanel) act = self.addAction('Detach Tab') act.setIcon(QIcon(resources.find('img/view/detach.png'))) act.triggered.connect(self.detachPanel) act = self.addAction('Detach Tab (as a Copy)') act.setIcon(QIcon(resources.find('img/view/detach_copy.png'))) act.triggered.connect(self.detachPanelCopy) act = self.addAction('Close Tab') act.setIcon(QIcon(resources.find('img/view/tab_remove.png'))) act.triggered.connect(self.closeView) self.addSeparator() act = self.addAction('Hide Tabs when Locked') act.triggered[bool].connect(self.setHideTabsWhenLocked) act.setCheckable(True) self._hideAction = act self.addSection('Panels') act = self.addAction('Split Panel Left/Right') act.setIcon(QIcon(resources.find('img/view/split_horizontal.png'))) act.triggered.connect(self.splitHorizontal) split_adv = QAction(self) split_adv.setIcon(QIcon(resources.find('img/advanced.png'))) split_adv.setToolTip('Specify number of columns for split') split_adv.triggered.connect(self.splitHorizontalAdvanced) self.setAdvancedAction(act, split_adv) act = self.addAction('Split Panel Top/Bottom') act.setIcon(QIcon(resources.find('img/view/split_vertical.png'))) act.triggered.connect(self.splitVertical) split_adv = QAction(self) split_adv.setIcon(QIcon(resources.find('img/advanced.png'))) split_adv.setToolTip('Specify number of rows for split') split_adv.triggered.connect(self.splitVerticalAdvanced) self.setAdvancedAction(act, split_adv) def setCurrentPanel(self, panel): super(XViewTabMenu, self).setCurrentPanel(panel) # update the current tab based on what view type it is viewType = '' grp = -1 if panel is not None and panel.currentView(): viewType = panel.currentView().viewName() grp = panel.currentView().viewingGroup() self._panelGroup.blockSignals(True) for act in self._panelGroup.actions(): act.setChecked(viewType == act.text()) self._panelGroup.blockSignals(False) self._groupingGroup.blockSignals(True) for act in self._groupingGroup.actions(): act.setChecked(grp == unwrapVariant(act.data())) self._groupingGroup.blockSignals(False) self._pluginMenu.setCurrentPanel(panel) self._groupingMenu.setEnabled(grp != -1) if panel: self._hideAction.setChecked(panel.hideTabsWhenLocked()) self._hideAction.setEnabled(True) else: self._hideAction.setEnabled(False) def setHideTabsWhenLocked(self, state): self.currentPanel().setHideTabsWhenLocked(state)
class XActionGroupWidget(QWidget): """ ~~>[img:widgets/xactiongroupwidget.png] The XActionGroupWidget class provides a simple class for creating a multi-checkable tool button based on QActions and QActionGroups. === Example Usage === |>>> from projexui.widgets.xactiongroupwidget import XActionGroupWidget |>>> import projexui | |>>> # create the widget |>>> widget = projexui.testWidget(XActionGroupWidget) | |>>> # add some actions (can be text or a QAction) |>>> widget.addAction('Day') |>>> widget.addAction('Month') |>>> widget.addAction('Year') | |>>> # create connections |>>> def printAction(act): print act.text() |>>> widget.actionGroup().triggered.connect(printAction) """ __designer_icon__ = projexui.resources.find('img/ui/multicheckbox.png') def __init__( self, parent = None ): super(XActionGroupWidget, self).__init__( parent ) # define custom properties self._actionGroup = None self._padding = 5 self._cornerRadius = 10 # set default properties layout = QBoxLayout(QBoxLayout.LeftToRight) layout.setContentsMargins(0, 0, 0, 0) layout.setSpacing(0) self.setSizePolicy( QSizePolicy.Preferred, QSizePolicy.Preferred ) self.setLayout(layout) # create connections def actionGroup( self ): """ Returns the action group linked with this widget. :return <QActionGroup> """ return self._actionGroup def addAction( self, action ): """ Adds the inputed action to this widget's action group. This will auto-\ create a new group if no group is already defined. :param action | <QAction> || <str> :return <QAction> """ if not isinstance(action, QAction): action_name = nativestring(action) action = QAction(action_name, self) action.setObjectName(action_name) action.setCheckable(True) if ( not self._actionGroup ): self._actionGroup = QActionGroup(self) action.setChecked(True) self._actionGroup.addAction(action) self.reset() return action def colorString( self, clr ): """ Renders the inputed color to an RGB string value. :return <str> """ return 'rgb(%s, %s, %s)' % (clr.red(), clr.green(), clr.blue()) def cornerRadius( self ): """ Returns the corner radius for this widget. :return <int> """ return self._cornerRadius def currentAction( self ): """ Returns the action that is currently checked in the system. :return <QAction> || None """ if ( not self._actionGroup ): return None for act in self._actionGroup.actions(): if ( act.isChecked() ): return act return None def direction( self ): """ Returns the direction for this widget. :return <QBoxLayout::Direction> """ return self.layout().direction() def findAction( self, text ): """ Looks up the action based on the inputed text. :return <QAction> || None """ for action in self.actionGroup().actions(): if ( text in (action.objectName(), action.text()) ): return action return None def padding( self ): """ Returns the button padding amount for this widget. :return <int> """ return self._padding def reset( self ): """ Resets the user interface buttons for this widget. """ # clear previous widgets for btn in self.findChildren(QToolButton): btn.close() btn.setParent(None) btn.deleteLater() # determine coloring options palette = self.palette() unchecked = palette.color(palette.Button) # determine if this is a dark or light scheme avg = (unchecked.red() + unchecked.green() + unchecked.blue()) / 3.0 if ( avg < 140 ): checked = unchecked.lighter(115) checked_clr = self.colorString(unchecked.lighter(120)) border_clr = self.colorString(unchecked.darker(140)) unchecked_clr = self.colorString(checked.lighter(140)) unchecked_clr_alt = self.colorString(checked.lighter(120)) checked_clr_alt = self.colorString(unchecked) else: checked = unchecked.lighter(120) checked_clr = self.colorString(unchecked) border_clr = self.colorString(unchecked.darker(160)) unchecked_clr = self.colorString(checked) unchecked_clr_alt = self.colorString(checked.darker(130)) checked_clr_alt = self.colorString(unchecked.darker(120)) # define the stylesheet options options = {} options['top_left_radius'] = 0 options['top_right_radius'] = 0 options['bot_left_radius'] = 0 options['bot_right_radius'] = 0 options['border_color'] = border_clr options['checked_clr'] = checked_clr options['checked_clr_alt'] = checked_clr_alt options['unchecked_clr'] = unchecked_clr options['unchecked_clr_alt'] = unchecked_clr_alt options['padding_top'] = 1 options['padding_bottom'] = 1 options['padding_left'] = 1 options['padding_right'] = 1 horiz = self.direction() in (QBoxLayout.LeftToRight, QBoxLayout.RightToLeft) if ( horiz ): options['x1'] = 0 options['y1'] = 0 options['x2'] = 0 options['y2'] = 1 else: options['x1'] = 0 options['y1'] = 0 options['x2'] = 1 options['y2'] = 1 actions = self.actionGroup().actions() count = len(actions) for i, action in enumerate(actions): btn = QToolButton(self) btn.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Preferred) btn.setDefaultAction(action) self.layout().insertWidget(i, btn) options['top_left_radius'] = 1 options['bot_left_radius'] = 1 options['top_right_radius'] = 1 options['bot_right_radius'] = 1 if ( horiz ): options['padding_left'] = self._padding options['padding_right'] = self._padding else: options['padding_top'] = self._padding options['padding_bottom'] = self._padding if ( not i ): if ( horiz ): options['top_left_radius'] = self.cornerRadius() options['bot_left_radius'] = self.cornerRadius() options['padding_left'] += self.cornerRadius() / 3.0 else: options['top_left_radius'] = self.cornerRadius() options['top_right_radius'] = self.cornerRadius() options['padding_top'] += self.cornerRadius() / 3.0 elif ( i == count - 1 ): if ( horiz ): options['top_right_radius'] = self.cornerRadius() options['bot_right_radius'] = self.cornerRadius() options['padding_right'] += self.cornerRadius() / 3.0 else: options['bot_left_radius'] = self.cornerRadius() options['bot_right_radius'] = self.cornerRadius() options['padding_bottom'] += self.cornerRadius() / 3.0 btn.setStyleSheet(TOOLBUTTON_STYLE % options) btn.setAutoFillBackground(True) def setActionGroup( self, actionGroup ): """ Sets the action group for this widget to the inputed action group. :param actionGroup | <QActionGroup> """ self._actionGroup = actionGroup self.reset() def setCornerRadius( self, radius ): """ Sets the corner radius value for this widget to the inputed radius. :param radius | <int> """ self._cornerRadius = radius def setDirection( self, direction ): """ Sets the direction that this group widget will face. :param direction | <QBoxLayout::Direction> """ self.layout().setDirection(direction) self.reset() def setPadding( self, padding ): """ Sets the padding amount for this widget's button set. :param padding | <int> """ self._padding = padding self.reset()
class XSplitButton(QWidget): """ ~~>[img:widgets/xsplitbutton.png] The XSplitButton class provides a simple class for creating a multi-checkable tool button based on QActions and QActionGroups. === Example Usage === |>>> from projexui.widgets.xsplitbutton import XSplitButton |>>> import projexui | |>>> # create the widget |>>> widget = projexui.testWidget(XSplitButton) | |>>> # add some actions (can be text or a QAction) |>>> widget.addAction('Day') |>>> widget.addAction('Month') |>>> widget.addAction('Year') | |>>> # create connections |>>> def printAction(act): print act.text() |>>> widget.actionGroup().triggered.connect(printAction) """ __designer_icon__ = projexui.resources.find('img/ui/multicheckbox.png') clicked = Signal() currentActionChanged = Signal(object) currentIndexChanged = Signal(int) hovered = Signal(object) triggered = Signal(object) def __init__(self, parent=None): super(XSplitButton, self).__init__(parent) # define custom properties self._actionGroup = QActionGroup(self) self._padding = 5 self._cornerRadius = 10 self._checkable = True # set default properties layout = QBoxLayout(QBoxLayout.LeftToRight) layout.setContentsMargins(0, 0, 0, 0) layout.setSpacing(0) self.setSizePolicy(QSizePolicy.Preferred, QSizePolicy.Preferred) self.setLayout(layout) self.clear() # create connections self._actionGroup.hovered.connect(self.emitHovered) self._actionGroup.triggered.connect(self.emitTriggered) def actions(self): """ Returns a list of the actions linked with this widget. :return [<QAction>, ..] """ return self._actionGroup.actions() def actionTexts(self): """ Returns a list of the action texts for this widget. :return [<str>, ..] """ return map(lambda x: x.text(), self._actionGroup.actions()) def actionGroup(self): """ Returns the action group linked with this widget. :return <QActionGroup> """ return self._actionGroup def addAction(self, action, checked=None, autoBuild=True): """ Adds the inputed action to this widget's action group. This will auto-\ create a new group if no group is already defined. :param action | <QAction> || <str> :return <QAction> """ # clear the holder actions = self._actionGroup.actions() if actions and actions[0].objectName() == 'place_holder': self._actionGroup.removeAction(actions[0]) actions[0].deleteLater() # create an action from the name if not isinstance(action, QAction): action_name = nativestring(action) action = QAction(action_name, self) action.setObjectName(action_name) action.setCheckable(self.isCheckable()) # auto-check the first option if checked or (not self._actionGroup.actions() and checked is None): action.setChecked(True) elif self.isCheckable(): action.setCheckable(True) if not self.currentAction(): action.setChecked(True) self._actionGroup.addAction(action) if autoBuild: self.rebuild() return action def clear(self, autoBuild=True): """ Clears the actions for this widget. """ for action in self._actionGroup.actions(): self._actionGroup.removeAction(action) action = QAction('', self) action.setObjectName('place_holder') self._actionGroup.addAction(action) if autoBuild: self.rebuild() def cornerRadius(self): """ Returns the corner radius for this widget. :return <int> """ return self._cornerRadius def count(self): """ Returns the number of actions associated with this button. :return <int> """ actions = self._actionGroup.actions() if len(actions) == 1 and actions[0].objectName() == 'place_holder': return 0 return len(actions) def currentAction(self): """ Returns the action that is currently checked in the system. :return <QAction> || None """ return self._actionGroup.checkedAction() def direction(self): """ Returns the direction for this widget. :return <QBoxLayout::Direction> """ return self.layout().direction() def emitClicked(self): """ Emits the clicked signal whenever any of the actions are clicked. """ if not self.signalsBlocked(): self.clicked.emit() def emitHovered(self, action): """ Emits the hovered action for this widget. :param action | <QAction> """ if not self.signalsBlocked(): self.hovered.emit(action) def emitTriggered(self, action): """ Emits the triggered action for this widget. :param action | <QAction> """ self.currentActionChanged.emit(action) self.currentIndexChanged.emit(self.indexOf(action)) if not self.signalsBlocked(): self.triggered.emit(action) def findAction(self, text): """ Looks up the action based on the inputed text. :return <QAction> || None """ for action in self.actionGroup().actions(): if text in (action.objectName(), action.text()): return action return None def indexOf(self, action): """ Returns the index of the inputed action. :param action | <QAction> || None :return <int> """ for i, act in enumerate(self.actionGroup().actions()): if action in (act, act.objectName(), act.text()): return i return -1 def isCheckable(self): """ Returns whether or not the actions within this button should be checkable. :return <bool> """ return self._checkable def padding(self): """ Returns the button padding amount for this widget. :return <int> """ return self._padding def rebuild(self): """ Rebuilds the user interface buttons for this widget. """ self.setUpdatesEnabled(False) # sync up the toolbuttons with our actions actions = self._actionGroup.actions() btns = self.findChildren(QToolButton) horiz = self.direction() in (QBoxLayout.LeftToRight, QBoxLayout.RightToLeft) # remove unnecessary buttons if len(actions) < len(btns): rem_btns = btns[len(actions) - 1:] btns = btns[:len(actions)] for btn in rem_btns: btn.close() btn.setParent(None) btn.deleteLater() # create new buttons elif len(btns) < len(actions): for i in range(len(btns), len(actions)): btn = QToolButton(self) btn.setAutoFillBackground(True) btns.append(btn) self.layout().addWidget(btn) btn.clicked.connect(self.emitClicked) # determine coloring options palette = self.palette() checked = palette.color(palette.Highlight) checked_fg = palette.color(palette.HighlightedText) unchecked = palette.color(palette.Button) unchecked_fg = palette.color(palette.ButtonText) border = palette.color(palette.Mid) # define the stylesheet options options = {} options['top_left_radius'] = 0 options['top_right_radius'] = 0 options['bot_left_radius'] = 0 options['bot_right_radius'] = 0 options['border_color'] = border.name() options['checked_fg'] = checked_fg.name() options['checked_bg'] = checked.name() options['checked_bg_alt'] = checked.darker(120).name() options['unchecked_fg'] = unchecked_fg.name() options['unchecked_bg'] = unchecked.name() options['unchecked_bg_alt'] = unchecked.darker(120).name() options['padding_top'] = 1 options['padding_bottom'] = 1 options['padding_left'] = 1 options['padding_right'] = 1 if horiz: options['x1'] = 0 options['y1'] = 0 options['x2'] = 0 options['y2'] = 1 else: options['x1'] = 0 options['y1'] = 0 options['x2'] = 1 options['y2'] = 1 # sync up the actions and buttons count = len(actions) palette = self.palette() font = self.font() for i, action in enumerate(actions): btn = btns[i] # assign the action for this button if btn.defaultAction() != action: # clear out any existing actions for act in btn.actions(): btn.removeAction(act) # assign the given action btn.setDefaultAction(action) options['top_left_radius'] = 1 options['bot_left_radius'] = 1 options['top_right_radius'] = 1 options['bot_right_radius'] = 1 if horiz: options['padding_left'] = self._padding options['padding_right'] = self._padding else: options['padding_top'] = self._padding options['padding_bottom'] = self._padding if not i: if horiz: options['top_left_radius'] = self.cornerRadius() options['bot_left_radius'] = self.cornerRadius() options['padding_left'] += self.cornerRadius() / 3.0 else: options['top_left_radius'] = self.cornerRadius() options['top_right_radius'] = self.cornerRadius() options['padding_top'] += self.cornerRadius() / 3.0 if i == count - 1: if horiz: options['top_right_radius'] = self.cornerRadius() options['bot_right_radius'] = self.cornerRadius() options['padding_right'] += self.cornerRadius() / 3.0 else: options['bot_left_radius'] = self.cornerRadius() options['bot_right_radius'] = self.cornerRadius() options['padding_bottom'] += self.cornerRadius() / 3.0 btn.setFont(font) btn.setPalette(palette) btn.setStyleSheet(TOOLBUTTON_STYLE % options) if horiz: btn.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Preferred) else: btn.setSizePolicy(QSizePolicy.Preferred, QSizePolicy.Expanding) self.setUpdatesEnabled(True) def setActions(self, actions): """ Sets the actions for this widget to th inputed list of actions. :param [<QAction>, ..] """ self.clear(autoBuild=False) for action in actions: self.addAction(action, autoBuild=False) self.rebuild() def setActionTexts(self, names): """ Convenience method for auto-generating actions based on text names, sets the list of actions for this widget to the inputed list of names. :param names | [<str>, ..] """ self.setActions(names) def setActionGroup(self, actionGroup): """ Sets the action group for this widget to the inputed action group. :param actionGroup | <QActionGroup> """ self._actionGroup = actionGroup self.rebuild() def setCheckable(self, state): """ Sets whether or not the actions within this button should be checkable. :param state | <bool> """ self._checkable = state for act in self._actionGroup.actions(): act.setCheckable(state) def setCornerRadius(self, radius): """ Sets the corner radius value for this widget to the inputed radius. :param radius | <int> """ self._cornerRadius = radius def setCurrentAction(self, action): """ Sets the current action for this button to the inputed action. :param action | <QAction> || <str> """ self._actionGroup.blockSignals(True) for act in self._actionGroup.actions(): act.setChecked(act == action or act.text() == action) self._actionGroup.blockSignals(False) def setDirection(self, direction): """ Sets the direction that this group widget will face. :param direction | <QBoxLayout::Direction> """ self.layout().setDirection(direction) self.rebuild() def setFont(self, font): """ Sets the font for this widget and propogates down to the buttons. :param font | <QFont> """ super(XSplitButton, self).setFont(font) self.rebuild() def setPadding(self, padding): """ Sets the padding amount for this widget's button set. :param padding | <int> """ self._padding = padding self.rebuild() def setPalette(self, palette): """ Rebuilds the buttons for this widget since they use specific palette options. :param palette | <QPalette> """ super(XSplitButton, self).setPalette(palette) self.rebuild() def sizeHint(self): """ Returns the base size hint for this widget. :return <QSize> """ return QSize(35, 22) x_actionTexts = Property(QStringList, actionTexts, setActionTexts) x_checkable = Property(bool, isCheckable, setCheckable)
class XViewTabMenu(XViewBaseMenu): def __init__(self, parent): self._pluginMenu = None self._groupingMenu = None self._panelGroup = QActionGroup(parent) self._groupingGroup = QActionGroup(parent) self._hideAction = None super(XViewTabMenu, self).__init__(parent) def setupActions(self): viewWidget = self.viewWidget() self.addSection('Views') self._pluginMenu = XViewPluginMenu(self.viewWidget()) self._pluginMenu.setTitle('Switch View') self.addMenu(self._pluginMenu) self._pluginMenu.triggered.disconnect(self._pluginMenu.addView) self._pluginMenu.setIcon(QIcon(resources.find('img/view/switch.png'))) self._pluginMenu.triggered.connect(self.swapTabType) # create view grouping options self._groupingMenu = self.addMenu('Set Group') icon = QIcon(resources.find('img/view/group.png')) self._groupingMenu.setIcon(icon) act = self._groupingMenu.addAction('No Grouping') act.setData(wrapVariant(0)) act.setCheckable(True) self._groupingMenu.addSeparator() self._groupingGroup.addAction(act) for i in range(1, 6): act = self._groupingMenu.addAction('Group %i' % i) act.setData(wrapVariant(i)) act.setCheckable(True) self._groupingGroup.addAction(act) self._groupingMenu.triggered.connect(self.assignGroup) self.setTitle('Tabs') self.addSection('Tabs') act = self.addAction('Rename Tab') act.setIcon(QIcon(resources.find('img/edit.png'))) act.triggered.connect( self.renamePanel ) act = self.addAction('Detach Tab') act.setIcon(QIcon(resources.find('img/view/detach.png'))) act.triggered.connect( self.detachPanel ) act = self.addAction('Detach Tab (as a Copy)') act.setIcon(QIcon(resources.find('img/view/detach_copy.png'))) act.triggered.connect( self.detachPanelCopy ) act = self.addAction('Close Tab') act.setIcon(QIcon(resources.find('img/view/tab_remove.png'))) act.triggered.connect( self.closeView ) self.addSeparator() act = self.addAction('Hide Tabs when Locked') act.triggered[bool].connect(self.setHideTabsWhenLocked) act.setCheckable(True) self._hideAction = act self.addSection('Panels') act = self.addAction('Split Panel Left/Right') act.setIcon(QIcon(resources.find('img/view/split_horizontal.png'))) act.triggered.connect(self.splitHorizontal) split_adv = QAction(self) split_adv.setIcon(QIcon(resources.find('img/advanced.png'))) split_adv.setToolTip('Specify number of columns for split') split_adv.triggered.connect(self.splitHorizontalAdvanced) self.setAdvancedAction(act, split_adv) act = self.addAction('Split Panel Top/Bottom') act.setIcon(QIcon(resources.find('img/view/split_vertical.png'))) act.triggered.connect(self.splitVertical) split_adv = QAction(self) split_adv.setIcon(QIcon(resources.find('img/advanced.png'))) split_adv.setToolTip('Specify number of rows for split') split_adv.triggered.connect(self.splitVerticalAdvanced) self.setAdvancedAction(act, split_adv) def setCurrentPanel(self, panel): super(XViewTabMenu, self).setCurrentPanel(panel) # update the current tab based on what view type it is viewType = '' grp = -1 if panel is not None and panel.currentView(): viewType = panel.currentView().viewName() grp = panel.currentView().viewingGroup() self._panelGroup.blockSignals(True) for act in self._panelGroup.actions(): act.setChecked(viewType == act.text()) self._panelGroup.blockSignals(False) self._groupingGroup.blockSignals(True) for act in self._groupingGroup.actions(): act.setChecked(grp == unwrapVariant(act.data())) self._groupingGroup.blockSignals(False) self._pluginMenu.setCurrentPanel(panel) self._groupingMenu.setEnabled(grp != -1) if panel: self._hideAction.setChecked(panel.hideTabsWhenLocked()) self._hideAction.setEnabled(True) else: self._hideAction.setEnabled(False) def setHideTabsWhenLocked(self, state): self.currentPanel().setHideTabsWhenLocked(state)
class XViewProfileToolBar(XToolBar): profileCreated = Signal(PyObject) profileChanged = Signal(PyObject) profileRemoved = Signal(PyObject) profilesChanged = Signal() currentProfileChanged = Signal(PyObject) loadProfileFinished = Signal(PyObject) newWindowProfileRequested = Signal(PyObject) def __init__(self, parent): super(XViewProfileToolBar, self).__init__(parent) # create custom properties self._editingEnabled = True self._viewWidget = None self._profileText = 'Profile' self._profileGroup = QActionGroup(self) self._currentProfile = None # set the default options self.setIconSize(QSize(48, 48)) self.setToolButtonStyle(Qt.ToolButtonTextUnderIcon) self.setContextMenuPolicy(Qt.CustomContextMenu) # create connections self.actionTriggered.connect(self.handleActionTrigger) self.customContextMenuRequested.connect(self.showProfileMenu) def addProfile(self, profile): """ Adds the inputed profile as an action to the toolbar. :param profile | <projexui.widgets.xviewwidget.XViewProfile> """ # use the existing version for exist in self.profiles(): if exist.name() == profile.name(): if exist.version() < profile.version(): exist.setProfile(profile) return # otherwise create a new profile act = XViewProfileAction(profile, self) self._profileGroup.addAction(act) self.addAction(act) return act def clearActive(self): # clear the GUI self.blockSignals(True) for act in self.actions(): act.blockSignals(True) act.setChecked(False) act.blockSignals(False) self.blockSignals(False) self._currentProfile = None # reset the layout widget = self.viewWidget() if self.sender() != widget: widget.reset(force=True) widget.setLocked(False) self.currentProfileChanged.emit(None) def currentProfile(self): """ Returns the current profile for this toolbar. :return <projexui.widgets.xviewwidget.XViewProfile> || None """ return self._currentProfile def createProfile(self, profile=None, clearLayout=True): """ Prompts the user to create a new profile. """ if profile: prof = profile elif not self.viewWidget() or clearLayout: prof = XViewProfile() else: prof = self.viewWidget().saveProfile() blocked = self.signalsBlocked() self.blockSignals(False) changed = self.editProfile(prof) self.blockSignals(blocked) if not changed: return act = self.addProfile(prof) act.setChecked(True) # update the interface if self.viewWidget() and (profile or clearLayout): self.viewWidget().restoreProfile(prof) if not self.signalsBlocked(): self.profileCreated.emit(prof) self.profilesChanged.emit() @Slot(PyObject) def editProfile(self, profile): """ Prompts the user to edit the given profile. :param profile | <projexui.widgets.xviewwidget.XViewProfile> """ mod = XViewProfileDialog.edit(self.window(), profile) if not mod: return False # update the action interface for act in self._profileGroup.actions(): if act.profile() == profile: act.setProfile(profile) break # signal the change if not self.signalsBlocked(): self.profileChanged.emit(profile) self.profilesChanged.emit() return True def exportProfile(self, profile, filename=None): """ Exports this toolbar to the given filename. :param profile | <XViewProfile> filename | <str> || None """ if not filename: filename = QFileDialog.getSaveFileName(self, 'Export Profile', '', 'XML Files (*.xml)') if type(filename) == tuple: filename = nativestring(filename[0]) if not filename: return False profile.save(filename) return True def handleActionTrigger(self, action): """ Handles when an action has been triggered. If the inputed action is a XViewProfileAction, then the currentProfileChanged signal will emit. :param action | <QAction> """ # trigger a particular profile if isinstance(action, XViewProfileAction): # if the user CTRL+Clicks on the action, then attempt # to load it in a new window if QApplication.keyboardModifiers() == Qt.ControlModifier: self.newWindowProfileRequested.emit(action.profile()) self.setCurrentProfile(self.currentProfile()) return else: self.setCurrentProfile(action.profile()) def importProfile(self, filename=None): """ Imports the profiles from the given filename. :param filename | <str> || None """ if not filename: filename = QFileDialog.getOpenFileName( self, 'Import Perspective', '', 'XML Files (*.xml)') if type(filename) == tuple: filename = nativestring(filename[0]) if not (filename and os.path.exists(filename)): return False prof = XViewProfile.load(filename) if prof: self.addProfile(prof) def isEditingEnabled(self): """ Sets whether or not the create is enabled for this toolbar. :return <bool> """ return self._editingEnabled def isEmpty(self): """ Returns whether or not this toolbar is empty. :return <bool> """ return len(self._profileGroup.actions()) == 0 def loadString(self, profilestr, merge=False, loadProfile=True): """ Loads the information for this toolbar from the inputed string. :param profilestr | <str> """ try: xtoolbar = ElementTree.fromstring(nativestring(profilestr)) except ExpatError, e: return if not merge: self.clear() self.blockSignals(True) for xprofile in xtoolbar: prof = XViewProfile.fromXml(xprofile) if merge: self.removeProfile(prof.name(), silent=True) self.addProfile(prof) self.setCurrentProfile(xtoolbar.get('current')) self.blockSignals(False) self.profilesChanged.emit()
class XSplitButton(QWidget): """ ~~>[img:widgets/xsplitbutton.png] The XSplitButton class provides a simple class for creating a multi-checkable tool button based on QActions and QActionGroups. === Example Usage === |>>> from projexui.widgets.xsplitbutton import XSplitButton |>>> import projexui | |>>> # create the widget |>>> widget = projexui.testWidget(XSplitButton) | |>>> # add some actions (can be text or a QAction) |>>> widget.addAction('Day') |>>> widget.addAction('Month') |>>> widget.addAction('Year') | |>>> # create connections |>>> def printAction(act): print act.text() |>>> widget.actionGroup().triggered.connect(printAction) """ __designer_icon__ = projexui.resources.find('img/ui/multicheckbox.png') clicked = Signal() currentActionChanged = Signal(object) currentIndexChanged = Signal(int) hovered = Signal(object) triggered = Signal(object) def __init__( self, parent = None ): super(XSplitButton, self).__init__( parent ) # define custom properties self._actionGroup = QActionGroup(self) self._padding = 5 self._cornerRadius = 10 self._checkable = True # set default properties layout = QBoxLayout(QBoxLayout.LeftToRight) layout.setContentsMargins(0, 0, 0, 0) layout.setSpacing(0) self.setSizePolicy(QSizePolicy.Preferred, QSizePolicy.Preferred) self.setLayout(layout) self.clear() # create connections self._actionGroup.hovered.connect(self.emitHovered) self._actionGroup.triggered.connect(self.emitTriggered) def actions(self): """ Returns a list of the actions linked with this widget. :return [<QAction>, ..] """ return self._actionGroup.actions() def actionTexts(self): """ Returns a list of the action texts for this widget. :return [<str>, ..] """ return map(lambda x: x.text(), self._actionGroup.actions()) def actionGroup(self): """ Returns the action group linked with this widget. :return <QActionGroup> """ return self._actionGroup def addAction(self, action, checked=None, autoBuild=True): """ Adds the inputed action to this widget's action group. This will auto-\ create a new group if no group is already defined. :param action | <QAction> || <str> :return <QAction> """ # clear the holder actions = self._actionGroup.actions() if actions and actions[0].objectName() == 'place_holder': self._actionGroup.removeAction(actions[0]) actions[0].deleteLater() # create an action from the name if not isinstance(action, QAction): action_name = nativestring(action) action = QAction(action_name, self) action.setObjectName(action_name) action.setCheckable(self.isCheckable()) # auto-check the first option if checked or (not self._actionGroup.actions() and checked is None): action.setChecked(True) elif self.isCheckable(): action.setCheckable(True) if not self.currentAction(): action.setChecked(True) self._actionGroup.addAction(action) if autoBuild: self.rebuild() return action def clear(self, autoBuild=True): """ Clears the actions for this widget. """ for action in self._actionGroup.actions(): self._actionGroup.removeAction(action) action = QAction('', self) action.setObjectName('place_holder') self._actionGroup.addAction(action) if autoBuild: self.rebuild() def cornerRadius( self ): """ Returns the corner radius for this widget. :return <int> """ return self._cornerRadius def count(self): """ Returns the number of actions associated with this button. :return <int> """ actions = self._actionGroup.actions() if len(actions) == 1 and actions[0].objectName() == 'place_holder': return 0 return len(actions) def currentAction(self): """ Returns the action that is currently checked in the system. :return <QAction> || None """ return self._actionGroup.checkedAction() def direction( self ): """ Returns the direction for this widget. :return <QBoxLayout::Direction> """ return self.layout().direction() def emitClicked(self): """ Emits the clicked signal whenever any of the actions are clicked. """ if not self.signalsBlocked(): self.clicked.emit() def emitHovered(self, action): """ Emits the hovered action for this widget. :param action | <QAction> """ if not self.signalsBlocked(): self.hovered.emit(action) def emitTriggered(self, action): """ Emits the triggered action for this widget. :param action | <QAction> """ self.currentActionChanged.emit(action) self.currentIndexChanged.emit(self.indexOf(action)) if not self.signalsBlocked(): self.triggered.emit(action) def findAction(self, text): """ Looks up the action based on the inputed text. :return <QAction> || None """ for action in self.actionGroup().actions(): if text in (action.objectName(), action.text()): return action return None def indexOf(self, action): """ Returns the index of the inputed action. :param action | <QAction> || None :return <int> """ for i, act in enumerate(self.actionGroup().actions()): if action in (act, act.objectName(), act.text()): return i return -1 def isCheckable(self): """ Returns whether or not the actions within this button should be checkable. :return <bool> """ return self._checkable def padding( self ): """ Returns the button padding amount for this widget. :return <int> """ return self._padding def rebuild( self ): """ Rebuilds the user interface buttons for this widget. """ self.setUpdatesEnabled(False) # sync up the toolbuttons with our actions actions = self._actionGroup.actions() btns = self.findChildren(QToolButton) horiz = self.direction() in (QBoxLayout.LeftToRight, QBoxLayout.RightToLeft) # remove unnecessary buttons if len(actions) < len(btns): rem_btns = btns[len(actions)-1:] btns = btns[:len(actions)] for btn in rem_btns: btn.close() btn.setParent(None) btn.deleteLater() # create new buttons elif len(btns) < len(actions): for i in range(len(btns), len(actions)): btn = QToolButton(self) btn.setAutoFillBackground(True) btns.append(btn) self.layout().addWidget(btn) btn.clicked.connect(self.emitClicked) # determine coloring options palette = self.palette() checked = palette.color(palette.Highlight) checked_fg = palette.color(palette.HighlightedText) unchecked = palette.color(palette.Button) unchecked_fg = palette.color(palette.ButtonText) border = palette.color(palette.Mid) # define the stylesheet options options = {} options['top_left_radius'] = 0 options['top_right_radius'] = 0 options['bot_left_radius'] = 0 options['bot_right_radius'] = 0 options['border_color'] = border.name() options['checked_fg'] = checked_fg.name() options['checked_bg'] = checked.name() options['checked_bg_alt'] = checked.darker(120).name() options['unchecked_fg'] = unchecked_fg.name() options['unchecked_bg'] = unchecked.name() options['unchecked_bg_alt'] = unchecked.darker(120).name() options['padding_top'] = 1 options['padding_bottom'] = 1 options['padding_left'] = 1 options['padding_right'] = 1 if horiz: options['x1'] = 0 options['y1'] = 0 options['x2'] = 0 options['y2'] = 1 else: options['x1'] = 0 options['y1'] = 0 options['x2'] = 1 options['y2'] = 1 # sync up the actions and buttons count = len(actions) palette = self.palette() font = self.font() for i, action in enumerate(actions): btn = btns[i] # assign the action for this button if btn.defaultAction() != action: # clear out any existing actions for act in btn.actions(): btn.removeAction(act) # assign the given action btn.setDefaultAction(action) options['top_left_radius'] = 1 options['bot_left_radius'] = 1 options['top_right_radius'] = 1 options['bot_right_radius'] = 1 if horiz: options['padding_left'] = self._padding options['padding_right'] = self._padding else: options['padding_top'] = self._padding options['padding_bottom'] = self._padding if not i: if horiz: options['top_left_radius'] = self.cornerRadius() options['bot_left_radius'] = self.cornerRadius() options['padding_left'] += self.cornerRadius() / 3.0 else: options['top_left_radius'] = self.cornerRadius() options['top_right_radius'] = self.cornerRadius() options['padding_top'] += self.cornerRadius() / 3.0 if i == count - 1: if horiz: options['top_right_radius'] = self.cornerRadius() options['bot_right_radius'] = self.cornerRadius() options['padding_right'] += self.cornerRadius() / 3.0 else: options['bot_left_radius'] = self.cornerRadius() options['bot_right_radius'] = self.cornerRadius() options['padding_bottom'] += self.cornerRadius() / 3.0 btn.setFont(font) btn.setPalette(palette) btn.setStyleSheet(TOOLBUTTON_STYLE % options) if horiz: btn.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Preferred) else: btn.setSizePolicy(QSizePolicy.Preferred, QSizePolicy.Expanding) self.setUpdatesEnabled(True) def setActions(self, actions): """ Sets the actions for this widget to th inputed list of actions. :param [<QAction>, ..] """ self.clear(autoBuild=False) for action in actions: self.addAction(action, autoBuild=False) self.rebuild() def setActionTexts(self, names): """ Convenience method for auto-generating actions based on text names, sets the list of actions for this widget to the inputed list of names. :param names | [<str>, ..] """ self.setActions(names) def setActionGroup( self, actionGroup ): """ Sets the action group for this widget to the inputed action group. :param actionGroup | <QActionGroup> """ self._actionGroup = actionGroup self.rebuild() def setCheckable(self, state): """ Sets whether or not the actions within this button should be checkable. :param state | <bool> """ self._checkable = state for act in self._actionGroup.actions(): act.setCheckable(state) def setCornerRadius( self, radius ): """ Sets the corner radius value for this widget to the inputed radius. :param radius | <int> """ self._cornerRadius = radius def setCurrentAction(self, action): """ Sets the current action for this button to the inputed action. :param action | <QAction> || <str> """ self._actionGroup.blockSignals(True) for act in self._actionGroup.actions(): act.setChecked(act == action or act.text() == action) self._actionGroup.blockSignals(False) def setDirection( self, direction ): """ Sets the direction that this group widget will face. :param direction | <QBoxLayout::Direction> """ self.layout().setDirection(direction) self.rebuild() def setFont(self, font): """ Sets the font for this widget and propogates down to the buttons. :param font | <QFont> """ super(XSplitButton, self).setFont(font) self.rebuild() def setPadding( self, padding ): """ Sets the padding amount for this widget's button set. :param padding | <int> """ self._padding = padding self.rebuild() def setPalette(self, palette): """ Rebuilds the buttons for this widget since they use specific palette options. :param palette | <QPalette> """ super(XSplitButton, self).setPalette(palette) self.rebuild() def sizeHint(self): """ Returns the base size hint for this widget. :return <QSize> """ return QSize(35, 22) x_actionTexts = Property(QStringList, actionTexts, setActionTexts) x_checkable = Property(bool, isCheckable, setCheckable)
class XViewProfileToolBar(XToolBar): profileCreated = Signal(PyObject) profileChanged = Signal(PyObject) profileRemoved = Signal(PyObject) profilesChanged = Signal() currentProfileChanged = Signal(PyObject) loadProfileFinished = Signal(PyObject) newWindowProfileRequested = Signal(PyObject) def __init__(self, parent): super(XViewProfileToolBar, self).__init__(parent) # create custom properties self._editingEnabled = True self._viewWidget = None self._profileText = 'Profile' self._profileGroup = QActionGroup(self) self._currentProfile = None # set the default options self.setIconSize(QSize(48, 48)) self.setToolButtonStyle(Qt.ToolButtonTextUnderIcon) self.setContextMenuPolicy(Qt.CustomContextMenu) # create connections self.actionTriggered.connect(self.handleActionTrigger) self.customContextMenuRequested.connect(self.showProfileMenu) def addProfile(self, profile): """ Adds the inputed profile as an action to the toolbar. :param profile | <projexui.widgets.xviewwidget.XViewProfile> """ # use the existing version for exist in self.profiles(): if exist.name() == profile.name(): if exist.version() < profile.version(): exist.setProfile(profile) return # otherwise create a new profile act = XViewProfileAction(profile, self) self._profileGroup.addAction(act) self.addAction(act) return act def clearActive(self): # clear the GUI self.blockSignals(True) for act in self.actions(): act.blockSignals(True) act.setChecked(False) act.blockSignals(False) self.blockSignals(False) self._currentProfile = None # reset the layout widget = self.viewWidget() if self.sender() != widget: widget.reset(force=True) widget.setLocked(False) self.currentProfileChanged.emit(None) def currentProfile(self): """ Returns the current profile for this toolbar. :return <projexui.widgets.xviewwidget.XViewProfile> || None """ return self._currentProfile def createProfile(self, profile=None, clearLayout=True): """ Prompts the user to create a new profile. """ if profile: prof = profile elif not self.viewWidget() or clearLayout: prof = XViewProfile() else: prof = self.viewWidget().saveProfile() blocked = self.signalsBlocked() self.blockSignals(False) changed = self.editProfile(prof) self.blockSignals(blocked) if not changed: return act = self.addProfile(prof) act.setChecked(True) # update the interface if self.viewWidget() and (profile or clearLayout): self.viewWidget().restoreProfile(prof) if not self.signalsBlocked(): self.profileCreated.emit(prof) self.profilesChanged.emit() @Slot(PyObject) def editProfile(self, profile): """ Prompts the user to edit the given profile. :param profile | <projexui.widgets.xviewwidget.XViewProfile> """ mod = XViewProfileDialog.edit(self.window(), profile) if not mod: return False # update the action interface for act in self._profileGroup.actions(): if act.profile() == profile: act.setProfile(profile) break # signal the change if not self.signalsBlocked(): self.profileChanged.emit(profile) self.profilesChanged.emit() return True def exportProfile(self, profile, filename=None): """ Exports this toolbar to the given filename. :param profile | <XViewProfile> filename | <str> || None """ if not filename: filename = QFileDialog.getSaveFileName(self, 'Export Profile', '', 'XML Files (*.xml)') if type(filename) == tuple: filename = nativestring(filename[0]) if not filename: return False profile.save(filename) return True def handleActionTrigger(self, action): """ Handles when an action has been triggered. If the inputed action is a XViewProfileAction, then the currentProfileChanged signal will emit. :param action | <QAction> """ # trigger a particular profile if isinstance(action, XViewProfileAction): # if the user CTRL+Clicks on the action, then attempt # to load it in a new window if QApplication.keyboardModifiers() == Qt.ControlModifier: self.newWindowProfileRequested.emit(action.profile()) self.setCurrentProfile(self.currentProfile()) return else: self.setCurrentProfile(action.profile()) def importProfile(self, filename=None): """ Imports the profiles from the given filename. :param filename | <str> || None """ if not filename: filename = QFileDialog.getOpenFileName(self, 'Import Perspective', '', 'XML Files (*.xml)') if type(filename) == tuple: filename = nativestring(filename[0]) if not (filename and os.path.exists(filename)): return False prof = XViewProfile.load(filename) if prof: self.addProfile(prof) def isEditingEnabled(self): """ Sets whether or not the create is enabled for this toolbar. :return <bool> """ return self._editingEnabled def isEmpty(self): """ Returns whether or not this toolbar is empty. :return <bool> """ return len(self._profileGroup.actions()) == 0 def loadString(self, profilestr, merge=False, loadProfile=True): """ Loads the information for this toolbar from the inputed string. :param profilestr | <str> """ try: xtoolbar = ElementTree.fromstring(nativestring(profilestr)) except ExpatError, e: return if not merge: self.clear() self.blockSignals(True) for xprofile in xtoolbar: prof = XViewProfile.fromXml(xprofile) if merge: self.removeProfile(prof.name(), silent=True) self.addProfile(prof) self.setCurrentProfile(xtoolbar.get('current')) self.blockSignals(False) self.profilesChanged.emit()