1 /*
  2 Copyright (c) 2003-2012, CKSource - Frederico Knabben. All rights reserved.
  3 For licensing, see LICENSE.html or http://ckeditor.com/license
  4 */
  5 
  6 CKEDITOR.plugins.add( 'contextmenu',
  7 {
  8 	requires : [ 'menu' ],
  9 
 10 	// Make sure the base class (CKEDITOR.menu) is loaded before it (#3318).
 11 	onLoad : function()
 12 	{
 13 		CKEDITOR.plugins.contextMenu = CKEDITOR.tools.createClass(
 14 		{
 15 			base : CKEDITOR.menu,
 16 
 17 			$ : function( editor )
 18 			{
 19 				this.base.call( this, editor,
 20 				{
 21 					panel:
 22 					{
 23 						className : editor.skinClass + ' cke_contextmenu',
 24 						attributes :
 25 						{
 26 							'aria-label' : editor.lang.contextmenu.options
 27 						}
 28 					}
 29 				});
 30 			},
 31 
 32 			proto :
 33 			{
 34 				addTarget : function( element, nativeContextMenuOnCtrl )
 35 				{
 36 					// Opera doesn't support 'contextmenu' event, we have duo approaches employed here:
 37 					// 1. Inherit the 'button override' hack we introduced in v2 (#4530), while this require the Opera browser
 38 					//  option 'Allow script to detect context menu/right click events' to be always turned on.
 39 					// 2. Considering the fact that ctrl/meta key is not been occupied
 40 					//  for multiple range selecting (like Gecko), we use this key
 41 					//  combination as a fallback for triggering context-menu. (#4530)
 42 					if ( CKEDITOR.env.opera && !( 'oncontextmenu' in document.body ))
 43 					{
 44 						var contextMenuOverrideButton;
 45 						element.on( 'mousedown', function( evt )
 46 						{
 47 							evt = evt.data;
 48 							if ( evt.$.button != 2 )
 49 							{
 50 								if ( evt.getKeystroke() == CKEDITOR.CTRL + 1 )
 51 									element.fire( 'contextmenu', evt );
 52 								return;
 53 							}
 54 
 55 							if ( nativeContextMenuOnCtrl
 56 								 && ( CKEDITOR.env.mac ? evt.$.metaKey : evt.$.ctrlKey ) )
 57 								return;
 58 
 59 							var target = evt.getTarget();
 60 
 61 							if ( !contextMenuOverrideButton )
 62 							{
 63 								var ownerDoc =  target.getDocument();
 64 								contextMenuOverrideButton = ownerDoc.createElement( 'input' ) ;
 65 								contextMenuOverrideButton.$.type = 'button' ;
 66 								ownerDoc.getBody().append( contextMenuOverrideButton ) ;
 67 							}
 68 
 69 							contextMenuOverrideButton.setAttribute( 'style', 'position:absolute;top:' + ( evt.$.clientY - 2 ) +
 70 								'px;left:' + ( evt.$.clientX - 2 ) +
 71 								'px;width:5px;height:5px;opacity:0.01' );
 72 
 73 						} );
 74 
 75 						element.on( 'mouseup', function ( evt )
 76 						{
 77 							if ( contextMenuOverrideButton )
 78 							{
 79 								contextMenuOverrideButton.remove();
 80 								contextMenuOverrideButton = undefined;
 81 								// Simulate 'contextmenu' event.
 82 								element.fire( 'contextmenu', evt.data );
 83 							}
 84 						} );
 85 					}
 86 
 87 					element.on( 'contextmenu', function( event )
 88 						{
 89 							var domEvent = event.data;
 90 
 91 							if ( nativeContextMenuOnCtrl &&
 92 								 // Safari on Windows always show 'ctrlKey' as true in 'contextmenu' event,
 93 								// which make this property unreliable. (#4826)
 94 								 ( CKEDITOR.env.webkit ? holdCtrlKey : ( CKEDITOR.env.mac ? domEvent.$.metaKey : domEvent.$.ctrlKey ) ) )
 95 								return;
 96 
 97 
 98 							// Cancel the browser context menu.
 99 							domEvent.preventDefault();
100 
101 							var offsetParent = domEvent.getTarget().getDocument().getDocumentElement(),
102 								offsetX = domEvent.$.clientX,
103 								offsetY = domEvent.$.clientY;
104 
105 							CKEDITOR.tools.setTimeout( function()
106 								{
107 									this.open( offsetParent, null, offsetX, offsetY );
108 
109 								// IE needs a short while to allow selection change before opening menu. (#7908)
110 								}, CKEDITOR.env.ie? 200 : 0, this );
111 						},
112 						this );
113 
114 					if ( CKEDITOR.env.opera )
115 					{
116 						// 'contextmenu' event triggered by Windows menu key is unpreventable,
117 						// cancel the key event itself. (#6534)
118 						element.on( 'keypress' , function ( evt )
119 						{
120 							var domEvent = evt.data;
121 
122 							if ( domEvent.$.keyCode === 0 )
123 								domEvent.preventDefault();
124 						});
125 					}
126 
127 					if ( CKEDITOR.env.webkit )
128 					{
129 						var holdCtrlKey,
130 							onKeyDown = function( event )
131 							{
132 								holdCtrlKey = CKEDITOR.env.mac ? event.data.$.metaKey : event.data.$.ctrlKey ;
133 							},
134 							resetOnKeyUp = function()
135 							{
136 								holdCtrlKey = 0;
137 							};
138 
139 						element.on( 'keydown', onKeyDown );
140 						element.on( 'keyup', resetOnKeyUp );
141 						element.on( 'contextmenu', resetOnKeyUp );
142 					}
143 				},
144 
145 				open : function( offsetParent, corner, offsetX, offsetY )
146 				{
147 					this.editor.focus();
148 					offsetParent = offsetParent || CKEDITOR.document.getDocumentElement();
149 					this.show( offsetParent, corner, offsetX, offsetY );
150 				}
151 			}
152 		});
153 	},
154 
155 	beforeInit : function( editor )
156 	{
157 		editor.contextMenu = new CKEDITOR.plugins.contextMenu( editor );
158 
159 		editor.addCommand( 'contextMenu',
160 			{
161 				exec : function()
162 					{
163 						editor.contextMenu.open( editor.document.getBody() );
164 					}
165 			});
166 	}
167 });
168 
169 /**
170  * Whether to show the browser native context menu when the <em>Ctrl</em> or
171  * <em>Meta</em> (Mac) key is pressed on opening the context menu with the
172  * right mouse button click or the <em>Menu</em> key.
173  * @name CKEDITOR.config.browserContextMenuOnCtrl
174  * @since 3.0.2
175  * @type Boolean
176  * @default <code>true</code>
177  * @example
178  * config.browserContextMenuOnCtrl = false;
179  */
180