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