/*----------------------------------------------------------------------------*/
/*                                                                            */
/* Copyright (c) 2008-2014 Rexx Language Association. All rights reserved.    */
/*                                                                            */
/* This program and the accompanying materials are made available under       */
/* the terms of the Common Public License v1.0 which accompanies this         */
/* distribution. A copy is also available at the following address:           */
/* https://www.oorexx.org/license.html                                        */
/*                                                                            */
/* Redistribution and use in source and binary forms, with or                 */
/* without modification, are permitted provided that the following            */
/* conditions are met:                                                        */
/*                                                                            */
/* Redistributions of source code must retain the above copyright             */
/* notice, this list of conditions and the following disclaimer.              */
/* Redistributions in binary form must reproduce the above copyright          */
/* notice, this list of conditions and the following disclaimer in            */
/* the documentation and/or other materials provided with the distribution.   */
/*                                                                            */
/* Neither the name of Rexx Language Association nor the names                */
/* of its contributors may be used to endorse or promote products             */
/* derived from this software without specific prior written permission.      */
/*                                                                            */
/* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS        */
/* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT          */
/* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS          */
/* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT   */
/* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,      */
/* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED   */
/* TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,        */
/* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY     */
/* OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING    */
/* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS         */
/* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.               */
/*                                                                            */
/*----------------------------------------------------------------------------*/

/**
 * File:     imageButton.rex
 *
 * Category: ooDialog example
 *
 * Syntax:
 *   rc = imageButton
 *
 * Purpose:
 *   Provides some example code of new ooDialog feature introduced in 4.0.0
 *
 *   This program uses a resource script (.rc) file for the dialog definition.
 *   The dialog was designed using a "what you see is what you get" dialog
 *   editor.  The resource script can be compiled into a binary image for use
 *   with a ResDialog.  In this case it is just left as a .rc file for use in
 *   a RcDialog.
 *
 *   Dialogs built using a dialog editor are much easier to create and maintain
 *   then UserDialogs.
 *
 * Input:
 *   None.
 *
 * Returns:
 *   0 on success, non-zero if the dialog could not be created for some reason.
 *
 * Notes:
 *   This is a short program written to amuse my niece.  Two of the button
 *   controls in the dialog use an image list.  The text for the buttons is 'Add
 *   Pictures' and 'View Pictures' with a picture of a camera on them.  When the
 *   user moves the mouse pointer over either one of the buttons, the picture
 *   changes to a skull and the text changes to 'Death if you touch me.' When
 *   the user pushes either of the buttons the picture changes to a giant bee.
 *
 *   My niece and I think this is hilarious.
 *
 *   The dialog shows how to use a button control image list.  Since I am not
 *   artistic, I produced the images by cutting them out of digital pictures
 *   I have taken over the last few years.
 *
 *   A professional application would of course use professional graphics.  This
 *   dialog just gives an idea of what the button control can do with an image
 *   list.
\* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -*/
use arg

  -- Ensure we can be run from any directory.
  srcDir = locate()

  rcFile = srcDir"resources\imageButton.rc"
  symbolFile = srcDir"resources\imageButton.h"

  .application~setDefaults("O", symbolFile, .false)

  dlg = .ImageListDlg~new(rcFile, IDD_IMAGELIST_BUTTON)

  if dlg~initCode <> 0 then do
    say "The Image List Dialog was not created correctly"
    say "Init code:" dlg~initCode
    return 99
  end

  dlg~execute("SHOWTOP", IDI_DLG_OOREXX)

return 0
-- End of entry point.

/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -*\
  Directives, Classes, or Routines.
\* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -*/
::requires "ooDialog.cls"

::class 'ImageListDlg' subclass RcDialog

::method initDialog
  expose pbPush pbView pbAdd stStatus ctr imagesLoaded

  self~connectButtonEvent(IDC_PB_PUSHME, "CLICKED", "onPushMe")
  self~connectButtonEvent(IDC_PB_VIEW, "CLICKED", "onView")
  self~connectButtonEvent(IDC_PB_ADD, "CLICKED", "onAdd")

  self~connectButtonEvent(IDC_PB_VIEW, "HOTITEM", onHover)
  self~connectButtonEvent(IDC_PB_ADD, "HOTITEM", onHover)

  imagesLoaded = .false
  ctr = 0

  pbPush = self~newPushButton(IDC_PB_PUSHME)
  pbView  = self~newPushButton(IDC_PB_VIEW)
  pbAdd  = self~newPushButton(IDC_PB_ADD)
  stStatus = self~newStatic(IDC_ST_STATUS)

  stStatus~setText("")

  self~setPictureButtons

