Archive for January, 2009

Naive Mouse Gestures Implementation in ActionScript (Twitterified)

Domestic mouse attackIn the Twitterified Client, users can quickly perform some operations using mouse gestures.

When I implemented this, I googled for ActionScript mouse gesture classes but only found one and it was a commercial package.
I am sure that there are many ways to implement gestures, using rule engines, neural networks, etc.

But, curious and optimistic as usual, I decided to empirically build my own. Could it be done?
After some fumbling and several dead-ends, I came up with this solution which works as it should and that can easily be adapted to your own needs.
I believe that all the relevant source code is here; let me know if I’m wrong and do not hesitate to share your improvements!
 
 
Let’s begin with a few instance variables…

?View Code ACTIONSCRIPT
private var drawingCanvas:Canvas = new Canvas();
private var ctrlKeyIsDown:Boolean = false;
 
private var lastPoint:Point;
private var lastDrawPoint:Point;
private var gesturePath:Array;

drawingCanvas is going to be our, er, drawing canvas. We will use it to give the user visual feedback when doing a mouse gesture.
Note that lastDrawPoint will contain the coordinates of the last rendered point, whereas lastPoint will be used to track direction changes in the mouse’s course.
Similarly, gesturePath will only be updated when a significant update is detected.
 
 

?View Code ACTIONSCRIPT
public function main_init():void
{
...
	systemManager.addChild(drawingCanvas);
	systemManager.addEventListener(KeyboardEvent.KEY_DOWN, main_onKeyUpDown);
	systemManager.addEventListener(KeyboardEvent.KEY_UP, main_onKeyUpDown);
	systemManager.addEventListener(MouseEvent.MOUSE_MOVE, main_onMouseMove);
...
}

We are adding drawingCanvas to the top of our user interface, but not giving it a size, yet. We will wait for the user to actually initiate a mouse gesture to, then, detect our current real estate.
I am asking Flex to keep me updated of any keyboard change as I wish to use the [Ctrl] key as a hint that we wish to perform a mouse gesture. Obviously, you can go down a very different path.
 
 

?View Code ACTIONSCRIPT
private function main_onKeyUpDown(e:KeyboardEvent):void
{
	ctrlKeyIsDown = (e.ctrlKey);
	if(ctrlKeyIsDown)
	{
		drawingCanvas.x      = systemManager.stage.x;
		drawingCanvas.y      = systemManager.stage.y;
		drawingCanvas.width  = systemManager.stage.width;
		drawingCanvas.height = systemManager.stage.height;		
		drawingCanvas.graphics.lineStyle(2, 0x6EBAE5, 1);
		lastPoint = new Point(systemManager.stage.mouseX, systemManager.stage.mouseY);
		lastDrawPoint = lastPoint;
		gesturePath = new Array();
		gesturePath.push(new PathBit(lastPoint, lastPoint));
	}
	else
	{
		drawingCanvas.graphics.clear();
		// Gesture
		main_encodeGesture();
	}
}

Two possibilities: [Ctrl] is depressed and we initialize visual feedback and store the original mouse location, or it is up and we clear the feedback information and encode the gesture.
Note that this could be improved to not be disturbed by, say, the user depressing another key!
 
 

?View Code ACTIONSCRIPT
private function main_onMouseMove(e:MouseEvent):void
{
	if(!ctrlKeyIsDown)
		return;
	var x:int = systemManager.stage.mouseX;
	var y:int = systemManager.stage.mouseY;
	drawingCanvas.graphics.moveTo(x, y);
	drawingCanvas.graphics.lineTo(lastDrawPoint.x, lastDrawPoint.y);
	var newPoint:Point = new Point(x, y);
	var pathBit:PathBit = new PathBit(lastPoint, newPoint);
	// > 5% of stage?
	if(Math.abs(pathBit.horizontal) * 20 > systemManager.stage.width || Math.abs(pathBit.vertical) * 20 > systemManager.stage.height)
	{
		gesturePath.push(pathBit);
		lastPoint = newPoint;
	}
	lastDrawPoint = newPoint;
}

If [Ctrl] is depressed, store the mouse location. If significant motion has taken place, draw a new line and store the relevant waypoint.
 
 
What does a PathBit object look like? This is fairly simple: based on the latest mouse move angle, we determine a very rough direction.

