Index: /trunk/grails-app/conf/SecurityConfig.groovy
===================================================================
--- /trunk/grails-app/conf/SecurityConfig.groovy	(revision 181)
+++ /trunk/grails-app/conf/SecurityConfig.groovy	(revision 182)
@@ -46,5 +46,7 @@
     '/login/**': ['IS_AUTHENTICATED_ANONYMOUSLY'],
     '/logout*': ['IS_AUTHENTICATED_FULLY'],
-    '/logout/**': ['IS_AUTHENTICATED_FULLY']
+    '/logout/**': ['IS_AUTHENTICATED_FULLY'],
+    '/image*': ['IS_AUTHENTICATED_FULLY'],
+    '/image/**': ['IS_AUTHENTICATED_FULLY']
     ]
 
Index: /trunk/grails-app/conf/UrlMappings.groovy
===================================================================
--- /trunk/grails-app/conf/UrlMappings.groovy	(revision 181)
+++ /trunk/grails-app/conf/UrlMappings.groovy	(revision 182)
@@ -1,11 +1,20 @@
 class UrlMappings {
     static mappings = {
-      "/$controller/$action?/$id?"{
-	      constraints {
-			 // apply constraints here
-		  }
-	  }
-      "/"(view:"/index")
-	  "500"(view:'/error')
-	}
+        "/$controller/$action?/$id?"{
+            constraints {
+                // apply constraints here
+            }
+        }
+
+        "/image/$id/$size?/$filename?" {
+            constraints {
+                size(matches: /\d+/)
+            }
+            controller = 'pictureDetailed'
+            action = 'view'
+        }
+
+        "/"(view:"/index")
+        "500"(view:'/error')
+    }
 }
