aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authornyamatongwe <unknown>2005-03-25 05:08:20 +0000
committernyamatongwe <unknown>2005-03-25 05:08:20 +0000
commit66622bd04e7666e7b0556b17902dc683a22bc249 (patch)
treec11a0777465efddd66120e05cc73d7745d31ae50
parent3ab465e5e5e653cc00298c2cca632deea54e0ff0 (diff)
downloadscintilla-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.cxx23
-rw-r--r--src/ScintillaBase.h1
-rw-r--r--win32/PlatWin.cxx761
-rw-r--r--win32/ScintillaWin.cxx11
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))) {