2016-04-02 17:34:25 +03:00
|
|
|
/**********************************************************************
|
|
|
|
|
*
|
|
|
|
|
*
|
|
|
|
|
*
|
|
|
|
|
**********************************************************************/
|
2016-08-21 02:19:24 +03:00
|
|
|
((typeof define)[0]=='u'?function(f){module.exports=f(require)}:define)
|
|
|
|
|
(function(require){ var module={} // make module AMD/node compatible...
|
2016-08-20 22:49:36 +03:00
|
|
|
/*********************************************************************/
|
2016-04-02 17:34:25 +03:00
|
|
|
|
|
|
|
|
var actions = require('lib/actions')
|
|
|
|
|
var features = require('lib/features')
|
|
|
|
|
var toggler = require('lib/toggler')
|
|
|
|
|
var keyboard = require('lib/keyboard')
|
|
|
|
|
|
|
|
|
|
var core = require('features/core')
|
2016-04-30 18:39:14 +03:00
|
|
|
var widgets = require('features/ui-widgets')
|
2016-04-02 17:34:25 +03:00
|
|
|
|
2016-04-07 04:58:22 +03:00
|
|
|
var widget = require('lib/widget/widget')
|
|
|
|
|
var browse = require('lib/widget/browse')
|
|
|
|
|
var overlay = require('lib/widget/overlay')
|
|
|
|
|
var drawer = require('lib/widget/drawer')
|
|
|
|
|
|
2016-04-02 17:34:25 +03:00
|
|
|
|
|
|
|
|
|
|
|
|
|
/*********************************************************************/
|
|
|
|
|
|
2016-06-21 02:10:26 +03:00
|
|
|
// helper...
|
|
|
|
|
function customScale(n){
|
|
|
|
|
return {
|
2016-06-21 02:45:45 +03:00
|
|
|
default: 'fitCustom: '+n+' -- Set cutom image size',
|
|
|
|
|
'alt': 'setCustomSize: '+n+' -- Set current image size as custom',
|
|
|
|
|
'ctrl+shift': 'setCustomSize: '+n+' null -- Clear custom image size',
|
2016-06-21 02:10:26 +03:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2016-04-28 17:50:55 +03:00
|
|
|
// XXX might be a good idea to be able ignore actions rather than keys...
|
2016-04-02 17:34:25 +03:00
|
|
|
// XXX add this to the global doc...
|
|
|
|
|
var GLOBAL_KEYBOARD =
|
|
|
|
|
module.GLOBAL_KEYBOARD = {
|
2016-04-03 20:57:03 +03:00
|
|
|
'Global':{
|
|
|
|
|
doc: 'Global bindings that take priority over other sections.',
|
|
|
|
|
pattern: '*',
|
|
|
|
|
|
|
|
|
|
},
|
|
|
|
|
|
2016-04-02 17:34:25 +03:00
|
|
|
'Slideshow': {
|
|
|
|
|
pattern: '.slideshow-running',
|
|
|
|
|
ignore: [
|
2016-04-03 00:58:49 +03:00
|
|
|
'Esc',
|
2016-04-02 17:34:25 +03:00
|
|
|
'Up', 'Down', 'Enter',
|
2017-01-06 02:18:21 +03:00
|
|
|
'R', 'L', 'G', 'T',
|
2016-04-02 17:34:25 +03:00
|
|
|
],
|
|
|
|
|
|
2016-04-03 22:04:38 +03:00
|
|
|
Esc: 'toggleSlideshow: "off" -- Exit slideshow',
|
2016-04-02 17:34:25 +03:00
|
|
|
Enter: 'slideshowDialog',
|
|
|
|
|
|
|
|
|
|
Left: 'resetSlideshowTimer',
|
|
|
|
|
Right: 'resetSlideshowTimer',
|
|
|
|
|
Home: 'resetSlideshowTimer',
|
|
|
|
|
End: 'resetSlideshowTimer',
|
|
|
|
|
|
2016-04-16 01:36:55 +03:00
|
|
|
T: 'slideshowIntervalDialog',
|
2016-04-02 17:34:25 +03:00
|
|
|
R: 'toggleSlideshowDirection',
|
|
|
|
|
L: 'toggleSlideshowLooping',
|
|
|
|
|
},
|
|
|
|
|
|
2016-04-28 17:50:55 +03:00
|
|
|
// XXX do we need to prevent up/down navigation here, it may get confusing?
|
|
|
|
|
// XXX do we need to disable fast sorting here???
|
2016-04-03 00:58:49 +03:00
|
|
|
'Single Image': {
|
|
|
|
|
pattern: '.single-image-mode',
|
|
|
|
|
ignore: [
|
|
|
|
|
'Esc',
|
|
|
|
|
|
|
|
|
|
// do not crop in single image mode...
|
|
|
|
|
'C', 'F2',
|
|
|
|
|
|
2016-04-24 01:13:12 +03:00
|
|
|
// zooming...
|
|
|
|
|
'#0', '#1', '#2', '#3', '#4', '#5', '#6', '#7', '#8', '#9',
|
2016-04-03 00:58:49 +03:00
|
|
|
],
|
|
|
|
|
|
2016-06-21 02:45:45 +03:00
|
|
|
|
2016-10-23 04:16:54 +03:00
|
|
|
// NOTE: these are here so as to enable handling via the next
|
|
|
|
|
// block, i.e. the Viewer
|
|
|
|
|
// ...if not given, then the ignore above will shadow the
|
|
|
|
|
// keys...
|
|
|
|
|
// NOTE: the 'nop' action does not exist, this it will get ignored
|
|
|
|
|
'(': 'nop',
|
|
|
|
|
')': 'nop',
|
|
|
|
|
|
2016-06-21 02:45:45 +03:00
|
|
|
// zooming...
|
2016-04-28 18:01:00 +03:00
|
|
|
'#1': 'fitScreen',
|
2016-06-21 05:43:14 +03:00
|
|
|
// XXX should these also be implemented in the same way as 4-9???
|
2016-06-21 02:10:26 +03:00
|
|
|
'#2': {
|
|
|
|
|
default: 'fitNormal',
|
2016-06-21 05:43:14 +03:00
|
|
|
'alt': 'setNormalScale -- Set current image size as normal',
|
2016-06-21 06:06:34 +03:00
|
|
|
'ctrl+shift': 'setNormalScale: null -- Reset normal image size to default',
|
2016-06-21 02:10:26 +03:00
|
|
|
},
|
|
|
|
|
'#3': {
|
|
|
|
|
default: 'fitSmall',
|
2016-06-21 05:43:14 +03:00
|
|
|
'alt': 'setSmallScale -- Set current image size as small',
|
2016-06-21 06:06:34 +03:00
|
|
|
'ctrl+shift': 'setSmallScale: null -- Reset small image size to default',
|
2016-06-21 02:10:26 +03:00
|
|
|
},
|
|
|
|
|
'#4': customScale(4),
|
|
|
|
|
'#5': customScale(5),
|
|
|
|
|
'#6': customScale(6),
|
|
|
|
|
'#7': customScale(7),
|
|
|
|
|
'#8': customScale(8),
|
|
|
|
|
'#9': customScale(9),
|
|
|
|
|
'#0': customScale(0),
|
|
|
|
|
|
2016-04-24 01:13:12 +03:00
|
|
|
|
2016-04-03 22:04:38 +03:00
|
|
|
Esc: 'toggleSingleImage: "off" -- Exit single image view',
|
2016-04-28 16:11:55 +03:00
|
|
|
|
|
|
|
|
// ignore sorting and reversing...
|
2016-04-28 17:50:55 +03:00
|
|
|
// XXX not sure about these yet, especially reversing...
|
2016-04-28 16:11:55 +03:00
|
|
|
R: {
|
|
|
|
|
shift: 'IGNORE',
|
|
|
|
|
},
|
|
|
|
|
S: {
|
|
|
|
|
shift: 'IGNORE',
|
|
|
|
|
},
|
2016-04-03 00:58:49 +03:00
|
|
|
},
|
|
|
|
|
|
2016-04-05 18:54:09 +03:00
|
|
|
// XXX add "save as collection..."
|
2016-04-03 20:57:03 +03:00
|
|
|
'Cropped': {
|
2016-04-04 19:36:50 +03:00
|
|
|
pattern: '.crop-mode',
|
2016-04-05 18:54:09 +03:00
|
|
|
|
|
|
|
|
Esc: {
|
|
|
|
|
default: 'uncrop',
|
|
|
|
|
ctrl: 'uncropAll',
|
|
|
|
|
},
|
2016-04-03 20:57:03 +03:00
|
|
|
},
|
|
|
|
|
|
2016-12-29 23:23:05 +03:00
|
|
|
'Range': {
|
|
|
|
|
doc: 'Range editing',
|
|
|
|
|
pattern: '.brace',
|
|
|
|
|
|
|
|
|
|
// XXX add:
|
|
|
|
|
// - range navigation
|
|
|
|
|
// - range manipulation
|
|
|
|
|
|
|
|
|
|
Esc: 'clearRange',
|
|
|
|
|
},
|
|
|
|
|
|
2016-04-05 18:54:09 +03:00
|
|
|
// XXX add "save as collection..." (???)
|
2016-04-03 20:57:03 +03:00
|
|
|
// XXX cleanup...
|
|
|
|
|
'Viewer': {
|
2016-04-02 17:34:25 +03:00
|
|
|
doc: 'NOTE: binding priority is the same as the order of sections '+
|
|
|
|
|
'on this page.',
|
|
|
|
|
pattern: '*',
|
|
|
|
|
|
2016-04-07 04:58:22 +03:00
|
|
|
X: {
|
|
|
|
|
alt: 'close',
|
|
|
|
|
},
|
|
|
|
|
|
2016-04-02 17:34:25 +03:00
|
|
|
F4: {
|
|
|
|
|
alt: 'close',
|
|
|
|
|
},
|
|
|
|
|
Q: {
|
|
|
|
|
meta: 'close',
|
|
|
|
|
},
|
2016-04-03 20:57:03 +03:00
|
|
|
// XXX
|
2016-04-02 17:34:25 +03:00
|
|
|
F5: keyboard.doc('Full reload viewer',
|
|
|
|
|
function(){
|
|
|
|
|
//a.stop()
|
|
|
|
|
/*
|
|
|
|
|
killAllWorkers()
|
|
|
|
|
.done(function(){
|
|
|
|
|
reload()
|
|
|
|
|
})
|
|
|
|
|
*/
|
|
|
|
|
location.reload()
|
|
|
|
|
return false
|
|
|
|
|
}),
|
|
|
|
|
F12: 'showDevTools',
|
|
|
|
|
// NOTE: these are for systems where F** keys are not available
|
|
|
|
|
// or do other stuff...
|
|
|
|
|
R: {
|
|
|
|
|
default: 'rotateCW',
|
|
|
|
|
shift: 'reverseImages',
|
2016-06-09 00:10:11 +03:00
|
|
|
ctrl: 'loadNewImages!',
|
2016-08-07 02:42:25 +03:00
|
|
|
alt: 'browseActions: "/Ribbon/" -- Open ribbon menu',
|
2016-06-09 00:10:11 +03:00
|
|
|
'ctrl+alt': 'reload!',
|
2016-04-02 17:34:25 +03:00
|
|
|
'ctrl+shift': 'F5',
|
|
|
|
|
},
|
|
|
|
|
L: 'rotateCCW',
|
|
|
|
|
H: {
|
|
|
|
|
default: 'flipHorizontal',
|
|
|
|
|
ctrl: 'listURLHistory',
|
2016-05-04 21:28:46 +03:00
|
|
|
'ctrl+shift': 'listSaveHistory',
|
2016-04-03 21:33:36 +03:00
|
|
|
alt: 'browseActions: "/History/" -- Open history menu',
|
2016-04-02 17:34:25 +03:00
|
|
|
},
|
|
|
|
|
V: 'flipVertical',
|
2016-11-23 16:14:38 +03:00
|
|
|
|
|
|
|
|
// tilt...
|
|
|
|
|
// XXX experimental, not sure if wee need this with a keyboard...
|
|
|
|
|
T: {
|
|
|
|
|
default: 'rotateRibbonCCW -- Tilt ribbons counter clock wise',
|
|
|
|
|
shift: 'rotateRibbonCW -- Tilt ribbons clock wise',
|
|
|
|
|
|
|
|
|
|
alt: 'resetRibbonRotation -- Reset ribbon tilt',
|
|
|
|
|
},
|
|
|
|
|
|
2016-04-02 17:34:25 +03:00
|
|
|
P: {
|
|
|
|
|
'ctrl+shift': 'F12',
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
// NOTE: this is handled by the wrapper at this point, so we do
|
|
|
|
|
// not have to do anything here...
|
|
|
|
|
F11: 'toggleFullScreen',
|
|
|
|
|
F: {
|
|
|
|
|
ctrl: 'F11',
|
|
|
|
|
meta: 'F11',
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
// XXX testing...
|
|
|
|
|
|
|
|
|
|
Enter: 'toggleSingleImage',
|
|
|
|
|
|
|
|
|
|
Home: {
|
|
|
|
|
default: 'firstImage',
|
|
|
|
|
ctrl: 'firstGlobalImage',
|
|
|
|
|
shift: 'firstRibbon',
|
|
|
|
|
},
|
|
|
|
|
End: {
|
|
|
|
|
default: 'lastImage',
|
|
|
|
|
ctrl: 'lastGlobalImage',
|
|
|
|
|
shift: 'lastRibbon',
|
|
|
|
|
},
|
|
|
|
|
Left: {
|
|
|
|
|
default: 'prevImage',
|
|
|
|
|
alt: 'shiftImageLeft!',
|
|
|
|
|
ctrl: 'prevScreen',
|
|
|
|
|
// XXX need to prevent default on mac + browser...
|
|
|
|
|
meta: 'prevScreen',
|
|
|
|
|
},
|
|
|
|
|
PgUp: 'prevScreen',
|
|
|
|
|
PgDown: 'nextScreen',
|
|
|
|
|
Right: {
|
|
|
|
|
default: 'nextImage',
|
|
|
|
|
alt: 'shiftImageRight!',
|
|
|
|
|
ctrl: 'nextScreen',
|
|
|
|
|
// XXX need to prevent default on mac + browser...
|
|
|
|
|
meta: 'nextScreen',
|
|
|
|
|
},
|
2016-04-23 00:07:04 +03:00
|
|
|
Space: 'Right',
|
|
|
|
|
Backspace: 'Left',
|
2016-04-02 17:34:25 +03:00
|
|
|
'(': 'prevImageInOrder',
|
|
|
|
|
')': 'nextImageInOrder',
|
|
|
|
|
',': 'prevMarked',
|
|
|
|
|
'.': 'nextMarked',
|
2016-12-28 20:11:42 +03:00
|
|
|
'[': {
|
|
|
|
|
default: 'prevBookmarked',
|
|
|
|
|
// XXX experimental
|
|
|
|
|
shift: 'openRange',
|
|
|
|
|
},
|
|
|
|
|
']': {
|
|
|
|
|
default: 'nextBookmarked',
|
|
|
|
|
// XXX experimental
|
|
|
|
|
shift: 'closeRange',
|
|
|
|
|
},
|
2016-04-02 17:34:25 +03:00
|
|
|
Up: {
|
|
|
|
|
default: 'prevRibbon',
|
|
|
|
|
shift: 'shiftImageUp',
|
|
|
|
|
'alt+shift': 'travelImageUp',
|
|
|
|
|
'ctrl+shift': 'shiftImageUpNewRibbon',
|
|
|
|
|
},
|
|
|
|
|
Down: {
|
|
|
|
|
default: 'nextRibbon',
|
|
|
|
|
shift: 'shiftImageDown',
|
|
|
|
|
'alt+shift': 'travelImageDown',
|
|
|
|
|
'ctrl+shift': 'shiftImageDownNewRibbon',
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
'#0': 'fitMax',
|
|
|
|
|
'#1': {
|
|
|
|
|
default: 'fitImage',
|
|
|
|
|
shift: 'fitRibbon',
|
|
|
|
|
ctrl: 'fitOrig!',
|
|
|
|
|
},
|
2016-04-03 22:04:38 +03:00
|
|
|
'#2': 'fitImage: 2 -- Fit 2 Images',
|
2016-04-02 17:34:25 +03:00
|
|
|
'#3': {
|
2016-04-03 22:04:38 +03:00
|
|
|
default: 'fitImage: 3 -- Fit 3 images',
|
|
|
|
|
shift: 'fitRibbon: 3.5 -- Fit 3.5 ribbons',
|
2016-04-02 17:34:25 +03:00
|
|
|
},
|
2016-04-07 05:41:06 +03:00
|
|
|
'#4': 'fitImage: 4 -- Fit 4 images',
|
2016-04-02 17:34:25 +03:00
|
|
|
'#5': {
|
2016-04-03 22:04:38 +03:00
|
|
|
default: 'fitImage: 5 -- Fit 5 images',
|
|
|
|
|
shift: 'fitRibbon: 5.5 -- Fit 5.5 ribbons',
|
2016-04-02 17:34:25 +03:00
|
|
|
},
|
2016-04-03 22:04:38 +03:00
|
|
|
'#6': 'fitImage: 6 -- Fit 6 images',
|
|
|
|
|
'#7': 'fitImage: 7 -- Fit 7 images',
|
|
|
|
|
'#8':'fitImage: 8 -- Fit 8 images',
|
|
|
|
|
'#9': 'fitImage: 9 -- Fit 9 images',
|
2016-04-02 17:34:25 +03:00
|
|
|
|
2016-11-09 04:53:42 +03:00
|
|
|
'+': {
|
|
|
|
|
default: 'zoomIn',
|
|
|
|
|
ctrl: 'lighterTheme!',
|
|
|
|
|
},
|
2016-04-02 17:34:25 +03:00
|
|
|
'=': '+',
|
2016-11-09 04:53:42 +03:00
|
|
|
'-': {
|
|
|
|
|
default: 'zoomOut',
|
|
|
|
|
ctrl: 'darkerTheme!',
|
|
|
|
|
},
|
2016-04-02 17:34:25 +03:00
|
|
|
|
|
|
|
|
F2: {
|
|
|
|
|
default: 'cropRibbon',
|
|
|
|
|
shift: 'cropRibbonAndAbove',
|
|
|
|
|
ctrl: 'cropMarked',
|
|
|
|
|
alt: 'cropBookmarked',
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
// marking...
|
|
|
|
|
M: {
|
|
|
|
|
default: 'toggleMark',
|
2016-04-03 22:04:38 +03:00
|
|
|
alt: 'browseActions: "/Mark/" -- Show mark menu',
|
2016-04-02 17:34:25 +03:00
|
|
|
},
|
|
|
|
|
A: {
|
|
|
|
|
alt: 'browseActions',
|
|
|
|
|
'alt+shift': 'listActions',
|
|
|
|
|
|
2016-04-03 22:04:38 +03:00
|
|
|
ctrl: 'toggleMark!: "ribbon" "on" -- Mark all images in ribbon',
|
2016-04-02 17:34:25 +03:00
|
|
|
},
|
|
|
|
|
D: {
|
2016-04-03 22:04:38 +03:00
|
|
|
ctrl: 'toggleMark!: "ribbon" "off" -- Unmark all images in ribbon',
|
2016-04-02 17:34:25 +03:00
|
|
|
},
|
|
|
|
|
I: {
|
|
|
|
|
default: 'showMetadata',
|
2016-06-23 01:00:24 +03:00
|
|
|
alt: 'browseActions: "/Image/" -- Show image menu',
|
2016-04-02 17:34:25 +03:00
|
|
|
shift: 'toggleStatusBar',
|
|
|
|
|
|
2016-04-03 22:04:38 +03:00
|
|
|
ctrl: 'toggleMark!: "ribbon" -- Invert marks in ribbon',
|
|
|
|
|
'ctrl+shift': 'showMetadata: "current" "full" -- Show full metadata',
|
2016-04-02 17:34:25 +03:00
|
|
|
|
|
|
|
|
'meta+alt': 'showDevTools',
|
|
|
|
|
},
|
2016-12-28 20:11:42 +03:00
|
|
|
// XXX experimental...
|
|
|
|
|
'*': 'setRangeBorder',
|
2016-04-02 17:34:25 +03:00
|
|
|
|
|
|
|
|
B: {
|
|
|
|
|
default: 'toggleBookmark',
|
|
|
|
|
ctrl: 'toggleTheme!',
|
2016-11-09 04:53:42 +03:00
|
|
|
'ctrl+shift': 'toggleTheme!: "prev"',
|
|
|
|
|
|
2016-04-03 22:04:38 +03:00
|
|
|
alt: 'browseActions: "/Bookmark/" -- Show bookmark menu',
|
2016-06-29 19:58:10 +03:00
|
|
|
|
|
|
|
|
// XXX not sure if this is the right way to go...
|
|
|
|
|
shift: 'setBaseRibbon',
|
2016-04-02 17:34:25 +03:00
|
|
|
},
|
|
|
|
|
E: {
|
|
|
|
|
default: 'openInExtenalEditor',
|
2016-04-03 22:04:38 +03:00
|
|
|
shift: 'openInExtenalEditor: 1 -- Open in alternative editor',
|
2016-04-02 17:34:25 +03:00
|
|
|
alt: 'listExtenalEditors',
|
|
|
|
|
},
|
2016-04-12 19:01:40 +03:00
|
|
|
C: {
|
|
|
|
|
default: 'browseActions: "/Crop/" -- Show crop menu',
|
|
|
|
|
// do the default copy thing...
|
|
|
|
|
// NOTE: this stops the default: handler from getting the ctrl:
|
|
|
|
|
// key case...
|
|
|
|
|
ctrl: '',
|
|
|
|
|
},
|
2016-04-02 17:34:25 +03:00
|
|
|
O: 'browsePath',
|
|
|
|
|
S: {
|
|
|
|
|
default: 'slideshowDialog',
|
2016-04-22 18:11:22 +03:00
|
|
|
//shift: 'sortImages: "Date" -- Sort images by date',
|
|
|
|
|
shift: 'sortImages -- Sort images',
|
2016-04-28 17:50:55 +03:00
|
|
|
//alt: 'browseActions: "/Sort/"',
|
|
|
|
|
alt: 'sortDialog',
|
2016-04-02 17:34:25 +03:00
|
|
|
// XXX need to make this save to base_path if it exists and
|
|
|
|
|
// ask the user if it does not... now it always asks.
|
|
|
|
|
ctrl: 'saveIndexHere',
|
2016-04-16 05:19:31 +03:00
|
|
|
'ctrl+shift': 'exportDialog',
|
2016-04-02 17:34:25 +03:00
|
|
|
},
|
|
|
|
|
|
|
|
|
|
// XXX still experimental...
|
|
|
|
|
U: {
|
2016-12-31 19:51:16 +03:00
|
|
|
default: 'undo',
|
|
|
|
|
shift: 'redo',
|
2016-04-02 17:34:25 +03:00
|
|
|
},
|
|
|
|
|
Z: {
|
2016-12-31 19:51:16 +03:00
|
|
|
ctrl: 'undo',
|
|
|
|
|
'ctrl+shift': 'redo',
|
2016-04-02 17:34:25 +03:00
|
|
|
},
|
|
|
|
|
|
|
|
|
|
G: {
|
|
|
|
|
default: 'editStatusBarIndex!',
|
|
|
|
|
shift: 'toggleStatusBarIndexMode!',
|
|
|
|
|
|
|
|
|
|
// XXX for debug...
|
2016-11-17 04:21:20 +03:00
|
|
|
//ctrl: function(){ $('.viewer').toggleClass('visible-gid') },
|
2016-04-02 17:34:25 +03:00
|
|
|
},
|
2016-04-07 05:41:06 +03:00
|
|
|
|
|
|
|
|
'?': 'showKeyboardBindings',
|
2016-04-02 17:34:25 +03:00
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2016-04-03 00:58:49 +03:00
|
|
|
|
2017-01-07 04:22:53 +03:00
|
|
|
/*********************************************************************/
|
2017-01-08 03:32:49 +03:00
|
|
|
// + simpler to group bindings
|
|
|
|
|
// - harder to automate binding creation (e.g. via customScale(..))
|
|
|
|
|
//
|
|
|
|
|
var GLOBAL_KEYBOARD2 =
|
|
|
|
|
module.GLOBAL_KEYBOARD2 = {
|
|
|
|
|
'Global': {
|
|
|
|
|
doc: 'Global bindings that take priority over other sections.',
|
|
|
|
|
pattern: '*',
|
2017-01-07 04:22:53 +03:00
|
|
|
|
2017-01-08 03:32:49 +03:00
|
|
|
},
|
2017-01-07 04:22:53 +03:00
|
|
|
|
2017-01-08 03:32:49 +03:00
|
|
|
'Slideshow': {
|
|
|
|
|
pattern: '.slideshow-running',
|
|
|
|
|
drop: [
|
|
|
|
|
'Esc',
|
|
|
|
|
'Up', 'Down', 'Enter',
|
|
|
|
|
'R', 'L', 'G', 'T',
|
|
|
|
|
],
|
|
|
|
|
|
|
|
|
|
Esc: 'toggleSlideshow: "off" -- Exit slideshow',
|
|
|
|
|
Enter: 'slideshowDialog',
|
|
|
|
|
|
|
|
|
|
Left: 'resetSlideshowTimer',
|
|
|
|
|
Right: 'resetSlideshowTimer',
|
|
|
|
|
Home: 'resetSlideshowTimer',
|
|
|
|
|
End: 'resetSlideshowTimer',
|
|
|
|
|
|
|
|
|
|
T: 'slideshowIntervalDialog',
|
|
|
|
|
R: 'toggleSlideshowDirection',
|
|
|
|
|
L: 'toggleSlideshowLooping',
|
2017-01-07 04:22:53 +03:00
|
|
|
},
|
|
|
|
|
|
2017-01-08 03:32:49 +03:00
|
|
|
// XXX do we need to prevent up/down navigation here, it may get confusing?
|
|
|
|
|
// XXX do we need to disable fast sorting here???
|
|
|
|
|
'Single Image': {
|
|
|
|
|
pattern: '.single-image-mode',
|
|
|
|
|
drop: [
|
|
|
|
|
'Esc',
|
|
|
|
|
|
|
|
|
|
// do not crop in single image mode...
|
|
|
|
|
'C', 'F2',
|
|
|
|
|
|
|
|
|
|
// zooming...
|
|
|
|
|
'#0', '#1', '#2', '#3', '#4', '#5', '#6', '#7', '#8', '#9',
|
|
|
|
|
],
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// NOTE: these are here so as to enable handling via the next
|
|
|
|
|
// block, i.e. the Viewer
|
|
|
|
|
// ...if not given, then the ignore above will shadow the
|
|
|
|
|
// keys...
|
|
|
|
|
// NOTE: the 'nop' action does not exist, this it will get ignored
|
|
|
|
|
'(': 'nop',
|
|
|
|
|
')': 'nop',
|
|
|
|
|
|
|
|
|
|
// zooming...
|
|
|
|
|
'#1': 'fitScreen',
|
|
|
|
|
// XXX should these also be implemented in the same way as 4-9???
|
|
|
|
|
'#2': 'fitNormal',
|
|
|
|
|
'alt+#2': 'setNormalScale -- Set current image size as normal',
|
|
|
|
|
'ctrl+shift+#2': 'setNormalScale: null -- Reset normal image size to default',
|
|
|
|
|
'#3': 'fitSmall',
|
|
|
|
|
'alt+#3': 'setSmallScale -- Set current image size as small',
|
|
|
|
|
'ctrl+shift+#3': 'setSmallScale: null -- Reset small image size to default',
|
|
|
|
|
/*/ XXX
|
|
|
|
|
'#4': customScale(4),
|
|
|
|
|
'#5': customScale(5),
|
|
|
|
|
'#6': customScale(6),
|
|
|
|
|
'#7': customScale(7),
|
|
|
|
|
'#8': customScale(8),
|
|
|
|
|
'#9': customScale(9),
|
|
|
|
|
'#0': customScale(0),
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
Esc: 'toggleSingleImage: "off" -- Exit single image view',
|
|
|
|
|
|
|
|
|
|
// ignore sorting and reversing...
|
|
|
|
|
// XXX not sure about these yet, especially reversing...
|
|
|
|
|
shift_R: 'IGNORE',
|
|
|
|
|
shift_S: 'IGNORE',
|
2017-01-07 04:22:53 +03:00
|
|
|
},
|
|
|
|
|
|
2017-01-08 03:32:49 +03:00
|
|
|
// XXX add "save as collection..."
|
|
|
|
|
'Cropped': {
|
|
|
|
|
pattern: '.crop-mode',
|
2017-01-07 04:22:53 +03:00
|
|
|
|
2017-01-08 03:32:49 +03:00
|
|
|
Esc: 'uncrop',
|
2017-01-08 04:58:32 +03:00
|
|
|
ctrl_Esc: 'uncropAll',
|
2017-01-08 03:32:49 +03:00
|
|
|
},
|
2017-01-07 04:22:53 +03:00
|
|
|
|
2017-01-08 03:32:49 +03:00
|
|
|
'Range': {
|
|
|
|
|
doc: 'Range editing',
|
|
|
|
|
pattern: '.brace',
|
2017-01-07 04:22:53 +03:00
|
|
|
|
2017-01-08 03:32:49 +03:00
|
|
|
// XXX add:
|
|
|
|
|
// - range navigation
|
|
|
|
|
// - range manipulation
|
2017-01-07 04:22:53 +03:00
|
|
|
|
2017-01-08 03:32:49 +03:00
|
|
|
Esc: 'clearRange',
|
|
|
|
|
},
|
2017-01-07 04:22:53 +03:00
|
|
|
|
2017-01-08 03:32:49 +03:00
|
|
|
// XXX add "save as collection..." (???)
|
|
|
|
|
// XXX cleanup...
|
|
|
|
|
'Viewer': {
|
|
|
|
|
doc: 'NOTE: binding priority is the same as the order of sections '+
|
|
|
|
|
'on this page.',
|
|
|
|
|
pattern: '*',
|
2017-01-07 04:22:53 +03:00
|
|
|
|
2017-01-08 03:32:49 +03:00
|
|
|
alt_X: 'close',
|
|
|
|
|
alt_F4: 'close',
|
|
|
|
|
meta_Q: 'close',
|
2017-01-08 04:58:32 +03:00
|
|
|
|
2017-01-08 03:32:49 +03:00
|
|
|
// XXX
|
|
|
|
|
F5: keyboard.doc('Full reload viewer',
|
|
|
|
|
function(){
|
|
|
|
|
//a.stop()
|
|
|
|
|
/*
|
|
|
|
|
killAllWorkers()
|
|
|
|
|
.done(function(){
|
|
|
|
|
reload()
|
2017-01-07 04:22:53 +03:00
|
|
|
})
|
2017-01-08 03:32:49 +03:00
|
|
|
*/
|
|
|
|
|
location.reload()
|
|
|
|
|
return false
|
|
|
|
|
}),
|
2017-01-08 04:58:32 +03:00
|
|
|
|
2017-01-08 03:32:49 +03:00
|
|
|
F12: 'showDevTools',
|
|
|
|
|
// NOTE: these are for systems where F** keys are not available
|
|
|
|
|
// or do other stuff...
|
2017-01-08 04:58:32 +03:00
|
|
|
meta_alt_I: 'F12',
|
|
|
|
|
ctrl_shift_p: 'F12',
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// dialogs...
|
|
|
|
|
// XXX should this be all here or in respective sections???
|
|
|
|
|
alt_A: 'browseActions',
|
|
|
|
|
|
|
|
|
|
//alt_S: 'browseActions: "/Sort/"',
|
|
|
|
|
alt_shift_A: 'listActions',
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// open/save...
|
|
|
|
|
O: 'browsePath',
|
|
|
|
|
ctrl_S: 'saveIndexHere',
|
|
|
|
|
ctrl_shift_S: 'exportDialog',
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// external editors...
|
|
|
|
|
// XXX not sure if this is the right way to go...
|
|
|
|
|
E: 'openInExtenalEditor',
|
|
|
|
|
shift_E: 'openInExtenalEditor: 1 -- Open in alternative editor',
|
|
|
|
|
alt_E: 'listExtenalEditors',
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// history...
|
2017-01-08 03:32:49 +03:00
|
|
|
ctrl_H: 'listURLHistory',
|
|
|
|
|
ctrl_shift_H: 'listSaveHistory',
|
2017-01-08 04:58:32 +03:00
|
|
|
|
|
|
|
|
U: 'undo',
|
|
|
|
|
ctrl_Z: 'undo',
|
|
|
|
|
shift_U: 'redo',
|
|
|
|
|
ctrl_shift_Z: 'redo',
|
2017-01-08 03:32:49 +03:00
|
|
|
alt_H: 'browseActions: "/History/" -- Open history menu',
|
2017-01-08 04:58:32 +03:00
|
|
|
|
2017-01-07 04:22:53 +03:00
|
|
|
|
2017-01-08 03:32:49 +03:00
|
|
|
// tilt...
|
|
|
|
|
// XXX experimental, not sure if wee need this with a keyboard...
|
|
|
|
|
T: 'rotateRibbonCCW -- Tilt ribbons counter clock wise',
|
|
|
|
|
shift_T: 'rotateRibbonCW -- Tilt ribbons clock wise',
|
|
|
|
|
alt_T: 'resetRibbonRotation -- Reset ribbon tilt',
|
2017-01-07 04:22:53 +03:00
|
|
|
|
|
|
|
|
|
2017-01-08 03:32:49 +03:00
|
|
|
// NOTE: this is handled by the wrapper at this point, so we do
|
|
|
|
|
// not have to do anything here...
|
|
|
|
|
F11: 'toggleFullScreen',
|
|
|
|
|
ctrl_F: 'F11',
|
|
|
|
|
meta_F: 'F11',
|
2017-01-07 04:22:53 +03:00
|
|
|
|
2017-01-08 04:58:32 +03:00
|
|
|
ctrl_R: 'loadNewImages!',
|
|
|
|
|
ctrl_alt_R: 'reload!',
|
|
|
|
|
ctrl_shift_R: 'F5',
|
|
|
|
|
|
2017-01-07 04:22:53 +03:00
|
|
|
|
2017-01-08 04:58:32 +03:00
|
|
|
// modes...
|
2017-01-08 03:32:49 +03:00
|
|
|
Enter: 'toggleSingleImage',
|
2017-01-08 04:58:32 +03:00
|
|
|
S: 'slideshowDialog',
|
2017-01-07 04:22:53 +03:00
|
|
|
|
2017-01-08 04:58:32 +03:00
|
|
|
|
|
|
|
|
// statusbar...
|
|
|
|
|
shift_I: 'toggleStatusBar',
|
|
|
|
|
G: 'editStatusBarIndex!',
|
|
|
|
|
shift_G: 'toggleStatusBarIndexMode!',
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// theme...
|
|
|
|
|
ctrl_R: 'toggleTheme!',
|
|
|
|
|
ctrl_shift_R: 'toggleTheme!: "prev"',
|
|
|
|
|
'ctrl+-': 'darkerTheme!',
|
|
|
|
|
'ctrl++': 'lighterTheme!',
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// navigation...
|
2017-01-08 03:32:49 +03:00
|
|
|
Left: 'prevImage',
|
2017-01-08 04:58:32 +03:00
|
|
|
Backspace: 'Left',
|
|
|
|
|
Right: 'nextImage',
|
|
|
|
|
Space: 'Right',
|
|
|
|
|
|
|
|
|
|
'(': 'prevImageInOrder',
|
|
|
|
|
')': 'nextImageInOrder',
|
|
|
|
|
|
|
|
|
|
PgUp: 'prevScreen',
|
2017-01-08 03:32:49 +03:00
|
|
|
ctrl_Left: 'prevScreen',
|
|
|
|
|
// XXX need to prevent default on mac + browser...
|
|
|
|
|
meta_Left: 'prevScreen',
|
|
|
|
|
PgDown: 'nextScreen',
|
|
|
|
|
ctrl_Right: 'nextScreen',
|
|
|
|
|
// XXX need to prevent default on mac + browser...
|
|
|
|
|
meta_Right: 'nextScreen',
|
2017-01-08 04:58:32 +03:00
|
|
|
|
|
|
|
|
Home: 'firstImage',
|
|
|
|
|
ctrl_Home: 'firstGlobalImage',
|
|
|
|
|
shift_Home: 'firstRibbon',
|
|
|
|
|
End: 'lastImage',
|
|
|
|
|
ctrl_End: 'lastGlobalImage',
|
|
|
|
|
shift_End: 'lastRibbon',
|
|
|
|
|
|
2017-01-08 03:32:49 +03:00
|
|
|
Up: 'prevRibbon',
|
2017-01-08 04:58:32 +03:00
|
|
|
Down: 'nextRibbon',
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// shifting...
|
2017-01-08 03:32:49 +03:00
|
|
|
shift_Up: 'shiftImageUp',
|
|
|
|
|
alt_shift_Up: 'travelImageUp',
|
|
|
|
|
ctrl_shift_Up: 'shiftImageUpNewRibbon',
|
2017-01-08 04:58:32 +03:00
|
|
|
|
2017-01-08 03:32:49 +03:00
|
|
|
shift_Down: 'shiftImageDown',
|
|
|
|
|
alt_shift_Down: 'travelImageDown',
|
|
|
|
|
ctrl_shift_Down: 'shiftImageDownNewRibbon',
|
2017-01-07 04:22:53 +03:00
|
|
|
|
2017-01-08 04:58:32 +03:00
|
|
|
alt_Left: 'shiftImageLeft!',
|
|
|
|
|
alt_Right: 'shiftImageRight!',
|
|
|
|
|
|
|
|
|
|
shift_R: 'setBaseRibbon',
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// editing...
|
|
|
|
|
R: 'rotateCW',
|
|
|
|
|
L: 'rotateCCW',
|
|
|
|
|
H: 'flipHorizontal',
|
|
|
|
|
V: 'flipVertical',
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// ribbon image stuff...
|
|
|
|
|
alt_I: 'browseActions: "/Image/" -- Show image menu',
|
|
|
|
|
alt_R: 'browseActions: "/Ribbon/" -- Open ribbon menu',
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// ranges...
|
|
|
|
|
// XXX experimental
|
|
|
|
|
// XXX add border jumping to Home/End...
|
|
|
|
|
'{': 'openRange',
|
|
|
|
|
'}': 'closeRange',
|
|
|
|
|
'*': 'setRangeBorder',
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// zooming...
|
|
|
|
|
'+': 'zoomIn',
|
|
|
|
|
'=': '+',
|
|
|
|
|
'-': 'zoomOut',
|
|
|
|
|
'_': '-',
|
|
|
|
|
|
2017-01-08 03:32:49 +03:00
|
|
|
'#0': 'fitMax',
|
|
|
|
|
'#1': 'fitImage',
|
|
|
|
|
'shift+#1': 'fitRibbon',
|
|
|
|
|
'ctrl+#1': 'fitOrig!',
|
|
|
|
|
'#2': 'fitImage: 2 -- Fit 2 Images',
|
|
|
|
|
'#3': 'fitImage: 3 -- Fit 3 images',
|
|
|
|
|
'shift+#3': 'fitRibbon: 3.5 -- Fit 3.5 ribbons',
|
|
|
|
|
'#4': 'fitImage: 4 -- Fit 4 images',
|
|
|
|
|
'#5': 'fitImage: 5 -- Fit 5 images',
|
|
|
|
|
'shift+#5': 'fitRibbon: 5.5 -- Fit 5.5 ribbons',
|
|
|
|
|
'#6': 'fitImage: 6 -- Fit 6 images',
|
|
|
|
|
'#7': 'fitImage: 7 -- Fit 7 images',
|
|
|
|
|
'#8':'fitImage: 8 -- Fit 8 images',
|
|
|
|
|
'#9': 'fitImage: 9 -- Fit 9 images',
|
|
|
|
|
|
2017-01-07 04:22:53 +03:00
|
|
|
|
2017-01-08 04:58:32 +03:00
|
|
|
// cropping...
|
2017-01-08 03:32:49 +03:00
|
|
|
F2: 'cropRibbon',
|
|
|
|
|
shift_F2: 'cropRibbonAndAbove',
|
|
|
|
|
ctrl_F2: 'cropMarked',
|
|
|
|
|
alt_F2: 'cropBookmarked',
|
2017-01-08 04:58:32 +03:00
|
|
|
C: 'browseActions: "/Crop/" -- Show crop menu',
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// metadata...
|
|
|
|
|
I: 'showMetadata',
|
|
|
|
|
ctrl_shift_I: 'showMetadata: "current" "full" -- Show full metadata',
|
|
|
|
|
|
2017-01-08 03:32:49 +03:00
|
|
|
|
|
|
|
|
// marking...
|
|
|
|
|
M: 'toggleMark',
|
|
|
|
|
ctrl_A: 'toggleMark!: "ribbon" "on" -- Mark all images in ribbon',
|
|
|
|
|
ctrl_D: 'toggleMark!: "ribbon" "off" -- Unmark all images in ribbon',
|
|
|
|
|
ctrl_I: 'toggleMark!: "ribbon" -- Invert marks in ribbon',
|
2017-01-08 04:58:32 +03:00
|
|
|
',': 'prevMarked',
|
|
|
|
|
'.': 'nextMarked',
|
|
|
|
|
alt_M: 'browseActions: "/Mark/" -- Show mark menu',
|
2017-01-08 03:32:49 +03:00
|
|
|
|
|
|
|
|
|
2017-01-08 04:58:32 +03:00
|
|
|
// bookmarking...
|
2017-01-08 03:32:49 +03:00
|
|
|
B: 'toggleBookmark',
|
2017-01-08 04:58:32 +03:00
|
|
|
'[': 'prevBookmarked',
|
|
|
|
|
']': 'nextBookmarked',
|
|
|
|
|
alt_B: 'browseActions: "/Bookmark/" -- Show bookmark menu',
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// copy/paste...
|
2017-01-08 03:32:49 +03:00
|
|
|
// do the default copy thing...
|
|
|
|
|
// NOTE: this stops the default: handler from getting the ctrl:
|
|
|
|
|
// key case...
|
|
|
|
|
ctrl_C: '',
|
2017-01-08 04:58:32 +03:00
|
|
|
|
|
|
|
|
|
|
|
|
|
// sort...
|
2017-01-08 03:32:49 +03:00
|
|
|
//shift_S: 'sortImages: "Date" -- Sort images by date',
|
|
|
|
|
shift_S: 'sortImages -- Sort images',
|
|
|
|
|
// XXX need to make this save to base_path if it exists and
|
|
|
|
|
// ask the user if it does not... now it always asks.
|
2017-01-08 04:58:32 +03:00
|
|
|
shift_R: 'reverseImages',
|
|
|
|
|
alt_S: 'sortDialog',
|
2017-01-08 03:32:49 +03:00
|
|
|
|
2017-01-08 04:58:32 +03:00
|
|
|
|
|
|
|
|
// doc...
|
2017-01-08 03:32:49 +03:00
|
|
|
// XXX for debug...
|
|
|
|
|
//ctrl_G: function(){ $('.viewer').toggleClass('visible-gid') },
|
|
|
|
|
'?': 'showKeyboardBindings',
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Format:
|
|
|
|
|
// {
|
|
|
|
|
// <mode>: {
|
|
|
|
|
// doc: <doc>,
|
|
|
|
|
// drop: [ <key>, ... ] | '*',
|
|
|
|
|
//
|
|
|
|
|
// <alias>: <handler>,
|
|
|
|
|
//
|
|
|
|
|
// <key>: <handler>,
|
|
|
|
|
// <key>: <alias>,
|
|
|
|
|
// },
|
|
|
|
|
// ...
|
|
|
|
|
// }
|
|
|
|
|
var Keyboard2HandlerProto = {
|
|
|
|
|
key_separators: ['+', '-', '_'],
|
|
|
|
|
modifiers: ['ctrl', 'alt', 'meta', 'shift'],
|
|
|
|
|
service_fields: ['doc', 'drop'],
|
|
|
|
|
|
|
|
|
|
// object/function
|
|
|
|
|
keyboard: null,
|
|
|
|
|
// XXX is this needed???
|
|
|
|
|
context: null,
|
|
|
|
|
|
|
|
|
|
// helpers...
|
|
|
|
|
event2key: function(evt){
|
|
|
|
|
evt = evt || event
|
|
|
|
|
|
|
|
|
|
var key = []
|
|
|
|
|
evt.ctrlKey && key.push('ctrl')
|
|
|
|
|
evt.altKey && key.push('alt')
|
|
|
|
|
evt.metaKey && key.push('meta')
|
|
|
|
|
evt.shiftKey && key.push('shift')
|
|
|
|
|
key.push(this.code2key(evt.keyCode))
|
|
|
|
|
|
|
|
|
|
return key
|
|
|
|
|
},
|
|
|
|
|
key2code: function(key){
|
|
|
|
|
return key in keyboard._KEY_CODES ?
|
|
|
|
|
keyboard._KEY_CODES[key]
|
|
|
|
|
: key.charCodeAt(0) },
|
|
|
|
|
code2key: function(code){
|
|
|
|
|
var name = String.fromCharCode(code)
|
|
|
|
|
return code in keyboard._SPECIAL_KEYS ? keyboard._SPECIAL_KEYS[code]
|
|
|
|
|
: name != '' ? name
|
|
|
|
|
: null },
|
|
|
|
|
shifted: function(key){
|
|
|
|
|
var output = key instanceof Array ? 'array' : 'string'
|
|
|
|
|
key = this.normalizeKey(this.splitKey(key)).slice()
|
|
|
|
|
var k = key.pop()
|
|
|
|
|
|
|
|
|
|
var s = (key.indexOf('shift') >= 0 ?
|
|
|
|
|
keyboard._SHIFT_KEYS[k]
|
|
|
|
|
: keyboard._UNSHIFT_KEYS[k])
|
|
|
|
|
|| null
|
|
|
|
|
|
|
|
|
|
var res = s == null ? key
|
|
|
|
|
: (key.indexOf('shift') >= 0 ?
|
|
|
|
|
key.filter(function(k){ return k != 'shift' })
|
|
|
|
|
: key.concat(['shift']))
|
|
|
|
|
res.push(s)
|
|
|
|
|
|
|
|
|
|
return s == null ? null
|
|
|
|
|
: output == 'string' ? res.join(this.key_separators[0])
|
|
|
|
|
: res
|
|
|
|
|
},
|
|
|
|
|
// XXX handle .key_separators as keys...
|
|
|
|
|
splitKey: function(key){
|
|
|
|
|
return key instanceof Array ?
|
|
|
|
|
key
|
|
|
|
|
: key
|
|
|
|
|
//.slice(0, -1)
|
|
|
|
|
.split(RegExp('['+this.key_separators.join('\\')+']'))
|
|
|
|
|
//.concat(key.slice(-1))
|
|
|
|
|
.filter(function(c){ return c != '' }) },
|
|
|
|
|
normalizeKey: function(key){
|
|
|
|
|
var output = key instanceof Array ? 'array' : 'string'
|
|
|
|
|
var modifiers = this.modifiers
|
|
|
|
|
// sort modifiers via .modifiers and keep the key last...
|
|
|
|
|
key = this.splitKey(key)
|
|
|
|
|
.sort(function(a, b){
|
|
|
|
|
a = modifiers.indexOf(a)
|
|
|
|
|
b = modifiers.indexOf(b)
|
|
|
|
|
return a >= 0 && b >= 0 ? a - b
|
|
|
|
|
: a < 0 ? 1
|
|
|
|
|
: -1 })
|
|
|
|
|
key.push(key.pop().capitalize())
|
|
|
|
|
return output == 'array' ? key : key.join(this.key_separators[0] || '+')
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
/*/ XXX not sure if this is needed...
|
|
|
|
|
normalizeBindings: function(keyboard){
|
|
|
|
|
keyboard = keyboard || this.keyboard
|
|
|
|
|
var that = this
|
|
|
|
|
var service_fields = this.service_fields
|
|
|
|
|
Object.keys(keyboard).forEach(function(mode){
|
|
|
|
|
mode = keyboard[mode]
|
|
|
|
|
|
|
|
|
|
Object.keys(mode).forEach(function(key){
|
|
|
|
|
// skip service fields...
|
|
|
|
|
if(service_fields.indexOf(key) >= 0){
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var n = that.normalizeKey(key)
|
|
|
|
|
|
|
|
|
|
if(n != key){
|
|
|
|
|
// duplicate key...
|
|
|
|
|
if(n in mode){
|
|
|
|
|
console.warn('duplicate keys: "'+ n +'" and "'+ k +'"')
|
2017-01-07 04:22:53 +03:00
|
|
|
}
|
2017-01-08 03:32:49 +03:00
|
|
|
|
|
|
|
|
mode[n] = mode[key]
|
|
|
|
|
delete mode[key]
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
})
|
|
|
|
|
return keyboard
|
|
|
|
|
},
|
|
|
|
|
//*/
|
|
|
|
|
|
|
|
|
|
//isModeApplicable: function(mode, context){ return true },
|
|
|
|
|
|
|
|
|
|
// get keys for handler...
|
|
|
|
|
//
|
|
|
|
|
keys: function(handler){
|
|
|
|
|
var res = {}
|
|
|
|
|
var keyboard = this.keyboard
|
|
|
|
|
|
|
|
|
|
Object.keys(keyboard).forEach(function(mode){
|
|
|
|
|
var bindings = keyboard[mode]
|
|
|
|
|
var keys = Object.keys(bindings)
|
|
|
|
|
// filter out the handler...
|
|
|
|
|
.filter(function(key){
|
|
|
|
|
return handler instanceof Function ?
|
|
|
|
|
handler(bindings[key])
|
|
|
|
|
: handler == bindings[key] })
|
|
|
|
|
// walk aliases...
|
|
|
|
|
.map(function(key){
|
|
|
|
|
var seen = []
|
|
|
|
|
while(bindings[key] in bindings){
|
|
|
|
|
key = bindings[key]
|
|
|
|
|
if(seen.indexOf(key) >= 0){
|
|
|
|
|
return null
|
|
|
|
|
}
|
|
|
|
|
seen.push(key)
|
2017-01-07 04:22:53 +03:00
|
|
|
}
|
2017-01-08 03:32:49 +03:00
|
|
|
return key
|
|
|
|
|
})
|
|
|
|
|
// clear out the loops from last stage...
|
|
|
|
|
.filter(function(key){ return !!key })
|
|
|
|
|
|
|
|
|
|
if(keys.length > 0){
|
|
|
|
|
res[mode] = keys
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
return res
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
// get/set handler for key...
|
|
|
|
|
//
|
|
|
|
|
handler: function(mode, key, handler){
|
|
|
|
|
var that = this
|
|
|
|
|
var keyboard = this.keyboard
|
|
|
|
|
var key_separators = this.key_separators
|
|
|
|
|
|
|
|
|
|
key = this.normalizeKey(this.splitKey(key))
|
|
|
|
|
var shift_key = this.shifted(key)
|
|
|
|
|
|
|
|
|
|
// match candidates...
|
|
|
|
|
var keys = key_separators
|
|
|
|
|
// full key...
|
|
|
|
|
.map(function(s){ return key.join(s) })
|
|
|
|
|
// full shift key...
|
|
|
|
|
.concat(shift_key ?
|
|
|
|
|
key_separators
|
|
|
|
|
.map(function(s){ return shift_key.join(s) })
|
|
|
|
|
: [])
|
|
|
|
|
|
|
|
|
|
// get modes...
|
|
|
|
|
var modes = mode == '*' ? Object.keys(keyboard)
|
|
|
|
|
: mode == 'applicable' || mode == '?' ? this.modes()
|
|
|
|
|
: mode instanceof Array ? mode
|
|
|
|
|
: [mode]
|
|
|
|
|
|
|
|
|
|
var walkAliases = function(bindings, handler){
|
|
|
|
|
// walk aliases...
|
|
|
|
|
var seen = []
|
|
|
|
|
while(handler in bindings){
|
|
|
|
|
handler = bindings[handler]
|
|
|
|
|
|
|
|
|
|
// check for loops...
|
|
|
|
|
if(seen.indexOf(handler) >= 0){
|
|
|
|
|
handler = null
|
|
|
|
|
break
|
2017-01-07 04:22:53 +03:00
|
|
|
}
|
2017-01-08 03:32:49 +03:00
|
|
|
seen.push(handler)
|
|
|
|
|
}
|
|
|
|
|
return handler
|
|
|
|
|
}
|
2017-01-07 04:22:53 +03:00
|
|
|
|
2017-01-08 03:32:49 +03:00
|
|
|
// get...
|
|
|
|
|
if(handler === undefined){
|
|
|
|
|
var res = {}
|
|
|
|
|
var k = key.slice(-1)[0]
|
|
|
|
|
var c = this.key2code(k)
|
|
|
|
|
|
|
|
|
|
// also test single key and code if everything else fails...
|
|
|
|
|
keys = keys
|
|
|
|
|
.concat([ k, c ])
|
|
|
|
|
.unique()
|
|
|
|
|
|
2017-01-08 04:21:22 +03:00
|
|
|
var drop = mode == 'applicable' || mode == '?'
|
|
|
|
|
for(var i=0; i < modes.length; i++){
|
|
|
|
|
var m = modes[i]
|
2017-01-07 04:22:53 +03:00
|
|
|
|
2017-01-08 03:32:49 +03:00
|
|
|
var bindings = keyboard[m]
|
2017-01-07 04:22:53 +03:00
|
|
|
|
2017-01-08 03:32:49 +03:00
|
|
|
handler = walkAliases(
|
|
|
|
|
bindings,
|
|
|
|
|
keys
|
|
|
|
|
.filter(function(k){ return bindings[k] })[0])
|
2017-01-07 04:22:53 +03:00
|
|
|
|
2017-01-08 04:21:22 +03:00
|
|
|
// handle explicit IGNORE...
|
|
|
|
|
if(drop && handler == 'IGNORE'){
|
|
|
|
|
break
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// we got a match...
|
2017-01-08 03:32:49 +03:00
|
|
|
if(handler){
|
|
|
|
|
res[m] = handler
|
|
|
|
|
}
|
|
|
|
|
|
2017-01-08 04:21:22 +03:00
|
|
|
// if key in .drop then ignore the rest...
|
|
|
|
|
if(drop
|
|
|
|
|
&& (bindings.drop == '*'
|
2017-01-08 03:32:49 +03:00
|
|
|
// XXX should this be more flexible by adding a
|
|
|
|
|
// specific key combo?
|
|
|
|
|
// ... if yes, we'll need to differentiate
|
|
|
|
|
// between X meaning drop only X and drop
|
|
|
|
|
// all combos with X...
|
2017-01-08 04:21:22 +03:00
|
|
|
|| (bindings.drop || []).indexOf(k) >= 0)){
|
|
|
|
|
break
|
|
|
|
|
}
|
|
|
|
|
}
|
2017-01-07 04:22:53 +03:00
|
|
|
|
2017-01-08 03:32:49 +03:00
|
|
|
return (typeof(mode) == typeof('str')
|
|
|
|
|
&& ['*', 'applicable', '?'].indexOf(mode) < 0) ?
|
|
|
|
|
res[mode]
|
|
|
|
|
: res
|
|
|
|
|
|
|
|
|
|
// set / remove...
|
|
|
|
|
} else {
|
|
|
|
|
modes.forEach(function(m){
|
|
|
|
|
var bindings = keyboard[m]
|
|
|
|
|
|
|
|
|
|
// remove all matching keys...
|
|
|
|
|
keys
|
|
|
|
|
.unique()
|
|
|
|
|
.forEach(function(k){
|
|
|
|
|
delete bindings[k]
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
// set handler if given...
|
|
|
|
|
if(handler && handler != ''){
|
|
|
|
|
keyboard[mode][key] = handler
|
2017-01-07 04:22:53 +03:00
|
|
|
}
|
|
|
|
|
})
|
2017-01-08 03:32:49 +03:00
|
|
|
}
|
2017-01-07 04:22:53 +03:00
|
|
|
|
2017-01-08 03:32:49 +03:00
|
|
|
return this
|
2017-01-07 04:22:53 +03:00
|
|
|
},
|
|
|
|
|
|
|
|
|
|
// get applicable modes...
|
|
|
|
|
//
|
|
|
|
|
modes: function(context){
|
2017-01-08 03:32:49 +03:00
|
|
|
var that = this
|
|
|
|
|
return Object.keys(this.keyboard)
|
|
|
|
|
.filter(function(mode){
|
|
|
|
|
return !that.isModeApplicable
|
|
|
|
|
|| that.isModeApplicable(mode, context || this.context) }) },
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2017-01-08 04:21:22 +03:00
|
|
|
/*/ XXX for testing...
|
2017-01-08 03:32:49 +03:00
|
|
|
var kb = window.kb = Object.create(Keyboard2HandlerProto)
|
|
|
|
|
kb.keyboard = GLOBAL_KEYBOARD2
|
|
|
|
|
kb.isModeApplicable = function(mode, context){
|
|
|
|
|
var pattern = this.keyboard[mode].pattern
|
|
|
|
|
return !pattern
|
|
|
|
|
|| pattern == '*'
|
|
|
|
|
|| $(this.keyboard[mode].pattern).length > 0
|
2017-01-07 04:22:53 +03:00
|
|
|
}
|
2017-01-08 04:21:22 +03:00
|
|
|
//*/
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// XXX this is not compatible with GLOBAL_KEYBOARD use GLOBAL_KEYBOARD2!!
|
|
|
|
|
function makeKeyboardHandler(keyboard, unhandled, actions){
|
|
|
|
|
var kb = Object.create(Keyboard2HandlerProto)
|
|
|
|
|
kb.keyboard = keyboard
|
|
|
|
|
// XXX this is specific to ImageGrid ...
|
|
|
|
|
kb.isModeApplicable = function(mode, context){
|
|
|
|
|
var pattern = this.keyboard[mode].pattern
|
|
|
|
|
return !pattern
|
|
|
|
|
|| pattern == '*'
|
|
|
|
|
|| $(this.keyboard[mode].pattern).length > 0
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return function(evt){
|
|
|
|
|
var res
|
|
|
|
|
var did_handling = false
|
|
|
|
|
var key = kb.event2key(evt)
|
|
|
|
|
var handlers = kb.handler('applicable', key)
|
|
|
|
|
|
|
|
|
|
Object.keys(handlers).forEach(function(mode){
|
|
|
|
|
// XXX do we need this???
|
|
|
|
|
if(res === false){
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var h = keyboard.parseActionCall(handlers[mode])
|
|
|
|
|
|
|
|
|
|
if(h && h.action in actions){
|
|
|
|
|
did_handling = true
|
|
|
|
|
|
|
|
|
|
h.no_default
|
|
|
|
|
&& evt.preventDefault()
|
|
|
|
|
|
|
|
|
|
// call the handler...
|
|
|
|
|
res = actions[h.action].apply(actions, h.args)
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
unhandled
|
|
|
|
|
&& !did_handling
|
|
|
|
|
&& unhandled.call(actions, evt)
|
|
|
|
|
}
|
|
|
|
|
}
|
2017-01-07 04:22:53 +03:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2016-04-02 17:34:25 +03:00
|
|
|
/*********************************************************************/
|
2016-04-03 06:30:30 +03:00
|
|
|
// XXX add a key binding list UI...
|
|
|
|
|
// XXX add loading/storing of kb bindings...
|
2016-04-02 17:34:25 +03:00
|
|
|
|
2016-04-03 00:58:49 +03:00
|
|
|
// XXX add introspection and doc actions...
|
2016-04-02 17:34:25 +03:00
|
|
|
var KeyboardActions = actions.Actions({
|
|
|
|
|
config: {
|
|
|
|
|
// limit key repeat to one per N milliseconds.
|
|
|
|
|
//
|
|
|
|
|
// Set this to -1 or null to run keys without any limitations.
|
|
|
|
|
'max-key-repeat-rate': 0,
|
2016-12-03 14:39:04 +03:00
|
|
|
|
|
|
|
|
'keyboard-repeat-pause-check': 100,
|
2016-12-03 17:28:58 +03:00
|
|
|
|
|
|
|
|
// Sets the target element to which the keyboard event handler
|
|
|
|
|
// is bound...
|
|
|
|
|
//
|
|
|
|
|
// Supported values:
|
|
|
|
|
// 'window' - window element
|
|
|
|
|
// 'document' - document element
|
|
|
|
|
// 'viewer' - the viewer (default)
|
|
|
|
|
// null - default element
|
|
|
|
|
// <css selector> - any css selector
|
|
|
|
|
//
|
|
|
|
|
// NOTE: this value is not live, to update the target restart
|
|
|
|
|
// the handler by cycling the toggler off and on...
|
|
|
|
|
// NOTE: the target element must be focusable...
|
|
|
|
|
'keyboard-event-source': 'window',
|
2016-04-02 17:34:25 +03:00
|
|
|
},
|
|
|
|
|
|
|
|
|
|
get keyboard(){
|
2017-01-03 02:55:25 +03:00
|
|
|
return this.__keyboard_config },
|
2016-04-02 17:34:25 +03:00
|
|
|
|
2016-12-03 14:39:04 +03:00
|
|
|
pauseKeyboardRepeat: ['- Interface/',
|
2016-12-03 17:28:58 +03:00
|
|
|
function(){
|
|
|
|
|
this.__keyboard_repeat_paused = true }],
|
2016-12-03 14:39:04 +03:00
|
|
|
|
2017-01-05 03:06:06 +03:00
|
|
|
toggleKeyboardHandling: ['- Interface/Keyboard handling',
|
2016-04-02 17:34:25 +03:00
|
|
|
toggler.Toggler(null, function(_, state){
|
|
|
|
|
if(state == null){
|
|
|
|
|
return this.__keyboard_handler ? 'on' : 'off'
|
|
|
|
|
}
|
|
|
|
|
|
2016-12-03 14:39:04 +03:00
|
|
|
// repeat stop checker...
|
|
|
|
|
var check = (function(){
|
|
|
|
|
if(this.config['keyboard-repeat-pause-check'] > 0
|
|
|
|
|
&& this.__keyboard_repeat_paused){
|
|
|
|
|
var that = this
|
|
|
|
|
this.__keyboard_repeat_pause_timeout
|
|
|
|
|
&& clearTimeout(this.__keyboard_repeat_pause_timeout)
|
|
|
|
|
|
|
|
|
|
this.__keyboard_repeat_pause_timeout = setTimeout(function(){
|
|
|
|
|
delete that.__keyboard_repeat_paused
|
|
|
|
|
delete that.__keyboard_repeat_pause_timeout
|
|
|
|
|
}, this.config['keyboard-repeat-pause-check'] || 100)
|
|
|
|
|
|
|
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
return true
|
|
|
|
|
}).bind(this)
|
|
|
|
|
|
2016-04-02 17:34:25 +03:00
|
|
|
// start/reset keyboard handling...
|
|
|
|
|
if(state == 'on'){
|
|
|
|
|
var that = this
|
|
|
|
|
|
2016-12-03 17:28:58 +03:00
|
|
|
// NOTE: the target element must be focusable...
|
|
|
|
|
var target =
|
|
|
|
|
this.__keyboard_event_source =
|
|
|
|
|
this.config['keyboard-event-source'] == null
|
|
|
|
|
|| this.config['keyboard-event-source'] == 'viewer' ? this.ribbons.viewer
|
|
|
|
|
: this.config['keyboard-event-source'] == 'window' ? $(window)
|
|
|
|
|
: this.config['keyboard-event-source'] == 'document' ? $(document)
|
|
|
|
|
: $(this.config['keyboard-event-source'])
|
|
|
|
|
|
2016-04-02 17:34:25 +03:00
|
|
|
// need to reset...
|
|
|
|
|
if(this.__keyboard_handler != null){
|
|
|
|
|
target.off('keydown', this.__keyboard_handler)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// setup base keyboard for devel, in case something breaks...
|
|
|
|
|
// This branch does not drop keys...
|
|
|
|
|
if(this.config['max-key-repeat-rate'] < 0
|
|
|
|
|
|| this.config['max-key-repeat-rate'] == null){
|
|
|
|
|
//this.ribbons.viewer
|
|
|
|
|
var handler =
|
|
|
|
|
this.__keyboard_handler =
|
2016-12-12 15:29:37 +03:00
|
|
|
keyboard.stoppableKeyboardRepeat(
|
2016-12-03 14:39:04 +03:00
|
|
|
keyboard.makeKeyboardHandler(
|
|
|
|
|
function(){ return that.__keyboard_config },
|
|
|
|
|
function(k){ window.DEBUG && console.log('KEY:', k) },
|
|
|
|
|
this),
|
|
|
|
|
check)
|
2016-04-02 17:34:25 +03:00
|
|
|
|
|
|
|
|
// drop keys if repeating too fast...
|
|
|
|
|
// NOTE: this is done for smoother animations...
|
|
|
|
|
} else {
|
|
|
|
|
var handler =
|
|
|
|
|
this.__keyboard_handler =
|
2016-12-12 15:29:37 +03:00
|
|
|
keyboard.stoppableKeyboardRepeat(
|
2016-12-03 14:39:04 +03:00
|
|
|
keyboard.dropRepeatingkeys(
|
|
|
|
|
keyboard.makeKeyboardHandler(
|
|
|
|
|
function(){ return that.__keyboard_config },
|
|
|
|
|
function(k){ window.DEBUG && console.log(k) },
|
|
|
|
|
this),
|
|
|
|
|
function(){
|
|
|
|
|
return that.config['max-key-repeat-rate']
|
|
|
|
|
}),
|
|
|
|
|
check)
|
2016-04-02 17:34:25 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
target.keydown(handler)
|
|
|
|
|
|
|
|
|
|
// stop keyboard handling...
|
|
|
|
|
} else {
|
2016-12-03 17:28:58 +03:00
|
|
|
this.__keyboard_event_source
|
|
|
|
|
&& this.__keyboard_event_source
|
|
|
|
|
.off('keydown', this.__keyboard_handler)
|
|
|
|
|
|
2016-04-02 17:34:25 +03:00
|
|
|
delete this.__keyboard_handler
|
2016-12-03 17:28:58 +03:00
|
|
|
delete this.__keyboard_event_source
|
2016-04-02 17:34:25 +03:00
|
|
|
}
|
|
|
|
|
},
|
2016-04-03 20:57:03 +03:00
|
|
|
['on', 'off'])],
|
|
|
|
|
|
2017-01-03 02:55:25 +03:00
|
|
|
// Format:
|
|
|
|
|
// {
|
2017-01-05 03:06:06 +03:00
|
|
|
// <action>: [
|
|
|
|
|
// <key>,
|
2017-01-03 02:55:25 +03:00
|
|
|
// ...
|
2017-01-05 03:06:06 +03:00
|
|
|
// ],
|
2017-01-03 02:55:25 +03:00
|
|
|
// ...
|
|
|
|
|
// }
|
|
|
|
|
//
|
2017-01-05 03:06:06 +03:00
|
|
|
// XXX this does not check overloading between modes...
|
2017-01-03 02:55:25 +03:00
|
|
|
getKeysForAction: ['- Interface/',
|
2017-01-05 03:06:06 +03:00
|
|
|
function(actions, modes){
|
|
|
|
|
actions = actions == '*' ? null : actions
|
|
|
|
|
actions = !actions || actions instanceof Array ? actions : [actions]
|
2017-01-03 02:55:25 +03:00
|
|
|
|
2017-01-05 03:06:06 +03:00
|
|
|
modes = modes || null
|
|
|
|
|
modes = !modes || modes instanceof Array ? modes : [modes]
|
|
|
|
|
modes = modes || this.getKeyboardModes()
|
|
|
|
|
|
|
|
|
|
// XXX does this handle overloading???
|
2017-01-04 20:27:47 +03:00
|
|
|
var help = keyboard.buildKeybindingsHelp(
|
|
|
|
|
this.keyboard,
|
|
|
|
|
null,
|
|
|
|
|
this,
|
2017-01-05 03:06:06 +03:00
|
|
|
// get full doc compatible with get path...
|
|
|
|
|
function(action, args){
|
|
|
|
|
// NOTE: we do not care about the actual args
|
|
|
|
|
// here, all we need is for this to mismatch
|
|
|
|
|
// if args exist...
|
|
|
|
|
//return args.length == 0 ? Object.keys(this.getPath(action))[0] : '--' })
|
|
|
|
|
return args.length == 0 ? action : '--' })
|
2017-01-03 02:55:25 +03:00
|
|
|
var res = {}
|
|
|
|
|
|
2017-01-05 03:06:06 +03:00
|
|
|
// build the result...
|
|
|
|
|
Object.keys(help)
|
|
|
|
|
// filter modes...
|
|
|
|
|
.filter(function(mode){ return modes.indexOf(mode) >= 0 })
|
|
|
|
|
.forEach(function(mode){
|
|
|
|
|
Object.keys(help[mode])
|
|
|
|
|
// keep only the actions given...
|
|
|
|
|
.filter(function(action){
|
|
|
|
|
return action != '--'
|
|
|
|
|
&& action != 'doc'
|
|
|
|
|
&& (!actions
|
|
|
|
|
|| actions.indexOf(action) >= 0)
|
|
|
|
|
})
|
|
|
|
|
.forEach(function(action){
|
|
|
|
|
res[action] = (res[action] || []).concat(help[mode][action])
|
|
|
|
|
})
|
|
|
|
|
})
|
2017-01-03 02:55:25 +03:00
|
|
|
|
|
|
|
|
return res
|
|
|
|
|
}],
|
|
|
|
|
|
|
|
|
|
// XXX argument #3 is not yet used (see: lib/keyboard.js)...
|
|
|
|
|
getKeyboardModes: ['- Interface/',
|
|
|
|
|
function(){
|
2017-01-03 02:59:56 +03:00
|
|
|
return this.__keyboard_event_source ?
|
|
|
|
|
keyboard.getApplicableModes(this.keyboard, null, this.__keyboard_event_source)
|
|
|
|
|
: [] }],
|
2017-01-03 02:55:25 +03:00
|
|
|
|
2016-04-07 04:58:22 +03:00
|
|
|
// XXX need to pre-process the docs...
|
|
|
|
|
// - remove the path component...
|
|
|
|
|
// - insert the action name where not doc present...
|
|
|
|
|
// XXX cleanup CSS
|
2016-05-08 19:03:04 +03:00
|
|
|
showKeyboardBindings: ['Interface/Show keyboard bindings...',
|
2016-04-30 18:39:14 +03:00
|
|
|
widgets.makeUIDialog('Drawer',
|
|
|
|
|
function(){
|
2017-01-04 20:27:47 +03:00
|
|
|
return keyboard.buildKeybindingsHelpHTML(
|
|
|
|
|
this.__keyboard_config,
|
|
|
|
|
this,
|
|
|
|
|
function(action){
|
|
|
|
|
return Object.keys(this.getPath(action))[0] })
|
2016-04-30 18:39:14 +03:00
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
background: 'white',
|
|
|
|
|
focusable: true,
|
|
|
|
|
})],
|
2017-01-05 03:06:06 +03:00
|
|
|
|
|
|
|
|
|
2017-01-05 05:23:29 +03:00
|
|
|
// XXX Things not to forget:
|
|
|
|
|
// * sort modes
|
|
|
|
|
// * sort actions
|
|
|
|
|
// * action editor dialog
|
|
|
|
|
// * mode editor dialog
|
|
|
|
|
// * add ability to disable key (???)
|
2017-01-05 23:00:49 +03:00
|
|
|
// * ignore flag/list...
|
2017-01-05 05:40:48 +03:00
|
|
|
// XXX key editor:
|
|
|
|
|
//
|
2017-01-05 06:03:46 +03:00
|
|
|
// [ mode ]
|
2017-01-05 05:40:48 +03:00
|
|
|
// [ action (text with dataset) ] [ args (text field) ] no default: [_]
|
|
|
|
|
// ---
|
|
|
|
|
// <list of keys>
|
|
|
|
|
// new
|
2017-01-05 07:38:56 +03:00
|
|
|
// XXX add view mode (read only)...
|
|
|
|
|
// XXX BUG sections with doc do not show up in title...
|
2017-01-07 04:22:53 +03:00
|
|
|
// XXX BUG:
|
|
|
|
|
// ig.bindKey('Global', 'X', 'editKeyboardBindings')
|
|
|
|
|
// ig.editKeyboardBindings()
|
|
|
|
|
// -> shows alt-X instead of X
|
|
|
|
|
// ig.bindKey('Global', 'X', 'editKeyboardBindings')
|
|
|
|
|
// ig.editKeyboardBindings()
|
|
|
|
|
// -> shows two keys ctrl-Z and ctrl-shift-Z instead of Z
|
2017-01-05 23:00:49 +03:00
|
|
|
browseKeyboardBindings: ['Interface/Keyboard bindings...',
|
|
|
|
|
widgets.makeUIDialog(function(path, edit){
|
2017-01-05 04:53:46 +03:00
|
|
|
var actions = this
|
2017-01-05 03:06:06 +03:00
|
|
|
|
|
|
|
|
// Format:
|
|
|
|
|
// {
|
|
|
|
|
// <mode>: {
|
|
|
|
|
// <action-code>: [ <key>, ... ],
|
|
|
|
|
// ...
|
|
|
|
|
// },
|
|
|
|
|
// ...
|
|
|
|
|
// }
|
|
|
|
|
var keys = keyboard.buildKeybindingsHelp(
|
|
|
|
|
this.keyboard,
|
|
|
|
|
null,
|
|
|
|
|
this,
|
|
|
|
|
// get full doc compatible with get path...
|
|
|
|
|
function(action, args, no_default, doc){
|
|
|
|
|
return action
|
|
|
|
|
+ (no_default ? '!' : '')
|
|
|
|
|
+ (args.length > 0 ?
|
|
|
|
|
': '+ args.map(JSON.stringify).join(' ')
|
|
|
|
|
: '')
|
|
|
|
|
})
|
|
|
|
|
|
2017-01-05 04:53:46 +03:00
|
|
|
var dialog = browse.makeLister(null,
|
|
|
|
|
function(path, make){
|
|
|
|
|
Object.keys(keys)
|
|
|
|
|
.forEach(function(mode){
|
2017-01-05 23:00:49 +03:00
|
|
|
var ignored = actions.keyboard[mode].ignore || []
|
2017-01-06 02:18:21 +03:00
|
|
|
var bound_ignored = []
|
2017-01-05 23:00:49 +03:00
|
|
|
|
2017-01-05 04:53:46 +03:00
|
|
|
// section heading...
|
|
|
|
|
make(keys[mode].doc ?
|
|
|
|
|
$('<span>')
|
|
|
|
|
// NOTE: at this time adding a br
|
|
|
|
|
// is faster and simpler than
|
|
|
|
|
// doing this in CSS...
|
|
|
|
|
// XXX revise...
|
|
|
|
|
.html(mode + '<br>')
|
|
|
|
|
.append($('<span>')
|
|
|
|
|
.addClass('doc')
|
|
|
|
|
.html(keys[mode].doc))
|
2017-01-06 02:18:21 +03:00
|
|
|
: mode,
|
|
|
|
|
{
|
|
|
|
|
not_filtered_out: true,
|
|
|
|
|
// XXX should sections be searchable???
|
|
|
|
|
//not_searchable: true,
|
|
|
|
|
})
|
|
|
|
|
.addClass('mode')
|
2017-01-05 07:38:56 +03:00
|
|
|
|
2017-01-05 04:53:46 +03:00
|
|
|
// bindings...
|
2017-01-06 02:18:21 +03:00
|
|
|
var c = 0
|
|
|
|
|
Object.keys(keys[mode]).forEach(function(action){
|
|
|
|
|
action != 'doc'
|
|
|
|
|
// NOTE: wee need the button spec to be
|
|
|
|
|
// searchable, thus we are not using
|
|
|
|
|
// the keys attr as in .browseActions(..)
|
|
|
|
|
&& make([action, ' ', '$BUTTONS']
|
|
|
|
|
.concat($('<span>')
|
|
|
|
|
.addClass('text')
|
|
|
|
|
.html(keys[mode][action]
|
2017-01-05 23:00:49 +03:00
|
|
|
// mark key if it is in ignored...
|
|
|
|
|
.map(function(s){
|
|
|
|
|
s = s.split('+')
|
|
|
|
|
var k = s.pop()
|
2017-01-06 02:18:21 +03:00
|
|
|
var i = ignored.indexOf(k)
|
|
|
|
|
i >= 0
|
|
|
|
|
&& bound_ignored
|
|
|
|
|
.push(ignored[i])
|
2017-01-05 23:00:49 +03:00
|
|
|
s.push(k
|
2017-01-06 02:18:21 +03:00
|
|
|
+ (i >= 0 ? '<sup>*</sup>' : ''))
|
|
|
|
|
return s.join('+') })
|
|
|
|
|
.join(' / '))))
|
|
|
|
|
.addClass('key '
|
|
|
|
|
+ (action == 'IGNORE' ? 'ignored' : ''))
|
|
|
|
|
&& c++
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
// no keys in view mode...
|
|
|
|
|
// XXX is adding info stuff like this a correct
|
|
|
|
|
// thing to do in code?
|
|
|
|
|
c == 0 && !edit
|
|
|
|
|
&& make('No bindings...',
|
|
|
|
|
{
|
|
|
|
|
disabled: true,
|
|
|
|
|
hide_on_search: true,
|
|
|
|
|
})
|
|
|
|
|
.addClass('info')
|
|
|
|
|
|
|
|
|
|
// unpropagated and unbound keys...
|
|
|
|
|
make(['Unpropagated and unbound keys:',
|
|
|
|
|
// NOTE: this blank is so as to avoid
|
|
|
|
|
// sticking the action and keys
|
|
|
|
|
// together in path...
|
|
|
|
|
' ',
|
|
|
|
|
'$BUTTONS',
|
|
|
|
|
ignored
|
|
|
|
|
.filter(function(k){
|
|
|
|
|
return bound_ignored.indexOf(k) == -1 })
|
|
|
|
|
.join(' / ')])
|
|
|
|
|
.addClass('ignore-list')
|
2017-01-05 04:53:46 +03:00
|
|
|
|
2017-01-05 23:00:49 +03:00
|
|
|
// controls...
|
|
|
|
|
if(edit){
|
|
|
|
|
var elem = make('new', {
|
|
|
|
|
buttons: [
|
|
|
|
|
// XXX
|
|
|
|
|
['key',
|
|
|
|
|
function(){
|
|
|
|
|
//elem.before( XXX )
|
|
|
|
|
}],
|
|
|
|
|
// XXX
|
|
|
|
|
['mode',
|
|
|
|
|
function(){
|
|
|
|
|
//elem.after( XXX )
|
|
|
|
|
}],
|
|
|
|
|
]})
|
|
|
|
|
.addClass('new')
|
|
|
|
|
}
|
2017-01-05 04:53:46 +03:00
|
|
|
})
|
2017-01-06 02:18:21 +03:00
|
|
|
|
|
|
|
|
// notes...
|
|
|
|
|
// XXX is adding info stuff like this a correct
|
|
|
|
|
// thing to do in code?
|
|
|
|
|
make('---')
|
|
|
|
|
make($('<span>')
|
|
|
|
|
.addClass('text')
|
|
|
|
|
.html('<sup>*</sup> keys not propogated to next section.'),
|
|
|
|
|
{
|
|
|
|
|
disabled: true ,
|
|
|
|
|
hide_on_search: true,
|
|
|
|
|
})
|
|
|
|
|
.addClass('info')
|
2017-01-05 04:53:46 +03:00
|
|
|
}, {
|
2017-01-05 23:00:49 +03:00
|
|
|
cls: [
|
|
|
|
|
'key-bindings',
|
|
|
|
|
'no-item-numbers',
|
|
|
|
|
(edit ? 'edit' : 'browse'),
|
|
|
|
|
].join(' '),
|
|
|
|
|
|
|
|
|
|
itemButtons: edit ?
|
|
|
|
|
[
|
|
|
|
|
// NOTE: ordering within one section is purely
|
|
|
|
|
// aesthetic and has no function...
|
|
|
|
|
// XXX do wee actually need ordering???
|
|
|
|
|
// XXX up
|
|
|
|
|
//['⏶', function(){}],
|
|
|
|
|
// XXX down
|
|
|
|
|
//['⏷', function(){}],
|
|
|
|
|
|
|
|
|
|
// XXX edit -- launch the editor...
|
|
|
|
|
['⋯', function(){}],
|
|
|
|
|
//['edit', function(){}],
|
|
|
|
|
//['🖉', function(){}],
|
|
|
|
|
]
|
|
|
|
|
: [],
|
2017-01-05 04:53:46 +03:00
|
|
|
})
|
|
|
|
|
|
|
|
|
|
return dialog
|
2017-01-05 03:06:06 +03:00
|
|
|
})],
|
2017-01-05 23:00:49 +03:00
|
|
|
// XXX place this in /Doc/.. (???)
|
|
|
|
|
editKeyboardBindings: ['Interface/Keyboard bindings editor...',
|
|
|
|
|
widgets.uiDialog(function(path){
|
|
|
|
|
return this.browseKeyboardBindings(path, true) })],
|
2017-01-05 05:23:29 +03:00
|
|
|
|
|
|
|
|
// XXX
|
|
|
|
|
resetKeyBindings: ['Interface/Restore default key bindings',
|
2017-01-05 23:00:49 +03:00
|
|
|
function(){
|
2017-01-07 04:22:53 +03:00
|
|
|
this.__keyboard_config = GLOBAL_KEYBOARD }],
|
|
|
|
|
|
|
|
|
|
// XXX do we look for aliases in this mode only or in all modes?
|
|
|
|
|
getKeyHandler: ['- Interface/',
|
|
|
|
|
function(modes, key, action){
|
2017-01-08 03:32:49 +03:00
|
|
|
var that = this
|
|
|
|
|
|
|
|
|
|
// XXX normalize key...
|
|
|
|
|
var full_key = key
|
|
|
|
|
var modifiers = key.split('+')
|
|
|
|
|
key = modifiers.pop()
|
|
|
|
|
|
|
|
|
|
var code = keyboard.toKeyCode(key)
|
|
|
|
|
var args = [].slice.call(arguments).slice(3)
|
|
|
|
|
|
|
|
|
|
// set handler...
|
|
|
|
|
if(action){
|
|
|
|
|
modes = modes instanceof Array ? modes : [modes]
|
|
|
|
|
// ignore all but the first mode...
|
|
|
|
|
modes = modes.slice(0, 1)
|
|
|
|
|
|
|
|
|
|
// get handler...
|
|
|
|
|
} else {
|
|
|
|
|
var shift_key = (modifiers.indexOf('shift') >= 0 ?
|
|
|
|
|
keyboard._SHIFT_KEYS[key]
|
|
|
|
|
: keyboard._UNSHIFT_KEYS[key])
|
|
|
|
|
|| ''
|
|
|
|
|
var shift_modifiers = shift_key != ''
|
|
|
|
|
&& (((modifiers.indexOf('shift') >= 0 ?
|
|
|
|
|
modifiers.filter(function(k){ return k != 'shift' })
|
|
|
|
|
: modifiers.concat(['shift'])))
|
|
|
|
|
|| modifiers).join('+')
|
|
|
|
|
var full_shift_key = shift_modifiers == '' ?
|
|
|
|
|
shift_key
|
|
|
|
|
: shift_modifiers +'+'+ shift_key
|
|
|
|
|
|
|
|
|
|
var any = modes == 'any'
|
|
|
|
|
modes = any ? this.getKeyboardModes()
|
|
|
|
|
: modes == '*' ? Object.keys(this.keyboard)
|
|
|
|
|
: modes
|
|
|
|
|
modes = modes instanceof Array ? modes : [modes]
|
|
|
|
|
|
|
|
|
|
// filter modes...
|
|
|
|
|
var ignore = false
|
|
|
|
|
modes = any ?
|
|
|
|
|
modes
|
|
|
|
|
.filter(function(mode){
|
|
|
|
|
if(ignore){
|
|
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var i = that.keyboard[mode].ignore || []
|
|
|
|
|
|
|
|
|
|
ignore = i.indexOf(full_key) >= 0
|
|
|
|
|
|| i.indexOf(key) >= 0
|
|
|
|
|
|| i.indexOf(shift_key) >= 0
|
|
|
|
|
|| i.indexOf(full_shift_key) >= 0
|
|
|
|
|
|| i.indexOf(code) >= 0
|
|
|
|
|
|
|
|
|
|
return true
|
|
|
|
|
})
|
|
|
|
|
: modes
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
modifiers = modifiers.join('+')
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// search modes...
|
|
|
|
|
var res = {}
|
|
|
|
|
ignore = false
|
|
|
|
|
modes
|
|
|
|
|
.forEach(function(mode){
|
|
|
|
|
if(ignore){
|
|
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var bindings = that.keyboard[mode]
|
|
|
|
|
|
|
|
|
|
if(action){
|
|
|
|
|
var match = 'direct'
|
|
|
|
|
var alias = code in bindings ? code : key
|
|
|
|
|
|
|
|
|
|
} else {
|
|
|
|
|
// direct match...
|
|
|
|
|
var match = 'direct'
|
|
|
|
|
var alias = full_key in bindings ? full_key
|
|
|
|
|
: key in bindings ? key
|
|
|
|
|
: null
|
|
|
|
|
// shift key match...
|
|
|
|
|
match = alias == null ? 'shifted' : match
|
|
|
|
|
alias = alias == null ?
|
|
|
|
|
(full_shift_key in bindings ? full_shift_key
|
|
|
|
|
: shift_key in bindings ? shift_key
|
|
|
|
|
: null)
|
|
|
|
|
: alias
|
|
|
|
|
// code match...
|
|
|
|
|
match = alias == null ? 'code' : match
|
|
|
|
|
alias = alias == null ?
|
|
|
|
|
(code in bindings ? code : null)
|
|
|
|
|
: alias
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var mod = (match == 'code' || match == 'direct') ?
|
|
|
|
|
modifiers
|
|
|
|
|
: shift_modifiers
|
|
|
|
|
mod = mod == '' ? 'default' : mod
|
|
|
|
|
|
|
|
|
|
var handler = alias
|
|
|
|
|
|
|
|
|
|
// spin through aliases...
|
|
|
|
|
// XXX do we look for aliases in this mode only or in all modes?
|
|
|
|
|
var seen = []
|
|
|
|
|
while(handler in bindings){
|
|
|
|
|
// handler loop...
|
|
|
|
|
if(seen.indexOf(handler) >= 0){
|
|
|
|
|
return null
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
alias = handler
|
|
|
|
|
handler = bindings[alias]
|
|
|
|
|
seen.push(alias)
|
|
|
|
|
|
|
|
|
|
// go into the map structure...
|
|
|
|
|
if(!action && typeof(handler) != typeof('str')){
|
|
|
|
|
handler = handler[mod]
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// set the action...
|
|
|
|
|
if(action){
|
|
|
|
|
if(handler == null || typeof(handler) == typeof('str')){
|
|
|
|
|
bindings[alias] = modifiers.length == 0 ?
|
|
|
|
|
action
|
|
|
|
|
: { modifiers : action }
|
|
|
|
|
|
|
|
|
|
} else if(modifiers.length == 0){
|
|
|
|
|
handler['default'] = action
|
|
|
|
|
|
|
|
|
|
} else {
|
|
|
|
|
handler[modifiers] = action
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// get the action...
|
|
|
|
|
} else {
|
|
|
|
|
if(handler){
|
|
|
|
|
res[mode] = handler
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ignore = any && handler == 'IGNORE'
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
return !action ?
|
|
|
|
|
(modes.length == 1 ? res[modes[0]] : res) || null
|
|
|
|
|
: undefined
|
2017-01-07 04:22:53 +03:00
|
|
|
}],
|
|
|
|
|
// XXX move this to lib/keyboard.js
|
|
|
|
|
// XXX not done yet...
|
|
|
|
|
bindKey: ['- Interface/',
|
|
|
|
|
function(mode, key, action){
|
|
|
|
|
var modifiers = key.split('+')
|
|
|
|
|
key = modifiers.pop()
|
|
|
|
|
modifiers = modifiers.join('+')
|
|
|
|
|
var code = keyboard.toKeyCode(key)
|
|
|
|
|
var args = [].slice.call(arguments).slice(3)
|
|
|
|
|
action = action
|
|
|
|
|
+ (args.length > 0 ?
|
|
|
|
|
': '+ args.map(JSON.stringify).join(' ')
|
|
|
|
|
: '')
|
|
|
|
|
var bindings = this.keyboard[mode]
|
|
|
|
|
|
|
|
|
|
var alias = code in bindings ? code : key
|
|
|
|
|
var handler = bindings[key] || bindings[code]
|
|
|
|
|
|
|
|
|
|
// spin through aliases...
|
|
|
|
|
var seen = []
|
|
|
|
|
while(handler in bindings){
|
|
|
|
|
// handler loop...
|
|
|
|
|
if(seen.indexOf(handler) >= 0){
|
|
|
|
|
return null
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
alias = handler
|
|
|
|
|
handler = bindings[alias]
|
|
|
|
|
seen.push(alias)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if(handler == null || typeof(handler) == typeof('str')){
|
|
|
|
|
bindings[alias] = modifiers.length == 0 ?
|
|
|
|
|
action
|
|
|
|
|
: { modifiers : action }
|
|
|
|
|
|
|
|
|
|
} else if(modifiers.length == 0){
|
|
|
|
|
handler['default'] = action
|
|
|
|
|
|
|
|
|
|
} else {
|
|
|
|
|
handler[modifiers] = action
|
|
|
|
|
}
|
|
|
|
|
}],
|
2016-04-02 17:34:25 +03:00
|
|
|
})
|
|
|
|
|
|
|
|
|
|
var Keyboard =
|
|
|
|
|
module.Keyboard = core.ImageGridFeatures.Feature({
|
|
|
|
|
title: '',
|
|
|
|
|
doc: '',
|
|
|
|
|
|
|
|
|
|
tag: 'keyboard',
|
|
|
|
|
depends: [
|
|
|
|
|
'ui'
|
|
|
|
|
],
|
|
|
|
|
|
|
|
|
|
actions: KeyboardActions,
|
|
|
|
|
|
|
|
|
|
handlers: [
|
|
|
|
|
['start',
|
|
|
|
|
function(){
|
|
|
|
|
var that = this
|
|
|
|
|
this.__keyboard_config = this.keyboard || GLOBAL_KEYBOARD
|
|
|
|
|
|
|
|
|
|
this.toggleKeyboardHandling('on')
|
2016-12-03 14:39:04 +03:00
|
|
|
}],
|
|
|
|
|
|
|
|
|
|
// pause keyboard repeat...
|
|
|
|
|
['shiftImageUp.pre shiftImageDown.pre',
|
|
|
|
|
function(){
|
|
|
|
|
var r = this.current_ribbon
|
|
|
|
|
|
|
|
|
|
return function(){
|
|
|
|
|
// pause repeat if shifting last image out of the ribbon...
|
|
|
|
|
if(this.data.ribbons[r] == null
|
|
|
|
|
|| this.data.ribbons[r].len == 0){
|
|
|
|
|
this.pauseKeyboardRepeat()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}],
|
2016-04-02 17:34:25 +03:00
|
|
|
],
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**********************************************************************
|
2016-08-20 22:49:36 +03:00
|
|
|
* vim:set ts=4 sw=4 : */ return module })
|