Integrating hotkeys with commands
This commit is contained in:
parent
199115a9d3
commit
1a0729336b
@ -102,7 +102,7 @@ Template.toolbarSection.helpers({
|
|||||||
iconClasses: 'fa fa-trash'
|
iconClasses: 'fa fa-trash'
|
||||||
});
|
});
|
||||||
|
|
||||||
var buttonData = [];
|
const buttonData = [];
|
||||||
|
|
||||||
buttonData.push({
|
buttonData.push({
|
||||||
id: 'zoom',
|
id: 'zoom',
|
||||||
@ -175,7 +175,8 @@ Template.toolbarSection.helpers({
|
|||||||
id: 'toggleCinePlay',
|
id: 'toggleCinePlay',
|
||||||
title: 'Toggle CINE Play',
|
title: 'Toggle CINE Play',
|
||||||
classes: 'imageViewerCommand',
|
classes: 'imageViewerCommand',
|
||||||
buttonTemplateName: 'playClipButton'
|
buttonTemplateName: 'playClipButton',
|
||||||
|
active: OHIF.viewerbase.viewportUtils.isPlaying
|
||||||
});
|
});
|
||||||
|
|
||||||
buttonData.push({
|
buttonData.push({
|
||||||
@ -183,7 +184,8 @@ Template.toolbarSection.helpers({
|
|||||||
title: 'CINE',
|
title: 'CINE',
|
||||||
classes: 'imageViewerCommand',
|
classes: 'imageViewerCommand',
|
||||||
iconClasses: 'fa fa-youtube-play',
|
iconClasses: 'fa fa-youtube-play',
|
||||||
disableFunction: OHIF.viewerbase.viewportUtils.hasMultipleFrames
|
disableFunction: OHIF.viewerbase.viewportUtils.hasMultipleFrames,
|
||||||
|
active: () => $('#cineDialog').is(':visible')
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,5 +1,4 @@
|
|||||||
import { Tracker } from 'meteor/tracker';
|
import { ReactiveVar } from 'meteor/reactive-var';
|
||||||
import { Session } from 'meteor/session';
|
|
||||||
import { _ } from 'meteor/underscore';
|
import { _ } from 'meteor/underscore';
|
||||||
import { OHIF } from 'meteor/ohif:core';
|
import { OHIF } from 'meteor/ohif:core';
|
||||||
|
|
||||||
@ -7,11 +6,8 @@ export class CommandsManager {
|
|||||||
constructor() {
|
constructor() {
|
||||||
this.contexts = {};
|
this.contexts = {};
|
||||||
|
|
||||||
Tracker.autorun(() => {
|
// Enable reactivity by storing the last executed command
|
||||||
const currentContextName = OHIF.context.get();
|
this.last = new ReactiveVar('');
|
||||||
OHIF.log.info(currentContextName);
|
|
||||||
// TODO: initialize hotkeys context
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
getContext(contextName) {
|
getContext(contextName) {
|
||||||
@ -80,7 +76,12 @@ export class CommandsManager {
|
|||||||
return OHIF.log.warn(`No action was defined for command "${command}"`);
|
return OHIF.log.warn(`No action was defined for command "${command}"`);
|
||||||
} else {
|
} else {
|
||||||
const result = action(params);
|
const result = action(params);
|
||||||
Session.set('lastCommand', command);
|
if (this.last.get() === command) {
|
||||||
|
this.last.dep.changed();
|
||||||
|
} else {
|
||||||
|
this.last.set(command);
|
||||||
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -9,39 +9,48 @@ export class HotkeysContext {
|
|||||||
|
|
||||||
extend(definitions={}) {
|
extend(definitions={}) {
|
||||||
if (typeof definitions !== 'object') return;
|
if (typeof definitions !== 'object') return;
|
||||||
this.destroy();
|
Object.keys(definitions).forEach(command => {
|
||||||
Object.assign(this.definitions, definitions);
|
const hotkey = definitions[command];
|
||||||
this.initialize();
|
this.unregister(command);
|
||||||
|
this.register(command, hotkey);
|
||||||
|
this.definitions[command] = hotkey;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
register(command, hotkey) {
|
||||||
|
if (!hotkey) {
|
||||||
|
return OHIF.log.warn(`No hotkey was defined for "${command}"`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!command) {
|
||||||
|
return OHIF.log.warn(`No command was defined for hotkey "${hotkey}"`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const bindingKey = `keydown.hotkey.${this.name}.${command}`;
|
||||||
|
const bind = hotkey => $(document).bind(bindingKey, hotkey, event => {
|
||||||
|
if (!this.enabled.get()) return;
|
||||||
|
OHIF.commands.run(command);
|
||||||
|
});
|
||||||
|
|
||||||
|
if (hotkey instanceof Array) {
|
||||||
|
hotkey.forEach(hotkey => bind(hotkey));
|
||||||
|
} else {
|
||||||
|
bind(hotkey);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
unregister(command) {
|
||||||
|
const bindingKey = `keydown.hotkey.${this.name}.${command}`;
|
||||||
|
if (this.definitions[command]) {
|
||||||
|
$(document).unbind(bindingKey);
|
||||||
|
delete this.definitions[command];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
initialize() {
|
initialize() {
|
||||||
Object.keys(this.definitions).forEach(definitionKey => {
|
Object.keys(this.definitions).forEach(command => {
|
||||||
const { hotkey, action, disabled } = this.definitions[definitionKey];
|
const hotkey = this.definitions[command];
|
||||||
let message;
|
this.register(command, hotkey);
|
||||||
if (!hotkey) {
|
|
||||||
message = `No hotkey was defined for "${definitionKey}"`;
|
|
||||||
} else if (!action) {
|
|
||||||
message = `No action was defined for "${definitionKey}" using "${hotkey}" hotkey`;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (message) {
|
|
||||||
return OHIF.log.warn(message);
|
|
||||||
}
|
|
||||||
|
|
||||||
const bindHotkey = hotkey => $(document).bind(`keydown.hotkey.${this.name}`, hotkey, event => {
|
|
||||||
if (!this.enabled.get()) return;
|
|
||||||
if (typeof disabled !== undefined) {
|
|
||||||
if ((typeof disabled === 'function' && disabled()) || disabled) return;
|
|
||||||
}
|
|
||||||
|
|
||||||
action(event);
|
|
||||||
});
|
|
||||||
|
|
||||||
if (hotkey instanceof Array) {
|
|
||||||
hotkey.forEach(hotkey => bindHotkey(hotkey));
|
|
||||||
} else {
|
|
||||||
bindHotkey(hotkey);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,4 +1,6 @@
|
|||||||
import { ReactiveVar } from 'meteor/reactive-var';
|
import { ReactiveVar } from 'meteor/reactive-var';
|
||||||
|
import { Tracker } from 'meteor/tracker';
|
||||||
|
import { OHIF } from 'meteor/ohif:core';
|
||||||
import { HotkeysContext } from 'meteor/ohif:hotkeys/client/classes/HotkeysContext';
|
import { HotkeysContext } from 'meteor/ohif:hotkeys/client/classes/HotkeysContext';
|
||||||
|
|
||||||
export class HotkeysManager {
|
export class HotkeysManager {
|
||||||
@ -6,6 +8,11 @@ export class HotkeysManager {
|
|||||||
this.contexts = {};
|
this.contexts = {};
|
||||||
this.currentContextName = null;
|
this.currentContextName = null;
|
||||||
this.enabled = new ReactiveVar(true);
|
this.enabled = new ReactiveVar(true);
|
||||||
|
|
||||||
|
Tracker.autorun(() => {
|
||||||
|
const contextName = OHIF.context.get();
|
||||||
|
this.switchToContext(contextName);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
disable() {
|
disable() {
|
||||||
@ -24,7 +31,7 @@ export class HotkeysManager {
|
|||||||
return this.getContext(this.currentContextName);
|
return this.getContext(this.currentContextName);
|
||||||
}
|
}
|
||||||
|
|
||||||
setContext(contextName, contextDefinitions) {
|
set(contextName, contextDefinitions, extend=false) {
|
||||||
const enabled = this.enabled;
|
const enabled = this.enabled;
|
||||||
const context = new HotkeysContext(contextName, contextDefinitions, enabled);
|
const context = new HotkeysContext(contextName, contextDefinitions, enabled);
|
||||||
const currentContext = this.getCurrentContext();
|
const currentContext = this.getCurrentContext();
|
||||||
@ -36,6 +43,16 @@ export class HotkeysManager {
|
|||||||
this.contexts[contextName] = context;
|
this.contexts[contextName] = context;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
register(contextName, command, hotkey) {
|
||||||
|
if (!command || !hotkey) return;
|
||||||
|
const context = this.getContext(contextName);
|
||||||
|
if (!context) {
|
||||||
|
this.set(contextName, {});
|
||||||
|
}
|
||||||
|
|
||||||
|
context.register(command, hotkey);
|
||||||
|
}
|
||||||
|
|
||||||
unsetContext(contextName) {
|
unsetContext(contextName) {
|
||||||
if (contextName === this.currentContextName) {
|
if (contextName === this.currentContextName) {
|
||||||
this.getCurrentContext().destroy();
|
this.getCurrentContext().destroy();
|
||||||
|
|||||||
@ -16,7 +16,6 @@ Package.onUse(function(api) {
|
|||||||
]);
|
]);
|
||||||
|
|
||||||
// OHIF dependencies
|
// OHIF dependencies
|
||||||
api.use('ohif:core');
|
|
||||||
api.use('ohif:commands');
|
api.use('ohif:commands');
|
||||||
|
|
||||||
// Main module definition
|
// Main module definition
|
||||||
|
|||||||
@ -7,7 +7,7 @@ Template.toolbarSectionButton.onCreated(() => {
|
|||||||
const instance = Template.instance();
|
const instance = Template.instance();
|
||||||
|
|
||||||
instance.isActive = activeToolId => {
|
instance.isActive = activeToolId => {
|
||||||
// TODO: Find a way to prevent the 'flash' after a click, but before this helper runs
|
OHIF.commands.last.dep.depend();
|
||||||
const instance = Template.instance();
|
const instance = Template.instance();
|
||||||
const subTools = instance.data.subTools;
|
const subTools = instance.data.subTools;
|
||||||
const currentId = instance.data.id;
|
const currentId = instance.data.id;
|
||||||
@ -15,9 +15,10 @@ Template.toolbarSectionButton.onCreated(() => {
|
|||||||
const isSubTool = subTools && _.findWhere(subTools, { id: activeToolId });
|
const isSubTool = subTools && _.findWhere(subTools, { id: activeToolId });
|
||||||
const activeCommandButtons = Session.get('ToolManagerActiveCommandButtons') || [];
|
const activeCommandButtons = Session.get('ToolManagerActiveCommandButtons') || [];
|
||||||
const isActiveCommandButton = activeCommandButtons.indexOf(instance.data.id) !== -1;
|
const isActiveCommandButton = activeCommandButtons.indexOf(instance.data.id) !== -1;
|
||||||
|
const isActive = typeof instance.data.active === 'function' && instance.data.active();
|
||||||
|
|
||||||
// Check if the current tool, a sub tool or a command button is active
|
// Check if the current tool, a sub tool or a command button is active
|
||||||
return isCurrentTool || isSubTool || isActiveCommandButton;
|
return isActive || isCurrentTool || isSubTool || isActiveCommandButton;
|
||||||
};
|
};
|
||||||
|
|
||||||
instance.getActiveToolSubProperty = (propertyName, activeToolId) => {
|
instance.getActiveToolSubProperty = (propertyName, activeToolId) => {
|
||||||
@ -36,19 +37,34 @@ Template.toolbarSectionButton.onCreated(() => {
|
|||||||
|
|
||||||
instance.autorun(computation => {
|
instance.autorun(computation => {
|
||||||
// Get the last executed command
|
// Get the last executed command
|
||||||
const lastCommand = Session.get('lastCommand');
|
const lastCommand = OHIF.commands.last.get();
|
||||||
|
|
||||||
// Prevent running this computation on its first run
|
// Prevent running this computation on its first run
|
||||||
if (computation.firstRun) return;
|
if (computation.firstRun) return;
|
||||||
|
|
||||||
// Stop here if it's not the last command or if it's already an active tool
|
// Stop here if it's not the last command or if it's already an active tool
|
||||||
const { id } = instance.data;
|
const { id } = instance.data;
|
||||||
if (lastCommand !== id || instance.isActive(id)) return;
|
const activeToolId = OHIF.viewerbase.toolManager.getActiveTool();
|
||||||
|
if (lastCommand !== id || instance.isActive(activeToolId)) return;
|
||||||
|
|
||||||
// Add an active class to a button for 100ms to give the impression the button was pressed
|
// Add an active class to a button for 100ms to give the impression the button was pressed
|
||||||
const $button = instance.$('.toolbarSectionButton:first');
|
const flashButton = $element => {
|
||||||
$button.addClass('active');
|
$element.addClass('active');
|
||||||
setTimeout(() => $button.removeClass('active'), 100);
|
setTimeout(() => {
|
||||||
|
if ($element.hasClass('expandable') && $element.find('.toolbarSectionButton.active').length) return;
|
||||||
|
$element.removeClass('active');
|
||||||
|
}, 100);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Flash the active button
|
||||||
|
const $button = instance.$('.toolbarSectionButton').first();
|
||||||
|
flashButton($button);
|
||||||
|
|
||||||
|
// Flash the parent button as well in case of this button is inside a drawer
|
||||||
|
const $parentButton = $button.closest('.toolbarSectionButton.expandable');
|
||||||
|
if ($parentButton.length) {
|
||||||
|
flashButton($parentButton);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@ -37,7 +37,7 @@ Meteor.startup(function() {
|
|||||||
flipH: 'H',
|
flipH: 'H',
|
||||||
wwwc: 'W',
|
wwwc: 'W',
|
||||||
zoom: 'Z',
|
zoom: 'Z',
|
||||||
cinePlay: 'SPACE',
|
toggleCinePlay: 'SPACE',
|
||||||
rotateR: 'R',
|
rotateR: 'R',
|
||||||
rotateL: 'L',
|
rotateL: 'L',
|
||||||
toggleOverlayTags: 'SHIFT',
|
toggleOverlayTags: 'SHIFT',
|
||||||
@ -71,6 +71,7 @@ Meteor.startup(function() {
|
|||||||
angle: 'Angle',
|
angle: 'Angle',
|
||||||
dragProbe: 'Probe',
|
dragProbe: 'Probe',
|
||||||
ellipticalRoi: 'Ellipse',
|
ellipticalRoi: 'Ellipse',
|
||||||
|
rectangleRoi: 'Rectangle',
|
||||||
magnify: 'Magnify',
|
magnify: 'Magnify',
|
||||||
annotate: 'Annotate',
|
annotate: 'Annotate',
|
||||||
stackScroll: 'Stack Scroll',
|
stackScroll: 'Stack Scroll',
|
||||||
@ -98,7 +99,10 @@ Meteor.startup(function() {
|
|||||||
flipV: 'Flip vertically',
|
flipV: 'Flip vertically',
|
||||||
rotateR: 'Rotate right',
|
rotateR: 'Rotate right',
|
||||||
rotateL: 'Rotate left',
|
rotateL: 'Rotate left',
|
||||||
toggleCinePlay: 'Play/Pause'
|
toggleCinePlay: 'Play/Pause',
|
||||||
|
toggleCineDialog: 'CINE dialog',
|
||||||
|
resetViewport: 'Reset',
|
||||||
|
clearTools: 'Clear'
|
||||||
});
|
});
|
||||||
|
|
||||||
// Functions to register the preset switching commands
|
// Functions to register the preset switching commands
|
||||||
@ -172,10 +176,6 @@ Meteor.startup(function() {
|
|||||||
const $dicomTags = $('.imageViewerViewportOverlay .dicomTag');
|
const $dicomTags = $('.imageViewerViewportOverlay .dicomTag');
|
||||||
$dicomTags.toggle($dicomTags.eq(0).css('display') === 'none');
|
$dicomTags.toggle($dicomTags.eq(0).css('display') === 'none');
|
||||||
}
|
}
|
||||||
},
|
|
||||||
cineDialog: {
|
|
||||||
name: 'Toggle CINE',
|
|
||||||
action: viewportUtils.toggleCineDialog()
|
|
||||||
}
|
}
|
||||||
}, true);
|
}, true);
|
||||||
|
|
||||||
@ -203,45 +203,14 @@ function setOHIFHotkeys(hotkeys) {
|
|||||||
OHIF.viewer.hotkeys = hotkeys;
|
OHIF.viewer.hotkeys = hotkeys;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Add an active class to a button for 100ms only
|
|
||||||
* to give the impressiont the button was pressed.
|
|
||||||
* This is for tools that don't keep the button "pressed"
|
|
||||||
* all the time the tool is active.
|
|
||||||
*
|
|
||||||
* @param button DOM Element for the button to be "flashed"
|
|
||||||
*/
|
|
||||||
function flashButton(button) {
|
|
||||||
if (!button) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
button.classList.add('active');
|
|
||||||
setTimeout(() => {
|
|
||||||
button.classList.remove('active');
|
|
||||||
}, 100);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Binds all hotkeys keydown events to the tasks defined in
|
* Binds all hotkeys keydown events to the tasks defined in
|
||||||
* OHIF.viewer.hotkeys or a given param
|
* OHIF.viewer.hotkeys or a given param
|
||||||
* @param {Object} hotkeys hotkey and task mapping (not required). If not given, uses OHIF.viewer.hotkeys
|
* @param {Object} hotkeys hotkey and task mapping (not required). If not given, uses OHIF.viewer.hotkeys
|
||||||
*/
|
*/
|
||||||
function enableHotkeys(hotkeys) {
|
function enableHotkeys(hotkeys) {
|
||||||
const viewerHotkeys = hotkeys || OHIF.viewer.hotkeys;
|
const definitions = hotkeys || OHIF.viewer.hotkeys;
|
||||||
|
OHIF.hotkeys.set('viewer', definitions);
|
||||||
const definitions = {};
|
|
||||||
Object.keys(viewerHotkeys).forEach(definition => {
|
|
||||||
const hotkey = viewerHotkeys[definition];
|
|
||||||
const action = OHIF.viewer.hotkeyFunctions[definition];
|
|
||||||
definitions[definition] = {
|
|
||||||
hotkey,
|
|
||||||
action
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
OHIF.hotkeys.setContext('viewer', definitions);
|
|
||||||
OHIF.hotkeys.switchToContext('viewer');
|
|
||||||
OHIF.context.set('viewer');
|
OHIF.context.set('viewer');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user