Repository: DigitalSlideArchive/HistomicsUI (language: python)

Issue:
Keyboard shortcuts
- [x] Set up keyboard short cuts to toggle drawing / stop drawing an annotation.  If no annotation is selected, perhaps this would open the new annotation dialog. #154

- [x] Set up keyboard short cuts to pick the style group. #155

- [x] Also, set up a keyboard shortcut to close a polygon (same action as double clicking when drawing a polygon). #157

- [x] In the new annotation dialog, it would be nice if the keyboard could perform the default action.  Broadly, in any dialog, it would be nice to do something like `$('.btn').prop('tabindex', '0')`. #158


Discussion / hints:

Fix patch:
diff --git a/histomicsui/web_client/templates/panels/annotationSelector.pug b/histomicsui/web_client/templates/panels/annotationSelector.pug
index 7c4e71a0..23e516d2 100644
--- a/histomicsui/web_client/templates/panels/annotationSelector.pug
+++ b/histomicsui/web_client/templates/panels/annotationSelector.pug
@@ -76,6 +76,6 @@ block content
       input#h-toggle-interactive(type='checkbox', checked=interactiveMode)
       | Interactive
     if accessLevel >= writeAccessLevel
-      button.btn.btn-sm.btn-primary.h-create-annotation
+      button.btn.btn-sm.btn-primary.h-create-annotation(title='Create a new annotation. Keyboard shortcut: space bar')
         | #[span.icon-plus-squared] New
     .clearfix
diff --git a/histomicsui/web_client/templates/panels/drawWidget.pug b/histomicsui/web_client/templates/panels/drawWidget.pug
index fda007cd..879d85c6 100644
--- a/histomicsui/web_client/templates/panels/drawWidget.pug
+++ b/histomicsui/web_client/templates/panels/drawWidget.pug
@@ -15,27 +15,27 @@ block content
   .btn-group.btn-justified.btn-group-sm.h-draw-tools
     .btn-group.btn-group-sm
       button.h-draw.btn.btn-default(
-        type='button', data-type='point', data-toggle='tooltip', title='Draw a new point', class=drawingType === 'point' ? 'active' : null)
+        type='button', data-type='point', data-toggle='tooltip', title='Draw a new point (keyboard shortcut: o)', class=drawingType === 'point' ? 'active' : null)
         | #[span.icon-circle]Point
     .btn-group.btn-group-sm
       button.h-draw.btn.btn-default(
-        type='button', data-type='rectangle', data-toggle='tooltip', title='Draw a new rectangle', class=drawingType === 'rectangle' ? 'active' : null)
+        type='button', data-type='rectangle', data-toggle='tooltip', title='Draw a new rectangle (keyboard shortcut: r)', class=drawingType === 'rectangle' ? 'active' : null)
         | #[span.icon-check-empty]Rectangle
     .btn-group.btn-group-sm
       button.h-draw.btn.btn-default(
-        type='button', data-type='ellipse', data-toggle='tooltip', title='Draw a new ellipse', class=drawingType === 'ellipse' ? 'active' : null)
+        type='button', data-type='ellipse', data-toggle='tooltip', title='Draw a new ellipse (keyboard shortcut: i)', class=drawingType === 'ellipse' ? 'active' : null)
         | #[span.icon-circle-empty.flattenicon]Ellipse
     .btn-group.btn-group-sm
       button.h-draw.btn.btn-default(
-        type='button', data-type='circle', data-toggle='tooltip', title='Draw a new circle', class=drawingType === 'circle' ? 'active' : null)
+        type='button', data-type='circle', data-toggle='tooltip', title='Draw a new circle (keyboard shortcut: c)', class=drawingType === 'circle' ? 'active' : null)
         | #[span.icon-circle-empty]Circle
     .btn-group.btn-group-sm
       button.h-draw.btn.btn-default(
-        type='button', data-type='polygon', data-toggle='tooltip', title='Draw a new polygon', class=drawingType === 'polygon' ? 'active' : null)
+        type='button', data-type='polygon', data-toggle='tooltip', title='Draw a new polygon (keyboard shortcut: p)', class=drawingType === 'polygon' ? 'active' : null)
         | #[span.icon-draw-polygon]Polygon
     .btn-group.btn-group-sm
       button.h-draw.btn.btn-default(
-        type='button', data-type='line', data-toggle='tooltip', title='Draw a new line', class=drawingType === 'line' ? 'active' : null)
+        type='button', data-type='line', data-toggle='tooltip', title='Draw a new line (keyboard shortcut: l)', class=drawingType === 'line' ? 'active' : null)
         | #[span.icon-pencil]Line
 
   if elements.length
