ImageView.st
author Claus Gittinger <cg@exept.de>
Fri, 15 Jun 2018 10:54:35 +0200
changeset 5816 7876c07931a7
parent 5783 e789f1b6f781
child 5881 34608d309383
permissions -rw-r--r--
#DOCUMENTATION by cg class: ComboListView class comment/format in: #documentation

"{ Encoding: utf8 }"

"
 COPYRIGHT (c) 1993 by Claus Gittinger
	      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' }"

"{ NameSpace: Smalltalk }"

View subclass:#ImageView
	instanceVariableNames:'image magnifiedImage smoothMagnifiedImage adjust
		explicitMagnificationFactor magnificationFactor tileMode
		tileOffset lastMousePoint adjustHolder imageEditAction
		forceSmoothingHolder smoothingProcess'
	classVariableNames:'DoNotMagnifyQuery'
	poolDictionaries:''
	category:'Views-Misc'
!

!ImageView class methodsFor:'documentation'!

copyright
"
 COPYRIGHT (c) 1993 by Claus Gittinger
	      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
"
    This View knows how to display a (bitmap-)image (or form).

    You can display an image with:

        ImageView openOn:anImageFileName
    or:
        ImageView openOnImage:anImage
    or:
        ImageView new image:anImage

    i.e.

        ImageView openOn:'../../goodies/bitmaps/gifImages/garfield.gif'
        ImageView openOn:'../../goodies/bitmaps/SBrowser.xbm'

        ImageView openOnImage:(Image fromFile:'../../goodies/bitmaps/gifImages/garfield.gif')
        ImageView openOnImage:(Image fromFile:'../../goodies/bitmaps/SBrowser.xbm')

    adjust:
        controls how images are displayed;
        can be one of:
            #topLeft    - image is displayed as usual
            #center     - image is shown centered
            #fitBig     - big images are shrunk to make it fit the view
            #fitSmall   - small images are magnified to make it fit the view,
            #fit        - all images are magnified to fit the view

    [author:]
        Claus Gittinger

    [see also:]
        Image Form
"
!

examples
"
    |top imgView scrView|

    top := StandardSystemView new.
    top extent:300@300.

    imgView := ImageView new.
    imgView image:(Image fromFile:'../../goodies/bitmaps/gifImages/garfield.gif').

    scrView := HVScrollableView forView:imgView.
    scrView origin:0@0 corner:1.0@1.0.
    top add:scrView.

    top open.
"
! !

!ImageView class methodsFor:'initialization'!

initialize
    DoNotMagnifyQuery isNil ifTrue:[
        DoNotMagnifyQuery := QuerySignal new defaultAnswer:false.
    ].
! !

!ImageView class methodsFor:'menu specs'!

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

    <resource: #menu>

    ^ 
     #(Menu
        (
         (MenuItem
            label: 'Size to Fit'
            isVisible: fitBigMenuItemVisible
            choice: adjustHolder
            choiceValue: fitBig
          )
         (MenuItem
            label: 'Size to Fit (smooth)'
            isVisible: smoothFitBigMenuItemVisible
            choice: adjustHolder
            choiceValue: smoothFitBig
          )
         (MenuItem
            label: 'Original Size'
            choice: adjustHolder
            choiceValue: topLeft
          )
         (MenuItem
            label: 'Magnify'
            submenu: 
           (Menu
              (
               (MenuItem
                  label: '* 0.5'
                  itemValue: magnifyBy:
                  argument: 0.5
                )
               (MenuItem
                  label: '* 0.75'
                  itemValue: magnifyBy:
                  argument: 0.75
                )
               (MenuItem
                  label: '* 1'
                  itemValue: magnifyBy:
                  argument: 1
                )
               (MenuItem
                  label: '* 2'
                  itemValue: magnifyBy:
                  argument: 2
                )
               (MenuItem
                  label: '* 4'
                  itemValue: magnifyBy:
                  argument: 4
                )
               (MenuItem
                  label: '-'
                  isVisible: smoothingMenuItemVisible
                )
               (MenuItem
                  label: 'Smoothing'
                  isVisible: smoothingMenuItemVisible
                  hideMenuOnActivated: false
                  indication: forceSmoothingHolder
                )
               )
              nil
              nil
            )
          )
         (MenuItem
            label: '-'
            isVisible: selfIsNotImageEditor
          )
         (MenuItem
            label: 'Save As...'
            itemValue: saveImageAs
            isVisible: selfIsNotImageEditor
          )
         (MenuItem
            label: 'Edit'
            itemValue: editImage
            isVisible: selfIsNotImageEditor
          )
         (MenuItem
            label: 'Extra Slice'
            submenuChannel: middleButtonMenuExtraSlice
            isMenuSlice: true
          )
         )
        nil
        nil
      )
