<commit_before>/*
 * $Id$
 *
 * Copyright(C) 1998-2006 <NAME>
 * All rights reserved.
 *
 */

#pragma warning(disable:4786)

#include <qmaccount.h>
#include <qmapplication.h>
#include <qmfilenames.h>
#include <qmfolder.h>
#include <qmlistwindow.h>
#include <qmmacro.h>
#include <qmmessageholder.h>

#include <qsaccelerator.h>
#include <qsconv.h>
#include <qsdevicecontext.h>
#include <qsdragdrop.h>
#include <qsmenu.h>
#include <qsosutil.h>
#include <qsprofile.h>
#include <qsstl.h>
#include <qsstream.h>
#include <qsuiutil.h>

#include <algorithm>
#include <vector>

#include "listwindow.h"
#include "messageframewindow.h"
#include "resourceinc.h"
#include "syncdialog.h"
#include "syncutil.h"
#include "uimanager.h"
#include "../action/action.h"
#include "../model/dataobject.h"
#include "../uimodel/viewmodel.h"

using namespace qm;
using namespace qs;


/****************************************************************************
 *
 * ListWindowImpl
 *
 */

struct qm::ListWindowImpl :
	public NotifyHandler,
	public ViewModelManagerHandler,
	public ViewModelHandler,
	public DragGestureHandler,
	public DragSourceHandler,
	public DropTargetHandler
{
public:
	enum {
		ID_HEADERCOLUMN		= 1000,
#ifdef QMTOOLTIP
		ID_TOOLTIP			= 1001
#endif
	};
	enum {
		LINE_SPACING		= 4,
		HORIZONTAL_BORDER	= 2,
		INDENT				= 15
	};
	enum {
		WM_VIEWMODEL_ITEMADDED		= WM_APP + 1101,
		WM_VIEWMODEL_ITEMREMOVED	= WM_APP + 1102,
		WM_VIEWMODEL_ITEMCHANGED	= WM_APP + 1103
	};

public:
	bool createHeaderColumn();
	void layoutChildren();
	void layoutChildren(int cx, int cy);
	
	void getRect(RECT* pRect);
	void paintMessage(const PaintInfo& pi);
	void invalidateLine(unsigned int nLine);
	void invalidateSelected();
	void ensureVisible(unsigned int nLine);
	void scrollVertical(int nPos);
	void scrollHorizontal(int nPos);
	void updateScrollBar(bool bVertical);
	unsigned int getLineFromPoint(const POINT& pt) const;
	unsigned int getColumnFromPoint(const POINT& pt) const;
	void getRect(unsigned int nLine,
				 unsigned int nColumn,
				 RECT* pRect);
	void reloadProfiles(bool bInitialize);
	void updateLineHeight();
	COLORREF getColor(int nIndex) const;
#ifdef QMTOOLTIP
	void relayToolTipEvent(UINT uMsg,
						   WPARAM wParam,
						   LPARAM lParam);
	void hideToolTip();
#endif
	
	void postRefreshMessage(UINT uMsg,
							unsigned int nItem);
	void handleRefreshMessage();
	void postInvalidateMessage(UINT uMsg,
							   unsigned int nItem);
	void handleInvalidateMessage(unsigned int nItem);

public:
	virtual LRESULT onNotify(NMHDR* pnmhdr,
							 bool* pbHandled);

public:
	virtual void viewModelSelected(const ViewModelManagerEvent& event);

public:
	virtual void itemAdded(const ViewModelEvent& event);
	virtual void itemRemoved(const ViewModelEvent& event);
	virtual void itemChanged(const ViewModelEvent& event);
	virtual void itemStateChanged(const ViewModelEvent& event);
	virtual void itemAttentionPaid(const ViewModelEvent& event);
	virtual void updated(const ViewModelEvent& event);
	virtual void sorted(const ViewModelEvent& event);
	virtual void colorChanged(const ViewModelEvent& event);
	virtual void columnChanged(const ViewModelEvent& event);
	virtual void destroyed(const ViewModelEvent& event);

public:
	virtual void dragGestureRecognized(const DragGestureEvent& event);

public:
	virtual void dragDropEnd(const DragSourceDropEvent& event);

public:
	virtual void dragEnter(const DropTargetDragEvent& event);
	virtual void dragOver(const DropTargetDragEvent& event);
	virtual void dragExit(const DropTargetEvent& event);
	virtual void drop(const DropTargetDropEvent& event);

private:
#ifdef QMTOOLTIP
	LRESULT onShow(NMHDR* pnmhdr,
				   bool* pbHandled);
#endif

private:
	HIMAGELIST createDragImage(const POINT& ptCursor,
							   POINT* pptHotspot);

private:
	static int getMessageImage(MessageHolder* pmh,
							   unsigned int nFlags);

public:
	ListWindow* pThis_;
	
	Profile* pProfile_;
	MessageFrameWindowManager* pMessageFrameWindowManager_;
	MenuManager* pMenuManager_;
	std::auto_ptr<Accelerator> pAccelerator_;
	AccountManager* pAccountManager_;
	ViewModelManager* pViewModelManager_;
	SyncManager* pSyncManager_;
	SyncDialogManager* pSyncDialogManager_;
	
	HFONT hfont_;
	HFONT hfontBold_;
	bool bUseSystemColor_;
	COLORREF crForeground_;
	COLORREF crBackground_;
	bool bSingleClickOpen_;
	bool bEllipsis_;
	int nLineHeight_;
	ListHeaderColumn* pHeaderColumn_;
	HIMAGELIST hImageList_;
	HIMAGELIST hImageListData_;
	HPEN hpenThreadLine_;
	HPEN hpenFocusedThreadLine_;
	
#ifdef QMTOOLTIP
	HWND hwndToolTip_;
	unsigned int nToolTipLine_;
	unsigned int nToolTipColumn_;
	int nToolTipIndent_;
#endif
	
	std::auto_ptr<DragGestureRecognizer> pDragGestureRecognizer_;
	std::auto_ptr<DropTarget> pDropTarget_;
	bool bCanDrop_;
	
	volatile LONG nRefreshing_;
	volatile LONG nInvalidating_;
};

bool qm::ListWindowImpl::createHeaderColumn()
{
	std::auto_ptr<ListHeaderColumn> pHeaderColumn(
		new ListHeaderColumn(pThis_, pProfile_));
	
	RECT rect;
	pThis_->getClientRect(&rect);
	if (!pHeaderColumn->create(L"QmListHeaderColumn", 0,
		WS_CHILD, 0, 0, rect.right - rect.left, 0,
		pThis_->getHandle(), 0, 0, ID_HEADERCOLUMN, 0))
		return false;
	pHeaderColumn_ = pHeaderColumn.release();
	pHeaderColumn_->setFont(hfont_, false);
	
	return true;
}

void qm::ListWindowImpl::layoutChildren()
{
	RECT rect;
	pThis_->getClientRect(&rect);
	layoutChildren(rect.right - rect.left, rect.bottom - rect.top);
}

void qm::ListWindowImpl::layoutChildren(int cx,
										int cy)
{
	if (pHeaderColumn_)
		pHeaderColumn_->setWindowPos(0, 0, 0, cx + pThis_->getScrollPos(SB_HORZ),
			pHeaderColumn_->getPreferredHeight(cx, cy), SWP_NOMOVE | SWP_NOZORDER);
	updateScrollBar(true);
	updateScrollBar(false);
	pThis_->invalidate();
}

void qm::ListWindowImpl::getRect(RECT* pRect)
{
	assert(pRect);
	
	pThis_->getClientRect(pRect);
	pRect->top += pHeaderColumn_->getHeight();
}

