diff options
| -rw-r--r-- | src/ScintillaBase.cxx | 23 | ||||
| -rw-r--r-- | src/ScintillaBase.h | 1 | ||||
| -rw-r--r-- | win32/PlatWin.cxx | 761 | ||||
| -rw-r--r-- | win32/ScintillaWin.cxx | 11 | 
4 files changed, 662 insertions, 134 deletions
| diff --git a/src/ScintillaBase.cxx b/src/ScintillaBase.cxx index 9526ab968..adf21ce6b 100644 --- a/src/ScintillaBase.cxx +++ b/src/ScintillaBase.cxx @@ -38,6 +38,7 @@  ScintillaBase::ScintillaBase() {  	displayPopupMenu = true;  	listType = 0; +	maxListWidth = 0;  #ifdef SCI_LEXER  	lexLanguage = SCLEX_CONTAINER;  	lexCurrent = 0; @@ -214,7 +215,8 @@ void ScintillaBase::AutoCompleteStart(int lenEntered, const char *list) {  			return;  		}  	} -	ac.Start(wMain, idAutoComplete, currentPos, lenEntered, vs.lineHeight, IsUnicodeMode()); +	ac.Start(wMain, idAutoComplete, currentPos, LocationFromPosition(currentPos), +				lenEntered, vs.lineHeight, IsUnicodeMode());  	PRectangle rcClient = GetClientRectangle();  	Point pt = LocationFromPosition(currentPos - lenEntered); @@ -242,7 +244,8 @@ void ScintillaBase::AutoCompleteStart(int lenEntered, const char *list) {  	rcac.bottom = Platform::Minimum(rcac.top + heightLB, rcClient.bottom);  	ac.lb->SetPositionRelative(rcac, wMain);  	ac.lb->SetFont(vs.styles[STYLE_DEFAULT].font); -	ac.lb->SetAverageCharWidth(vs.styles[STYLE_DEFAULT].aveCharWidth); +	unsigned int aveCharWidth = vs.styles[STYLE_DEFAULT].aveCharWidth; +	ac.lb->SetAverageCharWidth(aveCharWidth);  	ac.lb->SetDoubleClickAction(AutoCompleteDoubleClick, this);  	ac.SetList(list); @@ -251,6 +254,8 @@ void ScintillaBase::AutoCompleteStart(int lenEntered, const char *list) {  	PRectangle rcList = ac.lb->GetDesiredRect();  	int heightAlloced = rcList.bottom - rcList.top;  	widthLB = Platform::Maximum(widthLB, rcList.right - rcList.left); +	if (maxListWidth != 0) +		widthLB = Platform::Minimum(widthLB, aveCharWidth*maxListWidth);  	// Make an allowance for large strings in list  	rcList.left = pt.x - ac.lb->CaretFromEdge();  	rcList.right = rcList.left + widthLB; @@ -559,6 +564,20 @@ sptr_t ScintillaBase::WndProc(unsigned int iMessage, uptr_t wParam, sptr_t lPara  	case SCI_AUTOCGETDROPRESTOFWORD:  		return ac.dropRestOfWord; +	case SCI_AUTOCSETMAXHEIGHT: +		ac.lb->SetVisibleRows(wParam); +		break; + +	case SCI_AUTOCGETMAXHEIGHT: +		return ac.lb->GetVisibleRows(); + +	case SCI_AUTOCSETMAXWIDTH: +		maxListWidth = wParam; +		break; + +	case SCI_AUTOCGETMAXWIDTH: +		return maxListWidth; +  	case SCI_REGISTERIMAGE:  		ac.lb->RegisterImage(wParam, reinterpret_cast<const char *>(lParam));  		break; diff --git a/src/ScintillaBase.h b/src/ScintillaBase.h index 362231148..631e3e05d 100644 --- a/src/ScintillaBase.h +++ b/src/ScintillaBase.h @@ -38,6 +38,7 @@ protected:  	int listType;			///< 0 is an autocomplete list  	SString listSelected;	///< Receives listbox selected string +	int maxListWidth;		/// Maximum width of list, in average character widths  #ifdef SCI_LEXER  	int lexLanguage; diff --git a/win32/PlatWin.cxx b/win32/PlatWin.cxx index 6279340a4..d3de3c9ad 100644 --- a/win32/PlatWin.cxx +++ b/win32/PlatWin.cxx @@ -376,7 +376,7 @@ void SurfaceImpl::Release() {  		::SelectObject(reinterpret_cast<HDC>(hdc), fontOld);  		fontOld = 0;  	} -	font =0; +	font = 0;  	if (bitmapOld) {  		::SelectObject(reinterpret_cast<HDC>(hdc), bitmapOld);  		::DeleteObject(bitmap); @@ -858,53 +858,107 @@ void Window::SetTitle(const char *s) {  	::SetWindowText(reinterpret_cast<HWND>(id), s);  } -class LineToType { -	int *data; +struct ListItemData { +	const char *text; +	int pixId; +}; + +#define _ROUND2(n,pow2) \ +        ( ( (n) + (pow2) - 1) & ~((pow2) - 1) ) + +class LineToItem { +	char *words; +	int wordsCount; +	int wordsSize; + +	ListItemData *data;  	int len; -	int maximum; +	int count; + +private: +	void FreeWords() { +		delete []words; +		words = NULL; +		wordsCount = 0; +		wordsSize = 0; +	} +	char *AllocWord(const char *word); +  public: -	LineToType() :data(0), len(0), maximum(0) { +	LineToItem() : words(NULL), wordsCount(0), wordsSize(0), data(NULL), len(0), count(0) {  	} -	~LineToType() { +	~LineToItem() {  		Clear();  	}  	void Clear() { +		FreeWords();  		delete []data; -		data = 0; +		data = NULL;  		len = 0; -		maximum = 0; +		count = 0;  	} -	void Add(int index, int value) { -		if (index >= maximum) { -			if (index >= len) { -				int lenNew = (index+1) * 2; -				int *dataNew = new int[lenNew]; -				for (int i=0; i<maximum; i++) { -					dataNew[i] = data[i]; -				} -				delete []data; -				data = dataNew; -				len = lenNew; -			} -			while (maximum < index) { -				data[maximum] = 0; -				maximum++; -			} -		} -		data[index] = value; -		if (index == maximum) { -			maximum++; -		} -	} -	int Get(int index) { -		if (index < maximum) { + +	ListItemData *Append(const char *text, int value); + +	ListItemData Get(int index) const { +		if (index >= 0 && index < count) {  			return data[index];  		} else { -			return 0; +			ListItemData missing = {"", -1}; +			return missing;  		}  	} +	int Count() const { +		return count; +	} + +	ListItemData *AllocItem(); + +	void SetWords(char *s) { +		words = s;	// N.B. will be deleted on destruction +	}  }; +char *LineToItem::AllocWord(const char *text) { +	int chars = strlen(text) + 1; +	int newCount = wordsCount + chars; +	if (newCount > wordsSize) { +		wordsSize = _ROUND2(newCount * 2, 8192); +		char *wordsNew = new char[wordsSize]; +		memcpy(wordsNew, words, wordsCount); +		int offset = wordsNew - words; +		for (int i=0; i<count; i++) +			data[i].text += offset; +		delete []words; +		words = wordsNew; +	} +	char *s = &words[wordsCount]; +	wordsCount = newCount; +	strncpy(s, text, chars); +	return s; +} + +ListItemData *LineToItem::AllocItem() { +	if (count >= len) { +		int lenNew = _ROUND2((count+1) * 2, 1024); +		ListItemData *dataNew = new ListItemData[lenNew]; +		memcpy(dataNew, data, count * sizeof(ListItemData)); +		delete []data; +		data = dataNew; +		len = lenNew; +	} +	ListItemData *item = &data[count]; +	count++; +	return item; +} + +ListItemData *LineToItem::Append(const char *text, int imageIndex) { +	ListItemData *item = AllocItem(); +	item->text = AllocWord(text); +	item->pixId = imageIndex; +	return item; +} +  const char ListBoxX_ClassName[] = "ListBoxX";  ListBox::ListBox() { @@ -917,15 +971,51 @@ class ListBoxX : public ListBox {  	int lineHeight;  	FontID fontCopy;  	XPMSet xset; -	LineToType ltt; +	LineToItem lti;  	HWND lb;  	bool unicodeMode;  	int desiredVisibleRows;  	unsigned int maxItemCharacters;  	unsigned int aveCharWidth; +	Window *parent; +	int ctrlID; +	CallBackAction doubleClickAction; +	void *doubleClickActionData; +	const char *widestItem; +	unsigned int maxCharWidth; +	int resizeHit; +	PRectangle rcPreSize; +	Point dragOffset; +	Point location;	// Caret location at which the list is opened + +	HWND GetHWND() const; +	void AppendListItem(const char *startword, const char *numword); +	void AdjustWindowRect(PRectangle *rc) const; +	int ItemHeight() const; +	int MinClientWidth() const; +	int TextOffset() const; +	Point GetClientExtent() const; +	Point MinTrackSize() const; +	Point MaxTrackSize() const; +	void SetRedraw(bool on); +	void OnDoubleClick(); +	void ResizeToCursor(); +	void StartResize(WPARAM); +	int NcHitTest(WPARAM, LPARAM) const; +	void CentreItem(int); +	void Paint(HDC); +	void Erase(HDC); +	static long PASCAL ControlWndProc(HWND hWnd, UINT iMessage, WPARAM wParam, LPARAM lParam); + +	static const Point ItemInset;	// Padding around whole item +	static const Point TextInset;	// Padding around text +	static const Point ImageInset;	// Padding around image +  public:  	ListBoxX() : lineHeight(10), fontCopy(0), lb(0), unicodeMode(false), -		desiredVisibleRows(5), maxItemCharacters(0), aveCharWidth(8){ +		desiredVisibleRows(5), maxItemCharacters(0), aveCharWidth(8), +		parent(NULL), ctrlID(0), doubleClickAction(NULL), doubleClickActionData(NULL), +		widestItem(NULL), maxCharWidth(1), resizeHit(0) {  	}  	virtual ~ListBoxX() {  		if (fontCopy) { @@ -934,11 +1024,11 @@ public:  		}  	}  	virtual void SetFont(Font &font); -	virtual void Create(Window &parent, int ctrlID, int lineHeight_, bool unicodeMode_); +	virtual void Create(Window &parent, int ctrlID, Point location_, int lineHeight_, bool unicodeMode_);  	virtual void SetAverageCharWidth(int width);  	virtual void SetVisibleRows(int rows); +	virtual int GetVisibleRows() const;  	virtual PRectangle GetDesiredRect(); -	int IconWidth();  	virtual int CaretFromEdge();  	virtual void Clear();  	virtual void Append(char *s, int type = -1); @@ -949,30 +1039,43 @@ public:  	virtual void GetValue(int n, char *value, int len);  	virtual void RegisterImage(int type, const char *xpm_data);  	virtual void ClearRegisteredImages(); -	virtual void SetDoubleClickAction(CallBackAction, void *) { +	virtual void SetDoubleClickAction(CallBackAction action, void *data) { +		doubleClickAction = action; +		doubleClickActionData = data;  	} +	virtual void SetList(const char *list, char separator, char typesep);  	void Draw(DRAWITEMSTRUCT *pDrawItem);  	long WndProc(HWND hWnd, UINT iMessage, WPARAM wParam, LPARAM lParam);  	static long PASCAL StaticWndProc(HWND hWnd, UINT iMessage, WPARAM wParam, LPARAM lParam);  }; +const Point ListBoxX::ItemInset(0, 0); +const Point ListBoxX::TextInset(2, 0); +const Point ListBoxX::ImageInset(1, 0); +  ListBox *ListBox::Allocate() {  	ListBoxX *lb = new ListBoxX();  	return lb;  } -void ListBoxX::Create(Window &parent, int ctrlID, int lineHeight_, bool unicodeMode_) { +void ListBoxX::Create(Window &parent_, int ctrlID_, Point location_, int lineHeight_, bool unicodeMode_) { +	parent = &parent_; +	ctrlID = ctrlID_; +	location = location_;  	lineHeight = lineHeight_;  	unicodeMode = unicodeMode_; -	HINSTANCE hinstanceParent = GetWindowInstance(reinterpret_cast<HWND>(parent.GetID())); +	HWND hwndParent = reinterpret_cast<HWND>(parent->GetID()); +	HINSTANCE hinstanceParent = GetWindowInstance(hwndParent); +	// Window created as popup so not clipped within parent client area  	id = ::CreateWindowEx(  		WS_EX_WINDOWEDGE, ListBoxX_ClassName, TEXT(""), -		WS_CHILD | WS_THICKFRAME, -		100,100, 150,80, reinterpret_cast<HWND>(parent.GetID()), -		reinterpret_cast<HMENU>(ctrlID), +		WS_POPUP | WS_THICKFRAME, +		100,100, 150,80, hwndParent, +		NULL,  		hinstanceParent,  		this); -	lb = ::GetWindow(reinterpret_cast<HWND>(id), GW_CHILD); + +	::MapWindowPoints(hwndParent, NULL, reinterpret_cast<POINT*>(&location), 1);  }  void ListBoxX::SetFont(Font &font) { @@ -995,79 +1098,108 @@ void ListBoxX::SetVisibleRows(int rows) {  	desiredVisibleRows = rows;  } +int ListBoxX::GetVisibleRows() const { +	return desiredVisibleRows; +} + +HWND ListBoxX::GetHWND() const { +	return reinterpret_cast<HWND>(GetID()); +} +  PRectangle ListBoxX::GetDesiredRect() {  	PRectangle rcDesired = GetPosition(); -	int itemHeight = ::SendMessage(lb, LB_GETITEMHEIGHT, 0, 0); +  	int rows = Length();  	if ((rows == 0) || (rows > desiredVisibleRows))  		rows = desiredVisibleRows; -	// The +6 allows for borders -	rcDesired.bottom = rcDesired.top + 6 + itemHeight * rows; -	int width = maxItemCharacters; -	if (width < 12) -		width = 12; -	rcDesired.right = rcDesired.left + width * (aveCharWidth+aveCharWidth/3); +	rcDesired.bottom = rcDesired.top + ItemHeight() * rows; + +	int width = MinClientWidth(); +	HDC hdc = ::GetDC(lb); +	HFONT oldFont = SelectFont(hdc, fontCopy); +	SIZE textSize = {0, 0}; +	int len = strlen(widestItem); +	if (unicodeMode) { +		wchar_t tbuf[MAX_US_LEN]; +		int tlen = UCS2FromUTF8(widestItem, len, tbuf, sizeof(tbuf)/sizeof(wchar_t)-1); +		tbuf[tlen] = L'\0'; +		::GetTextExtentPoint32W(hdc, tbuf, tlen, &textSize); +	} else { +		::GetTextExtentPoint32(hdc, widestItem, len, &textSize); +	} +	TEXTMETRIC tm; +	::GetTextMetrics(hdc, &tm); +	maxCharWidth = tm.tmMaxCharWidth; +	SelectFont(hdc, oldFont); +	::ReleaseDC(lb, hdc); +	if (textSize.cx > width) +		width = textSize.cx; + +	rcDesired.right = rcDesired.left + TextOffset() + width + (TextInset.x * 2);  	if (Length() > rows)  		rcDesired.right += ::GetSystemMetrics(SM_CXVSCROLL); -	rcDesired.right += IconWidth(); + +	AdjustWindowRect(&rcDesired);  	return rcDesired;  } -int ListBoxX::IconWidth() { -	return xset.GetWidth() + 2; +int ListBoxX::TextOffset() const { +	int pixWidth = const_cast<XPMSet*>(&xset)->GetWidth(); +	return pixWidth == 0 ? ItemInset.x : ItemInset.x + pixWidth + (ImageInset.x * 2);  }  int ListBoxX::CaretFromEdge() { -	return 4 + IconWidth(); +	PRectangle rc; +	AdjustWindowRect(&rc); +	return TextOffset() + TextInset.x + (0 - rc.left) - 1;  }  void ListBoxX::Clear() {  	::SendMessage(lb, LB_RESETCONTENT, 0, 0);  	maxItemCharacters = 0; -	ltt.Clear(); +	widestItem = NULL; +	lti.Clear();  }  void ListBoxX::Append(char *s, int type) { -	::SendMessage(lb, LB_ADDSTRING, 0, reinterpret_cast<LPARAM>(s)); +	int index = ::SendMessage(lb, LB_ADDSTRING, 0, reinterpret_cast<LPARAM>(s)); +	if (index < 0) +		return; +	ListItemData *newItem = lti.Append(s, type);  	unsigned int len = static_cast<unsigned int>(strlen(s)); -	if (maxItemCharacters < len) +	if (maxItemCharacters < len) {  		maxItemCharacters = len; -	int count = ::SendMessage(lb, LB_GETCOUNT, 0, 0); -	ltt.Add(count-1, type); +		widestItem = newItem->text; +	}  }  int ListBoxX::Length() { -	return ::SendMessage(lb, LB_GETCOUNT, 0, 0); +	return lti.Count();  }  void ListBoxX::Select(int n) { +	// We are going to scroll to centre on the new selection and then select it, so disable +	// redraw to avoid flicker caused by a painting new selection twice in unselected and then +	// selected states +	SetRedraw(false); +	CentreItem(n);  	::SendMessage(lb, LB_SETCURSEL, n, 0); +	SetRedraw(true);  }  int ListBoxX::GetSelection() {  	return ::SendMessage(lb, LB_GETCURSEL, 0, 0);  } -int ListBoxX::Find(const char *prefix) { -	return ::SendMessage(lb, LB_FINDSTRING, static_cast<WPARAM>(-1), -		reinterpret_cast<LPARAM>(prefix)); +// This is not actually called at present +int ListBoxX::Find(const char *) { +	return LB_ERR;  }  void ListBoxX::GetValue(int n, char *value, int len) { -	int lenText = ::SendMessage(lb, LB_GETTEXTLEN, n, 0); -	if ((len > 0) && (lenText > 0)){ -		char *text = new char[len+1]; -		if (text) { -			::SendMessage(lb, LB_GETTEXT, n, reinterpret_cast<LPARAM>(text)); -			strncpy(value, text, len); -			value[len-1] = '\0'; -			delete []text; -		} else { -			value[0] = '\0'; -		} -	} else { -		value[0] = '\0'; -	} +	ListItemData item = lti.Get(n); +	strncpy(value, item.text, len); +	value[len-1] = '\0';  }  void ListBoxX::RegisterImage(int type, const char *xpm_data) { @@ -1080,102 +1212,484 @@ void ListBoxX::ClearRegisteredImages() {  void ListBoxX::Draw(DRAWITEMSTRUCT *pDrawItem) {  	if ((pDrawItem->itemAction == ODA_SELECT) || (pDrawItem->itemAction == ODA_DRAWENTIRE)) { -		char text[1000]; -		int len = ::SendMessage(pDrawItem->hwndItem, LB_GETTEXTLEN, pDrawItem->itemID, 0); -		if (len < static_cast<int>(sizeof(text))) { -			::SendMessage(pDrawItem->hwndItem, LB_GETTEXT, pDrawItem->itemID, reinterpret_cast<LPARAM>(text)); -		} else { -			len = 0; -			text[0] = '\0'; -		} -		int pixId = ltt.Get(pDrawItem->itemID); -		XPM *pxpm = xset.Get(pixId); +		RECT rcBox = pDrawItem->rcItem; +		rcBox.left += TextOffset();  		if (pDrawItem->itemState & ODS_SELECTED) { +			RECT rcImage = pDrawItem->rcItem; +			rcImage.right = rcBox.left; +			// The image is not highlighted +			::FillRect(pDrawItem->hDC, &rcImage, reinterpret_cast<HBRUSH>(COLOR_WINDOW+1)); +			::FillRect(pDrawItem->hDC, &rcBox, reinterpret_cast<HBRUSH>(COLOR_HIGHLIGHT+1));  			::SetBkColor(pDrawItem->hDC, ::GetSysColor(COLOR_HIGHLIGHT));  			::SetTextColor(pDrawItem->hDC, ::GetSysColor(COLOR_HIGHLIGHTTEXT));  		} else { +			::FillRect(pDrawItem->hDC, &pDrawItem->rcItem, reinterpret_cast<HBRUSH>(COLOR_WINDOW+1));  			::SetBkColor(pDrawItem->hDC, ::GetSysColor(COLOR_WINDOW));  			::SetTextColor(pDrawItem->hDC, ::GetSysColor(COLOR_WINDOWTEXT));  		} -		int widthPix = xset.GetWidth() + 2; + +		ListItemData item = lti.Get(pDrawItem->itemID); +		int pixId = item.pixId; +		const char *text = item.text; +		int len = strlen(text); + +		RECT rcText = rcBox; +		::InsetRect(&rcText, TextInset.x, TextInset.y); +  		if (unicodeMode) {  			wchar_t tbuf[MAX_US_LEN];  			int tlen = UCS2FromUTF8(text, len, tbuf, sizeof(tbuf)/sizeof(wchar_t)-1);  			tbuf[tlen] = L'\0'; -			::ExtTextOutW(pDrawItem->hDC, pDrawItem->rcItem.left+widthPix+1, pDrawItem->rcItem.top, -				ETO_OPAQUE, &(pDrawItem->rcItem), tbuf, tlen, NULL); +			::DrawTextW(pDrawItem->hDC, tbuf, tlen, &rcText, DT_NOPREFIX|DT_END_ELLIPSIS|DT_SINGLELINE|DT_NOCLIP);  		} else { -			::ExtTextOut(pDrawItem->hDC, pDrawItem->rcItem.left+widthPix+1, pDrawItem->rcItem.top, -				ETO_OPAQUE, &(pDrawItem->rcItem), text, -				len, NULL); +			::DrawText(pDrawItem->hDC, text, len, &rcText, DT_NOPREFIX|DT_END_ELLIPSIS|DT_SINGLELINE|DT_NOCLIP);  		} +		if (pDrawItem->itemState & ODS_SELECTED) { +			::DrawFocusRect(pDrawItem->hDC, &rcBox); +		} + +		// Draw the image, if any +		XPM *pxpm = xset.Get(pixId);  		if (pxpm) {  			Surface *surfaceItem = Surface::Allocate();  			if (surfaceItem) {  				surfaceItem->Init(pDrawItem->hDC, pDrawItem->hwndItem);  				//surfaceItem->SetUnicodeMode(unicodeMode);  				//surfaceItem->SetDBCSMode(codePage); -				int left = pDrawItem->rcItem.left; -				PRectangle rc(left + 1, pDrawItem->rcItem.top, -					left + 1 + widthPix, pDrawItem->rcItem.bottom); -				pxpm->Draw(surfaceItem, rc); +				int left = pDrawItem->rcItem.left + ItemInset.x + ImageInset.x; +				PRectangle rcImage(left, pDrawItem->rcItem.top, +					left + xset.GetWidth(), pDrawItem->rcItem.bottom); +				pxpm->Draw(surfaceItem, rcImage);  				delete surfaceItem;  				::SetTextAlign(pDrawItem->hDC, TA_TOP);  			}  		} -        if (pDrawItem->itemState & ODS_SELECTED) { -			::DrawFocusRect(pDrawItem->hDC, &(pDrawItem->rcItem)); +	} +} + +void ListBoxX::AppendListItem(const char *startword, const char *numword) { +	ListItemData *item = lti.AllocItem(); +	item->text = startword; +	if (numword) { +		int pixId = 0; +		char ch; +        while ( (ch = *++numword) != '\0' ) { +            pixId = 10 * pixId + (ch - '0');          } +		item->pixId = pixId; +	} else { +		item->pixId = -1; +	} + +	unsigned int len = static_cast<unsigned int>(strlen(item->text)); +	if (maxItemCharacters < len) { +		maxItemCharacters = len; +		widestItem = item->text; +	} +} + +void ListBoxX::SetList(const char *list, char separator, char typesep) { +	// Turn off redraw while populating the list - this has a significant effect, even if +	// the listbox is not visible. +	SetRedraw(false); +	Clear(); +	int size = strlen(list) + 1; +	char *words = new char[size]; +	if (words) { +		lti.SetWords(words); +		memcpy(words, list, size); +		char *startword = words; +		char *numword = NULL; +		int i = 0; +		for (; words[i]; i++) { +			if (words[i] == separator) { +				words[i] = '\0'; +				if (numword) +					*numword = '\0'; +				AppendListItem(startword, numword); +				startword = words + i + 1; +				numword = NULL; +			} else if (words[i] == typesep) { +				numword = words + i; +			} +		} +		if (startword) { +			if (numword) +				*numword = '\0'; +			AppendListItem(startword, numword); +		} + +		// Finally populate the listbox itself with the correct number of items +		int count = lti.Count(); +		::SendMessage(lb, LB_INITSTORAGE, count, 0); +		for (int j=0; j<count; j++) { +			::SendMessage(lb, LB_ADDSTRING, 0, 0); +		} +	} +	SetRedraw(true); +} + +void ListBoxX::AdjustWindowRect(PRectangle *rc) const { +	::AdjustWindowRectEx(reinterpret_cast<RECT*>(rc), WS_THICKFRAME, false, WS_EX_WINDOWEDGE); +} + +int ListBoxX::ItemHeight() const { +	int itemHeight = lineHeight + (TextInset.y * 2); +	int pixHeight = const_cast<XPMSet*>(&xset)->GetHeight() + (ImageInset.y * 2); +	if (itemHeight < pixHeight) { +		itemHeight = pixHeight; +	} +	return itemHeight; +} + +int ListBoxX::MinClientWidth() const { +	return 12 * (aveCharWidth+aveCharWidth/3); +} + +Point ListBoxX::MinTrackSize() const { +	PRectangle rc(0, 0, MinClientWidth(), ItemHeight()); +	AdjustWindowRect(&rc); +	return Point(rc.Width(), rc.Height()); +} + +Point ListBoxX::MaxTrackSize() const { +	PRectangle rc(0, 0, maxCharWidth * maxItemCharacters, ItemHeight() * lti.Count()); +	AdjustWindowRect(&rc); +	return Point(rc.Width(), rc.Height()); +} + +void ListBoxX::SetRedraw(bool on) { +	::SendMessage(lb, WM_SETREDRAW, static_cast<BOOL>(on), 0); +	if (on) +		::InvalidateRect(lb, NULL, TRUE); +} + +void ListBoxX::ResizeToCursor() { +	PRectangle rc = GetPosition(); +	Point pt; +	::GetCursorPos(reinterpret_cast<POINT*>(&pt)); +	pt.x += dragOffset.x; +	pt.y += dragOffset.y; + +	switch (resizeHit) { +		case HTLEFT: +			rc.left = pt.x; +			break; +		case HTRIGHT: +			rc.right = pt.x; +			break; +		case HTTOP: +			rc.top = pt.y; +			break; +		case HTTOPLEFT: +			rc.top = pt.y; +			rc.left = pt.x; +			break; +		case HTTOPRIGHT: +			rc.top = pt.y; +			rc.right = pt.x; +			break; +		case HTBOTTOM: +			rc.bottom = pt.y; +			break; +		case HTBOTTOMLEFT: +			rc.bottom = pt.y; +			rc.left = pt.x; +			break; +		case HTBOTTOMRIGHT: +			rc.bottom = pt.y; +			rc.right = pt.x; +			break; +	} + +	Point ptMin = MinTrackSize(); +	Point ptMax = MaxTrackSize(); +	// We don't allow the left edge to move at present, but just in case +	rc.left = Platform::Maximum(Platform::Minimum(rc.left, rcPreSize.right - ptMin.x), rcPreSize.right - ptMax.x); +	rc.top = Platform::Maximum(Platform::Minimum(rc.top, rcPreSize.bottom - ptMin.y), rcPreSize.bottom - ptMax.y); +	rc.right = Platform::Maximum(Platform::Minimum(rc.right, rcPreSize.left + ptMax.x), rcPreSize.left + ptMin.x); +	rc.bottom = Platform::Maximum(Platform::Minimum(rc.bottom, rcPreSize.top + ptMax.y), rcPreSize.top + ptMin.y); + +	SetPosition(rc); +} + +void ListBoxX::StartResize(WPARAM hitCode) { +	rcPreSize = GetPosition(); +	POINT cursorPos; +	::GetCursorPos(&cursorPos); + +	switch (hitCode) { +		case HTRIGHT: +		case HTBOTTOM: +		case HTBOTTOMRIGHT: +			dragOffset.x = rcPreSize.right - cursorPos.x; +			dragOffset.y = rcPreSize.bottom - cursorPos.y; +			break; + +		case HTTOPRIGHT: +			dragOffset.x = rcPreSize.right - cursorPos.x; +			dragOffset.y = rcPreSize.top - cursorPos.y; +			break; + +		// Note that the current hit test code prevents the left edge cases ever firing +		// as we don't want the left edge to be moveable +		case HTLEFT: +		case HTTOP: +		case HTTOPLEFT: +			dragOffset.x = rcPreSize.left - cursorPos.x; +			dragOffset.y = rcPreSize.top - cursorPos.y; +			break; +		case HTBOTTOMLEFT: +			dragOffset.x = rcPreSize.left - cursorPos.x; +			dragOffset.y = rcPreSize.bottom - cursorPos.y; +			break; + +		default: +			return; +	} + +	::SetCapture(GetHWND()); +	resizeHit = hitCode; +} + +int ListBoxX::NcHitTest(WPARAM wParam, LPARAM lParam) const { +	int hit = ::DefWindowProc(GetHWND(), WM_NCHITTEST, wParam, lParam); +	// There is an apparent bug in the DefWindowProc hit test code whereby it will +	// return HTTOPXXX if the window in question is shorter than the default +	// window caption height + frame, even if one is hovering over the bottom edge of +	// the frame, so workaround that here +	if (hit >= HTTOP && hit <= HTTOPRIGHT) { +		int minHeight = GetSystemMetrics(SM_CYMINTRACK); +		PRectangle rc = const_cast<ListBoxX*>(this)->GetPosition(); +		int yPos = GET_Y_LPARAM(lParam); +		if ((rc.Height() < minHeight) && (yPos > ((rc.top + rc.bottom)/2))) { +			hit += HTBOTTOM - HTTOP; +		} +	} + +	// Nerver permit resizing that moves the left edge. Allow movement of top or bottom edge +	// depending on whether the list is above or below the caret +	switch (hit) { +		case HTLEFT: +		case HTTOPLEFT: +		case HTBOTTOMLEFT: +			hit = HTERROR; +			break; + +		case HTTOP: +		case HTTOPRIGHT: { +				PRectangle rc = const_cast<ListBoxX*>(this)->GetPosition(); +				// Valid only if caret below list +				if (location.y < rc.top) +					hit = HTERROR; +			} +			break; + +		case HTBOTTOM: +		case HTBOTTOMRIGHT: { +				PRectangle rc = const_cast<ListBoxX*>(this)->GetPosition(); +				// Valid only if caret above list +				if (rc.bottom < location.y) +					hit = HTERROR; +			} +			break; +	} + +	return hit; +} + +void ListBoxX::OnDoubleClick() { + +	if (doubleClickAction != NULL) { +		doubleClickAction(doubleClickActionData); +	} +} + +Point ListBoxX::GetClientExtent() const { +	PRectangle rc = const_cast<ListBoxX*>(this)->GetClientPosition(); +	return Point(rc.Width(), rc.Height()); +} + +void ListBoxX::CentreItem(int n) { +	// If below mid point, scroll up to centre, but with more items below if uneven +	if (n >= 0) { +		Point extent = GetClientExtent(); +		int visible = extent.y/ItemHeight(); +		if (visible < Length()) { +			int top = ::SendMessage(lb, LB_GETTOPINDEX, 0, 0); +			int half = (visible - 1) / 2; +			if (n > (top + half)) +				::SendMessage(lb, LB_SETTOPINDEX, n - half , 0); +		} +	} +} + +// Performs a double-buffered paint operation to avoid flicker +void ListBoxX::Paint(HDC hDC) { +	Point extent = GetClientExtent(); +	HBITMAP hBitmap = ::CreateCompatibleBitmap(hDC, extent.x, extent.y); +	HDC bitmapDC = ::CreateCompatibleDC(hDC); +	SelectBitmap(bitmapDC, hBitmap); +	// The list background is mainly erased during painting, but can be a small +	// unpainted area when at the end of a non-integrally sized list with a +	// vertical scroll bar +	RECT rc = { 0, 0, extent.x, extent.y }; +	::FillRect(bitmapDC, &rc, reinterpret_cast<HBRUSH>(COLOR_WINDOW+1)); +	// Paint the entire client area and vertical scrollbar +	::SendMessage(lb, WM_PRINT, reinterpret_cast<WPARAM>(bitmapDC), PRF_CLIENT|PRF_NONCLIENT); +	::BitBlt(hDC, 0, 0, extent.x, extent.y, bitmapDC, 0, 0, SRCCOPY); +	::DeleteDC(bitmapDC); +	::DeleteObject(hBitmap); +} + +long PASCAL ListBoxX::ControlWndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { +	switch (uMsg) { +	case WM_ERASEBKGND: +		return TRUE; + +	case WM_PAINT: { +			PAINTSTRUCT ps; +			HDC hDC = ::BeginPaint(hWnd, &ps); +			ListBoxX *lbx = reinterpret_cast<ListBoxX *>(PointerFromWindow(::GetParent(hWnd))); +			if (lbx) +				lbx->Paint(hDC); +			::EndPaint(hWnd, &ps); +		} +		return 0; + +	case WM_MOUSEACTIVATE: +		// This prevents the view activating when the scrollbar is clicked +		return MA_NOACTIVATE; + +	case WM_LBUTTONDOWN: { +			// We must take control of selection to prevent the ListBox activating +			// the popup +			LRESULT lResult = ::SendMessage(hWnd, LB_ITEMFROMPOINT, 0, lParam); +			int item = LOWORD(lResult); +			if (HIWORD(lResult) == 0 && item >= 0) { +				::SendMessage(hWnd, LB_SETCURSEL, item, 0); +			} +		} +		return 0; + +	case WM_LBUTTONUP: +		return 0; + +	case WM_LBUTTONDBLCLK: { +			ListBoxX *lbx = reinterpret_cast<ListBoxX *>(PointerFromWindow(::GetParent(hWnd))); +			if (lbx) { +				lbx->OnDoubleClick(); +			} +		} +		return 0; +	} + +	WNDPROC prevWndProc = reinterpret_cast<WNDPROC>(GetWindowLong(hWnd, GWL_USERDATA)); +	if (prevWndProc) { +		return ::CallWindowProc(prevWndProc, hWnd, uMsg, wParam, lParam); +	} else { +		return ::DefWindowProc(hWnd, uMsg, wParam, lParam);  	}  }  long ListBoxX::WndProc(HWND hWnd, UINT iMessage, WPARAM wParam, LPARAM lParam) {  	switch (iMessage) {  	case WM_CREATE: { -		HWND parent = ::GetParent(hWnd); -		HINSTANCE hinstanceParent = GetWindowInstance(parent); -		CREATESTRUCT *pCreate = reinterpret_cast<CREATESTRUCT *>(lParam); -		::CreateWindowEx( -			WS_EX_WINDOWEDGE, TEXT("listbox"), TEXT(""), -			WS_CHILD | WS_VSCROLL | WS_VISIBLE | -			LBS_OWNERDRAWFIXED | LBS_HASSTRINGS | LBS_NOTIFY, -			0, 0, 150,80, hWnd, -			pCreate->hMenu, -			hinstanceParent, -			0); +			HINSTANCE hinstanceParent = GetWindowInstance(reinterpret_cast<HWND>(parent->GetID())); +			// Note that LBS_NOINTEGRALHEIGHT is specified to fix cosmetic issue when resizing the list +			// but has useful side effect of speeding up list population significantly +			lb = ::CreateWindowEx( +				0, TEXT("listbox"), TEXT(""), +				WS_CHILD | WS_VSCROLL | WS_VISIBLE | +				LBS_OWNERDRAWFIXED | LBS_NODATA | LBS_NOINTEGRALHEIGHT, +				0, 0, 150,80, hWnd, +				reinterpret_cast<HMENU>(ctrlID), +				hinstanceParent, +				0); +			WNDPROC prevWndProc = reinterpret_cast<WNDPROC>(::SetWindowLong(lb, GWL_WNDPROC, reinterpret_cast<LONG>(ControlWndProc))); +			::SetWindowLong(lb, GWL_USERDATA, reinterpret_cast<LONG>(prevWndProc));  		}  		break; -	case WM_SIZE: { -			HWND lb = ::GetWindow(hWnd, GW_CHILD); -			::SetWindowPos(lb, 0, 0,0, LOWORD(lParam), HIWORD(lParam), 0); + +	case WM_SIZE: +		if (lb) { +			SetRedraw(false); +			::SetWindowPos(lb, 0, 0,0, LOWORD(lParam), HIWORD(lParam), SWP_NOZORDER|SWP_NOACTIVATE|SWP_NOMOVE); +			// Ensure the selection remains visible +			CentreItem(GetSelection()); +			SetRedraw(true);  		}  		break; +  	case WM_PAINT: {  			PAINTSTRUCT ps;  			::BeginPaint(hWnd, &ps);  			::EndPaint(hWnd, &ps);  		}  		break; -	case WM_COMMAND: { -			HWND parent = ::GetParent(hWnd); -			::SendMessage(parent, iMessage, wParam, lParam); -		} + +	case WM_COMMAND: +		// This is not actually needed now - the registered double click action is used +		// directly to action a choice from the list. +		::SendMessage(reinterpret_cast<HWND>(parent->GetID()), iMessage, wParam, lParam);  		break;  	case WM_MEASUREITEM: {  			MEASUREITEMSTRUCT *pMeasureItem = reinterpret_cast<MEASUREITEMSTRUCT *>(lParam); -			pMeasureItem->itemHeight = lineHeight; -			if (pMeasureItem->itemHeight < static_cast<unsigned int>(xset.GetHeight())) { -				pMeasureItem->itemHeight = xset.GetHeight(); -			} +			pMeasureItem->itemHeight = static_cast<unsigned int>(ItemHeight());  		}  		break; +  	case WM_DRAWITEM:  		Draw(reinterpret_cast<DRAWITEMSTRUCT *>(lParam));  		break; +  	case WM_DESTROY: +		lb = 0;  		::SetWindowLong(hWnd, 0, 0);  		return ::DefWindowProc(hWnd, iMessage, wParam, lParam); + +	case WM_ERASEBKGND: +		// To reduce flicker we can elide background erasure since this window is +		// completely covered by its child. +		return TRUE; + +	case WM_GETMINMAXINFO: { +			MINMAXINFO *minMax = reinterpret_cast<MINMAXINFO*>(lParam); +			*reinterpret_cast<Point*>(&minMax->ptMaxTrackSize) = MaxTrackSize(); +			*reinterpret_cast<Point*>(&minMax->ptMinTrackSize) = MinTrackSize(); +		} +		break; + +	case WM_MOUSEACTIVATE: +		return MA_NOACTIVATE; + +	case WM_NCHITTEST: +		return NcHitTest(wParam, lParam); + +	case WM_NCLBUTTONDOWN: +		// We have to implement our own window resizing because the DefWindowProc +		// implementation insists on activating the resized window +		StartResize(wParam); +		return 0; + +	case WM_MOUSEMOVE: { +			if (resizeHit == 0) { +				return ::DefWindowProc(hWnd, iMessage, wParam, lParam); +			} else { +				ResizeToCursor(); +			} +		} +		break; + +	case WM_LBUTTONUP: +	case WM_CANCELMODE: +		if (resizeHit != 0) { +			resizeHit = 0; +			::ReleaseCapture(); +		} +		return ::DefWindowProc(hWnd, iMessage, wParam, lParam); +  	default:  		return ::DefWindowProc(hWnd, iMessage, wParam, lParam);  	} @@ -1201,6 +1715,9 @@ long PASCAL ListBoxX::StaticWndProc(  static bool ListBoxX_Register() {  	WNDCLASSEX wndclassc;  	wndclassc.cbSize = sizeof(wndclassc); +	// We need CS_HREDRAW and CS_VREDRAW because of the ellipsis that might be drawn for +	// truncated items in the list and the appearance/disappearance of the vertical scroll bar. +	// The list repaint is double-buffered to avoid the flicker this would otherwise cause.  	wndclassc.style = CS_GLOBALCLASS | CS_HREDRAW | CS_VREDRAW;  	wndclassc.cbClsExtra = 0;  	wndclassc.cbWndExtra = sizeof(ListBoxX *); diff --git a/win32/ScintillaWin.cxx b/win32/ScintillaWin.cxx index d50b3c98e..0900e7a21 100644 --- a/win32/ScintillaWin.cxx +++ b/win32/ScintillaWin.cxx @@ -527,15 +527,6 @@ sptr_t ScintillaWin::WndProc(unsigned int iMessage, uptr_t wParam, sptr_t lParam  	case WM_COMMAND:  #ifdef TOTAL_CONTROL -		if (LoWord(wParam) == idAutoComplete) { -			int cmd = HiWord(wParam); -			if (cmd == LBN_DBLCLK) { -				AutoCompleteCompleted(); -			} else { -				if (cmd != LBN_SETFOCUS) -					::SetFocus(MainHWND()); -			} -		}  		Command(LoWord(wParam));  #endif  		break; @@ -762,7 +753,7 @@ sptr_t ScintillaWin::WndProc(unsigned int iMessage, uptr_t wParam, sptr_t lParam  	case WM_KILLFOCUS: {  			HWND wOther = reinterpret_cast<HWND>(wParam); -			HWND wThis = reinterpret_cast<HWND>(wMain.GetID()); +			HWND wThis = MainHWND();  			HWND wCT = reinterpret_cast<HWND>(ct.wCallTip.GetID());  			if (!wParam ||  				!(::IsChild(wThis,wOther) || (wOther == wCT))) { | 
