Archive for the ‘web’ Category

The best Chrome feature: violation of Apple Human Interface

Thursday, March 25th, 2010

Google Chrome is awesome. There don’t seem to be many people out there that will argue otherwise.

One of the things I like best about it is that they seem to willing to do whatever it takes to make the best user experience out of their product. They aren’t worried about the politics or competing interests that seem to plague progress on Firefox.

In my opinion, one of the best feature of Google Chrome on OSX is that it violates the Apple Human Interface Guidelines.

Apple has very strict interface guidelines that developers by and large tend to stick to. Partially because of Interface Builder, partially because of the community, applications on OSX tend to look and behave the same. This is in contrast to Windows where Microsoft can’t seem to settle for a single Widget toolkit for their own applications. So the guidelines are great, except when they are not.

Tabs in the title bar

The most obvious violation is related to tabs in the title bar of the UI.

from the guidelines:

The only controls that belong in a title bar are the close, minimize, and zoom buttons. If a title bar is combined with a toolbar, the unified area can contain the toolbar control and the toolbar customization contextual menu (these controls are described in “Title Bar Buttons”). Do not place other controls in a title bar.

This is in some ways just a nicety, but it is a new way to think about how to group information. It separates the tabs and makes it much more clear to a user that the back, forward, and URL controls are specific to the content in that tab. This also has the added benefit of making it possible for windows to be dropped into other windows to be merged or split.

Control focus when tabbing on the keyboard

According to the guidelines, controls should only receive focus if they are text entry fields. This means that hitting tab on a form will always tab over a drop down box.

from the guidelines

In default keyboard access mode, focus moves only between fields that receive keyboard input.

Anybody that has entered an address on a website knows what a pain in the ass it is when it tabs right past the state drop down field.

Apple suggests that application designers avoid this problem by limiting popup menus to be used only when you have short lists of items.
from the guidelines:

Use a pop-up menu to present up to 12 mutually exclusive choices that the user doesn’t need to see all the time. Sometimes a pop-up menu can be a good alternative to other types of selection controls.

Good in theory, but this isn’t how popups are used. Not in applications, and especially not on the web. Chrome tabs to select boxes on OSX like it does on other platforms. Google has decided that the web is their operating system and is using the same UX design across platforms accordingly. Perhaps it is time for companies to come together and come up with some cross-OS UX behaviors.

*This is actually one of my biggest pet peeves with the apple UI guidelines. They were set in stone many years ago and very rarely change. I understand the need for consistency but I think things like tabbing to all controls on a web-page need to be addressed. Perhaps they are long overdue for a review and overhaul. Apple, after all, is notorious for violating their own guidelines.

Simple Image Cropping with Flex

Wednesday, January 6th, 2010

Here is a simple, pure as3, image cropper I wrote for Flex. I’ll try do a post later in the week describing how to use it in Flash cs3 and cs4.

