'''
 ====================================================================
 Copyright (c) 2003-2006 Barry A Scott.  All rights reserved.

 This software is licensed as described in the file LICENSE.txt,
 which you should have received as part of this distribution.

 ====================================================================

    wb_subversion_list_handler_common.py

'''
import os
import tempfile
import types

import pysvn
import wx

import wb_subversion_history
import wb_subversion_annotate
import wb_list_panel_common
import wb_exceptions
import wb_subversion_utils
import wb_read_file
import wb_subversion_info_dialog
import wb_subversion_properties_dialog
import wb_subversion_diff

col_labels = [
        ('Name',        25, 10, 100, wx.LIST_FORMAT_LEFT),
        ('State',        4,  2,   8, wx.LIST_FORMAT_LEFT),
        ('Date',        12,  4,  20, wx.LIST_FORMAT_RIGHT),
        ('Rev',          4,  2,   8, wx.LIST_FORMAT_RIGHT),
        ('Author',      10,  4,  80, wx.LIST_FORMAT_LEFT),
#        ('Size',        6,  4,  10, wx.LIST_FORMAT_RIGHT),
        ('Mimetype',    10,  6,  50, wx.LIST_FORMAT_LEFT),
        ('EOL',         6,  2,  10, wx.LIST_FORMAT_LEFT),
        ('Type',        4,  4,  10, wx.LIST_FORMAT_LEFT),
        ('Lock Owner',  10,  4,  80, wx.LIST_FORMAT_LEFT),
        ('Lock Comment',60,  10,  100, wx.LIST_FORMAT_LEFT),
        ]

class ColumnInfo:
    def __init__( self, name, width, min_width, max_width, alignment ):
        self.name = name
        self.width = int(width)
        self.min_width = int(min_width)
        self.max_width = int(max_width)
        self.alignment = alignment
        self.included = False
        self.column = 0

class ViewColumnInfo:
    def __init__( self ):
        self.column_info = {}
        self.column_order = []
        for col_info in col_labels:
            name, width, min_width, max_width, alignment = col_info
            self.column_info[ name ] = ColumnInfo( name, width, min_width, max_width, alignment )

    def setFromPreferenceData( self, p ):
        self.setFrom( p.column_order[:], p.column_widths[:] )

    def setFrom( self, column_order, column_widths ):
        # must have Name
        if 'Name' not in column_order:
            column_order.insert( 0, 'Name' )
            column_widths.insert( 0, '0' )

        for index, name in enumerate( column_order ):
            if self.column_info.has_key( name ):
                if len(column_widths) > index:
                    try:
                        width = int(column_widths[index])
                        if( width >= self.column_info[ name ].min_width
                        and width <= self.column_info[ name ].max_width ):
                            self.column_info[ name ].width = width

                    except ValueError:
                        pass

        self.setColumnOrder( column_order )


    def getInfo( self, name ):
        return self.column_info[ name ]

    def excludedInfo( self ):
        return [info for info in self.column_info.values() if not info.included]

    def setColumnOrder( self, column_order ):
        self.column_order = column_order[:]
        for index, name in enumerate( self.column_order ):
            self.column_info[ name ].column = index

    def getColumnOrder( self ):
        return self.column_order

    def getNameByColumn( self, col ):
        return self.column_order[ col ]

    def getInfoByColumn( self, col ):
        return self.column_info[ self.column_order[ col ] ]

    def getColumnWidths( self ):
        return [str(self.column_info[name].width) for name in self.column_order]