::method setPictureButtons private
  expose pbView pbAdd stStatus imagesLoaded imageList

  -- Using an image list with the button controls needs at least comctl32
  -- version 6.  See what version we are running under and don't use the image
  -- list if we are on an older version of Windows.
  if .DlgUtil~comCtl32Version < 6 then return self~oldSetButtons

  pbView~style = "MULTILINE BOTTOM"

  -- In the following code, there are a lot of if tests.  You can change any of
  -- the if tests to its converse to get an idea of the dialog behavior when
  -- errors happen.  Have fun, experiment.

  srcDir = .application~srcDir

  -- The images are loaded from files.
  files = .array~new()
  files[1] = srcDir"resources\Normal.bmp"       -- Normal
  files[2] = srcDir"resources\Hot.bmp"          -- Hot (hover)
  files[3] = srcDir"resources\Pushed.bmp"       -- Pushed
  files[4] = srcDir"resources\Disabled.bmp"     -- Disabled
  files[5] = srcDir"resources\Default.bmp"      -- Default button
  files[6] = srcDir"resources\Hot.bmp"          -- Stylus hot, tablet PC only

  -- .ImageList~create() exposes the Windows API ImageList_Create().  Use the
  -- MSDN documentation to fully understand this API.  Simply do a Google search
  -- with: MSDN ImageList_Create
  --
  -- The IIC_COLOR24 flag and the size values correspond to the values the
  -- bitmaps were created with.  You can look at the properties page for one
  -- of the bitamp files in Explorer to see these values.

  -- We set the flags to create a 24 bit color, masked image list.
  flags = 'COLOR24 MASK'

  -- Create an empty .ImageList object:
  imageList = .ImageList~create(.Size~new(61, 46), flags, 10, 10);

  -- Add the images to the image list, only if the image list is not null.  If
  -- the .ImageList object is null, an error happened when it was created and it
  -- is not usable.
  --
  -- When an image is added to the image list, the index of the image is
  -- returned.  -1 signals that the image was not added.  We use that as a flag
  -- to indicate some images are not added.
  index = -1
  lastGoodIndex = -1
  if \ imageList~isNull then do
    stStatus~setText('Created image list:' imageList~handle)

    -- We use a colorref when adding the image to the image list.  The 255,255,
    -- 255 value is white.  Essentially it tells the image list to create a mask
    -- that causes every  white pixel in the image to be transparent.
    --
    -- The only image it has any effect on is the camera.  In the camera image,
    -- the background uses white pixels.  So, when the camera image is drawn,
    -- the background becomes transparent, which allows the button color to show
    -- through.
    cRef = .Image~colorRef(255, 255, 255)

    -- For each image file, get an .Image object and add it to our image list.
    do f over files

      -- The defaults for the optional arguments to getImage() are good for this
      -- application.  Again, isNull() signals the .Image object is not valid.
      image = .Image~getImage(f)
      if \ image~isNull then do

        -- Add the image, quit if we get back a -1.
        index = imageList~addMasked(image, cRef)
        if index == -1 then leave

        stStatus~setText('Added image index:' index)
        lastGoodIndex = index

        -- The underlying ImageList control makes a copy of the image passed to
        -- it.  We can now relase the image to free up the system resource.
        -- Note that when this program ends, the OS will free the resource
        -- automatically, so this is NOT necessary.  Note also that you should
        -- not release SHARED images.  However, when an image is loaded from a
        -- file, it can not be a shared image.
        image~release

        -- Once you release the image, the .Image object is no longer valid and
        -- trying to use it will raise a syntax error.  You can use the isNull()
        -- method on any image object at any time to test if it is valid without
        -- raising a syntax error.  In addition, if you invoke the release()
        -- method after the image has already been released, it is just ignored.
      end
      else do
        say 'Failed to load bitmap:' f 'system error code:' .SystemErrorCode
      end
    end
  end
  else do
    stStatus~setText('Error creating .ImageList.  Images are not available.')
    return
  end

  if lastGoodIndex == -1 then do
    stStatus~setText("Failed to add any images.  Images are not available")
    imageList~release
    return
  end

  imagesLoaded = .true

  if index == -1 then do
    stStatus~setText("Not all images were loaded.  Last good image at index" lastGoodIndex)
  end

  -- Set up the alignment and margin around the image on the 'View Pictures'
  -- button.  Then set the image list in the button control
  align = 'LEFT'
  margin = .Rect~new(1)

  pbView~setImageList(imageList, margin, align)

  if .SystemErrorCode <> 0 then do
    stStatus~setText('setImageList() failed. SystemErrorCode:' .SystemErrorCode 'Images not available.')
    imageList~release
    return
  end
  else do
    stStatus~setText("View Pictures button image list loaded.")
  end

  -- Temporarily set the title to the longest text, to calculate ideal size.
  pbView~setTitle("DEATH if you touch me")
  bestSize = pbView~getIdealSize

  -- Write this to the screen, example only.
  say 'Got ideal size:' bestSize
  say '  width: ' bestSize~width
  say '  height:' bestSize~height
  say

  -- Now reset the button label and set the size to the ideal size.
  pbView~setTitle("View Pictures")
  pbView~setRect(0, 0, bestSize~width, bestSize~height, "NOMOVE")

  -- Example only of the getImageList() method.  A .Directory object is
  -- returned.
  d = pbView~getImageList

  -- Just write this to the screen.
  say 'Got View Picutes button control image list information.'
  say 'Returned:' d
  if d <> .nil then do
    say '  image list:' d~imageList
    say '  handle:    ' d~imageList~handle
    say '  rect:      ' d~rect
    say '  align:     ' d~alignmentKeyword
    say 'Image margins:' d~rect~left',' d~rect~top',' d~rect~right',' d~rect~bottom
  end
  else do
    stStatus~setText("Error getting View Pictures button control image list information.")
  end
  say

  -- For the Add Pictures button, use the same image list created for the View
  -- Pictures button.  We just change the margins and the alignment.
  align = 'CENTER'
  margin~left = 10
  margin~right = 10
  margin~top = 30
  margin~bottom = 15

  pbAdd~setImageList(imageList, margin, align)

  if .SystemErrorCode <> 0 then do
    stStatus~setText('setImageList() failed. SystemErrorCode:' .SystemErrorCode 'Images not available.')

    -- Releasing the image list now, after it has been successfully set for the
    -- View Pictures button, causes the images to disappear from that button.
    --
    -- In general, DON'T release an image list that is in use.  This is just
    -- done here to demonstrate that things won't blow up.
    imageList~release
    return
  end
  else do
    stStatus~setText("Add Pictures button image list loaded.")
  end

  -- Now, the same as above, figure out the ideal size and then adjust the
  -- button size.
  pbAdd~style = "BOTTOM MULTILINE"
  pbAdd~setTitle("DEATH if you touch me")
  s = pbAdd~getIdealSize
  pbAdd~setTitle("Add Pictures")
  pbAdd~setRect(0, 0, s~width, s~height, "NOMOVE")

  -- Again, just for an example of the getImageList() method.
  d = pbAdd~getImageList

  say 'Got Add Picutes button control image list information.'
  say 'Returned:' d
  if d <> .nil then do
    -- Write this to the screen.  Note that the image list hand is the same as
    -- the above.
    say '  image list:' d~imageList
    say '  handle:    ' d~imageList~handle
    say '  rect:      ' d~rect
    say '  align:     ' d~alignmentKeyword
    say 'Image margins:' d~rect~left',' d~rect~top',' d~rect~right',' d~rect~bottom
  end
  else do
    stStatus~setText("Error getting Add Pictures button control image list information.")
  end
  say

