www.pudn.com > glzoom.zip > GLTESTView.cpp
// GLView.cpp : implementation of the CGLTESTView class // #include "stdafx.h" #include "GLTest.h" #include "GLTestDoc.h" #include "GLTestView.h" #include "mainfrm.h" #include#ifdef _DEBUG #define new DEBUG_NEW #undef THIS_FILE static char THIS_FILE[] = __FILE__; #endif // add support for OpenGL 1.1 if we're using an old header // These are new PIXELFORMATDESCRIPTOR flags for OpenGL 1.1 #ifndef PFD_GENERIC_ACCELERATED #define PFD_GENERIC_ACCELERATED 0x00001000 #endif #ifndef PFD_DEPTH_DONTCARE #define PFD_DEPTH_DONTCARE 0x20000000 #endif #define INSTALLABLE_DRIVER_TYPE_MASK (PFD_GENERIC_ACCELERATED|PFD_GENERIC_FORMAT) ///////////////////////////////////////////////////////////////////////////// // CGLTESTView IMPLEMENT_DYNCREATE(CGLTESTView, CView) BEGIN_MESSAGE_MAP(CGLTESTView, CView) //{{AFX_MSG_MAP(CGLTESTView) ON_WM_SIZE() ON_WM_LBUTTONDOWN() ON_WM_MOUSEMOVE() ON_COMMAND(ID_ZOOM_WINDOW, OnZoomWindow) ON_WM_SETCURSOR() ON_WM_ERASEBKGND() ON_COMMAND(ID_ZOOM_OUT, OnZoomOut) ON_COMMAND(ID_ZOOM_IN, OnZoomIn) ON_COMMAND(ID_ZOOM_PREVIOUS, OnZoomPrevious) ON_COMMAND(ID_PAN, OnPan) ON_WM_CREATE() ON_WM_DESTROY() ON_WM_KEYDOWN() ON_WM_MOVE() ON_WM_SETFOCUS() ON_WM_KILLFOCUS() ON_UPDATE_COMMAND_UI(ID_ZOOM_WINDOW, OnUpdateZoomWindow) ON_UPDATE_COMMAND_UI(ID_ZOOM_PREVIOUS, OnUpdateZoomPrevious) //}}AFX_MSG_MAP // Standard printing commands ON_COMMAND(ID_FILE_PRINT, CView::OnFilePrint) ON_COMMAND(ID_FILE_PRINT_DIRECT, CView::OnFilePrint) ON_COMMAND(ID_FILE_PRINT_PREVIEW, CView::OnFilePrintPreview) END_MESSAGE_MAP() ///////////////////////////////////////////////////////////////////////////// // CGLTESTView construction/destruction CGLTESTView::CGLTESTView() { m_StartPoint.east = m_StartPoint.north = m_StartPoint.elev = 0.0; m_EndPoint.east = m_EndPoint.north = m_EndPoint.elev = 0.0; m_MovePoint.east = m_MovePoint.north = m_MovePoint.elev = 0.0; currTool == -1; m_FirstPickPoint = FALSE; xOrg = -1000; yOrg = -1000; xMax = 1000; yMax = 1000; zMax = 500; m_CursorPosition.x = -1; m_CursorPosition.y = -1; m_CanZoomPrev = FALSE; } CGLTESTView::~CGLTESTView() { } BOOL CGLTESTView::PreCreateWindow(CREATESTRUCT& cs) { cs.style |= WS_CLIPSIBLINGS | WS_CLIPCHILDREN; return CView::PreCreateWindow(cs); } ///////////////////////////////////////////////////////////////////////////// // CGLTESTView drawing void CGLTESTView::OnDraw(CDC* pDC) { HDC hdc; HGLRC rc; CGLTESTView::GetCurrent(hdc, rc);//Store current rendering and device contexts MakeActive();//Make view's rendering context current m_CursorPosition.x = -1; m_CursorPosition.y = -1; CView::OnDraw(pDC); ::glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT ); ::glPushMatrix(); RenderScene(); ::glPopMatrix(); // Tell OpenGL to flush its pipeline ::glFinish(); // Now Swap the buffers if ( FALSE == ::SwapBuffers(m_pDC->GetSafeHdc())) return; SetLimits(); CGLTESTView::SetCurrent(hdc, rc);//Restore last rendering and device contexts } ///////////////////////////////////////////////////////////////////////////// // CGLTESTView printing BOOL CGLTESTView::OnPreparePrinting(CPrintInfo* pInfo) { // default preparation return DoPreparePrinting(pInfo); } void CGLTESTView::OnBeginPrinting(CDC* /*pDC*/, CPrintInfo* /*pInfo*/) { // TODO: add extra initialization before printing } void CGLTESTView::OnEndPrinting(CDC* /*pDC*/, CPrintInfo* /*pInfo*/) { // TODO: add cleanup after printing } ///////////////////////////////////////////////////////////////////////////// // CGLTESTView diagnostics #ifdef _DEBUG void CGLTESTView::AssertValid() const { CView::AssertValid(); } void CGLTESTView::Dump(CDumpContext& dc) const { CView::Dump(dc); } CGLTESTDoc* CGLTESTView::GetDocument() // non-debug version is inline { ASSERT(m_pDocument->IsKindOf(RUNTIME_CLASS(CGLTESTDoc))); return (CGLTESTDoc*)m_pDocument; } #endif //_DEBUG ///////////////////////////////////////////////////////////////////////////// // CGLTESTView message handlers void CGLTESTView::OnSize(UINT nType, int cx, int cy) { CView::OnSize(nType, cx, cy); SetupOrtho(cx, cy); } BOOL CGLTESTView::RenderScene() { ::glColor3d(0.0, 0.0, 1.0); ::glBegin(GL_LINE_LOOP); { ::glVertex3d(-500.0,-500.0, 0.0); ::glVertex3d(-500.0,500.0, 0.0); ::glVertex3d(500.0,500.0, 0.0); ::glVertex3d(500.0,-500.0, 0.0); } ::glEnd(); COLOR_STRUCT color; color.red = 1.0f; color.green = 0.0f; color.blue = 0.0f; VERTEX circle_vertex; circle_vertex.east = 0.0; circle_vertex.north = 0.0; circle_vertex.elev = 0.0; CCircleEntity circle(&color, &circle_vertex, 500); circle.Draw(); return TRUE; } void CGLTESTView::OnLButtonDown(UINT nFlags, CPoint point) { CView::OnLButtonDown(nFlags, point); GLdouble modelMatrix[16]; GLdouble projMatrix[16]; GLint viewport[4]; GLdouble objx, objy, objz; glGetDoublev(GL_MODELVIEW_MATRIX, modelMatrix); glGetDoublev(GL_PROJECTION_MATRIX, projMatrix); glGetIntegerv(GL_VIEWPORT, viewport); CRect rect; GetClientRect(&rect); int y = rect.Height() - point.y; gluUnProject(point.x, y, 0, modelMatrix, projMatrix, viewport, &objx, &objy, &objz); if(!m_FirstPickPoint) { switch(currTool) { case ID_ZOOM_WINDOW: { m_EndPoint.east = m_StartPoint.east = objx; m_EndPoint.north = m_StartPoint.north = objy; m_EndPoint.elev = m_StartPoint.elev = objz; m_FirstPickPoint = TRUE; m_StartPosition.x = m_CursorPosition.x = point.x; m_StartPosition.y = m_CursorPosition.y = point.y; DrawZoomRect(); break; } case ID_PAN: { m_EndPoint.east = m_StartPoint.east = objx; m_EndPoint.north = m_StartPoint.north = objy; m_EndPoint.elev = m_StartPoint.elev = objz; m_FirstPickPoint = TRUE; m_StartPosition.x = m_CursorPosition.x = point.x; m_StartPosition.y = m_CursorPosition.y = point.y; DrawRubberBand(); break; } } } else { switch(currTool) { case ID_ZOOM_WINDOW: { m_PrevZoomLeft.east = xOrg; m_PrevZoomLeft.north = yOrg; m_PrevZoomLeft.elev = -zMax; m_PrevZoomRight.east = xMax; m_PrevZoomRight.north = yMax; m_PrevZoomRight.elev = zMax; m_CursorPosition.x = point.x; m_CursorPosition.y = point.y; DrawZoomRect(); m_EndPoint.east = objx; m_EndPoint.north = objy; m_EndPoint.elev = objz; xOrg = min(m_StartPoint.east, m_EndPoint.east); xMax = max(m_StartPoint.east, m_EndPoint.east); yOrg = min(m_StartPoint.north, m_EndPoint.north); yMax = max(m_StartPoint.north, m_EndPoint.north); currTool = -1; m_FirstPickPoint = FALSE; SetupOrtho(); Invalidate(); m_CanZoomPrev = TRUE; break; } case ID_PAN: { m_PrevZoomLeft.east = xOrg; m_PrevZoomLeft.north = yOrg; m_PrevZoomLeft.elev = -zMax; m_PrevZoomRight.east = xMax; m_PrevZoomRight.north = yMax; m_PrevZoomRight.elev = zMax; m_CursorPosition.x = point.x; m_CursorPosition.y = point.y; DrawRubberBand(); m_EndPoint.east = objx; m_EndPoint.north = objy; m_EndPoint.elev = objz; GLdouble DE = m_EndPoint.east - m_StartPoint.east; GLdouble DN = m_EndPoint.north - m_StartPoint.north; xOrg -= DE; xMax -= DE; yOrg -= DN; yMax -= DN; currTool = -1; m_FirstPickPoint = FALSE; SetupOrtho(); Invalidate(); m_CanZoomPrev = TRUE; break; } default:break; } } } void CGLTESTView::OnMouseMove(UINT nFlags, CPoint point) { CView::OnMouseMove(nFlags, point); CPoint temp(point); ClientToScreen(&temp); CMainFrame* pMainWnd = (CMainFrame*)AfxGetMainWnd(); if(currTool == ID_ZOOM_WINDOW) if(m_FirstPickPoint) DrawZoomRect(); if(currTool == ID_PAN) if(m_FirstPickPoint) DrawRubberBand(); m_CursorPosition.x = point.x; m_CursorPosition.y = point.y; GLdouble modelMatrix[16]; GLdouble projMatrix[16]; GLint viewport[4]; GLdouble objx, objy, objz; glGetDoublev(GL_MODELVIEW_MATRIX, modelMatrix); glGetDoublev(GL_PROJECTION_MATRIX, projMatrix); glGetIntegerv(GL_VIEWPORT, viewport); CRect rect; GetClientRect(&rect); point.y = rect.Height() - point.y; gluUnProject(point.x, point.y, 0, modelMatrix, projMatrix, viewport, &objx, &objy, &objz); m_EndPoint.east = m_MovePoint.east = objx; m_EndPoint.north = m_MovePoint.north = objy; m_EndPoint.elev = m_MovePoint.elev = objz; if(currTool == ID_ZOOM_WINDOW) if(m_FirstPickPoint) DrawZoomRect(); if(currTool == ID_PAN) if(m_FirstPickPoint) DrawRubberBand(); } void CGLTESTView::OnZoomWindow() { currTool = ID_ZOOM_WINDOW; m_FirstPickPoint = FALSE; } void CGLTESTView::SetupOrtho(int cx, int cy) { if ( 0 >= cx || 0 >= cy ) return; GLdouble dx = xMax - xOrg; GLdouble dy = yMax - yOrg; SetupViewport( cx, cy ); // OK, now save the dimensions of the window, cutting the width in half m_width = cx; m_height = cy; // Now that the dimensions are set up, we can set up the projection // matrix. Since we've overridden OnSize(), we need to do it ourselves // select the viewing volumn ::glMatrixMode ( GL_PROJECTION ); ::glLoadIdentity (); if(fabs(dx) > 0.001 || fabs(dy) > 0.001) { if(dx > dy) { GLdouble dY = dx * m_height / m_width; GLdouble yMax = yOrg + dY; ::glOrtho( xOrg, xMax, yOrg, yMax, -zMax, zMax); } else { GLdouble dX = dy * m_width / m_height; GLdouble xMax = xOrg + dX; ::glOrtho( xOrg, xMax, yOrg, yMax, -zMax, zMax); } } // switch back to the modelview matrix and clear it ::glMatrixMode( GL_MODELVIEW ); ::glLoadIdentity(); } void CGLTESTView::OnInitialUpdate() { CView::OnInitialUpdate(); } void CGLTESTView::SetupOrtho() { // Now that the dimensions are set up, we can set up the projection // matrix. Since we've overridden OnSize(), we need to do it ourselves CRect rect; GetClientRect(&rect); // OK, now save the dimensions of the window, cutting the width in half m_width = rect.Width(); m_height = rect.Height(); SetupOrtho(rect.Width(), rect.Height()); } BOOL CGLTESTView::OnSetCursor(CWnd* pWnd, UINT nHitTest, UINT message) { return CView::OnSetCursor(pWnd, nHitTest, message); } BOOL CGLTESTView::OnEraseBkgnd(CDC* pDC) { return TRUE; // tell Windows not to erase the background } void CGLTESTView::OnZoomOut() { m_PrevZoomLeft.east = xOrg; m_PrevZoomLeft.north = yOrg; m_PrevZoomLeft.elev = -zMax; m_PrevZoomRight.east = xMax; m_PrevZoomRight.north = yMax; m_PrevZoomRight.elev = zMax; m_CanZoomPrev = TRUE; GLdouble width = xMax - xOrg; GLdouble height = yMax - yOrg; xOrg = xOrg - width/2; xMax = xMax + width/2; yOrg = yOrg - width/2; yMax = yMax + width/2; SetupOrtho(); Invalidate(); } void CGLTESTView::OnZoomIn() { m_PrevZoomLeft.east = xOrg; m_PrevZoomLeft.north = yOrg; m_PrevZoomLeft.elev = -zMax; m_PrevZoomRight.east = xMax; m_PrevZoomRight.north = yMax; m_PrevZoomRight.elev = zMax; m_CanZoomPrev = TRUE; GLdouble width = xMax - xOrg; GLdouble height = yMax - yOrg; xOrg = xOrg + width/4; xMax = xMax - width/4; yOrg = yOrg + width/4; yMax = yMax - width/4; SetupOrtho(); Invalidate(); } void CGLTESTView::OnZoomPrevious() { VERTEX temp1, temp2; temp1.east = xOrg; temp1.north = yOrg; temp1.elev = -zMax; temp2.east = xMax; temp2.north = yMax; temp2.elev = zMax; xOrg = m_PrevZoomLeft.east; yOrg = m_PrevZoomLeft.north; xMax = m_PrevZoomRight.east; yMax = m_PrevZoomRight.north; zMax = m_PrevZoomRight.elev; SetupOrtho(); Invalidate(); memcpy(&m_PrevZoomLeft, &temp1, sizeof(VERTEX)); memcpy(&m_PrevZoomRight, &temp2, sizeof(VERTEX)); } void CGLTESTView::DrawZoomRect() { /* HDC hdc = wglGetCurrentDC(); CDC* dc = CDC::FromHandle(hdc); CPen pen(PS_DOT, 0, RGB(0, 0, 0)); CPen* oldPen = dc->SelectObject(&pen); HBRUSH hbrush = (HBRUSH)GetStockObject(NULL_BRUSH); CBrush* brush = CBrush::FromHandle(hbrush); CBrush* oldBrush = dc->SelectObject(brush); dc->SetROP2(R2_XORPEN); CRect rect(m_StartPosition.x, m_StartPosition.y, m_CursorPosition.x, m_CursorPosition.y); dc->DPtoLP(&rect); dc->Rectangle(&rect); dc->SelectObject(oldPen); dc->SelectObject(oldBrush); */ GLushort pattern; GLint repeat; ::glEnable(GL_LINE_STIPPLE); ::glGetIntegerv(GL_LINE_STIPPLE_PATTERN, (GLint*)&pattern); ::glGetIntegerv(GL_LINE_STIPPLE_REPEAT, &repeat); GLushort line_stipple = 4351; ::glLineStipple(3, line_stipple); ::glDrawBuffer(GL_FRONT); GLboolean depthtest = ::glIsEnabled(GL_DEPTH_TEST); ::glDisable(GL_DEPTH_TEST); GLenum depthfunc, logicop; ::glGetIntegerv(GL_DEPTH_FUNC,(GLint*) &depthfunc); ::glDepthFunc(GL_LEQUAL); ::glEnable(GL_COLOR_LOGIC_OP); ::glGetIntegerv(GL_LOGIC_OP_MODE,(GLint*) &logicop); ::glLogicOp(GL_XOR); ::glColor3d(1.0,1.0,0.0); ::glBegin(GL_LINE_LOOP); ::glVertex2d(m_StartPoint.east, m_StartPoint.north); ::glVertex2d(m_EndPoint.east, m_StartPoint.north); ::glVertex2d(m_EndPoint.east, m_EndPoint.north); ::glVertex2d(m_StartPoint.east, m_EndPoint.north); ::glEnd(); ::glDrawBuffer(GL_BACK); if(depthtest) ::glEnable(GL_DEPTH_TEST); ::glDepthFunc(depthfunc); ::glLogicOp(logicop); ::glLineStipple(repeat, pattern); } void CGLTESTView::DrawRubberBand() { /* HDC hdc = wglGetCurrentDC(); CDC* dc = CDC::FromHandle(hdc); CPen pen(PS_DOT, 0, RGB(0, 0, 0)); CPen* oldPen = dc->SelectObject(&pen); HBRUSH hbrush = (HBRUSH)GetStockObject(NULL_BRUSH); CBrush* brush = CBrush::FromHandle(hbrush); CBrush* oldBrush = dc->SelectObject(brush); CPoint point1(m_StartPosition); CPoint point2(m_CursorPosition); dc->DPtoLP(&point1); dc->DPtoLP(&point2); dc->SetROP2(R2_XORPEN); dc->MoveTo(point1); dc->LineTo(point2); dc->SelectObject(oldPen); dc->SelectObject(oldBrush); */ GLushort pattern; GLint repeat; ::glEnable(GL_LINE_STIPPLE); ::glGetIntegerv(GL_LINE_STIPPLE_PATTERN, (GLint*)&pattern); ::glGetIntegerv(GL_LINE_STIPPLE_REPEAT, &repeat); ::glLineStipple(1, 128); ::glDrawBuffer(GL_FRONT); GLboolean depthtest = ::glIsEnabled(GL_DEPTH_TEST); ::glDisable(GL_DEPTH_TEST); GLenum depthfunc, logicop; ::glGetIntegerv(GL_DEPTH_FUNC,(GLint*) &depthfunc); ::glDepthFunc(GL_LEQUAL); ::glEnable(GL_COLOR_LOGIC_OP); ::glGetIntegerv(GL_LOGIC_OP_MODE,(GLint*) &logicop); ::glLogicOp(GL_XOR); ::glColor3d(1.0,1.0,0.0); ::glBegin(GL_LINE_STRIP); ::glVertex2d(m_StartPoint.east, m_StartPoint.north); ::glVertex2d(m_EndPoint.east, m_EndPoint.north); ::glEnd(); ::glDrawBuffer(GL_BACK); if(depthtest) ::glEnable(GL_DEPTH_TEST); ::glDepthFunc(depthfunc); ::glLogicOp(logicop); ::glLineStipple(repeat, pattern); } BOOL CGLTESTView::PreTranslateMessage(MSG* pMsg) { return CView::PreTranslateMessage(pMsg); } void CGLTESTView::OnPan() { currTool = ID_PAN; m_FirstPickPoint = FALSE; } int CGLTESTView::OnCreate(LPCREATESTRUCT lpCreateStruct) { if (CView::OnCreate(lpCreateStruct) == -1) return -1; // TODO: Add your specialized creation code here GetParentFrame()->GetWindowText( m_WindowTitle ); // get rid of that " - Untitled" stuff GetParentFrame()->ModifyStyle(FWS_ADDTOTITLE,0); InitializeOpenGL(); return 0; } void CGLTESTView::OnDestroy() { CView::OnDestroy(); if ( FALSE == ::wglDeleteContext( m_hRC ) ) return; // For Color-Index mode, you should reset the palette to the original here if ( m_pDC ) { delete m_pDC; } } ////////////////////////////////////////////////////////////////////////////// // InitializeOpenGL // - just that. This is set up for RGB mode, though I've indicated // where you would add code for color-index mode. BOOL CGLTESTView::InitializeOpenGL() { // Can we put this in the constructor? m_pDC = new CClientDC(this); if ( NULL == m_pDC ) // failure to get DC return FALSE; if (!SetupPixelFormat()) { return FALSE; } // For Color-Index mode, you'd probably create your palette here, right // after you select the pixel format if ( 0 == (m_hRC = ::wglCreateContext( m_pDC->GetSafeHdc() ) ) ) return FALSE; if ( FALSE == MakeActive()) return FALSE; // specify black as clear color ::glClearColor(0.0f, 0.0f, 0.0f, 0.0f); // specify the back of the buffer as clear depth ::glClearDepth(1.0f); // enable depth testing ::glEnable(GL_DEPTH_TEST); return TRUE; } ///////////////////////////////////////////////////////////////////////////// BOOL CGLTESTView::SetupPixelFormat() { static PIXELFORMATDESCRIPTOR pfd = { sizeof(PIXELFORMATDESCRIPTOR), // size of this pfd 1, // version number PFD_DRAW_TO_WINDOW | // support window PFD_SUPPORT_OPENGL | // support OpenGL PFD_DOUBLEBUFFER, // double buffered PFD_TYPE_RGBA, // RGBA type 24, // 24-bit color depth 0, 0, 0, 0, 0, 0, // color bits ignored 0, // no alpha buffer 0, // shift bit ignored 0, // no accumulation buffer 0, 0, 0, 0, // accum bits ignored 32, // 32-bit z-buffer 0, // no stencil buffer 0, // no auxiliary buffer PFD_MAIN_PLANE, // main layer 0, // reserved 0, 0, 0 // layer masks ignored }; if ( 0 == (m_PixelFormat = ::ChoosePixelFormat(m_pDC->GetSafeHdc(), &pfd)) ) return FALSE; if ( FALSE == ::SetPixelFormat(m_pDC->GetSafeHdc(), m_PixelFormat, &pfd) ) return FALSE; return TRUE; } ///////////////////////////////////////////////////////////////////////////// // SetupViewport BOOL CGLTESTView::SetupViewport( int cx, int cy ) { // select the full client area ::glViewport(0, 0, cx, cy); return TRUE; } void CGLTESTView::OnKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags) { CView::OnKeyDown(nChar, nRepCnt, nFlags); if(nChar == VK_ESCAPE) { if(currTool != -1) { switch(currTool) { case ID_ZOOM_WINDOW:DrawZoomRect(); break; case ID_PAN: DrawRubberBand(); break; } currTool = -1; m_FirstPickPoint = FALSE; } } } void CGLTESTView::SetLimits() { GLdouble modelMatrix[16]; GLdouble projMatrix[16]; GLint viewport[4]; glGetDoublev(GL_MODELVIEW_MATRIX, modelMatrix); glGetDoublev(GL_PROJECTION_MATRIX, projMatrix); glGetIntegerv(GL_VIEWPORT, viewport); CRect rect; GetClientRect(&rect); gluUnProject(0, 0, 0, modelMatrix, projMatrix, viewport, &xOrg, &yOrg, &zMax); gluUnProject(rect.Width(), rect.Height(), 0, modelMatrix, projMatrix, viewport, &xMax, &yMax, &zMax); gluUnProject(1, 1, 0, modelMatrix, projMatrix, viewport, &m_PixelX, &m_PixelY, &zMax); m_PixelX -= xOrg; m_PixelY -= yOrg; } void CGLTESTView::OnMove(int x, int y) { CView::OnMove(x, y); } void CGLTESTView::OnSetFocus(CWnd* pOldWnd) { CView::OnSetFocus(pOldWnd); MakeActive(); } BOOL CGLTESTView::MakeActive() { return SetCurrent(m_pDC->GetSafeHdc(), m_hRC); } void CGLTESTView::OnKillFocus(CWnd* pNewWnd) { CView::OnKillFocus(pNewWnd); } void CGLTESTView::GetCurrent(HDC& hdc, HGLRC& rc) { hdc = ::wglGetCurrentDC(); rc = ::wglGetCurrentContext(); } BOOL CGLTESTView::SetCurrent(HDC hdc, HGLRC rc) { if (FALSE == ::wglMakeCurrent(hdc, rc)) return FALSE; return TRUE; } void CGLTESTView::OnUpdateZoomWindow(CCmdUI* pCmdUI) { GLdouble dx = xMax - xOrg; GLdouble dy = yMax - yOrg; pCmdUI->Enable(fabs(dx) > 0.001 || fabs(dy) > 0.001); } void CGLTESTView::OnUpdateZoomPrevious(CCmdUI* pCmdUI) { pCmdUI->Enable(m_CanZoomPrev); }