!

middleButtonMenuExtraSlice
    "can be refdefined in subclasses to add more menu items"

    ^ nil
! !

!ImageView class methodsFor:'queries-plugin'!

aspectSelectors
    ^ #( imageChannel )

    "Created: / 11.2.2000 / 00:37:33 / cg"
! !

!ImageView class methodsFor:'startup'!

openOn:anImageOrFileName
    "startup an image viewer on an image or
     an image read from a file.
     Return the imageView (not the topView)"

    anImageOrFileName isImage ifTrue:[
        ^ self openOnImage:anImageOrFileName
    ].
    ^ self openOnFile:anImageOrFileName

    "
     ImageView openOn:'bitmaps/gifImages/garfield.gif'
     ImageView openOn:'bitmaps/xpmBitmaps/misc_icons/BOOK.xpm'
    "

    "Modified: / 31-10-1997 / 16:17:52 / cg"
    "Modified (comment): / 13-02-2017 / 10:13:13 / cg"
!

openOnFile:aFileName
    "startup an image viewer on an image read from a file.
     Return the imageView (not the topView)"

    |fn imageView img e|

    fn := aFileName asFilename.

    img := Image fromFile:fn.
    img isNil ifTrue:[
        fn exists ifTrue:[
            e := 'Unknown or unsupported image format.'
        ] ifFalse:[
            e := 'No such image file.'.
        ].
        self warn:(self resources string:e).
        ^ nil
    ].
    imageView := self openOnImage:img.
    imageView topView label:(fn pathName) iconLabel:(fn baseName).
    ^ imageView

    "
     ImageView openOnFile:'../../goodies/bitmaps/gifImages/garfield.gif'
     ImageView openOnFile:'../../goodies/bitmaps/xpmBitmaps/misc_icons/BOOK.xpm'
    "

    "Modified: / 31-10-1997 / 16:17:52 / cg"
    "Modified (comment): / 13-02-2017 / 10:12:53 / cg"
!

openOnImage:anImage
    "startup an image viewer on an image.
     Return the imageView (not the topView)"

    |lbl|

    anImage isImage ifTrue:[
        lbl := anImage fileName ? 'an Image'
    ] ifFalse:[
        anImage isNil ifTrue:[
            lbl := 'an Image'
        ] ifFalse:[
            lbl := 'a Form'
        ]
    ].
    ^ self openOnImage:anImage title:lbl

    "
     ImageView openOnImage:(Image fromFile:'bitmaps/gifImages/garfield.gif') title:'garfield'
     ImageView openOnImage:(Image fromFile:'../../libtool/bitmaps/SBrowser.xbm') title:'old browser icon'
    "

    "Modified (comment): / 13-02-2017 / 10:12:43 / cg"
!

