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>
