itemctrl.py :  » Project-Management » Task-Coach » TaskCoach-1.0.3 » taskcoachlib » widgets » Python Open Source

Home
Python Open Source
1.3.1.2 Python
2.Ajax
3.Aspect Oriented
4.Blog
5.Build
6.Business Application
7.Chart Report
8.Content Management Systems
9.Cryptographic
10.Database
11.Development
12.Editor
13.Email
14.ERP
15.Game 2D 3D
16.GIS
17.GUI
18.IDE
19.Installer
20.IRC
21.Issue Tracker
22.Language Interface
23.Log
24.Math
25.Media Sound Audio
26.Mobile
27.Network
28.Parser
29.PDF
30.Project Management
31.RSS
32.Search
33.Security
34.Template Engines
35.Test
36.UML
37.USB Serial
38.Web Frameworks
39.Web Server
40.Web Services
41.Web Unit
42.Wiki
43.Windows
44.XML
Python Open Source » Project Management » Task Coach 
Task Coach » TaskCoach 1.0.3 » taskcoachlib » widgets » itemctrl.py
'''
Task Coach - Your friendly task manager
Copyright (C) 2004-2009 Frank Niessink <frank@niessink.com>
Copyright (C) 2007-2008 Jerome Laheurte <fraca7@free.fr>

Task Coach is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

Task Coach is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program.  If not, see <http://www.gnu.org/licenses/>.
'''

''' Base classes for controls with items, such as ListCtrl, TreeCtrl, 
    and TreeListCtrl. ''' # pylint: disable-msg=W0105


import wx, wx.lib.mixins.listctrl, draganddrop, autowidth, tooltip
from taskcoachlib.thirdparty import hypertreelist


class _CtrlWithItemsMixin(object):
    ''' Base class for controls with items, such as ListCtrl, TreeCtrl,
        TreeListCtrl, etc. '''

    def _itemIsOk(self, item):
        try:
            return item.IsOk()          # for Tree(List)Ctrl
        except AttributeError:
            return item != wx.NOT_FOUND # for ListCtrl
        
    def _objectBelongingTo(self, item):
        if not self._itemIsOk(item):
            return None
        try:
            return self.GetItemPyData(item) # TreeListCtrl
        except AttributeError:
            return self.getItemWithIndex(item) # ListCtrl

    def SelectItem(self, item, *args, **kwargs):
        try:
            # Tree(List)Ctrl:
            super(_CtrlWithItemsMixin, self).SelectItem(item, *args, **kwargs)
        except AttributeError:
            # ListCtrl:
            select = kwargs.get('select', True)
            newState = wx.LIST_STATE_SELECTED
            if not select:
                newState = ~newState
            self.SetItemState(item, newState, wx.LIST_STATE_SELECTED)


class _CtrlWithPopupMenuMixin(_CtrlWithItemsMixin):
    ''' Base class for controls with popupmenu's. '''
    
    @staticmethod
    def _attachPopupMenu(eventSource, eventTypes, eventHandler):
        for eventType in eventTypes:
            eventSource.Bind(eventType, eventHandler)


class _CtrlWithItemPopupMenuMixin(_CtrlWithPopupMenuMixin):
    ''' Popupmenu's on items. '''

    def __init__(self, *args, **kwargs):
        self.__popupMenu = kwargs.pop('itemPopupMenu')
        super(_CtrlWithItemPopupMenuMixin, self).__init__(*args, **kwargs)
        if self.__popupMenu is not None:
            self._attachPopupMenu(self,
                (wx.EVT_TREE_ITEM_RIGHT_CLICK, wx.EVT_CONTEXT_MENU), 
                self.onItemPopupMenu)

    def onItemPopupMenu(self, event):
        # Make sure the window this control is in has focus:
        try:
            window = event.GetEventObject().MainWindow
        except AttributeError:
            window = event.GetEventObject()
        window.SetFocus()
        if hasattr(event, 'GetPoint'):
            # Make sure the item under the mouse is selected because that
            # is what users expect and what is most user-friendly. Not all
            # widgets do this by default, e.g. the TreeListCtrl does not.
            item = self.HitTest(event.GetPoint())[0]
            if not self._itemIsOk(item):
                return
            if not self.IsSelected(item):
                self.UnselectAll()
                self.SelectItem(item)
        self.PopupMenu(self.__popupMenu)