openOnImage:anImage title:aString
    "startup an image viewer on an image.
     Return the imageView (not the topView)"

    |top v imageView icnW icnH iconView magX magY mag imgWidth imgHeight|

    top := StandardSystemView label:aString.

    v := HVScrollableView for:self in:top.
    v origin:0@0 extent:1.0@1.0.
    imageView := v scrolledView.

    anImage notNil ifTrue:[
        imageView image:anImage.

        "define an icon view showing a little version of image.
         Since some window managers cannot handle this correctly (twm),
         this is only done when running on an IRIS"

        (true "(OperatingSystem getSystemType = 'iris')"
        and:[StyleSheet name == #iris]) ifTrue:[
            iconView := ImageView new.

            "for now; should somehow get access to preferred iconview extent ..."
            icnW := 86.
            icnH := 68.

            imgWidth := anImage width.
            imgHeight := anImage height.

            ((imgWidth <= icnW) and:[imgHeight <= icnH]) ifTrue:[
                iconView extent:(imgWidth @ imgHeight).
                mag := 1 @ 1
            ] ifFalse:[
                magX := icnW / imgWidth.
                magY := icnH / imgHeight.

                "scale image"
"
                mag := magX @ magY.
"
                "preserve ratio"
"
                mag := (magX min:magY) asPoint.
"
                mag := (magX max:magY) asPoint.
                iconView extent:((anImage width @ anImage height) * mag) rounded.
            ].

            top iconView:iconView.
        ].
    ].

    top open.

    iconView notNil ifTrue:[
        top windowGroup addView:iconView.
        [
            iconView image:(anImage magnifiedBy:mag).
        ] forkAt:4
    ].
    ^ imageView

    "
     ImageView openOnImage:(Image fromFile:'../../goodies/bitmaps/gifImages/garfield.gif') title:'garfield'
     ImageView openOnImage:(Image fromFile:'../../libtool/bitmaps/SBrowser.xbm')
    "

    "Modified: / 01-06-2010 / 18:31:14 / cg"
    "Modified (comment): / 13-02-2017 / 10:12:35 / cg"
! !

!ImageView methodsFor:'accessing'!

adjust
    "get the adjust (how the image is displayed);
     currently, support #topLeft, #center, #fitBig, #fitSmall and #fit:
            #topLeft    - image is displayed as usual
            #center     - image is shown centered
            #fitBig     - big images are shrunk to make it fit the view
            #fitSmall   - small images are magnified to make it fit the view,
            #fit        - all images are magnified to fit the view
            #smoothFitBig     - fitBig with smoothing
            #smoothFitSmall   - fitSmall with smoothing
            #smoothFit        - fit with smoothing
    "

    ^ adjust ? #topLeft

    "Modified (comment): / 10-09-2017 / 17:06:16 / cg"
!

adjust:layoutSymbol
    "set the adjust (how the image is displayed);
     currently, support #topLeft, #center, #fitBig, #fitSmall and #fit:
            #topLeft    - image is displayed as usual
            #center     - image is shown centered
            #fitBig     - big images are shrunk to make it fit the view
            #fitSmall   - small images are magnified to make it fit the view,
            #fit        - all images are magnified to fit the view
            #topLeftNoZoom    - image is displayed as usual, and magnification is reset
            #smoothFitBig     - fitBig with smoothing
            #smoothFitSmall   - fitSmall with smoothing
            #smoothFit        - fit with smoothing
    "

    |layoutUsed|

    layoutUsed := layoutSymbol.
    layoutUsed == #topLeftNoZoom ifTrue:[
        layoutUsed := #topLeft
    ].
    self magnificationFactor ~= 1 ifTrue:[
        explicitMagnificationFactor := magnificationFactor := 1.
        self magnificationFactor:1
    ].
    
    adjust ~= layoutUsed ifTrue:[
        adjust := layoutUsed.
        adjustHolder notNil ifTrue:[
            adjustHolder value:layoutUsed withoutNotifying:self
        ].    

        magnifiedImage := smoothMagnifiedImage := nil.
        shown ifTrue:[
            image notNil ifTrue:[
                "/ self magnificationFactor ~= 1 ifTrue:[
                    self generateMagnifiedImage.
                "/ ].    
                self clear.
                self scrollToTopLeft.
                self invalidate.
                self contentsChanged.
            ]
        ].
    ].

    "Modified: / 28-09-2017 / 18:20:55 / cg"
!

adjustHolder
    "get a valeHolder for the adjust (how the image is displayed);
     currently, support #topLeft, #center, #fitBig, #fitSmall and #fit:
            #topLeft    - image is displayed as usual
            #center     - image is shown centered
            #fitBig     - big images are shrunk to make it fit the view
            #fitSmall   - small images are magnified to make it fit the view,
            #fit        - all images are magnified to fit the view
            #smoothFitBig     - fitBig with smoothing
            #smoothFitSmall   - fitSmall with smoothing
            #smoothFit        - fit with smoothing
    "

    adjustHolder isNil ifTrue:[
        adjustHolder := self adjust asValue.
        adjustHolder addDependent:self.
    ].        
    ^ adjustHolder

    "Modified (comment): / 10-09-2017 / 17:06:23 / cg"
    "Modified: / 02-06-2018 / 07:31:26 / Claus Gittinger"
