mirror of
https://github.com/bsnes-emu/bsnes.git
synced 2025-02-24 07:02:27 +01:00
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.
455 lines
14 KiB
C++
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
|