JasonWoof Got questions, comments, patches, etc.? Contact Jason Woofenden
b009dee02344480c88f80fdbbb243358aa9f716b
[ckeditor.git] / _source / plugins / contextmenu / plugin.js
1 /*\r
2 Copyright (c) 2003-2010, CKSource - Frederico Knabben. All rights reserved.\r
3 For licensing, see LICENSE.html or http://ckeditor.com/license\r
4 */\r
5 \r
6 CKEDITOR.plugins.add( 'contextmenu',\r
7 {\r
8         requires : [ 'menu' ],\r
9 \r
10         beforeInit : function( editor )\r
11         {\r
12                 editor.contextMenu = new CKEDITOR.plugins.contextMenu( editor );\r
13 \r
14                 editor.addCommand( 'contextMenu',\r
15                         {\r
16                                 exec : function()\r
17                                         {\r
18                                                 editor.contextMenu.show( editor.document.getBody() );\r
19                                         }\r
20                         });\r
21         }\r
22 });\r
23 \r
24 CKEDITOR.plugins.contextMenu = CKEDITOR.tools.createClass(\r
25 {\r
26         $ : function( editor )\r
27         {\r
28                 this.id = 'cke_' + CKEDITOR.tools.getNextNumber();\r
29                 this.editor = editor;\r
30                 this._.listeners = [];\r
31                 this._.functionId = CKEDITOR.tools.addFunction( function( commandName )\r
32                         {\r
33                                 this._.panel.hide();\r
34                                 editor.focus();\r
35                                 editor.execCommand( commandName );\r
36                         },\r
37                         this);\r
38 \r
39                 this.definition =\r
40                 {\r
41                         panel:\r
42                         {\r
43                                 className : editor.skinClass + ' cke_contextmenu',\r
44                                 attributes :\r
45                                 {\r
46                                         'aria-label' : editor.lang.contextmenu.options\r
47                                 }\r
48                         }\r
49                 };\r
50         },\r
51 \r
52         _ :\r
53         {\r
54                 onMenu : function( offsetParent, corner, offsetX, offsetY )\r
55                 {\r
56                         var menu = this._.menu,\r
57                                 editor = this.editor;\r
58 \r
59                         if ( menu )\r
60                         {\r
61                                 menu.hide();\r
62                                 menu.removeAll();\r
63                         }\r
64                         else\r
65                         {\r
66                                 menu = this._.menu = new CKEDITOR.menu( editor, this.definition );\r
67                                 menu.onClick = CKEDITOR.tools.bind( function( item )\r
68                                 {\r
69                                         menu.hide();\r
70 \r
71                                         if ( item.onClick )\r
72                                                 item.onClick();\r
73                                         else if ( item.command )\r
74                                                 editor.execCommand( item.command );\r
75 \r
76                                 }, this );\r
77 \r
78                                 menu.onEscape = function( keystroke )\r
79                                 {\r
80                                         var parent = this.parent;\r
81                                         // 1. If it's sub-menu, restore the last focused item\r
82                                         // of upper level menu.\r
83                                         // 2. In case of a top-menu, close it.\r
84                                         if ( parent )\r
85                                         {\r
86                                                 parent._.panel.hideChild();\r
87                                                 // Restore parent block item focus.\r
88                                                 var parentBlock = parent._.panel._.panel._.currentBlock,\r
89                                                         parentFocusIndex =  parentBlock._.focusIndex;\r
90                                                 parentBlock._.markItem( parentFocusIndex );\r
91                                         }\r
92                                         else if ( keystroke == 27 )\r
93                                         {\r
94                                                 this.hide();\r
95                                                 editor.focus();\r
96                                         }\r
97                                         return false;\r
98                                 };\r
99                         }\r
100 \r
101                         var listeners = this._.listeners,\r
102                                 includedItems = [];\r
103 \r
104                         var selection = this.editor.getSelection(),\r
105                                 element = selection && selection.getStartElement();\r
106 \r
107                         menu.onHide = CKEDITOR.tools.bind( function()\r
108                                 {\r
109                                         menu.onHide = null;\r
110 \r
111                                         if ( CKEDITOR.env.ie )\r
112                                         {\r
113                                                 var selection = editor.getSelection();\r
114                                                 selection && selection.unlock();\r
115                                         }\r
116 \r
117                                         this.onHide && this.onHide();\r
118                                 },\r
119                                 this );\r
120 \r
121                         // Call all listeners, filling the list of items to be displayed.\r
122                         for ( var i = 0 ; i < listeners.length ; i++ )\r
123                         {\r
124                                 var listenerItems = listeners[ i ]( element, selection );\r
125 \r
126                                 if ( listenerItems )\r
127                                 {\r
128                                         for ( var itemName in listenerItems )\r
129                                         {\r
130                                                 var item = this.editor.getMenuItem( itemName );\r
131 \r
132                                                 if ( item )\r
133                                                 {\r
134                                                         item.state = listenerItems[ itemName ];\r
135                                                         menu.add( item );\r
136                                                 }\r
137                                         }\r
138                                 }\r
139                         }\r
140 \r
141                         // Don't show context menu with zero items.\r
142                         menu.items.length && menu.show( offsetParent, corner || ( editor.lang.dir == 'rtl' ? 2 : 1 ), offsetX, offsetY );\r
143                 }\r
144         },\r
145 \r
146         proto :\r
147         {\r
148                 addTarget : function( element, nativeContextMenuOnCtrl )\r
149                 {\r
150                         // Opera doesn't support 'contextmenu' event, we have duo approaches employed here:\r
151                         // 1. Inherit the 'button override' hack we introduced in v2 (#4530), while this require the Opera browser\r
152                         //  option 'Allow script to detect context menu/right click events' to be always turned on.\r
153                         // 2. Considering the fact that ctrl/meta key is not been occupied\r
154                         //  for multiple range selecting (like Gecko), we use this key\r
155                         //  combination as a fallback for triggering context-menu. (#4530)\r
156                         if ( CKEDITOR.env.opera )\r
157                         {\r
158                                 var contextMenuOverrideButton;\r
159                                 element.on( 'mousedown', function( evt )\r
160                                 {\r
161                                         evt = evt.data;\r
162                                         if ( evt.$.button != 2 )\r
163                                         {\r
164                                                 if ( evt.getKeystroke() == CKEDITOR.CTRL + 1 )\r
165                                                         element.fire( 'contextmenu', evt );\r
166                                                 return;\r
167                                         }\r
168 \r
169                                         if ( nativeContextMenuOnCtrl\r
170                                                  && ( CKEDITOR.env.mac ? evt.$.metaKey : evt.$.ctrlKey ) )\r
171                                                 return;\r
172 \r
173                                         var target = evt.getTarget();\r
174 \r
175                                         if ( !contextMenuOverrideButton )\r
176                                         {\r
177                                                 var ownerDoc =  target.getDocument();\r
178                                                 contextMenuOverrideButton = ownerDoc.createElement( 'input' ) ;\r
179                                                 contextMenuOverrideButton.$.type = 'button' ;\r
180                                                 ownerDoc.getBody().append( contextMenuOverrideButton ) ;\r
181                                         }\r
182 \r
183                                         contextMenuOverrideButton.setAttribute( 'style', 'position:absolute;top:' + ( evt.$.clientY - 2 ) +\r
184                                                 'px;left:' + ( evt.$.clientX - 2 ) +\r
185                                                 'px;width:5px;height:5px;opacity:0.01' );\r
186 \r
187                                 } );\r
188 \r
189                                 element.on( 'mouseup', function ( evt )\r
190                                 {\r
191                                         if ( contextMenuOverrideButton )\r
192                                         {\r
193                                                 contextMenuOverrideButton.remove();\r
194                                                 contextMenuOverrideButton = undefined;\r
195                                                 // Simulate 'contextmenu' event.\r
196                                                 element.fire( 'contextmenu', evt.data );\r
197                                         }\r
198                                 } );\r
199                         }\r
200 \r
201                         element.on( 'contextmenu', function( event )\r
202                                 {\r
203                                         var domEvent = event.data;\r
204 \r
205                                         if ( nativeContextMenuOnCtrl &&\r
206                                              // Safari on Windows always show 'ctrlKey' as true in 'contextmenu' event,\r
207                                                 // which make this property unreliable. (#4826)\r
208                                              ( CKEDITOR.env.webkit ? holdCtrlKey : ( CKEDITOR.env.mac ? domEvent.$.metaKey : domEvent.$.ctrlKey ) ) )\r
209                                                 return;\r
210 \r
211 \r
212                                         // Cancel the browser context menu.\r
213                                         domEvent.preventDefault();\r
214 \r
215                                         var offsetParent = domEvent.getTarget().getDocument().getDocumentElement(),\r
216                                                 offsetX = domEvent.$.clientX,\r
217                                                 offsetY = domEvent.$.clientY;\r
218 \r
219                                         CKEDITOR.tools.setTimeout( function()\r
220                                                 {\r
221                                                         this.show( offsetParent, null, offsetX, offsetY );\r
222                                                 },\r
223                                                 0, this );\r
224                                 },\r
225                                 this );\r
226 \r
227                         if ( CKEDITOR.env.webkit )\r
228                         {\r
229                                 var holdCtrlKey,\r
230                                         onKeyDown = function( event )\r
231                                         {\r
232                                                 holdCtrlKey = CKEDITOR.env.mac ? event.data.$.metaKey : event.data.$.ctrlKey ;\r
233                                         },\r
234                                         resetOnKeyUp = function()\r
235                                         {\r
236                                                 holdCtrlKey = 0;\r
237                                         };\r
238 \r
239                                 element.on( 'keydown', onKeyDown );\r
240                                 element.on( 'keyup', resetOnKeyUp );\r
241                                 element.on( 'contextmenu', resetOnKeyUp );\r
242                         }\r
243                 },\r
244 \r
245                 addListener : function( listenerFn )\r
246                 {\r
247                         this._.listeners.push( listenerFn );\r
248                 },\r
249 \r
250                 show : function( offsetParent, corner, offsetX, offsetY )\r
251                 {\r
252                         this.editor.focus();\r
253 \r
254                         // Selection will be unavailable after context menu shows up\r
255                         // in IE, lock it now.\r
256                         if ( CKEDITOR.env.ie )\r
257                         {\r
258                                 var selection = this.editor.getSelection();\r
259                                 selection && selection.lock();\r
260                         }\r
261 \r
262                         this._.onMenu( offsetParent || CKEDITOR.document.getDocumentElement(), corner, offsetX || 0, offsetY || 0 );\r
263                 }\r
264         }\r
265 });\r
266 \r
267 /**\r
268  * Whether to show the browser native context menu when the CTRL or the\r
269  * META (Mac) key is pressed while opening the context menu.\r
270  * @name CKEDITOR.config.browserContextMenuOnCtrl\r
271  * @since 3.0.2\r
272  * @type Boolean\r
273  * @default true\r
274  * @example\r
275  * config.browserContextMenuOnCtrl = false;\r
276  */\r