class _CtrlWithColumnPopupMenuMixin(_CtrlWithPopupMenuMixin):
    ''' This class enables a right-click popup menu on column headers. The 
        popup menu should expect a public property columnIndex to be set so 
        that the control can tell the menu which column the user clicked to
        popup the menu. '''
    
    def __init__(self, *args, **kwargs):
        self.__popupMenu = kwargs.pop('columnPopupMenu')
        super(_CtrlWithColumnPopupMenuMixin, self).__init__(*args, **kwargs)
        if self.__popupMenu is not None:
            self._attachPopupMenu(self, [wx.EVT_LIST_COL_RIGHT_CLICK],
                self.onColumnPopupMenu)
            
    def onColumnPopupMenu(self, event):
        # We store the columnIndex in the menu, because it's near to 
        # impossible for commands in the menu to determine on what column the
        # menu was popped up.
        columnIndex = event.GetColumn()
        self.__popupMenu.columnIndex = columnIndex
        # Because right-clicking on column headers does not automatically give
        # focus to the control, we force the focus:
        event.GetEventObject().MainWindow.SetFocus()
        self.PopupMenu(self.__popupMenu, event.GetPosition())
        event.Skip()
        

class _CtrlWithDropTargetMixin(_CtrlWithItemsMixin):
    ''' Control that accepts files, e-mails or URLs being dropped onto items. '''
    
    def __init__(self, *args, **kwargs):
        self.__onDropURLCallback = kwargs.pop('onDropURL', None)
        self.__onDropFilesCallback = kwargs.pop('onDropFiles', None)
        self.__onDropMailCallback = kwargs.pop('onDropMail', None)
        super(_CtrlWithDropTargetMixin, self).__init__(*args, **kwargs)
        if self.__onDropURLCallback or self.__onDropFilesCallback or self.__onDropMailCallback:
            dropTarget = draganddrop.DropTarget(self.onDropURL,
                                                self.onDropFiles,
                                                self.onDropMail,
                                                self.onDragOver)
            self.GetMainWindow().SetDropTarget(dropTarget)

    def onDropURL(self, x, y, url):
        item = self.HitTest((x, y))[0]
        if self.__onDropURLCallback:
            self.__onDropURLCallback(self._objectBelongingTo(item), url)

    def onDropFiles(self, x, y, filenames):
        item = self.HitTest((x, y))[0]
        if self.__onDropFilesCallback:
            self.__onDropFilesCallback(self._objectBelongingTo(item), filenames)

    def onDropMail(self, x, y, mail):
        item = self.HitTest((x, y))[0]
        if self.__onDropMailCallback:
            self.__onDropMailCallback(self._objectBelongingTo(item), mail)

    def onDragOver(self, x, y, defaultResult):
        item, flags = self.HitTest((x, y))[:2]
        if self._itemIsOk(item):
            if flags & wx.TREE_HITTEST_ONITEMBUTTON:
                self.Expand(item)
        return defaultResult

    def GetMainWindow(self):
        try:
            return super(_CtrlWithDropTargetMixin, self).GetMainWindow()
        except AttributeError:
            return self


class CtrlWithToolTipMixin(_CtrlWithItemsMixin, tooltip.ToolTipMixin):
    ''' Control that has a different tooltip for each item '''
    def __init__(self, *args, **kwargs):
        super(CtrlWithToolTipMixin, self).__init__(*args, **kwargs)
        self.__tip = tooltip.SimpleToolTip(self)

    def OnBeforeShowToolTip(self, x, y): 
        item, _, column = self.HitTest(wx.Point(x, y))
        domainObject = self._objectBelongingTo(item)
        if domainObject:
            tooltipData = self.getItemTooltipData(domainObject, column)
            doShow = any([data[1] for data in tooltipData])
            if doShow:
                self.__tip.SetData(tooltipData)
                return self.__tip
        return None


class CtrlWithItemsMixin(_CtrlWithItemPopupMenuMixin, _CtrlWithDropTargetMixin):
    pass


