/*
    Getme Ltd reserve all intellectual property rights of this source code.
    
    EXCEPT AS OTHERWISE EXPRESSLY PROVIDED IN A WRITTEN AGREEMENT BETWEEN YOU 
    AND GETME LTD, THIS SOURCE CODE IS PROVIDED "AS IS" AND WITHOUT ANY 
    WARRANTIES OF ANY KIND, INCLUDING WARRANTIES OF MERCHANTABILITY OR FITNESS 
    FOR A PARTICULAR PURPOSE.

    IN NO EVENT SHALL GETME LTD BE LIABLE FOR ANY DIRECT, INDIRECT, 
    INCIDENTIAL, SPECIAL OR CONSEQUENTIAL DAMAGES OR DAMAGES FROM BUSINESS 
    INTERRUPTION OR LOSS OF PROFITS, REVENUE, DATA OR USE, INCURRED BY YOU OR 
    ANY THIRD PARTY, WHETHER IN CONTRACT OR TORT, ARISING FROM YOUR ACCESS TO, 
    OR USE OF, THIS SOURCE CODE EVEN IF GETME LTD HAS BEEN ADVISED OF THE 
    POSSIBILITY OF SUCH DAMAGES.
    
    Copyright (c)2009 Getme Ltd.
 */

/**
 * @fileoverview    User Interface components.
 * @version         0.0.1
 * @author          Anthony Blackshaw ant@getme.co.uk
 */

// @@ Icons for menus titles

// Dependency check(s)
if (window.jQuery===undefined) {
    throw Error('JQuery JavaScript framework (http://jquery.com/) is required.');
}

if (window.jQuery.timer===undefined) {
    throw Error('JQuery JavaScript timer plugin (http://plugins.jquery.com/project/timers) is required.');
}

if (!jQuery.fn.backgroundPosition) {
    /**
     * Fix an issue in retrieving the background position for IE.
     * @ignore
     */
    (function($) {
      jQuery.fn.backgroundPosition = function() {
        var pos = $(this).css('background-position');
        if(!pos) {
            return $(this).css('background-position-x')+' '+$(this).css('background-position-y');
        } else {
            return pos;
        }
      };
    })(jQuery);
}

if(!Array.indexOf) {
    /**
     * Ensure indexOf method is supported (cheers for this IE)
     * @ignore
     */    
    Array.prototype.indexOf = function(obj) {
        for(var i=0; i<this.length; i++) {
            if(this[i]==obj) {
                return i;
            }
        }
        return -1;
    }
}


/**
 * The UI namespace.
 * The user interface library uses UI to namespace it's classes and functions.
 * @final
 * @type Namespace
 */  
var UI = {};

/**
 * A handle to the single(ton) instance of the Application class.
 * @type UI.Application
 * @private
 */
UI._application = null;

/**
 * A puesdo ID counter used by the default 'request_widget_id' to return 
 * a unique ID for a UI widget when the user does not provide one.
 * @type Number
 * @private
 */
UI._puesdo_id_counter = 0; 

/**
 * Return a handle to the Single(ton) instance of the Application class.
 * @return A handle to the Single(ton) instance of the Application class.
 * @type UI.Application
 */
UI.get_application = function() {
    return new UI.Application();
};

/**
 * Returns a unique ID that can be used to identify a UI widget.
 * @return A unique ID for an UI Widget.
 * @type Number
 */
UI.request_id = function() {
    UI._puesdo_id_counter++;
    return String(UI._puesdo_id_counter);
}


/**
 * Create a new UI Widget. 
 * @class A base UI Widget class.
 * @constructor
 */ 
UI.Widget = function(owner,id,options) {

    options = options || {};

    /**
     * The Widget that this Widget belongs to.
     * @type UI.Widget
     * @private
     */
    this._owner = owner;
    
    /**
     * A list of children that this Widget owns.
     * @type Array
     * @private
     */
    this._child_list = new Array();

    /**
     * The Widgets ID.
     * @type String
     * @private
     */
    this._id = id || UI.request_id();
    
    /**
     * The ID of the element representing the Widget in the HTML DOM. 
     * @type String
     */
    this.element_id = this._id;
    if (id===undefined) {
        this.element_id = 'wui-'+this.element_id;
    }

    /**
     * A flag indicating if the Widget is visible.
     * @type Boolean
     * @private
     */
    this._visible = true;

    /**
     * A flag indicating if the Widget is enabled.
     * @type Boolean
     * @private
     */
    this._enabled = true;
    
    /**
     * The title of the Widget. 
     * @type String
     * @private
     */
    this._title = options['title'] || '';

    /**
     * The Widgets icon.
     * @type String
     * @private
     */
    this._icon = options['icon'];

    /**
     * The title of the Widget. 
     * @type String
     * @private
     */
    this._size = options['size'] || [0,0];
    
    /**
     * The style of the Widget. 
     * @type String
     * @private
     */
    this._style = options['style'] || new Object();
    
    /**
     * The CSS classes for of the Widget. 
     * @type String
     * @private
     */
    this._class_list = options['css_list'] || new Array();
    
    /**
     * A map of functions bound to events supported by the Widget that get
     * exectuted when an event is triggered.
     * @type Object
     * @private
     */    
    this._event_map = new Object();
    
    /**
     * The physical node in the HTML DOM. 
     * @type Element
     */
    this.node = null;

    // Register the widget
    if (this._owner) {
        this._owner._register_child(this);
    }
    
}

/**
 * Set/Get an attribute for the Widget.
 * @param {String} attr The Icon attribute to get or set.
 * @param {*} value Optional. The value of the attribute to set.
 * @returns The Icon attribute value.
 * @type String
 */
UI.Widget.prototype._attr = function(attr,value,update) {
    if (value===undefined) {
        return this[attr];
    } else {
        this[attr]=value;
        switch (update) {
            case 'attribute':
                $('#'+this.element_id).attr(attr.replace(/^_/,''),value);
                break;
            case 'self':
                this.refresh();
                break;
            case 'owner':
                if (this._owner) {
                    this._owner.refresh();
                }
                break;
            case 'application':
                UI.get_application().refresh();
                break;
        }
    }
}

/**
 * Paint the Widget.
 */
UI.Widget.prototype._paint = function() {}

/**
 * Prepare to refresh the Widget.
 */
UI.Widget.prototype._pre_refresh = function() {}

/**
 * A list which contains the Widgets base type(s) and final type. Used to 
 * identify Widgets by type.
 * @type Array
 * @private
 */
UI.Widget.prototype._is_a = ['base'];

/**
 * Return true if the Widget is of the specified type.
 * @param {String} widget_type The type of Widget to match against.
 * @return Whether the Widget is of the specified type.
 * @type Boolean
 */
UI.Widget.prototype.is_a = function(widget_type) {
    return this._is_a.indexOf(widget_type)!=-1;
} 

/**
 * Return the Widgets final type.
 * @return The final Widget type.
 * @type String
 */
UI.Widget.prototype.get_final_type = function() {
    return this._is_a[this._is_a.length-1];
}

/**
 * Return the Widgets parent Widget.
 * @return The parent Widget.
 * @type String
 */
UI.Widget.prototype.get_owner = function() {
    return this._owner;
}

/**
 * Return a list of children for the Widget.
 * @return A list of child Widgets.
 * @type Array
 */
UI.Widget.prototype.get_child_list = function() {
    return this._child_list;
}

/**
 * Register a child Widget.
 * @param {UI.Widget} child The child Widget to register.
 */
UI.Widget.prototype._register_child = function(child) {
    this._child_list.push(child);
}

/**
 * Unregister a child Widget.
 * @param {UI.Widget} child The child Widget to register.
 */
