Source code for MSQuant: ListViewColumnSorter.vb, MSQuant/msquant/src/GUI/ListViewColumnSorter.vb.

Table of contents page.

Home page for MSQuant.

'****************************************************************************
'* Copyright (C) 2004 Peter Mortensen and Matthias Mann                     *
'* This file is part of MSQuant.                                            *
'*                                                                          *
'* MSQuant is distributed under the terms of                                *
'* the GNU General Public License. See src/COPYING.TXT or                   *
'* <http://www.gnu.org/licenses/gpl.txt> for details.                       *
'*                                                                          *
'* MSQuant 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 2 of the                             *
'* License, or (at your option) any later version.                          *
'*                                                                          *
'* MSQuant 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 MSQuant; if not, write to                      *
'* the Free Software Foundation, Inc., 59 Temple                            *
'* Place, Suite 330, Boston, MA  02111-1307  USA                            *
'*                                                                          *
'* Purpose: provides sort class to be used to sort a ListView - by          *
'*          setting the property ListViewItemSorter to instance of          *
'*          this class. Clients specify a logical column, actual column     *
'*          is determined by an instance of a listviewColumnMapper.         *
'*          Clients can specify secondary columns/keys for the sort and     *
'*          specify datatype and direction (ascending/descending) for each  *
'*          key                                                             *
'*                                                                          *
'****************************************************************************

'****************************************************************************
'*                               CEBI                                       *
'*                    Software Development Group                            *
'*                         Peter Mortensen                                  *
'*                E-mail: NUKESPAMMERSdrmortensen@get2netZZZZZZ.dk          *
'*                 WWW: http://www.cebi.sdu.dk/                             *
'*                                                                          *
'*  Program for post-processing of result from search in mass               *
'*    spectrometric data.                                                   *
'*                                                                          *
'*    FILENAME:   listviewColumnMapper.vb                                   *
'*    TYPE:  VISUAL_BASIC                                                   *
'*                                                                          *
'* CREATED: PM 2006-02-23   Vrs 1.0.                                        *
'* UPDATED: PM 2005-xx-xx                                                   *
'*                                                                          *
'****************************************************************************

'Future:
'  1. What if the column for a key does not exist in the current ListViewItem?


Option Strict On
Option Explicit On 

Imports System.Collections.Generic 'For Dictionary and List.



