languages

SuperGenPass patched for Google Chrome

I am a huge fan of SuperGenPass. There are so many obvious reasons why it’s a great concept that I won’t bore you with a rehash of all of them.
Unfortunately, in Google Chrome, SuperGenPass chokes on some pages. I do not blame Chrome for that: it’s for security reasons.

I’ve patched the basic version of SuperGenPass so that it can now work on those pages. I am not sure that it fixes everything for everybody but I hope it makes your life easier, like it does mine.

Just go to this page and get the patched bookmark.

If you are using a customized bookmark, I am afraid that you will have to patch it yourself. Here is what the patch looks like:

Look for

?View Code JAVASCRIPT
var%20FrameTest=window.frames[i].src;

Replace with

?View Code JAVASCRIPT
var%20FrameTest=window.frames[i].src;var%20FrameTest=window.frames[i].src;FrameTest=window.frames[i].document.forms;

Done!

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


Magic: Ruby 1.8.5 + 1.9.1/Rails/Sinatra and Lighttpd

Ladies and gentlemen, gather ’round! The Great Panini is going to perform an incredible illusion before your very eyes! You will tell your grandchildren of this day and they will not believe you! Take pictures!

The Great Panini will start with a pre-steam machines era CentOS 4 server. He will install two versions of Ruby and they will coexist peacefully! He will then use the blazing fast Lighttpd server to proxy queries to various Ruby frameworks and he will even make it look easy!

Hrm…Sorry.
I am going to tell you how I quickly updated a couple servers with Ruby 1.9.1 and Lighttpd. And it will look easy because it is, in fact, easy.
This article’s aim is to be practical but I will explain as we go along.

The first thing I did was update CentOS to a more recent version. This could take a while but it’s always a good idea to keep a server software up-to-date so I’m sure that your already are almost there:

yum update

In my case, after a couple hours (oops), the update was complete and I rebooted the servers.

Ruby 1.8.5

That’s the easy part because it’s the version of Ruby that currently comes with CentOS. Therefore you can install it using yum:

yum install ruby ruby-devel ruby-irb ruby-rdoc ruby-ri

Ruby 1.9.1

Download the package from ruby-lang.org:

wget ftp://ftp.ruby-lang.org/pub/ruby/1.9/ruby-1.9.1-p0.tar.bz2
tar zxvf ruby-1.9.1-p0.tar.bz2
cd ruby-1.9.1-p0

The trick, here, is to give all 1.9.1 binaries a different name. Fortunately, configure offers an option for that:

./configure --program-suffix=19

Build and install:

make && make install

Rubygems

Get rubygems from Rubyforge:

wget http://rubyforge.org/frs/download.php/60718/rubygems-1.3.5.tgz
tar zxvf rubygems-1.3.5.tgz
cd rubygems-1.3.5

Note that we are now using our brand new 1.9.1 binaries:

ruby19 setup.rb

Now, make sure your gems are up to date:

gem19 update

Let’s see. We want to use two frameworks: Rails and Sinatra. Installing them could not be simpler:

gem19 install rails
gem19 install sinatra

We will use thin to run our programs:

gem19 install thin

Let’s make sure that thin is run when the server boots up:

thin install

This will create /etc/init.d/thin which we can then link to the appropriate runlevels using chkconfig

We will, when creating Ruby applications, tell thin about the instances it needs to run. This will be done by adding yml files to /etc/thin/

I am going, in this article, to create these applications in /home/yourdirectory/. Of course, use your own directory.

Rails

Let’s create a Rails application:

rails myrailsapp

In the application’s config/ directory, let’s create our thin yml file (thin_myrailsapp.yml)

chdir: /home/yourdirectory/myrailsapp
address: 127.0.0.1
port: 3000
servers: 4
max_conns: 1024
timeout: 30
max_persistent_conns: 512
user: www
group: www
environment: development
pid: tmp/pids/thin.pid
log: log/thin.log
daemonize: true

chdir tells thin where our document root is located. Rails serves documents from document root/public/
address is localhost because that’s what will be used when proxying through Lighttpd
servers is ‘4′ which means that four servers will be instantiated, starting at , i.e. 3000, 3001, 3002, 3003
User and group: I am using the same user and group that Lighttpd runs as for simplicity sake.

Let’s tell Lighttpd about this new application. Edit /etc/lighttpd/lighttpd.conf, or wherever your configuration file is:

$HTTP["host"] =~ "myrailsapp\.yourdomain\.com$" {
        $HTTP["url"] =~ "^/((images|javascripts|stylesheets)/(.*)$)" {
                server.document-root = /home/yourdirectory/myrailsapp/public"
        }
        proxy.balance = "fair"
        proxy.server =  ("" =>
                (
                        ( "host" => "127.0.0.1", "port" => 3000 )
                )
        )
}