void qm::ListWindowImpl::paintMessage(const PaintInfo& pi)
{
	DeviceContext* pdc = pi.getDeviceContext();
	ViewModel* pViewModel = pi.getViewModel();
	const ViewModelItem* pItem = pi.getItem();
	MessageHolder* pmh = pItem->getMessageHolder();
	const RECT& rect = pi.getRect();
	
	bool bHasFocus = pThis_->hasFocus();
	bool bSelected = (pItem->getFlags() & ViewModelItem::FLAG_SELECTED) != 0;
	
	COLORREF crBackground = pItem->getBackground();
	if (crBackground == 0xff000000)
		crBackground = getColor(COLOR_WINDOW);
	
	if (bSelected) {
		pdc->setTextColor(getColor(bHasFocus ? COLOR_HIGHLIGHTTEXT : COLOR_WINDOWTEXT));
		pdc->setBkColor(getColor(bHasFocus ? COLOR_HIGHLIGHT : COLOR_INACTIVEBORDER));
	}
	else {
		COLORREF crText = pItem->getForeground();
		if (crText == 0xff000000)
			crText = getColor(COLOR_WINDOWTEXT);
		pdc->setTextColor(crText);
		pdc->setBkColor(crBackground);
	}
	ObjectSelector<HFONT> fontSelector(*pdc, pItem->isBold() ? hfontBold_ : 0);
	
	RECT r = { rect.left, rect.top, rect.left, rect.bottom };
#if defined _WIN32_WCE && _WIN32_WCE >= 0x300 && defined _WIN32_WCE_PSPC
	RECT rectFocus = { 0, rect.top, 0, rect.bottom };
#else
	RECT rectFocus = { -1, rect.top, 0, rect.bottom };
#endif
	
	unsigned int nLevel = 0;
	if ((pViewModel->getSort() & ViewModel::SORT_THREAD_MASK) == ViewModel::SORT_THREAD)
		nLevel = pItem->getLevel();
	
	int nThreadLeft = -1;
	int nThreadRight = -1;
	const ViewColumnList& listColumn = pViewModel->getColumns();
	for (ViewColumnList::const_iterator it = listColumn.begin(); it != listColumn.end(); ++it) {
		ViewColumn* pColumn = *it;
		
		r.right += pColumn->getWidth();
		
		int nOffset = 0;
		if (pColumn->isFlag(ViewColumn::FLAG_INDENT) &&
			((pViewModel->getSort() & ViewModel::SORT_THREAD_MASK) == ViewModel::SORT_THREAD)) {
			nOffset = nLevel*INDENT;
			if (pColumn->isFlag(ViewColumn::FLAG_LINE)) {
				nThreadLeft = r.left;
				nThreadRight = r.right;
			}
		}
		
		ViewColumn::Type type = pColumn->getType();
		if (!pColumn->isFlag(ViewColumn::FLAG_ICON)) {
			wstring_ptr wstrText(pColumn->getText(pViewModel, pItem));
			size_t nLen = wcslen(wstrText.get());
			POINT pt = { r.left + HORIZONTAL_BORDER, r.top + LINE_SPACING/2 };
			if (pColumn->isFlag(ViewColumn::FLAG_RIGHTALIGN)) {
				SIZE size;
				pdc->getTextExtent(wstrText.get(), static_cast<int>(nLen), &size);
				int nX = r.right - size.cx - HORIZONTAL_BORDER;
				if (nX > pt.x)
					pt.x = nX;
			}
			else {
				pt.x += nOffset;
			}
			
			if (bEllipsis_)
				pdc->extTextOutEllipsis(pt.x, pt.y, r.right - pt.x - HORIZONTAL_BORDER,
					ETO_CLIPPED | ETO_OPAQUE, r, wstrText.get(), static_cast<UINT>(nLen));
			else
				pdc->extTextOut(pt.x, pt.y, ETO_CLIPPED | ETO_OPAQUE,
					r, wstrText.get(), static_cast<UINT>(nLen), 0);
			
			if (rectFocus.left == -1)
				rectFocus.left = r.left;
			rectFocus.right = r.right;
		}
		else {
			unsigned int nValue = pColumn->getNumber(pViewModel, pItem);
			
#if defined _WIN32_WCE && _WIN32_WCE >= 0x300 && defined _WIN32_WCE_PSPC
			COLORREF crBk = bSelected ? 0xff000000 : pItem->getBackground();
#else
			COLORREF crBk = pItem->getBackground();
#endif
			if (crBk == 0xff000000) {
#if defined _WIN32_WCE && _WIN32_WCE >= 0x300 && defined _WIN32_WCE_PSPC
				UINT nBkColorId = bSelected ? pThis_->hasFocus() ?
					COLOR_HIGHLIGHT : COLOR_INACTIVEBORDER : COLOR_WINDOW;
#else
				UINT nBkColorId = COLOR_WINDOW;
#endif
				crBk = getColor(nBkColorId);
			}
			COLORREF crBkOld = pdc->getBkColor();
			pdc->fillSolidRect(r, crBk);
			pdc->setBkColor(crBkOld);
			
#if defined _WIN32_WCE && _WIN32_WCE >= 0x300 && defined _WIN32_WCE_PSPC
			UINT nFlags = bSelected ? ILD_TRANSPARENT : ILD_NORMAL;
#else
			UINT nFlags = bSelected ? ILD_SELECTED : ILD_NORMAL;
#endif
			int y = r.top + (r.bottom - r.top - 16)/2;
			ImageList_Draw(hImageList_, getMessageImage(pmh, nValue),
				pdc->getHandle(), r.left + 7, y, nFlags);
			if (nValue & MessageHolder::FLAG_MULTIPART)
				ImageList_Draw(hImageListData_, 0, pdc->getHandle(),
					r.left, y, nFlags);
			if (nValue & MessageHolder::FLAG_REPLIED)
				ImageList_Draw(hImageListData_, 1, pdc->getHandle(),
					r.left + 15, y, nFlags);
			else if (nValue & MessageHolder::FLAG_FORWARDED)
				ImageList_Draw(hImageListData_, 2, pdc->getHandle(),
					r.left + 15, y, nFlags);
			if (nValue & MessageHolder::FLAG_DOWNLOAD ||
				nValue & MessageHolder::FLAG_DOWNLOADTEXT)
				ImageList_Draw(hImageListData_, 3, pdc->getHandle(),
					r.left + 7, y, nFlags);
			if (nValue & MessageHolder::FLAG_DELETED)
				ImageList_Draw(hImageListData_, 4, pdc->getHandle(),
					r.left + 7, y, nFlags);
			if (nValue & MessageHolder::FLAG_MARKED)
				ImageList_Draw(hImageListData_, 5, pdc->getHandle(),
					r.left + 15, y, nFlags);
			
#if defined _WIN32_WCE && _WIN32_WCE >= 0x300 && defined _WIN32_WCE_PSPC
			rectFocus.right = r.right;
#endif
		}
		
		r.left = r.right;
	}
	r.right = rect.right;
	pdc->fillSolidRect(r, crBackground);
	
	if (((pViewModel->getSort() & ViewModel::SORT_THREAD_MASK) == ViewModel::SORT_THREAD) &&
		nThreadLeft != -1) {
		if (nLevel > 0) {
			typedef std::vector<int> Line;
			Line line(nLevel, 0);
			for (unsigned int n = pi.getIndex() + 1; n < pViewModel->getCount(); ++n) {
				const ViewModelItem* pNextItem = pViewModel->getItem(n);
				unsigned int nNextLevel = pNextItem->getLevel();
				if (nNextLevel == 0) {
					break;
				}
				else if (nNextLevel <= nLevel) {
					line[nNextLevel - 1] = 1;
					nLevel = nNextLevel;
				}
			}
			
			HPEN hpen = (bSelected && bHasFocus) ?
				hpenFocusedThreadLine_ : hpenThreadLine_;
			ObjectSelector<HPEN> selector(*pdc, hpen);
			int nOffset = 7;
			POINT points[4];
			for (Line::iterator it = line.begin(); it != line.end(); ++it) {
				int nPoint = 2;
				if (*it || it + 1 == line.end()) {
					points[0].x = nThreadLeft + nOffset;
					points[0].y = rect.top;
					points[1].x = points[0].x;
					points[1].y = *it ? rect.bottom : (rect.top + rect.bottom)/2;
					if (it + 1 == line.end()) {
						if (*it) {
							points[nPoint].x = points[1].x;
							points[nPoint].y = (rect.top + rect.bottom)/2;
							++nPoint;
						}
						points[nPoint].x = points[nPoint - 1].x + 5;
						points[nPoint].y = points[nPoint - 1].y;
						++nPoint;
					}
					pdc->polyline(points, nPoint);
				}
				nOffset += INDENT;
				if (nThreadLeft + nOffset > nThreadRight)
					break;
			}
		}
	}
	
#if !defined _WIN32_WCE || _WIN32_WCE < 0x300 || !defined _WIN32_WCE_PSPC
	if ((pItem->getFlags() & ViewModelItem::FLAG_FOCUSED) && bHasFocus) {
		pdc->setTextColor(COLOR_WINDOWTEXT);
		pdc->drawFocusRect(rectFocus);
	}
#endif
}