UI.Widget.prototype._unregister_child = function(child) {
    this._child_list.splice(this._child_list.indexOf(child),1);
}

/**
 * Set/Get the title for the Widget.
 * @param {String} title Optional. The Widgets title.
 * @returns The Widgets title.
 * @type String
 */
UI.Widget.prototype.title = function(title) {
    return this._attr('_title',title,'attribute');
}

/**
 * Set/Get the icon for the Widget.
 * @param {UI.Icon} icon Optional. The Widgets Icon.
 * @returns The Buttons Icon.
 * @type UI.Icon
 */
UI.Widget.prototype.icon = function(icon) {
    return this._attr('_icon',icon,'self');
}

/**
 * Set/Get the size of the Widget.
 * @param {Array} size Optional. The Widgets size.
 * @returns The Widgets size.
 * @type Array
 */
UI.Widget.prototype.size = function(size) {
    return this._attr('_size',size,'self');
}

/**
 * Set/Get the style of the Widget.
 * @param {String} style Optional. The Widgets style.
 * @returns The Widgets style.
 * @type Object
 */
UI.Widget.prototype.style = function(style) {
    if (style===undefined) {
        return this._style;
    } else {
        var modified = false;
        for (name in style) {
            if (style[name]) {
                this._style[name] = style[name];
                modified = true;
            } else {
                if (this._style!=undefined) {
                    delete this._style[name];
                    modified = true;
                }
            }
        }
        if (modified) {
            this.refresh();
        }
    }
}

/**
 * Set/Get the CSS class list for the Widget.
 * @param {Array} class_list Optional. The Widgets CSS class list.
 * @returns The Widgets CSS class list.
 * @type Array
 */
UI.Widget.prototype.class_list = function(class_list) {
    return this._attr('_class_list',class_list,'self');
}

/**
 * Apply a CSS class to the Widget.
 * @param {String} class The CSS class to apply to the Widget.
 */
UI.Widget.prototype.add_class = function(css_class) {
    css_class_list = css_class.split(' ');
    for (var i=0; i<css_class_list.length; i++) {
        this._class_list.push(css_class_list[i]);
    }
    this.refresh();
}

/**
 * Remove a CSS class from the Widget.
 * @param {String} class The CSS class to remove from the Widget.
 */
UI.Widget.prototype.remove_class = function(css_class) {
    var modified = false;
    css_class_list = css_class.split(' ');
    for (var i=0; i<css_class_list.length; i++) {
        var class_index = this._class_list.indexOf(css_class_list[i]);
        if (class_index>-1) {
            this._class_list.splice(class_index,1);
            modified = true;
        }
    }
    if (modified) {
        this.refresh();
    }
}

/**
 * Show/Hide the Widget.
 * @param {Boolean} Optional. If true or undefined the Widget is shown, if
 * false the Widget is hidden.
 */
UI.Widget.prototype.show = function(show) {
    if (show===undefined||show) {
        $(this.node).removeClass('wui-hidden');
        this._visible = true;
    } 
    else {
        $(this.node).addClass('wui-hidden');
        this._visible = false;
    }
}

/**
 * Return true if the Widget is visible.
 * @returns Whether the Widget is visible.
 * @type Boolean
 */
UI.Widget.prototype.is_visible = function() {
    return this._visible;
}

/**
 * Enable/Disable the Widget.
 * @param {Boolean} Optional. If true or undefined the Widget is enabled, if
 * false the Widget is disabled.
 */
UI.Widget.prototype.enable = function(enable) {
    if (enable===undefined||enable) {
        $(this.node).removeClass('wui-disabled');
        this._enabled = true;
    } 
    else {
        $(this.node).addClass('wui-disabled');
        this._enabled = false;
    }
}

/**
 * Return true if the Widget is enabled.
 * @returns Whether the Widget is enabled.
 * @type Boolean
 */
UI.Widget.prototype.is_enabled = function() {
    return this._enabled;
}

/**
 * Clear the Widget from the interface.
 */
UI.Widget.prototype.clear = function() {
    $('#'+this.element_id).unbind();
    $('#'+this.element_id).remove();
}

/**
 * Destroy the Widget.
 */
UI.Widget.prototype.destroy = function() {
    this.clear();
    this.get_owner()._unregister_child(this);
}

/**
 * Refresh the Widget.
 */
UI.Widget.prototype.refresh = function() {
    // Only render if the Widget has a parent
    if (!this.get_owner()) {
        return;
    }
    
    this._pre_refresh();
    for(var i=0; i<this._child_list.length; i++) {
        this._child_list[i]._pre_refresh();
    }
    
    this.clear();
    $(this.get_owner().node).append(this._paint());
    this.node = $('#'+this.element_id)[0];
    
    $(this.node).addClass('wui-widget');
    $(this.node).addClass('wui-'+this.get_final_type());
    
    if (!this.is_visible()) {
        $(this.node).addClass('wui-hidden');
    }
    
    if (!this.is_enabled()) {
        $(this.node).addClass('wui-disabled');
    }
    
    this.title(this.title());      
    
    if (this.style()) {
        $(this.node).css(this.style());
    }
    
    var css_class_list = this.class_list();
    for (var i=0; i<css_class_list.length; i++) {
        var css_class = css_class_list[i];
        $(this.node).addClass(css_class);
    }
    
    if (this.size()[0]>0 || this.size()[1]>0) {
        $(this.node).css({'width':this.size()[0], 'height':this.size()[1]});
    }
    
    this._bind_events();

    // Refresh all children
    for(var i=0; i<this._child_list.length; i++) {
        this._child_list[i].refresh();
    }    
}

/**
 * Refresh the Widgets children.
 */
UI.Widget.prototype.refresh_children = function() {
    for(var i=0; i<this._child_list.length; i++) {
        this._child_list[i].refresh();
    }    
}

// Event management

/**
 * Bind internal events to the Widget.
 */
UI.Widget.prototype._bind_events = function() {}

/**
 * Bind a function to an event. Supported events are;
 * @param {String} event The name of the event to bind to.
 * @param {Object} data Additional data passed to the event handler as 
 * 'event.data'.
 * @param {Function} func A function called whenever the event is triggered. 
 */
UI.Widget.prototype.bind = function(event, data, func) {
    if (!this._event_map[event]) {
        this._event_map[event] = new Array();
    }
    this._event_map[event].push([data,func]);
}

/**
 * Unbind a function or all functions from an event or all events.
 * @param {String} event The event to remove a function/all functions from.
 * @param {Function} func The function to remove from an event (if not 
 * specified all functions are removed from the event).
 */
UI.Widget.prototype.unbind = function(event, func) {
    if (event) {
        if (func) {
            if (this._event_map[event]) {
                var clean_event_list = new Array();
                for (var i=0; i<this._event_map[event].length; i++ ) {
                    if (this._event_map[event][i][1]!=func) {
                        clean_event_list.push(this._event_map[event][i]);
                    }
                }
                this._event_map[event] = clean_event_list;
            }
        } else {
            delete this._event_map[event];
        }
    } else {
        for (var event in this._event_map) {
            this.unbind(event,func);
        }
    }
}

/**
 * Trigger and event.
 * @param {String} event The event to trigger.
 * @param {Object} data An object containing data about the event.
 */
UI.Widget.prototype.trigger = function(event, data) {
    if (this._event_map[event]) {
        for (var i=0; i<this._event_map[event].length; i++) {
            data.data = this._event_map[event][i][0];
            this._event_map[event][i][1](data);
        }
    }
}


/**
 * Create an Application class for UI management. (Imporant: This class behaves
 * as a singleton).
 * @class A class for UI management.
 * @constructor
 */  