class SubversionListHandlerCommon(wb_list_panel_common.ListHandler):
    col_name = 'Name'
    col_state = 'State'
    col_date = 'Date'
    col_revision = 'Rev'
    col_author = 'Author'
    col_type = 'Type'
    col_size = 'Size'
    col_mime_type = 'Mimetype'
    col_eol_style = 'EOL'
    col_lock_owner = 'Lock Owner'
    col_lock_comment = 'Lock Comment'

    def __init__( self, app, list_panel, project_info ):
        wb_list_panel_common.ListHandler.__init__( self, list_panel )
        self.project_info = project_info
        self.app = app

        self.all_files = []

        self.all_item_attr = {}

        self.column_info = ViewColumnInfo()
        self.need_properties = False

        self.is_project_parent = False

        self.__last_wc_path = None

        self.__branch_origin_revision = None
        self.__branch_url = None
        self.__branch_origin_url = None

    def setIsProjectParent( self ):
        self.is_project_parent = True

    def isProjectParent( self ):
        return self.is_project_parent

    def updateStatus( self ):
        self.project_info.updateStatus()

    def getBranchOriginRevision( self ):
        if self.__branch_origin_revision is None:
            try:
                log_entries = self.project_info.client_bg.log(self.project_info.url, discover_changed_paths=True)
            except pysvn.ClientError, e:
                self.app.log_client_error( e )
                return None

            branch_log_entry = log_entries[-1]
            changed_paths = branch_log_entry['changed_paths'][0]

            # If the oldest revision has a copyfrom_path, this is a branch,
            # so we set __branch_origin_url, __branch_url and __branch_origin_revision.
            if changed_paths['copyfrom_path'] is not None:
                base_url = '/'.join(self.project_info.url.split('/')[:4])
                self.__branch_origin_url = base_url + changed_paths['copyfrom_path']
                self.__branch_url = base_url + changed_paths['path']
                self.__branch_origin_revision = changed_paths['copyfrom_revision']

        return self.__branch_origin_revision

    def getBranchOriginUrl( self ):
        self.getBranchOriginRevision()
        return self.__branch_origin_url

    def getBranchUrl( self ):
        self.getBranchOriginRevision()
        return self.__branch_url

    def getProjectInfo( self ):
        return self.project_info

    def setupColumnInfo( self ):
        self.column_info.setFromPreferenceData( self.app.prefs.getView() )

    def setupColumns( self ):
        self.setupColumnInfo()

        g = self.list_panel.list_ctrl

        # should update in place in case the list was used by other code
        while g.GetColumnCount() > 0:
            g.DeleteColumn( 0 )

        need_properties = False

        char_width = 11
        for index, name in enumerate( self.column_info.getColumnOrder() ):
            info = self.column_info.getInfo( name )
            g.InsertColumn( index, info.name, info.alignment, info.width*char_width )
            if name in [self.col_mime_type, self.col_eol_style]:
                need_properties = True

        self.project_info.setNeedProperties( need_properties )

    def initList( self, sort_data, filter_field, filter_text ):
        self.app.trace.info( 'SubversionListHandlerCommon.initList %s', self.project_info.project_name )

        g = self.list_panel.list_ctrl

        self.list_panel.updateHeader( self.project_info.url, self.project_info.wc_path )

        # nothing doing if the wc does not exist
        if self.project_info.need_checkout:
            # empty the list
            g.DeleteAllItems()

            self.__last_wc_path = None
            self.app.trace.info( 'initList no wc_path %s', self.project_info.project_name )
            g.SetItemCount( 1 )
            return

        self.app.log.debug( 'initList - last wc_path %r' % self.__last_wc_path )
        if self.__last_wc_path == self.project_info.wc_path:
            selection_state = self.__saveListSelectionState()
        else:
            self.app.log.debug( 'initList - changed wc_path to %r' % self.project_info.wc_path )
            selection_state = (None, [])

        self.__last_wc_path = self.project_info.wc_path

        # empty the list
        g.DeleteAllItems()

        prefix_len = len( self.project_info.wc_path ) + 1
        if len(filter_text) > 0:
            if filter_field == 'Name':
                self.all_files = [f for f in self.project_info.getFilesStatus()
                            if filter_text in f.path[prefix_len:]]
            elif filter_field == 'Author':
                self.all_files = [f for f in self.project_info.getFilesStatus()
                            if f.entry is not None and filter_text in f.entry.commit_author]
        else:
            self.all_files = self.project_info.getFilesStatus()

        self.all_files.sort( SortList( sort_data, self.project_info ) )

        self.__restoreListSelectionState( selection_state )

    def sortList( self, sort_data ):
        self.app.log.debug('sortList' )

        if self.project_info.need_checkout:
            # nothing to sort
            return

        selection_state = self.__saveListSelectionState()
        self.all_files.sort( SortList( sort_data, self.project_info ) )
        self.__restoreListSelectionState( selection_state )

        self.list_panel.list_ctrl.RefreshItems( 0, len(self.all_files)-1 )

    def __saveListSelectionState( self ):
        row = self.__focusedRow()
        if row is None:
            focused_filename = None
        else:
            focused_filename = self.getFilename( row )

        all_selected_filenames = [self.getFilename( row )
                                    for row in self.list_panel.getSelectedRows()]

        self.app.log.debug( '__saveListSelectionState %r %r' % (focused_filename, all_selected_filenames) )
        return (focused_filename, all_selected_filenames)

    def __restoreListSelectionState( self, (focused_filename, all_selected_filenames) ):
        g = self.list_panel.list_ctrl

        self.app.log.debug( '__restoreListSelectionState %r %r' % (focused_filename, all_selected_filenames) )

        focused_index = 0
        g.SetItemCount( len(self.all_files) )
        for index, status in enumerate( self.all_files ):
            state = 0
            if status.path in all_selected_filenames:
                state = wx.LIST_STATE_SELECTED
            if focused_filename is not None and status.path == focused_filename:
                focused_index = index
                state |= wx.LIST_STATE_FOCUSED
            g.SetItemState( index, state, wx.LIST_STATE_SELECTED|wx.LIST_STATE_FOCUSED )

        if len(self.all_files) > 0:
            g.EnsureVisible( focused_index )

    def __focusedRow( self ):
        g = self.list_panel.list_ctrl
        for index in range( len( self.all_files ) ):
            if g.GetItemState( index, wx.LIST_STATE_FOCUSED ) != 0:
                return index

        return None

    def columnRequired( self, col ):
        return self.column_info.getInfo( col ).included

    def columnIndex( self, col ):
        return self.column_info.getInfo( col ).column

    def getColumnId( self, col ):
        return self.column_info.getColumnOrder()[col]

    def OnGetItemText( self, index, col ):

        prefix_len = len( self.project_info.wc_path ) + 1

        column = self.column_info.getNameByColumn( col )

        if self.project_info.need_checkout:
            if column == self.col_name:
                if self.isProjectParent():
                    return 'Use the Checkout command to fetch files'
                else:
                    return 'Use the Update command to fetch files'
            else:
                return ''

        status = self.all_files[ index ]

        if column == self.col_name:
            value = self.__get_NameColumn( status, prefix_len )
        elif column == self.col_state:
            value = self.__get_StateColumn( status )
        elif column == self.col_date:
            value = self.__get_DateColumn( status )
        elif column == self.col_revision:
            value = self.__get_RevisionColumn( status )
        elif column == self.col_author:
            value = self.__get_AuthorColumn( status )
        elif column == self.col_type:
            value = self.__get_TypeColumn( status )
        elif column == self.col_size:
            value = self.__get_SizeColumn( status )
        elif column == self.col_mime_type:
            value = self.__get_MimeTypeColumn( self.project_info.getProperty( status.path, 'svn:mime-type' ) )
        elif column == self.col_eol_style:
            value = self.__get_EolStyleColumn( self.project_info.getProperty( status.path, 'svn:eol-style' ) )
        elif column == self.col_lock_owner:
            value = self.__get_LockOwnerColumn( status )
        elif column == self.col_lock_comment:
            value = self.__get_LockCommentColumn( status )
        else:
            value = 'Opss'
        return value

    def OnGetItemAttr( self, index ):
        if self.project_info.need_checkout:
            colour = wx.RED
        else:
            colour = self.statusColour( self.all_files[ index ] )

        if colour not in self.all_item_attr:
            attr = wx.ListItemAttr()
            attr.SetTextColour( colour )
            self.all_item_attr[ colour ] = attr

        return self.all_item_attr[ colour ]

    def __get_NameColumn( self, status, prefix_len ):
        if status.entry is None:
            is_dir = os.path.isdir( status.path )
        else:
            is_dir = status.entry.kind == pysvn.node_kind.dir
        if is_dir:
            filename = status.path + os.sep
        else:
            filename = status.path

        return filename[prefix_len:]

    def __get_SizeColumn( self, status ):
        type( status )
        return '%d' % 0

    def __get_EolStyleColumn( self, value ):
        if value is None:
            value = ''

        return value

    def __get_MimeTypeColumn( self, value ):
        if value is None:
            value = ''

        return value

    def __get_StateColumn( self, status ):
        return self.statusFormatString( status )

    def __get_TypeColumn( self, status ):
        if status.entry is None:
            return ''
        else:
            return str(status.entry.kind)

    def __get_AuthorColumn( self, status ):
        if status.entry is None or status.entry.commit_author is None:
            return ''
        else:
            return status.entry.commit_author

    def __get_RevisionColumn( self, status ):
        if status.entry is None or status.entry.commit_revision.number < 0:
            return ''
        else:
            return str(status.entry.commit_revision.number)

    def __get_DateColumn( self, status ):
        if status.entry is None or status.entry.commit_time == 0:
            return ''
        else:
            return wb_subversion_utils.fmtDateTime( status.entry.commit_time )

    def __get_LockCommentColumn( self, status ):
        if status.repos_lock is not None:
            comment = status.repos_lock['comment'].replace( '\n', ' ' )
        else:
            comment = status.entry.lock_comment.replace( '\n', ' ' )
        return comment

    def __get_LockOwnerColumn( self, status ):
        if status.repos_lock is not None:
            comment = status.repos_lock['owner']
        else:
            comment = status.entry.lock_owner
        return comment

    def getState( self, all_rows ):
        if len(all_rows) == 0:
            return None

        state = wb_list_panel_common.ListItemState()

        if self.project_info.need_checkout:
            state.need_checkout = True
            state.ui_project_parent = True
            state.versioned = True
            return state

        state.modified = True
        state.new_versioned = True
        state.versioned = True
        state.unversioned = True
        state.need_checkin = True
        state.conflict = True
        state.file_exists = True

        for row in all_rows:
            filename = self.all_files[ row ].path

            if not os.path.exists( filename ):
                state.file_exists = False

            if os.path.isdir( filename ):
                state.modified = False
                state.conflict = False
                state.file_exists = False

            text_status = self.getTextStatus( row )
            if text_status in [pysvn.wc_status_kind.unversioned]:
                state.versioned = False
            else:
                state.unversioned = False

            state.new_versioned = state.new_versioned and text_status in [pysvn.wc_status_kind.added]

            prop_status = self.getPropStatus( row )
            state.modified = state.modified and (text_status in [pysvn.wc_status_kind.modified,
                                pysvn.wc_status_kind.conflicted]
                        or
                        prop_status in [pysvn.wc_status_kind.modified,
                                pysvn.wc_status_kind.conflicted])
            state.need_checkin = state.need_checkin and (text_status in [pysvn.wc_status_kind.added,
                                    pysvn.wc_status_kind.deleted,
                                    pysvn.wc_status_kind.modified]
                            or
                            prop_status in [pysvn.wc_status_kind.added,
                                    pysvn.wc_status_kind.deleted,
                                    pysvn.wc_status_kind.modified])
            state.conflict = state.conflict and text_status in [pysvn.wc_status_kind.conflicted]

        #state.printState( 'getState' )

        return state

    def getStatusFromRowOrStatus( self, row_or_status ):
        if type(row_or_status) == types.IntType:
            return self.all_files[ row_or_status ]
        return row_or_status

    def mayOpen( self, row_or_status ):
        status = self.getStatusFromRowOrStatus( row_or_status )
        if status.entry is None:
            return not os.path.isdir( self.getFilename( row_or_status ) )
        else:
            return status.entry.kind == pysvn.node_kind.dir

    def getFilename( self, row_or_status ):
        status = self.getStatusFromRowOrStatus( row_or_status )
        return status.path

    def getStatusString( self, row_or_status ):
        status = self.getStatusFromRowOrStatus( row_or_status )
        return wb_subversion_utils._status_format( status )

    def getStatusAndFilenames( self, all_rows ):
        return [(self.getStatusString( row_or_status ), self.getFilename( row_or_status )) for row_or_status in all_rows]

    def isControlled( self, row_or_status ):
        status = self.getStatusFromRowOrStatus( row_or_status )
        return status.entry is not None

    def getUrl( self, row_or_status ):
        status = self.getStatusFromRowOrStatus( row_or_status )
        if status.entry is None:
            return ''
        else:
            return status.entry.url

    def getConflictOld( self, row_or_status ):
        status = self.getStatusFromRowOrStatus( row_or_status )
        if status.entry is None:
            return ''
        else:
            return os.path.join( self.project_info.wc_path, status.entry.conflict_old )

    def getConflictNew( self, row_or_status ):
        status = self.getStatusFromRowOrStatus( row_or_status )
        if status.entry is None:
            return ''
        else:
            return os.path.join( self.project_info.wc_path,
                    status.entry.conflict_new )

    def getConflictMine( self, row_or_status ):
        status = self.getStatusFromRowOrStatus( row_or_status )
        if status.entry is None:
            return ''
        else:
            return os.path.join( self.project_info.wc_path,
                    status.entry.conflict_work )

    def getTextStatus( self, row_or_status ):
        status = self.getStatusFromRowOrStatus( row_or_status )
        return status.text_status

    def getPropStatus( self, row_or_status ):
        status = self.getStatusFromRowOrStatus( row_or_status )
        return status.prop_status

    def statusFormatString( self, status ):
        return wb_subversion_utils._status_format( status )

    def getAllGreyFilenames( self ):
        raise NotImplemented

    def statusColour( self, status ):
        # default colour when nothing special is know for the file
        colour = wx.BLACK

        # show that a file is on the clipboard
        if status.path in self.getAllGreyFilenames():
            colour = wx.Colour( 128, 128, 128 )

        # show that a file is uncontrolled
        elif status.entry is None:
            colour = wx.GREEN

        else:
            # show a file is locked
            if( status.is_locked ):
                colour = wx.RED
            # show a file is modified
            elif( self.getTextStatus( status ) != pysvn.wc_status_kind.normal
            or self.getPropStatus( status ) not in [pysvn.wc_status_kind.normal,pysvn.wc_status_kind.none]
            or status.is_copied or status.is_switched ):
                colour = wx.BLUE

        return colour

    #------------------------------------------------------------
    def Cmd_File_Annotate( self, all_rows ):
        for filename in [self.getFilename( row ) for row in all_rows]:
            self.app.setProgress( 'Annotating %(count)d', 0 )

            self.app.setAction( 'Annotate %s...' % filename )

            yield self.app.backgroundProcess

            ok = False
            try:
                annotation = self.project_info.client_bg.annotate( filename )
                ok = True
            except pysvn.ClientError, e:
                self.app.log_client_error( e )

            yield self.app.foregroundProcess

            if not ok:
                break

            h_frame = wb_subversion_annotate.AnnotateFrame( self.app, self.project_info, filename, annotation )
            h_frame.Show( True )

        self.app.clearProgress()
        self.app.setAction( 'Ready' )

    def Cmd_File_DiffWorkBase( self, all_rows ):
        for filename in [self.getFilename( row ) for row in all_rows]:
            self.app.setAction( 'Diff BASE %s...' % filename )

            info1 = wb_subversion_diff.PathInfoForDiff()

            info1.path = filename
            info1.revision = pysvn.Revision( pysvn.opt_revision_kind.base )
            info1.title = filename + '@BASE'

            info2 = wb_subversion_diff.PathInfoForDiff()

            info2.path = filename
            info2.revision = pysvn.Revision( pysvn.opt_revision_kind.working )
            info2.title = filename

            generator = wb_subversion_diff.subversionDiffFiles(
                                self.app,
                                self.project_info,
                                info1,
                                info2 )
            if type(generator) == types.GeneratorType:
                while True:
                    try:
                        where_to_go_next = generator.next()
                    except StopIteration:
                        # no problem all done
                        break

                    yield where_to_go_next

        self.app.setAction( 'Ready' )

    def Cmd_File_DiffWorkHead( self, all_rows ):
        for filename, url in [(self.getFilename( row ), self.getUrl( row ))
                                for row in all_rows]:

            self.app.setAction( 'Diff HEAD %s...' % filename )

            info1 = wb_subversion_diff.PathInfoForDiff()

            info1.path = filename
            info1.revision = pysvn.Revision( pysvn.opt_revision_kind.head )
            info1.title = filename + '@HEAD'

            info2 = wb_subversion_diff.PathInfoForDiff()

            info2.path = filename
            info2.revision = pysvn.Revision( pysvn.opt_revision_kind.working )
            info2.title = filename

            generator = wb_subversion_diff.subversionDiffFiles(
                                self.app,
                                self.project_info,
                                info1,
                                info2 )
                            
            if type(generator) == types.GeneratorType:
                while True:
                    try:
                        where_to_go_next = generator.next()
                    except StopIteration:
                        # no problem all done
                        break

                    yield where_to_go_next

        self.app.setAction( 'Ready' )

    def Cmd_File_DiffWorkBranchOriginBase( self, all_rows ):
        self.app.setAction( "Retrieving branch info..." )

        yield self.app.backgroundProcess
        branch_url = self.getBranchUrl()
        yield self.app.foregroundProcess

        if branch_url is None:
            wx.MessageBox( '"%s" is not a branch.' % self.project_info.url, "Error", style=wx.OK|wx.ICON_ERROR )
        else:
            branch_origin_url = self.getBranchOriginUrl()

            for filename, url in [(self.getFilename( row ), self.getUrl( row ))
                                    for row in all_rows]:
                origin_url = branch_origin_url + url[len(branch_url):]

                self.app.setAction( 'Diff branch origin BASE %s...' % filename )

                info1 = wb_subversion_diff.PathInfoForDiff()

                info1.path = origin_url
                info1.revision = self.getBranchOriginRevision()
                info1.title = '%s@%d' % (filename, self.getBranchOriginRevision().number)

                info2 = wb_subversion_diff.PathInfoForDiff()

                info2.path = filename
                info2.revision = pysvn.Revision( pysvn.opt_revision_kind.working )
                info2.title = filename

                generator = wb_subversion_diff.subversionDiffFiles(
                                    self.app,
                                    self.project_info,
                                    info1,
                                    info2 )

            if type(generator) == types.GeneratorType:
                while True:
                    try:
                        where_to_go_next = generator.next()
                    except StopIteration:
                        # no problem all done
                        break

                    yield where_to_go_next

        self.app.setAction( 'Ready' )

    def Cmd_File_DiffWorkBranchOriginHead( self, all_rows ):
        self.app.setAction( "Retrieving branch info..." )
        yield self.app.backgroundProcess
        branch_url = self.getBranchUrl()
        yield self.app.foregroundProcess

        if branch_url is None:
            wx.MessageBox( '"%s" is not a branch.' % self.project_info.url, "Error", style=wx.OK|wx.ICON_ERROR )
        else:
            branch_origin_url = self.getBranchOriginUrl()

            for filename, url in [(self.getFilename( row ), self.getUrl( row ))
                                    for row in all_rows]:
                origin_url = branch_origin_url + url[len(branch_url):]

                self.app.setAction( 'Diff branch origin HEAD %s...' % filename )

                info1 = wb_subversion_diff.PathInfoForDiff()

                info1.path = origin_url
                info1.revision = pysvn.Revision( pysvn.opt_revision_kind.head )
                info1.title = '%s@HEAD' % filename

                info2 = wb_subversion_diff.PathInfoForDiff()

                info2.path = filename
                info2.revision = pysvn.Revision( pysvn.opt_revision_kind.working )
                info2.title = filename

                generator = wb_subversion_diff.subversionDiffFiles(
                                    self.app,
                                    self.project_info,
                                    info1,
                                    info2 )

                if type(generator) == types.GeneratorType:
                    while True:
                        try:
                            where_to_go_next = generator.next()
                        except StopIteration:
                            # no problem all done
                            break

                        yield where_to_go_next

        self.app.setAction( 'Ready' )

    def Cmd_File_History( self, all_rows ):
        dialog = wb_subversion_history.HistoryDialog( self.app.frame.tree_panel.tree_ctrl )
        result = dialog.ShowModal()
        if result != wx.ID_OK:
            return

        for filename in [self.getFilename( row ) for row in all_rows]:
            self.app.setAction( 'Log history %s...' % filename )

            yield self.app.backgroundProcess

            ok = False
            try:
                file_url, history_entries = wb_subversion_history.getHistoryEntries(
                            self.project_info,
                            filename,
                            dialog.getLimit(),
                            dialog.getRevisionEnd() )
                ok = True
            except pysvn.ClientError, e:
                self.app.log_client_error( e )

            yield self.app.foregroundProcess

            if not ok:
                break

            h_frame = wb_subversion_history.HistoryFileFrame(
                        self.app,
                        self.project_info,
                        filename,
                        file_url,
                        history_entries )
            h_frame.Show( True )

        self.app.setAction( 'Ready' )

    def Cmd_File_Info( self, all_rows ):
        for filename in [self.getFilename( row ) for row in all_rows]:
            try:
                if hasattr( self.project_info.client_fg, 'info2' ):
                    entry = self.project_info.client_fg.info2( filename, recurse=False )
                else:
                    entry = self.project_info.client_fg.info( filename )

                dialog = wb_subversion_info_dialog.InfoDialog( self.app,
                        self.list_panel.list_ctrl,
                        filename,
                        entry )
                dialog.ShowModal()

            except pysvn.ClientError, e:
                self.app.log_client_error( e )

    def Cmd_File_Lock( self, all_rows ):
        all_filenames = [self.getFilename( row ) for row in all_rows]
        comment, force = self.app.getLockMessage( 'Lock', self.getStatusAndFilenames( all_rows ) )
        if not comment:
            return

        for filename in all_filenames:
            self.app.setProgress( 'Locking %(count)d', 0 )

            self.app.setAction( 'Locking %s...' % filename )

            yield self.app.backgroundProcess

            ok = False
            try:
                self.project_info.client_bg.lock( filename, comment=comment, force=force )
                ok = True
            except pysvn.ClientError, e:
                self.app.log_client_error( e )

            yield self.app.foregroundProcess

            if not ok:
                break

        self.app.clearProgress()
        self.app.setAction( 'Ready' )
        self.app.refreshFrame()

    def Cmd_File_Properties( self, all_rows ):
        client_fg = self.project_info.client_fg

        for filename in [self.getFilename( row ) for row in all_rows]:
            try:
                prop_list = client_fg.proplist( filename,
                        revision=pysvn.Revision( pysvn.opt_revision_kind.working ) )
                if len(prop_list) == 0:
                    prop_dict = {}
                else:
                    _, prop_dict = prop_list[0]
                if os.path.isdir( filename ):
                    dialog = wb_subversion_properties_dialog.DirPropertiesDialog( self.app,
                            self.list_panel.list_ctrl,
                            filename,
                            prop_dict )
                else:
                    dialog = wb_subversion_properties_dialog.FilePropertiesDialog( self.app,
                            self.list_panel.list_ctrl,
                            filename,
                            prop_dict )
                if dialog.ShowModal() == wx.OK:
                    for present, name, value in dialog.getModifiedProperties():
                        if not present:
                            # delete name
                            client_fg.propdel( name, filename )
                        else:
                            # add/update name value
                            client_fg.propset( name, value, filename )

            except pysvn.ClientError, e:
                self.app.log_client_error( e )
                break
            self.app.refreshFrame()

    def Cmd_File_Unlock( self, all_rows ):
        confirmed, force = self.app.confirmForceAction( 'Unlock', self.getStatusAndFilenames( all_rows ) )
        if not confirmed:
            return

        for filename, url in [(self.getFilename( row ), self.getUrl( row )) for row in all_rows]:
            self.app.setProgress( 'Unlocking %(count)d', 0 )

            self.app.setAction( 'Unlocking %s...' % filename )

            yield self.app.backgroundProcess

            ok = False
            try:
                if force:
                    self.project_info.client_bg.unlock( url, force=True )
                else:
                    self.project_info.client_bg.unlock( filename, force=False )
                ok = True
            except pysvn.ClientError, e:
                self.app.log_client_error( e )

            yield self.app.foregroundProcess

            if not ok:
                break

        self.app.clearProgress()
        self.app.setAction( 'Ready' )
        self.app.refreshFrame()

