Mercurial > projects > dwt-win
view dwt/internal/BidiUtil.d @ 212:ab60f3309436
reverted the char[] to String and use the an alias.
author | Frank Benoit <benoit@tionex.de> |
---|---|
date | Mon, 05 May 2008 00:12:38 +0200 |
parents | 184ab53b7785 |
children | 36f5cb12e1a2 |
line wrap: on
line source
/******************************************************************************* * Copyright (c) 2000, 2007 IBM Corporation and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * IBM Corporation - initial API and implementation * Port to the D programming language: * Frank Benoit <benoit@tionex.de> *******************************************************************************/ module dwt.internal.BidiUtil; import dwt.DWT; import dwt.graphics.GC; import dwt.internal.win32.OS; import tango.util.Convert; import dwt.dwthelper.utils; import dwt.dwthelper.Runnable; /* * Wraps Win32 API used to bidi enable the StyledText widget. */ public class BidiUtil { // Keyboard language ids public static const int KEYBOARD_NON_BIDI = 0; public static const int KEYBOARD_BIDI = 1; // bidi flag static int isBidiPlatform_ = -1; // getRenderInfo flag values public static const int CLASSIN = 1; public static const int LINKBEFORE = 2; public static const int LINKAFTER = 4; // variables used for providing a listener mechanism for keyboard language // switching static Runnable[HWND] languageMap; static Runnable[HWND] keyMap; static WNDPROC[HWND] oldProcMap; /* * This code is intentionally commented. In order * to support CLDC, .class cannot be used because * it does not compile on some Java compilers when * they are targeted for CLDC. */ // static Callback callback = new Callback (BidiUtil.class, "windowProc", 4); static const String CLASS_NAME = "org.eclipse.swt.internal.BidiUtil"; //$NON-NLS-1$ // static this() { // try { // callback = new Callback (Class.forName (CLASS_NAME), "windowProc", 4); //$NON-NLS-1$ // if (callback.getAddress () is 0) DWT.error (DWT.ERROR_NO_MORE_CALLBACKS); // } catch (ClassNotFoundException e) {} // } // GetCharacterPlacement constants static const int GCP_REORDER = 0x0002; static const int GCP_GLYPHSHAPE = 0x0010; static const int GCP_LIGATE = 0x0020; static const int GCP_CLASSIN = 0x00080000; static const byte GCPCLASS_ARABIC = 2; static const byte GCPCLASS_HEBREW = 2; static const byte GCPCLASS_LOCALNUMBER = 4; static const byte GCPCLASS_LATINNUMBER = 5; static const int GCPGLYPH_LINKBEFORE = 0x8000; static const int GCPGLYPH_LINKAFTER = 0x4000; // ExtTextOut constants static const int ETO_CLIPPED = 0x4; static const int ETO_GLYPH_INDEX = 0x0010; // Windows primary language identifiers static const int LANG_ARABIC = 0x01; static const int LANG_HEBREW = 0x0d; // code page identifiers static const String CD_PG_HEBREW = "1255"; //$NON-NLS-1$ static const String CD_PG_ARABIC = "1256"; //$NON-NLS-1$ // ActivateKeyboard constants static const int HKL_NEXT = 1; static const int HKL_PREV = 0; /* * Public character class constants are the same as Windows * platform constants. * Saves conversion of class array in getRenderInfo to arbitrary * constants for now. */ public static const int CLASS_HEBREW = GCPCLASS_ARABIC; public static const int CLASS_ARABIC = GCPCLASS_HEBREW; public static const int CLASS_LOCALNUMBER = GCPCLASS_LOCALNUMBER; public static const int CLASS_LATINNUMBER = GCPCLASS_LATINNUMBER; public static const int REORDER = GCP_REORDER; public static const int LIGATE = GCP_LIGATE; public static const int GLYPHSHAPE = GCP_GLYPHSHAPE; /** * Adds a language listener. The listener will get notified when the language of * the keyboard changes (via Alt-Shift on Win platforms). Do this by creating a * window proc for the Control so that the window messages for the Control can be * monitored. * <p> * * @param hwnd the handle of the Control that is listening for keyboard language * changes * @param runnable the code that should be executed when a keyboard language change * occurs */ public static void addLanguageListener (HWND hwnd, Runnable runnable) { languageMap[hwnd] = runnable; subclass(hwnd); } /** * Proc used for OS.EnumSystemLanguageGroups call during isBidiPlatform test. */ static extern(Windows) int EnumSystemLanguageGroupsProc(uint lpLangGrpId, wchar* lpLangGrpIdString, wchar* lpLangGrpName, uint options, int lParam) { if (lpLangGrpId is OS.LGRPID_HEBREW) { isBidiPlatform_ = 1; return 0; } if (lpLangGrpId is OS.LGRPID_ARABIC) { isBidiPlatform_ = 1; return 0; } return 1; } /** * Wraps the ExtTextOut function. * <p> * * @param gc the gc to use for rendering * @param renderBuffer the glyphs to render as an array of characters * @param renderDx the width of each glyph in renderBuffer * @param x x position to start rendering * @param y y position to start rendering */ public static void drawGlyphs(GC gc, wchar[] renderBuffer, int[] renderDx, int x, int y) { int length_ = renderDx.length; if (!OS.IsWinCE && OS.WIN32_VERSION >= OS.VERSION(4, 10)) { if (OS.GetLayout (gc.handle) !is 0) { reverse(renderDx); renderDx[length_-1]--; //fixes bug 40006 reverse(renderBuffer); } } // render transparently to avoid overlapping segments. fixes bug 40006 int oldBkMode = OS.SetBkMode(gc.handle, OS.TRANSPARENT); OS.ExtTextOutW(gc.handle, x, y, ETO_GLYPH_INDEX , null, renderBuffer.ptr, renderBuffer.length, renderDx.ptr); OS.SetBkMode(gc.handle, oldBkMode); } /** * Return ordering and rendering information for the given text. Wraps the GetFontLanguageInfo * and GetCharacterPlacement functions. * <p> * * @param gc the GC to use for measuring of this line, input parameter * @param text text that bidi data should be calculated for, input parameter * @param order an array of integers representing the visual position of each character in * the text array, output parameter * @param classBuffer an array of integers representing the type (e.g., ARABIC, HEBREW, * LOCALNUMBER) of each character in the text array, input/output parameter * @param dx an array of integers representing the pixel width of each glyph in the returned * glyph buffer, output parameter * @param flags an integer representing rendering flag information, input parameter * @param offsets text segments that should be measured and reordered separately, input * parameter. See org.eclipse.swt.custom.BidiSegmentEvent for details. * @return buffer with the glyphs that should be rendered for the given text */ public static char[] getRenderInfo(GC gc, String text, int[] order, byte[] classBuffer, int[] dx, int flags, int [] offsets) { auto fontLanguageInfo = OS.GetFontLanguageInfo(gc.handle); auto hHeap = OS.GetProcessHeap(); int[8] lpCs; int cs = OS.GetTextCharset(gc.handle); bool isRightOriented = false; if (!OS.IsWinCE && OS.WIN32_VERSION >= OS.VERSION(4, 10)) { isRightOriented = OS.GetLayout(gc.handle) !is 0; } OS.TranslateCharsetInfo( cast(uint*)cs, cast(CHARSETINFO*)lpCs.ptr, OS.TCI_SRCCHARSET); TCHAR[] textBuffer = StrToTCHARs(lpCs[1], text, false); int byteCount = textBuffer.length; bool linkBefore = (flags & LINKBEFORE) is LINKBEFORE; bool linkAfter = (flags & LINKAFTER) is LINKAFTER; GCP_RESULTS result; result.lStructSize = GCP_RESULTS.sizeof; result.nGlyphs = byteCount; auto lpOrder = result.lpOrder = cast(uint*)OS.HeapAlloc(hHeap, OS.HEAP_ZERO_MEMORY, byteCount * 4); auto lpDx = result.lpDx = cast(int*)OS.HeapAlloc(hHeap, OS.HEAP_ZERO_MEMORY, byteCount * 4); auto lpClass = result.lpClass = cast(char*)OS.HeapAlloc(hHeap, OS.HEAP_ZERO_MEMORY, byteCount); auto lpGlyphs = result.lpGlyphs = cast(wchar*)OS.HeapAlloc(hHeap, OS.HEAP_ZERO_MEMORY, byteCount * 2); // set required dwFlags int dwFlags = 0; int glyphFlags = 0; // Always reorder. We assume that if we are calling this function we're // on a platform that supports bidi. Fixes 20690. dwFlags |= GCP_REORDER; if ((fontLanguageInfo & GCP_LIGATE) is GCP_LIGATE) { dwFlags |= GCP_LIGATE; glyphFlags |= 0; } if ((fontLanguageInfo & GCP_GLYPHSHAPE) is GCP_GLYPHSHAPE) { dwFlags |= GCP_GLYPHSHAPE; if (linkBefore) { glyphFlags |= GCPGLYPH_LINKBEFORE; } if (linkAfter) { glyphFlags |= GCPGLYPH_LINKAFTER; } } byte[] lpGlyphs2; if (linkBefore || linkAfter) { lpGlyphs2 = new byte[2]; lpGlyphs2[0]=cast(byte)glyphFlags; lpGlyphs2[1]=cast(byte)(glyphFlags >> 8); } else { lpGlyphs2 = [cast(byte) glyphFlags]; } OS.MoveMemory(result.lpGlyphs, lpGlyphs2.ptr, lpGlyphs2.length); if ((flags & CLASSIN) is CLASSIN) { // set classification values for the substring dwFlags |= GCP_CLASSIN; OS.MoveMemory(result.lpClass, classBuffer.ptr, classBuffer.length); } wchar[] glyphBuffer = new wchar[result.nGlyphs]; int glyphCount = 0; for (int i=0; i<offsets.length-1; i++) { int offset = offsets [i]; int length_ = offsets [i+1] - offsets [i]; // The number of glyphs expected is <= length (segment length); // the actual number returned may be less in case of Arabic ligatures. result.nGlyphs = length_; TCHAR[] textBuffer2 = StrToTCHARs(lpCs[1], text.substring(offset, offset + length_), false); OS.GetCharacterPlacement(gc.handle, textBuffer2.ptr, textBuffer2.length, 0, &result, dwFlags); if (dx !is null) { int [] dx2 = new int [result.nGlyphs]; OS.MoveMemory(dx2.ptr, result.lpDx, dx2.length * 4); if (isRightOriented) { reverse(dx2); } System.arraycopy (dx2, 0, dx, glyphCount, dx2.length); } if (order !is null) { int [] order2 = new int [length_]; OS.MoveMemory(order2.ptr, result.lpOrder, order2.length * 4); translateOrder(order2, glyphCount, isRightOriented); System.arraycopy (order2, 0, order, offset, length_); } if (classBuffer !is null) { byte [] classBuffer2 = new byte [length_]; OS.MoveMemory(classBuffer2.ptr, result.lpClass, classBuffer2.length); System.arraycopy (classBuffer2, 0, classBuffer, offset, length_); } wchar[] glyphBuffer2 = new wchar[result.nGlyphs]; OS.MoveMemory(glyphBuffer2.ptr, result.lpGlyphs, glyphBuffer2.length * 2); if (isRightOriented) { reverse(glyphBuffer2); } System.arraycopy (glyphBuffer2, 0, glyphBuffer, glyphCount, glyphBuffer2.length); glyphCount += glyphBuffer2.length; // We concatenate successive results of calls to GCP. // For Arabic, it is the only good method since the number of output // glyphs might be less than the number of input characters. // This assumes that the whole line is built by successive adjacent // segments without overlapping. result.lpOrder += length_ * 4; result.lpDx += length_ * 4; result.lpClass += length_; result.lpGlyphs += glyphBuffer2.length * 2; } /* Free the memory that was allocated. */ OS.HeapFree(hHeap, 0, lpGlyphs); OS.HeapFree(hHeap, 0, lpClass); OS.HeapFree(hHeap, 0, lpDx); OS.HeapFree(hHeap, 0, lpOrder); return WCHARsToStr(glyphBuffer); } /** * Return bidi ordering information for the given text. Does not return rendering * information (e.g., glyphs, glyph distances). Use this method when you only need * ordering information. Doing so will improve performance. Wraps the * GetFontLanguageInfo and GetCharacterPlacement functions. * <p> * * @param gc the GC to use for measuring of this line, input parameter * @param text text that bidi data should be calculated for, input parameter * @param order an array of integers representing the visual position of each character in * the text array, output parameter * @param classBuffer an array of integers representing the type (e.g., ARABIC, HEBREW, * LOCALNUMBER) of each character in the text array, input/output parameter * @param flags an integer representing rendering flag information, input parameter * @param offsets text segments that should be measured and reordered separately, input * parameter. See org.eclipse.swt.custom.BidiSegmentEvent for details. */ public static void getOrderInfo(GC gc, String text, int[] order, byte[] classBuffer, int flags, int [] offsets) { int fontLanguageInfo = OS.GetFontLanguageInfo(gc.handle); auto hHeap = OS.GetProcessHeap(); int[8] lpCs; int cs = OS.GetTextCharset(gc.handle); OS.TranslateCharsetInfo( cast(uint*) cs, cast(CHARSETINFO*)lpCs.ptr, OS.TCI_SRCCHARSET); TCHAR[] textBuffer = StrToTCHARs(lpCs[1], text, false); int byteCount = textBuffer.length; bool isRightOriented = false; if (!OS.IsWinCE && OS.WIN32_VERSION >= OS.VERSION(4, 10)) { isRightOriented = OS.GetLayout(gc.handle) !is 0; } GCP_RESULTS result; result.lStructSize = GCP_RESULTS.sizeof; result.nGlyphs = byteCount; auto lpOrder = result.lpOrder = cast(uint*) OS.HeapAlloc(hHeap, OS.HEAP_ZERO_MEMORY, byteCount * 4); auto lpClass = result.lpClass = cast(char*) OS.HeapAlloc(hHeap, OS.HEAP_ZERO_MEMORY, byteCount); // set required dwFlags, these values will affect how the text gets rendered and // ordered int dwFlags = 0; // Always reorder. We assume that if we are calling this function we're // on a platform that supports bidi. Fixes 20690. dwFlags |= GCP_REORDER; if ((fontLanguageInfo & GCP_LIGATE) is GCP_LIGATE) { dwFlags |= GCP_LIGATE; } if ((fontLanguageInfo & GCP_GLYPHSHAPE) is GCP_GLYPHSHAPE) { dwFlags |= GCP_GLYPHSHAPE; } if ((flags & CLASSIN) is CLASSIN) { // set classification values for the substring, classification values // can be specified on input dwFlags |= GCP_CLASSIN; OS.MoveMemory(result.lpClass, classBuffer.ptr, classBuffer.length); } int glyphCount = 0; for (int i=0; i<offsets.length-1; i++) { int offset = offsets [i]; int length_ = offsets [i+1] - offsets [i]; // The number of glyphs expected is <= length (segment length); // the actual number returned may be less in case of Arabic ligatures. result.nGlyphs = length_; TCHAR[] textBuffer2 = StrToTCHARs(lpCs[1], text.substring(offset, offset + length_), false); OS.GetCharacterPlacement(gc.handle, textBuffer2.ptr, textBuffer2.length, 0, &result, dwFlags); if (order !is null) { int [] order2 = new int [length_]; OS.MoveMemory(order2.ptr, result.lpOrder, order2.length * 4); translateOrder(order2, glyphCount, isRightOriented); System.arraycopy (order2, 0, order, offset, length_); } if (classBuffer !is null) { byte [] classBuffer2 = new byte [length_]; OS.MoveMemory(classBuffer2.ptr, result.lpClass, classBuffer2.length); System.arraycopy (classBuffer2, 0, classBuffer, offset, length_); } glyphCount += result.nGlyphs; // We concatenate successive results of calls to GCP. // For Arabic, it is the only good method since the number of output // glyphs might be less than the number of input characters. // This assumes that the whole line is built by successive adjacent // segments without overlapping. result.lpOrder += length_ * 4; result.lpClass += length_; } /* Free the memory that was allocated. */ OS.HeapFree(hHeap, 0, lpClass); OS.HeapFree(hHeap, 0, lpOrder); } /** * Return bidi attribute information for the font in the specified gc. * <p> * * @param gc the gc to query * @return bitwise OR of the REORDER, LIGATE and GLYPHSHAPE flags * defined by this class. */ public static int getFontBidiAttributes(GC gc) { int fontStyle = 0; int fontLanguageInfo = OS.GetFontLanguageInfo(gc.handle); if (((fontLanguageInfo & GCP_REORDER) !is 0)) { fontStyle |= REORDER; } if (((fontLanguageInfo & GCP_LIGATE) !is 0)) { fontStyle |= LIGATE; } if (((fontLanguageInfo & GCP_GLYPHSHAPE) !is 0)) { fontStyle |= GLYPHSHAPE; } return fontStyle; } /** * Return the active keyboard language type. * <p> * * @return an integer representing the active keyboard language (KEYBOARD_BIDI, * KEYBOARD_NON_BIDI) */ public static int getKeyboardLanguage() { int layout = cast(int) OS.GetKeyboardLayout(0); // only interested in low 2 bytes, which is the primary // language identifier layout = layout & 0x000000FF; if (layout is LANG_HEBREW) return KEYBOARD_BIDI; if (layout is LANG_ARABIC) return KEYBOARD_BIDI; // return non-bidi for all other languages return KEYBOARD_NON_BIDI; } /** * Return the languages that are installed for the keyboard. * <p> * * @return integer array with an entry for each installed language */ static void*[] getKeyboardLanguageList() { int maxSize = 10; void*[] tempList = new void*[maxSize]; int size = OS.GetKeyboardLayoutList(maxSize, tempList.ptr); void*[] list = new void*[size]; System.arraycopy(tempList, 0, list, 0, size); return list; } /** * Return whether or not the platform supports a bidi language. Determine this * by looking at the languages that are installed. * <p> * * @return true if bidi is supported, false otherwise. Always * false on Windows CE. */ public static bool isBidiPlatform() { if (OS.IsWinCE) return false; if (isBidiPlatform_ !is -1) return isBidiPlatform_ is 1; // already set isBidiPlatform_ = 0; // The following test is a workaround for bug report 27629. On WinXP, // both bidi and complex script (e.g., Thai) languages must be installed // at the same time. Since the bidi platform calls do not support // double byte characters, there is no way to run Eclipse using the // complex script languages on XP, so constrain this test to answer true // only if a bidi input language is defined. Doing so will allow complex // script languages to work (e.g., one can install bidi and complex script // languages, but only install the Thai keyboard). if (!isKeyboardBidi()) return false; //Callback callback = null; //try { //callback = new Callback (Class.forName (CLASS_NAME), "EnumSystemLanguageGroupsProc", 5); //$NON-NLS-1$ //int lpEnumSystemLanguageGroupsProc = callback.getAddress (); //if (lpEnumSystemLanguageGroupsProc is 0) DWT.error(DWT.ERROR_NO_MORE_CALLBACKS); OS.EnumSystemLanguageGroups(&EnumSystemLanguageGroupsProc, OS.LGRPID_INSTALLED, 0); //callback.dispose (); //} catch (ClassNotFoundException e) { //if (callback !is null) callback.dispose(); //} if (isBidiPlatform_ is 1) return true; // need to look at system code page for NT & 98 platforms since EnumSystemLanguageGroups is // not supported for these platforms String codePage = to!(String)(OS.GetACP()); if (CD_PG_ARABIC==/*eq*/codePage || CD_PG_HEBREW==/*eq*/codePage) { isBidiPlatform_ = 1; } return isBidiPlatform_ is 1; } /** * Return whether or not the keyboard supports input of a bidi language. Determine this * by looking at the languages that are installed for the keyboard. * <p> * * @return true if bidi is supported, false otherwise. */ public static bool isKeyboardBidi() { void*[] list = getKeyboardLanguageList(); for (int i=0; i<list.length; i++) { int id = cast(int)list[i] & 0x000000FF; if ((id is LANG_ARABIC) || (id is LANG_HEBREW)) { return true; } } return false; } /** * Removes the specified language listener. * <p> * * @param hwnd the handle of the Control that is listening for keyboard language changes */ public static void removeLanguageListener (HWND hwnd) { languageMap.remove(hwnd); unsubclass(hwnd); } /** * Switch the keyboard language to the specified language type. We do * not distinguish between multiple bidi or multiple non-bidi languages, so * set the keyboard to the first language of the given type. * <p> * * @param language integer representing language. One of * KEYBOARD_BIDI, KEYBOARD_NON_BIDI. */ public static void setKeyboardLanguage(int language) { // don't switch the keyboard if it doesn't need to be if (language is getKeyboardLanguage()) return; if (language is KEYBOARD_BIDI) { // get the list of active languages void*[] list = getKeyboardLanguageList(); // set to first bidi language for (int i=0; i<list.length; i++) { int id = cast(int)list[i] & 0x000000FF; if ((id is LANG_ARABIC) || (id is LANG_HEBREW)) { OS.ActivateKeyboardLayout(list[i], 0); return; } } } else { // get the list of active languages void*[] list = getKeyboardLanguageList(); // set to the first non-bidi language (anything not // Hebrew or Arabic) for (int i=0; i<list.length; i++) { int id = cast(int)list[i] & 0x000000FF; if ((id !is LANG_HEBREW) && (id !is LANG_ARABIC)) { OS.ActivateKeyboardLayout(list[i], 0); return; } } } } /** * Sets the orientation (writing order) of the specified control. Text will * be right aligned for right to left writing order. * <p> * * @param hwnd the handle of the Control to change the orientation of * @param orientation one of DWT.RIGHT_TO_LEFT or DWT.LEFT_TO_RIGHT * @return true if the orientation was changed, false if the orientation * could not be changed */ public static bool setOrientation (HWND hwnd, int orientation) { if (OS.IsWinCE) return false; if (OS.WIN32_VERSION < OS.VERSION(4, 10)) return false; int bits = OS.GetWindowLong (hwnd, OS.GWL_EXSTYLE); if ((orientation & DWT.RIGHT_TO_LEFT) !is 0) { bits |= OS.WS_EX_LAYOUTRTL; } else { bits &= ~OS.WS_EX_LAYOUTRTL; } OS.SetWindowLong (hwnd, OS.GWL_EXSTYLE, bits); return true; } /** * Override the window proc. * * @param hwnd control to override the window proc of */ static void subclass(HWND hwnd) { HWND key = hwnd; if ( ( key in oldProcMap ) is null) { int oldProc = OS.GetWindowLong(hwnd, OS.GWL_WNDPROC); oldProcMap[key] = cast(WNDPROC)oldProc; WNDPROC t = &windowProc; // test signature OS.SetWindowLong(hwnd, OS.GWL_WNDPROC, cast(int) &windowProc); } } /** * Reverse the character array. Used for right orientation. * * @param charArray character array to reverse */ static void reverse(wchar[] charArray) { int length_ = charArray.length; for (int i = 0; i <= (length_ - 1) / 2; i++) { wchar tmp = charArray[i]; charArray[i] = charArray[length_ - 1 - i]; charArray[length_ - 1 - i] = tmp; } } /** * Reverse the integer array. Used for right orientation. * * @param intArray integer array to reverse */ static void reverse(int[] intArray) { int length_ = intArray.length; for (int i = 0; i <= (length_ - 1) / 2; i++) { int tmp = intArray[i]; intArray[i] = intArray[length_ - 1 - i]; intArray[length_ - 1 - i] = tmp; } } /** * Adjust the order array so that it is relative to the start of the line. Also reverse the order array if the orientation * is to the right. * * @param orderArray integer array of order values to translate * @param glyphCount number of glyphs that have been processed for the current line * @param isRightOriented flag indicating whether or not current orientation is to the right */ static void translateOrder(int[] orderArray, int glyphCount, bool isRightOriented) { int maxOrder = 0; int length_ = orderArray.length; if (isRightOriented) { for (int i=0; i<length_; i++) { maxOrder = Math.max(maxOrder, orderArray[i]); } } for (int i=0; i<length_; i++) { if (isRightOriented) orderArray[i] = maxOrder - orderArray[i]; orderArray [i] += glyphCount; } } /** * Remove the overridden the window proc. * * @param hwnd control to remove the window proc override for */ static void unsubclass(HWND hwnd) { HWND key = hwnd; if (( key in languageMap ) is null && ( key in keyMap ) is null) { WNDPROC proc; if( auto p = key in oldProcMap ){ proc = *p; oldProcMap.remove( key ); } if (proc is null) return; OS.SetWindowLong(hwnd, OS.GWL_WNDPROC, cast(int)proc ); } } /** * Window proc to intercept keyboard language switch event (WS_INPUTLANGCHANGE) * and widget orientation changes. * Run the Control's registered runnable when the keyboard language is switched. * * @param hwnd handle of the control that is listening for the keyboard language * change event * @param msg window message */ static extern(Windows) int windowProc (HWND hwnd, uint msg, uint wParam, int lParam) { HWND key = hwnd; switch (msg) { case 0x51 /*OS.WM_INPUTLANGCHANGE*/: Runnable runnable = languageMap[key]; if (runnable !is null) runnable.run (); break; default: } auto oldProc = oldProcMap[key]; return OS.CallWindowProc ( oldProc, hwnd, msg, wParam, lParam); } }