UI.Application = function(options) {
    
    options = options || {};
    
    /**
     * The Widget that this Widget belongs to.
     * @type UI.Widget
     * @private
     */
    this._owner = null;

    /**
     * A list of children that this Widget owns.
     * @type Array
     * @private
     */
    this._child_list = new Array();

    /**
     * The Widgets ID.
     * @type String
     * @private
     */
    this._id = 'wui-application';

    /**
     * The ID of the element representing the Widget in the HTML DOM. 
     * @type String
     */
    this.element_id = this._id;
    
    /**
     * A flag indicating if the Widget is visible.
     * @type Boolean
     * @private
     */
    this._visible = true;

    /**
     * A flag indicating if the Widget is enabled.
     * @type Boolean
     * @private
     */
    this._enabled = true;
    
    /**
     * The title of the Widget. 
     * @type String
     * @private
     */
    this._title = options['title'] || '';

    /**
     * The title of the Widget. 
     * @type String
     * @private
     */
    this._size = options['size'] || [0,0];
    
    /**
     * The physical node in the HTML DOM. 
     * @type Element
     */
    this.node = null;
        
    // Define singleton behaviour
    if (UI._application!=null) {
        return UI._application;
    }
    UI._application = this;
    
    /**
     * Flag indicating if the application is running in modal mode.
     * @type Boolean
     * @private
     */
     this._modal = false;

    /**
     * Flag indicating if the application is busy.
     * @type Boolean
     * @private
     */
     this._busy = false;   
    
    /**
     * A flag used to remember the Applications modal state before it became
     * busy.
     * @type Boolean
     * @private
     */
     this._pre_busy = this._modal;  
     
    /**
     * Flag indicating if the application is visible or not.
     * @type Boolean
     * @private
     */
     this._visible = true;
    
    // Create the three core Widgets for the applications two modes; modeless 
    // and modal. The widgets are divided by a screen Widget which is required
    // when in modal mode.
    
    /**
     * The modeless Panel.
     * @type UI.Panel
     */
    this.modeless_panel = new UI.Panel(this,'wui-modeless');
    
    /**
     * The the height of modeless Panel. This value is used to determine the 
     * 'margin-top' added to body to ensure content is visible whilst in a
     * modeless state.
     * @type Number
     * @private
     */
    this._modeless_height = 32;
    
    /**
     * The modal screen.
     * @type UI.Panel
     * @private
     */
    this._modal_screen = new UI.Panel(this,'wui-modal-screen');
    this._modal_screen.show(false);
    
    /**
     * The modal Panel.
     * @type UI.Panel
     */
    this.modal_panel = new UI.Panel(this,'wui-modal');
    this.modal_panel.show(false);
    
    /**
     * Create a busy Animation.
     * @type UI.Animation
     * @private
     */
    this._busy_anim = new UI.Animation(this,
        'wui-busy-anim',
        {'frame_size':[64,64],'frame_count':8});
    
    /**
     * Create a notification Panel.
     * @type UI.Panel
     * @private
     */
    this._notify_panel = new UI.Panel(this,'wui-notification');

    /**
     * The view currently being displayed.
     * @type UI.View
     * @private
     */
    this._current_view = null;

    // Capture 'escape' key events for use in interface iteraction
    $(document).bind('keydown',this,
        function(ev){
            if (ev.data._modal && ev.data._current_view) {
                switch (ev.keyCode) {
                    case 27:
                        ev.data._current_view.show(false);
                        ev.data._current_view.trigger('close',this);
                        return false;
                }
            }
        });
    
    this.refresh();
}

// Inheritance
UI.Application.prototype = new UI.Widget;
UI.Application.prototype._is_a = ['widget','application'];

/**
 * Return the applications current view.
 * @return The applications current View.
 * @type UI.View
 */
UI.Application.prototype.get_current_view = function() {
    return this._current_view;
}

/**
 * Paint the Application.
 * @return The HTML for the widget.
 * @type String
 */
UI.Application.prototype._paint = function() {
    return '<div id="'+this.element_id+'" ></div>';
}

/**
 * Set/Get the modeless height of the Application.
 * @param {Number} height Optional. The modeless height.
 * @returns The Applications modeless height.
 * @type Array
 */
UI.Widget.prototype.modeless_height = function(size) {
    return this._attr('_modeless_height',size,'self');
}

/**
 * Refresh the Application.
 */
UI.Application.prototype.refresh = function() {
    this._pre_refresh();
    for(var i=0; i<this._child_list.length; i++) {
        this._child_list[i]._pre_refresh();
    }
    
    this.clear();
    $('body').append(this._paint());
    this.node = $('#'+this.element_id)[0];
    
    $(this.node).addClass('wui-widget');
    $(this.node).addClass('wui-'+this.get_final_type());
    
    if (!this.is_visible()) {
        $(this.node).addClass('wui-hidden');
    }
    
    if (!this.is_enabled()) {
        $(this.node).addClass('wui-disabled');
    }
    this._bind_events();

    // Refresh all children
    for(var i=0; i<this._child_list.length; i++) {
        this._child_list[i].refresh();
    }        
  
    if (this._visible) {
        $(this.node).css('display','block');
        $('body').css({'margin-top': this._modeless_height});
        this.modeless_panel.show(this._modeless_height!=0);
    } else {
        $(this.node).css('display','none');
    }
} 

/**
 * Set the Application to a modal state.
 * @return Whether the Application could be set into a modal state.
 * @type Boolean
 */
UI.Application.prototype.modal = function(modal) {
    if (modal===undefined||modal) {
        if (this._modal) {
            return false;
        }
        this.modeless_panel.show(false);
        this._modal_screen.show(true);
        this.modal_panel.show(true);
        this._modal = true;
        return true;        
    } 
    else {
        if (!this._modal) {
            return false;
        }
        this.modeless_panel.show(true);
        this._modal_screen.show(false);
        this.modal_panel.show(false);
        this._modal = false;
        return true;  
    }
}

/**
 * Post a notification.
 * @param {String} The notification message to post.
 */
UI.Application.prototype.notify = function(message) {
    var nofity_pos = $(this._notify_panel.node).position().top;
    var notify_height = $(this._notify_panel.node).outerHeight();
    
    // Handle previous notification if still visible
    if (nofity_pos>0-notify_height) {
        $(this._notify_panel.node).stop(true,false);
        $(this._notify_panel.node).stopTime();
        $(this._notify_panel.node).css('top',0-notify_height);
    }

    // Trigger the notification
    $(this._notify_panel.node).html(message);
    $(this._notify_panel.node).animate({top: '0px'},250,function() {
        $(this).oneTime(2500, function() {
            $(this).animate({top: '-'+$(this).outerHeight()+'px'},250);
        });
    });

    // If the notification is moused over then hide it
    $(this._notify_panel.node).one('mousemove',function(ev){
        $(this).stop(true,false);
        $(this).stopTime();
        $(this).css('top',0-$(this).outerHeight());        
    });
}

/**
 * Set the application as busy.
 * @param {Boolean} busy If true or undefined the Application set as busy, if
 * false the Application is released of being busy.
 * @param {Boolean} modal If true the Application will remain modal after 
 * busy mode has been left.
 */
