DirectoryContentsBrowser.st
author penk
Fri, 27 Sep 2002 17:23:38 +0200
changeset 3907 a06fe9db6195
parent 3906 8a76cb4f5884
child 3911 d2015b482122
permissions -rw-r--r--
next stage

"{ Package: 'stx:libtool' }"

AbstractFileBrowser subclass:#DirectoryContentsBrowser
	instanceVariableNames:'browser updateDirectoryContentsTask modificationTime directory
		columnDescriptors filterBlock iconIndex fileDescriptionIndex
		iconExtent tableColumns sortBlock currentSortOrder previewIndex
		allItemsList updateColumnsTask currentItemList wantSelectFiles
		updateToExternFileHolderLock lastResponse oldDropItem oldItemIcon
		updateTaskSema updateFromSensorTask'
	classVariableNames:''
	poolDictionaries:''
	category:'Interface-Tools-File'
!

Object subclass:#DirectoryContentsItem
	instanceVariableNames:'fileName fileInfo icon fileType link linkInfo
		contentsBrowserChangeModificationTime isDirectory baseName suffix
		preview group owner pathName permissions size timeAndDate date
		lastButOneSuffix suffixes modificationTime modifiedByMe mimeType
		iconKey'
	classVariableNames:''
	poolDictionaries:''
	privateIn:DirectoryContentsBrowser
!

!DirectoryContentsBrowser class methodsFor:'documentation'!

documentation

"
    DirectoryContentsBrowser openIn:'/home/ca/work/stx/goodies/bitmaps/xpmBitmaps'
    DirectoryContentsBrowser openIn:'/home/ca/TEST'
"
! !

!DirectoryContentsBrowser class methodsFor:'instance creation'!

openIn:aDirectory
    "opens a directory (standalone) for a directory
    "
    self openIn:aDirectory withDirectories:false

"
self openIn:(Filename currentDirectory construct:'.cvsignore')
"
!

openIn:aDirectory withDirectories:withDirectoriesBoolean
    "opens a directory (standalone) for a directory
    "
    |browser|

    browser := DirectoryContentsBrowser new.
    browser open.
    aDirectory notNil ifTrue:[
        browser currentFileNameHolder value:(OrderedCollection with:(aDirectory asFilename))
    ].
    browser viewDirectoriesInDirectoryContentsBrowser value:withDirectoriesBoolean.
  ^ browser

"
self openIn:(Filename currentDirectory asAbsoluteFilename) withDirectories:false
"
! !

!DirectoryContentsBrowser class methodsFor:'classAccess'!

itemClass

    ^ DirectoryContentsItem


"
self itemClass
"
! !

!DirectoryContentsBrowser class methodsFor:'constant'!

updateTaskPriority
    "returns the priority of the update task
    "
    ^ 8
! !

!DirectoryContentsBrowser class methodsFor:'interface specs'!

tableColumns
    "This resource specification was automatically generated
     by the DataSetBuilder of ST/X."

    "Do not manually edit this!! If it is corrupted,
     the DataSetBuilder may not be able to read the specification."

    "
     DataSetBuilder new openOnClass:DirectoryContentsBrowser andSelector:#tableColumns
    "

    <resource: #tableColumns>

    ^#(
      #(#DataSetColumnSpec
         #labelButtonType: #Group
         #width: 22
         #minWidth: 22
         #height: 16
         #model: #icon
         #canSelect: false
         #showRowSeparator: false
         #showColSeparator: false
       )
      #(#DataSetColumnSpec
         #label: 'Filename'
         #id: #filename
         #labelAlignment: #left
         #labelButtonType: #Button
         #labelActionSelector: #sortList:
         #labelActionArgument: 'baseName'
         #minWidth: 150
         #model: #baseName
         #canSelect: false
         #showRowSeparator: false
         #showColSeparator: false
       )
      #(#DataSetColumnSpec
         #label: 'Suffix'
         #id: #suffix
         #labelAlignment: #left
         #labelButtonType: #Button
         #labelActionSelector: #sortList:
         #labelActionArgument: 'suffix'
         #width: 40
         #model: #suffix
         #canSelect: false
         #isResizeable: false
         #showRowSeparator: false
         #showColSeparator: false
       )
      #(#DataSetColumnSpec
         #label: 'Perm'
         #id: #perm
         #labelAlignment: #left
         #labelButtonType: #Button
         #labelActionSelector: #sortList:
         #labelActionArgument: 'permissions'
         #width: 75
         #model: #permissions
         #canSelect: false
         #isResizeable: false
         #showRowSeparator: false
         #showColSeparator: false
       )
      #(#DataSetColumnSpec
         #label: 'Owner'
         #id: #owner
         #labelAlignment: #left
         #labelButtonType: #Button
         #labelActionSelector: #sortList:
         #labelActionArgument: 'owner'
         #usePreferredWidth: true
         #width: 50
         #model: #owner
         #canSelect: false
         #showRowSeparator: false
         #showColSeparator: false
       )
      #(#DataSetColumnSpec
         #label: 'Group'
         #id: #group
         #labelAlignment: #left
         #labelButtonType: #Button
         #labelActionSelector: #sortList:
         #labelActionArgument: 'group'
         #usePreferredWidth: true
         #width: 40
         #model: #group
         #canSelect: false
         #showRowSeparator: false
         #showColSeparator: false
       )
      #(#DataSetColumnSpec
         #label: 'Size'
         #id: #size
         #translateLabel: true
         #labelAlignment: #right
         #labelButtonType: #Button
         #labelActionSelector: #sortList:
         #labelActionArgument: 'size'
         #columnAlignment: #right
         #usePreferredWidth: true
         #model: #sizeString
         #canSelect: false
         #showRowSeparator: false
         #showColSeparator: false
       )
      #(#DataSetColumnSpec
         #label: 'Date & Time'
         #id: #timeAndDate
         #labelAlignment: #right
         #labelButtonType: #Button
         #labelActionSelector: #sortList:
         #labelActionArgument: 'modificationTime'
         #columnAlignment: #right
         #usePreferredWidth: true
         #width: 140
         #model: #timeAndDate
         #writeSelector: #date:
         #canSelect: false
         #showRowSeparator: false
         #showColSeparator: false
       )
      #(#DataSetColumnSpec
         #label: 'File Info'
         #id: #fileInfo
         #labelAlignment: #left
         #labelButtonType: #Button
         #labelActionSelector: #sortList:
         #labelActionArgument: 'fileType'
         #width: 250
         #model: #fileInfoString
         #canSelect: false
         #showRowSeparator: false
         #showColSeparator: false
       )
      #(#DataSetColumnSpec
         #label: 'Pre +/-'
         #id: #preview
         #labelButtonType: #Button
         #labelActionSelector: #doResizeImage
         #labelActionArgument: ''
         #columnAlignment: #center
         #width: 45
         #height: 16
         #model: #preview
         #canSelect: false
         #showRowSeparator: false
         #showColSeparator: false
       )
      )
    
!

windowSpec
    "This resource specification was automatically generated
     by the UIPainter of ST/X."

    "Do not manually edit this!! If it is corrupted,
     the UIPainter may not be able to read the specification."

    "
     UIPainter new openOnClass:DirectoryContentsBrowser andSelector:#windowSpec
     DirectoryContentsBrowser new openInterface:#windowSpec
     DirectoryContentsBrowser open
    "

    <resource: #canvas>

    ^ 
     #(#FullSpec
        #name: #windowSpec
        #window: 
       #(#WindowSpec
          #label: 'Directory ContentsBrowser'
          #name: 'Directory ContentsBrowser'
          #min: #(#Point 10 10)
          #max: #(#Point 1152 900)
          #bounds: #(#Rectangle 723 276 1311 754)
          #menu: #menu
        )
        #component: 
       #(#SpecCollection
          #collection: #(
           #(#DataSetSpec
              #name: 'browser'
              #layout: #(#LayoutFrame 0 0.0 0 0.0 0 1.0 0 1.0)
              #model: #selectionInFileList
              #menu: #directoryContentsBrowserMenu
              #hasHorizontalScrollBar: true
              #hasVerticalScrollBar: true
              #miniScrollerHorizontal: false
              #miniScrollerVertical: false
              #dataList: #descriptions
              #has3Dsepartors: false
              #doubleClickSelector: #doubleClickedAt:
              #columnHolder: #tableColumns
              #multipleSelectOk: true
              #verticalSpacing: 0
              #columns: 
             #(#OrderedCollection
                
               #(#DataSetColumnSpec
                  #labelButtonType: #Group
                  #width: 22
                  #minWidth: 22
                  #height: 16
                  #model: #icon
                  #canSelect: false
                  #showRowSeparator: false
                  #showColSeparator: false
                ) 
               #(#DataSetColumnSpec
                  #label: 'file name'
                  #labelAlignment: #left
                  #labelButtonType: #Button
                  #model: #baseName
                  #canSelect: false
                  #showRowSeparator: false
                  #showColSeparator: false
                )
                
               #(#DataSetColumnSpec
                  #label: '+/-'
                  #labelButtonType: #Button
                  #labelActionSelector: #doResizeImage
                  #labelActionArgument: ''
                  #columnAlignment: #center
                  #width: 30
                  #height: 16
                  #model: #preview
                  #canSelect: false
                  #showRowSeparator: false
                  #showSelectionHighLighted: false
                  #showColSeparator: false
                )
              )
              #viewClassName: 'DirectoryContentsBrowser::Widget'
              #postBuildCallback: #postBuildBrowser:
              #properties: 
             #(#PropertyListDictionary
                #dragArgument: #browser
                #startDragSelector: #doStartDrag:in:
                #displayObjectSelector: #getDisplayObjects:
                #dropObjectSelector: #getDropObjects:
                #dropArgument: #browser
                #canDropSelector: #canDrop:argument:
                #leaveSelector: #dropLeave:
                #dropSelector: #doDrop:argument:
              )
            )
           )
         
        )
      )