void qm::ListWindowImpl::invalidateLine(unsigned int nLine)
{
	RECT rect;
	getRect(&rect);
	
	int nPos = pThis_->getScrollPos(SB_VERT);
	if (static_cast<int>(nLine) < nPos ||
		nPos + (rect.bottom - rect.top)/nLineHeight_ + 1 < static_cast<int>(nLine))
		return;
	rect.top += (nLine - nPos)*nLineHeight_;
	rect.bottom = rect.top + nLineHeight_;
	
	pThis_->invalidateRect(rect);
}

void qm::ListWindowImpl::invalidateSelected()
{
	ViewModel* pViewModel = pViewModelManager_->getCurrentViewModel();
	if (pViewModel) {
		Lock<ViewModel> lock(*pViewModel);
		
		RECT rect;
		getRect(&rect);
		int nTop = rect.top;
		
		SCROLLINFO si = {
			sizeof(si),
			SIF_PAGE | SIF_POS
		};
		pThis_->getScrollInfo(SB_VERT, &si);
		
		unsigned int nCount = pViewModel->getCount();
		unsigned int nFocused = pViewModel->getFocused();
		for (unsigned int n = si.nPos; n < nCount && n <= si.nPos + si.nPage; ++n) {
			if (pViewModel->isSelected(n) || n == nFocused) {
				rect.top = nTop + (n - si.nPos)*nLineHeight_;
				rect.bottom = rect.top + nLineHeight_;
				pThis_->invalidateRect(rect);
			}
		}
	}
}

void qm::ListWindowImpl::ensureVisible(unsigned int nLine)
{
	SCROLLINFO si = {
		sizeof(si),
		SIF_POS | SIF_PAGE | SIF_RANGE
	};
	pThis_->getScrollInfo(SB_VERT, &si);
	if (si.nMax - si.nMin < static_cast<int>(si.nPage))
		return;
	
	if (nLine < static_cast<unsigned int>(si.nPos))
		scrollVertical(nLine);
	else if (nLine >= static_cast<unsigned int>(si.nPos + si.nPage) - 1)
		scrollVertical(nLine + 1 - si.nPage);
}

void qm::ListWindowImpl::scrollVertical(int nPos)
{
	SCROLLINFO si = {
		sizeof(si),
		SIF_RANGE | SIF_POS | SIF_PAGE
	};
	pThis_->getScrollInfo(SB_VERT, &si);
	
	if (nPos < 0)
		nPos = 0;
	else if (nPos > si.nMax - static_cast<int>(si.nPage) + 1)
		nPos = si.nMax - si.nPage + 1;
	
	if (nPos == si.nPos)
		return;
	
	RECT rect;
	getRect(&rect);
	
	int nOldPos = si.nPos;
	pThis_->setScrollPos(SB_VERT, nPos);
	pThis_->scrollWindow(0, (nOldPos - nPos)*nLineHeight_,
		&rect, &rect, 0, 0, SW_INVALIDATE);
	
#ifdef QMTOOLTIP
	hideToolTip();
#endif
}

void qm::ListWindowImpl::scrollHorizontal(int nPos)
{
	SCROLLINFO si = {
		sizeof(si),
		SIF_RANGE | SIF_POS | SIF_PAGE
	};
	pThis_->getScrollInfo(SB_HORZ, &si);
	
	if (nPos < 0)
		nPos = 0;
	else if (nPos > si.nMax - static_cast<int>(si.nPage) + 1)
		nPos = si.nMax - si.nPage;
	
	if (nPos == si.nPos)
		return;
	
	RECT rect;
	pThis_->getClientRect(&rect);
	
	int nOldPos = si.nPos;
	pThis_->setScrollPos(SB_HORZ, nPos);
	pThis_->scrollWindow(nOldPos - nPos, 0,
		&rect, &rect, 0, 0, SW_INVALIDATE/* | SW_SCROLLCHILDREN*/);
	
	pHeaderColumn_->setWindowPos(0, -nPos, 0,
		(rect.right - rect.left) + nPos, pHeaderColumn_->getHeight(),
		SWP_NOZORDER | SWP_NOACTIVATE);
	
#ifdef QMTOOLTIP
	hideToolTip();
#endif
}

void qm::ListWindowImpl::updateScrollBar(bool bVertical)
{
	RECT rect;
	getRect(&rect);
	
	if (bVertical) {
		ViewModel* pViewModel = pViewModelManager_->getCurrentViewModel();
		unsigned int nCount = 0;
		if (pViewModel) {
			Lock<ViewModel> lock(*pViewModel);
			nCount = pViewModel->getCount();
		}
		
		unsigned int nPage = (rect.bottom - rect.top)/nLineHeight_;
		if (nPage > nCount)
			nPage = nCount;
		SCROLLINFO si = {
			sizeof(si),
			SIF_PAGE | SIF_RANGE,
			0,
			nCount == 0 ? 0 : nCount - 1,
			nPage,
			0,
		};
		pThis_->setScrollInfo(SB_VERT, si);
	}
	else {
		unsigned int nWidth = pHeaderColumn_->getWidth();
		SCROLLINFO si = {
			sizeof(si),
			SIF_PAGE | SIF_RANGE,
			0,
			nWidth,
			rect.right - rect.left,
			0,
		};
		pThis_->setScrollInfo(SB_HORZ, si);
		
		pHeaderColumn_->setWindowPos(0, -pThis_->getScrollPos(SB_HORZ),
			0, 0, 0, SWP_NOZORDER | SWP_NOSIZE | SWP_NOACTIVATE);
	}
}

unsigned int qm::ListWindowImpl::getLineFromPoint(const POINT& pt) const
{
	ViewModel* pViewModel = pViewModelManager_->getCurrentViewModel();
	assert(pViewModel);
	assert(pViewModel->isLocked());
	
	unsigned int nLine = (pt.y - pHeaderColumn_->getHeight())/
		nLineHeight_ + pThis_->getScrollPos(SB_VERT);
	return nLine < pViewModel->getCount() ? nLine : -1;
}

unsigned int qm::ListWindowImpl::getColumnFromPoint(const POINT& pt) const
{
	ViewModel* pViewModel = pViewModelManager_->getCurrentViewModel();
	assert(pViewModel);
	
	int nX = pt.x + pThis_->getScrollPos(SB_HORZ);
	
	const ViewColumnList& listColumn = pViewModel->getColumns();
	for (unsigned int n = 0; n < listColumn.size(); ++n) {
		int nWidth = listColumn[n]->getWidth();
		if (nX < nWidth)
			return n;
		nX -= nWidth;
	}
	return -1;
}

void qm::ListWindowImpl::getRect(unsigned int nLine,
								 unsigned int nColumn,
								 RECT* pRect)
{
	assert(pRect);
	
	ViewModel* pViewModel = pViewModelManager_->getCurrentViewModel();
	assert(pViewModel);
	
	pRect->top = pHeaderColumn_->getHeight() +
		(nLine - pThis_->getScrollPos(SB_VERT))*nLineHeight_;
	pRect->bottom = pRect->top + nLineHeight_;
	
	pRect->left = -pThis_->getScrollPos(SB_HORZ);
	for (unsigned int n = 0; n < nColumn; ++n)
		pRect->left += pViewModel->getColumn(n).getWidth();
	pRect->right = pRect->left + pViewModel->getColumn(nColumn).getWidth();
}