diff --git a/histomicsui/web_client/views/body/ImageView.js b/histomicsui/web_client/views/body/ImageView.js
index eef849e0..9a3bef4f 100644
--- a/histomicsui/web_client/views/body/ImageView.js
+++ b/histomicsui/web_client/views/body/ImageView.js
@@ -1103,6 +1103,65 @@ var ImageView = View.extend({
             case 's':
                 this.annotationSelector.selectAnnotationByRegion();
                 break;
+            case ' ': // pressing space bar creates a new annotation
+                if (!this.annotationSelector._activeAnnotation) {
+                    this.annotationSelector.createAnnotation();
+                }
+                break;
+            case 'o':
+                if (this.annotationSelector._activeAnnotation) {
+                    if (this.drawWidget._drawingType === 'point') {
+                        this.drawWidget.cancelDrawMode();
+                    } else {
+                        this.drawWidget.drawElement(undefined, 'point');
+                    }
+                }
+                break;
+            case 'r':
+                if (this.annotationSelector._activeAnnotation) {
+                    if (this.drawWidget._drawingType === 'rectangle') {
+                        this.drawWidget.cancelDrawMode();
+                    } else {
+                        this.drawWidget.drawElement(undefined, 'rectangle');
+                    }
+                }
+                break;
+            case 'i':
+                if (this.annotationSelector._activeAnnotation) {
+                    if (this.drawWidget._drawingType === 'ellipse') {
+                        this.drawWidget.cancelDrawMode();
+                    } else {
+                        this.drawWidget.drawElement(undefined, 'ellipse');
+                    }
+                }
+                break;
+            case 'c':
+                if (this.annotationSelector._activeAnnotation) {
+                    if (this.drawWidget._drawingType === 'circle') {
+                        this.drawWidget.cancelDrawMode();
+                    } else {
+                        this.drawWidget.drawElement(undefined, 'circle');
+                    }
+                }
+                break;
+            case 'p':
+                if (this.annotationSelector._activeAnnotation) {
+                    if (this.drawWidget._drawingType === 'polygon') {
+                        this.drawWidget.cancelDrawMode();
+                    } else {
+                        this.drawWidget.drawElement(undefined, 'polygon');
+                    }
+                }
+                break;
+            case 'l':
+                if (this.annotationSelector._activeAnnotation) {
+                    if (this.drawWidget._drawingType === 'line') {
+                        this.drawWidget.cancelDrawMode();
+                    } else {
+                        this.drawWidget.drawElement(undefined, 'line');
+                    }
+                }
+                break;
         }
     },
 


Test patch:
diff --git a/tests/web_client_specs/annotationSpec.js b/tests/web_client_specs/annotationSpec.js
index 6e9af76f..eee067a2 100644
--- a/tests/web_client_specs/annotationSpec.js
+++ b/tests/web_client_specs/annotationSpec.js
@@ -404,6 +404,43 @@ girderTest.promise.done(function () {
                     expect($('button.h-draw[data-type="point"]').hasClass('active')).toBe(true);
                 });
             });
+
+            it('check that keyboard shortcuts behave correctly', function () {
+                runs(function () {
+                    // expect that hitting keyboard shortcut for each shape will
+                    // switch the annotation mode to it, and hitting it again will
+                    // turn off drawing mode.
+                    $('.h-image-body').trigger($.Event('keydown', {key: 'r'}));
+                    expect($('.h-draw.active').attr('data-type')).toBe('rectangle');
+                    $('.h-image-body').trigger($.Event('keydown', {key: 'r'}));
+                    expect($('.h-draw.active').length).toBe(0);
+
+                    $('.h-image-body').trigger($.Event('keydown', {key: 'o'}));
+                    expect($('.h-draw.active').attr('data-type')).toBe('point');
+                    $('.h-image-body').trigger($.Event('keydown', {key: 'o'}));
+                    expect($('.h-draw.active').length).toBe(0);
+
+                    $('.h-image-body').trigger($.Event('keydown', {key: 'c'}));
+                    expect($('.h-draw.active').attr('data-type')).toBe('circle');
+                    $('.h-image-body').trigger($.Event('keydown', {key: 'c'}));
+                    expect($('.h-draw.active').length).toBe(0);
+
+                    $('.h-image-body').trigger($.Event('keydown', {key: 'i'}));
+                    expect($('.h-draw.active').attr('data-type')).toBe('ellipse');
+                    $('.h-image-body').trigger($.Event('keydown', {key: 'i'}));
+                    expect($('.h-draw.active').length).toBe(0);
+
+                    $('.h-image-body').trigger($.Event('keydown', {key: 'p'}));
+                    expect($('.h-draw.active').attr('data-type')).toBe('polygon');
+                    $('.h-image-body').trigger($.Event('keydown', {key: 'p'}));
+                    expect($('.h-draw.active').length).toBe(0);
+
+                    $('.h-image-body').trigger($.Event('keydown', {key: 'l'}));
+                    expect($('.h-draw.active').attr('data-type')).toBe('line');
+                    $('.h-image-body').trigger($.Event('keydown', {key: 'l'}));
+                    expect($('.h-draw.active').length).toBe(0);
+                });
+            });
         });
 
         describe('Annotation styles', function () {