?View Code ACTIONSCRIPT
package com.voilaweb.tfd {
 
	import flash.geom.Point;
 
	public class PathBit
	{
		static public const UP:int    = 0x01;
		static public const DOWN:int  = 0x02;
		static public const RIGHT:int = 0x04;
		static public const LEFT:int  = 0x08;
		static public const U:String  = "U";
		static public const D:String  = "D";
		static public const L:String  = "L";
		static public const R:String  = "R";
		static public const UL:String = "7";
		static public const UR:String = "9";
		static public const DL:String = "1";
		static public const DR:String = "3";
		public var point:Point = new Point();
		public var horizontal:int;
		public var vertical:int;
		public var direction:int;
 
		function PathBit(point1:Point, point2:Point)
		{
			horizontal = point2.x - point1.x;
			vertical   = point2.y - point1.y;			
			point.x = point2.x;
			point.y = point2.y;	
			direction = 0;
			if(0 == horizontal && 0 == vertical) return;
			// Compute angle, in degrees: horizontal == new x, vertical == new y
			var angle:Number = Math.atan2(horizontal, -vertical) * 180 / Math.PI;
			if(angle < 0) angle = 360 + angle;
			if(angle >= 337.5 || angle < 22.5)
			{
				direction = UP;
			} 
			else if(angle >= 22.5 && angle < 67.5)
			{
				direction = UP | RIGHT;
			}
			else if(angle >= 66.5 && angle < 112.5)
			{
				direction = RIGHT;
			}
			else if(angle >= 112.5 && angle < 157.5)
			{
				direction = RIGHT | DOWN;
			}
			else if(angle >= 157.5 && angle < 202.5)
			{
				direction = DOWN;
			}
			else if(angle >= 202.5 && angle < 247.5)
			{
				direction = DOWN | LEFT;
			}
			else if(angle >= 247.5 && angle < 292.5)
			{
				direction = LEFT;
			}
			else if(angle >= 292.5 && angle < 337.5)
			{
				direction = LEFT | UP;
			}
		} 		
	}
}

You may have noticed that our constant values are also very basic; I was not lying when talking about a naive implementation.
 
 

?View Code ACTIONSCRIPT
private function main_encodeGesture():void
{
	var container:Container = null;
 
	switch(showingTab)
	{
		case FRIENDS_TL:
			container = friends_timeline;
			break;
		case USER_TL:
			container = user_timeline;
			break;
		case PUBLIC_TL:
			container = public_timeline;
			break;
	}	
 
	if(!container)
		return;
 
	// Analyze path	
	var children:Array = container.getChildren();
	var statusRow:UIComponent;
	var multiComponent:Boolean = false;
	var curStatus:StatusRow;
	var lastStatus:StatusRow = null;
	var pathBit:PathBit;	
	var overallPathString:String = '';
 
	for each(pathBit in gesturePath)
	{
		curStatus = null;
		for each(statusRow in children)
		{
			if(statusRow is StatusRow)
			{
				if(StatusRow(statusRow).isBounding(pathBit.point))
				{
					curStatus = statusRow as StatusRow;
					break;
				}
			}
		}
		if(lastStatus && curStatus != lastStatus)
			multiComponent = true;		
		lastStatus = curStatus;
		if(pathBit.direction & PathBit.DOWN)
		{
			if(pathBit.direction & PathBit.LEFT)
				overallPathString += PathBit.DL;
			else if(pathBit.direction & PathBit.RIGHT)
				overallPathString += PathBit.DR;
			else
				overallPathString += PathBit.D;
		}
		else if(pathBit.direction & PathBit.UP)
		{
			if(pathBit.direction & PathBit.LEFT)
				overallPathString += PathBit.UL;
			else if(pathBit.direction & PathBit.RIGHT)
				overallPathString += PathBit.UR;
			else
				overallPathString += PathBit.U;
		}
		else if(pathBit.direction & PathBit.LEFT)
			overallPathString += PathBit.L;
		else if(pathBit.direction & PathBit.RIGHT)
			overallPathString += PathBit.R;
	}			
	overallPathString    = main_cleanupPath(overallPathString);
	main_parseGesture(multiComponent, overallPathString, lastStatus, container);
}

