The Making of PleaseBrowseMe
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)