[kml_flashembed publishmethod=”static” fversion=”9.0.0″ useexpressinstall=”true” replaceId=”flex_image_crop” movie=”http://www.wirelust.com/apps/flex_image_crop/bin-debug/main.swf” width=”640″ height=”458″ targetclass=”flashmovie”]

screenshot for people without flash

[/kml_flashembed]

Features:

  • Very Simple
  • Allows you to set min and max values for height and width
  • Holding shift key keeps aspect ratio

You currently have to do the cropping on your own, either in flash or server side. I will followup later with examples of how to do that.

To embed this into you application, all you have to do is:

import com.wirelust.imagecrop.ImageCrop;
import com.wirelust.imagecrop.CropBox;
 
private var imageCrop:ImageCrop = new ImageCrop();
private function onCreationComplete():void {
	imageCrop.loadImage("demo1.jpg");
	
	// imageBox is an mx:HBox in my mxml
	imageBox.addChild(imageCrop);
	
	var crop:CropBox = imageCrop.cropBox;
	
	// Set up the initial crop
	crop.cropX = 178;
	crop.cropY = 187;
	crop.cropWidth = 249;
	crop.cropHeight = 219;
}

You can then listen for CropBox.EVENT_CHANGED to get the dimensions of the box as it changes:

private function onCreationComplete():void {
	// snipped ...
	
	crop.addEventListener(CropBox.EVENT_CHANGED, onCropChanged);
}
private function onCropChanged(event:Event):void {
	if (imageCrop.cropBox != null) {
		xValue.text = imageCrop.cropBox.cropX.toString();
		yValue.text = imageCrop.cropBox.cropY.toString();
		widthValue.text = imageCrop.cropBox.cropWidth.toString();
		heightValue.text = imageCrop.cropBox.cropHeight.toString();
	}
}

Files

 Download source code

[snippit] – generating a faux waveform in AS3

Tuesday, December 8th, 2009

Working on a Flash project. I had to use the microphone to record some audio and generate a simple waveform so the user has some feedback that they are being heard. While not a true waveform, you can use the microphone activity level to generate something that works pretty well.

[kml_flashembed publishmethod=”static” fversion=”9.0.0″ movie=”http://www.wirelust.com/examples/waveform/bin-debug/waveform.swf” width=”500″ height=”250″ targetclass=”flashmovie”]

Get Adobe Flash player

[/kml_flashembed]

Here is the code to generate the above sample:

import com.wirelust.waveform.Waveform;
import flash.media.Microphone;
 
private var wave:Waveform = new Waveform();
private var mic:Microphone;
 
private function init():void {
	wave.height = 50;
	wave.width = 450;
	wave.y = this.height/2 - wave.height/2;
	wave.x = this.width/2 - wave.width/2;
	this.addChild(wave);
	
	mic = Microphone.getMicrophone();
	mic.setUseEchoSuppression(false);
	
	// loopback is required so we can get the activity level and create the waveform. - so stupid
	mic.setLoopBack(true);
	
	// turn off the volume for the loopback
	var mySoundTransform:SoundTransform = new SoundTransform();
	mySoundTransform.volume = 0;
	mic.soundTransform = mySoundTransform;
	
	this.addEventListener(Event.ENTER_FRAME, onFrameEnter);
}
 
private function onFrameEnter(event:Event):void {
	if (mic != null && !mic.muted) {
		wave.add(mic.activityLevel);
		wave.invalidateDisplayList();
	}
}

And for the Waform class:

import flash.display.*;
import flash.events.*;
import flash.geom.Matrix;
 
import mx.core.*;
import mx.graphics.IFill;
import mx.graphics.IStroke;
import mx.graphics.SolidColor;
import mx.graphics.Stroke;
 
public class Waveform extends UIComponent {
	private var levels:Array = new Array();
	public var color:uint = 0x000000;
	public var borderColor:uint = 0xCCCCCC;
	public var borderWidth:uint = 1;
	public var autoScale:Boolean = true;
	
	public function Waveform() {
	}
	
	public function add(level:Number):void {
		// trim the levels so we don't keep eating up memory
		while(levels.length > this.width) {
			levels.shift();
		}
		levels.push(level);
	}
	
	override protected function updateDisplayList(param1:Number, param2:Number) : void {
		var scaleFactor:Number = 1;
		// autoscale will find the highest volume and scale all lines in the display accordingly.
		if (autoScale) {
			var highestLevel:Number = 0;
			for (var i:uint=0; i<levels.length; i++) {
				if (levels[i] > highestLevel) {
					highestLevel = levels[i];
				}
			}
			scaleFactor = height/2/highestLevel;
		}
 
		super.updateDisplayList(param1, param2);
		graphics.clear();
		graphics.lineStyle(1, color, 1, true, LineScaleMode.NORMAL);
		var lineX:uint = 0;
		for (var j:uint=0; j<levels.length; j++) {
			graphics.moveTo(lineX, this.height/2 - (levels[j] * scaleFactor));
			graphics.lineTo(lineX, this.height/2 + (levels[j] * scaleFactor));
			lineX++;
		}
		
		if (borderWidth > 0) {
			graphics.lineStyle(borderWidth, borderColor, 1, true, LineScaleMode.NORMAL);
			graphics.drawRect(0, 0, this.width, this.height);
		}
	}
}
}

Files

 Download source code

AS3 port of JZLib

Monday, June 8th, 2009

Last month I was working on a project that used FZip to decompress some zip files in flash.

One tricky thing about FZip is that when running in Flash Player it requires your zip files to have an adler32 checksum for each file in order to work. This is normally fixed with a work around python script provided with FZip.

The python script is easy and all, but why not figure out how to do it in pure AS3 with more standard zip files?

The checksum is needed because AS3’s ByteArray only supports ZLib when running in Flash Player. In AIR it supports deflate which is what zip files use by default. I would be really curious to hear from Adobe why they chose not to support deflate since you need deflate for Zlib to work anyway.

I decided to implement inflate in as3 but I didn’t want to do it with new code so I looked for FOSS projects to port. JZlib was a good choice because Java is similar to AS3 and it didn’t rely on any external system calls.

This port supports everything in JZlib so you can use it for any inflate or deflate operations you might need.

To use with FZip

I used this library in FZip so it no longer requires that zip files be converted before use. It is tested to be working with the OSX cli zip command. It doesn’t work with OSX finder zip compression because of another issue.

In FZipFile.as:

protected function parseContent(data:IDataInput):void {
	if(_compressionMethod === COMPRESSION_DEFLATED && !_encrypted) {
		if(HAS_INFLATE) {
			// Adobe Air supports inflate decompression.
			// If we got here, this is an Air application
			// and we can decompress without using the Adler32 hack
			// so we just write out the raw deflate compressed file
			data.readBytes(_content, 0, _sizeCompressed);
		} else if(_hasAdler32) {
			// Add zlib header
			// CMF (compression method and info)
			_content.writeByte(0x78);
			// FLG (compression level, preset dict, checkbits)
			var flg:uint = (~_deflateSpeedOption << 6) & 0xc0;
			flg += 31 - (((0x78 << 8) | flg) % 31);
			_content.writeByte(flg);
			// Add raw deflate-compressed file
			data.readBytes(_content, 2, _sizeCompressed);
			// Add adler32 checksum
			_content.position = _content.length;
			_content.writeUnsignedInt(_adler32);
		} else {
			data.readBytes(_content, 0, _sizeCompressed);
 
			//throw new Error("Adler32 checksum not found.");
		}
		isCompressed = true;
	} else if(_compressionMethod == COMPRESSION_NONE) {
		data.readBytes(_content, 0, _sizeCompressed);
		isCompressed = false;
	} else {
		throw new Error("Compression method " + _compressionMethod + " is not supported.");
	}
	_content.position = 0;
}
 
protected function uncompress():void {
	if(isCompressed && _content.length > 0) {
		_content.position = 0;
		if(HAS_INFLATE) {
			_content.uncompress.apply(_content, ["deflate"]);
		} else if(_hasAdler32) {
			_content.uncompress();
		} else {
			var uncompr:ByteArray = new ByteArray();
			var d_stream:ZStream=new ZStream();
			d_stream.next_in = _content;
			d_stream.next_in_index = 0;
			d_stream.next_out = uncompr;
			d_stream.next_out_index = 0;
			
			var err:int = d_stream.inflateInitWithNoWrap(true);
			while(d_stream.total_in <= _content.length && i<=_content.length*4) {
				d_stream.avail_in=d_stream.avail_out=10;
			
				err=d_stream.inflate(JZlib.Z_NO_FLUSH);
				if(err == JZlib.Z_STREAM_END) {
					System.println("decompress success:" + this.filename);
					break;
				} else if (err == JZlib.Z_STREAM_ERROR) {
					System.println("stream error:" + this.filename + " " + d_stream.msg);
					break;
				} else if (err == JZlib.Z_DATA_ERROR) {
					System.println("data error:" + this.filename + " " + d_stream.msg);
					break;
				//} else {
				//	System.println("status:" + this.filename + " " + err);
				}				
			}
			err=d_stream.inflateEnd();
 
			_content = uncompr;			
		}
		_content.position = 0;
		isCompressed = false;
	}
}

*** UPDATE 2/9/2010: Thanks to Kathrin Furtlehner for sending me a test case for a bug where it wasn’t extracting the full file when dealing with longer strings. I updated the patched FZip archive to reflect the fix.

Fonts on the web – Part 1

Sunday, May 31st, 2009

I’ve been reading and hearing a lot of rumbling lately from designers lately desperately looking for a better way to add fonts to their web design.

I feel that there is a lot of confusion in the design and web developer community as to what the issues are with font embedding or linking and when there isn’t confusion I often find myself on the opposite side of the debate and end up at a impasse with whoever I am talking with. In the past month I have found myself in this position more than once in person.

Last week I was posting a response on MildFuzz (link) and I realized that just debating the technical merits of the problem and proposed solutions isn’t going to get me anywhere. I set out to write a blog post to express my positions and beliefs and explain the technical problems to people but as I was writing I realized that there is just too much to dump into a single blog post.

My goal here is to write a long multi-part post that frames the technical issues, talks about proposed solutions, touches on the debates going on in the community, and offers my thoughts on a solution to the problem. This first post will just be an introduction.

first. About me:
I have been working as a professional web and application developer for 11 years now. Before that I did some type design and released several commercial and free fonts over at GrilledCheese.com. While the site may appear very very stale, I have been working for months now updating all of my typefaces and preparing them for a re-release. (Cleaning up glyphs that are over 10 years old and converting to OpenType is quite a chore, not to mention the 10 or so unfinished works I never released). I am most likely going to release most of my typefaces as Creative Commons licensed works or some hybrid commercial entity that I will talk about in a later article. Since I am now a professional programmer and not a designer I tend to look at things from more of a technical perspective and that will probably come through in this writing.

here is an outline of the posts I am working on

  • A brief history of type and fonts
  • The type industry compared to other creative works
  • Type on the web – methods and controversy
  • My proposed solution

Converting binary to signed decimal in Actionscript

Wednesday, April 8th, 2009

If you’re like me, you may have been living these past few years taking advantage of the luxury of new sleek programming languages that rarely require you to do low level operations. One of the old skills I seem to always let get rusty enough is bitwise operation. Most code written with the help of frameworks doesn’t require this type of code and most programmers probably don’t even bother to learn it anymore.

I came into an instance yesterday where I needed to do some bitwise work in actionscript. The code was reading a bytestream from a binary file and converting to an array of numbers. Since this project was ported from Java (by someone else), a simple operation was overlooked. It was taking the binary number and converting it to a signed short. Since actionscript doesn’t have the same casting mechanisms conversion isn’t as straight forward in java.

In java the operation looked like this:

//  binary: 1111 1111 1111 0001
int hexValue = 0xFFF1;
 
short value = (short)(hexValue);
// value = -15 

Since short in java is always stored with a signed bit, the cast appropriately set the value at -15.

The way this was translated into Actionscript looked liked this:

//  binary: 1111 1111 1111 0001
var hexValue:int = 0xFFF1;
 
var value:int = int(hexValue);
// value = 65521  // Wrong! 

Actionscript has no concept of casting for primitives so the int(hexValue) doesn’t do anything at all.

Here is the workaround I came up with, I suspect there is a shorter way to get the same result but I can’t think of one right now.

//  binary: 1111 1111 1111 0001
var hexValue:int = 0xFFF1;
 
// get everything except the signed bit "111 1111 1111 0001"
var unsignedValue:Number = (hexValue & 0x7FFF);  
// unsignedValue now equals 32,753
 
var signedValue:Number = unsignedValue;
 
// if the signed flag is set, flip the value
if ((hexValue >> 15) == 1) {
    signedValue = unsignedValue - 0x8000;   // 0x800 =  32,768 (maximum 15 bit number)
}
 
// signedValue = -15 

Picture Pump

Monday, April 6th, 2009

About five years ago Ben Sisto had an idea for an application that I thought would be useful. The idea being that if you want to download images from Google image search — it takes forever to go through the results, wait for them to load, and save the images that aren’t 404. The application Ben envisioned, would simply download every result and ignore the ones that were 404 or took too long to load. Once you have all the results on your hard drive it is a lot easier to sift through them.

I quickly banged out a release that was workable in a few days and never really looked at it again.

This week I had an exact need for this application again. When I fired it up, it seems Google changed their site so it wasn’t working anymore. I spent some time tonight to fix it up and got it working much better than the original.

So here it is. v1.0 (i guess) release of Picture Pump (named after the site Ben ran at the time – HoneyPump):

Click launch to start/install the application. OSX users should be all set, windows users you might have to get java first.

also, hey look. source code

Email me with any suggestions or patches.

UPDATE: 10/24/2009
– fixed my mime settings so that launch button works again (sorry moved servers and forgot to set it)
– fixed a few bugs
– added support for safe search
– added filtering by image type and license
– source code is now under the Apache Public 2.0

Font Reader

Thursday, April 2nd, 2009

I’ve been doing some work with Five3D lately. I extended it so I could render text with proper kerning pairs. I hit a bit of a wall though with converting my fonts to as3 since the way Mathieu does it doesn’t lend itself well to getting kerning pairs from flash itself.

Looking for an alternative, I started taking a look at Font Reader which is a port of a project called Typecast I saw a few years back.

Anyway. Long story short, I’ve been up all night playing around with and extending Font Reader (for my current project, but also because it’s fun to play with).

The version that is posted on the site uses a custom written path renderer which seems to have some bugs. In particular it seems to clip at the end of a logical path segment.

The custom objects were very close to the new stuff in the Flex 4 gumbo stuff from Adobe, so I was able to easily swap them out and use the Adobe classes instead. Since that was so easy, I started working on getting editable point handles so I can start editing the paths within the application. I have this about 75% done, but I can’t help quell that feeling that there must be a reusable class out there someone else wrote. Anyone know of one?

Here’s a screen shot of my current working build for comparison:

If I get this working by next week, I’ll post the code.

Finding a good web based Excel-like grid

Wednesday, February 25th, 2009

I’ve been searching for a good Excel-like grid widget that can be embedded in web-apps. I essentially need something like Google Docs but open so I can alter and self host it.

Here is my wishlist:

  • Looks nice
  • Allows inline cell editing
  • good Javascript API or extensibility
  • can plop into an existing project without committing to a whole framework (sorry RichFaces)
  • Supports as many functions as possible, Excel functions would be nice
  • Open source (LGPL or something similar)

I looked at a ton of projects. These grids looked great for displaying data but didn’t have any support for functions so they were out. I’m putting the list here to remind myself to keep them in mind for other projects.

In the end it came down to these three projects:

Sigma Grid:

This is a nice grid for structured data and supports pretty much everything I would need for most projects. It even has chart, print, and calendar support which would make it great for end users. It supports formulas but only ones defined in the code on the page, not ones the user enters, so it wouldn’t work for this particular project.

Simple Spreadsheet

This spreadsheet appears to be the most capable Excel like clone I could find. It supports charts, formulas, and custom macros. I ended up not using this because the GUI is lacking. Out of the box my users would be forced to know some html. There also seemed to be some weird behavior when you are editing a cell with a formula in it and click on another cell. I think this would be a great project if it got a makeover and a nice user-friendly toolbar. Try it out Here.

Social Calc

I don’t remember how I found this project because their website is the least flashy and really doesn’t show much about the grid at all. Social Calc could also use some help on the GUI front, and I will have to clean it up a little before I put it in my project – but it seems to be a solid reliable control. Once you get past the UI differences from Excel — you can’t right click or select rows or columns — the editing is pretty smooth and natural. For client side formulas I would have been happy with =SUM() and =AVG() functions and was pleased to find =VLOOKUP() and most others working. In fact, it seems to support most Excel functions with the exact syntax that business guys love.
It doesn’t seem to be able to do nested formula calls. I am going to look into how difficult it would be to add that if I end up using it.

I couldn’t find a demo of Social Calc anywhere on their site. I put their demo up here if you want to try it out.

While I’m thinking about proxys.

Monday, February 23rd, 2009

Sloppy is a tool I use all the time to test out sites in. It will load any site and simulate a slower connection than you are currently using. It used to be great to see how your site loaded on a 56k modem — now I use it to test how my sites load on a cell phone connection.

It also works cross platform without any install thanks to Java Web Start.