class Column(object):
    def __init__(self, name, columnHeader, *eventTypes, **kwargs):
        self.__name = name
        self.__columnHeader = columnHeader
        self.width = kwargs.pop('width', hypertreelist._DEFAULT_COL_WIDTH) # pylint: disable-msg=W0212
        # The event types to use for registering an observer that is
        # interested in changes that affect this column:
        self.__eventTypes = eventTypes
        self.__sortCallback = kwargs.pop('sortCallback', None)
        self.__renderCallback = kwargs.pop('renderCallback',
            self.defaultRenderer)
        self.__renderDescriptionCallback = kwargs.pop('renderDescriptionCallback',
            self.defaultDescriptionRenderer)
        self.__resizeCallback = kwargs.pop('resizeCallback', None)
        self.__alignment = kwargs.pop('alignment', wx.LIST_FORMAT_LEFT)
        self.__imageIndexCallback = kwargs.pop('imageIndexCallback', 
            self.defaultImageIndex)
        # NB: because the header image is needed for sorting a fixed header
        # image cannot be combined with a sortable column
        self.__headerImageIndex = kwargs.pop('headerImageIndex', -1)
        
    def name(self):
        return self.__name
        
    def header(self):
        return self.__columnHeader
    
    def headerImageIndex(self):
        return self.__headerImageIndex

    def eventTypes(self):
        return self.__eventTypes
    
    def setWidth(self, width):
        self.width = width
        if self.__resizeCallback:
            self.__resizeCallback(self, width)

    def sort(self, *args, **kwargs):
        if self.__sortCallback:
            self.__sortCallback(*args, **kwargs)
        
    def render(self, *args, **kwargs):
        return self.__renderCallback(*args, **kwargs)

    def renderDescription(self, *args, **kwargs):
        return self.__renderDescriptionCallback(*args, **kwargs)

    def defaultRenderer(self, *args, **kwargs): # pylint: disable-msg=W0613
        return unicode(args[0])

    def defaultDescriptionRenderer(self, *args, **kwargs): # pylint: disable-msg=W0613
        return None

    def alignment(self):
        return self.__alignment
    
    def defaultImageIndex(self, *args, **kwargs): # pylint: disable-msg=W0613
        return -1
    
    def imageIndex(self, *args, **kwargs):
        return self.__imageIndexCallback(*args, **kwargs)
        
    def __eq__(self, other):
        return self.name() == other.name()
        

class _BaseCtrlWithColumnsMixin(object):
    ''' A base class for all controls with columns. Note that this class and 
        its subclasses do not support addition or deletion of columns after 
        the initial setting of columns. '''

    def __init__(self, *args, **kwargs):
        self.__allColumns = kwargs.pop('columns')
        super(_BaseCtrlWithColumnsMixin, self).__init__(*args, **kwargs)
        # This  is  used to  keep  track  of  which column  has  which
        # index. The only  other way would be (and  was) find a column
        # using its header, which causes problems when several columns
        # have the same header. It's a list of (index, column) tuples.
        self.__indexMap = []
        self._setColumns()

    def _setColumns(self):
        for columnIndex, column in enumerate(self.__allColumns):
            self._insertColumn(columnIndex, column)
            
    def _insertColumn(self, columnIndex, column):
        newMap = []
        for colIndex, col in self.__indexMap:
            if colIndex >= columnIndex:
                newMap.append((colIndex + 1, col))
            else:
                newMap.append((colIndex, col))
        newMap.append((columnIndex, column))
        self.__indexMap = newMap

        self.InsertColumn(columnIndex, column.header(), 
            format=column.alignment(), width=column.width)

        columnInfo = self.GetColumn(columnIndex)
        columnInfo.SetImage(column.headerImageIndex())
        self.SetColumn(columnIndex, columnInfo)

    def _deleteColumn(self, columnIndex):
        newMap = []
        for colIndex, col in self.__indexMap:
            if colIndex > columnIndex:
                newMap.append((colIndex - 1, col))
            elif colIndex < columnIndex:
                newMap.append((colIndex, col))
        self.__indexMap = newMap
        self.DeleteColumn(columnIndex)

    def _allColumns(self):
        return self.__allColumns

    def _getColumn(self, columnIndex):
        for colIndex, col in self.__indexMap:
            if colIndex == columnIndex:
                return col
        raise IndexError
   
    def _getColumnHeader(self, columnIndex):
        ''' The currently displayed column header in the column with index 
            columnIndex. '''
        return self.GetColumn(columnIndex).GetText()

    def _getColumnIndex(self, column):
        ''' The current column index of the column 'column'. '''
        try:
            return self.__allColumns.index(column) # Uses overriden __eq__
        except ValueError:
            raise ValueError, '%s: unknown column' % column.name()

        
