1 /*
  2 Copyright (c) 2003-2009, CKSource - Frederico Knabben. All rights reserved.
  3 For licensing, see LICENSE.html or http://ckeditor.com/license
  4 */
  5
  6 CKEDITOR.plugins.add( 'richcombo',
  7 {
  8 	requires : [ 'floatpanel', 'listblock', 'button' ],
  9
 10 	beforeInit : function( editor )
 11 	{
 12 		editor.ui.addHandler( CKEDITOR.UI_RICHCOMBO, CKEDITOR.ui.richCombo.handler );
 13 	}
 14 });
 15
 16 /**
 17  * Button UI element.
 18  * @constant
 19  * @example
 20  */
 21 CKEDITOR.UI_RICHCOMBO = 3;
 22
 23 CKEDITOR.ui.richCombo = CKEDITOR.tools.createClass(
 24 {
 25 	$ : function( definition )
 26 	{
 27 		// Copy all definition properties to this object.
 28 		CKEDITOR.tools.extend( this, definition,
 29 			// Set defaults.
 30 			{
 31 				title : definition.label,
 32 				modes : { wysiwyg : 1 }
 33 			});
 34
 35 		// We don't want the panel definition in this object.
 36 		var panelDefinition = this.panel || {};
 37 		delete this.panel;
 38
 39 		this.id = CKEDITOR.tools.getNextNumber();
 40
 41 		this.document = ( panelDefinition
 42 							&& panelDefinition.parent
 43 							&& panelDefinition.parent.getDocument() )
 44 						|| CKEDITOR.document;
 45
 46 		panelDefinition.className = ( panelDefinition.className || '' ) + ' cke_rcombopanel';
 47
 48 		this._ =
 49 		{
 50 			panelDefinition : panelDefinition,
 51 			items : {},
 52 			state : CKEDITOR.TRISTATE_OFF
 53 		};
 54 	},
 55
 56 	statics :
 57 	{
 58 		handler :
 59 		{
 60 			create : function( definition )
 61 			{
 62 				return new CKEDITOR.ui.richCombo( definition );
 63 			}
 64 		}
 65 	},
 66
 67 	proto :
 68 	{
 69 		renderHtml : function( editor )
 70 		{
 71 			var output = [];
 72 			this.render( editor, output );
 73 			return output.join( '' );
 74 		},
 75
 76 		/**
 77 		 * Renders the combo.
 78 		 * @param {CKEDITOR.editor} editor The editor instance which this button is
 79 		 *		to be used by.
 80 		 * @param {Array} output The output array to which append the HTML relative
 81 		 *		to this button.
 82 		 * @example
 83 		 */
 84 		render : function( editor, output )
 85 		{
 86 			var id = 'cke_' + this.id;
 87 			var clickFn = CKEDITOR.tools.addFunction( function( $element )
 88 				{
 89 					var _ = this._;
 90
 91 					if ( _.state == CKEDITOR.TRISTATE_DISABLED )
 92 						return;
 93
 94 					this.createPanel( editor );
 95
 96 					if ( _.on )
 97 					{
 98 						_.panel.hide();
 99 						return;
100 					}
101
102 					if ( !_.committed )
103 					{
104 						_.list.commit();
105 						_.committed = 1;
106 					}
107
108 					var value = this.getValue();
109 					if ( value )
110 						_.list.mark( value );
111 					else
112 						_.list.unmarkAll();
113
114 					_.panel.showBlock( this.id, new CKEDITOR.dom.element( $element ), 4 );
115 				},
116 				this );
117
118 			var instance = {
119 				id : id,
120 				combo : this,
121 				focus : function()
122 				{
123 					var element = CKEDITOR.document.getById( id ).getChild( 1 );
124 					element.focus();
125 				},
126 				execute : clickFn
127 			};
128
129 			editor.on( 'mode', function()
130 				{
131 					this.setState( this.modes[ editor.mode ] ? CKEDITOR.TRISTATE_OFF : CKEDITOR.TRISTATE_DISABLED );
132 				},
133 				this );
134
135 			var keyDownFn = CKEDITOR.tools.addFunction( function( ev, element )
136 				{
137 					ev = new CKEDITOR.dom.event( ev );
138
139 					var keystroke = ev.getKeystroke();
140 					switch ( keystroke )
141 					{
142 						case 13 :	// ENTER
143 						case 32 :	// SPACE
144 						case 40 :	// ARROW-DOWN
145 							// Show panel
146 							CKEDITOR.tools.callFunction( clickFn, element );
147 							break;
148 						default :
149 							// Delegate the default behavior to toolbar button key handling.
150 							instance.onkey( instance,  keystroke );
151 					}
152
153 					// Avoid subsequent focus grab on editor document.
154 					ev.preventDefault();
155 				});
156
157 			output.push(
158 				'<span class="cke_rcombo">',
159 				'<span id=', id );
160
161 			if ( this.className )
162 				output.push( ' class="', this.className, ' cke_off"');
163
164 			output.push(
165 				'>' +
166 					'<span class=cke_label>', this.label, '</span>' +
167 					'<a hidefocus=true title="', this.title, '" tabindex="-1" href="javascript:void(\'', this.label, '\')"' );
168
169 			// Some browsers don't cancel key events in the keydown but in the
170 			// keypress.
171 			// TODO: Check if really needed for Gecko+Mac.
172 			if ( CKEDITOR.env.opera || ( CKEDITOR.env.gecko && CKEDITOR.env.mac ) )
173 			{
174 				output.push(
175 					' onkeypress="return false;"' );
176 			}
177
178 			// With Firefox, we need to force it to redraw, otherwise it
179 			// will remain in the focus state.
180 			if ( CKEDITOR.env.gecko )
181 			{
182 				output.push(
183 					' onblur="this.style.cssText = this.style.cssText;"' );
184 			}
185
186 			output.push(
187 					' onkeydown="CKEDITOR.tools.callFunction( ', keyDownFn, ', event, this );"' +
188 					' onclick="CKEDITOR.tools.callFunction(', clickFn, ', this); return false;">' +
189 						'<span>' +
190 							'<span class="cke_accessibility">' + ( this.voiceLabel ? this.voiceLabel + ' ' : '' ) + '</span>' +
191 							'<span id="' + id + '_text" class="cke_text cke_inline_label">' + this.label + '</span>' +
192 						'</span>' +
193 						'<span class=cke_openbutton></span>' +
194 					'</a>' +
195 				'</span>' +
196 				'</span>' );
197
198 			if ( this.onRender )
199 				this.onRender();
200
201 			return instance;
202 		},
203
204 		createPanel : function( editor )
205 		{
206 			if ( this._.panel )
207 				return;
208
209 			var panelDefinition = this._.panelDefinition,
210 				panelParentElement = panelDefinition.parent || CKEDITOR.document.getBody(),
211 				panel = new CKEDITOR.ui.floatPanel( editor, panelParentElement, panelDefinition ),
212 				list = panel.addListBlock( this.id, this.multiSelect ),
213 				me = this;
214
215 			panel.onShow = function()
216 				{
217 					if ( me.className )
218 						this.element.getFirst().addClass( me.className + '_panel' );
219
220 					me.setState( CKEDITOR.TRISTATE_ON );
221
222 					list.focus( !me.multiSelect && me.getValue() );
223
224 					me._.on = 1;
225
226 					if ( me.onOpen )
227 						me.onOpen();
228 				};
229
230 			panel.onHide = function()
231 				{
232 					if ( me.className )
233 						this.element.getFirst().removeClass( me.className + '_panel' );
234
235 					me.setState( CKEDITOR.TRISTATE_OFF );
236
237 					me._.on = 0;
238
239 					if ( me.onClose )
240 						me.onClose();
241 				};
242
243 			panel.onEscape = function()
244 				{
245 					panel.hide();
246 					me.document.getById( 'cke_' + me.id ).getFirst().getNext().focus();
247 				};
248
249 			list.onClick = function( value, marked )
250 				{
251 					// Move the focus to the main windows, otherwise it will stay
252 					// into the floating panel, even if invisible, and Safari and
253 					// Opera will go a bit crazy.
254 					me.document.getWindow().focus();
255
256 					if ( me.onClick )
257 						me.onClick.call( me, value, marked );
258
259 					if ( marked )
260 						me.setValue( value, me._.items[ value ] );
261 					else
262 						me.setValue( '' );
263
264 					panel.hide();
265 				};
266
267 			this._.panel = panel;
268 			this._.list = list;
269
270 			panel.getBlock( this.id ).onHide = function()
271 				{
272 					me._.on = 0;
273 					me.setState( CKEDITOR.TRISTATE_OFF );
274 				};
275
276 			if ( this.init )
277 				this.init();
278 		},
279
280 		setValue : function( value, text )
281 		{
282 			this._.value = value;
283
284 			var textElement = this.document.getById( 'cke_' + this.id + '_text' );
285
286 			if ( !value )
287 			{
288 				text = this.label;
289 				textElement.addClass( 'cke_inline_label' );
290 			}
291 			else
292 				textElement.removeClass( 'cke_inline_label' );
293 			textElement.setHtml( typeof text != 'undefined' ? text : value );
294 		},
295
296 		getValue : function()
297 		{
298 			return this._.value || '';
299 		},
300
301 		unmarkAll : function()
302 		{
303 			this._.list.unmarkAll();
304 		},
305
306 		mark : function( value )
307 		{
308 			this._.list.mark( value );
309 		},
310
311 		hideItem : function( value )
312 		{
313 			this._.list.hideItem( value );
314 		},
315
316 		hideGroup : function( groupTitle )
317 		{
318 			this._.list.hideGroup( groupTitle );
319 		},
320
321 		showAll : function()
322 		{
323 			this._.list.showAll();
324 		},
325
326 		add : function( value, html, text )
327 		{
328 			this._.items[ value ] = text || value;
329 			this._.list.add( value, html, text );
330 		},
331
332 		startGroup : function( title )
333 		{
334 			this._.list.startGroup( title );
335 		},
336
337 		commit : function()
338 		{
339 			this._.list.commit();
340 		},
341
342 		setState : function( state )
343 		{
344 			if ( this._.state == state )
345 				return;
346
347 			this.document.getById( 'cke_' + this.id ).setState( state );
348
349 			this._.state = state;
350 		}
351 	}
352 });
353
354 CKEDITOR.ui.prototype.addRichCombo = function( name, definition )
355 {
356 	this.add( name, CKEDITOR.UI_RICHCOMBO, definition );
357 };
358