! !

!DirectoryContentsBrowser class methodsFor:'menu specs'!

directoryContentsBrowserMenu
    "This resource specification was automatically generated
     by the MenuEditor of ST/X."

    "Do not manually edit this!! If it is corrupted,
     the MenuEditor may not be able to read the specification."

    "
     MenuEditor new openOnClass:DirectoryContentsBrowser andSelector:#directoryContentsBrowserMenu
     (Menu new fromLiteralArrayEncoding:(DirectoryContentsBrowser directoryContentsBrowserMenu)) startUp
    "

    <resource: #menu>

    ^ 
     #(#Menu
        #(
         #(#MenuItem
            #label: 'Copy'
            #translateLabel: true
            #value: #doCopy
            #shortcutKeyCharacter: #Copy
          )
         #(#MenuItem
            #label: 'Cut'
            #translateLabel: true
            #value: #doCut
            #shortcutKeyCharacter: #Cut
          )
         #(#MenuItem
            #label: 'Paste'
            #translateLabel: true
            #value: #pasteFiles
            #enabled: #canPaste
            #shortcutKeyCharacter: #Paste
          )
         #(#MenuItem
            #label: 'Delete'
            #translateLabel: true
            #nameKey: #delete
            #value: #doDelete
            #shortcutKeyCharacter: #Delete
          )
         #(#MenuItem
            #label: 'Rename'
            #translateLabel: true
            #value: #renameSelection
            #enabled: #hasSelection
            #shortcutKeyCharacter: #Replace
          )
         #(#MenuItem
            #label: '-'
          )
         #(#MenuItem
            #label: 'View'
            #translateLabel: true
            #nameKey: #View
            #submenuChannel: #viewBrowserMenu
          )
         #(#MenuItem
            #label: 'Sort'
            #translateLabel: true
            #nameKey: #Sort
            #submenuChannel: #sortMenu
          )
         #(#MenuItem
            #label: '-'
          )
         #(#MenuItem
            #label: 'New'
            #translateLabel: true
            #submenuChannel: #newMenu
          )
         #(#MenuItem
            #label: 'Update'
            #translateLabel: true
            #value: #doUpdate
          )
         #(#MenuItem
            #label: 'Properties...'
            #translateLabel: true
            #value: #doShowProperties
            #enabled: #hasSelection
          )
         #(#MenuItem
            #label: 'Others'
            #submenu: 
           #(#Menu
              #(
               #(#MenuItem
                  #label: 'File In'
                  #translateLabel: true
                  #value: #fileFileIn
                  #enabled: #hasSelection
                  #shortcutKeyCharacter: #Accept
                )
               #(#MenuItem
                  #label: 'File in to NameSpace...'
                  #translateLabel: true
                  #value: #fileFileInToNameSpace
                  #enabled: #hasSelection
                )
               #(#MenuItem
                  #label: 'Directory Up'
                  #translateLabel: true
                  #value: #doGoDirectoryUp
                  #enabled: #enableDirectoryUp
                  #shortcutKeyCharacter: #CursorLeft
                )
               #(#MenuItem
                  #label: 'Open in TextEditor'
                  #translateLabel: true
                  #value: #doShowFileContents
                  #enabled: #hasSelection
                )
               #(#MenuItem
                  #label: 'Select All'
                  #translateLabel: true
                  #value: #doSelectAll
                )
               #(#MenuItem
                  #label: 'Copy File List'
                  #translateLabel: true
                  #value: #copyFileList
                  #enabled: #fileListIsNotEmpty
                )
               )
              nil
              nil
            )
          )
         )
        nil
        nil
      )
!

viewBrowserMenu
    "This resource specification was automatically generated
     by the MenuEditor of ST/X."

    "Do not manually edit this!! If it is corrupted,
     the MenuEditor may not be able to read the specification."

    "
     MenuEditor new openOnClass:DirectoryContentsBrowser andSelector:#viewBrowserMenu
     (Menu new fromLiteralArrayEncoding:(DirectoryContentsBrowser viewBrowserMenu)) startUp
    "

    <resource: #menu>

    ^ 
     #(#Menu
        #(
         #(#MenuItem
            #label: 'View Details'
            #translateLabel: true
            #hideMenuOnActivated: false
            #indication: #viewDetails
          )
         #(#MenuItem
            #label: 'View Suffix'
            #translateLabel: true
            #hideMenuOnActivated: false
            #enabled: #viewDetails
            #indication: #viewType
          )
         #(#MenuItem
            #label: 'View Permissions'
            #translateLabel: true
            #hideMenuOnActivated: false
            #enabled: #viewDetails
            #indication: #viewPermissions
          )
         #(#MenuItem
            #label: 'View Owner'
            #translateLabel: true
            #hideMenuOnActivated: false
            #enabled: #viewDetails
            #indication: #viewOwner
          )
         #(#MenuItem
            #label: 'View Group'
            #translateLabel: true
            #hideMenuOnActivated: false
            #enabled: #viewDetails
            #indication: #viewGroup
          )
         #(#MenuItem
            #label: 'View Size'
            #translateLabel: true
            #hideMenuOnActivated: false
            #enabled: #viewDetails
            #indication: #viewSize
          )
         #(#MenuItem
            #label: 'View Date && Time'
            #translateLabel: true
            #hideMenuOnActivated: false
            #enabled: #viewDetails
            #indication: #viewTime
          )
         #(#MenuItem
            #label: 'View File Info'
            #translateLabel: true
            #hideMenuOnActivated: false
            #enabled: #viewDetails
            #indication: #viewDescription
          )
         #(#MenuItem
            #label: 'View Preview'
            #translateLabel: true
            #hideMenuOnActivated: false
            #enabled: #viewDetails
            #indication: #viewPreview
          )
         #(#MenuItem
            #label: '-'
          )
         #(#MenuItem
            #label: 'Show Hidden Files'
            #translateLabel: true
            #indication: #showHiddenFiles
          )
         )
        nil
        nil
      )
! !

!DirectoryContentsBrowser methodsFor:'accessing'!

columnDescriptors
    "get the column description
    "
    ^ columnDescriptors
!

columnDescriptors:aListOfColumns
    "set the column description
    "
    columnDescriptors = aListOfColumns ifTrue:[
        ^ self
    ].

    columnDescriptors    := OrderedCollection new.
    previewIndex        := 0.
    iconIndex            := 0.
    fileDescriptionIndex := 0.
    iconExtent           := nil.

    aListOfColumns keysAndValuesDo:[:anIndex :aDesc| |col|
        col := aDesc isSequenceable ifTrue:[DataSetColumnSpec new fromLiteralArrayEncoding:aDesc]
                                   ifFalse:[aDesc].
        columnDescriptors add:col.

        col readSelector == #preview ifTrue:[
            previewIndex := anIndex.
            iconExtent := Point x:(col width) y:(col height).
        ] ifFalse:[
            col readSelector == #fileInfoString ifTrue:[
                fileDescriptionIndex := anIndex.
            ] ifFalse:[
                col readSelector == #icon ifTrue:[
                    iconIndex  := anIndex.
                ]
            ]
        ]
    ].

!

directory
    "returns the current directory or nil
    "
    ^ directory

!

directory:aDirectory
    "change the current directory and read the items
    "
    directory := aDirectory.
    self readDirectory.
!

filterBlock
    "return the value of the instance variable 'filterBlock' (automatically generated)"

    ^ filterBlock
!