class SortList:
    def __init__( self, sort_data, project_info ):
        self.sort_data = sort_data
        self.project_info = project_info

    def __call__( self, a, b ):
        # called to cmp
        a_field = self.getField( a, self.sort_data.getField() )
        b_field = self.getField( b, self.sort_data.getField() )
        ordering = cmp( a_field, b_field )
        if ordering == 0:
            a_path = self.getField( a, SubversionListHandlerCommon.col_name )
            b_path = self.getField( b, SubversionListHandlerCommon.col_name )
            ordering = cmp( a_path, b_path )
            return ordering
        else:
            return ordering * self.sort_data.getOrder()

    def getField( self, status, field ):
        __pychecker__ = '--no-returnvalues'

        if field == SubversionListHandlerCommon.col_name:
            return status.path
        if field == SubversionListHandlerCommon.col_state:
            # Use positive text_status first
            # then positive prop_status
            # other wise use negative text_status
            text_val = wb_subversion_utils.wc_status_kind_text_sort_map[ status.text_status ]
            if text_val > 0:
                return text_val

            prop_val = wb_subversion_utils.wc_status_kind_text_sort_map[ status.prop_status ]
            if prop_val > 0:
                return prop_val + wb_subversion_utils.prop_sort_offset

            if text_val < 0:
                return -text_val

            if self.sort_data.getOrder():
                return 999
            else:
                return 0

        if field == SubversionListHandlerCommon.col_revision:
            if status.entry is None or status.entry.commit_revision.number == 0:
                if self.sort_data.getOrder() > 0:
                    return 9999999
                else:
                    return 0
            else:
                return status.entry.commit_revision.number
        if field == SubversionListHandlerCommon.col_author:
            if status.entry is None or status.entry.commit_author is None:
                if self.sort_data.getOrder() > 0:
                    return (1, u'')
                else:
                    return (-1, u'')
            else:
                return (0, status.entry.commit_author)

        if field == SubversionListHandlerCommon.col_date:
            if status.entry is None:
                return 0
            else:
                return status.entry.commit_time

        if field == SubversionListHandlerCommon.col_type:
            if status.entry is None:
                return pysvn.node_kind.none
            else:
                return status.entry.kind


        if field == SubversionListHandlerCommon.col_eol_style:
            return self.project_info.getProperty( status.path, 'svn:eol-style' )

        if field == SubversionListHandlerCommon.col_mime_type:
            return self.project_info.getProperty( status.path, 'svn:mime-type' )

        raise wb_exceptions.InternalError( 'SortList does not support field %s' % field )

class SortListCtrl(SortList):
    def __init__( self, all_files, sort_data, project_info ):
        SortList.__init__( self, sort_data, project_info )
        self.all_files = all_files

    def getField( self, index, field ):
        return SortList.getField( self, self.all_files[index], field )