UI.Application.prototype.busy = function(busy,modal) {
    if (busy===undefined||busy) {
        this._busy = true;
        this._busy_pre = this._modal || modal;
        
        // Ensure we are modal before going busy
        if (!this._modal) {
            this.modal();
        } else {
            // Hide any modal widgets
            this.modal_panel.show(false);
        }
        
        // Show busy overlay
        this._busy_anim.show();
        this._busy_anim.start();
        
        // Auto center the busy panel
        var x = $(window).width()/2 - $(this._busy_anim.node).outerWidth()/2;
        var y = $(window).height()/2 - $(this._busy_anim.node).outerHeight()/2;
        $(this._busy_anim.node).css({'top':y, 'left':x});
    } 
    else {
        this._busy = false;
        
        // Restore previous application state
        if (this._busy_pre) {
            this.modal_panel.show(true);
            this.modal();
        } else {
            this.modal(false);
        }
        
        // Show busy overlay
        this._busy_anim.stop();
        this._busy_anim.show(false);
    }    
}


/**
 * Ask the user to inform something.
 * @param {String} title The title of the information View.
 * @param {String} view A message displayed within the information View.
 * @param {Object} data Optional. Additional data passed to the callback 
 * function as the first argument.
 * @param {Function} callback A function called when the user has selected 
 * OK.
 */
UI.Application.prototype.inform = function(title, icon, message, data, callback) {
    // Ensure the application is in a modal state
    this.modal();
    
    // Hide any modal widgets
    this.modal_panel.show(false);
    
    var inform_view = new UI.View(this, null, {'title':title, 'icon':icon});
    inform_view.size([500, 'auto']);
    inform_view.content = function(node) {
        $(node).append('<div class="wui-area"></div>');
        var area_node = $(node).find('.wui-area:last');
        $(area_node).append(message);
    }
    var button_panel = new UI.Panel(inform_view,null,{css_list:['wui-break']});
    var ok_button = new UI.Button(button_panel,null,{
        text:'OK',
        icon:RESOURCES.ICONS.OK,
        css_list:['wui-center']
        });
    var function_call_data ={'app':app,
        'view':inform_view,
        'callback':callback,
        'data':data}
    ok_button.bind('click',function_call_data,function(ev) {
        ev.data.view.destroy();
        ev.data.app.modal_panel.show();
        ev.data.callback(true,ev.data.data);
    });
    inform_view.refresh();
    inform_view.show();
}

/**
 * Ask the user to confirm something.
 * @param {String} title The title of the confirmation View.
 * @param {String} view A message displayed within the confirmation View.
 * @param {Object} data Optional. Additional data passed to the callback 
 * function as the first argument.
 * @param {Function} callback A function called when the user has selected 
 * Yes/No.
 */
UI.Application.prototype.confirm = function(title,icon,message,data,callback) {
    // Ensure the application is in a modal state
    this.modal();
    
    // Hide any modal widgets
    this.modal_panel.show(false);
    
    var confirm_view = new UI.View(this,null,{'title':title,'icon':icon});
    confirm_view.size([500,'auto']);
    confirm_view.content = function(node) {
        $(node).append('<div class="wui-area"></div>');
        var area_node = $(node).find('.wui-area:last');
        $(area_node).append(message);
    }
    var button_panel = new UI.Panel(confirm_view,null,{css_list:['wui-break']});
    var confirm_button = new UI.Button(button_panel,null,{
        text:'Confirm',
        icon:RESOURCES.ICONS.OK,
        css_list:['wui-button-confirm']
        });
    var cancel_button = new UI.Button(button_panel,null,{
        text:'Cancel',
        icon:RESOURCES.ICONS.CANCEL,
        css_list:['wui-right']
        });
    var function_call_data ={'app':app,
        'view':confirm_view,
        'callback':callback,
        'data':data} 
    cancel_button.bind('click',function_call_data,function(ev) {
        ev.data.view.destroy();
        ev.data.app.modal_panel.show();
        ev.data.callback(false,ev.data.data);
    });
    confirm_button.bind('click',function_call_data,function(ev) {
        ev.data.view.destroy();
        ev.data.app.modal_panel.show();
        ev.data.callback(true,ev.data.data);
    });
    confirm_view.refresh();
    confirm_view.show();
}


/**
 * Create a new UI BreadcrumbPath. 
 * @class A UI BreadcrumbPath class.
 * @constructor
 */ 
UI.BreadcrumbPath = function(owner,id,options) {
    options = options || {};
    UI.Widget.call(this,owner,id,options);
    
    /**
     * A list of breadcrumbs in the path.
     * @type Array
     * @private
     */
    this._breadcrumb_list = new Array();
    
    this.refresh();
}

// Inheritance
UI.BreadcrumbPath.prototype = new UI.Widget;
UI.BreadcrumbPath.prototype._is_a = ['widget','breadcrumb-path'];

/**
 * Paint the Breadcrumb path.
 * @return The HTML for the widget.
 * @type String
 */
UI.BreadcrumbPath.prototype._paint = function() {
    return '<ul id="'+this.element_id+'" ><li class="wui-title">Nav</li></ul>';
}

/**
 * Clear the breadcrumb path.
 */
UI.BreadcrumbPath.prototype.clear_path = function() {
    this._breadcrumb_list = new Array();
    this.refresh();
}

/**
 * Add a breadcrumb to the path.
 * @param {UI.Breadcrumb}
 */
UI.BreadcrumbPath.prototype.add_breadcrumb = function(breadcrumb) {
    this._breadcrumb_list.push(breadcrumb);
    this.refresh();
}

/**
 * Remove a breadcrumb from the path.
 * @param {UI.Breadcrumb}
 */
UI.BreadcrumbPath.prototype.remove_breadcrumb = function(breadcrumb) {
    var breadcrumb_index = this._breadcrumb_list.indexOf(breadcrumb);
    if (breadcrumb_index>-1) {
        this._breadcrumb_list.splice(breadcrumb_index,1);
    }
    this.refresh();
}

/**
 * Refresh the BreadcrumbPath.
 */
UI.BreadcrumbPath.prototype.refresh = function() {
    UI.Panel.prototype.refresh.call(this);
    for (var i=0; i<this._breadcrumb_list.length; i++) {
        var breadcrumb = this._breadcrumb_list[i];
        $(this.node).append(breadcrumb._paint());
        if (breadcrumb._action) {
            var breadcrumb_node = $(this.node).find('.wui-breadcrumb:last');
            breadcrumb_node.addClass('puesdo-link');
            $(breadcrumb_node).bind('click',breadcrumb,breadcrumb._action);
        }
    }
}

/**
 * Create a new UI Breadcrumb. 
 * @class A UI Breadcrumb class.
 * @param {String} A label for the crumb.
 * @param {Function} An action to form when the crumb is clicked.
 * @constructor
 */ 
UI.Breadcrumb = function(label,action) {
    
    /**
     * The label displayed for a breadcrumb in the path.
     * @type String
     * @private
     */
    this._label = label;
    
    /**
     * An action (function) called when the breadcrumb is clicked on in the
     * path.
     * @type Function
     * @private
     */
    this._action = action || null;
}

/**
 * Paint the Breadcrumb.
 * @return The HTML for the widget.
 * @type String
 */
UI.Breadcrumb.prototype._paint = function() {
    return '<li class="wui-breadcrumb" >'+this._label+'</li>';
}


/**
 * Create a new UI Panel. 
 * @class A UI Panel class.
 * @constructor
 */ 
UI.Panel = function(owner,id,options) {
    options = options || {};
    UI.Widget.call(this,owner,id,options);
    this.refresh();
}

// Inheritance
UI.Panel.prototype = new UI.Widget;
UI.Panel.prototype._is_a = ['widget','panel'];

/**
 * Paint the Panel.
 * @return The HTML for the widget.
 * @type String
 */
UI.Panel.prototype._paint = function() {
    return '<div id="'+this.element_id+'" ></div>';
}

/**
 * Bind events to the Panel.
 */
