2014-10-19 13:44:27 -05:00
// 19 october 2014
# define UNICODE
# define _UNICODE
# define STRICT
# define STRICT_TYPED_ITEMIDS
// get Windows version right; right now Windows XP
# define WINVER 0x0501
# define _WIN32_WINNT 0x0501
# define _WIN32_WINDOWS 0x0501 /* according to Microsoft's winperf.h */
# define _WIN32_IE 0x0600 /* according to Microsoft's sdkddkver.h */
# define NTDDI_VERSION 0x05010000 /* according to Microsoft's sdkddkver.h */
# include <windows.h>
# include <commctrl.h>
# include <stdint.h>
# include <uxtheme.h>
# include <string.h>
# include <wchar.h>
# include <windowsx.h>
# include <vsstyle.h>
# include <vssym32.h>
2014-10-20 12:40:57 -05:00
// #qo LIBS: user32 kernel32 gdi32 comctl32
2014-10-19 13:44:27 -05:00
2014-10-20 09:32:11 -05:00
// TODO
// - http://blogs.msdn.com/b/oldnewthing/archive/2003/09/09/54826.aspx (relies on the integrality parts? IDK)
// - might want to http://blogs.msdn.com/b/oldnewthing/archive/2003/09/17/54944.aspx instead
2014-10-19 13:44:27 -05:00
# define tableWindowClass L"gouitable"
struct table {
HWND hwnd ;
HFONT defaultFont ;
HFONT font ;
intptr_t selected ;
2014-10-19 18:40:23 -05:00
intptr_t count ;
2014-10-19 21:20:53 -05:00
intptr_t firstVisible ;
2014-10-20 12:06:26 -05:00
intptr_t pagesize ; // in rows
2014-10-20 09:21:47 -05:00
int wheelCarry ;
2014-10-20 12:40:57 -05:00
HWND header ;
int headerHeight ;
2014-10-19 13:44:27 -05:00
} ;
2014-10-20 10:19:35 -05:00
static LONG rowHeight ( struct table * t )
2014-10-19 22:33:08 -05:00
{
2014-10-19 22:48:25 -05:00
HFONT thisfont , prevfont ;
TEXTMETRICW tm ;
HDC dc ;
2014-10-19 22:33:08 -05:00
2014-10-19 22:48:25 -05:00
dc = GetDC ( t - > hwnd ) ;
if ( dc = = NULL )
abort ( ) ;
thisfont = t - > font ; // in case WM_SETFONT happens before we return
prevfont = ( HFONT ) SelectObject ( dc , thisfont ) ;
if ( prevfont = = NULL )
abort ( ) ;
if ( GetTextMetricsW ( dc , & tm ) = = 0 )
abort ( ) ;
if ( SelectObject ( dc , prevfont ) ! = ( HGDIOBJ ) ( thisfont ) )
abort ( ) ;
if ( ReleaseDC ( t - > hwnd , dc ) = = 0 )
abort ( ) ;
2014-10-20 10:19:35 -05:00
return tm . tmHeight ;
}
2014-10-20 10:34:33 -05:00
static void redrawAll ( struct table * t )
{
if ( InvalidateRect ( t - > hwnd , NULL , TRUE ) = = 0 )
abort ( ) ;
if ( UpdateWindow ( t - > hwnd ) = = 0 )
abort ( ) ;
}
2014-10-20 12:06:26 -05:00
static void keySelect ( struct table * t , WPARAM wParam , LPARAM lParam )
{
// TODO what happens if up/page up is pressed with nothing selected?
if ( t - > count = = 0 ) // don't try to do anything if there's nothing to do
return ;
switch ( wParam ) {
case VK_UP :
t - > selected - - ;
break ;
case VK_DOWN :
t - > selected + + ;
break ;
case VK_PRIOR :
t - > selected - = t - > pagesize ;
break ;
case VK_NEXT :
t - > selected + = t - > pagesize ;
break ;
case VK_HOME :
t - > selected = 0 ;
break ;
case VK_END :
t - > selected = t - > count - 1 ;
break ;
default :
// don't touch anything
return ;
}
if ( t - > selected < 0 )
t - > selected = 0 ;
if ( t - > selected > = t - > count )
t - > selected = t - > count - 1 ;
// TODO update only the old and new selected items
redrawAll ( t ) ;
// TODO scroll to the selected item if it's not entirely visible
}
2014-10-20 10:34:33 -05:00
static void selectItem ( struct table * t , WPARAM wParam , LPARAM lParam )
{
int x , y ;
LONG h ;
x = GET_X_LPARAM ( lParam ) ;
y = GET_Y_LPARAM ( lParam ) ;
h = rowHeight ( t ) ;
y + = t - > firstVisible * h ;
y / = h ;
t - > selected = y ;
if ( t - > selected > = t - > count )
t - > selected = - 1 ;
// TODO update only the old and new selected items
redrawAll ( t ) ;
// TODO scroll to the selected item if it's not entirely visible
}
2014-10-20 10:19:35 -05:00
static void vscrollto ( struct table * t , intptr_t newpos )
{
SCROLLINFO si ;
2014-10-19 22:48:25 -05:00
2014-10-20 09:21:47 -05:00
if ( newpos < 0 )
newpos = 0 ;
if ( newpos > ( t - > count - t - > pagesize ) )
newpos = ( t - > count - t - > pagesize ) ;
// negative because ScrollWindowEx() is "backwards"
2014-10-20 10:19:35 -05:00
if ( ScrollWindowEx ( t - > hwnd , 0 , ( - ( newpos - t - > firstVisible ) ) * rowHeight ( t ) ,
2014-10-20 09:21:47 -05:00
NULL , NULL , NULL , NULL ,
SW_ERASE | SW_INVALIDATE ) = = ERROR )
abort ( ) ;
t - > firstVisible = newpos ;
ZeroMemory ( & si , sizeof ( SCROLLINFO ) ) ;
si . cbSize = sizeof ( SCROLLINFO ) ;
si . fMask = SIF_PAGE | SIF_POS | SIF_RANGE ;
si . nPage = t - > pagesize ;
si . nMin = 0 ;
si . nMax = t - > count - 1 ; // nMax is inclusive
si . nPos = t - > firstVisible ;
SetScrollInfo ( t - > hwnd , SB_VERT , & si , TRUE ) ;
}
static void vscrollby ( struct table * t , intptr_t n )
{
vscrollto ( t , t - > firstVisible + n ) ;
}
static void wheelscroll ( struct table * t , WPARAM wParam )
{
int delta ;
int lines ;
UINT scrollAmount ;
delta = GET_WHEEL_DELTA_WPARAM ( wParam ) ;
if ( SystemParametersInfoW ( SPI_GETWHEELSCROLLLINES , 0 , & scrollAmount , 0 ) = = 0 )
abort ( ) ;
if ( scrollAmount = = WHEEL_PAGESCROLL )
scrollAmount = t - > pagesize ;
if ( scrollAmount = = 0 ) // no mouse wheel scrolling (or t->pagesize == 0)
return ;
// the rest of this is basically http://blogs.msdn.com/b/oldnewthing/archive/2003/08/07/54615.aspx and http://blogs.msdn.com/b/oldnewthing/archive/2003/08/11/54624.aspx
// see those pages for information on subtleties
delta + = t - > wheelCarry ;
lines = delta * ( ( int ) scrollAmount ) / WHEEL_DELTA ;
t - > wheelCarry = delta - lines * WHEEL_DELTA / ( ( int ) scrollAmount ) ;
vscrollby ( t , - lines ) ;
}
static void vscroll ( struct table * t , WPARAM wParam )
{
SCROLLINFO si ;
intptr_t newpos ;
2014-10-19 22:33:08 -05:00
ZeroMemory ( & si , sizeof ( SCROLLINFO ) ) ;
si . cbSize = sizeof ( SCROLLINFO ) ;
si . fMask = SIF_POS | SIF_TRACKPOS ;
if ( GetScrollInfo ( t - > hwnd , SB_VERT , & si ) = = 0 )
abort ( ) ;
newpos = t - > firstVisible ;
switch ( LOWORD ( wParam ) ) {
case SB_TOP :
newpos = 0 ;
break ;
case SB_BOTTOM :
newpos = t - > count - t - > pagesize ;
break ;
case SB_LINEUP :
newpos - - ;
break ;
case SB_LINEDOWN :
newpos + + ;
break ;
case SB_PAGEUP :
newpos - = t - > pagesize ;
break ;
case SB_PAGEDOWN :
newpos + = t - > pagesize ;
break ;
case SB_THUMBPOSITION :
newpos = ( intptr_t ) ( si . nPos ) ;
break ;
case SB_THUMBTRACK :
newpos = ( intptr_t ) ( si . nTrackPos ) ;
}
2014-10-20 09:21:47 -05:00
vscrollto ( t , newpos ) ;
2014-10-19 22:33:08 -05:00
}
2014-10-19 22:48:25 -05:00
static void resize ( struct table * t )
{
RECT r ;
SCROLLINFO si ;
2014-10-20 12:40:57 -05:00
HDLAYOUT headerlayout ;
WINDOWPOS headerpos ;
2014-10-19 22:48:25 -05:00
if ( GetClientRect ( t - > hwnd , & r ) = = 0 )
abort ( ) ;
2014-10-20 10:19:35 -05:00
t - > pagesize = ( r . bottom - r . top ) / rowHeight ( t ) ;
2014-10-19 22:48:25 -05:00
ZeroMemory ( & si , sizeof ( SCROLLINFO ) ) ;
si . cbSize = sizeof ( SCROLLINFO ) ;
si . fMask = SIF_RANGE | SIF_PAGE ;
si . nMin = 0 ;
si . nMax = t - > count - 1 ;
si . nPage = t - > pagesize ;
SetScrollInfo ( t - > hwnd , SB_VERT , & si , TRUE ) ;
2014-10-20 12:40:57 -05:00
headerlayout . prc = & r ;
headerlayout . pwpos = & headerpos ;
if ( SendMessageW ( t - > header , HDM_LAYOUT , 0 , ( LPARAM ) ( & headerlayout ) ) = = FALSE )
abort ( ) ;
if ( SetWindowPos ( t - > header , headerpos . hwndInsertAfter , headerpos . x , headerpos . y , headerpos . cx , headerpos . cy , headerpos . flags | SWP_SHOWWINDOW ) = = 0 )
abort ( ) ;
t - > headerHeight = headerpos . cy ;
2014-10-19 22:48:25 -05:00
}
2014-10-19 20:49:27 -05:00
static void drawItems ( struct table * t , HDC dc , RECT cliprect )
2014-10-19 13:44:27 -05:00
{
2014-10-19 19:01:01 -05:00
HFONT thisfont , prevfont ;
2014-10-19 13:44:27 -05:00
TEXTMETRICW tm ;
LONG y ;
intptr_t i ;
RECT r ;
2014-10-19 20:49:27 -05:00
intptr_t first , last ;
2014-10-19 21:20:53 -05:00
POINT prevOrigin ;
2014-10-19 13:44:27 -05:00
2014-10-19 20:49:27 -05:00
// TODO eliminate the need (only use cliprect)
2014-10-19 13:44:27 -05:00
if ( GetClientRect ( t - > hwnd , & r ) = = 0 )
abort ( ) ;
2014-10-19 20:49:27 -05:00
2014-10-19 19:01:01 -05:00
thisfont = t - > font ; // in case WM_SETFONT happens before we return
prevfont = ( HFONT ) SelectObject ( dc , thisfont ) ;
2014-10-19 13:44:27 -05:00
if ( prevfont = = NULL )
abort ( ) ;
if ( GetTextMetricsW ( dc , & tm ) = = 0 )
abort ( ) ;
2014-10-19 20:49:27 -05:00
2014-10-19 21:20:53 -05:00
// adjust the clip rect and the viewport so that (0, 0) is always the first item
if ( OffsetRect ( & cliprect , 0 , t - > firstVisible * tm . tmHeight ) = = 0 )
abort ( ) ;
if ( GetWindowOrgEx ( dc , & prevOrigin ) = = 0 )
abort ( ) ;
if ( SetWindowOrgEx ( dc , prevOrigin . x , prevOrigin . y + ( t - > firstVisible * tm . tmHeight ) , NULL ) = = 0 )
abort ( ) ;
2014-10-19 20:49:27 -05:00
// see http://blogs.msdn.com/b/oldnewthing/archive/2003/07/29/54591.aspx and http://blogs.msdn.com/b/oldnewthing/archive/2003/07/30/54600.aspx
first = cliprect . top / tm . tmHeight ;
if ( first < 0 )
first = 0 ;
last = ( cliprect . bottom + tm . tmHeight - 1 ) / tm . tmHeight ;
if ( last > = t - > count )
last = t - > count ;
2014-10-19 22:51:42 -05:00
y = first * tm . tmHeight ;
2014-10-19 20:49:27 -05:00
for ( i = first ; i < last ; i + + ) {
2014-10-19 18:02:18 -05:00
RECT rsel ;
HBRUSH background ;
2014-10-20 11:10:30 -05:00
int textColor ;
2014-10-20 10:19:35 -05:00
WCHAR msg [ 100 ] ;
2014-10-19 18:02:18 -05:00
2014-10-19 13:44:27 -05:00
// TODO check errors
2014-10-19 18:02:18 -05:00
rsel . left = r . left ;
rsel . top = y ;
rsel . right = r . right - r . left ;
rsel . bottom = y + tm . tmHeight ;
2014-10-20 11:10:30 -05:00
// TODO verify these two
2014-10-19 18:02:18 -05:00
background = ( HBRUSH ) ( COLOR_WINDOW + 1 ) ;
2014-10-20 11:10:30 -05:00
textColor = COLOR_WINDOWTEXT ;
2014-10-19 13:44:27 -05:00
if ( t - > selected = = i ) {
2014-10-20 11:10:30 -05:00
// these are the colors wine uses (http://source.winehq.org/source/dlls/comctl32/listview.c)
// the two for unfocused are also suggested by http://stackoverflow.com/questions/10428710/windows-forms-inactive-highlight-color
2014-10-19 18:02:18 -05:00
background = ( HBRUSH ) ( COLOR_HIGHLIGHT + 1 ) ;
2014-10-20 11:10:30 -05:00
textColor = COLOR_HIGHLIGHTTEXT ;
if ( GetFocus ( ) ! = t - > hwnd ) {
background = ( HBRUSH ) ( COLOR_BTNFACE + 1 ) ;
textColor = COLOR_BTNTEXT ;
}
}
SetTextColor ( dc , GetSysColor ( textColor ) ) ;
2014-10-19 18:02:18 -05:00
FillRect ( dc , & rsel , background ) ;
2014-10-19 13:44:27 -05:00
SetBkMode ( dc , TRANSPARENT ) ;
2014-10-20 10:19:35 -05:00
TextOutW ( dc , r . left , y , msg , wsprintf ( msg , L " Item %d " , i ) ) ;
2014-10-19 13:44:27 -05:00
y + = tm . tmHeight ;
}
2014-10-19 20:49:27 -05:00
2014-10-19 21:20:53 -05:00
// reset everything
if ( SetWindowOrgEx ( dc , prevOrigin . x , prevOrigin . y , NULL ) = = 0 )
abort ( ) ;
2014-10-19 19:01:01 -05:00
if ( SelectObject ( dc , prevfont ) ! = ( HGDIOBJ ) ( thisfont ) )
2014-10-19 13:44:27 -05:00
abort ( ) ;
}
static LRESULT CALLBACK tableWndProc ( HWND hwnd , UINT uMsg , WPARAM wParam , LPARAM lParam )
{
struct table * t ;
HDC dc ;
PAINTSTRUCT ps ;
t = ( struct table * ) GetWindowLongPtrW ( hwnd , GWLP_USERDATA ) ;
if ( t = = NULL ) {
2014-10-20 12:40:57 -05:00
// we have to do things this way because creating the header control will fail mysteriously if we create it first thing
// (which is fine; we can get the parent hInstance this way too)
if ( uMsg = = WM_NCCREATE ) {
CREATESTRUCTW * cs = ( CREATESTRUCTW * ) lParam ;
t = ( struct table * ) malloc ( sizeof ( struct table ) ) ;
if ( t = = NULL )
abort ( ) ;
ZeroMemory ( t , sizeof ( struct table ) ) ;
t - > hwnd = hwnd ;
// TODO this should be a global
t - > defaultFont = ( HFONT ) GetStockObject ( SYSTEM_FONT ) ;
if ( t - > defaultFont = = NULL )
abort ( ) ;
t - > font = t - > defaultFont ;
2014-10-19 18:40:23 -05:00
t - > selected = 5 ; t - > count = 100 ; //TODO
2014-10-20 12:40:57 -05:00
t - > header = CreateWindowExW ( 0 ,
WC_HEADERW , L " " ,
// TODO is HOTTRACK needed?
WS_CHILD | HDS_FULLDRAG | HDS_HORZ | HDS_HOTTRACK ,
0 , 0 , 0 , 0 ,
t - > hwnd , ( HMENU ) 100 , cs - > hInstance , NULL ) ;
if ( t - > header = = NULL )
abort ( ) ;
{ HDITEMW item ;
ZeroMemory ( & item , sizeof ( HDITEMW ) ) ;
item . mask = HDI_WIDTH | HDI_TEXT | HDI_FORMAT ;
item . cxy = 200 ;
item . pszText = L " Column " ;
item . fmt = HDF_LEFT | HDF_STRING ;
if ( SendMessage ( t - > header , HDM_INSERTITEM , 0 , ( LPARAM ) ( & item ) ) = = ( LRESULT ) ( - 1 ) )
abort ( ) ; }
SetWindowLongPtrW ( hwnd , GWLP_USERDATA , ( LONG_PTR ) t ) ;
}
// even if we did the above, fall through
return DefWindowProcW ( hwnd , uMsg , wParam , lParam ) ;
2014-10-19 13:44:27 -05:00
}
switch ( uMsg ) {
case WM_PAINT :
dc = BeginPaint ( hwnd , & ps ) ;
if ( dc = = NULL )
abort ( ) ;
2014-10-19 20:49:27 -05:00
drawItems ( t , dc , ps . rcPaint ) ;
2014-10-19 13:44:27 -05:00
EndPaint ( hwnd , & ps ) ;
return 0 ;
case WM_SETFONT :
t - > font = ( HFONT ) wParam ;
if ( t - > font = = NULL )
t - > font = t - > defaultFont ;
2014-10-20 12:40:57 -05:00
// also set the header font
SendMessageW ( t - > header , WM_SETFONT , wParam , lParam ) ;
2014-10-20 10:34:33 -05:00
if ( LOWORD ( lParam ) ! = FALSE ) {
// the scrollbar page size will change so redraw that too
2014-10-20 12:40:57 -05:00
// also recalculate the header height
// TODO do that when this is FALSE too somehow
2014-10-20 10:34:33 -05:00
resize ( t ) ;
redrawAll ( t ) ;
}
2014-10-19 13:44:27 -05:00
return 0 ;
case WM_GETFONT :
return ( LRESULT ) t - > font ;
2014-10-19 22:33:08 -05:00
case WM_VSCROLL :
vscroll ( t , wParam ) ;
return 0 ;
2014-10-20 09:21:47 -05:00
case WM_MOUSEWHEEL :
wheelscroll ( t , wParam ) ;
return 0 ;
2014-10-19 22:33:08 -05:00
case WM_SIZE :
2014-10-19 22:48:25 -05:00
resize ( t ) ;
2014-10-19 22:33:08 -05:00
return 0 ;
2014-10-20 10:34:33 -05:00
case WM_LBUTTONDOWN :
selectItem ( t , wParam , lParam ) ;
return 0 ;
2014-10-20 11:10:30 -05:00
case WM_SETFOCUS :
case WM_KILLFOCUS :
// all we need to do here is redraw the highlight
// TODO localize to just the selected item
// TODO ensure giving focus works right
redrawAll ( t ) ;
return 0 ;
2014-10-20 12:06:26 -05:00
case WM_KEYDOWN :
keySelect ( t , wParam , lParam ) ;
return 0 ;
2014-10-19 13:44:27 -05:00
default :
return DefWindowProcW ( hwnd , uMsg , wParam , lParam ) ;
}
abort ( ) ;
return 0 ; // unreached
}
void makeTableWindowClass ( void )
{
WNDCLASSW wc ;
ZeroMemory ( & wc , sizeof ( WNDCLASSW ) ) ;
wc . lpszClassName = tableWindowClass ;
wc . lpfnWndProc = tableWndProc ;
wc . hCursor = LoadCursorW ( NULL , IDC_ARROW ) ;
wc . hIcon = LoadIconW ( NULL , IDI_APPLICATION ) ;
wc . hbrBackground = ( HBRUSH ) ( COLOR_WINDOW + 1 ) ; // TODO correct?
2014-10-19 20:49:27 -05:00
wc . style = CS_HREDRAW | CS_VREDRAW ;
2014-10-19 13:44:27 -05:00
wc . hInstance = GetModuleHandle ( NULL ) ;
if ( RegisterClassW ( & wc ) = = 0 )
abort ( ) ;
}
int main ( void )
{
HWND mainwin ;
MSG msg ;
2014-10-20 12:40:57 -05:00
INITCOMMONCONTROLSEX icc ;
2014-10-19 13:44:27 -05:00
2014-10-20 12:40:57 -05:00
ZeroMemory ( & icc , sizeof ( INITCOMMONCONTROLSEX ) ) ;
icc . dwSize = sizeof ( INITCOMMONCONTROLSEX ) ;
icc . dwICC = ICC_LISTVIEW_CLASSES ;
if ( InitCommonControlsEx ( & icc ) = = 0 )
abort ( ) ;
2014-10-19 13:44:27 -05:00
makeTableWindowClass ( ) ;
mainwin = CreateWindowExW ( 0 ,
tableWindowClass , L " Main Window " ,
WS_OVERLAPPEDWINDOW | WS_HSCROLL | WS_VSCROLL ,
CW_USEDEFAULT , CW_USEDEFAULT ,
400 , 400 ,
NULL , NULL , GetModuleHandle ( NULL ) , NULL ) ;
if ( mainwin = = NULL )
abort ( ) ;
ShowWindow ( mainwin , SW_SHOWDEFAULT ) ;
if ( UpdateWindow ( mainwin ) = = 0 )
abort ( ) ;
while ( GetMessageW ( & msg , NULL , 0 , 0 ) > 0 ) {
TranslateMessage ( & msg ) ;
DispatchMessageW ( & msg ) ;
}
return 0 ;
}