filterBlock:something
    "set the value of the instance variable 'filterBlock' (automatically generated)"

    filterBlock := something.
!

oldChangedIcon

    oldItemIcon isNil ifTrue:[
        oldItemIcon := Association new.
    ].
    ^ oldItemIcon
!

oldItemIcon

    oldItemIcon isNil ifTrue:[
        oldItemIcon := Association new.
    ].
    ^ oldItemIcon
!

sortBlock


    sortBlock isNil ifTrue:[
        self sortBlock:[ : a : b |
                            self sortCaseless value ifTrue:[
                                a asString asLowercase < b asString asLowercase.
                            ] ifFalse:[
                                a asString < b asString.
                            ]
                        ].
    ].
    ^ sortBlock
!

sortBlock:aSortBlock

" sort directories before files anyway "
    sortBlock := [:a :b|
        | res |
        (self viewDirectoriesInDirectoryContentsBrowser
        and:[(a isDirectory == b isDirectory) not]) ifTrue:[
            res := a isDirectory 
        ] ifFalse:[
            res := aSortBlock value:a value:b.
        ].
        res
    ].
    ^ sortBlock
!

updateColumnsTask
    "return the value of the instance variable 'updateColumnsTask' (automatically generated)"

    ^ updateColumnsTask
!

updateColumnsTask:something
    "set the value of the instance variable 'updateColumnsTask' (automatically generated)"

    updateColumnsTask := something.
!

updateDirectoryContentsTask
    "return the value of the instance variable 'updateDirectoryContentsTask' (automatically generated)"

    ^ updateDirectoryContentsTask
!

updateDirectoryContentsTask:something
    "set the value of the instance variable 'updateDirectoryContentsTask' (automatically generated)"

    updateDirectoryContentsTask := something.
!

updateToExternFileHolderLock
    "return the value of the instance variable 'updateToExternFileHolder' (automatically generated)"

    updateToExternFileHolderLock isNil ifTrue:[
        updateToExternFileHolderLock := self class newLock.
    ].
    ^ updateToExternFileHolderLock
! !

!DirectoryContentsBrowser methodsFor:'actions'!

doResizeImage
    |column w h|

    previewIndex == 0 ifTrue:[^ self].


    column := columnDescriptors at:previewIndex.
    w := iconExtent x.
    h := iconExtent y.

    w == column width ifTrue:[
        w := w * 2.
        h := h * 2.
    ].
    column width:w.
    column height:h.

    self updateColumnsTaskStop.
    self descriptions do:[:el| el resetImageFile ].
    browser columnDescriptors:columnDescriptors.
    self updateColumnsTaskStart.
!

doUpdate

    modificationTime := nil.
    self updateDirectoryContentsTaskRestart.
!

doubleClickedAt:anIndex
    |item|

    item := self descriptions at:anIndex ifAbsent:nil.
    self withWaitCursorDo:[
        self enterActionFor:item.
    ]
!

enterActionFor:anItem
    | filename|

    filename := anItem fileName.

    filename = '..' asFilename ifTrue:[
        self currentFileNameHolder value:(OrderedCollection with:(self directory directory)).
        ^ self.
    ].
    (anItem isRemoteDirectory 
    or:[filename isDirectory]) ifTrue:[
        self currentFileNameHolder value:(OrderedCollection with:filename).
        ^ self.
    ].
    self openApplByFileItem:anItem.
!

selectFileName:aColOfFilenames
    | indexInDescriptions curSel descr index|

    descr := self descriptions.
    indexInDescriptions := OrderedCollection new.
    aColOfFilenames do:[ : file |
        index := descr findFirst:[: item |
            item fileName = file.
        ].
        index ~= 0 ifTrue:[
            indexInDescriptions add:index.
        ]
    ].
    curSel := self selectionInFileList value.
    curSel = indexInDescriptions ifTrue:[
        ^ self.
    ].
    self selectionInFileList value:indexInDescriptions withoutNotifying:self.
! !

!DirectoryContentsBrowser methodsFor:'aspects'!

descriptions
    ^ self browserFileList
!

descriptions:aCol

    | showDir|

    self updateColumnsTaskStop.  "/ dont access rows while rows changed
    self updateToExternFileHolderLock doLocked:[
        aCol isEmpty ifTrue:[
            self descriptions contents:aCol.
        ] ifFalse:[
            showDir := self viewDirectoriesInDirectoryContentsBrowser value.
            (showDir and:[self directory isRootDirectory not]) ifTrue:[
                aCol addFirst:(DirectoryContentsItem with:('..' asFilename)).
            ].
            self descriptions contents:aCol.
        ].
    ].
    self updateColumnsTaskStart.
    wantSelectFiles notNil ifTrue:[
        self selectFileName:wantSelectFiles.
    ].
!

tableColumns
    "automatically generated by UIPainter ..."

    "*** the code below creates a default model when invoked."
    "*** (which may not be the one you wanted)"
    "*** Please change as required and accept it in the browser."
    "*** (and replace this comment by something more useful ;-)"

    tableColumns isNil ifTrue:[
        tableColumns := self class tableColumns asValue.
"/ if your app needs to be notified of changes, uncomment one of the lines below:
"/       tableColumns addDependent:self.
"/       tableColumns onChangeSend:#tableColumnsChanged to:self.
    ].
    ^ tableColumns.
! !

!DirectoryContentsBrowser methodsFor:'change & update'!

fileNamesChanged:aColOfFiles
    "filename changed
    "
    | newDir currentDir colDirectories|


    wantSelectFiles := aColOfFiles. "/ set the file to the current filename to change selection if update process have added the list
    aColOfFiles isEmpty ifTrue:[
        self directory:nil.
        ^ self
    ].
    colDirectories := self getDirectoriesForFileList:aColOfFiles.
    colDirectories asSet size > 1 ifTrue:[
        self directory:nil.
        ^ self
    ].
    newDir := colDirectories first.
    currentDir := self directory.
    newDir = currentDir ifTrue:[
        self selectFileName:aColOfFiles.
        ^ self.
    ] ifFalse:[
        "/ change of the directory filename 
        self directory:newDir.
    ].
!

filterChanged

    self filterBlock: [: el | (self showHiddenFiles value ifFalse:[
                            (el asString startsWith:'.') not])
                            and:[self filterModel value match:el]].

    self evaluateFilter.
!

update:something with:aParameter from:aModel
    "one of my models changed
    "
    | selection window sensor|

    aModel == self filterModel ifTrue:[
        self filterChanged.
        ^ self.
    ].             
    aModel == self sortBlockHolder ifTrue:[
        self sortList:(self sortBlockHolder value) withReverse:false.
        ^ self.
    ].             
    aModel == self currentFileNameHolder ifTrue:[
        window := self window.
        window ifNotNil:[
            sensor := window sensor.
            sensor ifNotNil:[
                sensor flushUserEventsFor:self withType:#updateFromModel.
                sensor pushUserEvent:#updateFromModel for:self.
              ^ self
            ].
        ].
        self fileNamesChanged:aModel value.
        " do not return here because the AbstractFileBrowserComponent have to do something
          for every application "  
"/        ^ self.
    ].             
    aModel == self sortCaseless ifTrue:[
        ^ self sortList:#baseName withReverse:false.
    ].             
    aModel == self selectionInFileList ifTrue:[
        self updateToExternFileHolderLock doIfUnLocked:[
            self directory isNil ifTrue:[^ self].   
            selection := self getSelectedFiles.
            selection isEmpty ifTrue:[
                self currentFileNameHolder value:(OrderedCollection with:self directory) withoutNotifying:self.
            ] ifFalse:[
                wantSelectFiles := selection.
                self currentFileNameHolder value:selection withoutNotifying:self.
            ].
        ].
        ^ self.
    ].
    aModel == self viewDirectoriesInDirectoryContentsBrowser ifTrue:[
        self evaluateFilter.
        ^ self
    ].

    super update:something with:aParameter from:aModel
!

updateFromModel
    ||

    updateTaskSema isNil ifTrue:[
        [
            updateTaskSema isNil ifTrue:[
                updateTaskSema := Semaphore forMutualExclusion.
            ].
        ] valueUninterruptably
    ].
    updateTaskSema critical:[
        self updateFromSensorTaskStop.
        updateFromSensorTask := Process for:[ |window sensor|
            [
            window := self window.
            window ifNotNil:[
                sensor := window sensor.
                sensor ifNotNil:[
                    sensor flushUserEventsFor:self withType:#updateFromModel.
                ].
            ].
            self fileNamesChanged:(self currentFileNameHolder value).
            ] valueNowOrOnUnwindDo:[
                updateFromSensorTask := nil
            ].
        ] priority:8.
        updateFromSensorTask resume.
        updateTaskSema signal.
    ].
