You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1984 lines
52 KiB
1984 lines
52 KiB
//========= Copyright Valve Corporation, All rights reserved. ============// |
|
// |
|
// Purpose: |
|
// |
|
//===========================================================================// |
|
|
|
#include "stdafx.h" |
|
#include "Gizmo.h" |
|
#include "GlobalFunctions.h" // FIXME: For NotifyDuplicates |
|
#include "History.h" |
|
#include "MainFrm.h" |
|
#include "MapDoc.h" |
|
#include "MapDefs.h" |
|
#include "MapEntity.h" |
|
#include "MapPointHandle.h" |
|
#include "MapSolid.h" |
|
#include "MapView2D.h" |
|
#include "MapViewLogical.h" |
|
#include "MapView3D.h" |
|
#include "ObjectProperties.h" |
|
#include "Options.h" |
|
#include "Render2D.h" |
|
#include "ToolSelection.h" |
|
#include "StatusBarIDs.h" |
|
#include "ToolManager.h" |
|
#include "hammer.h" |
|
#include "vgui/Cursor.h" |
|
#include "mapdecal.h" |
|
#include "RenderUtils.h" |
|
#include "tier0/icommandline.h" |
|
#include "Manifest.h" |
|
|
|
// memdbgon must be the last include file in a .cpp file!!! |
|
#include <tier0/memdbgon.h> |
|
|
|
|
|
#pragma warning(disable:4244) |
|
|
|
|
|
// For debugging mouse messages |
|
//static int _nMouseMove = 0; |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
Selection3D::Selection3D(void) |
|
{ |
|
// The block tool uses our bounds as the default size when starting a new |
|
// box. Set to reasonable defaults to begin with. |
|
|
|
m_bIsLogicalTranslating = false; |
|
m_bInLogicalBoxSelection = false; |
|
m_bBoxSelection = false; |
|
|
|
m_bEyedropper = false; |
|
m_b3DEditMode = false; |
|
m_bSelected = false; |
|
m_bLButtonDown = false; |
|
m_bLeftDragged = false; |
|
m_bDrawAsSolidBox = false; |
|
|
|
SetDrawFlags(Box3D::expandbox | Box3D::boundstext); |
|
SetDrawColors(Options.colors.clrToolHandle, Options.colors.clrToolSelection); |
|
m_clrLogicalBox = Options.colors.clrToolSelection; |
|
m_vLDownLogicalClient.Init(); |
|
|
|
m_pSelection = NULL; |
|
} |
|
|
|
|
|
void Selection3D::Init( CMapDoc *pDocument ) |
|
{ |
|
Box3D::Init( pDocument ); |
|
m_pSelection = pDocument->GetSelection(); |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
Selection3D::~Selection3D(void) |
|
{ |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Called when the tool is activated. |
|
// Input : eOldTool - The ID of the previously active tool. |
|
//----------------------------------------------------------------------------- |
|
void Selection3D::OnActivate() |
|
{ |
|
EnableHandles(true); |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Called when the tool is deactivated. |
|
// Input : eNewTool - The ID of the tool that is being activated. |
|
//----------------------------------------------------------------------------- |
|
void Selection3D::OnDeactivate() |
|
{ |
|
EnableHandles(false); |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Enables or disables the selection handles based on the current |
|
// state of the tool. |
|
//----------------------------------------------------------------------------- |
|
void Selection3D::UpdateHandleState(void) |
|
{ |
|
if ( !IsActiveTool() || m_pSelection->IsEditable() == false ) |
|
{ |
|
EnableHandles(false); |
|
} |
|
else |
|
{ |
|
EnableHandles(true); |
|
} |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
// Input : pView - The view that invoked the eyedropper. |
|
// VarList - |
|
// Output : |
|
//----------------------------------------------------------------------------- |
|
GDinputvariable *Selection3D::ChooseEyedropperVar(CMapView *pView, CUtlVector<GDinputvariable *> &VarList) |
|
{ |
|
// |
|
// Build a popup menu containing all the variable names. |
|
// |
|
CMenu menu; |
|
menu.CreatePopupMenu(); |
|
int nVarCount = VarList.Count(); |
|
for (int nVar = 0; nVar < nVarCount; nVar++) |
|
{ |
|
GDinputvariable *pVar = VarList.Element(nVar); |
|
menu.AppendMenu(MF_STRING, nVar + 1, pVar->GetLongName()); |
|
} |
|
|
|
// |
|
// Invoke the popup menu. |
|
// |
|
CPoint point; |
|
GetCursorPos(&point); |
|
int nID = menu.TrackPopupMenu(TPM_LEFTALIGN | TPM_LEFTBUTTON | TPM_NONOTIFY | TPM_RETURNCMD, point.x, point.y, NULL, NULL); |
|
if (nID == 0) |
|
{ |
|
return NULL; |
|
} |
|
|
|
return VarList.Element(nID - 1); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
// Input : pt - |
|
// bValidOnly - |
|
// Output : Returns the handle under the given point, -1 if there is none. |
|
//----------------------------------------------------------------------------- |
|
int Selection3D::HitTest(CMapView *pView, const Vector2D &ptClient, bool bTestHandles) |
|
{ |
|
if (!IsEmpty()) |
|
{ |
|
return Box3D::HitTest(pView, ptClient, bTestHandles); |
|
} |
|
|
|
return FALSE; |
|
} |
|
|
|
bool Selection3D::HitTestLogical( CMapView *pView, const Vector2D &ptClient ) |
|
{ |
|
Vector2D vecLogicalMins, vecLogicalMaxs; |
|
if ( !m_pSelection->GetLogicalBounds(vecLogicalMins, vecLogicalMaxs) ) |
|
return false; |
|
|
|
// Build a rect from our bounds to hit test against. |
|
Vector2D vecMinClient, vecMaxClient; |
|
Vector vecMins( vecLogicalMins.x, vecLogicalMins.y, 0.0f ); |
|
Vector vecMaxs( vecLogicalMaxs.x, vecLogicalMaxs.y, 0.0f ); |
|
|
|
pView->WorldToClient( vecMinClient, vecMins ); |
|
pView->WorldToClient( vecMaxClient, vecMaxs ); |
|
|
|
CRect rect(vecMinClient.x, vecMinClient.y, vecMaxClient.x, vecMaxClient.y); |
|
rect.NormalizeRect(); |
|
|
|
// See if the point lies within the main rect. |
|
return rect.PtInRect( CPoint( ptClient.x, ptClient.y ) ); |
|
} |
|
|
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void Selection3D::SetEmpty(void) |
|
{ |
|
m_vTranslation.Init(); |
|
m_bIsTranslating = false; |
|
m_pSelection->SelectObject(NULL,scClear); |
|
UpdateSelectionBounds(); |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
bool Selection3D::IsEmpty(void) |
|
{ |
|
return (m_bBoxSelection || m_pSelection->GetCount()) ? false : true; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
// Input : |
|
//----------------------------------------------------------------------------- |
|
void Selection3D::UpdateSelectionBounds( void ) |
|
{ |
|
if ( !m_pSelection->GetBounds( bmins, bmaxs ) ) |
|
{ |
|
ResetBounds(); |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
// Input : pt3 - |
|
// Output : Returns TRUE on success, FALSE on failure. |
|
//----------------------------------------------------------------------------- |
|
bool Selection3D::StartBoxSelection( CMapView *pView, const Vector2D &vPoint, const Vector &vStart) |
|
{ |
|
m_bBoxSelection = true; |
|
|
|
Box3D::StartNew( pView, vPoint, vStart, Vector(0,0,0) ); |
|
|
|
return true; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void Selection3D::EndBoxSelection() |
|
{ |
|
m_pDocument->UpdateAllViews( MAPVIEW_UPDATE_TOOL | MAPVIEW_UPDATE_SELECTION ); |
|
m_bBoxSelection = false; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Start, end logical selection |
|
//----------------------------------------------------------------------------- |
|
void Selection3D::StartLogicalBoxSelection( CMapViewLogical *pView, const Vector &vStart ) |
|
{ |
|
m_bInLogicalBoxSelection = true; |
|
m_clrLogicalBox = RGB( 50, 255, 255 ); |
|
m_vecLogicalSelBoxMins = m_vecLogicalSelBoxMaxs = vStart.AsVector2D(); |
|
} |
|
|
|
void Selection3D::EndLogicalBoxSelection( ) |
|
{ |
|
m_clrLogicalBox = Options.colors.clrToolSelection; |
|
m_pDocument->UpdateAllViews( MAPVIEW_UPDATE_TOOL | MAPVIEW_UPDATE_SELECTION ); |
|
m_bInLogicalBoxSelection = false; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void Selection3D::TransformSelection(void) |
|
{ |
|
// Transform the selected objects. |
|
const CMapObjectList *pSelList = m_pSelection->GetList(); |
|
for (int i = 0; i < pSelList->Count(); i++) |
|
{ |
|
CMapClass *pobj = pSelList->Element(i); |
|
pobj->Transform( GetTransformMatrix() ); |
|
} |
|
|
|
m_pDocument->SetModifiedFlag(); |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void Selection3D::TransformLogicalSelection( const Vector2D &vecTranslation ) |
|
{ |
|
// Transform the selected objects. |
|
const CMapObjectList *pSelList = m_pSelection->GetList(); |
|
for (int i = 0; i < pSelList->Count(); i++) |
|
{ |
|
CMapClass *pObj = pSelList->Element(i); |
|
Vector2D vecNewPosition; |
|
Vector2DAdd( pObj->GetLogicalPosition(), vecTranslation, vecNewPosition ); |
|
pObj->SetLogicalPosition( vecNewPosition ); |
|
} |
|
|
|
// The transformation may have changed some entity properties (such as the "angles" key), |
|
// so we must refresh the Object Properties dialog. |
|
GetMainWnd()->pObjectProperties->MarkDataDirty(); |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Draws objects when they are selected. Odd, how this code is stuck |
|
// in this obscure place, away from all the other 2D rendering code. |
|
// Input : pobj - Object to draw. |
|
// pSel - |
|
// Output : Returns TRUE to keep enumerating. |
|
//----------------------------------------------------------------------------- |
|
static BOOL DrawObject(CMapClass *pobj, CRender *pRender) |
|
{ |
|
if ( !pobj->IsVisible() ) |
|
return true; |
|
|
|
// switch selection mode so transformed object is drawn normal |
|
pobj->SetSelectionState( SELECT_NONE ); |
|
|
|
CRender2D *pRender2D = dynamic_cast<CRender2D*>(pRender); |
|
|
|
if ( pRender2D ) |
|
pobj->Render2D(pRender2D); |
|
|
|
CRender3D *pRender3D = dynamic_cast<CRender3D*>(pRender); |
|
|
|
if ( pRender3D ) |
|
pobj->Render3D(pRender3D); |
|
|
|
pobj->SetSelectionState( SELECT_MODIFY ); |
|
|
|
return TRUE; |
|
} |
|
|
|
|
|
static BOOL DrawObjectLogical( CMapClass *pObj, CRender2D *pRender2D ) |
|
{ |
|
if ( !pObj->IsVisibleLogical() ) |
|
return true; |
|
|
|
// switch selection mode so transformed object is drawn normal |
|
pObj->SetSelectionState( SELECT_NONE ); |
|
|
|
if ( pRender2D ) |
|
{ |
|
pObj->RenderLogical( pRender2D ); |
|
} |
|
|
|
pObj->SetSelectionState( SELECT_MODIFY ); |
|
|
|
return TRUE; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
// Input : *pRender - |
|
//----------------------------------------------------------------------------- |
|
void Selection3D::RenderTool2D(CRender2D *pRender) |
|
{ |
|
if ( !m_pSelection->IsEmpty() && IsTranslating() && !IsBoxSelecting() ) |
|
{ |
|
// |
|
// Even if this is not the active tool, selected objects should be rendered |
|
// with the selection color. |
|
// |
|
COLORREF clr = Options.colors.clrSelection; |
|
|
|
pRender->SetDrawColor( GetRValue(clr), GetGValue(clr), GetBValue(clr) ); |
|
|
|
VMatrix matrix = GetTransformMatrix(); |
|
|
|
pRender->BeginLocalTransfrom( matrix ); |
|
|
|
const CMapObjectList *pSelList = m_pSelection->GetList(); |
|
for (int i = 0; i < pSelList->Count(); i++) |
|
{ |
|
CMapClass *pobj = pSelList->Element(i); |
|
|
|
DrawObject(pobj, pRender); |
|
pobj->EnumChildren((ENUMMAPCHILDRENPROC)DrawObject, (DWORD)pRender); |
|
} |
|
|
|
pRender->EndLocalTransfrom(); |
|
} |
|
else if ( !IsBoxSelecting() ) |
|
{ |
|
UpdateSelectionBounds(); |
|
} |
|
|
|
Box3D::RenderTool2D(pRender); |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Render tool in visio view |
|
//----------------------------------------------------------------------------- |
|
void Selection3D::RenderToolLogical( CRender2D *pRender ) |
|
{ |
|
if ( !m_pSelection->IsEmpty() && m_bIsLogicalTranslating && !IsLogicalBoxSelecting() ) |
|
{ |
|
// Even if this is not the active tool, selected objects should be rendered |
|
// with the selection color. |
|
COLORREF clr = Options.colors.clrSelection; |
|
|
|
pRender->SetDrawColor( GetRValue(clr), GetGValue(clr), GetBValue(clr) ); |
|
|
|
VMatrix matrix = GetTransformMatrix(); |
|
MatrixBuildTranslation( matrix, m_vLogicalTranslation.x, m_vLogicalTranslation.y, 0.0f ); |
|
pRender->BeginLocalTransfrom( matrix ); |
|
|
|
const CMapObjectList *pSelList = m_pSelection->GetList(); |
|
for (int i = 0; i < pSelList->Count(); i++) |
|
{ |
|
CMapClass *pobj = pSelList->Element(i); |
|
|
|
DrawObjectLogical(pobj, pRender); |
|
pobj->EnumChildren((ENUMMAPCHILDRENPROC)DrawObjectLogical, (DWORD)pRender); |
|
} |
|
|
|
pRender->EndLocalTransfrom(); |
|
} |
|
|
|
Vector2D vecLogicalMins, vecLogicalMaxs; |
|
if ( IsLogicalBoxSelecting() ) |
|
{ |
|
vecLogicalMins = m_vecLogicalSelBoxMins; |
|
vecLogicalMaxs = m_vecLogicalSelBoxMaxs; |
|
} |
|
else if ( !m_pSelection->GetLogicalBounds( vecLogicalMins, vecLogicalMaxs ) ) |
|
return; |
|
|
|
|
|
|
|
Vector mins( vecLogicalMins.x, vecLogicalMins.y, 0.0f ); |
|
Vector maxs( vecLogicalMaxs.x, vecLogicalMaxs.y, 0.0f ); |
|
|
|
Assert( pRender ); |
|
pRender->PushRenderMode( RENDER_MODE_DOTTED ); |
|
pRender->SetDrawColor( GetRValue(Options.colors.clrToolDrag), GetGValue(Options.colors.clrToolDrag), GetBValue(Options.colors.clrToolDrag) ); |
|
pRender->DrawRectangle( mins, maxs, false, 2 ); |
|
pRender->PopRenderMode(); |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Renders a selection gizmo at our bounds center. |
|
// Input : pRender - |
|
//----------------------------------------------------------------------------- |
|
void Selection3D::RenderTool3D(CRender3D *pRender) |
|
{ |
|
const CMapObjectList *pSelList = m_pSelection->GetList(); |
|
|
|
if ( m_bDrawAsSolidBox ) |
|
{ |
|
// while picking draw Selection tool as solid box |
|
// so we cant pick stuff behind it |
|
if ( pSelList->Count() ) |
|
{ |
|
pRender->PushRenderMode( RENDER_MODE_FLAT ); |
|
pRender->BeginRenderHitTarget( pSelList->Element(0) ); |
|
pRender->RenderBox( bmins, bmaxs, 255,255,255, SELECT_NONE ); |
|
pRender->EndRenderHitTarget(); |
|
pRender->PopRenderMode(); |
|
} |
|
return; |
|
} |
|
|
|
else if ( !m_pSelection->IsEmpty() && IsTranslating() && !IsBoxSelecting() ) |
|
{ |
|
// |
|
// Even if this is not the active tool, selected objects should be rendered |
|
// with the selection color. |
|
// |
|
COLORREF clr = Options.colors.clrSelection; |
|
|
|
pRender->SetDrawColor( GetRValue(clr), GetGValue(clr), GetBValue(clr) ); |
|
|
|
VMatrix matrix = GetTransformMatrix(); |
|
pRender->BeginLocalTransfrom( matrix ); |
|
|
|
|
|
for (int i = 0; i < pSelList->Count(); i++) |
|
{ |
|
CMapClass *pobj = pSelList->Element(i); |
|
|
|
DrawObject(pobj, pRender); |
|
pobj->EnumChildren((ENUMMAPCHILDRENPROC)DrawObject, (DWORD)pRender); |
|
} |
|
|
|
pRender->EndLocalTransfrom(); |
|
|
|
if ( m_pDocument->m_bShowGrid && m_b3DEditMode ) |
|
RenderTranslationPlane( pRender ); |
|
} |
|
else if ( !IsBoxSelecting() ) |
|
{ |
|
UpdateSelectionBounds(); |
|
} |
|
|
|
if ( m_b3DEditMode ) |
|
{ |
|
Box3D::RenderTool3D(pRender); |
|
} |
|
} |
|
|
|
CBaseTool *Selection3D::GetToolObject( CMapView2D *pView, const Vector2D &vPoint, bool bAttach ) |
|
{ |
|
|
|
const CMapObjectList *pSelList = m_pSelection->GetList(); |
|
for (int i = 0; i < pSelList->Count(); i++) |
|
{ |
|
CMapClass *pObject = pSelList->Element(i); |
|
|
|
// |
|
// Hit test against the object. nHitData will return with object-specific |
|
// information about what was clicked on. |
|
// |
|
HitInfo_t HitData; |
|
if ( pObject->HitTest2D(pView, vPoint, HitData) ) |
|
{ |
|
// |
|
// They clicked on some part of the object. See if there is a |
|
// tool associated with what we clicked on. |
|
// |
|
CBaseTool *pToolHit = HitData.pObject->GetToolObject(HitData.uData, bAttach ); |
|
if ( pToolHit != NULL ) |
|
{ |
|
return pToolHit; |
|
} |
|
} |
|
} |
|
|
|
return NULL; |
|
} |
|
|
|
CBaseTool *Selection3D::GetToolObjectLogical( CMapViewLogical *pView, const Vector2D &vPoint, bool bAttach ) |
|
{ |
|
const CMapObjectList *pSelList = m_pSelection->GetList(); |
|
for (int i = 0; i < pSelList->Count(); i++) |
|
{ |
|
CMapClass *pObject = pSelList->Element(i); |
|
|
|
// |
|
// Hit test against the object. nHitData will return with object-specific |
|
// information about what was clicked on. |
|
// |
|
HitInfo_t HitData; |
|
if ( pObject->HitTestLogical(pView, vPoint, HitData) ) |
|
{ |
|
// |
|
// They clicked on some part of the object. See if there is a |
|
// tool associated with what we clicked on. |
|
// |
|
CBaseTool *pToolHit = HitData.pObject->GetToolObject(HitData.uData, bAttach ); |
|
if ( pToolHit != NULL ) |
|
{ |
|
return pToolHit; |
|
} |
|
} |
|
} |
|
|
|
return NULL; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
// Input : pView - |
|
// point - |
|
//----------------------------------------------------------------------------- |
|
bool Selection3D::OnContextMenu2D(CMapView2D *pView, UINT nFlags, const Vector2D &vPoint) |
|
{ |
|
// First give any selected tool helpers a chance to handle the message. |
|
// Don't hit test against tool helpers when shift is held down |
|
// (beginning a Clone operation). |
|
|
|
CBaseTool *pToolHit = GetToolObject( pView, vPoint, true ); |
|
|
|
if ( pToolHit ) |
|
{ |
|
return pToolHit->OnContextMenu2D(pView, nFlags, vPoint); |
|
} |
|
|
|
static CMenu menu, menuSelection; |
|
static bool bInit = false; |
|
|
|
if (!bInit) |
|
{ |
|
bInit = true; |
|
menu.LoadMenu(IDR_POPUPS); |
|
menuSelection.Attach(::GetSubMenu(menu.m_hMenu, 0)); |
|
} |
|
|
|
if ( !pView->PointInClientRect( vPoint ) ) |
|
return false; |
|
|
|
if (!IsEmpty() && !IsBoxSelecting()) |
|
{ |
|
if ( HitTest(pView, vPoint, false) ) |
|
{ |
|
CPoint ptScreen( vPoint.x,vPoint.y); |
|
pView->ClientToScreen(&ptScreen); |
|
menuSelection.TrackPopupMenu(TPM_LEFTBUTTON | TPM_RIGHTBUTTON | TPM_LEFTALIGN, ptScreen.x, ptScreen.y, pView); |
|
return true; |
|
} |
|
} |
|
|
|
return false; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
// Input : pView - |
|
// point - |
|
//----------------------------------------------------------------------------- |
|
bool Selection3D::OnContextMenuLogical(CMapViewLogical *pView, UINT nFlags, const Vector2D &vPoint) |
|
{ |
|
// First give any selected tool helpers a chance to handle the message. |
|
// Don't hit test against tool helpers when shift is held down |
|
// (beginning a Clone operation). |
|
CBaseTool *pToolHit = GetToolObjectLogical( pView, vPoint, true ); |
|
if ( pToolHit ) |
|
return pToolHit->OnContextMenuLogical(pView, nFlags, vPoint); |
|
|
|
static CMenu menu, menuSelection; |
|
static bool bInit = false; |
|
|
|
if (!bInit) |
|
{ |
|
bInit = true; |
|
menu.LoadMenu(IDR_POPUPS); |
|
menuSelection.Attach(::GetSubMenu(menu.m_hMenu, 8)); |
|
} |
|
|
|
if ( !pView->PointInClientRect( vPoint ) ) |
|
return false; |
|
|
|
if (!IsEmpty() && !IsLogicalBoxSelecting()) |
|
{ |
|
if ( HitTestLogical( pView, vPoint ) ) |
|
{ |
|
CPoint ptScreen( vPoint.x, vPoint.y ); |
|
pView->ClientToScreen(&ptScreen); |
|
menuSelection.TrackPopupMenu(TPM_LEFTBUTTON | TPM_RIGHTBUTTON | TPM_LEFTALIGN, ptScreen.x, ptScreen.y, pView); |
|
return true; |
|
} |
|
} |
|
|
|
return false; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void Selection3D::SelectInBox(CMapDoc *pDoc, bool bInsideOnly) |
|
{ |
|
BoundBox box(*this); |
|
EndBoxSelection(); |
|
|
|
// |
|
// Make selection box "infinite" in 0-depth axes, of which there |
|
// should not be more than 1. |
|
// |
|
int countzero = 0; |
|
for(int i = 0; i < 3; i++) |
|
{ |
|
if (box.bmaxs[i] == box.bmins[i]) |
|
{ |
|
box.bmins[i] = -COORD_NOTINIT; |
|
box.bmaxs[i] = COORD_NOTINIT; |
|
++countzero; |
|
} |
|
} |
|
|
|
if (countzero <= 1) |
|
{ |
|
pDoc->SelectRegion(&box, bInsideOnly); |
|
} |
|
|
|
UpdateSelectionBounds(); |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void Selection3D::SelectInLogicalBox(CMapDoc *pDoc, bool bInsideOnly) |
|
{ |
|
Vector2D mins = m_vecLogicalSelBoxMins; |
|
Vector2D maxs = m_vecLogicalSelBoxMaxs; |
|
|
|
// Make selection box "infinite" in 0-depth axes, of which there |
|
// should not be more than 1. |
|
int countzero = 0; |
|
for (int i = 0; i < 2; i++) |
|
{ |
|
if (maxs[i] == mins[i]) |
|
{ |
|
mins[i] = -COORD_NOTINIT; |
|
maxs[i] = COORD_NOTINIT; |
|
++countzero; |
|
} |
|
} |
|
|
|
if (countzero <= 1) |
|
{ |
|
pDoc->SelectLogicalRegion( mins, maxs, bInsideOnly ); |
|
} |
|
|
|
UpdateSelectionBounds(); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void Selection3D::NudgeObjects(CMapView *pView, int nChar, bool bSnap, bool bClone) |
|
{ |
|
Vector vecDelta, vVert, vHorz, vThrd; |
|
|
|
pView->GetBestTransformPlane( vHorz, vVert, vThrd ); |
|
|
|
m_pDocument->GetNudgeVector( vHorz, vVert, nChar, bSnap, vecDelta); |
|
m_pDocument->NudgeObjects(vecDelta, bClone); |
|
|
|
CMapView2DBase *pView2D = dynamic_cast<CMapView2DBase*>(pView); |
|
|
|
if ( !pView2D ) |
|
return; |
|
|
|
// Try to keep the selection fully in the view if it started that way. |
|
bool bFullyVisible = pView2D->IsBoxFullyVisible(bmins, bmaxs); |
|
|
|
// Make sure it can still fit entirely in the view after nudging and don't scroll the |
|
// view if it can't. This second check is probably unnecessary, but it can't hurt, |
|
// and there might be cases where the selection changes size after a nudge operation. |
|
if (bFullyVisible && pView2D->CanBoxFitInView(bmins, bmaxs)) |
|
{ |
|
pView2D->LockWindowUpdate(); |
|
pView2D->EnsureVisible(bmins, 25); |
|
pView2D->EnsureVisible(bmaxs, 25); |
|
pView2D->UnlockWindowUpdate(); |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Handles key down events in the 2D view. |
|
// Input : Per CWnd::OnKeyDown. |
|
// Output : Returns true on success, false on failure. |
|
//----------------------------------------------------------------------------- |
|
bool Selection3D::OnKeyDown2D(CMapView2D *pView, UINT nChar, UINT nRepCnt, UINT nFlags) |
|
{ |
|
bool bShift = ((GetKeyState(VK_SHIFT) & 0x8000) != 0); |
|
bool bCtrl = ((GetKeyState(VK_CONTROL) & 0x8000) != 0); |
|
|
|
if (Options.view2d.bNudge && (nChar == VK_UP || nChar == VK_DOWN || nChar == VK_LEFT || nChar == VK_RIGHT)) |
|
{ |
|
if (!IsEmpty()) |
|
{ |
|
bool bSnap = m_pDocument->IsSnapEnabled() && !bCtrl; |
|
NudgeObjects(pView, nChar, bSnap, bShift); |
|
return true; |
|
} |
|
} |
|
|
|
switch (nChar) |
|
{ |
|
// TODO: do we want this here or in the view? |
|
case VK_DELETE: |
|
{ |
|
m_pDocument->OnCmdMsg(ID_EDIT_DELETE, CN_COMMAND, NULL, NULL); |
|
break; |
|
} |
|
|
|
case VK_NEXT: |
|
{ |
|
m_pDocument->OnCmdMsg(ID_EDIT_SELNEXT, CN_COMMAND, NULL, NULL); |
|
break; |
|
} |
|
|
|
case VK_PRIOR: |
|
{ |
|
m_pDocument->OnCmdMsg(ID_EDIT_SELPREV, CN_COMMAND, NULL, NULL); |
|
break; |
|
} |
|
|
|
case VK_ESCAPE: |
|
{ |
|
OnEscape(m_pDocument); |
|
break; |
|
} |
|
|
|
case VK_RETURN: |
|
{ |
|
if (IsBoxSelecting()) |
|
{ |
|
SelectInBox(m_pDocument, bShift); |
|
UpdateHandleState(); |
|
} |
|
break; |
|
} |
|
|
|
default: |
|
{ |
|
return false; |
|
} |
|
} |
|
|
|
return true; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Handles key down events in the logical view. |
|
// Input : Per CWnd::OnKeyDown. |
|
// Output : Returns true on success, false on failure. |
|
//----------------------------------------------------------------------------- |
|
bool Selection3D::OnKeyDownLogical(CMapViewLogical *pView, UINT nChar, UINT nRepCnt, UINT nFlags) |
|
{ |
|
bool bShift = ((GetKeyState(VK_SHIFT) & 0x8000) != 0); |
|
bool bAlt = GetKeyState(VK_MENU) < 0; |
|
|
|
/* FIXME |
|
if ( Options.view2d.bNudge && ( nChar == VK_UP || nChar == VK_DOWN || nChar == VK_LEFT || nChar == VK_RIGHT ) ) |
|
{ |
|
if (!IsEmpty()) |
|
{ |
|
NudgeObjects2D(pView, nChar, !bCtrl, bShift); |
|
return true; |
|
} |
|
} |
|
*/ |
|
switch (nChar) |
|
{ |
|
// TODO: do we want this here or in the view? |
|
case VK_DELETE: |
|
m_pDocument->OnCmdMsg(ID_EDIT_DELETE, CN_COMMAND, NULL, NULL); |
|
break; |
|
|
|
case VK_NEXT: |
|
m_pDocument->OnCmdMsg( bAlt ? ID_EDIT_SELNEXT_CASCADING : ID_EDIT_SELNEXT, CN_COMMAND, NULL, NULL); |
|
break; |
|
|
|
case VK_PRIOR: |
|
m_pDocument->OnCmdMsg( bAlt ? ID_EDIT_SELPREV_CASCADING : ID_EDIT_SELPREV, CN_COMMAND, NULL, NULL); |
|
break; |
|
|
|
case VK_ESCAPE: |
|
OnEscape( m_pDocument ); |
|
break; |
|
|
|
case VK_RETURN: |
|
if ( m_bInLogicalBoxSelection ) |
|
{ |
|
EndLogicalBoxSelection( ); |
|
SelectInLogicalBox( m_pDocument, bShift ); |
|
} |
|
break; |
|
|
|
default: |
|
return false; |
|
} |
|
|
|
return true; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Handles left button down events in the 2D view. |
|
// Input : Per CWnd::OnLButtonDown. |
|
// Output : Returns true if the message was handled, false if not. |
|
//----------------------------------------------------------------------------- |
|
bool Selection3D::OnLMouseDown2D(CMapView2D *pView, UINT nFlags, const Vector2D &vPoint) |
|
{ |
|
// First give any selected tool helpers a chance to handle the message. |
|
// Don't hit test against tool helpers when shift is held down |
|
// (beginning a Clone operation). |
|
|
|
if (!(nFlags & MK_SHIFT)) |
|
{ |
|
CBaseTool *pToolHit = GetToolObject( pView, vPoint, true ); |
|
|
|
if (pToolHit) |
|
{ |
|
// There is a tool. Attach the object to the tool and forward |
|
// the message to the tool. |
|
return pToolHit->OnLMouseDown2D(pView, nFlags, vPoint); |
|
} |
|
} |
|
|
|
Tool3D::OnLMouseDown2D(pView, nFlags, vPoint); |
|
|
|
m_bSelected = false; |
|
|
|
if ( IsBoxSelecting() ) |
|
{ |
|
// if we click outside of the current selection box, remove old box |
|
if ( !HitTest(pView, vPoint, true) ) |
|
{ |
|
EndBoxSelection(); |
|
} |
|
} |
|
|
|
if (nFlags & MK_CONTROL) |
|
{ |
|
// add object under cursor to selection |
|
m_bSelected = pView->SelectAt(vPoint, false, false); |
|
UpdateHandleState(); |
|
} |
|
else if ( IsEmpty() || !HitTest(pView,vPoint, true) ) |
|
{ |
|
// start new selection |
|
m_TranslateMode = modeScale; |
|
m_bSelected = pView->SelectAt(vPoint, true, false); |
|
UpdateHandleState(); |
|
} |
|
|
|
return true; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Returns the constraints flags for the translation. |
|
// Input : bDisableSnap - |
|
// nKeyFlags - |
|
//----------------------------------------------------------------------------- |
|
unsigned int Selection3D::GetConstraints(unsigned int nKeyFlags) |
|
{ |
|
unsigned int uConstraints = Tool3D::GetConstraints( nKeyFlags ); |
|
|
|
if ( m_TranslateMode==modeRotate ) |
|
{ |
|
// backwards capability, SHIFT turns snapping off during rotation |
|
if ( (nKeyFlags & MK_SHIFT) || !Options.view2d.bRotateConstrain ) |
|
{ |
|
uConstraints = 0; |
|
} |
|
} |
|
|
|
if ( uConstraints & constrainSnap ) |
|
{ |
|
if ( m_pSelection->GetCount() == 1) |
|
{ |
|
CMapClass *pObject = m_pSelection->GetList()->Element(0); |
|
|
|
if (pObject->ShouldSnapToHalfGrid()) |
|
{ |
|
uConstraints |= constrainHalfSnap; |
|
} |
|
} |
|
} |
|
|
|
return uConstraints; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Handles mouse move events in the 2D view. |
|
// Input : Per CWnd::OnMouseMove. |
|
// Output : Returns true if the message was handled, false if not. |
|
//----------------------------------------------------------------------------- |
|
bool Selection3D::OnMouseMove2D(CMapView2D *pView, UINT nFlags, const Vector2D &vPoint) |
|
{ |
|
Tool3D::OnMouseMove2D(pView, nFlags, vPoint); |
|
|
|
bool IsEditable = m_pSelection->IsEditable(); |
|
|
|
vgui::HCursor hCursor = vgui::dc_arrow; |
|
|
|
bool bCtrl = (GetAsyncKeyState(VK_CONTROL) & 0x8000); |
|
unsigned int uConstraints = GetConstraints( nFlags); |
|
|
|
// Convert to world coords. |
|
|
|
Vector vecWorld; |
|
pView->ClientToWorld(vecWorld, vPoint); |
|
|
|
// |
|
// Update status bar position display. |
|
// |
|
char szBuf[128]; |
|
sprintf(szBuf, " @%.0f, %.0f ", vecWorld[pView->axHorz], vecWorld[pView->axVert]); |
|
SetStatusText(SBI_COORDS, szBuf); |
|
|
|
// |
|
// If we are currently dragging the selection (moving, scaling, rotating, or shearing) |
|
// update that operation based on the current cursor position and keyboard state. |
|
// |
|
if ( IsTranslating() ) |
|
{ |
|
Tool3D::UpdateTranslation( pView, vPoint, uConstraints ); |
|
|
|
hCursor = vgui::dc_none; |
|
} |
|
// |
|
// Else if we have just started dragging the selection, begin a new translation |
|
// |
|
else if ( m_bMouseDragged[MOUSE_LEFT] ) |
|
{ |
|
pView->SetCapture(); |
|
|
|
if ( IsEditable && !bCtrl && HitTest( pView, m_vMouseStart[MOUSE_LEFT], true) ) |
|
{ |
|
// we selected a handle - start translation the selection |
|
StartTranslation( pView, m_vMouseStart[MOUSE_LEFT], m_LastHitTestHandle ); |
|
|
|
hCursor = UpdateCursor( pView, m_LastHitTestHandle, m_TranslateMode ); |
|
} |
|
else if ( !m_bSelected ) |
|
{ |
|
// start new box selection if we didnt select an addition object |
|
Vector ptOrg; |
|
pView->ClientToWorld(ptOrg, m_vMouseStart[MOUSE_LEFT] ); |
|
|
|
// set best third axis value |
|
ptOrg[pView->axThird] = COORD_NOTINIT; |
|
m_pDocument->GetBestVisiblePoint(ptOrg); |
|
|
|
if ( uConstraints & constrainSnap ) |
|
m_pDocument->Snap(ptOrg,uConstraints); |
|
|
|
StartBoxSelection( pView, m_vMouseStart[MOUSE_LEFT], ptOrg ); |
|
EnableHandles(true); |
|
} |
|
} |
|
else if (!IsEmpty()) |
|
{ |
|
//DBG("(%d) OnMouseMove2D: Selection NOT empty, update cursor\n", _nMouseMove); |
|
|
|
// |
|
// Just in case the selection set is not empty and "selection" hasn't received a left mouse click. |
|
// (NOTE: this is gross, but unfortunately necessary (cab)) |
|
// |
|
UpdateHandleState(); |
|
|
|
// |
|
// If the cursor is on a handle, the cursor will be set by the HitTest code. |
|
// |
|
bool bFoundTool = false; |
|
|
|
if ( GetToolObject( pView, vPoint, false ) ) |
|
{ |
|
// If they moused over an interactive handle, it should have set the cursor. |
|
hCursor = vgui::dc_crosshair; |
|
bFoundTool = true; |
|
} |
|
|
|
// If we haven't moused over any interactive handles contained in the object, see if the |
|
// mouse is over one of the selection handles. |
|
if ( IsEditable && !bFoundTool && HitTest(pView, vPoint, true) ) |
|
{ |
|
hCursor = UpdateCursor( pView, m_LastHitTestHandle, m_TranslateMode ); |
|
} |
|
} |
|
|
|
if ( hCursor != vgui::dc_none ) |
|
{ |
|
pView->SetCursor( hCursor ); |
|
} |
|
|
|
return true; |
|
} |
|
|
|
void Selection3D::FinishTranslation(bool bSave, bool bClone ) |
|
{ |
|
const CMapObjectList *pSelList = m_pSelection->GetList(); |
|
|
|
// keep copy of current objects? |
|
if ( bClone && (GetTranslateMode() == modeMove)) |
|
{ |
|
GetHistory()->MarkUndoPosition(pSelList, "Clone Objects"); |
|
m_pDocument->CloneObjects(*pSelList); |
|
GetHistory()->KeepNew(pSelList); |
|
} |
|
else |
|
{ |
|
GetHistory()->MarkUndoPosition(pSelList, "Translation"); |
|
GetHistory()->Keep(pSelList); |
|
} |
|
|
|
if ( bSave ) |
|
{ |
|
// transform selected objects |
|
TransformSelection(); |
|
} |
|
|
|
// finish the tool translation |
|
Box3D::FinishTranslation( bSave ); |
|
|
|
if ( bSave ) |
|
{ |
|
// update selection bounds |
|
UpdateSelectionBounds(); |
|
NotifyDuplicates(pSelList); |
|
} |
|
|
|
m_pSelection->SetSelectionState( SELECT_NORMAL ); |
|
} |
|
|
|
void Selection3D::StartTranslation(CMapView *pView, const Vector2D &vPoint, const Vector &vHandleOrigin ) |
|
{ |
|
Vector refPoint; |
|
Vector *pRefPoint = NULL; |
|
|
|
// use single object origin as translation origin |
|
if (m_pSelection->GetCount() == 1) |
|
{ |
|
if ( vHandleOrigin.IsZero() || m_TranslateMode == modeRotate ) |
|
{ |
|
CMapEntity *pObject = (CMapEntity *)m_pSelection->GetList()->Element(0); |
|
|
|
if ( pObject->IsMapClass(MAPCLASS_TYPE(CMapEntity)) && pObject->IsPlaceholder() ) |
|
{ |
|
// set entity origin as translation center |
|
pObject->GetOrigin( refPoint ); |
|
pRefPoint = &refPoint; |
|
} |
|
} |
|
} |
|
|
|
// we selected a handle - start translation the selection |
|
|
|
// If translating, redo our bounds temporarily to use the entity origins rather than their bounds |
|
// so things will stay on the grid correctly. |
|
Vector vCustomHandleBox[2]; |
|
Vector *pCustomHandleBox = NULL; |
|
if ( vHandleOrigin.IsZero() ) |
|
{ |
|
pCustomHandleBox = vCustomHandleBox; |
|
m_pSelection->GetBoundsForTranslation( vCustomHandleBox[0], vCustomHandleBox[1] ); |
|
} |
|
Box3D::StartTranslation( pView, vPoint, vHandleOrigin, pRefPoint, pCustomHandleBox ); |
|
if ( !m_pSelection->IsEmpty() ) |
|
UpdateSelectionBounds(); |
|
|
|
m_pSelection->SetSelectionState( SELECT_MODIFY ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Handles left button up events in the 2D view. |
|
// Input : Per CWnd::OnLButtonUp. |
|
// Output : Returns true if the message was handled, false if not. |
|
//----------------------------------------------------------------------------- |
|
bool Selection3D::OnLMouseUp2D(CMapView2D *pView, UINT nFlags, const Vector2D &vPoint) |
|
{ |
|
bool bShift = nFlags & MK_SHIFT; |
|
|
|
Tool3D::OnLMouseUp2D(pView, nFlags, vPoint); |
|
|
|
bool IsEditable = m_pSelection->IsEditable(); |
|
|
|
if ( IsTranslating() ) |
|
{ |
|
// selecting stuff in box |
|
if ( IsBoxSelecting() ) |
|
{ |
|
Box3D::FinishTranslation(true); |
|
|
|
if (Options.view2d.bAutoSelect) |
|
{ |
|
SelectInBox(m_pDocument, bShift); |
|
UpdateHandleState(); |
|
} |
|
} |
|
else |
|
{ |
|
FinishTranslation( true, bShift ); |
|
} |
|
|
|
} |
|
else if ( !m_bSelected && !m_pSelection->IsEmpty() ) |
|
{ |
|
if ( IsEditable && HitTest(pView, vPoint, false) ) |
|
{ |
|
ToggleTranslateMode(); |
|
|
|
UpdateCursor( pView, m_LastHitTestHandle, m_TranslateMode ); |
|
|
|
m_pDocument->UpdateAllViews( MAPVIEW_UPDATE_TOOL ); |
|
} |
|
} |
|
|
|
// we might have removed some stuff that was relevant: |
|
m_pDocument->UpdateStatusbar(); |
|
|
|
return true; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Handles left button down events in the 2D view. |
|
// Input : Per CWnd::OnLButtonDown. |
|
// Output : Returns true if the message was handled, false if not. |
|
//----------------------------------------------------------------------------- |
|
bool Selection3D::OnLMouseDownLogical(CMapViewLogical *pView, UINT nFlags, const Vector2D &vPoint) |
|
{ |
|
// First give any selected tool helpers a chance to handle the message. |
|
// Don't hit test against tool helpers when shift is held down |
|
// (beginning a Clone operation). |
|
|
|
if (!(nFlags & MK_SHIFT)) |
|
{ |
|
CBaseTool *pToolHit = GetToolObjectLogical( pView, vPoint, true ); |
|
|
|
if (pToolHit) |
|
{ |
|
// There is a tool. Attach the object to the tool and forward |
|
// the message to the tool. |
|
return pToolHit->OnLMouseDownLogical(pView, nFlags, vPoint); |
|
} |
|
} |
|
|
|
m_bLButtonDown = true; |
|
m_vLDownLogicalClient = vPoint; |
|
|
|
pView->SetCapture(); |
|
|
|
m_bLeftDragged = false; |
|
m_bSelected = false; |
|
|
|
if ( m_bInLogicalBoxSelection ) |
|
{ |
|
EndLogicalBoxSelection( ); |
|
} |
|
|
|
// If they weren't alt- or ctrl-clicking and we have a selection, if they clicked |
|
// in the selection rectangle, maintain what we got. |
|
bool bCtrlClick = (nFlags & MK_CONTROL) != 0; |
|
bool bAltClick = GetKeyState(VK_MENU) < 0; |
|
if ( !bAltClick && !bCtrlClick && !IsEmpty() ) |
|
{ |
|
if ( HitTestLogical( pView, vPoint ) ) |
|
return true; |
|
} |
|
|
|
if ( bAltClick ) |
|
{ |
|
m_bSelected = ( pView->SelectAtCascading( vPoint, !bCtrlClick ) == true ); |
|
return true; |
|
} |
|
|
|
m_bSelected = ( pView->SelectAt( vPoint, !bCtrlClick, false ) == true ); |
|
return true; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Handles mouse move events in the 2D visio view. |
|
// Input : Per CWnd::OnMouseMove. |
|
// Output : Returns true if the message was handled, false if not. |
|
//----------------------------------------------------------------------------- |
|
bool Selection3D::OnMouseMoveLogical(CMapViewLogical *pView, UINT nFlags, const Vector2D &vPoint) |
|
{ |
|
if ( m_bLButtonDown ) |
|
{ |
|
if ( !m_bLeftDragged ) |
|
{ |
|
// check if mouse was dragged if button is pressed down |
|
Vector2D sizeDragged = vPoint - m_vLDownLogicalClient; |
|
|
|
if ((abs(sizeDragged.x) > DRAG_THRESHHOLD) || (abs(sizeDragged.y) > DRAG_THRESHHOLD)) |
|
{ |
|
// If here, means we've dragged the mouse |
|
m_bLeftDragged = true; |
|
} |
|
} |
|
|
|
// Make sure the point is visible. |
|
pView->ToolScrollToPoint( vPoint ); |
|
} |
|
|
|
// Convert to world coords. |
|
Vector2D vecWorld; |
|
pView->ClientToWorld( vecWorld, vPoint ); |
|
|
|
// Update status bar position display. |
|
char szBuf[128]; |
|
sprintf(szBuf, " @%.0f, %.0f ", vecWorld.x, vecWorld.y); |
|
SetStatusText( SBI_COORDS, szBuf ); |
|
|
|
// If we are currently dragging the selection (moving) |
|
// update that operation based on the current cursor position and keyboard state. |
|
if ( m_bIsLogicalTranslating ) |
|
{ |
|
Vector2D vecTranslation; |
|
Vector2DSubtract( vecWorld, m_vLastLogicalDragPoint, vecTranslation ); |
|
m_vLastLogicalDragPoint = vecWorld; |
|
m_vLogicalTranslation += vecTranslation; |
|
pView->UpdateView( MAPVIEW_UPDATE_TOOL ); |
|
return true; |
|
} |
|
|
|
if ( m_bInLogicalBoxSelection && (nFlags & MK_LBUTTON) ) |
|
{ |
|
Vector vecStartWorld; |
|
pView->ClientToWorld( vecStartWorld, m_vLDownLogicalClient ); |
|
if ( vecWorld.x < vecStartWorld.x ) |
|
{ |
|
m_vecLogicalSelBoxMins.x = vecWorld.x; |
|
m_vecLogicalSelBoxMaxs.x = vecStartWorld.x; |
|
} |
|
else |
|
{ |
|
m_vecLogicalSelBoxMins.x = vecStartWorld.x; |
|
m_vecLogicalSelBoxMaxs.x = vecWorld.x; |
|
} |
|
if ( vecWorld.y < vecStartWorld.y ) |
|
{ |
|
m_vecLogicalSelBoxMins.y = vecWorld.y; |
|
m_vecLogicalSelBoxMaxs.y = vecStartWorld.y; |
|
} |
|
else |
|
{ |
|
m_vecLogicalSelBoxMins.y = vecStartWorld.y; |
|
m_vecLogicalSelBoxMaxs.y = vecWorld.y; |
|
} |
|
pView->UpdateView( MAPVIEW_UPDATE_TOOL ); |
|
return true; |
|
} |
|
|
|
// If we have just started dragging the selection, begin a new translation |
|
if ( m_bLButtonDown && (nFlags & MK_LBUTTON) && m_bLeftDragged ) |
|
{ |
|
pView->SetCapture(); |
|
|
|
// Check to see if the point at which we started clicking lies within the selection region |
|
if ( HitTestLogical( pView, m_vLDownLogicalClient ) ) |
|
{ |
|
pView->ClientToWorld( m_vLastLogicalDragPoint, m_vLDownLogicalClient ); |
|
m_vLogicalTranslation.Init(); |
|
m_bIsLogicalTranslating = true; |
|
pView->SetCursor( vgui::dc_sizeall ); |
|
m_pSelection->SetSelectionState( SELECT_MODIFY ); |
|
} |
|
else if ( !m_bSelected ) |
|
{ |
|
// We're doing a drag with the mouse down, and nothing is selected. |
|
// Start a logical box selection |
|
Vector ptOrg; |
|
pView->ClientToWorld( ptOrg, m_vLDownLogicalClient ); |
|
StartLogicalBoxSelection( pView, ptOrg ); |
|
} |
|
|
|
return true; |
|
} |
|
|
|
// If we are simply hovering over an object but the mouse isn't down, update the cursor. |
|
vgui::HCursor hCursor = vgui::dc_arrow; |
|
if ( !IsEmpty() ) |
|
{ |
|
// If the cursor is on a handle, the cursor will be set by the HitTest code. |
|
bool bFoundTool = false; |
|
if ( GetToolObjectLogical( pView, vPoint, false ) ) |
|
{ |
|
// If they moused over an interactive handle, it should have set the cursor. |
|
hCursor = vgui::dc_crosshair; |
|
bFoundTool = true; |
|
} |
|
|
|
// If we haven't moused over any interactive handles contained in the object, see if the |
|
// mouse is over one of the selection handles. |
|
if ( !bFoundTool && HitTestLogical(pView, vPoint) ) |
|
{ |
|
hCursor = vgui::dc_sizeall; |
|
} |
|
} |
|
|
|
if ( hCursor != vgui::dc_none ) |
|
{ |
|
pView->SetCursor( hCursor ); |
|
} |
|
return true; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Handles left button up events in the 2D view. |
|
// Input : Per CWnd::OnLButtonUp. |
|
// Output : Returns true if the message was handled, false if not. |
|
//----------------------------------------------------------------------------- |
|
bool Selection3D::OnLMouseUpLogical(CMapViewLogical *pView, UINT nFlags, const Vector2D &vPoint) |
|
{ |
|
bool bShift = ((GetKeyState(VK_SHIFT) & 0x8000) != 0); |
|
|
|
ReleaseCapture(); |
|
m_bLButtonDown = false; |
|
|
|
const CMapObjectList *pSelList = m_pSelection->GetList(); |
|
|
|
// selecting stuff in box |
|
if ( m_bInLogicalBoxSelection ) |
|
{ |
|
if ( Options.view2d.bAutoSelect ) |
|
{ |
|
EndLogicalBoxSelection( ); |
|
SelectInLogicalBox( m_pDocument, bShift ); |
|
} |
|
|
|
m_pSelection->SetSelectionState( SELECT_NORMAL ); |
|
goto updateStatusBar; |
|
} |
|
|
|
if ( m_bIsLogicalTranslating ) |
|
{ |
|
// keep copy of current objects? |
|
if ( nFlags & MK_SHIFT ) |
|
{ |
|
GetHistory()->MarkUndoPosition(pSelList, "Clone Objects"); |
|
m_pDocument->CloneObjects(*pSelList); |
|
GetHistory()->KeepNew(pSelList); |
|
} |
|
else |
|
{ |
|
GetHistory()->MarkUndoPosition(pSelList, "Logical Translation"); |
|
GetHistory()->Keep( pSelList ); |
|
} |
|
|
|
TransformLogicalSelection( m_vLogicalTranslation ); |
|
|
|
// finish the tool translation |
|
m_bIsLogicalTranslating = false; |
|
|
|
// update selection bounds |
|
UpdateSelectionBounds(); |
|
|
|
m_pDocument->SetModifiedFlag(); |
|
|
|
NotifyDuplicates( pSelList ); |
|
|
|
m_pSelection->SetSelectionState( SELECT_NORMAL ); |
|
goto updateStatusBar; |
|
} |
|
|
|
updateStatusBar: |
|
// we might have removed some stuff that was relevant: |
|
m_pDocument->UpdateStatusbar(); |
|
|
|
return true; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Handles key down events in the 3D view. |
|
// Input : Per CWnd::OnKeyDown. |
|
// Output : Returns true if the message was handled, false if not. |
|
//----------------------------------------------------------------------------- |
|
bool Selection3D::OnKeyDown3D(CMapView3D *pView, UINT nChar, UINT nRepCnt, UINT nFlags) |
|
{ |
|
bool bShift = ((GetKeyState(VK_SHIFT) & 0x8000) != 0); |
|
bool bCtrl = ((GetKeyState(VK_CONTROL) & 0x8000) != 0); |
|
|
|
switch (nChar) |
|
{ |
|
/* |
|
dvs: The eyedropper is a somewhat failed experiment, an attempt to create a way to |
|
quickly hook entities together. I think a dedicated connection tool with a more |
|
rubber-band style UI might be more successful. Either that or relegate that work |
|
to a Logical-style view. |
|
case 'e': |
|
case 'E': |
|
{ |
|
m_bEyedropper = !m_bEyedropper; |
|
if (m_bEyedropper) |
|
{ |
|
SetEyedropperCursor(); |
|
} |
|
else |
|
{ |
|
SetCursor(AfxGetApp()->LoadStandardCursor(IDC_ARROW)); |
|
} |
|
return true; |
|
} |
|
*/ |
|
#ifndef SDK_BUILD |
|
case 'x': |
|
case 'X': |
|
{ |
|
m_b3DEditMode = !m_b3DEditMode; |
|
pView->UpdateView( MAPVIEW_UPDATE_TOOL ); |
|
return true; |
|
} |
|
#endif |
|
|
|
case VK_DELETE: |
|
{ |
|
m_pDocument->OnCmdMsg(ID_EDIT_DELETE, CN_COMMAND, NULL, NULL); |
|
return true; |
|
} |
|
|
|
case VK_ESCAPE: |
|
{ |
|
OnEscape(m_pDocument); |
|
return true; |
|
} |
|
} |
|
|
|
if (Options.view2d.bNudge && (nChar == VK_UP || nChar == VK_DOWN || nChar == VK_LEFT || nChar == VK_RIGHT)) |
|
{ |
|
if (!IsEmpty()) |
|
{ |
|
bool bSnap = m_pDocument->IsSnapEnabled() && !bCtrl; |
|
NudgeObjects(pView, nChar, bSnap, bShift); |
|
return true; |
|
} |
|
} |
|
|
|
return false; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Handles double click events in the 3D view. |
|
// Input : Per CWnd::OnLButtonDblClk. |
|
// Output : Returns true if the message was handled, false if not. |
|
//----------------------------------------------------------------------------- |
|
bool Selection3D::OnLMouseDblClk3D(CMapView3D *pView, UINT nFlags, const Vector2D &vPoint) |
|
{ |
|
if ( !m_pSelection->IsEmpty() ) |
|
{ |
|
if ( m_pSelection->GetCount() == 1 ) |
|
{ |
|
CMapClass *pObject = m_pSelection->GetList()->Element( 0 ); |
|
CManifestInstance *pManifestInstance = dynamic_cast< CManifestInstance * >( pObject ); |
|
if ( pManifestInstance ) |
|
{ |
|
CManifest *pManifest = CMapDoc::GetManifest(); |
|
|
|
if ( pManifest ) |
|
{ |
|
pManifest->SetPrimaryMap( pManifestInstance->GetManifestMap() ); |
|
return true; |
|
} |
|
} |
|
} |
|
|
|
GetMainWnd()->pObjectProperties->ShowWindow(SW_SHOW); |
|
} |
|
|
|
return true; |
|
} |
|
|
|
bool Selection3D::OnLMouseDblClkLogical(CMapViewLogical *pView, UINT nFlags, const Vector2D &vPoint) |
|
{ |
|
if ( !m_pSelection->IsEmpty() ) |
|
{ |
|
GetMainWnd()->pObjectProperties->ShowWindow(SW_SHOW); |
|
} |
|
return true; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
// Input : pView - |
|
// point - |
|
//----------------------------------------------------------------------------- |
|
void Selection3D::EyedropperPick2D(CMapView2D *pView, const Vector2D &vPoint) |
|
{ |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
// Input : *pView - |
|
// point - |
|
//----------------------------------------------------------------------------- |
|
void Selection3D::EyedropperPick3D(CMapView3D *pView, const Vector2D &vPoint) |
|
{ |
|
// |
|
// We only want to do this if we have at least one entity selected. |
|
// |
|
if ( !m_pSelection->IsAnEntitySelected() ) |
|
{ |
|
MessageBox( NULL, "No entities are selected, so the eyedropper has nothing to assign to.", "No selected entities", MB_OK); |
|
return; |
|
} |
|
|
|
// |
|
// If they clicked on an entity, get the name of the entity they clicked on. |
|
// |
|
ULONG ulFace; |
|
CMapClass *pClickObject = pView->NearestObjectAt( vPoint, ulFace); |
|
if (pClickObject != NULL) |
|
{ |
|
EyedropperPick(pView, pClickObject); |
|
} |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
// Input : pObject - |
|
//----------------------------------------------------------------------------- |
|
void Selection3D::EyedropperPick(CMapView *pView, CMapClass *pObject) |
|
{ |
|
// |
|
// The eyedropper currently only supports clicking on entities. |
|
// TODO: consider using this to fill out face lists if they click on a solid |
|
// |
|
CMapEntity *pEntity = FindEntityInTree(pObject); |
|
if (pEntity == NULL) |
|
{ |
|
// They clicked on something that is not an entity. |
|
return; |
|
} |
|
|
|
// |
|
// Get the name of the clicked on entity. |
|
// |
|
const char *pszClickName = NULL; |
|
pszClickName = pEntity->GetKeyValue("targetname"); |
|
if (pszClickName == NULL) |
|
{ |
|
// |
|
// They clicked on an entity with no name. |
|
// |
|
MessageBox( NULL, "The chosen entity has no name.", "No name to pick", MB_OK ); |
|
return; |
|
} |
|
|
|
// |
|
// Build a list of all the keyvalues in the selected entities that support the eyedropper. |
|
// |
|
CUtlVector<GDinputvariable *> VarList; |
|
|
|
int nEntityCount = 0; |
|
|
|
const CMapObjectList *pSelList = m_pSelection->GetList(); |
|
|
|
for (int i = 0; i < pSelList->Count(); i++) |
|
{ |
|
pObject = pSelList->Element(i); |
|
pEntity = dynamic_cast <CMapEntity *> (pObject); |
|
if (pEntity != NULL) |
|
{ |
|
nEntityCount++; |
|
GDclass *pClass = pEntity->GetClass(); |
|
|
|
int nVarCount = pClass->GetVariableCount(); |
|
for (int nVar = 0; nVar < nVarCount; nVar++) |
|
{ |
|
GDinputvariable *pVar = pClass->GetVariableAt(nVar); |
|
if (pVar && ((pVar->GetType() == ivTargetDest) || (pVar->GetType() == ivTargetNameOrClass))) |
|
{ |
|
VarList.AddToTail(pVar); |
|
} |
|
} |
|
} |
|
} |
|
|
|
// |
|
// Prompt for what keyvalue in the selected entities we are filling out. |
|
// |
|
int nCount = VarList.Count(); |
|
if (nCount <= 0) |
|
{ |
|
// |
|
// No selected entities have keys of the appropriate type, so there's nothing we can do. |
|
// |
|
MessageBox( NULL, "No selected entities have keyvalues that accept an entity name, so the eyedropper has nothing to assign to.", "No eligible keyvalues", MB_OK ); |
|
return; |
|
} |
|
|
|
// |
|
// Determine the name of the key that we are filling out. |
|
// |
|
GDinputvariable *pVar = ChooseEyedropperVar(pView, VarList); |
|
if (!pVar) |
|
{ |
|
return; |
|
} |
|
const char *pszVarName = pVar->GetName(); |
|
if (!pszVarName) |
|
{ |
|
return; |
|
} |
|
|
|
GetHistory()->MarkUndoPosition( pSelList, "Set Keyvalue"); |
|
|
|
// |
|
// Apply the key to all selected entities with the chosen keyvalue. |
|
// |
|
for (int i = 0; i < pSelList->Count(); i++) |
|
{ |
|
pObject = pSelList->Element(i); |
|
|
|
pEntity = dynamic_cast <CMapEntity *> (pObject); |
|
if (pEntity != NULL) |
|
{ |
|
GDclass *pClass = pEntity->GetClass(); |
|
pVar = pClass->VarForName(pszVarName); |
|
if (pVar && ((pVar->GetType() == ivTargetDest) || (pVar->GetType() == ivTargetNameOrClass))) |
|
{ |
|
GetHistory()->Keep(pEntity); |
|
pEntity->SetKeyValue(pszVarName, pszClickName); |
|
} |
|
} |
|
} |
|
|
|
CMapDoc *pDoc = pView->GetMapDoc(); |
|
if (pDoc != NULL) |
|
{ |
|
pDoc->SetModifiedFlag(); |
|
} |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Returns the nearest CMapEntity object up the hierarchy from the |
|
// given object. |
|
// Input : pObject - Object to start from. |
|
//----------------------------------------------------------------------------- |
|
CMapEntity *Selection3D::FindEntityInTree(CMapClass *pObject) |
|
{ |
|
do |
|
{ |
|
CMapEntity *pEntity = dynamic_cast <CMapEntity *> (pObject); |
|
if (pEntity != NULL) |
|
{ |
|
return pEntity; |
|
} |
|
|
|
pObject = pObject->GetParent(); |
|
|
|
} while (pObject != NULL); |
|
|
|
// No entity in this branch of the object tree. |
|
return NULL; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Handles left button down events in the 3D view. |
|
// Input : Per CWnd::OnLButtonDown. |
|
// Output : Returns true if the message was handled, false if not. |
|
//----------------------------------------------------------------------------- |
|
bool Selection3D::OnLMouseDown3D(CMapView3D *pView, UINT nFlags, const Vector2D &vPoint) |
|
{ |
|
Tool3D::OnLMouseDown3D(pView, nFlags, vPoint); |
|
|
|
m_bSelected = false; |
|
|
|
// |
|
// If they are holding down the eyedropper hotkey, do an eyedropper pick. The eyedropper fills out |
|
// keyvalues in selected entities based on the object they clicked on. |
|
// |
|
if (m_bEyedropper) |
|
{ |
|
EyedropperPick3D(pView, vPoint); |
|
m_bEyedropper = false; |
|
SetCursor(AfxGetApp()->LoadStandardCursor(IDC_ARROW)); |
|
return true; |
|
} |
|
|
|
if (nFlags & MK_CONTROL) |
|
{ |
|
m_bSelected = pView->SelectAt(vPoint, false, false);; |
|
UpdateHandleState(); |
|
} |
|
else if ( m_b3DEditMode && HitTest(pView,vPoint, true) ) |
|
{ |
|
// if clicked on handles, never change selection |
|
if ( !IsBoxSelecting() && m_LastHitTestHandle == vec3_origin ) |
|
{ |
|
// clicked somewhere on our selection tool but maybe something else is inbetween |
|
|
|
HitInfo_t HitData; |
|
|
|
m_bDrawAsSolidBox = true; |
|
|
|
pView->ObjectsAt( vPoint, &HitData, 1 ); |
|
|
|
if ( HitData.pObject && !HitData.pObject->IsSelected() ) |
|
{ |
|
m_bSelected = pView->SelectAt(vPoint, true, false); |
|
UpdateHandleState(); |
|
} |
|
|
|
m_bDrawAsSolidBox = false; |
|
|
|
pView->SetCursor( UpdateCursor( pView, m_LastHitTestHandle, m_TranslateMode ) ); |
|
} |
|
} |
|
else |
|
{ |
|
m_TranslateMode = modeScale; |
|
m_bSelected = pView->SelectAt(vPoint, true, false); |
|
UpdateHandleState(); |
|
} |
|
|
|
if ( m_bSelected && !m_b3DEditMode ) |
|
{ |
|
pView->BeginPick(); |
|
} |
|
|
|
return true; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Handles left button up events in the 3D view. |
|
// Input : Per CWnd::OnLButtonUp. |
|
// Output : Returns true if the message was handled, false if not. |
|
//----------------------------------------------------------------------------- |
|
bool Selection3D::OnLMouseUp3D(CMapView3D *pView, UINT nFlags, const Vector2D &vPoint) |
|
{ |
|
bool bShift = nFlags & MK_SHIFT; |
|
|
|
Tool3D::OnLMouseUp3D(pView, nFlags, vPoint) ; |
|
|
|
bool IsEditable = m_pSelection->IsEditable(); |
|
|
|
if ( IsTranslating() ) |
|
{ |
|
// selecting stuff in box |
|
if ( IsBoxSelecting() ) |
|
{ |
|
Box3D::FinishTranslation(true); |
|
|
|
if (Options.view2d.bAutoSelect) |
|
{ |
|
SelectInBox(m_pDocument, bShift); |
|
UpdateHandleState(); |
|
} |
|
} |
|
else |
|
{ |
|
FinishTranslation( true, bShift ); |
|
} |
|
} |
|
else if ( m_b3DEditMode && !m_bSelected && !m_pSelection->IsEmpty() ) |
|
{ |
|
if ( IsEditable && HitTest(pView, vPoint, false) ) |
|
{ |
|
ToggleTranslateMode(); |
|
|
|
UpdateCursor( pView, m_LastHitTestHandle, m_TranslateMode ); |
|
|
|
m_pDocument->UpdateAllViews( MAPVIEW_UPDATE_TOOL ); |
|
} |
|
} |
|
|
|
pView->EndPick(); |
|
|
|
// we might have removed some stuff that was relevant: |
|
m_pDocument->UpdateStatusbar(); |
|
|
|
return true; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Handles mouse move events in the 3D view. |
|
// Input : Per CWnd::OnMouseMove. |
|
// Output : Returns true if the message was handled, false if not. |
|
//----------------------------------------------------------------------------- |
|
bool Selection3D::OnMouseMove3D(CMapView3D *pView, UINT nFlags, const Vector2D &vPoint) |
|
{ |
|
Tool3D::OnMouseMove3D(pView, nFlags, vPoint); |
|
|
|
bool IsEditable = m_pSelection->IsEditable(); |
|
|
|
vgui::HCursor hCursor = vgui::dc_arrow; |
|
|
|
if ( m_bEyedropper ) |
|
{ |
|
SetEyedropperCursor(); |
|
} |
|
// |
|
// If we are currently dragging the selection (moving, scaling, rotating, or shearing) |
|
// update that operation based on the current cursor position and keyboard state. |
|
// |
|
|
|
else if ( IsTranslating() ) |
|
{ |
|
unsigned int uConstraints = GetConstraints(nFlags); |
|
|
|
// |
|
// If they are dragging with a valid handle, update the views. |
|
// |
|
|
|
Tool3D::UpdateTranslation( pView, vPoint, uConstraints ); |
|
|
|
hCursor = vgui::dc_none; |
|
} |
|
// |
|
// Else if we have just started dragging the selection, begin a new translation |
|
// |
|
else if ( m_b3DEditMode && m_bMouseDragged[MOUSE_LEFT] ) |
|
{ |
|
if ( IsEditable && HitTest( pView, m_vMouseStart[MOUSE_LEFT], true) ) |
|
{ |
|
// we selected a handle - start translation the selection |
|
StartTranslation( pView, vPoint, m_LastHitTestHandle ); |
|
|
|
hCursor = UpdateCursor( pView, m_LastHitTestHandle, m_TranslateMode ); |
|
} |
|
} |
|
else if ( IsEditable && m_b3DEditMode && !IsEmpty() ) |
|
{ |
|
UpdateHandleState(); |
|
|
|
if ( HitTest(pView, vPoint, true) ) |
|
{ |
|
hCursor = UpdateCursor( pView, m_LastHitTestHandle, m_TranslateMode ); |
|
} |
|
} |
|
|
|
if ( hCursor != vgui::dc_none ) |
|
{ |
|
pView->SetCursor( hCursor ); |
|
} |
|
|
|
return true; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Sets the cursor to the eyedropper cursor. |
|
//----------------------------------------------------------------------------- |
|
void Selection3D::SetEyedropperCursor(void) |
|
{ |
|
static HCURSOR hcurEyedropper = NULL; |
|
|
|
if (!hcurEyedropper) |
|
{ |
|
hcurEyedropper = LoadCursor(AfxGetInstanceHandle(), MAKEINTRESOURCE(IDC_EYEDROPPER)); |
|
} |
|
|
|
SetCursor(hcurEyedropper); |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Handles the escape key in the 2D or 3D views. |
|
//----------------------------------------------------------------------------- |
|
void Selection3D::OnEscape(CMapDoc *pDoc) |
|
{ |
|
// |
|
// If we're in eyedropper mode, go back to selection mode. |
|
// |
|
if (m_bEyedropper) |
|
{ |
|
m_bEyedropper = false; |
|
SetCursor(AfxGetApp()->LoadStandardCursor(IDC_ARROW)); |
|
} |
|
// |
|
// If we're box selecting, clear the box. |
|
// |
|
else if (IsBoxSelecting()) |
|
{ |
|
EndBoxSelection(); |
|
UpdateSelectionBounds(); |
|
} |
|
// |
|
// If we're logical box selecting, clear the box. |
|
// |
|
else if ( m_bInLogicalBoxSelection ) |
|
{ |
|
EndLogicalBoxSelection(); |
|
} |
|
// |
|
// If we're moving a brush, put it back. |
|
// |
|
else if (IsTranslating()) |
|
{ |
|
FinishTranslation(false,false); |
|
} |
|
// |
|
// If we have a selection, deselect it. |
|
// |
|
else if (!IsEmpty()) |
|
{ |
|
SetEmpty(); |
|
} |
|
} |
|
|
|
|