Esempio n. 1
0
class XSplitButton(QWidget):
    """
    ~~>[img:widgets/xsplitbutton.png]

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

    === Example Usage ===

    |>>> from projexui.widgets.xsplitbutton import XSplitButton
    |>>> import projexui
    |
    |>>> # create the widget
    |>>> widget = projexui.testWidget(XSplitButton)
    |
    |>>> # add some actions (can be text or a QAction)
    |>>> widget.addAction('Day')
    |>>> widget.addAction('Month')
    |>>> widget.addAction('Year')
    |
    |>>> # create connections
    |>>> def printAction(act): print act.text()
    |>>> widget.actionGroup().triggered.connect(printAction)
    """
    
    __designer_icon__ = projexui.resources.find('img/ui/multicheckbox.png')
    
    clicked                 = Signal()
    currentActionChanged    = Signal(object)
    hovered                 = Signal(object)
    triggered               = Signal(object)
    
    def __init__( self, parent = None ):
        super(XSplitButton, self).__init__( parent )
        
        # define custom properties
        self._actionGroup   = QActionGroup(self)
        self._padding       = 5
        self._cornerRadius  = 10
        #self._currentAction = None
        self._checkable     = True
        
        # set default properties
        layout = QBoxLayout(QBoxLayout.LeftToRight)
        layout.setContentsMargins(0, 0, 0, 0)
        layout.setSpacing(0)
        self.setSizePolicy(QSizePolicy.Preferred, QSizePolicy.Preferred)
        self.setLayout(layout)
        self.clear()
        
        # create connections
        self._actionGroup.hovered.connect(self.emitHovered)
        self._actionGroup.triggered.connect(self.emitTriggered)
    
    def actions(self):
        """
        Returns a list of the actions linked with this widget.
        
        :return     [<QAction>, ..]
        """
        return self._actionGroup.actions()
    
    def actionTexts(self):
        """
        Returns a list of the action texts for this widget.
        
        :return     [<str>, ..]
        """
        return map(lambda x: x.text(), self._actionGroup.actions())
    
    def actionGroup( self ):
        """
        Returns the action group linked with this widget.
        
        :return     <QActionGroup>
        """
        return self._actionGroup
    
    def addAction(self, action, checked=None, autoBuild=True):
        """
        Adds the inputed action to this widget's action group.  This will auto-\
        create a new group if no group is already defined.
        
        :param      action | <QAction> || <str>
        
        :return     <QAction>
        """
        # clear the holder
        actions = self._actionGroup.actions()
        if actions and actions[0].objectName() == 'place_holder':
            self._actionGroup.removeAction(actions[0])
            actions[0].deleteLater()
        
        # create an action from the name
        if not isinstance(action, QAction):
            action_name = str(action)
            action = QAction(action_name, self)
            action.setObjectName(action_name)
            action.setCheckable(self.isCheckable())
            
            # auto-check the first option
            if checked or (not self._actionGroup.actions() and checked is None):
                action.setChecked(True)
        
        self._actionGroup.addAction(action)
        
        if autoBuild:
            self.rebuild()
        
        return action
    
    def clear(self, autoBuild=True):
        """
        Clears the actions for this widget.
        """
        for action in self._actionGroup.actions():
            self._actionGroup.removeAction(action)
        
        action = QAction('', self)
        action.setObjectName('place_holder')
#        self._currentAction = None
        self._actionGroup.addAction(action)
        if autoBuild:
            self.rebuild()
    
    def cornerRadius( self ):
        """
        Returns the corner radius for this widget.
        
        :return     <int>
        """
        return self._cornerRadius
    
    def count(self):
        """
        Returns the number of actions associated with this button.
        
        :return     <int>
        """
        actions = self._actionGroup.actions()
        if len(actions) == 1 and actions[0].objectName() == 'place_holder':
            return 0
        
        return len(actions)
    
    def currentAction( self ):
        """
        Returns the action that is currently checked in the system.
        
        :return     <QAction> || None
        """
        return self._actionGroup.checkedAction()
    
    def direction( self ):
        """
        Returns the direction for this widget.
        
        :return     <QBoxLayout::Direction>
        """
        return self.layout().direction()
    
    def emitClicked(self):
        """
        Emits the clicked signal whenever any of the actions are clicked.
        """
        if not self.signalsBlocked():
            self.clicked.emit()
    
    def emitHovered(self, action):
        """
        Emits the hovered action for this widget.
        
        :param      action | <QAction>
        """
        if not self.signalsBlocked():
            self.hovered.emit(action)
    
    def emitTriggered(self, action):
        """
        Emits the triggered action for this widget.
        
        :param      action | <QAction>
        """