!

forceSmoothing
    ^ self forceSmoothingHolder value

    "Created: / 28-09-2017 / 17:52:38 / cg"
!

forceSmoothing:aBoolean
    self forceSmoothingHolder value:aBoolean.

    "Created: / 28-09-2017 / 17:52:30 / cg"
    "Modified: / 29-09-2017 / 10:30:32 / cg"
!

forceSmoothingHolder
    forceSmoothingHolder isNil ifTrue:[
        forceSmoothingHolder := false asValue.
        forceSmoothingHolder 
            onChangeEvaluate:[
                smoothMagnifiedImage := nil.
                self invalidate.
            ].
    ].
    ^ forceSmoothingHolder

    "Created: / 28-09-2017 / 17:55:51 / cg"
    "Modified: / 29-09-2017 / 10:30:57 / cg"
!

image
    "return the image"

    ^ image
!

image:anImage
    "set the image scrolls as set by adjust
     - may show a wait cursor, if image dithering may take a while"

    self image:anImage scroll:true
!

image:anImage scroll:doScroll
    "set the image possibly scroll as set by adjust
     - may show a wait cursor, if image dithering may take a while"

    self setImage:anImage scroll:doScroll
!

image:anImage scroll:doScroll invalidate:doInvalidate
    "set the image possibly scroll as set by adjust
     - may show a wait cursor, if image dithering may take a while"

    self setImage:anImage scroll:doScroll invalidate:doInvalidate
!

imageEditAction:aOneArgBlockOrNil
    "if nonNil, aOneArgBlockOrNil will be called to open
     the image editor (as by the 'Edit Image' menu action).
     If never set, the default ImageEditor is opened."

    imageEditAction := aOneArgBlockOrNil.

    "Created: / 19-02-2017 / 22:58:14 / cg"
!

magnification
    magnifiedImage isNil ifTrue:[^ 1@1].
    ^ magnifiedImage extent / image extent
!

magnificationFactor
    ^ magnificationFactor ? 1
!

magnificationFactor:aNumber
    true "explicitMagnificationFactor ~= aNumber" ifTrue:[
        magnificationFactor := explicitMagnificationFactor := aNumber.
        "/    magnificationFactor fractionPart < 0.1 ifTrue:[
        "/        "magnifying by integer factor is faster"
        "/        magnificationFactor := magnificationFactor truncated.
        "/    ].
        self synchronized:[
            smoothingProcess notNil ifTrue:[
                smoothingProcess terminate.
                smoothMagnifiedImage := nil.
            ].
        ].    
        magnifiedImage := smoothMagnifiedImage := nil.
        magnificationFactor = 1 ifTrue:[
            self contentsChanged.
        ].
        (self adjust includesString:'fit' caseSensitive:false) ifTrue:[
            adjust := #topLeft.
        ].
        self invalidate.
    ].

    "Modified: / 06-09-2017 / 12:51:28 / Maren"
    "Modified: / 04-10-2017 / 16:40:00 / cg"
    "Modified: / 28-05-2018 / 14:11:50 / Claus Gittinger"
!

model:aValueHolder
    super model:aValueHolder.
    self updateFromModel.

    "Created: / 25-07-2011 / 15:32:10 / cg"
!

setImage:anImage
    "set the image - show a wait cursor, since image dithering may take a while"

    self setImage:anImage scroll:true invalidate:true

    "Modified: / 10.2.2000 / 23:25:51 / cg"
!

setImage:anImage scroll:doScroll
    "set the image - may show a wait cursor, if image dithering may take a while"

    self setImage:anImage scroll:doScroll invalidate:true
!

