2014-08-14 09:42:10 -05:00
// 24 march 2014
2014-08-05 11:05:43 -05:00
# include "winapi_windows.h"
2014-08-05 11:29:37 -05:00
# include "_cgo_export.h"
2014-08-05 11:05:43 -05:00
static void getScrollPos ( HWND hwnd , int * xpos , int * ypos )
{
SCROLLINFO si ;
ZeroMemory ( & si , sizeof ( SCROLLINFO ) ) ;
si . cbSize = sizeof ( SCROLLINFO ) ;
si . fMask = SIF_POS | SIF_TRACKPOS ;
if ( GetScrollInfo ( hwnd , SB_HORZ , & si ) = = 0 )
xpanic ( " error getting horizontal scroll position for Area " , GetLastError ( ) ) ;
* xpos = si . nPos ;
// MSDN example code reinitializes this each time, so we'll do it too just to be safe
ZeroMemory ( & si , sizeof ( SCROLLINFO ) ) ;
si . cbSize = sizeof ( SCROLLINFO ) ;
si . fMask = SIF_POS | SIF_TRACKPOS ;
2014-08-05 11:29:37 -05:00
if ( GetScrollInfo ( hwnd , SB_VERT , & si ) = = 0 )
2014-08-05 11:05:43 -05:00
xpanic ( " error getting vertical scroll position for Area " , GetLastError ( ) ) ;
* ypos = si . nPos ;
}
# define areaBackgroundBrush ((HBRUSH) (COLOR_BTNFACE + 1))
static void paintArea ( HWND hwnd , void * data )
{
RECT xrect ;
PAINTSTRUCT ps ;
HDC hdc ;
HDC rdc ;
HBITMAP rbitmap , prevrbitmap ;
RECT rrect ;
BITMAPINFO bi ;
VOID * ppvBits ;
HBITMAP ibitmap ;
HDC idc ;
HBITMAP previbitmap ;
BLENDFUNCTION blendfunc ;
void * i ;
intptr_t dx , dy ;
int hscroll , vscroll ;
// TRUE here indicates a
if ( GetUpdateRect ( hwnd , & xrect , TRUE ) = = 0 )
return ; // no update rect; do nothing
2014-08-05 11:29:37 -05:00
getScrollPos ( hwnd , & hscroll , & vscroll ) ;
2014-08-05 11:05:43 -05:00
i = doPaint ( & xrect , hscroll , vscroll , data , & dx , & dy ) ;
if ( i = = NULL ) // cliprect empty
return ;
// don't convert to BRGA just yet; see below
// TODO don't do the above, but always draw the background color?
hdc = BeginPaint ( hwnd , & ps ) ;
if ( hdc = = NULL )
xpanic ( " error beginning Area repaint " , GetLastError ( ) ) ;
// very big thanks to Ninjifox for suggesting this technique and helping me go through it
// first let's create the destination image, which we fill with the windows background color
// this is how we fake drawing the background; see also http://msdn.microsoft.com/en-us/library/ms969905.aspx
rdc = CreateCompatibleDC ( hdc ) ;
if ( rdc = = NULL )
xpanic ( " error creating off-screen rendering DC " , GetLastError ( ) ) ;
// the bitmap has to be compatible with the window
// if we create a bitmap compatible with the DC we just created, it'll be monochrome
// thanks to David Heffernan in http://stackoverflow.com/questions/23033636/winapi-gdi-fillrectcolor-btnface-fills-with-strange-grid-like-brush-on-window
rbitmap = CreateCompatibleBitmap ( hdc , xrect . right - xrect . left , xrect . bottom - xrect . top ) ;
if ( rbitmap = = NULL )
xpanic ( " error creating off-screen rendering bitmap " , GetLastError ( ) ) ;
prevrbitmap = ( HBITMAP ) SelectObject ( rdc , rbitmap ) ;
if ( prevrbitmap = = NULL )
xpanic ( " error connecting off-screen rendering bitmap to off-screen rendering DC " , GetLastError ( ) ) ;
rrect . left = 0 ;
rrect . right = xrect . right - xrect . left ;
rrect . top = 0 ;
rrect . bottom = xrect . bottom - xrect . top ;
if ( FillRect ( rdc , & rrect , areaBackgroundBrush ) = = 0 )
xpanic ( " error filling off-screen rendering bitmap with the system background color " , GetLastError ( ) ) ;
// now we need to shove realbits into a bitmap
// technically bitmaps don't know about alpha; they just ignore the alpha byte
// AlphaBlend(), however, sees it - see http://msdn.microsoft.com/en-us/library/windows/desktop/dd183352%28v=vs.85%29.aspx
ZeroMemory ( & bi , sizeof ( BITMAPINFO ) ) ;
bi . bmiHeader . biSize = sizeof ( BITMAPINFOHEADER ) ;
2014-08-05 20:34:58 -05:00
bi . bmiHeader . biWidth = ( LONG ) dx ;
bi . bmiHeader . biHeight = - ( ( LONG ) dy ) ; // negative height to force top-down drawing;
2014-08-05 11:05:43 -05:00
bi . bmiHeader . biPlanes = 1 ;
bi . bmiHeader . biBitCount = 32 ;
bi . bmiHeader . biCompression = BI_RGB ;
bi . bmiHeader . biSizeImage = ( DWORD ) ( dx * dy * 4 ) ;
// this is all we need, but because this confused me at first, I will say the two pixels-per-meter fields are unused (see http://blogs.msdn.com/b/oldnewthing/archive/2013/05/15/10418646.aspx and page 581 of Charles Petzold's Programming Windows, Fifth Edition)
// now for the trouble: CreateDIBSection() allocates the memory for us...
ibitmap = CreateDIBSection ( NULL , // Ninjifox does this, so do some wine tests (http://source.winehq.org/source/dlls/gdi32/tests/bitmap.c#L725, thanks vpovirk in irc.freenode.net/#winehackers) and even Raymond Chen (http://blogs.msdn.com/b/oldnewthing/archive/2006/11/16/1086835.aspx), so.
& bi , DIB_RGB_COLORS , & ppvBits , 0 , 0 ) ;
if ( ibitmap = = NULL )
xpanic ( " error creating HBITMAP for image returned by AreaHandler.Paint() " , GetLastError ( ) ) ;
// now we have to do TWO MORE things before we can finally do alpha blending
// first, we need to load the bitmap memory, because Windows makes it for us
// the pixels are arranged in RGBA order, but GDI requires BGRA
// this turns out to be just ARGB in little endian; let's convert into this memory
2014-08-05 11:29:37 -05:00
dotoARGB ( i , ( void * ) ppvBits ) ;
2014-08-05 11:05:43 -05:00
// the second thing is... make a device context for the bitmap :|
// Ninjifox just makes another compatible DC; we'll do the same
idc = CreateCompatibleDC ( hdc ) ;
if ( idc = = NULL )
xpanic ( " error creating HDC for image returned by AreaHandler.Paint() " , GetLastError ( ) ) ;
previbitmap = ( HBITMAP ) SelectObject ( idc , ibitmap ) ;
if ( previbitmap = = NULL )
xpanic ( " error connecting HBITMAP for image returned by AreaHandler.Paint() to its HDC " , GetLastError ( ) ) ;
// AND FINALLY WE CAN DO THE ALPHA BLENDING!!!!!!111
blendfunc . BlendOp = AC_SRC_OVER ;
blendfunc . BlendFlags = 0 ;
blendfunc . SourceConstantAlpha = 255 ; // only use per-pixel alphas
blendfunc . AlphaFormat = AC_SRC_ALPHA ; // premultiplied
if ( AlphaBlend ( rdc , 0 , 0 , ( int ) dx , ( int ) dy , // destination
idc , 0 , 0 , ( int ) dx , ( int ) dy , // source
blendfunc ) = = FALSE )
xpanic ( " error alpha-blending image returned by AreaHandler.Paint() onto background " , GetLastError ( ) ) ;
// and finally we can just blit that into the window
if ( BitBlt ( hdc , xrect . left , xrect . top , xrect . right - xrect . left , xrect . bottom - xrect . top ,
rdc , 0 , 0 , // from the rdc's origin
SRCCOPY ) = = 0 )
xpanic ( " error blitting Area image to Area " , GetLastError ( ) ) ;
// now to clean up
if ( SelectObject ( idc , previbitmap ) ! = ibitmap )
xpanic ( " error reverting HDC for image returned by AreaHandler.Paint() to original HBITMAP " , GetLastError ( ) ) ;
if ( SelectObject ( rdc , prevrbitmap ) ! = rbitmap )
xpanic ( " error reverting HDC for off-screen rendering to original HBITMAP " , GetLastError ( ) ) ;
if ( DeleteObject ( ibitmap ) = = 0 )
xpanic ( " error deleting HBITMAP for image returned by AreaHandler.Paint() " , GetLastError ( ) ) ;
if ( DeleteObject ( rbitmap ) = = 0 )
xpanic ( " error deleting HBITMAP for off-screen rendering " , GetLastError ( ) ) ;
if ( DeleteDC ( idc ) = = 0 )
xpanic ( " error deleting HDC for image returned by AreaHandler.Paint() " , GetLastError ( ) ) ;
if ( DeleteDC ( rdc ) = = 0 )
xpanic ( " error deleting HDC for off-screen rendering " , GetLastError ( ) ) ;
EndPaint ( hwnd , & ps ) ;
}
static SIZE getAreaControlSize ( HWND hwnd )
{
RECT rect ;
SIZE size ;
if ( GetClientRect ( hwnd , & rect ) = = 0 )
xpanic ( " error getting size of actual Area control " , GetLastError ( ) ) ;
size . cx = ( LONG ) ( rect . right - rect . left ) ;
size . cy = ( LONG ) ( rect . bottom - rect . top ) ;
return size ;
}
static void scrollArea ( HWND hwnd , void * data , WPARAM wParam , int which )
{
2014-08-05 11:29:37 -05:00
SCROLLINFO si ;
2014-08-05 11:05:43 -05:00
SIZE size ;
LONG cwid , cht ;
LONG pagesize , maxsize ;
LONG newpos ;
LONG delta ;
LONG dx , dy ;
size = getAreaControlSize ( hwnd ) ;
cwid = size . cx ;
cht = size . cy ;
if ( which = = SB_HORZ ) {
pagesize = cwid ;
2014-08-05 11:29:37 -05:00
maxsize = areaWidthLONG ( data ) ;
2014-08-05 11:05:43 -05:00
} else if ( which = = SB_VERT ) {
2014-08-05 11:29:37 -05:00
pagesize = cht ;
maxsize = areaHeightLONG ( data ) ;
2014-08-05 11:05:43 -05:00
} else
xpanic ( " invalid which sent to scrollArea() " , 0 ) ;
ZeroMemory ( & si , sizeof ( SCROLLINFO ) ) ;
si . cbSize = sizeof ( SCROLLINFO ) ;
si . fMask = SIF_POS | SIF_TRACKPOS ;
if ( GetScrollInfo ( hwnd , which , & si ) = = 0 )
xpanic ( " error getting current scroll position for scrolling " , GetLastError ( ) ) ;
newpos = ( LONG ) si . nPos ;
switch ( LOWORD ( wParam ) ) {
2014-08-09 20:49:21 -05:00
case SB_LEFT : // also SB_TOP; C won't let me have both (C89 §6.6.4.2; C99 §6.8.4.2)
2014-08-05 11:05:43 -05:00
newpos = 0 ;
break ;
case SB_RIGHT : // also SB_BOTTOM
// see comment in adjustAreaScrollbars() below
newpos = maxsize - pagesize ;
break ;
case SB_LINELEFT : // also SB_LINEUP
newpos - - ;
break ;
case SB_LINERIGHT : // also SB_LINEDOWN
newpos + + ;
break ;
case SB_PAGELEFT : // also SB_PAGEUP
newpos - = pagesize ;
break ;
case SB_PAGERIGHT : // also SB_PAGEDOWN
newpos + = pagesize ;
break ;
case SB_THUMBPOSITION :
// raymond chen says to just set the newpos to the SCROLLINFO nPos for this message; see http://blogs.msdn.com/b/oldnewthing/archive/2003/07/31/54601.aspx and http://blogs.msdn.com/b/oldnewthing/archive/2003/08/05/54602.aspx
// do nothing here; newpos already has nPos
break ;
case SB_THUMBTRACK :
newpos = ( LONG ) si . nTrackPos ;
}
// otherwise just keep the current position (that's what MSDN example code says, anyway)
// make sure we're not out of range
if ( newpos < 0 )
newpos = 0 ;
if ( newpos > ( maxsize - pagesize ) )
newpos = maxsize - pagesize ;
// this would be where we would put a check to not scroll if the scroll position changed, but see the note about SB_THUMBPOSITION above: Raymond Chen's code always does the scrolling anyway in this case
2014-08-05 11:29:37 -05:00
delta = - ( newpos - si . nPos ) ; // negative because ScrollWindowEx() scrolls in the opposite direction
dx = delta ;
dy = 0 ;
2014-08-05 11:05:43 -05:00
if ( which = = SB_VERT ) {
2014-08-05 11:29:37 -05:00
dx = 0 ;
dy = delta ;
2014-08-05 11:05:43 -05:00
}
2014-08-23 19:39:47 -05:00
// first move the edit control, if any, to avoid artifacting
if ( ( HWND ) GetWindowLongPtrW ( hwnd , 0 ) ! = NULL ) {
HWND edit ;
int x , y ;
edit = ( HWND ) GetWindowLongPtrW ( hwnd , 0 ) ;
x = ( int ) GetWindowLongPtrW ( hwnd , sizeof ( LONG_PTR ) ) ;
y = ( int ) GetWindowLongPtrW ( hwnd , 2 * sizeof ( LONG_PTR ) ) ;
x + = dx ;
y + = dy ;
if ( SetWindowPos ( edit , NULL , x , y , 0 , 0 , SWP_NOSIZE | SWP_NOZORDER ) = = 0 )
xpanic ( " error moving Area TextField in response to scroll " , GetLastError ( ) ) ;
SetWindowLongPtrW ( hwnd , sizeof ( LONG_PTR ) , ( LONG_PTR ) x ) ;
SetWindowLongPtrW ( hwnd , 2 * sizeof ( LONG_PTR ) , ( LONG_PTR ) y ) ;
// TODO doesn't work?
if ( InvalidateRect ( edit , NULL , TRUE ) = = 0 )
xpanic ( " error marking Area TextField as needing redraw " , GetLastError ( ) ) ;
}
2014-08-05 11:05:43 -05:00
if ( ScrollWindowEx ( hwnd ,
2014-08-05 20:34:58 -05:00
( int ) dx , ( int ) dy ,
2014-08-05 11:05:43 -05:00
// these four change what is scrolled and record info about the scroll; we're scrolling the whole client area and don't care about the returned information here
2014-08-05 20:34:58 -05:00
NULL , NULL , NULL , NULL ,
2014-08-05 11:05:43 -05:00
// mark the remaining rect as needing redraw and erase...
SW_INVALIDATE | SW_ERASE ) = = ERROR )
xpanic ( " error scrolling Area " , GetLastError ( ) ) ;
// ...but don't redraw the window yet; we need to apply our scroll changes
// we actually have to commit the change back to the scrollbar; otherwise the scroll position will merely reset itself
ZeroMemory ( & si , sizeof ( SCROLLINFO ) ) ;
si . cbSize = sizeof ( SCROLLINFO ) ;
si . fMask = SIF_POS ;
si . nPos = ( int ) newpos ;
2014-08-05 20:34:58 -05:00
// this is not expressly documented as returning an error so IDK what the error code is so assume there is none
2014-08-05 11:29:37 -05:00
SetScrollInfo ( hwnd , which , & si , TRUE ) ; // redraw scrollbar
2014-08-05 11:05:43 -05:00
// NOW redraw it
if ( UpdateWindow ( hwnd ) = = 0 )
2014-08-05 11:49:08 -05:00
xpanic ( " error updating Area after scrolling " , GetLastError ( ) ) ;
2014-08-23 19:39:47 -05:00
if ( ( HWND ) GetWindowLongPtrW ( hwnd , 0 ) ! = NULL )
if ( UpdateWindow ( ( HWND ) GetWindowLongPtrW ( hwnd , 0 ) ) = = 0 )
xpanic ( " error updating Area TextField after scrolling " , GetLastError ( ) ) ;
2014-08-05 11:05:43 -05:00
}
static void adjustAreaScrollbars ( HWND hwnd , void * data )
{
SCROLLINFO si ;
SIZE size ;
LONG cwid , cht ;
size = getAreaControlSize ( hwnd ) ;
cwid = size . cx ;
cht = size . cy ;
// the trick is we want a page to be the width/height of the visible area
// so the scroll range would go from [0..image_dimension - control_dimension]
// but judging from the sample code on MSDN, we don't need to do this; the scrollbar will do it for us
// we DO need to handle it when scrolling, though, since the thumb can only go up to this upper limit
// have to do horizontal and vertical separately
ZeroMemory ( & si , sizeof ( SCROLLINFO ) ) ;
si . cbSize = sizeof ( SCROLLINFO ) ;
si . fMask = SIF_RANGE | SIF_PAGE ;
si . nMin = 0 ;
si . nMax = ( int ) ( areaWidthLONG ( data ) - 1 ) ; // the max point is inclusive, so we have to pass in the last valid value, not the first invalid one (see http://blogs.msdn.com/b/oldnewthing/archive/2003/07/31/54601.aspx); if we don't, we get weird things like the scrollbar sometimes showing one extra scroll position at the end that you can never scroll to
si . nPage = ( UINT ) cwid ;
SetScrollInfo ( hwnd , SB_HORZ , & si , TRUE ) ; // redraw the scroll bar
// MSDN sample code does this a second time; let's do it too to be safe
ZeroMemory ( & si , sizeof ( SCROLLINFO ) ) ;
si . cbSize = sizeof ( SCROLLINFO ) ;
si . fMask = SIF_RANGE | SIF_PAGE ;
si . nMin = 0 ;
si . nMax = ( int ) ( areaHeightLONG ( data ) - 1 ) ;
si . nPage = ( UINT ) cht ;
SetScrollInfo ( hwnd , SB_VERT , & si , TRUE ) ;
}
2014-08-21 12:01:50 -05:00
void repaintArea ( HWND hwnd , RECT * r )
2014-08-05 11:05:43 -05:00
{
2014-08-05 20:34:58 -05:00
// NULL - the whole area; TRUE - have windows erase if possible
2014-08-21 12:01:50 -05:00
if ( InvalidateRect ( hwnd , r , TRUE ) = = 0 )
2014-08-05 11:05:43 -05:00
xpanic ( " error flagging Area as needing repainting after event " , GetLastError ( ) ) ;
if ( UpdateWindow ( hwnd ) = = 0 )
xpanic ( " error repainting Area after event " , GetLastError ( ) ) ;
}
2014-08-12 04:09:20 -05:00
void areaMouseEvent ( HWND hwnd , void * data , DWORD button , BOOL up , uintptr_t heldButtons , LPARAM lParam )
2014-08-05 11:05:43 -05:00
{
int xpos , ypos ;
// mouse coordinates are relative to control; make them relative to Area
getScrollPos ( hwnd , & xpos , & ypos ) ;
xpos + = GET_X_LPARAM ( lParam ) ;
ypos + = GET_Y_LPARAM ( lParam ) ;
2014-08-12 04:09:20 -05:00
finishAreaMouseEvent ( data , button , up , heldButtons , xpos , ypos ) ;
2014-08-05 11:05:43 -05:00
}
static LRESULT CALLBACK areaWndProc ( HWND hwnd , UINT uMsg , WPARAM wParam , LPARAM lParam )
{
void * data ;
DWORD which ;
2014-08-12 04:09:20 -05:00
uintptr_t heldButtons = ( uintptr_t ) wParam ;
2014-08-14 15:12:43 -05:00
LRESULT lResult ;
2014-08-05 11:05:43 -05:00
2014-08-14 15:12:43 -05:00
data = getWindowData ( hwnd , uMsg , wParam , lParam , & lResult , storeAreaHWND ) ;
if ( data = = NULL )
return lResult ;
2014-08-05 11:05:43 -05:00
switch ( uMsg ) {
case WM_PAINT :
paintArea ( hwnd , data ) ;
return 0 ;
case WM_ERASEBKGND :
// don't draw a background; we'll do so when painting
// this is to make things flicker-free; see http://msdn.microsoft.com/en-us/library/ms969905.aspx
return 1 ;
case WM_HSCROLL :
scrollArea ( hwnd , data , wParam , SB_HORZ ) ;
return 0 ;
case WM_VSCROLL :
scrollArea ( hwnd , data , wParam , SB_VERT ) ;
return 0 ;
case WM_SIZE :
adjustAreaScrollbars ( hwnd , data ) ;
return 0 ;
case WM_ACTIVATE :
// don't keep the double-click timer running if the user switched programs in between clicks
areaResetClickCounter ( data ) ;
return 0 ;
case WM_MOUSEMOVE :
2014-08-12 04:09:20 -05:00
areaMouseEvent ( hwnd , data , 0 , FALSE , heldButtons , lParam ) ;
2014-08-05 11:05:43 -05:00
return 0 ;
case WM_LBUTTONDOWN :
2014-08-23 18:44:55 -05:00
SetFocus ( hwnd ) ;
2014-08-12 04:09:20 -05:00
areaMouseEvent ( hwnd , data , 1 , FALSE , heldButtons , lParam ) ;
2014-08-05 11:05:43 -05:00
return 0 ;
case WM_LBUTTONUP :
2014-08-12 04:09:20 -05:00
areaMouseEvent ( hwnd , data , 1 , TRUE , heldButtons , lParam ) ;
2014-08-05 11:05:43 -05:00
return 0 ;
case WM_MBUTTONDOWN :
2014-08-23 18:44:55 -05:00
SetFocus ( hwnd ) ; // TODO correct?
2014-08-12 04:09:20 -05:00
areaMouseEvent ( hwnd , data , 2 , FALSE , heldButtons , lParam ) ;
2014-08-05 11:29:37 -05:00
return 0 ;
2014-08-05 11:05:43 -05:00
case WM_MBUTTONUP :
2014-08-12 04:09:20 -05:00
areaMouseEvent ( hwnd , data , 2 , TRUE , heldButtons , lParam ) ;
2014-08-05 11:05:43 -05:00
return 0 ;
case WM_RBUTTONDOWN :
2014-08-23 18:44:55 -05:00
SetFocus ( hwnd ) ; // TODO correct?
2014-08-12 04:09:20 -05:00
areaMouseEvent ( hwnd , data , 3 , FALSE , heldButtons , lParam ) ;
2014-08-05 11:05:43 -05:00
return 0 ;
case WM_RBUTTONUP :
2014-08-12 04:09:20 -05:00
areaMouseEvent ( hwnd , data , 3 , TRUE , heldButtons , lParam ) ;
2014-08-05 11:05:43 -05:00
return 0 ;
case WM_XBUTTONDOWN :
2014-08-23 18:44:55 -05:00
SetFocus ( hwnd ) ; // TODO correct?
2014-08-05 11:05:43 -05:00
// values start at 1; we want them to start at 4
which = ( DWORD ) GET_XBUTTON_WPARAM ( wParam ) + 3 ;
2014-08-12 04:09:20 -05:00
heldButtons = ( uintptr_t ) GET_KEYSTATE_WPARAM ( wParam ) ;
areaMouseEvent ( hwnd , data , which , FALSE , heldButtons , lParam ) ;
2014-08-05 11:05:43 -05:00
return TRUE ; // XBUTTON messages are different!
case WM_XBUTTONUP :
which = ( DWORD ) GET_XBUTTON_WPARAM ( wParam ) + 3 ;
2014-08-12 04:09:20 -05:00
heldButtons = ( uintptr_t ) GET_KEYSTATE_WPARAM ( wParam ) ;
areaMouseEvent ( hwnd , data , which , TRUE , heldButtons , lParam ) ;
2014-08-05 11:05:43 -05:00
return TRUE ;
2014-08-21 04:37:24 -05:00
case msgAreaKeyDown :
return ( LRESULT ) areaKeyEvent ( data , FALSE , wParam , lParam ) ;
case msgAreaKeyUp :
return ( LRESULT ) areaKeyEvent ( data , TRUE , wParam , lParam ) ;
2014-08-05 11:05:43 -05:00
case msgAreaSizeChanged :
adjustAreaScrollbars ( hwnd , data ) ;
2014-08-21 12:01:50 -05:00
repaintArea ( hwnd , NULL ) ; // this calls for an update
return 0 ;
case msgAreaGetScroll :
getScrollPos ( hwnd , ( int * ) wParam , ( int * ) lParam ) ;
return 0 ;
case msgAreaRepaint :
repaintArea ( hwnd , ( RECT * ) lParam ) ;
2014-08-05 11:05:43 -05:00
return 0 ;
case msgAreaRepaintAll :
2014-08-21 12:01:50 -05:00
repaintArea ( hwnd , NULL ) ;
2014-08-05 11:05:43 -05:00
return 0 ;
default :
return DefWindowProcW ( hwnd , uMsg , wParam , lParam ) ;
}
xmissedmsg ( " Area " , " areaWndProc() " , uMsg ) ;
2014-08-14 09:42:10 -05:00
return 0 ; // unreached
2014-08-05 11:05:43 -05:00
}
DWORD makeAreaWindowClass ( char * * errmsg )
{
WNDCLASSW wc ;
ZeroMemory ( & wc , sizeof ( WNDCLASSW ) ) ;
wc . style = CS_HREDRAW | CS_VREDRAW ; // no CS_DBLCLKS because do that manually
wc . lpszClassName = areaWindowClass ;
wc . lpfnWndProc = areaWndProc ;
wc . hInstance = hInstance ;
wc . hIcon = hDefaultIcon ;
wc . hCursor = hArrowCursor ,
2014-08-23 19:39:47 -05:00
wc . hbrBackground = NULL ; // no brush; we handle WM_ERASEBKGND
wc . cbWndExtra = 3 * sizeof ( LONG_PTR ) ; // text field handle, text field current x, text field current y
2014-08-05 11:05:43 -05:00
if ( RegisterClassW ( & wc ) = = 0 ) {
* errmsg = " error registering Area window class " ;
return GetLastError ( ) ;
}
return 0 ;
}
2014-08-05 11:17:44 -05:00
HWND newArea ( void * data )
{
HWND hwnd ;
hwnd = CreateWindowExW (
0 ,
areaWindowClass , L " " ,
2014-08-13 09:41:27 -05:00
WS_HSCROLL | WS_VSCROLL | WS_CHILD | WS_VISIBLE | WS_TABSTOP ,
2014-08-05 11:17:44 -05:00
CW_USEDEFAULT , CW_USEDEFAULT ,
100 , 100 ,
msgwin , NULL , hInstance , data ) ;
if ( hwnd = = NULL )
xpanic ( " container creation failed " , GetLastError ( ) ) ;
return hwnd ;
}
2014-08-22 12:57:32 -05:00
static LRESULT CALLBACK areaTextFieldSubProc ( HWND hwnd , UINT uMsg , WPARAM wParam , LPARAM lParam , UINT_PTR id , DWORD_PTR data )
{
switch ( uMsg ) {
case WM_KILLFOCUS :
ShowWindow ( hwnd , SW_HIDE ) ;
2014-08-23 19:39:47 -05:00
areaTextFieldDone ( ( void * ) data ) ;
2014-08-22 12:57:32 -05:00
return ( * fv_DefSubclassProc ) ( hwnd , uMsg , wParam , lParam ) ;
case WM_NCDESTROY :
if ( ( * fv_RemoveWindowSubclass ) ( hwnd , areaTextFieldSubProc , id ) = = FALSE )
xpanic ( " error removing Area TextField subclass (which was for handling WM_KILLFOCUS) " , GetLastError ( ) ) ;
return ( * fv_DefSubclassProc ) ( hwnd , uMsg , wParam , lParam ) ;
default :
return ( * fv_DefSubclassProc ) ( hwnd , uMsg , wParam , lParam ) ;
}
xmissedmsg ( " Area TextField " , " areaTextFieldSubProc() " , uMsg ) ;
return 0 ; // unreached
}
2014-08-23 19:39:47 -05:00
HWND newAreaTextField ( HWND area , void * goarea )
2014-08-22 12:57:32 -05:00
{
HWND tf ;
2014-08-23 18:44:55 -05:00
tf = CreateWindowExW ( textfieldExtStyle ,
2014-08-22 12:57:32 -05:00
L " edit " , L " " ,
2014-08-23 18:44:55 -05:00
textfieldStyle | WS_CHILD ,
2014-08-22 12:57:32 -05:00
0 , 0 , 0 , 0 ,
2014-08-23 19:39:47 -05:00
area , NULL , hInstance , NULL ) ;
2014-08-22 12:57:32 -05:00
if ( tf = = NULL )
xpanic ( " error making Area TextField " , GetLastError ( ) ) ;
2014-08-23 19:39:47 -05:00
if ( ( * fv_SetWindowSubclass ) ( tf , areaTextFieldSubProc , 0 , ( DWORD_PTR ) goarea ) = = FALSE )
2014-08-22 12:57:32 -05:00
xpanic ( " error subclassing Area TextField to give it its own WM_KILLFOCUS handler " , GetLastError ( ) ) ;
return tf ;
}
2014-08-23 19:39:47 -05:00
void areaOpenTextField ( HWND area , HWND textfield , int x , int y , int width , int height )
{
int sx , sy ;
int baseX , baseY ;
LONG unused ;
getScrollPos ( area , & sx , & sy ) ;
x + = sx ;
y + = sy ;
calculateBaseUnits ( textfield , & baseX , & baseY , & unused ) ;
width = MulDiv ( width , baseX , 4 ) ;
height = MulDiv ( height , baseY , 8 ) ;
if ( MoveWindow ( textfield , x , y , width , height , TRUE ) = = 0 )
xpanic ( " error moving Area TextField in Area.OpenTextFieldAt() " , GetLastError ( ) ) ;
ShowWindow ( textfield , SW_SHOW ) ;
if ( SetFocus ( textfield ) = = NULL )
xpanic ( " error giving Area TextField focus " , GetLastError ( ) ) ;
}
void areaMarkTextFieldDone ( HWND area )
{
SetWindowLongPtrW ( area , 0 , ( LONG_PTR ) NULL ) ;
}