Index: /trunk/grails-app/controllers/AssignedPersonDetailedController.groovy
===================================================================
--- /trunk/grails-app/controllers/AssignedPersonDetailedController.groovy	(revision 181)
+++ /trunk/grails-app/controllers/AssignedPersonDetailedController.groovy	(revision 182)
@@ -85,5 +85,5 @@
         if(!params.task?.id) {
             flash.message = "Please select a task and then 'Add Assigned Person'"
-            redirect(controller: "taskDetailed", action: list)
+            redirect(controller: "taskDetailed", action: search)
         }
         else {
Index: /trunk/grails-app/controllers/PictureDetailedController.groovy
===================================================================
--- /trunk/grails-app/controllers/PictureDetailedController.groovy	(revision 182)
+++ /trunk/grails-app/controllers/PictureDetailedController.groovy	(revision 182)
@@ -0,0 +1,214 @@
+import org.codehaus.groovy.grails.plugins.springsecurity.Secured
+
+class PictureDetailedController extends BaseController {
+
+    def index = { redirect(action:list, params:params) }
+
+    // the delete, save and update actions only accept POST requests
+    static allowedMethods = [delete:'POST', save:'POST', update:'POST']
+
+    def list = {
+        prepareList()
+        [ list: Picture.list(params), paginateCount: Picture.count() ]
+    }
+
+    void prepareList() {
+        if (!params.max) {
+            params.max = 10
+        }
+        if (!params.order) {
+            params.order = "desc"
+        }
+        if (!params.sort) {
+            params.sort = "id"
+        }
+    }
+
+    def show = {
+        // In the case of an actionSubmit button, rewrite action name from 'index'.
+        if(params._action_Show)
+        { params.action='show' }
+
+        def picture = Picture.get( params.id )
+
+        if(!picture) {
+            flash.message = "Picture not found."
+            redirect(action:list)
+        }
+        else { [ picture : picture ] }
+    }
+
+    def delete = {
+        def picture = Picture.get(params.id)
+        def inventoryItem = InventoryItem.get(picture?.inventoryItem?.id)
+
+        if (inventoryItem && picture) {
+            Picture.withTransaction { status ->
+                inventoryItem.picture = null
+                inventoryItem.save()
+                picture.delete()
+            }
+            flash.message = "Picture ${params.id} deleted"
+            redirect(controller: "inventoryItemDetailed", action: show, id: inventoryItem.id)
+
+        }
+        else {
+            if(picture) {
+                flash.message = "Removing orphan picture ${picture.id}."
+                picture.delete(flush:true)
+                redirect(action: list)
+            }
+            else {
+                flash.message = "Picture not found"
+                redirect(action: list)
+            }
+        }
+    }
+
+    def edit = {
+        // In the case of an actionSubmit button, rewrite action name from 'index'.
+        if(params._action_Edit)
+        { params.action='edit' }
+
+        def picture = Picture.get(params.id)
+        if (!picture) {
+            flash.message = "Picture not found"
+            redirect(action: list)
+        }
+        else {
+            [ picture : picture ]
+        }
+    }
+
+    def update = {
+        def picture = Picture.get(params.id)
+        def inventoryItem = picture?.inventoryItem
+        if (inventoryItem && picture) {
+
+            if(params.version) {
+                def version = params.version.toLong()
+                if(picture.version > version) {
+                    picture.errors.rejectValue("version", "picture.optimistic.locking.failure", "Another user has updated this Picture while you were editing.")
+                    render(view:'edit',model:[picture:picture])
+                    return
+                }
+            }
+
+            picture.properties = params
+            if (!picture.hasErrors()) {
+                def imaging = new Imaging()
+                def images = imaging.updateAll(picture)
+                boolean result = false
+                Picture.withTransaction{ status ->
+                    result = picture.save()
+                }
+                if (result) {
+                    flash.message = "Picture ${params.id} updated"
+                    redirect(action: show, id: picture.id)
+                }
+                else {
+                    render(view: 'edit', model: [ picture: picture ])
+                }
+            }
+            else {
+                render(view: 'edit', model: [ picture: picture ])
+            }
+        }
+        else {
+            flash.message = "Picture not updated, picture or inventory item not found."
+            redirect(action: list)
+        }
+    }
+
+    def create = {
+        if(!params.inventoryItem?.id) {
+            flash.message = "Please select an inventory item and then 'Add Picture'."
+            redirect(controller: "inventoryItemDetailed", action: 'search')
+            return
+        }
+        def picture = new Picture()
+        picture.properties = params
+        [ picture: picture ]
+    }
+
+    def save = {
+        def inventoryItem = InventoryItem.get(params.inventoryItem.id)
+        if (inventoryItem) {
+            if(inventoryItem.picture) {
+                flash.message = "Inventory item already has a picture, please delete the old picture first."
+                redirect(controller: 'inventoryItemDetailed', action: 'show', id: inventoryItem.id)
+                return
+            }
+            def picture = new Picture(params)
+            def multiPartFile = request.getFile('file')
+            def imaging = new Imaging()
+            def images = null
+            if (multiPartFile && !multiPartFile.isEmpty()) {
+                if (multiPartFile.getSize() > Image.MAX_SIZE) {
+                    picture.errors.rejectValue('file', 'picture.file.maxSize.exceeded',
+                        [ 'file', 'Picture', Image.MAX_SIZE ] as Object[],
+                        'Property [{0}] of class [{1}] image file is too big (maximum [{2}] bytes)')
+                }
+                else {
+                    try {
+                        images = imaging.createAll(inventoryItem, picture, multiPartFile.inputStream)
+                    }
+                    catch(Exception ex) {
+                        log.error("picture save", ex)
+                        picture.errors.rejectValue('file', 'picture.file.unrecognised',
+                            [ 'file', 'Picture', multiPartFile.originalFilename ] as Object[],
+                            'Property [{0}] of class [{1}] image file [{2}]: type not recognised')
+                    }
+                }
+            }
+            else {
+                picture.errors.rejectValue('file', 'picture.file.missing',
+                    ['file', 'Picture', ''] as Object[],
+                    'Property [{0}] of class [{1}] no file specified')
+            }
+            if (images && !picture.hasErrors()) {
+                boolean result = false
+                Picture.withTransaction { status ->
+                    images.each { image ->
+                        picture.addToImages(image)
+                    }
+                    result = picture.save()
+                    inventoryItem.picture = picture
+                    inventoryItem.save()
+                }
+                if (result) {
+                    flash.message = "Picture ${picture.id} created"
+                    redirect(controller: 'inventoryItemDetailed', action: 'show', id: inventoryItem.id)
+                }
+                else {
+                    render(view: 'create', model: [ picture: picture ])
+                }
+            }
+            else {
+                render(view: 'create', model: [ picture: picture ])
+            }
+        }
+        else {
+            flash.message = "Picture not created, inventory item not found."
+            redirect(action: list)
+        }
+    }
+
+    def view = {
+        def picture = Picture.get(params.id)
+        def size = params.size ? Integer.parseInt(params.size) : Image.Small
+        def image = picture ? Image.findByPictureAndSize(picture, size) : null
+        if (picture && image) {
+            response.setContentType(image.contentType)
+            response.setContentLength(image.data.size())
+            response.setHeader('filename', image.filename())
+            OutputStream out = response.outputStream
+            out.write(image.data)
+            out.close()
+        }
+        else {
+            response.sendError(404)
+        }
+    }
+
+}
Index: /trunk/grails-app/domain/Image.groovy
===================================================================
--- /trunk/grails-app/domain/Image.groovy	(revision 182)
+++ /trunk/grails-app/domain/Image.groovy	(revision 182)
@@ -0,0 +1,52 @@
+class Image implements Comparable {
+
+    Picture picture
+    Integer size
+    byte[] data
+    String contentType
+    Integer width
+    Integer height
+    Date dateCreated = new Date()
+    Date lastUpdated = new Date()
+
+    static belongsTo = [ Picture ]
+
+    static mapping = {
+        picture index: 'images_index', unique: true
+        size index: 'images_index', unique: true
+        data type: 'binary'
+    }
+
+    static constraints = {
+        data(maxSize: MAX_SIZE)
+    }
+
+    static final Integer MAX_SIZE = 10 * 1024 * 1024
+
+    static final Integer Original = 1
+    static final Integer Large = 2
+    static final Integer Medium = 3
+    static final Integer Small = 4
+
+    static final Integer[] Widths =  [ 0, 0, 500, 250, 100 ]
+    static final Integer[] Heights = [ 0, 0, 500, 250, 100 ]
+
+    static final String[] Names = [ '', 'Original', 'Large', 'Medium', 'Small' ]
+
+    int compareTo(obj) {
+        size.compareTo(obj.size)
+    }
+
+    String filename() {
+        Image.filename(picture.id, size)
+    }
+
+    static String filename(long id, int size) {
+        if (size == Original) {
+            return "${id}-${Names[size]}.jpg"
+        }
+        else {
+            return "${id}-${Names[size]}.png"
+        }
+    }
+}
Index: /trunk/grails-app/domain/InventoryItem.groovy
===================================================================
--- /trunk/grails-app/domain/InventoryItem.groovy	(revision 181)
+++ /trunk/grails-app/domain/InventoryItem.groovy	(revision 182)
@@ -5,4 +5,5 @@
     InventoryLocation inventoryLocation
     Period averageDeliveryPeriod
+    Picture picture
     String name
     String description = ""
@@ -17,4 +18,8 @@
     boolean enableReorder = true
 
+    static mapping = {
+        picture cascade: 'all-delete-orphan', lazy: true, inverse: true
+    }
+
     static hasMany = [alternateItems: InventoryItem,
                                     spareFor: Asset,
@@ -26,4 +31,5 @@
 
     static constraints = {
+        picture(nullable:true)
         name(unique:true, blank:false, maxSize:50)
         description()
@@ -45,3 +51,2 @@
     String toString() {"${this.name}"}
 }
-        
Index: /trunk/grails-app/domain/Picture.groovy
===================================================================
--- /trunk/grails-app/domain/Picture.groovy	(revision 182)
+++ /trunk/grails-app/domain/Picture.groovy	(revision 182)
@@ -0,0 +1,32 @@
+class Picture {
+
+    InventoryItem inventoryItem
+    SortedSet images
+    String file
+    Integer operation
+    String contentType
+    Integer width
+    Integer height
+    Date dateCreated = new Date()
+    Date lastUpdated = new Date()
+
+    static belongsTo = [ InventoryItem ]
+    static hasMany = [ images : Image ]
+
+    static transients = [ 'file', 'operation']
+
+    static constraints = {
+    }
+
+    static mapping = {
+        images cascade: 'all-delete-orphan', inverse: true
+    }
+
+    static final Integer NoOp = 0
+    static final Integer RotateClockWise90 = 1
+    static final Integer RotateAntiClockWise90 = 2
+    static final Integer Rotate180 = 3
+    static final Integer Flip = 4
+    static final Integer Flop = 5
+
+}
Index: /trunk/grails-app/services/CreateDataService.groovy
===================================================================
--- /trunk/grails-app/services/CreateDataService.groovy	(revision 181)
+++ /trunk/grails-app/services/CreateDataService.groovy	(revision 182)
@@ -9,5 +9,5 @@
     boolean transactional = false
 
-    def authenticateService
+    def personService
     def taskService
 
@@ -151,5 +151,5 @@
         //Person
         def passClearText = "pass"
-        def passwordEncoded = authenticateService.encodePassword(passClearText)
+        def passwordEncoded = personService.encodePassword(passClearText)
         def personInstance
 
@@ -168,5 +168,5 @@
         //Person
         def passClearText = "pass"
-        def passwordEncoded = authenticateService.encodePassword(passClearText)
+        def passwordEncoded = personService.encodePassword(passClearText)
         def personInstance
 
@@ -200,5 +200,5 @@
         //Person
         def passClearText = "pass"
-        def passwordEncoded = authenticateService.encodePassword(passClearText)
+        def passwordEncoded = personService.encodePassword(passClearText)
         def personInstance
 
Index: /trunk/grails-app/services/PersonService.groovy
===================================================================
--- /trunk/grails-app/services/PersonService.groovy	(revision 182)
+++ /trunk/grails-app/services/PersonService.groovy	(revision 182)
@@ -0,0 +1,26 @@
+/* Provides a service class with some methods that integrate the Person domain class and Acegi security.
+ *
+ */
+class PersonService {
+
+    boolean transactional = false
+
+    def authenticateService
+
+    // Get current user as a Person and in a safe way to avoid a null userDomain during bootstrap.
+    def currentUser() {
+        if(authenticateService.userDomain()) {
+            return Person.get(authenticateService.userDomain().id)
+        }
+        else {
+            //println "Warning: userDomain not active yet, attempting to return Person #1"
+            return Person.get(1)
+        }
+    }
+
+    // Convenience wrapper.
+    def encodePassword(passClearText) {
+        authenticateService.encodePassword(passClearText)
+    }
+
+}
Index: /trunk/grails-app/services/TaskService.groovy
===================================================================
--- /trunk/grails-app/services/TaskService.groovy	(revision 181)
+++ /trunk/grails-app/services/TaskService.groovy	(revision 182)
@@ -4,5 +4,5 @@
 
     def dateUtilService
-    def authenticateService
+    def personService
 
     def create(params) {
@@ -14,10 +14,6 @@
             result.taskInstance = taskInstance
 
-            // Get person in a safe way to avoid a null userDomain during bootstrap.
-            def userDomain = authenticateService.userDomain()
-            def person = userDomain ? Person.get(userDomain.id) : Person.get(1)
-
             if(taskInstance.save()) {
-                def taskModification = new TaskModification(person:person,
+                def taskModification = new TaskModification(person: personService.currentUser(),
                                                         taskModificationType: TaskModificationType.get(1),
                                                         task: taskInstance)
@@ -64,5 +60,5 @@
 
                 if(result.taskInstance.save()) {
-                    def taskModification = new TaskModification(person:Person.get(authenticateService.userDomain().id),
+                    def taskModification = new TaskModification(person:personService.currentUser(),
                                                             taskModificationType: TaskModificationType.get(3),
                                                             task: result.taskInstance)
@@ -107,5 +103,5 @@
 
                 if(result.taskInstance.save()) {
-                    def taskModification = new TaskModification(person:Person.get(authenticateService.userDomain().id),
+                    def taskModification = new TaskModification(person:personService.currentUser(),
                                                             taskModificationType: TaskModificationType.get(4),
                                                             task: result.taskInstance)
@@ -150,5 +146,5 @@
 
                 if(result.taskInstance.save()) {
-                    def taskModification = new TaskModification(person:Person.get(authenticateService.userDomain().id),
+                    def taskModification = new TaskModification(person:personService.currentUser(),
                                                             taskModificationType: TaskModificationType.get(5),
                                                             task: result.taskInstance)
@@ -193,5 +189,5 @@
 
                 if(result.taskInstance.save()) {
-                    def taskModification = new TaskModification(person:Person.get(authenticateService.userDomain().id),
+                    def taskModification = new TaskModification(person:personService.currentUser(),
                                                             taskModificationType: TaskModificationType.get(6),
                                                             task: result.taskInstance)
@@ -236,5 +232,5 @@
 
                 if(result.taskInstance.save()) {
-                    def taskModification = new TaskModification(person:Person.get(authenticateService.userDomain().id),
+                    def taskModification = new TaskModification(person:personService.currentUser(),
                                                             taskModificationType: TaskModificationType.get(7),
                                                             task: result.taskInstance)
@@ -279,5 +275,5 @@
 
                 if(result.taskInstance.save()) {
-                    def taskModification = new TaskModification(person:Person.get(authenticateService.userDomain().id),
+                    def taskModification = new TaskModification(person:personService.currentUser(),
                                                             taskModificationType: TaskModificationType.get(8),
                                                             task: result.taskInstance)
@@ -322,5 +318,5 @@
 
                 if(result.taskInstance.save()) {
-                    def taskModification = new TaskModification(person:Person.get(authenticateService.userDomain().id),
+                    def taskModification = new TaskModification(person:personService.currentUser(),
                                                             taskModificationType: TaskModificationType.get(9),
                                                             task: result.taskInstance)
Index: /trunk/grails-app/taglib/WebAlbumTagLib.groovy
===================================================================
--- /trunk/grails-app/taglib/WebAlbumTagLib.groovy	(revision 182)
+++ /trunk/grails-app/taglib/WebAlbumTagLib.groovy	(revision 182)
@@ -0,0 +1,96 @@
+class WebAlbumTagLib {
+
+    static namespace = "wa"
+
+    def ifDevEnv = { attrs, body ->
+        def map = grailsApplication.metadata
+        String env = map[grailsApplication.ENVIRONMENT]
+        if (env == grailsApplication.ENV_DEVELOPMENT) {
+            out << body()
+        }
+    }
+
+    def pictureAnchor = { attrs, body ->
+        def picture = attrs.remove('picture')
+        def size = attrs.remove('size')
+        out << "<a href=\"${createPictureLink(picture.id, size).encodeAsHTML()}\""
+        attrs.each { key, value ->
+            out << " $key=\"$value\""
+        }
+        out << '>'
+        out << body()
+        out << '</a>'
+    }
+
+    def pictureImg = { attrs ->
+        def picture = attrs.remove('picture')
+        def size = attrs.remove('size')
+        out << createPictureImage(picture, size, attrs)
+    }
+
+    def pictureLightboxAnchor = { attrs ->
+        def picture = attrs.remove('picture')
+        def size = attrs.remove('size')
+        def lightboxSize = attrs.remove('lightboxSize')
+        //def caption = picture.caption
+//         if (!caption) {
+            def caption = 'Show original'
+//         }
+        caption = caption.encodeAsHTML()
+        StringBuilder sb = new StringBuilder(40)
+        sb.append("<a href=\"${createPictureLink(picture.id, Image.Original).encodeAsHTML()}\"")
+        attrs.each { key, value ->
+            sb.append(" $key=\"$value\"")
+        }
+        sb.append('>')
+        sb.append(caption.replaceAll(/'/, '&#39;'))
+        sb.append('</a>')
+        def link = sb.toString().encodeAsHTML()
+        out << "<a href=\"${createPictureLink(picture.id, lightboxSize).encodeAsHTML()}\" rel=\"lightbox[list]\" title=\"${link}\">"
+        out << createPictureImage(picture, size, null)
+        out << '</a>'
+    }
+
+    private def createPictureImage(picture, size, attrs) {
+        Integer width = 0
+        Integer height = 0
+        switch (size) {
+            case Image.Original:
+                width = picture.width
+                height = picture.height
+                break
+            default:
+                width = Image.Widths[size]
+                height = Image.Widths[size]
+                break
+        }
+//         def caption = picture.caption
+//         if (!caption) {
+            def caption = 'Show original'
+//         }
+        def alt = attrs?.remove('alt')
+        if (!alt) {
+            alt = caption
+        }
+        alt = alt.encodeAsHTML()
+        def title = attrs?.remove('title')
+        if (!title) {
+            title = caption
+        }
+        title = title.encodeAsHTML()
+        StringBuilder sb = new StringBuilder(40)
+        sb.append("<img src=\"${createPictureLink(picture.id, size).encodeAsHTML()}\" alt=\"$alt\" title=\"$title\" width=\"$width\" height=\"$height\"")
+        if (attrs) {
+            attrs.each { key, value ->
+                sb.append(" $key=\"$value\"")
+            }
+        }
+        sb.append(' />')
+        sb.toString()
+    }
+
+    private def createPictureLink(id, size) {
+        def params = [ size: size, filename: Image.filename(id, size) ]
+        createLink(url: [ controller: 'pictureDetailed', action: 'view', id: id, params: params ])
+    }
+}
Index: /trunk/grails-app/utils/Imaging.groovy
===================================================================
--- /trunk/grails-app/utils/Imaging.groovy	(revision 182)
+++ /trunk/grails-app/utils/Imaging.groovy	(revision 182)
@@ -0,0 +1,149 @@
+import java.awt.AlphaComposite
+import java.awt.Color
+import java.awt.Graphics2D
+import java.awt.geom.AffineTransform
+import java.awt.geom.Rectangle2D
+import java.awt.image.AffineTransformOp
+import java.awt.image.BufferedImage
+import java.io.ByteArrayInputStream
+import java.io.ByteArrayOutputStream
+import javax.imageio.ImageIO
+
+class Imaging {
+
+    static def createAll(InventoryItem inventoryItem, Picture picture, InputStream stream) {
+        BufferedImage original = ImageIO.read(stream)
+        picture.contentType = 'image/jpeg'
+        picture.width = original.width
+        picture.height = original.height
+        def images = [ 
+            new Image(inventoryItem: inventoryItem, picture: picture, size: Image.Original),
+            new Image(inventoryItem: inventoryItem, picture: picture, size: Image.Large),
+            new Image(inventoryItem: inventoryItem, picture: picture, size: Image.Medium),
+            new Image(inventoryItem: inventoryItem, picture: picture, size: Image.Small)
+            ]
+        ByteArrayOutputStream output = new ByteArrayOutputStream(1024 * 1024)
+        updateImages(images, original, output)
+        images
+    }
+    
+    static def updateAll(Picture picture) {
+        def operation = picture.operation
+        if (operation == Picture.NoOp) {
+            return null
+        }
+        def images = picture.images.toArray() // Image.findAllByPicture(picture, [ sort: 'size', order: 'asc' ])
+        BufferedImage original = ImageIO.read(new ByteArrayInputStream(images[0].data))
+        AffineTransform transform = new AffineTransform();
+        def op = null
+        boolean paint = false
+        Integer width = original.width
+        Integer height = original.height
+        switch (operation) {
+            case Picture.RotateClockWise90:
+                transform.rotate(Math.PI / 2.0)
+                transform.translate(0, -height)
+                op = new AffineTransformOp(transform, AffineTransformOp.TYPE_BILINEAR);
+                width = original.height
+                height = original.width
+                paint = true
+                break
+            case Picture.RotateAntiClockWise90:
+                transform.rotate(-Math.PI / 2.0)
+                transform.translate(-width, 0)
+                op = new AffineTransformOp(transform, AffineTransformOp.TYPE_BILINEAR);
+                width = original.height
+                height = original.width
+                paint = true
+                break
+            case Picture.Rotate180:
+                transform.rotate(Math.PI)
+                transform.translate(-width, -height)
+                op = new AffineTransformOp(transform, AffineTransformOp.TYPE_BILINEAR);
+                break
+            case Picture.Flip: // vertical
+                transform.scale(-1.0d, 1.0d)
+                transform.translate(-width, 0)
+                op = new AffineTransformOp(transform, AffineTransformOp.TYPE_NEAREST_NEIGHBOR)
+                break
+            case Picture.Flop: // horizontal
+                transform.scale(1.0d, -1.0d)
+                transform.translate(0, -height)
+                op = new AffineTransformOp(transform, AffineTransformOp.TYPE_NEAREST_NEIGHBOR)
+                break
+            default:
+                return images
+                break
+        }
+        BufferedImage modified = op.filter(original, null);
+        if (paint) {
+            BufferedImage changed = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB)
+            Graphics2D graphics = changed.createGraphics()
+            graphics.drawImage(modified, 0, 0, width, height, null)
+            graphics.dispose()
+            modified = changed
+            picture.width = width
+            picture.height = height
+        }
+        ByteArrayOutputStream output = new ByteArrayOutputStream(1024 * 1024)
+        updateImages(images, modified, output)
+        images
+    }
+
+    private static def updateImages(images, original, stream) {
+        updateImage(images[0], original, 'jpeg', stream)
+        def large = resizeImage(original, dimensions(Image.Large), false)
+        updateImage(images[1], large, 'png', stream)
+        updateImage(images[2], resizeImage(large, dimensions(Image.Medium), true), 'png', stream)
+        updateImage(images[3], resizeImage(large, dimensions(Image.Small), true), 'png', stream)
+    }
+
+    private static def updateImage(image, data, format, stream) {
+        image.contentType = "image/${format}"
+        image.width = data.width
+        image.height = data.height
+        stream.reset()
+        if (!ImageIO.write(data, format, stream)) {
+            throw new IOException("Can't write the image in the given format '${format}'")
+        }
+        image.data = stream.toByteArray()
+    }
+
+    private static def dimensions(size) {
+        [ Image.Widths[size], Image.Heights[size] ]
+    }    
+
+    private static def resizeImage(imageBuffer, dims, fit) {
+        Integer width = dims[0]
+        Integer height = dims[1]
+        Integer imageWidth = imageBuffer.width
+        Integer imageHeight = imageBuffer.height
+      
+        Double widthScale = (double)width / (double)imageWidth
+        Double heightScale = (double)height / (double)imageHeight
+        BufferedImage resizedImage = imageBuffer
+        if (widthScale < 1.0d || heightScale < 1.0d) {
+            Double scale = Math.min(widthScale, heightScale)
+            def transform = new AffineTransform()
+            transform.scale(scale, scale)
+            def op = new AffineTransformOp(transform, AffineTransformOp.TYPE_BILINEAR)
+            resizedImage = op.filter(imageBuffer, null)
+            imageWidth = resizedImage.width
+            imageHeight = resizedImage.height
+        }
+
+        if (fit && (imageWidth < width || imageHeight < height)) {
+            BufferedImage fittedImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB)
+            Integer left = (width - imageWidth) / 2
+            Integer top = (height - imageHeight) / 2
+            Graphics2D graphics = fittedImage.createGraphics()
+            graphics.setComposite(AlphaComposite.getInstance(AlphaComposite.CLEAR, 0.0f))
+            graphics.fill(new Rectangle2D.Double(0, 0, width, height))
+            graphics.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 1.0f))
+            graphics.drawImage(resizedImage, left, top, imageWidth, imageHeight, null)
+            graphics.dispose()
+            return fittedImage
+        }
+        resizedImage
+    }
+}
Index: /trunk/grails-app/views/assetDetailed/list.gsp
===================================================================
--- /trunk/grails-app/views/assetDetailed/list.gsp	(revision 181)
+++ /trunk/grails-app/views/assetDetailed/list.gsp	(revision 182)
@@ -51,5 +51,5 @@
                             <td>
                                 <g:link action="show" id="${assetInstance.id}">
-                                    <img  src="${resource(dir:'images/skin',file:'database_table.png')}" alt="Show" />
+                                    <img  src="${resource(dir:'images/skin',file:'database_go.png')}" alt="Show" />
                                 </g:link>
                             </td>
Index: /trunk/grails-app/views/assetDetailed/search.gsp
===================================================================
--- /trunk/grails-app/views/assetDetailed/search.gsp	(revision 181)
+++ /trunk/grails-app/views/assetDetailed/search.gsp	(revision 182)
@@ -64,5 +64,5 @@
                             <td>
                                 <g:link action="show" id="${assetInstance.id}">
-                                    <img  src="${resource(dir:'images/skin',file:'database_table.png')}" alt="Show" />
+                                    <img  src="${resource(dir:'images/skin',file:'database_go.png')}" alt="Show" />
                                 </g:link>
                             </td>
Index: /trunk/grails-app/views/inventoryItemDetailed/create.gsp
===================================================================
--- /trunk/grails-app/views/inventoryItemDetailed/create.gsp	(revision 181)
+++ /trunk/grails-app/views/inventoryItemDetailed/create.gsp	(revision 182)
@@ -43,12 +43,11 @@
                                 </td>
                             </tr>
-
+                        
                             <tr class="prop">
                                 <td valign="top" class="name">
-                                    <label for="unitsInStock">In Stock:</label>
+                                    <label for="inventoryLocation">Inventory Location:</label>
                                 </td>
-                                <td valign="top" class="value ${hasErrors(bean:inventoryItemInstance,field:'unitsInStock','errors')}">
-                                    <input type="text" id="unitsInStock" name="unitsInStock" value="${fieldValue(bean:inventoryItemInstance,field:'unitsInStock')}" />
-                                    <g:select optionKey="id" from="${UnitOfMeasure.list()}" name="unitOfMeasure.id" value="${inventoryItemInstance?.unitOfMeasure?.id}" ></g:select>
+                                <td valign="top" class="value ${hasErrors(bean:inventoryItemInstance,field:'inventoryLocation','errors')}">
+                                    <g:select optionKey="id" from="${InventoryLocation.list()}" name="inventoryLocation.id" value="${inventoryItemInstance?.inventoryLocation?.id}" ></g:select>
                                 </td>
                             </tr>
@@ -60,4 +59,5 @@
                                 <td valign="top" class="value ${hasErrors(bean:inventoryItemInstance,field:'reorderPoint','errors')}">
                                     <input type="text" id="reorderPoint" name="reorderPoint" value="${fieldValue(bean:inventoryItemInstance,field:'reorderPoint')}" />
+                                    <g:select optionKey="id" from="${UnitOfMeasure.list()}" name="unitOfMeasure.id" value="${inventoryItemInstance?.unitOfMeasure?.id}" ></g:select>
                                 </td>
                             </tr> 
@@ -71,31 +71,4 @@
                                 </td>
                             </tr>
-                        
-                            <tr class="prop">
-                                <td valign="top" class="name">
-                                    <label for="recommendedReorderPoint">Recommended Reorder Point:</label>
-                                </td>
-                                <td valign="top" class="value ${hasErrors(bean:inventoryItemInstance,field:'recommendedReorderPoint','errors')}">
-                                    <input type="text" id="recommendedReorderPoint" name="recommendedReorderPoint" value="${fieldValue(bean:inventoryItemInstance,field:'recommendedReorderPoint')}" />
-                                </td>
-                            </tr>
-                        
-                            <tr class="prop">
-                                <td valign="top" class="name">
-                                    <label for="isActive">Active:</label>
-                                </td>
-                                <td valign="top" class="value ${hasErrors(bean:inventoryItemInstance,field:'isActive','errors')}">
-                                    <g:checkBox name="isActive" value="${inventoryItemInstance?.isActive}" ></g:checkBox>
-                                </td>
-                            </tr> 
-                        
-                            <tr class="prop">
-                                <td valign="top" class="name">
-                                    <label for="isObsolete">Obsolete:</label>
-                                </td>
-                                <td valign="top" class="value ${hasErrors(bean:inventoryItemInstance,field:'isObsolete','errors')}">
-                                    <g:checkBox name="isObsolete" value="${inventoryItemInstance?.isObsolete}" ></g:checkBox>
-                                </td>
-                            </tr> 
                         
                             <tr class="prop">
@@ -171,13 +144,4 @@
                             </tr>
                         
-                            <tr class="prop">
-                                <td valign="top" class="name">
-                                    <label for="inventoryLocation">Inventory Location:</label>
-                                </td>
-                                <td valign="top" class="value ${hasErrors(bean:inventoryItemInstance,field:'inventoryLocation','errors')}">
-                                    <g:select optionKey="id" from="${InventoryLocation.list()}" name="inventoryLocation.id" value="${inventoryItemInstance?.inventoryLocation?.id}" ></g:select>
-                                </td>
-                            </tr>
-                        
                         </tbody>
                     </table>
Index: /trunk/grails-app/views/inventoryItemDetailed/edit.gsp
===================================================================
--- /trunk/grails-app/views/inventoryItemDetailed/edit.gsp	(revision 181)
+++ /trunk/grails-app/views/inventoryItemDetailed/edit.gsp	(revision 182)
@@ -7,4 +7,5 @@
         <title>Edit InventoryItem</title>
         <nav:resources override="true"/>
+        <g:render template="/shared/pictureHead" />
     </head>
     <body>
@@ -27,4 +28,19 @@
                     <table>
                         <tbody>
+                    
+                            <tr class="prop">
+                                <td valign="top" class="name">Picture:</td>
+                                <td valign="top" class="value">
+                                    <g:if test="${inventoryItemInstance.picture}" >
+                                        <span class='gallery'>
+                                            <wa:pictureLightboxAnchor picture="${inventoryItemInstance.picture}" size="${Image.Medium}" lightboxSize="${Image.Large}" target="_blank" title="Show Original" />
+                                        </span>
+                                        <br />
+                                        <g:link controller="pictureDetailed" action="edit" id="${inventoryItemInstance.picture.id}" >
+                                            Edit Picture
+                                        </g:link>
+                                    </g:if>
+                                </td>
+                            </tr>
                         
                             <tr class="prop">
@@ -50,7 +66,6 @@
                                     <label for="unitsInStock">Units In Stock:</label>
                                 </td>
-                                <td valign="top" class="value ${hasErrors(bean:inventoryItemInstance,field:'unitsInStock','errors')}">
-                                    <input type="text" id="unitsInStock" name="unitsInStock" value="${fieldValue(bean:inventoryItemInstance,field:'unitsInStock')}" />
-                                    <g:select optionKey="id" from="${UnitOfMeasure.list()}" name="unitOfMeasure.id" value="${inventoryItemInstance?.unitOfMeasure?.id}" ></g:select>
+                                <td valign="top" class="value">
+                                    ${inventoryItemInstance.unitsInStock} ${inventoryItemInstance.unitOfMeasure}
                                 </td>
                             </tr>
Index: /trunk/grails-app/views/inventoryItemDetailed/list.gsp
===================================================================
--- /trunk/grails-app/views/inventoryItemDetailed/list.gsp	(revision 181)
+++ /trunk/grails-app/views/inventoryItemDetailed/list.gsp	(revision 182)
@@ -52,5 +52,5 @@
                             <td>
                                 <g:link action="show" id="${inventoryItemInstance.id}">
-                                    <img  src="${resource(dir:'images/skin',file:'database_table.png')}" alt="Show" />
+                                    <img  src="${resource(dir:'images/skin',file:'database_go.png')}" alt="Show" />
                                 </g:link>
                             </td>
Index: /trunk/grails-app/views/inventoryItemDetailed/search.gsp
===================================================================
--- /trunk/grails-app/views/inventoryItemDetailed/search.gsp	(revision 181)
+++ /trunk/grails-app/views/inventoryItemDetailed/search.gsp	(revision 182)
@@ -7,4 +7,5 @@
         <title>InventoryItem Search</title>
         <nav:resources override="true"/>
+        <g:render template="/shared/pictureHead" />
         <filterpane:includes />
     </head>
@@ -33,6 +34,6 @@
                     <thead>
                         <tr>
-                        
-                   	        <g:sortableColumn property="id" title="Id" params="${filterParams}" />
+
+                            <th>Picture</th>
                         
                    	        <g:sortableColumn property="name" title="Name" params="${filterParams}" />
@@ -50,19 +51,35 @@
                     <tbody>
                     <g:each in="${inventoryItemInstanceList}" status="i" var="inventoryItemInstance">
-                        <tr class="${(i % 2) == 0 ? 'clickableOdd' : 'clickableEven'}" onclick='window.location = "${request.getContextPath()}/inventoryItemDetailed/show/${inventoryItemInstance.id}"'/>
+                        <tr class="${(i % 2) == 0 ? 'clickableOdd' : 'clickableEven'}" />
+
+                            <td class='gallery'>
+                                <g:if test="${inventoryItemInstance.picture}" >
+                                    <wa:pictureLightboxAnchor picture="${inventoryItemInstance.picture}"
+                                                                                        size="${Image.Small}"
+                                                                                        lightboxSize="${Image.Large}"
+                                                                                        target="_blank"
+                                                                                        title="Show Original" />
+                                </g:if>
+                            </td>
                         
-                            <td>${fieldValue(bean:inventoryItemInstance, field:'id')}</td>
+                            <td onclick='window.location = "${request.getContextPath()}/inventoryItemDetailed/show/${inventoryItemInstance.id}"' >
+                                ${fieldValue(bean:inventoryItemInstance, field:'name')}
+                            </td>
                         
-                            <td>${fieldValue(bean:inventoryItemInstance, field:'name')}</td>
+                            <td onclick='window.location = "${request.getContextPath()}/inventoryItemDetailed/show/${inventoryItemInstance.id}"' >
+                                ${fieldValue(bean:inventoryItemInstance, field:'description')}
+                            </td>
                         
-                            <td>${fieldValue(bean:inventoryItemInstance, field:'description')}</td>
+                            <td onclick='window.location = "${request.getContextPath()}/inventoryItemDetailed/show/${inventoryItemInstance.id}"' >
+                                ${fieldValue(bean:inventoryItemInstance, field:'unitsInStock')}
+                            </td>
                         
-                            <td>${fieldValue(bean:inventoryItemInstance, field:'unitsInStock')}</td>
-                        
-                            <td>${fieldValue(bean:inventoryItemInstance, field:'unitOfMeasure')}</td>
+                            <td onclick='window.location = "${request.getContextPath()}/inventoryItemDetailed/show/${inventoryItemInstance.id}"' >
+                                ${fieldValue(bean:inventoryItemInstance, field:'unitOfMeasure')}
+                            </td>
 
                             <td>
                                 <g:link action="show" id="${inventoryItemInstance.id}">
-                                    <img  src="${resource(dir:'images/skin',file:'database_table.png')}" alt="Show" />
+                                    <img  src="${resource(dir:'images/skin',file:'database_go.png')}" alt="Show" />
                                 </g:link>
                             </td>
Index: /trunk/grails-app/views/inventoryItemDetailed/show.gsp
===================================================================
--- /trunk/grails-app/views/inventoryItemDetailed/show.gsp	(revision 181)
+++ /trunk/grails-app/views/inventoryItemDetailed/show.gsp	(revision 182)
@@ -7,4 +7,5 @@
         <title>Show InventoryItem</title>
         <nav:resources override="true"/>
+        <g:render template="/shared/pictureHead" />
     </head>
     <body>
@@ -19,5 +20,20 @@
                 <table>
                     <tbody>
-
+                    
+                        <tr class="prop">
+                            <td valign="top" class="name">Picture:</td>
+                            <td valign="top" class="value">
+                                <g:if test="${inventoryItemInstance.picture}" >
+                                    <span class='gallery'><wa:pictureLightboxAnchor picture="${inventoryItemInstance.picture}" size="${Image.Medium}" lightboxSize="${Image.Large}" target="_blank" title="Show Original" /></span>
+                                </g:if>
+                                <g:else>
+                                    <g:link controller="pictureDetailed"
+                                                    params="['inventoryItem.id':inventoryItemInstance?.id]"
+                                                    action="create">
+                                        Add Picture
+                                    </g:link>
+                                </g:else>
+                            </td>
+                        </tr>
                     
                         <tr class="prop">
@@ -43,17 +59,23 @@
                         
                         <tr class="prop">
-                            <td valign="top" class="name">Units In Stock:</td>
-                            
-                            <td valign="top" class="value">${fieldValue(bean:inventoryItemInstance, field:'unitsInStock')}</td>
-                            
-                        </tr>
-                    
-                        <tr class="prop">
-                            <td valign="top" class="name">Unit Of Measure:</td>
-                            
-                            <td valign="top" class="value">${inventoryItemInstance?.unitOfMeasure?.encodeAsHTML()}</td>
-                            
-                        </tr>
-
+                            <td valign="top" class="name">Location:</td>
+                            
+                            <td valign="top" class="value">
+                                <g:link controller="inventoryLocationDetailed" action="show" id="${inventoryItemInstance?.inventoryLocation?.id}">
+                                    ${inventoryItemInstance?.inventoryLocation?.encodeAsHTML()}
+                                </g:link>
+                                    in ${inventoryItemInstance?.inventoryLocation?.inventoryStore.encodeAsHTML()}
+                            </td>
+                            
+                        </tr>
+                        
+                        <tr class="prop">
+                            <td valign="top" class="name">In Stock:</td>
+                            
+                            <td valign="top" class="value">
+                                ${fieldValue(bean:inventoryItemInstance, field:'unitsInStock')} ${inventoryItemInstance?.unitOfMeasure?.encodeAsHTML()}
+                            </td>
+                            
+                        </tr>
                     
                         <tr class="prop">
@@ -184,11 +206,4 @@
                                 </ul>
                             </td>
-                            
-                        </tr>
-                        
-                        <tr class="prop">
-                            <td valign="top" class="name">Inventory Location:</td>
-                            
-                            <td valign="top" class="value"><g:link controller="inventoryLocationDetailed" action="show" id="${inventoryItemInstance?.inventoryLocation?.id}">${inventoryItemInstance?.inventoryLocation?.encodeAsHTML()}</g:link></td>
                             
                         </tr>
Index: /trunk/grails-app/views/layouts/main.gsp
===================================================================
--- /trunk/grails-app/views/layouts/main.gsp	(revision 181)
+++ /trunk/grails-app/views/layouts/main.gsp	(revision 182)
@@ -9,4 +9,5 @@
         <nav:resources override="true"/>
         <g:javascript library="application" />
+        <g:javascript library="prototype/effects" />
     </head>
 
Index: /trunk/grails-app/views/pictureDetailed/create.gsp
===================================================================
--- /trunk/grails-app/views/pictureDetailed/create.gsp	(revision 182)
+++ /trunk/grails-app/views/pictureDetailed/create.gsp	(revision 182)
@@ -0,0 +1,49 @@
+<html>
+    <head>
+        <meta name="layout" content="main" />
+        <title>Create Picture</title>
+        <nav:resources override="true"/>
+        <g:render template="/shared/pictureHead" />
+    </head>
+    <body>
+        <div class="nav">
+            <h1>Create Picture</h1>
+        </div>
+        <div class="body">
+            <g:render template="/shared/messages" />
+            <g:hasErrors bean="${picture}">
+                <div class="errors">
+                    <g:renderErrors bean="${picture}" as="list" />
+                </div>
+            </g:hasErrors>
+            <g:uploadForm action="save" onsubmit="return Lightbox.loading();">
+                <g:hiddenField name="inventoryItem.id" value="${picture.inventoryItem.id}" />
+                <div class="dialog">
+                    <table>
+                        <tbody>
+                            <tr class="prop">
+                                <td valign="top" class="name">
+                                    <label for="inventoryItem.id">Inventory Item:</label>
+                                </td>
+                                <td valign="top" class="value">
+                                    ${picture.inventoryItem}
+                                </td>
+                            </tr> 
+                            <tr class="prop">
+                                <td valign="top" class="name">
+                                    <label for="file">File:</label>
+                                </td>
+                                <td valign="top" class="value ${hasErrors(bean: picture, field: 'file', 'errors')}">
+                                    <input type="file" id="file" name="file" size="40"/>
+                                </td>
+                            </tr>
+                        </tbody>
+                    </table>
+                </div>
+                <div class="buttons">
+                    <span class="button"><input class="save" type="submit" value="Create" /></span>
+                </div>
+            </g:uploadForm>
+        </div>
+    </body>
+</html>
Index: /trunk/grails-app/views/pictureDetailed/edit.gsp
===================================================================
--- /trunk/grails-app/views/pictureDetailed/edit.gsp	(revision 182)
+++ /trunk/grails-app/views/pictureDetailed/edit.gsp	(revision 182)
@@ -0,0 +1,66 @@
+<html>
+    <head>
+        <meta name="layout" content="main" />
+        <title>Edit Picture</title>
+        <nav:resources override="true"/>
+        <g:render template="/shared/pictureHead" />
+    </head>
+    <body>
+        <div class="nav">
+            <h1>Edit Picture</h1>
+        </div>
+        <div class="body">
+            <g:render template="/shared/messages" />
+            <g:hasErrors bean="${picture}">
+                <div class="errors">
+                    <g:renderErrors bean="${picture}" as="list" />
+                </div>
+            </g:hasErrors>
+            <g:form method="post" onsubmit="return Lightbox.loading();">
+                <input type="hidden" name="id" value="${picture?.id}" />
+                <div class="dialog">
+                    <table>
+                        <tbody>
+                            <tr class="prop">
+                                <td valign="top" class="name">Picture:</td>
+                                <td valign="top" class="value">
+                                    <span class='gallery'><wa:pictureLightboxAnchor picture="${picture}" size="${Image.Medium}" lightboxSize="${Image.Large}" title="Slide Show" /></span>
+                                </td>
+                            </tr>
+                            <tr class="prop">
+                                <td valign="top" class="name">
+                                    <label for="operation">Operation:</label>
+                                </td>
+                                <td valign="top" class="value ${hasErrors(bean: picture, field: 'operation', 'errors')}">
+                                    <select id="operation" name="operation">
+                                        <option value="${Picture.NoOp}" selected="selected">None</option>
+                                        <option value="${Picture.RotateClockWise90}">Rotate clockwise 90 degrees</option>
+                                        <option value="${Picture.RotateAntiClockWise90}">Rotate anti-clockwise 90 degrees</option>
+                                        <option value="${Picture.Rotate180}">Rotate 180 degrees</option>
+                                        <option value="${Picture.Flip}">Vertical mirror image</option>
+                                        <option value="${Picture.Flop}">Horizontal mirror image</option>
+                                    </select>
+                                </td>
+                            </tr>
+                            <tr class="prop">
+                                <td valign="top" class="name">
+                                    <label for="inventoryItem.id">Inventory Item:</label>
+                                </td>
+                                <td valign="top" class="value">
+                                    <g:link controller="inventoryItemDetailed" action="show" id="${picture.inventoryItem?.id}" title="Show Inventory Item">
+                                        ${picture.inventoryItem?.toString()?.encodeAsHTML()}
+                                    </g:link>
+                                </td>
+                            </tr>
+                            </tr>
+                        </tbody>
+                    </table>
+                </div>
+                <div class="buttons">
+                    <span class="button"><g:actionSubmit class="save" value="Update" /></span>
+                    <span class="button"><g:actionSubmit class="delete" onclick="return confirm('Are you sure?');" value="Delete" /></span>
+                </div>
+            </g:form>
+        </div>
+    </body>
+</html>
Index: /trunk/grails-app/views/pictureDetailed/list.gsp
===================================================================
--- /trunk/grails-app/views/pictureDetailed/list.gsp	(revision 182)
+++ /trunk/grails-app/views/pictureDetailed/list.gsp	(revision 182)
@@ -0,0 +1,64 @@
+<html>
+    <head>
+        <meta name="layout" content="main" />
+        <title>Picture List</title>
+        <nav:resources override="true"/>
+        <g:render template="/shared/pictureHead" />
+    </head>
+    <body>
+        <div class="nav">
+            <h1>Picture List</h1>
+        </div>
+        <div class="body">
+            <g:render template="/shared/messages" />
+            <div class="list">
+                <table>
+                    <thead>
+                        <tr>
+                            <th>Picture</th>
+                            <g:sortableColumn property="inventoryItem" title="Inventory Item" />
+                            <g:sortableColumn property="dateCreated" title="Date Created" />
+                            <g:sortableColumn property="lastUpdated" title="Last Updated" />
+                            <th></th>
+                        </tr>
+                    </thead>
+                    <tbody>
+                        <g:each in="${list}" status="i" var="picture">
+                            <tr class="${(i % 2) == 0 ? 'clickableOdd' : 'clickableEven'}">
+                                <td class='gallery'>
+                                    <wa:pictureLightboxAnchor picture="${picture}"
+                                                                                        size="${Image.Small}"
+                                                                                        lightboxSize="${Image.Large}"
+                                                                                        target="_blank" title="Show Original" />
+                                </td>
+
+                                <td onclick='window.location = "${request.getContextPath()}/pictureDetailed/show/${picture.id}"' >
+                                    <g:link controller="inventoryItemDetailed" action="show" id="${picture.inventoryItem?.id}" title="Show Inventory Item">
+                                        ${picture.inventoryItem?.toString()?.encodeAsHTML()}
+                                    </g:link>
+                                </td>
+
+                                <td onclick='window.location = "${request.getContextPath()}/pictureDetailed/show/${picture.id}"' >
+                                    <g:formatDate format="EEE, dd-MMM-yyyy" date="${picture.dateCreated}"/>
+                                </td>
+
+                                <td onclick='window.location = "${request.getContextPath()}/pictureDetailed/show/${picture.id}"' >
+                                    <g:formatDate format="EEE, dd-MMM-yyyy" date="${picture.lastUpdated}"/>
+                                </td>
+
+                                <td>
+                                    <g:link action="show" id="${picture.id}">
+                                        <img  src="${resource(dir:'images/skin',file:'database_go.png')}" alt="Show" />
+                                    </g:link>
+                                </td>
+                            </tr>
+                        </g:each>
+                    </tbody>
+                </table>
+            </div>
+            <div class="paginateButtons">
+                <g:paginate total="${paginateCount}" />
+            </div>
+        </div>
+    </body>
+</html>
Index: /trunk/grails-app/views/pictureDetailed/show.gsp
===================================================================
--- /trunk/grails-app/views/pictureDetailed/show.gsp	(revision 182)
+++ /trunk/grails-app/views/pictureDetailed/show.gsp	(revision 182)
@@ -0,0 +1,54 @@
+<html>
+    <head>
+        <meta name="layout" content="main" />
+        <title>Show Picture</title>
+        <g:render template="/shared/pictureHead" />
+    </head>
+    <body>
+        <div class="nav">
+            <h1>Show Picture</h1>
+        </div>
+        <div class="body">
+            <g:render template="/shared/messages" />
+            <div class="dialog">
+                <table>
+                    <tbody>
+                        <tr class="prop">
+                            <td valign="top" class="name">Picture:</td>
+                            <td valign="top" class="value">
+                                <span class='gallery'><wa:pictureLightboxAnchor picture="${picture}" size="${Image.Medium}" lightboxSize="${Image.Large}" target="_blank" title="Show Original" /></span>
+                            </td>
+                        </tr>
+                        <tr class="prop">
+                            <td valign="top" class="name">Inventory Item:</td>
+                            <td valign="top" class="value"><g:link controller="inventoryItemDetailed" action="show" id="${picture.inventoryItem?.id}" title="Show Inventory Item">${picture.inventoryItem?.toString()?.encodeAsHTML()}</g:link></td>
+                        </tr>
+                        <tr class="prop">
+                            <td valign="top" class="name">Height:</td>
+                            <td valign="top" class="value">${picture.height}</td>
+                        </tr>
+                        <tr class="prop">
+                            <td valign="top" class="name">Width:</td>
+                            <td valign="top" class="value">${picture.width}</td>
+                        </tr>
+                        <tr class="prop">
+                            <td valign="top" class="name">Date Created:</td>
+                            <td valign="top" class="value"><g:formatDate format="EEE, dd-MMM-yyyy" date="${picture.dateCreated}"/></td>
+                        </tr>
+                        <tr class="prop">
+                            <td valign="top" class="name">Last Updated:</td>
+                            <td valign="top" class="value"><g:formatDate format="EEE, dd-MMM-yyyy" date="${picture.lastUpdated}"/></td>
+                        </tr>
+                    </tbody>
+                </table>
+            </div>
+            <div class="buttons">
+                <g:form>
+                    <input type="hidden" name="id" value="${picture?.id}" />
+                    <span class="button"><g:actionSubmit class="edit" value="Edit" /></span>
+                    <span class="button"><g:actionSubmit class="delete" onclick="return confirm('Are you sure?');" value="Delete" /></span>
+                </g:form>
+            </div>
+        </div>
+    </body>
+</html>
Index: /trunk/grails-app/views/shared/_messages.gsp
===================================================================
--- /trunk/grails-app/views/shared/_messages.gsp	(revision 182)
+++ /trunk/grails-app/views/shared/_messages.gsp	(revision 182)
@@ -0,0 +1,6 @@
+<g:if test="${flash.message}">
+    <div class="message">${flash.message}</div>
+</g:if>
+<g:if test="${flash.warning}">
+    <div class="warning">${flash.warning}</div>
+</g:if>
Index: /trunk/grails-app/views/shared/_pictureHead.gsp
===================================================================
--- /trunk/grails-app/views/shared/_pictureHead.gsp	(revision 182)
+++ /trunk/grails-app/views/shared/_pictureHead.gsp	(revision 182)
@@ -0,0 +1,6 @@
+
+<link rel="stylesheet" type="text/css" href="${resource(dir: 'css', file: 'webalbum.css')}" />
+<link rel="stylesheet" type="text/css" href="${resource(dir: 'css', file: 'lightbox.css')}" />
+<g:javascript library="prototype/prototype" />
+<g:javascript library="lightbox" />
+<script type="text/javascript"> document.observe("dom:loaded", Lightbox.onload); </script>
Index: /trunk/web-app/css/lightbox.css
===================================================================
--- /trunk/web-app/css/lightbox.css	(revision 182)
+++ /trunk/web-app/css/lightbox.css	(revision 182)
@@ -0,0 +1,88 @@
+#lightbox {
+  position: absolute;
+  left: 0;
+  width: 100%;
+  text-align: center;
+  line-height: 0;
+  overflow: auto;
+  z-index: 100;
+}
+
+#lightbox a img { border: none; }
+
+#outerImageContainer {
+  position: relative;
+  background-color: #fff;
+  width: 250px;
+  height: 250px;
+  margin: 0 auto;
+}
+
+#imageContainer {
+  padding: 10px;
+}
+
+#loading {
+  position: absolute;
+  top: 40%;
+  left: 0%;
+  height: 25%;
+  width: 100%;
+  text-align: center;
+  line-height: 0;
+  background: transparent url("../images/loading.gif") center center no-repeat;
+}
+
+#hoverNav {
+    position: absolute;
+    top: 0;
+    left: 0;
+    height: 100%;
+    width: 100%;
+    z-index: 10;
+}
+
+#imageContainer > #hoverNav { left: 0; }
+#hoverNav a { outline: none; }
+
+#prevLink, #nextLink {
+  width: 49%;
+  height: 100%;
+  background: transparent url("../images/blank.gif") no-repeat; /* Trick IE into showing hover */
+  display: block;
+}
+#prevLink { left: 0; float: left; }
+#nextLink { right: 0; float: right; }
+#prevLink:hover, #prevLink:visited:hover { background: url("../images/prevlabel.gif") left 15% no-repeat; }
+#nextLink:hover, #nextLink:visited:hover { background: url("../images/nextlabel.gif") right 15% no-repeat; }
+
+#imageDataContainer {
+  font: 10px Verdana, Helvetica, sans-serif;
+  background-color: #fff;
+  margin: 0 auto;
+  line-height: 1.4em;
+  width: 100%
+}
+
+#imageData { padding: 0 10px; color: #666; text-align: center; }
+#imageData #imageDetails { float: left; text-align: left; }	
+#imageData #caption { font-weight: bold; }
+#imageData #numberDisplay { display: block; clear: left; padding-bottom: 1.0em;	}			
+#imageData #bottomNav { float: right; text-align: right; } 
+
+#imageData #bottomNavClose { cursor: pointer; }
+
+#imageData #bottomNavNext, #imageData #bottomNavPrev { 
+  cursor: pointer;
+  padding: 5px 2px;
+}
+
+#overlay {
+  position: absolute;
+  top: 0;
+  left: 0;
+  z-index: 90;
+  width: 100%;
+  height: 500px;
+  background-color: #000;
+}
Index: /trunk/web-app/js/lightbox.js
===================================================================
--- /trunk/web-app/js/lightbox.js	(revision 182)
+++ /trunk/web-app/js/lightbox.js	(revision 182)
@@ -0,0 +1,726 @@
+//
+//	Lightbox version 3.0.0
+//	by Lokesh Dhakar - http://www.huddletogether.com
+//	04/03/2008
+//
+//	For more information on this script, visit:
+//	http://huddletogether.com/projects/lightbox2/
+//
+//	Licensed under the Creative Commons Attribution 2.5 License - http://creativecommons.org/licenses/by/2.5/
+//
+//	Credit also due to those who have helped, inspired, and made their code available to the public.
+//	Including: Scott Upton(uptonic.com), Peter-Paul Koch(quirksmode.org), Thomas Fuchs(mir.aculo.us), and others.
+//
+
+/*
+
+  Table of Contents
+  -----------------
+
+  Extending Built-in Objects
+  - Object.extend(Element)
+  - Array.prototype.removeDuplicates()
+
+  Lightbox Class Declaration
+  - block()
+  - onload()
+  - updateImageList()
+  
+  Miscellaneous Functions
+  - getPageScroll()
+  - getPageSize()
+  - showSelectBoxes()
+  - hideSelectBoxes()
+  - showObjects()
+  - hideObjects()
+  - pause()
+
+*/
+
+//	Additional methods for Element added by SU, Couloir
+//	- further additions by Lokesh Dhakar (huddletogether.com)
+Object.extend(Element, {
+  setWidth: function (element, width) {
+    $(element).style.width = width + "px";
+  },
+  setHeight: function (element, height) {
+    $(element).style.height = height + "px";
+  },
+  setTop: function (element, top) {
+    $(element).style.top = top + "px";
+  },
+  setLeft: function (element, left) {
+    $(element).style.left = left + "px";
+  },
+  setSrc: function (element, src) {
+    $(element).src = src;
+  },
+  setInnerHTML: function (element, content) {
+    $(element).innerHTML = content;
+  }
+});
+
+//	Extending built-in Array object
+//	- array.removeDuplicates()
+Array.prototype.removeDuplicates = function () {
+  for (i = 1; i < this.length; i++) {
+    if (this[i][0] == this[i - 1][0]) { this.splice(i, 1); }
+  }
+}
+
+//	Lightbox Class Declaration
+//	- block()
+//  - onload()
+//  - updateImageList()
+
+//	Structuring of code inspired by Scott Upton (http://www.uptonic.com/)
+
+var Lightbox = {
+
+  onload: function (event) {
+    var options = { 'minWidth' : 300};
+    return Lightbox._create(options);
+  },
+
+  // Fades to overlay and runs an animated loading gif (e.g after form submit)
+  loading: function(options) {
+    if (!document.getElementsByTagName) { return true; }
+
+     //Lightbox._options(options);
+
+     var buttons = document.getElementsByClassName("buttons");
+     buttons[0].style.visibility = "hidden";
+
+    var parent = document.getElementsByTagName("body").item(0);
+    var overlay = Lightbox._createElement("div", "overlay", "none", null, null, function (event) {} );
+    parent.appendChild(overlay);
+
+    var pageSize = getPageSize();
+    Element.setWidth('overlay', pageSize[0]);
+    Element.setHeight('overlay', pageSize[1]);
+
+    if (Lightbox._animate) {
+      new Effect.Appear('overlay', { duration: Lightbox._overlayBlockDuration, from: 0.0, to: Lightbox._blockOpacity });
+    }
+    else {
+      Element.setOpacity('overlay', Lightbox._opacity);
+    }
+
+    if (Lightbox._animate) {
+        var loading = Lightbox._createElement("div", 'loading');
+        parent.appendChild(loading);
+
+        // document.all should be detected in IE and Opera, workaround for IE gif freezing.
+        if(document.all) {
+            new Effect.Pulsate(loading);
+        }
+        else {
+            Element.show('loading');
+        }
+    }
+
+  },
+
+
+  // Blocks the page (after form submit)
+  block: function(message, options) {
+    if (!document.getElementsByTagName) { return true; }
+
+    Lightbox._options(options);
+    
+    var parent = document.getElementsByTagName("body").item(0);
+    var overlay = Lightbox._createElement("div", "overlay", "none", null, null, function (event) {} );
+    parent.appendChild(overlay);
+        
+    // The rest of this code inserts html at the bottom of the page that looks similar to this:
+    //
+    // <div id="lightbox">
+    //   <div id="outerImageContainer">
+    //     <div id="imageContainer">
+    //       <div id="loading"></div>
+    //       <div id="imageDataContainer"> message </div>
+    //     </div>
+    //   </div>
+    // </div>
+
+    var lightbox = Lightbox._createElement("div", "lightbox", "none");
+    parent.appendChild(lightbox);
+
+    var outerImageContainer = Lightbox._createElement("div", "outerImageContainer");
+    lightbox.appendChild(outerImageContainer);
+
+    var imageContainer = Lightbox._createElement("div", 'imageContainer');
+    outerImageContainer.appendChild(imageContainer);
+
+	imageContainer.appendChild(Lightbox._createElement("div", 'loading'));
+    imageDataContainer = Lightbox._createElement("div", 'imageDataContainer');
+    imageContainer.appendChild(imageDataContainer);
+    imageDataContainer.appendChild(document.createTextNode(message));
+
+    hideSelectBoxes();
+    hideObjects();
+    var pageSize = getPageSize();
+    Element.setWidth('overlay', pageSize[0]);
+    Element.setHeight('overlay', pageSize[1]);
+    Element.setOpacity('overlay', Lightbox._blockOpacity);
+    Element.show('overlay');
+    Element.setTop('lightbox', Number(getPageScroll()[1] + (getPageSize()[3] / 15)).toFixed());
+    Element.show('lightbox');
+    Element.show('loading');
+    return true;
+  },
+  
+  // Loops through anchor tags looking for 'lightbox' references and applies onclick
+  // events to appropriate links. You can rerun after dynamically adding images w/ajax.
+  updateImageList: function () {
+    if (!document.getElementsByTagName) { return; }
+
+    // loop through all anchor tags
+    var anchors = document.getElementsByTagName('a');
+    for (var i = 0, l = anchors.length; i < l; i++) {
+      var anchor = anchors[i];
+      // use the string.match() method to catch 'lightbox' references in the rel attribute
+      var relAttribute = String(anchor.getAttribute('rel'));
+      if (anchor.getAttribute('href') && (relAttribute.toLowerCase().match('lightbox'))) {
+        anchor.onclick = function () { return Lightbox._start(this); };
+      }
+    }
+
+    // loop through all area tags
+    // todo: combine anchor & area tag loops
+    var areas = document.getElementsByTagName('area');
+    for (var i = 0, l = areas.length; i < l; i++) {
+      var area = areas[i];
+      // use the string.match() method to catch 'lightbox' references in the rel attribute
+      var relAttribute = String(area.getAttribute('rel'));
+      if (area.getAttribute('href') && (relAttribute.toLowerCase().match('lightbox'))) {
+        Area.onclick = function () { return Lightbox.start(this); }
+      }
+    }
+  },
+
+  // Loops through anchor tags looking for 'lightbox' references and applies onclick events 
+  // to appropriate links. The 2nd section of the function inserts html at the bottom of the 
+  // page which is used to display the shadow overlay and the image container.
+  _create: function(options) {
+    if (!document.getElementsByTagName) return;
+
+    Lightbox._options(options);
+
+    var parent = document.getElementsByTagName("body").item(0);
+    var overlay = Lightbox._createElement("div", "overlay", "none", null, null, Lightbox._overlayEnd);
+    parent.appendChild(overlay);
+
+    Lightbox.updateImageList();
+        
+    // The rest of this code inserts html at the bottom of the page that looks similar to this:
+    //
+    // <div id="lightbox">
+    //   <div id="outerImageContainer">
+    //     <div id="imageContainer">
+    //       <img id="lightboxImage" />
+    //       <div id="hoverNav">
+    //         <a href="#" id="prevLink"></a>
+    //         <a href="#" id="nextLink"></a>
+    //       </div>
+    //       <div id="loading"></div>
+    //     </div>
+    //   </div>
+    //   <div id="imageDataContainer">
+    //     <div id="imageData">
+    //       <div id="imageDetails">
+    //         <span id="caption"></span>
+    //         <span id="numberDisplay"></span>
+    //       </div>
+    //       <div id="bottomNav">
+    //         <img id="bottomNavPrev" />
+    //         <img id="bottomNavNext" />
+    //         <img id="bottomNavClose" />
+    //       </div>
+    //     </div>
+    //   </div>
+    // </div>
+
+    var lightbox = Lightbox._createElement("div", "lightbox", "none", null, null, Lightbox._overlayEnd);
+    parent.appendChild(lightbox);
+
+    var outerImageContainer = Lightbox._createElement("div", "outerImageContainer");
+    lightbox.appendChild(outerImageContainer);
+
+    // When Lightbox starts it will resize itself from 250 by 250 to the current image dimension.
+    // If animations are turned off, it will be hidden as to prevent a flicker of a
+    // white 250 by 250 box.
+    if (Lightbox._animate) {
+      Element.setWidth('outerImageContainer', 250);
+      Element.setHeight('outerImageContainer', 250);
+    }
+    else {
+      Element.setWidth('outerImageContainer', 1);
+      Element.setHeight('outerImageContainer', 1);
+    }
+
+    var imageContainer = Lightbox._createElement("div", 'imageContainer');
+    outerImageContainer.appendChild(imageContainer);
+    imageContainer.appendChild(Lightbox._createElement("img", 'lightboxImage'));
+
+    var hoverNav = Lightbox._createElement("div", 'hoverNav');
+    imageContainer.appendChild(hoverNav);
+
+    hoverNav.appendChild(Lightbox._createElement("a", 'prevLink', null, null, '#'));
+    hoverNav.appendChild(Lightbox._createElement("a", 'nextLink', null, null, '#'));
+
+    imageContainer.appendChild(Lightbox._createElement("div", 'loading', null, null, null, Lightbox._end));
+
+    imageDataContainer = Lightbox._createElement("div", 'imageDataContainer');
+    lightbox.appendChild(imageDataContainer);
+
+    imageData = Lightbox._createElement("div", 'imageData');
+    imageDataContainer.appendChild(imageData);
+
+    var imageDetails = Lightbox._createElement("div", 'imageDetails');
+    imageData.appendChild(imageDetails);
+
+    imageDetails.appendChild(Lightbox._createElement("span", 'caption'));
+    imageDetails.appendChild(Lightbox._createElement("span", 'numberDisplay'));
+
+    bottomNav = Lightbox._createElement("div", 'bottomNav');
+    imageData.appendChild(bottomNav);
+
+    bottomNav.appendChild(Lightbox._createElement("img", 'bottomNavPrev',  null, Lightbox._imagePath + "miniprev.jpg",   null, Lightbox._prevImage));
+    bottomNav.appendChild(Lightbox._createElement("img", 'bottomNavNext',  null, Lightbox._imagePath + "mininext.jpg",   null, Lightbox._nextImage));
+    bottomNav.appendChild(Lightbox._createElement("img", 'bottomNavClose', null, Lightbox._imagePath + "closelabel.gif", null, Lightbox._end));
+  },
+
+  _createElement: function (type, id, display, src, href, onclick) {
+    elem = document.createElement(type);
+    if (id)      { elem.setAttribute('id', id); }
+    if (display) { elem.style.display = display; }
+    if (src)     { elem.setAttribute('src', src); }
+    if (href)    { elem.setAttribute('href', href); }
+	if (onclick) { elem.onclick = onclick; }
+    return elem;
+  },
+
+  _options: function(options) {
+    if (options) {
+      var option = options['borderSize'];
+      if (option) { Lightbox._borderSize = option; }
+      option = options['overlayDuration'];
+      if (option) { Lightbox._overlayDuration = option; }
+      option = options['resizeDuration'];
+      if (option) { Lightbox._resizeDuration = option; }
+      option = options['minWidth'];
+      if (option) { Lightbox._minWidth = option; }
+      option = options['imagePath'];
+      if (option) { Lightbox._imagePath = option; }
+      option = options['opacity'];
+      if (option) { Lightbox._opacity = option; }
+      option = options['blockOpacity'];
+      if (option) { Lightbox._blockOpacity = option; }
+      option = options['animate'];
+      if (option) { Lightbox._animate = option; }
+      option = options['text'];
+      if (option) { Lightbox._text = option; }
+    }    
+  },
+
+  // Display overlay and lightbox. If image is part of a set, add siblings to imageArray.
+  _start: function (imageLink) {
+    hideSelectBoxes();
+    hideObjects();
+
+    // stretch overlay to fill page and fade in
+    var pageSize = getPageSize();
+    Element.setWidth('overlay', pageSize[0]);
+    Element.setHeight('overlay', pageSize[1]);
+
+    if (Lightbox._animate) {
+      new Effect.Appear('overlay', { duration: Lightbox._overlayDuration, from: 0.0, to: Lightbox._opacity });
+    }
+    else {
+      Element.setOpacity('overlay', Lightbox._opacity);
+    }
+
+    Lightbox._activeImage = 0;
+    Lightbox._imageArray = [];
+    var imageNum = 0;
+    if ((imageLink.getAttribute('rel') == 'lightbox')) { // if image is NOT part of a set..
+	  Lightbox._imageArray.push(new Array(imageLink.getAttribute('href'), imageLink.getAttribute('title')));
+    }
+    else { // if image is part of a set..
+      // loop through anchors, find other images in set, and add them to imageArray
+      var rel = imageLink.getAttribute('rel');
+      var anchors = document.getElementsByTagName(imageLink.tagName);
+      for (var i = 0, l = anchors.length; i < l; i++) {
+        var anchor = anchors[i];
+        if (anchor.getAttribute('href') && (anchor.getAttribute('rel') == rel)) {
+          Lightbox._imageArray.push(new Array(anchor.getAttribute('href'), anchor.getAttribute('title')));
+        }
+      }
+      Lightbox._imageArray.removeDuplicates();
+      var href = imageLink.getAttribute('href');
+      while (Lightbox._imageArray[imageNum][0] != href) { imageNum++; }
+    }
+
+    // calculate top offset for the lightbox and display
+    var pageScroll = getPageScroll();
+    Element.setTop('lightbox', Number(pageScroll[1] + (pageSize[3] / 10)).toFixed());
+    Element.setLeft('lightbox', Number(pageScroll[0]).toFixed());
+    Element.show('lightbox');
+    Lightbox._changeImage(imageNum);
+    return false;
+  },
+
+  _overlayEnd: function (event) {
+    if (!event) { event = window.event; }
+    var id = Event.element(event).id;
+    if (id == 'overlay' || id == 'lightbox') { return Lightbox._end(); }
+    return true;
+  },
+
+  _end: function (event) {
+    Lightbox._disableKeyboardNav();
+    Element.hide('lightbox');
+    if (Lightbox._animate) {
+      new Effect.Fade('overlay', { duration: Lightbox._overlayDuration });
+    } 
+    else {
+      Element.hide('overlay');
+    }
+    showSelectBoxes();
+    showObjects();
+    return false;
+  },
+
+  _hasNext: function () {
+    return Lightbox._activeImage < (Lightbox._imageArray.length - 1);
+  },
+
+  _nextImage: function () {
+    Lightbox._changeImage(Lightbox._activeImage + 1);
+    return false;
+  },
+
+  _hasPrev: function () {
+    return Lightbox._activeImage > 0;
+  },
+
+  _prevImage: function () {
+    Lightbox._changeImage(Lightbox._activeImage - 1);
+    return false;
+  },
+
+  // Hide most elements and preload image in preparation for resizing image container.
+  _changeImage: function (imageNum) {
+    Lightbox._activeImage = imageNum;
+    
+    // hide elements during transition
+    if (Lightbox._animate) { Element.show('loading'); }
+    Element.hide('lightboxImage');
+    Element.hide('hoverNav');
+    Element.hide('prevLink');
+    Element.hide('nextLink');
+    Element.hide('bottomNavPrev');
+    Element.hide('bottomNavNext');
+    Element.hide('imageDataContainer');
+    Element.hide('caption');
+    Element.hide('numberDisplay');
+
+    // once image is preloaded, resize image container
+    Lightbox._preloader = new Image();
+    Lightbox._preloader.onload = function () {
+      Element.setSrc('lightboxImage', Lightbox._imageArray[imageNum][0]);
+      Lightbox._preloader.onload = function () { }; // clear onLoad, IE behaves irratically with animated gifs otherwise 
+      Lightbox._resizeImageContainer(Lightbox._preloader.width, Lightbox._preloader.height);
+    };
+    Lightbox._preloader.src = Lightbox._imageArray[imageNum][0];
+  },
+
+  _resizeImageContainer: function (imgWidth, imgHeight) {
+    var borders = Lightbox._borderSize * 2;
+    
+    // keep to a minimum width, if specified
+    if (Lightbox._minWidth > 0 && (imgWidth + borders) < Lightbox._minWidth) {
+      imgWidth = Lightbox._minWidth - borders;
+    }
+
+    // get current height and width
+    var widthCurrent = Element.getWidth('outerImageContainer');
+    var heightCurrent = Element.getHeight('outerImageContainer');
+
+    // get new width and height
+    var widthNew = imgWidth + borders;
+    var heightNew = imgHeight + borders;
+
+    // scalars based on change from old to new
+    var xScale = (widthNew / widthCurrent) * 100;
+    var yScale = (heightNew / heightCurrent) * 100;
+    
+    // calculate size difference between new and old image, and resize if necessary
+    var widthDiff = widthCurrent - widthNew;
+	var heightDiff = heightCurrent - heightNew;
+    if (heightDiff != 0) {
+      new Effect.Scale('outerImageContainer', yScale, { scaleX: false, duration: Lightbox._resizeDuration, queue: 'front' });
+    }
+	if (widthDiff != 0) {
+	  new Effect.Scale('outerImageContainer', xScale, { scaleY: false, duration: Lightbox._resizeDuration, delay: Lightbox._resizeDuration });
+	}
+
+    // if new and old image are same size and no scaling transition is necessary,
+    // do a quick pause to prevent image flicker.
+    if ((heightDiff == 0) && (widthDiff == 0)) {
+      if (navigator.appVersion.indexOf("MSIE") != -1) { pause(250); } else { pause(100); }
+    }
+
+    Element.setHeight('prevLink', imgHeight);
+    Element.setHeight('nextLink', imgHeight);
+    Element.setWidth('imageDataContainer', widthNew);
+    Lightbox._showImage();
+  },
+
+  // Display image.
+  _showImage: function () {
+    Element.hide('loading');
+    new Effect.Appear('lightboxImage', { duration: Lightbox._resizeDuration, queue: 'end', afterFinish: Lightbox._updateDetails });
+	Lightbox._preloadNeighborImages();
+  },
+
+  // Display caption, image number, and bottom nav.
+  _updateDetails: function () {
+    // if caption is not null
+	var caption = Lightbox._imageArray[Lightbox._activeImage][1];
+    if (caption) {
+      Element.show('caption');
+      Element.setInnerHTML('caption', caption);
+    }
+		
+    // if image is part of set display 'Image x of x'
+    if (Lightbox._imageArray.length > 1) {
+      Element.show('numberDisplay');
+      var text = Lightbox._text.replace("*", (Lightbox._activeImage + 1));
+      text = text.replace("*", Lightbox._imageArray.length);
+      Element.setInnerHTML('numberDisplay', text);
+    } 
+
+    if (Lightbox._hasPrev()) { Element.show('bottomNavPrev'); }	
+    if (Lightbox._hasNext()) { Element.show('bottomNavNext'); }
+
+	new Effect.Parallel(
+	  [ new Effect.SlideDown('imageDataContainer', { sync: true, duration: Lightbox._resizeDuration }),
+        new Effect.Appear('imageDataContainer', { sync: true, duration: Lightbox._resizeDuration }) ],
+      { duration: Lightbox._resizeDuration, afterFinish: Lightbox._updateNav });
+  },
+
+  // Display appropriate previous and next hover navigation.
+  _updateNav: function () {
+    $('imageDataContainer').style.overflow = 'auto'; // css float fix
+
+    Element.setHeight('overlay', getPageSize()[1]);
+
+    Element.show('hoverNav');
+
+    // if not first image in set, display prev image button
+    if (Lightbox._hasPrev()) {
+      document.getElementById('prevLink').onclick = Lightbox._prevImage;
+      Element.show('prevLink');
+      }
+
+    // if not last image in set, display next image button
+    if (Lightbox._hasNext()) {
+      document.getElementById('nextLink').onclick = Lightbox._nextImage;
+      Element.show('nextLink');
+    }
+   Lightbox._enableKeyboardNav();
+  },
+
+  _enableKeyboardNav: function () {
+    document.onkeydown = Lightbox._keyboardAction;
+  },
+
+  _disableKeyboardNav: function () {
+    document.onkeydown = '';
+  },
+
+  _keyboardAction: function (evnt) {
+    var keycode = 0, escapeKey = 0, key = 0;
+    if (evnt == null) { // ie
+      keycode = event.keyCode;
+      escapeKey = 27;
+    }
+    else { // mozilla
+      keycode = evnt.keyCode;
+      escapeKey = evnt.DOM_VK_ESCAPE;
+    }
+
+    key = String.fromCharCode(keycode).toLowerCase();
+    if ((key == 'x') || (key == 'o') || (key == 'c') || (keycode == escapeKey)) { // close lightbox
+      Lightbox._end();
+      return true;
+    } 
+    else if((key == 'p') || (keycode == 37)) { // display previous image
+      if (Lightbox._hasPrev()) {
+        Lightbox._disableKeyboardNav();
+        Lightbox._prevImage();
+        return true;
+      }
+    } 
+    else if((key == 'n') || (keycode == 39)) { // display next image
+      if (Lightbox._hasNext()) {
+        Lightbox._disableKeyboardNav();
+        Lightbox._nextImage();
+        return true;
+      }
+    }
+  return false;
+  },
+
+  _preloadNeighborImages: function () {
+    if (Lightbox._hasNext()) {
+      Lightbox._preloadNextImage = new Image();
+      Lightbox._preloadNextImage.src = Lightbox._imageArray[Lightbox._activeImage + 1][0];
+    }
+    if (Lightbox._hasPrev()) {
+      Lightbox._preloadPrevImage = new Image();
+      Lightbox._preloadPrevImage.src = Lightbox._imageArray[Lightbox._activeImage - 1][0];
+    }
+  },
+
+  _borderSize: 10,
+  _overlayDuration: 0.2,
+  _overlayBlockDuration: 0.6,
+  _resizeDuration: 0.4,
+  _minWidth: 0,
+  _imagePath: "/gnuMims/images/",
+  _opacity: 0.6,
+  _blockOpacity: 0.1,
+  _animate: true,
+  _text: "Image * of *",
+  _activeImage: 0,
+  _imageArray: []
+}
+
+// Returns array with x, y page scroll values.
+// Core code from - quirksmode.org
+function getPageScroll(){
+  var xScroll = 0, yScroll = 0;
+  if (self.pageYOffset) {
+    xScroll = self.pageXOffset;
+    yScroll = self.pageYOffset;
+  }
+  else if (document.documentElement && document.documentElement.scrollTop) {	// Explorer 6 Strict
+    xScroll = document.documentElement.scrollLeft;
+    yScroll = document.documentElement.scrollTop;
+  } 
+  else if (document.body) { // all other Explorers
+    xScroll = document.body.scrollLeft;	
+    yScroll = document.body.scrollTop;
+  }
+  return new Array(xScroll, yScroll)
+}
+
+// Returns array with page width, height and window width, height
+// Core code from - quirksmode.org
+// Edit for Firefox by pHaez
+function getPageSize() {
+  var xScroll = 0, yScroll = 0;
+  var docBody = document.body;
+  var docElem = document.documentElement;
+  if (window.innerHeight && window.scrollMaxY) {
+    xScroll = window.innerWidth + window.scrollMaxX;
+    yScroll = window.innerHeight + window.scrollMaxY;
+  }
+  else if (docBody.scrollHeight > docBody.offsetHeight) { // all but Explorer Mac
+    xScroll = docBody.scrollWidth;
+    yScroll = docBody.scrollHeight;
+  } 
+  else { // Explorer Mac...would also work in Explorer 6 Strict, Mozilla and Safari
+    xScroll = docBody.offsetWidth;
+    yScroll = docBody.offsetHeight;
+  }
+
+  var windowWidth = 0, windowHeight = 0, pageHeight = 0, pageWidth = 0;
+  if (self.innerHeight) {	// all except Explorer
+    if(docElem.clientWidth) {
+      windowWidth = docElem.clientWidth; 
+    }
+    else {
+      windowWidth = self.innerWidth;
+    }
+    windowHeight = self.innerHeight;
+  }
+  else if (docElem && docElem.clientHeight) { // Explorer 6 Strict Mode
+    windowWidth = docElem.clientWidth;
+    windowHeight = docElem.clientHeight;
+  }
+  else { // other Explorers
+    windowWidth = docBody.clientWidth;
+    windowHeight = docBody.clientHeight;
+  }
+
+  // for small pages with total height less then height of the viewport
+  if (yScroll < windowHeight) {
+    pageHeight = windowHeight;
+  }	
+  else {
+    pageHeight = yScroll;
+  }
+
+  // for small pages with total width less then width of the viewport
+  if (xScroll < windowWidth) {
+    pageWidth = xScroll;
+  }
+  else {
+    pageWidth = windowWidth;
+  }
+  return new Array(pageWidth, pageHeight, windowWidth, windowHeight)
+}
+
+function showSelectBoxes() {
+  var selects = document.getElementsByTagName("select");
+  for (var i = 0, l = selects.length; i < l; i++) {
+    selects[i].style.visibility = "visible";
+  }
+}
+
+function hideSelectBoxes() {
+  var selects = document.getElementsByTagName("select");
+  for (var i = 0, l = selects.length; i < l; i++) {
+    selects[i].style.visibility = "hidden";
+  }
+}
+
+function showObjects() {
+  var objects = document.getElementsByTagName("object");
+  for (var i = 0, l = objects.length; i < l; i++) {
+    objects[i].style.visibility = "visible";
+  }
+
+  var embeds = document.getElementsByTagName("embed");
+  for (var i = 0, l = embeds.length; i < l; i++) {
+    embeds[i].style.visibility = "visible";
+  }
+}
+
+function hideObjects() {
+  var objects = document.getElementsByTagName("object");
+  for (var i = 0, l = objects.length; i < l; i++) {
+    objects[i].style.visibility = "hidden";
+  }
+
+  var embeds = document.getElementsByTagName("embed");
+  for (var i = 0, l = embeds.length; i < l; i++) {
+    embeds[i].style.visibility = "hidden";
+  }
+}
+
+// pause(numberMillis)
+// Pauses code execution for specified time. Uses busy code, not good.
+// Code from http://www.faqts.com/knowledge_base/view.phtml/aid/1602
+// Help from Ran Bar-On [ran2103@gmail.com]
+function pause(ms) {
+  var date = new Date();
+  var curDate = null;
+  do {
+    curDate = new Date();
+  } while (curDate - date < ms);
+}