UI.Panel.prototype._bind_events = function() {
    $(this.node).bind('mouseover',this,
        function(ev){
            if (ev.data.is_enabled()) {
                ev.data.trigger('mouseover', ev.data);
            }
        });
    $(this.node).bind('mouseout',this,
        function(ev){
            if (ev.data.is_enabled()) {
                ev.data.trigger('mouseout', ev.data);
            }
        });
    $(this.node).bind('mousedown',this,
        function(ev){
            if (ev.data.is_enabled()) {
                ev.data.trigger('mousedown', ev.data);
            }
        });
    $(this.node).bind('mouseup',this,
        function(ev){
            if (ev.data.is_enabled()) {
                ev.data.trigger('mouseup', ev.data);
            }
        });
    $(this.node).bind('click',this,
        function(ev) {
            if (ev.data.is_enabled()) {
                ev.data.trigger('click', ev.data);
            }
        });
}


/**
 * Create a new UI Menu. 
 * @class A UI Menu class.
 * @constructor
 */ 
UI.Menu = function(owner,id,options) {
    options = options || {};
    UI.Widget.call(this,owner,id,options);
    
    /*
    * A flag that determines if the menu is open or closed.
    * @type Boolean
    * @private
    */
    this._collapsed = true;
    
    this.refresh();
    this.collapse(this._collapsed);
}

// Inheritance
UI.Menu.prototype = new UI.Panel;
UI.Menu.prototype._is_a = ['widget','panel','menu'];

/**
 * Paint the Menu.
 * @return The HTML for the widget.
 * @type String
 */
UI.Menu.prototype._paint = function() {
    return '<ul id="'+this.element_id+'" ></ul>';
}

/**
 * Bind events to the Menu.
 */
UI.Menu.prototype._bind_events = function() {
    UI.Widget.prototype._bind_events.call(this);
    $(this.node).bind('mouseover',this,
        function(ev){
            if (ev.data.is_enabled()) {
                if (ev.data._owner && ev.data._owner.is_a('menu-item')) {
                    $(ev.data._owner.node).addClass('wui-hover');
                    $(ev.data._owner.node).addClass('wui-active');
                }            
                ev.data.collapse(false);
                return false;
            }
        });
    $(this.node).bind('mouseout',this,
        function(ev){
            if (ev.data.is_enabled()) {
                if (ev.data._owner && ev.data._owner.is_a('menu-item')) {
                    $(ev.data._owner.node).removeClass('wui-hover');
                    $(ev.data._owner.node).removeClass('wui-active');
                }        
                ev.data.collapse();
                return false;
            }
        });
}

/**
 * Collapse/Expand the Menu.
 * @param {Boolean} Optional. If true or undefined the Menu is collapsed, if
 * false the Menu is expanded.
 */
UI.Menu.prototype.collapse = function(collapse) {
    if (this._owner.is_a('menu-item')) {
        if (collapse===undefined||collapse) {
            this._collapsed = true;
            this.show(false);
        } 
        else {
            this._collapsed = false;
            this.show();
        }
    } else {
        this._collapsed = false;
        this.show();    
    }
}

/**
 * Create a new UI Select. 
 * @class A UI Select class.
 * @constructor
 */ 
UI.Select = function(owner,id,options) {
    options = options || {};
    UI.Widget.call(this,owner,id,options);
 
    /**
     * A flag that determines if the Select has the mouse.
     * @type Boolean
     * @private
     */
     this._has_mouse = false;
 
    /**
     * A flag that determines if the select is open or closed.
     * @type Boolean
     * @private
     */
    this._collapsed = true;
    
    this.refresh();
    this.collapse(this._collapsed);
}

// Inheritance
UI.Select.prototype = new UI.Panel;
UI.Select.prototype._is_a = ['widget','panel','select'];

/**
 * Paint the Select.
 * @return The HTML for the widget.
 * @type String
 */
UI.Select.prototype._paint = function() {
    html = '<ul id="'+this.element_id+'" ><li class="wui-option wui-value-option">';
    var option = this.selected_option();
    if (option) {
        html += this.title()+': <em>' + option.text() + '</em>';
    } else {
        html += this.title();
    }
    html += '</li></ul>';
    return html
}

/**
 * Bind events to the Select.
 */
UI.Select.prototype._bind_events = function() {
    UI.Widget.prototype._bind_events.call(this);
    $(this.node).find('.wui-value-option').bind('mousedown',this,
        function(ev){
            if (ev.data.is_enabled() && ev.data._collapsed) {
                ev.data.collapse(false);
                return false;
            } else {
                ev.data.collapse();
                return false;                
            }
        });
    $(this.node).bind('mouseover mousemove',this,
        function(ev){
            ev.data._has_mouse = true;
        });
    $(this.node).bind('mouseout',this,
        function(ev){
            ev.data._has_mouse = false;
        });
    $('body').bind('mousedown', this, function(ev){
        if (ev.data.is_enabled() && !ev.data._collapsed && !ev.data.has_mouse()) {
            ev.data.collapse();
        }
    });
}

/**
 * Refresh the Select.
 */
UI.Select.prototype.refresh = function() {
    UI.Panel.prototype.refresh.call(this);
    if (this._collapsed) {
        $(this.node).addClass('collapsed');
    } else {
        $(this.node).removeClass('collapsed');
    }
}

/**
 * Return true if the Select has the mouse.
 */
UI.Select.prototype.has_mouse = function() {
    if (this._has_mouse) {
        return true;
    }
    for (var i=0; i<this._child_list.length; i++) {
        var child = this._child_list[i];
        if (child._has_mouse) {
            return true;
        }
    }    
    return false;
}

/**
 * Collapse/Expand the Select.
 * @param {Boolean} Optional. If true or undefined the Select is collapsed, if
 * false the Select is expanded.
 */
UI.Select.prototype.collapse = function(collapse) {
    if (collapse===undefined||collapse) {
        this._collapsed = true;
        $(this.node).addClass('collapsed');
    } else {
        this._collapsed = false;
        $(this.node).removeClass('collapsed');
    }
}

/**
 * Destroy the Select.
 */
UI.Select.prototype.destroy = function() {
    UI.Widget.prototype.destroy.call(this);
}

/**
 * Set/Get the value for the Select.
 * @param {String} value Optional. The Selects value.
 * @returns The Selects value.
 * @type String
 */
UI.Select.prototype.value = function(value, trigger) {
    if (value===undefined) {
        return this._value;
    } else {
        if (this._value!=value) {
            this._value = value;
            this.refresh();
            if (trigger || trigger === undefined) {
                this.trigger('change', this);
            }
        }
    }
}

/**
 * Get the selected Option for the Select.
 * @returns The selected Option for the Select.
 * @type UI.Option
 */
UI.Select.prototype.selected_option = function(value) {
    for (var i=0; i<this._child_list.length; i++) {
        var child = this._child_list[i];
        if (child.is_a('option') && child.value() == this.value()) {
            return child;
        }
    }       
}

/**
 * Create a new UI Toolbar. 
 * @class A UI Toolbar class.
 * @constructor
 */ 
UI.Toolbar = function(owner,id,options) {
    options = options || {};
    UI.Widget.call(this,owner,id,options);
    
    /**
    * A flag that determines if the toolbar is open or closed.
    * @type Boolean
    * @private
    */
    this._collapsed = true;
    this.refresh();

    this.collapse(this._collapsed);

    /**
    * The initial position of the toolbar from the top of the body.
    * @type Number
    * @private
    */
    this._top = 0;
    
    /**
    * The initial height of the toolbar.
    * @type Number
    * @private
    */
    this._height = 0;
}