setImage:anImage scroll:doScroll invalidate:doInvalidate
    "set the image - may show a wait cursor, if image dithering may take a while"

    |oldSize newSize newImageIsSmaller|

    self assert:(anImage isImageOrForm or:[anImage isNil]).
    
    oldSize := image isNil ifTrue:[0@0] ifFalse:[image extent].

    image := anImage.
    magnifiedImage := smoothMagnifiedImage := nil.
    self generateMagnifiedImage.

    newSize := image isNil ifTrue:[0@0] ifFalse:[(magnifiedImage ? image) extent].
    newImageIsSmaller := ((oldSize x > newSize x) or:[oldSize y > newSize y]).

    (newImageIsSmaller and:[ doInvalidate ]) ifTrue:[ self invalidate ].
    doScroll ifTrue:[ self scrollToTopLeft ].

    oldSize ~= newSize ifTrue:[
        "/ avoid endless loop in case of a resize happening due
        "/ to scrollBar visibility changes.
        "/ that QuerySignal suppresses another magnification in sizeChanged:
        DoNotMagnifyQuery answer:true do:[ self contentsChanged ]
    ].

    (shown and:[doInvalidate]) ifTrue:[
        "/ (anImage isNil "or:[newImageIsSmaller]") ifTrue:[
        "/     self clear.
        "/ ].
        self invalidate
    ].
    self changed:#image.

    "Modified: / 28-09-2017 / 18:21:07 / cg"
!

sizeToFit:aBoolean
    aBoolean ifTrue:[
        self adjust:#fitBig
    ] ifFalse:[
        explicitMagnificationFactor := magnificationFactor := 1.
        self adjust:nil.
    ]

    "Modified: / 10-09-2017 / 17:54:49 / cg"
!

tileMode:aBoolean tileOffset:aPoint

    tileMode := aBoolean.
    tileOffset := aPoint
! !

!ImageView methodsFor:'accessing-channels'!

imageChannel
    ^ self model

    "Modified: / 31-03-2011 / 10:45:58 / cg"
!

imageChannel:aValueHolder
    self model:aValueHolder.

    "Created: / 11-02-2000 / 00:34:33 / cg"
    "Modified: / 31-03-2011 / 10:46:07 / cg"
!

mousePointHolder
    ^ lastMousePoint

    "Created: / 25-02-2017 / 22:36:02 / cg"
! !

!ImageView methodsFor:'change & update'!

update:something with:aParameter from:changedObject
    changedObject == adjustHolder ifTrue:[
        self adjust:(adjustHolder value).
        ^ self
    ].

    changedObject == model ifTrue:[
        self updateFromModel.
        ^ self
    ].
    super update:something with:aParameter from:changedObject

    "Created: / 11-02-2000 / 00:37:02 / cg"
    "Modified: / 31-03-2011 / 10:46:16 / cg"
    "Modified: / 02-06-2018 / 07:31:13 / Claus Gittinger"
!

updateFromModel
    "the model changes, set my image"

    self image:model value.
! !

!ImageView methodsFor:'drawing'!

