2014-12-13 12:56:31 -06:00
// 13 december 2014
// damn winsock
static void doselect ( struct table * t , intptr_t row , intptr_t column )
{
2014-12-14 09:22:15 -06:00
RECT r , client ;
intptr_t oldrow ;
2014-12-14 16:10:08 -06:00
LONG width , height ;
2014-12-14 10:09:59 -06:00
struct rowcol rc ;
BOOL dovscroll ;
2014-12-14 16:10:08 -06:00
intptr_t i ;
intptr_t xpos ;
LONG clientWidth ;
2014-12-14 09:22:15 -06:00
2014-12-16 10:47:52 -06:00
// check existing selection to see if it's valid
if ( t - > selectedRow = = - 1 & & t - > selectedColumn ! = - 1 )
2014-12-16 10:50:53 -06:00
panic ( " sanity check failure: old Table selection invalid (row == -1, column != -1) " ) ;
2014-12-16 10:47:52 -06:00
if ( t - > selectedRow ! = - 1 & & t - > selectedColumn = = - 1 )
2014-12-16 10:50:53 -06:00
panic ( " sanity check failure: old Table selection invalid (row != -1, column == -1) " ) ;
2014-12-16 10:47:52 -06:00
if ( t - > selectedRow > = t - > count )
panic ( " sanity check failure: old Table selection invalid (row out of range) " ) ;
if ( t - > selectedColumn > = t - > nColumns )
2014-12-16 10:50:53 -06:00
panic ( " sanity check failure: old Table selection invalid (column out of range) " ) ;
2014-12-16 10:47:52 -06:00
2014-12-14 09:22:15 -06:00
oldrow = t - > selectedRow ;
2014-12-13 12:56:31 -06:00
t - > selectedRow = row ;
t - > selectedColumn = column ;
2014-12-14 09:22:15 -06:00
2014-12-16 10:47:52 -06:00
// check new selection to see if it's valid
if ( t - > selectedRow = = - 1 & & t - > selectedColumn ! = - 1 )
2014-12-16 10:50:53 -06:00
panic ( " sanity check failure: new Table selection invalid (row == -1, column != -1) " ) ;
2014-12-16 10:47:52 -06:00
if ( t - > selectedRow ! = - 1 & & t - > selectedColumn = = - 1 )
2014-12-16 10:50:53 -06:00
panic ( " sanity check failure: new Table selection invalid (row != -1, column == -1) " ) ;
2014-12-16 10:47:52 -06:00
if ( t - > selectedRow > = t - > count )
panic ( " sanity check failure: new Table selection invalid (row out of range) " ) ;
if ( t - > selectedColumn > = t - > nColumns )
2014-12-16 10:50:53 -06:00
panic ( " sanity check failure: new Table selection invalid (column out of range) " ) ;
2014-12-16 10:47:52 -06:00
2014-12-16 19:19:06 -06:00
// do this even if we don't scroll before; noScroll depends on it
2014-12-14 09:22:15 -06:00
if ( GetClientRect ( t - > hwnd , & client ) = = 0 )
panic ( " error getting Table client rect in doselect() " ) ;
client . top + = t - > headerHeight ;
height = rowht ( t ) ;
2014-12-14 10:09:59 -06:00
2014-12-16 19:19:06 -06:00
// only scroll if we selected something
if ( t - > selectedRow = = - 1 | | t - > selectedColumn = = - 1 )
goto noScroll ;
2014-12-14 10:09:59 -06:00
// first vertically scroll to the new row to make it fully visible (or as visible as possible)
if ( t - > selectedRow < t - > vscrollpos )
vscrollto ( t , t - > selectedRow ) ;
else {
rc . row = t - > selectedRow ;
rc . column = t - > selectedColumn ;
// first assume entirely outside the client area
dovscroll = TRUE ;
if ( rowColumnToClientRect ( t , rc , & r ) )
// partially outside the client area?
if ( r . bottom < = client . bottom ) // <= here since we are comparing bottoms (which are the first pixels outside the rectangle)
dovscroll = FALSE ;
if ( dovscroll )
vscrollto ( t , t - > selectedRow - t - > vpagesize + 1 ) ; // + 1 because apparently just t->selectedRow - t->vpagesize results in no scrolling (t->selectedRow - t->vpagesize == t->vscrollpos)...
}
2014-12-14 16:10:08 -06:00
// now see if the cell we want is to the left of offscreen, in which case scroll to its x-position
xpos = 0 ;
for ( i = 0 ; i < t - > selectedColumn ; i + + )
xpos + = columnWidth ( t , i ) ;
if ( xpos < t - > hscrollpos )
hscrollto ( t , xpos ) ;
2014-12-14 16:24:30 -06:00
else {
2014-12-14 16:10:08 -06:00
// if the full cell is not visible, scroll to the right just enough to make it fully visible (or as visible as possible)
width = columnWidth ( t , t - > selectedColumn ) ;
clientWidth = client . right - client . left ;
2014-12-14 16:24:30 -06:00
if ( xpos + width > t - > hscrollpos + clientWidth ) // > because both sides deal with the first pixel outside
2014-12-14 16:10:08 -06:00
// if the column is too wide, then just make it occupy the whole visible area (left-aligned)
2014-12-14 16:29:32 -06:00
if ( width > clientWidth ) // TODO >= ?
2014-12-14 16:10:08 -06:00
hscrollto ( t , xpos ) ;
else
2014-12-14 17:06:08 -06:00
// TODO don't use t->hpagesize here? depends if other code uses it
hscrollto ( t , ( xpos + width ) - t - > hpagesize ) ;
2014-12-14 16:10:08 -06:00
}
2014-12-14 10:09:59 -06:00
2014-12-16 17:40:58 -06:00
noScroll :
2014-12-14 10:09:59 -06:00
// now redraw the old and new /rows/
// we do this after scrolling so the rectangles to be invalidated make sense
2014-12-14 09:22:15 -06:00
r . left = client . left ;
r . right = client . right ;
if ( oldrow ! = - 1 & & oldrow > = t - > vscrollpos ) {
r . top = client . top + ( ( oldrow - t - > vscrollpos ) * height ) ;
r . bottom = r . top + height ;
if ( InvalidateRect ( t - > hwnd , & r , TRUE ) = = 0 )
panic ( " error queueing previously selected row for redraw in doselect() " ) ;
}
// t->selectedRow must be visible by this point; we scrolled to it
if ( t - > selectedRow ! = - 1 & & t - > selectedRow ! = oldrow ) {
r . top = client . top + ( ( t - > selectedRow - t - > vscrollpos ) * height ) ;
r . bottom = r . top + height ;
if ( InvalidateRect ( t - > hwnd , & r , TRUE ) = = 0 )
panic ( " error queueing newly selected row for redraw in doselect() " ) ;
}
2015-02-17 11:52:50 -06:00
2015-02-17 11:56:46 -06:00
// TODO what about deselect/defocus?
2015-02-17 11:52:50 -06:00
// TODO notify on the old row too?
NotifyWinEvent ( EVENT_OBJECT_SELECTION , t - > hwnd , OBJID_CLIENT , t - > selectedRow ) ;
// TODO send EVENT_OBJECT_STATECHANGED too?
2015-02-17 11:56:46 -06:00
// TODO send EVENT_OBJECT_FOCUS
2015-02-19 21:33:44 -06:00
// TODO before or after NotifyWinEvent()? (see what other things I'm doing)
notify ( t , tableNotificationSelectionChanged , t - > selectedRow , t - > selectedColumn , 0 ) ;
2014-12-13 12:56:31 -06:00
}
2014-12-22 19:15:10 -06:00
// TODO make this needless
HANDLER ( checkboxMouseDownHandler ) ;
2014-12-13 12:56:31 -06:00
// TODO which WM_xBUTTONDOWNs?
HANDLER ( mouseDownSelectHandler )
{
struct rowcol rc ;
2015-01-07 15:24:17 -06:00
// TODO separate this from here
// TODO other mouse buttons?
// don't check SetFocus()'s error (http://stackoverflow.com/questions/24073695/winapi-can-setfocus-return-null-without-an-error-because-thats-what-im-see)
2015-02-17 11:59:28 -06:00
// TODO NotifyWinEvent() here?
2015-01-07 15:24:17 -06:00
SetFocus ( t - > hwnd ) ;
2014-12-13 12:56:31 -06:00
rc = lParamToRowColumn ( t , lParam ) ;
// don't check if lParamToRowColumn() returned row -1 or column -1; we want deselection behavior
doselect ( t , rc . row , rc . column ) ;
2014-12-22 19:15:10 -06:00
// TODO separate this from here
checkboxMouseDownHandler ( t , uMsg , wParam , lParam , lResult ) ;
2014-12-13 12:56:31 -06:00
* lResult = 0 ;
return TRUE ;
}
2014-12-15 14:06:07 -06:00
/*
2014-12-16 01:57:31 -06:00
the routine below is intended to simulate the comctl32 . dll listview keyboard navigation rules , at least as far as vertical navigation is concerned .
2014-12-16 01:58:31 -06:00
horizontal scrolling is different because unlike the comctl32 listview , we say that a single column in each row has the keyboard focus , so left and right navigate between columns here , instead of scrolling left / right by pixels .
2014-12-16 01:57:31 -06:00
TODO provide an override for scrolling by pixels ?
TODO any other keyboard shortcuts ?
2014-12-16 10:28:08 -06:00
TODO browser keys
TODO media navigation keys
TODO XBUTTON1 / 2 ?
TODO clear keys ?
2014-12-16 01:57:31 -06:00
2014-12-15 14:06:07 -06:00
keyboard selection behaviors of the windows 7 listview :
with 100 items ( 0 - 99 ) , the window currently shows items 30 through 47 as well as having item 48 partially visible
- item 30 :
- page up - > item 13
- page down - > item 47
- item 31 :
2014-12-16 01:57:31 -06:00
- page up - > item 30
- page down - > item 47
2014-12-15 14:06:07 -06:00
- item 42 :
- page up - > item 30
- page down - > item 47
- item 46 :
2014-12-16 01:57:31 -06:00
- page up - > item 30
- page down - > item 47
2014-12-15 14:06:07 -06:00
- item 47 :
- page up : - > item 30
- page down : - > item 64
2014-12-15 19:03:46 -06:00
when nothing is selected :
- down selects item 0 regardless of scroll
- up selects nothing regardless of scroll
2014-12-15 22:48:09 -06:00
- page down selects the last fully visible item depending on scroll
- so with the above configuration :
- item 0 - > item 17
- item 30 - > item 47
- item 80 - > item 97
- page up selects item 0 regardless of scroll
2014-12-16 01:57:31 -06:00
- home selects item 0 regardless of scroll
- end selects the last item regardless of scroll
for left and right we will simulate up and down , respectively ( so right selects row 0 column 0 ) ; remember that you can ' t have either row or column be - 1 but not both
2014-12-17 13:58:39 -06:00
TODO what happens if page up and page down are pressed with an item selected and the scroll in a different position ?
2014-12-15 14:06:07 -06:00
*/
2014-12-16 10:28:08 -06:00
HANDLER ( keyDownSelectHandler )
{
intptr_t row ;
intptr_t column ;
if ( t - > count = = 0 | | t - > nColumns = = 0 ) // no items to select
return FALSE ;
row = t - > selectedRow ;
column = t - > selectedColumn ;
switch ( wParam ) {
case VK_UP :
if ( row = = - 1 )
return FALSE ;
row - - ;
if ( row < 0 )
row = 0 ;
break ;
case VK_DOWN :
if ( row = = - 1 ) {
row = 0 ;
column = 0 ;
} else {
row + + ;
2014-12-16 10:47:52 -06:00
if ( row > = t - > count )
2014-12-16 10:28:08 -06:00
row = t - > count - 1 ;
}
break ;
case VK_LEFT :
if ( column = = - 1 )
return FALSE ;
column - - ;
if ( column < 0 )
column = 0 ;
break ;
case VK_RIGHT :
if ( column = = - 1 ) {
row = 0 ;
column = 0 ;
} else {
column + + ;
2014-12-16 10:47:52 -06:00
if ( column > = t - > nColumns )
2014-12-16 10:28:08 -06:00
column = t - > nColumns - 1 ;
}
break ;
case VK_HOME :
row = 0 ;
if ( column = = - 1 )
column = 0 ;
break ;
case VK_END :
row = t - > count - 1 ;
if ( column = = - 1 )
column = 0 ;
break ;
case VK_PRIOR :
if ( row = = - 1 ) {
row = 0 ;
column = 0 ;
} else {
row = t - > vscrollpos ;
if ( row = = t - > selectedRow )
// TODO investigate why the - 1 is needed here and below
// TODO if this is a misunderstanding of how t->vpagesize works, figure out what happens if there is no partially visible row, and what is supposed to happen
row - = t - > vpagesize - 1 ;
if ( row < 0 )
row = 0 ;
}
break ;
case VK_NEXT :
if ( row = = - 1 ) {
2014-12-17 13:58:39 -06:00
row = t - > vscrollpos + t - > vpagesize - 1 ;
// TODO ensusre this is the case with the real list view
if ( row > = t - > count )
row = t - > count - 1 ;
2014-12-16 10:28:08 -06:00
column = 0 ;
} else {
row = t - > vscrollpos + t - > vpagesize - 1 ;
if ( row = = t - > selectedRow )
row + = t - > vpagesize - 1 ;
2014-12-16 10:47:52 -06:00
if ( row > = t - > count )
2014-12-16 10:28:08 -06:00
row = t - > count - 1 ;
}
break ;
default :
return FALSE ;
}
doselect ( t , row , column ) ;
* lResult = 0 ;
return TRUE ;
}