return

-- This method allows the dialog to work on systems prior to XP.  We just use
-- the old fashioned buttons.
::method oldSetButtons private
  expose pbView pbAdd

  pbView~setTitle("View Pictures")
  pbAdd~setTitle("Add Pictures")

  pbView~resize(50, 15)
  pbAdd~resize(50, 15)

return .true

-- As the user pushes the Push Me button, we cycle through some different
-- examples of changing the button state and style.  This changes the pictures
-- displayed on the buttons.  (Provided of course that there were no errors in
-- setting the button control image list(s).)  The state and style changes work
-- whether we have images or not, so we don't check for pictures loaded or not
-- on those.
::method onPushMe unguarded
  expose pbView pbAdd stStatus ctr imagesLoaded oldImageList

  select
    when ctr == 0 then do
      pbView~state = "FOCUS"
      stStatus~setText('View Pictures button set to the Focused state.')
      ctr += 1
    end
    when ctr == 1 then do
      pbAdd~style = "DEFPUSHBUTTON"
      stStatus~setText('Add Pictures button now the default push button.')
      ctr += 1
    end
    when ctr == 2 then do
      pbView~style = "DEFPUSHBUTTON"
      stStatus~setText('View Pictures button now the default push button.')
      ctr += 1
    end
    when ctr == 3 then do
      pbView~disable
      stStatus~setText('View Pictures button now disabled.')
      ctr += 1
    end
    when ctr == 4 then do
      pbView~enable
      cancel = self~newPushButton(IDCANCEL)
      cancel~style = "DEFPUSHBUTTON"
      stStatus~setText('View Pictures button now enabled, should not be default push button.')
      ctr = 5
    end
    when ctr == 5 then do
      if imagesLoaded then do
        oldImageList = pbView~setImageList(.nil)
        stStatus~setText('Removed View Pictures button images.  Push "Push Me one more time to restore".')
      end
      ctr = 6
    end
    when ctr == 6 then do
      if imagesLoaded, oldImageList \== .nil then do
        pbView~setImageList(oldImageList~imageList, oldImageList~rect, oldImageList~alignment)
        stStatus~setText('Restored View Pictures button images.')
      end
      ctr = 0
    end
    otherwise do
      -- Should be impossible to happen.  Just do the same as if ctr were 6.
      if imagesLoaded, oldImageList \== .nil then do
        pbView~setImageList(oldImageList~imageList, oldImageList~rect, oldImageList~alignment)
        stStatus~setText('Restored View Pictures button images.')
      end
      ctr = 0
    end
  end
  -- End select

