new PixelBuffer API (mainly tons of renames)
[org.ibex.core.git] / src / org / ibex / plat / Win32.cc
1 // Copyright 2002 Adam Megacz, see the COPYING file for licensing [LGPL]
2 #include "GCJ.cc"
3
4 // we have to do this because the jpeg libraries use the symbol 'INT32'
5 #define INT32 WIN32_INT32
6
7 // this has to precede the others so we don't get collisions on min/max
8 #include <org/ibex/js/JS.h>
9 #include <org/ibex/Box.h>
10
11 #include <stdint.h>
12 #include <stdlib.h>
13 #include <stdio.h>
14
15 #include <windows.h>
16 #include <mmsystem.h>
17 #undef STRICT
18 #undef MAX_PRIORITY
19 #undef MIN_PRIORITY
20 #undef NORM_PRIORITY
21
22 #include <gcj/cni.h>
23
24 #include <java/lang/Integer.h>
25 #include <java/util/Hashtable.h>
26 #include <org/ibex/Box.h>
27 #include <org/ibex/Surface.h>
28 #include <org/ibex/PixelBuffer.h>
29 #include <org/ibex/Picture.h>
30 #include <org/ibex/Platform.h>
31 #include <org/ibex/plat/Win32.h>
32 #include <org/ibex/plat/Win32$Win32Surface.h>
33 #include <org/ibex/plat/Win32$Win32PixelBuffer.h>
34 #include <org/ibex/plat/Win32$Win32Picture.h>
35 #include <org/ibex/util/Log.h>
36 #include <org/ibex/util/Semaphore.h>
37
38 // for debugging
39 #include <java/lang/System.h>
40 #include <java/io/PrintStream.h>
41
42 #define WM_USER_SETCURSOR WM_USER
43 #define WM_USER_DISPOSE (WM_USER + 1)
44 #define WM_USER_CREATEWINDOW (WM_USER + 2)
45 #define WS_NORMAL (WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU | WS_THICKFRAME | WS_MINIMIZEBOX | WS_MAXIMIZEBOX)
46
47 // FEATURE: there are lots of places where HANDLE's get casted to jint's -- this will break on Win64
48 //          a clean way to do this would be to '#define jraw (gnu::gcj::RawData*)'
49
50 // Callbacks ////////////////////////////////////////////////////////////////////
51
52 LRESULT CALLBACK WndProc(HWND hwnd, UINT iMsg, WPARAM wParam, LPARAM lParam) {
53     org::ibex::plat::Win32$Win32Surface* surface =
54         (org::ibex::plat::Win32$Win32Surface*)org::ibex::plat::Win32::hwndToWin32SurfaceMap->get(new java::lang::Integer((jint)hwnd));
55
56     if (surface != NULL) {
57         return (LRESULT)surface->WndProc((jint)hwnd, (jint)iMsg, (jint)wParam, (jint)lParam);
58
59     } else {
60         // this is really lame -- Win32 insists on being able to call your WndProc BEFORE CreateWindow returns...
61         return DefWindowProc(hwnd, iMsg, wParam, lParam);
62     }
63 }
64
65
66 // Initialization ////////////////////////////////////////////////////////////////////
67
68 static int window_class_counter = 0;
69
70 jstring org::ibex::plat::Win32::getTempPath() {
71     char buf[1024];
72     DWORD ret = GetTempPath(1024, buf);
73     if (ret == 0) criticalAbort(JvNewStringLatin1("GetTempPath() failed"));
74     return JvNewStringLatin1(buf);
75 }
76
77 // XOR mask for the hand cursor
78 static unsigned char hand_cursor_xor[32 * 4] = {
79   0x00, 0x00, 0x00, 0x00,
80   0x00, 0x0C, 0x00, 0x00,
81   0x00, 0x0C, 0x00, 0x00,
82   0x00, 0x0C, 0x00, 0x00,
83   0x00, 0x0C, 0x00, 0x00,
84   0x00, 0x0C, 0x00, 0x00,
85   0x00, 0x0D, 0x80, 0x00,
86   0x00, 0x0D, 0xB0, 0x00,
87   0x00, 0x0D, 0xB4, 0x00,
88   0x00, 0x0D, 0xB6, 0x00,
89   0x00, 0xCF, 0xF6, 0x00,
90   0x00, 0xEF, 0xFE, 0x00,
91   0x00, 0x6F, 0xFE, 0x00,
92   0x00, 0x2F, 0xFE, 0x00,
93   0x00, 0x3F, 0xFE, 0x00,
94   0x00, 0x1F, 0xFE, 0x00,
95   0x00, 0x1F, 0xFC, 0x00,
96   0x00, 0x0F, 0xFC, 0x00,
97   0x00, 0x0F, 0xFC, 0x00,
98   0x00, 0x07, 0xF8, 0x00,
99   0x00, 0x07, 0xF8, 0x00,
100   0x00, 0x00, 0x00, 0x00,
101   0x00, 0x00, 0x00, 0x00,
102   0x00, 0x00, 0x00, 0x00,
103   0x00, 0x00, 0x00, 0x00,
104   0x00, 0x00, 0x00, 0x00,
105   0x00, 0x00, 0x00, 0x00,
106   0x00, 0x00, 0x00, 0x00,
107   0x00, 0x00, 0x00, 0x00,
108   0x00, 0x00, 0x00, 0x00,
109   0x00, 0x00, 0x00, 0x00,
110   0x00, 0x00, 0x00, 0x00
111 };
112
113 // AND mask for the hand cursor
114 static unsigned char hand_cursor_and[32 * 4] = {
115   0xFF, 0xF3, 0xFF, 0xFF,
116   0xFF, 0xE1, 0xFF, 0xFF,
117   0xFF, 0xE1, 0xFF, 0xFF,
118   0xFF, 0xE1, 0xFF, 0xFF,
119   0xFF, 0xE1, 0xFF, 0xFF,
120   0xFF, 0xE0, 0x7F, 0xFF,
121   0xFF, 0xE0, 0x0F, 0xFF,
122   0xFF, 0xE0, 0x03, 0xFF,
123   0xFF, 0xE0, 0x01, 0xFF,
124   0xFF, 0x20, 0x00, 0xFF,
125   0xFE, 0x00, 0x00, 0xFF,
126   0xFE, 0x00, 0x00, 0xFF,
127   0xFF, 0x00, 0x00, 0xFF,
128   0xFF, 0x80, 0x00, 0xFF,
129   0xFF, 0x80, 0x00, 0xFF,
130   0xFF, 0xC0, 0x00, 0xFF,
131   0xFF, 0xC0, 0x01, 0xFF,
132   0xFF, 0xE0, 0x01, 0xFF,
133   0xFF, 0xE0, 0x01, 0xFF,
134   0xFF, 0xF0, 0x03, 0xFF,
135   0xFF, 0xF0, 0x03, 0xFF,
136   0xFF, 0xF0, 0x03, 0xFF,
137   0xFF, 0xFF, 0xFF, 0xFF,
138   0xFF, 0xFF, 0xFF, 0xFF,
139   0xFF, 0xFF, 0xFF, 0xFF,
140   0xFF, 0xFF, 0xFF, 0xFF,
141   0xFF, 0xFF, 0xFF, 0xFF,
142   0xFF, 0xFF, 0xFF, 0xFF,
143   0xFF, 0xFF, 0xFF, 0xFF,
144   0xFF, 0xFF, 0xFF, 0xFF,
145   0xFF, 0xFF, 0xFF, 0xFF,
146   0xFF, 0xFF, 0xFF, 0xFF
147 };
148
149 void org::ibex::plat::Win32::natPreInit() {
150   // Win32 throws stderr in the trash unless you designate your binary as a "console binary"
151   if (org::ibex::util::Log::logstream == java::lang::System::err) {
152         AllocConsole();
153         freopen("CONOUT$", "w+t", stderr);
154     }
155 }
156
157 void org::ibex::plat::Win32::natInit() {
158
159     // grab desktop dc/handle
160     desktop_handle = (jint)GetDesktopWindow();
161     desktop_dc = (jint)GetDC((HWND)desktop_handle);
162
163     // create cursors
164     org::ibex::plat::Win32::wait_cursor = (jint)LoadCursor(NULL, IDC_WAIT);
165     org::ibex::plat::Win32::default_cursor = (jint)LoadCursor(NULL, IDC_ARROW);
166     org::ibex::plat::Win32::crosshair_cursor = (jint)LoadCursor(NULL, IDC_CROSS);
167     org::ibex::plat::Win32::text_cursor = (jint)LoadCursor(NULL, IDC_IBEAM);
168     org::ibex::plat::Win32::move_cursor = (jint)LoadCursor(NULL, IDC_SIZEALL);
169     org::ibex::plat::Win32::sizenesw_cursor = (jint)LoadCursor(NULL, IDC_SIZENESW);
170     org::ibex::plat::Win32::sizens_cursor = (jint)LoadCursor(NULL, IDC_SIZENS);
171     org::ibex::plat::Win32::sizenwse_cursor = (jint)LoadCursor(NULL, IDC_SIZENWSE);
172     org::ibex::plat::Win32::sizewe_cursor = (jint)LoadCursor(NULL, IDC_SIZEWE);
173     org::ibex::plat::Win32::hand_cursor = (jint)CreateCursor(GetModuleHandle(NULL), 14, 1, 32, 32, hand_cursor_and, hand_cursor_xor);
174
175     messagePumpThread = (jint)GetCurrentThreadId();
176     messagePumpStarted->release();
177
178     MSG msg;
179     while(GetMessage(&msg, (HWND)NULL, 0, 0) > 0) {
180
181         if (msg.message == WM_USER_CREATEWINDOW) {
182             org::ibex::plat::Win32$Win32Surface *surface = (org::ibex::plat::Win32$Win32Surface*)msg.lParam;
183
184             // we must create a unique window class name for each
185             // window so that minimization icons can be set independantly
186             char buf[255];
187             sprintf(buf, "Ibex_WINDOW_CLASS_%i", window_class_counter++);
188
189             WNDCLASSEX wc;
190             wc.style = CS_DBLCLKS | CS_HREDRAW | CS_VREDRAW;
191             wc.lpfnWndProc = WndProc;
192             wc.cbClsExtra = 0;
193             wc.cbSize = sizeof(WNDCLASSEX);
194             wc.cbWndExtra = 0;
195             wc.hInstance = GetModuleHandle(NULL);
196             wc.hIcon = LoadIcon(NULL, IDI_APPLICATION);
197             wc.hIconSm = (HICON)LoadImage(GetModuleHandle(NULL),
198                                           MAKEINTRESOURCE(5),
199                                           IMAGE_ICON,
200                                           GetSystemMetrics(SM_CXSMICON),
201                                           GetSystemMetrics(SM_CYSMICON),
202                                           LR_DEFAULTCOLOR);
203             wc.hCursor = LoadCursor(NULL, IDC_ARROW);
204             wc.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);
205             wc.lpszMenuName = "menu";
206             wc.lpszClassName = buf;
207             RegisterClassEx(&wc);
208
209             surface->hwnd = (jint)CreateWindow(wc.lpszClassName, TEXT(""),
210                                                msg.wParam ? WS_NORMAL : WS_POPUP,
211                                                200, 200, 100, 100,
212                                                (HWND__*)NULL, (HMENU__*)NULL,
213                                                GetModuleHandle(NULL), (LPVOID)NULL);
214             
215             SetFocus((HWND)surface->hwnd);
216             surface->hwndCreated->release();
217             
218         } else {
219             TranslateMessage(&msg);
220             if (msg.message == WM_LBUTTONDOWN || msg.message == WM_RBUTTONDOWN || msg.message == WM_MBUTTONDOWN) SetFocus(msg.hwnd);
221             DispatchMessage(&msg);
222         }
223
224     }
225     java::lang::System::exit(-1);
226 }
227
228
229 // Platform Methods ///////////////////////////////////////////////////////////////////
230
231 jstring org::ibex::plat::Win32::_getEnv(jstring key) {
232     int len = JvGetStringUTFLength(key);
233     char buf[len + 1];
234     JvGetStringUTFRegion(key, 0, len, buf);
235     buf[len] = '\0';
236     char buf2[1024];
237     DWORD ret = GetEnvironmentVariable(buf, buf2, 1024);
238     if (ret > 0 && ret < 1024) return JvNewStringLatin1(buf2);
239     return NULL;
240 }
241
242 jstring org::ibex::plat::Win32::_fileDialog(jstring suggestedFileName, jboolean write) {
243
244     char buf[1024];
245     OPENFILENAME ofn;
246     memset(buf, 0, 1024);
247     memset(&ofn, 0, sizeof(OPENFILENAME));
248
249     if (suggestedFileName != NULL)
250         JvGetStringUTFRegion(suggestedFileName, 0, min(1023, JvGetStringUTFLength(suggestedFileName)), buf);
251     
252     ofn.lStructSize = sizeof(OPENFILENAME);
253     ofn.nMaxCustFilter = 0;
254     ofn.lpstrFile = buf;
255     ofn.nMaxFile = 1024;
256
257     if (write) ofn.Flags |= OFN_OVERWRITEPROMPT;
258     ofn.Flags |= OFN_HIDEREADONLY;
259
260     int ret = write ? GetSaveFileName(&ofn) : GetOpenFileName(&ofn);
261     return ret == 0 ? NULL : JvNewStringLatin1(buf);
262 }
263
264 void org::ibex::plat::Win32::__detectProxy(JArray<jstring>* container) {
265
266     HKEY hkey;
267     char buf[1024];
268     DWORD buflen = 1024;
269     DWORD type;
270     LONG result = RegOpenKey(HKEY_CURRENT_USER, "Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings", &hkey);
271     if (result != ERROR_SUCCESS) return;
272     
273     buf[0] = '\0';
274     type = REG_SZ;
275     buflen = 1024;
276     result = RegQueryValueEx(hkey, "AutoConfigURL", NULL, &type, (LPBYTE)buf, &buflen);
277     buf[buflen] = '\0';
278     if (result == ERROR_SUCCESS) elements(container)[2] = JvNewStringLatin1(buf);
279
280     buf[0] = '\0';
281     type = REG_BINARY;
282     RegQueryValueEx(hkey, "ProxyEnable", NULL, &type, (LPBYTE)buf, &buflen);
283     if (buf[0] != 1) return;
284
285     buf[0] = '\0';
286     type = REG_SZ;
287     buflen = 1024;
288     RegQueryValueEx(hkey, "ProxyServer", NULL, &type, (LPBYTE)buf, &buflen);
289     buf[buflen] = '\0';
290     elements(container)[0] = JvNewStringLatin1(buf);
291
292     buf[0] = '\0';
293     buflen = 1024;
294     RegQueryValueEx(hkey, "ProxyOverride", NULL, &type, (LPBYTE)buf, &buflen);
295     buf[buflen] = '\0';
296     elements(container)[1] = JvNewStringLatin1(buf);
297 }
298
299 jstring org::ibex::plat::Win32::_getClipBoard() {
300     OpenClipboard((HWND)desktop_handle);
301     HGLOBAL hmem = GetClipboardData(CF_TEXT);
302     if (hmem == NULL) return NULL;
303     char* buf = (char*)GlobalLock(hmem);
304     if (buf == NULL) return NULL;
305     jstring ret = JvNewStringLatin1(buf);
306     GlobalUnlock(hmem);
307     CloseClipboard();
308     return ret;
309 }
310
311 void org::ibex::plat::Win32::_setClipBoard(jstring s) {
312     OpenClipboard((HWND)desktop_handle);
313     HGLOBAL hmem = GlobalAlloc(GMEM_MOVEABLE | GMEM_DDESHARE, JvGetStringUTFLength(s) + 1);
314     if (hmem == NULL) return;
315     char* buf = (char*)GlobalLock(hmem);
316     if (buf == NULL) return;
317     JvGetStringUTFRegion(s, 0, JvGetStringUTFLength(s), buf);
318     buf[JvGetStringUTFLength(s)] = '\0';
319     GlobalUnlock(hmem);
320     SetClipboardData(CF_TEXT, hmem);
321     CloseClipboard();
322 }
323
324 void org::ibex::plat::Win32::_criticalAbort(jstring message) {
325     char buf[JvGetStringUTFLength(message) + 1];
326     buf[JvGetStringUTFLength(message)] = '\0';
327     JvGetStringUTFRegion(message, 0, JvGetStringUTFLength(message), buf);
328     MessageBox (NULL, buf, "Ibex Cannot Continue", MB_OK | MB_ICONSTOP | MB_TASKMODAL | MB_SETFOREGROUND);
329     java::lang::System::exit(-1);
330 }
331
332 jint org::ibex::plat::Win32::_getScreenWidth() {
333     RECT rect;
334     SystemParametersInfo(SPI_GETWORKAREA, 0, &rect, 0);
335     return rect.right - rect.left;
336 }
337
338 jint org::ibex::plat::Win32::_getScreenHeight() {
339     RECT rect;
340     SystemParametersInfo(SPI_GETWORKAREA, 0, &rect, 0);
341     return rect.bottom - rect.top;
342 }
343
344 jboolean org::ibex::plat::Win32::_newBrowserWindow_(jstring url) {
345
346     int len = min(2048, JvGetStringUTFLength(url));
347     char buf[len + 1];
348     JvGetStringUTFRegion(url, 0, len, buf);
349     buf[len] = '\0';
350
351     SHELLEXECUTEINFO ei;
352     memset(&ei, 0, sizeof(ei));
353     ei.cbSize = sizeof(ei);
354     ei.lpVerb = "open";
355     ei.lpFile = buf;
356     ei.fMask  = SEE_MASK_NOCLOSEPROCESS;
357     ei.nShow  = SW_SHOWDEFAULT;
358     return (ShellExecuteEx(&ei) == 0);
359
360 }
361
362
363 // Win32PixelBuffer /////////////////////////////////////////////////////////////////////////
364
365 // This is a scratch area; when blitting a translucent image, we copy the underlying data here first.
366 // Since all drawing operations are single-threaded, it's safe to use a global here.
367 static HBITMAP scratch = NULL;
368 static HDC scratch_dc = NULL;
369 static jint* scratch_bits = NULL;
370 static jint scratch_w = 0;
371 static jint scratch_h = 0;
372
373 #define max(a,b) ((a)>(b)?(a):(b))
374 #define min(a,b) ((a)<(b)?(a):(b))
375
376 void org::ibex::plat::Win32$Win32PixelBuffer::drawPicture(org::ibex::graphics::Picture* source0,
377                                                          jint dx, jint dy,
378                                                          jint cx1, jint cy1, jint cx2, jint cy2,
379                                                          jint rgb, jboolean alphaOnly) {
380     org::ibex::plat::Win32$Win32Picture* source = (org::ibex::plat::Win32$Win32Picture*)source0;
381
382     cx1 = max(dx, cx1);
383     cy1 = max(dy, cy1);
384     cx2 = min(dx + source->getWidth(), cx2);
385     cy2 = min(dy + source->getHeight(), cy2);
386     if (cx1 >= cx2 || cy1 >= cy2) return; 
387
388     if (source->hasalpha) {
389
390         if (scratch == NULL || scratch_w < cx2 - cx1 || scratch_h < cy2 - cy1) {
391             if (scratch_dc != NULL) DeleteDC(scratch_dc);
392             if (scratch != NULL) DeleteObject(scratch);
393             scratch_w = max(cx2 - cx1, scratch_w);
394             scratch_h = max(cy2 - cy1, scratch_h);
395
396             BITMAPINFO bitmapinfo;
397             memset(&bitmapinfo, 0, sizeof(BITMAPINFO));
398             bitmapinfo.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
399             bitmapinfo.bmiHeader.biWidth = scratch_w;
400             bitmapinfo.bmiHeader.biHeight = -1 * scratch_h;
401             bitmapinfo.bmiHeader.biPlanes = 1;
402             bitmapinfo.bmiHeader.biBitCount = 32;
403             bitmapinfo.bmiHeader.biCompression = BI_RGB;
404
405             // create section DIB
406             scratch = CreateDIBSection(NULL, &bitmapinfo, DIB_RGB_COLORS, (void**)&scratch_bits, NULL, 0);
407             scratch_dc = CreateCompatibleDC((HDC)org::ibex::plat::Win32::desktop_dc);
408             SelectObject(scratch_dc, scratch);
409         }
410
411         // copy from screen to scratch
412         BitBlt((HDC)scratch_dc, 0, 0, cx2 - cx1, cy2 - cy1, (HDC)hdc, cx1, cy1, SRCCOPY);
413
414         // apply alpha-blending to scratch
415         jint* dat = elements(source->data);
416
417         for(int x = cx1; x < cx2; x++)
418             for(int y = cy1; y < cy2; y++) {
419                 jint dst = scratch_bits[(y - dy) * scratch_w + (x - dx)];
420
421                 // FEATURE: see if we can leverage GDI to do something more clever here with alphaOnly
422                 jint src = alphaOnly ? rgb : dat[(y - dy) * source->getWidth() + x - dx];
423                 jint alpha = (dat[(y - dy) * source->getWidth() + x - dx] & 0xFF000000) >> 24;
424                 jint r = (((src & 0x00FF0000) >> 16) * alpha + ((dst & 0x00FF0000) >> 16) * (0xFF - alpha)) / 0xFF;
425                 jint g = (((src & 0x0000FF00) >> 8)  * alpha + ((dst & 0x0000FF00) >> 8)  * (0xFF - alpha)) / 0xFF;
426                 jint b = (((src & 0x000000FF))       * alpha + ((dst & 0x000000FF))       * (0xFF - alpha)) / 0xFF;
427                 scratch_bits[(y - dy) * scratch_w + (x - dx)] = (r << 16) | (g << 8) | b;
428             }
429
430         // copy back from scratch to screen
431         BitBlt((HDC)hdc, cx1, cy1, cx2 - cx1, cy2 - cy1, (HDC)scratch_dc, 0, 0, SRCCOPY);
432
433     } else {
434
435         // FIXME: support alphaOnly case here
436         if (source->hasmask) {
437             BitBlt((HDC)hdc, cx1, cy1, cx2 - cx1, cy2 - cy1, (HDC)source->maskdc, cx1 - dx, cy1 - dy, SRCAND);
438             BitBlt((HDC)hdc, cx1, cy1, cx2 - cx1, cy2 - cy1, (HDC)source->hdc, cx1 - dx, cy1 - dy, SRCPAINT);
439         } else {
440             BitBlt((HDC)hdc, cx1, cy1, cx2 - cx1, cy2 - cy1, (HDC)source->hdc, cx1 - dx, cy1 - dy, SRCCOPY);
441         }
442
443     }
444
445 }
446
447 void org::ibex::plat::Win32$Win32PixelBuffer::fillRect(jint x, jint y, jint x2, jint y2, jint color) {
448     jint w = x2 - x;
449     jint h = y2 - y;
450
451     // sadly, the ability to change the color of a brush didn't arrive until Win2k...
452     HBRUSH brush = CreateSolidBrush(PALETTERGB((color & 0xFF0000) >> 16, (color & 0xFF00) >> 8, color & 0xFF));
453     if (!brush) return;
454     RECT rect = { x, y, x + w, y + h };
455     FillRect((HDC)hdc, &rect, brush);
456     DeleteObject(brush);
457 }
458
459 void org::ibex::plat::Win32$Win32Surface::blit(org::ibex::graphics::PixelBuffer* s, jint sx, jint sy, jint dx, jint dy, jint dx2, jint dy2) {
460     // we create the DC lazily to get around some strange race condition in WinXP
461     if (hdc == 0) hdc = (jint)GetDC((HWND)hwnd);
462     BitBlt((HDC)hdc, dx, dy, dx2 - dx, dy2 - dy, (HDC)(((org::ibex::plat::Win32$Win32PixelBuffer*)s)->hdc), sx, sy, SRCCOPY);
463 }
464
465 void org::ibex::plat::Win32$Win32PixelBuffer::natInit() {
466     hbitmap = (jint)CreateCompatibleBitmap((HDC)org::ibex::plat::Win32::desktop_dc, w, h);
467     hdc = (jint)CreateCompatibleDC((HDC)org::ibex::plat::Win32::desktop_dc);
468     SetBkMode((HDC)hdc, TRANSPARENT);
469     SelectObject((HDC)hdc, (HBITMAP)hbitmap);
470 }
471
472 void org::ibex::plat::Win32$Win32PixelBuffer::finalize() {
473     DeleteObject((void*)hdc);
474     DeleteObject((void*)hbitmap);
475 }
476
477
478
479 // Win32Picture /////////////////////////////////////////////////////////////////////////
480
481 void org::ibex::plat::Win32$Win32Picture::natInit() {
482
483     BITMAPINFO bitmapinfo;
484     memset(&bitmapinfo, 0, sizeof(BITMAPINFO));
485     bitmapinfo.bmiHeader.biSize = sizeof(BITMAPINFOHEADER); 
486     bitmapinfo.bmiHeader.biWidth = width;
487     bitmapinfo.bmiHeader.biHeight = -1 * height;
488     bitmapinfo.bmiHeader.biPlanes = 1;
489     bitmapinfo.bmiHeader.biBitCount = 32;
490     bitmapinfo.bmiHeader.biCompression = BI_RGB;
491
492     hbitmap = (jint)CreateCompatibleBitmap((HDC)org::ibex::plat::Win32::desktop_dc, width, height);
493     hdc = (jint)CreateCompatibleDC((HDC)org::ibex::plat::Win32::desktop_dc);
494     SelectObject((HDC)hdc, (HBITMAP)hbitmap);
495     uint32_t* dat = (uint32_t*)elements(data);
496     for(int i=0; i<data->length; i++) if ((dat[i] & 0xFF000000) == 0x00000000) dat[i] = 0x00000000;
497     StretchDIBits((HDC)hdc, 0, 0, width, height, 0, 0, width, height, elements(data), &bitmapinfo, DIB_RGB_COLORS, SRCCOPY);
498
499     jint _copy[min(1024, data->length)];
500     jint* copy = data->length > 1024 ? (jint*)malloc(data->length * 4) : _copy;
501
502     memcpy(copy, elements(data), data->length * 4);
503     for(int i=0; i<data->length; i++)
504         if ((copy[i] & 0xFF000000) == 0x00000000) {
505             hasmask = 1;
506             copy[i] = 0x00FFFFFF;
507         } else if ((copy[i] & 0xFF000000) == 0xFF000000) {
508             copy[i] = 0x00000000;
509         } else {
510             hasalpha = 1;
511             hasmask = 0;
512             if (data->length > 1024) free(copy);
513             return;
514         }
515
516     if (!hasmask) {
517         if (data->length > 1024) free(copy);
518         return;
519     }
520
521     //    hmask = (jint)CreateBitmap(w, h, 1, 1, NULL);
522     hmask = (jint)CreateCompatibleBitmap((HDC)org::ibex::plat::Win32::desktop_dc, width, height);
523     maskdc = (jint)CreateCompatibleDC(NULL);
524     SelectObject((HDC)maskdc, (HBITMAP)hmask);
525     StretchDIBits((HDC)maskdc, 0, 0, width, height, 0, 0, width, height, copy, &bitmapinfo, DIB_RGB_COLORS, SRCCOPY);
526     if (data->length > 1024) free(copy);
527 }
528
529
530
531 // Win32Surface /////////////////////////////////////////////////////////////////////////////
532
533 void org::ibex::plat::Win32$Win32Surface::natInit(jboolean framed) {
534
535     // Ask the message-handling thread to create a window for us
536     PostThreadMessage((DWORD)org::ibex::plat::Win32::messagePumpThread, WM_USER + 2, (WPARAM)framed, (LPARAM)this);
537     hwndCreated->block();
538
539     // turn on incremental GC now that we have a user-visible interface
540     // [[this is causing segfaults; enable it later...]]
541     // GC_enable_incremental();
542
543     ShowWindow ((HWND)hwnd, SW_SHOWDEFAULT);
544 }
545
546 void org::ibex::plat::Win32$Win32Surface::finalize() { /* DeleteObject((void*)hwnd); */ }
547 void org::ibex::plat::Win32$Win32Surface::toFront() { BringWindowToTop((HWND)hwnd); }
548 void org::ibex::plat::Win32$Win32Surface::toBack() { SetWindowPos((HWND)hwnd, HWND_BOTTOM, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE); }
549 void org::ibex::plat::Win32$Win32Surface::_dispose() { PostMessage((HWND)hwnd, WM_USER_DISPOSE, 0, 0); }
550 void org::ibex::plat::Win32$Win32Surface::setInvisible(jboolean h) { ShowWindow((HWND)hwnd, h ? SW_HIDE : SW_SHOWNORMAL); }
551 void org::ibex::plat::Win32$Win32Surface::_setMinimized(jboolean m) { ShowWindow((HWND)hwnd, m ? SW_SHOWMINIMIZED : SW_SHOWNORMAL); }
552 void org::ibex::plat::Win32$Win32Surface::_setMaximized(jboolean m) { ShowWindow((HWND)hwnd, m ? SW_SHOWMAXIMIZED : SW_SHOWNORMAL); }
553 void org::ibex::plat::Win32$Win32Surface::postCursorChange() { PostMessage((HWND)hwnd, WM_USER_SETCURSOR, 0, 0); }
554
555 void org::ibex::plat::Win32$Win32Surface::setLocation() {
556     POINT point;
557     RECT rect;
558     point.x = 0;
559     point.y = 0;
560     ClientToScreen((HWND)hwnd, &point);
561     GetWindowRect((HWND)hwnd, &rect);
562     SetWindowPos((HWND)hwnd, NULL, root->x - (point.x - rect.left), root->y - (point.y - rect.top), 0, 0, SWP_NOZORDER | SWP_NOSIZE);
563 }
564
565 void org::ibex::plat::Win32$Win32Surface::_setSize(jint w, jint h) {
566     RECT client_rect, window_rect;
567     GetClientRect((HWND)hwnd, &client_rect);
568     GetWindowRect((HWND)hwnd, &window_rect);
569     int width = (window_rect.right - window_rect.left) - (client_rect.right - client_rect.left) + w;
570     int height = (window_rect.bottom - window_rect.top) - (client_rect.bottom - client_rect.top) + h;
571     SetWindowPos((HWND)hwnd, NULL, 0, 0, width, height, SWP_NOZORDER | SWP_NOMOVE);
572 }
573
574 void org::ibex::plat::Win32$Win32Surface::setTitleBarText(java::lang::String* title) {
575     int len = min(1024, JvGetStringUTFLength(title));
576     char buf[len + 1];
577     buf[len] = '\0';
578     JvGetStringUTFRegion(title, 0, len, buf);
579     SetWindowText((HWND)hwnd, buf);
580 }
581
582 void org::ibex::plat::Win32$Win32Surface::setIcon(org::ibex::graphics::Picture* p0) {
583
584     org::ibex::plat::Win32$Win32Picture* p = (org::ibex::plat::Win32$Win32Picture*)p0;
585     int icon_width = GetSystemMetrics(SM_CXSMICON);
586     int icon_height = GetSystemMetrics(SM_CYSMICON);
587
588     // we create the DC lazily to get around some strange race condition in WinXP
589     if (hdc == 0) hdc = (jint)GetDC((HWND)hwnd);
590
591     // create the bitmap
592     HBITMAP bit = CreateCompatibleBitmap((HDC)hdc, icon_width, icon_height);
593     HDC memdc = CreateCompatibleDC((HDC)hdc);
594     SelectObject(memdc, bit);
595     BitBlt((HDC)memdc, 0, 0, icon_width, icon_height, (HDC)(p->hdc), 0, 0, SRCCOPY);
596
597     // create the mask
598     jint* dat = elements(p->data);
599     HBITMAP bit_mask = CreateBitmap(icon_width, icon_height * 2, 1, 1, NULL);
600     SelectObject(memdc, bit_mask);
601     for(int x=0; x<icon_width; x++)
602         for(int y=0; y<icon_height; y++) {
603             int source = dat[(x * p->getWidth()) / icon_width + ((y * p->getHeight()) / icon_height) * (p->getWidth())];
604             if ((source & 0xFF000000) != 0x00000000) SetPixel(memdc, x, y, PALETTERGB(0, 0, 0));
605             else SetPixel(memdc, x, y, PALETTERGB(255, 255, 255));
606         }
607
608     // instantiate the icon and assign it to the window class
609     ICONINFO ici;
610     ici.fIcon = 1;
611     ici.hbmMask = bit_mask;
612     ici.hbmColor = bit;
613     HICON hicon = CreateIconIndirect(&ici);
614     HICON oldicon = (HICON)SetClassLong((HWND)hwnd, GCL_HICONSM, (LONG)hicon);
615     if (oldicon != NULL) DestroyIcon(oldicon);
616     DeleteObject(memdc);
617 }
618
619 static jstring keyToString(WPARAM wParam);
620 jint org::ibex::plat::Win32$Win32Surface::WndProc(jint _hwnd, jint _iMsg, jint _wParam, jint _lParam) {
621
622     UINT iMsg = (UINT)_iMsg;
623     WPARAM wParam = (WPARAM)_wParam;
624     LPARAM lParam = (LPARAM)_lParam;
625
626     int oldmousex, oldmousey;
627     MINMAXINFO* mmi;
628     int resizable;
629     POINT point;
630     HWND hwnd2;
631     RECT rect, rect2;
632     RECT client_rect, window_rect;
633     jboolean newinside;
634     int16_t mouse_x;
635     int16_t mouse_y;
636     int addwidth, addheight;
637
638     switch(iMsg) {
639     case WM_DEVMODECHANGE: break;    // FEATURE: color depth changed
640     case WM_DISPLAYCHANGE: break;    // FEATURE: screen size changed
641     case WM_MOUSEWHEEL:
642       VScroll(((jfloat)((wParam & 0xffff0000) >> 16)) / (jfloat)120);
643       break;
644
645     case WM_SYSKEYDOWN: if (GetKeyState(VK_MENU) >> (sizeof(SHORT) * 8 - 1) == 0) return 0;
646     case WM_KEYDOWN:
647         KeyPressed(keyToString(wParam));
648         return 0;
649
650     case WM_SYSKEYUP: if (GetKeyState(VK_MENU) >> (sizeof(SHORT) * 8 - 1) == 0) return 0;
651     case WM_KEYUP:
652         KeyReleased(keyToString(wParam));
653         return 0;
654
655     case WM_SETFOCUS: Focused(true); return 0;
656     case WM_KILLFOCUS: Focused(false); return 0;
657     case WM_LBUTTONDBLCLK: DoubleClick(1); return 0;
658     case WM_RBUTTONDBLCLK: DoubleClick(2); return 0;
659     case WM_MBUTTONDBLCLK: DoubleClick(3); return 0;
660     case WM_LBUTTONDOWN: Press(1); return 0;
661     case WM_RBUTTONDOWN: Press(2); return 0;
662     case WM_MBUTTONDOWN: Press(3); return 0;
663     case WM_CLOSE: Close(); return 0;
664     case WM_ERASEBKGND: return 0;
665
666     case WM_LBUTTONUP:
667     case WM_RBUTTONUP:
668     case WM_MBUTTONUP:
669         Release(iMsg == WM_LBUTTONUP ? 1 : (iMsg == WM_RBUTTONUP ? 2 : 3));
670         if (captured && !inside) {
671             ReleaseCapture();
672             SetCursor((HCURSOR)previous_cursor);
673         }
674         return 0;
675
676     case WM_MOVING:
677         GetClientRect((HWND)hwnd, &rect);
678         PosChange(rect.left, rect.top);
679         return 0;
680
681     case WM_SIZE:
682         if (wParam == SIZE_MINIMIZED) {
683             if (maximized) Maximized(false);
684             Minimized(true);
685         } else if (wParam == SIZE_MAXIMIZED) {
686             if (minimized) Minimized(false); 
687             Maximized(true);
688         } else {
689             if (minimized) Minimized(false); 
690             if (maximized) Maximized(false);
691         }
692         // deliberately fall through to WM_SIZING
693
694     case WM_SIZING:
695         GetClientRect((HWND)hwnd, &rect);
696         SizeChange(rect.right - rect.left, rect.bottom - rect.top);
697         return 0;
698
699     case WM_MOUSEMOVE:
700         if (mousex == lParam & 0xFFFF && mousey == (lParam & 0xFFFF0000) >> 16) return 0;
701
702         GetClientRect((HWND)hwnd, &rect);
703         point.x = mouse_x = lParam & 0xFFFF;
704         point.y = mouse_y = (lParam & 0xFFFF0000) >> 16;
705         ClientToScreen((HWND)hwnd, &point);
706         hwnd2 = WindowFromPoint(point);
707
708         newinside = hwnd2 == (HWND)hwnd && mouse_x > 0 && mouse_y > 0 && mouse_x < (rect.right - rect.left) && mouse_y < (rect.bottom - rect.top) ? 1 : 0;
709
710         Move(mouse_x, mouse_y);
711
712         if (newinside && !inside) {
713             inside = 1;
714             SetCapture((HWND)hwnd);
715             captured = 1;
716             previous_cursor = (jint)GetCursor();
717             PostMessage((HWND)hwnd, WM_USER_SETCURSOR, 0, 0);
718
719         } else if (!newinside && inside) {
720             inside = 0;
721             if (!button1 && !button2 && !button3) {
722                 ReleaseCapture();
723                 captured = 0;
724                 SetCursor((HCURSOR)previous_cursor);
725             }
726         }
727
728         return 0;
729
730     case WM_USER_SETCURSOR:
731         // I can't find documentation of this anywhere, but in my experience, an event must be received between an invocation
732         // of SetCapture() and SetCursor(). Furthermore, it seems that after calling SetCursor(), the cursor will not change until a
733         // return from WndProc (ie the completion of the processing of some event).  Hence, we use WM_USER to indicate that the screen
734         // cursor should be re-set to "current_cursor".
735         SetCursor((HCURSOR)current_cursor);
736         return 0;
737
738     case WM_USER_DISPOSE:
739         // used to signal that we should destroy ourselves
740         DestroyWindow((HWND)hwnd);
741         return 0;
742
743     case WM_GETMINMAXINFO:
744         GetClientRect((HWND)hwnd, &client_rect);
745         GetWindowRect((HWND)hwnd, &window_rect);
746         addwidth = (window_rect.right - window_rect.left) - (client_rect.right - client_rect.left);
747         addheight = (window_rect.bottom - window_rect.top) - (client_rect.bottom - client_rect.top);
748         mmi = (MINMAXINFO*)lParam;
749         mmi->ptMinTrackSize.x = ((uint32_t)root->minwidth) + addwidth;
750         mmi->ptMinTrackSize.y = ((uint32_t)root->minheight) + addheight;
751         resizable = !((root->minwidth == root->maxwidth) && (root->minheight == root->maxheight));
752         mmi->ptMaxTrackSize.x = resizable ? org::ibex::plat::Win32::getScreenWidth() : mmi->ptMinTrackSize.x;
753         mmi->ptMaxTrackSize.y = resizable ? org::ibex::plat::Win32::getScreenHeight() : mmi->ptMinTrackSize.y;
754         return 0;
755         
756     case WM_PAINT: 
757         PAINTSTRUCT ps;
758         BeginPaint((HWND)org::ibex::plat::Win32$Win32Surface::hwnd, &ps);
759         Dirty(ps.rcPaint.left, ps.rcPaint.top, ps.rcPaint.right - ps.rcPaint.left, ps.rcPaint.bottom - ps.rcPaint.top);
760         EndPaint((HWND)org::ibex::plat::Win32$Win32Surface::hwnd, &ps);
761         return 0;
762
763     default: break;
764     }  
765
766     return DefWindowProc((HWND)hwnd, iMsg, wParam, lParam);
767 }
768
769
770 // Key Translator Helper /////////////////////////////////////////////////////////////////////
771
772 static char keyarr [256] = { 0 };
773 static jstring keyToString(WPARAM wParam) {
774     char arr[8];
775     keyarr[VK_CAPITAL] = GetKeyState(VK_CAPITAL);
776     keyarr[VK_LSHIFT] = GetKeyState(VK_LSHIFT);
777     keyarr[VK_RSHIFT] = GetKeyState(VK_RSHIFT);
778     keyarr[VK_SHIFT] = GetKeyState(VK_SHIFT);
779
780     if (ToAsciiEx(wParam, 0, (BYTE*)keyarr, (WORD*)arr, 0, GetKeyboardLayout(0)) == 1) {
781         switch (arr[0]) {
782         case '\t': return JvNewStringLatin1("tab");
783         case 0x1b: return JvNewStringLatin1("escape");
784         case '\n': return JvNewStringLatin1("enter");
785         case '\r': return JvNewStringLatin1("enter");
786         case 0x08: return JvNewStringLatin1("back_space");
787         default: return JvNewStringLatin1(arr, 1);
788         }
789         
790     } else switch (wParam) {
791     case VK_CLEAR: return JvNewStringLatin1("clear");
792     case VK_SHIFT: return JvNewStringLatin1("shift");
793     case VK_CONTROL: return JvNewStringLatin1("control");
794     case VK_CAPITAL: return JvNewStringLatin1("caps_lock");
795     case VK_MENU: return JvNewStringLatin1("alt");
796     case VK_PAUSE: return JvNewStringLatin1("pause");
797     case VK_PRIOR: return JvNewStringLatin1("page_up");
798     case VK_NEXT: return JvNewStringLatin1("page_down");
799     case VK_END: return JvNewStringLatin1("end");
800     case VK_HOME: return JvNewStringLatin1("home");
801     case VK_LEFT: return JvNewStringLatin1("left");
802     case VK_UP: return JvNewStringLatin1("up");
803     case VK_RIGHT: return JvNewStringLatin1("right");
804     case VK_DOWN: return JvNewStringLatin1("down");
805     case VK_INSERT: return JvNewStringLatin1("insert");
806     case VK_DELETE: return JvNewStringLatin1("delete");
807     case VK_NUMPAD0: return JvNewStringLatin1("numpad0");
808     case VK_NUMPAD1: return JvNewStringLatin1("numpad1");
809     case VK_NUMPAD2: return JvNewStringLatin1("numpad2");
810     case VK_NUMPAD3: return JvNewStringLatin1("numpad3");
811     case VK_NUMPAD4: return JvNewStringLatin1("numpad4");
812     case VK_NUMPAD5: return JvNewStringLatin1("numpad5");
813     case VK_NUMPAD6: return JvNewStringLatin1("numpad6");
814     case VK_NUMPAD7: return JvNewStringLatin1("numpad7");
815     case VK_NUMPAD8: return JvNewStringLatin1("numpad8");
816     case VK_NUMPAD9: return JvNewStringLatin1("numpad9");
817     case VK_F1: return JvNewStringLatin1("f1");
818     case VK_F2: return JvNewStringLatin1("f2");
819     case VK_F3: return JvNewStringLatin1("f3");
820     case VK_F4: return JvNewStringLatin1("f4");
821     case VK_F5: return JvNewStringLatin1("f5");
822     case VK_F6: return JvNewStringLatin1("f6");
823     case VK_F7: return JvNewStringLatin1("f7");
824     case VK_F8: return JvNewStringLatin1("f8");
825     case VK_F9: return JvNewStringLatin1("f9");
826     case VK_F10: return JvNewStringLatin1("f10");
827     case VK_F11: return JvNewStringLatin1("f11");
828     case VK_F12: return JvNewStringLatin1("f12");
829     case VK_NUMLOCK: return JvNewStringLatin1("num_lock");
830     case VK_SCROLL: return JvNewStringLatin1("scroll_lock");
831     case VK_LSHIFT: return JvNewStringLatin1("shift");
832     case VK_RSHIFT: return JvNewStringLatin1("shift");
833     case VK_LCONTROL: return JvNewStringLatin1("control");
834     case VK_RCONTROL: return JvNewStringLatin1("control");
835     }
836     return NULL;
837 }