changeset 154:b0279a69d976

BidiUtil
author Frank Benoit <benoit@tionex.de>
date Thu, 14 Feb 2008 19:07:08 +0100
parents 6fbc419f4acd
children a5afe31f5cdd
files README.txt dwt/internal/BidiUtil.d
diffstat 2 files changed, 669 insertions(+), 9 deletions(-) [+]
line wrap: on
line diff
--- a/README.txt	Thu Feb 14 19:06:58 2008 +0100
+++ b/README.txt	Thu Feb 14 19:07:08 2008 +0100
@@ -26,6 +26,13 @@
 Disable this option in the dsss/etc/rebuild/dmd-win-tango file.
 Search for it (2 matches) and either delete those lines of change =on to =off.
 
+In some situations, you may get linker errors when building the dwt examples.  
+Try adding the "-full" switch to the dsss command line to fix these errors.
+
+Example:
+
+dsss build -full simple  
+
 Subsystem linker option
 =======================
 For dmd linker 'optlink' there is the option SUBSYSTEM which defines if the executable
@@ -33,15 +40,6 @@
 -L/SUBSYSTEM:windows:5
 Without this option, DWT renderes some controls not correctly. Eg. table headers are not shown.
 
-Themes
-======
-To use theming in winxp, there is a manifest necessary. This forces windows to load comctl32.dll
-in version 6 instead of 5.
-Use the resource compiler from visual studio "rc dwt.rc" to get dwt.res
-Link it with -L/RC:dwt.res
-
-
-
 Changes/Additions to SWT
 ========================
   o MessageBox can be instantiated without parent. Use the Ctor "this( int style )",
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dwt/internal/BidiUtil.d	Thu Feb 14 19:07:08 2008 +0100
@@ -0,0 +1,662 @@
+/*******************************************************************************
+ * 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.Integer;
+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 char[] 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 char[] CD_PG_HEBREW = "1255"; //$NON-NLS-1$
+    static const char[] 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, char[] 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, char[] 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
+    char[] codePage = to!(char[])(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;
+        }
+    auto oldProc = oldProcMap[key];
+    return OS.CallWindowProc ( oldProc, hwnd, msg, wParam, lParam);
+}
+
+}