// Inheritance
UI.Toolbar.prototype = new UI.Panel;
UI.Toolbar.prototype._is_a = ['widget','panel','toolbar'];

/**
 * Paint the Toolbar.
 * @return The HTML for the widget.
 * @type String
 */
UI.Toolbar.prototype._paint = function() {
    return '<ul id="'+this.element_id+'" ></ul>';
}

/**
 * Refresh the Toolbar.
 */
UI.Toolbar.prototype.refresh = function() {
    UI.Panel.prototype.refresh.call(this);
    if (this._collapsed) {
        $(this.node).append('<li class="wui-toolbar-tab"></li>');
        var tabber_node = $(this.node).find('.wui-toolbar-tab');
        if (this.icon()) {
            $(tabber_node).append(this.icon().paint_as_image());
        }    
        $(tabber_node).append(this.title());
        this._bind_events();
    }
}

/**
 * Collapse/Expand the Toolbar.
 * @param {Boolean} Optional. If true or undefined the Toolbar is collapsed, if
 * false the Toolbar is expanded.
 */
UI.Toolbar.prototype.collapse = function(collapse) {
    var _this = this;
    
    if (collapse==this._collapsed) {
        return;
    }
    
    if (collapse===undefined||collapse) {
        $(this.node).animate({top: this._top}, 250, function() {
            _this.style({top: this._top});
            _this._collapsed = true;
            _this.trigger('collapse', _this);
            UI.get_application().refresh();
        });   
    } 
    else {
        this._top = $(_this.node).position()['top'];
        this._height = $(_this.node).innerHeight();
        
        $(this.node).animate({top: this._height}, 250, function() {
            _this.style({top: _this._height});
            _this._collapsed = false;
            _this.trigger('expand', _this);
            UI.get_application().refresh();
        });        
    }
}

/**
 * Bind events to the Toolbar.
 */
UI.Toolbar.prototype._bind_events = function() {
    UI.Widget.prototype._bind_events.call(this);
    $(this.node).find('.wui-toolbar-tab').bind('mouseover',this,
        function(ev){
            $(this).addClass('wui-hover');
        });
    $(this.node).find('.wui-toolbar-tab').bind('mouseout',this,
        function(ev){
            $(this).removeClass('wui-hover');
        });
    $(this.node).find('.wui-toolbar-tab').bind('click',this,
        function(ev){
            ev.data.collapse(!ev.data._collapsed);
        });
}

/**
 * Create a new UI Separator. 
 * @class A UI Separator class.
 * @constructor
 */ 
UI.Separator = function(owner,id,options) {
    options = options || {};
    UI.Panel.call(this,owner,id,options);
}

// Inheritance
UI.Separator.prototype = new UI.Panel;
UI.Separator.prototype._is_a = ['widget','panel','separator'];

/**
 * Paint the Separator.
 * @return The HTML for the widget.
 * @type String
 */
UI.Separator.prototype._paint = function() {
    var element_type = '';
    
    if (this.get_owner().is_a('menu') || this.get_owner().is_a('toolbar')) {
        element_type = 'li'
    } else {
        element_type = 'div';
    }
    
    return '<'+element_type+' id="'+this.element_id+'"></'+element_type+'>';
}

/**
 * Create a new UI View. 
 * @class A UI View class.
 * @constructor
 */ 
UI.View = function(owner,id,options) {
    options = options || {};
    UI.Widget.call(this,owner,id,options);
    
    /**
     * A flag that determines if the view can be closed by the user.
     * @type Boolean
     * @private
     */
    this._can_close = options['can_close'] || false;

    /**
     * A Panel at the top of the View.
     * @type UI.Panel
     */
    this.top_panel = new UI.Panel(this);
    
    /**
     * The panel in which content is rendered. 
     * @type Element
     */
    this.content_panel = new UI.Panel(this,null,{css_list:['wui-view-content']});
    
    /**
     * A Panel at the bottom of the View.
     * @type UI.Panel
     */
    this.bottom_panel = new UI.Panel(this);
    
    /**
     * A BreadcrumbPath for of the View.
     * @type UI.BreadcrumbPath
     */
    this.breadcrumb_path = new UI.BreadcrumbPath(this); 
    this.breadcrumb_path.show(false);
    
    /**
     * A function called to render the content.
     * @type Function
     */
    this.content = null;
    
    this.refresh();
    
    UI.get_application()._current_view = this;
}

// Inheritance
UI.View.prototype = new UI.Panel;
UI.View.prototype._is_a = ['widget','panel','view'];

/**
 * Calculate and apply the max-height for the Views content.
 */
UI.View.prototype.calc_max_height = function() {
    $(this.content_panel.node).css({'max-height':'', 'overflow':'hidden'});
    var window_height = $(window).height();
    var view_height = $(this.node).outerHeight(true);
    if (view_height > window_height) {
        var content_height = $(this.content_panel.node).outerHeight();
        var max_height = window_height-(view_height-content_height);
        $(this.content_panel.node).css({'max-height':max_height, 
            'overflow':'auto'});
    }
}

/**
 * Paint the View.
 * @return The HTML for the Widget.
 * @type String
 */
UI.View.prototype._paint = function() {
    var html = '<div id="'+this.element_id+'" >';
    html += '<h2 class="wui-title">'+this._paint_title()+'</h2>';
    this._paint_title();
    if (this._can_close) {
        html += '<span class="wui-close">Close</span>';
    }
    html += '</div>';
    return html;
}

/**
 * Paint the Views title.
 * @return The HTML for the Widgets title.
 * @type String
 */
UI.View.prototype._paint_title = function() {
    var icon_html = '';
    if (this.icon()) {
        icon_html += this.icon().paint_as_image();
    }     
    return icon_html+this.title();
}

/**
 * Bind events to the View.
 */
UI.View.prototype._bind_events = function() {
    UI.Panel.prototype._bind_events.call(this);

    // Centre and calculate the views scrolls max-height everytime the window
    // is resized.
    $(window).bind('resize',this,
        function(ev){
            ev.data.calc_max_height();
            ev.data.centre();
        });

    $(this.node).find('.wui-close').bind('click',this,
        function(ev){
            ev.data.show(false);
            ev.data.trigger('close',ev.data);
        });
}

/**
 * Set/Get the title for the View.
 * @param {String} title Optional. The Views title.
 * @returns The Views title.
 * @type String
 */
UI.View.prototype.title = function(title) {
    if (title===undefined) {
        return this._title;
    } else {
        this._title = title;
        $(this.node).find('h2.wui-title:first').html(this._paint_title());
    }
}

/**
 * Set/Get the icon for the View.
 * @param {UI.Icon} icon Optional. The Views Icon.
 * @returns The Views Icon.
 * @type UI.Icon
 */
UI.View.prototype.icon = function(icon) {
    if (icon===undefined) {
        return this._icon;
    } else {
        this._icon = icon;
        $(this.node).find('.wui-title').html(this._paint_title());
    }
}

/**
 * Refresh the View.
 */
UI.View.prototype.refresh = function() {
    UI.Panel.prototype.refresh.call(this);
    if (this.content) {
        this.content(this.content_panel.node);
    }
    this.calc_max_height();
    this.centre();
}

/**
 * Refresh the Views 'content' only.
 */
UI.View.prototype.refresh_content = function() {
    $(this.content_panel.node).html('');
    if (this.content) {
        this.content(this.content_panel.node);
    }
    this.calc_max_height();
    this.centre();
}

/**
 * Centre the View.
 */
UI.View.prototype.centre = function() {
    // Auto centre the view
    var x = $(window).width()/2 - $(this.node).outerWidth(true)/2;
    var y = $(window).height()/2 - $(this.node).outerHeight(true)/2;
    $(this.node).css({'top':y, 'left':x});
}

