James Williams
LinkedInMastodonGithub

The Making of PleaseBrowseMe

Tags: Griffon

In the last post, I released PleaseBrowseMe, a browser for pleasedress.me made with Griffon. In this post, I'm going to explain a bit more of the technical details of creating it.

withWorker

This is one area that would have been more difficult using Java. SwingWorker, the Java concept that withWorker is based on, is designed to be executed once. That is not a restriction in Groovy and was very helpful in loading a batch of images in a EDT-safe way.

def doImageLoad = { evt = null ->   
    withWorker(start:true) {
        onInit {
            doLater {
                model.running = true
                app.appFrames[0].getGlassPane().setVisible(true)
                app.appFrames[0].getGlassPane().setBusy(true)
            }
        }

        work {
            for(int i = model.startCount; i < model.endCount; i++) {
                def url = model.searchResults[i].imageUrl
                if (model.searchResults[i].image == null || 
model.searchResults[i].image == model.loadingImage) {
                    def image = ImageIO.read(new URL(url))
                    if (image.width > 180 || image.height > 180) {
                        image = GraphicsUtilities.createThumbnailFast(image,180)
                    }
                    publish([id:i, image:image])
                } else {
                    publish([id:i])
                }       
            }
        }

        onUpdate { chunks ->
                   chunks.each { imageMap ->
                   if (imageMap.image != null)                              
                       model.searchResults[imageMap.id].put('image', imageMap.image)
                   view.shirtPanel.add(drawShirtPanel(imageMap.id))
            } 
        }

        onDone {
            model.running = false
            if (model.searchResults.size() == 0)
                view.shirtPanel.add(new JLabel("No results"))
            view.shirtPanel.revalidate() 
            app.appFrames[0].getGlassPane().setVisible(false)
        }
    }
}

search

The only thing special that's going on with search is that I used SwingX-WS calls for http requests/responses to avoid using the more verbose Apache varieties. The JXSearchField component from xswingx helps out too.

public search(text) {
    def session = new Session()
    def request = new Request(url:"http://pleasedress.me/api/1.0/shirts/search.xml?q="+text)
    def response = session.execute(request)
    def shirts = []
    if (response.getStatusCode() == StatusCode.OK) {
       def result = model.slurper.parseText(response.getBody())
       result.shirt.each {
            shirts+= [title:it.@title.toString(), url:it.@url.toString(), 
               image:model.loadingImage, imageUrl:it.@image.toString(), price:it.@price.toString()]
       }
       model.searchResults = shirts
       model.startCount = 0
       model.endCount = model.searchResults?.size() > 21 ? 21 : model.searchResults?.size()
       clearShirtPanel()
       doImageLoad()
    } else {
        JOptionPane.showMessageDialog(app.appFrames[0], "Error", "Something went wrong.", JOptionPane.ERROR_MESSAGE)
    }
}

GlassPane

To indicate when the application was retrieving information from pleasedress.me, I used a JXBusyLabel as my GlassPane. The GlassPane is an invisible layer that exists on top of every frame. To not obscure the underlying view as much, I changed the point shape to circles(Ellipse). I also tweaked the colors to match the animation more apparent. Because it is specific to J(X)Frame, when referring to the glasspane, one must call app.appFrames[number] directly. view.getGlassPane() will not work.

import java.awt.Dimension
import java.awt.geom.*
import org.jdesktop.swingx.JXBusyLabel
import org.jdesktop.swingx.painter.BusyPainter

def busyLabel = new JXBusyLabel(new Dimension(700,700))
busyLabel.setOpaque(false)
busyLabel.setBusy(true)
busyLabel.getBusyPainter().setHighlightColor(java.awt.Color.DARK_GRAY)
busyLabel.getBusyPainter().setPointShape(new Ellipse2D.Float(1,1,10,10))

app.appFrames[0].setGlassPane(busyLabel)