/**
* Copyright (c) 2011-2018 by Andrew Mustun. All rights reserved.
*
* This file is part of the QCAD project.
*
* QCAD is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* QCAD is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with QCAD.
*/
include("scripts/EAction.js");
include("scripts/Select/Select.js");
include("scripts/Widgets/CadToolBar/CadToolBar.js");
/**
* \class DefaultAction
* \brief Default action which is active when no other tool
* or action is active. Implements basic entity selection
* and drag and drop.
*/
function DefaultAction(guiAction) {
EAction.call(this, guiAction);
}
DefaultAction.prototype = new EAction();
DefaultAction.State = {
Neutral : 0,
Dragging : 1, //*< Started dragging, minor distance, could be simple click at this stage
SettingCorner2 : 2, //*< Setting 2nd corner of selection rectangle.
MovingEntity : 3, //*< Dragging entity, mouse button is down.
MovingEntityInBlock : 4, //*< Dragging entity inside block, mouse button is down.
SettingEntity : 5, //*< Setting position of previously dragged entity, mouse button is up.
MovingReference : 6, //*< Dragging reference point, mouse button is down.
SettingReference : 7 //*< Setting position of previously dragged reference point, mouse button is up.
};
DefaultAction.prototype.beginEvent = function() {
EAction.prototype.beginEvent.call(this);
//this.snapRangePixels = RSettings.getSnapRange();
//this.minSnapRangePixels = Math.min(this.snapRangePixels / 2, 10);
this.pickRangePixels = RSettings.getPickRange();
this.minPickRangePixels = Math.min(this.pickRangePixels / 2, 10);
this.d1Model = RVector.invalid;
this.d1Screen = RVector.invalid;
this.d2Model = RVector.invalid;
this.d2Screen = RVector.invalid;
this.di = this.getDocumentInterface();
this.document = this.di.getDocument();
this.setState(DefaultAction.State.Neutral);
this.blockRefId = RObject.INVALID_ID;
this.entityInBlockId = RObject.INVALID_ID;
this.optOutRelativeZeroResume = true;
};
DefaultAction.prototype.setState = function(state) {
EAction.prototype.setState.call(this, state);
var appWin = EAction.getMainWindow();
if (this.state === DefaultAction.State.MovingReference
|| this.state === DefaultAction.State.SettingReference
|| this.state === DefaultAction.State.MovingEntity
|| this.state === DefaultAction.State.MovingEntityInBlock
|| this.state === DefaultAction.State.SettingEntity) {
this.di.setClickMode(RAction.PickCoordinate);
this.setCrosshairCursor();
EAction.showSnapTools();
} else {
this.di.setClickMode(RAction.PickingDisabled);
this.setArrowCursor();
}
var tr;
switch (this.state) {
case DefaultAction.State.Neutral:
this.d1Model = RVector.invalid;
this.d1Screen = RVector.invalid;
this.d2Model = RVector.invalid;
this.d2Screen = RVector.invalid;
var ltip = qsTr("Select entity or region");
if (this.di.hasSelection()) {
ltip += "\n" + qsTr("Move entity or reference");
}
this.setLeftMouseTip(ltip);
this.setRightMouseTip("");
this.setCommandPrompt();
break;
case DefaultAction.State.Dragging:
this.d2Model = RVector.invalid;
this.d2Screen = RVector.invalid;
break;
case DefaultAction.State.SettingCorner2:
tr = qsTr("Second corner");
this.setLeftMouseTip(tr);
this.setCommandPrompt(tr);
this.setRightMouseTip("");
break;
case DefaultAction.State.MovingReference:
case DefaultAction.State.SettingReference:
tr = qsTr("Target point of reference point");
this.setLeftMouseTip(tr);
this.setCommandPrompt(tr);
this.setRightMouseTip("");
break;
case DefaultAction.State.MovingEntity:
case DefaultAction.State.SettingEntity:
tr = qsTr("Target point of selection");
this.setLeftMouseTip(tr);
this.setCommandPrompt(tr);
this.setRightMouseTip("");
break;
case DefaultAction.State.MovingEntityInBlock:
this.setLeftMouseTip(
qsTr("Move entity to desired location")
);
break;
default:
break;
}
};
DefaultAction.prototype.suspendEvent = function() {
if (!isNull(this.getGuiAction())) {
this.getGuiAction().setChecked(false);
}
EAction.prototype.suspendEvent.call(this);
};
DefaultAction.prototype.resumeEvent = function() {
EAction.prototype.resumeEvent.call(this);
};
DefaultAction.prototype.mouseMoveEvent = function(event) {
// we're in the middle of panning:
// do nothing:
if (event.buttons().valueOf() === RS.MiddleButton ||
(event.buttons().valueOf() === Qt.LeftButton.valueOf() &&
event.modifiers().valueOf() === Qt.ControlModifier.valueOf())) {
return;
}
// we're in the middle of choosing something from a context menu:
// do nothing:
if (this.waitingForContextMenu) {
return;
}
var view, referencePoint, entityId, range;
if (isNull(this.pickRangePixels)) {
return;
}
view = event.getGraphicsView();
switch (this.state) {
case DefaultAction.State.Neutral:
var screenPosition = event.getScreenPosition();
referencePoint = view.getClosestReferencePoint(screenPosition, this.minPickRangePixels);
if (referencePoint.isValid()) {
this.highlightReferencePoint(referencePoint);
} else {
if (RSettings.getBoolValue("GraphicsView/HighlightEntity", true)===true) {
entityId = EAction.getEntityIdUnderCursor(this.getDocumentInterface(), event, undefined, true);
// range = view.mapDistanceFromView(this.pickRangePixels);
// var strictRange = view.mapDistanceFromView(10);
// RMouseEvent.setOriginalMousePos(event.globalPos());
// entityId = this.di.getClosestEntity(event.getModelPosition(), range, strictRange, false);
// RMouseEvent.resetOriginalMousePos();
if (entityId !== RObject.INVALID_ID && this.document.isEntityEditable(entityId)) {
this.highlightEntity(entityId);
}
}
}
break;
case DefaultAction.State.Dragging:
this.d2Model = event.getModelPosition();
this.d2Screen = event.getScreenPosition();
view = event.getGraphicsView();
if (!this.d1Screen.equalsFuzzy(this.d2Screen, 10 /*this.minPickRangePixels*/)) {
// if the dragging started on top of a reference point,
// start moving the reference point:
referencePoint = view.getClosestReferencePoint(this.d1Screen, this.minPickRangePixels);
if (referencePoint.isValid() && !referencePoint.isIgnore() && !RSettings.getIgnoreAllReferencePoints() &&
!referencePoint.isArrow()) {
this.d1Model = referencePoint;
this.di.setRelativeZero(this.d1Model);
this.setState(DefaultAction.State.MovingReference);
} else {
// if the dragging started on top of an entity,
// start moving the entity:
entityId = view.getClosestEntity(this.d1Screen, this.minPickRangePixels, 10, false);
// in block easy drag and drop:
if (entityId !== RObject.INVALID_ID) {
var doc = this.getDocument();
if (!isNull(doc)) {
var entity = doc.queryEntityDirect(entityId);
if (isBlockReferenceEntity(entity)) {
var blockId = entity.getReferencedBlockId();
var block = doc.queryBlock(blockId);
if (!isNull(block)) {
range = view.mapDistanceFromView(this.pickRangePixels);
if (Math.abs(entity.getScaleFactors().x)>0) {
range /= Math.abs(entity.getScaleFactors().x);
}
// cursor, mapped to block coordinates:
var pBlock = entity.mapToBlock(this.d1Model);
var box = new RBox(pBlock, range);
var candidateIds = doc.queryIntersectedEntitiesXY(box, true, false, blockId);
var entityInBlockId;
if (candidateIds.length===1) {
entityInBlockId = candidateIds[0];
}
else {
entityInBlockId = doc.queryClosestXY(candidateIds, pBlock, range*2, false, range/2);
}
var entityInBlock = doc.queryEntityDirect(entityInBlockId);
if (!isNull(entityInBlock) && getInBlockEasyDragAndDrop(entityInBlock)) {
var refP = entityInBlock.getReferencePoints();
if (refP.length>0) {
this.d1Model = refP[0]; //entity.mapToBlock(refP[0]);
}
else {
this.d1Model = pBlock;
}
this.entityInBlockId = entityInBlockId;
this.blockRefId = entityId;
this.setState(DefaultAction.State.MovingEntityInBlock);
var guiAction = RGuiAction.getByScriptFile("scripts/Snap/SnapFree/SnapFree.js");
if (!isNull(guiAction)) {
guiAction.slotTrigger();
}
this.di.setSnap(new RSnapFree());
break;
}
}
}
}
}
if (entityId !== RObject.INVALID_ID && this.document.hasSelection()) {
// move start point of dragging operation to closest
// reference point:
// TODO: use auto snap instead here (optional?):
this.d1Model = view.getClosestReferencePoint(entityId, this.d1Screen);
this.di.setRelativeZero(this.d1Model);
this.setState(DefaultAction.State.MovingEntity);
} else {
// if the dragging started in an empty space,
// start box selection:
this.setState(DefaultAction.State.SettingCorner2);
// make sure one mouse move is enough to get a visible preview
// (for testing, one mouse move event has to be enough):
this.mouseMoveEvent(event);
}
}
}
break;
case DefaultAction.State.SettingCorner2:
this.d2Model = event.getModelPosition();
Select.previewSelectionBox(
this.getDocumentInterface(),
new RBox(this.d1Model, this.d2Model),
this.d1Model.x > this.d2Model.x
);
break;
// easy in block entity drag and drop (point mark labels):
// case DefaultAction.State.MovingEntityInBlock:
// this.moveEntityInBlock(event.getModelPosition(), true);
// break;
default:
break;
}
};
DefaultAction.prototype.mouseReleaseEvent = function(event) {
if (this.waitingForContextMenu) {
return;
}
var appWin = EAction.getMainWindow();
var persistentSelection = RSettings.getBoolValue("GraphicsView/PersistentSelection", false);
var entityId;
var shiftPressed = isShiftPressed(event);
var controlPressed = isControlPressed(event);
var addToSelection = this.getAddToSelection(shiftPressed) || controlPressed || persistentSelection===true;
if (event.button() === Qt.LeftButton) {
switch (this.state) {
case DefaultAction.State.Dragging:
// handle clicks on reference points:
this.d2Model = event.getModelPosition();
this.d2Screen = event.getScreenPosition();
var view = event.getGraphicsView();
if (this.d1Screen.equalsFuzzy(this.d2Screen, 10)) {
// mouse press and release at same position (click):
var referencePoint = view.getClosestReferencePoint(this.d1Screen, this.minPickRangePixels);
if (referencePoint.isValid() && !referencePoint.isIgnore() && !RSettings.getIgnoreAllReferencePoints()) {
this.d1Model = referencePoint;
var op = new RClickReferencePointOperation(this.d1Model);
this.di.applyOperation(op);
this.setState(DefaultAction.State.Neutral);
return;
}
}
// find selectable entity under cursor:
entityId = this.getEntityId(event, false, true);
//qDebug("entityId: ", entityId);
//if (entityId!==RObject.INVALID_ID) qDebug(" parentId: ", this.document.queryEntityDirect(entityId).getParentId());
if (entityId !== -1) {
if (addToSelection && (this.document.isSelected(entityId) || this.document.isSelectedWorkingSet(entityId))) {
this.deselectEntity(entityId);
}
else {
this.selectEntity(entityId, addToSelection);
}
} else {
if (!addToSelection) {
if (persistentSelection===false) {
this.di.clearSelection();
}
}
}
this.di.clearPreview();
this.di.repaintViews();
this.setState(DefaultAction.State.Neutral);
break;
case DefaultAction.State.SettingCorner2:
this.di.clearPreview();
this.d2Model = event.getModelPosition();
// select entities:
this.di.selectBoxXY(new RBox(this.d1Model, this.d2Model), addToSelection);
this.setState(DefaultAction.State.Neutral);
break;
// easy in block entity drag and drop (point mark labels):
// case DefaultAction.State.MovingEntityInBlock:
// this.moveEntityInBlock(event.getModelPosition(), false);
// break;
default:
break;
}
} else if (event.button() == Qt.RightButton) {
var handled = false;
if (this.state!==DefaultAction.State.Neutral && this.state!==DefaultAction.State.MovingEntityInBlock) {
this.di.clearPreview();
this.di.repaintViews();
this.setState(DefaultAction.State.Neutral);
handled = true;
}
else if (this.state===DefaultAction.State.Neutral) {
var rightClickRange = RSettings.getIntValue("GraphicsView/RightClickRange", 10);
entityId = EAction.getEntityIdUnderCursor(this.di, event, rightClickRange, true);
var rightClickToDeselect = RSettings.getBoolValue("GraphicsView/RightClickToDeselect", false);
// right-click on entity can be used to show context menu:
if (entityId!==-1) {
// show entity context menu:
var rightClickEntityContextMenu = RSettings.getBoolValue("ContextMenu/RightClickEntityContextMenu", true);
if (rightClickEntityContextMenu) {
event.accept();
appWin.showContextMenu(entityId, event.getModelPosition());
handled = true;
}
// use right-click on entity to select entity:
else if (rightClickToDeselect) {
this.selectEntity(entityId, addToSelection);
handled = true;
}
}
else {
// use right-click into empty area to deselect everything:
if (rightClickToDeselect) {
if (this.di.hasSelection()) {
this.di.clearSelection();
this.di.clearPreview();
this.di.repaintViews();
handled = true;
}
}
}
}
if (!handled) {
var cadToolBar = appWin.findChild("CadToolBar");
if (cadToolBar.back()===true) {
handled = true;
}
}
if (!handled) {
// show context menu for empty space:
var rightClickContextMenu = RSettings.getBoolValue("ContextMenu/RightClickContextMenu", true);
if (rightClickContextMenu===true) {
event.accept();
appWin.showContextMenu(RObject.INVALID_ID, event.getModelPosition());
}
}
}
};
DefaultAction.prototype.mousePressEvent = function(event) {
if (event.button() == Qt.LeftButton
&& event.modifiers().valueOf() != Qt.ControlModifier.valueOf()) {
if (this.state == DefaultAction.State.Neutral) {
this.d1Model = event.getModelPosition();
this.d1Screen = event.getScreenPosition();
this.setState(DefaultAction.State.Dragging);
this.di.clearPreview();
}
}
};
DefaultAction.prototype.mouseDoubleClickEvent = function(event) {
if (event.button() == Qt.LeftButton && (this.state===DefaultAction.State.Neutral || this.state===DefaultAction.State.Dragging)) {
if (this.state===DefaultAction.State.Dragging) {
// cancel dragging (double-click):
this.setState(DefaultAction.State.Neutral);
}
var view = event.getGraphicsView();
var range = view.mapDistanceFromView(this.pickRangePixels);
var strictRange = view.mapDistanceFromView(10);
var entityId = this.di.getClosestEntity(event.getModelPosition(), range, strictRange, false);
if (entityId===RObject.INVALID_ID) {
this.emptySpaceDoubleClicked();
return;
}
this.entityDoubleClicked(entityId, event);
}
EAction.prototype.mouseDoubleClickEvent.call(this, event);
};
DefaultAction.prototype.emptySpaceDoubleClicked = function() {
if (this.document.getCurrentViewportId()!==RObject.INVALID_ID) {
this.di.unsetCurrentViewport();
}
};
DefaultAction.prototype.escapeEvent = function(event) {
this.di.clearPreview();
this.di.repaintViews();
var appWin = EAction.getMainWindow();
var cadToolBar = appWin.findChild("CadToolBar");
cadToolBar.back();
this.setState(DefaultAction.State.Neutral);
};
DefaultAction.prototype.coordinateEvent = function(event) {
// required to make sure invalid coordinate event goes through (needed for drag and drop):
this.pickCoordinate(event, false);
};
DefaultAction.prototype.pickCoordinate = function(event, preview) {
var op;
var shiftPressed = isShiftPressed(event);
var addToSelection = this.getAddToSelection(shiftPressed);
var doc = this.getDocument();
switch (this.state) {
case DefaultAction.State.MovingReference:
case DefaultAction.State.SettingReference:
// find reference shape for lengthen, shorten when shift is pressed:
var referenceShape = undefined;
var d2 = event.getModelPosition();
if (shiftPressed) {
var view = event.getGraphicsView();
var modelRange = view.mapDistanceFromView(this.minPickRangePixels);
var referenceEntityId = this.di.getClosestEntity(this.d1Model, modelRange, 10, false, true);
if (referenceEntityId!==RObject.INVALID_ID) {
var e = doc.queryEntity(referenceEntityId);
if (!isNull(e)) {
referenceShape = e.getClosestSimpleShape(this.d1Model);
}
}
if (!isNull(referenceShape)) {
d2 = referenceShape.getClosestPointOnShape(d2, false);
this.di.addAuxShapeToPreview(new RLine(this.d2Model, event.getModelPosition()));
}
}
if (preview) {
this.d2Model = d2;
op = new RMoveReferencePointOperation(this.d1Model, this.d2Model, event.getModifiers());
var scene = this.di.getGraphicsSceneWithFocus();
op.setGraphicsScene(scene);
this.di.previewOperation(op);
}
else {
if (this.state===DefaultAction.State.MovingReference) {
this.setState(DefaultAction.State.SettingReference);
}
else {
this.d2Model = d2;
op = new RMoveReferencePointOperation(this.d1Model, this.d2Model, event.getModifiers());
var scene = this.di.getGraphicsSceneWithFocus();
op.setGraphicsScene(scene);
op.setText(qsTr("Move Reference Point"));
this.di.applyOperation(op);
this.di.clearPreview();
this.di.repaintViews();
CadToolBar.back();
this.setState(DefaultAction.State.Neutral);
}
}
break;
case DefaultAction.State.MovingEntity:
case DefaultAction.State.SettingEntity:
if (preview) {
this.d2Model = event.getModelPosition();
op = new RMoveSelectionOperation(this.d1Model, this.d2Model);
this.di.previewOperation(op);
}
else {
if (this.state===DefaultAction.State.MovingEntity) {
this.setState(DefaultAction.State.SettingEntity);
}
else {
this.d2Model = event.getModelPosition();
op = new RMoveSelectionOperation(this.d1Model, this.d2Model);
op.setText(qsTr("Move Selection"));
this.di.applyOperation(op);
this.di.clearPreview();
this.di.repaintViews();
CadToolBar.back();
this.setState(DefaultAction.State.Neutral);
}
}
break;
// easy in block entity drag and drop (point mark labels):
case DefaultAction.State.MovingEntityInBlock:
var doc = this.getDocument();
if (isNull(doc)) {
break;
}
var blockRef = doc.queryEntity(this.blockRefId);
if (isNull(blockRef)) {
break;
}
this.d2Model = blockRef.mapToBlock(event.getModelPosition());
var entityInBlock = doc.queryEntity(this.entityInBlockId);
entityInBlock.move(this.d2Model.operator_subtract(this.d1Model));
op = new RAddObjectsOperation();
op.setText(qsTr("Move Entity"));
op.addObject(entityInBlock, false);
if (preview) {
this.di.previewOperation(op);
}
else {
doc.removeFromSpatialIndex(blockRef);
this.di.applyOperation(op);
blockRef.update();
doc.addToSpatialIndex(blockRef);
this.setState(DefaultAction.State.Neutral);
var guiAction = RGuiAction.getByScriptFile("scripts/Snap/SnapAuto/SnapAuto.js");
if (!isNull(guiAction)) {
guiAction.slotTrigger();
}
this.di.setSnap(new RSnapAuto());
}
break;
default:
break;
}
};
/**
* Called when the mouse cursor hovers over an entity.
*/
DefaultAction.prototype.highlightEntity = function(entityId) {
if (RSettings.getBoolValue("GraphicsView/HighlightEntity", true)===false) {
return;
}
if (isNull(this.di)) {
return;
}
this.di.highlightEntity(entityId);
};
/**
* Called when the mouse cursor hovers over a reference point.
*/
DefaultAction.prototype.highlightReferencePoint = function(referencePoint) {
if (isNull(this.di)) {
return;
}
this.di.highlightReferencePoint(referencePoint);
};
/**
* Called when the user deselects a single entity.
*/
DefaultAction.prototype.deselectEntity = function(entityId) {
if (isNull(this.di)) {
return;
}
this.di.deselectEntity(entityId);
};
/**
* Called when the user selects a single entity.
*/
DefaultAction.prototype.selectEntity = function(entityId, add) {
if (isNull(this.di)) {
return;
}
this.di.selectEntity(entityId, add);
};
/**
* Called when the user double-clicks an entity.
*/
DefaultAction.prototype.entityDoubleClicked = function(entityId, event) {
if (isNull(this.document) || isNull(this.di)) {
return;
}
var entity = this.document.queryEntity(entityId);
if (isTextEntity(entity) ||
isAttributeEntity(entity) ||
isAttributeDefinitionEntity(entity)) {
if (RSettings.getBoolValue("GraphicsView/DoubleClickEditText", true)===true) {
include("scripts/Modify/EditText/EditText.js");
EditText.editText(entity);
}
}
else if (isBlockReferenceEntity(entity)) {
// in block text editing with double-click:
/*
var blockId = entity.getReferencedBlockId();
var block = this.document.queryBlock(blockId);
if (!isNull(block)) {
var view = event.getGraphicsView();
var range = view.mapDistanceFromView(this.pickRangePixels);
// cursor, mapped to block coordinates:
var pBlock = entity.mapToBlock(event.getModelPosition());
var box = new RBox(
pBlock.operator_subtract(new RVector(range,range)),
pBlock.operator_add(new RVector(range,range))
);
var res = this.document.queryIntersectedEntitiesXY(box, true, false, blockId);
var entityInBlockId;
if (res.length===1) {
entityInBlockId = res[0];
}
else {
entityInBlockId = this.document.queryClosestXY(res, pBlock, range*2, false);
}
var entityInBlock = this.document.queryEntity(entityInBlockId);
if (!isNull(entityInBlock) && isTextBasedEntity(entityInBlock) && getInBlockTextEdit(entityInBlock)) {
include("scripts/Modify/EditText/EditText.js");
EditText.editText(entityInBlock);
return;
}
}
*/
// block editing with double click might be disabled globally:
if (RSettings.getBoolValue("GraphicsView/DoubleClickEditBlock", true)===true) {
// block editing with double click might be disabled for this entity:
if (entity.getCustomBoolProperty("QCAD", "DoubleClickEditBlock", true)===true) {
include("scripts/Block/Block.js");
EAction.handleUserMessage(qsTr("Editing block \"%1\"
Choose Block > Return to Main Drawing when done").arg(entity.getReferencedBlockName()), false);
Block.editBlock(this.di, entity.getReferencedBlockName());
}
}
}
else if (isHatchEntity(entity)) {
if (RSettings.getBoolValue("GraphicsView/DoubleClickEditHatch", true)===true) {
include("scripts/Modify/EditHatch/EditHatch.js");
EditHatch.editHatch(entity);
}
}
else if (isLineEntity(entity) ||
isArcEntity(entity) ||
isSplineEntity(entity) ||
isPolylineEntity(entity) ||
isEllipseEntity(entity)) {
if (RSettings.getBoolValue("GraphicsView/DoubleClickSelectContour", true)===true) {
include("scripts/Select/SelectContour/SelectContour.js");
var tol = RSettings.getDoubleValue("GraphicsView/DoubleClickSelectContourTolerance", 0.001);
var layerId = RObject.INVALID_ID;
if (isAltPressed(event)) {
// Alt: restrict to same layer:
layerId = entity.getLayerId();
}
var matchingEntityIds = SelectContour.getConnectedEntities(this.document, entityId, tol, layerId);
var add = isShiftPressed(event);
if (entity.isSelected()) {
this.di.selectEntities(matchingEntityIds, add);
}
else {
this.di.deselectEntities(matchingEntityIds);
}
}
}
// TODO:
// else if (isViewportEntity(entity)) {
// this.di.setCurrentViewport(entity.data());
// }
};
/**
* \return True to add to selection (by default this is when shiftPressed is true).
* May be reimplemented to allow other ways to add to selection (e.g. user interface button, etc.)
*/
DefaultAction.prototype.getAddToSelection = function(shiftPressed) {
return shiftPressed;
};