I am using the “fair” load balancer because, as the default option, it tries to be…fair, obviously, and isn’t too greedy: it does not compute a hash for each url.

Let’s now tell thin about this application by simply creating a symbolic link in /etc/thin/:

ln -s /home/yourdirectory/myrailsapp/config/thin_myrailsapp.yml /etc/thin/

Restart Lighttpd and start thin:

/etc/init.d/lighttpd restart && /etc/init.d/thin start

I know…supposedly I should be able to simply type ‘lighttpd reload’ and it will reload its configuration files but that does not always seem to work.

Now, the fun stuff:

Go to http://myrailsapp.yourdomain.com:3000 and you should be greeted by Rail’s welcome page.

Now, go to http://myrailsapp.yourdomain.com and you should see the same page, except this time it was proxyied by Lighttpd.

Sinatra

Create your application; e.g. the ubiquitous “hi” application. Again in /home/yourdirectory/mysinatraapp, create hi.rb:

require 'rubygems'
require 'sinatra'
get '/' do
    'Hello world! I love kittens.'
end

Create a config/ subdirectory:

mkdir config && cd config

In the config/ directory, let’s create our thin yml file (thin_mysinatraapp.yml)

rackup: /home/yourdirectory/mysinatraapp/config/mysinatraapp.ru
chdir: /home/yourdirectory/mysinatraapp
address: 127.0.0.1
port: 4567
servers: 4
max_conns: 1024
timeout: 30
max_persistent_conns: 512
user: www
group: www
environment: development
pid: /home/yourdirectory/mysinatraapp/thin.pid
log: /home/yourdirectory/mysinatraapp/thin.log
daemonize: true

Starting at port 4567 because, by convention, it’s Sinatra’s default port when started standalone.

Notice the main difference? Sinatra will rely on rack for its setup, hence the ‘rackup‘ keyword.
Let’s create that rack file (mysinatraapp.ru)

require 'sinatra'
 
Sinatra::Application.default_options.merge!(
  :run => false,
  :env => :development
)
 
require 'hi.rb'
run Sinatra.application

Do not forget Lighttpd:

$HTTP["host"] =~ "mysinatraapp\.yourdomain\.com$" {
         proxy.balance = "fair"
         proxy.server =  ("" =>
                 (
                         ( "host" => "127.0.0.1", "port" => 4567 )
                         # room for more instances
                 )
         )
}

Of course, if you know that some directories will be dedicated to static content you can also check for these directory names and have Lighttpd serve them statically, as shown in myrailsapp’s example.

Restart Lighttpd and thin:

/etc/init.d/lighttpd restart && killall -HUP thin

Test it:

Go to http://mysinatraapp.yourdomain.com:4567 and you should see the message returned by hi.rb.

Now, go to http://mysinatraapp.yourdomain.com and you should see the same page, except, again. it was proxyied by Lighttpd.

Conclusion

As I wrote earlier, this is easy and actually fairly straightforward. It some of this is not working for you, it is likely because I glossed over something I really shoudln’t have. Post a comment and describe your issue and I will gladly help.

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


S2ajax v1.0 connects simply PHP and JavaScript

S2ajaxHere comes S2ajax v1.0!
And it was long overdue. Six months already since I posted S2ajax says “hi()” I can hardly believe it.

What I think of as v1.0’s main feature is that it is now possible to simply export classes in PHP and these classes can be instantiated in JavaScript. Whenever these instances are modified through asynchronous method calls, these modifications are transparently persisted server-side.

The concept

Is it a PHP class? Is it a JavaScript class? Why, it’s both! The class is defined in PHP on the server. Instances of the class are created on demand using JavaScript on the client. Whatever modifications are made to an instance are serialized on the server.
You can create a complex application using as many classes and instances as you need.

100

Under the hood

The PHP class is exported; the proxy JavaScript code is generated.
Whenever the client needs to access one of the class’ properties/methods, the proxy transparently talks to the class; the class lives server-side.

99

The Client’s point of view

An arbitrary number of instances of the class can be created in JavaScript.
The only hint that you are using a client-server architecture is the fact that when invoking a method, its return value is obtained through a callback. This mechanism allows your client interface to remain responsive while the server is preparing a response.

101

The server’s point of view

S2ajax automatically persists your instances’ state and data between consecutive asynchronous calls. You still get the benefits of the “shared nothing” approach of PHP but complex objects can be manipulated through an unlimited number of clients requests.

102

A code sample

Server-side

Let’s create a class that will increment an instance variable every time a method is invoked. Let’s keep it as simple as possible:

class CounterTester
{
    private $counter;
 
    function __construct() {
        $this->counter = 0;
    }
 
    public function increment_counter() {
        $this->counter++;
        return $this->counter;
    }
}

Clearly, every time increment_counter() is invoked, $counter will be incremented and its new value returned.

Client-Side

First, an instance of our class is created. Then, when a user click on the button labeled ‘Increment counter’, the instance’s increment_counter() method will be invoked and the new $counter value returned to our callback and displayed.

?View Code JAVASCRIPT
<script>
function display_result(val) {
    alert(val); // Display new counter value
}
var counter = new CounterTester();
</script>
<button onclick="counter.increment_counter(display_result);">Increment counter</button>

Note that we could create as many instances of our class as we wish and provided we display the matching buttons, we could independently increment multiple counters.

Get it now!

Click our valiant friend “Octocat”, artistically represented below, to go to S2ajax’s Github page. If you just wish to use the library, look for the [Download] button:

Git!

Git!

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


PHP PDO class and XAMPP/”exotic” MySQL configurations

Last night I was trying to setup a @mail server but the installer kept choking when attempting to connect to my local database.

I am posting here my quick workaround in case you too, dear reader, get a dreaded “PDO” error message complaining about your attempt to “connect to unix://

Here is I how I solved the issue for @mail: I opened library/Zend/Db/Adapter/Pdo/Abstract.php, found the line that creates the PDO object (”new PDO(…“) and, in this example, the first argument passed to the constructor was $dsn.

The problem is that PDO sees that this variables references “localhost” and decides that, since the database is local, it is going to use mysql.sock (hence the unix:// scheme)

This works as long as the file is found in its default location. If it is elsewhere, you are out of luck. This happens, for instance, if you use XAMPP.

Here is my quick fix, inserted right before the creation of the PDO object:

$dsn = str_replace(’host=localhost’, ‘unix_socket=/opt/lampp/var/mysql/mysql.sock’, $dsn);

All done.

Note: I have not investigated enough to know whether this is something missing in the Zend Framework or in @mail itself.

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


Determining if an IP is within a specific range: redux

Submarine NetI was reading Paul Gregg’s very clear explanation of “classless” ranges comparison when I realized that his code was not as “bare metal” as could be.

So, here is the code I’ve been using in nextBBS.
It only accepts ranges in the form “x.x.x.x/b” but it’s short and all I needed, really.

function isSubnet($subnet, $ip)
{
    // Classless (in more than one way) comparison
    $cursubnet = explode('/', $subnet);
    $longsubnet = ip2long($cursubnet[0]);
    $longip = ip2long($ip);
 
    if(count($cursubnet)<2)
    {
        // Compare IP itself
        return ($longip==$longsubnet);
    }
 
    // IPv4 only!
    $subnetmask = 0xffffffff << (32-$cursubnet[1]);
    return (($longip & $subnetmask) == ($longsubnet & $subnetmask));
}

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


One-line variables swap in PHP, Ruby, Perl, Python and C

Joy in BlueToday’s fun and games: let’s swap a few variables without using more variables than necessary.

Let me know if you disagree with any of this…

$a = 1;
$b = 2;
list($a, $b) = array($b, $a);
print "a=$a, b=$b\n";
a = 1
b = 2
a, b = b, a
puts "a=#{a}, b=#{b}\n"
my $a = 1;
my $b = 2;
($a, $b) = ($b, $a);
print "a=$a, b=$b\n";
?View Code PYTHON
a=1
b=2
a, b = b, a
print 'a=%i, b=%i' % (a, b)

And now, good old C. Let’s use the only method that does not generate an overflow…first, for integer types:

int a=0, b=27;
a=(a^(b=(a=a^b)^b));
printf("a=%d, b=%d\n", a, b);

“Well,” you may say, “but what about pointers arithmetic? Any solution?”
Why, yes. That’s what I came up with after 15 minutes of fiddling:

char *a = "one", *b = "two";
a=(void *)(((long)a)^(long)(b=(void *)(((long)(a=(void *)((long)a^(long)b)))^(long)b)));
printf("a=%s, b=%s\n", a, b);

Note the use of void *, which allows me to ignore the pointer type. And, yes, I know, a C++ compiler may not really like this syntax. This may be doable using templates but I haven’t looked into that.
Sorry, I haven’t looked into Erlang or Haskell yet…

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


Bespin in Titanium: From The Jaws Of Victory…

bespin is really an intriguing project. Since I’ve grown frustrated with the inconsistencies between the various code editors that I have been using — I work on Leopard at home and Ubuntu at work — I thought that creating my own editor would be the answer to that. Nothing fancy, mind you. Just something consistent.

My first impulse was to use Flex. And it almost worked! Using mx:html I was able to wrap a nice web page in an otherwise very ActionScript-y application.
And then, catastrophe! Flex Webkit’s canvas implementation is subpar and I could only get a very mamed version of bespin. Nothing usable, anyway.

Thus, I turned to Titanium.
After some light trial and error, I got it to work!

Unfortunately, the result is less than awesome: Titanium’s Webkit gets easily overwhelmed and, worse, crashes reliably ( :g: ) as soon as I ask it to do some medium lifting.

This video shows the original victory followed by the vexing defeat:

Note that, to get it to work, I replaced embed.js with my own version that works around any dojo.request()/eval issue:

?View Code JAVASCRIPT
(function() {
    // -- Load Script
    var loadme = new Array();
    var loadScript = function(src, onload) {
        var embedscript = document.createElement("script");
        embedscript.type = "text/javascript";
        embedscript.src = src;
        embedscript.onload = onload;
        document.getElementsByTagName("head")[0].appendChild(embedscript);
    }
    var onScriptLoaded = function() {
        var src = loadme.shift();
        if(src)
            loadScript(src, onScriptLoaded);
    }
 
    var componentRequires = function() {
        dojo.require("bespin.bespin");
 
        dojo.require("bespin.util.canvas");
        dojo.require("bespin.util.keys");
        dojo.require("bespin.util.navigate");
        dojo.require("bespin.util.path");
        dojo.require("bespin.util.tokenobject");
        dojo.require("bespin.util.util");
        dojo.require("bespin.util.mousewheelevent");
        dojo.require("bespin.util.urlbar");
 
        dojo.require("bespin.client.filesystem");
        dojo.require("bespin.client.settings");
        dojo.require("bespin.client.status");
        dojo.require("bespin.client.server");
        dojo.require("bespin.client.session");
 
        dojo.require("bespin.editor.actions");
        dojo.require("bespin.editor.clipboard");
        dojo.require("bespin.editor.cursor");
        dojo.require("bespin.editor.editor");
        dojo.require("bespin.editor.events");
        dojo.require("bespin.editor.model");
        dojo.require("bespin.editor.toolbar");
        dojo.require("bespin.editor.themes");
        dojo.require("bespin.editor.undo");
 
        dojo.require("bespin.syntax.base"); 
        dojo.require("bespin.syntax.simple._base");
 
        dojo.require("bespin.cmd.commandline");
        dojo.require("bespin.cmd.commands");
        dojo.require("bespin.cmd.editorcommands");
 
        dojo.require("th.helpers"); // -- Thunderhead... hooooo
        dojo.require("th.css");
        dojo.require("th.th");
        dojo.require("th.models");
        dojo.require("th.borders");
        dojo.require("th.components");      
    }
 
    loadScript("js/dojo/dojo.js.uncompressed.js", function() {
        dojo.require = function(src) {          
            loadme.push('js/' + src.replace(/\./g, '/') + '.js');
        }
        componentRequires();
        dojo.require("bespin.editor.component");
        loadScript(loadme.shift(), onScriptLoaded);
    });
})();

As you can see, I override dojo.request() with my own, stack up all the component names, then load them one by one, waiting for each to be fully loaded before moving on.

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


PHP classes and Javascript: S2ajax says “hi()”

S2ajax LogoSajax is a ‘managed’ AJAX framework that was created by the fine folks at Modern Method a few years ago.
What’s so great about it is the seamless communication between your back-end and the web page itself: you write your PHP code, tell Sajax which functions to export and they are now accessible from Javascript.

For instance — from the ‘example_types.php’ file:

 function return_string() {
     return "Name: Tom / Age: 26";
 }

The corresponding Javascript call would be:

?View Code JAVASCRIPT
<button onclick="x_return_string(display_result);">Return as string</button>

OK so this is a pretty great package, no doubt.

Unfortunately there are exactly three things that bother me here:

  1. The choice to prefix all remote calls with ‘x_’ which feels less natural, even though it is a convenient way to avoid namespace collisions.
  2. More importantly, Sajax does not support PHP classes and I am not comfortable working with strictly procedural code. After all, object-oriented PHP has been around for quite some time now.
  3. Of course, it would seem that the last Sajax release happened sometime in 2006, which would explain #2

Thus, S2ajax was born.

If supports classes and methods, does not require prefixing and the export() calls are now more powerful.
The syntax is still very straightforward and relies on clean Javascript code.
And the license, obviously, is still the very open BSD.

Additionally, this S2ajax can be easily integrated with PHP 5’s magic class and methods loading. For instance, it works with my own PHP framework.

As usual, all this goodness is available at github.com!

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


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!


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!