/********************************************************************** * * * **********************************************************************/ //var DEBUG = DEBUG != null ? DEBUG : true var STEPS_TO_CHANGE_DIRECTION = 2 var _STEPS_LEFT_TO_CHANGE_DIRECTION = STEPS_TO_CHANGE_DIRECTION var DIRECTION = 'next' /*********************************************************************/ function updateDirection(direction){ if(DIRECTION != direction){ _STEPS_LEFT_TO_CHANGE_DIRECTION-- if(_STEPS_LEFT_TO_CHANGE_DIRECTION == 0){ DIRECTION = direction _STEPS_LEFT_TO_CHANGE_DIRECTION = 2 } } else { _STEPS_LEFT_TO_CHANGE_DIRECTION = 2 } } function directionImage(reverse){ if(DIRECTION == (reverse ? 'prev' : 'next')){ nextImage() } else { prevImage() } } /*********************************************************************/ var KEYBOARD_CONFIG = { // info overlay... // // NOTE: this is here to prevent selecting images while trying to // select info text... '.overlay-info:hover': { title: 'Info overlay', doc: 'Displayed on bottom of the screen if enabled (toggle with '+ 'I) and/or inline, at bottom of an image when cursor '+ 'is over it (only in ribbon mode, toggle with alt-I)'+ '
NOTE: when the cursor is over the info overlay one can use '+ 'Ctrl-A and Ctrl-D for info text selection, without affecting '+ 'image selection/marks.', ignore: [ 'A' ], A: { // NOTE: this is here only for documentation... ctrl: doc('Select all'), }, D: { ctrl: doc('Clear selection', function(){ document.getSelection().empty() return false }) } }, // dialogs... // '.viewer.overlay .overlay-block.dialog': { title: 'Dialog', doc: 'NOTE: to close a dialog, in addition to the keyaboard '+ 'shortcuts, one can also click anywhere outside the dialog.', ignore: '*', 'insert-return': doc('Insert return'), Enter: { default: doc('Accept dialog', function(){ var f = $(':focus') // trigger the default button action... if(f.length > 0 && (/button/i.test(f[0].tagName) || f.attr('type') == 'button')){ return true // accept the dialog... } else { getOverlay($('.viewer')).trigger('accept') hideOverlay($('.viewer')) } }), shift: 'insert-return', //ctrl: 'insert-return', }, Esc: doc('Close dialog', function(){ //getOverlay($('.viewer')).trigger('close') hideOverlay($('.viewer')) return false }), }, // help mode... // // NOTE: need to keep all info modes before the rest so as to give // their bindings priority... '.drawer-mode': { title: 'Drawer modes', doc: 'NOTE: In this mode all other key bindings are disabled, '+ 'except app defaults and the ones explicitly defined here.', ignore: '*', Esc: doc('Close drawer', function(){ toggleKeyboardHelp('off') return false }), Q: 'Esc', }, // slideshow mode... // '.slideshow-mode': { title: 'Slideshow mode', doc: 'To enter this mode press S.', // XXX think about what else to disable here... ignore: [ 'Up', 'Down', 'Enter', 'R', 'L', ], L: doc('Toggle slideshow looping', function(){ SLIDESHOW_LOOP = SLIDESHOW_LOOP ? false : true showStatus('Slideshow: looping', SLIDESHOW_LOOP ? 'enabled...' : 'disabled...') return false }), R: doc('Reverse slideshow direction', function(){ SLIDESHOW_DIRECTION = SLIDESHOW_DIRECTION == 'next' ? 'prev' : 'next' showStatus('Slideshow: direction:', SLIDESHOW_DIRECTION + '...') return false }), Esc: doc('Exit/stop slideshow', function(){ toggleSlideShowMode('off') return false }), S: 'Esc', Q: 'Esc', }, // single image mode... // '.single-image-mode': { title: 'Single image mode', doc: 'To toggle between this and ribbon modes press Enter.', Esc: doc('Exit single image mode', function(){ toggleSingleImageMode('off') return false }), Q: 'Esc', }, // marked only ribbon mode... // '.single-ribbon-mode:not(.single-image-mode), .marked-only-view:not(.single-image-mode)': { title: 'Marked only and single ribbon views', doc: 'To show marked-only images press shift-F2 and for single ribbon mode press F3.', Esc: { default: doc('Uncrop to last state', function(){ uncropLastState() return false }), shift: doc('Exit crop mode', function(){ toggleMarkedOnlyView('off') toggleSingleRibbonMode('off') return false }), }, Q: 'Esc', }, // visible marks... // '.marks-visible': { title: 'Visible marks', Esc: doc('Hide marks', function(){ toggleMarkesView('off') return false }), }, // ribbon mode only... // // XXX this breaks getKeyHandlers(...) when modes argument is given... '.viewer:not(.overlay):not(.single-image-mode)': { title: 'Ribbon mode', Left: { alt: doc('Shift image left', function(){ event.preventDefault() shiftImageLeft() centerView(null, 'css') // XXX for some odd reason centerRibbons does // something really odd here... //centerRibbons() // XXX HACK... // XXX this still gets misaligned sometimes but this // is likely due to one of the align bugs if(window._center_ribbon_delay != null){ clearTimeout(_center_ribbon_delay) } _center_ribbon_delay = setTimeout( function(){ centerRibbons() }, 300) return false }), }, Right: { alt: doc('Shift image right', function(){ event.preventDefault() shiftImageRight() centerView(null, 'css') // XXX for some odd reason centerRibbons does // something really odd here... //centerRibbons() // XXX HACK... // XXX this still gets misaligned sometimes but this // is likely due to one of the align bugs // (see: TODO.otl) if(window._center_ribbon_delay != null){ clearTimeout(_center_ribbon_delay) } _center_ribbon_delay = setTimeout( function(){ centerRibbons() }, 300) return false }), }, // zooming... '#1': doc('Fit one image', function(){ fitNImages(1) }), '#2': doc('Fit two images', function(){ fitNImages(2) }), '#3': doc('Fit three images', function(){ fitNImages(3) }), '#4': doc('Fit four images', function(){ fitNImages(4) }), '#5': doc('Fit five images', function(){ fitNImages(5) }), '#6': doc('Fit six images', function(){ fitNImages(6) }), '#7': doc('Fit seven images', function(){ fitNImages(7) }), '#8': doc('Fit eight images', function(){ fitNImages(8) }), '#9': doc('Fit nine images', function(){ fitNImages(9) }), // cropping... F2: { shift: doc('Crop marked only images', function(){ toggleMarkedOnlyView('on') // prevent the default from the main mode from // getting called... return false }), }, F3: doc('Crop single ribbon', function(){ event.preventDefault() toggleSingleRibbonMode('on') }), }, // general setup... // '.viewer:not(.overlay)': { title: 'Global', doc: 'These key bindings work in most other modes.'+ '
NOTE: shifting all marked images from different ribbons will '+ 'perform the operations on ALL marked images but relative '+ 'the the current ribbon. i.e. some images might get promoted, '+ 'others demoted while some will not change position. ', // Basics... // XXX STUB: use a real path browser... O: doc('Open a directory path', function(){ loadDirectoryDialog() }), // Navigation... // XXX need to cancel the animation of the prev action... Left: { default: doc('Previous image', function(){ event.preventDefault() // update direction... updateDirection('prev') prevImage() centerRibbons() }), ctrl: 'prev-screen', }, Right: { default: doc('Next image', function(){ event.preventDefault() // update direction... updateDirection('next') nextImage() centerRibbons() }), ctrl: 'next-screen', }, 'prev-screen': doc('Previous screen', function(){ event.preventDefault() prevScreenImages() centerRibbons() }), 'next-screen': doc('Next screen', function(){ event.preventDefault() nextScreenImages() centerRibbons() }), Space: { default: 'Right', shift: 'Left', // screen-oriented movement... ctrl: 'Right', 'ctrl+shift': 'prev-screen', }, Backspace: { default: 'Left', shift: 'Right', // screen-oriented movement... ctrl: 'Left', 'ctrl+shift': 'next-screen', }, Home: doc('First image', function(){ event.preventDefault() firstImage() centerRibbons() }), End: doc('Last image', function(){ event.preventDefault() lastImage() centerRibbons() }), // combined navigation and editor actions... Up: { default: doc('Go to ribbon above', function(){ event.preventDefault() prevRibbon() centerRibbons() }), shift: doc('Shift image up', function(){ event.preventDefault() shiftImageUp(null, DIRECTION) centerRibbons() }), 'ctrl+shift': doc('Shift image up to empty ribbon', function(){ event.preventDefault() shiftImageUpNewRibbon(null, DIRECTION) centerRibbons() }), alt: doc('Shift marked images up', function(){ toggleMarkesView('on') shiftMarkedImagesUp() }), 'alt+shift': doc('Shift marked images up to empty ribbon', function(){ // XXX }), }, Down: { default: doc('Go to ribbon below', function(){ event.preventDefault() nextRibbon() centerRibbons() }), shift: doc('Shift image down', function(){ event.preventDefault() shiftImageDown(null, DIRECTION) centerRibbons() }), 'ctrl+shift': doc('Shift image down to empty ribbon', function(){ event.preventDefault() shiftImageDownNewRibbon(null, DIRECTION) centerRibbons() }), alt: doc('Shift marked images down', function(){ toggleMarkesView('on') shiftMarkedImagesDown() }), 'alt+shift': doc('Shift marked images down to empty ribbon', function(){ // XXX }), }, L: doc('Rotate image left', function(){ rotateLeft() }), R: { default: doc('Rotate image right', function(){ rotateRight() }), ctrl: doc('Reverse image order', function(){ event.preventDefault() reverseImageOrder() }), }, H: doc('Flip image horizontally', function(){ var o = getImage().attr('orientation') // need to rotate relative to user, not relative to image... if(o == 90 || o == 270){ flipVertical() } else { flipHorizontal() } }), V: doc('Flip image vertically', function(){ var o = getImage().attr('orientation') // need to rotate relative to user, not relative to image... if(o == 90 || o == 270){ flipHorizontal() } else { flipVertical() } }), // zooming... '#1': doc('Fit image to screen', function(){ fitNImages(1) }), // XXX this will do different stuff for different proportioned screens... '#2': doc('Show big image', function(){ fitNImages(1.5, true) }), '#3': doc('Show small image', function(){ fitNImages(3, true) }), '-': doc('Zoom in', function(){ zoomOut() }), '=': doc('Zoom out', function(){ zoomIn() }), Enter: doc('Toggle single image view', function(){ toggleSingleImageMode() }), B: doc('Toggle theme', function(){ toggleTheme() }), S: { default: doc('Start slideshow', function(){ toggleSlideShowMode('on') }), shift: doc('Sort images', function(){ sortImagesDialog() }), ctrl: doc('Save current state', function(){ event.preventDefault() //saveLocalStorage() showStatusQ('Saving: localStorage: Data.') saveLocalStorageData() showStatusQ('Saving: localStorage: Marks.') saveLocalStorageMarks() showStatusQ('Saving: localStorage: Settings.') saveLocalStorageSettings() if(IMAGES_CREATED){ showStatusQ('Saving: File: Images.') dumpJSON(normalizePath(CACHE_DIR +'/'+ IMAGES_FILE_DEFAULT), IMAGES) //saveFileImages() IMAGES_CREATED = false } showStatusQ('Saving: File: State.') saveFileState() showStatusQ('Saving: Done.') }), 'ctrl+shift': doc('Export', function(){ exportPreviewsDialog() }), }, Z: { ctrl: doc('Restore to last saved state', function(){ loadLocalStorage() loadLocalStorageMarks() }) }, // marking... // XXX not final, think of a better way to do this... // XXX need mark navigation... // XXX need marked image shift up/down actions... // XXX unmarking an image in marked-only mode results in nothing // visible focused if we unmark the first or last image in // the ribbon... M: { // NOTE: marking moves in the same direction as the last // move... // i.e. marking can change direction depending on where // we moved last... // NOTE: marking does not change move direction... // XXX should this toggle or set mark to on? default: doc('Mark current image and advance', function(){ toggleImageMark('on') directionImage() // XXX do we need this??? //if(getImage().filter(':visible').length == 0){ // centerView(focusImage(getImageBefore())) //} centerRibbons() }), // same as default but in reverse direction... shift: doc('Mark current image and return', function(){ toggleImageMark('on') directionImage(true) // XXX do we need this??? //if(getImage().filter(':visible').length == 0){ // centerView(focusImage(getImageBefore())) //} centerRibbons() }), }, Ins: doc('Toggle mark on current image', function(){ toggleImageMark() }), 'invert-marks': doc('Invert image marks', function(){ invertImageMarks() }), A: { // XXX does not yet work with DATA (???) //shift: doc('Toggle marks in current contagious block', // function(){ toggleImageMarkBlock() }), ctrl: doc('Mark current ribbon', function(){ toggleMarkesView('on') markAll('ribbon') }), 'ctrl+shift': doc('Mark all images', function(){ toggleMarkesView('on') markAll('all') }), }, D: { ctrl: doc('Unmark current ribbon', function(){ event.preventDefault() removeImageMarks('ribbon') }), 'ctrl+shift': doc('Unmark all images', function(){ removeImageMarks('all') }), }, U: { default: doc('Unmark current image', function(){ toggleImageMark('off') }), ctrl: doc('Unmark current ribbon', function(){ removeImageMarks('ribbon') }), shift: doc('Unamrk all', function(){ removeImageMarks('all') }), }, F2: doc('Toggle mark visibility', function(){ toggleMarkesView() }), // XXX should we be able to toggle crop modes from single image mode??? // ...if yes, then remove the F2 & F3 definitions form ribbon // mode... // one way to go is to exit single-image-mode on s-f2 or f3... /* F2: { default: doc('Toggle mark visibility', function(){ toggleMarkesView() }), shift: doc('Crop marked only images', function(){ toggleMarkedOnlyView('on') }), }, F3: doc('Crop single ribbon', function(){ event.preventDefault() toggleSingleRibbonMode('on') }), */ E: doc('Open image in external software', openImage), // XXX make F4 a default editor and E a default viewer... F4: { default: 'E', alt: doc('Close viewer', function(){ closeWindow() }), }, // info... I: { default: doc('Show current image info', function(){ showImageInfo() //toggleImageInfoDrawer() }), shift: doc('Toggle image info display', function(){ toggleImageInfo() }), alt: doc('Toggle inline image info display', function(){ toggleInlineImageInfo() }), // marking... ctrl: 'invert-marks', }, P: { default: doc('Show options', function(){ toggleOptionsUI() }), ctrl: doc('Print keyboard help', function(){ toggleKeyboardHelp('on') // NOTE: on chrome this is blocking... print() toggleKeyboardHelp('off') }), }, // NOTE: this is handled by the wrapper at this point, so we do // not have to do anything here... F11: doc('Toggle full screen mode', function(){ toggleFullscreenMode() }), // Help and info... '?': doc('Show keyboard bindings', function(){ toggleKeyboardHelp() }), F1: doc('Show help', function(){ toggleHelp() }), // XXX DEBUG MODE... // ...remove these in production... F12: doc('Show devTools', function(){ showDevTools() }), F5: doc('Reload app', function(){ reload() }), /* testing the shift-key feature... '~': { default: function(){ alert('~') }, // this is inaccessible... shift: function(){ alert('shift-~') }, ctrl: function(){ alert('ctrl-~') }, 'ctrl+alt': function(){ alert('ctrl-alt-~') }, }, '`': { default: function(){ alert('`') }, // this is also not accessible as it is shadowed by '''... shift: function(){ alert('shift-`') }, ctrl: function(){ alert('ctrl-`') }, 'ctrl+alt': function(){ alert('ctrl-alt-`') }, }, */ } } /********************************************************************** * vim:set ts=4 sw=4 : */