summaryrefslogtreecommitdiff
path: root/tk/win/tkWinX.c
diff options
context:
space:
mode:
Diffstat (limited to 'tk/win/tkWinX.c')
-rw-r--r--tk/win/tkWinX.c513
1 files changed, 488 insertions, 25 deletions
diff --git a/tk/win/tkWinX.c b/tk/win/tkWinX.c
index aed6113e888..93c305dc78e 100644
--- a/tk/win/tkWinX.c
+++ b/tk/win/tkWinX.c
@@ -16,12 +16,62 @@
#include "tkWinInt.h"
/*
+ * The w32api 1.1 package (included in Mingw 1.1) does not define _WIN32_IE
+ * by default. Define it here to gain access to the InitCommonControlsEx API
+ * in commctrl.h.
+ */
+
+#ifndef _WIN32_IE
+#define _WIN32_IE 0x0300
+#endif
+
+#include <commctrl.h>
+
+/*
* The zmouse.h file includes the definition for WM_MOUSEWHEEL.
*/
#include <zmouse.h>
/*
+ * imm.h is needed by HandleIMEComposition
+ */
+
+#include <imm.h>
+
+static TkWinProcs asciiProcs = {
+ 0,
+
+ (LRESULT (WINAPI *)(WNDPROC lpPrevWndFunc, HWND hWnd, UINT Msg,
+ WPARAM wParam, LPARAM lParam)) CallWindowProcA,
+ (LRESULT (WINAPI *)(HWND hWnd, UINT Msg, WPARAM wParam,
+ LPARAM lParam)) DefWindowProcA,
+ (ATOM (WINAPI *)(CONST WNDCLASS *lpWndClass)) RegisterClassA,
+ (BOOL (WINAPI *)(HWND hWnd, LPCTSTR lpString)) SetWindowTextA,
+ (HWND (WINAPI *)(DWORD dwExStyle, LPCTSTR lpClassName,
+ LPCTSTR lpWindowName, DWORD dwStyle, int x, int y,
+ int nWidth, int nHeight, HWND hWndParent, HMENU hMenu,
+ HINSTANCE hInstance, LPVOID lpParam)) CreateWindowExA,
+};
+
+static TkWinProcs unicodeProcs = {
+ 1,
+
+ (LRESULT (WINAPI *)(WNDPROC lpPrevWndFunc, HWND hWnd, UINT Msg,
+ WPARAM wParam, LPARAM lParam)) CallWindowProcW,
+ (LRESULT (WINAPI *)(HWND hWnd, UINT Msg, WPARAM wParam,
+ LPARAM lParam)) DefWindowProcW,
+ (ATOM (WINAPI *)(CONST WNDCLASS *lpWndClass)) RegisterClassW,
+ (BOOL (WINAPI *)(HWND hWnd, LPCTSTR lpString)) SetWindowTextW,
+ (HWND (WINAPI *)(DWORD dwExStyle, LPCTSTR lpClassName,
+ LPCTSTR lpWindowName, DWORD dwStyle, int x, int y,
+ int nWidth, int nHeight, HWND hWndParent, HMENU hMenu,
+ HINSTANCE hInstance, LPVOID lpParam)) CreateWindowExW,
+};
+
+TkWinProcs *tkWinProcs;
+
+/*
* Declarations of static variables used in this file.
*/
@@ -29,9 +79,12 @@ static char winScreenName[] = ":0"; /* Default name of windows display. */
static HINSTANCE tkInstance; /* Application instance handle. */
static int childClassInitialized; /* Registered child class? */
static WNDCLASS childClass; /* Window class for child windows. */
-static int tkPlatformId; /* version of Windows platform */
-
-TCL_DECLARE_MUTEX(winXMutex)
+static int tkPlatformId = 0; /* version of Windows platform */
+static Tcl_Encoding keyInputEncoding = NULL;/* The current character
+ * encoding for keyboard input */
+static int keyInputCharset = -1; /* The Win32 CHARSET for the keyboard
+ * encoding */
+static Tcl_Encoding unicodeEncoding = NULL; /* unicode encoding */
/*
* Thread local storage. Notice that now each thread must have its
@@ -54,6 +107,9 @@ static void GenerateXEvent _ANSI_ARGS_((HWND hwnd, UINT message,
static unsigned int GetState _ANSI_ARGS_((UINT message, WPARAM wParam,
LPARAM lParam));
static void GetTranslatedKey _ANSI_ARGS_((XKeyEvent *xkey));
+static void UpdateInputLanguage _ANSI_ARGS_((int charset));
+static int HandleIMEComposition _ANSI_ARGS_((HWND hwnd,
+ LPARAM lParam));
/*
*----------------------------------------------------------------------
@@ -85,8 +141,14 @@ TkGetServerInfo(interp, tkwin)
os.dwOSVersionInfoSize = sizeof(OSVERSIONINFO);
GetVersionEx(&os);
- sprintf(buffer, "Windows %d.%d %d Win32", os.dwMajorVersion,
- os.dwMinorVersion, os.dwBuildNumber);
+ sprintf(buffer, "Windows %d.%d %d %s", os.dwMajorVersion,
+ os.dwMinorVersion, os.dwBuildNumber,
+#ifdef _WIN64
+ "Win64"
+#else
+ "Win32"
+#endif
+ );
Tcl_SetResult(interp, buffer, TCL_VOLATILE);
}
@@ -132,18 +194,27 @@ void
TkWinXInit(hInstance)
HINSTANCE hInstance;
{
- OSVERSIONINFO os;
-
if (childClassInitialized != 0) {
return;
}
childClassInitialized = 1;
- tkInstance = hInstance;
+ if (TkWinGetPlatformId() == VER_PLATFORM_WIN32_NT) {
+ /*
+ * This is necessary to enable the use of themeable elements on XP,
+ * so we don't even try and call it for Win9*.
+ */
- os.dwOSVersionInfoSize = sizeof(OSVERSIONINFO);
- GetVersionEx(&os);
- tkPlatformId = os.dwPlatformId;
+ INITCOMMONCONTROLSEX comctl;
+ ZeroMemory(&comctl, sizeof(comctl));
+ (void) InitCommonControlsEx(&comctl);
+
+ tkWinProcs = &unicodeProcs;
+ } else {
+ tkWinProcs = &asciiProcs;
+ }
+
+ tkInstance = hInstance;
/*
* When threads are enabled, we cannot use CLASSDC because
@@ -209,6 +280,11 @@ TkWinXCleanup(hInstance)
UnregisterClass(TK_WIN_CHILD_CLASS_NAME, hInstance);
}
+ if (unicodeEncoding != NULL) {
+ Tcl_FreeEncoding(unicodeEncoding);
+ unicodeEncoding = NULL;
+ }
+
/*
* And let the window manager clean up its own class(es).
*/
@@ -222,7 +298,7 @@ TkWinXCleanup(hInstance)
* TkWinGetPlatformId --
*
* Determines whether running under NT, 95, or Win32s, to allow
- * runtime conditional code.
+ * runtime conditional code. Win32s is no longer supported.
*
* Results:
* The return value is one of:
@@ -239,6 +315,13 @@ TkWinXCleanup(hInstance)
int
TkWinGetPlatformId()
{
+ if (tkPlatformId == 0) {
+ OSVERSIONINFO os;
+
+ os.dwOSVersionInfoSize = sizeof(OSVERSIONINFO);
+ GetVersionEx(&os);
+ tkPlatformId = os.dwPlatformId;
+ }
return tkPlatformId;
}
@@ -259,10 +342,10 @@ TkWinGetPlatformId()
*----------------------------------------------------------------------
*/
-char *
+CONST char *
TkGetDefaultScreenName(interp, screenName)
Tcl_Interp *interp; /* Not used. */
- char *screenName; /* If NULL, use default string. */
+ CONST char *screenName; /* If NULL, use default string. */
{
if ((screenName == NULL) || (screenName[0] == '\0')) {
screenName = winScreenName;
@@ -289,7 +372,7 @@ TkGetDefaultScreenName(interp, screenName)
TkDisplay *
TkpOpenDisplay(display_name)
- char *display_name;
+ CONST char *display_name;
{
Screen *screen;
HDC dc;
@@ -308,6 +391,8 @@ TkpOpenDisplay(display_name)
}
display = (Display *) ckalloc(sizeof(Display));
+ ZeroMemory(display, sizeof(Display));
+
display->display_name = (char *) ckalloc(strlen(display_name)+1);
strcpy(display->display_name, display_name);
@@ -339,7 +424,7 @@ TkpOpenDisplay(display_name)
twdPtr->window.winPtr = NULL;
twdPtr->window.handle = NULL;
screen->root = (Window)twdPtr;
-
+
/*
* On windows, when creating a color bitmap, need two pieces of
* information: the number of color planes and the number of
@@ -404,6 +489,7 @@ TkpOpenDisplay(display_name)
screen->cmap = XCreateColormap(display, None, screen->root_visual,
AllocNone);
tsdPtr->winDisplay = (TkDisplay *) ckalloc(sizeof(TkDisplay));
+ ZeroMemory(tsdPtr->winDisplay, sizeof(TkDisplay));
tsdPtr->winDisplay->display = display;
tsdPtr->updatingClipboard = FALSE;
return tsdPtr->winDisplay;
@@ -472,7 +558,6 @@ TkpCloseDisplay(dispPtr)
ckfree((char *) display->screens);
}
ckfree((char *) display);
- ckfree((char *) dispPtr);
}
/*
@@ -526,6 +611,18 @@ TkWinChildProc(hwnd, message, wParam, lParam)
LRESULT result;
switch (message) {
+ case WM_INPUTLANGCHANGE:
+ UpdateInputLanguage(wParam);
+ result = 1;
+ break;
+
+ case WM_IME_COMPOSITION:
+ result = 0;
+ if (HandleIMEComposition(hwnd, lParam) == 0) {
+ result = DefWindowProc(hwnd, message, wParam, lParam);
+ }
+ break;
+
case WM_SETCURSOR:
/*
* Short circuit the WM_SETCURSOR message since we set
@@ -738,6 +835,15 @@ GenerateXEvent(hwnd, message, wParam, lParam)
while (otherWinPtr && !(otherWinPtr->flags & TK_TOP_LEVEL)) {
otherWinPtr = otherWinPtr->parentPtr;
}
+
+ /*
+ * Do a catch-all Tk_SetCaretPos here to make sure that the
+ * window receiving focus sets the caret at least once.
+ */
+ if (message == WM_SETFOCUS) {
+ Tk_SetCaretPos((Tk_Window) winPtr, 0, 0, 0);
+ }
+
if (otherWinPtr == winPtr) {
return;
}
@@ -746,6 +852,14 @@ GenerateXEvent(hwnd, message, wParam, lParam)
event.type = (message == WM_SETFOCUS) ? FocusIn : FocusOut;
event.xfocus.mode = NotifyNormal;
event.xfocus.detail = NotifyNonlinear;
+
+ /*
+ * Destroy the caret if we own it. If we are moving to another Tk
+ * window, it will reclaim and reposition it with Tk_SetCaretPos.
+ */
+ if (message == WM_KILLFOCUS) {
+ DestroyCaret();
+ }
break;
}
@@ -828,13 +942,11 @@ GenerateXEvent(hwnd, message, wParam, lParam)
/*
* Check for translated characters in the event queue.
* Setting xany.send_event to -1 indicates to the
- * Windows implementation of XLookupString that this
+ * Windows implementation of TkpGetString() that this
* event was generated by windows and that the Windows
* extension xkey.trans_chars is filled with the
- * characters that came from the TranslateMessage
- * call. If it is not -1, xkey.keycode is the
- * virtual key being sent programmatically by generic
- * code.
+ * MBCS characters that came from the TranslateMessage
+ * call.
*/
event.type = KeyPress;
@@ -1019,7 +1131,6 @@ GetTranslatedKey(xkey)
XKeyEvent *xkey;
{
MSG msg;
- char buf[XMaxTransChars];
xkey->nbytes = 0;
@@ -1039,9 +1150,21 @@ GetTranslatedKey(xkey)
if ((msg.message == WM_CHAR) && (msg.lParam & 0x20000000)) {
xkey->state = 0;
}
- buf[xkey->nbytes] = (char) msg.wParam;
xkey->trans_chars[xkey->nbytes] = (char) msg.wParam;
xkey->nbytes++;
+
+ if (((unsigned short) msg.wParam) > ((unsigned short) 0xff)) {
+ /*
+ * Some "addon" input devices, such as the popular
+ * PenPower Chinese writing pad, generate 16 bit
+ * values in WM_CHAR messages (instead of passing them
+ * in two separate WM_CHAR messages containing two
+ * 8-bit values.
+ */
+
+ xkey->trans_chars[xkey->nbytes] = (char) (msg.wParam >> 8);
+ xkey->nbytes ++;
+ }
} else {
break;
}
@@ -1051,9 +1174,252 @@ GetTranslatedKey(xkey)
/*
*----------------------------------------------------------------------
*
+ * UpdateInputLanguage --
+ *
+ * Gets called when a WM_INPUTLANGCHANGE message is received
+ * by the TK child window procedure. This message is sent
+ * by the Input Method Editor system when the user chooses
+ * a different input method. All subsequent WM_CHAR
+ * messages will contain characters in the new encoding. We record
+ * the new encoding so that TkpGetString() knows how to
+ * correctly translate the WM_CHAR into unicode.
+ *
+ * Results:
+ * Records the new encoding in keyInputEncoding.
+ *
+ * Side effects:
+ * Old value of keyInputEncoding is freed.
+ *
+ *----------------------------------------------------------------------
+ */
+
+static void
+UpdateInputLanguage(charset)
+ int charset;
+{
+ CHARSETINFO charsetInfo;
+ Tcl_Encoding encoding;
+ char codepage[4 + TCL_INTEGER_SPACE];
+
+ if (keyInputCharset == charset) {
+ return;
+ }
+ if (TranslateCharsetInfo((DWORD*)charset, &charsetInfo, TCI_SRCCHARSET)
+ == 0) {
+ /*
+ * Some mysterious failure.
+ */
+
+ return;
+ }
+
+ wsprintfA(codepage, "cp%d", charsetInfo.ciACP);
+
+ if ((encoding = Tcl_GetEncoding(NULL, codepage)) == NULL) {
+ /*
+ * The encoding is not supported by Tcl.
+ */
+
+ return;
+ }
+
+ if (keyInputEncoding != NULL) {
+ Tcl_FreeEncoding(keyInputEncoding);
+ }
+
+ keyInputEncoding = encoding;
+ keyInputCharset = charset;
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * TkWinGetKeyInputEncoding --
+ *
+ * Returns the current keyboard input encoding selected by the
+ * user (with WM_INPUTLANGCHANGE events).
+ *
+ * Results:
+ * The current keyboard input encoding.
+ *
+ * Side effects:
+ * None.
+ *
+ *----------------------------------------------------------------------
+ */
+
+Tcl_Encoding
+TkWinGetKeyInputEncoding()
+{
+ return keyInputEncoding;
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * TkWinGetUnicodeEncoding --
+ *
+ * Returns the cached unicode encoding.
+ *
+ * Results:
+ * The unicode encoding.
+ *
+ * Side effects:
+ * None.
+ *
+ *----------------------------------------------------------------------
+ */
+
+Tcl_Encoding
+TkWinGetUnicodeEncoding()
+{
+ if (unicodeEncoding == NULL) {
+ unicodeEncoding = Tcl_GetEncoding(NULL, "unicode");
+ }
+ return unicodeEncoding;
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * HandleIMEComposition --
+ *
+ * This function works around a definciency in some versions
+ * of Windows 2000 to make it possible to entry multi-lingual
+ * characters under all versions of Windows 2000.
+ *
+ * When an Input Method Editor (IME) is ready to send input
+ * characters to an application, it sends a WM_IME_COMPOSITION
+ * message with the GCS_RESULTSTR. However, The DefWindowProc()
+ * on English Windows 2000 arbitrarily converts all non-Latin-1
+ * characters in the composition to "?".
+ *
+ * This function correctly processes the composition data and
+ * sends the UNICODE values of the composed characters to
+ * TK's event queue.
+ *
+ * Results:
+ * If this function has processed the composition data, returns 1.
+ * Otherwise returns 0.
+ *
+ * Side effects:
+ * Key events are put into the TK event queue.
+ *
+ *----------------------------------------------------------------------
+ */
+
+static int
+HandleIMEComposition(hwnd, lParam)
+ HWND hwnd; /* Window receiving the message. */
+ LPARAM lParam; /* Flags for the WM_IME_COMPOSITION
+ * message */
+{
+ HIMC hIMC;
+ int i, n;
+ XEvent event;
+ char * buff;
+ TkWindow *winPtr;
+ Tcl_Encoding unicodeEncoding = TkWinGetUnicodeEncoding();
+ BOOL isWinNT = (TkWinGetPlatformId() == VER_PLATFORM_WIN32_NT);
+
+ if ((lParam & GCS_RESULTSTR) == 0) {
+ /*
+ * Composition is not finished yet.
+ */
+
+ return 0;
+ }
+
+ hIMC = ImmGetContext(hwnd);
+ if (hIMC) {
+ if (isWinNT) {
+ n = ImmGetCompositionStringW(hIMC, GCS_RESULTSTR, NULL, 0);
+ } else {
+ n = ImmGetCompositionStringA(hIMC, GCS_RESULTSTR, NULL, 0);
+ }
+
+ if ((n > 0) && ((buff = (char *) ckalloc(n)) != NULL)) {
+ if (isWinNT) {
+ n = ImmGetCompositionStringW(hIMC, GCS_RESULTSTR, buff, n);
+ } else {
+ Tcl_DString utfString, unicodeString;
+
+ n = ImmGetCompositionStringA(hIMC, GCS_RESULTSTR, buff, n);
+ Tcl_DStringInit(&utfString);
+ Tcl_ExternalToUtfDString(keyInputEncoding, buff, n,
+ &utfString);
+ Tcl_UtfToExternalDString(unicodeEncoding,
+ Tcl_DStringValue(&utfString), -1, &unicodeString);
+ i = Tcl_DStringLength(&unicodeString);
+ if (n < i) {
+ /*
+ * Only alloc more space if we need, otherwise just
+ * use what we've created. Don't realloc as that may
+ * copy data we no longer need.
+ */
+ ckfree((char *) buff);
+ buff = (char *) ckalloc(i);
+ }
+ n = i;
+ memcpy(buff, Tcl_DStringValue(&unicodeString), n);
+ Tcl_DStringFree(&utfString);
+ Tcl_DStringFree(&unicodeString);
+ }
+
+ /*
+ * Set up the fields pertinent to key event.
+ *
+ * We set send_event to the special value of -2, so that
+ * TkpGetString() in tkWinKey.c knows that trans_chars[]
+ * already contains a UNICODE char and there's no need to
+ * do encoding conversion.
+ */
+
+ winPtr = (TkWindow *)Tk_HWNDToWindow(hwnd);
+
+ event.xkey.serial = winPtr->display->request++;
+ event.xkey.send_event = -2;
+ event.xkey.display = winPtr->display;
+ event.xkey.window = winPtr->window;
+ event.xkey.root = RootWindow(winPtr->display, winPtr->screenNum);
+ event.xkey.subwindow = None;
+ event.xkey.state = TkWinGetModifierState();
+ event.xkey.time = TkpGetMS();
+ event.xkey.same_screen = True;
+ event.xkey.keycode = 0;
+ event.xkey.nbytes = 2;
+
+ for (i=0; i<n;) {
+ /*
+ * Simulate a pair of KeyPress and KeyRelease events
+ * for each UNICODE character in the composition.
+ */
+
+ event.xkey.trans_chars[0] = (char) buff[i++];
+ event.xkey.trans_chars[1] = (char) buff[i++];
+
+ event.type = KeyPress;
+ Tk_QueueWindowEvent(&event, TCL_QUEUE_TAIL);
+
+ event.type = KeyRelease;
+ Tk_QueueWindowEvent(&event, TCL_QUEUE_TAIL);
+ }
+
+ ckfree(buff);
+ }
+ ImmReleaseContext(hwnd, hIMC);
+ return 1;
+ }
+
+ return 0;
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
* Tk_FreeXId --
*
- * This inteface is not needed under Windows.
+ * This interface is not needed under Windows.
*
* Results:
* None.
@@ -1184,3 +1550,100 @@ TkWinUpdatingClipboard(int mode)
tsdPtr->updatingClipboard = mode;
}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * Tk_SetCaretPos --
+ *
+ * This enables correct movement of focus in the MS Magnifier, as well
+ * as allowing us to correctly position the IME Window. The following
+ * Win32 APIs are used to work with MS caret:
+ *
+ * CreateCaret DestroyCaret SetCaretPos GetCaretPos
+ *
+ * Only one instance of caret can be active at any time
+ * (e.g. DestroyCaret API does not take any argument such as handle).
+ * Since do-it-right approach requires to track the create/destroy
+ * caret status all the time in a global scope among windows (or
+ * widgets), we just implement this minimal setup to get the job done.
+ *
+ * Results:
+ * None
+ *
+ * Side effects:
+ * Sets the global Windows caret position.
+ *
+ *----------------------------------------------------------------------
+ */
+
+void
+Tk_SetCaretPos(Tk_Window tkwin, int x, int y, int height)
+{
+ static HWND caretHWND = NULL;
+ TkCaret *caretPtr = &(((TkWindow *) tkwin)->dispPtr->caret);
+ Window win;
+
+ /*
+ * Prevent processing anything if the values haven't changed.
+ * Windows only has one display, so we can do this with statics.
+ */
+ if ((caretPtr->winPtr == ((TkWindow *) tkwin))
+ && (caretPtr->x == x) && (caretPtr->y == y)) {
+ return;
+ }
+
+ caretPtr->winPtr = ((TkWindow *) tkwin);
+ caretPtr->x = x;
+ caretPtr->y = y;
+ caretPtr->height = height;
+
+ /*
+ * We adjust to the toplevel to get the coords right, as setting
+ * the IME composition window is based on the toplevel hwnd, so
+ * ignore height.
+ */
+
+ while (!Tk_IsTopLevel(tkwin)) {
+ x += Tk_X(tkwin);
+ y += Tk_Y(tkwin);
+ tkwin = Tk_Parent(tkwin);
+ if (tkwin == NULL) {
+ return;
+ }
+ }
+
+ win = Tk_WindowId(tkwin);
+ if (win) {
+ HIMC hIMC;
+ HWND hwnd = Tk_GetHWND(win);
+
+ if (hwnd != caretHWND) {
+ DestroyCaret();
+ if (CreateCaret(hwnd, NULL, 0, 0)) {
+ caretHWND = hwnd;
+ }
+ }
+
+ if (!SetCaretPos(x, y) && CreateCaret(hwnd, NULL, 0, 0)) {
+ caretHWND = hwnd;
+ SetCaretPos(x, y);
+ }
+
+ /*
+ * The IME composition window should be updated whenever the caret
+ * position is changed because a clause of the composition string may
+ * be converted to the final characters and the other clauses still
+ * stay on the composition window. -- yamamoto
+ */
+ hIMC = ImmGetContext(hwnd);
+ if (hIMC) {
+ COMPOSITIONFORM cform;
+ cform.dwStyle = CFS_POINT;
+ cform.ptCurrentPos.x = x;
+ cform.ptCurrentPos.y = y;
+ ImmSetCompositionWindow(hIMC, &cform);
+ ImmReleaseContext(hwnd, hIMC);
+ }
+ }
+}