The switch statement is used to figure out which container is currently displayed. Because this container contains a list of Tweets, I can then check whether the mouse gesture entirely took place within the confines of a single Tweet, or multiple Tweets. This allows me, for instance, to mark a Tweet as ‘read’ if the mouse did not wander outside that Tweet’s display area.
I then build a path string, called ‘overallPathString‘, based on the content of gesturePath.
 
 
At this point, you may be wondering about the implementation of isBounding(). Here it is:

?View Code ACTIONSCRIPT
	public class StatusRow extends WindowShade
	{
	...
		public function isBounding(point:Point):Boolean
		{
			var pt:Point = this.globalToLocal(point);	
			return (pt.x >=0 && pt.y >= 0 && pt.x <= this.width && pt.y <= this.height);
		}
	...
	}

A simple helper.
 
 

?View Code ACTIONSCRIPT
private function main_cleanupPath(path:String):String
{
	var i:int, l:int = path.length;
	var c:String, prevC:String = null, ret:String = '';
	for (i=0; i<l; i++)
	{
		c = path.charAt(i);
		if(prevC)
		{
			if(c != prevC)
				ret += c;	
		}
		else
		{
			ret = c;
		}
		prevC = c;
	}
	return ret;	
}

As you can see, all this method does is collapse redundant information. Actually, without it, we would never be able to identify any gesture.
 
 

?View Code ACTIONSCRIPT
private function main_parseGesture(multi:Boolean, op:String, status:StatusRow,container:Container):void
{
	if(!multi)
	{
		// Single-status gesture!
		if(PathBit.L == op) // L
			main_toggleReadFlag(status, true);
		else if(PathBit.R == op) // R
			main_toggleReadFlag(status, false);
	}
	else
	{
		if("13" == op) // DL, DR
			main_toggleReadFlagForAll(container, true);
		else if("31" == op) // DR, DL
			main_toggleReadFlagForAll(container, false);
	}
}

I am sure that this could be rewritten to be more cleanly; potentially with no hard-coding at all. But it does the trick:
- If the gesture happened within the confines of a single Tweet, and is a single “right-to-left” move, then we mark this Tweet as ‘read’.
- If the gesture happened across multiple Tweets and goes “top-right-to-bottom-left-to-bottom-right” then mark all Tweets as read.

Well, there you have it. At least it’s easy to maintain :)

If you enjoyed this post, make sure you subscribe to my RSS feed!


A quick-fix for the Tweetback spam issue

Yahoo Censorship Still Sucks, Part FourI do not get it. I am a big fan of Smashing Magazine and I think that it was very nice of @jdevalk to write a plug-in that gives us full control over “Tweetbacks”. See this blog entry.
Where Smashing Magazine and the author lose me, though, is that I have not seen any reaction, either on the magazine’s web site, or the author’s blog, regarding the issue that many blogs who installed the plug-in seem to now be suffering: they are being inundated with thousands of spammy Tweetbacks, most of them containing text like “This is a test”, presaging a potentially much more virulent attack when the spammers get their tool working.
In fact, it may not be spam, but simply some tests gone wrong, but whatever the reason it caused grief to many blog owners — including me.

After de-activating the plug-in, here is the SQL command you can use to get rid of these Tweetbacks:

delete from wp_comments where comment_author_url like 'http://twitter.com/%/statuses/%';

If you do not have access to your database, contact me, I may end up writing a short plug-in that will wrap this query.

Again, I really like Smashing Magazine and that is why I am very surprised by their silence on this issue.

If you enjoyed this post, make sure you subscribe to my RSS feed!


Chris’ Weekly High-Protein Tweets

If you are drowning in Twitter updates or simply missed a few of my Tweets, here is a list of what I consider worth reading this week.
By that, I mean that less interesting, personal and topical tweets are not in this list. This list will be published every week, save the occasional attack of laziness.

