<?xml version='1.0' encoding='UTF-8'?><?xml-stylesheet href="http://www.blogger.com/styles/atom.css" type="text/css"?><feed xmlns='http://www.w3.org/2005/Atom' xmlns:openSearch='http://a9.com/-/spec/opensearchrss/1.0/' xmlns:georss='http://www.georss.org/georss' xmlns:gd='http://schemas.google.com/g/2005' xmlns:thr='http://purl.org/syndication/thread/1.0'><id>tag:blogger.com,1999:blog-5092807049363133934</id><updated>2011-12-05T21:54:18.124-08:00</updated><category term='flash'/><category term='snow leopard'/><category term='web'/><category term='dtd'/><category term='books'/><category term='bug'/><category term='web hosting'/><category term='plug-in'/><category term='soa'/><category term='on-rev'/><category term='community'/><category term='human-computer interaction'/><category term='jbi'/><category term='open source'/><category term='RIA'/><category term='message queue'/><category term='validation'/><category term='cocoa'/><category term='incident'/><category term='revmedia'/><category term='applications'/><category term='zeroconf'/><category term='ejb'/><category term='unicode'/><category term='custom control'/><category term='revselect'/><category term='runrevlive'/><category term='scripting'/><category term='xml'/><category term='business'/><category term='reports'/><category term='java'/><category term='PDF'/><category term='error handling'/><category term='webinar'/><category term='repeat loops'/><category term='procedure'/><category term='livecode'/><category term='graphics'/><category term='livecode server'/><category term='formatting'/><category term='experiment'/><category term='concurrency'/><category term='data grid'/><category term='Service-Oriented Architecture'/><category term='integration'/><category term='desktop'/><category term='multi-threading'/><category term='newsletter'/><category term='programmer syndrome'/><category term='design'/><category term='iText'/><category term='testing'/><category term='character'/><category term='release'/><category term='byte'/><category term='bugfix'/><category term='Introduction'/><category term='Object-Oriented Programming'/><category term='xcard'/><category term='process communication'/><category term='slides'/><category term='support'/><category term='javascript'/><category term='revtalk'/><category term='locale'/><category term='runrev'/><category term='domain-specific languages'/><category term='forums'/><category term='conference'/><category term='MacOS X'/><category term='eai'/><category term='apple events'/><category term='modification'/><category term='quartam pdf library'/><category term='quartam reports'/><category term='animation'/><category term='enterprise'/><category term='browser'/><category term='devoxx'/><category term='antlr'/><category term='user interface'/><category term='revweb'/><category term='code reuse'/><category term='future-proof'/><category term='update'/><category term='quartam tools'/><category term='javafx'/><category term='NSImage'/><category term='silverlight'/><category term='ajax'/><category term='sockets'/><category term='programming'/><category term='hypercard'/><category term='lexical analysis'/><category term='website'/><category term='ribbon'/><category term='libraries'/><category term='question'/><category term='revlet'/><category term='netbeans'/><category term='alive'/><category term='databases'/><category term='example code'/><category term='sql'/><category term='xml schema'/><category term='unix'/><category term='behavior'/><category term='revolution'/><category term='parser'/><category term='bonjour'/><category term='jmdns'/><title type='text'>Quartam Software - developing thoughts</title><subtitle type='html'></subtitle><link rel='http://schemas.google.com/g/2005#feed' type='application/atom+xml' href='http://quartam.blogspot.com/feeds/posts/default'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5092807049363133934/posts/default?max-results=100'/><link rel='alternate' type='text/html' href='http://quartam.blogspot.com/'/><link rel='hub' href='http://pubsubhubbub.appspot.com/'/><author><name>Quartam Software - Jan Schenkel</name><uri>http://www.blogger.com/profile/08312925714131118786</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><generator version='7.00' uri='http://www.blogger.com'>Blogger</generator><openSearch:totalResults>61</openSearch:totalResults><openSearch:startIndex>1</openSearch:startIndex><openSearch:itemsPerPage>100</openSearch:itemsPerPage><entry><id>tag:blogger.com,1999:blog-5092807049363133934.post-9207299019165150952</id><published>2011-11-06T05:02:00.001-08:00</published><updated>2011-11-06T07:56:29.246-08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='livecode'/><category scheme='http://www.blogger.com/atom/ns#' term='cocoa'/><category scheme='http://www.blogger.com/atom/ns#' term='example code'/><category scheme='http://www.blogger.com/atom/ns#' term='custom control'/><title type='text'>StyledImageView custom control in LiveCode (part 2)</title><content type='html'>In the &lt;a href="http://quartam.blogspot.com/2011/11/styledimageview-custom-control-in.html"&gt;previous post&lt;/a&gt;, we started to work on a StyledImageView custom control, inspired by chapter 10 of the book '&lt;a href="http://www.cocoabook.com/"&gt;Objective-C and Cocoa: Up and running&lt;/a&gt;' by Scott Stevenson. Just as a reminder, here's the original we're trying to mimic:&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://2.bp.blogspot.com/-F5NsXw758U8/TrGzop-Yj9I/AAAAAAAAAHI/WPyHPwAR4NM/s1600/StyledImageView_0.png"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 320px; height: 230px;" src="http://2.bp.blogspot.com/-F5NsXw758U8/TrGzop-Yj9I/AAAAAAAAAHI/WPyHPwAR4NM/s320/StyledImageView_0.png" border="0" alt=""id="BLOGGER_PHOTO_ID_5670510916879814610" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;How far did we get last time?&lt;br /&gt;&lt;br /&gt;- A background gradient behind the image&lt;br /&gt;- An image with a 4px white border&lt;br /&gt;- A drop shadow below the image&lt;br /&gt;&lt;br /&gt;And our custom control group has a behavior button script to handle resizing and toggling the display of the drop shadow. Now we come to the tricky part:&lt;br /&gt;&lt;br /&gt;- A sheen over the image&lt;br /&gt;&lt;br /&gt;Perhaps it's best to explain what a 'sheen' is: a visual effect that displays a glossy white overlay, clipped to a bezier curve, rendering the illusion of a light source in the upper-left corner of the image. Cute, but how does one implement it? Scott used a linear gradient (tilted slightly to enhance the illusion) to fill a path determined by the edges of the image and a bezier path. As LiveCode doesn't have bezier curves on offer, it was time to think a bit differently.&lt;br /&gt;&lt;br /&gt;Let's start with the gradient. Linear gradients are not too difficult to implement; but we have to give it the right angle. Trigonometry has never been my best subject, but I remembered anough to figure out how to rotate the gradient by 10 degrees. And the LiveCode documentation came with example code for converting degrees to radians.&lt;br /&gt;Here's an update for the "Create" button:&lt;br /&gt;&lt;pre language="LiveCode"&gt;&lt;br /&gt;on mouseUp&lt;br /&gt;   local tGroup&lt;br /&gt;   CreateCustomControlGroup "StyledImageView", tGroup&lt;br /&gt;   CreateBackgroundGraphic tGroup&lt;br /&gt;   CreateViewImage tGroup&lt;br /&gt;   &lt;b&gt;CreateSheen tGroup&lt;/b&gt;&lt;br /&gt;   set the behavior of tGroup to the long id of button "StyledImageView Behavior"&lt;br /&gt;   set the showShadow of tGroup to true&lt;br /&gt;end mouseUp&lt;br /&gt;&lt;br /&gt;private command CreateCustomControlGroup pGroupName, @rGroupId&lt;br /&gt;   &lt;i&gt;-- same as before, omitting for brevity&lt;/i&gt;&lt;br /&gt;end CreateCustomControlGroup&lt;br /&gt;&lt;br /&gt;private command CreateBackgroundGraphic pGroup&lt;br /&gt;   &lt;i&gt;-- same as before, omitting for brevity&lt;/i&gt;&lt;br /&gt;end CreateBackgroundGraphic&lt;br /&gt;&lt;br /&gt;private command CreateViewImage pGroup&lt;br /&gt;   &lt;i&gt;-- same as before, omitting for brevity&lt;/i&gt;&lt;br /&gt;end CreateViewImage&lt;br /&gt;&lt;br /&gt;&lt;b&gt;private command CreateSheen pGroup&lt;br /&gt;   CreateSheenGradient pGroup&lt;br /&gt;end CreateSheen&lt;br /&gt;&lt;br /&gt;private command CreateSheenGradient pGroup&lt;br /&gt;   reset the templateGraphic&lt;br /&gt;   set the style of the templateGraphic to "Rectangle"&lt;br /&gt;   set the rectangle of the templateGraphic to 30,130,370,310&lt;br /&gt;   set the lineSize of the templateGraphic to 0&lt;br /&gt;   set the opaque of the templateGraphic to true&lt;br /&gt;   set the fillGradient["type"] of the templateGraphic to "linear"&lt;br /&gt;   set the fillGradient["from"] of the templateGraphic to 30,130&lt;br /&gt;   set the fillGradient["to"] of the templateGraphic to 61,307&lt;br /&gt;   set the fillGradient["via"] of the templateGraphic to 207,99&lt;br /&gt;   set the fillGradient["ramp"] of the templateGraphic to \&lt;br /&gt;         "0.00000,255,255,255" &amp; return &amp; \&lt;br /&gt;         "0.40000,255,255,255,51" &amp; return &amp; \&lt;br /&gt;         "0.79999,255,255,255,13" &amp; return &amp; \&lt;br /&gt;         "0.80000,255,255,255,0" &amp; return &amp; \&lt;br /&gt;         "1.00000,255,255,255,0"&lt;br /&gt;   create graphic "GradientGraphic" in pGroup&lt;br /&gt;   reset the templateGraphic&lt;br /&gt;end CreateSheenGradient&lt;/b&gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Delete the existing custom control group and hit the button again. You should see the gradient graphic on top of the image control.&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://4.bp.blogspot.com/-l2C9UhjOcBs/TrasuOjvmfI/AAAAAAAAAIE/0yATMjCA6ZY/s1600/StyledImageView_5.png"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 305px; height: 320px;" src="http://4.bp.blogspot.com/-l2C9UhjOcBs/TrasuOjvmfI/AAAAAAAAAIE/0yATMjCA6ZY/s320/StyledImageView_5.png" border="0" alt=""id="BLOGGER_PHOTO_ID_5671910690901694962" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;We'll also have to update the resizing logic in our behavior script, but I'll leave that for later. Our next challenge is the bezier curve, and how we can construct a path to clip the gradient to it. And then it struck me: we can achieve the same effect (in this case) by creating an oval graphic and then clip that out of the gradient graphic.&lt;br /&gt;&lt;br /&gt;Inspiration came from an old blog post by RunRev's Mark Waddingham. The original blog is no onger on the web, but thanks to the Wayback Machine internet archive, I was able to dig up &lt;a href="http://web.archive.org/web/20070520021541/http://www.revdeveloper.com/include/blog/mark/?p=4"&gt;this snapshot&lt;/a&gt;. Here's the gist: you take two control, comlbine them into a group, and then use different blend modes for the group and graphics to achieve soft clipping.&lt;br /&gt;The only difference: Mark talks about clipping something down to a graphic shape, whereas I wanted to clip a graphic shape out of the original shape. But as it turns out to, this was as easy as picking a different blend mode for the clipping graphic. So let's use this knowledge to once more update the "Create" button:&lt;br /&gt;&lt;pre language="LiveCode"&gt;&lt;br /&gt;on mouseUp&lt;br /&gt;   local tGroup&lt;br /&gt;   CreateCustomControlGroup "StyledImageView", tGroup&lt;br /&gt;   CreateBackgroundGraphic tGroup&lt;br /&gt;   CreateViewImage tGroup&lt;br /&gt;   CreateSheen tGroup&lt;br /&gt;   set the behavior of tGroup to the long id of button "StyledImageView Behavior"&lt;br /&gt;   set the showShadow of tGroup to true&lt;br /&gt;end mouseUp&lt;br /&gt;&lt;br /&gt;private command CreateCustomControlGroup pGroupName, @rGroupId&lt;br /&gt;   &lt;i&gt;-- same as before, omitting for brevity&lt;/i&gt;&lt;br /&gt;end CreateCustomControlGroup&lt;br /&gt;&lt;br /&gt;private command CreateBackgroundGraphic pGroup&lt;br /&gt;   &lt;i&gt;-- same as before, omitting for brevity&lt;/i&gt;&lt;br /&gt;end CreateBackgroundGraphic&lt;br /&gt;&lt;br /&gt;private command CreateViewImage pGroup&lt;br /&gt;   &lt;i&gt;-- same as before, omitting for brevity&lt;/i&gt;&lt;br /&gt;end CreateViewImage&lt;br /&gt;&lt;br /&gt;private command CreateSheen pGroup&lt;br /&gt;   &lt;b&gt;local tSheenGroup&lt;br /&gt;   reset the templateGroup&lt;br /&gt;   set the rectangle of the templateGroup to 30,130,370,310&lt;br /&gt;   set the lockLocation of the templateGroup to true&lt;br /&gt;   set the margins of the templateGroup to 0&lt;br /&gt;   set the ink of the templateGroup to "blendSrcOver"&lt;br /&gt;   create group "SheenGroup" in pGroup&lt;br /&gt;   put it into tSheenGroup&lt;br /&gt;   reset the templateGroup&lt;/b&gt;&lt;br /&gt;   CreateSheenGradient &lt;b&gt;tSheenGroup&lt;/b&gt;&lt;br /&gt;   &lt;b&gt;CreateSheenClipping tSheenGroup&lt;/b&gt;&lt;br /&gt;end CreateSheen&lt;br /&gt;&lt;br /&gt;private command CreateSheenGradient pGroup&lt;br /&gt;   &lt;i&gt;-- same as before, omitting for brevity&lt;/i&gt;&lt;br /&gt;end CreateSheenGradient&lt;br /&gt;&lt;br /&gt;&lt;b&gt;private command CreateSheenClipping pGroup&lt;br /&gt;   reset the templateGraphic&lt;br /&gt;   set the style of the templateGraphic to "Oval"&lt;br /&gt;   set the rectangle of the templateGraphic to 10,10,10,10&lt;br /&gt;   set the ink of the templateGraphic to "blendDstOut"&lt;br /&gt;   set the lineSize of the templateGraphic to 0&lt;br /&gt;   set the opaque of the templateGraphic to true&lt;br /&gt;   create graphic "ClippingGraphic" in pGroup&lt;br /&gt;   set the rectangle of it to -21,154,761,586&lt;br /&gt;   reset the templateGraphic&lt;br /&gt;end CreateSheenClipping&lt;/b&gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Delete the existing custom control group and hit the button again. &lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://2.bp.blogspot.com/-xPQuyAf2AaQ/TrasuXk858I/AAAAAAAAAIQ/l4GIvzg8tDI/s1600/StyledImageView_6.png"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 304px; height: 320px;" src="http://2.bp.blogspot.com/-xPQuyAf2AaQ/TrasuXk858I/AAAAAAAAAIQ/l4GIvzg8tDI/s320/StyledImageView_6.png" border="0" alt=""id="BLOGGER_PHOTO_ID_5671910693322680258" /&gt;&lt;/a&gt;&lt;br /&gt;Getting there - now we just need to extend the behavior script with the logic to properly resize these new parts:&lt;br /&gt;&lt;pre language="LiveCode"&gt;&lt;br /&gt;## engine message handlers&lt;br /&gt;&lt;br /&gt;on mouseDoubleUp&lt;br /&gt;   &lt;i&gt;-- same as before, omitting for brevity&lt;/i&gt;&lt;br /&gt;end mouseDoubleUp&lt;br /&gt;&lt;br /&gt;on resizeControl&lt;br /&gt;   &lt;i&gt;-- same as before, omitting for brevity&lt;/i&gt;&lt;br /&gt;end resizeControl&lt;br /&gt;&lt;br /&gt;## property setters and getters&lt;br /&gt;&lt;br /&gt;setProp showShadow pShowShadowBoolean&lt;br /&gt;   &lt;i&gt;-- same as before, omitting for brevity&lt;/i&gt;&lt;br /&gt;end showShadow&lt;br /&gt;&lt;br /&gt;&lt;b&gt;setProp showSheen pShowSheenBoolean&lt;br /&gt;   set the visible of group "SheenGroup" of me to pShowSheenBoolean&lt;br /&gt;end showSheen&lt;br /&gt;&lt;br /&gt;getProp showSheen&lt;br /&gt;   return the visible of group "SheenGroup" of me&lt;br /&gt;end showSheen&lt;/b&gt;&lt;br /&gt;&lt;br /&gt;setProp imageFileName pFileName&lt;br /&gt;   &lt;i&gt;-- same as before, omitting for brevity&lt;/i&gt;&lt;br /&gt;end imageFileName&lt;br /&gt;&lt;br /&gt;getProp imageFileName&lt;br /&gt;   &lt;i&gt;-- same as before, omitting for brevity&lt;/i&gt;&lt;br /&gt;end imageFileName&lt;br /&gt;&lt;br /&gt;## private commands and functions&lt;br /&gt;&lt;br /&gt;private command SIV_ReshapeControl&lt;br /&gt;   local tImageRectangle&lt;br /&gt;   SIV_DetermineImageRectangle tImageRectangle&lt;br /&gt;   SIV_ReshapeBackground&lt;br /&gt;   SIV_ReshapeImage tImageRectangle&lt;br /&gt;   &lt;b&gt;SIV_ReshapeSheen tImageRectangle&lt;/b&gt;&lt;br /&gt;end SIV_ReshapeControl&lt;br /&gt;&lt;br /&gt;private command SIV_DetermineImageRectangle @rImageRectangle&lt;br /&gt;   &lt;i&gt;-- same as before, omitting for brevity&lt;/i&gt;&lt;br /&gt;end SIV_DetermineImageRectangle&lt;br /&gt;&lt;br /&gt;private command SIV_ReshapeBackground&lt;br /&gt;   &lt;i&gt;-- same as before, omitting for brevity&lt;/i&gt;&lt;br /&gt;end SIV_ReshapeBackground&lt;br /&gt;&lt;br /&gt;private command SIV_ReshapeImage pImageRectangle&lt;br /&gt;   &lt;i&gt;-- same as before, omitting for brevity&lt;/i&gt;&lt;br /&gt;end SIV_ReshapeImage&lt;br /&gt;&lt;br /&gt;&lt;b&gt;private command SIV_ReshapeSheen pImageRectangle&lt;br /&gt;   set the rectangle of group "SheenGroup" of me to pImageRectangle&lt;br /&gt;   local tImageWidth, tImageHeight&lt;br /&gt;   put (item 3 of pImageRectangle) - (item 1 of pImageRectangle) into tImageWidth&lt;br /&gt;   put (item 4 of pImageRectangle) - (item 2 of pImageRectangle) into tImageHeight&lt;br /&gt;   --&gt; reshape the gradient rect&lt;br /&gt;   local tGradientRectangle, tGradientHeight, tFromPoint, tToPoint, tViaPoint&lt;br /&gt;   put pImageRectangle into tGradientRectangle&lt;br /&gt;   put trunc(tImageHeight * 0.75) into tGradientHeight&lt;br /&gt;   put ((item 2 of tGradientRectangle) + tGradientHeight) into item 4 of tGradientRectangle&lt;br /&gt;   put   (item 1 of tGradientRectangle), \&lt;br /&gt;         (item 2 of tGradientRectangle) \&lt;br /&gt;         into tFromPoint&lt;br /&gt;   put   ((item 1 of tGradientRectangle) + trunc(SIV_SinInDegrees(10) * tGradientHeight)), \&lt;br /&gt;         ((item 2 of tGradientRectangle) + trunc(SIV_CosInDegrees(10) * tGradientHeight)) \&lt;br /&gt;         into tToPoint&lt;br /&gt;   put   ((item 1 of tGradientRectangle) + trunc(SIV_SinInDegrees(80) * tGradientHeight)), \&lt;br /&gt;         ((item 2 of tGradientRectangle) - trunc(SIV_CosInDegrees(80) * tGradientHeight)) \&lt;br /&gt;         into tViaPoint&lt;br /&gt;   set the rectangle of graphic "GradientGraphic" of group "SheenGroup" of me to tGradientRectangle&lt;br /&gt;   set the fillGradient["from"] of graphic "GradientGraphic" of group "SheenGroup" of me to tFromPoint&lt;br /&gt;   set the fillGradient["to"] of graphic "GradientGraphic" of group "SheenGroup" of me to tToPoint&lt;br /&gt;   set the fillGradient["via"] of graphic "GradientGraphic" of group "SheenGroup" of me to tViaPoint&lt;br /&gt;   --&gt; reshape the clipping oval&lt;br /&gt;   local tClippingRectangle, tXRadius, tYRadius&lt;br /&gt;   put trunc(tImageWidth * 1.15) into tXRadius&lt;br /&gt;   put trunc(tImageHeight * 0.9) into tYRadius&lt;br /&gt;   put   ((item 3 of pImageRectangle) - tXRadius), \&lt;br /&gt;         ((item 4 of pImageRectangle) - tYRadius), \&lt;br /&gt;         ((item 3 of pImageRectangle) + tXRadius), \&lt;br /&gt;         ((item 4 of pImageRectangle) + tYRadius) \&lt;br /&gt;         into tClippingRectangle&lt;br /&gt;   set the rectangle of graphic "ClippingGraphic" of group "SheenGroup" of me to tClippingRectangle&lt;br /&gt;end SIV_ReshapeSheen&lt;br /&gt;&lt;br /&gt;private function SIV_SinInDegrees angleInDegrees&lt;br /&gt;   return sin(angleInDegrees * pi / 180)&lt;br /&gt;end SIV_SinInDegrees&lt;br /&gt;&lt;br /&gt;private function SIV_CosInDegrees angleInDegrees&lt;br /&gt;   return cos(angleInDegrees * pi / 180)&lt;br /&gt;end SIV_CosInDegrees&lt;/b&gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Delete the existing custom control group and hit the "Create" button again. Then double-click to select an image. I downloaded and selected the &lt;a href="http://commons.wikimedia.org/wiki/File:STS-125_Atlantis_Liftoff_02.jpg"&gt;original image that Scott used&lt;/a&gt; - a public domain image from NASA.&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://1.bp.blogspot.com/-_47Wnd05kvo/TrasuhVmp5I/AAAAAAAAAIY/Q-CFWHkFt_s/s1600/StyledImageView_7.png"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 307px; height: 320px;" src="http://1.bp.blogspot.com/-_47Wnd05kvo/TrasuhVmp5I/AAAAAAAAAIY/Q-CFWHkFt_s/s320/StyledImageView_7.png" border="0" alt=""id="BLOGGER_PHOTO_ID_5671910695942662034" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;I would say that this looks like the original StyledImageViewClass. Next time, we'll take this custom control a bit further, by turning it into a &lt;a href="http://droptools.sonsothunder.com"&gt;DropTool&lt;/a&gt;. Stay tuned...&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5092807049363133934-9207299019165150952?l=quartam.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://quartam.blogspot.com/feeds/9207299019165150952/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=5092807049363133934&amp;postID=9207299019165150952' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5092807049363133934/posts/default/9207299019165150952'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5092807049363133934/posts/default/9207299019165150952'/><link rel='alternate' type='text/html' href='http://quartam.blogspot.com/2011/11/styledimageview-custom-control-in_06.html' title='StyledImageView custom control in LiveCode (part 2)'/><author><name>Quartam Software - Jan Schenkel</name><uri>http://www.blogger.com/profile/08312925714131118786</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://2.bp.blogspot.com/-F5NsXw758U8/TrGzop-Yj9I/AAAAAAAAAHI/WPyHPwAR4NM/s72-c/StyledImageView_0.png' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5092807049363133934.post-7291037454965591653</id><published>2011-11-01T11:07:00.000-07:00</published><updated>2011-11-02T14:30:06.704-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='livecode'/><category scheme='http://www.blogger.com/atom/ns#' term='cocoa'/><category scheme='http://www.blogger.com/atom/ns#' term='example code'/><category scheme='http://www.blogger.com/atom/ns#' term='custom control'/><title type='text'>StyledImageView custom control in LiveCode</title><content type='html'>In an effort to get back into native development on MacOS X, I've been working my way through the book '&lt;a href="http://www.cocoabook.com"&gt;Cocoa and Objective-C: Up and running&lt;/a&gt;' by Scott Stevenson. Chapter 10 (Custom Views and Drawing) describes step by step how to build a StyledImageView class.&lt;br /&gt;And I have to say it looks pretty nice:&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://2.bp.blogspot.com/-F5NsXw758U8/TrGzop-Yj9I/AAAAAAAAAHI/WPyHPwAR4NM/s1600/StyledImageView_0.png"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 320px; height: 230px;" src="http://2.bp.blogspot.com/-F5NsXw758U8/TrGzop-Yj9I/AAAAAAAAAHI/WPyHPwAR4NM/s320/StyledImageView_0.png" border="0" alt=""id="BLOGGER_PHOTO_ID_5670510916879814610" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;Curious as ever, I started to wonder how this same effect could be achieved as a custom control in LiveCode. What do we have conceptually?&lt;br /&gt;- A background gradient behind the image&lt;br /&gt;- An image with a 4px white border&lt;br /&gt;- A drop shadow below the image&lt;br /&gt;- A sheen over the image&lt;br /&gt;The first three items are quite straightforward; but the sheen proved more difficult. &lt;br /&gt;&lt;br /&gt;Let's get started. Note that I'm doing all my control creation and property settings via script. Just drop a button onto a new stack, set its script, and click on the button to get the custom control group. The first thing we want is the group itself and gradient background; easy enough:&lt;br /&gt;&lt;pre language="LiveCode"&gt;&lt;br /&gt;on mouseUp&lt;br /&gt;   local tGroup&lt;br /&gt;   CreateCustomControlGroup "StyledImageView", tGroup&lt;br /&gt;   CreateBackgroundGraphic tGroup&lt;br /&gt;end mouseUp&lt;br /&gt;&lt;br /&gt;private command CreateCustomControlGroup pGroupName, @rGroupId&lt;br /&gt;   reset the templateGroup&lt;br /&gt;   set the margins of the templateGroup to 0&lt;br /&gt;   create group pGroupName&lt;br /&gt;   put it into rGroupId&lt;br /&gt;   reset the templateGroup&lt;br /&gt;end CreateCustomControlGroup&lt;br /&gt;&lt;br /&gt;private command CreateBackgroundGraphic pGroup&lt;br /&gt;   reset the templateGraphic&lt;br /&gt;   set the style of the templateGraphic to "Rectangle"&lt;br /&gt;   set the rectangle of the templateGraphic to 0,0,400,300&lt;br /&gt;   set the lineSize of the templateGraphic to 0&lt;br /&gt;   set the opaque of the templateGraphic to true&lt;br /&gt;   set the fillGradient["type"] of the templateGraphic to "linear"&lt;br /&gt;   set the fillGradient["from"] of the templateGraphic to 0,300&lt;br /&gt;   set the fillGradient["to"] of the templateGraphic to 0,0&lt;br /&gt;   set the fillGradient["via"] of the templateGraphic to 400,300&lt;br /&gt;   set the fillGradient["ramp"] of the templateGraphic to \&lt;br /&gt;         "0.00000,45,45,45" &amp; return &amp; "1.00000,89,89,89"&lt;br /&gt;   create graphic "BackgroundGraphic" in pGroup&lt;br /&gt;   reset the templateGraphic&lt;br /&gt;end CreateBackgroundGraphic&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Here's the result of clicking on the button:&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://3.bp.blogspot.com/-SE2fW9lm-g4/TrGzpPO3HPI/AAAAAAAAAHY/DeZpe0yL4i4/s1600/StyledImageView_1.png"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 311px; height: 320px;" src="http://3.bp.blogspot.com/-SE2fW9lm-g4/TrGzpPO3HPI/AAAAAAAAAHY/DeZpe0yL4i4/s320/StyledImageView_1.png" border="0" alt=""id="BLOGGER_PHOTO_ID_5670510926881037554" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;Now let's add in the image. Something that initially trips up a lot of new LiveCode developers is that an image will happily jump back to its original size if you don't set the 'lockLocation' of the image to "true". Another quirk is that you can't set the borderColor of an image control directly. However, it does 'inherit' the borderColor from the chain of owners (the containing group if applicable, the card or the stack) so to make sure we get a white border, we need to set the borderColor of our custom control group. So the following update to the button script will do nicely:&lt;br /&gt;&lt;pre language="LiveCode"&gt;&lt;br /&gt;on mouseUp&lt;br /&gt;   local tGroup&lt;br /&gt;   CreateCustomControlGroup "StyledImageView", tGroup&lt;br /&gt;   CreateBackgroundGraphic tGroup&lt;br /&gt;   &lt;b&gt;CreateViewImage tGroup&lt;/b&gt;&lt;br /&gt;end mouseUp&lt;br /&gt;&lt;br /&gt;private command CreateCustomControlGroup pGroupName, @rGroupId&lt;br /&gt;   reset the templateGroup&lt;br /&gt;   set the margins of the templateGroup to 0&lt;br /&gt;   &lt;b&gt;set the borderColor of the templateGroup to "white"&lt;/b&gt;&lt;br /&gt;   create group pGroupName&lt;br /&gt;   put it into rGroupId&lt;br /&gt;   reset the templateGroup&lt;br /&gt;end CreateCustomControlGroup&lt;br /&gt;&lt;br /&gt;private command CreateBackgroundGraphic pGroup&lt;br /&gt;   &lt;i&gt;-- same as before, omitting for brevity&lt;/i&gt;&lt;br /&gt;end CreateBackgroundGraphic&lt;br /&gt;&lt;br /&gt;&lt;b&gt;private command CreateViewImage pGroup&lt;br /&gt;   reset the templateImage&lt;br /&gt;   set the rectangle of the templateImage to 30,30,370,270&lt;br /&gt;   set the borderWidth of the templateImage to 4&lt;br /&gt;   set the threeD of the templateImage to false&lt;br /&gt;   set the showBorder of the templateImage to true&lt;br /&gt;   set the resizeQuality of the templateImage to "best"&lt;br /&gt;   set the lockLocation of the templateImage to true&lt;br /&gt;   create image "ViewImage" in pGroup&lt;br /&gt;   reset the templateImage&lt;br /&gt;end CreateViewImage&lt;/b&gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Delete the existing custom control group, and hit the button again. Here's what you should see:&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://2.bp.blogspot.com/-VLtM1u18zSI/TrGzpmacTHI/AAAAAAAAAHg/0TGakIgO4LA/s1600/StyledImageView_2.png"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 306px; height: 320px;" src="http://2.bp.blogspot.com/-VLtM1u18zSI/TrGzpmacTHI/AAAAAAAAAHg/0TGakIgO4LA/s320/StyledImageView_2.png" border="0" alt=""id="BLOGGER_PHOTO_ID_5670510933103627378" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;Next up is the drop shadow for the image. While we could do this by setting the properties in the 'CreateViewImage' command above, I think it's better of we don't repeat code, and put that in the custom control group script - after all, we want to be able to toggle it via script. So let's drop another button on the stack, set its name to "StyledImageView Behavior" and start scripting that button. Later on, we'll set the 'behavior' property of the custom control group.&lt;br /&gt;&lt;br /&gt;The most important item in the behavor script is handling resizing. We want to leave a margin of 10% around the image so that we can see the gradient background, and we want to scale the image proportionately rather than stretch it out. While we're add it, we'll add some custom property setters and getters for the drop shadow and the image file name. And we'll add a 'mouseDoubleUp' handler to allow the user to select an image. Here is the first version of the behavior script:&lt;br /&gt;&lt;pre language="LiveCode"&gt;&lt;br /&gt;## engine message handlers&lt;br /&gt;&lt;br /&gt;on mouseDoubleUp&lt;br /&gt;   answer file "Select an image to view"&lt;br /&gt;   if it is empty then exit mouseDoubleUp&lt;br /&gt;   set the imageFileName of me to it&lt;br /&gt;   pass mouseDoubleUp&lt;br /&gt;end mouseDoubleUp&lt;br /&gt;&lt;br /&gt;on resizeControl&lt;br /&gt;   SIV_ReshapeControl&lt;br /&gt;   pass resizeControl&lt;br /&gt;end resizeControl&lt;br /&gt;&lt;br /&gt;## property setters and getters&lt;br /&gt;&lt;br /&gt;setProp showShadow pShowShadowBoolean&lt;br /&gt;   if pShowShadowBoolean is "true" then&lt;br /&gt;      local tDropShadow&lt;br /&gt;      put 0,0,0 into tDropShadow["color"]&lt;br /&gt;      put 8 into tDropShadow["size"]&lt;br /&gt;      put 6 into tDropShadow["distance"]&lt;br /&gt;      put 90 into tDropShadow["angle"]&lt;br /&gt;      put 0 into tDropShadow["spread"]&lt;br /&gt;      put 255 into tDropShadow["opacity"]&lt;br /&gt;      put "normal" into tDropShadow["blendmode"]&lt;br /&gt;      put "box3pass" into tDropShadow["filter"]&lt;br /&gt;      put true into tDropShadow["knockout"]&lt;br /&gt;      set the dropShadow of image "ViewImage" of me to tDropShadow&lt;br /&gt;   else&lt;br /&gt;      set the dropShadow of image "ViewImage" of me to empty&lt;br /&gt;   end if&lt;br /&gt;   pass showShadow&lt;br /&gt;end showShadow&lt;br /&gt;&lt;br /&gt;setProp imageFileName pFileName&lt;br /&gt;   set the fileName of image "ViewImage" of me to pFileName&lt;br /&gt;   SIV_ReshapeControl&lt;br /&gt;end imageFileName&lt;br /&gt;&lt;br /&gt;getProp imageFileName&lt;br /&gt;   return the fileName of image "ViewImage" of me&lt;br /&gt;end imageFileName&lt;br /&gt;&lt;br /&gt;## private commands and functions&lt;br /&gt;&lt;br /&gt;private command SIV_ReshapeControl&lt;br /&gt;   local tImageRectangle&lt;br /&gt;   SIV_DetermineImageRectangle tImageRectangle&lt;br /&gt;   SIV_ReshapeBackground&lt;br /&gt;   SIV_ReshapeImage tImageRectangle&lt;br /&gt;end SIV_ReshapeControl&lt;br /&gt;&lt;br /&gt;private command SIV_DetermineImageRectangle @rImageRectangle&lt;br /&gt;   -- 10% margin around the image while preserving the aspect ratio&lt;br /&gt;   local tSourceWidth, tSourceHeight, tTargetWidth, tTargetHeight&lt;br /&gt;   put the formattedWidth of image "ViewImage" of me into tSourceWidth&lt;br /&gt;   put the formattedHeight of image "ViewImage" of me into tSourceHeight&lt;br /&gt;   put (the width of me) * 0.8 into tTargetWidth&lt;br /&gt;   put (the height of me) * 0.8 into tTargetHeight&lt;br /&gt;   -- make sure we don't get divide by zero errors&lt;br /&gt;   if tSourceWidth = 0&lt;br /&gt;   then put 1 into tSourceWidth&lt;br /&gt;   if tSourceHeight = 0&lt;br /&gt;   then put 1 into tSourceHeight&lt;br /&gt;   local tScaleFactor, tImageWidth, tImageHeight&lt;br /&gt;   put min(tTargetWidth / tSourceWidth, tTargetHeight / tSourceHeight) into tScaleFactor&lt;br /&gt;   put trunc(tSourceWidth * tScaleFactor) into tImageWidth&lt;br /&gt;   put trunc(tSourceHeight * tScaleFactor) into tImageHeight&lt;br /&gt;   local tLeft, tTop&lt;br /&gt;   put (the left of me) + (((the width of me) - tImageWidth) div 2) into tLeft&lt;br /&gt;   put (the top of me) + (((the height of me) - tImageHeight) div 2) into tTop&lt;br /&gt;   put tLeft,tTop,(tLeft + tImageWidth),(tTop + tImageHeight) into rImageRectangle&lt;br /&gt;end SIV_DetermineImageRectangle&lt;br /&gt;&lt;br /&gt;private command SIV_ReshapeBackground&lt;br /&gt;   set the rectangle of graphic "BackgroundGraphic" of me to the rectangle of me&lt;br /&gt;end SIV_ReshapeImage&lt;br /&gt;&lt;br /&gt;private command SIV_ReshapeImage pImageRectangle&lt;br /&gt;   set the rectangle of image "ViewImage" of me to pImageRectangle&lt;br /&gt;end SIV_ReshapeImage&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;So just a little tweak to our 'Create' button script is required to set the behavior and activate the drop shadow:&lt;br /&gt;&lt;pre language="LiveCode"&gt;&lt;br /&gt;on mouseUp&lt;br /&gt;   local tGroup&lt;br /&gt;   CreateCustomControlGroup "StyledImageView", tGroup&lt;br /&gt;   CreateBackgroundGraphic tGroup&lt;br /&gt;   CreateViewImage tGroup&lt;br /&gt;   &lt;b&gt;set the behavior of tGroup to the long id of button "StyledImageView Behavior"&lt;br /&gt;   set the showShadow of tGroup to true&lt;/b&gt;&lt;br /&gt;end mouseUp&lt;br /&gt;&lt;br /&gt;private command CreateCustomControlGroup pGroupName, @rGroupId&lt;br /&gt;   &lt;i&gt;-- same as before, omitting for brevity&lt;/i&gt;&lt;br /&gt;end CreateCustomControlGroup&lt;br /&gt;&lt;br /&gt;private command CreateBackgroundGraphic pGroup&lt;br /&gt;   &lt;i&gt;-- same as before, omitting for brevity&lt;/i&gt;&lt;br /&gt;end CreateBackgroundGraphic&lt;br /&gt;&lt;br /&gt;private command CreateViewImage pGroup&lt;br /&gt;   &lt;i&gt;-- same as before, omitting for brevity&lt;/i&gt;&lt;br /&gt;end CreateViewImage&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;One more thing to do before we call it a day: handle the 'resizeStack' message in the card script, so that we automatically resize the custom control group as the user changes the stack size. Just plop the following into the card script:&lt;br /&gt;&lt;pre language="LiveCode"&gt;&lt;br /&gt;on resizeStack pNewWidth, pNewHeight&lt;br /&gt;   if there is a group "StyledImageView" then&lt;br /&gt;      local tRectangle&lt;br /&gt;      put the rectangle of group "StyledImageView" into tRectangle&lt;br /&gt;      put pNewWidth into item 3 of tRectangle&lt;br /&gt;      put pNewHeight into item 4 of tRectangle&lt;br /&gt;      set the rectangle of group "StyledImageView" to tRectangle&lt;br /&gt;   end if&lt;br /&gt;   pass resizeStack&lt;br /&gt;end resizeStack&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Delete the existing custom control group and hit the button again. The embedded image control now has a nice drop shadow, which we can turn off and on using the message box or by scripting a simple checkbox button.&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://1.bp.blogspot.com/-ni9KrWTRdU0/TrGzprUjqwI/AAAAAAAAAHo/FTD5LkwV_As/s1600/StyledImageView_3.png"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 308px; height: 320px;" src="http://1.bp.blogspot.com/-ni9KrWTRdU0/TrGzprUjqwI/AAAAAAAAAHo/FTD5LkwV_As/s320/StyledImageView_3.png" border="0" alt=""id="BLOGGER_PHOTO_ID_5670510934421121794" /&gt;&lt;/a&gt;&lt;br /&gt;And when you double-click on the custom control, you can select an image file and the embedded image control automagically reshapes in order to display the chosen image file proportionately.&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://4.bp.blogspot.com/-aQjlpS5pHd0/TrGzp1d1jlI/AAAAAAAAAH4/Q2Ri0DOZiow/s1600/StyledImageView_4.png"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 307px; height: 320px;" src="http://4.bp.blogspot.com/-aQjlpS5pHd0/TrGzp1d1jlI/AAAAAAAAAH4/Q2Ri0DOZiow/s320/StyledImageView_4.png" border="0" alt=""id="BLOGGER_PHOTO_ID_5670510937144397394" /&gt;&lt;/a&gt;&lt;br /&gt;Looks good doesn't it? Next time, we'll tackle the sheen. It's tricky, but it can be done!&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5092807049363133934-7291037454965591653?l=quartam.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://quartam.blogspot.com/feeds/7291037454965591653/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=5092807049363133934&amp;postID=7291037454965591653' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5092807049363133934/posts/default/7291037454965591653'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5092807049363133934/posts/default/7291037454965591653'/><link rel='alternate' type='text/html' href='http://quartam.blogspot.com/2011/11/styledimageview-custom-control-in.html' title='StyledImageView custom control in LiveCode'/><author><name>Quartam Software - Jan Schenkel</name><uri>http://www.blogger.com/profile/08312925714131118786</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://2.bp.blogspot.com/-F5NsXw758U8/TrGzop-Yj9I/AAAAAAAAAHI/WPyHPwAR4NM/s72-c/StyledImageView_0.png' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5092807049363133934.post-2189123612424347367</id><published>2011-10-02T09:12:00.000-07:00</published><updated>2011-10-04T22:01:48.879-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='java'/><category scheme='http://www.blogger.com/atom/ns#' term='livecode'/><category scheme='http://www.blogger.com/atom/ns#' term='MacOS X'/><category scheme='http://www.blogger.com/atom/ns#' term='example code'/><category scheme='http://www.blogger.com/atom/ns#' term='NSImage'/><title type='text'>MacOSX standard artwork in LiveCode</title><content type='html'>When you develop an application for Mac, users have certain expectations about how your app will look and behave. One of the often forgotten parts of your user interface is the icon artwork - it is very tempting to simply buy an icon collection and then use it everywhere in your cross-platform application. However, to help developers achieve a consistent user interface, across multiple versions of the operating system, Apple has been adding 'template' images to its Cocoa NSImage class.&lt;br /&gt;&lt;br /&gt;Here are a few examples which I recently used in a project:&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://4.bp.blogspot.com/-qeSuDcNXYec/TovkXw-TdgI/AAAAAAAAAHA/F7vtY4lfZyg/s1600/NSImages.png"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 320px; height: 208px;" src="http://4.bp.blogspot.com/-qeSuDcNXYec/TovkXw-TdgI/AAAAAAAAAHA/F7vtY4lfZyg/s320/NSImages.png" border="0" alt=""id="BLOGGER_PHOTO_ID_5659868453655508482" /&gt;&lt;/a&gt;&lt;br /&gt;For a complete list of the available template images, look &lt;a href="http://developer.apple.com/library/mac/#documentation/Cocoa/Reference/ApplicationKit/Classes/NSImage_Class/Reference/Reference.html#//apple_ref/doc/constant_group/Image_Template_Constants"&gt;here&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;Great stuff, but how are we going to get at it from LiveCode? Not directly, I'm afraid. But all is not lost: &lt;a href="http://developer.apple.com/library/mac/#documentation/Java/Conceptual/Java14Development/04-JavaUIToolkits/JavaUIToolkits.html#//apple_ref/doc/uid/TP40001901-SW5"&gt;a while back&lt;/a&gt;, Apple's Java engineers added to their Java implementation on MacOS X 10.5 Leopard (and later) the ability to fetch these images using a simple call:&lt;br /&gt;&lt;pre language="java"&gt;Toolkit.getDefaultToolkit().getImage("NSImage://NSColorPanel")&lt;/pre&gt;&lt;br /&gt;You can see where I'm heading with this: writing a helper Java class that you can execute with a simple '&lt;a href="http://docs.runrev.com/Function/shell"&gt;shell&lt;/a&gt;' function call. So let's start by writing up the Java class code. We'll set it up with two parameters: the first parameter accepts either "*" (to say you want a complete set of images) or a comma-separated list of image names; the second parameter is the output directory where the available images will be saved in PNG format.&lt;br /&gt;&lt;pre language="Java"&gt;&lt;br /&gt;import java.awt.Graphics;&lt;br /&gt;import java.awt.Image;&lt;br /&gt;import java.awt.Toolkit;&lt;br /&gt;import java.awt.image.BufferedImage;&lt;br /&gt;import java.io.File;&lt;br /&gt;import java.io.IOException;&lt;br /&gt;import java.util.EnumSet;&lt;br /&gt;import javax.imageio.ImageIO;&lt;br /&gt;&lt;br /&gt;public class NSImageFetcher {&lt;br /&gt;&lt;br /&gt;    public enum NSImageName {&lt;br /&gt;        // Image Template Constants&lt;br /&gt;        NSQuickLookTemplate,&lt;br /&gt;        NSBluetoothTemplate,&lt;br /&gt;        NSIChatTheaterTemplate,&lt;br /&gt;        NSSlideshowTemplate,&lt;br /&gt;        NSActionTemplate,&lt;br /&gt;        NSSmartBadgeTemplate,&lt;br /&gt;        NSPathTemplate,&lt;br /&gt;        NSInvalidDataFreestandingTemplate,&lt;br /&gt;        NSLockLockedTemplate,&lt;br /&gt;        NSLockUnlockedTemplate,&lt;br /&gt;        NSGoRightTemplate,&lt;br /&gt;        NSGoLeftTemplate,&lt;br /&gt;        NSRightFacingTriangleTemplate,&lt;br /&gt;        NSLeftFacingTriangleTemplate,&lt;br /&gt;        NSAddTemplate,&lt;br /&gt;        NSRemoveTemplate,&lt;br /&gt;        NSRevealFreestandingTemplate,&lt;br /&gt;        NSFollowLinkFreestandingTemplate,&lt;br /&gt;        NSEnterFullScreenTemplate,&lt;br /&gt;        NSExitFullScreenTemplate,&lt;br /&gt;        NSStopProgressTemplate,&lt;br /&gt;        NSStopProgressFreestandingTemplate,&lt;br /&gt;        NSRefreshTemplate,&lt;br /&gt;        NSRefreshFreestandingTemplate,&lt;br /&gt;        NSFolder,&lt;br /&gt;        NSTrashEmpty,&lt;br /&gt;        NSTrashFull,&lt;br /&gt;        NSHomeTemplate,&lt;br /&gt;        NSBookmarksTemplate,&lt;br /&gt;        NSCaution,&lt;br /&gt;        NSStatusAvailable,&lt;br /&gt;        NSStatusPartiallyAvailable,&lt;br /&gt;        NSStatusUnavailable,&lt;br /&gt;        NSStatusNone,&lt;br /&gt;        NSApplicationIcon,&lt;br /&gt;        NSMenuOnStateTemplate,&lt;br /&gt;        NSMenuMixedStateTemplate,&lt;br /&gt;        NSUserGuest,&lt;br /&gt;        NSMobileMe,&lt;br /&gt;        // Multiple Documents Drag Image&lt;br /&gt;        NSMultipleDocuments,&lt;br /&gt;        // Sharing Permissions Names Images&lt;br /&gt;        NSUser,&lt;br /&gt;        NSUserGroup,&lt;br /&gt;        NSEveryone,&lt;br /&gt;        // System Entity Images&lt;br /&gt;        NSBonjour,&lt;br /&gt;        NSDotMac,&lt;br /&gt;        NSComputer,&lt;br /&gt;        NSFolderBurnable,&lt;br /&gt;        NSFolderSmart,&lt;br /&gt;        NSNetwork,&lt;br /&gt;        // Toolbar Named Images&lt;br /&gt;        NSUserAccounts,&lt;br /&gt;        NSPreferencesGeneral,&lt;br /&gt;        NSAdvanced,&lt;br /&gt;        NSInfo,&lt;br /&gt;        NSFontPanel,&lt;br /&gt;        NSColorPanel,&lt;br /&gt;        // View Type Template Images&lt;br /&gt;        NSIconViewTemplate,&lt;br /&gt;        NSListViewTemplate,&lt;br /&gt;        NSColumnViewTemplate,&lt;br /&gt;        NSFlowViewTemplate;&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    &lt;br /&gt;    public static Image getNSImage(final NSImageName nsImageName) {&lt;br /&gt;        return getNSImage(nsImageName.name());&lt;br /&gt;    }&lt;br /&gt;    &lt;br /&gt;    public static Image getNSImage(final String nsImageName) {&lt;br /&gt;        return Toolkit.getDefaultToolkit().getImage("NSImage://" + nsImageName);&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    public static void saveNSImage(final NSImageName nsImageName, final File outputFile) throws IOException {&lt;br /&gt;        saveNSImage(nsImageName.name(), outputFile);&lt;br /&gt;    }&lt;br /&gt;    &lt;br /&gt;    public static void saveNSImage(final String nsImageName, final File outputFile) throws IOException {&lt;br /&gt;        final Image nsImage = getNSImage(nsImageName);&lt;br /&gt;        if (nsImage != null) {&lt;br /&gt;            BufferedImage bufferedImage = null;&lt;br /&gt;            if (nsImage instanceof BufferedImage) {&lt;br /&gt;                bufferedImage = (BufferedImage) nsImage;&lt;br /&gt;            } else {&lt;br /&gt;                bufferedImage = new BufferedImage(nsImage.getWidth(null), nsImage.getHeight(null), BufferedImage.TYPE_INT_ARGB);&lt;br /&gt;                final Graphics bufImgGraphics = bufferedImage.getGraphics();&lt;br /&gt;                bufImgGraphics.drawImage(nsImage, 0, 0, null);&lt;br /&gt;            }&lt;br /&gt;            if (!outputFile.exists()) {&lt;br /&gt;                outputFile.createNewFile();&lt;br /&gt;            }&lt;br /&gt;            ImageIO.write(bufferedImage, "png", outputFile);&lt;br /&gt;        }&lt;br /&gt;    }&lt;br /&gt;    &lt;br /&gt;    public static void main(String[] args) throws IOException {&lt;br /&gt;        // We can't run in headless mode or we get empty icons&lt;br /&gt;        //    System.setProperty("java.awt.headless", "true");&lt;br /&gt;&lt;br /&gt;        if (args.length != 2) {&lt;br /&gt;            System.out.println("Usage: java NSImageFetcher &amp;lt;list of NSImageNames&amp;gt;,&amp;lt;output directory&amp;gt;");&lt;br /&gt;            return;&lt;br /&gt;        }&lt;br /&gt;        final File outputDirectory = new File(args[1]);&lt;br /&gt;        if ("*".equals(args[0])) {&lt;br /&gt;            for (final NSImageName nsImageName : EnumSet.allOf(NSImageName.class)) {&lt;br /&gt;                final File outputFile = new File(outputDirectory.getAbsolutePath() + File.separator + nsImageName.name() + ".png");&lt;br /&gt;                saveNSImage(nsImageName, outputFile);&lt;br /&gt;            }&lt;br /&gt;        } else {&lt;br /&gt;            for (final String nsImageName : args[0].split(",")) {&lt;br /&gt;                try {&lt;br /&gt;                    final File outputFile = new File(outputDirectory.getAbsolutePath() + File.separator + nsImageName + ".png");&lt;br /&gt;                    saveNSImage(nsImageName, outputFile);&lt;br /&gt;                } catch (IllegalArgumentException e) {&lt;br /&gt;                    // ignore&lt;br /&gt;                }&lt;br /&gt;            }&lt;br /&gt;        }&lt;br /&gt;    }&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;And here's the LiveCode button script to put all the images into a subdirectory 'resources' in the directory where the stack is saved:&lt;br /&gt;&lt;pre language="LiveCode"&gt;&lt;br /&gt;on mouseUp&lt;br /&gt;   -- save settings&lt;br /&gt;   local tOldDefaultFolder, tOldHideConsoleWindows&lt;br /&gt;   put the defaultFolder into tOldDefaultFolder&lt;br /&gt;   set the defaultFolder to StackFolderPath()&lt;br /&gt;   put the hideConsoleWindows into tOldHideConsoleWindows&lt;br /&gt;   set the hideConsoleWindows to true&lt;br /&gt;   -- call the command line via shell&lt;br /&gt;   local tShellCommand, tShellResult&lt;br /&gt;   put "java NSImageFetcher" &amp;&amp; \&lt;br /&gt;         quote &amp; "*" &amp; quote &amp;&amp; \&lt;br /&gt;         quote &amp; StackFolderPath("resources") &amp; quote into tShellCommand&lt;br /&gt;   answer tShellCommand &amp; return &amp; shell(tShellCommand)&lt;br /&gt;   -- restore settings&lt;br /&gt;   set the defaultFolder to tOldDefaultFolder&lt;br /&gt;   set the hideConsoleWindows to tOldHideConsoleWindows&lt;br /&gt;   -- go ahead and link&lt;br /&gt;end mouseUp&lt;br /&gt;&lt;br /&gt;function StackFolderPath pRelativePath&lt;br /&gt;   local tPath&lt;br /&gt;   put the effective filename of this stack into tPath&lt;br /&gt;   set the itemDelimiter to slash&lt;br /&gt;   if pRelativePath is empty then&lt;br /&gt;      delete item -1 of tPath&lt;br /&gt;   else&lt;br /&gt;      put pRelativePath into item -1 of tPath&lt;br /&gt;   end if&lt;br /&gt;   return tPath&lt;br /&gt;end StackFolderPath&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Okay, MacOSX comes with a preinstalled Java runtime so the above should work on MacOSX 10.5 Leopard, MacOSX 10.6 Snow Leopard and even on MacOSX 10.7 Lion (since there's still a Java 6 'out of the box'). Naturally, the artwork may change between operating system versions. So my approach would be to:&lt;br /&gt;- check if there is a '&amp;lt;MyCompanyName&amp;gt;/&amp;lt;MyAppName&amp;gt;/resources' directory in the user's preferences directory (by means of the '&lt;a href="http://docs.runrev.com/Function/specialfolderpath"&gt;specialFolderPath&lt;/a&gt;' function);&lt;br /&gt;- if the directory is absent, create it and use the 'shell' function as above to fetch the images I need, and save the '&lt;a href="http://docs.runrev.com/Function/systemversion"&gt;systemVersion&lt;/a&gt;' in a text file in the same directory&lt;br /&gt;- if the directory is present, compare the current value of the 'systemVersion' with the one saved in said directory, and once again fetch the images if there's a difference&lt;br /&gt;&lt;br /&gt;It's up to you to either use references to these .PNG images by setting the '&lt;a href="http://docs.runrev.com/Property/filename"&gt;filename&lt;/a&gt;' property of an '&lt;a href="http://docs.runrev.com/Object/image"&gt;image&lt;/a&gt;' control; or to then import the .PNG files into a stack.&lt;br /&gt;&lt;br /&gt;Note that this artwork is copyrighted by Apple, Inc. You shouldn't extract it and then use it in your apps on any platform other than MacOSX. Oh, and please head the warning "You should always use named images according to their intended purpose, and not according to how the image appears when loaded. The appearance of images can change between releases. If you use an image for its intended purpose (and not because of it looks), your code should look correct from release to release."&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5092807049363133934-2189123612424347367?l=quartam.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://quartam.blogspot.com/feeds/2189123612424347367/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=5092807049363133934&amp;postID=2189123612424347367' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5092807049363133934/posts/default/2189123612424347367'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5092807049363133934/posts/default/2189123612424347367'/><link rel='alternate' type='text/html' href='http://quartam.blogspot.com/2011/10/macosx-standard-artwork-in-livecode.html' title='MacOSX standard artwork in LiveCode'/><author><name>Quartam Software - Jan Schenkel</name><uri>http://www.blogger.com/profile/08312925714131118786</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://4.bp.blogspot.com/-qeSuDcNXYec/TovkXw-TdgI/AAAAAAAAAHA/F7vtY4lfZyg/s72-c/NSImages.png' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5092807049363133934.post-6109647557508925895</id><published>2011-09-25T11:46:00.000-07:00</published><updated>2011-09-27T12:02:39.555-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='livecode'/><category scheme='http://www.blogger.com/atom/ns#' term='MacOS X'/><category scheme='http://www.blogger.com/atom/ns#' term='example code'/><category scheme='http://www.blogger.com/atom/ns#' term='user interface'/><title type='text'>Determining the Aqua UI appearance on MacOS X</title><content type='html'>&lt;span style="font-style:italic;"&gt;Due to high workload at the day-job, I haven't been able to keep my schedule regarding the experiment building a Ribbon custom control for LiveCode. It's still going to happen, but is going to take a bit longer than planned. In the meantime, I will post some tidbits that are relevant to those that design custom controls for LiveCode.&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;In MacOS X, the Preferences application has a panel titled Appearance:&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://2.bp.blogspot.com/-FzjJg1-ACyE/Tn96pYsvcmI/AAAAAAAAAGI/vHeQQc_1-Fo/s1600/NSAquaColorVariant1.png"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 320px; height: 267px;" src="http://2.bp.blogspot.com/-FzjJg1-ACyE/Tn96pYsvcmI/AAAAAAAAAGI/vHeQQc_1-Fo/s320/NSAquaColorVariant1.png" border="0" alt=""id="BLOGGER_PHOTO_ID_5656374508423967330" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;Yeah, I know I'm still on MacOS X Snow Leopard 10.6.8 - just waiting for Lion 10.7.2 before I upgrade.&lt;br /&gt;Anyway, the Appearance option menu has two entries: Blue and Graphite.&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://4.bp.blogspot.com/-jnefqR09qsc/Tn96ppmcBLI/AAAAAAAAAGQ/-o2soVefgs0/s1600/NSAquaColorVariant2.png"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 176px; height: 78px;" src="http://4.bp.blogspot.com/-jnefqR09qsc/Tn96ppmcBLI/AAAAAAAAAGQ/-o2soVefgs0/s320/NSAquaColorVariant2.png" border="0" alt=""id="BLOGGER_PHOTO_ID_5656374512960930994" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;Below that option menu, is the Highlight color option menu; and if we want to know this color in our LiveCode scripts, all we have to do is check the global '&lt;a href="http://docs.runrev.com/Property/hiliteColor"&gt;hiliteColor&lt;/a&gt;' property. But there's no built-in way to access the Appearance. So how can we get this information? Do we have to write an external to find out whether we should have blue or graphite custom controls?&lt;br /&gt;&lt;br /&gt;As it turns out, we can get at this system setting by using the '&lt;a href="http://developer.apple.com/library/mac/#documentation/Darwin/Refehttp://www.blogger.com/img/blank.gifrence/ManPages/man1/defaults.1.html"&gt;defaults&lt;/a&gt;' command via the '&lt;a href="http://docs.runrev.com/Function/shell"&gt;shell&lt;/a&gt;' function. Just plop the following in a button script:&lt;br /&gt;&lt;pre language="LiveCode"&gt;&lt;br /&gt;on mouseUp&lt;br /&gt;   local tVariant, tAppearance&lt;br /&gt;   put word 1 to -1 \&lt;br /&gt;       of shell("defaults read -g AppleAquaColorVariant") \&lt;br /&gt;       into tVariant&lt;br /&gt;   switch tVariant&lt;br /&gt;      case 1&lt;br /&gt;         put "Blue" into tAppearance&lt;br /&gt;         break&lt;br /&gt;      case 6&lt;br /&gt;         put "Graphite" into tAppearance&lt;br /&gt;         break&lt;br /&gt;      default&lt;br /&gt;         put "Unrecognized appearance variant [" &amp; \&lt;br /&gt;             tVariant &amp; "]" into tAppearance&lt;br /&gt;         break&lt;br /&gt;   end switch&lt;br /&gt;   answer tAppearance&lt;br /&gt;end mouseUp&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Click the button, and depending on your operating system and preferences setting, you'll see the appropriate message. It appears that at one point there were a few more appearances, but they were lost during a chopping frenzy in Cupertino. Be that as it may, you now have a way to check for the appearance setting in the preferences window.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5092807049363133934-6109647557508925895?l=quartam.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://quartam.blogspot.com/feeds/6109647557508925895/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=5092807049363133934&amp;postID=6109647557508925895' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5092807049363133934/posts/default/6109647557508925895'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5092807049363133934/posts/default/6109647557508925895'/><link rel='alternate' type='text/html' href='http://quartam.blogspot.com/2011/09/determining-aqua-ui-appearance-on-macos.html' title='Determining the Aqua UI appearance on MacOS X'/><author><name>Quartam Software - Jan Schenkel</name><uri>http://www.blogger.com/profile/08312925714131118786</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://2.bp.blogspot.com/-FzjJg1-ACyE/Tn96pYsvcmI/AAAAAAAAAGI/vHeQQc_1-Fo/s72-c/NSAquaColorVariant1.png' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5092807049363133934.post-1882288230774736057</id><published>2011-08-07T12:03:00.000-07:00</published><updated>2011-08-07T12:34:05.258-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='ribbon'/><category scheme='http://www.blogger.com/atom/ns#' term='livecode'/><category scheme='http://www.blogger.com/atom/ns#' term='example code'/><category scheme='http://www.blogger.com/atom/ns#' term='experiment'/><title type='text'>Experimenting with ribbons in LiveCode (part 4)</title><content type='html'>Four weeks ago, we &lt;a href="http://quartam.blogspot.com/2011/07/experimenting-with-ribbons-in-livecode.html"&gt;kicked off&lt;/a&gt; an experiment to create a ribbon control for LiveCode. By now, we have a clear goal, derived an initial XML structure for our &lt;a href="http://quartam.blogspot.com/2011/07/experimenting-with-ribbons-in-livecode_24.html"&gt;'ribbonText'&lt;/a&gt; property, and in the previous post, we finally got some visual work done, implementing the &lt;a href="http://quartam.blogspot.com/2011/08/experimenting-with-ribbons-in-livecode.html"&gt;ribbon background and tabs&lt;/a&gt;. The next logical step is to implement our ribbon groups.&lt;br /&gt;&lt;br /&gt;There is actually more to a ribbon group than initially meets the eye:&lt;br /&gt;- groups can have an associated dialog (e.g. to call up the classic font dialog)&lt;br /&gt;- groups can be added to the quick access toolbar&lt;br /&gt;- groups have rules for dynamic resizing&lt;br /&gt;&lt;br /&gt;But we'll postpone these juicier bits until wev'e gotten around to actually adding buttons to our ribbon control. Right now, our goal is to turn the group definitions in our ribbonText property into controls. First we extend the ribbonText parser to also read the &amp;lt;Group&amp;gt; definitions from the XML structure, and then we add the necessary code to the 'qrtRexRibbonGroupsBehavior' script. Under our current (limited) interpretation, each ribbon group is nothing more than the label and the separator next to it.&lt;br /&gt;&lt;br /&gt;Once more, I've opted for the approach of taking a slice from a screenshot to represent the separator, as I couldn't get the stroke and fill gradients of a graphic to exactly mirror the original. Maybe one of these days I'll throw out the images and replace them all with gradients anyway - but today is not that day. At any rate, with just two controls to add, it's pretty easy.&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://1.bp.blogspot.com/-W3VjsFGImdE/Tj7nMiF6h3I/AAAAAAAAAFc/_sG2o-fA7zE/s1600/qrtrex_tmpl_6.png"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 246px; height: 320px;" src="http://1.bp.blogspot.com/-W3VjsFGImdE/Tj7nMiF6h3I/AAAAAAAAAFc/_sG2o-fA7zE/s320/qrtrex_tmpl_6.png" border="0" alt=""id="BLOGGER_PHOTO_ID_5638197986010498930" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;And by separating out the code that triggers the population of the nested RibbonGroups custom control group, we can easily switch ribbon group sets by handling the custom 'ribbonTabPick' event that is dispatched from the nested RibbonTabs custom control group which we implemented in the previous post.&lt;br /&gt;&lt;br /&gt;Download the current revision &lt;a href="http://downloads.quartam.com/qrtrex.20110807.zip"&gt;here&lt;/a&gt; - next time we'll tackle the buttons.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5092807049363133934-1882288230774736057?l=quartam.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://quartam.blogspot.com/feeds/1882288230774736057/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=5092807049363133934&amp;postID=1882288230774736057' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5092807049363133934/posts/default/1882288230774736057'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5092807049363133934/posts/default/1882288230774736057'/><link rel='alternate' type='text/html' href='http://quartam.blogspot.com/2011/08/experimenting-with-ribbons-in-livecode_07.html' title='Experimenting with ribbons in LiveCode (part 4)'/><author><name>Quartam Software - Jan Schenkel</name><uri>http://www.blogger.com/profile/08312925714131118786</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://1.bp.blogspot.com/-W3VjsFGImdE/Tj7nMiF6h3I/AAAAAAAAAFc/_sG2o-fA7zE/s72-c/qrtrex_tmpl_6.png' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5092807049363133934.post-2640596849641183452</id><published>2011-08-02T14:03:00.001-07:00</published><updated>2011-08-02T14:21:55.930-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='ribbon'/><category scheme='http://www.blogger.com/atom/ns#' term='livecode'/><category scheme='http://www.blogger.com/atom/ns#' term='example code'/><category scheme='http://www.blogger.com/atom/ns#' term='experiment'/><title type='text'>Experimenting with ribbons in LiveCode (part 3)</title><content type='html'>Three weeks ago, we kicked off an experiment to create a ribbon control for LiveCode. By now, we have a good plan, and we have an initial XML structure for our 'ribbonText' property. So let's go ahead and start putting together some code! Let's start with the ribbon background and header area, shall we?&lt;br /&gt;&lt;br /&gt;I really wanted to do it all with only graphics, gradients and effects, but I couldn't quite get the details right. So eventually I went for the oldest trick in the book: I took a screenshot and then extracted three slices from the image: 2 pixels wide for the left and right edge, and then 1 pixel wide for the center. You can't use this slice as a pattern for another control, but you can just stretch that image as far as you need: because the original is only 1 pixel wide, the engine can stretch the image at a low cost, because there is no interpolation required.&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://1.bp.blogspot.com/-X3thGhmPswQ/Tjhn7jcw0uI/AAAAAAAAAE8/XbONBhkOQU0/s1600/qrtrex_tmpl_2.png"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 303px; height: 320px;" src="http://1.bp.blogspot.com/-X3thGhmPswQ/Tjhn7jcw0uI/AAAAAAAAAE8/XbONBhkOQU0/s320/qrtrex_tmpl_2.png" border="0" alt=""id="BLOGGER_PHOTO_ID_5636369206479999714" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;Let's get a head start on the ApplicationMenu button: take some more screenshots while hovering and after clicking on it; then extract the images for each state of the ApplicationMenu; drop in a button and assign the appropriate image ids for the button's regular, hilited and hover icons. This is not the time for the menu itself, though, so let's move on to the ribbon tabs.&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://1.bp.blogspot.com/-R3MEeQP5n-M/Tjhn77130kI/AAAAAAAAAFE/ybmOpp6EoAs/s1600/qrtrex_tmpl_3.png"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 303px; height: 320px;" src="http://1.bp.blogspot.com/-R3MEeQP5n-M/Tjhn77130kI/AAAAAAAAAFE/ybmOpp6EoAs/s320/qrtrex_tmpl_3.png" border="0" alt=""id="BLOGGER_PHOTO_ID_5636369213027766850" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;But before that, let's drop in a field "RibbonText" and a button to apply it to our ribbon group. It will make testing a whole lot easier as we go and add features.&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://2.bp.blogspot.com/-RJf3jRhcfew/Tjhn72QI_GI/AAAAAAAAAFM/0Yhi3j4SVds/s1600/qrtrex_tmpl_4.png"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 303px; height: 320px;" src="http://2.bp.blogspot.com/-RJf3jRhcfew/Tjhn72QI_GI/AAAAAAAAAFM/0Yhi3j4SVds/s320/qrtrex_tmpl_4.png" border="0" alt=""id="BLOGGER_PHOTO_ID_5636369211527330914" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight:bold;"&gt;Pop quiz&lt;/span&gt;: what is the fastest way to create a custom tab header in LiveCode?&lt;br /&gt;&lt;span style="font-weight:bold;"&gt;Answer&lt;/span&gt;: why, a radio button group, of course!&lt;br /&gt;&lt;br /&gt;I know what you're thinking: "Hold on, have you lost your marbles? Doesn't a group of radio buttons look like this?"&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://1.bp.blogspot.com/-mAigAOAYMgM/TjhmRF2SJdI/AAAAAAAAAEs/Usw_36-Z3bU/s1600/radbtn_grp_1.png"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 320px; height: 86px;" src="http://1.bp.blogspot.com/-mAigAOAYMgM/TjhmRF2SJdI/AAAAAAAAAEs/Usw_36-Z3bU/s320/radbtn_grp_1.png" border="0" alt=""id="BLOGGER_PHOTO_ID_5636367377467844050" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;Well, sure - but by setting a few properties, you can turn that into &lt;span style="font-style:italic;"&gt;this&lt;/span&gt;:&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://2.bp.blogspot.com/-2fMdE2igAWI/TjhmRFUzJGI/AAAAAAAAAE0/I8zS07-kCmE/s1600/radbtn_grp_2.png"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 320px; height: 86px;" src="http://2.bp.blogspot.com/-2fMdE2igAWI/TjhmRFUzJGI/AAAAAAAAAE0/I8zS07-kCmE/s320/radbtn_grp_2.png" border="0" alt=""id="BLOGGER_PHOTO_ID_5636367377327400034" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;And that's accomplished without any scripting; once you start handling mouse event messages, you can dynamically apply graphic effects to even greater effect. But that won't suffice for our ribbon, I'm afraid, altough the principle stands: a group of 'radio buttons' can adequately implement our ribbon tabs, as long as they don't have to do it on their own...&lt;br /&gt;&lt;br /&gt;There are several factors that determine a ribbon tab's appearance: whether or not the tab is hilited, whether or not the mouse is hovering over the tab, and whether the mouse is pressed or not. So we will enlist the help of some image controls to achieve each of these appearances.&lt;br /&gt;&lt;br /&gt;Once again, we use an image editor to take three slices from a screenshot: one for the left edge, one for the right edge, and another to serve as a background, stretched accross the gap. Rinse and repeat for each appearance. The width of the tab can be calculated by taking the formattedWidth of the button, and adding another eighteen pixels, nine on each side, for good measure.&lt;br /&gt;&lt;br /&gt;With our approach clearly outlined, we can code the creation of all the controls needed for the ribbon tabs, one tab at a time: first we create the three images for the tab, and then a heavily modified radio button; next, we determine the correct rectangle of our tab button, and adapt the rectangles of our three backing images to match; finally, we inject copies of the appropriate original images; and all the layers are as we would expect.&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://4.bp.blogspot.com/-OOXxMoUdiG4/TjhpNHDdR2I/AAAAAAAAAFU/Ap8T-CsW_H8/s1600/qrtrex_tmpl_5.png"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 303px; height: 320px;" src="http://4.bp.blogspot.com/-OOXxMoUdiG4/TjhpNHDdR2I/AAAAAAAAAFU/Ap8T-CsW_H8/s320/qrtrex_tmpl_5.png" border="0" alt=""id="BLOGGER_PHOTO_ID_5636370607606941538" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;As soon as we have the tab creation working correctly, we can code the necessary engine event message handlers, property getters and setters, as well as a splash of private commands to make the ribbon tabs act like the real deal. I'd rather not paste the code into this blog post - just download the current revision &lt;a href="http://downloads.quartam.com/qrtrex.20110802.zip"&gt;here&lt;/a&gt; and take a good look at the scripts in the qrtRexBehaviors substack.&lt;br /&gt;&lt;br /&gt;Next time, we'll tackle the creation of groups - and the weather is still a bit of a mess, so check back soon!&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5092807049363133934-2640596849641183452?l=quartam.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://quartam.blogspot.com/feeds/2640596849641183452/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=5092807049363133934&amp;postID=2640596849641183452' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5092807049363133934/posts/default/2640596849641183452'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5092807049363133934/posts/default/2640596849641183452'/><link rel='alternate' type='text/html' href='http://quartam.blogspot.com/2011/08/experimenting-with-ribbons-in-livecode.html' title='Experimenting with ribbons in LiveCode (part 3)'/><author><name>Quartam Software - Jan Schenkel</name><uri>http://www.blogger.com/profile/08312925714131118786</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://1.bp.blogspot.com/-X3thGhmPswQ/Tjhn7jcw0uI/AAAAAAAAAE8/XbONBhkOQU0/s72-c/qrtrex_tmpl_2.png' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5092807049363133934.post-7347999163443899892</id><published>2011-07-24T12:14:00.000-07:00</published><updated>2011-07-24T13:32:33.457-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='ribbon'/><category scheme='http://www.blogger.com/atom/ns#' term='livecode'/><category scheme='http://www.blogger.com/atom/ns#' term='example code'/><category scheme='http://www.blogger.com/atom/ns#' term='experiment'/><title type='text'>Experimenting with ribbons in LiveCode (part 2)</title><content type='html'>In an &lt;a href="http://quartam.blogspot.com/2011/07/experimenting-with-ribbons-in-livecode.html"&gt;earlier post&lt;/a&gt;, we kicked off a series on the construction of a ribbon using pure LiveCode. For now, we'll concentrate on the Windows 7 Wordpad look-and-feel, but future posts will refine it for cross-platform consistency. After looking into Ribbon components such as &lt;a href="http://www.infragistics.com/"&gt;Infragistics NetAdvantage WinToolbars&lt;/a&gt; and &lt;a href="http://www.microsoft.com/download/en/details.aspx?id=11877"&gt;Microsoft Ribbon for WPF&lt;/a&gt;, I pondered how a developer would define and customize our ribbon implementation.&lt;br /&gt;&lt;br /&gt;For better or worse, I opted for the developer setting the 'ribbonText' property of our custom control group, and that this property would be an XML structure. So let's break the ribbon down into pieces, break those pieces down into other pieces, and so on. Initially, one would think that the ribbon is a set of tabs, each with their own groups, each with their own buttons. But there's a little more to the story: there are several types of buttons, and aren't we glossing over the application menu and quick access toolbar?&lt;br /&gt;Plus, we need some centralized management for what these buttons, menuitems, etc. look like, what label they have, etc. Microsoft calls it a 'RibbonCommand', but since theseribbon items could also be toggle buttons, that spounded odd - so I think we'll stick with the Infragistics terminology of 'Tool' - far more neutral. This tool will also include a 'message' that is sent when the user clicks the button.&lt;br /&gt;&lt;br /&gt;So here's a first example of our ribbonText XML structure:&lt;br /&gt;&lt;pre language="xml"&gt;&lt;br /&gt;&amp;lt;?xml version=&amp;quot;1.0&amp;quot;?&amp;gt;&lt;br /&gt;&amp;lt;Ribbon&amp;gt;&lt;br /&gt;  &amp;lt;Tools&amp;gt;&lt;br /&gt;    &amp;lt;Tool ToolId=&amp;quot;CutTool&amp;quot; Label=&amp;quot;Cut&amp;quot; Message=&amp;quot;CutText&amp;quot;/&amp;gt;&lt;br /&gt;    &amp;lt;Tool ToolId=&amp;quot;CopyTool&amp;quot; Label=&amp;quot;Copy&amp;quot; Message=&amp;quot;CopyText&amp;quot;/&amp;gt;&lt;br /&gt;  &amp;lt;/Tools&amp;gt;&lt;br /&gt;  &amp;lt;Tabs&amp;gt;&lt;br /&gt;    &amp;lt;Tab TabId=&amp;quot;HomeTab&amp;quot; Label=&amp;quot;Home&amp;quot;&amp;gt;&lt;br /&gt;      &amp;lt;Groups&amp;gt;&lt;br /&gt;        &amp;lt;Group GroupId=&amp;quot;ClipboardGroup&amp;quot; Label=&amp;quot;Clipboard&amp;quot;&amp;gt;&lt;br /&gt;          &amp;lt;GroupItems&amp;gt;&lt;br /&gt;            &amp;lt;CommandButton ToolId=&amp;quot;CutTool&amp;quot; /&amp;gt;&lt;br /&gt;            &amp;lt;CommandButton ToolId=&amp;quot;CopyTool&amp;quot; /&amp;gt;&lt;br /&gt;          &amp;lt;/GroupItems&amp;gt;&lt;br /&gt;        &amp;lt;/Group&amp;gt;&lt;br /&gt;      &amp;lt;/Groups&amp;gt;&lt;br /&gt;    &amp;lt;/Tab&amp;gt;&lt;br /&gt;  &amp;lt;/Tabs&amp;gt;&lt;br /&gt;&amp;lt;/Ribbon&amp;gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;As you can see, the Tool entry defines the label and message, and the CommandButton instance referenes that Tool via its ToolId attribute. Now let's add two more things to the mix: DropMenuButton and SplitMenuButton - clicking on the former will drop down a menu, whereas clicking the latter has different results, depending on where you click: it either executes the main function, or drops down a menu.&lt;br /&gt;&lt;pre language="xml"&gt;&lt;br /&gt;&amp;lt;?xml version=&amp;quot;1.0&amp;quot;?&amp;gt;&lt;br /&gt;&amp;lt;Ribbon&amp;gt;&lt;br /&gt;  &amp;lt;Tools&amp;gt;&lt;br /&gt;    &amp;lt;Tool ToolId=&amp;quot;CutTool&amp;quot; Label=&amp;quot;Cut&amp;quot; Message=&amp;quot;CutText&amp;quot;/&amp;gt;&lt;br /&gt;    &amp;lt;Tool ToolId=&amp;quot;CopyTool&amp;quot; Label=&amp;quot;Copy&amp;quot; Message=&amp;quot;CopyText&amp;quot;/&amp;gt;&lt;br /&gt;    &amp;lt;Tool ToolId=&amp;quot;PasteTool&amp;quot; Label=&amp;quot;Paste&amp;quot; Message=&amp;quot;PasteText&amp;quot;/&amp;gt;&lt;br /&gt;    &amp;lt;Tool ToolId=&amp;quot;PasteSpecialTool&amp;quot; Label=&amp;quot;Paste special&amp;quot; Message=&amp;quot;PasteSpecial&amp;quot;/&amp;gt;&lt;br /&gt;    &amp;lt;Tool ToolId=&amp;quot;InchesTool&amp;quot; Label=&amp;quot;Inches&amp;quot; Message=&amp;quot;SwitchToInches&amp;quot;/&amp;gt;&lt;br /&gt;    &amp;lt;Tool ToolId=&amp;quot;CentimetersTool&amp;quot; Label=&amp;quot;Centimeters&amp;quot; Message=&amp;quot;SwitchToCentimeters&amp;quot;/&amp;gt;&lt;br /&gt;    &amp;lt;Tool ToolId=&amp;quot;PointsTool&amp;quot; Label=&amp;quot;Points&amp;quot; Message=&amp;quot;SwitchToPoints&amp;quot;/&amp;gt;&lt;br /&gt;  &amp;lt;/Tools&amp;gt;&lt;br /&gt;  &amp;lt;Tabs&amp;gt;&lt;br /&gt;    &amp;lt;Tab TabId=&amp;quot;HomeTab&amp;quot; Label=&amp;quot;Home&amp;quot;&amp;gt;&lt;br /&gt;      &amp;lt;Groups&amp;gt;&lt;br /&gt;        &amp;lt;Group GroupId=&amp;quot;ClipboardGroup&amp;quot; Label=&amp;quot;Clipboard&amp;quot;&amp;gt;&lt;br /&gt;          &amp;lt;GroupItems&amp;gt;&lt;br /&gt;            &amp;lt;SplitMenuButton ToolId=&amp;quot;PasteTool&amp;quot;&amp;gt;&lt;br /&gt;              &amp;lt;MenuItems&amp;gt;&lt;br /&gt;                &amp;lt;CommandButton ToolId=&amp;quot;PasteTool&amp;quot; /&amp;gt;&lt;br /&gt;                &amp;lt;CommandButton ToolId=&amp;quot;PasteSpecialTool&amp;quot; /&amp;gt;&lt;br /&gt;              &amp;lt;/MenuItems&amp;gt;&lt;br /&gt;            &amp;lt;/SplitMenuButton&amp;gt;&lt;br /&gt;            &amp;lt;CommandButton ToolId=&amp;quot;CutTool&amp;quot; /&amp;gt;&lt;br /&gt;            &amp;lt;CommandButton ToolId=&amp;quot;CopyTool&amp;quot; /&amp;gt;&lt;br /&gt;          &amp;lt;/GroupItems&amp;gt;&lt;br /&gt;        &amp;lt;/Group&amp;gt;&lt;br /&gt;      &amp;lt;/Groups&amp;gt;&lt;br /&gt;    &amp;lt;/Tab&amp;gt;&lt;br /&gt;    &amp;lt;Tab TabId=&amp;quot;ViewTab&amp;quot; Label=&amp;quot;View&amp;quot;&amp;gt;&lt;br /&gt;      &amp;lt;Groups&amp;gt;&lt;br /&gt;        &amp;lt;Group GroupId=&amp;quot;SettingsGroup&amp;quot; Label=&amp;quot;Settings&amp;quot;&amp;gt;&lt;br /&gt;          &amp;lt;GroupItems&amp;gt;&lt;br /&gt;            &amp;lt;DropMenuButton Label=&amp;quot;Measurement units&amp;quot;&amp;gt;&lt;br /&gt;              &amp;lt;MenuItems&amp;gt;&lt;br /&gt;                &amp;lt;CommandButton ToolId=&amp;quot;InchesTool&amp;quot; /&amp;gt;&lt;br /&gt;                &amp;lt;CommandButton ToolId=&amp;quot;CentimetersTool&amp;quot; /&amp;gt;&lt;br /&gt;                &amp;lt;CommandButton ToolId=&amp;quot;PointsTool&amp;quot; /&amp;gt;&lt;br /&gt;              &amp;lt;/MenuItems&amp;gt;&lt;br /&gt;            &amp;lt;/DropMenuButton&amp;gt;&lt;br /&gt;          &amp;lt;/GroupItems&amp;gt;&lt;br /&gt;        &amp;lt;/Group&amp;gt;&lt;br /&gt;      &amp;lt;/Groups&amp;gt;&lt;br /&gt;    &amp;lt;/Tab&amp;gt;&lt;br /&gt;  &amp;lt;/Tabs&amp;gt;&lt;br /&gt;&amp;lt;/Ribbon&amp;gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;I think this will do as a first draft of the ribbonText XML structure. Of course, an XML structure needs a good document type definition to verify that what is coming in is valid data; and since the XML structure is still evolving, we should define some specification version into the structure. So let's call the current structure SpecificationVersion 0.1 - and here's the DTD:&lt;br /&gt;&lt;pre language="dtd"&gt;&lt;br /&gt;&amp;lt;!ELEMENT Ribbon &lt;br /&gt;          (Tools,Tabs)&amp;gt;&lt;br /&gt;&amp;lt;!ELEMENT Tools (Tool)+&amp;gt;&lt;br /&gt;&amp;lt;!ELEMENT Tool EMPTY&amp;gt;&lt;br /&gt;&amp;lt;!ELEMENT Tabs (Tab)+&amp;gt;&lt;br /&gt;&amp;lt;!ELEMENT Tab (Groups)&amp;gt;&lt;br /&gt;&amp;lt;!ELEMENT Groups (Group)+&amp;gt;&lt;br /&gt;&amp;lt;!ELEMENT Group (GroupItems)&amp;gt;&lt;br /&gt;&amp;lt;!ELEMENT GroupItems &lt;br /&gt;          (CommandButton|SplitMenuButton|DropMenuButton)*&amp;gt;&lt;br /&gt;&amp;lt;!ELEMENT CommandButton EMPTY&amp;gt;&lt;br /&gt;&amp;lt;!ELEMENT ToggleButton EMPTY&amp;gt;&lt;br /&gt;&amp;lt;!ELEMENT SplitMenuButton (MenuItems)&amp;gt;&lt;br /&gt;&amp;lt;!ELEMENT DropMenuButton (MenuItems)&amp;gt;&lt;br /&gt;&amp;lt;!ELEMENT MenuItems&lt;br /&gt;          (CommandButton|SplitMenuButton|DropMenuButton)*&amp;gt;&lt;br /&gt;&amp;lt;!ATTLIST Ribbon &lt;br /&gt;          SpecificationVersion CDATA #FIXED &amp;quot;0.1&amp;quot;&amp;gt;&lt;br /&gt;&amp;lt;!ATTLIST Tool &lt;br /&gt;          ToolId ID #REQUIRED&lt;br /&gt;          Label CDATA #REQUIRED&lt;br /&gt;          Message CDATA #IMPLIED&lt;br /&gt;          SmallIcon CDATA #IMPLIED&lt;br /&gt;          LargeIcon CDATA #IMPLIED&amp;gt;&lt;br /&gt;&amp;lt;!ATTLIST Tab &lt;br /&gt;          TabId ID #REQUIRED&lt;br /&gt;          Label CDATA #REQUIRED&amp;gt;&lt;br /&gt;&amp;lt;!ATTLIST Group &lt;br /&gt;          GroupId ID #REQUIRED&lt;br /&gt;          Label CDATA #REQUIRED&amp;gt;&lt;br /&gt;&amp;lt;!ATTLIST CommandButton&lt;br /&gt;          ToolId IDREF #REQUIRED&amp;gt;&lt;br /&gt;&amp;lt;!ATTLIST SplitMenuButton&lt;br /&gt;          ToolId IDREF #REQUIRED&amp;gt;&lt;br /&gt;&amp;lt;!ATTLIST DropMenuButton&lt;br /&gt;          Label CDATA #REQUIRED&amp;gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Since we added a required attribute to the Ribbon root tag, our example XML needs to be slightly amended to:&lt;br /&gt;&lt;pre lkanguage="xml"&gt;&lt;br /&gt;&amp;lt;?xml version=&amp;quot;1.0&amp;quot;?&amp;gt;&lt;br /&gt;&amp;lt;Ribbon SpecificationVersion=&amp;quot;0.1&amp;quot;&amp;gt;&lt;br /&gt;  &amp;lt;Tools&amp;gt;&lt;br /&gt;    &amp;lt;Tool ToolId=&amp;quot;CutTool&amp;quot; Label=&amp;quot;Cut&amp;quot; Message=&amp;quot;CutText&amp;quot;/&amp;gt;&lt;br /&gt;    &amp;lt;Tool ToolId=&amp;quot;CopyTool&amp;quot; Label=&amp;quot;Copy&amp;quot; Message=&amp;quot;CopyText&amp;quot;/&amp;gt;&lt;br /&gt;    &amp;lt;Tool ToolId=&amp;quot;PasteTool&amp;quot; Label=&amp;quot;Paste&amp;quot; Message=&amp;quot;PasteText&amp;quot;/&amp;gt;&lt;br /&gt;    &amp;lt;Tool ToolId=&amp;quot;PasteSpecialTool&amp;quot; Label=&amp;quot;Paste special&amp;quot; Message=&amp;quot;PasteSpecial&amp;quot;/&amp;gt;&lt;br /&gt;    &amp;lt;Tool ToolId=&amp;quot;InchesTool&amp;quot; Label=&amp;quot;Inches&amp;quot; Message=&amp;quot;SwitchToInches&amp;quot;/&amp;gt;&lt;br /&gt;    &amp;lt;Tool ToolId=&amp;quot;CentimetersTool&amp;quot; Label=&amp;quot;Centimeters&amp;quot; Message=&amp;quot;SwitchToCentimeters&amp;quot;/&amp;gt;&lt;br /&gt;    &amp;lt;Tool ToolId=&amp;quot;PointsTool&amp;quot; Label=&amp;quot;Points&amp;quot; Message=&amp;quot;SwitchToPoints&amp;quot;/&amp;gt;&lt;br /&gt;  &amp;lt;/Tools&amp;gt;&lt;br /&gt;  &amp;lt;Tabs&amp;gt;&lt;br /&gt;    &amp;lt;Tab TabId=&amp;quot;HomeTab&amp;quot; Label=&amp;quot;Home&amp;quot;&amp;gt;&lt;br /&gt;      &amp;lt;Groups&amp;gt;&lt;br /&gt;        &amp;lt;Group GroupId=&amp;quot;ClipboardGroup&amp;quot; Label=&amp;quot;Clipboard&amp;quot;&amp;gt;&lt;br /&gt;          &amp;lt;GroupItems&amp;gt;&lt;br /&gt;            &amp;lt;SplitMenuButton ToolId=&amp;quot;PasteTool&amp;quot;&amp;gt;&lt;br /&gt;              &amp;lt;MenuItems&amp;gt;&lt;br /&gt;                &amp;lt;CommandButton ToolId=&amp;quot;PasteTool&amp;quot; /&amp;gt;&lt;br /&gt;                &amp;lt;CommandButton ToolId=&amp;quot;PasteSpecialTool&amp;quot; /&amp;gt;&lt;br /&gt;              &amp;lt;/MenuItems&amp;gt;&lt;br /&gt;            &amp;lt;/SplitMenuButton&amp;gt;&lt;br /&gt;            &amp;lt;CommandButton ToolId=&amp;quot;CutTool&amp;quot; /&amp;gt;&lt;br /&gt;            &amp;lt;CommandButton ToolId=&amp;quot;CopyTool&amp;quot; /&amp;gt;&lt;br /&gt;          &amp;lt;/GroupItems&amp;gt;&lt;br /&gt;        &amp;lt;/Group&amp;gt;&lt;br /&gt;      &amp;lt;/Groups&amp;gt;&lt;br /&gt;    &amp;lt;/Tab&amp;gt;&lt;br /&gt;    &amp;lt;Tab TabId=&amp;quot;ViewTab&amp;quot; Label=&amp;quot;View&amp;quot;&amp;gt;&lt;br /&gt;      &amp;lt;Groups&amp;gt;&lt;br /&gt;        &amp;lt;Group GroupId=&amp;quot;SettingsGroup&amp;quot; Label=&amp;quot;Settings&amp;quot;&amp;gt;&lt;br /&gt;          &amp;lt;GroupItems&amp;gt;&lt;br /&gt;            &amp;lt;DropMenuButton Label=&amp;quot;Measurement units&amp;quot;&amp;gt;&lt;br /&gt;              &amp;lt;MenuItems&amp;gt;&lt;br /&gt;                &amp;lt;CommandButton ToolId=&amp;quot;InchesTool&amp;quot; /&amp;gt;&lt;br /&gt;                &amp;lt;CommandButton ToolId=&amp;quot;CentimetersTool&amp;quot; /&amp;gt;&lt;br /&gt;                &amp;lt;CommandButton ToolId=&amp;quot;PointsTool&amp;quot; /&amp;gt;&lt;br /&gt;              &amp;lt;/MenuItems&amp;gt;&lt;br /&gt;            &amp;lt;/DropMenuButton&amp;gt;&lt;br /&gt;          &amp;lt;/GroupItems&amp;gt;&lt;br /&gt;        &amp;lt;/Group&amp;gt;&lt;br /&gt;      &amp;lt;/Groups&amp;gt;&lt;br /&gt;    &amp;lt;/Tab&amp;gt;&lt;br /&gt;  &amp;lt;/Tabs&amp;gt;&lt;br /&gt;&amp;lt;/Ribbon&amp;gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Next time, we'll work on actually turning such definitions into actual controls in our LiveCode custom control group, so stay tuned!&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5092807049363133934-7347999163443899892?l=quartam.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://quartam.blogspot.com/feeds/7347999163443899892/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=5092807049363133934&amp;postID=7347999163443899892' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5092807049363133934/posts/default/7347999163443899892'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5092807049363133934/posts/default/7347999163443899892'/><link rel='alternate' type='text/html' href='http://quartam.blogspot.com/2011/07/experimenting-with-ribbons-in-livecode_24.html' title='Experimenting with ribbons in LiveCode (part 2)'/><author><name>Quartam Software - Jan Schenkel</name><uri>http://www.blogger.com/profile/08312925714131118786</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5092807049363133934.post-758813051183372952</id><published>2011-07-10T09:42:00.000-07:00</published><updated>2011-07-10T10:05:42.138-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='ribbon'/><category scheme='http://www.blogger.com/atom/ns#' term='livecode'/><category scheme='http://www.blogger.com/atom/ns#' term='example code'/><category scheme='http://www.blogger.com/atom/ns#' term='experiment'/><title type='text'>Experimenting with ribbons in LiveCode (part 1)</title><content type='html'>In the &lt;a href="http://quartam.blogspot.com/2011/07/experimenting-with-ribbons-in-livecode.html"&gt;previous post&lt;/a&gt;, we kicked off a series on the construction of a ribbon using pure LiveCode. For now, we'll concentrate on the Windows 7 Wordpad look-and-feel, but future posts will refine it for cross-platform consistency. Let's get started by creating a new stack "qrtRibbonExperiment" in LiveCode, with two substacks "qrtRexBehaviors" and "qrtRexTemplate" - the mainstack will be a launchpad whereas the substacks will each provide support for the experiment.&lt;br /&gt;&lt;br /&gt;The stack qrtRexBehaviors will contain all the behavior scripts as a series of buttons. The stack qrtRexTemplate will be used to as a template to 'clone' isntance stacks from. This way, you have a skeleton of a document editor to learn from as well.&lt;br /&gt;&lt;br /&gt;Peeking ahead at the actual ribbon custom control itself, we note that we'll need 5 areas, which we'll implement as subgroups, each with their own behavior script:&lt;br /&gt;- the application menu in the topleft&lt;br /&gt;- the tab items in the topright (minimize and/or help/customize)&lt;br /&gt;- the ribbon tabs between these two blocks at the top&lt;br /&gt;- the ribbon groups in the center&lt;br /&gt;- the quick access toolbar at the bottom&lt;br /&gt;&lt;br /&gt;First things first - the main stack:&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://2.bp.blogspot.com/-787AIDjQKww/ThnYMmhgH7I/AAAAAAAAAEc/w_QThWk-LCk/s1600/qrtrex_main_1.png"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 320px; height: 237px;" src="http://2.bp.blogspot.com/-787AIDjQKww/ThnYMmhgH7I/AAAAAAAAAEc/w_QThWk-LCk/s320/qrtrex_main_1.png" border="0" alt=""id="BLOGGER_PHOTO_ID_5627766920387239858" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;It contains three simple buttons and some information about this experiment. Nothing spectacular there, so we'll move along to the behaviors stack:&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://3.bp.blogspot.com/-AvFzYFanUs8/ThnYMRb2VOI/AAAAAAAAAEU/ym8b2CJGFBY/s1600/qrtrex_bhvr_1.png"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 304px; height: 320px;" src="http://3.bp.blogspot.com/-AvFzYFanUs8/ThnYMRb2VOI/AAAAAAAAAEU/ym8b2CJGFBY/s320/qrtrex_bhvr_1.png" border="0" alt=""id="BLOGGER_PHOTO_ID_5627766914726384866" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;As you can see, it contains seven buttons right now, aptly named after the different items they provide the behavior for. The number of buttons will expand as we add moire features, but this will do for now. Open each of the button scripts for editing and move on to the template stack.&lt;br /&gt;&lt;br /&gt;We'll start by creating the menubar for the stack - even though it's only for Mac, it pays off to put it in straight away. Just go to the 'Tools' menu, and select the option 'Menu Builder' ; then click on the 'New...' button to create a menubar called 'qrtRexMenubar' ; not feeling particularly adventurous, we'll use the prefab set for now.&lt;br /&gt;To make the next step easier, uncheck the 'Set as stack Menu bar' box. Now close the menu builder, and you'll see the menubar as part of your stack. When you set the menubar property of the stack, the stack content moves upward, and that's why I want to help you visualize where the controls end up. The menubar group should have its margins set to 4 and thus have a bottom of 22 - which wiill be our anchor point for the ribbon custom control group.&lt;br /&gt;&lt;br /&gt;So now we'll add a rectangle graphic as background for our ribbon - just draw one, and set its rectangle using the property inspector to: -1, 21, 401, 161. Set its name to 'Ribbon_Background', make it opaque and set its fill color to a light gray (I picked 'Silver' from the Mac crayons color set).&lt;br /&gt;&lt;br /&gt;Next we'll add the background rectangle graphics for each of the ribbon areas:&lt;br /&gt;- ApplicationMenu_Background -&gt; rect: -1, 21, 70, 46 | color: salmon&lt;br /&gt;- TabHeaderItems_Background -&gt; rect: 350, 21, 401, 46 | color: banana&lt;br /&gt;- RibbonTabs_Background -&gt; rect: 70, 21, 350, 46 | color: honeydew&lt;br /&gt;- RibbonGroups_Background -&gt; rect: -1, 46, 401, 137 | color: lavender&lt;br /&gt;- QuickAccessToolbar_Background -&gt; rect: -1, 137, 401, 163 | color: melon&lt;br /&gt;&lt;br /&gt;In case you're wondering about the colors: of course they don't match the ribbon color scheme - but they'll help visualize errors in our resizing scripts. But before we start scripting, let's create the necessary custom control groups out of these background graphics.&lt;br /&gt;Once you've grouped the graphic, set the containing group's lockLocation property to true and change its rectangle to clip to the edges of the window&lt;br /&gt;- ApplicationMenu_Background -&gt; group: ApplicationMenu | rect: 0, 21, 70, 46&lt;br /&gt;- TabHeaderItems_Background -&gt; group: TabHeaderItems | rect: 350, 21, 400, 46&lt;br /&gt;- RibbonTabs_Background -&gt; group: RibbonTabs | rect: 70, 21, 350, 46&lt;br /&gt;- RibbonGroups_Background -&gt; group: RibbonTabs | rect: 0, 46, 400, 137&lt;br /&gt;- QuickAccessToolbar_Background -&gt; group: QuickAccessToolbar | rect: 0, 137, 400, 163&lt;br /&gt;&lt;br /&gt;Then take those five groups, and the rectangle graphic 'Ribbon_Background', and group everything once more; name that outer group 'Ribbon' and set its margins to 0 as well, before settings its lockLocation to true and its rectangle to 0, 21, 400, 163.&lt;br /&gt;We end up with something like this:&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://2.bp.blogspot.com/-c0gCKPoAwZA/ThnYN7Q3soI/AAAAAAAAAEk/FO7yTimRGo8/s1600/qrtrex_tmpl_1.png"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 290px; height: 320px;" src="http://2.bp.blogspot.com/-c0gCKPoAwZA/ThnYN7Q3soI/AAAAAAAAAEk/FO7yTimRGo8/s320/qrtrex_tmpl_1.png" border="0" alt=""id="BLOGGER_PHOTO_ID_5627766943134495362" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;Now we can start setting the behaviors and do some scripting at last. Open the message box, and execute the following seven lines one by one:&lt;br /&gt;&lt;pre language="LiveCode"&gt;set the behavior of this stack to the long id of button "qrtRexStackBehavior" of stack "qrtRexBehaviors"&lt;br /&gt;set the behavior of group "Ribbon" to the long id of button "qrtRexRibbonBehavior" of stack "qrtRexBehaviors"&lt;br /&gt;set the behavior of group "ApplicationMenu" to the long id of button "qrtRexApplicationMenuBehavior" of stack "qrtRexBehaviors"&lt;br /&gt;set the behavior of group "TabHeaderItems" to the long id of button "qrtRexTabHeaderItemsBehavior" of stack "qrtRexBehaviors"&lt;br /&gt;set the behavior of group "RibbonTabs" to the long id of button "qrtRexRibbonTabsBehavior" of stack "qrtRexBehaviors"&lt;br /&gt;set the behavior of group "RibbonGroups" to the long id of button "qrtRexRibbonGroupsBehavior" of stack "qrtRexBehaviors"&lt;br /&gt;set the behavior of group "QuickAccessToolbar" to the long name of button "qrtRexQuickAccessToolbarBehavior" of stack "qrtRexBehaviors"&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;On to the script of the 'qrtRexStackBehavior' button:&lt;br /&gt;&lt;pre language="LiveCode"&gt;##&lt;br /&gt;on resizeStack pNewWidth, pNewHeight&lt;br /&gt;   local tRectangle&lt;br /&gt;   lock screen&lt;br /&gt;   --&gt; resize the ribbon group&lt;br /&gt;   put the rectangle of group "Ribbon" into tRectangle&lt;br /&gt;   put pNewWidth into item 3 of tRectangle&lt;br /&gt;   set the rectangle of group "Ribbon" to tRectangle&lt;br /&gt;   --&gt; pass the message&lt;br /&gt;   unlock screen&lt;br /&gt;   pass resizeStack&lt;br /&gt;end resizeStack&lt;br /&gt;##&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Next comes the script of the 'qrtRexRibbonBehavior' button:&lt;br /&gt;&lt;pre language="LiveCode"&gt;##&lt;br /&gt;on resizeControl&lt;br /&gt;   local tRectangle&lt;br /&gt;   lock screen&lt;br /&gt;   --&gt; resize the background graphic&lt;br /&gt;   put the rectangle of graphic "Ribbon_Background" of me into tRectangle&lt;br /&gt;   put (the width of me) + 1 into item 3 of tRectangle&lt;br /&gt;   set the rectangle of graphic "Ribbon_Background" of me to tRectangle&lt;br /&gt;   --&gt; move the tab header items group&lt;br /&gt;   set the right of group "TabHeaderItems" of me to the right of me&lt;br /&gt;   --&gt; resize the ribbon tabs group&lt;br /&gt;   put the rectangle of group "RibbonTabs" of me into tRectangle&lt;br /&gt;   put the left of group "TabHeaderItems" of me into item 3 of tRectangle&lt;br /&gt;   set the rectangle of group "RibbonTabs" of me to tRectangle&lt;br /&gt;   --&gt; resize the ribbon groups group&lt;br /&gt;   put the rectangle of group "RibbonGroups" of me into tRectangle&lt;br /&gt;   put the width of me into item 3 of tRectangle&lt;br /&gt;   set the rectangle of group "RibbonGroups" of me to tRectangle&lt;br /&gt;   --&gt; resize the quick access toolbar group&lt;br /&gt;   put the rectangle of group "QuickAccessToolbar" of me into tRectangle&lt;br /&gt;   put the width of me into item 3 of tRectangle&lt;br /&gt;   set the rectangle of group "QuickAccessToolbar" of me to tRectangle&lt;br /&gt;   --&gt; pass the message&lt;br /&gt;   unlock screen&lt;br /&gt;   pass resizeControl&lt;br /&gt;end resizeControl&lt;br /&gt;##&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Next comes the script of the 'qrtRexApplicationMenuBehavior' button:&lt;br /&gt;&lt;pre language="LiveCode"&gt;##&lt;br /&gt;--&gt; TODO&lt;br /&gt;##&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Next comes the script of the 'qrtRexTabHeaderItemsBehavior' button:&lt;br /&gt;&lt;pre language="LiveCode"&gt;##&lt;br /&gt;--&gt; TODO&lt;br /&gt;##&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Next comes the script of the 'qrtRexRibbonTabsBehavior' button:&lt;br /&gt;&lt;pre language="LiveCode"&gt;##&lt;br /&gt;on resizeControl&lt;br /&gt;   local tRectangle&lt;br /&gt;   --&gt; resize the background graphic&lt;br /&gt;   put the rectangle of graphic "RibbonTabs_Background" of me into tRectangle&lt;br /&gt;   put (the width of me) + 1 into item 3 of tRectangle&lt;br /&gt;   set the rectangle of graphic "RibbonTabs_Background" of me to tRectangle&lt;br /&gt;   --&gt; TODO: whatever needs to happen to the group content&lt;br /&gt;   --&gt; do not pass the message&lt;br /&gt;end resizeControl&lt;br /&gt;##&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Next comes the script of the 'qrtRexRibbonTabsBehavior' button:&lt;br /&gt;&lt;pre language="LiveCode"&gt;##&lt;br /&gt;on resizeControl&lt;br /&gt;   local tRectangle&lt;br /&gt;   --&gt; resize the background graphic&lt;br /&gt;   put the rectangle of graphic "RibbonTabs_Background" of me into tRectangle&lt;br /&gt;   put the right of me into item 3 of tRectangle&lt;br /&gt;   set the rectangle of graphic "RibbonTabs_Background" of me to tRectangle&lt;br /&gt;   --&gt; TODO: whatever needs to happen to the group content&lt;br /&gt;   --&gt; do not pass the message&lt;br /&gt;end resizeControl&lt;br /&gt;##&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Next comes the script of the 'qrtRexRibbonGroupsBehavior' button:&lt;br /&gt;&lt;pre language="LiveCode"&gt;##&lt;br /&gt;on resizeControl&lt;br /&gt;   local tRectangle&lt;br /&gt;   --&gt; resize the background graphic&lt;br /&gt;   put the rectangle of graphic "RibbonGroups_Background" of me into tRectangle&lt;br /&gt;   put (the width of me) + 1 into item 3 of tRectangle&lt;br /&gt;   set the rectangle of graphic "RibbonGroups_Background" of me to tRectangle&lt;br /&gt;   --&gt; TODO: whatever needs to happen to the group content&lt;br /&gt;   --&gt; do not pass the message&lt;br /&gt;end resizeControl&lt;br /&gt;##&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Finally comes the script of the 'qrtRexQuickAccessToolbar' button&lt;br /&gt;&lt;pre language="LiveCode"&gt;##&lt;br /&gt;on resizeControl&lt;br /&gt;   local tRectangle&lt;br /&gt;   --&gt; resize the background graphic&lt;br /&gt;   put the rectangle of graphic "QuickAccessToolbar_Background" of me into tRectangle&lt;br /&gt;   put (the width of me) + 1 into item 3 of tRectangle&lt;br /&gt;   set the rectangle of graphic "QuickAccessToolbar_Background" of me to tRectangle&lt;br /&gt;   --&gt; TODO: whatever needs to happen to the group content&lt;br /&gt;   --&gt; do not pass the message&lt;br /&gt;end resizeControl&lt;br /&gt;##&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Don't worry, there will be more to those scripts soon - bot for now this gives us a set of groups that resize correctly as the stack is resized. You have to start somewhere :-)&lt;br /&gt;&lt;br /&gt;In the next installment, we'll build on this foundation by introducing the 'ribbonText' property - an XML structure that describes the content of the ribbon. In the meantime, you can download the first draft of our ribbon experiment &lt;a href="http://downloads.quartam.com/qrtrex.20110710.zip"&gt;here&lt;/a&gt;.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5092807049363133934-758813051183372952?l=quartam.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://quartam.blogspot.com/feeds/758813051183372952/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=5092807049363133934&amp;postID=758813051183372952' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5092807049363133934/posts/default/758813051183372952'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5092807049363133934/posts/default/758813051183372952'/><link rel='alternate' type='text/html' href='http://quartam.blogspot.com/2011/07/experimenting-with-ribbons-in-livecode_10.html' title='Experimenting with ribbons in LiveCode (part 1)'/><author><name>Quartam Software - Jan Schenkel</name><uri>http://www.blogger.com/profile/08312925714131118786</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://2.bp.blogspot.com/-787AIDjQKww/ThnYMmhgH7I/AAAAAAAAAEc/w_QThWk-LCk/s72-c/qrtrex_main_1.png' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5092807049363133934.post-870328593751497384</id><published>2011-07-10T09:06:00.000-07:00</published><updated>2011-07-10T10:03:23.791-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='ribbon'/><category scheme='http://www.blogger.com/atom/ns#' term='livecode'/><category scheme='http://www.blogger.com/atom/ns#' term='example code'/><category scheme='http://www.blogger.com/atom/ns#' term='experiment'/><title type='text'>Experimenting with ribbons in LiveCode (prelude)</title><content type='html'>It's a rainy summer afternoon here in Belgium - what better time to pick up on an old experiment, and share the thought process with you, my fellow LiveCode developers? As you may have guessed from this post's subject, we'll be building a cross-platform Ribbon control using only LiveCode controls and behaviors.&lt;br /&gt;Ribbons were introduced by Microsoft in Office 2007, and replace the traditional menubar and toolbar combination with a single tabbed toolbar. The primary goal: provide a better structure for the myriad of available options, allowing the user to discover these options without becoming overwhelmed.&lt;br /&gt;&lt;br /&gt;It's something Microsoft developers have been acutely aware of for a long time, but their past forays into this type of user experience had been a mixed success. Take adaptive menus: I'm sure I wasn't the only one to loathe the automatic hiding of unused options that first appeared around 2000. &lt;br /&gt;Sure, there was a chevron widget to bring back the hidden options, but the whole approach was confusing as the user interface became unpredictable. Have you ever had to support an application where the user could completely customize the look-and-feel, including menus and toolbars? Then you know the sort of nightmare this can induce.&lt;br /&gt;&lt;br /&gt;Anyway, be sure to check out &lt;a href="http://blogs.msdn.com/b/jensenh/archive/2008/03/12/the-story-of-the-ribbon.aspx"&gt;The Story of the Ribbon&lt;/a&gt; by Jensen Harris, and his related series of blog entries on this topic: &lt;a href="http://blogs.msdn.com/b/jensenh/archive/tags/why+the+new+ui_3f00_/default.aspx"&gt;Why the UI?&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;Now let's take a look at some screenshots of the ribbon across different Microsoft applications. Here's the original Word 2007 edition:&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://4.bp.blogspot.com/-NJO9jxdcOD8/ThnT6C1oulI/AAAAAAAAADk/cDMxjgZhgi4/s1600/Word2007.png"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 320px; height: 48px;" src="http://4.bp.blogspot.com/-NJO9jxdcOD8/ThnT6C1oulI/AAAAAAAAADk/cDMxjgZhgi4/s320/Word2007.png" border="0" alt=""id="BLOGGER_PHOTO_ID_5627762203523856978" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;Followed by the latest Word 2010 edition:&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://4.bp.blogspot.com/-qnUEEqwMKuE/ThnT6YxnhKI/AAAAAAAAADs/WuiG1VHXI-0/s1600/Word2010.png"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 320px; height: 45px;" src="http://4.bp.blogspot.com/-qnUEEqwMKuE/ThnT6YxnhKI/AAAAAAAAADs/WuiG1VHXI-0/s320/Word2010.png" border="0" alt=""id="BLOGGER_PHOTO_ID_5627762209412580514" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;And here's WordPad as it first shipped with Windows 7:&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://1.bp.blogspot.com/-c12x11zDKFI/ThnT79AeU7I/AAAAAAAAAD0/SWic94SVonE/s1600/WordPadWindows7.png"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 320px; height: 50px;" src="http://1.bp.blogspot.com/-c12x11zDKFI/ThnT79AeU7I/AAAAAAAAAD0/SWic94SVonE/s320/WordPadWindows7.png" border="0" alt=""id="BLOGGER_PHOTO_ID_5627762236318438322" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;Finally, a screenshot of Word 2011 for Mac:&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://4.bp.blogspot.com/-V6kRQyaciCc/ThnT72-FicI/AAAAAAAAAD8/IzchIXTORfY/s1600/Word2011.png"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 320px; height: 55px;" src="http://4.bp.blogspot.com/-V6kRQyaciCc/ThnT72-FicI/AAAAAAAAAD8/IzchIXTORfY/s320/Word2011.png" border="0" alt=""id="BLOGGER_PHOTO_ID_5627762234697812418" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;But before we work on implementing it, what are the different parts of the ribbon?&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://4.bp.blogspot.com/-fZxwqCeeKqw/ThnT8Hs4R-I/AAAAAAAAAEE/fIfKsxvUuDg/s1600/RibbonParts.png"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 320px; height: 131px;" src="http://4.bp.blogspot.com/-fZxwqCeeKqw/ThnT8Hs4R-I/AAAAAAAAAEE/fIfKsxvUuDg/s320/RibbonParts.png" border="0" alt=""id="BLOGGER_PHOTO_ID_5627762239189043170" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;Hmm, looking at all these screenshots, it's clear some compromises are inevitable to build our ribbon in pure LiveCode:&lt;br /&gt;&lt;br /&gt;1. Application menu and quick access toolbar&lt;br /&gt;- in Office 2007, the application menu (the Jewel or Orb as it's sometimes referred to) fuses with the quick access toolbar into the window titlebar.&lt;br /&gt;- we could replace the standard window decorations with our own implementations, but this brings its own challenges (such as smooth window resizing)&lt;br /&gt;- in later releases, Microsoft decided the application menu could be toned down a bit, and moved it into a button next to the ribbon tabs, so let's just skip this problem altogether and go for the Windows 7 WordPad approach&lt;br /&gt;- that leaves us with the quick access toolbar in the window titlebar, but as this can also be displayed beneath the ribbon, we'll sidestep the issue and simply move the quick access toolbar to the bottom of the ribbon, shall we?&lt;br /&gt;&lt;br /&gt;2. Application menu panel&lt;br /&gt;- before Office 2010, the application menu panel wasn't constrained to the window but in its own layer&lt;br /&gt;- this makes it a bit tricky, but we'll get to it in a future post&lt;br /&gt;&lt;br /&gt;3. Windows vs. Mac&lt;br /&gt;- while the Windows version dispensed with the menubar entirely, the Mac version still has a complete menubar, but no application menu button&lt;br /&gt;- we just decided to move the quick access toolbar out of the window titlebar and to the bottom of the ribbon, but on Mac the user has no option to move this toolbar and it's always between the window titlebar and the ribbon tabs&lt;br /&gt;- the ribbon group titles have different locations as well: at the bottom of the ribbon group on Windows, at the top on Mac&lt;br /&gt;&lt;br /&gt;4. Out of scope&lt;br /&gt;For various reasons, the following ribbon features won't be in scope for this experiment&lt;br /&gt;- contextual tabs&lt;br /&gt;- galleries&lt;br /&gt;- enhanced tooltips&lt;br /&gt;&lt;br /&gt;So we'll compromise, but should still have a pretty good ribbon implementation at the end of the experiment - not just a one-off ribbon implementation, but a generic one which you can modify from script. Here's what we're aiming for:&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://2.bp.blogspot.com/-5CD8VKQ3lYU/ThnUwy9HoyI/AAAAAAAAAEM/WSDXSyAZcHU/s1600/WordPadWindows7bis.png"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 320px; height: 58px;" src="http://2.bp.blogspot.com/-5CD8VKQ3lYU/ThnUwy9HoyI/AAAAAAAAAEM/WSDXSyAZcHU/s320/WordPadWindows7bis.png" border="0" alt=""id="BLOGGER_PHOTO_ID_5627763144153080610" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;It won't be pixel-prefect, but we can get pretty close. In our first installment, we'll take care of the groundworks. Hang in there - I'll post it shortly.&lt;br /&gt;&lt;br /&gt;In the meantime, you should definitely read up on these MSDN articles:&lt;br /&gt;- &lt;a href="http://msdn.microsoft.com/en-us/library/cc872781.aspx"&gt;Ribbon design process&lt;/a&gt;&lt;br /&gt;- &lt;a href="http://msdn.microsoft.com/en-us/library/cc872782.aspx"&gt;UX Guidelines - Ribbons&lt;/a&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5092807049363133934-870328593751497384?l=quartam.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://quartam.blogspot.com/feeds/870328593751497384/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=5092807049363133934&amp;postID=870328593751497384' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5092807049363133934/posts/default/870328593751497384'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5092807049363133934/posts/default/870328593751497384'/><link rel='alternate' type='text/html' href='http://quartam.blogspot.com/2011/07/experimenting-with-ribbons-in-livecode.html' title='Experimenting with ribbons in LiveCode (prelude)'/><author><name>Quartam Software - Jan Schenkel</name><uri>http://www.blogger.com/profile/08312925714131118786</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://4.bp.blogspot.com/-NJO9jxdcOD8/ThnT6C1oulI/AAAAAAAAADk/cDMxjgZhgi4/s72-c/Word2007.png' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5092807049363133934.post-3436260720763581616</id><published>2011-06-13T07:27:00.000-07:00</published><updated>2011-06-13T07:40:56.648-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='livecode'/><category scheme='http://www.blogger.com/atom/ns#' term='livecode server'/><category scheme='http://www.blogger.com/atom/ns#' term='quartam pdf library'/><category scheme='http://www.blogger.com/atom/ns#' term='on-rev'/><title type='text'>Quartam PDF Library 1.1.2 Available</title><content type='html'>&lt;span style="font-style:italic;"&gt;This maintenance update to Quartam PDF Library fixes a bug with clipping in combination with automatic page breaks.&lt;br /&gt;&lt;br /&gt;The cross-platform .zip archive can be downloaded at: &lt;a href="http://downloads.quartam.com/qrtpdflib_112_xplatform.zip"&gt;http://downloads.quartam.com/qrtpdflib_112_xplatform.zip&lt;/a&gt;&lt;br /&gt;A web page with LiveCode Server / On-Rev demos is available at: &lt;a href="http://quartam.on-rev.com/qrtpdfdemos.irev"&gt;http://quartam.on-rev.com/qrtpdfdemos.irev&lt;/a&gt;&lt;br /&gt;New examples were added to demonstrate using form data to fill PDF documents and email them.&lt;br /&gt;&lt;br /&gt;Quartam PDF Library for LiveCode - version 1.1 introduced support for transformations, transparency and blendmodes, gradients, clipping, text box fitting, inserting pages, compression, experimental support for including EPS files, as well as support for LiveCode Server and On-Rev. It is released as open source under a dual license (GNU Affero General Public License / Commercial License).&lt;/span&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5092807049363133934-3436260720763581616?l=quartam.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://quartam.blogspot.com/feeds/3436260720763581616/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=5092807049363133934&amp;postID=3436260720763581616' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5092807049363133934/posts/default/3436260720763581616'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5092807049363133934/posts/default/3436260720763581616'/><link rel='alternate' type='text/html' href='http://quartam.blogspot.com/2011/06/quartam-pdf-library-112-available.html' title='Quartam PDF Library 1.1.2 Available'/><author><name>Quartam Software - Jan Schenkel</name><uri>http://www.blogger.com/profile/08312925714131118786</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5092807049363133934.post-2370244210609601179</id><published>2011-05-30T12:53:00.000-07:00</published><updated>2011-05-30T13:44:29.050-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='multi-threading'/><category scheme='http://www.blogger.com/atom/ns#' term='java'/><category scheme='http://www.blogger.com/atom/ns#' term='livecode'/><category scheme='http://www.blogger.com/atom/ns#' term='example code'/><title type='text'>Leveraging Java from LiveCode - experiments with multi-threading</title><content type='html'>For some time now, I've been promoting the idea of extending &lt;a href="http://www.runrev.com"&gt;LiveCode&lt;/a&gt; using &lt;a href="http://www.oracle.com/technetwork/java/index.html"&gt;Java&lt;/a&gt; - both are wonderful cross-platform technologies, each with their own strengths and weaknesses. One area where Java has really made an impact is its built-in approach to multi-threading, by offering a way to split work into chunks that can be executed separately and simultaneously. In a world where chip makers turn to multiple cores to speed up processing, this is a very important technology to incorporate into our software designs.&lt;br /&gt;&lt;br /&gt;LiveCode is single-threaded - well, mostly single-threaded: script execution happens on a single thread, but some parts, like socket communication, can be used with event callbacks. This means that when new data arrives, an event message is sent to a control and handled on the script execution thread; but the actual reading and writing can happen on other threads. This is a solid middle ground, as coders don't have to worry about data races and the other un-fun things that come with full-blown multi-threaded programming.&lt;br /&gt;&lt;br /&gt;Now my earlier postrs on extending LiveCode with Java were based on a strategy of handing off some work to a Java process (via shell calls or process communication via pipes) and waiting for the result to come back. This is all nice and dandy if the work is done quickly, but what if the processing may take a long time to complete? One of the main reasons to employ multi-threading, after all, is to make sure we can update the user interface while some heavy processing continues in the background.&lt;br /&gt;&lt;br /&gt;One approach could be to use the 'in time' variant of the '&lt;a href="http://docs.runrev.com/Command/read-from-process"&gt;read from process&lt;/a&gt;' command, and poll the other side for content, but I wanted to show a different approach, by wrapping our Java code in an &lt;a href="http://download.oracle.com/javase/6/docs/api/java/util/concurrent/ExecutorService.html"&gt;ExecutorService&lt;/a&gt;. The basic idea: we can either 'call' a command immedialtely, or 'submit' it as a task to process in  the background. Submitting a command returns a pending-result-id, which we can use to 'cancel' (if it hasn't started or finished yet), check on its 'status' (done, cancelled, pending) or even 'get' its result (waiting for the command to complete).&lt;br /&gt;&lt;br /&gt;I know that's a lot of things in one paragraph, so let's put it in Java code:&lt;br /&gt;&lt;br /&gt;&lt;pre language="java"&gt;&lt;br /&gt;import java.io.BufferedReader;&lt;br /&gt;import java.io.IOException;&lt;br /&gt;import java.io.InputStreamReader;&lt;br /&gt;import java.util.HashMap;&lt;br /&gt;import java.util.Map;&lt;br /&gt;import java.util.concurrent.Callable;&lt;br /&gt;import java.util.concurrent.ExecutorService;&lt;br /&gt;import java.util.concurrent.Executors;&lt;br /&gt;import java.util.concurrent.Future;&lt;br /&gt;import java.util.concurrent.RejectedExecutionException;&lt;br /&gt;import java.util.concurrent.atomic.AtomicInteger;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;public class LiveCodeJavaExecutor {&lt;br /&gt;&lt;br /&gt; private final static String EXIT_COMMAND = "exit";&lt;br /&gt; private final static String CALL_COMMAND = "call";&lt;br /&gt; private final static String SUBMIT_COMMAND = "submit";&lt;br /&gt; private final static String CANCEL_COMMAND = "cancel";&lt;br /&gt; private final static String STATUS_COMMAND = "status";&lt;br /&gt; private final static String GET_COMMAND = "get";&lt;br /&gt;&lt;br /&gt; private final static ExecutorService EXECUTOR_SERVICE;&lt;br /&gt; private final static Map&amp;lt;Integer,Future&amp;lt;String&amp;gt;&amp;gt; PENDING_RESULTS;&lt;br /&gt; private final static AtomicInteger NEXT_PENDING_ID;&lt;br /&gt; &lt;br /&gt; static {&lt;br /&gt;  EXECUTOR_SERVICE = Executors.newCachedThreadPool();&lt;br /&gt;  NEXT_PENDING_ID = new AtomicInteger();&lt;br /&gt;  PENDING_RESULTS = new HashMap&amp;lt;Integer,Future&amp;lt;String&amp;gt;&amp;gt;();&lt;br /&gt; }&lt;br /&gt; &lt;br /&gt; /**&lt;br /&gt;  * @param args&lt;br /&gt;  */&lt;br /&gt; public static void main(String[] args) {&lt;br /&gt;  // Make sure to run in head-less mode&lt;br /&gt;  System.setProperty("java.awt.headless", "true");&lt;br /&gt;&lt;br /&gt;  String commandLine = "";  // the last received command line&lt;br /&gt;  System.out.println("LiveCodeJavaExecutor started. Type the command you wish to execute (type 'exit' to stop):");&lt;br /&gt;  final InputStreamReader isr = new InputStreamReader(System.in);&lt;br /&gt;  final BufferedReader bir = new BufferedReader(isr);&lt;br /&gt;&lt;br /&gt;  InfiniteLoop:&lt;br /&gt;  while(!EXIT_COMMAND.equalsIgnoreCase(commandLine)) {&lt;br /&gt;   try {&lt;br /&gt;    commandLine = bir.readLine();&lt;br /&gt;   } catch (IOException e) {&lt;br /&gt;    continue InfiniteLoop;&lt;br /&gt;   }&lt;br /&gt;   final String[] commandParts = commandLine.split("\t");&lt;br /&gt;   final String commandName = commandParts[0];&lt;br /&gt;   if (EXIT_COMMAND.equalsIgnoreCase(commandName)) {&lt;br /&gt;    System.out.println("LiveCodeJavaExecutor is shutting down");&lt;br /&gt;    EXECUTOR_SERVICE.shutdown();&lt;br /&gt;    System.out.println("LiveCodeJavaExecutor is exiting");&lt;br /&gt;   } else if (CALL_COMMAND.equalsIgnoreCase(commandName)) {&lt;br /&gt;    String result = "";&lt;br /&gt;    final CallableCommand callableCommand = new CallableCommand(commandParts);&lt;br /&gt;    try {&lt;br /&gt;     result = callableCommand.call();&lt;br /&gt;    } catch (Exception e) {&lt;br /&gt;     result = e.getMessage().trim();&lt;br /&gt;    }&lt;br /&gt;    System.out.println(result);&lt;br /&gt;   } else if (SUBMIT_COMMAND.equalsIgnoreCase(commandName)){&lt;br /&gt;    String result = "";&lt;br /&gt;    try {&lt;br /&gt;     final CallableCommand callableCommand = new CallableCommand(commandParts);&lt;br /&gt;     final Integer pendingResultId = Integer.valueOf(NEXT_PENDING_ID.incrementAndGet());&lt;br /&gt;     final Future&lt;String&gt; pendingResult = EXECUTOR_SERVICE.submit(callableCommand);&lt;br /&gt;     PENDING_RESULTS.put(pendingResultId, pendingResult);&lt;br /&gt;     result = pendingResultId.toString();&lt;br /&gt;    } catch (RejectedExecutionException e) {&lt;br /&gt;     result = "error - could not submit command";&lt;br /&gt;    }&lt;br /&gt;    System.out.println(result);&lt;br /&gt;   } else if (CANCEL_COMMAND.equalsIgnoreCase(commandName)) {&lt;br /&gt;    String result = "";&lt;br /&gt;    try {&lt;br /&gt;     final Integer pendingResultId = Integer.valueOf(commandParts[1]);&lt;br /&gt;     final Future&lt;String&gt; pendingResult = PENDING_RESULTS.get(pendingResultId);&lt;br /&gt;     if (pendingResult != null) {&lt;br /&gt;      result = String.valueOf(pendingResult.cancel(true));&lt;br /&gt;     } else {&lt;br /&gt;      result = "unknown pending result id: " + pendingResultId.toString();&lt;br /&gt;     }&lt;br /&gt;    } catch (ArrayIndexOutOfBoundsException e) {&lt;br /&gt;     result = "invalid parameters - expected: cancel&amp;lt;tab&amp;gt;&amp;lt;pending-result-id&amp;gt;";&lt;br /&gt;    } catch (Exception e) {&lt;br /&gt;     result = e.getMessage().trim();&lt;br /&gt;    }&lt;br /&gt;    System.out.println(result);&lt;br /&gt;   } else if (STATUS_COMMAND.equalsIgnoreCase(commandName)) {&lt;br /&gt;    String result = "";&lt;br /&gt;    try {&lt;br /&gt;     final Integer pendingResultId = Integer.valueOf(commandParts[1]);&lt;br /&gt;     final Future&lt;String&gt; pendingResult = PENDING_RESULTS.get(pendingResultId);&lt;br /&gt;     if (pendingResult != null) {&lt;br /&gt;      if (pendingResult.isCancelled()) {&lt;br /&gt;       result = "cancelled";&lt;br /&gt;      } else if (pendingResult.isDone()) {&lt;br /&gt;       result = "done";&lt;br /&gt;      } else {&lt;br /&gt;       result = "pending";&lt;br /&gt;      }&lt;br /&gt;     } else {&lt;br /&gt;      result = "unknown pending result id: " + pendingResultId.toString();&lt;br /&gt;     }&lt;br /&gt;    } catch (ArrayIndexOutOfBoundsException e) {&lt;br /&gt;     result = "invalid parameters - expected: status&amp;lt;tab&amp;gt;&amp;lt;pending-result-id&amp;gt;";&lt;br /&gt;    } catch (Exception e) {&lt;br /&gt;     result = e.getMessage().trim();&lt;br /&gt;    }&lt;br /&gt;    System.out.println(result);&lt;br /&gt;   } else if (GET_COMMAND.equalsIgnoreCase(commandName)) {&lt;br /&gt;    String result = "";&lt;br /&gt;    try {&lt;br /&gt;     final Integer pendingResultId = Integer.valueOf(commandParts[1]);&lt;br /&gt;     final Future&lt;String&gt; pendingResult = PENDING_RESULTS.get(pendingResultId);&lt;br /&gt;     if (pendingResult != null) {&lt;br /&gt;      result = pendingResult.get();&lt;br /&gt;      PENDING_RESULTS.remove(pendingResultId);&lt;br /&gt;     } else {&lt;br /&gt;      result = "unknown pending result id: " + pendingResultId.toString();&lt;br /&gt;     }&lt;br /&gt;    } catch (ArrayIndexOutOfBoundsException e) {&lt;br /&gt;     result = "invalid parameters - expected: get&amp;lt;tab&amp;gt;&amp;lt;pending-result-id&amp;gt;";&lt;br /&gt;    } catch (Exception e) {&lt;br /&gt;     result = e.getMessage().trim();&lt;br /&gt;    }&lt;br /&gt;    System.out.println(result);&lt;br /&gt;   } else {&lt;br /&gt;    System.out.println("unrecognized command: " + commandLine);&lt;br /&gt;   }&lt;br /&gt;   System.out.println('.');&lt;br /&gt;  }&lt;br /&gt; }&lt;br /&gt;&lt;br /&gt; private static class CallableCommand implements Callable&amp;lt;String&amp;gt; {&lt;br /&gt;  private final String[] commandParts;&lt;br /&gt;  public CallableCommand(final String[] commandParts) {&lt;br /&gt;   super();&lt;br /&gt;   this.commandParts = commandParts;&lt;br /&gt;  }&lt;br /&gt;  public String call() throws Exception {&lt;br /&gt;   String result = null;&lt;br /&gt;   result = dispatchCommand(commandParts);&lt;br /&gt;   return result;&lt;br /&gt;  }&lt;br /&gt; }&lt;br /&gt;&lt;br /&gt; public static String dispatchCommand(String[] commandParts) throws Exception {&lt;br /&gt;  String commandName = commandParts[1];&lt;br /&gt;  Thread.sleep(10000);&lt;br /&gt;  return commandName;&lt;br /&gt; }&lt;br /&gt;&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Admittedly, the actual command execution part is a bit silly to say the least: sleeping for ten seconds and then just returning the command name is not exactly business logic. But you can take a look at some of my previous examples, to put in code for concatenating PDF files, validating XML data using XSD schemas, image processing or anything else that could take quite a bit of time to finish processing.&lt;br /&gt;&lt;br /&gt;Turning our attention to the LiveCode side, we would have a stack script looking something like this:&lt;br /&gt;&lt;pre language="livecode"&gt;&lt;br /&gt;local sProcess&lt;br /&gt;&lt;br /&gt;on JExecutor_StartHelper&lt;br /&gt;   if sProcess is empty then&lt;br /&gt;      local tDefaultFolder, tStackFolder&lt;br /&gt;      put ImageIO_StackFolder() into tStackFolder&lt;br /&gt;      put the defaultFolder into tDefaultFolder&lt;br /&gt;      set the defaultFolder to tStackFolder&lt;br /&gt;      put "java LiveCodeJavaExecutor" into sProcess&lt;br /&gt;      open process sProcess for update&lt;br /&gt;      set the defaultFolder to tDefaultFolder&lt;br /&gt;      --&gt; make sure we're speaking with the right helper&lt;br /&gt;      read from process sProcess for 1 line&lt;br /&gt;      if it begins with "LiveCodeJavaExecutor started." then&lt;br /&gt;         --&gt; good to go&lt;br /&gt;      else&lt;br /&gt;         close process sProcess&lt;br /&gt;         put empty into sProcess&lt;br /&gt;      end if&lt;br /&gt;   end if&lt;br /&gt;end JExecutor_StartHelper&lt;br /&gt;&lt;br /&gt;on JExecutor_StopHelper&lt;br /&gt;   write ("exit") &amp; return to process sProcess&lt;br /&gt;   close process sProcess&lt;br /&gt;   put empty into sProcess&lt;br /&gt;end JExecutor_StopHelper&lt;br /&gt;&lt;br /&gt;command JExecutor_CallCommand pCommand&lt;br /&gt;   write("call" &amp; tab &amp; pCommand) &amp; return to process sProcess&lt;br /&gt;   read from process sprocess for 1 line&lt;br /&gt;   return line 1 of it&lt;br /&gt;end JExecutor_CallCommand&lt;br /&gt;&lt;br /&gt;command JExecutor_SubmitCommand pCommand&lt;br /&gt;   write("submit" &amp; tab &amp; pCommand) &amp; return to process sProcess&lt;br /&gt;   read from process sprocess for 1 line&lt;br /&gt;   return line 1 of it&lt;br /&gt;end JExecutor_SubmitCommand&lt;br /&gt;&lt;br /&gt;command JExecutor_CancelRequest pRequestId&lt;br /&gt;   write("cancel" &amp; tab &amp; pRequestId) &amp; return to process sProcess&lt;br /&gt;   read from process sprocess for 1 line&lt;br /&gt;   return line 1 of it&lt;br /&gt;end JExecutor_CancelRequest&lt;br /&gt;&lt;br /&gt;function JExecutor_StatusOfRequest pRequestId&lt;br /&gt;   write("status" &amp; tab &amp; pRequestId) &amp; return to process sProcess&lt;br /&gt;   read from process sprocess for 1 line&lt;br /&gt;   return line 1 of it&lt;br /&gt;end JExecutor_StatusOfRequest&lt;br /&gt;&lt;br /&gt;function JExecutor_GetResultOfRequest pRequestId&lt;br /&gt;   write("get" &amp; tab &amp; pRequestId) &amp; return to process sProcess&lt;br /&gt;   read from process sprocess for 1 line&lt;br /&gt;   return line 1 of it&lt;br /&gt;end JExecutor_GetResultOfRequest&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Now we have the commands and functions we need to start the separate Java process, 'call' commands, 'submit' commands, 'cancel' pending requests, checking the 'status' of pending requests, and fetching the result with a 'get'. Enough to control the lifecycle of such submitted commands a.k.a. pending requests.&lt;br /&gt;&lt;br /&gt;If we're sure the command is just a small bit of work, we use the 'call' command and wait for the result to appear; but if it may take a long time to complete, we can 'submit' a command and use the returned 'pending request id' to track its progress - and while the Java side is churning away, we can update our LiveCode user interface with a lovely indeterminate progress display, polling every second or so until the 'status' returns "done" and then use 'get' to fetch the completed command output.&lt;br /&gt;&lt;br /&gt;So perhaps the title was a little misleading, as I've only given you a skeleton. Hopefully it will inspire you to combine this technique with code that I posted previously, and actually use it in your own projects. Happy coding!&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5092807049363133934-2370244210609601179?l=quartam.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://quartam.blogspot.com/feeds/2370244210609601179/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=5092807049363133934&amp;postID=2370244210609601179' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5092807049363133934/posts/default/2370244210609601179'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5092807049363133934/posts/default/2370244210609601179'/><link rel='alternate' type='text/html' href='http://quartam.blogspot.com/2011/05/leveraging-java-from-livecode.html' title='Leveraging Java from LiveCode - experiments with multi-threading'/><author><name>Quartam Software - Jan Schenkel</name><uri>http://www.blogger.com/profile/08312925714131118786</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5092807049363133934.post-5522937012319694903</id><published>2011-05-09T14:55:00.000-07:00</published><updated>2011-05-09T15:02:01.101-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='livecode'/><category scheme='http://www.blogger.com/atom/ns#' term='livecode server'/><category scheme='http://www.blogger.com/atom/ns#' term='quartam pdf library'/><category scheme='http://www.blogger.com/atom/ns#' term='on-rev'/><title type='text'>Quartam PDF Library 1.1.1 Available</title><content type='html'>&lt;span style="font-style:italic;"&gt;This maintenance update to Quartam PDF Library fixes two bugs specific to LiveCode Server environments, and extends the WriteTextTable command to allow more control over border drawing.&lt;br /&gt;&lt;br /&gt;The cross-platform .zip archive can be downloaded at: &lt;a href="http://downloads.quartam.com/qrtpdflib_111_xplatform.zip"&gt;http://downloads.quartam.com/qrtpdflib_111_xplatform.zip&lt;/a&gt;&lt;br /&gt;A web page with LiveCode Server / On-Rev demos is available at: &lt;a href="http://quartam.on-rev.com/qrtpdfdemos.irev"&gt;http://quartam.on-rev.com/qrtpdfdemos.irev&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;Quartam PDF Library for LiveCode - version 1.1 introduced support for transformations, transparency and blendmodes, gradients, clipping, text box fitting, inserting pages, compression, experimental support for including EPS files, as well as support for LiveCode Server and On-Rev. It is released as open source under a dual license (GNU Affero General Public License / Commercial License).&lt;/span&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5092807049363133934-5522937012319694903?l=quartam.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://quartam.blogspot.com/feeds/5522937012319694903/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=5092807049363133934&amp;postID=5522937012319694903' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5092807049363133934/posts/default/5522937012319694903'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5092807049363133934/posts/default/5522937012319694903'/><link rel='alternate' type='text/html' href='http://quartam.blogspot.com/2011/05/quartam-pdf-library-111-available.html' title='Quartam PDF Library 1.1.1 Available'/><author><name>Quartam Software - Jan Schenkel</name><uri>http://www.blogger.com/profile/08312925714131118786</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5092807049363133934.post-829979450204655582</id><published>2011-05-08T08:12:00.000-07:00</published><updated>2011-05-08T08:25:32.463-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='java'/><category scheme='http://www.blogger.com/atom/ns#' term='livecode'/><category scheme='http://www.blogger.com/atom/ns#' term='runrevlive'/><category scheme='http://www.blogger.com/atom/ns#' term='example code'/><title type='text'>Presenting at RunRevLive.11 - the code</title><content type='html'>&lt;a href="http://quartam.blogspot.com/2011/04/presenting-at-runrevlive11.html"&gt;As I mentioned earlier&lt;/a&gt;, I presented two topics at the &lt;a href="http://www.runrevlive.com"&gt;RunRevLive.11 developer conference&lt;/a&gt;. After returning home, I got clobbered with a backlog of work at the day-job; but today, I finally got around to uploading the example code.&lt;br /&gt;&lt;br /&gt;- &lt;a href="http://downloads.quartam.com/tea200.zip"&gt;Advanced databases&lt;/a&gt; (or: migrating from a single-user desktop application to a multi-user networked database application)&lt;br /&gt;- &lt;a href="http://downloads.quartam.com/elcwj.zip"&gt;Extending LiveCode with Java&lt;/a&gt; (or: leveraging Java libraries from within LiveCode scripts)&lt;br /&gt;&lt;br /&gt;Enjoy - and don't forget that if you couldn't attend it in person and bought a SimulCast pass, you can see the whole presentation through the &lt;a href="http://www.runrevlive.com/simulcast11/login.php"&gt;RunRevLive SimulCast&lt;/a&gt; - or wait for the DVD to come out :-)&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5092807049363133934-829979450204655582?l=quartam.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://quartam.blogspot.com/feeds/829979450204655582/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=5092807049363133934&amp;postID=829979450204655582' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5092807049363133934/posts/default/829979450204655582'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5092807049363133934/posts/default/829979450204655582'/><link rel='alternate' type='text/html' href='http://quartam.blogspot.com/2011/05/presenting-at-runrevlive11-code.html' title='Presenting at RunRevLive.11 - the code'/><author><name>Quartam Software - Jan Schenkel</name><uri>http://www.blogger.com/profile/08312925714131118786</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5092807049363133934.post-4911264469284663562</id><published>2011-04-22T01:46:00.000-07:00</published><updated>2011-04-22T01:57:53.315-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='livecode'/><category scheme='http://www.blogger.com/atom/ns#' term='quartam pdf library'/><category scheme='http://www.blogger.com/atom/ns#' term='on-rev'/><category scheme='http://www.blogger.com/atom/ns#' term='bugfix'/><title type='text'>Quartam PDF Library On-Rev Demos</title><content type='html'>&lt;a href="http://quartam.blogspot.com/2011/04/quartam-pdf-library-goes-open-source.html"&gt;As announced yesterday&lt;/a&gt;, Quartam PDF Library for LiveCode is now a free / open source software project, available under the terms of the GNU &lt;a href="http://www.gnu.org/licenses/agpl-3.0.html"&gt;Affero General Public License&lt;/a&gt; (AGPL).&lt;br /&gt;&lt;br /&gt;One of the advantages of version 1.1.0 is that it comes with support for LiveCode Server and &lt;a href="http://www.on-rev.com/"&gt;On-Rev&lt;/a&gt;. I just put up a &lt;a href="http://quartam.on-rev.com/qrtpdfdemos.irev"&gt;page with demos&lt;/a&gt; so that you can see how straightforward it is to port your desktop PDF generation scripts to .irev scripts.&lt;br /&gt;&lt;br /&gt;As it turns out, there are two bugs to address: loading images throws errors, and callback messages have problems as well. Unfortunately, I'm working frantically to finish my &lt;a href="http://quartam.blogspot.com/2011/04/presenting-at-runrevlive11.html"&gt;presentations for RunRevLive.11&lt;/a&gt; - but now that it's an open source project, anyone can go and take a stab at fixing bugs. So I'm looking forward to your helping hand in finding solutions so we can put out an update soon.&lt;br /&gt;&lt;br /&gt;Onwards!&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5092807049363133934-4911264469284663562?l=quartam.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://quartam.blogspot.com/feeds/4911264469284663562/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=5092807049363133934&amp;postID=4911264469284663562' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5092807049363133934/posts/default/4911264469284663562'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5092807049363133934/posts/default/4911264469284663562'/><link rel='alternate' type='text/html' href='http://quartam.blogspot.com/2011/04/quartam-pdf-library-on-rev-demos.html' title='Quartam PDF Library On-Rev Demos'/><author><name>Quartam Software - Jan Schenkel</name><uri>http://www.blogger.com/profile/08312925714131118786</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5092807049363133934.post-4153183833175378456</id><published>2011-04-21T11:26:00.000-07:00</published><updated>2011-04-21T12:11:15.695-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='PDF'/><category scheme='http://www.blogger.com/atom/ns#' term='open source'/><category scheme='http://www.blogger.com/atom/ns#' term='livecode'/><category scheme='http://www.blogger.com/atom/ns#' term='quartam pdf library'/><title type='text'>Quartam PDF Library goes open source</title><content type='html'>Today, &lt;a href="http://www.quartam.com"&gt;Quartam Software&lt;/a&gt; is proud to announce the release of Quartam PDF Library for LiveCode version 1.1, as open source under a dual license. Quartam PDF Library allows LiveCode developers to go beyond 'print to pdf file' as it offers pin-point control, extensive graphics support and much more.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight:bold;"&gt;Where does Quartam PDF Library come from?&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;Back in December 2005, I started Quartam PDF Library as a research project for adding PDF export to Quartam Reports. It was spun off as a separate commercial product and used in a wide variety of LiveCode-based projects, such as BlueMango's &lt;a href="http://www.bluemangolearning.com/screensteps/"&gt;ScreenSteps&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;Although I had kept working on new features, more pressing matters (like my day-job) kept me from pushing ahead and wrapping up a new release. The advent of 'print to pdf' features in LiveCode 4.5 triggered a soul search and eventually led to the decision to release the new version as open source.&lt;br /&gt;&lt;br /&gt;The end result: nearly 7300 lines of production quality code, ready for you to use in your LiveCode projects.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight:bold;"&gt;So what is new in version 1.1?&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;The code got a good cleanup, complies with the rules of variable checking, and replaces most string literals with constants to prevent bugs.&lt;br /&gt;&lt;br /&gt;Plus, the following features were added:&lt;br /&gt;- Transformations (scale, translate, rotate, skew, mirror)&lt;br /&gt;- Transparency and blendmodes&lt;br /&gt;- Gradients&lt;br /&gt;- Clipping&lt;br /&gt;- Text box fitting&lt;br /&gt;- Inserting pages (ideal for building a table of contents with bookmarks)&lt;br /&gt;- Compression&lt;br /&gt;- Experimental support for including EPS files (Emulated PostScript)&lt;br /&gt;&lt;br /&gt;And for the first time, we offer support for generating PDF documents in LiveCode Server scripts, including On-Rev!&lt;br /&gt;&lt;br /&gt;Quartam PDF Library now requires Revolution 3.0 or later, with LiveCode 4.6 highly recommended.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight:bold;"&gt;How do you mean: open source under a dual license?&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;Although Quartam PDF Library is a free/open source software (F/OSS) project, giving you a lot of freedom and flexibility as to how you use it in your own projects, this doesn't mean you're free to do anything you want with it: you have to respect the GNU &lt;a href="http://www.gnu.org/licenses/agpl-3.0.html"&gt;Affero General Public License&lt;/a&gt; (AGPL).&lt;br /&gt;&lt;br /&gt;You can be released from the requirements of the AGPL license by purchasing a commercial license from Quartam Software.&lt;br /&gt;&lt;br /&gt;Buying such a license is mandatory as soon as you develop commercial activities involving Quartam PDF Library without disclosing the source code of your own applications. These activities include: offering paid services to customers as an ASP, serving PDF documents generated dynamically in a web application, shipping Quartam PDF Library with a closed source product.&lt;br /&gt;&lt;br /&gt;Such a commercial license releases you from the requirements of the copyleft AGPL license, which include: distribution of all source code, including your own product; licensing of your own product under the AGPL license; prominent mention of the Quartam copyright and the AGPL license; and disclosure of modifications to the library.&lt;br /&gt;&lt;br /&gt;In addition, the commercial license releases you from the requirement not to change the PDF Producer line in the generated PDF document properties.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight:bold;"&gt;What about my previous commercial license for Quartam PDF Library version 1.0?&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;Of course you can keep using the closed source version 1.0 in your projects. And if your project is open source, you can use version 1.1 without an additional charge.&lt;br /&gt;&lt;br /&gt;However, if you want to use version 1.1 in a commercial activity, you have to purchase an upgrade for USD 49 from the Quartam Software Online Store.&lt;br /&gt;&lt;br /&gt;And if you never bought a copy of Quartam PDF Library, you can purchase the commercial license for USD 149 from the Quartam Software Online Store or the LiveCode Marketplace.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight:bold;"&gt;How can I contribute to the Quartam PDF Library project?&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;I'm glad you asked - the plan is to build a community around Quartam PDF Library in order to streamline the development of newer versions. If you can help with squashing bugs, researching new features, improving documentation, or any other way, you're more than welcome to join us.&lt;br /&gt;&lt;br /&gt;All you need to do is download, sign and email back the &lt;a href="http://www.quartam.com/qosca.pdf"&gt;Quartam Open Source Contributor Agreement&lt;/a&gt; so that your contributions can be incorporated into the project. Quartam Software has the role of project custodian, taking care of versioning and distribution.&lt;br /&gt;&lt;br /&gt;One such contribution was made by John Craig (Splash21) to add compression support to the library, which is included in Quartam PDF Library version 1.1 - another contribution was made by Trevor DeVore (BlueMango) who offered code for writing LiveCode htmlText to a PDF document, which I have yet to integrate but looks really promising.&lt;br /&gt;&lt;br /&gt;And I have some experimental code that I'd love to share and put into the project after review - so any reports of this library's death were greatly exaggerated.&lt;br /&gt;&lt;br /&gt;So roll up your sleeves, &lt;a href="http://downloads.quartam.com/qrtpdflib_110_xplatform.zip"&gt;download the new version&lt;/a&gt; and get stuck in!&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5092807049363133934-4153183833175378456?l=quartam.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://quartam.blogspot.com/feeds/4153183833175378456/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=5092807049363133934&amp;postID=4153183833175378456' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5092807049363133934/posts/default/4153183833175378456'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5092807049363133934/posts/default/4153183833175378456'/><link rel='alternate' type='text/html' href='http://quartam.blogspot.com/2011/04/quartam-pdf-library-goes-open-source.html' title='Quartam PDF Library goes open source'/><author><name>Quartam Software - Jan Schenkel</name><uri>http://www.blogger.com/profile/08312925714131118786</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5092807049363133934.post-8426897457340981899</id><published>2011-04-17T09:43:00.000-07:00</published><updated>2011-04-17T10:06:13.250-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='java'/><category scheme='http://www.blogger.com/atom/ns#' term='databases'/><category scheme='http://www.blogger.com/atom/ns#' term='livecode'/><category scheme='http://www.blogger.com/atom/ns#' term='conference'/><category scheme='http://www.blogger.com/atom/ns#' term='process communication'/><category scheme='http://www.blogger.com/atom/ns#' term='quartam tools'/><category scheme='http://www.blogger.com/atom/ns#' term='quartam reports'/><category scheme='http://www.blogger.com/atom/ns#' term='runrevlive'/><title type='text'>Presenting at RunRevLive.11</title><content type='html'>Next week I'll be at the &lt;a href="http://www.runrevlive.com"&gt;RunRevLive.11 conference&lt;/a&gt; in sunny San Jose, California, giving two presentations: &lt;span style="font-style:italic;"&gt;Advanced Databases&lt;/span&gt; and &lt;span style="font-style:italic;"&gt;Extending LiveCode with Java&lt;/span&gt;. Here is a brief description for both sessions.&lt;br /&gt;&lt;br /&gt;--&lt;br /&gt;&lt;br /&gt;&lt;span style="font-style:italic;"&gt;Advanced databases&lt;/span&gt;:&lt;br /&gt;You've just finished a desktop application that stores its data in a local SQLite database. All works well, and now you're asked to make a multi-user version, so that other users can access and update the data as well. Unfortunately, you can't just load the data into a MySQL dataabse and be done with it: your data structures and business logic have to be ready as well!&lt;br /&gt;In this presentation, I'll take a desktop SQLite application and turn it into a front-end for a networked MySQL database. Along the way, you'll get crucial tips to avoid the pitfalls of such a transformation.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-style:italic;"&gt;Extending LiveCode with Java&lt;/span&gt;:&lt;br /&gt;We all love LiveCode for its cross-platform abilities, combined with an elegant and productive scripting language. But the 200-pound gorilla in this space is indubitably Java. In this presentation we will see how we can leverage Java libraries from within LiveCode through process communication.&lt;br /&gt;This allows us to extend our LiveCode desktop applications with Zeroconf discovery, XML schemas, PDF file enhancements and image processing.&lt;br /&gt;&lt;br /&gt;--&lt;br /&gt;&lt;br /&gt;Last but not least, you're invited to drop by the LiveCode Marketplace vendor area and ask me any questions you have regarding the &lt;a href="http://www.quartam.com"&gt;Quartam developer tools for LiveCode&lt;/a&gt;. You might even get a sneak preview of Quartam Reports 1.2 :-)&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5092807049363133934-8426897457340981899?l=quartam.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://quartam.blogspot.com/feeds/8426897457340981899/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=5092807049363133934&amp;postID=8426897457340981899' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5092807049363133934/posts/default/8426897457340981899'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5092807049363133934/posts/default/8426897457340981899'/><link rel='alternate' type='text/html' href='http://quartam.blogspot.com/2011/04/presenting-at-runrevlive11.html' title='Presenting at RunRevLive.11'/><author><name>Quartam Software - Jan Schenkel</name><uri>http://www.blogger.com/profile/08312925714131118786</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5092807049363133934.post-2294158572742359910</id><published>2011-03-06T01:23:00.000-08:00</published><updated>2011-03-06T02:27:00.268-08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='java'/><category scheme='http://www.blogger.com/atom/ns#' term='formatting'/><category scheme='http://www.blogger.com/atom/ns#' term='livecode'/><category scheme='http://www.blogger.com/atom/ns#' term='locale'/><title type='text'>Locale aware formatting in LiveCode using Java</title><content type='html'>Recently on the Use-LiveCode mailing list, &lt;a href="http://lists.runrev.com/pipermail/use-livecode/2011-February/153597.html"&gt;Peter Haworth asked&lt;/a&gt; how he could get a hold of the date/times/number/currency format preferences to ensure that data display corresponded to the user locale. For date and time, you can use 'the system date' and 'the system time' for quick formatting, and 'the dateFormat' to figure out some of the details. But formatting numbers is quite a different &lt;a href="http://lists.runrev.com/pipermail/use-livecode/2011-March/153865.html"&gt;challenge&lt;/a&gt;. And even if you can get the 'system' information, what to do if you're building a text that shouldn't be in English or user formatting?&lt;br /&gt;&lt;br /&gt;At the risk of sounding like a broken record: let's use Java - it has powerful formatting capabilities for all the required items, and its concept of &lt;a href="http://download.oracle.com/javase/6/docs/api/java/util/Locale.html"&gt;Locale&lt;/a&gt; is robust. To avoid startup overhead and improve response time, we'll use the same approach as for the &lt;a href="http://quartam.blogspot.com/2011/01/zeroconfbonjour-in-livecode-with-jmdns.html"&gt;Zeroconf example&lt;/a&gt;: process communication to start a helper process, interact with it, and finally close it when we're done.&lt;br /&gt;&lt;br /&gt;So let's write a helper class in Java that will (a) give us the default Locale, (b) list the available locales, (c) allows us to format a LiveCode number as currency, integer or decimal number, applying a given or the default locale, (d) allows us to format a LiveCode date as short, medium, long or full date, applying a given or the default locale, and (e) allows us to format a LiveCode time as short, medium, long or full time, applying a given or the default locale. Sounds like a lot to do, but it's actually quite straightforward to implement:&lt;br /&gt;&lt;pre language="java"&gt;import java.io.BufferedReader;&lt;br /&gt;import java.io.IOException;&lt;br /&gt;import java.io.InputStreamReader;&lt;br /&gt;import java.text.DateFormat;&lt;br /&gt;import java.text.NumberFormat;&lt;br /&gt;import java.text.ParseException;&lt;br /&gt;import java.util.ArrayList;&lt;br /&gt;import java.util.Collections;&lt;br /&gt;import java.util.Date;&lt;br /&gt;import java.util.HashMap;&lt;br /&gt;import java.util.List;&lt;br /&gt;import java.util.Locale;&lt;br /&gt;import java.util.Map;&lt;br /&gt;&lt;br /&gt;public class LiveCodeLocaleHelper {&lt;br /&gt;&lt;br /&gt;  private final static String EXIT_COMMAND = "exit";&lt;br /&gt;  private final static String LIST_COMMAND = "list";&lt;br /&gt;  private final static String DEFAULT_COMMAND = "default";&lt;br /&gt;  private final static String CURRENCY_COMMAND = "currency";&lt;br /&gt;  private final static String INTEGER_COMMAND = "integer";&lt;br /&gt;  private final static String NUMBER_COMMAND = "number";&lt;br /&gt;  private final static String DATE_COMMAND = "date";&lt;br /&gt;  private final static String TIME_COMMAND = "time";&lt;br /&gt;&lt;br /&gt;  private static final Map&lt;String,Locale&gt; LOCALE_MAP;&lt;br /&gt;  private static final DateFormat LC_DATE_FORMAT;&lt;br /&gt;  private static final DateFormat LC_TIME_FORMAT;&lt;br /&gt;  &lt;br /&gt;  static {&lt;br /&gt;    Locale en_US_locale = null;&lt;br /&gt;    LOCALE_MAP = new HashMap&lt;String,Locale&gt;();&lt;br /&gt;    for (Locale locale : Locale.getAvailableLocales()) {&lt;br /&gt;      if ("en".equals(locale.getLanguage()) &amp;&amp; "US".equals(locale.getCountry())) {&lt;br /&gt;        en_US_locale = locale;&lt;br /&gt;      }&lt;br /&gt;      LOCALE_MAP.put(locale.toString(), locale);&lt;br /&gt;    }&lt;br /&gt;    LC_DATE_FORMAT = DateFormat.getDateInstance(DateFormat.SHORT, en_US_locale);&lt;br /&gt;    LC_TIME_FORMAT = DateFormat.getTimeInstance(DateFormat.SHORT, en_US_locale);&lt;br /&gt;  }&lt;br /&gt;  &lt;br /&gt;  public static void main(final String[] args) throws IOException {&lt;br /&gt;    String commandLine = "";  // the last received command line&lt;br /&gt;    System.out.println("LiveCodeLocaleHelper started. Type the command you wish to execute (type 'exit' to stop):");&lt;br /&gt;    final InputStreamReader isr = new InputStreamReader(System.in);&lt;br /&gt;    final BufferedReader bir = new BufferedReader(isr);&lt;br /&gt;    &lt;br /&gt;    while(!EXIT_COMMAND.equalsIgnoreCase(commandLine)) {&lt;br /&gt;      commandLine = bir.readLine();&lt;br /&gt;      final String[] commandParts = commandLine.split("\t");&lt;br /&gt;      final String commandName = commandParts[0];&lt;br /&gt;      if (EXIT_COMMAND.equalsIgnoreCase(commandName)) {&lt;br /&gt;        System.out.println("LiveCodeLocaleHelper is exiting");&lt;br /&gt;      } else if (LIST_COMMAND.equalsIgnoreCase(commandName)) {&lt;br /&gt;        handleListCommand(commandParts);&lt;br /&gt;      } else if (DEFAULT_COMMAND.equalsIgnoreCase(commandName)) {&lt;br /&gt;        handleDefaultCommand(commandParts);&lt;br /&gt;      } else if (CURRENCY_COMMAND.equalsIgnoreCase(commandName)) {&lt;br /&gt;        handleCurrencyCommand(commandParts);&lt;br /&gt;      } else if (INTEGER_COMMAND.equalsIgnoreCase(commandName)) {&lt;br /&gt;        handleIntegerCommand(commandParts);&lt;br /&gt;      } else if (NUMBER_COMMAND.equalsIgnoreCase(commandName)) {&lt;br /&gt;        handleNumberCommand(commandParts);&lt;br /&gt;      } else if (DATE_COMMAND.equalsIgnoreCase(commandName)) {&lt;br /&gt;        handleDateCommand(commandParts);&lt;br /&gt;      } else if (TIME_COMMAND.equalsIgnoreCase(commandName)) {&lt;br /&gt;        handleTimeCommand(commandParts);&lt;br /&gt;      } else {&lt;br /&gt;        System.out.println("Unrecognized command: " + commandLine);&lt;br /&gt;      }&lt;br /&gt;    }&lt;br /&gt;  }&lt;br /&gt;&lt;br /&gt;  private static void handleListCommand(final String[] commandParts) {&lt;br /&gt;    final List&lt;String&gt; localeIdentifiers = new ArrayList&lt;String&gt;(LOCALE_MAP.keySet());&lt;br /&gt;    Collections.sort(localeIdentifiers);&lt;br /&gt;    for (String localeIdentifier : localeIdentifiers) {&lt;br /&gt;      System.out.println(localeIdentifier);&lt;br /&gt;    }&lt;br /&gt;    System.out.println(".");&lt;br /&gt;  }&lt;br /&gt;&lt;br /&gt;  private static void handleDefaultCommand(final String[] commandParts) {&lt;br /&gt;    System.out.println(Locale.getDefault().toString());&lt;br /&gt;  }&lt;br /&gt;&lt;br /&gt;  private static void handleCurrencyCommand(final String[] commandParts) {&lt;br /&gt;    double number;&lt;br /&gt;    try {&lt;br /&gt;      number = Double.parseDouble(commandParts[1]);&lt;br /&gt;    } catch (NumberFormatException e) {&lt;br /&gt;      System.out.println("Invalid number: " + commandParts[1]);&lt;br /&gt;      return;&lt;br /&gt;    }&lt;br /&gt;    NumberFormat currencyFormat = null;&lt;br /&gt;    if (commandParts.length &gt; 2) {&lt;br /&gt;      final String localeIdentifier = commandParts[2];&lt;br /&gt;      currencyFormat = NumberFormat.getCurrencyInstance(LOCALE_MAP.get(localeIdentifier));&lt;br /&gt;    } else {&lt;br /&gt;      currencyFormat = NumberFormat.getCurrencyInstance();&lt;br /&gt;    }&lt;br /&gt;    System.out.println(currencyFormat.format(number));&lt;br /&gt;  }&lt;br /&gt;&lt;br /&gt;  private static void handleIntegerCommand(final String[] commandParts) {&lt;br /&gt;    double number;&lt;br /&gt;    try {&lt;br /&gt;      number = Double.parseDouble(commandParts[1]);&lt;br /&gt;    } catch (NumberFormatException e) {&lt;br /&gt;      System.out.println("Invalid number: " + commandParts[1]);&lt;br /&gt;      return;&lt;br /&gt;    }&lt;br /&gt;    NumberFormat integerFormat = null;&lt;br /&gt;    if (commandParts.length &gt; 2) {&lt;br /&gt;      final String localeIdentifier = commandParts[2];&lt;br /&gt;      integerFormat = NumberFormat.getIntegerInstance(LOCALE_MAP.get(localeIdentifier));&lt;br /&gt;    } else {&lt;br /&gt;      integerFormat = NumberFormat.getIntegerInstance();&lt;br /&gt;    }&lt;br /&gt;    System.out.println(integerFormat.format(number));&lt;br /&gt;  }&lt;br /&gt;&lt;br /&gt;  private static void handleNumberCommand(final String[] commandParts) {&lt;br /&gt;    double number;&lt;br /&gt;    try {&lt;br /&gt;      number = Double.parseDouble(commandParts[1]);&lt;br /&gt;    } catch (NumberFormatException e) {&lt;br /&gt;      System.out.println("Invalid number: " + commandParts[1]);&lt;br /&gt;      return;&lt;br /&gt;    }&lt;br /&gt;    NumberFormat numberFormat = null;&lt;br /&gt;    if (commandParts.length &gt; 2) {&lt;br /&gt;      final String localeIdentifier = commandParts[2];&lt;br /&gt;      numberFormat = NumberFormat.getNumberInstance(LOCALE_MAP.get(localeIdentifier));&lt;br /&gt;    } else {&lt;br /&gt;      numberFormat = NumberFormat.getNumberInstance();&lt;br /&gt;    }&lt;br /&gt;    System.out.println(numberFormat.format(number));&lt;br /&gt;  }&lt;br /&gt;&lt;br /&gt;  private static void handleDateCommand(final String[] commandParts) {&lt;br /&gt;    Date date = null;&lt;br /&gt;    try {&lt;br /&gt;      date = LC_DATE_FORMAT.parse(commandParts[1]);&lt;br /&gt;    } catch (ParseException e) {&lt;br /&gt;      System.out.println("Invalid date: " + commandParts[1]);&lt;br /&gt;      return;&lt;br /&gt;    }&lt;br /&gt;    int dateSize = DateFormat.SHORT;&lt;br /&gt;    final String dateFormatString = commandParts[2];&lt;br /&gt;    if ("medium".equalsIgnoreCase(dateFormatString)) {&lt;br /&gt;      dateSize = DateFormat.MEDIUM;&lt;br /&gt;    } else if ("long".equalsIgnoreCase(dateFormatString)) {&lt;br /&gt;      dateSize = DateFormat.LONG;&lt;br /&gt;    } else if ("full".equalsIgnoreCase(dateFormatString)) {&lt;br /&gt;      dateSize = DateFormat.FULL;&lt;br /&gt;    }&lt;br /&gt;    DateFormat dateFormat = null;&lt;br /&gt;    if (commandParts.length &gt; 3) {&lt;br /&gt;      final String localeIdentifier = commandParts[3];&lt;br /&gt;      dateFormat = DateFormat.getDateInstance(dateSize, LOCALE_MAP.get(localeIdentifier));&lt;br /&gt;    } else {&lt;br /&gt;      dateFormat = DateFormat.getDateInstance(dateSize);&lt;br /&gt;    }&lt;br /&gt;    System.out.println(dateFormat.format(date));&lt;br /&gt;  }&lt;br /&gt;&lt;br /&gt;  private static void handleTimeCommand(final String[] commandParts) {&lt;br /&gt;    Date date = null;&lt;br /&gt;    try {&lt;br /&gt;      date = LC_TIME_FORMAT.parse(commandParts[1]);&lt;br /&gt;    } catch (ParseException e) {&lt;br /&gt;      System.out.println("Invalid time: " + commandParts[1]);&lt;br /&gt;      return;&lt;br /&gt;    }&lt;br /&gt;    int timeSize = DateFormat.SHORT;&lt;br /&gt;    final String dateFormatString = commandParts[2];&lt;br /&gt;    if ("medium".equalsIgnoreCase(dateFormatString)) {&lt;br /&gt;      timeSize = DateFormat.MEDIUM;&lt;br /&gt;    } else if ("long".equalsIgnoreCase(dateFormatString)) {&lt;br /&gt;      timeSize = DateFormat.LONG;&lt;br /&gt;    } else if ("full".equalsIgnoreCase(dateFormatString)) {&lt;br /&gt;      timeSize = DateFormat.FULL;&lt;br /&gt;    }&lt;br /&gt;    DateFormat dateFormat = null;&lt;br /&gt;    if (commandParts.length &gt; 3) {&lt;br /&gt;      final String localeIdentifier = commandParts[3];&lt;br /&gt;      dateFormat = DateFormat.getTimeInstance(timeSize, LOCALE_MAP.get(localeIdentifier));&lt;br /&gt;    } else {&lt;br /&gt;      dateFormat = DateFormat.getTimeInstance(timeSize);&lt;br /&gt;    }&lt;br /&gt;    System.out.println(dateFormat.format(date));&lt;br /&gt;  }&lt;br /&gt;&lt;br /&gt;}&lt;/pre&gt;&lt;br /&gt;With the Java helper class written, we can turn our attention to the LiveCode side. Let's create a new stack:&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://4.bp.blogspot.com/-PHB8oevdryQ/TXNgUN-qb_I/AAAAAAAAADY/QoyBryUixWI/s1600/LiveCodeLocaleHelper.png"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 252px; height: 320px;" src="http://4.bp.blogspot.com/-PHB8oevdryQ/TXNgUN-qb_I/AAAAAAAAADY/QoyBryUixWI/s320/LiveCodeLocaleHelper.png" border="0" alt=""id="BLOGGER_PHOTO_ID_5580910263708315634" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;As you can see, we have two buttons at the top for starting/stopping the helper process, along with an option menu to list the available locales and a field to show the default locale. Now put the following into the stack script:&lt;br /&gt;&lt;pre language="livecode"&gt;local sProcess&lt;br /&gt;&lt;br /&gt;on Locale_StartHelper&lt;br /&gt;   if sProcess is empty then&lt;br /&gt;      local tDefaultFolder, tStackFolder&lt;br /&gt;      put Locale_StackFolder() into tStackFolder&lt;br /&gt;      put the defaultFolder into tDefaultFolder&lt;br /&gt;      set the defaultFolder to tStackFolder&lt;br /&gt;      put "java LiveCodeLocaleHelper" into sProcess&lt;br /&gt;      open process sProcess for update&lt;br /&gt;      set the defaultFolder to tDefaultFolder&lt;br /&gt;      --&gt; make sure we're speaking with the right helper&lt;br /&gt;      read from process sProcess for 1 line&lt;br /&gt;      if it begins with "LiveCodeLocaleHelper started." then&lt;br /&gt;         disable button "Start"&lt;br /&gt;         enable button "Stop"&lt;br /&gt;      else&lt;br /&gt;         close process sProcess&lt;br /&gt;         put empty into sProcess&lt;br /&gt;      end if&lt;br /&gt;   end if&lt;br /&gt;end Locale_StartHelper&lt;br /&gt;&lt;br /&gt;function Locale_AvailableLocales&lt;br /&gt;   local tLocaleList&lt;br /&gt;   write ("list") &amp; return to process sProcess&lt;br /&gt;   repeat forever&lt;br /&gt;      read from process sProcess for 1 line&lt;br /&gt;      if it begins with "." then exit repeat&lt;br /&gt;      put it after tLocaleList&lt;br /&gt;   end repeat&lt;br /&gt;   delete char -1 of tLocaleList&lt;br /&gt;   return tLocaleList&lt;br /&gt;end Locale_AvailableLocales&lt;br /&gt;&lt;br /&gt;function Locale_DefaultLocale&lt;br /&gt;   write ("default") &amp; return to process sProcess&lt;br /&gt;   read from process sProcess for 1 line&lt;br /&gt;   return it&lt;br /&gt;end Locale_DefaultLocale&lt;br /&gt;&lt;br /&gt;function Locale_FormatCurrency pAmount, pLocale&lt;br /&gt;   if pLocale is empty then&lt;br /&gt;      write ("currency" &amp; tab &amp; pAmount) &amp; return to process sProcess&lt;br /&gt;   else&lt;br /&gt;      write ("currency" &amp; tab &amp; pAmount &amp; tab &amp; pLocale) &amp; return to process sProcess&lt;br /&gt;   end if&lt;br /&gt;   read from process sProcess for 1 line&lt;br /&gt;   return line 1 of it&lt;br /&gt;end Locale_FormatCurrency&lt;br /&gt;&lt;br /&gt;function Locale_FormatInteger pAmount, pLocale&lt;br /&gt;   if pLocale is empty then&lt;br /&gt;      write ("integer" &amp; tab &amp; pAmount) &amp; return to process sProcess&lt;br /&gt;   else&lt;br /&gt;      write ("integer" &amp; tab &amp; pAmount &amp; tab &amp; pLocale) &amp; return to process sProcess&lt;br /&gt;   end if&lt;br /&gt;   read from process sProcess for 1 line&lt;br /&gt;   return line 1 of it&lt;br /&gt;end Locale_FormatInteger&lt;br /&gt;&lt;br /&gt;function Locale_FormatNumber pAmount, pLocale&lt;br /&gt;   if pLocale is empty then&lt;br /&gt;      write ("number" &amp; tab &amp; pAmount) &amp; return to process sProcess&lt;br /&gt;   else&lt;br /&gt;      write ("number" &amp; tab &amp; pAmount &amp; tab &amp; pLocale) &amp; return to process sProcess&lt;br /&gt;   end if&lt;br /&gt;   read from process sProcess for 1 line&lt;br /&gt;   return line 1 of it&lt;br /&gt;end Locale_FormatNumber&lt;br /&gt;&lt;br /&gt;function Locale_FormatDate pDate, pSize, pLocale&lt;br /&gt;   if pLocale is empty then&lt;br /&gt;      write ("date" &amp; tab &amp; pDate &amp; tab &amp; pSize) &amp; return to process sProcess&lt;br /&gt;   else&lt;br /&gt;      write ("date" &amp; tab &amp; pDate &amp; tab &amp; pSize &amp; tab &amp; pLocale) &amp; return to process sProcess&lt;br /&gt;   end if&lt;br /&gt;   read from process sProcess for 1 line&lt;br /&gt;   return line 1 of it&lt;br /&gt;end Locale_FormatDate&lt;br /&gt;&lt;br /&gt;function Locale_FormatTime pTime, pSize, pLocale&lt;br /&gt;   if pLocale is empty then&lt;br /&gt;      write ("time" &amp; tab &amp; pTime &amp; tab &amp; pSize) &amp; return to process sProcess&lt;br /&gt;   else&lt;br /&gt;      write ("time" &amp; tab &amp; pTime &amp; tab &amp; pSize &amp; tab &amp; pLocale) &amp; return to process sProcess&lt;br /&gt;   end if&lt;br /&gt;   read from process sProcess for 1 line&lt;br /&gt;   return line 1 of it&lt;br /&gt;end Locale_FormatTime&lt;br /&gt;&lt;br /&gt;on Locale_StopHelper&lt;br /&gt;   write ("exit") &amp; return to process sProcess&lt;br /&gt;   close process sProcess&lt;br /&gt;   enable button "Start"&lt;br /&gt;   disable button "Stop"&lt;br /&gt;   put empty into sProcess&lt;br /&gt;end Locale_StopHelper&lt;br /&gt;&lt;br /&gt;function Locale_StackFolder&lt;br /&gt;   local tStackFolder&lt;br /&gt;   put the effective filename of this stack into tStackFolder&lt;br /&gt;   set the itemDelimiter to slash&lt;br /&gt;   delete item -1 of tStackFolder&lt;br /&gt;   return tStackFolder&lt;br /&gt;end Locale_StackFolder&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;The script for the Start button is easy enough:&lt;br /&gt;&lt;pre language="livecode"&gt;on mouseUp&lt;br /&gt;   disable button "Start"&lt;br /&gt;   Locale_StartHelper&lt;br /&gt;   put Locale_AvailableLocales() into button "AvailableLocales"&lt;br /&gt;   put Locale_DefaultLocale() into field "DefaultLocale"&lt;br /&gt;   enable button "Stop"&lt;br /&gt;end mouseUp&lt;/pre&gt;&lt;br /&gt;And the script of the Stop button is equally trivial:&lt;br /&gt;&lt;pre language="LiveCode"&gt;on mouseUp&lt;br /&gt;   disable button "Stop"&lt;br /&gt;   Locale_StopHelper&lt;br /&gt;   enable button "Start"&lt;br /&gt;end mouseUp&lt;/pre&gt;&lt;br /&gt;The script for the Default button in the Number formatting section looks like this:&lt;br /&gt;&lt;pre language="LiveCode"&gt;on mouseUp&lt;br /&gt;   local tNumber&lt;br /&gt;   put field "Number" into tNumber&lt;br /&gt;   put "Currency:" &amp; tab &amp; Locale_FormatCurrency(tNumber) &amp; return &amp; \&lt;br /&gt;       "Integer:" &amp; tab &amp; Locale_FormatInteger(tNumber) &amp; return &amp; \&lt;br /&gt;       "Number:" &amp; tab &amp; Locale_FormatNumber(tNumber) \&lt;br /&gt;       into field "FormattedNumbers"&lt;br /&gt;end mouseUp&lt;/pre&gt;&lt;br /&gt;And the script for the Available button in the Number formatting section looks like this:&lt;br /&gt;&lt;pre language="LiveCode"&gt;on mouseUp&lt;br /&gt;   local tNumber, tLocale&lt;br /&gt;   put field "Number" into tNumber&lt;br /&gt;   put the label of button "AvailableLocales" into tLocale&lt;br /&gt;   put "Currency:" &amp; tab &amp; Locale_FormatCurrency(tNumber, tLocale) &amp; return &amp; \&lt;br /&gt;       "Integer:" &amp; tab &amp; Locale_FormatInteger(tNumber, tLocale) &amp; return &amp; \&lt;br /&gt;       "Number:" &amp; tab &amp; Locale_FormatNumber(tNumber, tLocale) \&lt;br /&gt;       into field "FormattedNumbers"&lt;br /&gt;end mouseUp&lt;/pre&gt;&lt;br /&gt;Likewise, the script for the Default button in the Date formatting section becomes:&lt;br /&gt;&lt;pre language="LiveCode"&gt;on mouseUp&lt;br /&gt;   local tDate&lt;br /&gt;   put field "Date" into tDate&lt;br /&gt;   put "Short:" &amp; tab &amp; Locale_FormatDate(tDate, "short") &amp; return &amp; \&lt;br /&gt;       "Medium:" &amp; tab &amp; Locale_FormatDate(tDate, "medium") &amp; return &amp; \&lt;br /&gt;       "Long:" &amp; tab &amp; Locale_FormatDate(tDate, "long") &amp; return &amp; \&lt;br /&gt;       "Full:" &amp; tab &amp; Locale_FormatDate(tDate, "full") \&lt;br /&gt;       into field "FormattedDates"&lt;br /&gt;end mouseUp&lt;/pre&gt;&lt;br /&gt;Hence, the script for the Available button in the Date formatting section becomes:&lt;br /&gt;&lt;pre language="LiveCode"&gt;on mouseUp&lt;br /&gt;   local tDate, tLocale&lt;br /&gt;   put field "Date" into tDate&lt;br /&gt;   put the label of button "AvailableLocales" into tLocale&lt;br /&gt;   put "Short:" &amp; tab &amp; Locale_FormatDate(tDate, "short", tLocale) &amp; return &amp; \&lt;br /&gt;       "Medium:" &amp; tab &amp; Locale_FormatDate(tDate, "medium", tLocale) &amp; return &amp; \&lt;br /&gt;       "Long:" &amp; tab &amp; Locale_FormatDate(tDate, "long", tLocale) &amp; return &amp; \&lt;br /&gt;       "Full:" &amp; tab &amp; Locale_FormatDate(tDate, "full", tLocale) \&lt;br /&gt;       into field "FormattedDates"&lt;br /&gt;end mouseUp&lt;/pre&gt;&lt;br /&gt;Almost done - here's the script for the Default button in the Time formatting section:&lt;br /&gt;&lt;pre language=:"LiveCode"&gt;on mouseUp&lt;br /&gt;   local tTime&lt;br /&gt;   put field "Time" into tTime&lt;br /&gt;   put "Short:" &amp; tab &amp; Locale_FormatTime(tTime, "short") &amp; return &amp; \&lt;br /&gt;       "Medium:" &amp; tab &amp; Locale_FormatTime(tTime, "medium") &amp; return &amp; \&lt;br /&gt;       "Long:" &amp; tab &amp; Locale_FormatTime(tTime, "long") &amp; return &amp; \&lt;br /&gt;       "Full:" &amp; tab &amp; Locale_FormatTime(tTime, "full") \&lt;br /&gt;       into field "FormattedTimes"&lt;br /&gt;end mouseUp&lt;/pre&gt;&lt;br /&gt;Finally, the logical extension for the Available button in the Time formatting section:&lt;br /&gt;&lt;pre language="LiveCode"&gt;on mouseUp&lt;br /&gt;   local tTime, tLocale&lt;br /&gt;   put field "Time" into tTime&lt;br /&gt;   put the label of button "AvailableLocales" into tLocale&lt;br /&gt;   put "Short:" &amp; tab &amp; Locale_FormatTime(tTime, "short", tLocale) &amp; return &amp; \&lt;br /&gt;       "Medium:" &amp; tab &amp; Locale_FormatTime(tTime, "medium", tLocale) &amp; return &amp; \&lt;br /&gt;       "Long:" &amp; tab &amp; Locale_FormatTime(tTime, "long", tLocale) &amp; return &amp; \&lt;br /&gt;       "Full:" &amp; tab &amp; Locale_FormatTime(tTime, "full", tLocale) \&lt;br /&gt;       into field "FormattedTimes"&lt;br /&gt;end mouseUp&lt;/pre&gt;&lt;br /&gt;Save the stack, and copy the compiled LiveCodeLocaleHelper.class file into the same directory where you saved the stack. Click the Start button, then play around with the rest of the user interface to enter different numbers, dates and times, clicking the buttons to see the formatting output, switching between the different available locales.&lt;br /&gt;&lt;br /&gt;Download it &lt;a href="http://www.quartam.com/blogdemos/LCLOCALE.zip"&gt;here&lt;/a&gt;.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5092807049363133934-2294158572742359910?l=quartam.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://quartam.blogspot.com/feeds/2294158572742359910/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=5092807049363133934&amp;postID=2294158572742359910' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5092807049363133934/posts/default/2294158572742359910'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5092807049363133934/posts/default/2294158572742359910'/><link rel='alternate' type='text/html' href='http://quartam.blogspot.com/2011/03/locale-aware-formatting-in-livecode.html' title='Locale aware formatting in LiveCode using Java'/><author><name>Quartam Software - Jan Schenkel</name><uri>http://www.blogger.com/profile/08312925714131118786</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://4.bp.blogspot.com/-PHB8oevdryQ/TXNgUN-qb_I/AAAAAAAAADY/QoyBryUixWI/s72-c/LiveCodeLocaleHelper.png' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5092807049363133934.post-8350041592902664417</id><published>2011-02-13T11:11:00.000-08:00</published><updated>2011-02-13T11:14:07.537-08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='livecode'/><category scheme='http://www.blogger.com/atom/ns#' term='quartam reports'/><title type='text'>Quartam Reports 1.1.6 Available</title><content type='html'>&lt;span style="font-style:italic;"&gt;This maintenance update to Quartam Reports contains all the enhancements of versions 1.1.1 through 1.1.5, and improves error handling and behavior on MacOSX.&lt;br /&gt;&lt;br /&gt;The MacOSX version can be downloaded at: &lt;a href="http://downloads.quartam.com/qrtReports_116_MacOSX_Setup.dmg"&gt;http://downloads.quartam.com/qrtReports_116_MacOSX_Setup.dmg&lt;/a&gt;&lt;br /&gt;The Windows version can be downloaded at: &lt;a href="http://downloads.quartam.com/qrtReports_116_Windows_Setup.exe"&gt;http://downloads.quartam.com/qrtReports_116_Windows_Setup.exe&lt;/a&gt;&lt;br /&gt;The (expertimental) Linux version can be downloaded at: &lt;a href="http://downloads.quartam.com/qrtReports_116_Linux_Archive.zip"&gt;http://downloads.quartam.com/qrtReports_116_Linux_Archive.zip&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;Quartam Reports for LiveCode - version 1.1 introduced stretching data fields and relative positioning, added title and summary sections, brought data groups to everyone and extended the Professional edition with barcodes, 2D charts and export to HTML and Excel.&lt;br /&gt;With Quartam Reports, LiveCode and just a little SQL, you can create professional reports - cross-platform and database-independent.&lt;/span&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5092807049363133934-8350041592902664417?l=quartam.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://quartam.blogspot.com/feeds/8350041592902664417/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=5092807049363133934&amp;postID=8350041592902664417' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5092807049363133934/posts/default/8350041592902664417'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5092807049363133934/posts/default/8350041592902664417'/><link rel='alternate' type='text/html' href='http://quartam.blogspot.com/2011/02/quartam-reports-116-available.html' title='Quartam Reports 1.1.6 Available'/><author><name>Quartam Software - Jan Schenkel</name><uri>http://www.blogger.com/profile/08312925714131118786</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5092807049363133934.post-8730767228077461186</id><published>2011-01-16T10:32:00.000-08:00</published><updated>2011-01-16T13:57:51.970-08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='java'/><category scheme='http://www.blogger.com/atom/ns#' term='livecode'/><category scheme='http://www.blogger.com/atom/ns#' term='process communication'/><category scheme='http://www.blogger.com/atom/ns#' term='bonjour'/><category scheme='http://www.blogger.com/atom/ns#' term='jmdns'/><category scheme='http://www.blogger.com/atom/ns#' term='example code'/><category scheme='http://www.blogger.com/atom/ns#' term='zeroconf'/><title type='text'>ZeroConf/Bonjour in LiveCode with JmDNS</title><content type='html'>In the &lt;a href="http://quartam.blogspot.com/2010/12/concatenating-pdf-files.html"&gt;last&lt;/a&gt; &lt;a href="http://quartam.blogspot.com/2010/12/stamping-pdf-files.html"&gt;few&lt;/a&gt; &lt;a href="http://quartam.blogspot.com/2010/12/xml-validation-using-schema.html"&gt;posts&lt;/a&gt;, we examined how LiveCode scripts can delegate work to Java classes using the &lt;a href="http://docs.runrev.com/Function/shell"&gt;shell function&lt;/a&gt;. Even though this generally works quite fast and reliably, the downside of this approach is that a new Java process is started every time.&lt;br /&gt;&lt;br /&gt;In this post, we'll use process communication to start a helper process, interact with it, and finally close it when we're done. Our use case: &lt;a href="http://www.zeroconf.org/"&gt;ZeroConf&lt;/a&gt; / &lt;a href="http://developer.apple.com/technologies/mac/networking.html"&gt;Bonjour&lt;/a&gt; registration and discovery of services. The &lt;a href="http://sourceforge.net/projects/jmdns/"&gt;JmDNS&lt;/a&gt; project offers a pure-Java implementation of &lt;a href="http://www.multicastdns.org/"&gt;Multicast DNS&lt;/a&gt; and &lt;a href="http://www.dns-sd.org/"&gt;DNS Service Discovery&lt;/a&gt; technologies. &lt;br /&gt;&lt;br /&gt;LiveCode process communication revolves around opening a process (a.k.a. command-line interface application), writing data, reading data, and finally closing said process. But we'll get to that later; let's start by writing a LiveCodeJmDNSHelper class in Java. First &lt;a href="http://sourceforge.net/projects/jmdns/files/"&gt;download a copy of the JmDNS library&lt;/a&gt; - I picked version 3.0 as I was building this on an older Mac which can't run Java 6.&lt;br /&gt;&lt;br /&gt;In the .zip file, you'll find a file 'jmdns.jar' which is all you need for the rest of this example. Copy it into your Eclipse project, add it to the Build path, and use the following code for the LiveCodeJmDNSHelper.java file:&lt;br /&gt;&lt;br /&gt;&lt;pre language="Java"&gt;import java.io.BufferedReader;&lt;br /&gt;import java.io.IOException;&lt;br /&gt;import java.io.InputStreamReader;&lt;br /&gt;import java.util.HashMap;&lt;br /&gt;import java.util.Map;&lt;br /&gt;&lt;br /&gt;import javax.jmdns.JmDNS;&lt;br /&gt;import javax.jmdns.ServiceInfo;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;public class LiveCodeJmDNSHelper {&lt;br /&gt; &lt;br /&gt; private final static String EXIT_COMMAND = "exit";&lt;br /&gt; private final static String LIST_COMMAND = "list";&lt;br /&gt; private final static String REGISTER_COMMAND = "register";&lt;br /&gt; private final static String UNREGISTER_COMMAND = "unregister";&lt;br /&gt; &lt;br /&gt; private JmDNS jmdns;&lt;br /&gt; private Map&amp;lt;String, ServiceInfo&amp;gt; regMap; &lt;br /&gt; &lt;br /&gt; public LiveCodeJmDNSHelper() {&lt;br /&gt;  super();&lt;br /&gt; }&lt;br /&gt; &lt;br /&gt; public void start() throws IOException {&lt;br /&gt;  this.jmdns = JmDNS.create();&lt;br /&gt;  this.regMap = new HashMap&amp;lt;String, ServiceInfo&amp;gt;();&lt;br /&gt;  String commandLine = "";  // the last received command line&lt;br /&gt;  System.out.println("LiveCodeJmDNSHelper started. Type the command you wish to execute (type 'exit' to stop):");&lt;br /&gt;  final InputStreamReader isr = new InputStreamReader(System.in);&lt;br /&gt;  final BufferedReader bir = new BufferedReader(isr);&lt;br /&gt;  &lt;br /&gt;  while(!EXIT_COMMAND.equals(commandLine)) {&lt;br /&gt;   commandLine = bir.readLine();&lt;br /&gt;   final String[] commandParts = commandLine.split(" ");&lt;br /&gt;   final String commandName = commandParts[0];&lt;br /&gt;   if (EXIT_COMMAND.equalsIgnoreCase(commandName)) {&lt;br /&gt;    System.out.println("LiveCodeHelper is exiting");&lt;br /&gt;    System.exit(0);&lt;br /&gt;   } else if (LIST_COMMAND.equalsIgnoreCase(commandName)) {&lt;br /&gt;    handleListCommand(commandParts);&lt;br /&gt;   } else if (REGISTER_COMMAND.equalsIgnoreCase(commandName)) {&lt;br /&gt;    handleRegisterCommand(commandParts);&lt;br /&gt;   } else if (UNREGISTER_COMMAND.equalsIgnoreCase(commandName)) {&lt;br /&gt;    handleUnregisterCommand(commandParts);&lt;br /&gt;   } else {&lt;br /&gt;    System.out.println("Unrecognized command: " + commandLine);&lt;br /&gt;   }&lt;br /&gt;  }&lt;br /&gt; }&lt;br /&gt;&lt;br /&gt; private void handleListCommand(final String[] commandParts) {&lt;br /&gt;  final String svcType = commandParts[1];&lt;br /&gt;  final ServiceInfo[] svcInfos = this.jmdns.list(svcType);&lt;br /&gt;  for (ServiceInfo svcInfo : svcInfos) {&lt;br /&gt;   System.out.println(svcInfo);&lt;br /&gt;  }&lt;br /&gt;  System.out.println(".");&lt;br /&gt; }&lt;br /&gt;&lt;br /&gt; private void handleRegisterCommand(final String[] commandParts) {&lt;br /&gt;  final String svcAlias = commandParts[1];&lt;br /&gt;  final String svcType = commandParts[2];&lt;br /&gt;  final String svcName = commandParts[3];&lt;br /&gt;  final int svcPort = Integer.parseInt(commandParts[4]);&lt;br /&gt;  final String svcText = commandParts[5];&lt;br /&gt;  ServiceInfo svcInfo = ServiceInfo.create(svcType, svcName, svcPort, svcText);&lt;br /&gt;  try {&lt;br /&gt;   this.jmdns.registerService(svcInfo);&lt;br /&gt;   this.regMap.put(svcAlias, svcInfo);&lt;br /&gt;   System.out.println("Registered service '" + svcAlias + "' as: " + svcInfo);&lt;br /&gt;  } catch (IOException e) {&lt;br /&gt;   System.out.println("Failed to register service '" + svcAlias + "'");&lt;br /&gt;  }&lt;br /&gt; }&lt;br /&gt;&lt;br /&gt; private void handleUnregisterCommand(final String[] commandParts) {&lt;br /&gt;  final String svcAlias = commandParts[1];&lt;br /&gt;  final ServiceInfo svcInfo = this.regMap.remove(svcAlias);&lt;br /&gt;  if (svcInfo != null) {&lt;br /&gt;   this.jmdns.unregisterService(svcInfo);&lt;br /&gt;   System.out.println("Unregistered service '" + svcAlias + "'");&lt;br /&gt;  } else {&lt;br /&gt;   System.out.println("Failed to unregister service '" + svcAlias + "'");&lt;br /&gt;  }&lt;br /&gt; }&lt;br /&gt; &lt;br /&gt; public static void main(String[] args) throws IOException {&lt;br /&gt;  final LiveCodeJmDNSHelper helper = new LiveCodeJmDNSHelper();&lt;br /&gt;  helper.start();&lt;br /&gt; }&lt;br /&gt;&lt;br /&gt;}&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;The start() method goes into an infinite loop, waiting for a line of text as its input, parsing the command and executing it. You can 'list' the ServiceInfo data for a given service type, 'register' your own services, and 'unregister' those services again. All nice and dandy, but how do we talk to it from LiveCode? Let's start by creating a new stack.&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://4.bp.blogspot.com/_NlL9VZsJHto/TTNAVREZqeI/AAAAAAAAADM/n5-ZC8uuYKI/s1600/LiveCodeJmDNS.png"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 303px; height: 320px;" src="http://4.bp.blogspot.com/_NlL9VZsJHto/TTNAVREZqeI/AAAAAAAAADM/n5-ZC8uuYKI/s320/LiveCodeJmDNS.png" border="0" alt=""id="BLOGGER_PHOTO_ID_5562860698835986914" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;So we have two buttons at the top, to Start and Stop the helper process. Then we have a button to Register a service of our own, with fields for the Alias, Type, Name, Port and Text. Next there is a button to Unregister our own service, given an Alias. Finally, there is a button to List all the services of a specific Type in a scrolling field below. With the user interface laid out, we can put the following code into the stack script:&lt;br /&gt;&lt;pre language="LiveCode"&gt;local sProcess&lt;br /&gt;&lt;br /&gt;on JmDNS_StartHelper&lt;br /&gt;   if sProcess is empty then&lt;br /&gt;      local tClassPath&lt;br /&gt;      if the platform is "Win32" then&lt;br /&gt;         put ".;jmdns.jar" into tClassPath&lt;br /&gt;      else&lt;br /&gt;         put ".:jmdns.jar" into tClassPath&lt;br /&gt;      end if&lt;br /&gt;      local tDefaultFolder, tStackFolder&lt;br /&gt;      put JMDNS_StackFolder() into tStackFolder&lt;br /&gt;      put the defaultFolder into tDefaultFolder&lt;br /&gt;      set the defaultFolder to tStackFolder&lt;br /&gt;      put "java -cp" &amp;&amp; tClassPath &amp;&amp; "LiveCodeJmDNSHelper" into sProcess&lt;br /&gt;      open process sProcess for update&lt;br /&gt;      set the defaultFolder to tDefaultFolder&lt;br /&gt;      --&gt; make sure we're speaking with the right helper&lt;br /&gt;      read from process sProcess for 1 line&lt;br /&gt;      if it begins with "LiveCodeJmDNSHelper started." then&lt;br /&gt;         --&gt; ready for interaction&lt;br /&gt;      else&lt;br /&gt;         close process sProcess&lt;br /&gt;         put empty into sProcess&lt;br /&gt;      end if&lt;br /&gt;   end if&lt;br /&gt;end JmDNS_StartHelper&lt;br /&gt;&lt;br /&gt;function JmDNS_GetList pServiceType&lt;br /&gt;   local tJmDNSList&lt;br /&gt;   write ("list" &amp;&amp; pServiceType) &amp; return to process sProcess&lt;br /&gt;   repeat forever&lt;br /&gt;      read from process sProcess for 1 line&lt;br /&gt;      if it begins with "." then exit repeat&lt;br /&gt;      put it after tJmDNSList&lt;br /&gt;   end repeat&lt;br /&gt;   delete char -1 of tJmDNSList&lt;br /&gt;   return tJmDNSList&lt;br /&gt;end JmDNS_GetList&lt;br /&gt;&lt;br /&gt;command JmDNS_RegisterService pAlias, pType, pName, pPort, pText&lt;br /&gt;   write ("register" &amp;&amp; pAlias &amp;&amp; pType &amp;&amp; pName &amp;&amp; pPort &amp;&amp; pText) &amp; return to process sProcess&lt;br /&gt;   read from process sProcess for 1 line&lt;br /&gt;   return it&lt;br /&gt;end JmDNS_RegisterService&lt;br /&gt;&lt;br /&gt;command JmDNS_UnregisterService pAlias&lt;br /&gt;   write ("unregister" &amp;&amp; pAlias) &amp; return to process sProcess&lt;br /&gt;   read from process sProcess for 1 line&lt;br /&gt;   return it&lt;br /&gt;end JmDNS_UnregisterService&lt;br /&gt;&lt;br /&gt;command JmDNS_StopHelper&lt;br /&gt;   write ("exit") &amp; return to process sProcess&lt;br /&gt;   close process sProcess&lt;br /&gt;   put empty into sProcess&lt;br /&gt;end JmDNS_StopHelper&lt;br /&gt;&lt;br /&gt;function JmDNS_StackFolder&lt;br /&gt;   local tStackFolder&lt;br /&gt;   put the effective filename of this stack into tStackFolder&lt;br /&gt;   set the itemDelimiter to slash&lt;br /&gt;   delete item -1 of tStackFolder&lt;br /&gt;   return tStackFolder&lt;br /&gt;end JmDNS_StackFolder&lt;/pre&gt;&lt;br /&gt;The script for the Start button is easy enough:&lt;br /&gt;&lt;pre language="LiveCode"&gt;on mouseUp&lt;br /&gt;   JmDNS_StartHelper&lt;br /&gt;   disable button "Start"&lt;br /&gt;   enable button "Stop"&lt;br /&gt;end mouseUp&lt;/pre&gt;&lt;br /&gt;And the script of the Stop button is equally trivial:&lt;br /&gt;&lt;pre language="LiveCode"&gt;on mouseUp&lt;br /&gt;   JmDNS_StopHelper&lt;br /&gt;   enable button "Start"&lt;br /&gt;   disable button "Stop"&lt;br /&gt;end mouseUp&lt;/pre&gt;&lt;br /&gt;The script of the Register button isn't too complicated either:&lt;br /&gt;&lt;pre language="LiveCode"&gt;on mouseUp&lt;br /&gt;   JmDNS_RegisterService field "Alias", field "Type", field "Name", field "Port", field "Text"&lt;br /&gt;   answer the result&lt;br /&gt;end mouseUp&lt;/pre&gt;&lt;br /&gt;Neither is the script of the Unregister button:&lt;br /&gt;&lt;pre language="LiveCode"&gt;on mouseUp&lt;br /&gt;   ask "Unregister which alias?" with field "Alias"&lt;br /&gt;   if it is empty then exit mouseUp&lt;br /&gt;   JmDNS_UnregisterService it&lt;br /&gt;   answer the result&lt;br /&gt;end mouseUp&lt;/pre&gt;&lt;br /&gt;Nor the script of the List button:&lt;br /&gt;&lt;pre language="LiveCode"&gt;on mouseUp&lt;br /&gt;   ask "List services of which type?" with field "Type"&lt;br /&gt;   if it is empty then exit mouseUp&lt;br /&gt;   put JMDNS_GetList(it) into field "List"&lt;br /&gt;end mouseUp&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Save the stack, and copy jmdns.jar as well as the compiled LiveCodeJmDNSHelper.class into the same directory where you saved the stack. Click the Start button, Register a service of your own, click the List button to make sure it shows up, click the Unregister button to remove it again, and then click the List button to make sure it's gone. If you want, you can launch some of the Java samples that come with JmDNS to see that both Java and LiveCode recognize the same services.&lt;br /&gt;&lt;br /&gt;For the purists: no, I didn't do proper error checking or exception handling in this example. Because it is just that: an &lt;span style="font-style:italic;"&gt;example&lt;/span&gt;. Never promised it was production-ready, did I? ;-)&lt;br /&gt;Download it &lt;a href="http://www.quartam.com/blogdemos/LCJMDNS.zip"&gt;here&lt;/a&gt;.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5092807049363133934-8730767228077461186?l=quartam.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://quartam.blogspot.com/feeds/8730767228077461186/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=5092807049363133934&amp;postID=8730767228077461186' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5092807049363133934/posts/default/8730767228077461186'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5092807049363133934/posts/default/8730767228077461186'/><link rel='alternate' type='text/html' href='http://quartam.blogspot.com/2011/01/zeroconfbonjour-in-livecode-with-jmdns.html' title='ZeroConf/Bonjour in LiveCode with JmDNS'/><author><name>Quartam Software - Jan Schenkel</name><uri>http://www.blogger.com/profile/08312925714131118786</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://4.bp.blogspot.com/_NlL9VZsJHto/TTNAVREZqeI/AAAAAAAAADM/n5-ZC8uuYKI/s72-c/LiveCodeJmDNS.png' height='72' width='72'/><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5092807049363133934.post-279539435126655575</id><published>2010-12-31T08:09:00.000-08:00</published><updated>2010-12-31T09:00:49.528-08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='xml schema'/><category scheme='http://www.blogger.com/atom/ns#' term='java'/><category scheme='http://www.blogger.com/atom/ns#' term='livecode'/><category scheme='http://www.blogger.com/atom/ns#' term='xml'/><category scheme='http://www.blogger.com/atom/ns#' term='example code'/><category scheme='http://www.blogger.com/atom/ns#' term='validation'/><title type='text'>XML validation using a Schema</title><content type='html'>In earlier posts, I examined how we can use the built-in revXML library in LiveCode to &lt;a href="http://quartam.blogspot.com/2010/09/xml-validation-using-dtd.html"&gt;validate XML data against a DTD&lt;/a&gt;, later &lt;a href="http://quartam.blogspot.com/2010/09/xml-validation-using-dtd-and-versions.html"&gt;refining it with a version check&lt;/a&gt; to match evolving requirements. Unfortunately, a Document Type Definition is quite a limited way of XML validation. So this time, we'll improve our defenses again, by incorporating XML Schemas.&lt;br /&gt;&lt;br /&gt;Whereas a DTD is limited to defining the basic structure of the XML in terms of elements and attributes, XML Schemas allow you to define validation on the actual content of the elements and attributes. So you can be sure that an element defined as "xs:date" is actually a valid date, or that an attribute defines as "xs:positiveInteger" is actually a positive integer, etc. A full explanation of XML Schemas is beyond the scope of this post, you'll find plenty of information around the web - a good first stop is &lt;a href="http://www.w3schools.com/schema/default.asp"&gt;this W3Schools XML Schema tutorial&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;This all sounds very good, but here's the rub: the revXML library offers no built-in support for XML Schemas. So yet again we turn to Java, with its built-in XML Validation API. We can easily execute Java code using LiveCode's &lt;a href="http://docs.runrev.com/Function/shell"&gt;shell function&lt;/a&gt; - so let's start by writing the XmlValidateSchema class:&lt;br /&gt;&lt;pre language="Java"&gt;import java.io.File;&lt;br /&gt;import java.io.IOException;&lt;br /&gt;&lt;br /&gt;import javax.xml.XMLConstants;&lt;br /&gt;import javax.xml.parsers.DocumentBuilder;&lt;br /&gt;import javax.xml.parsers.DocumentBuilderFactory;&lt;br /&gt;import javax.xml.parsers.ParserConfigurationException;&lt;br /&gt;import javax.xml.transform.Source;&lt;br /&gt;import javax.xml.transform.dom.DOMSource;&lt;br /&gt;import javax.xml.validation.Schema;&lt;br /&gt;import javax.xml.validation.SchemaFactory;&lt;br /&gt;import javax.xml.validation.Validator;&lt;br /&gt;&lt;br /&gt;import org.w3c.dom.Document;&lt;br /&gt;import org.xml.sax.SAXException;&lt;br /&gt;&lt;br /&gt;public class XmlValidateSchema {&lt;br /&gt; public static void main(String[] args) throws SAXException, ParserConfigurationException, IOException {&lt;br /&gt;  final File xmlFile = new File(args[0]);&lt;br /&gt;  final File xsdFile = new File(args[1]);&lt;br /&gt;  // Load the XML file&lt;br /&gt;  final DocumentBuilderFactory docBuilderFactory = DocumentBuilderFactory.newInstance();&lt;br /&gt;  final DocumentBuilder docBuilder = docBuilderFactory.newDocumentBuilder();&lt;br /&gt;  final Document document = docBuilder.parse(xmlFile);&lt;br /&gt;  // Load the XSD file&lt;br /&gt;  final SchemaFactory schemaFactory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);&lt;br /&gt;  final Schema schema = schemaFactory.newSchema(xsdFile);&lt;br /&gt;  final Validator validator = schema.newValidator();&lt;br /&gt;  // Validate the XML document against the XSL schema&lt;br /&gt;  final Source source = new DOMSource(document);&lt;br /&gt;  validator.validate(source);&lt;br /&gt; }&lt;br /&gt;}&lt;/pre&gt;&lt;br /&gt;In keeping with earlier Java examples, the code is a bit lazy when it comes to exception handling: if any exception is thrown, it will end up in the output of our shell function call. The only important thing to remember is that the first parameter is the XML file, and the second is the XML Schema Definition (XSD) file.&lt;br /&gt;&lt;br /&gt;Let's go to LiveCode and create a new stack for the user interface.&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://4.bp.blogspot.com/_NlL9VZsJHto/TR4HGCFvZKI/AAAAAAAAAC8/ZMOqiZ_9O8U/s1600/XMLSchema-0.png"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 250px; height: 320px;" src="http://4.bp.blogspot.com/_NlL9VZsJHto/TR4HGCFvZKI/AAAAAAAAAC8/ZMOqiZ_9O8U/s320/XMLSchema-0.png" border="0" alt=""id="BLOGGER_PHOTO_ID_5556886790443328674" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;As you can see, there's a field for the Schema text, a field for the XML text, and a button to Validate the XML against the Schema. Since it's perhaps a tad small, here's the content of the Schema field:&lt;br /&gt;&lt;pre language="XML"&gt;&amp;lt;?xml version=&amp;quot;1.0&amp;quot;?&amp;gt;&lt;br /&gt;&amp;lt;xs:schema &lt;br /&gt;    xmlns:xs=&amp;quot;http://www.w3.org/2001/XMLSchema&amp;quot;&lt;br /&gt;    targetNamespace=&amp;quot;http://www.quartam.com&amp;quot; &lt;br /&gt;    xmlns=&amp;quot;http://www.quartam.com&amp;quot;&lt;br /&gt;    elementFormDefault=&amp;quot;qualified&amp;quot;&amp;gt;&lt;br /&gt;&lt;br /&gt;&amp;lt;xs:element name=&amp;quot;RootNode&amp;quot; type=&amp;quot;RootNode&amp;quot;/&amp;gt;&lt;br /&gt;&lt;br /&gt;&amp;lt;xs:complexType name=&amp;quot;RootNode&amp;quot;&amp;gt;&lt;br /&gt;  &amp;lt;xs:sequence&amp;gt;&lt;br /&gt;    &amp;lt;xs:element name=&amp;quot;BranchNode&amp;quot; type=&amp;quot;BranchNode&amp;quot; maxOccurs=&amp;quot;unbounded&amp;quot;/&amp;gt;&lt;br /&gt;  &amp;lt;/xs:sequence&amp;gt;&lt;br /&gt;  &amp;lt;xs:attribute name=&amp;quot;SpecVersion&amp;quot; type=&amp;quot;xs:string&amp;quot;/&amp;gt;&lt;br /&gt;&amp;lt;/xs:complexType&amp;gt;&lt;br /&gt;&lt;br /&gt;&amp;lt;xs:complexType name=&amp;quot;BranchNode&amp;quot;&amp;gt;&lt;br /&gt;  &amp;lt;xs:sequence&amp;gt;&lt;br /&gt;    &amp;lt;xs:element name=&amp;quot;LeafNode&amp;quot; type=&amp;quot;xs:string&amp;quot; maxOccurs=&amp;quot;unbounded&amp;quot;/&amp;gt;&lt;br /&gt;  &amp;lt;/xs:sequence&amp;gt;&lt;br /&gt;&amp;lt;/xs:complexType&amp;gt;&lt;br /&gt;&lt;br /&gt;&amp;lt;/xs:schema&amp;gt;&lt;/pre&gt;&lt;br /&gt;And here's the content of the XML field:&lt;br /&gt;&lt;pre language="XML"&gt;&amp;lt;?xml version=&amp;quot;1.0&amp;quot;?&amp;gt;&lt;br /&gt;&amp;lt;RootNode &lt;br /&gt;    xmlns=&amp;quot;http://www.quartam.com&amp;quot;&lt;br /&gt;    xmlns:xsi=&amp;quot;http://www.w3.org/2001/XMLSchema-instance&amp;quot;&lt;br /&gt;    xsi:schemaLocation=&amp;quot;http://www.quartam.com schema.xsd&amp;quot;&lt;br /&gt;    SpecVersion=&amp;quot;1.0&amp;quot;&amp;gt;&lt;br /&gt;  &amp;lt;BranchNode&amp;gt;&lt;br /&gt;    &amp;lt;LeafNode&amp;gt;The first leaf node&amp;lt;/LeafNode&amp;gt;&lt;br /&gt;    &amp;lt;LeafNode&amp;gt;The second leaf node&amp;lt;/LeafNode&amp;gt;&lt;br /&gt;    &amp;lt;LeafNode&amp;gt;The third leaf node&amp;lt;/LeafNode&amp;gt;&lt;br /&gt;  &amp;lt;/BranchNode&amp;gt;&lt;br /&gt;  &amp;lt;BranchNode&amp;gt;&lt;br /&gt;    &amp;lt;LeafNode&amp;gt;The fourth leaf node&amp;lt;/LeafNode&amp;gt;&lt;br /&gt;    &amp;lt;LeafNode&amp;gt;The fifth leaf node&amp;lt;/LeafNode&amp;gt;&lt;br /&gt;  &amp;lt;/BranchNode&amp;gt;&lt;br /&gt;&amp;lt;/RootNode&amp;gt;&lt;/pre&gt;&lt;br /&gt;After saving the stack, we copy the compiled XmlValidateSchema.class file into the same directory as the stack. Now we can write the script for the 'Validate' button:&lt;br /&gt;&lt;pre language="LiveCode"&gt;on mouseUp&lt;br /&gt;   --&gt; write Schema and XML to temporary files&lt;br /&gt;   local tSchemaFile, tXmlFile&lt;br /&gt;   put the tempName into tSchemaFile&lt;br /&gt;   put field "Schema" into URL ("file:" &amp; tSchemaFile)&lt;br /&gt;   put the tempName into tXmlFile&lt;br /&gt;   put field "XML" into URL ("file:" &amp; tXmlFile)&lt;br /&gt;   --&gt; assemble the shell command&lt;br /&gt;   local tShellCommand&lt;br /&gt;   put "java XmlValidateSchema" &amp;&amp; \&lt;br /&gt;          ShellPath(tXmlFile) &amp;&amp; \&lt;br /&gt;          ShellPath(tSchemaFile) \&lt;br /&gt;          into tShellCommand&lt;br /&gt;   --&gt; execute the shell command&lt;br /&gt;   local tHideConsoleWindows, tDefaultFolder, tShellResult&lt;br /&gt;   put the hideConsoleWindows into tHideConsoleWindows&lt;br /&gt;   set the hideConsoleWindows to true&lt;br /&gt;   put the defaultFolder into tDefaultFolder&lt;br /&gt;   set the defaultFolder to AbsolutePathFromStack()&lt;br /&gt;   put shell(tShellCommand) into tShellResult&lt;br /&gt;   set the defaultFolder to tDefaultFolder&lt;br /&gt;   set the hideConsoleWindows to tHideConsoleWindows&lt;br /&gt;   --&gt; cleanup the temporary files&lt;br /&gt;   delete file tSchemaFile&lt;br /&gt;   delete file tXmlFile&lt;br /&gt;   if tShellResult is not empty then&lt;br /&gt;      answer error tShellResult&lt;br /&gt;   end if&lt;br /&gt;end mouseUp&lt;br /&gt;&lt;br /&gt;function AbsolutePathFromStack pFileName&lt;br /&gt;   local tAbsolutePath&lt;br /&gt;   put the effective filename of this stack into tAbsolutePath&lt;br /&gt;   set the itemDelimiter to slash&lt;br /&gt;   if pFileName is not empty then&lt;br /&gt;      put pFileName into item -1 of tAbsolutePath&lt;br /&gt;   else&lt;br /&gt;      delete item -1 of tAbsolutePath&lt;br /&gt;   end if&lt;br /&gt;   return tAbsolutePath&lt;br /&gt;end AbsolutePathFromStack&lt;br /&gt;&lt;br /&gt;function ShellPath pPath&lt;br /&gt;   if the platform is "Win32" then&lt;br /&gt;      put quote &amp; pPath &amp; quote into pPath&lt;br /&gt;   else&lt;br /&gt;      replace space with backslash &amp; space in pPath&lt;br /&gt;   end if&lt;br /&gt;   return pPath&lt;br /&gt;end ShellPath&lt;/pre&gt;&lt;br /&gt;This time around, we didn't have to fiddle with the Java classpath, as the XML Validation API is built-in. However, we had to write the XML and Schema to temporary files, to avoid length limitations in the shell command. If we now make a deliberate mistake, say change one of the 'LeafNode' elements into a 'BeafNode' element, we see this error:&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://2.bp.blogspot.com/_NlL9VZsJHto/TR4KYu8bSSI/AAAAAAAAADE/RnDI2aPXWMw/s1600/XMLSchema-1.png"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 320px; height: 148px;" src="http://2.bp.blogspot.com/_NlL9VZsJHto/TR4KYu8bSSI/AAAAAAAAADE/RnDI2aPXWMw/s320/XMLSchema-1.png" border="0" alt=""id="BLOGGER_PHOTO_ID_5556890410256386338" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;Again, the image is a bit small, so here's the content of the error:&lt;br /&gt;&lt;pre&gt;ERROR:  'cvc-complex-type.2.4.a: Invalid content was found starting with element 'BeafNode'. One of '{&amp;quot;http://www.quartam.com&amp;quot;:LeafNode}' is expected.'&lt;br /&gt;Exception in thread &amp;quot;main&amp;quot; org.xml.sax.SAXParseException: cvc-complex-type.2.4.a: Invalid content was found starting with element 'BeafNode'. One of '{&amp;quot;http://www.quartam.com&amp;quot;:LeafNode}' is expected.&lt;br /&gt;&amp;#9;at com.sun.org.apache.xerces.internal.jaxp.validation.Util.toSAXParseException(Util.java:109)&lt;br /&gt;&amp;#9;at com.sun.org.apache.xerces.internal.jaxp.validation.ErrorHandlerAdaptor.error(ErrorHandlerAdaptor.java:104)&lt;br /&gt;&amp;#9;at com.sun.org.apache.xerces.internal.impl.XMLErrorReporter.reportError(XMLErrorReporter.java:382)&lt;br /&gt;&amp;#9;at com.sun.org.apache.xerces.internal.impl.XMLErrorReporter.reportError(XMLErrorReporter.java:316)&lt;br /&gt;&amp;#9;at com.sun.org.apache.xerces.internal.impl.xs.XMLSchemaValidator$XSIErrorReporter.reportError(XMLSchemaValidator.java:429)&lt;br /&gt;&amp;#9;at com.sun.org.apache.xerces.internal.impl.xs.XMLSchemaValidator.reportSchemaError(XMLSchemaValidator.java:3185)&lt;br /&gt;&amp;#9;at com.sun.org.apache.xerces.internal.impl.xs.XMLSchemaValidator.handleStartElement(XMLSchemaValidator.java:1831)&lt;br /&gt;&amp;#9;at com.sun.org.apache.xerces.internal.impl.xs.XMLSchemaValidator.startElement(XMLSchemaValidator.java:705)&lt;br /&gt;&amp;#9;at com.sun.org.apache.xerces.internal.jaxp.validation.ValidatorHandlerImpl.startElement(ValidatorHandlerImpl.java:335)&lt;br /&gt;&amp;#9;at com.sun.org.apache.xml.internal.serializer.ToXMLSAXHandler.closeStartTag(ToXMLSAXHandler.java:205)&lt;br /&gt;&amp;#9;at com.sun.org.apache.xml.internal.serializer.ToXMLSAXHandler.characters(ToXMLSAXHandler.java:524)&lt;br /&gt;&amp;#9;at com.sun.org.apache.xml.internal.serializer.ToXMLSAXHandler.characters(ToXMLSAXHandler.java:467)&lt;br /&gt;&amp;#9;at com.sun.org.apache.xalan.internal.xsltc.trax.DOM2TO.parse(DOM2TO.java:229)&lt;br /&gt;&amp;#9;at com.sun.org.apache.xalan.internal.xsltc.trax.DOM2TO.parse(DOM2TO.java:215)&lt;br /&gt;&amp;#9;at com.sun.org.apache.xalan.internal.xsltc.trax.DOM2TO.parse(DOM2TO.java:215)&lt;br /&gt;&amp;#9;at com.sun.org.apache.xalan.internal.xsltc.trax.DOM2TO.parse(DOM2TO.java:215)&lt;br /&gt;&amp;#9;at com.sun.org.apache.xalan.internal.xsltc.trax.DOM2TO.parse(DOM2TO.java:121)&lt;br /&gt;&amp;#9;at com.sun.org.apache.xalan.internal.xsltc.trax.DOM2TO.parse(DOM2TO.java:85)&lt;br /&gt;&amp;#9;at com.sun.org.apache.xalan.internal.xsltc.trax.TransformerImpl.transformIdentity(TransformerImpl.java:615)&lt;br /&gt;&amp;#9;at com.sun.org.apache.xalan.internal.xsltc.trax.TransformerImpl.transform(TransformerImpl.java:661)&lt;br /&gt;&amp;#9;at com.sun.org.apache.xalan.internal.xsltc.trax.TransformerImpl.transform(TransformerImpl.java:300)&lt;br /&gt;&amp;#9;at com.sun.org.apache.xerces.internal.jaxp.validation.ValidatorImpl.process(ValidatorImpl.java:220)&lt;br /&gt;&amp;#9;at com.sun.org.apache.xerces.internal.jaxp.validation.ValidatorImpl.validate(ValidatorImpl.java:141)&lt;br /&gt;&amp;#9;at javax.xml.validation.Validator.validate(Validator.java:82)&lt;br /&gt;&amp;#9;at XmlValidateSchema.main(XmlValidateSchema.java:31)&lt;/pre&gt;&lt;br /&gt;Thanks to the combination of LiveCode and Java, we can develop cross-platform solution quickly, without having to give up the power of existing libraries. Unfortunately, loading Java every time for a shell call is not the optimal solution, so in another post, we'll investigate how we can run Java code using LiveCode's 'process' communication. Stay tuned...&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5092807049363133934-279539435126655575?l=quartam.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://quartam.blogspot.com/feeds/279539435126655575/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=5092807049363133934&amp;postID=279539435126655575' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5092807049363133934/posts/default/279539435126655575'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5092807049363133934/posts/default/279539435126655575'/><link rel='alternate' type='text/html' href='http://quartam.blogspot.com/2010/12/xml-validation-using-schema.html' title='XML validation using a Schema'/><author><name>Quartam Software - Jan Schenkel</name><uri>http://www.blogger.com/profile/08312925714131118786</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://4.bp.blogspot.com/_NlL9VZsJHto/TR4HGCFvZKI/AAAAAAAAAC8/ZMOqiZ_9O8U/s72-c/XMLSchema-0.png' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5092807049363133934.post-8616533495560953283</id><published>2010-12-30T05:38:00.000-08:00</published><updated>2010-12-30T06:00:40.925-08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='PDF'/><category scheme='http://www.blogger.com/atom/ns#' term='java'/><category scheme='http://www.blogger.com/atom/ns#' term='livecode'/><category scheme='http://www.blogger.com/atom/ns#' term='iText'/><category scheme='http://www.blogger.com/atom/ns#' term='example code'/><title type='text'>Stamping PDF files</title><content type='html'>In a &lt;a href="http://quartam.blogspot.com/2010/12/concatenating-pdf-files.html"&gt;previous post&lt;/a&gt;, I examined how we can use &lt;a href="http://www.runrev.com/products/livecode/"&gt;LiveCode&lt;/a&gt; and the Java-based &lt;a href="http://itextpdf.com/"&gt;iText&lt;/a&gt; library to concatenate a series of existing PDF files into a single PDF file. Now we will examine how we can 'stamp' a PDF file with an image using the same technique.&lt;br /&gt;&lt;br /&gt;The first thing to code is the Java class that we will call using the shell function. Here's what I came up with:&lt;br /&gt;&lt;pre language="Java"&gt;import java.io.FileOutputStream;&lt;br /&gt;import java.io.IOException;&lt;br /&gt;import java.io.OutputStream;&lt;br /&gt;&lt;br /&gt;import com.lowagie.text.DocumentException;&lt;br /&gt;import com.lowagie.text.Image;&lt;br /&gt;import com.lowagie.text.Rectangle;&lt;br /&gt;import com.lowagie.text.pdf.PdfContentByte;&lt;br /&gt;import com.lowagie.text.pdf.PdfReader;&lt;br /&gt;import com.lowagie.text.pdf.PdfStamper;&lt;br /&gt;&lt;br /&gt;public class StampPdfFile {&lt;br /&gt;  public static void main(String[] args) throws IOException, DocumentException {&lt;br /&gt;    final String inputFile = args[0];&lt;br /&gt;    final String outputFile = args[1];&lt;br /&gt;    final String imageFile = args[2];&lt;br /&gt;    final String[] coords = args[3].split(",");&lt;br /&gt;    final PdfReader inputReader = new PdfReader(inputFile);&lt;br /&gt;    final OutputStream outputStream = new FileOutputStream(outputFile);&lt;br /&gt;    final PdfStamper outputStamper = new PdfStamper(inputReader, outputStream);&lt;br /&gt;    final int pageCount = inputReader.getNumberOfPages();&lt;br /&gt;    final Image image = Image.getInstance(imageFile);&lt;br /&gt;    final int left = Integer.parseInt(coords[0]);&lt;br /&gt;    final int top = Integer.parseInt(coords[1]);&lt;br /&gt;    final int right = Integer.parseInt(coords[2]);&lt;br /&gt;    final int bottom = Integer.parseInt(coords[3]);&lt;br /&gt;    final int height = bottom - top;&lt;br /&gt;    final int width = right - left;&lt;br /&gt;    image.scaleToFit(width, height);&lt;br /&gt;    for (int pageIndex = 1; pageIndex &lt;= pageCount; pageIndex++) {&lt;br /&gt;      final PdfContentByte overContent = outputStamper.getOverContent(pageIndex);&lt;br /&gt;      final Rectangle pageSize = inputReader.getPageSize(pageIndex);&lt;br /&gt;      image.setAbsolutePosition(left, pageSize.getHeight() - bottom);&lt;br /&gt;      overContent.addImage(image);&lt;br /&gt;    }&lt;br /&gt;    outputStamper.close();&lt;br /&gt;  }&lt;br /&gt;}&lt;/pre&gt;&lt;br /&gt;In a nutshell, the first parameter is the input file, the second the output file, the third the image file, and the fourth parameter is a comma-separated list of coordinates making up the target rectangle. As usual, the code is a tad lazy when it comes to faulty input parameters and exception handling - if there's a mistake you'll simply get the stacktrace as the output of the shell command.&lt;br /&gt;&lt;br /&gt;The most important bit is in the loop over the pages, where we use the outputStamper.getOverContent() method to draw our image on top of the existing content. If you'd rather have the image in the back, as a watermark, you would use the outputStamper.getUnderContent() methopd instead. Also note that setting the image position coordinate system works from the bottomLeft of the page, so we have to use the original page height and subtract the bottom coordinate from it.&lt;br /&gt;&lt;br /&gt;Now we can proceed with writing a LiveCode button script:&lt;br /&gt;&lt;pre language=LiveCode"&gt;on mouseUp&lt;br /&gt;   --&gt; determine the input, output and image files&lt;br /&gt;   local tInputFile, tOutputFile, tImageFile&lt;br /&gt;   put ShellPath(AbsolutePathFromStack("demo1.pdf")) \&lt;br /&gt;          into tInputFile&lt;br /&gt;   put ShellPath(AbsolutePathFromStack("stamp.pdf")) \&lt;br /&gt;          into tOutputFile&lt;br /&gt;   put ShellPath(AbsolutePathFromStack("Template.png")) \&lt;br /&gt;          into tImageFile&lt;br /&gt;   --&gt; determine the image target rectangle&lt;br /&gt;   local tImageRect&lt;br /&gt;   put quote &amp; "10,10,103,87" &amp; quote \&lt;br /&gt;          into tImageRect&lt;br /&gt;   --&gt; determine the class path&lt;br /&gt;   local tClassPath&lt;br /&gt;   if the platform is "Win32" then&lt;br /&gt;      put ".;iText-2.1.7.jar" into tClassPath&lt;br /&gt;   else&lt;br /&gt;      put ".:iText-2.1.7.jar" into tClassPath&lt;br /&gt;   end if&lt;br /&gt;   --&gt; assemble the shell command&lt;br /&gt;   local tShellCommand&lt;br /&gt;   put "java -classpath" &amp;&amp; tClassPath &amp;&amp; \&lt;br /&gt;          "StampPdfFile" &amp;&amp; \&lt;br /&gt;          tInputFile &amp;&amp; \&lt;br /&gt;          tOutputFile &amp;&amp; \&lt;br /&gt;          tImageFile &amp;&amp; \&lt;br /&gt;          tImageRect \&lt;br /&gt;          into tShellCommand&lt;br /&gt;   --&gt; execute the shell command&lt;br /&gt;   local tHideConsoleWindows, tDefaultFolder, tShellResult&lt;br /&gt;   put the hideConsoleWindows into tHideConsoleWindows&lt;br /&gt;   set the hideConsoleWindows to true&lt;br /&gt;   put the defaultFolder into tDefaultFolder&lt;br /&gt;   set the defaultFolder to AbsolutePathFromStack()&lt;br /&gt;   put shell(tShellCommand) into tShellResult&lt;br /&gt;   set the defaultFolder to tDefaultFolder&lt;br /&gt;   set the hideConsoleWindows to tHideConsoleWindows&lt;br /&gt;   if tShellResult is not empty then&lt;br /&gt;      answer error tShellResult&lt;br /&gt;   end if&lt;br /&gt;end mouseUp&lt;br /&gt;&lt;br /&gt;function AbsolutePathFromStack pFileName&lt;br /&gt;   local tAbsolutePath&lt;br /&gt;   put the effective filename of this stack into tAbsolutePath&lt;br /&gt;   set the itemDelimiter to slash&lt;br /&gt;   if pFileName is not empty then&lt;br /&gt;      put pFileName into item -1 of tAbsolutePath&lt;br /&gt;   else&lt;br /&gt;      delete item -1 of tAbsolutePath&lt;br /&gt;   end if&lt;br /&gt;   return tAbsolutePath&lt;br /&gt;end AbsolutePathFromStack&lt;br /&gt;&lt;br /&gt;function ShellPath pPath&lt;br /&gt;   if the platform is "Win32" then&lt;br /&gt;      put quote &amp; pPath &amp; quote into pPath&lt;br /&gt;   else&lt;br /&gt;      replace space with backslash &amp; space in pPath&lt;br /&gt;   end if&lt;br /&gt;   return pPath&lt;br /&gt;end ShellPath&lt;/pre&gt;&lt;br /&gt;Click the button, and it happily takes the existing PDF files (demo1.pdf), paints the image (Template.png) on top of all pages, and writes a new PDF file (stamp.pdf) in the same folder as our stack. There we have it, another example of using iText from within LiveCode.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5092807049363133934-8616533495560953283?l=quartam.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://quartam.blogspot.com/feeds/8616533495560953283/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=5092807049363133934&amp;postID=8616533495560953283' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5092807049363133934/posts/default/8616533495560953283'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5092807049363133934/posts/default/8616533495560953283'/><link rel='alternate' type='text/html' href='http://quartam.blogspot.com/2010/12/stamping-pdf-files.html' title='Stamping PDF files'/><author><name>Quartam Software - Jan Schenkel</name><uri>http://www.blogger.com/profile/08312925714131118786</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5092807049363133934.post-8319601462716211596</id><published>2010-12-29T01:11:00.000-08:00</published><updated>2010-12-30T05:53:39.663-08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='PDF'/><category scheme='http://www.blogger.com/atom/ns#' term='java'/><category scheme='http://www.blogger.com/atom/ns#' term='livecode'/><category scheme='http://www.blogger.com/atom/ns#' term='iText'/><category scheme='http://www.blogger.com/atom/ns#' term='example code'/><title type='text'>Concatenating PDF files</title><content type='html'>Since the advent of &lt;a href="http://www.runrev.com/products/livecode/new-in-45/"&gt;LiveCode 4.5&lt;/a&gt;, developers have the ability to 'print' stack content directly to PDF files. And if you need pin-point control over what goes where, you can use &lt;a href="http://www.quartam.com"&gt;Quartam PDF Library&lt;/a&gt; to generate PDF files from scripts. That's great if you are in full control of the content, but what if you need to work with existing PDF files? In the next few posts, we will examine how you can tap into the power of the Java-based &lt;a href="http://itextpdf.com"&gt;iText library&lt;/a&gt; from LiveCode.&lt;br /&gt;So let's start by &lt;a href="http://sourceforge.net/projects/itext/files/iText/"&gt;downloading a copy of iText&lt;/a&gt; version 2.1.7 - do not use version 5.x as the API changed and the following example code won't work.&lt;br /&gt;&lt;br /&gt;The first question is: how can we execute Java code from LiveCode? The simplest solution is the &lt;a href="http://docs.runrev.com/Function/shell"&gt;shell function&lt;/a&gt;: it allows you to execute DOS or Unix commands, as if you typed them in from the command line. Note that on Windows, using this function will show a DOS window, but you can control that by setting the &lt;a href="http://docs.runrev.com/Property/hideConsoleWindows"&gt;hideConsoleWindows property&lt;/a&gt; before calling the shell function.&lt;br /&gt;You can test it out by simply executing the following line from the message box:&lt;br /&gt;&lt;pre language="LiveCode"&gt;  answer shell("java -version")&lt;/pre&gt;&lt;br /&gt;The second question is: what sort of Java code do we need to write? Well, I fired up a copy of Eclipse, started a new project, and created a new class 'ConcatPdfFiles' in the default package. Then I grabbed my paper copy of &lt;a href="http://www.manning.com/lowagie/"&gt;iText in action (first edition)&lt;/a&gt; and flipped to page 64 as this contains the examples for concatenating PDF files. A little bit of thinking, and I derived the following code:&lt;br /&gt;&lt;pre language="Java"&gt;import java.io.FileOutputStream;&lt;br /&gt;import java.io.IOException;&lt;br /&gt;import java.io.OutputStream;&lt;br /&gt;&lt;br /&gt;import com.lowagie.text.Document;&lt;br /&gt;import com.lowagie.text.DocumentException;&lt;br /&gt;import com.lowagie.text.pdf.PdfCopy;&lt;br /&gt;import com.lowagie.text.pdf.PdfReader;&lt;br /&gt;&lt;br /&gt;public class ConcatPdfFiles {&lt;br /&gt; public static void main(String[] args) throws DocumentException, IOException {&lt;br /&gt;  final String outputFilePath = args[0];&lt;br /&gt;  final OutputStream outputStream = new FileOutputStream(outputFilePath);&lt;br /&gt;  final Document outputDocument = new Document();&lt;br /&gt;  final PdfCopy outputCopy = new PdfCopy(outputDocument, outputStream);&lt;br /&gt;  outputDocument.open();&lt;br /&gt;  for (int i = 1; i &lt; args.length; i++) {&lt;br /&gt;   final PdfReader inputPdfReader = new PdfReader(args[i]);&lt;br /&gt;   final int pageCount = inputPdfReader.getNumberOfPages();&lt;br /&gt;   for (int pageIndex = 0; pageIndex &lt; pageCount; pageIndex++) {&lt;br /&gt;    outputCopy.addPage(outputCopy.getImportedPage(inputPdfReader, pageIndex + 1));&lt;br /&gt;   }&lt;br /&gt;  }&lt;br /&gt;  outputDocument.close();&lt;br /&gt; }&lt;br /&gt;}&lt;/pre&gt;&lt;br /&gt;As you can see, the code is a bit lazy when it comes to exception handling: I just let the exceptions get thrown, and this will be the output of our shell call if something goes wrong. Note also that the first argument is the output file, followed by the input files that you want to concatenate into the output file.&lt;br /&gt;&lt;br /&gt;More importantly, at this point in time, the code doesn't compile. The problem is, we haven't yet told Eclipse where that iText-2.1.7.jar library file is, so compilation fails. This is sometimes referred to as 'classpath hell' - you have to give Java a list of paths where it can find the necessary additional libraries, not just at compile time but also at runtime as we'll see later.&lt;br /&gt;Because I like to keep everything together in my Java projects, I added a new 'lib' folder to my project, and copied the iText2.1.7.jar file into it. At that point, you can use the contextual menu on the iText.2.1.7.jar file, and add it to the Build Path. Now the code I showed earlier compiles just fine, and we can proceed to the next stage.&lt;br /&gt;&lt;br /&gt;The third question is: how do we put everything together in LiveCode? We'll begin by putting all the necessary parts into a single folder: the iText-2.1.7.jar library file, the ConcatPdfFiles.class compiled file and two example PDF files (demo1.pdf and demo2.pdf). Then we fire up LiveCode, create a new stack 'ConcatPdfFiles' and save it in the same folder as the other files, naming it "ConcatPdfFiles.liveCode'. Now we can drop a button onto the stack and start scripting.&lt;br /&gt;&lt;br /&gt;Now we need to determine the correct command to be executed by the shell function. It should look something like:&lt;br /&gt;&lt;pre&gt;java -classpath &amp;lt;class-path&amp;gt; ConcatPdfFiles &amp;lt;output-file&amp;gt; &amp;lt;input-file-1&amp;gt; &amp;lt;input-file-2&amp;gt; ...&lt;/pre&gt;&lt;br /&gt;The java executable needs the correct classpath, and we need to pass in compatible file paths. &lt;br /&gt;&lt;br /&gt;Let's start with the classpath. This is a list of places that java needs to look for its .class files - as separate files in folders, or stored together in a .jar file. And for extra fun, the separator character is a colon on Unix-based platforms, and a semicolon on Windows. You can have relative paths in this classpath, and '.' (period) is short for the &lt;span style="font-style:italic;"&gt;current directory&lt;/span&gt;. So rather than building a long class path, we can circumvent the issue by setting the &lt;a href="http://docs.runrev.com/Property/defaultFolder"&gt;defaultFolder property&lt;/a&gt; to change the working directory before calling the shell function. Then our classpath can be as short as: &lt;pre&gt;.:iText-2.1.7.jar&lt;/pre&gt; on MacOS X/Linux and &lt;pre&gt;.;iText-2.1.7.jar&lt;/pre&gt; on Windows.&lt;br /&gt;&lt;br /&gt;The next bit is compatible file paths. The good news: LiveCode uses a '/' (slash) as separator, regardless of the underlying platform, and Java is more than happy to accept '/' in a path, even when it's running on Windows. However, if there are spaces in the path, we need to save them by putting quotes around the path on Windows, and escaping the spaces with a backslash on Unix-based platforms.&lt;br /&gt;And to determine the paths relative to the stack's location on your hard disk, we'll need a helper function that uses the &lt;a href="http://docs.runrev.com/Property/filename-of-stack"&gt;effective filename property&lt;/a&gt; of our stack.&lt;br /&gt;&lt;br /&gt;So finally, we have a button script as follows:&lt;br /&gt;&lt;pre language="LiveCode"&gt;on mouseUp&lt;br /&gt;   --&gt; determine the input and output files&lt;br /&gt;   local tInputFiles, tOutputFile&lt;br /&gt;   put ShellPath(AbsolutePathFromStack("demo1.pdf")) &amp;&amp; \&lt;br /&gt;          ShellPath(AbsolutePathFromStack("demo2.pdf")) \&lt;br /&gt;          into tInputFiles&lt;br /&gt;   put ShellPath(AbsolutePathFromStack("output.pdf")) \&lt;br /&gt;          into tOutputFile&lt;br /&gt;   --&gt; determine the class path&lt;br /&gt;   local tClassPath&lt;br /&gt;   if the platform is "Win32" then&lt;br /&gt;      put ".;iText-2.1.7.jar" into tClassPath&lt;br /&gt;   else&lt;br /&gt;      put ".:iText-2.1.7.jar" into tClassPath&lt;br /&gt;   end if&lt;br /&gt;   --&gt; assemble the shell command&lt;br /&gt;   local tShellCommand&lt;br /&gt;   put "java -classpath" &amp;&amp; tClassPath &amp;&amp; \&lt;br /&gt;          "ConcatPdfFiles" &amp;&amp; \&lt;br /&gt;          tOutputFile &amp;&amp; tInputFiles \&lt;br /&gt;          into tShellCommand&lt;br /&gt;   --&gt; execute the shell command&lt;br /&gt;   local tHideConsoleWindows, tDefaultFolder, tShellResult&lt;br /&gt;   put the hideConsoleWindows into tHideConsoleWindows&lt;br /&gt;   set the hideConsoleWindows to true&lt;br /&gt;   put the defaultFolder into tDefaultFolder&lt;br /&gt;   set the defaultFolder to AbsolutePathFromStack()&lt;br /&gt;   put shell(tShellCommand) into tShellResult&lt;br /&gt;   set the defaultFolder to tDefaultFolder&lt;br /&gt;   set the hideConsoleWindows to tHideConsoleWindows&lt;br /&gt;   if tShellResult is not empty then&lt;br /&gt;      answer error tShellResult&lt;br /&gt;   end if&lt;br /&gt;end mouseUp&lt;br /&gt;&lt;br /&gt;function AbsolutePathFromStack pFileName&lt;br /&gt;   local tAbsolutePath&lt;br /&gt;   put the effective filename of this stack into tAbsolutePath&lt;br /&gt;   set the itemDelimiter to slash&lt;br /&gt;   if pFileName is not empty then&lt;br /&gt;      put pFileName into item -1 of tAbsolutePath&lt;br /&gt;   else&lt;br /&gt;      delete item -1 of tAbsolutePath&lt;br /&gt;   end if&lt;br /&gt;   return tAbsolutePath&lt;br /&gt;end AbsolutePathFromStack&lt;br /&gt;&lt;br /&gt;function ShellPath pPath&lt;br /&gt;   if the platform is "Win32" then&lt;br /&gt;      put quote &amp; pPath &amp; quote into pPath&lt;br /&gt;   else&lt;br /&gt;      replace space with backslash &amp; space in pPath&lt;br /&gt;   end if&lt;br /&gt;   return pPath&lt;br /&gt;end ShellPath&lt;/pre&gt;&lt;br /&gt;Click the button, and it happily concatenates the two PDF files (demo1.pdf and demo2.pdf) into a single PDF file (output.pdf) in the same folder as our stack. There we have it, our first use of iText from within LiveCode.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5092807049363133934-8319601462716211596?l=quartam.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://quartam.blogspot.com/feeds/8319601462716211596/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=5092807049363133934&amp;postID=8319601462716211596' title='2 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5092807049363133934/posts/default/8319601462716211596'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5092807049363133934/posts/default/8319601462716211596'/><link rel='alternate' type='text/html' href='http://quartam.blogspot.com/2010/12/concatenating-pdf-files.html' title='Concatenating PDF files'/><author><name>Quartam Software - Jan Schenkel</name><uri>http://www.blogger.com/profile/08312925714131118786</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5092807049363133934.post-526090851181860245</id><published>2010-11-21T08:43:00.001-08:00</published><updated>2010-11-21T21:41:34.014-08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='PDF'/><category scheme='http://www.blogger.com/atom/ns#' term='java'/><category scheme='http://www.blogger.com/atom/ns#' term='livecode'/><category scheme='http://www.blogger.com/atom/ns#' term='conference'/><category scheme='http://www.blogger.com/atom/ns#' term='devoxx'/><category scheme='http://www.blogger.com/atom/ns#' term='books'/><title type='text'>I went to the Devoxx 2010 conference and...</title><content type='html'>&lt;span style="font-style:italic;"&gt;and I got the t-shirt to prove it&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://3.bp.blogspot.com/_NlL9VZsJHto/TOlaE-JQ8ZI/AAAAAAAAACw/WcoEdRU9OQA/s1600/devoxxtshirt.jpg"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 320px; height: 240px;" src="http://3.bp.blogspot.com/_NlL9VZsJHto/TOlaE-JQ8ZI/AAAAAAAAACw/WcoEdRU9OQA/s320/devoxxtshirt.jpg" border="0" alt=""id="BLOGGER_PHOTO_ID_5542059857903939986" /&gt;&lt;/a&gt;&lt;br /&gt;100% Javaholic was the motto of this year's Java developer conference, organised by the Belkgian JUG and held in Antwerp.&lt;br /&gt;Of course, I wasn't there to collect the t-shirt - unlike some others who shall remain unnamed and went from booth to booth to fetch a t-shirt, even if they weren't interested in the product. You know who you are :-p&lt;br /&gt;&lt;br /&gt;&lt;span style="font-style:italic;"&gt;and I met Bruno Lowagie&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;For those who don't know &lt;a href="http://www.lowagie.com/"&gt;Bruno Lowagie&lt;/a&gt;, he's the primary developer behind the &lt;a href="http://itextpdf.com/"&gt;iText project&lt;/a&gt;, an open-source library for producing PDF files using Java. There must be something in the water around here, as he lives about 30 miles from my house - two Belgians, both writing PDF libraries - what are the odds?&lt;br /&gt;As he was busy signing copies of &lt;a href="http://www.manning.com/lowagie2/"&gt;iText in Action (second edition)&lt;/a&gt; the conversation was rather short, but he signed my copy of his book with "To a fellow PDF developer" - what a compliment :-)&lt;br /&gt;&lt;br /&gt;&lt;span style="font-style:italic;"&gt;and I managed to squeeze LiveCode onto a whiteboard&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://lh5.ggpht.com/_ZrYb6GjXXeM/TObIKUz1KtI/AAAAAAAAKqc/vlk5GOjRLg4/s912/DSC_2112.jpg"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 912px; height: 607px;" src="http://lh5.ggpht.com/_ZrYb6GjXXeM/TObIKUz1KtI/AAAAAAAAKqc/vlk5GOjRLg4/s912/DSC_2112.jpg" border="0" alt="" /&gt;&lt;/a&gt;&lt;br /&gt;Given that someone actually added 'HyperTalk :-)' to the board right behind my entry, I think I wasn't the only guy there who knows LiveCode. Next time, poke me and I'll buy you a drink.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-style:italic;"&gt;and had a very good time overall&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;Plenty of interesting sessions, friendly environment and excellent organization. I'll definitely go back next time I get the chance.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5092807049363133934-526090851181860245?l=quartam.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://quartam.blogspot.com/feeds/526090851181860245/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=5092807049363133934&amp;postID=526090851181860245' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5092807049363133934/posts/default/526090851181860245'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5092807049363133934/posts/default/526090851181860245'/><link rel='alternate' type='text/html' href='http://quartam.blogspot.com/2010/11/i-want-to-devoxx-2010-conference-and.html' title='I went to the Devoxx 2010 conference and...'/><author><name>Quartam Software - Jan Schenkel</name><uri>http://www.blogger.com/profile/08312925714131118786</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://3.bp.blogspot.com/_NlL9VZsJHto/TOlaE-JQ8ZI/AAAAAAAAACw/WcoEdRU9OQA/s72-c/devoxxtshirt.jpg' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5092807049363133934.post-8382782984872327862</id><published>2010-09-20T11:54:00.000-07:00</published><updated>2010-09-20T12:15:42.284-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='revtalk'/><category scheme='http://www.blogger.com/atom/ns#' term='livecode'/><category scheme='http://www.blogger.com/atom/ns#' term='runrev'/><category scheme='http://www.blogger.com/atom/ns#' term='revolution'/><title type='text'>Revolution is now LiveCode</title><content type='html'>Earlier today, &lt;a href="http://www.runrev.com"&gt;RunRev&lt;/a&gt; announced the &lt;a href="http://runrev.com/company/press-room/latest-news-archive/runrev-introduces-livecode-software-development-platform-with-pre-release-version-for-iphoneipad-development/"&gt;availability of LiveCode 4.5&lt;/a&gt; - the new name and version for the development platform Revolution.&lt;br /&gt;&lt;br /&gt;If you've come here and don't know what LiveCode/Revolution is: it is a cross-platform development tool for Windows, MacOSX and Linux, sporting a very-high-level-language that allows you to create working solutions in far less time than other tools and languages. The LiveCode Web browser plug-in works very similar to Flash and Silverlight. There are also pre-releases available of LiveCode Server (comparable to a PHP engine) and LiveCode for iOS (yep, IPhone, iPad and iPod Touch).&lt;br /&gt;&lt;br /&gt;Whenever I get a chance, I'll use LiveCode to put together a quick tool for my own use, or a prototype/mockup for something that will eventually be developed in another language like Progress OpenEdge ABL or Java. I also offer Quartam Reports for LiveCode, the essential report generator, and Quartam PDF Library, for those occasions when you need to go beyond 'print to pdf file'.&lt;br /&gt;&lt;br /&gt;Where Java is built around a relatively small platform-specific Virtual Machine implementation, with a huge bytecode library on top, LiveCode has a far more optimized yet surprisingly lean core implementation for each supported operating system, and a rich language that means you'll type fewer lines of code to accomplish the same result.&lt;br /&gt;&lt;br /&gt;I like the name change and the clearer product line-up. And I like how this new version is faster than ever before. Cheers to the RunRev team for wrapping it up after the setback concerning the iPhone/iPad implementation. Combined with Apple's recent liberation of developer tools, the future looks decidedly LiveCode!&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5092807049363133934-8382782984872327862?l=quartam.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://quartam.blogspot.com/feeds/8382782984872327862/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=5092807049363133934&amp;postID=8382782984872327862' title='3 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5092807049363133934/posts/default/8382782984872327862'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5092807049363133934/posts/default/8382782984872327862'/><link rel='alternate' type='text/html' href='http://quartam.blogspot.com/2010/09/revolution-is-now-livecode.html' title='Revolution is now LiveCode'/><author><name>Quartam Software - Jan Schenkel</name><uri>http://www.blogger.com/profile/08312925714131118786</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>3</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5092807049363133934.post-5975474710171641326</id><published>2010-09-12T09:19:00.000-07:00</published><updated>2010-09-20T12:29:15.830-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='revtalk'/><category scheme='http://www.blogger.com/atom/ns#' term='livecode'/><category scheme='http://www.blogger.com/atom/ns#' term='dtd'/><category scheme='http://www.blogger.com/atom/ns#' term='revolution'/><category scheme='http://www.blogger.com/atom/ns#' term='xml'/><category scheme='http://www.blogger.com/atom/ns#' term='example code'/><category scheme='http://www.blogger.com/atom/ns#' term='web'/><title type='text'>XML validation using a DTD and Versions</title><content type='html'>In a &lt;a href="http://quartam.blogspot.com/2010/09/xml-validation-using-dtd.html"&gt;previous post&lt;/a&gt;, I invetigated how we could validate an XML file in revTalk by using a DTD. As we all know, requirements evolve and our software needs to adapt likewise, especially at the integration end-points. So if we need to accept more data, we should (a) know that it's coming, and (b) make sure it's there before we process the incoming data.&lt;br /&gt;&lt;br /&gt;How do we do that? Well, as we've seen beofre, we can check the structure of the XML file. And to make sure we check it correctly, we should introduce a &lt;span style="font-style:italic;"&gt;specification version&lt;/span&gt; for our XML structure, and attach it to the &lt;span style="font-style:italic;"&gt;root node&lt;/span&gt; of the document as an attribute. Then all we need to do is check the root node, extract its SpecificationVersion attribute, and we can apply the correct DTD validation. &lt;br /&gt;&lt;br /&gt;Doesn't sound too complicated, does it? Let's expand our current stack design a bit so that it looks like this:&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://4.bp.blogspot.com/_NlL9VZsJHto/TI0HYenPCdI/AAAAAAAAACI/yWIp7OQjf9Q/s1600/XMLDTD-4.png"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 213px; height: 320px;" src="http://4.bp.blogspot.com/_NlL9VZsJHto/TI0HYenPCdI/AAAAAAAAACI/yWIp7OQjf9Q/s320/XMLDTD-4.png" border="0" alt=""id="BLOGGER_PHOTO_ID_5516073235714607570" /&gt;&lt;/a&gt;&lt;br /&gt;As you can see, the field "DTD" was renamed to "DTD 1.0" and another field "DTD 1.1" was added to hold the DTD for specification version 1.1; finally, I moved down the "Validate" button and modified its script:&lt;br /&gt;&lt;pre language="revtalk"&gt;&lt;br /&gt;constant kMaxVersion = 1.1&lt;br /&gt;&lt;br /&gt;on mouseUp&lt;br /&gt;   -- Load the XML into a local variable&lt;br /&gt;   local tXmlText&lt;br /&gt;   put field "XML" into tXmlText&lt;br /&gt;   -- Parse the XML text into a Tree&lt;br /&gt;   local tXmlTree&lt;br /&gt;   put revCreateXmlTree(tXmlText, \&lt;br /&gt;          false, \&lt;br /&gt;          true,  \&lt;br /&gt;          false) \&lt;br /&gt;          into tXmlTree&lt;br /&gt;   if tXmlTree is not an integer then&lt;br /&gt;      answer error \&lt;br /&gt;             "There is an error in the XML structure" &amp; return &amp; \&lt;br /&gt;             tXmlTree  -- contains the full error message&lt;br /&gt;   else&lt;br /&gt;      -- Validate the root node&lt;br /&gt;      local tRootNode&lt;br /&gt;      put revXmlRootNode(tXmlTree) into tRootNode&lt;br /&gt;      if tRootNode is not "RootNode" then&lt;br /&gt;         answer error \&lt;br /&gt;                "The XML root node should be 'RootNode'"&lt;br /&gt;      else&lt;br /&gt;         -- Validate the SpecVersion&lt;br /&gt;         local tSpecVersion&lt;br /&gt;         put revXmlAttribute(tXmlTree, tRootNode, \&lt;br /&gt;                "SpecificationVersion") into tSpecVersion&lt;br /&gt;         if tSpecVersion begins with "xmlerr" then&lt;br /&gt;            answer error \&lt;br /&gt;                   "The SpecificationVersion is missing"&lt;br /&gt;         else if tSpecVersion &gt; kMaxVersion then&lt;br /&gt;            answer error \&lt;br /&gt;                   "The SpecificationVersion " &amp;&amp; tSpecVersion &amp;&amp; \&lt;br /&gt;                   "is newer than" &amp;&amp; kMaxVersion&lt;br /&gt;         else&lt;br /&gt;            -- Load the corresponding DTD&lt;br /&gt;            local tDtdText&lt;br /&gt;            if tSpecVersion is 1.0 then&lt;br /&gt;               put field "DTD 1.0" into tDtdText&lt;br /&gt;            else&lt;br /&gt;               put field "DTD 1.1" into tDtdText&lt;br /&gt;            end if&lt;br /&gt;            -- Validate the XML against the DTD&lt;br /&gt;            local tValidationResult&lt;br /&gt;            put revXmlValidateDTD(tXmlTree, tDtdText) \&lt;br /&gt;                   into tValidationResult&lt;br /&gt;            if tValidationResult is not empty then&lt;br /&gt;               answer error \&lt;br /&gt;                      "The XML structure does not conform" &amp; \&lt;br /&gt;                      return &amp; tValidationResult&lt;br /&gt;            else&lt;br /&gt;               answer information "The XML conforms to the DTD"&lt;br /&gt;            end if&lt;br /&gt;         end if&lt;br /&gt;      end if&lt;br /&gt;      -- Cleanup&lt;br /&gt;      revDeleteXmlTree tXmlTree&lt;br /&gt;   end if&lt;br /&gt;end mouseUp&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;So how does this new version work?&lt;br /&gt;- first, it parses the XML document&lt;br /&gt;- next, it verifies the root node&lt;br /&gt;- next, it checks the specification version&lt;br /&gt;- next, it loads the appropriate DTD&lt;br /&gt;- finally, it validates the XML against the DTD&lt;br /&gt;&lt;br /&gt;If we test it, it correctly informs us that the XML document conforms to our specification version 1.1. What happens if we change the SpecificationVersion to 1.2?&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://1.bp.blogspot.com/_NlL9VZsJHto/TI0HZPpVoVI/AAAAAAAAACQ/ikO14Qb_Wk0/s1600/XMLDTD-5.png"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 213px; height: 320px;" src="http://1.bp.blogspot.com/_NlL9VZsJHto/TI0HZPpVoVI/AAAAAAAAACQ/ikO14Qb_Wk0/s320/XMLDTD-5.png" border="0" alt=""id="BLOGGER_PHOTO_ID_5516073248876765522" /&gt;&lt;/a&gt;&lt;br /&gt;Then we get this error message:&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://1.bp.blogspot.com/_NlL9VZsJHto/TI0HZuOv9_I/AAAAAAAAACY/1IAx_b7Hu_A/s1600/XMLDTD-6.png"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 320px; height: 102px;" src="http://1.bp.blogspot.com/_NlL9VZsJHto/TI0HZuOv9_I/AAAAAAAAACY/1IAx_b7Hu_A/s320/XMLDTD-6.png" border="0" alt=""id="BLOGGER_PHOTO_ID_5516073257086744562" /&gt;&lt;/a&gt;&lt;br /&gt;And finally, what happens if we change the SpecificationVersion to the original 1.0?&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://4.bp.blogspot.com/_NlL9VZsJHto/TI0HaCzIuBI/AAAAAAAAACg/G_Hy9RwV6Kw/s1600/XMLDTD-7.png"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 213px; height: 320px;" src="http://4.bp.blogspot.com/_NlL9VZsJHto/TI0HaCzIuBI/AAAAAAAAACg/G_Hy9RwV6Kw/s320/XMLDTD-7.png" border="0" alt=""id="BLOGGER_PHOTO_ID_5516073262608070674" /&gt;&lt;/a&gt;&lt;br /&gt;Then we get this error message:&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://3.bp.blogspot.com/_NlL9VZsJHto/TI0Ha7cI1UI/AAAAAAAAACo/Q5JrnsZ-DBY/s1600/XMLDTD-8.png"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 320px; height: 143px;" src="http://3.bp.blogspot.com/_NlL9VZsJHto/TI0Ha7cI1UI/AAAAAAAAACo/Q5JrnsZ-DBY/s320/XMLDTD-8.png" border="0" alt=""id="BLOGGER_PHOTO_ID_5516073277812430146" /&gt;&lt;/a&gt;&lt;br /&gt;This is a much safer way to check the incoming data in XML format. Unfortunately, a Document Type Definition is quite a limited way of XML validation. So next time, we'll improve our defenses again, by incorporating &lt;span style="font-style:italic;"&gt;XML Schemas&lt;/span&gt;.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5092807049363133934-5975474710171641326?l=quartam.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://quartam.blogspot.com/feeds/5975474710171641326/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=5092807049363133934&amp;postID=5975474710171641326' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5092807049363133934/posts/default/5975474710171641326'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5092807049363133934/posts/default/5975474710171641326'/><link rel='alternate' type='text/html' href='http://quartam.blogspot.com/2010/09/xml-validation-using-dtd-and-versions.html' title='XML validation using a DTD and Versions'/><author><name>Quartam Software - Jan Schenkel</name><uri>http://www.blogger.com/profile/08312925714131118786</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://4.bp.blogspot.com/_NlL9VZsJHto/TI0HYenPCdI/AAAAAAAAACI/yWIp7OQjf9Q/s72-c/XMLDTD-4.png' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5092807049363133934.post-2776247427160927265</id><published>2010-09-07T10:44:00.001-07:00</published><updated>2010-09-07T12:28:43.055-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='revtalk'/><category scheme='http://www.blogger.com/atom/ns#' term='dtd'/><category scheme='http://www.blogger.com/atom/ns#' term='revolution'/><category scheme='http://www.blogger.com/atom/ns#' term='xml'/><category scheme='http://www.blogger.com/atom/ns#' term='example code'/><category scheme='http://www.blogger.com/atom/ns#' term='web'/><title type='text'>XML validation using a DTD</title><content type='html'>One of the positive aspects of the &lt;a href="http://en.wikipedia.org/wiki/XML"&gt;Extensible Markup Language&lt;/a&gt; XML is that it is a flexible way to structure data in a human-readable format, in a cross-platform and technology-independent way. No wonder it is widely used as a way to exchange data between applications, and forms the foundation for &lt;a href="http://en.wikipedia.org/wiki/XML-RPC"&gt;XML-RPC&lt;/a&gt;, &lt;a href="http://en.wikipedia.org/wiki/SOAP"&gt;SOAP&lt;/a&gt; and other &lt;a href="http://en.wikipedia.org/wiki/Web_services"&gt;Web Service&lt;/a&gt; methods.&lt;br /&gt;&lt;br /&gt;But it would be naive to think that every XML document that we get is not only well-formed, but also in the format that we expect it to be, with the right elements and attributes. In this post, we'll examine a strategy to validate incoming XML data in our revTalk application, using a &lt;a href="http://en.wikipedia.org/wiki/Document_Type_Definition"&gt;Document Type Definition&lt;/a&gt; - a.k.a. DTD.&lt;br /&gt;&lt;br /&gt;Part of the XML specification since the very start, a DTD describes the structure of the XML elements and attributes. For more information, I advise you to study the excellent introductory tutorials on &lt;a href="http://w3schools.com/"&gt;W3Schools.com&lt;/a&gt;. We're here to use it from revTalk, so let's start by creating a new stack for the user interface.&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://3.bp.blogspot.com/_NlL9VZsJHto/TIaPH6zfUqI/AAAAAAAAABo/Q2kFlOjMScw/s1600/XMLDTD-0.png"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 303px; height: 320px;" src="http://3.bp.blogspot.com/_NlL9VZsJHto/TIaPH6zfUqI/AAAAAAAAABo/Q2kFlOjMScw/s320/XMLDTD-0.png" border="0" alt=""id="BLOGGER_PHOTO_ID_5514252159968826018" /&gt;&lt;/a&gt;&lt;br /&gt;Simply drop two scrolling fields onto it, name them "XML" and "DTD" respectively, and then group each of them separately so we can put a nice group label on top (I have the memory of a goldfish so I might forget which-is-which ;-) ) Finally drop a button at the bottom of the stack and set its name to "Validate" - and now we're ready to start scripting the button.&lt;br /&gt;&lt;br /&gt;First things first, we need to parse the XML text into an XML tree to use all the rev XML commands and functions.&lt;br /&gt;&lt;pre language="revTalk"&gt;&lt;br /&gt;on mouseUp&lt;br /&gt;   -- Load DTD and XML into local variables&lt;br /&gt;   local tDtdText, tXmlText&lt;br /&gt;   put field "DTD" into tDtdText&lt;br /&gt;   put field "XML" into tXmlText&lt;br /&gt;   -- Parse the XML text into a Tree&lt;br /&gt;   local tXmlTree&lt;br /&gt;   put revCreateXmlTree(tXmlText, \&lt;br /&gt;          false, \ -- must be well-formed&lt;br /&gt;          true,  \ -- create a tree in memory&lt;br /&gt;          false) \ -- no SAX parser messages&lt;br /&gt;          into tXmlTree&lt;br /&gt;   if tXmlTree is not an integer then&lt;br /&gt;      answer error \&lt;br /&gt;             "There is an error in the XML structure" &amp; return &amp; \&lt;br /&gt;             tXmlTree  -- contains the full error message&lt;br /&gt;   else&lt;br /&gt;      -- Clean up resources&lt;br /&gt;      revDeleteXmlTree tXmlTree&lt;br /&gt;   end if&lt;br /&gt;end mouseUp&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;We use the &lt;a href="http://docs.runrev.com/Function/revCreateXMLTree"&gt;revCreateXmlTree&lt;/a&gt; function to parse the XML text into a tree structure. If the XML test is not well-formed then we report the error, otherwise we know we have a valid XML tree structure at our disposal - which we need to cleanup after we're done, using the &lt;a href="http://docs.runrev.com/Command/revDeleteXMLTree"&gt;revDeleteXmlTree&lt;/a&gt; command. Now that we have the basics covered, we can add the DTD validation to our script.&lt;br /&gt;&lt;pre language="revTalk"&gt;&lt;br /&gt;on mouseUp&lt;br /&gt;   -- Load DTD and XML into local variables&lt;br /&gt;   local tDtdText, tXmlText&lt;br /&gt;   put field "DTD" into tDtdText&lt;br /&gt;   put field "XML" into tXmlText&lt;br /&gt;   -- Parse the XML text into a Tree&lt;br /&gt;   local tXmlTree&lt;br /&gt;   put revCreateXmlTree(tXmlText, \&lt;br /&gt;          false, \  -- must be well-formed&lt;br /&gt;          true, \ -- create a tree in memory&lt;br /&gt;          false) \ -- no SAX parser messages&lt;br /&gt;          into tXmlTree&lt;br /&gt;   if tXmlTree is not an integer then&lt;br /&gt;      answer error \&lt;br /&gt;             "There is an error in the XML structure" &amp; return &amp; \&lt;br /&gt;             tXmlTree  -- contains the full error message&lt;br /&gt;   else&lt;br /&gt;      -- Validate the XML against the DTD&lt;br /&gt;      local tValidationResult&lt;br /&gt;      put revXmlValidateDTD(tXmlTree, tDtdText) \&lt;br /&gt;             into tValidationResult&lt;br /&gt;      if tValidationResult is not empty then&lt;br /&gt;         answer error \&lt;br /&gt;                "XML structure does not conform to the DTD" &amp; return &amp; \&lt;br /&gt;                tValidationResult  -- contains the full error message&lt;br /&gt;      else&lt;br /&gt;         answer information "The XML conforms to the DTD"&lt;br /&gt;      end if&lt;br /&gt;      -- Clean up resources&lt;br /&gt;      revDeleteXmlTree tXmlTree&lt;br /&gt;   end if&lt;br /&gt;end mouseUp&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;If the XML conforms to the DTD, the &lt;a href="http://docs.runrev.com/Function/revXMLValidateDTD"&gt;revXmlValidateDtd&lt;/a&gt; function will return empty, otherwise its output contains the validation error. Pretty straightforward, so let's test this with a simple XML and DTD:&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://3.bp.blogspot.com/_NlL9VZsJHto/TIaPIC3KrZI/AAAAAAAAABw/vVjVk8hMFO0/s1600/XMLDTD-1.png"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 303px; height: 320px;" src="http://3.bp.blogspot.com/_NlL9VZsJHto/TIaPIC3KrZI/AAAAAAAAABw/vVjVk8hMFO0/s320/XMLDTD-1.png" border="0" alt=""id="BLOGGER_PHOTO_ID_5514252162131733906" /&gt;&lt;/a&gt;&lt;br /&gt;When we click the 'Validate' button, we get the message that the XML conforms to the DTD. Exactly what we were hoping for. Now let's change the XML somewhat to see if it fails when our XML text clearly does not conform to the DTD.&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://1.bp.blogspot.com/_NlL9VZsJHto/TIaPIeP3oYI/AAAAAAAAAB4/AkpCXXLCJ8w/s1600/XMLDTD-2.png"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 303px; height: 320px;" src="http://1.bp.blogspot.com/_NlL9VZsJHto/TIaPIeP3oYI/AAAAAAAAAB4/AkpCXXLCJ8w/s320/XMLDTD-2.png" border="0" alt=""id="BLOGGER_PHOTO_ID_5514252169483100546" /&gt;&lt;/a&gt;&lt;br /&gt;And here's the error message that we get on our screen:&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://2.bp.blogspot.com/_NlL9VZsJHto/TIaPIjfPl7I/AAAAAAAAACA/U-8jO4tdNHg/s1600/XMLDTD-3.png"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 320px; height: 143px;" src="http://2.bp.blogspot.com/_NlL9VZsJHto/TIaPIjfPl7I/AAAAAAAAACA/U-8jO4tdNHg/s320/XMLDTD-3.png" border="0" alt=""id="BLOGGER_PHOTO_ID_5514252170889762738" /&gt;&lt;/a&gt;&lt;br /&gt;With very little scripting, we have added a first layer of defense against incoming XML data that is not up to our specifications. Next time, we'll elaborate on this example and bolster our defenses.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5092807049363133934-2776247427160927265?l=quartam.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://quartam.blogspot.com/feeds/2776247427160927265/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=5092807049363133934&amp;postID=2776247427160927265' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5092807049363133934/posts/default/2776247427160927265'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5092807049363133934/posts/default/2776247427160927265'/><link rel='alternate' type='text/html' href='http://quartam.blogspot.com/2010/09/xml-validation-using-dtd.html' title='XML validation using a DTD'/><author><name>Quartam Software - Jan Schenkel</name><uri>http://www.blogger.com/profile/08312925714131118786</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://3.bp.blogspot.com/_NlL9VZsJHto/TIaPH6zfUqI/AAAAAAAAABo/Q2kFlOjMScw/s72-c/XMLDTD-0.png' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5092807049363133934.post-859436694373534907</id><published>2010-08-18T10:08:00.001-07:00</published><updated>2010-08-18T20:56:47.763-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='revtalk'/><category scheme='http://www.blogger.com/atom/ns#' term='revolution'/><category scheme='http://www.blogger.com/atom/ns#' term='MacOS X'/><category scheme='http://www.blogger.com/atom/ns#' term='unix'/><category scheme='http://www.blogger.com/atom/ns#' term='example code'/><title type='text'>Fun with detailed files - and stat</title><content type='html'>In a &lt;a href="http://quartam.blogspot.com/2010/08/fun-with-detailed-files.html"&gt;previous post&lt;/a&gt;, I tried to find a way to convert Unix user and group ids into their corresponding names. After I reported my findings to the use-revolution mailing list, Andre Garzia stepped in, &lt;a href="http://mail.runrev.com/pipermail/use-revolution/2010-August/144485.html"&gt;suggesting&lt;/a&gt; to take a look at the &lt;a href="http://developer.apple.com/mac/library/documentation/Darwin/Reference/ManPages/man1/stat.1.html"&gt;stat&lt;/a&gt; command.&lt;br /&gt;&lt;br /&gt;Calling 'stat' through the shell function for every file would be costly and slow; using it to fetch the information for an entire folder in one go wouldn't be so bad, but if you already use the detailed files function built into revTalk, it would seem like a waste of cycles. So I decided to rewrite my work handlers, employing a caching mechanism for the id-to-name mapping, and using 'stat' to pick up the mapping information not available in the cache.&lt;br /&gt;&lt;pre&gt;--&gt; script-local 'static' variables&lt;br /&gt;&lt;br /&gt;local sGroupIdMap, sUserIdMap&lt;br /&gt;&lt;br /&gt;--&gt; public functions and commands&lt;br /&gt;&lt;br /&gt;command LoadIdMaps&lt;br /&gt;  p_LoadGroupIdMap&lt;br /&gt;  p_LoadUserIdMap&lt;br /&gt;end LoadIdMaps&lt;br /&gt;&lt;br /&gt;function GroupIdToName pGroupId, pFile&lt;br /&gt;  local tName, tShellCommand&lt;br /&gt;  put sGroupIdMap[pGroupId] into tName&lt;br /&gt;  if tName is empty and the platform is "MacOS" then&lt;br /&gt;    -- stat is only available on MacOS X&lt;br /&gt;    put p_GetFileStat(pFile, "%Sg") into tName&lt;br /&gt;    put tName into sGroupIdMap[pGroupId]&lt;br /&gt;  end if&lt;br /&gt;  return tName&lt;br /&gt;end GroupIdToName&lt;br /&gt;&lt;br /&gt;function UserIdToName pUserId, pFile&lt;br /&gt;  local tName, tShellCommand&lt;br /&gt;  put sUserIdMap[pUserId] into tName&lt;br /&gt;  if tName is empty and the platform is "MacOS" then&lt;br /&gt;    -- stat is only available on MacOS X&lt;br /&gt;    put p_GetFileStat(pFile, "%Su") into tName&lt;br /&gt;    put tName into sUserIdMap[pUserId]&lt;br /&gt;  end if&lt;br /&gt;  return tName&lt;br /&gt;end UserIdToName&lt;br /&gt;&lt;br /&gt;--&gt; private functions and commands&lt;br /&gt;&lt;br /&gt;private function p_GetFileStat pFile, pFormat&lt;br /&gt;  local tName, tShellCommand&lt;br /&gt;  -- make sure to escape spaces in the file name&lt;br /&gt;  replace space with backslash &amp; space in pFile&lt;br /&gt;  put "stat -f" &amp;&amp; pFormat &amp;&amp; pFile into tShellCommand&lt;br /&gt;  put word 1 of shell(tShellCommand) into tName&lt;br /&gt;  return tName&lt;br /&gt;end p_GetFileStat&lt;br /&gt;&lt;br /&gt;private command p_LoadGroupIdMap&lt;br /&gt;  if the platform is "Win32" then exit p_LoadGroupIdMap&lt;br /&gt;  p_LoadFileIdMap "/etc/group", sGroupIdMap&lt;br /&gt;end p_LoadGroupIdMap&lt;br /&gt;&lt;br /&gt;private command p_LoadUserIdMap&lt;br /&gt;  if the platform is "Win32" then exit p_LoadUserIdMap&lt;br /&gt;  p_LoadFileIdMap "/etc/passwd", sUserIdMap&lt;br /&gt;end p_LoadUserIdMap&lt;br /&gt;&lt;br /&gt;private command p_LoadFileIdMap pFile, @pIdMap&lt;br /&gt;  local tData, tLine&lt;br /&gt;  put URL ("file:" &amp; pFile) into tData&lt;br /&gt;  set the itemDelimiter to colon&lt;br /&gt;  repeat for each line tLine in tData&lt;br /&gt;    -- skip empty and comment lines&lt;br /&gt;    if tLine is empty \&lt;br /&gt;        or char 1 of tLine is "#" then next repeat&lt;br /&gt;    -- fill the id map&lt;br /&gt;    put item 1 of tLine into pIdMap[item 3 of tLine]&lt;br /&gt;  end repeat&lt;br /&gt;end p_LoadFileIdMap&lt;/pre&gt;&lt;br /&gt;Armed with the above set of commands and functions, we can still use the detailed files function and obtain user and group names from a cache, calling into 'stat' as necessary. Let's update our sample button script:&lt;br /&gt;&lt;pre&gt;on mouseUp&lt;br /&gt;  local tOldFolder, tNewFolder, tDetailedFiles&lt;br /&gt;  answer folder "Select a folder"&lt;br /&gt;  if the result is "Cancel" then exit mouseUp&lt;br /&gt;  put it into tNewFolder&lt;br /&gt;  --&gt; extract the detailed files information&lt;br /&gt;  put the defaultFolder into tOldFolder&lt;br /&gt;  set the defaultFolder to tNewFolder&lt;br /&gt;  put the detailed files into tDetailedFiles&lt;br /&gt;  --&gt; load the group and user id maps&lt;br /&gt;  LoadIdMaps&lt;br /&gt;  --&gt; build the extended file information&lt;br /&gt;  local tDetailedFile, tExtendedFiles&lt;br /&gt;  local tFile, tUserId, tGroupId&lt;br /&gt;  repeat for each line tDetailedFile in tDetailedFiles&lt;br /&gt;    -- skip hidden files&lt;br /&gt;    if char 1 of tDetailedFile is "." then next repeat&lt;br /&gt;    -- fill the extended file entry&lt;br /&gt;    put urlDecode(item 1 of tDetailedFile) into tFile&lt;br /&gt;    put item 8 of tDetailedFile into tUserId&lt;br /&gt;    put item 9 of tDetailedFile into tGroupId&lt;br /&gt;    put tFileName &amp; tab &amp; \&lt;br /&gt;        UserIdToName(tUserId, tFile) &amp; tab &amp; \&lt;br /&gt;        GroupIdToName(tGroupId, tFile) &amp; return \&lt;br /&gt;        after tExtendedFiles&lt;br /&gt;  end repeat&lt;br /&gt;  --&gt; wrap things up&lt;br /&gt;  set the defaultFolder to tOldFolder&lt;br /&gt;  put char 1 to -2 of tExtendedFiles into field "Files"&lt;br /&gt;end mouseUp&lt;/pre&gt;&lt;br /&gt;The more observant amongst you may think "If all you needed was a file's user and group name, why not just 'stat' and get it over with?" - well, the detailed files function returns a lot of useful information in one go, including the size of the resource fork of a file on MacOS systems.&lt;br /&gt;Just one more thing: the 'stat' function was added in MacOS X 10.4 Tiger - so if your application still needs to support MacOS X 10.3 Panther, you're going to have to merge in the 'nireport' call from the previous post. Consider it an exercise for the reader.&lt;br /&gt;&lt;br /&gt;On to the next challenge!&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5092807049363133934-859436694373534907?l=quartam.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://quartam.blogspot.com/feeds/859436694373534907/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=5092807049363133934&amp;postID=859436694373534907' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5092807049363133934/posts/default/859436694373534907'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5092807049363133934/posts/default/859436694373534907'/><link rel='alternate' type='text/html' href='http://quartam.blogspot.com/2010/08/fun-with-detailed-files-and-stat.html' title='Fun with detailed files - and stat'/><author><name>Quartam Software - Jan Schenkel</name><uri>http://www.blogger.com/profile/08312925714131118786</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5092807049363133934.post-4352050054496838886</id><published>2010-08-17T14:14:00.000-07:00</published><updated>2010-08-18T11:08:53.257-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='revtalk'/><category scheme='http://www.blogger.com/atom/ns#' term='revolution'/><category scheme='http://www.blogger.com/atom/ns#' term='MacOS X'/><category scheme='http://www.blogger.com/atom/ns#' term='unix'/><category scheme='http://www.blogger.com/atom/ns#' term='example code'/><title type='text'>Fun with detailed files</title><content type='html'>Earlier today, the following question was posted on the use-revolution mailing list: &lt;a href="http://mail.runrev.com/pipermail/use-revolution/2010-August/144440.html"&gt;file owner &amp; group names&lt;/a&gt;. Rev veteran Monte Goulding was wondering how to convert file owner and group ids into actual user and group names. I happily logged into the Solaris server at work and dug around a bit, finally &lt;a href="http://mail.runrev.com/pipermail/use-revolution/2010-August/144449.html"&gt;replying&lt;/a&gt; that he could parse this information from the following two files: &lt;pre&gt;/etc/group&lt;br /&gt;/etc/passwd&lt;/pre&gt;&lt;br /&gt;And what would such a parsing script look like in revTalk? I'm glad you asked:&lt;br /&gt;&lt;pre&gt;command LoadGroupIdMap @pGroupIdMap&lt;br /&gt;  p_LoadFileIdMap "/etc/group", pGroupIdMap&lt;br /&gt;end LoadGroupIdMap&lt;br /&gt;&lt;br /&gt;command LoadUserIdMap @pUserIdMap&lt;br /&gt;  p_LoadFileIdMap "/etc/passwd", pUserIdMap&lt;br /&gt;end LoadUserIdMap&lt;br /&gt;&lt;br /&gt;private command p_LoadFileIdMap pFile, @pIdMap&lt;br /&gt;  local tData, tLine&lt;br /&gt;  put URL ("file:" &amp; pFile) into tData&lt;br /&gt;  set the itemDelimiter to colon&lt;br /&gt;  repeat for each line tLine in tData&lt;br /&gt;    -- skip empty and comment lines&lt;br /&gt;    if tLine is empty \&lt;br /&gt;        or char 1 of tLine is "#" then next repeat&lt;br /&gt;    -- fill the id map&lt;br /&gt;    put item 1 of tLine into pIdMap[item 3 of tLine]&lt;br /&gt;  end repeat&lt;br /&gt;end p_LoadFileIdMap&lt;/pre&gt;&lt;br /&gt;Given that both files use a colon as item delimiter, and have the name as first item and the id as third item, I extracted the parsing into a separate private command. Put the above handlers in your stack script, and then fetch each id-to-name-mapping before interpreting the &lt;a href="http://docs.runrev.com/Function/files"&gt;detailed files&lt;/a&gt;.&lt;br /&gt;Here's a simple button script that uses the above commands:&lt;br /&gt;&lt;pre&gt;on mouseUp&lt;br /&gt;  local tOldFolder, tNewFolder, tDetailedFiles&lt;br /&gt;  answer folder "Select a folder"&lt;br /&gt;  if the result is "Cancel" then exit mouseUp&lt;br /&gt;  put it into tNewFolder&lt;br /&gt;  --&gt; extract the detailed files information&lt;br /&gt;  put the defaultFolder into tOldFolder&lt;br /&gt;  set the defaultFolder to tNewFolder&lt;br /&gt;  put the detailed files into tDetailedFiles&lt;br /&gt;  set the defaultFolder to tOldFolder&lt;br /&gt;  --&gt; load the group and user id maps&lt;br /&gt;  local tGroupIdMap, tUserIdMap&lt;br /&gt;  LoadGroupIdMap tGroupIdMap&lt;br /&gt;  LoadUserIdMap tUserIdMap&lt;br /&gt;  --&gt; build the extended file information&lt;br /&gt;  local tDetailedFile, tExtendedFiles&lt;br /&gt;  repeat for each line tDetailedFile in tDetailedFiles&lt;br /&gt;    -- skip hidden files&lt;br /&gt;    if char 1 of tDetailedFile is "." then next repeat&lt;br /&gt;    -- fill the extended file entry&lt;br /&gt;    put urlDecode(item 1 of tDetailedFile) &amp; tab &amp; \&lt;br /&gt;        tUserIdMap[item 8 of tDetailedFile] &amp; tab &amp; \&lt;br /&gt;        tGroupIdMap[item 9 of tDetailedFile] &amp; return \&lt;br /&gt;        after tExtendedFiles&lt;br /&gt;  end repeat&lt;br /&gt;  --&gt; wrap things up&lt;br /&gt;  put char 1 to -2 of tExtendedFiles into field "Files"&lt;br /&gt;end mouseUp&lt;/pre&gt;&lt;br /&gt;Once I got home, I wanted to verify this on my MacOS X machine, only to discover that it wasn't properly mapping the &lt;span style="font-style:italic;"&gt;uid&lt;/span&gt; and &lt;span style="font-style:italic;"&gt;gid&lt;/span&gt; to user and group names. Intrigued, I dug a bit deeper and found out that MacOS X actually relies on a &lt;a href="http://developer.apple.com/mac/library/documentation/Darwin/Reference/ManPages/man8/DirectoryService.8.html"&gt;DirectoryService&lt;/a&gt; when it's not running in single-user mode. Lovely, but how can we get what we need using revTalk?&lt;br /&gt;Well, it turns out you can use the &lt;a href="http://developer.apple.com/mac/library/documentation/Darwin/Reference/ManPages/10.4/man1/nireport.1.html"&gt;nireport&lt;/a&gt; command via the  &lt;a href="http://docs.runrev.com/Function/shell"&gt;shell&lt;/a&gt; function to fetch a list of the group and user names with id. So let's add another helper command to do things the MacOS X way, and update our existing commands to use the correct approach, depending on &lt;a href="http://docs.runrev.com/Function/platform"&gt;the platform&lt;/a&gt;.&lt;br /&gt;&lt;pre&gt;command LoadGroupIdMap @pGroupIdMap&lt;br /&gt;  if the platform is "MacOS" then&lt;br /&gt;    p_LoadNireportIdMap "/groups gid name", pGroupIdMap&lt;br /&gt;  else if the platform is "Linux" then&lt;br /&gt;    p_LoadFileIdMap "/etc/group", pGroupIdMap&lt;br /&gt;  end if&lt;br /&gt;end LoadGroupIdMap&lt;br /&gt;&lt;br /&gt;command LoadUserIdMap @pUserIdMap&lt;br /&gt;  if the platform is "MacOS" then&lt;br /&gt;    p_LoadNireportIdMap "/users uid name", pUserIdMap&lt;br /&gt;  else if the platform is "Linux" then&lt;br /&gt;    p_LoadFileIdMap "/etc/passwd", pUserIdMap&lt;br /&gt;  end if&lt;br /&gt;end LoadUserIdMap&lt;br /&gt;&lt;br /&gt;private command p_LoadFileIdMap pFile, @pIdMap&lt;br /&gt;  local tData, tLine&lt;br /&gt;  put URL ("file:" &amp; pFile) into tData&lt;br /&gt;  set the itemDelimiter to colon&lt;br /&gt;  repeat for each line tLine in tData&lt;br /&gt;    -- skip empty and comment lines&lt;br /&gt;    if tLine is empty \&lt;br /&gt;        or char 1 of tLine is "#" then next repeat&lt;br /&gt;    -- fill the id map&lt;br /&gt;    put item 1 of tLine into pIdMap[item 3 of tLine]&lt;br /&gt;  end repeat&lt;br /&gt;end p_LoadFileIdMap&lt;br /&gt;&lt;br /&gt;private command p_LoadNireportIdMap pParams, @pIdMap&lt;br /&gt;  local tData, tLine&lt;br /&gt;  put shell ("nireport ." &amp;&amp; pParams) into tData&lt;br /&gt;  set the itemDelimiter to tab&lt;br /&gt;  repeat for each line tLine in tData&lt;br /&gt;    put item 2 of tLine into pIdMap[item 1 of tLine]&lt;br /&gt;  end repeat&lt;br /&gt;end p_LoadNireportIdMap&lt;/pre&gt;&lt;br /&gt;This seemed to do the trick on my iMac PowerPC G5 running MacOS X 10.4 Tiger - but when I tried it on my MacBook Pro Intel running MacOS X 10.6 Snow Leopard, it failed as there is no "nireport" command available. As it turns out, Apple dropped NetInfo when MacOS X 10.5 Leopard came out. So my solution is not quite complete, unfortunately.&lt;br /&gt;&lt;br /&gt;To be continued...&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5092807049363133934-4352050054496838886?l=quartam.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://quartam.blogspot.com/feeds/4352050054496838886/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=5092807049363133934&amp;postID=4352050054496838886' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5092807049363133934/posts/default/4352050054496838886'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5092807049363133934/posts/default/4352050054496838886'/><link rel='alternate' type='text/html' href='http://quartam.blogspot.com/2010/08/fun-with-detailed-files.html' title='Fun with detailed files'/><author><name>Quartam Software - Jan Schenkel</name><uri>http://www.blogger.com/profile/08312925714131118786</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5092807049363133934.post-4740459976235723845</id><published>2010-08-01T09:56:00.001-07:00</published><updated>2010-08-29T07:47:34.745-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='revtalk'/><category scheme='http://www.blogger.com/atom/ns#' term='revolution'/><category scheme='http://www.blogger.com/atom/ns#' term='example code'/><title type='text'>BorderLayout example - with resizing</title><content type='html'>In a &lt;a href="http://quartam.blogspot.com/2010/07/borderlayout-example-stack.html"&gt;previous post&lt;/a&gt;, I examined how you can mimic Java's BorderLayout behavior in revTalk. At the end of that post, I promised to explain how you can add resizers to the West and East panels. Just as a reminder, here's what we had accomplished in the previous installment:&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://1.bp.blogspot.com/_NlL9VZsJHto/TE3qVqaLg5I/AAAAAAAAAA0/eaObzlMpGNk/s1600/BorderLayout.png"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 303px; height: 320px;" src="http://1.bp.blogspot.com/_NlL9VZsJHto/TE3qVqaLg5I/AAAAAAAAAA0/eaObzlMpGNk/s320/BorderLayout.png" border="0" alt=""id="BLOGGER_PHOTO_ID_5498308377971491730" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;Before we get to the meat of the resizers, let's do some preparation work. To clearly show the user that he can drag around the resizers, we'll make a local copy of the vertical divide resize cursor that the RunRev team ships with the IDE. Open the message box and type:&lt;br /&gt;&lt;pre language="revTalk"&gt;&lt;br /&gt;copy image "vdividecursor.gif" of card id 1030 of stack "revMacCursors" to this card&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;We'll be using that as our cursor later, but it doesn't have to be visible for that to work - so you can hide it now. Next, we add a rectangular button, carefully placing it over the touching edges of the West and Center panels. Set the button's name to "WestResizer" and its width to 5, and make sure its height is the same as the West and Center panel. Here's what it should look like:&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://3.bp.blogspot.com/_NlL9VZsJHto/TFWqzuOpfNI/AAAAAAAAABQ/9CkEGKAu2yU/s1600/BorderLayout3.png"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 303px; height: 320px;" src="http://3.bp.blogspot.com/_NlL9VZsJHto/TFWqzuOpfNI/AAAAAAAAABQ/9CkEGKAu2yU/s320/BorderLayout3.png" border="0" alt=""id="BLOGGER_PHOTO_ID_5500490325462449362" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;Before we add the script, we'll also add two custom properties to the West graphic:&lt;br /&gt;-set the uMinWidth custom property to 75&lt;br /&gt;-set the uMaxWidth custom property to 150&lt;br /&gt;&lt;br /&gt;Now that we have the visuals worked out, let's script the resizer button:&lt;br /&gt;&lt;pre language="revTalk"&gt;&lt;br /&gt;local sIsResizing&lt;br /&gt;local sMinX, sMaxX&lt;br /&gt;&lt;br /&gt;on mouseEnter&lt;br /&gt;   lock cursor&lt;br /&gt;   set the cursor to the short id of image "vdividecursor.gif"&lt;br /&gt;end mouseEnter&lt;br /&gt;&lt;br /&gt;on mouseLeave&lt;br /&gt;   unlock cursor&lt;br /&gt;end mouseLeave&lt;br /&gt;&lt;br /&gt;on mouseDown&lt;br /&gt;   local tMouseOffset&lt;br /&gt;   put item 1 of the mouseLoc - item 1 of the location of me \&lt;br /&gt;       into tMouseOffset&lt;br /&gt;   put the uMinWidth of graphic "West" + tMouseOffset into sMinX&lt;br /&gt;   put the uMaxWidth of graphic "West" + tMouseOffset into sMaxX&lt;br /&gt;   put true into sIsResizing&lt;br /&gt;   pass mouseDown&lt;br /&gt;end mouseDown&lt;br /&gt;&lt;br /&gt;on mouseUp&lt;br /&gt;   put false into sIsResizing&lt;br /&gt;   pass mouseUp&lt;br /&gt;end mouseUp&lt;br /&gt;&lt;br /&gt;on mouseRelease&lt;br /&gt;   put false into sIsResizing&lt;br /&gt;   pass mouseRelease&lt;br /&gt;end mouseRelease&lt;br /&gt;&lt;br /&gt;on mouseMove x,y&lt;br /&gt;   if sIsResizing is true then&lt;br /&gt;      local tNewX&lt;br /&gt;      put min(max(x, sMinX), sMaxX) into tNewX&lt;br /&gt;      lock screen&lt;br /&gt;      set the left of me to tNewX - 3&lt;br /&gt;      set the widthFromLeft of graphic "West" to tNewX&lt;br /&gt;      dispatch "resizeCenterPanel" to this card&lt;br /&gt;      unlock screen&lt;br /&gt;   end if&lt;br /&gt;   pass mouseMove&lt;br /&gt;end mouseMove&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;While it's not too long a script, some explanation is in order:&lt;br /&gt;- the &lt;span style="font-style:italic;"&gt;mouseEnter&lt;/span&gt; and &lt;span style="font-style:italic;"&gt;mouseLeave&lt;/span&gt; messages are handled to update the cursor&lt;br /&gt;- the &lt;span style="font-style:italic;"&gt;mouseDown&lt;/span&gt; message is handled to setup a few script local variables:&lt;br /&gt;-&gt; the sIsResizing variable is used as a flag to track the status&lt;br /&gt;-&gt; the sMinX and sMaxX variables are calculated once, based on the offset between the mouse and the resizer button, and taking into account the minimum and maximum width we set earlier&lt;br /&gt;-&gt; calculating those once helps cut down on the math later on&lt;br /&gt;- the mouseUp and mouseRelease messages are handled to turn off the sIsResizing flag&lt;br /&gt;- the mouseMove message is handled to resize the West and Center panels&lt;br /&gt;-&gt; but only if the sIsResizing flag is true&lt;br /&gt;-&gt; it uses the sMinX and sMaxX variables to make sure we don't go too far either way&lt;br /&gt;-&gt; it updates the resizer button itself, the West panel, and the Center panel.&lt;br /&gt;&lt;br /&gt;To make this work, we obviously also have to add a handler for our custom 'resizeCenterPanel' event. We'll add that to the card script in a bit, after we've finished the resizer for the East panel. So duplicate the WestResizer button, name the copy EastResizer and move it over to the right, placing it over the touching edges of the East and Center panels. &lt;br /&gt;&lt;br /&gt;Before we add the script, we'll also add two custom properties to the East graphic:&lt;br /&gt;-set the uMinWidth custom property to 100&lt;br /&gt;-set the uMaxWidth custom property to 200&lt;br /&gt;&lt;br /&gt;Next, update its script to:&lt;br /&gt;&lt;pre language="revTalk"&gt;&lt;br /&gt;local sIsResizing&lt;br /&gt;local sMinX, sMaxX&lt;br /&gt;&lt;br /&gt;on mouseEnter&lt;br /&gt;   set the lockCursor to true&lt;br /&gt;   set the cursor to the short id of image "vdividecursor.gif"&lt;br /&gt;end mouseEnter&lt;br /&gt;&lt;br /&gt;on mouseLeave&lt;br /&gt;   set the lockCursor to false&lt;br /&gt;end mouseLeave&lt;br /&gt;&lt;br /&gt;on mouseDown&lt;br /&gt;   local tMouseOffset&lt;br /&gt;   put item 1 of the mouseLoc - item 1 of the location of me \&lt;br /&gt;       into tMouseOffset&lt;br /&gt;   put the width of this stack - \&lt;br /&gt;       the uMinWidth of graphic "East" + \&lt;br /&gt;       tMouseOffset into sMaxX&lt;br /&gt;   put the width of this stack - \&lt;br /&gt;       the uMaxWidth of graphic "East" + \&lt;br /&gt;       tMouseOffset into sMinX&lt;br /&gt;   put true into sIsResizing&lt;br /&gt;   pass mouseDown&lt;br /&gt;end mouseDown&lt;br /&gt;&lt;br /&gt;on mouseUp&lt;br /&gt;   put false into sIsResizing&lt;br /&gt;   pass mouseUp&lt;br /&gt;end mouseUp&lt;br /&gt;&lt;br /&gt;on mouseRelease&lt;br /&gt;   put false into sIsResizing&lt;br /&gt;   pass mouseRelease&lt;br /&gt;end mouseRelease&lt;br /&gt;&lt;br /&gt;on mouseMove x,y&lt;br /&gt;   if sIsResizing is true then&lt;br /&gt;      local tNewX&lt;br /&gt;      put min(max(x, sMinX), sMaxX) into tNewX&lt;br /&gt;      lock screen&lt;br /&gt;      set the left of me to tNewX - 3&lt;br /&gt;      set the left of graphic "East" to tNewX&lt;br /&gt;      set the widthFromLeft of graphic "East" to \&lt;br /&gt;          the width of this stack - tNewX&lt;br /&gt;      dispatch "resizeCenterPanel" to this card&lt;br /&gt;      unlock screen&lt;br /&gt;   end if&lt;br /&gt;   pass mouseMove&lt;br /&gt;end mouseMove&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;The difference with the WestResizer is that the EastResizer also has to take into account the width of the stack, but that's not too complicated. Finally we'll update the card script, adding a handler for the 'resizeCenterPanel' custom event, and we'll also take care of updating the resizer's location and height when the stack is resized. The new card script looks like this:&lt;br /&gt;&lt;pre language="revTalk"&gt;&lt;br /&gt;on resizeStack pNewWidth, pNewHeight&lt;br /&gt;   local tCenterHeight, tCenterWidth&lt;br /&gt;   --&lt;br /&gt;   lock screen&lt;br /&gt;   -- update the north and south panels&lt;br /&gt;   set the widthFromLeft of graphic "North" to pNewWidth&lt;br /&gt;   set the widthFromLeft of graphic "South" to pNewWidth&lt;br /&gt;   set the bottom of graphic "South" to pNewHeight&lt;br /&gt;   put the top of graphic "South" - \&lt;br /&gt;       the bottom of graphic "North" \&lt;br /&gt;       into tCenterHeight&lt;br /&gt;   -- update the west and east panels&lt;br /&gt;   set the heightFromTop of graphic "West" to tCenterHeight&lt;br /&gt;   set the heightFromTop of graphic "East" to tCenterHeight&lt;br /&gt;   set the right of graphic "East" to pNewWidth&lt;br /&gt;   put the left of graphic "East" - \&lt;br /&gt;       the right of graphic "West" \&lt;br /&gt;       into tCenterWidth&lt;br /&gt;   -- update the west and east resizers&lt;br /&gt;   set the heightFromTop of button "WestResizer" to tCenterHeight&lt;br /&gt;   set the heightFromTop of button "EastResizer" to tCenterHeight&lt;br /&gt;   set the left of button "EastResizer" to \&lt;br /&gt;       the left of graphic "East" - 3&lt;br /&gt;   -- update the center panel&lt;br /&gt;   set the widthFromLeft of graphic "Center" to tCenterWidth&lt;br /&gt;   set the heightFromTop of graphic "Center" to tCenterHeight&lt;br /&gt;   --&lt;br /&gt;   unlock screen&lt;br /&gt;   pass resizeStack&lt;br /&gt;end resizeStack&lt;br /&gt;&lt;br /&gt;--&gt; custom event handlers&lt;br /&gt;&lt;br /&gt;on resizeCenterPanel&lt;br /&gt;   local tCenterWidth, tCenterHeight&lt;br /&gt;   put the top of graphic "South" - \&lt;br /&gt;       the bottom of graphic "North" \&lt;br /&gt;       into tCenterHeight&lt;br /&gt;   put the left of graphic "East" - \&lt;br /&gt;       the right of graphic "West" \&lt;br /&gt;       into tCenterWidth&lt;br /&gt;   lock screen&lt;br /&gt;   set the left of graphic "Center" to the right of graphic "West"&lt;br /&gt;   set the top of graphic "Center" to the bottom of graphic "North"&lt;br /&gt;   set the widthFromLeft of graphic "Center" to tCenterWidth&lt;br /&gt;   set the heightFromTop of graphic "Center" to tCenterHeight&lt;br /&gt;   unlock screen&lt;br /&gt;   pass resizeCenterPanel&lt;br /&gt;end resizeCenterPanel&lt;br /&gt;&lt;br /&gt;--&gt; helper property setters&lt;br /&gt;&lt;br /&gt;setProp widthFromLeft pNewWidth&lt;br /&gt;   local tRectangle&lt;br /&gt;   --&lt;br /&gt;   lock screen&lt;br /&gt;   put the rectangle of the target into tRectangle&lt;br /&gt;   put item 1 of tRectangle + pNewWidth into item 3 of tRectangle&lt;br /&gt;   set the rectangle of the target to tRectangle&lt;br /&gt;   unlock screen&lt;br /&gt;end widthFromLeft&lt;br /&gt;&lt;br /&gt;setProp heightFromTop pNewHeight&lt;br /&gt;   local tRectangle&lt;br /&gt;   --&lt;br /&gt;   lock screen&lt;br /&gt;   put the rectangle of the target into tRectangle&lt;br /&gt;   put item 2 of tRectangle + pNewHeight into item 4 of tRectangle&lt;br /&gt;   set the rectangle of the target to tRectangle&lt;br /&gt;   unlock screen&lt;br /&gt;end heightFromTop&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;And there we have it: a BorderLayout with resizers for the West and East panels.&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://1.bp.blogspot.com/_NlL9VZsJHto/TFW31BV5fHI/AAAAAAAAABY/aRtHQyN2F3Q/s1600/BorderLayout4.png"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 303px; height: 320px;" src="http://1.bp.blogspot.com/_NlL9VZsJHto/TFW31BV5fHI/AAAAAAAAABY/aRtHQyN2F3Q/s320/BorderLayout4.png" border="0" alt=""id="BLOGGER_PHOTO_ID_5500504641424161906" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;Once you're happy with the way the resizers work, you can change the style of the WestResizer and EastResizer button to &lt;span style="font-style:italic;"&gt;transparent&lt;/span&gt;. They'll continue to work just fine, but won't be visible. With a little more work, you can also add resizers for the North and South panels. But I'll leave that as an exercise to the reader...&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5092807049363133934-4740459976235723845?l=quartam.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://quartam.blogspot.com/feeds/4740459976235723845/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=5092807049363133934&amp;postID=4740459976235723845' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5092807049363133934/posts/default/4740459976235723845'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5092807049363133934/posts/default/4740459976235723845'/><link rel='alternate' type='text/html' href='http://quartam.blogspot.com/2010/08/borderlayout-example-with-resizing.html' title='BorderLayout example - with resizing'/><author><name>Quartam Software - Jan Schenkel</name><uri>http://www.blogger.com/profile/08312925714131118786</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://1.bp.blogspot.com/_NlL9VZsJHto/TE3qVqaLg5I/AAAAAAAAAA0/eaObzlMpGNk/s72-c/BorderLayout.png' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5092807049363133934.post-6231284348745567098</id><published>2010-07-26T12:58:00.000-07:00</published><updated>2010-07-26T13:42:15.061-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='revtalk'/><category scheme='http://www.blogger.com/atom/ns#' term='revolution'/><category scheme='http://www.blogger.com/atom/ns#' term='example code'/><title type='text'>BorderLayout example script</title><content type='html'>As you may know, I spend quite a bit of my time in Java, not merely building back-end systems with message queues and raw socket communication, but also creating user interfaces in Swing. One of the most compelling features of Swing is its system of LayoutManagers - and certainly the easiest-to-use is the BorderLayout. Today, I'm going to explain how you can mimic this type of layout handling in revTalk.&lt;br /&gt;&lt;br /&gt;The principle of the BorderLayout is quite simple: it divides the window into 5 panels: North, East, South, West and Center. As a picture says more than a thousand words, here's what this layout generally looks like:&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://1.bp.blogspot.com/_NlL9VZsJHto/TE3qVqaLg5I/AAAAAAAAAA0/eaObzlMpGNk/s1600/BorderLayout.png"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 303px; height: 320px;" src="http://1.bp.blogspot.com/_NlL9VZsJHto/TE3qVqaLg5I/AAAAAAAAAA0/eaObzlMpGNk/s320/BorderLayout.png" border="0" alt=""id="BLOGGER_PHOTO_ID_5498308377971491730" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;The North panel is a good place to store a toolbar, the South panel usually plays host for a status area, while the West panel may show an outline, and the East panel a property grid, leaving the Center for the actual content. &lt;br /&gt;&lt;br /&gt;For this example, I chose the easy route, creating a single rectangle graphic for each panel. I also picked distinct colors so you can more easily see the effect of resizing the window once the pieces of the puzzle fall into place. And it turns out it isn't that much work to implement, as I set the card script to:&lt;br /&gt;&lt;br /&gt;&lt;pre language="revTalk"&gt;&lt;br /&gt;on resizeStack pNewWidth, pNewHeight&lt;br /&gt;   local tCenterHeight, tCenterWidth&lt;br /&gt;   --&lt;br /&gt;   lock screen&lt;br /&gt;   -- update the north and south panels&lt;br /&gt;   set the widthFromLeft of graphic "North" to pNewWidth&lt;br /&gt;   set the widthFromLeft of graphic "South" to pNewWidth&lt;br /&gt;   set the bottom of graphic "South" to pNewHeight&lt;br /&gt;   put the top of graphic "South" - \&lt;br /&gt;       the bottom of graphic "North" \&lt;br /&gt;       into tCenterHeight&lt;br /&gt;   -- update the west and east panels&lt;br /&gt;   set the heightFromTop of graphic "West" to \&lt;br /&gt;       tCenterHeight&lt;br /&gt;   set the heightFromTop of graphic "East" to \&lt;br /&gt;       tCenterHeight&lt;br /&gt;   set the right of graphic "East" to pNewWidth&lt;br /&gt;   put the left of graphic "East" - \&lt;br /&gt;       the right of graphic "West" \&lt;br /&gt;       into tCenterWidth&lt;br /&gt;   -- update the center panel&lt;br /&gt;   set the widthFromLeft of graphic "Center" to \&lt;br /&gt;       tCenterWidth&lt;br /&gt;   set the heightFromTop of graphic "Center" to \&lt;br /&gt;       tCenterHeight&lt;br /&gt;   --&lt;br /&gt;   unlock screen&lt;br /&gt;   pass resizeStack&lt;br /&gt;end resizeStack&lt;br /&gt;&lt;br /&gt;--&gt; helper property setters&lt;br /&gt;&lt;br /&gt;setProp widthFromLeft pNewWidth&lt;br /&gt;   local tRectangle&lt;br /&gt;   --&lt;br /&gt;   lock screen&lt;br /&gt;   put the rectangle of the target into tRectangle&lt;br /&gt;   put item 1 of tRectangle + pNewWidth \&lt;br /&gt;       into item 3 of tRectangle&lt;br /&gt;   set the rectangle of the target to tRectangle&lt;br /&gt;   unlock screen&lt;br /&gt;end widthFromLeft&lt;br /&gt;&lt;br /&gt;setProp heightFromTop pNewHeight&lt;br /&gt;   local tRectangle&lt;br /&gt;   --&lt;br /&gt;   lock screen&lt;br /&gt;   put the rectangle of the target into tRectangle&lt;br /&gt;   put item 2 of tRectangle + pNewHeight \&lt;br /&gt;       into item 4 of tRectangle&lt;br /&gt;   set the rectangle of the target to tRectangle&lt;br /&gt;   unlock screen&lt;br /&gt;end heightFromTop&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Let's see what is happening here. The script handles the &lt;span style="font-style:italic;"&gt;resizeStack&lt;/span&gt; event, which has 4 parameters (new width, new height, old width, old height) of which we only use the first two.&lt;br /&gt;&lt;br /&gt;As the North and South panels use up the entire width, I can simply set their width to the new width of the stack. However, setting the width of a control in revTalk works from the &lt;span style="font-style:italic;"&gt;location&lt;/span&gt; of the control (which is the center, not the topleft).&lt;br /&gt;So I use a fake helper property setter 'widthFromLeft' to update the rectangle of the control instead. Finally, we move the South panel to the bottom edge of the window, and calculate the distance between the top of the south panel and the bottom of the north panel, as this is the new height for our West, East and Center panels.&lt;br /&gt;&lt;br /&gt;Armed with this information, I follow a similar path for the West and East panels, updating their height using a fake helper property setter 'heightFromTop', and moving the East panel to the right edge of the window, calculating the distance between the east and west panels to determine the new width of the Center panel. And now it is dead-easy to update the Center panel with the calculated width and height.&lt;br /&gt;&lt;br /&gt;That's all there is to it! Admittedly, this example merely resizes 5 graphics, but you can extend this logic to groups, handling the &lt;span style="font-style:italic;"&gt;resizeControl&lt;/span&gt; message in your group scripts to automatically update the layout of the group's contents.&lt;br /&gt;&lt;br /&gt;Here's what it looks like when stretched horizontally.&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://2.bp.blogspot.com/_NlL9VZsJHto/TE3wiOg6mqI/AAAAAAAAAA8/-D7CBwjJTOU/s1600/BorderLayout1.png"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 320px; height: 225px;" src="http://2.bp.blogspot.com/_NlL9VZsJHto/TE3wiOg6mqI/AAAAAAAAAA8/-D7CBwjJTOU/s320/BorderLayout1.png" border="0" alt=""id="BLOGGER_PHOTO_ID_5498315190891616930" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;And here's what it looks like when stretched vertically.&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://1.bp.blogspot.com/_NlL9VZsJHto/TE3wipHrd7I/AAAAAAAAABE/oSyN7Fk0_4s/s1600/BorderLayout2.png"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 206px; height: 320px;" src="http://1.bp.blogspot.com/_NlL9VZsJHto/TE3wipHrd7I/AAAAAAAAABE/oSyN7Fk0_4s/s320/BorderLayout2.png" border="0" alt=""id="BLOGGER_PHOTO_ID_5498315198033524658" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;Straightforward to implement with very little code. Next time, I'll add some resizers for the West and East panels.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5092807049363133934-6231284348745567098?l=quartam.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://quartam.blogspot.com/feeds/6231284348745567098/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=5092807049363133934&amp;postID=6231284348745567098' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5092807049363133934/posts/default/6231284348745567098'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5092807049363133934/posts/default/6231284348745567098'/><link rel='alternate' type='text/html' href='http://quartam.blogspot.com/2010/07/borderlayout-example-stack.html' title='BorderLayout example script'/><author><name>Quartam Software - Jan Schenkel</name><uri>http://www.blogger.com/profile/08312925714131118786</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://1.bp.blogspot.com/_NlL9VZsJHto/TE3qVqaLg5I/AAAAAAAAAA0/eaObzlMpGNk/s72-c/BorderLayout.png' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5092807049363133934.post-8224217917657552383</id><published>2010-07-22T12:53:00.000-07:00</published><updated>2010-07-22T13:24:11.386-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='revtalk'/><category scheme='http://www.blogger.com/atom/ns#' term='revolution'/><category scheme='http://www.blogger.com/atom/ns#' term='example code'/><category scheme='http://www.blogger.com/atom/ns#' term='data grid'/><title type='text'>DataGrid Filter example - with sorting</title><content type='html'>The other day I posted an example of how you can add a filter to a DataGrid in Revolution. At the end of that blog post, I remarked how I had deliberately turned off column sorting. Fear not, it is straightforward to add support for this, again thanks to the &lt;a href="http://lessons.runrev.com/spaces/lessons/manuals/datagrid"&gt;DataGrid lessons&lt;/a&gt; available on the runrev.com website.&lt;br /&gt;&lt;br /&gt;Just as a reminder, here's a screenshot of the example stack:&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://2.bp.blogspot.com/_NlL9VZsJHto/TESrTux5keI/AAAAAAAAAAU/dpLvLdLsiNk/s1600/DataGridFilter.png"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 294px; height: 320px;" src="http://2.bp.blogspot.com/_NlL9VZsJHto/TESrTux5keI/AAAAAAAAAAU/dpLvLdLsiNk/s320/DataGridFilter.png" border="0" alt=""id="BLOGGER_PHOTO_ID_5495705800762036706" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;So what do we need to do to allow the user to click on the column header and apply the sort and filter at the same time? It's quite a simple bity of scripting, but before we get to that, you need to make some minor modifications to the datagrid columns.&lt;br /&gt;- Switch to the 'pointer' tool, and select the datagrid, then open the Object Inspector, and switch to the 'Columns' panel&lt;br /&gt;-&gt; select the column 'index' and set its Sort by column type to 'Numeric'&lt;br /&gt;-&gt; select the column 'name' and make sure its Sort by column type is 'Text'&lt;br /&gt;&lt;br /&gt;Now edit the script of the datagrid, and set it to:&lt;br /&gt;&lt;pre language="revTalk"&gt;&lt;br /&gt;local sOriginalData&lt;br /&gt;local sIndexMap&lt;br /&gt;local sFilter&lt;br /&gt;&lt;br /&gt;setProp dgData pData&lt;br /&gt;  put pData into sOriginalData&lt;br /&gt;  RebuildMap&lt;br /&gt;end dgData&lt;br /&gt;&lt;br /&gt;getProp dgData&lt;br /&gt;  return sOriginalData&lt;br /&gt;end dgData&lt;br /&gt;&lt;br /&gt;setProp dgFilter pFilter&lt;br /&gt;  put pFilter into sFilter&lt;br /&gt;  RebuildMap&lt;br /&gt;end dgFilter&lt;br /&gt;&lt;br /&gt;getProp dgFilter&lt;br /&gt;  return sFilter&lt;br /&gt;end dgFilter&lt;br /&gt;&lt;br /&gt;--&gt; datagrid callbacks&lt;br /&gt;&lt;br /&gt;on GetDataForLine pLine, @pData&lt;br /&gt;  local tKey&lt;br /&gt;  -- retrieve the original line of data&lt;br /&gt;  put sIndexMap[pLine] into tKey&lt;br /&gt;  put sOriginalData[tKey] into pData&lt;br /&gt;end GetDataForLine&lt;br /&gt;&lt;br /&gt;on SortDataGridColumn pColumn&lt;br /&gt;  SortMap pColumn&lt;br /&gt;  HiliteAndStoreSortByColumn pColumn&lt;br /&gt;  dispatch "RefreshList" to me&lt;br /&gt;end SortDataGridColumn&lt;br /&gt;&lt;br /&gt;--&gt; private commands and functions&lt;br /&gt;&lt;br /&gt;private command RebuildMap&lt;br /&gt;  local tKeys, tKey, tIndex&lt;br /&gt;  put empty into sIndexMap&lt;br /&gt;  put 0 into tIndex&lt;br /&gt;  put the keys of sOriginalData into tKeys&lt;br /&gt;  sort tKeys numeric&lt;br /&gt;  repeat for each line tKey in tKeys&lt;br /&gt;    if sFilter is empty \&lt;br /&gt;    or sOriginalData[tKey]["name"] contains sFilter \&lt;br /&gt;    then&lt;br /&gt;      add 1 to tIndex&lt;br /&gt;      put tKey into sIndexMap[tIndex]&lt;br /&gt;    end if&lt;br /&gt;  end repeat&lt;br /&gt;  set the dgNumberOfRecords of me to tIndex&lt;br /&gt;  SortMap&lt;br /&gt;  dispatch "ResetList" to me&lt;br /&gt;end RebuildMap&lt;br /&gt;&lt;br /&gt;private command SortMap pColumn&lt;br /&gt;  local tSortDirection, tSortType, tSortCaseSensitive&lt;br /&gt;  local tSortCommand&lt;br /&gt;  local tKeys, tKey, tNewIndex, tNewIndexMap&lt;br /&gt;  -- works whether a column is specified or not&lt;br /&gt;  if pColumn is empty then&lt;br /&gt;    -- find the current sort column&lt;br /&gt;    put the dgProps["sort by column"] of me into pColumn&lt;br /&gt;    if pColumn is empty then&lt;br /&gt;      exit SortMap&lt;br /&gt;    end if&lt;br /&gt;  end if&lt;br /&gt;  -- assemble and do the sort command&lt;br /&gt;  put the dgColumnSortDirection[pColumn] of me \&lt;br /&gt;    into tSortDirection&lt;br /&gt;  put the dgColumnSortType[pColumn] of me \&lt;br /&gt;    into tSortType&lt;br /&gt;  put the dgColumnSortCaseSensitive[pColumn] of me \&lt;br /&gt;    into tSortCaseSensitive&lt;br /&gt;  put "sort lines of tKeys" &amp;&amp; tSortDirection \&lt;br /&gt;    into tSortCommand&lt;br /&gt;  switch tSortType&lt;br /&gt;    case "numeric"&lt;br /&gt;      put " numeric" after tSortCommand&lt;br /&gt;      break&lt;br /&gt;    case "international"&lt;br /&gt;      put " international" after tSortCommand&lt;br /&gt;      break&lt;br /&gt;    case "datetime"&lt;br /&gt;    case "system datetime"'&lt;br /&gt;      put " dateTime" after tSortCommand&lt;br /&gt;      break&lt;br /&gt;  end switch&lt;br /&gt;  put " by ColumnValueForLine(pColumn, each)" \&lt;br /&gt;    after tSortCommand&lt;br /&gt;  set the caseSensitive to \&lt;br /&gt;    (tSortCaseSensitive is true)&lt;br /&gt;  set the useSystemDate to \&lt;br /&gt;    (tSortType is "system datetime")&lt;br /&gt;  put the keys of sIndexMap into tKeys&lt;br /&gt;  do tSortCommand&lt;br /&gt;  -- rebuild the index map&lt;br /&gt;  put 0 into tNewIndex&lt;br /&gt;  repeat for each line tKey in tKeys&lt;br /&gt;    add 1 to tNewIndex&lt;br /&gt;    put sIndexMap[tKey] into \&lt;br /&gt;      tNewIndexMap[tNewIndex]&lt;br /&gt;  end repeat&lt;br /&gt;  put tNewIndexMap into sIndexMap&lt;br /&gt;end SortMap&lt;br /&gt;&lt;br /&gt;private function ColumnValueForLine pColumn, pLine&lt;br /&gt;  local tKey&lt;br /&gt;  -- retrieve the original line of data&lt;br /&gt;  put sIndexMap[pLine] into tKey&lt;br /&gt;  return sOriginalData[tKey][pColumn]&lt;br /&gt;end ColumnValueForLine&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;What's changed in comparison to the last implementation? Well, the bulk of the necessary work is done in the new private command SortMap, which reads the column sort properties and uses a custom sort function to get the filtered lines in the right order on the basis of the column value in the original lines of data. Then it builds a new IndexMap to replace the old one, and the regular refresh routines of the datagrid will "just work."&lt;br /&gt;&lt;br /&gt;So what happens when we sort the data descending on the name, without applying a filter?&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://2.bp.blogspot.com/_NlL9VZsJHto/TEilhFKPu2I/AAAAAAAAAAk/o4UPSQ-t0oQ/s1600/DataGridFilter2.png"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 294px; height: 320px;" src="http://2.bp.blogspot.com/_NlL9VZsJHto/TEilhFKPu2I/AAAAAAAAAAk/o4UPSQ-t0oQ/s320/DataGridFilter2.png" border="0" alt=""id="BLOGGER_PHOTO_ID_5496825332945107810" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;Looking good - does the filter still work? Let's type 'url' into the filter field.&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://4.bp.blogspot.com/_NlL9VZsJHto/TEil9XJIcWI/AAAAAAAAAAs/cZCbAOZuaFE/s1600/DataGridFilter3.png"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 294px; height: 320px;" src="http://4.bp.blogspot.com/_NlL9VZsJHto/TEil9XJIcWI/AAAAAAAAAAs/cZCbAOZuaFE/s320/DataGridFilter3.png" border="0" alt=""id="BLOGGER_PHOTO_ID_5496825818808611170" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;You may have noticed that I also added getProp-handlers for dgData and dgFilter, making the above script easily reusable. With just a bit of lateral thinking and coding, you too can build just about anything, thanks to Revolution.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5092807049363133934-8224217917657552383?l=quartam.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://quartam.blogspot.com/feeds/8224217917657552383/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=5092807049363133934&amp;postID=8224217917657552383' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5092807049363133934/posts/default/8224217917657552383'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5092807049363133934/posts/default/8224217917657552383'/><link rel='alternate' type='text/html' href='http://quartam.blogspot.com/2010/07/datagrid-filter-example-with-sorting.html' title='DataGrid Filter example - with sorting'/><author><name>Quartam Software - Jan Schenkel</name><uri>http://www.blogger.com/profile/08312925714131118786</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://2.bp.blogspot.com/_NlL9VZsJHto/TESrTux5keI/AAAAAAAAAAU/dpLvLdLsiNk/s72-c/DataGridFilter.png' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5092807049363133934.post-3574750719717429856</id><published>2010-07-19T12:21:00.000-07:00</published><updated>2010-07-20T00:40:42.783-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='revtalk'/><category scheme='http://www.blogger.com/atom/ns#' term='revolution'/><category scheme='http://www.blogger.com/atom/ns#' term='example code'/><category scheme='http://www.blogger.com/atom/ns#' term='data grid'/><title type='text'>DataGrid Filter example</title><content type='html'>One of the powerful additions to Revolution 3.5 is the DataGrid - a custom control group that allows you to display any type of data with astonishing flexibility. Recently, I had to add filtering capabilities to a data grid for one of my projects. In this post, I will explain how I accomplished it, thanks to the &lt;a href="http://lessons.runrev.com/spaces/lessons/manuals/datagrid"&gt;DataGrid lessons&lt;/a&gt; available on the runrev.com website.&lt;br /&gt;&lt;br /&gt;Here's a screenshot of the example stack:&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://2.bp.blogspot.com/_NlL9VZsJHto/TESrTux5keI/AAAAAAAAAAU/dpLvLdLsiNk/s1600/DataGridFilter.png"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 294px; height: 320px;" src="http://2.bp.blogspot.com/_NlL9VZsJHto/TESrTux5keI/AAAAAAAAAAU/dpLvLdLsiNk/s320/DataGridFilter.png" border="0" alt=""id="BLOGGER_PHOTO_ID_5495705800762036706" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;Execute the following steps, if you want to follow along:&lt;br /&gt;- Create a new stack&lt;br /&gt;- Drag a button to the topleft of the card and set its name to "Fill"&lt;br /&gt;- Drag a text field to the topright of the card&lt;br /&gt;- Drag a label field next to the text field and set its content to "Filter:"&lt;br /&gt;- Drag a data grid onto the card and configure it using the Object Inspector&lt;br /&gt;-&gt; In the 'Basic Properties' panel, turn off the checkbox "Allow Text Editing"&lt;br /&gt;-&gt; Switch to the 'Columns' panel, and add 2 columns "index" and "name"&lt;br /&gt;-&gt; Edit the script of the DataGrid, and set it to the following:&lt;br /&gt;&lt;pre language="revTalk"&gt;&lt;br /&gt;local sOriginalData&lt;br /&gt;local sIndexMap&lt;br /&gt;local sFilter&lt;br /&gt;&lt;br /&gt;setProp dgData pData&lt;br /&gt;  put pData into sOriginalData&lt;br /&gt;  RebuildMap&lt;br /&gt;end dgData&lt;br /&gt;&lt;br /&gt;setProp dgFilter pFilter&lt;br /&gt;  put pFilter into sFilter&lt;br /&gt;  RebuildMap&lt;br /&gt;end dgFilter&lt;br /&gt;&lt;br /&gt;private command RebuildMap&lt;br /&gt;  local tKeys, tKey, tIndex&lt;br /&gt;  put 0 into tIndex&lt;br /&gt;  put the keys of sOriginalData into tKeys&lt;br /&gt;  sort tKeys numeric&lt;br /&gt;  repeat for each line tKey in tKeys&lt;br /&gt;    if sFilter is empty \&lt;br /&gt;    or sFilter is in sOriginalData[tKey]["name"] \&lt;br /&gt;    then&lt;br /&gt;      add 1 to tIndex&lt;br /&gt;      put tKey into sIndexMap[tIndex]&lt;br /&gt;    end if&lt;br /&gt;  end repeat&lt;br /&gt;  set the dgNumberOfRecords of me to tIndex&lt;br /&gt;  dispatch "ResetList" to me&lt;br /&gt;end RebuildMap&lt;br /&gt;&lt;br /&gt;on GetDataForLine pLine, @pData&lt;br /&gt;  local tKey&lt;br /&gt;  put sIndexMap[pLine] into tKey&lt;br /&gt;  put sOriginalData[tKey] into pData&lt;br /&gt;end GetDataForLine&lt;br /&gt;&lt;br /&gt;on SortDataGridColumn pColumn&lt;br /&gt;  -- prevent column sorting&lt;br /&gt;end SortDataGridColumn&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;The above data grid script will take care of filtering the data. Now it's just a matter of wiring up the text field.&lt;br /&gt;-&gt; Edit the script of the text field and set it to the following:&lt;br /&gt;&lt;pre language="revTalk"&gt;&lt;br /&gt;local sUpdateMsg&lt;br /&gt;&lt;br /&gt;on keyUp pKey&lt;br /&gt;  ScheduleUpdate&lt;br /&gt;  pass keyUp&lt;br /&gt;end keyUp&lt;br /&gt;&lt;br /&gt;on deleteKey&lt;br /&gt;  ScheduleUpdate&lt;br /&gt;  pass deleteKey&lt;br /&gt;end deleteKey&lt;br /&gt;&lt;br /&gt;on backspaceKey&lt;br /&gt;  ScheduleUpdate&lt;br /&gt;  pass backspaceKey&lt;br /&gt;end backspaceKey&lt;br /&gt;&lt;br /&gt;on pasteKey&lt;br /&gt;  ScheduleUpdate&lt;br /&gt;  pass pasteKey&lt;br /&gt;end pasteKey&lt;br /&gt;&lt;br /&gt;on cutKey&lt;br /&gt;  ScheduleUpdate&lt;br /&gt;  pass cutKey&lt;br /&gt;end cutKey&lt;br /&gt;&lt;br /&gt;command ScheduleUpdate&lt;br /&gt;  if sUpdateMsg is empty then&lt;br /&gt;    send "UpdateFilter" to me in 100 milliseconds&lt;br /&gt;    put the result into sUpdateMsg&lt;br /&gt;  end if&lt;br /&gt;end ScheduleUpdate&lt;br /&gt;&lt;br /&gt;on UpdateFilter&lt;br /&gt;  set the dgFilter of group "DataGrid 1" \&lt;br /&gt;    to the text of me&lt;br /&gt;  put empty into sUpdateMsg&lt;br /&gt;end UpdateFilter&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;By trapping the different key events, we can update the data grid filter as we go along. Note that I used the ScheduleUpdate helper command to try and minimize the number of refreshes for quick typers. Play around with the interval until you find the sweet spot of responsive user interface.&lt;br /&gt;&lt;br /&gt;Finally, it is time to fill the data gird with some data for testing, so edit the script of the button and set it to the following:&lt;br /&gt;&lt;pre language="revTalk"&gt;&lt;br /&gt;on mouseUp&lt;br /&gt;  local tData, tIndex, tName&lt;br /&gt;  put 0 into tIndex&lt;br /&gt;  repeat for each line tName in the functionNames&lt;br /&gt;    add 1 to tIndex&lt;br /&gt;    put tIndex into tData[tIndex]["index"]&lt;br /&gt;    put tName into tData[tIndex]["name"]&lt;br /&gt;  end repeat&lt;br /&gt;  set the dgData of group "DataGrid 1" to tData&lt;br /&gt;end mouseUp&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;This simple script will fill our data grid with a list of the built-in functions that revTalk has to offer. Click the button, and type away in the filter text field to see the effect. Here's what happens when I type "cos":&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://1.bp.blogspot.com/_NlL9VZsJHto/TESsBrzsYOI/AAAAAAAAAAc/iKx9sy-44_s/s1600/DataGridFilter1.png"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 294px; height: 320px;" src="http://1.bp.blogspot.com/_NlL9VZsJHto/TESsBrzsYOI/AAAAAAAAAAc/iKx9sy-44_s/s320/DataGridFilter1.png" border="0" alt=""id="BLOGGER_PHOTO_ID_5495706590238236898" /&gt;&lt;/a&gt;&lt;br /&gt;Looks pretty good, doesn't it? It certainly was good enough for my use, but there are a few things to bear in mind about this implementation:&lt;br /&gt;- I didn't have to edit the data in the grid.&lt;br /&gt;- I didn't have to support sorting for my particular project.&lt;br /&gt;Both of these limitations can be overcome, but I'll leave that for another time...&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5092807049363133934-3574750719717429856?l=quartam.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://quartam.blogspot.com/feeds/3574750719717429856/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=5092807049363133934&amp;postID=3574750719717429856' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5092807049363133934/posts/default/3574750719717429856'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5092807049363133934/posts/default/3574750719717429856'/><link rel='alternate' type='text/html' href='http://quartam.blogspot.com/2010/07/datagrid-filter-example.html' title='DataGrid Filter example'/><author><name>Quartam Software - Jan Schenkel</name><uri>http://www.blogger.com/profile/08312925714131118786</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://2.bp.blogspot.com/_NlL9VZsJHto/TESrTux5keI/AAAAAAAAAAU/dpLvLdLsiNk/s72-c/DataGridFilter.png' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5092807049363133934.post-5936904911378454639</id><published>2010-07-18T13:41:00.000-07:00</published><updated>2010-07-27T10:49:37.892-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='forums'/><category scheme='http://www.blogger.com/atom/ns#' term='website'/><title type='text'>Forums moved over to quartam.on-rev.com</title><content type='html'>&lt;span style="font-style:italic;"&gt;Hi All,&lt;br /&gt;&lt;br /&gt;After much deliberation, I have decided to move the forums over from ning.com to my new on-rev based website: &lt;a href="http://quartam.on-rev.com/forums/index.php"&gt;http://quartam.on-rev.com/forums/index.php&lt;/a&gt;.&lt;br /&gt;And here's an even shorter link: &lt;a href="http://forums.quartam.com"&gt;http://forums.quartam.com&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;All posts were imported as plain text, and the topics were locked.&lt;br /&gt;Unfortunately, you will have to re-register as a user on the new forum.&lt;br /&gt;&lt;br /&gt;I apologize for any inconvenience, caused by this switch.&lt;br /&gt;&lt;br /&gt;Best regards,&lt;br /&gt;&lt;br /&gt;Jan Schenkel&lt;br /&gt;--&lt;br /&gt;Quartam Reports &amp; PDF Library for Revolution&lt;br /&gt;www.quartam.com&lt;/span&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5092807049363133934-5936904911378454639?l=quartam.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://quartam.blogspot.com/feeds/5936904911378454639/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=5092807049363133934&amp;postID=5936904911378454639' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5092807049363133934/posts/default/5936904911378454639'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5092807049363133934/posts/default/5936904911378454639'/><link rel='alternate' type='text/html' href='http://quartam.blogspot.com/2010/07/forums-moved-over-to-quartamon-revcom.html' title='Forums moved over to quartam.on-rev.com'/><author><name>Quartam Software - Jan Schenkel</name><uri>http://www.blogger.com/profile/08312925714131118786</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5092807049363133934.post-2037362381666321950</id><published>2010-03-14T10:27:00.000-07:00</published><updated>2010-08-29T02:12:13.279-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='revtalk'/><category scheme='http://www.blogger.com/atom/ns#' term='revolution'/><category scheme='http://www.blogger.com/atom/ns#' term='example code'/><title type='text'>But the waiting makes me curious</title><content type='html'>&lt;span style="font-style:italic;"&gt;Be patient, is very good advice, but the waiting makes me curious.&lt;/span&gt;&lt;br /&gt;It's a line from the song '&lt;a href="http://www.disneyclips.com/lyrics/alicelyrics5.html"&gt;Very Good advice&lt;/a&gt;' written for the original Disney 'Alice in Wonderland' movie and recently remade by The Cure's Robert Smith for the '&lt;a href="http://www.almostalicemusic.com/"&gt;Almost Alice&lt;/a&gt;' soundtrack. And I just couldn't think of a better introduction to this blog entry on the revTalk 'wait' command.&lt;br /&gt;&lt;br /&gt;When your script is in a tight loop, screen updates my not happen until after the whole script is finished.&lt;br /&gt;&lt;pre class="revtalk"&gt;on mouseUp&lt;br /&gt;  repeat with i = 1 to 5000&lt;br /&gt;    put i into field "Output"&lt;br /&gt;  end repeat&lt;br /&gt;end mouseUp&lt;/pre&gt;&lt;br /&gt;Rather than continuously updating the content of the field, the field may only get updated after the repeat loop. While that's good enough in this silly example, it's not suitable for keeping the user updated on the progress of a real-world data crunching routine.&lt;br /&gt;&lt;br /&gt;You can give the engine some breathing room by inserting a call to the 'wait' command.&lt;br /&gt;&lt;pre class="revtalk"&gt;on mouseUp&lt;br /&gt;  repeat with i = 1 to 5000&lt;br /&gt;    put i into field "Output"&lt;br /&gt;    -- allow screen to redraw&lt;br /&gt;    wait 0 milliseconds&lt;br /&gt;  end repeat&lt;br /&gt;end mouseUp&lt;/pre&gt;&lt;br /&gt;Now you'll see the content of the field refreshed regularly; but it also makes your loop much slower.&lt;br /&gt;&lt;pre class="revtalk"&gt;on mouseUp&lt;br /&gt;  constant kRefresh = 25&lt;br /&gt;  repeat with i = 1 to 5000&lt;br /&gt;    put i into field "Output"&lt;br /&gt;    if i mod kRefresh is 0 then&lt;br /&gt;      -- allow screen to redraw&lt;br /&gt;      wait 0 milliseconds&lt;br /&gt;    end if&lt;br /&gt;  end repeat&lt;br /&gt;end mouseUp&lt;/pre&gt;&lt;br /&gt;By only redrawing the screen every 25th iteration, we can still refresh the screen with less impact on performance. Play with the 'kRefresh' constant value to find the right redraw/performance ratio.&lt;br /&gt;&lt;br /&gt;But that's not all the 'wait' command can do: if used properly, you can give your users a way to 'cancel' out of a tight loop.&lt;br /&gt;&lt;pre class="revtalk"&gt;on mouseUp&lt;br /&gt;  constant kRefresh = 25&lt;br /&gt;  enable button "Cancel"&lt;br /&gt;  set the uCancel of me to false&lt;br /&gt;  repeat with i = 1 to 5000&lt;br /&gt;    put i into field "Output"&lt;br /&gt;    if i mod kRefresh is 0 then&lt;br /&gt;      -- allow screen to redraw and user to cancel&lt;br /&gt;      wait 0 milliseconds with messages &lt;br /&gt;      if the uCancel of me is true then&lt;br /&gt;        answer "Are you sure you want to cancel?" \&lt;br /&gt;            with "Yes" or "No"&lt;br /&gt;        if it is "Yes"&lt;br /&gt;        then exit repeat&lt;br /&gt;        else set the uCancel of me to false&lt;br /&gt;      end if&lt;br /&gt;    end if&lt;br /&gt;  end repeat&lt;br /&gt;  disable button "Cancel"&lt;br /&gt;end mouseUp&lt;/pre&gt;&lt;br /&gt;Now we add a button "Cancel" and give it the following script:&lt;br /&gt;&lt;pre class="revtalk"&gt;on mouseUp&lt;br /&gt;  set the uCancel of button "TightLoop" to true&lt;br /&gt;end mouseUp&lt;/pre&gt;&lt;br /&gt;And presto! Not only does the screen redraw, but we now allow the user to stop the time-consuming process in our tight loop. Of course you'll have to add your logic to 'revert' any changes you've made in the process, but the above provides you with a skeleton script for handling this sort of situation.&lt;br /&gt;&lt;br /&gt;As with all good things in life, 'wait with messages' comes at a price: as the user can now click your button twice, or trigger other scripts, it is very important to disable those parts of your user interface that shouldn't be executed 'concurrently' - my favourite method is to switch to a 'limited' environment, disabling all controls, displaying a blended white overlay over them as well as the 'Cancel' button making it the only enabled control. After the loop, the stack returns to the 'unlimited' environment, hiding the overlay and enabling the controls.&lt;br /&gt;&lt;br /&gt;Some of you may wonder what happens when a script issues 'wait 1 second with messages' and the user clicks on another button: the other button's script is executed first, and then the engine checks if the wait timeout has passed, and resumes the original script. This means that if your other button script takes 5 seconds to complete, your first button script will remain suspended - the revEngine is not multi-threaded. But with a little planning, you can provide a much better user experience.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5092807049363133934-2037362381666321950?l=quartam.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://quartam.blogspot.com/feeds/2037362381666321950/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=5092807049363133934&amp;postID=2037362381666321950' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5092807049363133934/posts/default/2037362381666321950'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5092807049363133934/posts/default/2037362381666321950'/><link rel='alternate' type='text/html' href='http://quartam.blogspot.com/2010/03/but-waiting-makes-me-curious.html' title='But the waiting makes me curious'/><author><name>Quartam Software - Jan Schenkel</name><uri>http://www.blogger.com/profile/08312925714131118786</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5092807049363133934.post-1790879052876870017</id><published>2010-01-12T12:14:00.000-08:00</published><updated>2010-01-12T12:37:55.495-08:00</updated><title type='text'>In memoriam: Bill Marriott</title><content type='html'>&lt;span style="font-style:italic;"&gt;As usual after the Holidays, I had some catching up to do with emails on the use-revolution mailing list. It was quite a shock when I read the news that &lt;a href="http://www.linkedin.com/in/wmarriott"&gt;William Marriott&lt;/a&gt;, marketing director at &lt;a href="http://www.runrev.com"&gt;RunRev&lt;/a&gt;, had &lt;a href="http://mail.runrev.com/pipermail/use-revolution/2010-January/132931.html"&gt;passed away&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;To the rev developer community, he was the guy who worked his way up from the trenches, growing from a vocal advocate of quality, to the man who held surveys to figure out who we were and what we wanted, and helped in so many ways to reshape the image of the RunRev company. For the revSelect third-party add-on developers, he was the guy who not only gracefully hosted the webinars, but was in general someone you could easily work with to get results (a character trait shared at RunRev HQ, I might add).&lt;br /&gt;&lt;br /&gt;At the RunRevLive '08 conference in Las Vegas, I had the pleasure of taking up an afternoon of his time to talk about how I could improve my existing products, and exchange thoughts on an assortment of ideas. His combined marketing and programming skills, plus his sense of humour, make him a hard act to follow.&lt;br /&gt;&lt;br /&gt;My condolences and thoughts are with his family. May he rest in peace, or find a way to smuggle his laptop into heaven.&lt;/span&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5092807049363133934-1790879052876870017?l=quartam.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://quartam.blogspot.com/feeds/1790879052876870017/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=5092807049363133934&amp;postID=1790879052876870017' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5092807049363133934/posts/default/1790879052876870017'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5092807049363133934/posts/default/1790879052876870017'/><link rel='alternate' type='text/html' href='http://quartam.blogspot.com/2010/01/in-memoriam-bill-marriott.html' title='In memoriam: Bill Marriott'/><author><name>Quartam Software - Jan Schenkel</name><uri>http://www.blogger.com/profile/08312925714131118786</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5092807049363133934.post-6321604735088675265</id><published>2009-11-17T01:38:00.000-08:00</published><updated>2009-11-17T02:15:51.909-08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='revtalk'/><category scheme='http://www.blogger.com/atom/ns#' term='error handling'/><category scheme='http://www.blogger.com/atom/ns#' term='example code'/><title type='text'>Error handling in revTalk</title><content type='html'>No matter how carefully we craft our code to do the right thing, there will always be error outside of our control: a disk may be full, we may not have sufficient privileges, the server connection is lost, etc. So how do we handle this sort of situation in &lt;span style="font-weight:bold;"&gt;&lt;span style="font-style:italic;"&gt;revTalk&lt;/span&gt;&lt;/span&gt;? Depending on the situation, we check '&lt;span style="font-style:italic;"&gt;the result&lt;/span&gt;' after each potentially failing action, or we adopt the '&lt;span style="font-style:italic;"&gt;try-catch-throw&lt;/span&gt;' triad. &lt;br /&gt;&lt;br /&gt;Personally, I favor the triad as it is common in languages such as Java or the .NET family. And that's why I used it extensively in Quartam PDF Library, throwing errors whenever something was wrong. Some people find this annoying, I feel that it keeps my code cleaner as there is no error checking all over my script, obfuscating what I'm really trying to accomplish.&lt;br /&gt;&lt;br /&gt;The important thing to remember is that revTalk is a language which mixes the approaches: for a command like 'open file' you must check &lt;span style="font-style:italic;"&gt;the result&lt;/span&gt; to see if it failed by any chance. However, if you try to set the 'angle' of an image to "foobar" it will &lt;span style="font-style:italic;"&gt;throw&lt;/span&gt; an error as "foobar" is not an integer. It's probably due to the HyperCard heritage, where there was no &lt;span style="font-style:italic;"&gt;try-catch-throw&lt;/span&gt; triad, but it's an inconsistency I'd rather see cleaned up.&lt;br /&gt;&lt;br /&gt;Anyway, here's an example to get you started with the &lt;span style="font-style:italic;"&gt;try-catch-throw&lt;/span&gt; paradigm. Just create a new stack, add a field named 'Log' and add a button with the following script:&lt;br /&gt;&lt;pre class="revtalk"&gt;on mouseUp&lt;br /&gt;   try&lt;br /&gt;      Foo&lt;br /&gt;   catch tError&lt;br /&gt;      Log "mouseUp: running catch block"&lt;br /&gt;      answer error tError&lt;br /&gt;   finally&lt;br /&gt;      Log "mouseUp: running finally block"&lt;br /&gt;   end try&lt;br /&gt;end mouseUp&lt;br /&gt;&lt;br /&gt;command Foo&lt;br /&gt;   try&lt;br /&gt;      Bar&lt;br /&gt;   catch tError&lt;br /&gt;      Log "Foo: running catch block (" &amp; tError &amp; ")"&lt;br /&gt;   finally&lt;br /&gt;      Log "Foo: running finally block"&lt;br /&gt;   end try&lt;br /&gt;   if tError is not empty then&lt;br /&gt;      Log "Foo: rethrowing error (" &amp; tError &amp; ")"&lt;br /&gt;      throw tError&lt;br /&gt;   end if&lt;br /&gt;end Foo&lt;br /&gt;&lt;br /&gt;command Bar&lt;br /&gt;   Log "Bar: before calling Snafu"&lt;br /&gt;   Snafu&lt;br /&gt;   Log "Bar: after calling Snafu"&lt;br /&gt;end Bar&lt;br /&gt;&lt;br /&gt;command Snafu&lt;br /&gt;   answer "Continue, throw or exit to top?" with \&lt;br /&gt;          "Continue" or "Throw" or "Exit to top"&lt;br /&gt;   Log "Snafu: user picked (" &amp; it &amp; ")"&lt;br /&gt;   if it is "Throw" then&lt;br /&gt;      throw "Snafu: user decided to throw"&lt;br /&gt;   else if it is "Exit to top" then&lt;br /&gt;      exit to top&lt;br /&gt;   end if&lt;br /&gt;end Snafu&lt;br /&gt;&lt;br /&gt;--&lt;br /&gt;&lt;br /&gt;private command Log pText&lt;br /&gt;   put pText &amp; return after field "Log"&lt;br /&gt;end Log&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Click the button and pick the option 'Continue' - the following text should be in the log field:&lt;br /&gt;&lt;pre&gt;Bar: before calling Snafu&lt;br /&gt;Snafu: user picked (Continue)&lt;br /&gt;Bar: after calling Snafu&lt;br /&gt;Foo: running finally block&lt;br /&gt;mouseUp: running finally block&lt;/pre&gt;&lt;br /&gt;As you can see, everything went smoothly, and the code in the &lt;span style="font-style:italic;"&gt;finally&lt;/span&gt; blocks of both the Foo and mouseUp handlers were called. This is where you should handle any cleanup (such as closing the database, files, cursors,...)&lt;br /&gt;&lt;br /&gt;Now click the button again and pick the option 'Throw' - the following text should appear in the log field:&lt;br /&gt;&lt;pre&gt;Bar: before calling Snafu&lt;br /&gt;Snafu: user picked (Throw)&lt;br /&gt;Foo: running catch block (Snafu: user decided to throw)&lt;br /&gt;Foo: running finally block&lt;br /&gt;Foo: rethrowing error (Snafu: user decided to throw)&lt;br /&gt;mouseUp: running catch block&lt;br /&gt;mouseUp: running finally block&lt;/pre&gt;&lt;br /&gt;As you can see, the &lt;span style="font-style:italic;"&gt;throw&lt;/span&gt; in the Snafu handler caused the &lt;span style="font-style:italic;"&gt;catch&lt;/span&gt; block to be executed in the Foo handler, followed by its &lt;span style="font-style:italic;"&gt;finally&lt;/span&gt; block. In this case, I decided to re-&lt;span style="font-style:italic;"&gt;throw&lt;/span&gt; the error after the &lt;span style="font-style:italic;"&gt;try&lt;/span&gt; block of the Foo handler, so that the &lt;span style="font-style:italic;"&gt;catch&lt;/span&gt; block in the mouseUp handler would also be called, automatically followed by its &lt;span style="font-style:italic;"&gt;finally&lt;/span&gt; block. Note that the Bar handler was immediately interrupted so no 'after' log line was written.&lt;br /&gt;&lt;br /&gt;Now click the button once more and pick the option 'Exit to top' - the following text should appear in the log field:&lt;br /&gt;&lt;pre&gt;Bar: before calling Snafu&lt;br /&gt;Snafu: user picked (Exit to top)&lt;/pre&gt;&lt;br /&gt;Yes, that's right, this means that execution halts completely, and not even the &lt;span style="font-style:italic;"&gt;finally&lt;/span&gt; blocks in the Foo and mouseUp handlers are called.&lt;br /&gt;&lt;br /&gt;This last bit teaches us to be cautious and not try and mix the different approaches to error handling without testing out the consequences.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5092807049363133934-6321604735088675265?l=quartam.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://quartam.blogspot.com/feeds/6321604735088675265/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=5092807049363133934&amp;postID=6321604735088675265' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5092807049363133934/posts/default/6321604735088675265'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5092807049363133934/posts/default/6321604735088675265'/><link rel='alternate' type='text/html' href='http://quartam.blogspot.com/2009/11/error-handling-in-revtalk.html' title='Error handling in revTalk'/><author><name>Quartam Software - Jan Schenkel</name><uri>http://www.blogger.com/profile/08312925714131118786</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5092807049363133934.post-5892255580678257484</id><published>2009-11-14T12:17:00.000-08:00</published><updated>2009-11-15T02:09:11.596-08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='revtalk'/><category scheme='http://www.blogger.com/atom/ns#' term='animation'/><category scheme='http://www.blogger.com/atom/ns#' term='revolution'/><category scheme='http://www.blogger.com/atom/ns#' term='graphics'/><category scheme='http://www.blogger.com/atom/ns#' term='revlet'/><category scheme='http://www.blogger.com/atom/ns#' term='revweb'/><category scheme='http://www.blogger.com/atom/ns#' term='revmedia'/><title type='text'>Some time for reflection?</title><content type='html'>What do you do when you have two weeks off, after working hard for months to meet deadlines and ship a new module on time and per specification, with as only interruption: going to the &lt;a href="http://www.runrevlive.com"&gt;RunRevLive'09 conference&lt;/a&gt; and doing three presentations? Well, I'm glad you asked - I started by catching up on sleep and doing the couch potato thing, watching Star Trek Enterprise season 3.&lt;br /&gt;&lt;br /&gt;But along came the release of &lt;a href="http://www.runrev.com/products/the-rev-platform/whats-new-in-40/"&gt;version 4.0 of the revPlatform&lt;/a&gt;, with the highly anticipated &lt;a href="http://revweb.runrev.com/"&gt;revWeb browser plug-in&lt;/a&gt;. A-ha, that means it's experiment time! So I sat down and made a little demo revlet, using nothing but graphics, gradients and a few lines of code to demonstrate one way of faking a reflection while playing a simple animation of three circles.&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://3.bp.blogspot.com/_NlL9VZsJHto/Sv_S4J3dDBI/AAAAAAAAAAM/TNLhnxS_GMM/s1600-h/ReflectionRevlet.png"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 320px; height: 239px;" src="http://3.bp.blogspot.com/_NlL9VZsJHto/Sv_S4J3dDBI/AAAAAAAAAAM/TNLhnxS_GMM/s320/ReflectionRevlet.png" border="0" alt=""id="BLOGGER_PHOTO_ID_5404269940030901266" /&gt;&lt;/a&gt;&lt;br /&gt;If you don't have the revWeb plug-in installed yet, get it &lt;a href="http://revweb.runrev.com/"&gt;here&lt;/a&gt;. Then open a new browser window to &lt;a href="http://www.quartam.com/blogdemos/ReflectionRevlet.html"&gt;run the demo here&lt;/a&gt;. Click the 'Start' button to let the three circles roam around. I've included a link for you to download the original stack; if you don't yet have a copy of revMedia 4.0, you can download it &lt;a href="http://revmedia.runrev.com/"&gt;here&lt;/a&gt; for free.&lt;br /&gt;&lt;br /&gt;I'm sure the resident animation and graphics revGurus &lt;a href="http://www.runrevlive.com/speakers/ben-beaumont/"&gt;Ben Beaumont&lt;/a&gt;, &lt;a href="http://www.awesomemegamightygames.com/"&gt;Malte Brill&lt;/a&gt; or &lt;a href="http://www.tactilemedia.com/"&gt;Scott Rossi&lt;/a&gt; could make it look so much better and smoother, but it's the result of playing around for about an hour, and I'm quite pleased with what I put together, given that my multimedia skills are virtually limited to stick figures.&lt;br /&gt;&lt;br /&gt;The beauty of the revPlatform is its elegant language and extreme productivity. And now you can take revMedia for free and start creating revlets - no need to learn Flash ActionScript or JavaScript. Who can resist that?&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5092807049363133934-5892255580678257484?l=quartam.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://quartam.blogspot.com/feeds/5892255580678257484/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=5092807049363133934&amp;postID=5892255580678257484' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5092807049363133934/posts/default/5892255580678257484'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5092807049363133934/posts/default/5892255580678257484'/><link rel='alternate' type='text/html' href='http://quartam.blogspot.com/2009/11/some-time-for-reflection.html' title='Some time for reflection?'/><author><name>Quartam Software - Jan Schenkel</name><uri>http://www.blogger.com/profile/08312925714131118786</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://3.bp.blogspot.com/_NlL9VZsJHto/Sv_S4J3dDBI/AAAAAAAAAAM/TNLhnxS_GMM/s72-c/ReflectionRevlet.png' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5092807049363133934.post-4693343781378803268</id><published>2009-11-14T08:47:00.001-08:00</published><updated>2009-11-14T09:16:51.090-08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='support'/><category scheme='http://www.blogger.com/atom/ns#' term='question'/><category scheme='http://www.blogger.com/atom/ns#' term='incident'/><category scheme='http://www.blogger.com/atom/ns#' term='release'/><category scheme='http://www.blogger.com/atom/ns#' term='bug'/><category scheme='http://www.blogger.com/atom/ns#' term='procedure'/><category scheme='http://www.blogger.com/atom/ns#' term='modification'/><category scheme='http://www.blogger.com/atom/ns#' term='business'/><title type='text'>Support questions and bug reports</title><content type='html'>They're part of every-day life in the software business: users or colleagues encounter problems, and they need some way to find solutions. Sometimes a quick email is the answer, but generally speaking, there should be procedures in place to handle these items efficiently and transparently.&lt;br /&gt;&lt;br /&gt;At my day-job, we have an internal application called MIA, which offers a complete environment for running a software business. Aside from the more mundane tasks of accounting, billing and license keys, it also keeps track of complaints, incidents, modifications and releases.&lt;br /&gt;There is a web interface where customers can enter their issues, the support team picks these from the queue and tries to resolve them; if necessary, these incidents are escalated to a software engineer, which can result in a solution or a modification. The customer can track the status of the incident and if development is needed and scheduled, the planned version is added to the display as well.&lt;br /&gt;We have corrective, functional and structural releases of our products and depending on the type of incident, it will be resolved in one of the next releases. Overall, it is an efficient system that our customers appreciate very much - even if they may not always like how long it takes to actually implement the change. &lt;br /&gt;&lt;br /&gt;Of course, the situation is a little different for Quartam Software. It is mostly a one-man-show - apart from the external accountant who takes care of the taxes, and some help for the other accounting duties, I take care of everything: sending license keys, answering support emails, developing new features and fixing bugs, plus the information requests as well as the consulting and custom software jobs.&lt;br /&gt;Unfortunately, that means I can't get to everything as quickly as I wish. I do try and get back to people in a reasonable time frame, and try to offer patch versions when some big problem pops up and the next functional release is still some time off. Sometimes, I fail to meet my own standards. But I, Jan Schenkel, am the person to send emails to, for everything.&lt;br /&gt;&lt;br /&gt;In a larger organization, that is simply impossible to do: the manager is there to run the business and ensure the commercial success while the operations run smoothly. When procedures are in place, they should be followed to ensure that every item is handled correctly by the designated person. Only when things don't appear to be moving or important issues seem to get ignored, should you go one step higher, and involve management.&lt;br /&gt;But please don't do it for every item that crops up, and be willing to compromise. The customer may be king, but the guy with the compiler is not a serf.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5092807049363133934-4693343781378803268?l=quartam.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://quartam.blogspot.com/feeds/4693343781378803268/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=5092807049363133934&amp;postID=4693343781378803268' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5092807049363133934/posts/default/4693343781378803268'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5092807049363133934/posts/default/4693343781378803268'/><link rel='alternate' type='text/html' href='http://quartam.blogspot.com/2009/11/support-questions-and-bug-reports.html' title='Support questions and bug reports'/><author><name>Quartam Software - Jan Schenkel</name><uri>http://www.blogger.com/profile/08312925714131118786</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5092807049363133934.post-8460477132119089473</id><published>2009-10-25T22:18:00.000-07:00</published><updated>2009-10-25T23:16:03.221-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='revtalk'/><category scheme='http://www.blogger.com/atom/ns#' term='revolution'/><category scheme='http://www.blogger.com/atom/ns#' term='apple events'/><category scheme='http://www.blogger.com/atom/ns#' term='snow leopard'/><category scheme='http://www.blogger.com/atom/ns#' term='MacOS X'/><title type='text'>Revolution, Snow Leopard and AppleEvent handlers</title><content type='html'>As you know, Macs have always sported a rather different operating system - in fact, MacOSX is quite good at hiding its powerful UNIX underpinnings from the average user, while still offering its full power to the more advanced computer user. But tossing out the old brittle 'cooperative multi-tasking' MacOS Classic, and swapping in the modern, robust NeXtStep with a new coat of paint, required that the Apple developers came up with a whole compatibility layer, named &lt;span style="font-style:italic;"&gt;Carbon&lt;/span&gt;.&lt;br /&gt;&lt;br /&gt;And one of the things that Carbon brought with it, was the system of &lt;span style="font-style:italic;"&gt;Apple Events&lt;/span&gt; - added in System 7.0, this new type of event allowed Mac applicatiosn to talk to one another. Later, these Apple Events formed the underpinning of &lt;span style="font-style:italic;"&gt;AppleScript&lt;/span&gt;, the system-wide scripting mechanism that is widely used for automating processes (and in turn, underpins the &lt;span style="font-style:italic;"&gt;Automator&lt;/span&gt; tasks that allow you to point-and-click automation workflows in MacOSX).&lt;br /&gt;&lt;br /&gt;What exactly is an Apple Event? An AppleEvent has a generic Class and Id, as well as associated data - and our rev stacks can handle these events by implementing an 'appleEvent' handler in our (stack) script. Alright, but why should I care as revDeveloper? Because if you want to write a decent Mac application, you had better implement the 'core' events. These are apple events of class 'aevt' and have an id of 'oapp' (open application), 'odoc' (open document), 'pdoc' (print document) or 'quit' (exit application).&lt;br /&gt;&lt;br /&gt;Now, the revEngine is kind enough to handle the 'oapp' and 'quit' events for us, translating them into built-in events for us to handle, So we can concentrate on 'odoc' and 'pdoc' - and in fact, we only need to bother with these if we have some sort of 'editor' application. Of course, the Quartam Reports Layout Builder is exactly such an application. So I implemented a simple handler in my stack:&lt;br /&gt;&lt;pre name=code class=revTalk&gt;on appleEvent pClass, pId&lt;br /&gt;  if pClass is "aevt" and pId is "odoc" then&lt;br /&gt;    -- get the associated data&lt;br /&gt;    request appleEvent data&lt;br /&gt;    put it into tFilePath&lt;br /&gt;    -- now handle opening the file somewhere else&lt;br /&gt;    OpenFile tFilePath&lt;br /&gt;  else&lt;br /&gt;    -- let the revEngine handle other events&lt;br /&gt;    pass appleEvent&lt;br /&gt;  end if&lt;br /&gt;end appleEvent&lt;/pre&gt;&lt;br /&gt;However, with the release of Snow Leopard, the format of the associated data appears to have changed rather dramatically. In previous releases, we would get a file path that we could use directly in file processing. For instance, your 'OpenFile' handler could look something like this:&lt;br /&gt;&lt;pre name=code class=revTalk&gt;on OpenFile pFilePath&lt;br /&gt;  put URL ("binfile:" &amp; pFilePath into tBinaryData&lt;br /&gt;  -- go ahead and parse the content of the file&lt;br /&gt;end OpenFile&lt;/pre&gt;&lt;br /&gt;For Snow Leopard, the good engineers at Apple have decided to change the format and where a file path from an 'odoc' event used to look something like "/My Big Project/Layouts/My First Layout.qrl", it now looks something like "file://localhost/My%20Big%20Project/Layouts/My%20First%20Layout.qrl" - presumabl, they're paving the way for other protocls than "file://" in the future, but this change has left our apps in the dust. I'm sure they were kind enough to update the &lt;span style="font-style:italic;"&gt;Cocoa&lt;/span&gt; framework to translate this automatically, but what about us who don't drink the Objective-C Kool-Aid?&lt;br /&gt;&lt;br /&gt;Well, the '%20' substitute for the space character, clued me in that there was some sort of URL encoding going on. So after a bit of fiddling, I came up with the following workaround for this prickly problem:&lt;br /&gt;&lt;pre name=code class=revTalk&gt;on appleEvent pCLass, pId&lt;br /&gt;  if pClass is "aevt" and pId is "odoc" then&lt;br /&gt;    -- get the associated data&lt;br /&gt;    request appleEvent data&lt;br /&gt;    put NormalizedPath(it) into tFilePath&lt;br /&gt;    -- now handle opening the file somewhere else&lt;br /&gt;    OpenFile tFilePath&lt;br /&gt;  else&lt;br /&gt;    -- let the revEngine handle other events&lt;br /&gt;    pass appleEvent&lt;br /&gt;  end if&lt;br /&gt;end appleEvent&lt;br /&gt;&lt;br /&gt;function NormalizedPath pFilePath&lt;br /&gt;  put pFilePath into tNormalizedPath&lt;br /&gt;  -- workaround change in Snow Leopard&lt;br /&gt;  if char 1 to 7 of pFilePath is "file://" then&lt;br /&gt;    set the itemDelimiter to slash&lt;br /&gt;    delete item 1 to 3 of pFilePath -- remove "file://localhost" from front&lt;br /&gt;    repeat for each item pFilePart in pFilePath&lt;br /&gt;      put slash &amp; urlDecode(tFilePart) after tNormalizedPath&lt;br /&gt;    end repeat&lt;br /&gt;  end if&lt;br /&gt;  return tNormalizedPath&lt;br /&gt;end NormalizedPath&lt;/pre&gt;&lt;br /&gt;Splitting off the NormalizedPath function means I can reuse it for the 'pdoc' Apple Event, if I ever were to support that. Plus, I can now add that to my 'generic' library that I share among all my applications. Anyway, I hope this blog post is of help to some fellow revDeveloper baffled by this problem.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5092807049363133934-8460477132119089473?l=quartam.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://quartam.blogspot.com/feeds/8460477132119089473/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=5092807049363133934&amp;postID=8460477132119089473' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5092807049363133934/posts/default/8460477132119089473'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5092807049363133934/posts/default/8460477132119089473'/><link rel='alternate' type='text/html' href='http://quartam.blogspot.com/2009/10/revolution-snow-leopard-and-appleevent.html' title='Revolution, Snow Leopard and AppleEvent handlers'/><author><name>Quartam Software - Jan Schenkel</name><uri>http://www.blogger.com/profile/08312925714131118786</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5092807049363133934.post-1074336843925227388</id><published>2009-10-19T22:19:00.000-07:00</published><updated>2009-10-19T22:35:32.935-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='revtalk'/><category scheme='http://www.blogger.com/atom/ns#' term='revolution'/><category scheme='http://www.blogger.com/atom/ns#' term='repeat loops'/><category scheme='http://www.blogger.com/atom/ns#' term='example code'/><title type='text'>Repeat loops, dialog boxes and aborting</title><content type='html'>It's one of those things that happen to all revolution developers at one point: you have written a repeat loop, inserted some 'answer' dialogs to figure out the problem, only to discover you're stuck and can't abort the loop because the dialog box is in the way. The only way out is killing the process, which has as downside that you've lost all changes to your stack ince the last Save.&lt;br /&gt;&lt;br /&gt;After you've been bitten once, you'll probably adopt the habit of inserting an escape plan in your loops. At the same time, you ought to prevent problems in your shipping code with these escape routes, but more on that later - a first escape route may look something like this:&lt;br /&gt;&lt;pre class="revtalk"&gt;&lt;br /&gt;repeat forever &lt;br /&gt;  add 1 to theCounter &lt;br /&gt;  answer theCounter &lt;br /&gt;  -- next block allows escape &lt;br /&gt;  if the cantAbort of this stack is false and the shiftKey is down then &lt;br /&gt;    answer "Are you sure you want to exit the loop after running" &amp;&amp; theCounter &amp;&amp; "times?" with "Continue" or "Exit" &lt;br /&gt;    if it is "Exit" then exit repeat &lt;br /&gt;  end if &lt;br /&gt;end repeat&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;The &lt;span style="font-style:italic;"&gt;cantAbort&lt;/span&gt; property of a stack determines whether or not you can abort in the first place (something which you really ought to turn on in standalones) and there's a checkbox on the stack inspector so it's quite convenient. &lt;br /&gt;&lt;br /&gt;Another option is to move that block of code into a separate handler at the stack level &lt;br /&gt;&lt;pre class="revtalk"&gt;&lt;br /&gt;on CheckForAbort pCounter &lt;br /&gt;  if the cantAbort of this stack is false and the shiftKey is down then &lt;br /&gt;    answer "Are you sure you want to abort execution of" &amp;&amp; pInfo &amp;&amp; "?" with "Continue" or "Exit" &lt;br /&gt;    if it is "Exit" then exit to top &lt;br /&gt;  end if &lt;br /&gt;end CheckForAbort&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;and then add a call to that handler in your loop &lt;br /&gt;&lt;pre class="revtalk"&gt;&lt;br /&gt;repeat forever &lt;br /&gt;  add 1 to theCounter &lt;br /&gt;  answer theCounter &lt;br /&gt;  CheckForAbort "TightLoop-" &amp; theCounter &lt;br /&gt;end repeat&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;The downside of the second approach is that any cleanup code that comes after the repeat won't be called. Of course you can also rely on try-throw-catch exception handling, but some people find this awkward. &lt;br /&gt;&lt;br /&gt;So just for completion, here's how you'd allow escape with cleanup via exception handling; first change the CheckForAbort handler to &lt;br /&gt;&lt;pre class="revtalk"&gt;&lt;br /&gt;on CheckForAbort pInfo &lt;br /&gt;  if the cantAbort of this stack is false and the shiftKey is down then &lt;br /&gt;    answer "Are you sure you want to abort execution of" &amp;&amp; pInfo &amp;&amp; "?" with "Continue" or "Exit" &lt;br /&gt;    if it is "Exit" then throw "Aborting execution of" &amp;&amp; pInfo &lt;br /&gt;  end if &lt;br /&gt;end CheckForAbort&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;and then modify the script with the loop so that it resembles the following &lt;br /&gt;&lt;pre class="revtalk"&gt;&lt;br /&gt;try &lt;br /&gt;  repeat forever &lt;br /&gt;    add 1 to theCounter &lt;br /&gt;    answer theCounter &lt;br /&gt;    CheckForAbort "TightLoop-" &amp; theCounter &lt;br /&gt;  end repeat &lt;br /&gt;catch tError &lt;br /&gt;  answer error tError &lt;br /&gt;finally &lt;br /&gt;  -- do your cleanup here &lt;br /&gt;  --&gt; it will be called whether or not something throws an error &lt;br /&gt;end try&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Hopefully, this will be of use to you at one point or another. Make sure to check out these entries in the documentation for more information about aborting running scripts: &lt;span style="font-style:italic;"&gt;cantAbort&lt;/span&gt; stack property, &lt;span style="font-style:italic;"&gt;allowInterrupts&lt;/span&gt; global property, &lt;span style="font-style:italic;"&gt;interrupt&lt;/span&gt; function and of course the &lt;span style="font-style:italic;"&gt;try-throw-catch&lt;/span&gt; triade for exception handling. Use them wisely, and provide your users with a safer envronment to work in...&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5092807049363133934-1074336843925227388?l=quartam.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://quartam.blogspot.com/feeds/1074336843925227388/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=5092807049363133934&amp;postID=1074336843925227388' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5092807049363133934/posts/default/1074336843925227388'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5092807049363133934/posts/default/1074336843925227388'/><link rel='alternate' type='text/html' href='http://quartam.blogspot.com/2009/10/repeat-loops-dialog-boxes-and-aborting.html' title='Repeat loops, dialog boxes and aborting'/><author><name>Quartam Software - Jan Schenkel</name><uri>http://www.blogger.com/profile/08312925714131118786</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5092807049363133934.post-1697754457776163431</id><published>2009-10-04T08:00:00.000-07:00</published><updated>2009-10-04T08:11:08.491-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='webinar'/><category scheme='http://www.blogger.com/atom/ns#' term='revolution'/><category scheme='http://www.blogger.com/atom/ns#' term='quartam reports'/><category scheme='http://www.blogger.com/atom/ns#' term='revselect'/><category scheme='http://www.blogger.com/atom/ns#' term='reports'/><title type='text'>Quartam Reports Webinar</title><content type='html'>&lt;span style="font-style:italic;"&gt;As &lt;a href="http://www.runrev.com/newsletter/october/issue79/newsletter4.php"&gt;announced in the revUp 79 newsletter&lt;/a&gt; for revAfficionados, I'll be hosting a webinar on Quartam Reports, sponsored by the &lt;a href="http://www.runrev.com/products/related-software/"&gt;revSelect third-party extension program&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;What can you expect to see?&lt;br /&gt;- a few slides to explain how Quartam Reports can help you revDevelopers&lt;br /&gt;- some examples of what you can achieve with Quartam Reports&lt;br /&gt;- a brisk walkthrough creating a report for an example application&lt;br /&gt;- time for questions and answers at the end&lt;br /&gt;&lt;br /&gt;&lt;a href="https://www2.gotomeeting.com/register/686916339"&gt;Click here to register&lt;/a&gt; and join us on October 6, 2009 at 7pm BST (8pm for most of Europe, 2pm Eastern for US viewers) - if you can't make it, you can always watch the recording a few days later. I'll post the download link once available.&lt;/span&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5092807049363133934-1697754457776163431?l=quartam.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://quartam.blogspot.com/feeds/1697754457776163431/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=5092807049363133934&amp;postID=1697754457776163431' title='2 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5092807049363133934/posts/default/1697754457776163431'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5092807049363133934/posts/default/1697754457776163431'/><link rel='alternate' type='text/html' href='http://quartam.blogspot.com/2009/10/quartam-reports-webinar.html' title='Quartam Reports Webinar'/><author><name>Quartam Software - Jan Schenkel</name><uri>http://www.blogger.com/profile/08312925714131118786</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5092807049363133934.post-3202243496822826660</id><published>2009-10-04T07:54:00.000-07:00</published><updated>2009-10-04T08:11:53.447-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='conference'/><category scheme='http://www.blogger.com/atom/ns#' term='slides'/><category scheme='http://www.blogger.com/atom/ns#' term='revolution'/><category scheme='http://www.blogger.com/atom/ns#' term='example code'/><title type='text'>RunRevLive'09 Slides and Example Code</title><content type='html'>&lt;span style="font-style:italic;"&gt;Just a month ago, &lt;a href="http://www.runrev.com"&gt;RunRev&lt;/a&gt; held their successful &lt;a href="http://www.runrevlive.com"&gt;RunRevLive'09 conference&lt;/a&gt; in Edinburgh.&lt;br /&gt;&lt;br /&gt;As I've blogged before, I had the honour of presenting on three topics:&lt;br /&gt;- 'Working with Java Classes'&lt;br /&gt;- 'Desktop Databases with SQLite'&lt;br /&gt;- 'Basic Reports &amp; Output'&lt;br /&gt;&lt;br /&gt;By popular demand, I've made the slides and example code for these sessions available in the Downloads section on the &lt;a href="http://www.quartam.com"&gt;quartam.com&lt;/a&gt; website.&lt;/span&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5092807049363133934-3202243496822826660?l=quartam.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://quartam.blogspot.com/feeds/3202243496822826660/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=5092807049363133934&amp;postID=3202243496822826660' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5092807049363133934/posts/default/3202243496822826660'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5092807049363133934/posts/default/3202243496822826660'/><link rel='alternate' type='text/html' href='http://quartam.blogspot.com/2009/10/runrevlive09-slides-and-example-code.html' title='RunRevLive&apos;09 Slides and Example Code'/><author><name>Quartam Software - Jan Schenkel</name><uri>http://www.blogger.com/profile/08312925714131118786</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5092807049363133934.post-1303657895012620565</id><published>2009-08-08T01:32:00.000-07:00</published><updated>2009-10-04T08:12:27.231-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='java'/><category scheme='http://www.blogger.com/atom/ns#' term='databases'/><category scheme='http://www.blogger.com/atom/ns#' term='conference'/><category scheme='http://www.blogger.com/atom/ns#' term='revolution'/><category scheme='http://www.blogger.com/atom/ns#' term='reports'/><title type='text'>Presenting at RunRevLive'09</title><content type='html'>Edinburgh is the hometown of &lt;a href="http://www.runrev.com"&gt;Runtime Revolution Ltd&lt;/a&gt;, the guys behind Revolution - and the scene of this year's &lt;a href="http://www.runrevlive.com"&gt;RunRevLive&lt;/a&gt; conference. After the huge success of last year's conference in Las Vegas, it has the makings of yet aother groundbreaking conference, focusing on the revWeb browser plug-in, the new product line-up (revMedia for free, revStudio and revEnterprise for extremely affordable prices) and the unveiling of revServer (the engine behind &lt;a href="http://www.on-rev.com"&gt;On-Rev)&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;When the crew asked me to present at this conference, I jumped at the chance to not just be there as one of the many attendees, but share my experiences with the rest of the community -  as I have done for years on the &lt;a href="http://lists.runrev.com/mailman/listinfo/use-revolution"&gt;use-revolution mailing list&lt;/a&gt; and the &lt;a href="http://forums.runrev.com"&gt;official forums&lt;/a&gt;. After submitting proposals, I got the green light for three presentations: 'Working with Java Classes', 'Desktop Databases with SQLite' and 'Basic Reports &amp; Output.'&lt;br /&gt;&lt;br /&gt;'Working with Java Classes' (Day Zero) delves into the various ways Revolution applications can interact with Java classes: a half-hour rollercoaster ride along shell commands, process comunication, socket communication, web services, message queues and externals.&lt;br /&gt;&lt;br /&gt;'Desktop Databases with SQLite' (Day One) will take you on a trip through a straightforward desktop application where the data is stored in an SQLite database. By the end, you should have a pretty good idea how to quickly develop a database front-end with revStudio or revEnterprise.&lt;br /&gt;&lt;br /&gt;'Basic Reports &amp; Output' (Day One) will use the same desktop application to demonstrate various ways of reporting: generating HTML and RTF documents, using Excel as reporting vehicle in multiple ways, sending stuff to the printer using the built-in commands, and (obviously) how you ca make your life easier with &lt;a href="http://www.quartam.com"&gt;Quartam Reports and Quartam PDF Library for Revolution&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;I'm looking forward to seeing you all at the conference, both familiar and new faces - there are few things better than finally meeting someone you've only known through exchanging emails, or going for drinks with someone you only get to see via these get-togethers. Plenty of good stuff, and no jet-lag for me as it's only a hop accross the channel :-)&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5092807049363133934-1303657895012620565?l=quartam.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://quartam.blogspot.com/feeds/1303657895012620565/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=5092807049363133934&amp;postID=1303657895012620565' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5092807049363133934/posts/default/1303657895012620565'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5092807049363133934/posts/default/1303657895012620565'/><link rel='alternate' type='text/html' href='http://quartam.blogspot.com/2009/08/presenting-at-runrevlive09.html' title='Presenting at RunRevLive&apos;09'/><author><name>Quartam Software - Jan Schenkel</name><uri>http://www.blogger.com/profile/08312925714131118786</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5092807049363133934.post-6116594257431812159</id><published>2009-07-22T12:33:00.000-07:00</published><updated>2009-07-22T12:59:19.618-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='plug-in'/><category scheme='http://www.blogger.com/atom/ns#' term='silverlight'/><category scheme='http://www.blogger.com/atom/ns#' term='RIA'/><category scheme='http://www.blogger.com/atom/ns#' term='revolution'/><category scheme='http://www.blogger.com/atom/ns#' term='ajax'/><category scheme='http://www.blogger.com/atom/ns#' term='javafx'/><category scheme='http://www.blogger.com/atom/ns#' term='flash'/><category scheme='http://www.blogger.com/atom/ns#' term='web'/><category scheme='http://www.blogger.com/atom/ns#' term='browser'/><title type='text'>revMedia 4 - a Revolution on the Web</title><content type='html'>First announced last year at the &lt;a href="http://www.runrevlive.com"&gt;runrevlive&lt;/a&gt;'08 conference in Las Vegas, the &lt;a href="http://www.runrev.com"&gt;RunRev&lt;/a&gt; team has unveiled the first public alpha of their revWeb browser plug-in - the easiest way to create RIAs (Rich Internet Applications) for use on Mac, Windows &lt;span style="font-style:italic;"&gt;and&lt;/span&gt; Linux. [click &lt;a href="http://www.runrev.com/company/press-release-archive/new-free-and-accessible-web-platform-launched/"&gt;here&lt;/a&gt; to read the press release]&lt;br /&gt;&lt;br /&gt;And to top it off, the revMedia toolkit will be absolutely &lt;span style="font-style:italic;"&gt;free&lt;/span&gt;. No longer do you have to cobble together an AJAX-based RIA using JavaScript in the browser and PHP or something else on the back-end; you can stop wondering why the Flash designer tool just doesn't think like you and me; gone are the days of pondering if Microsoft is going to cripple Silverlight on other platforms; and you don't even have to place bets on whether or not JavaFX is really such a splendid idea from server-focused Sun (the beleaguered company that is soon-to-be-gobbled-up-by-Oracle, if you're not keeping tabs on that platform).&lt;br /&gt;&lt;br /&gt;This is it: start of with revMedia, and deploy to the web; move up to revStudio when you need to build desktop applications that are native for each platform and don't require a 50 MB runtime download; and move up again to revEnterprise  when you need Oracle or SSL; and when you need easy hosting that uses the exact same language to create dynamic websites, On-Rev is your platform of choice.&lt;br /&gt;&lt;br /&gt;To be frank, I've always expressed my dismay regarding browsers as a deployment platform for 'real' business applications. Maybe if &lt;a href="http://en.wikipedia.org/wiki/XUL"&gt;XUL&lt;/a&gt; had taken off, building a feature-rich application running inside a browser would have made more sense. But including miles and miles of JavaScript to try and mimick a desktop app, that was clearly an impressive workaround, but still a hobbled experience.&lt;br /&gt;&lt;br /&gt;So I'm glad to see that the Revolution has hit the web, and we'll see a renaissance of good ol' &lt;a href="http://en.wikipedia.org/wiki/HyperCard"&gt;HyperCard&lt;/a&gt;: easy to program and easy to run. Keep up the good work, RunRev team!&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5092807049363133934-6116594257431812159?l=quartam.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://quartam.blogspot.com/feeds/6116594257431812159/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=5092807049363133934&amp;postID=6116594257431812159' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5092807049363133934/posts/default/6116594257431812159'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5092807049363133934/posts/default/6116594257431812159'/><link rel='alternate' type='text/html' href='http://quartam.blogspot.com/2009/07/revmedia-4-revolution-on-web.html' title='revMedia 4 - a Revolution on the Web'/><author><name>Quartam Software - Jan Schenkel</name><uri>http://www.blogger.com/profile/08312925714131118786</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5092807049363133934.post-5386679649205379312</id><published>2009-05-23T02:15:00.000-07:00</published><updated>2009-05-23T03:23:14.129-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='java'/><category scheme='http://www.blogger.com/atom/ns#' term='ejb'/><category scheme='http://www.blogger.com/atom/ns#' term='enterprise'/><category scheme='http://www.blogger.com/atom/ns#' term='eai'/><category scheme='http://www.blogger.com/atom/ns#' term='netbeans'/><category scheme='http://www.blogger.com/atom/ns#' term='books'/><category scheme='http://www.blogger.com/atom/ns#' term='jbi'/><category scheme='http://www.blogger.com/atom/ns#' term='soa'/><title type='text'>Another Batch of New Items on my Bookshelf</title><content type='html'>&lt;a href="http://www.packtpub.com"&gt;Packt Publishing Ltd&lt;/a&gt; is celebrating its 5th Anniversay - and I couldn't resist their special deal of 5 books for 75 euros. So my book collection got expanded with the following titles.&lt;br /&gt;&lt;br /&gt;&lt;a href="http://www.packtpub.com/service-oriented-architecture-for-java-applications/book"&gt;Service Oriented Architecture with Java&lt;/a&gt; was the first book that caught my eye. It gives a broad overview of why &lt;a href="http://en.wikipedia.org/wiki/Service_oriented_architecture"&gt;SOA (Service Oriented Architecture)&lt;/a&gt; is a good approach to developing new and combining existing applications. It also does a good job explaining the difference between tradition &lt;a href="http://en.wikipedia.org/wiki/Enterprise_application_integration"&gt;EAI (Enterprise Application Integration)&lt;/a&gt; and SOA, and why using an &lt;a href="http://en.wikipedia.org/wiki/Enterprise_service_bus"&gt;ESB (Enterprise Service Bus)&lt;/a&gt; is a good idea to assemble all the parts.&lt;br /&gt;Having finished this book the dame day as it arrived, I have to say I liked the content, but was a little confused by some of the sentence constructs; it doesn't help when both the authors and the reviewer are non-native English speakers; I had it a little easier since I worked with developers from India in a not-too-distant past. Even though a technical book doesn't have to read like poetry, an editor should prevent from adding to the complexity of the material.&lt;br /&gt;&lt;br /&gt;&lt;a href="http://www.packtpub.com/service-oriented-java-business-integration/book"&gt;Service Oriented Java Business Integration&lt;/a&gt; was the logical next item, as JBI is one of those items I have a particular interest in. Even though this book is centered around &lt;a href="http://servicemix.apache.org/home.html"&gt;Apache ServiceMix&lt;/a&gt;, I'm sure I'll be able to apply its principles to the alternative &lt;a href="https://open-esb.dev.java.net/"&gt;Glassfish OpenESB&lt;/a&gt; open-source project backed by Sun. Especially when I combine its contents with what I learned from the book &lt;a href="http://www.packtpub.com/netbeans-enterprise-pack/book"&gt;Building SOA-Based Composite Applications Using NetBeans IDE 6&lt;/a&gt; that I picked up last year.&lt;br /&gt;&lt;br /&gt;&lt;a href="http://www.packtpub.com/java-ee5-development-with-netbeans-6/book"&gt;Java EE 5 Development with NetBeans 6&lt;/a&gt; continues the theme of my desire to learn more about Java EE development with NetBeans and Glassfish. Currently about a third into this book, I find it a good companion to the book &lt;a href="http://www.packtpub.com/Java-EE-5-GlassFish-Application-Servers/book"&gt;Java EE 5 Development using GlassFish Application Server&lt;/a&gt; by the same author, which I also picked up last year - naturally, there is some overlap, but I have the memory of a goldfish and the author really does a good job of explaining things.&lt;br /&gt;&lt;br /&gt;&lt;a href="http://www.packtpub.com/developer-guide-for-ejb3/book"&gt;EJB 3 Developer Guide&lt;/a&gt; wraps up the enterprise-oriented book tour. Packed with practical examples, it gets straight to business. When I want a more architectural point-of-view, I can always refer back to the book &lt;a href="http://www.manning.com/panda/"&gt;EJB 3 in Action&lt;/a&gt;, published by Manning, which is three times the size but also contains more of the theory and differences between EJB 3 and older versions - and how the introduction of &lt;a href="http://java.sun.com/javaee/technologies/javaee5.jsp"&gt;Java EE 5&lt;/a&gt;  really did simplify development a lot.&lt;br /&gt;&lt;br /&gt;&lt;a href="http://www.packtpub.com/java-swing-extreme-testing/book"&gt;Swing Extreme Testing&lt;/a&gt; may seem like the odd-one-out, but what's the use of building an enterprise application back-end, if no one can see the contents? Having built a number of Swing-based front-ends, I can attest it takes time to get it "right" - and I'm sure I'm still doing things wrong. I sure wish someone would publish a book that explains best practices for Swing user interface development like &lt;a href="http://java.sun.com/docs/books/effective/"&gt;Effective Java&lt;/a&gt; does for the Java language.&lt;br /&gt;But I digress. While I've gradually converted to using &lt;a href="http://www.junit.org/"&gt;JUnit&lt;/a&gt; for writing unit tests for the classes that make up the domain model, I still have to decide on a framework for testing the user interface. Once I've worked my way through this book, I'll be sure to check out &lt;a href="http://easytesting.org/swing/wiki/pmwiki.php"&gt;FEST (Fixtures for Easy Software Testing)&lt;/a&gt; as the online articles I've read are quite favorable to this framework.&lt;br /&gt;&lt;br /&gt;Ah, so much to read, so many experiments to conduct - and only so little time...&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5092807049363133934-5386679649205379312?l=quartam.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://quartam.blogspot.com/feeds/5386679649205379312/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=5092807049363133934&amp;postID=5386679649205379312' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5092807049363133934/posts/default/5386679649205379312'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5092807049363133934/posts/default/5386679649205379312'/><link rel='alternate' type='text/html' href='http://quartam.blogspot.com/2009/05/another-batch-of-new-items-on-my.html' title='Another Batch of New Items on my Bookshelf'/><author><name>Quartam Software - Jan Schenkel</name><uri>http://www.blogger.com/profile/08312925714131118786</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5092807049363133934.post-4589503715316167385</id><published>2009-04-16T11:53:00.000-07:00</published><updated>2009-04-16T12:19:54.134-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='javascript'/><category scheme='http://www.blogger.com/atom/ns#' term='java'/><category scheme='http://www.blogger.com/atom/ns#' term='revolution'/><category scheme='http://www.blogger.com/atom/ns#' term='desktop'/><category scheme='http://www.blogger.com/atom/ns#' term='web hosting'/><category scheme='http://www.blogger.com/atom/ns#' term='applications'/><category scheme='http://www.blogger.com/atom/ns#' term='browser'/><title type='text'>Vive la Revolution!</title><content type='html'>It has been a busy month of April at the &lt;a href="http://www.runrev.com"&gt;Runtime Revolution&lt;/a&gt; HQ - last week we witnessed the release of &lt;a href="http://www.runrev.com/company/latest-news-archive/revolution-35-shipping/"&gt;Revolution 3.5&lt;/a&gt;, with its data grid and behavior scripting, and this week they've announced the &lt;a href="http://www.on-rev.com/home/"&gt;On-Rev web hosting&lt;/a&gt; initiative.&lt;br /&gt;&lt;br /&gt;Since the unveiling of the RunRev web strategy at the &lt;a href="http://www.runrev.com/newsletter/may/issue48/"&gt;runrevlive '08 conference&lt;/a&gt;, last year in Las Vegas, Rev-afficionados around the globe have been eagerly anticipating the day where they can do away with the mix-and-match (or should that be 'mismatch'?) of technologies needed to build modern, Internet-enabled applications.&lt;br /&gt;With customers and end-users requesting applications that work on desktops and web-browsers alike, developers have been forced to use combinations of JavaScript, Flash, .NET, Java and a myriad of frameworks to deliver solutions where not all features might have been fully implemented accross the different client interfaces.&lt;br /&gt;&lt;br /&gt;With the Revolution of the Future, developers will finally be able to build those same solutions with a comprehensive technology stack based on one language, and have it work as a desktop application or in a web browser, backed by business logic with database access on the server-side.&lt;br /&gt;Java promised us this 15 years ago, and then kinda-sorta forgot about this promise. Granted, &lt;a href="http://java.sun.com/developer/technicalArticles/javase/java6u10/"&gt;Java SE 6u10&lt;/a&gt; finally fixed the Applets experience, and &lt;a href="http://java.sun.com/javaee/technologies/javaee5.jsp"&gt;Java EE 5&lt;/a&gt; definitely simplified server-side development; but in their panic to provide an alternative to Flash, they produced &lt;a href="http://javafx.com/"&gt;JavaFX&lt;/a&gt; with a new scripting language that doesn't even work like Java.&lt;br /&gt;&lt;br /&gt;In short, I think the RunRev team has a winner on their hands - and I am definitely looking forward to putting these new Revolution technologies to good use.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5092807049363133934-4589503715316167385?l=quartam.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://quartam.blogspot.com/feeds/4589503715316167385/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=5092807049363133934&amp;postID=4589503715316167385' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5092807049363133934/posts/default/4589503715316167385'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5092807049363133934/posts/default/4589503715316167385'/><link rel='alternate' type='text/html' href='http://quartam.blogspot.com/2009/04/vive-la-revolution.html' title='Vive la Revolution!'/><author><name>Quartam Software - Jan Schenkel</name><uri>http://www.blogger.com/profile/08312925714131118786</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5092807049363133934.post-8147686790334721624</id><published>2009-03-26T14:38:00.000-07:00</published><updated>2009-03-26T15:27:30.222-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='behavior'/><category scheme='http://www.blogger.com/atom/ns#' term='revolution'/><category scheme='http://www.blogger.com/atom/ns#' term='code reuse'/><category scheme='http://www.blogger.com/atom/ns#' term='data grid'/><category scheme='http://www.blogger.com/atom/ns#' term='scripting'/><category scheme='http://www.blogger.com/atom/ns#' term='Object-Oriented Programming'/><title type='text'>Behavior on the Grid</title><content type='html'>Today, the good people of &lt;a href="http://www.runrev.com"&gt;Runtime Revolution Ltd.&lt;/a&gt; presented an impressive webinar, showcasing the brand new Data Grid control that is a salivating new feature of Revolution 3.5 - in fact, this new control has given us a lot more than what most people would want from a table control. You can setup everything from a straightforward table with multiple alignments, to a spectacular form with dynamically resizing rows containing arbitrary controls with judicious use of template groups.&lt;br /&gt;&lt;br /&gt;This crown jewel was developed using the other new big technological addition to Revolution: behaviors. This allows you to write a script once and apply it to several other controls at once - and when you update the original behavior script, all those controls adopt the change in behavior automatically.&lt;br /&gt;Of course, if you're used to object-oriented programming, this sounds like simple inheritance. The xCard paradigm, pioneered over 20 years ago by Apple's &lt;a href="http://en.wikipedia.org/wiki/HyperCard"&gt;HyperCard&lt;/a&gt; is traditionally object-based but not fully object-oriented; there's a limited set of built-in controls and you can 'specialize' these by adding a script.&lt;br /&gt;Another typical OO design pattern, &lt;a href="http://en.wikipedia.org/wiki/Chain-of-responsibility_pattern"&gt;Chain of Responsibility&lt;/a&gt;, was the basis of the xCard &lt;a href="http://www.fourthworld.com/embassy/articles/revolution_message_path.html"&gt;message path&lt;/a&gt;, which allows developers to place handlers for events, command and function calls in a central place - if you needed to know where the event originated, you could use 'the target' property to work your magic from there.&lt;br /&gt;&lt;br /&gt;Strictly speaking this was enough to build something like the Data Grid in the past, but the new 'behavior' feature makes it a lot easier. The 'me' object points to the 'target' rather than the button that contains the 'behavior' script - so we no longer have to subject ourselves to 'long id' shenanigans to make sure we're modifiying the right controls.&lt;br /&gt;Experiments with the new 'behavior' scripting methods have me pondering how I can adopt this for all my Revolution projects and which controls I can rebuild and put together in a single library for reuse - and perhaps for others to use. Ah, if only there was a function to conjure a &lt;a href="http://en.wikipedia.org/wiki/Magical_objects_in_Harry_Potter#Time-Turners"&gt;Time-Turner&lt;/a&gt; so I could do everything I want to do but fail to find the time for...&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5092807049363133934-8147686790334721624?l=quartam.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://quartam.blogspot.com/feeds/8147686790334721624/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=5092807049363133934&amp;postID=8147686790334721624' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5092807049363133934/posts/default/8147686790334721624'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5092807049363133934/posts/default/8147686790334721624'/><link rel='alternate' type='text/html' href='http://quartam.blogspot.com/2009/03/behavior-on-grid.html' title='Behavior on the Grid'/><author><name>Quartam Software - Jan Schenkel</name><uri>http://www.blogger.com/profile/08312925714131118786</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5092807049363133934.post-1106148262327254757</id><published>2009-02-28T10:39:00.000-08:00</published><updated>2009-02-28T11:13:00.294-08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='bug'/><category scheme='http://www.blogger.com/atom/ns#' term='testing'/><category scheme='http://www.blogger.com/atom/ns#' term='programming'/><category scheme='http://www.blogger.com/atom/ns#' term='bugfix'/><title type='text'>The 15 Minute Fix</title><content type='html'>Every once in a while, you hear the infamous words "That should only take 15 minutes to fix" - often spoken by someone who isn't a professional software developer. While it is true that a number of bugs can be rectified rather quickly - think: typo or simple inversion of logic - the change in the source code is only a tiny part of the process.&lt;br /&gt;&lt;br /&gt;At my day-job, we have to pay extreme attention to the accurate workings of our software. As we cater to the healthcare indsutry, the lives of patients may well depend on our software. The physicians need acuurate data to apply the best treatment, so it is our duty that we correctly handle all the input coming into our laboratory informaton system, as well as the output we produce in the form of reports or data exchange with the general hospital system.&lt;br /&gt;&lt;br /&gt;This means we follow a strict procedure for every modification to the source code: analysis -&gt; analysis review -&gt; code -&gt; code review -&gt; unit test -&gt; integration test -&gt; documentation + localisation -&gt; final review. Exhaustive test plans are executed at every release to ensure that the software is reliable and that one change doesn't have a hidden impact on another part of the system. And that's something which simply can't be done in a quarter of an hour.&lt;br /&gt;&lt;br /&gt;Even then, you can never be sure that your program will doexectly what it's supposed to do. There are many internal and external variables at play - and even if a computer is supposed to be a deterministic system, where the exact same actions should have the exact same result, errors do occur that are sometimes hard to figure out. Computer software is a composition of many layers - hardware, operating system, frameworks, libraries - most of which are outside of our control.&lt;br /&gt;&lt;br /&gt;Yet we all rely on technology and the building blocks we have, to build new functionality, patch existing code and sometimes make radical changes. And we hope that the person who built that underlying layer, is at least as smart as we are, and caught all the mistakes before that layer came to us for further use. Looking at the giant software companies' bug lists may leave you in a bit of a shock when you see thousands upon thousands of entries, some lingering for a decade.&lt;br /&gt;&lt;br /&gt;Is it all gloom and despair though? Definitely not - there are plenty of good practices we as developers can and should apply. Where I used to only care about producing the code for the features, I now take the time to write tests for my code, and run my libraries and applications through a battery of manual tests before releasing them into the wild. That sure prevents a lot of frustration for testers and myself, yet even with all that care, sme things are bound to pop up in the field.&lt;br /&gt;&lt;br /&gt;A quick internal build for test purposes may only take 15 minutes - but a &lt;span style="font-style:italic;"&gt;real&lt;/span&gt; solution, fully tested and vetted, that can't be done in such a timeframe.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5092807049363133934-1106148262327254757?l=quartam.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://quartam.blogspot.com/feeds/1106148262327254757/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=5092807049363133934&amp;postID=1106148262327254757' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5092807049363133934/posts/default/1106148262327254757'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5092807049363133934/posts/default/1106148262327254757'/><link rel='alternate' type='text/html' href='http://quartam.blogspot.com/2009/02/15-minute-fix.html' title='The 15 Minute Fix'/><author><name>Quartam Software - Jan Schenkel</name><uri>http://www.blogger.com/profile/08312925714131118786</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5092807049363133934.post-1220094543275726417</id><published>2009-01-18T07:48:00.000-08:00</published><updated>2009-01-18T08:05:14.917-08:00</updated><title type='text'>In memoriam: Eric Chatonet</title><content type='html'>&lt;span style="font-style:italic;"&gt;Today, as I was catching up with emails on the &lt;a href="http://lists.runrev.com/mailman/listinfo/use-revolution"&gt;use-revolution&lt;/a&gt; mailing list, when I came accross a post &lt;a href="http://mail.runrev.com/pipermail/use-revolution/2009-January/118966.html"&gt;La communauté Revolution est en deuil&lt;/a&gt; that informed the members of how Eric Chatonet had passed away.&lt;br /&gt;&lt;br /&gt;Like many others, this news has left me with a deep sadness - for we, the members of the Revolution community, have all lost in Eric an extraordinary man - intelligent, generous, always helpful and armed with a wonderful sense of humour. I fondly remember meeting him in person at the Revolution Conference in Malta in 2006, exchanging ideas and experiences, and &lt;a href="http://www.landmarksofbritain.co.uk/eurorevcon_2006/conference/large-11.html"&gt;smoking cigarettes&lt;/a&gt; with him, Malte Brill, and Tariel Gogoberidze on the balcony of the hotel.&lt;br /&gt;&lt;br /&gt;My condoleances and thoughts are with his family. Having unexpectedly lost my father 4 years ago, I have an idea of how his son must be feeling right now. May he rest in peace, with a glass of wine and a cigarette, enjoying the afterlife to its fullest. He certainly earned it.&lt;/span&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5092807049363133934-1220094543275726417?l=quartam.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://quartam.blogspot.com/feeds/1220094543275726417/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=5092807049363133934&amp;postID=1220094543275726417' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5092807049363133934/posts/default/1220094543275726417'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5092807049363133934/posts/default/1220094543275726417'/><link rel='alternate' type='text/html' href='http://quartam.blogspot.com/2009/01/in-memoriam-eric-chatonet.html' title='In memoriam: Eric Chatonet'/><author><name>Quartam Software - Jan Schenkel</name><uri>http://www.blogger.com/profile/08312925714131118786</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5092807049363133934.post-1049411180855654449</id><published>2009-01-03T23:17:00.000-08:00</published><updated>2009-01-03T23:50:46.488-08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='community'/><category scheme='http://www.blogger.com/atom/ns#' term='revolution'/><category scheme='http://www.blogger.com/atom/ns#' term='newsletter'/><category scheme='http://www.blogger.com/atom/ns#' term='programming'/><title type='text'>Revolution newsletter articles</title><content type='html'>One of the best things about &lt;a href="http://www.runrev.com"&gt;Revolution&lt;/a&gt; is its strong-knit community - whether it's on the &lt;a href="http://lists.runrev.com/mailman/listinfo/use-revolution"&gt;mailing list&lt;/a&gt; or on the &lt;a href="http://forums.runrev.com"&gt;forums&lt;/a&gt;, when you post a question there's bound to come an answer from one of the many users that have faced a similar problem. And soon enough, the newbies come of age and start replying to questions as well. This way, the good advice keeps getting spread over an ever-growing group of users.&lt;br /&gt;&lt;br /&gt;Another important resource is the &lt;a href="http://www.runrev.com/developers/newsletters/"&gt;Revolution newsletter&lt;/a&gt;, where both the company's engineers and a variety of writers from the uer community share their knowledge and their solutions for challenges encountered while developing their applications. I just concluded a series of articles on using the 'merge' function, which got good reader feedback.&lt;br /&gt;&lt;br /&gt;The 'merge' function is one of those extremely helpful weapons in the Revolution armoury, which every developer should know about and use, as it has many applications and can save you a lot of time. It goes through a text, and evaluates expressions that you've put in between [[...]] double square brackets, and executes statements that you've put in between &amp;lt;?...?&amp;gt; processing instruction brackets; and when it's done, it hands you back the end result.&lt;br /&gt;&lt;br /&gt;This makes it an ideal tool for assembling HTML pages, RTF documents, XML files and other text-based file formats. But you can also use it as a preprocessor for templates, ranging from SQL queries to VBScript or AppleScript. One particular combination is to use Microsoft Word as a reporting system in two ways: merging an RTF template into a document, and then merging the necessary script to automate Word and print the document for you.&lt;br /&gt;&lt;br /&gt;Here are links to the individual articles:&lt;br /&gt;- &lt;a href="http://runrev.com/newsletter/august/issue55/newsletter2.php"&gt;The Power of Merge&lt;/a&gt; (revUp | Issue 55 | August 18th, 2008)&lt;br /&gt;- &lt;a href="http://runrev.com/newsletter/november/issue61/newsletter3.php"&gt;The Word of Merge - part 1&lt;/a&gt; (revUp | Issue 61 | November 20th, 2008)&lt;br /&gt;- &lt;a href="http://runrev.com/newsletter/december/issue62/newsletter3.php"&gt;The Word of Merge - part 2&lt;/a&gt; (revUp | Issue 62 | December 22nd, 2008)&lt;br /&gt;&lt;br /&gt;Enjoy the articles and Revolution! And if you still have to make a New Year's resolution, make it something along the lines of 'I want to learn' - as education is the only path that leads to prosperity...&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5092807049363133934-1049411180855654449?l=quartam.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://quartam.blogspot.com/feeds/1049411180855654449/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=5092807049363133934&amp;postID=1049411180855654449' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5092807049363133934/posts/default/1049411180855654449'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5092807049363133934/posts/default/1049411180855654449'/><link rel='alternate' type='text/html' href='http://quartam.blogspot.com/2009/01/revolution-newsletter-articles.html' title='Revolution newsletter articles'/><author><name>Quartam Software - Jan Schenkel</name><uri>http://www.blogger.com/profile/08312925714131118786</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5092807049363133934.post-5711218712245206389</id><published>2008-11-26T12:03:00.000-08:00</published><updated>2008-11-30T02:38:22.908-08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='java'/><category scheme='http://www.blogger.com/atom/ns#' term='character'/><category scheme='http://www.blogger.com/atom/ns#' term='xcard'/><category scheme='http://www.blogger.com/atom/ns#' term='revolution'/><category scheme='http://www.blogger.com/atom/ns#' term='unicode'/><category scheme='http://www.blogger.com/atom/ns#' term='byte'/><category scheme='http://www.blogger.com/atom/ns#' term='future-proof'/><category scheme='http://www.blogger.com/atom/ns#' term='hypercard'/><category scheme='http://www.blogger.com/atom/ns#' term='scripting'/><title type='text'>Future-proofing your Revolution scripts</title><content type='html'>While I'm still not giving out details on the new features or release date for &lt;a href="http://www.quartam.com"&gt;Quartam PDF Library&lt;/a&gt; 1.1, I would like to share some insights that I gained as part of my efforts on producing this new version. Back in September, the team of Runtime Revolution shipped &lt;a href="http://runrev.com/company/press-release-archive/revolution-30-shipping/"&gt;version 3.0&lt;/a&gt; of its cross-platform development tool &lt;a href="http://www.runrev.com"&gt;Revolution&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;Overall, Revolution 3.0 is an impressive release, with a revamped script editor, gradients, nested arrays and much-improved out-of-the-box experience thanks to the Start Center and Resource Center. One of the items that was also introduced, but not mentioned in the press release, was the new 'byte' chunk type.&lt;br /&gt;&lt;br /&gt;If you're wondering what it's good for or why you would want to use it instead of the good old 'character' chunk type, then allow me to explain the underlying reason for its introduction. Back in the HyperCard days, not much attention was given to languages that didn't fall neatly in the &lt;a href="http://en.wikipedia.org/wiki/ASCII"&gt;ASCII&lt;/a&gt;-domain, based on the English alphabet, where one character takes up a single byte and thus there are 256 possible characters (a number of which are control characters that can't even be displayed on screen).&lt;br /&gt;&lt;br /&gt;Naturally, this isn't quite enough room for a couple of thousand Chinese or Japanese characters, and that's where &lt;a href="http://www.unicode.org/"&gt;Unicode&lt;/a&gt; comes into play. This is a set of standards that govern how this much larger number of characters can be stored and should be interpreted. The &lt;a href="http://en.wikipedia.org/wiki/UTF-8"&gt;UTF-8&lt;/a&gt; standard still relies on single bytes as its main storage system, but uses special bit-settings to determine if the next character is a single byte or more than one byte.&lt;br /&gt;&lt;br /&gt;My other favorite cross-platform technology &lt;a href="http://java.sun.com"&gt;Java&lt;/a&gt; embraced Unicode from the very first day, which made perfect sense given its goal of "write once, run anywhere" global software. And to make this easier, if less memory-efficient for those circumstances where you only have to deal with the ASCII characters, it uses &lt;a href="http://en.wikipedia.org/wiki/UTF-16"&gt;UTF-16&lt;/a&gt; everywhere.&lt;br /&gt;&lt;br /&gt;When &lt;a href="http://runrev.com/company/press-release-archive/revolution-turns-two/"&gt;Revolution 2.0&lt;/a&gt; saw the daylight, it introduced support for Unicode text entry and manipulation. Rather than jumping on the UTF-16 bandwagon, the engineers decided to implement a more flexible system that would use plain-old ASCII for everyday operations, while allowing the developer to use UTF-16 only when necessary.&lt;br /&gt;&lt;br /&gt;While the implementation could still use some love and care, Revolution's technology director Mark Waddingham has big plans for it and wants to evolve to a system that is far less painful than anything else out there, but as resource-efficient as the current implementation. Which means that in the future, Revolution developers will be able to use the 'character', 'word', 'item' and 'line' chunks without having to care about the underlying encoding - the engine will know what constitutes a word or a sentence in the language of that piece of text and will take care of collation and all the gruesome details of Unicode.&lt;br /&gt;&lt;br /&gt;Traditionally, the one-byte-is-one-character paradigm of xCard meant that developers could use the 'character' keyword, along with the 'charToNum' and 'numToChar' functions, to manipulate data at the byte-level. But with this equivalence fading away at some point in the future, the new 'byte' chunk type is our saviour!&lt;br /&gt;&lt;br /&gt;Quartam PDF Library reads &lt;a href="http://www.w3.org/Graphics/PNG/"&gt;PNG&lt;/a&gt; and &lt;a href="http://www.jpeg.org/"&gt;JPEG&lt;/a&gt; image files as byte streams, extracting information about the image height and width, bit depth and other binary-encoded information. To ensure that this and other binary-reading code will keep working correctly in the future, I've decided to make the necessary changes in version 1.1 to use the 'byte' chunk type rather than the 'character' chunk type.&lt;br /&gt;&lt;br /&gt;While this means that the next version will require developers have Revolution 3.0 or higher, I feel this is a fair trade-off. In my experience, the new Revolution version performs well with less problems and quirks than ever. And if I compare my glacial speed of development with their feature-packed release momentum, I'm sure they will be shipping their next version before I do ;-)&lt;br /&gt;&lt;br /&gt;Just in case you'd like to know how I updated and thus future-proofed my code, here are some pointers:&lt;br /&gt;- replace 'open file theFilePath for read' with 'open file theFilePath for binary read'&lt;br /&gt;- replace 'open file theFilePath for write' with 'open file theFilePath for binary write'&lt;br /&gt;- replace 'length(theBinaryVariable)' / 'the number of chars in theBinaryVariable' with 'the number of bytes in theBinaryVariable'&lt;br /&gt;- replace 'char x to y of theBinaryVariable' with 'byte x to y of theBinaryVariable'&lt;br /&gt;- replace 'charToNum(char x of theBinaryVariable)' with 'byteToNum(byte x of theBinaryVariable)'&lt;br /&gt;- replace 'numToChar(theNumber)' with 'numToByte(theNumber)'&lt;br /&gt;&lt;br /&gt;That's it - no other changes required! The modification was straightforward and consistent, and everything works just fine. I know that my code will continue to work in future versions, and I can enjoy the same linear speed that I have now with the 'character' chunk type once that part is overhauled for Unicode embracing.&lt;br /&gt;&lt;br /&gt;If only all requested or necessary changes were so easy to implement...&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5092807049363133934-5711218712245206389?l=quartam.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://quartam.blogspot.com/feeds/5711218712245206389/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=5092807049363133934&amp;postID=5711218712245206389' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5092807049363133934/posts/default/5711218712245206389'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5092807049363133934/posts/default/5711218712245206389'/><link rel='alternate' type='text/html' href='http://quartam.blogspot.com/2008/11/future-proofing-your-revolution-scripts.html' title='Future-proofing your Revolution scripts'/><author><name>Quartam Software - Jan Schenkel</name><uri>http://www.blogger.com/profile/08312925714131118786</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5092807049363133934.post-4347524656033260016</id><published>2008-11-11T01:32:00.000-08:00</published><updated>2008-11-11T02:36:22.647-08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='alive'/><category scheme='http://www.blogger.com/atom/ns#' term='update'/><title type='text'>An Apparition</title><content type='html'>&lt;span style="font-style:italic;"&gt;If you have been wondering why it's been so quiet here lately, the answer is simple: a lot of work piled up, there was release pressure and the band that I sing in had its first gig - which ate up a lot of hours and energy. But things are slowly getting back to normal, and I'll have two weeks off from the day-job very soon - time which I'm going to spend relaxing and catching up on items that languished while I was busy. This blog is one of those bits that deserve attention. So let me start by finishing a post that I started to write back in August: &lt;span style="font-weight:bold;"&gt;&lt;a href="http://quartam.blogspot.com/2008/08/programmer-syndrome.html"&gt;'The Programmer Syndrome'&lt;/a&gt;&lt;/span&gt;&lt;/span&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5092807049363133934-4347524656033260016?l=quartam.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://quartam.blogspot.com/feeds/4347524656033260016/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=5092807049363133934&amp;postID=4347524656033260016' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5092807049363133934/posts/default/4347524656033260016'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5092807049363133934/posts/default/4347524656033260016'/><link rel='alternate' type='text/html' href='http://quartam.blogspot.com/2008/11/apparition.html' title='An Apparition'/><author><name>Quartam Software - Jan Schenkel</name><uri>http://www.blogger.com/profile/08312925714131118786</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5092807049363133934.post-8922794506468943495</id><published>2008-08-07T10:43:00.000-07:00</published><updated>2008-11-11T01:32:19.127-08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='programming'/><category scheme='http://www.blogger.com/atom/ns#' term='programmer syndrome'/><category scheme='http://www.blogger.com/atom/ns#' term='code reuse'/><category scheme='http://www.blogger.com/atom/ns#' term='libraries'/><title type='text'>The Programmer Syndrome</title><content type='html'>No, it's not as if all developers exhibit the same behaviours or nervous tics - but a lot of us tend to suffer from two less-than-stellar character traits: the "Not Invented Here" syndrome and its nefarious cousin "I've always done it this way". Not exactly what you're looking for in an industry where there's a continuing debate about code reuse.&lt;br /&gt;&lt;br /&gt;Of course it is a lot more fun to claim you're at the forefront of technology and innovation, but half of the time we're just rehashing the same technology - message queues are about as old as UN*X and let's not even talk about the Network Computer brouha. And sometimes this running in circles makes us blind and turn us into snooty stubborn time-wasters. If you're wondering what triggered this post, well twice this week have I been confronted with this near-pathological behaviour.&lt;br /&gt;&lt;br /&gt;The first occasion was reading one colleague's analysis for a new logging system to better track changes at the record level. Obviously an important feature that should be done right, and in this case the idea was to extend the logging features currently present in the application - thumbs up in the reuse department. What struck me as odd about it, was that he didn't want to adopt the auditing system that was added to the underlying database system in a not too distant past. What &lt;span style="font-style:italic;"&gt;really&lt;/span&gt; irked me about it, was that he didn't even mention it and didn't bother to express a good reason as to why we shouldn't use the built-in mechanism.&lt;br /&gt;&lt;br /&gt;The second occasion was when a colleague started to explain how they solved a certain asynchronous data processing problem at his previous job, in what we can euphemistically call a 1980's solution: have an external process poll the database every minute. And he insisted that this was the only robust way to do it, and it worked fast because they had hundreds of these batch processes running on an IBM RS-6000. As the problem unfolded, he had to add more and more checks and balances to counteract concurrent modifications by different processes. When I suggested that for this particular situation a message queue might be more appropriate, he dismissed it as costly and overcomplicated. I don't know about you, but message queues are generally built by very smart people who have thought through the different corner cases, and I'll happily follow their path.&lt;br /&gt;&lt;br /&gt;Naturally, as we have creative minds (and no, it's not because we express ourselves through code rather than music or painting that we shouldn't be considered "creative") we seek challenges and aim for the sky when we decide not to fix the code but rewrite it - paraphrasing Captain Jack Sparrow: "a bigger boat, a better boat, &lt;span style="font-style:italic;"&gt;that&lt;/span&gt; boat." And it is extremely important to deliver something that we feel proud of, which is what drives us to work insane hours and then some more, just to get it done by this impossible deadline with more features than were in the requirements.&lt;br /&gt;&lt;br /&gt;But aren't we sometimes overdoing it by completely ripping something apart and replacing it with an alternative that lives up to our &lt;span style="font-style:italic;"&gt;current&lt;/span&gt; standards? None of us were born with programming experience, we all had to learn through "Hello, world!" in a dozen programming languages, and if all is right, we're still learning new things every day. Yet with an ever-growing to do-list and much longer to research-list, I prefer not to duplicate efforts. Which is why open source libraries, with the right licensing policy, are a great blessing.&lt;br /&gt;&lt;br /&gt;Steering back to the topic, I feel this &lt;a href="http://java.sun.com/developer/technicalArticles/Interviews/bloch_effective_08_qa.html"&gt;interview with Joshua Bloch&lt;/a&gt; is a must-read. Joshua Bloch is the author of &lt;a href="http://java.sun.com/docs/books/effective/"&gt;Effective Java&lt;/a&gt;, considered by many to be among the best books on how to use the Java programming language. Here's the single-best quote from that interview: "In order to stay sane, most developers have a can-do attitude, and some take it too far. They say to themselves, 'Yes, there's a library, but I can do better.' Maybe you can, but that doesn't mean you should."&lt;br /&gt;&lt;br /&gt;Mind you, when I recently rebuilt a monitoring tool, I did scrap nearly 90% of the existing code and replaced it with new code that made as much use as possible of new features added in recent versions of Java. So please do not read the above as a mindless "holier-than-thou" rant about short-sighted colleagues. What I really hope, is that you remember to keep an open mind, learn about what's waiting for you to use in your applications - either in the core of your development libraries or in what's available from the outside world. And if you do roll your own, at least make a list of the arguments &lt;span style="font-style:italic;"&gt;pro&lt;/span&gt; and &lt;span style="font-style:italic;"&gt;contra&lt;/span&gt;.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5092807049363133934-8922794506468943495?l=quartam.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://quartam.blogspot.com/feeds/8922794506468943495/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=5092807049363133934&amp;postID=8922794506468943495' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5092807049363133934/posts/default/8922794506468943495'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5092807049363133934/posts/default/8922794506468943495'/><link rel='alternate' type='text/html' href='http://quartam.blogspot.com/2008/08/programmer-syndrome.html' title='The Programmer Syndrome'/><author><name>Quartam Software - Jan Schenkel</name><uri>http://www.blogger.com/profile/08312925714131118786</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5092807049363133934.post-7664858215804559791</id><published>2008-07-31T10:27:00.000-07:00</published><updated>2008-07-31T13:45:31.995-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Service-Oriented Architecture'/><category scheme='http://www.blogger.com/atom/ns#' term='java'/><category scheme='http://www.blogger.com/atom/ns#' term='Object-Oriented Programming'/><title type='text'>Service-Orientation vs. Object-Orientation</title><content type='html'>What better way to fill an hour-long lunch-break than by reading up on various websites? Some people like to play cards and others take a stroll around the village. Lots of people leave breadcrumbs in their keybpoard as they read their private emails, but my daily ritual includes trips to &lt;a href="http://www.macworld.com"&gt;MacWorld&lt;/a&gt;, &lt;a href="http://www.macuser.co.uk"&gt;MacUser&lt;/a&gt;, &lt;a href="http://www.oreillynet.com"&gt;OreillyNet&lt;/a&gt;, &lt;a href="http://www.java.net"&gt;Java.Net&lt;/a&gt;, &lt;a href="http://www.javaworld.com"&gt;JavaWorld&lt;/a&gt; and &lt;a href="http://www.javalobby.org"&gt;JavaLobby&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;Today I bumped into this fresh post by Masoud Kalali on JavaLobby: &lt;a href="http://java.dzone.com/articles/service-orientation-vs-object-"&gt;Service-Orientation vs. Object-Orientation: Understanding the Impedance Mismatch&lt;/a&gt;. Even if your language of choice is Revolution and therefore not object-oriented (even if it's object-based, but we'll leave that for some other time) you'll still want to head over to the site and read the article. After all, a smart programmer keeps his horizons broad so he knows which is the right tool for a particular job, right?&lt;br /&gt;&lt;br /&gt;Object-oriented programming has long established itself as a solid software development paradigm, improving on procedural programming by offering encaspulation, inheritance and polymorphism. Service-oriented architecture may still be surrounded by a lot of buzzwords,(often referred to as the SOA ALphabet Soup) but has clearly shown the way to seamless interoperability, regardless of development platform or operating system.&lt;br /&gt;&lt;br /&gt;Both OOP and SOA promote reuse. And as more and more business models, developed using an object-oriented language like Java, need to expose their logic as &lt;span style="font-style:italic;"&gt;services&lt;/span&gt;, it naturally becomes important to do it correctly: rather than just exposing all individually available procedures and functions as web services, you should take a step back and think very hard about exactly what services you seek to offer the outside world.&lt;br /&gt;&lt;br /&gt;The innards of your system may be as tightly- or loosely-coupled as you prefer, the truth is that there's a lot of methods in your classes that you're only going to use internally. Services are better defined in terms of business processes, anyway: your 'order entry' service would be wise to offer both a high-level 'do it all in one go' service where an entire order can be created using a single XML structure that includes master and detail information, and a fine-grained 'do just one thing at a time' API to create/read/update/delete one part at a time.&lt;br /&gt;&lt;br /&gt;So setting up your services becomes a matter of defining the sensible parts of a workflow, starting with a small area of your application and expanding as it makes sense, based on the feedback of the users of your services - which can be colleagues in the IT-department or external partners. In such an approach, services unsurprisingly have a lot in common with 'task oriented' user interfaces, where the user is guided through the different steps of the process - not in the sometimes belittling way of wizards, but as part of a way to empower the user by concentrating on his or her job.&lt;br /&gt;&lt;br /&gt;Information Technology in general and software development in particular are in so many ways still in their infancy. And as we're growing up, we're relearning things from the past, re-inventing how combining existing and sometimes considered-obsolete technologies delivers new value and makes our applications more powerful while easier to use. It feels great to take part in all this...&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5092807049363133934-7664858215804559791?l=quartam.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://quartam.blogspot.com/feeds/7664858215804559791/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=5092807049363133934&amp;postID=7664858215804559791' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5092807049363133934/posts/default/7664858215804559791'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5092807049363133934/posts/default/7664858215804559791'/><link rel='alternate' type='text/html' href='http://quartam.blogspot.com/2008/07/service-orientation-vs-object.html' title='Service-Orientation vs. Object-Orientation'/><author><name>Quartam Software - Jan Schenkel</name><uri>http://www.blogger.com/profile/08312925714131118786</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5092807049363133934.post-6890196662043693468</id><published>2008-07-22T11:56:00.000-07:00</published><updated>2008-07-22T13:19:31.446-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='java'/><category scheme='http://www.blogger.com/atom/ns#' term='integration'/><category scheme='http://www.blogger.com/atom/ns#' term='sockets'/><category scheme='http://www.blogger.com/atom/ns#' term='revolution'/><category scheme='http://www.blogger.com/atom/ns#' term='enterprise'/><category scheme='http://www.blogger.com/atom/ns#' term='message queue'/><title type='text'>Message in a Bottle</title><content type='html'>Most people who have spent some time with me, are convinced that I am musically stuck in the eighties: &lt;a href="http://www.thecure.com"&gt;The Cure&lt;/a&gt;, &lt;a href="http://www.siouxsie.com"&gt;Siouxsie and the Banshees&lt;/a&gt;, &lt;a href="http://www.the-sisters-of-mercy.com/"&gt;The Sisters of Mercy&lt;/a&gt; and all those happy-sounding bands. But don't let the title of this post lead you to believe I'm talking about &lt;a href="http://www.thepolice.com"&gt;The Police&lt;/a&gt;. No, I'm talking about enterprise integration in the form of &lt;span style="font-style:italic;"&gt;message queues&lt;/span&gt;.&lt;br /&gt;&lt;br /&gt;In my last post, I talked about how interesting it would be if we could combine the easy-to-master power of cross-platform desktop software development tool &lt;a href="http://www.runrev.com"&gt;Revolution&lt;/a&gt; with the heavy-weight champion in cross-platform development: &lt;a href="http://java.sun.com"&gt;Java&lt;/a&gt;. Well, the first way we can make these two technologies work together and play into one another's strengths, is by means of message queues.&lt;br /&gt;&lt;br /&gt;Back in the old days, most application integration consisted of exchanging files: application A exported data into a file, and then application B would import the data from that file, usually at the click of two buttons. Convenient, even if you had to copy them to and from floppy disks. In the multi-user time-sharing Unix world, pipes have traditionally played an important role in data exchange, as one application connected its output pipe to the other application's input pipe and vice versa, and just wrote data to one another.&lt;br /&gt;&lt;br /&gt;And of course, in the golden age of internetworked computers, you're bound to have enjoyed the miracles of TCP/IP and other networking protocols, as you surfed the web, exchanged instant messages and legally downloaded the occasional file. All thanks to the wonderful world of sockets that connected an application on your computer with an application running on another computer somewhere out there.&lt;br /&gt;&lt;br /&gt;Sockets are also often used to let two applications talk to one another while running on the same computer - does the word 'localhost' ring a bell? The common theme in this type of information interchange is that both sides agree on the data format and talk to one another directly in one form or another. Now while that's pretty easy to put together as long as the two parties can come to an agreement, it gets complicated as you have to talk to more and more applications.&lt;br /&gt;&lt;br /&gt;Think about it: if two apps talk to one another, you have one connection to develop; when a third app joins the party, two extra connections need to be made; a fourth app means adding three more to the mix. The mathematical formula is &lt;span style="font-style:italic;"&gt;n x (n - 1)&lt;/span&gt; connections to interconnect &lt;span style="font-style:italic;"&gt;n&lt;/span&gt; applications. And that's not such a strange scenario: nowadays, our applications can't sit there in their ivory towers - our users expect them to talk to one another &lt;span style="font-style:italic;"&gt;seamlessly&lt;/span&gt;.&lt;br /&gt;&lt;br /&gt;So, would you like to write an ever increasing number of connectors? Or would you prefer an approach that takes care of the plumbing? Enter message queues: think of them like a mass-mailing system for applications - you post a message to the queue and it will make sure that your message reaches all the applications that have declared their interest in receiving all or just certain types of messages. In this method, all you have to agree on is the message format and where it will be posted.&lt;br /&gt;&lt;br /&gt;I'm sure you'll agree nothing is easier than speaking the same language and delegating the actual delivery of messages? Add to this the promise of delivering your messages to the applications that are hooked into the system - in order and exactly once! That's certainly a much better deal than people losing the USB-stick, deleting the email or forgetting to import those data exchange files, right? And how about this: when you change the data in one client application, you broadcast it via the message queue and all other clients pick up the message and refresh accordingly? Sounds good to me...&lt;br /&gt;&lt;br /&gt;But enough theory, how can we do this in practice? The good news is that most Message Queue vendors have signed on to the Java Messaging System standard: &lt;a href="http://java.sun.com/products/jms/"&gt;JMS&lt;/a&gt;. Even better news is that in these days of open-source software development, we can pick up a solid implementation for free: Apache &lt;a href="http://activemq.apache.org"&gt;ActiveMQ&lt;/a&gt; - yes, from the makers of the server that's running most of the worldwide web. And the best news (not just for Revolution developers), is the &lt;a href="http://stomp.codehaus.org/"&gt;STOMP&lt;/a&gt; project: a combination of a bridge and a standard protocol, which allows any socket-wielding application to talk to just about all JMS-enabled Message Queues.&lt;br /&gt;&lt;br /&gt;You guessed it: the first way of combining Java and Revolution is using sockets to talk to a JMS-enabled Message Queue. Armed with the STOMP-specs and earlier experiments with socket communication, I put together a STOMP library in a few hours yesterday morning. Now don't rush over to the &lt;a href="http://www.quartam.com"&gt;quartam.com&lt;/a&gt; website just yet, as it needs more testing and tweaking before it's ready for general use. But I can tell you that sending and receiving messages works like a treat. Yet another reason to add Revolution to your developer toolbox if you haven't done so already.&lt;br /&gt;&lt;br /&gt;So where does the bottle come into this story? Well, I happily drank the rest of the bottle of dry white wine that I opened yesterday evening, while typing this post. Or maybe I &lt;span style="font-style:italic;"&gt;was&lt;/span&gt; just trying to lure you in with the title of one of the most famous songs by &lt;span style="font-style:italic;"&gt;The Police&lt;/span&gt;. Then again, wouldn't it be nice if all the messages we carefully wrote as a child at the beach, shoved in a coke bottle and threw into the sea, actually made it to their destinations?&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5092807049363133934-6890196662043693468?l=quartam.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://quartam.blogspot.com/feeds/6890196662043693468/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=5092807049363133934&amp;postID=6890196662043693468' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5092807049363133934/posts/default/6890196662043693468'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5092807049363133934/posts/default/6890196662043693468'/><link rel='alternate' type='text/html' href='http://quartam.blogspot.com/2008/07/message-in-bottle.html' title='Message in a Bottle'/><author><name>Quartam Software - Jan Schenkel</name><uri>http://www.blogger.com/profile/08312925714131118786</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5092807049363133934.post-393799432045543662</id><published>2008-06-30T11:54:00.001-07:00</published><updated>2008-06-30T12:38:57.162-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='java'/><category scheme='http://www.blogger.com/atom/ns#' term='sockets'/><category scheme='http://www.blogger.com/atom/ns#' term='revolution'/><category scheme='http://www.blogger.com/atom/ns#' term='books'/><category scheme='http://www.blogger.com/atom/ns#' term='web'/><category scheme='http://www.blogger.com/atom/ns#' term='concurrency'/><title type='text'>More Items on my Bookshelf</title><content type='html'>As a follow-up on my previous post, I have a confession to make. When I looked through my recent additions in Delicious Library, I realized that I had accidentally skipped one of my recent purchases. So in an effort to rectify that, here are some more items that recently found themselves added to my collection.&lt;br /&gt;&lt;br /&gt;The item that I forgot to mention, was &lt;a href="http://www.elsevier.com/wps/find/bookdescription.cws_home/714108/description#description"&gt;TCP/IP Sockets in Java&lt;/a&gt;. One of the tasks at my day-job is the maintenance of a monitoring service for diagnostics machines - also known as sample or specimen analyzers. The communication between our Laboratory Information System and these machines is of the utmost importance to our customers, and the laboratory staff need to be alerted when the analyzers fail to send their data because of a stalled translator service.&lt;br /&gt;The monitoring system is basically divided in two parts: the service daemon which monitors communication, and the dashboard user interface that displays the status. Both of these are written in Java, enabling monitoring on multiple operating systems - the ubiquitous Windows workstations, as well as Mac, Linux, Solaris and other Unix-derivatives. The data exchange between these parts relies on multi-threaded socket communication, and that's why I bought this book, as I'm sure I'll need it when I go about rewriting portions of the engine to make it more scalable and alleviate the limitations that the current version is bumping into.&lt;br /&gt;&lt;br /&gt;Which brings me to the next item on my bookshelf: &lt;a href="http://www.javaconcurrencyinpractice.com/"&gt;Java Concurrency in Practice&lt;/a&gt;. As computers gain multiple cores and both operating systems and CPUs get ever smarter about dividing up the workload among the available resources, not to mention the fact that users don't like waiting around twiddling their thumbs while data is getting processed, it becomes more and more important to learn how to write multi-threaded applications.&lt;br /&gt;Now Java was built from the start with multi-threading in mind. Over the years, the APIs have been improved, and Java 5 and Java 6 added classes that make concurrent programming a lot easier, shielding developers form the low-level plumbing needed to make it happen. And there's a lot more planned in Java 7 to help sorting and other tasks make better use of the processing powers offered by multi-core environments. This book gives you a lot better insight about the tricky little details of concurrency, explaining how processors, in an effort to maximize throughput, may shuffle instructions around in memory to optimize performance. If you're using Java and want to make optimal use of concurrency, this is a must-read.&lt;br /&gt;&lt;br /&gt;The last item that I want to mention this time around, is &lt;a href="http://java.sun.com/docs/books/effective/"&gt;Effective Java (2nd edition)&lt;/a&gt;. Considered by many as the bible of properly using Java, this book was revised and updated for Java 6, exploring new design patterns and language idioms, showing the reader how to make the most of features ranging from generics to enums to annotations to autoboxing to (you guessed it) concurrency utilities.&lt;br /&gt;As I'm spending more and more time with Java, I'm looking for a deeper understanding of the technology and the language, hoping to make my code clearer, more correct, more robust, and (fingers crossed) more reusable. Don't worry, I'm still using &lt;a href="http://www.runrev.com"&gt;Revolution&lt;/a&gt; as often as I can - it's the language that I jump back to whenever I get a chance. It makes things so much easier and with the upcoming browser plug-in, I can avoid the un-fun of Java applets, AJAX, Flash and Silverlight.&lt;br /&gt;&lt;br /&gt;But it is equally important to look around, learn from the available technologies and then pick the best tool for the job. Revolution is a wonderful solution for desktop applications that both need to look good and connect to databases and the internet. But when it comes to building highly-scalable multi-threaded applications, there's no replacement for Java. Ah, if only we could marry these two cross-platform technologies. Or maybe we can? That will have to wait for another post, however.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5092807049363133934-393799432045543662?l=quartam.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://quartam.blogspot.com/feeds/393799432045543662/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=5092807049363133934&amp;postID=393799432045543662' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5092807049363133934/posts/default/393799432045543662'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5092807049363133934/posts/default/393799432045543662'/><link rel='alternate' type='text/html' href='http://quartam.blogspot.com/2008/06/more-items-on-my-bookshelf.html' title='More Items on my Bookshelf'/><author><name>Quartam Software - Jan Schenkel</name><uri>http://www.blogger.com/profile/08312925714131118786</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5092807049363133934.post-9036728861961402711</id><published>2008-06-23T11:43:00.000-07:00</published><updated>2008-06-23T12:54:01.618-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='sql'/><category scheme='http://www.blogger.com/atom/ns#' term='databases'/><category scheme='http://www.blogger.com/atom/ns#' term='parser'/><category scheme='http://www.blogger.com/atom/ns#' term='lexical analysis'/><category scheme='http://www.blogger.com/atom/ns#' term='MacOS X'/><category scheme='http://www.blogger.com/atom/ns#' term='domain-specific languages'/><category scheme='http://www.blogger.com/atom/ns#' term='books'/><category scheme='http://www.blogger.com/atom/ns#' term='cocoa'/><category scheme='http://www.blogger.com/atom/ns#' term='antlr'/><title type='text'>New Items on my Bookshelf</title><content type='html'>One of the defining characteristics of Information Technology, is that it is constantly shifting, and that you need to spend a lot of time just to keep up with the topics that you focus on. Not to mention the pet projects that you hope you'll eventually get around to but have already bought some books on for that magical moment when have time to actually read them.&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;And maybe I &lt;span class="Apple-style-span" style="font-style: italic;"&gt;should&lt;/span&gt; wait to buy these books until I have that copious free time needed to properly digest their contents, but hey, what better motivation to move things along than a growing stack of books that you convince yourself have to be read before the end of the summer? A little pressure never hurt anyone, right?&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;On with the show - what did I recently add to my collection?&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;a href="http://www.pragmaticprogrammer.com/titles/tpantlr/index.html"&gt;The Definitive ANTLR Reference&lt;/a&gt; - if you've ever dreamt of building domain-specific languages, &lt;a href="http://www.antlr.org"&gt;ANTLR&lt;/a&gt; is the tool to get if you're not already knee-deep in LEX and YACC. Now why on earth would you need that? Well, if you're building business applications, this sort of embedded scripting languages can make a world of difference when it comes to customizing the workflow of your application.&lt;/div&gt;&lt;div&gt;Granted, if your focus is exclusively on &lt;a href="http://www.apple.com/macosx/"&gt;MacOS X&lt;/a&gt;, you're better off making your application OSA-scriptable, so that your users can interact with your application via &lt;a href="http://www.apple.com/applescript/"&gt;AppleScript&lt;/a&gt;. Or go one step further, and embed &lt;a href="http://developer.apple.com/macosx/automator.html"&gt;Automator&lt;/a&gt; actions. But if you're not that lucky, and you need cross-platform scripting, ANTLR and the visual grammar development environment ANTLRWorks will make your life a heck of a lot easier - producing code that is actually readable, rather than the undecipherable state machine mess that you get from YACC.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;And now that we're talking about MacOS X (ooh, there was a smooth transition to the next book) - I just got my copy of &lt;a href="http://www.bignerdranch.com/products.shtml"&gt;Cocoa Programming for MacOS X&lt;/a&gt; by Aaron Hillegass. The just-released third edition was updated for MacOS X Tiger and Leopard, including coverage of XCode 3, Objective-C 2, Core Data, the garbage collector and Core Animation.&lt;/div&gt;&lt;div&gt;While I also have a copy of the Wrox-book &lt;a href="http://www.wrox.com/WileyCDA/WroxTitle/productCd-0764573993.html"&gt;Beginning MacOS X Programming&lt;/a&gt;, this will be the one that gets me going with Cocoa (that's my story and I'm sticking to it!) as the last Mac-specific development I did was using &lt;a href="http://www.pascal-central.com/tplus.html"&gt;Think Pascal&lt;/a&gt; and the first 5 books of the &lt;span class="Apple-style-span" style="font-style: italic;"&gt;original&lt;/span&gt; &lt;a href="http://www.amazon.com/Inside-Macintosh-Cd-Rom-Apple-Computer/dp/0201406748"&gt;Inside Mac&lt;/a&gt; series, back in the day when Macs used Motorola 680x0 processors and we were happy to get &lt;a href="http://en.wikipedia.org/wiki/System_7"&gt;System 7&lt;/a&gt;.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;The last book I want to mention is &lt;a href="http://oreilly.com/catalog/9780596008949/"&gt;The Art of SQL&lt;/a&gt;. Now I may live and breathe databases but you can never learn enough tricks of the trade. In this book, the author aims to teach people who are no longer novices how to write &lt;span class="Apple-style-span" style="font-style: italic;"&gt;good&lt;/span&gt; SQL code from the start and most importantly, to have a view of SQL code that goes beyond individual SQL statements.&lt;/div&gt;&lt;div&gt;Remember the days when developers managed to fit entire accounting applications, including the data, onto a set of floppy disks or (gasp, we will &lt;span class="Apple-style-span" style="font-style: italic;"&gt;never&lt;/span&gt; fill that up) 10 megabyte hard disks, running in 128 kilobyte RAM or less? With the way database sizes are exploding nowadays, you need to plan ahead and employ a different strategy - so I'm definitely looking forward to getting more in-depth than ever.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;And just in case you're wondering: no, I don't &lt;span class="Apple-style-span" style="font-style: italic;"&gt;always&lt;/span&gt; cuddle up on the sofa with a mug of hot cocoa and this type of book. Whenever I get a chance, I'll read books by &lt;a href="http://www.crydee.com/"&gt;Raymond E. Feist&lt;/a&gt;, &lt;a href="http://www.tadwilliams.com"&gt;Tad Williams&lt;/a&gt;, &lt;a href="http://www.eddingschronicles.com/index.html"&gt;David Eddings&lt;/a&gt;, &lt;a href="http://en.wikipedia.org/wiki/Margaret_Weis"&gt;Margaret Weis&lt;/a&gt; and &lt;a href="http://en.wikipedia.org/wiki/Tracy_Hickman"&gt;Tracy Hickmann&lt;/a&gt;. Hmm, another stereotypical geek trait: fantasy and science fiction. Ah well, when the shoe fits :-)&lt;/div&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5092807049363133934-9036728861961402711?l=quartam.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://quartam.blogspot.com/feeds/9036728861961402711/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=5092807049363133934&amp;postID=9036728861961402711' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5092807049363133934/posts/default/9036728861961402711'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5092807049363133934/posts/default/9036728861961402711'/><link rel='alternate' type='text/html' href='http://quartam.blogspot.com/2008/06/new-items-on-my-bookshelf.html' title='New Items on my Bookshelf'/><author><name>Quartam Software - Jan Schenkel</name><uri>http://www.blogger.com/profile/08312925714131118786</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5092807049363133934.post-7785950927401651059</id><published>2008-05-25T12:23:00.001-07:00</published><updated>2008-05-25T22:35:01.393-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='human-computer interaction'/><category scheme='http://www.blogger.com/atom/ns#' term='design'/><category scheme='http://www.blogger.com/atom/ns#' term='user interface'/><title type='text'>UI Design Essentials</title><content type='html'>My design skills are limited to stick figures - and even then, people will react "Is that a MacBook the guy is holding?" - "No, that's a chihuahua peeking out of her purse..." Nevertheless, as a commercial software developer, I need to somehow be both a programmer and a designer, at the same time.&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;And when you cater to the Mac market, you're talking about the platform where applications have to bleed coolness - often with ground-breaking user interfaces, like &lt;a href="http://www.delicious-monster.com/"&gt;Delicious Library&lt;/a&gt; or &lt;a href="http://www.apple.com/"&gt;Apple&lt;/a&gt;'s iApps. Following the platform's interface guidelines is only half the work: your job is to provide an elegant user interface that conveys information in an efficient manner, interacting in a compelling way at the same level as your target user.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Granted, I make database front-ends for a living. The boring kind of applications: accounting, order processing, and all that happy fun stuff that involves storing data in and loading it back from a database, and providing reports and statistics so that the end users can do their job. In my time, I have seen plenty of applications that were built from a developers' point of view. Which means it's logical, in a way, though not necessarily matching the user's line of thinking.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;If you're lucky, the team that built the thing in the first place, took the time to read up on human-computer interaction, and tried to learn from the other applications out there. And if you're really lucky, they had someone on their team smart enough to say: "It's not enough to have menus and windows and buttons, but we should make sure that all elements in our application work consistently, as this will cut down on user training and support calls."&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;But I didn't mean to step onto the soapbox here - I just wanted to point you over to &lt;a href="http://www.theocacao.com/document.page/570"&gt;this entry&lt;/a&gt; on the &lt;a href="http://www.theocacao.com/"&gt;Theocacao&lt;/a&gt; website - a blog for Mac developers, written by &lt;span class="Apple-style-span" style="font-style: italic;"&gt;Scott Stevenson&lt;/span&gt;, who also maintains the &lt;a href="http://www.cocoadevcentral.com/"&gt;Cocoa Dev Central&lt;/a&gt; learning center. You can fetch the slides of his UI Design Essentials talk as well as a movie recording. It may take a while to download the 532MB QuickTime HD file, but it is well worth it.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;He talks about the history of developers and designers in software development, and although he can't make you a designer in as short a timeframe as an hour, he does make excellent points and offers practical advice. Specific topics touched include: the basics of iterative design and usability testing, the use of whitespace and partitioning, correct labels and prompts, as well as fonts and language design, covering inspector palettes and icons in the process.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;He also ran through a real application that someone was brave enough to submit, pointing out design flaws that turn this functionally excellent application into something users &lt;span class="Apple-style-span" style="font-style: italic;"&gt;can&lt;/span&gt; get to work by themselves, but could be so much better and easier to use by making a number of tweaks. In the wrap-up, he mentions a number of 'model citizens' that we should look at. Some of the best bits came up during the Q&amp;amp;A session at the end, which is only available in the video recording, unfortunately.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;As you wait for the download to make its way to your computer, you may also want to chase down these books:&lt;/div&gt;&lt;div&gt;&lt;ul&gt;&lt;li&gt;&lt;a href="http://www.designinginterfaces.com/"&gt;Designing Interfaces&lt;/a&gt; by Jenifer Tidwell (O'Reilly)&lt;/li&gt;&lt;li&gt;&lt;a href="http://www.cooper.com/insights/books/"&gt;About Face 3&lt;/a&gt; by Alan Cooper, Robert Reimann and David Cronin (Wiley)&lt;/li&gt;&lt;/ul&gt;While the latter is a very theoretical book which goes through great lengths to match up the implementation and mental models of developers and users respectively, the first book has great tips on how to improve the user interface of your desktop as well as web applications, filled with screenshots and explanations in a very readable format.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Of course, there are other resources out there, but I'm not going to add much more to this post - instead, I would like to encourage you to go out and look at applications that you use every day, and write down what you find annoying and what you like about the way it interacts with you as a user. And the n try to repeat what thery do right, and avoid what they do wrong.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;While Quartam Reports works the way it does, as it was modeled after the Report Builder that shipped with Foxpro, it's not going to stay that way forever. I've already been prototyping a complete overhaul for a while now, and hope to make it a reality for version 2.0 - balancing between the "let's just get it over with" mindset of a developer who has to develop 189 of those pesky things, and the mindset of someone who is perhaps less-seasoned in the raw developing process, but has a good idea of the end-product and how it should turn mere facts into empowering information.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;In the meantime, there's plenty to learn and consider and tweak, as usual... Not to mention plenty of code to toss and rewrite... It just never ends, does it?&lt;/div&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5092807049363133934-7785950927401651059?l=quartam.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://quartam.blogspot.com/feeds/7785950927401651059/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=5092807049363133934&amp;postID=7785950927401651059' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5092807049363133934/posts/default/7785950927401651059'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5092807049363133934/posts/default/7785950927401651059'/><link rel='alternate' type='text/html' href='http://quartam.blogspot.com/2008/05/ui-design-essentials.html' title='UI Design Essentials'/><author><name>Quartam Software - Jan Schenkel</name><uri>http://www.blogger.com/profile/08312925714131118786</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5092807049363133934.post-4472542906238535881</id><published>2008-05-14T11:26:00.000-07:00</published><updated>2008-05-14T13:14:22.768-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='PDF'/><category scheme='http://www.blogger.com/atom/ns#' term='RIA'/><category scheme='http://www.blogger.com/atom/ns#' term='revolution'/><category scheme='http://www.blogger.com/atom/ns#' term='web'/><category scheme='http://www.blogger.com/atom/ns#' term='browser'/><title type='text'>Revolution Live '08 - redefining the web</title><content type='html'>Now that I'm back home, caught up on sleep a bit and am starting the happy-fun return of my internal body clock to the Central European Time Zone, it seemed like a good opportunity to blog about the &lt;a href="http://www.runrev.com/newsletter/may/issue48/"&gt;announcements&lt;/a&gt; at the Revolution Live Conference.&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;The great news for Revolution customers around the globe, is that their web offerings are shaping up really well: a PHP-style server module for creating web applications, plus a web browser plug-in - talk about a slam-dunk by the Runrev team! &lt;/div&gt;&lt;div&gt;This is the perfect answer to Adobe's trying to bring Flash to the desktop: anyone can now build Internet-enabled applications without having to struggle with ActionScript and tools that were meant for &lt;span class="Apple-style-span" style="font-style: italic; "&gt;designers&lt;/span&gt;. Not that designers are illogical people - if they were clueless about putting two and two together, they wouldn't be able to pull off that AJAX stuff. &lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Boy, am I glad I won't have to continue struggling with that HTML+CSS+JavaScript batter mix either, if I want to build a Rich Internet Application... Let's check how Revolution stacks up against all these technologies that are trying to win the next round of browser wars:&lt;/div&gt;&lt;div&gt;&lt;ul&gt;&lt;li&gt;&lt;span class="Apple-style-span" style="font-weight: bold;"&gt;&lt;a href="http://en.wikipedia.org/wiki/AJAX"&gt;AJAX&lt;/a&gt;&lt;/span&gt; is an interesting use of existing technologies, but in the end it's just another way to stretch what a browser can do. The fact of the matter is: the browser needs updating for it to ever become a true universal application platform.&lt;br /&gt;&lt;/li&gt;&lt;li&gt;&lt;span class="Apple-style-span" style="font-weight: bold;"&gt;&lt;a href="http://www.adobe.com/products/flash/"&gt;Flash&lt;/a&gt;&lt;/span&gt; started life as a way to animate vector graphics, and whatever people may tell you, that's still what it is - they don't even have real buttons, it's all simulated. &lt;span class="Apple-style-span" style="font-style: italic;"&gt;Thank you, please don't come again&lt;/span&gt;.&lt;/li&gt;&lt;li&gt;&lt;span class="Apple-style-span" style="font-weight: bold;"&gt;&lt;a href="http://www.javafx.com/"&gt;JavaFX&lt;/a&gt;&lt;/span&gt; is an interesting idea, but suffers from one major flaw: it is &lt;span class="Apple-style-span" style="font-style: italic;"&gt;not&lt;/span&gt; Java. Which means that Java developers have to learn a whole new thing just to be with the times. If they really wanted it to become popular, they would have just provided a great applet builder, not this monstrosity.&lt;/li&gt;&lt;li&gt;&lt;span class="Apple-style-span" style="font-weight: bold;"&gt;&lt;a href="http://silverlight.net/"&gt;Silverlight&lt;/a&gt;&lt;/span&gt; is Microsoft's attempt to beat Adobe in the browser plug-in wars. So far, it doesn't really seem to be getting very far. Maybe around version 3.0 we can consider it a major force, but I'm sure that you'll have to update your Silverlight apps every single time the Redmond team decides to replace essential parts.&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;&lt;div&gt;Some people may think that Revolution is focusing entirely on the multimedia market with this strategy, but they couldn't be further from the truth: this is the single largest opportunity in the history of Revolution to make it big in the world of Enterprise applications!&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Of course, there will be plenty of multimedia Flash-like stuff built using this browser plug-in, and it should attract a whole new crowd of Revolution developers. But looking at the biz app developers now, Revolution is by far the most secure technology of the bunch: short of obfuscating your Javascript, your AJAX apps are wide-open for anyone to take apart and try to take advantage of.&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;The Revolution community can now deploy applications for those customers who want to see a browser user interface, and we won't even have to learn a new language. Likewise, the server module will serve as an application server gateway like we haven't had before.&lt;/div&gt;&lt;div&gt;And I can assure you that existing and future &lt;a href="http://www.quartam.com"&gt;Quartam&lt;/a&gt; tools will help you get to the bottom of things: use the server module and Quartam PDF Library to dynamically generate documents for your web applications; and once Quartam Reports hits version 2.0, you'll be able to leverage this report generation tool on the browser platform as well.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Good times...&lt;/div&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5092807049363133934-4472542906238535881?l=quartam.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://quartam.blogspot.com/feeds/4472542906238535881/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=5092807049363133934&amp;postID=4472542906238535881' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5092807049363133934/posts/default/4472542906238535881'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5092807049363133934/posts/default/4472542906238535881'/><link rel='alternate' type='text/html' href='http://quartam.blogspot.com/2008/05/revolution-live-08-redefining-web.html' title='Revolution Live &apos;08 - redefining the web'/><author><name>Quartam Software - Jan Schenkel</name><uri>http://www.blogger.com/profile/08312925714131118786</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5092807049363133934.post-8750436212181879478</id><published>2008-05-04T05:03:00.000-07:00</published><updated>2008-05-04T05:54:09.427-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='website'/><category scheme='http://www.blogger.com/atom/ns#' term='design'/><title type='text'>Website make-over</title><content type='html'>After about 4 years of a design that was clean but not very modern, I decided to sit down and give my site a complete make-over. I had a pretty good idea of what I wanted it to look like, and in my &lt;span class="Apple-style-span" style="font-style: italic;"&gt;hubris&lt;/span&gt; estimated I could do it in a day or two.&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;The original website design was done using &lt;a href="http://en.wikipedia.org/wiki/Claris_Home_Page"&gt;Claris Home Page&lt;/a&gt;, a dinosaur even in the day that I picked it in 2004 as my tool for table-based layouting. But for the rather simple site I had in mind, it was the logical choice, given that I had last designed a website back in 1994, in the days when &lt;a href="http://en.wikipedia.org/wiki/HotMetal_PRO"&gt;HoTMetaL Pro&lt;/a&gt; was considered a major step forwards.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;My mindset is that of a developer, not a designer. Which means that I'm pretty good at straightforward HTML, and I can work with XML and the gory details of XSL transformations. But for some reason, my brain comes to a grinding halt when it is confronted with CSS. So I generally let the designer "do his thang" and integrate it with my XSLT file or AJAX routine. Which I write in a programmer's text editor, not some fancy tool like Dreamweaver.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;However, for my own website I was going to get it right - and of course, it resulted in a lot of expletives and crying and pleading with every deity that could remotely help - not that I really expected the ancient Roman goddess of wisdom, &lt;a href="http://en.wikipedia.org/wiki/Minerva"&gt;Minerva&lt;/a&gt;, to be any good at CSS, but hey, it would have been cool if she had swooped by and fixed the problems - and I was getting desparate at that point.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Until I decided to put away my programmer's hat, and dug out my copy of &lt;a href="http://www.softpress.com/products/freewayfeatures.php"&gt;Freeway Pro&lt;/a&gt;, which I had recently upgraded to version 5 anyway. Created by Softpress, it is aimed at visual designers who like working with Quark XPress, drawing graphic elements and filling them with content.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;A long long time ago, in a galaxy far away, I was active in various student associations, where I invariably ended up layouting the association's magazine using PageMaker or RagTime. So after a lot of experimenting, unlearning and documentation-consulting, in these past four (*) days, I managed to completely revamp the &lt;a href="http://www.quartam.com"&gt;quartam.com&lt;/a&gt; website.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;The result is CSS-based, and my website no longer looks like something designed in the 20th century. There's one more section to redo as far as layout goes, and I still have to wrap up those elusive tutorials that I started back in 2006 - I'll let you know when those eggs hatch. Right now, I have to get back to preparing for &lt;a href="http://www.runrevlive.com"&gt;runrevlive.08&lt;/a&gt; - where I will be presenting the session on Advanced Databases.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;(*) Four days, whereas I had planned two days. Great - just great. If my boss reads this, I'm sure he'll have a field day - according to him, you need to multiply a developer's time estimate by 2 and then you may get a more realistic timeframe. Don't you just hate it when the manager is right? Then again, he worked his way up from the coding trenches - which means he has a much better idea about software development than, say, a former bank director pretending to be a CEO in IT...&lt;/div&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5092807049363133934-8750436212181879478?l=quartam.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://quartam.blogspot.com/feeds/8750436212181879478/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=5092807049363133934&amp;postID=8750436212181879478' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5092807049363133934/posts/default/8750436212181879478'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5092807049363133934/posts/default/8750436212181879478'/><link rel='alternate' type='text/html' href='http://quartam.blogspot.com/2008/05/website-make-over.html' title='Website make-over'/><author><name>Quartam Software - Jan Schenkel</name><uri>http://www.blogger.com/profile/08312925714131118786</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5092807049363133934.post-2045003909565810257</id><published>2008-04-04T12:04:00.000-07:00</published><updated>2008-04-05T08:46:51.275-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Introduction'/><title type='text'>Thinking of an opening line</title><content type='html'>Starting a blog is like trying to convince a girl that there's more to you than a pair of glasses, some ruffled brown hair and a strange grin. What exactly are you supposed to say?&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Let's start with explaining what I hope to do here. There are lots of corporate blogs that are either filled by the managers, or their secretary copywriters. The good new for you, is that I am neither a manager, nor a secretary.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;During the day I am a software engineer at MIPS Belgium - a leading provider of laboratory information management systems. At the job, I use a mixture of Progress OpenEdge, Java and -to a lesser extent- C/C++.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;After hours, I use Revolution - one of the best-kept secrets in software development, it allows anyone with the gift of reason to quickly create an application by dragging buttons and fields onto a card and adding scripts to them. In an easy to understand English-like syntax, not some mystifying combination of dot-notation and enforced object-orientation.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;My plan for this blog is to talk about the technology that I work with - the tools that I build and sell to fellow developers, little bits of code that I write to test a feature or just to satisfy my curiosity, but also the books that I read and try to glean information out of that will help me in my daily occupations.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Don't expect multiple posts per day - given my hectic schedule, it is bound to be more or less a once-a-week affair. But then again, I may enjoy writing entries that I will do it more often. You'll just have to wait and see...&lt;/div&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5092807049363133934-2045003909565810257?l=quartam.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://quartam.blogspot.com/feeds/2045003909565810257/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=5092807049363133934&amp;postID=2045003909565810257' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5092807049363133934/posts/default/2045003909565810257'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5092807049363133934/posts/default/2045003909565810257'/><link rel='alternate' type='text/html' href='http://quartam.blogspot.com/2008/04/thinking-of-opening-line.html' title='Thinking of an opening line'/><author><name>Quartam Software - Jan Schenkel</name><uri>http://www.blogger.com/profile/08312925714131118786</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry></feed>
