2013-08-05 23:36:10 +00:00
|
|
|
/*!
|
|
|
|
* VisualEditor UserInterface ToolGroup class.
|
|
|
|
*
|
|
|
|
* @copyright 2011-2013 VisualEditor Team and others; see AUTHORS.txt
|
|
|
|
* @license The MIT License (MIT); see LICENSE.txt
|
|
|
|
*/
|
|
|
|
|
|
|
|
/**
|
|
|
|
* UserInterface tool group.
|
|
|
|
*
|
|
|
|
* @class
|
|
|
|
* @abstract
|
|
|
|
* @extends ve.ui.Widget
|
|
|
|
* @mixins ve.ui.GroupElement
|
|
|
|
*
|
2013-08-27 23:28:29 +00:00
|
|
|
* Tools can be specified in the following ways:
|
|
|
|
* - A specific tool: `{ 'name': 'tool-name' }` or `'tool-name'`
|
|
|
|
* - All tools in a group: `{ 'group': 'group-name' }`
|
|
|
|
* - All tools: `'*'`
|
2013-08-09 18:29:09 +00:00
|
|
|
*
|
2013-08-05 23:36:10 +00:00
|
|
|
* @constructor
|
|
|
|
* @param {ve.ui.Toolbar} toolbar
|
|
|
|
* @param {Object} [config] Config options
|
2013-08-27 23:28:29 +00:00
|
|
|
* @cfg {Array|string} [include=[]] List of tools to include
|
|
|
|
* @cfg {Array|string} [exclude=[]] List of tools to exclude
|
|
|
|
* @cfg {Array|string} [promote=[]] List of tools to promote to the beginning
|
|
|
|
* @cfg {Array|string} [demote=[]] List of tools to demote to the end
|
2013-08-05 23:36:10 +00:00
|
|
|
*/
|
|
|
|
ve.ui.ToolGroup = function VeUiToolGroup( toolbar, config ) {
|
2013-08-09 18:29:09 +00:00
|
|
|
// Configuration initialization
|
|
|
|
config = config || {};
|
|
|
|
|
2013-08-05 23:36:10 +00:00
|
|
|
// Parent constructor
|
|
|
|
ve.ui.Widget.call( this, config );
|
|
|
|
|
|
|
|
// Mixin constructors
|
2013-08-27 23:28:29 +00:00
|
|
|
ve.ui.GroupElement.call( this, this.$$( '<div>' ) );
|
2013-08-05 23:36:10 +00:00
|
|
|
|
|
|
|
// Properties
|
|
|
|
this.toolbar = toolbar;
|
2013-08-09 18:29:09 +00:00
|
|
|
this.tools = {};
|
2013-08-27 23:28:29 +00:00
|
|
|
this.pressed = null;
|
2013-08-09 18:29:09 +00:00
|
|
|
this.include = config.include || [];
|
|
|
|
this.exclude = config.exclude || [];
|
|
|
|
this.promote = config.promote || [];
|
|
|
|
this.demote = config.demote || [];
|
2013-08-27 23:28:29 +00:00
|
|
|
this.onCapturedMouseUpHandler = ve.bind( this.onCapturedMouseUp, this );
|
2013-08-05 23:36:10 +00:00
|
|
|
|
|
|
|
// Events
|
2013-08-27 23:28:29 +00:00
|
|
|
this.$.on( {
|
|
|
|
'mousedown': ve.bind( this.onMouseDown, this ),
|
|
|
|
'mouseup': ve.bind( this.onMouseUp, this ),
|
|
|
|
'mouseover': ve.bind( this.onMouseOver, this ),
|
|
|
|
'mouseout': ve.bind( this.onMouseOut, this )
|
|
|
|
} );
|
2013-08-09 18:29:09 +00:00
|
|
|
ve.ui.toolFactory.connect( this, { 'register': 'onToolFactoryRegister' } );
|
2013-08-27 23:28:29 +00:00
|
|
|
ve.ui.triggerRegistry.connect( this, { 'register': 'onTriggerRegistryRegister' } );
|
2013-08-05 23:36:10 +00:00
|
|
|
|
|
|
|
// Initialization
|
2013-08-27 23:28:29 +00:00
|
|
|
this.$group.addClass( 've-ui-toolGroup-tools' );
|
|
|
|
this.$
|
|
|
|
.addClass( 've-ui-toolGroup' )
|
|
|
|
.append( this.$group );
|
|
|
|
this.populate();
|
2013-08-05 23:36:10 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
/* Inheritance */
|
|
|
|
|
|
|
|
ve.inheritClass( ve.ui.ToolGroup, ve.ui.Widget );
|
|
|
|
|
|
|
|
ve.mixinClass( ve.ui.ToolGroup, ve.ui.GroupElement );
|
2013-08-09 18:29:09 +00:00
|
|
|
|
2013-08-27 23:28:29 +00:00
|
|
|
/* Events */
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @event update
|
|
|
|
*/
|
|
|
|
|
|
|
|
/* Static Properties */
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Show title in tooltip.
|
|
|
|
*
|
|
|
|
* @static
|
|
|
|
* @property {string}
|
|
|
|
* @inheritable
|
|
|
|
*/
|
|
|
|
ve.ui.ToolGroup.static.showTitle = false;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Show trigger in tooltip.
|
|
|
|
*
|
|
|
|
* @static
|
|
|
|
* @property {string}
|
|
|
|
* @inheritable
|
|
|
|
*/
|
|
|
|
ve.ui.ToolGroup.static.showTrigger = false;
|
|
|
|
|
2013-08-09 18:29:09 +00:00
|
|
|
/* Methods */
|
|
|
|
|
2013-08-27 23:28:29 +00:00
|
|
|
/**
|
|
|
|
* Handle mouse down events.
|
|
|
|
*
|
|
|
|
* @method
|
|
|
|
* @param {jQuery.Event} e Mouse down event
|
|
|
|
*/
|
|
|
|
ve.ui.ToolGroup.prototype.onMouseDown = function ( e ) {
|
|
|
|
if ( !this.disabled && e.which === 1 ) {
|
|
|
|
this.pressed = this.getTargetTool( e );
|
|
|
|
if ( this.pressed ) {
|
|
|
|
this.pressed.setActive( true );
|
|
|
|
this.getElementDocument().addEventListener(
|
|
|
|
'mouseup', this.onCapturedMouseUpHandler, true
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Handle captured mouse up events.
|
|
|
|
*
|
|
|
|
* @method
|
|
|
|
* @param {Event} e Mouse up event
|
|
|
|
*/
|
|
|
|
ve.ui.ToolGroup.prototype.onCapturedMouseUp = function ( e ) {
|
|
|
|
this.getElementDocument().removeEventListener( 'mouseup', this.onCapturedMouseUpHandler, true );
|
|
|
|
// onMouseUp may be called a second time, depending on where the mouse is when the button is
|
|
|
|
// released, but since `this.pressed` will no longer be true, the second call will be ignored.
|
|
|
|
this.onMouseUp( e );
|
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Handle mouse up events.
|
|
|
|
*
|
|
|
|
* @method
|
|
|
|
* @param {jQuery.Event} e Mouse up event
|
|
|
|
*/
|
|
|
|
ve.ui.ToolGroup.prototype.onMouseUp = function ( e ) {
|
|
|
|
var tool = this.getTargetTool( e );
|
|
|
|
|
|
|
|
if ( !this.disabled && e.which === 1 && this.pressed && this.pressed === tool ) {
|
|
|
|
this.pressed.onSelect();
|
|
|
|
}
|
|
|
|
|
|
|
|
this.pressed = null;
|
|
|
|
return false;
|
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Handle mouse over events.
|
|
|
|
*
|
|
|
|
* @method
|
|
|
|
* @param {jQuery.Event} e Mouse over event
|
|
|
|
*/
|
|
|
|
ve.ui.ToolGroup.prototype.onMouseOver = function ( e ) {
|
|
|
|
var tool = this.getTargetTool( e );
|
|
|
|
|
|
|
|
if ( this.pressed && this.pressed === tool ) {
|
|
|
|
this.pressed.setActive( true );
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Handle mouse out events.
|
|
|
|
*
|
|
|
|
* @method
|
|
|
|
* @param {jQuery.Event} e Mouse out event
|
|
|
|
*/
|
|
|
|
ve.ui.ToolGroup.prototype.onMouseOut = function ( e ) {
|
|
|
|
var tool = this.getTargetTool( e );
|
|
|
|
|
|
|
|
if ( this.pressed && this.pressed === tool ) {
|
|
|
|
this.pressed.setActive( false );
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Get the closest tool to a jQuery.Event.
|
|
|
|
*
|
|
|
|
* @method
|
|
|
|
* @private
|
|
|
|
* @param {jQuery.Event} e
|
|
|
|
* @returns {ve.ui.Tool|null} Tool, `null` if none was found
|
|
|
|
*/
|
|
|
|
ve.ui.ToolGroup.prototype.getTargetTool = function ( e ) {
|
|
|
|
var $item = $( e.target ).closest( '.ve-ui-tool' );
|
|
|
|
if ( $item.length ) {
|
|
|
|
return $item.data( 've-ui-tool' );
|
|
|
|
}
|
|
|
|
return null;
|
|
|
|
};
|
|
|
|
|
2013-08-09 18:29:09 +00:00
|
|
|
/**
|
|
|
|
* Handle tool registry register events.
|
|
|
|
*
|
2013-08-27 23:28:29 +00:00
|
|
|
* If a tool is registered after the group is created, we must repopulate the list to account for:
|
|
|
|
* - a tool being added that may be included
|
|
|
|
* - a tool already included being overridden
|
2013-08-09 18:29:09 +00:00
|
|
|
*
|
|
|
|
* @param {string} name Symbolic name of tool
|
|
|
|
*/
|
|
|
|
ve.ui.ToolGroup.prototype.onToolFactoryRegister = function () {
|
2013-08-27 23:28:29 +00:00
|
|
|
this.populate();
|
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Handle trigger registry register events.
|
|
|
|
*
|
|
|
|
* If a trigger is registered after the tool is loaded, this handler will ensure matching tools'
|
|
|
|
* titles are updated to reflect the available key command for the tool.
|
|
|
|
*
|
|
|
|
* @param {string} name Symbolic name of trigger
|
|
|
|
*/
|
|
|
|
ve.ui.ToolGroup.prototype.onTriggerRegistryRegister = function ( name ) {
|
|
|
|
if ( this.tools[name] ) {
|
|
|
|
this.updateToolTitle( name );
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Update the title of a tool.
|
|
|
|
*
|
|
|
|
* @param {string} name Tool name
|
|
|
|
* @chainable
|
|
|
|
*/
|
|
|
|
ve.ui.ToolGroup.prototype.updateToolTitle = function ( name ) {
|
|
|
|
var tool, trigger, labelMessage, labelText,
|
|
|
|
showTitle = this.constructor.static.showTitle,
|
|
|
|
showTrigger = this.constructor.static.showTrigger;
|
|
|
|
|
|
|
|
tool = this.tools[name];
|
|
|
|
if ( tool ) {
|
|
|
|
labelText = '';
|
|
|
|
if ( showTitle ) {
|
|
|
|
labelMessage = tool.constructor.static.titleMessage;
|
|
|
|
if ( labelMessage ) {
|
|
|
|
labelText += ve.msg( labelMessage );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if ( showTrigger ) {
|
|
|
|
trigger = ve.ui.triggerRegistry.lookup( tool.constructor.static.name );
|
|
|
|
if ( trigger ) {
|
|
|
|
labelText += ' [' + trigger.getMessage() + ']';
|
|
|
|
}
|
|
|
|
}
|
|
|
|
tool.setTitle( labelText );
|
|
|
|
}
|
|
|
|
return this;
|
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Get the toolbar this group is in.
|
|
|
|
*
|
|
|
|
* @return {ve.ui.Toolbar} Toolbar of group
|
|
|
|
*/
|
|
|
|
ve.ui.ToolGroup.prototype.getToolbar = function () {
|
|
|
|
return this.toolbar;
|
2013-08-09 18:29:09 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Add and remove tools based on configuration.
|
|
|
|
*
|
|
|
|
* @method
|
|
|
|
*/
|
2013-08-27 23:28:29 +00:00
|
|
|
ve.ui.ToolGroup.prototype.populate = function () {
|
2013-08-09 18:29:09 +00:00
|
|
|
var i, len, name, tool,
|
|
|
|
names = {},
|
2013-08-27 23:28:29 +00:00
|
|
|
add = [],
|
|
|
|
remove = [],
|
|
|
|
list = ve.ui.toolFactory.getTools( this.include, this.exclude, this.promote, this.demote );
|
2013-08-09 18:29:09 +00:00
|
|
|
|
|
|
|
// Build a list of needed tools
|
|
|
|
for ( i = 0, len = list.length; i < len; i++ ) {
|
|
|
|
name = list[i];
|
2013-08-27 23:28:29 +00:00
|
|
|
if ( this.toolbar.isToolAvailable( name ) ) {
|
|
|
|
tool = this.tools[name];
|
|
|
|
if ( !tool ) {
|
|
|
|
// Auto-initialize tools on first use
|
|
|
|
this.tools[name] = tool = ve.ui.toolFactory.create( name, this.toolbar );
|
|
|
|
this.updateToolTitle( name );
|
|
|
|
}
|
|
|
|
this.toolbar.reserveTool( name );
|
|
|
|
add.push( tool );
|
|
|
|
names[name] = true;
|
2013-08-09 18:29:09 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
// Remove tools that are no longer needed
|
|
|
|
for ( name in this.tools ) {
|
|
|
|
if ( !names[name] ) {
|
|
|
|
this.tools[name].destroy();
|
2013-08-27 23:28:29 +00:00
|
|
|
this.toolbar.releaseTool( name );
|
|
|
|
remove.push( this.tools[name] );
|
2013-08-09 18:29:09 +00:00
|
|
|
delete this.tools[name];
|
|
|
|
}
|
|
|
|
}
|
2013-08-27 23:28:29 +00:00
|
|
|
if ( remove.length ) {
|
|
|
|
this.removeItems( remove );
|
|
|
|
}
|
|
|
|
// Update emptiness state
|
|
|
|
if ( add.length ) {
|
|
|
|
this.$.removeClass( 've-ui-toolGroup-empty' );
|
|
|
|
} else {
|
|
|
|
this.$.addClass( 've-ui-toolGroup-empty' );
|
|
|
|
}
|
2013-08-09 18:29:09 +00:00
|
|
|
// Re-add tools (moving existing ones to new locations)
|
2013-08-27 23:28:29 +00:00
|
|
|
this.addItems( add );
|
2013-08-09 18:29:09 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Destroy tool group.
|
|
|
|
*
|
|
|
|
* @method
|
|
|
|
*/
|
|
|
|
ve.ui.ToolGroup.prototype.destroy = function () {
|
|
|
|
var name;
|
|
|
|
|
|
|
|
this.clearItems();
|
|
|
|
ve.ui.toolFactory.disconnect( this );
|
|
|
|
for ( name in this.tools ) {
|
2013-08-27 23:28:29 +00:00
|
|
|
this.toolbar.releaseTool( name );
|
|
|
|
this.tools[name].disconnect( this ).destroy();
|
|
|
|
delete this.tools[name];
|
2013-08-09 18:29:09 +00:00
|
|
|
}
|
|
|
|
this.$.remove();
|
|
|
|
};
|