!

updateFromSensorTaskStop
    |task|

    (task := updateFromSensorTask) notNil ifTrue:[
        updateFromSensorTask := nil.

        Object errorSignal handle:[:ex|
            Dialog warn:ex description.
        ]do:[
            task isDead ifFalse:[
                task terminateWithAllSubprocesses.
                task waitUntilTerminated.
            ]
        ]
    ].
!

viewedColumnsChanged

    | columns buffer |

    columns := OrderedCollection new.
    self class tableColumns do:[:el|
        columns add:(DataSetColumnSpec new fromLiteralArrayEncoding:el).
    ].
    buffer := columns copy.
    self viewDetails value = false ifTrue:[
        browser showLabels:false.
        buffer do:[:col |
            (col readSelector = #baseName or:[col readSelector = #icon]) ifFalse:[
                columns remove:col.                
            ]
        ]
    ] ifFalse:[
        browser showLabels:true.
        buffer do:[:col |
            | id |
            id := col id.
            id notNil ifTrue:[

                (id == #filename) ifTrue:[
                        self viewType value ifTrue:[
                            col readSelector:#baseNameWithOutSuffix
                        ] ifFalse:[
                            col readSelector:#baseName
                        ].
                    ] ifFalse:[
                    (id == #suffix and:[self viewType value not]) ifTrue:[
                        columns remove:col. 
                        ] ifFalse:[
                            (id == #group and:[self viewGroup value not]) ifTrue:[
                                columns remove:col. 
                            ] ifFalse:[
                                (id == #owner and:[self viewOwner value not]) ifTrue:[
                                    columns remove:col.
                                ] ifFalse:[
                                    (id == #perm and:[self viewPermissions value not]) ifTrue:[
                                        columns remove:col.
                                    ] ifFalse:[
                                        (id == #preview and:[self viewPreview value not]) ifTrue:[
                                            columns remove:col.
                                        ] ifFalse:[
                                            (id == #size and:[self viewSize value not]) ifTrue:[
                                                columns remove:col.
                                            ] ifFalse:[
                                                (id == #timeAndDate and:[self viewTime value not]) ifTrue:[
                                                    columns remove:col.
                                                ] ifFalse:[
                                                    (id == #fileInfo and:[self viewDescription value not]) ifTrue:[
                                                        columns remove:col.
                                                ] 
                                            ]
                                        ]
                                    ]
                                ]
                            ]
                        ]
                    ]
                ]
            ]
        ]
    ].
    self tableColumns value:columns.
    self columnDescriptors:(self tableColumns value).
! !

!DirectoryContentsBrowser methodsFor:'directory contents'!

evaluateFilter

    | showDir|

    showDir := self viewDirectoriesInDirectoryContentsBrowser value.

    currentItemList := OrderedCollection new.
    allItemsList size ~~ 0 ifTrue:[ 
        self filterBlock isNil ifTrue:[
            allItemsList do:[:aItem | 
                (showDir not and:[aItem isDirectory]) ifFalse:[
                    currentItemList add:aItem 
                ]
            ]
        ] ifFalse:[
            allItemsList do:[:aItem | 
                (showDir not and:[aItem isDirectory]) ifFalse:[
                    (self filterBlock value:aItem baseName) ifTrue:[currentItemList add:aItem].
                ]
            ]
        ].
    ].
    self evaluateSortBlock.
!

evaluateSortBlock

    | newList|

    newList := SortedCollection sortBlock:(self sortBlock).
    newList addAll:currentItemList.
    self descriptions:(newList asList).
!

readDirectory

    self updateDirectoryContentsTaskStop.
    allItemsList := self readDirectoryItems.
    self evaluateFilter.  "/ filter makes a sort anyway
    self updateDirectoryContentsTaskStart.
!

readDirectoryItems
    |dir list mountPoints mountInfo|

    mountPoints := OperatingSystem mountPoints.

    dir := self directory.
    list := OrderedCollection new.
    dir notNil ifTrue:[
        modificationTime := dir modificationTime.
        dir directoryContentsAsFilenamesDo:[:eachFileOrDirectory |
            mountInfo := mountPoints detect:[:mInfo | mInfo mountPointPath = eachFileOrDirectory name] ifNone:nil.
            (mountInfo notNil and:[mountInfo isRemote]) ifTrue:[
                list add:(DirectoryContentsItem withRemoteDirectory:eachFileOrDirectory).
            ] ifFalse:[
                list add:(DirectoryContentsItem with:eachFileOrDirectory).
            ].
        ].
    ].
    ^ list
! !

!DirectoryContentsBrowser methodsFor:'drag & drop'!

canDrop:aContext argument:arg2 

    | dir archivListDrop source lineNr destinationItem dropToFile|

    dir := self directory.
    dir isNil ifTrue:[ ^ false].
    source := aContext dropSource.
    source isNil ifTrue:[ ^ false].
    archivListDrop := (source argument == #archivApplication).
    lineNr := self getLineNumberFor:aContext.
    lineNr notNil ifTrue:[
        destinationItem := self descriptions at:lineNr.
    ].
    destinationItem isNil ifTrue:[
        oldDropItem notNil ifTrue:[
            self enqueueChangeDraggedItem:nil with:aContext.
            oldDropItem := nil.
        ].
        dropToFile := self directory.
    ] ifFalse:[
        dropToFile := destinationItem fileName.
    ].

    (destinationItem ~~ oldDropItem) ifTrue:[
        oldDropItem := destinationItem.
        self enqueueChangeDraggedItem:nil with:aContext.
        (destinationItem notNil and:[destinationItem isDirectory]) ifTrue:[
            self enqueueChangeDraggedItem:destinationItem with:aContext.
        ].
        archivListDrop ifTrue:[
            lastResponse := self canDropArchiv:(aContext dropObjects) for:dropToFile.
        ] ifFalse:[
            lastResponse := self canDropFiles:(aContext dropObjects) for:dropToFile.
        ]
    ].
    ^ lastResponse
!

changeDraggedItem:anItem with:aDropContext

    self changeDraggedItem:anItem with:aDropContext saveDraw:true
!

changeDraggedItem:anItem with:aDropContext saveDraw:saveDraw

    saveDraw ifTrue:[
        aDropContext saveDraw:[
            self changeIconFor:anItem.
            self windowGroup processExposeEvents
        ]
    ] ifFalse:[
        self changeIconFor:anItem.
        self windowGroup processExposeEvents
    ]
!

changeIconFor:anItemOrNil
    |icon oldItem|

    oldItem := self oldItemIcon.
    (anItemOrNil isNil or:[(oldItem key) ~~ anItemOrNil]) ifTrue:[
        (oldItem notNil and:[oldItem key notNil]) ifTrue:[
            icon := oldItem value.
            icon notNil ifTrue:[
                oldItem key icon:(oldItem value).
                browser invalidateVisibleRow:(oldItem key) colAt:iconIndex
            ].
        ].
        oldItem key:anItemOrNil.

        anItemOrNil notNil ifTrue:[
            oldItem value:(anItemOrNil icon).
            icon := FileBrowser iconForKeyMatching:#directoryOpenGray.
            icon notNil ifTrue:[
                anItemOrNil icon:icon.
                browser invalidateVisibleRow:anItemOrNil colAt:iconIndex
            ].
        ].
    ].
!

doDrop:aContext

    |col answer destinationPath source receiver lineNr destinationItem|

    source := aContext dropSource.
    lineNr := self getLineNumberFor:aContext.
    lineNr notNil ifTrue:[
        destinationItem := self descriptions at:lineNr.
    ].
    destinationItem isNil ifTrue:[
        destinationPath := self directory.
    ] ifFalse:[
        destinationPath := self getDirWithoutFileName:(destinationItem fileName).
    ].
    source argument == #archivApplication ifTrue:[
        receiver := source receiver.
        receiver extractSelectedFilesTo:destinationPath askForExtractOptions:true.
        ^ true.
    ].
    col := OrderedCollection new.
    aContext dropObjects do:[:obj|
        col add:(obj theObject).
    ].
    answer := self copyOrMoveDialog:col.
    
    answer == #copy ifTrue:[
        "/ copy files
        self copyFiles:col to:destinationPath.
    ].
    answer == #move ifTrue:[
        "/ copy files
        self moveFiles:col to:destinationPath.
    ].
    ^ true
!

doDrop:aContext argument:arg2 

    | aBoolean |

    aBoolean := self doDrop:aContext.
    self dropLeave:aContext.
    ^ aBoolean.
!

doStartDrag:aDropSource in:aView
    "set the cursors before starting the drag & drop operation
    "
    |sel|


    sel := self selectionInFileList value.
    ((((self descriptions at:(sel first)) fileName) = ('..' asFilename)) and:[ sel size == 1 ]) ifTrue:[
        ^ self.
    ].
    super doStartDrag:aDropSource in:aView
