HierarchicalFileList.st
author penk
Tue, 20 Aug 2002 09:28:25 +0200
changeset 2140 7dfe3b338437
parent 2138 4175c127f9b8
child 2180 d01612ed451e
permissions -rw-r--r--
expanded icon comes from HierarchicalFileList now not the default icon anymore

"
 COPYRIGHT (c) 1999 by eXept Software AG
              All Rights Reserved

 This software is furnished under a license and may be used
 only in accordance with the terms of that license and with the
 inclusion of the above copyright notice.   This software may not
 be provided or otherwise made available to, or used by, any
 other person.  No title to or ownership of the software is
 hereby transferred.
"


"{ Package: 'stx:libwidg2' }"

HierarchicalList subclass:#HierarchicalFileList
	instanceVariableNames:'icons matchBlock showCursor'
	classVariableNames:''
	poolDictionaries:''
	category:'Views-Support'
!

HierarchicalItem subclass:#File
	instanceVariableNames:'fileName icon'
	classVariableNames:''
	poolDictionaries:''
	privateIn:HierarchicalFileList
!

HierarchicalFileList::File subclass:#Directory
	instanceVariableNames:'modificationTime'
	classVariableNames:''
	poolDictionaries:''
	privateIn:HierarchicalFileList::File
!

!HierarchicalFileList class methodsFor:'documentation'!

copyright
"
 COPYRIGHT (c) 1999 by eXept Software AG
              All Rights Reserved

 This software is furnished under a license and may be used
 only in accordance with the terms of that license and with the
 inclusion of the above copyright notice.   This software may not
 be provided or otherwise made available to, or used by, any
 other person.  No title to or ownership of the software is
 hereby transferred.
"

! !

!HierarchicalFileList class methodsFor:'examples'!

test
"
self test
"
    |top sel list|

    list := HierarchicalFileList new.
    list directory:(Filename homeDirectory).
    list showRoot:false.
    list matchBlock:[:fn :isDir| true ].

    top := StandardSystemView new; extent:300@300.
    sel := ScrollableView for:HierarchicalListView miniScroller:true
                       origin:0.0@0.0 corner:1.0@1.0 in:top.

    sel list:list.
    list root expand.

    sel doubleClickAction:[:i| (list at:i) toggleExpand ].
    sel   indicatorAction:[:i| (list at:i) toggleExpand ].

    top open.
! !

!HierarchicalFileList class methodsFor:'resources'!

icons
    ^ FileBrowser icons.




! !

!HierarchicalFileList methodsFor:'accessing'!

directory
    "returns the root directory or nil
    "
    ^ root notNil ifTrue:[root fileName] ifFalse:[nil]

!

directory:aDirectory
    "set the root directory or nil
    "
    |directory|

"/    must explicitly enabled by user: ca
"/    monitoringTaskDelay := 1.

    (aDirectory notNil and:[(directory := aDirectory asFilename) exists]) ifTrue:[
        directory isDirectory ifFalse:[
            directory := directory directory
        ]
    ] ifFalse:[
        directory := nil
    ].

    directory = self directory ifFalse:[
        directory notNil ifTrue:[
            directory := File fileName:directory isDirectory:true
        ].
        self root:directory
    ].
! !

!HierarchicalFileList methodsFor:'actions'!

matchBlock
    "set the matchBlock - if non-nil, it controls which files are visible.
    "
    ^ matchBlock
        
!

matchBlock:aBlock
    "set the matchBlock - if non-nil, it controls which files are visible.
    "
    matchBlock := aBlock.

    root notNil ifTrue:[
        self recursionLock critical:[
            self stopMonitoringTask.
            root matchBlockChanged.
        ].
        self startMonitoringTask.
    ].

! !

!HierarchicalFileList methodsFor:'private monitoring task'!

monitoringCycle
    "the block evaluated by the monitoring task; test whether directory is expanded
     otherwise we have not to evaluate the directory contents.
     think about remote file-systems
    "
    |index item|

    item  := root.
    index := showRoot ifTrue:[1] ifFalse:[0].

    [item notNil] whileTrue:[
        (item isDirectory and:[item getChildren notNil]) ifTrue:[
            "/ children already initialized; thus we can update the contents
            item monitoringCycle
        ].
        Processor yield.
        index := index + 1.
        item  := self at:index ifAbsent:nil.
    ].
! !

!HierarchicalFileList methodsFor:'protocol'!