Om Malik talks about Cisco’s “big fat margins” – but Cisco is already taking margin hits in the server market! http://bit.ly/ATHs
A brilliant dose of reality regarding PHP’s “victory”: http://bit.ly/17rsc
@CleverClogs ‘Portmanteau’ is an old word, from the french ‘coat rack’, alluding to mixing multiple words for a new meaning.
Intense irony: apptheater.com iPhone demo videos not viewable on iPhone http://bit.ly/wzhI
Finally a credible skin for Java Swing (check Look and Feel tab) http://bit.ly/f1zo
Exactly my experience with Playstation Home: http://bit.ly/cfRL
Web development project estimator (via Ajaxian) http://bit.ly/J1Ok
These MySQL devs denied entry in Australia…the reason is still pure conjecture. Of course this doesn’t stop “reporting” http://bit.ly/PXzZ
Very simple Javascript animation library. And small, too (<4KB) http://bit.ly/pKh0
Improved windows management, typography, etc. Windows 7’s somewhat secret new features: http://bit.ly/MfKX
Nice MyIsam vs. InnoDB comparison. What’s left out: MyIsam is accessible to non-database admins. http://bit.ly/dKu7
At last, a very clear explanation of what UX (User Experience) is not: http://bit.ly/vcs6
Ubuntu 8.10: Disabled KDE fancy desktop, re-enabled Compiz: speed gain = orders of magnitude.
Hilarious — SCO plans to sell off its actual products to finance their lawsuits. Yes, you know which lawsuits: http://bit.ly/1ujKgE

If you enjoyed this post, make sure you subscribe to my RSS feed!


ExtPHP now on Github

Day CQ5 - Workflow EditorAbout 10 months ago, I released this tool, allowing developers to handle ExtJS like “managed” code in PHP: ExtPHP.

Shortly thereafter, Jack Slocum changed ExtJS’ licensing model and it all became very muddy but my main question was: “As a commercial user, what would the status of the extensions be?”
It seems that it’s OK to create an extension and not GPL it as long as it doesn’t contain any ExtJS original code. The claim is that most extensions now fall under that category. Of course, there is still the risk of an extension still containing ExtJS code — hint: that’s very likely — and the company who paid for ExtJS commercial licensing is now burdened with…what? De-facto GPL code? Or does the code that was reused considered “commercially licensed” as well? Even though it is part of an open-source extension?

Ten months later, I am not getting a sense that things were clarified well enough. Look at this topic on ExtJS’ forums. It looks like, after September, everybody gave up.

So, what now?

Well, if anyone could point me to a comprehensive answer regarding this issue, that will sure help me decide whether to revisit my decision to give up on ExtJS from a commercial standpoint.

In the meantime, ExtPHP is now available at Github.

If you enjoyed this post, make sure you subscribe to my RSS feed!


Chris’ Weekly High-Protein Tweets

If you are drowning in Twitter updates or simply missed a few of my Tweets, here is a list of what I consider worth reading this week.
By that, I mean that less interesting, personal and topical tweets are not in this list. This list will be published every week, save the occasional attack of laziness.