/**
 * Show/Hide the View.
 * @param {Boolean} Optional. If true or undefined the Widget is shown, if
 * false the Widget is hidden.
 */
UI.View.prototype.show = function(show) {
    if (show===undefined||show) {
        $(this.node).removeClass('wui-hidden');
        this._visible = true;
        UI.get_application()._current_view = this;
    } 
    else {
        $(this.node).addClass('wui-hidden');
        this._visible = false;
    }
}


/**
 * Create a new UI Animation. 
 * @class A UI Animation class.
 * @constructor
 */ 
UI.Animation = function(owner,id,options) {
    options = options || {};
    UI.Widget.call(this,owner,id,options);
    
    /**
     * A timer for tracking the animation. 
     * @type Timer
     * @private
     */
    this._timer = null;    
    
    /**
     * The size of each frame in the Animation. 
     * @type Array
     */
    this.frame_size = options['frame_size']||new Array(16,16);    
    
    /**
     * The number of frames in the Animation. 
     * @type Number
     */
    this.frame_count = options['frame_count']||1;
    
    /**
     * The number of milliseconds to delay between each frame whilst playing 
     * the Animation. 
     * @type Number
     */
    this.delay = options['delay']||120;
    
    this.refresh();
}

// Inheritance
UI.Animation.prototype = new UI.Panel;
UI.Animation.prototype._is_a = ['widget','panel','animation'];

/**
 * Start the Animation.
 */ 
UI.Animation.prototype.start = function() {
    var animation_width = this.frame_size[0]*(this.frame_count-1);
    var animation_itter = this.frame_size[0];
    $(this).css('background-position','0px 0px');
    this.show();
    this.size(this.frame_size);
    this._timer = $(this.node).everyTime(this.delay,function() {
        var background_position = $.trim($(this).backgroundPosition());
        var animation_pos = 0;
        if (background_position) {
            animation_pos = background_position.match(/^(-*\d+)(%|px)\s+(\d+)(%|px)$/i)[1];
            animation_pos = (parseInt(animation_pos))-animation_itter;
        }
        if (animation_pos<0-animation_width) {
            animation_pos = 0;
        }
        $(this).css('background-position',String(animation_pos)+'px 0px');
    });
}

/**
 * Stop the Animation.
 */ 
UI.Animation.prototype.stop = function() {
    if (this._timer) {
        this._timer.stopTime();
    }
    this.show(false);
}


/**
 * Create a new UI Button. 
 * @class A UI Button class.
 * @constructor
 */ 
UI.Button = function(owner,id,options) {
    options = options || {};
    
    /**
     * The Buttons text.
     * @type String
     * @private
     */
    this._text = options['text'];

    /**
     * A flag that determines if the Button is currently toggled.
     * @type Boolean
     * @private 
     */
    this._toggled = false;

    if (this._text===undefined && !options['icon']) {
        this._text = UI.Button.DEFAULT_TEXT;
    }
    
    UI.Panel.call(this,owner,id,options);
}

// Inheritance
UI.Button.prototype = new UI.Panel;
UI.Button.prototype._is_a = ['widget','panel','button'];

/**
 * The default Button text.
 * @final
 * @type String
 */
UI.Button.DEFAULT_TEXT = 'Button';

/**
 * Paint the Button.
 */
UI.Button.prototype._paint = function() {
    var html = '<div id="'+this.element_id+'" title="'+this.title()+'">';
    if (this.icon()) {
        html += this.icon().paint_as_image();
    }
    if (this.text()) {
        if (this.icon()) {
            html += '<span class="wui-label">'+this.text()+'</span>';
        } else {
            html += '<span class="wui-label wui-center">'+this.text()+'</span>';
        }
    }
    html += '</div>';
    return html;
}

/**
 * Bind events to the Button.
 */
UI.Button.prototype._bind_events = function() {
    UI.Panel.prototype._bind_events.call(this);
    $(this.node).bind('mouseover',this,
        function(ev){
            if (ev.data.is_enabled()) {
                $(this).addClass('wui-hover');
            }
        });
    $(this.node).bind('mouseout',this,
        function(ev){
            if (ev.data.is_enabled()) {
                $(this).removeClass('wui-hover');
                $(this).removeClass('wui-active');
            }
        });
    $(this.node).bind('mousedown',this,
        function(ev){
            if (ev.data.is_enabled()) {
                $(this).addClass('wui-active');
            }
        });
    $(this.node).bind('mouseup',this,
        function(ev){
            if (ev.data.is_enabled()) {
                $(this).removeClass('wui-active');
            }
        });
}

/**
 * Set/Get the text for the Button.
 * @param {String} text Optional. The Buttons text.
 * @returns The Buttons text.
 * @type String
 */
UI.Button.prototype.text = function(text) {
    return this._attr('_text',text,'self');
}

/**
 * Return True if the Button is currently toggled.
 * @return Whether the Button is toggled or not.
 * @type Boolean
 **/
UI.Button.prototype.toggled = function(value) {
    if (value===undefined) {
        return this._toggled;
    } else {
        if (this._toggled!=value) {
            this._toggled = value;
            if (this._toggled) {
                $(this.node).addClass('wui-toggled');
            } else {
                $(this.node).removeClass('wui-toggled');
            }
        }
    }    
}

UI.Button.prototype.refresh = function() {
    UI.Panel.prototype.refresh.call(this);
    if (this._toggled) {
        $(this.node).addClass('wui-toggled');
    } else {
        $(this.node).removeClass('wui-toggled');
    }    
}

/**
 * Create a new UI MenuItem. 
 * @class A UI MenuItem class.
 * @constructor
 */ 
UI.MenuItem = function(owner,id,options) {
    options = options || {};
    UI.Button.call(this,owner,id,options);
}

// Inheritance
UI.MenuItem.prototype = new UI.Button;
UI.MenuItem.prototype._is_a = ['widget','panel','button','menu-item'];

/**
 * Paint the MenuItem.
 * @return The HTML for the widget.
 * @type String
 */
UI.MenuItem.prototype._paint = function() {
    var html = '<li id="'+this.element_id+'" >';
    if (this.icon()) {
        html += this.icon().paint_as_image();
    }
    if (this.text()) {
        if (this.icon()) {
            html += '<span class="wui-label wui-right">'+this.text()+'</span>';
        } else {
            html += '<span class="wui-label wui-center">'+this.text()+'</span>';
        }
    }
    html += '</li>';
    return html;
}

/**
 * Bind events to the Menu item.
 */
UI.MenuItem.prototype._bind_events = function() {
    UI.Panel.prototype._bind_events.call(this);
    $(this.node).bind('mousedown',this,
        function(ev){
            if (ev.data.is_enabled()) {
                $(this).addClass('wui-active');
            }
        });
    $(this.node).bind('mouseup',this,
        function(ev){
            if (ev.data.is_enabled()) {
                $(this).removeClass('wui-active');
            }
        });
    $(this.node).bind('mouseover',this,
        function(ev){
            if (ev.data.is_enabled()) {
                $(this).addClass('wui-hover');
                for (var i=0; i<ev.data._child_list.length; i++) {
                    var child = ev.data._child_list[i];
                    if (child.is_a('menu')) {
                        child.collapse(false);
                    }
                }       
            }
        });
    $(this.node).bind('mouseout',this,
        function(ev){            
            if (ev.data.is_enabled()) { 
                $(this).removeClass('wui-hover');
                $(this).removeClass('wui-active');
                if (ev.data._owner.is_a('menu')) {
                    ev.data._owner.collapse();
                    if (ev.data._owner._owner && ev.data._owner._owner.is_a('menu-item')) {
                        $(ev.data._owner._owner.node).removeClass('wui-hover');
                        $(ev.data._owner._owner.node).removeClass('wui-active');
                    }
                }
                for (var i=0; i<ev.data._child_list.length; i++) {
                    var child = ev.data._child_list[i];
                    if (child.is_a('menu')) {
                        child.collapse();
                    }
                }    
                return false;
            }
        });
    $(this.node).bind('click',this,
        function(ev){
            if (ev.data.is_enabled()) {
                if (ev.data._owner._owner && ev.data._owner._owner.is_a('menu-item')) {
                    ev.data._owner.collapse();
                }
            }
        });
}

