2018-06-17 10:48:39 -05:00
// 17 june 2018
# include "uipriv_windows.hpp"
# include "table.hpp"
2018-06-21 20:56:24 -05:00
// TODOs
// - clicking on the same item restarts editing instead of cancels it
2018-06-20 09:39:27 -05:00
// this is not how the real list view positions and sizes the edit control, but this is a) close enough b) a lot easier to follow c) something I can actually get working d) something I'm slightly more comfortable including in libui
2018-06-20 17:47:55 -05:00
static HRESULT resizeEdit ( uiTable * t , WCHAR * wstr , int iItem , int iSubItem )
2018-06-20 09:39:27 -05:00
{
2018-06-20 17:47:55 -05:00
uiprivTableMetrics * m ;
RECT r ;
2018-06-20 09:39:27 -05:00
HDC dc ;
HFONT prevFont ;
TEXTMETRICW tm ;
SIZE textSize ;
2018-06-20 17:56:03 -05:00
RECT editRect , clientRect ;
2018-06-20 17:47:55 -05:00
HRESULT hr ;
hr = uiprivTableGetMetrics ( t , iItem , iSubItem , & m ) ;
if ( hr ! = S_OK )
return hr ;
r = m - > realTextRect ;
uiprivFree ( m ) ;
2018-06-20 09:39:27 -05:00
// TODO check errors for all these
dc = GetDC ( t - > hwnd ) ; // use the list view DC since we're using its coordinate space
prevFont = ( HFONT ) SelectObject ( dc , hMessageFont ) ;
GetTextMetricsW ( dc , & tm ) ;
GetTextExtentPoint32W ( dc , wstr , wcslen ( wstr ) , & textSize ) ;
SelectObject ( dc , prevFont ) ;
ReleaseDC ( t - > hwnd , dc ) ;
SendMessageW ( t - > edit , EM_GETRECT , 0 , ( LPARAM ) ( & editRect ) ) ;
2018-06-20 17:47:55 -05:00
r . left - = editRect . left ;
2018-06-20 09:39:27 -05:00
// find the top of the text
2018-06-20 17:47:55 -05:00
r . top + = ( ( r . bottom - r . top ) - tm . tmHeight ) / 2 ;
2018-06-20 09:39:27 -05:00
// and move THAT by the right offset
2018-06-20 17:47:55 -05:00
r . top - = editRect . top ;
r . right = r . left + textSize . cx ;
2018-06-20 09:39:27 -05:00
// the real listview does this to add some extra space at the end
// TODO this still isn't enough space
2018-06-20 17:47:55 -05:00
r . right + = 4 * GetSystemMetrics ( SM_CXEDGE ) + GetSystemMetrics ( SM_CYEDGE ) ;
2018-06-20 09:39:27 -05:00
// and make the bottom equally positioned to the top
2018-06-20 17:47:55 -05:00
r . bottom = r . top + editRect . top + tm . tmHeight + editRect . top ;
2018-06-20 09:39:27 -05:00
2018-06-20 17:56:03 -05:00
// make sure the edit box doesn't stretch outside the listview
// the list view just does this, which is dumb for when the list view wouldn't be visible at all, but given that it doesn't scroll the edit into view either...
// TODO check errors
GetClientRect ( t - > hwnd , & clientRect ) ;
IntersectRect ( & r , & r , & clientRect ) ;
2018-06-20 09:39:27 -05:00
// TODO check error or use the right function
SetWindowPos ( t - > edit , NULL ,
2018-06-20 17:47:55 -05:00
r . left , r . top ,
r . right - r . left , r . bottom - r . top ,
2018-06-20 09:39:27 -05:00
SWP_NOACTIVATE | SWP_NOOWNERZORDER | SWP_NOZORDER ) ;
return S_OK ;
}
2018-06-19 22:07:24 -05:00
// the real list view intercepts these keys to control editing
static LRESULT CALLBACK editSubProc ( HWND hwnd , UINT uMsg , WPARAM wParam , LPARAM lParam , UINT_PTR uIDSubclass , DWORD_PTR dwRefData )
{
uiTable * t = ( uiTable * ) dwRefData ;
HRESULT hr ;
switch ( uMsg ) {
case WM_KEYDOWN :
switch ( wParam ) {
// TODO handle VK_TAB and VK_SHIFT+VK_TAB
case VK_RETURN :
hr = uiprivTableFinishEditingText ( t ) ;
if ( hr ! = S_OK ) {
// TODO
}
return 0 ; // yes, the real list view just returns here
case VK_ESCAPE :
hr = uiprivTableAbortEditingText ( t ) ;
if ( hr ! = S_OK ) {
// TODO
}
return 0 ;
}
break ;
// the real list view also forces these flags
case WM_GETDLGCODE :
return DLGC_HASSETSEL | DLGC_WANTALLKEYS ;
case WM_NCDESTROY :
if ( RemoveWindowSubclass ( hwnd , editSubProc , uIDSubclass ) = = FALSE )
logLastError ( L " RemoveWindowSubclass() " ) ;
// fall through
}
return DefSubclassProc ( hwnd , uMsg , wParam , lParam ) ;
}
2018-06-17 16:56:45 -05:00
static HRESULT openEditControl ( uiTable * t , int iItem , int iSubItem , uiprivTableColumnParams * p )
{
uiTableData * data ;
WCHAR * wstr ;
HRESULT hr ;
2018-06-19 22:07:24 -05:00
// the real list view accepts changes to the existing item when editing a new item
hr = uiprivTableFinishEditingText ( t ) ;
if ( hr ! = S_OK )
return hr ;
2018-06-17 16:56:45 -05:00
// the real list view creates the edit control with the string
data = ( * ( t - > model - > mh - > CellValue ) ) ( t - > model - > mh , t - > model , iItem , p - > textModelColumn ) ;
wstr = toUTF16 ( uiTableDataString ( data ) ) ;
uiFreeTableData ( data ) ;
// TODO copy WS_EX_RTLREADING
t - > edit = CreateWindowExW ( 0 ,
L " EDIT " , wstr ,
// these styles are what the normal listview edit uses
WS_CHILD | WS_CLIPSIBLINGS | WS_BORDER | ES_AUTOHSCROLL ,
// as is this size
0 , 0 , 16384 , 16384 ,
// and this control ID
t - > hwnd , ( HMENU ) 1 , hInstance , NULL ) ;
if ( t - > edit = = NULL ) {
logLastError ( L " CreateWindowExW() " ) ;
uiprivFree ( wstr ) ;
return E_FAIL ;
}
2018-06-17 20:22:57 -05:00
SendMessageW ( t - > edit , WM_SETFONT , ( WPARAM ) hMessageFont , ( LPARAM ) TRUE ) ;
2018-06-20 09:39:27 -05:00
// TODO check errors
SetWindowSubclass ( t - > edit , editSubProc , 0 , ( DWORD_PTR ) t ) ;
2018-06-17 16:56:45 -05:00
2018-06-20 17:47:55 -05:00
hr = resizeEdit ( t , wstr , iItem , iSubItem ) ;
2018-06-20 09:39:27 -05:00
if ( hr ! = S_OK )
// TODO proper cleanup
return hr ;
// TODO check errors on these two, if any
SetFocus ( t - > edit ) ;
ShowWindow ( t - > edit , SW_SHOW ) ;
SendMessageW ( t - > edit , EM_SETSEL , 0 , ( LPARAM ) ( - 1 ) ) ;
2018-06-17 16:56:45 -05:00
2018-06-18 22:51:58 -05:00
uiprivFree ( wstr ) ;
2018-06-19 22:07:24 -05:00
t - > editedItem = iItem ;
t - > editedSubitem = iSubItem ;
return S_OK ;
}
2018-06-20 17:47:55 -05:00
HRESULT uiprivTableResizeWhileEditing ( uiTable * t )
{
WCHAR * text ;
HRESULT hr ;
if ( t - > edit = = NULL )
return S_OK ;
text = windowText ( t - > edit ) ;
hr = resizeEdit ( t , text , t - > editedItem , t - > editedSubitem ) ;
uiprivFree ( text ) ;
return hr ;
}
2018-06-19 22:07:24 -05:00
HRESULT uiprivTableFinishEditingText ( uiTable * t )
{
2018-06-20 18:31:21 -05:00
uiprivTableColumnParams * p ;
uiTableData * data ;
char * text ;
2018-06-19 22:07:24 -05:00
if ( t - > edit = = NULL )
return S_OK ;
2018-06-20 18:31:21 -05:00
text = uiWindowsWindowText ( t - > edit ) ;
data = uiNewTableDataString ( text ) ;
uiFreeText ( text ) ;
p = ( * ( t - > columns ) ) [ t - > editedSubitem ] ;
( * ( t - > model - > mh - > SetCellValue ) ) ( t - > model - > mh , t - > model , t - > editedItem , p - > textModelColumn , data ) ;
uiFreeTableData ( data ) ;
// always refresh the value in case the model rejected it
if ( SendMessageW ( t - > hwnd , LVM_UPDATE , ( WPARAM ) ( t - > editedItem ) , 0 ) = = ( LRESULT ) ( - 1 ) ) {
logLastError ( L " LVM_UPDATE " ) ;
return E_FAIL ;
}
2018-06-19 22:07:24 -05:00
return uiprivTableAbortEditingText ( t ) ;
}
HRESULT uiprivTableAbortEditingText ( uiTable * t )
{
2018-06-20 18:31:21 -05:00
HWND edit ;
2018-06-19 22:07:24 -05:00
if ( t - > edit = = NULL )
return S_OK ;
2018-06-20 18:31:21 -05:00
// set t->edit to NULL now so we don't trigger commits on focus killed
edit = t - > edit ;
t - > edit = NULL ;
if ( DestroyWindow ( edit ) = = 0 ) {
2018-06-19 22:07:24 -05:00
logLastError ( L " DestroyWindow() " ) ;
return E_FAIL ;
}
2018-06-17 16:56:45 -05:00
return S_OK ;
}
2018-06-17 10:48:39 -05:00
HRESULT uiprivTableHandleNM_CLICK ( uiTable * t , NMITEMACTIVATE * nm , LRESULT * lResult )
{
LVHITTESTINFO ht ;
uiprivTableColumnParams * p ;
int modelColumn , editableColumn ;
2018-06-17 14:06:45 -05:00
bool text , checkbox ;
2018-06-17 10:48:39 -05:00
uiTableData * data ;
int checked , editable ;
2018-06-17 16:56:45 -05:00
HRESULT hr ;
2018-06-17 10:48:39 -05:00
ZeroMemory ( & ht , sizeof ( LVHITTESTINFO ) ) ;
ht . pt = nm - > ptAction ;
if ( SendMessageW ( t - > hwnd , LVM_SUBITEMHITTEST , 0 , ( LPARAM ) ( & ht ) ) = = ( LRESULT ) ( - 1 ) )
goto done ;
modelColumn = - 1 ;
editableColumn = - 1 ;
2018-06-17 14:06:45 -05:00
text = false ;
2018-06-17 10:48:39 -05:00
checkbox = false ;
p = ( * ( t - > columns ) ) [ ht . iSubItem ] ;
2018-06-17 14:06:45 -05:00
if ( p - > textModelColumn ! = - 1 ) {
modelColumn = p - > textModelColumn ;
editableColumn = p - > textEditableColumn ;
text = true ;
} else if ( p - > checkboxModelColumn ! = - 1 ) {
2018-06-17 10:48:39 -05:00
modelColumn = p - > checkboxModelColumn ;
editableColumn = p - > checkboxEditableColumn ;
checkbox = true ;
} else if ( p - > buttonModelColumn ! = - 1 ) {
modelColumn = p - > buttonModelColumn ;
editableColumn = p - > buttonClickableModelColumn ;
}
if ( modelColumn = = - 1 )
goto done ;
2018-06-17 14:06:45 -05:00
if ( text & & t - > inDoubleClickTimer )
// don't even ask for info if it's too soon to edit text
goto done ;
2018-06-17 10:48:39 -05:00
switch ( editableColumn ) {
case uiTableModelColumnNeverEditable :
goto done ;
case uiTableModelColumnAlwaysEditable :
break ;
default :
data = ( * ( t - > model - > mh - > CellValue ) ) ( t - > model - > mh , t - > model , ht . iItem , editableColumn ) ;
editable = uiTableDataInt ( data ) ;
uiFreeTableData ( data ) ;
if ( ! editable )
goto done ;
}
2018-06-17 14:06:45 -05:00
if ( text ) {
2018-06-17 16:56:45 -05:00
hr = openEditControl ( t , ht . iItem , ht . iSubItem , p ) ;
if ( hr ! = S_OK )
return hr ;
2018-06-17 14:06:45 -05:00
} else if ( checkbox ) {
2018-06-17 10:48:39 -05:00
if ( ( ht . flags & LVHT_ONITEMICON ) = = 0 )
goto done ;
data = ( * ( t - > model - > mh - > CellValue ) ) ( t - > model - > mh , t - > model , ht . iItem , modelColumn ) ;
checked = uiTableDataInt ( data ) ;
uiFreeTableData ( data ) ;
data = uiNewTableDataInt ( ! checked ) ;
( * ( t - > model - > mh - > SetCellValue ) ) ( t - > model - > mh , t - > model , ht . iItem , modelColumn , data ) ;
uiFreeTableData ( data ) ;
} else
( * ( t - > model - > mh - > SetCellValue ) ) ( t - > model - > mh , t - > model , ht . iItem , modelColumn , NULL ) ;
// always refresh the value in case the model rejected it
if ( SendMessageW ( t - > hwnd , LVM_UPDATE , ( WPARAM ) ( ht . iItem ) , 0 ) = = ( LRESULT ) ( - 1 ) ) {
logLastError ( L " LVM_UPDATE " ) ;
return E_FAIL ;
}
done :
* lResult = 0 ;
return S_OK ;
}