-- This is the amusing part.  We get this event message when the mouse is first
-- moved over a button (entering will be .true) or when the mouse moves off a
-- button (entering will be .false.)  We change the text and style of the
-- button, the button control itself changes the picture.
::method onHover unguarded
  expose imagesLoaded pbView pbAdd
  use arg id, entering

  if \ imagesLoaded then return

  isViewButton = (id == .constDir[IDC_PB_VIEW])

  if entering then do
    text = "DEATH if you touch me"
    style = "TOP"
  end
  else do
    if isViewButton then text = "View Pictures"
    else text = "Add Pictures"
    style = "BOTTOM"
  end

  if isViewButton then do
    pbView~style = style
    pbView~setTitle(text)
  end
  else do
    pbAdd~style = style
    pbAdd~setTitle(text)
  end

::method onView unguarded
  expose stStatus
  stStatus~setText('In onView, no actions defined.')

::method onAdd unguarded
  expose stStatus
  stStatus~setText('In onAdd, no actions defined.')


/* When you are done using an image list you can release it to free up system
 * resources.
 *
 * Please note that when this program ends, the process will disappear and the
 * OS will free up the resources automatically.
 *
 * As noted in a comment above, if you release the image list when it is in
 * use, the pictures disappear.  Testing has shown that releasing an image list
 * when it is in use, does not crash the system.  However, releasing an image
 * list while it is in use would seem to be a foolish practice.
 *
 * When would you want to release an image list?  In this case if this dialog
 * was a part of a bigger, long running application.  When this dialog ends, it
 * makes sense to free up the system resources used for the image list.  Since
 * the main application is still running, the OS will not release the resources
 * used in this dialog.
 *
 * Microsoft says, you should release an image list when you are done with it.
 *
 * In this case that would be when the dialog is closed.  There are at least 2
 * strategies that could be employed.
 *
 * 1.) Save a reference to the image list handle returned by .ImageList~create()
 * and pass it to release().
 *
 * 2.) Use getImageList() to ask the button to give you the image list
 * information and use the image list object that is returned as part of the
 * .Directory object.  The image list object in strategy 1 and 2 are the same
 * object.
 *
 * Don't forget to release the handle if either the dialog is canceled or if it
 * is closed.  Below is a demonstration of both strategies.
 */

::method cancel
  expose imageList imagesLoaded

  if imagesLoaded then imageList~release
  return self~cancel:super

::method ok
  expose imagesLoaded pbView

  if imagesLoaded then do
    imageListInfo = pbView~getImageList
    if imageListInfo <> .nil then imageListInfo~imageList~release
  end

  return self~ok:super

