diff options
Diffstat (limited to 'hammer/ToolSelection.cpp')
| -rw-r--r-- | hammer/ToolSelection.cpp | 1984 |
1 files changed, 1984 insertions, 0 deletions
diff --git a/hammer/ToolSelection.cpp b/hammer/ToolSelection.cpp new file mode 100644 index 0000000..f53fc9c --- /dev/null +++ b/hammer/ToolSelection.cpp @@ -0,0 +1,1984 @@ +//========= 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(); + } +} + |