Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 6 additions & 3 deletions src/components/ControlsModal.vue
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,10 @@

<script setup lang="ts">
import { computed } from 'vue';
import { actionToKey } from '@/src/composables/useKeyboardShortcuts';
import {
actionToKey,
readableBinding,
} from '@/src/composables/useKeyboardShortcuts';
import { ACTIONS } from '@/src/constants';
import { useKeyboardShortcutsStore } from '@/src/store/keyboard-shortcuts';
import CloseableDialog from './CloseableDialog.vue';
Expand All @@ -43,9 +46,9 @@ import { getEntries } from '../utils';
const keyboardStore = useKeyboardShortcutsStore();

const bindings = computed(() =>
getEntries(actionToKey.value).map(([action, key]) => [
getEntries(actionToKey.value).map(([action, binding]) => [
ACTIONS[action].readable,
key,
readableBinding(binding),
])
);
</script>
Expand Down
33 changes: 17 additions & 16 deletions src/components/ControlsStripTools.vue
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,7 @@

<script lang="ts">
import { computed, defineComponent, ref, watch } from 'vue';
import { onKeyDown, useMagicKeys } from '@vueuse/core';
import { onKeyDown } from '@vueuse/core';
import { Tools } from '@/src/store/tools/types';
import ControlButton from '@/src/components/ControlButton.vue';
import ItemGroup from '@/src/components/ItemGroup.vue';
Expand All @@ -150,7 +150,11 @@ import RulerControls from '@/src/components/RulerControls.vue';
import RectangleControls from '@/src/components/RectangleControls.vue';
import PolygonControls from '@/src/components/PolygonControls.vue';
import WindowLevelControls from '@/src/components/tools/windowing/WindowLevelControls.vue';
import { actionToKey } from '@/src/composables/useKeyboardShortcuts';
import {
actionToKey,
readableBinding,
useActionHeld,
} from '@/src/composables/useKeyboardShortcuts';
import { useCurrentImage } from '@/src/composables/useCurrentImage';
import { useViewStore } from '@/src/store/views';

Expand Down Expand Up @@ -199,10 +203,7 @@ export default defineComponent({
windowingMenu.value = false;
});

const keys = useMagicKeys();
const enableTempCrosshairs = computed(
() => keys[actionToKey.value.temporaryCrosshairs].value
);
const enableTempCrosshairs = useActionHeld('temporaryCrosshairs');
watch(enableTempCrosshairs, (enable) => {
if (enable) toolStore.activateTemporaryCrosshairs();
else toolStore.deactivateTemporaryCrosshairs();
Expand All @@ -212,16 +213,16 @@ export default defineComponent({
const nameToShortcut = computed(() => {
const keyMap = actionToKey.value;
return {
'Window & Level': keyMap.windowLevel,
Pan: keyMap.pan,
Zoom: keyMap.zoom,
Crosshairs: keyMap.crosshairs,
Select: keyMap.select,
Paint: keyMap.paint,
Rectangle: keyMap.rectangle,
Polygon: keyMap.polygon,
Ruler: keyMap.ruler,
Crop: keyMap.crop,
'Window & Level': readableBinding(keyMap.windowLevel),
Pan: readableBinding(keyMap.pan),
Zoom: readableBinding(keyMap.zoom),
Crosshairs: readableBinding(keyMap.crosshairs),
Select: readableBinding(keyMap.select),
Paint: readableBinding(keyMap.paint),
Rectangle: readableBinding(keyMap.rectangle),
Polygon: readableBinding(keyMap.polygon),
Ruler: readableBinding(keyMap.ruler),
Crop: readableBinding(keyMap.crop),
};
});

Expand Down
11 changes: 2 additions & 9 deletions src/components/MeasurementsToolList.vue
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { useCurrentImage } from '@/src/composables/useCurrentImage';
import { frameOfReferenceToImageSliceAndAxis } from '@/src/utils/frameOfReference';
import { nonNullable } from '@/src/utils/index';
import { AnnotationToolType } from '@/src/store/tools/types';
import { useAnnotationToolStore } from '@/src/store/tools';
import { removeSelectedTools, useAnnotationToolStore } from '@/src/store/tools';
import {
useMultipleToolSelection,
MultipleSelectionState,
Expand Down Expand Up @@ -89,13 +89,6 @@ const toggleSelectAll = (shouldSelectAll: Maybe<boolean>) => {
}
};

function removeAll() {
selectionStore.selection.forEach((sel) => {
const store = useAnnotationToolStore(sel.type);
store.removeTool(sel.id);
});
}

// If all selected tools are already hidden, it should be "show".
// If at least one selected tool is visible, it should be "hide".
const allHidden = computed(() => {
Expand Down Expand Up @@ -156,7 +149,7 @@ function toggleGlobalHidden() {
icon
variant="text"
:disabled="selectionState === MultipleSelectionState.None"
@click.stop="removeAll"
@click.stop="removeSelectedTools"
>
<v-icon>mdi-delete</v-icon>
<v-tooltip
Expand Down
8 changes: 2 additions & 6 deletions src/components/tools/paint/PaintWidget2D.vue
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ import {
watchEffect,
inject,
} from 'vue';
import { useMagicKeys } from '@vueuse/core';
import vtkPlaneManipulator from '@kitware/vtk.js/Widgets/Manipulators/PlaneManipulator';
import { vec3 } from 'gl-matrix';
import { getLPSAxisFromDir } from '@/src/utils/lps';
Expand All @@ -24,7 +23,7 @@ import { onVTKEvent } from '@/src/composables/onVTKEvent';
import { useSliceInfo } from '@/src/composables/useSliceInfo';
import { VtkViewContext } from '@/src/components/vtk/context';
import { Maybe } from '@/src/types';
import { actionToKey } from '@/src/composables/useKeyboardShortcuts';
import { useActionHeld } from '@/src/composables/useKeyboardShortcuts';

export default defineComponent({
name: 'PaintWidget2D',
Expand Down Expand Up @@ -172,10 +171,7 @@ export default defineComponent({
});

// Brush size scroll wheel control with customizable modifier key
const keys = useMagicKeys();
const enableBrushSizeAdjustment = computed(
() => keys[actionToKey.value.brushSizeModifier].value
);
const enableBrushSizeAdjustment = useActionHeld('brushSizeModifier');

const handleWheelEvent = (event: WheelEvent) => {
if (!enableBrushSizeAdjustment.value) return;
Expand Down
9 changes: 3 additions & 6 deletions src/components/tools/polygon/PolygonTool.vue
Original file line number Diff line number Diff line change
Expand Up @@ -101,11 +101,11 @@ import {
} from '@/src/composables/annotationTool';
import AnnotationContextMenu from '@/src/components/tools/AnnotationContextMenu.vue';
import AnnotationInfo from '@/src/components/tools/AnnotationInfo.vue';
import { actionToKey } from '@/src/composables/useKeyboardShortcuts';
import { useActionHeld } from '@/src/composables/useKeyboardShortcuts';
import { Maybe } from '@/src/types';
import { useViewLocator } from '@/src/composables/useViewLocator';
import { locatorPatch } from '@/src/core/annotations/locator';
import { useMagicKeys, watchImmediate } from '@vueuse/core';
import { watchImmediate } from '@vueuse/core';
import { fillPoly } from '@thi.ng/rasterize';
import type { IGrid2D } from '@thi.ng/api';
import vtkImageData from '@kitware/vtk.js/Common/DataModel/ImageData';
Expand Down Expand Up @@ -211,10 +211,7 @@ export default defineComponent({
placingTool.remove();
});

const keys = useMagicKeys();
const mergeKey = computed(
() => keys[actionToKey.value.mergeNewPolygon].value
);
const mergeKey = useActionHeld('mergeNewPolygon');

const onToolPlaced = () => {
if (imageId.value) {
Expand Down
7 changes: 3 additions & 4 deletions src/components/vtk/VtkCineScrubKeyManipulator.vue
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,10 @@ import { Maybe } from '@/src/types';
import vtkGatedMouseRangeManipulator from '@/src/vtk/GatedMouseRangeManipulator';
import { IMouseRangeManipulatorInitialValues } from '@kitware/vtk.js/Interaction/Manipulators/MouseRangeManipulator';
import vtkInteractorStyleManipulator from '@kitware/vtk.js/Interaction/Style/InteractorStyleManipulator';
import { syncRef, useMagicKeys } from '@vueuse/core';
import { syncRef } from '@vueuse/core';
import { inject, toRefs, unref, watch, computed } from 'vue';
import { useViewStore } from '@/src/store/views';
import { actionToKey } from '@/src/composables/useKeyboardShortcuts';
import { useActionHeld } from '@/src/composables/useKeyboardShortcuts';

type Props = {
viewId: string;
Expand Down Expand Up @@ -45,8 +45,7 @@ const { instance: rangeManipulator } = useVtkInteractionManipulator(

rangeManipulator.value.setupMouseMove(view.interactor);

const keys = useMagicKeys();
const enableGrabSlice = computed(() => keys[actionToKey.value.grabSlice].value);
const enableGrabSlice = useActionHeld('grabSlice');
watch(
enableGrabSlice,
(value) => {
Expand Down
7 changes: 3 additions & 4 deletions src/components/vtk/VtkSliceViewSlicingKeyManipulator.vue
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,10 @@ import { LPSAxisDir } from '@/src/types/lps';
import vtkGatedMouseRangeManipulator from '@/src/vtk/GatedMouseRangeManipulator';
import { IMouseRangeManipulatorInitialValues } from '@kitware/vtk.js/Interaction/Manipulators/MouseRangeManipulator';
import vtkInteractorStyleManipulator from '@kitware/vtk.js/Interaction/Style/InteractorStyleManipulator';
import { syncRef, useMagicKeys } from '@vueuse/core';
import { syncRef } from '@vueuse/core';
import { inject, toRefs, unref, watch, computed } from 'vue';
import { useViewStore } from '@/src/store/views';
import { actionToKey } from '@/src/composables/useKeyboardShortcuts';
import { useActionHeld } from '@/src/composables/useKeyboardShortcuts';

type Props = {
viewId: string;
Expand Down Expand Up @@ -49,8 +49,7 @@ const { instance: rangeManipulator } = useVtkInteractionManipulator(

rangeManipulator.value.setupMouseMove(view.interactor);

const keys = useMagicKeys();
const enableGrabSlice = computed(() => keys[actionToKey.value.grabSlice].value);
const enableGrabSlice = useActionHeld('grabSlice');
watch(
enableGrabSlice,
(value) => {
Expand Down
106 changes: 106 additions & 0 deletions src/composables/__tests__/deleteSelectedAnnotations.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
import { afterEach, beforeEach, describe, expect, it } from 'vitest';
import { setActivePinia, createPinia } from 'pinia';
import { effectScope, nextTick } from 'vue';
import vtkImageData from '@kitware/vtk.js/Common/DataModel/ImageData';

import { useImageCacheStore } from '@/src/store/image-cache';
import { AnnotationToolType } from '@/src/store/tools/types';
import { useToolSelectionStore } from '@/src/store/tools/toolSelection';
import { useRulerStore } from '@/src/store/tools/rulers';
import { useKeyboardShortcuts } from '@/src/composables/useKeyboardShortcuts';

const IMAGE_ID = 'img-1';

/** Presses delete under a live listener, then tears it down. */
const pressDelete = async () => {
const scope = effectScope();
scope.run(() => useKeyboardShortcuts());

// a real keystroke starts at the focused element and bubbles to window
document.activeElement?.dispatchEvent(
new KeyboardEvent('keydown', {
key: 'Delete',
bubbles: true,
cancelable: true,
})
);
await nextTick();

scope.stop();
};

const addSelectedRuler = () => {
const id = useRulerStore().addTool({
imageID: IMAGE_ID,
placing: false,
firstPoint: [1, 1, 1],
secondPoint: [2, 2, 2],
});
useToolSelectionStore().addSelection(id, AnnotationToolType.Ruler);
return id;
};

describe('delete key removes selected annotations', () => {
beforeEach(async () => {
setActivePinia(createPinia());
useImageCacheStore().addVTKImageData(vtkImageData.newInstance(), 'CT', {
id: IMAGE_ID,
});
await nextTick();
});

afterEach(() => {
document.body.innerHTML = '';
});

it('removes the selected annotation when delete is pressed', async () => {
const ruler = addSelectedRuler();

await pressDelete();

expect(useRulerStore().toolByID).not.toHaveProperty(ruler);
expect(useToolSelectionStore().selection).toEqual([]);
});

it('keeps unselected annotations when delete is pressed', async () => {
const rulerStore = useRulerStore();
const kept = rulerStore.addTool({
imageID: IMAGE_ID,
placing: false,
firstPoint: [3, 3, 3],
secondPoint: [4, 4, 4],
});
const selected = addSelectedRuler();

await pressDelete();

expect(rulerStore.toolByID).not.toHaveProperty(selected);
expect(rulerStore.toolByID).toHaveProperty(kept);
});

it('ignores the delete key while typing in a text field', async () => {
const ruler = addSelectedRuler();

const input = document.createElement('input');
document.body.appendChild(input);
input.focus();

await pressDelete();

expect(useRulerStore().toolByID).toHaveProperty(ruler);
});

// Checking a row in the annotations panel leaves focus on its checkbox
it('removes the annotation while a checkbox holds focus', async () => {
const ruler = addSelectedRuler();

const checkbox = document.createElement('input');
checkbox.type = 'checkbox';
document.body.appendChild(checkbox);
checkbox.focus();

await pressDelete();

expect(useRulerStore().toolByID).not.toHaveProperty(ruler);
});
});
Loading
Loading