DirectoryDifferenceViewApplication.st
author Claus Gittinger <cg@exept.de>
Mon, 20 Jan 2020 21:02:47 +0100
changeset 19422 c6ca1c3e0fd7
parent 19418 baa308d1ad87
child 19502 329089e25915
permissions -rw-r--r--
#REFACTORING by exept class: MultiViewToolApplication added: #askForFile:default:forSave:thenDo: changed: #askForFile:default:thenDo: #askForFile:thenDo: #menuSaveAllAs #menuSaveAs

"
 COPYRIGHT (c) 2006 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:libtool' }"

"{ NameSpace: Smalltalk }"

AbstractFileFinderApplicationComponent subclass:#DirectoryDifferenceViewApplication
	instanceVariableNames:'directory1Holder directory2Holder directory1 directory2
		filesOnlyInDirectory1 filesOnlyInDirectory2
		filesWhichAreDifferent directoriesOnlyInDirectory1
		directoriesOnlyInDirectory2 namePatternHolder
		excludedNamePatternHolder excludedDirectoryNamePatternHolder
		ignoreCaseInNameHolder ignoreCaseInExcludedNameHolder
		ignoreCaseInExcludedDirectoryNameHolder
		ignoreCaseInDirectoryNameHolder filesSkippedCount
		directoriesSkippedCount unfilteredFilesList filteredFilesList
		hasResultListHolder itemsFoundQueue showWhatHolder'
	classVariableNames:''
	poolDictionaries:''
	category:'Interface-Tools-File'
!

!DirectoryDifferenceViewApplication class methodsFor:'documentation'!

copyright
"
 COPYRIGHT (c) 2006 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.
"
!

documentation
"
    documentation to be added.

    class:
        <a short class summary here, describing what instances represent>

    responsibilities:    
        <describing what my main role is>

    collaborators:    
        <describing with whom and how I talk to>

    API:
        <public api and main messages>
        
    example:
        <a one-line examples on how to use - can also be in a separate example method>

    implementation:
        <implementation points>

    [author:]
        exept MBP

    [instance variables:]

    [class variables:]

    [see also:]

"
!

examples
"
 Notice that everything between [exBegin] and [exEnd] is extracted by the html-doc generator
 to create nicely formatted and clickable executable examples in the generated html-doc.
 (see the browser's class-documentation menu items for more)

 opening the application:
                                                        [exBegin]
    DirectoryDifferenceViewApplication open
                                                        [exEnd]

 opening the application on some model:
                                                        [exBegin]
    DirectoryDifferenceViewApplication openOn:aModel
                                                        [exEnd]
"
! !

!DirectoryDifferenceViewApplication class methodsFor:'help specs'!

helpSpec
    "This resource specification was automatically generated
     by the UIHelpTool of ST/X."

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

    "
     UIHelpTool openOnClass:DirectoryDifferenceViewApplication
    "

    <resource: #help>

    ^ super helpSpec addPairsFrom:#(

#excludedDirectoryNamePattern
'Directory name(s) to skip.\Can be matchPatterns, separated by ";"'

#excludedNamePattern
'File name(s) to skip.\Can be matchPatterns, separated by ";"'

#ignoreCaseInExcludedDirectoryName
'Ignore case in directory name exclude pattern(s)'

#ignoreCaseInExcludedName
'Ignore case in file name exclude pattern(s)'

#ignoreCaseInName
'Ignore case in file name pattern(s)'

#namePattern
'Filename(s) to search for.\Can be matchPatterns, separated by ";".\(eg. "*.c ; *.h" searches for C and header files. So does "*.[ch]")'

#searchDirectory1
'The first folder, to be compared against folder2'

#searchDirectory2
'The second folder, to be compared against folder1'

#showAll
'Show all differences in the result list'

#showDifferent
'Show only files in the result list\which are present in both, but different'

#showOnlyIn1
'Show only files in the result list\which are present in the first directory,\but missing in the second'

#showOnlyIn2
'Show only files in the result list\which are present in the second directory,\but missing in the first'

)
! !

!DirectoryDifferenceViewApplication class methodsFor:'image specs'!