#        if action != self._currentAction:
#            self._currentAction = action
#            self.currentActionChanged.emit(action)
        
#        self._currentAction = action
        if not self.signalsBlocked():
            self.triggered.emit(action)
    
    def findAction( self, text ):
        """
        Looks up the action based on the inputed text.
        
        :return     <QAction> || None
        """
        for action in self.actionGroup().actions():
            if ( text in (action.objectName(), action.text()) ):
                return action
        return None
    
    def isCheckable(self):
        """
        Returns whether or not the actions within this button should be
        checkable.
        
        :return     <bool>
        """
        return self._checkable
    
    def padding( self ):
        """
        Returns the button padding amount for this widget.
        
        :return     <int>
        """
        return self._padding
    
    def rebuild( self ):
        """
        Rebuilds the user interface buttons for this widget.
        """
        self.setUpdatesEnabled(False)
        
        # sync up the toolbuttons with our actions
        actions = self._actionGroup.actions()
        btns    = self.findChildren(QToolButton)
        horiz   = self.direction() in (QBoxLayout.LeftToRight, 
                                       QBoxLayout.RightToLeft)
        
        # remove unnecessary buttons
        if len(actions) < len(btns):
            rem_btns = btns[len(actions)-1:]
            btns = btns[:len(actions)]
            
            for btn in rem_btns:
                btn.close()
                btn.setParent(None)
                btn.deleteLater()
        
        # create new buttons
        elif len(btns) < len(actions):
            for i in range(len(btns), len(actions)):
                btn = QToolButton(self)
                btn.setAutoFillBackground(True)
                btns.append(btn)
                self.layout().addWidget(btn)
                
                btn.clicked.connect(self.emitClicked)
        
        # determine coloring options
        palette      = self.palette()
        checked      = palette.color(palette.Highlight)
        checked_fg   = palette.color(palette.HighlightedText)
        unchecked    = palette.color(palette.Button)
        unchecked_fg = palette.color(palette.ButtonText)
        border       = palette.color(palette.Mid)
        
        # define the stylesheet options
        options = {}
        options['top_left_radius']      = 0
        options['top_right_radius']     = 0
        options['bot_left_radius']      = 0
        options['bot_right_radius']     = 0
        options['border_color']         = border.name()
        options['checked_fg']           = checked_fg.name()
        options['checked_bg']           = checked.name()
        options['checked_bg_alt']       = checked.darker(120).name()
        options['unchecked_fg']         = unchecked_fg.name()
        options['unchecked_bg']         = unchecked.name()
        options['unchecked_bg_alt']     = unchecked.darker(120).name()
        options['padding_top']          = 1
        options['padding_bottom']       = 1
        options['padding_left']         = 1
        options['padding_right']        = 1
        
        if horiz:
            options['x1'] = 0
            options['y1'] = 0
            options['x2'] = 0
            options['y2'] = 1
        else:
            options['x1'] = 0
            options['y1'] = 0
            options['x2'] = 1
            options['y2'] = 1
        
        # sync up the actions and buttons
        count = len(actions)
        palette = self.palette()
        font = self.font()
        for i, action in enumerate(actions):
            btn = btns[i]
            
            # assign the action for this button
            if btn.defaultAction() != action:
                # clear out any existing actions
                for act in btn.actions():
                    btn.removeAction(act)
                
                # assign the given action
                btn.setDefaultAction(action)
            
            options['top_left_radius']  = 1
            options['bot_left_radius']  = 1
            options['top_right_radius'] = 1
            options['bot_right_radius'] = 1
            
            if horiz:
                options['padding_left']     = self._padding
                options['padding_right']    = self._padding
            else:
                options['padding_top']      = self._padding
                options['padding_bottom']   = self._padding
            
            if not i:
                if horiz:
                    options['top_left_radius'] = self.cornerRadius()
                    options['bot_left_radius'] = self.cornerRadius()
                    options['padding_left']    += self.cornerRadius() / 3.0
                else:
                    options['top_left_radius'] = self.cornerRadius()
                    options['top_right_radius'] = self.cornerRadius()
                    options['padding_top']     += self.cornerRadius() / 3.0
                    
            if i == count - 1:
                if horiz:
                    options['top_right_radius'] = self.cornerRadius()
                    options['bot_right_radius'] = self.cornerRadius()
                    options['padding_right']    += self.cornerRadius() / 3.0
                else:
                    options['bot_left_radius']  = self.cornerRadius()
                    options['bot_right_radius'] = self.cornerRadius()
                    options['padding_bottom']   += self.cornerRadius() / 3.0
            
            btn.setFont(font)
            btn.setPalette(palette)
            btn.setStyleSheet(TOOLBUTTON_STYLE % options)
            if horiz:
                btn.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Preferred)
            else:
                btn.setSizePolicy(QSizePolicy.Preferred, QSizePolicy.Expanding)
        
        self.setUpdatesEnabled(True)
    
    def setActions(self, actions):
        """
        Sets the actions for this widget to th inputed list of actions.
        
        :param      [<QAction>, ..]
        """
        self.clear(autoBuild=False)
        for action in actions:
            self.addAction(action, autoBuild=False)
        self.rebuild()
    
    def setActionTexts(self, names):
        """
        Convenience method for auto-generating actions based on text names,
        sets the list of actions for this widget to the inputed list of names.
        
        :param      names | [<str>, ..]
        """
        self.setActions(names)
    
    def setActionGroup( self, actionGroup ):
        """
        Sets the action group for this widget to the inputed action group.
        
        :param      actionGroup | <QActionGroup>
        """
        self._actionGroup = actionGroup
        self.rebuild()
    
    def setCheckable(self, state):
        """
        Sets whether or not the actions within this button should be checkable.
        
        :param      state | <bool>
        """
        self._checkable = state
        for act in self._actionGroup.actions():
            act.setCheckable(state)
    
    def setCornerRadius( self, radius ):
        """
        Sets the corner radius value for this widget to the inputed radius.
        
        :param      radius | <int>
        """
        self._cornerRadius = radius
    
    def setCurrentAction(self, action):
        """
        Sets the current action for this button to the inputed action.
        
        :param      action | <QAction> || <str>
        """
        self._actionGroup.blockSignals(True)
        for act in self._actionGroup.actions():
            act.setChecked(act == action or act.text() == action)
        self._actionGroup.blockSignals(False)
    
    def setDirection( self, direction ):
        """
        Sets the direction that this group widget will face.
        
        :param      direction | <QBoxLayout::Direction>
        """
        self.layout().setDirection(direction)
        self.rebuild()
    
    def setFont(self, font):
        """
        Sets the font for this widget and propogates down to the buttons.
        
        :param      font | <QFont>
        """
        super(XSplitButton, self).setFont(font)
        self.rebuild()
    
    def setPadding( self, padding ):
        """
        Sets the padding amount for this widget's button set.
        
        :param      padding | <int>
        """
        self._padding = padding
        self.rebuild()
    
    def setPalette(self, palette):
        """
        Rebuilds the buttons for this widget since they use specific palette
        options.
        
        :param      palette | <QPalette>
        """
        super(XSplitButton, self).setPalette(palette)
        self.rebuild()
    
    def sizeHint(self):
        """
        Returns the base size hint for this widget.
        
        :return     <QSize>
        """
        return QSize(35, 22)
    
    x_actionTexts = Property(QStringList, actionTexts, setActionTexts)
    x_checkable   = Property(bool, isCheckable, setCheckable)
