bsnes/hiro/windows/widget/list-view.cpp
Tim Allen ea02f1e36a Update to v094r31 release.
byuu says:

This WIP scores 448/920 tests passed.

Gave a shot at ROM prefetch that failed miserably (ranged from 409 to
494 tests passed. Nowhere near where it would be if it were implemented
correctly.)

Three remaining issues:
- ROM prefetch
- DMA timing
- timers (I suspect it's a 3-clock delay in starting, not a 3-clock into
  the future affair)

Probably only going to be able to get the timers working without heroic
amounts of effort.

MUL timing is fixed to use idle cycles.
STMIA is fixed to set sequential at the right moments.
DMA priority support is added, so DMA 0 can interrupt DMA 1 mid-transfer.

In other news ...

I'm calling gtk_widget_destroy on the GtkWindow now, so hopefully those
Window_configure issues go away.

I realize I was leaking Display* handles in the X-video driver while
I was looking at it, so I fixed those.

I added DT_NOPREFIX so the Windows ListView will show & characters
correctly now.
2015-06-25 19:52:32 +10:00

455 lines
14 KiB
C++

#if defined(Hiro_ListView)
namespace hiro {
static auto CALLBACK ListView_windowProc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) -> LRESULT {
if(auto object = (mObject*)GetWindowLongPtr(hwnd, GWLP_USERDATA)) {
if(auto listView = dynamic_cast<mListView*>(object)) {
if(auto self = listView->self()) {
if(!listView->enabled(true)) {
if(msg == WM_KEYDOWN || msg == WM_KEYUP || msg == WM_SYSKEYDOWN || msg == WM_SYSKEYUP) {
//WC_LISTVIEW responds to key messages even when its HWND is disabled
//the control should be inactive when disabled; so we intercept the messages here
return false;
}
}
return self->windowProc(hwnd, msg, wparam, lparam);
}
}
}
return DefWindowProc(hwnd, msg, wparam, lparam);
}
auto pListView::construct() -> void {
hwnd = CreateWindowEx(
WS_EX_CLIENTEDGE, WC_LISTVIEW, L"",
WS_CHILD | WS_TABSTOP | LVS_REPORT | LVS_SHOWSELALWAYS,
0, 0, 0, 0, _parentHandle(), nullptr, GetModuleHandle(0), 0
);
SetWindowLongPtr(hwnd, GWLP_USERDATA, (LONG_PTR)&reference);
windowProc = (WindowProc)GetWindowLongPtr(hwnd, GWLP_WNDPROC);
SetWindowLongPtr(hwnd, GWLP_WNDPROC, (LONG_PTR)&ListView_windowProc);
ListView_SetExtendedListViewStyle(hwnd, LVS_EX_FULLROWSELECT | LVS_EX_SUBITEMIMAGES);
pWidget::_setState();
setBackgroundColor(state().backgroundColor);
setBatchable(state().batchable);
setCheckable(state().checkable);
setGridVisible(state().gridVisible);
setHeaderVisible(state().headerVisible);
setSortable(state().sortable);
_setIcons();
resizeColumns();
}
auto pListView::destruct() -> void {
if(imageList) { ImageList_Destroy(imageList); imageList = 0; }
DestroyWindow(hwnd);
}
auto pListView::append(sListViewColumn column) -> void {
lock();
wchar_t text[] = L"";
LVCOLUMN lvColumn;
lvColumn.mask = LVCF_FMT | LVCF_TEXT | LVCF_SUBITEM;
lvColumn.fmt = LVCFMT_LEFT;
lvColumn.iSubItem = column->offset();
lvColumn.pszText = text;
ListView_InsertColumn(hwnd, column->offset(), &lvColumn);
if(auto self = column->self()) {
self->_setState();
}
resizeColumns();
unlock();
}
auto pListView::append(sListViewItem item) -> void {
lock();
wchar_t text[] = L"";
LVITEM lvItem;
lvItem.mask = LVIF_TEXT;
lvItem.iItem = item->offset();
lvItem.iSubItem = 0;
lvItem.pszText = text;
ListView_InsertItem(hwnd, &lvItem);
if(auto self = item->self()) {
self->setChecked(item->state.checked);
self->setSelected(item->state.selected);
}
for(auto& cell : item->state.cells) {
if(auto self = cell->self()) {
self->_setState();
}
}
unlock();
}
auto pListView::checkAll() -> void {
for(auto& item : state().items) {
item->self()->setChecked(true);
}
}
auto pListView::remove(sListViewColumn column) -> void {
lock();
ListView_DeleteColumn(hwnd, column->offset());
unlock();
}
auto pListView::remove(sListViewItem item) -> void {
lock();
ListView_DeleteItem(hwnd, item->offset());
unlock();
}
auto pListView::reset() -> void {
lock();
ListView_DeleteAllItems(hwnd);
LVCOLUMN lvColumn{LVCF_WIDTH};
while(ListView_GetColumn(hwnd, 0, &lvColumn)) ListView_DeleteColumn(hwnd, 0);
_setIcons(); //free icon resources
unlock();
}
auto pListView::resizeColumns() -> void {
lock();
vector<signed> widths;
signed minimumWidth = 0;
signed expandable = 0;
for(auto column : range(state().columns)) {
signed width = _width(column);
widths.append(width);
minimumWidth += width;
if(state().columns[column]->expandable()) expandable++;
}
signed maximumWidth = self().geometry().width() - 4;
SCROLLBARINFO sbInfo{sizeof(SCROLLBARINFO)};
if(GetScrollBarInfo(hwnd, OBJID_VSCROLL, &sbInfo)) {
if(!(sbInfo.rgstate[0] & STATE_SYSTEM_INVISIBLE)) {
maximumWidth -= sbInfo.rcScrollBar.right - sbInfo.rcScrollBar.left;
}
}
signed expandWidth = 0;
if(expandable && maximumWidth > minimumWidth) {
expandWidth = (maximumWidth - minimumWidth) / expandable;
}
for(auto column : range(state().columns)) {
if(auto self = state().columns[column]->self()) {
signed width = widths[column];
if(self->state().expandable) width += expandWidth;
self->_width = width;
self->_setState();
}
}
unlock();
}
auto pListView::selectAll() -> void {
lock();
ListView_SetItemState(hwnd, -1, LVIS_SELECTED, LVIS_SELECTED);
unlock();
}
auto pListView::setBackgroundColor(Color color) -> void {
if(!color) color = {255, 255, 255};
ListView_SetBkColor(hwnd, RGB(color.red(), color.green(), color.blue()));
}
auto pListView::setBatchable(bool batchable) -> void {
auto style = GetWindowLong(hwnd, GWL_STYLE);
!batchable ? style |= LVS_SINGLESEL : style &=~ LVS_SINGLESEL;
SetWindowLong(hwnd, GWL_STYLE, style);
}
auto pListView::setCheckable(bool checkable) -> void {
auto style = ListView_GetExtendedListViewStyle(hwnd);
checkable ? style |= LVS_EX_CHECKBOXES : style &=~ LVS_EX_CHECKBOXES;
ListView_SetExtendedListViewStyle(hwnd, style);
}
auto pListView::setForegroundColor(Color color) -> void {
}
auto pListView::setGridVisible(bool visible) -> void {
//rendered via onCustomDraw
}
auto pListView::setHeaderVisible(bool visible) -> void {
auto style = GetWindowLong(hwnd, GWL_STYLE);
!visible ? style |= LVS_NOCOLUMNHEADER : style &=~ LVS_NOCOLUMNHEADER;
SetWindowLong(hwnd, GWL_STYLE, style);
}
auto pListView::setSortable(bool sortable) -> void {
auto style = GetWindowLong(hwnd, GWL_STYLE);
!sortable ? style |= LVS_NOSORTHEADER : style &=~ LVS_NOSORTHEADER;
SetWindowLong(hwnd, GWL_STYLE, style);
}
auto pListView::uncheckAll() -> void {
for(auto& item : state().items) {
item->self()->setChecked(false);
}
}
auto pListView::unselectAll() -> void {
lock();
ListView_SetItemState(hwnd, -1, 0, LVIS_FOCUSED | LVIS_SELECTED);
unlock();
}
auto pListView::onActivate(LPARAM lparam) -> void {
auto nmlistview = (LPNMLISTVIEW)lparam;
if(ListView_GetSelectedCount(hwnd) == 0) return;
if(!locked()) {
//LVN_ITEMACTIVATE is not re-entrant until DispatchMessage() completes
//thus, we don't call self().doActivate() here
PostMessageOnce(_parentHandle(), AppMessage::ListView_onActivate, 0, (LPARAM)&reference);
}
}
auto pListView::onChange(LPARAM lparam) -> void {
auto nmlistview = (LPNMLISTVIEW)lparam;
if(!(nmlistview->uChanged & LVIF_STATE)) return;
bool modified = false;
for(auto& item : state().items) {
bool selected = ListView_GetItemState(hwnd, item->offset(), LVIS_SELECTED) & LVIS_SELECTED;
if(item->state.selected != selected) {
modified = true;
item->state.selected = selected;
}
}
if(modified && !locked()) {
//state event change messages are sent for every item
//so when doing a batch select/deselect; this can generate several messages
//we use a delayed AppMessage so that only one callback event is fired off
PostMessageOnce(_parentHandle(), AppMessage::ListView_onChange, 0, (LPARAM)&reference);
}
if(!locked()) {
unsigned row = nmlistview->iItem;
unsigned mask = nmlistview->uNewState & LVIS_STATEIMAGEMASK;
if(mask == 0x1000 || mask == 0x2000) {
bool checked = mask == 0x2000;
if(auto item = self().item(row)) {
if(checked != item->state.checked) { //WC_LISTVIEW sends this message twice
item->state.checked = checked;
self().doToggle(item);
}
}
}
}
}
auto pListView::onContext(LPARAM lparam) -> void {
auto nmitemactivate = (LPNMITEMACTIVATE)lparam;
return self().doContext();
}
auto pListView::onCustomDraw(LPARAM lparam) -> LRESULT {
auto lvcd = (LPNMLVCUSTOMDRAW)lparam;
switch(lvcd->nmcd.dwDrawStage) {
default: return CDRF_DODEFAULT;
case CDDS_PREPAINT: return CDRF_NOTIFYITEMDRAW;
case CDDS_ITEMPREPAINT: return CDRF_NOTIFYSUBITEMDRAW | CDRF_NOTIFYPOSTPAINT;
case CDDS_ITEMPREPAINT | CDDS_SUBITEM: return CDRF_SKIPDEFAULT;
case CDDS_ITEMPOSTPAINT: {
HDC hdc = lvcd->nmcd.hdc;
HDC hdcSource = CreateCompatibleDC(hdc);
unsigned row = lvcd->nmcd.dwItemSpec;
for(auto column : range(state().columns)) {
RECT rc, rcLabel;
ListView_GetSubItemRect(hwnd, row, column, LVIR_BOUNDS, &rc);
ListView_GetSubItemRect(hwnd, row, column, LVIR_LABEL, &rcLabel);
rc.right = rcLabel.right; //bounds of column 0 returns width of entire item
signed iconSize = rc.bottom - rc.top - 1;
bool checked = state().items(row)->state.checked;
bool selected = state().items(row)->state.selected;
HBRUSH brush = CreateSolidBrush(selected ? GetSysColor(COLOR_HIGHLIGHT) : CreateRGB(_backgroundColor(row, column)));
FillRect(hdc, &rc, brush);
DeleteObject(brush);
if(state().checkable && column == 0) {
if(auto htheme = OpenThemeData(hwnd, L"BUTTON")) {
unsigned state = checked ? CBS_CHECKEDNORMAL : CBS_UNCHECKEDNORMAL;
SIZE size;
GetThemePartSize(htheme, hdc, BP_CHECKBOX, state, NULL, TS_TRUE, &size);
signed center = max(0, (rc.bottom - rc.top - size.cy) / 2);
RECT rd{rc.left + center, rc.top + center, rc.left + center + size.cx, rc.top + center + size.cy};
DrawThemeBackground(htheme, hdc, BP_CHECKBOX, state, &rd, NULL);
CloseThemeData(htheme);
}
rc.left += iconSize + 2;
} else {
rc.left += 2;
}
auto cell = self().item(row)->cell(column);
if(!cell) continue;
if(auto icon = cell->state.icon) {
icon.scale(iconSize, iconSize);
icon.transform();
auto bitmap = CreateBitmap(icon);
SelectBitmap(hdcSource, bitmap);
BLENDFUNCTION blend{AC_SRC_OVER, 0, (BYTE)(selected ? 128 : 255), AC_SRC_ALPHA};
AlphaBlend(hdc, rc.left, rc.top, iconSize, iconSize, hdcSource, 0, 0, iconSize, iconSize, blend);
DeleteObject(bitmap);
rc.left += iconSize + 2;
}
if(auto text = cell->state.text) {
auto halign = state().columns(column)->state.horizontalAlignment;
auto valign = state().columns(column)->state.verticalAlignment;
utf16_t wText(text);
SetBkMode(hdc, TRANSPARENT);
SetTextColor(hdc, selected ? GetSysColor(COLOR_HIGHLIGHTTEXT) : CreateRGB(_foregroundColor(row, column)));
auto style = DT_SINGLELINE | DT_NOPREFIX | DT_END_ELLIPSIS;
style |= halign < 0.333 ? DT_LEFT : halign > 0.666 ? DT_RIGHT : DT_CENTER;
style |= valign < 0.333 ? DT_TOP : valign > 0.666 ? DT_BOTTOM : DT_VCENTER;
rc.right -= 2;
auto font = pFont::create(_font(row, column));
SelectObject(hdc, font);
DrawText(hdc, wText, -1, &rc, style);
DeleteObject(font);
}
if(state().gridVisible) {
ListView_GetSubItemRect(hwnd, row, column, LVIR_BOUNDS, &rc);
rc.top = rc.bottom - 1;
FillRect(hdc, &rc, (HBRUSH)GetStockObject(LTGRAY_BRUSH));
ListView_GetSubItemRect(hwnd, row, column, LVIR_LABEL, &rc);
rc.left = rc.right - 1;
FillRect(hdc, &rc, (HBRUSH)GetStockObject(LTGRAY_BRUSH));
}
}
DeleteDC(hdcSource);
return CDRF_SKIPDEFAULT;
}
}
}
auto pListView::onSort(LPARAM lparam) -> void {
auto nmlistview = (LPNMLISTVIEW)lparam;
self().doSort(self().column(nmlistview->iSubItem));
}
auto pListView::_backgroundColor(unsigned _row, unsigned _column) -> Color {
if(auto item = self().item(_row)) {
if(auto cell = item->cell(_column)) {
if(auto color = cell->backgroundColor()) return color;
}
if(auto color = item->backgroundColor()) return color;
}
if(auto column = self().column(_column)) {
if(auto color = column->backgroundColor()) return color;
}
if(auto color = self().backgroundColor()) return color;
if(state().columns.size() >= 2 && _row % 2) return {240, 240, 240};
return {255, 255, 255};
}
auto pListView::_cellWidth(unsigned _row, unsigned _column) -> unsigned {
unsigned width = 6;
if(state().checkable && _column == 0) width += 16 + 2;
if(auto item = self().item(_row)) {
if(auto cell = item->cell(_column)) {
if(auto& icon = cell->state.icon) {
width += 16 + 2;
}
if(auto& text = cell->state.text) {
width += Font::size(_font(_row, _column), text).width();
}
}
}
return width;
}
auto pListView::_columnWidth(unsigned _column) -> unsigned {
unsigned width = 12;
if(auto column = self().column(_column)) {
if(auto& icon = column->state.icon) {
width += 16 + 12; //yes; icon spacing in column headers is excessive
}
if(auto& text = column->state.text) {
width += Font::size(self().font(true), text).width();
}
}
return width;
}
auto pListView::_font(unsigned _row, unsigned _column) -> string {
if(auto item = self().item(_row)) {
if(auto cell = item->cell(_column)) {
if(auto font = cell->font()) return font;
}
if(auto font = item->font()) return font;
}
if(auto column = self().column(_column)) {
if(auto font = column->font()) return font;
}
if(auto font = self().font(true)) return font;
return Font::sans(8);
}
auto pListView::_foregroundColor(unsigned _row, unsigned _column) -> Color {
if(auto item = self().item(_row)) {
if(auto cell = item->cell(_column)) {
if(auto color = cell->foregroundColor()) return color;
}
if(auto color = item->foregroundColor()) return color;
}
if(auto column = self().column(_column)) {
if(auto color = column->foregroundColor()) return color;
}
if(auto color = self().foregroundColor()) return color;
return {0, 0, 0};
}
auto pListView::_setIcons() -> void {
ListView_SetImageList(hwnd, NULL, LVSIL_SMALL);
if(imageList) ImageList_Destroy(imageList);
imageList = ImageList_Create(16, 16, ILC_COLOR32, 1, 0);
ListView_SetImageList(hwnd, imageList, LVSIL_SMALL);
for(auto column : range(state().columns)) {
auto icon = state().columns(column)->state.icon;
if(icon) {
icon.scale(16, 16);
icon.transform();
} else {
icon.allocate(16, 16);
icon.fill(0x00ffffff);
}
auto bitmap = CreateBitmap(icon);
ImageList_Add(imageList, bitmap, NULL);
DeleteObject(bitmap);
}
//empty icon used for ListViewItems (drawn manually via onCustomDraw)
image icon;
icon.allocate(16, 16);
icon.fill(0x00ffffff);
auto bitmap = CreateBitmap(icon);
ImageList_Add(imageList, bitmap, NULL);
DeleteObject(bitmap);
}
auto pListView::_width(unsigned column) -> unsigned {
if(auto width = state().columns[column]->width()) return width;
unsigned width = 1;
if(state().headerVisible) {
width = max(width, _columnWidth(column));
}
for(auto row : range(state().items)) {
width = max(width, _cellWidth(row, column));
}
return width;
}
}
#endif