diff options
author | toasted-nutbread <toasted-nutbread@users.noreply.github.com> | 2020-09-09 16:59:03 -0400 |
---|---|---|
committer | GitHub <noreply@github.com> | 2020-09-09 16:59:03 -0400 |
commit | 0d00f7e1cf8a0fa1e2b1aa2732bceaae39f4e23c (patch) | |
tree | 92b0a2e72ef2cecb31e8cc70da354ee43b87e2b4 | |
parent | acb7ad32f39c40b879400c9daa4bc8cd25585ba7 (diff) |
Scanning input generalization (#789)
* Add inputs to options.scanning
* Update CSS for mouse buttons
* Update list counters
* Set up HTML/CSS
* Add input controller
* Use new inputs
* Include mouse buttons
* Update how button inputs are detected
* Add index/empty fields to the input details object
* Update none check for scanning modifier
* Remove old settings
* Remove unused global
-rw-r--r-- | ext/bg/css/settings.css | 90 | ||||
-rw-r--r-- | ext/bg/data/options-schema.json | 34 | ||||
-rw-r--r-- | ext/bg/js/options.js | 19 | ||||
-rw-r--r-- | ext/bg/js/settings/keyboard-mouse-input-field.js | 2 | ||||
-rw-r--r-- | ext/bg/js/settings/main.js | 23 | ||||
-rw-r--r-- | ext/bg/js/settings/profile-conditions-ui.js | 2 | ||||
-rw-r--r-- | ext/bg/js/settings/scan-inputs-controller.js | 178 | ||||
-rw-r--r-- | ext/bg/settings.html | 34 | ||||
-rw-r--r-- | ext/fg/js/frontend.js | 7 | ||||
-rw-r--r-- | ext/mixed/js/display.js | 3 | ||||
-rw-r--r-- | ext/mixed/js/document-util.js | 25 | ||||
-rw-r--r-- | ext/mixed/js/text-scanner.js | 88 |
12 files changed, 417 insertions, 88 deletions
diff --git a/ext/bg/css/settings.css b/ext/bg/css/settings.css index 2dc71d91..6d32c9c5 100644 --- a/ext/bg/css/settings.css +++ b/ext/bg/css/settings.css @@ -46,6 +46,21 @@ html:root:not([data-options-general-result-output-mode=merge]) #dict-main-group border-color: #f00000; } +.mouse-button { + padding-left: 10px; + padding-right: 10px; +} +.mouse-button[hidden] { + display: none; +} +.mouse-button-icon { + width: 20px; + height: 20px; + display: block; + background: url(/mixed/img/mouse.svg) no-repeat center center; + background-size: 20px 20px; +} + .condition { display: flex; -flex-wrap: wrap; @@ -113,21 +128,80 @@ html:root:not([data-options-general-result-output-mode=merge]) #dict-main-group display: none; } -.condition-mouse-button[hidden] { - display: none; +.scan-input-list { + counter-reset: scan-input-id; +} +.scan-input-table { + width: 100%; +} +.scan-input-list:not(:empty)+#scan-input-add { + border-top-left-radius: 0; + border-top-right-radius: 0; +} +.scan-input-index-cell { + position: relative; + min-width: 40px; +} +.scan-input-index { + position: absolute; + left: 0; + top: 0; + right: 0; + bottom: 0; + font-size: 14px; + color: #555; + background-color: #eee; + border: 1px solid #ccc; + border-top-left-radius: 4px; + display: flex; + justify-content: center; + align-items: center; +} +.scan-input-index:after { + display: block; + counter-increment: scan-input-id; + content: counter(scan-input-id); +} +.scan-input-prefix { + padding: 6px 12px; + font-size: 14px; + color: #555; + text-align: center; + background-color: #eee; + border: 1px solid #ccc; + width: 100%; +} +.scan-input-input-cell { + width: 100%; +} +.scan-input-input-cell>input { + border-radius: 0; +} +.scan-input-mouse-button-cell>button { + border-radius: 0; +} +.scan-input-remove-button-cell>button { + border-top-left-radius: 0; + border-bottom-left-radius: 0; +} +.scan-input:nth-child(n+2) .scan-input-index { + border-top-left-radius: 0; +} +.scan-input:last-child tr:last-of-type .scan-input-mouse-button-cell>button { + border-bottom-right-radius: 4px; } -.audio-source-list { - counter-reset: audio-source-id; +.generic-input-list { + counter-reset: generic-input-id; } -.audio-source-list .audio-source-prefix { +.generic-input-list .generic-input-prefix { flex: 0 0 auto; width: 39px; text-align: center; } -.audio-source-list .audio-source-prefix:after { - counter-increment: audio-source-id; - content: counter(audio-source-id); +.generic-input-list .generic-input-prefix:after { + counter-increment: generic-input-id; + content: counter(generic-input-id); } #custom-popup-css, diff --git a/ext/bg/data/options-schema.json b/ext/bg/data/options-schema.json index 52d96db9..17fe096c 100644 --- a/ext/bg/data/options-schema.json +++ b/ext/bg/data/options-schema.json @@ -318,7 +318,7 @@ "scanning": { "type": "object", "required": [ - "middleMouse", + "inputs", "touchInputEnabled", "selectText", "alphanumeric", @@ -336,9 +336,30 @@ "layoutAwareScan" ], "properties": { - "middleMouse": { - "type": "boolean", - "default": true + "inputs": { + "type": "array", + "default": [ + { + "include": "shift", + "exclude": "" + } + ], + "items": { + "required": [ + "include", + "exclude" + ], + "properties": { + "include": { + "type": "string", + "default": "shift" + }, + "exclude": { + "type": "string", + "default": "" + } + } + } }, "touchInputEnabled": { "type": "boolean", @@ -371,11 +392,6 @@ "minimum": 1, "default": 10 }, - "modifier": { - "type": "string", - "enum": ["none", "alt", "ctrl", "shift", "meta"], - "default": "shift" - }, "deepDomScan": { "type": "boolean", "default": false diff --git a/ext/bg/js/options.js b/ext/bg/js/options.js index 398fb95c..ab32bb11 100644 --- a/ext/bg/js/options.js +++ b/ext/bg/js/options.js @@ -474,6 +474,7 @@ class OptionsUtil { // Added usePopupWindow. // Updated handlebars templates to include "clipboard-image" definition. // Added hideDelay. + // Added inputs to profileOptions.scanning. for (const {conditionGroups} of options.profiles) { for (const {conditions} of conditionGroups) { for (const condition of conditions) { @@ -489,6 +490,24 @@ class OptionsUtil { for (const {options: profileOptions} of options.profiles) { profileOptions.general.usePopupWindow = false; profileOptions.scanning.hideDelay = 0; + + const {modifier, middleMouse} = profileOptions.scanning; + const scanningInputs = []; + switch (modifier) { + case 'alt': + case 'ctrl': + case 'shift': + case 'meta': + scanningInputs.push(modifier); + break; + case 'none': + scanningInputs.push(''); + break; + } + if (middleMouse) { + scanningInputs.push('mouse2'); + } + profileOptions.scanning.inputs = scanningInputs; } await this._addFieldTemplatesToOptions(options, '/bg/data/anki-field-templates-upgrade-v4.handlebars'); return options; diff --git a/ext/bg/js/settings/keyboard-mouse-input-field.js b/ext/bg/js/settings/keyboard-mouse-input-field.js index b1530c1b..4ba0d3ed 100644 --- a/ext/bg/js/settings/keyboard-mouse-input-field.js +++ b/ext/bg/js/settings/keyboard-mouse-input-field.js @@ -172,7 +172,7 @@ class KeyboardMouseInputField extends EventDispatcher { _onMouseButtonMouseDown(e) { e.preventDefault(); - this._addInputs([`mouse${e.button}`]); + this._addInputs(DocumentUtil.getActiveButtons(e)); } _onMouseButtonMouseUp(e) { diff --git a/ext/bg/js/settings/main.js b/ext/bg/js/settings/main.js index 7fa8e502..4932586b 100644 --- a/ext/bg/js/settings/main.js +++ b/ext/bg/js/settings/main.js @@ -22,10 +22,10 @@ * ClipboardPopupsController * DictionaryController * DictionaryImportController - * DocumentUtil * GenericSettingController * PopupPreviewController * ProfileController + * ScanInputsController * SettingsBackup * SettingsController * StorageController @@ -40,23 +40,6 @@ function showExtensionInformation() { node.textContent = `${manifest.name} v${manifest.version}`; } -async function settingsPopulateModifierKeys() { - const scanModifierKeySelect = document.querySelector('#scan-modifier-key'); - scanModifierKeySelect.textContent = ''; - - const {platform: {os}} = await api.getEnvironmentInfo(); - const modifierKeys = [ - ['none', 'None'], - ...DocumentUtil.getModifierKeys(os) - ]; - for (const [value, name] of modifierKeys) { - const option = document.createElement('option'); - option.value = value; - option.textContent = name; - scanModifierKeySelect.appendChild(option); - } -} - async function setupEnvironmentInfo() { const {browser, platform} = await api.getEnvironmentInfo(); document.documentElement.dataset.browser = browser; @@ -71,7 +54,6 @@ async function setupEnvironmentInfo() { setupEnvironmentInfo(); showExtensionInformation(); - settingsPopulateModifierKeys(); const optionsFull = await api.optionsGetFull(); @@ -111,6 +93,9 @@ async function setupEnvironmentInfo() { const settingsBackup = new SettingsBackup(settingsController); settingsBackup.prepare(); + const scanInputsController = new ScanInputsController(settingsController); + scanInputsController.prepare(); + yomichan.ready(); } catch (e) { yomichan.logError(e); diff --git a/ext/bg/js/settings/profile-conditions-ui.js b/ext/bg/js/settings/profile-conditions-ui.js index d88f932b..419c35b3 100644 --- a/ext/bg/js/settings/profile-conditions-ui.js +++ b/ext/bg/js/settings/profile-conditions-ui.js @@ -441,7 +441,7 @@ class ProfileConditionUI { this._operatorOptionContainer = this._operatorInput.querySelector('optgroup'); this._valueInput = this._node.querySelector('.condition-input-inner'); this._removeButton = this._node.querySelector('.condition-remove'); - this._mouseButton = this._node.querySelector('.condition-mouse-button'); + this._mouseButton = this._node.querySelector('.mouse-button'); const operatorDetails = this._getOperatorDetails(type, operator); this._updateTypes(type); diff --git a/ext/bg/js/settings/scan-inputs-controller.js b/ext/bg/js/settings/scan-inputs-controller.js new file mode 100644 index 00000000..3b3945ff --- /dev/null +++ b/ext/bg/js/settings/scan-inputs-controller.js @@ -0,0 +1,178 @@ +/* + * Copyright (C) 2020 Yomichan Authors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +/* global + * KeyboardMouseInputField + * api + */ + +class ScanInputsController { + constructor(settingsController) { + this._settingsController = settingsController; + this._os = null; + this._container = null; + this._addButton = null; + this._entries = []; + } + + async prepare() { + const {platform: {os}} = await api.getEnvironmentInfo(); + this._os = os; + + this._container = document.querySelector('#scan-input-list'); + this._addButton = document.querySelector('#scan-input-add'); + + this._addButton.addEventListener('click', this._onAddButtonClick.bind(this), false); + this._settingsController.on('optionsChanged', this._onOptionsChanged.bind(this)); + + const options = await this._settingsController.getOptions(); + this._onOptionsChanged({options}); + } + + removeInput(index) { + if (index < 0 || index >= this._entries.length) { return false; } + const input = this._entries[index]; + input.cleanup(); + this._entries.splice(index, 1); + for (let i = index, ii = this._entries.length; i < ii; ++i) { + this._entries[i].index = i; + } + this._settingsController.modifyProfileSettings([{ + action: 'splice', + path: 'scanning.inputs', + start: index, + deleteCount: 1, + items: [] + }]); + } + + setProperty(index, property, value) { + const path = `scanning.inputs[${index}].${property}`; + this._settingsController.setProfileSetting(path, value); + } + + // Private + + _onOptionsChanged({options}) { + const {inputs} = options.scanning; + + for (let i = this._entries.length - 1; i >= 0; --i) { + this._entries[i].cleanup(); + } + this._entries.length = 0; + + for (let i = 0, ii = inputs.length; i < ii; ++i) { + const {include, exclude} = inputs[i]; + this._addOption(i, include, exclude); + } + } + + _onAddButtonClick(e) { + e.preventDefault(); + + const index = this._entries.length; + const include = ''; + const exclude = ''; + this._addOption(index, include, exclude); + this._settingsController.modifyProfileSettings([{ + action: 'splice', + path: 'scanning.inputs', + start: index, + deleteCount: 0, + items: [{include, exclude}] + }]); + } + + _addOption(index, include, exclude) { + const field = new ScanInputField(this, index, this._os); + this._entries.push(field); + field.prepare(this._container, include, exclude); + } +} + +class ScanInputField { + constructor(parent, index, os) { + this._parent = parent; + this._index = index; + this._os = os; + this._node = null; + this._includeInputField = null; + this._excludeInputField = null; + this._eventListeners = new EventListenerCollection(); + } + + get index() { + return this._index; + } + + set index(value) { + this._index = value; + } + + prepare(container, include, exclude) { + const node = this._instantiateTemplate('#scan-input-template'); + const includeInputNode = node.querySelector('.scan-input-include .scan-input-field'); + const includeMouseButton = node.querySelector('.scan-input-include .mouse-button'); + const excludeInputNode = node.querySelector('.scan-input-exclude .scan-input-field'); + const excludeMouseButton = node.querySelector('.scan-input-exclude .mouse-button'); + const removeButton = node.querySelector('.scan-input-remove'); + + this._node = node; + container.appendChild(node); + + this._includeInputField = new KeyboardMouseInputField(includeInputNode, includeMouseButton, this._os); + this._excludeInputField = new KeyboardMouseInputField(excludeInputNode, excludeMouseButton, this._os); + this._includeInputField.prepare(include, 'modifierInputs'); + this._excludeInputField.prepare(exclude, 'modifierInputs'); + + this._eventListeners.on(this._includeInputField, 'change', this._onIncludeValueChange.bind(this)); + this._eventListeners.on(this._excludeInputField, 'change', this._onExcludeValueChange.bind(this)); + this._eventListeners.addEventListener(removeButton, 'click', this._onRemoveClick.bind(this)); + } + + cleanup() { + this._eventListeners.removeAllEventListeners(); + if (this._includeInputField !== null) { + this._includeInputField.cleanup(); + this._includeInputField = null; + } + if (this._node !== null) { + const parent = this._node.parentNode; + if (parent !== null) { parent.removeChild(this._node); } + this._node = null; + } + } + + _onIncludeValueChange({value}) { + this._parent.setProperty(this._index, 'include', value); + } + + _onExcludeValueChange({value}) { + this._parent.setProperty(this._index, 'exclude', value); + } + + _onRemoveClick(e) { + e.preventDefault(); + this._parent.removeInput(this._index); + } + + _instantiateTemplate(templateSelector) { + const template = document.querySelector(templateSelector); + const content = document.importNode(template.content, true); + return content.firstChild; + } +} diff --git a/ext/bg/settings.html b/ext/bg/settings.html index 3fa14f49..ae89ca1f 100644 --- a/ext/bg/settings.html +++ b/ext/bg/settings.html @@ -125,7 +125,7 @@ <div class="input-group-btn"><select class="form-control btn btn-default condition-operator"><optgroup label="Operator"></optgroup></select></div> <div class="condition-line-break"></div> <div class="condition-input"><input type="text" class="form-control condition-input-inner"></div> - <div class="input-group-btn"><button class="btn btn-default condition-mouse-button" title="Mouse button" style="padding-left: 10px; padding-right: 10px;" hidden><span style="width: 20px; height: 20px; display: block; background: url(/mixed/img/mouse.svg) no-repeat center center; background-size: 20px 20px;"></span></button><button class="btn btn-danger condition-remove" title="Remove"><span class="glyphicon glyphicon-remove"></span></button></div> + <div class="input-group-btn"><button class="btn btn-default mouse-button" title="Mouse button"><span class="mouse-button-icon"></span></button><button class="btn btn-danger condition-remove" title="Remove"><span class="glyphicon glyphicon-remove"></span></button></div> </div></template> </div> @@ -363,13 +363,13 @@ <div class="form-group ignore-form-changes"> <label>Audio playback sources</label> - <div class="audio-source-list"></div> + <div class="audio-source-list generic-input-list"></div> <div class="input-group audio-source-options"> <button class="btn btn-default audio-source-add" title="Add audio playback source"><span class="glyphicon glyphicon-plus"></span></button> </div> <template id="audio-source-template"><div class="input-group audio-source"> - <div class="input-group-addon audio-source-prefix"></div> + <div class="input-group-addon generic-input-prefix"></div> <select class="form-control audio-source-select"> <option value="jpod101">JapanesePod101</option> <option value="jpod101-alternate">JapanesePod101 (Alternate)</option> @@ -387,10 +387,6 @@ <h3>Scanning Options</h3> <div class="checkbox"> - <label><input type="checkbox" id="middle-mouse-button-scan" data-setting="scanning.middleMouse"> Middle mouse button scans</label> - </div> - - <div class="checkbox"> <label><input type="checkbox" id="touch-input-enabled" data-setting="scanning.touchInputEnabled"> Touch input enabled</label> </div> @@ -433,8 +429,27 @@ </div> <div class="form-group"> - <label for="scan-modifier-key">Scan modifier key</label> - <select class="form-control" id="scan-modifier-key" data-setting="scanning.modifier"></select> + <label>Scan inputs</label> + <div class="scan-input-list" id="scan-input-list"></div> + <button class="btn btn-default" id="scan-input-add" title="Add scan input"><span class="glyphicon glyphicon-plus"></span></button> + + <template id="scan-input-template"><div class="scan-input"> + <table class="scan-input-table"><tbody> + <tr class="scan-input-include"> + <td class="scan-input-index-cell" rowspan="2"><div class="scan-input-index"></div></td> + <td class="scan-input-prefix-cell"><div class="scan-input-prefix">Include</div></td> + <td class="scan-input-input-cell"><input type="text" class="form-control scan-input-field" placeholder="No inputs"></td> + <td class="scan-input-mouse-button-cell"><button class="btn btn-default mouse-button" title="Mouse button"><span class="mouse-button-icon"></span></button></td> + <td class="scan-input-remove-button-cell"><button class="btn btn-danger scan-input-remove" title="Remove"><span class="glyphicon glyphicon-remove"></span></button></td> + </tr> + <tr class="scan-input-exclude"> + <td class="scan-input-prefix-cell"><div class="scan-input-prefix">Exclude</div></td> + <td class="scan-input-input-cell"><input type="text" class="form-control scan-input-field" placeholder="No inputs"></td> + <td class="scan-input-mouse-button-cell"><button class="btn btn-default mouse-button" title="Mouse button"><span class="mouse-button-icon"></span></button></td> + <td class="scan-input-empty-cell"></td> + </tr> + </tbody></table> + </div></template> </div> </div> @@ -1176,6 +1191,7 @@ <script src="/bg/js/settings/popup-preview.js"></script> <script src="/bg/js/settings/profiles.js"></script> <script src="/bg/js/settings/profile-conditions-ui.js"></script> + <script src="/bg/js/settings/scan-inputs-controller.js"></script> <script src="/bg/js/settings/settings-controller.js"></script> <script src="/bg/js/settings/storage.js"></script> <script src="/mixed/js/dictionary-data-util.js"></script> diff --git a/ext/fg/js/frontend.js b/ext/fg/js/frontend.js index 3ddf0d25..8189b8ad 100644 --- a/ext/fg/js/frontend.js +++ b/ext/fg/js/frontend.js @@ -254,12 +254,12 @@ class Frontend { await this.updateOptions(); } - _onSearched({type, definitions, sentence, input: {cause}, textSource, optionsContext, error}) { + _onSearched({type, definitions, sentence, input: {cause, empty}, textSource, optionsContext, error}) { const scanningOptions = this._options.scanning; if (error !== null) { if (yomichan.isExtensionUnloaded) { - if (textSource !== null && scanningOptions.modifier !== 'none') { + if (textSource !== null && !empty) { this._showExtensionUnloaded(textSource); } } else { @@ -321,10 +321,9 @@ class Frontend { await this._updatePopup(); this._textScanner.setOptions({ + inputs: scanningOptions.inputs, deepContentScan: scanningOptions.deepDomScan, selectText: scanningOptions.selectText, - modifier: scanningOptions.modifier, - useMiddleMouse: scanningOptions.middleMouse, delay: scanningOptions.delay, touchInputEnabled: scanningOptions.touchInputEnabled, scanLength: scanningOptions.length, diff --git a/ext/mixed/js/display.js b/ext/mixed/js/display.js index 70b3895a..ea6b52c0 100644 --- a/ext/mixed/js/display.js +++ b/ext/mixed/js/display.js @@ -239,10 +239,9 @@ class Display extends EventDispatcher { selectedParser: options.parsing.selectedParser, termSpacing: options.parsing.termSpacing, scanning: { + inputs: scanning.inputs, deepContentScan: scanning.deepDomScan, selectText: scanning.selectText, - modifier: scanning.modifier, - useMiddleMouse: scanning.middleMouse, delay: scanning.delay, touchInputEnabled: scanning.touchInputEnabled, scanLength: scanning.length, diff --git a/ext/mixed/js/document-util.js b/ext/mixed/js/document-util.js index 0b72ff9a..58b0c759 100644 --- a/ext/mixed/js/document-util.js +++ b/ext/mixed/js/document-util.js @@ -188,6 +188,18 @@ class DocumentUtil { return modifiers; } + static getActiveModifiersAndButtons(event) { + const modifiers = this.getActiveModifiers(event); + this._getActiveButtons(event, modifiers); + return modifiers; + } + + static getActiveButtons(event) { + const buttons = new Set(); + this._getActiveButtons(event, buttons); + return buttons; + } + static getKeyFromEvent(event) { const key = event.key; return (typeof key === 'string' ? (key.length === 1 ? key.toUpperCase() : key) : ''); @@ -299,6 +311,19 @@ class DocumentUtil { return !(browser === 'firefox' || browser === 'firefox-mobile') || os === 'mac'; } + static _getActiveButtons(event, set) { + const {buttons} = event; + if (typeof buttons === 'number') { + for (let i = 0; i < 6; ++i) { + const buttonFlag = (1 << i); + if ((buttons & buttonFlag) !== 0) { + set.add(`mouse${i}`); + } + } + } + return set; + } + // Private _setImposterStyle(style, propertyName, value) { diff --git a/ext/mixed/js/text-scanner.js b/ext/mixed/js/text-scanner.js index f3e99577..6eafc82c 100644 --- a/ext/mixed/js/text-scanner.js +++ b/ext/mixed/js/text-scanner.js @@ -43,13 +43,12 @@ class TextScanner extends EventDispatcher { this._deepContentScan = false; this._selectText = false; - this._modifier = 'none'; - this._useMiddleMouse = false; this._delay = 0; this._touchInputEnabled = false; this._scanLength = 1; this._sentenceExtent = 1; this._layoutAwareScan = false; + this._inputs = []; this._enabled = false; this._eventListeners = new EventListenerCollection(); @@ -94,19 +93,19 @@ class TextScanner extends EventDispatcher { } } - setOptions({deepContentScan, selectText, modifier, useMiddleMouse, delay, touchInputEnabled, scanLength, sentenceExtent, layoutAwareScan}) { + setOptions({inputs, deepContentScan, selectText, delay, touchInputEnabled, scanLength, sentenceExtent, layoutAwareScan}) { + if (Array.isArray(inputs)) { + this._inputs = inputs.map(({include, exclude}) => ({ + include: this._getInputArray(include), + exclude: this._getInputArray(exclude) + })); + } if (typeof deepContentScan === 'boolean') { this._deepContentScan = deepContentScan; } if (typeof selectText === 'boolean') { this._selectText = selectText; } - if (typeof modifier === 'string') { - this._modifier = modifier; - } - if (typeof useMiddleMouse === 'boolean') { - this._useMiddleMouse = useMiddleMouse; - } if (typeof delay === 'number') { this._delay = delay; } @@ -184,7 +183,7 @@ class TextScanner extends EventDispatcher { } async search(textSource) { - return await this._search(textSource, {cause: 'script'}); + return await this._search(textSource, {cause: 'script', index: -1, empty: false}); } // Private @@ -242,17 +241,14 @@ class TextScanner extends EventDispatcher { return; } - const modifiers = DocumentUtil.getActiveModifiers(e); + const modifiers = DocumentUtil.getActiveModifiersAndButtons(e); this.trigger('activeModifiersChanged', {modifiers}); - if (!( - this._isScanningModifierPressed(this._modifier, e) || - (this._useMiddleMouse && DocumentUtil.isMouseButtonDown(e, 'auxiliary')) - )) { - return; - } + const inputInfo = this._getMatchingInputGroup(modifiers); + if (inputInfo === null) { return; } - this._searchAtFromMouse(e.clientX, e.clientY); + const {index, empty} = inputInfo; + this._searchAtFromMouse(e.clientX, e.clientY, index, empty); } _onMouseDown(e) { @@ -276,7 +272,7 @@ class TextScanner extends EventDispatcher { _onClick(e) { if (this._searchOnClick) { - this._searchAt(e.clientX, e.clientY, {cause: 'click'}); + this._searchAt(e.clientX, e.clientY, {cause: 'click', index: -1, empty: false}); } if (this._preventNextClick) { @@ -349,7 +345,7 @@ class TextScanner extends EventDispatcher { return; } - this._searchAt(primaryTouch.clientX, primaryTouch.clientY, {cause: 'touchMove'}); + this._searchAt(primaryTouch.clientX, primaryTouch.clientY, {cause: 'touchMove', index: -1, empty: false}); e.preventDefault(); // Disable scroll } @@ -406,17 +402,6 @@ class TextScanner extends EventDispatcher { ]; } - _isScanningModifierPressed(scanningModifier, mouseEvent) { - switch (scanningModifier) { - case 'alt': return mouseEvent.altKey; - case 'ctrl': return mouseEvent.ctrlKey; - case 'shift': return mouseEvent.shiftKey; - case 'meta': return mouseEvent.metaKey; - case 'none': return true; - default: return false; - } - } - _getTouch(touchList, identifier) { for (const touch of touchList) { if (touch.identifier === identifier) { @@ -498,17 +483,17 @@ class TextScanner extends EventDispatcher { } } - async _searchAtFromMouse(x, y) { + async _searchAtFromMouse(x, y, inputIndex, inputEmpty) { if (this._pendingLookup) { return; } - if (this._modifier === 'none') { + if (inputEmpty) { if (!await this._scanTimerWait()) { // Aborted return; } } - await this._searchAt(x, y, {cause: 'mouse'}); + await this._searchAt(x, y, {cause: 'mouse', index: inputIndex, empty: inputEmpty}); } async _searchAtFromTouchStart(x, y) { @@ -516,7 +501,7 @@ class TextScanner extends EventDispatcher { const textSourceCurrentPrevious = this._textSourceCurrent !== null ? this._textSourceCurrent.clone() : null; - await this._searchAt(x, y, {cause: 'touchStart'}); + await this._searchAt(x, y, {cause: 'touchStart', index: -1, empty: false}); if ( this._textSourceCurrent !== null && @@ -527,4 +512,37 @@ class TextScanner extends EventDispatcher { this._preventNextMouseDown = true; } } + + _getMatchingInputGroup(modifiers) { + let fallback = null; + for (let i = 0, ii = this._inputs.length; i < ii; ++i) { + const input = this._inputs[i]; + const {include, exclude} = input; + if (this._setHasAll(modifiers, include) && (exclude.length === 0 || !this._setHasAll(modifiers, exclude))) { + if (include.length > 0) { + return {index: i, empty: false, input}; + } else if (fallback === null) { + fallback = {index: i, empty: true, input}; + } + } + } + return fallback; + } + + _setHasAll(set, values) { + for (const value of values) { + if (!set.has(value)) { + return false; + } + } + return true; + } + + _getInputArray(value) { + return ( + typeof value === 'string' ? + value.split(/[,;\s]+/).map((v) => v.trim().toLowerCase()).filter((v) => v.length > 0) : + [] + ); + } } |