void qm::ListWindowImpl::reloadProfiles(bool bInitialize)
{
	bSingleClickOpen_ = pProfile_->getInt(L"ListWindow", L"SingleClickOpen") != 0;
	bEllipsis_ = pProfile_->getInt(L"ListWindow", L"Ellipsis") != 0;
	
	HFONT hfont = qs::UIUtil::createFontFromProfile(pProfile_,
		L"ListWindow", qs::UIUtil::DEFAULTFONT_UI);
	if (!bInitialize) {
		assert(hfont_);
		pHeaderColumn_->setFont(hfont);
		::DeleteObject(hfont_);
	}
	hfont_ = hfont;
	
	LOGFONT lf;
	::GetObject(hfont, sizeof(lf), &lf);
	lf.lfWeight = FW_BOLD;
	HFONT hfontBold = ::CreateFontIndirect(&lf);
	if (!bInitialize) {
		assert(hfontBold_);
		::DeleteObject(hfontBold_);
	}
	hfontBold_ = hfontBold;
	
	if (!bInitialize) {
		updateLineHeight();
		layoutChildren();
	}
	
	bUseSystemColor_ = pProfile_->getInt(L"ListWindow", L"UseSystemColor") != 0;
	if (!bUseSystemColor_) {
		struct {
			const WCHAR* pwszKey_;
			COLORREF* pcr_;
		} colors[] = {
			{ L"ForegroundColor",	&crForeground_	},
			{ L"BackgroundColor",	&crBackground_	}
		};
		for (int n = 0; n < countof(colors); ++n) {
			wstring_ptr wstr(pProfile_->getString(L"ListWindow", colors[n].pwszKey_));
			Color color(wstr.get());
			if (color.getColor() != 0xffffffff)
				*colors[n].pcr_ = color.getColor();
		}
	}
}

void qm::ListWindowImpl::updateLineHeight()
{
	ClientDeviceContext dc(pThis_->getHandle());
	ObjectSelector<HFONT> selector(dc, hfont_);
	TEXTMETRIC tm;
	dc.getTextMetrics(&tm);
	nLineHeight_ = tm.tmHeight +
		tm.tmExternalLeading + ListWindowImpl::LINE_SPACING;
}

COLORREF qm::ListWindowImpl::getColor(int nIndex) const
{
	if (!bUseSystemColor_) {
		switch (nIndex) {
		case COLOR_WINDOWTEXT:
			return crForeground_;
		case COLOR_WINDOW:
			return crBackground_;
		default:
			break;
		}
	}
	return ::GetSysColor(nIndex);
}

#ifdef QMTOOLTIP
void qm::ListWindowImpl::relayToolTipEvent(UINT uMsg,
										   WPARAM wParam,
										   LPARAM lParam)
{
	POINT pt = {
		GET_X_LPARAM(lParam),
		GET_Y_LPARAM(lParam)
	};
	
	Window toolTip(hwndToolTip_);
	
	wstring_ptr wstrText;
	bool bUpdate = false;
	
	ViewModel* pViewModel = pViewModelManager_->getCurrentViewModel();
	if (pViewModel) {
		Lock<ViewModel> lock(*pViewModel);
		unsigned int nLine = getLineFromPoint(pt);
		unsigned int nColumn = getColumnFromPoint(pt);
		if (nLine != nToolTipLine_ || nColumn != nToolTipColumn_) {
			nToolTipIndent_ = 0;
			if (nLine != -1 && nColumn != -1) {
				const ViewColumn& column = pViewModel->getColumn(nColumn);
				const ViewModelItem* pItem = pViewModel->getItem(nLine);
				if (!column.isFlag(ViewColumn::FLAG_ICON)) {
					wstring_ptr wstr(column.getText(pViewModel, pItem));
					
					HFONT hfont = pItem->isBold() ? hfontBold_ : hfont_;
					ClientDeviceContext dc(pThis_->getHandle());
					ObjectSelector<HFONT> selector(dc, hfont);
					SIZE size;
					dc.getTextExtent(wstr.get(), static_cast<int>(wcslen(wstr.get())), &size);
					
					int nIndent = 0;
					if ((pViewModel->getSort() & ViewModel::SORT_THREAD_MASK) == ViewModel::SORT_THREAD &&
						column.isFlag(ViewColumn::FLAG_INDENT))
						nIndent = pItem->getLevel()*INDENT;
					int nWidth = size.cx + HORIZONTAL_BORDER*2 + nIndent;
					
					if (nWidth > static_cast<int>(column.getWidth())) {
						wstrText = wstr;
						
						COLORREF crText = pItem->getForeground();
						if (crText == 0xff000000)
							crText = getColor(COLOR_WINDOWTEXT);
						toolTip.sendMessage(TTM_SETTIPTEXTCOLOR, crText);
						toolTip.setFont(hfont);
						
						nToolTipIndent_ = nIndent;
					}
				}
			}
			nToolTipLine_ = nLine;
			nToolTipColumn_ = nColumn;
			bUpdate = true;
		}
	}
	else {
		bUpdate = nToolTipLine_ != -1 || nToolTipColumn_ != -1;
		nToolTipLine_ = -1;
		nToolTipColumn_ = -1;
		nToolTipIndent_ = 0;
	}
	
	if (bUpdate) {
		toolTip.sendMessage(TTM_POP);
		toolTip.showWindow(SW_HIDE);
		
		W2T(wstrText.get(), ptszText);
		TOOLINFO ti = {
			sizeof(ti),
			0,
			pThis_->getHandle(),
			ID_TOOLTIP,
			{ 0, 0, 0, 0 },
			0,
			const_cast<LPTSTR>(ptszText)
		};
		toolTip.sendMessage(TTM_UPDATETIPTEXT,
			0, reinterpret_cast<LPARAM>(&ti));
	}
	
	MSG msg = {
		pThis_->getHandle(),
		uMsg,
		wParam,
		lParam
	};
	toolTip.sendMessage(TTM_RELAYEVENT, 0, reinterpret_cast<LPARAM>(&msg));
}
#endif

void qm::ListWindowImpl::hideToolTip()
{
	Window(hwndToolTip_).sendMessage(TTM_POP);
	nToolTipLine_ = -1;
	nToolTipColumn_ = -1;
	nToolTipIndent_ = 0;
}

void qm::ListWindowImpl::postRefreshMessage(UINT uMsg,
											unsigned int nItem)
{
	if (InterlockedCompareExchange(UNVOLATILE(LONG*)(&nRefreshing_), 1, 0))
		return;
	if (!pThis_->postMessage(uMsg, nItem, 0))
		InterlockedExchange(UNVOLATILE(LONG*)(&nRefreshing_), 0);
}

void qm::ListWindowImpl::handleRefreshMessage()
{
	InterlockedExchange(UNVOLATILE(LONG*)(&nRefreshing_), 0);
	pThis_->refresh();
}

void qm::ListWindowImpl::postInvalidateMessage(UINT uMsg,
											   unsigned int nItem)
{
	if (InterlockedIncrement(UNVOLATILE(LONG*)(&nInvalidating_)) > 10) {
		InterlockedDecrement(UNVOLATILE(LONG*)(&nInvalidating_));
		return;
	}
	if (!pThis_->postMessage(uMsg, nItem, 0))
		InterlockedDecrement(UNVOLATILE(LONG*)(&nInvalidating_));
}