Esempio n. 2
0
class XViewPanelMenu(XMenu):
    _instance = None
    
    def __init__(self, viewWidget, interfaceMode = False):
        # initialize the super class
        super(XViewPanelMenu, self).__init__( viewWidget )
        
        # define custom properties
        self._viewWidget    = viewWidget
        self._currentPanel  = None
        self._interfaceMenu = None
        self._groupingMenu  = None
        self._panelGroup    = QActionGroup(self)
        self._groupingGroup = QActionGroup(self)
        
        # initialize the menu
        if not interfaceMode:
            self.setTitle('Panel Options')
            
            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)
            
            act = self.addAction('Split Panel Top/Bottom')
            act.setIcon(QIcon(resources.find('img/view/split_vertical.png')))
            act.triggered.connect(self.splitVertical)
            
            self.addSeparator()
            
            act = self.addAction('Add Panel')
            act.setIcon(QIcon(resources.find('img/view/add.png')))
            act.triggered.connect(self.addPanel)
            
            # create pane options menu
            self._interfaceMenu = XViewPanelMenu(viewWidget, True)
            self.addMenu(self._interfaceMenu)
            
            menu = self.addMenu('Switch Panels')
            menu.setIcon(QIcon(resources.find('img/view/switch.png')))
            
            act = menu.addAction('Move Up')
            act.setIcon(QIcon(resources.find('img/view/up.png')))
            act = menu.addAction('Move Down')
            act.setIcon(QIcon(resources.find('img/view/down.png')))
            menu.addSeparator()
            act = menu.addAction('Move Left')
            act.setIcon(QIcon(resources.find('img/view/left.png')))
            act = menu.addAction('Move Right')
            act.setIcon(QIcon(resources.find('img/view/right.png')))
            
            menu.triggered.connect(self.switchPanels)
            
            set_tab_menu = self.addMenu('Switch View')
            for viewType in viewWidget.viewTypes():
                act = set_tab_menu.addAction(viewType.viewName())
                act.setIcon(QIcon(viewType.viewIcon()))
                act.setCheckable(True)
                self._panelGroup.addAction(act)
            set_tab_menu.triggered.connect( self.swapTabType )
            
            self.addSeparator()
            
            act = self.addAction('Close Panel (Closes All Tabs)')
            act.setIcon(QIcon(resources.find('img/view/close.png')))
            act.triggered.connect( self.closePanel )
            
            act = self.addAction('Close All Panels (Clears Dashboard)')
            act.setIcon(QIcon(resources.find('img/view/reset.png')))
            act.triggered.connect( self.reset )
            
            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.addSection('Views')
            
            # 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(qt.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(qt.wrapVariant(i))
                act.setCheckable(True)
                
                self._groupingGroup.addAction(act)
            
            self._groupingMenu.triggered.connect(self.assignGroup)
            
        else:
            self.setTitle( 'Add View' )
            
            for viewType in viewWidget.viewTypes():
                act = self.addAction(viewType.viewName())
                act.setIcon(QIcon(viewType.viewIcon()))
                
            self.triggered.connect( self.addView )
            self.addSeparator()
    
    def addPanel( self ):
        panel = self.currentPanel()
        if panel is not None:
            return panel.addPanel()
        return None
    
    def addView( self, action ):
        panel = self.currentPanel()
        
        if ( panel is None ):
            return False
        
        viewType = self._viewWidget.viewType(action.text())
        return panel.addView(viewType)
    
    def assignGroup( self, action ):
        """
        Assigns the group for the given action to the current view.
        
        :param      action | <QAction>
        """
        grp  = qt.unwrapVariant(action.data())
        view = self._currentPanel.currentView()
        view.setViewingGroup(grp)
    
    def closePanel( self ):
        """
        Closes the current panel within the view widget.
        """
        panel = self.currentPanel()
        if panel is not None:
            return panel.closePanel()
        return False
    
    def closeView( self ):
        """
        Closes the current view within the panel.
        """
        panel = self.currentPanel()
        if ( panel is not None ):
            return panel.closeView()
        return False
    
    def currentPanel( self ):
        """
        Returns the current panel widget.
        
        :return     <XViewPanel>
        """
        return self._currentPanel
    
    def detachPanel( self ):
        """
        Detaches the current panel as a floating window.
        """
        #from projexui.widgets.xviewwidget import XViewDialog
        
        dlg = XViewDialog(self._viewWidget, self._viewWidget.viewTypes())
        size = self._currentPanel.size()
        dlg.viewWidget().currentPanel().snagViewFromPanel(self._currentPanel)
        dlg.resize(size)
        dlg.show()
    
    def detachPanelCopy( self ):
        """
        Detaches the current panel as a floating window.
        """
        #from projexui.widgets.xviewwidget import XViewDialog
        
        dlg = XViewDialog(self._viewWidget, self._viewWidget.viewTypes())
        size = self._currentPanel.size()
        view = self._currentPanel.currentView()
        
        # duplicate the current view
        if ( view ):
            new_view = view.duplicate(dlg.viewWidget().currentPanel())
            view_widget = dlg.viewWidget()
            view_panel = view_widget.currentPanel()
            view_panel.addTab(new_view, new_view.windowTitle())
            
        dlg.resize(size)
        dlg.show()
        
    def gotoNext( self ):
        """
        Goes to the next panel tab.
        """
        index = self._currentPanel.currentIndex() + 1
        if ( self._currentPanel.count() == index ):
            index = 0
            
        self._currentPanel.setCurrentIndex(index)
    
    def gotoPrevious( self ):
        """
        Goes to the previous panel tab.
        """
        index = self._currentPanel.currentIndex() - 1
        if ( index < 0 ):
            index = self._currentPanel.count() - 1
        
        self._currentPanel.setCurrentIndex(index)
    
    def newPanelTab( self ):
        """
        Creates a new panel with a copy of the current widget.
        """
        view = self._currentPanel.currentView()
        
        # duplicate the current view
        if ( view ):
            new_view = view.duplicate(self._currentPanel)
            self._currentPanel.addTab(new_view, new_view.windowTitle())
    
    def renamePanel( self ):
        """
        Prompts the user for a custom name for the current panel tab.
        """
        index = self._currentPanel.currentIndex()
        title = self._currentPanel.tabText(index)
        
        new_title, accepted = QInputDialog.getText( self,
                                                    'Rename Tab',
                                                    'Name:',
                                                    QLineEdit.Normal,
                                                    title )
        
        if ( accepted ):
            widget = self._currentPanel.currentView()
            widget.setWindowTitle(new_title)
            self._currentPanel.setTabText(index, new_title)
    
    def reset( self ):
        """
        Clears the current views from the system
        """
        self._viewWidget.reset()
    
    def setCurrentPanel( self, panel ):
        self._currentPanel = 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 == qt.unwrapVariant(act.data()))
        self._groupingGroup.blockSignals(False)
        
        if ( self._groupingMenu ):
            self._groupingMenu.setEnabled(grp != -1)
        
        if ( self._interfaceMenu ):
            self._interfaceMenu.setCurrentPanel(panel)
        
    def splitVertical( self ):
        panel = self.currentPanel()
        if ( panel is not None ):
            return panel.splitVertical()
        return None
        
    def splitHorizontal( self ):
        panel = self.currentPanel()
        if ( panel is not None ):
            return panel.splitHorizontal()
        return None
    
    def switchPanels(self, action):
        direction = action.text()
        
        if direction in ('Move Up', 'Move Down'):
            orientation = Qt.Vertical
        else:
            orientation = Qt.Horizontal
        
        widget = self.currentPanel()
        if not widget:
            return
        
        # look up the splitter for the widget
        splitter = widget.parent()
        while widget and isinstance(splitter, QSplitter):
            if splitter.orientation() == orientation:
                break
            
            widget = splitter
            splitter = splitter.parent()
        
        if not isinstance(splitter, QSplitter):
            return
        
        # determine the new location for the panel
        index = splitter.indexOf(widget)
        if direction in ('Move Down', 'Move Right'):
            new_index = index
            widget    = splitter.widget(index + 1)
        else:
            new_index = index - 1
        
        if widget and 0 <= new_index and new_index < splitter.count():
            splitter.insertWidget(new_index, widget)
    
    def swapTabType( self, action ):
        """
        Swaps the current tab view for the inputed action's type.
        
        :param      action | <QAction>
        """
        # for a new tab, use the add tab slot
        if not self._currentPanel.count():
            self.addView(action)
            return
        
        # make sure we're not trying to switch to the same type
        viewType = self._viewWidget.viewType(action.text())
        view = self._currentPanel.currentView()
        if ( type(view) == viewType ):
            return
        
        # create a new view and close the old one
        self._currentPanel.setUpdatesEnabled(False)
        
        # create the new view
        new_view    = viewType.createInstance(self._currentPanel)
        index       = self._currentPanel.currentIndex()
        
        # cleanup the view
        view.destroyInstance(view)
        
        # add the new view
        self._currentPanel.insertTab(index - 1,
                                     new_view, 
                                     new_view.windowTitle())
        
        self._currentPanel.setUpdatesEnabled(True)