Mercurial > projects > dwt2
diff org.eclipse.swt.win32.win32.x86/src/org/eclipse/swt/widgets/Tree.d @ 0:6dd524f61e62
add dwt win and basic java stuff
author | Frank Benoit <benoit@tionex.de> |
---|---|
date | Mon, 02 Mar 2009 14:44:16 +0100 |
parents | |
children | 2e09b0e6857a |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/org.eclipse.swt.win32.win32.x86/src/org/eclipse/swt/widgets/Tree.d Mon Mar 02 14:44:16 2009 +0100 @@ -0,0 +1,7821 @@ +/******************************************************************************* + * Copyright (c) 2000, 2008 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * IBM Corporation - initial API and implementation + * Port to the D programming language: + * Frank Benoit <benoit@tionex.de> + *******************************************************************************/ +module org.eclipse.swt.widgets.Tree; + +import org.eclipse.swt.SWT; +import org.eclipse.swt.SWTException; +import org.eclipse.swt.events.SelectionEvent; +import org.eclipse.swt.events.SelectionListener; +import org.eclipse.swt.events.TreeListener; +import org.eclipse.swt.graphics.Cursor; +import org.eclipse.swt.graphics.Font; +import org.eclipse.swt.graphics.GC; +import org.eclipse.swt.graphics.GCData; +import org.eclipse.swt.graphics.Image; +import org.eclipse.swt.graphics.Point; +import org.eclipse.swt.graphics.Rectangle; +import org.eclipse.swt.internal.ImageList; +import org.eclipse.swt.internal.win32.OS; + +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.TreeItem; +import org.eclipse.swt.widgets.TreeColumn; +import org.eclipse.swt.widgets.TypedListener; +import org.eclipse.swt.widgets.Event; +import org.eclipse.swt.widgets.Display; +import org.eclipse.swt.widgets.Shell; +import org.eclipse.swt.widgets.Control; +import org.eclipse.swt.widgets.Listener; +import org.eclipse.swt.widgets.Widget; +import org.eclipse.swt.widgets.Table; + +import java.lang.all; + + +/** + * Instances of this class provide a selectable user interface object + * that displays a hierarchy of items and issues notification when an + * item in the hierarchy is selected. + * <p> + * The item children that may be added to instances of this class + * must be of type <code>TreeItem</code>. + * </p><p> + * Style <code>VIRTUAL</code> is used to create a <code>Tree</code> whose + * <code>TreeItem</code>s are to be populated by the client on an on-demand basis + * instead of up-front. This can provide significant performance improvements for + * trees that are very large or for which <code>TreeItem</code> population is + * expensive (for example, retrieving values from an external source). + * </p><p> + * Here is an example of using a <code>Tree</code> with style <code>VIRTUAL</code>: + * <code><pre> + * final Tree tree = new Tree(parent, SWT.VIRTUAL | SWT.BORDER); + * tree.setItemCount(20); + * tree.addListener(SWT.SetData, new Listener() { + * public void handleEvent(Event event) { + * TreeItem item = (TreeItem)event.item; + * TreeItem parentItem = item.getParentItem(); + * String text = null; + * if (parentItem is null) { + * text = "node " + tree.indexOf(item); + * } else { + * text = parentItem.getText() + " - " + parentItem.indexOf(item); + * } + * item.setText(text); + * System.out.println(text); + * item.setItemCount(10); + * } + * }); + * </pre></code> + * </p><p> + * Note that although this class is a subclass of <code>Composite</code>, + * it does not normally make sense to add <code>Control</code> children to + * it, or set a layout on it, unless implementing something like a cell + * editor. + * </p><p> + * <dl> + * <dt><b>Styles:</b></dt> + * <dd>SINGLE, MULTI, CHECK, FULL_SELECTION, VIRTUAL, NO_SCROLL</dd> + * <dt><b>Events:</b></dt> + * <dd>Selection, DefaultSelection, Collapse, Expand, SetData, MeasureItem, EraseItem, PaintItem</dd> + * </dl> + * </p><p> + * Note: Only one of the styles SINGLE and MULTI may be specified. + * </p><p> + * IMPORTANT: This class is <em>not</em> intended to be subclassed. + * </p> + * + * @see <a href="http://www.eclipse.org/swt/snippets/#tree">Tree, TreeItem, TreeColumn snippets</a> + * @see <a href="http://www.eclipse.org/swt/examples.php">SWT Example: ControlExample</a> + * @see <a href="http://www.eclipse.org/swt/">Sample code and further information</a> + */ +public class Tree : Composite { + + alias Composite.computeSize computeSize; + alias Composite.setBackgroundImage setBackgroundImage; + alias Composite.setBounds setBounds; + alias Composite.setCursor setCursor; + alias Composite.sort sort; + + TreeItem [] items; + TreeColumn [] columns; + int columnCount; + ImageList imageList, headerImageList; + TreeItem currentItem; + TreeColumn sortColumn; + RECT* focusRect; + HWND hwndParent, hwndHeader; + HANDLE hAnchor, hInsert, hSelect; + int lastID; + HANDLE hFirstIndexOf, hLastIndexOf; + int lastIndexOf, itemCount, sortDirection; + bool dragStarted, gestureCompleted, insertAfter, shrink, ignoreShrink; + bool ignoreSelect, ignoreExpand, ignoreDeselect, ignoreResize; + bool lockSelection, oldSelected, newSelected, ignoreColumnMove; + bool linesVisible, customDraw, printClient, painted, ignoreItemHeight; + bool ignoreCustomDraw, ignoreDrawForeground, ignoreDrawBackground, ignoreDrawFocus; + bool ignoreDrawSelection, ignoreDrawHot, ignoreFullSelection, explorerTheme; + int scrollWidth, selectionForeground; + HANDLE headerToolTipHandle, itemToolTipHandle; + static const int INSET = 3; + static const int GRID_WIDTH = 1; + static const int SORT_WIDTH = 10; + static const int HEADER_MARGIN = 12; + static const int HEADER_EXTRA = 3; + static const int INCREMENT = 5; + static const int EXPLORER_EXTRA = 2; + static const int DRAG_IMAGE_SIZE = 301; + static const bool EXPLORER_THEME = true; + private static /+const+/ WNDPROC TreeProc; + static const TCHAR[] TreeClass = OS.WC_TREEVIEW; + private static /+const+/ WNDPROC HeaderProc; + static const TCHAR[] HeaderClass = OS.WC_HEADER; + + private static bool static_this_completed = false; + private static void static_this() { + if( static_this_completed ){ + return; + } + synchronized { + if( static_this_completed ){ + return; + } + WNDCLASS lpWndClass; + OS.GetClassInfo (null, TreeClass.ptr, &lpWndClass); + TreeProc = lpWndClass.lpfnWndProc; + OS.GetClassInfo (null, HeaderClass.ptr, &lpWndClass); + HeaderProc = lpWndClass.lpfnWndProc; + static_this_completed = true; + } + } + + private static Tree sThis; +/** + * Constructs a new instance of this class given its parent + * and a style value describing its behavior and appearance. + * <p> + * The style value is either one of the style constants defined in + * class <code>SWT</code> which is applicable to instances of this + * class, or must be built by <em>bitwise OR</em>'ing together + * (that is, using the <code>int</code> "|" operator) two or more + * of those <code>SWT</code> style constants. The class description + * lists the style constants that are applicable to the class. + * Style bits are also inherited from superclasses. + * </p> + * + * @param parent a composite control which will be the parent of the new instance (cannot be null) + * @param style the style of control to construct + * + * @exception IllegalArgumentException <ul> + * <li>ERROR_NULL_ARGUMENT - if the parent is null</li> + * </ul> + * @exception SWTException <ul> + * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the parent</li> + * <li>ERROR_INVALID_SUBCLASS - if this class is not an allowed subclass</li> + * </ul> + * + * @see SWT#SINGLE + * @see SWT#MULTI + * @see SWT#CHECK + * @see SWT#FULL_SELECTION + * @see SWT#VIRTUAL + * @see SWT#NO_SCROLL + * @see Widget#checkSubclass + * @see Widget#getStyle + */ +public this (Composite parent, int style) { + static_this(); + super (parent, checkStyle (style)); +} + +static int checkStyle (int style) { + /* + * Feature in Windows. Even when WS_HSCROLL or + * WS_VSCROLL is not specified, Windows creates + * trees and tables with scroll bars. The fix + * is to set H_SCROLL and V_SCROLL. + * + * NOTE: This code appears on all platforms so that + * applications have consistent scroll bar behavior. + */ + if ((style & SWT.NO_SCROLL) is 0) { + style |= SWT.H_SCROLL | SWT.V_SCROLL; + } + /* + * Note: Windows only supports TVS_NOSCROLL and TVS_NOHSCROLL. + */ + if ((style & SWT.H_SCROLL) !is 0 && (style & SWT.V_SCROLL) is 0) { + style |= SWT.V_SCROLL; + } + return checkBits (style, SWT.SINGLE, SWT.MULTI, 0, 0, 0, 0); +} + +override void _addListener (int eventType, Listener listener) { + super._addListener (eventType, listener); + switch (eventType) { + case SWT.DragDetect: { + if ((state & DRAG_DETECT) !is 0) { + int bits = OS.GetWindowLong (handle, OS.GWL_STYLE); + bits &= ~OS.TVS_DISABLEDRAGDROP; + OS.SetWindowLong (handle, OS.GWL_STYLE, bits); + } + break; + } + case SWT.MeasureItem: + case SWT.EraseItem: + case SWT.PaintItem: { + customDraw = true; + style |= SWT.DOUBLE_BUFFERED; + if (isCustomToolTip ()) createItemToolTips (); + OS.SendMessage (handle, OS.TVM_SETSCROLLTIME, 0, 0); + int bits = OS.GetWindowLong (handle, OS.GWL_STYLE); + if (eventType is SWT.MeasureItem) { + /* + * This code is intentionally commented. + */ +// if (explorerTheme) { +// int bits1 = (int)/*64*/OS.SendMessage (handle, OS.TVM_GETEXTENDEDSTYLE, 0, 0); +// bits1 &= ~OS.TVS_EX_AUTOHSCROLL; +// OS.SendMessage (handle, OS.TVM_SETEXTENDEDSTYLE, 0, bits1); +// } + bits |= OS.TVS_NOHSCROLL; + } + /* + * Feature in Windows. When the tree has the style + * TVS_FULLROWSELECT, the background color for the + * entire row is filled when an item is painted, + * drawing on top of any custom drawing. The fix + * is to clear TVS_FULLROWSELECT. + */ + if ((style & SWT.FULL_SELECTION) !is 0) { + if (eventType !is SWT.MeasureItem) { + if (!explorerTheme) bits &= ~OS.TVS_FULLROWSELECT; + } + } + if (bits !is OS.GetWindowLong (handle, OS.GWL_STYLE)) { + OS.SetWindowLong (handle, OS.GWL_STYLE, bits); + OS.InvalidateRect (handle, null, true); + /* + * Bug in Windows. When TVS_NOHSCROLL is set after items + * have been inserted into the tree, Windows shows the + * scroll bar. The fix is to check for this case and + * explicitly hide the scroll bar. + */ + int count = OS.SendMessage (handle, OS.TVM_GETCOUNT, 0, 0); + if (count !is 0 && (bits & OS.TVS_NOHSCROLL) !is 0) { + static if (!OS.IsWinCE) OS.ShowScrollBar (handle, OS.SB_HORZ, false); + } + } + break; + } + default: + } +} + +TreeItem _getItem (HANDLE hItem) { + TVITEM tvItem; + tvItem.mask = OS.TVIF_HANDLE | OS.TVIF_PARAM; + tvItem.hItem = cast(HTREEITEM)hItem; + if (OS.SendMessage (handle, OS.TVM_GETITEM, 0, &tvItem) !is 0) { + return _getItem (tvItem.hItem, tvItem.lParam); + } + return null; +} + +TreeItem _getItem (HANDLE hItem, int id) { + if ((style & SWT.VIRTUAL) is 0) return items [id]; + return id !is -1 ? items [id] : new TreeItem (this, SWT.NONE, cast(HANDLE)-1, cast(HANDLE)-1, hItem); +} + +void _setBackgroundPixel (int newPixel) { + int oldPixel = OS.SendMessage (handle, OS.TVM_GETBKCOLOR, 0, 0); + if (oldPixel !is newPixel) { + /* + * Bug in Windows. When TVM_SETBKCOLOR is used more + * than once to set the background color of a tree, + * the background color of the lines and the plus/minus + * does not change to the new color. The fix is to set + * the background color to the default before setting + * the new color. + */ + if (oldPixel !is -1) { + OS.SendMessage (handle, OS.TVM_SETBKCOLOR, 0, -1); + } + + /* Set the background color */ + OS.SendMessage (handle, OS.TVM_SETBKCOLOR, 0, newPixel); + + /* + * Feature in Windows. When TVM_SETBKCOLOR is used to + * set the background color of a tree, the plus/minus + * animation draws badly. The fix is to clear the effect. + */ + if (explorerTheme) { + int bits2 = OS.SendMessage (handle, OS.TVM_GETEXTENDEDSTYLE, 0, 0); + if (newPixel is -1 && findImageControl () is null) { + bits2 |= OS.TVS_EX_FADEINOUTEXPANDOS; + } else { + bits2 &= ~OS.TVS_EX_FADEINOUTEXPANDOS; + } + OS.SendMessage (handle, OS.TVM_SETEXTENDEDSTYLE, 0, bits2); + } + + /* Set the checkbox image list */ + if ((style & SWT.CHECK) !is 0) setCheckboxImageList (); + } +} + +/** + * Adds the listener to the collection of listeners who will + * be notified when the user changes the receiver's selection, by sending + * it one of the messages defined in the <code>SelectionListener</code> + * interface. + * <p> + * When <code>widgetSelected</code> is called, the item field of the event object is valid. + * If the receiver has the <code>SWT.CHECK</code> style and the check selection changes, + * the event object detail field contains the value <code>SWT.CHECK</code>. + * <code>widgetDefaultSelected</code> is typically called when an item is double-clicked. + * The item field of the event object is valid for default selection, but the detail field is not used. + * </p> + * + * @param listener the listener which should be notified when the user changes the receiver's selection + * + * @exception IllegalArgumentException <ul> + * <li>ERROR_NULL_ARGUMENT - if the listener is null</li> + * </ul> + * @exception SWTException <ul> + * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> + * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> + * </ul> + * + * @see SelectionListener + * @see #removeSelectionListener + * @see SelectionEvent + */ +public void addSelectionListener(SelectionListener listener) { + checkWidget (); + if (listener is null) error (SWT.ERROR_NULL_ARGUMENT); + TypedListener typedListener = new TypedListener (listener); + addListener (SWT.Selection, typedListener); + addListener (SWT.DefaultSelection, typedListener); +} + +/** + * Adds the listener to the collection of listeners who will + * be notified when an item in the receiver is expanded or collapsed + * by sending it one of the messages defined in the <code>TreeListener</code> + * interface. + * + * @param listener the listener which should be notified + * + * @exception IllegalArgumentException <ul> + * <li>ERROR_NULL_ARGUMENT - if the listener is null</li> + * </ul> + * @exception SWTException <ul> + * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> + * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> + * </ul> + * + * @see TreeListener + * @see #removeTreeListener + */ +public void addTreeListener(TreeListener listener) { + checkWidget (); + if (listener is null) error (SWT.ERROR_NULL_ARGUMENT); + TypedListener typedListener = new TypedListener (listener); + addListener (SWT.Expand, typedListener); + addListener (SWT.Collapse, typedListener); +} + +override HWND borderHandle () { + return hwndParent !is null ? hwndParent : handle; +} + +LRESULT CDDS_ITEMPOSTPAINT (NMTVCUSTOMDRAW* nmcd, int wParam, int lParam) { + if (ignoreCustomDraw) return null; + if (nmcd.nmcd.rc.left is nmcd.nmcd.rc.right) return new LRESULT (OS.CDRF_DODEFAULT); + auto hDC = nmcd.nmcd.hdc; + OS.RestoreDC (hDC, -1); + TreeItem item = getItem (nmcd); + + /* + * Feature in Windows. When a new tree item is inserted + * using TVM_INSERTITEM and the tree is using custom draw, + * a NM_CUSTOMDRAW is sent before TVM_INSERTITEM returns + * and before the item is added to the items array. The + * fix is to check for null. + * + * NOTE: This only happens on XP with the version 6.00 of + * COMCTL32.DLL, + */ + if (item is null) return null; + + /* + * Feature in Windows. Under certain circumstances, Windows + * sends CDDS_ITEMPOSTPAINT for an empty rectangle. This is + * not a problem providing that graphics do not occur outside + * the rectangle. The fix is to test for the rectangle and + * draw nothing. + * + * NOTE: This seems to happen when both I_IMAGECALLBACK + * and LPSTR_TEXTCALLBACK are used at the same time with + * TVM_SETITEM. + */ + if (nmcd.nmcd.rc.left >= nmcd.nmcd.rc.right || nmcd.nmcd.rc.top >= nmcd.nmcd.rc.bottom) return null; + if (!OS.IsWindowVisible (handle)) return null; + if ((style & SWT.FULL_SELECTION) !is 0 || findImageControl () !is null || ignoreDrawSelection || explorerTheme) { + OS.SetBkMode (hDC, OS.TRANSPARENT); + } + bool selected = isItemSelected (nmcd); + bool hot = explorerTheme && (nmcd.nmcd.uItemState & OS.CDIS_HOT) !is 0; + if (OS.IsWindowEnabled (handle)) { + if (explorerTheme) { + int bits = OS.GetWindowLong (handle, OS.GWL_STYLE); + if ((bits & OS.TVS_TRACKSELECT) !is 0) { + if ((style & SWT.FULL_SELECTION) !is 0 && (selected || hot)) { + OS.SetTextColor (hDC, OS.GetSysColor (OS.COLOR_WINDOWTEXT)); + } else { + OS.SetTextColor (hDC, getForegroundPixel ()); + } + } + } + } + int [] order = null; + RECT clientRect; + OS.GetClientRect (scrolledHandle (), &clientRect); + if (hwndHeader !is null) { + OS.MapWindowPoints (hwndParent, handle, cast(POINT*) &clientRect, 2); + if (columnCount !is 0) { + order = new int [columnCount]; + OS.SendMessage (hwndHeader, OS.HDM_GETORDERARRAY, columnCount, cast(int) order.ptr); + } + } + int sortIndex = -1, clrSortBk = -1; + if (OS.COMCTL32_MAJOR >= 6 && OS.IsAppThemed ()) { + if (sortColumn !is null && sortDirection !is SWT.NONE) { + if (findImageControl () is null) { + sortIndex = indexOf (sortColumn); + clrSortBk = getSortColumnPixel (); + } + } + } + int x = 0; + Point size = null; + for (int i=0; i<Math.max (1, columnCount); i++) { + int index = order is null ? i : order [i], width = nmcd.nmcd.rc.right - nmcd.nmcd.rc.left; + if (columnCount > 0 && hwndHeader !is null) { + HDITEM hdItem; + hdItem.mask = OS.HDI_WIDTH; + OS.SendMessage (hwndHeader, OS.HDM_GETITEM, index, &hdItem); + width = hdItem.cxy; + } + if (i is 0) { + if ((style & SWT.FULL_SELECTION) !is 0) { + bool clear = !explorerTheme && !ignoreDrawSelection && findImageControl () is null; + if (clear || (selected && !ignoreDrawSelection) || (hot && !ignoreDrawHot)) { + bool draw = true; + RECT pClipRect; + OS.SetRect (&pClipRect, width, nmcd.nmcd.rc.top, nmcd.nmcd.rc.right, nmcd.nmcd.rc.bottom); + if (explorerTheme) { + if (hooks (SWT.EraseItem)) { + RECT* itemRect = item.getBounds (index, true, true, false, false, true, hDC); + itemRect.left -= EXPLORER_EXTRA; + itemRect.right += EXPLORER_EXTRA + 1; + pClipRect.left = itemRect.left; + pClipRect.right = itemRect.right; + if (columnCount > 0 && hwndHeader !is null) { + HDITEM hdItem; + hdItem.mask = OS.HDI_WIDTH; + OS.SendMessage (hwndHeader, OS.HDM_GETITEM, index, &hdItem); + pClipRect.right = Math.min (pClipRect.right, nmcd.nmcd.rc.left + hdItem.cxy); + } + } + RECT pRect; + OS.SetRect (&pRect, nmcd.nmcd.rc.left, nmcd.nmcd.rc.top, nmcd.nmcd.rc.right, nmcd.nmcd.rc.bottom); + if (columnCount > 0 && hwndHeader !is null) { + int totalWidth = 0; + HDITEM hdItem; + hdItem.mask = OS.HDI_WIDTH; + for (int j=0; j<columnCount; j++) { + OS.SendMessage (hwndHeader, OS.HDM_GETITEM, j, &hdItem); + totalWidth += hdItem.cxy; + } + if (totalWidth > clientRect.right - clientRect.left) { + pRect.left = 0; + pRect.right = totalWidth; + } else { + pRect.left = clientRect.left; + pRect.right = clientRect.right; + } + } + draw = false; + auto hTheme = OS.OpenThemeData (handle, cast(TCHAR*) Display.TREEVIEW); + int iStateId = selected ? OS.TREIS_SELECTED : OS.TREIS_HOT; + if (OS.GetFocus () !is handle && selected && !hot) iStateId = OS.TREIS_SELECTEDNOTFOCUS; + OS.DrawThemeBackground (hTheme, hDC, OS.TVP_TREEITEM, iStateId, &pRect, &pClipRect); + OS.CloseThemeData (hTheme); + } + if (draw) fillBackground (hDC, OS.GetBkColor (hDC), &pClipRect); + } + } + } + if (x + width > clientRect.left) { + RECT* rect = new RECT(), backgroundRect = null; + bool drawItem = true, drawText = true, drawImage = true, drawBackground_ = false; + if (i is 0) { + drawItem = drawImage = drawText = false; + if (findImageControl () !is null) { + if (explorerTheme) { + if (OS.IsWindowEnabled (handle) && !hooks (SWT.EraseItem)) { + Image image = null; + if (index is 0) { + image = item.image; + } else { + Image [] images = item.images; + if (images !is null) image = images [index]; + } + if (image !is null) { + Rectangle bounds = image.getBounds (); + if (size is null) size = getImageSize (); + if (!ignoreDrawForeground) { + GCData data = new GCData(); + data.device = display; + GC gc = GC.win32_new (hDC, data); + RECT* iconRect = item.getBounds (index, false, true, false, false, true, hDC); + gc.setClipping (iconRect.left, iconRect.top, iconRect.right - iconRect.left, iconRect.bottom - iconRect.top); + gc.drawImage (image, 0, 0, bounds.width, bounds.height, iconRect.left, iconRect.top, size.x, size.y); + OS.SelectClipRgn (hDC, null); + gc.dispose (); + } + } + } + } else { + drawItem = drawText = drawBackground_ = true; + rect = item.getBounds (index, true, false, false, false, true, hDC); + if (linesVisible) { + rect.right++; + rect.bottom++; + } + } + } + if (selected && !ignoreDrawSelection && !ignoreDrawBackground) { + if (!explorerTheme) fillBackground (hDC, OS.GetBkColor (hDC), rect); + drawBackground_ = false; + } + backgroundRect = rect; + if (hooks (SWT.EraseItem)) { + drawItem = drawText = drawImage = true; + rect = item.getBounds (index, true, true, false, false, true, hDC); + if ((style & SWT.FULL_SELECTION) !is 0) { + backgroundRect = rect; + } else { + backgroundRect = item.getBounds (index, true, false, false, false, true, hDC); + } + } + } else { + selectionForeground = -1; + ignoreDrawForeground = ignoreDrawBackground = ignoreDrawSelection = ignoreDrawFocus = ignoreDrawHot = false; + OS.SetRect (rect, x, nmcd.nmcd.rc.top, x + width, nmcd.nmcd.rc.bottom); + backgroundRect = rect; + } + int clrText = -1, clrTextBk = -1; + auto hFont = item.fontHandle (index); + if (selectionForeground !is -1) clrText = selectionForeground; + if (OS.IsWindowEnabled (handle)) { + bool drawForeground = false; + if (selected) { + if (i !is 0 && (style & SWT.FULL_SELECTION) is 0) { + OS.SetTextColor (hDC, getForegroundPixel ()); + OS.SetBkColor (hDC, getBackgroundPixel ()); + drawForeground = drawBackground_ = true; + } + } else { + drawForeground = drawBackground_ = true; + } + if (drawForeground) { + clrText = item.cellForeground !is null ? item.cellForeground [index] : -1; + if (clrText is -1) clrText = item.foreground; + } + if (drawBackground_) { + clrTextBk = item.cellBackground !is null ? item.cellBackground [index] : -1; + if (clrTextBk is -1) clrTextBk = item.background; + if (clrTextBk is -1 && index is sortIndex) clrTextBk = clrSortBk; + } + } else { + if (clrTextBk is -1 && index is sortIndex) { + drawBackground_ = true; + clrTextBk = clrSortBk; + } + } + if (explorerTheme) { + if (selected || (nmcd.nmcd.uItemState & OS.CDIS_HOT) !is 0) { + if ((style & SWT.FULL_SELECTION) !is 0) { + drawBackground_ = false; + } else { + if (i is 0) { + drawBackground_ = false; + if (!hooks (SWT.EraseItem)) drawText = false; + } + } + } + } + if (drawItem) { + if (i !is 0) { + if (hooks (SWT.MeasureItem)) { + sendMeasureItemEvent (item, index, hDC); + if (isDisposed () || item.isDisposed ()) break; + } + if (hooks (SWT.EraseItem)) { + RECT* cellRect = item.getBounds (index, true, true, true, true, true, hDC); + int nSavedDC = OS.SaveDC (hDC); + GCData data = new GCData (); + data.device = display; + data.foreground = OS.GetTextColor (hDC); + data.background = OS.GetBkColor (hDC); + if (!selected || (style & SWT.FULL_SELECTION) is 0) { + if (clrText !is -1) data.foreground = clrText; + if (clrTextBk !is -1) data.background = clrTextBk; + } + data.font = item.getFont (index); + data.uiState = OS.SendMessage (handle, OS.WM_QUERYUISTATE, 0, 0); + GC gc = GC.win32_new (hDC, data); + Event event = new Event (); + event.item = item; + event.index = index; + event.gc = gc; + event.detail |= SWT.FOREGROUND; + if (clrTextBk !is -1) event.detail |= SWT.BACKGROUND; + if ((style & SWT.FULL_SELECTION) !is 0) { + if (hot) event.detail |= SWT.HOT; + if (selected) event.detail |= SWT.SELECTED; + if (!explorerTheme) { + //if ((nmcd.uItemState & OS.CDIS_FOCUS) !is 0) { + if (OS.SendMessage (handle, OS.TVM_GETNEXTITEM, OS.TVGN_CARET, 0) is nmcd.nmcd.dwItemSpec) { + if (handle is OS.GetFocus ()) { + int uiState = OS.SendMessage (handle, OS.WM_QUERYUISTATE, 0, 0); + if ((uiState & OS.UISF_HIDEFOCUS) is 0) event.detail |= SWT.FOCUSED; + } + } + } + } + event.x = cellRect.left; + event.y = cellRect.top; + event.width = cellRect.right - cellRect.left; + event.height = cellRect.bottom - cellRect.top; + gc.setClipping (event.x, event.y, event.width, event.height); + sendEvent (SWT.EraseItem, event); + event.gc = null; + int newTextClr = data.foreground; + gc.dispose (); + OS.RestoreDC (hDC, nSavedDC); + if (isDisposed () || item.isDisposed ()) break; + if (event.doit) { + ignoreDrawForeground = (event.detail & SWT.FOREGROUND) is 0; + ignoreDrawBackground = (event.detail & SWT.BACKGROUND) is 0; + if ((style & SWT.FULL_SELECTION) !is 0) { + ignoreDrawSelection = (event.detail & SWT.SELECTED) is 0; + ignoreDrawFocus = (event.detail & SWT.FOCUSED) is 0; + ignoreDrawHot = (event.detail & SWT.HOT) is 0; + } + } else { + ignoreDrawForeground = ignoreDrawBackground = ignoreDrawSelection = ignoreDrawFocus = ignoreDrawHot = true; + } + if (selected && ignoreDrawSelection) ignoreDrawHot = true; + if ((style & SWT.FULL_SELECTION) !is 0) { + if (ignoreDrawSelection) ignoreFullSelection = true; + if (!ignoreDrawSelection || !ignoreDrawHot) { + if (!selected && !hot) { + selectionForeground = OS.GetSysColor (OS.COLOR_HIGHLIGHTTEXT); + } else { + if (!explorerTheme) { + drawBackground_ = true; + ignoreDrawBackground = false; + if ((handle is OS.GetFocus () || display.getHighContrast ()) && OS.IsWindowEnabled (handle)) { + clrTextBk = OS.GetSysColor (OS.COLOR_HIGHLIGHT); + } else { + clrTextBk = OS.GetSysColor (OS.COLOR_3DFACE); + } + if (!ignoreFullSelection && index is columnCount - 1) { + RECT* selectionRect = new RECT (); + OS.SetRect (selectionRect, backgroundRect.left, backgroundRect.top, nmcd.nmcd.rc.right, backgroundRect.bottom); + backgroundRect = selectionRect; + } + } else { + RECT pRect; + OS.SetRect (&pRect, nmcd.nmcd.rc.left, nmcd.nmcd.rc.top, nmcd.nmcd.rc.right, nmcd.nmcd.rc.bottom); + if (columnCount > 0 && hwndHeader !is null) { + int totalWidth = 0; + HDITEM hdItem; + hdItem.mask = OS.HDI_WIDTH; + for (int j=0; j<columnCount; j++) { + OS.SendMessage (hwndHeader, OS.HDM_GETITEM, j, &hdItem); + totalWidth += hdItem.cxy; + } + if (totalWidth > clientRect.right - clientRect.left) { + pRect.left = 0; + pRect.right = totalWidth; + } else { + pRect.left = clientRect.left; + pRect.right = clientRect.right; + } + if (index is columnCount - 1) { + RECT* selectionRect = new RECT (); + OS.SetRect (selectionRect, backgroundRect.left, backgroundRect.top, pRect.right, backgroundRect.bottom); + backgroundRect = selectionRect; + } + } + auto hTheme = OS.OpenThemeData (handle, cast(TCHAR*) Display.TREEVIEW); + int iStateId = selected ? OS.TREIS_SELECTED : OS.TREIS_HOT; + if (OS.GetFocus () !is handle && selected && !hot) iStateId = OS.TREIS_SELECTEDNOTFOCUS; + OS.DrawThemeBackground (hTheme, hDC, OS.TVP_TREEITEM, iStateId, &pRect, backgroundRect); + OS.CloseThemeData (hTheme); + } + } + } else { + if (selected) { + selectionForeground = newTextClr; + if (!explorerTheme) { + if (clrTextBk is -1 && OS.IsWindowEnabled (handle)) { + Control control = findBackgroundControl (); + if (control is null) control = this; + clrTextBk = control.getBackgroundPixel (); + } + } + } + } + } + } + if (selectionForeground !is -1) clrText = selectionForeground; + } + if (!ignoreDrawBackground) { + if (clrTextBk !is -1) { + if (drawBackground_) fillBackground (hDC, clrTextBk, backgroundRect); + } else { + Control control = findImageControl (); + if (control !is null) { + if (i is 0) { + int right = Math.min (rect.right, width); + OS.SetRect (rect, rect.left, rect.top, right, rect.bottom); + if (drawBackground_) fillImageBackground (hDC, control, rect); + } else { + if (drawBackground_) fillImageBackground (hDC, control, rect); + } + } + } + } + rect.left += INSET - 1; + if (drawImage) { + Image image = null; + if (index is 0) { + image = item.image; + } else { + Image [] images = item.images; + if (images !is null) image = images [index]; + } + int inset = i !is 0 ? INSET : 0; + int offset = i !is 0 ? INSET : INSET + 2; + if (image !is null) { + Rectangle bounds = image.getBounds (); + if (size is null) size = getImageSize (); + if (!ignoreDrawForeground) { + //int y1 = rect.top + (index is 0 ? (getItemHeight () - size.y) / 2 : 0); + int y1 = rect.top; + int x1 = Math.max (rect.left, rect.left - inset + 1); + GCData data = new GCData(); + data.device = display; + GC gc = GC.win32_new (hDC, data); + gc.setClipping (x1, rect.top, rect.right - x1, rect.bottom - rect.top); + gc.drawImage (image, 0, 0, bounds.width, bounds.height, x1, y1, size.x, size.y); + OS.SelectClipRgn (hDC, null); + gc.dispose (); + } + OS.SetRect (rect, rect.left + size.x + offset, rect.top, rect.right - inset, rect.bottom); + } else { + if (i is 0) { + if (OS.SendMessage (handle, OS.TVM_GETIMAGELIST, OS.TVSIL_NORMAL, 0) !is 0) { + if (size is null) size = getImageSize (); + rect.left = Math.min (rect.left + size.x + offset, rect.right); + } + } else { + OS.SetRect (rect, rect.left + offset, rect.top, rect.right - inset, rect.bottom); + } + } + } + if (drawText) { + /* + * Bug in Windows. When DrawText() is used with DT_VCENTER + * and DT_ENDELLIPSIS, the ellipsis can draw outside of the + * rectangle when the rectangle is empty. The fix is avoid + * all text drawing for empty rectangles. + */ + if (rect.left < rect.right) { + String string = null; + if (index is 0) { + string = item.text; + } else { + String [] strings = item.strings; + if (strings !is null) string = strings [index]; + } + if (string !is null) { + if (hFont !is cast(HFONT)-1) hFont = cast(HFONT)OS.SelectObject (hDC, hFont); + if (clrText !is -1) clrText = OS.SetTextColor (hDC, clrText); + if (clrTextBk !is -1) clrTextBk = OS.SetBkColor (hDC, clrTextBk); + int flags = OS.DT_NOPREFIX | OS.DT_SINGLELINE | OS.DT_VCENTER; + if (i !is 0) flags |= OS.DT_ENDELLIPSIS; + TreeColumn column = columns !is null ? columns [index] : null; + if (column !is null) { + if ((column.style & SWT.CENTER) !is 0) flags |= OS.DT_CENTER; + if ((column.style & SWT.RIGHT) !is 0) flags |= OS.DT_RIGHT; + } + TCHAR[] buffer = StrToTCHARs (getCodePage (), string, false); + if (!ignoreDrawForeground) OS.DrawText (hDC, buffer.ptr, buffer.length, rect, flags); + OS.DrawText (hDC, buffer.ptr, buffer.length, rect, flags | OS.DT_CALCRECT); + if (hFont !is cast(HFONT)-1) hFont = cast(HFONT)OS.SelectObject (hDC, hFont); + if (clrText !is -1) clrText = OS.SetTextColor (hDC, clrText); + if (clrTextBk !is -1) clrTextBk = OS.SetBkColor (hDC, clrTextBk); + } + } + } + } + if (selectionForeground !is -1) clrText = selectionForeground; + if (hooks (SWT.PaintItem)) { + RECT* itemRect = item.getBounds (index, true, true, false, false, false, hDC); + int nSavedDC = OS.SaveDC (hDC); + GCData data = new GCData (); + data.device = display; + data.font = item.getFont (index); + data.foreground = OS.GetTextColor (hDC); + data.background = OS.GetBkColor (hDC); + if (selected && (style & SWT.FULL_SELECTION) !is 0) { + if (selectionForeground !is -1) data.foreground = selectionForeground; + } else { + if (clrText !is -1) data.foreground = clrText; + if (clrTextBk !is -1) data.background = clrTextBk; + } + data.uiState = OS.SendMessage (handle, OS.WM_QUERYUISTATE, 0, 0); + GC gc = GC.win32_new (hDC, data); + Event event = new Event (); + event.item = item; + event.index = index; + event.gc = gc; + event.detail |= SWT.FOREGROUND; + if (clrTextBk !is -1) event.detail |= SWT.BACKGROUND; + if (hot) event.detail |= SWT.HOT; + if (selected && (i is 0 /*nmcd.iSubItem is 0*/ || (style & SWT.FULL_SELECTION) !is 0)) { + event.detail |= SWT.SELECTED; + } + if (!explorerTheme) { + //if ((nmcd.uItemState & OS.CDIS_FOCUS) !is 0) { + if (OS.SendMessage (handle, OS.TVM_GETNEXTITEM, OS.TVGN_CARET, 0) is nmcd.nmcd.dwItemSpec) { + if (i is 0 /*nmcd.iSubItem is 0*/ || (style & SWT.FULL_SELECTION) !is 0) { + if (handle is OS.GetFocus ()) { + int uiState = OS.SendMessage (handle, OS.WM_QUERYUISTATE, 0, 0); + if ((uiState & OS.UISF_HIDEFOCUS) is 0) event.detail |= SWT.FOCUSED; + } + } + } + } + event.x = itemRect.left; + event.y = itemRect.top; + event.width = itemRect.right - itemRect.left; + event.height = itemRect.bottom - itemRect.top; + RECT* cellRect = item.getBounds (index, true, true, true, true, true, hDC); + int cellWidth = cellRect.right - cellRect.left; + int cellHeight = cellRect.bottom - cellRect.top; + gc.setClipping (cellRect.left, cellRect.top, cellWidth, cellHeight); + sendEvent (SWT.PaintItem, event); + if (data.focusDrawn) focusRect = null; + event.gc = null; + gc.dispose (); + OS.RestoreDC (hDC, nSavedDC); + if (isDisposed () || item.isDisposed ()) break; + } + } + x += width; + if (x > clientRect.right) break; + } + if (linesVisible) { + if ((style & SWT.FULL_SELECTION) !is 0) { + if (columnCount !is 0) { + HDITEM hdItem; + hdItem.mask = OS.HDI_WIDTH; + OS.SendMessage (hwndHeader, OS.HDM_GETITEM, 0, &hdItem); + RECT rect; + OS.SetRect (&rect, nmcd.nmcd.rc.left + hdItem.cxy, nmcd.nmcd.rc.top, nmcd.nmcd.rc.right, nmcd.nmcd.rc.bottom); + OS.DrawEdge (hDC, &rect, OS.BDR_SUNKENINNER, OS.BF_BOTTOM); + } + } + RECT rect; + OS.SetRect (&rect, nmcd.nmcd.rc.left, nmcd.nmcd.rc.top, nmcd.nmcd.rc.right, nmcd.nmcd.rc.bottom); + OS.DrawEdge (hDC, &rect, OS.BDR_SUNKENINNER, OS.BF_BOTTOM); + } + if (!ignoreDrawFocus && focusRect !is null) { + OS.DrawFocusRect (hDC, focusRect); + focusRect = null; + } else { + if (!explorerTheme) { + if (handle is OS.GetFocus ()) { + int uiState = OS.SendMessage (handle, OS.WM_QUERYUISTATE, 0, 0); + if ((uiState & OS.UISF_HIDEFOCUS) is 0) { + auto hItem = cast(HANDLE) OS.SendMessage (handle, OS.TVM_GETNEXTITEM, OS.TVGN_CARET, 0); + if (hItem is item.handle) { + if (!ignoreDrawFocus && findImageControl () !is null) { + if ((style & SWT.FULL_SELECTION) !is 0) { + RECT focusRect; + OS.SetRect (&focusRect, 0, nmcd.nmcd.rc.top, clientRect.right + 1, nmcd.nmcd.rc.bottom); + OS.DrawFocusRect (hDC, &focusRect); + } else { + int index = OS.SendMessage (hwndHeader, OS.HDM_ORDERTOINDEX, 0, 0); + RECT* focusRect = item.getBounds (index, true, false, false, false, false, hDC); + RECT* clipRect = item.getBounds (index, true, false, false, false, true, hDC); + OS.IntersectClipRect (hDC, clipRect.left, clipRect.top, clipRect.right, clipRect.bottom); + OS.DrawFocusRect (hDC, focusRect); + OS.SelectClipRgn (hDC, null); + } + } + } + } + } + } + } + return new LRESULT (OS.CDRF_DODEFAULT); +} + +LRESULT CDDS_ITEMPREPAINT (NMTVCUSTOMDRAW* nmcd, int wParam, int lParam) { + /* + * Even when custom draw is being ignored, the font needs + * to be selected into the HDC so that the item bounds are + * measured correctly. + */ + TreeItem item = getItem (nmcd); + /* + * Feature in Windows. When a new tree item is inserted + * using TVM_INSERTITEM and the tree is using custom draw, + * a NM_CUSTOMDRAW is sent before TVM_INSERTITEM returns + * and before the item is added to the items array. The + * fix is to check for null. + * + * NOTE: This only happens on XP with the version 6.00 of + * COMCTL32.DLL, + */ + if (item is null) return null; + auto hDC = nmcd.nmcd.hdc; + int index = hwndHeader !is null ? OS.SendMessage (hwndHeader, OS.HDM_ORDERTOINDEX, 0, 0) : 0; + auto hFont = item.fontHandle (index); + if (hFont !is cast(HFONT)-1) OS.SelectObject (hDC, hFont); + if (ignoreCustomDraw || nmcd.nmcd.rc.left is nmcd.nmcd.rc.right) { + return new LRESULT (hFont is cast(HFONT)-1 ? OS.CDRF_DODEFAULT : OS.CDRF_NEWFONT); + } + RECT* clipRect = null; + if (columnCount !is 0) { + bool clip = !printClient; + if (!OS.IsWinCE && OS.WIN32_VERSION >= OS.VERSION (6, 0)) { + clip = true; + } + if (clip) { + clipRect = new RECT (); + HDITEM hdItem; + hdItem.mask = OS.HDI_WIDTH; + OS.SendMessage (hwndHeader, OS.HDM_GETITEM, index, &hdItem); + OS.SetRect (clipRect, nmcd.nmcd.rc.left, nmcd.nmcd.rc.top, nmcd.nmcd.rc.left + hdItem.cxy, nmcd.nmcd.rc.bottom); + } + } + int clrText = -1, clrTextBk = -1; + if (OS.IsWindowEnabled (handle)) { + clrText = item.cellForeground !is null ? item.cellForeground [index] : -1; + if (clrText is -1) clrText = item.foreground; + clrTextBk = item.cellBackground !is null ? item.cellBackground [index] : -1; + if (clrTextBk is -1) clrTextBk = item.background; + } + int clrSortBk = -1; + if (OS.COMCTL32_MAJOR >= 6 && OS.IsAppThemed ()) { + if (sortColumn !is null && sortDirection !is SWT.NONE) { + if (findImageControl () is null) { + if (indexOf (sortColumn) is index) { + clrSortBk = getSortColumnPixel (); + if (clrTextBk is -1) clrTextBk = clrSortBk; + } + } + } + } + bool selected = isItemSelected (nmcd); + bool hot = explorerTheme && (nmcd.nmcd.uItemState & OS.CDIS_HOT) !is 0; + bool focused = explorerTheme && (nmcd.nmcd.uItemState & OS.CDIS_FOCUS) !is 0; + if (OS.IsWindowVisible (handle) && nmcd.nmcd.rc.left < nmcd.nmcd.rc.right && nmcd.nmcd.rc.top < nmcd.nmcd.rc.bottom) { + if (hFont !is cast(HFONT)-1) OS.SelectObject (hDC, hFont); + if (linesVisible) { + RECT rect; + OS.SetRect (&rect, nmcd.nmcd.rc.left, nmcd.nmcd.rc.top, nmcd.nmcd.rc.right, nmcd.nmcd.rc.bottom); + OS.DrawEdge (hDC, &rect, OS.BDR_SUNKENINNER, OS.BF_BOTTOM); + } + //TODO - BUG - measure and erase sent when first column is clipped + Event measureEvent = null; + if (hooks (SWT.MeasureItem)) { + measureEvent = sendMeasureItemEvent (item, index, hDC); + if (isDisposed () || item.isDisposed ()) return null; + } + selectionForeground = -1; + ignoreDrawForeground = ignoreDrawBackground = ignoreDrawSelection = ignoreDrawFocus = ignoreDrawHot = ignoreFullSelection = false; + if (hooks (SWT.EraseItem)) { + RECT rect; + OS.SetRect (&rect, nmcd.nmcd.rc.left, nmcd.nmcd.rc.top, nmcd.nmcd.rc.right, nmcd.nmcd.rc.bottom); + RECT* cellRect = item.getBounds (index, true, true, true, true, true, hDC); + if (clrSortBk !is -1) { + drawBackground (hDC, cellRect, clrSortBk); + } else { + if (OS.IsWindowEnabled (handle) || findImageControl () !is null) { + drawBackground (hDC, &rect); + } else { + fillBackground (hDC, OS.GetBkColor (hDC), &rect); + } + } + int nSavedDC = OS.SaveDC (hDC); + GCData data = new GCData (); + data.device = display; + if (selected && explorerTheme) { + data.foreground = OS.GetSysColor (OS.COLOR_WINDOWTEXT); + } else { + data.foreground = OS.GetTextColor (hDC); + } + data.background = OS.GetBkColor (hDC); + if (!selected) { + if (clrText !is -1) data.foreground = clrText; + if (clrTextBk !is -1) data.background = clrTextBk; + } + data.uiState = OS.SendMessage (handle, OS.WM_QUERYUISTATE, 0, 0); + data.font = item.getFont (index); + GC gc = GC.win32_new (hDC, data); + Event event = new Event (); + event.index = index; + event.item = item; + event.gc = gc; + event.detail |= SWT.FOREGROUND; + if (clrTextBk !is -1) event.detail |= SWT.BACKGROUND; + if (hot) event.detail |= SWT.HOT; + if (selected) event.detail |= SWT.SELECTED; + if (!explorerTheme) { + //if ((nmcd.uItemState & OS.CDIS_FOCUS) !is 0) { + if (OS.SendMessage (handle, OS.TVM_GETNEXTITEM, OS.TVGN_CARET, 0) is nmcd.nmcd.dwItemSpec) { + if (handle is OS.GetFocus ()) { + int uiState = OS.SendMessage (handle, OS.WM_QUERYUISTATE, 0, 0); + if ((uiState & OS.UISF_HIDEFOCUS) is 0) { + focused = true; + event.detail |= SWT.FOCUSED; + } + } + } + } + event.x = cellRect.left; + event.y = cellRect.top; + event.width = cellRect.right - cellRect.left; + event.height = cellRect.bottom - cellRect.top; + gc.setClipping (event.x, event.y, event.width, event.height); + sendEvent (SWT.EraseItem, event); + event.gc = null; + int newTextClr = data.foreground; + gc.dispose (); + OS.RestoreDC (hDC, nSavedDC); + if (isDisposed () || item.isDisposed ()) return null; + if (event.doit) { + ignoreDrawForeground = (event.detail & SWT.FOREGROUND) is 0; + ignoreDrawBackground = (event.detail & SWT.BACKGROUND) is 0; + ignoreDrawSelection = (event.detail & SWT.SELECTED) is 0; + ignoreDrawFocus = (event.detail & SWT.FOCUSED) is 0; + ignoreDrawHot = (event.detail & SWT.HOT) is 0; + } else { + ignoreDrawForeground = ignoreDrawBackground = ignoreDrawSelection = ignoreDrawFocus = ignoreDrawHot = true; + } + if (selected && ignoreDrawSelection) ignoreDrawHot = true; + if (!ignoreDrawBackground && clrTextBk !is -1) { + bool draw = !selected && !hot; + if (!explorerTheme && selected) draw = !ignoreDrawSelection; + if (draw) { + if (columnCount is 0) { + if ((style & SWT.FULL_SELECTION) !is 0) { + fillBackground (hDC, clrTextBk, &rect); + } else { + RECT* textRect = item.getBounds (index, true, false, false, false, true, hDC); + if (measureEvent !is null) { + textRect.right = Math.min (cellRect.right, measureEvent.x + measureEvent.width); + } + fillBackground (hDC, clrTextBk, textRect); + } + } else { + fillBackground (hDC, clrTextBk, cellRect); + } + } + } + if (ignoreDrawSelection) ignoreFullSelection = true; + if (!ignoreDrawSelection || !ignoreDrawHot) { + if (!selected && !hot) { + selectionForeground = clrText = OS.GetSysColor (OS.COLOR_HIGHLIGHTTEXT); + } + if (explorerTheme) { + if ((style & SWT.FULL_SELECTION) is 0) { + RECT* pRect = item.getBounds (index, true, true, false, false, false, hDC); + RECT* pClipRect = item.getBounds (index, true, true, true, false, true, hDC); + if (measureEvent !is null) { + pRect.right = Math.min (pClipRect.right, measureEvent.x + measureEvent.width); + } else { + pRect.right += EXPLORER_EXTRA; + pClipRect.right += EXPLORER_EXTRA; + } + pRect.left -= EXPLORER_EXTRA; + pClipRect.left -= EXPLORER_EXTRA; + auto hTheme = OS.OpenThemeData (handle, Display.TREEVIEW.ptr); + int iStateId = selected ? OS.TREIS_SELECTED : OS.TREIS_HOT; + if (OS.GetFocus () !is handle && selected && !hot) iStateId = OS.TREIS_SELECTEDNOTFOCUS; + OS.DrawThemeBackground (hTheme, hDC, OS.TVP_TREEITEM, iStateId, pRect, pClipRect); + OS.CloseThemeData (hTheme); + } + } else { + /* + * Feature in Windows. When the tree has the style + * TVS_FULLROWSELECT, the background color for the + * entire row is filled when an item is painted, + * drawing on top of any custom drawing. The fix + * is to emulate TVS_FULLROWSELECT. + */ + if ((style & SWT.FULL_SELECTION) !is 0) { + if ((style & SWT.FULL_SELECTION) !is 0 && columnCount is 0) { + fillBackground (hDC, OS.GetBkColor (hDC), &rect); + } else { + fillBackground (hDC, OS.GetBkColor (hDC), cellRect); + } + } else { + RECT* textRect = item.getBounds (index, true, false, false, false, true, hDC); + if (measureEvent !is null) { + textRect.right = Math.min (cellRect.right, measureEvent.x + measureEvent.width); + } + fillBackground (hDC, OS.GetBkColor (hDC), textRect); + } + } + } else { + if (selected || hot) { + selectionForeground = clrText = newTextClr; + ignoreDrawSelection = ignoreDrawHot = true; + } + if (explorerTheme) { + nmcd.nmcd.uItemState |= OS.CDIS_DISABLED; + /* + * Feature in Windows. On Vista only, when the text + * color is unchanged and an item is asked to draw + * disabled, it uses the disabled color. The fix is + * to modify the color so that is it no longer equal. + */ + int newColor = clrText is -1 ? getForegroundPixel () : clrText; + if (nmcd.clrText is newColor) { + nmcd.clrText |= 0x20000000; + if (nmcd.clrText is newColor) nmcd.clrText &= ~0x20000000; + } else { + nmcd.clrText = newColor; + } + OS.MoveMemory (lParam, nmcd, NMTVCUSTOMDRAW.sizeof); + } + } + if (focused && !ignoreDrawFocus && (style & SWT.FULL_SELECTION) is 0) { + RECT* textRect = item.getBounds (index, true, explorerTheme, false, false, true, hDC); + if (measureEvent !is null) { + textRect.right = Math.min (cellRect.right, measureEvent.x + measureEvent.width); + } + nmcd.nmcd.uItemState &= ~OS.CDIS_FOCUS; + OS.MoveMemory (lParam, nmcd, NMTVCUSTOMDRAW.sizeof); + focusRect = textRect; + } + if (explorerTheme) { + if (selected || (hot && ignoreDrawHot)) nmcd.nmcd.uItemState &= ~OS.CDIS_HOT; + OS.MoveMemory (cast(void*)lParam, nmcd, NMTVCUSTOMDRAW.sizeof); + } + RECT* itemRect = item.getBounds (index, true, true, false, false, false, hDC); + OS.SaveDC (hDC); + OS.SelectClipRgn (hDC, null); + if (explorerTheme) { + itemRect.left -= EXPLORER_EXTRA; + itemRect.right += EXPLORER_EXTRA; + } + //TODO - bug in Windows selection or SWT itemRect + /*if (selected)*/ itemRect.right++; + if (linesVisible) itemRect.bottom++; + if (clipRect !is null) { + OS.IntersectClipRect (hDC, clipRect.left, clipRect.top, clipRect.right, clipRect.bottom); + } + OS.ExcludeClipRect (hDC, itemRect.left, itemRect.top, itemRect.right, itemRect.bottom); + return new LRESULT (OS.CDRF_DODEFAULT | OS.CDRF_NOTIFYPOSTPAINT); + } + /* + * Feature in Windows. When the tree has the style + * TVS_FULLROWSELECT, the background color for the + * entire row is filled when an item is painted, + * drawing on top of any custom drawing. The fix + * is to emulate TVS_FULLROWSELECT. + */ + if ((style & SWT.FULL_SELECTION) !is 0) { + int bits = OS.GetWindowLong (handle, OS.GWL_STYLE); + if ((bits & OS.TVS_FULLROWSELECT) is 0) { + RECT rect; + OS.SetRect (&rect, nmcd.nmcd.rc.left, nmcd.nmcd.rc.top, nmcd.nmcd.rc.right, nmcd.nmcd.rc.bottom); + if (selected) { + fillBackground (hDC, OS.GetBkColor (hDC), &rect); + } else { + if (OS.IsWindowEnabled (handle)) drawBackground (hDC, &rect); + } + nmcd.nmcd.uItemState &= ~OS.CDIS_FOCUS; + OS.MoveMemory (cast(void*)lParam, nmcd, NMTVCUSTOMDRAW.sizeof); + } + } + } + LRESULT result = null; + if (clrText is -1 && clrTextBk is -1 && hFont is cast(HFONT)-1) { + result = new LRESULT (OS.CDRF_DODEFAULT | OS.CDRF_NOTIFYPOSTPAINT); + } else { + result = new LRESULT (OS.CDRF_NEWFONT | OS.CDRF_NOTIFYPOSTPAINT); + if (hFont !is cast(HFONT)-1) OS.SelectObject (hDC, hFont); + if (OS.IsWindowEnabled (handle) && OS.IsWindowVisible (handle)) { + /* + * Feature in Windows. Windows does not fill the entire cell + * with the background color when TVS_FULLROWSELECT is not set. + * The fix is to fill the cell with the background color. + */ + if (clrTextBk !is -1) { + int bits = OS.GetWindowLong (handle, OS.GWL_STYLE); + if ((bits & OS.TVS_FULLROWSELECT) is 0) { + if (columnCount !is 0 && hwndHeader !is null) { + RECT rect; + HDITEM hdItem; + hdItem.mask = OS.HDI_WIDTH; + OS.SendMessage (hwndHeader, OS.HDM_GETITEM, index, &hdItem); + OS.SetRect (&rect, nmcd.nmcd.rc.left, nmcd.nmcd.rc.top, nmcd.nmcd.rc.left + hdItem.cxy, nmcd.nmcd.rc.bottom); + if (OS.COMCTL32_MAJOR < 6 || !OS.IsAppThemed ()) { + RECT itemRect; + if (OS.TreeView_GetItemRect (handle, cast(HTREEITEM)item.handle, &itemRect, true)) { + rect.left = Math.min (itemRect.left, rect.right); + } + } + if ((style & SWT.FULL_SELECTION) !is 0) { + if (!selected) fillBackground (hDC, clrTextBk, &rect); + } else { + if (explorerTheme) { + if (!selected && !hot) fillBackground (hDC, clrTextBk, &rect); + } else { + fillBackground (hDC, clrTextBk, &rect); + } + } + } else { + if ((style & SWT.FULL_SELECTION) !is 0) { + RECT rect; + OS.SetRect (&rect, nmcd.nmcd.rc.left, nmcd.nmcd.rc.top, nmcd.nmcd.rc.right, nmcd.nmcd.rc.bottom); + if (!selected) fillBackground (hDC, clrTextBk, &rect); + } + } + } + } + if (!selected) { + nmcd.clrText = clrText is -1 ? getForegroundPixel () : clrText; + nmcd.clrTextBk = clrTextBk is -1 ? getBackgroundPixel () : clrTextBk; + OS.MoveMemory (lParam, nmcd, NMTVCUSTOMDRAW.sizeof); + } + } + } + if (OS.IsWindowEnabled (handle)) { + /* + * On Vista only, when an item is asked to draw disabled, + * the background of the text is not filled with the + * background color of the tree. This is true for both + * regular and full selection trees. In order to draw a + * background image, mark the item as disabled using + * CDIS_DISABLED (when not selected) and set the text + * to the regular text color to avoid drawing disabled. + */ + if (explorerTheme) { + if (findImageControl () !is null) { + if (!selected && (nmcd.nmcd.uItemState & (OS.CDIS_HOT | OS.CDIS_SELECTED)) is 0) { + nmcd.nmcd.uItemState |= OS.CDIS_DISABLED; + /* + * Feature in Windows. On Vista only, when the text + * color is unchanged and an item is asked to draw + * disabled, it uses the disabled color. The fix is + * to modify the color so it is no longer equal. + */ + int newColor = clrText is -1 ? getForegroundPixel () : clrText; + if (nmcd.clrText is newColor) { + nmcd.clrText |= 0x20000000; + if (nmcd.clrText is newColor) nmcd.clrText &= ~0x20000000; + } else { + nmcd.clrText = newColor; + } + OS.MoveMemory (lParam, nmcd, NMTVCUSTOMDRAW.sizeof); + if (clrTextBk !is -1) { + if ((style & SWT.FULL_SELECTION) !is 0) { + RECT rect; + if (columnCount !is 0) { + HDITEM hdItem; + hdItem.mask = OS.HDI_WIDTH; + OS.SendMessage (hwndHeader, OS.HDM_GETITEM, index, &hdItem); + OS.SetRect (&rect, nmcd.nmcd.rc.left, nmcd.nmcd.rc.top, nmcd.nmcd.rc.left + hdItem.cxy, nmcd.nmcd.rc.bottom); + } else { + OS.SetRect (&rect, nmcd.nmcd.rc.left, nmcd.nmcd.rc.top, nmcd.nmcd.rc.right, nmcd.nmcd.rc.bottom); + } + fillBackground (hDC, clrTextBk, &rect); + } else { + RECT* textRect = item.getBounds (index, true, false, true, false, true, hDC); + fillBackground (hDC, clrTextBk, textRect); + } + } + } + } + } + } else { + /* + * Feature in Windows. When the tree is disabled, it draws + * with a gray background over the sort column. The fix is + * to fill the background with the sort column color. + */ + if (clrSortBk !is -1) { + RECT rect; + HDITEM hdItem; + hdItem.mask = OS.HDI_WIDTH; + OS.SendMessage (hwndHeader, OS.HDM_GETITEM, index, &hdItem); + OS.SetRect (&rect, nmcd.nmcd.rc.left, nmcd.nmcd.rc.top, nmcd.nmcd.rc.left + hdItem.cxy, nmcd.nmcd.rc.bottom); + fillBackground (hDC, clrSortBk, &rect); + } + } + OS.SaveDC (hDC); + if (clipRect !is null) { + auto hRgn = OS.CreateRectRgn (clipRect.left, clipRect.top, clipRect.right, clipRect.bottom); + POINT lpPoint; + OS.GetWindowOrgEx (hDC, &lpPoint); + OS.OffsetRgn (hRgn, -lpPoint.x, -lpPoint.y); + OS.SelectClipRgn (hDC, hRgn); + OS.DeleteObject (hRgn); + } + return result; +} + +LRESULT CDDS_POSTPAINT (NMTVCUSTOMDRAW* nmcd, int wParam, int lParam) { + if (ignoreCustomDraw) return null; + if (OS.IsWindowVisible (handle)) { + if (OS.COMCTL32_MAJOR >= 6 && OS.IsAppThemed ()) { + if (sortColumn !is null && sortDirection !is SWT.NONE) { + if (findImageControl () is null) { + int index = indexOf (sortColumn); + if (index !is -1) { + int top = nmcd.nmcd.rc.top; + /* + * Bug in Windows. For some reason, during a collapse, + * when TVM_GETNEXTITEM is sent with TVGN_LASTVISIBLE + * and the collapse causes the item being collapsed + * to become the last visible item in the tree, the + * message takes a long time to process. In order for + * the slowness to happen, the children of the item + * must have children. Times of up to 11 seconds have + * been observed with 23 children, each having one + * child. The fix is to use the bottom partially + * visible item rather than the last possible item + * that could be visible. + * + * NOTE: This problem only happens on Vista during + * WM_NOTIFY with NM_CUSTOMDRAW and CDDS_POSTPAINT. + */ + HANDLE hItem; + if (!OS.IsWinCE && OS.WIN32_VERSION >= OS.VERSION (6, 0)) { + hItem = getBottomItem (); + } else { + hItem = cast(HANDLE) OS.SendMessage (handle, OS.TVM_GETNEXTITEM, OS.TVGN_LASTVISIBLE, 0); + } + if (hItem !is null) { + RECT rect; + if (OS.TreeView_GetItemRect (handle, cast(HTREEITEM)hItem, &rect, false)) { + top = rect.bottom; + } + } + RECT rect; + OS.SetRect (&rect, nmcd.nmcd.rc.left, top, nmcd.nmcd.rc.right, nmcd.nmcd.rc.bottom); + RECT headerRect; + OS.SendMessage (hwndHeader, OS.HDM_GETITEMRECT, index, &headerRect); + rect.left = headerRect.left; + rect.right = headerRect.right; + fillBackground (nmcd.nmcd.hdc, getSortColumnPixel (), &rect); + } + } + } + } + if (linesVisible) { + auto hDC = nmcd.nmcd.hdc; + if (hwndHeader !is null) { + int x = 0; + RECT rect; + HDITEM hdItem; + hdItem.mask = OS.HDI_WIDTH; + for (int i=0; i<columnCount; i++) { + int index = OS.SendMessage (hwndHeader, OS.HDM_ORDERTOINDEX, i, 0); + OS.SendMessage (hwndHeader, OS.HDM_GETITEM, index, &hdItem); + OS.SetRect (&rect, x, nmcd.nmcd.rc.top, x + hdItem.cxy, nmcd.nmcd.rc.bottom); + OS.DrawEdge (hDC, &rect, OS.BDR_SUNKENINNER, OS.BF_RIGHT); + x += hdItem.cxy; + } + } + int height = 0; + RECT rect; + /* + * Bug in Windows. For some reason, during a collapse, + * when TVM_GETNEXTITEM is sent with TVGN_LASTVISIBLE + * and the collapse causes the item being collapsed + * to become the last visible item in the tree, the + * message takes a long time to process. In order for + * the slowness to happen, the children of the item + * must have children. Times of up to 11 seconds have + * been observed with 23 children, each having one + * child. The fix is to use the bottom partially + * visible item rather than the last possible item + * that could be visible. + * + * NOTE: This problem only happens on Vista during + * WM_NOTIFY with NM_CUSTOMDRAW and CDDS_POSTPAINT. + */ + HANDLE hItem; + if (!OS.IsWinCE && OS.WIN32_VERSION >= OS.VERSION (6, 0)) { + hItem = getBottomItem (); + } else { + hItem = cast(HANDLE) OS.SendMessage (handle, OS.TVM_GETNEXTITEM, OS.TVGN_LASTVISIBLE, 0); + } + if (hItem !is null) { + if (OS.TreeView_GetItemRect (handle, cast(HTREEITEM)hItem, &rect, false)) { + height = rect.bottom - rect.top; + } + } + if (height is 0) { + height = OS.SendMessage (handle, OS.TVM_GETITEMHEIGHT, 0, 0); + OS.GetClientRect (handle, &rect); + OS.SetRect (&rect, rect.left, rect.top, rect.right, rect.top + height); + OS.DrawEdge (hDC, &rect, OS.BDR_SUNKENINNER, OS.BF_BOTTOM); + } + while (rect.bottom < nmcd.nmcd.rc.bottom) { + int top = rect.top + height; + OS.SetRect (&rect, rect.left, top, rect.right, top + height); + OS.DrawEdge (hDC, &rect, OS.BDR_SUNKENINNER, OS.BF_BOTTOM); + } + } + } + return new LRESULT (OS.CDRF_DODEFAULT); +} + +LRESULT CDDS_PREPAINT (NMTVCUSTOMDRAW* nmcd, int wParam, int lParam) { + if (explorerTheme) { + if ((OS.IsWindowEnabled (handle) && hooks (SWT.EraseItem)) || findImageControl () !is null) { + RECT rect; + OS.SetRect (&rect, nmcd.nmcd.rc.left, nmcd.nmcd.rc.top, nmcd.nmcd.rc.right, nmcd.nmcd.rc.bottom); + drawBackground (nmcd.nmcd.hdc, &rect); + } + } + return new LRESULT (OS.CDRF_NOTIFYITEMDRAW | OS.CDRF_NOTIFYPOSTPAINT); +} + +override int callWindowProc (HWND hwnd, int msg, int wParam, int lParam) { + if (handle is null) return 0; + if (hwndParent !is null && hwnd is hwndParent) { + return OS.DefWindowProc (hwnd, msg, wParam, lParam); + } + if (hwndHeader !is null && hwnd is hwndHeader) { + return OS.CallWindowProc (HeaderProc, hwnd, msg, wParam, lParam); + } + switch (msg) { + case OS.WM_SETFOCUS: { + /* + * Feature in Windows. When a tree control processes WM_SETFOCUS, + * if no item is selected, the first item in the tree is selected. + * This is unexpected and might clear the previous selection. + * The fix is to detect that there is no selection and set it to + * the first visible item in the tree. If the item was not selected, + * only the focus is assigned. + */ + if ((style & SWT.SINGLE) !is 0) break; + HANDLE hItem = cast(HANDLE) OS.SendMessage (handle, OS.TVM_GETNEXTITEM, OS.TVGN_CARET, 0); + if (hItem is null) { + hItem = cast(HANDLE) OS.SendMessage (handle, OS.TVM_GETNEXTITEM, OS.TVGN_FIRSTVISIBLE, 0); + if (hItem !is null) { + TVITEM tvItem; + tvItem.mask = OS.TVIF_HANDLE | OS.TVIF_STATE; + tvItem.hItem = cast(HTREEITEM)hItem; + OS.SendMessage (handle, OS.TVM_GETITEM, 0, &tvItem); + hSelect = hItem; + ignoreDeselect = ignoreSelect = lockSelection = true; + OS.SendMessage (handle, OS.TVM_SELECTITEM, OS.TVGN_CARET, hItem); + ignoreDeselect = ignoreSelect = lockSelection = false; + hSelect = null; + if ((tvItem.state & OS.TVIS_SELECTED) is 0) { + OS.SendMessage (handle, OS.TVM_SETITEM, 0, &tvItem); + } + } + } + break; + } + default: + } + HANDLE hItem; + bool redraw = false; + switch (msg) { + /* Keyboard messages */ + case OS.WM_KEYDOWN: + if (wParam is OS.VK_CONTROL || wParam is OS.VK_SHIFT) break; + //FALL THROUGH + case OS.WM_CHAR: + case OS.WM_IME_CHAR: + case OS.WM_KEYUP: + case OS.WM_SYSCHAR: + case OS.WM_SYSKEYDOWN: + case OS.WM_SYSKEYUP: + //FALL THROUGH + + /* Scroll messages */ + case OS.WM_HSCROLL: + case OS.WM_VSCROLL: + //FALL THROUGH + + /* Resize messages */ + case OS.WM_SIZE: + redraw = findImageControl () !is null && drawCount is 0 && OS.IsWindowVisible (handle); + if (redraw) OS.DefWindowProc (handle, OS.WM_SETREDRAW, 0, 0); + //FALL THROUGH + + /* Mouse messages */ + case OS.WM_LBUTTONDBLCLK: + case OS.WM_LBUTTONDOWN: + case OS.WM_LBUTTONUP: + case OS.WM_MBUTTONDBLCLK: + case OS.WM_MBUTTONDOWN: + case OS.WM_MBUTTONUP: + case OS.WM_MOUSEHOVER: + case OS.WM_MOUSELEAVE: + case OS.WM_MOUSEMOVE: + case OS.WM_MOUSEWHEEL: + case OS.WM_RBUTTONDBLCLK: + case OS.WM_RBUTTONDOWN: + case OS.WM_RBUTTONUP: + case OS.WM_XBUTTONDBLCLK: + case OS.WM_XBUTTONDOWN: + case OS.WM_XBUTTONUP: + //FALL THROUGH + + /* Other messages */ + case OS.WM_SETFONT: + case OS.WM_TIMER: { + if (findImageControl () !is null) { + hItem = cast(HANDLE) OS.SendMessage (handle, OS.TVM_GETNEXTITEM, OS.TVGN_FIRSTVISIBLE, 0); + } + break; + } + default: + } + int /*long*/ code = OS.CallWindowProc (TreeProc, hwnd, msg, wParam, lParam); + switch (msg) { + /* Keyboard messages */ + case OS.WM_KEYDOWN: + if (wParam is OS.VK_CONTROL || wParam is OS.VK_SHIFT) break; + //FALL THROUGH + case OS.WM_CHAR: + case OS.WM_IME_CHAR: + case OS.WM_KEYUP: + case OS.WM_SYSCHAR: + case OS.WM_SYSKEYDOWN: + case OS.WM_SYSKEYUP: + //FALL THROUGH + + /* Scroll messages */ + case OS.WM_HSCROLL: + case OS.WM_VSCROLL: + //FALL THROUGH + + /* Resize messages */ + case OS.WM_SIZE: + if (redraw) { + OS.DefWindowProc (handle, OS.WM_SETREDRAW, 1, 0); + OS.InvalidateRect (handle, null, true); + if (hwndHeader !is null) OS.InvalidateRect (hwndHeader, null, true); + } + //FALL THROUGH + + /* Mouse messages */ + case OS.WM_LBUTTONDBLCLK: + case OS.WM_LBUTTONDOWN: + case OS.WM_LBUTTONUP: + case OS.WM_MBUTTONDBLCLK: + case OS.WM_MBUTTONDOWN: + case OS.WM_MBUTTONUP: + case OS.WM_MOUSEHOVER: + case OS.WM_MOUSELEAVE: + case OS.WM_MOUSEMOVE: + case OS.WM_MOUSEWHEEL: + case OS.WM_RBUTTONDBLCLK: + case OS.WM_RBUTTONDOWN: + case OS.WM_RBUTTONUP: + case OS.WM_XBUTTONDBLCLK: + case OS.WM_XBUTTONDOWN: + case OS.WM_XBUTTONUP: + //FALL THROUGH + + /* Other messages */ + case OS.WM_SETFONT: + case OS.WM_TIMER: { + if (findImageControl () !is null) { + if (hItem !is cast(HANDLE) OS.SendMessage (handle, OS.TVM_GETNEXTITEM, OS.TVGN_FIRSTVISIBLE, 0)) { + OS.InvalidateRect (handle, null, true); + } + } + updateScrollBar (); + break; + } + + case OS.WM_PAINT: + painted = true; + break; + default: + } + return code; +} + +override void checkBuffered () { + super.checkBuffered (); + if ((style & SWT.VIRTUAL) !is 0) { + style |= SWT.DOUBLE_BUFFERED; + OS.SendMessage (handle, OS.TVM_SETSCROLLTIME, 0, 0); + } + if (EXPLORER_THEME) { + if (!OS.IsWinCE && OS.WIN32_VERSION >= OS.VERSION (6, 0) && OS.IsAppThemed ()) { + int exStyle = OS.SendMessage (handle, OS.TVM_GETEXTENDEDSTYLE, 0, 0); + if ((exStyle & OS.TVS_EX_DOUBLEBUFFER) !is 0) style |= SWT.DOUBLE_BUFFERED; + } + } +} + +bool checkData (TreeItem item, bool redraw) { + if ((style & SWT.VIRTUAL) is 0) return true; + if (!item.cached) { + TreeItem parentItem = item.getParentItem (); + return checkData (item, parentItem is null ? indexOf (item) : parentItem.indexOf (item), redraw); + } + return true; +} + +bool checkData (TreeItem item, int index, bool redraw) { + if ((style & SWT.VIRTUAL) is 0) return true; + if (!item.cached) { + item.cached = true; + Event event = new Event (); + event.item = item; + event.index = index; + TreeItem oldItem = currentItem; + currentItem = item; + sendEvent (SWT.SetData, event); + //widget could be disposed at this point + currentItem = oldItem; + if (isDisposed () || item.isDisposed ()) return false; + if (redraw) item.redraw (); + } + return true; +} + +override bool checkHandle (HWND hwnd) { + return hwnd is handle || (hwndParent !is null && hwnd is hwndParent) || (hwndHeader !is null && hwnd is hwndHeader); +} + +bool checkScroll (HANDLE hItem) { + /* + * Feature in Windows. If redraw is turned off using WM_SETREDRAW + * and a tree item that is not a child of the first root is selected or + * scrolled using TVM_SELECTITEM or TVM_ENSUREVISIBLE, then scrolling + * does not occur. The fix is to detect this case, and make sure + * that redraw is temporarily enabled. To avoid flashing, DefWindowProc() + * is called to disable redrawing. + * + * NOTE: The code that actually works around the problem is in the + * callers of this method. + */ + if (drawCount is 0) return false; + int /*long*/ hRoot = OS.SendMessage (handle, OS.TVM_GETNEXTITEM, OS.TVGN_ROOT, 0); + int /*long*/ hParent = OS.SendMessage (handle, OS.TVM_GETNEXTITEM, OS.TVGN_PARENT, hItem); + while (hParent !is hRoot && hParent !is 0) { + hParent = OS.SendMessage (handle, OS.TVM_GETNEXTITEM, OS.TVGN_PARENT, hParent); + } + return hParent is 0; +} + +override protected void checkSubclass () { + if (!isValidSubclass ()) error (SWT.ERROR_INVALID_SUBCLASS); +} + +/** + * Clears the item at the given zero-relative index in the receiver. + * The text, icon and other attributes of the item are set to the default + * value. If the tree was created with the <code>SWT.VIRTUAL</code> style, + * these attributes are requested again as needed. + * + * @param index the index of the item to clear + * @param all <code>true</code> if all child items of the indexed item should be + * cleared recursively, and <code>false</code> otherwise + * + * @exception IllegalArgumentException <ul> + * <li>ERROR_INVALID_RANGE - if the index is not between 0 and the number of elements in the list minus 1 (inclusive)</li> + * </ul> + * @exception SWTException <ul> + * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> + * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> + * </ul> + * + * @see SWT#VIRTUAL + * @see SWT#SetData + * + * @since 3.2 + */ +public void clear (int index, bool all) { + checkWidget (); + HANDLE hItem = cast(HANDLE) OS.SendMessage (handle, OS.TVM_GETNEXTITEM, OS.TVGN_ROOT, 0); + if (hItem is null) error (SWT.ERROR_INVALID_RANGE); + hItem = findItem (hItem, index); + if (hItem is null) error (SWT.ERROR_INVALID_RANGE); + TVITEM tvItem; + tvItem.mask = OS.TVIF_HANDLE | OS.TVIF_PARAM; + clear (hItem, &tvItem); + if (all) { + hItem = cast(HANDLE) OS.SendMessage (handle, OS.TVM_GETNEXTITEM, OS.TVGN_CHILD, hItem); + clearAll (hItem, &tvItem, all); + } +} + +void clear (HANDLE hItem, TVITEM* tvItem) { + tvItem.hItem = cast(HTREEITEM)hItem; + TreeItem item = null; + if (OS.SendMessage (handle, OS.TVM_GETITEM, 0, tvItem) !is 0) { + item = tvItem.lParam !is -1 ? items [tvItem.lParam] : null; + } + if (item !is null) { + if ((style & SWT.VIRTUAL) !is 0 && !item.cached) return; + item.clear (); + item.redraw (); + } +} + +/** + * Clears all the items in the receiver. The text, icon and other + * attributes of the items are set to their default values. If the + * tree was created with the <code>SWT.VIRTUAL</code> style, these + * attributes are requested again as needed. + * + * @param all <code>true</code> if all child items should be cleared + * recursively, and <code>false</code> otherwise + * + * @exception SWTException <ul> + * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> + * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> + * </ul> + * + * @see SWT#VIRTUAL + * @see SWT#SetData + * + * @since 3.2 + */ +public void clearAll (bool all) { + checkWidget (); + HANDLE hItem = cast(HANDLE) OS.SendMessage (handle, OS.TVM_GETNEXTITEM, OS.TVGN_ROOT, 0); + if (hItem is null) return; + if (all) { + bool redraw = false; + for (int i=0; i<items.length; i++) { + TreeItem item = items [i]; + if (item !is null && item !is currentItem) { + item.clear (); + redraw = true; + } + } + if (redraw) OS.InvalidateRect (handle, null, true); + } else { + TVITEM tvItem; + tvItem.mask = OS.TVIF_HANDLE | OS.TVIF_PARAM; + clearAll (hItem, &tvItem, all); + } +} + +void clearAll (HANDLE hItem, TVITEM* tvItem, bool all) { + while (hItem !is null) { + clear (hItem, tvItem); + if (all) { + auto hFirstItem = cast(HANDLE)OS.SendMessage (handle, OS.TVM_GETNEXTITEM, OS.TVGN_CHILD, hItem); + clearAll (hFirstItem, tvItem, all); + } + hItem = cast(HANDLE) OS.SendMessage (handle, OS.TVM_GETNEXTITEM, OS.TVGN_NEXT, hItem); + } +} + +private static extern(Windows) int CompareFunc (int lParam1, int lParam2, int lParamSort) { + return sThis.CompareProc( lParam1, lParam2, lParamSort ); +} +int CompareProc (int lParam1, int lParam2, int lParamSort) { + TreeItem item1 = items [lParam1], item2 = items [lParam2]; + String text1 = item1.getText (lParamSort), text2 = item2.getText (lParamSort); + return sortDirection is SWT.UP ? ( text1 < text2 ) : ( text2 < text1 ); +} + +override public Point computeSize (int wHint, int hHint, bool changed) { + checkWidget (); + int width = 0, height = 0; + if (hwndHeader !is null) { + HDITEM hdItem; + hdItem.mask = OS.HDI_WIDTH; + for (int i=0; i<columnCount; i++) { + OS.SendMessage (hwndHeader, OS.HDM_GETITEM, i, &hdItem); + width += hdItem.cxy; + } + RECT rect; + OS.GetWindowRect (hwndHeader, &rect); + height += rect.bottom - rect.top; + } + RECT rect; + HANDLE hItem = cast(HANDLE) OS.SendMessage (handle, OS.TVM_GETNEXTITEM, OS.TVGN_ROOT, 0); + while (hItem !is null) { + if ((style & SWT.VIRTUAL) is 0 && !painted) { + TVITEM tvItem; + tvItem.mask = OS.TVIF_HANDLE | OS.TVIF_TEXT; + tvItem.hItem = cast(HTREEITEM)hItem; + tvItem.pszText = OS.LPSTR_TEXTCALLBACK; + ignoreCustomDraw = true; + OS.SendMessage (handle, OS.TVM_SETITEM, 0, &tvItem); + ignoreCustomDraw = false; + } + if (OS.TreeView_GetItemRect (handle, cast(HTREEITEM)hItem, &rect, true)) { + width = Math.max (width, rect.right); + height += rect.bottom - rect.top; + } + hItem = cast(HANDLE) OS.SendMessage (handle, OS.TVM_GETNEXTITEM, OS.TVGN_NEXTVISIBLE, hItem); + } + if (width is 0) width = DEFAULT_WIDTH; + if (height is 0) height = DEFAULT_HEIGHT; + if (wHint !is SWT.DEFAULT) width = wHint; + if (hHint !is SWT.DEFAULT) height = hHint; + int border = getBorderWidth (); + width += border * 2; + height += border * 2; + if ((style & SWT.V_SCROLL) !is 0) { + width += OS.GetSystemMetrics (OS.SM_CXVSCROLL); + } + if ((style & SWT.H_SCROLL) !is 0) { + height += OS.GetSystemMetrics (OS.SM_CYHSCROLL); + } + return new Point (width, height); +} + +override void createHandle () { + super.createHandle (); + state &= ~(CANVAS | THEME_BACKGROUND); + + /* Use the Explorer theme */ + if (EXPLORER_THEME) { + if (!OS.IsWinCE && OS.WIN32_VERSION >= OS.VERSION (6, 0) && OS.IsAppThemed ()) { + explorerTheme = true; + OS.SetWindowTheme (handle, cast(TCHAR*) Display.EXPLORER, null); + int bits = OS.TVS_EX_DOUBLEBUFFER | OS.TVS_EX_FADEINOUTEXPANDOS | OS.TVS_EX_RICHTOOLTIP; + /* + * This code is intentionally commented. + */ +// if ((style & SWT.FULL_SELECTION) is 0) bits |= OS.TVS_EX_AUTOHSCROLL; + OS.SendMessage (handle, OS.TVM_SETEXTENDEDSTYLE, 0, bits); + /* + * Bug in Windows. When the tree is using the explorer + * theme, it does not use COLOR_WINDOW_TEXT for the + * default foreground color. The fix is to explicitly + * set the foreground. + */ + setForegroundPixel (-1); + } + } + + /* + * Feature in Windows. In version 5.8 of COMCTL32.DLL, + * if the font is changed for an item, the bounds for the + * item are not updated, causing the text to be clipped. + * The fix is to detect the version of COMCTL32.DLL, and + * if it is one of the versions with the problem, then + * use version 5.00 of the control (a version that does + * not have the problem). This is the recommended work + * around from the MSDN. + */ + static if (!OS.IsWinCE) { + if (OS.COMCTL32_MAJOR < 6) { + OS.SendMessage (handle, OS.CCM_SETVERSION, 5, 0); + } + } + + /* Set the checkbox image list */ + if ((style & SWT.CHECK) !is 0) setCheckboxImageList (); + + /* + * Feature in Windows. When the control is created, + * it does not use the default system font. A new HFONT + * is created and destroyed when the control is destroyed. + * This means that a program that queries the font from + * this control, uses the font in another control and then + * destroys this control will have the font unexpectedly + * destroyed in the other control. The fix is to assign + * the font ourselves each time the control is created. + * The control will not destroy a font that it did not + * create. + */ + HFONT hFont = OS.GetStockObject (OS.SYSTEM_FONT); + OS.SendMessage (handle, OS.WM_SETFONT, hFont, 0); +} + +void createHeaderToolTips () { + static if (OS.IsWinCE) return; + if (headerToolTipHandle !is null) return; + int bits = 0; + if (OS.WIN32_VERSION >= OS.VERSION (4, 10)) { + if ((style & SWT.RIGHT_TO_LEFT) !is 0) bits |= OS.WS_EX_LAYOUTRTL; + } + headerToolTipHandle = OS.CreateWindowEx ( + bits, + OS.TOOLTIPS_CLASS.ptr, + null, + OS.TTS_NOPREFIX, + OS.CW_USEDEFAULT, 0, OS.CW_USEDEFAULT, 0, + handle, + null, + OS.GetModuleHandle (null), + null); + if (headerToolTipHandle is null) error (SWT.ERROR_NO_HANDLES); + /* + * Feature in Windows. Despite the fact that the + * tool tip text contains \r\n, the tooltip will + * not honour the new line unless TTM_SETMAXTIPWIDTH + * is set. The fix is to set TTM_SETMAXTIPWIDTH to + * a large value. + */ + OS.SendMessage (headerToolTipHandle, OS.TTM_SETMAXTIPWIDTH, 0, 0x7FFF); +} + +void createItem (TreeColumn column, int index) { + if (hwndHeader is null) createParent (); + if (!(0 <= index && index <= columnCount)) error (SWT.ERROR_INVALID_RANGE); + if (columnCount is columns.length) { + TreeColumn [] newColumns = new TreeColumn [columns.length + 4]; + System.arraycopy (columns, 0, newColumns, 0, columns.length); + columns = newColumns; + } + for (int i=0; i<items.length; i++) { + TreeItem item = items [i]; + if (item !is null) { + String [] strings = item.strings; + if (strings !is null) { + String [] temp = new String [columnCount + 1]; + System.arraycopy (strings, 0, temp, 0, index); + System.arraycopy (strings, index, temp, index + 1, columnCount - index); + item.strings = temp; + } + Image [] images = item.images; + if (images !is null) { + Image [] temp = new Image [columnCount + 1]; + System.arraycopy (images, 0, temp, 0, index); + System.arraycopy (images, index, temp, index + 1, columnCount - index); + item.images = temp; + } + if (index is 0) { + if (columnCount !is 0) { + if (strings is null) { + item.strings = new String [columnCount + 1]; + item.strings [1] = item.text; + } + item.text = ""; + if (images is null) { + item.images = new Image [columnCount + 1]; + item.images [1] = item.image; + } + item.image = null; + } + } + if (item.cellBackground !is null) { + int [] cellBackground = item.cellBackground; + int [] temp = new int [columnCount + 1]; + System.arraycopy (cellBackground, 0, temp, 0, index); + System.arraycopy (cellBackground, index, temp, index + 1, columnCount - index); + temp [index] = -1; + item.cellBackground = temp; + } + if (item.cellForeground !is null) { + int [] cellForeground = item.cellForeground; + int [] temp = new int [columnCount + 1]; + System.arraycopy (cellForeground, 0, temp, 0, index); + System.arraycopy (cellForeground, index, temp, index + 1, columnCount - index); + temp [index] = -1; + item.cellForeground = temp; + } + if (item.cellFont !is null) { + Font [] cellFont = item.cellFont; + Font [] temp = new Font [columnCount + 1]; + System.arraycopy (cellFont, 0, temp, 0, index); + System.arraycopy (cellFont, index, temp, index + 1, columnCount- index); + item.cellFont = temp; + } + } + } + System.arraycopy (columns, index, columns, index + 1, columnCount++ - index); + columns [index] = column; + + /* + * Bug in Windows. For some reason, when HDM_INSERTITEM + * is used to insert an item into a header without text, + * if is not possible to set the text at a later time. + * The fix is to insert the item with an empty string. + */ + auto hHeap = OS.GetProcessHeap (); + auto pszText = cast(TCHAR*) OS.HeapAlloc (hHeap, OS.HEAP_ZERO_MEMORY, TCHAR.sizeof); + HDITEM hdItem; + hdItem.mask = OS.HDI_TEXT | OS.HDI_FORMAT; + hdItem.pszText = pszText; + if ((column.style & SWT.LEFT) is SWT.LEFT) hdItem.fmt = OS.HDF_LEFT; + if ((column.style & SWT.CENTER) is SWT.CENTER) hdItem.fmt = OS.HDF_CENTER; + if ((column.style & SWT.RIGHT) is SWT.RIGHT) hdItem.fmt = OS.HDF_RIGHT; + OS.SendMessage (hwndHeader, OS.HDM_INSERTITEM, index, &hdItem); + if (pszText !is null) OS.HeapFree (hHeap, 0, pszText); + + /* When the first column is created, hide the horizontal scroll bar */ + if (columnCount is 1) { + scrollWidth = 0; + if ((style & SWT.H_SCROLL) !is 0) { + int bits = OS.GetWindowLong (handle, OS.GWL_STYLE); + bits |= OS.TVS_NOHSCROLL; + OS.SetWindowLong (handle, OS.GWL_STYLE, bits); + } + /* + * Bug in Windows. When TVS_NOHSCROLL is set after items + * have been inserted into the tree, Windows shows the + * scroll bar. The fix is to check for this case and + * explicitly hide the scroll bar explicitly. + */ + int count = OS.SendMessage (handle, OS.TVM_GETCOUNT, 0, 0); + if (count !is 0) { + static if (!OS.IsWinCE) OS.ShowScrollBar (handle, OS.SB_HORZ, false); + } + createItemToolTips (); + if (itemToolTipHandle !is null) { + OS.SendMessage (itemToolTipHandle, OS.TTM_SETDELAYTIME, OS.TTDT_AUTOMATIC, -1); + } + } + setScrollWidth (); + updateImageList (); + updateScrollBar (); + + /* Redraw to hide the items when the first column is created */ + if (columnCount is 1 && OS.SendMessage (handle, OS.TVM_GETCOUNT, 0, 0) !is 0) { + OS.InvalidateRect (handle, null, true); + } + + /* Add the tool tip item for the header */ + if (headerToolTipHandle !is null) { + RECT rect; + if (OS.SendMessage (hwndHeader, OS.HDM_GETITEMRECT, index, &rect) !is 0) { + TOOLINFO lpti; + lpti.cbSize = OS.TOOLINFO_sizeof; + lpti.uFlags = OS.TTF_SUBCLASS; + lpti.hwnd = hwndHeader; + lpti.uId = column.id = display.nextToolTipId++; + lpti.rect.left = rect.left; + lpti.rect.top = rect.top; + lpti.rect.right = rect.right; + lpti.rect.bottom = rect.bottom; + lpti.lpszText = OS.LPSTR_TEXTCALLBACK; + OS.SendMessage (headerToolTipHandle, OS.TTM_ADDTOOL, 0, &lpti); + } + } +} + +void createItem (TreeItem item, HANDLE hParent, HANDLE hInsertAfter, HANDLE hItem) { + int id = -1; + if (item !is null) { + id = lastID < items.length ? lastID : 0; + while (id < items.length && items [id] !is null) id++; + if (id is items.length) { + /* + * Grow the array faster when redraw is off or the + * table is not visible. When the table is painted, + * the items array is resized to be smaller to reduce + * memory usage. + */ + int length = 0; + if (drawCount is 0 && OS.IsWindowVisible (handle)) { + length = items.length + 4; + } else { + shrink = true; + length = Math.max (4, items.length * 3 / 2); + } + TreeItem [] newItems = new TreeItem [length]; + System.arraycopy (items, 0, newItems, 0, items.length); + items = newItems; + } + lastID = id + 1; + } + HANDLE hNewItem; + HANDLE hFirstItem = cast(HANDLE) OS.SendMessage (handle, OS.TVM_GETNEXTITEM, OS.TVGN_CHILD, hParent); + bool fixParent = hFirstItem is null; + if (hItem is null) { + TVINSERTSTRUCT tvInsert; + tvInsert.hParent = cast(HTREEITEM)hParent; + tvInsert.hInsertAfter = cast(HTREEITEM)hInsertAfter; + tvInsert.item.lParam = id; + tvInsert.item.pszText = OS.LPSTR_TEXTCALLBACK; + tvInsert.item.iImage = tvInsert.item.iSelectedImage = cast(HBITMAP) OS.I_IMAGECALLBACK; + tvInsert.item.mask = OS.TVIF_TEXT | OS.TVIF_IMAGE | OS.TVIF_SELECTEDIMAGE | OS.TVIF_HANDLE | OS.TVIF_PARAM; + if ((style & SWT.CHECK) !is 0) { + tvInsert.item.mask = tvInsert.item.mask | OS.TVIF_STATE; + tvInsert.item.state = 1 << 12; + tvInsert.item.stateMask = OS.TVIS_STATEIMAGEMASK; + } + ignoreCustomDraw = true; + hNewItem = cast(HANDLE) OS.SendMessage (handle, OS.TVM_INSERTITEM, 0, &tvInsert); + ignoreCustomDraw = false; + if (hNewItem is null) error (SWT.ERROR_ITEM_NOT_ADDED); + /* + * This code is intentionally commented. + */ +// if (hParent !is 0) { +// int bits = OS.GetWindowLong (handle, OS.GWL_STYLE); +// bits |= OS.TVS_LINESATROOT; +// OS.SetWindowLong (handle, OS.GWL_STYLE, bits); +// } + } else { + TVITEM tvItem; + tvItem.mask = OS.TVIF_HANDLE | OS.TVIF_PARAM; + tvItem.hItem = cast(HTREEITEM)( hNewItem = hItem ); + tvItem.lParam = id; + OS.SendMessage (handle, OS.TVM_SETITEM, 0, &tvItem); + } + if (item !is null) { + item.handle = hNewItem; + items [id] = item; + } + if (hFirstItem is null) { + if (cast(int)hInsertAfter is OS.TVI_FIRST || cast(int)hInsertAfter is OS.TVI_LAST) { + hFirstIndexOf = hLastIndexOf = hFirstItem = hNewItem; + itemCount = lastIndexOf = 0; + } + } + if (hFirstItem is hFirstIndexOf && itemCount !is -1) itemCount++; + if (hItem is null) { + /* + * Bug in Windows. When a child item is added to a parent item + * that has no children outside of WM_NOTIFY with control code + * TVN_ITEMEXPANDED, the tree widget does not redraw the + / - + * indicator. The fix is to detect the case when the first + * child is added to a visible parent item and redraw the parent. + */ + if (fixParent) { + if (drawCount is 0 && OS.IsWindowVisible (handle)) { + RECT rect; + if (OS.TreeView_GetItemRect (handle, cast(HTREEITEM)hParent, &rect, false)) { + OS.InvalidateRect (handle, &rect, true); + } + } + } + /* + * Bug in Windows. When a new item is added while Windows + * is requesting data a tree item using TVN_GETDISPINFO, + * outstanding damage for items that are below the new item + * is not scrolled. The fix is to explicitly damage the + * new area. + */ + if ((style & SWT.VIRTUAL) !is 0) { + if (currentItem !is null) { + RECT rect; + if (OS.TreeView_GetItemRect (handle, cast(HTREEITEM)hNewItem, &rect, false)) { + RECT damageRect; + bool damaged = cast(bool) OS.GetUpdateRect (handle, &damageRect, true); + if (damaged && damageRect.top < rect.bottom) { + static if (OS.IsWinCE) { + OS.OffsetRect (&damageRect, 0, rect.bottom - rect.top); + OS.InvalidateRect (handle, &damageRect, true); + } else { + HRGN rgn = OS.CreateRectRgn (0, 0, 0, 0); + int result = OS.GetUpdateRgn (handle, rgn, true); + if (result !is OS.NULLREGION) { + OS.OffsetRgn (rgn, 0, rect.bottom - rect.top); + OS.InvalidateRgn (handle, rgn, true); + } + OS.DeleteObject (rgn); + } + } + } + } + } + updateScrollBar (); + } +} + +void createItemToolTips () { + static if (OS.IsWinCE) return; + if (itemToolTipHandle !is null) return; + int bits1 = OS.GetWindowLong (handle, OS.GWL_STYLE); + bits1 |= OS.TVS_NOTOOLTIPS; + OS.SetWindowLong (handle, OS.GWL_STYLE, bits1); + int bits2 = 0; + if (OS.WIN32_VERSION >= OS.VERSION (4, 10)) { + if ((style & SWT.RIGHT_TO_LEFT) !is 0) bits2 |= OS.WS_EX_LAYOUTRTL; + } + /* + * Feature in Windows. For some reason, when the user + * clicks on a tool tip, it temporarily takes focus, even + * when WS_EX_NOACTIVATE is specified. The fix is to + * use WS_EX_TRANSPARENT, even though WS_EX_TRANSPARENT + * is documented to affect painting, not hit testing. + * + * NOTE: Windows 2000 doesn't have the problem and + * setting WS_EX_TRANSPARENT causes pixel corruption. + */ + if (OS.COMCTL32_MAJOR >= 6) bits2 |= OS.WS_EX_TRANSPARENT; + itemToolTipHandle = OS.CreateWindowEx ( + bits2, + OS.TOOLTIPS_CLASS.ptr, + null, + OS.TTS_NOPREFIX | OS.TTS_NOANIMATE | OS.TTS_NOFADE, + OS.CW_USEDEFAULT, 0, OS.CW_USEDEFAULT, 0, + handle, + null, + OS.GetModuleHandle (null), + null); + if (itemToolTipHandle is null) error (SWT.ERROR_NO_HANDLES); + OS.SendMessage (itemToolTipHandle, OS.TTM_SETDELAYTIME, OS.TTDT_INITIAL, 0); + TOOLINFO lpti; + lpti.cbSize = OS.TOOLINFO_sizeof; + lpti.hwnd = handle; + lpti.uId = cast(int)handle; + lpti.uFlags = OS.TTF_SUBCLASS | OS.TTF_TRANSPARENT; + lpti.lpszText = OS.LPSTR_TEXTCALLBACK; + OS.SendMessage (itemToolTipHandle, OS.TTM_ADDTOOL, 0, &lpti); +} + +void createParent () { + forceResize (); + RECT rect; + OS.GetWindowRect (handle, &rect); + OS.MapWindowPoints (null, parent.handle, cast(POINT*) &rect, 2); + int oldStyle = OS.GetWindowLong (handle, OS.GWL_STYLE); + int newStyle = super.widgetStyle () & ~OS.WS_VISIBLE; + if ((oldStyle & OS.WS_DISABLED) !is 0) newStyle |= OS.WS_DISABLED; +// if ((oldStyle & OS.WS_VISIBLE) !is 0) newStyle |= OS.WS_VISIBLE; + hwndParent = OS.CreateWindowEx ( + super.widgetExtStyle (), + StrToTCHARz( 0, super.windowClass () ), + null, + newStyle, + rect.left, + rect.top, + rect.right - rect.left, + rect.bottom - rect.top, + parent.handle, + null, + OS.GetModuleHandle (null), + null); + if (hwndParent is null) error (SWT.ERROR_NO_HANDLES); + OS.SetWindowLongPtr (hwndParent, OS.GWLP_ID, cast(LONG_PTR)hwndParent); + int bits = 0; + if (OS.WIN32_VERSION >= OS.VERSION (4, 10)) { + bits |= OS.WS_EX_NOINHERITLAYOUT; + if ((style & SWT.RIGHT_TO_LEFT) !is 0) bits |= OS.WS_EX_LAYOUTRTL; + } + hwndHeader = OS.CreateWindowEx ( + bits, + HeaderClass.ptr, + null, + OS.HDS_BUTTONS | OS.HDS_FULLDRAG | OS.HDS_DRAGDROP | OS.HDS_HIDDEN | OS.WS_CHILD | OS.WS_CLIPSIBLINGS, + 0, 0, 0, 0, + hwndParent, + null, + OS.GetModuleHandle (null), + null); + if (hwndHeader is null) error (SWT.ERROR_NO_HANDLES); + OS.SetWindowLongPtr (hwndHeader, OS.GWLP_ID, cast(LONG_PTR)hwndHeader); + if (OS.IsDBLocale) { + auto hIMC = OS.ImmGetContext (handle); + OS.ImmAssociateContext (hwndParent, hIMC); + OS.ImmAssociateContext (hwndHeader, hIMC); + OS.ImmReleaseContext (handle, hIMC); + } + //This code is intentionally commented +// if (!OS.IsPPC) { +// if ((style & SWT.BORDER) !is 0) { +// int oldExStyle = OS.GetWindowLong (handle, OS.GWL_EXSTYLE); +// oldExStyle &= ~OS.WS_EX_CLIENTEDGE; +// OS.SetWindowLong (handle, OS.GWL_EXSTYLE, oldExStyle); +// } +// } + HFONT hFont = cast(HFONT) OS.SendMessage (handle, OS.WM_GETFONT, 0, 0); + if (hFont !is null) OS.SendMessage (hwndHeader, OS.WM_SETFONT, hFont, 0); + HANDLE hwndInsertAfter = OS.GetWindow (handle, OS.GW_HWNDPREV); + int flags = OS.SWP_NOSIZE | OS.SWP_NOMOVE | OS.SWP_NOACTIVATE; + SetWindowPos (hwndParent, hwndInsertAfter, 0, 0, 0, 0, flags); + SCROLLINFO info; + info.cbSize = SCROLLINFO.sizeof; + info.fMask = OS.SIF_RANGE | OS.SIF_PAGE; + OS.GetScrollInfo (hwndParent, OS.SB_HORZ, &info); + info.nPage = info.nMax + 1; + OS.SetScrollInfo (hwndParent, OS.SB_HORZ, &info, true); + OS.GetScrollInfo (hwndParent, OS.SB_VERT, &info); + info.nPage = info.nMax + 1; + OS.SetScrollInfo (hwndParent, OS.SB_VERT, &info, true); + customDraw = true; + deregister (); + if ((oldStyle & OS.WS_VISIBLE) !is 0) { + OS.ShowWindow (hwndParent, OS.SW_SHOW); + } + HWND hwndFocus = OS.GetFocus (); + if (hwndFocus is handle) OS.SetFocus (hwndParent); + OS.SetParent (handle, hwndParent); + if (hwndFocus is handle) OS.SetFocus (handle); + register (); + subclass (); +} + +override void createWidget () { + super.createWidget (); + items = new TreeItem [4]; + columns = new TreeColumn [4]; + itemCount = -1; +} + +override int defaultBackground () { + return OS.GetSysColor (OS.COLOR_WINDOW); +} + +override void deregister () { + super.deregister (); + if (hwndParent !is null) display.removeControl (hwndParent); + if (hwndHeader !is null) display.removeControl (hwndHeader); +} + +void deselect (HANDLE hItem, TVITEM* tvItem, HANDLE hIgnoreItem) { + while (hItem !is null) { + if (hItem !is hIgnoreItem) { + tvItem.hItem = cast(HTREEITEM)hItem; + OS.SendMessage (handle, OS.TVM_SETITEM, 0, tvItem); + } + auto hFirstItem = cast(HANDLE) OS.SendMessage (handle, OS.TVM_GETNEXTITEM, OS.TVGN_CHILD, hItem); + deselect (hFirstItem, tvItem, hIgnoreItem); + hItem = cast(HANDLE) OS.SendMessage (handle, OS.TVM_GETNEXTITEM, OS.TVGN_NEXT, hItem); + } +} + +/** + * Deselects an item in the receiver. If the item was already + * deselected, it remains deselected. + * + * @param item the item to be deselected + * + * @exception IllegalArgumentException <ul> + * <li>ERROR_NULL_ARGUMENT - if the item is null</li> + * <li>ERROR_INVALID_ARGUMENT - if the item has been disposed</li> + * </ul> + * @exception SWTException <ul> + * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> + * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> + * </ul> + * + * @since 3.4 + */ +public void deselect (TreeItem item) { + checkWidget (); + if (item is null) error (SWT.ERROR_NULL_ARGUMENT); + if (item.isDisposed ()) error (SWT.ERROR_INVALID_ARGUMENT); + TVITEM tvItem; + tvItem.mask = OS.TVIF_HANDLE | OS.TVIF_STATE; + tvItem.stateMask = OS.TVIS_SELECTED; + tvItem.hItem = cast(HTREEITEM)item.handle; + OS.SendMessage (handle, OS.TVM_SETITEM, 0, cast(int)&tvItem); +} + +/** + * Deselects all selected items in the receiver. + * + * @exception SWTException <ul> + * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> + * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> + * </ul> + */ +public void deselectAll () { + checkWidget (); + TVITEM tvItem; + tvItem.mask = OS.TVIF_HANDLE | OS.TVIF_STATE; + tvItem.stateMask = OS.TVIS_SELECTED; + if ((style & SWT.SINGLE) !is 0) { + HANDLE hItem = cast(HANDLE) OS.SendMessage (handle, OS.TVM_GETNEXTITEM, OS.TVGN_CARET, 0); + if (hItem !is null) { + tvItem.hItem = cast(HTREEITEM)hItem; + OS.SendMessage (handle, OS.TVM_SETITEM, 0, &tvItem); + } + } else { + auto oldProc = OS.GetWindowLongPtr (handle, OS.GWLP_WNDPROC); + OS.SetWindowLongPtr (handle, OS.GWLP_WNDPROC, cast(LONG_PTR)TreeProc); + if ((style & SWT.VIRTUAL) !is 0) { + HANDLE hItem = cast(HANDLE) OS.SendMessage (handle, OS.TVM_GETNEXTITEM, OS.TVGN_ROOT, 0); + deselect (hItem, &tvItem, null); + } else { + for (int i=0; i<items.length; i++) { + TreeItem item = items [i]; + if (item !is null) { + tvItem.hItem = cast(HTREEITEM)item.handle; + OS.SendMessage (handle, OS.TVM_SETITEM, 0, &tvItem); + } + } + } + OS.SetWindowLongPtr (handle, OS.GWLP_WNDPROC, oldProc); + } +} + +void destroyItem (TreeColumn column) { + if (hwndHeader is null) error (SWT.ERROR_ITEM_NOT_REMOVED); + int index = 0; + while (index < columnCount) { + if (columns [index] is column) break; + index++; + } + int [] oldOrder = new int [columnCount]; + OS.SendMessage (hwndHeader, OS.HDM_GETORDERARRAY, columnCount, oldOrder.ptr); + int orderIndex = 0; + while (orderIndex < columnCount) { + if (oldOrder [orderIndex] is index) break; + orderIndex++; + } + RECT headerRect; + OS.SendMessage (hwndHeader, OS.HDM_GETITEMRECT, index, &headerRect); + if (OS.SendMessage (hwndHeader, OS.HDM_DELETEITEM, index, 0) is 0) { + error (SWT.ERROR_ITEM_NOT_REMOVED); + } + System.arraycopy (columns, index + 1, columns, index, --columnCount - index); + columns [columnCount] = null; + for (int i=0; i<items.length; i++) { + TreeItem item = items [i]; + if (item !is null) { + if (columnCount is 0) { + item.strings = null; + item.images = null; + item.cellBackground = null; + item.cellForeground = null; + item.cellFont = null; + } else { + if (item.strings !is null) { + String [] strings = item.strings; + if (index is 0) { + item.text = strings [1] !is null ? strings [1] : ""; + } + String [] temp = new String [columnCount]; + System.arraycopy (strings, 0, temp, 0, index); + System.arraycopy (strings, index + 1, temp, index, columnCount - index); + item.strings = temp; + } else { + if (index is 0) item.text = ""; + } + if (item.images !is null) { + Image [] images = item.images; + if (index is 0) item.image = images [1]; + Image [] temp = new Image [columnCount]; + System.arraycopy (images, 0, temp, 0, index); + System.arraycopy (images, index + 1, temp, index, columnCount - index); + item.images = temp; + } else { + if (index is 0) item.image = null; + } + if (item.cellBackground !is null) { + int [] cellBackground = item.cellBackground; + int [] temp = new int [columnCount]; + System.arraycopy (cellBackground, 0, temp, 0, index); + System.arraycopy (cellBackground, index + 1, temp, index, columnCount - index); + item.cellBackground = temp; + } + if (item.cellForeground !is null) { + int [] cellForeground = item.cellForeground; + int [] temp = new int [columnCount]; + System.arraycopy (cellForeground, 0, temp, 0, index); + System.arraycopy (cellForeground, index + 1, temp, index, columnCount - index); + item.cellForeground = temp; + } + if (item.cellFont !is null) { + Font [] cellFont = item.cellFont; + Font [] temp = new Font [columnCount]; + System.arraycopy (cellFont, 0, temp, 0, index); + System.arraycopy (cellFont, index + 1, temp, index, columnCount - index); + item.cellFont = temp; + } + } + } + } + + /* + * When the last column is deleted, show the horizontal + * scroll bar. Otherwise, left align the first column + * and redraw the columns to the right. + */ + if (columnCount is 0) { + scrollWidth = 0; + if (!hooks (SWT.MeasureItem)) { + int bits = OS.GetWindowLong (handle, OS.GWL_STYLE); + if ((style & SWT.H_SCROLL) !is 0) bits &= ~OS.TVS_NOHSCROLL; + OS.SetWindowLong (handle, OS.GWL_STYLE, bits); + OS.InvalidateRect (handle, null, true); + } + if (itemToolTipHandle !is null) { + OS.SendMessage (itemToolTipHandle, OS.TTM_SETDELAYTIME, OS.TTDT_INITIAL, 0); + } + } else { + if (index is 0) { + columns [0].style &= ~(SWT.LEFT | SWT.RIGHT | SWT.CENTER); + columns [0].style |= SWT.LEFT; + HDITEM hdItem; + hdItem.mask = OS.HDI_FORMAT | OS.HDI_IMAGE; + OS.SendMessage (hwndHeader, OS.HDM_GETITEM, index, &hdItem); + hdItem.fmt &= ~OS.HDF_JUSTIFYMASK; + hdItem.fmt |= OS.HDF_LEFT; + OS.SendMessage (hwndHeader, OS.HDM_SETITEM, index, &hdItem); + } + RECT rect; + OS.GetClientRect (handle, &rect); + rect.left = headerRect.left; + OS.InvalidateRect (handle, &rect, true); + } + setScrollWidth (); + updateImageList (); + updateScrollBar (); + if (columnCount !is 0) { + int [] newOrder = new int [columnCount]; + OS.SendMessage (hwndHeader, OS.HDM_GETORDERARRAY, columnCount, newOrder.ptr); + TreeColumn [] newColumns = new TreeColumn [columnCount - orderIndex]; + for (int i=orderIndex; i<newOrder.length; i++) { + newColumns [i - orderIndex] = columns [newOrder [i]]; + newColumns [i - orderIndex].updateToolTip (newOrder [i]); + } + for (int i=0; i<newColumns.length; i++) { + if (!newColumns [i].isDisposed ()) { + newColumns [i].sendEvent (SWT.Move); + } + } + } + + /* Remove the tool tip item for the header */ + if (headerToolTipHandle !is null) { + TOOLINFO lpti; + lpti.cbSize = OS.TOOLINFO_sizeof; + lpti.uId = column.id; + lpti.hwnd = hwndHeader; + OS.SendMessage (headerToolTipHandle, OS.TTM_DELTOOL, 0, &lpti); + } +} + +void destroyItem (TreeItem item, HANDLE hItem) { + hFirstIndexOf = hLastIndexOf = null; + itemCount = -1; + /* + * Feature in Windows. When an item is removed that is not + * visible in the tree because it belongs to a collapsed branch, + * Windows redraws the tree causing a flash for each item that + * is removed. The fix is to detect whether the item is visible, + * force the widget to be fully painted, turn off redraw, remove + * the item and validate the damage caused by the removing of + * the item. + * + * NOTE: This fix is not necessary when double buffering and + * can cause problems for virtual trees due to the call to + * UpdateWindow() that flushes outstanding WM_PAINT events, + * allowing application code to add or remove items during + * this remove operation. + */ + HANDLE hParent; + bool fixRedraw = false; + if ((style & SWT.DOUBLE_BUFFERED) is 0) { + if (drawCount is 0 && OS.IsWindowVisible (handle)) { + RECT rect; + fixRedraw = !OS.TreeView_GetItemRect (handle, cast(HTREEITEM)hItem, &rect, false); + } + } + if (fixRedraw) { + hParent = cast(HANDLE) OS.SendMessage (handle, OS.TVM_GETNEXTITEM, OS.TVGN_PARENT, hItem); + OS.UpdateWindow (handle); + OS.DefWindowProc (handle, OS.WM_SETREDRAW, 0, 0); + /* + * This code is intentionally commented. + */ +// OS.SendMessage (handle, OS.WM_SETREDRAW, 0, 0); + } + if ((style & SWT.MULTI) !is 0) { + ignoreDeselect = ignoreSelect = lockSelection = true; + } + + /* + * Feature in Windows. When an item is deleted and a tool tip + * is showing, Window requests the new text for the new item + * that is under the cursor right away. This means that when + * multiple items are deleted, the tool tip flashes, showing + * each new item in the tool tip as it is scrolled into view. + * The fix is to hide tool tips when any item is deleted. + * + * NOTE: This only happens on Vista. + */ + if (!OS.IsWinCE && OS.WIN32_VERSION >= OS.VERSION (6, 0)) { + auto hwndToolTip = cast(HWND) OS.SendMessage (handle, OS.TVM_GETTOOLTIPS, 0, 0); + if (hwndToolTip !is null) OS.SendMessage (hwndToolTip, OS.TTM_POP, 0 ,0); + } + + shrink = ignoreShrink = true; + OS.SendMessage (handle, OS.TVM_DELETEITEM, 0, hItem); + ignoreShrink = false; + if ((style & SWT.MULTI) !is 0) { + ignoreDeselect = ignoreSelect = lockSelection = false; + } + if (fixRedraw) { + OS.DefWindowProc (handle, OS.WM_SETREDRAW, 1, 0); + OS.ValidateRect (handle, null); + /* + * If the item that was deleted was the last child of a tree item that + * is visible, redraw the parent item to force the + / - to be updated. + */ + if (OS.SendMessage (handle, OS.TVM_GETNEXTITEM, OS.TVGN_CHILD, hParent) is 0) { + RECT rect; + if (OS.TreeView_GetItemRect (handle, cast(HTREEITEM)hParent, &rect, false)) { + OS.InvalidateRect (handle, &rect, true); + } + } + } + int count = OS.SendMessage (handle, OS.TVM_GETCOUNT, 0, 0); + if (count is 0) { + if (imageList !is null) { + OS.SendMessage (handle, OS.TVM_SETIMAGELIST, 0, 0); + display.releaseImageList (imageList); + } + imageList = null; + if (hwndParent is null && !linesVisible) { + if (!hooks (SWT.MeasureItem) && !hooks (SWT.EraseItem) && !hooks (SWT.PaintItem)) { + customDraw = false; + } + } + items = new TreeItem [4]; + scrollWidth = 0; + setScrollWidth (); + } + updateScrollBar (); +} + +override void destroyScrollBar (int type) { + super.destroyScrollBar (type); + int bits = OS.GetWindowLong (handle, OS.GWL_STYLE); + if ((style & (SWT.H_SCROLL | SWT.V_SCROLL)) is 0) { + bits &= ~(OS.WS_HSCROLL | OS.WS_VSCROLL); + bits |= OS.TVS_NOSCROLL; + } else { + if ((style & SWT.H_SCROLL) is 0) { + bits &= ~OS.WS_HSCROLL; + bits |= OS.TVS_NOHSCROLL; + } + } + OS.SetWindowLong (handle, OS.GWL_STYLE, bits); +} + +override void enableDrag (bool enabled) { + int bits = OS.GetWindowLong (handle, OS.GWL_STYLE); + if (enabled && hooks (SWT.DragDetect)) { + bits &= ~OS.TVS_DISABLEDRAGDROP; + } else { + bits |= OS.TVS_DISABLEDRAGDROP; + } + OS.SetWindowLong (handle, OS.GWL_STYLE, bits); +} + +override void enableWidget (bool enabled) { + super.enableWidget (enabled); + /* + * Feature in Windows. When a tree is given a background color + * using TVM_SETBKCOLOR and the tree is disabled, Windows draws + * the tree using the background color rather than the disabled + * colors. This is different from the table which draws grayed. + * The fix is to set the default background color while the tree + * is disabled and restore it when enabled. + */ + Control control = findBackgroundControl (); + /* + * Bug in Windows. On Vista only, Windows does not draw using + * the background color when the tree is disabled. The fix is + * to set the default color, even when the color has not been + * changed, causing Windows to draw correctly. + */ + if (!OS.IsWinCE && OS.WIN32_VERSION >= OS.VERSION (6, 0)) { + if (control is null) control = this; + } + if (control !is null) { + if (control.backgroundImage is null) { + _setBackgroundPixel (enabled ? control.getBackgroundPixel () : -1); + } + } + if (hwndParent !is null) OS.EnableWindow (hwndParent, enabled); + + /* + * Feature in Windows. When the tree has the style + * TVS_FULLROWSELECT, the background color for the + * entire row is filled when an item is painted, + * drawing on top of the sort column color. The fix + * is to clear TVS_FULLROWSELECT when a their is + * as sort column. + */ + updateFullSelection (); +} + +bool findCell (int x, int y, inout TreeItem item, inout int index, inout RECT* cellRect, inout RECT* itemRect) { + bool found = false; + TVHITTESTINFO lpht; + lpht.pt.x = x; + lpht.pt.y = y; + OS.SendMessage (handle, OS.TVM_HITTEST, 0, &lpht); + if (lpht.hItem !is null) { + item = _getItem (lpht.hItem); + POINT pt; + pt.x = x; + pt.y = y; + auto hDC = OS.GetDC (handle); + HFONT oldFont; + auto newFont = cast(HFONT)OS.SendMessage (handle, OS.WM_GETFONT, 0, 0); + if (newFont !is null) oldFont = OS.SelectObject (hDC, newFont); + RECT rect; + if (hwndParent !is null) { + OS.GetClientRect (hwndParent, &rect); + OS.MapWindowPoints (hwndParent, handle, cast(POINT*)&rect, 2); + } else { + OS.GetClientRect (handle, &rect); + } + int count = Math.max (1, columnCount); + int [] order = new int [count]; + if (hwndHeader !is null) OS.SendMessage (hwndHeader, OS.HDM_GETORDERARRAY, count, cast(int) order.ptr); + index = 0; + bool quit = false; + while (index < count && !quit) { + auto hFont = item.fontHandle (order [index]); + if (hFont !is cast(HFONT)-1) hFont = OS.SelectObject (hDC, hFont); + cellRect = item.getBounds (order [index], true, false, true, false, true, hDC); + if (cellRect.left > rect.right) { + quit = true; + } else { + cellRect.right = Math.min (cellRect.right, rect.right); + if (OS.PtInRect ( cellRect, pt)) { + if (isCustomToolTip ()) { + Event event = sendMeasureItemEvent (item, order [index], hDC); + if (isDisposed () || item.isDisposed ()) break; + itemRect = new RECT (); + itemRect.left = event.x; + itemRect.right = event.x + event.width; + itemRect.top = event.y; + itemRect.bottom = event.y + event.height; + } else { + itemRect = item.getBounds (order [index], true, false, false, false, false, hDC); + } + if (itemRect.right > cellRect.right) found = true; + quit = true; + } + } + if (hFont !is cast(HFONT)-1) OS.SelectObject (hDC, hFont); + if (!found) index++; + } + if (newFont !is null) OS.SelectObject (hDC, oldFont); + OS.ReleaseDC (handle, hDC); + } + return found; +} + +int findIndex (HANDLE hFirstItem, HANDLE hItem) { + if (hFirstItem is null) return -1; + if (hFirstItem is hFirstIndexOf) { + if (hFirstIndexOf is hItem) { + hLastIndexOf = hFirstIndexOf; + return lastIndexOf = 0; + } + if (hLastIndexOf is hItem) return lastIndexOf; + auto hPrevItem = cast(HANDLE) OS.SendMessage (handle, OS.TVM_GETNEXTITEM, OS.TVGN_PREVIOUS, hLastIndexOf); + if (hPrevItem is hItem) { + hLastIndexOf = hPrevItem; + return --lastIndexOf; + } + HANDLE hNextItem = cast(HANDLE) OS.SendMessage (handle, OS.TVM_GETNEXTITEM, OS.TVGN_NEXT, hLastIndexOf); + if (hNextItem is hItem) { + hLastIndexOf = hNextItem; + return ++lastIndexOf; + } + int previousIndex = lastIndexOf - 1; + while (hPrevItem !is null && hPrevItem !is hItem) { + hPrevItem = cast(HANDLE) OS.SendMessage (handle, OS.TVM_GETNEXTITEM, OS.TVGN_PREVIOUS, hPrevItem); + --previousIndex; + } + if (hPrevItem is hItem) { + hLastIndexOf = hPrevItem; + return lastIndexOf = previousIndex; + } + int nextIndex = lastIndexOf + 1; + while (hNextItem !is null && hNextItem !is hItem) { + hNextItem = cast(HANDLE) OS.SendMessage (handle, OS.TVM_GETNEXTITEM, OS.TVGN_NEXT, hNextItem); + nextIndex++; + } + if (hNextItem is hItem) { + hLastIndexOf = hNextItem; + return lastIndexOf = nextIndex; + } + return -1; + } + int index = 0; + auto hNextItem = hFirstItem; + while (hNextItem !is null && hNextItem !is hItem) { + hNextItem = cast(HANDLE) OS.SendMessage (handle, OS.TVM_GETNEXTITEM, OS.TVGN_NEXT, hNextItem); + index++; + } + if (hNextItem is hItem) { + itemCount = -1; + hFirstIndexOf = hFirstItem; + hLastIndexOf = hNextItem; + return lastIndexOf = index; + } + return -1; +} + +override Widget findItem (HANDLE hItem) { + return _getItem (hItem); +} + +HANDLE findItem (HANDLE hFirstItem, int index) { + if (hFirstItem is null) return null; + if (hFirstItem is hFirstIndexOf) { + if (index is 0) { + lastIndexOf = 0; + return hLastIndexOf = hFirstIndexOf; + } + if (lastIndexOf is index) return hLastIndexOf; + if (lastIndexOf - 1 is index) { + --lastIndexOf; + return hLastIndexOf = cast(HANDLE) OS.SendMessage (handle, OS.TVM_GETNEXTITEM, OS.TVGN_PREVIOUS, hLastIndexOf); + } + if (lastIndexOf + 1 is index) { + lastIndexOf++; + return hLastIndexOf = cast(HANDLE) OS.SendMessage (handle, OS.TVM_GETNEXTITEM, OS.TVGN_NEXT, hLastIndexOf); + } + if (index < lastIndexOf) { + int previousIndex = lastIndexOf - 1; + auto hPrevItem = cast(HANDLE) OS.SendMessage (handle, OS.TVM_GETNEXTITEM, OS.TVGN_PREVIOUS, hLastIndexOf); + while (hPrevItem !is null && index < previousIndex) { + hPrevItem = cast(HANDLE) OS.SendMessage (handle, OS.TVM_GETNEXTITEM, OS.TVGN_PREVIOUS, hPrevItem); + --previousIndex; + } + if (index is previousIndex) { + lastIndexOf = previousIndex; + return hLastIndexOf = hPrevItem; + } + } else { + int nextIndex = lastIndexOf + 1; + auto hNextItem = cast(HANDLE) OS.SendMessage (handle, OS.TVM_GETNEXTITEM, OS.TVGN_NEXT, hLastIndexOf); + while (hNextItem !is null && nextIndex < index) { + hNextItem = cast(HANDLE) OS.SendMessage (handle, OS.TVM_GETNEXTITEM, OS.TVGN_NEXT, hNextItem); + nextIndex++; + } + if (index is nextIndex) { + lastIndexOf = nextIndex; + return hLastIndexOf = hNextItem; + } + } + return null; + } + int nextIndex = 0; + auto hNextItem = hFirstItem; + while (hNextItem !is null && nextIndex < index) { + hNextItem = cast(HANDLE) OS.SendMessage (handle, OS.TVM_GETNEXTITEM, OS.TVGN_NEXT, hNextItem); + nextIndex++; + } + if (index is nextIndex) { + itemCount = -1; + lastIndexOf = nextIndex; + hFirstIndexOf = hFirstItem; + return hLastIndexOf = hNextItem; + } + return null; +} + +TreeItem getFocusItem () { +// checkWidget (); + HANDLE hItem = cast(HANDLE) OS.SendMessage (handle, OS.TVM_GETNEXTITEM, OS.TVGN_CARET, 0); + return hItem !is null ? _getItem (hItem) : null; +} + +/** + * Returns the width in pixels of a grid line. + * + * @return the width of a grid line in pixels + * + * @exception SWTException <ul> + * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> + * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> + * </ul> + * + * @since 3.1 + */ +public int getGridLineWidth () { + checkWidget (); + return GRID_WIDTH; +} + +/** + * Returns the height of the receiver's header + * + * @return the height of the header or zero if the header is not visible + * + * @exception SWTException <ul> + * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> + * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> + * </ul> + * + * @since 3.1 + */ +public int getHeaderHeight () { + checkWidget (); + if (hwndHeader is null) return 0; + RECT rect; + OS.GetWindowRect (hwndHeader, &rect); + return rect.bottom - rect.top; +} + +/** + * Returns <code>true</code> if the receiver's header is visible, + * and <code>false</code> otherwise. + * <p> + * If one of the receiver's ancestors is not visible or some + * other condition makes the receiver not visible, this method + * may still indicate that it is considered visible even though + * it may not actually be showing. + * </p> + * + * @return the receiver's header's visibility state + * + * @exception SWTException <ul> + * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> + * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> + * </ul> + * + * @since 3.1 + */ +public bool getHeaderVisible () { + checkWidget (); + if (hwndHeader is null) return false; + int bits = OS.GetWindowLong (hwndHeader, OS.GWL_STYLE); + return (bits & OS.WS_VISIBLE) !is 0; +} + +Point getImageSize () { + if (imageList !is null) return imageList.getImageSize (); + return new Point (0, getItemHeight ()); +} + +HANDLE getBottomItem () { + HANDLE hItem = cast(HANDLE) OS.SendMessage (handle, OS.TVM_GETNEXTITEM, OS.TVGN_FIRSTVISIBLE, 0); + if (hItem is null) return null; + int index = 0, count = OS.SendMessage (handle, OS.TVM_GETVISIBLECOUNT, 0, 0); + while (index < count) { + HANDLE hNextItem = cast(HANDLE) OS.SendMessage (handle, OS.TVM_GETNEXTITEM, OS.TVGN_NEXTVISIBLE, hItem); + if (hNextItem is null) return hItem; + hItem = hNextItem; + index++; + } + return hItem; +} + +/** + * Returns the column at the given, zero-relative index in the + * receiver. Throws an exception if the index is out of range. + * Columns are returned in the order that they were created. + * If no <code>TreeColumn</code>s were created by the programmer, + * this method will throw <code>ERROR_INVALID_RANGE</code> despite + * the fact that a single column of data may be visible in the tree. + * This occurs when the programmer uses the tree like a list, adding + * items but never creating a column. + * + * @param index the index of the column to return + * @return the column at the given index + * + * @exception IllegalArgumentException <ul> + * <li>ERROR_INVALID_RANGE - if the index is not between 0 and the number of elements in the list minus 1 (inclusive)</li> + * </ul> + * @exception SWTException <ul> + * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> + * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> + * </ul> + * + * @see Tree#getColumnOrder() + * @see Tree#setColumnOrder(int[]) + * @see TreeColumn#getMoveable() + * @see TreeColumn#setMoveable(bool) + * @see SWT#Move + * + * @since 3.1 + */ +public TreeColumn getColumn (int index) { + checkWidget (); + if (!(0 <= index && index < columnCount)) error (SWT.ERROR_INVALID_RANGE); + return columns [index]; +} + +/** + * Returns the number of columns contained in the receiver. + * If no <code>TreeColumn</code>s were created by the programmer, + * this value is zero, despite the fact that visually, one column + * of items may be visible. This occurs when the programmer uses + * the tree like a list, adding items but never creating a column. + * + * @return the number of columns + * + * @exception SWTException <ul> + * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> + * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> + * </ul> + * + * @since 3.1 + */ +public int getColumnCount () { + checkWidget (); + return columnCount; +} + +/** + * Returns an array of zero-relative integers that map + * the creation order of the receiver's items to the + * order in which they are currently being displayed. + * <p> + * Specifically, the indices of the returned array represent + * the current visual order of the items, and the contents + * of the array represent the creation order of the items. + * </p><p> + * Note: This is not the actual structure used by the receiver + * to maintain its list of items, so modifying the array will + * not affect the receiver. + * </p> + * + * @return the current visual order of the receiver's items + * + * @exception SWTException <ul> + * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> + * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> + * </ul> + * + * @see Tree#setColumnOrder(int[]) + * @see TreeColumn#getMoveable() + * @see TreeColumn#setMoveable(bool) + * @see SWT#Move + * + * @since 3.2 + */ +public int[] getColumnOrder () { + checkWidget (); + if (columnCount is 0) return null; + int [] order = new int [columnCount]; + OS.SendMessage (hwndHeader, OS.HDM_GETORDERARRAY, columnCount, order.ptr); + return order; +} + +/** + * Returns an array of <code>TreeColumn</code>s which are the + * columns in the receiver. Columns are returned in the order + * that they were created. If no <code>TreeColumn</code>s were + * created by the programmer, the array is empty, despite the fact + * that visually, one column of items may be visible. This occurs + * when the programmer uses the tree like a list, adding items but + * never creating a column. + * <p> + * Note: This is not the actual structure used by the receiver + * to maintain its list of items, so modifying the array will + * not affect the receiver. + * </p> + * + * @return the items in the receiver + * + * @exception SWTException <ul> + * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> + * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> + * </ul> + * + * @see Tree#getColumnOrder() + * @see Tree#setColumnOrder(int[]) + * @see TreeColumn#getMoveable() + * @see TreeColumn#setMoveable(bool) + * @see SWT#Move + * + * @since 3.1 + */ +public TreeColumn [] getColumns () { + checkWidget (); + TreeColumn [] result = new TreeColumn [columnCount]; + System.arraycopy (columns, 0, result, 0, columnCount); + return result; +} + +/** + * Returns the item at the given, zero-relative index in the + * receiver. Throws an exception if the index is out of range. + * + * @param index the index of the item to return + * @return the item at the given index + * + * @exception IllegalArgumentException <ul> + * <li>ERROR_INVALID_RANGE - if the index is not between 0 and the number of elements in the list minus 1 (inclusive)</li> + * </ul> + * @exception SWTException <ul> + * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> + * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> + * </ul> + * + * @since 3.1 + */ +public TreeItem getItem (int index) { + checkWidget (); + if (index < 0) error (SWT.ERROR_INVALID_RANGE); + auto hFirstItem = cast(HANDLE) OS.SendMessage (handle, OS.TVM_GETNEXTITEM, OS.TVGN_ROOT, 0); + if (hFirstItem is null) error (SWT.ERROR_INVALID_RANGE); + HANDLE hItem = findItem (hFirstItem, index); + if (hItem is null) error (SWT.ERROR_INVALID_RANGE); + return _getItem (hItem); +} + +TreeItem getItem (NMTVCUSTOMDRAW* nmcd) { + /* + * Bug in Windows. If the lParam field of TVITEM + * is changed during custom draw using TVM_SETITEM, + * the lItemlParam field of the NMTVCUSTOMDRAW struct + * is not updated until the next custom draw. The + * fix is to query the field from the item instead + * of using the struct. + */ + int id = nmcd.nmcd.lItemlParam; + if ((style & SWT.VIRTUAL) !is 0) { + if (id is -1) { + TVITEM tvItem; + tvItem.mask = OS.TVIF_HANDLE | OS.TVIF_PARAM; + tvItem.hItem = cast(HTREEITEM) nmcd.nmcd.dwItemSpec; + OS.SendMessage (handle, OS.TVM_GETITEM, 0, &tvItem); + id = tvItem.lParam; + } + } + return _getItem (cast(HANDLE) nmcd.nmcd.dwItemSpec, id); +} + +/** + * Returns the item at the given point in the receiver + * or null if no such item exists. The point is in the + * coordinate system of the receiver. + * <p> + * The item that is returned represents an item that could be selected by the user. + * For example, if selection only occurs in items in the first column, then null is + * returned if the point is outside of the item. + * Note that the SWT.FULL_SELECTION style hint, which specifies the selection policy, + * determines the extent of the selection. + * </p> + * + * @param point the point used to locate the item + * @return the item at the given point, or null if the point is not in a selectable item + * + * @exception IllegalArgumentException <ul> + * <li>ERROR_NULL_ARGUMENT - if the point is null</li> + * </ul> + * @exception SWTException <ul> + * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> + * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> + * </ul> + */ +public TreeItem getItem (Point point) { + checkWidget (); + if (point is null) error (SWT.ERROR_NULL_ARGUMENT); + TVHITTESTINFO lpht; + lpht.pt.x = point.x; + lpht.pt.y = point.y; + OS.SendMessage (handle, OS.TVM_HITTEST, 0, &lpht); + if (lpht.hItem !is null) { + int flags = OS.TVHT_ONITEM; + if ((style & SWT.FULL_SELECTION) !is 0) { + flags |= OS.TVHT_ONITEMRIGHT | OS.TVHT_ONITEMINDENT; + } else { + if (hooks (SWT.MeasureItem)) { + lpht.flags &= ~(OS.TVHT_ONITEMICON | OS.TVHT_ONITEMLABEL); + if (hitTestSelection ( lpht.hItem, lpht.pt.x, lpht.pt.y)) { + lpht.flags |= OS.TVHT_ONITEMICON | OS.TVHT_ONITEMLABEL; + } + } + } + if ((lpht.flags & flags) !is 0) return _getItem (lpht.hItem); + } + return null; +} + +/** + * Returns the number of items contained in the receiver + * that are direct item children of the receiver. The + * number that is returned is the number of roots in the + * tree. + * + * @return the number of items + * + * @exception SWTException <ul> + * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> + * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> + * </ul> + */ +public int getItemCount () { + checkWidget (); + HANDLE hItem = cast(HANDLE) OS.SendMessage (handle, OS.TVM_GETNEXTITEM, OS.TVGN_ROOT, 0); + if (hItem is null) return 0; + return getItemCount (hItem); +} + +int getItemCount (HANDLE hItem) { + int count = 0; + auto hFirstItem = hItem; + if (hItem is hFirstIndexOf) { + if (itemCount !is -1) return itemCount; + hFirstItem = hLastIndexOf; + count = lastIndexOf; + } + while (hFirstItem !is null) { + hFirstItem = cast(HANDLE) OS.SendMessage (handle, OS.TVM_GETNEXTITEM, OS.TVGN_NEXT, hFirstItem); + count++; + } + if (hItem is hFirstIndexOf) itemCount = count; + return count; +} + +/** + * Returns the height of the area which would be used to + * display <em>one</em> of the items in the tree. + * + * @return the height of one item + * + * @exception SWTException <ul> + * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> + * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> + * </ul> + */ +public int getItemHeight () { + checkWidget (); + return OS.SendMessage (handle, OS.TVM_GETITEMHEIGHT, 0, 0); +} + +/** + * Returns a (possibly empty) array of items contained in the + * receiver that are direct item children of the receiver. These + * are the roots of the tree. + * <p> + * Note: This is not the actual structure used by the receiver + * to maintain its list of items, so modifying the array will + * not affect the receiver. + * </p> + * + * @return the items + * + * @exception SWTException <ul> + * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> + * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> + * </ul> + */ +public TreeItem [] getItems () { + checkWidget (); + HANDLE hItem = cast(HANDLE) OS.SendMessage (handle, OS.TVM_GETNEXTITEM, OS.TVGN_ROOT, 0); + if (hItem is null) return null; + return getItems (hItem); +} + +TreeItem [] getItems (HANDLE hTreeItem) { + int count = 0; + auto hItem = hTreeItem; + while (hItem !is null) { + hItem = cast(HANDLE) OS.SendMessage (handle, OS.TVM_GETNEXTITEM, OS.TVGN_NEXT, hItem); + count++; + } + int index = 0; + TreeItem [] result = new TreeItem [count]; + TVITEM tvItem; + tvItem.mask = OS.TVIF_HANDLE | OS.TVIF_PARAM; + tvItem.hItem = cast(HTREEITEM)hTreeItem; + /* + * Feature in Windows. In some cases an expand or collapse message + * can occur from within TVM_DELETEITEM. When this happens, the item + * being destroyed has been removed from the list of items but has not + * been deleted from the tree. The fix is to check for null items and + * remove them from the list. + */ + while (tvItem.hItem !is null) { + OS.SendMessage (handle, OS.TVM_GETITEM, 0, &tvItem); + TreeItem item = _getItem (tvItem.hItem, tvItem.lParam); + if (item !is null) result [index++] = item; + tvItem.hItem = cast(HTREEITEM) OS.SendMessage (handle, OS.TVM_GETNEXTITEM, OS.TVGN_NEXT, tvItem.hItem); + } + if (index !is count) { + TreeItem [] newResult = new TreeItem [index]; + System.arraycopy (result, 0, newResult, 0, index); + result = newResult; + } + return result; +} + +/** + * Returns <code>true</code> if the receiver's lines are visible, + * and <code>false</code> otherwise. + * <p> + * If one of the receiver's ancestors is not visible or some + * other condition makes the receiver not visible, this method + * may still indicate that it is considered visible even though + * it may not actually be showing. + * </p> + * + * @return the visibility state of the lines + * + * @exception SWTException <ul> + * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> + * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> + * </ul> + * + * @since 3.1 + */ +public bool getLinesVisible () { + checkWidget (); + return linesVisible; +} + +HANDLE getNextSelection (HANDLE hItem, TVITEM* tvItem) { + while (hItem !is null) { + int state = 0; + static if (OS.IsWinCE) { + tvItem.hItem = hItem; + OS.SendMessage (handle, OS.TVM_GETITEM, 0, tvItem); + state = tvItem.state; + } else { + state = OS.SendMessage (handle, OS.TVM_GETITEMSTATE, hItem, OS.TVIS_SELECTED); + } + if ((state & OS.TVIS_SELECTED) !is 0) return hItem; + auto hFirstItem = cast(HANDLE) OS.SendMessage (handle, OS.TVM_GETNEXTITEM, OS.TVGN_CHILD, hItem); + auto hSelected = getNextSelection (hFirstItem, tvItem); + if (hSelected !is null) return hSelected; + hItem = cast(HANDLE) OS.SendMessage (handle, OS.TVM_GETNEXTITEM, OS.TVGN_NEXT, hItem); + } + return null; +} + +/** + * Returns the receiver's parent item, which must be a + * <code>TreeItem</code> or null when the receiver is a + * root. + * + * @return the receiver's parent item + * + * @exception SWTException <ul> + * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> + * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> + * </ul> + */ +public TreeItem getParentItem () { + checkWidget (); + return null; +} + +int getSelection (HANDLE hItem, TVITEM* tvItem, TreeItem [] selection, int index, int count, bool bigSelection, bool all) { + while (hItem !is null) { + if (OS.IsWinCE || bigSelection) { + tvItem.hItem = cast(HTREEITEM)hItem; + OS.SendMessage (handle, OS.TVM_GETITEM, 0, tvItem); + if ((tvItem.state & OS.TVIS_SELECTED) !is 0) { + if (selection !is null && index < selection.length) { + selection [index] = _getItem (hItem, tvItem.lParam); + } + index++; + } + } else { + int state = OS.SendMessage (handle, OS.TVM_GETITEMSTATE, hItem, OS.TVIS_SELECTED); + if ((state & OS.TVIS_SELECTED) !is 0) { + if (tvItem !is null && selection !is null && index < selection.length) { + tvItem.hItem = cast(HTREEITEM)hItem; + OS.SendMessage (handle, OS.TVM_GETITEM, 0, tvItem); + selection [index] = _getItem (hItem, tvItem.lParam); + } + index++; + } + } + if (index is count) break; + if (all) { + auto hFirstItem = cast(HANDLE) OS.SendMessage (handle, OS.TVM_GETNEXTITEM, OS.TVGN_CHILD, hItem); + if ((index = getSelection (hFirstItem, tvItem, selection, index, count, bigSelection, all)) is count) { + break; + } + hItem = cast(HANDLE) OS.SendMessage (handle, OS.TVM_GETNEXTITEM, OS.TVGN_NEXT, hItem); + } else { + hItem = cast(HANDLE) OS.SendMessage (handle, OS.TVM_GETNEXTITEM, OS.TVGN_NEXTVISIBLE, hItem); + } + } + return index; +} + +/** + * Returns an array of <code>TreeItem</code>s that are currently + * selected in the receiver. The order of the items is unspecified. + * An empty array indicates that no items are selected. + * <p> + * Note: This is not the actual structure used by the receiver + * to maintain its selection, so modifying the array will + * not affect the receiver. + * </p> + * @return an array representing the selection + * + * @exception SWTException <ul> + * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> + * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> + * </ul> + */ +public TreeItem [] getSelection () { + checkWidget (); + if ((style & SWT.SINGLE) !is 0) { + HANDLE hItem = cast(HANDLE) OS.SendMessage (handle, OS.TVM_GETNEXTITEM, OS.TVGN_CARET, 0); + if (hItem is null) return new TreeItem [0]; + TVITEM tvItem; + tvItem.mask = OS.TVIF_HANDLE | OS.TVIF_PARAM | OS.TVIF_STATE; + tvItem.hItem = cast(HTREEITEM)hItem; + OS.SendMessage (handle, OS.TVM_GETITEM, 0, &tvItem); + if ((tvItem.state & OS.TVIS_SELECTED) is 0) return new TreeItem [0]; + return [_getItem (tvItem.hItem, tvItem.lParam)]; + } + int count = 0; + TreeItem [] guess = new TreeItem [(style & SWT.VIRTUAL) !is 0 ? 8 : 1]; + int /*long*/ oldProc = OS.GetWindowLongPtr (handle, OS.GWLP_WNDPROC); + OS.SetWindowLongPtr (handle, OS.GWLP_WNDPROC, cast(LONG_PTR)TreeProc); + if ((style & SWT.VIRTUAL) !is 0) { + TVITEM tvItem; + tvItem.mask = OS.TVIF_HANDLE | OS.TVIF_PARAM | OS.TVIF_STATE; + HANDLE hItem = cast(HANDLE) OS.SendMessage (handle, OS.TVM_GETNEXTITEM, OS.TVGN_ROOT, 0); + count = getSelection (hItem, &tvItem, guess, 0, -1, false, true); + } else { + TVITEM tvItem; + static if (OS.IsWinCE) { + //tvItem = new TVITEM (); + tvItem.mask = OS.TVIF_STATE; + } + for (int i=0; i<items.length; i++) { + TreeItem item = items [i]; + if (item !is null) { + HANDLE hItem = item.handle; + int state = 0; + static if (OS.IsWinCE) { + tvItem.hItem = hItem; + OS.SendMessage (handle, OS.TVM_GETITEM, 0, &tvItem); + state = tvItem.state; + } else { + state = OS.SendMessage (handle, OS.TVM_GETITEMSTATE, hItem, OS.TVIS_SELECTED); + } + if ((state & OS.TVIS_SELECTED) !is 0) { + if (count < guess.length) guess [count] = item; + count++; + } + } + } + } + OS.SetWindowLongPtr (handle, OS.GWLP_WNDPROC, oldProc); + if (count is 0) return new TreeItem [0]; + if (count is guess.length) return guess; + TreeItem [] result = new TreeItem [count]; + if (count < guess.length) { + System.arraycopy (guess, 0, result, 0, count); + return result; + } + OS.SetWindowLongPtr (handle, OS.GWLP_WNDPROC, cast(LONG_PTR)TreeProc); + TVITEM tvItem; + tvItem.mask = OS.TVIF_HANDLE | OS.TVIF_PARAM | OS.TVIF_STATE; + HANDLE hItem = cast(HANDLE) OS.SendMessage (handle, OS.TVM_GETNEXTITEM, OS.TVGN_ROOT, 0); + int itemCount = OS.SendMessage (handle, OS.TVM_GETCOUNT, 0, 0); + bool bigSelection = result.length > itemCount / 2; + if (count !is getSelection (hItem, &tvItem, result, 0, count, bigSelection, false)) { + getSelection (hItem, &tvItem, result, 0, count, bigSelection, true); + } + OS.SetWindowLongPtr (handle, OS.GWLP_WNDPROC, oldProc); + return result; +} + +/** + * Returns the number of selected items contained in the receiver. + * + * @return the number of selected items + * + * @exception SWTException <ul> + * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> + * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> + * </ul> + */ +public int getSelectionCount () { + checkWidget (); + if ((style & SWT.SINGLE) !is 0) { + HANDLE hItem = cast(HANDLE) OS.SendMessage (handle, OS.TVM_GETNEXTITEM, OS.TVGN_CARET, 0); + if (hItem is null) return 0; + int state = 0; + static if (OS.IsWinCE) { + TVITEM tvItem; + tvItem.hItem = hItem; + tvItem.mask = OS.TVIF_STATE; + OS.SendMessage (handle, OS.TVM_GETITEM, 0, &tvItem); + state = tvItem.state; + } else { + state = OS.SendMessage (handle, OS.TVM_GETITEMSTATE, hItem, OS.TVIS_SELECTED); + } + return (state & OS.TVIS_SELECTED) is 0 ? 0 : 1; + } + int count = 0; + int /*long*/ oldProc = OS.GetWindowLongPtr (handle, OS.GWLP_WNDPROC); + TVITEM tvItem_; + TVITEM* tvItem = null; + static if (OS.IsWinCE) { + tvItem = &tvitem_; + tvItem.mask = OS.TVIF_STATE; + } + OS.SetWindowLongPtr (handle, OS.GWLP_WNDPROC, cast(LONG_PTR)TreeProc); + if ((style & SWT.VIRTUAL) !is 0) { + HANDLE hItem = cast(HANDLE) OS.SendMessage (handle, OS.TVM_GETNEXTITEM, OS.TVGN_ROOT, 0); + count = getSelection (hItem, tvItem, null, 0, -1, false, true); + } else { + for (int i=0; i<items.length; i++) { + TreeItem item = items [i]; + if (item !is null) { + auto hItem = item.handle; + int state = 0; + static if (OS.IsWinCE) { + tvItem.hItem = hItem; + OS.SendMessage (handle, OS.TVM_GETITEM, 0, tvItem); + state = tvItem.state; + } else { + state = OS.SendMessage (handle, OS.TVM_GETITEMSTATE, hItem, OS.TVIS_SELECTED); + } + if ((state & OS.TVIS_SELECTED) !is 0) count++; + } + } + } + OS.SetWindowLongPtr (handle, OS.GWLP_WNDPROC, oldProc); + return count; +} + +/** + * Returns the column which shows the sort indicator for + * the receiver. The value may be null if no column shows + * the sort indicator. + * + * @return the sort indicator + * + * @exception SWTException <ul> + * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> + * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> + * </ul> + * + * @see #setSortColumn(TreeColumn) + * + * @since 3.2 + */ +public TreeColumn getSortColumn () { + checkWidget (); + return sortColumn; +} + +int getSortColumnPixel () { + int pixel = OS.IsWindowEnabled (handle) ? getBackgroundPixel () : OS.GetSysColor (OS.COLOR_3DFACE); + int red = pixel & 0xFF; + int green = (pixel & 0xFF00) >> 8; + int blue = (pixel & 0xFF0000) >> 16; + if (red > 240 && green > 240 && blue > 240) { + red -= 8; + green -= 8; + blue -= 8; + } else { + red = Math.min (0xFF, (red / 10) + red); + green = Math.min (0xFF, (green / 10) + green); + blue = Math.min (0xFF, (blue / 10) + blue); + } + return (red & 0xFF) | ((green & 0xFF) << 8) | ((blue & 0xFF) << 16); +} + +/** + * Returns the direction of the sort indicator for the receiver. + * The value will be one of <code>UP</code>, <code>DOWN</code> + * or <code>NONE</code>. + * + * @return the sort direction + * + * @exception SWTException <ul> + * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> + * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> + * </ul> + * + * @see #setSortDirection(int) + * + * @since 3.2 + */ +public int getSortDirection () { + checkWidget (); + return sortDirection; +} + +/** + * Returns the item which is currently at the top of the receiver. + * This item can change when items are expanded, collapsed, scrolled + * or new items are added or removed. + * + * @return the item at the top of the receiver + * + * @exception SWTException <ul> + * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> + * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> + * </ul> + * + * @since 2.1 + */ +public TreeItem getTopItem () { + checkWidget (); + HANDLE hItem = cast(HANDLE) OS.SendMessage (handle, OS.TVM_GETNEXTITEM, OS.TVGN_FIRSTVISIBLE, 0); + return hItem !is null ? _getItem (hItem) : null; +} + +bool hitTestSelection (HANDLE hItem, int x, int y) { + if (hItem is null) return false; + TreeItem item = _getItem (hItem); + if (item is null) return false; + if (!hooks (SWT.MeasureItem)) return false; + bool result = false; + + //BUG? - moved columns, only hittest first column + //BUG? - check drag detect + int [] order = new int [1], index = new int [1]; + + auto hDC = OS.GetDC (handle); + HFONT oldFont, newFont = cast(HFONT)OS.SendMessage (handle, OS.WM_GETFONT, 0, 0); + if (newFont !is null) oldFont = OS.SelectObject (hDC, newFont); + auto hFont = item.fontHandle (order [index [0]]); + if (hFont !is cast(HFONT)-1) hFont = OS.SelectObject (hDC, hFont); + Event event = sendMeasureItemEvent (item, order [index [0]], hDC); + if (event.getBounds ().contains (x, y)) result = true; + if (newFont !is null) OS.SelectObject (hDC, oldFont); + OS.ReleaseDC (handle, hDC); +// if (isDisposed () || item.isDisposed ()) return false; + return result; +} + +int imageIndex (Image image, int index) { + if (image is null) return OS.I_IMAGENONE; + if (imageList is null) { + Rectangle bounds = image.getBounds (); + imageList = display.getImageList (style & SWT.RIGHT_TO_LEFT, bounds.width, bounds.height); + } + int imageIndex = imageList.indexOf (image); + if (imageIndex is -1) imageIndex = imageList.add (image); + if (hwndHeader is null || OS.SendMessage (hwndHeader, OS.HDM_ORDERTOINDEX, 0, 0) is index) { + /* + * Feature in Windows. When setting the same image list multiple + * times, Windows does work making this operation slow. The fix + * is to test for the same image list before setting the new one. + */ + auto hImageList = imageList.getHandle (); + auto hOldImageList = cast(HANDLE) OS.SendMessage (handle, OS.TVM_GETIMAGELIST, OS.TVSIL_NORMAL, 0); + if (hOldImageList !is hImageList) { + OS.SendMessage (handle, OS.TVM_SETIMAGELIST, OS.TVSIL_NORMAL, hImageList); + updateScrollBar (); + } + } + return imageIndex; +} + +int imageIndexHeader (Image image) { + if (image is null) return OS.I_IMAGENONE; + if (headerImageList is null) { + Rectangle bounds = image.getBounds (); + headerImageList = display.getImageList (style & SWT.RIGHT_TO_LEFT, bounds.width, bounds.height); + int index = headerImageList.indexOf (image); + if (index is -1) index = headerImageList.add (image); + auto hImageList = headerImageList.getHandle (); + if (hwndHeader !is null) { + OS.SendMessage (hwndHeader, OS.HDM_SETIMAGELIST, 0, hImageList); + } + updateScrollBar (); + return index; + } + int index = headerImageList.indexOf (image); + if (index !is -1) return index; + return headerImageList.add (image); +} + +/** + * Searches the receiver's list starting at the first column + * (index 0) until a column is found that is equal to the + * argument, and returns the index of that column. If no column + * is found, returns -1. + * + * @param column the search column + * @return the index of the column + * + * @exception IllegalArgumentException <ul> + * <li>ERROR_NULL_ARGUMENT - if the column is null</li> + * </ul> + * @exception SWTException <ul> + * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> + * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> + * </ul> + * + * @since 3.1 + */ +public int indexOf (TreeColumn column) { + checkWidget (); + if (column is null) error (SWT.ERROR_NULL_ARGUMENT); + if (column.isDisposed()) error(SWT.ERROR_INVALID_ARGUMENT); + for (int i=0; i<columnCount; i++) { + if (columns [i] is column) return i; + } + return -1; +} + +/** + * Searches the receiver's list starting at the first item + * (index 0) until an item is found that is equal to the + * argument, and returns the index of that item. If no item + * is found, returns -1. + * + * @param item the search item + * @return the index of the item + * + * @exception IllegalArgumentException <ul> + * <li>ERROR_NULL_ARGUMENT - if the item is null</li> + * <li>ERROR_INVALID_ARGUMENT - if the item has been disposed</li> + * </ul> + * @exception SWTException <ul> + * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> + * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> + * </ul> + * + * @since 3.1 + */ +public int indexOf (TreeItem item) { + checkWidget (); + if (item is null) error (SWT.ERROR_NULL_ARGUMENT); + if (item.isDisposed()) error(SWT.ERROR_INVALID_ARGUMENT); + HANDLE hItem = cast(HANDLE) OS.SendMessage (handle, OS.TVM_GETNEXTITEM, OS.TVGN_ROOT, 0); + return hItem is null ? -1 : findIndex (hItem, item.handle); +} + +bool isCustomToolTip () { + return hooks (SWT.MeasureItem); +} + +bool isItemSelected (NMTVCUSTOMDRAW* nmcd) { + bool selected = false; + if (OS.IsWindowEnabled (handle)) { + TVITEM tvItem; + tvItem.mask = OS.TVIF_HANDLE | OS.TVIF_STATE; + tvItem.hItem = cast(HTREEITEM)nmcd.nmcd.dwItemSpec; + OS.SendMessage (handle, OS.TVM_GETITEM, 0, &tvItem); + if ((tvItem.state & (OS.TVIS_SELECTED | OS.TVIS_DROPHILITED)) !is 0) { + selected = true; + /* + * Feature in Windows. When the mouse is pressed and the + * selection is first drawn for a tree, the previously + * selected item is redrawn but the the TVIS_SELECTED bits + * are not cleared. When the user moves the mouse slightly + * and a drag and drop operation is not started, the item is + * drawn again and this time with TVIS_SELECTED is cleared. + * This means that an item that contains colored cells will + * not draw with the correct background until the mouse is + * moved. The fix is to test for the selection colors and + * guess that the item is not selected. + * + * NOTE: This code does not work when the foreground and + * background of the tree are set to the selection colors + * but this does not happen in a regular application. + */ + if (handle is OS.GetFocus ()) { + if (OS.GetTextColor (nmcd.nmcd.hdc) !is OS.GetSysColor (OS.COLOR_HIGHLIGHTTEXT)) { + selected = false; + } else { + if (OS.GetBkColor (nmcd.nmcd.hdc) !is OS.GetSysColor (OS.COLOR_HIGHLIGHT)) { + selected = false; + } + } + } + } else { + if (nmcd.nmcd.dwDrawStage is OS.CDDS_ITEMPOSTPAINT) { + /* + * Feature in Windows. When the mouse is pressed and the + * selection is first drawn for a tree, the item is drawn + * selected, but the TVIS_SELECTED bits for the item are + * not set. When the user moves the mouse slightly and + * a drag and drop operation is not started, the item is + * drawn again and this time TVIS_SELECTED is set. This + * means that an item that is in a tree that has the style + * TVS_FULLROWSELECT and that also contains colored cells + * will not draw the entire row selected until the user + * moves the mouse. The fix is to test for the selection + * colors and guess that the item is selected. + * + * NOTE: This code does not work when the foreground and + * background of the tree are set to the selection colors + * but this does not happen in a regular application. + */ + if (OS.GetTextColor (nmcd.nmcd.hdc) is OS.GetSysColor (OS.COLOR_HIGHLIGHTTEXT)) { + if (OS.GetBkColor (nmcd.nmcd.hdc) is OS.GetSysColor (OS.COLOR_HIGHLIGHT)) { + selected = true; + } + } + } + } + } + return selected; +} + +void redrawSelection () { + if ((style & SWT.SINGLE) !is 0) { + HANDLE hItem = cast(HANDLE) OS.SendMessage (handle, OS.TVM_GETNEXTITEM, OS.TVGN_CARET, 0); + if (hItem !is null) { + RECT rect; + if (OS.TreeView_GetItemRect (handle, cast(HTREEITEM)hItem, &rect, false)) { + OS.InvalidateRect (handle, &rect, true); + } + } + } else { + HANDLE hItem = cast(HANDLE) OS.SendMessage (handle, OS.TVM_GETNEXTITEM, OS.TVGN_FIRSTVISIBLE, 0); + if (hItem !is null) { + TVITEM tvItem; + static if (OS.IsWinCE) { + //tvItem = new TVITEM (); + tvItem.mask = OS.TVIF_STATE; + } + RECT rect; + int index = 0, count = OS.SendMessage (handle, OS.TVM_GETVISIBLECOUNT, 0, 0); + while (index <= count && hItem !is null) { + int state = 0; + static if (OS.IsWinCE) { + tvItem.hItem = hItem; + OS.SendMessage (handle, OS.TVM_GETITEM, 0, &tvItem); + state = tvItem.state; + } else { + state = OS.SendMessage (handle, OS.TVM_GETITEMSTATE, hItem, OS.TVIS_SELECTED); + } + if ((state & OS.TVIS_SELECTED) !is 0) { + if (OS.TreeView_GetItemRect (handle, cast(HTREEITEM)hItem, &rect, false)) { + OS.InvalidateRect (handle, &rect, true); + } + } + hItem = cast(HANDLE) OS.SendMessage (handle, OS.TVM_GETNEXTITEM, OS.TVGN_NEXTVISIBLE, hItem); + index++; + } + } + } +} + +override void register () { + super.register (); + if (hwndParent !is null) display.addControl (hwndParent, this); + if (hwndHeader !is null) display.addControl (hwndHeader, this); +} + +void releaseItem (HANDLE hItem, TVITEM* tvItem, bool release) { + if (hItem is hAnchor) hAnchor = null; + if (hItem is hInsert) hInsert = null; + tvItem.hItem = cast(HTREEITEM)hItem; + if (OS.SendMessage (handle, OS.TVM_GETITEM, 0, tvItem) !is 0) { + if (tvItem.lParam !is -1) { + if (tvItem.lParam < lastID) lastID = tvItem.lParam; + if (release) { + TreeItem item = items [tvItem.lParam]; + if (item !is null) item.release (false); + } + items [tvItem.lParam] = null; + } + } +} + +void releaseItems (HANDLE hItem, TVITEM* tvItem) { + hItem = cast(HANDLE) OS.SendMessage (handle, OS.TVM_GETNEXTITEM, OS.TVGN_CHILD, hItem); + while (hItem !is null) { + releaseItems (hItem, tvItem); + releaseItem (hItem, tvItem, true); + hItem = cast(HANDLE) OS.SendMessage (handle, OS.TVM_GETNEXTITEM, OS.TVGN_NEXT, hItem); + } +} + +override void releaseHandle () { + super.releaseHandle (); + hwndParent = hwndHeader = null; +} + +override void releaseChildren (bool destroy) { + if (items !is null) { + for (int i=0; i<items.length; i++) { + TreeItem item = items [i]; + if (item !is null && !item.isDisposed ()) { + item.release (false); + } + } + items = null; + } + if (columns !is null) { + for (int i=0; i<columns.length; i++) { + TreeColumn column = columns [i]; + if (column !is null && !column.isDisposed ()) { + column.release (false); + } + } + columns = null; + } + super.releaseChildren (destroy); +} + +override void releaseWidget () { + super.releaseWidget (); + /* + * Feature in Windows. For some reason, when TVM_GETIMAGELIST + * or TVM_SETIMAGELIST is sent, the tree issues NM_CUSTOMDRAW + * messages. This behavior is unwanted when the tree is being + * disposed. The fix is to ignore NM_CUSTOMDRAW messages by + * clearing the custom draw flag. + * + * NOTE: This only happens on Windows XP. + */ + customDraw = false; + if (imageList !is null) { + OS.SendMessage (handle, OS.TVM_SETIMAGELIST, OS.TVSIL_NORMAL, 0); + display.releaseImageList (imageList); + } + if (headerImageList !is null) { + if (hwndHeader !is null) { + OS.SendMessage (hwndHeader, OS.HDM_SETIMAGELIST, 0, 0); + } + display.releaseImageList (headerImageList); + } + imageList = headerImageList = null; + auto hStateList = cast(HANDLE) OS.SendMessage (handle, OS.TVM_GETIMAGELIST, OS.TVSIL_STATE, 0); + OS.SendMessage (handle, OS.TVM_SETIMAGELIST, OS.TVSIL_STATE, 0); + if (hStateList !is null) OS.ImageList_Destroy (hStateList); + if (itemToolTipHandle !is null) OS.DestroyWindow (itemToolTipHandle); + if (headerToolTipHandle !is null) OS.DestroyWindow (headerToolTipHandle); + itemToolTipHandle = headerToolTipHandle = null; +} + +/** + * Removes all of the items from the receiver. + * + * @exception SWTException <ul> + * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> + * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> + * </ul> + */ +public void removeAll () { + checkWidget (); + hFirstIndexOf = hLastIndexOf = null; + itemCount = -1; + for (int i=0; i<items.length; i++) { + TreeItem item = items [i]; + if (item !is null && !item.isDisposed ()) { + item.release (false); + } + } + ignoreDeselect = ignoreSelect = true; + bool redraw = drawCount is 0 && OS.IsWindowVisible (handle); + if (redraw) OS.DefWindowProc (handle, OS.WM_SETREDRAW, 0, 0); + shrink = ignoreShrink = true; + int /*long*/ result = OS.SendMessage (handle, OS.TVM_DELETEITEM, 0, OS.TVI_ROOT); + ignoreShrink = false; + if (redraw) { + OS.DefWindowProc (handle, OS.WM_SETREDRAW, 1, 0); + OS.InvalidateRect (handle, null, true); + } + ignoreDeselect = ignoreSelect = false; + if (result is 0) error (SWT.ERROR_ITEM_NOT_REMOVED); + if (imageList !is null) { + OS.SendMessage (handle, OS.TVM_SETIMAGELIST, 0, 0); + display.releaseImageList (imageList); + } + imageList = null; + if (hwndParent is null && !linesVisible) { + if (!hooks (SWT.MeasureItem) && !hooks (SWT.EraseItem) && !hooks (SWT.PaintItem)) { + customDraw = false; + } + } + hAnchor = hInsert = hFirstIndexOf = hLastIndexOf = null; + itemCount = -1; + items = new TreeItem [4]; + scrollWidth = 0; + setScrollWidth (); + updateScrollBar (); +} + +/** + * Removes the listener from the collection of listeners who will + * be notified when the user changes the receiver's selection. + * + * @param listener the listener which should no longer be notified + * + * @exception IllegalArgumentException <ul> + * <li>ERROR_NULL_ARGUMENT - if the listener is null</li> + * </ul> + * @exception SWTException <ul> + * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> + * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> + * </ul> + * + * @see SelectionListener + * @see #addSelectionListener + */ +public void removeSelectionListener (SelectionListener listener) { + checkWidget (); + if (listener is null) error (SWT.ERROR_NULL_ARGUMENT); + eventTable.unhook (SWT.Selection, listener); + eventTable.unhook (SWT.DefaultSelection, listener); +} + +/** + * Removes the listener from the collection of listeners who will + * be notified when items in the receiver are expanded or collapsed. + * + * @param listener the listener which should no longer be notified + * + * @exception IllegalArgumentException <ul> + * <li>ERROR_NULL_ARGUMENT - if the listener is null</li> + * </ul> + * @exception SWTException <ul> + * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> + * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> + * </ul> + * + * @see TreeListener + * @see #addTreeListener + */ +public void removeTreeListener(TreeListener listener) { + checkWidget (); + if (listener is null) error (SWT.ERROR_NULL_ARGUMENT); + if (eventTable is null) return; + eventTable.unhook (SWT.Expand, listener); + eventTable.unhook (SWT.Collapse, listener); +} + +/** + * Display a mark indicating the point at which an item will be inserted. + * The drop insert item has a visual hint to show where a dragged item + * will be inserted when dropped on the tree. + * + * @param item the insert item. Null will clear the insertion mark. + * @param before true places the insert mark above 'item'. false places + * the insert mark below 'item'. + * + * @exception IllegalArgumentException <ul> + * <li>ERROR_INVALID_ARGUMENT - if the item has been disposed</li> + * </ul> + * @exception SWTException <ul> + * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> + * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> + * </ul> + */ +public void setInsertMark (TreeItem item, bool before) { + checkWidget (); + HANDLE hItem; + if (item !is null) { + if (item.isDisposed()) error(SWT.ERROR_INVALID_ARGUMENT); + hItem = item.handle; + } + hInsert = hItem; + insertAfter = !before; + OS.SendMessage (handle, OS.TVM_SETINSERTMARK, insertAfter ? 1 : 0, hInsert); +} + +/** + * Sets the number of root-level items contained in the receiver. + * + * @param count the number of items + * + * @exception SWTException <ul> + * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> + * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> + * </ul> + * + * @since 3.2 + */ +public void setItemCount (int count) { + checkWidget (); + count = Math.max (0, count); + HANDLE hItem = cast(HANDLE) OS.SendMessage (handle, OS.TVM_GETNEXTITEM, OS.TVGN_ROOT, 0); + setItemCount (count, cast(HANDLE) OS.TVGN_ROOT, hItem); +} + +void setItemCount (int count, HANDLE hParent, HANDLE hItem) { + bool redraw = false; + if (OS.SendMessage (handle, OS.TVM_GETCOUNT, 0, 0) is 0) { + redraw = drawCount is 0 && OS.IsWindowVisible (handle); + if (redraw) OS.DefWindowProc (handle, OS.WM_SETREDRAW, 0, 0); + } + int itemCount = 0; + while (hItem !is null && itemCount < count) { + hItem = cast(HANDLE) OS.SendMessage (handle, OS.TVM_GETNEXTITEM, OS.TVGN_NEXT, hItem); + itemCount++; + } + bool expanded = false; + TVITEM tvItem; + tvItem.mask = OS.TVIF_HANDLE | OS.TVIF_PARAM; + if (!redraw && (style & SWT.VIRTUAL) !is 0) { + if (OS.IsWinCE) { + tvItem.hItem = cast(HTREEITEM)hParent; + tvItem.mask = OS.TVIF_STATE; + OS.SendMessage (handle, OS.TVM_GETITEM, 0, cast(int)&tvItem); + expanded = (tvItem.state & OS.TVIS_EXPANDED) !is 0; + } else { + /* + * Bug in Windows. Despite the fact that TVM_GETITEMSTATE claims + * to return only the bits specified by the stateMask, when called + * with TVIS_EXPANDED, the entire state is returned. The fix is + * to explicitly check for the TVIS_EXPANDED bit. + */ + int state = OS.SendMessage (handle, OS.TVM_GETITEMSTATE, hParent, OS.TVIS_EXPANDED); + expanded = (state & OS.TVIS_EXPANDED) !is 0; + } + } + while (hItem !is null) { + tvItem.hItem = cast(HTREEITEM)hItem; + OS.SendMessage (handle, OS.TVM_GETITEM, 0, &tvItem); + hItem = cast(HANDLE) OS.SendMessage (handle, OS.TVM_GETNEXTITEM, OS.TVGN_NEXT, hItem); + TreeItem item = tvItem.lParam !is -1 ? items [tvItem.lParam] : null; + if (item !is null && !item.isDisposed ()) { + item.dispose (); + } else { + releaseItem (tvItem.hItem, &tvItem, false); + destroyItem (null, tvItem.hItem); + } + } + if ((style & SWT.VIRTUAL) !is 0) { + for (int i=itemCount; i<count; i++) { + if (expanded) ignoreShrink = true; + createItem (null, hParent, cast(HTREEITEM) OS.TVI_LAST, null); + if (expanded) ignoreShrink = false; + } + } else { + shrink = true; + int extra = Math.max (4, (count + 3) / 4 * 4); + TreeItem [] newItems = new TreeItem [items.length + extra]; + System.arraycopy (items, 0, newItems, 0, items.length); + items = newItems; + for (int i=itemCount; i<count; i++) { + new TreeItem (this, SWT.NONE, hParent, cast(HTREEITEM) OS.TVI_LAST, null); + } + } + if (redraw) { + OS.DefWindowProc (handle, OS.WM_SETREDRAW, 1, 0); + OS.InvalidateRect (handle, null, true); + } +} + +/** + * Sets the height of the area which would be used to + * display <em>one</em> of the items in the tree. + * + * @param itemHeight the height of one item + * + * @exception SWTException <ul> + * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> + * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> + * </ul> + * + * @since 3.2 + */ +/*public*/ void setItemHeight (int itemHeight) { + checkWidget (); + if (itemHeight < -1) error (SWT.ERROR_INVALID_ARGUMENT); + OS.SendMessage (handle, OS.TVM_SETITEMHEIGHT, itemHeight, 0); +} + +/** + * Marks the receiver's lines as visible if the argument is <code>true</code>, + * and marks it invisible otherwise. + * <p> + * If one of the receiver's ancestors is not visible or some + * other condition makes the receiver not visible, marking + * it visible may not actually cause it to be displayed. + * </p> + * + * @param show the new visibility state + * + * @exception SWTException <ul> + * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> + * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> + * </ul> + * + * @since 3.1 + */ +public void setLinesVisible (bool show) { + checkWidget (); + if (linesVisible is show) return; + linesVisible = show; + if (hwndParent is null && linesVisible) customDraw = true; + OS.InvalidateRect (handle, null, true); +} + +override HWND scrolledHandle () { + if (hwndHeader is null) return handle; + return columnCount is 0 && scrollWidth is 0 ? handle : hwndParent; +} + +void select (HANDLE hItem, TVITEM* tvItem) { + while (hItem !is null) { + tvItem.hItem = cast(HTREEITEM)hItem; + OS.SendMessage (handle, OS.TVM_SETITEM, 0, tvItem); + auto hFirstItem = cast(HANDLE) OS.SendMessage (handle, OS.TVM_GETNEXTITEM, OS.TVGN_CHILD, hItem); + select (hFirstItem, tvItem); + hItem = cast(HANDLE) OS.SendMessage (handle, OS.TVM_GETNEXTITEM, OS.TVGN_NEXT, hItem); + } +} + +/** + * Selects an item in the receiver. If the item was already + * selected, it remains selected. + * + * @param item the item to be selected + * + * @exception IllegalArgumentException <ul> + * <li>ERROR_NULL_ARGUMENT - if the item is null</li> + * <li>ERROR_INVALID_ARGUMENT - if the item has been disposed</li> + * </ul> + * @exception SWTException <ul> + * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> + * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> + * </ul> + * + * @since 3.4 + */ +public void select (TreeItem item) { + checkWidget (); + if (item is null) error (SWT.ERROR_NULL_ARGUMENT); + if (item.isDisposed ()) error (SWT.ERROR_INVALID_ARGUMENT); + if ((style & SWT.SINGLE) !is 0) { + auto hItem = item.handle; + int state = 0; + static if (OS.IsWinCE) { + TVITEM tvItem; + tvItem.hItem = hItem; + tvItem.mask = OS.TVIF_STATE; + OS.SendMessage (handle, OS.TVM_GETITEM, 0, cast(int)&tvItem); + state = tvItem.state; + } else { + state = OS.SendMessage (handle, OS.TVM_GETITEMSTATE, cast(int)&hItem, OS.TVIS_SELECTED); + } + if ((state & OS.TVIS_SELECTED) !is 0) return; + /* + * Feature in Windows. When an item is selected with + * TVM_SELECTITEM and TVGN_CARET, the tree expands and + * scrolls to show the new selected item. Unfortunately, + * there is no other way in Windows to set the focus + * and select an item. The fix is to save the current + * scroll bar positions, turn off redraw, select the item, + * then scroll back to the original position and redraw + * the entire tree. + */ + SCROLLINFO* hInfo = null; + int bits = OS.GetWindowLong (handle, OS.GWL_STYLE); + if ((bits & (OS.TVS_NOHSCROLL | OS.TVS_NOSCROLL)) is 0) { + hInfo = new SCROLLINFO (); + hInfo.cbSize = SCROLLINFO.sizeof; + hInfo.fMask = OS.SIF_ALL; + OS.GetScrollInfo (handle, OS.SB_HORZ, hInfo); + } + SCROLLINFO vInfo; + vInfo.cbSize = SCROLLINFO.sizeof; + vInfo.fMask = OS.SIF_ALL; + OS.GetScrollInfo (handle, OS.SB_VERT, &vInfo); + bool redraw = drawCount is 0 && OS.IsWindowVisible (handle); + if (redraw) { + OS.UpdateWindow (handle); + OS.DefWindowProc (handle, OS.WM_SETREDRAW, 0, 0); + } + setSelection (item); + if (hInfo !is null) { + int /*long*/ hThumb = OS.MAKELPARAM (OS.SB_THUMBPOSITION, hInfo.nPos); + OS.SendMessage (handle, OS.WM_HSCROLL, hThumb, 0); + } + int /*long*/ vThumb = OS.MAKELPARAM (OS.SB_THUMBPOSITION, vInfo.nPos); + OS.SendMessage (handle, OS.WM_VSCROLL, vThumb, 0); + if (redraw) { + OS.DefWindowProc (handle, OS.WM_SETREDRAW, 1, 0); + OS.InvalidateRect (handle, null, true); + if ((style & SWT.DOUBLE_BUFFERED) is 0) { + int oldStyle = style; + style |= SWT.DOUBLE_BUFFERED; + OS.UpdateWindow (handle); + style = oldStyle; + } + } + return; + } + TVITEM tvItem; + tvItem.mask = OS.TVIF_HANDLE | OS.TVIF_STATE; + tvItem.stateMask = OS.TVIS_SELECTED; + tvItem.state = OS.TVIS_SELECTED; + tvItem.hItem = cast(HTREEITEM)item.handle; + OS.SendMessage (handle, OS.TVM_SETITEM, 0, cast(int)&tvItem); +} + +/** + * Selects all of the items in the receiver. + * <p> + * If the receiver is single-select, do nothing. + * </p> + * + * @exception SWTException <ul> + * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> + * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> + * </ul> + */ +public void selectAll () { + checkWidget (); + if ((style & SWT.SINGLE) !is 0) return; + TVITEM tvItem; + tvItem.mask = OS.TVIF_HANDLE | OS.TVIF_STATE; + tvItem.state = OS.TVIS_SELECTED; + tvItem.stateMask = OS.TVIS_SELECTED; + int /*long*/ oldProc = OS.GetWindowLongPtr (handle, OS.GWLP_WNDPROC); + OS.SetWindowLongPtr (handle, OS.GWLP_WNDPROC, cast(LONG_PTR)TreeProc); + if ((style & SWT.VIRTUAL) !is 0) { + HANDLE hItem = cast(HANDLE) OS.SendMessage (handle, OS.TVM_GETNEXTITEM, OS.TVGN_ROOT, 0); + select (hItem, &tvItem); + } else { + for (int i=0; i<items.length; i++) { + TreeItem item = items [i]; + if (item !is null) { + tvItem.hItem = cast(HTREEITEM)item.handle; + OS.SendMessage (handle, OS.TVM_SETITEM, 0, &tvItem); + } + } + } + OS.SetWindowLongPtr (handle, OS.GWLP_WNDPROC, oldProc); +} + +Event sendEraseItemEvent (TreeItem item, NMTTCUSTOMDRAW* nmcd, int column, RECT* cellRect) { + int nSavedDC = OS.SaveDC (nmcd.nmcd.hdc); + RECT* insetRect = toolTipInset (cellRect); + OS.SetWindowOrgEx (nmcd.nmcd.hdc, insetRect.left, insetRect.top, null); + GCData data = new GCData (); + data.device = display; + data.foreground = OS.GetTextColor (nmcd.nmcd.hdc); + data.background = OS.GetBkColor (nmcd.nmcd.hdc); + data.font = item.getFont (column); + data.uiState = OS.SendMessage (handle, OS.WM_QUERYUISTATE, 0, 0); + GC gc = GC.win32_new (nmcd.nmcd.hdc, data); + Event event = new Event (); + event.item = item; + event.index = column; + event.gc = gc; + event.detail |= SWT.FOREGROUND; + event.x = cellRect.left; + event.y = cellRect.top; + event.width = cellRect.right - cellRect.left; + event.height = cellRect.bottom - cellRect.top; + //gc.setClipping (event.x, event.y, event.width, event.height); + sendEvent (SWT.EraseItem, event); + event.gc = null; + //int newTextClr = data.foreground; + gc.dispose (); + OS.RestoreDC (nmcd.nmcd.hdc, nSavedDC); + return event; +} + +Event sendMeasureItemEvent (TreeItem item, int index, HDC hDC) { + RECT* itemRect = item.getBounds (index, true, true, false, false, false, hDC); + int nSavedDC = OS.SaveDC (hDC); + GCData data = new GCData (); + data.device = display; + data.font = item.getFont (index); + GC gc = GC.win32_new (hDC, data); + Event event = new Event (); + event.item = item; + event.gc = gc; + event.index = index; + event.x = itemRect.left; + event.y = itemRect.top; + event.width = itemRect.right - itemRect.left; + event.height = itemRect.bottom - itemRect.top; + sendEvent (SWT.MeasureItem, event); + event.gc = null; + gc.dispose (); + OS.RestoreDC (hDC, nSavedDC); + if (isDisposed () || item.isDisposed ()) return null; + if (hwndHeader !is null) { + if (columnCount is 0) { + if (event.x + event.width > scrollWidth) { + setScrollWidth (scrollWidth = event.x + event.width); + } + } + } + if (event.height > getItemHeight ()) setItemHeight (event.height); + return event; +} + +Event sendPaintItemEvent (TreeItem item, NMTTCUSTOMDRAW* nmcd, int column, RECT* itemRect) { + int nSavedDC = OS.SaveDC (nmcd.nmcd.hdc); + RECT* insetRect = toolTipInset (itemRect); + OS.SetWindowOrgEx (nmcd.nmcd.hdc, insetRect.left, insetRect.top, null); + GCData data = new GCData (); + data.device = display; + data.font = item.getFont (column); + data.foreground = OS.GetTextColor (nmcd.nmcd.hdc); + data.background = OS.GetBkColor (nmcd.nmcd.hdc); + data.uiState = OS.SendMessage (handle, OS.WM_QUERYUISTATE, 0, 0); + GC gc = GC.win32_new (nmcd.nmcd.hdc, data); + Event event = new Event (); + event.item = item; + event.index = column; + event.gc = gc; + event.detail |= SWT.FOREGROUND; + event.x = itemRect.left; + event.y = itemRect.top; + event.width = itemRect.right - itemRect.left; + event.height = itemRect.bottom - itemRect.top; + //gc.setClipping (cellRect.left, cellRect.top, cellWidth, cellHeight); + sendEvent (SWT.PaintItem, event); + event.gc = null; + gc.dispose (); + OS.RestoreDC (nmcd.nmcd.hdc, nSavedDC); + return event; +} + +override void setBackgroundImage (HBITMAP hBitmap) { + super.setBackgroundImage (hBitmap); + if (hBitmap !is null) { + /* + * Feature in Windows. If TVM_SETBKCOLOR is never + * used to set the background color of a tree, the + * background color of the lines and the plus/minus + * will be drawn using the default background color, + * not the HBRUSH returned from WM_CTLCOLOR. The fix + * is to set the background color to the default (when + * it is already the default) to make Windows use the + * brush. + */ + if (OS.SendMessage (handle, OS.TVM_GETBKCOLOR, 0, 0) is -1) { + OS.SendMessage (handle, OS.TVM_SETBKCOLOR, 0, -1); + } + _setBackgroundPixel (-1); + } else { + Control control = findBackgroundControl (); + if (control is null) control = this; + if (control.backgroundImage is null) { + setBackgroundPixel (control.getBackgroundPixel ()); + } + } + /* + * Feature in Windows. When the tree has the style + * TVS_FULLROWSELECT, the background color for the + * entire row is filled when an item is painted, + * drawing on top of the background image. The fix + * is to clear TVS_FULLROWSELECT when a background + * image is set. + */ + updateFullSelection (); +} + +override void setBackgroundPixel (int pixel) { + Control control = findImageControl (); + if (control !is null) { + setBackgroundImage (control.backgroundImage); + return; + } + /* + * Feature in Windows. When a tree is given a background color + * using TVM_SETBKCOLOR and the tree is disabled, Windows draws + * the tree using the background color rather than the disabled + * colors. This is different from the table which draws grayed. + * The fix is to set the default background color while the tree + * is disabled and restore it when enabled. + */ + if (OS.IsWindowEnabled (handle)) _setBackgroundPixel (pixel); + + /* + * Feature in Windows. When the tree has the style + * TVS_FULLROWSELECT, the background color for the + * entire row is filled when an item is painted, + * drawing on top of the background image. The fix + * is to restore TVS_FULLROWSELECT when a background + * color is set. + */ + updateFullSelection (); +} + +override void setCursor () { + /* + * Bug in Windows. Under certain circumstances, when WM_SETCURSOR + * is sent from SendMessage(), Windows GP's in the window proc for + * the tree. The fix is to avoid calling the tree window proc and + * set the cursor for the tree outside of WM_SETCURSOR. + * + * NOTE: This code assumes that the default cursor for the tree + * is IDC_ARROW. + */ + Cursor cursor = findCursor (); + auto hCursor = cursor is null ? OS.LoadCursor (null, cast(TCHAR*) OS.IDC_ARROW) : cursor.handle; + OS.SetCursor (hCursor); +} + +/** + * Sets the order that the items in the receiver should + * be displayed in to the given argument which is described + * in terms of the zero-relative ordering of when the items + * were added. + * + * @param order the new order to display the items + * + * @exception SWTException <ul> + * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> + * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> + * </ul> + * @exception IllegalArgumentException <ul> + * <li>ERROR_INVALID_ARGUMENT - if the item order is not the same length as the number of items</li> + * </ul> + * + * @see Tree#getColumnOrder() + * @see TreeColumn#getMoveable() + * @see TreeColumn#setMoveable(bool) + * @see SWT#Move + * + * @since 3.2 + */ +public void setColumnOrder (int [] order) { + checkWidget (); + // SWT extension: allow null array + //if (order is null) error (SWT.ERROR_NULL_ARGUMENT); + if (columnCount is 0) { + if (order.length !is 0) error (SWT.ERROR_INVALID_ARGUMENT); + return; + } + if (order.length !is columnCount) error (SWT.ERROR_INVALID_ARGUMENT); + int [] oldOrder = new int [columnCount]; + OS.SendMessage (hwndHeader, OS.HDM_GETORDERARRAY, columnCount, oldOrder.ptr); + bool reorder = false; + bool [] seen = new bool [columnCount]; + for (int i=0; i<order.length; i++) { + int index = order [i]; + if (index < 0 || index >= columnCount) error (SWT.ERROR_INVALID_RANGE); + if (seen [index]) error (SWT.ERROR_INVALID_ARGUMENT); + seen [index] = true; + if (index !is oldOrder [i]) reorder = true; + } + if (reorder) { + RECT [] oldRects = new RECT [columnCount]; + for (int i=0; i<columnCount; i++) { + //oldRects [i] = new RECT (); + OS.SendMessage (hwndHeader, OS.HDM_GETITEMRECT, i, & oldRects [i]); + } + OS.SendMessage (hwndHeader, OS.HDM_SETORDERARRAY, order.length, order.ptr); + OS.InvalidateRect (handle, null, true); + updateImageList (); + TreeColumn [] newColumns = new TreeColumn [columnCount]; + System.arraycopy (columns, 0, newColumns, 0, columnCount); + RECT newRect; + for (int i=0; i<columnCount; i++) { + TreeColumn column = newColumns [i]; + if (!column.isDisposed ()) { + OS.SendMessage (hwndHeader, OS.HDM_GETITEMRECT, i, &newRect); + if (newRect.left !is oldRects [i].left) { + column.updateToolTip (i); + column.sendEvent (SWT.Move); + } + } + } + } +} + +void setCheckboxImageList () { + if ((style & SWT.CHECK) is 0) return; + int count = 5, flags = 0; + static if (OS.IsWinCE) { + flags |= OS.ILC_COLOR; + } else { + if (OS.COMCTL32_MAJOR >= 6 && OS.IsAppThemed ()) { + flags |= OS.ILC_COLOR32; + } else { + auto hDC = OS.GetDC (handle); + int bits = OS.GetDeviceCaps (hDC, OS.BITSPIXEL); + int planes = OS.GetDeviceCaps (hDC, OS.PLANES); + OS.ReleaseDC (handle, hDC); + int depth = bits * planes; + switch (depth) { + case 4: flags |= OS.ILC_COLOR4; break; + case 8: flags |= OS.ILC_COLOR8; break; + case 16: flags |= OS.ILC_COLOR16; break; + case 24: flags |= OS.ILC_COLOR24; break; + case 32: flags |= OS.ILC_COLOR32; break; + default: flags |= OS.ILC_COLOR; break; + } + flags |= OS.ILC_MASK; + } + } + if ((style & SWT.RIGHT_TO_LEFT) !is 0) flags |= OS.ILC_MIRROR; + int height = OS.SendMessage (handle, OS.TVM_GETITEMHEIGHT, 0, 0), width = height; + auto hStateList = OS.ImageList_Create (width, height, flags, count, count); + auto hDC = OS.GetDC (handle); + auto memDC = OS.CreateCompatibleDC (hDC); + auto hBitmap = OS.CreateCompatibleBitmap (hDC, width * count, height); + auto hOldBitmap = OS.SelectObject (memDC, hBitmap); + RECT rect; + OS.SetRect (&rect, 0, 0, width * count, height); + /* + * NOTE: DrawFrameControl() draws a black and white + * mask when not drawing a push button. In order to + * make the box surrounding the check mark transparent, + * fill it with a color that is neither black or white. + */ + int clrBackground = 0; + if (OS.COMCTL32_MAJOR >= 6 && OS.IsAppThemed ()) { + Control control = findBackgroundControl (); + if (control is null) control = this; + clrBackground = control.getBackgroundPixel (); + } else { + clrBackground = 0x020000FF; + if ((clrBackground & 0xFFFFFF) is OS.GetSysColor (OS.COLOR_WINDOW)) { + clrBackground = 0x0200FF00; + } + } + auto hBrush = OS.CreateSolidBrush (clrBackground); + OS.FillRect (memDC, &rect, hBrush); + OS.DeleteObject (hBrush); + auto oldFont = OS.SelectObject (hDC, defaultFont ()); + TEXTMETRIC tm; + OS.GetTextMetrics (hDC, &tm); + OS.SelectObject (hDC, oldFont); + int itemWidth = Math.min (tm.tmHeight, width); + int itemHeight = Math.min (tm.tmHeight, height); + int left = (width - itemWidth) / 2, top = (height - itemHeight) / 2 + 1; + OS.SetRect (&rect, left + width, top, left + width + itemWidth, top + itemHeight); + if (OS.COMCTL32_MAJOR >= 6 && OS.IsAppThemed ()) { + auto hTheme = display.hButtonTheme (); + OS.DrawThemeBackground (hTheme, memDC, OS.BP_CHECKBOX, OS.CBS_UNCHECKEDNORMAL, &rect, null); + rect.left += width; rect.right += width; + OS.DrawThemeBackground (hTheme, memDC, OS.BP_CHECKBOX, OS.CBS_CHECKEDNORMAL, &rect, null); + rect.left += width; rect.right += width; + OS.DrawThemeBackground (hTheme, memDC, OS.BP_CHECKBOX, OS.CBS_UNCHECKEDNORMAL, &rect, null); + rect.left += width; rect.right += width; + OS.DrawThemeBackground (hTheme, memDC, OS.BP_CHECKBOX, OS.CBS_MIXEDNORMAL, &rect, null); + } else { + OS.DrawFrameControl (memDC, &rect, OS.DFC_BUTTON, OS.DFCS_BUTTONCHECK | OS.DFCS_FLAT); + rect.left += width; rect.right += width; + OS.DrawFrameControl (memDC, &rect, OS.DFC_BUTTON, OS.DFCS_BUTTONCHECK | OS.DFCS_CHECKED | OS.DFCS_FLAT); + rect.left += width; rect.right += width; + OS.DrawFrameControl (memDC, &rect, OS.DFC_BUTTON, OS.DFCS_BUTTONCHECK | OS.DFCS_INACTIVE | OS.DFCS_FLAT); + rect.left += width; rect.right += width; + OS.DrawFrameControl (memDC, &rect, OS.DFC_BUTTON, OS.DFCS_BUTTONCHECK | OS.DFCS_CHECKED | OS.DFCS_INACTIVE | OS.DFCS_FLAT); + } + OS.SelectObject (memDC, hOldBitmap); + OS.DeleteDC (memDC); + OS.ReleaseDC (handle, hDC); + if (OS.COMCTL32_MAJOR >= 6 && OS.IsAppThemed ()) { + OS.ImageList_Add (hStateList, hBitmap, null); + } else { + OS.ImageList_AddMasked (hStateList, hBitmap, clrBackground); + } + OS.DeleteObject (hBitmap); + auto hOldStateList = cast(HANDLE) OS.SendMessage (handle, OS.TVM_GETIMAGELIST, OS.TVSIL_STATE, 0); + OS.SendMessage (handle, OS.TVM_SETIMAGELIST, OS.TVSIL_STATE, hStateList); + if (hOldStateList !is null) OS.ImageList_Destroy (hOldStateList); +} + +override public void setFont (Font font) { + checkWidget (); + super.setFont (font); + if ((style & SWT.CHECK) !is 0) setCheckboxImageList (); +} + +override void setForegroundPixel (int pixel) { + /* + * Bug in Windows. When the tree is using the explorer + * theme, it does not use COLOR_WINDOW_TEXT for the + * foreground. When TVM_SETTEXTCOLOR is called with -1, + * it resets the color to black, not COLOR_WINDOW_TEXT. + * The fix is to explicitly set the color. + */ + if (explorerTheme) { + if (pixel is -1) pixel = defaultForeground (); + } + OS.SendMessage (handle, OS.TVM_SETTEXTCOLOR, 0, pixel); +} + +/** + * Marks the receiver's header as visible if the argument is <code>true</code>, + * and marks it invisible otherwise. + * <p> + * If one of the receiver's ancestors is not visible or some + * other condition makes the receiver not visible, marking + * it visible may not actually cause it to be displayed. + * </p> + * + * @param show the new visibility state + * + * @exception SWTException <ul> + * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> + * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> + * </ul> + * + * @since 3.1 + */ +public void setHeaderVisible (bool show) { + checkWidget (); + if (hwndHeader is null) { + if (!show) return; + createParent (); + } + int bits = OS.GetWindowLong (hwndHeader, OS.GWL_STYLE); + if (show) { + if ((bits & OS.HDS_HIDDEN) is 0) return; + bits &= ~OS.HDS_HIDDEN; + OS.SetWindowLong (hwndHeader, OS.GWL_STYLE, bits); + OS.ShowWindow (hwndHeader, OS.SW_SHOW); + } else { + if ((bits & OS.HDS_HIDDEN) !is 0) return; + bits |= OS.HDS_HIDDEN; + OS.SetWindowLong (hwndHeader, OS.GWL_STYLE, bits); + OS.ShowWindow (hwndHeader, OS.SW_HIDE); + } + setScrollWidth (); + updateHeaderToolTips (); + updateScrollBar (); +} + +override public void setRedraw (bool redraw) { + checkWidget (); + /* + * Feature in Windows. When WM_SETREDRAW is used to + * turn off redraw, the scroll bars are updated when + * items are added and removed. The fix is to call + * the default window proc to stop all drawing. + * + * Bug in Windows. For some reason, when WM_SETREDRAW + * is used to turn redraw on for a tree and the tree + * contains no items, the last item in the tree does + * not redraw properly. If the tree has only one item, + * that item is not drawn. If another window is dragged + * on top of the item, parts of the item are redrawn + * and erased at random. The fix is to ensure that this + * case doesn't happen by inserting and deleting an item + * when redraw is turned on and there are no items in + * the tree. + */ + HANDLE hItem; + if (redraw) { + if (drawCount is 1) { + int count = OS.SendMessage (handle, OS.TVM_GETCOUNT, 0, 0); + if (count is 0) { + TVINSERTSTRUCT tvInsert; + tvInsert.hInsertAfter = cast(HTREEITEM) OS.TVI_FIRST; + hItem = cast(HANDLE) OS.SendMessage (handle, OS.TVM_INSERTITEM, 0, &tvInsert); + } + OS.DefWindowProc (handle, OS.WM_SETREDRAW, 1, 0); + updateScrollBar (); + } + } + super.setRedraw (redraw); + if (!redraw) { + if (drawCount is 1) OS.DefWindowProc (handle, OS.WM_SETREDRAW, 0, 0); + } + if (hItem !is null) { + ignoreShrink = true; + OS.SendMessage (handle, OS.TVM_DELETEITEM, 0, hItem); + ignoreShrink = false; + } +} + +void setScrollWidth () { + if (hwndHeader is null || hwndParent is null) return; + int width = 0; + HDITEM hdItem; + for (int i=0; i<columnCount; i++) { + hdItem.mask = OS.HDI_WIDTH; + OS.SendMessage (hwndHeader, OS.HDM_GETITEM, i, &hdItem); + width += hdItem.cxy; + } + setScrollWidth (Math.max (scrollWidth, width)); +} + +void setScrollWidth (int width) { + if (hwndHeader is null || hwndParent is null) return; + //TEMPORARY CODE + //scrollWidth = width; + int left = 0; + RECT rect; + SCROLLINFO info; + info.cbSize = SCROLLINFO.sizeof; + info.fMask = OS.SIF_RANGE | OS.SIF_PAGE; + if (columnCount is 0 && width is 0) { + OS.GetScrollInfo (hwndParent, OS.SB_HORZ, &info); + info.nPage = info.nMax + 1; + OS.SetScrollInfo (hwndParent, OS.SB_HORZ, &info, true); + OS.GetScrollInfo (hwndParent, OS.SB_VERT, &info); + info.nPage = info.nMax + 1; + OS.SetScrollInfo (hwndParent, OS.SB_VERT, &info, true); + } else { + if ((style & SWT.H_SCROLL) !is 0) { + OS.GetClientRect (hwndParent, &rect); + OS.GetScrollInfo (hwndParent, OS.SB_HORZ, &info); + info.nMax = width; + info.nPage = rect.right - rect.left + 1; + OS.SetScrollInfo (hwndParent, OS.SB_HORZ, &info, true); + info.fMask = OS.SIF_POS; + OS.GetScrollInfo (hwndParent, OS.SB_HORZ, &info); + left = info.nPos; + } + } + if (horizontalBar !is null) { + horizontalBar.setIncrement (INCREMENT); + horizontalBar.setPageIncrement (info.nPage); + } + OS.GetClientRect (hwndParent, &rect); + HDLAYOUT playout; + RECT layoutrect = rect; + playout.prc = &layoutrect; + WINDOWPOS pos; + playout.pwpos = &pos; + OS.SendMessage (hwndHeader, OS.HDM_LAYOUT, 0, &playout); + SetWindowPos (hwndHeader, cast(HWND)OS.HWND_TOP, pos.x - left, pos.y, pos.cx + left, pos.cy, OS.SWP_NOACTIVATE); + int bits = OS.GetWindowLong (handle, OS.GWL_EXSTYLE); + int b = (bits & OS.WS_EX_CLIENTEDGE) !is 0 ? OS.GetSystemMetrics (OS.SM_CXEDGE) : 0; + int w = pos.cx + (columnCount is 0 && width is 0 ? 0 : OS.GetSystemMetrics (OS.SM_CXVSCROLL)); + int h = rect.bottom - rect.top - pos.cy; + bool oldIgnore = ignoreResize; + ignoreResize = true; + SetWindowPos (handle, null, pos.x - left - b, pos.y + pos.cy - b, w + left + b * 2, h + b * 2, OS.SWP_NOACTIVATE | OS.SWP_NOZORDER); + ignoreResize = oldIgnore; +} + +void setSelection (HANDLE hItem, TVITEM* tvItem, TreeItem [] selection) { + while (hItem !is null) { + int index = 0; + while (index < selection.length) { + TreeItem item = selection [index]; + if (item !is null && item.handle is hItem) break; + index++; + } + tvItem.hItem = cast(HTREEITEM)hItem; + OS.SendMessage (handle, OS.TVM_GETITEM, 0, &tvItem); + if ((tvItem.state & OS.TVIS_SELECTED) !is 0) { + if (index is selection.length) { + tvItem.state = 0; + OS.SendMessage (handle, OS.TVM_SETITEM, 0, &tvItem); + } + } else { + if (index !is selection.length) { + tvItem.state = OS.TVIS_SELECTED; + OS.SendMessage (handle, OS.TVM_SETITEM, 0, &tvItem); + } + } + auto hFirstItem = cast(HANDLE) OS.SendMessage (handle, OS.TVM_GETNEXTITEM, OS.TVGN_CHILD, hItem); + setSelection (hFirstItem, tvItem, selection); + hItem = cast(HANDLE) OS.SendMessage (handle, OS.TVM_GETNEXTITEM, OS.TVGN_NEXT, hItem); + } +} + +/** + * Sets the receiver's selection to the given item. + * The current selection is cleared before the new item is selected. + * <p> + * If the item is not in the receiver, then it is ignored. + * </p> + * + * @param item the item to select + * + * @exception IllegalArgumentException <ul> + * <li>ERROR_NULL_ARGUMENT - if the item is null</li> + * <li>ERROR_INVALID_ARGUMENT - if the item has been disposed</li> + * </ul> + * @exception SWTException <ul> + * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> + * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> + * </ul> + * + * @since 3.2 + */ +public void setSelection (TreeItem item) { + checkWidget (); + if (item is null) error (SWT.ERROR_NULL_ARGUMENT); + setSelection ([item]); +} + +/** + * Sets the receiver's selection to be the given array of items. + * The current selection is cleared before the new items are selected. + * <p> + * Items that are not in the receiver are ignored. + * If the receiver is single-select and multiple items are specified, + * then all items are ignored. + * </p> + * + * @param items the array of items + * + * @exception IllegalArgumentException <ul> + * <li>ERROR_INVALID_ARGUMENT - if one of the items has been disposed</li> + * </ul> + * @exception SWTException <ul> + * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> + * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> + * </ul> + * + * @see Tree#deselectAll() + */ +public void setSelection (TreeItem [] items) { + checkWidget (); + // SWT extension: allow null array + //if (items is null) error (SWT.ERROR_NULL_ARGUMENT); + int length = items.length; + if (length is 0 || ((style & SWT.SINGLE) !is 0 && length > 1)) { + deselectAll(); + return; + } + + /* Select/deselect the first item */ + TreeItem item = items [0]; + if (item !is null) { + if (item.isDisposed ()) error (SWT.ERROR_INVALID_ARGUMENT); + HANDLE hOldItem = cast(HANDLE) OS.SendMessage (handle, OS.TVM_GETNEXTITEM, OS.TVGN_CARET, 0); + HANDLE hNewItem = hAnchor = item.handle; + + /* + * Bug in Windows. When TVM_SELECTITEM is used to select and + * scroll an item to be visible and the client area of the tree + * is smaller that the size of one item, TVM_SELECTITEM makes + * the next item in the tree visible by making it the top item + * instead of making the desired item visible. The fix is to + * detect the case when the client area is too small and make + * the desired visible item be the top item in the tree. + * + * Note that TVM_SELECTITEM when called with TVGN_FIRSTVISIBLE + * also requires the work around for scrolling. + */ + bool fixScroll = checkScroll (hNewItem); + if (fixScroll) { + OS.SendMessage (handle, OS.WM_SETREDRAW, 1, 0); + OS.DefWindowProc (handle, OS.WM_SETREDRAW, 0, 0); + } + ignoreSelect = true; + OS.SendMessage (handle, OS.TVM_SELECTITEM, OS.TVGN_CARET, hNewItem); + ignoreSelect = false; + if (OS.SendMessage (handle, OS.TVM_GETVISIBLECOUNT, 0, 0) is 0) { + OS.SendMessage (handle, OS.TVM_SELECTITEM, OS.TVGN_FIRSTVISIBLE, hNewItem); + int /*long*/ hParent = OS.SendMessage (handle, OS.TVM_GETNEXTITEM, OS.TVGN_PARENT, hNewItem); + if (hParent is 0) OS.SendMessage (handle, OS.WM_HSCROLL, OS.SB_TOP, 0); + } + if (fixScroll) { + OS.DefWindowProc (handle, OS.WM_SETREDRAW, 1, 0); + OS.SendMessage (handle, OS.WM_SETREDRAW, 0, 0); + } + + /* + * Feature in Windows. When the old and new focused item + * are the same, Windows does not check to make sure that + * the item is actually selected, not just focused. The + * fix is to force the item to draw selected by setting + * the state mask, and to ensure that it is visible. + */ + if (hOldItem is hNewItem) { + TVITEM tvItem; + tvItem.mask = OS.TVIF_HANDLE | OS.TVIF_STATE; + tvItem.state = OS.TVIS_SELECTED; + tvItem.stateMask = OS.TVIS_SELECTED; + tvItem.hItem = cast(HTREEITEM)hNewItem; + OS.SendMessage (handle, OS.TVM_SETITEM, 0, &tvItem); + showItem (hNewItem); + } + } + if ((style & SWT.SINGLE) !is 0) return; + + /* Select/deselect the rest of the items */ + TVITEM tvItem; + tvItem.mask = OS.TVIF_HANDLE | OS.TVIF_STATE; + tvItem.stateMask = OS.TVIS_SELECTED; + int /*long*/ oldProc = OS.GetWindowLongPtr (handle, OS.GWLP_WNDPROC); + OS.SetWindowLongPtr (handle, OS.GWLP_WNDPROC, cast(LONG_PTR)TreeProc); + if ((style & SWT.VIRTUAL) !is 0) { + HANDLE hItem = cast(HANDLE) OS.SendMessage (handle, OS.TVM_GETNEXTITEM, OS.TVGN_ROOT, 0); + setSelection (hItem, &tvItem, items); + } else { + for (int i=0; i<this.items.length; i++) { + item = this.items [i]; + if (item !is null) { + int index = 0; + while (index < length) { + if (items [index] is item) break; + index++; + } + tvItem.hItem = cast(HTREEITEM)item.handle; + OS.SendMessage (handle, OS.TVM_GETITEM, 0, &tvItem); + if ((tvItem.state & OS.TVIS_SELECTED) !is 0) { + if (index is length) { + tvItem.state = 0; + OS.SendMessage (handle, OS.TVM_SETITEM, 0, &tvItem); + } + } else { + if (index !is length) { + tvItem.state = OS.TVIS_SELECTED; + OS.SendMessage (handle, OS.TVM_SETITEM, 0, &tvItem); + } + } + } + } + } + OS.SetWindowLongPtr (handle, OS.GWLP_WNDPROC, oldProc); +} + +/** + * Sets the column used by the sort indicator for the receiver. A null + * value will clear the sort indicator. The current sort column is cleared + * before the new column is set. + * + * @param column the column used by the sort indicator or <code>null</code> + * + * @exception IllegalArgumentException <ul> + * <li>ERROR_INVALID_ARGUMENT - if the column is disposed</li> + * </ul> + * @exception SWTException <ul> + * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> + * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> + * </ul> + * + * @since 3.2 + */ +public void setSortColumn (TreeColumn column) { + checkWidget (); + if (column !is null && column.isDisposed ()) error (SWT.ERROR_INVALID_ARGUMENT); + if (sortColumn !is null && !sortColumn.isDisposed ()) { + sortColumn.setSortDirection (SWT.NONE); + } + sortColumn = column; + if (sortColumn !is null && sortDirection !is SWT.NONE) { + sortColumn.setSortDirection (sortDirection); + } +} + +/** + * Sets the direction of the sort indicator for the receiver. The value + * can be one of <code>UP</code>, <code>DOWN</code> or <code>NONE</code>. + * + * @param direction the direction of the sort indicator + * + * @exception SWTException <ul> + * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> + * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> + * </ul> + * + * @since 3.2 + */ +public void setSortDirection (int direction) { + checkWidget (); + if ((direction & (SWT.UP | SWT.DOWN)) is 0 && direction !is SWT.NONE) return; + sortDirection = direction; + if (sortColumn !is null && !sortColumn.isDisposed ()) { + sortColumn.setSortDirection (direction); + } +} + +/** + * Sets the item which is currently at the top of the receiver. + * This item can change when items are expanded, collapsed, scrolled + * or new items are added or removed. + * + * @param item the item to be shown + * + * @exception IllegalArgumentException <ul> + * <li>ERROR_NULL_ARGUMENT - if the item is null</li> + * <li>ERROR_INVALID_ARGUMENT - if the item has been disposed</li> + * </ul> + * @exception SWTException <ul> + * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> + * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> + * </ul> + * + * @see Tree#getTopItem() + * + * @since 2.1 + */ +public void setTopItem (TreeItem item) { + checkWidget (); + if (item is null) SWT.error (SWT.ERROR_NULL_ARGUMENT); + if (item.isDisposed ()) SWT.error (SWT.ERROR_INVALID_ARGUMENT); + HANDLE hItem = item.handle; + auto hTopItem = cast(HANDLE) OS.SendMessage (handle, OS.TVM_GETNEXTITEM, OS.TVGN_FIRSTVISIBLE, 0); + if (hItem is hTopItem) return; + bool fixScroll = checkScroll (hItem), redraw = false; + if (fixScroll) { + OS.SendMessage (handle, OS.WM_SETREDRAW, 1, 0); + OS.DefWindowProc (handle, OS.WM_SETREDRAW, 0, 0); + } else { + redraw = drawCount is 0 && OS.IsWindowVisible (handle); + if (redraw) OS.DefWindowProc (handle, OS.WM_SETREDRAW, 0, 0); + } + OS.SendMessage (handle, OS.TVM_SELECTITEM, OS.TVGN_FIRSTVISIBLE, hItem); + auto hParent = cast(HANDLE) OS.SendMessage (handle, OS.TVM_GETNEXTITEM, OS.TVGN_PARENT, hItem); + if (hParent is null) OS.SendMessage (handle, OS.WM_HSCROLL, OS.SB_TOP, 0); + if (fixScroll) { + OS.DefWindowProc (handle, OS.WM_SETREDRAW, 1, 0); + OS.SendMessage (handle, OS.WM_SETREDRAW, 0, 0); + } else { + if (redraw) { + OS.DefWindowProc (handle, OS.WM_SETREDRAW, 1, 0); + OS.InvalidateRect (handle, null, true); + } + } + updateScrollBar (); +} + +void showItem (HANDLE hItem) { + /* + * Bug in Windows. When TVM_ENSUREVISIBLE is used to ensure + * that an item is visible and the client area of the tree is + * smaller that the size of one item, TVM_ENSUREVISIBLE makes + * the next item in the tree visible by making it the top item + * instead of making the desired item visible. The fix is to + * detect the case when the client area is too small and make + * the desired visible item be the top item in the tree. + */ + if (OS.SendMessage (handle, OS.TVM_GETVISIBLECOUNT, 0, 0) is 0) { + bool fixScroll = checkScroll (hItem); + if (fixScroll) { + OS.SendMessage (handle, OS.WM_SETREDRAW, 1, 0); + OS.DefWindowProc (handle, OS.WM_SETREDRAW, 0, 0); + } + OS.SendMessage (handle, OS.TVM_SELECTITEM, OS.TVGN_FIRSTVISIBLE, hItem); + /* This code is intentionally commented */ + //int hParent = OS.SendMessage (handle, OS.TVM_GETNEXTITEM, OS.TVGN_PARENT, hItem); + //if (hParent is 0) OS.SendMessage (handle, OS.WM_HSCROLL, OS.SB_TOP, 0); + OS.SendMessage (handle, OS.WM_HSCROLL, OS.SB_TOP, 0); + if (fixScroll) { + OS.DefWindowProc (handle, OS.WM_SETREDRAW, 1, 0); + OS.SendMessage (handle, OS.WM_SETREDRAW, 0, 0); + } + } else { + bool scroll = true; + RECT itemRect; + if (OS.TreeView_GetItemRect (handle, cast(HTREEITEM)hItem, &itemRect, true)) { + forceResize (); + RECT rect; + OS.GetClientRect (handle, &rect); + POINT pt; + pt.x = itemRect.left; + pt.y = itemRect.top; + if (OS.PtInRect (&rect, pt)) { + pt.y = itemRect.bottom; + if (OS.PtInRect (&rect, pt)) scroll = false; + } + } + if (scroll) { + bool fixScroll = checkScroll (hItem); + if (fixScroll) { + OS.SendMessage (handle, OS.WM_SETREDRAW, 1, 0); + OS.DefWindowProc (handle, OS.WM_SETREDRAW, 0, 0); + } + OS.SendMessage (handle, OS.TVM_ENSUREVISIBLE, 0, hItem); + if (fixScroll) { + OS.DefWindowProc (handle, OS.WM_SETREDRAW, 1, 0); + OS.SendMessage (handle, OS.WM_SETREDRAW, 0, 0); + } + } + } + if (hwndParent !is null) { + RECT itemRect; + if (OS.TreeView_GetItemRect (handle, cast(HTREEITEM)hItem, &itemRect, true)) { + forceResize (); + RECT rect; + OS.GetClientRect (hwndParent, &rect); + OS.MapWindowPoints (hwndParent, handle, cast(POINT*) &rect, 2); + POINT pt; + pt.x = itemRect.left; + pt.y = itemRect.top; + if (!OS.PtInRect (&rect, pt)) { + pt.y = itemRect.bottom; + if (!OS.PtInRect (&rect, pt)) { + SCROLLINFO info; + info.cbSize = SCROLLINFO.sizeof; + info.fMask = OS.SIF_POS; + info.nPos = Math.max (0, pt.x - Tree.INSET / 2); + OS.SetScrollInfo (hwndParent, OS.SB_HORZ, &info, true); + setScrollWidth (); + } + } + } + } + updateScrollBar (); +} + +/** + * Shows the column. If the column is already showing in the receiver, + * this method simply returns. Otherwise, the columns are scrolled until + * the column is visible. + * + * @param column the column to be shown + * + * @exception IllegalArgumentException <ul> + * <li>ERROR_NULL_ARGUMENT - if the item is null</li> + * <li>ERROR_INVALID_ARGUMENT - if the item has been disposed</li> + * </ul> + * @exception SWTException <ul> + * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> + * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> + * </ul> + * + * @since 3.1 + */ +public void showColumn (TreeColumn column) { + checkWidget (); + if (column is null) error (SWT.ERROR_NULL_ARGUMENT); + if (column.isDisposed ()) error(SWT.ERROR_INVALID_ARGUMENT); + if (column.parent !is this) return; + int index = indexOf (column); + if (index is -1) return; + if (0 <= index && index < columnCount) { + forceResize (); + RECT rect; + OS.GetClientRect (hwndParent, &rect); + OS.MapWindowPoints (hwndParent, handle, cast(POINT*) &rect, 2); + RECT headerRect; + OS.SendMessage (hwndHeader, OS.HDM_GETITEMRECT, index, &headerRect); + bool scroll = headerRect.left < rect.left; + if (!scroll) { + int width = Math.min (rect.right - rect.left, headerRect.right - headerRect.left); + scroll = headerRect.left + width > rect.right; + } + if (scroll) { + SCROLLINFO info; + info.cbSize = SCROLLINFO.sizeof; + info.fMask = OS.SIF_POS; + info.nPos = Math.max (0, headerRect.left - Tree.INSET / 2); + OS.SetScrollInfo (hwndParent, OS.SB_HORZ, &info, true); + setScrollWidth (); + } + } +} + +/** + * Shows the item. If the item is already showing in the receiver, + * this method simply returns. Otherwise, the items are scrolled + * and expanded until the item is visible. + * + * @param item the item to be shown + * + * @exception IllegalArgumentException <ul> + * <li>ERROR_NULL_ARGUMENT - if the item is null</li> + * <li>ERROR_INVALID_ARGUMENT - if the item has been disposed</li> + * </ul> + * @exception SWTException <ul> + * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> + * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> + * </ul> + * + * @see Tree#showSelection() + */ +public void showItem (TreeItem item) { + checkWidget (); + if (item is null) error (SWT.ERROR_NULL_ARGUMENT); + if (item.isDisposed ()) error(SWT.ERROR_INVALID_ARGUMENT); + showItem (item.handle); +} + +/** + * Shows the selection. If the selection is already showing in the receiver, + * this method simply returns. Otherwise, the items are scrolled until + * the selection is visible. + * + * @exception SWTException <ul> + * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> + * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> + * </ul> + * + * @see Tree#showItem(TreeItem) + */ +public void showSelection () { + checkWidget (); + HANDLE hItem; + if ((style & SWT.SINGLE) !is 0) { + hItem = cast(HANDLE) OS.SendMessage (handle, OS.TVM_GETNEXTITEM, OS.TVGN_CARET, 0); + if (hItem is null) return; + int state = 0; + static if (OS.IsWinCE) { + TVITEM tvItem; + tvItem.hItem = hItem; + tvItem.mask = OS.TVIF_STATE; + OS.SendMessage (handle, OS.TVM_GETITEM, 0, &tvItem); + state = tvItem.state; + } else { + state = OS.SendMessage (handle, OS.TVM_GETITEMSTATE, hItem, OS.TVIS_SELECTED); + } + if ((state & OS.TVIS_SELECTED) is 0) return; + } else { + int /*long*/ oldProc = OS.GetWindowLongPtr (handle, OS.GWLP_WNDPROC); + OS.SetWindowLongPtr (handle, OS.GWLP_WNDPROC, cast(LONG_PTR)TreeProc); + TVITEM tvItem_; + TVITEM* tvItem; + static if (OS.IsWinCE) { + tvItem = &tvItem_; + tvItem.mask = OS.TVIF_STATE; + } + if ((style & SWT.VIRTUAL) !is 0) { + auto hRoot = cast(HANDLE) OS.SendMessage (handle, OS.TVM_GETNEXTITEM, OS.TVGN_ROOT, 0); + hItem = getNextSelection (hRoot, tvItem); + } else { + //FIXME - this code expands first selected item it finds + int index = 0; + while (index <items.length) { + TreeItem item = items [index]; + if (item !is null) { + int state = 0; + static if (OS.IsWinCE) { + tvItem.hItem = item.handle; + OS.SendMessage (handle, OS.TVM_GETITEM, 0, tvItem); + state = tvItem.state; + } else { + state = OS.SendMessage (handle, OS.TVM_GETITEMSTATE, item.handle, OS.TVIS_SELECTED); + } + if ((state & OS.TVIS_SELECTED) !is 0) { + hItem = item.handle; + break; + } + } + index++; + } + } + OS.SetWindowLongPtr (handle, OS.GWLP_WNDPROC, oldProc); + } + if (hItem !is null) showItem (hItem); +} + +/*public*/ void sort () { + checkWidget (); + if ((style & SWT.VIRTUAL) !is 0) return; + sort ( cast(HTREEITEM) OS.TVI_ROOT, false); +} + +void sort (HANDLE hParent, bool all) { + int itemCount = OS.SendMessage (handle, OS.TVM_GETCOUNT, 0, 0); + if (itemCount is 0 || itemCount is 1) return; + hFirstIndexOf = hLastIndexOf = null; + itemCount = -1; + if (sortDirection is SWT.UP || sortDirection is SWT.NONE) { + OS.SendMessage (handle, OS.TVM_SORTCHILDREN, all ? 1 : 0, hParent); + } else { + //Callback compareCallback = new Callback (this, "CompareFunc", 3); + //int lpfnCompare = compareCallback.getAddress (); + sThis = this; + TVSORTCB psort; + psort.hParent = cast(HTREEITEM)hParent; + psort.lpfnCompare = &CompareFunc; + psort.lParam = sortColumn is null ? 0 : indexOf (sortColumn); + OS.SendMessage (handle, OS.TVM_SORTCHILDRENCB, all ? 1 : 0, &psort); + sThis = null; + //compareCallback.dispose (); + } +} + +override void subclass () { + super.subclass (); + if (hwndHeader !is null) { + OS.SetWindowLongPtr (hwndHeader, OS.GWLP_WNDPROC, display.windowProc); + } +} + +RECT* toolTipInset (RECT* rect) { + if (!OS.IsWinCE && OS.WIN32_VERSION >= OS.VERSION (6, 0)) { + RECT* insetRect = new RECT(); + OS.SetRect (insetRect, rect.left - 1, rect.top - 1, rect.right + 1, rect.bottom + 1); + return insetRect; + } + return rect; +} + +RECT* toolTipRect (RECT* rect) { + RECT* toolRect = new RECT (); + if (!OS.IsWinCE && OS.WIN32_VERSION >= OS.VERSION (6, 0)) { + OS.SetRect (toolRect, rect.left - 1, rect.top - 1, rect.right + 1, rect.bottom + 1); + } else { + OS.SetRect (toolRect, rect.left, rect.top, rect.right, rect.bottom); + int dwStyle = OS.GetWindowLong (itemToolTipHandle, OS.GWL_STYLE); + int dwExStyle = OS.GetWindowLong (itemToolTipHandle, OS.GWL_EXSTYLE); + OS.AdjustWindowRectEx (toolRect, dwStyle, false, dwExStyle); + } + return toolRect; +} + +override String toolTipText (NMTTDISPINFO* hdr) { + auto hwndToolTip = cast(HWND) OS.SendMessage (handle, OS.TVM_GETTOOLTIPS, 0, 0); + if (hwndToolTip is hdr.hdr.hwndFrom && toolTipText_ !is null) return ""; //$NON-NLS-1$ + if (headerToolTipHandle is hdr.hdr.hwndFrom) { + for (int i=0; i<columnCount; i++) { + TreeColumn column = columns [i]; + if (column.id is hdr.hdr.idFrom) return column.toolTipText; + } + return super.toolTipText (hdr); + } + if (itemToolTipHandle is hdr.hdr.hwndFrom) { + if (toolTipText_ !is null) return ""; + int pos = OS.GetMessagePos (); + POINT pt; + OS.POINTSTOPOINT (pt, pos); + OS.ScreenToClient (handle, &pt); + int index; + TreeItem item; + RECT* cellRect, itemRect; + if (findCell (pt.x, pt.y, item, index, cellRect, itemRect)) { + String text = null; + if (index is 0) { + text = item.text; + } else { + String[] strings = item.strings; + if (strings !is null) text = strings [index]; + } + //TEMPORARY CODE + if (!OS.IsWinCE && OS.WIN32_VERSION >= OS.VERSION (6, 0)) { + if (isCustomToolTip ()) text = " "; + } + if (text !is null) return text; + } + } + return super.toolTipText (hdr); +} + +override HWND topHandle () { + return hwndParent !is null ? hwndParent : handle; +} + +void updateFullSelection () { + if ((style & SWT.FULL_SELECTION) !is 0) { + int oldBits = OS.GetWindowLong (handle, OS.GWL_STYLE), newBits = oldBits; + if ((newBits & OS.TVS_FULLROWSELECT) !is 0) { + if (!OS.IsWindowEnabled (handle) || findImageControl () !is null) { + if (!explorerTheme) newBits &= ~OS.TVS_FULLROWSELECT; + } + } else { + if (OS.IsWindowEnabled (handle) && findImageControl () is null) { + if (!hooks (SWT.EraseItem) && !hooks (SWT.PaintItem)) { + newBits |= OS.TVS_FULLROWSELECT; + } + } + } + if (newBits !is oldBits) { + OS.SetWindowLong (handle, OS.GWL_STYLE, newBits); + OS.InvalidateRect (handle, null, true); + } + } +} + +void updateHeaderToolTips () { + if (headerToolTipHandle is null) return; + RECT rect; + TOOLINFO lpti; + lpti.cbSize = OS.TOOLINFO_sizeof; + lpti.uFlags = OS.TTF_SUBCLASS; + lpti.hwnd = hwndHeader; + lpti.lpszText = OS.LPSTR_TEXTCALLBACK; + for (int i=0; i<columnCount; i++) { + TreeColumn column = columns [i]; + if (OS.SendMessage (hwndHeader, OS.HDM_GETITEMRECT, i, &rect) !is 0) { + lpti.uId = column.id = display.nextToolTipId++; + lpti.rect.left = rect.left; + lpti.rect.top = rect.top; + lpti.rect.right = rect.right; + lpti.rect.bottom = rect.bottom; + OS.SendMessage (headerToolTipHandle, OS.TTM_ADDTOOL, 0, &lpti); + } + } +} + +void updateImageList () { + if (imageList is null) return; + if (hwndHeader is null) return; + int i = 0, index = OS.SendMessage (hwndHeader, OS.HDM_ORDERTOINDEX, 0, 0); + while (i < items.length) { + TreeItem item = items [i]; + if (item !is null) { + Image image = null; + if (index is 0) { + image = item.image; + } else { + Image [] images = item.images; + if (images !is null) image = images [index]; + } + if (image !is null) break; + } + i++; + } + /* + * Feature in Windows. When setting the same image list multiple + * times, Windows does work making this operation slow. The fix + * is to test for the same image list before setting the new one. + */ + HBITMAP hImageList = i is items.length ? null : imageList.getHandle (); + HANDLE hOldImageList = cast(HANDLE) OS.SendMessage (handle, OS.TVM_GETIMAGELIST, OS.TVSIL_NORMAL, 0); + if (hImageList !is hOldImageList) { + OS.SendMessage (handle, OS.TVM_SETIMAGELIST, OS.TVSIL_NORMAL, hImageList); + } +} + +override void updateImages () { + if (sortColumn !is null && !sortColumn.isDisposed ()) { + if (OS.COMCTL32_MAJOR < 6) { + switch (sortDirection) { + case SWT.UP: + case SWT.DOWN: + sortColumn.setImage (display.getSortImage (sortDirection), true, true); + break; + default: + } + } + } +} + +void updateScrollBar () { + if (hwndParent !is null) { + if (columnCount !is 0 || scrollWidth !is 0) { + SCROLLINFO info; + info.cbSize = SCROLLINFO.sizeof; + info.fMask = OS.SIF_ALL; + int itemCount = OS.SendMessage (handle, OS.TVM_GETCOUNT, 0, 0); + if (itemCount is 0) { + OS.GetScrollInfo (hwndParent, OS.SB_VERT, &info); + info.nPage = info.nMax + 1; + OS.SetScrollInfo (hwndParent, OS.SB_VERT, &info, true); + } else { + OS.GetScrollInfo (handle, OS.SB_VERT, &info); + if (!OS.IsWinCE && OS.WIN32_VERSION >= OS.VERSION(4, 10)) { + if (info.nPage is 0) { + SCROLLBARINFO psbi; + psbi.cbSize = SCROLLBARINFO.sizeof; + OS.GetScrollBarInfo (handle, OS.OBJID_VSCROLL, &psbi); + if ((psbi.rgstate [0] & OS.STATE_SYSTEM_INVISIBLE) !is 0) { + info.nPage = info.nMax + 1; + } + } + } + OS.SetScrollInfo (hwndParent, OS.SB_VERT, &info, true); + } + } + } +} + +override void unsubclass () { + super.unsubclass (); + if (hwndHeader !is null) { + OS.SetWindowLongPtr (hwndHeader, OS.GWLP_WNDPROC, cast(LONG_PTR)HeaderProc); + } +} + +override int widgetStyle () { + int bits = super.widgetStyle () | OS.TVS_SHOWSELALWAYS | OS.TVS_LINESATROOT | OS.TVS_HASBUTTONS | OS.TVS_NONEVENHEIGHT; + if (EXPLORER_THEME && !OS.IsWinCE && OS.WIN32_VERSION >= OS.VERSION (6, 0) && OS.IsAppThemed ()) { + bits |= OS.TVS_TRACKSELECT; + if ((style & SWT.FULL_SELECTION) !is 0) bits |= OS.TVS_FULLROWSELECT; + } else { + if ((style & SWT.FULL_SELECTION) !is 0) { + bits |= OS.TVS_FULLROWSELECT; + } else { + bits |= OS.TVS_HASLINES; + } + } + if ((style & (SWT.H_SCROLL | SWT.V_SCROLL)) is 0) { + bits &= ~(OS.WS_HSCROLL | OS.WS_VSCROLL); + bits |= OS.TVS_NOSCROLL; + } else { + if ((style & SWT.H_SCROLL) is 0) { + bits &= ~OS.WS_HSCROLL; + bits |= OS.TVS_NOHSCROLL; + } + } +// bits |= OS.TVS_NOTOOLTIPS | OS.TVS_DISABLEDRAGDROP; + return bits | OS.TVS_DISABLEDRAGDROP; +} + +override String windowClass () { + return TCHARsToStr(TreeClass); +} + +override int windowProc () { + return cast(int) TreeProc; +} + +override int windowProc (HWND hwnd, int msg, int wParam, int lParam) { + if (hwndHeader !is null && hwnd is hwndHeader) { + switch (msg) { + /* This code is intentionally commented */ +// case OS.WM_CONTEXTMENU: { +// LRESULT result = wmContextMenu (hwnd, wParam, lParam); +// if (result !is null) return result.value; +// break; +// } + case OS.WM_CAPTURECHANGED: { + /* + * Bug in Windows. When the capture changes during a + * header drag, Windows does not redraw the header item + * such that the header remains pressed. For example, + * when focus is assigned to a push button, the mouse is + * pressed (but not released), then the SPACE key is + * pressed to activate the button, the capture changes, + * the header not notified and NM_RELEASEDCAPTURE is not + * sent. The fix is to redraw the header when the capture + * changes to another control. + * + * This does not happen on XP. + */ + if (OS.COMCTL32_MAJOR < 6) { + if (lParam !is 0 && cast(HANDLE) lParam !is hwndHeader) { + OS.InvalidateRect (hwndHeader, null, true); + } + } + break; + } + case OS.WM_MOUSELEAVE: { + /* + * Bug in Windows. On XP, when a tooltip is hidden + * due to a time out or mouse press, the tooltip + * remains active although no longer visible and + * won't show again until another tooltip becomes + * active. The fix is to reset the tooltip bounds. + */ + if (OS.COMCTL32_MAJOR >= 6) updateHeaderToolTips (); + updateHeaderToolTips (); + break; + } + case OS.WM_NOTIFY: { + NMHDR* hdr = cast(NMHDR*)lParam; + //OS.MoveMemory (hdr, lParam, NMHDR.sizeof); + switch (hdr.code) { + case OS.TTN_SHOW: + case OS.TTN_POP: + case OS.TTN_GETDISPINFOA: + case OS.TTN_GETDISPINFOW: + return OS.SendMessage (handle, msg, wParam, lParam); + default: + } + break; + } + case OS.WM_SETCURSOR: { + if (cast(HWND)wParam is hwnd) { + int hitTest = cast(short) OS.LOWORD (lParam); + if (hitTest is OS.HTCLIENT) { + HDHITTESTINFO pinfo; + int pos = OS.GetMessagePos (); + POINT pt; + OS.POINTSTOPOINT (pt, pos); + OS.ScreenToClient (hwnd, &pt); + pinfo.pt.x = pt.x; + pinfo.pt.y = pt.y; + int index = OS.SendMessage (hwndHeader, OS.HDM_HITTEST, 0, &pinfo); + if (0 <= index && index < columnCount && !columns [index].resizable) { + if ((pinfo.flags & (OS.HHT_ONDIVIDER | OS.HHT_ONDIVOPEN)) !is 0) { + OS.SetCursor (OS.LoadCursor (null, cast(TCHAR*) OS.IDC_ARROW)); + return 1; + } + } + } + } + break; + } + default: + } + return callWindowProc (hwnd, msg, wParam, lParam); + } + if (hwndParent !is null && hwnd is hwndParent) { + switch (msg) { + case OS.WM_MOVE: { + sendEvent (SWT.Move); + return 0; + } + case OS.WM_SIZE: { + setScrollWidth (); + if (ignoreResize) return 0; + setResizeChildren (false); + int /*long*/ code = callWindowProc (hwnd, OS.WM_SIZE, wParam, lParam); + sendEvent (SWT.Resize); + if (isDisposed ()) return 0; + if (layout_ !is null) { + markLayout (false, false); + updateLayout (false, false); + } + setResizeChildren (true); + updateScrollBar (); + return code; + } + case OS.WM_NCPAINT: { + LRESULT result = wmNCPaint (hwnd, wParam, lParam); + if (result !is null) return result.value; + break; + } + case OS.WM_PRINT: { + LRESULT result = wmPrint (hwnd, wParam, lParam); + if (result !is null) return result.value; + break; + } + case OS.WM_COMMAND: + case OS.WM_NOTIFY: + case OS.WM_SYSCOLORCHANGE: { + return OS.SendMessage (handle, msg, wParam, lParam); + } + case OS.WM_HSCROLL: { + /* + * Bug on WinCE. lParam should be NULL when the message is not sent + * by a scroll bar control, but it contains the handle to the window. + * When the message is sent by a scroll bar control, it correctly + * contains the handle to the scroll bar. The fix is to check for + * both. + */ + if (horizontalBar !is null && (lParam is 0 || lParam is cast(int)hwndParent)) { + wmScroll (horizontalBar, true, hwndParent, OS.WM_HSCROLL, wParam, lParam); + } + setScrollWidth (); + break; + } + case OS.WM_VSCROLL: { + SCROLLINFO info; + info.cbSize = SCROLLINFO.sizeof; + info.fMask = OS.SIF_ALL; + OS.GetScrollInfo (hwndParent, OS.SB_VERT, &info); + /* + * Update the nPos field to match the nTrackPos field + * so that the tree scrolls when the scroll bar of the + * parent is dragged. + * + * NOTE: For some reason, this code is only necessary + * on Windows Vista. + */ + if (!OS.IsWinCE && OS.WIN32_VERSION >= OS.VERSION (6, 0)) { + if (OS.LOWORD (wParam) is OS.SB_THUMBTRACK) { + info.nPos = info.nTrackPos; + } + } + OS.SetScrollInfo (handle, OS.SB_VERT, &info, true); + int /*long*/ code = OS.SendMessage (handle, OS.WM_VSCROLL, wParam, lParam); + OS.GetScrollInfo (handle, OS.SB_VERT, &info); + OS.SetScrollInfo (hwndParent, OS.SB_VERT, &info, true); + return code; + } + default: + } + return callWindowProc (hwnd, msg, wParam, lParam); + } + if (msg is Display.DI_GETDRAGIMAGE) { + /* + * When there is more than one item selected, DI_GETDRAGIMAGE + * returns the item under the cursor. This happens because + * the tree does not have implement multi-select. The fix + * is to disable DI_GETDRAGIMAGE when more than one item is + * selected. + */ + if ((style & SWT.MULTI) !is 0 || hooks (SWT.EraseItem) || hooks (SWT.PaintItem)) { + auto hItem = cast(HANDLE) OS.SendMessage (handle, OS.TVM_GETNEXTITEM, OS.TVGN_FIRSTVISIBLE, 0); + TreeItem [] items = new TreeItem [10]; + TVITEM tvItem; + tvItem.mask = OS.TVIF_HANDLE | OS.TVIF_PARAM | OS.TVIF_STATE; + int count = getSelection (hItem, &tvItem, items, 0, 10, false, true); + if (count is 0) return 0; + POINT mousePos; + OS.POINTSTOPOINT (mousePos, OS.GetMessagePos ()); + OS.MapWindowPoints (null, handle, &mousePos, 1); + RECT clientRect; + OS.GetClientRect(handle, &clientRect); + RECT rect = *(items [0].getBounds (0, true, true, false)); + if ((style & SWT.FULL_SELECTION) !is 0) { + int width = DRAG_IMAGE_SIZE; + rect.left = Math.max (clientRect.left, mousePos.x - width / 2); + if (clientRect.right > rect.left + width) { + rect.right = rect.left + width; + } else { + rect.right = clientRect.right; + rect.left = Math.max (clientRect.left, rect.right - width); + } + } + auto hRgn = OS.CreateRectRgn (rect.left, rect.top, rect.right, rect.bottom); + for (int i = 1; i < count; i++) { + if (rect.bottom - rect.top > DRAG_IMAGE_SIZE) break; + if (rect.bottom > clientRect.bottom) break; + RECT itemRect = *(items[i].getBounds (0, true, true, false)); + if ((style & SWT.FULL_SELECTION) !is 0) { + itemRect.left = rect.left; + itemRect.right = rect.right; + } + auto rectRgn = OS.CreateRectRgn (itemRect.left, itemRect.top, itemRect.right, itemRect.bottom); + OS.CombineRgn (hRgn, hRgn, rectRgn, OS.RGN_OR); + OS.DeleteObject (rectRgn); + rect.bottom = itemRect.bottom; + + } + OS.GetRgnBox (hRgn, &rect); + + /* Create resources */ + auto hdc = OS.GetDC (handle); + auto memHdc = OS.CreateCompatibleDC (hdc); + BITMAPINFOHEADER bmiHeader; + bmiHeader.biSize = BITMAPINFOHEADER.sizeof; + bmiHeader.biWidth = rect.right - rect.left; + bmiHeader.biHeight = -(rect.bottom - rect.top); + bmiHeader.biPlanes = 1; + bmiHeader.biBitCount = 32; + bmiHeader.biCompression = OS.BI_RGB; + byte [] bmi = new byte [BITMAPINFOHEADER.sizeof]; + OS.MoveMemory (bmi.ptr, &bmiHeader, BITMAPINFOHEADER.sizeof); + void* [1] pBits; + auto memDib = OS.CreateDIBSection (null, cast(BITMAPINFO*) bmi.ptr, OS.DIB_RGB_COLORS, pBits.ptr, null, 0); + if (memDib is null) SWT.error (SWT.ERROR_NO_HANDLES); + auto oldMemBitmap = OS.SelectObject (memHdc, memDib); + int colorKey = 0x0000FD; + POINT pt; + OS.SetWindowOrgEx (memHdc, rect.left, rect.top, &pt); + OS.FillRect (memHdc, &rect, findBrush (colorKey, OS.BS_SOLID)); + OS.OffsetRgn (hRgn, -rect.left, -rect.top); + OS.SelectClipRgn (memHdc, hRgn); + OS.PrintWindow (handle, memHdc, 0); + OS.SetWindowOrgEx (memHdc, pt.x, pt.y, null); + OS.SelectObject (memHdc, oldMemBitmap); + OS.DeleteDC (memHdc); + OS.ReleaseDC (null, hdc); + OS.DeleteObject (hRgn); + + SHDRAGIMAGE shdi; + shdi.hbmpDragImage = memDib; + shdi.crColorKey = colorKey; + shdi.sizeDragImage.cx = bmiHeader.biWidth; + shdi.sizeDragImage.cy = -bmiHeader.biHeight; + shdi.ptOffset.x = mousePos.x - rect.left; + shdi.ptOffset.y = mousePos.y - rect.top; + if ((style & SWT.MIRRORED) !is 0) { + shdi.ptOffset.x = shdi.sizeDragImage.cx - shdi.ptOffset.x; + } + OS.MoveMemory (cast(void*)lParam, &shdi, SHDRAGIMAGE.sizeof); + return 1; + } + } + return super.windowProc (hwnd, msg, wParam, lParam); +} + +override LRESULT WM_CHAR (int wParam, int lParam) { + LRESULT result = super.WM_CHAR (wParam, lParam); + if (result !is null) return result; + /* + * Feature in Windows. The tree control beeps + * in WM_CHAR when the search for the item that + * matches the key stroke fails. This is the + * standard tree behavior but is unexpected when + * the key that was typed was ESC, CR or SPACE. + * The fix is to avoid calling the tree window + * proc in these cases. + */ + switch (wParam) { + case ' ': { + HANDLE hItem = cast(HANDLE) OS.SendMessage (handle, OS.TVM_GETNEXTITEM, OS.TVGN_CARET, 0); + if (hItem !is null) { + hAnchor = hItem; + OS.SendMessage (handle, OS.TVM_ENSUREVISIBLE, 0, hItem); + TVITEM tvItem; + tvItem.mask = OS.TVIF_HANDLE | OS.TVIF_STATE | OS.TVIF_PARAM; + tvItem.hItem = cast(HTREEITEM)hItem; + if ((style & SWT.CHECK) !is 0) { + tvItem.stateMask = OS.TVIS_STATEIMAGEMASK; + OS.SendMessage (handle, OS.TVM_GETITEM, 0, &tvItem); + int state = tvItem.state >> 12; + if ((state & 0x1) !is 0) { + state++; + } else { + --state; + } + tvItem.state = state << 12; + OS.SendMessage (handle, OS.TVM_SETITEM, 0, &tvItem); + static if (!OS.IsWinCE) { + int id = cast(int) hItem; + if (OS.COMCTL32_MAJOR >= 6) { + id = OS.SendMessage (handle, OS.TVM_MAPHTREEITEMTOACCID, hItem, 0); + } + OS.NotifyWinEvent (OS.EVENT_OBJECT_FOCUS, handle, OS.OBJID_CLIENT, id); + } + } + tvItem.stateMask = OS.TVIS_SELECTED; + OS.SendMessage (handle, OS.TVM_GETITEM, 0, &tvItem); + if ((style & SWT.MULTI) !is 0 && OS.GetKeyState (OS.VK_CONTROL) < 0) { + if ((tvItem.state & OS.TVIS_SELECTED) !is 0) { + tvItem.state &= ~OS.TVIS_SELECTED; + } else { + tvItem.state |= OS.TVIS_SELECTED; + } + } else { + tvItem.state |= OS.TVIS_SELECTED; + } + OS.SendMessage (handle, OS.TVM_SETITEM, 0, &tvItem); + TreeItem item = _getItem (hItem, tvItem.lParam); + Event event = new Event (); + event.item = item; + postEvent (SWT.Selection, event); + if ((style & SWT.CHECK) !is 0) { + event = new Event (); + event.item = item; + event.detail = SWT.CHECK; + postEvent (SWT.Selection, event); + } + } + return LRESULT.ZERO; + } + case SWT.CR: { + /* + * Feature in Windows. Windows sends NM_RETURN from WM_KEYDOWN + * instead of using WM_CHAR. This means that application code + * that expects to consume the key press and therefore avoid a + * SWT.DefaultSelection event from WM_CHAR will fail. The fix + * is to implement SWT.DefaultSelection in WM_CHAR instead of + * using NM_RETURN. + */ + Event event = new Event (); + HANDLE hItem = cast(HANDLE) OS.SendMessage (handle, OS.TVM_GETNEXTITEM, OS.TVGN_CARET, 0); + if (hItem !is null) event.item = _getItem (hItem); + postEvent (SWT.DefaultSelection, event); + return LRESULT.ZERO; + } + case SWT.ESC: + return LRESULT.ZERO; + default: + } + return result; +} + +override LRESULT WM_ERASEBKGND (int wParam, int lParam) { + LRESULT result = super.WM_ERASEBKGND (wParam, lParam); + if ((style & SWT.DOUBLE_BUFFERED) !is 0) return LRESULT.ONE; + if (findImageControl () !is null) return LRESULT.ONE; + return result; +} + +override LRESULT WM_GETOBJECT (int wParam, int lParam) { + /* + * Ensure that there is an accessible object created for this + * control because support for checked item and tree column + * accessibility is temporarily implemented in the accessibility + * package. + */ + if ((style & SWT.CHECK) !is 0 || hwndParent !is null) { + if (accessible is null) accessible = new_Accessible (this); + } + return super.WM_GETOBJECT (wParam, lParam); +} + +override LRESULT WM_HSCROLL (int wParam, int lParam) { + bool fixScroll = false; + if ((style & SWT.DOUBLE_BUFFERED) !is 0) { + fixScroll = (style & SWT.VIRTUAL) !is 0 || hooks (SWT.EraseItem) || hooks (SWT.PaintItem); + } + if (fixScroll) { + style &= ~SWT.DOUBLE_BUFFERED; + if (explorerTheme) { + OS.SendMessage (handle, OS.TVM_SETEXTENDEDSTYLE, OS.TVS_EX_DOUBLEBUFFER, 0); + } + } + LRESULT result = super.WM_HSCROLL (wParam, lParam); + if (fixScroll) { + style |= SWT.DOUBLE_BUFFERED; + if (explorerTheme) { + OS.SendMessage (handle, OS.TVM_SETEXTENDEDSTYLE, OS.TVS_EX_DOUBLEBUFFER, OS.TVS_EX_DOUBLEBUFFER); + } + } + if (result !is null) return result; + return result; +} + +override LRESULT WM_KEYDOWN (int /*long*/ wParam, int /*long*/ lParam) { + LRESULT result = super.WM_KEYDOWN (wParam, lParam); + if (result !is null) return result; + switch (wParam) { + case OS.VK_SPACE: + /* + * Ensure that the window proc does not process VK_SPACE + * so that it can be handled in WM_CHAR. This allows the + * application to cancel an operation that is normally + * performed in WM_KEYDOWN from WM_CHAR. + */ + return LRESULT.ZERO; + case OS.VK_ADD: + if (OS.GetKeyState (OS.VK_CONTROL) < 0) { + if (hwndHeader !is null) { + TreeColumn [] newColumns = new TreeColumn [columnCount]; + System.arraycopy (columns, 0, newColumns, 0, columnCount); + for (int i=0; i<columnCount; i++) { + TreeColumn column = newColumns [i]; + if (!column.isDisposed () && column.getResizable ()) { + column.pack (); + } + } + } + } + break; + case OS.VK_UP: + case OS.VK_DOWN: + case OS.VK_PRIOR: + case OS.VK_NEXT: + case OS.VK_HOME: + case OS.VK_END: { + OS.SendMessage (handle, OS.WM_CHANGEUISTATE, OS.UIS_INITIALIZE, 0); + if ((style & SWT.SINGLE) !is 0) break; + if (OS.GetKeyState (OS.VK_SHIFT) < 0) { + HANDLE hItem = cast(HANDLE) OS.SendMessage (handle, OS.TVM_GETNEXTITEM, OS.TVGN_CARET, 0); + if (hItem !is null) { + if (hAnchor is null) hAnchor = hItem; + ignoreSelect = ignoreDeselect = true; + int /*long*/ code = callWindowProc (handle, OS.WM_KEYDOWN, wParam, lParam); + ignoreSelect = ignoreDeselect = false; + auto hNewItem = cast(HANDLE) OS.SendMessage (handle, OS.TVM_GETNEXTITEM, OS.TVGN_CARET, 0); + TVITEM tvItem; + tvItem.mask = OS.TVIF_HANDLE | OS.TVIF_STATE; + tvItem.stateMask = OS.TVIS_SELECTED; + auto hDeselectItem = hItem; + RECT rect1; + if (!OS.TreeView_GetItemRect (handle, cast(HTREEITEM)hAnchor, &rect1, false)) { + hAnchor = hItem; + OS.TreeView_GetItemRect (handle, cast(HTREEITEM)hAnchor, &rect1, false); + } + RECT rect2; + OS.TreeView_GetItemRect (handle, cast(HTREEITEM)hDeselectItem, &rect2, false); + int flags = rect1.top < rect2.top ? OS.TVGN_PREVIOUSVISIBLE : OS.TVGN_NEXTVISIBLE; + while (hDeselectItem !is hAnchor) { + tvItem.hItem = cast(HTREEITEM)hDeselectItem; + OS.SendMessage (handle, OS.TVM_SETITEM, 0, &tvItem); + hDeselectItem = cast(HANDLE) OS.SendMessage (handle, OS.TVM_GETNEXTITEM, flags, hDeselectItem); + } + auto hSelectItem = hAnchor; + OS.TreeView_GetItemRect (handle, cast(HTREEITEM)hNewItem, &rect1, false); + OS.TreeView_GetItemRect (handle, cast(HTREEITEM)hSelectItem, &rect2, false); + tvItem.state = OS.TVIS_SELECTED; + flags = rect1.top < rect2.top ? OS.TVGN_PREVIOUSVISIBLE : OS.TVGN_NEXTVISIBLE; + while (hSelectItem !is hNewItem) { + tvItem.hItem = cast(HTREEITEM)hSelectItem; + OS.SendMessage (handle, OS.TVM_SETITEM, 0, &tvItem); + hSelectItem = cast(HANDLE) OS.SendMessage (handle, OS.TVM_GETNEXTITEM, flags, hSelectItem); + } + tvItem.hItem = cast(HTREEITEM)hNewItem; + OS.SendMessage (handle, OS.TVM_SETITEM, 0, &tvItem); + tvItem.mask = OS.TVIF_HANDLE | OS.TVIF_PARAM; + tvItem.hItem = cast(HTREEITEM)hNewItem; + OS.SendMessage (handle, OS.TVM_GETITEM, 0, &tvItem); + Event event = new Event (); + event.item = _getItem (hNewItem, tvItem.lParam); + postEvent (SWT.Selection, event); + return new LRESULT (code); + } + } + if (OS.GetKeyState (OS.VK_CONTROL) < 0) { + HANDLE hItem = cast(HANDLE) OS.SendMessage (handle, OS.TVM_GETNEXTITEM, OS.TVGN_CARET, 0); + if (hItem !is null) { + TVITEM tvItem; + tvItem.mask = OS.TVIF_HANDLE | OS.TVIF_STATE; + tvItem.stateMask = OS.TVIS_SELECTED; + tvItem.hItem = cast(HTREEITEM)hItem; + OS.SendMessage (handle, OS.TVM_GETITEM, 0, &tvItem); + bool oldSelected = (tvItem.state & OS.TVIS_SELECTED) !is 0; + HANDLE hNewItem; + switch (wParam) { + case OS.VK_UP: + hNewItem = cast(HANDLE) OS.SendMessage (handle, OS.TVM_GETNEXTITEM, OS.TVGN_PREVIOUSVISIBLE, hItem); + break; + case OS.VK_DOWN: + hNewItem = cast(HANDLE) OS.SendMessage (handle, OS.TVM_GETNEXTITEM, OS.TVGN_NEXTVISIBLE, hItem); + break; + case OS.VK_HOME: + hNewItem = cast(HANDLE) OS.SendMessage (handle, OS.TVM_GETNEXTITEM, OS.TVGN_ROOT, 0); + break; + case OS.VK_PRIOR: + hNewItem = cast(HANDLE) OS.SendMessage (handle, OS.TVM_GETNEXTITEM, OS.TVGN_FIRSTVISIBLE, 0); + if (hNewItem is hItem) { + OS.SendMessage (handle, OS.WM_VSCROLL, OS.SB_PAGEUP, 0); + hNewItem = cast(HANDLE) OS.SendMessage (handle, OS.TVM_GETNEXTITEM, OS.TVGN_FIRSTVISIBLE, 0); + } + break; + case OS.VK_NEXT: + RECT rect, clientRect; + OS.GetClientRect (handle, &clientRect); + hNewItem = cast(HANDLE) OS.SendMessage (handle, OS.TVM_GETNEXTITEM, OS.TVGN_FIRSTVISIBLE, 0); + do { + auto hVisible = cast(HANDLE) OS.SendMessage (handle, OS.TVM_GETNEXTITEM, OS.TVGN_NEXTVISIBLE, hNewItem); + if (hVisible is null) break; + if (!OS.TreeView_GetItemRect (handle, cast(HTREEITEM)hVisible, &rect, false)) break; + if (rect.bottom > clientRect.bottom) break; + if ((hNewItem = hVisible) is hItem) { + OS.SendMessage (handle, OS.WM_VSCROLL, OS.SB_PAGEDOWN, 0); + } + } while (hNewItem !is null); + break; + case OS.VK_END: + hNewItem = cast(HANDLE) OS.SendMessage (handle, OS.TVM_GETNEXTITEM, OS.TVGN_LASTVISIBLE, 0); + break; + default: + } + if (hNewItem !is null) { + OS.SendMessage (handle, OS.TVM_ENSUREVISIBLE, 0, hNewItem); + tvItem.hItem = cast(HTREEITEM)hNewItem; + OS.SendMessage (handle, OS.TVM_GETITEM, 0, &tvItem); + bool newSelected = (tvItem.state & OS.TVIS_SELECTED) !is 0; + bool redraw = !newSelected && drawCount is 0 && OS.IsWindowVisible (handle); + if (redraw) { + OS.UpdateWindow (handle); + OS.DefWindowProc (handle, OS.WM_SETREDRAW, 0, 0); + } + hSelect = hNewItem; + ignoreSelect = true; + OS.SendMessage (handle, OS.TVM_SELECTITEM, OS.TVGN_CARET, hNewItem); + ignoreSelect = false; + hSelect = null; + if (oldSelected) { + tvItem.state = OS.TVIS_SELECTED; + tvItem.hItem = cast(HTREEITEM)hItem; + OS.SendMessage (handle, OS.TVM_SETITEM, 0, &tvItem); + } + if (!newSelected) { + tvItem.state = 0; + tvItem.hItem = cast(HTREEITEM)hNewItem; + OS.SendMessage (handle, OS.TVM_SETITEM, 0, &tvItem); + } + if (redraw) { + RECT rect1, rect2; + bool fItemRect = (style & SWT.FULL_SELECTION) is 0; + if (hooks (SWT.EraseItem) || hooks (SWT.PaintItem)) fItemRect = false; + if (!OS.IsWinCE && OS.WIN32_VERSION >= OS.VERSION (6, 0)) fItemRect = false; + OS.TreeView_GetItemRect (handle, cast(HTREEITEM)hItem, &rect1, fItemRect); + OS.TreeView_GetItemRect (handle, cast(HTREEITEM)hNewItem, &rect2, fItemRect); + OS.DefWindowProc (handle, OS.WM_SETREDRAW, 1, 0); + OS.InvalidateRect (handle, &rect1, true); + OS.InvalidateRect (handle, &rect2, true); + OS.UpdateWindow (handle); + } + return LRESULT.ZERO; + } + } + } + int /*long*/ code = callWindowProc (handle, OS.WM_KEYDOWN, wParam, lParam); + hAnchor = cast(HANDLE) OS.SendMessage (handle, OS.TVM_GETNEXTITEM, OS.TVGN_CARET, 0); + return new LRESULT (code); + } + default: + } + return result; +} + +override LRESULT WM_KILLFOCUS (int wParam, int lParam) { + /* + * Bug in Windows. When a tree item that has an image + * with alpha is expanded or collapsed, the area where + * the image is drawn is not erased before it is drawn. + * This means that the image gets darker each time. + * The fix is to redraw the selection. + * + * Feature in Windows. When multiple item have + * the TVIS_SELECTED state, Windows redraws only + * the focused item in the color used to show the + * selection when the tree loses or gains focus. + * The fix is to force Windows to redraw the + * selection when focus is gained or lost. + */ + bool redraw = (style & SWT.MULTI) !is 0; + if (!redraw) { + if (!OS.IsWinCE && OS.COMCTL32_MAJOR >= 6) { + if (imageList !is null) { + int bits = OS.GetWindowLong (handle, OS.GWL_STYLE); + if ((bits & OS.TVS_FULLROWSELECT) is 0) { + redraw = true; + } + } + } + } + if (redraw) redrawSelection (); + return super.WM_KILLFOCUS (wParam, lParam); +} + +override LRESULT WM_LBUTTONDBLCLK (int wParam, int lParam) { + TVHITTESTINFO lpht; + lpht.pt.x = OS.GET_X_LPARAM (lParam); + lpht.pt.y = OS.GET_Y_LPARAM (lParam); + OS.SendMessage (handle, OS.TVM_HITTEST, 0, &lpht); + if (lpht.hItem !is null) { + if ((style & SWT.CHECK) !is 0) { + if ((lpht.flags & OS.TVHT_ONITEMSTATEICON) !is 0) { + Display display = this.display; + display.captureChanged = false; + sendMouseEvent (SWT.MouseDown, 1, handle, OS.WM_LBUTTONDOWN, wParam, lParam); + if (!sendMouseEvent (SWT.MouseDoubleClick, 1, handle, OS.WM_LBUTTONDBLCLK, wParam, lParam)) { + if (!display.captureChanged && !isDisposed ()) { + if (OS.GetCapture () !is handle) OS.SetCapture (handle); + } + return LRESULT.ZERO; + } + if (!display.captureChanged && !isDisposed ()) { + if (OS.GetCapture () !is handle) OS.SetCapture (handle); + } + OS.SetFocus (handle); + TVITEM tvItem; + tvItem.hItem = lpht.hItem; + tvItem.mask = OS.TVIF_HANDLE | OS.TVIF_PARAM | OS.TVIF_STATE; + tvItem.stateMask = OS.TVIS_STATEIMAGEMASK; + OS.SendMessage (handle, OS.TVM_GETITEM, 0, &tvItem); + int state = tvItem.state >> 12; + if ((state & 0x1) !is 0) { + state++; + } else { + --state; + } + tvItem.state = state << 12; + OS.SendMessage (handle, OS.TVM_SETITEM, 0, &tvItem); + static if (!OS.IsWinCE) { + int id = cast(int) tvItem.hItem; + if (OS.COMCTL32_MAJOR >= 6) { + id = OS.SendMessage (handle, OS.TVM_MAPHTREEITEMTOACCID, tvItem.hItem, 0); + } + OS.NotifyWinEvent (OS.EVENT_OBJECT_FOCUS, handle, OS.OBJID_CLIENT, id); + } + Event event = new Event (); + event.item = _getItem (tvItem.hItem, tvItem.lParam); + event.detail = SWT.CHECK; + postEvent (SWT.Selection, event); + return LRESULT.ZERO; + } + } + } + LRESULT result = super.WM_LBUTTONDBLCLK (wParam, lParam); + if (result is LRESULT.ZERO) return result; + if (lpht.hItem !is null) { + int flags = OS.TVHT_ONITEM; + if ((style & SWT.FULL_SELECTION) !is 0) { + flags |= OS.TVHT_ONITEMRIGHT | OS.TVHT_ONITEMINDENT; + } else { + if (hooks (SWT.MeasureItem)) { + lpht.flags &= ~(OS.TVHT_ONITEMICON | OS.TVHT_ONITEMLABEL); + if (hitTestSelection (lpht.hItem, lpht.pt.x, lpht.pt.y)) { + lpht.flags |= OS.TVHT_ONITEMICON | OS.TVHT_ONITEMLABEL; + } + } + } + if ((lpht.flags & flags) !is 0) { + Event event = new Event (); + event.item = _getItem (lpht.hItem); + postEvent (SWT.DefaultSelection, event); + } + } + return result; +} + +override LRESULT WM_LBUTTONDOWN (int wParam, int lParam) { + /* + * In a multi-select tree, if the user is collapsing a subtree that + * contains selected items, clear the selection from these items and + * issue a selection event. Only items that are selected and visible + * are cleared. This code also runs in the case when the white space + * below the last item is selected. + */ + TVHITTESTINFO lpht; + lpht.pt.x = OS.GET_X_LPARAM (lParam); + lpht.pt.y = OS.GET_Y_LPARAM (lParam); + OS.SendMessage (handle, OS.TVM_HITTEST, 0, &lpht); + if (lpht.hItem is null || (lpht.flags & OS.TVHT_ONITEMBUTTON) !is 0) { + Display display = this.display; + display.captureChanged = false; + if (!sendMouseEvent (SWT.MouseDown, 1, handle, OS.WM_LBUTTONDOWN, wParam, lParam)) { + if (!display.captureChanged && !isDisposed ()) { + if (OS.GetCapture () !is handle) OS.SetCapture (handle); + } + return LRESULT.ZERO; + } + bool fixSelection = false, deselected = false; + HANDLE hOldSelection = cast(HANDLE) OS.SendMessage (handle, OS.TVM_GETNEXTITEM, OS.TVGN_CARET, 0); + if (lpht.hItem !is null && (style & SWT.MULTI) !is 0) { + if (hOldSelection !is null) { + TVITEM tvItem; + tvItem.mask = OS.TVIF_HANDLE | OS.TVIF_STATE; + tvItem.hItem = lpht.hItem; + OS.SendMessage (handle, OS.TVM_GETITEM, 0, &tvItem); + if ((tvItem.state & OS.TVIS_EXPANDED) !is 0) { + fixSelection = true; + tvItem.stateMask = OS.TVIS_SELECTED; + auto hNext = cast(HANDLE) OS.SendMessage (handle, OS.TVM_GETNEXTITEM, OS.TVGN_NEXTVISIBLE, lpht.hItem); + while (hNext !is null) { + if (hNext is hAnchor) hAnchor = null; + tvItem.hItem = cast(HTREEITEM)hNext; + OS.SendMessage (handle, OS.TVM_GETITEM, 0, &tvItem); + if ((tvItem.state & OS.TVIS_SELECTED) !is 0) deselected = true; + tvItem.state = 0; + OS.SendMessage (handle, OS.TVM_SETITEM, 0, &tvItem); + HANDLE hItem = hNext = cast(HANDLE) OS.SendMessage (handle, OS.TVM_GETNEXTITEM, OS.TVGN_NEXTVISIBLE, hNext); + while (hItem !is null && hItem !is lpht.hItem) { + hItem = cast(HANDLE) OS.SendMessage (handle, OS.TVM_GETNEXTITEM, OS.TVGN_PARENT, hItem); + } + if (hItem is null) break; + } + } + } + } + dragStarted = gestureCompleted = false; + if (fixSelection) ignoreDeselect = ignoreSelect = lockSelection = true; + int /*long*/ code = callWindowProc (handle, OS.WM_LBUTTONDOWN, wParam, lParam); + if (!OS.IsWinCE && OS.WIN32_VERSION >= OS.VERSION (6, 0)) { + if (OS.GetFocus () !is handle) OS.SetFocus (handle); + } + if (fixSelection) ignoreDeselect = ignoreSelect = lockSelection = false; + HANDLE hNewSelection = cast(HANDLE) OS.SendMessage (handle, OS.TVM_GETNEXTITEM, OS.TVGN_CARET, 0); + if (hOldSelection !is hNewSelection) hAnchor = hNewSelection; + if (dragStarted) { + if (!display.captureChanged && !isDisposed ()) { + if (OS.GetCapture () !is handle) OS.SetCapture (handle); + } + } + /* + * Bug in Windows. When a tree has no images and an item is + * expanded or collapsed, for some reason, Windows changes + * the size of the selection. When the user expands a tree + * item, the selection rectangle is made a few pixels larger. + * When the user collapses an item, the selection rectangle + * is restored to the original size but the selection is not + * redrawn, causing pixel corruption. The fix is to detect + * this case and redraw the item. + */ + if ((lpht.flags & OS.TVHT_ONITEMBUTTON) !is 0) { + int bits = OS.GetWindowLong (handle, OS.GWL_STYLE); + if ((bits & OS.TVS_FULLROWSELECT) is 0) { + if (OS.SendMessage (handle, OS.TVM_GETIMAGELIST, OS.TVSIL_NORMAL, 0) is 0) { + auto hItem = cast(HANDLE) OS.SendMessage (handle, OS.TVM_GETNEXTITEM, OS.TVGN_CARET, 0); + if (hItem !is null) { + RECT rect; + if (OS.TreeView_GetItemRect (handle, cast(HTREEITEM)hItem, &rect, false)) { + OS.InvalidateRect (handle, &rect, true); + } + } + } + } + } + if (deselected) { + Event event = new Event (); + event.item = _getItem (lpht.hItem); + postEvent (SWT.Selection, event); + } + return new LRESULT (code); + } + + /* Look for check/uncheck */ + if ((style & SWT.CHECK) !is 0) { + if ((lpht.flags & OS.TVHT_ONITEMSTATEICON) !is 0) { + Display display = this.display; + display.captureChanged = false; + if (!sendMouseEvent (SWT.MouseDown, 1, handle, OS.WM_LBUTTONDOWN, wParam, lParam)) { + if (!display.captureChanged && !isDisposed ()) { + if (OS.GetCapture () !is handle) OS.SetCapture (handle); + } + return LRESULT.ZERO; + } + if (!display.captureChanged && !isDisposed ()) { + if (OS.GetCapture () !is handle) OS.SetCapture (handle); + } + OS.SetFocus (handle); + TVITEM tvItem; + tvItem.hItem = lpht.hItem; + tvItem.mask = OS.TVIF_HANDLE | OS.TVIF_PARAM | OS.TVIF_STATE; + tvItem.stateMask = OS.TVIS_STATEIMAGEMASK; + OS.SendMessage (handle, OS.TVM_GETITEM, 0, &tvItem); + int state = tvItem.state >> 12; + if ((state & 0x1) !is 0) { + state++; + } else { + --state; + } + tvItem.state = state << 12; + OS.SendMessage (handle, OS.TVM_SETITEM, 0, &tvItem); + static if (!OS.IsWinCE) { + int id = cast(int) tvItem.hItem; + if (OS.COMCTL32_MAJOR >= 6) { + id = OS.SendMessage (handle, OS.TVM_MAPHTREEITEMTOACCID, tvItem.hItem, 0); + } + OS.NotifyWinEvent (OS.EVENT_OBJECT_FOCUS, handle, OS.OBJID_CLIENT, id); + } + Event event = new Event (); + event.item = _getItem (tvItem.hItem, tvItem.lParam); + event.detail = SWT.CHECK; + postEvent (SWT.Selection, event); + return LRESULT.ZERO; + } + } + + /* + * Feature in Windows. When the tree has the style + * TVS_FULLROWSELECT, the background color for the + * entire row is filled when an item is painted, + * drawing on top of any custom drawing. The fix + * is to emulate TVS_FULLROWSELECT. + */ + bool selected = false; + bool fakeSelection = false; + if (lpht.hItem !is null) { + if ((style & SWT.FULL_SELECTION) !is 0) { + int bits = OS.GetWindowLong (handle, OS.GWL_STYLE); + if ((bits & OS.TVS_FULLROWSELECT) is 0) fakeSelection = true; + } else { + if (hooks (SWT.MeasureItem)) { + selected = hitTestSelection (lpht.hItem, lpht.pt.x, lpht.pt.y) !is 0; + if (selected) { + if ((lpht.flags & OS.TVHT_ONITEM) is 0) fakeSelection = true; + } + } + } + } + + /* Process the mouse when an item is not selected */ + if (!selected && (style & SWT.FULL_SELECTION) is 0) { + if ((lpht.flags & OS.TVHT_ONITEM) is 0) { + Display display = this.display; + display.captureChanged = false; + if (!sendMouseEvent (SWT.MouseDown, 1, handle, OS.WM_LBUTTONDOWN, wParam, lParam)) { + if (!display.captureChanged && !isDisposed ()) { + if (OS.GetCapture () !is handle) OS.SetCapture (handle); + } + return LRESULT.ZERO; + } + int /*long*/ code = callWindowProc (handle, OS.WM_LBUTTONDOWN, wParam, lParam); + if (!OS.IsWinCE && OS.WIN32_VERSION >= OS.VERSION (6, 0)) { + if (OS.GetFocus () !is handle) OS.SetFocus (handle); + } + if (!display.captureChanged && !isDisposed ()) { + if (OS.GetCapture () !is handle) OS.SetCapture (handle); + } + return new LRESULT (code); + } + } + + /* Get the selected state of the item under the mouse */ + TVITEM tvItem; + tvItem.mask = OS.TVIF_HANDLE | OS.TVIF_STATE; + tvItem.stateMask = OS.TVIS_SELECTED; + bool hittestSelected = false; + if ((style & SWT.MULTI) !is 0) { + tvItem.hItem = lpht.hItem; + OS.SendMessage (handle, OS.TVM_GETITEM, 0, &tvItem); + hittestSelected = (tvItem.state & OS.TVIS_SELECTED) !is 0; + } + + /* Get the selected state of the last selected item */ + auto hOldItem = cast(HANDLE) OS.SendMessage (handle, OS.TVM_GETNEXTITEM, OS.TVGN_CARET, 0); + if ((style & SWT.MULTI) !is 0) { + tvItem.hItem = cast(HTREEITEM)hOldItem; + OS.SendMessage (handle, OS.TVM_GETITEM, 0, &tvItem); + + /* Check for CONTROL or drag selection */ + if (hittestSelected || (wParam & OS.MK_CONTROL) !is 0) { + /* + * Feature in Windows. When the tree is not drawing focus + * and the user selects a tree item while the CONTROL key + * is down, the tree window proc sends WM_UPDATEUISTATE + * to the top level window, causing controls within the shell + * to redraw. When drag detect is enabled, the tree window + * proc runs a modal loop that allows WM_PAINT messages to be + * delivered during WM_LBUTTONDOWN. When WM_SETREDRAW is used + * to disable drawing for the tree and a WM_PAINT happens for + * a parent of the tree (or a sibling that overlaps), the parent + * will draw on top of the tree. If WM_SETREDRAW is turned back + * on without redrawing the entire tree, pixel corruption occurs. + * This case only seems to happen when the tree has been given + * focus from WM_MOUSEACTIVATE of the shell. The fix is to + * force the WM_UPDATEUISTATE to be sent before disabling + * the drawing. + * + * NOTE: Any redraw of a parent (or sibling) will be dispatched + * during the modal drag detect loop. This code only fixes the + * case where the tree causes a redraw from WM_UPDATEUISTATE. + * In SWT, the InvalidateRect() that caused the pixel corruption + * is found in Composite.WM_UPDATEUISTATE(). + */ + int uiState = OS.SendMessage (handle, OS.WM_QUERYUISTATE, 0, 0); + if ((uiState & OS.UISF_HIDEFOCUS) !is 0) { + OS.SendMessage (handle, OS.WM_CHANGEUISTATE, OS.UIS_INITIALIZE, 0); + } + OS.UpdateWindow (handle); + OS.DefWindowProc (handle, OS.WM_SETREDRAW, 0, 0); + } else { + deselectAll (); + } + } + + /* Do the selection */ + Display display = this.display; + display.captureChanged = false; + if (!sendMouseEvent (SWT.MouseDown, 1, handle, OS.WM_LBUTTONDOWN, wParam, lParam)) { + if (!display.captureChanged && !isDisposed ()) { + if (OS.GetCapture () !is handle) OS.SetCapture (handle); + } + return LRESULT.ZERO; + } + hSelect = lpht.hItem; + dragStarted = gestureCompleted = false; + ignoreDeselect = ignoreSelect = true; + int /*long*/ code = callWindowProc (handle, OS.WM_LBUTTONDOWN, wParam, lParam); + if (!OS.IsWinCE && OS.WIN32_VERSION >= OS.VERSION (6, 0)) { + if (OS.GetFocus () !is handle) OS.SetFocus (handle); + } + auto hNewItem = cast(HANDLE) OS.SendMessage (handle, OS.TVM_GETNEXTITEM, OS.TVGN_CARET, 0); + if (fakeSelection) { + if (hOldItem is null || (hNewItem is hOldItem && lpht.hItem !is hOldItem)) { + OS.SendMessage (handle, OS.TVM_SELECTITEM, OS.TVGN_CARET, lpht.hItem); + hNewItem = cast(HANDLE) OS.SendMessage (handle, OS.TVM_GETNEXTITEM, OS.TVGN_CARET, 0); + } + if (!dragStarted && (state & DRAG_DETECT) !is 0 && hooks (SWT.DragDetect)) { + dragStarted = dragDetect (handle, lpht.pt.x, lpht.pt.y, false, null, null); + } + } + ignoreDeselect = ignoreSelect = false; + hSelect = null; + if (dragStarted) { + if (!display.captureChanged && !isDisposed ()) { + if (OS.GetCapture () !is handle) OS.SetCapture (handle); + } + } + + /* + * Feature in Windows. When the old and new focused item + * are the same, Windows does not check to make sure that + * the item is actually selected, not just focused. The + * fix is to force the item to draw selected by setting + * the state mask. This is only necessary when the tree + * is single select. + */ + if ((style & SWT.SINGLE) !is 0) { + if (hOldItem is hNewItem) { + tvItem.mask = OS.TVIF_HANDLE | OS.TVIF_STATE; + tvItem.state = OS.TVIS_SELECTED; + tvItem.stateMask = OS.TVIS_SELECTED; + tvItem.hItem = cast(HTREEITEM)hNewItem; + OS.SendMessage (handle, OS.TVM_SETITEM, 0, &tvItem); + } + } + + /* Reselect the last item that was unselected */ + if ((style & SWT.MULTI) !is 0) { + + /* Check for CONTROL and reselect the last item */ + if (hittestSelected || (wParam & OS.MK_CONTROL) !is 0) { + if (hOldItem is hNewItem && hOldItem is lpht.hItem) { + if ((wParam & OS.MK_CONTROL) !is 0) { + tvItem.state ^= OS.TVIS_SELECTED; + if (dragStarted) tvItem.state = OS.TVIS_SELECTED; + OS.SendMessage (handle, OS.TVM_SETITEM, 0, &tvItem); + } + } else { + if ((tvItem.state & OS.TVIS_SELECTED) !is 0) { + tvItem.state = OS.TVIS_SELECTED; + OS.SendMessage (handle, OS.TVM_SETITEM, 0, &tvItem); + } + if ((wParam & OS.MK_CONTROL) !is 0 && !dragStarted) { + if (hittestSelected) { + tvItem.state = 0; + tvItem.hItem = lpht.hItem; + OS.SendMessage (handle, OS.TVM_SETITEM, 0, &tvItem); + } + } + } + RECT rect1, rect2; + bool fItemRect = (style & SWT.FULL_SELECTION) is 0; + if (hooks (SWT.EraseItem) || hooks (SWT.PaintItem)) fItemRect = false; + if (!OS.IsWinCE && OS.WIN32_VERSION >= OS.VERSION (6, 0)) fItemRect = false; + OS.TreeView_GetItemRect (handle, cast(HTREEITEM)hOldItem, &rect1, fItemRect); + OS.TreeView_GetItemRect (handle, cast(HTREEITEM)hNewItem, &rect2, fItemRect); + OS.DefWindowProc (handle, OS.WM_SETREDRAW, 1, 0); + OS.InvalidateRect (handle, &rect1, true); + OS.InvalidateRect (handle, &rect2, true); + OS.UpdateWindow (handle); + } + + /* Check for SHIFT or normal select and deselect/reselect items */ + if ((wParam & OS.MK_CONTROL) is 0) { + if (!hittestSelected || !dragStarted) { + tvItem.state = 0; + auto oldProc = OS.GetWindowLongPtr (handle, OS.GWLP_WNDPROC); + OS.SetWindowLongPtr (handle, OS.GWLP_WNDPROC, cast(LONG_PTR)TreeProc); + if ((style & SWT.VIRTUAL) !is 0) { + HANDLE hItem = cast(HANDLE) OS.SendMessage (handle, OS.TVM_GETNEXTITEM, OS.TVGN_ROOT, 0); + deselect (hItem, &tvItem, hNewItem); + } else { + for (int i=0; i<items.length; i++) { + TreeItem item = items [i]; + if (item !is null && item.handle !is hNewItem) { + tvItem.hItem = cast(HTREEITEM)item.handle; + OS.SendMessage (handle, OS.TVM_SETITEM, 0, &tvItem); + } + } + } + tvItem.hItem = cast(HTREEITEM)hNewItem; + tvItem.state = OS.TVIS_SELECTED; + OS.SendMessage (handle, OS.TVM_SETITEM, 0, &tvItem); + OS.SetWindowLongPtr (handle, OS.GWLP_WNDPROC, oldProc); + if ((wParam & OS.MK_SHIFT) !is 0) { + RECT rect1; + if (hAnchor is null) hAnchor = hNewItem; + if (OS.TreeView_GetItemRect (handle, cast(HTREEITEM)hAnchor, &rect1, false)) { + RECT rect2; + if (OS.TreeView_GetItemRect (handle, cast(HTREEITEM)hNewItem, &rect2, false)) { + int flags = rect1.top < rect2.top ? OS.TVGN_NEXTVISIBLE : OS.TVGN_PREVIOUSVISIBLE; + tvItem.state = OS.TVIS_SELECTED; + auto hItem = tvItem.hItem = cast(HTREEITEM)hAnchor; + OS.SendMessage (handle, OS.TVM_SETITEM, 0, &tvItem); + while (hItem !is hNewItem) { + tvItem.hItem = hItem; + OS.SendMessage (handle, OS.TVM_SETITEM, 0, &tvItem); + hItem = cast(HTREEITEM) OS.SendMessage (handle, OS.TVM_GETNEXTITEM, flags, hItem); + } + } + } + } + } + } + } + if ((wParam & OS.MK_SHIFT) is 0) hAnchor = hNewItem; + + /* Issue notification */ + if (!gestureCompleted) { + tvItem.hItem = cast(HTREEITEM)hNewItem; + tvItem.mask = OS.TVIF_HANDLE | OS.TVIF_PARAM; + OS.SendMessage (handle, OS.TVM_GETITEM, 0, &tvItem); + Event event = new Event (); + event.item = _getItem (tvItem.hItem, tvItem.lParam); + postEvent (SWT.Selection, event); + } + gestureCompleted = false; + + /* + * Feature in Windows. Inside WM_LBUTTONDOWN and WM_RBUTTONDOWN, + * the widget starts a modal loop to determine if the user wants + * to begin a drag/drop operation or marquee select. Unfortunately, + * this modal loop eats the corresponding mouse up. The fix is to + * detect the cases when the modal loop has eaten the mouse up and + * issue a fake mouse up. + */ + if (dragStarted) { + sendDragEvent (1, OS.GET_X_LPARAM (lParam), OS.GET_Y_LPARAM (lParam)); + } else { + int bits = OS.GetWindowLong (handle, OS.GWL_STYLE); + if ((bits & OS.TVS_DISABLEDRAGDROP) is 0) { + sendMouseEvent (SWT.MouseUp, 1, handle, OS.WM_LBUTTONUP, wParam, lParam); + } + } + dragStarted = false; + return new LRESULT (code); +} + +override LRESULT WM_MOUSEMOVE (int wParam, int lParam) { + Display display = this.display; + LRESULT result = super.WM_MOUSEMOVE (wParam, lParam); + if (result !is null) return result; + if (itemToolTipHandle !is null) { + /* + * Bug in Windows. On some machines that do not have XBUTTONs, + * the MK_XBUTTON1 and OS.MK_XBUTTON2 bits are sometimes set, + * causing mouse capture to become stuck. The fix is to test + * for the extra buttons only when they exist. + */ + int mask = OS.MK_LBUTTON | OS.MK_MBUTTON | OS.MK_RBUTTON; + if (display.xMouse) mask |= OS.MK_XBUTTON1 | OS.MK_XBUTTON2; + if ((wParam & mask) is 0) { + int x = OS.GET_X_LPARAM (lParam); + int y = OS.GET_Y_LPARAM (lParam); + int index; + TreeItem item; + RECT* cellRect, itemRect; + if (findCell (x, y, item, index, cellRect, itemRect)) { + /* + * Feature in Windows. When the new tool rectangle is + * set using TTM_NEWTOOLRECT and the tooltip is visible, + * Windows draws the tooltip right away and the sends + * WM_NOTIFY with TTN_SHOW. This means that the tooltip + * shows first at the wrong location and then moves to + * the right one. The fix is to hide the tooltip window. + */ + if (OS.SendMessage (itemToolTipHandle, OS.TTM_GETCURRENTTOOL, 0, 0) is 0) { + if (OS.IsWindowVisible (itemToolTipHandle)) { + OS.ShowWindow (itemToolTipHandle, OS.SW_HIDE); + } + } + TOOLINFO lpti; + lpti.cbSize = OS.TOOLINFO_sizeof; + lpti.hwnd = handle; + lpti.uId = cast(int) handle; + lpti.uFlags = OS.TTF_SUBCLASS | OS.TTF_TRANSPARENT; + lpti.rect.left = cellRect.left; + lpti.rect.top = cellRect.top; + lpti.rect.right = cellRect.right; + lpti.rect.bottom = cellRect.bottom; + OS.SendMessage (itemToolTipHandle, OS.TTM_NEWTOOLRECT, 0, &lpti); + } + } + } + return result; +} + +override LRESULT WM_MOVE (int wParam, int lParam) { + if (ignoreResize) return null; + return super.WM_MOVE (wParam, lParam); +} + +override LRESULT WM_RBUTTONDOWN (int wParam, int lParam) { + /* + * Feature in Windows. The receiver uses WM_RBUTTONDOWN + * to initiate a drag/drop operation depending on how the + * user moves the mouse. If the user clicks the right button, + * without moving the mouse, the tree consumes the corresponding + * WM_RBUTTONUP. The fix is to avoid calling the window proc for + * the tree. + */ + Display display = this.display; + display.captureChanged = false; + if (!sendMouseEvent (SWT.MouseDown, 3, handle, OS.WM_RBUTTONDOWN, wParam, lParam)) { + if (!display.captureChanged && !isDisposed ()) { + if (OS.GetCapture () !is handle) OS.SetCapture (handle); + } + return LRESULT.ZERO; + } + /* + * This code is intentionally commented. + */ +// if (OS.GetCapture () !is handle) OS.SetCapture (handle); + if (OS.GetFocus () !is handle) OS.SetFocus (handle); + + /* + * Feature in Windows. When the user selects a tree item + * with the right mouse button, the item remains selected + * only as long as the user does not release or move the + * mouse. As soon as this happens, the selection snaps + * back to the previous selection. This behavior can be + * observed in the Explorer but is not instantly apparent + * because the Explorer explicitly sets the selection when + * the user chooses a menu item. If the user cancels the + * menu, the selection snaps back. The fix is to avoid + * calling the window proc and do the selection ourselves. + * This behavior is consistent with the table. + */ + TVHITTESTINFO lpht; + lpht.pt.x = OS.GET_X_LPARAM (lParam); + lpht.pt.y = OS.GET_Y_LPARAM (lParam); + OS.SendMessage (handle, OS.TVM_HITTEST, 0, &lpht); + if (lpht.hItem !is null) { + bool fakeSelection = (style & SWT.FULL_SELECTION) !is 0; + if (!fakeSelection) { + if (hooks (SWT.MeasureItem)) { + fakeSelection = hitTestSelection (lpht.hItem, lpht.pt.x, lpht.pt.y); + } else { + int flags = OS.TVHT_ONITEMICON | OS.TVHT_ONITEMLABEL; + fakeSelection = (lpht.flags & flags) !is 0; + } + } + if (fakeSelection) { + if ((wParam & (OS.MK_CONTROL | OS.MK_SHIFT)) is 0) { + TVITEM tvItem; + tvItem.mask = OS.TVIF_HANDLE | OS.TVIF_STATE; + tvItem.stateMask = OS.TVIS_SELECTED; + tvItem.hItem = lpht.hItem; + OS.SendMessage (handle, OS.TVM_GETITEM, 0, &tvItem); + if ((tvItem.state & OS.TVIS_SELECTED) is 0) { + ignoreSelect = true; + OS.SendMessage (handle, OS.TVM_SELECTITEM, OS.TVGN_CARET, 0); + ignoreSelect = false; + OS.SendMessage (handle, OS.TVM_SELECTITEM, OS.TVGN_CARET, lpht.hItem); + } + } + } + } + return LRESULT.ZERO; +} + +override LRESULT WM_PAINT (int wParam, int lParam) { + if (shrink && !ignoreShrink) { + /* Resize the item array to fit the last item */ + int count = items.length - 1; + while (count >= 0) { + if (items [count] !is null) break; + --count; + } + count++; + if (items.length > 4 && items.length - count > 3) { + int length = Math.max (4, (count + 3) / 4 * 4); + TreeItem [] newItems = new TreeItem [length]; + System.arraycopy (items, 0, newItems, 0, count); + items = newItems; + } + shrink = false; + } + if ((style & SWT.DOUBLE_BUFFERED) !is 0 || findImageControl () !is null) { + bool doubleBuffer = true; + if (explorerTheme) { + int exStyle = OS.SendMessage (handle, OS.TVM_GETEXTENDEDSTYLE, 0, 0); + if ((exStyle & OS.TVS_EX_DOUBLEBUFFER) !is 0) doubleBuffer = false; + } + if (doubleBuffer) { + GC gc = null; + HDC paintDC; + PAINTSTRUCT ps; + bool hooksPaint = hooks (SWT.Paint); + if (hooksPaint) { + GCData data = new GCData (); + data.ps = &ps; + data.hwnd = handle; + gc = GC.win32_new (this, data); + paintDC = gc.handle; + } else { + paintDC = OS.BeginPaint (handle, &ps); + } + int width = ps.rcPaint.right - ps.rcPaint.left; + int height = ps.rcPaint.bottom - ps.rcPaint.top; + if (width !is 0 && height !is 0) { + auto hDC = OS.CreateCompatibleDC (paintDC); + POINT lpPoint1, lpPoint2; + OS.SetWindowOrgEx (hDC, ps.rcPaint.left, ps.rcPaint.top, &lpPoint1); + OS.SetBrushOrgEx (hDC, ps.rcPaint.left, ps.rcPaint.top, &lpPoint2); + auto hBitmap = OS.CreateCompatibleBitmap (paintDC, width, height); + auto hOldBitmap = OS.SelectObject (hDC, hBitmap); + RECT rect; + OS.SetRect (&rect, ps.rcPaint.left, ps.rcPaint.top, ps.rcPaint.right, ps.rcPaint.bottom); + drawBackground (hDC, &rect); + callWindowProc (handle, OS.WM_PAINT, cast(int) hDC, 0); + OS.SetWindowOrgEx (hDC, lpPoint1.x, lpPoint1.y, null); + OS.SetBrushOrgEx (hDC, lpPoint2.x, lpPoint2.y, null); + OS.BitBlt (paintDC, ps.rcPaint.left, ps.rcPaint.top, width, height, hDC, 0, 0, OS.SRCCOPY); + OS.SelectObject (hDC, hOldBitmap); + OS.DeleteObject (hBitmap); + OS.DeleteObject (hDC); + if (hooksPaint) { + Event event = new Event (); + event.gc = gc; + event.x = ps.rcPaint.left; + event.y = ps.rcPaint.top; + event.width = ps.rcPaint.right - ps.rcPaint.left; + event.height = ps.rcPaint.bottom - ps.rcPaint.top; + sendEvent (SWT.Paint, event); + // widget could be disposed at this point + event.gc = null; + } + } + if (hooksPaint) { + gc.dispose (); + } else { + OS.EndPaint (handle, &ps); + } + return LRESULT.ZERO; + } + } + return super.WM_PAINT (wParam, lParam); +} + +override LRESULT WM_PRINTCLIENT (int wParam, int lParam) { + LRESULT result = super.WM_PRINTCLIENT (wParam, lParam); + if (result !is null) return result; + /* + * Feature in Windows. For some reason, when WM_PRINT is used + * to capture an image of a hierarchy that contains a tree with + * columns, the clipping that is used to stop the first column + * from drawing on top of subsequent columns stops the first + * column and the tree lines from drawing. This does not happen + * during WM_PAINT. The fix is to draw without clipping and + * then draw the rest of the columns on top. Since the drawing + * is happening in WM_PRINTCLIENT, the redrawing is not visible. + */ + printClient = true; + int /*long*/ code = callWindowProc (handle, OS.WM_PRINTCLIENT, wParam, lParam); + printClient = false; + return new LRESULT (code); +} + +override LRESULT WM_SETFOCUS (int wParam, int lParam) { + /* + * Bug in Windows. When a tree item that has an image + * with alpha is expanded or collapsed, the area where + * the image is drawn is not erased before it is drawn. + * This means that the image gets darker each time. + * The fix is to redraw the selection. + * + * Feature in Windows. When multiple item have + * the TVIS_SELECTED state, Windows redraws only + * the focused item in the color used to show the + * selection when the tree loses or gains focus. + * The fix is to force Windows to redraw the + * selection when focus is gained or lost. + */ + bool redraw = (style & SWT.MULTI) !is 0; + if (!redraw) { + if (!OS.IsWinCE && OS.COMCTL32_MAJOR >= 6) { + if (imageList !is null) { + int bits = OS.GetWindowLong (handle, OS.GWL_STYLE); + if ((bits & OS.TVS_FULLROWSELECT) is 0) { + redraw = true; + } + } + } + } + if (redraw) redrawSelection (); + return super.WM_SETFOCUS (wParam, lParam); +} + +override LRESULT WM_SETFONT (int wParam, int lParam) { + LRESULT result = super.WM_SETFONT (wParam, lParam); + if (result !is null) return result; + if (hwndHeader !is null) { + /* + * Bug in Windows. When a header has a sort indicator + * triangle, Windows resizes the indicator based on the + * size of the n-1th font. The fix is to always make + * the n-1th font be the default. This makes the sort + * indicator always be the default size. + */ + OS.SendMessage (hwndHeader, OS.WM_SETFONT, 0, lParam); + OS.SendMessage (hwndHeader, OS.WM_SETFONT, wParam, lParam); + } + if (itemToolTipHandle !is null) { + OS.SendMessage (itemToolTipHandle, OS.WM_SETFONT, wParam, lParam); + } + if (headerToolTipHandle !is null) { + OS.SendMessage (headerToolTipHandle, OS.WM_SETFONT, wParam, lParam); + } + return result; +} + +override LRESULT WM_SETREDRAW (int wParam, int lParam) { + LRESULT result = super.WM_SETREDRAW (wParam, lParam); + if (result !is null) return result; + /* + * Bug in Windows. Under certain circumstances, when + * WM_SETREDRAW is used to turn off drawing and then + * TVM_GETITEMRECT is sent to get the bounds of an item + * that is not inside the client area, Windows segment + * faults. The fix is to call the default window proc + * rather than the default tree proc. + * + * NOTE: This problem is intermittent and happens on + * Windows Vista running under the theme manager. + */ + if (!OS.IsWinCE && OS.WIN32_VERSION >= OS.VERSION (6, 0)) { + int /*long*/ code = OS.DefWindowProc (handle, OS.WM_SETREDRAW, wParam, lParam); + return code is 0 ? LRESULT.ZERO : new LRESULT (code); + } + return result; +} + +override LRESULT WM_SIZE (int wParam, int lParam) { + /* + * Bug in Windows. When TVS_NOHSCROLL is set when the + * size of the tree is zero, the scroll bar is shown the + * next time the tree resizes. The fix is to hide the + * scroll bar every time the tree is resized. + */ + int bits = OS.GetWindowLong (handle, OS.GWL_STYLE); + if ((bits & OS.TVS_NOHSCROLL) !is 0) { + static if (!OS.IsWinCE) OS.ShowScrollBar (handle, OS.SB_HORZ, false); + } + /* + * Bug in Windows. On Vista, when the Explorer theme + * is used with a full selection tree, when the tree + * is resized to be smaller, the rounded right edge + * of the selected items is not drawn. The fix is the + * redraw the entire tree. + */ + if (explorerTheme && (style & SWT.FULL_SELECTION) !is 0) { + OS.InvalidateRect (handle, null, false); + } + if (ignoreResize) return null; + return super.WM_SIZE (wParam, lParam); +} + +override LRESULT WM_SYSCOLORCHANGE (int wParam, int lParam) { + LRESULT result = super.WM_SYSCOLORCHANGE (wParam, lParam); + if (result !is null) return result; + /* + * Bug in Windows. When the tree is using the explorer + * theme, it does not use COLOR_WINDOW_TEXT for the + * default foreground color. The fix is to explicitly + * set the foreground. + */ + if (explorerTheme) { + if (foreground is -1) setForegroundPixel (-1); + } + if ((style & SWT.CHECK) !is 0) setCheckboxImageList (); + return result; +} + +override LRESULT WM_VSCROLL (int /*long*/ wParam, int /*long*/ lParam) { + bool fixScroll = false; + if ((style & SWT.DOUBLE_BUFFERED) !is 0) { + int code = OS.LOWORD (wParam); + switch (code) { + case OS.SB_TOP: + case OS.SB_BOTTOM: + case OS.SB_LINEDOWN: + case OS.SB_LINEUP: + case OS.SB_PAGEDOWN: + case OS.SB_PAGEUP: + fixScroll = (style & SWT.VIRTUAL) !is 0 || hooks (SWT.EraseItem) || hooks (SWT.PaintItem); + break; + default: + } + } + if (fixScroll) { + style &= ~SWT.DOUBLE_BUFFERED; + if (explorerTheme) { + OS.SendMessage (handle, OS.TVM_SETEXTENDEDSTYLE, OS.TVS_EX_DOUBLEBUFFER, 0); + } + } + LRESULT result = super.WM_VSCROLL (wParam, lParam); + if (fixScroll) { + style |= SWT.DOUBLE_BUFFERED; + if (explorerTheme) { + OS.SendMessage (handle, OS.TVM_SETEXTENDEDSTYLE, OS.TVS_EX_DOUBLEBUFFER, OS.TVS_EX_DOUBLEBUFFER); + } + } + if (result !is null) return result; + return result; +} + +override LRESULT wmColorChild (int wParam, int lParam) { + if (findImageControl () !is null) { + if (OS.COMCTL32_MAJOR < 6) { + return super.wmColorChild (wParam, lParam); + } + return new LRESULT ( cast(int) OS.GetStockObject (OS.NULL_BRUSH)); + } + /* + * Feature in Windows. Tree controls send WM_CTLCOLOREDIT + * to allow application code to change the default colors. + * This is undocumented and conflicts with TVM_SETTEXTCOLOR + * and TVM_SETBKCOLOR, the documented way to do this. The + * fix is to ignore WM_CTLCOLOREDIT messages from trees. + */ + return null; +} + +override LRESULT wmNotify (NMHDR* hdr, int wParam, int lParam) { + if (hdr.hwndFrom is itemToolTipHandle) { + LRESULT result = wmNotifyToolTip (hdr, wParam, lParam); + if (result !is null) return result; + } + if (hdr.hwndFrom is hwndHeader) { + LRESULT result = wmNotifyHeader (hdr, wParam, lParam); + if (result !is null) return result; + } + return super.wmNotify (hdr, wParam, lParam); +} + +override LRESULT wmNotifyChild (NMHDR* hdr, int wParam, int lParam) { + switch (hdr.code) { + case OS.TVN_GETDISPINFOA: + case OS.TVN_GETDISPINFOW: { + NMTVDISPINFO* lptvdi = cast(NMTVDISPINFO*)lParam; + //OS.MoveMemory (lptvdi, lParam, NMTVDISPINFO.sizeof); + if ((style & SWT.VIRTUAL) !is 0) { + /* + * Feature in Windows. When a new tree item is inserted + * using TVM_INSERTITEM, a TVN_GETDISPINFO is sent before + * TVM_INSERTITEM returns and before the item is added to + * the items array. The fix is to check for null. + * + * NOTE: This only happens on XP with the version 6.00 of + * COMCTL32.DLL. + */ + bool checkVisible = true; + /* + * When an item is being deleted from a virtual tree, do not + * allow the application to provide data for a new item that + * becomes visible until the item has been removed from the + * items array. Because arbitrary application code can run + * during the callback, the items array might be accessed + * in an inconsistent state. Rather than answering the data + * right away, queue a redraw for later. + */ + if (!ignoreShrink) { + if (items !is null && lptvdi.item.lParam !is -1) { + if (items [lptvdi.item.lParam] !is null && items [lptvdi.item.lParam].cached) { + checkVisible = false; + } + } + } + if (checkVisible) { + if (drawCount !is 0 || !OS.IsWindowVisible (handle)) break; + RECT itemRect; + if (!OS.TreeView_GetItemRect (handle, lptvdi.item.hItem, &itemRect, false)) { + break; + } + RECT rect; + OS.GetClientRect (handle, &rect); + if (!OS.IntersectRect (&rect, &rect, &itemRect)) break; + if (ignoreShrink) { + OS.InvalidateRect (handle, &rect, true); + break; + } + } + } + if (items is null) break; + /* + * Bug in Windows. If the lParam field of TVITEM + * is changed during custom draw using TVM_SETITEM, + * the lItemlParam field of the NMTVCUSTOMDRAW struct + * is not updated until the next custom draw. The + * fix is to query the field from the item instead + * of using the struct. + */ + int id = lptvdi.item.lParam; + if ((style & SWT.VIRTUAL) !is 0) { + if (id is -1) { + TVITEM tvItem; + tvItem.mask = OS.TVIF_HANDLE | OS.TVIF_PARAM; + tvItem.hItem = lptvdi.item.hItem; + OS.SendMessage (handle, OS.TVM_GETITEM, 0, &tvItem); + id = tvItem.lParam; + } + } + TreeItem item = _getItem (lptvdi.item.hItem, id); + /* + * Feature in Windows. When a new tree item is inserted + * using TVM_INSERTITEM, a TVN_GETDISPINFO is sent before + * TVM_INSERTITEM returns and before the item is added to + * the items array. The fix is to check for null. + * + * NOTE: This only happens on XP with the version 6.00 of + * COMCTL32.DLL. + * + * Feature in Windows. When TVM_DELETEITEM is called with + * TVI_ROOT to remove all items from a tree, under certain + * circumstances, the tree sends TVN_GETDISPINFO for items + * that are about to be disposed. The fix is to check for + * disposed items. + */ + if (item is null) break; + if (item.isDisposed ()) break; + if (!item.cached) { + if ((style & SWT.VIRTUAL) !is 0) { + if (!checkData (item, false)) break; + } + if (painted) item.cached = true; + } + int index = 0; + if (hwndHeader !is null) { + index = OS.SendMessage (hwndHeader, OS.HDM_ORDERTOINDEX, 0, 0); + } + if ((lptvdi.item.mask & OS.TVIF_TEXT) !is 0) { + String string = null; + if (index is 0) { + string = item.text; + } else { + String [] strings = item.strings; + if (strings !is null) string = strings [index]; + } + if (string !is null) { + TCHAR[] buffer = StrToTCHARs (getCodePage (), string, false); + int byteCount = Math.min (buffer.length, lptvdi.item.cchTextMax - 1) * TCHAR.sizeof; + OS.MoveMemory (lptvdi.item.pszText, buffer.ptr, byteCount); + int st = byteCount/TCHAR.sizeof; + lptvdi.item.pszText[ st .. st+1 ] = 0; + //OS.MoveMemory (lptvdi.pszText + byteCount, new byte [TCHAR.sizeof], TCHAR.sizeof); + lptvdi.item.cchTextMax = Math.min (lptvdi.item.cchTextMax, string.length + 1); + } + } + if ((lptvdi.item.mask & (OS.TVIF_IMAGE | OS.TVIF_SELECTEDIMAGE)) !is 0) { + Image image = null; + if (index is 0) { + image = item.image; + } else { + Image [] images = item.images; + if (images !is null) image = images [index]; + } + lptvdi.item.iImage = lptvdi.item.iSelectedImage = OS.I_IMAGENONE; + if (image !is null) { + lptvdi.item.iImage = lptvdi.item.iSelectedImage = imageIndex (image, index); + } + if (explorerTheme && OS.IsWindowEnabled (handle)) { + if (findImageControl () !is null) { + lptvdi.item.iImage = lptvdi.item.iSelectedImage = OS.I_IMAGENONE; + } + } + } + //OS.MoveMemory (cast(void*)lParam, lptvdi, NMTVDISPINFO.sizeof); + break; + } + case OS.NM_CUSTOMDRAW: { + if (hdr.hwndFrom is hwndHeader) break; + if (hooks (SWT.MeasureItem)) { + if (hwndHeader is null) createParent (); + } + if (!customDraw && findImageControl () is null) { + if (OS.COMCTL32_MAJOR >= 6 && OS.IsAppThemed ()) { + if (sortColumn is null || sortDirection is SWT.NONE) { + break; + } + } + } + NMTVCUSTOMDRAW* nmcd = cast(NMTVCUSTOMDRAW*)lParam; + //OS.MoveMemory (nmcd, lParam, NMTVCUSTOMDRAW.sizeof); + switch (nmcd.nmcd.dwDrawStage) { + case OS.CDDS_PREPAINT: return CDDS_PREPAINT (nmcd, wParam, lParam); + case OS.CDDS_ITEMPREPAINT: return CDDS_ITEMPREPAINT (nmcd, wParam, lParam); + case OS.CDDS_ITEMPOSTPAINT: return CDDS_ITEMPOSTPAINT (nmcd, wParam, lParam); + case OS.CDDS_POSTPAINT: return CDDS_POSTPAINT (nmcd, wParam, lParam); + default: + } + break; + } + case OS.NM_DBLCLK: { + /* + * When the user double clicks on a tree item + * or a line beside the item, the window proc + * for the tree collapses or expand the branch. + * When application code associates an action + * with double clicking, then the tree expand + * is unexpected and unwanted. The fix is to + * avoid the operation by testing to see whether + * the mouse was inside a tree item. + */ + if (hooks (SWT.MeasureItem)) return LRESULT.ONE; + if (hooks (SWT.DefaultSelection)) { + POINT pt; + int pos = OS.GetMessagePos (); + OS.POINTSTOPOINT (pt, pos); + OS.ScreenToClient (handle, &pt); + TVHITTESTINFO lpht; + lpht.pt.x = pt.x; + lpht.pt.y = pt.y; + OS.SendMessage (handle, OS.TVM_HITTEST, 0, &lpht); + if (lpht.hItem !is null && (lpht.flags & OS.TVHT_ONITEM) !is 0) { + return LRESULT.ONE; + } + } + break; + } + /* + * Bug in Windows. On Vista, when TVM_SELECTITEM is called + * with TVGN_CARET in order to set the selection, for some + * reason, Windows deselects the previous two items that + * were selected. The fix is to stop the selection from + * changing on all but the item that is supposed to be + * selected. + */ + case OS.TVN_ITEMCHANGINGA: + case OS.TVN_ITEMCHANGINGW: { + if (!OS.IsWinCE && OS.WIN32_VERSION >= OS.VERSION (6, 0)) { + if ((style & SWT.MULTI) !is 0) { + if (hSelect !is null) { + NMTVITEMCHANGE* pnm = cast(NMTVITEMCHANGE*)lParam; + //OS.MoveMemory (pnm, lParam, NMTVITEMCHANGE.sizeof); + if (hSelect is pnm.hItem) break; + return LRESULT.ONE; + } + } + } + break; + } + case OS.TVN_SELCHANGINGA: + case OS.TVN_SELCHANGINGW: { + if ((style & SWT.MULTI) !is 0) { + if (lockSelection) { + /* Save the old selection state for both items */ + auto treeView = cast(NMTREEVIEW*)lParam; + TVITEM* tvItem = &treeView.itemOld; + oldSelected = (tvItem.state & OS.TVIS_SELECTED) !is 0; + tvItem = &treeView.itemNew; + newSelected = (tvItem.state & OS.TVIS_SELECTED) !is 0; + } + } + if (!ignoreSelect && !ignoreDeselect) { + hAnchor = null; + if ((style & SWT.MULTI) !is 0) deselectAll (); + } + break; + } + case OS.TVN_SELCHANGEDA: + case OS.TVN_SELCHANGEDW: { + NMTREEVIEW* treeView = null; + if ((style & SWT.MULTI) !is 0) { + if (lockSelection) { + /* Restore the old selection state of both items */ + if (oldSelected) { + if (treeView is null) { + treeView = cast(NMTREEVIEW*)lParam; + } + TVITEM tvItem = treeView.itemOld; + tvItem.mask = OS.TVIF_STATE; + tvItem.stateMask = OS.TVIS_SELECTED; + tvItem.state = OS.TVIS_SELECTED; + OS.SendMessage (handle, OS.TVM_SETITEM, 0, &tvItem); + } + if (!newSelected && ignoreSelect) { + if (treeView is null) { + treeView = cast(NMTREEVIEW*)lParam; + } + TVITEM tvItem = treeView.itemNew; + tvItem.mask = OS.TVIF_STATE; + tvItem.stateMask = OS.TVIS_SELECTED; + tvItem.state = 0; + OS.SendMessage (handle, OS.TVM_SETITEM, 0, &tvItem); + } + } + } + if (!ignoreSelect) { + if (treeView is null) { + treeView = cast(NMTREEVIEW*)lParam; + } + TVITEM tvItem = treeView.itemNew; + hAnchor = tvItem.hItem; + Event event = new Event (); + event.item = _getItem (&tvItem.hItem, tvItem.lParam); + postEvent (SWT.Selection, event); + } + updateScrollBar (); + break; + } + case OS.TVN_ITEMEXPANDINGA: + case OS.TVN_ITEMEXPANDINGW: { + bool runExpanded = false; + if ((style & SWT.VIRTUAL) !is 0) style &= ~SWT.DOUBLE_BUFFERED; + if (hooks (SWT.EraseItem) || hooks (SWT.PaintItem)) style &= ~SWT.DOUBLE_BUFFERED; + if (findImageControl () !is null && drawCount is 0 && OS.IsWindowVisible (handle)) { + OS.DefWindowProc (handle, OS.WM_SETREDRAW, 0, 0); + } + /* + * Bug in Windows. When TVM_SETINSERTMARK is used to set + * an insert mark for a tree and an item is expanded or + * collapsed near the insert mark, the tree does not redraw + * the insert mark properly. The fix is to hide and show + * the insert mark whenever an item is expanded or collapsed. + */ + if (hInsert !is null) { + OS.SendMessage (handle, OS.TVM_SETINSERTMARK, 0, 0); + } + if (!ignoreExpand) { + NMTREEVIEW* treeView = cast(NMTREEVIEW*)lParam; + + TVITEM* tvItem = &treeView.itemNew; + /* + * Feature in Windows. In some cases, TVM_ITEMEXPANDING + * is sent from within TVM_DELETEITEM for the tree item + * being destroyed. By the time the message is sent, + * the item has already been removed from the list of + * items. The fix is to check for null. + */ + if (items is null) break; + TreeItem item = _getItem (tvItem.hItem, tvItem.lParam); + if (item is null) break; + Event event = new Event (); + event.item = item; + switch (treeView.action) { + case OS.TVE_EXPAND: + /* + * Bug in Windows. When the numeric keypad asterisk + * key is used to expand every item in the tree, Windows + * sends TVN_ITEMEXPANDING to items in the tree that + * have already been expanded. The fix is to detect + * that the item is already expanded and ignore the + * notification. + */ + if ((tvItem.state & OS.TVIS_EXPANDED) is 0) { + sendEvent (SWT.Expand, event); + if (isDisposed ()) return LRESULT.ZERO; + } + break; + case OS.TVE_COLLAPSE: + sendEvent (SWT.Collapse, event); + if (isDisposed ()) return LRESULT.ZERO; + break; + default: + } + /* + * Bug in Windows. When all of the items are deleted during + * TVN_ITEMEXPANDING, Windows does not send TVN_ITEMEXPANDED. + * The fix is to detect this case and run the TVN_ITEMEXPANDED + * code in this method. + */ + auto hFirstItem = cast(HANDLE) OS.SendMessage (handle, OS.TVM_GETNEXTITEM, OS.TVGN_CHILD, tvItem.hItem); + runExpanded = hFirstItem is null; + } + if (!runExpanded) break; + //FALL THROUGH + } + case OS.TVN_ITEMEXPANDEDA: + case OS.TVN_ITEMEXPANDEDW: { + if ((style & SWT.VIRTUAL) !is 0) style |= SWT.DOUBLE_BUFFERED; + if (hooks (SWT.EraseItem) || hooks (SWT.PaintItem)) style |= SWT.DOUBLE_BUFFERED; + if (findImageControl () !is null && drawCount is 0 /*&& OS.IsWindowVisible (handle)*/) { + OS.DefWindowProc (handle, OS.WM_SETREDRAW, 1, 0); + OS.InvalidateRect (handle, null, true); + } + /* + * Bug in Windows. When TVM_SETINSERTMARK is used to set + * an insert mark for a tree and an item is expanded or + * collapsed near the insert mark, the tree does not redraw + * the insert mark properly. The fix is to hide and show + * the insert mark whenever an item is expanded or collapsed. + */ + if (hInsert !is null) { + OS.SendMessage (handle, OS.TVM_SETINSERTMARK, insertAfter ? 1 : 0, hInsert); + } + /* + * Bug in Windows. When a tree item that has an image + * with alpha is expanded or collapsed, the area where + * the image is drawn is not erased before it is drawn. + * This means that the image gets darker each time. + * The fix is to redraw the item. + */ + if (!OS.IsWinCE && OS.COMCTL32_MAJOR >= 6) { + if (imageList !is null) { + NMTREEVIEW* treeView = cast(NMTREEVIEW*)lParam; + + TVITEM* tvItem = &treeView.itemNew; + if (tvItem.hItem !is null) { + int bits = OS.GetWindowLong (handle, OS.GWL_STYLE); + if ((bits & OS.TVS_FULLROWSELECT) is 0) { + RECT rect; + if (OS.TreeView_GetItemRect (handle, tvItem.hItem, &rect, false)) { + OS.InvalidateRect (handle, &rect, true); + } + } + } + } + } + updateScrollBar (); + break; + } + case OS.TVN_BEGINDRAGA: + case OS.TVN_BEGINDRAGW: + if (OS.GetKeyState (OS.VK_LBUTTON) >= 0) break; + //FALL THROUGH + case OS.TVN_BEGINRDRAGA: + case OS.TVN_BEGINRDRAGW: { + dragStarted = true; + NMTREEVIEW* treeView = cast(NMTREEVIEW*)lParam; + TVITEM* tvItem = &treeView.itemNew; + if (tvItem.hItem !is null && (tvItem.state & OS.TVIS_SELECTED) is 0) { + hSelect = tvItem.hItem; + ignoreSelect = ignoreDeselect = true; + OS.SendMessage (handle, OS.TVM_SELECTITEM, OS.TVGN_CARET, tvItem.hItem); + ignoreSelect = ignoreDeselect = false; + hSelect = null; + } + break; + } + case OS.NM_RECOGNIZEGESTURE: { + /* + * Feature in Pocket PC. The tree and table controls detect the tap + * and hold gesture by default. They send a GN_CONTEXTMENU message to show + * the popup menu. This default behaviour is unwanted on Pocket PC 2002 + * when no menu has been set, as it still draws a red circle. The fix + * is to disable this default behaviour when no menu is set by returning + * TRUE when receiving the Pocket PC 2002 specific NM_RECOGNIZEGESTURE + * message. + */ + if (OS.IsPPC) { + bool hasMenu = menu !is null && !menu.isDisposed (); + if (!hasMenu && !hooks (SWT.MenuDetect)) return LRESULT.ONE; + } + break; + } + case OS.GN_CONTEXTMENU: { + if (OS.IsPPC) { + bool hasMenu = menu !is null && !menu.isDisposed (); + if (hasMenu || hooks (SWT.MenuDetect)) { + NMRGINFO* nmrg = cast(NMRGINFO*)lParam; + //OS.MoveMemory (nmrg, lParam, NMRGINFO.sizeof); + showMenu (nmrg.x, nmrg.y); + gestureCompleted = true; + return LRESULT.ONE; + } + } + break; + } + default: + } + return super.wmNotifyChild (hdr, wParam, lParam); +} + +LRESULT wmNotifyHeader (NMHDR* hdr, int /*long*/ wParam, int /*long*/ lParam) { + /* + * Feature in Windows. On NT, the automatically created + * header control is created as a UNICODE window, not an + * ANSI window despite the fact that the parent is created + * as an ANSI window. This means that it sends UNICODE + * notification messages to the parent window on NT for + * no good reason. The data and size in the NMHEADER and + * HDITEM structs is identical between the platforms so no + * different message is actually necessary. Despite this, + * Windows sends different messages. The fix is to look + * for both messages, despite the platform. This works + * because only one will be sent on either platform, never + * both. + */ + switch (hdr.code) { + case OS.HDN_BEGINTRACKW: + case OS.HDN_BEGINTRACKA: + case OS.HDN_DIVIDERDBLCLICKW: + case OS.HDN_DIVIDERDBLCLICKA: { + NMHEADER* phdn = cast(NMHEADER*)lParam; + TreeColumn column = columns [phdn.iItem]; + if (column !is null && !column.getResizable ()) { + return LRESULT.ONE; + } + ignoreColumnMove = true; + switch (hdr.code) { + case OS.HDN_DIVIDERDBLCLICKW: + case OS.HDN_DIVIDERDBLCLICKA: + if (column !is null) column.pack (); + default: + } + break; + } + case OS.NM_RELEASEDCAPTURE: { + if (!ignoreColumnMove) { + for (int i=0; i<columnCount; i++) { + TreeColumn column = columns [i]; + column.updateToolTip (i); + } + updateImageList (); + } + ignoreColumnMove = false; + break; + } + case OS.HDN_BEGINDRAG: { + if (ignoreColumnMove) return LRESULT.ONE; + NMHEADER* phdn = cast(NMHEADER*)lParam; + if (phdn.iItem !is -1) { + TreeColumn column = columns [phdn.iItem]; + if (column !is null && !column.getMoveable ()) { + ignoreColumnMove = true; + return LRESULT.ONE; + } + } + break; + } + case OS.HDN_ENDDRAG: { + NMHEADER* phdn = cast(NMHEADER*)lParam; + if (cast(int)phdn.iItem !is -1 && phdn.pitem !is null) { + HDITEM* pitem = cast(HDITEM*)phdn.pitem; + if ((pitem.mask & OS.HDI_ORDER) !is 0 && pitem.iOrder !is -1) { + int [] order = new int [columnCount]; + OS.SendMessage (hwndHeader, OS.HDM_GETORDERARRAY, columnCount, order.ptr); + int index = 0; + while (index < order.length) { + if (order [index] is phdn.iItem) break; + index++; + } + if (index is order.length) index = 0; + if (index is pitem.iOrder) break; + int start = Math.min (index, pitem.iOrder); + int end = Math.max (index, pitem.iOrder); + RECT rect, headerRect; + OS.GetClientRect (handle, &rect); + OS.SendMessage (hwndHeader, OS.HDM_GETITEMRECT, order [start], &headerRect); + rect.left = Math.max (rect.left, headerRect.left); + OS.SendMessage (hwndHeader, OS.HDM_GETITEMRECT, order [end], &headerRect); + rect.right = Math.min (rect.right, headerRect.right); + OS.InvalidateRect (handle, &rect, true); + ignoreColumnMove = false; + for (int i=start; i<=end; i++) { + TreeColumn column = columns [order [i]]; + if (!column.isDisposed ()) { + column.postEvent (SWT.Move); + } + } + } + } + break; + } + case OS.HDN_ITEMCHANGINGW: + case OS.HDN_ITEMCHANGINGA: { + NMHEADER* phdn = cast(NMHEADER*)lParam; + if (phdn.pitem !is null) { + HDITEM* newItem = cast(HDITEM*)phdn.pitem; + if ((newItem.mask & OS.HDI_WIDTH) !is 0) { + RECT rect; + OS.GetClientRect (handle, &rect); + HDITEM oldItem; + oldItem.mask = OS.HDI_WIDTH; + OS.SendMessage (hwndHeader, OS.HDM_GETITEM, phdn.iItem, &oldItem); + int deltaX = newItem.cxy - oldItem.cxy; + RECT headerRect; + OS.SendMessage (hwndHeader, OS.HDM_GETITEMRECT, phdn.iItem, &headerRect); + int gridWidth = linesVisible ? GRID_WIDTH : 0; + rect.left = headerRect.right - gridWidth; + int newX = rect.left + deltaX; + rect.right = Math.max (rect.right, rect.left + Math.abs (deltaX)); + if (explorerTheme || (findImageControl () !is null || hooks (SWT.MeasureItem) || hooks (SWT.EraseItem) || hooks (SWT.PaintItem))) { + rect.left -= OS.GetSystemMetrics (OS.SM_CXFOCUSBORDER); + OS.InvalidateRect (handle, &rect, true); + OS.OffsetRect (&rect, deltaX, 0); + OS.InvalidateRect (handle, &rect, true); + } else { + int flags = OS.SW_INVALIDATE | OS.SW_ERASE; + OS.ScrollWindowEx (handle, deltaX, 0, &rect, null, null, null, flags); + } + if (OS.SendMessage (hwndHeader, OS.HDM_ORDERTOINDEX, phdn.iItem, 0) !is 0) { + rect.left = headerRect.left; + rect.right = newX; + OS.InvalidateRect (handle, &rect, true); + } + setScrollWidth (); + } + } + break; + } + case OS.HDN_ITEMCHANGEDW: + case OS.HDN_ITEMCHANGEDA: { + NMHEADER* phdn = cast(NMHEADER*)lParam; + if (phdn.pitem !is null) { + HDITEM* pitem = cast(HDITEM*)phdn.pitem; + if ((pitem.mask & OS.HDI_WIDTH) !is 0) { + if (ignoreColumnMove) { + if (!OS.IsWinCE && OS.WIN32_VERSION >= OS.VERSION (6, 0)) { + int flags = OS.RDW_UPDATENOW | OS.RDW_ALLCHILDREN; + OS.RedrawWindow (handle, null, null, flags); + } else { + if ((style & SWT.DOUBLE_BUFFERED) is 0) { + int oldStyle = style; + style |= SWT.DOUBLE_BUFFERED; + OS.UpdateWindow (handle); + style = oldStyle; + } + } + } + TreeColumn column = columns [phdn.iItem]; + if (column !is null) { + column.updateToolTip (phdn.iItem); + column.sendEvent (SWT.Resize); + if (isDisposed ()) return LRESULT.ZERO; + TreeColumn [] newColumns = new TreeColumn [columnCount]; + System.arraycopy (columns, 0, newColumns, 0, columnCount); + int [] order = new int [columnCount]; + OS.SendMessage (hwndHeader, OS.HDM_GETORDERARRAY, columnCount, order.ptr); + bool moved = false; + for (int i=0; i<columnCount; i++) { + TreeColumn nextColumn = newColumns [order [i]]; + if (moved && !nextColumn.isDisposed ()) { + nextColumn.updateToolTip (order [i]); + nextColumn.sendEvent (SWT.Move); + } + if (nextColumn is column) moved = true; + } + } + } + setScrollWidth (); + } + break; + } + case OS.HDN_ITEMCLICKW: + case OS.HDN_ITEMCLICKA: { + NMHEADER* phdn = cast(NMHEADER*)lParam; + TreeColumn column = columns [phdn.iItem]; + if (column !is null) { + column.postEvent (SWT.Selection); + } + break; + } + case OS.HDN_ITEMDBLCLICKW: + case OS.HDN_ITEMDBLCLICKA: { + NMHEADER* phdn = cast(NMHEADER*)lParam; + TreeColumn column = columns [phdn.iItem]; + if (column !is null) { + column.postEvent (SWT.DefaultSelection); + } + break; + } + default: + } + return null; +} + +LRESULT wmNotifyToolTip (NMHDR* hdr, int /*long*/ wParam, int /*long*/ lParam) { + if (OS.IsWinCE) return null; + switch (hdr.code) { + case OS.NM_CUSTOMDRAW: { + NMTTCUSTOMDRAW* nmcd = cast(NMTTCUSTOMDRAW*)lParam; + return wmNotifyToolTip (nmcd, lParam); + } + case OS.TTN_SHOW: { + LRESULT result = super.wmNotify (hdr, wParam, lParam); + if (result !is null) return result; + int pos = OS.GetMessagePos (); + POINT pt; + OS.POINTSTOPOINT (pt, pos); + OS.ScreenToClient (handle, &pt); + int index; + TreeItem item; + RECT* cellRect, itemRect; + if (findCell (pt.x, pt.y, item, index, cellRect, itemRect)) { + RECT* toolRect = toolTipRect (itemRect); + OS.MapWindowPoints (handle, null, cast(POINT*)toolRect, 2); + int width = toolRect.right - toolRect.left; + int height = toolRect.bottom - toolRect.top; + int flags = OS.SWP_NOACTIVATE | OS.SWP_NOZORDER | OS.SWP_NOSIZE; + if (isCustomToolTip ()) flags &= ~OS.SWP_NOSIZE; + SetWindowPos (itemToolTipHandle, null, toolRect.left, toolRect.top, width, height, flags); + return LRESULT.ONE; + } + return result; + } + default: + } + return null; +} + +LRESULT wmNotifyToolTip (NMTTCUSTOMDRAW* nmcd, int /*long*/ lParam) { + if (OS.IsWinCE) return null; + switch (nmcd.nmcd.dwDrawStage) { + case OS.CDDS_PREPAINT: { + if (isCustomToolTip ()) { + //TEMPORARY CODE + //nmcd.uDrawFlags |= OS.DT_CALCRECT; + //OS.MoveMemory (lParam, nmcd, NMTTCUSTOMDRAW.sizeof); + if (!OS.IsWinCE && OS.WIN32_VERSION < OS.VERSION (6, 0)) { + OS.SetTextColor (nmcd.nmcd.hdc, OS.GetSysColor (OS.COLOR_INFOBK)); + } + return new LRESULT (OS.CDRF_NOTIFYPOSTPAINT | OS.CDRF_NEWFONT); + } + break; + } + case OS.CDDS_POSTPAINT: { + if (!OS.IsWinCE && OS.WIN32_VERSION < OS.VERSION (6, 0)) { + OS.SetTextColor (nmcd.nmcd.hdc, OS.GetSysColor (OS.COLOR_INFOTEXT)); + } + if (OS.SendMessage (itemToolTipHandle, OS.TTM_GETCURRENTTOOL, 0, 0) !is 0) { + TOOLINFO lpti; + lpti.cbSize = OS.TOOLINFO_sizeof; + if (OS.SendMessage (itemToolTipHandle, OS.TTM_GETCURRENTTOOL, 0, cast(int)&lpti) !is 0) { + int index; + TreeItem item; + RECT* cellRect, itemRect; + int pos = OS.GetMessagePos (); + POINT pt; + OS.POINTSTOPOINT (pt, pos); + OS.ScreenToClient (handle, &pt); + if (findCell (pt.x, pt.y, item, index, cellRect, itemRect)) { + auto hDC = OS.GetDC (handle); + auto hFont = item.fontHandle (index); + if (hFont is cast(HFONT)-1) hFont = cast(HFONT) OS.SendMessage (handle, OS.WM_GETFONT, 0, 0); + HFONT oldFont = OS.SelectObject (hDC, hFont); + LRESULT result = null; + bool drawForeground = true; + cellRect = item.getBounds (index, true, true, false, false, false, hDC); + if (hooks (SWT.EraseItem)) { + Event event = sendEraseItemEvent (item, nmcd, index, cellRect); + if (isDisposed () || item.isDisposed ()) break; + if (event.doit) { + drawForeground = (event.detail & SWT.FOREGROUND) !is 0; + } else { + drawForeground = false; + } + } + if (drawForeground) { + int nSavedDC = OS.SaveDC (nmcd.nmcd.hdc); + int gridWidth = getLinesVisible () ? Table.GRID_WIDTH : 0; + RECT* insetRect = toolTipInset (cellRect); + OS.SetWindowOrgEx (nmcd.nmcd.hdc, insetRect.left, insetRect.top, null); + GCData data = new GCData (); + data.device = display; + data.foreground = OS.GetTextColor (nmcd.nmcd.hdc); + data.background = OS.GetBkColor (nmcd.nmcd.hdc); + data.font = Font.win32_new (display, hFont); + GC gc = GC.win32_new (nmcd.nmcd.hdc, data); + int x = cellRect.left + INSET; + if (index !is 0) x -= gridWidth; + Image image = item.getImage (index); + if (image !is null || index is 0) { + Point size = getImageSize (); + RECT* imageRect = item.getBounds (index, false, true, false, false, false, hDC); + if (imageList is null) size.x = imageRect.right - imageRect.left; + if (image !is null) { + Rectangle rect = image.getBounds (); + gc.drawImage (image, rect.x, rect.y, rect.width, rect.height, x, imageRect.top, size.x, size.y); + x += INSET + (index is 0 ? 1 : 0); + } + x += size.x; + } else { + x += INSET; + } + String string = item.getText (index); + if (string !is null) { + int flags = OS.DT_NOPREFIX | OS.DT_SINGLELINE | OS.DT_VCENTER; + TreeColumn column = columns !is null ? columns [index] : null; + if (column !is null) { + if ((column.style & SWT.CENTER) !is 0) flags |= OS.DT_CENTER; + if ((column.style & SWT.RIGHT) !is 0) flags |= OS.DT_RIGHT; + } + TCHAR[] buffer = StrToTCHARs (getCodePage (), string, false); + RECT textRect; + OS.SetRect (&textRect, x, cellRect.top, cellRect.right, cellRect.bottom); + OS.DrawText (nmcd.nmcd.hdc, buffer.ptr, buffer.length, &textRect, flags); + } + gc.dispose (); + OS.RestoreDC (nmcd.nmcd.hdc, nSavedDC); + } + if (hooks (SWT.PaintItem)) { + itemRect = item.getBounds (index, true, true, false, false, false, hDC); + sendPaintItemEvent (item, nmcd, index, itemRect); + } + OS.SelectObject (hDC, oldFont); + OS.ReleaseDC (handle, hDC); + if (result !is null) return result; + } + break; + } + } + break; + } + default: + } + return null; +} + +}