class _CtrlWithHideableColumnsMixin(_BaseCtrlWithColumnsMixin):        
    ''' This class supports hiding columns. '''
    
    def showColumn(self, column, show=True):
        ''' showColumn shows or hides the column for column. 
            The column is actually removed or inserted into the control because 
            although TreeListCtrl supports hiding columns, ListCtrl does not. 
            '''
        columnIndex = self._getColumnIndex(column)
        if show and not self.isColumnVisible(column):
            self._insertColumn(columnIndex, column)
        elif not show and self.isColumnVisible(column):
            self._deleteColumn(columnIndex)

    def isColumnVisible(self, column):
        return column in self._visibleColumns()

    def _getColumnIndex(self, column):
        ''' _getColumnIndex returns the actual columnIndex of the column if it 
            is visible, or the position it would have if it were visible. '''
        columnIndexWhenAllColumnsVisible = super(_CtrlWithHideableColumnsMixin, self)._getColumnIndex(column)
        for columnIndex, visibleColumn in enumerate(self._visibleColumns()):
            if super(_CtrlWithHideableColumnsMixin, self)._getColumnIndex(visibleColumn) >= columnIndexWhenAllColumnsVisible:
                return columnIndex
        return self.GetColumnCount() # Column header not found

    def _visibleColumns(self):
        return [self._getColumn(columnIndex) for columnIndex in range(self.GetColumnCount())]


class _CtrlWithSortableColumnsMixin(_BaseCtrlWithColumnsMixin):
    ''' This class adds sort indicators and clickable column headers that 
        trigger callbacks to (re)sort the contents of the control. '''
    
    def __init__(self, *args, **kwargs):
        super(_CtrlWithSortableColumnsMixin, self).__init__(*args, **kwargs)
        self.Bind(wx.EVT_LIST_COL_CLICK, self.onColumnClick)
        self.__currentSortColumn = self._getColumn(0)
        self.__currentSortImageIndex = -1
                
    def onColumnClick(self, event):
        event.Skip()
        # Make sure the window this control is in has focus:
        event.GetEventObject().MainWindow.SetFocus()
        columnIndex = event.GetColumn()
        if 0 <= columnIndex < self.GetColumnCount():
            column = self._getColumn(columnIndex)
            # Use CallAfter to make sure the window this control is in is
            # activated before we process the column click:
            wx.CallAfter(column.sort, event)
        
    def showSortColumn(self, column):
        if column != self.__currentSortColumn:
            self._clearSortImage()
        self.__currentSortColumn = column
        self._showSortImage()

    def showSortOrder(self, imageIndex):
        self.__currentSortImageIndex = imageIndex
        self._showSortImage()
                
    def _clearSortImage(self):
        self.__setSortColumnImage(-1)
    
    def _showSortImage(self):
        self.__setSortColumnImage(self.__currentSortImageIndex)
            
    def _currentSortColumn(self):
        return self.__currentSortColumn
        
    def __setSortColumnImage(self, imageIndex):
        columnIndex = self._getColumnIndex(self.__currentSortColumn)
        columnInfo = self.GetColumn(columnIndex)
        if columnInfo.GetImage() == imageIndex:
            pass # The column is already showing the right image, so we're done
        else:
            columnInfo.SetImage(imageIndex)
            self.SetColumn(columnIndex, columnInfo)


class _CtrlWithAutoResizedColumnsMixin(autowidth.AutoColumnWidthMixin):
    def __init__(self, *args, **kwargs):
        super(_CtrlWithAutoResizedColumnsMixin, self).__init__(*args, **kwargs)
        self.Bind(wx.EVT_LIST_COL_END_DRAG, self.onEndColumnResize)
        
    def onEndColumnResize(self, event):
        ''' Save the column widths after the user did a resize. '''
        for index, column in enumerate(self._visibleColumns()):
            column.setWidth(self.GetColumnWidth(index))
        event.Skip()
        

class CtrlWithColumnsMixin(_CtrlWithAutoResizedColumnsMixin, 
                           _CtrlWithHideableColumnsMixin,
                           _CtrlWithSortableColumnsMixin, 
                           _CtrlWithColumnPopupMenuMixin):
    ''' CtrlWithColumnsMixin combines the functionality of its four parent 
        classes: automatic resizing of columns, hideable columns, columns with 
        sort indicators, and column popup menu's. '''
        
    def showColumn(self, column, show=True):
        super(CtrlWithColumnsMixin, self).showColumn(column, show)
        # Show sort indicator if the column that was just made visible is being sorted on
        if show and column == self._currentSortColumn():
            self._showSortImage()
            
    def _clearSortImage(self):
        # Only clear the sort image if the column in question is visible
        if self.isColumnVisible(self._currentSortColumn()):
            super(CtrlWithColumnsMixin, self)._clearSortImage()
            
    def _showSortImage(self):
        # Only show the sort image if the column in question is visible
        if self.isColumnVisible(self._currentSortColumn()):
            super(CtrlWithColumnsMixin, self)._showSortImage()
            

www.java2java.com | Contact Us
Copyright 2009 - 12 Demo Source and Support. All rights reserved.
All other trademarks are property of their respective owners.