165 lines
4.5 KiB
C
165 lines
4.5 KiB
C
|
// 4 december 2014
|
||
|
|
||
|
// TODO find a better place for these (metrics.h?)
|
||
|
static LONG textHeight(struct table *t, HDC dc, BOOL select)
|
||
|
{
|
||
|
BOOL release;
|
||
|
HFONT prevfont, newfont;
|
||
|
TEXTMETRICW tm;
|
||
|
|
||
|
release = FALSE;
|
||
|
if (dc == NULL) {
|
||
|
dc = GetDC(t->hwnd);
|
||
|
if (dc == NULL)
|
||
|
panic("error getting Table DC for rowHeight()");
|
||
|
release = TRUE;
|
||
|
}
|
||
|
if (select)
|
||
|
prevfont = selectFont(t, dc, &newfont);
|
||
|
if (GetTextMetricsW(dc, &tm) == 0)
|
||
|
panic("error getting text metrics for rowHeight()");
|
||
|
if (select)
|
||
|
deselectFont(dc, prevfont, newfont);
|
||
|
if (release)
|
||
|
if (ReleaseDC(t->hwnd, dc) == 0)
|
||
|
panic("error releasing Table DC for rowHeight()");
|
||
|
return tm.tmHeight;
|
||
|
}
|
||
|
|
||
|
#define tableImageWidth() GetSystemMetrics(SM_CXSMICON)
|
||
|
#define tableImageHeight() GetSystemMetrics(SM_CYSMICON)
|
||
|
|
||
|
// TODO omit column types that are not present?
|
||
|
static LONG rowHeight(struct table *t, HDC dc, BOOL select)
|
||
|
{
|
||
|
LONG height;
|
||
|
LONG tmHeight;
|
||
|
|
||
|
height = tableImageHeight(); // start with this to avoid two function calls
|
||
|
tmHeight = textHeight(t, dc, select);
|
||
|
if (height < tmHeight)
|
||
|
height = tmHeight;
|
||
|
if (height < t->checkboxHeight)
|
||
|
height = t->checkboxHeight;
|
||
|
return height;
|
||
|
}
|
||
|
|
||
|
#define rowht(t) rowHeight(t, NULL, TRUE)
|
||
|
|
||
|
struct rowcol {
|
||
|
intptr_t row;
|
||
|
intptr_t column;
|
||
|
};
|
||
|
|
||
|
static struct rowcol clientCoordToRowColumn(struct table *t, POINT pt)
|
||
|
{
|
||
|
RECT r;
|
||
|
struct rowcol rc;
|
||
|
intptr_t i;
|
||
|
|
||
|
if (GetClientRect(t->hwnd, &r) == 0)
|
||
|
panic("error getting Table client rect in clientCoordToRowColumn()");
|
||
|
r.top += t->headerHeight;
|
||
|
if (PtInRect(&r, pt) == 0)
|
||
|
goto outside;
|
||
|
|
||
|
// the row is easy
|
||
|
pt.y -= t->headerHeight;
|
||
|
rc.row = (pt.y / rowht(t)) + t->vscrollpos;
|
||
|
if (rc.row >= t->count)
|
||
|
goto outside;
|
||
|
|
||
|
// the column... not so much
|
||
|
// we scroll p.x, then subtract column widths until we cross the left edge of the control
|
||
|
pt.x += t->hscrollpos;
|
||
|
rc.column = 0;
|
||
|
for (i = 0; i < t->nColumns; i++) {
|
||
|
pt.x -= columnWidth(t, i);
|
||
|
// use <, not <=, here:
|
||
|
// assume r.left and t->hscrollpos == 0;
|
||
|
// given the first column is 100 wide,
|
||
|
// pt.x == 0 (first pixel of col 0) -> p.x - 100 == -100 < 0 -> break
|
||
|
// pt.x == 99 (last pixel of col 0) -> p.x - 100 == -1 < 0 -> break
|
||
|
// pt.x == 100 (first pixel of col 1) -> p.x - 100 == 0 >= 0 -> next column
|
||
|
if (pt.x < r.left)
|
||
|
break;
|
||
|
rc.column++;
|
||
|
}
|
||
|
if (rc.column >= t->nColumns)
|
||
|
goto outside;
|
||
|
|
||
|
return rc;
|
||
|
|
||
|
outside:
|
||
|
rc.row = -1;
|
||
|
rc.column = -1;
|
||
|
return rc;
|
||
|
}
|
||
|
|
||
|
// same as client coordinates, but stored in a lParam (like the various mouse messages provide)
|
||
|
static struct rowcol lParamToRowColumn(struct table *t, LPARAM lParam)
|
||
|
{
|
||
|
POINT pt;
|
||
|
|
||
|
pt.x = GET_X_LPARAM(lParam);
|
||
|
pt.y = GET_Y_LPARAM(lParam);
|
||
|
return clientCoordToRowColumn(t, pt);
|
||
|
}
|
||
|
|
||
|
// returns TRUE if the row is visible (even partially visible) and thus has a rectangle in the client area; FALSE otherwise
|
||
|
static BOOL rowColumnToClientRect(struct table *t, struct rowcol rc, RECT *r)
|
||
|
{
|
||
|
RECT client;
|
||
|
RECT out; // don't change r if we return FALSE
|
||
|
LONG height;
|
||
|
intptr_t xpos;
|
||
|
intptr_t i;
|
||
|
|
||
|
if (rc.row < t->vscrollpos)
|
||
|
return FALSE;
|
||
|
rc.row -= t->vscrollpos; // align with client.top
|
||
|
|
||
|
if (GetClientRect(t->hwnd, &client) == 0)
|
||
|
panic("error getting Table client rect in rowColumnToClientRect()");
|
||
|
client.top += t->headerHeight;
|
||
|
|
||
|
height = rowht(t);
|
||
|
out.top = client.top + (rc.row * height);
|
||
|
if (out.top >= client.bottom) // >= because RECT.bottom is the first pixel outside the rectangle
|
||
|
return FALSE;
|
||
|
out.bottom = out.top + height;
|
||
|
|
||
|
// and again the columns are the hard part
|
||
|
// so we start with client.left - t->hscrollpos, then keep adding widths until we get to the column we want
|
||
|
xpos = client.left - t->hscrollpos;
|
||
|
for (i = 0; i < rc.column; i++)
|
||
|
xpos += columnWidth(t, i);
|
||
|
// did we stray too far to the right? if so it's not visible
|
||
|
if (xpos >= client.right) // >= because RECT.right is the first pixel outside the rectangle
|
||
|
return FALSE;
|
||
|
out.left = xpos;
|
||
|
out.right = xpos + columnWidth(t, rc.column);
|
||
|
// and is this too far to the left?
|
||
|
if (out.right < client.left) // < because RECT.left is the first pixel inside the rect
|
||
|
return FALSE;
|
||
|
|
||
|
*r = out;
|
||
|
return TRUE;
|
||
|
}
|
||
|
|
||
|
// TODO idealCoordToRowColumn/rowColumnToIdealCoord?
|
||
|
|
||
|
static void toCellContentRect(struct table *t, RECT *r, LRESULT xoff, intptr_t width, intptr_t height)
|
||
|
{
|
||
|
if (xoff == 0)
|
||
|
xoff = SendMessageW(t->header, HDM_GETBITMAPMARGIN, 0, 0);
|
||
|
r->left += xoff;
|
||
|
if (width != 0)
|
||
|
r->right = r->left + width;
|
||
|
if (height != 0)
|
||
|
// TODO vertical center
|
||
|
r->bottom = r->top + height;
|
||
|
}
|
||
|
|
||
|
#define toCheckboxRect(t, r, xoff) toCellContentRect(t, r, xoff, t->checkboxWidth, t->checkboxHeight)
|