You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
14445 lines
447 KiB
14445 lines
447 KiB
#include "ctlspriv.h"
|
|
#include "listview.h"
|
|
#include "image.h"
|
|
#include <mlang.h>
|
|
#include <inetreg.h>
|
|
#include "uxthemep.h"
|
|
|
|
#define __IOleControl_INTERFACE_DEFINED__ // There is a conflict with the IOleControl's def of CONTROLINFO
|
|
#include "shlobj.h"
|
|
|
|
#ifdef FULL_DEBUG
|
|
#define LISTVIEW_VFX_DEFAULT TRUE
|
|
#else
|
|
#define LISTVIEW_VFX_DEFAULT FALSE
|
|
#endif
|
|
|
|
int LV_GetNewColWidth(LV* plv, int iFirst, int iLast);
|
|
void ListView_RecalcTileSize(LV* plv);
|
|
int ListView_ComputeCXItemSize(LV* plv);
|
|
|
|
#define IE_SETTINGS TEXT("Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\Advanced")
|
|
#define USE_DBL_CLICK_TIMER TEXT("UseDoubleClickTimer")
|
|
|
|
int g_bUseDblClickTimer;
|
|
|
|
#define LVMP_WINDOWPOSCHANGED (WM_USER + 1)
|
|
HRESULT WINAPI UninitializeFlatSB(HWND hwnd);
|
|
|
|
// the insert mark is 6 pixels wide
|
|
#define INSERTMARKSIZE 6
|
|
#define GROUPHEADER_PADDING 6
|
|
#define GRADIENT_WIDTH 300
|
|
|
|
#define COLORISLIGHT(clr) ((5*GetGValue((clr)) + 2*GetRValue((clr)) + GetBValue((clr))) > 8*128)
|
|
|
|
void ListView_HandleMouse(LV* plv, BOOL fDoubleClick, int x, int y, UINT keyFlags, BOOL bMouseWheel);
|
|
|
|
/// function table setup
|
|
const PFNLISTVIEW_DRAWITEM pfnListView_DrawItem[5] =
|
|
{
|
|
ListView_IDrawItem,
|
|
ListView_RDrawItem,
|
|
ListView_IDrawItem,
|
|
ListView_LDrawItem,
|
|
ListView_TDrawItem,
|
|
};
|
|
|
|
void ListView_HandleStateIconClick(LV* plv, int iItem);
|
|
|
|
DWORD ListView_IApproximateViewRect(LV* ,int, int, int);
|
|
DWORD ListView_RApproximateViewRect(LV* ,int, int, int);
|
|
DWORD ListView_LApproximateViewRect(LV* ,int, int, int);
|
|
|
|
const PFNLISTVIEW_APPROXIMATEVIEWRECT pfnListView_ApproximateViewRect[5] =
|
|
{
|
|
ListView_IApproximateViewRect,
|
|
ListView_RApproximateViewRect,
|
|
ListView_IApproximateViewRect,
|
|
ListView_LApproximateViewRect,
|
|
ListView_IApproximateViewRect,
|
|
};
|
|
|
|
const PFNLISTVIEW_UPDATESCROLLBARS pfnListView_UpdateScrollBars[5] =
|
|
{
|
|
ListView_IUpdateScrollBars,
|
|
ListView_RUpdateScrollBars,
|
|
ListView_IUpdateScrollBars,
|
|
ListView_LUpdateScrollBars,
|
|
ListView_IUpdateScrollBars,
|
|
};
|
|
|
|
const PFNLISTVIEW_ITEMHITTEST pfnListView_ItemHitTest[5] =
|
|
{
|
|
ListView_IItemHitTest,
|
|
ListView_RItemHitTest,
|
|
ListView_SItemHitTest,
|
|
ListView_LItemHitTest,
|
|
ListView_TItemHitTest,
|
|
};
|
|
|
|
const PFNLISTVIEW_ONSCROLL pfnListView_OnScroll[5] =
|
|
{
|
|
ListView_IOnScroll,
|
|
ListView_ROnScroll,
|
|
ListView_IOnScroll,
|
|
ListView_LOnScroll,
|
|
ListView_IOnScroll,
|
|
};
|
|
|
|
const PFNLISTVIEW_SCROLL2 pfnListView_Scroll2[5] =
|
|
{
|
|
ListView_IScroll2,
|
|
ListView_RScroll2,
|
|
ListView_IScroll2,
|
|
ListView_LScroll2,
|
|
ListView_IScroll2,
|
|
};
|
|
|
|
const PFNLISTVIEW_GETSCROLLUNITSPERLINE pfnListView_GetScrollUnitsPerLine[5] =
|
|
{
|
|
ListView_IGetScrollUnitsPerLine,
|
|
ListView_RGetScrollUnitsPerLine,
|
|
ListView_IGetScrollUnitsPerLine,
|
|
ListView_LGetScrollUnitsPerLine,
|
|
ListView_IGetScrollUnitsPerLine,
|
|
};
|
|
|
|
const PFNLISTVIEW_RECOMPUTELABELSIZE pfnListView_RecomputeLabelSize[5] =
|
|
{
|
|
ListView_IRecomputeLabelSize,
|
|
ListView_IRecomputeLabelSize,
|
|
ListView_IRecomputeLabelSize,
|
|
ListView_IRecomputeLabelSize,
|
|
ListView_TRecomputeLabelSize,
|
|
};
|
|
|
|
BOOL ListView_NULLRecomputeEx(LV* plv, HDPA hdpaSort, int iFrom, BOOL fForce)
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
const PFNLISTVIEW_RECOMPUTEEX pfnListView_RecomputeEx[5] =
|
|
{
|
|
ListView_IRecomputeEx,
|
|
ListView_RRecomputeEx,
|
|
ListView_IRecomputeEx,
|
|
ListView_NULLRecomputeEx,
|
|
ListView_IRecomputeEx,
|
|
};
|
|
|
|
#ifdef DEBUG_PAINT
|
|
void ListView_DebugDrawInvalidRegion(LV* plv, RECT* prc, HRGN hrgn)
|
|
{
|
|
HDC hdc;
|
|
HBRUSH hbrush;
|
|
int bkMode;
|
|
static int s_iclr;
|
|
static COLORREF s_aclr[] =
|
|
{
|
|
RGB(255, 0, 0), RGB(0, 255, 0),
|
|
RGB(255, 255, 0), RGB(0, 255, 255),
|
|
};
|
|
|
|
s_iclr = (s_iclr + 1) % ARRAYSIZE(s_aclr);
|
|
hdc = GetDC(plv->ci.hwnd);
|
|
hbrush = CreateHatchBrush(HS_DIAGCROSS, s_aclr[s_iclr]);
|
|
bkMode = SetBkMode(hdc, TRANSPARENT);
|
|
if (prc)
|
|
{
|
|
FillRect(hdc, prc, hbrush);
|
|
}
|
|
else if (hrgn)
|
|
{
|
|
FillRgn(hdc, hrgn, hbrush);
|
|
}
|
|
DeleteObject((HGDIOBJ)hbrush);
|
|
SetBkMode(hdc, bkMode);
|
|
ReleaseDC(plv->ci.hwnd, hdc);
|
|
Sleep(120);
|
|
}
|
|
|
|
BOOL ListView_DebugDrawInvalidItem(LV* plv, int iItem)
|
|
{
|
|
RECT rcLabel;
|
|
RECT rcIcon;
|
|
ListView_GetRects(plv, iItem, QUERY_DEFAULT,
|
|
&rcIcon, &rcLabel, NULL, NULL);
|
|
ListView_DebugDrawInvalidRegion(plv, &rcIcon, NULL);
|
|
ListView_DebugDrawInvalidRegion(plv, &rcLabel, NULL);
|
|
return TRUE;
|
|
|
|
}
|
|
|
|
|
|
void ListView_DebugDisplayClipRegion(LV* plv, RECT* prc, HRGN hrgn)
|
|
{
|
|
HDC hdc = GetDC(plv->ci.hwnd);
|
|
if (prc)
|
|
{
|
|
InvertRect(hdc, prc);
|
|
}
|
|
else if (hrgn)
|
|
{
|
|
InvertRgn(hdc, hrgn);
|
|
}
|
|
|
|
Sleep(120);
|
|
|
|
if (prc)
|
|
{
|
|
InvertRect(hdc, prc);
|
|
}
|
|
else if (hrgn)
|
|
{
|
|
InvertRgn(hdc, hrgn);
|
|
}
|
|
|
|
ReleaseDC(plv->ci.hwnd, hdc);
|
|
}
|
|
#else
|
|
#define ListView_DebugDrawInvalidItem(plv, iItem) FALSE
|
|
#endif
|
|
|
|
// redefine to trace at most calls to ListView_SendChange
|
|
#define DM_LVSENDCHANGE 0
|
|
|
|
|
|
// penwin.h is messed up; define local stuff for now
|
|
#define HN_BEGINDIALOG 40 // Lens/EditText/garbage detection dialog is about
|
|
// to come up on this hedit/bedit
|
|
#define HN_ENDDIALOG 41 // Lens/EditText/garbage detection dialog has
|
|
// just been destroyed
|
|
|
|
//---------------------------------------------------------
|
|
// no way am I gonna make TWO function calls where I can do FOUR comparisons!
|
|
//
|
|
#define RECTS_IN_SIZE(sz, r2) (!RECTS_NOT_IN_SIZE(sz, r2))
|
|
|
|
#define RECTS_NOT_IN_SIZE(sz, r2) (\
|
|
((sz).cx <= (r2).left) ||\
|
|
(0 >= (r2).right) ||\
|
|
((sz).cy <= (r2).top) ||\
|
|
(0 >= (r2).bottom))
|
|
|
|
//---------------------------------------------------------
|
|
|
|
|
|
void ListView_OnUpdate(LV* plv, int i);
|
|
void ListView_OnDestroy(LV* plv);
|
|
BOOL ListView_ValidateScrollParams(LV* plv, int * dx, int *dy);
|
|
void ListView_ButtonSelect(LV* plv, int iItem, UINT keyFlags, BOOL bSelected);
|
|
void ListView_DeselectAll(LV* plv, int iDontDeselect);
|
|
void ListView_LRInvalidateBelow(LV* plv, int i, int fSmoothScroll);
|
|
void ListView_IInvalidateBelow(LV* plv, int i);
|
|
void ListView_InvalidateFoldedItem(LV* plv, int iItem, BOOL fSelectionOnly, UINT fRedraw);
|
|
void ListView_ReleaseBkImage(LV *plv);
|
|
void ListView_RecalcRegion(LV *plv, BOOL fForce, BOOL fRedraw);
|
|
|
|
BOOL g_fSlowMachine = -1;
|
|
|
|
BOOL ListView_Init(HINSTANCE hinst)
|
|
{
|
|
WNDCLASS wc;
|
|
|
|
wc.lpfnWndProc = ListView_WndProc;
|
|
wc.hCursor = LoadCursor(NULL, IDC_ARROW);
|
|
wc.hIcon = NULL;
|
|
wc.lpszMenuName = NULL;
|
|
wc.hInstance = hinst;
|
|
wc.lpszClassName = c_szListViewClass;
|
|
wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1); // NULL;
|
|
wc.style = CS_DBLCLKS | CS_GLOBALCLASS;
|
|
wc.cbWndExtra = sizeof(LV*);
|
|
wc.cbClsExtra = 0;
|
|
|
|
return (RegisterClass(&wc) || (GetLastError() == ERROR_CLASS_ALREADY_EXISTS));
|
|
}
|
|
|
|
// Cancel tracking tooltips which are activated by item focus via keyboard
|
|
void ListView_CancelTipTrack(LV* plv)
|
|
{
|
|
// Make sure in tracking mode
|
|
if (plv->hwndToolTips)
|
|
{
|
|
// Cancel any pending timer
|
|
KillTimer(plv->ci.hwnd, IDT_TRACKINGTIP);
|
|
|
|
if (ListView_IsKbdTipTracking(plv))
|
|
{
|
|
TOOLINFO ti = {0};
|
|
|
|
// Mark as tracking nothing
|
|
plv->iTracking = LVKTT_NOTRACK;
|
|
|
|
// Reset tooltip to non-tracking
|
|
ti.cbSize = sizeof(TOOLINFO);
|
|
ti.hwnd = plv->ci.hwnd;
|
|
|
|
SendMessage(plv->hwndToolTips, TTM_GETTOOLINFO, 0, (LPARAM)&ti);
|
|
|
|
SendMessage(plv->hwndToolTips, TTM_TRACKACTIVATE, FALSE, (LPARAM)&ti);
|
|
|
|
// Switch tooltip window back to non-tracking (manual) mode
|
|
ti.uFlags &= ~TTF_TRACK;
|
|
SendMessage(plv->hwndToolTips, TTM_SETTOOLINFO, 0, (LPARAM)&ti);
|
|
}
|
|
}
|
|
}
|
|
|
|
BOOL ListView_GetRegIASetting(BOOL *pb)
|
|
{
|
|
HKEY hkey;
|
|
BOOL bRet = FALSE;
|
|
BOOL bValue = TRUE;
|
|
|
|
if (RegOpenKeyEx(HKEY_CURRENT_USER, IE_SETTINGS, 0, KEY_READ, &hkey) == ERROR_SUCCESS)
|
|
{
|
|
DWORD dwType;
|
|
DWORD dwValue;
|
|
DWORD cbValue = sizeof(DWORD);
|
|
|
|
if (RegQueryValueEx(hkey, (LPTSTR)USE_DBL_CLICK_TIMER, 0, &dwType, (LPBYTE)&dwValue, &cbValue) == ERROR_SUCCESS)
|
|
{
|
|
bValue = (BOOL)dwValue;
|
|
bRet = TRUE;
|
|
}
|
|
RegCloseKey(hkey);
|
|
}
|
|
|
|
*pb = bValue;
|
|
return bRet;
|
|
}
|
|
|
|
|
|
BOOL ListView_NotifyCacheHint(LV* plv, int iFrom, int iTo)
|
|
{
|
|
NM_CACHEHINT nm;
|
|
|
|
if (iFrom <= iTo)
|
|
{
|
|
nm.iFrom = iFrom;
|
|
nm.iTo = iTo;
|
|
return !(BOOL)CCSendNotify(&plv->ci, LVN_ODCACHEHINT, &nm.hdr);
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
void ListView_LazyCreateObjects(LV *plv, int iMin, int iMax)
|
|
{
|
|
for (; iMin < iMax; iMin++)
|
|
NotifyWinEvent(EVENT_OBJECT_CREATE, plv->ci.hwnd, OBJID_CLIENT, 1 + iMin);
|
|
}
|
|
|
|
//
|
|
// Owner-data causes MSAA lots of grief, because there is no way to tell
|
|
// MSAA "I just created 25 million items". You have to tell it one at a
|
|
// time. Instead of sending out 25 million "add item" notifications, we
|
|
// just send them out as they scroll into view.
|
|
//
|
|
// plv->iMSAAMin and plv->iMSAAMax are the range of items we most
|
|
// recently told MSAA about. MSAAMax is *exclusive*, just like RECTs.
|
|
// It makes the math easier.
|
|
//
|
|
// We use iMSAAMin and iMSAAMax to avoid sending blatantly redundant
|
|
// notifications, which would other happen very frequently.
|
|
//
|
|
void ListView_LazyCreateWinEvents(LV *plv, int iFrom, int iTo)
|
|
{
|
|
int iMin = iFrom;
|
|
int iMax = iTo+1; // Convert from [From,To] to [Min,Max)
|
|
|
|
//
|
|
// If the incoming range is entirely contained within the existing
|
|
// range, then there is nothing to do. This happens a lot.
|
|
//
|
|
if (iMin >= plv->iMSAAMin && iMax <= plv->iMSAAMax)
|
|
return;
|
|
|
|
//
|
|
// If the incoming range is adjacent to or overlaps the low end
|
|
// of the existing range... (This happens when scrolling backwards.)
|
|
//
|
|
if (iMin <= plv->iMSAAMin && iMax >= plv->iMSAAMin)
|
|
{
|
|
// Notify the low end.
|
|
ListView_LazyCreateObjects(plv, iMin, plv->iMSAAMin);
|
|
|
|
// Extend the list of things we've notified.
|
|
plv->iMSAAMin = iMin;
|
|
|
|
// Remove it from the things left to be notified.
|
|
iMin = plv->iMSAAMax;
|
|
}
|
|
|
|
//
|
|
// Now do the same thing to the top end.
|
|
// (This happens when scrolling forwards.)
|
|
//
|
|
if (iMax >= plv->iMSAAMax && iMin <= plv->iMSAAMax)
|
|
{
|
|
// Notify the top end.
|
|
ListView_LazyCreateObjects(plv, plv->iMSAAMax, iMax);
|
|
|
|
// Extend the list of things we've notified.
|
|
plv->iMSAAMax = iMax;
|
|
|
|
// Remove it from the things left to be notified.
|
|
iMax = plv->iMSAAMin;
|
|
}
|
|
|
|
//
|
|
// If there are still things to be notified, then it means that the
|
|
// incoming range isn't contiguous with the previous range, so throw
|
|
// away the old range and just set it to the current range.
|
|
// (This happens when you grab the scrollbar and jump to a completely
|
|
// unrelated part of the listview.)
|
|
//
|
|
if (iMin < iMax)
|
|
{
|
|
plv->iMSAAMin = iMin;
|
|
plv->iMSAAMax = iMax;
|
|
ListView_LazyCreateObjects(plv, iMin, iMax);
|
|
}
|
|
|
|
}
|
|
|
|
LRESULT ListView_RequestFindItem(LV* plv, CONST LV_FINDINFO* plvfi, int iStart)
|
|
{
|
|
NM_FINDITEM nm;
|
|
|
|
nm.lvfi = *plvfi;
|
|
nm.iStart = iStart;
|
|
return CCSendNotify(&plv->ci, LVN_ODFINDITEM, &nm.hdr);
|
|
}
|
|
|
|
BOOL ListView_SendChange(LV* plv, int i, int iSubItem, int code, UINT oldState, UINT newState,
|
|
UINT changed, LPARAM lParam)
|
|
{
|
|
NM_LISTVIEW nm;
|
|
|
|
nm.iItem = i;
|
|
nm.iSubItem = iSubItem;
|
|
nm.uNewState = newState;
|
|
nm.uOldState = oldState;
|
|
nm.uChanged = changed;
|
|
nm.ptAction.x = 0;
|
|
nm.ptAction.y = 0;
|
|
nm.lParam = lParam;
|
|
|
|
return !CCSendNotify(&plv->ci, code, &nm.hdr);
|
|
}
|
|
|
|
void ListView_SendODChangeAndInvalidate(LV* plv, int iFrom, int iTo, UINT oldState,
|
|
UINT newState)
|
|
{
|
|
NM_ODSTATECHANGE nm;
|
|
|
|
nm.iFrom = iFrom;
|
|
nm.iTo = iTo;
|
|
nm.uNewState = newState;
|
|
nm.uOldState = oldState;
|
|
|
|
CCSendNotify(&plv->ci, LVN_ODSTATECHANGED, &nm.hdr);
|
|
|
|
// Tell accessibility, "Selection changed in a complex way"
|
|
NotifyWinEvent(EVENT_OBJECT_SELECTIONWITHIN, plv->ci.hwnd, OBJID_CLIENT, CHILDID_SELF);
|
|
|
|
// considerable speed increase less than 100 to do this method
|
|
// while over 100, the other method works faster
|
|
if ((iTo - iFrom) > 100)
|
|
{
|
|
InvalidateRect(plv->ci.hwnd, NULL, FALSE);
|
|
}
|
|
else
|
|
{
|
|
while (iFrom <= iTo)
|
|
{
|
|
ListView_InvalidateItem(plv, iFrom, TRUE, RDW_INVALIDATE);
|
|
iFrom++;
|
|
}
|
|
}
|
|
}
|
|
|
|
//
|
|
// This function autoarranges or snaps to grid based on the style and ExStyle
|
|
//
|
|
// Note: AutoArrange overrides the snap-to-grid style.
|
|
//
|
|
void ListView_ArrangeOrSnapToGrid(LV *plv)
|
|
{
|
|
if (plv->ci.style & LVS_AUTOARRANGE)
|
|
ListView_OnArrange(plv, LVA_DEFAULT);
|
|
else if (plv->exStyle & LVS_EX_SNAPTOGRID)
|
|
ListView_OnArrange(plv, LVA_SNAPTOGRID);
|
|
}
|
|
|
|
BOOL ListView_Notify(LV* plv, int i, int iSubItem, int code)
|
|
{
|
|
NM_LISTVIEW nm;
|
|
nm.iItem = i;
|
|
nm.iSubItem = iSubItem;
|
|
nm.uNewState = nm.uOldState = 0;
|
|
nm.uChanged = 0;
|
|
nm.lParam = 0;
|
|
|
|
if (!ListView_IsOwnerData(plv))
|
|
{
|
|
if (code == LVN_DELETEITEM)
|
|
{
|
|
LISTITEM * pItem = ListView_GetItemPtr(plv, i);
|
|
if (pItem)
|
|
nm.lParam = pItem->lParam;
|
|
}
|
|
}
|
|
|
|
return (BOOL)CCSendNotify(&plv->ci, code, &nm.hdr);
|
|
}
|
|
|
|
BOOL ListView_GetEmptyText(LV* plv)
|
|
{
|
|
BOOL fRet;
|
|
|
|
if (plv->fNoEmptyText)
|
|
{
|
|
fRet = FALSE;
|
|
}
|
|
else if (plv->pszEmptyText)
|
|
{
|
|
fRet = TRUE;
|
|
}
|
|
else
|
|
{
|
|
TCHAR szText[80];
|
|
NMLVDISPINFO nm = {0};
|
|
|
|
// For each listview control, we will only send this notify
|
|
// once if necessary.
|
|
szText[0] = TEXT('\0');
|
|
|
|
nm.item.mask = LVIF_TEXT;
|
|
nm.item.pszText = szText;
|
|
nm.item.cchTextMax = ARRAYSIZE(szText);
|
|
|
|
fRet = (BOOL)CCSendNotify(&plv->ci, LVN_GETEMPTYTEXT, &nm.hdr);
|
|
|
|
if (fRet)
|
|
{
|
|
// save the text so we don't notify again.
|
|
Str_Set(&plv->pszEmptyText, szText);
|
|
}
|
|
else
|
|
{
|
|
// set a flag so we don't notify again.
|
|
plv->fNoEmptyText = TRUE;
|
|
}
|
|
}
|
|
|
|
return fRet;
|
|
}
|
|
|
|
void ListView_NotifyFocusEvent(LV *plv)
|
|
{
|
|
if (plv->iFocus != -1 && IsWindowVisible(plv->ci.hwnd) && GetFocus() == plv->ci.hwnd)
|
|
NotifyWinEvent(EVENT_OBJECT_FOCUS, plv->ci.hwnd, OBJID_CLIENT,
|
|
plv->iFocus+1);
|
|
}
|
|
|
|
//
|
|
// Call this function when the listview has changed in a radical manner.
|
|
// It notifies MSAA that "Whoa, things are completely different now."
|
|
//
|
|
void ListView_NotifyRecreate(LV *plv)
|
|
{
|
|
NotifyWinEvent(EVENT_OBJECT_DESTROY, plv->ci.hwnd, OBJID_CLIENT, CHILDID_SELF);
|
|
NotifyWinEvent(EVENT_OBJECT_CREATE, plv->ci.hwnd, OBJID_CLIENT, CHILDID_SELF);
|
|
plv->iMSAAMin = plv->iMSAAMax = 0;
|
|
}
|
|
|
|
int ListView_OnSetItemCount(LV *plv, int iItems, DWORD dwFlags)
|
|
{
|
|
BOOL frt = TRUE;
|
|
|
|
// For compatability we assume 0 for flags implies old (Athena) type of functionality and
|
|
// does a Invalidate all otherwise if low bit is set we try to be a bit smarter. First pass
|
|
// If the first added item is visible invalidate all. Yes we can do better...
|
|
if (ListView_IsOwnerData(plv))
|
|
{
|
|
int iItem;
|
|
int cTotalItemsOld = plv->cTotalItems;
|
|
BOOL fInvalidateAll = ((dwFlags & LVSICF_NOINVALIDATEALL) == 0);
|
|
|
|
if ((iItems >= 0) && (iItems <= MAX_LISTVIEWITEMS))
|
|
{
|
|
plv->cTotalItems = iItems;
|
|
|
|
// check focus
|
|
if (plv->iFocus >= iItems)
|
|
plv->iFocus = -1;
|
|
if (plv->iDropHilite >= iItems)
|
|
plv->iDropHilite = -1;
|
|
|
|
// check mark
|
|
if (plv->iMark >= iItems)
|
|
plv->iMark = -1;
|
|
|
|
// make sure no selections above number of items
|
|
plv->plvrangeCut->lpVtbl->ExcludeRange(plv->plvrangeCut, iItems, SELRANGE_MAXVALUE);
|
|
if (FAILED(plv->plvrangeSel->lpVtbl->ExcludeRange(plv->plvrangeSel, iItems, SELRANGE_MAXVALUE)))
|
|
{
|
|
SetLastError(ERROR_OUTOFMEMORY);
|
|
return FALSE;
|
|
}
|
|
|
|
|
|
plv->rcView.left = RECOMPUTE; // recompute view rect
|
|
|
|
if (ListView_IsAutoArrangeView(plv))
|
|
{
|
|
// Call off to the arrange function.
|
|
ListView_OnArrange(plv, LVA_DEFAULT);
|
|
|
|
if (!fInvalidateAll)
|
|
{
|
|
// Try to be smart and invalidate only what we need to.
|
|
// Add a little logic to erase any message like no items found when
|
|
// the view was previously empty...
|
|
if (cTotalItemsOld < iItems)
|
|
iItem = cTotalItemsOld;
|
|
else
|
|
iItem = iItems - 1; // Get the index
|
|
|
|
if ((iItem >= 0) && (cTotalItemsOld > 0))
|
|
ListView_IInvalidateBelow(plv, iItem);
|
|
else
|
|
fInvalidateAll = TRUE;
|
|
}
|
|
|
|
}
|
|
else
|
|
{
|
|
ListView_Recompute(plv);
|
|
// if we have empty text and old count was zero... then we should redraw all
|
|
if (plv->pszEmptyText && (cTotalItemsOld == 0) && (iItems > 0))
|
|
fInvalidateAll = TRUE;
|
|
|
|
// Try to do smart invalidates...
|
|
if (!fInvalidateAll)
|
|
{
|
|
// Try to be smart and invalidate only what we need to.
|
|
if (cTotalItemsOld < iItems)
|
|
iItem = cTotalItemsOld;
|
|
else
|
|
iItem = iItems - 1; // Get the index
|
|
|
|
if (iItem >= 0)
|
|
ListView_LRInvalidateBelow(plv, iItem, FALSE);
|
|
}
|
|
|
|
|
|
// We may try to resize the column
|
|
ListView_MaybeResizeListColumns(plv, 0, ListView_Count(plv)-1);
|
|
|
|
// For compatability we assume 0 for flags implies old type
|
|
// of functionality and scrolls the important item into view.
|
|
// If second bit is set, we leave the scroll position alone.
|
|
if ((dwFlags & LVSICF_NOSCROLL) == 0)
|
|
{
|
|
// what is the important item
|
|
iItem = (plv->iFocus >= 0) ?
|
|
plv->iFocus :
|
|
ListView_OnGetNextItem(plv, -1, LVNI_SELECTED);
|
|
|
|
iItem = max(0, iItem);
|
|
|
|
// make important item visable
|
|
ListView_OnEnsureVisible(plv, iItem, FALSE);
|
|
}
|
|
}
|
|
|
|
|
|
if (fInvalidateAll)
|
|
InvalidateRect(plv->ci.hwnd, NULL, TRUE);
|
|
ListView_UpdateScrollBars(plv);
|
|
|
|
ListView_NotifyRecreate(plv);
|
|
ListView_NotifyFocusEvent(plv);
|
|
|
|
}
|
|
else
|
|
{
|
|
frt = FALSE;
|
|
}
|
|
|
|
}
|
|
else
|
|
{
|
|
if (plv->hdpaSubItems)
|
|
{
|
|
int iCol;
|
|
for (iCol = plv->cCol - 1; iCol >= 0; iCol--)
|
|
{
|
|
HDPA hdpa = ListView_GetSubItemDPA(plv, iCol);
|
|
if (hdpa) // this is optional, call backs don't have them
|
|
DPA_Grow(hdpa, iItems);
|
|
}
|
|
}
|
|
|
|
DPA_Grow(plv->hdpa, iItems);
|
|
DPA_Grow(plv->hdpaZOrder, iItems);
|
|
}
|
|
|
|
return frt;
|
|
}
|
|
|
|
VOID ListView_InvalidateTTLastHit(LV* plv, int iNewHit)
|
|
{
|
|
if (plv->iTTLastHit == iNewHit)
|
|
{
|
|
plv->iTTLastHit = -1;
|
|
if (plv->pszTip && plv->pszTip != LPSTR_TEXTCALLBACK)
|
|
{
|
|
plv->pszTip[0] = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
typedef struct
|
|
{
|
|
LV *plv;
|
|
BOOL fSortIndices;
|
|
PFNLVCOMPARE pfnCompare;
|
|
LPARAM lParam;
|
|
BOOL bPassLP;
|
|
} LVSortInfo;
|
|
|
|
int CALLBACK ListView_SortCallback(void * dw1, void * dw2, LPARAM lParam)
|
|
{
|
|
LISTITEM *pitem1;
|
|
LISTITEM *pitem2;
|
|
LVSortInfo *pSortInfo = (LVSortInfo *)lParam;
|
|
|
|
ASSERT(!ListView_IsOwnerData(pSortInfo->plv));
|
|
|
|
// determine whether dw1 and dw2 are indices or the real items
|
|
// and assign pitem? accordingly
|
|
if (pSortInfo->fSortIndices)
|
|
{
|
|
pitem1 = ListView_GetItemPtr(pSortInfo->plv, PtrToUlong(dw1));
|
|
pitem2 = ListView_GetItemPtr(pSortInfo->plv, PtrToUlong(dw2));
|
|
}
|
|
else
|
|
{
|
|
pitem1 = (LISTITEM *)dw1;
|
|
pitem2 = (LISTITEM *)dw2;
|
|
}
|
|
|
|
if (!pSortInfo->pfnCompare)
|
|
{
|
|
// Treat NULL pszText like null string.
|
|
LPCTSTR pszText1 = pitem1->pszText ? pitem1->pszText : c_szNULL;
|
|
LPCTSTR pszText2 = pitem2->pszText ? pitem2->pszText : c_szNULL;
|
|
|
|
// REARCHITECT: should allow callbacks in text
|
|
if (pszText1 != LPSTR_TEXTCALLBACK &&
|
|
pszText2 != LPSTR_TEXTCALLBACK)
|
|
{
|
|
return lstrcmpi(pitem1->pszText, pitem2->pszText);
|
|
}
|
|
RIPMSG(0, "LVM_SORTITEM(EX): Cannot combine NULL callback with LPSTR_TEXTCALLBACK");
|
|
return -1;
|
|
}
|
|
else
|
|
{
|
|
if (pSortInfo->bPassLP)
|
|
return pSortInfo->pfnCompare(pitem1->lParam, pitem2->lParam, pSortInfo->lParam);
|
|
else
|
|
{
|
|
if (pSortInfo->fSortIndices)
|
|
return pSortInfo->pfnCompare((LPARAM)dw1, (LPARAM)dw2, pSortInfo->lParam);
|
|
else
|
|
{
|
|
// we want to sort by the indices, but all we've got are pointers to the items
|
|
// and there is no way to get back from that pointer to an index
|
|
RIPMSG(0, "LVM_SORTITEM(EX): Want to sort by indicies, but only have pointers");
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
}
|
|
RIPMSG(0, "LVM_SORTITEM(EX): Didn't seem to sort by anything");
|
|
return -1;
|
|
}
|
|
|
|
LISTGROUP* ListView_FindGroupFromID(LV* plv, int iGroupId, int* piIndex)
|
|
{
|
|
if (plv->hdpaGroups)
|
|
{
|
|
int cGroups = DPA_GetPtrCount(plv->hdpaGroups);
|
|
int iGroup;
|
|
for (iGroup = 0; iGroup < cGroups; iGroup++)
|
|
{
|
|
LISTGROUP* pgrp = DPA_FastGetPtr(plv->hdpaGroups, iGroup);
|
|
if (pgrp->iGroupId == iGroupId)
|
|
{
|
|
if (piIndex)
|
|
*piIndex = iGroup;
|
|
return pgrp;
|
|
}
|
|
}
|
|
}
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
BOOL ListView_VerifyGroupIdIsUnique(LV* plv, int iGroupId)
|
|
{
|
|
if (plv->hdpaGroups)
|
|
{
|
|
int cGroups = DPA_GetPtrCount(plv->hdpaGroups);
|
|
int iGroup;
|
|
for (iGroup = 0; iGroup < cGroups; iGroup++)
|
|
{
|
|
LISTGROUP* pgrp = DPA_FastGetPtr(plv->hdpaGroups, iGroup);
|
|
if (pgrp->iGroupId == iGroupId)
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
BOOL ListView_RemoveItemFromItsGroup(LV* plv, LISTITEM* pitem)
|
|
{
|
|
if (LISTITEM_HASGROUP(pitem))
|
|
{
|
|
LISTGROUP* pgrp = pitem->pGroup;
|
|
int cItems = DPA_GetPtrCount(pgrp->hdpa);
|
|
int iItem;
|
|
for (iItem = 0; iItem < cItems; iItem++)
|
|
{
|
|
LISTITEM* pgitem = DPA_FastGetPtr(pgrp->hdpa, iItem);
|
|
if (pgitem == pitem)
|
|
{
|
|
DPA_DeletePtr(pgrp->hdpa, iItem);
|
|
return TRUE;
|
|
}
|
|
}
|
|
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
BOOL ListView_FixupGroupsAfterSorting(LV *plv)
|
|
{
|
|
BOOL fRet = FALSE;
|
|
int cGroups = DPA_GetPtrCount(plv->hdpaGroups);
|
|
|
|
int *rgiGroupIds = LocalAlloc(LPTR, sizeof(int) * cGroups);
|
|
|
|
// rgi will be where we keep the index in each group as we add items to them
|
|
int *rgi = LocalAlloc(LPTR, sizeof(int) * cGroups);
|
|
|
|
if (rgiGroupIds && rgi)
|
|
{
|
|
int i;
|
|
int iMax = DPA_GetPtrCount(plv->hdpa);
|
|
|
|
// Save away the group IDs, and temporary replace them with straight indices
|
|
for (i=0; i < cGroups; i++)
|
|
{
|
|
LISTGROUP* pgrp = DPA_FastGetPtr(plv->hdpaGroups, i);
|
|
rgiGroupIds[i] = pgrp->iGroupId;
|
|
pgrp->iGroupId = i;
|
|
}
|
|
|
|
// Now all the items are sorted, and all we need to do it put them back in their
|
|
// respective groups is sorted order
|
|
for (i=0; i < iMax;i++)
|
|
{
|
|
LISTITEM *pitem = ListView_FastGetItemPtr(plv, i);
|
|
LISTGROUP* pgrp = LISTITEM_GROUP(pitem);
|
|
if (pgrp)
|
|
{
|
|
ASSERT(pgrp->hdpa);
|
|
DPA_SetPtr(pgrp->hdpa, rgi[pgrp->iGroupId]++, pitem);
|
|
}
|
|
}
|
|
|
|
#if DEBUG
|
|
// At this point, we should still have the proper number of items in each group!
|
|
for (i=0; i < cGroups; i++)
|
|
{
|
|
LISTGROUP* pgrp = DPA_FastGetPtr(plv->hdpaGroups, i);
|
|
ASSERT(DPA_GetPtrCount(pgrp->hdpa) == rgi[i]);
|
|
}
|
|
#endif
|
|
|
|
// Restore the proper GroupIds now
|
|
for (i=0; i < cGroups; i++)
|
|
{
|
|
LISTGROUP* pgrp = DPA_FastGetPtr(plv->hdpaGroups, i);
|
|
pgrp->iGroupId = rgiGroupIds[i];
|
|
}
|
|
|
|
fRet = TRUE;
|
|
}
|
|
LocalFree(rgiGroupIds);
|
|
LocalFree(rgi);
|
|
return fRet;
|
|
}
|
|
|
|
BOOL ListView_SortAllColumns(LV* plv, LVSortInfo * psi)
|
|
{
|
|
BOOL fReturn;
|
|
ASSERT(!ListView_IsOwnerData(plv));
|
|
|
|
ListView_InvalidateTTLastHit(plv, plv->iTTLastHit);
|
|
|
|
// don't do this optimization if we will need the indices to sort by
|
|
if (psi->bPassLP &&
|
|
((!plv->hdpaSubItems) ||
|
|
!DPA_GetPtrCount(plv->hdpaSubItems)))
|
|
{
|
|
psi->fSortIndices = FALSE;
|
|
fReturn = DPA_Sort(plv->hdpa, ListView_SortCallback, (LPARAM)psi);
|
|
}
|
|
else
|
|
{
|
|
// if we need to sort several hdpa's, create one DPA of just indices
|
|
// and sort that, then fix up all the dpa's
|
|
|
|
// initialize the hdpa with indices
|
|
HDPA hdpa = DPA_Clone(plv->hdpa, NULL);
|
|
|
|
fReturn = FALSE;
|
|
if (hdpa)
|
|
{
|
|
int i, iMax;
|
|
void **ph;
|
|
void **pNewIndices;
|
|
ASSERT(DPA_GetPtrCount(plv->hdpa) == DPA_GetPtrCount(hdpa));
|
|
ph = pNewIndices = DPA_GetPtrPtr(hdpa);
|
|
iMax = DPA_GetPtrCount(hdpa);
|
|
for (i = 0; i < iMax; ph++, i++)
|
|
{
|
|
*ph = IntToPtr(i);
|
|
}
|
|
|
|
psi->fSortIndices = TRUE;
|
|
if (DPA_Sort(hdpa, ListView_SortCallback, (LPARAM)psi))
|
|
{
|
|
ph = LocalAlloc(LPTR, sizeof(void *) * iMax);
|
|
if (ph)
|
|
{
|
|
int j;
|
|
void **pSubItems;
|
|
// we could get here because bPassLP is false, even if we don't have subitems
|
|
if (plv->hdpaSubItems && DPA_GetPtrCount(plv->hdpaSubItems))
|
|
{
|
|
for (i = DPA_GetPtrCount(plv->hdpaSubItems) - 1; i >= 0; i--)
|
|
{
|
|
HDPA hdpaSubItem = ListView_GetSubItemDPA(plv, i);
|
|
if (hdpaSubItem)
|
|
{
|
|
// make sure it's of the right size
|
|
while (DPA_GetPtrCount(hdpaSubItem) < iMax)
|
|
{
|
|
if (DPA_InsertPtr(hdpaSubItem, iMax, NULL) == -1)
|
|
goto Bail;
|
|
}
|
|
|
|
// actually copy across the dpa with the new indices
|
|
pSubItems = DPA_GetPtrPtr(hdpaSubItem);
|
|
for (j = 0; j < iMax; j++)
|
|
{
|
|
ph[j] = pSubItems[PtrToUlong(pNewIndices[j])];
|
|
}
|
|
|
|
// finally, copy it all back to the pSubItems;
|
|
memcpy(pSubItems, ph, sizeof(void *) * iMax);
|
|
}
|
|
}
|
|
}
|
|
|
|
// now do the main hdpa
|
|
pSubItems = DPA_GetPtrPtr(plv->hdpa);
|
|
for (j = 0; j < iMax; j++)
|
|
{
|
|
ph[j] = pSubItems[PtrToUlong(pNewIndices[j])];
|
|
}
|
|
|
|
// finally, copy it all back to the pSubItems;
|
|
memcpy(pSubItems, ph, sizeof(void *) * iMax);
|
|
fReturn = TRUE;
|
|
Bail:
|
|
LocalFree(ph);
|
|
}
|
|
}
|
|
DPA_Destroy(hdpa);
|
|
}
|
|
}
|
|
if (fReturn && plv->fGroupView && plv->hdpaGroups && DPA_GetPtrCount(plv->hdpaGroups) > 0 && ListView_IsGroupedView(plv))
|
|
{
|
|
fReturn = ListView_FixupGroupsAfterSorting(plv);
|
|
}
|
|
return fReturn;
|
|
}
|
|
|
|
DWORD ListView_OnApproximateViewRect(LV* plv, int iCount, int iWidth, int iHeight)
|
|
{
|
|
if (iCount == -1)
|
|
iCount = ListView_Count(plv);
|
|
|
|
if (iWidth == -1)
|
|
iWidth = plv->sizeClient.cx;
|
|
|
|
if (iHeight == -1)
|
|
iHeight = plv->sizeClient.cy;
|
|
|
|
return _ListView_ApproximateViewRect(plv, iCount, iWidth, iHeight);
|
|
}
|
|
|
|
DWORD ListView_OnSetLVRangeObject(LV* plv, int iWhich, ILVRange *plvrange)
|
|
{
|
|
ILVRange **pplvrange;
|
|
switch (iWhich)
|
|
{
|
|
case LVSR_SELECTION:
|
|
pplvrange = &plv->plvrangeSel;
|
|
break;
|
|
case LVSR_CUT:
|
|
pplvrange = &plv->plvrangeCut;
|
|
break;
|
|
default:
|
|
return FALSE;
|
|
}
|
|
if (*pplvrange)
|
|
{
|
|
// Release the old one
|
|
(*pplvrange)->lpVtbl->Release(*pplvrange);
|
|
}
|
|
*pplvrange = plvrange;
|
|
|
|
// Hold onto the pointer...
|
|
if (plvrange)
|
|
plvrange->lpVtbl->AddRef(plvrange);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
BOOL ListView_OnSortItems(LV *plv, LPARAM lParam, PFNLVCOMPARE pfnCompare, BOOL bPassLP)
|
|
{
|
|
LVSortInfo SortInfo;
|
|
LISTITEM *pitemFocused;
|
|
SortInfo.pfnCompare = pfnCompare;
|
|
SortInfo.lParam = lParam;
|
|
SortInfo.plv = plv;
|
|
SortInfo.bPassLP = bPassLP;
|
|
|
|
if (ListView_IsOwnerData(plv))
|
|
{
|
|
RIPMSG(0, "LVM_SORTITEMS: Invalid for owner-data listview");
|
|
return FALSE;
|
|
}
|
|
|
|
ListView_DismissEdit(plv, TRUE); // cancel edits
|
|
|
|
// we're going to mess with the indices, so stash away the pointer to the
|
|
// focused item.
|
|
if (plv->iFocus != -1)
|
|
{
|
|
pitemFocused = ListView_GetItemPtr(plv, plv->iFocus);
|
|
}
|
|
else
|
|
pitemFocused = NULL;
|
|
|
|
if (ListView_SortAllColumns(plv, &SortInfo))
|
|
{
|
|
// restore the focused item.
|
|
if (pitemFocused)
|
|
{
|
|
int i;
|
|
for (i = ListView_Count(plv) - 1; i >= 0 ; i--)
|
|
{
|
|
if (ListView_GetItemPtr(plv, i) == pitemFocused)
|
|
{
|
|
plv->iFocus = i;
|
|
plv->iMark = i;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (ListView_IsAutoArrangeView(plv))
|
|
{
|
|
ListView_CommonArrange(plv, LVA_DEFAULT, plv->hdpa);
|
|
}
|
|
else
|
|
{
|
|
InvalidateRect(plv->ci.hwnd, NULL, TRUE);
|
|
}
|
|
|
|
// The items in the view have moved around; let apps know
|
|
NotifyWinEvent(EVENT_OBJECT_REORDER, plv->ci.hwnd, OBJID_CLIENT, 0);
|
|
return TRUE;
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
|
|
void ListView_EnableWindow(LV* plv, BOOL wParam)
|
|
{
|
|
if (wParam)
|
|
{
|
|
if (plv->ci.style & WS_DISABLED)
|
|
{
|
|
plv->ci.style &= ~WS_DISABLED; // enabled
|
|
ListView_OnSetBkColor(plv, plv->clrBkSave);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (!(plv->ci.style & WS_DISABLED))
|
|
{
|
|
plv->clrBkSave = plv->clrBk;
|
|
plv->ci.style |= WS_DISABLED; // disabled
|
|
ListView_OnSetBkColor(plv, g_clrBtnFace);
|
|
}
|
|
}
|
|
RedrawWindow(plv->ci.hwnd, NULL, NULL, RDW_INVALIDATE | RDW_ERASE);
|
|
}
|
|
|
|
|
|
BOOL ListView_IsItemVisibleI(LV* plv, int i)
|
|
// Assumes parmss ok etc for speed. Called inside region calc code.
|
|
{
|
|
RECT rcBounds;
|
|
|
|
// get bounding rect of item
|
|
ListView_GetRects(plv, i, QUERY_DEFAULT, NULL, NULL, &rcBounds, NULL);
|
|
|
|
// Should perf this up for multimonitor case where there are dead zones in work area...
|
|
return RECTS_IN_SIZE(plv->sizeClient, rcBounds);
|
|
}
|
|
|
|
|
|
// Helper for ListView_RecalcRegion
|
|
#define BitOn(lpbits, x, y, cx) (*((BYTE *)(lpbits + ((y * cx) + (x / 8)))) & (0x80 >> (x % 8)))
|
|
|
|
void ListView_RecalcRegion(LV* plv, BOOL fForce, BOOL fRedraw)
|
|
{
|
|
HRGN hrgnUnion = NULL;
|
|
HRGN hrgn = NULL;
|
|
int i;
|
|
HDC hdc = NULL;
|
|
BYTE * lpBits = NULL;
|
|
HBITMAP hbmp = NULL, hbmpOld = NULL;
|
|
RECT rc, rcIcon = {0};
|
|
LISTITEM * pitem;
|
|
BITMAP bm;
|
|
|
|
// Bail out if we don't need to do any work
|
|
if (!(plv->exStyle & LVS_EX_REGIONAL) || !ListView_RedrawEnabled(plv) ||
|
|
(plv->flags & LVF_INRECALCREGION))
|
|
return;
|
|
|
|
// To prevent recursion
|
|
plv->flags |= LVF_INRECALCREGION;
|
|
|
|
if ((ListView_Count(plv) > 0))
|
|
{
|
|
int cxIcon, cyIcon;
|
|
int dxOffset, dyOffset;
|
|
|
|
// Run through first to see if anything changed - bail if not!
|
|
if (!fForce)
|
|
{
|
|
for (i = 0; i < ListView_Count(plv); i++)
|
|
{
|
|
pitem = ListView_FastGetItemPtr(plv, i);
|
|
|
|
if (!ListView_IsItemVisibleI(plv, i))
|
|
{
|
|
if (pitem->hrgnIcon == (HANDLE)-1 || !pitem->hrgnIcon)
|
|
// Item was invisible and still is. Nothing changed.
|
|
continue;
|
|
|
|
if (pitem->hrgnIcon)
|
|
{
|
|
// Item was visible and now is invisible... Something
|
|
// changed.
|
|
pitem->ptRgn.x = RECOMPUTE;
|
|
pitem->ptRgn.y = RECOMPUTE;
|
|
DeleteObject(pitem->hrgnIcon);
|
|
pitem->hrgnIcon = NULL;
|
|
}
|
|
}
|
|
|
|
ListView_GetRects(plv, i, QUERY_DEFAULT, NULL, &rc, NULL, NULL);
|
|
|
|
// If the location of the icon or the text rectangle have
|
|
// changed, then we need to continue so that we can recalculate
|
|
// the region.
|
|
if ((pitem->pt.x != pitem->ptRgn.x) ||
|
|
(pitem->pt.y != pitem->ptRgn.y) ||
|
|
(!pitem->hrgnIcon) ||
|
|
!EqualRect((CONST RECT *)&pitem->rcTextRgn, (CONST RECT *)&rc))
|
|
goto changed;
|
|
|
|
}
|
|
// If we go through all the items and nothing changed, then
|
|
// we can return without doing any work!
|
|
ASSERT(i == ListView_Count(plv));
|
|
goto exit;
|
|
changed:;
|
|
}
|
|
|
|
// Figure out the dimensions of the Icon rectangle - assumes
|
|
// each Icon rectangle is the same size.
|
|
ListView_GetRects(plv, 0, QUERY_DEFAULT, &rcIcon, NULL, NULL, NULL);
|
|
|
|
// Center the icon in the rectangle
|
|
CCGetIconSize(&plv->ci, plv->himl, &cxIcon, &cyIcon);
|
|
|
|
dxOffset = (rcIcon.right - rcIcon.left - cxIcon) / 2;
|
|
dyOffset = (rcIcon.bottom - rcIcon.top - cyIcon) / 2;
|
|
cxIcon = rcIcon.right - rcIcon.left;
|
|
cyIcon = rcIcon.bottom - rcIcon.top;
|
|
|
|
if (!(hdc = CreateCompatibleDC(NULL)) ||
|
|
(!(hbmp = CreateBitmap(cxIcon, cyIcon, 1, 1, NULL))))
|
|
{
|
|
goto BailOut;
|
|
}
|
|
|
|
GetObject(hbmp, sizeof(bm), &bm);
|
|
|
|
if (!(lpBits = (BYTE *)GlobalAlloc(GPTR, bm.bmWidthBytes * bm.bmHeight)))
|
|
goto BailOut;
|
|
|
|
hbmpOld = SelectObject(hdc, hbmp);
|
|
PatBlt(hdc, 0, 0, cxIcon, cyIcon, WHITENESS);
|
|
|
|
if (hrgnUnion = CreateRectRgn(0, 0, 0, 0))
|
|
{
|
|
for (i = 0; i < ListView_Count(plv); i++)
|
|
{
|
|
int x, y, iResult;
|
|
BOOL fStarted = FALSE;
|
|
LPRECT lprc;
|
|
|
|
pitem = ListView_FastGetItemPtr(plv, i);
|
|
|
|
if (pitem->pt.y == RECOMPUTE)
|
|
continue;
|
|
|
|
if (!ListView_IsItemVisibleI(plv, i))
|
|
{
|
|
// ignore invisible items
|
|
if (pitem->hrgnIcon && pitem->hrgnIcon!=(HANDLE)-1)
|
|
{
|
|
pitem->ptRgn.x = RECOMPUTE;
|
|
pitem->ptRgn.y = RECOMPUTE;
|
|
DeleteObject(pitem->hrgnIcon);
|
|
pitem->hrgnIcon = (HANDLE)-1;
|
|
}
|
|
continue;
|
|
}
|
|
|
|
// Add the region for the icon text first
|
|
ListView_GetRects(plv, i, QUERY_DEFAULT, &rcIcon, &rc, NULL, NULL);
|
|
|
|
// If we're in edit mode always use rcTextRgn
|
|
if (i == plv->iEdit)
|
|
lprc = &pitem->rcTextRgn;
|
|
else
|
|
lprc = &rc;
|
|
|
|
if (!(hrgn = CreateRectRgnIndirect(lprc)))
|
|
goto Error;
|
|
|
|
iResult = CombineRgn(hrgnUnion, hrgn, hrgnUnion, RGN_OR);
|
|
|
|
DeleteObject(hrgn);
|
|
|
|
if (iResult == ERROR)
|
|
{
|
|
// Error case - out of memory. Just select in a NULL region.
|
|
Error:
|
|
DeleteObject(hrgnUnion);
|
|
hrgnUnion = NULL;
|
|
break;
|
|
}
|
|
|
|
// Succeeded, copy the rectangle to rcTextRgn so we
|
|
// can test against it in the future. Don't copy over
|
|
// it if we are in edit mode, the rectangle is used to
|
|
// store the edit window in that case.
|
|
if (plv->iEdit != i)
|
|
CopyRect(&pitem->rcTextRgn, (CONST RECT *)&rc);
|
|
|
|
// Now create a region for the icon mask - or use the cached one
|
|
if (!pitem->hrgnIcon || pitem->hrgnIcon == (HANDLE)-1)
|
|
{
|
|
// (pitem->pt.x != pitem->ptRgn.x) ||
|
|
// (pitem->pt.y != pitem->ptRgn.y))
|
|
HRGN hrgnIcon = NULL;
|
|
|
|
// On slow machines, we'll just wrap the icon with a rectangle. But on
|
|
// faster machines, we'll build a region that corresponds to the
|
|
// mask for the icon so it looks sweet.
|
|
if (g_fSlowMachine)
|
|
{
|
|
// Modify the rectangle slightly so it looks better
|
|
|
|
// Glue the icon and text rectangles together
|
|
rcIcon.bottom = rc.top;
|
|
// Shrink the width of the rectangle so it's only as big as the icon itself
|
|
InflateRect(&rcIcon, -dxOffset, 0);
|
|
hrgnIcon = CreateRectRgnIndirect(&rcIcon);
|
|
}
|
|
else
|
|
{
|
|
// If the image isn't around, get it now.
|
|
if (pitem->iImage == I_IMAGECALLBACK)
|
|
{
|
|
LV_ITEM item;
|
|
|
|
item.iItem = i;
|
|
item.iSubItem = 0;
|
|
item.mask = LVIF_IMAGE;
|
|
item.stateMask = LVIS_ALL;
|
|
item.pszText = NULL;
|
|
item.cchTextMax = 0;
|
|
// BOGUS - do we need to worry about our state
|
|
// getting messed up during the callback?
|
|
ListView_OnGetItem(plv, &item);
|
|
}
|
|
|
|
ImageList_Draw(plv->himl, pitem->iImage, hdc, 0, 0, ILD_MASK | (pitem->state & LVIS_OVERLAYMASK));
|
|
|
|
GetBitmapBits(hbmp, bm.bmWidthBytes * bm.bmHeight, (void *)lpBits);
|
|
|
|
for (y = 0; y < cyIcon; y++)
|
|
{
|
|
for (x = 0; x < cxIcon; x++)
|
|
{
|
|
if (!fStarted && !BitOn(lpBits, x, y, bm.bmWidthBytes))
|
|
{
|
|
rc.left = x;
|
|
rc.top = y;
|
|
rc.bottom = y + 1;
|
|
fStarted = TRUE;
|
|
if (x == (cxIcon - 1))
|
|
{
|
|
x++;
|
|
goto AddIt;
|
|
}
|
|
else
|
|
{
|
|
continue;
|
|
}
|
|
}
|
|
|
|
if (fStarted && BitOn(lpBits, x, y, bm.bmWidthBytes))
|
|
{
|
|
AddIt:
|
|
rc.right = x;
|
|
//
|
|
// Mirror the region so that the icons get displayed ok. [samera]
|
|
//
|
|
if (plv->ci.dwExStyle & RTL_MIRRORED_WINDOW)
|
|
{
|
|
int iLeft = rc.left;
|
|
rc.left = (cxIcon - (rc.right+1));
|
|
rc.right = (cxIcon - (iLeft+1));
|
|
OffsetRect(&rc, rcIcon.left - dxOffset, rcIcon.top + dyOffset);
|
|
}
|
|
else
|
|
OffsetRect(&rc, rcIcon.left + dxOffset, rcIcon.top + dyOffset);
|
|
|
|
|
|
if (hrgn = CreateRectRgnIndirect(&rc))
|
|
{
|
|
if (hrgnIcon || (hrgnIcon = CreateRectRgn(0, 0, 0, 0)))
|
|
iResult = CombineRgn(hrgnIcon, hrgn, hrgnIcon, RGN_OR);
|
|
else
|
|
iResult = ERROR;
|
|
|
|
DeleteObject(hrgn);
|
|
}
|
|
|
|
if (!hrgn || (iResult == ERROR))
|
|
{
|
|
if (hrgnIcon)
|
|
DeleteObject(hrgnIcon);
|
|
goto Error;
|
|
}
|
|
|
|
fStarted = FALSE;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (hrgnIcon)
|
|
{
|
|
// Cache it since it takes a long time to build it
|
|
if (pitem->hrgnIcon && pitem->hrgnIcon != (HANDLE)-1)
|
|
DeleteObject(pitem->hrgnIcon);
|
|
pitem->hrgnIcon = hrgnIcon;
|
|
pitem->ptRgn = pitem->pt;
|
|
|
|
// Add it to the accumulated window region
|
|
if (ERROR == CombineRgn(hrgnUnion, hrgnIcon, hrgnUnion, RGN_OR))
|
|
goto Error;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
OffsetRgn(pitem->hrgnIcon, pitem->pt.x - pitem->ptRgn.x, pitem->pt.y - pitem->ptRgn.y);
|
|
pitem->ptRgn = pitem->pt;
|
|
if (ERROR == CombineRgn(hrgnUnion, pitem->hrgnIcon, hrgnUnion, RGN_OR))
|
|
goto Error;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
BailOut:
|
|
if (lpBits)
|
|
GlobalFree((HGLOBAL)lpBits);
|
|
if (hbmp)
|
|
{
|
|
SelectObject(hdc, hbmpOld);
|
|
DeleteObject(hbmp);
|
|
}
|
|
if (hdc)
|
|
DeleteDC(hdc);
|
|
|
|
// Windows takes ownership of the region when we select it in to the window
|
|
SetWindowRgn(plv->ci.hwnd, hrgnUnion, fRedraw);
|
|
|
|
exit:
|
|
plv->flags &= ~LVF_INRECALCREGION;
|
|
}
|
|
|
|
HIMAGELIST CreateCheckBoxImagelist(HIMAGELIST himl, BOOL fTree, BOOL fUseColorKey, BOOL fMirror)
|
|
{
|
|
int cxImage, cyImage;
|
|
HBITMAP hbm;
|
|
HBITMAP hbmTemp;
|
|
COLORREF clrMask = CLR_DEFAULT;
|
|
HDC hdcDesk = GetDC(NULL);
|
|
HDC hdc;
|
|
RECT rc;
|
|
int nImages = fTree ? 3 : 2;
|
|
HTHEME hTheme;
|
|
DTBGOPTS dtbg = {sizeof(DTBGOPTS), DTBG_DRAWSOLID, 0,}; // tell drawthemebackground to preserve the alpha channel
|
|
|
|
if (!hdcDesk)
|
|
return NULL;
|
|
|
|
hdc = CreateCompatibleDC(hdcDesk);
|
|
ReleaseDC(NULL, hdcDesk);
|
|
|
|
if (!hdc)
|
|
return NULL;
|
|
|
|
hTheme = OpenThemeData(NULL, L"Button");
|
|
|
|
|
|
// Must protect against ImageList_GetIconSize failing in case app
|
|
// gave us a bad himl
|
|
if (himl && ImageList_GetIconSize(himl, &cxImage, &cyImage))
|
|
{
|
|
// cxImage and cyImage are okay
|
|
}
|
|
else
|
|
{
|
|
cxImage = g_cxSmIcon;
|
|
cyImage = g_cySmIcon;
|
|
}
|
|
|
|
himl = ImageList_Create(cxImage, cyImage, ILC_MASK | ILC_COLOR32, 0, nImages);
|
|
hbm = CreateColorBitmap(cxImage * nImages, cyImage);
|
|
|
|
// fill
|
|
hbmTemp = SelectObject(hdc, hbm);
|
|
rc.left = rc.top = 0;
|
|
rc.bottom = cyImage;
|
|
rc.right = cxImage * nImages;
|
|
|
|
if (!hTheme)
|
|
{
|
|
if (fUseColorKey)
|
|
{
|
|
clrMask = RGB(255,000,255); // magenta
|
|
if (clrMask == g_clrWindow)
|
|
clrMask = RGB(000,000,255); // blue
|
|
}
|
|
else
|
|
{
|
|
clrMask = g_clrWindow;
|
|
}
|
|
|
|
// Don't fill the image with the mask when themes are on. We want this to
|
|
// "Alpha blend to zero" or be clear. No transparent blt needed.
|
|
FillRectClr(hdc, &rc, clrMask);
|
|
}
|
|
|
|
rc.right = cxImage;
|
|
// now draw the real controls on
|
|
InflateRect(&rc, -g_cxEdge, -g_cyEdge);
|
|
rc.right++;
|
|
rc.bottom++;
|
|
|
|
if (fTree)
|
|
OffsetRect(&rc, cxImage, 0);
|
|
|
|
if (hTheme)
|
|
{
|
|
DrawThemeBackgroundEx(hTheme, hdc, BP_CHECKBOX, CBS_UNCHECKEDNORMAL, &rc, &dtbg);
|
|
}
|
|
else
|
|
{
|
|
DrawFrameControl(hdc, &rc, DFC_BUTTON, DFCS_BUTTONCHECK | DFCS_FLAT |
|
|
(fUseColorKey? 0 : DFCS_TRANSPARENT));
|
|
}
|
|
|
|
OffsetRect(&rc, cxImage, 0);
|
|
// [msadek]; For the mirrored case, there is an off-by-one somewhere in MirrorIcon() or System API.
|
|
// Since I will not be touching MirrorIcon() by any mean and no chance to fix a system API,
|
|
// let's compensate for it here.
|
|
if (fMirror)
|
|
{
|
|
OffsetRect(&rc, -1, 0);
|
|
}
|
|
|
|
if (hTheme)
|
|
{
|
|
DrawThemeBackgroundEx(hTheme, hdc, BP_CHECKBOX, CBS_CHECKEDNORMAL, &rc, &dtbg);
|
|
}
|
|
else
|
|
{
|
|
DrawFrameControl(hdc, &rc, DFC_BUTTON, DFCS_BUTTONCHECK | DFCS_FLAT | DFCS_CHECKED |
|
|
(fUseColorKey? 0 : DFCS_TRANSPARENT));
|
|
}
|
|
|
|
SelectObject(hdc, hbmTemp);
|
|
|
|
if (fUseColorKey)
|
|
{
|
|
ImageList_AddMasked(himl, hbm, clrMask);
|
|
}
|
|
else
|
|
{
|
|
ImageList_Add(himl, hbm, NULL);
|
|
}
|
|
|
|
if (fMirror)
|
|
{
|
|
HICON hIcon = ImageList_ExtractIcon(0, himl, nImages-1);
|
|
MirrorIcon(&hIcon, NULL);
|
|
ImageList_ReplaceIcon(himl, nImages-1, hIcon);
|
|
}
|
|
|
|
DeleteDC(hdc);
|
|
DeleteObject(hbm);
|
|
if (hTheme)
|
|
CloseThemeData(hTheme);
|
|
return himl;
|
|
}
|
|
|
|
void ListView_InitCheckBoxes(LV* plv, BOOL fInitializeState)
|
|
{
|
|
HIMAGELIST himlCopy = (plv->himlSmall ? plv->himlSmall : plv->himl);
|
|
HIMAGELIST himl;
|
|
BOOL bMirror = FALSE;
|
|
// [msadek], CheckBoxed need not to be mirrored.
|
|
// mirroer it during imagelist creation time so that it displays correctly
|
|
|
|
himl = CreateCheckBoxImagelist(himlCopy, FALSE, TRUE, IS_WINDOW_RTL_MIRRORED(plv->ci.hwnd));
|
|
ImageList_SetBkColor(himl, IsUsingCleartype()? (plv->clrBk) : (CLR_NONE));
|
|
ListView_OnSetImageList(plv, himl, LVSIL_STATE);
|
|
|
|
if (fInitializeState)
|
|
ListView_OnSetItemState(plv, -1, INDEXTOSTATEIMAGEMASK(1), LVIS_STATEIMAGEMASK);
|
|
}
|
|
|
|
void ListView_PopBubble(LV *plv)
|
|
{
|
|
if (plv->hwndToolTips)
|
|
SendMessage(plv->hwndToolTips, TTM_POP, 0, 0);
|
|
}
|
|
|
|
DWORD ListView_ExtendedStyleChange(LV* plv, DWORD dwNewStyle, DWORD dwExMask)
|
|
{
|
|
DWORD dwOldStyle = plv->exStyle;
|
|
|
|
// this will change the listview report size and painting algorithm
|
|
// because of the leading edge, so need to re-update scroll bars
|
|
// and repaint everything
|
|
if (ListView_IsReportView(plv))
|
|
{
|
|
ListView_RUpdateScrollBars(plv);
|
|
InvalidateRect(plv->ci.hwnd, NULL, TRUE);
|
|
}
|
|
|
|
// Change of styles may also changes tooltip policy, so pop it
|
|
ListView_PopBubble(plv);
|
|
|
|
if (dwExMask)
|
|
dwNewStyle = (plv->exStyle & ~ dwExMask) | (dwNewStyle & dwExMask);
|
|
|
|
// Currently, LVS_EX_REGIONAL, LVS_EX_MULTIWORKAREAS, LVS_EX_HIDELABELS, and
|
|
// LVS_EX_SINGLEROW are only supported for large icon view
|
|
if (!ListView_IsIconView(plv))
|
|
{
|
|
dwNewStyle &= ~(LVS_EX_REGIONAL | LVS_EX_MULTIWORKAREAS | LVS_EX_HIDELABELS | LVS_EX_SINGLEROW);
|
|
}
|
|
|
|
// LVS_EX_REGIONAL and LVS_EX_SINGLEROW are not supported for ownerdata
|
|
if (ListView_IsOwnerData(plv))
|
|
{
|
|
dwNewStyle &= ~(LVS_EX_REGIONAL | LVS_EX_SINGLEROW);
|
|
}
|
|
|
|
plv->exStyle = dwNewStyle;
|
|
|
|
// do any invalidation or whatever is needed here.
|
|
if ((dwOldStyle ^ dwNewStyle) & LVS_EX_HIDELABELS)
|
|
{
|
|
plv->rcView.left = RECOMPUTE;
|
|
InvalidateRect(plv->ci.hwnd, NULL, TRUE);
|
|
}
|
|
|
|
if ((dwOldStyle ^ dwNewStyle) & LVS_EX_GRIDLINES)
|
|
{
|
|
if (ListView_IsReportView(plv))
|
|
{
|
|
InvalidateRect(plv->ci.hwnd, NULL, TRUE);
|
|
}
|
|
}
|
|
|
|
if ((dwOldStyle ^ dwNewStyle) & (LVS_EX_UNDERLINEHOT | LVS_EX_UNDERLINECOLD |
|
|
LVS_EX_ONECLICKACTIVATE | LVS_EX_TWOCLICKACTIVATE |
|
|
LVS_EX_SUBITEMIMAGES | LVS_EX_SNAPTOGRID))
|
|
{
|
|
plv->rcView.left = RECOMPUTE;
|
|
InvalidateRect(plv->ci.hwnd, NULL, TRUE);
|
|
}
|
|
|
|
if ((dwOldStyle ^ dwNewStyle) & LVS_EX_CHECKBOXES)
|
|
{
|
|
if (dwNewStyle & LVS_EX_CHECKBOXES)
|
|
{
|
|
ListView_InitCheckBoxes(plv, TRUE);
|
|
}
|
|
else
|
|
{
|
|
// destroy the check boxes!
|
|
HIMAGELIST himl = ListView_OnSetImageList(plv, NULL, LVSIL_STATE);
|
|
if (himl)
|
|
ImageList_Destroy(himl);
|
|
}
|
|
}
|
|
|
|
if ((dwOldStyle ^ dwNewStyle) & LVS_EX_FLATSB)
|
|
{
|
|
if (dwNewStyle & LVS_EX_FLATSB)
|
|
{
|
|
InitializeFlatSB(plv->ci.hwnd);
|
|
if (plv->hwndHdr)
|
|
{
|
|
SetWindowBits(plv->hwndHdr, GWL_STYLE, HDS_FLAT, HDS_FLAT);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (plv->hwndHdr)
|
|
{
|
|
SetWindowBits(plv->hwndHdr, GWL_STYLE, HDS_FLAT, 0);
|
|
}
|
|
UninitializeFlatSB(plv->ci.hwnd);
|
|
}
|
|
}
|
|
|
|
if ((dwOldStyle ^ dwNewStyle) & LVS_EX_REGIONAL)
|
|
{
|
|
g_fSlowMachine = FALSE;
|
|
|
|
if (dwNewStyle & LVS_EX_REGIONAL)
|
|
{
|
|
ListView_RecalcRegion(plv, TRUE, TRUE);
|
|
}
|
|
else
|
|
{
|
|
int i;
|
|
LISTITEM * pitem;
|
|
|
|
// Delete all the cached regions, then NULL out our selected region.
|
|
for (i = 0; i < ListView_Count(plv); i++)
|
|
{
|
|
pitem = ListView_FastGetItemPtr(plv, i);
|
|
if (pitem->hrgnIcon && pitem->hrgnIcon!=(HANDLE)-1)
|
|
{
|
|
DeleteObject(pitem->hrgnIcon);
|
|
}
|
|
pitem->hrgnIcon = NULL;
|
|
}
|
|
SetWindowRgn(plv->ci.hwnd, (HRGN)NULL, TRUE);
|
|
}
|
|
InvalidateRect(plv->ci.hwnd, NULL, TRUE);
|
|
}
|
|
|
|
if ((dwOldStyle ^ dwNewStyle) & LVS_EX_SINGLEROW)
|
|
{
|
|
_ListView_RecomputeEx(plv, NULL, 0, TRUE);
|
|
}
|
|
|
|
if (ListView_IsDoubleBuffer(plv))
|
|
{
|
|
InvalidateRect(plv->ci.hwnd, NULL, TRUE);
|
|
}
|
|
|
|
return dwOldStyle;
|
|
}
|
|
|
|
// Bug#94368 raymondc v6.0: Doesn't detect WM_WINDOWPOSCHANGING as a way
|
|
// of being shown. NT5 defview has to hack around it pretty grossly.
|
|
// Fix for v6.0.
|
|
|
|
void LV_OnShowWindow(LV* plv, BOOL fShow)
|
|
{
|
|
if (fShow)
|
|
{
|
|
if (!(plv->flags & LVF_VISIBLE))
|
|
{
|
|
plv->flags |= LVF_VISIBLE;
|
|
if (plv->fGroupView)
|
|
_ListView_RecomputeEx(plv, NULL, 0, FALSE);
|
|
ListView_UpdateScrollBars(plv);
|
|
}
|
|
}
|
|
else
|
|
plv->flags &= ~LVF_VISIBLE;
|
|
|
|
}
|
|
|
|
LRESULT ListView_OnHelp(LV* plv, LPHELPINFO lpHelpInfo)
|
|
{
|
|
|
|
// If we're seeing WM_HELP because of our child header control, then
|
|
// munge the HELPINFO structure to use the ListView's control id.
|
|
// win\core\user\combo.c has similiar code to handle the child edit
|
|
// control of a combo box.
|
|
if ((lpHelpInfo != NULL) && (plv->wView == LV_VIEW_DETAILS) &&
|
|
(lpHelpInfo->iCtrlId == LVID_HEADER))
|
|
{
|
|
|
|
lpHelpInfo->hItemHandle = plv->ci.hwnd;
|
|
lpHelpInfo->iCtrlId = GetWindowID(plv->ci.hwnd);
|
|
// Shouldn't have to do this: USER would have filled in the appropriate
|
|
// context id by walking up the parent hwnd chain.
|
|
//lpHelpInfo->dwContextId = GetContextHelpId(hwnd);
|
|
|
|
}
|
|
|
|
return DefWindowProc(plv->ci.hwnd, WM_HELP, 0, (LPARAM)lpHelpInfo);
|
|
|
|
}
|
|
|
|
DWORD ListView_OnSetIconSpacing(LV* plv, LPARAM lParam)
|
|
{
|
|
DWORD dwOld = MAKELONG(plv->cxIconSpacing, plv->cyIconSpacing);
|
|
|
|
int cxIconSpacing, cyIconSpacing;
|
|
|
|
if (lParam == (LPARAM)-1)
|
|
{
|
|
// go back to using defaults
|
|
plv->flags &= ~LVF_ICONSPACESET;
|
|
cxIconSpacing = (plv->cxIcon + (g_cxIconSpacing - g_cxIcon));
|
|
cyIconSpacing = (plv->cyIcon + (g_cyIconSpacing - g_cyIcon));
|
|
}
|
|
else
|
|
{
|
|
if (LOWORD(lParam))
|
|
{
|
|
cxIconSpacing = LOWORD(lParam);
|
|
if (ListView_IsDPIScaled(plv))
|
|
CCDPIScaleX(&cxIconSpacing);
|
|
}
|
|
else
|
|
{
|
|
cxIconSpacing = plv->cxIconSpacing;
|
|
}
|
|
|
|
if (HIWORD(lParam))
|
|
{
|
|
cyIconSpacing = HIWORD(lParam);
|
|
if (ListView_IsDPIScaled(plv))
|
|
CCDPIScaleY(&cyIconSpacing);
|
|
}
|
|
else
|
|
{
|
|
cyIconSpacing = plv->cyIconSpacing;
|
|
}
|
|
|
|
plv->flags |= LVF_ICONSPACESET;
|
|
}
|
|
|
|
if ((cxIconSpacing != plv->cxIconSpacing) ||
|
|
(cyIconSpacing != plv->cyIconSpacing))
|
|
{
|
|
plv->cxIconSpacing = cxIconSpacing;
|
|
plv->cyIconSpacing = cyIconSpacing;
|
|
|
|
plv->rcView.left = RECOMPUTE;
|
|
|
|
// Recomputing is necessary except when snap-to-grid is toggled. Snap to grid assumes icon spacing
|
|
// is the grid, however this is the only style that makes this assumption.
|
|
if(!(plv->exStyle & LVS_EX_SNAPTOGRID))
|
|
_ListView_RecomputeEx(plv, NULL, 0, TRUE);
|
|
|
|
plv->iFreeSlot = -1;
|
|
}
|
|
|
|
return dwOld;
|
|
}
|
|
|
|
BOOL ListView_OnSetCursorMsg(LV* plv)
|
|
{
|
|
if (plv->exStyle & (LVS_EX_ONECLICKACTIVATE|LVS_EX_TWOCLICKACTIVATE))
|
|
{
|
|
if (plv->iHot != -1)
|
|
{
|
|
if (((plv->exStyle & LVS_EX_ONECLICKACTIVATE && plv->fOneClickOK)) ||
|
|
ListView_OnGetItemState(plv, plv->iHot, LVIS_SELECTED))
|
|
{
|
|
if (!plv->hCurHot)
|
|
{
|
|
plv->hCurHot = LoadCursor(NULL, IDC_HAND);
|
|
}
|
|
|
|
SetCursor(plv->hCurHot);
|
|
|
|
return TRUE;
|
|
}
|
|
}
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
void ListView_OnSetHotItem(LV* plv, int iItem)
|
|
{
|
|
if (iItem != plv->iHot)
|
|
{
|
|
if ((plv->exStyle & LVS_EX_ONECLICKACTIVATE) ||
|
|
(plv->exStyle & LVS_EX_TWOCLICKACTIVATE)) // We only change visuals for hot with Underline
|
|
{
|
|
BOOL fSelectOnly;
|
|
UINT uInvalidateFlags = RDW_INVALIDATE;
|
|
BOOL fBlended = FALSE;
|
|
|
|
// Check to see if the item we are making not is in a blended state
|
|
if (iItem != -1)
|
|
{
|
|
// Cut is blended so we need to erase...
|
|
fBlended = ListView_OnGetItemState(plv, iItem, LVIS_CUT);
|
|
if (!fBlended)
|
|
fBlended = ListView_OnGetItemState(plv, iItem, LVIS_SELECTED);
|
|
}
|
|
|
|
// If we need to erase either one then we erase both.
|
|
if (plv->iHot != -1 && ListView_IsValidItemNumber(plv, plv->iHot) && !fBlended)
|
|
{
|
|
// Cut is blended so we need to erase...
|
|
fBlended = ListView_OnGetItemState(plv, plv->iHot, LVIS_CUT);
|
|
if (!fBlended)
|
|
fBlended = ListView_OnGetItemState(plv, plv->iHot, LVIS_SELECTED);
|
|
}
|
|
|
|
if (ImageList_GetFlags(plv->himl) & ILC_COLOR32)
|
|
fBlended = TRUE;
|
|
|
|
// Affects only apply if double buffering
|
|
if (ListView_IsDoubleBuffer(plv) ||
|
|
plv->fListviewShadowText ||
|
|
fBlended)
|
|
{
|
|
uInvalidateFlags |= RDW_ERASE;
|
|
}
|
|
|
|
fSelectOnly = ListView_FullRowSelect(plv);
|
|
|
|
|
|
ListView_InvalidateItemEx(plv, plv->iHot, fSelectOnly, uInvalidateFlags, LVIF_TEXT | LVIF_IMAGE);
|
|
ListView_InvalidateItemEx(plv, iItem, fSelectOnly, uInvalidateFlags, LVIF_TEXT | LVIF_IMAGE);
|
|
}
|
|
plv->iHot = iItem;
|
|
}
|
|
}
|
|
|
|
|
|
BOOL fShouldFirstClickActivate()
|
|
{
|
|
static BOOL fInited = FALSE;
|
|
static BOOL fActivate = TRUE;
|
|
if (!fInited)
|
|
{
|
|
long cb = 0;
|
|
if (RegQueryValue(HKEY_CURRENT_USER, TEXT("Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\NoFirstClickActivate"),
|
|
NULL, &cb) == ERROR_SUCCESS)
|
|
{
|
|
fActivate = FALSE;
|
|
}
|
|
fInited = TRUE;
|
|
}
|
|
return fActivate;
|
|
}
|
|
|
|
BOOL ChildOfDesktop(HWND hwnd)
|
|
{
|
|
return IsChild(GetShellWindow(), hwnd);
|
|
}
|
|
|
|
|
|
void ListView_OnMouseMove(LV* plv, int x, int y, UINT uFlags)
|
|
{
|
|
int iItem;
|
|
LV_HITTESTINFO ht;
|
|
NMLISTVIEW nm;
|
|
|
|
ht.pt.x = x;
|
|
ht.pt.y = y;
|
|
iItem = ListView_OnSubItemHitTest(plv, &ht);
|
|
if (ht.iSubItem != 0)
|
|
{
|
|
// if we're not in full row select,
|
|
// hitting on a subitem is like hitting on nowhere
|
|
// also, in win95, ownerdraw fixed effectively had full row select
|
|
if (!ListView_FullRowSelect(plv) &&
|
|
!(plv->ci.style & LVS_OWNERDRAWFIXED))
|
|
{
|
|
iItem = -1;
|
|
ht.flags = LVHT_NOWHERE;
|
|
}
|
|
}
|
|
|
|
if (ht.flags & LVHT_NOWHERE ||
|
|
ht.flags & LVHT_ONITEMSTATEICON)
|
|
{
|
|
iItem = -1; // this is possible in the list mode (sigh)
|
|
}
|
|
|
|
nm.iItem = iItem;
|
|
nm.iSubItem = ht.iSubItem;
|
|
nm.uChanged = 0;
|
|
nm.ptAction.x = x;
|
|
nm.ptAction.y = y;
|
|
|
|
if (!CCSendNotify(&plv->ci, LVN_HOTTRACK, &nm.hdr))
|
|
{
|
|
|
|
#ifdef DEBUG
|
|
if ((nm.iItem != -1) && nm.iSubItem != 0)
|
|
nm.iItem = -1;
|
|
#endif
|
|
|
|
ListView_OnSetHotItem(plv, nm.iItem);
|
|
// Ensure our cursor is correct now since the WM_SETCURSOR
|
|
// message was already generated for this mouse event.
|
|
ListView_OnSetCursorMsg(plv);
|
|
|
|
// this lets us know when we've left an item
|
|
// and can then reselect/toggle it on hover events
|
|
if (iItem != plv->iNoHover)
|
|
{
|
|
plv->iNoHover = -1;
|
|
}
|
|
}
|
|
}
|
|
|
|
BOOL EditBoxHasFocus()
|
|
{
|
|
HWND hwndFocus = GetFocus();
|
|
if (hwndFocus)
|
|
{
|
|
if (SendMessage(hwndFocus, WM_GETDLGCODE, 0, 0) & DLGC_HASSETSEL)
|
|
return TRUE;
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
void ListView_OnMouseHover(LV* plv, int x, int y, UINT uFlags)
|
|
{
|
|
int iItem;
|
|
BOOL bSelected;
|
|
LV_HITTESTINFO ht;
|
|
BOOL fControl;
|
|
BOOL fShift;
|
|
BOOL fNotifyReturn = FALSE;
|
|
|
|
if (GetCapture() || !ChildOfActiveWindow(plv->ci.hwnd) ||
|
|
EditBoxHasFocus())
|
|
return; // ignore hover while editing or any captured (d/d) operation
|
|
|
|
if (CCSendNotify(&plv->ci, NM_HOVER, NULL))
|
|
{
|
|
return;
|
|
}
|
|
|
|
// REVIEW: right button implies no shift or control stuff
|
|
// Single selection style also implies no modifiers
|
|
//if (RIGHTBUTTON(keyFlags) || (plv->ci.style & LVS_SINGLESEL))
|
|
if ((plv->ci.style & LVS_SINGLESEL))
|
|
{
|
|
fControl = FALSE;
|
|
fShift = FALSE;
|
|
}
|
|
else
|
|
{
|
|
fControl = GetAsyncKeyState(VK_CONTROL) < 0;
|
|
fShift = GetAsyncKeyState(VK_SHIFT) < 0;
|
|
}
|
|
|
|
ht.pt.x = x;
|
|
ht.pt.y = y;
|
|
iItem = ListView_OnHitTest(plv, &ht);
|
|
|
|
if (iItem == -1 ||
|
|
iItem == plv->iNoHover)
|
|
return;
|
|
|
|
//before we hover select we launch any pending item
|
|
//this prevents clicking on one item and hover selecting other before
|
|
//the timer goes off which result in wrong item being launched
|
|
if (plv->exStyle & LVS_EX_ONECLICKACTIVATE && plv->fOneClickHappened && plv->fOneClickOK)
|
|
{
|
|
HWND hwnd = plv->ci.hwnd;
|
|
|
|
KillTimer(plv->ci.hwnd, IDT_ONECLICKHAPPENED);
|
|
plv->fOneClickHappened = FALSE;
|
|
CCSendNotify(&plv->ci, LVN_ITEMACTIVATE, &(plv->nmOneClickHappened.hdr));
|
|
if (!IsWindow(hwnd))
|
|
return;
|
|
}
|
|
|
|
plv->iNoHover = iItem;
|
|
bSelected = ListView_OnGetItemState(plv, iItem, LVIS_SELECTED);
|
|
|
|
if (ht.flags & (LVHT_ONITEMLABEL | LVHT_ONITEMICON))
|
|
{
|
|
UINT keyFlags = 0;
|
|
|
|
if (fShift)
|
|
keyFlags |= MK_SHIFT;
|
|
if (fControl)
|
|
keyFlags |= MK_CONTROL;
|
|
|
|
if (!bSelected)
|
|
{
|
|
// if it wasn't selected, we're about to select it... play
|
|
// a little ditty for us...
|
|
CCPlaySound(c_szSelect);
|
|
}
|
|
|
|
ListView_ButtonSelect(plv, iItem, keyFlags, bSelected);
|
|
|
|
if (fControl)
|
|
{
|
|
ListView_SetFocusSel(plv, iItem, !fShift, FALSE, !fShift);
|
|
}
|
|
|
|
if (!fShift)
|
|
plv->iMark = iItem;
|
|
|
|
ListView_OnSetCursorMsg(plv);
|
|
|
|
SetFocus(plv->ci.hwnd); // activate this window
|
|
}
|
|
}
|
|
|
|
BOOL EqualRects(LPRECT prcNew, LPRECT prcOld, int nRects)
|
|
{
|
|
int i;
|
|
for (i = 0; i < nRects; i++)
|
|
if (!EqualRect(&prcNew[i], &prcOld[i]))
|
|
return FALSE;
|
|
return TRUE;
|
|
}
|
|
|
|
BOOL ListView_FindWorkArea(LV * plv, POINT pt, short * piWorkArea)
|
|
{
|
|
int iWork;
|
|
for (iWork = 0; iWork < plv->nWorkAreas; iWork++)
|
|
{
|
|
if (PtInRect(&plv->prcWorkAreas[iWork], pt))
|
|
{
|
|
*piWorkArea = (short)iWork;
|
|
return TRUE;
|
|
}
|
|
}
|
|
|
|
// (dli) default case is the primary work area
|
|
*piWorkArea = 0;
|
|
return FALSE;
|
|
}
|
|
|
|
void ListView_BullyIconsOnWorkarea(LV * plv, HDPA hdpaLostItems)
|
|
{
|
|
int ihdpa;
|
|
int iFree = -1; // the last free slot number
|
|
LVFAKEDRAW lvfd;
|
|
LV_ITEM item;
|
|
|
|
// Caller should've filtered this case out
|
|
ASSERT(DPA_GetPtrCount(hdpaLostItems) > 0);
|
|
|
|
// Set up in case caller is customdraw
|
|
ListView_BeginFakeCustomDraw(plv, &lvfd, &item);
|
|
item.mask = LVIF_PARAM;
|
|
item.iSubItem = 0;
|
|
|
|
// Go through my hdpa list of lost icons and try to place them within bound
|
|
for (ihdpa = 0; ihdpa < DPA_GetPtrCount(hdpaLostItems); ihdpa++)
|
|
{
|
|
POINT ptNew, pt;
|
|
RECT rcBound = {0};
|
|
int cxBound, cyBound;
|
|
int iWidth, iHeight;
|
|
int iItem;
|
|
LISTITEM * pitem;
|
|
|
|
iItem = PtrToUlong(DPA_GetPtr(hdpaLostItems, ihdpa));
|
|
pitem = ListView_FastGetItemPtr(plv, iItem);
|
|
pt = pitem->pt;
|
|
|
|
iWidth = RECTWIDTH(plv->prcWorkAreas[pitem->iWorkArea]);
|
|
iHeight = RECTHEIGHT(plv->prcWorkAreas[pitem->iWorkArea]);
|
|
|
|
ListView_GetRects(plv, iItem, QUERY_DEFAULT, NULL, NULL, &rcBound, NULL);
|
|
cxBound = RECTWIDTH(rcBound);
|
|
cyBound = RECTHEIGHT(rcBound);
|
|
|
|
pt.x -= plv->prcWorkAreas[pitem->iWorkArea].left;
|
|
pt.y -= plv->prcWorkAreas[pitem->iWorkArea].top;
|
|
|
|
if (pt.x < (-cxBound / 2))
|
|
{
|
|
ptNew.x = 0;
|
|
}
|
|
else if (pt.x > (iWidth - (cxBound / 2)))
|
|
{
|
|
ptNew.x = iWidth - cxBound;
|
|
}
|
|
else
|
|
ptNew.x = pt.x;
|
|
|
|
if (pt.y < (-cyBound/2))
|
|
{
|
|
ptNew.y = 0;
|
|
}
|
|
else if (pt.y > (iHeight - (cyBound / 2)))
|
|
{
|
|
ptNew.y = iHeight - cyBound;
|
|
}
|
|
else
|
|
ptNew.y = pt.y;
|
|
|
|
if ((ptNew.x != pt.x) || (ptNew.y != pt.y))
|
|
{
|
|
BOOL fUpdate;
|
|
RECT rcTest;
|
|
ptNew.x += plv->prcWorkAreas[pitem->iWorkArea].left;
|
|
ptNew.y += plv->prcWorkAreas[pitem->iWorkArea].top;
|
|
|
|
// See if the potential rectangle intersects other items.
|
|
rcTest.left = ptNew.x - plv->ptOrigin.x;
|
|
rcTest.top = ptNew.y - plv->ptOrigin.y;
|
|
rcTest.right = rcTest.left + cxBound;
|
|
rcTest.bottom = rcTest.top + cyBound;
|
|
|
|
item.iItem = iItem;
|
|
item.lParam = pitem->lParam;
|
|
ListView_BeginFakeItemDraw(&lvfd);
|
|
|
|
if (!ListView_IsCleanRect(plv, &rcTest, iItem, QUERY_DEFAULT, &fUpdate, lvfd.nmcd.nmcd.hdc))
|
|
{
|
|
// doh! We hit another item, let's try to find an available location
|
|
// for this item
|
|
BOOL fUpdateSB;
|
|
BOOL fAppendAtEnd = FALSE;
|
|
int iWidth, iHeight;
|
|
int cSlots = ListView_GetSlotCountEx(plv, FALSE, pitem->iWorkArea, &iWidth, &iHeight);
|
|
iFree = ListView_FindFreeSlot(plv, iItem, iFree + 1, cSlots, QUERY_DEFAULT, &fUpdateSB, &fAppendAtEnd, lvfd.nmcd.nmcd.hdc, iWidth, iHeight);
|
|
if (iFree == -1)
|
|
goto SetFirstGuess;
|
|
ListView_SetIconPos(plv, pitem, iFree, cSlots);
|
|
ListView_EndFakeItemDraw(&lvfd);
|
|
continue;
|
|
}
|
|
SetFirstGuess:
|
|
ListView_EndFakeItemDraw(&lvfd);
|
|
ListView_OnSetItemPosition(plv, iItem, ptNew.x, ptNew.y);
|
|
}
|
|
}
|
|
ListView_EndFakeCustomDraw(&lvfd);
|
|
}
|
|
|
|
#define DPA_LAST 0x7fffffff
|
|
|
|
//
|
|
// ListView_OnSetWorkAreas
|
|
//
|
|
// set the "work areas" for the list view.
|
|
// the "work areas" are a group of sub rectanges of the list view client rect
|
|
// where icons are aranged, and parked by default.
|
|
//
|
|
void ListView_OnSetWorkAreas(LV* plv, int nWorkAreas, LPRECT prc)
|
|
{
|
|
int iWork;
|
|
HDPA hdpaLostItems = NULL;
|
|
RECT rcOldWorkAreas[LV_MAX_WORKAREAS];
|
|
|
|
BOOL bAutoArrange = plv->ci.style & LVS_AUTOARRANGE;
|
|
int nOldWorkAreas = plv->nWorkAreas;
|
|
|
|
if (nOldWorkAreas > 0)
|
|
{
|
|
ASSERT(plv->prcWorkAreas != NULL);
|
|
memcpy(&rcOldWorkAreas[0], &plv->prcWorkAreas[0], sizeof(RECT) * nOldWorkAreas);
|
|
}
|
|
// for the mirrored case, the coordinates are reversed. IsRectEmpty() will always succeed
|
|
if (nWorkAreas == 0 || prc == NULL || ((IsRectEmpty(prc)) && !(plv->ci.dwExStyle & RTL_MIRRORED_WINDOW)))
|
|
plv->nWorkAreas = 0;
|
|
else
|
|
{
|
|
plv->nWorkAreas = min(nWorkAreas, LV_MAX_WORKAREAS);
|
|
|
|
if (plv->prcWorkAreas == NULL)
|
|
plv->prcWorkAreas = (LPRECT)LocalAlloc(LPTR, sizeof(RECT) * LV_MAX_WORKAREAS);
|
|
|
|
if (plv->prcWorkAreas == NULL)
|
|
return;
|
|
|
|
//Should we check if they intersect? This problem is sort of
|
|
// solved (or made more confusing) by ListView_GetFreeSlot since it checks all of the icons for
|
|
// intersection instead of just the ones in the workarea.
|
|
for (iWork = 0; iWork < plv->nWorkAreas; iWork++)
|
|
CopyRect(&plv->prcWorkAreas[iWork], &prc[iWork]);
|
|
}
|
|
|
|
// We don't support workareas for owner-data because our icon placement
|
|
// algorithm (ListView_IGetRectsOwnerData) completely ignores workareas
|
|
// and just dumps the icons in a rectangular array starting at (0,0).
|
|
if (!ListView_IsOwnerData(plv) &&
|
|
plv->nWorkAreas > 0 &&
|
|
((plv->nWorkAreas != nOldWorkAreas) ||
|
|
(!EqualRects(&plv->prcWorkAreas[0], &rcOldWorkAreas[0], nOldWorkAreas))))
|
|
{
|
|
int iItem;
|
|
LISTITEM * pitem;
|
|
|
|
//
|
|
// Subtle - ListView_Recompute cleans up all the RECOMPUTE icons,
|
|
// but in order to do that, it needs to have valid work area
|
|
// rectangles. So the call must happen after the CopyRect but before
|
|
// the loop that checks the icon positions.
|
|
//
|
|
ListView_Recompute(plv);
|
|
|
|
for (iItem = 0; iItem < ListView_Count(plv); iItem++)
|
|
{
|
|
pitem = ListView_FastGetItemPtr(plv, iItem);
|
|
|
|
if (pitem->pt.x == RECOMPUTE || pitem->pt.y == RECOMPUTE)
|
|
{
|
|
// ListView_Recompute should've fixed these if we were in
|
|
// an iconical view.
|
|
ASSERT(!(ListView_IsIconView(plv) || ListView_IsSmallView(plv)));
|
|
continue;
|
|
}
|
|
|
|
// Try to move me to the same location relative to the same workarea.
|
|
// This will give the cool shift effect when tools bars take the border areas.
|
|
// And we only want to do this for the workareas that changed
|
|
|
|
// Don't bully the icons on the workareas, Autoarrange will do the work for us
|
|
|
|
if (nOldWorkAreas > 0)
|
|
{
|
|
int iOldWorkArea;
|
|
iOldWorkArea = pitem->iWorkArea;
|
|
if (iOldWorkArea >= plv->nWorkAreas)
|
|
{
|
|
// My workarea is gone, put me on the primary workarea i.e. #0
|
|
pitem->iWorkArea = 0;
|
|
if (!bAutoArrange)
|
|
{
|
|
// If this item point location is already in the new primary workarea,
|
|
// move it out, and let ListView_BullyIconsOnWorkarea arrange it to the
|
|
// right place. NOTE: this could happen in the case the old secondary monitor
|
|
// is to the left of the old primary monitor, and user kills the secondary monitor
|
|
if (PtInRect(&plv->prcWorkAreas[0], pitem->pt))
|
|
{
|
|
pitem->pt.x = plv->prcWorkAreas[0].right + 1;
|
|
plv->iFreeSlot = -1; // an item moved -- old slot info is invalid
|
|
}
|
|
goto InsertLostItemsArray;
|
|
}
|
|
}
|
|
else if ((!bAutoArrange) && (!EqualRect(&plv->prcWorkAreas[iOldWorkArea], &rcOldWorkAreas[iOldWorkArea])))
|
|
{
|
|
RECT rcBound = {0};
|
|
POINT ptCenter;
|
|
pitem->pt.x += plv->prcWorkAreas[iOldWorkArea].left - rcOldWorkAreas[iOldWorkArea].left;
|
|
pitem->pt.y += plv->prcWorkAreas[iOldWorkArea].top - rcOldWorkAreas[iOldWorkArea].top;
|
|
|
|
// Use the center of this icon to determine whether it's out of bound
|
|
ListView_GetRects(plv, iItem, QUERY_DEFAULT, NULL, NULL, &rcBound, NULL);
|
|
ptCenter.x = pitem->pt.x + RECTWIDTH(rcBound) / 2;
|
|
ptCenter.y = pitem->pt.y + RECTHEIGHT(rcBound) / 2;
|
|
|
|
// If this shifted me out of bounds, register to be bullied on the workarea
|
|
if (!PtInRect(&plv->prcWorkAreas[iOldWorkArea], ptCenter))
|
|
{
|
|
InsertLostItemsArray:
|
|
if (!hdpaLostItems)
|
|
{
|
|
hdpaLostItems = DPA_Create(4);
|
|
}
|
|
|
|
if (hdpaLostItems)
|
|
DPA_InsertPtr(hdpaLostItems, DPA_LAST, IntToPtr(iItem));
|
|
}
|
|
}
|
|
|
|
}
|
|
else
|
|
{
|
|
// My first time in a multi-workarea system, so find out my workarea
|
|
if (!ListView_FindWorkArea(plv, pitem->pt, &(pitem->iWorkArea)) && !bAutoArrange)
|
|
goto InsertLostItemsArray;
|
|
}
|
|
|
|
if ((plv->exStyle & LVS_EX_REGIONAL) && (pitem->hrgnIcon))
|
|
{
|
|
if (pitem->hrgnIcon != (HANDLE)-1)
|
|
DeleteObject(pitem->hrgnIcon);
|
|
pitem->hrgnIcon = NULL;
|
|
}
|
|
}
|
|
|
|
if (hdpaLostItems)
|
|
{
|
|
ASSERT(!bAutoArrange);
|
|
if (DPA_GetPtrCount(hdpaLostItems) > 0)
|
|
ListView_BullyIconsOnWorkarea(plv, hdpaLostItems);
|
|
|
|
DPA_Destroy(hdpaLostItems);
|
|
}
|
|
|
|
if (plv->exStyle & LVS_EX_REGIONAL)
|
|
ListView_RecalcRegion(plv, TRUE, TRUE);
|
|
|
|
if (ListView_IsSmallView(plv) || ListView_IsIconView(plv))
|
|
ListView_ArrangeOrSnapToGrid(plv);
|
|
}
|
|
|
|
RedrawWindow(plv->ci.hwnd, NULL, NULL, RDW_INVALIDATE | RDW_ERASE);
|
|
}
|
|
|
|
void ListView_OnGetNumberOfWorkAreas(LV* plv, int * pnWorkAreas)
|
|
{
|
|
if (pnWorkAreas)
|
|
*pnWorkAreas = plv->nWorkAreas;
|
|
}
|
|
|
|
void ListView_OnGetWorkAreas(LV* plv, int nWorkAreas, LPRECT prc)
|
|
{
|
|
if (prc != NULL)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < min(plv->nWorkAreas, nWorkAreas); i++)
|
|
{
|
|
if (i < plv->nWorkAreas)
|
|
{
|
|
CopyRect(&prc[i], &plv->prcWorkAreas[i]);
|
|
}
|
|
else
|
|
{
|
|
// Set the workareas to all zeros if we don't have it.
|
|
ZeroMemory(&prc[i], sizeof(RECT));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// test an item to see if it is unfolded (because it is focused)
|
|
|
|
BOOL ListView_IsItemUnfolded(LV *plv, int item)
|
|
{
|
|
return plv && (item >= 0) && ListView_IsIconView(plv) &&
|
|
(plv->flags & LVF_UNFOLDED) && (plv->iFocus == item);
|
|
}
|
|
|
|
BOOL ListView_IsItemUnfoldedPtr(LV *plv, LISTITEM *pitem)
|
|
{
|
|
return plv && pitem && ListView_IsIconView(plv) &&
|
|
(plv->flags & LVF_UNFOLDED) && (pitem->state & LVIS_FOCUSED);
|
|
}
|
|
|
|
// Returns TRUE if unfolding the item will be worthwhile
|
|
BOOL ListView_GetUnfoldedRect(LV* plv, int iItem, RECT *prc)
|
|
{
|
|
ListView_GetRects(plv, iItem, QUERY_DEFAULT, NULL, prc, NULL, NULL);
|
|
return ListView_UnfoldRects(plv, iItem, NULL, prc, NULL, NULL);
|
|
}
|
|
|
|
BOOL ListView_OnSetGroupInfoInternal(LV* plv, PLVGROUP plvgrp, LISTGROUP* pgrp)
|
|
{
|
|
if (plvgrp == NULL ||
|
|
plvgrp->cbSize < sizeof(LVGROUP))
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
if (plvgrp->mask & LVGF_STATE)
|
|
{
|
|
if ((plvgrp->state & LVGS_MASK) != plvgrp->state)
|
|
return FALSE;
|
|
|
|
pgrp->state = plvgrp->state;
|
|
}
|
|
|
|
if (plvgrp->mask & LVGF_ALIGN)
|
|
{
|
|
if ((plvgrp->uAlign & LVGA_ALIGN_MASK) != plvgrp->uAlign)
|
|
return FALSE;
|
|
|
|
pgrp->uAlign = plvgrp->uAlign;
|
|
}
|
|
else
|
|
{
|
|
pgrp->uAlign = LVGA_HEADER_LEFT;
|
|
|
|
}
|
|
|
|
if (ListView_VerifyGroupIdIsUnique(plv, plvgrp->iGroupId))
|
|
{
|
|
pgrp->iGroupId = plvgrp->iGroupId;
|
|
}
|
|
else
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
if (plvgrp->mask & LVGF_HEADER)
|
|
{
|
|
Str_SetPtr(&pgrp->pszHeader, plvgrp->pszHeader);
|
|
}
|
|
|
|
if (plvgrp->mask & LVGF_FOOTER)
|
|
{
|
|
Str_SetPtr(&pgrp->pszFooter, plvgrp->pszFooter);
|
|
}
|
|
|
|
// Update the group.
|
|
InvalidateRect(plv->ci.hwnd, &pgrp->rc, TRUE);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
int ListView_OnSetGroupInfo(LV* plv, int iGroupId, PLVGROUP plvgrp)
|
|
{
|
|
LISTGROUP* pgrp = ListView_FindGroupFromID(plv, iGroupId, NULL);
|
|
if (pgrp)
|
|
{
|
|
ListView_OnSetGroupInfoInternal(plv, plvgrp, pgrp);
|
|
return iGroupId;
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
int ListView_OnGetGroupInfo(LV* plv, int iGroupId, PLVGROUP plvgrp)
|
|
{
|
|
LISTGROUP* pgrp = ListView_FindGroupFromID(plv, iGroupId, NULL);
|
|
|
|
if (plvgrp != NULL &&
|
|
plvgrp->cbSize >= sizeof(LVGROUP) &&
|
|
pgrp)
|
|
{
|
|
if (plvgrp->mask & LVGF_HEADER)
|
|
{
|
|
plvgrp->pszHeader = pgrp->pszHeader;
|
|
}
|
|
|
|
if (plvgrp->mask & LVGF_FOOTER)
|
|
{
|
|
plvgrp->pszFooter = pgrp->pszFooter;
|
|
}
|
|
|
|
if (plvgrp->mask & LVGF_STATE)
|
|
{
|
|
plvgrp->state = pgrp->state & plvgrp->stateMask;
|
|
}
|
|
|
|
if (plvgrp->mask & LVGF_ALIGN)
|
|
{
|
|
plvgrp->uAlign = pgrp->uAlign;
|
|
}
|
|
|
|
if (plvgrp->mask & LVGF_GROUPID)
|
|
{
|
|
plvgrp->iGroupId = pgrp->iGroupId;
|
|
}
|
|
|
|
return iGroupId;
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
LISTGROUP* ListView_CreateGroup(LV* plv, PLVGROUP plvgrp)
|
|
{
|
|
LISTGROUP* pgrp;
|
|
|
|
// Validate Group
|
|
if (plvgrp == NULL ||
|
|
plvgrp->cbSize < sizeof(LVGROUP))
|
|
{
|
|
return NULL;
|
|
}
|
|
|
|
if (!(plvgrp->mask & LVGF_GROUPID))
|
|
{
|
|
// Have to have a group id...
|
|
return NULL;
|
|
}
|
|
|
|
pgrp = LocalAlloc(LPTR, sizeof(LISTGROUP));
|
|
if (pgrp)
|
|
{
|
|
if (!ListView_OnSetGroupInfoInternal(plv, plvgrp, pgrp))
|
|
{
|
|
LocalFree(pgrp);
|
|
return NULL;
|
|
}
|
|
|
|
pgrp->hdpa = DPA_Create(5);
|
|
SetRect(&pgrp->rc, 0, 0, 0, 0);
|
|
}
|
|
return pgrp;
|
|
}
|
|
|
|
void ListView_FreeGroupItem(LISTGROUP* pgrp)
|
|
{
|
|
DPA_Destroy(pgrp->hdpa);
|
|
Str_SetPtr(&pgrp->pszFooter, NULL);
|
|
Str_SetPtr(&pgrp->pszHeader, NULL);
|
|
LocalFree(pgrp);
|
|
}
|
|
|
|
LISTGROUP* ListView_FindFirstVisibleGroup(LV* plv)
|
|
{
|
|
LISTGROUP* pgrp = NULL;
|
|
int iGroup;
|
|
int cGroups = DPA_GetPtrCount(plv->hdpaGroups);
|
|
|
|
// Find the first group with an item in it.
|
|
for (iGroup = 0; iGroup < cGroups; iGroup++)
|
|
{
|
|
pgrp = DPA_FastGetPtr(plv->hdpaGroups, iGroup);
|
|
if (DPA_GetPtrCount(pgrp->hdpa) > 0)
|
|
break;
|
|
}
|
|
|
|
return pgrp;
|
|
}
|
|
|
|
|
|
LRESULT ListView_OnInsertGroup(LV* plv, int iGroup, PLVGROUP plvgrp)
|
|
{
|
|
int iInsert = -1;
|
|
LISTGROUP* pgrp = ListView_CreateGroup(plv, plvgrp);
|
|
|
|
if (!pgrp)
|
|
{
|
|
return -1;
|
|
}
|
|
|
|
if (iGroup == -1)
|
|
{
|
|
iGroup = DA_LAST;
|
|
}
|
|
|
|
if (plv->hdpaGroups == NULL)
|
|
plv->hdpaGroups = DPA_Create(4);
|
|
|
|
if (plv->hdpaGroups)
|
|
iInsert = DPA_InsertPtr(plv->hdpaGroups, iGroup, pgrp);
|
|
|
|
if (iInsert == -1)
|
|
{
|
|
ListView_FreeGroupItem(pgrp);
|
|
}
|
|
|
|
plv->rcView.left = RECOMPUTE;
|
|
InvalidateRect(plv->ci.hwnd, NULL, TRUE);
|
|
|
|
return iInsert;
|
|
}
|
|
|
|
|
|
LRESULT ListView_OnRemoveGroup(LV* plv, int iGroupId)
|
|
{
|
|
int iIndex;
|
|
LISTGROUP* pgrp = ListView_FindGroupFromID(plv, iGroupId, &iIndex);
|
|
if (pgrp)
|
|
{
|
|
int cItems = DPA_GetPtrCount(pgrp->hdpa);
|
|
int iItem;
|
|
for (iItem = 0; iItem < cItems; iItem++)
|
|
{
|
|
LISTITEM* pitem = DPA_FastGetPtr(pgrp->hdpa, iItem);
|
|
if (pitem)
|
|
{
|
|
LISTITEM_SETHASNOTASKEDFORGROUP(pitem);
|
|
}
|
|
}
|
|
|
|
ListView_FreeGroupItem(pgrp);
|
|
|
|
DPA_DeletePtr(plv->hdpaGroups, iIndex);
|
|
plv->rcView.left = RECOMPUTE;
|
|
InvalidateRect(plv->ci.hwnd, NULL, TRUE);
|
|
|
|
return iIndex;
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
int CALLBACK DestroyGroups(void* pv, void* pvData)
|
|
{
|
|
LISTGROUP* pgrp = (LISTGROUP*)pv;
|
|
ListView_FreeGroupItem(pgrp);
|
|
return 1;
|
|
}
|
|
|
|
LRESULT ListView_OnRemoveAllGroups(LV* plv)
|
|
{
|
|
if (!ListView_IsOwnerData(plv) && plv->hdpaGroups)
|
|
{
|
|
int i;
|
|
int cItems = ListView_Count(plv);
|
|
|
|
plv->fGroupView = FALSE;
|
|
|
|
for (i = 0; i < cItems; i++)
|
|
{
|
|
LISTITEM* pitem = DPA_FastGetPtr(plv->hdpa, i);
|
|
if (pitem)
|
|
LISTITEM_SETHASNOTASKEDFORGROUP(pitem);
|
|
}
|
|
|
|
|
|
DPA_DestroyCallback(plv->hdpaGroups, DestroyGroups, NULL);
|
|
plv->hdpaGroups = NULL;
|
|
|
|
plv->rcView.left = RECOMPUTE;
|
|
InvalidateRect(plv->ci.hwnd, NULL, TRUE);
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
|
|
LRESULT ListView_OnSetGroupMetrics(LV* plv, PLVGROUPMETRICS pgm)
|
|
{
|
|
BOOL fRecompute = FALSE;
|
|
|
|
if (pgm->mask & LVGMF_BORDERSIZE)
|
|
{
|
|
plv->rcBorder.left = pgm->Left;
|
|
plv->rcBorder.top = pgm->Top;
|
|
plv->rcBorder.right = pgm->Right;
|
|
plv->rcBorder.bottom = pgm->Bottom;
|
|
fRecompute = TRUE;
|
|
}
|
|
|
|
if (pgm->mask & LVGMF_BORDERCOLOR)
|
|
{
|
|
plv->crTop = pgm->crTop;
|
|
plv->crLeft = pgm->crLeft;
|
|
plv->crRight = pgm->crRight;
|
|
plv->crBottom = pgm->crBottom;
|
|
}
|
|
|
|
if (pgm->mask & LVGMF_TEXTCOLOR)
|
|
{
|
|
plv->crHeader = pgm->crHeader;
|
|
plv->crFooter = pgm->crFooter;
|
|
}
|
|
|
|
if (fRecompute)
|
|
{
|
|
plv->rcView.left = RECOMPUTE;
|
|
ListView_Recompute(plv);
|
|
ListView_UpdateScrollBars(plv);
|
|
}
|
|
InvalidateRect(plv->ci.hwnd, NULL, TRUE);
|
|
|
|
return 1;
|
|
}
|
|
|
|
LRESULT ListView_OnGetGroupMetrics(LV* plv, PLVGROUPMETRICS pgm)
|
|
{
|
|
if (pgm->mask & LVGMF_BORDERSIZE)
|
|
{
|
|
pgm->Left = plv->rcBorder.left;
|
|
pgm->Top = plv->rcBorder.top;
|
|
pgm->Right = plv->rcBorder.right;
|
|
pgm->Bottom = plv->rcBorder.bottom;
|
|
}
|
|
|
|
if (pgm->mask & LVGMF_BORDERCOLOR)
|
|
{
|
|
pgm->crTop = plv->crTop;
|
|
pgm->crLeft = plv->crLeft;
|
|
pgm->crRight = plv->crRight;
|
|
pgm->crBottom = plv->crBottom;
|
|
}
|
|
|
|
if (pgm->mask & LVGMF_TEXTCOLOR)
|
|
{
|
|
pgm->crHeader = plv->crHeader;
|
|
pgm->crFooter= plv->crFooter;
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
typedef struct
|
|
{
|
|
PFNLVGROUPCOMPARE pfnCompare;
|
|
void * pvData;
|
|
} SORTGROUPDATA;
|
|
|
|
int CALLBACK pfnGroupSort(LPARAM one, LPARAM two, LPARAM pvData)
|
|
{
|
|
SORTGROUPDATA* psg = (SORTGROUPDATA*)pvData;
|
|
LISTGROUP* pgrp1 = (LISTGROUP*)one;
|
|
LISTGROUP* pgrp2 = (LISTGROUP*)two;
|
|
|
|
if (!one)
|
|
return 1;
|
|
|
|
if (!two)
|
|
return -1;
|
|
|
|
return psg->pfnCompare(pgrp1->iGroupId, pgrp2->iGroupId, psg->pvData);
|
|
}
|
|
|
|
LRESULT ListView_OnSortGroups(LV* plv, PFNLVGROUPCOMPARE pfnCompare, void * pvData)
|
|
{
|
|
if (plv->hdpaGroups)
|
|
{
|
|
SORTGROUPDATA sg;
|
|
sg.pfnCompare = pfnCompare;
|
|
sg.pvData = pvData;
|
|
DPA_Sort(plv->hdpaGroups, (PFNDPACOMPARE)pfnGroupSort, (LPARAM)&sg);
|
|
|
|
plv->rcView.left = RECOMPUTE;
|
|
ListView_Recompute(plv);
|
|
InvalidateRect(plv->ci.hwnd, NULL, TRUE);
|
|
ListView_UpdateScrollBars(plv);
|
|
|
|
return 1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
LRESULT ListView_OnInsertGroupSorted(LV* plv, LVINSERTGROUPSORTED* pinsert)
|
|
{
|
|
int iInsertIndex = -1;
|
|
SORTGROUPDATA sg;
|
|
LISTGROUP* pgrp = ListView_CreateGroup(plv, &pinsert->lvGroup);
|
|
|
|
sg.pfnCompare = pinsert->pfnGroupCompare;
|
|
sg.pvData = pinsert->pvData;
|
|
|
|
if (pgrp)
|
|
{
|
|
if (plv->hdpaGroups == NULL)
|
|
plv->hdpaGroups = DPA_Create(4);
|
|
|
|
if (plv->hdpaGroups)
|
|
{
|
|
iInsertIndex = DPA_SortedInsertPtr(plv->hdpaGroups, pgrp, 0, (PFNDPACOMPARE)pfnGroupSort,
|
|
(LPARAM)&sg, DPAS_INSERTAFTER, pgrp);
|
|
}
|
|
if (iInsertIndex == -1)
|
|
ListView_FreeGroupItem(pgrp);
|
|
|
|
plv->rcView.left = RECOMPUTE;
|
|
InvalidateRect(plv->ci.hwnd, NULL, TRUE);
|
|
}
|
|
|
|
return iInsertIndex;
|
|
}
|
|
|
|
BOOL ListView_OnSetTileViewInfo(LV* plv, PLVTILEVIEWINFO pTileViewInfo)
|
|
{
|
|
BOOL bRecompute = FALSE;
|
|
if (!pTileViewInfo || (pTileViewInfo->cbSize != sizeof(LVTILEVIEWINFO)))
|
|
return FALSE;
|
|
|
|
if (pTileViewInfo->dwMask & LVTVIM_COLUMNS)
|
|
{
|
|
if (plv->cSubItems != pTileViewInfo->cLines)
|
|
{
|
|
bRecompute = TRUE;
|
|
plv->cSubItems = pTileViewInfo->cLines;
|
|
}
|
|
}
|
|
|
|
if (pTileViewInfo->dwMask & LVTVIM_TILESIZE)
|
|
{
|
|
DWORD dwTileFlags = pTileViewInfo->dwFlags & (LVTVIF_FIXEDHEIGHT | LVTVIF_FIXEDWIDTH);
|
|
|
|
if (plv->dwTileFlags != dwTileFlags)
|
|
{
|
|
plv->dwTileFlags = dwTileFlags;
|
|
bRecompute = TRUE;
|
|
}
|
|
|
|
if (ListView_IsDPIScaled(plv))
|
|
{
|
|
CCDPIScaleX(&pTileViewInfo->sizeTile.cx);
|
|
CCDPIScaleY(&pTileViewInfo->sizeTile.cy);
|
|
}
|
|
|
|
|
|
if ((plv->dwTileFlags & LVTVIF_FIXEDHEIGHT) &&
|
|
plv->sizeTile.cy != pTileViewInfo->sizeTile.cy)
|
|
{
|
|
plv->sizeTile.cy = pTileViewInfo->sizeTile.cy;
|
|
bRecompute = TRUE;
|
|
}
|
|
|
|
if ((plv->dwTileFlags & LVTVIF_FIXEDWIDTH) &&
|
|
plv->sizeTile.cx != pTileViewInfo->sizeTile.cx)
|
|
{
|
|
plv->sizeTile.cx = pTileViewInfo->sizeTile.cx;
|
|
bRecompute = TRUE;
|
|
}
|
|
|
|
}
|
|
|
|
if ((pTileViewInfo->dwMask & LVTVIM_LABELMARGIN) &&
|
|
!EqualRect(&plv->rcTileLabelMargin, &pTileViewInfo->rcLabelMargin))
|
|
{
|
|
plv->rcTileLabelMargin = pTileViewInfo->rcLabelMargin;
|
|
bRecompute = TRUE;
|
|
}
|
|
|
|
if (bRecompute)
|
|
{
|
|
ListView_RecalcTileSize(plv);
|
|
plv->rcView.left = RECOMPUTE;
|
|
ListView_Recompute(plv);
|
|
InvalidateRect(plv->ci.hwnd, NULL, TRUE);
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
BOOL ListView_OnGetTileViewInfo(LV* plv, PLVTILEVIEWINFO pTileViewInfo)
|
|
{
|
|
if (!pTileViewInfo || (pTileViewInfo->cbSize != sizeof(LVTILEVIEWINFO)))
|
|
return FALSE;
|
|
|
|
if (pTileViewInfo->dwMask & LVTVIM_COLUMNS)
|
|
{
|
|
pTileViewInfo->cLines = plv->cSubItems;
|
|
}
|
|
|
|
if (pTileViewInfo->dwMask & LVTVIM_TILESIZE)
|
|
{
|
|
pTileViewInfo->dwFlags = plv->dwTileFlags;
|
|
pTileViewInfo->sizeTile = plv->sizeTile;
|
|
}
|
|
|
|
if (pTileViewInfo->dwMask & LVTVIM_LABELMARGIN)
|
|
{
|
|
pTileViewInfo->rcLabelMargin = plv->rcTileLabelMargin;
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
BOOL ListView_OnSetTileInfo(LV* plv, PLVTILEINFO pTileInfo)
|
|
{
|
|
LVITEM lvi;
|
|
|
|
if (!pTileInfo || (pTileInfo->cbSize != sizeof(LVTILEINFO)))
|
|
return FALSE;
|
|
|
|
lvi.mask = LVIF_COLUMNS;
|
|
lvi.cColumns = pTileInfo->cColumns;
|
|
lvi.puColumns = pTileInfo->puColumns;
|
|
lvi.iSubItem = 0;
|
|
lvi.iItem = pTileInfo->iItem;
|
|
|
|
return ListView_OnSetItem(plv, &lvi);
|
|
}
|
|
|
|
BOOL ListView_OnGetTileInfo(LV* plv, PLVTILEINFO pTileInfo)
|
|
{
|
|
LVITEM lvi;
|
|
|
|
if (!pTileInfo || (pTileInfo->cbSize != sizeof(LVTILEINFO)))
|
|
return FALSE;
|
|
|
|
lvi.mask = LVIF_COLUMNS;
|
|
lvi.iSubItem = 0;
|
|
lvi.iItem = pTileInfo->iItem;
|
|
lvi.cColumns = pTileInfo->cColumns;
|
|
lvi.puColumns = pTileInfo->puColumns;
|
|
|
|
if (ListView_OnGetItem(plv, &lvi))
|
|
{
|
|
pTileInfo->cColumns = lvi.cColumns;
|
|
return TRUE;
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
LRESULT ListView_OnSetInsertMark(LV* plv, LPLVINSERTMARK plvim)
|
|
{
|
|
if (plvim->cbSize != sizeof(LVINSERTMARK))
|
|
return 0;
|
|
|
|
if (plvim->iItem != plv->iInsertItem ||
|
|
BOOLIFY(plv->fInsertAfter) != BOOLIFY(plvim->dwFlags & LVIM_AFTER))
|
|
{
|
|
if (plv->iInsertItem != -1)
|
|
ListView_InvalidateMark(plv);
|
|
|
|
plv->iInsertItem = plvim->iItem;
|
|
plv->fInsertAfter = BOOLIFY(plvim->dwFlags & LVIM_AFTER);
|
|
|
|
if (plv->iInsertItem != -1)
|
|
ListView_InvalidateMark(plv);
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
LRESULT ListView_OnSetInfoTip(LV *plv, PLVSETINFOTIP plvSetInfoTip)
|
|
{
|
|
LPWSTR pszText = NULL;
|
|
LPWSTR pszProduced = NULL;
|
|
LRESULT lRet = 0;
|
|
|
|
// Check size and flags. MBZ for now.
|
|
if (plvSetInfoTip->cbSize == sizeof(LVSETINFOTIP) &&
|
|
plvSetInfoTip->dwFlags == 0 &&
|
|
plvSetInfoTip->pszText != NULL)
|
|
{
|
|
pszText = plvSetInfoTip->pszText;
|
|
|
|
// If we are still looking at the same item, then set its text, and pop up the tip.
|
|
if (plvSetInfoTip->iItem == plv->iTTLastHit && plvSetInfoTip->iSubItem == plv->iTTLastSubHit)
|
|
{
|
|
TCHAR szBuf[INFOTIPSIZE];
|
|
BOOL bItemUnfolded;
|
|
BOOL fInfoTip = FALSE;
|
|
szBuf[0] = 0;
|
|
|
|
// preload the default tip text for folded items.
|
|
bItemUnfolded = ListView_IsItemUnfolded2(plv, plv->iTTLastHit, plv->iTTLastSubHit, szBuf, ARRAYSIZE(szBuf));
|
|
|
|
if (ListView_IsInfoTip(plv) && plv->iTTLastSubHit == 0)
|
|
{
|
|
if (*pszText && lstrcmp(szBuf, pszText) != 0)
|
|
{
|
|
// App changed something - there is a real infotip
|
|
fInfoTip = TRUE;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
pszText = szBuf;
|
|
}
|
|
|
|
//
|
|
// Set the margins now before the TTN_SHOW because it will be too late then.
|
|
//
|
|
// We want fat margins if we're an infotip, thin margins if we're an
|
|
// in-place tooltip.
|
|
//
|
|
if (fInfoTip)
|
|
{
|
|
static const RECT rcMargin = {4, 4, 4, 4};
|
|
SendMessage(plv->hwndToolTips, TTM_SETMARGIN, 0, (LPARAM)&rcMargin);
|
|
CCSetInfoTipWidth(plv->ci.hwnd, plv->hwndToolTips);
|
|
plv->fPlaceTooltip = FALSE; // Set it to TRUE only if Unfolding tip is set
|
|
|
|
}
|
|
else
|
|
{
|
|
static const RECT rcMargin = {0, 0, 0, 0};
|
|
plv->fPlaceTooltip = TRUE;
|
|
SendMessage(plv->hwndToolTips, TTM_SETMARGIN, 0, (LPARAM)&rcMargin);
|
|
CCResetInfoTipWidth(plv->ci.hwnd, plv->hwndToolTips);
|
|
}
|
|
|
|
Str_Set(&plv->pszTip, pszText);
|
|
|
|
// Re-display tooltip. If tracking, call tracking start code (same as timer code)
|
|
if (!ListView_IsKbdTipTracking(plv))
|
|
lRet = SendMessage(plv->hwndToolTips, TTM_POPUP, 0, 0);
|
|
else
|
|
ListView_OnTimer(plv, IDT_TRACKINGTIP);
|
|
}
|
|
|
|
if (pszProduced)
|
|
FreeProducedString(pszProduced);
|
|
}
|
|
return lRet;
|
|
}
|
|
|
|
LRESULT ListView_OnNotify(LV* plv, WPARAM wParam, LPNMHDR pnmh)
|
|
{
|
|
// we can't switch on the control ID because the tooltip is a WS_POPUP window
|
|
// and does not have a control ID. (header and tooltip both have 0 as ID)
|
|
|
|
if (plv->hwndHdr && (plv->hwndHdr == pnmh->hwndFrom))
|
|
{
|
|
// this is a notify for the header, deal with it as needed
|
|
|
|
return ListView_HeaderNotify(plv, (HD_NOTIFY *)pnmh);
|
|
}
|
|
else if (plv->hwndToolTips && (plv->hwndToolTips == pnmh->hwndFrom))
|
|
{
|
|
// implement unfolding the text for items as well as info tip support
|
|
|
|
switch (pnmh->code)
|
|
{
|
|
case TTN_NEEDTEXT:
|
|
{
|
|
POINT pt;
|
|
UINT uFlags;
|
|
int iNewHit;
|
|
int iNewSubHit;
|
|
NMTTDISPINFO *pttt = (NMTTDISPINFO *)pnmh;
|
|
|
|
// If keyboard tracking, do not hit test based on last cursor position
|
|
if (ListView_IsKbdTipTracking(plv))
|
|
{
|
|
RECT rcItem;
|
|
ListView_GetItemRect(plv->ci.hwnd, plv->iTracking, &rcItem, LVIR_LABEL);
|
|
|
|
pt.x = rcItem.left;
|
|
pt.y = rcItem.top;
|
|
}
|
|
else
|
|
GetMessagePosClient(plv->ci.hwnd, &pt);
|
|
|
|
iNewHit = _ListView_ItemHitTest(plv, pt.x, pt.y, &uFlags, &iNewSubHit);
|
|
|
|
if (iNewHit != plv->iTTLastHit || iNewSubHit != plv->iTTLastSubHit)
|
|
{
|
|
plv->fPlaceTooltip = FALSE; // Set it to TRUE only if Unfolding tip is set
|
|
Str_Set(&plv->pszTip, NULL); // clear the old tip
|
|
|
|
plv->iTTLastHit = iNewHit;
|
|
plv->iTTLastSubHit = iNewSubHit;
|
|
|
|
if ((iNewHit >= 0) && (plv->iEdit == -1))
|
|
{
|
|
TCHAR szBuf[INFOTIPSIZE], szBuf2[INFOTIPSIZE];
|
|
BOOL bItemUnfolded;
|
|
BOOL fInfoTip = FALSE;
|
|
LPTSTR pszTip = szBuf; // Use this one first
|
|
|
|
szBuf[0] = 0;
|
|
szBuf2[0] = 0;
|
|
|
|
// preload the tip text for folded items. this
|
|
// may be overridden by callback below
|
|
bItemUnfolded = ListView_IsItemUnfolded2(plv, plv->iTTLastHit, plv->iTTLastSubHit, szBuf, ARRAYSIZE(szBuf));
|
|
|
|
// Backup the unfolding text
|
|
StringCchCopy(szBuf2, ARRAYSIZE(szBuf2), szBuf);
|
|
|
|
if (ListView_IsInfoTip(plv) && iNewSubHit == 0)
|
|
{
|
|
NMLVGETINFOTIP git;
|
|
|
|
git.dwFlags = bItemUnfolded ? LVGIT_UNFOLDED : 0;
|
|
git.pszText = szBuf;
|
|
git.cchTextMax = ARRAYSIZE(szBuf);
|
|
git.iItem = plv->iTTLastHit;
|
|
git.iSubItem = 0;
|
|
git.lParam = 0;
|
|
|
|
// for folded items pszText is prepopulated with the
|
|
// item text, clients should append to this
|
|
|
|
CCSendNotify(&plv->ci, LVN_GETINFOTIP, &git.hdr);
|
|
|
|
if (*szBuf && lstrcmp(szBuf, szBuf2) != 0)
|
|
{
|
|
// App changed something - there is a real infotip
|
|
fInfoTip = TRUE;
|
|
}
|
|
|
|
}
|
|
|
|
//
|
|
// Set the margins now before the TTN_SHOW because it will be too late then.
|
|
//
|
|
// We want fat margins if we're an infotip, thin margins if we're an
|
|
// in-place tooltip.
|
|
//
|
|
if (fInfoTip)
|
|
{
|
|
static const RECT rcMargin = {4, 4, 4, 4};
|
|
SendMessage(plv->hwndToolTips, TTM_SETMARGIN, 0, (LPARAM)&rcMargin);
|
|
CCSetInfoTipWidth(plv->ci.hwnd, plv->hwndToolTips);
|
|
|
|
}
|
|
else
|
|
{
|
|
static const RECT rcMargin = {0, 0, 0, 0};
|
|
plv->fPlaceTooltip = TRUE;
|
|
SendMessage(plv->hwndToolTips, TTM_SETMARGIN, 0, (LPARAM)&rcMargin);
|
|
CCResetInfoTipWidth(plv->ci.hwnd, plv->hwndToolTips);
|
|
}
|
|
|
|
Str_Set(&plv->pszTip, pszTip);
|
|
}
|
|
}
|
|
|
|
pttt->lpszText = plv->pszTip; // here it is...
|
|
}
|
|
break;
|
|
|
|
// Handle custom draw as we want the tooltip painted as a multi-line that
|
|
// matches the formatting used by the list view.
|
|
|
|
case NM_CUSTOMDRAW:
|
|
{
|
|
LPNMTTCUSTOMDRAW pnm = (LPNMTTCUSTOMDRAW) pnmh;
|
|
|
|
if (plv->fPlaceTooltip &&
|
|
(pnm->nmcd.dwDrawStage == CDDS_PREPAINT ||
|
|
pnm->nmcd.dwDrawStage == CDDS_ITEMPREPAINT))
|
|
{
|
|
DWORD dwCustom = 0;
|
|
|
|
//
|
|
// Set up the customdraw DC to match the font of the LV item.
|
|
//
|
|
if (plv->iTTLastHit != -1)
|
|
{
|
|
LVFAKEDRAW lvfd;
|
|
LV_ITEM item;
|
|
ListView_BeginFakeCustomDraw(plv, &lvfd, &item);
|
|
|
|
item.iItem = plv->iTTLastHit;
|
|
item.iSubItem = plv->iTTLastSubHit;
|
|
item.mask = LVIF_PARAM;
|
|
ListView_OnGetItem(plv, &item);
|
|
dwCustom = ListView_BeginFakeItemDraw(&lvfd);
|
|
|
|
// If client changed the font, then transfer the font
|
|
// from our private hdc into the tooltip's HDC. We use
|
|
// a private HDC because we only want to let the app change
|
|
// the font, not the colors or anything else.
|
|
if (dwCustom & CDRF_NEWFONT)
|
|
{
|
|
SelectObject(pnm->nmcd.hdc, GetCurrentObject(lvfd.nmcd.nmcd.hdc, OBJ_FONT));
|
|
}
|
|
ListView_EndFakeItemDraw(&lvfd);
|
|
ListView_EndFakeCustomDraw(&lvfd);
|
|
|
|
}
|
|
|
|
//
|
|
// The Large Icon tooltip needs to be drawn specially.
|
|
//
|
|
if (ListView_IsIconView(plv))
|
|
{
|
|
pnm->uDrawFlags &= ~(DT_SINGLELINE|DT_LEFT);
|
|
pnm->uDrawFlags |= DT_CENTER|DT_LVWRAP;
|
|
|
|
if (pnm->uDrawFlags & DT_CALCRECT)
|
|
{
|
|
pnm->nmcd.rc.right = pnm->nmcd.rc.left + (plv->cxIconSpacing - g_cxLabelMargin * 2);
|
|
pnm->nmcd.rc.bottom = pnm->nmcd.rc.top + 0x10000; // big number, no limit!
|
|
}
|
|
}
|
|
|
|
// Don't return other wacky flags to TT, since all we
|
|
// did was change the font (if even that)
|
|
return dwCustom & CDRF_NEWFONT;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case TTN_SHOW:
|
|
if (plv->iTTLastHit != -1)
|
|
{
|
|
if (plv->fPlaceTooltip)
|
|
{
|
|
LPNMTTSHOWINFO psi = (LPNMTTSHOWINFO)pnmh;
|
|
RECT rcLabel;
|
|
|
|
// In case we're doing subitem hit-testing
|
|
rcLabel.top = plv->iTTLastSubHit;
|
|
rcLabel.left = LVIR_LABEL;
|
|
|
|
// reposition to allign with the text rect and
|
|
// set it to topmost
|
|
if (plv->iTTLastSubHit && ListView_OnGetSubItemRect(plv, plv->iTTLastHit, &rcLabel))
|
|
{
|
|
LV_ITEM item;
|
|
|
|
// we got the subitem rect. When we draw subitems, we give
|
|
// them SHDT_EXTRAMARGIN, so we have to also
|
|
rcLabel.left += g_cxLabelMargin * 3;
|
|
rcLabel.right -= g_cxLabelMargin * 3;
|
|
|
|
// And take the image into account, too.
|
|
// ListView_OnGetItem will worry about LVS_EX_SUBITEMIMAGES.
|
|
item.mask = LVIF_IMAGE;
|
|
item.iImage = -1;
|
|
item.iItem = plv->iTTLastHit;
|
|
item.iSubItem = plv->iTTLastSubHit;
|
|
ListView_OnGetItem(plv, &item);
|
|
if (item.iImage != -1)
|
|
rcLabel.left += plv->cxSmIcon;
|
|
}
|
|
else
|
|
{ // a tip from subitem zero
|
|
ListView_GetUnfoldedRect(plv, plv->iTTLastHit, &rcLabel);
|
|
// SHDrawText actually leaves a g_cxLabelMargin margin
|
|
rcLabel.left += g_cxLabelMargin;
|
|
rcLabel.right -= g_cxLabelMargin;
|
|
}
|
|
|
|
// In report and list views, SHDrawText does vertical
|
|
// centering (without consulting the custom-draw client,
|
|
// even, so it just centers by a random amount).
|
|
if (ListView_IsListView(plv) || ListView_IsReportView(plv))
|
|
{
|
|
rcLabel.top += (rcLabel.bottom - rcLabel.top - plv->cyLabelChar) / 2;
|
|
}
|
|
|
|
SendMessage(plv->hwndToolTips, TTM_ADJUSTRECT, TRUE, (LPARAM)&rcLabel);
|
|
MapWindowRect(plv->ci.hwnd, HWND_DESKTOP, &rcLabel);
|
|
|
|
if (!ListView_IsIconView(plv))
|
|
{
|
|
// In non-large-icon view, the label size may be greater than the rect returned by ListView_GetUnfoldedRect.
|
|
// So don't specify the size
|
|
SetWindowPos(plv->hwndToolTips, HWND_TOP,
|
|
rcLabel.left, rcLabel.top,
|
|
0, 0, SWP_NOSIZE | SWP_NOACTIVATE | SWP_HIDEWINDOW);
|
|
}
|
|
else
|
|
{
|
|
SetWindowPos(plv->hwndToolTips, HWND_TOP,
|
|
rcLabel.left, rcLabel.top,
|
|
(rcLabel.right - rcLabel.left), (rcLabel.bottom - rcLabel.top),
|
|
SWP_NOACTIVATE | SWP_HIDEWINDOW);
|
|
}
|
|
// This is an inplace tooltip, so disable animation.
|
|
psi->dwStyle |= TTS_NOANIMATE;
|
|
return TRUE;
|
|
}
|
|
else if (ListView_IsKbdTipTracking(plv)) // Size tip when keyboard tracking
|
|
{
|
|
RECT rc;
|
|
RECT rcTT;
|
|
RECT rcItem;
|
|
POINT ptTT;
|
|
POINT ptItem;
|
|
|
|
MONITORINFO mi = {0};
|
|
mi.cbSize = sizeof(MONITORINFO);
|
|
|
|
// Establish item screen position and size
|
|
ListView_GetItemRect(plv->ci.hwnd, plv->iTracking, &rcItem, LVIR_ICON);
|
|
ListView_GetItemRect(plv->ci.hwnd, plv->iTracking, &rc, LVIR_BOUNDS);
|
|
rcItem.top = rc.top;
|
|
rcItem.bottom = rc.bottom;
|
|
ptItem.x = rcItem.left;
|
|
ptItem.y = rcItem.top;
|
|
ClientToScreen(plv->ci.hwnd, &ptItem);
|
|
|
|
// Get tip rect
|
|
GetWindowRect(plv->hwndToolTips, &rcTT);
|
|
|
|
// Init tooltip position
|
|
ptTT.x = ptItem.x + RECTWIDTH(rcItem);
|
|
ptTT.y = ptItem.y + RECTHEIGHT(rcItem);
|
|
|
|
// Get screen info where tooltip is being displayed
|
|
GetMonitorInfo(MonitorFromPoint(ptTT, MONITOR_DEFAULTTONEAREST), &mi);
|
|
|
|
// Update tooltip position if it runs off the screen
|
|
if ((ptTT.x + RECTWIDTH(rcTT)) > mi.rcMonitor.right)
|
|
ptTT.x = (ptItem.x + g_cxIconMargin) - RECTWIDTH(rcTT);
|
|
|
|
if ((ptTT.y + RECTHEIGHT(rcTT)) > mi.rcMonitor.bottom)
|
|
ptTT.y = ptItem.y - RECTHEIGHT(rcTT);
|
|
|
|
SetWindowPos(plv->hwndToolTips, NULL, ptTT.x, ptTT.y, 0, 0, SWP_NOSIZE|SWP_NOACTIVATE);
|
|
|
|
return TRUE;
|
|
}
|
|
}
|
|
break;
|
|
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
// Pass the focus to the given window, and then check to see if it exists.
|
|
// Passing focus can cause the window to be destroyed (by the Explorer
|
|
// when renaming).
|
|
|
|
BOOL ListView_SetFocus(HWND hwnd)
|
|
{
|
|
SetFocus(hwnd);
|
|
return IsWindow(hwnd);
|
|
}
|
|
|
|
void ListView_Realize(LV* plv, HDC hdcParam, BOOL fBackground, BOOL fForceRepaint)
|
|
{
|
|
if (plv->hpalHalftone)
|
|
{
|
|
HDC hdc = hdcParam ? hdcParam : GetDC(plv->ci.hwnd);
|
|
|
|
if (hdc)
|
|
{
|
|
BOOL fRepaint;
|
|
|
|
SelectPalette(hdc, plv->hpalHalftone, fBackground);
|
|
fRepaint = RealizePalette(hdc) || fForceRepaint;
|
|
|
|
if (!hdcParam)
|
|
ReleaseDC(plv->ci.hwnd, hdc);
|
|
|
|
if (fRepaint)
|
|
{
|
|
InvalidateRect(plv->ci.hwnd, NULL, TRUE);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
BOOL RectInRect(const RECT* prcOuter, const RECT* prcInner)
|
|
{
|
|
RECT rcDummy;
|
|
return IntersectRect(&rcDummy, prcOuter, prcInner);
|
|
}
|
|
|
|
|
|
LRESULT LVGenerateDragImage(LV* plv, SHDRAGIMAGE* pshdi)
|
|
{
|
|
LRESULT lRet = 0;
|
|
int iNumSelected = plv->nSelected;
|
|
int iIndex;
|
|
int iSelectedItem;
|
|
RECT rc = {0, 0, 0, 0};
|
|
RECT rcVisRect;
|
|
HBITMAP hbmpOld = NULL;
|
|
HDC hdcDragImage;
|
|
BOOL fBorderSelect = (plv->exStyle & LVS_EX_BORDERSELECT);
|
|
|
|
// First loop through can get the selection rect
|
|
if (ListView_IsOwnerData(plv))
|
|
{
|
|
plv->plvrangeSel->lpVtbl->CountIncluded(plv->plvrangeSel, &iNumSelected);
|
|
}
|
|
|
|
if (iNumSelected == 0)
|
|
return FALSE;
|
|
|
|
plv->flags |= LVF_DRAGIMAGE;
|
|
|
|
GetClientRect(plv->ci.hwnd, &rcVisRect);
|
|
|
|
|
|
// Loop Through and calculate the enclosing rect.
|
|
for (iIndex = iNumSelected - 1, iSelectedItem = -1; iIndex >= 0; iIndex--)
|
|
{
|
|
iSelectedItem = ListView_OnGetNextItem(plv, iSelectedItem, LVNI_SELECTED);
|
|
if (iSelectedItem != -1)
|
|
{
|
|
RECT rcItemBounds;
|
|
|
|
// Make sure this is in the visible region
|
|
if (ListView_GetItemRect(plv->ci.hwnd, iSelectedItem, &rcItemBounds, LVIR_SELECTBOUNDS) &&
|
|
RectInRect(&rcVisRect, &rcItemBounds))
|
|
{
|
|
UnionRect(&rc, &rc, &rcItemBounds);
|
|
}
|
|
}
|
|
}
|
|
|
|
hdcDragImage = CreateCompatibleDC(NULL);
|
|
|
|
if (hdcDragImage)
|
|
{
|
|
RGBQUAD* prgbBits;
|
|
BITMAPINFO bi;
|
|
|
|
// Need to turn this off because it doesn't look good.
|
|
plv->exStyle &= ~LVS_EX_BORDERSELECT;
|
|
|
|
// After this rc contains the bounds of all the items in Client Coordinates.
|
|
//
|
|
// Mirror the the DC, if the listview is mirrored.
|
|
//
|
|
if (plv->ci.dwExStyle & RTL_MIRRORED_WINDOW)
|
|
{
|
|
SET_DC_RTL_MIRRORED(hdcDragImage);
|
|
}
|
|
|
|
#define MAX_DRAG_RECT_WIDTH 300
|
|
#define MAX_DRAG_RECT_HEIGHT 300
|
|
// If this rect is too big, fix it.
|
|
if (RECTWIDTH(rc) > MAX_DRAG_RECT_WIDTH)
|
|
{
|
|
int iLeft = MAX_DRAG_RECT_WIDTH / 2;
|
|
int iRight = MAX_DRAG_RECT_WIDTH /2;
|
|
|
|
int iRectOriginalLeft = rc.left;
|
|
// Is the left boundry outside the visible rect?
|
|
if (rc.left < plv->ptCapture.x - iLeft)
|
|
{
|
|
// Yes, then we have to clip it.
|
|
rc.left = plv->ptCapture.x - iLeft;
|
|
}
|
|
else
|
|
{
|
|
// No? Well then shift the visible rect to the right, so that we have
|
|
// more room.
|
|
iRight += rc.left - (plv->ptCapture.x - iLeft);
|
|
}
|
|
|
|
// Is the right boundry outside the visible rect?
|
|
if (rc.right > plv->ptCapture.x + iRight)
|
|
{
|
|
// Yes, then we have to clip it.
|
|
rc.right = plv->ptCapture.x + iRight;
|
|
}
|
|
else
|
|
{
|
|
// No? Then try and add it to the left
|
|
if (rc.left > iRectOriginalLeft)
|
|
{
|
|
rc.left -= iRight - (rc.right - plv->ptCapture.x);
|
|
if (rc.left < iRectOriginalLeft)
|
|
rc.left = iRectOriginalLeft;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (RECTHEIGHT(rc) > MAX_DRAG_RECT_HEIGHT)
|
|
{
|
|
// same for top and bottom:
|
|
// Is the top boundry outside the visible rect?
|
|
int iTop = MAX_DRAG_RECT_HEIGHT / 2;
|
|
int iBottom = MAX_DRAG_RECT_HEIGHT /2;
|
|
int iRectOriginalTop = rc.top;
|
|
if (rc.top < plv->ptCapture.y - iTop)
|
|
{
|
|
// Yes, then we have to clip it.
|
|
rc.top = plv->ptCapture.y - iTop;
|
|
}
|
|
else
|
|
{
|
|
// No? Well then shift the visible rect to the right, so that we have
|
|
// more room.
|
|
iBottom += rc.top - (plv->ptCapture.y - iTop);
|
|
}
|
|
|
|
// Is the right boundry outside the visible rect?
|
|
if (rc.bottom > plv->ptCapture.y + iBottom)
|
|
{
|
|
// Yes, then we have to clip it.
|
|
rc.bottom = plv->ptCapture.y + iBottom;
|
|
}
|
|
else
|
|
{
|
|
// No? Then try and add it to the top
|
|
if (rc.top > iRectOriginalTop)
|
|
{
|
|
rc.top -= iBottom - (rc.bottom - plv->ptCapture.y);
|
|
if (rc.top < iRectOriginalTop)
|
|
rc.top = iRectOriginalTop;
|
|
}
|
|
}
|
|
}
|
|
|
|
pshdi->sizeDragImage.cx = RECTWIDTH(rc) + 1;
|
|
pshdi->sizeDragImage.cy = RECTHEIGHT(rc) + 1;
|
|
bi.bmiHeader.biSize = sizeof(bi.bmiHeader);
|
|
bi.bmiHeader.biWidth = pshdi->sizeDragImage.cx;
|
|
bi.bmiHeader.biHeight = pshdi->sizeDragImage.cy;
|
|
bi.bmiHeader.biPlanes = 1;
|
|
bi.bmiHeader.biBitCount = 32;
|
|
bi.bmiHeader.biCompression = BI_RGB;
|
|
pshdi->hbmpDragImage = CreateDIBSection(hdcDragImage, &bi, DIB_RGB_COLORS, &prgbBits, NULL, 0);
|
|
|
|
if (pshdi->hbmpDragImage)
|
|
{
|
|
int iTotal = bi.bmiHeader.biWidth * bi.bmiHeader.biHeight;
|
|
LVDRAWITEM lvdi;
|
|
int cItem;
|
|
|
|
RECT rcImage = {0, 0, pshdi->sizeDragImage.cx, pshdi->sizeDragImage.cy};
|
|
hbmpOld = SelectObject(hdcDragImage, pshdi->hbmpDragImage);
|
|
|
|
ZeroMemory(prgbBits, pshdi->sizeDragImage.cx * pshdi->sizeDragImage.cy);
|
|
pshdi->crColorKey = CLR_NONE;
|
|
|
|
|
|
// Calculate the offset... The cursor should be in the bitmap rect.
|
|
|
|
if (plv->ci.dwExStyle & RTL_MIRRORED_WINDOW)
|
|
pshdi->ptOffset.x = rc.right - plv->ptCapture.x;
|
|
else
|
|
pshdi->ptOffset.x = plv->ptCapture.x - rc.left;
|
|
pshdi->ptOffset.y = plv->ptCapture.y - rc.top;
|
|
|
|
lvdi.prcClip = NULL;
|
|
lvdi.plv = plv;
|
|
lvdi.nmcd.nmcd.hdc = hdcDragImage;
|
|
lvdi.pitem = NULL;
|
|
cItem = ListView_Count(plv);
|
|
|
|
// Now loop through again for the paint cycle
|
|
for (iIndex = cItem - 1, iSelectedItem = -1; iIndex >= 0; iIndex--)
|
|
{
|
|
if (ListView_IsOwnerData(plv))
|
|
{
|
|
iSelectedItem++;
|
|
plv->plvrangeSel->lpVtbl->NextSelected(plv->plvrangeSel, iSelectedItem, &iSelectedItem);
|
|
}
|
|
else
|
|
{
|
|
LISTITEM* pitem;
|
|
iSelectedItem = (int)(UINT_PTR)DPA_FastGetPtr(plv->hdpaZOrder, iIndex);
|
|
pitem = ListView_FastGetItemPtr(plv, iSelectedItem);
|
|
if (!(pitem->state & LVIS_SELECTED))
|
|
iSelectedItem = -1;
|
|
}
|
|
|
|
if (iSelectedItem != -1)
|
|
{
|
|
int iOldItemDrawing;
|
|
COLORREF crSave;
|
|
POINT ptOrigin = {-rc.left, -rc.top}; //Offset the rects by...
|
|
RECT rcItemBounds;
|
|
RECT rcTemp;
|
|
|
|
iOldItemDrawing = plv->iItemDrawing;
|
|
plv->iItemDrawing = iSelectedItem;
|
|
lvdi.nmcd.nmcd.dwItemSpec = iSelectedItem;
|
|
ListView_GetRects(plv, iSelectedItem, QUERY_DEFAULT, NULL, NULL, &rcItemBounds, NULL);
|
|
|
|
// Make sure this is in the visible region
|
|
if (IntersectRect(&rcTemp, &rcVisRect, &rcItemBounds))
|
|
{
|
|
ptOrigin.x += rcItemBounds.left;
|
|
ptOrigin.y += rcItemBounds.top;
|
|
// these may get changed
|
|
lvdi.lpptOrg = &ptOrigin;
|
|
lvdi.flags = LVDI_NOEFFECTS;
|
|
lvdi.nmcd.clrText = plv->clrText;
|
|
lvdi.nmcd.clrTextBk = plv->clrTextBk;
|
|
lvdi.nmcd.clrFace = plv->clrBk;
|
|
lvdi.nmcd.iIconEffect = ILD_NORMAL;
|
|
lvdi.nmcd.iIconPhase = 0;
|
|
|
|
// Save the Background color!
|
|
crSave = plv->clrBk;
|
|
plv->clrBk = CLR_NONE; // None so that it "bleeds" into the alpha channel
|
|
|
|
ListView_DrawItem(&lvdi);
|
|
|
|
plv->clrBk = crSave;
|
|
}
|
|
plv->iItemDrawing = iOldItemDrawing;
|
|
}
|
|
}
|
|
|
|
|
|
for (iIndex = 0; iIndex < iTotal; iIndex++)
|
|
{
|
|
RGBQUAD* prgb = &prgbBits[iIndex];
|
|
if (prgb->rgbReserved == 0 &&
|
|
(prgb->rgbRed || prgb->rgbGreen || prgb->rgbBlue)) // Do we have color an no alpha?
|
|
{
|
|
prgb->rgbReserved = 0xFF;
|
|
}
|
|
}
|
|
|
|
SelectObject(hdcDragImage, hbmpOld);
|
|
DeleteDC(hdcDragImage);
|
|
|
|
// We're passing back the created HBMP.
|
|
lRet = 1;
|
|
}
|
|
|
|
if (fBorderSelect)
|
|
plv->exStyle |= LVS_EX_BORDERSELECT;
|
|
}
|
|
|
|
plv->flags &= ~LVF_DRAGIMAGE;
|
|
|
|
|
|
return lRet;
|
|
}
|
|
|
|
|
|
LRESULT ListView_OnEnableGroupView(LV* plv, BOOL fEnable)
|
|
{
|
|
if (plv->ci.style & LVS_OWNERDATA) // Not supported in ownerdata case.
|
|
return -1;
|
|
|
|
if (fEnable ^ plv->fGroupView)
|
|
{
|
|
if (fEnable)
|
|
{
|
|
// Turning on groupview, so nuke insertmark, because that's not allowed
|
|
// in group view
|
|
LVINSERTMARK lvim = {0};
|
|
lvim.cbSize = sizeof(LVINSERTMARK);
|
|
lvim.iItem = -1;
|
|
ListView_OnSetInsertMark(plv, &lvim);
|
|
}
|
|
|
|
plv->fGroupView = fEnable;
|
|
|
|
if (fEnable)
|
|
{
|
|
if (plv->hdpaGroups == NULL)
|
|
plv->hdpaGroups = DPA_Create(4);
|
|
|
|
if (plv->hdpaGroups == NULL)
|
|
return -1;
|
|
}
|
|
|
|
plv->rcView.left = RECOMPUTE;
|
|
SetWindowLongPtr(plv->ci.hwnd, GWL_STYLE, GetWindowLongPtr(plv->ci.hwnd, GWL_STYLE) | LVS_AUTOARRANGE);
|
|
_ListView_RecomputeEx(plv, NULL, 0, TRUE);
|
|
InvalidateRect(plv->ci.hwnd, NULL, TRUE);
|
|
ListView_UpdateScrollBars(plv);
|
|
|
|
return 1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
LRESULT ListView_SetViewType(LV* plv, WORD wView)
|
|
{
|
|
if (wView > LV_VIEW_MAX)
|
|
return -1;
|
|
else if (plv->wView != wView)
|
|
{
|
|
int i;
|
|
WORD wViewOld = plv->wView;
|
|
|
|
ListView_DismissEdit(plv, FALSE);
|
|
|
|
// (dli) Setting the small icon width here and only in the case when we go
|
|
// from large icon view to some other view because of three reasons:
|
|
// 1. According to chee, we want to set this before we change the style bit in
|
|
// plv or after we scale.
|
|
// 2. We don't want to do it after we scale because we want to set the width to
|
|
// the maximum value so that the items in this listview do not cover each other
|
|
// 3. we do it from large icon view because large icon view has fixed width for
|
|
// each item, small icon view width can be scaled.
|
|
|
|
if (wViewOld == LV_VIEW_ICON)
|
|
ListView_ISetColumnWidth(plv, 0, LV_GetNewColWidth(plv, 0, ListView_Count(plv)-1), FALSE);
|
|
|
|
if (wView == LV_VIEW_TILE)
|
|
{
|
|
ListView_RecalcTileSize(plv);
|
|
}
|
|
|
|
plv->wView = wView;
|
|
|
|
ListView_TypeChange(plv, wViewOld, BOOLIFY(plv->ci.style & LVS_OWNERDRAWFIXED));
|
|
|
|
// Else we would like to make the most important item to still
|
|
// be visible. So first we will look for a cursorered item
|
|
// if this fails, we will look for the first selected item,
|
|
// else we will simply ask for the first item (assuming the
|
|
// count > 0
|
|
//
|
|
// And make sure the scrollbars are up to date Note this
|
|
// also updates some variables that some views need
|
|
ListView_UpdateScrollBars(plv);
|
|
|
|
i = (plv->iFocus >= 0) ? plv->iFocus : ListView_OnGetNextItem(plv, -1, LVNI_SELECTED);
|
|
if ((i == -1) && (ListView_Count(plv) > 0))
|
|
i = 0;
|
|
|
|
if (i != -1)
|
|
ListView_OnEnsureVisible(plv, i, TRUE);
|
|
|
|
RedrawWindow(plv->ci.hwnd, NULL, NULL, RDW_INVALIDATE | RDW_ERASE);
|
|
|
|
// Change of styles also changes tooltip policy, so pop it
|
|
ListView_PopBubble(plv);
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
BOOL ListView_OnGetFrozenSlot(LV* plv, LPRECT pSlotRect)
|
|
{
|
|
int cSlots, iWidth = 0, iHeight = 0;
|
|
LISTITEM *pItem;
|
|
|
|
if((plv->iFrozenSlot == LV_NOFROZENSLOT) || !ListView_IsIconView(plv) || ListView_IsOwnerData(plv) || (pSlotRect == NULL)) //Supported only in Large Icon mode!
|
|
return FALSE;
|
|
|
|
cSlots = ListView_GetSlotCount(plv, TRUE, &iWidth, &iHeight);
|
|
|
|
//We need to have a valid pItem to pass to ListView_CalcSlotRect() function.
|
|
pItem = plv->pFrozenItem; //Try to use a frozen item, if present.
|
|
if(pItem == NULL)
|
|
pItem = ListView_GetItemPtr(plv, 0); //Or else, use the first item.
|
|
|
|
if(pItem == NULL) //If we couldn't get any pItem, then we can't call CalcSlotRect().
|
|
return FALSE; //... Hence, we have to return failure.
|
|
else
|
|
{
|
|
ListView_CalcSlotRect(plv, pItem, plv->iFrozenSlot, cSlots, FALSE,
|
|
iWidth, iHeight,pSlotRect);
|
|
return TRUE;
|
|
}
|
|
}
|
|
|
|
BOOL ListView_OnSetFrozenSlot(LV* plv, BOOL fFreeze, LPPOINT pPt)
|
|
{
|
|
if(!ListView_IsIconView(plv) || ListView_IsOwnerData(plv)) //Supported only in Large Icon mode!
|
|
return FALSE;
|
|
|
|
if(fFreeze)
|
|
{
|
|
//First, find the slot where the given point lies.
|
|
int cSlots, iWidth = 0, iHeight = 0;
|
|
cSlots = ListView_GetSlotCount(plv, TRUE, &iWidth, &iHeight);
|
|
plv->iFrozenSlot = ListView_CalcHitSlot(plv, *pPt, cSlots, iWidth, iHeight);
|
|
}
|
|
else
|
|
{
|
|
//Unfreeze a frozen slot.
|
|
plv->iFrozenSlot = LV_NOFROZENSLOT; //No slot is frozen.
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
int ListView_OnGetFrozenItem(LV* plv)
|
|
{
|
|
int i;
|
|
LISTITEM *pItem;
|
|
|
|
if((plv->pFrozenItem == NULL) || !ListView_IsIconView(plv) || ListView_IsOwnerData(plv)) //Supported only in Large Icon mode!
|
|
return LV_NOFROZENITEM;
|
|
|
|
for(i = 0; i < ListView_Count(plv); i++)
|
|
{
|
|
pItem = ListView_GetItemPtr(plv, i);
|
|
if((pItem != NULL) && (pItem == plv->pFrozenItem))
|
|
return (i);
|
|
}
|
|
|
|
return LV_NOFROZENITEM;
|
|
}
|
|
|
|
BOOL ListView_OnSetFrozenItem(LV* plv, BOOL fFreeze, int iIndex)
|
|
{
|
|
LISTITEM *pitem;
|
|
|
|
if(!ListView_IsIconView(plv) || ListView_IsOwnerData(plv)) //Supported only in Large Icon mode!
|
|
return FALSE;
|
|
|
|
if(fFreeze)
|
|
{
|
|
//Freeze the given item.
|
|
pitem = ListView_GetItemPtr(plv, iIndex);
|
|
|
|
if(pitem == NULL)
|
|
return FALSE;
|
|
|
|
plv->pFrozenItem = pitem;
|
|
}
|
|
else
|
|
{
|
|
//Unfreeze the currently frozen item.
|
|
plv->pFrozenItem = NULL;
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
// Item focus changed via the keyboard, start tracking tooltip timeout for keyboard nav popups
|
|
//
|
|
BOOL ListView_OnKeyboardSelected(LV* plv, int iNewFocus)
|
|
{
|
|
if (iNewFocus >= 0 && plv->hwndToolTips)
|
|
{
|
|
// Focus via the keyboard (already cancelled via entry into this function)
|
|
plv->iTracking = iNewFocus;
|
|
|
|
// Delay will be replaced with an SPI
|
|
SetTimer(plv->ci.hwnd, IDT_TRACKINGTIP, GetDoubleClickTime() * 2, NULL);
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
LRESULT ListView_OnMapIndexToID(LV* plv, UINT iItem)
|
|
{
|
|
LISTITEM* pitem;
|
|
if (!ListView_IsValidItemNumber(plv, iItem) || ListView_IsOwnerData(plv))
|
|
{
|
|
return -1;
|
|
}
|
|
|
|
pitem = ListView_FastGetItemPtr(plv, iItem);
|
|
|
|
ASSERT (pitem);
|
|
|
|
return (LRESULT)pitem->dwId;
|
|
}
|
|
|
|
#ifdef DEBUG
|
|
UINT uAverageSeekCount = 0;
|
|
UINT uTotalSeeks = 0;
|
|
UINT uPerSeekCount = 0;
|
|
#endif
|
|
LRESULT ListView_OnMapIdToIndex(LV* plv, UINT Id)
|
|
{
|
|
DWORD dwRet = -1;
|
|
UINT cCounter = 0;
|
|
UINT cItems = ListView_Count(plv);
|
|
UINT i;
|
|
|
|
if (ListView_IsOwnerData(plv))
|
|
return -1;
|
|
|
|
if (plv->iLastId >= cItems)
|
|
plv->iLastId = 0;
|
|
|
|
DEBUG_CODE(uTotalSeeks++);
|
|
|
|
|
|
for (i = plv->iLastId; cCounter < cItems; cCounter++)
|
|
{
|
|
LISTITEM* pitem = ListView_FastGetItemPtr(plv, i);
|
|
if (pitem->dwId == Id)
|
|
{
|
|
if (plv->iLastId > i)
|
|
plv->iIncrement = -1;
|
|
else
|
|
plv->iIncrement = 1;
|
|
|
|
plv->iLastId = dwRet = i;
|
|
break;
|
|
}
|
|
|
|
DEBUG_CODE(uPerSeekCount++);
|
|
|
|
i += (DWORD)plv->iIncrement;
|
|
|
|
if (i == -1) // Wrapped around to "Less than zero"?
|
|
i = cItems - 1;
|
|
if (i >= cItems)
|
|
i = 0;
|
|
}
|
|
|
|
DEBUG_CODE(uAverageSeekCount = uPerSeekCount / uTotalSeeks);
|
|
|
|
return (LRESULT)dwRet;
|
|
}
|
|
|
|
void ListView_OnSize(LV* plv)
|
|
{
|
|
if (plv->hwndToolTips)
|
|
{
|
|
TOOLINFO ti;
|
|
|
|
if (ListView_IsLabelTip(plv))
|
|
{
|
|
// A truncated label may have been exposed or vice versa.
|
|
ListView_InvalidateTTLastHit(plv, plv->iTTLastHit);
|
|
}
|
|
|
|
ti.cbSize = sizeof(ti);
|
|
ti.hwnd = plv->ci.hwnd;
|
|
ti.uId = 0;
|
|
|
|
// Resize the tooltip control so that it covers the entire
|
|
// area of the window when its parent gets resized.
|
|
GetClientRect(plv->ci.hwnd, &ti.rect);
|
|
SendMessage(plv->hwndToolTips, TTM_NEWTOOLRECT, 0, (LPARAM) &ti);
|
|
}
|
|
|
|
// if we're supposed to center the image,
|
|
// we need to do a full redraw on each size
|
|
if ((plv->ulBkImageFlags & LVBKIF_SOURCE_MASK) &&
|
|
(plv->ulBkImageFlags & LVBKIF_STYLE_MASK) == LVBKIF_STYLE_NORMAL &&
|
|
(plv->xOffsetPercent || plv->yOffsetPercent))
|
|
{
|
|
InvalidateRect(plv->ci.hwnd, NULL, TRUE);
|
|
}
|
|
}
|
|
|
|
BOOL ListView_OnSetViewMargins(LV* plv, RECT *prc)
|
|
{
|
|
if (!IsEqualRect(plv->rcViewMargin, *prc))
|
|
{
|
|
plv->rcViewMargin = *prc;
|
|
plv->rcView.left = RECOMPUTE;
|
|
InvalidateRect(plv->ci.hwnd, NULL, TRUE);
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
BOOL ListView_OnGetViewMargins(LV* plv, RECT *prc)
|
|
{
|
|
*prc = plv->rcViewMargin;
|
|
return TRUE;
|
|
}
|
|
|
|
LRESULT CALLBACK ListView_WndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
|
|
{
|
|
LV* plv = ListView_GetPtr(hwnd);
|
|
|
|
if (plv == NULL)
|
|
{
|
|
if (uMsg == WM_NCCREATE)
|
|
{
|
|
plv = (LV*)LocalAlloc(LPTR, sizeof(LV));
|
|
if (!plv)
|
|
{
|
|
TraceMsg(TF_ERROR, "ListView: Out of memory");
|
|
return 0L; // fail the window create
|
|
}
|
|
|
|
plv->ci.hwnd = hwnd;
|
|
plv->flags = LVF_REDRAW; // assume that redrawing enabled!
|
|
plv->iFocus = -1; // no focus
|
|
plv->iMark = -1;
|
|
plv->iSelCol = -1;
|
|
plv->iDropHilite = -1; // Assume no item has drop hilite...
|
|
plv->cyItem = plv->cyItemSave = 1; // never let these be zero, not even for a moment
|
|
plv->hTheme = OpenThemeData(hwnd, L"ListView");
|
|
plv->iInsertItem = -1; // No insert mark by default of course
|
|
plv->clrim = CLR_DEFAULT;
|
|
plv->iTracking = LVKTT_NOTRACK;
|
|
plv->hheap = GetProcessHeap();
|
|
plv->iFrozenSlot = LV_NOFROZENSLOT; //No slot is frozen to begin with!
|
|
plv->iIncrement = -1;
|
|
ListView_SetPtr(hwnd, plv);
|
|
}
|
|
goto DoDefault;
|
|
}
|
|
|
|
if ((uMsg >= WM_MOUSEFIRST) && (uMsg <= WM_MOUSELAST))
|
|
{
|
|
if (plv->exStyle & (LVS_EX_TRACKSELECT|LVS_EX_ONECLICKACTIVATE|LVS_EX_TWOCLICKACTIVATE))
|
|
{
|
|
TRACKMOUSEEVENT tme;
|
|
|
|
tme.cbSize = sizeof(tme);
|
|
tme.hwndTrack = plv->ci.hwnd;
|
|
tme.dwHoverTime = plv->dwHoverTime;
|
|
tme.dwFlags = TME_LEAVE | TME_HOVER | TME_QUERY;
|
|
|
|
// see what's set
|
|
TrackMouseEvent(&tme);
|
|
tme.dwFlags &= TME_HOVER | TME_LEAVE;
|
|
|
|
// set these bits if they aren't already set
|
|
tme.dwFlags ^= TME_LEAVE;
|
|
if (plv->exStyle & LVS_EX_TRACKSELECT)
|
|
{
|
|
tme.dwFlags ^= TME_HOVER;
|
|
}
|
|
|
|
tme.cbSize = sizeof(tme);
|
|
tme.hwndTrack = plv->ci.hwnd;
|
|
tme.dwHoverTime = plv->dwHoverTime;
|
|
// set it if there's anything to set
|
|
if (tme.dwFlags & (TME_HOVER | TME_LEAVE))
|
|
{
|
|
TrackMouseEvent(&tme);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (uMsg == g_uDragImages)
|
|
{
|
|
return LVGenerateDragImage(plv, (SHDRAGIMAGE*)lParam);
|
|
}
|
|
|
|
switch (uMsg)
|
|
{
|
|
HANDLE_MSG(plv, WM_CREATE, ListView_OnCreate);
|
|
HANDLE_MSG(plv, WM_DESTROY, ListView_OnDestroy);
|
|
HANDLE_MSG(plv, WM_ERASEBKGND, ListView_OnEraseBkgnd);
|
|
HANDLE_MSG(plv, WM_COMMAND, ListView_OnCommand);
|
|
HANDLE_MSG(plv, WM_SETFOCUS, ListView_OnSetFocus);
|
|
HANDLE_MSG(plv, WM_KILLFOCUS, ListView_OnKillFocus);
|
|
|
|
HANDLE_MSG(plv, WM_HSCROLL, ListView_OnHScroll);
|
|
HANDLE_MSG(plv, WM_VSCROLL, ListView_OnVScroll);
|
|
HANDLE_MSG(plv, WM_GETDLGCODE, ListView_OnGetDlgCode);
|
|
HANDLE_MSG(plv, WM_SETFONT, ListView_OnSetFont);
|
|
HANDLE_MSG(plv, WM_GETFONT, ListView_OnGetFont);
|
|
HANDLE_MSG(plv, WM_TIMER, ListView_OnTimer);
|
|
HANDLE_MSG(plv, WM_SETREDRAW, ListView_OnSetRedraw);
|
|
HANDLE_MSG(plv, WM_NCDESTROY, ListView_OnNCDestroy);
|
|
|
|
case WM_SETCURSOR:
|
|
if (ListView_OnSetCursorMsg(plv))
|
|
return TRUE;
|
|
break;
|
|
|
|
case WM_PALETTECHANGED:
|
|
if ((HWND)wParam == hwnd)
|
|
break;
|
|
case WM_QUERYNEWPALETTE:
|
|
// Want to pass FALSE if WM_QUERYNEWPALETTE...
|
|
ListView_Realize(plv, NULL, uMsg == WM_PALETTECHANGED, uMsg == WM_PALETTECHANGED);
|
|
return TRUE;
|
|
|
|
case LVMP_WINDOWPOSCHANGED:
|
|
case WM_WINDOWPOSCHANGED:
|
|
HANDLE_WM_WINDOWPOSCHANGED(plv, wParam, lParam, ListView_OnWindowPosChanged);
|
|
break;
|
|
|
|
case WM_WINDOWPOSCHANGING:
|
|
{
|
|
WINDOWPOS* wp = (WINDOWPOS*)lParam;
|
|
if ((wp->flags & SWP_SHOWWINDOW)||
|
|
(wp->flags & SWP_HIDEWINDOW))
|
|
{
|
|
BOOL fShow = (wp->flags & SWP_SHOWWINDOW);
|
|
LV_OnShowWindow(plv, fShow);
|
|
}
|
|
|
|
if (ListView_IsWatermarked(plv))
|
|
{
|
|
RECT rc = {wp->x, wp->y, wp->x + wp->cx, wp->y + wp->y};
|
|
// Invalidate New.
|
|
rc.left = rc.right - plv->szWatermark.cx;
|
|
rc.top = rc.bottom - plv->szWatermark.cy;
|
|
InvalidateRect(plv->ci.hwnd, &rc, TRUE);
|
|
|
|
// and Old:
|
|
GetClientRect(plv->ci.hwnd, &rc);
|
|
rc.left = rc.right - plv->szWatermark.cx;
|
|
rc.top = rc.bottom - plv->szWatermark.cy;
|
|
InvalidateRect(plv->ci.hwnd, &rc, TRUE);
|
|
}
|
|
}
|
|
break;
|
|
|
|
case WM_MBUTTONDOWN:
|
|
if (ListView_SetFocus(hwnd) && plv->hwndToolTips)
|
|
RelayToToolTips(plv->hwndToolTips, hwnd, uMsg, wParam, lParam);
|
|
break;
|
|
|
|
case WM_LBUTTONDBLCLK:
|
|
case WM_RBUTTONDBLCLK:
|
|
// Cancel manual tip track on any mouse button down
|
|
ListView_CancelTipTrack(plv);
|
|
if (plv->hwndToolTips)
|
|
RelayToToolTips(plv->hwndToolTips, hwnd, uMsg, wParam, lParam);
|
|
ListView_OnButtonDown(plv, TRUE, GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam), (UINT) wParam);
|
|
break;
|
|
|
|
case WM_LBUTTONDOWN:
|
|
case WM_RBUTTONDOWN:
|
|
// Cancel manual tip track on any mouse button down
|
|
ListView_CancelTipTrack(plv);
|
|
if (plv->hwndToolTips)
|
|
RelayToToolTips(plv->hwndToolTips, hwnd, uMsg, wParam, lParam);
|
|
ListView_OnButtonDown(plv, FALSE, GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam), (UINT) wParam);
|
|
break;
|
|
|
|
case WM_LBUTTONUP:
|
|
case WM_RBUTTONUP:
|
|
case WM_MBUTTONUP:
|
|
case WM_NCMOUSEMOVE:
|
|
if (plv->hwndToolTips)
|
|
RelayToToolTips(plv->hwndToolTips, hwnd, uMsg, wParam, lParam);
|
|
break;
|
|
|
|
case WM_PRINTCLIENT:
|
|
case WM_PAINT:
|
|
ListView_OnPaint(plv, (HDC)wParam);
|
|
return 0;
|
|
|
|
case WM_NCPAINT:
|
|
{
|
|
if (plv->hTheme && plv->ci.dwExStyle & WS_EX_CLIENTEDGE)
|
|
{
|
|
HRGN hrgn = (wParam != 1) ? (HRGN)wParam : NULL;
|
|
|
|
if (CCDrawNonClientTheme(plv->hTheme, hwnd, hrgn, plv->hbrBk, 0, 0))
|
|
{
|
|
return 1;
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
|
|
case WM_SHOWWINDOW:
|
|
LV_OnShowWindow(plv, BOOLFROMPTR(wParam));
|
|
break;
|
|
|
|
case WM_MOUSEHOVER:
|
|
ListView_OnMouseHover(plv, GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam), (UINT) wParam);
|
|
break;
|
|
|
|
case WM_MOUSELEAVE:
|
|
ListView_OnSetHotItem(plv, -1);
|
|
plv->iNoHover = -1;
|
|
|
|
break;
|
|
|
|
case WM_KEYUP:
|
|
plv->iScrollCount = 0;
|
|
break;
|
|
|
|
case WM_KEYDOWN:
|
|
HANDLE_WM_KEYDOWN(plv, wParam, lParam, ListView_OnKey);
|
|
break;
|
|
|
|
case WM_IME_COMPOSITION:
|
|
// Now only Korean version is interested in incremental search with composition string.
|
|
if (g_fDBCSInputEnabled)
|
|
{
|
|
if (((ULONG_PTR)GetKeyboardLayout(0L) & 0xF000FFFFL) == 0xE0000412L)
|
|
{
|
|
if (ListView_OnImeComposition(plv, wParam, lParam))
|
|
{
|
|
lParam &= ~GCS_RESULTSTR;
|
|
break;
|
|
}
|
|
else
|
|
return 0;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case WM_CHAR:
|
|
if (plv->iPuntChar)
|
|
{
|
|
plv->iPuntChar--;
|
|
return TRUE;
|
|
}
|
|
else
|
|
{
|
|
return HANDLE_WM_CHAR(plv, wParam, lParam, ListView_OnChar);
|
|
}
|
|
|
|
case WM_WININICHANGE:
|
|
ListView_OnWinIniChange(plv, wParam, lParam);
|
|
break;
|
|
|
|
case WM_NOTIFYFORMAT:
|
|
return CIHandleNotifyFormat(&plv->ci, lParam);
|
|
|
|
case WM_ENABLE:
|
|
// HACK: we don't get WM_STYLECHANGE on EnableWindow()
|
|
ListView_EnableWindow(plv, BOOLFROMPTR(wParam));
|
|
break;
|
|
|
|
case WM_SYSCOLORCHANGE:
|
|
InitGlobalColors();
|
|
if (plv->ci.style & WS_DISABLED)
|
|
{
|
|
if (!(plv->flags & LVF_USERBKCLR))
|
|
plv->clrBkSave = g_clrWindow;
|
|
ListView_OnSetBkColor(plv, g_clrBtnFace);
|
|
}
|
|
else if (!(plv->flags & LVF_USERBKCLR))
|
|
{
|
|
ListView_OnSetBkColor(plv, g_clrWindow);
|
|
}
|
|
|
|
if (plv->exStyle & LVS_EX_CHECKBOXES)
|
|
{
|
|
ListView_InitCheckBoxes(plv, FALSE);
|
|
}
|
|
|
|
plv->crHeader = GetSysColor(COLOR_WINDOWTEXT);
|
|
plv->crTop = GetSysColor(COLOR_BTNFACE);
|
|
plv->crLeft = GetSysColor(COLOR_BTNFACE);
|
|
|
|
// 98/11/19 #249967 vtan: Always invalidate the list view
|
|
// rectangle so that the color change causes a refresh.
|
|
|
|
InvalidateRect(plv->ci.hwnd, NULL, TRUE);
|
|
break;
|
|
|
|
// don't use HANDLE_MSG because this needs to go to the default handler
|
|
case WM_SYSKEYDOWN:
|
|
HANDLE_WM_SYSKEYDOWN(plv, wParam, lParam, ListView_OnKey);
|
|
break;
|
|
|
|
case WM_UPDATEUISTATE:
|
|
{
|
|
DWORD dwUIStateMask = MAKEWPARAM(0xFFFF, UISF_HIDEFOCUS);
|
|
|
|
// we care only about focus not accel, and redraw only if changed
|
|
if (CCOnUIState(&(plv->ci), WM_UPDATEUISTATE, wParam & dwUIStateMask, lParam))
|
|
{
|
|
if (plv->iFocus >= 0)
|
|
{
|
|
// an item has the focus, invalidate it
|
|
ListView_InvalidateItem(plv, plv->iFocus, FALSE, RDW_INVALIDATE | RDW_ERASE);
|
|
}
|
|
}
|
|
|
|
goto DoDefault;
|
|
}
|
|
|
|
case LVM_GETITEMA:
|
|
return (LRESULT)ListView_OnGetItemA(plv, (LV_ITEMA *)lParam);
|
|
|
|
case LVM_SETITEMA:
|
|
return (LRESULT)ListView_OnSetItemA(plv, (LV_ITEMA *)lParam);
|
|
|
|
case LVM_INSERTITEMA:
|
|
return (LRESULT)ListView_OnInsertItemA(plv, (LV_ITEMA *)lParam);
|
|
|
|
case LVM_FINDITEMA:
|
|
return (LRESULT)ListView_OnFindItemA(plv, (int)wParam, (LV_FINDINFOA *)lParam);
|
|
|
|
case LVM_GETSTRINGWIDTHA:
|
|
return (LRESULT)ListView_OnGetStringWidthA(plv, (LPCSTR)lParam, NULL);
|
|
|
|
case LVM_GETCOLUMNA:
|
|
return (LRESULT)ListView_OnGetColumnA(plv, (int)wParam, (LV_COLUMNA *)lParam);
|
|
|
|
case LVM_SETCOLUMNA:
|
|
return (LRESULT)ListView_OnSetColumnA(plv, (int)wParam, (LV_COLUMNA *)lParam);
|
|
|
|
case LVM_INSERTCOLUMNA:
|
|
return (LRESULT)ListView_OnInsertColumnA(plv, (int)wParam, (LV_COLUMNA *)lParam);
|
|
|
|
case LVM_GETITEMTEXTA:
|
|
return (LRESULT)ListView_OnGetItemTextA(plv, (int)wParam, (LV_ITEMA *)lParam);
|
|
|
|
case LVM_SETITEMTEXTA:
|
|
if (!lParam)
|
|
return FALSE;
|
|
|
|
return (LRESULT)ListView_OnSetItemTextA(plv, (int)wParam,
|
|
((LV_ITEMA *)lParam)->iSubItem,
|
|
(LPCSTR)((LV_ITEMA *)lParam)->pszText);
|
|
|
|
case LVM_GETBKIMAGEA:
|
|
return (LRESULT)ListView_OnGetBkImageA(plv, (LPLVBKIMAGEA)lParam);
|
|
|
|
case LVM_SETBKIMAGEA:
|
|
return (LRESULT)ListView_OnSetBkImageA(plv, (LPLVBKIMAGEA)lParam);
|
|
|
|
case WM_STYLECHANGING:
|
|
ListView_OnStyleChanging(plv, (UINT)wParam, (LPSTYLESTRUCT)lParam);
|
|
return 0;
|
|
|
|
case WM_STYLECHANGED:
|
|
ListView_OnStyleChanged(plv, (UINT) wParam, (LPSTYLESTRUCT)lParam);
|
|
return 0L;
|
|
|
|
case WM_HELP:
|
|
return ListView_OnHelp(plv, (LPHELPINFO)lParam);
|
|
|
|
|
|
case LVM_GETIMAGELIST:
|
|
return (LRESULT)(UINT_PTR)(ListView_OnGetImageList(plv, (int)wParam));
|
|
|
|
case LVM_SETIMAGELIST:
|
|
return (LRESULT)(UINT_PTR)ListView_OnSetImageList(plv, (HIMAGELIST)lParam, (int)wParam);
|
|
|
|
case LVM_GETBKCOLOR:
|
|
return (LRESULT)(plv->ci.style & WS_DISABLED ? plv->clrBkSave : plv->clrBk);
|
|
|
|
case LVM_SETBKCOLOR:
|
|
plv->flags |= LVF_USERBKCLR;
|
|
if (plv->ci.style & WS_DISABLED)
|
|
{
|
|
plv->clrBkSave = (COLORREF)lParam;
|
|
return TRUE;
|
|
}
|
|
else
|
|
{
|
|
return (LRESULT)ListView_OnSetBkColor(plv, (COLORREF)lParam);
|
|
}
|
|
|
|
case LVM_GETTEXTCOLOR:
|
|
return (LRESULT)plv->clrText;
|
|
case LVM_SETTEXTCOLOR:
|
|
plv->clrText = (COLORREF)lParam;
|
|
return TRUE;
|
|
case LVM_GETTEXTBKCOLOR:
|
|
return (LRESULT)plv->clrTextBk;
|
|
case LVM_SETTEXTBKCOLOR:
|
|
plv->clrTextBk = (COLORREF)lParam;
|
|
return TRUE;
|
|
case LVM_GETHOTLIGHTCOLOR:
|
|
return (LRESULT)plv->clrHotlight;
|
|
case LVM_SETHOTLIGHTCOLOR:
|
|
plv->clrHotlight = (COLORREF)lParam;
|
|
return TRUE;
|
|
|
|
case LVM_GETITEMCOUNT:
|
|
if (ListView_IsOwnerData(plv))
|
|
return (LRESULT)plv->cTotalItems;
|
|
else if (!plv->hdpa)
|
|
return 0;
|
|
else
|
|
return (LRESULT)DPA_GetPtrCount(plv->hdpa);
|
|
break;
|
|
|
|
case LVM_GETITEM:
|
|
return (LRESULT)ListView_OnGetItem(plv, (LV_ITEM*)lParam);
|
|
|
|
case LVM_GETITEMSTATE:
|
|
return (LRESULT)ListView_OnGetItemState(plv, (int)wParam, (UINT)lParam);
|
|
|
|
case LVM_SETITEMSTATE:
|
|
if (!lParam)
|
|
return FALSE;
|
|
|
|
return (LRESULT)ListView_OnSetItemState(plv, (int)wParam,
|
|
((LV_ITEM *)lParam)->state,
|
|
((LV_ITEM *)lParam)->stateMask);
|
|
|
|
case LVM_SETITEMTEXT:
|
|
if (!lParam)
|
|
return FALSE;
|
|
|
|
return (LRESULT)ListView_OnSetItemText(plv, (int)wParam,
|
|
((LV_ITEM *)lParam)->iSubItem,
|
|
(LPCTSTR)((LV_ITEM *)lParam)->pszText);
|
|
|
|
case LVM_GETITEMTEXT:
|
|
return (LRESULT)ListView_OnGetItemText(plv, (int)wParam, (LV_ITEM *)lParam);
|
|
|
|
case LVM_GETBKIMAGE:
|
|
return (LRESULT)ListView_OnGetBkImage(plv, (LPLVBKIMAGE)lParam);
|
|
|
|
case LVM_SETBKIMAGE:
|
|
return (LRESULT)ListView_OnSetBkImage(plv, (LPLVBKIMAGE)lParam);
|
|
|
|
case LVM_GETSELECTEDCOLUMN:
|
|
return plv->iLastColSort;
|
|
|
|
case LVM_SETSELECTEDCOLUMN:
|
|
plv->iLastColSort = (int) wParam;
|
|
|
|
if (ListView_IsTileView(plv))
|
|
{
|
|
// Tileview displays the selected column on the second line, if available. The second
|
|
// line might be blank w/o it. So when this changes, we need to recompute each tile.
|
|
if (!ListView_IsOwnerData(plv))
|
|
{
|
|
int i;
|
|
for (i = 0; i < ListView_Count(plv); i++)
|
|
{
|
|
LISTITEM *pitem = ListView_FastGetItemPtr(plv, i);
|
|
ListView_SetSRecompute(pitem);
|
|
}
|
|
}
|
|
|
|
plv->rcView.left = RECOMPUTE;
|
|
InvalidateRect(plv->ci.hwnd, NULL, TRUE);
|
|
}
|
|
|
|
return 1;
|
|
|
|
case LVM_SETVIEW:
|
|
return ListView_SetViewType(plv, (WORD)wParam);
|
|
|
|
case LVM_GETVIEW:
|
|
return plv->wView;
|
|
|
|
case LVM_SETITEM:
|
|
return (LRESULT)ListView_OnSetItem(plv, (const LV_ITEM*)lParam);
|
|
|
|
case LVM_INSERTITEM:
|
|
return (LRESULT)ListView_OnInsertItem(plv, (const LV_ITEM*)lParam);
|
|
|
|
case LVM_DELETEITEM:
|
|
return (LRESULT)ListView_OnDeleteItem(plv, (int)wParam);
|
|
|
|
case LVM_UPDATE:
|
|
ListView_OnUpdate(plv, (int)wParam);
|
|
UpdateWindow(plv->ci.hwnd);
|
|
return TRUE;
|
|
|
|
case LVM_DELETEALLITEMS:
|
|
lParam = (LRESULT)ListView_OnDeleteAllItems(plv);
|
|
// Optimization: Instead of sending out a zillion EVENT_OBJECT_DESTROY's,
|
|
// we send out a destroy of ourselves followed by a fresh create.
|
|
// For compatibility with IE4, we still send out the REORDER notification.
|
|
NotifyWinEvent(EVENT_OBJECT_REORDER, hwnd, OBJID_CLIENT, 0);
|
|
ListView_NotifyRecreate(plv);
|
|
return lParam;
|
|
|
|
case LVM_GETITEMRECT:
|
|
return (LRESULT)ListView_OnGetItemRect(plv, (int)wParam, (RECT*)lParam);
|
|
|
|
case LVM_GETSUBITEMRECT:
|
|
return (LRESULT)ListView_OnGetSubItemRect(plv, (int)wParam, (LPRECT)lParam);
|
|
|
|
case LVM_SUBITEMHITTEST:
|
|
return (LRESULT)ListView_OnSubItemHitTest(plv, (LPLVHITTESTINFO)lParam);
|
|
|
|
case LVM_GETISEARCHSTRINGA:
|
|
if (GetFocus() == plv->ci.hwnd)
|
|
return (LRESULT)GetIncrementSearchStringA(&plv->is, plv->ci.uiCodePage, (LPSTR)lParam);
|
|
else
|
|
return 0;
|
|
|
|
case LVM_GETISEARCHSTRING:
|
|
if (GetFocus() == plv->ci.hwnd)
|
|
return (LRESULT)GetIncrementSearchString(&plv->is, (LPTSTR)lParam);
|
|
else
|
|
return 0;
|
|
|
|
case LVM_GETITEMSPACING:
|
|
if (wParam)
|
|
return MAKELONG(plv->cxItem, plv->cyItem);
|
|
else
|
|
return MAKELONG(plv->cxIconSpacing, plv->cyIconSpacing);
|
|
|
|
case LVM_GETNEXTITEM:
|
|
return (LRESULT)ListView_OnGetNextItem(plv, (int)wParam, (UINT)lParam);
|
|
|
|
case LVM_FINDITEM:
|
|
return (LRESULT)ListView_OnFindItem(plv, (int)wParam, (const LV_FINDINFO*)lParam);
|
|
|
|
case LVM_SETSELECTIONMARK:
|
|
{
|
|
int iOldMark = plv->iMark;
|
|
int iNewMark = (int)lParam;
|
|
if (iNewMark == -1 || ListView_IsValidItemNumber(plv, iNewMark))
|
|
{
|
|
plv->iMark = iNewMark;
|
|
}
|
|
return iOldMark;
|
|
}
|
|
|
|
case LVM_GETSELECTIONMARK:
|
|
return plv->iMark;
|
|
|
|
case LVM_GETITEMPOSITION:
|
|
return (LRESULT)ListView_OnGetItemPosition(plv, (int)wParam,
|
|
(POINT*)lParam);
|
|
|
|
case LVM_SETITEMPOSITION:
|
|
return (LRESULT)ListView_OnSetItemPosition(plv, (int)wParam,
|
|
GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam));
|
|
|
|
case LVM_SETITEMPOSITION32:
|
|
if (!lParam)
|
|
return FALSE;
|
|
|
|
return (LRESULT)ListView_OnSetItemPosition(plv, (int)wParam,
|
|
((LPPOINT)lParam)->x, ((LPPOINT)lParam)->y);
|
|
|
|
case LVM_SCROLL:
|
|
{
|
|
int dx = (int)wParam;
|
|
int dy = (int)lParam;
|
|
return (LRESULT)(ListView_ValidateScrollParams(plv, &dx, &dy) &&
|
|
ListView_OnScroll(plv, dx, dy));
|
|
}
|
|
|
|
case LVM_ENSUREVISIBLE:
|
|
return (LRESULT)ListView_OnEnsureVisible(plv, (int)wParam, BOOLFROMPTR(lParam));
|
|
|
|
case LVM_REDRAWITEMS:
|
|
return (LRESULT)ListView_OnRedrawItems(plv, (int)wParam, (int)lParam);
|
|
|
|
case LVM_ARRANGE:
|
|
return (LRESULT)ListView_OnArrange(plv, (UINT)wParam);
|
|
|
|
case LVM_GETEDITCONTROL:
|
|
return (LRESULT)(UINT_PTR)plv->hwndEdit;
|
|
|
|
case LVM_EDITLABELA:
|
|
{
|
|
LPWSTR lpEditString = NULL;
|
|
HWND hRet;
|
|
|
|
if (lParam)
|
|
{
|
|
lpEditString = ProduceWFromA(plv->ci.uiCodePage, (LPSTR)lParam);
|
|
}
|
|
|
|
hRet = ListView_OnEditLabel(plv, (int)wParam, lpEditString);
|
|
|
|
if (lpEditString)
|
|
{
|
|
FreeProducedString(lpEditString);
|
|
}
|
|
|
|
return (LRESULT)hRet;
|
|
}
|
|
|
|
case LVM_EDITLABEL:
|
|
return (LRESULT)(UINT_PTR)ListView_OnEditLabel(plv, (int)wParam, (LPTSTR)lParam);
|
|
|
|
case LVM_HITTEST:
|
|
return (LRESULT)ListView_OnHitTest(plv, (LV_HITTESTINFO*)lParam);
|
|
|
|
case LVM_GETSTRINGWIDTH:
|
|
return (LRESULT)ListView_OnGetStringWidth(plv, (LPCTSTR)lParam, NULL);
|
|
|
|
case LVM_GETCOLUMN:
|
|
return (LRESULT)ListView_OnGetColumn(plv, (int)wParam, (LV_COLUMN*)lParam);
|
|
|
|
case LVM_SETCOLUMN:
|
|
return (LRESULT)ListView_OnSetColumn(plv, (int)wParam, (const LV_COLUMN*)lParam);
|
|
|
|
case LVM_SETCOLUMNORDERARRAY:
|
|
return SendMessage(plv->hwndHdr, HDM_SETORDERARRAY, wParam, lParam);
|
|
|
|
case LVM_GETCOLUMNORDERARRAY:
|
|
return SendMessage(plv->hwndHdr, HDM_GETORDERARRAY, wParam, lParam);
|
|
|
|
case LVM_GETHEADER:
|
|
{
|
|
HWND hwndOld = plv->hwndHdr;
|
|
if (lParam && IsWindow((HWND)lParam))
|
|
{
|
|
plv->hwndHdr = (HWND)lParam;
|
|
}
|
|
return (LRESULT)hwndOld;
|
|
}
|
|
|
|
case LVM_INSERTCOLUMN:
|
|
return (LRESULT)ListView_OnInsertColumn(plv, (int)wParam, (const LV_COLUMN*)lParam);
|
|
|
|
case LVM_DELETECOLUMN:
|
|
return (LRESULT)ListView_OnDeleteColumn(plv, (int)wParam);
|
|
|
|
case LVM_CREATEDRAGIMAGE:
|
|
return (LRESULT)(UINT_PTR)ListView_OnCreateDragImage(plv, (int)wParam, (LPPOINT)lParam);
|
|
|
|
|
|
case LVMI_PLACEITEMS:
|
|
if (plv->uUnplaced)
|
|
{
|
|
ListView_Recompute(plv);
|
|
ListView_UpdateScrollBars(plv);
|
|
}
|
|
return 0;
|
|
|
|
case LVM_GETVIEWRECT:
|
|
return (LPARAM)ListView_OnGetViewRect(plv, (RECT*)lParam);
|
|
|
|
case LVM_GETCOLUMNWIDTH:
|
|
return (LPARAM)ListView_OnGetColumnWidth(plv, (int)wParam);
|
|
|
|
case LVM_SETCOLUMNWIDTH:
|
|
return (LPARAM)ListView_ISetColumnWidth(plv, (int)wParam,
|
|
GET_X_LPARAM(lParam), TRUE);
|
|
|
|
case LVM_SETCALLBACKMASK:
|
|
plv->stateCallbackMask = (UINT)wParam;
|
|
return (LPARAM)TRUE;
|
|
|
|
case LVM_GETCALLBACKMASK:
|
|
return (LPARAM)(UINT)plv->stateCallbackMask;
|
|
|
|
case LVM_GETTOPINDEX:
|
|
return (LPARAM)ListView_OnGetTopIndex(plv);
|
|
|
|
case LVM_GETCOUNTPERPAGE:
|
|
return (LPARAM)ListView_OnGetCountPerPage(plv);
|
|
|
|
case LVM_GETORIGIN:
|
|
return (LPARAM)ListView_OnGetOrigin(plv, (POINT*)lParam);
|
|
|
|
case LVM_SETITEMCOUNT:
|
|
return ListView_OnSetItemCount(plv, (int)wParam, (DWORD)lParam);
|
|
|
|
case LVM_GETSELECTEDCOUNT:
|
|
if (ListView_IsOwnerData(plv))
|
|
{
|
|
plv->plvrangeSel->lpVtbl->CountIncluded(plv->plvrangeSel, &plv->nSelected);
|
|
}
|
|
|
|
return plv->nSelected;
|
|
|
|
case LVM_SORTITEMS:
|
|
return ListView_OnSortItems(plv, (LPARAM)wParam, (PFNLVCOMPARE)lParam, TRUE);
|
|
|
|
case LVM_SORTITEMSEX:
|
|
return ListView_OnSortItems(plv, (LPARAM)wParam, (PFNLVCOMPARE)lParam, FALSE);
|
|
|
|
case LVM_SETEXTENDEDLISTVIEWSTYLE:
|
|
return ListView_ExtendedStyleChange(plv, (DWORD) lParam, (DWORD) wParam);
|
|
|
|
case LVM_GETEXTENDEDLISTVIEWSTYLE:
|
|
return plv->exStyle;
|
|
|
|
case LVM_GETHOVERTIME:
|
|
return plv->dwHoverTime;
|
|
|
|
case LVM_SETHOVERTIME:
|
|
{
|
|
DWORD dwRet = plv->dwHoverTime;
|
|
plv->dwHoverTime = (DWORD)lParam;
|
|
return dwRet;
|
|
}
|
|
|
|
case LVM_GETTOOLTIPS:
|
|
return (LRESULT)plv->hwndToolTips;
|
|
|
|
case LVM_SETTOOLTIPS:
|
|
{
|
|
HWND hwndToolTips = plv->hwndToolTips;
|
|
plv->hwndToolTips = (HWND)wParam;
|
|
return (LRESULT)hwndToolTips;
|
|
}
|
|
|
|
case LVM_SETICONSPACING:
|
|
{
|
|
DWORD dwRet = ListView_OnSetIconSpacing(plv, lParam);
|
|
|
|
// rearrange as necessary
|
|
if (ListView_RedrawEnabled(plv) &&
|
|
(ListView_IsSmallView(plv) || ListView_IsIconView(plv)))
|
|
{
|
|
ListView_ArrangeOrSnapToGrid(plv);
|
|
}
|
|
return dwRet;
|
|
}
|
|
|
|
case LVM_SETHOTITEM:
|
|
{
|
|
int iOld = plv->iHot;
|
|
int iNew = (int)wParam;
|
|
if (iNew == -1 || ListView_IsValidItemNumber(plv, iNew))
|
|
{
|
|
ListView_OnSetHotItem(plv, (int)wParam);
|
|
}
|
|
return iOld;
|
|
}
|
|
|
|
case LVM_GETHOTITEM:
|
|
return plv->iHot;
|
|
|
|
// hCurHot is used iff LVS_EX_TRACKSELECT
|
|
case LVM_SETHOTCURSOR:
|
|
{
|
|
HCURSOR hCurOld = plv->hCurHot;
|
|
plv->hCurHot = (HCURSOR)lParam;
|
|
return (LRESULT)hCurOld;
|
|
}
|
|
|
|
case LVM_GETHOTCURSOR:
|
|
if (!plv->hCurHot)
|
|
{
|
|
plv->hCurHot = LoadCursor(NULL, IDC_HAND);
|
|
}
|
|
return (LRESULT)plv->hCurHot;
|
|
|
|
case LVM_APPROXIMATEVIEWRECT:
|
|
return ListView_OnApproximateViewRect(plv, (int)wParam, GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam));
|
|
|
|
case LVM_SETLVRANGEOBJECT:
|
|
return ListView_OnSetLVRangeObject(plv, (int)wParam, (ILVRange *)lParam);
|
|
|
|
case LVM_SETWORKAREAS:
|
|
ListView_OnSetWorkAreas(plv, (int)wParam, (RECT *)lParam);
|
|
return 0;
|
|
|
|
case LVM_GETWORKAREAS:
|
|
ListView_OnGetWorkAreas(plv, (int)wParam, (RECT *)lParam);
|
|
return 0;
|
|
|
|
case LVM_GETNUMBEROFWORKAREAS:
|
|
ListView_OnGetNumberOfWorkAreas(plv, (int *)lParam);
|
|
return 0;
|
|
|
|
case LVM_RESETEMPTYTEXT:
|
|
plv->fNoEmptyText = FALSE;
|
|
Str_Set(&plv->pszEmptyText, NULL);
|
|
if (ListView_Count(plv) == 0)
|
|
InvalidateRect(plv->ci.hwnd, NULL, TRUE);
|
|
return 1;
|
|
|
|
case LVM_INSERTGROUPSORTED:
|
|
return ListView_OnInsertGroupSorted(plv, (LVINSERTGROUPSORTED*) wParam);
|
|
|
|
case LVM_SORTGROUPS:
|
|
return ListView_OnSortGroups(plv, (PFNLVGROUPCOMPARE)wParam, (void *)lParam);
|
|
|
|
case LVM_ENABLEGROUPVIEW:
|
|
return ListView_OnEnableGroupView(plv, (BOOL)wParam);
|
|
|
|
case LVM_ISGROUPVIEWENABLED:
|
|
return plv->fGroupView;
|
|
|
|
case LVM_INSERTGROUP:
|
|
return ListView_OnInsertGroup(plv, (int) wParam, (PLVGROUP)lParam);
|
|
|
|
case LVM_SETGROUPINFO:
|
|
return ListView_OnSetGroupInfo(plv, (int) wParam, (PLVGROUP)lParam);
|
|
|
|
case LVM_GETGROUPINFO:
|
|
return ListView_OnGetGroupInfo(plv, (int) wParam, (PLVGROUP)lParam);
|
|
|
|
case LVM_REMOVEGROUP:
|
|
return ListView_OnRemoveGroup(plv, (int) wParam);
|
|
|
|
case LVM_REMOVEALLGROUPS:
|
|
return ListView_OnRemoveAllGroups(plv);
|
|
|
|
case LVM_HASGROUP:
|
|
{
|
|
LISTGROUP* pgrp = ListView_FindGroupFromID(plv, (int)wParam, NULL);
|
|
if (pgrp)
|
|
return 1;
|
|
else
|
|
return 0;
|
|
}
|
|
break;
|
|
|
|
case LVM_SETGROUPMETRICS:
|
|
return ListView_OnSetGroupMetrics(plv, (PLVGROUPMETRICS)lParam);
|
|
|
|
case LVM_GETGROUPMETRICS:
|
|
return ListView_OnGetGroupMetrics(plv, (PLVGROUPMETRICS)lParam);
|
|
|
|
case LVM_SETTILEVIEWINFO:
|
|
return ListView_OnSetTileViewInfo(plv, (PLVTILEVIEWINFO)lParam);
|
|
|
|
case LVM_GETTILEVIEWINFO:
|
|
return ListView_OnGetTileViewInfo(plv, (PLVTILEVIEWINFO)lParam);
|
|
|
|
case LVM_SETTILEINFO:
|
|
return ListView_OnSetTileInfo(plv, (PLVTILEINFO)lParam);
|
|
|
|
case LVM_GETTILEINFO:
|
|
return ListView_OnGetTileInfo(plv, (PLVTILEINFO)lParam);
|
|
|
|
case LVM_SETINSERTMARK:
|
|
if (ListView_IsRearrangeableView(plv) && (plv->ci.style & LVS_AUTOARRANGE) && !plv->fGroupView)
|
|
return ListView_OnSetInsertMark(plv, (LPLVINSERTMARK)lParam);
|
|
else
|
|
return FALSE;
|
|
|
|
case LVM_GETINSERTMARK:
|
|
{
|
|
LPLVINSERTMARK plvim = (LPLVINSERTMARK)lParam;
|
|
|
|
if (plvim->cbSize != sizeof(LVINSERTMARK))
|
|
return FALSE;
|
|
|
|
plvim->dwFlags = (plv->fInsertAfter ? LVIM_AFTER : 0) | LVIM_SETFROMINFO;
|
|
plvim->iItem = plv->iInsertItem;
|
|
return TRUE;
|
|
}
|
|
|
|
case LVM_GETINSERTMARKRECT:
|
|
{
|
|
return ListView_OnGetInsertMarkRect(plv, (LPRECT)lParam);
|
|
}
|
|
|
|
case LVM_SETINSERTMARKCOLOR:
|
|
{
|
|
LRESULT lres = (LRESULT)ListView_OnGetInsertMarkColor(plv);
|
|
plv->clrim = (COLORREF) lParam;
|
|
return lres;
|
|
}
|
|
|
|
case LVM_GETINSERTMARKCOLOR:
|
|
return ListView_OnGetInsertMarkColor(plv);
|
|
|
|
case LVM_INSERTMARKHITTEST:
|
|
{
|
|
LPPOINT ppt = (LPPOINT)wParam;
|
|
return ListView_OnInsertMarkHitTest(plv, ppt->x, ppt->y, (LPLVINSERTMARK)lParam);
|
|
}
|
|
|
|
case LVM_SETINFOTIP:
|
|
{
|
|
return ListView_OnSetInfoTip(plv, (PLVSETINFOTIP)lParam);
|
|
}
|
|
|
|
case LVM_SETOUTLINECOLOR:
|
|
{
|
|
LRESULT lres = (LRESULT)plv->clrOutline;
|
|
plv->clrOutline = (COLORREF) lParam;
|
|
return lres;
|
|
}
|
|
|
|
case LVM_GETOUTLINECOLOR:
|
|
return (LRESULT)plv->clrOutline;
|
|
|
|
case LVM_SETFROZENITEM:
|
|
return ListView_OnSetFrozenItem(plv, (BOOL) wParam, (int) lParam);
|
|
|
|
case LVM_GETFROZENITEM:
|
|
return ListView_OnGetFrozenItem(plv);
|
|
|
|
case LVM_SETFROZENSLOT:
|
|
return ListView_OnSetFrozenSlot(plv, (BOOL) wParam, (LPPOINT)lParam);
|
|
|
|
case LVM_GETFROZENSLOT:
|
|
return ListView_OnGetFrozenSlot(plv, (LPRECT)lParam);
|
|
|
|
case LVM_SETVIEWMARGINS:
|
|
return ListView_OnSetViewMargins(plv, (LPRECT)lParam);
|
|
|
|
case LVM_GETVIEWMARGINS:
|
|
return ListView_OnGetViewMargins(plv, (LPRECT)lParam);
|
|
|
|
case LVM_KEYBOARDSELECTED:
|
|
ListView_CancelTipTrack(plv);
|
|
return lParam == 0 ? ListView_OnKeyboardSelected(plv, (int)wParam) : FALSE;
|
|
|
|
case LVM_CANCELEDITLABEL:
|
|
ListView_DismissEdit(plv, FALSE);
|
|
return 1;
|
|
|
|
case LVM_MAPINDEXTOID:
|
|
return ListView_OnMapIndexToID(plv, (UINT)wParam);
|
|
case LVM_MAPIDTOINDEX:
|
|
return ListView_OnMapIdToIndex(plv, (UINT)wParam);
|
|
|
|
case LVM_ISITEMVISIBLE:
|
|
if (ListView_IsValidItemNumber(plv, (UINT)wParam))
|
|
{
|
|
return ListView_IsItemVisibleI(plv, (UINT)wParam);
|
|
}
|
|
else
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
case WM_SIZE:
|
|
if (plv)
|
|
{
|
|
ListView_OnSize(plv);
|
|
}
|
|
break;
|
|
|
|
case WM_NOTIFY:
|
|
return ListView_OnNotify(plv, wParam, (LPNMHDR)lParam);
|
|
|
|
|
|
case WM_MOUSEMOVE:
|
|
// Cancel manual track if mouse moved
|
|
if (plv->lLastMMove != lParam)
|
|
{
|
|
ListView_CancelTipTrack(plv);
|
|
|
|
if (plv->hwndToolTips)
|
|
{
|
|
UINT uFlags;
|
|
int iHit, iSubHit;
|
|
|
|
RelayToToolTips(plv->hwndToolTips, hwnd, uMsg, wParam, lParam);
|
|
|
|
if (!ListView_IsKbdTipTracking(plv))
|
|
{
|
|
// check that we are still on the hit item, pop it!
|
|
iHit = _ListView_ItemHitTest(plv, GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam), &uFlags, &iSubHit);
|
|
|
|
if (iHit != plv->iTTLastHit || iSubHit != plv->iTTLastSubHit)
|
|
ListView_PopBubble(plv);
|
|
}
|
|
}
|
|
}
|
|
|
|
plv->lLastMMove = lParam;
|
|
ListView_OnMouseMove(plv, GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam), (UINT) wParam);
|
|
break;
|
|
|
|
case WM_GETOBJECT:
|
|
if (lParam == OBJID_QUERYCLASSNAMEIDX)
|
|
return MSAA_CLASSNAMEIDX_LISTVIEW;
|
|
break;
|
|
|
|
case WM_THEMECHANGED:
|
|
if (plv->hTheme)
|
|
CloseThemeData(plv->hTheme);
|
|
|
|
plv->hTheme = OpenThemeData(plv->ci.hwnd, L"ListView");
|
|
|
|
InvalidateRect(plv->ci.hwnd, NULL, TRUE);
|
|
break;
|
|
|
|
default:
|
|
{
|
|
LRESULT lres;
|
|
if (CCWndProc(&plv->ci, uMsg, wParam, lParam, &lres))
|
|
return lres;
|
|
}
|
|
|
|
if (uMsg == g_msgMSWheel)
|
|
{
|
|
DWORD dwStyle;
|
|
int sb;
|
|
SCROLLINFO si;
|
|
int cScrollUnitsPerLine;
|
|
int cPage;
|
|
int cLinesPerDetant;
|
|
int cDetants;
|
|
int dPos;
|
|
int iWheelDelta = (int)(short)HIWORD(wParam);
|
|
BOOL fScroll = !(wParam & (MK_SHIFT | MK_CONTROL));
|
|
BOOL fDataZoom = (BOOL) (wParam & MK_SHIFT);
|
|
|
|
// Update count of scroll amount
|
|
gcWheelDelta -= iWheelDelta;
|
|
cDetants = gcWheelDelta / WHEEL_DELTA;
|
|
if (cDetants != 0)
|
|
{
|
|
gcWheelDelta %= WHEEL_DELTA;
|
|
}
|
|
|
|
if (fScroll)
|
|
{
|
|
if (g_ucScrollLines > 0 &&
|
|
cDetants != 0 &&
|
|
((WS_VSCROLL | WS_HSCROLL) & (dwStyle = ListView_GetWindowStyle(plv))))
|
|
{
|
|
sb = (dwStyle & WS_VSCROLL) ? SB_VERT : SB_HORZ;
|
|
|
|
// Get the scroll amount of one line
|
|
cScrollUnitsPerLine = _ListView_GetScrollUnitsPerLine(plv, sb);
|
|
ASSERT(cScrollUnitsPerLine > 0);
|
|
|
|
si.cbSize = sizeof(SCROLLINFO);
|
|
si.fMask = SIF_PAGE | SIF_POS;
|
|
if (!ListView_GetScrollInfo(plv, sb, &si))
|
|
return 1;
|
|
|
|
// The size of a page is at least one line, and
|
|
// leaves one line of overlap
|
|
cPage = (max(cScrollUnitsPerLine, (int)si.nPage - cScrollUnitsPerLine)) / cScrollUnitsPerLine;
|
|
|
|
// Don't scroll more than one page per detant
|
|
cLinesPerDetant = (int) min((ULONG) cPage, (ULONG) g_ucScrollLines);
|
|
|
|
dPos = cLinesPerDetant * cDetants * cScrollUnitsPerLine;
|
|
|
|
ListView_DismissEdit(plv, FALSE);
|
|
ListView_ComOnScroll(plv, SB_THUMBTRACK, si.nPos + dPos,
|
|
sb, cScrollUnitsPerLine, - 1);
|
|
ListView_UpdateScrollBars(plv);
|
|
|
|
// After scrolling, the tooltip might need to change
|
|
// so send the tooltip a fake mousemove message to force
|
|
// a recompute. We use WM_NCMOUSEMOVE since our lParam
|
|
// is in screen coordinates, not client coordinates.
|
|
ListView_PopBubble(plv);
|
|
RelayToToolTips(plv->hwndToolTips, plv->ci.hwnd,
|
|
WM_NCMOUSEMOVE, HTCLIENT, lParam);
|
|
}
|
|
return 1;
|
|
}
|
|
else if (fDataZoom)
|
|
{
|
|
LV_HITTESTINFO ht;
|
|
ht.pt.x = GET_X_LPARAM(lParam);
|
|
ht.pt.y = GET_Y_LPARAM(lParam);
|
|
ScreenToClient(hwnd, &(ht.pt));
|
|
|
|
// If we are rolling forward and we hit an item then navigate
|
|
// into that item (simulate dblclk which will open it). Otherwise
|
|
// just fall through so it isn't handled. In that case if we
|
|
// are being hosted in explorer it will do a backwards
|
|
// history navigation.
|
|
if ((iWheelDelta > 0) && (ListView_OnSubItemHitTest(plv, &ht) >= 0) &&
|
|
(ht.flags & LVHT_ONITEM) && cDetants != 0)
|
|
{
|
|
BYTE aKeyState[256];
|
|
// This is a bit yucky but when ListView_HandleMouse sends the
|
|
// notification to the listview owner we need to make sure that
|
|
// it doesn't think the shift key is down. Otherwise it may
|
|
// perform some "alternate" action but in this case we always
|
|
// want it to perform the default open action.
|
|
//
|
|
// Strip the high bit of VK_SHIFT so that the shift key is
|
|
// not down.
|
|
if (GetKeyboardState(aKeyState))
|
|
{
|
|
aKeyState[VK_SHIFT] &= 0x7f;
|
|
SetKeyboardState(aKeyState);
|
|
}
|
|
ListView_HandleMouse(plv, FALSE, ht.pt.x, ht.pt.y, 0, TRUE);
|
|
ListView_HandleMouse(plv, TRUE, ht.pt.x, ht.pt.y, 0, TRUE);
|
|
return 1;
|
|
}
|
|
// else fall through
|
|
}
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
DoDefault:
|
|
return DefWindowProc(hwnd, uMsg, wParam, lParam);
|
|
}
|
|
|
|
BOOL Listview_UpdateViewEffects(LV* plv)
|
|
{
|
|
BOOL fChanged = FALSE;
|
|
UINT fScroll = SHRegGetBoolUSValue(REGSTR_EXPLORER_ADVANCED, TEXT("ListviewScrollOver"),
|
|
FALSE, // Don't ignore HKCU
|
|
LISTVIEW_VFX_DEFAULT); // Assume a fast enough machine
|
|
UINT fWatermark = SHRegGetBoolUSValue(REGSTR_EXPLORER_ADVANCED, TEXT("ListviewWatermark"),
|
|
FALSE, // Don't ignore HKCU
|
|
LISTVIEW_VFX_DEFAULT); // Assume a fast enough machine
|
|
UINT fAlphaSelect = SHRegGetBoolUSValue(REGSTR_EXPLORER_ADVANCED, TEXT("ListviewAlphaSelect"),
|
|
FALSE, // Don't ignore HKCU
|
|
LISTVIEW_VFX_DEFAULT); // Assume a fast enough machine
|
|
|
|
UINT fShadow = SHRegGetBoolUSValue(REGSTR_EXPLORER_ADVANCED, TEXT("ListviewShadow"),
|
|
FALSE, // Don't ignore HKCU
|
|
LISTVIEW_VFX_DEFAULT); // Assume a fast enough machine
|
|
|
|
if (plv->fListviewAlphaSelect != fAlphaSelect ||
|
|
plv->fListviewShadowText != fShadow ||
|
|
plv->fListviewWatermarkBackgroundImages != fScroll ||
|
|
plv->fListviewEnableWatermark != fWatermark)
|
|
{
|
|
fChanged = TRUE;
|
|
}
|
|
|
|
plv->fListviewAlphaSelect = BOOLIFY(fAlphaSelect);
|
|
plv->fListviewShadowText = BOOLIFY(fShadow);
|
|
plv->fListviewWatermarkBackgroundImages = BOOLIFY(fScroll);
|
|
plv->fListviewEnableWatermark = BOOLIFY(fWatermark);
|
|
|
|
|
|
return fChanged;
|
|
}
|
|
|
|
void ListView_OnWinIniChange(LV* plv, WPARAM wParam, LPARAM lParam)
|
|
{
|
|
// REARCHITECT: will this also catch sysparametersinfo?
|
|
// we need a general way of handling this, not
|
|
// just relying on the listview.
|
|
InitGlobalMetrics(wParam);
|
|
|
|
if (Listview_UpdateViewEffects(plv))
|
|
{
|
|
InvalidateRect(plv->ci.hwnd, NULL, TRUE);
|
|
}
|
|
|
|
switch (wParam)
|
|
{
|
|
case 0:
|
|
case SPI_SETNONCLIENTMETRICS:
|
|
case SPI_SETICONTITLELOGFONT:
|
|
case SPI_SETICONMETRICS:
|
|
// If wParam is 0, only reload settings if lParam is 0 too. This catches the wild-card scenario
|
|
// (like the old plus tab which does WM_WININICHANGE, 0, 0) but allows us to ignore wParam = 0
|
|
// and lParam = lpszSectionName. Reduces unecessary flashing.
|
|
if (wParam || !lParam)
|
|
{
|
|
if (!(plv->flags & LVF_ICONSPACESET))
|
|
ListView_OnSetIconSpacing(plv, (LPARAM)-1);
|
|
|
|
if (plv->flags & LVF_FONTCREATED)
|
|
ListView_OnSetFont(plv, NULL, TRUE);
|
|
|
|
// Force a recalc of all the icon regions by stripping and
|
|
// then adding back the LVS_EX_REGIONAL bit.
|
|
if (plv->exStyle & LVS_EX_REGIONAL)
|
|
{
|
|
ListView_ExtendedStyleChange(plv, 0, LVS_EX_REGIONAL);
|
|
ListView_ExtendedStyleChange(plv, LVS_EX_REGIONAL, LVS_EX_REGIONAL);
|
|
}
|
|
}
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
// If we are in an Iconic view and the user is in autoarrange mode,
|
|
// then we need to arrange the items.
|
|
//
|
|
if ((ListView_IsSmallView(plv) || ListView_IsIconView(plv)))
|
|
{
|
|
// Call off to the arrange function.
|
|
if (ListView_IsOwnerData(plv))
|
|
ListView_OnArrange(plv, LVA_DEFAULT);
|
|
else
|
|
ListView_ArrangeOrSnapToGrid(plv);
|
|
}
|
|
}
|
|
|
|
BOOL ListView_OnCreate(LV* plv, CREATESTRUCT* lpCreateStruct)
|
|
{
|
|
Listview_UpdateViewEffects(plv);
|
|
InitDitherBrush();
|
|
|
|
CIInitialize(&plv->ci, plv->ci.hwnd, lpCreateStruct);
|
|
|
|
plv->wView = (WORD)(plv->ci.style & LVS_TYPEMASK);
|
|
|
|
plv->dwExStyle = lpCreateStruct->dwExStyle;
|
|
|
|
if (plv->ci.style & WS_VISIBLE)
|
|
plv->flags |= LVF_VISIBLE;
|
|
|
|
ListView_GetRegIASetting(&g_bUseDblClickTimer);
|
|
|
|
if (ListView_IsOwnerData(plv))
|
|
{
|
|
// ownerdata initialization
|
|
plv->plvrangeSel = LVRange_Create();
|
|
if (NULL == plv->plvrangeSel)
|
|
goto error0;
|
|
|
|
plv->plvrangeCut = LVRange_Create();
|
|
if (NULL == plv->plvrangeCut)
|
|
goto error0;
|
|
}
|
|
else
|
|
{
|
|
ASSERT(plv->plvrangeSel == NULL);
|
|
ASSERT(plv->plvrangeCut == NULL);
|
|
|
|
plv->hdpa = DPA_CreateEx(LV_HDPA_GROW, plv->hheap);
|
|
if (!plv->hdpa)
|
|
goto error0;
|
|
|
|
plv->hdpaZOrder = DPA_CreateEx(LV_HDPA_GROW, plv->hheap);
|
|
if (!plv->hdpaZOrder)
|
|
goto error1;
|
|
}
|
|
|
|
ASSERT(plv->nWorkAreas == 0);
|
|
ASSERT(plv->prcWorkAreas == NULL);
|
|
ASSERT(plv->fIconsPositioned == FALSE);
|
|
plv->iNoHover = -1;
|
|
plv->dwHoverTime = HOVER_DEFAULT;
|
|
plv->iHot = -1;
|
|
plv->iEdit = -1;
|
|
plv->iFocus = -1;
|
|
plv->iDrag = -1;
|
|
plv->iTTLastHit = -1;
|
|
plv->iFreeSlot = -1;
|
|
plv->rcView.left = RECOMPUTE;
|
|
plv->iLastColSort = -1;
|
|
ASSERT(plv->sizeTile.cx == 0);
|
|
ASSERT(plv->sizeTile.cy == 0);
|
|
ASSERT(plv->dwTileFlags == 0);
|
|
plv->cSubItems = 1;
|
|
SetRect(&plv->rcBorder, 0, 12, 0, 0);
|
|
plv->crHeader = GetSysColor(COLOR_WINDOWTEXT);
|
|
plv->crTop = GetSysColor(COLOR_BTNFACE);
|
|
plv->crBottom = CLR_NONE;
|
|
plv->crLeft = CLR_NONE;
|
|
plv->crRight = CLR_NONE;
|
|
plv->paddingLeft = 12;
|
|
plv->paddingTop = 12;
|
|
plv->paddingRight = 0;
|
|
plv->paddingBottom = 12;
|
|
plv->szWatermark.cx = 200;
|
|
plv->szWatermark.cy = 200;
|
|
|
|
|
|
ASSERT(plv->iMSAAMin == plv->iMSAAMax);
|
|
|
|
plv->sizeClient.cx = lpCreateStruct->cx;
|
|
plv->sizeClient.cy = lpCreateStruct->cy;
|
|
|
|
// Setup flag to say if positions are in small or large view
|
|
if (ListView_IsSmallView(plv))
|
|
plv->flags |= LVF_ICONPOSSML;
|
|
|
|
// force calculation of listview metrics
|
|
ListView_OnSetFont(plv, NULL, FALSE);
|
|
|
|
plv->cxItem = ListView_ComputeCXItemSize(plv);
|
|
|
|
// if we're in ownerdraw report mode, the size got saved to cyItemSave
|
|
// at creation time, both need to have this
|
|
if ((plv->ci.style & LVS_OWNERDRAWFIXED) && ListView_IsReportView(plv))
|
|
plv->cyItem = plv->cyItemSave;
|
|
else
|
|
plv->cyItemSave = plv->cyItem;
|
|
|
|
ListView_OnSetIconSpacing(plv, (LPARAM)-1);
|
|
|
|
ListView_UpdateScrollBars(plv); // sets plv->cItemCol
|
|
|
|
plv->clrBk = CLR_NONE;
|
|
plv->clrText = CLR_DEFAULT;
|
|
plv->clrTextBk = CLR_DEFAULT;
|
|
plv->clrHotlight = CLR_DEFAULT;
|
|
plv->clrOutline = CLR_DEFAULT;
|
|
|
|
// create the bk brush, and set the imagelists colors if needed
|
|
ListView_OnSetBkColor(plv, g_clrWindow);
|
|
|
|
// Initialize report view fields
|
|
plv->xTotalColumnWidth = RECOMPUTE;
|
|
|
|
if (ListView_IsReportView(plv))
|
|
ListView_RInitialize(plv, FALSE);
|
|
|
|
if (plv->ci.style & WS_DISABLED)
|
|
{
|
|
plv->ci.style &= ~WS_DISABLED;
|
|
ListView_EnableWindow(plv, FALSE);
|
|
}
|
|
|
|
// tooltip for unfolding name lables
|
|
|
|
plv->hwndToolTips = CreateWindowEx(WS_EX_TRANSPARENT, TOOLTIPS_CLASS, NULL,
|
|
WS_POPUP|TTS_NOPREFIX, 0, 0, 0, 0,
|
|
NULL, NULL, g_hinst, NULL);
|
|
if (plv->hwndToolTips)
|
|
{
|
|
TOOLINFO ti;
|
|
|
|
ti.cbSize = sizeof(ti);
|
|
ti.uFlags = TTF_TRANSPARENT|TTF_ABSOLUTE;
|
|
ti.hwnd = plv->ci.hwnd;
|
|
ti.uId = 0;
|
|
ti.hinst = NULL;
|
|
ti.lpszText = LPSTR_TEXTCALLBACK;
|
|
|
|
GetClientRect(plv->ci.hwnd, &ti.rect);
|
|
SendMessage(plv->hwndToolTips, TTM_ADDTOOL, 0, (LPARAM) &ti);
|
|
|
|
/* Ensure that the tooltips use the same font as the view */
|
|
FORWARD_WM_SETFONT(plv->hwndToolTips, plv->hfontLabel, FALSE, SendMessage);
|
|
}
|
|
|
|
SetTimer(plv->ci.hwnd, IDT_ONECLICKOK, GetDoubleClickTime(), NULL);
|
|
|
|
return TRUE;
|
|
|
|
error1:
|
|
DPA_Destroy(plv->hdpa);
|
|
plv->hdpa = NULL;
|
|
|
|
error0:
|
|
if (plv->plvrangeSel)
|
|
{
|
|
plv->plvrangeSel->lpVtbl->Release(plv->plvrangeSel);
|
|
plv->plvrangeSel = NULL;
|
|
}
|
|
if (plv->plvrangeCut)
|
|
{
|
|
plv->plvrangeCut->lpVtbl->Release(plv->plvrangeCut);
|
|
plv->plvrangeCut = NULL;
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
void ListView_DeleteHrgnInval(LV* plv)
|
|
{
|
|
if (plv->hrgnInval && plv->hrgnInval != (HRGN)ENTIRE_REGION)
|
|
DeleteObject(plv->hrgnInval);
|
|
plv->hrgnInval = NULL;
|
|
}
|
|
|
|
void ListView_OnDestroy(LV* plv)
|
|
{
|
|
//
|
|
// The tooltip window may or may not exist at this point. It
|
|
// depends if the owning window of the tips is also being destroy.
|
|
// If so, then the tips are gone already.
|
|
//
|
|
|
|
if (IsWindow(plv->hwndToolTips))
|
|
DestroyWindow(plv->hwndToolTips);
|
|
|
|
if (plv->hCurHot)
|
|
DestroyCursor(plv->hCurHot);
|
|
|
|
plv->hwndToolTips = NULL;
|
|
|
|
Str_Set(&plv->pszTip, NULL);
|
|
Str_Set(&plv->pszEmptyText, NULL);
|
|
|
|
TerminateDitherBrush();
|
|
|
|
if (!ListView_IsOwnerData(plv))
|
|
{
|
|
// Make sure to notify the app
|
|
ListView_OnDeleteAllItems(plv);
|
|
}
|
|
|
|
if ((plv->flags & LVF_FONTCREATED) && plv->hfontLabel)
|
|
{
|
|
DeleteObject(plv->hfontLabel);
|
|
// plv->flags &= ~LVF_FONTCREATED;
|
|
// plv->hwfontLabel = NULL;
|
|
}
|
|
|
|
if (plv->hfontGroup)
|
|
{
|
|
DeleteObject(plv->hfontGroup);
|
|
}
|
|
|
|
if (plv->hFontHot)
|
|
{
|
|
DeleteObject(plv->hFontHot);
|
|
}
|
|
|
|
if (plv->hbmpWatermark)
|
|
{
|
|
DeleteObject(plv->hbmpWatermark);
|
|
}
|
|
|
|
ListView_DeleteHrgnInval(plv);
|
|
|
|
if (plv->prcWorkAreas)
|
|
{
|
|
// This assert is bogus: If the app created work areas then deleted
|
|
// them, nWorkAreas will be 0 but prcWorkAreas will be non-NULL.
|
|
// ASSERT(plv->nWorkAreas > 0);
|
|
LocalFree(plv->prcWorkAreas);
|
|
}
|
|
|
|
if (plv->hdpaGroups)
|
|
{
|
|
DPA_DestroyCallback(plv->hdpaGroups, DestroyGroups, NULL);
|
|
plv->hdpaGroups = NULL;
|
|
}
|
|
}
|
|
|
|
void ListView_OnNCDestroy(LV* plv)
|
|
{
|
|
if ((!(plv->ci.style & LVS_SHAREIMAGELISTS)) || ListView_CheckBoxes(plv))
|
|
{
|
|
|
|
if (plv->himlState &&
|
|
(plv->himlState != plv->himl) &&
|
|
(plv->himlState != plv->himlSmall))
|
|
{
|
|
ImageList_Destroy(plv->himlState);
|
|
}
|
|
}
|
|
|
|
if (!(plv->ci.style & LVS_SHAREIMAGELISTS))
|
|
{
|
|
if (plv->himl)
|
|
ImageList_Destroy(plv->himl);
|
|
if (plv->himlSmall)
|
|
ImageList_Destroy(plv->himlSmall);
|
|
}
|
|
|
|
if (ListView_IsOwnerData(plv))
|
|
{
|
|
if (plv->plvrangeSel)
|
|
{
|
|
plv->plvrangeSel->lpVtbl->Release(plv->plvrangeSel);
|
|
plv->plvrangeSel = NULL;
|
|
}
|
|
if (plv->plvrangeCut)
|
|
{
|
|
plv->plvrangeCut->lpVtbl->Release(plv->plvrangeCut);
|
|
plv->plvrangeCut = NULL;
|
|
}
|
|
plv->cTotalItems = 0;
|
|
}
|
|
|
|
ListView_ReleaseBkImage(plv);
|
|
|
|
if (plv->hbrBk)
|
|
DeleteBrush(plv->hbrBk);
|
|
|
|
if (plv->hdpa)
|
|
DPA_Destroy(plv->hdpa);
|
|
|
|
if (plv->hdpaZOrder)
|
|
DPA_Destroy(plv->hdpaZOrder);
|
|
|
|
ListView_RDestroy(plv);
|
|
|
|
IncrementSearchFree(&plv->is);
|
|
|
|
ListView_SetPtr(plv->ci.hwnd, NULL);
|
|
if (plv->hTheme)
|
|
CloseThemeData(plv->hTheme);
|
|
NearFree(plv);
|
|
}
|
|
|
|
|
|
// sets the background color for the listview
|
|
//
|
|
// this creats the brush for drawing the background as well
|
|
// as sets the imagelists background color if needed
|
|
|
|
BOOL ListView_OnSetBkColor(LV* plv, COLORREF clrBk)
|
|
{
|
|
if (plv->clrBk != clrBk)
|
|
{
|
|
if (plv->hbrBk)
|
|
{
|
|
DeleteBrush(plv->hbrBk);
|
|
plv->hbrBk = NULL;
|
|
}
|
|
|
|
if (clrBk != CLR_NONE)
|
|
{
|
|
plv->hbrBk = CreateSolidBrush(clrBk);
|
|
if (!plv->hbrBk)
|
|
return FALSE;
|
|
}
|
|
|
|
// don't mess with the imagelist color if things are shared
|
|
|
|
if (!(plv->ci.style & LVS_SHAREIMAGELISTS))
|
|
{
|
|
|
|
if (plv->himl)
|
|
ImageList_SetBkColor(plv->himl, clrBk);
|
|
|
|
if (plv->himlSmall)
|
|
ImageList_SetBkColor(plv->himlSmall, clrBk);
|
|
|
|
if (plv->himlState)
|
|
ImageList_SetBkColor(plv->himlState, clrBk);
|
|
}
|
|
|
|
plv->clrBk = clrBk;
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
void InitBrushOrg(LV* plv, HDC hdc)
|
|
{
|
|
int x;
|
|
|
|
if (ListView_IsSmallView(plv) || ListView_IsIconView(plv))
|
|
{
|
|
x = plv->ptOrigin.x;
|
|
}
|
|
else if (ListView_IsListView(plv))
|
|
{
|
|
x = plv->xOrigin;
|
|
}
|
|
else
|
|
{
|
|
x = (int)plv->ptlRptOrigin.x;
|
|
}
|
|
|
|
SetBrushOrgEx(hdc, -x, 0, NULL);
|
|
}
|
|
|
|
void ListView_InvalidateRegion(LV* plv, HRGN hrgn)
|
|
{
|
|
if (hrgn)
|
|
{
|
|
if (plv->hrgnInval == NULL)
|
|
{
|
|
plv->hrgnInval = hrgn;
|
|
}
|
|
else
|
|
{
|
|
|
|
// union it in if the entire region isn't marked for invalidate
|
|
if (plv->hrgnInval != (HRGN)ENTIRE_REGION)
|
|
{
|
|
UnionRgn(plv->hrgnInval, plv->hrgnInval, hrgn);
|
|
}
|
|
DeleteObject(hrgn);
|
|
}
|
|
}
|
|
}
|
|
|
|
//
|
|
// Used when a watermark is the listview's background (detected via clrTextBk
|
|
// being CLR_NONE) to perform a flicker-free scroll of the client area, using
|
|
// an offscreen bitmap
|
|
//
|
|
// potential perf issue -- caching DC and/or bitmap instead of create/destroy
|
|
// on each call
|
|
//
|
|
// jeffbog 2/29/96
|
|
//
|
|
|
|
void LVSeeThruScroll(LV *plv, LPRECT lprcUpdate)
|
|
{
|
|
HDC hdcOff;
|
|
HBITMAP hbmpOff;
|
|
int x,y,cx,cy;
|
|
HDC hdc = GetDC(plv->ci.hwnd);
|
|
|
|
if (!lprcUpdate)
|
|
{
|
|
x = y = 0;
|
|
cx = plv->sizeClient.cx;
|
|
cy = plv->sizeClient.cy;
|
|
}
|
|
else
|
|
{
|
|
x = lprcUpdate->left;
|
|
y = lprcUpdate->top;
|
|
cx = lprcUpdate->right - x;
|
|
cy = lprcUpdate->bottom - y;
|
|
}
|
|
|
|
hdcOff = CreateCompatibleDC(hdc);
|
|
hbmpOff = CreateCompatibleBitmap(hdc, plv->sizeClient.cx, plv->sizeClient.cy);
|
|
SelectObject(hdcOff, hbmpOff);
|
|
|
|
SendMessage(plv->ci.hwnd, WM_PRINT, (WPARAM)hdcOff, PRF_CLIENT | PRF_ERASEBKGND);
|
|
BitBlt(hdc, x, y, cx, cy, hdcOff, x, y, SRCCOPY);
|
|
ReleaseDC(plv->ci.hwnd, hdc);
|
|
DeleteDC(hdcOff);
|
|
DeleteObject(hbmpOff);
|
|
}
|
|
|
|
void ListView_OnPaint(LV* plv, HDC hdc)
|
|
{
|
|
PAINTSTRUCT ps;
|
|
RECT rcUpdate;
|
|
HDC hPaintDC = hdc;
|
|
HDC hMemDC = NULL;
|
|
HBITMAP hMemBm = NULL;
|
|
HBITMAP hOldBm;
|
|
BOOL fInternDC = FALSE;
|
|
|
|
// Before handling WM_PAINT, go ensure everything's recomputed...
|
|
//
|
|
if (plv->rcView.left == RECOMPUTE)
|
|
ListView_Recompute(plv);
|
|
|
|
// If we're in report view, update the header window: it looks
|
|
// better this way...
|
|
//
|
|
if (ListView_IsReportView(plv) && plv->hwndHdr)
|
|
UpdateWindow(plv->hwndHdr);
|
|
|
|
// If nothing to do (i.e., we recieved a WM_PAINT because
|
|
// of an RDW_INTERNALPAINT, and we didn't invalidate anything)
|
|
// don't bother with the Begin/EndPaint.
|
|
//
|
|
if (hdc || GetUpdateRect(plv->ci.hwnd, &rcUpdate, FALSE))
|
|
{
|
|
if (!(plv->flags & LVF_VISIBLE))
|
|
{
|
|
plv->flags |= LVF_VISIBLE;
|
|
// We may try to resize the column
|
|
ListView_MaybeResizeListColumns(plv, 0, ListView_Count(plv)-1);
|
|
ListView_UpdateScrollBars(plv);
|
|
}
|
|
|
|
// this needs to be done before the beginpaint because it clears
|
|
// out the update region
|
|
if (!(plv->flags & LVF_REDRAW))
|
|
{
|
|
// add this region to our local invalidate region
|
|
HRGN hrgn = CreateRectRgn(0, 0, 0,0);
|
|
if (hrgn)
|
|
{
|
|
|
|
// ok if GetUpdateRgn fails... then hrgn will still be
|
|
// and empty region..
|
|
GetUpdateRgn(plv->ci.hwnd, hrgn, FALSE);
|
|
ListView_InvalidateRegion(plv, hrgn);
|
|
}
|
|
}
|
|
|
|
// Get device context
|
|
if (!hdc)
|
|
{
|
|
hPaintDC = hdc = BeginPaint(plv->ci.hwnd, &ps);
|
|
fInternDC = TRUE;
|
|
}
|
|
else
|
|
{
|
|
GetClipBox(hdc, &ps.rcPaint);
|
|
}
|
|
|
|
// Skip painting if redrawing is not enabled but complete cycle (EndPaint)
|
|
if (ListView_RedrawEnabled(plv))
|
|
{
|
|
// Create memory surface and map rendering context if double buffering
|
|
if (ListView_IsDoubleBuffer(plv))
|
|
{
|
|
// Only make large enough for clipping region
|
|
hMemDC = CreateCompatibleDC(hdc);
|
|
if (hMemDC)
|
|
{
|
|
hMemBm = CreateCompatibleBitmap(hdc, RECTWIDTH(ps.rcPaint), RECTHEIGHT(ps.rcPaint));
|
|
if (hMemBm)
|
|
{
|
|
hOldBm = SelectObject(hMemDC, hMemBm);
|
|
|
|
// Offset painting to paint in region
|
|
OffsetWindowOrgEx(hMemDC, ps.rcPaint.left, ps.rcPaint.top, NULL);
|
|
}
|
|
else
|
|
{
|
|
DeleteDC(hMemDC);
|
|
hMemDC = NULL;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (hMemDC)
|
|
{
|
|
// Use memory DC if it was created
|
|
hPaintDC = hMemDC;
|
|
}
|
|
|
|
if (hPaintDC)
|
|
{
|
|
// Setup brush offset for list view scrolling
|
|
InitBrushOrg(plv, hPaintDC);
|
|
|
|
ListView_DebugDisplayClipRegion(plv, &ps.rcPaint, NULL);
|
|
|
|
// Draw backround in this pass if double buffering, otherwise, it was handled in WM_ERASEBKGND
|
|
if (ListView_IsDoubleBuffer(plv))
|
|
{
|
|
// Add on buffer offset to scrolling offset
|
|
POINT ptBrOrg;
|
|
GetBrushOrgEx(hPaintDC, &ptBrOrg);
|
|
|
|
SetBrushOrgEx(hPaintDC, ptBrOrg.x - ps.rcPaint.left, ptBrOrg.y - ps.rcPaint.top, NULL);
|
|
|
|
ListView_DrawBackground(plv, hPaintDC, &ps.rcPaint);
|
|
}
|
|
|
|
// Draw foreground
|
|
ListView_Redraw(plv, hPaintDC, &ps.rcPaint);
|
|
|
|
// Complete double buffering by blitting and freeing off-screen objects
|
|
if (ListView_IsDoubleBuffer(plv) &&
|
|
hMemDC)
|
|
{
|
|
|
|
if (plv->flags & LVF_MARQUEE)
|
|
{
|
|
HDC h = CreateCompatibleDC(hMemDC);
|
|
if (h)
|
|
{
|
|
HBITMAP hbmp, hbmpOld;
|
|
BLENDFUNCTION bf = {0};
|
|
RECT rcInvalid;
|
|
RECT rcMarquee = {0, 0, RECTWIDTH(plv->rcMarquee), RECTHEIGHT(plv->rcMarquee)};
|
|
IntersectRect(&rcInvalid, &ps.rcPaint, &plv->rcMarquee);
|
|
if (!IsRectEmpty(&rcInvalid))
|
|
{
|
|
hbmp = CreateCompatibleBitmap(hMemDC, RECTWIDTH(rcInvalid), RECTHEIGHT(rcInvalid));
|
|
if (hbmp)
|
|
{
|
|
hbmpOld = SelectObject(h, hbmp);
|
|
|
|
FillRectClr(h, &rcMarquee, g_clrMenuHilight);
|
|
|
|
bf.BlendOp = AC_SRC_OVER;
|
|
bf.SourceConstantAlpha = 70;
|
|
|
|
GdiAlphaBlend(hMemDC, rcInvalid.left, rcInvalid.top, RECTWIDTH(rcInvalid), RECTHEIGHT(rcInvalid),
|
|
h, 0, 0, RECTWIDTH(rcInvalid), RECTHEIGHT(rcInvalid), bf);
|
|
SelectObject(h, hbmpOld);
|
|
DeleteObject(hbmp);
|
|
}
|
|
|
|
SHOutlineRect(hMemDC, &plv->rcMarquee, g_clrHighlight, g_clrHighlight);
|
|
}
|
|
|
|
DeleteDC(h);
|
|
}
|
|
}
|
|
|
|
|
|
BitBlt(hdc, ps.rcPaint.left, ps.rcPaint.top, RECTWIDTH(ps.rcPaint), RECTHEIGHT(ps.rcPaint), hMemDC, ps.rcPaint.left, ps.rcPaint.top, SRCCOPY);
|
|
|
|
SelectObject(hMemDC, hOldBm);
|
|
|
|
DeleteObject(hMemBm);
|
|
DeleteDC(hMemDC);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Free DC if necessary
|
|
if (fInternDC)
|
|
EndPaint(plv->ci.hwnd, &ps);
|
|
}
|
|
}
|
|
|
|
void ListView_DrawSimpleBackground(LV *plv, HDC hdc, POINT* ppt, RECT *prcClip)
|
|
{
|
|
if (plv->clrBk != CLR_NONE)
|
|
{
|
|
//
|
|
// We just have a simple background color.
|
|
//
|
|
FillRect(hdc, prcClip, plv->hbrBk);
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// Parent HWND draws the background for us.
|
|
//
|
|
POINT pt = {0,0}, ptOrig;
|
|
MapWindowPoints(plv->ci.hwnd, plv->ci.hwndParent, &pt, 1); //Map it to parent's co-ordinates
|
|
OffsetWindowOrgEx(hdc, pt.x, pt.y, &ptOrig);
|
|
|
|
SendMessage(plv->ci.hwndParent, WM_ERASEBKGND, (WPARAM)hdc, (LPARAM)0); //Make the parent draw into child's DC
|
|
SetWindowOrgEx(hdc, ptOrig.x, ptOrig.y, NULL);
|
|
}
|
|
}
|
|
|
|
#define SATURATE(x, y) { int ___cTemp; ___cTemp = (x) + ((y + 1) * 100 * (x)) / 1000; if (___cTemp > 0xFF) ___cTemp = 0xFF; (x) = (BYTE)___cTemp; }
|
|
void SaturateDC(void * pvBitmapBits, int Amount, RECT* prcColumn, RECT* prcImage)
|
|
{
|
|
long x, y;
|
|
|
|
long uHeight = RECTHEIGHT(*prcImage);
|
|
long uWidth = RECTWIDTH(*prcImage);
|
|
ULONG* pul = (ULONG*)pvBitmapBits;
|
|
|
|
for (y = 0; y < uHeight ;y++)
|
|
{
|
|
for (x = 0; x < uWidth; x++)
|
|
{
|
|
if (x + prcImage->left >= prcColumn->left && x + prcImage->left <= prcColumn->right)
|
|
{
|
|
RGBQUAD* prgb = (RGBQUAD*)&pul[y * uWidth + x];
|
|
|
|
SATURATE(prgb->rgbRed, Amount);
|
|
SATURATE(prgb->rgbGreen, Amount);
|
|
SATURATE(prgb->rgbBlue, Amount);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void SaturateSortColumn(LV* plv, HDC hdc, void * pvBitmapBits, POINT* ppt, RECT* prcClip)
|
|
{
|
|
RECT rc;
|
|
RECT rcUpdate = *prcClip;
|
|
Header_GetItemRect(plv->hwndHdr, plv->iLastColSort, &rc);
|
|
|
|
OffsetRect(&rc, ppt->x, 0);
|
|
|
|
if (rcUpdate.left < rc.left)
|
|
rcUpdate.left = rc.left;
|
|
if (rcUpdate.right > rc.right)
|
|
rcUpdate.right = rc.right;
|
|
|
|
if (rcUpdate.left < rcUpdate.right ||
|
|
IntersectRect(&rc, &rcUpdate, prcClip))
|
|
{
|
|
SaturateDC(pvBitmapBits, 0, &rcUpdate, prcClip);
|
|
}
|
|
}
|
|
|
|
HDC PrepBackgroundDIBSection(HDC hdcDest, RECT* prc, void ** ppvBitmap, HBITMAP* phbmpOld)
|
|
{
|
|
HDC hdcRet = CreateCompatibleDC(hdcDest);
|
|
if (hdcRet)
|
|
{
|
|
HBITMAP hbmp;
|
|
BITMAPINFO bi = {0};
|
|
bi.bmiHeader.biSize = sizeof(bi.bmiHeader);
|
|
bi.bmiHeader.biWidth = RECTWIDTH(*prc);
|
|
bi.bmiHeader.biHeight = RECTHEIGHT(*prc);
|
|
bi.bmiHeader.biPlanes = 1;
|
|
bi.bmiHeader.biBitCount = 32;
|
|
bi.bmiHeader.biCompression = BI_RGB;
|
|
|
|
hbmp = CreateDIBSection(hdcRet, &bi, DIB_RGB_COLORS, ppvBitmap, NULL, 0);
|
|
|
|
*phbmpOld = (HBITMAP)SelectObject(hdcRet, hbmp);
|
|
|
|
SetViewportOrgEx(hdcRet, -prc->left, -prc->top, NULL);
|
|
}
|
|
return hdcRet;
|
|
}
|
|
|
|
void CleanupBackgroundDIBSection(HDC hdc, HBITMAP hbmpOld)
|
|
{
|
|
if (hdc)
|
|
{
|
|
HBITMAP hbmp = SelectObject(hdc, hbmpOld);
|
|
if (hbmp)
|
|
DeleteObject(hbmp);
|
|
DeleteDC(hdc);
|
|
}
|
|
}
|
|
|
|
void ListView_DrawBackground(LV *plv, HDC hdc, RECT *prcClip)
|
|
{
|
|
HRGN hrgnClipSave;
|
|
RECT rcClip;
|
|
POINT ptBackOrg = {0};
|
|
|
|
//
|
|
// Compute ptBackOrg (aka scrolling offset), based on view style.
|
|
//
|
|
switch (plv->wView)
|
|
{
|
|
case LV_VIEW_LIST:
|
|
ptBackOrg.x = -plv->xOrigin;
|
|
ptBackOrg.y = 0;
|
|
break;
|
|
|
|
case LV_VIEW_DETAILS:
|
|
ptBackOrg.x = -plv->ptlRptOrigin.x;
|
|
ptBackOrg.y = -plv->ptlRptOrigin.y + plv->yTop;
|
|
break;
|
|
|
|
default:
|
|
ptBackOrg.x = -plv->ptOrigin.x;
|
|
ptBackOrg.y = -plv->ptOrigin.y;
|
|
break;
|
|
}
|
|
|
|
|
|
// Optimize the common/simple case
|
|
if (!(plv->pImgCtx && plv->fImgCtxComplete))
|
|
{
|
|
|
|
ListView_DrawSimpleBackground(plv, hdc, &ptBackOrg, prcClip);
|
|
|
|
if (ListView_IsWatermarked(plv))
|
|
{
|
|
HDC hdcMem = CreateCompatibleDC(hdc);
|
|
if (hdcMem)
|
|
{
|
|
HBITMAP hbmp = (HBITMAP)SelectObject(hdcMem, plv->hbmpWatermark);
|
|
RECT rcWatermark;
|
|
GetClientRect(plv->ci.hwnd, &rcWatermark);
|
|
rcWatermark.left = rcWatermark.right - plv->szWatermark.cx;
|
|
rcWatermark.top = rcWatermark.bottom - plv->szWatermark.cy;
|
|
BitBlt(hdc, rcWatermark.left, rcWatermark.top, plv->szWatermark.cx, plv->szWatermark.cy,
|
|
hdcMem, 0, 0, SRCCOPY);
|
|
SelectObject(hdcMem, hbmp);
|
|
DeleteDC(hdcMem);
|
|
}
|
|
}
|
|
|
|
if (plv->wView == LV_VIEW_DETAILS &&
|
|
plv->iLastColSort != -1 && !plv->fGroupView)
|
|
{
|
|
RECT rcUpdate = *prcClip;
|
|
RECT rc;
|
|
COLORREF cr;
|
|
|
|
Header_GetItemRect(plv->hwndHdr, plv->iLastColSort, &rc);
|
|
|
|
OffsetRect(&rc, ptBackOrg.x, 0);
|
|
|
|
if (rcUpdate.left < rc.left)
|
|
rcUpdate.left = rc.left;
|
|
if (rcUpdate.right > rc.right)
|
|
rcUpdate.right = rc.right;
|
|
|
|
cr = GetSortColor(10, plv->clrBk);
|
|
|
|
FillRectClr(hdc, &rcUpdate, cr);
|
|
}
|
|
|
|
|
|
return;
|
|
}
|
|
|
|
//
|
|
// Save the old clipping region,
|
|
// since we whack on it a lot.
|
|
//
|
|
hrgnClipSave = CreateRectRgnIndirect(prcClip);
|
|
if (hrgnClipSave)
|
|
{
|
|
if (GetClipRgn(hdc, hrgnClipSave) <= 0)
|
|
{
|
|
DeleteObject(hrgnClipSave);
|
|
hrgnClipSave = NULL;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Clip the clipping region to the caller's rectangle,
|
|
// and save the final clipping rectangle in rcClip.
|
|
//
|
|
if (prcClip != NULL)
|
|
{
|
|
IntersectClipRect(hdc, prcClip->left, prcClip->top,
|
|
prcClip->right, prcClip->bottom);
|
|
}
|
|
GetClipBox(hdc, &rcClip);
|
|
|
|
if (plv->pImgCtx && plv->fImgCtxComplete)
|
|
{
|
|
RECT rcImage, rcClient;
|
|
ULONG ulState;
|
|
SIZE sizeImg;
|
|
ListView_Realize(plv, hdc, TRUE, FALSE);
|
|
|
|
switch (plv->ulBkImageFlags & LVBKIF_STYLE_MASK)
|
|
{
|
|
case LVBKIF_STYLE_TILE:
|
|
{
|
|
HDC hdcBackBuffer = hdc;
|
|
HBITMAP hbmpOld;
|
|
void * pvBits = NULL;
|
|
POINT ptBackTile = {0};
|
|
if (plv->wView == LV_VIEW_DETAILS &&
|
|
plv->iLastColSort != -1)
|
|
{
|
|
hdcBackBuffer = PrepBackgroundDIBSection(hdc, prcClip, &pvBits, &hbmpOld);
|
|
if (hdcBackBuffer == NULL)
|
|
hdcBackBuffer = hdc;
|
|
}
|
|
|
|
if (!plv->fListviewWatermarkBackgroundImages)
|
|
ptBackTile = ptBackOrg;
|
|
|
|
if (plv->ulBkImageFlags & LVBKIF_FLAG_TILEOFFSET)
|
|
{
|
|
// These offsets are in pixels, not percent (sorry)
|
|
ptBackTile.x -= plv->xOffsetPercent;
|
|
ptBackTile.y -= plv->yOffsetPercent;
|
|
}
|
|
IImgCtx_Tile(plv->pImgCtx, hdcBackBuffer, &ptBackTile, prcClip, NULL);
|
|
|
|
if (hdcBackBuffer != hdc)
|
|
{
|
|
SaturateSortColumn(plv, hdcBackBuffer, pvBits, &ptBackOrg, prcClip);
|
|
BitBlt(hdc, prcClip->left, prcClip->top, RECTWIDTH(*prcClip), RECTHEIGHT(*prcClip), hdcBackBuffer, prcClip->left, prcClip->top, SRCCOPY);
|
|
CleanupBackgroundDIBSection(hdcBackBuffer, hbmpOld);
|
|
}
|
|
|
|
}
|
|
ExcludeClipRect(hdc, prcClip->left, prcClip->top,
|
|
prcClip->right, prcClip->bottom);
|
|
break;
|
|
|
|
case LVBKIF_STYLE_NORMAL:
|
|
//
|
|
// Start with the base image.
|
|
//
|
|
IImgCtx_GetStateInfo(plv->pImgCtx, &ulState, &sizeImg, FALSE);
|
|
rcImage.left = 0;
|
|
rcImage.top = 0;
|
|
rcImage.right = sizeImg.cx;
|
|
rcImage.bottom = sizeImg.cy;
|
|
|
|
//
|
|
// Adjust for caller offsets.
|
|
//
|
|
GetClientRect(plv->ci.hwnd, &rcClient);
|
|
if (plv->xOffsetPercent)
|
|
{
|
|
LONG dx = plv->xOffsetPercent * (rcClient.right - sizeImg.cx) / 100;
|
|
|
|
rcImage.left += dx;
|
|
rcImage.right += dx;
|
|
}
|
|
if (plv->yOffsetPercent)
|
|
{
|
|
LONG dy = plv->yOffsetPercent * (rcClient.bottom - sizeImg.cy) / 100;
|
|
|
|
rcImage.top += dy;
|
|
rcImage.bottom += dy;
|
|
}
|
|
|
|
//
|
|
// Adjust for ptBackOrg (scrolling offset).
|
|
//
|
|
rcImage.left += ptBackOrg.x;
|
|
rcImage.top += ptBackOrg.y;
|
|
rcImage.right += ptBackOrg.x;
|
|
rcImage.bottom += ptBackOrg.y;
|
|
|
|
//
|
|
// Draw the image, if necessary.
|
|
//
|
|
if (RectVisible(hdc, &rcImage))
|
|
{
|
|
IImgCtx_Draw(plv->pImgCtx, hdc, &rcImage);
|
|
ExcludeClipRect(hdc, rcImage.left, rcImage.top,
|
|
rcImage.right, rcImage.bottom);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Now draw the rest of the background.
|
|
//
|
|
if (RectVisible(hdc, prcClip))
|
|
{
|
|
ListView_DrawSimpleBackground(plv, hdc, &ptBackOrg, prcClip);
|
|
}
|
|
|
|
//
|
|
// Restore old clipping region.
|
|
//
|
|
SelectClipRgn(hdc, hrgnClipSave);
|
|
if (hrgnClipSave)
|
|
{
|
|
DeleteObject(hrgnClipSave);
|
|
}
|
|
}
|
|
|
|
BOOL ListView_OnEraseBkgnd(LV *plv, HDC hdc)
|
|
{
|
|
// If redraw is turned off, still process erase bk
|
|
if (ListView_IsDoubleBuffer(plv) && (plv->flags & LVF_REDRAW))
|
|
{
|
|
// No erase, will happen in WM_PAINT handler (ListView_OnPaint)
|
|
return FALSE;
|
|
}
|
|
else
|
|
{
|
|
RECT rcClip;
|
|
|
|
//
|
|
// We draw our own background, erase with it.
|
|
//
|
|
GetClipBox(hdc, &rcClip);
|
|
ListView_DrawBackground(plv, hdc, &rcClip);
|
|
|
|
return TRUE;
|
|
}
|
|
}
|
|
|
|
void ListView_OnCommand(LV* plv, int id, HWND hwndCtl, UINT codeNotify)
|
|
{
|
|
if (hwndCtl == plv->hwndEdit)
|
|
{
|
|
switch (codeNotify)
|
|
{
|
|
case EN_UPDATE:
|
|
// We don't want flicker during replacing current selection
|
|
// as we use selection for IME composition.
|
|
//
|
|
if ((g_fDBCSInputEnabled) && (plv->flags & LVF_INSERTINGCOMP))
|
|
break;
|
|
// We will use the ID of the window as a Dirty flag...
|
|
if (IsWindowVisible(plv->hwndEdit))
|
|
{
|
|
SetWindowID(plv->hwndEdit, 1);
|
|
ListView_SetEditSize(plv);
|
|
}
|
|
break;
|
|
|
|
case EN_KILLFOCUS:
|
|
// We lost focus, so dismiss edit and save changes
|
|
// (Note that the owner might reject the change and restart
|
|
// edit mode, which traps the user. Owners need to give the
|
|
// user a way to get out.)
|
|
//
|
|
|
|
//
|
|
// Fix horrible undocumented hanging problem: LVN_ENDLABELEDIT
|
|
// is sent in response to EN_KILLFOCUS, which is send in response
|
|
// to WM_KILLFOCUS, and it is undocumented that you cannot display
|
|
// UI during WM_KILLFOCUS when a journal record hook is active,
|
|
// because the presence of a hook forces serialization of activation,
|
|
// and so when you put up UI, you generate activation changes, which
|
|
// get stuck because you haven't finished responding to the previous
|
|
// WM_KILLFOCUS message yet.
|
|
//
|
|
// See NT bug 414634.
|
|
//
|
|
if (InSendMessage())
|
|
ReplyMessage(0);
|
|
|
|
if (!ListView_DismissEdit(plv, FALSE))
|
|
return;
|
|
break;
|
|
|
|
case HN_BEGINDIALOG: // pen windows is bringing up a dialog
|
|
ASSERT(GetSystemMetrics(SM_PENWINDOWS)); // only on a pen system
|
|
plv->fNoDismissEdit = TRUE;
|
|
break;
|
|
|
|
case HN_ENDDIALOG: // pen windows has destroyed dialog
|
|
ASSERT(GetSystemMetrics(SM_PENWINDOWS)); // only on a pen system
|
|
plv->fNoDismissEdit = FALSE;
|
|
break;
|
|
}
|
|
|
|
// Forward edit control notifications up to parent
|
|
//
|
|
if (IsWindow(hwndCtl))
|
|
FORWARD_WM_COMMAND(plv->ci.hwndParent, id, hwndCtl, codeNotify, SendMessage);
|
|
}
|
|
}
|
|
|
|
void ListView_OnWindowPosChanged(LV* plv, const WINDOWPOS* lpwpos)
|
|
{
|
|
if (!lpwpos || !(lpwpos->flags & SWP_NOSIZE))
|
|
{
|
|
RECT rc;
|
|
int iOldSlots;
|
|
|
|
// Update scrollbars first, since ListView_OnEnsureVisible requires accurate scroll info
|
|
ListView_UpdateScrollBars(plv);
|
|
|
|
if (ListView_IsOwnerData(plv) &&
|
|
ListView_IsSlotView(plv))
|
|
{
|
|
iOldSlots = ListView_GetSlotCount(plv, TRUE, NULL, NULL);
|
|
}
|
|
|
|
GetClientRect(plv->ci.hwnd, &rc);
|
|
plv->sizeClient.cx = rc.right;
|
|
plv->sizeClient.cy = rc.bottom;
|
|
|
|
if (ListView_IsAutoArrangeView(plv))
|
|
{
|
|
// Call off to the arrange function.
|
|
ListView_ArrangeOrSnapToGrid(plv);
|
|
}
|
|
|
|
if (ListView_IsOwnerData(plv))
|
|
{
|
|
plv->rcView.left = RECOMPUTE;
|
|
ListView_Recompute(plv);
|
|
|
|
ListView_DismissEdit(plv, FALSE);
|
|
if (ListView_IsSlotView(plv))
|
|
{
|
|
// Uses the
|
|
int iNewSlots = ListView_GetSlotCount(plv, TRUE, NULL, NULL);
|
|
if ((iNewSlots != iOldSlots) && (ListView_Count(plv) > min(iNewSlots, iOldSlots)))
|
|
RedrawWindow(plv->ci.hwnd, NULL, NULL, RDW_INVALIDATE | RDW_ERASE);
|
|
}
|
|
}
|
|
|
|
ListView_RInitialize(plv, TRUE);
|
|
|
|
if (ListView_IsWatermarked(plv))
|
|
{
|
|
GetClientRect(plv->ci.hwnd, &rc);
|
|
rc.left = rc.right - plv->szWatermark.cx;
|
|
rc.top = rc.bottom - plv->szWatermark.cy;
|
|
InvalidateRect(plv->ci.hwnd, &rc, TRUE);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
void ListView_InvalidateSelectedOrCutOwnerData(LV* plv, ILVRange *plvrangeSel)
|
|
{
|
|
UINT rdwFlags = RDW_INVALIDATE;
|
|
int cItem = ListView_Count(plv);
|
|
DWORD dwType = plv->wView;
|
|
int i;
|
|
RECT rcView;
|
|
|
|
ASSERT(ListView_IsOwnerData(plv));
|
|
ASSERT(plv);
|
|
|
|
GetClientRect(plv->ci.hwnd, &rcView);
|
|
|
|
if (plv->clrTextBk == CLR_NONE
|
|
|| (plv->himl && (plv->clrBk != ImageList_GetBkColor(plv->himl))))
|
|
{
|
|
// always do an erase, otherwise the text background won't paint right
|
|
rdwFlags |= RDW_ERASE;
|
|
}
|
|
|
|
// calculate start of items and end of items visible on the view
|
|
//
|
|
switch (dwType)
|
|
{
|
|
case LV_VIEW_DETAILS:
|
|
i = ListView_RYHitTest(plv, rcView.top);
|
|
cItem = ListView_RYHitTest(plv, rcView.bottom) + 1;
|
|
break;
|
|
|
|
case LV_VIEW_LIST:
|
|
i = ListView_LCalcViewItem(plv, rcView.left, rcView.top);
|
|
cItem = ListView_LCalcViewItem(plv, rcView.right, rcView.bottom) + 1;
|
|
break;
|
|
|
|
default:
|
|
ListView_CalcMinMaxIndex(plv, &rcView, &i, &cItem);
|
|
break;
|
|
}
|
|
|
|
i = max(i, 0);
|
|
|
|
cItem = min(ListView_Count(plv), cItem);
|
|
if (cItem > i)
|
|
{
|
|
ListView_NotifyCacheHint(plv, i, cItem-1);
|
|
}
|
|
|
|
for (; i < cItem; i++)
|
|
{
|
|
if (plvrangeSel->lpVtbl->IsSelected(plvrangeSel, i) == S_OK)
|
|
{
|
|
ListView_InvalidateItem(plv, i, FALSE, rdwFlags);
|
|
}
|
|
}
|
|
}
|
|
|
|
void ListView_RedrawSelection(LV* plv)
|
|
{
|
|
if (ListView_IsOwnerData(plv))
|
|
{
|
|
ListView_InvalidateSelectedOrCutOwnerData(plv, plv->plvrangeSel);
|
|
}
|
|
else
|
|
{
|
|
|
|
int i = -1;
|
|
|
|
while ((i = ListView_OnGetNextItem(plv, i, LVNI_SELECTED)) != -1)
|
|
{
|
|
ListView_InvalidateItem(plv, i, TRUE, RDW_INVALIDATE | RDW_ERASE);
|
|
}
|
|
|
|
|
|
if (ListView_IsReportView(plv))
|
|
{
|
|
int iEnd = ListView_RYHitTest(plv, plv->sizeClient.cy) + 1;
|
|
|
|
iEnd = min(iEnd, ListView_Count(plv));
|
|
|
|
// if we're in report mode, sub items may have selection focus
|
|
for (i = ListView_RYHitTest(plv, 0); i < iEnd; i++)
|
|
{
|
|
int iCol;
|
|
|
|
for (iCol = 1; iCol < plv->cCol; iCol++)
|
|
{
|
|
LISTSUBITEM lsi;
|
|
ListView_GetSubItem(plv, i, iCol, &lsi);
|
|
if (lsi.state & LVIS_SELECTED)
|
|
{
|
|
ListView_InvalidateItem(plv, i, FALSE, RDW_INVALIDATE | RDW_ERASE);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
UpdateWindow(plv->ci.hwnd);
|
|
}
|
|
|
|
void ListView_OnSetFocus(LV* plv, HWND hwndOldFocus)
|
|
{
|
|
ASSERT(gcWheelDelta == 0);
|
|
|
|
// due to the way listview call SetFocus on themselves on buttondown,
|
|
// the window can get a strange sequence of focus messages: first
|
|
// set, then kill, and then set again. since these are not really
|
|
// focus changes, ignore them and only handle "real" cases.
|
|
//
|
|
// But still send out the accessibility notification because USER
|
|
// has already pushed focus back to the listview instead of to the
|
|
// focus item.
|
|
|
|
if (hwndOldFocus == plv->ci.hwnd)
|
|
{
|
|
ListView_NotifyFocusEvent(plv);
|
|
return;
|
|
}
|
|
|
|
plv->flags |= ListView_HideLabels(plv) ? LVF_FOCUSED : LVF_FOCUSED | LVF_UNFOLDED;
|
|
if (IsWindowVisible(plv->ci.hwnd))
|
|
{
|
|
if (plv->iFocus != -1)
|
|
{
|
|
ListView_InvalidateItem(plv, plv->iFocus, TRUE, RDW_INVALIDATE | RDW_ERASE);
|
|
ListView_NotifyFocusEvent(plv);
|
|
}
|
|
|
|
ListView_RedrawSelection(plv);
|
|
}
|
|
|
|
// Let the parent window know that we are getting the focus.
|
|
CCSendNotify(&plv->ci, NM_SETFOCUS, NULL);
|
|
}
|
|
|
|
void ListView_OnKillFocus(LV* plv, HWND hwndNewFocus)
|
|
{
|
|
// Reset wheel scroll amount
|
|
gcWheelDelta = 0;
|
|
|
|
// due to the way listview call SetFocus on themselves on buttondown,
|
|
// the window can get a strange sequence of focus messages: first
|
|
// set, then kill, and then set again. since these are not really
|
|
// focus changes, ignore them and only handle "real" cases.
|
|
if (!plv || hwndNewFocus == plv->ci.hwnd)
|
|
return;
|
|
|
|
ListView_CancelTipTrack(plv);
|
|
|
|
plv->flags &= ~(LVF_FOCUSED|LVF_UNFOLDED);
|
|
|
|
// Blow this off if we are not currently visible (being destroyed!)
|
|
if (IsWindowVisible(plv->ci.hwnd))
|
|
{
|
|
if (plv->iFocus != -1)
|
|
{
|
|
UINT fRedraw = RDW_INVALIDATE;
|
|
if (plv->clrTextBk == CLR_NONE || plv->fListviewShadowText)
|
|
fRedraw |= RDW_ERASE;
|
|
ListView_InvalidateFoldedItem(plv, plv->iFocus, TRUE, fRedraw);
|
|
}
|
|
ListView_RedrawSelection(plv);
|
|
}
|
|
|
|
// Let the parent window know that we are losing the focus.
|
|
CCSendNotify(&plv->ci, NM_KILLFOCUS, NULL);
|
|
IncrementSearchString(&plv->is, 0, NULL);
|
|
}
|
|
|
|
void ListView_DeselectAll(LV* plv, int iDontDeselect)
|
|
{
|
|
int i = -1;
|
|
int nSkipped = 0;
|
|
BOOL fWasSelected = FALSE;
|
|
|
|
if (iDontDeselect != -1)
|
|
{
|
|
if (ListView_OnGetItemState(plv, iDontDeselect, LVIS_SELECTED))
|
|
fWasSelected = TRUE;
|
|
}
|
|
|
|
if (ListView_IsOwnerData(plv))
|
|
{
|
|
|
|
// if there's only one item selected, and that item is the iDontDeselect
|
|
// then our work is done...
|
|
plv->plvrangeSel->lpVtbl->CountIncluded(plv->plvrangeSel, &plv->nSelected);
|
|
if (plv->nSelected == 1 && fWasSelected)
|
|
return;
|
|
|
|
ListView_InvalidateSelectedOrCutOwnerData(plv, plv->plvrangeSel);
|
|
|
|
ListView_OnSetItemState(plv, -1, 0, LVIS_SELECTED);
|
|
if (fWasSelected)
|
|
{
|
|
ListView_OnSetItemState(plv, iDontDeselect, LVIS_SELECTED, LVIS_SELECTED);
|
|
nSkipped = 1;
|
|
}
|
|
|
|
}
|
|
else
|
|
{
|
|
if (iDontDeselect != plv->iFocus)
|
|
{
|
|
ListView_OnSetItemState(plv, plv->iFocus, 0, LVIS_SELECTED);
|
|
}
|
|
|
|
while ((plv->nSelected - nSkipped) && (i = ListView_OnGetNextItem(plv, i, LVNI_SELECTED)) != -1)
|
|
{
|
|
if (i != iDontDeselect)
|
|
{
|
|
ListView_OnSetItemState(plv, i, 0, LVIS_SELECTED);
|
|
}
|
|
else
|
|
{
|
|
if (fWasSelected)
|
|
{
|
|
nSkipped++;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
RIPMSG((plv->nSelected - nSkipped) == 0, "ListView_DeselectAll: Do not refuse a deselect when telling listview to Deselect all.");
|
|
plv->nSelected = nSkipped;
|
|
}
|
|
|
|
// toggle the selection state of an item
|
|
|
|
void ListView_ToggleSelection(LV* plv, int iItem)
|
|
{
|
|
UINT cur_state;
|
|
if (iItem != -1)
|
|
{
|
|
cur_state = ListView_OnGetItemState(plv, iItem, LVIS_SELECTED);
|
|
ListView_OnSetItemState(plv, iItem, cur_state ^ LVIS_SELECTED, LVIS_SELECTED);
|
|
}
|
|
}
|
|
|
|
// Selects (or toggles) a range of items in the list.
|
|
// The curent iFocus is the starting location
|
|
// iItem - is the ending item
|
|
// fToggle - Well set all of the selection state of all of the items to
|
|
// inverse the starting location
|
|
//
|
|
void ListView_SelectRangeTo(LV* plv, int iItem, BOOL fResetRest)
|
|
{
|
|
int iMin, iMax;
|
|
int i = -1;
|
|
UINT uSelVal = LVIS_SELECTED;
|
|
|
|
if (plv->iMark == -1)
|
|
{
|
|
ListView_SetFocusSel(plv, iItem, TRUE, TRUE, FALSE);
|
|
return;
|
|
}
|
|
|
|
if (!fResetRest)
|
|
uSelVal = ListView_OnGetItemState(plv, plv->iMark, LVIS_SELECTED);
|
|
|
|
// If we are in report view or list view we simply walk through the
|
|
// indexes to see which items to select or deselect. otherwise it
|
|
// is is based off of the location of the objects being within the
|
|
// rectangle that is defined by
|
|
if (ListView_IsListView(plv) || (ListView_IsReportView(plv) && !plv->fGroupView))
|
|
{
|
|
iMin = min(iItem, plv->iMark);
|
|
iMax = max(iItem, plv->iMark);
|
|
|
|
if (ListView_IsOwnerData(plv))
|
|
{
|
|
if (fResetRest)
|
|
{
|
|
ListView_DeselectAll(plv, -1);
|
|
}
|
|
|
|
if (iMax > iMin)
|
|
{
|
|
if (LVIS_SELECTED & uSelVal)
|
|
{
|
|
if (FAILED(plv->plvrangeSel->lpVtbl->IncludeRange(plv->plvrangeSel, iMin, iMax)))
|
|
return;
|
|
}
|
|
else
|
|
{
|
|
if (FAILED(plv->plvrangeSel->lpVtbl->ExcludeRange(plv->plvrangeSel, iMin, iMax)))
|
|
return;
|
|
}
|
|
ListView_SendODChangeAndInvalidate(plv, iMin, iMax, uSelVal ^ LVIS_SELECTED, uSelVal);
|
|
}
|
|
else
|
|
{
|
|
ListView_OnSetItemState(plv, iMin, uSelVal, LVIS_SELECTED);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (fResetRest)
|
|
{
|
|
while ((i = ListView_OnGetNextItem(plv, i, LVNI_SELECTED)) != -1)
|
|
{
|
|
if (i < iMin || i > iMax)
|
|
ListView_OnSetItemState(plv, i, 0, LVIS_SELECTED);
|
|
}
|
|
}
|
|
|
|
while (iMin <= iMax)
|
|
{
|
|
ListView_OnSetItemState(plv, iMin, uSelVal, LVIS_SELECTED);
|
|
iMin++;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
RECT rcTemp;
|
|
RECT rcTemp2;
|
|
RECT rcBounding;
|
|
int iFirstItem = (plv->iMark < iItem)? plv->iMark: iItem;
|
|
int iSecondItem = (plv->iMark > iItem)? plv->iMark: iItem;
|
|
|
|
ListView_GetRects(plv, iFirstItem, QUERY_DEFAULT, NULL, NULL, NULL, &rcTemp);
|
|
ListView_GetRects(plv, iSecondItem, QUERY_DEFAULT, NULL, NULL, NULL, &rcTemp2);
|
|
UnionRect(&rcBounding, &rcTemp, &rcTemp2);
|
|
ListView_CalcMinMaxIndex(plv, &rcBounding, &iMin, &iMax);
|
|
|
|
if (ListView_IsOwnerData(plv) && (iMax > iMin))
|
|
{
|
|
if (fResetRest)
|
|
{
|
|
ListView_DeselectAll(plv, -1);
|
|
}
|
|
|
|
iMax = min(iMax, ListView_Count(plv));
|
|
iMin = max(iMin, 0);
|
|
|
|
if (LVIS_SELECTED & uSelVal)
|
|
{
|
|
if (FAILED(plv->plvrangeSel->lpVtbl->IncludeRange(plv->plvrangeSel, iMin, iMax - 1)))
|
|
return;
|
|
}
|
|
else
|
|
{
|
|
if (FAILED(plv->plvrangeSel->lpVtbl->ExcludeRange(plv->plvrangeSel, iMin, iMax - 1)))
|
|
return;
|
|
}
|
|
|
|
ListView_SendODChangeAndInvalidate(plv, iMin, iMax, uSelVal ^ LVIS_SELECTED, uSelVal);
|
|
|
|
}
|
|
else
|
|
{
|
|
int iZ;
|
|
POINT pt;
|
|
RECT rcItem;
|
|
|
|
for (i = 0; i < ListView_Count(plv); i++)
|
|
{
|
|
ListView_GetRects(plv, i, QUERY_DEFAULT, NULL, NULL, NULL, &rcItem);
|
|
pt.x = (rcItem.right + rcItem.left) / 2; // center of item
|
|
pt.y = (rcItem.bottom + rcItem.top) / 2;
|
|
|
|
// Is the item within the y bound of the first and last item?
|
|
if (pt.y > rcTemp.top &&
|
|
pt.y < rcTemp2.bottom)
|
|
{
|
|
// Yes. Check to see if the item is in the first row.
|
|
if (pt.y < rcTemp.bottom)
|
|
{
|
|
// It is. Then check to see if it's before the first item in that row.
|
|
if (pt.x < rcTemp.left)
|
|
{
|
|
// It is. Then this item is not to be selected.
|
|
if (fResetRest)
|
|
ListView_OnSetItemState(plv, i, 0, LVIS_SELECTED);
|
|
|
|
// Continue to the next item
|
|
continue;
|
|
}
|
|
|
|
}
|
|
|
|
// Is the item in the last row?
|
|
if (pt.y > rcTemp2.top)
|
|
{
|
|
// Yes. Is it after the last item in the selection?
|
|
if (pt.x > rcTemp2.right)
|
|
{
|
|
// It is. Then this item is not to be selected.
|
|
if (fResetRest)
|
|
ListView_OnSetItemState(plv, i, 0, LVIS_SELECTED);
|
|
|
|
// Continue to the next item
|
|
continue;
|
|
}
|
|
}
|
|
|
|
// The item is in the selection range. Go ahead and select it
|
|
|
|
if (!ListView_IsOwnerData(plv))
|
|
{
|
|
iZ = ListView_ZOrderIndex(plv, i);
|
|
|
|
if (iZ > 0)
|
|
DPA_InsertPtr(plv->hdpaZOrder, 0, DPA_DeletePtr(plv->hdpaZOrder, iZ));
|
|
}
|
|
|
|
ListView_OnSetItemState(plv, i, uSelVal, LVIS_SELECTED);
|
|
}
|
|
else if (fResetRest)
|
|
ListView_OnSetItemState(plv, i, 0, LVIS_SELECTED);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// makes an item the focused item and optionally selects it
|
|
//
|
|
// in:
|
|
// iItem item to get the focus
|
|
// fSelectAlso select this item as well as set it as the focus
|
|
// fDeselectAll deselect all items first
|
|
// fToggleSel toggle the selection state of the item
|
|
//
|
|
// returns:
|
|
// index of focus item (if focus change was refused)
|
|
|
|
int ListView_SetFocusSel(LV* plv, int iItem, BOOL fSelectAlso, BOOL fDeselectAll, BOOL fToggleSel)
|
|
{
|
|
int iFocus = plv->iFocus;
|
|
|
|
// if we're single sel mode, don't bother with this because
|
|
// the set item will do it for us
|
|
if (!(plv->ci.style & LVS_SINGLESEL) && (fDeselectAll))
|
|
ListView_DeselectAll(plv, -1);
|
|
|
|
if (iItem != plv->iFocus)
|
|
{
|
|
// remove the old focus
|
|
if (plv->iFocus != -1)
|
|
{
|
|
// If he refuses to give up the focus, bail out.
|
|
if (!ListView_OnSetItemState(plv, plv->iFocus, 0, LVIS_FOCUSED))
|
|
return plv->iFocus;
|
|
}
|
|
}
|
|
|
|
if (!ListView_IsOwnerData(plv))
|
|
{
|
|
|
|
if (fSelectAlso)
|
|
{
|
|
if (ListView_IsIconView(plv) || ListView_IsSmallView(plv) || ListView_IsTileView(plv))
|
|
{
|
|
int iZ = ListView_ZOrderIndex(plv, iItem);
|
|
|
|
if (iZ > 0)
|
|
DPA_InsertPtr(plv->hdpaZOrder, 0, DPA_DeletePtr(plv->hdpaZOrder, iZ));
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Ensure that when moving focus that we refresh the previous focus
|
|
owner properly. */
|
|
|
|
if (iFocus != -1 && iFocus != plv->iFocus && (plv->flags & LVF_UNFOLDED))
|
|
ListView_InvalidateFoldedItem(plv, iFocus, FALSE, RDW_INVALIDATE);
|
|
|
|
if (plv->iMark == -1)
|
|
plv->iMark = iItem;
|
|
|
|
SetTimer(plv->ci.hwnd, IDT_SCROLLWAIT, GetDoubleClickTime(), NULL);
|
|
plv->flags |= LVF_SCROLLWAIT;
|
|
|
|
if (fToggleSel)
|
|
{
|
|
ListView_ToggleSelection(plv, iItem);
|
|
ListView_OnSetItemState(plv, iItem, LVIS_FOCUSED, LVIS_FOCUSED);
|
|
}
|
|
else
|
|
{
|
|
UINT flags = ((fSelectAlso || plv->ci.style & LVS_SINGLESEL) ?
|
|
(LVIS_SELECTED | LVIS_FOCUSED) : LVIS_FOCUSED);
|
|
ListView_OnSetItemState(plv, iItem, flags, flags);
|
|
}
|
|
|
|
return iItem;
|
|
}
|
|
|
|
UINT GetLVKeyFlags()
|
|
{
|
|
UINT uFlags = 0;
|
|
|
|
if (GetKeyState(VK_MENU) < 0)
|
|
uFlags |= LVKF_ALT;
|
|
if (GetKeyState(VK_CONTROL) < 0)
|
|
uFlags |= LVKF_CONTROL;
|
|
if (GetKeyState(VK_SHIFT) < 0)
|
|
uFlags |= LVKF_SHIFT;
|
|
|
|
return uFlags;
|
|
}
|
|
|
|
void ListView_OnKey(LV* plv, UINT vk, BOOL fDown, int cRepeat, UINT flags)
|
|
{
|
|
UINT lvni = 0;
|
|
int iNewFocus;
|
|
BOOL fCtlDown;
|
|
BOOL fShiftDown;
|
|
LV_KEYDOWN nm;
|
|
HWND hwnd = plv->ci.hwnd;
|
|
|
|
if (!fDown)
|
|
return;
|
|
|
|
// Cancel manual tip track if any key is pressed
|
|
ListView_CancelTipTrack(plv);
|
|
|
|
// Swap the left and right arrow key if the control is mirrored.
|
|
vk = RTLSwapLeftRightArrows(&plv->ci, vk);
|
|
|
|
//prevent any change in selected items before the dbl click timer goes off
|
|
//so that we don't launch wrong item(s)
|
|
if (plv->exStyle & LVS_EX_ONECLICKACTIVATE && plv->fOneClickHappened && plv->fOneClickOK)
|
|
{
|
|
//if a key is pressed with a mouse click with one click activate and double click
|
|
//timer, we end up setting up a timer and then processing the keydown
|
|
//this causes an item to be launched right away (from this code) and in case
|
|
//of return being pressed it causes double activation
|
|
//prevent these cases:
|
|
if (vk == VK_SHIFT || vk == VK_CONTROL || vk == VK_MENU || vk == VK_RETURN)
|
|
return;
|
|
KillTimer(plv->ci.hwnd, IDT_ONECLICKHAPPENED);
|
|
plv->fOneClickHappened = FALSE;
|
|
CCSendNotify(&plv->ci, LVN_ITEMACTIVATE, &(plv->nmOneClickHappened.hdr));
|
|
if (!IsWindow(hwnd))
|
|
return;
|
|
}
|
|
|
|
// Notify
|
|
nm.wVKey = (WORD) vk;
|
|
nm.flags = flags;
|
|
if (CCSendNotify(&plv->ci, LVN_KEYDOWN, &nm.hdr))
|
|
{
|
|
plv->iPuntChar++;
|
|
return;
|
|
}
|
|
else if (plv->iPuntChar)
|
|
{
|
|
// this is tricky... if we want to punt the char, just increment the
|
|
// count. if we do NOT, then we must clear the queue of WM_CHAR's
|
|
// this is to preserve the iPuntChar to mean "punt the next n WM_CHAR messages
|
|
MSG msg;
|
|
while(plv->iPuntChar && PeekMessage(&msg, plv->ci.hwnd, WM_CHAR, WM_CHAR, PM_REMOVE))
|
|
{
|
|
plv->iPuntChar--;
|
|
}
|
|
ASSERT(!plv->iPuntChar);
|
|
}
|
|
|
|
if (ListView_Count(plv) == 0) // don't blow up on empty list
|
|
return;
|
|
|
|
fCtlDown = GetKeyState(VK_CONTROL) < 0;
|
|
fShiftDown = GetKeyState(VK_SHIFT) < 0;
|
|
|
|
switch (vk)
|
|
{
|
|
case VK_SPACE:
|
|
// If shift (extend) or control (disjoint) select,
|
|
// then toggle selection state of focused item.
|
|
if (fCtlDown)
|
|
{
|
|
plv->iMark = plv->iFocus;
|
|
ListView_ToggleSelection(plv, plv->iFocus);
|
|
plv->iPuntChar++;
|
|
}
|
|
|
|
if (fShiftDown)
|
|
{
|
|
ListView_SelectRangeTo(plv, plv->iFocus, TRUE);
|
|
}
|
|
|
|
if (ListView_CheckBoxes(plv))
|
|
{
|
|
if (plv->iFocus != -1)
|
|
ListView_HandleStateIconClick(plv, plv->iFocus);
|
|
|
|
if (ListView_IsSimpleSelect(plv))
|
|
{
|
|
int iToggle = -1;
|
|
while ((iToggle = ListView_OnGetNextItem(plv, iToggle, LVNI_SELECTED)) != -1)
|
|
{
|
|
if (plv->iFocus != iToggle)
|
|
{
|
|
ListView_HandleStateIconClick(plv, iToggle);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
//notify of navigation key usage
|
|
CCNotifyNavigationKeyUsage(&(plv->ci), UISF_HIDEFOCUS);
|
|
return;
|
|
|
|
case VK_RETURN:
|
|
CCSendNotify(&plv->ci, NM_RETURN, NULL);
|
|
|
|
/// some (comdlg32 for example) destroy on double click
|
|
// we need to bail if that happens because plv is no longer valid
|
|
if (!IsWindow(hwnd))
|
|
return;
|
|
|
|
{
|
|
NMITEMACTIVATE nm;
|
|
|
|
nm.iItem = plv->iFocus;
|
|
nm.iSubItem = 0;
|
|
nm.uChanged = 0;
|
|
nm.ptAction.x = -1;
|
|
nm.ptAction.y = -1;
|
|
nm.uKeyFlags = GetLVKeyFlags();
|
|
CCSendNotify(&plv->ci, LVN_ITEMACTIVATE, &nm.hdr);
|
|
if (!IsWindow(hwnd))
|
|
return;
|
|
}
|
|
//notify of navigation key usage
|
|
CCNotifyNavigationKeyUsage(&(plv->ci), UISF_HIDEFOCUS);
|
|
return;
|
|
|
|
case VK_ADD:
|
|
if (ListView_IsReportView(plv) && (GetKeyState(VK_CONTROL) < 0))
|
|
{
|
|
HCURSOR hcurPrev;
|
|
int i;
|
|
|
|
hcurPrev = SetCursor(LoadCursor(NULL, IDC_WAIT));
|
|
for (i=0; i < plv->cCol; i++)
|
|
{
|
|
ListView_RSetColumnWidth(plv, i, -1);
|
|
}
|
|
|
|
SetCursor(hcurPrev);
|
|
//notify of navigation key usage
|
|
CCNotifyNavigationKeyUsage(&(plv->ci), UISF_HIDEFOCUS);
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (GetKeyState(VK_MENU) < 0)
|
|
return;
|
|
|
|
// For a single selection listview, disable extending the selection
|
|
// by turning off the keyboard modifiers.
|
|
if (plv->ci.style & LVS_SINGLESEL)
|
|
{
|
|
fCtlDown = FALSE;
|
|
fShiftDown = FALSE;
|
|
}
|
|
|
|
//
|
|
// Let the Arrow function attempt to process the key.
|
|
//
|
|
iNewFocus = ListView_Arrow(plv, plv->iFocus, vk);
|
|
|
|
// If control (disjoint) selection, don't change selection.
|
|
// If shift (extend) or control selection, don't deselect all.
|
|
//
|
|
if (iNewFocus != -1)
|
|
{
|
|
if (fShiftDown)
|
|
{
|
|
ListView_SelectRangeTo(plv, iNewFocus, TRUE);
|
|
ListView_SetFocusSel(plv, iNewFocus, FALSE, FALSE, FALSE);
|
|
}
|
|
else
|
|
{
|
|
if (!fCtlDown)
|
|
plv->iMark = iNewFocus;
|
|
ListView_SetFocusSel(plv, iNewFocus, !fCtlDown, !fShiftDown && !fCtlDown, FALSE);
|
|
}
|
|
IncrementSearchString(&plv->is, 0, NULL);
|
|
CCPlaySound(c_szSelect);
|
|
|
|
ListView_OnKeyboardSelected(plv, iNewFocus);
|
|
}
|
|
|
|
// on keyboard movement, scroll immediately.
|
|
if (ListView_CancelScrollWait(plv))
|
|
{
|
|
ListView_OnEnsureVisible(plv, plv->iFocus, FALSE);
|
|
UpdateWindow(plv->ci.hwnd);
|
|
}
|
|
|
|
//notify of navigation key usage
|
|
CCNotifyNavigationKeyUsage(&(plv->ci), UISF_HIDEFOCUS);
|
|
}
|
|
|
|
//
|
|
// LVN_INCREMENTALSEARCH gives the app the opportunity to customize
|
|
// incremental search. For example, if the items are numeric,
|
|
// the app can do numerical search instead of string search.
|
|
//
|
|
// App sets pnmfi->lvfi.lParam to the result of the incremental search,
|
|
// or to -2 to fai the search and just beep.
|
|
//
|
|
// App can return 2 to indicate that all processing should stop, if
|
|
// app wants to take over incremental search completely.
|
|
//
|
|
BOOL ListView_IncrementalSearch(LV *plv, int iStartFrom, LPNMLVFINDITEM pnmfi, int *pi)
|
|
{
|
|
INT_PTR fRc;
|
|
|
|
ASSERT(!(pnmfi->lvfi.flags & LVFI_PARAM));
|
|
pnmfi->lvfi.lParam = -1;
|
|
|
|
fRc = CCSendNotify(&plv->ci, LVN_INCREMENTALSEARCH, &pnmfi->hdr);
|
|
*pi = (int)pnmfi->lvfi.lParam;
|
|
|
|
// Cannot just return fRc because some apps return 1 to all WM_NOTIFY's
|
|
return fRc == 2;
|
|
}
|
|
|
|
// Now only Korean version is interested in incremental search with composition string.
|
|
LPTSTR GET_COMP_STRING(HIMC hImc, DWORD dwFlags)
|
|
{
|
|
LONG iNumComp;
|
|
PTSTR pszCompStr;
|
|
iNumComp = ImmGetCompositionString(hImc, dwFlags, NULL, 0);
|
|
pszCompStr = (PTSTR)LocalAlloc(LPTR, sizeof(TCHAR)*(iNumComp+1));
|
|
if (pszCompStr)
|
|
{
|
|
if (iNumComp)
|
|
ImmGetCompositionString(hImc, dwFlags, pszCompStr, iNumComp+1);
|
|
pszCompStr[iNumComp] = TEXT('\0');
|
|
}
|
|
return pszCompStr;
|
|
}
|
|
|
|
#define FREE_COMP_STRING(pszCompStr) LocalFree((HLOCAL)(pszCompStr))
|
|
|
|
BOOL ListView_OnImeComposition(LV* plv, WPARAM wParam, LPARAM lParam)
|
|
{
|
|
LPTSTR lpsz;
|
|
NMLVFINDITEM nmfi;
|
|
int i;
|
|
int iStartFrom = -1;
|
|
int iLen;
|
|
int iCount;
|
|
HIMC hImc;
|
|
TCHAR *pszCompStr;
|
|
BOOL fRet = TRUE;
|
|
|
|
iCount = ListView_Count(plv);
|
|
|
|
if (!iCount || plv->iFocus == -1)
|
|
return fRet;
|
|
|
|
if (hImc = ImmGetContext(plv->ci.hwnd))
|
|
{
|
|
if (lParam & GCS_RESULTSTR)
|
|
{
|
|
fRet = FALSE;
|
|
pszCompStr = GET_COMP_STRING(hImc, GCS_RESULTSTR);
|
|
if (pszCompStr)
|
|
{
|
|
IncrementSearchImeCompStr(&plv->is, FALSE, pszCompStr, &lpsz);
|
|
FREE_COMP_STRING(pszCompStr);
|
|
}
|
|
}
|
|
if (lParam & GCS_COMPSTR)
|
|
{
|
|
fRet = TRUE;
|
|
pszCompStr = GET_COMP_STRING(hImc, GCS_COMPSTR);
|
|
if (pszCompStr)
|
|
{
|
|
if (IncrementSearchImeCompStr(&plv->is, TRUE, pszCompStr, &lpsz))
|
|
iStartFrom = plv->iFocus;
|
|
else
|
|
iStartFrom = ((plv->iFocus - 1) + iCount)% iCount;
|
|
|
|
nmfi.lvfi.flags = LVFI_SUBSTRING | LVFI_STRING | LVFI_WRAP;
|
|
nmfi.lvfi.psz = lpsz;
|
|
iLen = lstrlen(lpsz);
|
|
|
|
// special case space as the first character
|
|
if ((iLen == 1) && (*lpsz == TEXT(' ')))
|
|
{
|
|
if (plv->iFocus != -1)
|
|
{
|
|
ListView_OnSetItemState(plv, plv->iFocus, LVIS_SELECTED, LVIS_SELECTED);
|
|
IncrementSearchString(&plv->is, 0, NULL);
|
|
}
|
|
//notify of navigation key usage
|
|
CCNotifyNavigationKeyUsage(&(plv->ci), UISF_HIDEFOCUS);
|
|
return fRet;
|
|
}
|
|
|
|
// Give caller full string in case they want to do something custom
|
|
if (ListView_IncrementalSearch(plv, iStartFrom, &nmfi, &i))
|
|
return fRet;
|
|
|
|
if (iLen > 0 && SameChars(lpsz, lpsz[0]))
|
|
{
|
|
// The user has been typing the same char over and over again.
|
|
// Switch from incremental search to Windows 3.1 style search.
|
|
iStartFrom = plv->iFocus;
|
|
nmfi.lvfi.psz = lpsz + iLen - 1;
|
|
}
|
|
|
|
if (i == -1)
|
|
i = ListView_OnFindItem(plv, iStartFrom, &nmfi.lvfi);
|
|
|
|
if (!ListView_IsValidItemNumber(plv, i))
|
|
{
|
|
i = -1;
|
|
}
|
|
|
|
TraceMsg(TF_LISTVIEW, "CIme listsearch %d %s %d", (LPTSTR)lpsz, (LPTSTR)lpsz, i);
|
|
|
|
if (i != -1)
|
|
{
|
|
ListView_SetFocusSel(plv, i, TRUE, TRUE, FALSE);
|
|
plv->iMark = i;
|
|
if (ListView_CancelScrollWait(plv))
|
|
ListView_OnEnsureVisible(plv, i, FALSE);
|
|
}
|
|
else
|
|
{
|
|
// Don't beep on spaces, we use it for selection.
|
|
IncrementSearchBeep(&plv->is);
|
|
}
|
|
|
|
//notify of navigation key usage
|
|
CCNotifyNavigationKeyUsage(&(plv->ci), UISF_HIDEFOCUS);
|
|
FREE_COMP_STRING(pszCompStr);
|
|
}
|
|
}
|
|
ImmReleaseContext(plv->ci.hwnd, hImc);
|
|
}
|
|
return fRet;
|
|
}
|
|
|
|
// REVIEW: We will want to reset ichCharBuf to 0 on certain conditions,
|
|
// such as: focus change, ENTER, arrow key, mouse click, etc.
|
|
//
|
|
void ListView_OnChar(LV* plv, UINT ch, int cRepeat)
|
|
{
|
|
LPTSTR lpsz;
|
|
NMLVFINDITEM nmfi;
|
|
int i;
|
|
int iStartFrom = -1;
|
|
int iLen;
|
|
int iCount;
|
|
|
|
iCount = ListView_Count(plv);
|
|
|
|
if (!iCount)
|
|
return;
|
|
|
|
// Don't search for chars that cannot be in a file name (like ENTER and TAB)
|
|
// The Polish keyboard layout uses CTRL+ALT to
|
|
// enter some normal letters, so don't punt if the CTRL key is down or
|
|
// people in Poland are in trouble! We need to fix this. NTRAID 5262.
|
|
if (ch < TEXT(' '))// || GetKeyState(VK_CONTROL) < 0)
|
|
{
|
|
IncrementSearchString(&plv->is, 0, NULL);
|
|
return;
|
|
}
|
|
|
|
if (IncrementSearchString(&plv->is, ch, &lpsz))
|
|
iStartFrom = plv->iFocus;
|
|
else
|
|
iStartFrom = ((plv->iFocus - 1) + iCount)% iCount;
|
|
|
|
nmfi.lvfi.flags = LVFI_SUBSTRING | LVFI_STRING | LVFI_WRAP;
|
|
nmfi.lvfi.psz = lpsz;
|
|
iLen = lstrlen(lpsz);
|
|
|
|
// special case space as the first character
|
|
if ((iLen == 1) && (*lpsz == ' '))
|
|
{
|
|
if (plv->iFocus != -1)
|
|
{
|
|
ListView_OnSetItemState(plv, plv->iFocus, LVIS_SELECTED, LVIS_SELECTED);
|
|
IncrementSearchString(&plv->is, 0, NULL);
|
|
}
|
|
//notify of navigation key usage
|
|
CCNotifyNavigationKeyUsage(&(plv->ci), UISF_HIDEFOCUS);
|
|
return;
|
|
}
|
|
|
|
// Give caller full string in case they want to do something custom
|
|
if (ListView_IncrementalSearch(plv, iStartFrom, &nmfi, &i))
|
|
return;
|
|
|
|
if (iLen > 0 && SameChars(lpsz, lpsz[0]))
|
|
{
|
|
//
|
|
// The user has been typing the same char over and over again.
|
|
// Switch from incremental search to Windows 3.1 style search.
|
|
//
|
|
iStartFrom = plv->iFocus;
|
|
nmfi.lvfi.psz = lpsz + iLen - 1;
|
|
}
|
|
|
|
if (i == -1)
|
|
i = ListView_OnFindItem(plv, iStartFrom, &nmfi.lvfi);
|
|
|
|
if (!ListView_IsValidItemNumber(plv, i))
|
|
{
|
|
i = -1;
|
|
}
|
|
|
|
TraceMsg(TF_LISTVIEW, "listsearch %d %s %d", (LPTSTR)lpsz, (LPTSTR)lpsz, i);
|
|
|
|
if (i != -1)
|
|
{
|
|
ListView_SetFocusSel(plv, i, TRUE, TRUE, FALSE);
|
|
plv->iMark = i;
|
|
if (ListView_CancelScrollWait(plv))
|
|
ListView_OnEnsureVisible(plv, i, FALSE);
|
|
}
|
|
else
|
|
{
|
|
// Don't beep on spaces, we use it for selection.
|
|
IncrementSearchBeep(&plv->is);
|
|
}
|
|
|
|
//notify of navigation key usage
|
|
CCNotifyNavigationKeyUsage(&(plv->ci), UISF_HIDEFOCUS);
|
|
}
|
|
|
|
BOOL SameChars(LPTSTR lpsz, TCHAR c)
|
|
{
|
|
while (*lpsz)
|
|
{
|
|
if (*lpsz++ != c)
|
|
return FALSE;
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
UINT ListView_OnGetDlgCode(LV* plv, MSG* lpmsg)
|
|
{
|
|
return DLGC_WANTARROWS | DLGC_WANTCHARS;
|
|
}
|
|
|
|
int ListView_ComputeCXItemSize(LV* plv)
|
|
{
|
|
int cxItem;
|
|
|
|
cxItem = 16 * plv->cxLabelChar + plv->cxSmIcon;
|
|
if (cxItem == 0)
|
|
{
|
|
cxItem = g_cxBorder;
|
|
}
|
|
|
|
ASSERT(cxItem != 0);
|
|
return cxItem;
|
|
}
|
|
|
|
int ListView_ComputeCYItemSize(LV* plv)
|
|
{
|
|
int cyItem;
|
|
|
|
cyItem = max(plv->cyLabelChar, plv->cySmIcon);
|
|
|
|
if (plv->himlState)
|
|
{
|
|
cyItem = max(cyItem, plv->cyState);
|
|
}
|
|
|
|
cyItem += g_cyBorder;
|
|
|
|
ASSERT(cyItem != 0);
|
|
return cyItem;
|
|
}
|
|
|
|
void ListView_InvalidateCachedLabelSizes(LV* plv)
|
|
{
|
|
int i;
|
|
|
|
ListView_InvalidateTTLastHit(plv, plv->iTTLastHit);
|
|
|
|
// Label wrapping has changed, so we need to invalidate the
|
|
// size of the items, such that they will be recomputed.
|
|
//
|
|
if (!ListView_IsOwnerData(plv))
|
|
{
|
|
for (i = ListView_Count(plv) - 1; i >= 0; i--)
|
|
{
|
|
LISTITEM* pitem = ListView_FastGetItemPtr(plv, i);
|
|
ListView_SetSRecompute(pitem);
|
|
}
|
|
}
|
|
plv->rcView.left = RECOMPUTE;
|
|
|
|
if ((plv->ci.style & LVS_OWNERDRAWFIXED) && ListView_IsReportView(plv))
|
|
plv->cyItemSave = ListView_ComputeCYItemSize(plv);
|
|
else
|
|
{
|
|
plv->cyItem = ListView_ComputeCYItemSize(plv);
|
|
}
|
|
}
|
|
|
|
|
|
void ListView_OnStyleChanging(LV* plv, UINT gwl, LPSTYLESTRUCT pinfo)
|
|
{
|
|
if (gwl == GWL_STYLE)
|
|
{
|
|
// Don't allow LVS_OWNERDATA to change after creation
|
|
DWORD stylePreserve = LVS_OWNERDATA;
|
|
|
|
// Don't allow a LVS_EX_REGIONAL listview to change type, since
|
|
// it must be LVS_ICON
|
|
// Similarly, HideLabels only works in large icon mode so keep the type.
|
|
if ((plv->exStyle & LVS_EX_REGIONAL) || ListView_HideLabels(plv))
|
|
stylePreserve |= LVS_TYPEMASK;
|
|
|
|
// Preserve the bits that must be preserved
|
|
pinfo->styleNew ^= (pinfo->styleNew ^ pinfo->styleOld) & stylePreserve;
|
|
|
|
// If we're in group view, then listview must be in autoarrange
|
|
if (plv->fGroupView)
|
|
{
|
|
pinfo->styleNew |= LVS_AUTOARRANGE;
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
WORD MapViewStyle(DWORD style)
|
|
{
|
|
if (style == LVS_LIST)
|
|
return LV_VIEW_LIST;
|
|
if (style == LVS_SMALLICON)
|
|
return LV_VIEW_SMALLICON;
|
|
if (style == LVS_REPORT)
|
|
return LV_VIEW_DETAILS;
|
|
|
|
return LV_VIEW_ICON;
|
|
}
|
|
|
|
void ListView_OnStyleChanged(LV* plv, UINT gwl, LPSTYLESTRUCT pinfo)
|
|
{
|
|
// Style changed: redraw everything...
|
|
//
|
|
// try to do this smartly, avoiding unnecessary redraws
|
|
if (gwl == GWL_STYLE)
|
|
{
|
|
BOOL fRedraw = FALSE, fShouldScroll = FALSE;
|
|
DWORD changeFlags, styleOld;
|
|
|
|
ListView_DismissEdit(plv, FALSE);
|
|
|
|
changeFlags = plv->ci.style ^ pinfo->styleNew;
|
|
styleOld = plv->ci.style;
|
|
|
|
// (dli) Setting the small icon width here and only in the case when we go
|
|
// from large icon view to some other view because of three reasons:
|
|
// 1. According to chee, we want to set this before we change the style bit in
|
|
// plv or after we scale.
|
|
// 2. We don't want to do it after we scale because we want to set the width to
|
|
// the maximum value so that the items in this listview do not cover each other
|
|
// 3. we do it from large icon view because large icon view has fixed width for
|
|
// each item, small icon view width can be scaled.
|
|
//
|
|
if ((changeFlags & LVS_TYPEMASK) && (plv->wView == LV_VIEW_ICON))
|
|
ListView_ISetColumnWidth(plv, 0,
|
|
LV_GetNewColWidth(plv, 0, ListView_Count(plv)-1), FALSE);
|
|
|
|
plv->ci.style = pinfo->styleNew; // change our version
|
|
|
|
if (changeFlags & (WS_BORDER | WS_CAPTION | WS_THICKFRAME))
|
|
{
|
|
// the changing of these bits affect the size of the window
|
|
// but not until after this message is handled
|
|
// so post ourself a message.
|
|
PostMessage(plv->ci.hwnd, LVMP_WINDOWPOSCHANGED, 0, 0);
|
|
}
|
|
|
|
if (changeFlags & LVS_NOCOLUMNHEADER)
|
|
{
|
|
if (plv->hwndHdr)
|
|
{
|
|
SetWindowBits(plv->hwndHdr, GWL_STYLE, HDS_HIDDEN,
|
|
(plv->ci.style & LVS_NOCOLUMNHEADER) ? HDS_HIDDEN : 0);
|
|
|
|
fRedraw = TRUE;
|
|
fShouldScroll = TRUE;
|
|
}
|
|
}
|
|
|
|
if (changeFlags & LVS_NOLABELWRAP)
|
|
{
|
|
ListView_InvalidateCachedLabelSizes(plv);
|
|
fShouldScroll = TRUE;
|
|
fRedraw = TRUE;
|
|
}
|
|
|
|
if (changeFlags & LVS_TYPEMASK)
|
|
{
|
|
WORD wViewOld = plv->wView;
|
|
plv->wView = MapViewStyle(plv->ci.style & LVS_TYPEMASK);
|
|
ListView_TypeChange(plv, wViewOld, (BOOL)BOOLIFY(styleOld & LVS_OWNERDRAWFIXED));
|
|
fShouldScroll = TRUE;
|
|
fRedraw = TRUE;
|
|
}
|
|
|
|
if (changeFlags & LVS_AUTOARRANGE)
|
|
{
|
|
if (plv->ci.style & LVS_AUTOARRANGE)
|
|
{
|
|
// Turned on.
|
|
ListView_OnArrange(plv, LVA_DEFAULT);
|
|
fRedraw = TRUE;
|
|
}
|
|
else
|
|
{
|
|
// Turned off. Nuke insertmark, because that's not allowed when
|
|
// auto-arrange is off.
|
|
LVINSERTMARK lvim = {0};
|
|
lvim.cbSize = sizeof(LVINSERTMARK);
|
|
lvim.iItem = -1;
|
|
ListView_OnSetInsertMark(plv, &lvim);
|
|
}
|
|
}
|
|
|
|
// previously, this was the else to
|
|
// (changeFlags & LVS_AUTOARRANGE && (plv->ci.style & LVS_AUTOARRANGE))
|
|
// I'm not sure that was really the right thing..
|
|
if (fShouldScroll)
|
|
{
|
|
// Else we would like to make the most important item to still
|
|
// be visible. So first we will look for a cursorered item
|
|
// if this fails, we will look for the first selected item,
|
|
// else we will simply ask for the first item (assuming the
|
|
// count > 0
|
|
//
|
|
int i;
|
|
|
|
// And make sure the scrollbars are up to date Note this
|
|
// also updates some variables that some views need
|
|
ListView_UpdateScrollBars(plv);
|
|
|
|
i = (plv->iFocus >= 0) ? plv->iFocus : ListView_OnGetNextItem(plv, -1, LVNI_SELECTED);
|
|
if ((i == -1) && (ListView_Count(plv) > 0))
|
|
i = 0;
|
|
|
|
if (i != -1)
|
|
ListView_OnEnsureVisible(plv, i, TRUE);
|
|
}
|
|
|
|
if (fRedraw)
|
|
RedrawWindow(plv->ci.hwnd, NULL, NULL, RDW_INVALIDATE | RDW_ERASE);
|
|
}
|
|
else if (gwl == GWL_EXSTYLE)
|
|
{
|
|
//
|
|
// If the RTL_MIRROR extended style bit had changed, let's
|
|
// repaint the control window.
|
|
//
|
|
if ((plv->ci.dwExStyle&RTL_MIRRORED_WINDOW) != (pinfo->styleNew&RTL_MIRRORED_WINDOW))
|
|
RedrawWindow(plv->ci.hwnd, NULL, NULL, RDW_INVALIDATE | RDW_ERASE);
|
|
|
|
// Save the new ex-style bits
|
|
plv->ci.dwExStyle = pinfo->styleNew;
|
|
}
|
|
|
|
// Change of styles also changes tooltip policy, so pop it
|
|
ListView_PopBubble(plv);
|
|
}
|
|
|
|
void ListView_TypeChange(LV* plv, WORD wViewOld, BOOL fOwnerDrawFixed)
|
|
{
|
|
RECT rc;
|
|
int i;
|
|
//
|
|
// Invalidate all cached string metrics because customdraw clients
|
|
// may draw differently depending on the type. This happens more
|
|
// often than you might think, not on purpose, but because apps are
|
|
// buggy.
|
|
//
|
|
if (!ListView_IsOwnerData(plv))
|
|
{
|
|
for (i = 0; i < ListView_Count(plv); i++)
|
|
{
|
|
LISTITEM *pitem = ListView_FastGetItemPtr(plv, i);
|
|
ListView_SetSRecompute(pitem);
|
|
}
|
|
}
|
|
|
|
switch (wViewOld)
|
|
{
|
|
case LV_VIEW_DETAILS:
|
|
ShowWindow(plv->hwndHdr, SW_HIDE);
|
|
if (fOwnerDrawFixed)
|
|
{
|
|
// swap cyItem and cyFixed;
|
|
int temp = plv->cyItem;
|
|
plv->cyItem = plv->cyItemSave;
|
|
plv->cyItemSave = temp;
|
|
}
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
plv->ptOrigin.x = 0;
|
|
plv->ptOrigin.y = 0;
|
|
plv->ptlRptOrigin.x = 0;
|
|
plv->ptlRptOrigin.y = 0;
|
|
plv->rcView.left = RECOMPUTE;
|
|
|
|
_ListView_RecomputeEx(plv, NULL, 0, TRUE);
|
|
|
|
// Now handle any special setup needed for the new view
|
|
switch (plv->wView)
|
|
{
|
|
case LV_VIEW_LIST:
|
|
// We may need to resize the columns
|
|
ListView_MaybeResizeListColumns(plv, 0, ListView_Count(plv)-1);
|
|
break;
|
|
|
|
case LV_VIEW_DETAILS:
|
|
// if it's owner draw fixed, we may have to do funky stuff
|
|
if (wViewOld != LV_VIEW_DETAILS)
|
|
{
|
|
plv->cyItemSave = plv->cyItem;
|
|
}
|
|
ListView_RInitialize(plv, FALSE);
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
ListView_InvalidateTTLastHit(plv, plv->iTTLastHit);
|
|
|
|
GetClientRect(plv->ci.hwnd, &rc);
|
|
plv->sizeClient.cx = rc.right;
|
|
plv->sizeClient.cy = rc.bottom;
|
|
}
|
|
|
|
int ListView_OnHitTest(LV* plv, LV_HITTESTINFO* pinfo)
|
|
{
|
|
UINT flags;
|
|
int x, y;
|
|
|
|
if (!pinfo) return -1;
|
|
|
|
x = pinfo->pt.x;
|
|
y = pinfo->pt.y;
|
|
|
|
pinfo->iItem = -1;
|
|
flags = 0;
|
|
if (x < 0)
|
|
flags |= LVHT_TOLEFT;
|
|
else if (x >= plv->sizeClient.cx)
|
|
flags |= LVHT_TORIGHT;
|
|
if (y < 0)
|
|
flags |= LVHT_ABOVE;
|
|
else if (y >= plv->sizeClient.cy)
|
|
flags |= LVHT_BELOW;
|
|
|
|
if (flags == 0)
|
|
{
|
|
pinfo->iItem = _ListView_ItemHitTest(plv, x, y, &flags, NULL);
|
|
}
|
|
|
|
pinfo->flags = flags;
|
|
|
|
if (pinfo->iItem >= ListView_Count(plv))
|
|
{
|
|
pinfo->iItem = -1;
|
|
pinfo->flags = LVHT_NOWHERE;
|
|
}
|
|
return pinfo->iItem;
|
|
}
|
|
|
|
int ScrollAmount(int large, int iSmall, int unit)
|
|
{
|
|
|
|
return (((large - iSmall) + (unit - 1)) / unit) * unit;
|
|
}
|
|
|
|
// NOTE: this is duplicated in shell32.dll
|
|
//
|
|
// checks to see if we are at the end position of a scroll bar
|
|
// to avoid scrolling when not needed (avoid flashing)
|
|
//
|
|
// in:
|
|
// code SB_VERT or SB_HORZ
|
|
// bDown FALSE is up or left
|
|
// TRUE is down or right
|
|
BOOL CanScroll(LV* plv, int code, BOOL bDown)
|
|
{
|
|
SCROLLINFO si;
|
|
|
|
si.cbSize = sizeof(SCROLLINFO);
|
|
si.fMask = SIF_RANGE | SIF_PAGE | SIF_POS;
|
|
|
|
if (ListView_GetScrollInfo(plv, code, &si))
|
|
{
|
|
if (bDown)
|
|
{
|
|
if (si.nPage)
|
|
si.nMax -= (si.nPage - 1);
|
|
return si.nPos < si.nMax;
|
|
}
|
|
else
|
|
{
|
|
return si.nPos > si.nMin;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
// detect if we should auto scroll the window
|
|
//
|
|
// in:
|
|
// pt cursor pos in hwnd's client coords
|
|
// out:
|
|
// pdx, pdy ammount scrolled in x and y
|
|
//
|
|
// REVIEW, this should make sure a certain amount of time has passed
|
|
// before scrolling.
|
|
|
|
void ScrollDetect(LV* plv, POINT pt, int *pdx, int *pdy)
|
|
{
|
|
int dx, dy;
|
|
|
|
*pdx = *pdy = 0;
|
|
|
|
if (!(plv->ci.style & (WS_HSCROLL | WS_VSCROLL)))
|
|
return;
|
|
|
|
dx = dy = plv->cyIcon / 16;
|
|
if (ListView_IsReportView(plv))
|
|
{
|
|
if (!plv->fGroupView) // Groupview is always in pixels
|
|
dy = plv->cyItem; // we scroll in units of items...
|
|
|
|
if (!dx)
|
|
dx = plv->cxSmIcon;
|
|
}
|
|
|
|
|
|
if (ListView_IsListView(plv))
|
|
dx = plv->cxItem;
|
|
|
|
if (!dx)
|
|
dx = 1;
|
|
|
|
if (!dy)
|
|
dy = 1;
|
|
|
|
// we need to check if we can scroll before acutally doing it
|
|
// since the selection rect is adjusted based on how much
|
|
// we scroll by
|
|
|
|
if (plv->ci.style & WS_VSCROLL) // scroll vertically?
|
|
{
|
|
|
|
if (pt.y >= plv->sizeClient.cy)
|
|
{
|
|
if (CanScroll(plv, SB_VERT, TRUE))
|
|
*pdy = ScrollAmount(pt.y, plv->sizeClient.cy, dy); // down
|
|
}
|
|
else if (pt.y <= 0)
|
|
{
|
|
if (CanScroll(plv, SB_VERT, FALSE))
|
|
*pdy = -ScrollAmount(0, pt.y, dy); // up
|
|
}
|
|
}
|
|
|
|
if (plv->ci.style & WS_HSCROLL) // horizontally
|
|
{
|
|
if (pt.x >= plv->sizeClient.cx)
|
|
{
|
|
if (CanScroll(plv, SB_HORZ, TRUE))
|
|
*pdx = ScrollAmount(pt.x, plv->sizeClient.cx, dx); // right
|
|
}
|
|
else if (pt.x <= 0)
|
|
{
|
|
if (CanScroll(plv, SB_HORZ, FALSE))
|
|
*pdx = -ScrollAmount(0, pt.x, dx); // left
|
|
}
|
|
}
|
|
|
|
// REARCHITECT: this will potentially scroll outside the bounds of the
|
|
// listview. we should bound the scroll amount in CanScroll()
|
|
// or ScrollAmount().
|
|
|
|
if (*pdx || *pdy)
|
|
{
|
|
ListView_ValidateScrollParams(plv, pdx, pdy);
|
|
}
|
|
}
|
|
|
|
#define swap(pi1, pi2) {int i = *(pi1) ; *(pi1) = *(pi2) ; *(pi2) = i ;}
|
|
|
|
void OrderRect(RECT *prc)
|
|
{
|
|
if (prc->left > prc->right)
|
|
swap(&prc->left, &prc->right);
|
|
|
|
if (prc->bottom < prc->top)
|
|
swap(&prc->bottom, &prc->top);
|
|
}
|
|
|
|
// in:
|
|
// x, y starting point in client coords
|
|
|
|
#define SCROLL_FREQ (GetDoubleClickTime()/2) // 1/5 of a second between scrolls
|
|
|
|
BOOL ShouldScroll(LV* plv, LPPOINT ppt, LPRECT lprc)
|
|
{
|
|
ASSERT(ppt);
|
|
|
|
if (plv->ci.style & WS_VSCROLL)
|
|
{
|
|
if (ppt->y >= lprc->bottom)
|
|
{
|
|
if (CanScroll(plv, SB_VERT, TRUE))
|
|
return TRUE;
|
|
}
|
|
else if (ppt->y <= lprc->top)
|
|
{
|
|
if (CanScroll(plv, SB_VERT, FALSE))
|
|
return TRUE;
|
|
}
|
|
}
|
|
|
|
if (plv->ci.style & WS_HSCROLL)
|
|
{
|
|
if (ppt->x >= lprc->right)
|
|
{
|
|
if (CanScroll(plv, SB_HORZ, TRUE))
|
|
return TRUE;
|
|
}
|
|
else if (ppt->x <= lprc->left)
|
|
{
|
|
if (CanScroll(plv, SB_HORZ, FALSE))
|
|
return TRUE;
|
|
}
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
// Listview is "Alpha Capable" if:
|
|
// Colors >= 16bpp (Needed for alpha)
|
|
// The Listview is double buffered (Needed for flicker)
|
|
// The use has "Show window contents while dragging" (Needed to turn off on slow machines)
|
|
// NOTE: g_fDragFullWindows is turned off in comctl32 when running a remote session
|
|
BOOL ListView_IsAlphaMarqueeCapable(LV* plv)
|
|
{
|
|
BOOL fAlphaCapable = FALSE;
|
|
if (ListView_IsDoubleBuffer(plv))
|
|
{
|
|
if (AreAllMonitorsAtLeast(16))
|
|
{
|
|
fAlphaCapable = plv->fListviewAlphaSelect;
|
|
}
|
|
}
|
|
|
|
return fAlphaCapable;
|
|
}
|
|
|
|
void ListView_DragSelect(LV *plv, int x, int y)
|
|
{
|
|
RECT rc, rcWindow, rcOld, rcUnion, rcTemp2, rcClip;
|
|
POINT pt;
|
|
MSG32 msg32;
|
|
HDC hdc;
|
|
HWND hwnd = plv->ci.hwnd;
|
|
int i, iEnd, dx, dy;
|
|
BOOL bInOld, bInNew = FALSE, bLocked = FALSE;
|
|
DWORD dwTime, dwNewTime;
|
|
HRGN hrgnUpdate = NULL, hrgnLV = NULL;
|
|
BOOL fAlphaMarquee = ListView_IsAlphaMarqueeCapable(plv);
|
|
|
|
rc.left = rc.right = x;
|
|
rc.top = rc.bottom = y;
|
|
|
|
rcOld = rc;
|
|
|
|
UpdateWindow(plv->ci.hwnd);
|
|
|
|
if (plv->exStyle & LVS_EX_REGIONAL)
|
|
{
|
|
if ((hrgnUpdate = CreateRectRgn(0,0,0,0)) &&
|
|
(hrgnLV = CreateRectRgn(0,0,0,0)) &&
|
|
(LockWindowUpdate(GetParent(hwnd))))
|
|
{
|
|
hdc = GetDCEx(hwnd, NULL, DCX_PARENTCLIP | DCX_LOCKWINDOWUPDATE);
|
|
bLocked = TRUE;
|
|
}
|
|
else
|
|
{
|
|
goto BailOut;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
hdc = GetDC(hwnd);
|
|
}
|
|
|
|
SetCapture(hwnd);
|
|
|
|
if (fAlphaMarquee)
|
|
{
|
|
plv->flags |= LVF_MARQUEE;
|
|
plv->rcMarquee = rc;
|
|
InvalidateRect(plv->ci.hwnd, &plv->rcMarquee, TRUE);
|
|
}
|
|
else
|
|
{
|
|
DrawFocusRect(hdc, &rc);
|
|
}
|
|
|
|
GetClientRect(hwnd, &rcClip);
|
|
GetWindowRect(hwnd, &rcWindow);
|
|
|
|
dwTime = GetTickCount();
|
|
|
|
for (;;)
|
|
{
|
|
// WM_CANCELMODE messages will unset the capture, in that
|
|
// case I want to exit this loop
|
|
if (GetCapture() != hwnd)
|
|
{
|
|
break;
|
|
}
|
|
|
|
if (!PeekMessage32(&msg32, NULL, 0, 0, PM_REMOVE, TRUE))
|
|
{
|
|
// if the cursor is outside of the window rect
|
|
// we need to generate messages to make autoscrolling
|
|
// keep going
|
|
|
|
if (!PtInRect(&rcWindow, msg32.pt) &&
|
|
ShouldScroll(plv, &msg32.pt, &rcWindow))
|
|
{
|
|
SetCursorPos(msg32.pt.x, msg32.pt.y);
|
|
}
|
|
else
|
|
{
|
|
WaitMessage();
|
|
}
|
|
continue;
|
|
}
|
|
|
|
|
|
// See if the application wants to process the message...
|
|
if (CallMsgFilter32(&msg32, MSGF_COMMCTRL_DRAGSELECT, TRUE) != 0)
|
|
continue;
|
|
|
|
switch (msg32.message)
|
|
{
|
|
|
|
case WM_LBUTTONUP:
|
|
case WM_RBUTTONUP:
|
|
case WM_LBUTTONDOWN:
|
|
case WM_MBUTTONDOWN:
|
|
case WM_MBUTTONUP:
|
|
case WM_RBUTTONDOWN:
|
|
CCReleaseCapture(&plv->ci);
|
|
goto EndOfLoop;
|
|
|
|
|
|
case WM_TIMER:
|
|
if (msg32.wParam != IDT_MARQUEE)
|
|
goto DoDefault;
|
|
// else fall through
|
|
|
|
case WM_MOUSEMOVE:
|
|
{
|
|
int dMax = -1;
|
|
pt = msg32.pt;
|
|
ScreenToClient(hwnd, &pt);
|
|
|
|
dwNewTime = GetTickCount();
|
|
// if (1 || (dwNewTime - dwTime) > SCROLL_FREQ)
|
|
// {
|
|
dwTime = dwNewTime; // reset scroll timer
|
|
ScrollDetect(plv, pt, &dx, &dy);
|
|
// }
|
|
// else
|
|
// {
|
|
// dx = dy = 0;
|
|
// }
|
|
//SetTimer(plv->ci.hwnd, IDT_MARQUEE, SCROLL_FREQ, NULL);
|
|
|
|
y -= dy; // scroll up/down
|
|
x -= dx; // scroll left/right
|
|
|
|
rc.left = x;
|
|
rc.top = y;
|
|
rc.right = pt.x;
|
|
rc.bottom = pt.y;
|
|
|
|
// clip drag rect to the window
|
|
//
|
|
if (rc.right > rcClip.right)
|
|
rc.right = rcClip.right;
|
|
if (rc.right < rcClip.left)
|
|
rc.right = rcClip.left;
|
|
if (rc.bottom > rcClip.bottom)
|
|
rc.bottom = rcClip.bottom;
|
|
if (rc.bottom < rcClip.top)
|
|
rc.bottom = rcClip.top;
|
|
|
|
OrderRect(&rc);
|
|
|
|
if (EqualRect(&rc, &rcOld))
|
|
break;
|
|
|
|
// move the old rect
|
|
if (!fAlphaMarquee)
|
|
{
|
|
DrawFocusRect(hdc, &rcOld); // erase old
|
|
}
|
|
|
|
if (dx || dy)
|
|
ListView_OnScroll(plv, dx, dy);
|
|
OffsetRect(&rcOld, -dx, -dy);
|
|
|
|
//
|
|
// For Report and List view, we can speed things up by
|
|
// only searching through those items that are visible. We
|
|
// use the hittest to calculate the first item to paint.
|
|
// REARCHITECT:: We are using state specific info here...
|
|
//
|
|
UnionRect(&rcUnion, &rc, &rcOld);
|
|
|
|
if (ListView_IsReportView(plv) && !plv->fGroupView)
|
|
{
|
|
i = (int)((plv->ptlRptOrigin.y + rcUnion.top - plv->yTop)
|
|
/ plv->cyItem);
|
|
iEnd = (int)((plv->ptlRptOrigin.y + rcUnion.bottom - plv->yTop)
|
|
/ plv->cyItem) + 1;
|
|
}
|
|
|
|
else if (ListView_IsListView(plv))
|
|
{
|
|
i = ((plv->xOrigin + rcUnion.left)/ plv->cxItem)
|
|
* plv->cItemCol + rcUnion.top / plv->cyItem;
|
|
|
|
iEnd = ((plv->xOrigin + rcUnion.right)/ plv->cxItem)
|
|
* plv->cItemCol + rcUnion.bottom / plv->cyItem + 1;
|
|
}
|
|
|
|
else
|
|
{
|
|
if (ListView_IsOwnerData(plv))
|
|
{
|
|
ListView_CalcMinMaxIndex(plv, &rcUnion, &i, &iEnd);
|
|
}
|
|
else
|
|
{
|
|
i = 0;
|
|
iEnd = ListView_Count(plv);
|
|
}
|
|
}
|
|
|
|
// make sure our endpoint is in range.
|
|
if (iEnd > ListView_Count(plv))
|
|
iEnd = ListView_Count(plv);
|
|
|
|
if (i < 0)
|
|
i = 0;
|
|
|
|
if (ListView_IsOwnerData(plv) && (i < iEnd))
|
|
{
|
|
ListView_NotifyCacheHint(plv, i, iEnd-1);
|
|
}
|
|
|
|
if (bInNew && !(msg32.wParam & (MK_CONTROL | MK_SHIFT)))
|
|
{
|
|
plv->iMark = -1;
|
|
}
|
|
|
|
for (; i < iEnd; i++)
|
|
{
|
|
RECT dummy;
|
|
ListView_GetRects(plv, i, QUERY_DEFAULT, NULL, NULL, NULL, &rcTemp2);
|
|
|
|
// don't do this infaltion if we're in report&full row mode
|
|
// in that case, just touching is good enough
|
|
if (!(ListView_IsReportView(plv) && ListView_FullRowSelect(plv)))
|
|
{
|
|
int cxInflate = (rcTemp2.right - rcTemp2.left) / 4;
|
|
if (ListView_IsListView(plv))
|
|
{
|
|
cxInflate = min(cxInflate, plv->cxSmIcon);
|
|
}
|
|
InflateRect(&rcTemp2, -cxInflate, -(rcTemp2.bottom - rcTemp2.top) / 4);
|
|
}
|
|
|
|
bInOld = (IntersectRect(&dummy, &rcOld, &rcTemp2) != 0);
|
|
bInNew = (IntersectRect(&dummy, &rc, &rcTemp2) != 0);
|
|
|
|
if (msg32.wParam & MK_CONTROL)
|
|
{
|
|
if (bInOld != bInNew)
|
|
{
|
|
ListView_ToggleSelection(plv, i);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// was there a change?
|
|
if (bInOld != bInNew)
|
|
{
|
|
ListView_OnSetItemState(plv, i, bInOld ? 0 : LVIS_SELECTED, LVIS_SELECTED);
|
|
}
|
|
|
|
// if no alternate keys are down.. set the mark to
|
|
// the item furthest from the cursor
|
|
if (bInNew && !(msg32.wParam & (MK_CONTROL | MK_SHIFT)))
|
|
{
|
|
int dItem;
|
|
dItem = (rcTemp2.left - pt.x) * (rcTemp2.left - pt.x) +
|
|
(rcTemp2.top - pt.y) * (rcTemp2.top - pt.y);
|
|
// if it's further away, set this as the mark
|
|
//DebugMsg(TF_LISTVIEW, "dItem = %d, dMax = %d", dItem, dMax);
|
|
if (dItem > dMax)
|
|
{
|
|
//DebugMsg(TF_LISTVIEW, "taking dItem .. iMark = %d", i);
|
|
dMax = dItem;
|
|
plv->iMark = i;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (fAlphaMarquee)
|
|
{
|
|
RECT rcInvalid;
|
|
UnionRect(&rcInvalid, &rcOld, &rc);
|
|
InflateRect(&rcInvalid, 1, 1);
|
|
|
|
plv->flags |= LVF_MARQUEE;
|
|
plv->rcMarquee = rc;
|
|
|
|
InvalidateRect(plv->ci.hwnd, &rcInvalid, TRUE);
|
|
}
|
|
|
|
//DebugMsg(TF_LISTVIEW, "Final iMark = %d", plv->iMark);
|
|
if (bLocked)
|
|
{
|
|
if (GetUpdateRgn(plv->ci.hwnd, hrgnUpdate, FALSE) > NULLREGION)
|
|
{
|
|
ValidateRect(plv->ci.hwnd, NULL);
|
|
GetWindowRgn(plv->ci.hwnd, hrgnLV);
|
|
CombineRgn(hrgnUpdate, hrgnUpdate, hrgnLV, RGN_AND);
|
|
SelectClipRgn(hdc, hrgnUpdate);
|
|
SendMessage(plv->ci.hwnd, WM_PRINTCLIENT, (WPARAM)hdc, 0);
|
|
SelectClipRgn(hdc, NULL);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
UpdateWindow(plv->ci.hwnd); // make selection draw
|
|
}
|
|
|
|
|
|
if (!fAlphaMarquee)
|
|
{
|
|
DrawFocusRect(hdc, &rc);
|
|
}
|
|
|
|
rcOld = rc;
|
|
break;
|
|
}
|
|
|
|
case WM_KEYDOWN:
|
|
switch (msg32.wParam)
|
|
{
|
|
case VK_ESCAPE:
|
|
ListView_DeselectAll(plv, -1);
|
|
goto EndOfLoop;
|
|
}
|
|
case WM_CHAR:
|
|
case WM_KEYUP:
|
|
// don't process thay keyboard stuff during marquee
|
|
break;
|
|
|
|
|
|
default:
|
|
|
|
// don't process mouse wheel stuff
|
|
if (msg32.message == g_msgMSWheel)
|
|
break;
|
|
|
|
DoDefault:
|
|
TranslateMessage32(&msg32, TRUE);
|
|
DispatchMessage32(&msg32, TRUE);
|
|
}
|
|
}
|
|
|
|
EndOfLoop:
|
|
|
|
plv->flags &= ~LVF_MARQUEE;
|
|
|
|
if (fAlphaMarquee)
|
|
{
|
|
InvalidateRect(plv->ci.hwnd, &rcOld, TRUE);
|
|
}
|
|
else
|
|
{
|
|
DrawFocusRect(hdc, &rcOld); // erase old
|
|
}
|
|
|
|
ReleaseDC(hwnd, hdc);
|
|
|
|
BailOut:
|
|
if (hrgnUpdate)
|
|
DeleteObject(hrgnUpdate);
|
|
if (hrgnLV)
|
|
DeleteObject(hrgnLV);
|
|
if (bLocked)
|
|
LockWindowUpdate(NULL);
|
|
}
|
|
|
|
|
|
#define SHIFT_DOWN(keyFlags) (keyFlags & MK_SHIFT)
|
|
#define CONTROL_DOWN(keyFlags) (keyFlags & MK_CONTROL)
|
|
#define RIGHTBUTTON(keyFlags) (keyFlags & MK_RBUTTON)
|
|
|
|
void ListView_ButtonSelect(LV* plv, int iItem, UINT keyFlags, BOOL bSelected)
|
|
{
|
|
if (SHIFT_DOWN(keyFlags))
|
|
{
|
|
ListView_SelectRangeTo(plv, iItem, !CONTROL_DOWN(keyFlags));
|
|
ListView_SetFocusSel(plv, iItem, TRUE, FALSE, FALSE);
|
|
}
|
|
else if (!CONTROL_DOWN(keyFlags))
|
|
{
|
|
ListView_SetFocusSel(plv, iItem, TRUE, !bSelected, FALSE);
|
|
if (!RIGHTBUTTON(keyFlags) && bSelected && ListView_IsSimpleSelect(plv))
|
|
{
|
|
ListView_HandleStateIconClick(plv, iItem);
|
|
}
|
|
}
|
|
}
|
|
|
|
void ListView_HandleStateIconClick(LV* plv, int iItem)
|
|
{
|
|
int iState =
|
|
ListView_OnGetItemState(plv, iItem, LVIS_STATEIMAGEMASK);
|
|
|
|
iState = STATEIMAGEMASKTOINDEX(iState) -1;
|
|
iState++;
|
|
iState %= ImageList_GetImageCount(plv->himlState);
|
|
iState++;
|
|
ListView_OnSetItemState(plv, iItem, INDEXTOSTATEIMAGEMASK(iState), LVIS_STATEIMAGEMASK);
|
|
}
|
|
|
|
BOOL ListView_RBeginMarquee(LV* plv, int x, int y, LPLVHITTESTINFO plvhti)
|
|
{
|
|
if (ListView_FullRowSelect(plv) &&
|
|
ListView_IsReportView(plv) &&
|
|
!(plv->ci.style & LVS_SINGLESEL) &&
|
|
!ListView_OwnerDraw(plv) &&
|
|
plvhti->iSubItem == 0)
|
|
{
|
|
// can only begin marquee in column 0.
|
|
if (plvhti->flags == LVHT_ONITEM)
|
|
{
|
|
return TRUE;
|
|
}
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
void ListView_HandleMouse(LV* plv, BOOL fDoubleClick, int x, int y, UINT keyFlags, BOOL bMouseWheel)
|
|
{
|
|
LV_HITTESTINFO ht;
|
|
NMITEMACTIVATE nm;
|
|
int iItem, click, drag;
|
|
BOOL bSelected, fHadFocus, fNotifyReturn = FALSE;
|
|
BOOL fActive;
|
|
HWND hwnd = plv->ci.hwnd;
|
|
|
|
if (plv->fButtonDown)
|
|
return;
|
|
plv->fButtonDown = TRUE;
|
|
|
|
|
|
if (plv->exStyle & LVS_EX_ONECLICKACTIVATE && plv->fOneClickHappened && plv->fOneClickOK)
|
|
{
|
|
KillTimer(plv->ci.hwnd, IDT_ONECLICKHAPPENED);
|
|
plv->fOneClickHappened = FALSE;
|
|
CCSendNotify(&plv->ci, LVN_ITEMACTIVATE, &(plv->nmOneClickHappened.hdr));
|
|
if (!IsWindow(hwnd))
|
|
return;
|
|
}
|
|
|
|
fHadFocus = (GetFocus() == plv->ci.hwnd);
|
|
click = RIGHTBUTTON(keyFlags) ? NM_RCLICK : NM_CLICK;
|
|
drag = RIGHTBUTTON(keyFlags) ? LVN_BEGINRDRAG : LVN_BEGINDRAG;
|
|
|
|
fActive = ChildOfActiveWindow(plv->ci.hwnd) || fShouldFirstClickActivate() ||
|
|
ChildOfDesktop(plv->ci.hwnd);
|
|
|
|
TraceMsg(TF_LISTVIEW, "ListView_OnButtonDown %d", fDoubleClick);
|
|
|
|
SetCapture(plv->ci.hwnd);
|
|
|
|
plv->ptCapture.x = x;
|
|
plv->ptCapture.y = y;
|
|
|
|
if (!ListView_DismissEdit(plv, FALSE) && GetCapture() != plv->ci.hwnd)
|
|
goto EndButtonDown;
|
|
|
|
CCReleaseCapture(&plv->ci);
|
|
|
|
// REVIEW: right button implies no shift or control stuff
|
|
// Single selection style also implies no modifiers
|
|
//if (RIGHTBUTTON(keyFlags) || (plv->ci.style & LVS_SINGLESEL))
|
|
if ((plv->ci.style & LVS_SINGLESEL))
|
|
keyFlags &= ~(MK_SHIFT | MK_CONTROL);
|
|
|
|
ht.pt.x = x;
|
|
ht.pt.y = y;
|
|
iItem = ListView_OnSubItemHitTest(plv, &ht);
|
|
if (ht.iSubItem != 0)
|
|
{
|
|
// if we're not in full row select,
|
|
// hitting on a subitem is like hitting on nowhere
|
|
// also, in win95, ownerdraw fixed effectively had full row select
|
|
if (!ListView_FullRowSelect(plv) &&
|
|
!(plv->ci.style & LVS_OWNERDRAWFIXED))
|
|
{
|
|
iItem = -1;
|
|
ht.flags = LVHT_NOWHERE;
|
|
}
|
|
}
|
|
|
|
nm.iItem = iItem;
|
|
nm.iSubItem = ht.iSubItem;
|
|
nm.uChanged = 0;
|
|
nm.ptAction.x = x;
|
|
nm.ptAction.y = y;
|
|
nm.uKeyFlags = GetLVKeyFlags();
|
|
|
|
// FProt Profesional assumed that if the notification structure pointer + 14h bytes
|
|
// had a value 2 that it was a displayinfo structure and they then used offset +2c as lparam...
|
|
nm.uNewState = 0;
|
|
|
|
plv->iNoHover = iItem;
|
|
|
|
bSelected = (iItem >= 0) && ListView_OnGetItemState(plv, iItem, LVIS_SELECTED);
|
|
|
|
if (fDoubleClick)
|
|
{
|
|
// Cancel any name editing that might happen.
|
|
ListView_CancelPendingEdit(plv);
|
|
KillTimer(plv->ci.hwnd, IDT_SCROLLWAIT);
|
|
|
|
if (ht.flags & LVHT_NOWHERE)
|
|
{
|
|
// this would have been done in the first click in win95 except
|
|
// now we blow off the first click on focus change
|
|
if (!SHIFT_DOWN(keyFlags) && !CONTROL_DOWN(keyFlags))
|
|
ListView_DeselectAll(plv, -1);
|
|
}
|
|
|
|
click = RIGHTBUTTON(keyFlags) ? NM_RDBLCLK : NM_DBLCLK ;
|
|
if (CCSendNotify(&plv->ci, click, &nm.hdr))
|
|
goto EndButtonDown;
|
|
|
|
/// some (comdlg32 for example) destroy on double click
|
|
// we need to bail if that happens because plv is no longer valid
|
|
if (!IsWindow(hwnd))
|
|
return;
|
|
|
|
if (click == NM_DBLCLK)
|
|
{
|
|
// these shift control flags are to mirror when we don't send out the activate on the single click,
|
|
// but are in the oneclick activate mode (see below)
|
|
if (ht.flags & (LVHT_ONITEMLABEL | LVHT_ONITEMICON))
|
|
{
|
|
// possible scenarios below:
|
|
// 1) we're using classic windows style so double click => launch
|
|
// 2) we're using single click activate
|
|
// a) shift is down and item is selected => launch
|
|
// this implies that the first click selected it
|
|
// b) control is down => launch
|
|
// the first click toggled the selection so if the item was
|
|
// the only item selected and we double clicked on it
|
|
// the first click deselects it and no item is selected
|
|
// so nothing will be launched - this is win95 behavior
|
|
if (!(plv->exStyle & LVS_EX_ONECLICKACTIVATE && plv->fOneClickOK) ||
|
|
(plv->exStyle & LVS_EX_ONECLICKACTIVATE && plv->fOneClickOK &&
|
|
(SHIFT_DOWN(keyFlags) || CONTROL_DOWN(keyFlags))))
|
|
{
|
|
CCSendNotify(&plv->ci, LVN_ITEMACTIVATE, &nm.hdr);
|
|
}
|
|
}
|
|
// Double-click on checkbox state icon cycles it just like single click
|
|
else if ((ht.flags & LVHT_ONITEMSTATEICON) && ListView_CheckBoxes(plv))
|
|
{
|
|
ListView_HandleStateIconClick(plv, iItem);
|
|
}
|
|
}
|
|
|
|
if (!IsWindow(hwnd))
|
|
return;
|
|
goto EndButtonDown;
|
|
}
|
|
|
|
if (ht.flags & (LVHT_ONITEMLABEL | LVHT_ONITEMICON))
|
|
{
|
|
// if it wasn't selected, we're about to select it... play
|
|
// a little ditty for us...
|
|
CCPlaySound(c_szSelect);
|
|
|
|
if (!RIGHTBUTTON(keyFlags) || (!CONTROL_DOWN(keyFlags) && !SHIFT_DOWN(keyFlags)))
|
|
ListView_ButtonSelect(plv, iItem, keyFlags, bSelected);
|
|
|
|
// handle full row select
|
|
// If single-select listview, disable marquee selection.
|
|
//
|
|
// Careful - CheckForDragBegin yields and the app may have
|
|
// destroyed the item we were thinking about dragging!
|
|
//
|
|
if (!bMouseWheel && CheckForDragBegin(plv->ci.hwnd, x, y))
|
|
{
|
|
// should we do a marquee?
|
|
if (ListView_RBeginMarquee(plv, x, y, &ht) &&
|
|
!CCSendNotify(&plv->ci, LVN_MARQUEEBEGIN, &nm.hdr))
|
|
{
|
|
ListView_DragSelect(plv, x, y);
|
|
fNotifyReturn = !CCSendNotify(&plv->ci, click, &nm.hdr);
|
|
}
|
|
else
|
|
{
|
|
// Before we start dragging, make it sure that it is
|
|
// selected and has the focus.
|
|
ListView_SetFocusSel(plv, iItem, TRUE, FALSE, FALSE);
|
|
|
|
if (!SHIFT_DOWN(keyFlags))
|
|
plv->iMark = iItem;
|
|
|
|
// Then, we need to update the window before start dragging
|
|
// to show the selection chagne.
|
|
UpdateWindow(plv->ci.hwnd);
|
|
|
|
// Remember which item we're dragging, as it affects ListView_OnInsertMarkHitTest
|
|
plv->iDrag = iItem;
|
|
|
|
CCSendNotify(&plv->ci, drag, &nm.hdr);
|
|
|
|
plv->iDrag = -1;
|
|
|
|
goto EndButtonDown;
|
|
}
|
|
}
|
|
|
|
// CheckForDragBegin yields, so revalidate before continuing
|
|
else if (IsWindow(hwnd))
|
|
{
|
|
// button came up and we are not dragging
|
|
|
|
if (!RIGHTBUTTON(keyFlags))
|
|
{
|
|
if (CONTROL_DOWN(keyFlags))
|
|
{
|
|
// do this on the button up so that ctrl-dragging a range
|
|
// won't toggle the select.
|
|
|
|
if (SHIFT_DOWN(keyFlags))
|
|
ListView_SetFocusSel(plv, iItem, FALSE, FALSE, FALSE);
|
|
else
|
|
{
|
|
ListView_SetFocusSel(plv, iItem, TRUE, FALSE, TRUE);
|
|
}
|
|
}
|
|
}
|
|
if (!SHIFT_DOWN(keyFlags))
|
|
plv->iMark = iItem;
|
|
|
|
if (!ListView_SetFocus(plv->ci.hwnd)) // activate this window
|
|
return;
|
|
|
|
// now do the deselect stuff
|
|
if (!SHIFT_DOWN(keyFlags) && !CONTROL_DOWN(keyFlags) && !RIGHTBUTTON(keyFlags))
|
|
{
|
|
ListView_DeselectAll(plv, iItem);
|
|
if ((ht.flags & LVHT_ONITEMLABEL) && bSelected &&
|
|
!(plv->exStyle & (LVS_EX_ONECLICKACTIVATE|LVS_EX_TWOCLICKACTIVATE)))
|
|
{
|
|
|
|
// doing this check for ownerdrawfixed is for compatability.
|
|
// we don't want to go into edit mode if the user just happened to click
|
|
// to this window when a different one had focus,
|
|
// but ms hammer relied upon the notification being sent (and we
|
|
// don't go into edit mode anyways for ownerdraw)
|
|
if (fHadFocus ||
|
|
(plv->ci.style & LVS_OWNERDRAWFIXED))
|
|
{
|
|
// Click on item label. It was selected and
|
|
// no modifier keys were pressed and no drag operation
|
|
// So setup for name edit mode. Still need to wait
|
|
// to make sure user is not doing double click.
|
|
//
|
|
ListView_SetupPendingNameEdit(plv);
|
|
}
|
|
}
|
|
}
|
|
|
|
fNotifyReturn = !CCSendNotify(&plv->ci, click, &nm.hdr);
|
|
if (!IsWindow(hwnd))
|
|
return;
|
|
|
|
if (plv->exStyle & (LVS_EX_ONECLICKACTIVATE|LVS_EX_TWOCLICKACTIVATE))
|
|
{
|
|
if (!RIGHTBUTTON(keyFlags))
|
|
{
|
|
// We don't ItemActivate within one double-click time of creating
|
|
// this listview. This is a common occurence for people used to
|
|
// double-clicking. The first click pops up a new window which
|
|
// receives the second click and ItemActivates the item...
|
|
//
|
|
if ((plv->exStyle & LVS_EX_ONECLICKACTIVATE && plv->fOneClickOK) || bSelected)
|
|
{
|
|
if (fActive)
|
|
{
|
|
// condition: if we're in a single click activate mode
|
|
// don't launch if control or shift keys are pressed
|
|
BOOL bCond = plv->exStyle & LVS_EX_ONECLICKACTIVATE && !CONTROL_DOWN(keyFlags) && !SHIFT_DOWN(keyFlags);
|
|
|
|
if ((bSelected && plv->exStyle & LVS_EX_TWOCLICKACTIVATE) ||
|
|
(bCond && !g_bUseDblClickTimer))
|
|
{
|
|
CCSendNotify(&plv->ci, LVN_ITEMACTIVATE, &nm.hdr);
|
|
if (!IsWindow(hwnd))
|
|
return;
|
|
}
|
|
else if (bCond && g_bUseDblClickTimer)
|
|
{
|
|
plv->fOneClickHappened = TRUE;
|
|
plv->nmOneClickHappened = nm;
|
|
SetTimer(plv->ci.hwnd, IDT_ONECLICKHAPPENED, GetDoubleClickTime(), NULL);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// IsWindow() failed. Bail.
|
|
return;
|
|
}
|
|
}
|
|
else if (ht.flags & LVHT_ONITEMSTATEICON)
|
|
{
|
|
// Should activate window and send notificiation to parent...
|
|
if (!ListView_SetFocus(plv->ci.hwnd)) // activate this window
|
|
return;
|
|
fNotifyReturn = !CCSendNotify(&plv->ci, click, &nm.hdr);
|
|
if (fNotifyReturn && ListView_CheckBoxes(plv))
|
|
{
|
|
ListView_HandleStateIconClick(plv, iItem);
|
|
}
|
|
}
|
|
else if (ht.flags & LVHT_NOWHERE)
|
|
{
|
|
if (!ListView_SetFocus(plv->ci.hwnd)) // activate this window
|
|
return;
|
|
|
|
// If single-select listview, disable marquee selection.
|
|
if (!(plv->ci.style & LVS_SINGLESEL) && CheckForDragBegin(plv->ci.hwnd, x, y) &&
|
|
!CCSendNotify(&plv->ci, LVN_MARQUEEBEGIN, &nm.hdr))
|
|
{
|
|
if (!SHIFT_DOWN(keyFlags) && !CONTROL_DOWN(keyFlags))
|
|
ListView_DeselectAll(plv, -1);
|
|
ListView_DragSelect(plv, x, y);
|
|
fNotifyReturn = !CCSendNotify(&plv->ci, click, &nm.hdr);
|
|
}
|
|
else if (IsWindow(hwnd))
|
|
{
|
|
// if we didn't have focus and aren't showing selection always,
|
|
// make the first click just set focus
|
|
BOOL fDoFirstClickSelection = (fHadFocus || plv->ci.style & LVS_SHOWSELALWAYS ||
|
|
CONTROL_DOWN(keyFlags) || SHIFT_DOWN(keyFlags) ||
|
|
RIGHTBUTTON(keyFlags));
|
|
|
|
if (fDoFirstClickSelection && fActive)
|
|
{
|
|
|
|
if (!SHIFT_DOWN(keyFlags) && !CONTROL_DOWN(keyFlags))
|
|
ListView_DeselectAll(plv, -1);
|
|
|
|
fNotifyReturn = !CCSendNotify(&plv->ci, click, &nm.hdr);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// IsWindow() failed. Bail.
|
|
return;
|
|
}
|
|
}
|
|
|
|
// re-check the key state so we don't get confused by multiple clicks
|
|
|
|
// this needs to check the GetKeyState stuff only when we've gone into
|
|
// a modal loop waiting for the rbutton up.
|
|
if (fNotifyReturn && (click == NM_RCLICK)) // && (GetKeyState(VK_RBUTTON)>=0))
|
|
{
|
|
POINT pt = { x, y };
|
|
ClientToScreen(plv->ci.hwnd, &pt);
|
|
FORWARD_WM_CONTEXTMENU(plv->ci.hwnd, plv->ci.hwnd, pt.x, pt.y, SendMessage);
|
|
}
|
|
|
|
EndButtonDown:
|
|
if (IsWindow(hwnd))
|
|
plv->fButtonDown = FALSE;
|
|
}
|
|
|
|
void ListView_OnButtonDown(LV* plv, BOOL fDoubleClick, int x, int y, UINT keyFlags)
|
|
{
|
|
ListView_HandleMouse(plv, fDoubleClick, x, y, keyFlags, FALSE);
|
|
}
|
|
|
|
BOOL ListView_CancelPendingTimer(LV* plv, UINT fFlags, int idTimer)
|
|
{
|
|
if (plv->flags & fFlags)
|
|
{
|
|
KillTimer(plv->ci.hwnd, idTimer);
|
|
plv->flags &= ~fFlags;
|
|
return TRUE;
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
//
|
|
// ListView_OnTimer:
|
|
// process the WM_TIMER message. If the timer id is thta
|
|
// of the name editing, we should then start the name editing mode.
|
|
//
|
|
void ListView_OnTimer(LV* plv, UINT id)
|
|
{
|
|
KillTimer(plv->ci.hwnd, id);
|
|
|
|
if (id == IDT_NAMEEDIT)
|
|
{
|
|
// Kill the timer as we wont need any more messages from it.
|
|
|
|
if (ListView_CancelPendingEdit(plv))
|
|
{
|
|
// And start name editing mode.
|
|
if (!ListView_OnEditLabel(plv, plv->iFocus, NULL))
|
|
{
|
|
ListView_DismissEdit(plv, FALSE);
|
|
ListView_SetFocusSel(plv, plv->iFocus, TRUE, TRUE, FALSE);
|
|
}
|
|
}
|
|
}
|
|
else if (id == IDT_SCROLLWAIT)
|
|
{
|
|
if (ListView_CancelScrollWait(plv))
|
|
{
|
|
ListView_OnEnsureVisible(plv, plv->iFocus, TRUE);
|
|
}
|
|
}
|
|
else if (id == IDT_ONECLICKOK)
|
|
{
|
|
plv->fOneClickOK = TRUE;
|
|
}
|
|
else if (id == IDT_ONECLICKHAPPENED)
|
|
{
|
|
//if (!g_bUseDblClickTimer)
|
|
//{
|
|
//// EnableWindow(plv->ci.hwnd, TRUE);
|
|
// SetWindowBits(plv->ci.hwnd, GWL_STYLE, WS_DISABLED, 0);
|
|
// plv->fOneClickHappened = FALSE;
|
|
//}
|
|
// check the bit just in case they double-clicked
|
|
//else
|
|
if (plv->fOneClickHappened)
|
|
{
|
|
plv->fOneClickHappened = FALSE;
|
|
CCSendNotify(&plv->ci, LVN_ITEMACTIVATE, &(plv->nmOneClickHappened.hdr));
|
|
}
|
|
}
|
|
else if (id == IDT_TRACKINGTIP)
|
|
{
|
|
// Display keyboard nav tracking tooltip popups
|
|
|
|
if (ListView_IsKbdTipTracking(plv)) // Item requires tracking popup
|
|
{
|
|
// Ensure index is still valid
|
|
if (ListView_IsValidItemNumber(plv, plv->iTracking))
|
|
{
|
|
TOOLINFO ti = {0};
|
|
|
|
ti.cbSize = sizeof(TOOLINFO);
|
|
ti.hwnd = plv->ci.hwnd;
|
|
|
|
// Cancel previous
|
|
SendMessage(plv->hwndToolTips, TTM_TRACKACTIVATE, FALSE, (LPARAM)&ti);
|
|
|
|
// Switch ListView's tooltip window to "tracking" (manual) mode
|
|
SendMessage(plv->hwndToolTips, TTM_GETTOOLINFO, 0, (LPARAM)&ti);
|
|
ti.uFlags |= TTF_TRACK;
|
|
SendMessage(plv->hwndToolTips, TTM_SETTOOLINFO, 0, (LPARAM)&ti);
|
|
|
|
// Activate and establish size
|
|
SendMessage(plv->hwndToolTips, TTM_TRACKACTIVATE, TRUE, (LPARAM)&ti);
|
|
}
|
|
else
|
|
{
|
|
// Index was invalid (ListView set of items changed), tip track cancel, no popup
|
|
plv->iTracking = LVKTT_NOTRACK;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
//
|
|
// ListView_SetupPendingNameEdit:
|
|
// Sets up a timer to begin name editing at a delayed time. This
|
|
// will allow the user to double click on the already selected item
|
|
// without going into name editing mode, which is especially important
|
|
// in those views that only show a small icon.
|
|
//
|
|
void ListView_SetupPendingNameEdit(LV* plv)
|
|
{
|
|
SetTimer(plv->ci.hwnd, IDT_NAMEEDIT, GetDoubleClickTime(), NULL);
|
|
plv->flags |= LVF_NMEDITPEND;
|
|
}
|
|
|
|
void ListView_OnHVScroll(LV* plv, UINT code, int pos, int sb)
|
|
{
|
|
int iScrollCount = 0;
|
|
|
|
SCROLLINFO si;
|
|
|
|
si.cbSize = sizeof(SCROLLINFO);
|
|
si.fMask = SIF_TRACKPOS;
|
|
|
|
// if we're in 32bits, don't trust the pos since it's only 16bit's worth
|
|
if (ListView_GetScrollInfo(plv, sb, &si))
|
|
pos = (int)si.nTrackPos;
|
|
|
|
ListView_DismissEdit(plv, FALSE);
|
|
|
|
_ListView_OnScroll(plv, code, pos, sb);
|
|
|
|
switch (code)
|
|
{
|
|
case SB_PAGELEFT:
|
|
case SB_PAGERIGHT:
|
|
if (plv->iScrollCount < SMOOTHSCROLLLIMIT)
|
|
plv->iScrollCount += 3;
|
|
break;
|
|
|
|
case SB_LINELEFT:
|
|
case SB_LINERIGHT:
|
|
if (plv->iScrollCount < SMOOTHSCROLLLIMIT)
|
|
plv->iScrollCount++;
|
|
break;
|
|
|
|
case SB_ENDSCROLL:
|
|
plv->iScrollCount = 0;
|
|
break;
|
|
|
|
}
|
|
}
|
|
|
|
void ListView_OnVScroll(LV* plv, HWND hwndCtl, UINT code, int pos)
|
|
{
|
|
ListView_OnHVScroll(plv, code, pos, SB_VERT);
|
|
}
|
|
|
|
void ListView_OnHScroll(LV* plv, HWND hwndCtl, UINT code, int pos)
|
|
{
|
|
ListView_OnHVScroll(plv, code, pos, SB_HORZ);
|
|
}
|
|
|
|
int ListView_ValidateOneScrollParam(LV* plv, int iDirection, int dx)
|
|
{
|
|
SCROLLINFO si;
|
|
|
|
si.cbSize = sizeof(SCROLLINFO);
|
|
si.fMask = SIF_PAGE | SIF_RANGE | SIF_POS;
|
|
|
|
if (!ListView_GetScrollInfo(plv, iDirection, &si))
|
|
return 0;
|
|
|
|
if (si.nPage)
|
|
si.nMax -= (si.nPage - 1);
|
|
si.nPos += dx;
|
|
if (si.nPos < si.nMin)
|
|
{
|
|
dx += (int)(si.nMin - si.nPos);
|
|
}
|
|
else if (si.nPos > si.nMax)
|
|
{
|
|
dx -= (int)(si.nPos - si.nMax);
|
|
}
|
|
|
|
return dx;
|
|
}
|
|
|
|
BOOL ListView_ValidateScrollParams(LV* plv, int * pdx, int *pdy)
|
|
{
|
|
int dx = *pdx;
|
|
int dy = *pdy;
|
|
|
|
if (plv->ci.style & LVS_NOSCROLL)
|
|
return FALSE;
|
|
|
|
if (ListView_IsListView(plv))
|
|
{
|
|
ListView_MaybeResizeListColumns(plv, 0, ListView_Count(plv)-1);
|
|
#ifdef COLUMN_VIEW
|
|
if (dx < 0)
|
|
dx = (dx - (plv->cxItem - 1)) / plv->cxItem;
|
|
else
|
|
dx = (dx + (plv->cxItem - 1)) / plv->cxItem;
|
|
|
|
if (dy)
|
|
return FALSE;
|
|
#else
|
|
if (dy < 0)
|
|
dy = (dy - (plv->cyItem - 1)) / plv->cyItem;
|
|
else
|
|
dy = (dy + (plv->cyItem - 1)) / plv->cyItem;
|
|
|
|
if (dx)
|
|
return FALSE;
|
|
#endif
|
|
}
|
|
else if (ListView_IsReportView(plv))
|
|
{
|
|
//
|
|
// Note: This function expects that dy is in number of lines
|
|
// and we are working with pixels so do a conversion use some
|
|
// rounding up and down to make it right
|
|
if (dy > 0)
|
|
dy = (dy + plv->cyItem/2) / plv->cyItem;
|
|
else
|
|
dy = (dy - plv->cyItem/2) / plv->cyItem;
|
|
}
|
|
|
|
if (dy)
|
|
{
|
|
dy = ListView_ValidateOneScrollParam(plv, SB_VERT, dy);
|
|
if (ListView_IsReportView(plv)
|
|
#ifndef COLUMN_VIEW
|
|
|| ListView_IsListView(plv)
|
|
#endif
|
|
)
|
|
{
|
|
// convert back to pixels
|
|
dy *= plv->cyItem;
|
|
}
|
|
*pdy = dy;
|
|
}
|
|
|
|
if (dx)
|
|
{
|
|
dx = ListView_ValidateOneScrollParam(plv, SB_HORZ, dx);
|
|
#ifdef COLUMN_VIEW
|
|
if (ListView_IsListView(plv))
|
|
{
|
|
dx *= plv->cxItem;
|
|
}
|
|
#endif
|
|
*pdx = dx;
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
BOOL ListView_SendScrollNotify(LV* plv, BOOL fBegin, int dx, int dy)
|
|
{
|
|
NMLVSCROLL nm;
|
|
|
|
nm.dx = dx;
|
|
nm.dy = dy;
|
|
|
|
return !CCSendNotify(&plv->ci, fBegin ? LVN_BEGINSCROLL : LVN_ENDSCROLL, &nm.hdr);
|
|
}
|
|
|
|
|
|
BOOL ListView_OnScrollSelectSmooth(LV* plv, int dx, int dy, UINT uSmooth)
|
|
{
|
|
if (plv->ci.style & LVS_NOSCROLL)
|
|
return FALSE;
|
|
|
|
#ifdef DEBUG
|
|
// If we try and scroll an illegal amount then ListView_IScroll2_SmoothScroll
|
|
// will offset ptOrigin incorrectly (it doesn't check min/max range) which then
|
|
// mucks up hit testing and insert marks
|
|
if (ListView_IsIScrollView(plv))
|
|
{
|
|
int dxTmp = dx;
|
|
int dyTmp = dy;
|
|
|
|
ASSERT(ListView_ValidateScrollParams(plv, &dxTmp, &dyTmp) &&
|
|
dxTmp == dx && dyTmp == dy);
|
|
}
|
|
#endif
|
|
|
|
if (ListView_IsListView(plv))
|
|
{
|
|
// Scale pixel count to column count
|
|
//
|
|
#ifdef COLUMN_VIEW
|
|
if (dx < 0)
|
|
dx -= plv->cxItem - 1;
|
|
else
|
|
dx += plv->cxItem - 1;
|
|
|
|
dx = dx / plv->cxItem;
|
|
|
|
if (dy)
|
|
return FALSE;
|
|
#else
|
|
if (dy < 0)
|
|
dy -= plv->cyItem - 1;
|
|
else
|
|
dy += plv->cyItem - 1;
|
|
|
|
dy = dy / plv->cyItem;
|
|
|
|
if (dx)
|
|
return FALSE;
|
|
#endif
|
|
}
|
|
else if (ListView_IsReportView(plv) && !plv->fGroupView)
|
|
{
|
|
//
|
|
// Note: This function expects that dy is in number of lines
|
|
// and we are working with pixels so do a conversion use some
|
|
// rounding up and down to make it right
|
|
if (dy > 0)
|
|
dy = (dy + plv->cyItem/2) / plv->cyItem;
|
|
else
|
|
dy = (dy - plv->cyItem/2) / plv->cyItem;
|
|
}
|
|
|
|
ListView_SendScrollNotify(plv, TRUE, dx, dy);
|
|
_ListView_Scroll2(plv, dx, dy, uSmooth);
|
|
ListView_SendScrollNotify(plv, FALSE, dx, dy);
|
|
ListView_UpdateScrollBars(plv);
|
|
return TRUE;
|
|
}
|
|
|
|
BOOL ListView_OnScroll(LV* plv, int dx, int dy)
|
|
{
|
|
return ListView_OnScrollSelectSmooth(plv, dx, dy, 0);
|
|
}
|
|
|
|
#ifdef DEBUG
|
|
BOOL ListView_ValidatercView(LV* plv, RECT* prcView, BOOL fRecalcDone)
|
|
{
|
|
BOOL fRet = prcView->left != RECOMPUTE ? TRUE : !fRecalcDone;
|
|
|
|
// hitting this assert is only valuable if there's a manual repro, which never happens in stress
|
|
#ifdef FULL_DEBUG
|
|
if (!ListView_IsOwnerData(plv) && ListView_IsIScrollView(plv) && !(plv->fGroupView && plv->hdpaGroups) && ListView_RedrawEnabled(plv))
|
|
{
|
|
RECT rcViewTmp;
|
|
fRet = ListView_ICalcViewRect(plv, TRUE, &rcViewTmp);
|
|
if (fRet)
|
|
{
|
|
ASSERT(prcView->left != RECOMPUTE);
|
|
fRet = IsEqualRect(rcViewTmp, *prcView);
|
|
}
|
|
else
|
|
{
|
|
fRet = !fRecalcDone;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
return fRet;
|
|
}
|
|
|
|
BOOL ListView_ValidateScrollPositions(LV* plv, RECT* prcClient)
|
|
{
|
|
BOOL fRet = TRUE;
|
|
|
|
// hitting this assert is only valuable if there's a manual repro, which never happens in stress
|
|
#ifdef FULL_DEBUG
|
|
// if we're in ListView_FixIScrollPositions, then it will fix up the scroll positions when we unwind
|
|
if (ListView_IsIScrollView(plv) && (!plv->fInFixIScrollPositions) && ListView_RedrawEnabled(plv))
|
|
{
|
|
if (!(plv->ci.style & LVS_NOSCROLL))
|
|
{
|
|
// if we don't have a client rect there's no way to validate anything, assume everything will be recomputed later
|
|
RECT rcClient;
|
|
if (!prcClient)
|
|
{
|
|
if (plv->rcView.left != RECOMPUTE)
|
|
{
|
|
ListView_GetStyleAndClientRectGivenViewRect(plv, &plv->rcView, &rcClient);
|
|
prcClient = &rcClient;
|
|
}
|
|
}
|
|
|
|
if (prcClient)
|
|
{
|
|
if (fRet)
|
|
{
|
|
if (RECTWIDTH(*prcClient) < RECTWIDTH(plv->rcView))
|
|
{
|
|
fRet = (plv->rcView.left <= plv->ptOrigin.x) && (plv->ptOrigin.x+RECTWIDTH(*prcClient) <= plv->rcView.right);
|
|
}
|
|
else
|
|
{
|
|
fRet = (plv->ptOrigin.x <= plv->rcView.left) && (plv->rcView.right <= plv->ptOrigin.x+RECTWIDTH(*prcClient));
|
|
}
|
|
}
|
|
|
|
if (fRet)
|
|
{
|
|
if (RECTHEIGHT(*prcClient) < RECTHEIGHT(plv->rcView))
|
|
{
|
|
fRet = (plv->rcView.top <= plv->ptOrigin.y) && (plv->ptOrigin.y+RECTHEIGHT(*prcClient) <= plv->rcView.bottom);
|
|
}
|
|
else
|
|
{
|
|
fRet = (plv->ptOrigin.y <= plv->rcView.top) && (plv->rcView.bottom <= plv->ptOrigin.y+RECTHEIGHT(*prcClient));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
fRet = (plv->ptOrigin.x == 0) && (plv->ptOrigin.y == 0);
|
|
}
|
|
}
|
|
#endif
|
|
|
|
return fRet;
|
|
}
|
|
#endif
|
|
|
|
BOOL ListView_OnEnsureVisible(LV* plv, int i, BOOL fPartialOK)
|
|
{
|
|
RECT rcBounds;
|
|
RECT rc;
|
|
RECT rcClient;
|
|
int dx, dy;
|
|
|
|
if (!ListView_IsValidItemNumber(plv, i) || plv->ci.style & LVS_NOSCROLL)
|
|
return FALSE;
|
|
|
|
// we need to do this again inside because some callers don't do it.
|
|
// other callers that do this need to do it outside so that
|
|
// they can know not to call us if there's not wait pending
|
|
ListView_CancelScrollWait(plv);
|
|
|
|
if (ListView_IsReportView(plv))
|
|
return ListView_ROnEnsureVisible(plv, i, fPartialOK);
|
|
|
|
ListView_GetRects(plv, i, QUERY_DEFAULT, &rc, NULL, &rcBounds, NULL);
|
|
|
|
if (plv->fGroupView)
|
|
{
|
|
LISTITEM* pitem = ListView_GetItemPtr(plv, i);
|
|
if (pitem)
|
|
{
|
|
LISTGROUP* pgrp = ListView_FindFirstVisibleGroup(plv);
|
|
if (pitem->pGroup == pgrp && pgrp)
|
|
{
|
|
rcBounds.top -= LISTGROUP_HEIGHT(plv, pgrp);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!fPartialOK)
|
|
rc = rcBounds;
|
|
|
|
// Scrolling is done relative to this calculated rect, not the size of hwndListview (plv->sizeClient)
|
|
ListView_GetClientRect(plv, &rcClient, TRUE, NULL);
|
|
ASSERT(ListView_ValidateScrollPositions(plv, &rcClient));
|
|
|
|
// If any part of rc is outside of rcClient, then
|
|
// scroll so that all of rcBounds is visible.
|
|
//
|
|
dx = 0;
|
|
if (rc.left < 0 || (rc.right >= rcClient.right && rcClient.right != 0))
|
|
{
|
|
dx = rcBounds.left - 0;
|
|
if (dx >= 0)
|
|
{
|
|
dx = rcBounds.right - rcClient.right;
|
|
if (dx <= 0)
|
|
dx = 0;
|
|
else if ((rcBounds.left - dx) < 0)
|
|
dx = rcBounds.left - 0; // Not all fits...
|
|
}
|
|
}
|
|
dy = 0;
|
|
if (rc.top < 0 || (rc.bottom >= rcClient.bottom && rcClient.bottom != 0))
|
|
{
|
|
dy = rcBounds.top - 0;
|
|
if (dy >= 0)
|
|
{
|
|
dy = rcBounds.bottom - rcClient.bottom;
|
|
if (dy < 0)
|
|
dy = 0;
|
|
}
|
|
}
|
|
|
|
// if rcClient is 0 or 1 pixel in size, it is impossible to scroll it
|
|
if (dx | dy)
|
|
ListView_ValidateScrollParams(plv, &dx, &dy);
|
|
|
|
if (dx | dy)
|
|
return ListView_OnScrollSelectSmooth(plv, dx, dy, SSW_EX_IMMEDIATE);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
void ListView_UpdateScrollBars(LV* plv)
|
|
{
|
|
RECT rc;
|
|
DWORD dwStyle;
|
|
|
|
if ((plv->ci.style & LVS_NOSCROLL) ||
|
|
(!(ListView_RedrawEnabled(plv))))
|
|
return;
|
|
|
|
_ListView_UpdateScrollBars(plv);
|
|
|
|
GetClientRect(plv->ci.hwnd, &rc);
|
|
plv->sizeClient.cx = rc.right;
|
|
plv->sizeClient.cy = rc.bottom;
|
|
|
|
dwStyle = ListView_GetWindowStyle(plv);
|
|
plv->ci.style = (plv->ci.style & ~(WS_HSCROLL | WS_VSCROLL)) | (dwStyle & WS_HSCROLL | WS_VSCROLL);
|
|
}
|
|
|
|
void ListView_OnSetFont(LV* plv, HFONT hfont, BOOL fRedraw)
|
|
{
|
|
HDC hdc;
|
|
SIZE siz;
|
|
LOGFONT lf;
|
|
HFONT hfontPrev;
|
|
TEXTMETRIC tm;
|
|
|
|
if ((plv->flags & LVF_FONTCREATED) && plv->hfontLabel)
|
|
{
|
|
DeleteObject(plv->hfontLabel);
|
|
plv->flags &= ~LVF_FONTCREATED;
|
|
}
|
|
|
|
if (hfont == NULL)
|
|
{
|
|
SystemParametersInfo(SPI_GETICONTITLELOGFONT, sizeof(lf), &lf, FALSE);
|
|
hfont = CreateFontIndirect(&lf);
|
|
plv->flags |= LVF_FONTCREATED;
|
|
}
|
|
|
|
hdc = GetDC(HWND_DESKTOP);
|
|
if (hdc)
|
|
{
|
|
hfontPrev = SelectFont(hdc, hfont);
|
|
|
|
GetTextMetrics(hdc, &tm);
|
|
|
|
plv->cyLabelChar = tm.tmHeight;
|
|
plv->cxLabelChar = tm.tmAveCharWidth; // Maybe this should tm.tmMaxCharWidth
|
|
|
|
GetTextExtentPoint(hdc, c_szEllipses, CCHELLIPSES, &siz);
|
|
plv->cxEllipses = siz.cx;
|
|
|
|
SelectFont(hdc, hfontPrev);
|
|
ReleaseDC(HWND_DESKTOP, hdc);
|
|
}
|
|
|
|
plv->hfontLabel = hfont;
|
|
|
|
if (plv->hfontLabel)
|
|
{
|
|
LOGFONT lf;
|
|
if (GetObject(plv->hfontLabel, sizeof(LOGFONT), &lf))
|
|
{
|
|
if (plv->hfontGroup)
|
|
DeleteObject(plv->hfontGroup);
|
|
|
|
CCAdjustForBold(&lf);
|
|
|
|
plv->hfontGroup = CreateFontIndirect(&lf);
|
|
}
|
|
}
|
|
|
|
plv->ci.uiCodePage = GetCodePageForFont(hfont);
|
|
|
|
ListView_InvalidateCachedLabelSizes(plv);
|
|
|
|
/* Ensure that our tooltip control uses the same font as the list view is using, therefore
|
|
/ avoiding any nasty formatting problems. */
|
|
|
|
if (plv->hwndToolTips)
|
|
{
|
|
FORWARD_WM_SETFONT(plv->hwndToolTips, plv->hfontLabel, FALSE, SendMessage);
|
|
}
|
|
|
|
// If we have a header window, we need to forward this to it also
|
|
// as we have destroyed the hfont that they are using...
|
|
if (plv->hwndHdr)
|
|
{
|
|
FORWARD_WM_SETFONT(plv->hwndHdr, plv->hfontLabel, FALSE, SendMessage);
|
|
ListView_UpdateScrollBars(plv);
|
|
}
|
|
|
|
if (plv->hFontHot)
|
|
{
|
|
DeleteObject(plv->hFontHot);
|
|
plv->hFontHot = NULL;
|
|
}
|
|
|
|
CCGetHotFont(plv->hfontLabel, &plv->hFontHot);
|
|
plv->iFreeSlot = -1;
|
|
|
|
if (fRedraw)
|
|
RedrawWindow(plv->ci.hwnd, NULL, NULL, RDW_INVALIDATE | RDW_ERASE);
|
|
}
|
|
|
|
HFONT ListView_OnGetFont(LV* plv)
|
|
{
|
|
return plv->hfontLabel;
|
|
}
|
|
|
|
// This function process the WM_SETREDRAW message by setting or clearing
|
|
// a bit in the listview structure, which several places in the code will
|
|
// check...
|
|
//
|
|
// REVIEW: Should probably forward to DefWindowProc()
|
|
//
|
|
void ListView_OnSetRedraw(LV* plv, BOOL fRedraw)
|
|
{
|
|
if (fRedraw)
|
|
{
|
|
BOOL fChanges = FALSE;
|
|
// Only do work if we're turning redraw back on...
|
|
//
|
|
if (!(plv->flags & LVF_REDRAW))
|
|
{
|
|
plv->flags |= LVF_REDRAW;
|
|
|
|
// deal with any accumulated invalid regions
|
|
if (plv->hrgnInval)
|
|
{
|
|
UINT fRedraw = (plv->flags & LVF_ERASE) ? RDW_INVALIDATE | RDW_ERASE | RDW_UPDATENOW : RDW_UPDATENOW|RDW_INVALIDATE;
|
|
if (plv->hrgnInval == (HRGN)ENTIRE_REGION)
|
|
plv->hrgnInval = NULL;
|
|
|
|
RedrawWindow(plv->ci.hwnd, NULL, plv->hrgnInval, fRedraw);
|
|
ListView_DeleteHrgnInval(plv);
|
|
fChanges = TRUE;
|
|
}
|
|
plv->flags &= ~LVF_ERASE;
|
|
|
|
|
|
// Turning redraw on recomputes listview.
|
|
if (plv->fGroupView)
|
|
{
|
|
_ListView_RecomputeEx(plv, NULL, 0, TRUE);
|
|
}
|
|
|
|
if (plv->fGroupView || ListView_IsDoubleBuffer(plv))
|
|
InvalidateRect(plv->ci.hwnd, NULL, TRUE);
|
|
|
|
|
|
// now deal with the optimized stuff
|
|
if (ListView_IsListView(plv) ||
|
|
ListView_IsReportView(plv))
|
|
{
|
|
if (plv->iFirstChangedNoRedraw != -1)
|
|
{
|
|
// We may try to resize the column
|
|
if (!ListView_MaybeResizeListColumns(plv, plv->iFirstChangedNoRedraw,
|
|
ListView_Count(plv)-1))
|
|
ListView_OnUpdate(plv, plv->iFirstChangedNoRedraw);
|
|
}
|
|
else
|
|
ListView_UpdateScrollBars(plv);
|
|
}
|
|
else
|
|
{
|
|
int iCount;
|
|
|
|
if (plv->iFirstChangedNoRedraw != -1)
|
|
{
|
|
for (iCount = ListView_Count(plv) ; plv->iFirstChangedNoRedraw < iCount; plv->iFirstChangedNoRedraw++)
|
|
{
|
|
ListView_InvalidateItem(plv, plv->iFirstChangedNoRedraw, FALSE, RDW_INVALIDATE);
|
|
}
|
|
|
|
fChanges = TRUE;
|
|
}
|
|
|
|
if (fChanges)
|
|
{
|
|
ListView_RecalcRegion(plv, TRUE, TRUE);
|
|
}
|
|
|
|
if (((plv->ci.style & LVS_AUTOARRANGE) ||(plv->exStyle & LVS_EX_SNAPTOGRID)) && fChanges)
|
|
{
|
|
ListView_OnUpdate(plv, plv->iFirstChangedNoRedraw);
|
|
}
|
|
else
|
|
{
|
|
ListView_UpdateScrollBars(plv);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
plv->iFirstChangedNoRedraw = -1;
|
|
plv->flags &= ~LVF_REDRAW;
|
|
}
|
|
}
|
|
|
|
HIMAGELIST ListView_OnGetImageList(LV* plv, int iImageList)
|
|
{
|
|
switch (iImageList)
|
|
{
|
|
case LVSIL_NORMAL:
|
|
return plv->himl;
|
|
|
|
case LVSIL_SMALL:
|
|
return plv->himlSmall;
|
|
|
|
case LVSIL_STATE:
|
|
return plv->himlState;
|
|
}
|
|
RIPMSG(0, "ListView_GetImageList: Invalid Imagelist asked for");
|
|
return NULL;
|
|
}
|
|
|
|
HIMAGELIST ListView_OnSetImageList(LV* plv, HIMAGELIST himl, int iImageList)
|
|
{
|
|
HIMAGELIST hImageOld = NULL;
|
|
BOOL fImageSizeChanged = FALSE; //Assume the size hasn't changed!
|
|
|
|
switch (iImageList)
|
|
{
|
|
case LVSIL_NORMAL:
|
|
hImageOld = plv->himl;
|
|
plv->himl = himl;
|
|
if (himl)
|
|
{
|
|
int cxIconNew, cyIconNew;
|
|
//Get the Icon sizes from the new image list.
|
|
if (CCGetIconSize(&plv->ci, himl, &cxIconNew , &cyIconNew))
|
|
{
|
|
//Check to see if the sizes have changed!
|
|
if((cxIconNew != plv->cxIcon) || (cyIconNew != plv->cyIcon))
|
|
{
|
|
fImageSizeChanged = TRUE;
|
|
plv->cxIcon = cxIconNew;
|
|
plv->cyIcon = cyIconNew;
|
|
}
|
|
}
|
|
|
|
if (fImageSizeChanged && (!(plv->flags & LVF_ICONSPACESET)))
|
|
{
|
|
ListView_OnSetIconSpacing(plv, (LPARAM)-1);
|
|
}
|
|
}
|
|
break;
|
|
|
|
case LVSIL_SMALL:
|
|
hImageOld = plv->himlSmall;
|
|
plv->himlSmall = himl;
|
|
if (himl)
|
|
{
|
|
int cxSmIconNew, cySmIconNew;
|
|
//Get the small icon sizes from the new image list.
|
|
if(CCGetIconSize(&plv->ci, himl, &cxSmIconNew , &cySmIconNew))
|
|
{
|
|
//Check to see if the sizes have changed!
|
|
if((cxSmIconNew != plv->cxSmIcon) || (cySmIconNew != plv->cySmIcon))
|
|
{
|
|
fImageSizeChanged = TRUE;
|
|
plv->cxSmIcon = cxSmIconNew;
|
|
plv->cySmIcon = cySmIconNew;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (fImageSizeChanged)
|
|
{
|
|
plv->cxItem = ListView_ComputeCXItemSize(plv);
|
|
|
|
// After changing the imagelist, try to resize the columns, because we can't
|
|
// guess what the new size is going to be. Discovered by Thumbview
|
|
ListView_MaybeResizeListColumns(plv, 0, ListView_Count(plv)-1);
|
|
plv->cyItem = ListView_ComputeCYItemSize(plv);
|
|
}
|
|
|
|
if (plv->hwndHdr)
|
|
SendMessage(plv->hwndHdr, HDM_SETIMAGELIST, 0, (LPARAM)himl);
|
|
break;
|
|
|
|
case LVSIL_STATE:
|
|
fImageSizeChanged = TRUE;
|
|
if (himl)
|
|
{
|
|
CCGetIconSize(&plv->ci, himl, &plv->cxState , &plv->cyState);
|
|
}
|
|
else
|
|
{
|
|
plv->cxState = 0;
|
|
}
|
|
hImageOld = plv->himlState;
|
|
plv->himlState = himl;
|
|
plv->cyItem = ListView_ComputeCYItemSize(plv);
|
|
break;
|
|
|
|
default:
|
|
fImageSizeChanged = TRUE;
|
|
TraceMsg(TF_LISTVIEW, "sh TR - LVM_SETIMAGELIST: unrecognized iImageList");
|
|
break;
|
|
}
|
|
|
|
if (himl && !(plv->ci.style & LVS_SHAREIMAGELISTS))
|
|
ImageList_SetBkColor(himl, plv->clrBk);
|
|
|
|
// Imagelist size changed... if we're in tileview, we need to recalculate the tilesize.
|
|
if (ListView_IsTileView(plv) && (iImageList == LVSIL_STATE || iImageList == LVSIL_NORMAL))
|
|
{
|
|
ListView_RecalcTileSize(plv);
|
|
}
|
|
|
|
if(fImageSizeChanged)
|
|
{
|
|
// Now, recompute!
|
|
plv->rcView.left = RECOMPUTE; // invalidate this up front to avoid the asserts - it'll get recalculated anyway
|
|
_ListView_RecomputeEx(plv, NULL, 0, TRUE);
|
|
}
|
|
|
|
if (ListView_Count(plv) > 0)
|
|
InvalidateRect(plv->ci.hwnd, NULL, TRUE);
|
|
|
|
return hImageOld;
|
|
}
|
|
|
|
BOOL ListView_OnGetItemA(LV* plv, LV_ITEMA *plvi)
|
|
{
|
|
LPWSTR pszW = NULL;
|
|
LPSTR pszC = NULL;
|
|
BOOL fRet;
|
|
|
|
//HACK ALERT -- this code assumes that LV_ITEMA is exactly the same
|
|
// as LV_ITEMW except for the pointer to the string.
|
|
COMPILETIME_ASSERT(sizeof(LV_ITEMA) == sizeof(LV_ITEMW))
|
|
|
|
if (!plvi)
|
|
return FALSE;
|
|
|
|
if ((plvi->mask & LVIF_TEXT) && (plvi->pszText != NULL))
|
|
{
|
|
pszC = plvi->pszText;
|
|
pszW = LocalAlloc(LMEM_FIXED, plvi->cchTextMax * sizeof(WCHAR));
|
|
if (pszW == NULL)
|
|
return FALSE;
|
|
plvi->pszText = (LPSTR)pszW;
|
|
}
|
|
|
|
fRet = ListView_OnGetItem(plv, (LV_ITEM *) plvi);
|
|
|
|
if (pszW)
|
|
{
|
|
if (plvi->pszText != LPSTR_TEXTCALLBACKA)
|
|
{
|
|
if (fRet && plvi->cchTextMax)
|
|
ConvertWToAN(plv->ci.uiCodePage, pszC, plvi->cchTextMax, (LPWSTR)plvi->pszText, -1);
|
|
plvi->pszText = pszC;
|
|
}
|
|
|
|
LocalFree(pszW);
|
|
}
|
|
|
|
return fRet;
|
|
|
|
}
|
|
|
|
BOOL ListView_OnGetItem(LV* plv, LV_ITEM* plvi)
|
|
{
|
|
UINT mask;
|
|
LISTITEM* pitem = NULL;
|
|
LV_DISPINFO nm;
|
|
|
|
if (!plvi)
|
|
{
|
|
RIPMSG(0, "LVM_GET(ITEM|ITEMTEXT): Invalid pitem = NULL");
|
|
return FALSE;
|
|
}
|
|
|
|
if (!ListView_IsValidItemNumber(plv, plvi->iItem))
|
|
{
|
|
#ifdef DEBUG
|
|
// owner data views (e.g. docfind) may change the number of items in listview
|
|
// while we are doing something, thus hitting this rip
|
|
if (!ListView_IsOwnerData(plv))
|
|
RIPMSG(0, "LVM_GET(ITEM|ITEMTEXT|ITEMSTATE): item=%d does not exist", plvi->iItem);
|
|
#endif
|
|
return FALSE;
|
|
}
|
|
|
|
nm.item.mask = 0;
|
|
mask = plvi->mask;
|
|
|
|
if (!ListView_IsOwnerData(plv))
|
|
{
|
|
// Standard listviews
|
|
pitem = ListView_FastGetItemPtr(plv, plvi->iItem);
|
|
ASSERT(pitem);
|
|
|
|
// Handle sub-item cases for report view
|
|
//
|
|
if (plvi->iSubItem != 0)
|
|
{
|
|
LISTSUBITEM lsi;
|
|
|
|
ListView_GetSubItem(plv, plvi->iItem, plvi->iSubItem, &lsi);
|
|
if (mask & LVIF_TEXT)
|
|
{
|
|
if (lsi.pszText != LPSTR_TEXTCALLBACK)
|
|
{
|
|
Str_GetPtr0(lsi.pszText, plvi->pszText, plvi->cchTextMax);
|
|
}
|
|
else
|
|
{
|
|
// if this is LVIF_NORECOMPUTE we will update pszText later
|
|
nm.item.mask |= LVIF_TEXT;
|
|
}
|
|
}
|
|
|
|
if ((mask & LVIF_IMAGE) && (plv->exStyle & LVS_EX_SUBITEMIMAGES))
|
|
{
|
|
plvi->iImage = lsi.iImage;
|
|
if (lsi.iImage == I_IMAGECALLBACK)
|
|
nm.item.mask |= LVIF_IMAGE;
|
|
}
|
|
|
|
if (mask & LVIF_STATE)
|
|
{
|
|
|
|
if (ListView_FullRowSelect(plv))
|
|
{
|
|
// if we're in full row select,
|
|
// the state bit for select and focus follows column 0.
|
|
lsi.state |= pitem->state & (LVIS_SELECTED | LVIS_FOCUSED | LVIS_DROPHILITED);
|
|
}
|
|
|
|
plvi->state = lsi.state & plvi->stateMask;
|
|
|
|
|
|
if (plv->stateCallbackMask)
|
|
{
|
|
nm.item.stateMask = (plvi->stateMask & plv->stateCallbackMask);
|
|
if (nm.item.stateMask)
|
|
{
|
|
nm.item.mask |= LVIF_STATE;
|
|
nm.item.state = 0;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (mask & LVIF_TEXT)
|
|
{
|
|
if (pitem->pszText != LPSTR_TEXTCALLBACK)
|
|
{
|
|
Str_GetPtr0(pitem->pszText, plvi->pszText, plvi->cchTextMax);
|
|
}
|
|
else
|
|
{
|
|
// if this is LVIF_NORECOMPUTE we will update pszText later
|
|
nm.item.mask |= LVIF_TEXT;
|
|
}
|
|
}
|
|
|
|
if (mask & LVIF_IMAGE)
|
|
{
|
|
plvi->iImage = pitem->iImage;
|
|
if (pitem->iImage == I_IMAGECALLBACK)
|
|
nm.item.mask |= LVIF_IMAGE;
|
|
}
|
|
|
|
if (mask & LVIF_INDENT)
|
|
{
|
|
plvi->iIndent = pitem->iIndent;
|
|
if (pitem->iIndent == I_INDENTCALLBACK)
|
|
nm.item.mask |= LVIF_INDENT;
|
|
}
|
|
|
|
if (mask & LVIF_STATE)
|
|
{
|
|
plvi->state = (pitem->state & plvi->stateMask);
|
|
|
|
if (plv->stateCallbackMask)
|
|
{
|
|
nm.item.stateMask = (plvi->stateMask & plv->stateCallbackMask);
|
|
if (nm.item.stateMask)
|
|
{
|
|
nm.item.mask |= LVIF_STATE;
|
|
nm.item.state = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (mask & LVIF_GROUPID)
|
|
{
|
|
if (LISTITEM_HASGROUP(pitem))
|
|
{
|
|
plvi->iGroupId = pitem->pGroup->iGroupId;
|
|
}
|
|
else
|
|
{
|
|
nm.item.mask |= LVIF_GROUPID;
|
|
}
|
|
}
|
|
|
|
if (mask & LVIF_COLUMNS)
|
|
{
|
|
if ((plvi->puColumns == NULL) || (plvi->cColumns > CCMAX_TILE_COLUMNS))
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
if (pitem->cColumns == I_COLUMNSCALLBACK)
|
|
{
|
|
nm.item.mask |= LVIF_COLUMNS;
|
|
}
|
|
else
|
|
{
|
|
plvi->cColumns = pitem->cColumns;
|
|
if (plvi->cColumns < pitem->cColumns)
|
|
{
|
|
// Not enough room to store the columns
|
|
return FALSE;
|
|
}
|
|
|
|
// Copy the array
|
|
if (plvi->puColumns && pitem->puColumns)
|
|
{
|
|
CopyMemory(plvi->puColumns, pitem->puColumns, plvi->cColumns * sizeof(UINT));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (mask & LVIF_PARAM)
|
|
{
|
|
plvi->lParam = pitem->lParam;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Complete call back for info...
|
|
|
|
// Handle sub-item cases for report view
|
|
//
|
|
if (plvi->iSubItem != 0)
|
|
{
|
|
// if there are no subitem images, don't query for them
|
|
if (!(plv->exStyle & LVS_EX_SUBITEMIMAGES))
|
|
mask &= ~LVIF_IMAGE;
|
|
|
|
// don't allow indent on the non-0th column
|
|
mask &= ~LVIF_INDENT;
|
|
}
|
|
|
|
if (mask & LVIF_PARAM)
|
|
plvi->lParam = 0L; // Dont have any to return now...
|
|
|
|
if (mask & LVIF_STATE)
|
|
{
|
|
plvi->state = 0;
|
|
|
|
if ((plvi->iSubItem == 0) || ListView_FullRowSelect(plv))
|
|
{
|
|
if (plvi->iItem == plv->iFocus)
|
|
plvi->state |= LVIS_FOCUSED;
|
|
|
|
if (plv->plvrangeSel->lpVtbl->IsSelected(plv->plvrangeSel, plvi->iItem) == S_OK)
|
|
plvi->state |= LVIS_SELECTED;
|
|
|
|
if (plv->plvrangeCut->lpVtbl->IsSelected(plv->plvrangeCut, plvi->iItem) == S_OK)
|
|
plvi->state |= LVIS_CUT;
|
|
|
|
if (plvi->iItem == plv->iDropHilite)
|
|
plvi->state |= LVIS_DROPHILITED;
|
|
|
|
plvi->state &= plvi->stateMask;
|
|
}
|
|
|
|
if (plv->stateCallbackMask)
|
|
{
|
|
nm.item.stateMask = (plvi->stateMask & plv->stateCallbackMask);
|
|
if (nm.item.stateMask)
|
|
{
|
|
nm.item.mask |= LVIF_STATE;
|
|
nm.item.state = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (mask & LVIF_COLUMNS)
|
|
{
|
|
nm.item.mask |= LVIF_COLUMNS;
|
|
}
|
|
|
|
nm.item.mask |= (mask & (LVIF_TEXT | LVIF_IMAGE | LVIF_INDENT));
|
|
}
|
|
|
|
if (mask & LVIF_NORECOMPUTE)
|
|
{
|
|
if (nm.item.mask & LVIF_TEXT)
|
|
plvi->pszText = LPSTR_TEXTCALLBACK;
|
|
|
|
if (nm.item.mask & LVIF_COLUMNS)
|
|
plvi->cColumns = I_COLUMNSCALLBACK;
|
|
}
|
|
else if (nm.item.mask)
|
|
{
|
|
UINT rguColumns[CCMAX_TILE_COLUMNS];
|
|
nm.item.iItem = plvi->iItem;
|
|
nm.item.iSubItem = plvi->iSubItem;
|
|
if (ListView_IsOwnerData(plv))
|
|
nm.item.lParam = 0L;
|
|
else
|
|
nm.item.lParam = pitem->lParam;
|
|
|
|
// just in case LVIF_IMAGE is set and callback doesn't fill it in
|
|
// ... we'd rather have a -1 than whatever garbage is on the stack
|
|
nm.item.iImage = -1;
|
|
nm.item.iIndent = 0;
|
|
if (nm.item.mask & LVIF_TEXT)
|
|
{
|
|
RIPMSG(plvi->pszText != NULL, "LVM_GET(ITEM|ITEMTEXT) null string pointer");
|
|
|
|
if (plvi->pszText)
|
|
{
|
|
nm.item.pszText = plvi->pszText;
|
|
nm.item.cchTextMax = plvi->cchTextMax;
|
|
|
|
// Make sure the buffer is zero terminated...
|
|
if (nm.item.cchTextMax)
|
|
*nm.item.pszText = 0;
|
|
}
|
|
else
|
|
{
|
|
// Don't make caller smash null pointer
|
|
nm.item.mask &= ~LVIF_TEXT;
|
|
}
|
|
}
|
|
|
|
if (nm.item.mask & LVIF_COLUMNS)
|
|
{
|
|
nm.item.cColumns = plvi->cColumns;
|
|
nm.item.puColumns = rguColumns;
|
|
|
|
if (plvi->puColumns && plvi->cColumns && plvi->cColumns < ARRAYSIZE(rguColumns))
|
|
{
|
|
CopyMemory(rguColumns, plvi->puColumns, sizeof(UINT) * plvi->cColumns);
|
|
}
|
|
}
|
|
|
|
CCSendNotify(&plv->ci, LVN_GETDISPINFO, &nm.hdr);
|
|
|
|
// use nm.item.mask to give the app a chance to change values
|
|
if (nm.item.mask & LVIF_INDENT)
|
|
plvi->iIndent = nm.item.iIndent;
|
|
if (nm.item.mask & LVIF_GROUPID)
|
|
{
|
|
if (pitem)
|
|
{
|
|
if (nm.item.iGroupId == I_GROUPIDNONE)
|
|
{
|
|
ListView_RemoveItemFromItsGroup(plv, pitem);
|
|
LISTITEM_SETASKEDFORGROUP(pitem);
|
|
}
|
|
else
|
|
{
|
|
LISTGROUP* pgrp = ListView_FindGroupFromID(plv, nm.item.iGroupId, NULL);
|
|
|
|
if (pgrp != pitem->pGroup)
|
|
{
|
|
ListView_RemoveItemFromItsGroup(plv, pitem);
|
|
|
|
pitem->pGroup = pgrp;
|
|
if (pgrp)
|
|
{
|
|
DPA_AppendPtr(pgrp->hdpa, pitem);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
plvi->iGroupId = nm.item.iGroupId;
|
|
}
|
|
|
|
if (nm.item.mask & LVIF_STATE)
|
|
plvi->state ^= ((plvi->state ^ nm.item.state) & nm.item.stateMask);
|
|
if (nm.item.mask & LVIF_IMAGE)
|
|
plvi->iImage = nm.item.iImage;
|
|
if (nm.item.mask & LVIF_TEXT)
|
|
plvi->pszText = CCReturnDispInfoText(nm.item.pszText, plvi->pszText, plvi->cchTextMax);
|
|
if (nm.item.mask & LVIF_COLUMNS)
|
|
{
|
|
// Put the # of columns back in the LV_ITEM struct. Don't need to
|
|
// do anything with puColumns.
|
|
|
|
UINT cColumns = (nm.item.cColumns == I_COLUMNSCALLBACK) ? 0 : nm.item.cColumns;
|
|
UINT cColumnsToCopy = min(cColumns, plvi->cColumns);
|
|
|
|
// Copy rguColumns back into the thing we were passed.
|
|
CopyMemory(plvi->puColumns, rguColumns, sizeof(UINT) * cColumnsToCopy);
|
|
|
|
plvi->cColumns = cColumnsToCopy;
|
|
}
|
|
|
|
if (pitem && (nm.item.mask & LVIF_DI_SETITEM))
|
|
{
|
|
|
|
//
|
|
// The SendNotify above can set about a terrible series of events
|
|
// whereby asking for DISPINFO causes the shell to look around
|
|
// (call peekmessage) to see if its got a new async icon for the
|
|
// listview. This lets other messages be delivered, such as an
|
|
// UPDATEIMAGE of Index == -1 (if the user is changing icon sizing
|
|
// at the same time). This causes a re-enumeration of the desktop
|
|
// and hence this very listview is torn down and rebuilt while
|
|
// we're sitting here for the DISPINFO to finish. Thus, as a cheap
|
|
// and dirty solution, I check to see if the item I think I have
|
|
// is the same one I had when I made the notify, and if not, I
|
|
// bail. Don't blame me, I'm just cleaning up the mess.
|
|
|
|
if (pitem != ListView_GetItemPtr(plv, plvi->iItem))
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
if (nm.item.iSubItem == 0)
|
|
{
|
|
//DebugMsg(TF_LISTVIEW, "SAVING ITEMS!");
|
|
if (nm.item.mask & LVIF_IMAGE)
|
|
pitem->iImage = (short) nm.item.iImage;
|
|
|
|
if (nm.item.mask & LVIF_INDENT)
|
|
pitem->iIndent = (short) nm.item.iIndent;
|
|
|
|
if (nm.item.mask & LVIF_TEXT)
|
|
if (nm.item.pszText)
|
|
{
|
|
Str_Set(&pitem->pszText, nm.item.pszText);
|
|
}
|
|
|
|
if (nm.item.mask & LVIF_STATE)
|
|
pitem->state ^= ((pitem->state ^ nm.item.state) & nm.item.stateMask);
|
|
|
|
if (nm.item.mask & LVIF_COLUMNS)
|
|
{
|
|
Tile_Set(&pitem->puColumns, &pitem->cColumns, nm.item.puColumns, nm.item.cColumns);
|
|
|
|
// Just did a Tile_Set - need to recompute.
|
|
ListView_SetSRecompute(pitem);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
ListView_SetSubItem(plv, &nm.item);
|
|
}
|
|
}
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
BOOL ListView_OnSetItemA(LV* plv, LV_ITEMA* plvi)
|
|
{
|
|
LPWSTR pszW = NULL;
|
|
LPSTR pszC = NULL;
|
|
BOOL fRet;
|
|
|
|
// Let ListView_OnSetItem() handle owner-data validation
|
|
|
|
//HACK ALERT -- this code assumes that LV_ITEMA is exactly the same
|
|
// as LV_ITEMW except for the pointer to the string.
|
|
COMPILETIME_ASSERT(sizeof(LV_ITEMA) == sizeof(LV_ITEMW));
|
|
|
|
if (!plvi)
|
|
return FALSE;
|
|
|
|
if ((plvi->mask & LVIF_TEXT) && (plvi->pszText != NULL))
|
|
{
|
|
pszC = plvi->pszText;
|
|
pszW = ProduceWFromA(plv->ci.uiCodePage, pszC);
|
|
if (pszW == NULL)
|
|
return FALSE;
|
|
plvi->pszText = (LPSTR)pszW;
|
|
}
|
|
|
|
fRet = ListView_OnSetItem(plv, (const LV_ITEM*) plvi);
|
|
|
|
if (pszW != NULL)
|
|
{
|
|
plvi->pszText = pszC;
|
|
|
|
FreeProducedString(pszW);
|
|
}
|
|
return fRet;
|
|
}
|
|
|
|
BOOL ListView_OnSetItem(LV* plv, const LV_ITEM* plvi)
|
|
{
|
|
LISTITEM* pitem = NULL;
|
|
UINT mask;
|
|
UINT maskChanged;
|
|
UINT rdwFlags=RDW_INVALIDATE;
|
|
int i;
|
|
UINT stateOld, stateNew;
|
|
BOOL fFocused = FALSE;
|
|
BOOL fSelected = FALSE;
|
|
BOOL fStateImageChanged = FALSE;
|
|
|
|
if (ListView_IsOwnerData(plv))
|
|
{
|
|
RIPMSG(0, "LVM_SETITEM: Invalid for owner-data listview");
|
|
return FALSE;
|
|
}
|
|
|
|
if (!plvi)
|
|
return FALSE;
|
|
|
|
RIPMSG(plvi->iSubItem >= 0, "ListView_OnSetItem: Invalid item index");
|
|
|
|
if (plv->himl && (plv->clrBk != ImageList_GetBkColor(plv->himl)))
|
|
rdwFlags |= RDW_ERASE;
|
|
|
|
mask = plvi->mask;
|
|
if (!mask)
|
|
return TRUE;
|
|
|
|
// If we're setting a subitem, handle it elsewhere...
|
|
//
|
|
if (plvi->iSubItem > 0)
|
|
return ListView_SetSubItem(plv, plvi);
|
|
|
|
i = plvi->iItem;
|
|
|
|
pitem = ListView_GetItemPtr(plv, i);
|
|
if (!pitem)
|
|
return FALSE;
|
|
|
|
//REVIEW: This is a BOGUS HACK, and should be fixed.
|
|
//This incorrectly calculates the old state (since we may
|
|
// have to send LVN_GETDISPINFO to get it).
|
|
//
|
|
stateOld = stateNew = 0;
|
|
if (mask & LVIF_STATE)
|
|
{
|
|
stateOld = pitem->state & plvi->stateMask;
|
|
stateNew = plvi->state & plvi->stateMask;
|
|
}
|
|
|
|
// Prevent multiple selections in a single-select listview.
|
|
if ((plv->ci.style & LVS_SINGLESEL) && (mask & LVIF_STATE) && (stateNew & LVIS_SELECTED))
|
|
{
|
|
ListView_DeselectAll(plv, i);
|
|
|
|
// Refresh the old state information
|
|
stateOld = pitem->state & plvi->stateMask;
|
|
}
|
|
|
|
if (!ListView_SendChange(plv, i, 0, LVN_ITEMCHANGING, stateOld, stateNew, mask, pitem->lParam))
|
|
return FALSE;
|
|
|
|
maskChanged = 0;
|
|
|
|
if (mask & LVIF_STATE)
|
|
{
|
|
UINT change = (pitem->state ^ plvi->state) & plvi->stateMask;
|
|
|
|
if (change)
|
|
{
|
|
pitem->state ^= change;
|
|
|
|
maskChanged |= LVIF_STATE;
|
|
|
|
// the selection state has changed.. update selected count
|
|
if (change & LVIS_SELECTED)
|
|
{
|
|
fSelected = TRUE;
|
|
|
|
if (pitem->state & LVIS_SELECTED)
|
|
{
|
|
plv->nSelected++;
|
|
}
|
|
else
|
|
{
|
|
if (plv->nSelected > 0)
|
|
plv->nSelected--;
|
|
}
|
|
}
|
|
|
|
// For some bits we can only invert the label area...
|
|
// fSelectOnlyChange = ((change & ~(LVIS_SELECTED | LVIS_FOCUSED | LVIS_DROPHILITED)) == 0);
|
|
// fEraseItem = ((change & ~(LVIS_SELECTED | LVIS_DROPHILITED)) != 0);
|
|
|
|
// try to steal focus from the previous guy.
|
|
if (change & LVIS_FOCUSED)
|
|
{
|
|
BOOL fUnfolded = ListView_IsItemUnfolded(plv, plv->iFocus);
|
|
int iOldFocus = plv->iFocus;
|
|
RECT rcLabel;
|
|
|
|
fFocused = TRUE;
|
|
|
|
if (plv->iFocus != i)
|
|
{
|
|
if ((plv->iFocus == -1) || ListView_OnSetItemState(plv, plv->iFocus, 0, LVIS_FOCUSED))
|
|
{
|
|
ASSERT(pitem->state & LVIS_FOCUSED);
|
|
plv->iFocus = i;
|
|
if (plv->iMark == -1)
|
|
plv->iMark = i;
|
|
}
|
|
else
|
|
{
|
|
fFocused = FALSE;
|
|
pitem->state &= ~LVIS_FOCUSED;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
ASSERT(!(pitem->state & LVIS_FOCUSED));
|
|
plv->iFocus = -1;
|
|
}
|
|
|
|
// If we were previously unfolded and we move the focus we must
|
|
// attempt to refresh the previous focus owner to referect this change.
|
|
|
|
if (fUnfolded && !ListView_IsItemUnfolded(plv, iOldFocus) && (plv->iItemDrawing != iOldFocus))
|
|
{
|
|
ListView_GetUnfoldedRect(plv, iOldFocus, &rcLabel);
|
|
RedrawWindow(plv->ci.hwnd, &rcLabel, NULL, RDW_INVALIDATE|RDW_ERASE);
|
|
}
|
|
|
|
// Kill the tooltip if focus moves, it causes us headaches otherwise!
|
|
ListView_PopBubble(plv);
|
|
}
|
|
|
|
if (change & LVIS_CUT ||
|
|
plv->clrTextBk == CLR_NONE)
|
|
rdwFlags |= RDW_ERASE;
|
|
|
|
if (change & LVIS_OVERLAYMASK)
|
|
{
|
|
// Overlay changed, so need to blow away icon region cache
|
|
if (pitem->hrgnIcon)
|
|
{
|
|
if (pitem->hrgnIcon != (HANDLE) -1)
|
|
DeleteObject(pitem->hrgnIcon);
|
|
pitem->hrgnIcon = NULL;
|
|
}
|
|
}
|
|
|
|
fStateImageChanged = (change & LVIS_STATEIMAGEMASK);
|
|
|
|
}
|
|
}
|
|
|
|
if (mask & LVIF_TEXT)
|
|
{
|
|
// need to do this now because we're changing the text
|
|
// so we need to get the rect of the thing before the text changes
|
|
// but don't redraw the item we are currently painting
|
|
if (plv->iItemDrawing != i)
|
|
{
|
|
ListView_InvalidateItemEx(plv, i, FALSE,
|
|
RDW_INVALIDATE | RDW_ERASE, LVIF_TEXT);
|
|
}
|
|
|
|
if (!Str_Set(&pitem->pszText, plvi->pszText))
|
|
return FALSE;
|
|
|
|
plv->rcView.left = RECOMPUTE;
|
|
ListView_SetSRecompute(pitem);
|
|
maskChanged |= LVIF_TEXT;
|
|
}
|
|
|
|
if (mask & LVIF_INDENT)
|
|
{
|
|
if (pitem->iIndent != plvi->iIndent)
|
|
{
|
|
pitem->iIndent = (short) plvi->iIndent;
|
|
maskChanged |= LVIF_INDENT;
|
|
|
|
if (ListView_IsReportView(plv))
|
|
rdwFlags |= RDW_ERASE;
|
|
}
|
|
}
|
|
|
|
if (mask & LVIF_IMAGE)
|
|
{
|
|
if (pitem->iImage != plvi->iImage)
|
|
{
|
|
pitem->iImage = (short) plvi->iImage;
|
|
maskChanged |= LVIF_IMAGE;
|
|
|
|
if (pitem->hrgnIcon)
|
|
{
|
|
if (pitem->hrgnIcon != (HANDLE) -1)
|
|
DeleteObject(pitem->hrgnIcon);
|
|
pitem->hrgnIcon = NULL;
|
|
}
|
|
|
|
// erase if there was a set image
|
|
if (pitem->iImage != I_IMAGECALLBACK)
|
|
rdwFlags |= RDW_ERASE;
|
|
}
|
|
}
|
|
|
|
if (mask & LVIF_PARAM)
|
|
{
|
|
if (pitem->lParam != plvi->lParam)
|
|
{
|
|
pitem->lParam = plvi->lParam;
|
|
maskChanged |= LVIF_PARAM;
|
|
}
|
|
}
|
|
|
|
if (mask & LVIF_GROUPID)
|
|
{
|
|
LISTGROUP* pgrp = ListView_FindGroupFromID(plv, plvi->iGroupId, NULL);
|
|
if (pgrp)
|
|
{
|
|
if (pitem->pGroup != pgrp)
|
|
{
|
|
ListView_RemoveItemFromItsGroup(plv, pitem);
|
|
pitem->pGroup = pgrp;
|
|
DPA_AppendPtr(pgrp->hdpa, pitem);
|
|
|
|
if (ListView_RedrawEnabled(plv))
|
|
{
|
|
_ListView_RecomputeEx(plv, NULL, 0, FALSE);
|
|
InvalidateRect(plv->ci.hwnd, NULL, TRUE);
|
|
}
|
|
|
|
maskChanged |= LVIF_GROUPID;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (mask & LVIF_COLUMNS)
|
|
{
|
|
UINT uNumColumns = (plvi->cColumns == I_COLUMNSCALLBACK) ? 0 : plvi->cColumns;
|
|
|
|
if (((uNumColumns > 0) && (plvi->puColumns == NULL)) || // Didn't provide any columns
|
|
(uNumColumns > CCMAX_TILE_COLUMNS)) // Provided too many
|
|
{
|
|
return FALSE; // See note below about premature return.
|
|
}
|
|
|
|
if (!Tile_Set(&pitem->puColumns, &pitem->cColumns, plvi->puColumns, plvi->cColumns))
|
|
return FALSE;
|
|
// Note: if we fail here, we may have still set the LVIF_TEXT above...
|
|
// so the call partially succeeded. Oh well, no way to undo that.
|
|
|
|
maskChanged |= LVIF_COLUMNS;
|
|
|
|
// Columns changed - need to recompute this guy.
|
|
ListView_SetSRecompute(pitem);
|
|
}
|
|
|
|
if (maskChanged)
|
|
{
|
|
// don't redraw the item we are currently painting
|
|
if (plv->iItemDrawing != i)
|
|
ListView_InvalidateItemEx(plv, i, FALSE, rdwFlags, maskChanged);
|
|
|
|
TraceMsg(DM_LVSENDCHANGE, "LV - SendChange %d %d %d %d", i, stateOld, stateNew, maskChanged);
|
|
ListView_SendChange(plv, i, 0, LVN_ITEMCHANGED, stateOld, stateNew, maskChanged, pitem->lParam);
|
|
|
|
if (maskChanged & LVIF_TEXT)
|
|
NotifyWinEvent(EVENT_OBJECT_NAMECHANGE, plv->ci.hwnd, OBJID_CLIENT, i+1);
|
|
|
|
if (maskChanged & LVIF_STATE)
|
|
{
|
|
if (fFocused)
|
|
ListView_NotifyFocusEvent(plv);
|
|
|
|
if (fSelected)
|
|
{
|
|
if (stateNew & LVIS_SELECTED)
|
|
{
|
|
NotifyWinEvent((plv->nSelected == 1) ? EVENT_OBJECT_SELECTION :
|
|
EVENT_OBJECT_SELECTIONADD, plv->ci.hwnd, OBJID_CLIENT, i+1);
|
|
}
|
|
else
|
|
{
|
|
NotifyWinEvent(EVENT_OBJECT_SELECTIONREMOVE, plv->ci.hwnd, OBJID_CLIENT, i+1);
|
|
}
|
|
}
|
|
|
|
if (fStateImageChanged)
|
|
NotifyWinEvent(EVENT_OBJECT_STATECHANGE, plv->ci.hwnd, OBJID_CLIENT, i+1);
|
|
}
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
UINT ListView_OnGetItemState(LV* plv, int i, UINT mask)
|
|
{
|
|
LV_ITEM lvi;
|
|
|
|
lvi.mask = LVIF_STATE;
|
|
lvi.stateMask = mask;
|
|
lvi.iItem = i;
|
|
lvi.iSubItem = 0;
|
|
if (!ListView_OnGetItem(plv, &lvi))
|
|
return 0;
|
|
|
|
return lvi.state;
|
|
}
|
|
|
|
|
|
BOOL ListView_OnSetItemState(LV* plv, int i, UINT data, UINT mask)
|
|
{
|
|
UINT rdwFlags = RDW_INVALIDATE;
|
|
LV_ITEM lvi;
|
|
|
|
lvi.mask = LVIF_STATE;
|
|
lvi.state = data;
|
|
lvi.stateMask = mask;
|
|
lvi.iItem = i;
|
|
lvi.iSubItem = 0;
|
|
|
|
// if the item is -1, we will do it for all items. We special case
|
|
// a few cases here as to speed it up. For example if the mask is
|
|
// LVIS_SELECTED and data is zero it implies that we will deselect
|
|
// all items...
|
|
//
|
|
if (ListView_IsOwnerData(plv))
|
|
{
|
|
UINT uOldData = 0;
|
|
|
|
// these are the only two we handled
|
|
mask &= (LVIS_SELECTED | LVIS_FOCUSED | LVIS_CUT | LVIS_DROPHILITED);
|
|
if (!mask)
|
|
return TRUE;
|
|
|
|
if (plv->clrTextBk == CLR_NONE ||
|
|
(plv->himl && (plv->clrBk != ImageList_GetBkColor(plv->himl))))
|
|
{
|
|
rdwFlags |= RDW_ERASE;
|
|
}
|
|
|
|
if (i == -1)
|
|
{
|
|
// request selection state change for all
|
|
if (mask & LVIS_SELECTED)
|
|
{
|
|
if (data & LVIS_SELECTED)
|
|
{ // set selection
|
|
if ((plv->ci.style & LVS_SINGLESEL))
|
|
{ // cant make multiple selections in a single-select listview.
|
|
return FALSE;
|
|
}
|
|
|
|
if (plv->cTotalItems)
|
|
{
|
|
if (FAILED(plv->plvrangeSel->lpVtbl->IncludeRange(plv->plvrangeSel, 0, plv->cTotalItems - 1)))
|
|
return FALSE;
|
|
}
|
|
|
|
RedrawWindow(plv->ci.hwnd, NULL, NULL, rdwFlags);
|
|
}
|
|
else
|
|
{ // clear selection
|
|
if (plv->nSelected > 0)
|
|
{
|
|
ListView_InvalidateSelectedOrCutOwnerData(plv, plv->plvrangeSel);
|
|
if (FAILED(plv->plvrangeSel->lpVtbl->Clear(plv->plvrangeSel)))
|
|
return FALSE;
|
|
}
|
|
else
|
|
{
|
|
// if nothing was selected, then there's nothing to clear
|
|
// no change.
|
|
mask &= ~ LVIS_SELECTED;
|
|
}
|
|
}
|
|
uOldData |= (LVIS_SELECTED & (mask ^ data));
|
|
|
|
// Update our internal count to what the list thinks is the number selected...
|
|
plv->plvrangeSel->lpVtbl->CountIncluded(plv->plvrangeSel, &plv->nSelected);
|
|
}
|
|
|
|
// can maybe combine with above code...
|
|
if (mask & LVIS_CUT)
|
|
{
|
|
if (data & LVIS_CUT)
|
|
{ // set selection
|
|
|
|
if (plv->cTotalItems)
|
|
if (FAILED(plv->plvrangeCut->lpVtbl->IncludeRange(plv->plvrangeCut, 0, plv->cTotalItems - 1)))
|
|
return FALSE;
|
|
|
|
RedrawWindow(plv->ci.hwnd, NULL, NULL, rdwFlags);
|
|
|
|
}
|
|
else
|
|
{ // clear selection
|
|
if (plv->plvrangeCut->lpVtbl->IsEmpty(plv->plvrangeCut) != S_OK)
|
|
{
|
|
ListView_InvalidateSelectedOrCutOwnerData(plv, plv->plvrangeCut);
|
|
if (FAILED(plv->plvrangeCut->lpVtbl->Clear(plv->plvrangeCut)))
|
|
return FALSE;
|
|
}
|
|
else
|
|
{
|
|
// if nothing was selected, then there's nothing to clear
|
|
// no change.
|
|
mask &= ~ LVIS_CUT;
|
|
}
|
|
}
|
|
uOldData |= (LVIS_CUT & (mask ^ data));
|
|
}
|
|
|
|
// request focus state change
|
|
if (mask & LVIS_FOCUSED)
|
|
{
|
|
if (data & LVIS_FOCUSED)
|
|
{ // cant set focus to all
|
|
return FALSE;
|
|
}
|
|
else if (plv->iFocus != -1)
|
|
{
|
|
int iOldFocus = plv->iFocus;
|
|
// clear focus
|
|
uOldData |= (LVIS_FOCUSED & (mask ^ data));
|
|
plv->iFocus = -1;
|
|
// notify that the old focus is being lost
|
|
DebugMsg(DM_LVSENDCHANGE, TEXT("VLV: LVN_ITEMCHANGED: %d %d %d"), iOldFocus, LVIS_FOCUSED, 0);
|
|
ListView_SendChange(plv, iOldFocus, 0, LVN_ITEMCHANGED, LVIS_FOCUSED, 0, LVIF_STATE, 0);
|
|
ListView_InvalidateFoldedItem(plv, iOldFocus, TRUE, RDW_INVALIDATE |RDW_ERASE);
|
|
}
|
|
}
|
|
|
|
if (mask & LVIS_DROPHILITED)
|
|
{
|
|
if (data & LVIS_DROPHILITED)
|
|
{ // cant set focus to all
|
|
return FALSE;
|
|
}
|
|
else if (plv->iDropHilite != -1)
|
|
{
|
|
int iOldDropHilite = plv->iDropHilite;
|
|
// clear focus
|
|
uOldData |= (LVIS_FOCUSED & (mask ^ data));
|
|
plv->iDropHilite = -1;
|
|
// notify that the old focus is being lost
|
|
ListView_SendChange(plv, iOldDropHilite, 0, LVN_ITEMCHANGED, LVIS_DROPHILITED, 0, LVIF_STATE, 0);
|
|
ListView_InvalidateFoldedItem(plv, iOldDropHilite, TRUE, RDW_INVALIDATE |RDW_ERASE);
|
|
}
|
|
}
|
|
|
|
// invalidate and notify if there was a change
|
|
if (uOldData ^ (data & mask))
|
|
{
|
|
DebugMsg(DM_LVSENDCHANGE, TEXT("VLV: LVN_ITEMCHANGED: %d %d %d"), i, uOldData, data);
|
|
ListView_SendChange(plv, i, 0, LVN_ITEMCHANGED, uOldData, data, LVIF_STATE, 0);
|
|
|
|
if (mask & LVIS_SELECTED)
|
|
{
|
|
// Tell accessibility, "Selection changed in a complex way"
|
|
// (There is no "select all" or "select none" notification)
|
|
NotifyWinEvent(EVENT_OBJECT_SELECTIONWITHIN, plv->ci.hwnd, OBJID_CLIENT, CHILDID_SELF);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (!ListView_IsValidItemNumber(plv, i))
|
|
return FALSE;
|
|
|
|
// request selection state change
|
|
// and the selection state is new...
|
|
if ((mask & LVIS_SELECTED))
|
|
{
|
|
if (((plv->plvrangeSel->lpVtbl->IsSelected(plv->plvrangeSel, i) == S_OK) ? LVIS_SELECTED : 0) ^ (data & LVIS_SELECTED))
|
|
{
|
|
if (data & LVIS_SELECTED)
|
|
{ // set selection
|
|
if ((plv->ci.style & LVS_SINGLESEL))
|
|
{
|
|
// in single selection mode, we need to deselect everything else
|
|
if (!ListView_OnSetItemState(plv, -1, 0, LVIS_SELECTED))
|
|
return FALSE;
|
|
}
|
|
|
|
// now select the new item
|
|
if (FAILED(plv->plvrangeSel->lpVtbl->IncludeRange(plv->plvrangeSel, i, i)))
|
|
return FALSE;
|
|
}
|
|
else
|
|
{ // clear selection
|
|
if (FAILED(plv->plvrangeSel->lpVtbl->ExcludeRange(plv->plvrangeSel, i, i)))
|
|
return FALSE;
|
|
}
|
|
|
|
// something actually changed (or else we wouldn't be in this
|
|
// if block
|
|
uOldData |= (LVIS_SELECTED & (mask ^ data));
|
|
}
|
|
else
|
|
{
|
|
// nothing changed... so make the uOldData be the same for this bit
|
|
// else make this the same as
|
|
uOldData |= (LVIS_SELECTED & (mask & data));
|
|
}
|
|
|
|
// Update our internal count to what the list thinks is the number selected...
|
|
plv->plvrangeSel->lpVtbl->CountIncluded(plv->plvrangeSel, &plv->nSelected);
|
|
}
|
|
|
|
if ((mask & LVIS_CUT))
|
|
{
|
|
if (((plv->plvrangeCut->lpVtbl->IsSelected(plv->plvrangeCut, i) == S_OK) ? LVIS_CUT : 0) ^ (data & LVIS_CUT))
|
|
{
|
|
if (data & LVIS_CUT)
|
|
{
|
|
// now select the new item
|
|
if (FAILED(plv->plvrangeCut->lpVtbl->IncludeRange(plv->plvrangeCut, i, i)))
|
|
return FALSE;
|
|
}
|
|
else
|
|
{ // clear selection
|
|
if (FAILED(plv->plvrangeCut->lpVtbl->ExcludeRange(plv->plvrangeCut, i, i)))
|
|
return FALSE;
|
|
}
|
|
// something actually changed (or else we wouldn't be in this
|
|
// if block
|
|
uOldData |= (LVIS_CUT & (mask ^ data));
|
|
rdwFlags |= RDW_ERASE;
|
|
}
|
|
else
|
|
{
|
|
// nothing changed... so make the uOldData be the same for this bit
|
|
// else make this the same as
|
|
uOldData |= (LVIS_CUT & (mask & data));
|
|
}
|
|
}
|
|
|
|
// request focus state change
|
|
if (mask & LVIS_FOCUSED)
|
|
{
|
|
int iOldFocus = plv->iFocus;
|
|
|
|
if (data & LVIS_FOCUSED)
|
|
{ // set focus
|
|
if (i != plv->iFocus)
|
|
{
|
|
// we didn't have the focus before
|
|
plv->iFocus = i;
|
|
if (plv->iMark == -1)
|
|
plv->iMark = i;
|
|
if (iOldFocus != -1)
|
|
{
|
|
|
|
// we're stealing it from someone
|
|
// notify of the change
|
|
DebugMsg(DM_LVSENDCHANGE, TEXT("VLV: LVN_ITEMCHANGED: %d %d %d"), iOldFocus, LVIS_FOCUSED, 0);
|
|
ListView_SendChange(plv, iOldFocus, 0, LVN_ITEMCHANGED, LVIS_FOCUSED, 0, LVIF_STATE, 0);
|
|
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// we DID have the focus before
|
|
uOldData |= LVIS_FOCUSED;
|
|
}
|
|
}
|
|
else
|
|
{ // clear focus
|
|
if (i == plv->iFocus)
|
|
{
|
|
plv->iFocus = -1;
|
|
uOldData |= LVIS_FOCUSED;
|
|
}
|
|
}
|
|
}
|
|
|
|
// request focus state change
|
|
if (mask & LVIS_DROPHILITED)
|
|
{
|
|
int iOldDropHilite = plv->iDropHilite;
|
|
|
|
if (data & LVIS_DROPHILITED)
|
|
{ // set Drop Hilite
|
|
if (i != plv->iDropHilite)
|
|
{
|
|
// we didn't have the Drop Hilite before
|
|
plv->iDropHilite = i;
|
|
if (iOldDropHilite != -1)
|
|
{
|
|
// we're stealing it from someone
|
|
// notify of the change
|
|
ListView_SendChange(plv, iOldDropHilite, 0, LVN_ITEMCHANGED, LVIS_DROPHILITED, 0, LVIF_STATE, 0);
|
|
ListView_InvalidateFoldedItem(plv, iOldDropHilite, TRUE, RDW_INVALIDATE |RDW_ERASE);
|
|
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// we DID have the Drop Hilite before
|
|
uOldData |= LVIS_DROPHILITED;
|
|
}
|
|
}
|
|
else
|
|
{ // clear Drop Hilite
|
|
if (i == plv->iDropHilite)
|
|
{
|
|
plv->iDropHilite = -1;
|
|
uOldData |= LVIS_DROPHILITED;
|
|
}
|
|
}
|
|
}
|
|
|
|
// invalidate and notify if there was a change
|
|
if (uOldData ^ (data & mask))
|
|
{
|
|
DebugMsg(DM_LVSENDCHANGE, TEXT("VLV: LVN_ITEMCHANGED: %d %d %d"), i, uOldData, data);
|
|
ListView_SendChange(plv, i, 0, LVN_ITEMCHANGED, uOldData, data, LVIF_STATE, 0);
|
|
ListView_InvalidateItem(plv, i, TRUE, rdwFlags);
|
|
|
|
// Kill the tooltip if focus moves, it causes us headaches otherwise!
|
|
if ((uOldData ^ (data & mask)) & LVIS_FOCUSED)
|
|
{
|
|
ListView_PopBubble(plv);
|
|
ListView_NotifyFocusEvent(plv);
|
|
}
|
|
|
|
// Tell accessibility about the changes
|
|
if (mask & LVIS_SELECTED)
|
|
{
|
|
UINT event;
|
|
|
|
if (data & LVIS_SELECTED)
|
|
{
|
|
if (plv->nSelected == 1)
|
|
event = EVENT_OBJECT_SELECTION; // this object is the entire selection
|
|
else
|
|
event = EVENT_OBJECT_SELECTIONADD; // this object is selected
|
|
}
|
|
else
|
|
event = EVENT_OBJECT_SELECTIONREMOVE; // this object is unselected
|
|
NotifyWinEvent(event, plv->ci.hwnd, OBJID_CLIENT, i + 1);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (i != -1)
|
|
{
|
|
return ListView_OnSetItem(plv, &lvi);
|
|
}
|
|
else
|
|
{
|
|
UINT flags = LVNI_ALL;
|
|
|
|
if (data == 0)
|
|
{
|
|
switch (mask)
|
|
{
|
|
case LVIS_SELECTED:
|
|
flags = LVNI_SELECTED;
|
|
break;
|
|
case LVIS_CUT:
|
|
flags = LVNI_CUT;
|
|
break;
|
|
}
|
|
}
|
|
else if ((plv->ci.style & LVS_SINGLESEL) && (mask == LVIS_SELECTED))
|
|
return FALSE; /* can't select all in single-select listview */
|
|
else if ((mask & data) & LVIS_FOCUSED)
|
|
{
|
|
return FALSE; // can't set focus to everything
|
|
}
|
|
|
|
//
|
|
// Now iterate over all of the items that match our criteria and
|
|
// set their new value.
|
|
//
|
|
while ((lvi.iItem = ListView_OnGetNextItem(plv, lvi.iItem, flags)) != -1)
|
|
{
|
|
ListView_OnSetItem(plv, &lvi);
|
|
}
|
|
}
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
//
|
|
// Returns TRUE if the label of an item is not truncated (is unfolded) and FALSE
|
|
// otherwise. If FALSE is returned, it also fills the Unfolding text in pszText.
|
|
// If TRUE is returned, pszText is set to empty string.
|
|
//
|
|
BOOL ListView_IsItemUnfolded2(LV* plv, int iItem, int iSubItem, LPTSTR pszText, int cchTextMax)
|
|
{
|
|
BOOL bItemUnfolded = ListView_IsItemUnfolded(plv, iItem);
|
|
|
|
if (pszText && cchTextMax > 0) // Sanity checks on input params.
|
|
{
|
|
pszText[0] = 0;
|
|
|
|
if (!bItemUnfolded)
|
|
{
|
|
RECT rcLabel = {0};
|
|
LV_ITEM item;
|
|
|
|
item.iItem = iItem;
|
|
item.iSubItem = iSubItem;
|
|
item.mask = LVIF_TEXT | LVIF_PARAM;
|
|
|
|
if (ListView_IsTileView(plv))
|
|
{
|
|
TCalculateSubItemRect(plv, NULL, NULL, iItem, iSubItem, NULL, NULL, &bItemUnfolded);
|
|
if (!bItemUnfolded)
|
|
{
|
|
// Need to supply text.
|
|
item.pszText = pszText;
|
|
item.cchTextMax = cchTextMax;
|
|
ListView_OnGetItem(plv, &item);
|
|
}
|
|
}
|
|
else if (!ListView_IsIconView(plv))
|
|
{
|
|
if (ListView_IsLabelTip(plv) || ListView_IsInfoTip(plv))
|
|
{
|
|
BOOL fSuccess;
|
|
|
|
rcLabel.left = LVIR_LABEL;
|
|
|
|
if (iSubItem)
|
|
{
|
|
rcLabel.top = iSubItem;
|
|
fSuccess = ListView_OnGetSubItemRect(plv, iItem, &rcLabel);
|
|
}
|
|
else
|
|
{
|
|
fSuccess = ListView_OnGetItemRect(plv, iItem, &rcLabel);
|
|
}
|
|
|
|
if (fSuccess)
|
|
{
|
|
TCHAR szText[INFOTIPSIZE];
|
|
|
|
item.pszText = szText;
|
|
item.cchTextMax = min(ARRAYSIZE(szText), cchTextMax);
|
|
if (ListView_OnGetItem(plv, &item) && item.pszText != LPSTR_TEXTCALLBACK)
|
|
{
|
|
SIZE siz;
|
|
LVFAKEDRAW lvfd;
|
|
int cx;
|
|
HRESULT hr = E_FAIL;
|
|
|
|
ListView_BeginFakeCustomDraw(plv, &lvfd, &item);
|
|
ListView_BeginFakeItemDraw(&lvfd);
|
|
|
|
// ---------Label width----------- ---Client width---
|
|
cx = min(rcLabel.right - g_cxLabelMargin, plv->sizeClient.cx);
|
|
|
|
hr = GetTextExtentPoint32(lvfd.nmcd.nmcd.hdc, item.pszText, lstrlen(item.pszText), &siz) ?
|
|
S_OK : E_FAIL;
|
|
|
|
if (SUCCEEDED(hr) &&
|
|
(rcLabel.left + g_cxLabelMargin + siz.cx) > cx)
|
|
{
|
|
StringCchCopy(pszText, item.cchTextMax, item.pszText);
|
|
}
|
|
else
|
|
{
|
|
// Not truncated after all
|
|
bItemUnfolded = TRUE;
|
|
}
|
|
|
|
ListView_EndFakeItemDraw(&lvfd);
|
|
ListView_EndFakeCustomDraw(&lvfd);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Large icon view is the only one that folds
|
|
if (ListView_GetUnfoldedRect(plv, iItem, &rcLabel))
|
|
{
|
|
item.pszText = pszText;
|
|
item.cchTextMax = cchTextMax;
|
|
ListView_OnGetItem(plv, &item);
|
|
}
|
|
else
|
|
{
|
|
// Item was never folded
|
|
bItemUnfolded = TRUE;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return bItemUnfolded;
|
|
}
|
|
|
|
|
|
// Rather than thunking to ListView_OnGetItemText, we let ListView_GetItemA
|
|
// do the work.
|
|
|
|
int ListView_OnGetItemTextA(LV* plv, int i, LV_ITEMA *plvi)
|
|
{
|
|
if (!plvi)
|
|
return 0;
|
|
|
|
RIPMSG(plvi->pszText != NULL, "LVM_GETITEMTEXT null string pointer");
|
|
|
|
plvi->mask = LVIF_TEXT;
|
|
plvi->iItem = i;
|
|
if (!ListView_OnGetItemA(plv, plvi))
|
|
return 0;
|
|
|
|
return lstrlenA(plvi->pszText);
|
|
}
|
|
|
|
int ListView_OnGetItemText(LV* plv, int i, LV_ITEM *plvi)
|
|
{
|
|
if (!plvi)
|
|
return 0;
|
|
|
|
RIPMSG(plvi->pszText != NULL, "LVM_GETITEMTEXT null string pointer");
|
|
|
|
plvi->mask = LVIF_TEXT;
|
|
plvi->iItem = i;
|
|
if (!ListView_OnGetItem(plv, plvi))
|
|
return 0;
|
|
|
|
return lstrlen(plvi->pszText);
|
|
}
|
|
|
|
|
|
BOOL WINAPI ListView_OnSetItemTextA(LV* plv, int i, int iSubItem, LPCSTR pszText)
|
|
{
|
|
LPWSTR pszW = NULL;
|
|
BOOL fRet;
|
|
|
|
// Let ListView_OnSetItemText() handle owner-data validation
|
|
|
|
if (pszText != NULL)
|
|
{
|
|
pszW = ProduceWFromA(plv->ci.uiCodePage, pszText);
|
|
if (pszW == NULL)
|
|
{
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
fRet = ListView_OnSetItemText(plv, i, iSubItem, pszW);
|
|
|
|
FreeProducedString(pszW);
|
|
|
|
return fRet;
|
|
}
|
|
|
|
BOOL WINAPI ListView_OnSetItemText(LV* plv, int i, int iSubItem, LPCTSTR pszText)
|
|
{
|
|
LV_ITEM lvi;
|
|
|
|
if (ListView_IsOwnerData(plv))
|
|
{
|
|
RIPMSG(0, "LVM_SETITEMTEXT: Invalid for owner-data listview");
|
|
return FALSE;
|
|
}
|
|
|
|
ListView_InvalidateTTLastHit(plv, i);
|
|
|
|
lvi.mask = LVIF_TEXT;
|
|
lvi.pszText = (LPTSTR)pszText;
|
|
lvi.iItem = i;
|
|
lvi.iSubItem = iSubItem;
|
|
|
|
return ListView_OnSetItem(plv, &lvi);
|
|
}
|
|
|
|
VOID CALLBACK ImgCtxCallback(void * pvImgCtx, void * pvArg)
|
|
{
|
|
LV *plv = (LV *)pvArg;
|
|
ULONG ulState;
|
|
SIZE sizeImg;
|
|
IImgCtx *pImgCtx = plv->pImgCtx;
|
|
|
|
IImgCtx_GetStateInfo(pImgCtx, &ulState, &sizeImg, TRUE);
|
|
|
|
if (ulState & (IMGLOAD_STOPPED | IMGLOAD_ERROR))
|
|
{
|
|
TraceMsg(TF_BKIMAGE, "Listview ImageCallback: Error!");
|
|
plv->fImgCtxComplete = FALSE;
|
|
}
|
|
|
|
else if (ulState & IMGCHG_COMPLETE)
|
|
{
|
|
TraceMsg(TF_BKIMAGE, "Listview ImageCallback: Complete!");
|
|
plv->fImgCtxComplete = TRUE;
|
|
InvalidateRect(plv->ci.hwnd, NULL, TRUE);
|
|
}
|
|
}
|
|
|
|
void ListView_ReleaseBkImage(LV *plv)
|
|
{
|
|
if (plv->pImgCtx)
|
|
{
|
|
IImgCtx_Release(plv->pImgCtx);
|
|
plv->pImgCtx = NULL;
|
|
|
|
if (plv->hpalHalftone)
|
|
{
|
|
// No need to delete the half tone palette since we really
|
|
// share it with the image context and it will clean up.
|
|
plv->hpalHalftone = NULL;
|
|
}
|
|
}
|
|
|
|
if (plv->hbmBkImage)
|
|
{
|
|
DeleteObject(plv->hbmBkImage);
|
|
plv->hbmBkImage = NULL;
|
|
}
|
|
|
|
if (plv->pszBkImage)
|
|
{
|
|
LocalFree(plv->pszBkImage);
|
|
plv->pszBkImage = NULL;
|
|
}
|
|
}
|
|
|
|
BOOL WINAPI ListView_OnSetBkImage(LV* plv, LPLVBKIMAGE pbi)
|
|
{
|
|
BOOL fRet = FALSE;
|
|
if (!pbi)
|
|
return FALSE;
|
|
|
|
if (pbi->ulFlags & LVBKIF_TYPE_WATERMARK)
|
|
{
|
|
BITMAP bm;
|
|
if (pbi->ulFlags & ~LVBKIF_TYPE_WATERMARK)
|
|
return FALSE; // We don't support anything else with a watermark
|
|
|
|
if (plv->hbmpWatermark)
|
|
{
|
|
DeleteObject(plv->hbmpWatermark);
|
|
plv->hbmpWatermark = NULL;
|
|
}
|
|
|
|
if (pbi->hbm && GetObject(pbi->hbm, sizeof(bm), &bm))
|
|
{
|
|
plv->hbmpWatermark = pbi->hbm;
|
|
plv->szWatermark.cx = bm.bmWidth;
|
|
plv->szWatermark.cy = bm.bmHeight;
|
|
fRet = TRUE;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
LPCTSTR pszImage = pbi->pszImage;
|
|
long fl;
|
|
switch (pbi->ulFlags & LVBKIF_SOURCE_MASK)
|
|
{
|
|
case LVBKIF_SOURCE_NONE:
|
|
TraceMsg(TF_BKIMAGE, "LV SetBkImage to none");
|
|
ListView_ReleaseBkImage(plv);
|
|
break;
|
|
|
|
case LVBKIF_SOURCE_HBITMAP:
|
|
TraceMsg(TF_BKIMAGE, "LV SetBkImage to hBitmap %08lX", pbi->hbm);
|
|
ListView_ReleaseBkImage(plv);
|
|
if (pbi->hbm &&
|
|
(plv->pImgCtx = CBitmapImgCtx_Create(pbi->hbm)) != NULL)
|
|
{
|
|
plv->hbmBkImage = pbi->hbm;
|
|
}
|
|
else
|
|
{
|
|
pbi->ulFlags &= ~LVBKIF_SOURCE_HBITMAP;
|
|
}
|
|
break;
|
|
|
|
case LVBKIF_SOURCE_URL:
|
|
TraceMsg(TF_BKIMAGE, "LV SetBkImage to URL");
|
|
ListView_ReleaseBkImage(plv);
|
|
if (pszImage && pszImage[0])
|
|
{
|
|
HRESULT (*pfnCoCreateInstance)(REFCLSID, IUnknown *, DWORD, REFIID, void * *);
|
|
HRESULT hr;
|
|
HMODULE hmodOLE;
|
|
DWORD cchBkImage = lstrlen(pszImage)+1;
|
|
|
|
plv->pszBkImage = LocalAlloc(LPTR, cchBkImage * sizeof(TCHAR));
|
|
if (plv->pszBkImage == NULL)
|
|
{
|
|
TraceMsg(TF_BKIMAGE, "Wow, could not allocate memory for string!");
|
|
return FALSE;
|
|
}
|
|
StringCchCopy(plv->pszBkImage, cchBkImage, pszImage);
|
|
|
|
if (((hmodOLE = GetModuleHandle(TEXT("OLE32"))) == NULL) ||
|
|
((pfnCoCreateInstance = (HRESULT (*)(REFCLSID, IUnknown *, DWORD, REFIID, void * *))GetProcAddress(hmodOLE, "CoCreateInstance")) == NULL))
|
|
{
|
|
TraceMsg(TF_BKIMAGE, "Could not find CoCreateInstance!");
|
|
TraceMsg(TF_BKIMAGE, "Did the caller remember to call CoInitialize?");
|
|
return FALSE;
|
|
}
|
|
|
|
hr = pfnCoCreateInstance(&CLSID_IImgCtx, NULL, CLSCTX_INPROC_SERVER,
|
|
&IID_IImgCtx, (void * *)&plv->pImgCtx);
|
|
|
|
if (FAILED(hr))
|
|
{
|
|
TraceMsg(TF_BKIMAGE, "Could not create a pImgCtx!");
|
|
TraceMsg(TF_BKIMAGE, "Did you remember to register IEIMAGE.DLL?");
|
|
return FALSE;
|
|
}
|
|
//
|
|
// Mirror the downloaded image if the listview window is RTL mirrored,
|
|
// so that it would be displayed as is. [samera]
|
|
//
|
|
fl = ((IS_WINDOW_RTL_MIRRORED(plv->ci.hwnd)) ? DWN_MIRRORIMAGE : 0);
|
|
|
|
hr = IImgCtx_Load(plv->pImgCtx, pszImage, fl);
|
|
if (FAILED(hr))
|
|
{
|
|
IImgCtx_Release(plv->pImgCtx);
|
|
plv->pImgCtx = NULL;
|
|
TraceMsg(TF_BKIMAGE, "Could not init a pImgCtx!");
|
|
return FALSE;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
pbi->ulFlags &= ~LVBKIF_SOURCE_URL;
|
|
}
|
|
break;
|
|
|
|
default:
|
|
RIPMSG(0, "LVM_SETBKIMAGE: Unsupported image type %d", pbi->ulFlags & LVBKIF_SOURCE_MASK);
|
|
return FALSE;
|
|
}
|
|
|
|
plv->ulBkImageFlags = pbi->ulFlags;
|
|
plv->xOffsetPercent = pbi->xOffsetPercent;
|
|
plv->yOffsetPercent = pbi->yOffsetPercent;
|
|
|
|
//
|
|
// If we actually created a pImgCtx, initialize it here.
|
|
//
|
|
if (plv->pImgCtx)
|
|
{
|
|
if (plv->hpalHalftone == NULL)
|
|
{
|
|
IImgCtx_GetPalette(plv->pImgCtx, &plv->hpalHalftone);
|
|
}
|
|
|
|
plv->fImgCtxComplete = FALSE;
|
|
IImgCtx_SetCallback(plv->pImgCtx, ImgCtxCallback, plv);
|
|
IImgCtx_SelectChanges(plv->pImgCtx, IMGCHG_COMPLETE, 0, TRUE);
|
|
|
|
TraceMsg(TF_BKIMAGE, " SUCCESS!");
|
|
fRet = TRUE;
|
|
}
|
|
}
|
|
|
|
InvalidateRect(plv->ci.hwnd, NULL, TRUE);
|
|
|
|
return fRet;
|
|
}
|
|
|
|
BOOL WINAPI ListView_OnSetBkImageA(LV* plv, LPLVBKIMAGEA pbiA)
|
|
{
|
|
BOOL fProducedString = FALSE;
|
|
BOOL fRet;
|
|
LVBKIMAGEW biW;
|
|
|
|
CopyMemory(&biW, pbiA, sizeof(LVBKIMAGE));
|
|
|
|
switch (biW.ulFlags & LVBKIF_SOURCE_MASK)
|
|
{
|
|
case LVBKIF_SOURCE_NONE:
|
|
case LVBKIF_SOURCE_HBITMAP:
|
|
break;
|
|
|
|
case LVBKIF_SOURCE_URL:
|
|
if (biW.pszImage != NULL)
|
|
{
|
|
biW.pszImage = ProduceWFromA(plv->ci.uiCodePage, (LPCSTR)biW.pszImage);
|
|
if (biW.pszImage == (LPARAM)NULL)
|
|
{
|
|
return FALSE;
|
|
}
|
|
fProducedString = TRUE;
|
|
}
|
|
break;
|
|
|
|
default:
|
|
// Let ListView_OnSetBkImage() complain about the invalid parameter
|
|
break;
|
|
}
|
|
|
|
fRet = ListView_OnSetBkImage(plv, &biW);
|
|
|
|
if (fProducedString)
|
|
{
|
|
FreeProducedString((void *)biW.pszImage);
|
|
}
|
|
|
|
return fRet;
|
|
}
|
|
|
|
BOOL WINAPI ListView_OnGetBkImage(LV* plv, LPLVBKIMAGE pbi)
|
|
{
|
|
BOOL fRet = FALSE;
|
|
|
|
if (!IsBadWritePtr(pbi, sizeof(*pbi)))
|
|
{
|
|
if (pbi->ulFlags & LVBKIF_TYPE_WATERMARK)
|
|
{
|
|
pbi->hbm = plv->hbmpWatermark;
|
|
fRet = TRUE;
|
|
}
|
|
else
|
|
{
|
|
pbi->ulFlags = plv->ulBkImageFlags;
|
|
|
|
switch (plv->ulBkImageFlags & LVBKIF_SOURCE_MASK)
|
|
{
|
|
case LVBKIF_SOURCE_NONE:
|
|
fRet = TRUE;
|
|
break;
|
|
|
|
case LVBKIF_SOURCE_HBITMAP:
|
|
pbi->hbm = plv->hbmBkImage;
|
|
fRet = TRUE;
|
|
break;
|
|
|
|
case LVBKIF_SOURCE_URL:
|
|
if (!IsBadWritePtr(pbi->pszImage, pbi->cchImageMax * sizeof(TCHAR)))
|
|
{
|
|
StringCchCopy(pbi->pszImage, pbi->cchImageMax, plv->pszBkImage);
|
|
fRet = TRUE;
|
|
}
|
|
break;
|
|
|
|
default:
|
|
RIPMSG(0, "ListView_OnGetBkImage: Invalid source");
|
|
break;
|
|
}
|
|
|
|
pbi->xOffsetPercent = plv->xOffsetPercent;
|
|
pbi->yOffsetPercent = plv->yOffsetPercent;
|
|
}
|
|
}
|
|
|
|
return fRet;
|
|
}
|
|
|
|
BOOL WINAPI ListView_OnGetBkImageA(LV* plv, LPLVBKIMAGEA pbiA)
|
|
{
|
|
BOOL fRet = FALSE;
|
|
|
|
if (!IsBadWritePtr(pbiA, sizeof(*pbiA)))
|
|
{
|
|
pbiA->ulFlags = plv->ulBkImageFlags;
|
|
|
|
switch (plv->ulBkImageFlags & LVBKIF_SOURCE_MASK)
|
|
{
|
|
case LVBKIF_SOURCE_NONE:
|
|
fRet = TRUE;
|
|
break;
|
|
|
|
case LVBKIF_SOURCE_HBITMAP:
|
|
pbiA->hbm = plv->hbmBkImage;
|
|
fRet = TRUE;
|
|
break;
|
|
|
|
case LVBKIF_SOURCE_URL:
|
|
if (!IsBadWritePtr(pbiA->pszImage, pbiA->cchImageMax))
|
|
{
|
|
ConvertWToAN(plv->ci.uiCodePage, pbiA->pszImage,
|
|
pbiA->cchImageMax, plv->pszBkImage, -1);
|
|
fRet = TRUE;
|
|
}
|
|
break;
|
|
|
|
default:
|
|
RIPMSG(0, "ListView_OnGetBkImage: Invalid source");
|
|
break;
|
|
}
|
|
|
|
pbiA->xOffsetPercent = plv->xOffsetPercent;
|
|
pbiA->yOffsetPercent = plv->yOffsetPercent;
|
|
}
|
|
|
|
return fRet;
|
|
}
|
|
|
|
void ListView_FreeSubItem(PLISTSUBITEM plsi)
|
|
{
|
|
if (plsi)
|
|
{
|
|
Str_Set(&plsi->pszText, NULL);
|
|
LocalFree(plsi);
|
|
}
|
|
}
|
|
|
|
int ListView_GetCxScrollbar(LV* plv)
|
|
{
|
|
int cx;
|
|
|
|
if (((plv->exStyle & LVS_EX_FLATSB) == 0) ||
|
|
!FlatSB_GetScrollProp(plv->ci.hwnd, WSB_PROP_CXVSCROLL, &cx))
|
|
{
|
|
cx = g_cxScrollbar;
|
|
}
|
|
|
|
return cx;
|
|
}
|
|
|
|
int ListView_GetCyScrollbar(LV* plv)
|
|
{
|
|
int cy;
|
|
|
|
if (((plv->exStyle & LVS_EX_FLATSB) == 0) ||
|
|
!FlatSB_GetScrollProp(plv->ci.hwnd, WSB_PROP_CYHSCROLL, &cy))
|
|
{
|
|
cy = g_cyScrollbar;
|
|
}
|
|
|
|
return cy;
|
|
}
|
|
|
|
DWORD ListView_GetWindowStyle(LV* plv)
|
|
{
|
|
DWORD dwStyle;
|
|
|
|
if (((plv->exStyle & LVS_EX_FLATSB) == 0) ||
|
|
!FlatSB_GetScrollProp(plv->ci.hwnd, WSB_PROP_WINSTYLE, (LPINT)&dwStyle))
|
|
{
|
|
dwStyle = GetWindowStyle(plv->ci.hwnd);
|
|
}
|
|
|
|
return dwStyle;
|
|
}
|
|
|
|
int ListView_SetScrollInfo(LV *plv, int fnBar, LPSCROLLINFO lpsi, BOOL fRedraw)
|
|
{
|
|
int iRc;
|
|
|
|
if (plv->exStyle & LVS_EX_FLATSB)
|
|
{
|
|
iRc = FlatSB_SetScrollInfo(plv->ci.hwnd, fnBar, lpsi, fRedraw);
|
|
}
|
|
else
|
|
{
|
|
iRc = SetScrollInfo(plv->ci.hwnd, fnBar, lpsi, fRedraw);
|
|
}
|
|
|
|
//
|
|
// You'd think we were finished, but in fact the game is only half over.
|
|
//
|
|
// Some apps (e.g., Font Folder) will do
|
|
//
|
|
// SetWindowLong(hwnd, GWL_STYLE, newStyle);
|
|
//
|
|
// where newStyle toggles the WS_HSCROLL and/or WS_VSCROLL bits.
|
|
// This causes USER's internal bookkeeping to go completely out
|
|
// of whack: The ScrollInfo says that there is a scrollbar, but
|
|
// the window style says there isn't, or vice versa. The result
|
|
// is that we get a scrollbar when we shouldn't or vice versa.
|
|
//
|
|
// So each time we tweak the scroll info in a manner that changes
|
|
// the range and page, we kick USER in the head to make sure USER's
|
|
// view of the world (via style bits) is the same as the scroll
|
|
// bar's view of the world (via SCROLLINFO).
|
|
//
|
|
|
|
//
|
|
// We should always change SIF_PAGE and SIF_RANGE at the same time.
|
|
//
|
|
ASSERT((lpsi->fMask & (SIF_PAGE | SIF_RANGE)) == 0 ||
|
|
(lpsi->fMask & (SIF_PAGE | SIF_RANGE)) == (SIF_PAGE | SIF_RANGE));
|
|
|
|
if ((lpsi->fMask & (SIF_PAGE | SIF_RANGE)) == (SIF_PAGE | SIF_RANGE))
|
|
{
|
|
BOOL fShow;
|
|
fShow = lpsi->nMax && (int)lpsi->nPage <= lpsi->nMax;
|
|
|
|
#ifdef DEBUG
|
|
{
|
|
DWORD dwStyle, dwScroll, dwWant;
|
|
dwScroll = (fnBar == SB_VERT) ? WS_VSCROLL : WS_HSCROLL;
|
|
//
|
|
// We can short-circuit some logic with secret knowledge about how
|
|
// ListView uses SetScrollInfo.
|
|
//
|
|
ASSERT(lpsi->nMin == 0);
|
|
|
|
dwWant = fShow ? dwScroll : 0;
|
|
dwStyle = ListView_GetWindowStyle(plv);
|
|
if ((dwStyle & dwScroll) != dwWant)
|
|
{
|
|
TraceMsg(TF_LISTVIEW, "ListView_SetScrollInfo: App twiddled WS_[VH]SCROLL");
|
|
}
|
|
}
|
|
#endif
|
|
|
|
if (plv->exStyle & LVS_EX_FLATSB)
|
|
FlatSB_ShowScrollBar(plv->ci.hwnd, fnBar, fShow);
|
|
else
|
|
ShowScrollBar(plv->ci.hwnd, fnBar, fShow);
|
|
}
|
|
|
|
return iRc;
|
|
}
|
|
|
|
// Add/remove/replace item
|
|
|
|
BOOL ListView_FreeItem(LV* plv, LISTITEM* pitem)
|
|
{
|
|
ASSERT(!ListView_IsOwnerData(plv));
|
|
|
|
if (pitem)
|
|
{
|
|
if ((pitem->puColumns) && (pitem->cColumns != I_COLUMNSCALLBACK))
|
|
LocalFree(pitem->puColumns);
|
|
|
|
Str_Set(&pitem->pszText, NULL);
|
|
if (pitem->hrgnIcon && pitem->hrgnIcon!=(HANDLE)-1)
|
|
DeleteObject(pitem->hrgnIcon);
|
|
// NOTE: We never remove items from the image list; that's
|
|
// the app's responsibility.
|
|
// REVIEW: Should we do this? Or should we just provide
|
|
// a message that will adjust image indices for the guy
|
|
// when one is removed?
|
|
//
|
|
ControlFree(plv->hheap, pitem);
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
LISTITEM* ListView_CreateItem(LV* plv, const LV_ITEM* plvi)
|
|
{
|
|
LISTITEM* pitem = ControlAlloc(plv->hheap, sizeof(LISTITEM));
|
|
|
|
ASSERT(!ListView_IsOwnerData(plv));
|
|
|
|
if (pitem)
|
|
{
|
|
if (plvi->mask & LVIF_STATE)
|
|
{
|
|
if (plvi->state & ~LVIS_ALL)
|
|
{
|
|
DebugMsg(DM_ERROR, TEXT("ListView: Invalid state: %04x"), plvi->state);
|
|
return NULL;
|
|
}
|
|
|
|
// If adding a selected item to a single-select listview, deselect
|
|
// any other items.
|
|
if ((plv->ci.style & LVS_SINGLESEL) && (plvi->state & LVIS_SELECTED))
|
|
ListView_DeselectAll(plv, -1);
|
|
|
|
pitem->state = (plvi->state & ~(LVIS_FOCUSED | LVIS_SELECTED));
|
|
}
|
|
|
|
if (plvi->mask & LVIF_PARAM)
|
|
pitem->lParam = plvi->lParam;
|
|
|
|
if (plvi->mask & LVIF_IMAGE)
|
|
pitem->iImage = (short) plvi->iImage;
|
|
|
|
if (plvi->mask & LVIF_INDENT)
|
|
pitem->iIndent = (short) plvi->iIndent;
|
|
|
|
pitem->pt.x = pitem->pt.y = RECOMPUTE;
|
|
ListView_SetSRecompute(pitem);
|
|
|
|
pitem->pszText = NULL;
|
|
if (plvi->mask & LVIF_TEXT)
|
|
{
|
|
if (!Str_Set(&pitem->pszText, plvi->pszText))
|
|
{
|
|
ListView_FreeItem(plv, pitem);
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
if ((plvi->mask & LVIF_COLUMNS) && plvi->cColumns)
|
|
{
|
|
pitem->cColumns = plvi->cColumns;
|
|
if (plvi->cColumns != I_COLUMNSCALLBACK)
|
|
{
|
|
// Too many columns, or no column array? Then fail.
|
|
if ((plvi->cColumns > CCMAX_TILE_COLUMNS) || (plvi->puColumns == NULL))
|
|
{
|
|
ListView_FreeItem(plv, pitem);
|
|
return NULL;
|
|
}
|
|
|
|
pitem->puColumns = LocalAlloc(LPTR, sizeof(UINT) * pitem->cColumns);
|
|
if (pitem->puColumns == NULL)
|
|
{
|
|
ListView_FreeItem(plv, pitem);
|
|
return NULL;
|
|
}
|
|
|
|
CopyMemory(pitem->puColumns, plvi->puColumns, sizeof(UINT) * pitem->cColumns);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
pitem->cColumns = 0;
|
|
pitem->puColumns = NULL;
|
|
}
|
|
|
|
pitem->dwId = plv->idNext++; // This may overflow. How to deal?
|
|
}
|
|
|
|
return pitem;
|
|
}
|
|
|
|
// HACK ALERT!! -- fSmoothScroll is an added parameter! It allows for smooth
|
|
// scrolling when deleting items. ListView_LRInvalidateBelow is only currently
|
|
// called from ListView_OnUpdate and ListView_OnDeleteItem. Both these calls
|
|
// have been modified to work correctly and be backwards compatible.
|
|
//
|
|
void ListView_LRInvalidateBelow(LV* plv, int i, int fSmoothScroll)
|
|
{
|
|
if (ListView_IsListView(plv) || ListView_IsReportView(plv))
|
|
{
|
|
RECT rcItem;
|
|
|
|
if (!ListView_RedrawEnabled(plv) ||
|
|
(ListView_IsReportView(plv) && (plv->pImgCtx != NULL)))
|
|
fSmoothScroll = FALSE;
|
|
|
|
if (i >= 0)// && i < ListView_Count(plv))
|
|
{
|
|
ListView_GetRects(plv, i, QUERY_DEFAULT, NULL, NULL, &rcItem, NULL);
|
|
}
|
|
else
|
|
{
|
|
rcItem.left = rcItem.top = 0;
|
|
rcItem.right = plv->sizeClient.cx;
|
|
rcItem.bottom = plv->sizeClient.cy;
|
|
}
|
|
|
|
// Don't try to scroll over the header part
|
|
if (ListView_IsReportView(plv) && rcItem.top < plv->yTop)
|
|
rcItem.top = plv->yTop;
|
|
|
|
// For both List and report view need to erase the item and
|
|
// below. Note: do simple test to see if there is anything
|
|
// to redraw
|
|
|
|
// we can't check for bottom/right > 0 because if we nuked something
|
|
// above or to the left of the view, it may affect us all
|
|
if ((rcItem.top <= plv->sizeClient.cy) &&
|
|
(rcItem.left <= plv->sizeClient.cx))
|
|
{
|
|
rcItem.bottom = plv->sizeClient.cy;
|
|
|
|
if (ListView_RedrawEnabled(plv))
|
|
{
|
|
if ((plv->clrBk == CLR_NONE) && (plv->pImgCtx == NULL))
|
|
{
|
|
LVSeeThruScroll(plv, &rcItem);
|
|
}
|
|
else if (ListView_IsReportView(plv) && fSmoothScroll)
|
|
{
|
|
SMOOTHSCROLLINFO si =
|
|
{
|
|
sizeof(si),
|
|
SSIF_MINSCROLL,
|
|
plv->ci.hwnd,
|
|
0,
|
|
-(plv->cyItem),
|
|
&rcItem,
|
|
&rcItem,
|
|
NULL,
|
|
NULL,
|
|
SW_INVALIDATE|SW_ERASE,
|
|
SSI_DEFAULT,
|
|
1,
|
|
1,
|
|
};
|
|
SmoothScrollWindow(&si);
|
|
}
|
|
else
|
|
{
|
|
RedrawWindow(plv->ci.hwnd, &rcItem, NULL, RDW_INVALIDATE | RDW_ERASE);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
RedrawWindow(plv->ci.hwnd, &rcItem, NULL, RDW_INVALIDATE | RDW_ERASE);
|
|
}
|
|
|
|
if (ListView_IsListView(plv))
|
|
{
|
|
RECT rcClient;
|
|
// For Listview we need to erase the other columns...
|
|
rcClient.left = rcItem.right;
|
|
rcClient.top = 0;
|
|
rcClient.bottom = plv->sizeClient.cy;
|
|
rcClient.right = plv->sizeClient.cx;
|
|
RedrawWindow(plv->ci.hwnd, &rcClient, NULL, RDW_INVALIDATE | RDW_ERASE);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Used in Ownerdata Icon views to try to not invalidate the whole world...
|
|
void ListView_IInvalidateBelow(LV* plv, int i)
|
|
{
|
|
RECT rcItem;
|
|
|
|
if (i >= 0)
|
|
{
|
|
ListView_GetRects(plv, i, QUERY_DEFAULT, NULL, NULL, &rcItem, NULL);
|
|
}
|
|
else
|
|
{
|
|
rcItem.left = rcItem.top = 0;
|
|
rcItem.right = plv->sizeClient.cx;
|
|
rcItem.bottom = plv->sizeClient.cy;
|
|
}
|
|
|
|
// For Iconviews we need to invalidate everything to the right of us in this
|
|
// row and everything below the row...
|
|
// below. Note: do simple test to see if there is anything
|
|
// to redraw
|
|
|
|
if ((rcItem.top <= plv->sizeClient.cy) &&
|
|
(rcItem.left <= plv->sizeClient.cx))
|
|
{
|
|
rcItem.right = plv->sizeClient.cx;
|
|
RedrawWindow(plv->ci.hwnd, &rcItem, NULL, RDW_INVALIDATE | RDW_ERASE);
|
|
|
|
// Now erase everything below...
|
|
rcItem.top = rcItem.bottom;
|
|
rcItem.bottom = plv->sizeClient.cy;
|
|
rcItem.left = 0;
|
|
RedrawWindow(plv->ci.hwnd, &rcItem, NULL, RDW_INVALIDATE | RDW_ERASE);
|
|
}
|
|
}
|
|
|
|
|
|
void ListView_OnUpdate(LV* plv, int i)
|
|
{
|
|
// If in icon/small view, don't call InvalidateItem, since that'll force
|
|
// FindFreeSlot to get called, which is pig-like. Instead, just
|
|
// force a WM_PAINT message, which we'll catch and call Recompute with.
|
|
//
|
|
if (ListView_IsAutoArrangeView(plv))
|
|
{
|
|
ListView_ArrangeOrSnapToGrid(plv);
|
|
if (!(plv->ci.style & LVS_AUTOARRANGE))
|
|
RedrawWindow(plv->ci.hwnd, NULL, NULL, RDW_INTERNALPAINT | RDW_NOCHILDREN);
|
|
}
|
|
else
|
|
{
|
|
// HACK ALERT!! -- The third parameter is new. It allows for
|
|
// smooth scrolling when items are deleted in reportview.
|
|
// Passing 0, tells it NOT to scroll.
|
|
//
|
|
ListView_LRInvalidateBelow(plv, i, 0);
|
|
}
|
|
ListView_UpdateScrollBars(plv);
|
|
}
|
|
|
|
int ListView_OnInsertItemA(LV* plv, LV_ITEMA* plvi)
|
|
{
|
|
LPWSTR pszW = NULL;
|
|
LPSTR pszC = NULL;
|
|
int iRet;
|
|
|
|
//HACK ALERT -- this code assumes that LV_ITEMA is exactly the same
|
|
// as LV_ITEMW except for the pointer to the string.
|
|
COMPILETIME_ASSERT(sizeof(LV_ITEMA) == sizeof(LV_ITEMW));
|
|
|
|
if (!plvi)
|
|
{
|
|
return -1;
|
|
}
|
|
|
|
if ((plvi->mask & LVIF_TEXT) && (plvi->pszText != NULL))
|
|
{
|
|
pszC = plvi->pszText;
|
|
pszW = ProduceWFromA(plv->ci.uiCodePage, pszC);
|
|
if (pszW == NULL)
|
|
return -1;
|
|
plvi->pszText = (LPSTR)pszW;
|
|
}
|
|
|
|
iRet = ListView_OnInsertItem(plv, (const LV_ITEM*) plvi);
|
|
|
|
if (pszW != NULL)
|
|
{
|
|
plvi->pszText = pszC;
|
|
FreeProducedString(pszW);
|
|
}
|
|
|
|
return iRet;
|
|
|
|
}
|
|
|
|
int ListView_OnInsertItem(LV* plv, const LV_ITEM* plvi)
|
|
{
|
|
int i;
|
|
ListView_InsertItemInternal(plv, plvi, &i);
|
|
return i;
|
|
}
|
|
|
|
LISTITEM* ListView_InsertItemInternal(LV* plv, const LV_ITEM* plvi, int* pi)
|
|
{
|
|
int iItem;
|
|
LISTITEM *pitem = NULL;
|
|
|
|
*pi = -1;
|
|
if (plvi == NULL)
|
|
{
|
|
RIPMSG(0, "ListView_InsertItem: Do not pass a NULL LV_ITEM.");
|
|
return NULL;
|
|
}
|
|
|
|
if (plvi->iSubItem != 0) // can only insert the 0th item
|
|
{
|
|
RIPMSG(0, "ListView_InsertItem: iSubItem must be 0 (app passed %d)", plvi->iSubItem);
|
|
return NULL;
|
|
}
|
|
|
|
// If sorted, then insert sorted.
|
|
//
|
|
if (plv->ci.style & (LVS_SORTASCENDING | LVS_SORTDESCENDING)
|
|
&& !ListView_IsOwnerData(plv))
|
|
{
|
|
if (plvi->pszText == LPSTR_TEXTCALLBACK)
|
|
{
|
|
DebugMsg(DM_ERROR, TEXT("Don't use LPSTR_TEXTCALLBACK with LVS_SORTASCENDING or LVS_SORTDESCENDING"));
|
|
return NULL;
|
|
}
|
|
iItem = ListView_LookupString(plv, plvi->pszText, LVFI_SUBSTRING | LVFI_NEARESTXY, 0);
|
|
}
|
|
else
|
|
iItem = plvi->iItem;
|
|
|
|
ListView_InvalidateTTLastHit(plv, plv->iTTLastHit);
|
|
|
|
if (!ListView_IsOwnerData(plv))
|
|
{
|
|
int iZ;
|
|
static s_blah = 0;
|
|
UINT uSelMask = plvi->mask & LVIF_STATE ?
|
|
(plvi->state & (LVIS_FOCUSED | LVIS_SELECTED))
|
|
: 0;
|
|
UINT uSel = uSelMask;
|
|
pitem = ListView_CreateItem(plv, plvi);
|
|
|
|
if (!pitem)
|
|
return NULL;
|
|
|
|
iItem = DPA_InsertPtr(plv->hdpa, iItem, pitem);
|
|
if (iItem == -1)
|
|
{
|
|
ListView_FreeItem(plv, pitem);
|
|
return NULL;
|
|
}
|
|
|
|
plv->cTotalItems++;
|
|
|
|
if (plv->hdpaSubItems)
|
|
{
|
|
int iCol;
|
|
// slide all the colum DPAs down to match the location of the
|
|
// inserted item
|
|
//
|
|
for (iCol = plv->cCol - 1; iCol >= 0; iCol--)
|
|
{
|
|
HDPA hdpa = ListView_GetSubItemDPA(plv, iCol);
|
|
if (hdpa) // this is optional, call backs don't have them
|
|
{
|
|
// insert a blank item (REVIEW: should this be callback?)
|
|
|
|
// since this can be a tail sparce array,
|
|
// we need to make sure enough items are there.
|
|
if (iItem >= DPA_GetPtrCount(hdpa))
|
|
DPA_SetPtr(hdpa, iItem, NULL);
|
|
else if (DPA_InsertPtr(hdpa, iItem, NULL) != iItem)
|
|
goto Failure;
|
|
// Bad assert since hdpa can be tail sparse
|
|
// ASSERT(ListView_Count(plv) == DPA_GetPtrCount(hdpa));
|
|
ASSERT(ListView_Count(plv) >= DPA_GetPtrCount(hdpa));
|
|
}
|
|
}
|
|
}
|
|
|
|
// Add item to end of z order
|
|
//
|
|
iZ = DPA_InsertPtr(plv->hdpaZOrder, ListView_Count(plv), IntToPtr(iItem));
|
|
|
|
if (iZ == -1)
|
|
{
|
|
Failure:
|
|
DebugMsg(TF_LISTVIEW, TEXT("ListView_OnInsertItem() failed"));
|
|
if (DPA_DeletePtr(plv->hdpa, iItem))
|
|
plv->cTotalItems--;
|
|
ListView_FreeItem(plv, pitem);
|
|
return NULL;
|
|
}
|
|
|
|
// if we inserted before the focus point, move the focus point up one
|
|
if (iItem <= plv->iFocus)
|
|
plv->iFocus++;
|
|
// do the same thing for the mark
|
|
if (iItem <= plv->iMark)
|
|
plv->iMark++;
|
|
|
|
// If the item was not added at the end of the list we need
|
|
// to update the other indexes in the list
|
|
if (iItem != ListView_Count(plv) - 1)
|
|
{
|
|
int i2;
|
|
for (i2 = iZ - 1; i2 >= 0; i2--)
|
|
{
|
|
int iItemZ = (int)(UINT_PTR)DPA_FastGetPtr(plv->hdpaZOrder, i2);
|
|
if (iItemZ >= iItem)
|
|
DPA_SetPtr(plv->hdpaZOrder, i2, (void *)(UINT_PTR)(iItemZ + 1));
|
|
}
|
|
}
|
|
|
|
if (ListView_CheckBoxes(plv))
|
|
{
|
|
uSelMask |= LVIS_STATEIMAGEMASK;
|
|
uSel |= INDEXTOSTATEIMAGEMASK(1);
|
|
}
|
|
|
|
if (uSelMask)
|
|
{
|
|
// we masked off these in the createitem above.
|
|
// because turning these on means more than setting the bits.
|
|
ListView_OnSetItemState(plv, iItem, uSel, uSelMask);
|
|
}
|
|
|
|
if (plvi->mask & LVIF_GROUPID)
|
|
{
|
|
int iGroupId = plvi->iGroupId;
|
|
if (iGroupId == I_GROUPIDNONE)
|
|
{
|
|
LISTITEM_SETASKEDFORGROUP(pitem);
|
|
}
|
|
else if (iGroupId != I_GROUPIDCALLBACK)
|
|
{
|
|
LISTGROUP* pgrp = ListView_FindGroupFromID(plv, iGroupId, NULL);
|
|
if (!pgrp)
|
|
{
|
|
ListView_FreeItem(plv, pitem);
|
|
return NULL;
|
|
}
|
|
|
|
pitem->pGroup = pgrp;
|
|
DPA_AppendPtr(pgrp->hdpa, pitem);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
LISTITEM_SETHASNOTASKEDFORGROUP(pitem);
|
|
}
|
|
|
|
|
|
if (plv->fGroupView && (plv->flags & LVF_REDRAW))
|
|
{
|
|
_ListView_RecomputeEx(plv, NULL, 0, FALSE);
|
|
InvalidateRect(plv->ci.hwnd, NULL, TRUE);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// simply adjust selection and count
|
|
//
|
|
if ((iItem >= 0) && (iItem <= MAX_LISTVIEWITEMS))
|
|
{
|
|
if (FAILED(plv->plvrangeSel->lpVtbl->InsertItem(plv->plvrangeSel, iItem)))
|
|
{
|
|
return NULL;
|
|
}
|
|
plv->cTotalItems++;
|
|
plv->rcView.left = RECOMPUTE;
|
|
ListView_Recompute(plv);
|
|
if (!ListView_IsReportView(plv) && !ListView_IsListView(plv))
|
|
{
|
|
// We need to erase the background so that we don't leave
|
|
// turds from wrapped labels in large icon mode. This could
|
|
// be optimized by only invalidating to the right of and
|
|
// below the inserted item.
|
|
InvalidateRect(plv->ci.hwnd, NULL, TRUE);
|
|
}
|
|
|
|
// if we inserted before the focus point, move the focus point up
|
|
if (iItem <= plv->iFocus)
|
|
plv->iFocus++;
|
|
// do the same thing for the mark
|
|
if (iItem <= plv->iMark)
|
|
plv->iMark++;
|
|
}
|
|
}
|
|
|
|
if (!ListView_IsOwnerData(plv))
|
|
{
|
|
ASSERT(ListView_Count(plv) == DPA_GetPtrCount(plv->hdpaZOrder));
|
|
}
|
|
|
|
if (ListView_RedrawEnabled(plv))
|
|
{
|
|
// Update region
|
|
ListView_RecalcRegion(plv, TRUE, TRUE);
|
|
|
|
// The Maybe resize colmns may resize things in which case the next call
|
|
// to Update is not needed.
|
|
if (!ListView_MaybeResizeListColumns(plv, iItem, iItem))
|
|
ListView_OnUpdate(plv, iItem);
|
|
|
|
// this trick makes inserting lots of items cheap
|
|
// even if redraw is enabled.... don't calc or position items
|
|
// until this postmessage comes around
|
|
if (!plv->uUnplaced)
|
|
{
|
|
PostMessage(plv->ci.hwnd, LVMI_PLACEITEMS, 0, 0);
|
|
}
|
|
plv->uUnplaced++;
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// Special case code to make using SetRedraw work reasonably well
|
|
// for adding items to a listview which is in a non layout mode...
|
|
//
|
|
if ((plv->iFirstChangedNoRedraw == -1) ||
|
|
(iItem < plv->iFirstChangedNoRedraw))
|
|
plv->iFirstChangedNoRedraw = iItem;
|
|
|
|
}
|
|
|
|
// Nuke insertmark... it may be invalid now that an item has been added.
|
|
{
|
|
LVINSERTMARK lvim = {0};
|
|
lvim.cbSize = sizeof(LVINSERTMARK);
|
|
lvim.iItem = -1;
|
|
ListView_OnSetInsertMark(plv, (LPLVINSERTMARK)&lvim);
|
|
}
|
|
|
|
ListView_Notify(plv, iItem, 0, LVN_INSERTITEM);
|
|
|
|
NotifyWinEvent(EVENT_OBJECT_CREATE, plv->ci.hwnd, OBJID_CLIENT, iItem+1);
|
|
|
|
*pi = iItem;
|
|
|
|
return pitem;
|
|
}
|
|
|
|
BOOL ListView_OnDeleteItem(LV* plv, int iItem)
|
|
{
|
|
int iCount = ListView_Count(plv);
|
|
|
|
if (!ListView_IsValidItemNumber(plv, iItem))
|
|
return FALSE; // out of range
|
|
|
|
NotifyWinEvent(EVENT_OBJECT_DESTROY, plv->ci.hwnd, OBJID_CLIENT, iItem+1);
|
|
|
|
ListView_DismissEdit(plv, TRUE); // cancel edits
|
|
|
|
ListView_OnSetItemState(plv, iItem, 0, LVIS_SELECTED);
|
|
|
|
if (plv->iFocus == iItem)
|
|
ListView_OnSetItemState(plv, (iItem == iCount - 1 ? iItem - 1 : iItem + 1), LVIS_FOCUSED, LVIS_FOCUSED);
|
|
|
|
ListView_InvalidateTTLastHit(plv, plv->iTTLastHit);
|
|
|
|
if (!ListView_IsOwnerData(plv))
|
|
{
|
|
LISTITEM* pitem = ListView_FastGetItemPtr(plv, iItem);
|
|
int iZ;
|
|
|
|
if ((plv->rcView.left != RECOMPUTE) && ListView_IsSlotView(plv))
|
|
{
|
|
if (LV_IsItemOnViewEdge(plv, pitem))
|
|
{
|
|
plv->rcView.left = RECOMPUTE;
|
|
}
|
|
}
|
|
|
|
ListView_RemoveItemFromItsGroup(plv, pitem);
|
|
|
|
// We don't need to invalidate the item in report view because we
|
|
// will be scrolling on top of it.
|
|
//
|
|
if (!ListView_IsReportView(plv))
|
|
ListView_InvalidateItem(plv, iItem, FALSE, RDW_INVALIDATE | RDW_ERASE);
|
|
|
|
// this notify must be done AFTER the Invalidate because some items need callbacks
|
|
// to calculate the rect, but the notify might free it out
|
|
ListView_Notify(plv, iItem, 0, LVN_DELETEITEM);
|
|
|
|
// During the notify, the app might've done something to the listview
|
|
// so revalidate the item number pointer so we don't fault
|
|
#ifdef DEBUG
|
|
// Validate internally because DPA_DeletePtr will ASSERT if you ask it
|
|
// to delete something that doesn't exist.
|
|
if (!ListView_IsValidItemNumber(plv, iItem))
|
|
pitem = NULL;
|
|
else
|
|
#endif
|
|
pitem = DPA_DeletePtr(plv->hdpa, iItem);
|
|
|
|
if (!pitem)
|
|
{
|
|
RIPMSG(0, "Something strange happened during LVN_DELETEITEM; abandoning LVM_DELETEITEM");
|
|
return FALSE;
|
|
}
|
|
|
|
plv->cTotalItems = DPA_GetPtrCount(plv->hdpa);
|
|
|
|
// remove from the z-order, this is a lisearch to find this!
|
|
|
|
DPA_DeletePtr(plv->hdpaZOrder, ListView_ZOrderIndex(plv, iItem));
|
|
|
|
//
|
|
// As the Z-order hdpa is a set of indexes we also need to decrement
|
|
// all indexes that exceed the one we are deleting.
|
|
//
|
|
for (iZ = ListView_Count(plv) - 1; iZ >= 0; iZ--)
|
|
{
|
|
int iItemZ = (int)(UINT_PTR)DPA_FastGetPtr(plv->hdpaZOrder, iZ);
|
|
if (iItemZ > iItem)
|
|
DPA_SetPtr(plv->hdpaZOrder, iZ, IntToPtr(iItemZ - 1));
|
|
}
|
|
|
|
// remove from sub item DPAs if necessary
|
|
|
|
if (plv->hdpaSubItems)
|
|
{
|
|
int iCol;
|
|
for (iCol = plv->cCol - 1; iCol >= 0; iCol--)
|
|
{
|
|
HDPA hdpa = ListView_GetSubItemDPA(plv, iCol);
|
|
if (hdpa)
|
|
{ // this is optional, call backs don't have them
|
|
PLISTSUBITEM plsi;
|
|
|
|
// These DPAs are tail sparse, so don't get upset if we
|
|
// try to delete something that's past the end of the list
|
|
#ifdef DEBUG
|
|
plsi = iItem < DPA_GetPtrCount(hdpa) ? DPA_DeletePtr(hdpa, iItem) : NULL;
|
|
#else
|
|
plsi = DPA_DeletePtr(hdpa, iItem);
|
|
#endif
|
|
ListView_FreeSubItem(plsi);
|
|
}
|
|
}
|
|
}
|
|
|
|
ListView_FreeItem(plv, pitem); // ... finaly the item pointer
|
|
|
|
if (plv->fGroupView && (plv->flags & LVF_REDRAW))
|
|
{
|
|
_ListView_RecomputeEx(plv, NULL, 0, TRUE);
|
|
InvalidateRect(plv->ci.hwnd, NULL, TRUE);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// simply notify and then fixup selection state and count
|
|
//
|
|
if ((iItem >= 0) && (iItem <= MAX_LISTVIEWITEMS))
|
|
{
|
|
ListView_Notify(plv, iItem, 0, LVN_DELETEITEM);
|
|
|
|
if (FAILED(plv->plvrangeSel->lpVtbl->RemoveItem(plv->plvrangeSel, iItem)))
|
|
{
|
|
SetLastError(ERROR_OUTOFMEMORY);
|
|
return FALSE;
|
|
}
|
|
plv->cTotalItems--;
|
|
plv->rcView.left = RECOMPUTE;
|
|
ListView_Recompute(plv);
|
|
|
|
if (!ListView_IsReportView(plv) && !ListView_IsListView(plv))
|
|
{
|
|
// We need to erase the background so that the last item gets
|
|
// erased in both icon modes and so that we don't leave turds
|
|
// from wrapped labels in large icon mode. This could be
|
|
// optimized by only invalidating to the right of and below
|
|
// the deleted item.
|
|
InvalidateRect(plv->ci.hwnd, NULL, TRUE);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
iCount = ListView_Count(plv); // regrab count incase someone updated item...
|
|
|
|
if (!ListView_IsOwnerData(plv))
|
|
{
|
|
ASSERT(ListView_Count(plv) == DPA_GetPtrCount(plv->hdpaZOrder));
|
|
}
|
|
|
|
if (plv->iFocus == iItem)
|
|
{
|
|
if (plv->iFocus >= iCount)
|
|
{
|
|
plv->iFocus = iCount - 1;
|
|
}
|
|
}
|
|
|
|
if (plv->iFocus > iItem)
|
|
{
|
|
plv->iFocus--; // slide the focus index down
|
|
}
|
|
|
|
// same with the mark
|
|
if (plv->iMark == iItem)
|
|
{
|
|
// deleted the mark item
|
|
|
|
if (plv->iMark >= iCount) // did we nuke the last item?
|
|
plv->iMark = iCount - 1;
|
|
}
|
|
else if (plv->iMark > iItem)
|
|
plv->iMark--; // slide the mark index down
|
|
|
|
// Free up the hot item
|
|
if (plv->iHot == iItem)
|
|
plv->iHot = -1;
|
|
|
|
// Deleting an icon invalidates the icon positioning cache
|
|
plv->iFreeSlot = -1;
|
|
|
|
// HACK ALERT!! -- This construct with ReportView steals code from
|
|
// ListView_OnUpdate. Currently, it will work exactly the same as before,
|
|
// EXCEPT, that it won't call ListView_OnUpdate. This is to allow us to
|
|
// send a flag to ListView_LRUpdateBelow to tell it we're scrolling up.
|
|
//
|
|
if (ListView_IsReportView(plv))
|
|
{
|
|
|
|
// if the new count is zero and we will be showing empty text, simply invalidate the
|
|
// rect and redraw, else go through the invalidate below code...
|
|
|
|
// we don't know if we are going to show empty text if pszEmptyText is NULL, or not
|
|
// because we may get one through notify, so if iCount is 0 invalidate everything
|
|
if (iCount == 0)
|
|
InvalidateRect(plv->ci.hwnd, NULL, TRUE);
|
|
else
|
|
ListView_LRInvalidateBelow(plv,iItem,1);
|
|
|
|
|
|
|
|
if (ListView_RedrawEnabled(plv))
|
|
ListView_UpdateScrollBars(plv);
|
|
else {
|
|
//
|
|
// Special case code to make using SetRedraw work reasonably well
|
|
// for adding items to a listview which is in a non layout mode...
|
|
//
|
|
if ((plv->iFirstChangedNoRedraw != -1) && (iItem < plv->iFirstChangedNoRedraw))
|
|
plv->iFirstChangedNoRedraw--;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (ListView_RedrawEnabled(plv))
|
|
ListView_OnUpdate(plv, iItem);
|
|
|
|
else
|
|
{
|
|
ListView_LRInvalidateBelow(plv, iItem, 0);
|
|
//
|
|
// Special case code to make using SetRedraw work reasonably well
|
|
// for adding items to a listview which is in a non layout mode...
|
|
//
|
|
if ((plv->iFirstChangedNoRedraw != -1) && (iItem < plv->iFirstChangedNoRedraw))
|
|
plv->iFirstChangedNoRedraw--;
|
|
}
|
|
}
|
|
ListView_RecalcRegion(plv, TRUE, TRUE);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
void ListView_DeleteAllGroupItems(LV* plv)
|
|
{
|
|
if (plv->hdpaGroups)
|
|
{
|
|
int iGroup, cGroups = DPA_GetPtrCount(plv->hdpaGroups);
|
|
for (iGroup = 0; iGroup < cGroups; iGroup++)
|
|
{
|
|
LISTGROUP* pgrp = DPA_FastGetPtr(plv->hdpaGroups, iGroup);
|
|
DPA_Destroy(pgrp->hdpa);
|
|
pgrp->hdpa = DPA_Create(5);
|
|
}
|
|
}
|
|
}
|
|
|
|
BOOL ListView_OnDeleteAllItems(LV* plv)
|
|
{
|
|
int i;
|
|
BOOL bAlreadyNotified;
|
|
BOOL fHasItemData = !ListView_IsOwnerData(plv);
|
|
|
|
ListView_DismissEdit(plv, TRUE); // cancel edits
|
|
ListView_DeleteAllGroupItems(plv);
|
|
|
|
// Must neutralize the focus because some apps will call
|
|
// ListView_OnGetNextItem(LVNI_FOCUSED) during delete notifications,
|
|
// so we need to make sure the focus is in a safe place.
|
|
// May as well neutralize the mark, too.
|
|
plv->iMark = plv->iFocus = -1;
|
|
|
|
// Also nuke the icon positioning cache
|
|
plv->iFreeSlot = -1;
|
|
|
|
// Since we delete all items, There is no insertion slot!
|
|
plv->iInsertItem = -1;
|
|
|
|
bAlreadyNotified = (BOOL)ListView_Notify(plv, -1, 0, LVN_DELETEALLITEMS);
|
|
|
|
ListView_InvalidateTTLastHit(plv, plv->iTTLastHit);
|
|
|
|
if (fHasItemData || !bAlreadyNotified)
|
|
{
|
|
for (i = ListView_Count(plv) - 1; i >= 0; i--)
|
|
{
|
|
if (!bAlreadyNotified)
|
|
ListView_Notify(plv, i, 0, LVN_DELETEITEM);
|
|
|
|
if (fHasItemData)
|
|
{
|
|
ListView_FreeItem(plv, ListView_FastGetItemPtr(plv, i));
|
|
//
|
|
// CAREFUL! Applications such as NT Backup call back
|
|
// into ListView during the LVN_DELETEITEM notification,
|
|
// so we need to kill this item or we will fault at the
|
|
// next iteration because everybody relies on
|
|
// ListView_Count for validation.
|
|
//
|
|
DPA_FastDeleteLastPtr(plv->hdpa);
|
|
plv->cTotalItems--;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (ListView_IsOwnerData(plv))
|
|
{
|
|
if (FAILED(plv->plvrangeSel->lpVtbl->Clear(plv->plvrangeSel)))
|
|
{
|
|
SetLastError(ERROR_OUTOFMEMORY);
|
|
}
|
|
plv->cTotalItems = 0;
|
|
}
|
|
else
|
|
{
|
|
DPA_DeleteAllPtrs(plv->hdpa);
|
|
DPA_DeleteAllPtrs(plv->hdpaZOrder);
|
|
plv->cTotalItems = 0;
|
|
|
|
if (plv->hdpaSubItems)
|
|
{
|
|
int iCol;
|
|
for (iCol = plv->cCol - 1; iCol >= 0; iCol--)
|
|
{
|
|
HDPA hdpa = ListView_GetSubItemDPA(plv, iCol);
|
|
if (hdpa)
|
|
{
|
|
DPA_EnumCallback(hdpa, ListView_FreeColumnData, 0);
|
|
DPA_DeleteAllPtrs(hdpa);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
plv->rcView.left = RECOMPUTE;
|
|
plv->xOrigin = 0;
|
|
plv->nSelected = 0;
|
|
|
|
plv->ptlRptOrigin.x = 0;
|
|
plv->ptlRptOrigin.y = 0;
|
|
|
|
// reset the cxItem width
|
|
if (!(plv->flags & LVF_COLSIZESET))
|
|
{
|
|
plv->cxItem = ListView_ComputeCXItemSize(plv);
|
|
}
|
|
|
|
RedrawWindow(plv->ci.hwnd, NULL, NULL, RDW_INVALIDATE | RDW_ERASE);
|
|
ListView_UpdateScrollBars(plv);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
int ListView_IFindNearestItem(LV* plv, int left, int top, UINT vk)
|
|
{
|
|
int iMin = -1;
|
|
|
|
if (ListView_IsOwnerData(plv))
|
|
{
|
|
POINT pt;
|
|
int cSlots;
|
|
int iWidth = 0, iHeight = 0;
|
|
|
|
ASSERT(!ListView_IsReportView(plv) && !ListView_IsListView(plv));
|
|
|
|
pt.x = left + plv->ptOrigin.x;
|
|
pt.y = top + plv->ptOrigin.y;
|
|
|
|
cSlots = ListView_GetSlotCount(plv, TRUE, &iWidth, &iHeight);
|
|
iMin = ListView_CalcHitSlot(plv, pt, cSlots, iWidth, iHeight);
|
|
|
|
switch(vk)
|
|
{
|
|
case VK_HOME:
|
|
iMin = 0;
|
|
break;
|
|
|
|
case VK_END:
|
|
iMin = ListView_Count(plv) - 1;
|
|
break;
|
|
|
|
case VK_LEFT:
|
|
if (iMin % cSlots)
|
|
iMin -= 1;
|
|
break;
|
|
|
|
case VK_RIGHT:
|
|
if ((iMin + 1) % cSlots)
|
|
iMin += 1;
|
|
break;
|
|
|
|
case VK_UP:
|
|
if (iMin >= cSlots)
|
|
iMin -= cSlots;
|
|
break;
|
|
|
|
case VK_DOWN:
|
|
if (iMin + cSlots < ListView_Count(plv))
|
|
iMin += cSlots;
|
|
break;
|
|
|
|
default: ;
|
|
}
|
|
|
|
iMin = max(0, iMin);
|
|
iMin = min(ListView_Count(plv) - 1, iMin);
|
|
|
|
}
|
|
else
|
|
{
|
|
ULONGLONG dMin = 0;
|
|
int cyItem;
|
|
int yEnd = 0, yLimit = 0, xEnd = 0;
|
|
int iCount;
|
|
int i;
|
|
|
|
if (ListView_UseLargeIcons(plv))
|
|
{
|
|
cyItem = plv->cyIcon;
|
|
}
|
|
else
|
|
{
|
|
cyItem = plv->cyItem;
|
|
}
|
|
|
|
iCount = ListView_Count(plv);
|
|
if (iCount == 1)
|
|
return 0;
|
|
|
|
if (vk == VK_HOME)
|
|
{
|
|
yEnd = yLimit = plv->rcView.bottom;
|
|
xEnd = plv->rcView.right;
|
|
}
|
|
else if (vk == VK_END)
|
|
{
|
|
yEnd = yLimit = plv->rcView.top;
|
|
xEnd = plv->rcView.left;
|
|
}
|
|
for (i = 0; i < iCount; i++)
|
|
{
|
|
RECT rc;
|
|
int dx;
|
|
ULONGLONG dxAbs, dyAbs, dOffset;
|
|
int dy;
|
|
|
|
ListView_GetRects(plv, i, QUERY_DEFAULT, &rc, NULL, NULL, NULL);
|
|
|
|
dx = rc.left - left;
|
|
dxAbs = (ULONGLONG)(dx < 0 ? -dx : dx);
|
|
dy = rc.top - top;
|
|
dyAbs = (ULONGLONG)(dy < 0 ? -dy : dy);
|
|
|
|
if ((vk == VK_LEFT) && (dxAbs < dyAbs || dx >= 0))
|
|
continue;
|
|
else if ((vk == VK_RIGHT) && (dxAbs < dyAbs || dx <= 0))
|
|
continue;
|
|
else if ((vk == VK_UP) && (dxAbs > dyAbs || dy >= 0))
|
|
continue;
|
|
else if ((vk == VK_DOWN) && (dxAbs > dyAbs || dy <= 0))
|
|
continue;
|
|
|
|
if (vk == VK_HOME || vk == VK_END)
|
|
{
|
|
// home is not the nearest to the top corner, it's the leftmost of the top row.
|
|
// ditto (reversed) for end. thus we can't use the stuff below. bummer
|
|
if (vk == VK_HOME)
|
|
{
|
|
if ((rc.top + cyItem < yEnd) || // if it's fully above the highest line so, take it!
|
|
((rc.top < yLimit) && // if it's on the same row as the top item to date
|
|
(rc.left < xEnd)))
|
|
{
|
|
iMin = i;
|
|
xEnd = rc.left;
|
|
yEnd = rc.top;
|
|
if (rc.top + cyItem < yLimit)
|
|
yLimit = rc.top + cyItem;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if ((rc.top > yEnd) || //if it's full below the lowest row
|
|
((rc.top + cyItem > yLimit) && // if it's on the same row
|
|
(rc.right > xEnd)))
|
|
{
|
|
iMin = i;
|
|
xEnd = rc.right;
|
|
yEnd = rc.top;
|
|
if (rc.top > yLimit)
|
|
yLimit = rc.top;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
dOffset = ((dxAbs * dxAbs) + (dyAbs * dyAbs));
|
|
if (iMin == -1 || (dMin > dOffset))
|
|
{
|
|
dMin = dOffset;
|
|
iMin = i;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return iMin;
|
|
}
|
|
|
|
int ListView_Arrow(LV* plv, int iStart, UINT vk)
|
|
{
|
|
RECT rcFocus;
|
|
int i;
|
|
int dx;
|
|
int iCount;
|
|
|
|
//
|
|
// The algorithm to find which item depends if we are in a view
|
|
// that is arrange(layout) oriented or a sorted (list) view.
|
|
// For the sorted views we will use some optimizations to make
|
|
// it faster
|
|
//
|
|
iCount = ListView_Count(plv);
|
|
if ((ListView_IsReportView(plv) || ListView_IsListView(plv)) && !plv->fGroupView)
|
|
{
|
|
//
|
|
// For up and down arrows, simply increment or decrement the
|
|
// index. Note: in listview this will cause it to wrap columns
|
|
// which is fine as it is compatible with the file manager
|
|
//
|
|
// Assumes only one of these flags is set...
|
|
|
|
switch (vk)
|
|
{
|
|
case VK_LEFT:
|
|
if (ListView_IsReportView(plv))
|
|
{
|
|
ListView_ROnScroll(plv, (GetAsyncKeyState(VK_CONTROL) < 0) ? SB_PAGELEFT : SB_LINELEFT, 0, SB_HORZ);
|
|
}
|
|
else
|
|
iStart -= plv->cItemCol;
|
|
break;
|
|
|
|
case VK_RIGHT:
|
|
if (ListView_IsReportView(plv))
|
|
{
|
|
// Make this horizontally scroll the report view
|
|
ListView_ROnScroll(plv, (GetAsyncKeyState(VK_CONTROL) < 0) ? SB_PAGERIGHT : SB_LINERIGHT, 0, SB_HORZ);
|
|
}
|
|
else
|
|
iStart += plv->cItemCol;
|
|
break;
|
|
|
|
case VK_UP:
|
|
iStart--;
|
|
break;
|
|
|
|
case VK_DOWN:
|
|
iStart++;
|
|
break;
|
|
|
|
case VK_HOME:
|
|
iStart = 0;
|
|
break;
|
|
|
|
case VK_END:
|
|
iStart = iCount -1;
|
|
break;
|
|
|
|
case VK_NEXT:
|
|
if (ListView_IsReportView(plv))
|
|
{
|
|
i = iStart; // save away to make sure we dont go wrong way!
|
|
|
|
// First go to end of page...
|
|
iStart = (int)(((LONG)(plv->sizeClient.cy - (plv->cyItem)
|
|
- plv->yTop) + plv->ptlRptOrigin.y) / plv->cyItem);
|
|
|
|
// If Same item, increment by page size.
|
|
if (iStart <= i)
|
|
iStart = i + max(
|
|
(plv->sizeClient.cy - plv->yTop)/ plv->cyItem - 1,
|
|
1);
|
|
|
|
if (iStart >= iCount)
|
|
iStart = iCount - 1;
|
|
}
|
|
else
|
|
{
|
|
// multiply by 2/3 to give a good feel.. when the item is mostly shown
|
|
// you want to go to the next column
|
|
dx = (plv->sizeClient.cx + (plv->cxItem*2)/3) / plv->cxItem;
|
|
if (!dx)
|
|
dx = 1;
|
|
|
|
iStart += plv->cItemCol * dx;
|
|
if (plv->cItemCol)
|
|
{
|
|
while (iStart >= iCount)
|
|
iStart -= plv->cItemCol;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case VK_PRIOR:
|
|
|
|
if (ListView_IsReportView(plv))
|
|
{
|
|
i = iStart; // save away to make sure we dont go wrong way!
|
|
|
|
// First go to end of page...
|
|
iStart = (int)(plv->ptlRptOrigin.y / plv->cyItem);
|
|
|
|
// If Same item, increment by page size.
|
|
if (iStart >= i)
|
|
iStart = i - max(
|
|
(plv->sizeClient.cy - plv->yTop)/ plv->cyItem - 1,
|
|
1);
|
|
|
|
if (iStart < 0)
|
|
iStart = 0;
|
|
}
|
|
else
|
|
{
|
|
dx = (plv->sizeClient.cx + (plv->cxItem*2)/3) / plv->cxItem;
|
|
if (!dx)
|
|
dx = 1;
|
|
iStart -= plv->cItemCol * dx;
|
|
if (plv->cItemCol)
|
|
{
|
|
while (iStart < 0)
|
|
iStart += plv->cItemCol;
|
|
}
|
|
|
|
}
|
|
break;
|
|
|
|
default:
|
|
return -1; // Out of range
|
|
}
|
|
|
|
// Make sure it is in range!.
|
|
if ((iStart >= 0) && (iStart < iCount))
|
|
return iStart;
|
|
else if (iCount == 1)
|
|
return 0;
|
|
else
|
|
return -1;
|
|
}
|
|
|
|
else
|
|
{
|
|
//
|
|
// Layout type view. we need to use the position of the items
|
|
// to figure out the next item
|
|
//
|
|
|
|
if (ListView_IsOwnerData(plv))
|
|
{
|
|
iStart = max(0, iStart);
|
|
|
|
// if it does not matches any of the entries in the case statement below
|
|
// this is done to skip the call back by the GetRects
|
|
//
|
|
if (vk != VK_LEFT &&
|
|
vk != VK_RIGHT &&
|
|
vk != VK_UP &&
|
|
vk != VK_DOWN &&
|
|
vk != VK_HOME &&
|
|
vk != VK_END &&
|
|
vk != VK_NEXT &&
|
|
vk != VK_PRIOR)
|
|
{
|
|
return -1;
|
|
}
|
|
ListView_GetRects(plv, iStart, QUERY_DEFAULT, &rcFocus, NULL, NULL, NULL);
|
|
}
|
|
else
|
|
{
|
|
if (iStart != -1)
|
|
{
|
|
ListView_GetRects(plv, iStart, QUERY_DEFAULT, &rcFocus, NULL, NULL, NULL);
|
|
}
|
|
}
|
|
|
|
switch (vk)
|
|
{
|
|
// For standard arrow keys just fall out of here.
|
|
case VK_LEFT:
|
|
case VK_RIGHT:
|
|
case VK_UP:
|
|
case VK_DOWN:
|
|
if (ListView_IsOwnerData(plv))
|
|
{
|
|
break;
|
|
}
|
|
else
|
|
{
|
|
if (iStart != -1)
|
|
{
|
|
// all keys map to VK_HOME except VK_END
|
|
break;
|
|
}
|
|
|
|
// Fall through
|
|
vk = VK_HOME;
|
|
}
|
|
|
|
case VK_HOME:
|
|
rcFocus.left = - plv->ptOrigin.x;
|
|
rcFocus.top = - plv->ptOrigin.y;
|
|
break;
|
|
|
|
case VK_END:
|
|
rcFocus.left = plv->rcView.right;
|
|
rcFocus.top = plv->rcView.bottom;
|
|
break;
|
|
|
|
case VK_NEXT:
|
|
rcFocus.top += plv->sizeClient.cy;
|
|
vk = VK_UP;
|
|
break;
|
|
|
|
case VK_PRIOR:
|
|
vk = VK_DOWN;
|
|
rcFocus.top -= plv->sizeClient.cy;
|
|
break;
|
|
default:
|
|
return -1; // Out of range
|
|
}
|
|
|
|
return ListView_IFindNearestItem(plv, rcFocus.left, rcFocus.top, vk);
|
|
}
|
|
}
|
|
|
|
int ListView_OnGetNextItem(LV* plv, int i, UINT flags)
|
|
{
|
|
int iStart = i;
|
|
int cItemMax = ListView_Count(plv);
|
|
|
|
// Note that -1 is a valid starting point
|
|
if (i < -1 || i >= cItemMax)
|
|
return -1;
|
|
|
|
if (ListView_IsOwnerData(plv))
|
|
{
|
|
if (flags & (LVNI_CUT | LVNI_DROPHILITED | LVNI_PREVIOUS))
|
|
{
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
if (flags & LVNI_FOCUSED)
|
|
{
|
|
// we know which item is focused, jump right to it.
|
|
// but we have to mimick the code below exactly for compat:
|
|
// if directional bits are set, they take precedence.
|
|
if (!(flags & (LVNI_ABOVE | LVNI_BELOW | LVNI_TORIGHT | LVNI_TOLEFT)))
|
|
{
|
|
// there are no more focused items after iFocus
|
|
if (i >= plv->iFocus)
|
|
return -1;
|
|
|
|
// subtract one here -- we increment it below
|
|
i = plv->iFocus - 1;
|
|
}
|
|
}
|
|
|
|
while (TRUE)
|
|
{
|
|
if (flags & (LVNI_ABOVE | LVNI_BELOW | LVNI_TORIGHT | LVNI_TOLEFT))
|
|
{
|
|
UINT vk;
|
|
if (flags & LVNI_ABOVE)
|
|
vk = VK_UP;
|
|
else if (flags & LVNI_BELOW)
|
|
vk = VK_DOWN;
|
|
else if (flags & LVNI_TORIGHT)
|
|
vk = VK_RIGHT;
|
|
else
|
|
vk = VK_LEFT;
|
|
|
|
if (i != -1)
|
|
i = ListView_Arrow(plv, i, vk);
|
|
if (i == -1)
|
|
return i;
|
|
|
|
}
|
|
else
|
|
{
|
|
i++;
|
|
if (i == cItemMax)
|
|
return -1;
|
|
}
|
|
|
|
// See if any other restrictions are set
|
|
if (flags & ~(LVNI_ABOVE | LVNI_BELOW | LVNI_TORIGHT | LVNI_TOLEFT))
|
|
{
|
|
WORD wItemState;
|
|
|
|
if (ListView_IsOwnerData(plv))
|
|
{
|
|
if (flags & LVNI_FOCUSED)
|
|
{
|
|
// we check LVNI_FOCUSED before the loop, so i == iFocus
|
|
ASSERT(i == plv->iFocus && i != -1);
|
|
if (flags & LVNI_SELECTED)
|
|
{
|
|
if (plv->plvrangeSel->lpVtbl->IsSelected(plv->plvrangeSel, i) != S_OK)
|
|
{
|
|
i = -1;
|
|
}
|
|
}
|
|
}
|
|
else if (flags & LVNI_SELECTED)
|
|
{
|
|
i = max(i, 0);
|
|
plv->plvrangeSel->lpVtbl->NextSelected(plv->plvrangeSel, i, &i);
|
|
}
|
|
else
|
|
{
|
|
i = -1;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
{
|
|
LISTITEM* pitem = ListView_FastGetItemPtr(plv, i);
|
|
wItemState = pitem->state;
|
|
}
|
|
|
|
// for LVNI_FOCUSED, we start at the LVIS_FOCUSED element, if we're
|
|
// not on that element, one of the below continues was hit, so
|
|
// we'll never find the element. bail out early.
|
|
if ((flags & LVNI_FOCUSED) && !(wItemState & LVIS_FOCUSED))
|
|
{
|
|
ASSERT(i == plv->iFocus || i == plv->iFocus+1);
|
|
return -1;
|
|
}
|
|
|
|
if (((flags & LVNI_SELECTED) && !(wItemState & LVIS_SELECTED)) ||
|
|
((flags & LVNI_CUT) && !(wItemState & LVIS_CUT)) ||
|
|
((flags & LVNI_DROPHILITED) && !(wItemState & LVIS_DROPHILITED)))
|
|
{
|
|
if (i != iStart)
|
|
continue;
|
|
else
|
|
{
|
|
// we've looped and we can't find anything to fit this criteria
|
|
return -1;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return i;
|
|
}
|
|
}
|
|
|
|
int ListView_CompareString(LV* plv, int i, LPCTSTR pszFind, UINT flags, int iLen)
|
|
{
|
|
// REARCHITECT: non protected globals
|
|
int cb;
|
|
TCHAR ach[CCHLABELMAX];
|
|
LV_ITEM item;
|
|
|
|
ASSERT(!ListView_IsOwnerData(plv));
|
|
ASSERT(pszFind);
|
|
|
|
item.iItem = i;
|
|
item.iSubItem = 0;
|
|
item.mask = LVIF_TEXT;
|
|
item.pszText = ach;
|
|
item.cchTextMax = ARRAYSIZE(ach);
|
|
ListView_OnGetItem(plv, &item);
|
|
|
|
if (!(flags & (LVFI_PARTIAL | LVFI_SUBSTRING)))
|
|
return lstrcmpi(item.pszText, pszFind);
|
|
|
|
// FEATURE: LVFI_SUBSTRING is not really implemented yet.
|
|
|
|
cb = lstrlen(pszFind);
|
|
if (iLen && (cb > iLen))
|
|
{
|
|
cb = iLen;
|
|
}
|
|
|
|
//
|
|
// If the sub strings not equal then return the ordering based
|
|
// on the entire string.
|
|
//
|
|
return IntlStrEqNI(item.pszText, pszFind, cb) ? 0 : lstrcmp(item.pszText, pszFind);
|
|
}
|
|
|
|
int ListView_OnFindItemA(LV* plv, int iStart, LV_FINDINFOA * plvfi)
|
|
{
|
|
LPWSTR pszW = NULL;
|
|
LPCSTR pszC = NULL;
|
|
int iRet;
|
|
|
|
//HACK ALERT -- this code assumes that LV_FINDINFOA is exactly the same
|
|
// as LV_FINDINFOW except for the pointer to the string.
|
|
COMPILETIME_ASSERT(sizeof(LV_FINDINFOA) == sizeof(LV_FINDINFOW));
|
|
|
|
if (!plvfi)
|
|
return -1;
|
|
|
|
if (!(plvfi->flags & LVFI_PARAM) && !(plvfi->flags & LVFI_NEARESTXY))
|
|
{
|
|
pszC = plvfi->psz;
|
|
if ((pszW = ProduceWFromA(plv->ci.uiCodePage, pszC)) == NULL)
|
|
return -1;
|
|
plvfi->psz = (LPSTR)pszW;
|
|
}
|
|
|
|
iRet = ListView_OnFindItem(plv, iStart, (const LV_FINDINFO *)plvfi);
|
|
|
|
if (pszW != NULL)
|
|
{
|
|
plvfi->psz = pszC;
|
|
|
|
FreeProducedString(pszW);
|
|
}
|
|
|
|
return iRet;
|
|
}
|
|
|
|
int ListView_OnFindItem(LV* plv, int iStart, const LV_FINDINFO* plvfi)
|
|
{
|
|
int i;
|
|
int j;
|
|
int cItem;
|
|
UINT flags;
|
|
|
|
if (!plvfi)
|
|
return -1;
|
|
|
|
if (plvfi->flags & LVFI_NEARESTXY)
|
|
{
|
|
if (ListView_IsSlotView(plv))
|
|
{
|
|
return ListView_IFindNearestItem(plv, plvfi->pt.x, plvfi->pt.y, plvfi->vkDirection);
|
|
}
|
|
else
|
|
return -1;
|
|
}
|
|
|
|
// Note that -1 is a valid starting point
|
|
if (iStart < -1 || iStart >= ListView_Count(plv))
|
|
return -1;
|
|
|
|
if (ListView_IsOwnerData(plv))
|
|
{
|
|
// call back to owner for search
|
|
return (int) ListView_RequestFindItem(plv, plvfi, iStart + 1);
|
|
}
|
|
else
|
|
{
|
|
flags = plvfi->flags;
|
|
i = iStart;
|
|
cItem = ListView_Count(plv);
|
|
if (flags & LVFI_PARAM)
|
|
{
|
|
LPARAM lParam = plvfi->lParam;
|
|
|
|
// Lisearch with wraparound...
|
|
//
|
|
for (j = cItem; j-- != 0;)
|
|
{
|
|
++i;
|
|
if (i == cItem)
|
|
{
|
|
if (flags & LVFI_WRAP)
|
|
i = 0;
|
|
else
|
|
break;
|
|
}
|
|
|
|
if (ListView_FastGetItemPtr(plv, i)->lParam == lParam)
|
|
return i;
|
|
}
|
|
}
|
|
else // if (flags & (LVFI_STRING | LVFI_SUBSTRING | LVFI_PARTIAL))
|
|
{
|
|
LPCTSTR pszFind = plvfi->psz;
|
|
if (!pszFind)
|
|
return -1;
|
|
|
|
if (plv->ci.style & (LVS_SORTASCENDING | LVS_SORTDESCENDING))
|
|
return ListView_LookupString(plv, pszFind, flags, i + 1);
|
|
|
|
for (j = cItem; j-- != 0;)
|
|
{
|
|
++i;
|
|
if (i == cItem)
|
|
{
|
|
if (flags & LVFI_WRAP)
|
|
i = 0;
|
|
else
|
|
break;
|
|
}
|
|
|
|
if (ListView_CompareString(plv,
|
|
i,
|
|
pszFind,
|
|
(flags & (LVFI_PARTIAL | LVFI_SUBSTRING)), 0) == 0)
|
|
{
|
|
return i;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
BOOL ListView_OnGetItemRect(LV* plv, int i, RECT* prc)
|
|
{
|
|
LPRECT pRects[LVIR_MAX];
|
|
|
|
// validate parameters
|
|
if (!ListView_IsValidItemNumber(plv, i))
|
|
{
|
|
RIPMSG(0, "LVM_GETITEMRECT: invalid index %d", i);
|
|
return FALSE;
|
|
}
|
|
|
|
if (!prc || prc->left >= LVIR_MAX || prc->left < 0)
|
|
{
|
|
RIPMSG(0, "LVM_GETITEMRECT: invalid rect pointer");
|
|
return FALSE;
|
|
}
|
|
|
|
pRects[0] = NULL;
|
|
pRects[1] = NULL;
|
|
pRects[2] = NULL;
|
|
pRects[3] = NULL;
|
|
|
|
pRects[prc->left] = prc;
|
|
ListView_GetRects(plv, i, QUERY_DEFAULT, pRects[LVIR_ICON], pRects[LVIR_LABEL],
|
|
pRects[LVIR_BOUNDS], pRects[LVIR_SELECTBOUNDS]);
|
|
return TRUE;
|
|
}
|
|
|
|
//
|
|
// in:
|
|
// plv
|
|
// iItem MUST be a valid item index (in range)
|
|
// out:
|
|
// prcIcon icon bounding rect
|
|
// prcLabel label text bounding rect, for details this is the first column
|
|
// prcBounds entire item (all text and icon), including columns in details
|
|
// prcSelectionBounds union of icon and label rects, does NOT include columns
|
|
// in details view
|
|
|
|
// REARCHITECT raymondc - Need to pass an HDC parameter for measurement
|
|
// since sometimes we do this while painting
|
|
|
|
// This returns rects in Window Coordinates
|
|
void ListView_GetRects(LV* plv, int iItem, UINT fQueryLabelRects,
|
|
RECT* prcIcon, RECT* prcLabel, RECT* prcBounds,
|
|
RECT* prcSelectBounds)
|
|
{
|
|
ASSERT(plv);
|
|
|
|
if (ListView_IsReportView(plv))
|
|
{
|
|
ListView_RGetRects(plv, iItem, prcIcon, prcLabel, prcBounds, prcSelectBounds);
|
|
}
|
|
else if (ListView_IsListView(plv))
|
|
{
|
|
ListView_LGetRects(plv, iItem, prcIcon, prcLabel, prcBounds, prcSelectBounds);
|
|
}
|
|
else
|
|
{
|
|
if (ListView_IsOwnerData(plv))
|
|
{
|
|
RECT rcIcon;
|
|
RECT rcTextBounds;
|
|
LISTITEM item;
|
|
|
|
if (ListView_IsIconView(plv))
|
|
ListView_IGetRectsOwnerData(plv, iItem, &rcIcon, &rcTextBounds, &item, FALSE);
|
|
else if (ListView_IsSmallView(plv))
|
|
ListView_SGetRectsOwnerData(plv, iItem, &rcIcon, &rcTextBounds, &item, FALSE);
|
|
else if (ListView_IsTileView(plv))
|
|
ListView_TGetRectsOwnerData(plv, iItem, &rcIcon, &rcTextBounds, &item, FALSE);
|
|
|
|
if (prcIcon)
|
|
*prcIcon = rcIcon;
|
|
if (prcLabel)
|
|
*prcLabel = rcTextBounds;
|
|
|
|
if (prcBounds)
|
|
UnionRect(prcBounds, &rcIcon, &rcTextBounds);
|
|
|
|
if (prcSelectBounds)
|
|
UnionRect(prcSelectBounds, &rcIcon, &rcTextBounds);
|
|
}
|
|
else
|
|
{
|
|
if (iItem >= ListView_Count(plv))
|
|
{
|
|
return;
|
|
}
|
|
else
|
|
{
|
|
LISTITEM *pitem = ListView_FastGetItemPtr(plv, iItem);
|
|
|
|
if (pitem->cyFoldedLabel == SRECOMPUTE)
|
|
{
|
|
_ListView_RecomputeLabelSize(plv, pitem, iItem, NULL, FALSE);
|
|
}
|
|
_ListView_GetRectsFromItem(plv, ListView_IsSmallView(plv), pitem, fQueryLabelRects,
|
|
prcIcon, prcLabel, prcBounds, prcSelectBounds);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void ListView_GetRectsOwnerData(LV* plv, int iItem,
|
|
RECT* prcIcon, RECT* prcLabel, RECT* prcBounds,
|
|
RECT* prcSelectBounds, LISTITEM* pitem)
|
|
{
|
|
ASSERT(plv);
|
|
ASSERT(ListView_IsOwnerData(plv));
|
|
|
|
if (ListView_IsReportView(plv))
|
|
{
|
|
ListView_RGetRects(plv, iItem, prcIcon, prcLabel, prcBounds,
|
|
prcSelectBounds);
|
|
}
|
|
else if (ListView_IsListView(plv))
|
|
{
|
|
ListView_LGetRects(plv, iItem, prcIcon, prcLabel, prcBounds,
|
|
prcSelectBounds);
|
|
}
|
|
else
|
|
{
|
|
RECT rcIcon;
|
|
RECT rcTextBounds;
|
|
|
|
if (ListView_IsIconView(plv))
|
|
ListView_IGetRectsOwnerData(plv, iItem, &rcIcon, &rcTextBounds, pitem, TRUE);
|
|
else if (ListView_IsSmallView(plv))
|
|
ListView_SGetRectsOwnerData(plv, iItem, &rcIcon, &rcTextBounds, pitem, TRUE);
|
|
else if (ListView_IsTileView(plv))
|
|
ListView_TGetRectsOwnerData(plv, iItem, &rcIcon, &rcTextBounds, pitem, TRUE);
|
|
|
|
// Don't need to check for folding here, as will have been handled in user data
|
|
// rectangle fetching functions.
|
|
|
|
if (prcIcon)
|
|
*prcIcon = rcIcon;
|
|
if (prcLabel)
|
|
*prcLabel = rcTextBounds;
|
|
|
|
if (prcBounds)
|
|
UnionRect(prcBounds, &rcIcon, &rcTextBounds);
|
|
|
|
if (prcSelectBounds)
|
|
UnionRect(prcSelectBounds, &rcIcon, &rcTextBounds);
|
|
}
|
|
}
|
|
|
|
|
|
BOOL ListView_OnRedrawItems(LV* plv, int iFirst, int iLast)
|
|
{
|
|
int iCount = ListView_Count(plv);
|
|
|
|
if (iFirst < iCount)
|
|
{
|
|
if (iLast >= iCount)
|
|
iLast = iCount - 1;
|
|
|
|
while (iFirst <= iLast)
|
|
ListView_InvalidateItem(plv, iFirst++, FALSE, RDW_INVALIDATE | RDW_ERASE);
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
// fSelectionOnly use the selection bounds only, ie. don't include
|
|
// columns in invalidation if in details view
|
|
//
|
|
void ListView_InvalidateItemEx(LV* plv, int iItem, BOOL fSelectionOnly,
|
|
UINT fRedraw, UINT maskChanged)
|
|
{
|
|
RECT rc;
|
|
LPRECT prcIcon;
|
|
LPRECT prcLabel;
|
|
LPRECT prcBounds;
|
|
LPRECT prcSelectBounds;
|
|
LISTITEM* pitem = NULL;
|
|
|
|
if (iItem == -1)
|
|
return;
|
|
|
|
// Ok if NULL
|
|
if (plv->hdpa)
|
|
pitem = ListView_GetItemPtr(plv, iItem);
|
|
|
|
|
|
prcIcon = prcLabel = prcBounds = prcSelectBounds = NULL;
|
|
|
|
// if we're in owner draw mode, and there's been a new font,
|
|
// we don't really know what the selection bounds is, so always use the bounds
|
|
// in that case... unless we're in fullrowselect mode
|
|
if (ListView_IsOwnerData(plv) && plv->flags & LVF_CUSTOMFONT &&
|
|
!ListView_FullRowSelect(plv))
|
|
{
|
|
fSelectionOnly = FALSE;
|
|
}
|
|
|
|
// if we're owner draw, there's no such thing as selection only
|
|
if (plv->ci.style & LVS_OWNERDRAWFIXED)
|
|
fSelectionOnly = FALSE;
|
|
|
|
if (fSelectionOnly)
|
|
{
|
|
// In report mode non-fullrowselect,
|
|
// we have to use the full label rectangle rather
|
|
// than just the selection bounds, since the stuff outside the
|
|
// rectangle might need redrawing, too.
|
|
|
|
if (ListView_IsReportView(plv) && !ListView_FullRowSelect(plv))
|
|
prcLabel = &rc;
|
|
else
|
|
prcSelectBounds = &rc;
|
|
}
|
|
else
|
|
{
|
|
// if _only_the_text_ or _only_the_image_ changed then limit the redraw
|
|
switch (maskChanged)
|
|
{
|
|
|
|
case LVIF_IMAGE:
|
|
prcIcon = &rc;
|
|
break;
|
|
|
|
case LVIF_TEXT:
|
|
prcLabel = &rc;
|
|
break;
|
|
|
|
default:
|
|
prcBounds = &rc;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (ListView_RedrawEnabled(plv))
|
|
{
|
|
ListView_GetRects(plv, iItem, QUERY_DEFAULT,
|
|
prcIcon, prcLabel, prcBounds, prcSelectBounds);
|
|
|
|
if (RECTS_IN_SIZE(plv->sizeClient, rc))
|
|
{
|
|
if (ListView_IsBorderSelect(plv))
|
|
{
|
|
InflateRect(&rc, 4 + g_cxIconMargin, 4 + g_cyIconMargin); // account for selection border and seperation since drawing otside of icon
|
|
fRedraw |= RDW_ERASE;
|
|
}
|
|
|
|
// Affects only allowed if dubble buffering
|
|
if (ListView_IsDoubleBuffer(plv))
|
|
{
|
|
if ((pitem && (pitem->state & LVIS_GLOW)))
|
|
{
|
|
InflateRect(&rc, GLOW_EXPAND, GLOW_EXPAND);
|
|
fRedraw |= RDW_ERASE;
|
|
}
|
|
}
|
|
|
|
ListView_DebugDrawInvalidRegion(plv, &rc, NULL);
|
|
RedrawWindow(plv->ci.hwnd, &rc, NULL, fRedraw);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// if we're not visible, we'll get a full
|
|
// erase bk when we do become visible, so only do this stuff when
|
|
// we're on setredraw false
|
|
if (!(plv->flags & LVF_REDRAW))
|
|
{
|
|
|
|
// if we're invalidating that's new (thus hasn't been painted yet)
|
|
// blow it off
|
|
if ((plv->iFirstChangedNoRedraw != -1) &&
|
|
(iItem >= plv->iFirstChangedNoRedraw))
|
|
{
|
|
return;
|
|
}
|
|
|
|
ListView_GetRects(plv, iItem, QUERY_DEFAULT,
|
|
prcIcon, prcLabel, prcBounds, prcSelectBounds);
|
|
|
|
// Affects only allowed if dubble buffering
|
|
if (ListView_IsDoubleBuffer(plv))
|
|
{
|
|
if (pitem && (pitem->state & LVIS_GLOW))
|
|
{
|
|
InflateRect(&rc, GLOW_EXPAND, GLOW_EXPAND);
|
|
fRedraw |= RDW_ERASE;
|
|
}
|
|
}
|
|
|
|
if (ListView_IsBorderSelect(plv))
|
|
{
|
|
InflateRect(&rc, 4 + g_cxIconMargin, 4 + g_cyIconMargin); // account for selection border and seperation since drawing otside of icon
|
|
fRedraw |= RDW_ERASE;
|
|
}
|
|
|
|
// if it had the erase bit, add it to our region
|
|
if (RECTS_IN_SIZE(plv->sizeClient, rc))
|
|
{
|
|
HRGN hrgn = CreateRectRgnIndirect(&rc);
|
|
|
|
ListView_InvalidateRegion(plv, hrgn);
|
|
|
|
if (fRedraw & RDW_ERASE)
|
|
plv->flags |= LVF_ERASE;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// this returns BF_* flags to indicate which if any edge the item I is touching
|
|
// or crossing...
|
|
UINT LV_IsItemOnViewEdge(LV* plv, LISTITEM* pitem)
|
|
{
|
|
RECT rcItem;
|
|
UINT uRet = 0;
|
|
|
|
// as far as rcView goes, unfolded label rects determine edge-ness
|
|
_ListView_GetRectsFromItem(plv, ListView_IsSmallView(plv), pitem, QUERY_RCVIEW|QUERY_UNFOLDED,
|
|
NULL, NULL, &rcItem, NULL);
|
|
// translate from window coordinates to listview coordinate
|
|
OffsetRect(&rcItem, plv->ptOrigin.x, plv->ptOrigin.y);
|
|
// include the rcView buffer
|
|
ListView_AddViewRectBuffer(plv, &rcItem);
|
|
|
|
if (rcItem.right >= plv->rcView.right)
|
|
uRet |= BF_RIGHT;
|
|
|
|
if (rcItem.left <= plv->rcView.left)
|
|
uRet |= BF_LEFT;
|
|
|
|
if (rcItem.top <= plv->rcView.top)
|
|
uRet |= BF_TOP;
|
|
|
|
if (rcItem.bottom >= plv->rcView.bottom)
|
|
uRet |= BF_BOTTOM;
|
|
|
|
return uRet;
|
|
}
|
|
|
|
// Move pitem to x,y
|
|
// Update rcView to accomodate this if we can, or mark rcView for recomputation
|
|
void LV_AdjustViewRectOnMove(LV* plv, LISTITEM *pitem, int x, int y)
|
|
{
|
|
plv->iFreeSlot = -1; // The "free slot" cache is no good once an item moves
|
|
|
|
// if we have to recompute anyways, don't bother
|
|
if (!ListView_IsOwnerData(plv))
|
|
{
|
|
if ((plv->rcView.left != RECOMPUTE) &&
|
|
x != RECOMPUTE && y != RECOMPUTE &&
|
|
pitem->cyFoldedLabel != SRECOMPUTE)
|
|
{
|
|
RECT rcClient, rcAfter;
|
|
RECT rcView = plv->rcView;
|
|
|
|
// Our optimized move-adjust-rcView must maintain this, make sure it's true before we even start:
|
|
ASSERT(ListView_ValidatercView(plv, &plv->rcView, FALSE));
|
|
|
|
ListView_GetClientRect(plv, &rcClient, TRUE, NULL);
|
|
ASSERT(ListView_ValidateScrollPositions(plv, &rcClient));
|
|
|
|
if (pitem->pt.x != RECOMPUTE)
|
|
{
|
|
UINT uEdges;
|
|
|
|
uEdges = LV_IsItemOnViewEdge(plv, pitem);
|
|
|
|
pitem->pt.x = x;
|
|
pitem->pt.y = y;
|
|
|
|
// before and after the move, they need to be touching the
|
|
// same edges or not at all
|
|
if (uEdges != LV_IsItemOnViewEdge(plv, pitem))
|
|
{
|
|
goto FullRecompute;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// if the position wasn't set before
|
|
// we just need to find out what it is afterwards and
|
|
// enlarge the view... we don't need to shrink it
|
|
pitem->pt.x = x;
|
|
pitem->pt.y = y;
|
|
}
|
|
|
|
_ListView_GetRectsFromItem(plv, ListView_IsSmallView(plv), pitem, QUERY_RCVIEW|QUERY_UNFOLDED,
|
|
NULL, NULL, &rcAfter, NULL);
|
|
// translate from window coordinates to listview coordinates
|
|
OffsetRect(&rcAfter, plv->ptOrigin.x, plv->ptOrigin.y);
|
|
|
|
// include the rcView buffer
|
|
ListView_AddViewRectBuffer(plv, &rcAfter);
|
|
|
|
// if we make it here, we just have to make sure the new view rect
|
|
// encompases this new item
|
|
UnionRect(&rcView, &rcView, &rcAfter);
|
|
|
|
DebugMsg(TF_LISTVIEW, TEXT("Score! (%d %d %d %d) was (%d %d %d %d)"),
|
|
rcView.left, rcView.top, rcView.right, rcView.bottom,
|
|
plv->rcView.left, plv->rcView.top, plv->rcView.right, plv->rcView.bottom);
|
|
|
|
// Our optimized move-adjust-rcView must maintain this:
|
|
ASSERT(ListView_ValidatercView(plv, &rcView, FALSE));
|
|
plv->rcView = rcView;
|
|
|
|
// make sure our scroll positions are correct
|
|
if (ListView_IsIScrollView(plv))
|
|
ListView_FixIScrollPositions(plv, FALSE, &rcClient);
|
|
ASSERT(ListView_ValidateScrollPositions(plv, &rcClient));
|
|
}
|
|
else
|
|
{
|
|
FullRecompute:
|
|
plv->rcView.left = RECOMPUTE;
|
|
}
|
|
}
|
|
|
|
DebugMsg(TF_LISTVIEW, TEXT("LV -- AdjustViewRect pitem %d -- (%x, %x)"),
|
|
pitem,
|
|
pitem->pt.x, pitem->pt.y);
|
|
|
|
pitem->pt.x = x;
|
|
pitem->pt.y = y;
|
|
|
|
// Compute the workarea of this item if applicable
|
|
ListView_FindWorkArea(plv, pitem->pt, &(pitem->iWorkArea));
|
|
}
|
|
|
|
BOOL ListView_OnSetItemPosition(LV* plv, int i, int x, int y)
|
|
{
|
|
LISTITEM* pitem;
|
|
|
|
if (plv->fGroupView)
|
|
return FALSE;
|
|
|
|
if (ListView_IsListView(plv))
|
|
return FALSE;
|
|
|
|
if (ListView_IsOwnerData(plv))
|
|
{
|
|
RIPMSG(0, "LVM_SETITEMPOSITION: Invalid for owner-data listview");
|
|
return FALSE;
|
|
}
|
|
|
|
pitem = ListView_GetItemPtr(plv, i);
|
|
if (!pitem)
|
|
return FALSE;
|
|
|
|
//
|
|
// this is a hack to fix a bug in OLE drag/drop loop
|
|
//
|
|
if (x >= 0xF000 && x < 0x10000)
|
|
{
|
|
DebugMsg(TF_LISTVIEW, TEXT("LV -- On SetItemPosition fixing truncated negative number 0x%08X"), x);
|
|
x = x - 0x10000;
|
|
}
|
|
|
|
if (y >= 0xF000 && y < 0x10000)
|
|
{
|
|
DebugMsg(TF_LISTVIEW, TEXT("LV -- On SetItemPosition fixing truncated negative number 0x%08X"), y);
|
|
y = y - 0x10000;
|
|
}
|
|
|
|
ListView_InvalidateTTLastHit(plv, plv->iTTLastHit);
|
|
|
|
if (pitem->cyFoldedLabel == SRECOMPUTE)
|
|
{
|
|
_ListView_RecomputeLabelSize(plv, pitem, i, NULL, FALSE);
|
|
}
|
|
|
|
// erase old
|
|
|
|
if (y != pitem->pt.y || x != pitem->pt.x)
|
|
{
|
|
// Don't invalidate if it hasn't got a position yet
|
|
if (pitem->pt.y != RECOMPUTE)
|
|
{
|
|
ListView_InvalidateItem(plv, i, FALSE, RDW_INVALIDATE | RDW_ERASE);
|
|
}
|
|
else if (plv->uUnplaced)
|
|
{
|
|
// this means an unplaced item got placed
|
|
plv->uUnplaced--;
|
|
if (!plv->uUnplaced)
|
|
{
|
|
MSG msg;
|
|
// if this is now 0, pull out the postmessage
|
|
PeekMessage(&msg, plv->ci.hwnd, LVMI_PLACEITEMS, LVMI_PLACEITEMS, PM_REMOVE);
|
|
}
|
|
}
|
|
|
|
if (y == RECOMPUTE)
|
|
{
|
|
// if they're setting the new position to be a "any open spot" post that we
|
|
// need to calc this later
|
|
if (!plv->uUnplaced)
|
|
{
|
|
PostMessage(plv->ci.hwnd, LVMI_PLACEITEMS, 0, 0);
|
|
}
|
|
plv->uUnplaced++;
|
|
}
|
|
}
|
|
|
|
DebugMsg(TF_LISTVIEW, TEXT("LV -- On SetItemPosition %d %d %d %d -- (%x, %x)"),
|
|
plv->rcView.left, plv->rcView.top, plv->rcView.right, plv->rcView.bottom,
|
|
pitem->pt.x, pitem->pt.y);
|
|
|
|
|
|
LV_AdjustViewRectOnMove(plv, pitem, x, y);
|
|
|
|
// and draw at new position
|
|
ListView_RecalcRegion(plv, FALSE, TRUE);
|
|
ListView_InvalidateItem(plv, i, FALSE, RDW_INVALIDATE);
|
|
|
|
// If autoarrange is turned on, do it now...
|
|
if (ListView_RedrawEnabled(plv))
|
|
{
|
|
ListView_ArrangeOrSnapToGrid(plv);
|
|
if (!(plv->ci.style & LVS_AUTOARRANGE))
|
|
ListView_UpdateScrollBars(plv);
|
|
}
|
|
|
|
if (!(plv->ci.style & LVS_AUTOARRANGE))
|
|
{
|
|
plv->fIconsPositioned = TRUE;
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
BOOL ListView_OnGetItemPosition(LV* plv, int i, POINT* ppt)
|
|
{
|
|
LISTITEM* pitem;
|
|
|
|
//
|
|
// This needs to handle all views as it is used to figure out
|
|
// where the item is during drag and drop and the like
|
|
//
|
|
if (!ppt)
|
|
{
|
|
RIPMSG(0, "LVM_GETITEMPOSITION: Invalid ppt = NULL");
|
|
return FALSE;
|
|
}
|
|
|
|
if (ListView_IsListView(plv) || ListView_IsReportView(plv)
|
|
|| ListView_IsOwnerData(plv))
|
|
{
|
|
RECT rcIcon;
|
|
ListView_GetRects(plv, i, QUERY_DEFAULT, &rcIcon, NULL, NULL, NULL);
|
|
ppt->x = rcIcon.left;
|
|
ppt->y = rcIcon.top;
|
|
|
|
}
|
|
else
|
|
{
|
|
|
|
pitem = ListView_GetItemPtr(plv, i);
|
|
if (!pitem)
|
|
return FALSE;
|
|
|
|
if (pitem->pt.x == RECOMPUTE)
|
|
ListView_Recompute(plv);
|
|
|
|
ppt->x = pitem->pt.x;
|
|
ppt->y = pitem->pt.y;
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
BOOL ListView_OnGetOrigin(LV* plv, POINT* ppt)
|
|
{
|
|
if (!ppt)
|
|
{
|
|
DebugMsg(DM_ERROR, TEXT("ListView_OnGetOrigin: ppt is NULL"));
|
|
return FALSE;
|
|
}
|
|
|
|
if (ListView_IsListView(plv) || ListView_IsReportView(plv))
|
|
return FALSE;
|
|
|
|
*ppt = plv->ptOrigin;
|
|
return TRUE;
|
|
}
|
|
|
|
int ListView_OnGetStringWidthA(LV* plv, LPCSTR psz, HDC hdc)
|
|
{
|
|
LPWSTR pszW = NULL;
|
|
int iRet;
|
|
|
|
if (!psz)
|
|
return 0;
|
|
|
|
if ((psz != NULL) && (pszW = ProduceWFromA(plv->ci.uiCodePage, psz)) == NULL)
|
|
return 0;
|
|
|
|
iRet = ListView_OnGetStringWidth(plv, pszW, hdc);
|
|
|
|
FreeProducedString(pszW);
|
|
|
|
return iRet;
|
|
}
|
|
|
|
int ListView_OnGetStringWidth(LV* plv, LPCTSTR psz, HDC hdc)
|
|
{
|
|
SIZE siz;
|
|
HDC hdcFree = NULL;
|
|
HFONT hfontPrev;
|
|
|
|
if (!psz || psz == LPSTR_TEXTCALLBACK)
|
|
return 0;
|
|
|
|
if (!hdc)
|
|
{
|
|
hdcFree = hdc = GetDC(plv->ci.hwnd);
|
|
}
|
|
|
|
hfontPrev = SelectFont(hdc, plv->hfontLabel);
|
|
GetTextExtentPoint(hdc, psz, lstrlen(psz), &siz);
|
|
SelectFont(hdc, hfontPrev);
|
|
|
|
if (hdcFree)
|
|
{
|
|
ReleaseDC(plv->ci.hwnd, hdcFree);
|
|
}
|
|
|
|
return siz.cx;
|
|
}
|
|
|
|
int ListView_OnGetColumnWidth(LV* plv, int iCol)
|
|
{
|
|
if (ListView_IsReportView(plv))
|
|
return ListView_RGetColumnWidth(plv, iCol);
|
|
else if (ListView_IsListView(plv))
|
|
return plv->cxItem;
|
|
|
|
return 0;
|
|
}
|
|
|
|
BOOL ListView_ISetColumnWidth(LV* plv, int iCol, int cx, BOOL fExplicit)
|
|
{
|
|
|
|
if (ListView_IsListView(plv))
|
|
{
|
|
if (iCol != 0 || cx <= 0)
|
|
return FALSE;
|
|
|
|
// if it's different and this is an explicit set, or we've never set it explicitly
|
|
if (plv->cxItem != cx && (fExplicit || !(plv->flags & LVF_COLSIZESET)))
|
|
{
|
|
// REVIEW: Should optimize what gets invalidated here...
|
|
|
|
plv->cxItem = cx;
|
|
if (fExplicit)
|
|
plv->flags |= LVF_COLSIZESET; // Set the fact that we explictly set size!.
|
|
|
|
if (ListView_IsLabelTip(plv))
|
|
{
|
|
// A truncated label may have been exposed or vice versa.
|
|
ListView_InvalidateTTLastHit(plv, plv->iTTLastHit);
|
|
}
|
|
|
|
RedrawWindow(plv->ci.hwnd, NULL, NULL, RDW_INVALIDATE | RDW_ERASE);
|
|
ListView_UpdateScrollBars(plv);
|
|
}
|
|
return TRUE;
|
|
}
|
|
else if (ListView_IsReportView(plv))
|
|
{
|
|
if (ListView_IsLabelTip(plv))
|
|
{
|
|
// A truncated label may have been exposed or vice versa.
|
|
ListView_InvalidateTTLastHit(plv, plv->iTTLastHit);
|
|
}
|
|
return ListView_RSetColumnWidth(plv, iCol, cx);
|
|
}
|
|
else
|
|
{
|
|
if (cx && plv->cxItem != cx && (fExplicit || !(plv->flags & LVF_COLSIZESET)))
|
|
{
|
|
// REVIEW: Should optimize what gets invalidated here...
|
|
plv->cxItem = cx;
|
|
if (fExplicit)
|
|
plv->flags |= LVF_COLSIZESET; // Set the fact that we explictly set size!.
|
|
|
|
RedrawWindow(plv->ci.hwnd, NULL, NULL, RDW_INVALIDATE | RDW_ERASE);
|
|
ListView_UpdateScrollBars(plv);
|
|
}
|
|
// BUG-FOR-BUG COMPATIBILITY: IE4 accidentally returned FALSE here.
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
void DrawGradiantLine(HDC hdc, RECT* prcText, RECT* prcGroup)
|
|
{
|
|
COLORREF cr1 = GetSysColor(COLOR_GRADIENTACTIVECAPTION);
|
|
COLORREF cr2 = GetSysColor(COLOR_WINDOW);
|
|
TRIVERTEX pt[2];
|
|
GRADIENT_RECT gr;
|
|
|
|
RECT rc = {prcGroup->left, prcText->bottom-1, prcGroup->left + GRADIENT_WIDTH, prcText->bottom};
|
|
|
|
pt[0].x = rc.left;
|
|
pt[0].y = rc.top;
|
|
pt[1].x = rc.right;
|
|
pt[1].y = rc.bottom;
|
|
|
|
pt[0].Red = GetRValue(cr1) << 8;
|
|
pt[0].Green = GetGValue(cr1) << 8;
|
|
pt[0].Blue = GetBValue(cr1) << 8;
|
|
pt[0].Alpha = 0xFF00;
|
|
pt[1].Red = GetRValue(cr2) << 8;
|
|
pt[1].Green = GetGValue(cr2) << 8;
|
|
pt[1].Blue = GetBValue(cr2) << 8;
|
|
pt[1].Alpha = 0x0000;
|
|
|
|
|
|
gr.UpperLeft = 0;
|
|
gr.LowerRight = 1;
|
|
|
|
GdiGradientFill(hdc, pt, 2, &gr, 1, GRADIENT_FILL_RECT_H);
|
|
|
|
}
|
|
|
|
void ListView_Redraw(LV* plv, HDC hdc, RECT* prcClip)
|
|
{
|
|
int i = 0;
|
|
int cItem = ListView_Count(plv);
|
|
NMCUSTOMDRAW nmcd;
|
|
LVDRAWITEM lvdi = {0};
|
|
|
|
SetBkMode(hdc, TRANSPARENT);
|
|
SelectFont(hdc, plv->hfontLabel);
|
|
|
|
nmcd.hdc = hdc;
|
|
|
|
nmcd.rc = *prcClip;
|
|
|
|
plv->ci.dwCustom = CICustomDrawNotify(&plv->ci, CDDS_PREPAINT, &nmcd);
|
|
if (!(plv->ci.dwCustom & CDRF_SKIPDEFAULT))
|
|
{
|
|
int cGroups;
|
|
// Just before doing any painting, see if the region is up to date...
|
|
ListView_RecalcRegion(plv, FALSE, TRUE);
|
|
|
|
//
|
|
// For list view and report view, we can save a lot of time
|
|
// by calculating the index of the first item that may need
|
|
// painting...
|
|
//
|
|
|
|
switch (plv->wView)
|
|
{
|
|
case LV_VIEW_DETAILS:
|
|
if (!plv->fGroupView)
|
|
{
|
|
i = ListView_RYHitTest(plv, prcClip->top);
|
|
cItem = ListView_RYHitTest(plv, prcClip->bottom) + 1;
|
|
}
|
|
break;
|
|
|
|
case LV_VIEW_LIST:
|
|
i = ListView_LCalcViewItem(plv, prcClip->left, prcClip->top);
|
|
cItem = ListView_LCalcViewItem(plv, prcClip->right, prcClip->bottom) + 1;
|
|
break;
|
|
|
|
default:
|
|
if (ListView_IsOwnerData(plv))
|
|
{
|
|
ListView_CalcMinMaxIndex(plv, prcClip, &i, &cItem);
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (i < 0)
|
|
i = 0;
|
|
|
|
cItem = min(ListView_Count(plv), cItem);
|
|
if (ListView_IsOwnerData(plv) && (cItem > i))
|
|
{
|
|
ListView_NotifyCacheHint(plv, i, cItem-1);
|
|
ListView_LazyCreateWinEvents(plv, i, cItem-1);
|
|
}
|
|
|
|
lvdi.plv = plv;
|
|
lvdi.nmcd.nmcd.hdc = hdc;
|
|
lvdi.prcClip = prcClip;
|
|
lvdi.pitem = NULL;
|
|
|
|
if (plv->hdpaGroups)
|
|
{
|
|
cGroups = DPA_GetPtrCount(plv->hdpaGroups);
|
|
|
|
if (plv->fGroupView && cGroups > 0 && ListView_IsGroupedView(plv))
|
|
{
|
|
int iGroup;
|
|
RECT rcClient;
|
|
GetClientRect(plv->ci.hwnd, &rcClient);
|
|
|
|
for (iGroup = 0; iGroup < cGroups; iGroup++)
|
|
{
|
|
LISTGROUP* pgrp = DPA_FastGetPtr(plv->hdpaGroups, iGroup);
|
|
int cItems = DPA_GetPtrCount(pgrp->hdpa);
|
|
|
|
if (cItems > 0)
|
|
{
|
|
RECT rcT;
|
|
RECT rc;
|
|
|
|
SetRect(&rc, 0,
|
|
pgrp->rc.top - LISTGROUP_HEIGHT(plv, pgrp),
|
|
rcClient.right,
|
|
pgrp->rc.bottom + plv->rcBorder.bottom + plv->paddingBottom);
|
|
|
|
if (ListView_IsReportView(plv))
|
|
{
|
|
OffsetRect(&rc, -plv->ptlRptOrigin.x, -plv->ptlRptOrigin.y + plv->yTop);
|
|
}
|
|
else
|
|
{
|
|
OffsetRect(&rc, -plv->ptOrigin.x, -plv->ptOrigin.y);
|
|
}
|
|
|
|
|
|
if (IntersectRect(&rcT, &rc, prcClip))
|
|
{
|
|
NMLVCUSTOMDRAW nmcdGroup = {0};
|
|
DWORD dwCust;
|
|
UINT uAlign = LVCFMT_LEFT;
|
|
HFONT hfontOld;
|
|
RECT rcBorder = plv->rcBorder;
|
|
rcBorder.top = max(pgrp->cyTitle + 6, plv->rcBorder.top);
|
|
nmcdGroup.nmcd.hdc = hdc;
|
|
nmcdGroup.nmcd.rc = rc;
|
|
nmcdGroup.nmcd.dwItemSpec = pgrp->iGroupId;
|
|
nmcdGroup.dwItemType = LVCDI_GROUP;
|
|
|
|
nmcdGroup.rcText.left = rc.left + plv->paddingLeft;
|
|
nmcdGroup.rcText.top = rc.top;
|
|
nmcdGroup.rcText.bottom = rc.top + max(pgrp->cyTitle + 6, plv->rcBorder.top);
|
|
nmcdGroup.rcText.right = rc.right;
|
|
|
|
nmcdGroup.uAlign = pgrp->uAlign;
|
|
|
|
nmcdGroup.clrText = plv->crHeader;
|
|
|
|
dwCust = CICustomDrawNotify(&plv->ci, CDDS_PREPAINT, &nmcdGroup.nmcd);
|
|
|
|
if (!(dwCust & CDRF_SKIPDEFAULT))
|
|
{
|
|
RECT rcHeader = {0};
|
|
if (!(LVCDRF_NOGROUPFRAME & dwCust))
|
|
{
|
|
DrawGradiantLine(hdc, &nmcdGroup.rcText, &nmcdGroup.nmcd.rc);
|
|
}
|
|
|
|
if (!(dwCust & CDRF_NEWFONT))
|
|
{
|
|
hfontOld = SelectObject(hdc, plv->hfontGroup);
|
|
}
|
|
|
|
if (nmcdGroup.uAlign & LVGA_HEADER_CENTER)
|
|
uAlign = LVCFMT_CENTER;
|
|
else if (nmcdGroup.uAlign & LVGA_HEADER_RIGHT)
|
|
uAlign = LVCFMT_RIGHT;
|
|
|
|
SHDrawText(hdc, pgrp->pszHeader,
|
|
&nmcdGroup.rcText, uAlign, SHDT_VCENTER | SHDT_LEFT,
|
|
plv->cyLabelChar, plv->cxEllipses,
|
|
nmcdGroup.clrText, CLR_NONE);
|
|
|
|
// Need do do this before we unselect so that we get the right font...
|
|
DrawText(hdc, pgrp->pszHeader, -1, &rcHeader, DT_LV | DT_CALCRECT);
|
|
|
|
if (!(dwCust & CDRF_NEWFONT))
|
|
{
|
|
SelectObject(hdc, hfontOld);
|
|
}
|
|
}
|
|
|
|
dwCust = CICustomDrawNotify(&plv->ci, CDDS_POSTPAINT, &nmcdGroup.nmcd);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
cItem = min(ListView_Count(plv), cItem);
|
|
|
|
for (; i < cItem; i++)
|
|
{
|
|
BOOL bSuccess;
|
|
int i2;
|
|
|
|
if (ListView_IsRearrangeableView(plv) &&
|
|
!ListView_IsOwnerData(plv))
|
|
{
|
|
LISTITEM *pitem;
|
|
|
|
// Icon views: Draw back-to-front mapped through
|
|
// Z-order array for proper Z order appearance - If autoarrange
|
|
// is on, we don't need to do this as our arrange code is setup
|
|
// to not overlap items!
|
|
//
|
|
// For the cases where we might have overlap, we sped this up,
|
|
// by converting the hdpaZorder into a list of indexes instead
|
|
// of pointers. This ovoids the costly convert pointer to
|
|
// index call.
|
|
//
|
|
i2 = (int)(UINT_PTR)DPA_FastGetPtr(plv->hdpaZOrder, (cItem - 1) -i);
|
|
|
|
//
|
|
// do a fast clip check on the item so we dont even try to
|
|
// draw it unless it is visible
|
|
//
|
|
// for small icon view we cant clip on the left without
|
|
// getting the text
|
|
//
|
|
// for large icon view we cant clip on the top without
|
|
// getting the text
|
|
//
|
|
// for large icon view in NOLABELWRAP mode, we can't clip
|
|
// on the top without getting the text, nor can we clip to
|
|
// the left or right in case the text is long.
|
|
//
|
|
// we can always clip to the bottom
|
|
//
|
|
pitem = ListView_FastGetItemPtr(plv, i2);
|
|
|
|
if (pitem && pitem->pt.x != RECOMPUTE)
|
|
{
|
|
int yBias = 0;
|
|
if (ListView_IsBorderSelect(plv))
|
|
yBias = BORDERSELECT_THICKNESS;
|
|
|
|
if (pitem->pt.y - yBias - plv->ptOrigin.y > prcClip->bottom)
|
|
continue;
|
|
|
|
if (plv->wView == LV_VIEW_SMALLICON)
|
|
{
|
|
if (pitem->pt.x - plv->ptOrigin.x - plv->cxState > prcClip->right)
|
|
continue;
|
|
|
|
if (pitem->pt.y + yBias + plv->cyItem - plv->ptOrigin.y < prcClip->top)
|
|
continue;
|
|
}
|
|
else if (!(plv->ci.style & LVS_NOLABELWRAP))
|
|
{
|
|
if (plv->wView == LV_VIEW_TILE)
|
|
{
|
|
if (pitem->pt.x - plv->sizeTile.cx - plv->ptOrigin.x > prcClip->right)
|
|
continue;
|
|
|
|
if (pitem->pt.x + yBias + plv->sizeTile.cx - plv->ptOrigin.x < prcClip->left)
|
|
continue;
|
|
}
|
|
else // LV_VIEW_ICON
|
|
{
|
|
if (pitem->pt.x - plv->cxIconSpacing - plv->ptOrigin.x > prcClip->right)
|
|
continue;
|
|
|
|
if (pitem->pt.x + yBias + plv->cxIconSpacing - plv->ptOrigin.x < prcClip->left)
|
|
continue;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (plv->fGroupView &&
|
|
!LISTITEM_HASGROUP(pitem))
|
|
{
|
|
continue; // Don't paint items not in a group.
|
|
}
|
|
}
|
|
else
|
|
i2 = i;
|
|
|
|
|
|
plv->iItemDrawing = i2;
|
|
|
|
lvdi.nmcd.nmcd.dwItemSpec = i2;
|
|
|
|
// these may get changed
|
|
lvdi.lpptOrg = NULL;
|
|
lvdi.flags = 0;
|
|
lvdi.nmcd.clrText = plv->clrText;
|
|
lvdi.nmcd.clrTextBk = plv->clrTextBk;
|
|
lvdi.nmcd.clrFace = plv->clrBk;
|
|
lvdi.nmcd.iIconEffect = ILD_NORMAL;
|
|
lvdi.nmcd.iIconPhase = 0;
|
|
|
|
|
|
bSuccess = ListView_DrawItem(&lvdi);
|
|
|
|
if (!bSuccess)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (ListView_IsRearrangeableView(plv) &&
|
|
(ListView_IsOwnerData(plv)) &&
|
|
plv->iFocus != -1)
|
|
{
|
|
// since there's no zorder in ownerdata, we explicitly draw the focus guy last (again)
|
|
// so that it'll appear on top
|
|
// we may potentially want to do this for all items that are selected
|
|
plv->iItemDrawing = plv->iFocus;
|
|
|
|
lvdi.nmcd.nmcd.dwItemSpec = plv->iItemDrawing;
|
|
|
|
// these may get changed
|
|
lvdi.lpptOrg = NULL;
|
|
lvdi.flags = 0;
|
|
lvdi.nmcd.clrText = plv->clrText;
|
|
lvdi.nmcd.clrTextBk = plv->clrTextBk;
|
|
|
|
ListView_DrawItem(&lvdi);
|
|
}
|
|
|
|
|
|
// this is an NT5/Memphis feature.
|
|
|
|
if (ListView_Count(plv) == 0)
|
|
{
|
|
// there're no items in this view
|
|
// check if we need to display some text in this case.
|
|
|
|
if (ListView_GetEmptyText(plv))
|
|
{
|
|
RECT rcClip;
|
|
UINT flags = 0;
|
|
|
|
// Put some edging between the text and the border of the
|
|
// window so we don't slam up against the border.
|
|
// This keeps DBCS from looking horrid.
|
|
rcClip.left = g_cxEdge;
|
|
rcClip.top = g_cyEdge;
|
|
|
|
if (plv->dwExStyle & WS_EX_RTLREADING)
|
|
flags |= SHDT_RTLREADING;
|
|
|
|
// if its a report view && we have a header then move the text down
|
|
if (ListView_IsReportView(plv) && (!(plv->ci.style & LVS_NOCOLUMNHEADER)))
|
|
rcClip.top += plv->cyItem;
|
|
|
|
// Note: Use the full sizeClient.cx as the right margin
|
|
// in case pszEmptyText is wider than the client rectangle.
|
|
|
|
rcClip.left -= (int)plv->ptlRptOrigin.x;
|
|
rcClip.right = plv->sizeClient.cx;
|
|
rcClip.bottom = rcClip.top + plv->cyItem;
|
|
|
|
SHDrawText(hdc, plv->pszEmptyText,
|
|
&rcClip, LVCFMT_LEFT, flags,
|
|
plv->cyLabelChar, plv->cxEllipses,
|
|
plv->clrText, plv->clrBk);
|
|
}
|
|
}
|
|
|
|
plv->iItemDrawing = -1;
|
|
|
|
// post painting.... this is to do any extra (non item) painting
|
|
// such a grid lines
|
|
switch (plv->wView)
|
|
{
|
|
case LV_VIEW_DETAILS:
|
|
ListView_RAfterRedraw(plv, hdc);
|
|
break;
|
|
}
|
|
|
|
// Insert mark
|
|
{
|
|
RECT rcInsertMark;
|
|
if (ListView_OnGetInsertMarkRect(plv, &rcInsertMark))
|
|
{
|
|
OffsetRect(&rcInsertMark, -plv->ptOrigin.x, -plv->ptOrigin.y);
|
|
CCDrawInsertMark(hdc,
|
|
&rcInsertMark,
|
|
((plv->ci.style & LVS_ALIGNMASK) == LVS_ALIGNTOP),
|
|
ListView_OnGetInsertMarkColor(plv));
|
|
}
|
|
}
|
|
|
|
// notify parent afterwards if they want us to
|
|
if (plv->ci.dwCustom & CDRF_NOTIFYPOSTPAINT)
|
|
{
|
|
CICustomDrawNotify(&plv->ci, CDDS_POSTPAINT, &nmcd);
|
|
}
|
|
}
|
|
}
|
|
|
|
BOOL ListView_DrawItem(PLVDRAWITEM plvdi)
|
|
{
|
|
BOOL fAllowHotSelection = FALSE;
|
|
BOOL bRet = TRUE;
|
|
UINT state;
|
|
|
|
if (!ListView_IsOwnerData(plvdi->plv) && (!plvdi->plv->hdpa || plvdi->nmcd.nmcd.dwItemSpec > (UINT)DPA_GetPtrCount(plvdi->plv->hdpa)))
|
|
return FALSE;
|
|
|
|
if (!ListView_IsOwnerData(plvdi->plv))
|
|
{
|
|
plvdi->pitem = ListView_FastGetItemPtr(plvdi->plv, plvdi->nmcd.nmcd.dwItemSpec);
|
|
}
|
|
|
|
// notify on custom draw then do it!
|
|
plvdi->nmcd.nmcd.uItemState = 0;
|
|
plvdi->nmcd.nmcd.lItemlParam = (plvdi->pitem)? plvdi->pitem->lParam : 0;
|
|
|
|
if (!(plvdi->flags & LVDI_NOWAYFOCUS))
|
|
{
|
|
if (plvdi->plv->flags & LVF_FOCUSED)
|
|
{
|
|
|
|
// if we're ownerdraw or asked to callback, go
|
|
// fetch the state
|
|
if (!plvdi->pitem || (plvdi->plv->stateCallbackMask & (LVIS_SELECTED | LVIS_FOCUSED)))
|
|
{
|
|
state = (WORD) ListView_OnGetItemState(plvdi->plv, (int) plvdi->nmcd.nmcd.dwItemSpec,
|
|
LVIS_SELECTED | LVIS_FOCUSED);
|
|
}
|
|
else
|
|
{
|
|
state = plvdi->pitem->state;
|
|
}
|
|
|
|
|
|
if (state & LVIS_FOCUSED)
|
|
{
|
|
plvdi->nmcd.nmcd.uItemState |= CDIS_FOCUS;
|
|
}
|
|
|
|
if (state & LVIS_SELECTED)
|
|
{
|
|
plvdi->nmcd.nmcd.uItemState |= CDIS_SELECTED;
|
|
}
|
|
}
|
|
|
|
// NOTE: This is a bug. We should set CDIS_SELECTED only if the item
|
|
// really is selected. But this bug has existed forever so who knows
|
|
// what apps are relying on it. Standard workaround is for the client
|
|
// to do a GetItemState and reconfirm the LVIS_SELECTED flag.
|
|
// That's what we do in ListView_DrawImageEx.
|
|
if (plvdi->plv->ci.style & LVS_SHOWSELALWAYS)
|
|
{
|
|
plvdi->nmcd.nmcd.uItemState |= CDIS_SELECTED;
|
|
}
|
|
}
|
|
|
|
if (!(CCGetUIState(&(plvdi->plv->ci)) & UISF_HIDEFOCUS))
|
|
{
|
|
plvdi->nmcd.nmcd.uItemState |= CDIS_SHOWKEYBOARDCUES;
|
|
}
|
|
|
|
plvdi->nmcd.clrText = plvdi->plv->clrText;
|
|
plvdi->nmcd.clrTextBk = (plvdi->plv->ci.style & WS_DISABLED ? plvdi->plv->clrBk : plvdi->plv->clrTextBk);
|
|
|
|
|
|
if ((plvdi->plv->exStyle & LVS_EX_UNDERLINEHOT) &&
|
|
plvdi->plv->iHot == (int)plvdi->nmcd.nmcd.dwItemSpec &&
|
|
(plvdi->plv->exStyle & LVS_EX_ONECLICKACTIVATE) ||
|
|
((plvdi->plv->exStyle & LVS_EX_TWOCLICKACTIVATE) &&
|
|
ListView_OnGetItemState(plvdi->plv, (int) plvdi->nmcd.nmcd.dwItemSpec, LVIS_SELECTED)))
|
|
{
|
|
|
|
fAllowHotSelection = TRUE;
|
|
// Handle the HOT case
|
|
if (plvdi->plv->clrHotlight != CLR_DEFAULT)
|
|
{
|
|
plvdi->nmcd.clrText = plvdi->plv->clrHotlight;
|
|
}
|
|
else
|
|
{
|
|
plvdi->nmcd.clrText = GetSysColor(COLOR_HOTLIGHT);
|
|
}
|
|
|
|
// if hotlight color is the same as the background
|
|
// color you don't see the text -- slam to a visible color in this case.
|
|
if (plvdi->nmcd.clrText == plvdi->nmcd.clrTextBk)
|
|
{
|
|
if (COLORISLIGHT(plvdi->nmcd.clrTextBk))
|
|
plvdi->nmcd.clrText = 0x000000; // black
|
|
else
|
|
plvdi->nmcd.clrText = 0xFFFFFF; // white
|
|
}
|
|
|
|
SelectFont(plvdi->nmcd.nmcd.hdc, plvdi->plv->hFontHot);
|
|
|
|
plvdi->nmcd.nmcd.uItemState |= CDIS_HOT;
|
|
}
|
|
else if ((plvdi->plv->exStyle & LVS_EX_ONECLICKACTIVATE) ||
|
|
((plvdi->plv->exStyle & LVS_EX_TWOCLICKACTIVATE) &&
|
|
ListView_OnGetItemState(plvdi->plv, (int) plvdi->nmcd.nmcd.dwItemSpec, LVIS_SELECTED)))
|
|
{
|
|
|
|
// Handle the non-hot webview case
|
|
if ((plvdi->plv->exStyle & LVS_EX_UNDERLINECOLD) &&
|
|
plvdi->plv->hFontHot)
|
|
{
|
|
SelectFont(plvdi->nmcd.nmcd.hdc, plvdi->plv->hFontHot);
|
|
}
|
|
else
|
|
{
|
|
SelectFont(plvdi->nmcd.nmcd.hdc, plvdi->plv->hfontLabel);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Handle the non-webview case
|
|
SelectFont(plvdi->nmcd.nmcd.hdc, plvdi->plv->hfontLabel);
|
|
}
|
|
|
|
|
|
plvdi->dwCustom = CICustomDrawNotify(&plvdi->plv->ci, CDDS_ITEMPREPAINT, &plvdi->nmcd.nmcd);
|
|
|
|
plvdi->flags &= ~(LVDI_FOCUS | LVDI_SELECTED);
|
|
if (plvdi->nmcd.nmcd.uItemState & CDIS_FOCUS)
|
|
plvdi->flags |= LVDI_FOCUS;
|
|
|
|
if (plvdi->nmcd.nmcd.uItemState & CDIS_SELECTED)
|
|
{
|
|
if (plvdi->plv->flags & LVF_FOCUSED)
|
|
plvdi->flags |= LVDI_SELECTED;
|
|
else
|
|
plvdi->flags |= LVDI_SELECTNOFOCUS;
|
|
if (plvdi->plv->iHot == (int)plvdi->nmcd.nmcd.dwItemSpec && fAllowHotSelection)
|
|
plvdi->flags |= LVDI_HOTSELECTED;
|
|
}
|
|
|
|
if (!(plvdi->dwCustom & CDRF_SKIPDEFAULT))
|
|
{
|
|
|
|
if (!ListView_IsOwnerData(plvdi->plv))
|
|
{
|
|
if (plvdi->dwCustom & CDRF_NEWFONT)
|
|
{
|
|
_ListView_RecomputeLabelSize(plvdi->plv, plvdi->pitem, (int) plvdi->nmcd.nmcd.dwItemSpec, plvdi->nmcd.nmcd.hdc, FALSE);
|
|
}
|
|
}
|
|
|
|
bRet = _ListView_DrawItem(plvdi);
|
|
|
|
|
|
if (plvdi->dwCustom & CDRF_NOTIFYPOSTPAINT)
|
|
{
|
|
plvdi->nmcd.iSubItem = 0;
|
|
CICustomDrawNotify(&plvdi->plv->ci, CDDS_ITEMPOSTPAINT, &plvdi->nmcd.nmcd);
|
|
}
|
|
|
|
if (plvdi->dwCustom & CDRF_NEWFONT)
|
|
{
|
|
SelectObject(plvdi->nmcd.nmcd.hdc, plvdi->plv->hfontLabel);
|
|
plvdi->plv->flags |= LVF_CUSTOMFONT;
|
|
}
|
|
}
|
|
return bRet;
|
|
}
|
|
|
|
void WINAPI SHThemeDrawText(HTHEME hTheme, HDC hdc, int iPartId, int iStateId, LPCTSTR pszText, RECT* prc, int fmt,
|
|
UINT flags, int cyChar, int cxEllipses, COLORREF clrText, COLORREF clrTextBk)
|
|
{
|
|
int cchText;
|
|
COLORREF clrSave = GetTextColor(hdc), clrSaveBk = 0;
|
|
RECT rc;
|
|
UINT uETOFlags = 0;
|
|
BOOL fForeOnly = FALSE;
|
|
TCHAR ach[CCHLABELMAX + CCHELLIPSES];
|
|
int align;
|
|
BOOL fUseShadowedText = (flags & SHDT_SHADOWTEXT) && (!g_fHighContrast);
|
|
|
|
if (!pszText)
|
|
return;
|
|
|
|
if (IsRectEmpty(prc))
|
|
return;
|
|
|
|
if (flags & SHDT_RTLREADING)
|
|
{
|
|
align = GetTextAlign(hdc);
|
|
SetTextAlign(hdc, align | TA_RTLREADING);
|
|
}
|
|
|
|
|
|
rc = *prc;
|
|
|
|
if (fUseShadowedText)
|
|
{
|
|
if (!AreAllMonitorsAtLeast(16))
|
|
fUseShadowedText = FALSE;
|
|
}
|
|
|
|
// If needed, add in a little extra margin...
|
|
//
|
|
if (flags & SHDT_EXTRAMARGIN)
|
|
{
|
|
rc.left += g_cxLabelMargin * 3;
|
|
rc.right -= g_cxLabelMargin * 3;
|
|
}
|
|
else if (!(flags & SHDT_NOMARGIN))
|
|
{
|
|
rc.left += g_cxLabelMargin;
|
|
rc.right -= g_cxLabelMargin;
|
|
}
|
|
|
|
if ((rc.left >= rc.right) && !(flags & (SHDT_SELECTED | SHDT_DESELECTED | SHDT_SELECTNOFOCUS)))
|
|
return;
|
|
|
|
if ((flags & SHDT_ELLIPSES) &&
|
|
ListView_NeedsEllipses(hdc, pszText, &rc, &cchText, cxEllipses))
|
|
{
|
|
// In some cases cchText was comming back bigger than
|
|
// ARRYASIZE(ach), so we need to make sure we don't overflow the buffer
|
|
|
|
// if cchText is too big for the buffer, truncate it down to size
|
|
if (cchText >= ARRAYSIZE(ach) - CCHELLIPSES)
|
|
{
|
|
cchText = ARRAYSIZE(ach) - CCHELLIPSES - 1;
|
|
}
|
|
|
|
memcpy(ach, pszText, cchText * sizeof(TCHAR));
|
|
StringCchCopy(ach+cchText, ARRAYSIZE(ach)-cchText, c_szEllipses);
|
|
|
|
pszText = ach;
|
|
|
|
// Left-justify, in case there's no room for all of ellipses
|
|
//
|
|
fmt = LVCFMT_LEFT;
|
|
|
|
cchText += CCHELLIPSES;
|
|
}
|
|
else
|
|
{
|
|
cchText = lstrlen(pszText);
|
|
}
|
|
|
|
if (((clrTextBk == CLR_NONE) && !(flags & (SHDT_SELECTED | SHDT_SELECTNOFOCUS))) || (flags & SHDT_TRANSPARENT))
|
|
{
|
|
fForeOnly = TRUE;
|
|
clrSave = SetTextColor(hdc, (flags & SHDT_TRANSPARENT) ? 0 : clrText);
|
|
}
|
|
else if (!hTheme || clrTextBk != CLR_NONE)
|
|
{
|
|
HBRUSH hbrUse = NULL;
|
|
HBRUSH hbrDelete = NULL;
|
|
|
|
uETOFlags |= ETO_OPAQUE;
|
|
|
|
if ((flags & SHDT_SELECTED || flags & SHDT_SELECTNOFOCUS) && !(flags & SHDT_NOSELECTED))
|
|
{
|
|
fUseShadowedText = FALSE;
|
|
if (flags & SHDT_SELECTNOFOCUS)
|
|
{
|
|
clrText = g_clrBtnText;
|
|
clrTextBk = g_clrBtnFace;
|
|
if (flags & SHDT_DRAWTEXT)
|
|
{
|
|
hbrUse = g_hbrBtnFace;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
clrText = g_clrHighlightText;
|
|
if (flags & SHDT_HOTSELECTED)
|
|
clrTextBk = GetSysColor(COLOR_HOTLIGHT);
|
|
else
|
|
clrTextBk = g_clrHighlight;
|
|
|
|
if (flags & SHDT_DRAWTEXT)
|
|
hbrUse = (flags & SHDT_HOTSELECTED)?GetSysColorBrush(COLOR_HOTLIGHT): g_hbrHighlight;
|
|
}
|
|
}
|
|
else if (clrText == CLR_DEFAULT && clrTextBk == CLR_DEFAULT)
|
|
{
|
|
fUseShadowedText = FALSE;
|
|
clrText = g_clrWindowText;
|
|
clrTextBk = g_clrWindow;
|
|
|
|
if ((flags & (SHDT_DRAWTEXT | SHDT_DESELECTED)) ==
|
|
(SHDT_DRAWTEXT | SHDT_DESELECTED))
|
|
{
|
|
hbrUse = g_hbrWindow;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (clrText == CLR_DEFAULT)
|
|
clrText = g_clrWindowText;
|
|
|
|
if (clrTextBk == CLR_DEFAULT)
|
|
clrTextBk = g_clrWindow;
|
|
|
|
if (fUseShadowedText == FALSE &&
|
|
((flags & (SHDT_DRAWTEXT | SHDT_DESELECTED)) ==
|
|
(SHDT_DRAWTEXT | SHDT_DESELECTED) || hTheme))
|
|
{
|
|
hbrUse = CreateSolidBrush(GetNearestColor(hdc, clrTextBk));
|
|
if (hbrUse)
|
|
{
|
|
hbrDelete = hbrUse;
|
|
}
|
|
else
|
|
hbrUse = GetStockObject(WHITE_BRUSH);
|
|
}
|
|
}
|
|
|
|
// now set it
|
|
clrSave = SetTextColor(hdc, clrText);
|
|
clrSaveBk = SetBkColor(hdc, clrTextBk);
|
|
if (hbrUse)
|
|
{
|
|
FillRect(hdc, prc, hbrUse);
|
|
if (hbrDelete)
|
|
DeleteObject(hbrDelete);
|
|
}
|
|
}
|
|
|
|
// If we want the item to display as if it was depressed, we will
|
|
// offset the text rectangle down and to the left
|
|
if (flags & SHDT_DEPRESSED)
|
|
OffsetRect(&rc, g_cxBorder, g_cyBorder);
|
|
|
|
if (flags & SHDT_DRAWTEXT || hTheme)
|
|
{
|
|
HRESULT hr = E_FAIL;
|
|
UINT uDTFlags;
|
|
if (flags & SHDT_DRAWTEXT)
|
|
{
|
|
uDTFlags= DT_LVWRAP | DT_END_ELLIPSIS;
|
|
}
|
|
else
|
|
{
|
|
uDTFlags = DT_END_ELLIPSIS | DT_SINGLELINE | DT_VCENTER;
|
|
if (fmt & LVCFMT_CENTER)
|
|
uDTFlags |= DT_CENTER;
|
|
else if (fmt & LVCFMT_RIGHT)
|
|
uDTFlags |= DT_RIGHT;
|
|
}
|
|
|
|
if (flags & SHDT_DTELLIPSIS)
|
|
uDTFlags |= DT_WORD_ELLIPSIS;
|
|
|
|
if (!(flags & SHDT_CLIPPED))
|
|
uDTFlags |= DT_NOCLIP;
|
|
|
|
if (flags & SHDT_NODBCSBREAK)
|
|
uDTFlags |= DT_NOFULLWIDTHCHARBREAK;
|
|
|
|
if (flags & SHDT_VCENTER)
|
|
uDTFlags |= DT_VCENTER | DT_SINGLELINE;
|
|
|
|
if (flags & SHDT_LEFT)
|
|
uDTFlags = DT_LEFT | uDTFlags & ~DT_CENTER;
|
|
|
|
if (fUseShadowedText)
|
|
{
|
|
DrawShadowText(hdc, pszText, cchText, &rc, uDTFlags, RGB(255,255,255), RGB(0,0,0), 2, 2);
|
|
}
|
|
else
|
|
{
|
|
if (hTheme)
|
|
hr = DrawThemeText(hTheme, hdc, iPartId, iStateId, pszText, -1, uDTFlags, 0, &rc);
|
|
|
|
if (FAILED(hr))
|
|
DrawText(hdc, pszText, cchText, &rc, uDTFlags);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (fmt != LVCFMT_LEFT)
|
|
{
|
|
SIZE siz;
|
|
|
|
GetTextExtentPoint(hdc, pszText, cchText, &siz);
|
|
|
|
if (fmt == LVCFMT_CENTER)
|
|
rc.left = (rc.left + rc.right - siz.cx) / 2;
|
|
else // fmt == LVCFMT_RIGHT
|
|
rc.left = rc.right - siz.cx;
|
|
}
|
|
|
|
// Center vertically in case the bitmap (to the left) is larger than
|
|
// the height of one line
|
|
rc.top += (rc.bottom - rc.top - cyChar) / 2;
|
|
|
|
if (flags & SHDT_CLIPPED)
|
|
uETOFlags |= ETO_CLIPPED;
|
|
|
|
// HACK: ExtTextOut() has an off-by-one bug in its rendering of RTL
|
|
// text. We need this hack to render properly (RAID 439915).
|
|
// Note that this bug is NOT present in the DrawText() API.
|
|
if (flags & SHDT_RTLREADING)
|
|
rc.left--;
|
|
|
|
ExtTextOut(hdc, rc.left, rc.top, uETOFlags, prc, pszText, cchText, NULL);
|
|
}
|
|
|
|
if (flags & (SHDT_SELECTED | SHDT_DESELECTED | SHDT_TRANSPARENT))
|
|
{
|
|
SetTextColor(hdc, clrSave);
|
|
if (!fForeOnly)
|
|
SetBkColor(hdc, clrSaveBk);
|
|
}
|
|
|
|
if (flags & SHDT_RTLREADING)
|
|
SetTextAlign(hdc, align);
|
|
}
|
|
|
|
void WINAPI SHDrawText(HDC hdc, LPCTSTR pszText, RECT* prc, int fmt,
|
|
UINT flags, int cyChar, int cxEllipses, COLORREF clrText, COLORREF clrTextBk)
|
|
{
|
|
SHThemeDrawText(NULL, hdc, 0, 0, pszText, prc, fmt,
|
|
flags, cyChar, cxEllipses, clrText, clrTextBk);
|
|
}
|
|
|
|
|
|
|
|
/*----------------------------------------------------------------
|
|
** Create an imagelist to be used for dragging.
|
|
**
|
|
** 1) create mask and image bitmap matching the select bounds size
|
|
** 2) draw the text to both bitmaps (in black for now)
|
|
** 3) create an imagelist with these bitmaps
|
|
** 4) make a dithered copy of the image onto the new imagelist
|
|
**----------------------------------------------------------------*/
|
|
HIMAGELIST ListView_OnCreateDragImage(LV *plv, int iItem, LPPOINT lpptUpLeft)
|
|
{
|
|
HWND hwndLV = plv->ci.hwnd;
|
|
RECT rcBounds, rcImage, rcLabel;
|
|
HDC hdcMem = NULL;
|
|
HBITMAP hbmImage = NULL;
|
|
HBITMAP hbmMask = NULL;
|
|
HBITMAP hbmOld;
|
|
HIMAGELIST himl = NULL;
|
|
int dx, dy;
|
|
HIMAGELIST himlSrc;
|
|
LV_ITEM item;
|
|
POINT ptOrg;
|
|
LVDRAWITEM lvdi = {0};
|
|
RECT rcSelBounds;
|
|
BOOL bMirroredWnd = (plv->ci.dwExStyle&RTL_MIRRORED_WINDOW);
|
|
int iImageList;
|
|
|
|
if (!lpptUpLeft)
|
|
return NULL;
|
|
|
|
if (iItem >= ListView_Count(plv))
|
|
return NULL;
|
|
|
|
if (plv->iHot == iItem)
|
|
{
|
|
ListView_OnSetHotItem(plv, -1);
|
|
UpdateWindow(plv->ci.hwnd);
|
|
}
|
|
|
|
ListView_GetRects(plv, iItem, QUERY_DEFAULT, &rcImage, &rcLabel, &rcBounds, &rcSelBounds);
|
|
|
|
if (ListView_IsIconView(plv))
|
|
{
|
|
ListView_UnfoldRects(plv, iItem, &rcImage, &rcLabel,
|
|
&rcBounds, &rcSelBounds);
|
|
InflateRect(&rcImage, -g_cxIconMargin, -g_cyIconMargin);
|
|
}
|
|
|
|
// chop off any extra filler above icon
|
|
ptOrg.x = rcBounds.left - rcSelBounds.left;
|
|
ptOrg.y = rcBounds.top - rcImage.top;
|
|
dx = rcSelBounds.right - rcSelBounds.left;
|
|
dy = rcSelBounds.bottom - rcImage.top;
|
|
|
|
lpptUpLeft->x = rcSelBounds.left;
|
|
lpptUpLeft->y = rcImage.top;
|
|
|
|
if (!(hdcMem = CreateCompatibleDC(NULL)))
|
|
goto CDI_Exit;
|
|
if (!(hbmImage = CreateColorBitmap(dx, dy)))
|
|
goto CDI_Exit;
|
|
if (!(hbmMask = CreateMonoBitmap(dx, dy)))
|
|
goto CDI_Exit;
|
|
|
|
//
|
|
// Mirror the memory DC so that the transition from
|
|
// mirrored(memDC)->non-mirrored(imagelist DCs)->mirrored(screenDC)
|
|
// is consistent. [samera]
|
|
//
|
|
if (bMirroredWnd)
|
|
{
|
|
SET_DC_RTL_MIRRORED(hdcMem);
|
|
}
|
|
|
|
// prepare for drawing the item
|
|
SelectObject(hdcMem, plv->hfontLabel);
|
|
SetBkMode(hdcMem, TRANSPARENT);
|
|
|
|
lvdi.plv = plv;
|
|
lvdi.nmcd.nmcd.dwItemSpec = iItem;
|
|
lvdi.pitem = NULL; // make sure it is null as Owner data uses this to trigger things...
|
|
lvdi.nmcd.nmcd.hdc = hdcMem;
|
|
lvdi.lpptOrg = &ptOrg;
|
|
lvdi.prcClip = NULL;
|
|
lvdi.flags = LVDI_NOIMAGE | LVDI_TRANSTEXT | LVDI_NOWAYFOCUS | LVDI_UNFOLDED;
|
|
lvdi.nmcd.clrFace = CLR_NONE;
|
|
/*
|
|
** draw the text to both bitmaps
|
|
*/
|
|
hbmOld = SelectObject(hdcMem, hbmImage);
|
|
// fill image with black for transparency
|
|
PatBlt(hdcMem, 0, 0, dx, dy, BLACKNESS);
|
|
ListView_DrawItem(&lvdi);
|
|
if (bMirroredWnd)
|
|
MirrorBitmapInDC(hdcMem, hbmImage);
|
|
|
|
lvdi.flags = LVDI_NOIMAGE | LVDI_TRANSTEXT | LVDI_NOWAYFOCUS | LVDI_UNFOLDED;
|
|
SelectObject(hdcMem, hbmMask);
|
|
// fill mask with white for transparency
|
|
PatBlt(hdcMem, 0, 0, dx, dy, WHITENESS);
|
|
ListView_DrawItem(&lvdi);
|
|
if (bMirroredWnd)
|
|
MirrorBitmapInDC(hdcMem, hbmMask);
|
|
|
|
// unselect objects that we used
|
|
SelectObject(hdcMem, hbmOld);
|
|
SelectObject(hdcMem, g_hfontSystem);
|
|
|
|
if (ListView_IsIconView(plv) || ListView_IsTileView(plv))
|
|
iImageList = LVSIL_NORMAL;
|
|
else
|
|
iImageList = LVSIL_SMALL;
|
|
|
|
|
|
himlSrc = ListView_OnGetImageList(plv, iImageList);
|
|
|
|
/*
|
|
** make an image list that for now only has the text
|
|
** we use ImageList_Clone so we get a imagelist that
|
|
** the same color depth as our own imagelist
|
|
*/
|
|
if (!(himl = ImageList_Clone(himlSrc, dx, dy, ILC_MASK, 1, 0)))
|
|
goto CDI_Exit;
|
|
|
|
ImageList_SetBkColor(himl, CLR_NONE);
|
|
ImageList_Add(himl, hbmImage, hbmMask);
|
|
|
|
/*
|
|
** make a dithered copy of the image part onto our bitmaps
|
|
** (need both bitmap and mask to be dithered)
|
|
*/
|
|
if (himlSrc)
|
|
{
|
|
item.iItem = iItem;
|
|
item.iSubItem = 0;
|
|
item.mask = LVIF_IMAGE |LVIF_STATE;
|
|
item.stateMask = LVIS_OVERLAYMASK;
|
|
ListView_OnGetItem(plv, &item);
|
|
|
|
ImageList_CopyDitherImage(himl, 0, rcImage.left - rcSelBounds.left, 0, himlSrc, item.iImage, ((plv->ci.dwExStyle & dwExStyleRTLMirrorWnd) ? ILD_MIRROR : 0L) | (item.state & LVIS_OVERLAYMASK));
|
|
}
|
|
|
|
CDI_Exit:
|
|
if (hdcMem)
|
|
DeleteObject(hdcMem);
|
|
if (hbmImage)
|
|
DeleteObject(hbmImage);
|
|
if (hbmMask)
|
|
DeleteObject(hbmMask);
|
|
|
|
return himl;
|
|
}
|
|
|
|
// ListView_OnGetTopIndex -- Gets the index of the first visible item
|
|
// For list view and report view this calculates the actual index
|
|
// for iconic views it alway returns 0
|
|
//
|
|
int ListView_OnGetTopIndex(LV* plv)
|
|
{
|
|
if (ListView_IsReportView(plv) && !plv->fGroupView)
|
|
return (int)((plv->ptlRptOrigin.y) / plv->cyItem);
|
|
else if (ListView_IsListView(plv))
|
|
return (plv->xOrigin / plv->cxItem) * plv->cItemCol;
|
|
else
|
|
return 0;
|
|
}
|
|
|
|
|
|
// ListView_OnGetCountPerPage -- Gets the count of items that will fit
|
|
// on a page For list view and report view this calculates the
|
|
// count depending on the size of the window and for Iconic views it
|
|
// will always return the count of items in the list view.
|
|
//
|
|
int ListView_OnGetCountPerPage(LV* plv)
|
|
{
|
|
if (ListView_IsReportView(plv))
|
|
return (plv->sizeClient.cy - plv->yTop) / plv->cyItem;
|
|
|
|
else if (ListView_IsListView(plv))
|
|
return ((plv->sizeClient.cx)/ plv->cxItem)
|
|
* plv->cItemCol;
|
|
else
|
|
return (ListView_Count(plv));
|
|
}
|
|
|
|
|
|
/* Purpose:
|
|
/ Provides support for invalidating items within list views.
|
|
/
|
|
/ Notes:
|
|
/ Copes with invalidating the extra region in the list view that requires
|
|
/ us to erase the background. Design to optimise out the ERASURE of the
|
|
/ background.
|
|
/
|
|
/ For details on the API see ListView_InvalidateItem.
|
|
/
|
|
/ In:
|
|
/ plv->ListView structure to work with
|
|
/ iItem = item number
|
|
/ bSrelectionOnly = refesh the selection
|
|
/ fRedraw = Flags for RedrawWindow
|
|
/ Out:
|
|
/ -
|
|
*/
|
|
|
|
void ListView_InvalidateFoldedItem(LV* plv, int iItem, BOOL fSelectionOnly, UINT fRedraw)
|
|
{
|
|
ListView_InvalidateItem(plv, iItem, fSelectionOnly, fRedraw);
|
|
|
|
if (ListView_IsIconView(plv) &&
|
|
(!ListView_IsItemUnfolded(plv, iItem) || (fRedraw & RDW_ERASE)))
|
|
{
|
|
RECT rcLabel;
|
|
|
|
if (ListView_GetUnfoldedRect(plv, iItem, &rcLabel))
|
|
{
|
|
RedrawWindow(plv->ci.hwnd, &rcLabel, NULL, fRedraw|RDW_ERASE);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
/ Purpose:
|
|
/ Having previously called get rects, then call this function to ensure
|
|
/ that they are correctly unfolded.
|
|
/
|
|
/ Notes:
|
|
/ -
|
|
/
|
|
/ In:
|
|
/ plv-> list view to unfold on
|
|
/ iItem = item number
|
|
/ prcIcon -> icon bounding box
|
|
/ prcLabel -> rectangle for the label structure
|
|
/ prcBounds -> bounds rectangle / == NULL for none / These are currently the same for large icons
|
|
/ prcSelectBounds -> selection bounds / == NULL /
|
|
/ Out: TRUE if unfolding the item was worth anything
|
|
/ -
|
|
*/
|
|
BOOL ListView_UnfoldRects(LV* plv, int iItem,
|
|
RECT * prcIcon, RECT * prcLabel,
|
|
RECT * prcBounds, RECT * prcSelectBounds)
|
|
{
|
|
LISTITEM item;
|
|
LISTITEM* pitem = &item;
|
|
BOOL fRc = FALSE;
|
|
|
|
if (!ListView_IsIconView(plv))
|
|
return fRc;
|
|
|
|
// If we have a label pointer then expand as required
|
|
// nb - different paths for owner data
|
|
|
|
if (prcLabel)
|
|
{
|
|
if (!ListView_IsOwnerData(plv))
|
|
{
|
|
pitem = ListView_GetItemPtr(plv, iItem);
|
|
if (!EVAL(pitem))
|
|
{
|
|
// DavidShi was able to get us into here with an invalid
|
|
// item number during a delete notification. So if the
|
|
// item number is invalid, just return a blank rectangle
|
|
// instead of faulting.
|
|
SetRectEmpty(prcLabel);
|
|
goto doneLabel;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
_ListView_RecomputeLabelSize(plv, pitem, iItem, NULL, FALSE);
|
|
}
|
|
|
|
if (prcLabel->bottom != prcLabel->top + max(pitem->cyUnfoldedLabel, pitem->cyFoldedLabel))
|
|
fRc = TRUE;
|
|
|
|
// In HideLabel mode it's always "worthwhile" to "unfold" the rects because the label is not shown
|
|
// by default. By returning TRUE we cause the item's label to display in a tooltip where the label
|
|
// would normally be.
|
|
if (ListView_HideLabels(plv))
|
|
fRc = TRUE;
|
|
|
|
prcLabel->bottom = prcLabel->top + pitem->cyUnfoldedLabel;
|
|
}
|
|
doneLabel:
|
|
|
|
// Build the unions if required
|
|
if (prcBounds && prcIcon && prcLabel)
|
|
{
|
|
ListView_CalcBounds(plv, QUERY_UNFOLDED, prcIcon, prcLabel, prcBounds);
|
|
}
|
|
if (prcSelectBounds && prcIcon && prcLabel)
|
|
{
|
|
if (ListView_HideLabels(plv))
|
|
*prcBounds = *prcIcon;
|
|
else
|
|
UnionRect(prcSelectBounds, prcIcon, prcLabel);
|
|
}
|
|
|
|
return fRc;
|
|
}
|
|
|
|
|
|
|
|
void ListView_InvalidateMark(LV* plv)
|
|
{
|
|
RECT rc;
|
|
|
|
if (ListView_OnGetInsertMarkRect(plv, &rc))
|
|
{
|
|
OffsetRect(&rc, -plv->ptOrigin.x, -plv->ptOrigin.y);
|
|
InvalidateRect(plv->ci.hwnd, &rc, TRUE);
|
|
}
|
|
}
|
|
|
|
|
|
// Returns the insertmark rect in listview coordinates. Returns FALSE if there is no insertmarkrect
|
|
BOOL ListView_OnGetInsertMarkRect(LV* plv, LPRECT prc)
|
|
{
|
|
BOOL fVert;
|
|
RECT rcSlot;
|
|
LISTITEM *pitem;
|
|
|
|
if (plv->iInsertItem == -1)
|
|
return FALSE;
|
|
|
|
pitem = ListView_GetItemPtr(plv, plv->iInsertItem);
|
|
if (!pitem)
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
fVert = !((plv->ci.style & LVS_ALIGNMASK) == LVS_ALIGNTOP);
|
|
|
|
ListView_CalcItemSlotAndRect(plv, pitem, NULL, &rcSlot);
|
|
|
|
if (fVert)
|
|
{
|
|
int iY;
|
|
prc->left = rcSlot.left;
|
|
prc->right = rcSlot.right;
|
|
iY = (plv->fInsertAfter) ? rcSlot.bottom : rcSlot.top;
|
|
prc->top = iY - INSERTMARKSIZE/2;
|
|
prc->bottom = iY + INSERTMARKSIZE/2 + 1;
|
|
}
|
|
else
|
|
{
|
|
int iX;
|
|
prc->top = rcSlot.top;
|
|
prc->bottom = rcSlot.bottom;
|
|
iX = (plv->fInsertAfter) ? rcSlot.right : rcSlot.left;
|
|
prc->left = iX - INSERTMARKSIZE/2;
|
|
prc->right = iX + INSERTMARKSIZE/2 + 1;
|
|
}
|
|
return TRUE;
|
|
|
|
}
|
|
|
|
COLORREF ListView_OnGetInsertMarkColor(LV* plv)
|
|
{
|
|
if (plv->clrim == CLR_DEFAULT)
|
|
return plv->clrText;
|
|
else
|
|
return plv->clrim;
|
|
}
|