'****************************************************************************
'*    <placeholder for header>                                              *
'****************************************************************************
Namespace CEBI_generalGUI 'Better name? Things in this name space are
    '  application independent stuff (bug fixes, extensions, 
    '  generalisations) that could be used elsewhere. It is
    '  not even mass spec specific.


    Public Enum sortingDirectionEnum
        enumAscendingSort = 293
        enumDescendingSort
    End Enum 'sortingDirectionEnum


    Public Enum datatypeEnum
        enumText = 293
        enumInteger
        enumDecimalNumber
    End Enum 'sortingDirectionEnum


    'Changed PM_SORT_BY_SELECTION_AND_CHECKED 2008-12-09
    Public Enum specialSortKeyEnum
        enumBySelected = 2309
        enumByChecked
    End Enum 'sortingDirectionEnum



    '****************************************************************************
    '*    <placeholder for header>                                              *
    '****************************************************************************
    Public Class ListViewColumnSorter
        Implements IComparer


        Private Structure sortKeyStructure
            Dim columnID As Integer

            Dim physicalColumn As Integer 'Helper field. For caching.
            '  Value derived from columnID.

            Dim sortDirection As sortingDirectionEnum

            Dim dataType As datatypeEnum

            'For convenience initialisation.
            Public Sub New( _
              ByRef aColumnID As Integer, _
              ByVal aDataType As datatypeEnum, _
              ByVal aSortDirection As sortingDirectionEnum)

                columnID = aColumnID
                dataType = aDataType

                sortDirection = aSortDirection
            End Sub 'New
        End Structure 'sortKeyStructure


        'Changed PM_COLUMNSORT_BY_KEYBOARD_FLATTEN 2008-11-08
        'Private Const MENUPREFIX As String = "by "
        Private Const MENUPREFIX As String = "Sort by "

        'Changed PM_COLUMNSORT_BY_KEYBOARD 2008-11-07
        Private Structure columnSortInfoStruct
            Dim keyList As List(Of sortKeyStructure)

            Dim useMenuKeyboardShortCut As Boolean
        End Structure 'columnSortInfoStruct


        Private mColumnMapper As listviewColumnMapper

        'Changed PM_TYPESAFE_HASH 2008-11-06
        'Private mCurrentKeyList As ArrayList
        Private mCurrentKeyList3 As List(Of sortKeyStructure)

        'Changed PM_COLUMNSORT_BY_KEYBOARD 2008-11-07
        ''Changed PM_REFACTOR 2008-11-06
        'Private mColumnSortSpecificationHash As Dictionary( _
        '  Of Integer, List(Of sortKeyStructure))
        Private mColumnSortSpecificationHash As Dictionary( _
          Of Integer, columnSortInfoStruct)

        Private mLatestPrimaryKey As Integer


        '****************************************************************************
        '*  SUBROUTINE NAME:   New                                                  *
        'd$ <summary>Constructor</summary>
        Public Sub New(ByRef anInColumnMapper As listviewColumnMapper)

            MyBase.New() 'Is this necessary? Yes!

            Trace.Assert(Not anInColumnMapper Is Nothing, _
              "PIL ASSERT. anInColumnMapper is Nothing.")

            mColumnMapper = anInColumnMapper

            mColumnSortSpecificationHash = New Dictionary( _
              Of Integer, columnSortInfoStruct)

            mLatestPrimaryKey = -1 'Mark as uninitialised. To detect client errors.
        End Sub 'Constructor.


        '****************************************************************************
        '*    <placeholder for header>                                              *
        '****************************************************************************
        Private Function reverseSortDirection( _
          ByVal aSortingDirection3 As sortingDirectionEnum) _
          As sortingDirectionEnum

            Dim toReturn As sortingDirectionEnum = _
              sortingDirectionEnum.enumAscendingSort 'Default if fall-through below.

            Select Case aSortingDirection3
                Case sortingDirectionEnum.enumAscendingSort
                    toReturn = sortingDirectionEnum.enumDescendingSort
                Case sortingDirectionEnum.enumDescendingSort
                    toReturn = sortingDirectionEnum.enumAscendingSort
                Case Else
                    Trace.Assert(False, _
                      "PIL ASSERT. Select Case never fall-through")
            End Select
            Return toReturn
        End Function 'reverseSortDirection()


        'Changed PM_SORT_BY_SELECTION_AND_CHECKED 2008-12-09
        '****************************************************************************
        '*    <placeholder for header>                                              *
        '****************************************************************************
        Private Sub checkKey( _
          ByVal aKey As Integer, ByVal aDatatype As datatypeEnum)

            If aKey = specialSortKeyEnum.enumByChecked Or _
               aKey = specialSortKeyEnum.enumBySelected Then

                'It is a client error to specify other than text for
                'sorting by selected or checked. This is just an
                'internal convention that we enforce as soon as possible - here!.
                'Otherwise the error would happen much later, in the Compare()
                'function during sorting.
                Trace.Assert(aDatatype = datatypeEnum.enumText, _
                  "PIL ASSERT. Internal error. " & _
                  "Data type is not enumText for special key (value = " & _
                  aKey & ".")
            End If
        End Sub 'checkKey()


        '****************************************************************************
        '*    <placeholder for header>                                              *
        '****************************************************************************
        Public Sub addColumnPrimaryKey( _
          ByVal aPrimaryKey As Integer, _
          ByVal aDatatype As datatypeEnum, _
          ByVal aSortingDirection2 As sortingDirectionEnum, _
          ByVal aMenuKeyboardShortCut As Boolean)

            'Changed PM_SORT_BY_SELECTION_AND_CHECKED 2008-12-09
            checkKey(aPrimaryKey, aDatatype)

            'Changed PM_ALTERNATE_SORT_DIRECTION 2008-11-08
            'Note: we reverse the sort direction here at specification time. The
            '      reason is that we reverse in prepareSortAndSort(). In that way
            '      the first sort will be as specified.
            Dim sortingDirection As sortingDirectionEnum = _
              reverseSortDirection(aSortingDirection2)

            'Changed PM_COLUMNSORT_BY_KEYBOARD 2008-11-07
            'Dim keyList1 As List(Of sortKeyStructure) = _
            '  New List(Of sortKeyStructure)(10)
            Dim someColumnSortInfo As columnSortInfoStruct
            someColumnSortInfo.keyList = New List(Of sortKeyStructure)(10)
            someColumnSortInfo.useMenuKeyboardShortCut = aMenuKeyboardShortCut

            someColumnSortInfo.keyList.Add( _
                New sortKeyStructure( _
                  aPrimaryKey, _
                  aDatatype, _
                  sortingDirection) _
              )

            mColumnSortSpecificationHash.Add( _
              aPrimaryKey, someColumnSortInfo) 'Note: sub keys may be added later. In
            '  that case the key list in the hash will be updated.

            mLatestPrimaryKey = aPrimaryKey
        End Sub 'addColumnPrimaryKey()


        '****************************************************************************
        '*    <placeholder for header>                                              *
        '****************************************************************************
        Public Sub addColumnSecondaryKey( _
          ByVal aSecondaryKey As Integer, _
          ByVal aDatatype As datatypeEnum, _
          ByVal aSortingDirection As sortingDirectionEnum)

            'Note: the primary key is implied by the ***latest*** call
            '      to addColumnPrimaryKey()...

            'Changed PM_SORT_BY_SELECTION_AND_CHECKED 2008-12-09
            checkKey(aSecondaryKey, aDatatype)

            'Detect errors in the client call sequence.
            If mLatestPrimaryKey < 0 Then
                Trace.Assert(False, _
                  "PIL ASSERT. " & _
                  "Client call error in addColumnSecondaryKey(): " & _
                  "addColumnPrimaryKey() has not been called.")
            End If

            'Changed PM_COLUMNSORT_BY_KEYBOARD 2008-11-07
            'Dim keyList1 As List(Of sortKeyStructure) = _
            '  mColumnSortSpecificationHash(mLatestPrimaryKey)
            Dim someColumnSortInfo As columnSortInfoStruct = _
              mColumnSortSpecificationHash(mLatestPrimaryKey)

            someColumnSortInfo.keyList.Add( _
                New sortKeyStructure( _
                  aSecondaryKey, _
                  aDatatype, _
                  aSortingDirection) _
              )

            'Do we need to write back to the hash????

        End Sub 'addColumnSecondaryKey()


        'Changed PM_COLUMNSORT_BY_KEYBOARD 2008-11-06
        '****************************************************************************
        '*    Returns list of menu items representing those columns that can        *
        '*    be sorted. The client can use the returned list to add items to       *
        '*    menus so it is possible to sort columns without using the mouse.      *
        '****************************************************************************
        Public Function menuItemsForSortableColumns( _
          ByRef anMenuToAddTo As System.Windows.Forms.MenuItem, _
          ByRef aInHandlerFunction As EventHandler) _
          As List(Of System.Windows.Forms.MenuItem)

            'Note: perhaps we should order these menu items as the physical order 
            '      of the columns. mColumnMapper knows about that order. Right
            '      now the order is undefined, but probably the same order as
            '      specified by the client.

            Dim len As Integer = mColumnSortSpecificationHash.Count

            Dim toReturn As List(Of System.Windows.Forms.MenuItem) = _
              New List(Of System.Windows.Forms.MenuItem)(len)


            Dim keyBoardShortcutCounter As Integer = 0

            Dim hashEnumerator2 As Dictionary( _
              Of Integer, columnSortInfoStruct).Enumerator = _
                mColumnSortSpecificationHash.GetEnumerator()
            While hashEnumerator2.MoveNext()
                Dim curKey As Integer = hashEnumerator2.Current.Key
                Dim curValue As columnSortInfoStruct = _
                   hashEnumerator2.Current.Value

                'The primary key (for sorting) is the first in the list.
                Dim columnSortKey As Integer = curValue.keyList(0).columnID

                Dim inGUi As Boolean
                Dim info As listViewColumnInfoStruct = _
                  mColumnMapper.getColumnInfo(columnSortKey, inGUi)

                If inGUi Then
                    Dim someMenuItem As System.Windows.Forms.MenuItem = _
                      New System.Windows.Forms.MenuItem

                    someMenuItem.Tag = columnSortKey
                    someMenuItem.Text = MENUPREFIX & info.columnHeader

                    'Changed PM_COLUMNSORT_BY_KEYBOARD 2008-11-07
                    If curValue.useMenuKeyboardShortCut Then
                        Dim assignShortcut As Boolean = True
                        Dim sc As System.Windows.Forms.Shortcut

                        Select Case keyBoardShortcutCounter
                            Case 0
                                sc = Shortcut.Ctrl1
                            Case 1
                                sc = Shortcut.Ctrl2
                            Case 2
                                sc = Shortcut.Ctrl3
                            Case 3
                                sc = Shortcut.Ctrl4
                            Case 4
                                sc = Shortcut.Ctrl5
                            Case 5
                                sc = Shortcut.Ctrl6
                            Case 6
                                sc = Shortcut.Ctrl7
                            Case 7
                                sc = Shortcut.Ctrl8
                            Case 8
                                sc = Shortcut.Ctrl9
                            Case 9
                                sc = Shortcut.Ctrl0

                            Case Else
                                assignShortcut = False 'We have run out of
                                '  keyboard shortcuts... Silently do not add it.
                        End Select

                        If assignShortcut Then
                            someMenuItem.Shortcut = sc
                        End If

                        keyBoardShortcutCounter += 1
                    End If

                    anMenuToAddTo.MenuItems.AddRange( _
                      New System.Windows.Forms.MenuItem() {someMenuItem})

                    'Wire handling of menu command selection to the handler
                    'function specified by the client.
                    AddHandler _
                      someMenuItem.Click, _
                      aInHandlerFunction

                    toReturn.Add(someMenuItem)
                Else
                    Dim peter2 As Integer = 2 'For breakpoints. Defined, but
                    '  not added to the GUI.
                End If

            End While 'Hash iteration.

            'Dim someMI As System.Windows.Forms.MenuItem = _
            '  New System.Windows.Forms.MenuItem
            'Private mColumnSortSpecificationHash As Dictionary( _
            '  Of Integer, List(Of sortKeyStructure))

            Return toReturn
        End Function 'menuItemsForSortableColumns()


        '****************************************************************************
        '*    <placeholder for header>                                              *
        '****************************************************************************
        Private Sub prepareSort( _
          ByVal aKeylist2 As Generic.List(Of sortKeyStructure))

            'Old:
            '  ByVal aKeylist As ArrayList
            '  Item type of aKeylist is: sortKeyStructure


            'Cache the physical column number in the datastructure. Done here
            'during a deep copy of the input list.

            'Changed PM_TYPESAFE_HASH 2008-11-06
            'mCurrentKeyList = New ArrayList
            Dim inLen As Integer = aKeylist2.Count
            mCurrentKeyList3 = New List(Of sortKeyStructure)(inLen)

            Dim someKey As sortKeyStructure
            For Each someKey In aKeylist2
                Dim copiedKey As sortKeyStructure = someKey
                copiedKey.physicalColumn = _
                  mColumnMapper.item2ColumnIndex(copiedKey.columnID)
                mCurrentKeyList3.Add(copiedKey)
            Next
        End Sub 'prepareSort()


        '****************************************************************************
        '*    <placeholder for header>                                              *
        '****************************************************************************
        Public Sub prepareSortAndSort( _
          ByVal aPrimaryKey As Integer, _
          ByVal aListView As System.Windows.Forms.ListView)

            'Dim keylist As List(Of sortKeyStructure) = Nothing
            Dim columnSortInfo As columnSortInfoStruct

            If mColumnSortSpecificationHash.TryGetValue(aPrimaryKey, columnSortInfo) Then

                Dim doSort As Boolean = Not aListView Is Nothing


                'Changed PM_ALTERNATE_SORT_DIRECTION 2008-11-08
                'Note: we reverse the sort direction here at sort time. The reason 
                '      is that we reverse at specification time. In that way
                '      the first sort will be as specified. Second sort will 
                '      be reversed, etc.
                '
                If True Then 'Note: unconditional. We need to reverse even though we
                    '  don't explicitly sort below. The sort will happen implicitly -
                    '  when an instance of this class is assigned to
                    '  the ListViewItemSorter property of the ListView (or a
                    '  derived class).
                    '
                    Dim primaryKeyInfo As sortKeyStructure = columnSortInfo.keyList(0)

                    Dim newSortingDirection As sortingDirectionEnum = _
                      reverseSortDirection(primaryKeyInfo.sortDirection) 'Index 0
                    '  contains the primary sort key.

                    'Write back.
                    primaryKeyInfo.sortDirection = newSortingDirection
                    columnSortInfo.keyList(0) = primaryKeyInfo
                    'mColumnSortSpecificationHash(aPrimaryKey) = columnSortInfo 'Needed??
                End If 'Block. Alternating sort keys.


                Me.prepareSort(columnSortInfo.keyList)

                If doSort Then
                    aListView.Sort()
                Else
                    Dim peter2 As Integer = 2 'We are not going to
                    '  sort, at least not explicitly...
                End If
            Else
                'We have not defined sorting for the column in question.
                'Simply ignore the request. Should we throw a message
                'box instead?
                Dim peter1 As Integer = 1
            End If
        End Sub 'prepareSortAndSort


        '****************************************************************************
        '*    <placeholder for header>                                              *
        '****************************************************************************
        Private Function Compare( _
          ByVal x As Object, ByVal y As Object) _
          As Integer _
          Implements System.Collections.IComparer.Compare

            'Can we get rid of these casts??
            Dim item1 As ListViewItem = CType(x, ListViewItem)
            Dim item2 As ListViewItem = CType(y, ListViewItem)

            Dim toReturn As Integer = 0 'In case 

            Dim someKey2 As sortKeyStructure
            For Each someKey2 In mCurrentKeyList3

                Dim physCol As Integer = someKey2.physicalColumn

                If True Then 'Block. Early error detection.
                    'Under normal circumstances these two
                    'indices are always the same as all rows have
                    'the same number of columns. But it can
                    'happen that sorting is used during building
                    'up of the list (by a screen update?) and
                    'then a partly completed row can be compared
                    'against a full row. Clients should do specific
                    'things to avoid this situation. We detect it here
                    'if they don't...
                    Dim lastIndex1 As Integer = item1.SubItems.Count() - 1
                    Dim lastIndex2 As Integer = item2.SubItems.Count() - 1

                    Trace.Assert(physCol <= lastIndex1, _
                      "Index, " & physCol & ", is past last index in item1.SubItems, " & _
                      lastIndex1 & ".")
                    Trace.Assert(physCol <= lastIndex2, _
                      "Index, " & physCol & ", is past last index in item1.SubItems, " & _
                      lastIndex2 & ".")
                End If

                Dim str1 As String = Nothing
                Dim str2 As String = Nothing

                If someKey2.columnID = specialSortKeyEnum.enumBySelected Then

                    'Changed PM_SORT_BY_SELECTION_AND_CHECKED 2008-12-09.
                    'Note: any letters, but observe ascending or
                    '      descending order.
                    If item1.Selected Then
                        str1 = "S"
                    Else
                        str1 = "AU"
                    End If
                    If item2.Selected Then
                        str2 = "S"
                    Else
                        str2 = "AU"
                    End If
                Else
                    If someKey2.columnID = specialSortKeyEnum.enumByChecked Then
                        'Changed PM_SORT_BY_SELECTION_AND_CHECKED 2008-12-09
                        'Note: any letters, but observe ascending or
                        '      descending order.
                        If item1.Checked Then
                            str1 = "C"
                        Else
                            str1 = "AN"
                        End If
                        If item2.Checked Then
                            str2 = "C"
                        Else
                            str2 = "AN"
                        End If
                    Else
                        str1 = item1.SubItems(physCol).Text
                        str2 = item2.SubItems(physCol).Text

                        If str1 = "..." Then
                            'Changed PM_QUANTCOLUMNS_SORT 2007-04-03
                            'str1 = "0"
                            str1 = "-1e-20"
                        End If
                        If str2 = "..." Then
                            'Changed PM_QUANTCOLUMNS_SORT 2007-04-03
                            'str2 = "0"
                            str2 = "-1e-20"
                        End If

                        'Changed PM_QUANTCOLUMNS_SORT 2007-04-03
                        If str1 = "NaN" Then
                            str1 = "-1e-22"
                        End If
                        If str2 = "NaN" Then
                            str2 = "-1e-22"
                        End If
                    End If
                End If

                'Dim compareResult As Integer = 0
                Select Case someKey2.dataType
                    Case datatypeEnum.enumText

                        'Initially ascending, but we reverse the result later
                        'if descending is specified.
                        If str1 > str2 Then
                            toReturn = 1
                        Else
                            If str1 < str2 Then
                                toReturn = -1
                            Else
                                'Equal...
                                Dim peter1 As Integer = 1
                            End If
                        End If

                    Case datatypeEnum.enumInteger
                        Dim int1 As Integer = CInt(str1)
                        Dim int2 As Integer = CInt(str2)

                        'Initially ascending, but we reverse the result later
                        'if descending is specified.
                        If int1 > int2 Then
                            toReturn = 1
                        Else
                            If int1 < int2 Then
                                toReturn = -1
                            Else
                                'Equal...
                                Dim peter1 As Integer = 1
                            End If
                        End If

                    Case datatypeEnum.enumDecimalNumber
                        'Ignore any parenthesises around the number, e.g.:  (47)

                        Dim startIndex1 As Integer = str1.IndexOf("(")
                        If startIndex1 >= 0 Then
                            Dim endIndex1 As Integer = str1.IndexOf(")")
                            If endIndex1 >= 0 Then
                                startIndex1 += 1 'Step over "("
                                Dim len1 As Integer = endIndex1 - startIndex1
                                str1 = str1.Substring(startIndex1, len1)
                            End If
                        End If

                        Dim startIndex2 As Integer = str2.IndexOf("(")
                        If startIndex2 >= 0 Then
                            Dim endIndex2 As Integer = str2.IndexOf(")")
                            If endIndex2 >= 0 Then
                                startIndex2 += 1 'Step over "("
                                Dim len2 As Integer = endIndex2 - startIndex2
                                str2 = str2.Substring(startIndex2, len2)
                            End If
                        End If

                        Dim d1 As Double = CDbl(str1)
                        Dim d2 As Double = CDbl(str2)

                        'Initially ascending, but we reverse the result later
                        'if descending is specified.
                        If d1 > d2 Then
                            toReturn = 1
                        Else
                            If d1 < d2 Then
                                toReturn = -1
                            Else
                                'Equal...
                                Dim peter1 As Integer = 1
                            End If
                        End If

                    Case Else
                        Trace.Assert(False, "PIL ASSERT. Select Case never fall-through")
                End Select

                If someKey2.sortDirection = _
                   sortingDirectionEnum.enumDescendingSort Then

                    toReturn = -toReturn
                End If

                If toReturn = 0 Then
                    'The two keys are equal. We must go to a lower
                    'level key (if there are any) - by continuing the loop.
                    Dim peter1 As Integer = 1
                Else
                    Exit For 'Two current keys are not equal. We need not
                    'compare more keys. Stop.
                End If
            Next

            Return toReturn
        End Function 'Compare()


    End Class 'ListViewColumnSorter


End Namespace 'CEBI_generalGUI


    

    

Generated by script codePublish.pl at 2009-01-05T15:20:59.