/**
 * Create a new UI Option. 
 * @class A UI Option class.
 * @constructor
 */ 
UI.Option = function(owner,id,options) {
    options = options || {};
    UI.Button.call(this,owner,id,options);
    
    /**
     * The value of the widget.
     * @type String
     * @private
     */
    this._value = options['value'] || '';
    
    /**
     * A flag that determines if the Option has the mouse.
     * @type Boolean
     * @private
     */
     this._has_mouse = false;    
}

// Inheritance
UI.Option.prototype = new UI.Button;
UI.Option.prototype._is_a = ['widget','panel','button','option'];

/**
 * Paint the Option.
 * @return The HTML for the widget.
 * @type String
 */
UI.Option.prototype._paint = function() {
    var html = '<li id="'+this.element_id+'" >';
    if (this.icon()) {
        html += this.icon().paint_as_image();
    }
    if (this.text()) {
        if (this.icon()) {
            html += '<span class="wui-label wui-right">'+this.text()+'</span>';
        } else {
            html += '<span class="wui-label wui-center">'+this.text()+'</span>';
        }
    }
    html += '</li>';
    return html;
}

/**
 * Bind events to the Menu item.
 */
UI.Option.prototype._bind_events = function() {
    UI.Panel.prototype._bind_events.call(this);
    $(this.node).bind('mouseover',this,
        function(ev){
            if (ev.data.is_enabled()) {
                $(this).addClass('wui-hover');
            }
            ev.data._has_mouse = true;
        });
    $(this.node).bind('mouseout',this,
        function(ev){
            if (ev.data.is_enabled()) {
                $(this).removeClass('wui-hover');
            }
            ev.data._has_mouse = false;
        });
    $(this.node).bind('click',this,
        function(ev){
            if (ev.data.is_enabled()) {
                $(ev.data._owner.node).scrollTop(0);
                ev.data._owner.collapse();
                ev.data._owner.value(ev.data.value());
                return false;
            }
        });
}

/**
 * Set/Get the value for the Option.
 * @param {String} value Optional. The Options value.
 * @returns The Options value.
 * @type String
 */
UI.Option.prototype.value = function(value) {
    return this._attr('_value', value, 'attribute');
}

/**
 * Create a new UI ToolbarItem. 
 * @class A UI ToolbarItem class.
 * @constructor
 */ 
UI.ToolbarButton = function(owner,id,options) {
    options = options || {};
    UI.Button.call(this,owner,id,options);
}

// Inheritance
UI.ToolbarButton.prototype = new UI.Button;
UI.ToolbarButton.prototype._is_a = ['widget','panel','button','toolbar-button'];


/**
 * Create a new UI Group. A Group Widget is useful for grouping together 
 * multiple Widgets so that they can be managed as group. 
 * @class A UI Group class.
 * @constructor
 */ 
UI.Group = function(owner,id,options) {
    options = options || {};
    UI.Widget.call(this,owner,id,options);
    this.refresh();
}

// Inheritance
UI.Group.prototype = new UI.Widget;
UI.Group.prototype._is_a = ['widget','group'];


/**
 * Create a new UI Icon. 
 * @class A UI Icon class.
 * @constructor
 */ 
UI.Icon = function(src,alt,position,size,title) {

    /**
     * A map of Widgets this Icon belongs to.
     * @type Object
     * @private
     */
    this._owner_map = new Array();
    
    /**
     * The image source for the Icon.
     * @type String
     * @private
     */
    this._src = src;
    
    /**
     * The alternative text for the Icon image.
     * @type String
     * @private
     */
    this._alt = alt || '';
    
    /**
     * The position of the Icon image (only applicable to CSS sprites).
     * @type Array
     * @private
     */
    this._position = position || [0,0];
    
    /**
     * The size of the Icon image.
     * @type Array
     * @private
     */
    this._size = size || UI.Icon.DEFAULT_SIZE;
    
    /**
     * The size of the Icon image.
     * @type Array
     * @private
     */
    this._size = size || UI.Icon.DEFAULT_SIZE;
    
    /**
     * The title for the Icon.
     * @type String
     * @private
     */
    this._title = title || '';
}

/** 
 * The default size an Icon.
 * @final
 * @type Array
 */
UI.Icon.DEFAULT_SIZE = [16,16];

/**
 * Set/Get an attribute for the Icon.
 * @param {String} attr The Icon attribute to get or set.
 * @param {*} value Optional. The value of the attribute to set.
 * @returns The Icon attribute value.
 * @type String
 */
UI.Icon.prototype._attr = function(attr,value) {
    if (value===undefined) {
        return this[attr];
    } else {
        if (this[attr]!=value) {
            this[attr]=value;
            this.refresh();
        }
    }
}

/**
 * Set/Get the image source for the Icon.
 * @param {String} src Optional. The image source.
 * @returns The image source.
 * @type String
 */
UI.Icon.prototype.src = function(src) {
    return this._attr('_src',src);
}

/**
 * Set/Get the alternative text for the Icon.
 * @param {String} alt Optional. The alternative text.
 * @returns The alternative text.
 * @type String
 */
UI.Icon.prototype.alt = function(alt) {
    return this._attr('_alt',alt);
}

/**
 * Set/Get the position for the Icon image.
 * @param {Array} position Optional. The position of the image.
 * @returns The position of the image.
 * @type Array
 */
UI.Icon.prototype.position = function(position) {
    return this._attr('_position',position);
}

/**
 * Set/Get the size for the Icon.
 * @param {Array} size Optional. The size of the Icon.
 * @returns The size of the Icon.
 * @type Array
 */
UI.Icon.prototype.size = function(size) {
    return this._attr('_size',size);
}

/**
 * Set/Get the title for the Icon.
 * @param {String} title Optional. The title.
 * @returns The title.
 * @type String
 */
UI.Icon.prototype.title = function(title) {
    return this._attr('_title',title);
}

/**
 * Register an owner of the Icon.
 * @param {UI.Widget} owner The Widget that will share the ownership of this 
 * Icon.
 */
UI.Icon.prototype.register_owner = function(owner) {
    this._owner_map[owner.id] = owner;
}

/**
 * Unregister an owner from the Icon.
 * @param {UI.Widget} owner The Widget that will no longer share the ownership
 * of this  Icon.
 */
UI.Icon.prototype.unregister_owner = function(owner) {
    this._owner_map[owner.id] = undefined;
}

/**
 * Refresh the icon.
 */
UI.Icon.prototype.refresh = function() {
    for (var owner_id in this._owner_map) {
        this._owner_map[owner_id].refresh();
    }
}

/**
 * Return the Icon as an image in HTML.
 * @return The HTML for the widget.
 * @type String
 */
UI.Icon.prototype.paint_as_image = function() {
    var image_html = '<img src="'+this.src()+'" alt="'+this.alt()+'"';
    if (this.title()) { 
        image_html += ' title="'+this.title()+'"';
    }
    return image_html+' class="wui-icon" />';
}