void qm::ListWindowImpl::handleInvalidateMessage(unsigned int nItem)
{
	if (InterlockedDecrement(UNVOLATILE(LONG*)(&nInvalidating_)) >= 9)<commit_msg>Fix Windows CE versions don't compile.<commit_after>/*
 * $Id$
 *
 * Copyright(C) 1998-2006 <NAME>
 * All rights reserved.
 *
 */

#pragma warning(disable:4786)

#include <qmaccount.h>
#include <qmapplication.h>
#include <qmfilenames.h>
#include <qmfolder.h>
#include <qmlistwindow.h>
#include <qmmacro.h>
#include <qmmessageholder.h>

#include <qsaccelerator.h>
#include <qsconv.h>
#include <qsdevicecontext.h>
#include <qsdragdrop.h>
#include <qsmenu.h>
#include <qsosutil.h>
#include <qsprofile.h>
#include <qsstl.h>
#include <qsstream.h>
#include <qsuiutil.h>

#include <algorithm>
#include <vector>

#include "listwindow.h"
#include "messageframewindow.h"
#include "resourceinc.h"
#include "syncdialog.h"
#include "syncutil.h"
#include "uimanager.h"
#include "../action/action.h"
#include "../model/dataobject.h"
#include "../uimodel/viewmodel.h"

using namespace qm;
using namespace qs;


/****************************************************************************
 *
 * ListWindowImpl
 *
 */

struct qm::ListWindowImpl :
	public NotifyHandler,
	public ViewModelManagerHandler,
	public ViewModelHandler,
	public DragGestureHandler,
	public DragSourceHandler,
	public DropTargetHandler
{
public:
	enum {
		ID_HEADERCOLUMN		= 1000,
#ifdef QMTOOLTIP
		ID_TOOLTIP			= 1001
#endif
	};
	enum {
		LINE_SPACING		= 4,
		HORIZONTAL_BORDER	= 2,
		INDENT				= 15
	};
	enum {
		WM_VIEWMODEL_ITEMADDED		= WM_APP + 1101,
		WM_VIEWMODEL_ITEMREMOVED	= WM_APP + 1102,
		WM_VIEWMODEL_ITEMCHANGED	= WM_APP + 1103
	};

public:
	bool createHeaderColumn();
	void layoutChildren();
	void layoutChildren(int cx, int cy);
	
	void getRect(RECT* pRect);
	void paintMessage(const PaintInfo& pi);
	void invalidateLine(unsigned int nLine);
	void invalidateSelected();
	void ensureVisible(unsigned int nLine);
	void scrollVertical(int nPos);
	void scrollHorizontal(int nPos);
	void updateScrollBar(bool bVertical);
	unsigned int getLineFromPoint(const POINT& pt) const;
	unsigned int getColumnFromPoint(const POINT& pt) const;
	void getRect(unsigned int nLine,
				 unsigned int nColumn,
				 RECT* pRect);
	void reloadProfiles(bool bInitialize);
	void updateLineHeight();
	COLORREF getColor(int nIndex) const;
#ifdef QMTOOLTIP
	void relayToolTipEvent(UINT uMsg,
						   WPARAM wParam,
						   LPARAM lParam);
	void hideToolTip();
#endif
	
	void postRefreshMessage(UINT uMsg,
							unsigned int nItem);
	void handleRefreshMessage();
	void postInvalidateMessage(UINT uMsg,
							   unsigned int nItem);
	void handleInvalidateMessage(unsigned int nItem);

public:
	virtual LRESULT onNotify(NMHDR* pnmhdr,
							 bool* pbHandled);

public:
	virtual void viewModelSelected(const ViewModelManagerEvent& event);

public:
	virtual void itemAdded(const ViewModelEvent& event);
	virtual void itemRemoved(const ViewModelEvent& event);
	virtual void itemChanged(const ViewModelEvent& event);
	virtual void itemStateChanged(const ViewModelEvent& event);
	virtual void itemAttentionPaid(const ViewModelEvent& event);
	virtual void updated(const ViewModelEvent& event);
	virtual void sorted(const ViewModelEvent& event);
	virtual void colorChanged(const ViewModelEvent& event);
	virtual void columnChanged(const ViewModelEvent& event);
	virtual void destroyed(const ViewModelEvent& event);

public:
	virtual void dragGestureRecognized(const DragGestureEvent& event);

public:
	virtual void dragDropEnd(const DragSourceDropEvent& event);

public:
	virtual void dragEnter(const DropTargetDragEvent& event);
	virtual void dragOver(const DropTargetDragEvent& event);
	virtual void dragExit(const DropTargetEvent& event);
	virtual void drop(const DropTargetDropEvent& event);

private:
#ifdef QMTOOLTIP
	LRESULT onShow(NMHDR* pnmhdr,
				   bool* pbHandled);
#endif

private:
	HIMAGELIST createDragImage(const POINT& ptCursor,
							   POINT* pptHotspot);

private:
	static int getMessageImage(MessageHolder* pmh,
							   unsigned int nFlags);

public:
	ListWindow* pThis_;
	
	Profile* pProfile_;
	MessageFrameWindowManager* pMessageFrameWindowManager_;
	MenuManager* pMenuManager_;
	std::auto_ptr<Accelerator> pAccelerator_;
	AccountManager* pAccountManager_;
	ViewModelManager* pViewModelManager_;
	SyncManager* pSyncManager_;
	SyncDialogManager* pSyncDialogManager_;
	
	HFONT hfont_;
	HFONT hfontBold_;
	bool bUseSystemColor_;
	COLORREF crForeground_;
	COLORREF crBackground_;
	bool bSingleClickOpen_;
	bool bEllipsis_;
	int nLineHeight_;
	ListHeaderColumn* pHeaderColumn_;
	HIMAGELIST hImageList_;
	HIMAGELIST hImageListData_;
	HPEN hpenThreadLine_;
	HPEN hpenFocusedThreadLine_;
	
#ifdef QMTOOLTIP
	HWND hwndToolTip_;
	unsigned int nToolTipLine_;
	unsigned int nToolTipColumn_;
	int nToolTipIndent_;
#endif
	
	std::auto_ptr<DragGestureRecognizer> pDragGestureRecognizer_;
	std::auto_ptr<DropTarget> pDropTarget_;
	bool bCanDrop_;
	
	volatile LONG nRefreshing_;
	volatile LONG nInvalidating_;
};

bool qm::ListWindowImpl::createHeaderColumn()
{
	std::auto_ptr<ListHeaderColumn> pHeaderColumn(
		new ListHeaderColumn(pThis_, pProfile_));
	
	RECT rect;
	pThis_->getClientRect(&rect);
	if (!pHeaderColumn->create(L"QmListHeaderColumn", 0,
		WS_CHILD, 0, 0, rect.right - rect.left, 0,
		pThis_->getHandle(), 0, 0, ID_HEADERCOLUMN, 0))
		return false;
	pHeaderColumn_ = pHeaderColumn.release();
	pHeaderColumn_->setFont(hfont_, false);
	
	return true;
}

void qm::ListWindowImpl::layoutChildren()
{
	RECT rect;
	pThis_->getClientRect(&rect);
	layoutChildren(rect.right - rect.left, rect.bottom - rect.top);
}

void qm::ListWindowImpl::layoutChildren(int cx,
										int cy)
{
	if (pHeaderColumn_)
		pHeaderColumn_->setWindowPos(0, 0, 0, cx + pThis_->getScrollPos(SB_HORZ),
			pHeaderColumn_->getPreferredHeight(cx, cy), SWP_NOMOVE | SWP_NOZORDER);
	updateScrollBar(true);
	updateScrollBar(false);
	pThis_->invalidate();
}

void qm::ListWindowImpl::getRect(RECT* pRect)
{
	assert(pRect);
	
	pThis_->getClientRect(pRect);
	pRect->top += pHeaderColumn_->getHeight();
}

void qm::ListWindowImpl::paintMessage(const PaintInfo& pi)
{
	DeviceContext* pdc = pi.getDeviceContext();
	ViewModel* pViewModel = pi.getViewModel();
	const ViewModelItem* pItem = pi.getItem();
	MessageHolder* pmh = pItem->getMessageHolder();
	const RECT& rect = pi.getRect();
	
	bool bHasFocus = pThis_->hasFocus();
	bool bSelected = (pItem->getFlags() & ViewModelItem::FLAG_SELECTED) != 0;
	
	COLORREF crBackground = pItem->getBackground();
	if (crBackground == 0xff000000)
		crBackground = getColor(COLOR_WINDOW);
	
	if (bSelected) {
		pdc->setTextColor(getColor(bHasFocus ? COLOR_HIGHLIGHTTEXT : COLOR_WINDOWTEXT));
		pdc->setBkColor(getColor(bHasFocus ? COLOR_HIGHLIGHT : COLOR_INACTIVEBORDER));
	}
	else {
		COLORREF crText = pItem->getForeground();
		if (crText == 0xff000000)
			crText = getColor(COLOR_WINDOWTEXT);
		pdc->setTextColor(crText);
		pdc->setBkColor(crBackground);
	}
	ObjectSelector<HFONT> fontSelector(*pdc, pItem->isBold() ? hfontBold_ : 0);
	
	RECT r = { rect.left, rect.top, rect.left, rect.bottom };
#if defined _WIN32_WCE && _WIN32_WCE >= 0x300 && defined _WIN32_WCE_PSPC
	RECT rectFocus = { 0, rect.top, 0, rect.bottom };
#else
	RECT rectFocus = { -1, rect.top, 0, rect.bottom };
#endif
	
	unsigned int nLevel = 0;
	if ((pViewModel->getSort() & ViewModel::SORT_THREAD_MASK) == ViewModel::SORT_THREAD)
		nLevel = pItem->getLevel();
	
	int nThreadLeft = -1;
	int nThreadRight = -1;
	const ViewColumnList& listColumn = pViewModel->getColumns();
	for (ViewColumnList::const_iterator it = listColumn.begin(); it != listColumn.end(); ++it) {
		ViewColumn* pColumn = *it;
		
		r.right += pColumn->getWidth();
		
		int nOffset = 0;
		if (pColumn->isFlag(ViewColumn::FLAG_INDENT) &&
			((pViewModel->getSort() & ViewModel::SORT_THREAD_MASK) == ViewModel::SORT_THREAD)) {
			nOffset = nLevel*INDENT;
			if (pColumn->isFlag(ViewColumn::FLAG_LINE)) {
				nThreadLeft = r.left;
				nThreadRight = r.right;
			}
		}
		
		ViewColumn::Type type = pColumn->getType();
		if (!pColumn->isFlag(ViewColumn::FLAG_ICON)) {
			wstring_ptr wstrText(pColumn->getText(pViewModel, pItem));
			size_t nLen = wcslen(wstrText.get());
			POINT pt = { r.left + HORIZONTAL_BORDER, r.top + LINE_SPACING/2 };
			if (pColumn->isFlag(ViewColumn::FLAG_RIGHTALIGN)) {
				SIZE size;
				pdc->getTextExtent(wstrText.get(), static_cast<int>(nLen), &size);
				int nX = r.right - size.cx - HORIZONTAL_BORDER;
				if (nX > pt.x)
					pt.x = nX;
			}
			else {
				pt.x += nOffset;
			}
			
			if (bEllipsis_)
				pdc->extTextOutEllipsis(pt.x, pt.y, r.right - pt.x - HORIZONTAL_BORDER,
					ETO_CLIPPED | ETO_OPAQUE, r, wstrText.get(), static_cast<UINT>(nLen));
			else
				pdc->extTextOut(pt.x, pt.y, ETO_CLIPPED | ETO_OPAQUE,
					r, wstrText.get(), static_cast<UINT>(nLen), 0);
			
			if (rectFocus.left == -1)
				rectFocus.left = r.left;
			rectFocus.right = r.right;
		}
		else {
			unsigned int nValue = pColumn->getNumber(pViewModel, pItem);
			
#if defined _WIN32_WCE && _WIN32_WCE >= 0x300 && defined _WIN32_WCE_PSPC
			COLORREF crBk = bSelected ? 0xff000000 : pItem->getBackground();
#else
			COLORREF crBk = pItem->getBackground();
#endif
			if (crBk == 0xff000000) {
#if defined _WIN32_WCE && _WIN32_WCE >= 0x300 && defined _WIN32_WCE_PSPC
				UINT nBkColorId = bSelected ? pThis_->hasFocus() ?
					COLOR_HIGHLIGHT : COLOR_INACTIVEBORDER : COLOR_WINDOW;
#else
				UINT nBkColorId = COLOR_WINDOW;
#endif
				crBk = getColor(nBkColorId);
			}
			COLORREF crBkOld = pdc->getBkColor();
			pdc->fillSolidRect(r, crBk);
			pdc->setBkColor(crBkOld);
			
#if defined _WIN32_WCE && _WIN32_WCE >= 0x300 && defined _WIN32_WCE_PSPC
			UINT nFlags = bSelected ? ILD_TRANSPARENT : ILD_NORMAL;
#else
			UINT nFlags = bSelected ? ILD_SELECTED : ILD_NORMAL;
#endif
			int y = r.top + (r.bottom - r.top - 16)/2;
			ImageList_Draw(hImageList_, getMessageImage(pmh, nValue),
				pdc->getHandle(), r.left + 7, y, nFlags);
			if (nValue & MessageHolder::FLAG_MULTIPART)
				ImageList_Draw(hImageListData_, 0, pdc->getHandle(),
					r.left, y, nFlags);
			if (nValue & MessageHolder::FLAG_REPLIED)
				ImageList_Draw(hImageListData_, 1, pdc->getHandle(),
					r.left + 15, y, nFlags);
			else if (nValue & MessageHolder::FLAG_FORWARDED)
				ImageList_Draw(hImageListData_, 2, pdc->getHandle(),
					r.left + 15, y, nFlags);
			if (nValue & MessageHolder::FLAG_DOWNLOAD ||
				nValue & MessageHolder::FLAG_DOWNLOADTEXT)
				ImageList_Draw(hImageListData_, 3, pdc->getHandle(),
					r.left + 7, y, nFlags);
			if (nValue & MessageHolder::FLAG_DELETED)
				ImageList_Draw(hImageListData_, 4, pdc->getHandle(),
					r.left + 7, y, nFlags);
			if (nValue & MessageHolder::FLAG_MARKED)
				ImageList_Draw(hImageListData_, 5, pdc->getHandle(),
					r.left + 15, y, nFlags);
			
#if defined _WIN32_WCE && _WIN32_WCE >= 0x300 && defined _WIN32_WCE_PSPC
			rectFocus.right = r.right;
#endif
		}
		
		r.left = r.right;
	}
	r.right = rect.right;
	pdc->fillSolidRect(r, crBackground);
	
	if (((pViewModel->getSort() & ViewModel::SORT_THREAD_MASK) == ViewModel::SORT_THREAD) &&
		nThreadLeft != -1) {
		if (nLevel > 0) {
			typedef std::vector<int> Line;
			Line line(nLevel, 0);
			for (unsigned int n = pi.getIndex() + 1; n < pViewModel->getCount(); ++n) {
				const ViewModelItem* pNextItem = pViewModel->getItem(n);
				unsigned int nNextLevel = pNextItem->getLevel();
				if (nNextLevel == 0) {
					break;
				}
				else if (nNextLevel <= nLevel) {
					line[nNextLevel - 1] = 1;
					nLevel = nNextLevel;
				}
			}
			
			HPEN hpen = (bSelected && bHasFocus) ?
				hpenFocusedThreadLine_ : hpenThreadLine_;
			ObjectSelector<HPEN> selector(*pdc, hpen);
			int nOffset = 7;
			POINT points[4];
			for (Line::iterator it = line.begin(); it != line.end(); ++it) {
				int nPoint = 2;
				if (*it || it + 1 == line.end()) {
					points[0].x = nThreadLeft + nOffset;
					points[0].y = rect.top;
					points[1].x = points[0].x;
					points[1].y = *it ? rect.bottom : (rect.top + rect.bottom)/2;
					if (it + 1 == line.end()) {
						if (*it) {
							points[nPoint].x = points[1].x;
							points[nPoint].y = (rect.top + rect.bottom)/2;
							++nPoint;
						}
						points[nPoint].x = points[nPoint - 1].x + 5;
						points[nPoint].y = points[nPoint - 1].y;
						++nPoint;
					}
					pdc->polyline(points, nPoint);
				}
				nOffset += INDENT;
				if (nThreadLeft + nOffset > nThreadRight)
					break;
			}
		}
	}
	
#if !defined _WIN32_WCE || _WIN32_WCE < 0x300 || !defined _WIN32_WCE_PSPC
	if ((pItem->getFlags() & ViewModelItem::FLAG_FOCUSED) && bHasFocus) {
		pdc->setTextColor(COLOR_WINDOWTEXT);
		pdc->drawFocusRect(rectFocus);
	}
#endif
}

void qm::ListWindowImpl::invalidateLine(unsigned int nLine)
{
	RECT rect;
	getRect(&rect);
	
	int nPos = pThis_->getScrollPos(SB_VERT);
	if (static_cast<int>(nLine) < nPos ||
		nPos + (rect.bottom - rect.top)/nLineHeight_ + 1 < static_cast<int>(nLine))
		return;
	rect.top += (nLine - nPos)*nLineHeight_;
	rect.bottom = rect.top + nLineHeight_;
	
	pThis_->invalidateRect(rect);
}

void qm::ListWindowImpl::invalidateSelected()
{
	ViewModel* pViewModel = pViewModelManager_->getCurrentViewModel();
	if (pViewModel) {
		Lock<ViewModel> lock(*pViewModel);
		
		RECT rect;
		getRect(&rect);
		int nTop = rect.top;
		
		SCROLLINFO si = {
			sizeof(si),
			SIF_PAGE | SIF_POS
		};
		pThis_->getScrollInfo(SB_VERT, &si);
		
		unsigned int nCount = pViewModel->getCount();
		unsigned int nFocused = pViewModel->getFocused();
		for (unsigned int n = si.nPos; n < nCount && n <= si.nPos + si.nPage; ++n) {
			if (pViewModel->isSelected(n) || n == nFocused) {
				rect.top = nTop + (n - si.nPos)*nLineHeight_;
				rect.bottom = rect.top + nLineHeight_;
				pThis_->invalidateRect(rect);
			}
		}
	}
}

void qm::ListWindowImpl::ensureVisible(unsigned int nLine)
{
	SCROLLINFO si = {
		sizeof(si),
		SIF_POS | SIF_PAGE | SIF_RANGE
	};
	pThis_->getScrollInfo(SB_VERT, &si);
	if (si.nMax - si.nMin < static_cast<int>(si.nPage))
		return;
	
	if (nLine < static_cast<unsigned int>(si.nPos))
		scrollVertical(nLine);
	else if (nLine >= static_cast<unsigned int>(si.nPos + si.nPage) - 1)
		scrollVertical(nLine + 1 - si.nPage);
}

void qm::ListWindowImpl::scrollVertical(int nPos)
{
	SCROLLINFO si = {
		sizeof(si),
		SIF_RANGE | SIF_POS | SIF_PAGE
	};
	pThis_->getScrollInfo(SB_VERT, &si);
	
	if (nPos < 0)
		nPos = 0;
	else if (nPos > si.nMax - static_cast<int>(si.nPage) + 1)
		nPos = si.nMax - si.nPage + 1;
	
	if (nPos == si.nPos)
		return;
	
	RECT rect;
	getRect(&rect);
	
	int nOldPos = si.nPos;
	pThis_->setScrollPos(SB_VERT, nPos);
	pThis_->scrollWindow(0, (nOldPos - nPos)*nLineHeight_,
		&rect, &rect, 0, 0, SW_INVALIDATE);
	
#ifdef QMTOOLTIP
	hideToolTip();
#endif
}

void qm::ListWindowImpl::scrollHorizontal(int nPos)
{
	SCROLLINFO si = {
		sizeof(si),
		SIF_RANGE | SIF_POS | SIF_PAGE
	};
	pThis_->getScrollInfo(SB_HORZ, &si);
	
	if (nPos < 0)
		nPos = 0;
	else if (nPos > si.nMax - static_cast<int>(si.nPage) + 1)
		nPos = si.nMax - si.nPage;
	
	if (nPos == si.nPos)
		return;
	
	RECT rect;
	pThis_->getClientRect(&rect);
	
	int nOldPos = si.nPos;
	pThis_->setScrollPos(SB_HORZ, nPos);
	pThis_->scrollWindow(nOldPos - nPos, 0,
		&rect, &rect, 0, 0, SW_INVALIDATE/* | SW_SCROLLCHILDREN*/);
	
	pHeaderColumn_->setWindowPos(0, -nPos, 0,
		(rect.right - rect.left) + nPos, pHeaderColumn_->getHeight(),
		SWP_NOZORDER | SWP_NOACTIVATE);
	
#ifdef QMTOOLTIP
	hideToolTip();
#endif
}

void qm::ListWindowImpl::updateScrollBar(bool bVertical)
{
	RECT rect;
	getRect(&rect);
	
	if (bVertical) {
		ViewModel* pViewModel = pViewModelManager_->getCurrentViewModel();
		unsigned int nCount = 0;
		if (pViewModel) {
			Lock<ViewModel> lock(*pViewModel);
			nCount = pViewModel->getCount();
		}
		
		unsigned int nPage = (rect.bottom - rect.top)/nLineHeight_;
		if (nPage > nCount)
			nPage = nCount;
		SCROLLINFO si = {
			sizeof(si),
			SIF_PAGE | SIF_RANGE,
			0,
			nCount == 0 ? 0 : nCount - 1,
			nPage,
			0,
		};
		pThis_->setScrollInfo(SB_VERT, si);
	}
	else {
		unsigned int nWidth = pHeaderColumn_->getWidth();
		SCROLLINFO si = {
			sizeof(si),
			SIF_PAGE | SIF_RANGE,
			0,
			nWidth,
			rect.right - rect.left,
			0,
		};
		pThis_->setScrollInfo(SB_HORZ, si);
		
		pHeaderColumn_->setWindowPos(0, -pThis_->getScrollPos(SB_HORZ),
			0, 0, 0, SWP_NOZORDER | SWP_NOSIZE | SWP_NOACTIVATE);
	}
}

unsigned int qm::ListWindowImpl::getLineFromPoint(const POINT& pt) const
{
	ViewModel* pViewModel = pViewModelManager_->getCurrentViewModel();
	assert(pViewModel);
	assert(pViewModel->isLocked());
	
	unsigned int nLine = (pt.y - pHeaderColumn_->getHeight())/
		nLineHeight_ + pThis_->getScrollPos(SB_VERT);
	return nLine < pViewModel->getCount() ? nLine : -1;
}

unsigned int qm::ListWindowImpl::getColumnFromPoint(const POINT& pt) const
{
	ViewModel* pViewModel = pViewModelManager_->getCurrentViewModel();
	assert(pViewModel);
	
	int nX = pt.x + pThis_->getScrollPos(SB_HORZ);
	
	const ViewColumnList& listColumn = pViewModel->getColumns();
	for (unsigned int n = 0; n < listColumn.size(); ++n) {
		int nWidth = listColumn[n]->getWidth();
		if (nX < nWidth)
			return n;
		nX -= nWidth;
	}
	return -1;
}

void qm::ListWindowImpl::getRect(unsigned int nLine,
								 unsigned int nColumn,
								 RECT* pRect)
{
	assert(pRect);
	
	ViewModel* pViewModel = pViewModelManager_->getCurrentViewModel();
	assert(pViewModel);
	
	pRect->top = pHeaderColumn_->getHeight() +
		(nLine - pThis_->getScrollPos(SB_VERT))*nLineHeight_;
	pRect->bottom = pRect->top + nLineHeight_;
	
	pRect->left = -pThis_->getScrollPos(SB_HORZ);
	for (unsigned int n = 0; n < nColumn; ++n)
		pRect->left += pViewModel->getColumn(n).getWidth();
	pRect->right = pRect->left + pViewModel->getColumn(nColumn).getWidth();
}

void qm::ListWindowImpl::reloadProfiles(bool bInitialize)
{
	bSingleClickOpen_ = pProfile_->getInt(L"ListWindow", L"SingleClickOpen") != 0;
	bEllipsis_ = pProfile_->getInt(L"ListWindow", L"Ellipsis") != 0;
	
	HFONT hfont = qs::UIUtil::createFontFromProfile(pProfile_,
		L"ListWindow", qs::UIUtil::DEFAULTFONT_UI);
	if (!bInitialize) {
		assert(hfont_);
		pHeaderColumn_->setFont(hfont);
		::DeleteObject(hfont_);
	}
	hfont_ = hfont;
	
	LOGFONT lf;
	::GetObject(hfont, sizeof(lf), &lf);
	lf.lfWeight = FW_BOLD;
	HFONT hfontBold = ::CreateFontIndirect(&lf);
	if (!bInitialize) {
		assert(hfontBold_);
		::DeleteObject(hfontBold_);
	}
	hfontBold_ = hfontBold;
	
	if (!bInitialize) {
		updateLineHeight();
		layoutChildren();
	}
	
	bUseSystemColor_ = pProfile_->getInt(L"ListWindow", L"UseSystemColor") != 0;
	if (!bUseSystemColor_) {
		struct {
			const WCHAR* pwszKey_;
			COLORREF* pcr_;
		} colors[] = {
			{ L"ForegroundColor",	&crForeground_	},
			{ L"BackgroundColor",	&crBackground_	}
		};
		for (int n = 0; n < countof(colors); ++n) {
			wstring_ptr wstr(pProfile_->getString(L"ListWindow", colors[n].pwszKey_));
			Color color(wstr.get());
			if (color.getColor() != 0xffffffff)
				*colors[n].pcr_ = color.getColor();
		}
	}
}

void qm::ListWindowImpl::updateLineHeight()
{
	ClientDeviceContext dc(pThis_->getHandle());
	ObjectSelector<HFONT> selector(dc, hfont_);
	TEXTMETRIC tm;
	dc.getTextMetrics(&tm);
	nLineHeight_ = tm.tmHeight +
		tm.tmExternalLeading + ListWindowImpl::LINE_SPACING;
}

COLORREF qm::ListWindowImpl::getColor(int nIndex) const
{
	if (!bUseSystemColor_) {
		switch (nIndex) {
		case COLOR_WINDOWTEXT:
			return crForeground_;
		case COLOR_WINDOW:
			return crBackground_;
		default:
			break;
		}
	}
	return ::GetSysColor(nIndex);
}

#ifdef QMTOOLTIP
void qm::ListWindowImpl::relayToolTipEvent(UINT uMsg,
										   WPARAM wParam,
										   LPARAM lParam)
{
	POINT pt = {
		GET_X_LPARAM(lParam),
		GET_Y_LPARAM(lParam)
	};
	
	Window toolTip(hwndToolTip_);
	
	wstring_ptr wstrText;
	bool bUpdate = false;
	
	ViewModel* pViewModel = pViewModelManager_->getCurrentViewModel();
	if (pViewModel) {
		Lock<ViewModel> lock(*pViewModel);
		unsigned int nLine = getLineFromPoint(pt);
		unsigned int nColumn = getColumnFromPoint(pt);
		if (nLine != nToolTipLine_ || nColumn != nToolTipColumn_) {
			nToolTipIndent_ = 0;
			if (nLine != -1 && nColumn != -1) {
				const ViewColumn& column = pViewModel->getColumn(nColumn);
				const ViewModelItem* pItem = pViewModel->getItem(nLine);
				if (!column.isFlag(ViewColumn::FLAG_ICON)) {
					wstring_ptr wstr(column.getText(pViewModel, pItem));
					
					HFONT hfont = pItem->isBold() ? hfontBold_ : hfont_;
					ClientDeviceContext dc(pThis_->getHandle());
					ObjectSelector<HFONT> selector(dc, hfont);
					SIZE size;
					dc.getTextExtent(wstr.get(), static_cast<int>(wcslen(wstr.get())), &size);
					
					int nIndent = 0;
					if ((pViewModel->getSort() & ViewModel::SORT_THREAD_MASK) == ViewModel::SORT_THREAD &&
						column.isFlag(ViewColumn::FLAG_INDENT))
						nIndent = pItem->getLevel()*INDENT;
					int nWidth = size.cx + HORIZONTAL_BORDER*2 + nIndent;
					
					if (nWidth > static_cast<int>(column.getWidth())) {
						wstrText = wstr;
						
						COLORREF crText = pItem->getForeground();
						if (crText == 0xff000000)
							crText = getColor(COLOR_WINDOWTEXT);
						toolTip.sendMessage(TTM_SETTIPTEXTCOLOR, crText);
						toolTip.setFont(hfont);
						
						nToolTipIndent_ = nIndent;
					}
				}
			}
			nToolTipLine_ = nLine;
			nToolTipColumn_ = nColumn;
			bUpdate = true;
		}
	}
	else {
		bUpdate = nToolTipLine_ != -1 || nToolTipColumn_ != -1;
		nToolTipLine_ = -1;
		nToolTipColumn_ = -1;
		nToolTipIndent_ = 0;
	}
	
	if (bUpdate) {
		toolTip.sendMessage(TTM_POP);
		toolTip.showWindow(SW_HIDE);
		
		W2T(wstrText.get(), ptszText);
		TOOLINFO ti = {
			sizeof(ti),
			0,
			pThis_->getHandle(),
			ID_TOOLTIP,
			{ 0, 0, 0, 0 },
			0,
			const_cast<LPTSTR>(ptszText)
		};
		toolTip.sendMessage(TTM_UPDATETIPTEXT,
			0, reinterpret_cast<LPARAM>(&ti));
	}
	
	MSG msg = {
		pThis_->getHandle(),
		uMsg,
		wParam,
		lParam
	};
	toolTip.sendMessage(TTM_RELAYEVENT, 0, reinterpret_cast<LPARAM>(&msg));
}

void qm::ListWindowImpl::hideToolTip()
{
	Window(hwndToolTip_).sendMessage(TTM_POP);
	nToolTipLine_ = -1;
	nToolTipColumn_ = -1;
	nToolTipIndent_ = 0;
}
#endif

void qm::ListWindowImpl::postRefreshMessage(UINT uMsg,
											unsigned int nItem)
{
	if (InterlockedCompareExchange(UNVOLATILE(LONG*)(&nRefreshing_), 1, 0))
		return;
	if (!pThis_->postMessage(uMsg, nItem, 0))
		InterlockedExchange(UNVOLATILE(LONG*)(&nRefreshing_), 0);
}

void qm::ListWindowImpl::handleRefreshMessage()
{
	InterlockedExchange(UNVOLATILE(LONG*)(&nRefreshing_), 0);
	pThis_->refresh();
}

void qm::ListWindowImpl::postInvalidateMessage(UINT uMsg,
											   unsigned int nItem)
{
	if (InterlockedIncrement(UNVOLATILE(LONG*)(&nInvalidating_)) > 10) {
		InterlockedDecrement(UNVOLATILE(LONG*)(&nInvalidating_));
		return;
	}
	if (!pThis_->postMessage(uMsg, nItem, 0))
		InterlockedDecrement(UNVOLATILE(LONG*)(&nInvalidating_));
}

void qm::ListWindowImpl::handleInvalidateMessage(unsigned int nItem)
{
	if (InterlockedDecrement(UNVOLATILE(LONG*)(&nInvalidating_)) >= 9)
{'fraction_non_alphanumeric': 0.13913727890859004, 'fraction_numerical': 0.011587485515643106, 'mean_word_length': 4.39832408828042, 'pattern_counts': {'":': 0, '<': 129, '<?xml version=': 0, '>': 377, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 2, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '5973288', 'n_tokens_mistral': 19346, 'n_tokens_neox': 16925, 'n_words': 3346}