diff --git a/cvat/apps/engine/static/engine/js/attributeAnnotationMode.js b/cvat/apps/engine/static/engine/js/attributeAnnotationMode.js index e0e8d6cc9f58..0f79f1bfd206 100644 --- a/cvat/apps/engine/static/engine/js/attributeAnnotationMode.js +++ b/cvat/apps/engine/static/engine/js/attributeAnnotationMode.js @@ -116,33 +116,18 @@ class AAMModel extends Listener { if (this._activeAAM && this._active) { let label = this._active.label; let attrId = +this._attrIdByIdx(label, this._attrNumberByLabel[label].current); - let attrInfo = window.cvat.labelsInfo.attrInfo(attrId); let [xtl, ytl, xbr, ybr] = this._bbRect(this._currentShapes[this._activeIdx].interpolation.position); this._focus(xtl - this._margin, xbr + this._margin, ytl - this._margin, ybr + this._margin); - this._active.activeAAM = { - shape: true, - attribute: attrId, - }; - - this.notify(); - - if (attrInfo.type === 'text' || attrInfo.type === 'number') { - this._active.aamAttributeFocus(); - } - } - else { - this.notify(); + this._active.activeAttribute = attrId; } + this.notify(); } _deactivate() { if (this._activeAAM && this._active) { - this._active.activeAAM = { - shape: false, - attribute: null - }; + this._active.activeAttribute = null; } } @@ -232,33 +217,6 @@ class AAMModel extends Listener { this._activate(); } - setupAttributeValue(key) { - if (!this._activeAAM || !this._active) { - return; - } - - let label = this._active.label; - let frame = window.cvat.player.frames.current; - let attrId = this._attrIdByIdx(label, this._attrNumberByLabel[label].current); - let attrInfo = window.cvat.labelsInfo.attrInfo(attrId); - - if (key >= attrInfo.values.length) { - if (attrInfo.type === 'checkbox' && key < 2) { - this._active.updateAttribute(frame, attrId, !attrInfo.values[0]); - } - return; - } - - if (attrInfo.values[0] === AAMUndefinedKeyword) { - if (key >= attrInfo.values.length - 1) { - return; - } - key ++; - } - - this._active.updateAttribute(frame, attrId, attrInfo.values[key]); - } - onCollectionUpdate() { if (this._activeAAM) { // No need deactivate active view because all listeners already unsubscribed @@ -282,6 +240,10 @@ class AAMModel extends Listener { return this._activeAAM; } + get active() { + return this._active; + } + set margin(value) { this._margin = value; } @@ -320,28 +282,12 @@ class AAMController { e.preventDefault(); }.bind(this)); - let selectAttributeHandler = Logger.shortkeyLogDecorator(function(e) { - let key = e.keyCode; - if (key >= 48 && key <= 57) { - key -= 48; // 0 and 9 - } - else if (key >= 96 && key <= 105) { - key -= 96; // num 0 and 9 - } - else { - return; - } - - this._model.setupAttributeValue(key); - }.bind(this)); - let shortkeys = window.cvat.config.shortkeys; Mousetrap.bind(shortkeys["switch_aam_mode"].value, switchAAMHandler, 'keydown'); Mousetrap.bind(shortkeys["aam_next_attribute"].value, nextAttributeHandler, 'keydown'); Mousetrap.bind(shortkeys["aam_prev_attribute"].value, prevAttributeHandler, 'keydown'); Mousetrap.bind(shortkeys["aam_next_shape"].value, nextShapeHandler, 'keydown'); Mousetrap.bind(shortkeys["aam_prev_shape"].value, prevShapeHandler, 'keydown'); - Mousetrap.bind(shortkeys["select_i_attribute"].value, selectAttributeHandler, 'keydown'); } } @@ -359,6 +305,7 @@ class AAMView { this._aamCounter = $('#aamCounter'); this._aamHelpContainer = $('#aamHelpContainer'); this._zoomMargin = $('#aamZoomMargin'); + this._frameContent = SVG.adopt($('#frameContent')[0]); this._controller = aamController; this._zoomMargin.on('change', (e) => { @@ -368,7 +315,57 @@ class AAMView { aamModel.subscribe(this); } + + _setupAAMView(active, type, pos) { + let oldRect = $('#outsideRect'); + let oldMask = $('#outsideMask'); + + if (active) { + if (oldRect.length) { + oldRect.remove(); + oldMask.remove(); + } + + let size = { + x: 0, + y: 0, + width: window.cvat.player.geometry.frameWidth, + height: window.cvat.player.geometry.frameHeight + }; + + let excludeField = this._frameContent.rect(size.width, size.height).move(size.x, size.y).fill('#666'); + let includeField = null; + + if (type === 'box') { + includeField = this._frameContent.rect(pos.xbr - pos.xtl, pos.ybr - pos.ytl).move(pos.xtl, pos.ytl); + } + else { + includeField = this._frameContent.polygon(pos.points); + } + + this._frameContent.mask().add(excludeField).add(includeField).fill('black').attr('id', 'outsideMask'); + this._frameContent.rect(size.width, size.height).move(size.x, size.y).attr({ + mask: 'url(#outsideMask)', + id: 'outsideRect' + }); + + let content = $(this._frameContent.node); + let texts = content.find('.shapeText'); + for (let text of texts) { + content.append(text); + } + } + else { + oldRect.remove(); + oldMask.remove(); + } + } + onAAMUpdate(aam) { + this._setupAAMView(aam.active ? true : false, + aam.active ? aam.active.type.split('_')[1] : '', + aam.active ? aam.active.interpolate(window.cvat.player.frames.current).position : 0); + if (aam.activeAAM) { if (this._aamMenu.hasClass('hidden')) { this._trackManagement.addClass('hidden'); @@ -390,7 +387,5 @@ class AAMView { this._trackManagement.removeClass('hidden'); } } - // blur on change text attribute to other or on exit from aam - blurAllElements(); } } diff --git a/cvat/apps/engine/static/engine/js/player.js b/cvat/apps/engine/static/engine/js/player.js index bc298d518369..5bf843129448 100644 --- a/cvat/apps/engine/static/engine/js/player.js +++ b/cvat/apps/engine/static/engine/js/player.js @@ -475,6 +475,8 @@ class PlayerController { } zoom(e) { + if (e.ctrlKey) return; + let x = e.originalEvent.pageX - this._leftOffset; let y = e.originalEvent.pageY - this._topOffset; diff --git a/cvat/apps/engine/static/engine/js/shapeCollection.js b/cvat/apps/engine/static/engine/js/shapeCollection.js index 7f6416f866d9..52643c581bae 100644 --- a/cvat/apps/engine/static/engine/js/shapeCollection.js +++ b/cvat/apps/engine/static/engine/js/shapeCollection.js @@ -21,7 +21,6 @@ class ShapeCollectionModel extends Listener { this._groupIdx = 0; this._frame = null; this._activeShape = null; - this._activeAAMShape = null; this._lastPos = { x: 0, y: 0, @@ -97,14 +96,10 @@ class ShapeCollectionModel extends Listener { this._z_order.min = 0; if (this._activeShape) { - this._activeShape.active = false; - } - - if (this._activeAAMShape) { - this._activeAAMShape.activeAAM = { - shape: false, - attribute: null - }; + if (this._activeShape.activeAttribute != null) { + this._activeShape.activeAttribute = null; + } + this.resetActive(); } this._currentShapes = []; @@ -482,16 +477,14 @@ class ShapeCollectionModel extends Listener { // If frame was not changed and collection already interpolated (for example after pause() call) if (frame === this._frame && this._currentShapes.length) return; + if (this._activeShape) { - this._activeShape.active = false; - this._activeShape = null; - } - if (this._activeAAMShape) { - this._activeAAMShape.activeAAM = { - shape: false, - attribute: null, - }; + if (this._activeShape.activeAttribute != null) { + this._activeShape.activeAttribute = null; + } + this.resetActive(); } + this._frame = frame; this._interpolate(); } @@ -503,12 +496,24 @@ class ShapeCollectionModel extends Listener { onShapeUpdate(model) { switch (model.updateReason) { - case 'activeAAM': - if (model.activeAAM.shape) { - this._activeAAMShape = model; + case 'activeAttribute': + if (model.activeAttribute != null) { + if (this._activeShape && this._activeShape != model) { + if (this._activeShape.activeAttribute != null) { + this._activeShape.activeAttribute = null; + } + this.resetActive(); + } + this._activeShape = model; } - else if (this._activeAAMShape === model) { - this._activeAAMShape = null; + else if (this._activeShape) { + if (this._activeShape != model) { + throw Error('Unexpected behaviour. Variable _activeShape is obsolete'); + } + + if (this._activeShape.activeAttribute != null) { + this._activeShape.activeAttribute = null; + } } break; case 'activation': { @@ -927,7 +932,74 @@ class ShapeCollectionController { } }.bind(this)); + + let selectObjectHandler = function() { + let active = this._model.activeShape; + if (active) { + let label = active.label; + let attributes = window.cvat.labelsInfo.labelAttributes(label); + let firstKey = Object.keys(attributes)[0]; + if (typeof(firstKey) != 'undefined' && this._model.activeShape) { + if (!window.cvat.mode) { + this._model.activeShape.activeAttribute = +firstKey; + } + } + } + }.bind(this); + + let unselectObjectHandler = function() { + if (this._model.activeShape) { + if (!window.cvat.mode) { + this._model.activeShape.activeAttribute = null; + } + } + }.bind(this); + + let selectAttributeHandler = Logger.shortkeyLogDecorator(function(e) { + let active = this._model.activeShape; + if (active && active.activeAttribute) { + let key = e.keyCode; + if (key >= 48 && key <= 57) { + key -= 48; // 0 and 9 + } + else if (key >= 96 && key <= 105) { + key -= 96; // num 0 and 9 + } + else { + return; + } + + let attrId = active.activeAttribute; + let frame = window.cvat.player.frames.current; + let attrInfo = window.cvat.labelsInfo.attrInfo(attrId); + + if (e.ctrlKey) { + key --; + if (key < 0) { + key = 10; + } + e.preventDefault(); + } + else { + if (attrInfo.values[0] === AAMUndefinedKeyword) { + if (key >= attrInfo.values.length - 1) { + return; + } + key ++; + } + } + + if (key >= attrInfo.values.length) { + if (attrInfo.type === 'checkbox' && key < 2) { + active.updateAttribute(frame, attrId, !attrInfo.values[0]); + } + return; + } + active.updateAttribute(frame, attrId, attrInfo.values[key]); + } + }.bind(this)); let shortkeys = window.cvat.config.shortkeys; + Mousetrap.bind(shortkeys["switch_lock_property"].value, switchLockHandler.bind(this), 'keydown'); Mousetrap.bind(shortkeys["switch_all_lock_property"].value, switchAllLockHandler.bind(this), 'keydown'); Mousetrap.bind(shortkeys["switch_occluded_property"].value, switchOccludedHandler.bind(this), 'keydown'); @@ -939,11 +1011,32 @@ class ShapeCollectionController { Mousetrap.bind(shortkeys["change_shape_label"].value, switchLabelHandler.bind(this), 'keydown'); Mousetrap.bind(shortkeys["delete_shape"].value, removeActiveHandler.bind(this), 'keydown'); Mousetrap.bind(shortkeys["change_shape_color"].value, changeShapeColorHandler.bind(this), 'keydown'); + Mousetrap.bind(shortkeys["select_object"].value, selectObjectHandler.bind(this), 'keydown'); + Mousetrap.bind(shortkeys["select_object"].value, unselectObjectHandler.bind(this), 'keyup'); + Mousetrap.bind(shortkeys["select_i_attribute"].value.concat( + shortkeys["select_i_attribute"].value.map((x) => `ctrl+${x}`)), selectAttributeHandler, 'keydown'); if (window.cvat.job.z_order) { Mousetrap.bind(shortkeys["inc_z"].value, incZHandler.bind(this), 'keydown'); Mousetrap.bind(shortkeys["dec_z"].value, decZHandler.bind(this), 'keydown'); } + + $('#frameContent').on('mousewheel', (e) => { + let active = this._model.activeShape; + if (active && active.activeAttribute) { + let label = active.label; + let attributes = Object.keys(window.cvat.labelsInfo.labelAttributes(label)); + let idxInArr = attributes.indexOf(active.activeAttribute); + + if (e.originalEvent.deltaY < 0) idxInArr--; + else idxInArr++; + + if (idxInArr >= attributes.length) idxInArr = 0; + else if (idxInArr < 0) idxInArr = attributes.length - 1; + active.activeAttribute = attributes[idxInArr]; + } + e.preventDefault(); + }); } } @@ -1555,4 +1648,5 @@ class ShapeCollectionView { } } } + } diff --git a/cvat/apps/engine/static/engine/js/shapes.js b/cvat/apps/engine/static/engine/js/shapes.js index 667cf97bbbd5..06f971c4c67c 100644 --- a/cvat/apps/engine/static/engine/js/shapes.js +++ b/cvat/apps/engine/static/engine/js/shapes.js @@ -31,8 +31,7 @@ class ShapeModel extends Listener { this._merging = false; this._active = false; this._selected = false; - this._activeAAM = false; - this._activeAAMAttributeId = null; + this._activeAttributeId = null; this._merge = false; this._hiddenShape = false; this._hiddenText = true; @@ -442,10 +441,6 @@ class ShapeModel extends Listener { return frame in this._positions; } - aamAttributeFocus() { - this.notify('attributeFocus'); - } - select() { if (!this._selected) { this._selected = true; @@ -515,6 +510,10 @@ class ShapeModel extends Listener { set active(value) { this._active = value; + if (!value) { + this._activeAttributeId = null; + } + if (!this._removed) { this.notify('activation'); } @@ -524,17 +523,14 @@ class ShapeModel extends Listener { return this._active; } - set activeAAM(active) { - this._activeAAM = active.shape; - this._activeAAMAttributeId = active.attribute; - this.notify('activeAAM'); + set activeAttribute(value) { + this._activeAttributeId = value; + this._updateReason = 'activeAttribute'; + this.notify(); } - get activeAAM() { - return { - shape: this._activeAAM, - attributeId: this._activeAAMAttributeId - }; + get activeAttribute() { + return this._activeAttributeId; } set merge(value) { @@ -1422,7 +1418,6 @@ class ShapeView extends Listener { this._uis.shape.draggable().on('dragstart', () => { events.drag = Logger.addContinuedEvent(Logger.EventType.dragObject); this._flags.dragging = true; - blurAllElements(); this._hideShapeText(); this.notify('drag'); }).on('dragend', (e) => { @@ -2436,13 +2431,13 @@ class ShapeView extends Listener { onShapeUpdate(model) { let interpolation = model.interpolate(window.cvat.player.frames.current); - let hiddenText = model.hiddenText; - let hiddenShape = model.hiddenShape; - let activeAAM = model.activeAAM; + let activeAttribute = model.activeAttribute; + let hiddenText = model.hiddenText && activeAttribute === null; + let hiddenShape = model.hiddenShape && activeAttribute === null; this._makeNotEditable(); this._deselect(); - if (hiddenText && !activeAAM.shape) { + if (hiddenText) { this._hideShapeText(); } @@ -2454,7 +2449,8 @@ class ShapeView extends Listener { break; case 'attributes': this._updateMenuContent(interpolation); - setupHidden.call(this, hiddenShape, hiddenText, activeAAM, model.active, interpolation); + setupHidden.call(this, hiddenShape, hiddenText, + activeAttribute != null, activeAttribute, model.active, interpolation); break; case 'merge': this._setupMergeView(model.merge); @@ -2478,7 +2474,8 @@ class ShapeView extends Listener { this._updateButtonsBlock(interpolation.position); break; case 'hidden': - setupHidden.call(this, hiddenShape, hiddenText, activeAAM, model.active, interpolation); + setupHidden.call(this, hiddenShape, hiddenText, + activeAttribute != null, activeAttribute, model.active, interpolation); this._updateButtonsBlock(interpolation.position); this.notify('hidden'); break; @@ -2510,24 +2507,6 @@ class ShapeView extends Listener { this.notify('changelabel'); break; } - case 'attributeFocus': { - let attrId = model.activeAAM.attributeId; - this._uis.attributes[attrId].focus(); - this._uis.attributes[attrId].select(); - break; - } - case 'activeAAM': - this._setupAAMView(activeAAM.shape, interpolation.position); - setupHidden.call(this, hiddenShape, hiddenText, activeAAM, model.active, interpolation); - - if (activeAAM.shape && this._uis.shape) { - this._uis.shape.node.dispatchEvent(new Event('click')); - this._highlightAttribute(activeAAM.attributeId); - } - else { - this._highlightAttribute(null); - } - break; case 'color': { this._appearance.colors = model.color; this._applyColorSettings(); @@ -2553,9 +2532,9 @@ class ShapeView extends Listener { } } - if (model.active || activeAAM.shape) { + if (model.active || activeAttribute != null) { this._select(); - if (!activeAAM.shape) { + if (window.cvat.mode != 'aam') { this._makeEditable(); } } @@ -2564,25 +2543,44 @@ class ShapeView extends Listener { this._showShapeText(); } - function setupHidden(hiddenShape, hiddenText, activeAAM, active, interpolation) { + if (activeAttribute != null && this._uis.shape) { + if (model.updateReason != 'click') { + this._uis.shape.node.dispatchEvent(new Event('click')); + } + this._highlightAttribute(activeAttribute); + + let attrInfo = window.cvat.labelsInfo.attrInfo(activeAttribute); + if (attrInfo.type === 'text' || attrInfo.type === 'number') { + this._uis.attributes[activeAttribute].focus(); + this._uis.attributes[activeAttribute].select(); + } + else { + blurAllElements(); + } + } + else { + this._highlightAttribute(null); + } + + function setupHidden(hiddenShape, hiddenText, selectOnly, attributeId, active, interpolation) { this._makeNotEditable(); this._removeShapeUI(); this._removeShapeText(); - if (!hiddenShape || activeAAM.shape) { + if (!hiddenShape) { this._drawShapeUI(interpolation.position); this._setupOccludedUI(interpolation.position.occluded); - if (!hiddenText || active || activeAAM.shape) { + if (!hiddenText || active) { this._showShapeText(); } - if (model.active || activeAAM.shape) { + if (active || selectOnly) { this._select(); - if (!activeAAM.shape) { + if (!selectOnly) { this._makeEditable(); } else { - this._highlightAttribute(activeAAM.attributeId); + this._highlightAttribute(attributeId); } } } @@ -2755,38 +2753,6 @@ class BoxView extends ShapeView { ShapeView.prototype._drawShapeUI.call(this); } - - - _setupAAMView(active, pos) { - let oldRect = $('#outsideRect'); - let oldMask = $('#outsideMask'); - - if (active) { - if (oldRect.length) { - oldRect.remove(); - oldMask.remove(); - } - - let size = { - x: 0, - y: 0, - width: window.cvat.player.geometry.frameWidth, - height: window.cvat.player.geometry.frameHeight - }; - - let excludeField = this._scenes.svg.rect(size.width, size.height).move(size.x, size.y).fill('#666'); - let includeField = this._scenes.svg.rect(pos.xbr - pos.xtl, pos.ybr - pos.ytl).move(pos.xtl, pos.ytl); - this._scenes.svg.mask().add(excludeField).add(includeField).fill('black').attr('id', 'outsideMask'); - this._scenes.svg.rect(size.width, size.height).move(size.x, size.y).attr({ - mask: 'url(#outsideMask)', - id: 'outsideRect' - }); - } - else { - oldRect.remove(); - oldMask.remove(); - } - } } @@ -2805,39 +2771,6 @@ class PolyShapeView extends ShapeView { }; } - - _setupAAMView(active, pos) { - let oldRect = $('#outsideRect'); - let oldMask = $('#outsideMask'); - - if (active) { - if (oldRect.length) { - oldRect.remove(); - oldMask.remove(); - } - - let size = { - x: 0, - y: 0, - width: window.cvat.player.geometry.frameWidth, - height: window.cvat.player.geometry.frameHeight - }; - - let excludeField = this._scenes.svg.rect(size.width, size.height).move(size.x, size.y).fill('#666'); - let includeField = this._scenes.svg.polygon(pos.points); - this._scenes.svg.mask().add(excludeField).add(includeField).fill('black').attr('id', 'outsideMask'); - this._scenes.svg.rect(size.width, size.height).move(size.x, size.y).attr({ - mask: 'url(#outsideMask)', - id: 'outsideRect' - }); - } - else { - oldRect.remove(); - oldMask.remove(); - } - } - - _makeEditable() { ShapeView.prototype._makeEditable.call(this); if (this._flags.editable) { diff --git a/cvat/apps/engine/static/engine/js/userConfig.js b/cvat/apps/engine/static/engine/js/userConfig.js index 25feb1242d5a..af9934bf707b 100644 --- a/cvat/apps/engine/static/engine/js/userConfig.js +++ b/cvat/apps/engine/static/engine/js/userConfig.js @@ -55,13 +55,13 @@ class Config { }, change_shape_label: { - value: "ctrl+1,ctrl+2,ctrl+3,ctrl+4,ctrl+5,ctrl+6,ctrl+7,ctrl+8,ctrl+9".split(','), - view_value: "Ctrl + (1,2,3,4,5,6,7,8,9)", + value: [1,2,3,4,5,6,7,8,9].map((x) => `ctrl+shift+${x}`), + view_value: "Ctrl + Shift + (1,2,3,4,5,6,7,8,9)", description: "change shape label for existing object" }, change_default_label: { - value: "shift+1,shift+2,shift+3,shift+4,shift+5,shift+6,shift+7,shift+8,shift+9".split(','), + value: [1,2,3,4,5,6,7,8,9].map((x) => `shift+${x}`), view_value: "Shift + (1,2,3,4,5,6,7,8,9)", description: "change label default label" }, @@ -286,6 +286,12 @@ class Config { value: 'esc', view_value: "Esc", description: "cancel active mode" + }, + + select_object: { + value: 'ctrl', + view_value: "Ctrl", + description: "Select highlighted object" } };