!

dropLeave:aDropContext

    oldDropItem := nil.
    self changeDraggedItem:nil with:aDropContext saveDraw:false.
    lastResponse := nil.
!

enqueueChangeDraggedItem:anItem with:aContext 

    self enqueueMessage:#'changeDraggedItem:with:' for:self arguments:(Array with:anItem with:aContext) 
!

getDisplayObjects:anArgument

    | sel string size fnName stream|
    sel := self selectionInFileList value.
    (((self descriptions at:(sel first)) fileName) = ('..' asFilename)) ifTrue:[
        sel removeFirst.
    ].
    size := sel size.
    size == 0  ifTrue:[^ ''].
    stream := WriteStream on:''.
    stream nextPutAll:(self descriptions at:sel first) fileName baseName asString.
    size == 1 ifTrue:[
        fnName := 'ui_menuitem.xpm'.
    ] ifFalse:[
        fnName := 'ui_submenu_open.xpm'.
        stream nextPutAll:' ... '.
        stream nextPutAll:(self descriptions at:sel last) fileName baseName asString.
    ].
    string := stream contents.
    stream close.
    ^ Array with:(LabelAndIcon icon:(Image fromFile:fnName)
                             string:(Text string:string emphasis:#bold)
                 )
!

getDropObjects:anArgument

    | sel ret file|
    sel := self selectionInFileList value.
    ret := sel collect:[:el| 
        file := (self descriptions at:el) fileName.
        file ~= '..' asFilename ifTrue:[
            DropObject newFile:((self descriptions at:el) fileName)
        ]
    ].
    ^ ret
!

getLineNumberFor:aDropContext
    | yVisible|

    yVisible := (aDropContext targetPoint y).
    ^ browser yVisibleToRowNr:yVisible.
! !

!DirectoryContentsBrowser methodsFor:'event handling'!

processEvent:anEvent
    "filter keyboard events.
     Return true, if I have eaten the event"

    |focusView key rawKey|

    anEvent isKeyPressEvent ifTrue:[
        focusView := anEvent targetView.
        key := anEvent key.
        rawKey := anEvent rawKey.

        (focusView == browser) ifTrue:[
"/            (key ~= #'Alt_L') ifTrue:[self halt.].
            key == #Paste ifTrue:[
                self pasteFiles.
                ^ true.
            ].
            (key == #CursorLeft) ifTrue:[
                self doGoDirectoryUp.
                ^ true
            ].
            (key == #CursorRight) ifTrue:[
                self doBack.
                ^ true
            ].
            self descriptions size == 0 ifTrue:[
                ^ false
            ].
            key == #Copy ifTrue:[
                self doCopy.
                ^ true.
            ].
            (key == #Return) ifTrue:[
                self doOpenSelectedFile.
                ^ true.
            ].
            key == #Delete ifTrue:[
                self doDelete.
                ^ true.
            ].
            key == #Cut ifTrue:[
                self doCut.
                ^ true.
            ].
        ]
    ].
    ^ false
! !

!DirectoryContentsBrowser methodsFor:'file actions'!

doCopy
    "copy current selected files/directories
    "

    self copyFilesToClipBoard:(self getFilesFromSelection).
!

doCut
    "cut current selected files/directories
    "

    self cutFilesToClipBoard:(self getFilesFromSelection).
!

doDelete
    "delete current selected files/directories
    "
    self deleteFiles:(self getFilesFromSelection).
! !

!DirectoryContentsBrowser methodsFor:'menu accessing'!

viewBrowserMenu

    <resource: #programMenu >

    |menu|

    menu :=  Menu new fromLiteralArrayEncoding:self class viewBrowserMenu.
    menu ifNil:[ ^ nil ].
    menu receiver:self.
    ^ menu
! !

!DirectoryContentsBrowser methodsFor:'menu actions'!

doOpenSelectedFile

    | sel index item|

    sel := self selectionInFileList value.
    (sel notNil and:[sel notEmpty]) ifTrue:[
        index := sel first.
        item := self descriptions at:index ifAbsent:nil.
        self enterActionFor:item.
    ].
!

doSelectAll

    | sel |

    sel := OrderedCollection new.
    self descriptions keysAndValuesDo:[:index :value |
        (index == 1 and:[(value fileName asString = '..')]) ifFalse:[
            sel add:index.
        ]
    ].
    self selectionInFileList value:sel.
!

doShowFileContents

    | sel index item|

    sel := self selectionInFileList value.
    (sel notNil and:[sel notEmpty]) ifTrue:[
        index := sel first.
        item := self descriptions at:index ifAbsent:nil.
        self openTextEditorOn:item.
    ].
! !

!DirectoryContentsBrowser methodsFor:'queries'!

allItemsOfCurrentDirectory

    ^ self descriptions.
!

fileListIsEmpty

    ^ self descriptions isEmpty
!

getAllFilesAsStringCollection
    |files items|

    items := self descriptions.
    files := OrderedCollection new.
    items isEmpty ifTrue:[
        ^ files
    ].
    items do:[:aItem | 
        aItem isDirectory not ifTrue:[
            files add:aItem fileName asString
        ].
    ].
    ^ files.
!

getFileItemsFromSelection
    |indices files|

    indices := self selectionInFileList value.
    files := OrderedCollection new.
    (indices isNil or:[indices isEmpty]) ifTrue:[
        ^ files
    ].
    indices do:[:i | 
        |item|

        item := self descriptions at:i.
        item isDirectory not ifTrue:[
            files add:item
        ]
    ].
    ^ files.
!

getFirstItemFromSelection
    |indices|

    indices := self selectionInFileList value.
    (indices isNil or:[indices isEmpty]) ifTrue:[
        ^ nil
    ].
    ^ self descriptions at:(indices first).
!

getItemsFromSelection
    |indices items|

    indices := self selectionInFileList value.
    items := OrderedCollection new.
    (indices isNil or:[indices isEmpty]) ifTrue:[
        ^ items
    ].
    indices do:[:i | 
        |filename|

        items add:(self descriptions at:i)
    ].
    ^ items.
!

getSelectedFiles

    |indices files|

    indices := self selectionInFileList value.
    files := OrderedCollection new.
    (indices isNil or:[indices isEmpty]) ifTrue:[
        ^ files
    ].
    indices do:[:i | 
        |filename|
        filename := (self descriptions at:i) fileName.
        filename asString ~= '..' ifTrue:[
            files add:(self descriptions at:i) fileName
        ]
    ].
    ^ files.
! !

!DirectoryContentsBrowser methodsFor:'sorting'!

sortList:instanceName 

    self sortList:instanceName withReverse:true.
!

