diff options
author | nyamatongwe <unknown> | 2005-03-25 05:08:20 +0000 |
---|---|---|
committer | nyamatongwe <unknown> | 2005-03-25 05:08:20 +0000 |
commit | 66622bd04e7666e7b0556b17902dc683a22bc249 (patch) | |
tree | c11a0777465efddd66120e05cc73d7745d31ae50 | |
parent | 3ab465e5e5e653cc00298c2cca632deea54e0ff0 (diff) | |
download | scintilla-mirror-66622bd04e7666e7b0556b17902dc683a22bc249.tar.gz |
Patch from Blair McGlashan for autocompletion on Windows to
* Set maximum width of list
* set maximum height of list
* better calculate width
* use ellipsis when text is truncated to fit window
* use popup window so it can extend past parent window
* disallow resizing too small
* draw to bottom edge when resized so last item not full line high
* improve time to display by by 90%
Minor tweaks by me to fix warnings, layout etc.
-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))) { |