Thursday, October 8, 2009

Filesystem Based Events with Incron

Ok, so I installed and configured incron witch is a nice cron like system for subscribing to file system events such as file creation or modification. It works great with jobs like this:

/mydir/processing IN_CLOSE_WRITE mv $@/$# $@/../done


This line will look for new files written to /mydir/processing/ and move them to /mydir/done when they are finished witting and closed. (Won't work for log-type files that are kept open but there are other options for that.)

However you cant do either of the following things:

/mydir/processing/file*.txt IN_CLOSE_WRITE mv $@/$# $@/../done
/mydir/processing IN_CLOSE_WRITE cd $@ && ../proc_file $# && rm $#


The first example tried to monitor only a subset of the directory witch doesn't work. (Though you can monitor a single file by name.) The second example tries to execute a series bash operations on the file witch doesn't work. (Only one command can be executed, with no popping support.)

Both of these problems can be solved by writing a external bash file and calling that instead and passing it the file name like so.

/mydir/processing IN_CLOSE_WRITE myscript $#


But alas I'd prefer to not have hundreds of bash scripts and have the same capabilities for piping and concatenation that cron has. As it tunes out, after a few hours of screwing with it, two simple shell scripts will solve both these problems. With those two scripts installed the follwing command will work.

/mydir/processing IN_CLOSE_WRITE run filter $# ^*txt$ && cd $@ && ../proc_file $# && rm $#


In this case the run option will pass the rest of the line on to bash for full fledged execution. As the first step in the execution it will run the filter operation witch will return true if the filename matches the regex sting and continue execution or return false if not and end the command.

/usr/bin/run
#!/bin/bash
eval $@

/usr/bin/filter
#!/bin/bash
if [[ "$1" =~ $2 ]]; then
exit 0;
else
exit 1;
fi

Thursday, July 2, 2009

Tuesday, April 28, 2009

Tuesday, March 10, 2009

Reading CSV file in Regex

Csv filws come in many shapes and forms, and its trivial to read most a csv files. But then again it can be quite headache when the csv file has a mixture of formats and control characters.

The simplest way to parse a csv file it to use a simple regexp. One might start with something as simple as this:
split(/,/, $line);
But of course this is to simple. It will split on every coma and thus not work for a line like this:
123,asd,"123, asd"
Similarly the following will work:
split(/","/, $line);
"123","asd","123, asd"
But what if we don't have control over the quoter and a mixture of both is generated? And furthermore what if the fields can contain quotes and comas themselves?


In my case I have to parse a file that is quoted as such:
005101,"LITERATURE-P/S, WARRANTY","4,345,211.0000",0.0292"70P"
To phase this the regexp is a bit more complicated:
@line = split(/,(?!(?:[^",]|[^"],[^"])+")/, $line);
for $item (@line)
{
$item[$c] =~ s/^"(.*)"$/$1/;
}
In this case we first split the string on any coma but look ahead each time to check and see if the current coma is part of a quoted literal. If that is the case then the coma is skipped over. Next we look through each item and remove the quotation's if they are present.

This previous example is almost perfect but there is one more problem. Try the next item and it will parse wrong.
005101,"LITERATURE-P/S WARRANTY,","4,345,211.0000",0.0292,"70P"

Note the extra coma at the very end of the literals. This trips up the regexp logic. The simplest solution I found to this problem was to just add a extra space in this case and then trim it at the end.
$line =~ s/,",/, ",/g;
@line = split(/,(?!(?:[^",]|[^"],[^"])+")/, $line);
for $item (@line)
{
$line[$c] =~ s/, $/,/;
$item[$c] =~ s/^"(.*)"$/$1/;
}

The final script can be found below. The script included does a bit more then just phase a csv, it writes it back out as a fixed width file. This can be easily changed to just store the data in an array.
csvtofixed.pl
#!/usr/bin/perl

# Input File.
$in = shift;
# Output File.
$out = shift;
# Fixed width padding.
@size = split(/,/, shift);

if(!$in || !$out || $#size == -1)
{
print "usage: csvtofixed input output columns\n";
print "\tinput: Filename to read in.\n";
print "\toutput: Filename to write out to, will overwrite.\n";
print "\tcoumns: Field widths to pad input fields to. Example: 20,15,3,10\n";
exit;
}

if($in =~ $out)
{
print "Aborted: Can't use same input and output file. Please use a temparay $
exit;
}

# Open Files.
open(IN, $in) or die "Can't open input file $in";
open(OUT, ">$out") or die "Can't create output file $out";

# While there is input.
while(&th;in>)
{
# Read the next line.
$line = $_;

# Trim off the end.
$line =~ s/\r\n//;
# Fix for coma as the last char in a quote bug.
$line =~ s/,",/, ",/g;

# Split the line into its parts.
@line = split(/,(?!(?:[^",]|[^"],[^"])+")/, $line);

# For each column.
for($c=0;$c<=$#size;$c++)
{
# Trim and quoted fields.
$line[$c] =~ s/^"(.*)"$/$1/;
# Remove the extra space for the coma fix.
$line[$c] =~ s/, $/,/;

# Print out the field.
printf OUT "%*.*s|", $size[$c], $size[$c], $line[$c];
}

# Finish the line.
print OUT "\r\n";
}

# Close the files.
close(IN);
close(OUT);

exit;

Friday, February 27, 2009

Flex Date and Time Stepper Components

Update- 7/02/09 4:48 PM
Added Sample.

Update- 4/28/09 4:14 PM
Fixed a problem with the stepper stepping by year.

Update- 3/20/09 2:52 PM
Added the missing CustomStepperEvent class.

Update- 3/9/09 9:44 PM
Fixed a bug with a stepType of month and years.



So after fiddling with trying to make simple easy to use date and time controls smiler to the internal NumberStepper I was never able to fully replicate the NumberStepper interface, nor extend it. As a last attempt I duplicated the entire NUmberStepper class and revised it. Thus I bring you the CustomStepper class. This is a reimplementation of the NumberStepper that uses a Date instead of a Number as the value. It can also display said date in any format and it can step my any date component.

There are two extra classes, the DateStepper and TimeStepper, as well for simplified code. The DateStepper defaults to stepping one day at a time and renders in the MM/DD/YYYY format while the TimeStepper defaults to stepping one hour at a time and renders in the HH:MM AM|PM format.

Sample:
<hines:DateStepper id="begdate" value="{DateUtil.now.assign(DateUtil.DAY, 0).date}" stepSize="7"/>
<hines:TimeStepper id="begdate" value="{DateUtil.now.assign(DateUtil.DAY, 0).date}" stepType="minutes" stepSize="15"/>


Code:
The following style sheet needs to be included in your main application or the code withing put into an existing style sheet.
<Application>
<mx:Style source="style/CustomStepperStyle.css"/>
</Application>
CustomStepperStyle.css
CustomStepper
{
cornerRadius: 5;
downArrowDisabledSkin: ClassReference("mx.skins.halo.NumericStepperDownSkin");
downArrowDownSkin: ClassReference("mx.skins.halo.NumericStepperDownSkin");
downArrowOverSkin: ClassReference("mx.skins.halo.NumericStepperDownSkin");
downArrowUpSkin: ClassReference("mx.skins.halo.NumericStepperDownSkin");
focusRoundedCorners: "tr br"; /* Only round the right corners of the focus rect */
upArrowDisabledSkin: ClassReference("mx.skins.halo.NumericStepperUpSkin");
upArrowDownSkin: ClassReference("mx.skins.halo.NumericStepperUpSkin");
upArrowOverSkin: ClassReference("mx.skins.halo.NumericStepperUpSkin");
upArrowUpSkin: ClassReference("mx.skins.halo.NumericStepperUpSkin");
}


These are the two helper classes.
DateStepper.as
package
{
/**
* This is a custom DateStepper class based off the CustomStepper class and using the preconfigured date style.
*
* If fullButtons is set then larger buttons on the left and right sides will be used instead of buttons split vertically.
* This is mainly used for the touch screens to be more accessible.
*/
public class DateStepper extends CustomStepper
{
/**
* Create a new instance of DateStepper.
*/
public function DateStepper()
{
super(CustomStepper.TYPEDATE);
}
}
}
TimeStepper.as
package
{
/**
* This is a custom TimeStepper class based off the CustomStepper class and using the preconfigured time style.
*/
public class TimeStepper extends CustomStepper
{
/**
* Create a new instance of TimeStepper.
*/
public function TimeStepper()
{
super(CustomStepper.TYPETIME);
}
}
}

A custom is also needed for change events.

CustomStepperEvent.as
package
{
import flash.events.Event;

/**
* Represents events that are specific to the NumericStepper control.
*
* @see mx.controls.NumericStepper
*/
public class CustomStepperEvent extends Event
{
//--------------------------------------------------------------------------
//
// Class constants
//
//--------------------------------------------------------------------------

/**
* The <code>NumericStepperEvent.CHANGE</code> constant defines the value of the
* <code>type</code> property of the event object for a <code>change</code> event.
*
*
* @eventType change
*/
public static const CHANGE:String = "change";

//--------------------------------------------------------------------------
//
// Constructor
//
//--------------------------------------------------------------------------

/**
* Constructor.
*
* @param type The event type; indicates the action that caused the event.
*
* @param bubbles Specifies whether the event can bubble up the display list hierarchy.
*
* @param cancelable Specifies whether the behavior associated with the event can be prevented.
*
* @param value The value of the NumericStepper control when the event was dispatched.
*
* @param triggerEvent If the value changed in response to a user action, contains a value
* indicating the type of input action, either <code>InteractionInputType.MOUSE</code>
* or <code>InteractionInputType.KEYBOARD</code>.
*/
public function CustomStepperEvent(type:String, bubbles:Boolean = false,
cancelable:Boolean = false,
value:Date = null,
triggerEvent:Event = null)
{
super(type, bubbles, cancelable);

this.value = value;
this.triggerEvent = triggerEvent;
}

//--------------------------------------------------------------------------
//
// Properties
//
//--------------------------------------------------------------------------

//----------------------------------
// value
//----------------------------------

/**
* The value of the NumericStepper control when the event was dispatched.
*/
public var value:Date;

//----------------------------------
// triggerEvent
//----------------------------------

/**
* If the value is changed in response to a user action,
* this property contains a value indicating the type of input action.
* The value is either <code>InteractionInputType.MOUSE</code>
* or <code>InteractionInputType.KEYBOARD</code>.
*/
public var triggerEvent:Event;


//--------------------------------------------------------------------------
//
// Overridden methods: Event
//
//--------------------------------------------------------------------------

/**
* @private
*/
override public function clone():Event
{
return new CustomStepperEvent(type, bubbles, cancelable, value);
}
}
}


Main code of the CustomStepper class.
CustomStepper.as
////////////////////////////////////////////////////////////////////////////////
//
// ADOBE SYSTEMS INCORPORATED
// Copyright 2003-2007 Adobe Systems Incorporated
// All Rights Reserved.
//
// NOTICE: Adobe permits you to use, modify, and distribute this file
// in accordance with the terms of the license agreement accompanying it.
//
// Updated by Jeremy Pyne <jeremy pyne at gmail dot com>
// Changed stepper value to a Date for use in Date and Time steppers.
////////////////////////////////////////////////////////////////////////////////

package
{

import flash.display.DisplayObject;
import flash.events.Event;
import flash.events.FocusEvent;
import flash.events.KeyboardEvent;
import flash.events.MouseEvent;
import flash.text.TextField;
import flash.text.TextLineMetrics;
import flash.ui.Keyboard;

import mx.controls.listClasses.BaseListData;
import mx.controls.listClasses.IDropInListItemRenderer;
import mx.controls.listClasses.IListItemRenderer;
import mx.core.FlexVersion;
import mx.core.IDataRenderer;
import mx.core.IIMESupport;
import mx.core.UIComponent;
import mx.core.UITextField;
import mx.core.mx_internal;
import mx.events.FlexEvent;
import mx.formatters.DateFormatter;
import mx.managers.IFocusManager;
import mx.managers.IFocusManagerComponent;
import mx.styles.StyleProxy;


use namespace mx_internal;

//--------------------------------------
// Events
//--------------------------------------

/**
* Dispatched when the value of the NumberStepper control changes
* as a result of user interaction.
*
* @eventType mx.events.CustomStepperEvent.CHANGE
*/
[Event(name="change", type="hines.CustomStepperEvent")]

/**
* Dispatched when the <code>data</code> property changes.
*
* <p>When you use a component as an item renderer,
* the <code>data</code> property contains the data to display.
* You can listen for this event and update the component
* when the <code>data</code> property changes.</p>
*
* @eventType mx.events.FlexEvent.DATA_CHANGE
*/
[Event(name="dataChange", type="mx.events.FlexEvent")]

//--------------------------------------
// Styles
//--------------------------------------

/**
* Color of text in the component, including the component label.
*
* @default 0x0B333C
*/
[Style(name="color", type="uint", format="Color", inherit="yes")]

/**
* Color of text in the component if it is disabled.
*
* @default 0xAAB3B3
*/
[Style(name="disabledColor", type="uint", format="Color", inherit="yes")]

/**
* Sets the <code>antiAliasType</code> property of internal TextFields. The possible values are
* <code>"normal"</code> (<code>flash.text.AntiAliasType.NORMAL</code>)
* and <code>"advanced"</code> (<code>flash.text.AntiAliasType.ADVANCED</code>).
*
* <p>The default value is <code>"advanced"</code>, which enables advanced anti-aliasing for the font.
* Set to <code>"normal"</code> to disable the advanced anti-aliasing.</p>
*
* <p>This style has no effect for system fonts.</p>
*
* <p>This style applies to all the text in a TextField subcontrol;
* you cannot apply it to some characters and not others.</p>

* @default "advanced"
*
* @see flash.text.TextField
* @see flash.text.AntiAliasType
*/
[Style(name="fontAntiAliasType", type="String", enumeration="normal,advanced", inherit="yes")]

/**
* Name of the font to use.
* Unlike in a full CSS implementation,
* comma-separated lists are not supported.
* You can use any font family name.
* If you specify a generic font name,
* it is converted to an appropriate device font.
*
* @default "Verdana"
*/
[Style(name="fontFamily", type="String", inherit="yes")]

/**
* Sets the <code>gridFitType</code> property of internal TextFields that represent text in Flex controls.
* The possible values are <code>"none"</code> (<code>flash.text.GridFitType.NONE</code>),
* <code>"pixel"</code> (<code>flash.text.GridFitType.PIXEL</code>),
* and <code>"subpixel"</code> (<code>flash.text.GridFitType.SUBPIXEL</code>).
*
* <p>This property only applies when you are using an embedded font
* and the <code>fontAntiAliasType</code> property
* is set to <code>"advanced"</code>.</p>
*
* <p>This style has no effect for system fonts.</p>
*
* <p>This style applies to all the text in a TextField subcontrol;
* you can't apply it to some characters and not others.</p>
*
* @default "pixel"
*
* @see flash.text.TextField
* @see flash.text.GridFitType
*/
[Style(name="fontGridFitType", type="String", enumeration="none,pixel,subpixel", inherit="yes")]

/**
* Sets the <code>sharpness</code> property of internal TextFields that represent text in Flex controls.
* This property specifies the sharpness of the glyph edges. The possible values are Numbers
* from -400 through 400.
*
* <p>This property only applies when you are using an embedded font
* and the <code>fontAntiAliasType</code> property
* is set to <code>"advanced"</code>.</p>
*
* <p>This style has no effect for system fonts.</p>
*
* <p>This style applies to all the text in a TextField subcontrol;
* you can't apply it to some characters and not others.</p>
*
* @default 0
*
* @see flash.text.TextField
*/
[Style(name="fontSharpness", type="Number", inherit="yes")]

/**
* Height of the text, in pixels.
*
* The default value is 10 for all controls except the ColorPicker control.
* For the ColorPicker control, the default value is 11.
*/
[Style(name="fontSize", type="Number", format="Length", inherit="yes")]

/**
* Determines whether the text is italic font.
* Recognized values are <code>"normal"</code> and <code>"italic"</code>.
*
* @default "normal"
*/
[Style(name="fontStyle", type="String", enumeration="normal,italic", inherit="yes")]

/**
* Sets the <code>thickness</code> property of internal TextFields that represent text in Flex controls.
* This property specifies the thickness of the glyph edges.
* The possible values are Numbers from -200 to 200.
*
* <p>This property only applies when you are using an embedded font
* and the <code>fontAntiAliasType</code> property
* is set to <code>"advanced"</code>.</p>
*
* <p>This style has no effect on system fonts.</p>
*
* <p>This style applies to all the text in a TextField subcontrol;
* you can't apply it to some characters and not others.</p>
*
* @default 0
*
* @see flash.text.TextField
*/
[Style(name="fontThickness", type="Number", inherit="yes")]

/**
* Determines whether the text is boldface.
* Recognized values are <code>normal</code> and <code>bold</code>.
* The default value for Button controls is <code>bold</code>.
* The default value for all other controls is <code>normal</code>.
*/
[Style(name="fontWeight", type="String", enumeration="normal,bold", inherit="yes")]

/**
* A Boolean value that indicates whether kerning
* is enabled (<code>true</code>) or disabled (<code>false</code>).
* Kerning adjusts the gap between certain character pairs
* to improve readability, and should be used only when necessary,
* such as with headings in large fonts.
* Kerning is supported for embedded fonts only.
* Certain fonts, such as Verdana, and monospaced fonts,
* such as Courier New, do not support kerning.
*
* @default false
*/
[Style(name="kerning", type="Boolean", inherit="yes")]

/**
* The number of additional pixels to appear between each character.
* A positive value increases the character spacing beyond the normal spacing,
* while a negative value decreases it.
*
* @default 0
*/
[Style(name="letterSpacing", type="Number", inherit="yes")]

/**
* Alignment of text within a container.
* Possible values are <code>"left"</code>, <code>"right"</code>,
* or <code>"center"</code>.
*
* <p>The default value for most components is <code>"left"</code>.
* For the FormItem component,
* the default value is <code>"right"</code>.
* For the Button, LinkButton, and AccordionHeader components,
* the default value is <code>"center"</code>, and
* this property is only recognized when the
* <code>labelPlacement</code> property is set to <code>"left"</code> or
* <code>"right"</code>.
* If <code>labelPlacement</code> is set to <code>"top"</code> or
* <code>"bottom"</code>, the text and any icon are centered.</p>
*/
[Style(name="textAlign", type="String", enumeration="left,center,right", inherit="yes")]

/**
* Determines whether the text is underlined.
* Possible values are <code>"none"</code> and <code>"underline"</code>.
*
* @default "none"
*/
[Style(name="textDecoration", type="String", enumeration="none,underline", inherit="yes")]

/**
* Offset of first line of text from the left side of the container, in pixels.
*
* @default 0
*/
[Style(name="textIndent", type="Number", format="Length", inherit="yes")]

/**
* Number of pixels between the component's left border
* and the left edge of its content area.
* <p>The default value is 0.</p>
* <p>The default value for a Button control is 10.</p>
* <p>The default value for the ComboBox control is 5.</p>
* <p>The default value for the Form container is 16.</p>
* <p>The default value for the Tree control is 2.</p>
*/
[Style(name="paddingLeft", type="Number", format="Length", inherit="no")]

/**
* Number of pixels between the component's right border
* and the right edge of its content area.
* <p>The default value is 0.</p>
* <p>The default value for a Button control is 10.</p>
* <p>The default value for the ComboBox control is 5.</p>
* <p>The default value for the Form container is 16.</p>
*/
[Style(name="paddingRight", type="Number", format="Length", inherit="no")]

/**
* Additional vertical space between lines of text.
*
* <p>The default value is 2.</p>
* <p>The default value for the ComboBox control is 0.</p>
*/
[Style(name="leading", type="Number", format="Length", inherit="yes")]


/**
* The color for the icon in a skin.
* For example, this style is used by the CheckBoxIcon skin class
* to draw the check mark for a CheckBox control,
* by the ComboBoxSkin class to draw the down arrow of the ComboBox control,
* and by the DateChooserMonthArrowSkin skin class to draw the month arrow
* for the DateChooser control.
*
* The default value depends on the component class;
* if it is not overridden by the class, the default value is <code>0x111111</code>.
*/
[Style(name="iconColor", type="uint", format="Color", inherit="yes")]

/**
* The color for the icon in a disabled skin.
* For example, this style is used by the CheckBoxIcon skin class
* to draw the check mark for a disabled CheckBox control,
* by the ComboBoxSkin class to draw the down arrow of a disabled ComboBox control,
* and by the DateChooserMonthArrowSkin skin class to draw the month arrow
* for a disabled DateChooser control.
*
* The default value depends on the component class;
* if it is not overridden by the class, the default value is <code>0x999999</code>.
*/
[Style(name="disabledIconColor", type="uint", format="Color", inherit="yes")]
/**
* Specifies the alpha transparency value of the focus skin.
*
* @default 0.4
*/
[Style(name="focusAlpha", type="Number", inherit="no")]

/**
* Specifies which corners of the focus rectangle should be rounded.
* This value is a space-separated String that can contain any
* combination of <code>"tl"</code>, <code>"tr"</code>, <code>"bl"</code>
* and <code>"br"</code>.
* For example, to specify that the right side corners should be rounded,
* but the left side corners should be square, use <code>"tr br"</code>.
* The <code>cornerRadius</code> style property specifies
* the radius of the rounded corners.
* The default value depends on the component class; if not overridden for
* the class, default value is <code>"tl tr bl br"</code>.
*/
[Style(name="focusRoundedCorners", type="String", inherit="no")]

/**
* Alpha level of the color defined by the <code>backgroundColor</code>
* property, of the image or SWF file defined by the <code>backgroundImage</code>
* style.
* Valid values range from 0.0 to 1.0. For most controls, the default value is 1.0,
* but for ToolTip controls, the default value is 0.95 and for Alert controls, the default value is 0.9.
*
* @default 1.0
*/
[Style(name="backgroundAlpha", type="Number", inherit="no")]

/**
* Background color of a component.
* You can have both a <code>backgroundColor</code> and a
* <code>backgroundImage</code> set.
* Some components do not have a background.
* The DataGrid control ignores this style.
* The default value is <code>undefined</code>, which means it is not set.
* If both this style and the <code>backgroundImage</code> style
* are <code>undefined</code>, the component has a transparent background.
*
* <p>For the Application container, this style specifies the background color
* while the application loads, and a background gradient while it is running.
* Flex calculates the gradient pattern between a color slightly darker than
* the specified color, and a color slightly lighter than the specified color.</p>
*
* <p>The default skins of most Flex controls are partially transparent. As a result, the background color of
* a container partially "bleeds through" to controls that are in that container. You can avoid this by setting the
* alpha values of the control's <code>fillAlphas</code> property to 1, as the following example shows:
* <pre>
* <mx:<i>Container</i> backgroundColor="0x66CC66"/>
* <mx:<i>ControlName</i> ... fillAlphas="[1,1]"/>
* </mx:<i>Container</i>></pre>
* </p>
*/
[Style(name="backgroundColor", type="uint", format="Color", inherit="no")]

/**
* Background color of the component when it is disabled.
* The global default value is <code>undefined</code>.
* The default value for List controls is <code>0xDDDDDD</code> (light gray).
* If a container is disabled, the background is dimmed, and the degree of
* dimming is controlled by the <code>disabledOverlayAlpha</code> style.
*/
[Style(name="backgroundDisabledColor", type="uint", format="Color", inherit="yes")]

/**
* Background image of a component. This can be an absolute or relative
* URL or class. You can either have both a <code>backgroundColor</code> and a
* <code>backgroundImage</code> set at the same time. The background image is displayed
* on top of the background color.
* The default value is <code>undefined</code>, meaning "not set".
* If this style and the <code>backgroundColor</code> style are undefined,
* the component has a transparent background.
*
* <p>The default skins of most Flex controls are partially transparent. As a result, the background image of
* a container partially "bleeds through" to controls that are in that container. You can avoid this by setting the
* alpha values of the control's <code>fillAlphas</code> property to 1, as the following example shows:
* <pre>
* <mx:<i>Container</i> backgroundColor="0x66CC66"/>
* <mx:<i>ControlName</i> ... fillAlphas="[1,1]"/>
* </mx:<i>Container</i>></pre>
* </p>
*/
[Style(name="backgroundImage", type="Object", format="File", inherit="no")]

/**
* Scales the image specified by <code>backgroundImage</code>
* to different percentage sizes.
* A value of <code>"100%"</code> stretches the image
* to fit the entire component.
* To specify a percentage value, you must include the percent sign (%).
* The default for the Application container is <code>100%</code>.
* The default value for all other containers is <code>auto</code>, which maintains
* the original size of the image.
*/
[Style(name="backgroundSize", type="String", inherit="no")]

/**
* Color of the border.
* The default value depends on the component class;
* if not overridden for the class, the default value is <code>0xB7BABC</code>.
*/
[Style(name="borderColor", type="uint", format="Color", inherit="no")]

/**
* Bounding box sides.
* A space-delimited String that specifies the sides of the border to show.
* The String can contain <code>"left"</code>, <code>"top"</code>,
* <code>"right"</code>, and <code>"bottom"</code> in any order.
* The default value is <code>"left top right bottom"</code>,
* which shows all four sides.
*
* This style is only used when borderStyle is <code>"solid"</code>.
*/
[Style(name="borderSides", type="String", inherit="no")]

/**
* The border skin class of the component.
* The mx.skins.halo.HaloBorder class is the default value for all components
* that do not explicitly set their own default.
* The Panel container has a default value of mx.skins.halo.PanelSkin.
* To determine the default value for a component, see the default.css file.
*
* @default mx.skins.halo.HaloBorder
*/
[Style(name="borderSkin", type="Class", inherit="no")]

/**
* Bounding box style.
* The possible values are <code>"none"</code>, <code>"solid"</code>,
* <code>"inset"</code>, and <code>"outset"</code>.
* The default value depends on the component class;
* if not overridden for the class, the default value is <code>"inset"</code>.
* The default value for most Containers is <code>"none"</code>.
*/
[Style(name="borderStyle", type="String", enumeration="inset,outset,solid,none", inherit="no")]

/**
* Bounding box thickness.
* Only used when <code>borderStyle</code> is set to <code>"solid"</code>.
*
* @default 1
*/
[Style(name="borderThickness", type="Number", format="Length", inherit="no")]

/**
* Radius of component corners.
* The default value depends on the component class;
* if not overriden for the class, the default value is 0.
* The default value for ApplicationControlBar is 5.
*/
[Style(name="cornerRadius", type="Number", format="Length", inherit="no")]

/**
* Boolean property that specifies whether the component has a visible
* drop shadow.
* This style is used with <code>borderStyle="solid"</code>.
* The default value is <code>false</code>.
*
* <p><b>Note:</b> For drop shadows to appear on containers, set
* <code>backgroundColor</code> or <code>backgroundImage</code> properties.
* Otherwise, the shadow appears behind the container because
* the default background of a container is transparent.</p>
*/
[Style(name="dropShadowEnabled", type="Boolean", inherit="no")]

/**
* Color of the drop shadow.
*
* @default 0x000000
*/
[Style(name="dropShadowColor", type="uint", format="Color", inherit="yes")]

/**
* Direction of the drop shadow.
* Possible values are <code>"left"</code>, <code>"center"</code>,
* and <code>"right"</code>.
*
* @default "center"
*/
[Style(name="shadowDirection", type="String", enumeration="left,center,right", inherit="no")]

/**
* Distance of the drop shadow.
* If the property is set to a negative value, the shadow appears above the component.
*
* @default 2
*/
[Style(name="shadowDistance", type="Number", format="Length", inherit="no")]

/**
* Name of the class to use as the default skin for the down arrow.
*
* @default mx.skins.halo.NumberStepperDownSkin
*/
[Style(name="downArrowSkin", type="Class", inherit="no", states="up, over, down, disabled")]

/**
* Name of the class to use as the skin for the Down arrow
* when the arrow is disabled.
*
* @default mx.skins.halo.NumberStepperDownSkin
*/
[Style(name="downArrowDisabledSkin", type="Class", inherit="no")]

/**
* Name of the class to use as the skin for the Down arrow
* when the arrow is enabled and a user presses the mouse button over the arrow.
*
* @default mx.skins.halo.NumberStepperDownSkin
*/
[Style(name="downArrowDownSkin", type="Class", inherit="no")]

/**
* Name of the class to use as the skin for the Down arrow
* when the arrow is enabled and the mouse pointer is over the arrow.
*
* @default mx.skins.halo.NumberStepperDownSkin
*/
[Style(name="downArrowOverSkin", type="Class", inherit="no")]

/**
* Name of the class to use as the skin for the Down arrow
* when the arrow is enabled and the mouse pointer is not on the arrow.
* There is no default.
*/
[Style(name="downArrowUpSkin", type="Class", inherit="no")]

/**
* Alphas used for the highlight fill of controls.
*
* @default [ 0.3, 0.0 ]
*/
[Style(name="highlightAlphas", type="Array", arrayType="Number", inherit="no")]

/**
* Name of the class to use as the default skin for the up arrow.
*
* @default mx.skins.halo.NumberStepperUpSkin
*/
[Style(name="upArrowSkin", type="Class", inherit="no", states="up, over, down, disabled")]

/**
* Name of the class to use as the skin for the Up arrow
* when the arrow is disabled.
*
* @default mx.skins.halo.NumberStepperUpSkin
*/
[Style(name="upArrowDisabledSkin", type="Class", inherit="no")]

/**
* Name of the class to use as the skin for the Up arrow
* when the arrow is enabled and a user presses the mouse button over the arrow.
*
* @default mx.skins.halo.NumberStepperUpSkin
*/
[Style(name="upArrowDownSkin", type="Class", inherit="no")]

/**
* Name of the class to use as the skin for the Up arrow
* when the arrow is enabled and the mouse pointer is over the arrow.
*
* @default mx.skins.halo.NumberStepperUpSkin
*/
[Style(name="upArrowOverSkin", type="Class", inherit="no")]

/**
* Name of the class to use as the skin for the Up arrow
* when the arrow is enabled and the mouse pointer is not on the arrow.
*
* @default mx.skins.halo.NumberStepperUpSkin
*/
[Style(name="upArrowUpSkin", type="Class", inherit="no")]

//--------------------------------------
// Other metadata
//--------------------------------------

[DefaultBindingProperty(source="value", destination="value")]

[DefaultTriggerEvent("change")]

/**
* This is a custom stepper component based off the NumberStepper component in the flex framwork.
* It is a compleate duplication of the internal class as the changes are to complex and core to be changed with overrides.
* In short the value propertie was changed from a number to a Date and all associated calculations as well such as steping.
* There are also additinal properties to configure the display format and
* <pre>
* <mx:CustomStepper
* <strong>Properties</strong>
* format=L:NN A|MM/DD/YYYY|Custom
* prependDate=false
* stepType=Date Property
* </pre>
*
* The formatedValue and selectedDate properties were also added witch return the value in different formats.
*
* @author Jeremy Pyne
*
* The NumberStepper control lets the user select
* a number from an ordered set.
* The NumberStepper control consists of a single-line
* input text field and a pair of arrow buttons
* for stepping through the possible values.
* The Up Arrow and Down Arrow keys also cycle through the values.
*
* <p>The NumberStepper control has the following default characteristics:</p>
* <table class="innertable">
* <tr>
* <th>Characteristic</th>
* <th>Description</th>
* </tr>
* <tr>
* <td>Default size</td>
* <td>Wide enough to display the maximum number of digits used by the control</td>
* </tr>
* <tr>
* <td>Minimum size</td>
* <td>Based on the size of the text.</td>
* </tr>
* <tr>
* <td>Maximum size</td>
* <td>Undefined</td>
* </tr>
* </table>
*
* @mxml
*
* The <code><mx:NumberStepper></code> tag inherits all of the tag
* attributes of its superclass, and adds the following tag attributes:
*
* <pre>
* <mx:NumberStepper
* <strong>Properties</strong>
* imeMode="null"
* maxChars="10"
* maximum="10"
* minimum="0"
* stepSize="1"
* value="0"
*
* <strong>Styles</strong>
* backgroundAlpha="1.0"
* backgroundColor="undefined"
* backgroundImage="undefined"
* backgroundSize="auto"
* borderColor="0xAAB3B3"
* borderSides="left top right bottom"
* borderSkin="HaloBorder"
* borderStyle="inset"
* borderThickness="1"
* color="0x0B333C"
* cornerRadius="0"
* disabledColor="0xAAB3B3"
* disabledIconColor="0x999999"
* downArrowDisabledSkin="NumberStepperDownSkin"
* downArrowDownSkin="NumberStepperDownSkin"
* downArrowOverSkin="NumberStepperDownSkin"
* downArrowUpSkin="NumberStepperDownSkin"
* dropShadowEnabled="false"
* dropShadowColor="0x000000"
* focusAlpha="0.5"
* focusRoundedCorners="tl tr bl br"
* fontAntiAliasType="advanced"
* fontFamily="Verdana"
* fontGridFitType="pixel"
* fontSharpness="0"
* fontSize="10"
* fontStyle="normal|italic"
* fontThickness="0"
* fontWeight="normal|bold"
* highlightAlphas="[0.3,0.0]"
* iconColor="0x111111"
* leading="2"
* paddingLeft="0"
* paddingRight="0"
* shadowDirection="center"
* shadowDistance="2"
* textAlign="left|center|right"
* textDecoration="none|underline"
* textIndent="0"
* upArrowDisabledSkin="NumberStepperUpSkin"
* upArrowDownSkin="NumberStepperUpSkin"
* upArrowOverSkin="NumberStepperUpSkin"
* upArrowUpSkin="NumberStepperUpSkin"
*
* <strong>Events</strong>
* change="<i>No default</i>"
* dataChange="<i>No default</i>"
* />
* </pre>
*
* @includeExample examples/NumberStepperExample.mxml
*
*/
public class CustomStepper extends UIComponent
implements IDataRenderer, IDropInListItemRenderer,
IFocusManagerComponent, IIMESupport,
IListItemRenderer
{
//--------------------------------------------------------------------------
//
// Constructor
//
//--------------------------------------------------------------------------

/**
* Constructor.
*/
public function CustomStepper(type:String = "")
{
super();

tabChildren = true;

switch(type)
{
case TYPETIME:
stepType = "hours";
format = "L:NN A";
minimum.hours = 0;

maximum = new Date(0);
maximum.hours = 23;

var time:Date = new Date();
time.fullYear = 1969;
time.month = time.date = 0;
value = time;

prependDate = true;

break;
case TYPEDATE:
stepType = "date";
format="MM/DD/YYYY";

minimum.date += 1;
minimum.hours = 0;

var time:Date = new Date();
time.hours = time.minutes = time.seconds = time.seconds = 0;
value = time;
break
default:
break;
}
}

//--------------------------------------------------------------------------
//
// Variables
//
//--------------------------------------------------------------------------

/**
* @private
*/
mx_internal var inputField:TextInput;

/**
* @private
*/
mx_internal var nextButton:Button;

/**
* @private
*/
mx_internal var prevButton:Button;

/**
* @private
* Flag that will block default data/listData behavior
*/
private var valueSet:Boolean;

/**
* Formatter used to render the text.
*/
private var dateFormatter:DateFormatter = new DateFormatter();

//----------------------------------
// format
//----------------------------------

[Bindable("formatChanged")]
[Inspectable(category="General", defaultValue="")]
/**
* Format of the text in the input box.
*
* @default
*/
public function get format():String
{
return dateFormatter.formatString;
}

public function set format(value:String):void
{
dateFormatter.formatString = value;

dispatchEvent(new Event("formatChanged"));
}

public var prependDate:Boolean = false;

/**
* Step backward one step.
*/
private static const STEPPREVIOUS:Number = -1;
/**
* Step forward one step.
*/
private static const STEPNEXT:Number = 1;

/**
* Increment or decrement the passed in value by one step bassed on the stepType and stepSize.
*/
private function step(curValue:Date, stepDirection:Number = STEPNEXT):Date
{
if(curValue == null)
return null;

var newValue:Date = new Date(curValue.time)
newValue[stepType] += stepSize * stepDirection;
return newValue;
}

/**
* Make this control into a date stepper.
*/
public static const TYPEDATE:String = "date";
/**
* Make this control into a time stepper.
*/
public static const TYPETIME:String = "time";

[Bindable("change")]
[Bindable("valueCommit")]
[Inspectable(category="General", defaultValue="0")]
/**
* Get the properly formated value as dictated by format.
*/
private function get formatedValue():String
{
return dateFormatter.format(value);
}
//--------------------------------------------------------------------------
//
// Overridden properties
//
//--------------------------------------------------------------------------

//----------------------------------
// baselinePosition
//----------------------------------

/**
* @private
* The baselinePosition of a NumberStepper is calculated
* for its inputField.
*/
override public function get baselinePosition():Number
{
if (FlexVersion.compatibilityVersion < FlexVersion.VERSION_3_0)
return inputField ? inputField.baselinePosition : NaN;

if (!validateBaselinePosition())
return NaN;

return inputField.y + inputField.baselinePosition;
}

//----------------------------------
// enabled
//----------------------------------

/**
* @private
*/
private var enabledChanged:Boolean = false;

[Inspectable(category="General", enumeration="true,false", defaultValue="true")]

/**
* @private
*/
override public function set enabled(value:Boolean):void
{
super.enabled = value;
enabledChanged = true;

invalidateProperties();
}

/**
* @private
*/
override public function get enabled():Boolean
{
return super.enabled;
}

//----------------------------------
// tabIndex
//----------------------------------

/**
* @private
* Storage for the tabIndex property.
*/
private var _tabIndex:int = -1;

/**
* @private
*/
private var tabIndexChanged:Boolean = false;

/**
* @private
* Tab order in which the control receives the focus when navigating
* with the Tab key.
*
* @default -1
* @tiptext tabIndex of the component
* @helpid 3198
*/
override public function get tabIndex():int
{
return _tabIndex;
}

/**
* @private
*/
override public function set tabIndex(value:int):void
{
if (value == _tabIndex)
return;

_tabIndex = value;
tabIndexChanged = true;

invalidateProperties();
}


//--------------------------------------------------------------------------
//
// Properties
//
//--------------------------------------------------------------------------

//----------------------------------
// data
//----------------------------------

/**
* @private
* Storage for the data property.
*/
private var _data:Object;

[Bindable("dataChange")]
[Inspectable(environment="none")]

/**
* The <code>data</code> property lets you pass a value to the component
* when you use it in an item renderer or item editor.
* You typically use data binding to bind a field of the <code>data</code>
* property to a property of this component.
*
* <p>When you use the control as a drop-in item renderer or drop-in
* item editor, Flex automatically writes the current value of the item
* to the <code>value</code> property of this control.</p>
*
* @default null
* @see mx.core.IDataRenderer
*/
public function get data():Object
{
if (!_listData)
data = this.value;

return _data;
}

/**
* @private
*/
public function set data(value:Object):void
{
_data = value;

if (!valueSet)
{
this.value = _listData ? _listData.label as Date : _data as Date;
valueSet = false;
}

dispatchEvent(new FlexEvent(FlexEvent.DATA_CHANGE));
}

//----------------------------------
// downArrowStyleFilters
//----------------------------------

/**
* Set of styles to pass from the NumberStepper to the down arrow.
* @see mx.styles.StyleProxy
*/
protected function get downArrowStyleFilters():Object
{
return _downArrowStyleFilters;
}

private static var _downArrowStyleFilters:Object =
{
"cornerRadius" : "cornerRadius",
"highlightAlphas" : "highlightAlphas",
"downArrowUpSkin" : "downArrowUpSkin",
"downArrowOverSkin" : "downArrowOverSkin",
"downArrowDownSkin" : "downArrowDownSkin",
"downArrowDisabledSkin" : "downArrowDisabledSkin",
"downArrowSkin" : "downArrowSkin",
"repeatDelay" : "repeatDelay",
"repeatInterval" : "repeatInterval"
};

//----------------------------------
// imeMode
//----------------------------------

/**
* @private
*/
private var _imeMode:String = null;

[Inspectable(defaultValue="")]

/**
* Specifies the IME (Input Method Editor) mode.
* The IME enables users to enter text in Chinese, Japanese, and Korean.
* Flex sets the specified IME mode when the control gets the focus
* and sets it back to previous value when the control loses the focus.
*
* <p>The flash.system.IMEConversionMode class defines constants for the
* valid values for this property.
* You can also specify <code>null</code> to specify no IME.</p>
*
* @see flash.system.IMEConversionMode
*
* @default null
*/
public function get imeMode():String
{
return _imeMode;
}

/**
* @private
*/
public function set imeMode(value:String):void
{
_imeMode = value;

if (inputField)
inputField.imeMode = _imeMode;
}

//----------------------------------
// inputFieldStyleFilters
//----------------------------------

/**
* Set of styles to pass from the NumberStepper to the input field.
* @see mx.styles.StyleProxy
*/
protected function get inputFieldStyleFilters():Object
{
return _inputFieldStyleFilters;
}

private static var _inputFieldStyleFilters:Object =
{
"backgroundAlpha" : "backgroundAlpha",
"backgroundColor" : "backgroundColor",
"backgroundImage" : "backgroundImage",
"backgroundDisabledColor" : "backgroundDisabledColor",
"backgroundSize" : "backgroundSize",
"borderAlpha" : "borderAlpha",
"borderColor" : "borderColor",
"borderSides" : "borderSides",
"borderSkin" : "borderSkin",
"borderStyle" : "borderStyle",
"borderThickness" : "borderThickness",
"dropShadowColor" : "dropShadowColor",
"dropShadowEnabled" : "dropShadowEnabled",
"embedFonts" : "embedFonts",
"focusAlpha" : "focusAlpha",
"focusBlendMode" : "focusBlendMode",
"focusRoundedCorners" : "focusRoundedCorners",
"focusThickness" : "focusThickness",
"paddingLeft" : "paddingLeft",
"paddingRight" : "paddingRight",
"shadowDirection" : "shadowDirection",
"shadowDistance" : "shadowDistance",
"textDecoration" : "textDecoration"
};

//----------------------------------
// listData
//----------------------------------

/**
* @private
* Storage for the listData property.
*/
private var _listData:BaseListData;

[Bindable("dataChange")]
[Inspectable(environment="none")]

/**
* When a component is used as a drop-in item renderer or drop-in
* item editor, Flex initializes the <code>listData</code> property
* of the component with the appropriate data from the List control.
* The component can then use the <code>listData</code> property
* to initialize the <code>data</code> property of the drop-in
* item renderer or drop-in item editor.
*
* <p>You do not set this property in MXML or ActionScript;
* Flex sets it when the component is used as a drop-in item renderer
* or drop-in item editor.</p>
*
* @default null
* @see mx.controls.listClasses.IDropInListItemRenderer
*/
public function get listData():BaseListData
{
return _listData;
}

/**
* @private
*/
public function set listData(value:BaseListData):void
{
_listData = value;
}

//----------------------------------
// maxChars
//----------------------------------

/**
* @private
* Storage for the maxChars property.
*/
private var _maxChars:int = 0;

/**
* @private
*/
private var maxCharsChanged:Boolean = false;

[Bindable("maxCharsChanged")]

/**
* The maximum number of characters that can be entered in the field.
* A value of 0 means that any number of characters can be entered.
*
* @default 0
*/
public function get maxChars():int
{
return _maxChars;
}

public function set maxChars(value:int):void
{
if (value == _maxChars)
return;

_maxChars = value;
maxCharsChanged = true;

invalidateProperties();

dispatchEvent(new Event("maxCharsChanged"));
}

//----------------------------------
// maximum
//----------------------------------

/**
* @private
* Storage for maximum property.
* Maximum 32 bit date.
*/
private var _maximum:Date = new Date(2038, 1, 19);

[Bindable("maximumChanged")]
[Inspectable(category="General", defaultValue="")]

/**
* Maximum value of the NumberStepper.
* The maximum can be any number, including a fractional value.
*
* @default 10
*/
public function get maximum():Date
{
return _maximum;
}

public function set maximum(value:Date):void
{
_maximum = value;

// To validate the value as min/max/stepsize has changed.
if (!valueChanged)
{
this.value = this.value;
valueSet = false;
}

dispatchEvent(new Event("maximumChanged"));
}

//----------------------------------
// minimum
//----------------------------------

/**
* @private
* Storage for minimum property.
*/
private var _minimum:Date = new Date(0);

[Bindable("minimumChanged")]
[Inspectable(category="General", defaultValue="")]

/**
* Minimum value of the NumberStepper.
* The minimum can be any number, including a fractional value.
*
* @default 0
*/
public function get minimum():Date
{
return _minimum;
}

public function set minimum(value:Date):void
{
_minimum = value;

// To validate the value as min/max/stepsize has changed.
if (!valueChanged)
{
this.value = this.value;
valueSet = false;
}

dispatchEvent(new Event("minimumChanged"));
}

//----------------------------------
// nextValue
//----------------------------------

/**
* @private
* Storage for the nextValue property.
*/
private var _nextValue:Date = null;

/**
* The value that is one step larger than the current <code>value</code>
* property and not greater than the <code>maximum</code> property value.
*/
public function get nextValue():Date
{
if (checkRange(step(value, STEPNEXT)))
_nextValue = step(value, STEPNEXT);

return _nextValue;
}

//----------------------------------
// previousValue
//----------------------------------
/**
* @private
* Storage for the previousValue property.
*/
private var _previousValue:Date = null;

/**
* The value that is one step smaller than the current <code>value</code>
* property and not smaller than the <code>maximum</code> property value.
*/
public function get previousValue():Date
{
if (checkRange(step(_value, STEPPREVIOUS)))
_previousValue = step(value, STEPPREVIOUS);

return _previousValue;
}

//----------------------------------
// stepSize
//----------------------------------

/**
* @private
* Storage for the stepSize property.
*/
private var _stepSize:Number = 1;

[Bindable("stepSizeChanged")]
[Inspectable(category="General", defaultValue="1")]

/**
* Non-zero unit of change between values.
* The <code>value</code> property must be a multiple of this number.
*
* @default 1
*/
public function get stepSize():Number
{
return _stepSize;
}

/**
* @private
*/
public function set stepSize(value:Number):void
{
_stepSize = value;

// To validate the value as min/max/stepsize has changed.
if (!valueChanged)
{
this.value = this.value;
valueSet = false;
}

dispatchEvent(new Event("stepSizeChanged"));
}

//----------------------------------
// stepType
//----------------------------------

/**
* @private
* Storage for the stepType property.
*/
private var _stepType:String = "date";

[Bindable("stepSizeChanged")]
[Inspectable(category="General", defaultValue="date")]

/**
* Date field to step.
* The <code>value</code> property must be a multiple of this number.
*
* @default 1
*/
public function get stepType():String
{
return _stepType;
}

/**
* @private
*/
public function set stepType(value:String):void
{
_stepType = value;

// To validate the value as min/max/stepsize has changed.
if (!valueChanged)
{
this.value = this.value;
valueSet = false;
}

dispatchEvent(new Event("stepSizeChanged"));
}

//----------------------------------
// upArrowStyleFilters
//----------------------------------

/**
* Set of styles to pass from the NumberStepper to the up arrow.
* @see mx.styles.StyleProxy
*/
protected function get upArrowStyleFilters():Object
{
return _upArrowStyleFilters;
}

private static var _upArrowStyleFilters:Object =
{
"cornerRadius" : "cornerRadius",
"highlightAlphas" : "highlightAlphas",
"upArrowUpSkin" : "upArrowUpSkin",
"upArrowOverSkin" : "upArrowOverSkin",
"upArrowDownSkin" : "upArrowDownSkin",
"upArrowDisabledSkin" : "upArrowDisabledSkin",
"upArrowSkin" : "upArrowSkin",
"repeatDelay" : "repeatDelay",
"repeatInterval" : "repeatInterval"
};

//----------------------------------
// value
//----------------------------------

/**
* @private
* Storage for the value property.
*/
private var _value:Date = new Date();

/**
* @private
* last value we send CHANGE for.
* _value will hold uncommitted values as well
*/
private var lastValue:Date = new Date();

/**
* @private
* Holds the value of the value property
* until it is committed in commitProperties().
*/
private var proposedValue:Date = new Date();

/**
* @private
* Keeps track of whether we need to update
* the value in commitProperties().
*/
private var valueChanged:Boolean = false;

[Bindable("change")]
[Bindable("valueCommit")]
[Inspectable(category="General", defaultValue="0")]

/**
* Current value displayed in the text area of the NumberStepper control.
* If a user enters number that is not a multiple of the
* <code>stepSize</code> property or is not in the range
* between the <code>maximum</code> and <code>minimum</code> properties,
* this property is set to the closest valid value.
*
* @default 0
*/
public function get value():Date
{
return valueChanged ? proposedValue : _value;
}

/**
* @private
*/
public function set value(value:Date):void
{
valueSet = true;

proposedValue = value;
valueChanged = true;

invalidateProperties();
invalidateSize();
}

[Bindable("change")]
[Bindable("valueCommit")]
[Inspectable(category="General", defaultValue="0")]
/**
* Alias value to selectedDate for drop in replacment or dateChoosers.
*/
public function get selectedDate():Date
{
return valueChanged ? proposedValue : _value;
}

/**
* @private
*/
public function set selectedDate(value:Date):void
{
valueSet = true;

proposedValue = value;
valueChanged = true;

invalidateProperties();
invalidateSize();
}

//--------------------------------------------------------------------------
//
// Overridden methods
//
//--------------------------------------------------------------------------

/**
* @private
*/
override protected function createChildren():void
{
super.createChildren();

if (!inputField)
{
inputField = new TextInput();

inputField.styleName = new StyleProxy(this, inputFieldStyleFilters);
inputField.focusEnabled = false;

// restrict to numbers - dashes - commas - decimals
inputField.restrict = "0-9\\- /:a-z";

inputField.maxChars = _maxChars;
inputField.text = dateFormatter.format(_value);
inputField.parentDrawsFocus = true;
inputField.imeMode = _imeMode;

inputField.addEventListener(FocusEvent.FOCUS_IN, inputField_focusInHandler);
inputField.addEventListener(FocusEvent.FOCUS_OUT, inputField_focusOutHandler);
inputField.addEventListener(KeyboardEvent.KEY_DOWN, inputField_keyDownHandler);
inputField.addEventListener(Event.CHANGE, inputField_changeHandler);

addChild(inputField);
}

if (!nextButton)
{
nextButton = new Button();
nextButton.styleName = new StyleProxy(this, upArrowStyleFilters);
nextButton.upSkinName = "upArrowUpSkin";
nextButton.overSkinName = "upArrowOverSkin";
nextButton.downSkinName = "upArrowDownSkin";
nextButton.disabledSkinName = "upArrowDisabledSkin";
nextButton.skinName = "upArrowSkin";
nextButton.upIconName = "";
nextButton.overIconName = "";
nextButton.downIconName = "";
nextButton.disabledIconName = "";

nextButton.focusEnabled = false;
nextButton.autoRepeat = true;

nextButton.addEventListener(MouseEvent.CLICK, buttonClickHandler);
nextButton.addEventListener(FlexEvent.BUTTON_DOWN, buttonDownHandler);

addChild(nextButton);
}

if (!prevButton)
{
prevButton = new Button();
prevButton.styleName = new StyleProxy(this, downArrowStyleFilters);
prevButton.upSkinName = "downArrowUpSkin";
prevButton.overSkinName = "downArrowOverSkin";
prevButton.downSkinName = "downArrowDownSkin";
prevButton.disabledSkinName = "downArrowDisabledSkin";
prevButton.skinName = "downArrowSkin";
prevButton.upIconName = "";
prevButton.overIconName = "";
prevButton.downIconName = "";
prevButton.disabledIconName = "";

prevButton.focusEnabled = false;
prevButton.autoRepeat = true;

prevButton.addEventListener(MouseEvent.CLICK, buttonClickHandler);
prevButton.addEventListener(FlexEvent.BUTTON_DOWN, buttonDownHandler);

addChild(prevButton);
}
}

/**
* @private
*/
override protected function commitProperties():void
{
super.commitProperties();

if (maxCharsChanged)
{
maxCharsChanged = false;
inputField.maxChars = _maxChars;
}

if (valueChanged)
{
valueChanged = false;

setValue(proposedValue, false);
}

if (enabledChanged)
{
enabledChanged = false;

prevButton.enabled = enabled;
nextButton.enabled = enabled;
inputField.enabled = enabled;
}

if (tabIndexChanged)
{
inputField.tabIndex = _tabIndex;

tabIndexChanged = false;
}

}

/**
* @private
* Return the preferred sizes of the stepper.
*/
override protected function measure():void
{
super.measure();

var widestNumber:Date = dateFormatter.format(minimum).length >
dateFormatter.format(maximum).length ?
minimum :
maximum;

var lineMetrics:TextLineMetrics = measureText(dateFormatter.format(checkValidValue(widestNumber)));

var textHeight:Number = inputField.getExplicitOrMeasuredHeight();
var buttonHeight:Number = prevButton.getExplicitOrMeasuredHeight() +
nextButton.getExplicitOrMeasuredHeight();

var h:Number = Math.max(textHeight, buttonHeight);
h = Math.max(DEFAULT_MEASURED_MIN_HEIGHT, h);

var textWidth:Number = lineMetrics.width + UITextField.TEXT_WIDTH_PADDING;
var buttonWidth:Number = Math.max(prevButton.getExplicitOrMeasuredWidth(),
nextButton.getExplicitOrMeasuredWidth());

var w:Number = textWidth + buttonWidth + 20;
w = Math.max(DEFAULT_MEASURED_MIN_WIDTH, w);

measuredMinWidth = DEFAULT_MEASURED_MIN_WIDTH;
measuredMinHeight = DEFAULT_MEASURED_MIN_HEIGHT;

measuredWidth = w;
measuredHeight = h;
}

/**
* @private
* Place the buttons to the right of the text field.
*/
override protected function updateDisplayList(unscaledWidth:Number,
unscaledHeight:Number):void
{
super.updateDisplayList(unscaledWidth, unscaledHeight);

var w:Number = nextButton.getExplicitOrMeasuredWidth();
var h:Number = Math.round(unscaledHeight / 2);
var h2:Number = unscaledHeight - h;

nextButton.x = unscaledWidth - w;
nextButton.y = 0;
nextButton.setActualSize(w, h2);

prevButton.x = unscaledWidth - w;
prevButton.y = unscaledHeight - h;
prevButton.setActualSize(w, h);

inputField.setActualSize(unscaledWidth - w, unscaledHeight);
}

/**
* @private
* Update the text field.
*/
override public function setFocus():void
{
if (stage)
stage.focus = TextField(inputField.getTextField());
}

/**
* @private
*/
override protected function isOurFocus(target:DisplayObject):Boolean
{
return target == inputField || super.isOurFocus(target);
}

//--------------------------------------------------------------------------
//
// Methods
//
//--------------------------------------------------------------------------

/**
* @private
* Verify that the value is within range.
*/
private function checkRange(v:Date):Boolean
{
return v.time >= minimum.time && v.time <= maximum.time;
}

/**
* @private
*/
private function checkValidValue(value:Date):Date
{
if (isNaN(value.time))
return this.value;

var closest:Date = new Date(value.time);
switch(stepType)
{
case "fullYear":
closest.month = 0;
case "month":
closest.date = 1;
case "date":
closest.hours = 0;
case "day":
closest.hours = 0;
case "hours":
closest.minutes = 0;
case "minutes":
closest.seconds = 0;
case "seconds":
closest.milliseconds = 0;
case "milliseconds":
}

/*stepSize * Math.round(value / stepSize);

// The following logic has been implemented to fix bug 135045.
// It assumes that the above line of code which rounds off the
// value is not removed ! (That is, the precision of the value is
// never expected to be greater than the step size.
// ex : value = 1.11111 stepSize = 0.01)

// Use precision of the step to round of the value.
// When the stepSize is very small the system tends to put it in
// exponential format.(ex : 1E-7) The following string split logic
// cannot work with exponential notation. Hence we add 1 to the stepSize
// to make it get represented in the decimal format.
// We are only interested in the number of digits towards the right
// of the decimal place so it doesnot affect anything else.
var parts:Array = (new String(1 + stepSize)).split(".");

// we need to do the round of (to remove the floating point error)
// if the stepSize had a fractional value
if (parts.length == 2)
{
var scale:Number = Math.pow(10, parts[1].length);
closest = Math.round(closest * scale) / scale;
}*/

if (closest.time > maximum.time)
return maximum;
else if (closest.time < minimum.time)
return minimum;
else
return closest;
}

/**
* @private
*/
private function setValue(value:Date,
sendEvent:Boolean = true,
trigger:Event = null):void
{

var v:Date = checkValidValue(value);
if (v == lastValue)
return;

lastValue = _value = v;
inputField.text = dateFormatter.format(v);

//Hack to fix 7:00 time not rendering proerly
if(inputField.text == "")
inputField.text = v.toLocaleTimeString().replace(":00 ", " ").replace("07", "7");

if (sendEvent)
{
var event:CustomStepperEvent =
new CustomStepperEvent(CustomStepperEvent.CHANGE);
event.value = _value;
event.triggerEvent = trigger;

dispatchEvent(event);
}

dispatchEvent(new FlexEvent(FlexEvent.VALUE_COMMIT));
}

/**
* @private
* Checks the value in the text field. If it is valid value
* and different from the current value, it is taken as new value.
*/
private function takeValueFromTextField(trigger:Event = null):void
{
var inputValue:Date = new Date((prependDate ? value.toDateString() + " " : "") + inputField.text);
if ((inputValue.time != lastValue.time &&
(Math.abs(inputValue.time - lastValue.time) >= 0.000001 || inputValue == null)) ||
inputField.text == "")
{
var newValue:Date = checkValidValue(new Date((prependDate ? value.toDateString() + " " : "") + inputField.text));
inputField.text = dateFormatter.format(newValue);
setValue(newValue, trigger != null, trigger);
}
}

/**
* @private
* Increase/decrease the current value.
*/
private function buttonPress(button:Button, trigger:Event = null):void
{
if (enabled)
{
// we may get a buttonPress message before focusOut event for
// the text field. Hence we need to check the value in
// inputField.
takeValueFromTextField();

var oldValue:Date = lastValue;
setValue(button == nextButton ?
step(lastValue, STEPNEXT) :
step(lastValue, STEPPREVIOUS), true, trigger);

if (oldValue.time != lastValue.time)
inputField.getTextField().setSelection(0,0);
}
}

//--------------------------------------------------------------------------
//
// Overridden event handlers: UIComponent
//
//--------------------------------------------------------------------------

/**
* @private
* Remove the focus from the text field.
*/
override protected function focusInHandler(event:FocusEvent):void
{
super.focusInHandler(event);

var fm:IFocusManager = focusManager;
if (fm)
fm.defaultButtonEnabled = false;
}

/**
* @private
* Remove the focus from the text field.
*/
override protected function focusOutHandler(event:FocusEvent):void
{
var fm:IFocusManager = focusManager;
if (fm)
fm.defaultButtonEnabled = true;

super.focusOutHandler(event);

takeValueFromTextField(event);
}

//--------------------------------------------------------------------------
//
// Event handlers
//
//--------------------------------------------------------------------------

/**
* @private
*/
private function buttonDownHandler(event:FlexEvent):void
{
buttonPress(Button(event.currentTarget), event);
}

/**
* @private
*/
private function buttonClickHandler(event:MouseEvent):void
{
inputField.setFocus();
inputField.getTextField().setSelection(0, 0);
}

/**
* @private
*/
private function inputField_focusInHandler(event:FocusEvent):void
{
focusInHandler(event);

// Send out a new FocusEvent because the TextInput eats the event
// Make sure that it does not bubble.
dispatchEvent(new FocusEvent(event.type, false, false,
event.relatedObject,
event.shiftKey, event.keyCode));
}

/**
* @private
*/
private function inputField_focusOutHandler(event:FocusEvent):void
{
focusOutHandler(event);

// Send out a new FocusEvent because the TextInput eats the event
// Make sure that it does not bubble
dispatchEvent(new FocusEvent(event.type, false, false,
event.relatedObject,
event.shiftKey,event.keyCode));
}

/**
* @private
*/
private function inputField_keyDownHandler(event:KeyboardEvent):void
{
var tmpV:Date;

switch (event.keyCode)
{
case Keyboard.DOWN:
{
tmpV = step(value, STEPPREVIOUS);
setValue(tmpV, true);
break;
}

case Keyboard.UP:
{
tmpV = step(value, STEPNEXT);
setValue(tmpV, true);
break;
}

case Keyboard.HOME:
{
inputField.text = dateFormatter.format(minimum);
setValue(minimum, true);
break;
}

case Keyboard.END:
{
inputField.text = dateFormatter.format(maximum);
setValue(maximum, true);
break;
}

case Keyboard.ENTER:
case Keyboard.TAB:
{
var inputValue:Date = new Date((prependDate ? value.toDateString() + " " : "") + inputField.text);
if (inputValue.time != lastValue.time &&
(Math.abs(inputValue.time - lastValue.time) >= 0.000001 ||
isNaN(inputValue.time)))
{
var newValue:Date = checkValidValue(new Date((prependDate ? value.toDateString() + " " : "") + inputField.text));
inputField.text = dateFormatter.format(newValue);
setValue(newValue, true);
}

// Prevent the defaultButton from firing
event.stopImmediatePropagation();
break;
}
}

// Act as a proxy because the TextInput stops propogation
dispatchEvent(event);
}

/**
* @private
*/
private function inputField_changeHandler(event:Event):void
{
// Stop the event from bubbling up.
event.stopImmediatePropagation();

var inputValue:Date = new Date((prependDate ? value.toDateString() + " " : "") + inputField.text);
if ((inputValue.time != value.time &&
(Math.abs(inputValue.time - value.time) >= 0.000001 || isNaN(inputValue.time))) ||
inputField.text == "")
{
_value = checkValidValue(inputValue);
}
}

}

}

Monday, February 23, 2009

OS X Style Dock for Linux

So I recently switched to use Ubuntu at work and recently switched from the gnome task list to an OS X style dock. After playing around with Gnome-Do for a while I ended up settling on Avant Window Navigator instead. Awn turned out to be slightly more configurable but the Gnome-Do project is relatively new. So here are a few thoughts on both:

Gnome-Do:
To install a version of gnome-do with the docky theme I first had to add the following repositories.
deb http://ppa.launchpad.net/do-core/ppa/ubuntu intrepid main
deb-src http://ppa.launchpad.net/do-core/ppa/ubuntu intrepid main

Then install gnome-do:
sudo apt-get install gnome-do

Once installed open up Gnome-Do and switch the Theme to Docky. This is smiler the OS X dock and also has Quicksilver style searching.

This tool has a nice layout and a very powerful text based action system but there is currently only the primary theme and very few options to control the UI. It does Stack multiple instances of an application together but they window manager control of that stack is less then optimal. Also the dock itself is part of the theme not the main program so there are no way to extend the dock with plug-ins. It is a a simple list of shortcuts and running applications.

Avant Window Navigator
To install awn just run:
sudo apt-get install avant-window-navigator

This is a much more feature rick dock utility with a multitude of options and plugins, minus all the Quicksilver features.

This dock did take a bit more to get configured though, I have two monitors and where Gnome-Do automatically came up on my primary display awn favored the left most monitor witch happens to be my secondary monitor. There was no apparent way to drag it to teh correct locations nor option to change it. After a bit of fiddling I wound two settings that worked for me. To fix this problem I used gconf-editor to change the following properties.
/apps/avant-window-navigator/monitor_height = 1050
/apps/avant-window-navigator/monitor_width = 3360
/apps/avant-window-navigator/force_monitor = true
/apps/avant-window-navigator/bar/bar_pos = .74


The first two settings configure the virtual screen size taken from gnome-display-properties and the third option causes awn to use the virtual screen size instead of the size of the first monitor. The final option adjusts the position the dock, now centered between the two screens, to the center of the right screen.

Note: The property editor was rounding .75 to .7 so I actually had to run the following command to set the property.
gconftool-2 --set /apps/avant-window-navigator/bar/bar_pos .74 --type float


As it turned out awn has its own share of hiccups including:
There is no way to get multiple instance of the same application to stack up nicely.
Multiple windows sharing the same name will cause the process icons to fall away and reappear at the end of the list. This is visually confusing but would not be an issue if process could stack.

That said there are many nice plugins for the dock including a embedded terminal and a multitude of themes and visual tweaks, though I wish there was an option to make auto-hide only hide a set distance instead of completely hiding the bar.

Monday, February 16, 2009

Fancy move/copy commands

Upadte- 2/23/2009 4:25 PM
Updated the action detection to work for the mv variant as well.



Ok, here is a nice wrapper script I wrote in bash. It adds a nice progress bar tot he cp and mv commands Linux.

To install just save this file someplace and give it execute rights. Then create symbolic links for cp and mv in your $PATH.

$> mkdir ~/bin
Save as ~/bin/nice.ops
$> ln -s ~/bin/nice.ops ~/bin/cp
$> ln -s ~/bin/nice.ops ~/bin/mv
Added ~/bin to $PATH in ~/.bashrc


Code:
nice.ops
#!/bin/bash

# This is a custom wrapper for the cp and mv commands in *nix. It adds a nice progress bar to the file operation.
# Jeremy Pyne

# Config, Set command mode here if auto-detect doesn't work.
action= # cp|mv
message= # Copying|Moving

# Auto-detect command type.
if [ -z $action ] ; then
case `expr "$0" : '.*\(/cp\|/mv\)'` in
/cp)
action=cp
message=Copying
;;
/mv)
action=mv
message=Moving
;;
esac
fi

# Default Variables
dest=
quiet=0
detail=0
dest_dir=0
dest_size=0
pct=0

# Throw a usage message if there are to few parameters.
if [ $# -lt 2 ] ; then
/bin/$action $@
exit
fi

# Loop through each argument.
for arg ; do
case $arg in
# Show extra details.
--detail)
detail=1
continue ;;
# Show less details.
--quiet)
quiet=1
continue ;;
esac

# If there was a destination, change it to a source file.
if [ "$dest" ] ; then
files[${#files[@]}]="$dest"
fi

# Set the current item as the destination.
dest="$arg"
done

# Calculate the source file cound and size.
orig_size=`du -s "${files[@]}" | awk 'BEGIN{total=0}{total+=$1}END{print total}'`
orig_count=`find "${files[@]}" | awk 'BEGIN{total=0}{total++}END{print total}'`

# Show a info message.
if [ $quiet -eq 0 ] ; then
echo $message $orig_count files $orig_size bytes to $dest.
fi

# If a single source directory is being used, it will be renamed so we need to gets it's contents.
if [ ${#files[@]} -eq 1 -a -d "${files[0]}" ] ; then
# Flag the rename bit se we can properly look for files.
dest_dir=1
IFS=$'\n'
# Store each direct child of the source directory.
for file in `cd "${files[@]}" && ls ` ; do
ofiles[${#ofiles[@]}]="$file"
done
IFS=$' '
fi

# Preform the acctual copy or move command.
/bin/$action -f $params "${files[@]}" "$dest" 2>/tmp/.error &

# Start with a empty progress bar.
echo -en "[> 0%]\b\b\b\b\b"
sleep 1

# If there was an error echo it out and exit.
if [ -s /tmp/.error ] ; then
cat /tmp/.error
rm /tmp/.error
exit 1
fi

# If the source files were empty, just skip to 100% and exit.
if [ $orig_size -eq 0 ] ; then
echo -e "#>100%]\b\b\b\b\b\b"
exit 0
fi

# While still copying, update the progress bar.
while [ $orig_size -gt $dest_size ] ; do
if [ $dest_dir -eq 1 ] ; then
# If this was a single directory operation, check the new directories contents.
dest_size=`cd "$dest" && du -s "${ofiles[@]}" 2>/dev/null | awk 'BEGIN{total=0}{total+=$1}END{print total+4}'`
dest_count=`cd "$dest" && find "${ofiles[@]}" 2>/dev/null | awk 'BEGIN{total=0}{total++}END{print total}'`
elif [ -f "$dest" ] ; then
# If this was a single file operation, check the new file.
dest_size=`du -s "$dest" | awk 'BEGIN{total=0}{total+=$1}END{print total}'`
dest_count=`find "$dest" | awk 'BEGIN{total=0}{total++}END{print total}'`
elif [ -d "$dest" ] ; then
# If this was a many to one directory operation, check for the source items in the new directory.
dest_size=`cd "$dest" && du -s "${files[@]}" 2>/dev/null | awk 'BEGIN{total=0}{total+=$1}END{print total}'`
dest_count=`cd "$dest" && find "${files[@]}" 2>/dev/null | awk 'BEGIN{total=0}{total++}END{print total}'`
fi

# Calculate the operation percentage.
pct=$((( 100 * $dest_size ) / $orig_size ))

# Move the the start of the line and clear it.
echo -en "\033[120D\033[K["
# For 0 to 100
for count in `seq -s " " 0 50`; do
if [ $count -eq $(($pct / 2)) ] ; then
# Print a '>' at the current percent.
echo -ne "\033[s>"
elif [ $count -lt $(($pct / 2)) ] ; then
# Print a '#' for compleated area.
echo -n "#"
else
# Print a ' ' for space not done.
echo -n " "
fi
done
# Print the percentage and restore the cursor to the '>' icon.
echo -en "$pct%]\033[u"

# If detail was set, show extra info.
if [ $detail -eq 1 -a $quiet -eq 0 ] ; then
echo -en "\033[s"
echo -en "\033[1A\033[120D\033[K"
echo -en $message $dest_count/$orig_count files $dest_size/$orig_size bytes to $dest.
echo -en "\033[u"
fi
# Wait 1 second.
sleep 1
done
echo

exit 0

Monday, January 19, 2009

Flex Onscreen Keyboard

Upadte- 8/18/2009 9:31 AM
Added Sample.
Added a fix for the enter key in text areas and for editable DateFields.

Upadte- 1/20/2009 2:49 PM
Added logic to show or hide the keyboard automatically based on whether a input box is selected. This can be overridden by setting auto to false. Also clicking show/hide will toggle this feature.


This component adds a simple Onscreen Keyboard in flex. The keyboard is very customizable built at run time from a provided array.

It supports buttons to insert text as well as the special actions listed below and custom events and methods.

Button Types:
SHIFT Toggle the shift state.
TAB Tab to the next control on screen, or previous if shift is down.
ENTER Simulate an Enter action, causes validation/submit action.
HIDE Hide the onscreen keyboard.
BACKSPACE Delete the selected text or one character backward, or forward on shift.
MODE Switch to the next keyboard layout, or previous on shift.
Sample:
private const sampleKeyMap:Object = [[
{key: 'a', altkey: 'A'},
{key: 'b', altkey: 'B'},
{key: 'c', altkey: 'C'},
{key: ' ', label: 'Space', span: 2}
], [
{key: '', label: 'Backspace', altlabel: 'Delete', span: 3, type: OnscreenKeyboard.BACKSPACE}
{label: 'Shift', span: 2, type: OnscreenKeyboard.SHIFT},
{label: 'myFucntion', method: myFunction},
{label: 'myEvent', event: 'myEvent'}
]];

private function myFunction(event:MouseEvent):void
{
Alert.Show(osk.shift ? "Shift DOwn" : "Shift Up");
}

<mx:TextInput/>
<local.OnscreenKeyboard id="osk" modes="{sampleKeyMap}" myEvent="Alert.Show('I got clicked.')"/>
OnscreenKeyboard.as
package
{
import flash.events.Event;
import flash.events.IEventDispatcher;
import flash.events.KeyboardEvent;
import flash.events.MouseEvent;
import flash.ui.Keyboard;

import mx.binding.utils.BindingUtils;
import mx.binding.utils.ChangeWatcher;
import mx.containers.HBox;
import mx.containers.TitleWindow;
import mx.containers.VBox;
import mx.controls.TextArea;
import mx.controls.TextInput;
import mx.events.FlexEvent;
import mx.events.FocusRequestDirection;
import mx.events.PropertyChangeEvent;

/**
* This is a suctomizable onscreen keyboard. You can use the provided keymaps of provide your own.
*
* A keymap consists of a two dimensional array of keys. The outer array is a collection of rows and the inner array's are
* the keys. Each key is an object with any of the following properties.
*
* keyMap:Object {
* key: Characters to insert on key press.
* altkey: Alternate characters to insert on shift click.
* label: Normal label to display.
* altlabel: Alternate label to display on shift.
* span: Number of button spaces to span over.
* method: Custom method to raise on click. (Can access OnscreenKeyboard.shift to detect shift state.)
* event: Custom event to raise on click. (Can access OnscreenKeyboard.shift to detect shift state.)
* type: Special type of internal action to preform from the following:
* SHIFT Toggle the shift state.
* TAB Tab to the next control on screen, or previous if shift is down.
* ENTER Simulate an Enter action, causes validation/submit action.
* HIDE Hide the onscreen keyboard.
* BACKSPACE Delete the selected text or one character backward, or forward on shift.
* MODE Switch to the next keyboard layout, or previous on shift.
*
*
* Example:
* private const sampleKeyMap:Object = [[
* {key: 'a', altkey: 'A'},
* {key: 'b', altkey: 'B'},
* {key: 'c', altkey: 'C'},
* {key: ' ', label: 'Space', span: 2}
* ], [
* {key: '', label: 'Backspace', altlabel: 'Delete', span: 3, type: OnscreenKeyboard.BACKSPACE}
* {label: 'Shift', span: 2, type: OnscreenKeyboard.SHIFT},
* {label: 'myFucntion', method: myFunction},
* {label: 'myEvent', event: 'myEvent'}
* ]];
*
* @author Jeremy Pyne jeremy.pyne@gmail.com
*/
public class OnscreenKeyboard extends TitleWindow
{
/**
* Create a new instance of OnscreenKeyboard.
*/
public function OnscreenKeyboard()
{
super();

// Set up panel spaceing.
setStyle("paddingBottom", 6);
setStyle("paddingLeft", 6);
setStyle("paddingRight", 6);

// Set default modes.
modes = [alphaKeyMap, qwertyKeyMap];

// Listen for creation complete event.
addEventListener(FlexEvent.CREATION_COMPLETE, onInit);
}

/**
* Width of a one span button. A two span button would be 2x keyWidth + keyGap.
*/
public var keyWidth:Number = 40;

/**
* Horizontal gap between buttons.
*/
public var keyGap:Number = 8;

/**
* Auto show/hide the keyboard.
*/
public var auto:Boolean = true;

/**
* Keyboard visibility state.
*/
private var _visible:Boolean = false;

[Bindable("visibleChanged")]
/**
* Is the keyboard currently visible.
*/
public function get isVisible():Boolean
{
return _visible;
}
public function set isVisible(value:Boolean):void
{
_visible = value;

dispatchEvent(new Event("visibleChanged"));
}

[Bindable("visibleChanged")]
/**
* Is the keyboard currently hidden?
*/
public function get notVisible():Boolean
{
return !_visible;
}
public function set notVisible(value:Boolean):void
{
_visible = !value;

dispatchEvent(new Event("visibleChanged"));
}

[Bindable]
/**
* Is the shift button pressed?
*/
public var shift:Boolean = false;

/**
* Next keyboard to load from modes.
*/
private var nextMode:Number = -1;

/**
* Array of keyboard layouts. This can be changed to use cutsom layouts.
*/
public var modes:Array = new Array();

/**
* Rendered keymaps for displaying onscreen and storing when switched out.
*/
private var keyMaps:Array = new Array();

/**
* Keyboard portion of the panel.
*/
private var showView:VBox;

/**
* Hidden version of the keyboard with a show button.
*/
private var hideView:HBox;

/**
* Shift button for toggleing the shift state.
*/
public static const SHIFT:String = "shift";
/**
* Tab button to go to the next control.
*/
public static const TAB:String = "tab";
/**
* Enter button to validate and submit a form.
*/
public static const ENTER:String = "enter";
/**
* Hide button to hide the keyboard.
*/
public static const HIDE:String = "hide";
/**
* Backspace button to delete the selected text or a charecter.
*/
public static const BACKSPACE:String = "backspace";
/**
* Mode button to switch keyboard layouts.
*/
public static const MODE:String = "mode";

/**
* Run on creation compleate and load the default keymap.
*/
private function onInit(event:FlexEvent):void
{
// Load the default keymap.
loadKeyMap();

// Listen for focus change event.
parentApplication.addEventListener(FocusEvent.FOCUS_IN, focusIn);
parentApplication.addEventListener(FocusEvent.FOCUS_OUT, focusOut);

// Set the initial keyboard state.
isVisible = focusManager.getFocus() is mx.controls.TextInput || focusManager.getFocus() is mx.controls.TextArea;
}

/**
* Raised when a object gains focus.
*/
private function focusIn(event:FocusEvent):void
{
// If auto is on:
if(auto) {
// If a textbox is selected enable the keyobard.
if(event.target is UITextField)
isVisible = true;
// If not then disable the keyobard.
else
isVisible = false;
}
}

/**
* Raised when a object loses focus.
*/
private function focusOut(event:FocusEvent):void
{
// If auto is on hide the keyboard.
if(auto)
isVisible = false;
}

/**
* Activate a new keymap. If it hasn't been loaded yet load it.
*/
private function loadKeyMap():void
{
if(!shift) {
// Switch to the next mode.
nextMode = nextMode + 1 == modes.length ? 0 : nextMode + 1
} else {
// Switch to the prevoise mode.
nextMode = nextMode == 0 ? modes.length - 1 : nextMode - 1
}

// If this modes keymap isnt loaded, build it.
if(!keyMaps[nextMode])
{
// Prepare the new keymap.
keyMaps[nextMode] = new Array();

// For each row of keys.
for each (var rowMap:Object in modes[nextMode])
{
// Create a new row.
var row:HBox = new HBox();
// Set a custom horazontal gap.
row.setStyle("horizontalGap", keyGap);

// For each key in the row.
for each (var keyData:Object in rowMap)
{
// Create a new key.
var key:Button = new Button();
// Listen for the key press and call key_clickHandler or a custom method.
key.addEventListener(MouseEvent.CLICK, keyData.event ? keyData.event : key_clickHandler);

/**
* This extra property and shit variable is created so that all keys will automaticly detect shift changes and update
* their labels without having to loop through them manualy.
*/
// Bind this keys shift state to the panel.
BindingUtils.bindProperty(key, "shift", this, "shift");
// Watch for shift changes and fire off key_shiftListener to udate the keys label.
ChangeWatcher.watch(key, "shift", key_shiftListener);

// Set the initial label to an unshifted state.
key.label = keyData.label ? keyData.label : keyData.key;

// Set the buttons width to (span * keyWidth) + (keyGap * keyWidth - 1).
key.width = keyData.span? keyData.span * (keyWidth + keyGap) - keyGap : keyWidth;

// If this button is a toggle button, set toggle.
key.toggle = keyData.toggle;

// Store all the data for this key in it's data field for later use.
key.data = keyData;

// Disable focus on this key so that it won't steal focus.
key.focusEnabled = false;

// If this is a special shift key.
if(keyData.type == OnscreenKeyboard.SHIFT) {
// Bind the selected field to the shift state.
BindingUtils.bindProperty(key, "selected", this, "shift");
// Force the key to a toggle key.
key.toggle = true;
}

// Add this eky to the row.
row.addChild(key);
}

// Add the row to the key map.
keyMaps[nextMode].push(row);
}
}

// Clear the old rows of keys.
showView.removeAllChildren();

// oop through each row of keys.
for each(var rowBox:HBox in keyMaps[nextMode])
// Add this row to the keyboard view.
showView.addChild(rowBox);
}

/**
* Override the createChildren method
*/
override protected function createChildren():void
{
super.createChildren();

// Prepare the main keyboard view.
if(!showView) {
// Create a VBox for the rows.
showView = new VBox();

// Bind the visibility to the isVisible.
BindingUtils.bindProperty(showView, "visible", this, "isVisible");
BindingUtils.bindProperty(showView, "includeInLayout", this, "isVisible");

// Add the main view to the panel.
this.addChild(showView);
}

// Prepare the collapsed view.
if(!hideView) {
// Create a HBox for teh collapsed buttons.
hideView = new HBox();

// Bind the visibility to the notVisible.
BindingUtils.bindProperty(hideView, "visible", this, "notVisible");
BindingUtils.bindProperty(hideView, "includeInLayout", this, "notVisible");

// Create a show button.
var show:Button = new Button()

// Set the button label.
show.label = "Show Keyboard";

// Disable the focus for the button so it doesn't steal focus.
show.focusEnabled = false;

// Listen for the show button to be clicked.
show.addEventListener(MouseEvent.CLICK, show_clickHandler);

// Add the show button to the HBox.
hideView.addChild(show);

// Add the collapsed view to the panel.
this.addChild(hideView);
}
}

/**
* Fires then the show button is clicked.
*/
private function show_clickHandler(event:MouseEvent):void
{
// Set the isVisible to true.
isVisible = true;
// Toggle the autohide mode.
auto = !auto;
}

/**
* Fires when a key is perssed unless said key has a custom key handler.
*/
private function key_clickHandler(event:MouseEvent):void
{
// Custom shift action.
if(event.currentTarget.data.type == OnscreenKeyboard.SHIFT) {
// Toggle shift state.
shift = !shift;
return;
}
// Custom hide keyboard action.
if(event.currentTarget.data.type == OnscreenKeyboard.HIDE) {
// Switch shift off and hide the keyboard.
isVisible = shift = false;
// Toggle the autohide mode.
auto = !auto;
return;
}
// Custom enter action.
if(event.currentTarget.data.type == OnscreenKeyboard.ENTER) {
if(focusManager.getFocus() is mx.controls.TextArea) {

} else {
// If there if a IEventDispatcher selected.
if(focusManager.getFocus() is IEventDispatcher) {
// Pass it a keyDown and keyUp event.
(focusManager.getFocus()as IEventDispatcher).dispatchEvent(new KeyboardEvent("keyDown", true, false, Keyboard.ENTER, Keyboard.ENTER, 0, false, false, shift));
(focusManager.getFocus()as IEventDispatcher).dispatchEvent(new KeyboardEvent("keyUp", true, false, Keyboard.ENTER, Keyboard.ENTER, 0, false, false, shift));
}

// Switch shift off.
shift = false;
return;
}
}
if(event.currentTarget.data.type == OnscreenKeyboard.TAB) {
// If there if a IEventDispatcher selected.
if(focusManager.getFocus() is IEventDispatcher) {
focusManager.moveFocus(shift ? FocusRequestDirection.BACKWARD : FocusRequestDirection.FORWARD);
}

// Switch shift off.
shift = false;
return;
}
// Custom switch mode action.
if(event.currentTarget.data.type == OnscreenKeyboard.MODE) {
// Load the next keymap.
loadKeyMap();

// Switch shift off.
shift = false;
return;
}
// Fire a custom event.
if(event.currentTarget.data.event) {
// Fire the custom event.
dispatchEvent(new Event(event.currentTarget.data.event));

// Switch shift off.
shift = false;
return;
}

// Set the key charecter to altkey if shift is set or key.
var char:String = shift && event.currentTarget.data.altkey ? event.currentTarget.data.altkey : event.currentTarget.data.key;
var stepLeft:Boolean = false;
var stepRight:Boolean = false;

// If a TextInput has focus.
if(focusManager.getFocus() is mx.controls.TextInput) {
// Get the focused control.
var tiControl:mx.controls.TextInput = (focusManager.getFocus() as mx.controls.TextInput);
stepLeft = event.currentTarget.data.type == OnscreenKeyboard.BACKSPACE && tiControl.selectionBeginIndex == tiControl.selectionEndIndex && shift == false;
stepRight = event.currentTarget.data.type == OnscreenKeyboard.BACKSPACE && tiControl.selectionBeginIndex == tiControl.selectionEndIndex && shift == true;

// Replace the selected text with the charecter. If step then replace an extra charecter.
tiControl.text = tiControl.text.substr(0, tiControl.selectionBeginIndex - (stepLeft ? 1 : 0)) + char + tiControl.text.substr(tiControl.selectionEndIndex + (stepRight ? 1 : 0), tiControl.text.length - tiControl.selectionEndIndex)
// Reposition the cursor to the right the length of the charecters inserted or adjust for a backspace or delete.
tiControl.selectionBeginIndex = tiControl.selectionEndIndex = tiControl.selectionBeginIndex + char.length - (event.currentTarget.data.type == OnscreenKeyboard.BACKSPACE ? (stepLeft ? 1 : 0) : -1);
}
// If a TextArea has focus.
if(focusManager.getFocus() is mx.controls.TextArea) {
// Get the focused control.
var taControl:mx.controls.TextArea = (focusManager.getFocus() as mx.controls.TextArea);
stepLeft = event.currentTarget.data.type == OnscreenKeyboard.BACKSPACE && taControl.selectionBeginIndex == taControl.selectionEndIndex && shift == false;
stepRight = event.currentTarget.data.type == OnscreenKeyboard.BACKSPACE && taControl.selectionBeginIndex == taControl.selectionEndIndex && shift == true;

// Replace the selected text with the charecter. If step then replace an extra charecter.
taControl.text = taControl.text.substr(0, taControl.selectionBeginIndex - (stepLeft ? 1 : 0)) + char + taControl.text.substr(taControl.selectionEndIndex + (stepRight ? 1 : 0), taControl.text.length - taControl.selectionEndIndex)
// Reposition the cursor to the right the length of the charecters inserted or adjust for a backspace or delete.
taControl.selectionBeginIndex = taControl.selectionEndIndex = taControl.selectionBeginIndex + char.length - (event.currentTarget.data.type == OnscreenKeyboard.BACKSPACE ? (stepLeft ? 1 : 0) : -1);
}
// If a DateField has focus
if(focusManager.getFocus() is mx.controls.DateField) {
// Get the focused control.
var tdControl:mx.controls.DateField = (focusManager.getFocus() as mx.controls.DateField);
if(tdControl.editable) {
if(event.currentTarget.data.type == OnscreenKeyboard.BACKSPACE)
// Can't access selected text so jsut delete last letter.
tdControl.text = tdControl.text.substr(0, tdControl.text.length - 1);
else
// Can't access selected text so just appent to text.
tdControl.text = tdControl.text + char;

// Fire off a chaneg event on the source control.
tdControl.dispatchEvent(new Event(Event.CHANGE, false, false));
}
}


// Switch shift off.
shift = false;
}

/**
* Fires when the shift state has changed on all buttons.
*/
private function key_shiftListener(event:PropertyChangeEvent):void
{
//If shift is clicked use altlabel or the altkey.
if(shift && event.source.data.altlabel)
event.source.label = event.source.data.altlabel;
else if(shift && event.source.data.altkey)
event.source.label = event.source.data.altkey;
// If neither of these exist or shift isn't down then use the label or the key.
else if(event.source.data.label)
event.source.label = event.source.data.label;
else
event.source.label = event.source.data.key;
}

/**
* Default alphabetical keyboard layout.
*/
private const alphaKeyMap:Object = [[
{key: '1', altkey: '!'},
{key: '2', altkey: '@'},
{key: '3', altkey: '#'},
{key: '4', altkey: '$'},
{key: '5', altkey: '%'},
{key: '6', altkey: '^'},
{key: '7', altkey: '&'},
{key: '8', altkey: '*'},
{key: '9', altkey: '('},
{key: '0', altkey: ')'},
{key: '-', altkey: '_'},
{key: '=', altkey: '+'},
{key: '', label: 'Backspace', altlabel: 'Delete', span: 3, type: OnscreenKeyboard.BACKSPACE}
], [
{key: 'a', altkey: 'A'},
{key: 'b', altkey: 'B'},
{key: 'c', altkey: 'C'},
{key: 'd', altkey: 'D'},
{key: 'e', altkey: 'E'},
{key: 'f', altkey: 'F'},
{key: 'g', altkey: 'G'},
{key: 'h', altkey: 'H'},
{key: 'i', altkey: 'I'},
{key: 'j', altkey: 'J'},
{key: '[', altkey: '{'},
{key: ']', altkey: '}'},
{key: '\\', altkey: '|'},
{label: 'Tab', span: 2, type: OnscreenKeyboard.TAB},
{label: 'Enter', span: 2, type: OnscreenKeyboard.ENTER}
], [
{key: 'k', altkey: 'K'},
{key: 'l', altkey: 'L'},
{key: 'm', altkey: 'M'},
{key: 'n', altkey: 'N'},
{key: 'o', altkey: 'O'},
{key: 'p', altkey: 'P'},
{key: 'q', altkey: 'Q'},
{key: 'r', altkey: 'R'},
{key: 's', altkey: 'S'},
{key: ';', altkey: ':'},
{key: "'", altkey: '"'},
{key: ' ', label: 'Space', span: 2},
{label: 'Shift', span: 2, type: OnscreenKeyboard.SHIFT}
], [
{key: 't', altkey: 'T'},
{key: 'u', altkey: 'U'},
{key: 'v', altkey: 'V'},
{key: 'w', altkey: 'W'},
{key: 'x', altkey: 'X'},
{key: 'y', altkey: 'Y'},
{key: 'z', altkey: 'Z'},
{key: ',', altkey: '<'},
{key: '.', altkey: '>'},
{key: '/', altkey: '?'},
{key: '`', altkey: '~'},
{label: 'Mode', span: 2, type: OnscreenKeyboard.MODE},
{label: 'Hide', span: 2, type: OnscreenKeyboard.HIDE}
]];

/**
* Default qwerty keyboard layout.
*/
private const qwertyKeyMap:Object = [[
{key: '1', altkey: '!'},
{key: '2', altkey: '@'},
{key: '3', altkey: '#'},
{key: '4', altkey: '$'},
{key: '5', altkey: '%'},
{key: '6', altkey: '^'},
{key: '7', altkey: '&'},
{key: '8', altkey: '*'},
{key: '9', altkey: '('},
{key: '0', altkey: ')'},
{key: '-', altkey: '_'},
{key: '=', altkey: '+'},
{key: '', label: 'Backspace', altlabel: 'Delete', span: 3, type: OnscreenKeyboard.BACKSPACE}
], [
{key: 'q', altkey: 'Q'},
{key: 'w', altkey: 'W'},
{key: 'e', altkey: 'E'},
{key: 'r', altkey: 'R'},
{key: 't', altkey: 'T'},
{key: 'y', altkey: 'Y'},
{key: 'u', altkey: 'U'},
{key: 'i', altkey: 'I'},
{key: 'o', altkey: 'O'},
{key: 'p', altkey: 'P'},
{key: '[', altkey: '{'},
{key: ']', altkey: '}'},
{key: '\\', altkey: '|'},
{label: 'Tab', span: 2, type: OnscreenKeyboard.TAB},
{label: 'Enter', span: 2, type: OnscreenKeyboard.ENTER}
], [
{key: 'a', altkey: 'A'},
{key: 's', altkey: 'S'},
{key: 'd', altkey: 'D'},
{key: 'f', altkey: 'F'},
{key: 'g', altkey: 'G'},
{key: 'h', altkey: 'H'},
{key: 'j', altkey: 'J'},
{key: 'k', altkey: 'K'},
{key: 'l', altkey: 'L'},
{key: ';', altkey: ':'},
{key: "'", altkey: '"'},
{key: ' ', label: 'Space', span: 2},
{label: 'Shift', span: 2, type: OnscreenKeyboard.SHIFT}
], [
{key: 'z', altkey: 'Z'},
{key: 'x', altkey: 'X'},
{key: 'c', altkey: 'C'},
{key: 'v', altkey: 'V'},
{key: 'b', altkey: 'B'},
{key: 'n', altkey: 'N'},
{key: 'm', altkey: 'M'},
{key: ',', altkey: '<'},
{key: '.', altkey: '>'},
{key: '/', altkey: '?'},
{key: '`', altkey: '~'},
{label: 'Mode', span: 2, type: OnscreenKeyboard.MODE},
{label: 'Hide', span: 2, type: OnscreenKeyboard.HIDE}
]];

/**
* Default number pad layout.
*/
private const numberKeyMap:Object = [[
{key: '1'},
{key: '2'},
{key: '3'},
{key: '/'}
], [
{key: '4'},
{key: '5'},
{key: '6'},
{key: '*'}
], [
{key: '7'},
{key: '8'},
{key: '9'},
{key: '-'}
], [
{key: '0', span: 2},
{key: '.'},
{key: '+'}
], [
{key: '', label: 'Backspace', altlabel: 'Delete', span: 2, type: OnscreenKeyboard.BACKSPACE},
{label: 'Enter', span: 2, type: OnscreenKeyboard.ENTER}
], [
{label: 'Mode', span: 2, type: OnscreenKeyboard.MODE},
{label: 'Hide', span: 2, type: OnscreenKeyboard.HIDE}
]];
}
}


Button.as
package
{
import mx.controls.Button;
/**
* Custom Button control with an extra shift property.
*
* @author Jeremy Pyne jeremy.pyne@gmail.com
*/
public class Button extends mx.controls.Button
{
/**
* Create an instance of the Button class.
*/
public function Button()
{
super();
}

[Bindable]
public var shift:Boolean = false;
}
}