11783 lines
338 KiB
C
11783 lines
338 KiB
C
/*
|
|
* Listview control
|
|
*
|
|
* Copyright 1998, 1999 Eric Kohl
|
|
* Copyright 1999 Luc Tourangeau
|
|
* Copyright 2000 Jason Mawdsley
|
|
* Copyright 2001 CodeWeavers Inc.
|
|
* Copyright 2002 Dimitrie O. Paun
|
|
* Copyright 2009-2013 Nikolay Sivov
|
|
* Copyright 2009 Owen Rudge for CodeWeavers
|
|
* Copyright 2012-2013 Daniel Jelinski
|
|
*
|
|
* This library is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU Lesser General Public
|
|
* License as published by the Free Software Foundation; either
|
|
* version 2.1 of the License, or (at your option) any later version.
|
|
*
|
|
* This library is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
* Lesser General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Lesser General Public
|
|
* License along with this library; if not, write to the Free Software
|
|
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
|
|
*
|
|
* NOTES
|
|
*
|
|
* This code was audited for completeness against the documented features
|
|
* of Comctl32.dll version 6.0 on May. 20, 2005, by James Hawkins.
|
|
*
|
|
* Unless otherwise noted, we believe this code to be complete, as per
|
|
* the specification mentioned above.
|
|
* If you discover missing features, or bugs, please note them below.
|
|
*
|
|
* TODO:
|
|
*
|
|
* Default Message Processing
|
|
* -- WM_CREATE: create the icon and small icon image lists at this point only if
|
|
* the LVS_SHAREIMAGELISTS style is not specified.
|
|
* -- WM_WINDOWPOSCHANGED: arrange the list items if the current view is icon
|
|
* or small icon and the LVS_AUTOARRANGE style is specified.
|
|
* -- WM_TIMER
|
|
* -- WM_WININICHANGE
|
|
*
|
|
* Features
|
|
* -- Hot item handling, mouse hovering
|
|
* -- Workareas support
|
|
* -- Tilemode support
|
|
* -- Groups support
|
|
*
|
|
* Bugs
|
|
* -- Expand large item in ICON mode when the cursor is flying over the icon or text.
|
|
* -- Support CustomDraw options for _WIN32_IE >= 0x560 (see NMLVCUSTOMDRAW docs).
|
|
* -- LVA_SNAPTOGRID not implemented
|
|
* -- LISTVIEW_ApproximateViewRect partially implemented
|
|
* -- LISTVIEW_SetColumnWidth ignores header images & bitmap
|
|
* -- LISTVIEW_StyleChanged doesn't handle some changes too well
|
|
*
|
|
* Speedups
|
|
* -- LISTVIEW_GetNextItem needs to be rewritten. It is currently
|
|
* linear in the number of items in the list, and this is
|
|
* unacceptable for large lists.
|
|
* -- if list is sorted by item text LISTVIEW_InsertItemT could use
|
|
* binary search to calculate item index (e.g. DPA_Search()).
|
|
* This requires sorted state to be reliably tracked in item modifiers.
|
|
* -- we should keep an ordered array of coordinates in iconic mode
|
|
* this would allow to frame items (iterator_frameditems),
|
|
* and find nearest item (LVFI_NEARESTXY) a lot more efficiently
|
|
*
|
|
* Flags
|
|
* -- LVIF_COLUMNS
|
|
* -- LVIF_GROUPID
|
|
*
|
|
* States
|
|
* -- LVIS_ACTIVATING (not currently supported by comctl32.dll version 6.0)
|
|
* -- LVIS_DROPHILITED
|
|
*
|
|
* Styles
|
|
* -- LVS_NOLABELWRAP
|
|
* -- LVS_NOSCROLL (see Q137520)
|
|
* -- LVS_ALIGNTOP
|
|
*
|
|
* Extended Styles
|
|
* -- LVS_EX_BORDERSELECT
|
|
* -- LVS_EX_FLATSB
|
|
* -- LVS_EX_INFOTIP
|
|
* -- LVS_EX_LABELTIP
|
|
* -- LVS_EX_MULTIWORKAREAS
|
|
* -- LVS_EX_REGIONAL
|
|
* -- LVS_EX_SIMPLESELECT
|
|
* -- LVS_EX_TWOCLICKACTIVATE
|
|
* -- LVS_EX_UNDERLINECOLD
|
|
* -- LVS_EX_UNDERLINEHOT
|
|
*
|
|
* Notifications:
|
|
* -- LVN_BEGINSCROLL, LVN_ENDSCROLL
|
|
* -- LVN_GETINFOTIP
|
|
* -- LVN_HOTTRACK
|
|
* -- LVN_SETDISPINFO
|
|
*
|
|
* Messages:
|
|
* -- LVM_ENABLEGROUPVIEW
|
|
* -- LVM_GETBKIMAGE, LVM_SETBKIMAGE
|
|
* -- LVM_GETGROUPINFO, LVM_SETGROUPINFO
|
|
* -- LVM_GETGROUPMETRICS, LVM_SETGROUPMETRICS
|
|
* -- LVM_GETINSERTMARK, LVM_SETINSERTMARK
|
|
* -- LVM_GETINSERTMARKCOLOR, LVM_SETINSERTMARKCOLOR
|
|
* -- LVM_GETINSERTMARKRECT
|
|
* -- LVM_GETNUMBEROFWORKAREAS
|
|
* -- LVM_GETOUTLINECOLOR, LVM_SETOUTLINECOLOR
|
|
* -- LVM_GETSELECTEDCOLUMN, LVM_SETSELECTEDCOLUMN
|
|
* -- LVM_GETISEARCHSTRINGW, LVM_GETISEARCHSTRINGA
|
|
* -- LVM_GETTILEINFO, LVM_SETTILEINFO
|
|
* -- LVM_GETTILEVIEWINFO, LVM_SETTILEVIEWINFO
|
|
* -- LVM_GETWORKAREAS, LVM_SETWORKAREAS
|
|
* -- LVM_HASGROUP, LVM_INSERTGROUP, LVM_REMOVEGROUP, LVM_REMOVEALLGROUPS
|
|
* -- LVM_INSERTGROUPSORTED
|
|
* -- LVM_INSERTMARKHITTEST
|
|
* -- LVM_ISGROUPVIEWENABLED
|
|
* -- LVM_MOVEGROUP
|
|
* -- LVM_MOVEITEMTOGROUP
|
|
* -- LVM_SETINFOTIP
|
|
* -- LVM_SETTILEWIDTH
|
|
* -- LVM_SORTGROUPS
|
|
*
|
|
* Macros:
|
|
* -- ListView_GetHoverTime, ListView_SetHoverTime
|
|
* -- ListView_GetISearchString
|
|
* -- ListView_GetNumberOfWorkAreas
|
|
* -- ListView_GetWorkAreas, ListView_SetWorkAreas
|
|
*
|
|
* Functions:
|
|
* -- LVGroupComparE
|
|
*/
|
|
|
|
#include "config.h"
|
|
#include "wine/port.h"
|
|
|
|
#include <assert.h>
|
|
#include <ctype.h>
|
|
#include <string.h>
|
|
#include <stdlib.h>
|
|
#include <stdarg.h>
|
|
#include <stdio.h>
|
|
|
|
#include "windef.h"
|
|
#include "winbase.h"
|
|
#include "winnt.h"
|
|
#include "wingdi.h"
|
|
#include "winuser.h"
|
|
#include "winnls.h"
|
|
#include "commctrl.h"
|
|
#include "comctl32.h"
|
|
#include "uxtheme.h"
|
|
|
|
#include "wine/debug.h"
|
|
#include "wine/unicode.h"
|
|
|
|
WINE_DEFAULT_DEBUG_CHANNEL(listview);
|
|
|
|
typedef struct tagCOLUMN_INFO
|
|
{
|
|
RECT rcHeader; /* tracks the header's rectangle */
|
|
INT fmt; /* same as LVCOLUMN.fmt */
|
|
INT cxMin;
|
|
} COLUMN_INFO;
|
|
|
|
typedef struct tagITEMHDR
|
|
{
|
|
LPWSTR pszText;
|
|
INT iImage;
|
|
} ITEMHDR, *LPITEMHDR;
|
|
|
|
typedef struct tagSUBITEM_INFO
|
|
{
|
|
ITEMHDR hdr;
|
|
INT iSubItem;
|
|
} SUBITEM_INFO;
|
|
|
|
typedef struct tagITEM_ID ITEM_ID;
|
|
|
|
typedef struct tagITEM_INFO
|
|
{
|
|
ITEMHDR hdr;
|
|
UINT state;
|
|
LPARAM lParam;
|
|
INT iIndent;
|
|
ITEM_ID *id;
|
|
} ITEM_INFO;
|
|
|
|
struct tagITEM_ID
|
|
{
|
|
UINT id; /* item id */
|
|
HDPA item; /* link to item data */
|
|
};
|
|
|
|
typedef struct tagRANGE
|
|
{
|
|
INT lower;
|
|
INT upper;
|
|
} RANGE;
|
|
|
|
typedef struct tagRANGES
|
|
{
|
|
HDPA hdpa;
|
|
} *RANGES;
|
|
|
|
typedef struct tagITERATOR
|
|
{
|
|
INT nItem;
|
|
INT nSpecial;
|
|
RANGE range;
|
|
RANGES ranges;
|
|
INT index;
|
|
} ITERATOR;
|
|
|
|
typedef struct tagDELAYED_ITEM_EDIT
|
|
{
|
|
BOOL fEnabled;
|
|
INT iItem;
|
|
} DELAYED_ITEM_EDIT;
|
|
|
|
typedef struct tagLISTVIEW_INFO
|
|
{
|
|
/* control window */
|
|
HWND hwndSelf;
|
|
RECT rcList; /* This rectangle is really the window
|
|
* client rectangle possibly reduced by the
|
|
* horizontal scroll bar and/or header - see
|
|
* LISTVIEW_UpdateSize. This rectangle offset
|
|
* by the LISTVIEW_GetOrigin value is in
|
|
* client coordinates */
|
|
|
|
/* notification window */
|
|
SHORT notifyFormat;
|
|
HWND hwndNotify;
|
|
BOOL bDoChangeNotify; /* send change notification messages? */
|
|
UINT uCallbackMask;
|
|
|
|
/* tooltips */
|
|
HWND hwndToolTip;
|
|
|
|
/* items */
|
|
INT nItemCount; /* the number of items in the list */
|
|
HDPA hdpaItems; /* array ITEM_INFO pointers */
|
|
HDPA hdpaItemIds; /* array of ITEM_ID pointers */
|
|
HDPA hdpaPosX; /* maintains the (X, Y) coordinates of the */
|
|
HDPA hdpaPosY; /* items in LVS_ICON, and LVS_SMALLICON modes */
|
|
RANGES selectionRanges;
|
|
INT nSelectionMark; /* item to start next multiselection from */
|
|
INT nHotItem;
|
|
BOOL bAutoarrange; /* Autoarrange flag when NOT in LVS_AUTOARRANGE */
|
|
|
|
/* columns */
|
|
HDPA hdpaColumns; /* array of COLUMN_INFO pointers */
|
|
BOOL colRectsDirty; /* trigger column rectangles requery from header */
|
|
|
|
/* item metrics */
|
|
BOOL bNoItemMetrics; /* flags if item metrics are not yet computed */
|
|
INT nItemHeight;
|
|
INT nItemWidth;
|
|
|
|
/* sorting */
|
|
PFNLVCOMPARE pfnCompare; /* sorting callback pointer */
|
|
LPARAM lParamSort;
|
|
|
|
/* style */
|
|
DWORD dwStyle; /* the cached window GWL_STYLE */
|
|
DWORD dwLvExStyle; /* extended listview style */
|
|
DWORD uView; /* current view available through LVM_[G,S]ETVIEW */
|
|
|
|
/* edit item */
|
|
HWND hwndEdit;
|
|
WNDPROC EditWndProc;
|
|
INT nEditLabelItem;
|
|
DELAYED_ITEM_EDIT itemEdit; /* Pointer to this structure will be the timer ID */
|
|
|
|
/* icons */
|
|
HIMAGELIST himlNormal;
|
|
HIMAGELIST himlSmall;
|
|
HIMAGELIST himlState;
|
|
SIZE iconSize;
|
|
BOOL autoSpacing;
|
|
SIZE iconSpacing;
|
|
SIZE iconStateSize;
|
|
POINT currIconPos; /* this is the position next icon will be placed */
|
|
|
|
/* header */
|
|
HWND hwndHeader;
|
|
INT xTrackLine; /* The x coefficient of the track line or -1 if none */
|
|
|
|
/* marquee selection */
|
|
BOOL bMarqueeSelect; /* marquee selection/highlight underway */
|
|
BOOL bScrolling;
|
|
RECT marqueeRect; /* absolute coordinates of marquee selection */
|
|
RECT marqueeDrawRect; /* relative coordinates for drawing marquee */
|
|
POINT marqueeOrigin; /* absolute coordinates of marquee click origin */
|
|
|
|
/* focus drawing */
|
|
BOOL bFocus; /* control has focus */
|
|
INT nFocusedItem;
|
|
RECT rcFocus; /* focus bounds */
|
|
|
|
/* colors */
|
|
HBRUSH hBkBrush;
|
|
COLORREF clrBk;
|
|
COLORREF clrText;
|
|
COLORREF clrTextBk;
|
|
|
|
/* font */
|
|
HFONT hDefaultFont;
|
|
HFONT hFont;
|
|
INT ntmHeight; /* Some cached metrics of the font used */
|
|
INT ntmMaxCharWidth; /* by the listview to draw items */
|
|
INT nEllipsisWidth;
|
|
|
|
/* mouse operation */
|
|
BOOL bLButtonDown;
|
|
BOOL bDragging;
|
|
POINT ptClickPos; /* point where the user clicked */
|
|
INT nLButtonDownItem; /* tracks item to reset multiselection on WM_LBUTTONUP */
|
|
DWORD dwHoverTime;
|
|
HCURSOR hHotCursor;
|
|
INT cWheelRemainder;
|
|
|
|
/* keyboard operation */
|
|
DWORD lastKeyPressTimestamp;
|
|
WPARAM charCode;
|
|
INT nSearchParamLength;
|
|
WCHAR szSearchParam[ MAX_PATH ];
|
|
|
|
/* painting */
|
|
DWORD cditemmode; /* Keep the custom draw flags for an item/row */
|
|
BOOL bIsDrawing; /* Drawing in progress */
|
|
INT nMeasureItemHeight; /* WM_MEASUREITEM result */
|
|
BOOL bRedraw; /* WM_SETREDRAW switch */
|
|
|
|
/* misc */
|
|
DWORD iVersion; /* CCM_[G,S]ETVERSION */
|
|
} LISTVIEW_INFO;
|
|
|
|
/*
|
|
* constants
|
|
*/
|
|
/* How many we debug buffer to allocate */
|
|
#define DEBUG_BUFFERS 20
|
|
/* The size of a single debug buffer */
|
|
#define DEBUG_BUFFER_SIZE 256
|
|
|
|
/* Internal interface to LISTVIEW_HScroll and LISTVIEW_VScroll */
|
|
#define SB_INTERNAL -1
|
|
|
|
/* maximum size of a label */
|
|
#define DISP_TEXT_SIZE 260
|
|
|
|
/* padding for items in list and small icon display modes */
|
|
#define WIDTH_PADDING 12
|
|
|
|
/* padding for items in list, report and small icon display modes */
|
|
#define HEIGHT_PADDING 1
|
|
|
|
/* offset of items in report display mode */
|
|
#define REPORT_MARGINX 2
|
|
|
|
/* padding for icon in large icon display mode
|
|
* ICON_TOP_PADDING_NOTHITABLE - space between top of box and area
|
|
* that HITTEST will see.
|
|
* ICON_TOP_PADDING_HITABLE - spacing between above and icon.
|
|
* ICON_TOP_PADDING - sum of the two above.
|
|
* ICON_BOTTOM_PADDING - between bottom of icon and top of text
|
|
* LABEL_HOR_PADDING - between text and sides of box
|
|
* LABEL_VERT_PADDING - between bottom of text and end of box
|
|
*
|
|
* ICON_LR_PADDING - additional width above icon size.
|
|
* ICON_LR_HALF - half of the above value
|
|
*/
|
|
#define ICON_TOP_PADDING_NOTHITABLE 2
|
|
#define ICON_TOP_PADDING_HITABLE 2
|
|
#define ICON_TOP_PADDING (ICON_TOP_PADDING_NOTHITABLE + ICON_TOP_PADDING_HITABLE)
|
|
#define ICON_BOTTOM_PADDING 4
|
|
#define LABEL_HOR_PADDING 5
|
|
#define LABEL_VERT_PADDING 7
|
|
#define ICON_LR_PADDING 16
|
|
#define ICON_LR_HALF (ICON_LR_PADDING/2)
|
|
|
|
/* default label width for items in list and small icon display modes */
|
|
#define DEFAULT_LABEL_WIDTH 40
|
|
/* maximum select rectangle width for empty text item in LV_VIEW_DETAILS */
|
|
#define MAX_EMPTYTEXT_SELECT_WIDTH 80
|
|
|
|
/* default column width for items in list display mode */
|
|
#define DEFAULT_COLUMN_WIDTH 128
|
|
|
|
/* Size of "line" scroll for V & H scrolls */
|
|
#define LISTVIEW_SCROLL_ICON_LINE_SIZE 37
|
|
|
|
/* Padding between image and label */
|
|
#define IMAGE_PADDING 2
|
|
|
|
/* Padding behind the label */
|
|
#define TRAILING_LABEL_PADDING 12
|
|
#define TRAILING_HEADER_PADDING 11
|
|
|
|
/* Border for the icon caption */
|
|
#define CAPTION_BORDER 2
|
|
|
|
/* Standard DrawText flags */
|
|
#define LV_ML_DT_FLAGS (DT_TOP | DT_NOPREFIX | DT_EDITCONTROL | DT_CENTER | DT_WORDBREAK | DT_WORD_ELLIPSIS | DT_END_ELLIPSIS)
|
|
#define LV_FL_DT_FLAGS (DT_TOP | DT_NOPREFIX | DT_EDITCONTROL | DT_CENTER | DT_WORDBREAK | DT_NOCLIP)
|
|
#define LV_SL_DT_FLAGS (DT_VCENTER | DT_NOPREFIX | DT_EDITCONTROL | DT_SINGLELINE | DT_WORD_ELLIPSIS | DT_END_ELLIPSIS)
|
|
|
|
/* Image index from state */
|
|
#define STATEIMAGEINDEX(x) (((x) & LVIS_STATEIMAGEMASK) >> 12)
|
|
|
|
/* The time in milliseconds to reset the search in the list */
|
|
#define KEY_DELAY 450
|
|
|
|
/* Dump the LISTVIEW_INFO structure to the debug channel */
|
|
#define LISTVIEW_DUMP(iP) do { \
|
|
TRACE("hwndSelf=%p, clrBk=0x%06x, clrText=0x%06x, clrTextBk=0x%06x, ItemHeight=%d, ItemWidth=%d, Style=0x%08x\n", \
|
|
iP->hwndSelf, iP->clrBk, iP->clrText, iP->clrTextBk, \
|
|
iP->nItemHeight, iP->nItemWidth, iP->dwStyle); \
|
|
TRACE("hwndSelf=%p, himlNor=%p, himlSml=%p, himlState=%p, Focused=%d, Hot=%d, exStyle=0x%08x, Focus=%d\n", \
|
|
iP->hwndSelf, iP->himlNormal, iP->himlSmall, iP->himlState, \
|
|
iP->nFocusedItem, iP->nHotItem, iP->dwLvExStyle, iP->bFocus ); \
|
|
TRACE("hwndSelf=%p, ntmH=%d, icSz.cx=%d, icSz.cy=%d, icSp.cx=%d, icSp.cy=%d, notifyFmt=%d\n", \
|
|
iP->hwndSelf, iP->ntmHeight, iP->iconSize.cx, iP->iconSize.cy, \
|
|
iP->iconSpacing.cx, iP->iconSpacing.cy, iP->notifyFormat); \
|
|
TRACE("hwndSelf=%p, rcList=%s\n", iP->hwndSelf, wine_dbgstr_rect(&iP->rcList)); \
|
|
} while(0)
|
|
|
|
static const WCHAR themeClass[] = {'L','i','s','t','V','i','e','w',0};
|
|
|
|
/*
|
|
* forward declarations
|
|
*/
|
|
static BOOL LISTVIEW_GetItemT(const LISTVIEW_INFO *, LPLVITEMW, BOOL);
|
|
static void LISTVIEW_GetItemBox(const LISTVIEW_INFO *, INT, LPRECT);
|
|
static void LISTVIEW_GetItemOrigin(const LISTVIEW_INFO *, INT, LPPOINT);
|
|
static BOOL LISTVIEW_GetItemPosition(const LISTVIEW_INFO *, INT, LPPOINT);
|
|
static BOOL LISTVIEW_GetItemRect(const LISTVIEW_INFO *, INT, LPRECT);
|
|
static void LISTVIEW_GetOrigin(const LISTVIEW_INFO *, LPPOINT);
|
|
static BOOL LISTVIEW_GetViewRect(const LISTVIEW_INFO *, LPRECT);
|
|
static void LISTVIEW_UpdateSize(LISTVIEW_INFO *);
|
|
static LRESULT LISTVIEW_Command(LISTVIEW_INFO *, WPARAM, LPARAM);
|
|
static INT LISTVIEW_GetStringWidthT(const LISTVIEW_INFO *, LPCWSTR, BOOL);
|
|
static BOOL LISTVIEW_KeySelection(LISTVIEW_INFO *, INT, BOOL);
|
|
static UINT LISTVIEW_GetItemState(const LISTVIEW_INFO *, INT, UINT);
|
|
static BOOL LISTVIEW_SetItemState(LISTVIEW_INFO *, INT, const LVITEMW *);
|
|
static LRESULT LISTVIEW_VScroll(LISTVIEW_INFO *, INT, INT);
|
|
static LRESULT LISTVIEW_HScroll(LISTVIEW_INFO *, INT, INT);
|
|
static BOOL LISTVIEW_EnsureVisible(LISTVIEW_INFO *, INT, BOOL);
|
|
static HIMAGELIST LISTVIEW_SetImageList(LISTVIEW_INFO *, INT, HIMAGELIST);
|
|
static INT LISTVIEW_HitTest(const LISTVIEW_INFO *, LPLVHITTESTINFO, BOOL, BOOL);
|
|
static BOOL LISTVIEW_EndEditLabelT(LISTVIEW_INFO *, BOOL, BOOL);
|
|
static BOOL LISTVIEW_Scroll(LISTVIEW_INFO *, INT, INT);
|
|
|
|
/******** Text handling functions *************************************/
|
|
|
|
/* A text pointer is either NULL, LPSTR_TEXTCALLBACK, or points to a
|
|
* text string. The string may be ANSI or Unicode, in which case
|
|
* the boolean isW tells us the type of the string.
|
|
*
|
|
* The name of the function tell what type of strings it expects:
|
|
* W: Unicode, T: ANSI/Unicode - function of isW
|
|
*/
|
|
|
|
static inline BOOL is_text(LPCWSTR text)
|
|
{
|
|
return text != NULL && text != LPSTR_TEXTCALLBACKW;
|
|
}
|
|
|
|
static inline int textlenT(LPCWSTR text, BOOL isW)
|
|
{
|
|
return !is_text(text) ? 0 :
|
|
isW ? lstrlenW(text) : lstrlenA((LPCSTR)text);
|
|
}
|
|
|
|
static inline void textcpynT(LPWSTR dest, BOOL isDestW, LPCWSTR src, BOOL isSrcW, INT max)
|
|
{
|
|
if (isDestW)
|
|
if (isSrcW) lstrcpynW(dest, src, max);
|
|
else MultiByteToWideChar(CP_ACP, 0, (LPCSTR)src, -1, dest, max);
|
|
else
|
|
if (isSrcW) WideCharToMultiByte(CP_ACP, 0, src, -1, (LPSTR)dest, max, NULL, NULL);
|
|
else lstrcpynA((LPSTR)dest, (LPCSTR)src, max);
|
|
}
|
|
|
|
static inline LPWSTR textdupTtoW(LPCWSTR text, BOOL isW)
|
|
{
|
|
LPWSTR wstr = (LPWSTR)text;
|
|
|
|
if (!isW && is_text(text))
|
|
{
|
|
INT len = MultiByteToWideChar(CP_ACP, 0, (LPCSTR)text, -1, NULL, 0);
|
|
wstr = Alloc(len * sizeof(WCHAR));
|
|
if (wstr) MultiByteToWideChar(CP_ACP, 0, (LPCSTR)text, -1, wstr, len);
|
|
}
|
|
TRACE(" wstr=%s\n", text == LPSTR_TEXTCALLBACKW ? "(callback)" : debugstr_w(wstr));
|
|
return wstr;
|
|
}
|
|
|
|
static inline void textfreeT(LPWSTR wstr, BOOL isW)
|
|
{
|
|
if (!isW && is_text(wstr)) Free (wstr);
|
|
}
|
|
|
|
/*
|
|
* dest is a pointer to a Unicode string
|
|
* src is a pointer to a string (Unicode if isW, ANSI if !isW)
|
|
*/
|
|
static BOOL textsetptrT(LPWSTR *dest, LPCWSTR src, BOOL isW)
|
|
{
|
|
BOOL bResult = TRUE;
|
|
|
|
if (src == LPSTR_TEXTCALLBACKW)
|
|
{
|
|
if (is_text(*dest)) Free(*dest);
|
|
*dest = LPSTR_TEXTCALLBACKW;
|
|
}
|
|
else
|
|
{
|
|
LPWSTR pszText = textdupTtoW(src, isW);
|
|
if (*dest == LPSTR_TEXTCALLBACKW) *dest = NULL;
|
|
bResult = Str_SetPtrW(dest, pszText);
|
|
textfreeT(pszText, isW);
|
|
}
|
|
return bResult;
|
|
}
|
|
|
|
/*
|
|
* compares a Unicode to a Unicode/ANSI text string
|
|
*/
|
|
static inline int textcmpWT(LPCWSTR aw, LPCWSTR bt, BOOL isW)
|
|
{
|
|
if (!aw) return bt ? -1 : 0;
|
|
if (!bt) return 1;
|
|
if (aw == LPSTR_TEXTCALLBACKW)
|
|
return bt == LPSTR_TEXTCALLBACKW ? 1 : -1;
|
|
if (bt != LPSTR_TEXTCALLBACKW)
|
|
{
|
|
LPWSTR bw = textdupTtoW(bt, isW);
|
|
int r = bw ? lstrcmpW(aw, bw) : 1;
|
|
textfreeT(bw, isW);
|
|
return r;
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
static inline int lstrncmpiW(LPCWSTR s1, LPCWSTR s2, int n)
|
|
{
|
|
n = min(min(n, lstrlenW(s1)), lstrlenW(s2));
|
|
return CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE, s1, n, s2, n) - CSTR_EQUAL;
|
|
}
|
|
|
|
/******** Debugging functions *****************************************/
|
|
|
|
static inline LPCSTR debugtext_t(LPCWSTR text, BOOL isW)
|
|
{
|
|
if (text == LPSTR_TEXTCALLBACKW) return "(callback)";
|
|
return isW ? debugstr_w(text) : debugstr_a((LPCSTR)text);
|
|
}
|
|
|
|
static inline LPCSTR debugtext_tn(LPCWSTR text, BOOL isW, INT n)
|
|
{
|
|
if (text == LPSTR_TEXTCALLBACKW) return "(callback)";
|
|
n = min(textlenT(text, isW), n);
|
|
return isW ? debugstr_wn(text, n) : debugstr_an((LPCSTR)text, n);
|
|
}
|
|
|
|
static char* debug_getbuf(void)
|
|
{
|
|
static int index = 0;
|
|
static char buffers[DEBUG_BUFFERS][DEBUG_BUFFER_SIZE];
|
|
return buffers[index++ % DEBUG_BUFFERS];
|
|
}
|
|
|
|
static inline const char* debugrange(const RANGE *lprng)
|
|
{
|
|
if (!lprng) return "(null)";
|
|
return wine_dbg_sprintf("[%d, %d]", lprng->lower, lprng->upper);
|
|
}
|
|
|
|
static const char* debugscrollinfo(const SCROLLINFO *pScrollInfo)
|
|
{
|
|
char* buf = debug_getbuf(), *text = buf;
|
|
int len, size = DEBUG_BUFFER_SIZE;
|
|
|
|
if (pScrollInfo == NULL) return "(null)";
|
|
len = snprintf(buf, size, "{cbSize=%d, ", pScrollInfo->cbSize);
|
|
if (len == -1) goto end; buf += len; size -= len;
|
|
if (pScrollInfo->fMask & SIF_RANGE)
|
|
len = snprintf(buf, size, "nMin=%d, nMax=%d, ", pScrollInfo->nMin, pScrollInfo->nMax);
|
|
else len = 0;
|
|
if (len == -1) goto end; buf += len; size -= len;
|
|
if (pScrollInfo->fMask & SIF_PAGE)
|
|
len = snprintf(buf, size, "nPage=%u, ", pScrollInfo->nPage);
|
|
else len = 0;
|
|
if (len == -1) goto end; buf += len; size -= len;
|
|
if (pScrollInfo->fMask & SIF_POS)
|
|
len = snprintf(buf, size, "nPos=%d, ", pScrollInfo->nPos);
|
|
else len = 0;
|
|
if (len == -1) goto end; buf += len; size -= len;
|
|
if (pScrollInfo->fMask & SIF_TRACKPOS)
|
|
len = snprintf(buf, size, "nTrackPos=%d, ", pScrollInfo->nTrackPos);
|
|
else len = 0;
|
|
if (len == -1) goto end; buf += len;
|
|
goto undo;
|
|
end:
|
|
buf = text + strlen(text);
|
|
undo:
|
|
if (buf - text > 2) { buf[-2] = '}'; buf[-1] = 0; }
|
|
return text;
|
|
}
|
|
|
|
static const char* debugnmlistview(const NMLISTVIEW *plvnm)
|
|
{
|
|
if (!plvnm) return "(null)";
|
|
return wine_dbg_sprintf("iItem=%d, iSubItem=%d, uNewState=0x%x,"
|
|
" uOldState=0x%x, uChanged=0x%x, ptAction=%s, lParam=%ld",
|
|
plvnm->iItem, plvnm->iSubItem, plvnm->uNewState, plvnm->uOldState,
|
|
plvnm->uChanged, wine_dbgstr_point(&plvnm->ptAction), plvnm->lParam);
|
|
}
|
|
|
|
static const char* debuglvitem_t(const LVITEMW *lpLVItem, BOOL isW)
|
|
{
|
|
char* buf = debug_getbuf(), *text = buf;
|
|
int len, size = DEBUG_BUFFER_SIZE;
|
|
|
|
if (lpLVItem == NULL) return "(null)";
|
|
len = snprintf(buf, size, "{iItem=%d, iSubItem=%d, ", lpLVItem->iItem, lpLVItem->iSubItem);
|
|
if (len == -1) goto end; buf += len; size -= len;
|
|
if (lpLVItem->mask & LVIF_STATE)
|
|
len = snprintf(buf, size, "state=%x, stateMask=%x, ", lpLVItem->state, lpLVItem->stateMask);
|
|
else len = 0;
|
|
if (len == -1) goto end; buf += len; size -= len;
|
|
if (lpLVItem->mask & LVIF_TEXT)
|
|
len = snprintf(buf, size, "pszText=%s, cchTextMax=%d, ", debugtext_tn(lpLVItem->pszText, isW, 80), lpLVItem->cchTextMax);
|
|
else len = 0;
|
|
if (len == -1) goto end; buf += len; size -= len;
|
|
if (lpLVItem->mask & LVIF_IMAGE)
|
|
len = snprintf(buf, size, "iImage=%d, ", lpLVItem->iImage);
|
|
else len = 0;
|
|
if (len == -1) goto end; buf += len; size -= len;
|
|
if (lpLVItem->mask & LVIF_PARAM)
|
|
len = snprintf(buf, size, "lParam=%lx, ", lpLVItem->lParam);
|
|
else len = 0;
|
|
if (len == -1) goto end; buf += len; size -= len;
|
|
if (lpLVItem->mask & LVIF_INDENT)
|
|
len = snprintf(buf, size, "iIndent=%d, ", lpLVItem->iIndent);
|
|
else len = 0;
|
|
if (len == -1) goto end; buf += len;
|
|
goto undo;
|
|
end:
|
|
buf = text + strlen(text);
|
|
undo:
|
|
if (buf - text > 2) { buf[-2] = '}'; buf[-1] = 0; }
|
|
return text;
|
|
}
|
|
|
|
static const char* debuglvcolumn_t(const LVCOLUMNW *lpColumn, BOOL isW)
|
|
{
|
|
char* buf = debug_getbuf(), *text = buf;
|
|
int len, size = DEBUG_BUFFER_SIZE;
|
|
|
|
if (lpColumn == NULL) return "(null)";
|
|
len = snprintf(buf, size, "{");
|
|
if (len == -1) goto end; buf += len; size -= len;
|
|
if (lpColumn->mask & LVCF_SUBITEM)
|
|
len = snprintf(buf, size, "iSubItem=%d, ", lpColumn->iSubItem);
|
|
else len = 0;
|
|
if (len == -1) goto end; buf += len; size -= len;
|
|
if (lpColumn->mask & LVCF_FMT)
|
|
len = snprintf(buf, size, "fmt=%x, ", lpColumn->fmt);
|
|
else len = 0;
|
|
if (len == -1) goto end; buf += len; size -= len;
|
|
if (lpColumn->mask & LVCF_WIDTH)
|
|
len = snprintf(buf, size, "cx=%d, ", lpColumn->cx);
|
|
else len = 0;
|
|
if (len == -1) goto end; buf += len; size -= len;
|
|
if (lpColumn->mask & LVCF_TEXT)
|
|
len = snprintf(buf, size, "pszText=%s, cchTextMax=%d, ", debugtext_tn(lpColumn->pszText, isW, 80), lpColumn->cchTextMax);
|
|
else len = 0;
|
|
if (len == -1) goto end; buf += len; size -= len;
|
|
if (lpColumn->mask & LVCF_IMAGE)
|
|
len = snprintf(buf, size, "iImage=%d, ", lpColumn->iImage);
|
|
else len = 0;
|
|
if (len == -1) goto end; buf += len; size -= len;
|
|
if (lpColumn->mask & LVCF_ORDER)
|
|
len = snprintf(buf, size, "iOrder=%d, ", lpColumn->iOrder);
|
|
else len = 0;
|
|
if (len == -1) goto end; buf += len;
|
|
goto undo;
|
|
end:
|
|
buf = text + strlen(text);
|
|
undo:
|
|
if (buf - text > 2) { buf[-2] = '}'; buf[-1] = 0; }
|
|
return text;
|
|
}
|
|
|
|
static const char* debuglvhittestinfo(const LVHITTESTINFO *lpht)
|
|
{
|
|
if (!lpht) return "(null)";
|
|
|
|
return wine_dbg_sprintf("{pt=%s, flags=0x%x, iItem=%d, iSubItem=%d}",
|
|
wine_dbgstr_point(&lpht->pt), lpht->flags, lpht->iItem, lpht->iSubItem);
|
|
}
|
|
|
|
/* Return the corresponding text for a given scroll value */
|
|
static inline LPCSTR debugscrollcode(int nScrollCode)
|
|
{
|
|
switch(nScrollCode)
|
|
{
|
|
case SB_LINELEFT: return "SB_LINELEFT";
|
|
case SB_LINERIGHT: return "SB_LINERIGHT";
|
|
case SB_PAGELEFT: return "SB_PAGELEFT";
|
|
case SB_PAGERIGHT: return "SB_PAGERIGHT";
|
|
case SB_THUMBPOSITION: return "SB_THUMBPOSITION";
|
|
case SB_THUMBTRACK: return "SB_THUMBTRACK";
|
|
case SB_ENDSCROLL: return "SB_ENDSCROLL";
|
|
case SB_INTERNAL: return "SB_INTERNAL";
|
|
default: return "unknown";
|
|
}
|
|
}
|
|
|
|
|
|
/******** Notification functions ************************************/
|
|
|
|
static int get_ansi_notification(UINT unicodeNotificationCode)
|
|
{
|
|
switch (unicodeNotificationCode)
|
|
{
|
|
case LVN_BEGINLABELEDITA:
|
|
case LVN_BEGINLABELEDITW: return LVN_BEGINLABELEDITA;
|
|
case LVN_ENDLABELEDITA:
|
|
case LVN_ENDLABELEDITW: return LVN_ENDLABELEDITA;
|
|
case LVN_GETDISPINFOA:
|
|
case LVN_GETDISPINFOW: return LVN_GETDISPINFOA;
|
|
case LVN_SETDISPINFOA:
|
|
case LVN_SETDISPINFOW: return LVN_SETDISPINFOA;
|
|
case LVN_ODFINDITEMA:
|
|
case LVN_ODFINDITEMW: return LVN_ODFINDITEMA;
|
|
case LVN_GETINFOTIPA:
|
|
case LVN_GETINFOTIPW: return LVN_GETINFOTIPA;
|
|
/* header forwards */
|
|
case HDN_TRACKA:
|
|
case HDN_TRACKW: return HDN_TRACKA;
|
|
case HDN_ENDTRACKA:
|
|
case HDN_ENDTRACKW: return HDN_ENDTRACKA;
|
|
case HDN_BEGINDRAG: return HDN_BEGINDRAG;
|
|
case HDN_ENDDRAG: return HDN_ENDDRAG;
|
|
case HDN_ITEMCHANGINGA:
|
|
case HDN_ITEMCHANGINGW: return HDN_ITEMCHANGINGA;
|
|
case HDN_ITEMCHANGEDA:
|
|
case HDN_ITEMCHANGEDW: return HDN_ITEMCHANGEDA;
|
|
case HDN_ITEMCLICKA:
|
|
case HDN_ITEMCLICKW: return HDN_ITEMCLICKA;
|
|
case HDN_DIVIDERDBLCLICKA:
|
|
case HDN_DIVIDERDBLCLICKW: return HDN_DIVIDERDBLCLICKA;
|
|
default: break;
|
|
}
|
|
FIXME("unknown notification %x\n", unicodeNotificationCode);
|
|
return unicodeNotificationCode;
|
|
}
|
|
|
|
/* forwards header notifications to listview parent */
|
|
static LRESULT notify_forward_header(const LISTVIEW_INFO *infoPtr, NMHEADERW *lpnmhW)
|
|
{
|
|
LPCWSTR text = NULL, filter = NULL;
|
|
LRESULT ret;
|
|
NMHEADERA *lpnmh = (NMHEADERA*) lpnmhW;
|
|
|
|
/* on unicode format exit earlier */
|
|
if (infoPtr->notifyFormat == NFR_UNICODE)
|
|
return SendMessageW(infoPtr->hwndNotify, WM_NOTIFY, lpnmh->hdr.idFrom,
|
|
(LPARAM)lpnmh);
|
|
|
|
/* header always supplies unicode notifications,
|
|
all we have to do is to convert strings to ANSI */
|
|
if (lpnmh->pitem)
|
|
{
|
|
/* convert item text */
|
|
if (lpnmh->pitem->mask & HDI_TEXT)
|
|
{
|
|
text = (LPCWSTR)lpnmh->pitem->pszText;
|
|
Str_SetPtrWtoA(&lpnmh->pitem->pszText, text);
|
|
}
|
|
/* convert filter text */
|
|
if ((lpnmh->pitem->mask & HDI_FILTER) && (lpnmh->pitem->type == HDFT_ISSTRING) &&
|
|
lpnmh->pitem->pvFilter)
|
|
{
|
|
filter = (LPCWSTR)((HD_TEXTFILTERA*)lpnmh->pitem->pvFilter)->pszText;
|
|
Str_SetPtrWtoA(&((HD_TEXTFILTERA*)lpnmh->pitem->pvFilter)->pszText, filter);
|
|
}
|
|
}
|
|
lpnmh->hdr.code = get_ansi_notification(lpnmh->hdr.code);
|
|
|
|
ret = SendMessageW(infoPtr->hwndNotify, WM_NOTIFY, lpnmh->hdr.idFrom,
|
|
(LPARAM)lpnmh);
|
|
|
|
/* cleanup */
|
|
if(text)
|
|
{
|
|
Free(lpnmh->pitem->pszText);
|
|
lpnmh->pitem->pszText = (LPSTR)text;
|
|
}
|
|
if(filter)
|
|
{
|
|
Free(((HD_TEXTFILTERA*)lpnmh->pitem->pvFilter)->pszText);
|
|
((HD_TEXTFILTERA*)lpnmh->pitem->pvFilter)->pszText = (LPSTR)filter;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static LRESULT notify_hdr(const LISTVIEW_INFO *infoPtr, INT code, LPNMHDR pnmh)
|
|
{
|
|
LRESULT result;
|
|
|
|
TRACE("(code=%d)\n", code);
|
|
|
|
pnmh->hwndFrom = infoPtr->hwndSelf;
|
|
pnmh->idFrom = GetWindowLongPtrW(infoPtr->hwndSelf, GWLP_ID);
|
|
pnmh->code = code;
|
|
result = SendMessageW(infoPtr->hwndNotify, WM_NOTIFY, pnmh->idFrom, (LPARAM)pnmh);
|
|
|
|
TRACE(" <= %ld\n", result);
|
|
|
|
return result;
|
|
}
|
|
|
|
static inline BOOL notify(const LISTVIEW_INFO *infoPtr, INT code)
|
|
{
|
|
NMHDR nmh;
|
|
HWND hwnd = infoPtr->hwndSelf;
|
|
notify_hdr(infoPtr, code, &nmh);
|
|
return IsWindow(hwnd);
|
|
}
|
|
|
|
static inline void notify_itemactivate(const LISTVIEW_INFO *infoPtr, const LVHITTESTINFO *htInfo)
|
|
{
|
|
NMITEMACTIVATE nmia;
|
|
LVITEMW item;
|
|
|
|
if (htInfo) {
|
|
nmia.uNewState = 0;
|
|
nmia.uOldState = 0;
|
|
nmia.uChanged = 0;
|
|
nmia.uKeyFlags = 0;
|
|
|
|
item.mask = LVIF_PARAM|LVIF_STATE;
|
|
item.iItem = htInfo->iItem;
|
|
item.iSubItem = 0;
|
|
item.stateMask = (UINT)-1;
|
|
if (LISTVIEW_GetItemT(infoPtr, &item, TRUE)) {
|
|
nmia.lParam = item.lParam;
|
|
nmia.uOldState = item.state;
|
|
nmia.uNewState = item.state | LVIS_ACTIVATING;
|
|
nmia.uChanged = LVIF_STATE;
|
|
}
|
|
|
|
nmia.iItem = htInfo->iItem;
|
|
nmia.iSubItem = htInfo->iSubItem;
|
|
nmia.ptAction = htInfo->pt;
|
|
|
|
if (GetKeyState(VK_SHIFT) & 0x8000) nmia.uKeyFlags |= LVKF_SHIFT;
|
|
if (GetKeyState(VK_CONTROL) & 0x8000) nmia.uKeyFlags |= LVKF_CONTROL;
|
|
if (GetKeyState(VK_MENU) & 0x8000) nmia.uKeyFlags |= LVKF_ALT;
|
|
}
|
|
notify_hdr(infoPtr, LVN_ITEMACTIVATE, (LPNMHDR)&nmia);
|
|
}
|
|
|
|
static inline LRESULT notify_listview(const LISTVIEW_INFO *infoPtr, INT code, LPNMLISTVIEW plvnm)
|
|
{
|
|
TRACE("(code=%d, plvnm=%s)\n", code, debugnmlistview(plvnm));
|
|
return notify_hdr(infoPtr, code, (LPNMHDR)plvnm);
|
|
}
|
|
|
|
/* Handles NM_DBLCLK, NM_CLICK, NM_RDBLCLK, NM_RCLICK. Only NM_RCLICK return value is used. */
|
|
static BOOL notify_click(const LISTVIEW_INFO *infoPtr, INT code, const LVHITTESTINFO *lvht)
|
|
{
|
|
NMITEMACTIVATE nmia;
|
|
LVITEMW item;
|
|
HWND hwnd = infoPtr->hwndSelf;
|
|
LRESULT ret;
|
|
|
|
TRACE("code=%d, lvht=%s\n", code, debuglvhittestinfo(lvht));
|
|
ZeroMemory(&nmia, sizeof(nmia));
|
|
nmia.iItem = lvht->iItem;
|
|
nmia.iSubItem = lvht->iSubItem;
|
|
nmia.ptAction = lvht->pt;
|
|
item.mask = LVIF_PARAM;
|
|
item.iItem = lvht->iItem;
|
|
item.iSubItem = 0;
|
|
if (LISTVIEW_GetItemT(infoPtr, &item, TRUE)) nmia.lParam = item.lParam;
|
|
ret = notify_hdr(infoPtr, code, (NMHDR*)&nmia);
|
|
return IsWindow(hwnd) && (code == NM_RCLICK ? !ret : TRUE);
|
|
}
|
|
|
|
static BOOL notify_deleteitem(const LISTVIEW_INFO *infoPtr, INT nItem)
|
|
{
|
|
NMLISTVIEW nmlv;
|
|
LVITEMW item;
|
|
HWND hwnd = infoPtr->hwndSelf;
|
|
|
|
ZeroMemory(&nmlv, sizeof (NMLISTVIEW));
|
|
nmlv.iItem = nItem;
|
|
item.mask = LVIF_PARAM;
|
|
item.iItem = nItem;
|
|
item.iSubItem = 0;
|
|
if (LISTVIEW_GetItemT(infoPtr, &item, TRUE)) nmlv.lParam = item.lParam;
|
|
notify_listview(infoPtr, LVN_DELETEITEM, &nmlv);
|
|
return IsWindow(hwnd);
|
|
}
|
|
|
|
/*
|
|
Send notification. depends on dispinfoW having same
|
|
structure as dispinfoA.
|
|
infoPtr : listview struct
|
|
code : *Unicode* notification code
|
|
pdi : dispinfo structure (can be unicode or ansi)
|
|
isW : TRUE if dispinfo is Unicode
|
|
*/
|
|
static BOOL notify_dispinfoT(const LISTVIEW_INFO *infoPtr, UINT code, LPNMLVDISPINFOW pdi, BOOL isW)
|
|
{
|
|
INT length = 0, ret_length;
|
|
LPWSTR buffer = NULL, ret_text;
|
|
BOOL return_ansi = FALSE;
|
|
BOOL return_unicode = FALSE;
|
|
BOOL ret;
|
|
|
|
if ((pdi->item.mask & LVIF_TEXT) && is_text(pdi->item.pszText))
|
|
{
|
|
return_unicode = ( isW && infoPtr->notifyFormat == NFR_ANSI);
|
|
return_ansi = (!isW && infoPtr->notifyFormat == NFR_UNICODE);
|
|
}
|
|
|
|
ret_length = pdi->item.cchTextMax;
|
|
ret_text = pdi->item.pszText;
|
|
|
|
if (return_unicode || return_ansi)
|
|
{
|
|
if (code != LVN_GETDISPINFOW)
|
|
{
|
|
length = return_ansi ?
|
|
MultiByteToWideChar(CP_ACP, 0, (LPCSTR)pdi->item.pszText, -1, NULL, 0):
|
|
WideCharToMultiByte(CP_ACP, 0, pdi->item.pszText, -1, NULL, 0, NULL, NULL);
|
|
}
|
|
else
|
|
{
|
|
length = pdi->item.cchTextMax;
|
|
*pdi->item.pszText = 0; /* make sure we don't process garbage */
|
|
}
|
|
|
|
buffer = Alloc( (return_ansi ? sizeof(WCHAR) : sizeof(CHAR)) * length);
|
|
if (!buffer) return FALSE;
|
|
|
|
if (return_ansi)
|
|
MultiByteToWideChar(CP_ACP, 0, (LPCSTR)pdi->item.pszText, -1,
|
|
buffer, length);
|
|
else
|
|
WideCharToMultiByte(CP_ACP, 0, pdi->item.pszText, -1, (LPSTR) buffer,
|
|
length, NULL, NULL);
|
|
|
|
pdi->item.pszText = buffer;
|
|
pdi->item.cchTextMax = length;
|
|
}
|
|
|
|
if (infoPtr->notifyFormat == NFR_ANSI)
|
|
code = get_ansi_notification(code);
|
|
|
|
TRACE(" pdi->item=%s\n", debuglvitem_t(&pdi->item, infoPtr->notifyFormat != NFR_ANSI));
|
|
ret = notify_hdr(infoPtr, code, &pdi->hdr);
|
|
TRACE(" resulting code=%d\n", pdi->hdr.code);
|
|
|
|
if (return_ansi || return_unicode)
|
|
{
|
|
if (return_ansi && (pdi->hdr.code == LVN_GETDISPINFOA))
|
|
{
|
|
strcpy((char*)ret_text, (char*)pdi->item.pszText);
|
|
}
|
|
else if (return_unicode && (pdi->hdr.code == LVN_GETDISPINFOW))
|
|
{
|
|
strcpyW(ret_text, pdi->item.pszText);
|
|
}
|
|
else if (return_ansi) /* note : pointer can be changed by app ! */
|
|
{
|
|
WideCharToMultiByte(CP_ACP, 0, pdi->item.pszText, -1, (LPSTR) ret_text,
|
|
ret_length, NULL, NULL);
|
|
}
|
|
else
|
|
MultiByteToWideChar(CP_ACP, 0, (LPSTR) pdi->item.pszText, -1,
|
|
ret_text, ret_length);
|
|
|
|
pdi->item.pszText = ret_text; /* restores our buffer */
|
|
pdi->item.cchTextMax = ret_length;
|
|
|
|
Free(buffer);
|
|
return ret;
|
|
}
|
|
|
|
/* if dipsinfo holder changed notification code then convert */
|
|
if (!isW && (pdi->hdr.code == LVN_GETDISPINFOW) && (pdi->item.mask & LVIF_TEXT))
|
|
{
|
|
length = WideCharToMultiByte(CP_ACP, 0, pdi->item.pszText, -1, NULL, 0, NULL, NULL);
|
|
|
|
buffer = Alloc(length * sizeof(CHAR));
|
|
if (!buffer) return FALSE;
|
|
|
|
WideCharToMultiByte(CP_ACP, 0, pdi->item.pszText, -1, (LPSTR) buffer,
|
|
ret_length, NULL, NULL);
|
|
|
|
strcpy((LPSTR)pdi->item.pszText, (LPSTR)buffer);
|
|
Free(buffer);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static void customdraw_fill(NMLVCUSTOMDRAW *lpnmlvcd, const LISTVIEW_INFO *infoPtr, HDC hdc,
|
|
const RECT *rcBounds, const LVITEMW *lplvItem)
|
|
{
|
|
ZeroMemory(lpnmlvcd, sizeof(NMLVCUSTOMDRAW));
|
|
lpnmlvcd->nmcd.hdc = hdc;
|
|
lpnmlvcd->nmcd.rc = *rcBounds;
|
|
lpnmlvcd->clrTextBk = infoPtr->clrTextBk;
|
|
lpnmlvcd->clrText = infoPtr->clrText;
|
|
if (!lplvItem) return;
|
|
lpnmlvcd->nmcd.dwItemSpec = lplvItem->iItem + 1;
|
|
lpnmlvcd->iSubItem = lplvItem->iSubItem;
|
|
if (lplvItem->state & LVIS_SELECTED) lpnmlvcd->nmcd.uItemState |= CDIS_SELECTED;
|
|
if (lplvItem->state & LVIS_FOCUSED) lpnmlvcd->nmcd.uItemState |= CDIS_FOCUS;
|
|
if (lplvItem->iItem == infoPtr->nHotItem) lpnmlvcd->nmcd.uItemState |= CDIS_HOT;
|
|
lpnmlvcd->nmcd.lItemlParam = lplvItem->lParam;
|
|
}
|
|
|
|
static inline DWORD notify_customdraw (const LISTVIEW_INFO *infoPtr, DWORD dwDrawStage, NMLVCUSTOMDRAW *lpnmlvcd)
|
|
{
|
|
BOOL isForItem = (lpnmlvcd->nmcd.dwItemSpec != 0);
|
|
DWORD result;
|
|
|
|
lpnmlvcd->nmcd.dwDrawStage = dwDrawStage;
|
|
if (isForItem) lpnmlvcd->nmcd.dwDrawStage |= CDDS_ITEM;
|
|
if (lpnmlvcd->iSubItem) lpnmlvcd->nmcd.dwDrawStage |= CDDS_SUBITEM;
|
|
if (isForItem) lpnmlvcd->nmcd.dwItemSpec--;
|
|
result = notify_hdr(infoPtr, NM_CUSTOMDRAW, &lpnmlvcd->nmcd.hdr);
|
|
if (isForItem) lpnmlvcd->nmcd.dwItemSpec++;
|
|
return result;
|
|
}
|
|
|
|
static void prepaint_setup (const LISTVIEW_INFO *infoPtr, HDC hdc, NMLVCUSTOMDRAW *lpnmlvcd, BOOL SubItem)
|
|
{
|
|
if (lpnmlvcd->clrTextBk == CLR_DEFAULT)
|
|
lpnmlvcd->clrTextBk = comctl32_color.clrWindow;
|
|
if (lpnmlvcd->clrText == CLR_DEFAULT)
|
|
lpnmlvcd->clrText = comctl32_color.clrWindowText;
|
|
|
|
/* apparently, for selected items, we have to override the returned values */
|
|
if (!SubItem)
|
|
{
|
|
if (lpnmlvcd->nmcd.uItemState & CDIS_SELECTED)
|
|
{
|
|
if (infoPtr->bFocus)
|
|
{
|
|
lpnmlvcd->clrTextBk = comctl32_color.clrHighlight;
|
|
lpnmlvcd->clrText = comctl32_color.clrHighlightText;
|
|
}
|
|
else if (infoPtr->dwStyle & LVS_SHOWSELALWAYS)
|
|
{
|
|
lpnmlvcd->clrTextBk = comctl32_color.clr3dFace;
|
|
lpnmlvcd->clrText = comctl32_color.clrBtnText;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Set the text attributes */
|
|
if (lpnmlvcd->clrTextBk != CLR_NONE)
|
|
{
|
|
SetBkMode(hdc, OPAQUE);
|
|
SetBkColor(hdc,lpnmlvcd->clrTextBk);
|
|
}
|
|
else
|
|
SetBkMode(hdc, TRANSPARENT);
|
|
SetTextColor(hdc, lpnmlvcd->clrText);
|
|
}
|
|
|
|
static inline DWORD notify_postpaint (const LISTVIEW_INFO *infoPtr, NMLVCUSTOMDRAW *lpnmlvcd)
|
|
{
|
|
return notify_customdraw(infoPtr, CDDS_POSTPAINT, lpnmlvcd);
|
|
}
|
|
|
|
/* returns TRUE when repaint needed, FALSE otherwise */
|
|
static BOOL notify_measureitem(LISTVIEW_INFO *infoPtr)
|
|
{
|
|
MEASUREITEMSTRUCT mis;
|
|
mis.CtlType = ODT_LISTVIEW;
|
|
mis.CtlID = GetWindowLongPtrW(infoPtr->hwndSelf, GWLP_ID);
|
|
mis.itemID = -1;
|
|
mis.itemWidth = 0;
|
|
mis.itemData = 0;
|
|
mis.itemHeight= infoPtr->nItemHeight;
|
|
SendMessageW(infoPtr->hwndNotify, WM_MEASUREITEM, mis.CtlID, (LPARAM)&mis);
|
|
if (infoPtr->nItemHeight != max(mis.itemHeight, 1))
|
|
{
|
|
infoPtr->nMeasureItemHeight = infoPtr->nItemHeight = max(mis.itemHeight, 1);
|
|
return TRUE;
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
/******** Item iterator functions **********************************/
|
|
|
|
static RANGES ranges_create(int count);
|
|
static void ranges_destroy(RANGES ranges);
|
|
static BOOL ranges_add(RANGES ranges, RANGE range);
|
|
static BOOL ranges_del(RANGES ranges, RANGE range);
|
|
static void ranges_dump(RANGES ranges);
|
|
|
|
static inline BOOL ranges_additem(RANGES ranges, INT nItem)
|
|
{
|
|
RANGE range = { nItem, nItem + 1 };
|
|
|
|
return ranges_add(ranges, range);
|
|
}
|
|
|
|
static inline BOOL ranges_delitem(RANGES ranges, INT nItem)
|
|
{
|
|
RANGE range = { nItem, nItem + 1 };
|
|
|
|
return ranges_del(ranges, range);
|
|
}
|
|
|
|
/***
|
|
* ITERATOR DOCUMENTATION
|
|
*
|
|
* The iterator functions allow for easy, and convenient iteration
|
|
* over items of interest in the list. Typically, you create a
|
|
* iterator, use it, and destroy it, as such:
|
|
* ITERATOR i;
|
|
*
|
|
* iterator_xxxitems(&i, ...);
|
|
* while (iterator_{prev,next}(&i)
|
|
* {
|
|
* //code which uses i.nItem
|
|
* }
|
|
* iterator_destroy(&i);
|
|
*
|
|
* where xxx is either: framed, or visible.
|
|
* Note that it is important that the code destroys the iterator
|
|
* after it's done with it, as the creation of the iterator may
|
|
* allocate memory, which thus needs to be freed.
|
|
*
|
|
* You can iterate both forwards, and backwards through the list,
|
|
* by using iterator_next or iterator_prev respectively.
|
|
*
|
|
* Lower numbered items are draw on top of higher number items in
|
|
* LVS_ICON, and LVS_SMALLICON (which are the only modes where
|
|
* items may overlap). So, to test items, you should use
|
|
* iterator_next
|
|
* which lists the items top to bottom (in Z-order).
|
|
* For drawing items, you should use
|
|
* iterator_prev
|
|
* which lists the items bottom to top (in Z-order).
|
|
* If you keep iterating over the items after the end-of-items
|
|
* marker (-1) is returned, the iterator will start from the
|
|
* beginning. Typically, you don't need to test for -1,
|
|
* because iterator_{next,prev} will return TRUE if more items
|
|
* are to be iterated over, or FALSE otherwise.
|
|
*
|
|
* Note: the iterator is defined to be bidirectional. That is,
|
|
* any number of prev followed by any number of next, or
|
|
* five versa, should leave the iterator at the same item:
|
|
* prev * n, next * n = next * n, prev * n
|
|
*
|
|
* The iterator has a notion of an out-of-order, special item,
|
|
* which sits at the start of the list. This is used in
|
|
* LVS_ICON, and LVS_SMALLICON mode to handle the focused item,
|
|
* which needs to be first, as it may overlap other items.
|
|
*
|
|
* The code is a bit messy because we have:
|
|
* - a special item to deal with
|
|
* - simple range, or composite range
|
|
* - empty range.
|
|
* If you find bugs, or want to add features, please make sure you
|
|
* always check/modify *both* iterator_prev, and iterator_next.
|
|
*/
|
|
|
|
/****
|
|
* This function iterates through the items in increasing order,
|
|
* but prefixed by the special item, then -1. That is:
|
|
* special, 1, 2, 3, ..., n, -1.
|
|
* Each item is listed only once.
|
|
*/
|
|
static inline BOOL iterator_next(ITERATOR* i)
|
|
{
|
|
if (i->nItem == -1)
|
|
{
|
|
i->nItem = i->nSpecial;
|
|
if (i->nItem != -1) return TRUE;
|
|
}
|
|
if (i->nItem == i->nSpecial)
|
|
{
|
|
if (i->ranges) i->index = 0;
|
|
goto pickarange;
|
|
}
|
|
|
|
i->nItem++;
|
|
testitem:
|
|
if (i->nItem == i->nSpecial) i->nItem++;
|
|
if (i->nItem < i->range.upper) return TRUE;
|
|
|
|
pickarange:
|
|
if (i->ranges)
|
|
{
|
|
if (i->index < DPA_GetPtrCount(i->ranges->hdpa))
|
|
i->range = *(RANGE*)DPA_GetPtr(i->ranges->hdpa, i->index++);
|
|
else goto end;
|
|
}
|
|
else if (i->nItem >= i->range.upper) goto end;
|
|
|
|
i->nItem = i->range.lower;
|
|
if (i->nItem >= 0) goto testitem;
|
|
end:
|
|
i->nItem = -1;
|
|
return FALSE;
|
|
}
|
|
|
|
/****
|
|
* This function iterates through the items in decreasing order,
|
|
* followed by the special item, then -1. That is:
|
|
* n, n-1, ..., 3, 2, 1, special, -1.
|
|
* Each item is listed only once.
|
|
*/
|
|
static inline BOOL iterator_prev(ITERATOR* i)
|
|
{
|
|
BOOL start = FALSE;
|
|
|
|
if (i->nItem == -1)
|
|
{
|
|
start = TRUE;
|
|
if (i->ranges) i->index = DPA_GetPtrCount(i->ranges->hdpa);
|
|
goto pickarange;
|
|
}
|
|
if (i->nItem == i->nSpecial)
|
|
{
|
|
i->nItem = -1;
|
|
return FALSE;
|
|
}
|
|
|
|
testitem:
|
|
i->nItem--;
|
|
if (i->nItem == i->nSpecial) i->nItem--;
|
|
if (i->nItem >= i->range.lower) return TRUE;
|
|
|
|
pickarange:
|
|
if (i->ranges)
|
|
{
|
|
if (i->index > 0)
|
|
i->range = *(RANGE*)DPA_GetPtr(i->ranges->hdpa, --i->index);
|
|
else goto end;
|
|
}
|
|
else if (!start && i->nItem < i->range.lower) goto end;
|
|
|
|
i->nItem = i->range.upper;
|
|
if (i->nItem > 0) goto testitem;
|
|
end:
|
|
return (i->nItem = i->nSpecial) != -1;
|
|
}
|
|
|
|
static RANGE iterator_range(const ITERATOR *i)
|
|
{
|
|
RANGE range;
|
|
|
|
if (!i->ranges) return i->range;
|
|
|
|
if (DPA_GetPtrCount(i->ranges->hdpa) > 0)
|
|
{
|
|
range.lower = (*(RANGE*)DPA_GetPtr(i->ranges->hdpa, 0)).lower;
|
|
range.upper = (*(RANGE*)DPA_GetPtr(i->ranges->hdpa, DPA_GetPtrCount(i->ranges->hdpa) - 1)).upper;
|
|
}
|
|
else range.lower = range.upper = 0;
|
|
|
|
return range;
|
|
}
|
|
|
|
/***
|
|
* Releases resources associated with this iterator.
|
|
*/
|
|
static inline void iterator_destroy(const ITERATOR *i)
|
|
{
|
|
ranges_destroy(i->ranges);
|
|
}
|
|
|
|
/***
|
|
* Create an empty iterator.
|
|
*/
|
|
static inline BOOL iterator_empty(ITERATOR* i)
|
|
{
|
|
ZeroMemory(i, sizeof(*i));
|
|
i->nItem = i->nSpecial = i->range.lower = i->range.upper = -1;
|
|
return TRUE;
|
|
}
|
|
|
|
/***
|
|
* Create an iterator over a range.
|
|
*/
|
|
static inline BOOL iterator_rangeitems(ITERATOR* i, RANGE range)
|
|
{
|
|
iterator_empty(i);
|
|
i->range = range;
|
|
return TRUE;
|
|
}
|
|
|
|
/***
|
|
* Create an iterator over a bunch of ranges.
|
|
* Please note that the iterator will take ownership of the ranges,
|
|
* and will free them upon destruction.
|
|
*/
|
|
static inline BOOL iterator_rangesitems(ITERATOR* i, RANGES ranges)
|
|
{
|
|
iterator_empty(i);
|
|
i->ranges = ranges;
|
|
return TRUE;
|
|
}
|
|
|
|
/***
|
|
* Creates an iterator over the items which intersect frame.
|
|
* Uses absolute coordinates rather than compensating for the current offset.
|
|
*/
|
|
static BOOL iterator_frameditems_absolute(ITERATOR* i, const LISTVIEW_INFO* infoPtr, const RECT *frame)
|
|
{
|
|
RECT rcItem, rcTemp;
|
|
|
|
/* in case we fail, we want to return an empty iterator */
|
|
if (!iterator_empty(i)) return FALSE;
|
|
|
|
TRACE("(frame=%s)\n", wine_dbgstr_rect(frame));
|
|
|
|
if (infoPtr->uView == LV_VIEW_ICON || infoPtr->uView == LV_VIEW_SMALLICON)
|
|
{
|
|
INT nItem;
|
|
|
|
if (infoPtr->uView == LV_VIEW_ICON && infoPtr->nFocusedItem != -1)
|
|
{
|
|
LISTVIEW_GetItemBox(infoPtr, infoPtr->nFocusedItem, &rcItem);
|
|
if (IntersectRect(&rcTemp, &rcItem, frame))
|
|
i->nSpecial = infoPtr->nFocusedItem;
|
|
}
|
|
if (!(iterator_rangesitems(i, ranges_create(50)))) return FALSE;
|
|
/* to do better here, we need to have PosX, and PosY sorted */
|
|
TRACE("building icon ranges:\n");
|
|
for (nItem = 0; nItem < infoPtr->nItemCount; nItem++)
|
|
{
|
|
rcItem.left = (LONG_PTR)DPA_GetPtr(infoPtr->hdpaPosX, nItem);
|
|
rcItem.top = (LONG_PTR)DPA_GetPtr(infoPtr->hdpaPosY, nItem);
|
|
rcItem.right = rcItem.left + infoPtr->nItemWidth;
|
|
rcItem.bottom = rcItem.top + infoPtr->nItemHeight;
|
|
if (IntersectRect(&rcTemp, &rcItem, frame))
|
|
ranges_additem(i->ranges, nItem);
|
|
}
|
|
return TRUE;
|
|
}
|
|
else if (infoPtr->uView == LV_VIEW_DETAILS)
|
|
{
|
|
RANGE range;
|
|
|
|
if (frame->left >= infoPtr->nItemWidth) return TRUE;
|
|
if (frame->top >= infoPtr->nItemHeight * infoPtr->nItemCount) return TRUE;
|
|
|
|
range.lower = max(frame->top / infoPtr->nItemHeight, 0);
|
|
range.upper = min((frame->bottom - 1) / infoPtr->nItemHeight, infoPtr->nItemCount - 1) + 1;
|
|
if (range.upper <= range.lower) return TRUE;
|
|
if (!iterator_rangeitems(i, range)) return FALSE;
|
|
TRACE(" report=%s\n", debugrange(&i->range));
|
|
}
|
|
else
|
|
{
|
|
INT nPerCol = max((infoPtr->rcList.bottom - infoPtr->rcList.top) / infoPtr->nItemHeight, 1);
|
|
INT nFirstRow = max(frame->top / infoPtr->nItemHeight, 0);
|
|
INT nLastRow = min((frame->bottom - 1) / infoPtr->nItemHeight, nPerCol - 1);
|
|
INT nFirstCol;
|
|
INT nLastCol;
|
|
INT lower;
|
|
RANGE item_range;
|
|
INT nCol;
|
|
|
|
if (infoPtr->nItemWidth)
|
|
{
|
|
nFirstCol = max(frame->left / infoPtr->nItemWidth, 0);
|
|
nLastCol = min((frame->right - 1) / infoPtr->nItemWidth, (infoPtr->nItemCount + nPerCol - 1) / nPerCol);
|
|
}
|
|
else
|
|
{
|
|
nFirstCol = max(frame->left, 0);
|
|
nLastCol = min(frame->right - 1, (infoPtr->nItemCount + nPerCol - 1) / nPerCol);
|
|
}
|
|
|
|
lower = nFirstCol * nPerCol + nFirstRow;
|
|
|
|
TRACE("nPerCol=%d, nFirstRow=%d, nLastRow=%d, nFirstCol=%d, nLastCol=%d, lower=%d\n",
|
|
nPerCol, nFirstRow, nLastRow, nFirstCol, nLastCol, lower);
|
|
|
|
if (nLastCol < nFirstCol || nLastRow < nFirstRow) return TRUE;
|
|
|
|
if (!(iterator_rangesitems(i, ranges_create(nLastCol - nFirstCol + 1)))) return FALSE;
|
|
TRACE("building list ranges:\n");
|
|
for (nCol = nFirstCol; nCol <= nLastCol; nCol++)
|
|
{
|
|
item_range.lower = nCol * nPerCol + nFirstRow;
|
|
if(item_range.lower >= infoPtr->nItemCount) break;
|
|
item_range.upper = min(nCol * nPerCol + nLastRow + 1, infoPtr->nItemCount);
|
|
TRACE(" list=%s\n", debugrange(&item_range));
|
|
ranges_add(i->ranges, item_range);
|
|
}
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
/***
|
|
* Creates an iterator over the items which intersect lprc.
|
|
*/
|
|
static BOOL iterator_frameditems(ITERATOR* i, const LISTVIEW_INFO* infoPtr, const RECT *lprc)
|
|
{
|
|
RECT frame = *lprc;
|
|
POINT Origin;
|
|
|
|
TRACE("(lprc=%s)\n", wine_dbgstr_rect(lprc));
|
|
|
|
LISTVIEW_GetOrigin(infoPtr, &Origin);
|
|
OffsetRect(&frame, -Origin.x, -Origin.y);
|
|
|
|
return iterator_frameditems_absolute(i, infoPtr, &frame);
|
|
}
|
|
|
|
/***
|
|
* Creates an iterator over the items which intersect the visible region of hdc.
|
|
*/
|
|
static BOOL iterator_visibleitems(ITERATOR *i, const LISTVIEW_INFO *infoPtr, HDC hdc)
|
|
{
|
|
POINT Origin, Position;
|
|
RECT rcItem, rcClip;
|
|
INT rgntype;
|
|
|
|
rgntype = GetClipBox(hdc, &rcClip);
|
|
if (rgntype == NULLREGION) return iterator_empty(i);
|
|
if (!iterator_frameditems(i, infoPtr, &rcClip)) return FALSE;
|
|
if (rgntype == SIMPLEREGION) return TRUE;
|
|
|
|
/* first deal with the special item */
|
|
if (i->nSpecial != -1)
|
|
{
|
|
LISTVIEW_GetItemBox(infoPtr, i->nSpecial, &rcItem);
|
|
if (!RectVisible(hdc, &rcItem)) i->nSpecial = -1;
|
|
}
|
|
|
|
/* if we can't deal with the region, we'll just go with the simple range */
|
|
LISTVIEW_GetOrigin(infoPtr, &Origin);
|
|
TRACE("building visible range:\n");
|
|
if (!i->ranges && i->range.lower < i->range.upper)
|
|
{
|
|
if (!(i->ranges = ranges_create(50))) return TRUE;
|
|
if (!ranges_add(i->ranges, i->range))
|
|
{
|
|
ranges_destroy(i->ranges);
|
|
i->ranges = 0;
|
|
return TRUE;
|
|
}
|
|
}
|
|
|
|
/* now delete the invisible items from the list */
|
|
while(iterator_next(i))
|
|
{
|
|
LISTVIEW_GetItemOrigin(infoPtr, i->nItem, &Position);
|
|
rcItem.left = (infoPtr->uView == LV_VIEW_DETAILS) ? Origin.x : Position.x + Origin.x;
|
|
rcItem.top = Position.y + Origin.y;
|
|
rcItem.right = rcItem.left + infoPtr->nItemWidth;
|
|
rcItem.bottom = rcItem.top + infoPtr->nItemHeight;
|
|
if (!RectVisible(hdc, &rcItem))
|
|
ranges_delitem(i->ranges, i->nItem);
|
|
}
|
|
/* the iterator should restart on the next iterator_next */
|
|
TRACE("done\n");
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
/* Remove common elements from two iterators */
|
|
/* Passed iterators have to point on the first elements */
|
|
static BOOL iterator_remove_common_items(ITERATOR *iter1, ITERATOR *iter2)
|
|
{
|
|
if(!iter1->ranges || !iter2->ranges) {
|
|
int lower, upper;
|
|
|
|
if(iter1->ranges || iter2->ranges ||
|
|
(iter1->range.lower<iter2->range.lower && iter1->range.upper>iter2->range.upper) ||
|
|
(iter1->range.lower>iter2->range.lower && iter1->range.upper<iter2->range.upper)) {
|
|
ERR("result is not a one range iterator\n");
|
|
return FALSE;
|
|
}
|
|
|
|
if(iter1->range.lower==-1 || iter2->range.lower==-1)
|
|
return TRUE;
|
|
|
|
lower = iter1->range.lower;
|
|
upper = iter1->range.upper;
|
|
|
|
if(lower < iter2->range.lower)
|
|
iter1->range.upper = iter2->range.lower;
|
|
else if(upper > iter2->range.upper)
|
|
iter1->range.lower = iter2->range.upper;
|
|
else
|
|
iter1->range.lower = iter1->range.upper = -1;
|
|
|
|
if(iter2->range.lower < lower)
|
|
iter2->range.upper = lower;
|
|
else if(iter2->range.upper > upper)
|
|
iter2->range.lower = upper;
|
|
else
|
|
iter2->range.lower = iter2->range.upper = -1;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
iterator_next(iter1);
|
|
iterator_next(iter2);
|
|
|
|
while(1) {
|
|
if(iter1->nItem==-1 || iter2->nItem==-1)
|
|
break;
|
|
|
|
if(iter1->nItem == iter2->nItem) {
|
|
int delete = iter1->nItem;
|
|
|
|
iterator_prev(iter1);
|
|
iterator_prev(iter2);
|
|
ranges_delitem(iter1->ranges, delete);
|
|
ranges_delitem(iter2->ranges, delete);
|
|
iterator_next(iter1);
|
|
iterator_next(iter2);
|
|
} else if(iter1->nItem > iter2->nItem)
|
|
iterator_next(iter2);
|
|
else
|
|
iterator_next(iter1);
|
|
}
|
|
|
|
iter1->nItem = iter1->range.lower = iter1->range.upper = -1;
|
|
iter2->nItem = iter2->range.lower = iter2->range.upper = -1;
|
|
return TRUE;
|
|
}
|
|
|
|
/******** Misc helper functions ************************************/
|
|
|
|
static inline LRESULT CallWindowProcT(WNDPROC proc, HWND hwnd, UINT uMsg,
|
|
WPARAM wParam, LPARAM lParam, BOOL isW)
|
|
{
|
|
if (isW) return CallWindowProcW(proc, hwnd, uMsg, wParam, lParam);
|
|
else return CallWindowProcA(proc, hwnd, uMsg, wParam, lParam);
|
|
}
|
|
|
|
static inline BOOL is_autoarrange(const LISTVIEW_INFO *infoPtr)
|
|
{
|
|
return ((infoPtr->dwStyle & LVS_AUTOARRANGE) || infoPtr->bAutoarrange) &&
|
|
(infoPtr->uView == LV_VIEW_ICON || infoPtr->uView == LV_VIEW_SMALLICON);
|
|
}
|
|
|
|
static void toggle_checkbox_state(LISTVIEW_INFO *infoPtr, INT nItem)
|
|
{
|
|
DWORD state = STATEIMAGEINDEX(LISTVIEW_GetItemState(infoPtr, nItem, LVIS_STATEIMAGEMASK));
|
|
if(state == 1 || state == 2)
|
|
{
|
|
LVITEMW lvitem;
|
|
state ^= 3;
|
|
lvitem.state = INDEXTOSTATEIMAGEMASK(state);
|
|
lvitem.stateMask = LVIS_STATEIMAGEMASK;
|
|
LISTVIEW_SetItemState(infoPtr, nItem, &lvitem);
|
|
}
|
|
}
|
|
|
|
/* this should be called after window style got updated,
|
|
it used to reset view state to match current window style */
|
|
static inline void map_style_view(LISTVIEW_INFO *infoPtr)
|
|
{
|
|
switch (infoPtr->dwStyle & LVS_TYPEMASK)
|
|
{
|
|
case LVS_ICON:
|
|
infoPtr->uView = LV_VIEW_ICON;
|
|
break;
|
|
case LVS_REPORT:
|
|
infoPtr->uView = LV_VIEW_DETAILS;
|
|
break;
|
|
case LVS_SMALLICON:
|
|
infoPtr->uView = LV_VIEW_SMALLICON;
|
|
break;
|
|
case LVS_LIST:
|
|
infoPtr->uView = LV_VIEW_LIST;
|
|
}
|
|
}
|
|
|
|
/* computes next item id value */
|
|
static DWORD get_next_itemid(const LISTVIEW_INFO *infoPtr)
|
|
{
|
|
INT count = DPA_GetPtrCount(infoPtr->hdpaItemIds);
|
|
|
|
if (count > 0)
|
|
{
|
|
ITEM_ID *lpID = DPA_GetPtr(infoPtr->hdpaItemIds, count - 1);
|
|
return lpID->id + 1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/******** Internal API functions ************************************/
|
|
|
|
static inline COLUMN_INFO * LISTVIEW_GetColumnInfo(const LISTVIEW_INFO *infoPtr, INT nSubItem)
|
|
{
|
|
static COLUMN_INFO mainItem;
|
|
|
|
if (nSubItem == 0 && DPA_GetPtrCount(infoPtr->hdpaColumns) == 0) return &mainItem;
|
|
assert (nSubItem >= 0 && nSubItem < DPA_GetPtrCount(infoPtr->hdpaColumns));
|
|
|
|
/* update cached column rectangles */
|
|
if (infoPtr->colRectsDirty)
|
|
{
|
|
COLUMN_INFO *info;
|
|
LISTVIEW_INFO *Ptr = (LISTVIEW_INFO*)infoPtr;
|
|
INT i;
|
|
|
|
for (i = 0; i < DPA_GetPtrCount(infoPtr->hdpaColumns); i++) {
|
|
info = DPA_GetPtr(infoPtr->hdpaColumns, i);
|
|
SendMessageW(infoPtr->hwndHeader, HDM_GETITEMRECT, i, (LPARAM)&info->rcHeader);
|
|
}
|
|
Ptr->colRectsDirty = FALSE;
|
|
}
|
|
|
|
return DPA_GetPtr(infoPtr->hdpaColumns, nSubItem);
|
|
}
|
|
|
|
static INT LISTVIEW_CreateHeader(LISTVIEW_INFO *infoPtr)
|
|
{
|
|
DWORD dFlags = WS_CHILD | HDS_HORZ | HDS_FULLDRAG | HDS_DRAGDROP;
|
|
HINSTANCE hInst;
|
|
|
|
if (infoPtr->hwndHeader) return 0;
|
|
|
|
TRACE("Creating header for list %p\n", infoPtr->hwndSelf);
|
|
|
|
/* setup creation flags */
|
|
dFlags |= (LVS_NOSORTHEADER & infoPtr->dwStyle) ? 0 : HDS_BUTTONS;
|
|
dFlags |= (LVS_NOCOLUMNHEADER & infoPtr->dwStyle) ? HDS_HIDDEN : 0;
|
|
|
|
hInst = (HINSTANCE)GetWindowLongPtrW(infoPtr->hwndSelf, GWLP_HINSTANCE);
|
|
|
|
/* create header */
|
|
infoPtr->hwndHeader = CreateWindowW(WC_HEADERW, NULL, dFlags,
|
|
0, 0, 0, 0, infoPtr->hwndSelf, NULL, hInst, NULL);
|
|
if (!infoPtr->hwndHeader) return -1;
|
|
|
|
/* set header unicode format */
|
|
SendMessageW(infoPtr->hwndHeader, HDM_SETUNICODEFORMAT, TRUE, 0);
|
|
|
|
/* set header font */
|
|
SendMessageW(infoPtr->hwndHeader, WM_SETFONT, (WPARAM)infoPtr->hFont, TRUE);
|
|
|
|
/* set header image list */
|
|
if (infoPtr->himlSmall)
|
|
SendMessageW(infoPtr->hwndHeader, HDM_SETIMAGELIST, 0, (LPARAM)infoPtr->himlSmall);
|
|
|
|
LISTVIEW_UpdateSize(infoPtr);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static inline void LISTVIEW_GetHeaderRect(const LISTVIEW_INFO *infoPtr, INT nSubItem, LPRECT lprc)
|
|
{
|
|
*lprc = LISTVIEW_GetColumnInfo(infoPtr, nSubItem)->rcHeader;
|
|
}
|
|
|
|
static inline BOOL LISTVIEW_IsHeaderEnabled(const LISTVIEW_INFO *infoPtr)
|
|
{
|
|
return (infoPtr->uView == LV_VIEW_DETAILS ||
|
|
infoPtr->dwLvExStyle & LVS_EX_HEADERINALLVIEWS) &&
|
|
!(infoPtr->dwStyle & LVS_NOCOLUMNHEADER);
|
|
}
|
|
|
|
static inline BOOL LISTVIEW_GetItemW(const LISTVIEW_INFO *infoPtr, LPLVITEMW lpLVItem)
|
|
{
|
|
return LISTVIEW_GetItemT(infoPtr, lpLVItem, TRUE);
|
|
}
|
|
|
|
/* used to handle collapse main item column case */
|
|
static inline BOOL LISTVIEW_DrawFocusRect(const LISTVIEW_INFO *infoPtr, HDC hdc)
|
|
{
|
|
return (infoPtr->rcFocus.left < infoPtr->rcFocus.right) ?
|
|
DrawFocusRect(hdc, &infoPtr->rcFocus) : FALSE;
|
|
}
|
|
|
|
/* Listview invalidation functions: use _only_ these functions to invalidate */
|
|
|
|
static inline BOOL is_redrawing(const LISTVIEW_INFO *infoPtr)
|
|
{
|
|
return infoPtr->bRedraw;
|
|
}
|
|
|
|
static inline void LISTVIEW_InvalidateRect(const LISTVIEW_INFO *infoPtr, const RECT* rect)
|
|
{
|
|
if(!is_redrawing(infoPtr)) return;
|
|
TRACE(" invalidating rect=%s\n", wine_dbgstr_rect(rect));
|
|
InvalidateRect(infoPtr->hwndSelf, rect, TRUE);
|
|
}
|
|
|
|
static inline void LISTVIEW_InvalidateItem(const LISTVIEW_INFO *infoPtr, INT nItem)
|
|
{
|
|
RECT rcBox;
|
|
|
|
if(!is_redrawing(infoPtr)) return;
|
|
LISTVIEW_GetItemBox(infoPtr, nItem, &rcBox);
|
|
LISTVIEW_InvalidateRect(infoPtr, &rcBox);
|
|
}
|
|
|
|
static inline void LISTVIEW_InvalidateSubItem(const LISTVIEW_INFO *infoPtr, INT nItem, INT nSubItem)
|
|
{
|
|
POINT Origin, Position;
|
|
RECT rcBox;
|
|
|
|
if(!is_redrawing(infoPtr)) return;
|
|
assert (infoPtr->uView == LV_VIEW_DETAILS);
|
|
LISTVIEW_GetOrigin(infoPtr, &Origin);
|
|
LISTVIEW_GetItemOrigin(infoPtr, nItem, &Position);
|
|
LISTVIEW_GetHeaderRect(infoPtr, nSubItem, &rcBox);
|
|
rcBox.top = 0;
|
|
rcBox.bottom = infoPtr->nItemHeight;
|
|
OffsetRect(&rcBox, Origin.x + Position.x, Origin.y + Position.y);
|
|
LISTVIEW_InvalidateRect(infoPtr, &rcBox);
|
|
}
|
|
|
|
static inline void LISTVIEW_InvalidateList(const LISTVIEW_INFO *infoPtr)
|
|
{
|
|
LISTVIEW_InvalidateRect(infoPtr, NULL);
|
|
}
|
|
|
|
static inline void LISTVIEW_InvalidateColumn(const LISTVIEW_INFO *infoPtr, INT nColumn)
|
|
{
|
|
RECT rcCol;
|
|
|
|
if(!is_redrawing(infoPtr)) return;
|
|
LISTVIEW_GetHeaderRect(infoPtr, nColumn, &rcCol);
|
|
rcCol.top = infoPtr->rcList.top;
|
|
rcCol.bottom = infoPtr->rcList.bottom;
|
|
LISTVIEW_InvalidateRect(infoPtr, &rcCol);
|
|
}
|
|
|
|
/***
|
|
* DESCRIPTION:
|
|
* Retrieves the number of items that can fit vertically in the client area.
|
|
*
|
|
* PARAMETER(S):
|
|
* [I] infoPtr : valid pointer to the listview structure
|
|
*
|
|
* RETURN:
|
|
* Number of items per row.
|
|
*/
|
|
static inline INT LISTVIEW_GetCountPerRow(const LISTVIEW_INFO *infoPtr)
|
|
{
|
|
INT nListWidth = infoPtr->rcList.right - infoPtr->rcList.left;
|
|
|
|
return max(nListWidth/(infoPtr->nItemWidth ? infoPtr->nItemWidth : 1), 1);
|
|
}
|
|
|
|
/***
|
|
* DESCRIPTION:
|
|
* Retrieves the number of items that can fit horizontally in the client
|
|
* area.
|
|
*
|
|
* PARAMETER(S):
|
|
* [I] infoPtr : valid pointer to the listview structure
|
|
*
|
|
* RETURN:
|
|
* Number of items per column.
|
|
*/
|
|
static inline INT LISTVIEW_GetCountPerColumn(const LISTVIEW_INFO *infoPtr)
|
|
{
|
|
INT nListHeight = infoPtr->rcList.bottom - infoPtr->rcList.top;
|
|
|
|
return max(nListHeight / infoPtr->nItemHeight, 1);
|
|
}
|
|
|
|
|
|
/*************************************************************************
|
|
* LISTVIEW_ProcessLetterKeys
|
|
*
|
|
* Processes keyboard messages generated by pressing the letter keys
|
|
* on the keyboard.
|
|
* What this does is perform a case insensitive search from the
|
|
* current position with the following quirks:
|
|
* - If two chars or more are pressed in quick succession we search
|
|
* for the corresponding string (e.g. 'abc').
|
|
* - If there is a delay we wipe away the current search string and
|
|
* restart with just that char.
|
|
* - If the user keeps pressing the same character, whether slowly or
|
|
* fast, so that the search string is entirely composed of this
|
|
* character ('aaaaa' for instance), then we search for first item
|
|
* that starting with that character.
|
|
* - If the user types the above character in quick succession, then
|
|
* we must also search for the corresponding string ('aaaaa'), and
|
|
* go to that string if there is a match.
|
|
*
|
|
* PARAMETERS
|
|
* [I] hwnd : handle to the window
|
|
* [I] charCode : the character code, the actual character
|
|
* [I] keyData : key data
|
|
*
|
|
* RETURNS
|
|
*
|
|
* Zero.
|
|
*
|
|
* BUGS
|
|
*
|
|
* - The current implementation has a list of characters it will
|
|
* accept and it ignores everything else. In particular it will
|
|
* ignore accentuated characters which seems to match what
|
|
* Windows does. But I'm not sure it makes sense to follow
|
|
* Windows there.
|
|
* - We don't sound a beep when the search fails.
|
|
*
|
|
* SEE ALSO
|
|
*
|
|
* TREEVIEW_ProcessLetterKeys
|
|
*/
|
|
static INT LISTVIEW_ProcessLetterKeys(LISTVIEW_INFO *infoPtr, WPARAM charCode, LPARAM keyData)
|
|
{
|
|
WCHAR buffer[MAX_PATH];
|
|
DWORD prevTime;
|
|
LVITEMW item;
|
|
int startidx;
|
|
INT nItem;
|
|
INT diff;
|
|
|
|
/* simple parameter checking */
|
|
if (!charCode || !keyData || infoPtr->nItemCount == 0) return 0;
|
|
|
|
/* only allow the valid WM_CHARs through */
|
|
if (!isalnumW(charCode) &&
|
|
charCode != '.' && charCode != '`' && charCode != '!' &&
|
|
charCode != '@' && charCode != '#' && charCode != '$' &&
|
|
charCode != '%' && charCode != '^' && charCode != '&' &&
|
|
charCode != '*' && charCode != '(' && charCode != ')' &&
|
|
charCode != '-' && charCode != '_' && charCode != '+' &&
|
|
charCode != '=' && charCode != '\\'&& charCode != ']' &&
|
|
charCode != '}' && charCode != '[' && charCode != '{' &&
|
|
charCode != '/' && charCode != '?' && charCode != '>' &&
|
|
charCode != '<' && charCode != ',' && charCode != '~')
|
|
return 0;
|
|
|
|
/* update the search parameters */
|
|
prevTime = infoPtr->lastKeyPressTimestamp;
|
|
infoPtr->lastKeyPressTimestamp = GetTickCount();
|
|
diff = infoPtr->lastKeyPressTimestamp - prevTime;
|
|
|
|
if (diff >= 0 && diff < KEY_DELAY)
|
|
{
|
|
if (infoPtr->nSearchParamLength < MAX_PATH - 1)
|
|
infoPtr->szSearchParam[infoPtr->nSearchParamLength++] = charCode;
|
|
|
|
if (infoPtr->charCode != charCode)
|
|
infoPtr->charCode = charCode = 0;
|
|
}
|
|
else
|
|
{
|
|
infoPtr->charCode = charCode;
|
|
infoPtr->szSearchParam[0] = charCode;
|
|
infoPtr->nSearchParamLength = 1;
|
|
}
|
|
|
|
/* should start from next after focused item, so next item that matches
|
|
will be selected, if there isn't any and focused matches it will be selected
|
|
on second search stage from beginning of the list */
|
|
if (infoPtr->nFocusedItem >= 0 && infoPtr->nItemCount > 1)
|
|
{
|
|
/* with some accumulated search data available start with current focus, otherwise
|
|
it's excluded from search */
|
|
startidx = infoPtr->nSearchParamLength > 1 ? infoPtr->nFocusedItem : infoPtr->nFocusedItem + 1;
|
|
if (startidx == infoPtr->nItemCount) startidx = 0;
|
|
}
|
|
else
|
|
startidx = 0;
|
|
|
|
/* let application handle this for virtual listview */
|
|
if (infoPtr->dwStyle & LVS_OWNERDATA)
|
|
{
|
|
NMLVFINDITEMW nmlv;
|
|
|
|
memset(&nmlv.lvfi, 0, sizeof(nmlv.lvfi));
|
|
nmlv.lvfi.flags = (LVFI_WRAP | LVFI_PARTIAL);
|
|
nmlv.lvfi.psz = infoPtr->szSearchParam;
|
|
nmlv.iStart = startidx;
|
|
|
|
infoPtr->szSearchParam[infoPtr->nSearchParamLength] = 0;
|
|
|
|
nItem = notify_hdr(infoPtr, LVN_ODFINDITEMW, (LPNMHDR)&nmlv.hdr);
|
|
}
|
|
else
|
|
{
|
|
int i = startidx, endidx;
|
|
|
|
/* and search from the current position */
|
|
nItem = -1;
|
|
endidx = infoPtr->nItemCount;
|
|
|
|
/* first search in [startidx, endidx), on failure continue in [0, startidx) */
|
|
while (1)
|
|
{
|
|
/* start from first item if not found with >= startidx */
|
|
if (i == infoPtr->nItemCount && startidx > 0)
|
|
{
|
|
endidx = startidx;
|
|
startidx = 0;
|
|
}
|
|
|
|
for (i = startidx; i < endidx; i++)
|
|
{
|
|
/* retrieve text */
|
|
item.mask = LVIF_TEXT;
|
|
item.iItem = i;
|
|
item.iSubItem = 0;
|
|
item.pszText = buffer;
|
|
item.cchTextMax = MAX_PATH;
|
|
if (!LISTVIEW_GetItemW(infoPtr, &item)) return 0;
|
|
|
|
if (!lstrncmpiW(item.pszText, infoPtr->szSearchParam, infoPtr->nSearchParamLength))
|
|
{
|
|
nItem = i;
|
|
break;
|
|
}
|
|
/* this is used to find first char match when search string is not available yet,
|
|
otherwise every WM_CHAR will search to next item by first char, ignoring that we're
|
|
already waiting for user to complete a string */
|
|
else if (nItem == -1 && infoPtr->nSearchParamLength == 1 && !lstrncmpiW(item.pszText, infoPtr->szSearchParam, 1))
|
|
{
|
|
/* this would work but we must keep looking for a longer match */
|
|
nItem = i;
|
|
}
|
|
}
|
|
|
|
if ( nItem != -1 || /* found something */
|
|
endidx != infoPtr->nItemCount || /* second search done */
|
|
(startidx == 0 && endidx == infoPtr->nItemCount) /* full range for first search */ )
|
|
break;
|
|
};
|
|
}
|
|
|
|
if (nItem != -1)
|
|
LISTVIEW_KeySelection(infoPtr, nItem, FALSE);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*************************************************************************
|
|
* LISTVIEW_UpdateHeaderSize [Internal]
|
|
*
|
|
* Function to resize the header control
|
|
*
|
|
* PARAMS
|
|
* [I] hwnd : handle to a window
|
|
* [I] nNewScrollPos : scroll pos to set
|
|
*
|
|
* RETURNS
|
|
* None.
|
|
*/
|
|
static void LISTVIEW_UpdateHeaderSize(const LISTVIEW_INFO *infoPtr, INT nNewScrollPos)
|
|
{
|
|
RECT winRect;
|
|
POINT point[2];
|
|
|
|
TRACE("nNewScrollPos=%d\n", nNewScrollPos);
|
|
|
|
if (!infoPtr->hwndHeader) return;
|
|
|
|
GetWindowRect(infoPtr->hwndHeader, &winRect);
|
|
point[0].x = winRect.left;
|
|
point[0].y = winRect.top;
|
|
point[1].x = winRect.right;
|
|
point[1].y = winRect.bottom;
|
|
|
|
MapWindowPoints(HWND_DESKTOP, infoPtr->hwndSelf, point, 2);
|
|
point[0].x = -nNewScrollPos;
|
|
point[1].x += nNewScrollPos;
|
|
|
|
SetWindowPos(infoPtr->hwndHeader,0,
|
|
point[0].x,point[0].y,point[1].x,point[1].y,
|
|
(infoPtr->dwStyle & LVS_NOCOLUMNHEADER) ? SWP_HIDEWINDOW : SWP_SHOWWINDOW |
|
|
SWP_NOZORDER | SWP_NOACTIVATE);
|
|
}
|
|
|
|
/***
|
|
* DESCRIPTION:
|
|
* Update the scrollbars. This functions should be called whenever
|
|
* the content, size or view changes.
|
|
*
|
|
* PARAMETER(S):
|
|
* [I] infoPtr : valid pointer to the listview structure
|
|
*
|
|
* RETURN:
|
|
* None
|
|
*/
|
|
static void LISTVIEW_UpdateScroll(const LISTVIEW_INFO *infoPtr)
|
|
{
|
|
SCROLLINFO horzInfo, vertInfo;
|
|
INT dx, dy;
|
|
|
|
if ((infoPtr->dwStyle & LVS_NOSCROLL) || !is_redrawing(infoPtr)) return;
|
|
|
|
ZeroMemory(&horzInfo, sizeof(SCROLLINFO));
|
|
horzInfo.cbSize = sizeof(SCROLLINFO);
|
|
horzInfo.nPage = infoPtr->rcList.right - infoPtr->rcList.left;
|
|
|
|
/* for now, we'll set info.nMax to the _count_, and adjust it later */
|
|
if (infoPtr->uView == LV_VIEW_LIST)
|
|
{
|
|
INT nPerCol = LISTVIEW_GetCountPerColumn(infoPtr);
|
|
horzInfo.nMax = (infoPtr->nItemCount + nPerCol - 1) / nPerCol;
|
|
|
|
/* scroll by at least one column per page */
|
|
if(horzInfo.nPage < infoPtr->nItemWidth)
|
|
horzInfo.nPage = infoPtr->nItemWidth;
|
|
|
|
if (infoPtr->nItemWidth)
|
|
horzInfo.nPage /= infoPtr->nItemWidth;
|
|
}
|
|
else if (infoPtr->uView == LV_VIEW_DETAILS)
|
|
{
|
|
horzInfo.nMax = infoPtr->nItemWidth;
|
|
}
|
|
else /* LV_VIEW_ICON, or LV_VIEW_SMALLICON */
|
|
{
|
|
RECT rcView;
|
|
|
|
if (LISTVIEW_GetViewRect(infoPtr, &rcView)) horzInfo.nMax = rcView.right - rcView.left;
|
|
}
|
|
|
|
if (LISTVIEW_IsHeaderEnabled(infoPtr))
|
|
{
|
|
if (DPA_GetPtrCount(infoPtr->hdpaColumns))
|
|
{
|
|
RECT rcHeader;
|
|
INT index;
|
|
|
|
index = SendMessageW(infoPtr->hwndHeader, HDM_ORDERTOINDEX,
|
|
DPA_GetPtrCount(infoPtr->hdpaColumns) - 1, 0);
|
|
|
|
LISTVIEW_GetHeaderRect(infoPtr, index, &rcHeader);
|
|
horzInfo.nMax = rcHeader.right;
|
|
TRACE("horzInfo.nMax=%d\n", horzInfo.nMax);
|
|
}
|
|
}
|
|
|
|
horzInfo.fMask = SIF_RANGE | SIF_PAGE;
|
|
horzInfo.nMax = max(horzInfo.nMax - 1, 0);
|
|
dx = GetScrollPos(infoPtr->hwndSelf, SB_HORZ);
|
|
dx -= SetScrollInfo(infoPtr->hwndSelf, SB_HORZ, &horzInfo, TRUE);
|
|
TRACE("horzInfo=%s\n", debugscrollinfo(&horzInfo));
|
|
|
|
/* Setting the horizontal scroll can change the listview size
|
|
* (and potentially everything else) so we need to recompute
|
|
* everything again for the vertical scroll
|
|
*/
|
|
|
|
ZeroMemory(&vertInfo, sizeof(SCROLLINFO));
|
|
vertInfo.cbSize = sizeof(SCROLLINFO);
|
|
vertInfo.nPage = infoPtr->rcList.bottom - infoPtr->rcList.top;
|
|
|
|
if (infoPtr->uView == LV_VIEW_DETAILS)
|
|
{
|
|
vertInfo.nMax = infoPtr->nItemCount;
|
|
|
|
/* scroll by at least one page */
|
|
if(vertInfo.nPage < infoPtr->nItemHeight)
|
|
vertInfo.nPage = infoPtr->nItemHeight;
|
|
|
|
if (infoPtr->nItemHeight > 0)
|
|
vertInfo.nPage /= infoPtr->nItemHeight;
|
|
}
|
|
else if (infoPtr->uView != LV_VIEW_LIST) /* LV_VIEW_ICON, or LV_VIEW_SMALLICON */
|
|
{
|
|
RECT rcView;
|
|
|
|
if (LISTVIEW_GetViewRect(infoPtr, &rcView)) vertInfo.nMax = rcView.bottom - rcView.top;
|
|
}
|
|
|
|
vertInfo.fMask = SIF_RANGE | SIF_PAGE;
|
|
vertInfo.nMax = max(vertInfo.nMax - 1, 0);
|
|
dy = GetScrollPos(infoPtr->hwndSelf, SB_VERT);
|
|
dy -= SetScrollInfo(infoPtr->hwndSelf, SB_VERT, &vertInfo, TRUE);
|
|
TRACE("vertInfo=%s\n", debugscrollinfo(&vertInfo));
|
|
|
|
/* Change of the range may have changed the scroll pos. If so move the content */
|
|
if (dx != 0 || dy != 0)
|
|
{
|
|
RECT listRect;
|
|
listRect = infoPtr->rcList;
|
|
ScrollWindowEx(infoPtr->hwndSelf, dx, dy, &listRect, &listRect, 0, 0,
|
|
SW_ERASE | SW_INVALIDATE);
|
|
}
|
|
|
|
/* Update the Header Control */
|
|
if (infoPtr->hwndHeader)
|
|
{
|
|
horzInfo.fMask = SIF_POS;
|
|
GetScrollInfo(infoPtr->hwndSelf, SB_HORZ, &horzInfo);
|
|
LISTVIEW_UpdateHeaderSize(infoPtr, horzInfo.nPos);
|
|
}
|
|
}
|
|
|
|
|
|
/***
|
|
* DESCRIPTION:
|
|
* Shows/hides the focus rectangle.
|
|
*
|
|
* PARAMETER(S):
|
|
* [I] infoPtr : valid pointer to the listview structure
|
|
* [I] fShow : TRUE to show the focus, FALSE to hide it.
|
|
*
|
|
* RETURN:
|
|
* None
|
|
*/
|
|
static void LISTVIEW_ShowFocusRect(const LISTVIEW_INFO *infoPtr, BOOL fShow)
|
|
{
|
|
HDC hdc;
|
|
|
|
TRACE("fShow=%d, nItem=%d\n", fShow, infoPtr->nFocusedItem);
|
|
|
|
if (infoPtr->nFocusedItem < 0) return;
|
|
|
|
/* we need some gymnastics in ICON mode to handle large items */
|
|
if (infoPtr->uView == LV_VIEW_ICON)
|
|
{
|
|
RECT rcBox;
|
|
|
|
LISTVIEW_GetItemBox(infoPtr, infoPtr->nFocusedItem, &rcBox);
|
|
if ((rcBox.bottom - rcBox.top) > infoPtr->nItemHeight)
|
|
{
|
|
LISTVIEW_InvalidateRect(infoPtr, &rcBox);
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (!(hdc = GetDC(infoPtr->hwndSelf))) return;
|
|
|
|
/* for some reason, owner draw should work only in report mode */
|
|
if ((infoPtr->dwStyle & LVS_OWNERDRAWFIXED) && (infoPtr->uView == LV_VIEW_DETAILS))
|
|
{
|
|
DRAWITEMSTRUCT dis;
|
|
LVITEMW item;
|
|
|
|
HFONT hFont = infoPtr->hFont ? infoPtr->hFont : infoPtr->hDefaultFont;
|
|
HFONT hOldFont = SelectObject(hdc, hFont);
|
|
|
|
item.iItem = infoPtr->nFocusedItem;
|
|
item.iSubItem = 0;
|
|
item.mask = LVIF_PARAM;
|
|
if (!LISTVIEW_GetItemW(infoPtr, &item)) goto done;
|
|
|
|
ZeroMemory(&dis, sizeof(dis));
|
|
dis.CtlType = ODT_LISTVIEW;
|
|
dis.CtlID = (UINT)GetWindowLongPtrW(infoPtr->hwndSelf, GWLP_ID);
|
|
dis.itemID = item.iItem;
|
|
dis.itemAction = ODA_FOCUS;
|
|
if (fShow) dis.itemState |= ODS_FOCUS;
|
|
dis.hwndItem = infoPtr->hwndSelf;
|
|
dis.hDC = hdc;
|
|
LISTVIEW_GetItemBox(infoPtr, dis.itemID, &dis.rcItem);
|
|
dis.itemData = item.lParam;
|
|
|
|
SendMessageW(infoPtr->hwndNotify, WM_DRAWITEM, dis.CtlID, (LPARAM)&dis);
|
|
|
|
SelectObject(hdc, hOldFont);
|
|
}
|
|
else
|
|
{
|
|
LISTVIEW_DrawFocusRect(infoPtr, hdc);
|
|
}
|
|
done:
|
|
ReleaseDC(infoPtr->hwndSelf, hdc);
|
|
}
|
|
|
|
/***
|
|
* Invalidates all visible selected items.
|
|
*/
|
|
static void LISTVIEW_InvalidateSelectedItems(const LISTVIEW_INFO *infoPtr)
|
|
{
|
|
ITERATOR i;
|
|
|
|
iterator_frameditems(&i, infoPtr, &infoPtr->rcList);
|
|
while(iterator_next(&i))
|
|
{
|
|
if (LISTVIEW_GetItemState(infoPtr, i.nItem, LVIS_SELECTED))
|
|
LISTVIEW_InvalidateItem(infoPtr, i.nItem);
|
|
}
|
|
iterator_destroy(&i);
|
|
}
|
|
|
|
|
|
/***
|
|
* DESCRIPTION: [INTERNAL]
|
|
* Computes an item's (left,top) corner, relative to rcView.
|
|
* That is, the position has NOT been made relative to the Origin.
|
|
* This is deliberate, to avoid computing the Origin over, and
|
|
* over again, when this function is called in a loop. Instead,
|
|
* one can factor the computation of the Origin before the loop,
|
|
* and offset the value returned by this function, on every iteration.
|
|
*
|
|
* PARAMETER(S):
|
|
* [I] infoPtr : valid pointer to the listview structure
|
|
* [I] nItem : item number
|
|
* [O] lpptOrig : item top, left corner
|
|
*
|
|
* RETURN:
|
|
* None.
|
|
*/
|
|
static void LISTVIEW_GetItemOrigin(const LISTVIEW_INFO *infoPtr, INT nItem, LPPOINT lpptPosition)
|
|
{
|
|
assert(nItem >= 0 && nItem < infoPtr->nItemCount);
|
|
|
|
if ((infoPtr->uView == LV_VIEW_SMALLICON) || (infoPtr->uView == LV_VIEW_ICON))
|
|
{
|
|
lpptPosition->x = (LONG_PTR)DPA_GetPtr(infoPtr->hdpaPosX, nItem);
|
|
lpptPosition->y = (LONG_PTR)DPA_GetPtr(infoPtr->hdpaPosY, nItem);
|
|
}
|
|
else if (infoPtr->uView == LV_VIEW_LIST)
|
|
{
|
|
INT nCountPerColumn = LISTVIEW_GetCountPerColumn(infoPtr);
|
|
lpptPosition->x = nItem / nCountPerColumn * infoPtr->nItemWidth;
|
|
lpptPosition->y = nItem % nCountPerColumn * infoPtr->nItemHeight;
|
|
}
|
|
else /* LV_VIEW_DETAILS */
|
|
{
|
|
lpptPosition->x = REPORT_MARGINX;
|
|
/* item is always at zero indexed column */
|
|
if (DPA_GetPtrCount(infoPtr->hdpaColumns) > 0)
|
|
lpptPosition->x += LISTVIEW_GetColumnInfo(infoPtr, 0)->rcHeader.left;
|
|
lpptPosition->y = nItem * infoPtr->nItemHeight;
|
|
}
|
|
}
|
|
|
|
/***
|
|
* DESCRIPTION: [INTERNAL]
|
|
* Compute the rectangles of an item. This is to localize all
|
|
* the computations in one place. If you are not interested in some
|
|
* of these values, simply pass in a NULL -- the function is smart
|
|
* enough to compute only what's necessary. The function computes
|
|
* the standard rectangles (BOUNDS, ICON, LABEL) plus a non-standard
|
|
* one, the BOX rectangle. This rectangle is very cheap to compute,
|
|
* and is guaranteed to contain all the other rectangles. Computing
|
|
* the ICON rect is also cheap, but all the others are potentially
|
|
* expensive. This gives an easy and effective optimization when
|
|
* searching (like point inclusion, or rectangle intersection):
|
|
* first test against the BOX, and if TRUE, test against the desired
|
|
* rectangle.
|
|
* If the function does not have all the necessary information
|
|
* to computed the requested rectangles, will crash with a
|
|
* failed assertion. This is done so we catch all programming
|
|
* errors, given that the function is called only from our code.
|
|
*
|
|
* We have the following 'special' meanings for a few fields:
|
|
* * If LVIS_FOCUSED is set, we assume the item has the focus
|
|
* This is important in ICON mode, where it might get a larger
|
|
* then usual rectangle
|
|
*
|
|
* Please note that subitem support works only in REPORT mode.
|
|
*
|
|
* PARAMETER(S):
|
|
* [I] infoPtr : valid pointer to the listview structure
|
|
* [I] lpLVItem : item to compute the measures for
|
|
* [O] lprcBox : ptr to Box rectangle
|
|
* Same as LVM_GETITEMRECT with LVIR_BOUNDS
|
|
* [0] lprcSelectBox : ptr to select box rectangle
|
|
* Same as LVM_GETITEMRECT with LVIR_SELECTEDBOUNDS
|
|
* [O] lprcIcon : ptr to Icon rectangle
|
|
* Same as LVM_GETITEMRECT with LVIR_ICON
|
|
* [O] lprcStateIcon: ptr to State Icon rectangle
|
|
* [O] lprcLabel : ptr to Label rectangle
|
|
* Same as LVM_GETITEMRECT with LVIR_LABEL
|
|
*
|
|
* RETURN:
|
|
* None.
|
|
*/
|
|
static void LISTVIEW_GetItemMetrics(const LISTVIEW_INFO *infoPtr, const LVITEMW *lpLVItem,
|
|
LPRECT lprcBox, LPRECT lprcSelectBox,
|
|
LPRECT lprcIcon, LPRECT lprcStateIcon, LPRECT lprcLabel)
|
|
{
|
|
BOOL doSelectBox = FALSE, doIcon = FALSE, doLabel = FALSE, oversizedBox = FALSE;
|
|
RECT Box, SelectBox, Icon, Label;
|
|
COLUMN_INFO *lpColumnInfo = NULL;
|
|
SIZE labelSize = { 0, 0 };
|
|
|
|
TRACE("(lpLVItem=%s)\n", debuglvitem_t(lpLVItem, TRUE));
|
|
|
|
/* Be smart and try to figure out the minimum we have to do */
|
|
if (lpLVItem->iSubItem) assert(infoPtr->uView == LV_VIEW_DETAILS);
|
|
if (infoPtr->uView == LV_VIEW_ICON && (lprcBox || lprcLabel))
|
|
{
|
|
assert((lpLVItem->mask & LVIF_STATE) && (lpLVItem->stateMask & LVIS_FOCUSED));
|
|
if (lpLVItem->state & LVIS_FOCUSED) oversizedBox = doLabel = TRUE;
|
|
}
|
|
if (lprcSelectBox) doSelectBox = TRUE;
|
|
if (lprcLabel) doLabel = TRUE;
|
|
if (doLabel || lprcIcon || lprcStateIcon) doIcon = TRUE;
|
|
if (doSelectBox)
|
|
{
|
|
doIcon = TRUE;
|
|
doLabel = TRUE;
|
|
}
|
|
|
|
/************************************************************/
|
|
/* compute the box rectangle (it should be cheap to do) */
|
|
/************************************************************/
|
|
if (lpLVItem->iSubItem || infoPtr->uView == LV_VIEW_DETAILS)
|
|
lpColumnInfo = LISTVIEW_GetColumnInfo(infoPtr, lpLVItem->iSubItem);
|
|
|
|
if (lpLVItem->iSubItem)
|
|
{
|
|
Box = lpColumnInfo->rcHeader;
|
|
}
|
|
else
|
|
{
|
|
Box.left = 0;
|
|
Box.right = infoPtr->nItemWidth;
|
|
}
|
|
Box.top = 0;
|
|
Box.bottom = infoPtr->nItemHeight;
|
|
|
|
/******************************************************************/
|
|
/* compute ICON bounding box (ala LVM_GETITEMRECT) and STATEICON */
|
|
/******************************************************************/
|
|
if (doIcon)
|
|
{
|
|
LONG state_width = 0;
|
|
|
|
if (infoPtr->himlState && lpLVItem->iSubItem == 0)
|
|
state_width = infoPtr->iconStateSize.cx;
|
|
|
|
if (infoPtr->uView == LV_VIEW_ICON)
|
|
{
|
|
Icon.left = Box.left + state_width;
|
|
if (infoPtr->himlNormal)
|
|
Icon.left += (infoPtr->nItemWidth - infoPtr->iconSize.cx - state_width) / 2;
|
|
Icon.top = Box.top + ICON_TOP_PADDING;
|
|
Icon.right = Icon.left;
|
|
Icon.bottom = Icon.top;
|
|
if (infoPtr->himlNormal)
|
|
{
|
|
Icon.right += infoPtr->iconSize.cx;
|
|
Icon.bottom += infoPtr->iconSize.cy;
|
|
}
|
|
}
|
|
else /* LV_VIEW_SMALLICON, LV_VIEW_LIST or LV_VIEW_DETAILS */
|
|
{
|
|
Icon.left = Box.left + state_width;
|
|
|
|
if (infoPtr->uView == LV_VIEW_DETAILS && lpLVItem->iSubItem == 0)
|
|
{
|
|
/* we need the indent in report mode */
|
|
assert(lpLVItem->mask & LVIF_INDENT);
|
|
Icon.left += infoPtr->iconSize.cx * lpLVItem->iIndent + REPORT_MARGINX;
|
|
}
|
|
|
|
Icon.top = Box.top;
|
|
Icon.right = Icon.left;
|
|
if (infoPtr->himlSmall &&
|
|
(!lpColumnInfo || lpLVItem->iSubItem == 0 ||
|
|
((infoPtr->dwLvExStyle & LVS_EX_SUBITEMIMAGES) && lpLVItem->iImage != I_IMAGECALLBACK)))
|
|
Icon.right += infoPtr->iconSize.cx;
|
|
Icon.bottom = Icon.top + infoPtr->iconSize.cy;
|
|
}
|
|
if(lprcIcon) *lprcIcon = Icon;
|
|
TRACE(" - icon=%s\n", wine_dbgstr_rect(&Icon));
|
|
|
|
/* TODO: is this correct? */
|
|
if (lprcStateIcon)
|
|
{
|
|
lprcStateIcon->left = Icon.left - state_width;
|
|
lprcStateIcon->right = Icon.left;
|
|
lprcStateIcon->top = Icon.top;
|
|
lprcStateIcon->bottom = lprcStateIcon->top + infoPtr->iconSize.cy;
|
|
TRACE(" - state icon=%s\n", wine_dbgstr_rect(lprcStateIcon));
|
|
}
|
|
}
|
|
else Icon.right = 0;
|
|
|
|
/************************************************************/
|
|
/* compute LABEL bounding box (ala LVM_GETITEMRECT) */
|
|
/************************************************************/
|
|
if (doLabel)
|
|
{
|
|
/* calculate how far to the right can the label stretch */
|
|
Label.right = Box.right;
|
|
if (infoPtr->uView == LV_VIEW_DETAILS)
|
|
{
|
|
if (lpLVItem->iSubItem == 0)
|
|
{
|
|
/* we need a zero based rect here */
|
|
Label = lpColumnInfo->rcHeader;
|
|
OffsetRect(&Label, -Label.left, 0);
|
|
}
|
|
}
|
|
|
|
if (lpLVItem->iSubItem || ((infoPtr->dwStyle & LVS_OWNERDRAWFIXED) && infoPtr->uView == LV_VIEW_DETAILS))
|
|
{
|
|
labelSize.cx = infoPtr->nItemWidth;
|
|
labelSize.cy = infoPtr->nItemHeight;
|
|
goto calc_label;
|
|
}
|
|
|
|
/* we need the text in non owner draw mode */
|
|
assert(lpLVItem->mask & LVIF_TEXT);
|
|
if (is_text(lpLVItem->pszText))
|
|
{
|
|
HFONT hFont = infoPtr->hFont ? infoPtr->hFont : infoPtr->hDefaultFont;
|
|
HDC hdc = GetDC(infoPtr->hwndSelf);
|
|
HFONT hOldFont = SelectObject(hdc, hFont);
|
|
UINT uFormat;
|
|
RECT rcText;
|
|
|
|
/* compute rough rectangle where the label will go */
|
|
SetRectEmpty(&rcText);
|
|
rcText.right = infoPtr->nItemWidth - TRAILING_LABEL_PADDING;
|
|
rcText.bottom = infoPtr->nItemHeight;
|
|
if (infoPtr->uView == LV_VIEW_ICON)
|
|
rcText.bottom -= ICON_TOP_PADDING + infoPtr->iconSize.cy + ICON_BOTTOM_PADDING;
|
|
|
|
/* now figure out the flags */
|
|
if (infoPtr->uView == LV_VIEW_ICON)
|
|
uFormat = oversizedBox ? LV_FL_DT_FLAGS : LV_ML_DT_FLAGS;
|
|
else
|
|
uFormat = LV_SL_DT_FLAGS;
|
|
|
|
DrawTextW (hdc, lpLVItem->pszText, -1, &rcText, uFormat | DT_CALCRECT);
|
|
|
|
if (rcText.right != rcText.left)
|
|
labelSize.cx = min(rcText.right - rcText.left + TRAILING_LABEL_PADDING, infoPtr->nItemWidth);
|
|
|
|
labelSize.cy = rcText.bottom - rcText.top;
|
|
|
|
SelectObject(hdc, hOldFont);
|
|
ReleaseDC(infoPtr->hwndSelf, hdc);
|
|
}
|
|
|
|
calc_label:
|
|
if (infoPtr->uView == LV_VIEW_ICON)
|
|
{
|
|
Label.left = Box.left + (infoPtr->nItemWidth - labelSize.cx) / 2;
|
|
Label.top = Box.top + ICON_TOP_PADDING_HITABLE +
|
|
infoPtr->iconSize.cy + ICON_BOTTOM_PADDING;
|
|
Label.right = Label.left + labelSize.cx;
|
|
Label.bottom = Label.top + infoPtr->nItemHeight;
|
|
if (!oversizedBox && labelSize.cy > infoPtr->ntmHeight)
|
|
{
|
|
labelSize.cy = min(Box.bottom - Label.top, labelSize.cy);
|
|
labelSize.cy /= infoPtr->ntmHeight;
|
|
labelSize.cy = max(labelSize.cy, 1);
|
|
labelSize.cy *= infoPtr->ntmHeight;
|
|
}
|
|
Label.bottom = Label.top + labelSize.cy + HEIGHT_PADDING;
|
|
}
|
|
else if (infoPtr->uView == LV_VIEW_DETAILS)
|
|
{
|
|
Label.left = Icon.right;
|
|
Label.top = Box.top;
|
|
Label.right = lpLVItem->iSubItem ? lpColumnInfo->rcHeader.right :
|
|
lpColumnInfo->rcHeader.right - lpColumnInfo->rcHeader.left;
|
|
Label.bottom = Label.top + infoPtr->nItemHeight;
|
|
}
|
|
else /* LV_VIEW_SMALLICON or LV_VIEW_LIST */
|
|
{
|
|
Label.left = Icon.right;
|
|
Label.top = Box.top;
|
|
Label.right = min(Label.left + labelSize.cx, Label.right);
|
|
Label.bottom = Label.top + infoPtr->nItemHeight;
|
|
}
|
|
|
|
if (lprcLabel) *lprcLabel = Label;
|
|
TRACE(" - label=%s\n", wine_dbgstr_rect(&Label));
|
|
}
|
|
|
|
/************************************************************/
|
|
/* compute SELECT bounding box */
|
|
/************************************************************/
|
|
if (doSelectBox)
|
|
{
|
|
if (infoPtr->uView == LV_VIEW_DETAILS)
|
|
{
|
|
SelectBox.left = Icon.left;
|
|
SelectBox.top = Box.top;
|
|
SelectBox.bottom = Box.bottom;
|
|
|
|
if (labelSize.cx)
|
|
SelectBox.right = min(Label.left + labelSize.cx, Label.right);
|
|
else
|
|
SelectBox.right = min(Label.left + MAX_EMPTYTEXT_SELECT_WIDTH, Label.right);
|
|
}
|
|
else
|
|
{
|
|
UnionRect(&SelectBox, &Icon, &Label);
|
|
}
|
|
if (lprcSelectBox) *lprcSelectBox = SelectBox;
|
|
TRACE(" - select box=%s\n", wine_dbgstr_rect(&SelectBox));
|
|
}
|
|
|
|
/* Fix the Box if necessary */
|
|
if (lprcBox)
|
|
{
|
|
if (oversizedBox) UnionRect(lprcBox, &Box, &Label);
|
|
else *lprcBox = Box;
|
|
}
|
|
TRACE(" - box=%s\n", wine_dbgstr_rect(&Box));
|
|
}
|
|
|
|
/***
|
|
* DESCRIPTION: [INTERNAL]
|
|
*
|
|
* PARAMETER(S):
|
|
* [I] infoPtr : valid pointer to the listview structure
|
|
* [I] nItem : item number
|
|
* [O] lprcBox : ptr to Box rectangle
|
|
*
|
|
* RETURN:
|
|
* None.
|
|
*/
|
|
static void LISTVIEW_GetItemBox(const LISTVIEW_INFO *infoPtr, INT nItem, LPRECT lprcBox)
|
|
{
|
|
WCHAR szDispText[DISP_TEXT_SIZE] = { '\0' };
|
|
POINT Position, Origin;
|
|
LVITEMW lvItem;
|
|
|
|
LISTVIEW_GetOrigin(infoPtr, &Origin);
|
|
LISTVIEW_GetItemOrigin(infoPtr, nItem, &Position);
|
|
|
|
/* Be smart and try to figure out the minimum we have to do */
|
|
lvItem.mask = 0;
|
|
if (infoPtr->uView == LV_VIEW_ICON && infoPtr->bFocus && LISTVIEW_GetItemState(infoPtr, nItem, LVIS_FOCUSED))
|
|
lvItem.mask |= LVIF_TEXT;
|
|
lvItem.iItem = nItem;
|
|
lvItem.iSubItem = 0;
|
|
lvItem.pszText = szDispText;
|
|
lvItem.cchTextMax = DISP_TEXT_SIZE;
|
|
if (lvItem.mask) LISTVIEW_GetItemW(infoPtr, &lvItem);
|
|
if (infoPtr->uView == LV_VIEW_ICON)
|
|
{
|
|
lvItem.mask |= LVIF_STATE;
|
|
lvItem.stateMask = LVIS_FOCUSED;
|
|
lvItem.state = (lvItem.mask & LVIF_TEXT ? LVIS_FOCUSED : 0);
|
|
}
|
|
LISTVIEW_GetItemMetrics(infoPtr, &lvItem, lprcBox, 0, 0, 0, 0);
|
|
|
|
if (infoPtr->uView == LV_VIEW_DETAILS && infoPtr->dwLvExStyle & LVS_EX_FULLROWSELECT &&
|
|
SendMessageW(infoPtr->hwndHeader, HDM_ORDERTOINDEX, 0, 0))
|
|
{
|
|
OffsetRect(lprcBox, Origin.x, Position.y + Origin.y);
|
|
}
|
|
else
|
|
OffsetRect(lprcBox, Position.x + Origin.x, Position.y + Origin.y);
|
|
}
|
|
|
|
/* LISTVIEW_MapIdToIndex helper */
|
|
static INT CALLBACK MapIdSearchCompare(LPVOID p1, LPVOID p2, LPARAM lParam)
|
|
{
|
|
ITEM_ID *id1 = (ITEM_ID*)p1;
|
|
ITEM_ID *id2 = (ITEM_ID*)p2;
|
|
|
|
if (id1->id == id2->id) return 0;
|
|
|
|
return (id1->id < id2->id) ? -1 : 1;
|
|
}
|
|
|
|
/***
|
|
* DESCRIPTION:
|
|
* Returns the item index for id specified.
|
|
*
|
|
* PARAMETER(S):
|
|
* [I] infoPtr : valid pointer to the listview structure
|
|
* [I] iID : item id to get index for
|
|
*
|
|
* RETURN:
|
|
* Item index, or -1 on failure.
|
|
*/
|
|
static INT LISTVIEW_MapIdToIndex(const LISTVIEW_INFO *infoPtr, UINT iID)
|
|
{
|
|
ITEM_ID ID;
|
|
INT index;
|
|
|
|
TRACE("iID=%d\n", iID);
|
|
|
|
if (infoPtr->dwStyle & LVS_OWNERDATA) return -1;
|
|
if (infoPtr->nItemCount == 0) return -1;
|
|
|
|
ID.id = iID;
|
|
index = DPA_Search(infoPtr->hdpaItemIds, &ID, -1, MapIdSearchCompare, 0, DPAS_SORTED);
|
|
|
|
if (index != -1)
|
|
{
|
|
ITEM_ID *lpID = DPA_GetPtr(infoPtr->hdpaItemIds, index);
|
|
return DPA_GetPtrIndex(infoPtr->hdpaItems, lpID->item);
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
/***
|
|
* DESCRIPTION:
|
|
* Returns the item id for index given.
|
|
*
|
|
* PARAMETER(S):
|
|
* [I] infoPtr : valid pointer to the listview structure
|
|
* [I] iItem : item index to get id for
|
|
*
|
|
* RETURN:
|
|
* Item id.
|
|
*/
|
|
static DWORD LISTVIEW_MapIndexToId(const LISTVIEW_INFO *infoPtr, INT iItem)
|
|
{
|
|
ITEM_INFO *lpItem;
|
|
HDPA hdpaSubItems;
|
|
|
|
TRACE("iItem=%d\n", iItem);
|
|
|
|
if (infoPtr->dwStyle & LVS_OWNERDATA) return -1;
|
|
if (iItem < 0 || iItem >= infoPtr->nItemCount) return -1;
|
|
|
|
hdpaSubItems = DPA_GetPtr(infoPtr->hdpaItems, iItem);
|
|
lpItem = DPA_GetPtr(hdpaSubItems, 0);
|
|
|
|
return lpItem->id->id;
|
|
}
|
|
|
|
/***
|
|
* DESCRIPTION:
|
|
* Returns the current icon position, and advances it along the top.
|
|
* The returned position is not offset by Origin.
|
|
*
|
|
* PARAMETER(S):
|
|
* [I] infoPtr : valid pointer to the listview structure
|
|
* [O] lpPos : will get the current icon position
|
|
*
|
|
* RETURN:
|
|
* None
|
|
*/
|
|
static void LISTVIEW_NextIconPosTop(LISTVIEW_INFO *infoPtr, LPPOINT lpPos)
|
|
{
|
|
INT nListWidth = infoPtr->rcList.right - infoPtr->rcList.left;
|
|
|
|
*lpPos = infoPtr->currIconPos;
|
|
|
|
infoPtr->currIconPos.x += infoPtr->nItemWidth;
|
|
if (infoPtr->currIconPos.x + infoPtr->nItemWidth <= nListWidth) return;
|
|
|
|
infoPtr->currIconPos.x = 0;
|
|
infoPtr->currIconPos.y += infoPtr->nItemHeight;
|
|
}
|
|
|
|
|
|
/***
|
|
* DESCRIPTION:
|
|
* Returns the current icon position, and advances it down the left edge.
|
|
* The returned position is not offset by Origin.
|
|
*
|
|
* PARAMETER(S):
|
|
* [I] infoPtr : valid pointer to the listview structure
|
|
* [O] lpPos : will get the current icon position
|
|
*
|
|
* RETURN:
|
|
* None
|
|
*/
|
|
static void LISTVIEW_NextIconPosLeft(LISTVIEW_INFO *infoPtr, LPPOINT lpPos)
|
|
{
|
|
INT nListHeight = infoPtr->rcList.bottom - infoPtr->rcList.top;
|
|
|
|
*lpPos = infoPtr->currIconPos;
|
|
|
|
infoPtr->currIconPos.y += infoPtr->nItemHeight;
|
|
if (infoPtr->currIconPos.y + infoPtr->nItemHeight <= nListHeight) return;
|
|
|
|
infoPtr->currIconPos.x += infoPtr->nItemWidth;
|
|
infoPtr->currIconPos.y = 0;
|
|
}
|
|
|
|
|
|
/***
|
|
* DESCRIPTION:
|
|
* Moves an icon to the specified position.
|
|
* It takes care of invalidating the item, etc.
|
|
*
|
|
* PARAMETER(S):
|
|
* [I] infoPtr : valid pointer to the listview structure
|
|
* [I] nItem : the item to move
|
|
* [I] lpPos : the new icon position
|
|
* [I] isNew : flags the item as being new
|
|
*
|
|
* RETURN:
|
|
* Success: TRUE
|
|
* Failure: FALSE
|
|
*/
|
|
static BOOL LISTVIEW_MoveIconTo(const LISTVIEW_INFO *infoPtr, INT nItem, const POINT *lppt, BOOL isNew)
|
|
{
|
|
POINT old;
|
|
|
|
if (!isNew)
|
|
{
|
|
old.x = (LONG_PTR)DPA_GetPtr(infoPtr->hdpaPosX, nItem);
|
|
old.y = (LONG_PTR)DPA_GetPtr(infoPtr->hdpaPosY, nItem);
|
|
|
|
if (lppt->x == old.x && lppt->y == old.y) return TRUE;
|
|
LISTVIEW_InvalidateItem(infoPtr, nItem);
|
|
}
|
|
|
|
/* Allocating a POINTER for every item is too resource intensive,
|
|
* so we'll keep the (x,y) in different arrays */
|
|
if (!DPA_SetPtr(infoPtr->hdpaPosX, nItem, (void *)(LONG_PTR)lppt->x)) return FALSE;
|
|
if (!DPA_SetPtr(infoPtr->hdpaPosY, nItem, (void *)(LONG_PTR)lppt->y)) return FALSE;
|
|
|
|
LISTVIEW_InvalidateItem(infoPtr, nItem);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
/***
|
|
* DESCRIPTION:
|
|
* Arranges listview items in icon display mode.
|
|
*
|
|
* PARAMETER(S):
|
|
* [I] infoPtr : valid pointer to the listview structure
|
|
* [I] nAlignCode : alignment code
|
|
*
|
|
* RETURN:
|
|
* SUCCESS : TRUE
|
|
* FAILURE : FALSE
|
|
*/
|
|
static BOOL LISTVIEW_Arrange(LISTVIEW_INFO *infoPtr, INT nAlignCode)
|
|
{
|
|
void (*next_pos)(LISTVIEW_INFO *, LPPOINT);
|
|
POINT pos;
|
|
INT i;
|
|
|
|
if (infoPtr->uView != LV_VIEW_ICON && infoPtr->uView != LV_VIEW_SMALLICON) return FALSE;
|
|
|
|
TRACE("nAlignCode=%d\n", nAlignCode);
|
|
|
|
if (nAlignCode == LVA_DEFAULT)
|
|
{
|
|
if (infoPtr->dwStyle & LVS_ALIGNLEFT) nAlignCode = LVA_ALIGNLEFT;
|
|
else nAlignCode = LVA_ALIGNTOP;
|
|
}
|
|
|
|
switch (nAlignCode)
|
|
{
|
|
case LVA_ALIGNLEFT: next_pos = LISTVIEW_NextIconPosLeft; break;
|
|
case LVA_ALIGNTOP: next_pos = LISTVIEW_NextIconPosTop; break;
|
|
case LVA_SNAPTOGRID: next_pos = LISTVIEW_NextIconPosTop; break; /* FIXME */
|
|
default: return FALSE;
|
|
}
|
|
|
|
infoPtr->bAutoarrange = TRUE;
|
|
infoPtr->currIconPos.x = infoPtr->currIconPos.y = 0;
|
|
for (i = 0; i < infoPtr->nItemCount; i++)
|
|
{
|
|
next_pos(infoPtr, &pos);
|
|
LISTVIEW_MoveIconTo(infoPtr, i, &pos, FALSE);
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
/***
|
|
* DESCRIPTION:
|
|
* Retrieves the bounding rectangle of all the items, not offset by Origin.
|
|
* For LVS_REPORT always returns empty rectangle.
|
|
*
|
|
* PARAMETER(S):
|
|
* [I] infoPtr : valid pointer to the listview structure
|
|
* [O] lprcView : bounding rectangle
|
|
*
|
|
* RETURN:
|
|
* SUCCESS : TRUE
|
|
* FAILURE : FALSE
|
|
*/
|
|
static void LISTVIEW_GetAreaRect(const LISTVIEW_INFO *infoPtr, LPRECT lprcView)
|
|
{
|
|
INT i, x, y;
|
|
|
|
SetRectEmpty(lprcView);
|
|
|
|
switch (infoPtr->uView)
|
|
{
|
|
case LV_VIEW_ICON:
|
|
case LV_VIEW_SMALLICON:
|
|
for (i = 0; i < infoPtr->nItemCount; i++)
|
|
{
|
|
x = (LONG_PTR)DPA_GetPtr(infoPtr->hdpaPosX, i);
|
|
y = (LONG_PTR)DPA_GetPtr(infoPtr->hdpaPosY, i);
|
|
lprcView->right = max(lprcView->right, x);
|
|
lprcView->bottom = max(lprcView->bottom, y);
|
|
}
|
|
if (infoPtr->nItemCount > 0)
|
|
{
|
|
lprcView->right += infoPtr->nItemWidth;
|
|
lprcView->bottom += infoPtr->nItemHeight;
|
|
}
|
|
break;
|
|
|
|
case LV_VIEW_LIST:
|
|
y = LISTVIEW_GetCountPerColumn(infoPtr);
|
|
x = infoPtr->nItemCount / y;
|
|
if (infoPtr->nItemCount % y) x++;
|
|
lprcView->right = x * infoPtr->nItemWidth;
|
|
lprcView->bottom = y * infoPtr->nItemHeight;
|
|
break;
|
|
}
|
|
}
|
|
|
|
/***
|
|
* DESCRIPTION:
|
|
* Retrieves the bounding rectangle of all the items.
|
|
*
|
|
* PARAMETER(S):
|
|
* [I] infoPtr : valid pointer to the listview structure
|
|
* [O] lprcView : bounding rectangle
|
|
*
|
|
* RETURN:
|
|
* SUCCESS : TRUE
|
|
* FAILURE : FALSE
|
|
*/
|
|
static BOOL LISTVIEW_GetViewRect(const LISTVIEW_INFO *infoPtr, LPRECT lprcView)
|
|
{
|
|
POINT ptOrigin;
|
|
|
|
TRACE("(lprcView=%p)\n", lprcView);
|
|
|
|
if (!lprcView) return FALSE;
|
|
|
|
LISTVIEW_GetAreaRect(infoPtr, lprcView);
|
|
|
|
if (infoPtr->uView != LV_VIEW_DETAILS)
|
|
{
|
|
LISTVIEW_GetOrigin(infoPtr, &ptOrigin);
|
|
OffsetRect(lprcView, ptOrigin.x, ptOrigin.y);
|
|
}
|
|
|
|
TRACE("lprcView=%s\n", wine_dbgstr_rect(lprcView));
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
/***
|
|
* DESCRIPTION:
|
|
* Retrieves the subitem pointer associated with the subitem index.
|
|
*
|
|
* PARAMETER(S):
|
|
* [I] hdpaSubItems : DPA handle for a specific item
|
|
* [I] nSubItem : index of subitem
|
|
*
|
|
* RETURN:
|
|
* SUCCESS : subitem pointer
|
|
* FAILURE : NULL
|
|
*/
|
|
static SUBITEM_INFO* LISTVIEW_GetSubItemPtr(HDPA hdpaSubItems, INT nSubItem)
|
|
{
|
|
SUBITEM_INFO *lpSubItem;
|
|
INT i;
|
|
|
|
/* we should binary search here if need be */
|
|
for (i = 1; i < DPA_GetPtrCount(hdpaSubItems); i++)
|
|
{
|
|
lpSubItem = DPA_GetPtr(hdpaSubItems, i);
|
|
if (lpSubItem->iSubItem == nSubItem)
|
|
return lpSubItem;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
|
|
/***
|
|
* DESCRIPTION:
|
|
* Calculates the desired item width.
|
|
*
|
|
* PARAMETER(S):
|
|
* [I] infoPtr : valid pointer to the listview structure
|
|
*
|
|
* RETURN:
|
|
* The desired item width.
|
|
*/
|
|
static INT LISTVIEW_CalculateItemWidth(const LISTVIEW_INFO *infoPtr)
|
|
{
|
|
INT nItemWidth = 0;
|
|
|
|
TRACE("uView=%d\n", infoPtr->uView);
|
|
|
|
if (infoPtr->uView == LV_VIEW_ICON)
|
|
nItemWidth = infoPtr->iconSpacing.cx;
|
|
else if (infoPtr->uView == LV_VIEW_DETAILS)
|
|
{
|
|
if (DPA_GetPtrCount(infoPtr->hdpaColumns) > 0)
|
|
{
|
|
RECT rcHeader;
|
|
INT index;
|
|
|
|
index = SendMessageW(infoPtr->hwndHeader, HDM_ORDERTOINDEX,
|
|
DPA_GetPtrCount(infoPtr->hdpaColumns) - 1, 0);
|
|
|
|
LISTVIEW_GetHeaderRect(infoPtr, index, &rcHeader);
|
|
nItemWidth = rcHeader.right;
|
|
}
|
|
}
|
|
else /* LV_VIEW_SMALLICON, or LV_VIEW_LIST */
|
|
{
|
|
WCHAR szDispText[DISP_TEXT_SIZE] = { '\0' };
|
|
LVITEMW lvItem;
|
|
INT i;
|
|
|
|
lvItem.mask = LVIF_TEXT;
|
|
lvItem.iSubItem = 0;
|
|
|
|
for (i = 0; i < infoPtr->nItemCount; i++)
|
|
{
|
|
lvItem.iItem = i;
|
|
lvItem.pszText = szDispText;
|
|
lvItem.cchTextMax = DISP_TEXT_SIZE;
|
|
if (LISTVIEW_GetItemW(infoPtr, &lvItem))
|
|
nItemWidth = max(LISTVIEW_GetStringWidthT(infoPtr, lvItem.pszText, TRUE),
|
|
nItemWidth);
|
|
}
|
|
|
|
if (infoPtr->himlSmall) nItemWidth += infoPtr->iconSize.cx;
|
|
if (infoPtr->himlState) nItemWidth += infoPtr->iconStateSize.cx;
|
|
|
|
nItemWidth = max(DEFAULT_COLUMN_WIDTH, nItemWidth + WIDTH_PADDING);
|
|
}
|
|
|
|
return nItemWidth;
|
|
}
|
|
|
|
/***
|
|
* DESCRIPTION:
|
|
* Calculates the desired item height.
|
|
*
|
|
* PARAMETER(S):
|
|
* [I] infoPtr : valid pointer to the listview structure
|
|
*
|
|
* RETURN:
|
|
* The desired item height.
|
|
*/
|
|
static INT LISTVIEW_CalculateItemHeight(const LISTVIEW_INFO *infoPtr)
|
|
{
|
|
INT nItemHeight;
|
|
|
|
TRACE("uView=%d\n", infoPtr->uView);
|
|
|
|
if (infoPtr->uView == LV_VIEW_ICON)
|
|
nItemHeight = infoPtr->iconSpacing.cy;
|
|
else
|
|
{
|
|
nItemHeight = infoPtr->ntmHeight;
|
|
if (infoPtr->himlState)
|
|
nItemHeight = max(nItemHeight, infoPtr->iconStateSize.cy);
|
|
if (infoPtr->himlSmall)
|
|
nItemHeight = max(nItemHeight, infoPtr->iconSize.cy);
|
|
nItemHeight += HEIGHT_PADDING;
|
|
if (infoPtr->nMeasureItemHeight > 0)
|
|
nItemHeight = infoPtr->nMeasureItemHeight;
|
|
}
|
|
|
|
return max(nItemHeight, 1);
|
|
}
|
|
|
|
/***
|
|
* DESCRIPTION:
|
|
* Updates the width, and height of an item.
|
|
*
|
|
* PARAMETER(S):
|
|
* [I] infoPtr : valid pointer to the listview structure
|
|
*
|
|
* RETURN:
|
|
* None.
|
|
*/
|
|
static inline void LISTVIEW_UpdateItemSize(LISTVIEW_INFO *infoPtr)
|
|
{
|
|
infoPtr->nItemWidth = LISTVIEW_CalculateItemWidth(infoPtr);
|
|
infoPtr->nItemHeight = LISTVIEW_CalculateItemHeight(infoPtr);
|
|
}
|
|
|
|
|
|
/***
|
|
* DESCRIPTION:
|
|
* Retrieves and saves important text metrics info for the current
|
|
* Listview font.
|
|
*
|
|
* PARAMETER(S):
|
|
* [I] infoPtr : valid pointer to the listview structure
|
|
*
|
|
*/
|
|
static void LISTVIEW_SaveTextMetrics(LISTVIEW_INFO *infoPtr)
|
|
{
|
|
HDC hdc = GetDC(infoPtr->hwndSelf);
|
|
HFONT hFont = infoPtr->hFont ? infoPtr->hFont : infoPtr->hDefaultFont;
|
|
HFONT hOldFont = SelectObject(hdc, hFont);
|
|
TEXTMETRICW tm;
|
|
SIZE sz;
|
|
|
|
if (GetTextMetricsW(hdc, &tm))
|
|
{
|
|
infoPtr->ntmHeight = tm.tmHeight;
|
|
infoPtr->ntmMaxCharWidth = tm.tmMaxCharWidth;
|
|
}
|
|
|
|
if (GetTextExtentPoint32A(hdc, "...", 3, &sz))
|
|
infoPtr->nEllipsisWidth = sz.cx;
|
|
|
|
SelectObject(hdc, hOldFont);
|
|
ReleaseDC(infoPtr->hwndSelf, hdc);
|
|
|
|
TRACE("tmHeight=%d\n", infoPtr->ntmHeight);
|
|
}
|
|
|
|
/***
|
|
* DESCRIPTION:
|
|
* A compare function for ranges
|
|
*
|
|
* PARAMETER(S)
|
|
* [I] range1 : pointer to range 1;
|
|
* [I] range2 : pointer to range 2;
|
|
* [I] flags : flags
|
|
*
|
|
* RETURNS:
|
|
* > 0 : if range 1 > range 2
|
|
* < 0 : if range 2 > range 1
|
|
* = 0 : if range intersects range 2
|
|
*/
|
|
static INT CALLBACK ranges_cmp(LPVOID range1, LPVOID range2, LPARAM flags)
|
|
{
|
|
INT cmp;
|
|
|
|
if (((RANGE*)range1)->upper <= ((RANGE*)range2)->lower)
|
|
cmp = -1;
|
|
else if (((RANGE*)range2)->upper <= ((RANGE*)range1)->lower)
|
|
cmp = 1;
|
|
else
|
|
cmp = 0;
|
|
|
|
TRACE("range1=%s, range2=%s, cmp=%d\n", debugrange(range1), debugrange(range2), cmp);
|
|
|
|
return cmp;
|
|
}
|
|
|
|
#define ranges_check(ranges, desc) if (TRACE_ON(listview)) ranges_assert(ranges, desc, __FILE__, __LINE__)
|
|
|
|
static void ranges_assert(RANGES ranges, LPCSTR desc, const char *file, int line)
|
|
{
|
|
INT i;
|
|
RANGE *prev, *curr;
|
|
|
|
TRACE("*** Checking %s:%d:%s ***\n", file, line, desc);
|
|
assert (ranges);
|
|
assert (DPA_GetPtrCount(ranges->hdpa) >= 0);
|
|
ranges_dump(ranges);
|
|
if (DPA_GetPtrCount(ranges->hdpa) > 0)
|
|
{
|
|
prev = DPA_GetPtr(ranges->hdpa, 0);
|
|
assert (prev->lower >= 0 && prev->lower < prev->upper);
|
|
for (i = 1; i < DPA_GetPtrCount(ranges->hdpa); i++)
|
|
{
|
|
curr = DPA_GetPtr(ranges->hdpa, i);
|
|
assert (prev->upper <= curr->lower);
|
|
assert (curr->lower < curr->upper);
|
|
prev = curr;
|
|
}
|
|
}
|
|
TRACE("--- Done checking---\n");
|
|
}
|
|
|
|
static RANGES ranges_create(int count)
|
|
{
|
|
RANGES ranges = Alloc(sizeof(struct tagRANGES));
|
|
if (!ranges) return NULL;
|
|
ranges->hdpa = DPA_Create(count);
|
|
if (ranges->hdpa) return ranges;
|
|
Free(ranges);
|
|
return NULL;
|
|
}
|
|
|
|
static void ranges_clear(RANGES ranges)
|
|
{
|
|
INT i;
|
|
|
|
for(i = 0; i < DPA_GetPtrCount(ranges->hdpa); i++)
|
|
Free(DPA_GetPtr(ranges->hdpa, i));
|
|
DPA_DeleteAllPtrs(ranges->hdpa);
|
|
}
|
|
|
|
|
|
static void ranges_destroy(RANGES ranges)
|
|
{
|
|
if (!ranges) return;
|
|
ranges_clear(ranges);
|
|
DPA_Destroy(ranges->hdpa);
|
|
Free(ranges);
|
|
}
|
|
|
|
static RANGES ranges_clone(RANGES ranges)
|
|
{
|
|
RANGES clone;
|
|
INT i;
|
|
|
|
if (!(clone = ranges_create(DPA_GetPtrCount(ranges->hdpa)))) goto fail;
|
|
|
|
for (i = 0; i < DPA_GetPtrCount(ranges->hdpa); i++)
|
|
{
|
|
RANGE *newrng = Alloc(sizeof(RANGE));
|
|
if (!newrng) goto fail;
|
|
*newrng = *((RANGE*)DPA_GetPtr(ranges->hdpa, i));
|
|
DPA_SetPtr(clone->hdpa, i, newrng);
|
|
}
|
|
return clone;
|
|
|
|
fail:
|
|
TRACE ("clone failed\n");
|
|
ranges_destroy(clone);
|
|
return NULL;
|
|
}
|
|
|
|
static RANGES ranges_diff(RANGES ranges, RANGES sub)
|
|
{
|
|
INT i;
|
|
|
|
for (i = 0; i < DPA_GetPtrCount(sub->hdpa); i++)
|
|
ranges_del(ranges, *((RANGE *)DPA_GetPtr(sub->hdpa, i)));
|
|
|
|
return ranges;
|
|
}
|
|
|
|
static void ranges_dump(RANGES ranges)
|
|
{
|
|
INT i;
|
|
|
|
for (i = 0; i < DPA_GetPtrCount(ranges->hdpa); i++)
|
|
TRACE(" %s\n", debugrange(DPA_GetPtr(ranges->hdpa, i)));
|
|
}
|
|
|
|
static inline BOOL ranges_contain(RANGES ranges, INT nItem)
|
|
{
|
|
RANGE srchrng = { nItem, nItem + 1 };
|
|
|
|
TRACE("(nItem=%d)\n", nItem);
|
|
ranges_check(ranges, "before contain");
|
|
return DPA_Search(ranges->hdpa, &srchrng, 0, ranges_cmp, 0, DPAS_SORTED) != -1;
|
|
}
|
|
|
|
static INT ranges_itemcount(RANGES ranges)
|
|
{
|
|
INT i, count = 0;
|
|
|
|
for (i = 0; i < DPA_GetPtrCount(ranges->hdpa); i++)
|
|
{
|
|
RANGE *sel = DPA_GetPtr(ranges->hdpa, i);
|
|
count += sel->upper - sel->lower;
|
|
}
|
|
|
|
return count;
|
|
}
|
|
|
|
static BOOL ranges_shift(RANGES ranges, INT nItem, INT delta, INT nUpper)
|
|
{
|
|
RANGE srchrng = { nItem, nItem + 1 }, *chkrng;
|
|
INT index;
|
|
|
|
index = DPA_Search(ranges->hdpa, &srchrng, 0, ranges_cmp, 0, DPAS_SORTED | DPAS_INSERTAFTER);
|
|
if (index == -1) return TRUE;
|
|
|
|
for (; index < DPA_GetPtrCount(ranges->hdpa); index++)
|
|
{
|
|
chkrng = DPA_GetPtr(ranges->hdpa, index);
|
|
if (chkrng->lower >= nItem)
|
|
chkrng->lower = max(min(chkrng->lower + delta, nUpper - 1), 0);
|
|
if (chkrng->upper > nItem)
|
|
chkrng->upper = max(min(chkrng->upper + delta, nUpper), 0);
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
static BOOL ranges_add(RANGES ranges, RANGE range)
|
|
{
|
|
RANGE srchrgn;
|
|
INT index;
|
|
|
|
TRACE("(%s)\n", debugrange(&range));
|
|
ranges_check(ranges, "before add");
|
|
|
|
/* try find overlapping regions first */
|
|
srchrgn.lower = range.lower - 1;
|
|
srchrgn.upper = range.upper + 1;
|
|
index = DPA_Search(ranges->hdpa, &srchrgn, 0, ranges_cmp, 0, DPAS_SORTED);
|
|
|
|
if (index == -1)
|
|
{
|
|
RANGE *newrgn;
|
|
|
|
TRACE("Adding new range\n");
|
|
|
|
/* create the brand new range to insert */
|
|
newrgn = Alloc(sizeof(RANGE));
|
|
if(!newrgn) goto fail;
|
|
*newrgn = range;
|
|
|
|
/* figure out where to insert it */
|
|
index = DPA_Search(ranges->hdpa, newrgn, 0, ranges_cmp, 0, DPAS_SORTED | DPAS_INSERTAFTER);
|
|
TRACE("index=%d\n", index);
|
|
if (index == -1) index = 0;
|
|
|
|
/* and get it over with */
|
|
if (DPA_InsertPtr(ranges->hdpa, index, newrgn) == -1)
|
|
{
|
|
Free(newrgn);
|
|
goto fail;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
RANGE *chkrgn, *mrgrgn;
|
|
INT fromindex, mergeindex;
|
|
|
|
chkrgn = DPA_GetPtr(ranges->hdpa, index);
|
|
TRACE("Merge with %s @%d\n", debugrange(chkrgn), index);
|
|
|
|
chkrgn->lower = min(range.lower, chkrgn->lower);
|
|
chkrgn->upper = max(range.upper, chkrgn->upper);
|
|
|
|
TRACE("New range %s @%d\n", debugrange(chkrgn), index);
|
|
|
|
/* merge now common ranges */
|
|
fromindex = 0;
|
|
srchrgn.lower = chkrgn->lower - 1;
|
|
srchrgn.upper = chkrgn->upper + 1;
|
|
|
|
do
|
|
{
|
|
mergeindex = DPA_Search(ranges->hdpa, &srchrgn, fromindex, ranges_cmp, 0, 0);
|
|
if (mergeindex == -1) break;
|
|
if (mergeindex == index)
|
|
{
|
|
fromindex = index + 1;
|
|
continue;
|
|
}
|
|
|
|
TRACE("Merge with index %i\n", mergeindex);
|
|
|
|
mrgrgn = DPA_GetPtr(ranges->hdpa, mergeindex);
|
|
chkrgn->lower = min(chkrgn->lower, mrgrgn->lower);
|
|
chkrgn->upper = max(chkrgn->upper, mrgrgn->upper);
|
|
Free(mrgrgn);
|
|
DPA_DeletePtr(ranges->hdpa, mergeindex);
|
|
if (mergeindex < index) index --;
|
|
} while(1);
|
|
}
|
|
|
|
ranges_check(ranges, "after add");
|
|
return TRUE;
|
|
|
|
fail:
|
|
ranges_check(ranges, "failed add");
|
|
return FALSE;
|
|
}
|
|
|
|
static BOOL ranges_del(RANGES ranges, RANGE range)
|
|
{
|
|
RANGE *chkrgn;
|
|
INT index;
|
|
|
|
TRACE("(%s)\n", debugrange(&range));
|
|
ranges_check(ranges, "before del");
|
|
|
|
/* we don't use DPAS_SORTED here, since we need *
|
|
* to find the first overlapping range */
|
|
index = DPA_Search(ranges->hdpa, &range, 0, ranges_cmp, 0, 0);
|
|
while(index != -1)
|
|
{
|
|
chkrgn = DPA_GetPtr(ranges->hdpa, index);
|
|
|
|
TRACE("Matches range %s @%d\n", debugrange(chkrgn), index);
|
|
|
|
/* case 1: Same range */
|
|
if ( (chkrgn->upper == range.upper) &&
|
|
(chkrgn->lower == range.lower) )
|
|
{
|
|
DPA_DeletePtr(ranges->hdpa, index);
|
|
Free(chkrgn);
|
|
break;
|
|
}
|
|
/* case 2: engulf */
|
|
else if ( (chkrgn->upper <= range.upper) &&
|
|
(chkrgn->lower >= range.lower) )
|
|
{
|
|
DPA_DeletePtr(ranges->hdpa, index);
|
|
Free(chkrgn);
|
|
}
|
|
/* case 3: overlap upper */
|
|
else if ( (chkrgn->upper <= range.upper) &&
|
|
(chkrgn->lower < range.lower) )
|
|
{
|
|
chkrgn->upper = range.lower;
|
|
}
|
|
/* case 4: overlap lower */
|
|
else if ( (chkrgn->upper > range.upper) &&
|
|
(chkrgn->lower >= range.lower) )
|
|
{
|
|
chkrgn->lower = range.upper;
|
|
break;
|
|
}
|
|
/* case 5: fully internal */
|
|
else
|
|
{
|
|
RANGE *newrgn;
|
|
|
|
if (!(newrgn = Alloc(sizeof(RANGE)))) goto fail;
|
|
newrgn->lower = chkrgn->lower;
|
|
newrgn->upper = range.lower;
|
|
chkrgn->lower = range.upper;
|
|
if (DPA_InsertPtr(ranges->hdpa, index, newrgn) == -1)
|
|
{
|
|
Free(newrgn);
|
|
goto fail;
|
|
}
|
|
break;
|
|
}
|
|
|
|
index = DPA_Search(ranges->hdpa, &range, index, ranges_cmp, 0, 0);
|
|
}
|
|
|
|
ranges_check(ranges, "after del");
|
|
return TRUE;
|
|
|
|
fail:
|
|
ranges_check(ranges, "failed del");
|
|
return FALSE;
|
|
}
|
|
|
|
/***
|
|
* DESCRIPTION:
|
|
* Removes all selection ranges
|
|
*
|
|
* Parameters(s):
|
|
* [I] infoPtr : valid pointer to the listview structure
|
|
* [I] toSkip : item range to skip removing the selection
|
|
*
|
|
* RETURNS:
|
|
* SUCCESS : TRUE
|
|
* FAILURE : FALSE
|
|
*/
|
|
static BOOL LISTVIEW_DeselectAllSkipItems(LISTVIEW_INFO *infoPtr, RANGES toSkip)
|
|
{
|
|
LVITEMW lvItem;
|
|
ITERATOR i;
|
|
RANGES clone;
|
|
|
|
TRACE("()\n");
|
|
|
|
lvItem.state = 0;
|
|
lvItem.stateMask = LVIS_SELECTED;
|
|
|
|
/* need to clone the DPA because callbacks can change it */
|
|
if (!(clone = ranges_clone(infoPtr->selectionRanges))) return FALSE;
|
|
iterator_rangesitems(&i, ranges_diff(clone, toSkip));
|
|
while(iterator_next(&i))
|
|
LISTVIEW_SetItemState(infoPtr, i.nItem, &lvItem);
|
|
/* note that the iterator destructor will free the cloned range */
|
|
iterator_destroy(&i);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static inline BOOL LISTVIEW_DeselectAllSkipItem(LISTVIEW_INFO *infoPtr, INT nItem)
|
|
{
|
|
RANGES toSkip;
|
|
|
|
if (!(toSkip = ranges_create(1))) return FALSE;
|
|
if (nItem != -1) ranges_additem(toSkip, nItem);
|
|
LISTVIEW_DeselectAllSkipItems(infoPtr, toSkip);
|
|
ranges_destroy(toSkip);
|
|
return TRUE;
|
|
}
|
|
|
|
static inline BOOL LISTVIEW_DeselectAll(LISTVIEW_INFO *infoPtr)
|
|
{
|
|
return LISTVIEW_DeselectAllSkipItem(infoPtr, -1);
|
|
}
|
|
|
|
/***
|
|
* DESCRIPTION:
|
|
* Retrieves the number of items that are marked as selected.
|
|
*
|
|
* PARAMETER(S):
|
|
* [I] infoPtr : valid pointer to the listview structure
|
|
*
|
|
* RETURN:
|
|
* Number of items selected.
|
|
*/
|
|
static INT LISTVIEW_GetSelectedCount(const LISTVIEW_INFO *infoPtr)
|
|
{
|
|
INT nSelectedCount = 0;
|
|
|
|
if (infoPtr->uCallbackMask & LVIS_SELECTED)
|
|
{
|
|
INT i;
|
|
for (i = 0; i < infoPtr->nItemCount; i++)
|
|
{
|
|
if (LISTVIEW_GetItemState(infoPtr, i, LVIS_SELECTED))
|
|
nSelectedCount++;
|
|
}
|
|
}
|
|
else
|
|
nSelectedCount = ranges_itemcount(infoPtr->selectionRanges);
|
|
|
|
TRACE("nSelectedCount=%d\n", nSelectedCount);
|
|
return nSelectedCount;
|
|
}
|
|
|
|
/***
|
|
* DESCRIPTION:
|
|
* Manages the item focus.
|
|
*
|
|
* PARAMETER(S):
|
|
* [I] infoPtr : valid pointer to the listview structure
|
|
* [I] nItem : item index
|
|
*
|
|
* RETURN:
|
|
* TRUE : focused item changed
|
|
* FALSE : focused item has NOT changed
|
|
*/
|
|
static inline BOOL LISTVIEW_SetItemFocus(LISTVIEW_INFO *infoPtr, INT nItem)
|
|
{
|
|
INT oldFocus = infoPtr->nFocusedItem;
|
|
LVITEMW lvItem;
|
|
|
|
if (nItem == infoPtr->nFocusedItem) return FALSE;
|
|
|
|
lvItem.state = nItem == -1 ? 0 : LVIS_FOCUSED;
|
|
lvItem.stateMask = LVIS_FOCUSED;
|
|
LISTVIEW_SetItemState(infoPtr, nItem == -1 ? infoPtr->nFocusedItem : nItem, &lvItem);
|
|
|
|
return oldFocus != infoPtr->nFocusedItem;
|
|
}
|
|
|
|
static INT shift_item(const LISTVIEW_INFO *infoPtr, INT nShiftItem, INT nItem, INT direction)
|
|
{
|
|
if (nShiftItem < nItem) return nShiftItem;
|
|
|
|
if (nShiftItem > nItem) return nShiftItem + direction;
|
|
|
|
if (direction > 0) return nShiftItem + direction;
|
|
|
|
return min(nShiftItem, infoPtr->nItemCount - 1);
|
|
}
|
|
|
|
/* This function updates focus index.
|
|
|
|
Parameters:
|
|
focus : current focus index
|
|
item : index of item to be added/removed
|
|
direction : add/remove flag
|
|
*/
|
|
static void LISTVIEW_ShiftFocus(LISTVIEW_INFO *infoPtr, INT focus, INT item, INT direction)
|
|
{
|
|
BOOL old_change = infoPtr->bDoChangeNotify;
|
|
|
|
infoPtr->bDoChangeNotify = FALSE;
|
|
focus = shift_item(infoPtr, focus, item, direction);
|
|
if (focus != infoPtr->nFocusedItem)
|
|
LISTVIEW_SetItemFocus(infoPtr, focus);
|
|
infoPtr->bDoChangeNotify = old_change;
|
|
}
|
|
|
|
/**
|
|
* DESCRIPTION:
|
|
* Updates the various indices after an item has been inserted or deleted.
|
|
*
|
|
* PARAMETER(S):
|
|
* [I] infoPtr : valid pointer to the listview structure
|
|
* [I] nItem : item index
|
|
* [I] direction : Direction of shift, +1 or -1.
|
|
*
|
|
* RETURN:
|
|
* None
|
|
*/
|
|
static void LISTVIEW_ShiftIndices(LISTVIEW_INFO *infoPtr, INT nItem, INT direction)
|
|
{
|
|
TRACE("Shifting %i, %i steps\n", nItem, direction);
|
|
|
|
ranges_shift(infoPtr->selectionRanges, nItem, direction, infoPtr->nItemCount);
|
|
assert(abs(direction) == 1);
|
|
infoPtr->nSelectionMark = shift_item(infoPtr, infoPtr->nSelectionMark, nItem, direction);
|
|
|
|
/* But we are not supposed to modify nHotItem! */
|
|
}
|
|
|
|
/**
|
|
* DESCRIPTION:
|
|
* Adds a block of selections.
|
|
*
|
|
* PARAMETER(S):
|
|
* [I] infoPtr : valid pointer to the listview structure
|
|
* [I] nItem : item index
|
|
*
|
|
* RETURN:
|
|
* Whether the window is still valid.
|
|
*/
|
|
static BOOL LISTVIEW_AddGroupSelection(LISTVIEW_INFO *infoPtr, INT nItem)
|
|
{
|
|
INT nFirst = min(infoPtr->nSelectionMark, nItem);
|
|
INT nLast = max(infoPtr->nSelectionMark, nItem);
|
|
HWND hwndSelf = infoPtr->hwndSelf;
|
|
NMLVODSTATECHANGE nmlv;
|
|
LVITEMW item;
|
|
BOOL bOldChange;
|
|
INT i;
|
|
|
|
/* Temporarily disable change notification
|
|
* If the control is LVS_OWNERDATA, we need to send
|
|
* only one LVN_ODSTATECHANGED notification.
|
|
* See MSDN documentation for LVN_ITEMCHANGED.
|
|
*/
|
|
bOldChange = infoPtr->bDoChangeNotify;
|
|
if (infoPtr->dwStyle & LVS_OWNERDATA) infoPtr->bDoChangeNotify = FALSE;
|
|
|
|
if (nFirst == -1) nFirst = nItem;
|
|
|
|
item.state = LVIS_SELECTED;
|
|
item.stateMask = LVIS_SELECTED;
|
|
|
|
for (i = nFirst; i <= nLast; i++)
|
|
LISTVIEW_SetItemState(infoPtr,i,&item);
|
|
|
|
ZeroMemory(&nmlv, sizeof(nmlv));
|
|
nmlv.iFrom = nFirst;
|
|
nmlv.iTo = nLast;
|
|
nmlv.uOldState = 0;
|
|
nmlv.uNewState = item.state;
|
|
|
|
notify_hdr(infoPtr, LVN_ODSTATECHANGED, (LPNMHDR)&nmlv);
|
|
if (!IsWindow(hwndSelf))
|
|
return FALSE;
|
|
infoPtr->bDoChangeNotify = bOldChange;
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
/***
|
|
* DESCRIPTION:
|
|
* Sets a single group selection.
|
|
*
|
|
* PARAMETER(S):
|
|
* [I] infoPtr : valid pointer to the listview structure
|
|
* [I] nItem : item index
|
|
*
|
|
* RETURN:
|
|
* None
|
|
*/
|
|
static void LISTVIEW_SetGroupSelection(LISTVIEW_INFO *infoPtr, INT nItem)
|
|
{
|
|
RANGES selection;
|
|
LVITEMW item;
|
|
ITERATOR i;
|
|
BOOL bOldChange;
|
|
|
|
if (!(selection = ranges_create(100))) return;
|
|
|
|
item.state = LVIS_SELECTED;
|
|
item.stateMask = LVIS_SELECTED;
|
|
|
|
if ((infoPtr->uView == LV_VIEW_LIST) || (infoPtr->uView == LV_VIEW_DETAILS))
|
|
{
|
|
if (infoPtr->nSelectionMark == -1)
|
|
{
|
|
infoPtr->nSelectionMark = nItem;
|
|
ranges_additem(selection, nItem);
|
|
}
|
|
else
|
|
{
|
|
RANGE sel;
|
|
|
|
sel.lower = min(infoPtr->nSelectionMark, nItem);
|
|
sel.upper = max(infoPtr->nSelectionMark, nItem) + 1;
|
|
ranges_add(selection, sel);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
RECT rcItem, rcSel, rcSelMark;
|
|
POINT ptItem;
|
|
|
|
rcItem.left = LVIR_BOUNDS;
|
|
if (!LISTVIEW_GetItemRect(infoPtr, nItem, &rcItem)) {
|
|
ranges_destroy (selection);
|
|
return;
|
|
}
|
|
rcSelMark.left = LVIR_BOUNDS;
|
|
if (!LISTVIEW_GetItemRect(infoPtr, infoPtr->nSelectionMark, &rcSelMark)) {
|
|
ranges_destroy (selection);
|
|
return;
|
|
}
|
|
UnionRect(&rcSel, &rcItem, &rcSelMark);
|
|
iterator_frameditems(&i, infoPtr, &rcSel);
|
|
while(iterator_next(&i))
|
|
{
|
|
LISTVIEW_GetItemPosition(infoPtr, i.nItem, &ptItem);
|
|
if (PtInRect(&rcSel, ptItem)) ranges_additem(selection, i.nItem);
|
|
}
|
|
iterator_destroy(&i);
|
|
}
|
|
|
|
/* disable per item notifications on LVS_OWNERDATA style
|
|
FIXME: single LVN_ODSTATECHANGED should be used */
|
|
bOldChange = infoPtr->bDoChangeNotify;
|
|
if (infoPtr->dwStyle & LVS_OWNERDATA) infoPtr->bDoChangeNotify = FALSE;
|
|
|
|
LISTVIEW_DeselectAllSkipItems(infoPtr, selection);
|
|
|
|
|
|
iterator_rangesitems(&i, selection);
|
|
while(iterator_next(&i))
|
|
LISTVIEW_SetItemState(infoPtr, i.nItem, &item);
|
|
/* this will also destroy the selection */
|
|
iterator_destroy(&i);
|
|
|
|
infoPtr->bDoChangeNotify = bOldChange;
|
|
|
|
LISTVIEW_SetItemFocus(infoPtr, nItem);
|
|
}
|
|
|
|
/***
|
|
* DESCRIPTION:
|
|
* Sets a single selection.
|
|
*
|
|
* PARAMETER(S):
|
|
* [I] infoPtr : valid pointer to the listview structure
|
|
* [I] nItem : item index
|
|
*
|
|
* RETURN:
|
|
* None
|
|
*/
|
|
static void LISTVIEW_SetSelection(LISTVIEW_INFO *infoPtr, INT nItem)
|
|
{
|
|
LVITEMW lvItem;
|
|
|
|
TRACE("nItem=%d\n", nItem);
|
|
|
|
LISTVIEW_DeselectAllSkipItem(infoPtr, nItem);
|
|
|
|
lvItem.state = LVIS_FOCUSED | LVIS_SELECTED;
|
|
lvItem.stateMask = LVIS_FOCUSED | LVIS_SELECTED;
|
|
LISTVIEW_SetItemState(infoPtr, nItem, &lvItem);
|
|
|
|
infoPtr->nSelectionMark = nItem;
|
|
}
|
|
|
|
/***
|
|
* DESCRIPTION:
|
|
* Set selection(s) with keyboard.
|
|
*
|
|
* PARAMETER(S):
|
|
* [I] infoPtr : valid pointer to the listview structure
|
|
* [I] nItem : item index
|
|
* [I] space : VK_SPACE code sent
|
|
*
|
|
* RETURN:
|
|
* SUCCESS : TRUE (needs to be repainted)
|
|
* FAILURE : FALSE (nothing has changed)
|
|
*/
|
|
static BOOL LISTVIEW_KeySelection(LISTVIEW_INFO *infoPtr, INT nItem, BOOL space)
|
|
{
|
|
/* FIXME: pass in the state */
|
|
WORD wShift = HIWORD(GetKeyState(VK_SHIFT));
|
|
WORD wCtrl = HIWORD(GetKeyState(VK_CONTROL));
|
|
BOOL bResult = FALSE;
|
|
|
|
TRACE("nItem=%d, wShift=%d, wCtrl=%d\n", nItem, wShift, wCtrl);
|
|
if ((nItem >= 0) && (nItem < infoPtr->nItemCount))
|
|
{
|
|
bResult = TRUE;
|
|
|
|
if (infoPtr->dwStyle & LVS_SINGLESEL || (wShift == 0 && wCtrl == 0))
|
|
LISTVIEW_SetSelection(infoPtr, nItem);
|
|
else
|
|
{
|
|
if (wShift)
|
|
LISTVIEW_SetGroupSelection(infoPtr, nItem);
|
|
else if (wCtrl)
|
|
{
|
|
LVITEMW lvItem;
|
|
lvItem.state = ~LISTVIEW_GetItemState(infoPtr, nItem, LVIS_SELECTED);
|
|
lvItem.stateMask = LVIS_SELECTED;
|
|
if (space)
|
|
{
|
|
LISTVIEW_SetItemState(infoPtr, nItem, &lvItem);
|
|
if (lvItem.state & LVIS_SELECTED)
|
|
infoPtr->nSelectionMark = nItem;
|
|
}
|
|
bResult = LISTVIEW_SetItemFocus(infoPtr, nItem);
|
|
}
|
|
}
|
|
LISTVIEW_EnsureVisible(infoPtr, nItem, FALSE);
|
|
}
|
|
|
|
UpdateWindow(infoPtr->hwndSelf); /* update client area */
|
|
return bResult;
|
|
}
|
|
|
|
static BOOL LISTVIEW_GetItemAtPt(const LISTVIEW_INFO *infoPtr, LPLVITEMW lpLVItem, POINT pt)
|
|
{
|
|
LVHITTESTINFO lvHitTestInfo;
|
|
|
|
ZeroMemory(&lvHitTestInfo, sizeof(lvHitTestInfo));
|
|
lvHitTestInfo.pt.x = pt.x;
|
|
lvHitTestInfo.pt.y = pt.y;
|
|
|
|
LISTVIEW_HitTest(infoPtr, &lvHitTestInfo, TRUE, FALSE);
|
|
|
|
lpLVItem->mask = LVIF_PARAM;
|
|
lpLVItem->iItem = lvHitTestInfo.iItem;
|
|
lpLVItem->iSubItem = 0;
|
|
|
|
return LISTVIEW_GetItemT(infoPtr, lpLVItem, TRUE);
|
|
}
|
|
|
|
static inline BOOL LISTVIEW_IsHotTracking(const LISTVIEW_INFO *infoPtr)
|
|
{
|
|
return ((infoPtr->dwLvExStyle & LVS_EX_TRACKSELECT) ||
|
|
(infoPtr->dwLvExStyle & LVS_EX_ONECLICKACTIVATE) ||
|
|
(infoPtr->dwLvExStyle & LVS_EX_TWOCLICKACTIVATE));
|
|
}
|
|
|
|
/***
|
|
* DESCRIPTION:
|
|
* Called when the mouse is being actively tracked and has hovered for a specified
|
|
* amount of time
|
|
*
|
|
* PARAMETER(S):
|
|
* [I] infoPtr : valid pointer to the listview structure
|
|
* [I] fwKeys : key indicator
|
|
* [I] x,y : mouse position
|
|
*
|
|
* RETURN:
|
|
* 0 if the message was processed, non-zero if there was an error
|
|
*
|
|
* INFO:
|
|
* LVS_EX_TRACKSELECT: An item is automatically selected when the cursor remains
|
|
* over the item for a certain period of time.
|
|
*
|
|
*/
|
|
static LRESULT LISTVIEW_MouseHover(LISTVIEW_INFO *infoPtr, INT x, INT y)
|
|
{
|
|
NMHDR hdr;
|
|
|
|
if (notify_hdr(infoPtr, NM_HOVER, &hdr)) return 0;
|
|
|
|
if (LISTVIEW_IsHotTracking(infoPtr))
|
|
{
|
|
LVITEMW item;
|
|
POINT pt;
|
|
|
|
pt.x = x;
|
|
pt.y = y;
|
|
|
|
if (LISTVIEW_GetItemAtPt(infoPtr, &item, pt))
|
|
LISTVIEW_SetSelection(infoPtr, item.iItem);
|
|
|
|
SetFocus(infoPtr->hwndSelf);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
#define SCROLL_LEFT 0x1
|
|
#define SCROLL_RIGHT 0x2
|
|
#define SCROLL_UP 0x4
|
|
#define SCROLL_DOWN 0x8
|
|
|
|
/***
|
|
* DESCRIPTION:
|
|
* Utility routine to draw and highlight items within a marquee selection rectangle.
|
|
*
|
|
* PARAMETER(S):
|
|
* [I] infoPtr : valid pointer to the listview structure
|
|
* [I] coords_orig : original co-ordinates of the cursor
|
|
* [I] coords_offs : offsetted coordinates of the cursor
|
|
* [I] offset : offset amount
|
|
* [I] scroll : Bitmask of which directions we should scroll, if at all
|
|
*
|
|
* RETURN:
|
|
* None.
|
|
*/
|
|
static void LISTVIEW_MarqueeHighlight(LISTVIEW_INFO *infoPtr, const POINT *coords_orig,
|
|
const POINT *coords_offs, const POINT *offset,
|
|
INT scroll)
|
|
{
|
|
BOOL controlDown = FALSE;
|
|
LVITEMW item;
|
|
ITERATOR old_elems, new_elems;
|
|
RECT rect;
|
|
|
|
if (coords_offs->x > infoPtr->marqueeOrigin.x)
|
|
{
|
|
rect.left = infoPtr->marqueeOrigin.x;
|
|
rect.right = coords_offs->x;
|
|
}
|
|
else
|
|
{
|
|
rect.left = coords_offs->x;
|
|
rect.right = infoPtr->marqueeOrigin.x;
|
|
}
|
|
|
|
if (coords_offs->y > infoPtr->marqueeOrigin.y)
|
|
{
|
|
rect.top = infoPtr->marqueeOrigin.y;
|
|
rect.bottom = coords_offs->y;
|
|
}
|
|
else
|
|
{
|
|
rect.top = coords_offs->y;
|
|
rect.bottom = infoPtr->marqueeOrigin.y;
|
|
}
|
|
|
|
/* Cancel out the old marquee rectangle and draw the new one */
|
|
LISTVIEW_InvalidateRect(infoPtr, &infoPtr->marqueeDrawRect);
|
|
|
|
/* Scroll by the appropriate distance if applicable - speed up scrolling as
|
|
the cursor is further away */
|
|
|
|
if ((scroll & SCROLL_LEFT) && (coords_orig->x <= 0))
|
|
LISTVIEW_Scroll(infoPtr, coords_orig->x, 0);
|
|
|
|
if ((scroll & SCROLL_RIGHT) && (coords_orig->x >= infoPtr->rcList.right))
|
|
LISTVIEW_Scroll(infoPtr, (coords_orig->x - infoPtr->rcList.right), 0);
|
|
|
|
if ((scroll & SCROLL_UP) && (coords_orig->y <= 0))
|
|
LISTVIEW_Scroll(infoPtr, 0, coords_orig->y);
|
|
|
|
if ((scroll & SCROLL_DOWN) && (coords_orig->y >= infoPtr->rcList.bottom))
|
|
LISTVIEW_Scroll(infoPtr, 0, (coords_orig->y - infoPtr->rcList.bottom));
|
|
|
|
iterator_frameditems_absolute(&old_elems, infoPtr, &infoPtr->marqueeRect);
|
|
|
|
CopyRect(&infoPtr->marqueeRect, &rect);
|
|
|
|
CopyRect(&infoPtr->marqueeDrawRect, &rect);
|
|
OffsetRect(&infoPtr->marqueeDrawRect, offset->x, offset->y);
|
|
|
|
iterator_frameditems_absolute(&new_elems, infoPtr, &infoPtr->marqueeRect);
|
|
iterator_remove_common_items(&old_elems, &new_elems);
|
|
|
|
/* Iterate over no longer selected items */
|
|
while (iterator_next(&old_elems))
|
|
{
|
|
if (old_elems.nItem > -1)
|
|
{
|
|
if (LISTVIEW_GetItemState(infoPtr, old_elems.nItem, LVIS_SELECTED) == LVIS_SELECTED)
|
|
item.state = 0;
|
|
else
|
|
item.state = LVIS_SELECTED;
|
|
|
|
item.stateMask = LVIS_SELECTED;
|
|
|
|
LISTVIEW_SetItemState(infoPtr, old_elems.nItem, &item);
|
|
}
|
|
}
|
|
iterator_destroy(&old_elems);
|
|
|
|
|
|
/* Iterate over newly selected items */
|
|
if (GetKeyState(VK_CONTROL) & 0x8000)
|
|
controlDown = TRUE;
|
|
|
|
while (iterator_next(&new_elems))
|
|
{
|
|
if (new_elems.nItem > -1)
|
|
{
|
|
/* If CTRL is pressed, invert. If not, always select the item. */
|
|
if ((controlDown) && (LISTVIEW_GetItemState(infoPtr, new_elems.nItem, LVIS_SELECTED)))
|
|
item.state = 0;
|
|
else
|
|
item.state = LVIS_SELECTED;
|
|
|
|
item.stateMask = LVIS_SELECTED;
|
|
|
|
LISTVIEW_SetItemState(infoPtr, new_elems.nItem, &item);
|
|
}
|
|
}
|
|
iterator_destroy(&new_elems);
|
|
|
|
LISTVIEW_InvalidateRect(infoPtr, &infoPtr->marqueeDrawRect);
|
|
}
|
|
|
|
/***
|
|
* DESCRIPTION:
|
|
* Called when we are in a marquee selection that involves scrolling the listview (ie,
|
|
* the cursor is outside the bounds of the client area). This is a TIMERPROC.
|
|
*
|
|
* PARAMETER(S):
|
|
* [I] hwnd : Handle to the listview
|
|
* [I] uMsg : WM_TIMER (ignored)
|
|
* [I] idEvent : The timer ID interpreted as a pointer to a LISTVIEW_INFO struct
|
|
* [I] dwTimer : The elapsed time (ignored)
|
|
*
|
|
* RETURN:
|
|
* None.
|
|
*/
|
|
static VOID CALLBACK LISTVIEW_ScrollTimer(HWND hWnd, UINT uMsg, UINT_PTR idEvent, DWORD dwTime)
|
|
{
|
|
LISTVIEW_INFO *infoPtr;
|
|
SCROLLINFO scrollInfo;
|
|
POINT coords_orig;
|
|
POINT coords_offs;
|
|
POINT offset;
|
|
INT scroll = 0;
|
|
|
|
infoPtr = (LISTVIEW_INFO *) idEvent;
|
|
|
|
if (!infoPtr)
|
|
return;
|
|
|
|
/* Get the current cursor position and convert to client coordinates */
|
|
GetCursorPos(&coords_orig);
|
|
ScreenToClient(hWnd, &coords_orig);
|
|
|
|
/* Ensure coordinates are within client bounds */
|
|
coords_offs.x = max(min(coords_orig.x, infoPtr->rcList.right), 0);
|
|
coords_offs.y = max(min(coords_orig.y, infoPtr->rcList.bottom), 0);
|
|
|
|
/* Get offset */
|
|
LISTVIEW_GetOrigin(infoPtr, &offset);
|
|
|
|
/* Offset coordinates by the appropriate amount */
|
|
coords_offs.x -= offset.x;
|
|
coords_offs.y -= offset.y;
|
|
|
|
scrollInfo.cbSize = sizeof(SCROLLINFO);
|
|
scrollInfo.fMask = SIF_ALL;
|
|
|
|
/* Work out in which directions we can scroll */
|
|
if (GetScrollInfo(infoPtr->hwndSelf, SB_VERT, &scrollInfo))
|
|
{
|
|
if (scrollInfo.nPos != scrollInfo.nMin)
|
|
scroll |= SCROLL_UP;
|
|
|
|
if (((scrollInfo.nPage + scrollInfo.nPos) - 1) != scrollInfo.nMax)
|
|
scroll |= SCROLL_DOWN;
|
|
}
|
|
|
|
if (GetScrollInfo(infoPtr->hwndSelf, SB_HORZ, &scrollInfo))
|
|
{
|
|
if (scrollInfo.nPos != scrollInfo.nMin)
|
|
scroll |= SCROLL_LEFT;
|
|
|
|
if (((scrollInfo.nPage + scrollInfo.nPos) - 1) != scrollInfo.nMax)
|
|
scroll |= SCROLL_RIGHT;
|
|
}
|
|
|
|
if (((coords_orig.x <= 0) && (scroll & SCROLL_LEFT)) ||
|
|
((coords_orig.y <= 0) && (scroll & SCROLL_UP)) ||
|
|
((coords_orig.x >= infoPtr->rcList.right) && (scroll & SCROLL_RIGHT)) ||
|
|
((coords_orig.y >= infoPtr->rcList.bottom) && (scroll & SCROLL_DOWN)))
|
|
{
|
|
LISTVIEW_MarqueeHighlight(infoPtr, &coords_orig, &coords_offs, &offset, scroll);
|
|
}
|
|
}
|
|
|
|
/***
|
|
* DESCRIPTION:
|
|
* Called whenever WM_MOUSEMOVE is received.
|
|
*
|
|
* PARAMETER(S):
|
|
* [I] infoPtr : valid pointer to the listview structure
|
|
* [I] fwKeys : key indicator
|
|
* [I] x,y : mouse position
|
|
*
|
|
* RETURN:
|
|
* 0 if the message is processed, non-zero if there was an error
|
|
*/
|
|
static LRESULT LISTVIEW_MouseMove(LISTVIEW_INFO *infoPtr, WORD fwKeys, INT x, INT y)
|
|
{
|
|
LVHITTESTINFO ht;
|
|
RECT rect;
|
|
POINT pt;
|
|
|
|
if (!(fwKeys & MK_LBUTTON))
|
|
infoPtr->bLButtonDown = FALSE;
|
|
|
|
if (infoPtr->bLButtonDown)
|
|
{
|
|
rect.left = rect.right = infoPtr->ptClickPos.x;
|
|
rect.top = rect.bottom = infoPtr->ptClickPos.y;
|
|
|
|
InflateRect(&rect, GetSystemMetrics(SM_CXDRAG), GetSystemMetrics(SM_CYDRAG));
|
|
}
|
|
|
|
if (infoPtr->bLButtonDown)
|
|
{
|
|
if (infoPtr->bMarqueeSelect)
|
|
{
|
|
POINT coords_orig;
|
|
POINT coords_offs;
|
|
POINT offset;
|
|
|
|
coords_orig.x = x;
|
|
coords_orig.y = y;
|
|
|
|
/* Get offset */
|
|
LISTVIEW_GetOrigin(infoPtr, &offset);
|
|
|
|
/* Ensure coordinates are within client bounds */
|
|
coords_offs.x = max(min(x, infoPtr->rcList.right), 0);
|
|
coords_offs.y = max(min(y, infoPtr->rcList.bottom), 0);
|
|
|
|
/* Offset coordinates by the appropriate amount */
|
|
coords_offs.x -= offset.x;
|
|
coords_offs.y -= offset.y;
|
|
|
|
/* Enable the timer if we're going outside our bounds, in case the user doesn't
|
|
move the mouse again */
|
|
|
|
if ((x <= 0) || (y <= 0) || (x >= infoPtr->rcList.right) ||
|
|
(y >= infoPtr->rcList.bottom))
|
|
{
|
|
if (!infoPtr->bScrolling)
|
|
{
|
|
infoPtr->bScrolling = TRUE;
|
|
SetTimer(infoPtr->hwndSelf, (UINT_PTR) infoPtr, 1, LISTVIEW_ScrollTimer);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
infoPtr->bScrolling = FALSE;
|
|
KillTimer(infoPtr->hwndSelf, (UINT_PTR) infoPtr);
|
|
}
|
|
|
|
LISTVIEW_MarqueeHighlight(infoPtr, &coords_orig, &coords_offs, &offset, 0);
|
|
return 0;
|
|
}
|
|
|
|
pt.x = x;
|
|
pt.y = y;
|
|
|
|
ht.pt = pt;
|
|
LISTVIEW_HitTest(infoPtr, &ht, TRUE, TRUE);
|
|
|
|
/* reset item marker */
|
|
if (infoPtr->nLButtonDownItem != ht.iItem)
|
|
infoPtr->nLButtonDownItem = -1;
|
|
|
|
if (!PtInRect(&rect, pt))
|
|
{
|
|
/* this path covers the following:
|
|
1. WM_LBUTTONDOWN over selected item (sets focus on it)
|
|
2. change focus with keys
|
|
3. move mouse over item from step 1 selects it and moves focus on it */
|
|
if (infoPtr->nLButtonDownItem != -1 &&
|
|
!LISTVIEW_GetItemState(infoPtr, infoPtr->nLButtonDownItem, LVIS_SELECTED))
|
|
{
|
|
LVITEMW lvItem;
|
|
|
|
lvItem.state = LVIS_FOCUSED | LVIS_SELECTED;
|
|
lvItem.stateMask = LVIS_FOCUSED | LVIS_SELECTED;
|
|
|
|
LISTVIEW_SetItemState(infoPtr, infoPtr->nLButtonDownItem, &lvItem);
|
|
infoPtr->nLButtonDownItem = -1;
|
|
}
|
|
|
|
if (!infoPtr->bDragging)
|
|
{
|
|
ht.pt = infoPtr->ptClickPos;
|
|
LISTVIEW_HitTest(infoPtr, &ht, TRUE, TRUE);
|
|
|
|
/* If the click is outside the range of an item, begin a
|
|
highlight. If not, begin an item drag. */
|
|
if (ht.iItem == -1)
|
|
{
|
|
NMHDR hdr;
|
|
|
|
/* If we're allowing multiple selections, send notification.
|
|
If return value is non-zero, cancel. */
|
|
if (!(infoPtr->dwStyle & LVS_SINGLESEL) && (notify_hdr(infoPtr, LVN_MARQUEEBEGIN, &hdr) == 0))
|
|
{
|
|
/* Store the absolute coordinates of the click */
|
|
POINT offset;
|
|
LISTVIEW_GetOrigin(infoPtr, &offset);
|
|
|
|
infoPtr->marqueeOrigin.x = infoPtr->ptClickPos.x - offset.x;
|
|
infoPtr->marqueeOrigin.y = infoPtr->ptClickPos.y - offset.y;
|
|
|
|
/* Begin selection and capture mouse */
|
|
infoPtr->bMarqueeSelect = TRUE;
|
|
SetCapture(infoPtr->hwndSelf);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
NMLISTVIEW nmlv;
|
|
|
|
ZeroMemory(&nmlv, sizeof(nmlv));
|
|
nmlv.iItem = ht.iItem;
|
|
nmlv.ptAction = infoPtr->ptClickPos;
|
|
|
|
notify_listview(infoPtr, LVN_BEGINDRAG, &nmlv);
|
|
infoPtr->bDragging = TRUE;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
/* see if we are supposed to be tracking mouse hovering */
|
|
if (LISTVIEW_IsHotTracking(infoPtr)) {
|
|
TRACKMOUSEEVENT trackinfo;
|
|
|
|
trackinfo.cbSize = sizeof(TRACKMOUSEEVENT);
|
|
trackinfo.dwFlags = TME_QUERY;
|
|
|
|
/* see if we are already tracking this hwnd */
|
|
_TrackMouseEvent(&trackinfo);
|
|
|
|
if(!(trackinfo.dwFlags & TME_HOVER) || trackinfo.hwndTrack != infoPtr->hwndSelf) {
|
|
trackinfo.dwFlags = TME_HOVER;
|
|
trackinfo.dwHoverTime = infoPtr->dwHoverTime;
|
|
trackinfo.hwndTrack = infoPtr->hwndSelf;
|
|
|
|
/* call TRACKMOUSEEVENT so we receive WM_MOUSEHOVER messages */
|
|
_TrackMouseEvent(&trackinfo);
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
/***
|
|
* Tests whether the item is assignable to a list with style lStyle
|
|
*/
|
|
static inline BOOL is_assignable_item(const LVITEMW *lpLVItem, LONG lStyle)
|
|
{
|
|
if ( (lpLVItem->mask & LVIF_TEXT) &&
|
|
(lpLVItem->pszText == LPSTR_TEXTCALLBACKW) &&
|
|
(lStyle & (LVS_SORTASCENDING | LVS_SORTDESCENDING)) ) return FALSE;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
/***
|
|
* DESCRIPTION:
|
|
* Helper for LISTVIEW_SetItemT and LISTVIEW_InsertItemT: sets item attributes.
|
|
*
|
|
* PARAMETER(S):
|
|
* [I] infoPtr : valid pointer to the listview structure
|
|
* [I] lpLVItem : valid pointer to new item attributes
|
|
* [I] isNew : the item being set is being inserted
|
|
* [I] isW : TRUE if lpLVItem is Unicode, FALSE if it's ANSI
|
|
* [O] bChanged : will be set to TRUE if the item really changed
|
|
*
|
|
* RETURN:
|
|
* SUCCESS : TRUE
|
|
* FAILURE : FALSE
|
|
*/
|
|
static BOOL set_main_item(LISTVIEW_INFO *infoPtr, const LVITEMW *lpLVItem, BOOL isNew, BOOL isW, BOOL *bChanged)
|
|
{
|
|
ITEM_INFO *lpItem;
|
|
NMLISTVIEW nmlv;
|
|
UINT uChanged = 0;
|
|
LVITEMW item;
|
|
/* stateMask is ignored for LVM_INSERTITEM */
|
|
UINT stateMask = isNew ? ~0 : lpLVItem->stateMask;
|
|
|
|
TRACE("()\n");
|
|
|
|
assert(lpLVItem->iItem >= 0 && lpLVItem->iItem < infoPtr->nItemCount);
|
|
|
|
if (lpLVItem->mask == 0) return TRUE;
|
|
|
|
if (infoPtr->dwStyle & LVS_OWNERDATA)
|
|
{
|
|
/* a virtual listview only stores selection and focus */
|
|
if (lpLVItem->mask & ~LVIF_STATE)
|
|
return FALSE;
|
|
lpItem = NULL;
|
|
}
|
|
else
|
|
{
|
|
HDPA hdpaSubItems = DPA_GetPtr(infoPtr->hdpaItems, lpLVItem->iItem);
|
|
lpItem = DPA_GetPtr(hdpaSubItems, 0);
|
|
assert (lpItem);
|
|
}
|
|
|
|
/* we need to get the lParam and state of the item */
|
|
item.iItem = lpLVItem->iItem;
|
|
item.iSubItem = lpLVItem->iSubItem;
|
|
item.mask = LVIF_STATE | LVIF_PARAM;
|
|
item.stateMask = (infoPtr->dwStyle & LVS_OWNERDATA) ? LVIS_FOCUSED | LVIS_SELECTED : ~0;
|
|
|
|
item.state = 0;
|
|
item.lParam = 0;
|
|
if (!isNew && !LISTVIEW_GetItemW(infoPtr, &item)) return FALSE;
|
|
|
|
TRACE("oldState=%x, newState=%x\n", item.state, lpLVItem->state);
|
|
/* determine what fields will change */
|
|
if ((lpLVItem->mask & LVIF_STATE) && ((item.state ^ lpLVItem->state) & stateMask & ~infoPtr->uCallbackMask))
|
|
uChanged |= LVIF_STATE;
|
|
|
|
if ((lpLVItem->mask & LVIF_IMAGE) && (lpItem->hdr.iImage != lpLVItem->iImage))
|
|
uChanged |= LVIF_IMAGE;
|
|
|
|
if ((lpLVItem->mask & LVIF_PARAM) && (lpItem->lParam != lpLVItem->lParam))
|
|
uChanged |= LVIF_PARAM;
|
|
|
|
if ((lpLVItem->mask & LVIF_INDENT) && (lpItem->iIndent != lpLVItem->iIndent))
|
|
uChanged |= LVIF_INDENT;
|
|
|
|
if ((lpLVItem->mask & LVIF_TEXT) && textcmpWT(lpItem->hdr.pszText, lpLVItem->pszText, isW))
|
|
uChanged |= LVIF_TEXT;
|
|
|
|
TRACE("change mask=0x%x\n", uChanged);
|
|
|
|
memset(&nmlv, 0, sizeof(NMLISTVIEW));
|
|
nmlv.iItem = lpLVItem->iItem;
|
|
nmlv.uNewState = (item.state & ~stateMask) | (lpLVItem->state & stateMask);
|
|
nmlv.uOldState = item.state;
|
|
nmlv.uChanged = uChanged ? uChanged : lpLVItem->mask;
|
|
nmlv.lParam = item.lParam;
|
|
|
|
/* Send LVN_ITEMCHANGING notification, if the item is not being inserted
|
|
and we are _NOT_ virtual (LVS_OWNERDATA), and change notifications
|
|
are enabled. Even nothing really changed we still need to send this,
|
|
in this case uChanged mask is just set to passed item mask. */
|
|
if(lpItem && !isNew && infoPtr->bDoChangeNotify)
|
|
{
|
|
HWND hwndSelf = infoPtr->hwndSelf;
|
|
|
|
if (notify_listview(infoPtr, LVN_ITEMCHANGING, &nmlv))
|
|
return FALSE;
|
|
if (!IsWindow(hwndSelf))
|
|
return FALSE;
|
|
}
|
|
|
|
/* When item is inserted we need to shift existing focus index if new item has lower index. */
|
|
if (isNew && (stateMask & ~infoPtr->uCallbackMask & LVIS_FOCUSED) &&
|
|
/* this means we won't hit a focus change path later */
|
|
((uChanged & LVIF_STATE) == 0 || (!(lpLVItem->state & LVIS_FOCUSED) && (infoPtr->nFocusedItem != lpLVItem->iItem))))
|
|
{
|
|
if (infoPtr->nFocusedItem != -1 && (lpLVItem->iItem <= infoPtr->nFocusedItem))
|
|
infoPtr->nFocusedItem++;
|
|
}
|
|
|
|
if (!uChanged) return TRUE;
|
|
*bChanged = TRUE;
|
|
|
|
/* copy information */
|
|
if (lpLVItem->mask & LVIF_TEXT)
|
|
textsetptrT(&lpItem->hdr.pszText, lpLVItem->pszText, isW);
|
|
|
|
if (lpLVItem->mask & LVIF_IMAGE)
|
|
lpItem->hdr.iImage = lpLVItem->iImage;
|
|
|
|
if (lpLVItem->mask & LVIF_PARAM)
|
|
lpItem->lParam = lpLVItem->lParam;
|
|
|
|
if (lpLVItem->mask & LVIF_INDENT)
|
|
lpItem->iIndent = lpLVItem->iIndent;
|
|
|
|
if (uChanged & LVIF_STATE)
|
|
{
|
|
if (lpItem && (stateMask & ~infoPtr->uCallbackMask))
|
|
{
|
|
lpItem->state &= ~stateMask;
|
|
lpItem->state |= (lpLVItem->state & stateMask);
|
|
}
|
|
if (lpLVItem->state & stateMask & ~infoPtr->uCallbackMask & LVIS_SELECTED)
|
|
{
|
|
if (infoPtr->dwStyle & LVS_SINGLESEL) LISTVIEW_DeselectAllSkipItem(infoPtr, lpLVItem->iItem);
|
|
ranges_additem(infoPtr->selectionRanges, lpLVItem->iItem);
|
|
}
|
|
else if (stateMask & LVIS_SELECTED)
|
|
{
|
|
ranges_delitem(infoPtr->selectionRanges, lpLVItem->iItem);
|
|
}
|
|
/* If we are asked to change focus, and we manage it, do it.
|
|
It's important to have all new item data stored at this point,
|
|
because changing existing focus could result in a redrawing operation,
|
|
which in turn could ask for disp data, application should see all data
|
|
for inserted item when processing LVN_GETDISPINFO.
|
|
|
|
The way this works application will see nested item change notifications -
|
|
changed item notifications interrupted by ones from item losing focus. */
|
|
if (stateMask & ~infoPtr->uCallbackMask & LVIS_FOCUSED)
|
|
{
|
|
if (lpLVItem->state & LVIS_FOCUSED)
|
|
{
|
|
/* update selection mark */
|
|
if (infoPtr->nFocusedItem == -1 && infoPtr->nSelectionMark == -1)
|
|
infoPtr->nSelectionMark = lpLVItem->iItem;
|
|
|
|
if (infoPtr->nFocusedItem != -1)
|
|
{
|
|
/* remove current focus */
|
|
item.mask = LVIF_STATE;
|
|
item.state = 0;
|
|
item.stateMask = LVIS_FOCUSED;
|
|
|
|
/* recurse with redrawing an item */
|
|
LISTVIEW_SetItemState(infoPtr, infoPtr->nFocusedItem, &item);
|
|
}
|
|
|
|
infoPtr->nFocusedItem = lpLVItem->iItem;
|
|
LISTVIEW_EnsureVisible(infoPtr, lpLVItem->iItem, infoPtr->uView == LV_VIEW_LIST);
|
|
}
|
|
else if (infoPtr->nFocusedItem == lpLVItem->iItem)
|
|
{
|
|
infoPtr->nFocusedItem = -1;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* if we're inserting the item, we're done */
|
|
if (isNew) return TRUE;
|
|
|
|
/* send LVN_ITEMCHANGED notification */
|
|
if (lpLVItem->mask & LVIF_PARAM) nmlv.lParam = lpLVItem->lParam;
|
|
if (infoPtr->bDoChangeNotify) notify_listview(infoPtr, LVN_ITEMCHANGED, &nmlv);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
/***
|
|
* DESCRIPTION:
|
|
* Helper for LISTVIEW_{Set,Insert}ItemT *only*: sets subitem attributes.
|
|
*
|
|
* PARAMETER(S):
|
|
* [I] infoPtr : valid pointer to the listview structure
|
|
* [I] lpLVItem : valid pointer to new subitem attributes
|
|
* [I] isW : TRUE if lpLVItem is Unicode, FALSE if it's ANSI
|
|
* [O] bChanged : will be set to TRUE if the item really changed
|
|
*
|
|
* RETURN:
|
|
* SUCCESS : TRUE
|
|
* FAILURE : FALSE
|
|
*/
|
|
static BOOL set_sub_item(const LISTVIEW_INFO *infoPtr, const LVITEMW *lpLVItem, BOOL isW, BOOL *bChanged)
|
|
{
|
|
HDPA hdpaSubItems;
|
|
SUBITEM_INFO *lpSubItem;
|
|
|
|
/* we do not support subitems for virtual listviews */
|
|
if (infoPtr->dwStyle & LVS_OWNERDATA) return FALSE;
|
|
|
|
/* set subitem only if column is present */
|
|
if (lpLVItem->iSubItem >= DPA_GetPtrCount(infoPtr->hdpaColumns)) return FALSE;
|
|
|
|
/* First do some sanity checks */
|
|
/* The LVIF_STATE flag is valid for subitems, but does not appear to be
|
|
particularly useful. We currently do not actually do anything with
|
|
the flag on subitems.
|
|
*/
|
|
if (lpLVItem->mask & ~(LVIF_TEXT | LVIF_IMAGE | LVIF_STATE | LVIF_DI_SETITEM)) return FALSE;
|
|
if (!(lpLVItem->mask & (LVIF_TEXT | LVIF_IMAGE | LVIF_STATE))) return TRUE;
|
|
|
|
/* get the subitem structure, and create it if not there */
|
|
hdpaSubItems = DPA_GetPtr(infoPtr->hdpaItems, lpLVItem->iItem);
|
|
assert (hdpaSubItems);
|
|
|
|
lpSubItem = LISTVIEW_GetSubItemPtr(hdpaSubItems, lpLVItem->iSubItem);
|
|
if (!lpSubItem)
|
|
{
|
|
SUBITEM_INFO *tmpSubItem;
|
|
INT i;
|
|
|
|
lpSubItem = Alloc(sizeof(SUBITEM_INFO));
|
|
if (!lpSubItem) return FALSE;
|
|
/* we could binary search here, if need be...*/
|
|
for (i = 1; i < DPA_GetPtrCount(hdpaSubItems); i++)
|
|
{
|
|
tmpSubItem = DPA_GetPtr(hdpaSubItems, i);
|
|
if (tmpSubItem->iSubItem > lpLVItem->iSubItem) break;
|
|
}
|
|
if (DPA_InsertPtr(hdpaSubItems, i, lpSubItem) == -1)
|
|
{
|
|
Free(lpSubItem);
|
|
return FALSE;
|
|
}
|
|
lpSubItem->iSubItem = lpLVItem->iSubItem;
|
|
lpSubItem->hdr.iImage = I_IMAGECALLBACK;
|
|
*bChanged = TRUE;
|
|
}
|
|
|
|
if ((lpLVItem->mask & LVIF_IMAGE) && (lpSubItem->hdr.iImage != lpLVItem->iImage))
|
|
{
|
|
lpSubItem->hdr.iImage = lpLVItem->iImage;
|
|
*bChanged = TRUE;
|
|
}
|
|
|
|
if ((lpLVItem->mask & LVIF_TEXT) && textcmpWT(lpSubItem->hdr.pszText, lpLVItem->pszText, isW))
|
|
{
|
|
textsetptrT(&lpSubItem->hdr.pszText, lpLVItem->pszText, isW);
|
|
*bChanged = TRUE;
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
/***
|
|
* DESCRIPTION:
|
|
* Sets item attributes.
|
|
*
|
|
* PARAMETER(S):
|
|
* [I] infoPtr : valid pointer to the listview structure
|
|
* [I] lpLVItem : new item attributes
|
|
* [I] isW : TRUE if lpLVItem is Unicode, FALSE if it's ANSI
|
|
*
|
|
* RETURN:
|
|
* SUCCESS : TRUE
|
|
* FAILURE : FALSE
|
|
*/
|
|
static BOOL LISTVIEW_SetItemT(LISTVIEW_INFO *infoPtr, LVITEMW *lpLVItem, BOOL isW)
|
|
{
|
|
HWND hwndSelf = infoPtr->hwndSelf;
|
|
LPWSTR pszText = NULL;
|
|
BOOL bResult, bChanged = FALSE;
|
|
RECT oldItemArea;
|
|
|
|
TRACE("(lpLVItem=%s, isW=%d)\n", debuglvitem_t(lpLVItem, isW), isW);
|
|
|
|
if (!lpLVItem || lpLVItem->iItem < 0 || lpLVItem->iItem >= infoPtr->nItemCount)
|
|
return FALSE;
|
|
|
|
/* Store old item area */
|
|
LISTVIEW_GetItemBox(infoPtr, lpLVItem->iItem, &oldItemArea);
|
|
|
|
/* For efficiency, we transform the lpLVItem->pszText to Unicode here */
|
|
if ((lpLVItem->mask & LVIF_TEXT) && is_text(lpLVItem->pszText))
|
|
{
|
|
pszText = lpLVItem->pszText;
|
|
lpLVItem->pszText = textdupTtoW(lpLVItem->pszText, isW);
|
|
}
|
|
|
|
/* actually set the fields */
|
|
if (!is_assignable_item(lpLVItem, infoPtr->dwStyle)) return FALSE;
|
|
|
|
if (lpLVItem->iSubItem)
|
|
bResult = set_sub_item(infoPtr, lpLVItem, TRUE, &bChanged);
|
|
else
|
|
bResult = set_main_item(infoPtr, lpLVItem, FALSE, TRUE, &bChanged);
|
|
if (!IsWindow(hwndSelf))
|
|
return FALSE;
|
|
|
|
/* redraw item, if necessary */
|
|
if (bChanged && !infoPtr->bIsDrawing)
|
|
{
|
|
/* this little optimization eliminates some nasty flicker */
|
|
if ( infoPtr->uView == LV_VIEW_DETAILS && !(infoPtr->dwStyle & LVS_OWNERDRAWFIXED) &&
|
|
!(infoPtr->dwLvExStyle & LVS_EX_FULLROWSELECT) &&
|
|
lpLVItem->iSubItem > 0 && lpLVItem->iSubItem <= DPA_GetPtrCount(infoPtr->hdpaColumns) )
|
|
LISTVIEW_InvalidateSubItem(infoPtr, lpLVItem->iItem, lpLVItem->iSubItem);
|
|
else
|
|
{
|
|
LISTVIEW_InvalidateRect(infoPtr, &oldItemArea);
|
|
LISTVIEW_InvalidateItem(infoPtr, lpLVItem->iItem);
|
|
}
|
|
}
|
|
/* restore text */
|
|
if (pszText)
|
|
{
|
|
textfreeT(lpLVItem->pszText, isW);
|
|
lpLVItem->pszText = pszText;
|
|
}
|
|
|
|
return bResult;
|
|
}
|
|
|
|
/***
|
|
* DESCRIPTION:
|
|
* Retrieves the index of the item at coordinate (0, 0) of the client area.
|
|
*
|
|
* PARAMETER(S):
|
|
* [I] infoPtr : valid pointer to the listview structure
|
|
*
|
|
* RETURN:
|
|
* item index
|
|
*/
|
|
static INT LISTVIEW_GetTopIndex(const LISTVIEW_INFO *infoPtr)
|
|
{
|
|
INT nItem = 0;
|
|
SCROLLINFO scrollInfo;
|
|
|
|
scrollInfo.cbSize = sizeof(SCROLLINFO);
|
|
scrollInfo.fMask = SIF_POS;
|
|
|
|
if (infoPtr->uView == LV_VIEW_LIST)
|
|
{
|
|
if (GetScrollInfo(infoPtr->hwndSelf, SB_HORZ, &scrollInfo))
|
|
nItem = scrollInfo.nPos * LISTVIEW_GetCountPerColumn(infoPtr);
|
|
}
|
|
else if (infoPtr->uView == LV_VIEW_DETAILS)
|
|
{
|
|
if (GetScrollInfo(infoPtr->hwndSelf, SB_VERT, &scrollInfo))
|
|
nItem = scrollInfo.nPos;
|
|
}
|
|
else
|
|
{
|
|
if (GetScrollInfo(infoPtr->hwndSelf, SB_VERT, &scrollInfo))
|
|
nItem = LISTVIEW_GetCountPerRow(infoPtr) * (scrollInfo.nPos / infoPtr->nItemHeight);
|
|
}
|
|
|
|
TRACE("nItem=%d\n", nItem);
|
|
|
|
return nItem;
|
|
}
|
|
|
|
|
|
/***
|
|
* DESCRIPTION:
|
|
* Erases the background of the given rectangle
|
|
*
|
|
* PARAMETER(S):
|
|
* [I] infoPtr : valid pointer to the listview structure
|
|
* [I] hdc : device context handle
|
|
* [I] lprcBox : clipping rectangle
|
|
*
|
|
* RETURN:
|
|
* Success: TRUE
|
|
* Failure: FALSE
|
|
*/
|
|
static inline BOOL LISTVIEW_FillBkgnd(const LISTVIEW_INFO *infoPtr, HDC hdc, const RECT *lprcBox)
|
|
{
|
|
if (!infoPtr->hBkBrush) return FALSE;
|
|
|
|
TRACE("(hdc=%p, lprcBox=%s, hBkBrush=%p)\n", hdc, wine_dbgstr_rect(lprcBox), infoPtr->hBkBrush);
|
|
|
|
return FillRect(hdc, lprcBox, infoPtr->hBkBrush);
|
|
}
|
|
|
|
/***
|
|
* DESCRIPTION:
|
|
* Draws an item.
|
|
*
|
|
* PARAMETER(S):
|
|
* [I] infoPtr : valid pointer to the listview structure
|
|
* [I] hdc : device context handle
|
|
* [I] nItem : item index
|
|
* [I] nSubItem : subitem index
|
|
* [I] pos : item position in client coordinates
|
|
* [I] cdmode : custom draw mode
|
|
*
|
|
* RETURN:
|
|
* Success: TRUE
|
|
* Failure: FALSE
|
|
*/
|
|
static BOOL LISTVIEW_DrawItem(LISTVIEW_INFO *infoPtr, HDC hdc, INT nItem, INT nSubItem, POINT pos, DWORD cdmode)
|
|
{
|
|
UINT uFormat;
|
|
WCHAR szDispText[DISP_TEXT_SIZE] = { '\0' };
|
|
static WCHAR szCallback[] = { '(', 'c', 'a', 'l', 'l', 'b', 'a', 'c', 'k', ')', 0 };
|
|
DWORD cdsubitemmode = CDRF_DODEFAULT;
|
|
LPRECT lprcFocus;
|
|
RECT rcSelect, rcBox, rcIcon, rcLabel, rcStateIcon;
|
|
NMLVCUSTOMDRAW nmlvcd;
|
|
HIMAGELIST himl;
|
|
LVITEMW lvItem;
|
|
|
|
TRACE("(hdc=%p, nItem=%d, nSubItem=%d, pos=%s)\n", hdc, nItem, nSubItem, wine_dbgstr_point(&pos));
|
|
|
|
/* get information needed for drawing the item */
|
|
lvItem.mask = LVIF_TEXT | LVIF_IMAGE | LVIF_PARAM;
|
|
if (nSubItem == 0) lvItem.mask |= LVIF_STATE;
|
|
if (infoPtr->uView == LV_VIEW_DETAILS) lvItem.mask |= LVIF_INDENT;
|
|
lvItem.stateMask = LVIS_SELECTED | LVIS_FOCUSED | LVIS_STATEIMAGEMASK | LVIS_CUT | LVIS_OVERLAYMASK;
|
|
lvItem.iItem = nItem;
|
|
lvItem.iSubItem = nSubItem;
|
|
lvItem.state = 0;
|
|
lvItem.lParam = 0;
|
|
lvItem.cchTextMax = DISP_TEXT_SIZE;
|
|
lvItem.pszText = szDispText;
|
|
if (!LISTVIEW_GetItemW(infoPtr, &lvItem)) return FALSE;
|
|
if (nSubItem > 0 && (infoPtr->dwLvExStyle & LVS_EX_FULLROWSELECT))
|
|
lvItem.state = LISTVIEW_GetItemState(infoPtr, nItem, LVIS_SELECTED);
|
|
if (lvItem.pszText == LPSTR_TEXTCALLBACKW) lvItem.pszText = szCallback;
|
|
TRACE(" lvItem=%s\n", debuglvitem_t(&lvItem, TRUE));
|
|
|
|
/* now check if we need to update the focus rectangle */
|
|
lprcFocus = infoPtr->bFocus && (lvItem.state & LVIS_FOCUSED) ? &infoPtr->rcFocus : 0;
|
|
|
|
if (!lprcFocus) lvItem.state &= ~LVIS_FOCUSED;
|
|
LISTVIEW_GetItemMetrics(infoPtr, &lvItem, &rcBox, &rcSelect, &rcIcon, &rcStateIcon, &rcLabel);
|
|
OffsetRect(&rcBox, pos.x, pos.y);
|
|
OffsetRect(&rcSelect, pos.x, pos.y);
|
|
OffsetRect(&rcIcon, pos.x, pos.y);
|
|
OffsetRect(&rcStateIcon, pos.x, pos.y);
|
|
OffsetRect(&rcLabel, pos.x, pos.y);
|
|
TRACE(" rcBox=%s, rcSelect=%s, rcIcon=%s. rcLabel=%s\n",
|
|
wine_dbgstr_rect(&rcBox), wine_dbgstr_rect(&rcSelect),
|
|
wine_dbgstr_rect(&rcIcon), wine_dbgstr_rect(&rcLabel));
|
|
|
|
/* fill in the custom draw structure */
|
|
customdraw_fill(&nmlvcd, infoPtr, hdc, &rcBox, &lvItem);
|
|
|
|
if (nSubItem > 0) cdmode = infoPtr->cditemmode;
|
|
if (cdmode & CDRF_SKIPDEFAULT) goto postpaint;
|
|
if (cdmode & CDRF_NOTIFYITEMDRAW)
|
|
cdsubitemmode = notify_customdraw(infoPtr, CDDS_PREPAINT, &nmlvcd);
|
|
if (nSubItem == 0) infoPtr->cditemmode = cdsubitemmode;
|
|
if (cdsubitemmode & CDRF_SKIPDEFAULT) goto postpaint;
|
|
/* we have to send a CDDS_SUBITEM customdraw explicitly for subitem 0 */
|
|
if (nSubItem == 0 && (cdsubitemmode & CDRF_NOTIFYITEMDRAW) != 0)
|
|
{
|
|
cdsubitemmode = notify_customdraw(infoPtr, CDDS_SUBITEM | CDDS_ITEMPREPAINT, &nmlvcd);
|
|
if (cdsubitemmode & CDRF_SKIPDEFAULT) goto postpaint;
|
|
}
|
|
if (nSubItem == 0 || (cdmode & CDRF_NOTIFYITEMDRAW))
|
|
prepaint_setup(infoPtr, hdc, &nmlvcd, FALSE);
|
|
else if ((infoPtr->dwLvExStyle & LVS_EX_FULLROWSELECT) == FALSE)
|
|
prepaint_setup(infoPtr, hdc, &nmlvcd, TRUE);
|
|
|
|
/* FIXME: temporary hack */
|
|
rcSelect.left = rcLabel.left;
|
|
|
|
/* in icon mode, the label rect is really what we want to draw the
|
|
* background for */
|
|
/* in detail mode, we want to paint background for label rect when
|
|
* item is not selected or listview has full row select; otherwise paint
|
|
* background for text only */
|
|
if (infoPtr->uView == LV_VIEW_ICON ||
|
|
(infoPtr->uView == LV_VIEW_DETAILS &&
|
|
(!(lvItem.state & LVIS_SELECTED) ||
|
|
(infoPtr->dwLvExStyle & LVS_EX_FULLROWSELECT) != 0)))
|
|
rcSelect = rcLabel;
|
|
|
|
if (nmlvcd.clrTextBk != CLR_NONE)
|
|
ExtTextOutW(hdc, rcSelect.left, rcSelect.top, ETO_OPAQUE, &rcSelect, NULL, 0, NULL);
|
|
|
|
if(nSubItem == 0 && infoPtr->nFocusedItem == nItem)
|
|
{
|
|
if (infoPtr->uView == LV_VIEW_DETAILS && (infoPtr->dwLvExStyle & LVS_EX_FULLROWSELECT))
|
|
{
|
|
/* we have to update left focus bound too if item isn't in leftmost column
|
|
and reduce right box bound */
|
|
if (DPA_GetPtrCount(infoPtr->hdpaColumns) > 0)
|
|
{
|
|
INT leftmost;
|
|
|
|
if ((leftmost = SendMessageW(infoPtr->hwndHeader, HDM_ORDERTOINDEX, 0, 0)))
|
|
{
|
|
INT Originx = pos.x - LISTVIEW_GetColumnInfo(infoPtr, 0)->rcHeader.left;
|
|
INT index = SendMessageW(infoPtr->hwndHeader, HDM_ORDERTOINDEX,
|
|
DPA_GetPtrCount(infoPtr->hdpaColumns) - 1, 0);
|
|
|
|
rcBox.right = LISTVIEW_GetColumnInfo(infoPtr, index)->rcHeader.right + Originx;
|
|
rcSelect.left = LISTVIEW_GetColumnInfo(infoPtr, leftmost)->rcHeader.left + Originx;
|
|
}
|
|
}
|
|
|
|
rcSelect.right = rcBox.right;
|
|
}
|
|
|
|
/* store new focus rectangle */
|
|
infoPtr->rcFocus = rcSelect;
|
|
}
|
|
|
|
/* state icons */
|
|
if (infoPtr->himlState && STATEIMAGEINDEX(lvItem.state) && (nSubItem == 0))
|
|
{
|
|
UINT uStateImage = STATEIMAGEINDEX(lvItem.state);
|
|
if (uStateImage)
|
|
{
|
|
TRACE("uStateImage=%d\n", uStateImage);
|
|
ImageList_Draw(infoPtr->himlState, uStateImage - 1, hdc,
|
|
rcStateIcon.left, rcStateIcon.top, ILD_NORMAL);
|
|
}
|
|
}
|
|
|
|
/* item icons */
|
|
himl = (infoPtr->uView == LV_VIEW_ICON ? infoPtr->himlNormal : infoPtr->himlSmall);
|
|
if (himl && lvItem.iImage >= 0 && !IsRectEmpty(&rcIcon))
|
|
{
|
|
UINT style;
|
|
|
|
TRACE("iImage=%d\n", lvItem.iImage);
|
|
|
|
if (lvItem.state & (LVIS_SELECTED | LVIS_CUT) && infoPtr->bFocus)
|
|
style = ILD_SELECTED;
|
|
else
|
|
style = ILD_NORMAL;
|
|
|
|
ImageList_DrawEx(himl, lvItem.iImage, hdc, rcIcon.left, rcIcon.top,
|
|
rcIcon.right - rcIcon.left, rcIcon.bottom - rcIcon.top, infoPtr->clrBk,
|
|
lvItem.state & LVIS_CUT ? RGB(255, 255, 255) : CLR_DEFAULT,
|
|
style | (lvItem.state & LVIS_OVERLAYMASK));
|
|
}
|
|
|
|
/* Don't bother painting item being edited */
|
|
if (infoPtr->hwndEdit && nItem == infoPtr->nEditLabelItem && nSubItem == 0) goto postpaint;
|
|
|
|
/* figure out the text drawing flags */
|
|
uFormat = (infoPtr->uView == LV_VIEW_ICON ? (lprcFocus ? LV_FL_DT_FLAGS : LV_ML_DT_FLAGS) : LV_SL_DT_FLAGS);
|
|
if (infoPtr->uView == LV_VIEW_ICON)
|
|
uFormat = (lprcFocus ? LV_FL_DT_FLAGS : LV_ML_DT_FLAGS);
|
|
else if (nSubItem)
|
|
{
|
|
switch (LISTVIEW_GetColumnInfo(infoPtr, nSubItem)->fmt & LVCFMT_JUSTIFYMASK)
|
|
{
|
|
case LVCFMT_RIGHT: uFormat |= DT_RIGHT; break;
|
|
case LVCFMT_CENTER: uFormat |= DT_CENTER; break;
|
|
default: uFormat |= DT_LEFT;
|
|
}
|
|
}
|
|
if (!(uFormat & (DT_RIGHT | DT_CENTER)))
|
|
{
|
|
if (himl && lvItem.iImage >= 0 && !IsRectEmpty(&rcIcon)) rcLabel.left += IMAGE_PADDING;
|
|
else rcLabel.left += LABEL_HOR_PADDING;
|
|
}
|
|
else if (uFormat & DT_RIGHT) rcLabel.right -= LABEL_HOR_PADDING;
|
|
|
|
/* for GRIDLINES reduce the bottom so the text formats correctly */
|
|
if (infoPtr->uView == LV_VIEW_DETAILS && infoPtr->dwLvExStyle & LVS_EX_GRIDLINES)
|
|
rcLabel.bottom--;
|
|
|
|
DrawTextW(hdc, lvItem.pszText, -1, &rcLabel, uFormat);
|
|
|
|
postpaint:
|
|
if (cdsubitemmode & CDRF_NOTIFYPOSTPAINT)
|
|
notify_postpaint(infoPtr, &nmlvcd);
|
|
return TRUE;
|
|
}
|
|
|
|
/***
|
|
* DESCRIPTION:
|
|
* Draws listview items when in owner draw mode.
|
|
*
|
|
* PARAMETER(S):
|
|
* [I] infoPtr : valid pointer to the listview structure
|
|
* [I] hdc : device context handle
|
|
*
|
|
* RETURN:
|
|
* None
|
|
*/
|
|
static void LISTVIEW_RefreshOwnerDraw(const LISTVIEW_INFO *infoPtr, ITERATOR *i, HDC hdc, DWORD cdmode)
|
|
{
|
|
UINT uID = (UINT)GetWindowLongPtrW(infoPtr->hwndSelf, GWLP_ID);
|
|
DWORD cditemmode = CDRF_DODEFAULT;
|
|
NMLVCUSTOMDRAW nmlvcd;
|
|
POINT Origin, Position;
|
|
DRAWITEMSTRUCT dis;
|
|
LVITEMW item;
|
|
|
|
TRACE("()\n");
|
|
|
|
ZeroMemory(&dis, sizeof(dis));
|
|
|
|
/* Get scroll info once before loop */
|
|
LISTVIEW_GetOrigin(infoPtr, &Origin);
|
|
|
|
/* iterate through the invalidated rows */
|
|
while(iterator_next(i))
|
|
{
|
|
item.iItem = i->nItem;
|
|
item.iSubItem = 0;
|
|
item.mask = LVIF_PARAM | LVIF_STATE;
|
|
item.stateMask = LVIS_SELECTED | LVIS_FOCUSED;
|
|
if (!LISTVIEW_GetItemW(infoPtr, &item)) continue;
|
|
|
|
dis.CtlType = ODT_LISTVIEW;
|
|
dis.CtlID = uID;
|
|
dis.itemID = item.iItem;
|
|
dis.itemAction = ODA_DRAWENTIRE;
|
|
dis.itemState = 0;
|
|
if (item.state & LVIS_SELECTED) dis.itemState |= ODS_SELECTED;
|
|
if (infoPtr->bFocus && (item.state & LVIS_FOCUSED)) dis.itemState |= ODS_FOCUS;
|
|
dis.hwndItem = infoPtr->hwndSelf;
|
|
dis.hDC = hdc;
|
|
LISTVIEW_GetItemOrigin(infoPtr, dis.itemID, &Position);
|
|
dis.rcItem.left = Position.x + Origin.x;
|
|
dis.rcItem.right = dis.rcItem.left + infoPtr->nItemWidth;
|
|
dis.rcItem.top = Position.y + Origin.y;
|
|
dis.rcItem.bottom = dis.rcItem.top + infoPtr->nItemHeight;
|
|
dis.itemData = item.lParam;
|
|
|
|
TRACE("item=%s, rcItem=%s\n", debuglvitem_t(&item, TRUE), wine_dbgstr_rect(&dis.rcItem));
|
|
|
|
/*
|
|
* Even if we do not send the CDRF_NOTIFYITEMDRAW we need to fill the nmlvcd
|
|
* structure for the rest. of the paint cycle
|
|
*/
|
|
customdraw_fill(&nmlvcd, infoPtr, hdc, &dis.rcItem, &item);
|
|
if (cdmode & CDRF_NOTIFYITEMDRAW)
|
|
cditemmode = notify_customdraw(infoPtr, CDDS_PREPAINT, &nmlvcd);
|
|
|
|
if (!(cditemmode & CDRF_SKIPDEFAULT))
|
|
{
|
|
prepaint_setup (infoPtr, hdc, &nmlvcd, FALSE);
|
|
SendMessageW(infoPtr->hwndNotify, WM_DRAWITEM, dis.CtlID, (LPARAM)&dis);
|
|
}
|
|
|
|
if (cditemmode & CDRF_NOTIFYPOSTPAINT)
|
|
notify_postpaint(infoPtr, &nmlvcd);
|
|
}
|
|
}
|
|
|
|
/***
|
|
* DESCRIPTION:
|
|
* Draws listview items when in report display mode.
|
|
*
|
|
* PARAMETER(S):
|
|
* [I] infoPtr : valid pointer to the listview structure
|
|
* [I] hdc : device context handle
|
|
* [I] cdmode : custom draw mode
|
|
*
|
|
* RETURN:
|
|
* None
|
|
*/
|
|
static void LISTVIEW_RefreshReport(LISTVIEW_INFO *infoPtr, ITERATOR *i, HDC hdc, DWORD cdmode)
|
|
{
|
|
INT rgntype;
|
|
RECT rcClip, rcItem;
|
|
POINT Origin, Position;
|
|
RANGES colRanges;
|
|
INT col, index;
|
|
ITERATOR j;
|
|
|
|
TRACE("()\n");
|
|
|
|
/* figure out what to draw */
|
|
rgntype = GetClipBox(hdc, &rcClip);
|
|
if (rgntype == NULLREGION) return;
|
|
|
|
/* Get scroll info once before loop */
|
|
LISTVIEW_GetOrigin(infoPtr, &Origin);
|
|
|
|
colRanges = ranges_create(DPA_GetPtrCount(infoPtr->hdpaColumns));
|
|
|
|
/* narrow down the columns we need to paint */
|
|
for(col = 0; col < DPA_GetPtrCount(infoPtr->hdpaColumns); col++)
|
|
{
|
|
index = SendMessageW(infoPtr->hwndHeader, HDM_ORDERTOINDEX, col, 0);
|
|
|
|
LISTVIEW_GetHeaderRect(infoPtr, index, &rcItem);
|
|
if ((rcItem.right + Origin.x >= rcClip.left) && (rcItem.left + Origin.x < rcClip.right))
|
|
ranges_additem(colRanges, index);
|
|
}
|
|
iterator_rangesitems(&j, colRanges);
|
|
|
|
/* in full row select, we _have_ to draw the main item */
|
|
if (infoPtr->dwLvExStyle & LVS_EX_FULLROWSELECT)
|
|
j.nSpecial = 0;
|
|
|
|
/* iterate through the invalidated rows */
|
|
while(iterator_next(i))
|
|
{
|
|
SelectObject(hdc, infoPtr->hFont);
|
|
LISTVIEW_GetItemOrigin(infoPtr, i->nItem, &Position);
|
|
Position.y += Origin.y;
|
|
|
|
/* iterate through the invalidated columns */
|
|
while(iterator_next(&j))
|
|
{
|
|
LISTVIEW_GetHeaderRect(infoPtr, j.nItem, &rcItem);
|
|
Position.x = (j.nItem == 0) ? rcItem.left + Origin.x : Origin.x;
|
|
|
|
if (rgntype == COMPLEXREGION && !((infoPtr->dwLvExStyle & LVS_EX_FULLROWSELECT) && j.nItem == 0))
|
|
{
|
|
rcItem.top = 0;
|
|
rcItem.bottom = infoPtr->nItemHeight;
|
|
OffsetRect(&rcItem, Origin.x, Position.y);
|
|
if (!RectVisible(hdc, &rcItem)) continue;
|
|
}
|
|
|
|
LISTVIEW_DrawItem(infoPtr, hdc, i->nItem, j.nItem, Position, cdmode);
|
|
}
|
|
}
|
|
iterator_destroy(&j);
|
|
}
|
|
|
|
/***
|
|
* DESCRIPTION:
|
|
* Draws the gridlines if necessary when in report display mode.
|
|
*
|
|
* PARAMETER(S):
|
|
* [I] infoPtr : valid pointer to the listview structure
|
|
* [I] hdc : device context handle
|
|
*
|
|
* RETURN:
|
|
* None
|
|
*/
|
|
static void LISTVIEW_RefreshReportGrid(LISTVIEW_INFO *infoPtr, HDC hdc)
|
|
{
|
|
INT rgntype;
|
|
INT y, itemheight;
|
|
INT col, index;
|
|
HPEN hPen, hOldPen;
|
|
RECT rcClip, rcItem = {0};
|
|
POINT Origin;
|
|
RANGES colRanges;
|
|
ITERATOR j;
|
|
BOOL rmost = FALSE;
|
|
|
|
TRACE("()\n");
|
|
|
|
/* figure out what to draw */
|
|
rgntype = GetClipBox(hdc, &rcClip);
|
|
if (rgntype == NULLREGION) return;
|
|
|
|
/* Get scroll info once before loop */
|
|
LISTVIEW_GetOrigin(infoPtr, &Origin);
|
|
|
|
colRanges = ranges_create(DPA_GetPtrCount(infoPtr->hdpaColumns));
|
|
|
|
/* narrow down the columns we need to paint */
|
|
for(col = 0; col < DPA_GetPtrCount(infoPtr->hdpaColumns); col++)
|
|
{
|
|
index = SendMessageW(infoPtr->hwndHeader, HDM_ORDERTOINDEX, col, 0);
|
|
|
|
LISTVIEW_GetHeaderRect(infoPtr, index, &rcItem);
|
|
if ((rcItem.right + Origin.x >= rcClip.left) && (rcItem.left + Origin.x < rcClip.right))
|
|
ranges_additem(colRanges, index);
|
|
}
|
|
|
|
/* is right most vertical line visible? */
|
|
if (DPA_GetPtrCount(infoPtr->hdpaColumns) > 0)
|
|
{
|
|
index = SendMessageW(infoPtr->hwndHeader, HDM_ORDERTOINDEX, DPA_GetPtrCount(infoPtr->hdpaColumns) - 1, 0);
|
|
LISTVIEW_GetHeaderRect(infoPtr, index, &rcItem);
|
|
rmost = (rcItem.right + Origin.x < rcClip.right);
|
|
}
|
|
|
|
if ((hPen = CreatePen( PS_SOLID, 1, comctl32_color.clr3dFace )))
|
|
{
|
|
hOldPen = SelectObject ( hdc, hPen );
|
|
|
|
/* draw the vertical lines for the columns */
|
|
iterator_rangesitems(&j, colRanges);
|
|
while(iterator_next(&j))
|
|
{
|
|
LISTVIEW_GetHeaderRect(infoPtr, j.nItem, &rcItem);
|
|
if (rcItem.left == 0) continue; /* skip leftmost column */
|
|
rcItem.left += Origin.x;
|
|
rcItem.right += Origin.x;
|
|
rcItem.top = infoPtr->rcList.top;
|
|
rcItem.bottom = infoPtr->rcList.bottom;
|
|
TRACE("vert col=%d, rcItem=%s\n", j.nItem, wine_dbgstr_rect(&rcItem));
|
|
MoveToEx (hdc, rcItem.left, rcItem.top, NULL);
|
|
LineTo (hdc, rcItem.left, rcItem.bottom);
|
|
}
|
|
iterator_destroy(&j);
|
|
/* draw rightmost grid line if visible */
|
|
if (rmost)
|
|
{
|
|
index = SendMessageW(infoPtr->hwndHeader, HDM_ORDERTOINDEX,
|
|
DPA_GetPtrCount(infoPtr->hdpaColumns) - 1, 0);
|
|
LISTVIEW_GetHeaderRect(infoPtr, index, &rcItem);
|
|
|
|
rcItem.right += Origin.x;
|
|
|
|
MoveToEx (hdc, rcItem.right, infoPtr->rcList.top, NULL);
|
|
LineTo (hdc, rcItem.right, infoPtr->rcList.bottom);
|
|
}
|
|
|
|
/* draw the horizontal lines for the rows */
|
|
itemheight = LISTVIEW_CalculateItemHeight(infoPtr);
|
|
rcItem.left = infoPtr->rcList.left;
|
|
rcItem.right = infoPtr->rcList.right;
|
|
for(y = Origin.y > 1 ? Origin.y - 1 : itemheight - 1 + Origin.y % itemheight; y<=infoPtr->rcList.bottom; y+=itemheight)
|
|
{
|
|
rcItem.bottom = rcItem.top = y;
|
|
TRACE("horz rcItem=%s\n", wine_dbgstr_rect(&rcItem));
|
|
MoveToEx (hdc, rcItem.left, rcItem.top, NULL);
|
|
LineTo (hdc, rcItem.right, rcItem.top);
|
|
}
|
|
|
|
SelectObject( hdc, hOldPen );
|
|
DeleteObject( hPen );
|
|
}
|
|
else
|
|
ranges_destroy(colRanges);
|
|
}
|
|
|
|
/***
|
|
* DESCRIPTION:
|
|
* Draws listview items when in list display mode.
|
|
*
|
|
* PARAMETER(S):
|
|
* [I] infoPtr : valid pointer to the listview structure
|
|
* [I] hdc : device context handle
|
|
* [I] cdmode : custom draw mode
|
|
*
|
|
* RETURN:
|
|
* None
|
|
*/
|
|
static void LISTVIEW_RefreshList(LISTVIEW_INFO *infoPtr, ITERATOR *i, HDC hdc, DWORD cdmode)
|
|
{
|
|
POINT Origin, Position;
|
|
|
|
/* Get scroll info once before loop */
|
|
LISTVIEW_GetOrigin(infoPtr, &Origin);
|
|
|
|
while(iterator_prev(i))
|
|
{
|
|
SelectObject(hdc, infoPtr->hFont);
|
|
LISTVIEW_GetItemOrigin(infoPtr, i->nItem, &Position);
|
|
Position.x += Origin.x;
|
|
Position.y += Origin.y;
|
|
|
|
LISTVIEW_DrawItem(infoPtr, hdc, i->nItem, 0, Position, cdmode);
|
|
}
|
|
}
|
|
|
|
|
|
/***
|
|
* DESCRIPTION:
|
|
* Draws listview items.
|
|
*
|
|
* PARAMETER(S):
|
|
* [I] infoPtr : valid pointer to the listview structure
|
|
* [I] hdc : device context handle
|
|
* [I] prcErase : rect to be erased before refresh (may be NULL)
|
|
*
|
|
* RETURN:
|
|
* NoneX
|
|
*/
|
|
static void LISTVIEW_Refresh(LISTVIEW_INFO *infoPtr, HDC hdc, const RECT *prcErase)
|
|
{
|
|
COLORREF oldTextColor = 0, oldBkColor = 0;
|
|
NMLVCUSTOMDRAW nmlvcd;
|
|
HFONT hOldFont = 0;
|
|
DWORD cdmode;
|
|
INT oldBkMode = 0;
|
|
RECT rcClient;
|
|
ITERATOR i;
|
|
HDC hdcOrig = hdc;
|
|
HBITMAP hbmp = NULL;
|
|
RANGE range;
|
|
|
|
LISTVIEW_DUMP(infoPtr);
|
|
|
|
if (infoPtr->dwLvExStyle & LVS_EX_DOUBLEBUFFER) {
|
|
TRACE("double buffering\n");
|
|
|
|
hdc = CreateCompatibleDC(hdcOrig);
|
|
if (!hdc) {
|
|
ERR("Failed to create DC for backbuffer\n");
|
|
return;
|
|
}
|
|
hbmp = CreateCompatibleBitmap(hdcOrig, infoPtr->rcList.right,
|
|
infoPtr->rcList.bottom);
|
|
if (!hbmp) {
|
|
ERR("Failed to create bitmap for backbuffer\n");
|
|
DeleteDC(hdc);
|
|
return;
|
|
}
|
|
|
|
SelectObject(hdc, hbmp);
|
|
SelectObject(hdc, infoPtr->hFont);
|
|
|
|
if(GetClipBox(hdcOrig, &rcClient))
|
|
IntersectClipRect(hdc, rcClient.left, rcClient.top, rcClient.right, rcClient.bottom);
|
|
} else {
|
|
/* Save dc values we're gonna trash while drawing
|
|
* FIXME: Should be done in LISTVIEW_DrawItem() */
|
|
hOldFont = SelectObject(hdc, infoPtr->hFont);
|
|
oldBkMode = GetBkMode(hdc);
|
|
oldBkColor = GetBkColor(hdc);
|
|
oldTextColor = GetTextColor(hdc);
|
|
}
|
|
|
|
infoPtr->bIsDrawing = TRUE;
|
|
|
|
if (prcErase) {
|
|
LISTVIEW_FillBkgnd(infoPtr, hdc, prcErase);
|
|
} else if (infoPtr->dwLvExStyle & LVS_EX_DOUBLEBUFFER) {
|
|
/* If no erasing was done (usually because RedrawWindow was called
|
|
* with RDW_INVALIDATE only) we need to copy the old contents into
|
|
* the backbuffer before continuing. */
|
|
BitBlt(hdc, infoPtr->rcList.left, infoPtr->rcList.top,
|
|
infoPtr->rcList.right - infoPtr->rcList.left,
|
|
infoPtr->rcList.bottom - infoPtr->rcList.top,
|
|
hdcOrig, infoPtr->rcList.left, infoPtr->rcList.top, SRCCOPY);
|
|
}
|
|
|
|
infoPtr->cditemmode = CDRF_DODEFAULT;
|
|
|
|
GetClientRect(infoPtr->hwndSelf, &rcClient);
|
|
customdraw_fill(&nmlvcd, infoPtr, hdc, &rcClient, 0);
|
|
cdmode = notify_customdraw(infoPtr, CDDS_PREPAINT, &nmlvcd);
|
|
if (cdmode & CDRF_SKIPDEFAULT) goto enddraw;
|
|
|
|
/* nothing to draw */
|
|
if(infoPtr->nItemCount == 0) goto enddraw;
|
|
|
|
/* figure out what we need to draw */
|
|
iterator_visibleitems(&i, infoPtr, hdc);
|
|
range = iterator_range(&i);
|
|
|
|
/* send cache hint notification */
|
|
if (infoPtr->dwStyle & LVS_OWNERDATA)
|
|
{
|
|
NMLVCACHEHINT nmlv;
|
|
|
|
ZeroMemory(&nmlv, sizeof(NMLVCACHEHINT));
|
|
nmlv.iFrom = range.lower;
|
|
nmlv.iTo = range.upper - 1;
|
|
notify_hdr(infoPtr, LVN_ODCACHEHINT, &nmlv.hdr);
|
|
}
|
|
|
|
if ((infoPtr->dwStyle & LVS_OWNERDRAWFIXED) && (infoPtr->uView == LV_VIEW_DETAILS))
|
|
LISTVIEW_RefreshOwnerDraw(infoPtr, &i, hdc, cdmode);
|
|
else
|
|
{
|
|
if (infoPtr->uView == LV_VIEW_DETAILS)
|
|
LISTVIEW_RefreshReport(infoPtr, &i, hdc, cdmode);
|
|
else /* LV_VIEW_LIST, LV_VIEW_ICON or LV_VIEW_SMALLICON */
|
|
LISTVIEW_RefreshList(infoPtr, &i, hdc, cdmode);
|
|
|
|
/* if we have a focus rect and it's visible, draw it */
|
|
if (infoPtr->bFocus && range.lower <= infoPtr->nFocusedItem &&
|
|
(range.upper - 1) >= infoPtr->nFocusedItem)
|
|
LISTVIEW_DrawFocusRect(infoPtr, hdc);
|
|
}
|
|
iterator_destroy(&i);
|
|
|
|
enddraw:
|
|
/* For LVS_EX_GRIDLINES go and draw lines */
|
|
/* This includes the case where there were *no* items */
|
|
if ((infoPtr->uView == LV_VIEW_DETAILS) && infoPtr->dwLvExStyle & LVS_EX_GRIDLINES)
|
|
LISTVIEW_RefreshReportGrid(infoPtr, hdc);
|
|
|
|
/* Draw marquee rectangle if appropriate */
|
|
if (infoPtr->bMarqueeSelect)
|
|
DrawFocusRect(hdc, &infoPtr->marqueeDrawRect);
|
|
|
|
if (cdmode & CDRF_NOTIFYPOSTPAINT)
|
|
notify_postpaint(infoPtr, &nmlvcd);
|
|
|
|
if(hbmp) {
|
|
BitBlt(hdcOrig, infoPtr->rcList.left, infoPtr->rcList.top,
|
|
infoPtr->rcList.right - infoPtr->rcList.left,
|
|
infoPtr->rcList.bottom - infoPtr->rcList.top,
|
|
hdc, infoPtr->rcList.left, infoPtr->rcList.top, SRCCOPY);
|
|
|
|
DeleteObject(hbmp);
|
|
DeleteDC(hdc);
|
|
} else {
|
|
SelectObject(hdc, hOldFont);
|
|
SetBkMode(hdc, oldBkMode);
|
|
SetBkColor(hdc, oldBkColor);
|
|
SetTextColor(hdc, oldTextColor);
|
|
}
|
|
|
|
infoPtr->bIsDrawing = FALSE;
|
|
}
|
|
|
|
|
|
/***
|
|
* DESCRIPTION:
|
|
* Calculates the approximate width and height of a given number of items.
|
|
*
|
|
* PARAMETER(S):
|
|
* [I] infoPtr : valid pointer to the listview structure
|
|
* [I] nItemCount : number of items
|
|
* [I] wWidth : width
|
|
* [I] wHeight : height
|
|
*
|
|
* RETURN:
|
|
* Returns a DWORD. The width in the low word and the height in high word.
|
|
*/
|
|
static DWORD LISTVIEW_ApproximateViewRect(const LISTVIEW_INFO *infoPtr, INT nItemCount,
|
|
WORD wWidth, WORD wHeight)
|
|
{
|
|
DWORD dwViewRect = 0;
|
|
|
|
if (nItemCount == -1)
|
|
nItemCount = infoPtr->nItemCount;
|
|
|
|
if (infoPtr->uView == LV_VIEW_LIST)
|
|
{
|
|
INT nItemCountPerColumn = 1;
|
|
INT nColumnCount = 0;
|
|
|
|
if (wHeight == 0xFFFF)
|
|
{
|
|
/* use current height */
|
|
wHeight = infoPtr->rcList.bottom - infoPtr->rcList.top;
|
|
}
|
|
|
|
if (wHeight < infoPtr->nItemHeight)
|
|
wHeight = infoPtr->nItemHeight;
|
|
|
|
if (nItemCount > 0)
|
|
{
|
|
if (infoPtr->nItemHeight > 0)
|
|
{
|
|
nItemCountPerColumn = wHeight / infoPtr->nItemHeight;
|
|
if (nItemCountPerColumn == 0)
|
|
nItemCountPerColumn = 1;
|
|
|
|
if (nItemCount % nItemCountPerColumn != 0)
|
|
nColumnCount = nItemCount / nItemCountPerColumn;
|
|
else
|
|
nColumnCount = nItemCount / nItemCountPerColumn + 1;
|
|
}
|
|
}
|
|
|
|
/* Microsoft padding magic */
|
|
wHeight = nItemCountPerColumn * infoPtr->nItemHeight + 2;
|
|
wWidth = nColumnCount * infoPtr->nItemWidth + 2;
|
|
|
|
dwViewRect = MAKELONG(wWidth, wHeight);
|
|
}
|
|
else if (infoPtr->uView == LV_VIEW_DETAILS)
|
|
{
|
|
RECT rcBox;
|
|
|
|
if (infoPtr->nItemCount > 0)
|
|
{
|
|
LISTVIEW_GetItemBox(infoPtr, 0, &rcBox);
|
|
wWidth = rcBox.right - rcBox.left;
|
|
wHeight = (rcBox.bottom - rcBox.top) * nItemCount;
|
|
}
|
|
else
|
|
{
|
|
/* use current height and width */
|
|
if (wHeight == 0xffff)
|
|
wHeight = infoPtr->rcList.bottom - infoPtr->rcList.top;
|
|
if (wWidth == 0xffff)
|
|
wWidth = infoPtr->rcList.right - infoPtr->rcList.left;
|
|
}
|
|
|
|
dwViewRect = MAKELONG(wWidth, wHeight);
|
|
}
|
|
else if (infoPtr->uView == LV_VIEW_ICON)
|
|
{
|
|
UINT rows,cols;
|
|
UINT nItemWidth;
|
|
UINT nItemHeight;
|
|
|
|
nItemWidth = infoPtr->iconSpacing.cx;
|
|
nItemHeight = infoPtr->iconSpacing.cy;
|
|
|
|
if (wWidth == 0xffff)
|
|
wWidth = infoPtr->rcList.right - infoPtr->rcList.left;
|
|
|
|
if (wWidth < nItemWidth)
|
|
wWidth = nItemWidth;
|
|
|
|
cols = wWidth / nItemWidth;
|
|
if (cols > nItemCount)
|
|
cols = nItemCount;
|
|
if (cols < 1)
|
|
cols = 1;
|
|
|
|
if (nItemCount)
|
|
{
|
|
rows = nItemCount / cols;
|
|
if (nItemCount % cols)
|
|
rows++;
|
|
}
|
|
else
|
|
rows = 0;
|
|
|
|
wHeight = (nItemHeight * rows)+2;
|
|
wWidth = (nItemWidth * cols)+2;
|
|
|
|
dwViewRect = MAKELONG(wWidth, wHeight);
|
|
}
|
|
else if (infoPtr->uView == LV_VIEW_SMALLICON)
|
|
FIXME("uView == LV_VIEW_SMALLICON: not implemented\n");
|
|
|
|
return dwViewRect;
|
|
}
|
|
|
|
/***
|
|
* DESCRIPTION:
|
|
* Cancel edit label with saving item text.
|
|
*
|
|
* PARAMETER(S):
|
|
* [I] infoPtr : valid pointer to the listview structure
|
|
*
|
|
* RETURN:
|
|
* Always returns TRUE.
|
|
*/
|
|
static LRESULT LISTVIEW_CancelEditLabel(LISTVIEW_INFO *infoPtr)
|
|
{
|
|
if (infoPtr->hwndEdit)
|
|
{
|
|
/* handle value will be lost after LISTVIEW_EndEditLabelT */
|
|
HWND edit = infoPtr->hwndEdit;
|
|
|
|
LISTVIEW_EndEditLabelT(infoPtr, TRUE, IsWindowUnicode(infoPtr->hwndEdit));
|
|
SendMessageW(edit, WM_CLOSE, 0, 0);
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
/***
|
|
* DESCRIPTION:
|
|
* Create a drag image list for the specified item.
|
|
*
|
|
* PARAMETER(S):
|
|
* [I] infoPtr : valid pointer to the listview structure
|
|
* [I] iItem : index of item
|
|
* [O] lppt : Upper-left corner of the image
|
|
*
|
|
* RETURN:
|
|
* Returns a handle to the image list if successful, NULL otherwise.
|
|
*/
|
|
static HIMAGELIST LISTVIEW_CreateDragImage(LISTVIEW_INFO *infoPtr, INT iItem, LPPOINT lppt)
|
|
{
|
|
RECT rcItem;
|
|
SIZE size;
|
|
POINT pos;
|
|
HDC hdc, hdcOrig;
|
|
HBITMAP hbmp, hOldbmp;
|
|
HFONT hOldFont;
|
|
HIMAGELIST dragList = 0;
|
|
TRACE("iItem=%d Count=%d\n", iItem, infoPtr->nItemCount);
|
|
|
|
if (iItem < 0 || iItem >= infoPtr->nItemCount || !lppt)
|
|
return 0;
|
|
|
|
rcItem.left = LVIR_BOUNDS;
|
|
if (!LISTVIEW_GetItemRect(infoPtr, iItem, &rcItem))
|
|
return 0;
|
|
|
|
lppt->x = rcItem.left;
|
|
lppt->y = rcItem.top;
|
|
|
|
size.cx = rcItem.right - rcItem.left;
|
|
size.cy = rcItem.bottom - rcItem.top;
|
|
|
|
hdcOrig = GetDC(infoPtr->hwndSelf);
|
|
hdc = CreateCompatibleDC(hdcOrig);
|
|
hbmp = CreateCompatibleBitmap(hdcOrig, size.cx, size.cy);
|
|
hOldbmp = SelectObject(hdc, hbmp);
|
|
hOldFont = SelectObject(hdc, infoPtr->hFont);
|
|
|
|
rcItem.left = rcItem.top = 0;
|
|
rcItem.right = size.cx;
|
|
rcItem.bottom = size.cy;
|
|
FillRect(hdc, &rcItem, infoPtr->hBkBrush);
|
|
|
|
pos.x = pos.y = 0;
|
|
if (LISTVIEW_DrawItem(infoPtr, hdc, iItem, 0, pos, infoPtr->cditemmode))
|
|
{
|
|
dragList = ImageList_Create(size.cx, size.cy, ILC_COLOR, 10, 10);
|
|
SelectObject(hdc, hOldbmp);
|
|
ImageList_Add(dragList, hbmp, 0);
|
|
}
|
|
else
|
|
SelectObject(hdc, hOldbmp);
|
|
|
|
SelectObject(hdc, hOldFont);
|
|
DeleteObject(hbmp);
|
|
DeleteDC(hdc);
|
|
ReleaseDC(infoPtr->hwndSelf, hdcOrig);
|
|
|
|
TRACE("ret=%p\n", dragList);
|
|
|
|
return dragList;
|
|
}
|
|
|
|
|
|
/***
|
|
* DESCRIPTION:
|
|
* Removes all listview items and subitems.
|
|
*
|
|
* PARAMETER(S):
|
|
* [I] infoPtr : valid pointer to the listview structure
|
|
*
|
|
* RETURN:
|
|
* SUCCESS : TRUE
|
|
* FAILURE : FALSE
|
|
*/
|
|
static BOOL LISTVIEW_DeleteAllItems(LISTVIEW_INFO *infoPtr, BOOL destroy)
|
|
{
|
|
NMLISTVIEW nmlv;
|
|
HDPA hdpaSubItems = NULL;
|
|
BOOL bSuppress;
|
|
ITEMHDR *hdrItem;
|
|
ITEM_INFO *lpItem;
|
|
ITEM_ID *lpID;
|
|
INT i, j;
|
|
|
|
TRACE("()\n");
|
|
|
|
/* we do it directly, to avoid notifications */
|
|
ranges_clear(infoPtr->selectionRanges);
|
|
infoPtr->nSelectionMark = -1;
|
|
infoPtr->nFocusedItem = -1;
|
|
SetRectEmpty(&infoPtr->rcFocus);
|
|
/* But we are supposed to leave nHotItem as is! */
|
|
|
|
|
|
/* send LVN_DELETEALLITEMS notification */
|
|
ZeroMemory(&nmlv, sizeof(NMLISTVIEW));
|
|
nmlv.iItem = -1;
|
|
bSuppress = notify_listview(infoPtr, LVN_DELETEALLITEMS, &nmlv);
|
|
|
|
for (i = infoPtr->nItemCount - 1; i >= 0; i--)
|
|
{
|
|
if (!(infoPtr->dwStyle & LVS_OWNERDATA))
|
|
{
|
|
/* send LVN_DELETEITEM notification, if not suppressed
|
|
and if it is not a virtual listview */
|
|
if (!bSuppress) notify_deleteitem(infoPtr, i);
|
|
hdpaSubItems = DPA_GetPtr(infoPtr->hdpaItems, i);
|
|
lpItem = DPA_GetPtr(hdpaSubItems, 0);
|
|
/* free id struct */
|
|
j = DPA_GetPtrIndex(infoPtr->hdpaItemIds, lpItem->id);
|
|
lpID = DPA_GetPtr(infoPtr->hdpaItemIds, j);
|
|
DPA_DeletePtr(infoPtr->hdpaItemIds, j);
|
|
Free(lpID);
|
|
/* both item and subitem start with ITEMHDR header */
|
|
for (j = 0; j < DPA_GetPtrCount(hdpaSubItems); j++)
|
|
{
|
|
hdrItem = DPA_GetPtr(hdpaSubItems, j);
|
|
if (is_text(hdrItem->pszText)) Free(hdrItem->pszText);
|
|
Free(hdrItem);
|
|
}
|
|
DPA_Destroy(hdpaSubItems);
|
|
DPA_DeletePtr(infoPtr->hdpaItems, i);
|
|
}
|
|
DPA_DeletePtr(infoPtr->hdpaPosX, i);
|
|
DPA_DeletePtr(infoPtr->hdpaPosY, i);
|
|
infoPtr->nItemCount --;
|
|
}
|
|
|
|
if (!destroy)
|
|
{
|
|
LISTVIEW_Arrange(infoPtr, LVA_DEFAULT);
|
|
LISTVIEW_UpdateScroll(infoPtr);
|
|
}
|
|
LISTVIEW_InvalidateList(infoPtr);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
/***
|
|
* DESCRIPTION:
|
|
* Scrolls, and updates the columns, when a column is changing width.
|
|
*
|
|
* PARAMETER(S):
|
|
* [I] infoPtr : valid pointer to the listview structure
|
|
* [I] nColumn : column to scroll
|
|
* [I] dx : amount of scroll, in pixels
|
|
*
|
|
* RETURN:
|
|
* None.
|
|
*/
|
|
static void LISTVIEW_ScrollColumns(LISTVIEW_INFO *infoPtr, INT nColumn, INT dx)
|
|
{
|
|
COLUMN_INFO *lpColumnInfo;
|
|
RECT rcOld, rcCol;
|
|
POINT ptOrigin;
|
|
INT nCol;
|
|
HDITEMW hdi;
|
|
|
|
if (nColumn < 0 || DPA_GetPtrCount(infoPtr->hdpaColumns) < 1) return;
|
|
lpColumnInfo = LISTVIEW_GetColumnInfo(infoPtr, min(nColumn, DPA_GetPtrCount(infoPtr->hdpaColumns) - 1));
|
|
rcCol = lpColumnInfo->rcHeader;
|
|
if (nColumn >= DPA_GetPtrCount(infoPtr->hdpaColumns))
|
|
rcCol.left = rcCol.right;
|
|
|
|
/* adjust the other columns */
|
|
hdi.mask = HDI_ORDER;
|
|
if (SendMessageW(infoPtr->hwndHeader, HDM_GETITEMW, nColumn, (LPARAM)&hdi))
|
|
{
|
|
INT nOrder = hdi.iOrder;
|
|
for (nCol = 0; nCol < DPA_GetPtrCount(infoPtr->hdpaColumns); nCol++)
|
|
{
|
|
hdi.mask = HDI_ORDER;
|
|
SendMessageW(infoPtr->hwndHeader, HDM_GETITEMW, nCol, (LPARAM)&hdi);
|
|
if (hdi.iOrder >= nOrder) {
|
|
lpColumnInfo = LISTVIEW_GetColumnInfo(infoPtr, nCol);
|
|
lpColumnInfo->rcHeader.left += dx;
|
|
lpColumnInfo->rcHeader.right += dx;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* do not update screen if not in report mode */
|
|
if (!is_redrawing(infoPtr) || infoPtr->uView != LV_VIEW_DETAILS) return;
|
|
|
|
/* Need to reset the item width when inserting a new column */
|
|
infoPtr->nItemWidth += dx;
|
|
|
|
LISTVIEW_UpdateScroll(infoPtr);
|
|
LISTVIEW_GetOrigin(infoPtr, &ptOrigin);
|
|
|
|
/* scroll to cover the deleted column, and invalidate for redraw */
|
|
rcOld = infoPtr->rcList;
|
|
rcOld.left = ptOrigin.x + rcCol.left + dx;
|
|
ScrollWindowEx(infoPtr->hwndSelf, dx, 0, &rcOld, &rcOld, 0, 0, SW_ERASE | SW_INVALIDATE);
|
|
}
|
|
|
|
/***
|
|
* DESCRIPTION:
|
|
* Removes a column from the listview control.
|
|
*
|
|
* PARAMETER(S):
|
|
* [I] infoPtr : valid pointer to the listview structure
|
|
* [I] nColumn : column index
|
|
*
|
|
* RETURN:
|
|
* SUCCESS : TRUE
|
|
* FAILURE : FALSE
|
|
*/
|
|
static BOOL LISTVIEW_DeleteColumn(LISTVIEW_INFO *infoPtr, INT nColumn)
|
|
{
|
|
RECT rcCol;
|
|
|
|
TRACE("nColumn=%d\n", nColumn);
|
|
|
|
if (nColumn < 0 || DPA_GetPtrCount(infoPtr->hdpaColumns) == 0
|
|
|| nColumn >= DPA_GetPtrCount(infoPtr->hdpaColumns)) return FALSE;
|
|
|
|
/* While the MSDN specifically says that column zero should not be deleted,
|
|
what actually happens is that the column itself is deleted but no items or subitems
|
|
are removed.
|
|
*/
|
|
|
|
LISTVIEW_GetHeaderRect(infoPtr, nColumn, &rcCol);
|
|
|
|
if (!SendMessageW(infoPtr->hwndHeader, HDM_DELETEITEM, nColumn, 0))
|
|
return FALSE;
|
|
|
|
Free(DPA_GetPtr(infoPtr->hdpaColumns, nColumn));
|
|
DPA_DeletePtr(infoPtr->hdpaColumns, nColumn);
|
|
|
|
if (!(infoPtr->dwStyle & LVS_OWNERDATA) && nColumn)
|
|
{
|
|
SUBITEM_INFO *lpSubItem, *lpDelItem;
|
|
HDPA hdpaSubItems;
|
|
INT nItem, nSubItem, i;
|
|
|
|
for (nItem = 0; nItem < infoPtr->nItemCount; nItem++)
|
|
{
|
|
hdpaSubItems = DPA_GetPtr(infoPtr->hdpaItems, nItem);
|
|
nSubItem = 0;
|
|
lpDelItem = 0;
|
|
for (i = 1; i < DPA_GetPtrCount(hdpaSubItems); i++)
|
|
{
|
|
lpSubItem = DPA_GetPtr(hdpaSubItems, i);
|
|
if (lpSubItem->iSubItem == nColumn)
|
|
{
|
|
nSubItem = i;
|
|
lpDelItem = lpSubItem;
|
|
}
|
|
else if (lpSubItem->iSubItem > nColumn)
|
|
{
|
|
lpSubItem->iSubItem--;
|
|
}
|
|
}
|
|
|
|
/* if we found our subitem, zap it */
|
|
if (nSubItem > 0)
|
|
{
|
|
/* free string */
|
|
if (is_text(lpDelItem->hdr.pszText))
|
|
Free(lpDelItem->hdr.pszText);
|
|
|
|
/* free item */
|
|
Free(lpDelItem);
|
|
|
|
/* free dpa memory */
|
|
DPA_DeletePtr(hdpaSubItems, nSubItem);
|
|
}
|
|
}
|
|
}
|
|
|
|
/* update the other column info */
|
|
LISTVIEW_UpdateItemSize(infoPtr);
|
|
if(DPA_GetPtrCount(infoPtr->hdpaColumns) == 0)
|
|
LISTVIEW_InvalidateList(infoPtr);
|
|
else
|
|
LISTVIEW_ScrollColumns(infoPtr, nColumn, -(rcCol.right - rcCol.left));
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
/***
|
|
* DESCRIPTION:
|
|
* Invalidates the listview after an item's insertion or deletion.
|
|
*
|
|
* PARAMETER(S):
|
|
* [I] infoPtr : valid pointer to the listview structure
|
|
* [I] nItem : item index
|
|
* [I] dir : -1 if deleting, 1 if inserting
|
|
*
|
|
* RETURN:
|
|
* None
|
|
*/
|
|
static void LISTVIEW_ScrollOnInsert(LISTVIEW_INFO *infoPtr, INT nItem, INT dir)
|
|
{
|
|
INT nPerCol, nItemCol, nItemRow;
|
|
RECT rcScroll;
|
|
POINT Origin;
|
|
|
|
/* if we don't refresh, what's the point of scrolling? */
|
|
if (!is_redrawing(infoPtr)) return;
|
|
|
|
assert (abs(dir) == 1);
|
|
|
|
/* arrange icons if autoarrange is on */
|
|
if (is_autoarrange(infoPtr))
|
|
{
|
|
BOOL arrange = TRUE;
|
|
if (dir < 0 && nItem >= infoPtr->nItemCount) arrange = FALSE;
|
|
if (dir > 0 && nItem == infoPtr->nItemCount - 1) arrange = FALSE;
|
|
if (arrange) LISTVIEW_Arrange(infoPtr, LVA_DEFAULT);
|
|
}
|
|
|
|
/* scrollbars need updating */
|
|
LISTVIEW_UpdateScroll(infoPtr);
|
|
|
|
/* figure out the item's position */
|
|
if (infoPtr->uView == LV_VIEW_DETAILS)
|
|
nPerCol = infoPtr->nItemCount + 1;
|
|
else if (infoPtr->uView == LV_VIEW_LIST)
|
|
nPerCol = LISTVIEW_GetCountPerColumn(infoPtr);
|
|
else /* LV_VIEW_ICON, or LV_VIEW_SMALLICON */
|
|
return;
|
|
|
|
nItemCol = nItem / nPerCol;
|
|
nItemRow = nItem % nPerCol;
|
|
LISTVIEW_GetOrigin(infoPtr, &Origin);
|
|
|
|
/* move the items below up a slot */
|
|
rcScroll.left = nItemCol * infoPtr->nItemWidth;
|
|
rcScroll.top = nItemRow * infoPtr->nItemHeight;
|
|
rcScroll.right = rcScroll.left + infoPtr->nItemWidth;
|
|
rcScroll.bottom = nPerCol * infoPtr->nItemHeight;
|
|
OffsetRect(&rcScroll, Origin.x, Origin.y);
|
|
TRACE("rcScroll=%s, dx=%d\n", wine_dbgstr_rect(&rcScroll), dir * infoPtr->nItemHeight);
|
|
if (IntersectRect(&rcScroll, &rcScroll, &infoPtr->rcList))
|
|
{
|
|
TRACE("Invalidating rcScroll=%s, rcList=%s\n", wine_dbgstr_rect(&rcScroll), wine_dbgstr_rect(&infoPtr->rcList));
|
|
InvalidateRect(infoPtr->hwndSelf, &rcScroll, TRUE);
|
|
}
|
|
|
|
/* report has only that column, so we're done */
|
|
if (infoPtr->uView == LV_VIEW_DETAILS) return;
|
|
|
|
/* now for LISTs, we have to deal with the columns to the right */
|
|
rcScroll.left = (nItemCol + 1) * infoPtr->nItemWidth;
|
|
rcScroll.top = 0;
|
|
rcScroll.right = (infoPtr->nItemCount / nPerCol + 1) * infoPtr->nItemWidth;
|
|
rcScroll.bottom = nPerCol * infoPtr->nItemHeight;
|
|
OffsetRect(&rcScroll, Origin.x, Origin.y);
|
|
if (IntersectRect(&rcScroll, &rcScroll, &infoPtr->rcList))
|
|
InvalidateRect(infoPtr->hwndSelf, &rcScroll, TRUE);
|
|
}
|
|
|
|
/***
|
|
* DESCRIPTION:
|
|
* Removes an item from the listview control.
|
|
*
|
|
* PARAMETER(S):
|
|
* [I] infoPtr : valid pointer to the listview structure
|
|
* [I] nItem : item index
|
|
*
|
|
* RETURN:
|
|
* SUCCESS : TRUE
|
|
* FAILURE : FALSE
|
|
*/
|
|
static BOOL LISTVIEW_DeleteItem(LISTVIEW_INFO *infoPtr, INT nItem)
|
|
{
|
|
LVITEMW item;
|
|
const BOOL is_icon = (infoPtr->uView == LV_VIEW_SMALLICON || infoPtr->uView == LV_VIEW_ICON);
|
|
INT focus = infoPtr->nFocusedItem;
|
|
|
|
TRACE("(nItem=%d)\n", nItem);
|
|
|
|
if (nItem < 0 || nItem >= infoPtr->nItemCount) return FALSE;
|
|
|
|
/* remove selection, and focus */
|
|
item.state = 0;
|
|
item.stateMask = LVIS_SELECTED | LVIS_FOCUSED;
|
|
LISTVIEW_SetItemState(infoPtr, nItem, &item);
|
|
|
|
/* send LVN_DELETEITEM notification. */
|
|
if (!notify_deleteitem(infoPtr, nItem)) return FALSE;
|
|
|
|
/* we need to do this here, because we'll be deleting stuff */
|
|
if (is_icon)
|
|
LISTVIEW_InvalidateItem(infoPtr, nItem);
|
|
|
|
if (!(infoPtr->dwStyle & LVS_OWNERDATA))
|
|
{
|
|
HDPA hdpaSubItems;
|
|
ITEMHDR *hdrItem;
|
|
ITEM_INFO *lpItem;
|
|
ITEM_ID *lpID;
|
|
INT i;
|
|
|
|
hdpaSubItems = DPA_DeletePtr(infoPtr->hdpaItems, nItem);
|
|
lpItem = DPA_GetPtr(hdpaSubItems, 0);
|
|
|
|
/* free id struct */
|
|
i = DPA_GetPtrIndex(infoPtr->hdpaItemIds, lpItem->id);
|
|
lpID = DPA_GetPtr(infoPtr->hdpaItemIds, i);
|
|
DPA_DeletePtr(infoPtr->hdpaItemIds, i);
|
|
Free(lpID);
|
|
for (i = 0; i < DPA_GetPtrCount(hdpaSubItems); i++)
|
|
{
|
|
hdrItem = DPA_GetPtr(hdpaSubItems, i);
|
|
if (is_text(hdrItem->pszText)) Free(hdrItem->pszText);
|
|
Free(hdrItem);
|
|
}
|
|
DPA_Destroy(hdpaSubItems);
|
|
}
|
|
|
|
if (is_icon)
|
|
{
|
|
DPA_DeletePtr(infoPtr->hdpaPosX, nItem);
|
|
DPA_DeletePtr(infoPtr->hdpaPosY, nItem);
|
|
}
|
|
|
|
infoPtr->nItemCount--;
|
|
LISTVIEW_ShiftIndices(infoPtr, nItem, -1);
|
|
LISTVIEW_ShiftFocus(infoPtr, focus, nItem, -1);
|
|
|
|
/* now is the invalidation fun */
|
|
if (!is_icon)
|
|
LISTVIEW_ScrollOnInsert(infoPtr, nItem, -1);
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
/***
|
|
* DESCRIPTION:
|
|
* Callback implementation for editlabel control
|
|
*
|
|
* PARAMETER(S):
|
|
* [I] infoPtr : valid pointer to the listview structure
|
|
* [I] storeText : store edit box text as item text
|
|
* [I] isW : TRUE if psxText is Unicode, FALSE if it's ANSI
|
|
*
|
|
* RETURN:
|
|
* SUCCESS : TRUE
|
|
* FAILURE : FALSE
|
|
*/
|
|
static BOOL LISTVIEW_EndEditLabelT(LISTVIEW_INFO *infoPtr, BOOL storeText, BOOL isW)
|
|
{
|
|
HWND hwndSelf = infoPtr->hwndSelf;
|
|
WCHAR szDispText[DISP_TEXT_SIZE] = { 0 };
|
|
NMLVDISPINFOW dispInfo;
|
|
INT editedItem = infoPtr->nEditLabelItem;
|
|
BOOL same;
|
|
WCHAR *pszText = NULL;
|
|
BOOL res;
|
|
|
|
if (storeText)
|
|
{
|
|
DWORD len = isW ? GetWindowTextLengthW(infoPtr->hwndEdit) : GetWindowTextLengthA(infoPtr->hwndEdit);
|
|
|
|
if (len)
|
|
{
|
|
if ((pszText = Alloc((len+1) * (isW ? sizeof(WCHAR) : sizeof(CHAR)))))
|
|
{
|
|
if (isW) GetWindowTextW(infoPtr->hwndEdit, pszText, len+1);
|
|
else GetWindowTextA(infoPtr->hwndEdit, (CHAR*)pszText, len+1);
|
|
}
|
|
}
|
|
}
|
|
|
|
TRACE("(pszText=%s, isW=%d)\n", debugtext_t(pszText, isW), isW);
|
|
|
|
ZeroMemory(&dispInfo, sizeof(dispInfo));
|
|
dispInfo.item.mask = LVIF_PARAM | LVIF_STATE | LVIF_TEXT;
|
|
dispInfo.item.iItem = editedItem;
|
|
dispInfo.item.iSubItem = 0;
|
|
dispInfo.item.stateMask = ~0;
|
|
dispInfo.item.pszText = szDispText;
|
|
dispInfo.item.cchTextMax = DISP_TEXT_SIZE;
|
|
if (!LISTVIEW_GetItemT(infoPtr, &dispInfo.item, isW))
|
|
{
|
|
res = FALSE;
|
|
goto cleanup;
|
|
}
|
|
|
|
if (isW)
|
|
same = (lstrcmpW(dispInfo.item.pszText, pszText) == 0);
|
|
else
|
|
{
|
|
LPWSTR tmp = textdupTtoW(pszText, FALSE);
|
|
same = (lstrcmpW(dispInfo.item.pszText, tmp) == 0);
|
|
textfreeT(tmp, FALSE);
|
|
}
|
|
|
|
/* add the text from the edit in */
|
|
dispInfo.item.mask |= LVIF_TEXT;
|
|
dispInfo.item.pszText = same ? NULL : pszText;
|
|
dispInfo.item.cchTextMax = textlenT(dispInfo.item.pszText, isW);
|
|
|
|
/* Do we need to update the Item Text */
|
|
res = notify_dispinfoT(infoPtr, LVN_ENDLABELEDITW, &dispInfo, isW);
|
|
|
|
infoPtr->nEditLabelItem = -1;
|
|
infoPtr->hwndEdit = 0;
|
|
|
|
if (!res) goto cleanup;
|
|
|
|
if (!IsWindow(hwndSelf))
|
|
{
|
|
res = FALSE;
|
|
goto cleanup;
|
|
}
|
|
if (!pszText) return TRUE;
|
|
if (same)
|
|
{
|
|
res = TRUE;
|
|
goto cleanup;
|
|
}
|
|
|
|
if (!(infoPtr->dwStyle & LVS_OWNERDATA))
|
|
{
|
|
HDPA hdpaSubItems = DPA_GetPtr(infoPtr->hdpaItems, editedItem);
|
|
ITEM_INFO* lpItem = DPA_GetPtr(hdpaSubItems, 0);
|
|
if (lpItem && lpItem->hdr.pszText == LPSTR_TEXTCALLBACKW)
|
|
{
|
|
LISTVIEW_InvalidateItem(infoPtr, editedItem);
|
|
res = TRUE;
|
|
goto cleanup;
|
|
}
|
|
}
|
|
|
|
ZeroMemory(&dispInfo, sizeof(dispInfo));
|
|
dispInfo.item.mask = LVIF_TEXT;
|
|
dispInfo.item.iItem = editedItem;
|
|
dispInfo.item.iSubItem = 0;
|
|
dispInfo.item.pszText = pszText;
|
|
dispInfo.item.cchTextMax = textlenT(pszText, isW);
|
|
res = LISTVIEW_SetItemT(infoPtr, &dispInfo.item, isW);
|
|
|
|
cleanup:
|
|
Free(pszText);
|
|
|
|
return res;
|
|
}
|
|
|
|
/***
|
|
* DESCRIPTION:
|
|
* Subclassed edit control windproc function
|
|
*
|
|
* PARAMETER(S):
|
|
* [I] hwnd : the edit window handle
|
|
* [I] uMsg : the message that is to be processed
|
|
* [I] wParam : first message parameter
|
|
* [I] lParam : second message parameter
|
|
* [I] isW : TRUE if input is Unicode
|
|
*
|
|
* RETURN:
|
|
* Zero.
|
|
*/
|
|
static LRESULT EditLblWndProcT(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL isW)
|
|
{
|
|
LISTVIEW_INFO *infoPtr = (LISTVIEW_INFO *)GetWindowLongPtrW(GetParent(hwnd), 0);
|
|
BOOL save = TRUE;
|
|
|
|
TRACE("(hwnd=%p, uMsg=%x, wParam=%lx, lParam=%lx, isW=%d)\n",
|
|
hwnd, uMsg, wParam, lParam, isW);
|
|
|
|
switch (uMsg)
|
|
{
|
|
case WM_GETDLGCODE:
|
|
return DLGC_WANTARROWS | DLGC_WANTALLKEYS;
|
|
|
|
case WM_DESTROY:
|
|
{
|
|
WNDPROC editProc = infoPtr->EditWndProc;
|
|
infoPtr->EditWndProc = 0;
|
|
SetWindowLongPtrW(hwnd, GWLP_WNDPROC, (DWORD_PTR)editProc);
|
|
return CallWindowProcT(editProc, hwnd, uMsg, wParam, lParam, isW);
|
|
}
|
|
|
|
case WM_KEYDOWN:
|
|
if (VK_ESCAPE == (INT)wParam)
|
|
{
|
|
save = FALSE;
|
|
break;
|
|
}
|
|
else if (VK_RETURN == (INT)wParam)
|
|
break;
|
|
|
|
default:
|
|
return CallWindowProcT(infoPtr->EditWndProc, hwnd, uMsg, wParam, lParam, isW);
|
|
}
|
|
|
|
/* kill the edit */
|
|
if (infoPtr->hwndEdit)
|
|
LISTVIEW_EndEditLabelT(infoPtr, save, isW);
|
|
|
|
SendMessageW(hwnd, WM_CLOSE, 0, 0);
|
|
return 0;
|
|
}
|
|
|
|
/***
|
|
* DESCRIPTION:
|
|
* Subclassed edit control Unicode windproc function
|
|
*
|
|
* PARAMETER(S):
|
|
* [I] hwnd : the edit window handle
|
|
* [I] uMsg : the message that is to be processed
|
|
* [I] wParam : first message parameter
|
|
* [I] lParam : second message parameter
|
|
*
|
|
* RETURN:
|
|
*/
|
|
static LRESULT CALLBACK EditLblWndProcW(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
|
|
{
|
|
return EditLblWndProcT(hwnd, uMsg, wParam, lParam, TRUE);
|
|
}
|
|
|
|
/***
|
|
* DESCRIPTION:
|
|
* Subclassed edit control ANSI windproc function
|
|
*
|
|
* PARAMETER(S):
|
|
* [I] hwnd : the edit window handle
|
|
* [I] uMsg : the message that is to be processed
|
|
* [I] wParam : first message parameter
|
|
* [I] lParam : second message parameter
|
|
*
|
|
* RETURN:
|
|
*/
|
|
static LRESULT CALLBACK EditLblWndProcA(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
|
|
{
|
|
return EditLblWndProcT(hwnd, uMsg, wParam, lParam, FALSE);
|
|
}
|
|
|
|
/***
|
|
* DESCRIPTION:
|
|
* Creates a subclassed edit control
|
|
*
|
|
* PARAMETER(S):
|
|
* [I] infoPtr : valid pointer to the listview structure
|
|
* [I] text : initial text for the edit
|
|
* [I] style : the window style
|
|
* [I] isW : TRUE if input is Unicode
|
|
*
|
|
* RETURN:
|
|
*/
|
|
static HWND CreateEditLabelT(LISTVIEW_INFO *infoPtr, LPCWSTR text, BOOL isW)
|
|
{
|
|
static const DWORD style = WS_CHILDWINDOW|WS_CLIPSIBLINGS|ES_LEFT|ES_AUTOHSCROLL|WS_BORDER|WS_VISIBLE;
|
|
HINSTANCE hinst = (HINSTANCE)GetWindowLongPtrW(infoPtr->hwndSelf, GWLP_HINSTANCE);
|
|
HWND hedit;
|
|
|
|
TRACE("(%p, text=%s, isW=%d)\n", infoPtr, debugtext_t(text, isW), isW);
|
|
|
|
/* window will be resized and positioned after LVN_BEGINLABELEDIT */
|
|
if (isW)
|
|
hedit = CreateWindowW(WC_EDITW, text, style, 0, 0, 0, 0, infoPtr->hwndSelf, 0, hinst, 0);
|
|
else
|
|
hedit = CreateWindowA(WC_EDITA, (LPCSTR)text, style, 0, 0, 0, 0, infoPtr->hwndSelf, 0, hinst, 0);
|
|
|
|
if (!hedit) return 0;
|
|
|
|
infoPtr->EditWndProc = (WNDPROC)
|
|
(isW ? SetWindowLongPtrW(hedit, GWLP_WNDPROC, (DWORD_PTR)EditLblWndProcW) :
|
|
SetWindowLongPtrA(hedit, GWLP_WNDPROC, (DWORD_PTR)EditLblWndProcA) );
|
|
|
|
SendMessageW(hedit, WM_SETFONT, (WPARAM)infoPtr->hFont, FALSE);
|
|
SendMessageW(hedit, EM_SETLIMITTEXT, DISP_TEXT_SIZE-1, 0);
|
|
|
|
return hedit;
|
|
}
|
|
|
|
/***
|
|
* DESCRIPTION:
|
|
* Begin in place editing of specified list view item
|
|
*
|
|
* PARAMETER(S):
|
|
* [I] infoPtr : valid pointer to the listview structure
|
|
* [I] nItem : item index
|
|
* [I] isW : TRUE if it's a Unicode req, FALSE if ASCII
|
|
*
|
|
* RETURN:
|
|
* SUCCESS : TRUE
|
|
* FAILURE : FALSE
|
|
*/
|
|
static HWND LISTVIEW_EditLabelT(LISTVIEW_INFO *infoPtr, INT nItem, BOOL isW)
|
|
{
|
|
WCHAR disptextW[DISP_TEXT_SIZE] = { 0 };
|
|
HWND hwndSelf = infoPtr->hwndSelf;
|
|
NMLVDISPINFOW dispInfo;
|
|
HFONT hOldFont = NULL;
|
|
TEXTMETRICW tm;
|
|
RECT rect;
|
|
SIZE sz;
|
|
HDC hdc;
|
|
|
|
TRACE("(nItem=%d, isW=%d)\n", nItem, isW);
|
|
|
|
if (~infoPtr->dwStyle & LVS_EDITLABELS) return 0;
|
|
|
|
/* remove existing edit box */
|
|
if (infoPtr->hwndEdit)
|
|
{
|
|
SetFocus(infoPtr->hwndSelf);
|
|
infoPtr->hwndEdit = 0;
|
|
}
|
|
|
|
if (nItem < 0 || nItem >= infoPtr->nItemCount) return 0;
|
|
|
|
infoPtr->nEditLabelItem = nItem;
|
|
|
|
LISTVIEW_SetSelection(infoPtr, nItem);
|
|
LISTVIEW_SetItemFocus(infoPtr, nItem);
|
|
LISTVIEW_InvalidateItem(infoPtr, nItem);
|
|
|
|
rect.left = LVIR_LABEL;
|
|
if (!LISTVIEW_GetItemRect(infoPtr, nItem, &rect)) return 0;
|
|
|
|
ZeroMemory(&dispInfo, sizeof(dispInfo));
|
|
dispInfo.item.mask = LVIF_PARAM | LVIF_STATE | LVIF_TEXT;
|
|
dispInfo.item.iItem = nItem;
|
|
dispInfo.item.iSubItem = 0;
|
|
dispInfo.item.stateMask = ~0;
|
|
dispInfo.item.pszText = disptextW;
|
|
dispInfo.item.cchTextMax = DISP_TEXT_SIZE;
|
|
if (!LISTVIEW_GetItemT(infoPtr, &dispInfo.item, isW)) return 0;
|
|
|
|
infoPtr->hwndEdit = CreateEditLabelT(infoPtr, dispInfo.item.pszText, isW);
|
|
if (!infoPtr->hwndEdit) return 0;
|
|
|
|
if (notify_dispinfoT(infoPtr, LVN_BEGINLABELEDITW, &dispInfo, isW))
|
|
{
|
|
if (!IsWindow(hwndSelf))
|
|
return 0;
|
|
SendMessageW(infoPtr->hwndEdit, WM_CLOSE, 0, 0);
|
|
infoPtr->hwndEdit = 0;
|
|
return 0;
|
|
}
|
|
|
|
TRACE("disp text=%s\n", debugtext_t(dispInfo.item.pszText, isW));
|
|
|
|
/* position and display edit box */
|
|
hdc = GetDC(infoPtr->hwndSelf);
|
|
|
|
/* select the font to get appropriate metric dimensions */
|
|
if (infoPtr->hFont)
|
|
hOldFont = SelectObject(hdc, infoPtr->hFont);
|
|
|
|
/* use real edit box content, it could be altered during LVN_BEGINLABELEDIT notification */
|
|
GetWindowTextW(infoPtr->hwndEdit, disptextW, DISP_TEXT_SIZE);
|
|
TRACE("edit box text=%s\n", debugstr_w(disptextW));
|
|
|
|
/* get string length in pixels */
|
|
GetTextExtentPoint32W(hdc, disptextW, lstrlenW(disptextW), &sz);
|
|
|
|
/* add extra spacing for the next character */
|
|
GetTextMetricsW(hdc, &tm);
|
|
sz.cx += tm.tmMaxCharWidth * 2;
|
|
|
|
if (infoPtr->hFont)
|
|
SelectObject(hdc, hOldFont);
|
|
|
|
ReleaseDC(infoPtr->hwndSelf, hdc);
|
|
|
|
sz.cy = rect.bottom - rect.top + 2;
|
|
rect.left -= 2;
|
|
rect.top -= 1;
|
|
TRACE("moving edit=(%d,%d)-(%d,%d)\n", rect.left, rect.top, sz.cx, sz.cy);
|
|
MoveWindow(infoPtr->hwndEdit, rect.left, rect.top, sz.cx, sz.cy, FALSE);
|
|
ShowWindow(infoPtr->hwndEdit, SW_NORMAL);
|
|
SetFocus(infoPtr->hwndEdit);
|
|
SendMessageW(infoPtr->hwndEdit, EM_SETSEL, 0, -1);
|
|
return infoPtr->hwndEdit;
|
|
}
|
|
|
|
|
|
/***
|
|
* DESCRIPTION:
|
|
* Ensures the specified item is visible, scrolling into view if necessary.
|
|
*
|
|
* PARAMETER(S):
|
|
* [I] infoPtr : valid pointer to the listview structure
|
|
* [I] nItem : item index
|
|
* [I] bPartial : partially or entirely visible
|
|
*
|
|
* RETURN:
|
|
* SUCCESS : TRUE
|
|
* FAILURE : FALSE
|
|
*/
|
|
static BOOL LISTVIEW_EnsureVisible(LISTVIEW_INFO *infoPtr, INT nItem, BOOL bPartial)
|
|
{
|
|
INT nScrollPosHeight = 0;
|
|
INT nScrollPosWidth = 0;
|
|
INT nHorzAdjust = 0;
|
|
INT nVertAdjust = 0;
|
|
INT nHorzDiff = 0;
|
|
INT nVertDiff = 0;
|
|
RECT rcItem, rcTemp;
|
|
|
|
rcItem.left = LVIR_BOUNDS;
|
|
if (!LISTVIEW_GetItemRect(infoPtr, nItem, &rcItem)) return FALSE;
|
|
|
|
if (bPartial && IntersectRect(&rcTemp, &infoPtr->rcList, &rcItem)) return TRUE;
|
|
|
|
if (rcItem.left < infoPtr->rcList.left || rcItem.right > infoPtr->rcList.right)
|
|
{
|
|
/* scroll left/right, but in LV_VIEW_DETAILS mode */
|
|
if (infoPtr->uView == LV_VIEW_LIST)
|
|
nScrollPosWidth = infoPtr->nItemWidth;
|
|
else if ((infoPtr->uView == LV_VIEW_SMALLICON) || (infoPtr->uView == LV_VIEW_ICON))
|
|
nScrollPosWidth = 1;
|
|
|
|
if (rcItem.left < infoPtr->rcList.left)
|
|
{
|
|
nHorzAdjust = -1;
|
|
if (infoPtr->uView != LV_VIEW_DETAILS) nHorzDiff = rcItem.left - infoPtr->rcList.left;
|
|
}
|
|
else
|
|
{
|
|
nHorzAdjust = 1;
|
|
if (infoPtr->uView != LV_VIEW_DETAILS) nHorzDiff = rcItem.right - infoPtr->rcList.right;
|
|
}
|
|
}
|
|
|
|
if (rcItem.top < infoPtr->rcList.top || rcItem.bottom > infoPtr->rcList.bottom)
|
|
{
|
|
/* scroll up/down, but not in LVS_LIST mode */
|
|
if (infoPtr->uView == LV_VIEW_DETAILS)
|
|
nScrollPosHeight = infoPtr->nItemHeight;
|
|
else if ((infoPtr->uView == LV_VIEW_ICON) || (infoPtr->uView == LV_VIEW_SMALLICON))
|
|
nScrollPosHeight = 1;
|
|
|
|
if (rcItem.top < infoPtr->rcList.top)
|
|
{
|
|
nVertAdjust = -1;
|
|
if (infoPtr->uView != LV_VIEW_LIST) nVertDiff = rcItem.top - infoPtr->rcList.top;
|
|
}
|
|
else
|
|
{
|
|
nVertAdjust = 1;
|
|
if (infoPtr->uView != LV_VIEW_LIST) nVertDiff = rcItem.bottom - infoPtr->rcList.bottom;
|
|
}
|
|
}
|
|
|
|
if (!nScrollPosWidth && !nScrollPosHeight) return TRUE;
|
|
|
|
if (nScrollPosWidth)
|
|
{
|
|
INT diff = nHorzDiff / nScrollPosWidth;
|
|
if (nHorzDiff % nScrollPosWidth) diff += nHorzAdjust;
|
|
LISTVIEW_HScroll(infoPtr, SB_INTERNAL, diff);
|
|
}
|
|
|
|
if (nScrollPosHeight)
|
|
{
|
|
INT diff = nVertDiff / nScrollPosHeight;
|
|
if (nVertDiff % nScrollPosHeight) diff += nVertAdjust;
|
|
LISTVIEW_VScroll(infoPtr, SB_INTERNAL, diff);
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
/***
|
|
* DESCRIPTION:
|
|
* Searches for an item with specific characteristics.
|
|
*
|
|
* PARAMETER(S):
|
|
* [I] hwnd : window handle
|
|
* [I] nStart : base item index
|
|
* [I] lpFindInfo : item information to look for
|
|
*
|
|
* RETURN:
|
|
* SUCCESS : index of item
|
|
* FAILURE : -1
|
|
*/
|
|
static INT LISTVIEW_FindItemW(const LISTVIEW_INFO *infoPtr, INT nStart,
|
|
const LVFINDINFOW *lpFindInfo)
|
|
{
|
|
WCHAR szDispText[DISP_TEXT_SIZE] = { '\0' };
|
|
BOOL bWrap = FALSE, bNearest = FALSE;
|
|
INT nItem = nStart + 1, nLast = infoPtr->nItemCount, nNearestItem = -1;
|
|
ULONG xdist, ydist, dist, mindist = 0x7fffffff;
|
|
POINT Position, Destination;
|
|
LVITEMW lvItem;
|
|
|
|
/* Search in virtual listviews should be done by application, not by
|
|
listview control, so we just send LVN_ODFINDITEMW and return the result */
|
|
if (infoPtr->dwStyle & LVS_OWNERDATA)
|
|
{
|
|
NMLVFINDITEMW nmlv;
|
|
|
|
nmlv.iStart = nStart;
|
|
nmlv.lvfi = *lpFindInfo;
|
|
return notify_hdr(infoPtr, LVN_ODFINDITEMW, (LPNMHDR)&nmlv.hdr);
|
|
}
|
|
|
|
if (!lpFindInfo || nItem < 0) return -1;
|
|
|
|
lvItem.mask = 0;
|
|
if (lpFindInfo->flags & (LVFI_STRING | LVFI_PARTIAL) ||
|
|
lpFindInfo->flags & LVFI_SUBSTRING)
|
|
{
|
|
lvItem.mask |= LVIF_TEXT;
|
|
lvItem.pszText = szDispText;
|
|
lvItem.cchTextMax = DISP_TEXT_SIZE;
|
|
}
|
|
|
|
if (lpFindInfo->flags & LVFI_WRAP)
|
|
bWrap = TRUE;
|
|
|
|
if ((lpFindInfo->flags & LVFI_NEARESTXY) &&
|
|
(infoPtr->uView == LV_VIEW_ICON || infoPtr->uView == LV_VIEW_SMALLICON))
|
|
{
|
|
POINT Origin;
|
|
RECT rcArea;
|
|
|
|
LISTVIEW_GetOrigin(infoPtr, &Origin);
|
|
Destination.x = lpFindInfo->pt.x - Origin.x;
|
|
Destination.y = lpFindInfo->pt.y - Origin.y;
|
|
switch(lpFindInfo->vkDirection)
|
|
{
|
|
case VK_DOWN: Destination.y += infoPtr->nItemHeight; break;
|
|
case VK_UP: Destination.y -= infoPtr->nItemHeight; break;
|
|
case VK_RIGHT: Destination.x += infoPtr->nItemWidth; break;
|
|
case VK_LEFT: Destination.x -= infoPtr->nItemWidth; break;
|
|
case VK_HOME: Destination.x = Destination.y = 0; break;
|
|
case VK_NEXT: Destination.y += infoPtr->rcList.bottom - infoPtr->rcList.top; break;
|
|
case VK_PRIOR: Destination.y -= infoPtr->rcList.bottom - infoPtr->rcList.top; break;
|
|
case VK_END:
|
|
LISTVIEW_GetAreaRect(infoPtr, &rcArea);
|
|
Destination.x = rcArea.right;
|
|
Destination.y = rcArea.bottom;
|
|
break;
|
|
default: ERR("Unknown vkDirection=%d\n", lpFindInfo->vkDirection);
|
|
}
|
|
bNearest = TRUE;
|
|
}
|
|
else Destination.x = Destination.y = 0;
|
|
|
|
/* if LVFI_PARAM is specified, all other flags are ignored */
|
|
if (lpFindInfo->flags & LVFI_PARAM)
|
|
{
|
|
lvItem.mask |= LVIF_PARAM;
|
|
bNearest = FALSE;
|
|
lvItem.mask &= ~LVIF_TEXT;
|
|
}
|
|
|
|
again:
|
|
for (; nItem < nLast; nItem++)
|
|
{
|
|
lvItem.iItem = nItem;
|
|
lvItem.iSubItem = 0;
|
|
lvItem.pszText = szDispText;
|
|
if (!LISTVIEW_GetItemW(infoPtr, &lvItem)) continue;
|
|
|
|
if (lvItem.mask & LVIF_PARAM)
|
|
{
|
|
if (lpFindInfo->lParam == lvItem.lParam)
|
|
return nItem;
|
|
else
|
|
continue;
|
|
}
|
|
|
|
if (lvItem.mask & LVIF_TEXT)
|
|
{
|
|
if (lpFindInfo->flags & (LVFI_PARTIAL | LVFI_SUBSTRING))
|
|
{
|
|
WCHAR *p = strstrW(lvItem.pszText, lpFindInfo->psz);
|
|
if (!p || p != lvItem.pszText) continue;
|
|
}
|
|
else
|
|
{
|
|
if (lstrcmpW(lvItem.pszText, lpFindInfo->psz) != 0) continue;
|
|
}
|
|
}
|
|
|
|
if (!bNearest) return nItem;
|
|
|
|
/* This is very inefficient. To do a good job here,
|
|
* we need a sorted array of (x,y) item positions */
|
|
LISTVIEW_GetItemOrigin(infoPtr, nItem, &Position);
|
|
|
|
/* compute the distance^2 to the destination */
|
|
xdist = Destination.x - Position.x;
|
|
ydist = Destination.y - Position.y;
|
|
dist = xdist * xdist + ydist * ydist;
|
|
|
|
/* remember the distance, and item if it's closer */
|
|
if (dist < mindist)
|
|
{
|
|
mindist = dist;
|
|
nNearestItem = nItem;
|
|
}
|
|
}
|
|
|
|
if (bWrap)
|
|
{
|
|
nItem = 0;
|
|
nLast = min(nStart + 1, infoPtr->nItemCount);
|
|
bWrap = FALSE;
|
|
goto again;
|
|
}
|
|
|
|
return nNearestItem;
|
|
}
|
|
|
|
/***
|
|
* DESCRIPTION:
|
|
* Searches for an item with specific characteristics.
|
|
*
|
|
* PARAMETER(S):
|
|
* [I] hwnd : window handle
|
|
* [I] nStart : base item index
|
|
* [I] lpFindInfo : item information to look for
|
|
*
|
|
* RETURN:
|
|
* SUCCESS : index of item
|
|
* FAILURE : -1
|
|
*/
|
|
static INT LISTVIEW_FindItemA(const LISTVIEW_INFO *infoPtr, INT nStart,
|
|
const LVFINDINFOA *lpFindInfo)
|
|
{
|
|
BOOL hasText = lpFindInfo->flags & (LVFI_STRING | LVFI_PARTIAL) ||
|
|
lpFindInfo->flags & LVFI_SUBSTRING;
|
|
LVFINDINFOW fiw;
|
|
INT res;
|
|
LPWSTR strW = NULL;
|
|
|
|
memcpy(&fiw, lpFindInfo, sizeof(fiw));
|
|
if (hasText) fiw.psz = strW = textdupTtoW((LPCWSTR)lpFindInfo->psz, FALSE);
|
|
res = LISTVIEW_FindItemW(infoPtr, nStart, &fiw);
|
|
textfreeT(strW, FALSE);
|
|
return res;
|
|
}
|
|
|
|
/***
|
|
* DESCRIPTION:
|
|
* Retrieves column attributes.
|
|
*
|
|
* PARAMETER(S):
|
|
* [I] infoPtr : valid pointer to the listview structure
|
|
* [I] nColumn : column index
|
|
* [IO] lpColumn : column information
|
|
* [I] isW : if TRUE, then lpColumn is a LPLVCOLUMNW
|
|
* otherwise it is in fact a LPLVCOLUMNA
|
|
*
|
|
* RETURN:
|
|
* SUCCESS : TRUE
|
|
* FAILURE : FALSE
|
|
*/
|
|
static BOOL LISTVIEW_GetColumnT(const LISTVIEW_INFO *infoPtr, INT nColumn, LPLVCOLUMNW lpColumn, BOOL isW)
|
|
{
|
|
COLUMN_INFO *lpColumnInfo;
|
|
HDITEMW hdi;
|
|
|
|
if (!lpColumn || nColumn < 0 || nColumn >= DPA_GetPtrCount(infoPtr->hdpaColumns)) return FALSE;
|
|
lpColumnInfo = LISTVIEW_GetColumnInfo(infoPtr, nColumn);
|
|
|
|
/* initialize memory */
|
|
ZeroMemory(&hdi, sizeof(hdi));
|
|
|
|
if (lpColumn->mask & LVCF_TEXT)
|
|
{
|
|
hdi.mask |= HDI_TEXT;
|
|
hdi.pszText = lpColumn->pszText;
|
|
hdi.cchTextMax = lpColumn->cchTextMax;
|
|
}
|
|
|
|
if (lpColumn->mask & LVCF_IMAGE)
|
|
hdi.mask |= HDI_IMAGE;
|
|
|
|
if (lpColumn->mask & LVCF_ORDER)
|
|
hdi.mask |= HDI_ORDER;
|
|
|
|
if (lpColumn->mask & LVCF_SUBITEM)
|
|
hdi.mask |= HDI_LPARAM;
|
|
|
|
if (!SendMessageW(infoPtr->hwndHeader, isW ? HDM_GETITEMW : HDM_GETITEMA, nColumn, (LPARAM)&hdi)) return FALSE;
|
|
|
|
if (lpColumn->mask & LVCF_FMT)
|
|
lpColumn->fmt = lpColumnInfo->fmt;
|
|
|
|
if (lpColumn->mask & LVCF_WIDTH)
|
|
lpColumn->cx = lpColumnInfo->rcHeader.right - lpColumnInfo->rcHeader.left;
|
|
|
|
if (lpColumn->mask & LVCF_IMAGE)
|
|
lpColumn->iImage = hdi.iImage;
|
|
|
|
if (lpColumn->mask & LVCF_ORDER)
|
|
lpColumn->iOrder = hdi.iOrder;
|
|
|
|
if (lpColumn->mask & LVCF_SUBITEM)
|
|
lpColumn->iSubItem = hdi.lParam;
|
|
|
|
if (lpColumn->mask & LVCF_MINWIDTH)
|
|
lpColumn->cxMin = lpColumnInfo->cxMin;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
static BOOL LISTVIEW_GetColumnOrderArray(const LISTVIEW_INFO *infoPtr, INT iCount, LPINT lpiArray)
|
|
{
|
|
TRACE("iCount=%d, lpiArray=%p\n", iCount, lpiArray);
|
|
|
|
if (!lpiArray)
|
|
return FALSE;
|
|
|
|
return SendMessageW(infoPtr->hwndHeader, HDM_GETORDERARRAY, iCount, (LPARAM)lpiArray);
|
|
}
|
|
|
|
/***
|
|
* DESCRIPTION:
|
|
* Retrieves the column width.
|
|
*
|
|
* PARAMETER(S):
|
|
* [I] infoPtr : valid pointer to the listview structure
|
|
* [I] int : column index
|
|
*
|
|
* RETURN:
|
|
* SUCCESS : column width
|
|
* FAILURE : zero
|
|
*/
|
|
static INT LISTVIEW_GetColumnWidth(const LISTVIEW_INFO *infoPtr, INT nColumn)
|
|
{
|
|
INT nColumnWidth = 0;
|
|
HDITEMW hdItem;
|
|
|
|
TRACE("nColumn=%d\n", nColumn);
|
|
|
|
/* we have a 'column' in LIST and REPORT mode only */
|
|
switch(infoPtr->uView)
|
|
{
|
|
case LV_VIEW_LIST:
|
|
nColumnWidth = infoPtr->nItemWidth;
|
|
break;
|
|
case LV_VIEW_DETAILS:
|
|
/* We are not using LISTVIEW_GetHeaderRect as this data is updated only after a HDN_ITEMCHANGED.
|
|
* There is an application that subclasses the listview, calls LVM_GETCOLUMNWIDTH in the
|
|
* HDN_ITEMCHANGED handler and goes into infinite recursion if it receives old data.
|
|
*
|
|
* TODO: should we do the same in LVM_GETCOLUMN?
|
|
*/
|
|
hdItem.mask = HDI_WIDTH;
|
|
if (!SendMessageW(infoPtr->hwndHeader, HDM_GETITEMW, nColumn, (LPARAM)&hdItem))
|
|
{
|
|
WARN("(%p): HDM_GETITEMW failed for item %d\n", infoPtr->hwndSelf, nColumn);
|
|
return 0;
|
|
}
|
|
nColumnWidth = hdItem.cxy;
|
|
break;
|
|
}
|
|
|
|
TRACE("nColumnWidth=%d\n", nColumnWidth);
|
|
return nColumnWidth;
|
|
}
|
|
|
|
/***
|
|
* DESCRIPTION:
|
|
* In list or report display mode, retrieves the number of items that can fit
|
|
* vertically in the visible area. In icon or small icon display mode,
|
|
* retrieves the total number of visible items.
|
|
*
|
|
* PARAMETER(S):
|
|
* [I] infoPtr : valid pointer to the listview structure
|
|
*
|
|
* RETURN:
|
|
* Number of fully visible items.
|
|
*/
|
|
static INT LISTVIEW_GetCountPerPage(const LISTVIEW_INFO *infoPtr)
|
|
{
|
|
switch (infoPtr->uView)
|
|
{
|
|
case LV_VIEW_ICON:
|
|
case LV_VIEW_SMALLICON:
|
|
return infoPtr->nItemCount;
|
|
case LV_VIEW_DETAILS:
|
|
return LISTVIEW_GetCountPerColumn(infoPtr);
|
|
case LV_VIEW_LIST:
|
|
return LISTVIEW_GetCountPerRow(infoPtr) * LISTVIEW_GetCountPerColumn(infoPtr);
|
|
}
|
|
assert(FALSE);
|
|
return 0;
|
|
}
|
|
|
|
/***
|
|
* DESCRIPTION:
|
|
* Retrieves an image list handle.
|
|
*
|
|
* PARAMETER(S):
|
|
* [I] infoPtr : valid pointer to the listview structure
|
|
* [I] nImageList : image list identifier
|
|
*
|
|
* RETURN:
|
|
* SUCCESS : image list handle
|
|
* FAILURE : NULL
|
|
*/
|
|
static HIMAGELIST LISTVIEW_GetImageList(const LISTVIEW_INFO *infoPtr, INT nImageList)
|
|
{
|
|
switch (nImageList)
|
|
{
|
|
case LVSIL_NORMAL: return infoPtr->himlNormal;
|
|
case LVSIL_SMALL: return infoPtr->himlSmall;
|
|
case LVSIL_STATE: return infoPtr->himlState;
|
|
case LVSIL_GROUPHEADER:
|
|
FIXME("LVSIL_GROUPHEADER not supported\n");
|
|
break;
|
|
default:
|
|
WARN("got unknown imagelist index - %d\n", nImageList);
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
/* LISTVIEW_GetISearchString */
|
|
|
|
/***
|
|
* DESCRIPTION:
|
|
* Retrieves item attributes.
|
|
*
|
|
* PARAMETER(S):
|
|
* [I] hwnd : window handle
|
|
* [IO] lpLVItem : item info
|
|
* [I] isW : if TRUE, then lpLVItem is a LPLVITEMW,
|
|
* if FALSE, then lpLVItem is a LPLVITEMA.
|
|
*
|
|
* NOTE:
|
|
* This is the internal 'GetItem' interface -- it tries to
|
|
* be smart and avoid text copies, if possible, by modifying
|
|
* lpLVItem->pszText to point to the text string. Please note
|
|
* that this is not always possible (e.g. OWNERDATA), so on
|
|
* entry you *must* supply valid values for pszText, and cchTextMax.
|
|
* The only difference to the documented interface is that upon
|
|
* return, you should use *only* the lpLVItem->pszText, rather than
|
|
* the buffer pointer you provided on input. Most code already does
|
|
* that, so it's not a problem.
|
|
* For the two cases when the text must be copied (that is,
|
|
* for LVM_GETITEM, and LVM_GETITEMTEXT), use LISTVIEW_GetItemExtT.
|
|
*
|
|
* RETURN:
|
|
* SUCCESS : TRUE
|
|
* FAILURE : FALSE
|
|
*/
|
|
static BOOL LISTVIEW_GetItemT(const LISTVIEW_INFO *infoPtr, LPLVITEMW lpLVItem, BOOL isW)
|
|
{
|
|
ITEMHDR callbackHdr = { LPSTR_TEXTCALLBACKW, I_IMAGECALLBACK };
|
|
NMLVDISPINFOW dispInfo;
|
|
ITEM_INFO *lpItem;
|
|
ITEMHDR* pItemHdr;
|
|
HDPA hdpaSubItems;
|
|
INT isubitem;
|
|
|
|
TRACE("(item=%s, isW=%d)\n", debuglvitem_t(lpLVItem, isW), isW);
|
|
|
|
if (!lpLVItem || lpLVItem->iItem < 0 || lpLVItem->iItem >= infoPtr->nItemCount)
|
|
return FALSE;
|
|
|
|
if (lpLVItem->mask == 0) return TRUE;
|
|
TRACE("mask=%x\n", lpLVItem->mask);
|
|
|
|
/* make a local copy */
|
|
isubitem = lpLVItem->iSubItem;
|
|
|
|
/* a quick optimization if all we're asked is the focus state
|
|
* these queries are worth optimising since they are common,
|
|
* and can be answered in constant time, without the heavy accesses */
|
|
if ( (lpLVItem->mask == LVIF_STATE) && (lpLVItem->stateMask == LVIS_FOCUSED) &&
|
|
!(infoPtr->uCallbackMask & LVIS_FOCUSED) )
|
|
{
|
|
lpLVItem->state = 0;
|
|
if (infoPtr->nFocusedItem == lpLVItem->iItem)
|
|
lpLVItem->state |= LVIS_FOCUSED;
|
|
return TRUE;
|
|
}
|
|
|
|
ZeroMemory(&dispInfo, sizeof(dispInfo));
|
|
|
|
/* if the app stores all the data, handle it separately */
|
|
if (infoPtr->dwStyle & LVS_OWNERDATA)
|
|
{
|
|
dispInfo.item.state = 0;
|
|
|
|
/* apparently, we should not callback for lParam in LVS_OWNERDATA */
|
|
if ((lpLVItem->mask & ~(LVIF_STATE | LVIF_PARAM)) ||
|
|
((lpLVItem->mask & LVIF_STATE) && (infoPtr->uCallbackMask & lpLVItem->stateMask)))
|
|
{
|
|
UINT mask = lpLVItem->mask;
|
|
|
|
/* NOTE: copy only fields which we _know_ are initialized, some apps
|
|
* depend on the uninitialized fields being 0 */
|
|
dispInfo.item.mask = lpLVItem->mask & ~LVIF_PARAM;
|
|
dispInfo.item.iItem = lpLVItem->iItem;
|
|
dispInfo.item.iSubItem = isubitem;
|
|
if (lpLVItem->mask & LVIF_TEXT)
|
|
{
|
|
if (lpLVItem->mask & LVIF_NORECOMPUTE)
|
|
/* reset mask */
|
|
dispInfo.item.mask &= ~(LVIF_TEXT | LVIF_NORECOMPUTE);
|
|
else
|
|
{
|
|
dispInfo.item.pszText = lpLVItem->pszText;
|
|
dispInfo.item.cchTextMax = lpLVItem->cchTextMax;
|
|
}
|
|
}
|
|
if (lpLVItem->mask & LVIF_STATE)
|
|
dispInfo.item.stateMask = lpLVItem->stateMask & infoPtr->uCallbackMask;
|
|
/* could be zeroed on LVIF_NORECOMPUTE case */
|
|
if (dispInfo.item.mask)
|
|
{
|
|
notify_dispinfoT(infoPtr, LVN_GETDISPINFOW, &dispInfo, isW);
|
|
dispInfo.item.stateMask = lpLVItem->stateMask;
|
|
if (lpLVItem->mask & (LVIF_GROUPID|LVIF_COLUMNS))
|
|
{
|
|
/* full size structure expected - _WIN32IE >= 0x560 */
|
|
*lpLVItem = dispInfo.item;
|
|
}
|
|
else if (lpLVItem->mask & LVIF_INDENT)
|
|
{
|
|
/* indent member expected - _WIN32IE >= 0x300 */
|
|
memcpy(lpLVItem, &dispInfo.item, offsetof( LVITEMW, iGroupId ));
|
|
}
|
|
else
|
|
{
|
|
/* minimal structure expected */
|
|
memcpy(lpLVItem, &dispInfo.item, offsetof( LVITEMW, iIndent ));
|
|
}
|
|
lpLVItem->mask = mask;
|
|
TRACE(" getdispinfo(1):lpLVItem=%s\n", debuglvitem_t(lpLVItem, isW));
|
|
}
|
|
}
|
|
|
|
/* make sure lParam is zeroed out */
|
|
if (lpLVItem->mask & LVIF_PARAM) lpLVItem->lParam = 0;
|
|
|
|
/* callback marked pointer required here */
|
|
if ((lpLVItem->mask & LVIF_TEXT) && (lpLVItem->mask & LVIF_NORECOMPUTE))
|
|
lpLVItem->pszText = LPSTR_TEXTCALLBACKW;
|
|
|
|
/* we store only a little state, so if we're not asked, we're done */
|
|
if (!(lpLVItem->mask & LVIF_STATE) || isubitem) return TRUE;
|
|
|
|
/* if focus is handled by us, report it */
|
|
if ( lpLVItem->stateMask & ~infoPtr->uCallbackMask & LVIS_FOCUSED )
|
|
{
|
|
lpLVItem->state &= ~LVIS_FOCUSED;
|
|
if (infoPtr->nFocusedItem == lpLVItem->iItem)
|
|
lpLVItem->state |= LVIS_FOCUSED;
|
|
}
|
|
|
|
/* and do the same for selection, if we handle it */
|
|
if ( lpLVItem->stateMask & ~infoPtr->uCallbackMask & LVIS_SELECTED )
|
|
{
|
|
lpLVItem->state &= ~LVIS_SELECTED;
|
|
if (ranges_contain(infoPtr->selectionRanges, lpLVItem->iItem))
|
|
lpLVItem->state |= LVIS_SELECTED;
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
/* find the item and subitem structures before we proceed */
|
|
hdpaSubItems = DPA_GetPtr(infoPtr->hdpaItems, lpLVItem->iItem);
|
|
lpItem = DPA_GetPtr(hdpaSubItems, 0);
|
|
assert (lpItem);
|
|
|
|
if (isubitem)
|
|
{
|
|
SUBITEM_INFO *lpSubItem = LISTVIEW_GetSubItemPtr(hdpaSubItems, isubitem);
|
|
pItemHdr = lpSubItem ? &lpSubItem->hdr : &callbackHdr;
|
|
if (!lpSubItem)
|
|
{
|
|
WARN(" iSubItem invalid (%08x), ignored.\n", isubitem);
|
|
isubitem = 0;
|
|
}
|
|
}
|
|
else
|
|
pItemHdr = &lpItem->hdr;
|
|
|
|
/* Do we need to query the state from the app? */
|
|
if ((lpLVItem->mask & LVIF_STATE) && infoPtr->uCallbackMask && isubitem == 0)
|
|
{
|
|
dispInfo.item.mask |= LVIF_STATE;
|
|
dispInfo.item.stateMask = infoPtr->uCallbackMask;
|
|
}
|
|
|
|
/* Do we need to enquire about the image? */
|
|
if ((lpLVItem->mask & LVIF_IMAGE) && pItemHdr->iImage == I_IMAGECALLBACK &&
|
|
(isubitem == 0 || (infoPtr->dwLvExStyle & LVS_EX_SUBITEMIMAGES)))
|
|
{
|
|
dispInfo.item.mask |= LVIF_IMAGE;
|
|
dispInfo.item.iImage = I_IMAGECALLBACK;
|
|
}
|
|
|
|
/* Only items support indentation */
|
|
if ((lpLVItem->mask & LVIF_INDENT) && lpItem->iIndent == I_INDENTCALLBACK &&
|
|
(isubitem == 0))
|
|
{
|
|
dispInfo.item.mask |= LVIF_INDENT;
|
|
dispInfo.item.iIndent = I_INDENTCALLBACK;
|
|
}
|
|
|
|
/* Apps depend on calling back for text if it is NULL or LPSTR_TEXTCALLBACKW */
|
|
if ((lpLVItem->mask & LVIF_TEXT) && !(lpLVItem->mask & LVIF_NORECOMPUTE) &&
|
|
!is_text(pItemHdr->pszText))
|
|
{
|
|
dispInfo.item.mask |= LVIF_TEXT;
|
|
dispInfo.item.pszText = lpLVItem->pszText;
|
|
dispInfo.item.cchTextMax = lpLVItem->cchTextMax;
|
|
if (dispInfo.item.pszText && dispInfo.item.cchTextMax > 0)
|
|
*dispInfo.item.pszText = '\0';
|
|
}
|
|
|
|
/* If we don't have all the requested info, query the application */
|
|
if (dispInfo.item.mask)
|
|
{
|
|
dispInfo.item.iItem = lpLVItem->iItem;
|
|
dispInfo.item.iSubItem = lpLVItem->iSubItem; /* yes: the original subitem */
|
|
dispInfo.item.lParam = lpItem->lParam;
|
|
notify_dispinfoT(infoPtr, LVN_GETDISPINFOW, &dispInfo, isW);
|
|
TRACE(" getdispinfo(2):item=%s\n", debuglvitem_t(&dispInfo.item, isW));
|
|
}
|
|
|
|
/* we should not store values for subitems */
|
|
if (isubitem) dispInfo.item.mask &= ~LVIF_DI_SETITEM;
|
|
|
|
/* Now, handle the iImage field */
|
|
if (dispInfo.item.mask & LVIF_IMAGE)
|
|
{
|
|
lpLVItem->iImage = dispInfo.item.iImage;
|
|
if ((dispInfo.item.mask & LVIF_DI_SETITEM) && pItemHdr->iImage == I_IMAGECALLBACK)
|
|
pItemHdr->iImage = dispInfo.item.iImage;
|
|
}
|
|
else if (lpLVItem->mask & LVIF_IMAGE)
|
|
{
|
|
if(isubitem == 0 || (infoPtr->dwLvExStyle & LVS_EX_SUBITEMIMAGES))
|
|
lpLVItem->iImage = pItemHdr->iImage;
|
|
else
|
|
lpLVItem->iImage = 0;
|
|
}
|
|
|
|
/* The pszText field */
|
|
if (dispInfo.item.mask & LVIF_TEXT)
|
|
{
|
|
if ((dispInfo.item.mask & LVIF_DI_SETITEM) && pItemHdr->pszText)
|
|
textsetptrT(&pItemHdr->pszText, dispInfo.item.pszText, isW);
|
|
|
|
lpLVItem->pszText = dispInfo.item.pszText;
|
|
}
|
|
else if (lpLVItem->mask & LVIF_TEXT)
|
|
{
|
|
/* if LVN_GETDISPINFO's disabled with LVIF_NORECOMPUTE return callback placeholder */
|
|
if (isW || !is_text(pItemHdr->pszText)) lpLVItem->pszText = pItemHdr->pszText;
|
|
else textcpynT(lpLVItem->pszText, isW, pItemHdr->pszText, TRUE, lpLVItem->cchTextMax);
|
|
}
|
|
|
|
/* Next is the lParam field */
|
|
if (dispInfo.item.mask & LVIF_PARAM)
|
|
{
|
|
lpLVItem->lParam = dispInfo.item.lParam;
|
|
if ((dispInfo.item.mask & LVIF_DI_SETITEM))
|
|
lpItem->lParam = dispInfo.item.lParam;
|
|
}
|
|
else if (lpLVItem->mask & LVIF_PARAM)
|
|
lpLVItem->lParam = lpItem->lParam;
|
|
|
|
/* if this is a subitem, we're done */
|
|
if (isubitem) return TRUE;
|
|
|
|
/* ... the state field (this one is different due to uCallbackmask) */
|
|
if (lpLVItem->mask & LVIF_STATE)
|
|
{
|
|
lpLVItem->state = lpItem->state & lpLVItem->stateMask;
|
|
if (dispInfo.item.mask & LVIF_STATE)
|
|
{
|
|
lpLVItem->state &= ~dispInfo.item.stateMask;
|
|
lpLVItem->state |= (dispInfo.item.state & dispInfo.item.stateMask);
|
|
}
|
|
if ( lpLVItem->stateMask & ~infoPtr->uCallbackMask & LVIS_FOCUSED )
|
|
{
|
|
lpLVItem->state &= ~LVIS_FOCUSED;
|
|
if (infoPtr->nFocusedItem == lpLVItem->iItem)
|
|
lpLVItem->state |= LVIS_FOCUSED;
|
|
}
|
|
if ( lpLVItem->stateMask & ~infoPtr->uCallbackMask & LVIS_SELECTED )
|
|
{
|
|
lpLVItem->state &= ~LVIS_SELECTED;
|
|
if (ranges_contain(infoPtr->selectionRanges, lpLVItem->iItem))
|
|
lpLVItem->state |= LVIS_SELECTED;
|
|
}
|
|
}
|
|
|
|
/* and last, but not least, the indent field */
|
|
if (dispInfo.item.mask & LVIF_INDENT)
|
|
{
|
|
lpLVItem->iIndent = dispInfo.item.iIndent;
|
|
if ((dispInfo.item.mask & LVIF_DI_SETITEM) && lpItem->iIndent == I_INDENTCALLBACK)
|
|
lpItem->iIndent = dispInfo.item.iIndent;
|
|
}
|
|
else if (lpLVItem->mask & LVIF_INDENT)
|
|
{
|
|
lpLVItem->iIndent = lpItem->iIndent;
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
/***
|
|
* DESCRIPTION:
|
|
* Retrieves item attributes.
|
|
*
|
|
* PARAMETER(S):
|
|
* [I] hwnd : window handle
|
|
* [IO] lpLVItem : item info
|
|
* [I] isW : if TRUE, then lpLVItem is a LPLVITEMW,
|
|
* if FALSE, then lpLVItem is a LPLVITEMA.
|
|
*
|
|
* NOTE:
|
|
* This is the external 'GetItem' interface -- it properly copies
|
|
* the text in the provided buffer.
|
|
*
|
|
* RETURN:
|
|
* SUCCESS : TRUE
|
|
* FAILURE : FALSE
|
|
*/
|
|
static BOOL LISTVIEW_GetItemExtT(const LISTVIEW_INFO *infoPtr, LPLVITEMW lpLVItem, BOOL isW)
|
|
{
|
|
LPWSTR pszText;
|
|
BOOL bResult;
|
|
|
|
if (!lpLVItem || lpLVItem->iItem < 0 || lpLVItem->iItem >= infoPtr->nItemCount)
|
|
return FALSE;
|
|
|
|
pszText = lpLVItem->pszText;
|
|
bResult = LISTVIEW_GetItemT(infoPtr, lpLVItem, isW);
|
|
if (bResult && (lpLVItem->mask & LVIF_TEXT) && lpLVItem->pszText != pszText)
|
|
{
|
|
if (lpLVItem->pszText != LPSTR_TEXTCALLBACKW)
|
|
textcpynT(pszText, isW, lpLVItem->pszText, isW, lpLVItem->cchTextMax);
|
|
else
|
|
pszText = LPSTR_TEXTCALLBACKW;
|
|
}
|
|
lpLVItem->pszText = pszText;
|
|
|
|
return bResult;
|
|
}
|
|
|
|
|
|
/***
|
|
* DESCRIPTION:
|
|
* Retrieves the position (upper-left) of the listview control item.
|
|
* Note that for LVS_ICON style, the upper-left is that of the icon
|
|
* and not the bounding box.
|
|
*
|
|
* PARAMETER(S):
|
|
* [I] infoPtr : valid pointer to the listview structure
|
|
* [I] nItem : item index
|
|
* [O] lpptPosition : coordinate information
|
|
*
|
|
* RETURN:
|
|
* SUCCESS : TRUE
|
|
* FAILURE : FALSE
|
|
*/
|
|
static BOOL LISTVIEW_GetItemPosition(const LISTVIEW_INFO *infoPtr, INT nItem, LPPOINT lpptPosition)
|
|
{
|
|
POINT Origin;
|
|
|
|
TRACE("(nItem=%d, lpptPosition=%p)\n", nItem, lpptPosition);
|
|
|
|
if (!lpptPosition || nItem < 0 || nItem >= infoPtr->nItemCount) return FALSE;
|
|
|
|
LISTVIEW_GetOrigin(infoPtr, &Origin);
|
|
LISTVIEW_GetItemOrigin(infoPtr, nItem, lpptPosition);
|
|
|
|
if (infoPtr->uView == LV_VIEW_ICON)
|
|
{
|
|
lpptPosition->x += (infoPtr->nItemWidth - infoPtr->iconSize.cx) / 2;
|
|
lpptPosition->y += ICON_TOP_PADDING;
|
|
}
|
|
lpptPosition->x += Origin.x;
|
|
lpptPosition->y += Origin.y;
|
|
|
|
TRACE (" lpptPosition=%s\n", wine_dbgstr_point(lpptPosition));
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
/***
|
|
* DESCRIPTION:
|
|
* Retrieves the bounding rectangle for a listview control item.
|
|
*
|
|
* PARAMETER(S):
|
|
* [I] infoPtr : valid pointer to the listview structure
|
|
* [I] nItem : item index
|
|
* [IO] lprc : bounding rectangle coordinates
|
|
* lprc->left specifies the portion of the item for which the bounding
|
|
* rectangle will be retrieved.
|
|
*
|
|
* LVIR_BOUNDS Returns the bounding rectangle of the entire item,
|
|
* including the icon and label.
|
|
* *
|
|
* * For LVS_ICON
|
|
* * Experiment shows that native control returns:
|
|
* * width = min (48, length of text line)
|
|
* * .left = position.x - (width - iconsize.cx)/2
|
|
* * .right = .left + width
|
|
* * height = #lines of text * ntmHeight + icon height + 8
|
|
* * .top = position.y - 2
|
|
* * .bottom = .top + height
|
|
* * separation between items .y = itemSpacing.cy - height
|
|
* * .x = itemSpacing.cx - width
|
|
* LVIR_ICON Returns the bounding rectangle of the icon or small icon.
|
|
* *
|
|
* * For LVS_ICON
|
|
* * Experiment shows that native control returns:
|
|
* * width = iconSize.cx + 16
|
|
* * .left = position.x - (width - iconsize.cx)/2
|
|
* * .right = .left + width
|
|
* * height = iconSize.cy + 4
|
|
* * .top = position.y - 2
|
|
* * .bottom = .top + height
|
|
* * separation between items .y = itemSpacing.cy - height
|
|
* * .x = itemSpacing.cx - width
|
|
* LVIR_LABEL Returns the bounding rectangle of the item text.
|
|
* *
|
|
* * For LVS_ICON
|
|
* * Experiment shows that native control returns:
|
|
* * width = text length
|
|
* * .left = position.x - width/2
|
|
* * .right = .left + width
|
|
* * height = ntmH * linecount + 2
|
|
* * .top = position.y + iconSize.cy + 6
|
|
* * .bottom = .top + height
|
|
* * separation between items .y = itemSpacing.cy - height
|
|
* * .x = itemSpacing.cx - width
|
|
* LVIR_SELECTBOUNDS Returns the union of the LVIR_ICON and LVIR_LABEL
|
|
* rectangles, but excludes columns in report view.
|
|
*
|
|
* RETURN:
|
|
* SUCCESS : TRUE
|
|
* FAILURE : FALSE
|
|
*
|
|
* NOTES
|
|
* Note that the bounding rectangle of the label in the LVS_ICON view depends
|
|
* upon whether the window has the focus currently and on whether the item
|
|
* is the one with the focus. Ensure that the control's record of which
|
|
* item has the focus agrees with the items' records.
|
|
*/
|
|
static BOOL LISTVIEW_GetItemRect(const LISTVIEW_INFO *infoPtr, INT nItem, LPRECT lprc)
|
|
{
|
|
WCHAR szDispText[DISP_TEXT_SIZE] = { '\0' };
|
|
BOOL doLabel = TRUE, oversizedBox = FALSE;
|
|
POINT Position, Origin;
|
|
LVITEMW lvItem;
|
|
LONG mode;
|
|
|
|
TRACE("(hwnd=%p, nItem=%d, lprc=%p)\n", infoPtr->hwndSelf, nItem, lprc);
|
|
|
|
if (!lprc || nItem < 0 || nItem >= infoPtr->nItemCount) return FALSE;
|
|
|
|
LISTVIEW_GetOrigin(infoPtr, &Origin);
|
|
LISTVIEW_GetItemOrigin(infoPtr, nItem, &Position);
|
|
|
|
/* Be smart and try to figure out the minimum we have to do */
|
|
if (lprc->left == LVIR_ICON) doLabel = FALSE;
|
|
if (infoPtr->uView == LV_VIEW_DETAILS && lprc->left == LVIR_BOUNDS) doLabel = FALSE;
|
|
if (infoPtr->uView == LV_VIEW_ICON && lprc->left != LVIR_ICON &&
|
|
infoPtr->bFocus && LISTVIEW_GetItemState(infoPtr, nItem, LVIS_FOCUSED))
|
|
oversizedBox = TRUE;
|
|
|
|
/* get what we need from the item before hand, so we make
|
|
* only one request. This can speed up things, if data
|
|
* is stored on the app side */
|
|
lvItem.mask = 0;
|
|
if (infoPtr->uView == LV_VIEW_DETAILS) lvItem.mask |= LVIF_INDENT;
|
|
if (doLabel) lvItem.mask |= LVIF_TEXT;
|
|
lvItem.iItem = nItem;
|
|
lvItem.iSubItem = 0;
|
|
lvItem.pszText = szDispText;
|
|
lvItem.cchTextMax = DISP_TEXT_SIZE;
|
|
if (lvItem.mask && !LISTVIEW_GetItemW(infoPtr, &lvItem)) return FALSE;
|
|
/* we got the state already up, simulate it here, to avoid a reget */
|
|
if (infoPtr->uView == LV_VIEW_ICON && (lprc->left != LVIR_ICON))
|
|
{
|
|
lvItem.mask |= LVIF_STATE;
|
|
lvItem.stateMask = LVIS_FOCUSED;
|
|
lvItem.state = (oversizedBox ? LVIS_FOCUSED : 0);
|
|
}
|
|
|
|
if (infoPtr->uView == LV_VIEW_DETAILS && (infoPtr->dwLvExStyle & LVS_EX_FULLROWSELECT) && lprc->left == LVIR_SELECTBOUNDS)
|
|
lprc->left = LVIR_BOUNDS;
|
|
|
|
mode = lprc->left;
|
|
switch(lprc->left)
|
|
{
|
|
case LVIR_ICON:
|
|
LISTVIEW_GetItemMetrics(infoPtr, &lvItem, NULL, NULL, lprc, NULL, NULL);
|
|
break;
|
|
|
|
case LVIR_LABEL:
|
|
LISTVIEW_GetItemMetrics(infoPtr, &lvItem, NULL, NULL, NULL, NULL, lprc);
|
|
break;
|
|
|
|
case LVIR_BOUNDS:
|
|
LISTVIEW_GetItemMetrics(infoPtr, &lvItem, lprc, NULL, NULL, NULL, NULL);
|
|
break;
|
|
|
|
case LVIR_SELECTBOUNDS:
|
|
LISTVIEW_GetItemMetrics(infoPtr, &lvItem, NULL, lprc, NULL, NULL, NULL);
|
|
break;
|
|
|
|
default:
|
|
WARN("Unknown value: %d\n", lprc->left);
|
|
return FALSE;
|
|
}
|
|
|
|
if (infoPtr->uView == LV_VIEW_DETAILS)
|
|
{
|
|
if (mode != LVIR_BOUNDS)
|
|
OffsetRect(lprc, Origin.x + LISTVIEW_GetColumnInfo(infoPtr, 0)->rcHeader.left,
|
|
Position.y + Origin.y);
|
|
else
|
|
OffsetRect(lprc, Origin.x, Position.y + Origin.y);
|
|
}
|
|
else
|
|
OffsetRect(lprc, Position.x + Origin.x, Position.y + Origin.y);
|
|
|
|
TRACE(" rect=%s\n", wine_dbgstr_rect(lprc));
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
/***
|
|
* DESCRIPTION:
|
|
* Retrieves the spacing between listview control items.
|
|
*
|
|
* PARAMETER(S):
|
|
* [I] infoPtr : valid pointer to the listview structure
|
|
* [IO] lprc : rectangle to receive the output
|
|
* on input, lprc->top = nSubItem
|
|
* lprc->left = LVIR_ICON | LVIR_BOUNDS | LVIR_LABEL
|
|
*
|
|
* NOTE: for subItem = 0, we should return the bounds of the _entire_ item,
|
|
* not only those of the first column.
|
|
*
|
|
* RETURN:
|
|
* TRUE: success
|
|
* FALSE: failure
|
|
*/
|
|
static BOOL LISTVIEW_GetSubItemRect(const LISTVIEW_INFO *infoPtr, INT item, LPRECT lprc)
|
|
{
|
|
RECT rect = { 0, 0, 0, 0 };
|
|
POINT origin;
|
|
INT y;
|
|
|
|
if (!lprc) return FALSE;
|
|
|
|
TRACE("(item=%d, subitem=%d, type=%d)\n", item, lprc->top, lprc->left);
|
|
/* Subitem of '0' means item itself, and this works for all control view modes */
|
|
if (lprc->top == 0)
|
|
return LISTVIEW_GetItemRect(infoPtr, item, lprc);
|
|
|
|
if (infoPtr->uView != LV_VIEW_DETAILS) return FALSE;
|
|
|
|
LISTVIEW_GetOrigin(infoPtr, &origin);
|
|
/* this works for any item index, no matter if it exists or not */
|
|
y = item * infoPtr->nItemHeight + origin.y;
|
|
|
|
if (infoPtr->hwndHeader && SendMessageW(infoPtr->hwndHeader, HDM_GETITEMRECT, lprc->top, (LPARAM)&rect))
|
|
{
|
|
rect.top = 0;
|
|
rect.bottom = infoPtr->nItemHeight;
|
|
}
|
|
else
|
|
{
|
|
/* Native implementation is broken for this case and garbage is left for left and right fields,
|
|
we zero them to get predictable output */
|
|
lprc->left = lprc->right = lprc->top = 0;
|
|
lprc->bottom = infoPtr->nItemHeight;
|
|
OffsetRect(lprc, origin.x, y);
|
|
TRACE("return rect %s\n", wine_dbgstr_rect(lprc));
|
|
return TRUE;
|
|
}
|
|
|
|
switch (lprc->left)
|
|
{
|
|
case LVIR_ICON:
|
|
{
|
|
/* it doesn't matter if main item actually has an icon, if imagelist is set icon width is returned */
|
|
if (infoPtr->himlSmall)
|
|
rect.right = rect.left + infoPtr->iconSize.cx;
|
|
else
|
|
rect.right = rect.left;
|
|
|
|
rect.bottom = rect.top + infoPtr->iconSize.cy;
|
|
break;
|
|
}
|
|
case LVIR_LABEL:
|
|
case LVIR_BOUNDS:
|
|
break;
|
|
|
|
default:
|
|
ERR("Unknown bounds=%d\n", lprc->left);
|
|
return FALSE;
|
|
}
|
|
|
|
OffsetRect(&rect, origin.x, y);
|
|
*lprc = rect;
|
|
TRACE("return rect %s\n", wine_dbgstr_rect(lprc));
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
/***
|
|
* DESCRIPTION:
|
|
* Retrieves the spacing between listview control items.
|
|
*
|
|
* PARAMETER(S):
|
|
* [I] infoPtr : valid pointer to the listview structure
|
|
* [I] bSmall : flag for small or large icon
|
|
*
|
|
* RETURN:
|
|
* Horizontal + vertical spacing
|
|
*/
|
|
static LONG LISTVIEW_GetItemSpacing(const LISTVIEW_INFO *infoPtr, BOOL bSmall)
|
|
{
|
|
LONG lResult;
|
|
|
|
if (!bSmall)
|
|
{
|
|
lResult = MAKELONG(infoPtr->iconSpacing.cx, infoPtr->iconSpacing.cy);
|
|
}
|
|
else
|
|
{
|
|
if (infoPtr->uView == LV_VIEW_ICON)
|
|
lResult = MAKELONG(DEFAULT_COLUMN_WIDTH, GetSystemMetrics(SM_CXSMICON)+HEIGHT_PADDING);
|
|
else
|
|
lResult = MAKELONG(infoPtr->nItemWidth, infoPtr->nItemHeight);
|
|
}
|
|
return lResult;
|
|
}
|
|
|
|
/***
|
|
* DESCRIPTION:
|
|
* Retrieves the state of a listview control item.
|
|
*
|
|
* PARAMETER(S):
|
|
* [I] infoPtr : valid pointer to the listview structure
|
|
* [I] nItem : item index
|
|
* [I] uMask : state mask
|
|
*
|
|
* RETURN:
|
|
* State specified by the mask.
|
|
*/
|
|
static UINT LISTVIEW_GetItemState(const LISTVIEW_INFO *infoPtr, INT nItem, UINT uMask)
|
|
{
|
|
LVITEMW lvItem;
|
|
|
|
if (nItem < 0 || nItem >= infoPtr->nItemCount) return 0;
|
|
|
|
lvItem.iItem = nItem;
|
|
lvItem.iSubItem = 0;
|
|
lvItem.mask = LVIF_STATE;
|
|
lvItem.stateMask = uMask;
|
|
if (!LISTVIEW_GetItemW(infoPtr, &lvItem)) return 0;
|
|
|
|
return lvItem.state & uMask;
|
|
}
|
|
|
|
/***
|
|
* DESCRIPTION:
|
|
* Retrieves the text of a listview control item or subitem.
|
|
*
|
|
* PARAMETER(S):
|
|
* [I] hwnd : window handle
|
|
* [I] nItem : item index
|
|
* [IO] lpLVItem : item information
|
|
* [I] isW : TRUE if lpLVItem is Unicode
|
|
*
|
|
* RETURN:
|
|
* SUCCESS : string length
|
|
* FAILURE : 0
|
|
*/
|
|
static INT LISTVIEW_GetItemTextT(const LISTVIEW_INFO *infoPtr, INT nItem, LPLVITEMW lpLVItem, BOOL isW)
|
|
{
|
|
if (!lpLVItem || nItem < 0 || nItem >= infoPtr->nItemCount) return 0;
|
|
|
|
lpLVItem->mask = LVIF_TEXT;
|
|
lpLVItem->iItem = nItem;
|
|
if (!LISTVIEW_GetItemExtT(infoPtr, lpLVItem, isW)) return 0;
|
|
|
|
return textlenT(lpLVItem->pszText, isW);
|
|
}
|
|
|
|
/***
|
|
* DESCRIPTION:
|
|
* Searches for an item based on properties + relationships.
|
|
*
|
|
* PARAMETER(S):
|
|
* [I] infoPtr : valid pointer to the listview structure
|
|
* [I] nItem : item index
|
|
* [I] uFlags : relationship flag
|
|
*
|
|
* RETURN:
|
|
* SUCCESS : item index
|
|
* FAILURE : -1
|
|
*/
|
|
static INT LISTVIEW_GetNextItem(const LISTVIEW_INFO *infoPtr, INT nItem, UINT uFlags)
|
|
{
|
|
UINT uMask = 0;
|
|
LVFINDINFOW lvFindInfo;
|
|
INT nCountPerColumn;
|
|
INT nCountPerRow;
|
|
INT i;
|
|
|
|
TRACE("nItem=%d, uFlags=%x, nItemCount=%d\n", nItem, uFlags, infoPtr->nItemCount);
|
|
if (nItem < -1 || nItem >= infoPtr->nItemCount) return -1;
|
|
|
|
ZeroMemory(&lvFindInfo, sizeof(lvFindInfo));
|
|
|
|
if (uFlags & LVNI_CUT)
|
|
uMask |= LVIS_CUT;
|
|
|
|
if (uFlags & LVNI_DROPHILITED)
|
|
uMask |= LVIS_DROPHILITED;
|
|
|
|
if (uFlags & LVNI_FOCUSED)
|
|
uMask |= LVIS_FOCUSED;
|
|
|
|
if (uFlags & LVNI_SELECTED)
|
|
uMask |= LVIS_SELECTED;
|
|
|
|
/* if we're asked for the focused item, that's only one,
|
|
* so it's worth optimizing */
|
|
if (uFlags & LVNI_FOCUSED)
|
|
{
|
|
if ((LISTVIEW_GetItemState(infoPtr, infoPtr->nFocusedItem, uMask) & uMask) != uMask) return -1;
|
|
return (infoPtr->nFocusedItem == nItem) ? -1 : infoPtr->nFocusedItem;
|
|
}
|
|
|
|
if (uFlags & LVNI_ABOVE)
|
|
{
|
|
if ((infoPtr->uView == LV_VIEW_LIST) || (infoPtr->uView == LV_VIEW_DETAILS))
|
|
{
|
|
while (nItem >= 0)
|
|
{
|
|
nItem--;
|
|
if ((LISTVIEW_GetItemState(infoPtr, nItem, uMask) & uMask) == uMask)
|
|
return nItem;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
/* Special case for autoarrange - move 'til the top of a list */
|
|
if (is_autoarrange(infoPtr))
|
|
{
|
|
nCountPerRow = LISTVIEW_GetCountPerRow(infoPtr);
|
|
while (nItem - nCountPerRow >= 0)
|
|
{
|
|
nItem -= nCountPerRow;
|
|
if ((LISTVIEW_GetItemState(infoPtr, nItem, uMask) & uMask) == uMask)
|
|
return nItem;
|
|
}
|
|
return -1;
|
|
}
|
|
lvFindInfo.flags = LVFI_NEARESTXY;
|
|
lvFindInfo.vkDirection = VK_UP;
|
|
LISTVIEW_GetItemPosition(infoPtr, nItem, &lvFindInfo.pt);
|
|
while ((nItem = LISTVIEW_FindItemW(infoPtr, nItem, &lvFindInfo)) != -1)
|
|
{
|
|
if ((LISTVIEW_GetItemState(infoPtr, nItem, uMask) & uMask) == uMask)
|
|
return nItem;
|
|
}
|
|
}
|
|
}
|
|
else if (uFlags & LVNI_BELOW)
|
|
{
|
|
if ((infoPtr->uView == LV_VIEW_LIST) || (infoPtr->uView == LV_VIEW_DETAILS))
|
|
{
|
|
while (nItem < infoPtr->nItemCount)
|
|
{
|
|
nItem++;
|
|
if ((LISTVIEW_GetItemState(infoPtr, nItem, uMask) & uMask) == uMask)
|
|
return nItem;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
/* Special case for autoarrange - move 'til the bottom of a list */
|
|
if (is_autoarrange(infoPtr))
|
|
{
|
|
nCountPerRow = LISTVIEW_GetCountPerRow(infoPtr);
|
|
while (nItem + nCountPerRow < infoPtr->nItemCount )
|
|
{
|
|
nItem += nCountPerRow;
|
|
if ((LISTVIEW_GetItemState(infoPtr, nItem, uMask) & uMask) == uMask)
|
|
return nItem;
|
|
}
|
|
return -1;
|
|
}
|
|
lvFindInfo.flags = LVFI_NEARESTXY;
|
|
lvFindInfo.vkDirection = VK_DOWN;
|
|
LISTVIEW_GetItemPosition(infoPtr, nItem, &lvFindInfo.pt);
|
|
while ((nItem = LISTVIEW_FindItemW(infoPtr, nItem, &lvFindInfo)) != -1)
|
|
{
|
|
if ((LISTVIEW_GetItemState(infoPtr, nItem, uMask) & uMask) == uMask)
|
|
return nItem;
|
|
}
|
|
}
|
|
}
|
|
else if (uFlags & LVNI_TOLEFT)
|
|
{
|
|
if (infoPtr->uView == LV_VIEW_LIST)
|
|
{
|
|
nCountPerColumn = LISTVIEW_GetCountPerColumn(infoPtr);
|
|
while (nItem - nCountPerColumn >= 0)
|
|
{
|
|
nItem -= nCountPerColumn;
|
|
if ((LISTVIEW_GetItemState(infoPtr, nItem, uMask) & uMask) == uMask)
|
|
return nItem;
|
|
}
|
|
}
|
|
else if ((infoPtr->uView == LV_VIEW_SMALLICON) || (infoPtr->uView == LV_VIEW_ICON))
|
|
{
|
|
/* Special case for autoarrange - move 'til the beginning of a row */
|
|
if (is_autoarrange(infoPtr))
|
|
{
|
|
nCountPerRow = LISTVIEW_GetCountPerRow(infoPtr);
|
|
while (nItem % nCountPerRow > 0)
|
|
{
|
|
nItem --;
|
|
if ((LISTVIEW_GetItemState(infoPtr, nItem, uMask) & uMask) == uMask)
|
|
return nItem;
|
|
}
|
|
return -1;
|
|
}
|
|
lvFindInfo.flags = LVFI_NEARESTXY;
|
|
lvFindInfo.vkDirection = VK_LEFT;
|
|
LISTVIEW_GetItemPosition(infoPtr, nItem, &lvFindInfo.pt);
|
|
while ((nItem = LISTVIEW_FindItemW(infoPtr, nItem, &lvFindInfo)) != -1)
|
|
{
|
|
if ((LISTVIEW_GetItemState(infoPtr, nItem, uMask) & uMask) == uMask)
|
|
return nItem;
|
|
}
|
|
}
|
|
}
|
|
else if (uFlags & LVNI_TORIGHT)
|
|
{
|
|
if (infoPtr->uView == LV_VIEW_LIST)
|
|
{
|
|
nCountPerColumn = LISTVIEW_GetCountPerColumn(infoPtr);
|
|
while (nItem + nCountPerColumn < infoPtr->nItemCount)
|
|
{
|
|
nItem += nCountPerColumn;
|
|
if ((LISTVIEW_GetItemState(infoPtr, nItem, uMask) & uMask) == uMask)
|
|
return nItem;
|
|
}
|
|
}
|
|
else if ((infoPtr->uView == LV_VIEW_SMALLICON) || (infoPtr->uView == LV_VIEW_ICON))
|
|
{
|
|
/* Special case for autoarrange - move 'til the end of a row */
|
|
if (is_autoarrange(infoPtr))
|
|
{
|
|
nCountPerRow = LISTVIEW_GetCountPerRow(infoPtr);
|
|
while (nItem % nCountPerRow < nCountPerRow - 1 )
|
|
{
|
|
nItem ++;
|
|
if ((LISTVIEW_GetItemState(infoPtr, nItem, uMask) & uMask) == uMask)
|
|
return nItem;
|
|
}
|
|
return -1;
|
|
}
|
|
lvFindInfo.flags = LVFI_NEARESTXY;
|
|
lvFindInfo.vkDirection = VK_RIGHT;
|
|
LISTVIEW_GetItemPosition(infoPtr, nItem, &lvFindInfo.pt);
|
|
while ((nItem = LISTVIEW_FindItemW(infoPtr, nItem, &lvFindInfo)) != -1)
|
|
{
|
|
if ((LISTVIEW_GetItemState(infoPtr, nItem, uMask) & uMask) == uMask)
|
|
return nItem;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
nItem++;
|
|
|
|
/* search by index */
|
|
for (i = nItem; i < infoPtr->nItemCount; i++)
|
|
{
|
|
if ((LISTVIEW_GetItemState(infoPtr, i, uMask) & uMask) == uMask)
|
|
return i;
|
|
}
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
/* LISTVIEW_GetNumberOfWorkAreas */
|
|
|
|
/***
|
|
* DESCRIPTION:
|
|
* Retrieves the origin coordinates when in icon or small icon display mode.
|
|
*
|
|
* PARAMETER(S):
|
|
* [I] infoPtr : valid pointer to the listview structure
|
|
* [O] lpptOrigin : coordinate information
|
|
*
|
|
* RETURN:
|
|
* None.
|
|
*/
|
|
static void LISTVIEW_GetOrigin(const LISTVIEW_INFO *infoPtr, LPPOINT lpptOrigin)
|
|
{
|
|
INT nHorzPos = 0, nVertPos = 0;
|
|
SCROLLINFO scrollInfo;
|
|
|
|
scrollInfo.cbSize = sizeof(SCROLLINFO);
|
|
scrollInfo.fMask = SIF_POS;
|
|
|
|
if (GetScrollInfo(infoPtr->hwndSelf, SB_HORZ, &scrollInfo))
|
|
nHorzPos = scrollInfo.nPos;
|
|
if (GetScrollInfo(infoPtr->hwndSelf, SB_VERT, &scrollInfo))
|
|
nVertPos = scrollInfo.nPos;
|
|
|
|
TRACE("nHorzPos=%d, nVertPos=%d\n", nHorzPos, nVertPos);
|
|
|
|
lpptOrigin->x = infoPtr->rcList.left;
|
|
lpptOrigin->y = infoPtr->rcList.top;
|
|
if (infoPtr->uView == LV_VIEW_LIST)
|
|
nHorzPos *= infoPtr->nItemWidth;
|
|
else if (infoPtr->uView == LV_VIEW_DETAILS)
|
|
nVertPos *= infoPtr->nItemHeight;
|
|
|
|
lpptOrigin->x -= nHorzPos;
|
|
lpptOrigin->y -= nVertPos;
|
|
|
|
TRACE(" origin=%s\n", wine_dbgstr_point(lpptOrigin));
|
|
}
|
|
|
|
/***
|
|
* DESCRIPTION:
|
|
* Retrieves the width of a string.
|
|
*
|
|
* PARAMETER(S):
|
|
* [I] hwnd : window handle
|
|
* [I] lpszText : text string to process
|
|
* [I] isW : TRUE if lpszText is Unicode, FALSE otherwise
|
|
*
|
|
* RETURN:
|
|
* SUCCESS : string width (in pixels)
|
|
* FAILURE : zero
|
|
*/
|
|
static INT LISTVIEW_GetStringWidthT(const LISTVIEW_INFO *infoPtr, LPCWSTR lpszText, BOOL isW)
|
|
{
|
|
SIZE stringSize;
|
|
|
|
stringSize.cx = 0;
|
|
if (is_text(lpszText))
|
|
{
|
|
HFONT hFont = infoPtr->hFont ? infoPtr->hFont : infoPtr->hDefaultFont;
|
|
HDC hdc = GetDC(infoPtr->hwndSelf);
|
|
HFONT hOldFont = SelectObject(hdc, hFont);
|
|
|
|
if (isW)
|
|
GetTextExtentPointW(hdc, lpszText, lstrlenW(lpszText), &stringSize);
|
|
else
|
|
GetTextExtentPointA(hdc, (LPCSTR)lpszText, lstrlenA((LPCSTR)lpszText), &stringSize);
|
|
SelectObject(hdc, hOldFont);
|
|
ReleaseDC(infoPtr->hwndSelf, hdc);
|
|
}
|
|
return stringSize.cx;
|
|
}
|
|
|
|
/***
|
|
* DESCRIPTION:
|
|
* Determines which listview item is located at the specified position.
|
|
*
|
|
* PARAMETER(S):
|
|
* [I] infoPtr : valid pointer to the listview structure
|
|
* [IO] lpht : hit test information
|
|
* [I] subitem : fill out iSubItem.
|
|
* [I] select : return the index only if the hit selects the item
|
|
*
|
|
* NOTE:
|
|
* (mm 20001022): We must not allow iSubItem to be touched, for
|
|
* an app might pass only a structure with space up to iItem!
|
|
* (MS Office 97 does that for instance in the file open dialog)
|
|
*
|
|
* RETURN:
|
|
* SUCCESS : item index
|
|
* FAILURE : -1
|
|
*/
|
|
static INT LISTVIEW_HitTest(const LISTVIEW_INFO *infoPtr, LPLVHITTESTINFO lpht, BOOL subitem, BOOL select)
|
|
{
|
|
WCHAR szDispText[DISP_TEXT_SIZE] = { '\0' };
|
|
RECT rcBox, rcBounds, rcState, rcIcon, rcLabel, rcSearch;
|
|
POINT Origin, Position, opt;
|
|
BOOL is_fullrow;
|
|
LVITEMW lvItem;
|
|
ITERATOR i;
|
|
INT iItem;
|
|
|
|
TRACE("(pt=%s, subitem=%d, select=%d)\n", wine_dbgstr_point(&lpht->pt), subitem, select);
|
|
|
|
lpht->flags = 0;
|
|
lpht->iItem = -1;
|
|
if (subitem) lpht->iSubItem = 0;
|
|
|
|
LISTVIEW_GetOrigin(infoPtr, &Origin);
|
|
|
|
/* set whole list relation flags */
|
|
if (subitem && infoPtr->uView == LV_VIEW_DETAILS)
|
|
{
|
|
/* LVM_SUBITEMHITTEST checks left bound of possible client area */
|
|
if (infoPtr->rcList.left > lpht->pt.x && Origin.x < lpht->pt.x)
|
|
lpht->flags |= LVHT_TOLEFT;
|
|
|
|
if (lpht->pt.y < infoPtr->rcList.top && lpht->pt.y >= 0)
|
|
opt.y = lpht->pt.y + infoPtr->rcList.top;
|
|
else
|
|
opt.y = lpht->pt.y;
|
|
|
|
if (infoPtr->rcList.bottom < opt.y)
|
|
lpht->flags |= LVHT_BELOW;
|
|
}
|
|
else
|
|
{
|
|
if (infoPtr->rcList.left > lpht->pt.x)
|
|
lpht->flags |= LVHT_TOLEFT;
|
|
else if (infoPtr->rcList.right < lpht->pt.x)
|
|
lpht->flags |= LVHT_TORIGHT;
|
|
|
|
if (infoPtr->rcList.top > lpht->pt.y)
|
|
lpht->flags |= LVHT_ABOVE;
|
|
else if (infoPtr->rcList.bottom < lpht->pt.y)
|
|
lpht->flags |= LVHT_BELOW;
|
|
}
|
|
|
|
/* even if item is invalid try to find subitem */
|
|
if (infoPtr->uView == LV_VIEW_DETAILS && subitem)
|
|
{
|
|
RECT *pRect;
|
|
INT j;
|
|
|
|
opt.x = lpht->pt.x - Origin.x;
|
|
|
|
lpht->iSubItem = -1;
|
|
for (j = 0; j < DPA_GetPtrCount(infoPtr->hdpaColumns); j++)
|
|
{
|
|
pRect = &LISTVIEW_GetColumnInfo(infoPtr, j)->rcHeader;
|
|
|
|
if ((opt.x >= pRect->left) && (opt.x < pRect->right))
|
|
{
|
|
lpht->iSubItem = j;
|
|
break;
|
|
}
|
|
}
|
|
TRACE("lpht->iSubItem=%d\n", lpht->iSubItem);
|
|
|
|
/* if we're outside horizontal columns bounds there's nothing to test further */
|
|
if (lpht->iSubItem == -1)
|
|
{
|
|
lpht->iItem = -1;
|
|
lpht->flags = LVHT_NOWHERE;
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
TRACE("lpht->flags=0x%x\n", lpht->flags);
|
|
if (lpht->flags) return -1;
|
|
|
|
lpht->flags |= LVHT_NOWHERE;
|
|
|
|
/* first deal with the large items */
|
|
rcSearch.left = lpht->pt.x;
|
|
rcSearch.top = lpht->pt.y;
|
|
rcSearch.right = rcSearch.left + 1;
|
|
rcSearch.bottom = rcSearch.top + 1;
|
|
|
|
iterator_frameditems(&i, infoPtr, &rcSearch);
|
|
iterator_next(&i); /* go to first item in the sequence */
|
|
iItem = i.nItem;
|
|
iterator_destroy(&i);
|
|
|
|
TRACE("lpht->iItem=%d\n", iItem);
|
|
if (iItem == -1) return -1;
|
|
|
|
lvItem.mask = LVIF_STATE | LVIF_TEXT;
|
|
if (infoPtr->uView == LV_VIEW_DETAILS) lvItem.mask |= LVIF_INDENT;
|
|
lvItem.stateMask = LVIS_STATEIMAGEMASK;
|
|
if (infoPtr->uView == LV_VIEW_ICON) lvItem.stateMask |= LVIS_FOCUSED;
|
|
lvItem.iItem = iItem;
|
|
lvItem.iSubItem = subitem ? lpht->iSubItem : 0;
|
|
lvItem.pszText = szDispText;
|
|
lvItem.cchTextMax = DISP_TEXT_SIZE;
|
|
if (!LISTVIEW_GetItemW(infoPtr, &lvItem)) return -1;
|
|
if (!infoPtr->bFocus) lvItem.state &= ~LVIS_FOCUSED;
|
|
|
|
LISTVIEW_GetItemMetrics(infoPtr, &lvItem, &rcBox, NULL, &rcIcon, &rcState, &rcLabel);
|
|
LISTVIEW_GetItemOrigin(infoPtr, iItem, &Position);
|
|
opt.x = lpht->pt.x - Position.x - Origin.x;
|
|
|
|
if (lpht->pt.y < infoPtr->rcList.top && lpht->pt.y >= 0)
|
|
opt.y = lpht->pt.y - Position.y - Origin.y + infoPtr->rcList.top;
|
|
else
|
|
opt.y = lpht->pt.y - Position.y - Origin.y;
|
|
|
|
if (infoPtr->uView == LV_VIEW_DETAILS)
|
|
{
|
|
rcBounds = rcBox;
|
|
if (infoPtr->dwLvExStyle & LVS_EX_FULLROWSELECT)
|
|
opt.x = lpht->pt.x - Origin.x;
|
|
}
|
|
else
|
|
{
|
|
UnionRect(&rcBounds, &rcIcon, &rcLabel);
|
|
UnionRect(&rcBounds, &rcBounds, &rcState);
|
|
}
|
|
TRACE("rcBounds=%s\n", wine_dbgstr_rect(&rcBounds));
|
|
if (!PtInRect(&rcBounds, opt)) return -1;
|
|
|
|
/* That's a special case - row rectangle is used as item rectangle and
|
|
returned flags contain all item parts. */
|
|
is_fullrow = (infoPtr->uView == LV_VIEW_DETAILS) && ((infoPtr->dwLvExStyle & LVS_EX_FULLROWSELECT) || (infoPtr->dwStyle & LVS_OWNERDRAWFIXED));
|
|
|
|
if (PtInRect(&rcIcon, opt))
|
|
lpht->flags |= LVHT_ONITEMICON;
|
|
else if (PtInRect(&rcLabel, opt))
|
|
lpht->flags |= LVHT_ONITEMLABEL;
|
|
else if (infoPtr->himlState && PtInRect(&rcState, opt))
|
|
lpht->flags |= LVHT_ONITEMSTATEICON;
|
|
if (is_fullrow && !(lpht->flags & LVHT_ONITEM))
|
|
{
|
|
lpht->flags = LVHT_ONITEM | LVHT_ABOVE;
|
|
}
|
|
if (lpht->flags & LVHT_ONITEM)
|
|
lpht->flags &= ~LVHT_NOWHERE;
|
|
TRACE("lpht->flags=0x%x\n", lpht->flags);
|
|
|
|
if (select && !is_fullrow)
|
|
{
|
|
if (infoPtr->uView == LV_VIEW_DETAILS)
|
|
{
|
|
/* get main item bounds */
|
|
lvItem.iSubItem = 0;
|
|
LISTVIEW_GetItemMetrics(infoPtr, &lvItem, &rcBox, NULL, &rcIcon, &rcState, &rcLabel);
|
|
UnionRect(&rcBounds, &rcIcon, &rcLabel);
|
|
UnionRect(&rcBounds, &rcBounds, &rcState);
|
|
}
|
|
if (!PtInRect(&rcBounds, opt)) iItem = -1;
|
|
}
|
|
return lpht->iItem = iItem;
|
|
}
|
|
|
|
/***
|
|
* DESCRIPTION:
|
|
* Inserts a new item in the listview control.
|
|
*
|
|
* PARAMETER(S):
|
|
* [I] infoPtr : valid pointer to the listview structure
|
|
* [I] lpLVItem : item information
|
|
* [I] isW : TRUE if lpLVItem is Unicode, FALSE if it's ANSI
|
|
*
|
|
* RETURN:
|
|
* SUCCESS : new item index
|
|
* FAILURE : -1
|
|
*/
|
|
static INT LISTVIEW_InsertItemT(LISTVIEW_INFO *infoPtr, const LVITEMW *lpLVItem, BOOL isW)
|
|
{
|
|
INT nItem;
|
|
HDPA hdpaSubItems;
|
|
NMLISTVIEW nmlv;
|
|
ITEM_INFO *lpItem;
|
|
ITEM_ID *lpID;
|
|
BOOL is_sorted, has_changed;
|
|
LVITEMW item;
|
|
HWND hwndSelf = infoPtr->hwndSelf;
|
|
|
|
TRACE("(item=%s, isW=%d)\n", debuglvitem_t(lpLVItem, isW), isW);
|
|
|
|
if (infoPtr->dwStyle & LVS_OWNERDATA) return infoPtr->nItemCount++;
|
|
|
|
/* make sure it's an item, and not a subitem; cannot insert a subitem */
|
|
if (!lpLVItem || lpLVItem->iSubItem) return -1;
|
|
|
|
if (!is_assignable_item(lpLVItem, infoPtr->dwStyle)) return -1;
|
|
|
|
if (!(lpItem = Alloc(sizeof(ITEM_INFO)))) return -1;
|
|
|
|
/* insert item in listview control data structure */
|
|
if ( !(hdpaSubItems = DPA_Create(8)) ) goto fail;
|
|
if ( !DPA_SetPtr(hdpaSubItems, 0, lpItem) ) assert (FALSE);
|
|
|
|
/* link with id struct */
|
|
if (!(lpID = Alloc(sizeof(ITEM_ID)))) goto fail;
|
|
lpItem->id = lpID;
|
|
lpID->item = hdpaSubItems;
|
|
lpID->id = get_next_itemid(infoPtr);
|
|
if ( DPA_InsertPtr(infoPtr->hdpaItemIds, infoPtr->nItemCount, lpID) == -1) goto fail;
|
|
|
|
is_sorted = (infoPtr->dwStyle & (LVS_SORTASCENDING | LVS_SORTDESCENDING)) &&
|
|
!(infoPtr->dwStyle & LVS_OWNERDRAWFIXED) && (LPSTR_TEXTCALLBACKW != lpLVItem->pszText);
|
|
|
|
if (lpLVItem->iItem < 0 && !is_sorted) return -1;
|
|
|
|
/* calculate new item index */
|
|
if (is_sorted)
|
|
{
|
|
HDPA hItem;
|
|
ITEM_INFO *item_s;
|
|
INT i = 0, cmpv;
|
|
|
|
while (i < infoPtr->nItemCount)
|
|
{
|
|
hItem = DPA_GetPtr( infoPtr->hdpaItems, i);
|
|
item_s = DPA_GetPtr(hItem, 0);
|
|
|
|
cmpv = textcmpWT(item_s->hdr.pszText, lpLVItem->pszText, isW);
|
|
if (infoPtr->dwStyle & LVS_SORTDESCENDING) cmpv *= -1;
|
|
|
|
if (cmpv >= 0) break;
|
|
i++;
|
|
}
|
|
nItem = i;
|
|
}
|
|
else
|
|
nItem = min(lpLVItem->iItem, infoPtr->nItemCount);
|
|
|
|
TRACE("inserting at %d, sorted=%d, count=%d, iItem=%d\n", nItem, is_sorted, infoPtr->nItemCount, lpLVItem->iItem);
|
|
nItem = DPA_InsertPtr( infoPtr->hdpaItems, nItem, hdpaSubItems );
|
|
if (nItem == -1) goto fail;
|
|
infoPtr->nItemCount++;
|
|
|
|
/* shift indices first so they don't get tangled */
|
|
LISTVIEW_ShiftIndices(infoPtr, nItem, 1);
|
|
|
|
/* set the item attributes */
|
|
if (lpLVItem->mask & (LVIF_GROUPID|LVIF_COLUMNS))
|
|
{
|
|
/* full size structure expected - _WIN32IE >= 0x560 */
|
|
item = *lpLVItem;
|
|
}
|
|
else if (lpLVItem->mask & LVIF_INDENT)
|
|
{
|
|
/* indent member expected - _WIN32IE >= 0x300 */
|
|
memcpy(&item, lpLVItem, offsetof( LVITEMW, iGroupId ));
|
|
}
|
|
else
|
|
{
|
|
/* minimal structure expected */
|
|
memcpy(&item, lpLVItem, offsetof( LVITEMW, iIndent ));
|
|
}
|
|
item.iItem = nItem;
|
|
if (infoPtr->dwLvExStyle & LVS_EX_CHECKBOXES)
|
|
{
|
|
item.mask |= LVIF_STATE;
|
|
item.stateMask |= LVIS_STATEIMAGEMASK;
|
|
item.state &= ~LVIS_STATEIMAGEMASK;
|
|
item.state |= INDEXTOSTATEIMAGEMASK(1);
|
|
}
|
|
|
|
if (!set_main_item(infoPtr, &item, TRUE, isW, &has_changed)) goto undo;
|
|
|
|
/* make room for the position, if we are in the right mode */
|
|
if ((infoPtr->uView == LV_VIEW_SMALLICON) || (infoPtr->uView == LV_VIEW_ICON))
|
|
{
|
|
if (DPA_InsertPtr(infoPtr->hdpaPosX, nItem, 0) == -1)
|
|
goto undo;
|
|
if (DPA_InsertPtr(infoPtr->hdpaPosY, nItem, 0) == -1)
|
|
{
|
|
DPA_DeletePtr(infoPtr->hdpaPosX, nItem);
|
|
goto undo;
|
|
}
|
|
}
|
|
|
|
/* send LVN_INSERTITEM notification */
|
|
memset(&nmlv, 0, sizeof(NMLISTVIEW));
|
|
nmlv.iItem = nItem;
|
|
nmlv.lParam = lpItem->lParam;
|
|
notify_listview(infoPtr, LVN_INSERTITEM, &nmlv);
|
|
if (!IsWindow(hwndSelf))
|
|
return -1;
|
|
|
|
/* align items (set position of each item) */
|
|
if (infoPtr->uView == LV_VIEW_SMALLICON || infoPtr->uView == LV_VIEW_ICON)
|
|
{
|
|
POINT pt;
|
|
|
|
if (infoPtr->dwStyle & LVS_ALIGNLEFT)
|
|
LISTVIEW_NextIconPosLeft(infoPtr, &pt);
|
|
else
|
|
LISTVIEW_NextIconPosTop(infoPtr, &pt);
|
|
|
|
LISTVIEW_MoveIconTo(infoPtr, nItem, &pt, TRUE);
|
|
}
|
|
|
|
/* now is the invalidation fun */
|
|
LISTVIEW_ScrollOnInsert(infoPtr, nItem, 1);
|
|
return nItem;
|
|
|
|
undo:
|
|
LISTVIEW_ShiftIndices(infoPtr, nItem, -1);
|
|
LISTVIEW_ShiftFocus(infoPtr, infoPtr->nFocusedItem, nItem, -1);
|
|
DPA_DeletePtr(infoPtr->hdpaItems, nItem);
|
|
infoPtr->nItemCount--;
|
|
fail:
|
|
DPA_DeletePtr(hdpaSubItems, 0);
|
|
DPA_Destroy (hdpaSubItems);
|
|
Free (lpItem);
|
|
return -1;
|
|
}
|
|
|
|
/***
|
|
* DESCRIPTION:
|
|
* Checks item visibility.
|
|
*
|
|
* PARAMETER(S):
|
|
* [I] infoPtr : valid pointer to the listview structure
|
|
* [I] nFirst : item index to check for
|
|
*
|
|
* RETURN:
|
|
* Item visible : TRUE
|
|
* Item invisible or failure : FALSE
|
|
*/
|
|
static BOOL LISTVIEW_IsItemVisible(const LISTVIEW_INFO *infoPtr, INT nItem)
|
|
{
|
|
POINT Origin, Position;
|
|
RECT rcItem;
|
|
HDC hdc;
|
|
BOOL ret;
|
|
|
|
TRACE("nItem=%d\n", nItem);
|
|
|
|
if (nItem < 0 || nItem >= DPA_GetPtrCount(infoPtr->hdpaItems)) return FALSE;
|
|
|
|
LISTVIEW_GetOrigin(infoPtr, &Origin);
|
|
LISTVIEW_GetItemOrigin(infoPtr, nItem, &Position);
|
|
rcItem.left = Position.x + Origin.x;
|
|
rcItem.top = Position.y + Origin.y;
|
|
rcItem.right = rcItem.left + infoPtr->nItemWidth;
|
|
rcItem.bottom = rcItem.top + infoPtr->nItemHeight;
|
|
|
|
hdc = GetDC(infoPtr->hwndSelf);
|
|
if (!hdc) return FALSE;
|
|
ret = RectVisible(hdc, &rcItem);
|
|
ReleaseDC(infoPtr->hwndSelf, hdc);
|
|
|
|
return ret;
|
|
}
|
|
|
|
/***
|
|
* DESCRIPTION:
|
|
* Redraws a range of items.
|
|
*
|
|
* PARAMETER(S):
|
|
* [I] infoPtr : valid pointer to the listview structure
|
|
* [I] nFirst : first item
|
|
* [I] nLast : last item
|
|
*
|
|
* RETURN:
|
|
* SUCCESS : TRUE
|
|
* FAILURE : FALSE
|
|
*/
|
|
static BOOL LISTVIEW_RedrawItems(const LISTVIEW_INFO *infoPtr, INT nFirst, INT nLast)
|
|
{
|
|
INT i;
|
|
|
|
if (nLast < nFirst || min(nFirst, nLast) < 0 ||
|
|
max(nFirst, nLast) >= infoPtr->nItemCount)
|
|
return FALSE;
|
|
|
|
for (i = nFirst; i <= nLast; i++)
|
|
LISTVIEW_InvalidateItem(infoPtr, i);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
/***
|
|
* DESCRIPTION:
|
|
* Scroll the content of a listview.
|
|
*
|
|
* PARAMETER(S):
|
|
* [I] infoPtr : valid pointer to the listview structure
|
|
* [I] dx : horizontal scroll amount in pixels
|
|
* [I] dy : vertical scroll amount in pixels
|
|
*
|
|
* RETURN:
|
|
* SUCCESS : TRUE
|
|
* FAILURE : FALSE
|
|
*
|
|
* COMMENTS:
|
|
* If the control is in report view (LV_VIEW_DETAILS) the control can
|
|
* be scrolled only in line increments. "dy" will be rounded to the
|
|
* nearest number of pixels that are a whole line. Ex: if line height
|
|
* is 16 and an 8 is passed, the list will be scrolled by 16. If a 7
|
|
* is passed, then the scroll will be 0. (per MSDN 7/2002)
|
|
*/
|
|
static BOOL LISTVIEW_Scroll(LISTVIEW_INFO *infoPtr, INT dx, INT dy)
|
|
{
|
|
switch(infoPtr->uView) {
|
|
case LV_VIEW_DETAILS:
|
|
dy += (dy < 0 ? -1 : 1) * infoPtr->nItemHeight/2;
|
|
dy /= infoPtr->nItemHeight;
|
|
break;
|
|
case LV_VIEW_LIST:
|
|
if (dy != 0) return FALSE;
|
|
break;
|
|
default: /* icon */
|
|
break;
|
|
}
|
|
|
|
if (dx != 0) LISTVIEW_HScroll(infoPtr, SB_INTERNAL, dx);
|
|
if (dy != 0) LISTVIEW_VScroll(infoPtr, SB_INTERNAL, dy);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
/***
|
|
* DESCRIPTION:
|
|
* Sets the background color.
|
|
*
|
|
* PARAMETER(S):
|
|
* [I] infoPtr : valid pointer to the listview structure
|
|
* [I] color : background color
|
|
*
|
|
* RETURN:
|
|
* SUCCESS : TRUE
|
|
* FAILURE : FALSE
|
|
*/
|
|
static BOOL LISTVIEW_SetBkColor(LISTVIEW_INFO *infoPtr, COLORREF color)
|
|
{
|
|
TRACE("(color=%x)\n", color);
|
|
|
|
if(infoPtr->clrBk != color) {
|
|
if (infoPtr->clrBk != CLR_NONE) DeleteObject(infoPtr->hBkBrush);
|
|
infoPtr->clrBk = color;
|
|
if (color == CLR_NONE)
|
|
infoPtr->hBkBrush = (HBRUSH)GetClassLongPtrW(infoPtr->hwndSelf, GCLP_HBRBACKGROUND);
|
|
else
|
|
{
|
|
infoPtr->hBkBrush = CreateSolidBrush(color);
|
|
infoPtr->dwLvExStyle &= ~LVS_EX_TRANSPARENTBKGND;
|
|
}
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
/* LISTVIEW_SetBkImage */
|
|
|
|
/*** Helper for {Insert,Set}ColumnT *only* */
|
|
static void column_fill_hditem(const LISTVIEW_INFO *infoPtr, HDITEMW *lphdi, INT nColumn,
|
|
const LVCOLUMNW *lpColumn, BOOL isW)
|
|
{
|
|
if (lpColumn->mask & LVCF_FMT)
|
|
{
|
|
/* format member is valid */
|
|
lphdi->mask |= HDI_FORMAT;
|
|
|
|
/* set text alignment (leftmost column must be left-aligned) */
|
|
if (nColumn == 0 || (lpColumn->fmt & LVCFMT_JUSTIFYMASK) == LVCFMT_LEFT)
|
|
lphdi->fmt |= HDF_LEFT;
|
|
else if ((lpColumn->fmt & LVCFMT_JUSTIFYMASK) == LVCFMT_RIGHT)
|
|
lphdi->fmt |= HDF_RIGHT;
|
|
else if ((lpColumn->fmt & LVCFMT_JUSTIFYMASK) == LVCFMT_CENTER)
|
|
lphdi->fmt |= HDF_CENTER;
|
|
|
|
if (lpColumn->fmt & LVCFMT_BITMAP_ON_RIGHT)
|
|
lphdi->fmt |= HDF_BITMAP_ON_RIGHT;
|
|
|
|
if (lpColumn->fmt & LVCFMT_COL_HAS_IMAGES)
|
|
{
|
|
lphdi->fmt |= HDF_IMAGE;
|
|
lphdi->iImage = I_IMAGECALLBACK;
|
|
}
|
|
|
|
if (lpColumn->fmt & LVCFMT_FIXED_WIDTH)
|
|
lphdi->fmt |= HDF_FIXEDWIDTH;
|
|
}
|
|
|
|
if (lpColumn->mask & LVCF_WIDTH)
|
|
{
|
|
lphdi->mask |= HDI_WIDTH;
|
|
if(lpColumn->cx == LVSCW_AUTOSIZE_USEHEADER)
|
|
{
|
|
/* make it fill the remainder of the controls width */
|
|
RECT rcHeader;
|
|
INT item_index;
|
|
|
|
for(item_index = 0; item_index < (nColumn - 1); item_index++)
|
|
{
|
|
LISTVIEW_GetHeaderRect(infoPtr, item_index, &rcHeader);
|
|
lphdi->cxy += rcHeader.right - rcHeader.left;
|
|
}
|
|
|
|
/* retrieve the layout of the header */
|
|
GetClientRect(infoPtr->hwndSelf, &rcHeader);
|
|
TRACE("start cxy=%d rcHeader=%s\n", lphdi->cxy, wine_dbgstr_rect(&rcHeader));
|
|
|
|
lphdi->cxy = (rcHeader.right - rcHeader.left) - lphdi->cxy;
|
|
}
|
|
else
|
|
lphdi->cxy = lpColumn->cx;
|
|
}
|
|
|
|
if (lpColumn->mask & LVCF_TEXT)
|
|
{
|
|
lphdi->mask |= HDI_TEXT | HDI_FORMAT;
|
|
lphdi->fmt |= HDF_STRING;
|
|
lphdi->pszText = lpColumn->pszText;
|
|
lphdi->cchTextMax = textlenT(lpColumn->pszText, isW);
|
|
}
|
|
|
|
if (lpColumn->mask & LVCF_IMAGE)
|
|
{
|
|
lphdi->mask |= HDI_IMAGE;
|
|
lphdi->iImage = lpColumn->iImage;
|
|
}
|
|
|
|
if (lpColumn->mask & LVCF_ORDER)
|
|
{
|
|
lphdi->mask |= HDI_ORDER;
|
|
lphdi->iOrder = lpColumn->iOrder;
|
|
}
|
|
}
|
|
|
|
|
|
/***
|
|
* DESCRIPTION:
|
|
* Inserts a new column.
|
|
*
|
|
* PARAMETER(S):
|
|
* [I] infoPtr : valid pointer to the listview structure
|
|
* [I] nColumn : column index
|
|
* [I] lpColumn : column information
|
|
* [I] isW : TRUE if lpColumn is Unicode, FALSE otherwise
|
|
*
|
|
* RETURN:
|
|
* SUCCESS : new column index
|
|
* FAILURE : -1
|
|
*/
|
|
static INT LISTVIEW_InsertColumnT(LISTVIEW_INFO *infoPtr, INT nColumn,
|
|
const LVCOLUMNW *lpColumn, BOOL isW)
|
|
{
|
|
COLUMN_INFO *lpColumnInfo;
|
|
INT nNewColumn;
|
|
HDITEMW hdi;
|
|
|
|
TRACE("(nColumn=%d, lpColumn=%s, isW=%d)\n", nColumn, debuglvcolumn_t(lpColumn, isW), isW);
|
|
|
|
if (!lpColumn || nColumn < 0) return -1;
|
|
nColumn = min(nColumn, DPA_GetPtrCount(infoPtr->hdpaColumns));
|
|
|
|
ZeroMemory(&hdi, sizeof(HDITEMW));
|
|
column_fill_hditem(infoPtr, &hdi, nColumn, lpColumn, isW);
|
|
|
|
/*
|
|
* A mask not including LVCF_WIDTH turns into a mask of width, width 10
|
|
* (can be seen in SPY) otherwise column never gets added.
|
|
*/
|
|
if (!(lpColumn->mask & LVCF_WIDTH)) {
|
|
hdi.mask |= HDI_WIDTH;
|
|
hdi.cxy = 10;
|
|
}
|
|
|
|
/*
|
|
* when the iSubItem is available Windows copies it to the header lParam. It seems
|
|
* to happen only in LVM_INSERTCOLUMN - not in LVM_SETCOLUMN
|
|
*/
|
|
if (lpColumn->mask & LVCF_SUBITEM)
|
|
{
|
|
hdi.mask |= HDI_LPARAM;
|
|
hdi.lParam = lpColumn->iSubItem;
|
|
}
|
|
|
|
/* create header if not present */
|
|
LISTVIEW_CreateHeader(infoPtr);
|
|
if (!(LVS_NOCOLUMNHEADER & infoPtr->dwStyle) &&
|
|
(infoPtr->uView == LV_VIEW_DETAILS) && (WS_VISIBLE & infoPtr->dwStyle))
|
|
{
|
|
ShowWindow(infoPtr->hwndHeader, SW_SHOWNORMAL);
|
|
}
|
|
|
|
/* insert item in header control */
|
|
nNewColumn = SendMessageW(infoPtr->hwndHeader,
|
|
isW ? HDM_INSERTITEMW : HDM_INSERTITEMA,
|
|
nColumn, (LPARAM)&hdi);
|
|
if (nNewColumn == -1) return -1;
|
|
if (nNewColumn != nColumn) ERR("nColumn=%d, nNewColumn=%d\n", nColumn, nNewColumn);
|
|
|
|
/* create our own column info */
|
|
if (!(lpColumnInfo = Alloc(sizeof(COLUMN_INFO)))) goto fail;
|
|
if (DPA_InsertPtr(infoPtr->hdpaColumns, nNewColumn, lpColumnInfo) == -1) goto fail;
|
|
|
|
if (lpColumn->mask & LVCF_FMT) lpColumnInfo->fmt = lpColumn->fmt;
|
|
if (lpColumn->mask & LVCF_MINWIDTH) lpColumnInfo->cxMin = lpColumn->cxMin;
|
|
if (!SendMessageW(infoPtr->hwndHeader, HDM_GETITEMRECT, nNewColumn, (LPARAM)&lpColumnInfo->rcHeader))
|
|
goto fail;
|
|
|
|
/* now we have to actually adjust the data */
|
|
if (!(infoPtr->dwStyle & LVS_OWNERDATA) && infoPtr->nItemCount > 0)
|
|
{
|
|
SUBITEM_INFO *lpSubItem;
|
|
HDPA hdpaSubItems;
|
|
INT nItem, i;
|
|
LVITEMW item;
|
|
BOOL changed;
|
|
|
|
item.iSubItem = nNewColumn;
|
|
item.mask = LVIF_TEXT | LVIF_IMAGE;
|
|
item.iImage = I_IMAGECALLBACK;
|
|
item.pszText = LPSTR_TEXTCALLBACKW;
|
|
|
|
for (nItem = 0; nItem < infoPtr->nItemCount; nItem++)
|
|
{
|
|
hdpaSubItems = DPA_GetPtr(infoPtr->hdpaItems, nItem);
|
|
for (i = 1; i < DPA_GetPtrCount(hdpaSubItems); i++)
|
|
{
|
|
lpSubItem = DPA_GetPtr(hdpaSubItems, i);
|
|
if (lpSubItem->iSubItem >= nNewColumn)
|
|
lpSubItem->iSubItem++;
|
|
}
|
|
|
|
/* add new subitem for each item */
|
|
item.iItem = nItem;
|
|
set_sub_item(infoPtr, &item, isW, &changed);
|
|
}
|
|
}
|
|
|
|
/* make space for the new column */
|
|
LISTVIEW_ScrollColumns(infoPtr, nNewColumn + 1, lpColumnInfo->rcHeader.right - lpColumnInfo->rcHeader.left);
|
|
LISTVIEW_UpdateItemSize(infoPtr);
|
|
|
|
return nNewColumn;
|
|
|
|
fail:
|
|
if (nNewColumn != -1) SendMessageW(infoPtr->hwndHeader, HDM_DELETEITEM, nNewColumn, 0);
|
|
if (lpColumnInfo)
|
|
{
|
|
DPA_DeletePtr(infoPtr->hdpaColumns, nNewColumn);
|
|
Free(lpColumnInfo);
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
/***
|
|
* DESCRIPTION:
|
|
* Sets the attributes of a header item.
|
|
*
|
|
* PARAMETER(S):
|
|
* [I] infoPtr : valid pointer to the listview structure
|
|
* [I] nColumn : column index
|
|
* [I] lpColumn : column attributes
|
|
* [I] isW: if TRUE, then lpColumn is a LPLVCOLUMNW, else it is a LPLVCOLUMNA
|
|
*
|
|
* RETURN:
|
|
* SUCCESS : TRUE
|
|
* FAILURE : FALSE
|
|
*/
|
|
static BOOL LISTVIEW_SetColumnT(const LISTVIEW_INFO *infoPtr, INT nColumn,
|
|
const LVCOLUMNW *lpColumn, BOOL isW)
|
|
{
|
|
HDITEMW hdi, hdiget;
|
|
BOOL bResult;
|
|
|
|
TRACE("(nColumn=%d, lpColumn=%s, isW=%d)\n", nColumn, debuglvcolumn_t(lpColumn, isW), isW);
|
|
|
|
if (!lpColumn || nColumn < 0 || nColumn >= DPA_GetPtrCount(infoPtr->hdpaColumns)) return FALSE;
|
|
|
|
ZeroMemory(&hdi, sizeof(HDITEMW));
|
|
if (lpColumn->mask & LVCF_FMT)
|
|
{
|
|
hdi.mask |= HDI_FORMAT;
|
|
hdiget.mask = HDI_FORMAT;
|
|
if (SendMessageW(infoPtr->hwndHeader, HDM_GETITEMW, nColumn, (LPARAM)&hdiget))
|
|
hdi.fmt = hdiget.fmt & HDF_STRING;
|
|
}
|
|
column_fill_hditem(infoPtr, &hdi, nColumn, lpColumn, isW);
|
|
|
|
/* set header item attributes */
|
|
bResult = SendMessageW(infoPtr->hwndHeader, isW ? HDM_SETITEMW : HDM_SETITEMA, nColumn, (LPARAM)&hdi);
|
|
if (!bResult) return FALSE;
|
|
|
|
if (lpColumn->mask & LVCF_FMT)
|
|
{
|
|
COLUMN_INFO *lpColumnInfo = LISTVIEW_GetColumnInfo(infoPtr, nColumn);
|
|
INT oldFmt = lpColumnInfo->fmt;
|
|
|
|
lpColumnInfo->fmt = lpColumn->fmt;
|
|
if ((oldFmt ^ lpColumn->fmt) & (LVCFMT_JUSTIFYMASK | LVCFMT_IMAGE))
|
|
{
|
|
if (infoPtr->uView == LV_VIEW_DETAILS) LISTVIEW_InvalidateColumn(infoPtr, nColumn);
|
|
}
|
|
}
|
|
|
|
if (lpColumn->mask & LVCF_MINWIDTH)
|
|
LISTVIEW_GetColumnInfo(infoPtr, nColumn)->cxMin = lpColumn->cxMin;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
/***
|
|
* DESCRIPTION:
|
|
* Sets the column order array
|
|
*
|
|
* PARAMETERS:
|
|
* [I] infoPtr : valid pointer to the listview structure
|
|
* [I] iCount : number of elements in column order array
|
|
* [I] lpiArray : pointer to column order array
|
|
*
|
|
* RETURN:
|
|
* SUCCESS : TRUE
|
|
* FAILURE : FALSE
|
|
*/
|
|
static BOOL LISTVIEW_SetColumnOrderArray(LISTVIEW_INFO *infoPtr, INT iCount, const INT *lpiArray)
|
|
{
|
|
TRACE("iCount %d lpiArray %p\n", iCount, lpiArray);
|
|
|
|
if (!lpiArray || !IsWindow(infoPtr->hwndHeader)) return FALSE;
|
|
|
|
infoPtr->colRectsDirty = TRUE;
|
|
|
|
return SendMessageW(infoPtr->hwndHeader, HDM_SETORDERARRAY, iCount, (LPARAM)lpiArray);
|
|
}
|
|
|
|
/***
|
|
* DESCRIPTION:
|
|
* Sets the width of a column
|
|
*
|
|
* PARAMETERS:
|
|
* [I] infoPtr : valid pointer to the listview structure
|
|
* [I] nColumn : column index
|
|
* [I] cx : column width
|
|
*
|
|
* RETURN:
|
|
* SUCCESS : TRUE
|
|
* FAILURE : FALSE
|
|
*/
|
|
static BOOL LISTVIEW_SetColumnWidth(LISTVIEW_INFO *infoPtr, INT nColumn, INT cx)
|
|
{
|
|
WCHAR szDispText[DISP_TEXT_SIZE] = { 0 };
|
|
INT max_cx = 0;
|
|
HDITEMW hdi;
|
|
|
|
TRACE("(nColumn=%d, cx=%d\n", nColumn, cx);
|
|
|
|
/* set column width only if in report or list mode */
|
|
if (infoPtr->uView != LV_VIEW_DETAILS && infoPtr->uView != LV_VIEW_LIST) return FALSE;
|
|
|
|
/* take care of invalid cx values */
|
|
if(infoPtr->uView == LV_VIEW_DETAILS && cx < -2) cx = LVSCW_AUTOSIZE;
|
|
else if (infoPtr->uView == LV_VIEW_LIST && cx < 1) return FALSE;
|
|
|
|
/* resize all columns if in LV_VIEW_LIST mode */
|
|
if(infoPtr->uView == LV_VIEW_LIST)
|
|
{
|
|
infoPtr->nItemWidth = cx;
|
|
LISTVIEW_InvalidateList(infoPtr);
|
|
return TRUE;
|
|
}
|
|
|
|
if (nColumn < 0 || nColumn >= DPA_GetPtrCount(infoPtr->hdpaColumns)) return FALSE;
|
|
|
|
if (cx == LVSCW_AUTOSIZE || (cx == LVSCW_AUTOSIZE_USEHEADER && nColumn < DPA_GetPtrCount(infoPtr->hdpaColumns) -1))
|
|
{
|
|
INT nLabelWidth;
|
|
LVITEMW lvItem;
|
|
|
|
lvItem.mask = LVIF_TEXT;
|
|
lvItem.iItem = 0;
|
|
lvItem.iSubItem = nColumn;
|
|
lvItem.cchTextMax = DISP_TEXT_SIZE;
|
|
for (; lvItem.iItem < infoPtr->nItemCount; lvItem.iItem++)
|
|
{
|
|
lvItem.pszText = szDispText;
|
|
if (!LISTVIEW_GetItemW(infoPtr, &lvItem)) continue;
|
|
nLabelWidth = LISTVIEW_GetStringWidthT(infoPtr, lvItem.pszText, TRUE);
|
|
if (max_cx < nLabelWidth) max_cx = nLabelWidth;
|
|
}
|
|
if (infoPtr->himlSmall && (nColumn == 0 || (LISTVIEW_GetColumnInfo(infoPtr, nColumn)->fmt & LVCFMT_IMAGE)))
|
|
max_cx += infoPtr->iconSize.cx;
|
|
max_cx += TRAILING_LABEL_PADDING;
|
|
}
|
|
|
|
/* autosize based on listview items width */
|
|
if(cx == LVSCW_AUTOSIZE)
|
|
cx = max_cx;
|
|
else if(cx == LVSCW_AUTOSIZE_USEHEADER)
|
|
{
|
|
/* if iCol is the last column make it fill the remainder of the controls width */
|
|
if(nColumn == DPA_GetPtrCount(infoPtr->hdpaColumns) - 1)
|
|
{
|
|
RECT rcHeader;
|
|
POINT Origin;
|
|
|
|
LISTVIEW_GetOrigin(infoPtr, &Origin);
|
|
LISTVIEW_GetHeaderRect(infoPtr, nColumn, &rcHeader);
|
|
|
|
cx = infoPtr->rcList.right - Origin.x - rcHeader.left;
|
|
}
|
|
else
|
|
{
|
|
/* Despite what the MS docs say, if this is not the last
|
|
column, then MS resizes the column to the width of the
|
|
largest text string in the column, including headers
|
|
and items. This is different from LVSCW_AUTOSIZE in that
|
|
LVSCW_AUTOSIZE ignores the header string length. */
|
|
cx = 0;
|
|
|
|
/* retrieve header text */
|
|
hdi.mask = HDI_TEXT;
|
|
hdi.cchTextMax = DISP_TEXT_SIZE;
|
|
hdi.pszText = szDispText;
|
|
if (SendMessageW(infoPtr->hwndHeader, HDM_GETITEMW, nColumn, (LPARAM)&hdi))
|
|
{
|
|
HDC hdc = GetDC(infoPtr->hwndSelf);
|
|
HFONT old_font = SelectObject(hdc, (HFONT)SendMessageW(infoPtr->hwndHeader, WM_GETFONT, 0, 0));
|
|
SIZE size;
|
|
|
|
if (GetTextExtentPoint32W(hdc, hdi.pszText, lstrlenW(hdi.pszText), &size))
|
|
cx = size.cx + TRAILING_HEADER_PADDING;
|
|
/* FIXME: Take into account the header image, if one is present */
|
|
SelectObject(hdc, old_font);
|
|
ReleaseDC(infoPtr->hwndSelf, hdc);
|
|
}
|
|
cx = max (cx, max_cx);
|
|
}
|
|
}
|
|
|
|
if (cx < 0) return FALSE;
|
|
|
|
/* call header to update the column change */
|
|
hdi.mask = HDI_WIDTH;
|
|
hdi.cxy = max(cx, LISTVIEW_GetColumnInfo(infoPtr, nColumn)->cxMin);
|
|
TRACE("hdi.cxy=%d\n", hdi.cxy);
|
|
return SendMessageW(infoPtr->hwndHeader, HDM_SETITEMW, nColumn, (LPARAM)&hdi);
|
|
}
|
|
|
|
/***
|
|
* Creates the checkbox imagelist. Helper for LISTVIEW_SetExtendedListViewStyle
|
|
*
|
|
*/
|
|
static HIMAGELIST LISTVIEW_CreateCheckBoxIL(const LISTVIEW_INFO *infoPtr)
|
|
{
|
|
HDC hdc_wnd, hdc;
|
|
HBITMAP hbm_im, hbm_mask, hbm_orig;
|
|
RECT rc;
|
|
HBRUSH hbr_white = GetStockObject(WHITE_BRUSH);
|
|
HBRUSH hbr_black = GetStockObject(BLACK_BRUSH);
|
|
HIMAGELIST himl;
|
|
|
|
himl = ImageList_Create(GetSystemMetrics(SM_CXSMICON), GetSystemMetrics(SM_CYSMICON),
|
|
ILC_COLOR | ILC_MASK, 2, 2);
|
|
hdc_wnd = GetDC(infoPtr->hwndSelf);
|
|
hdc = CreateCompatibleDC(hdc_wnd);
|
|
hbm_im = CreateCompatibleBitmap(hdc_wnd, GetSystemMetrics(SM_CXSMICON), GetSystemMetrics(SM_CYSMICON));
|
|
hbm_mask = CreateBitmap(GetSystemMetrics(SM_CXSMICON), GetSystemMetrics(SM_CYSMICON), 1, 1, NULL);
|
|
ReleaseDC(infoPtr->hwndSelf, hdc_wnd);
|
|
|
|
rc.left = rc.top = 0;
|
|
rc.right = GetSystemMetrics(SM_CXSMICON);
|
|
rc.bottom = GetSystemMetrics(SM_CYSMICON);
|
|
|
|
hbm_orig = SelectObject(hdc, hbm_mask);
|
|
FillRect(hdc, &rc, hbr_white);
|
|
InflateRect(&rc, -2, -2);
|
|
FillRect(hdc, &rc, hbr_black);
|
|
|
|
SelectObject(hdc, hbm_im);
|
|
DrawFrameControl(hdc, &rc, DFC_BUTTON, DFCS_BUTTONCHECK | DFCS_MONO);
|
|
SelectObject(hdc, hbm_orig);
|
|
ImageList_Add(himl, hbm_im, hbm_mask);
|
|
|
|
SelectObject(hdc, hbm_im);
|
|
DrawFrameControl(hdc, &rc, DFC_BUTTON, DFCS_BUTTONCHECK | DFCS_MONO | DFCS_CHECKED);
|
|
SelectObject(hdc, hbm_orig);
|
|
ImageList_Add(himl, hbm_im, hbm_mask);
|
|
|
|
DeleteObject(hbm_mask);
|
|
DeleteObject(hbm_im);
|
|
DeleteDC(hdc);
|
|
|
|
return himl;
|
|
}
|
|
|
|
/***
|
|
* DESCRIPTION:
|
|
* Sets the extended listview style.
|
|
*
|
|
* PARAMETERS:
|
|
* [I] infoPtr : valid pointer to the listview structure
|
|
* [I] dwMask : mask
|
|
* [I] dwStyle : style
|
|
*
|
|
* RETURN:
|
|
* SUCCESS : previous style
|
|
* FAILURE : 0
|
|
*/
|
|
static DWORD LISTVIEW_SetExtendedListViewStyle(LISTVIEW_INFO *infoPtr, DWORD mask, DWORD ex_style)
|
|
{
|
|
DWORD old_ex_style = infoPtr->dwLvExStyle;
|
|
|
|
TRACE("mask=0x%08x, ex_style=0x%08x\n", mask, ex_style);
|
|
|
|
/* set new style */
|
|
if (mask)
|
|
infoPtr->dwLvExStyle = (old_ex_style & ~mask) | (ex_style & mask);
|
|
else
|
|
infoPtr->dwLvExStyle = ex_style;
|
|
|
|
if((infoPtr->dwLvExStyle ^ old_ex_style) & LVS_EX_CHECKBOXES)
|
|
{
|
|
HIMAGELIST himl = 0;
|
|
if(infoPtr->dwLvExStyle & LVS_EX_CHECKBOXES)
|
|
{
|
|
LVITEMW item;
|
|
item.mask = LVIF_STATE;
|
|
item.stateMask = LVIS_STATEIMAGEMASK;
|
|
item.state = INDEXTOSTATEIMAGEMASK(1);
|
|
LISTVIEW_SetItemState(infoPtr, -1, &item);
|
|
|
|
himl = LISTVIEW_CreateCheckBoxIL(infoPtr);
|
|
if(!(infoPtr->dwStyle & LVS_SHAREIMAGELISTS))
|
|
ImageList_Destroy(infoPtr->himlState);
|
|
}
|
|
himl = LISTVIEW_SetImageList(infoPtr, LVSIL_STATE, himl);
|
|
/* checkbox list replaces previous custom list or... */
|
|
if(((infoPtr->dwLvExStyle & LVS_EX_CHECKBOXES) &&
|
|
!(infoPtr->dwStyle & LVS_SHAREIMAGELISTS)) ||
|
|
/* ...previous was checkbox list */
|
|
(old_ex_style & LVS_EX_CHECKBOXES))
|
|
ImageList_Destroy(himl);
|
|
}
|
|
|
|
if((infoPtr->dwLvExStyle ^ old_ex_style) & LVS_EX_HEADERDRAGDROP)
|
|
{
|
|
DWORD style;
|
|
|
|
/* if not already created */
|
|
LISTVIEW_CreateHeader(infoPtr);
|
|
|
|
style = GetWindowLongW(infoPtr->hwndHeader, GWL_STYLE);
|
|
if (infoPtr->dwLvExStyle & LVS_EX_HEADERDRAGDROP)
|
|
style |= HDS_DRAGDROP;
|
|
else
|
|
style &= ~HDS_DRAGDROP;
|
|
SetWindowLongW(infoPtr->hwndHeader, GWL_STYLE, style);
|
|
}
|
|
|
|
/* GRIDLINES adds decoration at top so changes sizes */
|
|
if((infoPtr->dwLvExStyle ^ old_ex_style) & LVS_EX_GRIDLINES)
|
|
{
|
|
LISTVIEW_CreateHeader(infoPtr);
|
|
LISTVIEW_UpdateSize(infoPtr);
|
|
}
|
|
|
|
if((infoPtr->dwLvExStyle ^ old_ex_style) & LVS_EX_FULLROWSELECT)
|
|
{
|
|
LISTVIEW_CreateHeader(infoPtr);
|
|
}
|
|
|
|
if((infoPtr->dwLvExStyle ^ old_ex_style) & LVS_EX_TRANSPARENTBKGND)
|
|
{
|
|
if (infoPtr->dwLvExStyle & LVS_EX_TRANSPARENTBKGND)
|
|
LISTVIEW_SetBkColor(infoPtr, CLR_NONE);
|
|
}
|
|
|
|
if((infoPtr->dwLvExStyle ^ old_ex_style) & LVS_EX_HEADERINALLVIEWS)
|
|
{
|
|
if (infoPtr->dwLvExStyle & LVS_EX_HEADERINALLVIEWS)
|
|
LISTVIEW_CreateHeader(infoPtr);
|
|
else
|
|
ShowWindow(infoPtr->hwndHeader, SW_HIDE);
|
|
LISTVIEW_UpdateSize(infoPtr);
|
|
LISTVIEW_UpdateScroll(infoPtr);
|
|
}
|
|
|
|
LISTVIEW_InvalidateList(infoPtr);
|
|
return old_ex_style;
|
|
}
|
|
|
|
/***
|
|
* DESCRIPTION:
|
|
* Sets the new hot cursor used during hot tracking and hover selection.
|
|
*
|
|
* PARAMETER(S):
|
|
* [I] infoPtr : valid pointer to the listview structure
|
|
* [I] hCursor : the new hot cursor handle
|
|
*
|
|
* RETURN:
|
|
* Returns the previous hot cursor
|
|
*/
|
|
static HCURSOR LISTVIEW_SetHotCursor(LISTVIEW_INFO *infoPtr, HCURSOR hCursor)
|
|
{
|
|
HCURSOR oldCursor = infoPtr->hHotCursor;
|
|
|
|
infoPtr->hHotCursor = hCursor;
|
|
|
|
return oldCursor;
|
|
}
|
|
|
|
|
|
/***
|
|
* DESCRIPTION:
|
|
* Sets the hot item index.
|
|
*
|
|
* PARAMETERS:
|
|
* [I] infoPtr : valid pointer to the listview structure
|
|
* [I] iIndex : index
|
|
*
|
|
* RETURN:
|
|
* SUCCESS : previous hot item index
|
|
* FAILURE : -1 (no hot item)
|
|
*/
|
|
static INT LISTVIEW_SetHotItem(LISTVIEW_INFO *infoPtr, INT iIndex)
|
|
{
|
|
INT iOldIndex = infoPtr->nHotItem;
|
|
|
|
infoPtr->nHotItem = iIndex;
|
|
|
|
return iOldIndex;
|
|
}
|
|
|
|
|
|
/***
|
|
* DESCRIPTION:
|
|
* Sets the amount of time the cursor must hover over an item before it is selected.
|
|
*
|
|
* PARAMETER(S):
|
|
* [I] infoPtr : valid pointer to the listview structure
|
|
* [I] dwHoverTime : hover time, if -1 the hover time is set to the default
|
|
*
|
|
* RETURN:
|
|
* Returns the previous hover time
|
|
*/
|
|
static DWORD LISTVIEW_SetHoverTime(LISTVIEW_INFO *infoPtr, DWORD dwHoverTime)
|
|
{
|
|
DWORD oldHoverTime = infoPtr->dwHoverTime;
|
|
|
|
infoPtr->dwHoverTime = dwHoverTime;
|
|
|
|
return oldHoverTime;
|
|
}
|
|
|
|
/***
|
|
* DESCRIPTION:
|
|
* Sets spacing for icons of LVS_ICON style.
|
|
*
|
|
* PARAMETER(S):
|
|
* [I] infoPtr : valid pointer to the listview structure
|
|
* [I] cx : horizontal spacing (-1 = system spacing, 0 = autosize)
|
|
* [I] cy : vertical spacing (-1 = system spacing, 0 = autosize)
|
|
*
|
|
* RETURN:
|
|
* MAKELONG(oldcx, oldcy)
|
|
*/
|
|
static DWORD LISTVIEW_SetIconSpacing(LISTVIEW_INFO *infoPtr, INT cx, INT cy)
|
|
{
|
|
INT iconWidth = 0, iconHeight = 0;
|
|
DWORD oldspacing = MAKELONG(infoPtr->iconSpacing.cx, infoPtr->iconSpacing.cy);
|
|
|
|
TRACE("requested=(%d,%d)\n", cx, cy);
|
|
|
|
/* set to defaults, if instructed to */
|
|
if (cx == -1 && cy == -1)
|
|
{
|
|
infoPtr->autoSpacing = TRUE;
|
|
if (infoPtr->himlNormal)
|
|
ImageList_GetIconSize(infoPtr->himlNormal, &iconWidth, &iconHeight);
|
|
cx = GetSystemMetrics(SM_CXICONSPACING) - GetSystemMetrics(SM_CXICON) + iconWidth;
|
|
cy = GetSystemMetrics(SM_CYICONSPACING) - GetSystemMetrics(SM_CYICON) + iconHeight;
|
|
}
|
|
else
|
|
infoPtr->autoSpacing = FALSE;
|
|
|
|
/* if 0 then keep width */
|
|
if (cx != 0)
|
|
infoPtr->iconSpacing.cx = cx;
|
|
|
|
/* if 0 then keep height */
|
|
if (cy != 0)
|
|
infoPtr->iconSpacing.cy = cy;
|
|
|
|
TRACE("old=(%d,%d), new=(%d,%d), iconSize=(%d,%d), ntmH=%d\n",
|
|
LOWORD(oldspacing), HIWORD(oldspacing), infoPtr->iconSpacing.cx, infoPtr->iconSpacing.cy,
|
|
infoPtr->iconSize.cx, infoPtr->iconSize.cy,
|
|
infoPtr->ntmHeight);
|
|
|
|
/* these depend on the iconSpacing */
|
|
LISTVIEW_UpdateItemSize(infoPtr);
|
|
|
|
return oldspacing;
|
|
}
|
|
|
|
static inline void set_icon_size(SIZE *size, HIMAGELIST himl, BOOL small)
|
|
{
|
|
INT cx, cy;
|
|
|
|
if (himl && ImageList_GetIconSize(himl, &cx, &cy))
|
|
{
|
|
size->cx = cx;
|
|
size->cy = cy;
|
|
}
|
|
else
|
|
{
|
|
size->cx = GetSystemMetrics(small ? SM_CXSMICON : SM_CXICON);
|
|
size->cy = GetSystemMetrics(small ? SM_CYSMICON : SM_CYICON);
|
|
}
|
|
}
|
|
|
|
/***
|
|
* DESCRIPTION:
|
|
* Sets image lists.
|
|
*
|
|
* PARAMETER(S):
|
|
* [I] infoPtr : valid pointer to the listview structure
|
|
* [I] nType : image list type
|
|
* [I] himl : image list handle
|
|
*
|
|
* RETURN:
|
|
* SUCCESS : old image list
|
|
* FAILURE : NULL
|
|
*/
|
|
static HIMAGELIST LISTVIEW_SetImageList(LISTVIEW_INFO *infoPtr, INT nType, HIMAGELIST himl)
|
|
{
|
|
INT oldHeight = infoPtr->nItemHeight;
|
|
HIMAGELIST himlOld = 0;
|
|
|
|
TRACE("(nType=%d, himl=%p\n", nType, himl);
|
|
|
|
switch (nType)
|
|
{
|
|
case LVSIL_NORMAL:
|
|
himlOld = infoPtr->himlNormal;
|
|
infoPtr->himlNormal = himl;
|
|
if (infoPtr->uView == LV_VIEW_ICON) set_icon_size(&infoPtr->iconSize, himl, FALSE);
|
|
if (infoPtr->autoSpacing)
|
|
LISTVIEW_SetIconSpacing(infoPtr, -1, -1);
|
|
break;
|
|
|
|
case LVSIL_SMALL:
|
|
himlOld = infoPtr->himlSmall;
|
|
infoPtr->himlSmall = himl;
|
|
if (infoPtr->uView != LV_VIEW_ICON) set_icon_size(&infoPtr->iconSize, himl, TRUE);
|
|
if (infoPtr->hwndHeader)
|
|
SendMessageW(infoPtr->hwndHeader, HDM_SETIMAGELIST, 0, (LPARAM)himl);
|
|
break;
|
|
|
|
case LVSIL_STATE:
|
|
himlOld = infoPtr->himlState;
|
|
infoPtr->himlState = himl;
|
|
set_icon_size(&infoPtr->iconStateSize, himl, TRUE);
|
|
ImageList_SetBkColor(infoPtr->himlState, CLR_NONE);
|
|
break;
|
|
|
|
default:
|
|
ERR("Unknown icon type=%d\n", nType);
|
|
return NULL;
|
|
}
|
|
|
|
infoPtr->nItemHeight = LISTVIEW_CalculateItemHeight(infoPtr);
|
|
if (infoPtr->nItemHeight != oldHeight)
|
|
LISTVIEW_UpdateScroll(infoPtr);
|
|
|
|
return himlOld;
|
|
}
|
|
|
|
/***
|
|
* DESCRIPTION:
|
|
* Preallocates memory (does *not* set the actual count of items !)
|
|
*
|
|
* PARAMETER(S):
|
|
* [I] infoPtr : valid pointer to the listview structure
|
|
* [I] nItems : item count (projected number of items to allocate)
|
|
* [I] dwFlags : update flags
|
|
*
|
|
* RETURN:
|
|
* SUCCESS : TRUE
|
|
* FAILURE : FALSE
|
|
*/
|
|
static BOOL LISTVIEW_SetItemCount(LISTVIEW_INFO *infoPtr, INT nItems, DWORD dwFlags)
|
|
{
|
|
TRACE("(nItems=%d, dwFlags=%x)\n", nItems, dwFlags);
|
|
|
|
if (infoPtr->dwStyle & LVS_OWNERDATA)
|
|
{
|
|
INT nOldCount = infoPtr->nItemCount;
|
|
|
|
if (nItems < nOldCount)
|
|
{
|
|
RANGE range = { nItems, nOldCount };
|
|
ranges_del(infoPtr->selectionRanges, range);
|
|
if (infoPtr->nFocusedItem >= nItems)
|
|
{
|
|
LISTVIEW_SetItemFocus(infoPtr, -1);
|
|
SetRectEmpty(&infoPtr->rcFocus);
|
|
}
|
|
}
|
|
|
|
infoPtr->nItemCount = nItems;
|
|
LISTVIEW_UpdateScroll(infoPtr);
|
|
|
|
/* the flags are valid only in ownerdata report and list modes */
|
|
if (infoPtr->uView == LV_VIEW_ICON || infoPtr->uView == LV_VIEW_SMALLICON) dwFlags = 0;
|
|
|
|
if (!(dwFlags & LVSICF_NOSCROLL) && infoPtr->nFocusedItem != -1)
|
|
LISTVIEW_EnsureVisible(infoPtr, infoPtr->nFocusedItem, FALSE);
|
|
|
|
if (!(dwFlags & LVSICF_NOINVALIDATEALL))
|
|
LISTVIEW_InvalidateList(infoPtr);
|
|
else
|
|
{
|
|
INT nFrom, nTo;
|
|
POINT Origin;
|
|
RECT rcErase;
|
|
|
|
LISTVIEW_GetOrigin(infoPtr, &Origin);
|
|
nFrom = min(nOldCount, nItems);
|
|
nTo = max(nOldCount, nItems);
|
|
|
|
if (infoPtr->uView == LV_VIEW_DETAILS)
|
|
{
|
|
rcErase.left = 0;
|
|
rcErase.top = nFrom * infoPtr->nItemHeight;
|
|
rcErase.right = infoPtr->nItemWidth;
|
|
rcErase.bottom = nTo * infoPtr->nItemHeight;
|
|
OffsetRect(&rcErase, Origin.x, Origin.y);
|
|
if (IntersectRect(&rcErase, &rcErase, &infoPtr->rcList))
|
|
LISTVIEW_InvalidateRect(infoPtr, &rcErase);
|
|
}
|
|
else /* LV_VIEW_LIST */
|
|
{
|
|
INT nPerCol = LISTVIEW_GetCountPerColumn(infoPtr);
|
|
|
|
rcErase.left = (nFrom / nPerCol) * infoPtr->nItemWidth;
|
|
rcErase.top = (nFrom % nPerCol) * infoPtr->nItemHeight;
|
|
rcErase.right = rcErase.left + infoPtr->nItemWidth;
|
|
rcErase.bottom = nPerCol * infoPtr->nItemHeight;
|
|
OffsetRect(&rcErase, Origin.x, Origin.y);
|
|
if (IntersectRect(&rcErase, &rcErase, &infoPtr->rcList))
|
|
LISTVIEW_InvalidateRect(infoPtr, &rcErase);
|
|
|
|
rcErase.left = (nFrom / nPerCol + 1) * infoPtr->nItemWidth;
|
|
rcErase.top = 0;
|
|
rcErase.right = (nTo / nPerCol + 1) * infoPtr->nItemWidth;
|
|
rcErase.bottom = nPerCol * infoPtr->nItemHeight;
|
|
OffsetRect(&rcErase, Origin.x, Origin.y);
|
|
if (IntersectRect(&rcErase, &rcErase, &infoPtr->rcList))
|
|
LISTVIEW_InvalidateRect(infoPtr, &rcErase);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
/* According to MSDN for non-LVS_OWNERDATA this is just
|
|
* a performance issue. The control allocates its internal
|
|
* data structures for the number of items specified. It
|
|
* cuts down on the number of memory allocations. Therefore
|
|
* we will just issue a WARN here
|
|
*/
|
|
WARN("for non-ownerdata performance option not implemented.\n");
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
/***
|
|
* DESCRIPTION:
|
|
* Sets the position of an item.
|
|
*
|
|
* PARAMETER(S):
|
|
* [I] infoPtr : valid pointer to the listview structure
|
|
* [I] nItem : item index
|
|
* [I] pt : coordinate
|
|
*
|
|
* RETURN:
|
|
* SUCCESS : TRUE
|
|
* FAILURE : FALSE
|
|
*/
|
|
static BOOL LISTVIEW_SetItemPosition(LISTVIEW_INFO *infoPtr, INT nItem, const POINT *pt)
|
|
{
|
|
POINT Origin, Pt;
|
|
|
|
TRACE("(nItem=%d, pt=%s\n", nItem, wine_dbgstr_point(pt));
|
|
|
|
if (!pt || nItem < 0 || nItem >= infoPtr->nItemCount ||
|
|
!(infoPtr->uView == LV_VIEW_ICON || infoPtr->uView == LV_VIEW_SMALLICON)) return FALSE;
|
|
|
|
Pt = *pt;
|
|
LISTVIEW_GetOrigin(infoPtr, &Origin);
|
|
|
|
/* This point value seems to be an undocumented feature.
|
|
* The best guess is that it means either at the origin,
|
|
* or at true beginning of the list. I will assume the origin. */
|
|
if ((Pt.x == -1) && (Pt.y == -1))
|
|
Pt = Origin;
|
|
|
|
if (infoPtr->uView == LV_VIEW_ICON)
|
|
{
|
|
Pt.x -= (infoPtr->nItemWidth - infoPtr->iconSize.cx) / 2;
|
|
Pt.y -= ICON_TOP_PADDING;
|
|
}
|
|
Pt.x -= Origin.x;
|
|
Pt.y -= Origin.y;
|
|
|
|
infoPtr->bAutoarrange = FALSE;
|
|
|
|
return LISTVIEW_MoveIconTo(infoPtr, nItem, &Pt, FALSE);
|
|
}
|
|
|
|
/***
|
|
* DESCRIPTION:
|
|
* Sets the state of one or many items.
|
|
*
|
|
* PARAMETER(S):
|
|
* [I] infoPtr : valid pointer to the listview structure
|
|
* [I] nItem : item index
|
|
* [I] item : item or subitem info
|
|
*
|
|
* RETURN:
|
|
* SUCCESS : TRUE
|
|
* FAILURE : FALSE
|
|
*/
|
|
static BOOL LISTVIEW_SetItemState(LISTVIEW_INFO *infoPtr, INT nItem, const LVITEMW *item)
|
|
{
|
|
BOOL ret = TRUE;
|
|
LVITEMW lvItem;
|
|
|
|
if (!item) return FALSE;
|
|
|
|
lvItem.iItem = nItem;
|
|
lvItem.iSubItem = 0;
|
|
lvItem.mask = LVIF_STATE;
|
|
lvItem.state = item->state;
|
|
lvItem.stateMask = item->stateMask;
|
|
TRACE("item=%s\n", debuglvitem_t(&lvItem, TRUE));
|
|
|
|
if (nItem == -1)
|
|
{
|
|
UINT oldstate = 0;
|
|
BOOL notify;
|
|
|
|
/* special case optimization for recurring attempt to deselect all */
|
|
if (lvItem.state == 0 && lvItem.stateMask == LVIS_SELECTED && !LISTVIEW_GetSelectedCount(infoPtr))
|
|
return TRUE;
|
|
|
|
/* select all isn't allowed in LVS_SINGLESEL */
|
|
if ((lvItem.state & lvItem.stateMask & LVIS_SELECTED) && (infoPtr->dwStyle & LVS_SINGLESEL))
|
|
return FALSE;
|
|
|
|
/* focus all isn't allowed */
|
|
if (lvItem.state & lvItem.stateMask & LVIS_FOCUSED) return FALSE;
|
|
|
|
notify = infoPtr->bDoChangeNotify;
|
|
if (infoPtr->dwStyle & LVS_OWNERDATA)
|
|
{
|
|
infoPtr->bDoChangeNotify = FALSE;
|
|
if (!(lvItem.state & LVIS_SELECTED) && LISTVIEW_GetSelectedCount(infoPtr))
|
|
oldstate |= LVIS_SELECTED;
|
|
if (infoPtr->nFocusedItem != -1) oldstate |= LVIS_FOCUSED;
|
|
}
|
|
|
|
/* apply to all items */
|
|
for (lvItem.iItem = 0; lvItem.iItem < infoPtr->nItemCount; lvItem.iItem++)
|
|
if (!LISTVIEW_SetItemT(infoPtr, &lvItem, TRUE)) ret = FALSE;
|
|
|
|
if (infoPtr->dwStyle & LVS_OWNERDATA)
|
|
{
|
|
NMLISTVIEW nmlv;
|
|
|
|
infoPtr->bDoChangeNotify = notify;
|
|
|
|
nmlv.iItem = -1;
|
|
nmlv.iSubItem = 0;
|
|
nmlv.uNewState = lvItem.state & lvItem.stateMask;
|
|
nmlv.uOldState = oldstate & lvItem.stateMask;
|
|
nmlv.uChanged = LVIF_STATE;
|
|
nmlv.ptAction.x = nmlv.ptAction.y = 0;
|
|
nmlv.lParam = 0;
|
|
|
|
notify_listview(infoPtr, LVN_ITEMCHANGED, &nmlv);
|
|
}
|
|
}
|
|
else
|
|
ret = LISTVIEW_SetItemT(infoPtr, &lvItem, TRUE);
|
|
|
|
return ret;
|
|
}
|
|
|
|
/***
|
|
* DESCRIPTION:
|
|
* Sets the text of an item or subitem.
|
|
*
|
|
* PARAMETER(S):
|
|
* [I] hwnd : window handle
|
|
* [I] nItem : item index
|
|
* [I] lpLVItem : item or subitem info
|
|
* [I] isW : TRUE if input is Unicode
|
|
*
|
|
* RETURN:
|
|
* SUCCESS : TRUE
|
|
* FAILURE : FALSE
|
|
*/
|
|
static BOOL LISTVIEW_SetItemTextT(LISTVIEW_INFO *infoPtr, INT nItem, const LVITEMW *lpLVItem, BOOL isW)
|
|
{
|
|
LVITEMW lvItem;
|
|
|
|
if (!lpLVItem || nItem < 0 || nItem >= infoPtr->nItemCount) return FALSE;
|
|
if (infoPtr->dwStyle & LVS_OWNERDATA) return FALSE;
|
|
|
|
lvItem.iItem = nItem;
|
|
lvItem.iSubItem = lpLVItem->iSubItem;
|
|
lvItem.mask = LVIF_TEXT;
|
|
lvItem.pszText = lpLVItem->pszText;
|
|
lvItem.cchTextMax = lpLVItem->cchTextMax;
|
|
|
|
TRACE("(nItem=%d, lpLVItem=%s, isW=%d)\n", nItem, debuglvitem_t(&lvItem, isW), isW);
|
|
|
|
return LISTVIEW_SetItemT(infoPtr, &lvItem, isW);
|
|
}
|
|
|
|
/***
|
|
* DESCRIPTION:
|
|
* Set item index that marks the start of a multiple selection.
|
|
*
|
|
* PARAMETER(S):
|
|
* [I] infoPtr : valid pointer to the listview structure
|
|
* [I] nIndex : index
|
|
*
|
|
* RETURN:
|
|
* Index number or -1 if there is no selection mark.
|
|
*/
|
|
static INT LISTVIEW_SetSelectionMark(LISTVIEW_INFO *infoPtr, INT nIndex)
|
|
{
|
|
INT nOldIndex = infoPtr->nSelectionMark;
|
|
|
|
TRACE("(nIndex=%d)\n", nIndex);
|
|
|
|
infoPtr->nSelectionMark = nIndex;
|
|
|
|
return nOldIndex;
|
|
}
|
|
|
|
/***
|
|
* DESCRIPTION:
|
|
* Sets the text background color.
|
|
*
|
|
* PARAMETER(S):
|
|
* [I] infoPtr : valid pointer to the listview structure
|
|
* [I] color : text background color
|
|
*
|
|
* RETURN:
|
|
* SUCCESS : TRUE
|
|
* FAILURE : FALSE
|
|
*/
|
|
static BOOL LISTVIEW_SetTextBkColor(LISTVIEW_INFO *infoPtr, COLORREF color)
|
|
{
|
|
TRACE("(color=%x)\n", color);
|
|
|
|
infoPtr->clrTextBk = color;
|
|
return TRUE;
|
|
}
|
|
|
|
/***
|
|
* DESCRIPTION:
|
|
* Sets the text foreground color.
|
|
*
|
|
* PARAMETER(S):
|
|
* [I] infoPtr : valid pointer to the listview structure
|
|
* [I] color : text color
|
|
*
|
|
* RETURN:
|
|
* SUCCESS : TRUE
|
|
* FAILURE : FALSE
|
|
*/
|
|
static BOOL LISTVIEW_SetTextColor (LISTVIEW_INFO *infoPtr, COLORREF color)
|
|
{
|
|
TRACE("(color=%x)\n", color);
|
|
|
|
infoPtr->clrText = color;
|
|
return TRUE;
|
|
}
|
|
|
|
/***
|
|
* DESCRIPTION:
|
|
* Sets new ToolTip window to ListView control.
|
|
*
|
|
* PARAMETER(S):
|
|
* [I] infoPtr : valid pointer to the listview structure
|
|
* [I] hwndNewToolTip : handle to new ToolTip
|
|
*
|
|
* RETURN:
|
|
* old tool tip
|
|
*/
|
|
static HWND LISTVIEW_SetToolTips( LISTVIEW_INFO *infoPtr, HWND hwndNewToolTip)
|
|
{
|
|
HWND hwndOldToolTip = infoPtr->hwndToolTip;
|
|
infoPtr->hwndToolTip = hwndNewToolTip;
|
|
return hwndOldToolTip;
|
|
}
|
|
|
|
/*
|
|
* DESCRIPTION:
|
|
* sets the Unicode character format flag for the control
|
|
* PARAMETER(S):
|
|
* [I] infoPtr :valid pointer to the listview structure
|
|
* [I] fUnicode :true to switch to UNICODE false to switch to ANSI
|
|
*
|
|
* RETURN:
|
|
* Old Unicode Format
|
|
*/
|
|
static BOOL LISTVIEW_SetUnicodeFormat( LISTVIEW_INFO *infoPtr, BOOL unicode)
|
|
{
|
|
SHORT rc = infoPtr->notifyFormat;
|
|
infoPtr->notifyFormat = (unicode) ? NFR_UNICODE : NFR_ANSI;
|
|
return rc == NFR_UNICODE;
|
|
}
|
|
|
|
/*
|
|
* DESCRIPTION:
|
|
* sets the control view mode
|
|
* PARAMETER(S):
|
|
* [I] infoPtr :valid pointer to the listview structure
|
|
* [I] nView :new view mode value
|
|
*
|
|
* RETURN:
|
|
* SUCCESS: 1
|
|
* FAILURE: -1
|
|
*/
|
|
static INT LISTVIEW_SetView(LISTVIEW_INFO *infoPtr, DWORD nView)
|
|
{
|
|
HIMAGELIST himl;
|
|
|
|
if (infoPtr->uView == nView) return 1;
|
|
|
|
if ((INT)nView < 0 || nView > LV_VIEW_MAX) return -1;
|
|
if (nView == LV_VIEW_TILE)
|
|
{
|
|
FIXME("View LV_VIEW_TILE unimplemented\n");
|
|
return -1;
|
|
}
|
|
|
|
infoPtr->uView = nView;
|
|
|
|
SendMessageW(infoPtr->hwndEdit, WM_KILLFOCUS, 0, 0);
|
|
ShowWindow(infoPtr->hwndHeader, SW_HIDE);
|
|
|
|
ShowScrollBar(infoPtr->hwndSelf, SB_BOTH, FALSE);
|
|
SetRectEmpty(&infoPtr->rcFocus);
|
|
|
|
himl = (nView == LV_VIEW_ICON ? infoPtr->himlNormal : infoPtr->himlSmall);
|
|
set_icon_size(&infoPtr->iconSize, himl, nView != LV_VIEW_ICON);
|
|
|
|
switch (nView)
|
|
{
|
|
case LV_VIEW_ICON:
|
|
case LV_VIEW_SMALLICON:
|
|
LISTVIEW_Arrange(infoPtr, LVA_DEFAULT);
|
|
break;
|
|
case LV_VIEW_DETAILS:
|
|
{
|
|
HDLAYOUT hl;
|
|
WINDOWPOS wp;
|
|
|
|
LISTVIEW_CreateHeader( infoPtr );
|
|
|
|
hl.prc = &infoPtr->rcList;
|
|
hl.pwpos = ℘
|
|
SendMessageW(infoPtr->hwndHeader, HDM_LAYOUT, 0, (LPARAM)&hl);
|
|
SetWindowPos(infoPtr->hwndHeader, infoPtr->hwndSelf, wp.x, wp.y, wp.cx, wp.cy,
|
|
wp.flags | ((infoPtr->dwStyle & LVS_NOCOLUMNHEADER) ? SWP_HIDEWINDOW : SWP_SHOWWINDOW));
|
|
break;
|
|
}
|
|
case LV_VIEW_LIST:
|
|
break;
|
|
}
|
|
|
|
LISTVIEW_UpdateItemSize(infoPtr);
|
|
LISTVIEW_UpdateSize(infoPtr);
|
|
LISTVIEW_UpdateScroll(infoPtr);
|
|
LISTVIEW_InvalidateList(infoPtr);
|
|
|
|
TRACE("nView=%d\n", nView);
|
|
|
|
return 1;
|
|
}
|
|
|
|
/* LISTVIEW_SetWorkAreas */
|
|
|
|
/***
|
|
* DESCRIPTION:
|
|
* Callback internally used by LISTVIEW_SortItems() in response of LVM_SORTITEMS
|
|
*
|
|
* PARAMETER(S):
|
|
* [I] first : pointer to first ITEM_INFO to compare
|
|
* [I] second : pointer to second ITEM_INFO to compare
|
|
* [I] lParam : HWND of control
|
|
*
|
|
* RETURN:
|
|
* if first comes before second : negative
|
|
* if first comes after second : positive
|
|
* if first and second are equivalent : zero
|
|
*/
|
|
static INT WINAPI LISTVIEW_CallBackCompare(LPVOID first, LPVOID second, LPARAM lParam)
|
|
{
|
|
LISTVIEW_INFO *infoPtr = (LISTVIEW_INFO *)lParam;
|
|
ITEM_INFO* lv_first = DPA_GetPtr( first, 0 );
|
|
ITEM_INFO* lv_second = DPA_GetPtr( second, 0 );
|
|
|
|
/* Forward the call to the client defined callback */
|
|
return (infoPtr->pfnCompare)( lv_first->lParam , lv_second->lParam, infoPtr->lParamSort );
|
|
}
|
|
|
|
/***
|
|
* DESCRIPTION:
|
|
* Callback internally used by LISTVIEW_SortItems() in response of LVM_SORTITEMSEX
|
|
*
|
|
* PARAMETER(S):
|
|
* [I] first : pointer to first ITEM_INFO to compare
|
|
* [I] second : pointer to second ITEM_INFO to compare
|
|
* [I] lParam : HWND of control
|
|
*
|
|
* RETURN:
|
|
* if first comes before second : negative
|
|
* if first comes after second : positive
|
|
* if first and second are equivalent : zero
|
|
*/
|
|
static INT WINAPI LISTVIEW_CallBackCompareEx(LPVOID first, LPVOID second, LPARAM lParam)
|
|
{
|
|
LISTVIEW_INFO *infoPtr = (LISTVIEW_INFO *)lParam;
|
|
INT first_idx = DPA_GetPtrIndex( infoPtr->hdpaItems, first );
|
|
INT second_idx = DPA_GetPtrIndex( infoPtr->hdpaItems, second );
|
|
|
|
/* Forward the call to the client defined callback */
|
|
return (infoPtr->pfnCompare)( first_idx, second_idx, infoPtr->lParamSort );
|
|
}
|
|
|
|
/***
|
|
* DESCRIPTION:
|
|
* Sorts the listview items.
|
|
*
|
|
* PARAMETER(S):
|
|
* [I] infoPtr : valid pointer to the listview structure
|
|
* [I] pfnCompare : application-defined value
|
|
* [I] lParamSort : pointer to comparison callback
|
|
* [I] IsEx : TRUE when LVM_SORTITEMSEX used
|
|
*
|
|
* RETURN:
|
|
* SUCCESS : TRUE
|
|
* FAILURE : FALSE
|
|
*/
|
|
static BOOL LISTVIEW_SortItems(LISTVIEW_INFO *infoPtr, PFNLVCOMPARE pfnCompare,
|
|
LPARAM lParamSort, BOOL IsEx)
|
|
{
|
|
HDPA hdpaSubItems;
|
|
ITEM_INFO *lpItem;
|
|
LPVOID selectionMarkItem = NULL;
|
|
LPVOID focusedItem = NULL;
|
|
int i;
|
|
|
|
TRACE("(pfnCompare=%p, lParamSort=%lx)\n", pfnCompare, lParamSort);
|
|
|
|
if (infoPtr->dwStyle & LVS_OWNERDATA) return FALSE;
|
|
|
|
if (!pfnCompare) return FALSE;
|
|
if (!infoPtr->hdpaItems) return FALSE;
|
|
|
|
/* if there are 0 or 1 items, there is no need to sort */
|
|
if (infoPtr->nItemCount < 2) return TRUE;
|
|
|
|
/* clear selection */
|
|
ranges_clear(infoPtr->selectionRanges);
|
|
|
|
/* save selection mark and focused item */
|
|
if (infoPtr->nSelectionMark >= 0)
|
|
selectionMarkItem = DPA_GetPtr(infoPtr->hdpaItems, infoPtr->nSelectionMark);
|
|
if (infoPtr->nFocusedItem >= 0)
|
|
focusedItem = DPA_GetPtr(infoPtr->hdpaItems, infoPtr->nFocusedItem);
|
|
|
|
infoPtr->pfnCompare = pfnCompare;
|
|
infoPtr->lParamSort = lParamSort;
|
|
if (IsEx)
|
|
DPA_Sort(infoPtr->hdpaItems, LISTVIEW_CallBackCompareEx, (LPARAM)infoPtr);
|
|
else
|
|
DPA_Sort(infoPtr->hdpaItems, LISTVIEW_CallBackCompare, (LPARAM)infoPtr);
|
|
|
|
/* restore selection ranges */
|
|
for (i=0; i < infoPtr->nItemCount; i++)
|
|
{
|
|
hdpaSubItems = DPA_GetPtr(infoPtr->hdpaItems, i);
|
|
lpItem = DPA_GetPtr(hdpaSubItems, 0);
|
|
|
|
if (lpItem->state & LVIS_SELECTED)
|
|
ranges_additem(infoPtr->selectionRanges, i);
|
|
}
|
|
/* restore selection mark and focused item */
|
|
infoPtr->nSelectionMark = DPA_GetPtrIndex(infoPtr->hdpaItems, selectionMarkItem);
|
|
infoPtr->nFocusedItem = DPA_GetPtrIndex(infoPtr->hdpaItems, focusedItem);
|
|
|
|
/* I believe nHotItem should be left alone, see LISTVIEW_ShiftIndices */
|
|
|
|
/* refresh the display */
|
|
LISTVIEW_InvalidateList(infoPtr);
|
|
return TRUE;
|
|
}
|
|
|
|
/***
|
|
* DESCRIPTION:
|
|
* Update theme handle after a theme change.
|
|
*
|
|
* PARAMETER(S):
|
|
* [I] infoPtr : valid pointer to the listview structure
|
|
*
|
|
* RETURN:
|
|
* SUCCESS : 0
|
|
* FAILURE : something else
|
|
*/
|
|
static LRESULT LISTVIEW_ThemeChanged(const LISTVIEW_INFO *infoPtr)
|
|
{
|
|
HTHEME theme = GetWindowTheme(infoPtr->hwndSelf);
|
|
CloseThemeData(theme);
|
|
OpenThemeData(infoPtr->hwndSelf, themeClass);
|
|
return 0;
|
|
}
|
|
|
|
/***
|
|
* DESCRIPTION:
|
|
* Updates an items or rearranges the listview control.
|
|
*
|
|
* PARAMETER(S):
|
|
* [I] infoPtr : valid pointer to the listview structure
|
|
* [I] nItem : item index
|
|
*
|
|
* RETURN:
|
|
* SUCCESS : TRUE
|
|
* FAILURE : FALSE
|
|
*/
|
|
static BOOL LISTVIEW_Update(LISTVIEW_INFO *infoPtr, INT nItem)
|
|
{
|
|
TRACE("(nItem=%d)\n", nItem);
|
|
|
|
if (nItem < 0 || nItem >= infoPtr->nItemCount) return FALSE;
|
|
|
|
/* rearrange with default alignment style */
|
|
if (is_autoarrange(infoPtr))
|
|
LISTVIEW_Arrange(infoPtr, LVA_DEFAULT);
|
|
else
|
|
LISTVIEW_InvalidateItem(infoPtr, nItem);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
/***
|
|
* DESCRIPTION:
|
|
* Draw the track line at the place defined in the infoPtr structure.
|
|
* The line is drawn with a XOR pen so drawing the line for the second time
|
|
* in the same place erases the line.
|
|
*
|
|
* PARAMETER(S):
|
|
* [I] infoPtr : valid pointer to the listview structure
|
|
*
|
|
* RETURN:
|
|
* SUCCESS : TRUE
|
|
* FAILURE : FALSE
|
|
*/
|
|
static BOOL LISTVIEW_DrawTrackLine(const LISTVIEW_INFO *infoPtr)
|
|
{
|
|
HDC hdc;
|
|
|
|
if (infoPtr->xTrackLine == -1)
|
|
return FALSE;
|
|
|
|
if (!(hdc = GetDC(infoPtr->hwndSelf)))
|
|
return FALSE;
|
|
PatBlt( hdc, infoPtr->xTrackLine, infoPtr->rcList.top,
|
|
1, infoPtr->rcList.bottom - infoPtr->rcList.top, DSTINVERT );
|
|
ReleaseDC(infoPtr->hwndSelf, hdc);
|
|
return TRUE;
|
|
}
|
|
|
|
/***
|
|
* DESCRIPTION:
|
|
* Called when an edit control should be displayed. This function is called after
|
|
* we are sure that there was a single click - not a double click (this is a TIMERPROC).
|
|
*
|
|
* PARAMETER(S):
|
|
* [I] hwnd : Handle to the listview
|
|
* [I] uMsg : WM_TIMER (ignored)
|
|
* [I] idEvent : The timer ID interpreted as a pointer to a DELAYED_EDIT_ITEM struct
|
|
* [I] dwTimer : The elapsed time (ignored)
|
|
*
|
|
* RETURN:
|
|
* None.
|
|
*/
|
|
static VOID CALLBACK LISTVIEW_DelayedEditItem(HWND hwnd, UINT uMsg, UINT_PTR idEvent, DWORD dwTime)
|
|
{
|
|
DELAYED_ITEM_EDIT *editItem = (DELAYED_ITEM_EDIT *)idEvent;
|
|
LISTVIEW_INFO *infoPtr = (LISTVIEW_INFO *)GetWindowLongPtrW(hwnd, 0);
|
|
|
|
KillTimer(hwnd, idEvent);
|
|
editItem->fEnabled = FALSE;
|
|
/* check if the item is still selected */
|
|
if (infoPtr->bFocus && LISTVIEW_GetItemState(infoPtr, editItem->iItem, LVIS_SELECTED))
|
|
LISTVIEW_EditLabelT(infoPtr, editItem->iItem, TRUE);
|
|
}
|
|
|
|
/***
|
|
* DESCRIPTION:
|
|
* Creates the listview control - the WM_NCCREATE phase.
|
|
*
|
|
* PARAMETER(S):
|
|
* [I] hwnd : window handle
|
|
* [I] lpcs : the create parameters
|
|
*
|
|
* RETURN:
|
|
* Success: TRUE
|
|
* Failure: FALSE
|
|
*/
|
|
static LRESULT LISTVIEW_NCCreate(HWND hwnd, const CREATESTRUCTW *lpcs)
|
|
{
|
|
LISTVIEW_INFO *infoPtr;
|
|
LOGFONTW logFont;
|
|
|
|
TRACE("(lpcs=%p)\n", lpcs);
|
|
|
|
/* initialize info pointer */
|
|
infoPtr = Alloc(sizeof(LISTVIEW_INFO));
|
|
if (!infoPtr) return FALSE;
|
|
|
|
SetWindowLongPtrW(hwnd, 0, (DWORD_PTR)infoPtr);
|
|
|
|
infoPtr->hwndSelf = hwnd;
|
|
infoPtr->dwStyle = lpcs->style; /* Note: may be changed in WM_CREATE */
|
|
map_style_view(infoPtr);
|
|
/* determine the type of structures to use */
|
|
infoPtr->hwndNotify = lpcs->hwndParent;
|
|
/* infoPtr->notifyFormat will be filled in WM_CREATE */
|
|
|
|
/* initialize color information */
|
|
infoPtr->clrBk = CLR_NONE;
|
|
infoPtr->clrText = CLR_DEFAULT;
|
|
infoPtr->clrTextBk = CLR_DEFAULT;
|
|
LISTVIEW_SetBkColor(infoPtr, comctl32_color.clrWindow);
|
|
|
|
/* set default values */
|
|
infoPtr->nFocusedItem = -1;
|
|
infoPtr->nSelectionMark = -1;
|
|
infoPtr->nHotItem = -1;
|
|
infoPtr->bRedraw = TRUE;
|
|
infoPtr->bNoItemMetrics = TRUE;
|
|
infoPtr->bDoChangeNotify = TRUE;
|
|
infoPtr->autoSpacing = TRUE;
|
|
infoPtr->iconSpacing.cx = GetSystemMetrics(SM_CXICONSPACING) - GetSystemMetrics(SM_CXICON);
|
|
infoPtr->iconSpacing.cy = GetSystemMetrics(SM_CYICONSPACING) - GetSystemMetrics(SM_CYICON);
|
|
infoPtr->nEditLabelItem = -1;
|
|
infoPtr->nLButtonDownItem = -1;
|
|
infoPtr->dwHoverTime = HOVER_DEFAULT; /* default system hover time */
|
|
infoPtr->cWheelRemainder = 0;
|
|
infoPtr->nMeasureItemHeight = 0;
|
|
infoPtr->xTrackLine = -1; /* no track line */
|
|
infoPtr->itemEdit.fEnabled = FALSE;
|
|
infoPtr->iVersion = COMCTL32_VERSION;
|
|
infoPtr->colRectsDirty = FALSE;
|
|
|
|
/* get default font (icon title) */
|
|
SystemParametersInfoW(SPI_GETICONTITLELOGFONT, 0, &logFont, 0);
|
|
infoPtr->hDefaultFont = CreateFontIndirectW(&logFont);
|
|
infoPtr->hFont = infoPtr->hDefaultFont;
|
|
LISTVIEW_SaveTextMetrics(infoPtr);
|
|
|
|
/* allocate memory for the data structure */
|
|
if (!(infoPtr->selectionRanges = ranges_create(10))) goto fail;
|
|
if (!(infoPtr->hdpaItems = DPA_Create(10))) goto fail;
|
|
if (!(infoPtr->hdpaItemIds = DPA_Create(10))) goto fail;
|
|
if (!(infoPtr->hdpaPosX = DPA_Create(10))) goto fail;
|
|
if (!(infoPtr->hdpaPosY = DPA_Create(10))) goto fail;
|
|
if (!(infoPtr->hdpaColumns = DPA_Create(10))) goto fail;
|
|
return TRUE;
|
|
|
|
fail:
|
|
DestroyWindow(infoPtr->hwndHeader);
|
|
ranges_destroy(infoPtr->selectionRanges);
|
|
DPA_Destroy(infoPtr->hdpaItems);
|
|
DPA_Destroy(infoPtr->hdpaItemIds);
|
|
DPA_Destroy(infoPtr->hdpaPosX);
|
|
DPA_Destroy(infoPtr->hdpaPosY);
|
|
DPA_Destroy(infoPtr->hdpaColumns);
|
|
Free(infoPtr);
|
|
return FALSE;
|
|
}
|
|
|
|
/***
|
|
* DESCRIPTION:
|
|
* Creates the listview control - the WM_CREATE phase. Most of the data is
|
|
* already set up in LISTVIEW_NCCreate
|
|
*
|
|
* PARAMETER(S):
|
|
* [I] hwnd : window handle
|
|
* [I] lpcs : the create parameters
|
|
*
|
|
* RETURN:
|
|
* Success: 0
|
|
* Failure: -1
|
|
*/
|
|
static LRESULT LISTVIEW_Create(HWND hwnd, const CREATESTRUCTW *lpcs)
|
|
{
|
|
LISTVIEW_INFO *infoPtr = (LISTVIEW_INFO *)GetWindowLongPtrW(hwnd, 0);
|
|
|
|
TRACE("(lpcs=%p, style=0x%08x)\n", lpcs, lpcs->style);
|
|
|
|
infoPtr->dwStyle = lpcs->style;
|
|
map_style_view(infoPtr);
|
|
|
|
infoPtr->notifyFormat = SendMessageW(infoPtr->hwndNotify, WM_NOTIFYFORMAT,
|
|
(WPARAM)infoPtr->hwndSelf, NF_QUERY);
|
|
/* on error defaulting to ANSI notifications */
|
|
if (infoPtr->notifyFormat == 0) infoPtr->notifyFormat = NFR_ANSI;
|
|
TRACE("notify format=%d\n", infoPtr->notifyFormat);
|
|
|
|
if ((infoPtr->uView == LV_VIEW_DETAILS) && (lpcs->style & WS_VISIBLE))
|
|
{
|
|
if (LISTVIEW_CreateHeader(infoPtr) < 0) return -1;
|
|
}
|
|
else
|
|
infoPtr->hwndHeader = 0;
|
|
|
|
/* init item size to avoid division by 0 */
|
|
LISTVIEW_UpdateItemSize (infoPtr);
|
|
LISTVIEW_UpdateSize (infoPtr);
|
|
|
|
if (infoPtr->uView == LV_VIEW_DETAILS)
|
|
{
|
|
if (!(LVS_NOCOLUMNHEADER & lpcs->style) && (WS_VISIBLE & lpcs->style))
|
|
{
|
|
ShowWindow(infoPtr->hwndHeader, SW_SHOWNORMAL);
|
|
}
|
|
LISTVIEW_UpdateScroll(infoPtr);
|
|
/* send WM_MEASUREITEM notification */
|
|
if (infoPtr->dwStyle & LVS_OWNERDRAWFIXED) notify_measureitem(infoPtr);
|
|
}
|
|
|
|
OpenThemeData(hwnd, themeClass);
|
|
|
|
/* initialize the icon sizes */
|
|
set_icon_size(&infoPtr->iconSize, infoPtr->himlNormal, infoPtr->uView != LV_VIEW_ICON);
|
|
set_icon_size(&infoPtr->iconStateSize, infoPtr->himlState, TRUE);
|
|
return 0;
|
|
}
|
|
|
|
/***
|
|
* DESCRIPTION:
|
|
* Destroys the listview control.
|
|
*
|
|
* PARAMETER(S):
|
|
* [I] infoPtr : valid pointer to the listview structure
|
|
*
|
|
* RETURN:
|
|
* Success: 0
|
|
* Failure: -1
|
|
*/
|
|
static LRESULT LISTVIEW_Destroy(LISTVIEW_INFO *infoPtr)
|
|
{
|
|
HTHEME theme = GetWindowTheme(infoPtr->hwndSelf);
|
|
CloseThemeData(theme);
|
|
|
|
/* delete all items */
|
|
LISTVIEW_DeleteAllItems(infoPtr, TRUE);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/***
|
|
* DESCRIPTION:
|
|
* Enables the listview control.
|
|
*
|
|
* PARAMETER(S):
|
|
* [I] infoPtr : valid pointer to the listview structure
|
|
* [I] bEnable : specifies whether to enable or disable the window
|
|
*
|
|
* RETURN:
|
|
* SUCCESS : TRUE
|
|
* FAILURE : FALSE
|
|
*/
|
|
static BOOL LISTVIEW_Enable(const LISTVIEW_INFO *infoPtr)
|
|
{
|
|
if (infoPtr->dwStyle & LVS_OWNERDRAWFIXED)
|
|
InvalidateRect(infoPtr->hwndSelf, NULL, TRUE);
|
|
return TRUE;
|
|
}
|
|
|
|
/***
|
|
* DESCRIPTION:
|
|
* Erases the background of the listview control.
|
|
*
|
|
* PARAMETER(S):
|
|
* [I] infoPtr : valid pointer to the listview structure
|
|
* [I] hdc : device context handle
|
|
*
|
|
* RETURN:
|
|
* SUCCESS : TRUE
|
|
* FAILURE : FALSE
|
|
*/
|
|
static inline BOOL LISTVIEW_EraseBkgnd(const LISTVIEW_INFO *infoPtr, HDC hdc)
|
|
{
|
|
RECT rc;
|
|
|
|
TRACE("(hdc=%p)\n", hdc);
|
|
|
|
if (!GetClipBox(hdc, &rc)) return FALSE;
|
|
|
|
if (infoPtr->clrBk == CLR_NONE)
|
|
{
|
|
if (infoPtr->dwLvExStyle & LVS_EX_TRANSPARENTBKGND)
|
|
return SendMessageW(infoPtr->hwndNotify, WM_PRINTCLIENT,
|
|
(WPARAM)hdc, PRF_ERASEBKGND);
|
|
else
|
|
return SendMessageW(infoPtr->hwndNotify, WM_ERASEBKGND, (WPARAM)hdc, 0);
|
|
}
|
|
|
|
/* for double buffered controls we need to do this during refresh */
|
|
if (infoPtr->dwLvExStyle & LVS_EX_DOUBLEBUFFER) return FALSE;
|
|
|
|
return LISTVIEW_FillBkgnd(infoPtr, hdc, &rc);
|
|
}
|
|
|
|
|
|
/***
|
|
* DESCRIPTION:
|
|
* Helper function for LISTVIEW_[HV]Scroll *only*.
|
|
* Performs vertical/horizontal scrolling by a give amount.
|
|
*
|
|
* PARAMETER(S):
|
|
* [I] infoPtr : valid pointer to the listview structure
|
|
* [I] dx : amount of horizontal scroll
|
|
* [I] dy : amount of vertical scroll
|
|
*/
|
|
static void scroll_list(LISTVIEW_INFO *infoPtr, INT dx, INT dy)
|
|
{
|
|
/* now we can scroll the list */
|
|
ScrollWindowEx(infoPtr->hwndSelf, dx, dy, &infoPtr->rcList,
|
|
&infoPtr->rcList, 0, 0, SW_ERASE | SW_INVALIDATE);
|
|
/* if we have focus, adjust rect */
|
|
OffsetRect(&infoPtr->rcFocus, dx, dy);
|
|
UpdateWindow(infoPtr->hwndSelf);
|
|
}
|
|
|
|
/***
|
|
* DESCRIPTION:
|
|
* Performs vertical scrolling.
|
|
*
|
|
* PARAMETER(S):
|
|
* [I] infoPtr : valid pointer to the listview structure
|
|
* [I] nScrollCode : scroll code
|
|
* [I] nScrollDiff : units to scroll in SB_INTERNAL mode, 0 otherwise
|
|
* [I] hScrollWnd : scrollbar control window handle
|
|
*
|
|
* RETURN:
|
|
* Zero
|
|
*
|
|
* NOTES:
|
|
* SB_LINEUP/SB_LINEDOWN:
|
|
* for LVS_ICON, LVS_SMALLICON is 37 by experiment
|
|
* for LVS_REPORT is 1 line
|
|
* for LVS_LIST cannot occur
|
|
*
|
|
*/
|
|
static LRESULT LISTVIEW_VScroll(LISTVIEW_INFO *infoPtr, INT nScrollCode,
|
|
INT nScrollDiff)
|
|
{
|
|
INT nOldScrollPos, nNewScrollPos;
|
|
SCROLLINFO scrollInfo;
|
|
BOOL is_an_icon;
|
|
|
|
TRACE("(nScrollCode=%d(%s), nScrollDiff=%d)\n", nScrollCode,
|
|
debugscrollcode(nScrollCode), nScrollDiff);
|
|
|
|
if (infoPtr->hwndEdit) SendMessageW(infoPtr->hwndEdit, WM_KILLFOCUS, 0, 0);
|
|
|
|
scrollInfo.cbSize = sizeof(SCROLLINFO);
|
|
scrollInfo.fMask = SIF_PAGE | SIF_POS | SIF_RANGE | SIF_TRACKPOS;
|
|
|
|
is_an_icon = ((infoPtr->uView == LV_VIEW_ICON) || (infoPtr->uView == LV_VIEW_SMALLICON));
|
|
|
|
if (!GetScrollInfo(infoPtr->hwndSelf, SB_VERT, &scrollInfo)) return 1;
|
|
|
|
nOldScrollPos = scrollInfo.nPos;
|
|
switch (nScrollCode)
|
|
{
|
|
case SB_INTERNAL:
|
|
break;
|
|
|
|
case SB_LINEUP:
|
|
nScrollDiff = (is_an_icon) ? -LISTVIEW_SCROLL_ICON_LINE_SIZE : -1;
|
|
break;
|
|
|
|
case SB_LINEDOWN:
|
|
nScrollDiff = (is_an_icon) ? LISTVIEW_SCROLL_ICON_LINE_SIZE : 1;
|
|
break;
|
|
|
|
case SB_PAGEUP:
|
|
nScrollDiff = -scrollInfo.nPage;
|
|
break;
|
|
|
|
case SB_PAGEDOWN:
|
|
nScrollDiff = scrollInfo.nPage;
|
|
break;
|
|
|
|
case SB_THUMBPOSITION:
|
|
case SB_THUMBTRACK:
|
|
nScrollDiff = scrollInfo.nTrackPos - scrollInfo.nPos;
|
|
break;
|
|
|
|
default:
|
|
nScrollDiff = 0;
|
|
}
|
|
|
|
/* quit right away if pos isn't changing */
|
|
if (nScrollDiff == 0) return 0;
|
|
|
|
/* calculate new position, and handle overflows */
|
|
nNewScrollPos = scrollInfo.nPos + nScrollDiff;
|
|
if (nScrollDiff > 0) {
|
|
if (nNewScrollPos < nOldScrollPos ||
|
|
nNewScrollPos > scrollInfo.nMax)
|
|
nNewScrollPos = scrollInfo.nMax;
|
|
} else {
|
|
if (nNewScrollPos > nOldScrollPos ||
|
|
nNewScrollPos < scrollInfo.nMin)
|
|
nNewScrollPos = scrollInfo.nMin;
|
|
}
|
|
|
|
/* set the new position, and reread in case it changed */
|
|
scrollInfo.fMask = SIF_POS;
|
|
scrollInfo.nPos = nNewScrollPos;
|
|
nNewScrollPos = SetScrollInfo(infoPtr->hwndSelf, SB_VERT, &scrollInfo, TRUE);
|
|
|
|
/* carry on only if it really changed */
|
|
if (nNewScrollPos == nOldScrollPos) return 0;
|
|
|
|
/* now adjust to client coordinates */
|
|
nScrollDiff = nOldScrollPos - nNewScrollPos;
|
|
if (infoPtr->uView == LV_VIEW_DETAILS) nScrollDiff *= infoPtr->nItemHeight;
|
|
|
|
/* and scroll the window */
|
|
scroll_list(infoPtr, 0, nScrollDiff);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/***
|
|
* DESCRIPTION:
|
|
* Performs horizontal scrolling.
|
|
*
|
|
* PARAMETER(S):
|
|
* [I] infoPtr : valid pointer to the listview structure
|
|
* [I] nScrollCode : scroll code
|
|
* [I] nScrollDiff : units to scroll in SB_INTERNAL mode, 0 otherwise
|
|
* [I] hScrollWnd : scrollbar control window handle
|
|
*
|
|
* RETURN:
|
|
* Zero
|
|
*
|
|
* NOTES:
|
|
* SB_LINELEFT/SB_LINERIGHT:
|
|
* for LVS_ICON, LVS_SMALLICON 1 pixel
|
|
* for LVS_REPORT is 1 pixel
|
|
* for LVS_LIST is 1 column --> which is a 1 because the
|
|
* scroll is based on columns not pixels
|
|
*
|
|
*/
|
|
static LRESULT LISTVIEW_HScroll(LISTVIEW_INFO *infoPtr, INT nScrollCode,
|
|
INT nScrollDiff)
|
|
{
|
|
INT nOldScrollPos, nNewScrollPos;
|
|
SCROLLINFO scrollInfo;
|
|
BOOL is_an_icon;
|
|
|
|
TRACE("(nScrollCode=%d(%s), nScrollDiff=%d)\n", nScrollCode,
|
|
debugscrollcode(nScrollCode), nScrollDiff);
|
|
|
|
if (infoPtr->hwndEdit) SendMessageW(infoPtr->hwndEdit, WM_KILLFOCUS, 0, 0);
|
|
|
|
scrollInfo.cbSize = sizeof(SCROLLINFO);
|
|
scrollInfo.fMask = SIF_PAGE | SIF_POS | SIF_RANGE | SIF_TRACKPOS;
|
|
|
|
is_an_icon = ((infoPtr->uView == LV_VIEW_ICON) || (infoPtr->uView == LV_VIEW_SMALLICON));
|
|
|
|
if (!GetScrollInfo(infoPtr->hwndSelf, SB_HORZ, &scrollInfo)) return 1;
|
|
|
|
nOldScrollPos = scrollInfo.nPos;
|
|
|
|
switch (nScrollCode)
|
|
{
|
|
case SB_INTERNAL:
|
|
break;
|
|
|
|
case SB_LINELEFT:
|
|
nScrollDiff = (is_an_icon) ? -LISTVIEW_SCROLL_ICON_LINE_SIZE : -1;
|
|
break;
|
|
|
|
case SB_LINERIGHT:
|
|
nScrollDiff = (is_an_icon) ? LISTVIEW_SCROLL_ICON_LINE_SIZE : 1;
|
|
break;
|
|
|
|
case SB_PAGELEFT:
|
|
nScrollDiff = -scrollInfo.nPage;
|
|
break;
|
|
|
|
case SB_PAGERIGHT:
|
|
nScrollDiff = scrollInfo.nPage;
|
|
break;
|
|
|
|
case SB_THUMBPOSITION:
|
|
case SB_THUMBTRACK:
|
|
nScrollDiff = scrollInfo.nTrackPos - scrollInfo.nPos;
|
|
break;
|
|
|
|
default:
|
|
nScrollDiff = 0;
|
|
}
|
|
|
|
/* quit right away if pos isn't changing */
|
|
if (nScrollDiff == 0) return 0;
|
|
|
|
/* calculate new position, and handle overflows */
|
|
nNewScrollPos = scrollInfo.nPos + nScrollDiff;
|
|
if (nScrollDiff > 0) {
|
|
if (nNewScrollPos < nOldScrollPos ||
|
|
nNewScrollPos > scrollInfo.nMax)
|
|
nNewScrollPos = scrollInfo.nMax;
|
|
} else {
|
|
if (nNewScrollPos > nOldScrollPos ||
|
|
nNewScrollPos < scrollInfo.nMin)
|
|
nNewScrollPos = scrollInfo.nMin;
|
|
}
|
|
|
|
/* set the new position, and reread in case it changed */
|
|
scrollInfo.fMask = SIF_POS;
|
|
scrollInfo.nPos = nNewScrollPos;
|
|
nNewScrollPos = SetScrollInfo(infoPtr->hwndSelf, SB_HORZ, &scrollInfo, TRUE);
|
|
|
|
/* carry on only if it really changed */
|
|
if (nNewScrollPos == nOldScrollPos) return 0;
|
|
|
|
if (infoPtr->hwndHeader) LISTVIEW_UpdateHeaderSize(infoPtr, nNewScrollPos);
|
|
|
|
/* now adjust to client coordinates */
|
|
nScrollDiff = nOldScrollPos - nNewScrollPos;
|
|
if (infoPtr->uView == LV_VIEW_LIST) nScrollDiff *= infoPtr->nItemWidth;
|
|
|
|
/* and scroll the window */
|
|
scroll_list(infoPtr, nScrollDiff, 0);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static LRESULT LISTVIEW_MouseWheel(LISTVIEW_INFO *infoPtr, INT wheelDelta)
|
|
{
|
|
UINT pulScrollLines = 3;
|
|
|
|
TRACE("(wheelDelta=%d)\n", wheelDelta);
|
|
|
|
switch(infoPtr->uView)
|
|
{
|
|
case LV_VIEW_ICON:
|
|
case LV_VIEW_SMALLICON:
|
|
/*
|
|
* listview should be scrolled by a multiple of 37 dependently on its dimension or its visible item number
|
|
* should be fixed in the future.
|
|
*/
|
|
LISTVIEW_VScroll(infoPtr, SB_INTERNAL, (wheelDelta > 0) ?
|
|
-LISTVIEW_SCROLL_ICON_LINE_SIZE : LISTVIEW_SCROLL_ICON_LINE_SIZE);
|
|
break;
|
|
|
|
case LV_VIEW_DETAILS:
|
|
SystemParametersInfoW(SPI_GETWHEELSCROLLLINES,0, &pulScrollLines, 0);
|
|
|
|
/* if scrolling changes direction, ignore left overs */
|
|
if ((wheelDelta < 0 && infoPtr->cWheelRemainder < 0) ||
|
|
(wheelDelta > 0 && infoPtr->cWheelRemainder > 0))
|
|
infoPtr->cWheelRemainder += wheelDelta;
|
|
else
|
|
infoPtr->cWheelRemainder = wheelDelta;
|
|
if (infoPtr->cWheelRemainder && pulScrollLines)
|
|
{
|
|
int cLineScroll;
|
|
pulScrollLines = min((UINT)LISTVIEW_GetCountPerColumn(infoPtr), pulScrollLines);
|
|
cLineScroll = pulScrollLines * (float)infoPtr->cWheelRemainder / WHEEL_DELTA;
|
|
infoPtr->cWheelRemainder -= WHEEL_DELTA * cLineScroll / (int)pulScrollLines;
|
|
LISTVIEW_VScroll(infoPtr, SB_INTERNAL, -cLineScroll);
|
|
}
|
|
break;
|
|
|
|
case LV_VIEW_LIST:
|
|
LISTVIEW_HScroll(infoPtr, (wheelDelta > 0) ? SB_LINELEFT : SB_LINERIGHT, 0);
|
|
break;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/***
|
|
* DESCRIPTION:
|
|
* ???
|
|
*
|
|
* PARAMETER(S):
|
|
* [I] infoPtr : valid pointer to the listview structure
|
|
* [I] nVirtualKey : virtual key
|
|
* [I] lKeyData : key data
|
|
*
|
|
* RETURN:
|
|
* Zero
|
|
*/
|
|
static LRESULT LISTVIEW_KeyDown(LISTVIEW_INFO *infoPtr, INT nVirtualKey, LONG lKeyData)
|
|
{
|
|
HWND hwndSelf = infoPtr->hwndSelf;
|
|
INT nItem = -1;
|
|
NMLVKEYDOWN nmKeyDown;
|
|
|
|
TRACE("(nVirtualKey=%d, lKeyData=%d)\n", nVirtualKey, lKeyData);
|
|
|
|
/* send LVN_KEYDOWN notification */
|
|
nmKeyDown.wVKey = nVirtualKey;
|
|
nmKeyDown.flags = 0;
|
|
notify_hdr(infoPtr, LVN_KEYDOWN, &nmKeyDown.hdr);
|
|
if (!IsWindow(hwndSelf))
|
|
return 0;
|
|
|
|
switch (nVirtualKey)
|
|
{
|
|
case VK_SPACE:
|
|
nItem = infoPtr->nFocusedItem;
|
|
if (infoPtr->dwLvExStyle & LVS_EX_CHECKBOXES)
|
|
toggle_checkbox_state(infoPtr, infoPtr->nFocusedItem);
|
|
break;
|
|
|
|
case VK_RETURN:
|
|
if ((infoPtr->nItemCount > 0) && (infoPtr->nFocusedItem != -1))
|
|
{
|
|
if (!notify(infoPtr, NM_RETURN)) return 0;
|
|
if (!notify(infoPtr, LVN_ITEMACTIVATE)) return 0;
|
|
}
|
|
break;
|
|
|
|
case VK_HOME:
|
|
if (infoPtr->nItemCount > 0)
|
|
nItem = 0;
|
|
break;
|
|
|
|
case VK_END:
|
|
if (infoPtr->nItemCount > 0)
|
|
nItem = infoPtr->nItemCount - 1;
|
|
break;
|
|
|
|
case VK_LEFT:
|
|
nItem = LISTVIEW_GetNextItem(infoPtr, infoPtr->nFocusedItem, LVNI_TOLEFT);
|
|
break;
|
|
|
|
case VK_UP:
|
|
nItem = LISTVIEW_GetNextItem(infoPtr, infoPtr->nFocusedItem, LVNI_ABOVE);
|
|
break;
|
|
|
|
case VK_RIGHT:
|
|
nItem = LISTVIEW_GetNextItem(infoPtr, infoPtr->nFocusedItem, LVNI_TORIGHT);
|
|
break;
|
|
|
|
case VK_DOWN:
|
|
nItem = LISTVIEW_GetNextItem(infoPtr, infoPtr->nFocusedItem, LVNI_BELOW);
|
|
break;
|
|
|
|
case VK_PRIOR:
|
|
if (infoPtr->uView == LV_VIEW_DETAILS)
|
|
{
|
|
INT topidx = LISTVIEW_GetTopIndex(infoPtr);
|
|
if (infoPtr->nFocusedItem == topidx)
|
|
nItem = topidx - LISTVIEW_GetCountPerColumn(infoPtr) + 1;
|
|
else
|
|
nItem = topidx;
|
|
}
|
|
else
|
|
nItem = infoPtr->nFocusedItem - LISTVIEW_GetCountPerColumn(infoPtr)
|
|
* LISTVIEW_GetCountPerRow(infoPtr);
|
|
if(nItem < 0) nItem = 0;
|
|
break;
|
|
|
|
case VK_NEXT:
|
|
if (infoPtr->uView == LV_VIEW_DETAILS)
|
|
{
|
|
INT topidx = LISTVIEW_GetTopIndex(infoPtr);
|
|
INT cnt = LISTVIEW_GetCountPerColumn(infoPtr);
|
|
if (infoPtr->nFocusedItem == topidx + cnt - 1)
|
|
nItem = infoPtr->nFocusedItem + cnt - 1;
|
|
else
|
|
nItem = topidx + cnt - 1;
|
|
}
|
|
else
|
|
nItem = infoPtr->nFocusedItem + LISTVIEW_GetCountPerColumn(infoPtr)
|
|
* LISTVIEW_GetCountPerRow(infoPtr);
|
|
if(nItem >= infoPtr->nItemCount) nItem = infoPtr->nItemCount - 1;
|
|
break;
|
|
}
|
|
|
|
if ((nItem != -1) && (nItem != infoPtr->nFocusedItem || nVirtualKey == VK_SPACE))
|
|
LISTVIEW_KeySelection(infoPtr, nItem, nVirtualKey == VK_SPACE);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/***
|
|
* DESCRIPTION:
|
|
* Kills the focus.
|
|
*
|
|
* PARAMETER(S):
|
|
* [I] infoPtr : valid pointer to the listview structure
|
|
*
|
|
* RETURN:
|
|
* Zero
|
|
*/
|
|
static LRESULT LISTVIEW_KillFocus(LISTVIEW_INFO *infoPtr)
|
|
{
|
|
TRACE("()\n");
|
|
|
|
/* drop any left over scroll amount */
|
|
infoPtr->cWheelRemainder = 0;
|
|
|
|
/* if we did not have the focus, there's nothing more to do */
|
|
if (!infoPtr->bFocus) return 0;
|
|
|
|
/* send NM_KILLFOCUS notification */
|
|
if (!notify(infoPtr, NM_KILLFOCUS)) return 0;
|
|
|
|
/* if we have a focus rectangle, get rid of it */
|
|
LISTVIEW_ShowFocusRect(infoPtr, FALSE);
|
|
|
|
/* if have a marquee selection, stop it */
|
|
if (infoPtr->bMarqueeSelect)
|
|
{
|
|
/* Remove the marquee rectangle and release our mouse capture */
|
|
LISTVIEW_InvalidateRect(infoPtr, &infoPtr->marqueeRect);
|
|
ReleaseCapture();
|
|
|
|
SetRect(&infoPtr->marqueeRect, 0, 0, 0, 0);
|
|
|
|
infoPtr->bMarqueeSelect = FALSE;
|
|
infoPtr->bScrolling = FALSE;
|
|
KillTimer(infoPtr->hwndSelf, (UINT_PTR) infoPtr);
|
|
}
|
|
|
|
/* set window focus flag */
|
|
infoPtr->bFocus = FALSE;
|
|
|
|
/* invalidate the selected items before resetting focus flag */
|
|
LISTVIEW_InvalidateSelectedItems(infoPtr);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/***
|
|
* DESCRIPTION:
|
|
* Processes double click messages (left mouse button).
|
|
*
|
|
* PARAMETER(S):
|
|
* [I] infoPtr : valid pointer to the listview structure
|
|
* [I] wKey : key flag
|
|
* [I] x,y : mouse coordinate
|
|
*
|
|
* RETURN:
|
|
* Zero
|
|
*/
|
|
static LRESULT LISTVIEW_LButtonDblClk(LISTVIEW_INFO *infoPtr, WORD wKey, INT x, INT y)
|
|
{
|
|
LVHITTESTINFO htInfo;
|
|
|
|
TRACE("(key=%hu, X=%u, Y=%u)\n", wKey, x, y);
|
|
|
|
/* Cancel the item edition if any */
|
|
if (infoPtr->itemEdit.fEnabled)
|
|
{
|
|
KillTimer(infoPtr->hwndSelf, (UINT_PTR)&infoPtr->itemEdit);
|
|
infoPtr->itemEdit.fEnabled = FALSE;
|
|
}
|
|
|
|
/* send NM_RELEASEDCAPTURE notification */
|
|
if (!notify(infoPtr, NM_RELEASEDCAPTURE)) return 0;
|
|
|
|
htInfo.pt.x = x;
|
|
htInfo.pt.y = y;
|
|
|
|
/* send NM_DBLCLK notification */
|
|
LISTVIEW_HitTest(infoPtr, &htInfo, TRUE, FALSE);
|
|
if (!notify_click(infoPtr, NM_DBLCLK, &htInfo)) return 0;
|
|
|
|
/* To send the LVN_ITEMACTIVATE, it must be on an Item */
|
|
if(htInfo.iItem != -1) notify_itemactivate(infoPtr,&htInfo);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static LRESULT LISTVIEW_TrackMouse(const LISTVIEW_INFO *infoPtr, POINT pt)
|
|
{
|
|
MSG msg;
|
|
RECT r;
|
|
|
|
r.top = r.bottom = pt.y;
|
|
r.left = r.right = pt.x;
|
|
|
|
InflateRect(&r, GetSystemMetrics(SM_CXDRAG), GetSystemMetrics(SM_CYDRAG));
|
|
|
|
SetCapture(infoPtr->hwndSelf);
|
|
|
|
while (1)
|
|
{
|
|
if (PeekMessageW(&msg, 0, 0, 0, PM_REMOVE | PM_NOYIELD))
|
|
{
|
|
if (msg.message == WM_MOUSEMOVE)
|
|
{
|
|
pt.x = (short)LOWORD(msg.lParam);
|
|
pt.y = (short)HIWORD(msg.lParam);
|
|
if (PtInRect(&r, pt))
|
|
continue;
|
|
else
|
|
{
|
|
ReleaseCapture();
|
|
return 1;
|
|
}
|
|
}
|
|
else if (msg.message >= WM_LBUTTONDOWN &&
|
|
msg.message <= WM_RBUTTONDBLCLK)
|
|
{
|
|
break;
|
|
}
|
|
|
|
DispatchMessageW(&msg);
|
|
}
|
|
|
|
if (GetCapture() != infoPtr->hwndSelf)
|
|
return 0;
|
|
}
|
|
|
|
ReleaseCapture();
|
|
return 0;
|
|
}
|
|
|
|
|
|
/***
|
|
* DESCRIPTION:
|
|
* Processes mouse down messages (left mouse button).
|
|
*
|
|
* PARAMETERS:
|
|
* infoPtr [I ] valid pointer to the listview structure
|
|
* wKey [I ] key flag
|
|
* x,y [I ] mouse coordinate
|
|
*
|
|
* RETURN:
|
|
* Zero
|
|
*/
|
|
static LRESULT LISTVIEW_LButtonDown(LISTVIEW_INFO *infoPtr, WORD wKey, INT x, INT y)
|
|
{
|
|
LVHITTESTINFO lvHitTestInfo;
|
|
static BOOL bGroupSelect = TRUE;
|
|
POINT pt = { x, y };
|
|
INT nItem;
|
|
|
|
TRACE("(key=%hu, X=%u, Y=%u)\n", wKey, x, y);
|
|
|
|
/* send NM_RELEASEDCAPTURE notification */
|
|
if (!notify(infoPtr, NM_RELEASEDCAPTURE)) return 0;
|
|
|
|
/* set left button down flag and record the click position */
|
|
infoPtr->bLButtonDown = TRUE;
|
|
infoPtr->ptClickPos = pt;
|
|
infoPtr->bDragging = FALSE;
|
|
infoPtr->bMarqueeSelect = FALSE;
|
|
infoPtr->bScrolling = FALSE;
|
|
|
|
lvHitTestInfo.pt.x = x;
|
|
lvHitTestInfo.pt.y = y;
|
|
|
|
nItem = LISTVIEW_HitTest(infoPtr, &lvHitTestInfo, TRUE, TRUE);
|
|
TRACE("at %s, nItem=%d\n", wine_dbgstr_point(&pt), nItem);
|
|
if ((nItem >= 0) && (nItem < infoPtr->nItemCount))
|
|
{
|
|
if ((infoPtr->dwLvExStyle & LVS_EX_CHECKBOXES) && (lvHitTestInfo.flags & LVHT_ONITEMSTATEICON))
|
|
{
|
|
toggle_checkbox_state(infoPtr, nItem);
|
|
return 0;
|
|
}
|
|
|
|
if (infoPtr->dwStyle & LVS_SINGLESEL)
|
|
{
|
|
if (LISTVIEW_GetItemState(infoPtr, nItem, LVIS_SELECTED))
|
|
infoPtr->nEditLabelItem = nItem;
|
|
else
|
|
LISTVIEW_SetSelection(infoPtr, nItem);
|
|
}
|
|
else
|
|
{
|
|
if ((wKey & MK_CONTROL) && (wKey & MK_SHIFT))
|
|
{
|
|
if (bGroupSelect)
|
|
{
|
|
if (!LISTVIEW_AddGroupSelection(infoPtr, nItem)) return 0;
|
|
LISTVIEW_SetItemFocus(infoPtr, nItem);
|
|
infoPtr->nSelectionMark = nItem;
|
|
}
|
|
else
|
|
{
|
|
LVITEMW item;
|
|
|
|
item.state = LVIS_SELECTED | LVIS_FOCUSED;
|
|
item.stateMask = LVIS_SELECTED | LVIS_FOCUSED;
|
|
|
|
LISTVIEW_SetItemState(infoPtr,nItem,&item);
|
|
infoPtr->nSelectionMark = nItem;
|
|
}
|
|
}
|
|
else if (wKey & MK_CONTROL)
|
|
{
|
|
LVITEMW item;
|
|
|
|
bGroupSelect = (LISTVIEW_GetItemState(infoPtr, nItem, LVIS_SELECTED) == 0);
|
|
|
|
item.state = (bGroupSelect ? LVIS_SELECTED : 0) | LVIS_FOCUSED;
|
|
item.stateMask = LVIS_SELECTED | LVIS_FOCUSED;
|
|
LISTVIEW_SetItemState(infoPtr, nItem, &item);
|
|
infoPtr->nSelectionMark = nItem;
|
|
}
|
|
else if (wKey & MK_SHIFT)
|
|
{
|
|
LISTVIEW_SetGroupSelection(infoPtr, nItem);
|
|
}
|
|
else
|
|
{
|
|
if (LISTVIEW_GetItemState(infoPtr, nItem, LVIS_SELECTED))
|
|
{
|
|
infoPtr->nEditLabelItem = nItem;
|
|
infoPtr->nLButtonDownItem = nItem;
|
|
|
|
LISTVIEW_SetItemFocus(infoPtr, nItem);
|
|
}
|
|
else
|
|
/* set selection (clears other pre-existing selections) */
|
|
LISTVIEW_SetSelection(infoPtr, nItem);
|
|
}
|
|
}
|
|
|
|
if (infoPtr->dwLvExStyle & LVS_EX_ONECLICKACTIVATE)
|
|
if(lvHitTestInfo.iItem != -1) notify_itemactivate(infoPtr,&lvHitTestInfo);
|
|
}
|
|
else
|
|
{
|
|
if (!infoPtr->bFocus)
|
|
SetFocus(infoPtr->hwndSelf);
|
|
|
|
/* remove all selections */
|
|
if (!(wKey & MK_CONTROL) && !(wKey & MK_SHIFT))
|
|
LISTVIEW_DeselectAll(infoPtr);
|
|
ReleaseCapture();
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/***
|
|
* DESCRIPTION:
|
|
* Processes mouse up messages (left mouse button).
|
|
*
|
|
* PARAMETERS:
|
|
* infoPtr [I ] valid pointer to the listview structure
|
|
* wKey [I ] key flag
|
|
* x,y [I ] mouse coordinate
|
|
*
|
|
* RETURN:
|
|
* Zero
|
|
*/
|
|
static LRESULT LISTVIEW_LButtonUp(LISTVIEW_INFO *infoPtr, WORD wKey, INT x, INT y)
|
|
{
|
|
LVHITTESTINFO lvHitTestInfo;
|
|
|
|
TRACE("(key=%hu, X=%u, Y=%u)\n", wKey, x, y);
|
|
|
|
if (!infoPtr->bLButtonDown) return 0;
|
|
|
|
lvHitTestInfo.pt.x = x;
|
|
lvHitTestInfo.pt.y = y;
|
|
|
|
/* send NM_CLICK notification */
|
|
LISTVIEW_HitTest(infoPtr, &lvHitTestInfo, TRUE, FALSE);
|
|
if (!notify_click(infoPtr, NM_CLICK, &lvHitTestInfo)) return 0;
|
|
|
|
/* set left button flag */
|
|
infoPtr->bLButtonDown = FALSE;
|
|
|
|
/* set a single selection, reset others */
|
|
if(lvHitTestInfo.iItem == infoPtr->nLButtonDownItem && lvHitTestInfo.iItem != -1)
|
|
LISTVIEW_SetSelection(infoPtr, infoPtr->nLButtonDownItem);
|
|
infoPtr->nLButtonDownItem = -1;
|
|
|
|
if (infoPtr->bDragging || infoPtr->bMarqueeSelect)
|
|
{
|
|
/* Remove the marquee rectangle and release our mouse capture */
|
|
if (infoPtr->bMarqueeSelect)
|
|
{
|
|
LISTVIEW_InvalidateRect(infoPtr, &infoPtr->marqueeDrawRect);
|
|
ReleaseCapture();
|
|
}
|
|
|
|
SetRect(&infoPtr->marqueeRect, 0, 0, 0, 0);
|
|
SetRect(&infoPtr->marqueeDrawRect, 0, 0, 0, 0);
|
|
|
|
infoPtr->bDragging = FALSE;
|
|
infoPtr->bMarqueeSelect = FALSE;
|
|
infoPtr->bScrolling = FALSE;
|
|
|
|
KillTimer(infoPtr->hwndSelf, (UINT_PTR) infoPtr);
|
|
return 0;
|
|
}
|
|
|
|
/* if we clicked on a selected item, edit the label */
|
|
if(lvHitTestInfo.iItem == infoPtr->nEditLabelItem && (lvHitTestInfo.flags & LVHT_ONITEMLABEL))
|
|
{
|
|
/* we want to make sure the user doesn't want to do a double click. So we will
|
|
* delay the edit. WM_LBUTTONDBLCLICK will cancel the timer
|
|
*/
|
|
infoPtr->itemEdit.fEnabled = TRUE;
|
|
infoPtr->itemEdit.iItem = lvHitTestInfo.iItem;
|
|
SetTimer(infoPtr->hwndSelf,
|
|
(UINT_PTR)&infoPtr->itemEdit,
|
|
GetDoubleClickTime(),
|
|
LISTVIEW_DelayedEditItem);
|
|
}
|
|
|
|
if (!infoPtr->bFocus)
|
|
SetFocus(infoPtr->hwndSelf);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/***
|
|
* DESCRIPTION:
|
|
* Destroys the listview control (called after WM_DESTROY).
|
|
*
|
|
* PARAMETER(S):
|
|
* [I] infoPtr : valid pointer to the listview structure
|
|
*
|
|
* RETURN:
|
|
* Zero
|
|
*/
|
|
static LRESULT LISTVIEW_NCDestroy(LISTVIEW_INFO *infoPtr)
|
|
{
|
|
INT i;
|
|
|
|
TRACE("()\n");
|
|
|
|
/* destroy data structure */
|
|
DPA_Destroy(infoPtr->hdpaItems);
|
|
DPA_Destroy(infoPtr->hdpaItemIds);
|
|
DPA_Destroy(infoPtr->hdpaPosX);
|
|
DPA_Destroy(infoPtr->hdpaPosY);
|
|
/* columns */
|
|
for (i = 0; i < DPA_GetPtrCount(infoPtr->hdpaColumns); i++)
|
|
Free(DPA_GetPtr(infoPtr->hdpaColumns, i));
|
|
DPA_Destroy(infoPtr->hdpaColumns);
|
|
ranges_destroy(infoPtr->selectionRanges);
|
|
|
|
/* destroy image lists */
|
|
if (!(infoPtr->dwStyle & LVS_SHAREIMAGELISTS))
|
|
{
|
|
ImageList_Destroy(infoPtr->himlNormal);
|
|
ImageList_Destroy(infoPtr->himlSmall);
|
|
ImageList_Destroy(infoPtr->himlState);
|
|
}
|
|
|
|
/* destroy font, bkgnd brush */
|
|
infoPtr->hFont = 0;
|
|
if (infoPtr->hDefaultFont) DeleteObject(infoPtr->hDefaultFont);
|
|
if (infoPtr->clrBk != CLR_NONE) DeleteObject(infoPtr->hBkBrush);
|
|
|
|
SetWindowLongPtrW(infoPtr->hwndSelf, 0, 0);
|
|
|
|
/* free listview info pointer*/
|
|
Free(infoPtr);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/***
|
|
* DESCRIPTION:
|
|
* Handles notifications.
|
|
*
|
|
* PARAMETER(S):
|
|
* [I] infoPtr : valid pointer to the listview structure
|
|
* [I] lpnmhdr : notification information
|
|
*
|
|
* RETURN:
|
|
* Zero
|
|
*/
|
|
static LRESULT LISTVIEW_Notify(LISTVIEW_INFO *infoPtr, NMHDR *lpnmhdr)
|
|
{
|
|
NMHEADERW *lpnmh;
|
|
|
|
TRACE("(lpnmhdr=%p)\n", lpnmhdr);
|
|
|
|
if (!lpnmhdr || lpnmhdr->hwndFrom != infoPtr->hwndHeader) return 0;
|
|
|
|
/* remember: HDN_LAST < HDN_FIRST */
|
|
if (lpnmhdr->code > HDN_FIRST || lpnmhdr->code < HDN_LAST) return 0;
|
|
lpnmh = (NMHEADERW *)lpnmhdr;
|
|
|
|
if (lpnmh->iItem < 0 || lpnmh->iItem >= DPA_GetPtrCount(infoPtr->hdpaColumns)) return 0;
|
|
|
|
switch (lpnmhdr->code)
|
|
{
|
|
case HDN_TRACKW:
|
|
case HDN_TRACKA:
|
|
{
|
|
COLUMN_INFO *lpColumnInfo;
|
|
POINT ptOrigin;
|
|
INT x;
|
|
|
|
if (!lpnmh->pitem || !(lpnmh->pitem->mask & HDI_WIDTH))
|
|
break;
|
|
|
|
/* remove the old line (if any) */
|
|
LISTVIEW_DrawTrackLine(infoPtr);
|
|
|
|
/* compute & draw the new line */
|
|
lpColumnInfo = LISTVIEW_GetColumnInfo(infoPtr, lpnmh->iItem);
|
|
x = lpColumnInfo->rcHeader.left + lpnmh->pitem->cxy;
|
|
LISTVIEW_GetOrigin(infoPtr, &ptOrigin);
|
|
infoPtr->xTrackLine = x + ptOrigin.x;
|
|
LISTVIEW_DrawTrackLine(infoPtr);
|
|
return notify_forward_header(infoPtr, lpnmh);
|
|
}
|
|
|
|
case HDN_ENDTRACKA:
|
|
case HDN_ENDTRACKW:
|
|
/* remove the track line (if any) */
|
|
LISTVIEW_DrawTrackLine(infoPtr);
|
|
infoPtr->xTrackLine = -1;
|
|
return notify_forward_header(infoPtr, lpnmh);
|
|
|
|
case HDN_BEGINDRAG:
|
|
if ((infoPtr->dwLvExStyle & LVS_EX_HEADERDRAGDROP) == 0) return 1;
|
|
return notify_forward_header(infoPtr, lpnmh);
|
|
|
|
case HDN_ENDDRAG:
|
|
infoPtr->colRectsDirty = TRUE;
|
|
LISTVIEW_InvalidateList(infoPtr);
|
|
return notify_forward_header(infoPtr, lpnmh);
|
|
|
|
case HDN_ITEMCHANGEDW:
|
|
case HDN_ITEMCHANGEDA:
|
|
{
|
|
COLUMN_INFO *lpColumnInfo;
|
|
HDITEMW hdi;
|
|
INT dx, cxy;
|
|
|
|
if (!lpnmh->pitem || !(lpnmh->pitem->mask & HDI_WIDTH))
|
|
{
|
|
hdi.mask = HDI_WIDTH;
|
|
if (!SendMessageW(infoPtr->hwndHeader, HDM_GETITEMW, lpnmh->iItem, (LPARAM)&hdi)) return 0;
|
|
cxy = hdi.cxy;
|
|
}
|
|
else
|
|
cxy = lpnmh->pitem->cxy;
|
|
|
|
/* determine how much we change since the last know position */
|
|
lpColumnInfo = LISTVIEW_GetColumnInfo(infoPtr, lpnmh->iItem);
|
|
dx = cxy - (lpColumnInfo->rcHeader.right - lpColumnInfo->rcHeader.left);
|
|
if (dx != 0)
|
|
{
|
|
lpColumnInfo->rcHeader.right += dx;
|
|
|
|
hdi.mask = HDI_ORDER;
|
|
SendMessageW(infoPtr->hwndHeader, HDM_GETITEMW, lpnmh->iItem, (LPARAM)&hdi);
|
|
|
|
/* not the rightmost one */
|
|
if (hdi.iOrder + 1 < DPA_GetPtrCount(infoPtr->hdpaColumns))
|
|
{
|
|
INT nIndex = SendMessageW(infoPtr->hwndHeader, HDM_ORDERTOINDEX,
|
|
hdi.iOrder + 1, 0);
|
|
LISTVIEW_ScrollColumns(infoPtr, nIndex, dx);
|
|
}
|
|
else
|
|
{
|
|
/* only needs to update the scrolls */
|
|
infoPtr->nItemWidth += dx;
|
|
LISTVIEW_UpdateScroll(infoPtr);
|
|
}
|
|
LISTVIEW_UpdateItemSize(infoPtr);
|
|
if (infoPtr->uView == LV_VIEW_DETAILS && is_redrawing(infoPtr))
|
|
{
|
|
POINT ptOrigin;
|
|
RECT rcCol = lpColumnInfo->rcHeader;
|
|
|
|
LISTVIEW_GetOrigin(infoPtr, &ptOrigin);
|
|
OffsetRect(&rcCol, ptOrigin.x, 0);
|
|
|
|
rcCol.top = infoPtr->rcList.top;
|
|
rcCol.bottom = infoPtr->rcList.bottom;
|
|
|
|
/* resizing left-aligned columns leaves most of the left side untouched */
|
|
if ((lpColumnInfo->fmt & LVCFMT_JUSTIFYMASK) == LVCFMT_LEFT)
|
|
{
|
|
INT nMaxDirty = infoPtr->nEllipsisWidth + infoPtr->ntmMaxCharWidth;
|
|
if (dx > 0)
|
|
nMaxDirty += dx;
|
|
rcCol.left = max (rcCol.left, rcCol.right - nMaxDirty);
|
|
}
|
|
|
|
/* when shrinking the last column clear the now unused field */
|
|
if (hdi.iOrder == DPA_GetPtrCount(infoPtr->hdpaColumns) - 1)
|
|
{
|
|
RECT right;
|
|
|
|
rcCol.right -= dx;
|
|
|
|
/* deal with right from rightmost column area */
|
|
right.left = rcCol.right;
|
|
right.top = rcCol.top;
|
|
right.bottom = rcCol.bottom;
|
|
right.right = infoPtr->rcList.right;
|
|
|
|
LISTVIEW_InvalidateRect(infoPtr, &right);
|
|
}
|
|
|
|
LISTVIEW_InvalidateRect(infoPtr, &rcCol);
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
|
|
case HDN_ITEMCLICKW:
|
|
case HDN_ITEMCLICKA:
|
|
{
|
|
/* Handle sorting by Header Column */
|
|
NMLISTVIEW nmlv;
|
|
|
|
ZeroMemory(&nmlv, sizeof(NMLISTVIEW));
|
|
nmlv.iItem = -1;
|
|
nmlv.iSubItem = lpnmh->iItem;
|
|
notify_listview(infoPtr, LVN_COLUMNCLICK, &nmlv);
|
|
return notify_forward_header(infoPtr, lpnmh);
|
|
}
|
|
|
|
case HDN_DIVIDERDBLCLICKW:
|
|
case HDN_DIVIDERDBLCLICKA:
|
|
/* FIXME: for LVS_EX_HEADERINALLVIEWS and not LV_VIEW_DETAILS
|
|
we should use LVSCW_AUTOSIZE_USEHEADER, helper rework or
|
|
split needed for that */
|
|
LISTVIEW_SetColumnWidth(infoPtr, lpnmh->iItem, LVSCW_AUTOSIZE);
|
|
return notify_forward_header(infoPtr, lpnmh);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/***
|
|
* DESCRIPTION:
|
|
* Paint non-client area of control.
|
|
*
|
|
* PARAMETER(S):
|
|
* [I] infoPtr : valid pointer to the listview structureof the sender
|
|
* [I] region : update region
|
|
*
|
|
* RETURN:
|
|
* TRUE - frame was painted
|
|
* FALSE - call default window proc
|
|
*/
|
|
static BOOL LISTVIEW_NCPaint(const LISTVIEW_INFO *infoPtr, HRGN region)
|
|
{
|
|
HTHEME theme = GetWindowTheme (infoPtr->hwndSelf);
|
|
HDC dc;
|
|
RECT r;
|
|
HRGN cliprgn;
|
|
int cxEdge = GetSystemMetrics (SM_CXEDGE),
|
|
cyEdge = GetSystemMetrics (SM_CYEDGE);
|
|
|
|
if (!theme)
|
|
return DefWindowProcW (infoPtr->hwndSelf, WM_NCPAINT, (WPARAM)region, 0);
|
|
|
|
GetWindowRect(infoPtr->hwndSelf, &r);
|
|
|
|
cliprgn = CreateRectRgn (r.left + cxEdge, r.top + cyEdge,
|
|
r.right - cxEdge, r.bottom - cyEdge);
|
|
if (region != (HRGN)1)
|
|
CombineRgn (cliprgn, cliprgn, region, RGN_AND);
|
|
OffsetRect(&r, -r.left, -r.top);
|
|
|
|
dc = GetDCEx(infoPtr->hwndSelf, region, DCX_WINDOW|DCX_INTERSECTRGN);
|
|
OffsetRect(&r, -r.left, -r.top);
|
|
|
|
if (IsThemeBackgroundPartiallyTransparent (theme, 0, 0))
|
|
DrawThemeParentBackground(infoPtr->hwndSelf, dc, &r);
|
|
DrawThemeBackground (theme, dc, 0, 0, &r, 0);
|
|
ReleaseDC(infoPtr->hwndSelf, dc);
|
|
|
|
/* Call default proc to get the scrollbars etc. painted */
|
|
DefWindowProcW (infoPtr->hwndSelf, WM_NCPAINT, (WPARAM)cliprgn, 0);
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
/***
|
|
* DESCRIPTION:
|
|
* Determines the type of structure to use.
|
|
*
|
|
* PARAMETER(S):
|
|
* [I] infoPtr : valid pointer to the listview structureof the sender
|
|
* [I] hwndFrom : listview window handle
|
|
* [I] nCommand : command specifying the nature of the WM_NOTIFYFORMAT
|
|
*
|
|
* RETURN:
|
|
* Zero
|
|
*/
|
|
static LRESULT LISTVIEW_NotifyFormat(LISTVIEW_INFO *infoPtr, HWND hwndFrom, INT nCommand)
|
|
{
|
|
TRACE("(hwndFrom=%p, nCommand=%d)\n", hwndFrom, nCommand);
|
|
|
|
if (nCommand == NF_REQUERY)
|
|
infoPtr->notifyFormat = SendMessageW(infoPtr->hwndNotify, WM_NOTIFYFORMAT, (WPARAM)infoPtr->hwndSelf, NF_QUERY);
|
|
|
|
return infoPtr->notifyFormat;
|
|
}
|
|
|
|
/***
|
|
* DESCRIPTION:
|
|
* Paints/Repaints the listview control. Internal use.
|
|
*
|
|
* PARAMETER(S):
|
|
* [I] infoPtr : valid pointer to the listview structure
|
|
* [I] hdc : device context handle
|
|
*
|
|
* RETURN:
|
|
* Zero
|
|
*/
|
|
static LRESULT LISTVIEW_Paint(LISTVIEW_INFO *infoPtr, HDC hdc)
|
|
{
|
|
TRACE("(hdc=%p)\n", hdc);
|
|
|
|
if (infoPtr->bNoItemMetrics && infoPtr->nItemCount)
|
|
{
|
|
infoPtr->bNoItemMetrics = FALSE;
|
|
LISTVIEW_UpdateItemSize(infoPtr);
|
|
if (infoPtr->uView == LV_VIEW_ICON || infoPtr->uView == LV_VIEW_SMALLICON)
|
|
LISTVIEW_Arrange(infoPtr, LVA_DEFAULT);
|
|
LISTVIEW_UpdateScroll(infoPtr);
|
|
}
|
|
|
|
if (infoPtr->hwndHeader) UpdateWindow(infoPtr->hwndHeader);
|
|
|
|
if (hdc)
|
|
LISTVIEW_Refresh(infoPtr, hdc, NULL);
|
|
else
|
|
{
|
|
PAINTSTRUCT ps;
|
|
|
|
hdc = BeginPaint(infoPtr->hwndSelf, &ps);
|
|
if (!hdc) return 1;
|
|
LISTVIEW_Refresh(infoPtr, hdc, ps.fErase ? &ps.rcPaint : NULL);
|
|
EndPaint(infoPtr->hwndSelf, &ps);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/***
|
|
* DESCRIPTION:
|
|
* Paints/Repaints the listview control, WM_PAINT handler.
|
|
*
|
|
* PARAMETER(S):
|
|
* [I] infoPtr : valid pointer to the listview structure
|
|
* [I] hdc : device context handle
|
|
*
|
|
* RETURN:
|
|
* Zero
|
|
*/
|
|
static inline LRESULT LISTVIEW_WMPaint(LISTVIEW_INFO *infoPtr, HDC hdc)
|
|
{
|
|
TRACE("(hdc=%p)\n", hdc);
|
|
|
|
if (!is_redrawing(infoPtr))
|
|
return DefWindowProcW (infoPtr->hwndSelf, WM_PAINT, (WPARAM)hdc, 0);
|
|
|
|
return LISTVIEW_Paint(infoPtr, hdc);
|
|
}
|
|
|
|
/***
|
|
* DESCRIPTION:
|
|
* Paints/Repaints the listview control.
|
|
*
|
|
* PARAMETER(S):
|
|
* [I] infoPtr : valid pointer to the listview structure
|
|
* [I] hdc : device context handle
|
|
* [I] options : drawing options
|
|
*
|
|
* RETURN:
|
|
* Zero
|
|
*/
|
|
static LRESULT LISTVIEW_PrintClient(LISTVIEW_INFO *infoPtr, HDC hdc, DWORD options)
|
|
{
|
|
FIXME("Partial Stub: (hdc=%p options=0x%08x)\n", hdc, options);
|
|
|
|
if ((options & PRF_CHECKVISIBLE) && !IsWindowVisible(infoPtr->hwndSelf))
|
|
return 0;
|
|
|
|
if (options & PRF_ERASEBKGND)
|
|
LISTVIEW_EraseBkgnd(infoPtr, hdc);
|
|
|
|
if (options & PRF_CLIENT)
|
|
LISTVIEW_Paint(infoPtr, hdc);
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
/***
|
|
* DESCRIPTION:
|
|
* Processes double click messages (right mouse button).
|
|
*
|
|
* PARAMETER(S):
|
|
* [I] infoPtr : valid pointer to the listview structure
|
|
* [I] wKey : key flag
|
|
* [I] x,y : mouse coordinate
|
|
*
|
|
* RETURN:
|
|
* Zero
|
|
*/
|
|
static LRESULT LISTVIEW_RButtonDblClk(const LISTVIEW_INFO *infoPtr, WORD wKey, INT x, INT y)
|
|
{
|
|
LVHITTESTINFO lvHitTestInfo;
|
|
|
|
TRACE("(key=%hu,X=%u,Y=%u)\n", wKey, x, y);
|
|
|
|
/* send NM_RELEASEDCAPTURE notification */
|
|
if (!notify(infoPtr, NM_RELEASEDCAPTURE)) return 0;
|
|
|
|
/* send NM_RDBLCLK notification */
|
|
lvHitTestInfo.pt.x = x;
|
|
lvHitTestInfo.pt.y = y;
|
|
LISTVIEW_HitTest(infoPtr, &lvHitTestInfo, TRUE, FALSE);
|
|
notify_click(infoPtr, NM_RDBLCLK, &lvHitTestInfo);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/***
|
|
* DESCRIPTION:
|
|
* Processes WM_RBUTTONDOWN message and corresponding drag operation.
|
|
*
|
|
* PARAMETER(S):
|
|
* [I] infoPtr : valid pointer to the listview structure
|
|
* [I] wKey : key flag
|
|
* [I] x, y : mouse coordinate
|
|
*
|
|
* RETURN:
|
|
* Zero
|
|
*/
|
|
static LRESULT LISTVIEW_RButtonDown(LISTVIEW_INFO *infoPtr, WORD wKey, INT x, INT y)
|
|
{
|
|
LVHITTESTINFO ht;
|
|
INT item;
|
|
|
|
TRACE("(key=%hu, x=%d, y=%d)\n", wKey, x, y);
|
|
|
|
/* send NM_RELEASEDCAPTURE notification */
|
|
if (!notify(infoPtr, NM_RELEASEDCAPTURE)) return 0;
|
|
|
|
/* determine the index of the selected item */
|
|
ht.pt.x = x;
|
|
ht.pt.y = y;
|
|
item = LISTVIEW_HitTest(infoPtr, &ht, TRUE, TRUE);
|
|
|
|
/* make sure the listview control window has the focus */
|
|
if (!infoPtr->bFocus) SetFocus(infoPtr->hwndSelf);
|
|
|
|
if ((item >= 0) && (item < infoPtr->nItemCount))
|
|
{
|
|
LISTVIEW_SetItemFocus(infoPtr, item);
|
|
if (!((wKey & MK_SHIFT) || (wKey & MK_CONTROL)) &&
|
|
!LISTVIEW_GetItemState(infoPtr, item, LVIS_SELECTED))
|
|
LISTVIEW_SetSelection(infoPtr, item);
|
|
}
|
|
else
|
|
LISTVIEW_DeselectAll(infoPtr);
|
|
|
|
if (LISTVIEW_TrackMouse(infoPtr, ht.pt))
|
|
{
|
|
if (ht.iItem != -1)
|
|
{
|
|
NMLISTVIEW nmlv;
|
|
|
|
memset(&nmlv, 0, sizeof(nmlv));
|
|
nmlv.iItem = ht.iItem;
|
|
nmlv.ptAction = ht.pt;
|
|
|
|
notify_listview(infoPtr, LVN_BEGINRDRAG, &nmlv);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
SetFocus(infoPtr->hwndSelf);
|
|
|
|
ht.pt.x = x;
|
|
ht.pt.y = y;
|
|
LISTVIEW_HitTest(infoPtr, &ht, TRUE, FALSE);
|
|
|
|
if (notify_click(infoPtr, NM_RCLICK, &ht))
|
|
{
|
|
/* Send a WM_CONTEXTMENU message in response to the WM_RBUTTONUP */
|
|
SendMessageW(infoPtr->hwndSelf, WM_CONTEXTMENU,
|
|
(WPARAM)infoPtr->hwndSelf, (LPARAM)GetMessagePos());
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/***
|
|
* DESCRIPTION:
|
|
* Sets the cursor.
|
|
*
|
|
* PARAMETER(S):
|
|
* [I] infoPtr : valid pointer to the listview structure
|
|
* [I] hwnd : window handle of window containing the cursor
|
|
* [I] nHittest : hit-test code
|
|
* [I] wMouseMsg : ideintifier of the mouse message
|
|
*
|
|
* RETURN:
|
|
* TRUE if cursor is set
|
|
* FALSE otherwise
|
|
*/
|
|
static BOOL LISTVIEW_SetCursor(const LISTVIEW_INFO *infoPtr, WPARAM wParam, LPARAM lParam)
|
|
{
|
|
LVHITTESTINFO lvHitTestInfo;
|
|
|
|
if (!LISTVIEW_IsHotTracking(infoPtr)) goto forward;
|
|
|
|
if (!infoPtr->hHotCursor) goto forward;
|
|
|
|
GetCursorPos(&lvHitTestInfo.pt);
|
|
if (LISTVIEW_HitTest(infoPtr, &lvHitTestInfo, FALSE, FALSE) < 0) goto forward;
|
|
|
|
SetCursor(infoPtr->hHotCursor);
|
|
|
|
return TRUE;
|
|
|
|
forward:
|
|
|
|
return DefWindowProcW(infoPtr->hwndSelf, WM_SETCURSOR, wParam, lParam);
|
|
}
|
|
|
|
/***
|
|
* DESCRIPTION:
|
|
* Sets the focus.
|
|
*
|
|
* PARAMETER(S):
|
|
* [I] infoPtr : valid pointer to the listview structure
|
|
* [I] hwndLoseFocus : handle of previously focused window
|
|
*
|
|
* RETURN:
|
|
* Zero
|
|
*/
|
|
static LRESULT LISTVIEW_SetFocus(LISTVIEW_INFO *infoPtr, HWND hwndLoseFocus)
|
|
{
|
|
TRACE("(hwndLoseFocus=%p)\n", hwndLoseFocus);
|
|
|
|
/* if we have the focus already, there's nothing to do */
|
|
if (infoPtr->bFocus) return 0;
|
|
|
|
/* send NM_SETFOCUS notification */
|
|
if (!notify(infoPtr, NM_SETFOCUS)) return 0;
|
|
|
|
/* set window focus flag */
|
|
infoPtr->bFocus = TRUE;
|
|
|
|
/* put the focus rect back on */
|
|
LISTVIEW_ShowFocusRect(infoPtr, TRUE);
|
|
|
|
/* redraw all visible selected items */
|
|
LISTVIEW_InvalidateSelectedItems(infoPtr);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/***
|
|
* DESCRIPTION:
|
|
* Sets the font.
|
|
*
|
|
* PARAMETER(S):
|
|
* [I] infoPtr : valid pointer to the listview structure
|
|
* [I] fRedraw : font handle
|
|
* [I] fRedraw : redraw flag
|
|
*
|
|
* RETURN:
|
|
* Zero
|
|
*/
|
|
static LRESULT LISTVIEW_SetFont(LISTVIEW_INFO *infoPtr, HFONT hFont, WORD fRedraw)
|
|
{
|
|
HFONT oldFont = infoPtr->hFont;
|
|
INT oldHeight = infoPtr->nItemHeight;
|
|
|
|
TRACE("(hfont=%p,redraw=%hu)\n", hFont, fRedraw);
|
|
|
|
infoPtr->hFont = hFont ? hFont : infoPtr->hDefaultFont;
|
|
if (infoPtr->hFont == oldFont) return 0;
|
|
|
|
LISTVIEW_SaveTextMetrics(infoPtr);
|
|
|
|
infoPtr->nItemHeight = LISTVIEW_CalculateItemHeight(infoPtr);
|
|
|
|
if (infoPtr->uView == LV_VIEW_DETAILS)
|
|
{
|
|
SendMessageW(infoPtr->hwndHeader, WM_SETFONT, (WPARAM)hFont, MAKELPARAM(fRedraw, 0));
|
|
LISTVIEW_UpdateSize(infoPtr);
|
|
LISTVIEW_UpdateScroll(infoPtr);
|
|
}
|
|
else if (infoPtr->nItemHeight != oldHeight)
|
|
LISTVIEW_UpdateScroll(infoPtr);
|
|
|
|
if (fRedraw) LISTVIEW_InvalidateList(infoPtr);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/***
|
|
* DESCRIPTION:
|
|
* Message handling for WM_SETREDRAW.
|
|
* For the Listview, it invalidates the entire window (the doc specifies otherwise)
|
|
*
|
|
* PARAMETER(S):
|
|
* [I] infoPtr : valid pointer to the listview structure
|
|
* [I] bRedraw: state of redraw flag
|
|
*
|
|
* RETURN:
|
|
* DefWinProc return value
|
|
*/
|
|
static LRESULT LISTVIEW_SetRedraw(LISTVIEW_INFO *infoPtr, BOOL bRedraw)
|
|
{
|
|
TRACE("infoPtr->bRedraw=%d, bRedraw=%d\n", infoPtr->bRedraw, bRedraw);
|
|
|
|
/* we cannot use straight equality here because _any_ non-zero value is TRUE */
|
|
if ((infoPtr->bRedraw && bRedraw) || (!infoPtr->bRedraw && !bRedraw)) return 0;
|
|
|
|
infoPtr->bRedraw = bRedraw;
|
|
|
|
if(!bRedraw) return 0;
|
|
|
|
if (is_autoarrange(infoPtr))
|
|
LISTVIEW_Arrange(infoPtr, LVA_DEFAULT);
|
|
LISTVIEW_UpdateScroll(infoPtr);
|
|
|
|
/* despite what the WM_SETREDRAW docs says, apps expect us
|
|
* to invalidate the listview here... stupid! */
|
|
LISTVIEW_InvalidateList(infoPtr);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/***
|
|
* DESCRIPTION:
|
|
* Resizes the listview control. This function processes WM_SIZE
|
|
* messages. At this time, the width and height are not used.
|
|
*
|
|
* PARAMETER(S):
|
|
* [I] infoPtr : valid pointer to the listview structure
|
|
* [I] Width : new width
|
|
* [I] Height : new height
|
|
*
|
|
* RETURN:
|
|
* Zero
|
|
*/
|
|
static LRESULT LISTVIEW_Size(LISTVIEW_INFO *infoPtr, int Width, int Height)
|
|
{
|
|
RECT rcOld = infoPtr->rcList;
|
|
|
|
TRACE("(width=%d, height=%d)\n", Width, Height);
|
|
|
|
LISTVIEW_UpdateSize(infoPtr);
|
|
if (EqualRect(&rcOld, &infoPtr->rcList)) return 0;
|
|
|
|
/* do not bother with display related stuff if we're not redrawing */
|
|
if (!is_redrawing(infoPtr)) return 0;
|
|
|
|
if (is_autoarrange(infoPtr))
|
|
LISTVIEW_Arrange(infoPtr, LVA_DEFAULT);
|
|
|
|
LISTVIEW_UpdateScroll(infoPtr);
|
|
|
|
/* refresh all only for lists whose height changed significantly */
|
|
if ((infoPtr->uView == LV_VIEW_LIST) &&
|
|
(rcOld.bottom - rcOld.top) / infoPtr->nItemHeight !=
|
|
(infoPtr->rcList.bottom - infoPtr->rcList.top) / infoPtr->nItemHeight)
|
|
LISTVIEW_InvalidateList(infoPtr);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/***
|
|
* DESCRIPTION:
|
|
* Sets the size information.
|
|
*
|
|
* PARAMETER(S):
|
|
* [I] infoPtr : valid pointer to the listview structure
|
|
*
|
|
* RETURN:
|
|
* None
|
|
*/
|
|
static void LISTVIEW_UpdateSize(LISTVIEW_INFO *infoPtr)
|
|
{
|
|
TRACE("uView=%d, rcList(old)=%s\n", infoPtr->uView, wine_dbgstr_rect(&infoPtr->rcList));
|
|
|
|
GetClientRect(infoPtr->hwndSelf, &infoPtr->rcList);
|
|
|
|
if (infoPtr->uView == LV_VIEW_LIST)
|
|
{
|
|
/* Apparently the "LIST" style is supposed to have the same
|
|
* number of items in a column even if there is no scroll bar.
|
|
* Since if a scroll bar already exists then the bottom is already
|
|
* reduced, only reduce if the scroll bar does not currently exist.
|
|
* The "2" is there to mimic the native control. I think it may be
|
|
* related to either padding or edges. (GLA 7/2002)
|
|
*/
|
|
if (!(GetWindowLongW(infoPtr->hwndSelf, GWL_STYLE) & WS_HSCROLL))
|
|
infoPtr->rcList.bottom -= GetSystemMetrics(SM_CYHSCROLL);
|
|
infoPtr->rcList.bottom = max (infoPtr->rcList.bottom - 2, 0);
|
|
}
|
|
|
|
/* if control created invisible header isn't created */
|
|
if (infoPtr->hwndHeader)
|
|
{
|
|
HDLAYOUT hl;
|
|
WINDOWPOS wp;
|
|
|
|
hl.prc = &infoPtr->rcList;
|
|
hl.pwpos = ℘
|
|
SendMessageW( infoPtr->hwndHeader, HDM_LAYOUT, 0, (LPARAM)&hl );
|
|
TRACE(" wp.flags=0x%08x, wp=%d,%d (%dx%d)\n", wp.flags, wp.x, wp.y, wp.cx, wp.cy);
|
|
|
|
if (LISTVIEW_IsHeaderEnabled(infoPtr))
|
|
wp.flags |= SWP_SHOWWINDOW;
|
|
else
|
|
{
|
|
wp.flags |= SWP_HIDEWINDOW;
|
|
wp.cy = 0;
|
|
}
|
|
|
|
SetWindowPos(wp.hwnd, wp.hwndInsertAfter, wp.x, wp.y, wp.cx, wp.cy, wp.flags);
|
|
TRACE(" after SWP wp=%d,%d (%dx%d)\n", wp.x, wp.y, wp.cx, wp.cy);
|
|
|
|
infoPtr->rcList.top = max(wp.cy, 0);
|
|
}
|
|
/* extra padding for grid */
|
|
if (infoPtr->uView == LV_VIEW_DETAILS && infoPtr->dwLvExStyle & LVS_EX_GRIDLINES)
|
|
infoPtr->rcList.top += 2;
|
|
|
|
TRACE(" rcList=%s\n", wine_dbgstr_rect(&infoPtr->rcList));
|
|
}
|
|
|
|
/***
|
|
* DESCRIPTION:
|
|
* Processes WM_STYLECHANGED messages.
|
|
*
|
|
* PARAMETER(S):
|
|
* [I] infoPtr : valid pointer to the listview structure
|
|
* [I] wStyleType : window style type (normal or extended)
|
|
* [I] lpss : window style information
|
|
*
|
|
* RETURN:
|
|
* Zero
|
|
*/
|
|
static INT LISTVIEW_StyleChanged(LISTVIEW_INFO *infoPtr, WPARAM wStyleType,
|
|
const STYLESTRUCT *lpss)
|
|
{
|
|
UINT uNewView = lpss->styleNew & LVS_TYPEMASK;
|
|
UINT uOldView = lpss->styleOld & LVS_TYPEMASK;
|
|
UINT style;
|
|
|
|
TRACE("(styletype=%lx, styleOld=0x%08x, styleNew=0x%08x)\n",
|
|
wStyleType, lpss->styleOld, lpss->styleNew);
|
|
|
|
if (wStyleType != GWL_STYLE) return 0;
|
|
|
|
infoPtr->dwStyle = lpss->styleNew;
|
|
map_style_view(infoPtr);
|
|
|
|
if (((lpss->styleOld & WS_HSCROLL) != 0)&&
|
|
((lpss->styleNew & WS_HSCROLL) == 0))
|
|
ShowScrollBar(infoPtr->hwndSelf, SB_HORZ, FALSE);
|
|
|
|
if (((lpss->styleOld & WS_VSCROLL) != 0)&&
|
|
((lpss->styleNew & WS_VSCROLL) == 0))
|
|
ShowScrollBar(infoPtr->hwndSelf, SB_VERT, FALSE);
|
|
|
|
if (uNewView != uOldView)
|
|
{
|
|
HIMAGELIST himl;
|
|
|
|
SendMessageW(infoPtr->hwndEdit, WM_KILLFOCUS, 0, 0);
|
|
ShowWindow(infoPtr->hwndHeader, SW_HIDE);
|
|
|
|
ShowScrollBar(infoPtr->hwndSelf, SB_BOTH, FALSE);
|
|
SetRectEmpty(&infoPtr->rcFocus);
|
|
|
|
himl = (uNewView == LVS_ICON ? infoPtr->himlNormal : infoPtr->himlSmall);
|
|
set_icon_size(&infoPtr->iconSize, himl, uNewView != LVS_ICON);
|
|
|
|
if (uNewView == LVS_REPORT)
|
|
{
|
|
HDLAYOUT hl;
|
|
WINDOWPOS wp;
|
|
|
|
LISTVIEW_CreateHeader( infoPtr );
|
|
|
|
hl.prc = &infoPtr->rcList;
|
|
hl.pwpos = ℘
|
|
SendMessageW( infoPtr->hwndHeader, HDM_LAYOUT, 0, (LPARAM)&hl );
|
|
SetWindowPos(infoPtr->hwndHeader, infoPtr->hwndSelf, wp.x, wp.y, wp.cx, wp.cy,
|
|
wp.flags | ((infoPtr->dwStyle & LVS_NOCOLUMNHEADER)
|
|
? SWP_HIDEWINDOW : SWP_SHOWWINDOW));
|
|
}
|
|
|
|
LISTVIEW_UpdateItemSize(infoPtr);
|
|
}
|
|
|
|
if (uNewView == LVS_REPORT || infoPtr->dwLvExStyle & LVS_EX_HEADERINALLVIEWS)
|
|
{
|
|
if ((lpss->styleOld ^ lpss->styleNew) & LVS_NOCOLUMNHEADER)
|
|
{
|
|
if (lpss->styleNew & LVS_NOCOLUMNHEADER)
|
|
{
|
|
/* Turn off the header control */
|
|
style = GetWindowLongW(infoPtr->hwndHeader, GWL_STYLE);
|
|
TRACE("Hide header control, was 0x%08x\n", style);
|
|
SetWindowLongW(infoPtr->hwndHeader, GWL_STYLE, style | HDS_HIDDEN);
|
|
} else {
|
|
/* Turn on the header control */
|
|
if ((style = GetWindowLongW(infoPtr->hwndHeader, GWL_STYLE)) & HDS_HIDDEN)
|
|
{
|
|
TRACE("Show header control, was 0x%08x\n", style);
|
|
SetWindowLongW(infoPtr->hwndHeader, GWL_STYLE, (style & ~HDS_HIDDEN) | WS_VISIBLE);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if ( (uNewView == LVS_ICON || uNewView == LVS_SMALLICON) &&
|
|
(uNewView != uOldView || ((lpss->styleNew ^ lpss->styleOld) & LVS_ALIGNMASK)) )
|
|
LISTVIEW_Arrange(infoPtr, LVA_DEFAULT);
|
|
|
|
/* update the size of the client area */
|
|
LISTVIEW_UpdateSize(infoPtr);
|
|
|
|
/* add scrollbars if needed */
|
|
LISTVIEW_UpdateScroll(infoPtr);
|
|
|
|
/* invalidate client area + erase background */
|
|
LISTVIEW_InvalidateList(infoPtr);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/***
|
|
* DESCRIPTION:
|
|
* Processes WM_STYLECHANGING messages.
|
|
*
|
|
* PARAMETER(S):
|
|
* [I] wStyleType : window style type (normal or extended)
|
|
* [I0] lpss : window style information
|
|
*
|
|
* RETURN:
|
|
* Zero
|
|
*/
|
|
static INT LISTVIEW_StyleChanging(WPARAM wStyleType,
|
|
STYLESTRUCT *lpss)
|
|
{
|
|
TRACE("(styletype=%lx, styleOld=0x%08x, styleNew=0x%08x)\n",
|
|
wStyleType, lpss->styleOld, lpss->styleNew);
|
|
|
|
/* don't forward LVS_OWNERDATA only if not already set to */
|
|
if ((lpss->styleNew ^ lpss->styleOld) & LVS_OWNERDATA)
|
|
{
|
|
if (lpss->styleOld & LVS_OWNERDATA)
|
|
lpss->styleNew |= LVS_OWNERDATA;
|
|
else
|
|
lpss->styleNew &= ~LVS_OWNERDATA;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/***
|
|
* DESCRIPTION:
|
|
* Processes WM_SHOWWINDOW messages.
|
|
*
|
|
* PARAMETER(S):
|
|
* [I] infoPtr : valid pointer to the listview structure
|
|
* [I] bShown : window is being shown (FALSE when hidden)
|
|
* [I] iStatus : window show status
|
|
*
|
|
* RETURN:
|
|
* Zero
|
|
*/
|
|
static LRESULT LISTVIEW_ShowWindow(LISTVIEW_INFO *infoPtr, WPARAM bShown, LPARAM iStatus)
|
|
{
|
|
/* header delayed creation */
|
|
if ((infoPtr->uView == LV_VIEW_DETAILS) && bShown)
|
|
{
|
|
LISTVIEW_CreateHeader(infoPtr);
|
|
|
|
if (!(LVS_NOCOLUMNHEADER & infoPtr->dwStyle))
|
|
ShowWindow(infoPtr->hwndHeader, SW_SHOWNORMAL);
|
|
}
|
|
|
|
return DefWindowProcW(infoPtr->hwndSelf, WM_SHOWWINDOW, bShown, iStatus);
|
|
}
|
|
|
|
/***
|
|
* DESCRIPTION:
|
|
* Processes CCM_GETVERSION messages.
|
|
*
|
|
* PARAMETER(S):
|
|
* [I] infoPtr : valid pointer to the listview structure
|
|
*
|
|
* RETURN:
|
|
* Current version
|
|
*/
|
|
static inline LRESULT LISTVIEW_GetVersion(const LISTVIEW_INFO *infoPtr)
|
|
{
|
|
return infoPtr->iVersion;
|
|
}
|
|
|
|
/***
|
|
* DESCRIPTION:
|
|
* Processes CCM_SETVERSION messages.
|
|
*
|
|
* PARAMETER(S):
|
|
* [I] infoPtr : valid pointer to the listview structure
|
|
* [I] iVersion : version to be set
|
|
*
|
|
* RETURN:
|
|
* -1 when requested version is greater than DLL version;
|
|
* previous version otherwise
|
|
*/
|
|
static LRESULT LISTVIEW_SetVersion(LISTVIEW_INFO *infoPtr, DWORD iVersion)
|
|
{
|
|
INT iOldVersion = infoPtr->iVersion;
|
|
|
|
if (iVersion > COMCTL32_VERSION)
|
|
return -1;
|
|
|
|
infoPtr->iVersion = iVersion;
|
|
|
|
TRACE("new version %d\n", iVersion);
|
|
|
|
return iOldVersion;
|
|
}
|
|
|
|
/***
|
|
* DESCRIPTION:
|
|
* Window procedure of the listview control.
|
|
*
|
|
*/
|
|
static LRESULT WINAPI
|
|
LISTVIEW_WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
|
|
{
|
|
LISTVIEW_INFO *infoPtr = (LISTVIEW_INFO *)GetWindowLongPtrW(hwnd, 0);
|
|
|
|
TRACE("(hwnd=%p uMsg=%x wParam=%lx lParam=%lx)\n", hwnd, uMsg, wParam, lParam);
|
|
|
|
if (!infoPtr && (uMsg != WM_NCCREATE))
|
|
return DefWindowProcW(hwnd, uMsg, wParam, lParam);
|
|
|
|
switch (uMsg)
|
|
{
|
|
case LVM_APPROXIMATEVIEWRECT:
|
|
return LISTVIEW_ApproximateViewRect(infoPtr, (INT)wParam,
|
|
LOWORD(lParam), HIWORD(lParam));
|
|
case LVM_ARRANGE:
|
|
return LISTVIEW_Arrange(infoPtr, (INT)wParam);
|
|
|
|
case LVM_CANCELEDITLABEL:
|
|
return LISTVIEW_CancelEditLabel(infoPtr);
|
|
|
|
case LVM_CREATEDRAGIMAGE:
|
|
return (LRESULT)LISTVIEW_CreateDragImage(infoPtr, (INT)wParam, (LPPOINT)lParam);
|
|
|
|
case LVM_DELETEALLITEMS:
|
|
return LISTVIEW_DeleteAllItems(infoPtr, FALSE);
|
|
|
|
case LVM_DELETECOLUMN:
|
|
return LISTVIEW_DeleteColumn(infoPtr, (INT)wParam);
|
|
|
|
case LVM_DELETEITEM:
|
|
return LISTVIEW_DeleteItem(infoPtr, (INT)wParam);
|
|
|
|
case LVM_EDITLABELA:
|
|
case LVM_EDITLABELW:
|
|
return (LRESULT)LISTVIEW_EditLabelT(infoPtr, (INT)wParam,
|
|
uMsg == LVM_EDITLABELW);
|
|
/* case LVM_ENABLEGROUPVIEW: */
|
|
|
|
case LVM_ENSUREVISIBLE:
|
|
return LISTVIEW_EnsureVisible(infoPtr, (INT)wParam, (BOOL)lParam);
|
|
|
|
case LVM_FINDITEMW:
|
|
return LISTVIEW_FindItemW(infoPtr, (INT)wParam, (LPLVFINDINFOW)lParam);
|
|
|
|
case LVM_FINDITEMA:
|
|
return LISTVIEW_FindItemA(infoPtr, (INT)wParam, (LPLVFINDINFOA)lParam);
|
|
|
|
case LVM_GETBKCOLOR:
|
|
return infoPtr->clrBk;
|
|
|
|
/* case LVM_GETBKIMAGE: */
|
|
|
|
case LVM_GETCALLBACKMASK:
|
|
return infoPtr->uCallbackMask;
|
|
|
|
case LVM_GETCOLUMNA:
|
|
case LVM_GETCOLUMNW:
|
|
return LISTVIEW_GetColumnT(infoPtr, (INT)wParam, (LPLVCOLUMNW)lParam,
|
|
uMsg == LVM_GETCOLUMNW);
|
|
|
|
case LVM_GETCOLUMNORDERARRAY:
|
|
return LISTVIEW_GetColumnOrderArray(infoPtr, (INT)wParam, (LPINT)lParam);
|
|
|
|
case LVM_GETCOLUMNWIDTH:
|
|
return LISTVIEW_GetColumnWidth(infoPtr, (INT)wParam);
|
|
|
|
case LVM_GETCOUNTPERPAGE:
|
|
return LISTVIEW_GetCountPerPage(infoPtr);
|
|
|
|
case LVM_GETEDITCONTROL:
|
|
return (LRESULT)infoPtr->hwndEdit;
|
|
|
|
case LVM_GETEXTENDEDLISTVIEWSTYLE:
|
|
return infoPtr->dwLvExStyle;
|
|
|
|
/* case LVM_GETGROUPINFO: */
|
|
|
|
/* case LVM_GETGROUPMETRICS: */
|
|
|
|
case LVM_GETHEADER:
|
|
return (LRESULT)infoPtr->hwndHeader;
|
|
|
|
case LVM_GETHOTCURSOR:
|
|
return (LRESULT)infoPtr->hHotCursor;
|
|
|
|
case LVM_GETHOTITEM:
|
|
return infoPtr->nHotItem;
|
|
|
|
case LVM_GETHOVERTIME:
|
|
return infoPtr->dwHoverTime;
|
|
|
|
case LVM_GETIMAGELIST:
|
|
return (LRESULT)LISTVIEW_GetImageList(infoPtr, (INT)wParam);
|
|
|
|
/* case LVM_GETINSERTMARK: */
|
|
|
|
/* case LVM_GETINSERTMARKCOLOR: */
|
|
|
|
/* case LVM_GETINSERTMARKRECT: */
|
|
|
|
case LVM_GETISEARCHSTRINGA:
|
|
case LVM_GETISEARCHSTRINGW:
|
|
FIXME("LVM_GETISEARCHSTRING: unimplemented\n");
|
|
return FALSE;
|
|
|
|
case LVM_GETITEMA:
|
|
case LVM_GETITEMW:
|
|
return LISTVIEW_GetItemExtT(infoPtr, (LPLVITEMW)lParam, uMsg == LVM_GETITEMW);
|
|
|
|
case LVM_GETITEMCOUNT:
|
|
return infoPtr->nItemCount;
|
|
|
|
case LVM_GETITEMPOSITION:
|
|
return LISTVIEW_GetItemPosition(infoPtr, (INT)wParam, (LPPOINT)lParam);
|
|
|
|
case LVM_GETITEMRECT:
|
|
return LISTVIEW_GetItemRect(infoPtr, (INT)wParam, (LPRECT)lParam);
|
|
|
|
case LVM_GETITEMSPACING:
|
|
return LISTVIEW_GetItemSpacing(infoPtr, (BOOL)wParam);
|
|
|
|
case LVM_GETITEMSTATE:
|
|
return LISTVIEW_GetItemState(infoPtr, (INT)wParam, (UINT)lParam);
|
|
|
|
case LVM_GETITEMTEXTA:
|
|
case LVM_GETITEMTEXTW:
|
|
return LISTVIEW_GetItemTextT(infoPtr, (INT)wParam, (LPLVITEMW)lParam,
|
|
uMsg == LVM_GETITEMTEXTW);
|
|
|
|
case LVM_GETNEXTITEM:
|
|
return LISTVIEW_GetNextItem(infoPtr, (INT)wParam, LOWORD(lParam));
|
|
|
|
case LVM_GETNUMBEROFWORKAREAS:
|
|
FIXME("LVM_GETNUMBEROFWORKAREAS: unimplemented\n");
|
|
return 1;
|
|
|
|
case LVM_GETORIGIN:
|
|
if (!lParam) return FALSE;
|
|
if (infoPtr->uView == LV_VIEW_DETAILS ||
|
|
infoPtr->uView == LV_VIEW_LIST) return FALSE;
|
|
LISTVIEW_GetOrigin(infoPtr, (LPPOINT)lParam);
|
|
return TRUE;
|
|
|
|
/* case LVM_GETOUTLINECOLOR: */
|
|
|
|
/* case LVM_GETSELECTEDCOLUMN: */
|
|
|
|
case LVM_GETSELECTEDCOUNT:
|
|
return LISTVIEW_GetSelectedCount(infoPtr);
|
|
|
|
case LVM_GETSELECTIONMARK:
|
|
return infoPtr->nSelectionMark;
|
|
|
|
case LVM_GETSTRINGWIDTHA:
|
|
case LVM_GETSTRINGWIDTHW:
|
|
return LISTVIEW_GetStringWidthT(infoPtr, (LPCWSTR)lParam,
|
|
uMsg == LVM_GETSTRINGWIDTHW);
|
|
|
|
case LVM_GETSUBITEMRECT:
|
|
return LISTVIEW_GetSubItemRect(infoPtr, (UINT)wParam, (LPRECT)lParam);
|
|
|
|
case LVM_GETTEXTBKCOLOR:
|
|
return infoPtr->clrTextBk;
|
|
|
|
case LVM_GETTEXTCOLOR:
|
|
return infoPtr->clrText;
|
|
|
|
/* case LVM_GETTILEINFO: */
|
|
|
|
/* case LVM_GETTILEVIEWINFO: */
|
|
|
|
case LVM_GETTOOLTIPS:
|
|
if( !infoPtr->hwndToolTip )
|
|
infoPtr->hwndToolTip = COMCTL32_CreateToolTip( hwnd );
|
|
return (LRESULT)infoPtr->hwndToolTip;
|
|
|
|
case LVM_GETTOPINDEX:
|
|
return LISTVIEW_GetTopIndex(infoPtr);
|
|
|
|
case LVM_GETUNICODEFORMAT:
|
|
return (infoPtr->notifyFormat == NFR_UNICODE);
|
|
|
|
case LVM_GETVIEW:
|
|
return infoPtr->uView;
|
|
|
|
case LVM_GETVIEWRECT:
|
|
return LISTVIEW_GetViewRect(infoPtr, (LPRECT)lParam);
|
|
|
|
case LVM_GETWORKAREAS:
|
|
FIXME("LVM_GETWORKAREAS: unimplemented\n");
|
|
return FALSE;
|
|
|
|
/* case LVM_HASGROUP: */
|
|
|
|
case LVM_HITTEST:
|
|
return LISTVIEW_HitTest(infoPtr, (LPLVHITTESTINFO)lParam, FALSE, TRUE);
|
|
|
|
case LVM_INSERTCOLUMNA:
|
|
case LVM_INSERTCOLUMNW:
|
|
return LISTVIEW_InsertColumnT(infoPtr, (INT)wParam, (LPLVCOLUMNW)lParam,
|
|
uMsg == LVM_INSERTCOLUMNW);
|
|
|
|
/* case LVM_INSERTGROUP: */
|
|
|
|
/* case LVM_INSERTGROUPSORTED: */
|
|
|
|
case LVM_INSERTITEMA:
|
|
case LVM_INSERTITEMW:
|
|
return LISTVIEW_InsertItemT(infoPtr, (LPLVITEMW)lParam, uMsg == LVM_INSERTITEMW);
|
|
|
|
/* case LVM_INSERTMARKHITTEST: */
|
|
|
|
/* case LVM_ISGROUPVIEWENABLED: */
|
|
|
|
case LVM_ISITEMVISIBLE:
|
|
return LISTVIEW_IsItemVisible(infoPtr, (INT)wParam);
|
|
|
|
case LVM_MAPIDTOINDEX:
|
|
return LISTVIEW_MapIdToIndex(infoPtr, (UINT)wParam);
|
|
|
|
case LVM_MAPINDEXTOID:
|
|
return LISTVIEW_MapIndexToId(infoPtr, (INT)wParam);
|
|
|
|
/* case LVM_MOVEGROUP: */
|
|
|
|
/* case LVM_MOVEITEMTOGROUP: */
|
|
|
|
case LVM_REDRAWITEMS:
|
|
return LISTVIEW_RedrawItems(infoPtr, (INT)wParam, (INT)lParam);
|
|
|
|
/* case LVM_REMOVEALLGROUPS: */
|
|
|
|
/* case LVM_REMOVEGROUP: */
|
|
|
|
case LVM_SCROLL:
|
|
return LISTVIEW_Scroll(infoPtr, (INT)wParam, (INT)lParam);
|
|
|
|
case LVM_SETBKCOLOR:
|
|
return LISTVIEW_SetBkColor(infoPtr, (COLORREF)lParam);
|
|
|
|
/* case LVM_SETBKIMAGE: */
|
|
|
|
case LVM_SETCALLBACKMASK:
|
|
infoPtr->uCallbackMask = (UINT)wParam;
|
|
return TRUE;
|
|
|
|
case LVM_SETCOLUMNA:
|
|
case LVM_SETCOLUMNW:
|
|
return LISTVIEW_SetColumnT(infoPtr, (INT)wParam, (LPLVCOLUMNW)lParam,
|
|
uMsg == LVM_SETCOLUMNW);
|
|
|
|
case LVM_SETCOLUMNORDERARRAY:
|
|
return LISTVIEW_SetColumnOrderArray(infoPtr, (INT)wParam, (LPINT)lParam);
|
|
|
|
case LVM_SETCOLUMNWIDTH:
|
|
return LISTVIEW_SetColumnWidth(infoPtr, (INT)wParam, (short)LOWORD(lParam));
|
|
|
|
case LVM_SETEXTENDEDLISTVIEWSTYLE:
|
|
return LISTVIEW_SetExtendedListViewStyle(infoPtr, (DWORD)wParam, (DWORD)lParam);
|
|
|
|
/* case LVM_SETGROUPINFO: */
|
|
|
|
/* case LVM_SETGROUPMETRICS: */
|
|
|
|
case LVM_SETHOTCURSOR:
|
|
return (LRESULT)LISTVIEW_SetHotCursor(infoPtr, (HCURSOR)lParam);
|
|
|
|
case LVM_SETHOTITEM:
|
|
return LISTVIEW_SetHotItem(infoPtr, (INT)wParam);
|
|
|
|
case LVM_SETHOVERTIME:
|
|
return LISTVIEW_SetHoverTime(infoPtr, (DWORD)lParam);
|
|
|
|
case LVM_SETICONSPACING:
|
|
if(lParam == -1)
|
|
return LISTVIEW_SetIconSpacing(infoPtr, -1, -1);
|
|
return LISTVIEW_SetIconSpacing(infoPtr, LOWORD(lParam), HIWORD(lParam));
|
|
|
|
case LVM_SETIMAGELIST:
|
|
return (LRESULT)LISTVIEW_SetImageList(infoPtr, (INT)wParam, (HIMAGELIST)lParam);
|
|
|
|
/* case LVM_SETINFOTIP: */
|
|
|
|
/* case LVM_SETINSERTMARK: */
|
|
|
|
/* case LVM_SETINSERTMARKCOLOR: */
|
|
|
|
case LVM_SETITEMA:
|
|
case LVM_SETITEMW:
|
|
{
|
|
if (infoPtr->dwStyle & LVS_OWNERDATA) return FALSE;
|
|
return LISTVIEW_SetItemT(infoPtr, (LPLVITEMW)lParam, (uMsg == LVM_SETITEMW));
|
|
}
|
|
|
|
case LVM_SETITEMCOUNT:
|
|
return LISTVIEW_SetItemCount(infoPtr, (INT)wParam, (DWORD)lParam);
|
|
|
|
case LVM_SETITEMPOSITION:
|
|
{
|
|
POINT pt;
|
|
pt.x = (short)LOWORD(lParam);
|
|
pt.y = (short)HIWORD(lParam);
|
|
return LISTVIEW_SetItemPosition(infoPtr, (INT)wParam, &pt);
|
|
}
|
|
|
|
case LVM_SETITEMPOSITION32:
|
|
return LISTVIEW_SetItemPosition(infoPtr, (INT)wParam, (POINT*)lParam);
|
|
|
|
case LVM_SETITEMSTATE:
|
|
return LISTVIEW_SetItemState(infoPtr, (INT)wParam, (LPLVITEMW)lParam);
|
|
|
|
case LVM_SETITEMTEXTA:
|
|
case LVM_SETITEMTEXTW:
|
|
return LISTVIEW_SetItemTextT(infoPtr, (INT)wParam, (LPLVITEMW)lParam,
|
|
uMsg == LVM_SETITEMTEXTW);
|
|
|
|
/* case LVM_SETOUTLINECOLOR: */
|
|
|
|
/* case LVM_SETSELECTEDCOLUMN: */
|
|
|
|
case LVM_SETSELECTIONMARK:
|
|
return LISTVIEW_SetSelectionMark(infoPtr, (INT)lParam);
|
|
|
|
case LVM_SETTEXTBKCOLOR:
|
|
return LISTVIEW_SetTextBkColor(infoPtr, (COLORREF)lParam);
|
|
|
|
case LVM_SETTEXTCOLOR:
|
|
return LISTVIEW_SetTextColor(infoPtr, (COLORREF)lParam);
|
|
|
|
/* case LVM_SETTILEINFO: */
|
|
|
|
/* case LVM_SETTILEVIEWINFO: */
|
|
|
|
/* case LVM_SETTILEWIDTH: */
|
|
|
|
case LVM_SETTOOLTIPS:
|
|
return (LRESULT)LISTVIEW_SetToolTips(infoPtr, (HWND)lParam);
|
|
|
|
case LVM_SETUNICODEFORMAT:
|
|
return LISTVIEW_SetUnicodeFormat(infoPtr, wParam);
|
|
|
|
case LVM_SETVIEW:
|
|
return LISTVIEW_SetView(infoPtr, wParam);
|
|
|
|
/* case LVM_SETWORKAREAS: */
|
|
|
|
/* case LVM_SORTGROUPS: */
|
|
|
|
case LVM_SORTITEMS:
|
|
case LVM_SORTITEMSEX:
|
|
return LISTVIEW_SortItems(infoPtr, (PFNLVCOMPARE)lParam, wParam,
|
|
uMsg == LVM_SORTITEMSEX);
|
|
case LVM_SUBITEMHITTEST:
|
|
return LISTVIEW_HitTest(infoPtr, (LPLVHITTESTINFO)lParam, TRUE, FALSE);
|
|
|
|
case LVM_UPDATE:
|
|
return LISTVIEW_Update(infoPtr, (INT)wParam);
|
|
|
|
case CCM_GETVERSION:
|
|
return LISTVIEW_GetVersion(infoPtr);
|
|
|
|
case CCM_SETVERSION:
|
|
return LISTVIEW_SetVersion(infoPtr, wParam);
|
|
|
|
case WM_CHAR:
|
|
return LISTVIEW_ProcessLetterKeys( infoPtr, wParam, lParam );
|
|
|
|
case WM_COMMAND:
|
|
return LISTVIEW_Command(infoPtr, wParam, lParam);
|
|
|
|
case WM_NCCREATE:
|
|
return LISTVIEW_NCCreate(hwnd, (LPCREATESTRUCTW)lParam);
|
|
|
|
case WM_CREATE:
|
|
return LISTVIEW_Create(hwnd, (LPCREATESTRUCTW)lParam);
|
|
|
|
case WM_DESTROY:
|
|
return LISTVIEW_Destroy(infoPtr);
|
|
|
|
case WM_ENABLE:
|
|
return LISTVIEW_Enable(infoPtr);
|
|
|
|
case WM_ERASEBKGND:
|
|
return LISTVIEW_EraseBkgnd(infoPtr, (HDC)wParam);
|
|
|
|
case WM_GETDLGCODE:
|
|
return DLGC_WANTCHARS | DLGC_WANTARROWS;
|
|
|
|
case WM_GETFONT:
|
|
return (LRESULT)infoPtr->hFont;
|
|
|
|
case WM_HSCROLL:
|
|
return LISTVIEW_HScroll(infoPtr, (INT)LOWORD(wParam), 0);
|
|
|
|
case WM_KEYDOWN:
|
|
return LISTVIEW_KeyDown(infoPtr, (INT)wParam, (LONG)lParam);
|
|
|
|
case WM_KILLFOCUS:
|
|
return LISTVIEW_KillFocus(infoPtr);
|
|
|
|
case WM_LBUTTONDBLCLK:
|
|
return LISTVIEW_LButtonDblClk(infoPtr, (WORD)wParam, (SHORT)LOWORD(lParam), (SHORT)HIWORD(lParam));
|
|
|
|
case WM_LBUTTONDOWN:
|
|
return LISTVIEW_LButtonDown(infoPtr, (WORD)wParam, (SHORT)LOWORD(lParam), (SHORT)HIWORD(lParam));
|
|
|
|
case WM_LBUTTONUP:
|
|
return LISTVIEW_LButtonUp(infoPtr, (WORD)wParam, (SHORT)LOWORD(lParam), (SHORT)HIWORD(lParam));
|
|
|
|
case WM_MOUSEMOVE:
|
|
return LISTVIEW_MouseMove (infoPtr, (WORD)wParam, (SHORT)LOWORD(lParam), (SHORT)HIWORD(lParam));
|
|
|
|
case WM_MOUSEHOVER:
|
|
return LISTVIEW_MouseHover(infoPtr, (SHORT)LOWORD(lParam), (SHORT)HIWORD(lParam));
|
|
|
|
case WM_NCDESTROY:
|
|
return LISTVIEW_NCDestroy(infoPtr);
|
|
|
|
case WM_NCPAINT:
|
|
return LISTVIEW_NCPaint(infoPtr, (HRGN)wParam);
|
|
|
|
case WM_NOTIFY:
|
|
return LISTVIEW_Notify(infoPtr, (LPNMHDR)lParam);
|
|
|
|
case WM_NOTIFYFORMAT:
|
|
return LISTVIEW_NotifyFormat(infoPtr, (HWND)wParam, (INT)lParam);
|
|
|
|
case WM_PRINTCLIENT:
|
|
return LISTVIEW_PrintClient(infoPtr, (HDC)wParam, (DWORD)lParam);
|
|
|
|
case WM_PAINT:
|
|
return LISTVIEW_WMPaint(infoPtr, (HDC)wParam);
|
|
|
|
case WM_RBUTTONDBLCLK:
|
|
return LISTVIEW_RButtonDblClk(infoPtr, (WORD)wParam, (SHORT)LOWORD(lParam), (SHORT)HIWORD(lParam));
|
|
|
|
case WM_RBUTTONDOWN:
|
|
return LISTVIEW_RButtonDown(infoPtr, (WORD)wParam, (SHORT)LOWORD(lParam), (SHORT)HIWORD(lParam));
|
|
|
|
case WM_SETCURSOR:
|
|
return LISTVIEW_SetCursor(infoPtr, wParam, lParam);
|
|
|
|
case WM_SETFOCUS:
|
|
return LISTVIEW_SetFocus(infoPtr, (HWND)wParam);
|
|
|
|
case WM_SETFONT:
|
|
return LISTVIEW_SetFont(infoPtr, (HFONT)wParam, (WORD)lParam);
|
|
|
|
case WM_SETREDRAW:
|
|
return LISTVIEW_SetRedraw(infoPtr, (BOOL)wParam);
|
|
|
|
case WM_SHOWWINDOW:
|
|
return LISTVIEW_ShowWindow(infoPtr, wParam, lParam);
|
|
|
|
case WM_STYLECHANGED:
|
|
return LISTVIEW_StyleChanged(infoPtr, wParam, (LPSTYLESTRUCT)lParam);
|
|
|
|
case WM_STYLECHANGING:
|
|
return LISTVIEW_StyleChanging(wParam, (LPSTYLESTRUCT)lParam);
|
|
|
|
case WM_SYSCOLORCHANGE:
|
|
COMCTL32_RefreshSysColors();
|
|
return 0;
|
|
|
|
/* case WM_TIMER: */
|
|
case WM_THEMECHANGED:
|
|
return LISTVIEW_ThemeChanged(infoPtr);
|
|
|
|
case WM_VSCROLL:
|
|
return LISTVIEW_VScroll(infoPtr, (INT)LOWORD(wParam), 0);
|
|
|
|
case WM_MOUSEWHEEL:
|
|
if (wParam & (MK_SHIFT | MK_CONTROL))
|
|
return DefWindowProcW(hwnd, uMsg, wParam, lParam);
|
|
return LISTVIEW_MouseWheel(infoPtr, (short int)HIWORD(wParam));
|
|
|
|
case WM_WINDOWPOSCHANGED:
|
|
if (!(((WINDOWPOS *)lParam)->flags & SWP_NOSIZE))
|
|
{
|
|
SetWindowPos(infoPtr->hwndSelf, 0, 0, 0, 0, 0, SWP_FRAMECHANGED | SWP_NOACTIVATE |
|
|
SWP_NOZORDER | SWP_NOMOVE | SWP_NOSIZE);
|
|
|
|
if ((infoPtr->dwStyle & LVS_OWNERDRAWFIXED) && (infoPtr->uView == LV_VIEW_DETAILS))
|
|
{
|
|
if (notify_measureitem(infoPtr)) LISTVIEW_InvalidateList(infoPtr);
|
|
}
|
|
LISTVIEW_Size(infoPtr, ((WINDOWPOS *)lParam)->cx, ((WINDOWPOS *)lParam)->cy);
|
|
}
|
|
return DefWindowProcW(hwnd, uMsg, wParam, lParam);
|
|
|
|
/* case WM_WININICHANGE: */
|
|
|
|
default:
|
|
if ((uMsg >= WM_USER) && (uMsg < WM_APP) && !COMCTL32_IsReflectedMessage(uMsg))
|
|
ERR("unknown msg %04x wp=%08lx lp=%08lx\n", uMsg, wParam, lParam);
|
|
|
|
return DefWindowProcW(hwnd, uMsg, wParam, lParam);
|
|
}
|
|
|
|
}
|
|
|
|
/***
|
|
* DESCRIPTION:
|
|
* Registers the window class.
|
|
*
|
|
* PARAMETER(S):
|
|
* None
|
|
*
|
|
* RETURN:
|
|
* None
|
|
*/
|
|
void LISTVIEW_Register(void)
|
|
{
|
|
WNDCLASSW wndClass;
|
|
|
|
ZeroMemory(&wndClass, sizeof(WNDCLASSW));
|
|
wndClass.style = CS_GLOBALCLASS | CS_DBLCLKS;
|
|
wndClass.lpfnWndProc = LISTVIEW_WindowProc;
|
|
wndClass.cbClsExtra = 0;
|
|
wndClass.cbWndExtra = sizeof(LISTVIEW_INFO *);
|
|
wndClass.hCursor = LoadCursorW(0, (LPWSTR)IDC_ARROW);
|
|
wndClass.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
|
|
wndClass.lpszClassName = WC_LISTVIEWW;
|
|
RegisterClassW(&wndClass);
|
|
}
|
|
|
|
/***
|
|
* DESCRIPTION:
|
|
* Unregisters the window class.
|
|
*
|
|
* PARAMETER(S):
|
|
* None
|
|
*
|
|
* RETURN:
|
|
* None
|
|
*/
|
|
void LISTVIEW_Unregister(void)
|
|
{
|
|
UnregisterClassW(WC_LISTVIEWW, NULL);
|
|
}
|
|
|
|
/***
|
|
* DESCRIPTION:
|
|
* Handle any WM_COMMAND messages
|
|
*
|
|
* PARAMETER(S):
|
|
* [I] infoPtr : valid pointer to the listview structure
|
|
* [I] wParam : the first message parameter
|
|
* [I] lParam : the second message parameter
|
|
*
|
|
* RETURN:
|
|
* Zero.
|
|
*/
|
|
static LRESULT LISTVIEW_Command(LISTVIEW_INFO *infoPtr, WPARAM wParam, LPARAM lParam)
|
|
{
|
|
|
|
TRACE("(%p %x %x %lx)\n", infoPtr, HIWORD(wParam), LOWORD(wParam), lParam);
|
|
|
|
if (!infoPtr->hwndEdit) return 0;
|
|
|
|
switch (HIWORD(wParam))
|
|
{
|
|
case EN_UPDATE:
|
|
{
|
|
/*
|
|
* Adjust the edit window size
|
|
*/
|
|
WCHAR buffer[1024];
|
|
HDC hdc = GetDC(infoPtr->hwndEdit);
|
|
HFONT hFont, hOldFont = 0;
|
|
RECT rect;
|
|
SIZE sz;
|
|
|
|
if (!infoPtr->hwndEdit || !hdc) return 0;
|
|
GetWindowTextW(infoPtr->hwndEdit, buffer, sizeof(buffer)/sizeof(buffer[0]));
|
|
GetWindowRect(infoPtr->hwndEdit, &rect);
|
|
|
|
/* Select font to get the right dimension of the string */
|
|
hFont = (HFONT)SendMessageW(infoPtr->hwndEdit, WM_GETFONT, 0, 0);
|
|
if (hFont)
|
|
{
|
|
hOldFont = SelectObject(hdc, hFont);
|
|
}
|
|
|
|
if (GetTextExtentPoint32W(hdc, buffer, lstrlenW(buffer), &sz))
|
|
{
|
|
TEXTMETRICW textMetric;
|
|
|
|
/* Add Extra spacing for the next character */
|
|
GetTextMetricsW(hdc, &textMetric);
|
|
sz.cx += (textMetric.tmMaxCharWidth * 2);
|
|
|
|
SetWindowPos(infoPtr->hwndEdit, NULL, 0, 0, sz.cx,
|
|
rect.bottom - rect.top, SWP_DRAWFRAME | SWP_NOMOVE | SWP_NOZORDER);
|
|
}
|
|
if (hFont)
|
|
SelectObject(hdc, hOldFont);
|
|
|
|
ReleaseDC(infoPtr->hwndEdit, hdc);
|
|
|
|
break;
|
|
}
|
|
case EN_KILLFOCUS:
|
|
{
|
|
LISTVIEW_CancelEditLabel(infoPtr);
|
|
break;
|
|
}
|
|
|
|
default:
|
|
return SendMessageW (infoPtr->hwndNotify, WM_COMMAND, wParam, lParam);
|
|
}
|
|
|
|
return 0;
|
|
}
|