generateMagnifiedImage
    |adj smooth doFit innerWidth innerHeight imgWidth imgHeight|

    image isNil ifTrue:[^ self].
    smoothMagnifiedImage notNil ifTrue:[^ self].
    
    adj := adjust.
    smooth := false.
    (adjust notNil and:[adjust startsWith:#smooth]) ifTrue:[
        adj := (adjust copyFrom:'smooth' size+1) asLowercaseFirst asSymbol.
        smooth := true.
    ].
    self forceSmoothing ifTrue:[smooth := true].

    magnifiedImage isNil ifTrue:[
        doFit := false.

        innerWidth := self innerWidth.
        innerHeight := self innerHeight.

        imgWidth := image width.
        imgHeight := image height.

        (#(fit fitBig fitSmall) includes:adj) ifTrue:[
            magnificationFactor := explicitMagnificationFactor.
        ].

        tileMode ~~ true ifTrue:[
            ((imgWidth > innerWidth)
            or:[imgHeight > innerHeight]) ifTrue:[
                ((adj == #fit) or:[adj == #fitBig]) ifTrue:[
                    doFit := true
                ].
            ] ifFalse:[
                ((imgWidth < innerWidth)
                or:[imgHeight < innerHeight]) ifTrue:[
                    ((adj == #fit) or:[adj == #fitSmall]) ifTrue:[
                        doFit := true
                    ].
                ]
            ].
        ].

        doFit ifTrue:[
            magnifiedImage := image magnifiedPreservingRatioTo:(innerWidth @ innerHeight) smooth:false "smooth".
            magnificationFactor := magnifiedImage width / imgWidth.
        ] ifFalse:[
            (magnificationFactor notNil and:[magnificationFactor ~= 1]) ifTrue:[
                magnifiedImage := image magnifiedBy:magnificationFactor smooth:false "smooth".
            ] ifFalse:[
                magnifiedImage := image.
            ].
        ].
    
        (magnifiedImage width == 0 or:[magnifiedImage height == 0]) ifTrue:[
            magnifiedImage := nil.
        ].    

        smooth ifTrue:[
            smoothMagnifiedImage := nil.
        ] ifFalse:[
            smoothMagnifiedImage := magnifiedImage.
        ].
        
        self contentsChanged.
    ].
    
    smooth ifTrue:[
        magnifiedImage extent = image extent ifTrue:[
            smoothMagnifiedImage := magnifiedImage
        ] ifFalse:[    
            smoothMagnifiedImage := nil.
            
            self synchronized:[
                smoothingProcess notNil ifTrue:[
                    smoothingProcess terminate.
                    smoothMagnifiedImage := nil.
                ].
                smoothingProcess := 
                    [
                        |i|
                        
                        (i := magnifiedImage) notNil ifTrue:[
                            smoothMagnifiedImage := image magnifiedTo:(i extent) smooth:true.
                            self invalidate.
                        ]
                    ] fork.
            ].        
        ].    
    ].

    "Modified: / 06-09-2017 / 12:52:19 / Maren"
    "Modified: / 04-10-2017 / 16:43:44 / cg"
    "Modified: / 28-05-2018 / 14:20:59 / Claus Gittinger"
!

redrawX:x y:y width:w height:h
    |xI yI depth shownImage imgWidth imgHeight right bott "rectRight rectBelow"|

    self generateMagnifiedImage.

    shownImage := smoothMagnifiedImage ? magnifiedImage ? image.
    shownImage notNil ifTrue:[
        imgWidth := shownImage width.
        imgHeight := shownImage height.

        adjust == #center ifTrue:[
            xI := (width - (margin * 2) - imgWidth) // 2.
            yI := (height - (margin * 2) - imgHeight) // 2.
        ] ifFalse:[
            xI := yI := margin
        ].

        ((depth := shownImage depth) == 1) ifTrue:[
            self paint:(shownImage colorFromValue:1)
                    on:(shownImage colorFromValue:0).
        ].

        tileMode == true ifTrue:[
            (tileOffset y > 0 and:[tileOffset x > 0]) ifTrue:[
                (false "depth ~~ 1"
                or:[shownImage mask notNil]) ifTrue:[
                    self clearRectangleX:x y:y width:w height:h.
                    0 to:y+h by:tileOffset y do:[:oY |
                        0 to:x+w by:tileOffset x do:[:oX |
                            gc displayForm:image x:oX y:oY
                        ]
                    ].
                ] ifFalse:[
                    0 to:y+h by:tileOffset y do:[:oY |
                        0 to:x+w by:tileOffset x do:[:oX |
                            gc displayOpaqueForm:image x:oX y:oY
                        ]
                    ].
                ].
            ]
        ] ifFalse:[
            "/rectRight := (shownImage width @ 0) corner:(width @ shownImage height).
            "/rectBelow := 0 @ (shownImage height) corner:(shownImage height @ height).
            "/(rectRight width > 0 and:[rectRight height > 0]) ifTrue:[ self clearRectangle:rectRight ].
            "/(rectBelow width > 0 and:[rectBelow height > 0]) ifTrue:[ self clearRectangle:rectBelow ].

            xI > (x+w) ifTrue:[^ self]. "/ no need to draw
            yI > (y+h) ifTrue:[^ self]. "/ no need to draw
            (xI+imgWidth) < x ifTrue:[^ self]. "/ no need to draw
            (yI+imgHeight) < y ifTrue:[^ self]. "/ no need to draw

            (false "depth ~~ 1"
            or:[shownImage mask notNil]) ifTrue:[
                self clearRectangleX:x y:y width:w height:h.
                shownImage displayOn:self x:xI y:yI.
                "/ self displayForm:shownImage x:xI y:yI
            ] ifFalse:[
                (shownImage depth == 32 and:[ shownImage hasAlphaChannel ]) ifTrue:[
                    "/ now, we support at least some 32rgba images...
                    true "device supportsAlphaChannel" ifTrue:[
                        self clearRectangleX:x y:y width:w height:h.
                        self displayForm:shownImage x:xI y:yI
                    ] ifFalse:[
                        "/ hack: currently 32bit+alpha is not supported by Displays.
                        "/ make it a masked d24 image and draw that.

                        |d24Image|

                        d24Image := Depth24Image fromImage:shownImage.
                        d24Image mask:(ImageMask fromAlphaInImage:shownImage).
                        self clearRectangleX:x y:y width:w height:h.
                        d24Image displayOn:self x:xI y:yI.
                    ]
                ] ifFalse:[    
                    gc displayOpaqueForm:shownImage x:xI y:yI
                ].
            ].

            "/ right of image ?
            right := x + w - 1.
            right > (xI + imgWidth) ifTrue:[
                self clearRectangleX:(xI + imgWidth) y:y
                     width:(right - imgWidth - xI) height:h
            ].
            "/ below of image ?
            bott := y + h - 1.
            bott > (yI + imgHeight) ifTrue:[
                self clearRectangleX:margin y:(yI + imgHeight)
                     width:w height:(bott - imgHeight - yI)
            ].
        ].
    ] ifFalse:[
        self clearRectangleX:x y:y width:w height:h.
    ].

    "Created: / 11-07-1996 / 21:02:12 / cg"
    "Modified: / 28-09-2017 / 18:19:01 / cg"
! !

!ImageView methodsFor:'event handling'!

buttonMotion:state x:x y:y
    |delta mousePoint lastPoint|

    (state ~~ 0 and:[(lastPoint := lastMousePoint value) notNil]) ifTrue:[
        mousePoint := (x@y).
        delta := mousePoint - lastPoint.
        delta ~= (0@0) ifTrue:[
            "/ lastMousePoint value:mousePoint.
            self scrollTo:(self viewOrigin - delta) "/ waitForDrawingFinished:true.
        ]
    ] ifFalse:[
        super buttonMotion:state x:x y:y
    ]

    "Modified: / 05-10-2017 / 13:54:48 / cg"
!

buttonPress:button x:x y:y
    button == 1 ifTrue:[
        "self cursor:(Cursor handGrab). "
        lastMousePoint value:( x@y ).
        "/ self sensor compressMotionEvents:true.
    ].
    super buttonPress:button x:x y:y

    "Modified: / 05-10-2017 / 13:56:06 / cg"
!

buttonRelease:button x:x y:y
    button == 1 ifTrue:[
        lastMousePoint value: nil
    ].
    super buttonRelease:button x:x y:y

    "Modified: / 25-02-2017 / 22:35:20 / cg"
!

mouseWheelZoom:amount
    "CTRL-wheel action"

    |mul|

    amount > 0 ifTrue:[
        mul := 1.2.
    ] ifFalse:[
        mul := 0.8.
    ].
    self magnificationFactor:((magnificationFactor ? 1) * mul).

    "Created: / 06-05-2012 / 12:18:06 / cg"
!

sizeChanged:how
    (#(fit fitBig fitSmall smoothFit smoothFitBig smoothFitSmall) includes:adjust) ifTrue:[
        magnifiedImage notNil ifTrue:[
            DoNotMagnifyQuery query ifFalse:[
                self pushEvent:#updateImageAfterSizeChange.
            ]
        ].
    ] ifFalse:[
        adjust == #center ifTrue:[
            self clear.
            self invalidate.
        ].
    ].
    super sizeChanged:how

    "Modified: / 10-09-2017 / 17:11:08 / cg"
!

updateImageAfterSizeChange
    |oldMagnifiedImage|
    
    oldMagnifiedImage := magnifiedImage.
    smoothMagnifiedImage := magnifiedImage := nil.

    self generateMagnifiedImage.

    (oldMagnifiedImage isNil
      or:[ magnifiedImage isNil
      or:[ oldMagnifiedImage extent ~= magnifiedImage extent ]]
    ) ifTrue:[
        "/ self clear.
        self invalidate.
    ].

    "Modified (format): / 28-09-2017 / 18:20:32 / cg"
! !

!ImageView methodsFor:'initialization & release'!

destroy
    magnifiedImage := smoothMagnifiedImage := nil.
    "/ image := nil.
    super destroy.

    "Modified: / 28-09-2017 / 18:20:44 / cg"
!

initialize
    super initialize.

    lastMousePoint := nil asValue.

    "Created: / 25-02-2017 / 22:33:27 / cg"
! !

!ImageView methodsFor:'menu'!

editImage
    |img|

    img := self image.
    imageEditAction notNil ifTrue:[
        imageEditAction value:img
    ] ifFalse:[    
        ImageEditor openOnImage:img.
    ].

    "Created: / 16-02-2017 / 19:01:41 / cg"
    "Modified: / 19-02-2017 / 23:01:14 / cg"
!

fitBigMenuItemVisible
    ^ true
!

magnifyBy:scale
    self adjustHolder value:#Mag withoutNotifying:self.
    self adjust:#topLeft.
    "/ set again, so the menu shows nothing 
    self adjustHolder value:#Mag withoutNotifying:self.
    self magnificationFactor:scale.

    "Created: / 13-09-2017 / 09:43:11 / cg"
    "Modified: / 02-06-2018 / 07:33:15 / Claus Gittinger"
!

middleButtonMenu
    menuHolder notNil ifTrue:[^ super middleButtonMenu].
    ^ [ self class middleButtonMenu ]
!

middleButtonMenuExtraSlice
    ^ [ self class middleButtonMenuExtraSlice ]
!

saveImageAs
    |imageFilename|

    imageFilename := Dialog
                        requestFileName:(resources string:'Save Image As')
                        default:'image.png'
                        pattern:'*.png'
                        fromDirectory:Filename currentDirectory.

    imageFilename notNil ifTrue:[
        self saveImageAs:imageFilename
    ].
!

saveImageAs:filename
    PNGReader save:image onFile:filename
!

smoothFitBigMenuItemVisible
    ^ true
!

smoothingMenuItemVisible
    ^ true
! !

!ImageView methodsFor:'queries'!

heightOfContents
    "return the image's height - scrollbar needs this info"

    image isNil ifTrue:[^ 0].
    ^ (magnifiedImage ? image) height
!

selfIsNotImageEditor
    "for the menu; to hide edit item"

    ^ true

    "Created: / 16-02-2017 / 19:00:59 / cg"
!

widthOfContents
    "return the image's width - scrollbar needs this info"

    image isNil ifTrue:[^ 0].
    ^ (magnifiedImage ? image) width
! !

!ImageView methodsFor:'scrolling'!

scrollToMakeRectangleVisible:aRectangle
    "try to arrange for aRectangle to be visible (at the center, if possible)"

    self scrollToMakeVisible:(aRectangle center).
    self scrollToMakeVisible:(aRectangle corner).
    self scrollToMakeVisible:(aRectangle origin).

    "
     |v|
     v := ImageView new image:(Image fromFile:'../../goodies/bitmaps/gifImages/garfield.gif').
     v openAndWait.
     v inspect.
     v scrollToMakeRectangleVisible:((300@100) corner:(320@120)).
    "

    "Created: / 20-07-2017 / 13:45:29 / cg"
!

scrollToMakeVisible:aPoint
    "try to arrange for aPoint (in logical image coordinates)
     to be visible (at the center, if possible)"

    |mag magPos imgExtent newOffset|

    imgExtent := 0@0.
    image notNil ifTrue:[
        imgExtent := image extent.
    ].

    mag := self magnification.
    magPos := aPoint * mag.

    ((self bounds + self viewOrigin) containsPoint:magPos) ifFalse:[
        newOffset := magPos - (self extent / 2).
        newOffset := newOffset max:(0@0).
        newOffset := newOffset min:((imgExtent * mag) - (self extent / 2)).
        newOffset := newOffset rounded.
        self scrollTo:newOffset waitForDrawingFinished:true.
    ].

    "
     |v|
     v := ImageView new image:(Image fromFile:'../../goodies/bitmaps/gifImages/garfield.gif').
     v openAndWait.
     v inspect.
     v scrollToMakeVisible:(300@100).
    "

    "Modified: / 20-07-2017 / 14:37:11 / cg"
! !

!ImageView class methodsFor:'documentation'!

version
    ^ '$Header$'
!

version_CVS
    ^ '$Header$'
! !


ImageView initialize!