bsnes/hiro/windows/widget/list-view.cpp
Tim Allen 0253db8685 Update to higan and icarus v095r17 release.
byuu says:

higan supports Event mapping again.

Further, icarus can now detect Event ROMs and MSU1 games.

Event ROMs must be named "program.rom", "slot-(1,2,3).rom" MSU1 games
must contain "msu1.rom"; and tracks must be named "track-#.pcm"

When importing the CC'92, PF'94 ROMs, the program.rom and
slot-(1,2,3).rom files must be concatenated. The DSP firmware can
optionally be separate, but I'd recommend you go ahead and merge it all
to one file. Especially since that common "higan DSP pack" floating
around on the web left out the DSP1 ROMs (only has DSP1B) for god knows
what reason.

There is no support for loading "game.sfc+game.msu+game-*.pcm", because
I'm not going to support trying to pull in all of those files through
importing. Games will have to be distributed as game folders to use
MSU1. The MSU1 icarus support is simply so your game folders won't
require an unstable manifest.bml file to be played. So once they're in
there, they are good for life.

Note: the Event sizes in icarus' SFC heuristics are wrong for appended
firmware. Change from 0xXX8000 to 0xXX2000 and it works fine. Will be
fixed in r18.

Added Sintendo's flickering fixes. The window one's a big help for
regular controls, but the ListView double buffering does nothing for me
on Windows 7 :( Fairly sure I know why, but too lazy to try and fix that
now.

Also fixes the mMenu thing.
2015-12-20 13:53:40 +11:00

441 lines
15 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 | LVS_EX_DOUBLEBUFFER, 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);
setBordered(state().bordered);
_setIcons();
_setSortable();
resizeColumns();
}
auto pListView::destruct() -> void {
if(imageList) { ImageList_Destroy(imageList); imageList = nullptr; }
DestroyWindow(hwnd);
}
auto pListView::append(sListViewHeader header) -> void {
}
auto pListView::append(sListViewItem item) -> void {
}
auto pListView::remove(sListViewHeader header) -> void {
LVCOLUMN lvColumn{LVCF_WIDTH};
while(ListView_GetColumn(hwnd, 0, &lvColumn)) {
ListView_DeleteColumn(hwnd, 0);
}
}
auto pListView::remove(sListViewItem item) -> void {
}
auto pListView::resizeColumns() -> void {
lock();
if(auto& header = state().header) {
vector<signed> widths;
signed minimumWidth = 0;
signed expandable = 0;
for(auto column : range(header->columnCount())) {
signed width = _width(column);
widths.append(width);
minimumWidth += width;
if(header->column(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(header->columnCount())) {
if(auto self = header->state.columns[column]->self()) {
signed width = widths[column];
if(self->state().expandable) width += expandWidth;
self->_width = width;
self->_setState();
}
}
}
unlock();
}
auto pListView::setAlignment(Alignment alignment) -> void {
}
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::setBordered(bool bordered) -> void {
//rendered via onCustomDraw
}
auto pListView::setForegroundColor(Color color) -> void {
}
auto pListView::setGeometry(Geometry geometry) -> void {
pWidget::setGeometry(geometry);
if(auto& header = state().header) {
for(auto& column : header->state.columns) {
if(column->state.expandable) return resizeColumns();
}
}
}
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);
}
}
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;
auto& header = state().header;
if(!header) break;
for(auto column : range(header->columnCount())) {
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 selected = state().items(row)->state.selected;
if(auto cell = self().item(row)->cell(column)) {
auto backgroundColor = cell->backgroundColor(true);
HBRUSH brush = CreateSolidBrush(
selected ? GetSysColor(COLOR_HIGHLIGHT)
: backgroundColor ? CreateRGB(backgroundColor)
: GetSysColor(COLOR_WINDOW)
);
FillRect(hdc, &rc, brush);
DeleteObject(brush);
if(cell->state.checkable) {
if(auto htheme = OpenThemeData(hwnd, L"BUTTON")) {
unsigned state = cell->state.checked ? CBS_CHECKEDNORMAL : CBS_UNCHECKEDNORMAL;
SIZE size;
GetThemePartSize(htheme, hdc, BP_CHECKBOX, state, nullptr, 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, nullptr);
CloseThemeData(htheme);
} else {
//Windows Classic
rc.left += 2;
RECT rd{rc.left, rc.top, rc.left + iconSize, rc.top + iconSize};
DrawFrameControl(hdc, &rd, DFC_BUTTON, DFCS_BUTTONCHECK | (cell->state.checked ? DFCS_CHECKED : 0));
}
rc.left += iconSize + 2;
} else {
rc.left += 2;
}
if(auto& image = cell->state.image) {
auto bitmap = CreateBitmap(image);
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, image.width(), image.height(), blend);
DeleteObject(bitmap);
rc.left += iconSize + 2;
}
if(auto text = cell->state.text) {
auto alignment = cell->alignment(true);
if(!alignment) alignment = {0.0, 0.5};
utf16_t wText(text);
SetBkMode(hdc, TRANSPARENT);
auto foregroundColor = cell->foregroundColor(true);
SetTextColor(hdc,
selected ? GetSysColor(COLOR_HIGHLIGHTTEXT)
: foregroundColor ? CreateRGB(foregroundColor)
: GetSysColor(COLOR_WINDOWTEXT)
);
auto style = DT_SINGLELINE | DT_NOPREFIX | DT_END_ELLIPSIS;
style |= alignment.horizontal() < 0.333 ? DT_LEFT : alignment.horizontal() > 0.666 ? DT_RIGHT : DT_CENTER;
style |= alignment.vertical() < 0.333 ? DT_TOP : alignment.vertical() > 0.666 ? DT_BOTTOM : DT_VCENTER;
rc.right -= 2;
auto font = pFont::create(cell->font(true));
SelectObject(hdc, font);
DrawText(hdc, wText, -1, &rc, style);
DeleteObject(font);
}
} else {
auto backgroundColor = state().backgroundColor;
HBRUSH brush = CreateSolidBrush(
selected ? GetSysColor(COLOR_HIGHLIGHT)
: backgroundColor ? CreateRGB(backgroundColor)
: GetSysColor(COLOR_WINDOW)
);
FillRect(hdc, &rc, brush);
DeleteObject(brush);
}
if(state().bordered) {
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;
}
}
return CDRF_SKIPDEFAULT;
}
auto pListView::onSort(LPARAM lparam) -> void {
auto nmlistview = (LPNMLISTVIEW)lparam;
if(auto& header = state().header) {
if(auto column = header->column(nmlistview->iSubItem)) {
if(column->sortable()) self().doSort(column);
}
}
}
auto pListView::onToggle(LPARAM lparam) -> void {
auto itemActivate = (LPNMITEMACTIVATE)lparam;
LVHITTESTINFO hitTestInfo{0};
hitTestInfo.pt = itemActivate->ptAction;
ListView_SubItemHitTest(hwnd, &hitTestInfo);
if(auto cell = self().item(hitTestInfo.iItem).cell(hitTestInfo.iSubItem)) {
if(cell->state.checkable) {
cell->state.checked = !cell->state.checked;
if(!locked()) self().doToggle(cell);
//todo: try to find a way to only repaint this cell instead of the entire control to reduce flickering
PostMessageOnce(_parentHandle(), AppMessage::ListView_doPaint, 0, (LPARAM)&reference);
}
}
}
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(auto item = self().item(_row)) {
if(auto cell = item->cell(_column)) {
if(cell->state.checkable) {
width += 16 + 2;
}
if(auto& image = cell->state.image) {
width += 16 + 2;
}
if(auto& text = cell->state.text) {
width += pFont::size(_font(_row, _column), text).width();
}
}
}
return width;
}
auto pListView::_columnWidth(unsigned _column) -> unsigned {
unsigned width = 12;
if(auto header = state().header) {
if(auto column = header->column(_column)) {
if(auto& image = column->state.image) {
width += 16 + 12; //yes; icon spacing in column headers is excessive
}
if(auto& text = column->state.text) {
width += pFont::size(self().font(true), text).width();
}
}
}
return width;
}
auto pListView::_font(unsigned _row, unsigned _column) -> Font {
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 {};
}
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, nullptr, LVSIL_SMALL);
if(imageList) ImageList_Destroy(imageList);
imageList = ImageList_Create(16, 16, ILC_COLOR32, 1, 0);
ListView_SetImageList(hwnd, imageList, LVSIL_SMALL);
if(auto& header = state().header) {
for(auto column : range(header->columnCount())) {
nall::image icon;
if(auto& image = header->state.columns[column]->state.image) {
icon.allocate(image.width(), image.height());
memory::copy(icon.data(), image.data(), icon.size());
icon.scale(16, 16);
} else {
icon.allocate(16, 16);
icon.fill(0x00ffffff);
}
auto bitmap = CreateBitmap(icon);
ImageList_Add(imageList, bitmap, nullptr);
DeleteObject(bitmap);
}
}
//empty icon used for ListViewItems (drawn manually via onCustomDraw)
nall::image icon;
icon.allocate(16, 16);
icon.fill(0x00ffffff);
auto bitmap = CreateBitmap(icon);
ImageList_Add(imageList, bitmap, nullptr);
DeleteObject(bitmap);
}
auto pListView::_setSortable() -> void {
bool sortable = false;
if(auto& header = state().header) {
for(auto& column : header->state.columns) {
if(column->sortable()) sortable = true;
}
}
//note: this won't change the visual style: WC_LISTVIEW caches this in CreateWindow
auto style = GetWindowLong(hwnd, GWL_STYLE);
!sortable ? style |= LVS_NOSORTHEADER : style &=~ LVS_NOSORTHEADER;
SetWindowLong(hwnd, GWL_STYLE, style);
}
auto pListView::_width(unsigned column) -> unsigned {
if(auto& header = state().header) {
if(auto width = header->state.columns[column]->width()) return width;
unsigned width = 1;
if(header->visible()) width = max(width, _columnWidth(column));
for(auto row : range(state().items)) {
width = max(width, _cellWidth(row, column));
}
return width;
}
return 1;
}
}
#endif