childrenFor:anItem
    "returns all visible children derived from the physical
     directory contents.
    "
    |contents list block|

    anItem isDirectory ifFalse:[^ #()].

    contents := DirectoryContents directoryNamed:(anItem fileName).
    contents ifNil:[ ^ #() ].


    list  := SortedCollection sortBlock:[:a :b| |res|
        a isDirectory == b isDirectory ifTrue:[ res := a baseName <= b baseName ]
                                      ifFalse:[ res := a isDirectory ].
        res
    ].
    block := self matchBlockFor:anItem.

    block isNil ifTrue:[
        contents contentsDo:[:fn :isDir|
            list add:(File fileName:fn isDirectory:isDir)
        ]
    ] ifFalse:[
        contents contentsDo:[:fn :isDir|
            (block value:fn value:isDir) ifTrue:[
                list add:(File fileName:fn isDirectory:isDir)
            ]
        ]
    ].
    list isEmpty ifTrue:[^ #()].
  ^ list asOrderedCollection
!

hasChildrenFor:anItem
    "returns true if the physical directory contains at least
     one visible item otherwise false.
    "
    |block|

    anItem isDirectory ifFalse:[
        ^ false
    ].

    (block := self matchBlockFor:anItem) isNil ifTrue:[
        block := [:aFilename :isDirectory| true ]
    ].
    ^ DirectoryContents directoryNamed:(anItem fileName) detect:block
!

iconFor:anItem
    "returns the icon for an item
    "
    |fn key suff mimeType icn|

    fn := anItem fileName.
    icons isNil ifTrue:[ icons := self class icons ].

    fn isDirectory ifTrue:[
        icn := (anItem isExpanded and:[anItem hasChildren]) ifTrue:[icons at:#directoryOpen ifAbsent:nil].
        icn notNil ifTrue:[^ icn].

        (fn isReadable and:[fn isExecutable]) ifTrue:[
            key := fn isSymbolicLink ifTrue:[#directoryLink]
                                    ifFalse:[#directory]
        ] ifFalse:[
            key := #directoryLocked
        ].
        ^ icons at:key ifAbsent:nil
    ].

    fn isReadable     ifFalse:[ ^ icons at:#fileLocked ifAbsent:nil ].
    fn isSymbolicLink  ifTrue:[ ^ icons at:#fileLink   ifAbsent:nil ].

    suff := fn suffix.

    (suff = 'bak' or:[suff = 'sav']) ifTrue:[
        suff := fn withoutSuffix suffix.
    ].

    suff size > 0 ifTrue:[
        ( #( 'o' 'so' 'a' ) includes:suff) ifTrue:[
             ^ icons at:#executableFile ifAbsent:nil
        ].        
        mimeType := MIMETypes mimeTypeForSuffix:suff.

        mimeType notNil ifTrue:[
            (icn := icons at:mimeType ifAbsent:nil) notNil ifTrue:[^ icn].

            (mimeType startsWith:'image/') ifTrue:[
                ^ icons at:#imageFile ifAbsent:nil
            ].
        ].
    ].

    fn isExecutableProgram ifTrue:[
        ^ icons at:#executableFile ifAbsent:nil
    ].
    ^ icons at:#file ifAbsent:nil
!

matchBlockFor:anItem
    "get the matchBlock - if non-nil, it controls which files are
     visible within the physical directory
    "
    ^ matchBlock        
!

validateIcon:anIcon for:anItem
    ^ anIcon
! !

!HierarchicalFileList methodsFor:'searching'!

detectItemFor:aPathOrFilename
    "make a filename visible
    "
    |file node p1 p2 sz|

    aPathOrFilename isNil ifTrue:[^ nil].

    file := aPathOrFilename asFilename.

    file exists ifFalse:[
        ^ nil
    ].
    (node := root) isNil ifTrue:[^ nil].

    p1 := node pathName.
    p2 := file pathName.

    (p2 startsWith:p1) ifFalse:[^ nil].
    sz := 1 + p1 size.

    (p1 last == Filename separator) ifFalse:[
        sz := sz + 1.
    ].

    p2 size > sz ifFalse:[^ nil].

    p2 :=  Filename components:(p2 copyFrom:sz).

    p2 do:[:bn|
        node := node detect:[:el| el baseName = bn] ifNone:nil.
        node isNil ifTrue:[^ nil ].
    ].
    ^ node
! !

!HierarchicalFileList::File class methodsFor:'instance creation'!

fileName:aFileName isDirectory:isDirectory
    "instance creation
    "
    |item|

    item := isDirectory ifTrue:[Directory new] ifFalse:[HierarchicalFileList::File new].
    item fileName:aFileName.
  ^ item

! !

!HierarchicalFileList::File methodsFor:'accessing'!

baseName
    "returns the baseName
    "
    ^ fileName baseName.
"/    ^ baseName
!

children
    "always returns an empty list
    "
    ^ #()
!

fileName
    "returns the fileName
    "
    ^ fileName


!

fileName:fname
    "instance creation
    "
    fileName := fname.
"/    baseName := fname baseName.
!

icon
    "returns the icon key
    "
    |model|

    model := self model.
    model ifNil:[^ nil].

    icon isNil ifTrue:[
        icon := model iconFor:self
    ].
    ^ model validateIcon:icon for:self
!

label
    "returns the printable name, the baseName
    "
    ^ self baseName
!

pathName
    "returns the pathName
    "
    ^ fileName pathName
! !

!HierarchicalFileList::File methodsFor:'accessing hierarchy'!

recursiveExpand
    "redefined to expand
    "
    self expand


! !

!HierarchicalFileList::File methodsFor:'invalidate'!

invalidate
    "invalidate the contents
    "
    self invalidateRepairNow:false

!

invalidateRepairNow
    "invalidate the contents; repair now
    "
    self invalidateRepairNow:true

!

invalidateRepairNow:doRepair
    "invalidate the contents; dependent on the boolean
     do repair immediately
    "


!

matchBlockChanged
    "called if the matchBlock changed
    "

! !

!HierarchicalFileList::File methodsFor:'printing'!

printString

    ^ super printString, ' for: ', self fileName asString
! !

!HierarchicalFileList::File methodsFor:'queries'!

hasChildren
    "always returns false
    "
    ^ false
!

isDirectory
    "always returns false
    "
    ^ false

! !

!HierarchicalFileList::File::Directory methodsFor:'accessing'!

children
    "returns the list of children
    "
    |model list|

    children notNil ifTrue:[
        ^ children
    ].

    (model := self model) isNil ifTrue:[
        "/ must reread later
        modificationTime := children := nil.
        ^ nil
    ].

    children := #().
    modificationTime := fileName modificationTime.
    list := model childrenFor:self.

    list size ~~ 0 ifTrue:[
        list do:[:aChild| aChild parent:self].
        children := list.
    ].
    ^ children
!

icon
    "reset first and returns the new icon
    "
    icon := nil.    
    ^ super icon
! !

!HierarchicalFileList::File::Directory methodsFor:'accessing-hierarchy'!

expand
    "expand children; must redefine to raise a notification
     if children are not yet initialized and after initialization empty.
    "
    |notInit|

    notInit := children isNil.
    super expand.

    notInit ifTrue:[
        (children notNil and:[children isEmpty]) ifTrue:[
            "/ no children exists
            "/ indicator +/- has changed to unexistant
            self changed:#redraw
        ]
    ].
! !

!HierarchicalFileList::File::Directory methodsFor:'queries'!

hasChildren
    "returns true if children exists
    "
    ^ children isNil or:[children notEmpty]
!

isDirectory
    "always returns true
    "
    ^ true


! !

!HierarchicalFileList::File::Directory methodsFor:'validation'!

invalidateRepairNow:doRepair
    "invalidate contents
    "
    modificationTime := nil.

    doRepair ifTrue:[
        self monitoringCycle
    ] ifFalse:[
        (isExpanded or:[children size == 0]) ifFalse:[
            children := nil
        ]
    ].

!

matchBlockChanged
    "called if the matchBlock changed
    "
    modificationTime := nil.

    isExpanded ifFalse:[
        children := nil.
    ] ifTrue:[
        self monitoringCycle.

        children size ~~ 0 ifTrue:[
            children do:[:aChild| aChild matchBlockChanged ]
        ]
    ].
!

monitoringCycle
    "run monitoring cycle
    "
    |list size name modifyTime isNotEmpty wasNotEmpty model|

    modifyTime := fileName modificationTime.

    (modificationTime notNil and:[modifyTime <= modificationTime]) ifTrue:[
        ^ self
    ].
    model := self model.
    modificationTime := modifyTime.

    isExpanded ifFalse:[

     "/ CHECK WHETHER CHILDREN EXIST( INDICATOR )
     "/ =========================================

        isNotEmpty := model hasChildrenFor:self.

     "/ check whether has changed durring evaluation
        (isExpanded or:[modificationTime ~= modifyTime]) ifFalse:[
            wasNotEmpty := children isNil.
            children    := isNotEmpty ifTrue:[nil] ifFalse:[#()].

            wasNotEmpty ~~ isNotEmpty ifTrue:[
                self changed
            ]
        ].
        ^ self

    ].

 "/ START MERGING( CONTENTS IS VISIBLE )
 "/ ====================================

    list := model childrenFor:self.

    list size == 0 ifTrue:[                         "/ contents becomes empty
        ^ self removeAll                            "/ clear contents
    ].
    (size := children size) == 0 ifTrue:[           "/ old contents was empty 
        ^ self addAll:list.                         "/ take over new contents
    ].

    size to:1 by:-1 do:[:anIndex|                   "/ remove invisible items
        name := (children at:anIndex) baseName.

        (list findFirst:[:i|i baseName = name]) == 0 ifTrue:[
            self removeIndex:anIndex
        ]
    ].

    list keysAndValuesDo:[:anIndex :anItem|         "/ add new visible items
        name := anItem baseName.

        (children findFirst:[:i|i baseName = name]) == 0 ifTrue:[
            self add:anItem beforeIndex:anIndex
        ]
    ].
! !

!HierarchicalFileList class methodsFor:'documentation'!

version
    ^ '$Header: /cvs/stx/stx/libwidg2/HierarchicalFileList.st,v 1.17 2002-08-20 07:28:25 penk Exp $'
! !