michael@0: michael@0: /* michael@0: * Copyright 2011 Google Inc. michael@0: * michael@0: * Use of this source code is governed by a BSD-style license that can be michael@0: * found in the LICENSE file. michael@0: */ michael@0: #include "SkTypes.h" michael@0: michael@0: #if defined(SK_BUILD_FOR_WIN) michael@0: michael@0: #include michael@0: #include michael@0: #include "SkWGL.h" michael@0: #include "SkWindow.h" michael@0: #include "SkCanvas.h" michael@0: #include "SkOSMenu.h" michael@0: #include "SkTime.h" michael@0: #include "SkUtils.h" michael@0: michael@0: #include "SkGraphics.h" michael@0: michael@0: #if SK_ANGLE michael@0: #include "gl/GrGLInterface.h" michael@0: michael@0: #include "GLES2/gl2.h" michael@0: michael@0: #define ANGLE_GL_CALL(IFACE, X) \ michael@0: do { \ michael@0: (IFACE)->fFunctions.f##X; \ michael@0: } while (false) michael@0: michael@0: #endif michael@0: michael@0: #define INVALIDATE_DELAY_MS 200 michael@0: michael@0: static SkOSWindow* gCurrOSWin; michael@0: static HWND gEventTarget; michael@0: michael@0: #define WM_EVENT_CALLBACK (WM_USER+0) michael@0: michael@0: void post_skwinevent() michael@0: { michael@0: PostMessage(gEventTarget, WM_EVENT_CALLBACK, 0, 0); michael@0: } michael@0: michael@0: SkOSWindow::SkOSWindow(void* hWnd) { michael@0: fHWND = hWnd; michael@0: #if SK_SUPPORT_GPU michael@0: #if SK_ANGLE michael@0: fDisplay = EGL_NO_DISPLAY; michael@0: fContext = EGL_NO_CONTEXT; michael@0: fSurface = EGL_NO_SURFACE; michael@0: #endif michael@0: fHGLRC = NULL; michael@0: #endif michael@0: fAttached = kNone_BackEndType; michael@0: gEventTarget = (HWND)hWnd; michael@0: } michael@0: michael@0: SkOSWindow::~SkOSWindow() { michael@0: #if SK_SUPPORT_GPU michael@0: if (NULL != fHGLRC) { michael@0: wglDeleteContext((HGLRC)fHGLRC); michael@0: } michael@0: #if SK_ANGLE michael@0: if (EGL_NO_CONTEXT != fContext) { michael@0: eglDestroyContext(fDisplay, fContext); michael@0: fContext = EGL_NO_CONTEXT; michael@0: } michael@0: michael@0: if (EGL_NO_SURFACE != fSurface) { michael@0: eglDestroySurface(fDisplay, fSurface); michael@0: fSurface = EGL_NO_SURFACE; michael@0: } michael@0: michael@0: if (EGL_NO_DISPLAY != fDisplay) { michael@0: eglTerminate(fDisplay); michael@0: fDisplay = EGL_NO_DISPLAY; michael@0: } michael@0: #endif // SK_ANGLE michael@0: #endif // SK_SUPPORT_GPU michael@0: } michael@0: michael@0: static SkKey winToskKey(WPARAM vk) { michael@0: static const struct { michael@0: WPARAM fVK; michael@0: SkKey fKey; michael@0: } gPair[] = { michael@0: { VK_BACK, kBack_SkKey }, michael@0: { VK_CLEAR, kBack_SkKey }, michael@0: { VK_RETURN, kOK_SkKey }, michael@0: { VK_UP, kUp_SkKey }, michael@0: { VK_DOWN, kDown_SkKey }, michael@0: { VK_LEFT, kLeft_SkKey }, michael@0: { VK_RIGHT, kRight_SkKey } michael@0: }; michael@0: for (size_t i = 0; i < SK_ARRAY_COUNT(gPair); i++) { michael@0: if (gPair[i].fVK == vk) { michael@0: return gPair[i].fKey; michael@0: } michael@0: } michael@0: return kNONE_SkKey; michael@0: } michael@0: michael@0: static unsigned getModifiers(UINT message) { michael@0: return 0; // TODO michael@0: } michael@0: michael@0: bool SkOSWindow::wndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) { michael@0: switch (message) { michael@0: case WM_KEYDOWN: { michael@0: SkKey key = winToskKey(wParam); michael@0: if (kNONE_SkKey != key) { michael@0: this->handleKey(key); michael@0: return true; michael@0: } michael@0: } break; michael@0: case WM_KEYUP: { michael@0: SkKey key = winToskKey(wParam); michael@0: if (kNONE_SkKey != key) { michael@0: this->handleKeyUp(key); michael@0: return true; michael@0: } michael@0: } break; michael@0: case WM_UNICHAR: michael@0: this->handleChar((SkUnichar) wParam); michael@0: return true; michael@0: case WM_CHAR: { michael@0: this->handleChar(SkUTF8_ToUnichar((char*)&wParam)); michael@0: return true; michael@0: } break; michael@0: case WM_SIZE: { michael@0: INT width = LOWORD(lParam); michael@0: INT height = HIWORD(lParam); michael@0: this->resize(width, height); michael@0: break; michael@0: } michael@0: case WM_PAINT: { michael@0: PAINTSTRUCT ps; michael@0: HDC hdc = BeginPaint(hWnd, &ps); michael@0: this->doPaint(hdc); michael@0: EndPaint(hWnd, &ps); michael@0: return true; michael@0: } break; michael@0: michael@0: case WM_TIMER: { michael@0: RECT* rect = (RECT*)wParam; michael@0: InvalidateRect(hWnd, rect, FALSE); michael@0: KillTimer(hWnd, (UINT_PTR)rect); michael@0: delete rect; michael@0: return true; michael@0: } break; michael@0: michael@0: case WM_LBUTTONDOWN: michael@0: this->handleClick(GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam), michael@0: Click::kDown_State, NULL, getModifiers(message)); michael@0: return true; michael@0: michael@0: case WM_MOUSEMOVE: michael@0: this->handleClick(GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam), michael@0: Click::kMoved_State, NULL, getModifiers(message)); michael@0: return true; michael@0: michael@0: case WM_LBUTTONUP: michael@0: this->handleClick(GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam), michael@0: Click::kUp_State, NULL, getModifiers(message)); michael@0: return true; michael@0: michael@0: case WM_EVENT_CALLBACK: michael@0: if (SkEvent::ProcessEvent()) { michael@0: post_skwinevent(); michael@0: } michael@0: return true; michael@0: } michael@0: return false; michael@0: } michael@0: michael@0: void SkOSWindow::doPaint(void* ctx) { michael@0: this->update(NULL); michael@0: michael@0: if (kNone_BackEndType == fAttached) michael@0: { michael@0: HDC hdc = (HDC)ctx; michael@0: const SkBitmap& bitmap = this->getBitmap(); michael@0: michael@0: BITMAPINFO bmi; michael@0: memset(&bmi, 0, sizeof(bmi)); michael@0: bmi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER); michael@0: bmi.bmiHeader.biWidth = bitmap.width(); michael@0: bmi.bmiHeader.biHeight = -bitmap.height(); // top-down image michael@0: bmi.bmiHeader.biPlanes = 1; michael@0: bmi.bmiHeader.biBitCount = 32; michael@0: bmi.bmiHeader.biCompression = BI_RGB; michael@0: bmi.bmiHeader.biSizeImage = 0; michael@0: michael@0: // michael@0: // Do the SetDIBitsToDevice. michael@0: // michael@0: // TODO(wjmaclean): michael@0: // Fix this call to handle SkBitmaps that have rowBytes != width, michael@0: // i.e. may have padding at the end of lines. The SkASSERT below michael@0: // may be ignored by builds, and the only obviously safe option michael@0: // seems to be to copy the bitmap to a temporary (contiguous) michael@0: // buffer before passing to SetDIBitsToDevice(). michael@0: SkASSERT(bitmap.width() * bitmap.bytesPerPixel() == bitmap.rowBytes()); michael@0: bitmap.lockPixels(); michael@0: int ret = SetDIBitsToDevice(hdc, michael@0: 0, 0, michael@0: bitmap.width(), bitmap.height(), michael@0: 0, 0, michael@0: 0, bitmap.height(), michael@0: bitmap.getPixels(), michael@0: &bmi, michael@0: DIB_RGB_COLORS); michael@0: (void)ret; // we're ignoring potential failures for now. michael@0: bitmap.unlockPixels(); michael@0: } michael@0: } michael@0: michael@0: #if 0 michael@0: void SkOSWindow::updateSize() michael@0: { michael@0: RECT r; michael@0: GetWindowRect((HWND)this->getHWND(), &r); michael@0: this->resize(r.right - r.left, r.bottom - r.top); michael@0: } michael@0: #endif michael@0: michael@0: void SkOSWindow::onHandleInval(const SkIRect& r) { michael@0: RECT* rect = new RECT; michael@0: rect->left = r.fLeft; michael@0: rect->top = r.fTop; michael@0: rect->right = r.fRight; michael@0: rect->bottom = r.fBottom; michael@0: SetTimer((HWND)fHWND, (UINT_PTR)rect, INVALIDATE_DELAY_MS, NULL); michael@0: } michael@0: michael@0: void SkOSWindow::onAddMenu(const SkOSMenu* sk_menu) michael@0: { michael@0: } michael@0: michael@0: void SkOSWindow::onSetTitle(const char title[]){ michael@0: SetWindowTextA((HWND)fHWND, title); michael@0: } michael@0: michael@0: enum { michael@0: SK_MacReturnKey = 36, michael@0: SK_MacDeleteKey = 51, michael@0: SK_MacEndKey = 119, michael@0: SK_MacLeftKey = 123, michael@0: SK_MacRightKey = 124, michael@0: SK_MacDownKey = 125, michael@0: SK_MacUpKey = 126, michael@0: michael@0: SK_Mac0Key = 0x52, michael@0: SK_Mac1Key = 0x53, michael@0: SK_Mac2Key = 0x54, michael@0: SK_Mac3Key = 0x55, michael@0: SK_Mac4Key = 0x56, michael@0: SK_Mac5Key = 0x57, michael@0: SK_Mac6Key = 0x58, michael@0: SK_Mac7Key = 0x59, michael@0: SK_Mac8Key = 0x5b, michael@0: SK_Mac9Key = 0x5c michael@0: }; michael@0: michael@0: static SkKey raw2key(uint32_t raw) michael@0: { michael@0: static const struct { michael@0: uint32_t fRaw; michael@0: SkKey fKey; michael@0: } gKeys[] = { michael@0: { SK_MacUpKey, kUp_SkKey }, michael@0: { SK_MacDownKey, kDown_SkKey }, michael@0: { SK_MacLeftKey, kLeft_SkKey }, michael@0: { SK_MacRightKey, kRight_SkKey }, michael@0: { SK_MacReturnKey, kOK_SkKey }, michael@0: { SK_MacDeleteKey, kBack_SkKey }, michael@0: { SK_MacEndKey, kEnd_SkKey }, michael@0: { SK_Mac0Key, k0_SkKey }, michael@0: { SK_Mac1Key, k1_SkKey }, michael@0: { SK_Mac2Key, k2_SkKey }, michael@0: { SK_Mac3Key, k3_SkKey }, michael@0: { SK_Mac4Key, k4_SkKey }, michael@0: { SK_Mac5Key, k5_SkKey }, michael@0: { SK_Mac6Key, k6_SkKey }, michael@0: { SK_Mac7Key, k7_SkKey }, michael@0: { SK_Mac8Key, k8_SkKey }, michael@0: { SK_Mac9Key, k9_SkKey } michael@0: }; michael@0: michael@0: for (unsigned i = 0; i < SK_ARRAY_COUNT(gKeys); i++) michael@0: if (gKeys[i].fRaw == raw) michael@0: return gKeys[i].fKey; michael@0: return kNONE_SkKey; michael@0: } michael@0: michael@0: /////////////////////////////////////////////////////////////////////////////////////// michael@0: michael@0: void SkEvent::SignalNonEmptyQueue() michael@0: { michael@0: post_skwinevent(); michael@0: //SkDebugf("signal nonempty\n"); michael@0: } michael@0: michael@0: static UINT_PTR gTimer; michael@0: michael@0: VOID CALLBACK sk_timer_proc(HWND hwnd, UINT uMsg, UINT_PTR idEvent, DWORD dwTime) michael@0: { michael@0: SkEvent::ServiceQueueTimer(); michael@0: //SkDebugf("timer task fired\n"); michael@0: } michael@0: michael@0: void SkEvent::SignalQueueTimer(SkMSec delay) michael@0: { michael@0: if (gTimer) michael@0: { michael@0: KillTimer(NULL, gTimer); michael@0: gTimer = NULL; michael@0: } michael@0: if (delay) michael@0: { michael@0: gTimer = SetTimer(NULL, 0, delay, sk_timer_proc); michael@0: //SkDebugf("SetTimer of %d returned %d\n", delay, gTimer); michael@0: } michael@0: } michael@0: michael@0: #if SK_SUPPORT_GPU michael@0: michael@0: bool SkOSWindow::attachGL(int msaaSampleCount, AttachmentInfo* info) { michael@0: HDC dc = GetDC((HWND)fHWND); michael@0: if (NULL == fHGLRC) { michael@0: fHGLRC = SkCreateWGLContext(dc, msaaSampleCount, false); michael@0: if (NULL == fHGLRC) { michael@0: return false; michael@0: } michael@0: glClearStencil(0); michael@0: glClearColor(0, 0, 0, 0); michael@0: glStencilMask(0xffffffff); michael@0: glClear(GL_STENCIL_BUFFER_BIT | GL_COLOR_BUFFER_BIT); michael@0: } michael@0: if (wglMakeCurrent(dc, (HGLRC)fHGLRC)) { michael@0: // use DescribePixelFormat to get the stencil bit depth. michael@0: int pixelFormat = GetPixelFormat(dc); michael@0: PIXELFORMATDESCRIPTOR pfd; michael@0: DescribePixelFormat(dc, pixelFormat, sizeof(pfd), &pfd); michael@0: info->fStencilBits = pfd.cStencilBits; michael@0: michael@0: // Get sample count if the MSAA WGL extension is present michael@0: SkWGLExtensions extensions; michael@0: if (extensions.hasExtension(dc, "WGL_ARB_multisample")) { michael@0: static const int kSampleCountAttr = SK_WGL_SAMPLES; michael@0: extensions.getPixelFormatAttribiv(dc, michael@0: pixelFormat, michael@0: 0, michael@0: 1, michael@0: &kSampleCountAttr, michael@0: &info->fSampleCount); michael@0: } else { michael@0: info->fSampleCount = 0; michael@0: } michael@0: michael@0: glViewport(0, 0, michael@0: SkScalarRoundToInt(this->width()), michael@0: SkScalarRoundToInt(this->height())); michael@0: return true; michael@0: } michael@0: return false; michael@0: } michael@0: michael@0: void SkOSWindow::detachGL() { michael@0: wglMakeCurrent(GetDC((HWND)fHWND), 0); michael@0: wglDeleteContext((HGLRC)fHGLRC); michael@0: fHGLRC = NULL; michael@0: } michael@0: michael@0: void SkOSWindow::presentGL() { michael@0: glFlush(); michael@0: HDC dc = GetDC((HWND)fHWND); michael@0: SwapBuffers(dc); michael@0: ReleaseDC((HWND)fHWND, dc); michael@0: } michael@0: michael@0: #if SK_ANGLE michael@0: bool create_ANGLE(EGLNativeWindowType hWnd, michael@0: int msaaSampleCount, michael@0: EGLDisplay* eglDisplay, michael@0: EGLContext* eglContext, michael@0: EGLSurface* eglSurface, michael@0: EGLConfig* eglConfig) { michael@0: static const EGLint contextAttribs[] = { michael@0: EGL_CONTEXT_CLIENT_VERSION, 2, michael@0: EGL_NONE, EGL_NONE michael@0: }; michael@0: static const EGLint configAttribList[] = { michael@0: EGL_RED_SIZE, 8, michael@0: EGL_GREEN_SIZE, 8, michael@0: EGL_BLUE_SIZE, 8, michael@0: EGL_ALPHA_SIZE, 8, michael@0: EGL_DEPTH_SIZE, 8, michael@0: EGL_STENCIL_SIZE, 8, michael@0: EGL_NONE michael@0: }; michael@0: static const EGLint surfaceAttribList[] = { michael@0: EGL_NONE, EGL_NONE michael@0: }; michael@0: michael@0: EGLDisplay display = eglGetDisplay(GetDC(hWnd)); michael@0: if (display == EGL_NO_DISPLAY ) { michael@0: return false; michael@0: } michael@0: michael@0: // Initialize EGL michael@0: EGLint majorVersion, minorVersion; michael@0: if (!eglInitialize(display, &majorVersion, &minorVersion)) { michael@0: return false; michael@0: } michael@0: michael@0: EGLint numConfigs; michael@0: if (!eglGetConfigs(display, NULL, 0, &numConfigs)) { michael@0: return false; michael@0: } michael@0: michael@0: // Choose config michael@0: bool foundConfig = false; michael@0: if (msaaSampleCount) { michael@0: static const int kConfigAttribListCnt = michael@0: SK_ARRAY_COUNT(configAttribList); michael@0: EGLint msaaConfigAttribList[kConfigAttribListCnt + 4]; michael@0: memcpy(msaaConfigAttribList, michael@0: configAttribList, michael@0: sizeof(configAttribList)); michael@0: SkASSERT(EGL_NONE == msaaConfigAttribList[kConfigAttribListCnt - 1]); michael@0: msaaConfigAttribList[kConfigAttribListCnt - 1] = EGL_SAMPLE_BUFFERS; michael@0: msaaConfigAttribList[kConfigAttribListCnt + 0] = 1; michael@0: msaaConfigAttribList[kConfigAttribListCnt + 1] = EGL_SAMPLES; michael@0: msaaConfigAttribList[kConfigAttribListCnt + 2] = msaaSampleCount; michael@0: msaaConfigAttribList[kConfigAttribListCnt + 3] = EGL_NONE; michael@0: if (eglChooseConfig(display, configAttribList, eglConfig, 1, &numConfigs)) { michael@0: SkASSERT(numConfigs > 0); michael@0: foundConfig = true; michael@0: } michael@0: } michael@0: if (!foundConfig) { michael@0: if (!eglChooseConfig(display, configAttribList, eglConfig, 1, &numConfigs)) { michael@0: return false; michael@0: } michael@0: } michael@0: michael@0: // Create a surface michael@0: EGLSurface surface = eglCreateWindowSurface(display, *eglConfig, michael@0: (EGLNativeWindowType)hWnd, michael@0: surfaceAttribList); michael@0: if (surface == EGL_NO_SURFACE) { michael@0: return false; michael@0: } michael@0: michael@0: // Create a GL context michael@0: EGLContext context = eglCreateContext(display, *eglConfig, michael@0: EGL_NO_CONTEXT, michael@0: contextAttribs ); michael@0: if (context == EGL_NO_CONTEXT ) { michael@0: return false; michael@0: } michael@0: michael@0: // Make the context current michael@0: if (!eglMakeCurrent(display, surface, surface, context)) { michael@0: return false; michael@0: } michael@0: michael@0: *eglDisplay = display; michael@0: *eglContext = context; michael@0: *eglSurface = surface; michael@0: return true; michael@0: } michael@0: michael@0: bool SkOSWindow::attachANGLE(int msaaSampleCount, AttachmentInfo* info) { michael@0: if (EGL_NO_DISPLAY == fDisplay) { michael@0: bool bResult = create_ANGLE((HWND)fHWND, michael@0: msaaSampleCount, michael@0: &fDisplay, michael@0: &fContext, michael@0: &fSurface, michael@0: &fConfig); michael@0: if (false == bResult) { michael@0: return false; michael@0: } michael@0: SkAutoTUnref intf(GrGLCreateANGLEInterface()); michael@0: michael@0: if (intf) { michael@0: ANGLE_GL_CALL(intf, ClearStencil(0)); michael@0: ANGLE_GL_CALL(intf, ClearColor(0, 0, 0, 0)); michael@0: ANGLE_GL_CALL(intf, StencilMask(0xffffffff)); michael@0: ANGLE_GL_CALL(intf, Clear(GL_STENCIL_BUFFER_BIT |GL_COLOR_BUFFER_BIT)); michael@0: } michael@0: } michael@0: if (eglMakeCurrent(fDisplay, fSurface, fSurface, fContext)) { michael@0: eglGetConfigAttrib(fDisplay, fConfig, EGL_STENCIL_SIZE, &info->fStencilBits); michael@0: eglGetConfigAttrib(fDisplay, fConfig, EGL_SAMPLES, &info->fSampleCount); michael@0: michael@0: SkAutoTUnref intf(GrGLCreateANGLEInterface()); michael@0: michael@0: if (intf ) { michael@0: ANGLE_GL_CALL(intf, Viewport(0, 0, michael@0: SkScalarRoundToInt(this->width()), michael@0: SkScalarRoundToInt(this->height()))); michael@0: } michael@0: return true; michael@0: } michael@0: return false; michael@0: } michael@0: michael@0: void SkOSWindow::detachANGLE() { michael@0: eglMakeCurrent(fDisplay, EGL_NO_SURFACE , EGL_NO_SURFACE , EGL_NO_CONTEXT); michael@0: michael@0: eglDestroyContext(fDisplay, fContext); michael@0: fContext = EGL_NO_CONTEXT; michael@0: michael@0: eglDestroySurface(fDisplay, fSurface); michael@0: fSurface = EGL_NO_SURFACE; michael@0: michael@0: eglTerminate(fDisplay); michael@0: fDisplay = EGL_NO_DISPLAY; michael@0: } michael@0: michael@0: void SkOSWindow::presentANGLE() { michael@0: SkAutoTUnref intf(GrGLCreateANGLEInterface()); michael@0: michael@0: if (intf) { michael@0: ANGLE_GL_CALL(intf, Flush()); michael@0: } michael@0: michael@0: eglSwapBuffers(fDisplay, fSurface); michael@0: } michael@0: #endif // SK_ANGLE michael@0: #endif // SK_SUPPORT_GPU michael@0: michael@0: // return true on success michael@0: bool SkOSWindow::attach(SkBackEndTypes attachType, int msaaSampleCount, AttachmentInfo* info) { michael@0: michael@0: // attach doubles as "windowResize" so we need to allo michael@0: // already bound states to pass through again michael@0: // TODO: split out the resize functionality michael@0: // SkASSERT(kNone_BackEndType == fAttached); michael@0: bool result = true; michael@0: michael@0: switch (attachType) { michael@0: case kNone_BackEndType: michael@0: // nothing to do michael@0: break; michael@0: #if SK_SUPPORT_GPU michael@0: case kNativeGL_BackEndType: michael@0: result = attachGL(msaaSampleCount, info); michael@0: break; michael@0: #if SK_ANGLE michael@0: case kANGLE_BackEndType: michael@0: result = attachANGLE(msaaSampleCount, info); michael@0: break; michael@0: #endif // SK_ANGLE michael@0: #endif // SK_SUPPORT_GPU michael@0: default: michael@0: SkASSERT(false); michael@0: result = false; michael@0: break; michael@0: } michael@0: michael@0: if (result) { michael@0: fAttached = attachType; michael@0: } michael@0: michael@0: return result; michael@0: } michael@0: michael@0: void SkOSWindow::detach() { michael@0: switch (fAttached) { michael@0: case kNone_BackEndType: michael@0: // nothing to do michael@0: break; michael@0: #if SK_SUPPORT_GPU michael@0: case kNativeGL_BackEndType: michael@0: detachGL(); michael@0: break; michael@0: #if SK_ANGLE michael@0: case kANGLE_BackEndType: michael@0: detachANGLE(); michael@0: break; michael@0: #endif // SK_ANGLE michael@0: #endif // SK_SUPPORT_GPU michael@0: default: michael@0: SkASSERT(false); michael@0: break; michael@0: } michael@0: fAttached = kNone_BackEndType; michael@0: } michael@0: michael@0: void SkOSWindow::present() { michael@0: switch (fAttached) { michael@0: case kNone_BackEndType: michael@0: // nothing to do michael@0: return; michael@0: #if SK_SUPPORT_GPU michael@0: case kNativeGL_BackEndType: michael@0: presentGL(); michael@0: break; michael@0: #if SK_ANGLE michael@0: case kANGLE_BackEndType: michael@0: presentANGLE(); michael@0: break; michael@0: #endif // SK_ANGLE michael@0: #endif // SK_SUPPORT_GPU michael@0: default: michael@0: SkASSERT(false); michael@0: break; michael@0: } michael@0: } michael@0: michael@0: #endif