leftOnlyIcon
    "This resource specification was automatically generated
     by the ImageEditor of ST/X."

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

    "
     self leftOnlyIcon inspect
     ImageEditor openOnClass:self andSelector:#leftOnlyIcon
     Icon flushCachedIcons
    "

    <resource: #image>

    ^Icon
        constantNamed:'DirectoryDifferenceViewApplication class leftOnlyIcon'
        ifAbsentPut:[(Depth8Image width:24 height:24) bits:(ByteArray fromPackedString:'
X6M#X6M#X6M#X6M#X&I"X&I"X&I"X&I"X6M#X6M#X6M#X6M#VE!!"X&I"X&I"X&I"X6M#X6M#X6M#X6MXA ]"X&I"X&I"X&I"X6M#X6M#X6M#X0(KC@5"X&I"
X&I"X&I"X6M#X6M#X6M#A <PDP5"X&I"X&I"X&I"X6M#X6M#X1LSEADUE 5"X&I"X&I"X&I"X6M#X6M#D1LXE!!$ZD@5"X&I"X&I"X&I"X6M#X6MXA10]G!!<Z
H@4MCP4MCP4MHRI"X6M#X0(#G"@$IRXZI2 (JATUEQTUBB%"X6M#A"(HER,%G20-K"<0LSD2L3P5M 5"X6L7NB 9G1<&KB4DAC(;OC0=O#=@PP5"X3\>P$L/
F!!(ZKPPDN#-DOA-EQ$\?R@5"X3]IR"XZKR4-A@P:N1 <O$-LST8TK 5"X6L!!S1H.ACD1L#(;Q@TCQ$YLQ48TT@5"X6M#TUH,P3(:N#P[T5QTUUYWQ$1WLP5"
X6M#X5!!YL10;QDTCK3D1LSD1L#H;MB%"X6M#X6MXD38/U@MWL 4MCP4MCP4MJRM"X6M#X6M#VALON UFF@5"X&I"X&I"X&I"X6M#X6M#X5 SV30>W@5"X&I"
X&I"X&I"X6M#X6M#X6MXTU5FU 5"X&I"X&I"X&I"X6M#X6M#X6M#X5!!_X@5"X&I"X&I"X&I"X6M#X6M#X6M#X6MXXP5"X&I"X&I"X&I"X6M#X6M#X6M#X6M#
VE5"X&I"X&I"X&I"X6M#X6M#X6M#X6M#X&I"X&I"X&I"X&I"')
            colorMapFromArray:#[251 251 251 249 249 249 234 234 234 210 210 210 224 224 224 213 213 213 184 184 184 176 176 176 238 238 238 245 245 245 200 200 200 187 187 187 240 240 240 171 171 171 233 233 233 198 198 198 246 246 246 249 249 249 236 236 236 174 174 174 207 207 207 241 241 241 247 247 247 226 226 226 219 219 219 237 237 237 227 227 227 216 216 216 231 231 231 247 247 247 234 234 234 229 229 229 245 245 245 178 178 178 188 188 188 185 185 185 232 232 232 231 231 231 228 228 228 244 244 244 242 242 242 175 175 175 193 193 193 232 232 232 228 228 228 225 225 225 229 229 229 227 227 227 226 226 226 223 223 223 221 221 221 220 220 220 219 219 219 223 223 223 237 237 237 179 179 179 200 200 200 236 236 236 221 221 221 220 220 220 217 217 217 214 214 214 213 213 213 210 210 210 215 215 215 235 235 235 239 239 239 230 230 230 218 218 218 212 212 212 208 208 208 202 202 202 230 230 230 211 211 211 238 238 238 209 209 209 205 205 205 204 204 204 201 201 201 197 197 197 225 225 225 183 183 183 187 187 187 217 217 217 214 214 214 212 212 212 211 211 211 209 209 209 196 196 196 180 180 180 248 248 248 186 186 186 215 215 215 180 180 180 240 240 240 174 174 174 196 196 196 172 172 172 255 0 0 0 255 0]
            mask:((ImageMask width:24 height:24) bits:(ByteArray fromPackedString:'????????????????????????????????????????????????????????????????????????????????????????????????'); yourself); yourself]
!

rightOnlyIcon
    "This resource specification was automatically generated
     by the ImageEditor of ST/X."

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

    "
     self rightOnlyIcon inspect
     ImageEditor openOnClass:self andSelector:#rightOnlyIcon
     Icon flushCachedIcons
    "

    <resource: #image>

    ^Icon
        constantNamed:'DirectoryDifferenceViewApplication class rightOnlyIcon'
        ifAbsentPut:[(Depth8Image width:24 height:24) bits:(ByteArray fromPackedString:'
X&I"X&I"X&I"X&I"X6M#X6M#X6M#X6M#X&I"X&I"X&I"X%5XX6M#X6M#X6M#X6M#X&I"X&I"X&I"X 5!!VFM#X6M#X6M#X6M#X&I"X&I"X&I"X 5 W5!!#X6M#
X6M#X6M#X&I"X&I"X&I"X 5VQ%5QVFM#X6M#X6M#X&I"X&I"X&I"X 5\O#1[D5!!#X6M#X6M#X&I"X&I"X&I"X 4XQ T:C1MXX6M#X6M#X"L)CP4MCP4MCP42
U0MTK38SVFM#X6M#X"$4N3H2LSD1LSD/@4UDN103VU!!#X6M#X 41U41FU5YUUEQSF3P:N#)CKEIQX6M#X 5PED9GSDYF@0UDN3(2LSDDK!!IOHVM#X 4.ED9M
SD,>OA ;N PDKR4-F"YJRS]#X 5HO4]FQQ,<QC,:A@P-F!!(ZK4MBO#]#X 5APC<>OS0<N3(DAB4,I!!<_NR 8M6M#X 46MSP3L#D1LB<.KR0_IR,UBB(FX6M#
X"$HEQTUEQT(JB ''F"X%IB@^H0)#X6M#X"H!!CP4MCP4MCP4 F!!<^GQ0GVFM#X6M#X&I"X&I"X&I"X 4PF!!$VFALSX6M#X6M#X&I"X&I"X&I"X 4VEQDTD1M#
X6M#X6M#X&I"X&I"X&I"X 4QD@<FX6M#X6M#X6M#X&I"X&I"X&I"X 4LB0)#X6M#X6M#X6M#X&I"X&I"X&I"X \FVFM#X6M#X6M#X6M#X&I"X&I"X&I"X%!!X
X6M#X6M#X6M#X6M#X&I"X&I"X&I"X&I"X6M#X6M#X6M#X6M#')
            colorMapFromArray:#[251 251 251 249 249 249 234 234 234 210 210 210 224 224 224 213 213 213 184 184 184 176 176 176 238 238 238 245 245 245 200 200 200 187 187 187 240 240 240 171 171 171 233 233 233 198 198 198 246 246 246 249 249 249 236 236 236 174 174 174 207 207 207 241 241 241 247 247 247 226 226 226 219 219 219 237 237 237 227 227 227 216 216 216 231 231 231 247 247 247 234 234 234 229 229 229 245 245 245 178 178 178 188 188 188 185 185 185 232 232 232 231 231 231 228 228 228 244 244 244 242 242 242 175 175 175 193 193 193 232 232 232 228 228 228 225 225 225 229 229 229 227 227 227 226 226 226 223 223 223 221 221 221 220 220 220 219 219 219 223 223 223 237 237 237 179 179 179 200 200 200 236 236 236 221 221 221 220 220 220 217 217 217 214 214 214 213 213 213 210 210 210 215 215 215 235 235 235 239 239 239 230 230 230 218 218 218 212 212 212 208 208 208 202 202 202 230 230 230 211 211 211 238 238 238 209 209 209 205 205 205 204 204 204 201 201 201 197 197 197 225 225 225 183 183 183 187 187 187 217 217 217 214 214 214 212 212 212 211 211 211 209 209 209 196 196 196 180 180 180 248 248 248 186 186 186 215 215 215 180 180 180 240 240 240 174 174 174 196 196 196 172 172 172 255 0 0 0 255 0]
            mask:((ImageMask width:24 height:24) bits:(ByteArray fromPackedString:'????????????????????????????????????????????????????????????????????????????????????????????????'); yourself); yourself]
! !

!DirectoryDifferenceViewApplication class methodsFor:'interface specs'!

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:DirectoryDifferenceViewApplication andSelector:#windowSpec
     DirectoryDifferenceViewApplication new openInterface:#windowSpec
     DirectoryDifferenceViewApplication open
    "

    <resource: #canvas>

    ^ 
    #(FullSpec
       name: windowSpec
       uuid: '01d49d90-bac4-11e9-a284-10ddb1cc5928'
       window: 
      (WindowSpec
         label: 'Directory Diff'
         name: 'Directory Diff'
         uuid: '09a5b9c0-baa9-11e9-a284-10ddb1cc5928'
         min: (Point 377 131)
         bounds: (Rectangle 0 0 758 512)
      )
       component: 
      (SpecCollection
         collection: (
          (MenuPanelSpec
             name: 'SearchToolBar'
             layout: (LayoutFrame 0 0.0 0 0 0 1.0 32 0)
             uuid: '09a5bd8a-baa9-11e9-a284-10ddb1cc5928'
             level: 0
             menu: searchMenu
             textDefault: true
          )
          (ProgressIndicatorSpec
             name: 'ProgressIndicator1'
             layout: (LayoutFrame 125 0 11 0 231 0 21 0)
             uuid: '09a5c06e-baa9-11e9-a284-10ddb1cc5928'
             visibilityChannel: enableStop
             backgroundColor: (Color 0.0 67.0 67.0)
             showPercentage: false
             isActivityIndicator: true
          )
          (ViewSpec
             name: 'Box1'
             layout: (LayoutFrame 0 0.0 32 0 0 1.0 154 0)
             uuid: '09a5c2d0-baa9-11e9-a284-10ddb1cc5928'
             component: 
            (SpecCollection
               collection: (
                (LabelSpec
                   label: 'Directory1:'
                   name: 'DirectoryLabel'
                   layout: (LayoutFrame 2 0 7 0 154 0 24 0)
                   activeHelpKey: searchDirectory1
                   uuid: '09a5c42e-baa9-11e9-a284-10ddb1cc5928'
                   translateLabel: true
                   adjust: right
                )
                (FilenameInputFieldSpec
                   name: 'DirectoryEntryField'
                   layout: (LayoutFrame 156 0 4 0 0 1 24 0)
                   activeHelpKey: searchDirectory1
                   uuid: '09a5c67c-baa9-11e9-a284-10ddb1cc5928'
                   model: directory1Holder
                   immediateAccept: true
                   acceptOnPointerLeave: false
                )
                (LabelSpec
                   label: 'Directory2:'
                   name: 'Label2'
                   layout: (LayoutFrame 2 0 31 0 154 0 48 0)
                   activeHelpKey: searchDirectory2
                   uuid: '09a5ca00-baa9-11e9-a284-10ddb1cc5928'
                   translateLabel: true
                   adjust: right
                )
                (FilenameInputFieldSpec
                   name: 'FilenameEntryField1'
                   layout: (LayoutFrame 156 0 28 0 0 1 48 0)
                   activeHelpKey: searchDirectory2
                   uuid: '09a5cb40-baa9-11e9-a284-10ddb1cc5928'
                   model: directory2Holder
                   immediateAccept: true
                   acceptOnPointerLeave: false
                )
                (LabelSpec
                   label: 'Compare Files Named:'
                   name: 'FileNameLabel'
                   layout: (LayoutFrame 2 0 55 0 154 0 72 0)
                   activeHelpKey: namePattern
                   uuid: '09a5ccda-baa9-11e9-a284-10ddb1cc5928'
                   translateLabel: true
                   adjust: right
                )
                (InputFieldSpec
                   name: 'FileNameEntryField'
                   layout: (LayoutFrame 156 0 52 0 -180 1 72 0)
                   activeHelpKey: namePattern
                   uuid: '09a5cdf2-baa9-11e9-a284-10ddb1cc5928'
                   tabable: true
                   model: namePatternHolder
                   immediateAccept: true
                   acceptOnLeave: false
                   acceptOnPointerLeave: false
                )
                (CheckBoxSpec
                   label: 'Ignore Case'
                   name: 'IgnoreCaseInNameCheckBox'
                   layout: (LayoutFrame -169 1 53 0 -4 1 76 0)
                   activeHelpKey: ignoreCaseInName
                   uuid: '09a5cfb4-baa9-11e9-a284-10ddb1cc5928'
                   tabable: true
                   model: ignoreCaseInNameHolder
                   translateLabel: true
                )
                (LabelSpec
                   label: 'But Not Named:'
                   name: 'Label1'
                   layout: (LayoutFrame 2 0 79 0 190 0 96 0)
                   activeHelpKey: excludedNamePattern
                   uuid: '09a5d16c-baa9-11e9-a284-10ddb1cc5928'
                   translateLabel: true
                   adjust: right
                )
                (InputFieldSpec
                   name: 'EntryField1'
                   layout: (LayoutFrame 190 0 76 0 -180 1 96 0)
                   activeHelpKey: excludedNamePattern
                   uuid: '09a5d284-baa9-11e9-a284-10ddb1cc5928'
                   tabable: true
                   model: excludedNamePatternHolder
                   immediateAccept: true
                   acceptOnLeave: false
                   acceptOnPointerLeave: false
                )
                (CheckBoxSpec
                   label: 'Ignore Case'
                   name: 'CheckBox1'
                   layout: (LayoutFrame -169 1 77 0 -4 1 100 0)
                   activeHelpKey: ignoreCaseInExcludedName
                   uuid: '09a5d414-baa9-11e9-a284-10ddb1cc5928'
                   tabable: true
                   model: ignoreCaseInExcludedNameHolder
                   translateLabel: true
                )
                (LabelSpec
                   label: 'Ignore Folders Named:'
                   name: 'Label3'
                   layout: (LayoutFrame 2 0 103 0 190 0 120 0)
                   activeHelpKey: excludedDirectoryNamePattern
                   uuid: '09a5d518-baa9-11e9-a284-10ddb1cc5928'
                   translateLabel: true
                   adjust: right
                )
                (InputFieldSpec
                   name: 'EntryField2'
                   layout: (LayoutFrame 190 0 100 0 -180 1 120 0)
                   activeHelpKey: excludedDirectoryNamePattern
                   uuid: '09a5d63a-baa9-11e9-a284-10ddb1cc5928'
                   tabable: true
                   model: excludedDirectoryNamePatternHolder
                   immediateAccept: true
                   acceptOnLeave: false
                   acceptOnPointerLeave: false
                )
                (CheckBoxSpec
                   label: 'Ignore Case'
                   name: 'CheckBox2'
                   layout: (LayoutFrame -169 1 101 0 -4 1 124 0)
                   activeHelpKey: ignoreCaseInExcludedDirectoryName
                   uuid: '09a5d7d4-baa9-11e9-a284-10ddb1cc5928'
                   tabable: true
                   model: ignoreCaseInExcludedDirectoryNameHolder
                   translateLabel: true
                )
              )
            )
          )
          (VariableVerticalPanelSpec
             name: 'VariableVerticalPanel1'
             layout: (LayoutFrame 0 0.0 154 0 0 1.0 0 1)
             uuid: '09a5d914-baa9-11e9-a284-10ddb1cc5928'
             component: 
            (SpecCollection
               collection: (
                (DataSetSpec
                   name: 'Table1'
                   uuid: '09a5daa4-baa9-11e9-a284-10ddb1cc5928'
                   model: selectionHolder
                   hasHorizontalScrollBar: true
                   hasVerticalScrollBar: true
                   dataList: matchedFilesList
                   doubleClickSelector: fileListDoubleClick:
                   columnHolder: searchResultTable
                   valueChangeSelector: fileSelected:
                   multipleSelectOk: true
                   useIndex: true
                )
                (ArbitraryComponentSpec
                   name: 'DiffTextView'
                   uuid: '09a5deaa-baa9-11e9-a284-10ddb1cc5928'
                   hasHorizontalScrollBar: true
                   hasVerticalScrollBar: true
                   component: DiffTextView
                )
              )
            )
             handles: (Any 0.5 1.0)
          )
        )
      )
    )
! !

!DirectoryDifferenceViewApplication class methodsFor:'menu specs'!

searchMenu
    "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:FindFileApplication andSelector:#searchMenu
     (Menu new fromLiteralArrayEncoding:(FindFileApplication searchMenu)) startUp
    "

    <resource: #menu>

    ^ 
     #(Menu
        (
         (MenuItem
            label: 'Search'
            itemValue: doSearch
            translateLabel: true
            isButton: true
            labelImage: (ResourceRetriever ToolbarIconLibrary searchFileIcon)
          )
         (MenuItem
            enabled: enableStop
            label: 'Stop'
            itemValue: stop
            translateLabel: true
            isButton: true
            isVisible: enableStop
            labelImage: (ResourceRetriever ToolbarIconLibrary stop22x22Icon)
          )
         (MenuItem
            label: 'Close'
            itemValue: doClose
            translateLabel: true
            isButton: true
            startGroup: right
            isVisible: false
            labelImage: (ResourceRetriever ToolbarIconLibrary removeTabIcon)
          )
         (MenuItem
            activeHelpKey: showOnlyIn1
            label: 'Only in 1)'
            indication: showOnlyIn1Holder
            labelImage: (ResourceRetriever DirectoryDifferenceViewApplication leftOnlyIcon)
            isButton: true
            isVisible: hasResultListHolder
         )
         (MenuItem
            activeHelpKey: showOnlyIn2
            label: 'Only in 2)'
            indication: showOnlyIn2Holder
            labelImage: (ResourceRetriever DirectoryDifferenceViewApplication rightOnlyIcon)
            isButton: true
            isVisible: hasResultListHolder
         )
         (MenuItem
            activeHelpKey: showDifferent
            label: 'Different Only'
            indication: showDifferentOnlyHolder
            labelImage: (ResourceRetriever ToolbarIconLibrary diff22x22Icon2)
            isButton: true
            isVisible: hasResultListHolder
         )
         (MenuItem
            activeHelpKey: showAll
            label: 'Show All'
            indication: showAllHolder
            isButton: true
            isVisible: hasResultListHolder
         )
         )
        nil
        nil
      )
! !

!DirectoryDifferenceViewApplication class methodsFor:'tableColumns specs'!

searchResultTable
    "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:DirectoryDifferenceViewApplication andSelector:#searchResultTable
    "

    <resource: #tableColumns>

    ^#(
      (DataSetColumnSpec
         label: 'Filename'
         id: 'FileName'
         labelButtonType: Button
         height: heightOfFirstRow
         model: at:
         menuFromApplication: false
         writeSelector: at:put:
         canSelect: false
         showRowSeparator: false
         showColSeparator: false
      )
      (DataSetColumnSpec
         label: 'Diff'
         id: 'Diff'
         labelButtonType: Button
         height: heightOfFirstRow
         model: at:
         menuFromApplication: false
         writeSelector: at:put:
         canSelect: false
         showRowSeparator: false
         showColSeparator: false
      )
    )
! !

!DirectoryDifferenceViewApplication methodsFor:'accessing'!

directory1:directory1Arg directory2:directory2Arg 
    self directory1Holder value:directory1Arg.
    self directory2Holder value:directory2Arg.

"/    self namePatternHolder value:newPattern.
    self enableStop value:false.
    self enableSearch value:true.
    ^ true.

    "Modified: / 12-01-2012 / 01:36:54 / cg"
!

directory1Holder
    directory1Holder isNil ifTrue:[
        directory1Holder := nil asValue
    ].
    ^ directory1Holder

    "Created: / 12-01-2012 / 01:37:31 / cg"
!

directory2Holder
    directory2Holder isNil ifTrue:[
        directory2Holder := nil asValue
    ].
    ^ directory2Holder

    "Created: / 12-01-2012 / 01:37:41 / cg"
! !

!DirectoryDifferenceViewApplication methodsFor:'actions'!

fileListDoubleClick:indexOrIndexCollection
    |idx file fn1 fn2|

    idx := (indexOrIndexCollection isInteger)
            ifTrue:[indexOrIndexCollection]
            ifFalse:[indexOrIndexCollection first].
    file := (self shownList at:idx) first.

    fn1 := self directory1Holder value asFilename construct:file.
    fn2 := self directory2Holder value asFilename construct:file.
    (fn1 exists and:[fn1 isRegularFile]) ifTrue:[
        self fileDoubleClick:fn1
    ].
    (fn2 exists and:[fn2 isRegularFile]) ifTrue:[
        self fileDoubleClick:fn2
    ].
!

fileSelected:entries
    |entry file fn1 fn2 text1 text2 differ|

    entries isEmptyOrNil ifTrue:[^ self].

    entry := self shownList at:entries first.
    file := entry first.

    fn1 := self directory1Holder value asFilename construct:file.
    fn2 := self directory2Holder value asFilename construct:file.
    (fn1 exists and:[fn1 isRegularFile]) ifTrue:[
        text1 := fn1 contentsAsString.
    ].
    (fn2 exists and:[fn2 isRegularFile]) ifTrue:[
        text2 := fn2 contentsAsString.
    ].
    differ := builder componentAt:'DiffTextView'.
    differ text1:(text1 ? '') text2:(text2 ? '').

    "Created: / 12-01-2012 / 03:21:52 / cg"
!

shouldShow:aListItem
    |showingWhat|

    showingWhat := self showWhatHolder value.
    showingWhat == #all ifTrue:[^ true].

    "/ showing filtered
    ^ (aListItem at:3) == showingWhat
!

shownFilesChanged
    |showingWhat|

    showingWhat := self showWhatHolder value.
    showingWhat == #all ifTrue:[
        self matchedFilesList contents:unfilteredFilesList.
        "/ self shownListHolder valueHolder:unfilteredFilesList.
        self shownListHolder valueHolder:unfilteredFilesList.
        ^ self.
    ].

    "/ showing filtered

    filteredFilesList contents:(unfilteredFilesList select:[:entry | (entry at:3) == showingWhat]).
    self matchedFilesList contents:filteredFilesList.
    self shownListHolder valueHolder:filteredFilesList.
! !

!DirectoryDifferenceViewApplication methodsFor:'aspects'!

excludedDirectoryNamePatternHolder
    excludedDirectoryNamePatternHolder isNil ifTrue:[
        excludedDirectoryNamePatternHolder := '' asValue.
    ].
    ^ excludedDirectoryNamePatternHolder.

    "Created: / 13-01-2012 / 14:42:47 / cg"
!

excludedNamePatternHolder
    excludedNamePatternHolder isNil ifTrue:[
        excludedNamePatternHolder := '' asValue.
    ].
    ^ excludedNamePatternHolder.

    "Created: / 13-01-2012 / 14:42:47 / cg"
!

filteredFilesList
    filteredFilesList isNil ifTrue:[
        filteredFilesList := List new.
    ].
    ^ filteredFilesList.
!

hasResultListHolder
    hasResultListHolder isNil ifTrue:[
        hasResultListHolder := BlockValue
                                with:[:l | l value notEmptyOrNil]
                                argument:(self unfilteredFilesList)
    ].
    ^ hasResultListHolder
!

ignoreCaseInDirectoryNameHolder

    ignoreCaseInDirectoryNameHolder isNil ifTrue:[
        ignoreCaseInDirectoryNameHolder := true asValue.
    ].
    ^ ignoreCaseInDirectoryNameHolder.

    "Created: / 13-01-2012 / 14:37:28 / cg"
!

ignoreCaseInExcludedDirectoryNameHolder

    ignoreCaseInExcludedDirectoryNameHolder isNil ifTrue:[
        ignoreCaseInExcludedDirectoryNameHolder := true asValue.
    ].
    ^ ignoreCaseInExcludedDirectoryNameHolder.

    "Created: / 13-01-2012 / 14:37:28 / cg"
!

ignoreCaseInExcludedNameHolder

    ignoreCaseInExcludedNameHolder isNil ifTrue:[
        ignoreCaseInExcludedNameHolder := true asValue.
    ].
    ^ ignoreCaseInExcludedNameHolder.

    "Created: / 13-01-2012 / 14:37:28 / cg"
!

ignoreCaseInNameHolder

    ignoreCaseInNameHolder isNil ifTrue:[
        ignoreCaseInNameHolder := true asValue.
    ].
    ^ ignoreCaseInNameHolder.

    "Created: / 13-01-2012 / 14:37:28 / cg"
!

namePatternHolder

    namePatternHolder isNil ifTrue:[
        namePatternHolder := '*' asValue.
    ].
    ^ namePatternHolder.

    "Created: / 13-01-2012 / 14:37:28 / cg"
!

showAllHolder
    ^ (PluggableAdaptor on:(self showWhatHolder)) selectValue:#all
!

showDifferentOnlyHolder
    ^ (PluggableAdaptor on:(self showWhatHolder)) selectValue:#different
!

showOnlyIn1Holder
    ^ (PluggableAdaptor on:(self showWhatHolder)) selectValue:#onlyIn1
!

showOnlyIn2Holder
    ^ (PluggableAdaptor on:(self showWhatHolder)) selectValue:#onlyIn2
!

showWhatHolder
    showWhatHolder isNil ifTrue:[
        showWhatHolder := #all asValue.
        showWhatHolder onChangeSend:#shownFilesChanged to:self
    ].
    ^ showWhatHolder
!

unfilteredFilesList
    unfilteredFilesList isNil ifTrue:[
        unfilteredFilesList := List new.
    ].
    ^ unfilteredFilesList.
! !

!DirectoryDifferenceViewApplication methodsFor:'comparing'!

doSearch
    |dir1 dir2 
     namePattern excludedNamePattern excludedDirectoryNamePattern 
     ignoreCaseInName ignoreCaseInExcludeName ignoreCaseInExcludeDirectoryName
     fileExcludeFilterOrNil directoryExcludeFilterOrNil checkDir|

    filesOnlyInDirectory1 := OrderedCollection new.
    filesOnlyInDirectory2 := OrderedCollection new.
    directoriesOnlyInDirectory1 := OrderedCollection new.
    directoriesOnlyInDirectory2 := OrderedCollection new.
    filesWhichAreDifferent := OrderedCollection new.

    filesSkippedCount := directoriesSkippedCount := 0.

    dir1 := self directory1Holder value.
    dir1 isNil ifTrue:[^ self].

    dir2 := self directory2Holder value.
    dir2 isNil ifTrue:[^ self].

    dir1 := dir1 asFilename.
    dir2 := dir2 asFilename.

    dir1 pathName = dir2 pathName ifTrue:[
        self notify:('Same folder.' allBold).
        ^ self.
    ].

    namePattern := self namePatternHolder value.
    namePattern notEmptyOrNil ifTrue:[
        namePattern := namePattern withoutSeparators.
        (namePattern isEmptyOrNil or:[namePattern = '*']) ifTrue:[
            namePattern := nil
        ]
    ].

    ignoreCaseInName := self ignoreCaseInNameHolder value.
    ignoreCaseInExcludeName := self ignoreCaseInExcludedNameHolder value.
    ignoreCaseInExcludeDirectoryName := self ignoreCaseInExcludedDirectoryNameHolder value.

    excludedNamePattern := self excludedNamePatternHolder value.
    excludedNamePattern notEmptyOrNil ifTrue:[
        excludedNamePattern := excludedNamePattern withoutSeparators.
        excludedNamePattern notEmptyOrNil ifTrue:[
            fileExcludeFilterOrNil := 
                ignoreCaseInExcludeName 
                    ifTrue:[  [:baseName | (excludedNamePattern compoundMatch:baseName caseSensitive:false) not ] ]
                    ifFalse:[  [:baseName | (excludedNamePattern compoundMatch:baseName caseSensitive:true) not ] ].
        ].
    ].

    excludedDirectoryNamePattern := self excludedDirectoryNamePatternHolder value.
    excludedDirectoryNamePattern notEmptyOrNil ifTrue:[
        excludedDirectoryNamePattern := excludedDirectoryNamePattern withoutSeparators.
        excludedDirectoryNamePattern notEmptyOrNil ifTrue:[
            checkDir := 
                ignoreCaseInExcludeDirectoryName 
                    ifTrue:[ [:dir | (excludedDirectoryNamePattern compoundMatch:(dir baseName) caseSensitive:false) not ] ]
                    ifFalse:[ [:dir | (excludedDirectoryNamePattern compoundMatch:(dir baseName) caseSensitive:true) not ] ].
            directoryExcludeFilterOrNil := 
                [:fn |
                    (checkDir value:fn) ifFalse:[
                        directoriesSkippedCount := directoriesSkippedCount + 1.
                        false.
                    ] ifTrue:[
                        true
                    ]
                ].
        ].
    ].

    self unfilteredFilesList removeAll.
    self filteredFilesList removeAll.
    self shownFilesChanged. "/ to set the filtered list

    self windowGroup repairDamage.

    itemsFoundQueue := SharedQueue new.

    self 
        startSearchTask:[
            self changeInformationTo:'Directory Diff - searching ' toTab:true.

            dir1 
                recursiveDirectoryContentsDo:[:relFn1 |
                    |fn1 fn2 fn1BaseName newListItem|

                    fn1 := dir1 construct:relFn1.
                    fn1BaseName := fn1 baseName.
                    newListItem := nil.
                    (namePattern isNil or:[namePattern compoundMatch:fn1BaseName caseSensitive:ignoreCaseInName not]) ifTrue:[
                        (fileExcludeFilterOrNil isNil or:[(fileExcludeFilterOrNil value:fn1BaseName) not]) ifTrue:[
                            fn2 := dir2 construct:relFn1.
                            fn1 isDirectory ifTrue:[
                                self notify:('Comparing %1...' bindWith:relFn1 asString allBold).
                                fn2 exists ifFalse:[
                                    directoriesOnlyInDirectory1 add:fn1.
                                    newListItem := { relFn1 . 'Directory only in directory1' . #onlyIn1}.
                                ].
                            ] ifFalse:[
                                fn2 exists ifTrue:[
                                    filesSearchedCount := filesSearchedCount + 1.
                                    (fn1 sameContentsAs:fn2) ifFalse:[
                                        filesWhichAreDifferent add:fn1.
                                        newListItem := { relFn1 . 'Different' . #different }.
                                    ].
                                ] ifFalse:[
                                    filesOnlyInDirectory1 add:fn1.
                                    newListItem := { relFn1 . 'File only in directory1' . #onlyIn1}.
                                ].
                            ].
                            newListItem notNil ifTrue:[
                                unfilteredFilesList add:newListItem.
                                itemsFoundQueue add:newListItem.
                                self enqueueMessage:#updateList for:self arguments:#().
                            ].
                        ] ifFalse:[
                            filesSkippedCount := filesSkippedCount + 1.
                        ]
                    ]
                ]
                filterForVisitingDirectories:directoryExcludeFilterOrNil.

            dir2 
                recursiveDirectoryContentsDo:[:relFn2 |
                    |fn1 fn2 fn2BaseName newListItem|

                    fn2 := dir2 construct:relFn2.
                    fn2BaseName := fn2 baseName.
                    (namePattern isNil or:[namePattern compoundMatch:fn2BaseName caseSensitive:ignoreCaseInName not]) ifTrue:[
                        (fileExcludeFilterOrNil isNil or:[(fileExcludeFilterOrNil value:fn2BaseName) not]) ifTrue:[
                            fn1 := dir1 construct:relFn2.
                            fn1 exists ifFalse:[
                                fn2 isDirectory ifTrue:[
                                    directoriesOnlyInDirectory2 add:fn2.
                                    newListItem := { relFn2 . 'Directory only in directory2'  . #onlyIn2}.
                                ] ifFalse:[
                                    filesOnlyInDirectory2 add:fn2.
                                    newListItem := { relFn2 . 'File only in directory2'  . #onlyIn2}.
                                ].
                                newListItem notNil ifTrue:[
                                    unfilteredFilesList add:newListItem.
                                    itemsFoundQueue add:newListItem.
                                    self enqueueMessage:#updateList for:self arguments:#().
                                ].
                            ].
                        ].
                    ] ifFalse:[
                        filesSkippedCount := filesSkippedCount + 1.
                    ]
                ]
                filterForVisitingDirectories:directoryExcludeFilterOrNil.

            Transcript showCR:'--- search summary ------'.
            directoriesSkippedCount ~~ 0 ifTrue:[
                Transcript showCR:'%1 directories skipped' with:directoriesSkippedCount.
            ].
            filesSkippedCount ~~ 0 ifTrue:[
                Transcript showCR:'%1 files skipped' with:filesSkippedCount.
            ].
            filesOnlyInDirectory1 notEmpty ifTrue:[
                Transcript showCR:'%1 files only in directory1' with:filesOnlyInDirectory1 size.
            ].
            filesOnlyInDirectory2 notEmpty ifTrue:[
                Transcript showCR:'%1 files only in directory2' with:filesOnlyInDirectory2 size.
            ].
            directoriesOnlyInDirectory1 notEmpty ifTrue:[
                Transcript showCR:'%1 directories only in directory1' with:directoriesOnlyInDirectory1 size.
            ].
            directoriesOnlyInDirectory2 notEmpty ifTrue:[
                Transcript showCR:'%1 directories only in directory2' with:directoriesOnlyInDirectory2 size.
            ].
            filesWhichAreDifferent notEmpty ifTrue:[
                Transcript showCR:'%1 files are different' with:filesWhichAreDifferent size.
            ]
        ]
        name:'Directory Diff'.

    "Created: / 12-01-2012 / 01:07:20 / cg"
    "Modified: / 15-02-2019 / 09:42:51 / Claus Gittinger"
!

generateResultStringForTimeElapsed:timeDuration
    "at the end of a search, generate a nice informative summary string.
     Redefined to generate a different message."

    |timeMillis timeString nIn1 nIn2 nDifferent|

    timeMillis := timeDuration asMilliseconds.
    timeString := timeDuration printStringForApproximation.

    nIn1 := filesOnlyInDirectory1 size.
    nIn2 := filesOnlyInDirectory2 size.
    nDifferent := filesWhichAreDifferent size.

    ^ resources 
        string:('different:%1, only in 1: %2, only in 2: %3 (%4 files visited; %6 files/s).') 
        with:nDifferent 
        with:nIn1 
        with:nIn2
        with:filesSearchedCount
        with:(UnitConverter fileSizeSIStringFor:(bytesSearchedCount * 1000 / (timeMillis max:1)))
        with:((filesSearchedCount * 1000 / (timeMillis max:1)) asFixedPoint:1).
!

updateList
    |next|

    [(next := itemsFoundQueue nextOrNil) notNil] whileTrue:[
        (self shouldShow:next) ifTrue:[
            filteredFilesList add:next.
            self matchedFilesList add:next
        ]
    ].
    self windowGroup repairDamage.
! !

!DirectoryDifferenceViewApplication methodsFor:'startup & release'!

postOpenWith:aBuilder
    super postOpenWith:aBuilder.
    self doSearch

    "Created: / 12-01-2012 / 01:41:18 / cg"
! !

!DirectoryDifferenceViewApplication class methodsFor:'documentation'!

version
    ^ '$Header$'
!

version_CVS
    ^ '$Header$'
! !