2018-06-14 20:31:45 -05:00
// 14 june 2018
# include "uipriv_windows.hpp"
# include "table.hpp"
2018-06-21 22:47:24 -05:00
// TODOs:
// - properly hide selection when not focused (or switch on LVS_SHOWSELALWAYS and draw that state)
2018-06-20 17:03:56 -05:00
// TODO
# define cellValue(model, row, column) ((*((model)->mh->CellValue))((model)->mh, (model), (row), (column)))
2018-06-14 20:31:45 -05:00
struct drawState {
uiTable * t ;
2018-06-20 17:03:56 -05:00
uiTableModel * model ;
2018-06-14 20:31:45 -05:00
uiprivTableColumnParams * p ;
HDC dc ;
int iItem ;
int iSubItem ;
2018-06-20 17:03:56 -05:00
uiprivTableMetrics * m ;
2018-06-14 20:31:45 -05:00
COLORREF bgColor ;
HBRUSH bgBrush ;
BOOL freeBgBrush ;
COLORREF textColor ;
HBRUSH textBrush ;
BOOL freeTextBrush ;
} ;
2018-06-20 17:03:56 -05:00
static HRESULT drawBackgrounds ( struct drawState * s )
2018-06-14 20:31:45 -05:00
{
2018-06-20 17:03:56 -05:00
if ( s - > m - > hasImage )
if ( FillRect ( s - > dc , & ( s - > m - > subitemIcon ) , GetSysColorBrush ( COLOR_WINDOW ) ) = = 0 ) {
2018-06-15 21:50:19 -05:00
logLastError ( L " FillRect() icon " ) ;
return E_FAIL ;
}
2018-06-20 17:03:56 -05:00
if ( FillRect ( s - > dc , & ( s - > m - > realTextBackground ) , s - > bgBrush ) = = 0 ) {
2018-06-14 20:31:45 -05:00
logLastError ( L " FillRect() " ) ;
return E_FAIL ;
}
return S_OK ;
}
2018-06-16 09:22:41 -05:00
static void centerImageRect ( RECT * image , RECT * space )
{
2018-06-16 10:59:17 -05:00
LONG xoff , yoff ;
// first make sure both have the same upper-left
xoff = image - > left - space - > left ;
yoff = image - > top - space - > top ;
image - > left - = xoff ;
image - > top - = yoff ;
image - > right - = xoff ;
image - > bottom - = yoff ;
// now center
xoff = ( ( space - > right - space - > left ) - ( image - > right - image - > left ) ) / 2 ;
2018-06-16 09:22:41 -05:00
yoff = ( ( space - > bottom - space - > top ) - ( image - > bottom - image - > top ) ) / 2 ;
2018-06-16 10:59:17 -05:00
image - > left + = xoff ;
2018-06-16 09:22:41 -05:00
image - > top + = yoff ;
2018-06-16 10:59:17 -05:00
image - > right + = xoff ;
2018-06-16 09:22:41 -05:00
image - > bottom + = yoff ;
}
2018-06-15 21:50:19 -05:00
static HRESULT drawImagePart ( struct drawState * s )
{
uiTableData * data ;
IWICBitmap * wb ;
HBITMAP b ;
2018-06-16 09:22:41 -05:00
RECT r ;
2018-06-15 21:50:19 -05:00
UINT fStyle ;
HRESULT hr ;
if ( s - > p - > imageModelColumn = = - 1 )
return S_OK ;
2018-06-20 17:03:56 -05:00
data = cellValue ( s - > model , s - > iItem , s - > p - > imageModelColumn ) ;
2018-06-15 21:50:19 -05:00
wb = uiprivImageAppropriateForDC ( uiTableDataImage ( data ) , s - > dc ) ;
uiFreeTableData ( data ) ;
2018-06-20 17:03:56 -05:00
hr = uiprivWICToGDI ( wb , s - > dc , s - > m - > cxIcon , s - > m - > cyIcon , & b ) ;
2018-06-15 21:50:19 -05:00
if ( hr ! = S_OK )
return hr ;
// TODO rewrite this condition to make more sense; possibly swap the if and else blocks too
// TODO proper cleanup
2018-06-15 22:00:39 -05:00
if ( ImageList_GetImageCount ( s - > t - > imagelist ) > 1 ) {
if ( ImageList_Replace ( s - > t - > imagelist , 0 , b , NULL ) = = 0 ) {
2018-06-15 21:50:19 -05:00
logLastError ( L " ImageList_Replace() " ) ;
return E_FAIL ;
}
} else
2018-06-15 22:00:39 -05:00
if ( ImageList_Add ( s - > t - > imagelist , b , NULL ) = = - 1 ) {
2018-06-15 21:50:19 -05:00
logLastError ( L " ImageList_Add() " ) ;
return E_FAIL ;
}
// TODO error check
DeleteObject ( b ) ;
2018-06-20 17:03:56 -05:00
r = s - > m - > subitemIcon ;
r . right = r . left + s - > m - > cxIcon ;
r . bottom = r . top + s - > m - > cyIcon ;
centerImageRect ( & r , & ( s - > m - > subitemIcon ) ) ;
2018-06-15 21:50:19 -05:00
fStyle = ILD_NORMAL ;
2018-06-20 17:03:56 -05:00
if ( s - > m - > selected )
2018-06-15 21:50:19 -05:00
fStyle = ILD_SELECTED ;
2018-06-15 22:00:39 -05:00
if ( ImageList_Draw ( s - > t - > imagelist , 0 ,
2018-06-16 09:22:41 -05:00
s - > dc , r . left , r . top , fStyle ) = = 0 ) {
2018-06-15 21:50:19 -05:00
logLastError ( L " ImageList_Draw() " ) ;
return E_FAIL ;
}
return S_OK ;
}
2018-06-16 10:59:17 -05:00
// references for checkbox drawing:
// - https://blogs.msdn.microsoft.com/oldnewthing/20171129-00/?p=97485
// - https://blogs.msdn.microsoft.com/oldnewthing/20171201-00/?p=97505
static HRESULT drawUnthemedCheckbox ( struct drawState * s , int checked , int enabled )
{
RECT r ;
UINT state ;
2018-06-20 17:03:56 -05:00
r = s - > m - > subitemIcon ;
2018-06-16 10:59:17 -05:00
// this is what the actual list view LVS_EX_CHECKBOXES code does to size the checkboxes
// TODO reverify the initial size
r . right = r . left + GetSystemMetrics ( SM_CXSMICON ) ;
r . bottom = r . top + GetSystemMetrics ( SM_CYSMICON ) ;
if ( InflateRect ( & r , - GetSystemMetrics ( SM_CXEDGE ) , - GetSystemMetrics ( SM_CYEDGE ) ) = = 0 ) {
logLastError ( L " InflateRect() " ) ;
return E_FAIL ;
}
r . right + + ;
r . bottom + + ;
2018-06-20 17:03:56 -05:00
centerImageRect ( & r , & ( s - > m - > subitemIcon ) ) ;
2018-06-16 10:59:17 -05:00
state = DFCS_BUTTONCHECK | DFCS_FLAT ;
if ( checked )
state | = DFCS_CHECKED ;
if ( ! enabled )
state | = DFCS_INACTIVE ;
if ( DrawFrameControl ( s - > dc , & r , DFC_BUTTON , state ) = = 0 ) {
logLastError ( L " DrawFrameControl() " ) ;
return E_FAIL ;
}
return S_OK ;
}
static HRESULT drawThemedCheckbox ( struct drawState * s , HTHEME theme , int checked , int enabled )
{
RECT r ;
SIZE size ;
int state ;
HRESULT hr ;
hr = GetThemePartSize ( theme , s - > dc ,
BP_CHECKBOX , CBS_UNCHECKEDNORMAL ,
NULL , TS_DRAW , & size ) ;
if ( hr ! = S_OK ) {
logHRESULT ( L " GetThemePartSize() " , hr ) ;
return hr ; // TODO fall back?
}
2018-06-20 17:03:56 -05:00
r = s - > m - > subitemIcon ;
2018-06-16 10:59:17 -05:00
r . right = r . left + size . cx ;
r . bottom = r . top + size . cy ;
2018-06-20 17:03:56 -05:00
centerImageRect ( & r , & ( s - > m - > subitemIcon ) ) ;
2018-06-16 10:59:17 -05:00
if ( ! checked & & enabled )
state = CBS_UNCHECKEDNORMAL ;
else if ( checked & & enabled )
state = CBS_CHECKEDNORMAL ;
else if ( ! checked & & ! enabled )
state = CBS_UNCHECKEDDISABLED ;
else
state = CBS_CHECKEDDISABLED ;
hr = DrawThemeBackground ( theme , s - > dc ,
BP_CHECKBOX , state ,
& r , NULL ) ;
if ( hr ! = S_OK ) {
logHRESULT ( L " DrawThemeBackground() " , hr ) ;
return hr ;
}
return S_OK ;
}
static HRESULT drawCheckboxPart ( struct drawState * s )
{
uiTableData * data ;
int checked , enabled ;
HTHEME theme ;
HRESULT hr ;
if ( s - > p - > checkboxModelColumn = = - 1 )
return S_OK ;
2018-06-20 17:03:56 -05:00
data = cellValue ( s - > model , s - > iItem , s - > p - > checkboxModelColumn ) ;
2018-06-16 10:59:17 -05:00
checked = uiTableDataInt ( data ) ;
uiFreeTableData ( data ) ;
switch ( s - > p - > checkboxEditableColumn ) {
case uiTableModelColumnNeverEditable :
enabled = 0 ;
break ;
case uiTableModelColumnAlwaysEditable :
enabled = 1 ;
break ;
default :
2018-06-20 17:03:56 -05:00
data = cellValue ( s - > model , s - > iItem , s - > p - > checkboxEditableColumn ) ;
2018-06-16 10:59:17 -05:00
enabled = uiTableDataInt ( data ) ;
uiFreeTableData ( data ) ;
}
theme = OpenThemeData ( s - > t - > hwnd , L " button " ) ;
if ( theme ! = NULL ) {
hr = drawThemedCheckbox ( s , theme , checked , enabled ) ;
if ( hr ! = S_OK )
return hr ;
hr = CloseThemeData ( theme ) ;
if ( hr ! = S_OK ) {
logHRESULT ( L " CloseThemeData() " , hr ) ;
return hr ;
}
} else {
hr = drawUnthemedCheckbox ( s , checked , enabled ) ;
if ( hr ! = S_OK )
return hr ;
}
return S_OK ;
}
2018-06-14 20:31:45 -05:00
static HRESULT drawTextPart ( struct drawState * s )
{
COLORREF prevText ;
int prevMode ;
RECT r ;
uiTableData * data ;
WCHAR * wstr ;
2018-06-20 17:03:56 -05:00
if ( ! s - > m - > hasText )
2018-06-14 20:31:45 -05:00
return S_OK ;
2018-06-20 18:31:21 -05:00
// don't draw the text underneath an edit control
if ( s - > t - > edit ! = NULL & & s - > t - > editedItem = = s - > iItem & & s - > t - > editedSubitem = = s - > iSubItem )
return S_OK ;
2018-06-14 20:31:45 -05:00
prevText = SetTextColor ( s - > dc , s - > textColor ) ;
if ( prevText = = CLR_INVALID ) {
logLastError ( L " SetTextColor() " ) ;
return E_FAIL ;
}
prevMode = SetBkMode ( s - > dc , TRANSPARENT ) ;
if ( prevMode = = 0 ) {
logLastError ( L " SetBkMode() " ) ;
return E_FAIL ;
}
2018-06-20 17:03:56 -05:00
data = cellValue ( s - > model , s - > iItem , s - > p - > textModelColumn ) ;
2018-06-14 20:31:45 -05:00
wstr = toUTF16 ( uiTableDataString ( data ) ) ;
uiFreeTableData ( data ) ;
// These flags are a menagerie of flags from various sources:
// guessing, the Windows 2000 source leak, various custom
// draw examples on the web, etc.
// TODO find the real correct flags
2018-06-20 17:03:56 -05:00
if ( DrawTextW ( s - > dc , wstr , - 1 , & ( s - > m - > realTextRect ) , DT_LEFT | DT_VCENTER | DT_END_ELLIPSIS | DT_SINGLELINE | DT_NOPREFIX | DT_EDITCONTROL ) = = 0 ) {
2018-06-14 20:31:45 -05:00
uiprivFree ( wstr ) ;
logLastError ( L " DrawTextW() " ) ;
return E_FAIL ;
}
uiprivFree ( wstr ) ;
// TODO decide once and for all what to compare to here and with SelectObject()
2018-06-15 09:04:32 -05:00
if ( SetBkMode ( s - > dc , prevMode ) ! = TRANSPARENT ) {
2018-06-14 20:31:45 -05:00
logLastError ( L " SetBkMode() prev " ) ;
return E_FAIL ;
}
2018-06-15 09:04:32 -05:00
if ( SetTextColor ( s - > dc , prevText ) ! = s - > textColor ) {
2018-06-14 20:31:45 -05:00
logLastError ( L " SetTextColor() prev " ) ;
return E_FAIL ;
}
return S_OK ;
}
2018-06-16 12:57:46 -05:00
// much of this is to imitate what shell32.dll's CDrawProgressBar does
2018-06-16 17:06:44 -05:00
# define indeterminateSegments 8
2018-06-16 12:57:46 -05:00
static HRESULT drawProgressBarPart ( struct drawState * s )
{
int progress ;
2018-06-16 18:05:36 -05:00
LONG indeterminatePos ;
2018-06-16 12:57:46 -05:00
HTHEME theme ;
RECT r ;
2018-06-16 17:06:44 -05:00
RECT rBorder , rFill [ 2 ] ;
int i , nFill ;
2018-06-16 12:57:46 -05:00
TEXTMETRICW tm ;
int sysColor ;
HRESULT hr ;
if ( s - > p - > progressBarModelColumn = = - 1 )
return S_OK ;
2018-06-17 08:01:24 -05:00
2018-06-16 18:40:06 -05:00
progress = uiprivTableProgress ( s - > t , s - > iItem , s - > iSubItem , s - > p - > progressBarModelColumn , & indeterminatePos ) ;
2018-06-16 12:57:46 -05:00
2018-06-16 19:10:39 -05:00
theme = OpenThemeData ( s - > t - > hwnd , L " PROGRESS " ) ;
2018-06-16 12:57:46 -05:00
if ( GetTextMetricsW ( s - > dc , & tm ) = = 0 ) {
logLastError ( L " GetTextMetricsW() " ) ;
hr = E_FAIL ;
goto fail ;
}
2018-06-20 17:03:56 -05:00
r = s - > m - > subitemBounds ;
2018-06-16 12:57:46 -05:00
// this sets the height of the progressbar and vertically centers it in one fell swoop
r . top + = ( r . bottom - tm . tmHeight - r . top ) / 2 ;
r . bottom = r . top + tm . tmHeight ;
// TODO check errors
rBorder = r ;
InflateRect ( & rBorder , - 1 , - 1 ) ;
if ( theme ! = NULL ) {
2018-06-16 19:10:39 -05:00
RECT crect ;
hr = GetThemeBackgroundContentRect ( theme , s - > dc ,
PP_TRANSPARENTBAR , PBBS_NORMAL ,
& rBorder , & crect ) ;
if ( hr ! = S_OK ) {
logHRESULT ( L " GetThemeBackgroundContentRect() " , hr ) ;
goto fail ;
}
hr = DrawThemeBackground ( theme , s - > dc ,
PP_TRANSPARENTBAR , PBBS_NORMAL ,
& crect , NULL ) ;
if ( hr ! = S_OK ) {
logHRESULT ( L " DrawThemeBackground() border " , hr ) ;
goto fail ;
}
} else {
2018-06-16 12:57:46 -05:00
HPEN pen , prevPen ;
HBRUSH brush , prevBrush ;
sysColor = COLOR_HIGHLIGHT ;
2018-06-20 17:03:56 -05:00
if ( s - > m - > selected )
2018-06-16 12:57:46 -05:00
sysColor = COLOR_HIGHLIGHTTEXT ;
// TODO check errors everywhere
pen = CreatePen ( PS_SOLID , 1 , GetSysColor ( sysColor ) ) ;
prevPen = ( HPEN ) SelectObject ( s - > dc , pen ) ;
brush = ( HBRUSH ) GetStockObject ( NULL_BRUSH ) ;
prevBrush = ( HBRUSH ) SelectObject ( s - > dc , brush ) ;
Rectangle ( s - > dc , rBorder . left , rBorder . top , rBorder . right , rBorder . bottom ) ;
SelectObject ( s - > dc , prevBrush ) ;
SelectObject ( s - > dc , prevPen ) ;
DeleteObject ( pen ) ;
}
2018-06-16 17:06:44 -05:00
nFill = 1 ;
rFill [ 0 ] = r ;
2018-06-16 12:57:46 -05:00
// TODO check error
2018-06-16 17:06:44 -05:00
InflateRect ( & rFill [ 0 ] , - 1 , - 1 ) ;
if ( progress ! = - 1 )
rFill [ 0 ] . right - = ( rFill [ 0 ] . right - rFill [ 0 ] . left ) * ( 100 - progress ) / 100 ;
else {
LONG barWidth ;
LONG pieceWidth ;
// TODO explain all this
2018-06-16 18:40:06 -05:00
// TODO this should really start the progressbar scrolling into view instead of already on screen when first set
2018-06-16 17:06:44 -05:00
rFill [ 1 ] = rFill [ 0 ] ; // save in case we need it
barWidth = rFill [ 0 ] . right - rFill [ 0 ] . left ;
pieceWidth = barWidth / indeterminateSegments ;
2018-06-16 18:05:36 -05:00
rFill [ 0 ] . left + = indeterminatePos % barWidth ;
2018-06-16 17:06:44 -05:00
if ( ( rFill [ 0 ] . left + pieceWidth ) > = rFill [ 0 ] . right ) {
// make this piece wrap back around
nFill + + ;
rFill [ 1 ] . right = rFill [ 1 ] . left + ( pieceWidth - ( rFill [ 0 ] . right - rFill [ 0 ] . left ) ) ;
} else
rFill [ 0 ] . right = rFill [ 0 ] . left + pieceWidth ;
}
for ( i = 0 ; i < nFill ; i + + )
2018-06-16 19:10:39 -05:00
if ( theme ! = NULL ) {
hr = DrawThemeBackground ( theme , s - > dc ,
PP_FILL , PBFS_NORMAL ,
& rFill [ i ] , NULL ) ;
if ( hr ! = S_OK ) {
logHRESULT ( L " DrawThemeBackground() fill " , hr ) ;
goto fail ;
}
} else
2018-06-16 17:06:44 -05:00
// TODO check errors
FillRect ( s - > dc , & rFill [ i ] , GetSysColorBrush ( sysColor ) ) ;
2018-06-16 19:10:39 -05:00
2018-06-16 12:57:46 -05:00
hr = S_OK ;
fail :
// TODO check errors
if ( theme ! = NULL )
CloseThemeData ( theme ) ;
return hr ;
}
2018-06-17 08:01:24 -05:00
static HRESULT drawButtonPart ( struct drawState * s )
{
uiTableData * data ;
WCHAR * wstr ;
bool enabled ;
HTHEME theme ;
RECT r ;
TEXTMETRICW tm ;
HRESULT hr ;
if ( s - > p - > buttonModelColumn = = - 1 )
return S_OK ;
2018-06-20 17:03:56 -05:00
data = cellValue ( s - > model , s - > iItem , s - > p - > buttonModelColumn ) ;
2018-06-17 08:01:24 -05:00
wstr = toUTF16 ( uiTableDataString ( data ) ) ;
uiFreeTableData ( data ) ;
switch ( s - > p - > buttonClickableModelColumn ) {
case uiTableModelColumnNeverEditable :
enabled = 0 ;
break ;
case uiTableModelColumnAlwaysEditable :
enabled = 1 ;
break ;
default :
2018-06-20 17:03:56 -05:00
data = cellValue ( s - > model , s - > iItem , s - > p - > checkboxEditableColumn ) ;
2018-06-17 08:01:24 -05:00
enabled = uiTableDataInt ( data ) ;
uiFreeTableData ( data ) ;
}
theme = OpenThemeData ( s - > t - > hwnd , L " button " ) ;
if ( GetTextMetricsW ( s - > dc , & tm ) = = 0 ) {
logLastError ( L " GetTextMetricsW() " ) ;
hr = E_FAIL ;
goto fail ;
}
2018-06-20 17:03:56 -05:00
r = s - > m - > subitemBounds ;
2018-06-17 08:01:24 -05:00
if ( theme ! = NULL ) {
int state ;
state = PBS_NORMAL ;
if ( ! enabled )
state = PBS_DISABLED ;
hr = DrawThemeBackground ( theme , s - > dc ,
BP_PUSHBUTTON , state ,
& r , NULL ) ;
if ( hr ! = S_OK ) {
logHRESULT ( L " DrawThemeBackground() " , hr ) ;
goto fail ;
}
// TODO DT_EDITCONTROL?
// TODO DT_PATH_ELLIPSIS DT_WORD_ELLIPSIS instead of DT_END_ELLIPSIS? a middle-ellipsis option would be ideal here
// TODO is there a theme property we can get instead of hardcoding these flags? if not, make these flags a macro
hr = DrawThemeText ( theme , s - > dc ,
BP_PUSHBUTTON , state ,
wstr , - 1 ,
DT_CENTER | DT_VCENTER | DT_END_ELLIPSIS | DT_SINGLELINE | DT_NOPREFIX , 0 ,
& r ) ;
if ( hr ! = S_OK ) {
logHRESULT ( L " DrawThemeText() " , hr ) ;
goto fail ;
}
} else {
UINT state ;
HBRUSH color , prevColor ;
int prevBkMode ;
// TODO check errors
// TODO explain why we're not doing this in the themed case (it has to do with extra transparent pixels)
InflateRect ( & r , - 1 , - 1 ) ;
state = DFCS_BUTTONPUSH ;
if ( ! enabled )
state | = DFCS_INACTIVE ;
if ( DrawFrameControl ( s - > dc , & r , DFC_BUTTON , state ) = = 0 ) {
logLastError ( L " DrawFrameControl() " ) ;
hr = E_FAIL ;
goto fail ;
}
color = GetSysColorBrush ( COLOR_BTNTEXT ) ;
// TODO check errors for these two
prevColor = ( HBRUSH ) SelectObject ( s - > dc , color ) ;
prevBkMode = SetBkMode ( s - > dc , TRANSPARENT ) ;
// TODO DT_EDITCONTROL?
// TODO DT_PATH_ELLIPSIS DT_WORD_ELLIPSIS instead of DT_END_ELLIPSIS? a middle-ellipsis option would be ideal here
if ( DrawTextW ( s - > dc , wstr , - 1 , & r , DT_CENTER | DT_VCENTER | DT_END_ELLIPSIS | DT_SINGLELINE | DT_NOPREFIX ) = = 0 ) {
logLastError ( L " DrawTextW() " ) ;
hr = E_FAIL ;
goto fail ;
}
// TODO check errors for these two
SetBkMode ( s - > dc , prevBkMode ) ;
SelectObject ( s - > dc , prevColor ) ;
}
hr = S_OK ;
fail :
// TODO check errors
if ( theme ! = NULL )
CloseThemeData ( theme ) ;
uiprivFree ( wstr ) ;
return hr ;
}
2018-06-14 20:31:45 -05:00
static HRESULT freeDrawState ( struct drawState * s )
{
HRESULT hr , hrret ;
hrret = S_OK ;
2018-06-20 17:03:56 -05:00
if ( s - > m ! = NULL ) {
uiprivFree ( s - > m ) ;
s - > m = NULL ;
}
2018-06-15 09:04:32 -05:00
if ( s - > freeTextBrush ) {
if ( DeleteObject ( s - > textBrush ) = = 0 ) {
2018-06-14 20:31:45 -05:00
logLastError ( L " DeleteObject() " ) ;
hrret = E_FAIL ;
// continue cleaning up anyway
}
2018-06-15 09:04:32 -05:00
s - > freeTextBrush = FALSE ;
2018-06-14 20:31:45 -05:00
}
2018-06-15 09:04:32 -05:00
if ( s - > freeBgBrush ) {
if ( DeleteObject ( s - > bgBrush ) = = 0 ) {
2018-06-14 20:31:45 -05:00
logLastError ( L " DeleteObject() " ) ;
hrret = E_FAIL ;
// continue cleaning up anyway
}
2018-06-15 09:04:32 -05:00
s - > freeBgBrush = FALSE ;
2018-06-14 20:31:45 -05:00
}
return hrret ;
}
2018-06-15 09:04:32 -05:00
static COLORREF blend ( COLORREF base , double r , double g , double b , double a )
{
double br , bg , bb ;
br = ( ( double ) GetRValue ( base ) ) / 255.0 ;
bg = ( ( double ) GetGValue ( base ) ) / 255.0 ;
bb = ( ( double ) GetBValue ( base ) ) / 255.0 ;
br = ( r * a ) + ( br * ( 1.0 - a ) ) ;
bg = ( g * a ) + ( bg * ( 1.0 - a ) ) ;
bb = ( b * a ) + ( bb * ( 1.0 - a ) ) ;
return RGB ( ( BYTE ) ( br * 255 ) ,
( BYTE ) ( bg * 255 ) ,
( BYTE ) ( bb * 255 ) ) ;
}
2018-06-14 20:31:45 -05:00
static HRESULT fillDrawState ( struct drawState * s , uiTable * t , NMLVCUSTOMDRAW * nm , uiprivTableColumnParams * p )
{
LRESULT state ;
HWND header ;
HRESULT hr ;
ZeroMemory ( s , sizeof ( struct drawState ) ) ;
s - > t = t ;
2018-06-20 17:03:56 -05:00
s - > model = t - > model ;
2018-06-14 20:31:45 -05:00
s - > p = p ;
s - > dc = nm - > nmcd . hdc ;
s - > iItem = nm - > nmcd . dwItemSpec ;
s - > iSubItem = nm - > iSubItem ;
2018-06-20 17:03:56 -05:00
hr = uiprivTableGetMetrics ( t , s - > iItem , s - > iSubItem , & ( s - > m ) ) ;
2018-06-14 20:31:45 -05:00
if ( hr ! = S_OK )
goto fail ;
2018-06-20 17:03:56 -05:00
if ( s - > m - > selected ) {
2018-06-14 20:31:45 -05:00
s - > bgColor = GetSysColor ( COLOR_HIGHLIGHT ) ;
s - > bgBrush = GetSysColorBrush ( COLOR_HIGHLIGHT ) ;
s - > textColor = GetSysColor ( COLOR_HIGHLIGHTTEXT ) ;
s - > textBrush = GetSysColorBrush ( COLOR_HIGHLIGHTTEXT ) ;
} else {
uiTableData * data ;
2018-06-15 09:04:32 -05:00
double r , g , b , a ;
2018-06-14 20:31:45 -05:00
s - > bgColor = GetSysColor ( COLOR_WINDOW ) ;
s - > bgBrush = GetSysColorBrush ( COLOR_WINDOW ) ;
if ( t - > backgroundColumn ! = - 1 ) {
2018-06-20 17:03:56 -05:00
data = cellValue ( s - > model , s - > iItem , t - > backgroundColumn ) ;
2018-06-14 20:31:45 -05:00
if ( data ! = NULL ) {
uiTableDataColor ( data , & r , & g , & b , & a ) ;
uiFreeTableData ( data ) ;
s - > bgColor = blend ( s - > bgColor , r , g , b , a ) ;
2018-06-15 09:04:32 -05:00
s - > bgBrush = CreateSolidBrush ( s - > bgColor ) ;
2018-06-14 20:31:45 -05:00
if ( s - > bgBrush = = NULL ) {
logLastError ( L " CreateSolidBrush() " ) ;
hr = E_FAIL ;
goto fail ;
}
s - > freeBgBrush = TRUE ;
}
}
s - > textColor = GetSysColor ( COLOR_WINDOWTEXT ) ;
s - > textBrush = GetSysColorBrush ( COLOR_WINDOWTEXT ) ;
if ( p - > textParams . ColorModelColumn ! = - 1 ) {
2018-06-20 17:03:56 -05:00
data = cellValue ( s - > model , s - > iItem , p - > textParams . ColorModelColumn ) ;
2018-06-14 20:31:45 -05:00
if ( data ! = NULL ) {
uiTableDataColor ( data , & r , & g , & b , & a ) ;
uiFreeTableData ( data ) ;
s - > textColor = blend ( s - > bgColor , r , g , b , a ) ;
s - > textBrush = CreateSolidBrush ( s - > textColor ) ;
if ( s - > textBrush = = NULL ) {
logLastError ( L " CreateSolidBrush() " ) ;
hr = E_FAIL ;
goto fail ;
}
s - > freeTextBrush = TRUE ;
}
}
}
return S_OK ;
fail :
// ignore the error; we need to return the one we got above
freeDrawState ( s ) ;
return hr ;
}
2018-06-15 09:04:32 -05:00
2018-06-21 22:47:24 -05:00
static HRESULT updateAndDrawFocusRects ( uiTable * t , HDC dc , int iItem , RECT * realTextBackground , RECT * focus , bool * first )
2018-06-20 20:11:15 -05:00
{
2018-06-21 22:47:24 -05:00
LRESULT state ;
2018-06-20 20:11:15 -05:00
if ( GetFocus ( ) ! = t - > hwnd )
return S_OK ;
2018-06-21 22:47:24 -05:00
// uItemState CDIS_FOCUS doesn't quite work right because of bugs in the Windows list view that causes spurious redraws without the flag while we hover over the focused item
// TODO only call this once
state = SendMessageW ( t - > hwnd , LVM_GETITEMSTATE , ( WPARAM ) iItem , ( LRESULT ) ( LVIS_FOCUSED ) ) ;
if ( ( state & LVIS_FOCUSED ) = = 0 )
return S_OK ;
2018-06-20 20:11:15 -05:00
2018-06-21 22:47:24 -05:00
if ( realTextBackground ! = NULL )
if ( * first ) {
* focus = * realTextBackground ;
2018-06-20 20:11:15 -05:00
first = false ;
2018-06-21 22:47:24 -05:00
return S_OK ;
} else if ( focus - > right = = realTextBackground - > left ) {
focus - > right = realTextBackground - > right ;
return S_OK ;
2018-06-20 20:11:15 -05:00
}
2018-06-21 22:47:24 -05:00
if ( DrawFocusRect ( dc , focus ) = = 0 ) {
logLastError ( L " DrawFocusRect() " ) ;
2018-06-20 20:11:15 -05:00
return E_FAIL ;
}
2018-06-21 22:47:24 -05:00
if ( realTextBackground ! = NULL )
* focus = * realTextBackground ;
2018-06-20 20:11:15 -05:00
return S_OK ;
}
2018-06-21 20:56:24 -05:00
// normally we would only draw stuff in subitem stages
// this broke when we tried drawing focus rects in postpaint; the drawing either kept getting wiped or overdrawn, mouse hovering had something to do with it, and none of the "solutions" to this or similar problems on the internet worked
// so now we do everything in the item prepaint stage
// TODO consider switching to full-on owner draw
// TODO only compute the background brushes once?
// TODO integrate the focus rect stuff above
// TODO do hr chaining like in tablemetrics.cpp
2018-06-15 09:04:32 -05:00
HRESULT uiprivTableHandleNM_CUSTOMDRAW ( uiTable * t , NMLVCUSTOMDRAW * nm , LRESULT * lResult )
{
struct drawState s ;
uiprivTableColumnParams * p ;
2018-06-21 20:56:24 -05:00
NMLVCUSTOMDRAW b ;
size_t i , n ;
2018-06-21 22:47:24 -05:00
RECT focus ;
bool focusFirst ;
2018-06-15 09:04:32 -05:00
HRESULT hr ;
switch ( nm - > nmcd . dwDrawStage ) {
case CDDS_PREPAINT :
* lResult = CDRF_NOTIFYITEMDRAW ;
return S_OK ;
case CDDS_ITEMPREPAINT :
break ;
default :
* lResult = CDRF_DODEFAULT ;
return S_OK ;
}
2018-06-21 20:56:24 -05:00
n = t - > columns - > size ( ) ;
b = * nm ;
2018-06-21 22:47:24 -05:00
focusFirst = false ;
2018-06-21 20:56:24 -05:00
for ( i = 0 ; i < n ; i + + ) {
b . iSubItem = i ;
p = ( * ( t - > columns ) ) [ i ] ;
hr = fillDrawState ( & s , t , & b , p ) ;
if ( hr ! = S_OK )
return hr ;
hr = drawBackgrounds ( & s ) ;
if ( hr ! = S_OK )
goto fail ;
hr = drawImagePart ( & s ) ;
if ( hr ! = S_OK )
goto fail ;
hr = drawCheckboxPart ( & s ) ;
if ( hr ! = S_OK )
goto fail ;
hr = drawTextPart ( & s ) ;
if ( hr ! = S_OK )
goto fail ;
hr = drawProgressBarPart ( & s ) ;
if ( hr ! = S_OK )
goto fail ;
hr = drawButtonPart ( & s ) ;
2018-06-21 22:47:24 -05:00
if ( hr ! = S_OK )
goto fail ;
hr = updateAndDrawFocusRects ( s . t , s . dc , nm - > nmcd . dwItemSpec , & ( s . m - > realTextBackground ) , & focus , & focusFirst ) ;
2018-06-21 20:56:24 -05:00
if ( hr ! = S_OK )
goto fail ;
hr = freeDrawState ( & s ) ;
if ( hr ! = S_OK ) // TODO really error out here?
return hr ;
}
2018-06-21 22:47:24 -05:00
// and draw the last focus rect
hr = updateAndDrawFocusRects ( t , nm - > nmcd . hdc , nm - > nmcd . dwItemSpec , NULL , & focus , & focusFirst ) ;
if ( hr ! = S_OK )
return hr ;
2018-06-15 09:04:32 -05:00
* lResult = CDRF_SKIPDEFAULT ;
return S_OK ;
fail :
// ignore error here
2018-06-21 20:56:24 -05:00
// TODO this is awkward cleanup placement for something that only really exists in a for loop
2018-06-15 09:04:32 -05:00
freeDrawState ( & s ) ;
return hr ;
}
2018-06-15 21:50:19 -05:00
// TODO run again when the DPI or the theme changes
// TODO properly clean things up here
// TODO properly destroy the old lists here too
HRESULT uiprivUpdateImageListSize ( uiTable * t )
{
HDC dc ;
int cxList , cyList ;
HTHEME theme ;
SIZE sizeCheck ;
HRESULT hr ;
dc = GetDC ( t - > hwnd ) ;
if ( dc = = NULL ) {
logLastError ( L " GetDC() " ) ;
return E_FAIL ;
}
cxList = GetSystemMetrics ( SM_CXSMICON ) ;
cyList = GetSystemMetrics ( SM_CYSMICON ) ;
sizeCheck . cx = cxList ;
sizeCheck . cy = cyList ;
theme = OpenThemeData ( t - > hwnd , L " button " ) ;
if ( theme ! = NULL ) {
hr = GetThemePartSize ( theme , dc ,
BP_CHECKBOX , CBS_UNCHECKEDNORMAL ,
NULL , TS_DRAW , & sizeCheck ) ;
if ( hr ! = S_OK ) {
logHRESULT ( L " GetThemePartSize() " , hr ) ;
return hr ; // TODO fall back?
}
// make sure these checkmarks fit
// unthemed checkmarks will by the code above be smaller than cxList/cyList here
if ( cxList < sizeCheck . cx )
cxList = sizeCheck . cx ;
if ( cyList < sizeCheck . cy )
cyList = sizeCheck . cy ;
2018-06-16 10:59:17 -05:00
hr = CloseThemeData ( theme ) ;
if ( hr ! = S_OK ) {
logHRESULT ( L " CloseThemeData() " , hr ) ;
return hr ;
}
2018-06-15 21:50:19 -05:00
}
// TODO handle errors
t - > imagelist = ImageList_Create ( cxList , cyList ,
ILC_COLOR32 ,
1 , 1 ) ;
2018-06-15 22:00:39 -05:00
if ( t - > imagelist = = NULL ) {
2018-06-15 21:50:19 -05:00
logLastError ( L " ImageList_Create() " ) ;
return E_FAIL ;
}
// TODO will this return NULL here because it's an initial state?
2018-06-15 22:00:39 -05:00
SendMessageW ( t - > hwnd , LVM_SETIMAGELIST , LVSIL_SMALL , ( LPARAM ) ( t - > imagelist ) ) ;
2018-06-15 21:50:19 -05:00
if ( ReleaseDC ( t - > hwnd , dc ) = = 0 ) {
logLastError ( L " ReleaseDC() " ) ;
return E_FAIL ;
}
return S_OK ;
}