| 1 | var PLUGIN_INFO = |
|---|
| 2 | <VimperatorPlugin> |
|---|
| 3 | <name>Mouse Gestures</name> |
|---|
| 4 | <name lang='ja'>マウスジェスチャー</name> |
|---|
| 5 | <description>mouse gestures</description> |
|---|
| 6 | <description lang='ja'>マウスジェスチャー</description> |
|---|
| 7 | <version>0.10.1</version> |
|---|
| 8 | <author>pekepeke</author> |
|---|
| 9 | <minVersion>2.0pre</minVersion> |
|---|
| 10 | <maxVersion>2.0pre</maxVersion> |
|---|
| 11 | <updateURL>http://svn.coderepos.org/share/lang/javascript/vimperator-plugins/trunk/mouse_gestures.js</updateURL> |
|---|
| 12 | <detail lang='ja'><![CDATA[ |
|---|
| 13 | == .vimperatorrc example == |
|---|
| 14 | >|| |
|---|
| 15 | js <<EOM |
|---|
| 16 | liberator.globalVariables.mousegesture_showmsg = true; // default is true |
|---|
| 17 | liberator.globalVariables.mousegesture_rocker = true; // default is false |
|---|
| 18 | liberator.globalVariables.mousegesture_wheel = true; // default is false |
|---|
| 19 | liberator.globalVariables.mousegesture_list = [ |
|---|
| 20 | //['UDLR', 'Description', '#id or function or :vimp command or vimp_key', noremap flag] |
|---|
| 21 | ['L' , 'Back', '#Browser:Back'], |
|---|
| 22 | ['R' , 'Forward', '#Browser:Forward'], |
|---|
| 23 | ['RLR', 'Close Tab Or Window', '#cmd_close'], |
|---|
| 24 | ['LD' , 'Stop Loading Page', '#Browser:Stop'], |
|---|
| 25 | ['LR' , 'Undo Close Tab', '#History:UndoCloseTab'], |
|---|
| 26 | ['UL' , 'Select Previous Tab', 'gT', true], |
|---|
| 27 | ['UR' , 'Select Next Tab', 'gt', true], |
|---|
| 28 | ['LU' , 'Scroll To Top', function() goDoCommand('cmd_scrollTop')], |
|---|
| 29 | ['LD' , 'Scroll To Bottom', function() goDoCommand('cmd_scrollBottom')], |
|---|
| 30 | ['UDR', 'Add Bookmark', ':dialog addbookmark'], |
|---|
| 31 | ['L>R', 'Forward', '#Browser:Forward'], |
|---|
| 32 | ['L<R', 'Back', '#Browser:Back'], |
|---|
| 33 | ['W-' , 'Select Previous Tab', function() gBrowser.tabContainer.advanceSelectedTab(-1, true) ], |
|---|
| 34 | ['W+' , 'Select Next Tab', function() gBrowser.tabContainer.advanceSelectedTab(+1, true) ], |
|---|
| 35 | ]; |
|---|
| 36 | EOM |
|---|
| 37 | ||< |
|---|
| 38 | == liberator.globalVariables == |
|---|
| 39 | - mousegesture_showmsg |
|---|
| 40 | ジェスチャー情報を表示するかどうか(デフォルト=true:表示する) |
|---|
| 41 | - mousegesture_rocker |
|---|
| 42 | ロッカージェスチャを有効にするかどうか(デフォルト=false:無効) |
|---|
| 43 | - mousegesture_wheel |
|---|
| 44 | ホイールジェスチャを有効にするかどうか(デフォルト=false:無効) |
|---|
| 45 | - mousegesture_list |
|---|
| 46 | ジェスチャー設定。2次元配列で指定してください。 |
|---|
| 47 | [ <UDLR>, <Description>, <Command>, <noremap flag> ] |
|---|
| 48 | - UDLR |
|---|
| 49 | ジェスチャーを指定します。 |
|---|
| 50 | UDLRの文字列を指定してください。 |
|---|
| 51 | それぞれ、マウスジェスチャーの↑、↓、←, →に対応しています。 |
|---|
| 52 | 一応、ロッカージェスチャー・ホイールジェスチャー等にも暫定で対応しています(別途、オプションを有効にする必要がある)。 |
|---|
| 53 | ロッカージェスチャはL>R(左→右クリック), L<R(右→左クリック)で指定可能。 |
|---|
| 54 | ホイールジェスチャは W-(↓回転), W+(上回転)で指定可能 |
|---|
| 55 | - Description |
|---|
| 56 | コマンドの説明文。 |
|---|
| 57 | - Command |
|---|
| 58 | ジェスチャーが実施された際に実行するコマンドを指定します。 |
|---|
| 59 | 以下の3通りの指定が可能です。 |
|---|
| 60 | - '#id' |
|---|
| 61 | document.getElementById(id).doCommand() を実行します。 |
|---|
| 62 | - function() { ... } |
|---|
| 63 | 記述された関数を実行します。 |
|---|
| 64 | - ':[command]' |
|---|
| 65 | Vimperatorのユーザコマンド [command]を実行します。 |
|---|
| 66 | - '[key]' |
|---|
| 67 | キーを送ります。 |
|---|
| 68 | - noremap flag |
|---|
| 69 | キーを送る、かつ、そのキーコードを noremap で処理を行いたい場合、true を指定してください。 |
|---|
| 70 | ]]></detail> |
|---|
| 71 | </VimperatorPlugin> |
|---|
| 72 | |
|---|
| 73 | liberator.plugins.MouseGestures = (function() { |
|---|
| 74 | |
|---|
| 75 | const Ci = Components.interfaces; |
|---|
| 76 | var global = liberator.globalVariables; |
|---|
| 77 | |
|---|
| 78 | if (typeof global.mousegesture_list == 'undefined') return; |
|---|
| 79 | if (liberator.plugins.MouseGestures) liberator.plugins.MouseGestures.registerEvents('remove'); |
|---|
| 80 | |
|---|
| 81 | var Class = function() function() {this.initialize.apply(this, arguments);}; |
|---|
| 82 | var MouseGestures = new Class(); |
|---|
| 83 | |
|---|
| 84 | var doCommandByID = function(id) { |
|---|
| 85 | if (document.getElementById(id)) |
|---|
| 86 | document.getElementById(id).doCommand(); |
|---|
| 87 | }; |
|---|
| 88 | |
|---|
| 89 | MouseGestures.prototype = { |
|---|
| 90 | initialize: function() { |
|---|
| 91 | this.parseSetting(); |
|---|
| 92 | |
|---|
| 93 | var self = this; |
|---|
| 94 | this.registerEvents('add'); |
|---|
| 95 | window.addEventListener('unload', function() { self.registerEvents('remove'); }, false); |
|---|
| 96 | }, |
|---|
| 97 | parseSetting: function() { |
|---|
| 98 | var gestures = {}; |
|---|
| 99 | var self = this; |
|---|
| 100 | this._showstatus = global.mousegesture_showmsg || true; |
|---|
| 101 | |
|---|
| 102 | this._enableRocker = global.mousegesture_rocker || false; |
|---|
| 103 | this._enableWheel = global.mousegesture_wheel || false; |
|---|
| 104 | if (this._enableRocker) this.captureEvents.push('draggesture'); |
|---|
| 105 | if (this._enableWheel) this.captureEvents.push('DOMMouseScroll'); |
|---|
| 106 | global.mousegesture_list.forEach(function( [gesture, desc, action, noremap] ) { |
|---|
| 107 | action = action || desc; |
|---|
| 108 | noremap = noremap || false; |
|---|
| 109 | if (typeof action == 'string') { |
|---|
| 110 | let str = action; |
|---|
| 111 | if (str.charAt(0) == ':') action = function() liberator.execute(str.substr(1)); |
|---|
| 112 | else if (str.charAt(0) == '#') action = function() doCommandByID(str.substr(1)); |
|---|
| 113 | else action = function() modules.events.feedkeys(str, noremap); |
|---|
| 114 | } |
|---|
| 115 | gestures[gesture] = [desc, action]; |
|---|
| 116 | }); |
|---|
| 117 | this.GESTURES = gestures; |
|---|
| 118 | }, |
|---|
| 119 | captureEvents : ['mousedown', 'mousemove', 'mouseup', 'contextmenu'], |
|---|
| 120 | registerEvents: function(action) { |
|---|
| 121 | var self = this; |
|---|
| 122 | this.captureEvents.forEach( |
|---|
| 123 | function(type) { getBrowser().mPanelContainer[action + 'EventListener'](type, self, type == 'contextmenu' || type == 'draggesture'); |
|---|
| 124 | }); |
|---|
| 125 | }, |
|---|
| 126 | set status(msg) { |
|---|
| 127 | if (this._showstatus) commandline.echo(msg, null, commandline.FORCE_SINGLELINE); |
|---|
| 128 | }, |
|---|
| 129 | handleEvent: function(event) { |
|---|
| 130 | switch(event.type) { |
|---|
| 131 | case 'mousedown': |
|---|
| 132 | if (event.button == 2) { |
|---|
| 133 | this._isMouseDownR = true; |
|---|
| 134 | this._suppressContext = false; |
|---|
| 135 | this.startGesture(event); |
|---|
| 136 | if (this._enableRocker && this._isMouseDownL) { |
|---|
| 137 | this._isMouseDownR = false; |
|---|
| 138 | this._suppressContext = true; |
|---|
| 139 | this._gesture = 'L>R'; |
|---|
| 140 | this.stopGesture(event); |
|---|
| 141 | } |
|---|
| 142 | } else if (this._enableRocker && event.button == 0) { |
|---|
| 143 | this._isMouseDownL = true; |
|---|
| 144 | if (this._isMouseDownR) { |
|---|
| 145 | this._isMouseDownL = false; |
|---|
| 146 | this._suppressContext = true; |
|---|
| 147 | this._gesture = 'L<R'; |
|---|
| 148 | this.stopGesture(event); |
|---|
| 149 | } |
|---|
| 150 | } |
|---|
| 151 | break; |
|---|
| 152 | case 'mousemove': |
|---|
| 153 | if (this._isMouseDownR) this.progressGesture(event); |
|---|
| 154 | break; |
|---|
| 155 | case 'mouseup': |
|---|
| 156 | if (this._isMouseDownR && event.button == 2) { |
|---|
| 157 | this._isMouseDownR = false; |
|---|
| 158 | this._suppressContext = !!this._gesture; |
|---|
| 159 | if (this._enableWheel && this._gesture && this._gesture.charAt(0) == 'W') this._gesture = ''; |
|---|
| 160 | this.stopGesture(event); |
|---|
| 161 | if (this._shouldFireContext) { // for Linux & Mac |
|---|
| 162 | this._shouldFireContext = false; |
|---|
| 163 | let mEvent = event.originalTarget.ownerDocument.createEvent('MouseEvents'); |
|---|
| 164 | mEvent.initMouseEvent('contextmenu', true, true, aEvent.originalTarget.defaultView, 0, event.screenX, event.screenY, event.clientX, event.clientY, false, false, false, false, 2, null); |
|---|
| 165 | event.originalTarget.dispatchEvent(mEvent); |
|---|
| 166 | } |
|---|
| 167 | } else if (this._isMouseDownL && event.button == 0) this._isMouseDownL = false; |
|---|
| 168 | break; |
|---|
| 169 | case 'contextmenu': |
|---|
| 170 | if (this._suppressContext || this._isMouseDownR) { |
|---|
| 171 | this._suppressContext = false; |
|---|
| 172 | this._shouldFireContext = this._isMouseDownR; |
|---|
| 173 | event.preventDefault(); |
|---|
| 174 | event.stopPropagation(); |
|---|
| 175 | } |
|---|
| 176 | break; |
|---|
| 177 | case 'DOMMouseScroll': |
|---|
| 178 | if (this._enableWheel && this._isMouseDownR) { |
|---|
| 179 | event.preventDefault(); |
|---|
| 180 | event.stopPropagation(); |
|---|
| 181 | this._suppressContext = true; |
|---|
| 182 | this._gesture = 'W' + (event.detail > 0 ? '+' : '-'); |
|---|
| 183 | this.stopGesture( event, false ); |
|---|
| 184 | } |
|---|
| 185 | break; |
|---|
| 186 | case 'draggesture': |
|---|
| 187 | this._isMouseDownL = false; |
|---|
| 188 | break; |
|---|
| 189 | } |
|---|
| 190 | }, |
|---|
| 191 | startGesture: function(event) { |
|---|
| 192 | this._gesture = ''; |
|---|
| 193 | this._x = event.screenX; |
|---|
| 194 | this._y = event.screenY; |
|---|
| 195 | this._origDoc = event.originalTarget.ownerDocument; |
|---|
| 196 | this._links = []; |
|---|
| 197 | }, |
|---|
| 198 | progressGesture: function(event) { |
|---|
| 199 | if (!this._origDoc) return; |
|---|
| 200 | for (let node = event.originalTarget; node; node = node.parentNode) { |
|---|
| 201 | if (node instanceof Ci.nsIDOMHTMLAnchorElement) { |
|---|
| 202 | if (this._links.indexOf(node.href) == -1) this._links.push(node.href); |
|---|
| 203 | break; |
|---|
| 204 | } |
|---|
| 205 | } |
|---|
| 206 | this.timerGesture(); |
|---|
| 207 | var x = event.screenX, y = event.screenY; |
|---|
| 208 | var distX = Math.abs(x-this._x), distY = Math.abs(y-this._y); |
|---|
| 209 | var threshold = 15/ (gBrowser.selectedBrowser.markupDocumentViewer.fullZoom || 1.0); |
|---|
| 210 | if (distX < threshold && distY < threshold) return; |
|---|
| 211 | var dir = distX > distY ? (x<this._x ? 'L' : 'R') : (y<this._y ? 'U': 'D'); |
|---|
| 212 | if (dir != this._gesture.slice(-1)) { |
|---|
| 213 | this._gesture += dir; |
|---|
| 214 | this.status = 'Gesture: ' + this._gesture + (this.GESTURES[this._gesture] ? ' (' + this.GESTURES[this._gesture][0] + ')' : ''); |
|---|
| 215 | } |
|---|
| 216 | this._x = x; this._y = y; |
|---|
| 217 | }, |
|---|
| 218 | timerGesture: function(isClear) { |
|---|
| 219 | if (this._timer) clearTimeout(this._timer); |
|---|
| 220 | this._timer = setTimeout( !isClear ? function(self) self.stopGesture({}, true) : function(self) self._timer = self.status = '', 1500, this); |
|---|
| 221 | }, |
|---|
| 222 | stopGesture: function(event, cancel) { |
|---|
| 223 | if (this._gesture) { |
|---|
| 224 | try { |
|---|
| 225 | if (cancel) throw 'Gesture Canceled'; |
|---|
| 226 | |
|---|
| 227 | let cmd = this.GESTURES[this._gesture] || null; |
|---|
| 228 | /* |
|---|
| 229 | if ( !cmd && this.GESTURES['*'] ) { |
|---|
| 230 | for (let key in this.GESTURES['*']) { |
|---|
| 231 | if (this.GESTURES['*'][key].test(this._gesture)) { |
|---|
| 232 | cmd = this.GESTURES[key]; |
|---|
| 233 | break; |
|---|
| 234 | } |
|---|
| 235 | } |
|---|
| 236 | } |
|---|
| 237 | */ |
|---|
| 238 | if (!cmd) throw 'Unknown Gesture: ' + this._gesture; |
|---|
| 239 | |
|---|
| 240 | cmd[1].call(this); |
|---|
| 241 | this.status = 'Gesture: ' + cmd[0]; |
|---|
| 242 | } catch (exp) { |
|---|
| 243 | this.status = exp; |
|---|
| 244 | } |
|---|
| 245 | this.timerGesture(true); |
|---|
| 246 | } |
|---|
| 247 | this._origDoc = this._links = null; |
|---|
| 248 | } |
|---|
| 249 | }; |
|---|
| 250 | return new MouseGestures(); |
|---|
| 251 | })(); |
|---|