sortList:instanceName withReverse:aBoolean

    | aSymbol cmpOp sortCaselessLocal|

    aSymbol := instanceName asSymbol.
    sortCaselessLocal := self sortCaseless value.
    currentSortOrder isNil ifTrue:[
        currentSortOrder := aSymbol, sortCaselessLocal asString.
        currentSortOrder := Dictionary new.
        currentSortOrder at:#column put:aSymbol.
        currentSortOrder at:#reverse put:false.
        currentSortOrder at:#sortCaseless put:sortCaselessLocal.
    ] ifFalse:[
        (currentSortOrder at:#sortCaseless) ~= sortCaselessLocal ifTrue:[
            "/ sort caseless changed
            currentSortOrder at:#sortCaseless put:sortCaselessLocal.
        ] ifFalse:[
            (currentSortOrder at:#column) = aSymbol ifTrue:[
                "/ same column like before - change sort order ifReverse is true
                aBoolean ifTrue:[
                    | isReverse |
                    isReverse := currentSortOrder at:#reverse.
                    currentSortOrder at:#reverse put:(isReverse not).
                ].
            ] ifFalse:[
                "/ another column - remark column
                currentSortOrder at:#column put:aSymbol.
            ]
        ]
    ].
    (currentSortOrder at:#reverse) ifTrue:[
        cmpOp := #'>'
    ] ifFalse:[
        cmpOp := #'<'
    ].
    self sortBlock: [:a :b | 
            |entry1 entry2|

            entry1 := (a perform:aSymbol).
            entry2 := (b perform:aSymbol).
            ((entry1 isNil) or:[entry2 isNil]) ifTrue:[
                ((entry1 isNil) and:[entry2 isNil]) ifTrue:[true] ifFalse:[
                    ((entry1 notNil) and:[entry2 isNil]) ifTrue:[
                       (currentSortOrder at:#reverse)
                    ].
                    (currentSortOrder at:#reverse) not
                ]
            ] ifFalse:[
                (aSymbol = #baseName) ifTrue:[
                    sortCaselessLocal ifTrue:[
                        entry1 := entry1 asString asLowercase.
                        entry2 := entry2 asString asLowercase.
                    ] ifFalse:[
                        entry1 := entry1 asString.
                        entry2 := entry2 asString.
                    ].
                ].
                entry1 perform:cmpOp with:entry2
            ]
    ].
    self sortBlockHolder value:instanceName.
    self evaluateSortBlock.
! !

!DirectoryContentsBrowser methodsFor:'startup / release'!

makeDependent

    self currentFileNameHolder addDependent:self.
    self viewDirectoriesInDirectoryContentsBrowser addDependent:self.
    self filterModel addDependent:self.
    self sortBlockHolder addDependent:self.
    self selectionInFileList addDependent:self.
    self sortCaseless addDependent:self.
    self viewDescription onChangeSend:#viewedColumnsChanged to:self.
    self viewDetails onChangeSend:#viewedColumnsChanged to:self.
    self viewGroup onChangeSend:#viewedColumnsChanged to:self.
    self viewOwner onChangeSend:#viewedColumnsChanged to:self.
    self viewPermissions onChangeSend:#viewedColumnsChanged to:self.
    self viewPreview onChangeSend:#viewedColumnsChanged to:self.
    self viewSize onChangeSend:#viewedColumnsChanged to:self.
    self viewTime onChangeSend:#viewedColumnsChanged to:self.
    self viewType onChangeSend:#viewedColumnsChanged to:self.
    self showHiddenFiles onChangeSend:#filterChanged to:self.
!

postBuildBrowser:aWidget

    browser       := aWidget scrolledView.
    browser wantsFocusWithPointerEnter.
"/ dont register icons anymore
"/    FileBrowser icons keysAndValuesDo:[:aKey :anIcon|
"/        browser registerImage:anIcon key:aKey.
"/    ].
    self columnDescriptors:(self class tableColumns).
!

postBuildWith:aBuilder

    self viewedColumnsChanged.
    self filterChanged.
    ^ super postBuildWith:aBuilder.
!

postOpenAsSubcanvasWith:aBuilder

    self windowGroup addPreEventHook:self.
    ^ super postOpenAsSubcanvasWith:aBuilder.
!

postOpenWith:aBuilder

    "
    only invoked if the application not started from a master
    "
    self currentFileNameHolder changed.
    self windowGroup addPreEventHook:self.
    ^ super postOpenWith:aBuilder.
!

preBuildWith:aBuilder

    self masterApplication isNil ifTrue:[
        self masterApplication:nil.
    ].
    ^ super preBuildWith:aBuilder.
!

release
    "release my resources
    "                    
    self updateColumnsTaskStop.
    self updateDirectoryContentsTaskStop.
    self updateFromSensorTaskStop.
    super release.
!

releaseAsSubCanvas

    self updateColumnsTaskStop.
    self updateDirectoryContentsTaskStop.
    ^ super releaseAsSubCanvas.
! !

!DirectoryContentsBrowser methodsFor:'task update columns'!

findNextBuildInDescFor:aBlock
    |vis idx|

    vis := browser indexOfFirstRowShown.
    vis = 0 ifTrue:[vis := 1].
    idx := self descriptions findFirst:aBlock startingAt:vis.

    idx == 0 ifTrue:[
        (idx := self descriptions findLast:aBlock startingAt:(vis - 1)) == 0 ifTrue:[
            ^ nil
        ]
    ].
    ^ self descriptions at:idx ifAbsent:nil
!

findNextItemFor:aBlock

   ^ allItemsList detect:aBlock ifNone:[nil].
!

getFileInfoFor:aItem

    | type|

    type := aItem fileName fileType.
    aItem fileType:type.
    ^ type
!

getIconFor:aItem

    | key icon|

    key := aItem iconKey.
    icon := FileBrowser iconForKeyMatching:key.
    aItem isRemoteDirectory ifTrue:[
        ^ icon "/ do not query attributes
    ].
    ^ FileBrowser addOnIconsFor:(aItem fileName) to:icon.
!

getIconForMimeTypeByContents:aItem

    | currentIcon newIcon icon|
    newIcon := FileBrowser iconForKeyMatching:(aItem mimeTypeForContents).
    currentIcon := aItem icon.
    (currentIcon class == MultiImage) ifTrue:[
        | images |
        images := currentIcon images.
        images removeFirst.
        images addFirst:newIcon.
        icon := currentIcon.
    ] ifFalse:[
        icon := newIcon.
    ].
    ^ icon
!

getPreviewFor:aItem

    | column icon|
    
    column := browser columnAt:previewIndex.
    column isNil ifTrue:[
        previewIndex := 0.
    ] ifFalse:[
        (aItem hasMimeType and:[aItem mimeType isImage]) ifTrue:[
            Exception handle:[:ex|
                icon := nil.
            ] do:[ |scale extent w h |
                icon := Image fromFile:(aItem pathName).
                icon notNil ifTrue:[
                    extent := icon extent.
                    w := column width - 4.
                    h := column height.

                    (extent x > w or:[extent y > h]) ifTrue:[
                        scale := extent / (w@h).
                        scale := scale x max:(scale y).
                        icon  := icon magnifiedTo:((extent / scale max:8.0) asInteger).
                    ].
                    icon := icon onDevice:(browser device).
                    icon clearMaskedPixels.
                ]
            ]
        ] ifFalse:[ icon := nil].
        aItem preview:(icon ? '').
    ].
    ^ icon
!

updateChangesFor:aItem

    aItem modifiedByMe ifTrue:[
        aItem modifiedByMe:false.
    ] ifFalse:[
        self notifyChannel value:aItem fileName asString, ' was changed by someone else'.
        aItem resetItemForChangesFromSomeOneElse.
    ].
!

updateColumnsTaskCycle

" update items in the foolowing order
    - update item if item properties are changed
    - update icon for visible items by suffix
    - update icon for visible items by contents
    - update file info for visible items if file info column is shown 
    - update preview for visible items if preview column is shown 

    - update icon for invisible items by suffix
    - update icon for invisible items by contents
    - update file info for invisible items if file info column is shown 
    - update preview for invisible items if preview column is shown 
    - update file info for invisible items if file info column is not shown 
"

    |desc icon type preview|

    currentItemList notEmpty ifTrue:[
        "/ update item if item properties are changed
        desc := self findNextBuildInDescFor:[:n| n changedForDirectoryContentsBrowser ].
        
        desc notNil ifTrue:[
            browser invalidateVisibleRow:desc.
            self updateChangesFor:desc.
            ^ true.
        ].
        "/ update icon for visible items by suffix
        desc := self findNextBuildInDescFor:[:n| n icon isNil ].
        desc notNil ifTrue:[
            icon := self getIconFor:desc.
            desc icon:icon.
            (icon notNil and:[iconIndex ~~ 0]) ifTrue:[
                browser invalidateVisibleRow:desc colAt:iconIndex
            ].
            ^ true
        ].
        "/ update icon for visible items by contents
        desc := self findNextBuildInDescFor:[:n| 
            (n iconKey == #file 
            and:[n mimeTypeForContents asSymbol ~= #unknown])
        ].
        desc notNil ifTrue:[
            icon := self getIconForMimeTypeByContents:desc.
            desc icon:icon.
            (icon notNil and:[iconIndex ~~ 0]) ifTrue:[
                browser invalidateVisibleRow:desc colAt:iconIndex
            ].
            ^ true
        ].
        "/ update file info for visible items if file info column is shown
        fileDescriptionIndex ~~ 0 ifTrue:[
            desc := self findNextBuildInDescFor:[:n| n fileType isNil ].
            desc notNil ifTrue:[
                type := self getFileInfoFor:desc.
                type notNil ifTrue:[browser invalidateVisibleRow:desc colAt:fileDescriptionIndex].
                ^ true
            ]
        ].
        "/ update preview for visible items if preview column is shown
        previewIndex ~~ 0 ifTrue:[
            desc := self findNextBuildInDescFor:[:n| n preview isNil ].
            desc notNil ifTrue:[
                preview := self getPreviewFor:desc.
                preview notNil ifTrue:[
                    browser invalidateVisibleRow:desc colAt:previewIndex
                ].
                ^ true
            ]
        ].
    ].

    "/ may we want to see the other items later make this items to
    allItemsList notEmpty ifTrue:[
        "/ update icon for invisible items by suffix
        desc := self findNextItemFor:[:n| n icon isNil ].
        desc notNil ifTrue:[
            icon := self getIconFor:desc.
            desc icon:icon.
            ^ true
        ].
        "/ update icon for invisible items by contents
        desc := self findNextItemFor:[:n| (n iconKey == #file and:[n mimeTypeForContents ~= #unknown])].
        desc notNil ifTrue:[
            icon := self getIconForMimeTypeByContents:desc.
            desc icon:icon.
            ^ true
        ].
        "/ update file info for invisible items if file info column is shown
        fileDescriptionIndex ~~ 0 ifTrue:[
            desc := self findNextItemFor:[:n| n fileType isNil ].
            desc notNil ifTrue:[
                self getFileInfoFor:desc.
                ^ true
            ].
        ].
        "/ update preview for invisible items if preview column is shown 
        previewIndex ~~ 0 ifTrue:[
            desc := self findNextItemFor:[:n| n preview isNil ].
            desc notNil ifTrue:[
                self getPreviewFor:desc.
                ^ true
            ].
        ].
        "/ update file info for invisible items if file info column is not shown
        desc := self findNextItemFor:[:n| n fileType isNil ].
        desc notNil ifTrue:[
            self getFileInfoFor:desc.
            ^ true
        ].
    ].
    ^ false
!

updateColumnsTaskRestart

    self updateColumnsTaskStop.
    self updateColumnsTaskStart.
!

updateColumnsTaskStart

    | somethigToDo p |

    somethigToDo := true.
    self directory notNil ifTrue:[
        p := [ 
                 [   
                     [true] whileTrue:[
                         somethigToDo := false.
                         (browser shown and:[self directory notNil]) ifTrue:[
                             somethigToDo := self updateColumnsTaskCycle.
                             Processor yield.
                         ].
                         somethigToDo ifFalse:[
                             Delay waitForSeconds:1.0
                         ]
                     ]
                 ] valueNowOrOnUnwindDo:[
                     self updateColumnsTask:nil.
                 ]
             ] newProcess.
        p priority:(Processor systemBackgroundPriority).

        self updateColumnsTask: p.   
        self updateColumnsTask name:('DirectoryContentsBrowser update columns[', self directory baseName, ']').
        self updateColumnsTask resume.
    ]
!

updateColumnsTaskStop
    |task|

    (task := self updateColumnsTask) notNil ifTrue:[
        self updateColumnsTask:nil.

        Object errorSignal handle:[:ex|
            Dialog warn:ex description.
        ]do:[
            task isDead ifFalse:[
                task terminateWithAllSubprocesses.
                task waitUntilTerminated.
            ]
        ]
    ].
! !

!DirectoryContentsBrowser methodsFor:'task update dir contents'!

updateDirectoryContentsTaskCycle

    | contents|

    "/ DIRECTORY CONTNETS HAVE CHANGED
    (modificationTime ~= self directory modificationTime) ifTrue:[
        modificationTime := self directory modificationTime.
        contents := self directory directoryContentsAsFilenames.
        contents size == 0 ifTrue:[ 
            allItemsList removeAll. 
        ] ifFalse:[
            "/ MERGE WITH CURRENT CONTENTS
            allItemsList reverseDo:[:aDesc|
                "/ remove no longer valid files
                (contents detect:[:f| aDesc fileName = f] ifNone:nil) isNil ifTrue:[
                    allItemsList removeIdentical:aDesc
                ]
            ].
            allItemsList size ~~ contents size ifTrue:[
                "/ add new files
                | addedFiles |
                addedFiles := OrderedCollection new.
                contents reverseDo:[:aFile|
                    (allItemsList detect:[:item| item fileName = aFile] ifNone:[nil]) isNil ifTrue:[
                        addedFiles add:(DirectoryContentsItem with:aFile)
                    ]
                ].
                allItemsList addAll:addedFiles.
            ]
        ].
        self evaluateFilter.
    ].
!

updateDirectoryContentsTaskRestart

    self updateDirectoryContentsTaskStop.
    self updateDirectoryContentsTaskStart.
!

updateDirectoryContentsTaskStart
    |p|

    self directory notNil ifTrue:[
        p := [ 
                 [   
                     [true] whileTrue:[
                         (browser shown and:[self directory notNil]) ifTrue:[
                             self updateDirectoryContentsTaskCycle.
                             Processor yield.
                         ].
                         Delay waitForSeconds:1.0
                     ]
                 ] valueNowOrOnUnwindDo:[
                     self updateDirectoryContentsTask:nil.
                     self updateColumnsTaskStop.
                 ]
              ] newProcess.

        p priority:(Processor systemBackgroundPriority).
        self updateDirectoryContentsTask:p.

        p name:('DirectoryContentsBrowser update contents[', self directory baseName, ']').
        p resume.
    ]
!

updateDirectoryContentsTaskStop
    |task|

    (task := self updateDirectoryContentsTask) notNil ifTrue:[
        self updateDirectoryContentsTask:nil.

        Object errorSignal handle:[:ex|
            Dialog warn:ex description.
        ]do:[
            task isDead ifFalse:[
                task terminateWithAllSubprocesses.
                task waitUntilTerminated.
            ]
        ]
    ].
! !

!DirectoryContentsBrowser::DirectoryContentsItem class methodsFor:'instance creation'!

with:aFilename

    | instance |
    instance := self new.
    instance fileName:aFilename.
    instance initialize.
    ^ instance
!

withRemoteDirectory:aFilename

    | instance |
    instance := self new.
    instance fileName:aFilename.
    instance beRemoteDirectory.
    instance initialize.
    ^ instance
! !

!DirectoryContentsBrowser::DirectoryContentsItem methodsFor:'accessing'!

fileName
    "returns the fileName
    "
    ^ fileName


!

fileName:aFilename
    "set fileName for instance
    "
    fileName := aFilename.
!

fileType
    "returns the type of the file
    "

    ^ fileType 
!

fileType:aType
    "returns the type of the file
    "

    fileType := aType.
!

icon
    "returns the icon assigned to the file
    "
    ^ icon


!

icon:anIcon

    icon := anIcon.
!

iconKey
    "return the value of the instance variable 'iconKey' (automatically generated)"

    iconKey isNil ifTrue:[
        self isRemoteDirectory ifTrue:[
            iconKey := FileBrowser iconKeyForRemoteDirectory:fileName.
        ] ifFalse:[
            iconKey := FileBrowser iconKeyForFile:fileName.
        ]
    ].
    ^ iconKey
!

isDirectory
    "return the value of the instance variable 'isDirectory' (automatically generated)"

    isDirectory isNil ifTrue:[
        isDirectory := self fileName isDirectory.
    ].
    ^ isDirectory
!

lastButOneSuffix
    "returns the suffix of the file
    "

    ^ self suffixes at:2
!

lastSuffix
    "returns the suffix of the file
    "

    ^ self suffixes at:1
!

link
    "returns the type of the file
    "
    link isNil ifTrue:[
        link := fileName isSymbolicLink.
    ].
    ^ link
!

linkInfo
    "returns the type of the file
    "
    linkInfo isNil ifTrue:[
        linkInfo := self fileName linkInfo.
    ].
    ^ linkInfo
!

mimeType
    "return the value of the instance variable 'mimeType' (automatically generated)"

    mimeType isNil ifTrue:[
        mimeType := MIMETypes mimeTypeForFilename:(self fileName).
        mimeType isNil ifTrue:[
            mimeType := #unknown
        ]
    ].
    ^ mimeType
!

modifiedByMe
    "return the value of the instance variable 'modifiedByMe' (automatically generated)"

    ^ modifiedByMe
!

modifiedByMe:something
    "set the value of the instance variable 'modifiedByMe' (automatically generated)"

    modifiedByMe := something.
!

preview

    ^ preview
!

preview:aLabel

    preview := aLabel.
!

suffix
    "returns the suffix of the file
    "
    suffix isNil ifTrue:[
        (OperatingSystem isUNIXlike and:[(self baseName at:1) == $.]) ifTrue:[
            suffix := ''.
        ] ifFalse:[
            suffix := fileName suffix.
        ]
    ].
    ^ suffix
!

suffixes

    | lastSuff lastButOneSuff|


    suffixes isNil ifTrue:[
        ((lastSuff := self suffix) isEmpty) ifTrue:[  
            lastSuff := nil.
        ] ifFalse:[
            lastSuff := lastSuff asLowercase. 
        ].
        ((lastButOneSuff := fileName withoutSuffix suffix) isEmpty) ifTrue:[  
            lastButOneSuff := nil.
        ] ifFalse:[
            lastButOneSuff := lastButOneSuff asLowercase. 
        ].

        (lastSuff notNil and:[lastButOneSuff notNil]) ifTrue:[
            (lastSuff = 'bak' or:[lastSuff = 'sav']) ifTrue:[
                lastSuff := lastButOneSuff.
                lastButOneSuff := nil.
            ]
        ].
        suffixes := Array with:lastSuff with:lastButOneSuff
    ].
    ^ suffixes
!

type
    "returns the type of the file
    "
    ^ self valueAt:#type

! !

!DirectoryContentsBrowser::DirectoryContentsItem methodsFor:'actions'!

resetImageFile

    " if the image preview extent is changed the icon will set to nil to force the update task to reload the image "

    (mimeType notNil and:[ mimeType isImage]) ifTrue:[
        preview := nil
    ]
!

resetItemForChangesFromSomeOneElse

    " dont know what someone else is doing with the file - read all item properties new "
    link := nil.
    linkInfo := nil.
    fileInfo := nil.
    size := nil.
    icon := nil.
    link := nil.
    fileType := nil.
    preview := nil.
    owner := nil.
    modificationTime := nil.
    permissions := nil.
    timeAndDate := nil.
    date := nil.
    group := nil.
!

resetItemForTextEditorChange

    " read size and time item properties new "
    size := nil.
    linkInfo := nil.
    fileInfo := nil.
    timeAndDate := nil.
    date := nil.
    modificationTime := nil.
    modifiedByMe := true.
"/    textEditorChangeModificationTime := nil.
! !

!DirectoryContentsBrowser::DirectoryContentsItem methodsFor:'change queries'!

changedForDirectoryContentsBrowser

    "check if item modification time is changed 
    "
    | time info|

    self isRemoteDirectory ifTrue:[^ false].

    info := fileName asAbsoluteFilename info.
    info isNil ifTrue:[ ^ false].
    time := info modified.
    contentsBrowserChangeModificationTime isNil ifTrue:[
        contentsBrowserChangeModificationTime := time.
        ^ false.
    ].
    ((contentsBrowserChangeModificationTime = time) not) ifTrue:[
        contentsBrowserChangeModificationTime := time.
        ^ true
    ].
    ^ false
! !

!DirectoryContentsBrowser::DirectoryContentsItem methodsFor:'compare'!

= aItem

"/    Transcript showCR:(aItem species == self species).
    aItem species == self species ifFalse:[  ^ false].
    aItem isNil ifTrue:[ ^ false].
    ^ (self fileName = aItem fileName)
!

hash
    ^ fileName hash
! !

!DirectoryContentsBrowser::DirectoryContentsItem methodsFor:'instance creation'!

initialize
    "initialize instance 
    "
    contentsBrowserChangeModificationTime := (self valueAt:#modified).  "/ remark modification to get changes of file in contents browser
! !

!DirectoryContentsBrowser::DirectoryContentsItem methodsFor:'presentation'!

baseName
    "returns the baseName of the file
    "
    baseName isNil ifTrue:[
        baseName := fileName baseName.
    ].
    ^ baseName
!

baseName:aName
    "rename the file
    "
    ^ self
!

baseNameWithOutSuffix
    "returns the baseName of the file
    "
    | baseNameWithOutSuff |

    baseNameWithOutSuff := self baseName asFilename withoutSuffix asString.
    baseNameWithOutSuff isEmpty ifTrue:[
        ^ self baseName
    ].
    ^ baseNameWithOutSuff.
!

date
    "returns the date
    "
    | modTime |

    date isNil ifTrue:[
        modTime := self valueAt:#modified.
        modTime isNil ifTrue:[
            date := '???'
        ] ifFalse:[
            date := modTime asDate printString.
        ]
    ].
    ^ date
!

fileInfoString
    "get stat info on selected file - return a string which can be
     shown in a box"

    | afileType |

    fileName isNil ifTrue:[^ nil].
    afileType := self fileType.
    afileType isNil ifTrue:[
        ^ ''.
    ].
    ^ afileType
!

group
    "returns the printable group
    "
    |gid|

    group isNil ifTrue:[
        gid := self valueAt:#gid.
        group := gid notNil ifTrue:[OperatingSystem getGroupNameFromID:gid] ifFalse:[^ '???']
    ].
    ^ group
!

modificationTime
    "returns the absolute time of modification
    "
    modificationTime isNil ifTrue:[
        modificationTime := self valueAt:#modified.
    ].
    ^ modificationTime
!

owner
    "returns the printable owner
    "
    |uid|

    owner isNil ifTrue:[
        uid := self valueAt:#uid.
        owner := uid notNil ifTrue:[OperatingSystem getUserNameFromID:uid] ifFalse:[^ '???'].
    ].
    ^ owner
!

pathName
    "returns the pathName of the file
    "
    pathName isNil ifTrue:[
        pathName := fileName pathName.
    ].
    ^ pathName
!

permissions
    "returns the permissions as printable string
    "
    |mode|

    permissions isNil ifTrue:[
        (mode := self valueAt:#mode) notNil ifTrue:[
            permissions := String new:9 withAll:$-.

            1 to:9 by:3 do:[:i|
                (mode bitAt:i    ) == 1 ifTrue:[permissions at:10 - i put:$x].
                (mode bitAt:i + 1) == 1 ifTrue:[permissions at:9  - i put:$w].
                (mode bitAt:i + 2) == 1 ifTrue:[permissions at:8  - i put:$r].
            ]
        ] ifFalse:[
            permissions := '???'.
        ].
    ].
    ^ permissions
!

printString
    ^ fileName asString
!

size
    "returns the size of the file
    "
    ^ self valueAt:#size 
!

sizeString
    "returns the size of the file
    "
    | unitString n locSize|

    size isNil ifTrue:[
        unitString := ''.
        locSize := self valueAt:#size.
        locSize notNil ifTrue:[
            locSize < (500 * 1024) ifTrue:[
                locSize < 1024 ifTrue:[
                    n := locSize
                ] ifFalse:[
                    n := (locSize * 10 // 1024 / 10.0).
                    unitString := ' Kb'
                ]
            ] ifFalse:[
                n := (locSize * 10 // 1024 // 1024 / 10.0).
                unitString := ' Mb'
            ].
            locSize := (n printStringLeftPaddedTo:5) , unitString.
        ] ifFalse:[
            locSize := '???'
        ].
        size := locSize.
    ].
    ^ size
!

timeAndDate
    "returns the date
    "
    | date modTime stream|

    timeAndDate isNil ifTrue:[
        modTime := self valueAt:#modified.
        modTime isNil ifTrue:[
            timeAndDate :=  '???'
        ] ifFalse:[
            stream := WriteStream on:''.
            date := modTime asDate.
            stream nextPutAll:date printString.
            stream space.
            modTime printOn:stream format:'%h:%m:%s'.
            timeAndDate := stream contents.
            stream close.
        ]
    ].
    ^ timeAndDate
! !

!DirectoryContentsBrowser::DirectoryContentsItem methodsFor:'private'!

valueAt:aKey

    |info|

    fileInfo isNil ifTrue:[
        fileInfo := IdentityDictionary new.
        self isRemoteDirectory ifTrue:[
            "/ do not access, to avoid automount
        ] ifFalse:[
            info := fileName linkInfo.
            info notNil ifTrue:[
                link := true
            ] ifFalse:[
                info := fileName info.
            ].
            info notNil ifTrue:[
                fileInfo at:#size       put:(info size).
                fileInfo at:#mode       put:(info mode).
                fileInfo at:#type       put:(info type).
                fileInfo at:#uid        put:(info uid).
                fileInfo at:#gid        put:(info gid).
                fileInfo at:#accessed   put:(info accessed).
                fileInfo at:#modified   put:(info modified).
            ]
        ]
    ].
    ^ fileInfo at:aKey ifAbsent:nil
! !

!DirectoryContentsBrowser::DirectoryContentsItem methodsFor:'queries'!

beRemoteDirectory
    fileType := #remoteDirectory.
    isDirectory := true.
!

exists
    "returns the fileName
    "
    ^ fileName exists 
!

hasMimeType

    ^ (self mimeType ~= #unknown)
!

isRemoteDirectory
    ^ fileType == #remoteDirectory
!

mimeTypeForContents

    |mimeTypeForContents|

    mimeTypeForContents isNil ifTrue:[
        mimeTypeForContents := MIMETypes mimeTypeOfContents:(self fileName).
        mimeTypeForContents isNil ifTrue:[
            mimeTypeForContents := #unknown
        ] ifFalse:[
            " set mimeType to mimeType of contents if a better mimeType is found "
            mimeType := mimeTypeForContents.
            iconKey := mimeTypeForContents.
        ]
    ].
    ^ mimeTypeForContents
! !

!DirectoryContentsBrowser class methodsFor:'documentation'!

version
    ^ '$Header: /cvs/stx/stx/libtool/DirectoryContentsBrowser.st,v 1.4 2002-09-27 15:23:00 penk Exp $'
! !