Very nice video that shows how Kiva.org (micro-lending) works — watch it and join! http://bit.ly/TJ0X
Max Barry: No more money for the MPAA. Consumers want to watch the DVDs they buy, no matter how silly this may sound: http://bit.ly/uPN
Now that’s Google being “not evil(tm)” — your blog content is yours: http://bit.ly/qacD
Dr.Dobb’s ghost says: “Volatile in Java is not the same as Volatile in C” — well you got me there: http://bit.ly/CIB6
Windows 7 products keys giveaway: http://bit.ly/33qaDe
Back to basics — Wiki markdown == WikiCreole: http://bit.ly/FZOa
Ironic proof of my previous tweet (http://bit.ly/4iyh) about #tweetbacks it does not show at http://tinyurl.com/9e4ank
#tweetbacks are a good idea but are they accurate? eg. bit.ly and snipurl do not produce same links every time http://bit.ly/GuMX
Apple(s)/Oranges: ReactOS is nothing like Psystar. It does not use Microsoft code – terrible analogy: http://bit.ly/88A1
Pirate Facebook: awesome! (Thanks Alex) http://bit.ly/X9Mr
Nice jQuery page slide component: http://bit.ly/12vXh
Another “Who needs Flash?” comment. SVG is nice but you will need Flash to scale your demo to 1000’s of nodes: http://bit.ly/O5mY
Really smart address picker – impressive because it reveals how unique your address is: http://bit.ly/JTGC
Interesting: what’s driving Twitter usage? http://www.hpl.hp.com/resea…

If you enjoyed this post, make sure you subscribe to my RSS feed!


What would you like to read?

Brighton Ruby Users GroupWith the new year, this is your chance to shape the future of this space:

What do you think this blog’s focus should be?

 
Your comments will be all be read and everybody will get a reply, so feel free to share!

If you enjoyed this post, make sure you subscribe to my RSS feed!


Six Resources To Study And Master Git Source Control

Logo by Henrik Nyh (http://henrik.nyh.se/2007/06/alternative-git-logo-and-favicon)It’s no secret that Git, as a source code control tool, has become quite popular in 2008, in part boosted by the success of the socially oriented github.com Here is a list of six resources that will help you get more comfy with Git, whatever your current knowledge of the tool. Let me know if you think that, after reading these six documents, you can still point to a few stones left unturned.

3 Reasons To Switch To Git From Subversion

A to-the-point document, created by Mark McBride, that covers several scenarios and for each scenario, shows how Git could make your life easier if you are not using it yet.

3reasons

My Git Cheatsheet

A concise guide for the absolute beginner who wants to hit the ground running. Go through the exercises and you will end up with a pretty good picture of what Git can do.

mygitcheatsheet

Everyday GIT With 20 Commands Or So

A quick cheat sheet: start with the section corresponding to your role (individual developer, contributor, integrator…) You will not learn a lot about Git’s philosophy, but you will certainly get the job done.

everydaygitwith20

Git Magic

Ben Lynn wrote a whole book covering not just cloning and branching but also some precious recipes: What to do when something goes wrong? What if my commit is too big? Remote access magic, history digging…Even Git’s shortcomings and their workarounds are covered.

gitmagic

Git from the bottom up

John Wiegley makes a convincing case that approaching Git from a 10,000 ft level is not the best way to understand the tool’s philosophy. Rather, building your knowledge of the tool based precisely on the very concepts that it uses make it much easier to get an overall grasp of Git’s world.

gitfrombottomup

Using Git to Maintain Your Website

Using source code control has always been an effective tool to maintain a web site, especially avoiding these “Uh-oh I clobbered something I shouldn’t have” moments. But Git is particularly well suited to this task, due to its self-sufficient nature. Daniel Miessler shows us how to do just that using Git hooks.

gittomaintain

If you enjoyed this post, make sure you subscribe to my RSS feed!


A very geeky Holidays break

Kill BillWas your break as geeky as mine? Come on, admit it: you’ve done at least one incredibly unsexy thing in the last couple weeks. I know I have. Well, in fact, I had to take a four weeks-long break and it shows in the number of silly things I’ve played with.

In no particular order:

If you enjoyed this post, make sure you subscribe to my RSS feed!


Flex: A hack to give instant focus to a form field

Day 79 - f o c u sI was working on a “quick add” tool for Toodledo — which I am liking more and more — and I assumed that my biggest challenge would be wiring a hotkey to my application — a can of worms in its own right. My mini-application’s main requirement is to be fast and not get in the user’s way. Part of this requirement is that when the “Add” window is displayed, the “Task description” field should be automatically selected, an inviting cursor happily blinking. Easier said than done!

Flex will let me display the native window and select the description field using setFocus() but no cursor for me. I googled the issue and found that when a Flex application runs in a browser, the solution is to give the application itself focus using Javascript.
But I’m not running in a browser!

And now, my silly workaround: since displaying an other panel container — such as an alert message — and dismissing it will return proper focus to my original field, I will simply “display” an invisible container and immediately get rid of it.
There is no guarantee that, on the odd instance, a race condition will not occur.

?View Code ACTIONSCRIPT
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
package com.voilaweb.addtdtask
{
    import flash.events.TimerEvent;
 
    public class Main
    {
        import mx.core.Application;
        import mx.containers.TitleWindow;
        import flash.display.DisplayObject;
        import flash.utils.Timer;
        import flash.events.TimerEvent;
        import mx.managers.PopUpManager;
 
        private var _tw:TitleWindow;
 
        public function Main()
        {
            Application.application.task_description.setFocus();
            Application.application.task_description.drawFocus(true);
            _tw = new TitleWindow();
            _tw.width = _tw.height = 0;
            var t:Timer = new Timer(100, 1);
            t.addEventListener(TimerEvent.TIMER, showCursor);
            t.start();
            mx.managers.PopUpManager.addPopUp(_tw, Application.application as DisplayObject, true);
        }
 
        public function showCursor(event:TimerEvent):void
        {
            mx.managers.PopUpManager.removePopUp(_tw);
        }
    }
}

If you enjoyed this post, make sure you subscribe to my RSS feed!