Source code of Windows XP (NT5)
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

4342 lines
133 KiB

  1. // Copyright 1996-98 Microsoft
  2. #include "priv.h"
  3. #include "sccls.h"
  4. #include "autocomp.h"
  5. #include "itbar.h"
  6. #include "address.h"
  7. #include "addrlist.h"
  8. #include "resource.h"
  9. #include "mluisupp.h"
  10. #ifdef UNIX
  11. #include "unixstuff.h"
  12. #endif
  13. #include "apithk.h"
  14. extern HRESULT CACLMRU_CreateInstance(IUnknown *punkOuter, IUnknown **ppunk, LPCOBJECTINFO poi, LPCTSTR pszMRU);
  15. #define WZ_REGKEY_QUICKCOMPLETE L"Software\\Microsoft\\Internet Explorer\\Toolbar\\QuickComplete"
  16. #define WZ_DEFAULTQUICKCOMPLETE L"http://www.%s.com"
  17. // Statics
  18. const static TCHAR c_szAutoDefQuickComp[] = TEXT("%s");
  19. const static TCHAR c_szAutoCompleteProp[] = TEXT("CAutoComplete_This");
  20. const static TCHAR c_szParentWindowProp[] = TEXT("CParentWindow_This");
  21. const static TCHAR c_szAutoSuggest[] = TEXT("AutoSuggest Drop-Down");
  22. const static TCHAR c_szAutoSuggestTitle[] = TEXT("Internet Explorer");
  23. BOOL CAutoComplete::s_fNoActivate = FALSE;
  24. HWND CAutoComplete::s_hwndDropDown = NULL;
  25. HHOOK CAutoComplete::s_hhookMouse = NULL;
  26. #define MAX_QUICK_COMPLETE_STRING 64
  27. #define LISTVIEW_COLUMN_WIDTH 30000
  28. //
  29. // FLAGS for dwFlags
  30. //
  31. #define ACF_RESET 0x00000000
  32. #define ACF_IGNOREUPDOWN 0x00000004
  33. #define URL_SEPARATOR_CHAR TEXT('/')
  34. #ifdef UNIX
  35. #define DIR_SEPARATOR_CHAR TEXT('/')
  36. #define DIR_SEPARATOR_STRING TEXT("/")
  37. #else
  38. #define DIR_SEPARATOR_CHAR TEXT('\\')
  39. #define DIR_SEPARATOR_STRING TEXT("\\")
  40. #endif
  41. /////////////////////////////////////////////////////////////////////////////
  42. // Line Break Character Table
  43. //
  44. // This was swipped from mlang. Special break characters added for URLs
  45. // have an "IE:" in the comment. Note that this table must be sorted!
  46. const WCHAR g_szBreakChars[] = {
  47. 0x0009, // TAB
  48. 0x0020, // SPACE
  49. 0x0021, // IE: !
  50. 0x0022, // IE: "
  51. 0x0023, // IE: #
  52. 0x0024, // IE: $
  53. 0x0025, // IE: %
  54. 0x0026, // IE: &
  55. 0x0027, // IE: '
  56. 0x0028, // LEFT PARENTHESIS
  57. 0x0029, // RIGHT PARENTHESIS
  58. 0x002A, // IE: *
  59. 0x002B, // IE: +
  60. 0x002C, // IE: ,
  61. 0x002D, // HYPHEN
  62. 0x002E, // IE: .
  63. 0x002F, // IE: /
  64. 0x003A, // IE: :
  65. 0x003B, // IE: ;
  66. 0x003C, // IE: <
  67. 0x003D, // IE: =
  68. 0x003E, // IE: >
  69. 0x003F, // IE: ?
  70. 0x0040, // IE: @
  71. 0x005B, // LEFT SQUARE BRACKET
  72. 0x005C, // IE: '\'
  73. 0x005D, // RIGHT SQUARE BRACKET
  74. 0x005E, // IE: ^
  75. 0x005F, // IE: _
  76. 0x0060, // IE:`
  77. 0x007B, // LEFT CURLY BRACKET
  78. 0x007C, // IE: |
  79. 0x007D, // RIGHT CURLY BRACKET
  80. 0x007E, // IE: ~
  81. 0x00AB, // LEFT-POINTING DOUBLE ANGLE QUOTATION MARK
  82. 0x00AD, // OPTIONAL HYPHEN
  83. 0x00BB, // RIGHT-POINTING DOUBLE ANGLE QUOTATION MARK
  84. 0x02C7, // CARON
  85. 0x02C9, // MODIFIER LETTER MACRON
  86. 0x055D, // ARMENIAN COMMA
  87. 0x060C, // ARABIC COMMA
  88. 0x2002, // EN SPACE
  89. 0x2003, // EM SPACE
  90. 0x2004, // THREE-PER-EM SPACE
  91. 0x2005, // FOUR-PER-EM SPACE
  92. 0x2006, // SIX-PER-EM SPACE
  93. 0x2007, // FIGURE SPACE
  94. 0x2008, // PUNCTUATION SPACE
  95. 0x2009, // THIN SPACE
  96. 0x200A, // HAIR SPACE
  97. 0x200B, // ZERO WIDTH SPACE
  98. 0x2013, // EN DASH
  99. 0x2014, // EM DASH
  100. 0x2016, // DOUBLE VERTICAL LINE
  101. 0x2018, // LEFT SINGLE QUOTATION MARK
  102. 0x201C, // LEFT DOUBLE QUOTATION MARK
  103. 0x201D, // RIGHT DOUBLE QUOTATION MARK
  104. 0x2022, // BULLET
  105. 0x2025, // TWO DOT LEADER
  106. 0x2026, // HORIZONTAL ELLIPSIS
  107. 0x2027, // HYPHENATION POINT
  108. 0x2039, // SINGLE LEFT-POINTING ANGLE QUOTATION MARK
  109. 0x203A, // SINGLE RIGHT-POINTING ANGLE QUOTATION MARK
  110. 0x2045, // LEFT SQUARE BRACKET WITH QUILL
  111. 0x2046, // RIGHT SQUARE BRACKET WITH QUILL
  112. 0x207D, // SUPERSCRIPT LEFT PARENTHESIS
  113. 0x207E, // SUPERSCRIPT RIGHT PARENTHESIS
  114. 0x208D, // SUBSCRIPT LEFT PARENTHESIS
  115. 0x208E, // SUBSCRIPT RIGHT PARENTHESIS
  116. 0x226A, // MUCH LESS THAN
  117. 0x226B, // MUCH GREATER THAN
  118. 0x2574, // BOX DRAWINGS LIGHT LEFT
  119. 0x3001, // IDEOGRAPHIC COMMA
  120. 0x3002, // IDEOGRAPHIC FULL STOP
  121. 0x3003, // DITTO MARK
  122. 0x3005, // IDEOGRAPHIC ITERATION MARK
  123. 0x3008, // LEFT ANGLE BRACKET
  124. 0x3009, // RIGHT ANGLE BRACKET
  125. 0x300A, // LEFT DOUBLE ANGLE BRACKET
  126. 0x300B, // RIGHT DOUBLE ANGLE BRACKET
  127. 0x300C, // LEFT CORNER BRACKET
  128. 0x300D, // RIGHT CORNER BRACKET
  129. 0x300E, // LEFT WHITE CORNER BRACKET
  130. 0x300F, // RIGHT WHITE CORNER BRACKET
  131. 0x3010, // LEFT BLACK LENTICULAR BRACKET
  132. 0x3011, // RIGHT BLACK LENTICULAR BRACKET
  133. 0x3014, // LEFT TORTOISE SHELL BRACKET
  134. 0x3015, // RIGHT TORTOISE SHELL BRACKET
  135. 0x3016, // LEFT WHITE LENTICULAR BRACKET
  136. 0x3017, // RIGHT WHITE LENTICULAR BRACKET
  137. 0x3018, // LEFT WHITE TORTOISE SHELL BRACKET
  138. 0x3019, // RIGHT WHITE TORTOISE SHELL BRACKET
  139. 0x301A, // LEFT WHITE SQUARE BRACKET
  140. 0x301B, // RIGHT WHITE SQUARE BRACKET
  141. 0x301D, // REVERSED DOUBLE PRIME QUOTATION MARK
  142. 0x301E, // DOUBLE PRIME QUOTATION MARK
  143. 0x3041, // HIRAGANA LETTER SMALL A
  144. 0x3043, // HIRAGANA LETTER SMALL I
  145. 0x3045, // HIRAGANA LETTER SMALL U
  146. 0x3047, // HIRAGANA LETTER SMALL E
  147. 0x3049, // HIRAGANA LETTER SMALL O
  148. 0x3063, // HIRAGANA LETTER SMALL TU
  149. 0x3083, // HIRAGANA LETTER SMALL YA
  150. 0x3085, // HIRAGANA LETTER SMALL YU
  151. 0x3087, // HIRAGANA LETTER SMALL YO
  152. 0x308E, // HIRAGANA LETTER SMALL WA
  153. 0x309B, // KATAKANA-HIRAGANA VOICED SOUND MARK
  154. 0x309C, // KATAKANA-HIRAGANA SEMI-VOICED SOUND MARK
  155. 0x309D, // HIRAGANA ITERATION MARK
  156. 0x309E, // HIRAGANA VOICED ITERATION MARK
  157. 0x30A1, // KATAKANA LETTER SMALL A
  158. 0x30A3, // KATAKANA LETTER SMALL I
  159. 0x30A5, // KATAKANA LETTER SMALL U
  160. 0x30A7, // KATAKANA LETTER SMALL E
  161. 0x30A9, // KATAKANA LETTER SMALL O
  162. 0x30C3, // KATAKANA LETTER SMALL TU
  163. 0x30E3, // KATAKANA LETTER SMALL YA
  164. 0x30E5, // KATAKANA LETTER SMALL YU
  165. 0x30E7, // KATAKANA LETTER SMALL YO
  166. 0x30EE, // KATAKANA LETTER SMALL WA
  167. 0x30F5, // KATAKANA LETTER SMALL KA
  168. 0x30F6, // KATAKANA LETTER SMALL KE
  169. 0x30FC, // KATAKANA-HIRAGANA PROLONGED SOUND MARK
  170. 0x30FD, // KATAKANA ITERATION MARK
  171. 0x30FE, // KATAKANA VOICED ITERATION MARK
  172. 0xFD3E, // ORNATE LEFT PARENTHESIS
  173. 0xFD3F, // ORNATE RIGHT PARENTHESIS
  174. 0xFE30, // VERTICAL TWO DOT LEADER
  175. 0xFE31, // VERTICAL EM DASH
  176. 0xFE33, // VERTICAL LOW LINE
  177. 0xFE34, // VERTICAL WAVY LOW LINE
  178. 0xFE35, // PRESENTATION FORM FOR VERTICAL LEFT PARENTHESIS
  179. 0xFE36, // PRESENTATION FORM FOR VERTICAL RIGHT PARENTHESIS
  180. 0xFE37, // PRESENTATION FORM FOR VERTICAL LEFT CURLY BRACKET
  181. 0xFE38, // PRESENTATION FORM FOR VERTICAL RIGHT CURLY BRACKET
  182. 0xFE39, // PRESENTATION FORM FOR VERTICAL LEFT TORTOISE SHELL BRACKET
  183. 0xFE3A, // PRESENTATION FORM FOR VERTICAL RIGHT TORTOISE SHELL BRACKET
  184. 0xFE3B, // PRESENTATION FORM FOR VERTICAL LEFT BLACK LENTICULAR BRACKET
  185. 0xFE3C, // PRESENTATION FORM FOR VERTICAL RIGHT BLACK LENTICULAR BRACKET
  186. 0xFE3D, // PRESENTATION FORM FOR VERTICAL LEFT DOUBLE ANGLE BRACKET
  187. 0xFE3E, // PRESENTATION FORM FOR VERTICAL RIGHT DOUBLE ANGLE BRACKET
  188. 0xFE3F, // PRESENTATION FORM FOR VERTICAL LEFT ANGLE BRACKET
  189. 0xFE40, // PRESENTATION FORM FOR VERTICAL RIGHT ANGLE BRACKET
  190. 0xFE41, // PRESENTATION FORM FOR VERTICAL LEFT CORNER BRACKET
  191. 0xFE42, // PRESENTATION FORM FOR VERTICAL RIGHT CORNER BRACKET
  192. 0xFE43, // PRESENTATION FORM FOR VERTICAL LEFT WHITE CORNER BRACKET
  193. 0xFE44, // PRESENTATION FORM FOR VERTICAL RIGHT WHITE CORNER BRACKET
  194. 0xFE4F, // WAVY LOW LINE
  195. 0xFE50, // SMALL COMMA
  196. 0xFE51, // SMALL IDEOGRAPHIC COMMA
  197. 0xFE59, // SMALL LEFT PARENTHESIS
  198. 0xFE5A, // SMALL RIGHT PARENTHESIS
  199. 0xFE5B, // SMALL LEFT CURLY BRACKET
  200. 0xFE5C, // SMALL RIGHT CURLY BRACKET
  201. 0xFE5D, // SMALL LEFT TORTOISE SHELL BRACKET
  202. 0xFE5E, // SMALL RIGHT TORTOISE SHELL BRACKET
  203. 0xFF08, // FULLWIDTH LEFT PARENTHESIS
  204. 0xFF09, // FULLWIDTH RIGHT PARENTHESIS
  205. 0xFF0C, // FULLWIDTH COMMA
  206. 0xFF0E, // FULLWIDTH FULL STOP
  207. 0xFF1C, // FULLWIDTH LESS-THAN SIGN
  208. 0xFF1E, // FULLWIDTH GREATER-THAN SIGN
  209. 0xFF3B, // FULLWIDTH LEFT SQUARE BRACKET
  210. 0xFF3D, // FULLWIDTH RIGHT SQUARE BRACKET
  211. 0xFF40, // FULLWIDTH GRAVE ACCENT
  212. 0xFF5B, // FULLWIDTH LEFT CURLY BRACKET
  213. 0xFF5C, // FULLWIDTH VERTICAL LINE
  214. 0xFF5D, // FULLWIDTH RIGHT CURLY BRACKET
  215. 0xFF5E, // FULLWIDTH TILDE
  216. 0xFF61, // HALFWIDTH IDEOGRAPHIC FULL STOP
  217. 0xFF62, // HALFWIDTH LEFT CORNER BRACKET
  218. 0xFF63, // HALFWIDTH RIGHT CORNER BRACKET
  219. 0xFF64, // HALFWIDTH IDEOGRAPHIC COMMA
  220. 0xFF67, // HALFWIDTH KATAKANA LETTER SMALL A
  221. 0xFF68, // HALFWIDTH KATAKANA LETTER SMALL I
  222. 0xFF69, // HALFWIDTH KATAKANA LETTER SMALL U
  223. 0xFF6A, // HALFWIDTH KATAKANA LETTER SMALL E
  224. 0xFF6B, // HALFWIDTH KATAKANA LETTER SMALL O
  225. 0xFF6C, // HALFWIDTH KATAKANA LETTER SMALL YA
  226. 0xFF6D, // HALFWIDTH KATAKANA LETTER SMALL YU
  227. 0xFF6E, // HALFWIDTH KATAKANA LETTER SMALL YO
  228. 0xFF6F, // HALFWIDTH KATAKANA LETTER SMALL TU
  229. 0xFF70, // HALFWIDTH KATAKANA-HIRAGANA PROLONGED SOUND MARK
  230. 0xFF9E, // HALFWIDTH KATAKANA VOICED SOUND MARK
  231. 0xFF9F, // HALFWIDTH KATAKANA SEMI-VOICED SOUND MARK
  232. 0xFFE9, // HALFWIDTH LEFTWARDS ARROW
  233. 0xFFEB, // HALFWIDTH RIGHTWARDS ARROW
  234. };
  235. /*
  236. //
  237. // AutoComplete Common Functions / Structures
  238. //
  239. const struct {
  240. UINT idMenu;
  241. UINT idCmd;
  242. } MenuToMessageId[] = {
  243. { IDM_AC_UNDO, WM_UNDO },
  244. { IDM_AC_CUT, WM_CUT },
  245. { IDM_AC_COPY, WM_COPY },
  246. { IDM_AC_PASTE, WM_PASTE }
  247. };
  248. */
  249. //+-------------------------------------------------------------------------
  250. // IUnknown methods
  251. //--------------------------------------------------------------------------
  252. HRESULT CAutoComplete::QueryInterface(REFIID riid, void **ppvObj)
  253. {
  254. if (IsEqualIID(riid, IID_IUnknown) || IsEqualIID(riid, IID_IAutoComplete) ||
  255. IsEqualIID(riid, IID_IAutoComplete2))
  256. {
  257. *ppvObj = SAFECAST(this, IAutoComplete2*);
  258. }
  259. else if (IsEqualIID(riid, IID_IAutoCompleteDropDown))
  260. {
  261. *ppvObj = SAFECAST(this, IAutoCompleteDropDown*);
  262. }
  263. else if (IsEqualIID(riid, IID_IEnumString))
  264. {
  265. *ppvObj = SAFECAST(this, IEnumString*);
  266. }
  267. else
  268. {
  269. return _DefQueryInterface(riid, ppvObj);
  270. }
  271. AddRef();
  272. return S_OK;
  273. }
  274. ULONG CAutoComplete::AddRef(void)
  275. {
  276. return InterlockedIncrement(&m_cRef);
  277. }
  278. ULONG CAutoComplete::Release(void)
  279. {
  280. ASSERT(m_cRef > 0);
  281. if (InterlockedDecrement(&m_cRef))
  282. {
  283. TraceMsg(AC_GENERAL, "CAutoComplete::Release() --- m_cRef = %i", m_cRef);
  284. return m_cRef;
  285. }
  286. TraceMsg(AC_GENERAL, "CAutoComplete::Release() --- m_cRef = %i", m_cRef);
  287. delete this;
  288. return 0;
  289. }
  290. /* IAutoComplete methods */
  291. //+-------------------------------------------------------------------------
  292. // This object can be Inited in two ways. This function will init it in
  293. // the first way, which works as follows:
  294. //
  295. // 1. The caller called CoInitialize or OleInitialize() and the corresponding
  296. // uninit will not be called until the control we are subclassing and
  297. // our selfs are long gone.
  298. // 2. The caller calls us on their main thread and we create and destroy
  299. // the background thread as needed.
  300. //--------------------------------------------------------------------------
  301. HRESULT CAutoComplete::Init
  302. (
  303. HWND hwndEdit, // control to be subclassed
  304. IUnknown *punkACL, // autocomplete list
  305. LPCOLESTR pwszRegKeyPath, // reg location where ctrl-enter completion is stored stored
  306. LPCOLESTR pwszQuickComplete // default format string for ctrl-enter completion
  307. )
  308. {
  309. HRESULT hr = S_OK;
  310. TraceMsg(AC_GENERAL, "CAutoComplete::Init(hwndEdit=0x%x, punkACL = 0x%x, pwszRegKeyPath = 0x%x, pwszQuickComplete = 0x%x)",
  311. hwndEdit, punkACL, pwszRegKeyPath, pwszQuickComplete);
  312. #ifdef DEBUG
  313. // Ensure that the Line Break Character Table is ordered
  314. WCHAR c = g_szBreakChars[0];
  315. for (int i = 1; i < ARRAYSIZE(g_szBreakChars); ++i)
  316. {
  317. ASSERT(c < g_szBreakChars[i]);
  318. c = g_szBreakChars[i];
  319. }
  320. #endif
  321. if (m_hwndEdit != NULL)
  322. {
  323. // Can currently only be initialized once
  324. ASSERT(FALSE);
  325. return E_FAIL;
  326. }
  327. m_hwndEdit = hwndEdit;
  328. #ifndef UNIX
  329. // Add our custom word-break callback so that we recognize URL delimitors when
  330. // ctrl-arrowing around.
  331. //
  332. // There is a bug with how USER handles WH_CALLWNDPROC global hooks in Win95 that
  333. // causes us to blow up if one is installed and a wordbreakproc is set. Thus,
  334. // if an app is running that has one of these hooks installed (intellipoint 1.1 etc.) then
  335. // if we install our wordbreakproc the app will fault when the proc is called. There
  336. // does not appear to be any way for us to work around it since USER's thunking code
  337. // trashes the stack so this API is disabled for Win95.
  338. //
  339. m_fEditControlUnicode = g_fRunningOnNT && IsWindowUnicode(m_hwndEdit);
  340. if (m_fEditControlUnicode)
  341. {
  342. m_oldEditWordBreakProc = (EDITWORDBREAKPROC)SendMessage(m_hwndEdit, EM_GETWORDBREAKPROC, 0, 0);
  343. SendMessage(m_hwndEdit, EM_SETWORDBREAKPROC, 0, (DWORD_PTR)EditWordBreakProcW);
  344. }
  345. #endif
  346. //
  347. // bug 81414 : To avoid clashing with app messages used by the edit window, we
  348. // use registered messages.
  349. //
  350. m_uMsgSearchComplete = RegisterWindowMessageA("AC_SearchComplete");
  351. m_uMsgItemActivate = RegisterWindowMessageA("AC_ItemActivate");
  352. if (m_uMsgSearchComplete == 0)
  353. {
  354. m_uMsgSearchComplete = WM_APP + 300;
  355. }
  356. if (m_uMsgItemActivate == 0)
  357. {
  358. m_uMsgItemActivate = WM_APP + 301;
  359. }
  360. _SetQuickCompleteStrings(pwszRegKeyPath, pwszQuickComplete);
  361. // IEnumString required
  362. ASSERT(m_pes == NULL);
  363. EVAL(SUCCEEDED(punkACL->QueryInterface(IID_IEnumString, (void **)&m_pes)));
  364. // IACList optional
  365. ASSERT(m_pacl == NULL);
  366. punkACL->QueryInterface(IID_IACList, (void **)&m_pacl);
  367. AddRef(); // Hold on to a ref for our Subclass.
  368. // Initial creation should have failed if the thread object was not allocated!
  369. ASSERT(m_pThread);
  370. m_pThread->Init(m_pes, m_pacl);
  371. // subclass the edit window
  372. SetWindowSubclass(m_hwndEdit, &s_EditWndProc, 0, (DWORD_PTR)this);
  373. //#define TEST_SETFONT
  374. #ifdef TEST_SETFONT
  375. HFONT h = CreateFont(20, 5, 0, 0, FW_BOLD, TRUE, FALSE, FALSE, ANSI_CHARSET,
  376. OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS, DEFAULT_QUALITY,
  377. FF_ROMAN, NULL);
  378. SendMessage(m_hwndEdit, WM_SETFONT, (WPARAM)h, TRUE);
  379. #endif
  380. // See what autocomplete features are enabled
  381. _SeeWhatsEnabled();
  382. // See if hwndEdit is part of a combobox
  383. HWND hwndParent = GetParent(m_hwndEdit);
  384. WCHAR szClass[30];
  385. int nLen = GetClassName(hwndParent, szClass, ARRAYSIZE(szClass));
  386. if (nLen != 0 &&
  387. (StrCmpI(szClass, L"combobox") == 0 || StrCmpI(szClass, L"comboboxex") == 0))
  388. {
  389. m_hwndCombo = hwndParent;
  390. }
  391. // If we've already got focus, then we need to call GotFocus...
  392. if (GetFocus() == hwndEdit)
  393. {
  394. m_pThread->GotFocus();
  395. }
  396. return hr;
  397. }
  398. //+-------------------------------------------------------------------------
  399. // Checks to see if autoappend or autosuggest freatures are enabled
  400. //--------------------------------------------------------------------------
  401. void CAutoComplete::_SeeWhatsEnabled()
  402. {
  403. #ifdef ALLOW_ALWAYS_DROP_UP
  404. m_fAlwaysDropUp = SHRegGetBoolUSValue(REGSTR_PATH_AUTOCOMPLETE,
  405. TEXT("AlwaysDropUp"), FALSE, /*default:*/FALSE);
  406. #endif
  407. // If autosuggest was just enabled, create the dropdown window
  408. if (_IsAutoSuggestEnabled() && NULL == m_hwndDropDown)
  409. {
  410. // Create the dropdown Window
  411. WNDCLASS wc = {0};
  412. wc.lpfnWndProc = s_DropDownWndProc;
  413. wc.cbWndExtra = SIZEOF(CAutoComplete*);
  414. wc.hInstance = HINST_THISDLL;
  415. wc.hCursor = LoadCursor(NULL, IDC_ARROW);
  416. wc.hbrBackground = (HBRUSH)(COLOR_WINDOW+1);
  417. wc.lpszClassName = c_szAutoSuggestClass;
  418. SHRegisterClass(&wc);
  419. DWORD dwExStyle = WS_EX_TOPMOST | WS_EX_TOOLWINDOW | WS_EX_NOPARENTNOTIFY;
  420. if(_IsRTLReadingEnabled())
  421. {
  422. dwExStyle |= WS_EX_RIGHT | WS_EX_RTLREADING | WS_EX_LEFTSCROLLBAR;
  423. }
  424. #ifdef UNIX
  425. // IEUNIX : Working around window activation problems in mainwin
  426. // Autocomplete is completely hosed without this flag on UNIX.
  427. dwExStyle |= WS_EX_MW_UNMANAGED_WINDOW;
  428. #endif
  429. #ifdef AC_TRANSLUCENCY
  430. if (g_bRunOnNT5 && g_fIE)
  431. {
  432. dwExStyle |= WS_EX_LAYERED;
  433. }
  434. #endif
  435. // The dropdown holds a ref on this object
  436. AddRef();
  437. m_hwndDropDown = CreateWindowEx(dwExStyle,
  438. c_szAutoSuggestClass,
  439. c_szAutoSuggestTitle, // GPF dialog is picking up this name!
  440. WS_POPUP | WS_BORDER | WS_CLIPCHILDREN,
  441. 0, 0, 100, 100,
  442. NULL, NULL, HINST_THISDLL, this);
  443. if (m_hwndDropDown)
  444. {
  445. #ifdef AC_TRANSLUCENCY
  446. if (g_fIE)
  447. {
  448. SetLayeredWindowAttributes(m_hwndDropDown,
  449. 0,
  450. 230,
  451. LWA_ALPHA);
  452. }
  453. #endif
  454. m_fDropDownResized = FALSE;
  455. }
  456. else
  457. {
  458. Release();
  459. }
  460. }
  461. else if (!_IsAutoSuggestEnabled() && NULL != m_hwndDropDown)
  462. {
  463. // We don't need the dropdown Window.
  464. if (m_hwndList)
  465. {
  466. DestroyWindow(m_hwndList);
  467. }
  468. DestroyWindow(m_hwndDropDown);
  469. }
  470. }
  471. //+-------------------------------------------------------------------------
  472. // Returns TRUE if autocomplete is currently enabled
  473. //--------------------------------------------------------------------------
  474. BOOL CAutoComplete::IsEnabled()
  475. {
  476. BOOL fRet;
  477. //
  478. // If we have not used the new IAutoComplete2 interface, we revert
  479. // to the old IE4 global registry setting
  480. //
  481. if (m_dwOptions & ACO_UNINITIALIZED)
  482. {
  483. fRet = SHRegGetBoolUSValue(REGSTR_PATH_AUTOCOMPLETE,
  484. REGSTR_VAL_USEAUTOCOMPLETE, FALSE, TRUE);
  485. }
  486. else
  487. {
  488. fRet = (m_dwOptions & (ACO_AUTOAPPEND | ACO_AUTOSUGGEST));
  489. }
  490. return fRet;
  491. }
  492. //+-------------------------------------------------------------------------
  493. // Enables/disables the up down arrow for autocomplete. Used by comboboxes
  494. // to disable arrow keys when the combo box is dropped. (This function is
  495. // now redundent because we check to see of the combo is dropped.)
  496. //--------------------------------------------------------------------------
  497. HRESULT CAutoComplete::Enable(BOOL fEnable)
  498. {
  499. TraceMsg(AC_GENERAL, "CAutoComplete::Enable(0x%x)", fEnable);
  500. HRESULT hr = (m_dwFlags & ACF_IGNOREUPDOWN) ? S_FALSE : S_OK;
  501. if (fEnable)
  502. m_dwFlags &= ~ACF_IGNOREUPDOWN;
  503. else
  504. m_dwFlags |= ACF_IGNOREUPDOWN;
  505. return hr;
  506. }
  507. /* IAutocomplete2 methods */
  508. //+-------------------------------------------------------------------------
  509. // Enables/disables various autocomplete features (see ACO_* flags)
  510. //--------------------------------------------------------------------------
  511. HRESULT CAutoComplete::SetOptions(DWORD dwOptions)
  512. {
  513. m_dwOptions = dwOptions;
  514. _SeeWhatsEnabled();
  515. return S_OK;
  516. }
  517. //+-------------------------------------------------------------------------
  518. // Returns the current option settings
  519. //--------------------------------------------------------------------------
  520. HRESULT CAutoComplete::GetOptions(DWORD* pdwOptions)
  521. {
  522. HRESULT hr = E_INVALIDARG;
  523. if (pdwOptions)
  524. {
  525. *pdwOptions = m_dwOptions;
  526. hr = S_OK;
  527. }
  528. return hr;
  529. }
  530. /* IAutocompleteDropDown methods */
  531. //+-------------------------------------------------------------------------
  532. // Returns the current dropdown status
  533. //--------------------------------------------------------------------------
  534. HRESULT CAutoComplete::GetDropDownStatus(DWORD *pdwFlags, LPWSTR *ppwszString)
  535. {
  536. if (m_hwndDropDown && IsWindowVisible(m_hwndDropDown))
  537. {
  538. if (pdwFlags)
  539. {
  540. *pdwFlags = ACDD_VISIBLE;
  541. }
  542. if (ppwszString)
  543. {
  544. *ppwszString=NULL;
  545. if (m_hwndList)
  546. {
  547. int iCurSel = ListView_GetNextItem(m_hwndList, -1, LVNI_SELECTED);
  548. if (iCurSel != -1)
  549. {
  550. WCHAR szBuf[MAX_URL_STRING];
  551. _GetItem(iCurSel, szBuf, ARRAYSIZE(szBuf), FALSE);
  552. *ppwszString = (LPWSTR) CoTaskMemAlloc((lstrlenW(szBuf)+1)*sizeof(WCHAR));
  553. if (*ppwszString)
  554. {
  555. StrCpyW(*ppwszString, szBuf);
  556. }
  557. }
  558. }
  559. }
  560. }
  561. else
  562. {
  563. if (pdwFlags)
  564. {
  565. *pdwFlags = 0;
  566. }
  567. if (ppwszString)
  568. {
  569. *ppwszString = NULL;
  570. }
  571. }
  572. return S_OK;
  573. }
  574. HRESULT CAutoComplete::ResetEnumerator()
  575. {
  576. _StopSearch();
  577. _ResetSearch();
  578. _FreeDPAPtrs(m_hdpa);
  579. m_hdpa = NULL;
  580. // If the dropdown is currently visible, re-search the IEnumString
  581. // and show the dropdown. Otherwise wait for user input.
  582. if (m_hwndDropDown && IsWindowVisible(m_hwndDropDown))
  583. {
  584. _StartCompletion(FALSE, TRUE);
  585. }
  586. return S_OK;
  587. }
  588. /* IEnumString methods */
  589. //+-------------------------------------------------------------------------
  590. // Resets the IEnumString functionality exposed for external users.
  591. //--------------------------------------------------------------------------
  592. HRESULT CAutoComplete::Reset()
  593. {
  594. HRESULT hr = E_FAIL;
  595. if (!m_szEnumString) // If we needed it once, we will most likely continue to need it.
  596. m_szEnumString = (LPTSTR) LocalAlloc(LPTR, MAX_URL_STRING * SIZEOF(TCHAR));
  597. if (!m_szEnumString)
  598. return E_OUTOFMEMORY;
  599. GetWindowText(m_hwndEdit, m_szEnumString, MAX_URL_STRING);
  600. if (m_pesExtern)
  601. hr = m_pesExtern->Reset();
  602. else
  603. {
  604. hr = m_pes->Clone(&m_pesExtern);
  605. if (SUCCEEDED(hr))
  606. hr = m_pesExtern->Reset();
  607. }
  608. return hr;
  609. }
  610. //+-------------------------------------------------------------------------
  611. // Returns the next BSTR from the autocomplete enumeration.
  612. //
  613. // For consistant results, the caller should not allow the AutoComplete text
  614. // to change between one call to Next() and another call to Next().
  615. // AutoComplete text should change only before Reset() is called.
  616. //--------------------------------------------------------------------------
  617. HRESULT CAutoComplete::Next
  618. (
  619. ULONG celt, // number items to fetch, needs to be 1
  620. LPOLESTR *rgelt, // returned BSTR, caller must free
  621. ULONG *pceltFetched // number of items returned
  622. )
  623. {
  624. HRESULT hr = S_FALSE;
  625. LPOLESTR pwszUrl;
  626. ULONG cFetched;
  627. // Pre-init in case of error
  628. if (rgelt)
  629. *rgelt = NULL;
  630. if (pceltFetched)
  631. *pceltFetched = 0;
  632. if (!EVAL(rgelt) || (!EVAL(pceltFetched)) || (!EVAL(1 == celt)) || !EVAL(m_pesExtern))
  633. return E_INVALIDARG;
  634. while (S_OK == (hr = m_pesExtern->Next(1, &pwszUrl, &cFetched)))
  635. {
  636. if (!StrCmpNI(m_szEnumString, pwszUrl, lstrlen(m_szEnumString)))
  637. {
  638. TraceMsg(TF_BAND|TF_GENERAL, "CAutoComplete: Next(). AutoSearch Failed URL=%s.", pwszUrl);
  639. break;
  640. }
  641. else
  642. {
  643. // If the string can't be added to our list, we will free it.
  644. TraceMsg(TF_BAND|TF_GENERAL, "CAutoComplete: Next(). AutoSearch Match URL=%s.", pwszUrl);
  645. CoTaskMemFree(pwszUrl);
  646. }
  647. }
  648. if (S_OK == hr)
  649. {
  650. *rgelt = (LPOLESTR)pwszUrl;
  651. *pceltFetched = 1; // We will always only fetch one.
  652. }
  653. return hr;
  654. }
  655. CAutoComplete::CAutoComplete() : m_cRef(1)
  656. {
  657. DllAddRef();
  658. TraceMsg(AC_GENERAL, "CAutoComplete::CAutoComplete()");
  659. // This class requires that this COM object be allocated in Zero INITed
  660. // memory. If the asserts below go off, then this was violated.
  661. ASSERT(!m_dwFlags);
  662. ASSERT(!m_hwndEdit);
  663. ASSERT(!m_pszCurrent);
  664. ASSERT(!m_iCurrent);
  665. ASSERT(!m_dwLastSearchFlags);
  666. ASSERT(!m_pes);
  667. ASSERT(!m_pacl);
  668. ASSERT(!m_pesExtern);
  669. ASSERT(!m_szEnumString);
  670. ASSERT(!m_pThread);
  671. m_dwOptions = ACO_UNINITIALIZED;
  672. m_hfontListView = NULL;
  673. }
  674. CAutoComplete::~CAutoComplete()
  675. {
  676. TraceMsg(AC_GENERAL, "CAutoComplete::~CAutoComplete()");
  677. ASSERT(m_hwndDropDown == NULL)
  678. SAFERELEASE(m_pes);
  679. SAFERELEASE(m_pacl);
  680. SAFERELEASE(m_pesExtern);
  681. SetStr(&m_pszCurrent, NULL);
  682. if (m_szEnumString)
  683. LocalFree(m_szEnumString);
  684. if (m_hdpaSortIndex)
  685. {
  686. // Note that this list pointed to items in m_hdpa, so we don't need
  687. // to free the items pointed to by this list.
  688. DPA_Destroy(m_hdpaSortIndex);
  689. m_hdpaSortIndex = NULL;
  690. }
  691. _FreeDPAPtrs(m_hdpa);
  692. if (m_pThread)
  693. {
  694. m_pThread->SyncShutDownBGThread();
  695. SAFERELEASE(m_pThread);
  696. }
  697. DllRelease();
  698. }
  699. STDMETHODIMP CAutoComplete::get_accName(VARIANT varChild, BSTR *pszName)
  700. {
  701. HRESULT hr;
  702. if (varChild.vt == VT_I4)
  703. {
  704. if (varChild.lVal > 0)
  705. {
  706. WCHAR szBuf[MAX_URL_STRING];
  707. _GetItem(varChild.lVal - 1, szBuf, ARRAYSIZE(szBuf), TRUE);
  708. *pszName = SysAllocString(szBuf);
  709. }
  710. else
  711. {
  712. *pszName = NULL;
  713. }
  714. hr = S_OK;
  715. }
  716. else
  717. {
  718. hr = E_UNEXPECTED;
  719. }
  720. return hr;
  721. }
  722. //+-------------------------------------------------------------------------
  723. // Private initialization
  724. //--------------------------------------------------------------------------
  725. BOOL CAutoComplete::_Init()
  726. {
  727. m_pThread = new CACThread(*this);
  728. return (NULL != m_pThread);
  729. }
  730. //+-------------------------------------------------------------------------
  731. // Creates and instance of CAutoComplete
  732. //--------------------------------------------------------------------------
  733. HRESULT CAutoComplete_CreateInstance(IUnknown* pUnkOuter, IUnknown** ppunk, LPCOBJECTINFO poi)
  734. {
  735. // Note - Aggregation checking is handled in class factory
  736. *ppunk = NULL;
  737. CAutoComplete* p = new CAutoComplete();
  738. if (p)
  739. {
  740. if (p->_Init())
  741. {
  742. *ppunk = SAFECAST(p, IAutoComplete *);
  743. return S_OK;
  744. }
  745. delete p;
  746. }
  747. return E_OUTOFMEMORY;
  748. }
  749. //+-------------------------------------------------------------------------
  750. // Helper function to add default autocomplete functionality to and edit
  751. // window.
  752. //--------------------------------------------------------------------------
  753. HRESULT SHUseDefaultAutoComplete
  754. (
  755. HWND hwndEdit,
  756. IBrowserService * pbs, IN OPTIONAL
  757. IAutoComplete2 ** ppac, OUT OPTIONAL
  758. IShellService ** ppssACLISF, OUT OPTIONAL
  759. BOOL fUseCMDMRU
  760. )
  761. {
  762. HRESULT hr;
  763. IUnknown * punkACLMulti;
  764. if (ppac)
  765. *ppac = NULL;
  766. if (ppssACLISF)
  767. *ppssACLISF = NULL;
  768. hr = CoCreateInstance(CLSID_ACLMulti, NULL, CLSCTX_INPROC_SERVER, IID_IUnknown, (void **)&punkACLMulti);
  769. if (SUCCEEDED(hr))
  770. {
  771. IObjMgr * pomMulti;
  772. hr = punkACLMulti->QueryInterface(IID_IObjMgr, (LPVOID *)&pomMulti);
  773. if (SUCCEEDED(hr))
  774. {
  775. BOOL fReady = FALSE; // Fail only if all we are not able to create at least one list.
  776. // ADD The MRU List
  777. IUnknown * punkACLMRU;
  778. // MRU for run dialog no longer adds URL MRU automatically
  779. // so we have to add it ourselves
  780. if (fUseCMDMRU)
  781. {
  782. hr = CACLMRU_CreateInstance(NULL, &punkACLMRU, NULL, SZ_REGKEY_TYPEDCMDMRU);
  783. if (SUCCEEDED(hr))
  784. {
  785. pomMulti->Append(punkACLMRU);
  786. punkACLMRU->Release();
  787. fReady = TRUE;
  788. }
  789. }
  790. hr = CACLMRU_CreateInstance(NULL, &punkACLMRU, NULL, SZ_REGKEY_TYPEDURLMRU);
  791. if (SUCCEEDED(hr))
  792. {
  793. pomMulti->Append(punkACLMRU);
  794. punkACLMRU->Release();
  795. fReady = TRUE;
  796. }
  797. // ADD The History List
  798. IUnknown * punkACLHist;
  799. hr = CoCreateInstance(CLSID_ACLHistory, NULL, CLSCTX_INPROC_SERVER, IID_IUnknown, (void **)&punkACLHist);
  800. if (SUCCEEDED(hr))
  801. {
  802. pomMulti->Append(punkACLHist);
  803. punkACLHist->Release();
  804. fReady = TRUE;
  805. }
  806. // ADD The ISF List
  807. IUnknown * punkACLISF;
  808. hr = CoCreateInstance(CLSID_ACListISF, NULL, CLSCTX_INPROC_SERVER, IID_IUnknown, (void **)&punkACLISF);
  809. if (SUCCEEDED(hr))
  810. {
  811. // We need to give the ISF AutoComplete List a pointer to the IBrowserService
  812. // so it can retrieve the current browser location to AutoComplete correctly.
  813. IShellService * pss;
  814. hr = punkACLISF->QueryInterface(IID_IShellService, (LPVOID *)&pss);
  815. if (SUCCEEDED(hr))
  816. {
  817. if (pbs)
  818. pss->SetOwner(pbs);
  819. if (ppssACLISF)
  820. *ppssACLISF = pss;
  821. else
  822. pss->Release();
  823. }
  824. //
  825. // Set options
  826. //
  827. IACList2* pacl;
  828. if (SUCCEEDED(punkACLISF->QueryInterface(IID_IACList2, (LPVOID *)&pacl)))
  829. {
  830. // Specify directories to search
  831. pacl->SetOptions(ACLO_CURRENTDIR | ACLO_FAVORITES | ACLO_MYCOMPUTER | ACLO_DESKTOP);
  832. pacl->Release();
  833. }
  834. pomMulti->Append(punkACLISF);
  835. punkACLISF->Release();
  836. fReady = TRUE;
  837. }
  838. if (fReady)
  839. {
  840. IAutoComplete2 * pac;
  841. // Create the AutoComplete Object
  842. hr = CoCreateInstance(CLSID_AutoComplete, NULL, CLSCTX_INPROC_SERVER, IID_IAutoComplete2, (void **)&pac);
  843. if (SUCCEEDED(hr))
  844. {
  845. // Load the quick complete string.
  846. WCHAR szQuickComplete[50]; // US string is 17 characters, 50 should be plenty without blowing the stack
  847. MLLoadString(IDS_QUICKCOMPLETE, szQuickComplete, ARRAYSIZE(szQuickComplete));
  848. hr = pac->Init(hwndEdit, punkACLMulti, WZ_REGKEY_QUICKCOMPLETE, szQuickComplete);
  849. if (ppac)
  850. *ppac = pac;
  851. else
  852. pac->Release();
  853. }
  854. }
  855. pomMulti->Release();
  856. }
  857. punkACLMulti->Release();
  858. }
  859. return hr;
  860. }
  861. /* Private functions */
  862. //+-------------------------------------------------------------------------
  863. // Removes anything that we appended to the edit text
  864. //--------------------------------------------------------------------------
  865. void CAutoComplete::_RemoveCompletion()
  866. {
  867. TraceMsg(AC_GENERAL, "CAutoComplete::_RemoveCompletion()");
  868. if (m_fAppended)
  869. {
  870. // Remove any highlighted text that we displayed
  871. Edit_ReplaceSel(m_hwndEdit, TEXT(""));
  872. m_fAppended = FALSE;
  873. }
  874. }
  875. //+-------------------------------------------------------------------------
  876. // Updates the text in the edit control
  877. //--------------------------------------------------------------------------
  878. void CAutoComplete::_SetEditText(LPCWSTR psz)
  879. {
  880. //
  881. // We set a flag so that we can distinguish between us setting the text
  882. // and someone else doing it. If someone else sets the text we hide our
  883. // dropdown.
  884. //
  885. m_fSettingText = TRUE;
  886. // Don't display our special wildcard search string
  887. if (psz[0] == CH_WILDCARD)
  888. {
  889. Edit_SetText(m_hwndEdit, L"");
  890. }
  891. else
  892. {
  893. Edit_SetText(m_hwndEdit, psz);
  894. }
  895. m_fSettingText = FALSE;
  896. }
  897. //+-------------------------------------------------------------------------
  898. // Removed anything that we appended to the edit text and then updates
  899. // m_pszCurrent with the current string.
  900. //--------------------------------------------------------------------------
  901. void CAutoComplete::_GetEditText(void)
  902. {
  903. TraceMsg(AC_GENERAL, "CAutoComplete::_GetEditText()");
  904. _RemoveCompletion(); // remove anything we added
  905. int iCurrent = GetWindowTextLength(m_hwndEdit);
  906. //
  907. // If the current buffer is too small, delete it.
  908. //
  909. if (m_pszCurrent &&
  910. LocalSize(m_pszCurrent) <= (UINT)(iCurrent + 1) * sizeof(TCHAR))
  911. {
  912. SetStr(&m_pszCurrent, NULL);
  913. }
  914. //
  915. // If there is no current buffer, try to allocate one
  916. // with some room to grow.
  917. //
  918. if (!m_pszCurrent)
  919. {
  920. m_pszCurrent = (LPTSTR)LocalAlloc(LPTR, (iCurrent + (MAX_URL_STRING / 2)) * SIZEOF(TCHAR));
  921. }
  922. //
  923. // If we have a current buffer, get the text.
  924. //
  925. if (m_pszCurrent)
  926. {
  927. if (!GetWindowText(m_hwndEdit, m_pszCurrent, iCurrent + 1))
  928. {
  929. *m_pszCurrent = L'\0';
  930. }
  931. // On win9x GetWindowTextLength can return more than the # of characters
  932. m_iCurrent = lstrlen(m_pszCurrent);
  933. }
  934. else
  935. {
  936. m_iCurrent = 0;
  937. }
  938. }
  939. //+-------------------------------------------------------------------------
  940. // Updates the text in the edit control
  941. //--------------------------------------------------------------------------
  942. void CAutoComplete::_UpdateText
  943. (
  944. int iStartSel, // start location for selected
  945. int iEndSel, // end location of selected text
  946. LPCTSTR pszCurrent, // unselected text
  947. LPCTSTR pszNew // autocompleted (selected) text
  948. )
  949. {
  950. TraceMsg(AC_GENERAL, "CAutoComplete::_UpdateText(iStart=%i; iEndSel = %i, pszCurrent=>%s<, pszNew=>%s<)",
  951. iStartSel, iEndSel, (pszCurrent ? pszCurrent : TEXT("(null)")), (pszNew ? pszNew : TEXT("(null)")));
  952. //
  953. // Restore the old text.
  954. //
  955. _SetEditText(pszCurrent);
  956. //
  957. // Put the cursor at the insertion point.
  958. //
  959. Edit_SetSel(m_hwndEdit, iStartSel, iStartSel);
  960. //
  961. // Insert the new text.
  962. //
  963. Edit_ReplaceSel(m_hwndEdit, pszNew);
  964. //
  965. // Select the newly added text.
  966. //
  967. Edit_SetSel(m_hwndEdit, iStartSel, iEndSel);
  968. }
  969. //+-------------------------------------------------------------------------
  970. // If pwszQuickComplete is NULL, we will use our internal default.
  971. // pwszRegKeyValue can be NULL indicating that there is not a key.
  972. //--------------------------------------------------------------------------
  973. BOOL CAutoComplete::_SetQuickCompleteStrings(LPCOLESTR pwszRegKeyPath, LPCOLESTR pwszQuickComplete)
  974. {
  975. TraceMsg(AC_GENERAL, "CAutoComplete::_SetQuickCompleteStrings(pwszRegKeyPath=0x%x, pwszQuickComplete = 0x%x)",
  976. pwszRegKeyPath, pwszQuickComplete);
  977. if (pwszRegKeyPath)
  978. {
  979. lstrcpyn(m_szRegKeyPath, pwszRegKeyPath, ARRAYSIZE(m_szRegKeyPath));
  980. }
  981. else
  982. {
  983. // can be empty
  984. m_szRegKeyPath[0] = TEXT('\0');
  985. }
  986. if (pwszQuickComplete)
  987. {
  988. lstrcpyn(m_szQuickComplete, pwszQuickComplete, ARRAYSIZE(m_szQuickComplete));
  989. }
  990. else
  991. {
  992. // use default value
  993. lstrcpyn(m_szQuickComplete, c_szAutoDefQuickComp, ARRAYSIZE(m_szQuickComplete));
  994. }
  995. return TRUE;
  996. }
  997. //+-------------------------------------------------------------------------
  998. // Formats the current contents of the edit box with the appropriate prefix
  999. // and endfix and returns the completed string.
  1000. //--------------------------------------------------------------------------
  1001. LPTSTR CAutoComplete::_QuickEnter()
  1002. {
  1003. //
  1004. // If they shift-enter, then do the favorite pre/post-fix.
  1005. //
  1006. TCHAR szFormat[MAX_QUICK_COMPLETE_STRING];
  1007. TCHAR szNewText[MAX_URL_STRING];
  1008. int iLen;
  1009. TraceMsg(AC_GENERAL, "CAutoComplete::_QuickEnter()");
  1010. if (NULL == m_pszCurrent)
  1011. {
  1012. return NULL;
  1013. }
  1014. lstrcpyn(szFormat, m_szQuickComplete, ARRAYSIZE(szFormat));
  1015. DWORD cb = sizeof(szFormat);
  1016. SHGetValue(HKEY_CURRENT_USER, m_szRegKeyPath, TEXT("QuickComplete"), NULL, &szFormat, &cb);
  1017. //
  1018. // Remove preceeding and trailing white space
  1019. //
  1020. PathRemoveBlanks(m_pszCurrent);
  1021. //
  1022. // Make sure we don't GPF.
  1023. //
  1024. iLen = lstrlen(m_pszCurrent) + lstrlen(szFormat);
  1025. if (iLen < ARRAYSIZE(szNewText))
  1026. {
  1027. // If the quick complete is already present, don't add it again
  1028. LPWSTR pszInsertion = StrStrI(szFormat, L"%s");
  1029. LPWSTR pszFormat = szFormat;
  1030. if (pszInsertion)
  1031. {
  1032. // If prefix is already present, don't add it again.
  1033. // (we could improve this to only add parts of the predfix that are missing)
  1034. int iInsertion = (int)(pszInsertion - pszFormat);
  1035. if (iInsertion == 0 || StrCmpNI(pszFormat, m_pszCurrent, iInsertion) == 0)
  1036. {
  1037. // Skip over prefix
  1038. pszFormat = pszInsertion;
  1039. }
  1040. // If postfix is already present, don't add it again.
  1041. LPWSTR pszPostFix = pszInsertion + ARRAYSIZE(L"%s") - 1;
  1042. int cchCurrent = lstrlen(m_pszCurrent);
  1043. int cchPostFix = lstrlen(pszPostFix);
  1044. if (cchPostFix > 0 && cchPostFix < cchCurrent &&
  1045. StrCmpI(m_pszCurrent + (cchCurrent - cchPostFix), pszPostFix) == 0)
  1046. {
  1047. // Lop off postfix
  1048. *pszPostFix = 0;
  1049. }
  1050. }
  1051. wnsprintf(szNewText, ARRAYSIZE(szNewText), pszFormat, m_pszCurrent);
  1052. SetStr(&m_pszCurrent, szNewText);
  1053. }
  1054. return m_pszCurrent;
  1055. }
  1056. BOOL CAutoComplete::_ResetSearch(void)
  1057. {
  1058. TraceMsg(AC_GENERAL, "CAutoComplete::_ResetSearch()");
  1059. m_dwFlags = ACF_RESET;
  1060. return TRUE;
  1061. }
  1062. //+-------------------------------------------------------------------------
  1063. // Returns TRUE if the char is a forward or backackwards slash
  1064. //--------------------------------------------------------------------------
  1065. BOOL CAutoComplete::_IsWhack(TCHAR ch)
  1066. {
  1067. return (ch == TEXT('/')) || (ch == TEXT('\\'));
  1068. }
  1069. //+-------------------------------------------------------------------------
  1070. // Returns TRUE if the string points to a character used to separate words
  1071. //--------------------------------------------------------------------------
  1072. BOOL CAutoComplete::_IsBreakChar(WCHAR wch)
  1073. {
  1074. // Do a binary search in our table of break characters
  1075. int iMin = 0;
  1076. int iMax = ARRAYSIZE(g_szBreakChars) - 1;
  1077. while (iMax - iMin >= 2)
  1078. {
  1079. int iTry = (iMax + iMin + 1) / 2;
  1080. if (wch < g_szBreakChars[iTry])
  1081. iMax = iTry;
  1082. else if (wch > g_szBreakChars[iTry])
  1083. iMin = iTry;
  1084. else
  1085. return TRUE;
  1086. }
  1087. return (wch == g_szBreakChars[iMin] || wch == g_szBreakChars[iMax]);
  1088. }
  1089. //+-------------------------------------------------------------------------
  1090. // Returns TRUE if we want to append to the current edit box contents
  1091. //--------------------------------------------------------------------------
  1092. BOOL CAutoComplete::_WantToAppendResults()
  1093. {
  1094. //
  1095. // Users get annoyed if we append real text after a
  1096. // slash, because they type "c:\" and we complete
  1097. // it to "c:\windows" when they aren't looking.
  1098. //
  1099. // Also, it's annoying to have "\" autocompleted to "\\"
  1100. //
  1101. return (m_pszCurrent &&
  1102. (!(_IsWhack(m_pszCurrent[0]) && m_pszCurrent[1] == NULL) &&
  1103. !_IsWhack(m_pszCurrent[lstrlen(m_pszCurrent)-1])));
  1104. }
  1105. #ifdef UNIX
  1106. extern "C" BOOL MwTranslateUnixKeyBinding( HWND hwnd, DWORD message,
  1107. WPARAM *pwParam, DWORD *pModifiers );
  1108. #endif
  1109. //+-------------------------------------------------------------------------
  1110. // Callback routine used by the edit window to determine where to break
  1111. // words. We install this custom callback dor the ctl arrow keys
  1112. // recognize our break characters.
  1113. //--------------------------------------------------------------------------
  1114. int CALLBACK CAutoComplete::EditWordBreakProcW
  1115. (
  1116. LPWSTR pszEditText, // pointer to edit text
  1117. int ichCurrent, // index of starting point
  1118. int cch, // length in characters of edit text
  1119. int code // action to take
  1120. )
  1121. {
  1122. LPWSTR psz = pszEditText + ichCurrent;
  1123. int iIndex;
  1124. BOOL fFoundNonDelimiter = FALSE;
  1125. static BOOL fRight = FALSE; // hack due to bug in USER
  1126. switch (code)
  1127. {
  1128. case WB_ISDELIMITER:
  1129. fRight = TRUE;
  1130. // Simple case - is the current character a delimiter?
  1131. iIndex = (int)_IsBreakChar(*psz);
  1132. break;
  1133. case WB_LEFT:
  1134. // Move to the left to find the first delimiter. If we are
  1135. // currently at a delimiter, then skip delimiters until we
  1136. // find the first non-delimiter, then start from there.
  1137. //
  1138. // Special case for fRight - if we are currently at a delimiter
  1139. // then just return the current word!
  1140. while ((psz = CharPrev(pszEditText, psz)) != pszEditText)
  1141. {
  1142. if (_IsBreakChar(*psz))
  1143. {
  1144. if (fRight || fFoundNonDelimiter)
  1145. break;
  1146. }
  1147. else
  1148. {
  1149. fFoundNonDelimiter = TRUE;
  1150. fRight = FALSE;
  1151. }
  1152. }
  1153. iIndex = (int)(psz - pszEditText);
  1154. // We are currently pointing at the delimiter, next character
  1155. // is the beginning of the next word.
  1156. if (iIndex > 0 && iIndex < cch)
  1157. iIndex++;
  1158. break;
  1159. case WB_RIGHT:
  1160. fRight = FALSE;
  1161. // If we are not at a delimiter, then skip to the right until
  1162. // we find the first delimiter. If we started at a delimiter, or
  1163. // we have just finished scanning to the first delimiter, then
  1164. // skip all delimiters until we find the first non delimiter.
  1165. //
  1166. // Careful - the string passed in to us may not be NULL terminated!
  1167. fFoundNonDelimiter = !_IsBreakChar(*psz);
  1168. if (psz != (pszEditText + cch))
  1169. {
  1170. while ((psz = CharNext(psz)) != (pszEditText + cch))
  1171. {
  1172. if (_IsBreakChar(*psz))
  1173. {
  1174. fFoundNonDelimiter = FALSE;
  1175. }
  1176. else
  1177. {
  1178. if (!fFoundNonDelimiter)
  1179. break;
  1180. }
  1181. }
  1182. }
  1183. // We are currently pointing at the next word.
  1184. iIndex = (int) (psz - pszEditText);
  1185. break;
  1186. default:
  1187. iIndex = 0;
  1188. break;
  1189. }
  1190. return iIndex;
  1191. }
  1192. //+-------------------------------------------------------------------------
  1193. // Returns the index of the next or previous break character in m_pszCurrent
  1194. //--------------------------------------------------------------------------
  1195. int CAutoComplete::_JumpToNextBreak
  1196. (
  1197. int iLoc, // current location
  1198. DWORD dwFlags // direction (WB_RIGHT or WB_LEFT)
  1199. )
  1200. {
  1201. return EditWordBreakProcW(m_pszCurrent, iLoc, lstrlen(m_pszCurrent), dwFlags);
  1202. }
  1203. //+-------------------------------------------------------------------------
  1204. // Handles Horizontal cursor movement. Returns TRUE if the message should
  1205. // passed on to the OS. Note that we only call this on win9x. On NT we
  1206. // use EM_SETWORDBREAKPROC to set a callback instead because it sets the
  1207. // caret correctly. This callback can crash on win9x.
  1208. //--------------------------------------------------------------------------
  1209. BOOL CAutoComplete::_CursorMovement
  1210. (
  1211. WPARAM wParam // virtual key data from WM_KEYDOWN
  1212. )
  1213. {
  1214. BOOL fShift, fControl;
  1215. DWORD dwKey = (DWORD)wParam;
  1216. int iStart, iEnd;
  1217. TraceMsg(AC_GENERAL, "CAutoComplete::_CursorMovement(wParam = 0x%x)",
  1218. wParam);
  1219. fShift = (0 > GetKeyState(VK_SHIFT)) ;
  1220. fControl = (0 > GetKeyState(VK_CONTROL));
  1221. // We don't do anything special unless the CTRL
  1222. // key is down so we don't want to mess up arrowing around
  1223. // UNICODE character clusters. (INDIC o+d+j+d+k+w)
  1224. if (!fControl)
  1225. return TRUE; // let OS handle because of UNICODE char clusters
  1226. #ifdef UNIX
  1227. {
  1228. DWORD dwModifiers;
  1229. dwModifiers = 0;
  1230. if ( fShift ) {
  1231. dwModifiers |= FSHIFT;
  1232. }
  1233. if ( fControl ) {
  1234. dwModifiers |= FCONTROL;
  1235. }
  1236. MwTranslateUnixKeyBinding( m_hwndEdit, WM_KEYDOWN,
  1237. (WPARAM*) &dwKey, &dwModifiers );
  1238. fShift = ( dwModifiers & FSHIFT );
  1239. fControl = ( dwModifiers & FCONTROL );
  1240. }
  1241. #endif
  1242. // get the current selection
  1243. SendMessage(m_hwndEdit, EM_GETSEL, (WPARAM)&iStart, (LPARAM)&iEnd);
  1244. // the user is editting the text, so this is now invalid.
  1245. m_dwFlags = ACF_RESET;
  1246. _GetEditText();
  1247. if (!m_pszCurrent)
  1248. return TRUE; // we didn't handle it... let the default wndproc try
  1249. // Determine the previous selection direction
  1250. int dwSelectionDirection;
  1251. if (iStart == iEnd)
  1252. {
  1253. // Nothing previously selected, so use new direction
  1254. dwSelectionDirection = dwKey;
  1255. }
  1256. else
  1257. {
  1258. // Base the selection direction on whether the caret is positioned
  1259. // at the beginning or the end of the selection
  1260. POINT pt;
  1261. int cchCaret = iEnd;
  1262. if (GetCaretPos(&pt))
  1263. {
  1264. cchCaret = (int)SendMessage(m_hwndEdit, EM_CHARFROMPOS, 0, (LPARAM)MAKELPARAM(pt.x, 0));
  1265. }
  1266. dwSelectionDirection = (cchCaret >= iEnd) ? VK_RIGHT : VK_LEFT;
  1267. }
  1268. if (fControl)
  1269. {
  1270. if (dwKey == VK_RIGHT)
  1271. {
  1272. // did we orginally go to the left?
  1273. if (dwSelectionDirection == VK_LEFT)
  1274. {
  1275. // yes...unselect
  1276. iStart = _JumpToNextBreak(iStart, WB_RIGHT);
  1277. // if (!iStart)
  1278. // iStart = m_iCurrent;
  1279. }
  1280. else if (iEnd != m_iCurrent)
  1281. {
  1282. // select or "jump over" characters
  1283. iEnd = _JumpToNextBreak(iEnd, WB_RIGHT);
  1284. // if (!iEnd)
  1285. // iEnd = m_iCurrent;
  1286. }
  1287. }
  1288. else // dwKey == VK_LEFT
  1289. {
  1290. // did we orginally go to the right?
  1291. if (dwSelectionDirection == VK_RIGHT)
  1292. {
  1293. // yes...unselect
  1294. // int iRemember = iEnd;
  1295. iEnd = _JumpToNextBreak(iEnd, WB_LEFT);
  1296. }
  1297. else if (iStart) // != 0
  1298. {
  1299. // select or "jump over" characters
  1300. iStart = _JumpToNextBreak(iStart, WB_LEFT);
  1301. }
  1302. }
  1303. }
  1304. else // if !fControl
  1305. {
  1306. // This code is benign if the SHIFT key isn't down
  1307. // because it has to do with modifying the selection.
  1308. if (dwKey == VK_RIGHT)
  1309. {
  1310. if (dwSelectionDirection == VK_LEFT)
  1311. {
  1312. iStart++;
  1313. }
  1314. else
  1315. {
  1316. iEnd++;
  1317. }
  1318. }
  1319. else // dwKey == VK_LEFT
  1320. {
  1321. LPTSTR pszPrev;
  1322. if (dwSelectionDirection == VK_RIGHT)
  1323. {
  1324. pszPrev = CharPrev(m_pszCurrent, &m_pszCurrent[iEnd]);
  1325. iEnd = (int)(pszPrev - m_pszCurrent);
  1326. }
  1327. else
  1328. {
  1329. pszPrev = CharPrev(m_pszCurrent, &m_pszCurrent[iStart]);
  1330. iStart = (int)(pszPrev - m_pszCurrent);
  1331. }
  1332. }
  1333. }
  1334. // Are we selecting or moving?
  1335. if (!fShift)
  1336. { // just moving...
  1337. if (dwKey == VK_RIGHT)
  1338. {
  1339. iStart = iEnd;
  1340. }
  1341. else // pachi->dwSelectionDirection == VK_LEFT
  1342. {
  1343. iEnd = iStart;
  1344. }
  1345. }
  1346. //
  1347. // If we are selecting text to the left, we have to jump hoops
  1348. // to get the caret on the left of the selection. Edit_SetSel
  1349. // always places the caret on the right, and if we position the
  1350. // caret ourselves the edit control still uses the old caret
  1351. // position. So we have to send VK_LEFT messages to the edit
  1352. // control to get it to select things properly.
  1353. //
  1354. if (fShift && dwSelectionDirection == VK_LEFT && iStart < iEnd)
  1355. {
  1356. // Temporarily reset the control key (yuk!)
  1357. BYTE keyState[256];
  1358. BOOL fGetKeyboardState;
  1359. if (fControl)
  1360. {
  1361. fGetKeyboardState = GetKeyboardState(keyState);
  1362. if (fGetKeyboardState )
  1363. {
  1364. keyState[VK_CONTROL] &= 0x7f;
  1365. SetKeyboardState(keyState);
  1366. }
  1367. }
  1368. // Select the last character and select left
  1369. // one character at a time. Arrrggg.
  1370. SendMessage(m_hwndEdit, WM_SETREDRAW, FALSE, 0);
  1371. Edit_SetSel(m_hwndEdit, iEnd, iEnd);
  1372. while (iEnd > iStart)
  1373. {
  1374. DefSubclassProc(m_hwndEdit, WM_KEYDOWN, VK_LEFT, 0);
  1375. --iEnd;
  1376. }
  1377. SendMessage(m_hwndEdit, WM_SETREDRAW, TRUE, 0);
  1378. InvalidateRect(m_hwndEdit, NULL, FALSE);
  1379. UpdateWindow(m_hwndEdit);
  1380. // Restore the control key
  1381. if (fControl && fGetKeyboardState )
  1382. {
  1383. keyState[VK_CONTROL] |= 0x80;
  1384. SetKeyboardState(keyState);
  1385. }
  1386. }
  1387. else
  1388. {
  1389. Edit_SetSel(m_hwndEdit, iStart, iEnd);
  1390. }
  1391. return FALSE; // we handled it
  1392. }
  1393. //+-------------------------------------------------------------------------
  1394. // Process WM_KEYDOWN message. Returns TRUE if the message should be passed
  1395. // to the original wndproc.
  1396. //--------------------------------------------------------------------------
  1397. BOOL CAutoComplete::_OnKeyDown(WPARAM wParam)
  1398. {
  1399. WPARAM wParamTranslated;
  1400. TraceMsg(AC_GENERAL, "CAutoComplete::_OnKeyDown(wParam = 0x%x)",
  1401. wParam);
  1402. if (m_pThread->IsDisabled())
  1403. {
  1404. //
  1405. // Let the original wndproc handle it.
  1406. //
  1407. return TRUE;
  1408. }
  1409. wParamTranslated = wParam;
  1410. #ifdef UNIX
  1411. // Don't pass in HWND as the edit control will and we don't
  1412. // want translation to cause two SendMessages for the same
  1413. // control
  1414. DWORD dwModifiers;
  1415. dwModifiers = 0;
  1416. if (GetKeyState(VK_CONTROL) < 0) {
  1417. dwModifiers |= FCONTROL;
  1418. }
  1419. MwTranslateUnixKeyBinding( NULL, WM_KEYDOWN,
  1420. &wParamTranslated, &dwModifiers );
  1421. #endif
  1422. switch (wParamTranslated)
  1423. {
  1424. case VK_RETURN:
  1425. {
  1426. #ifndef UNIX
  1427. if (0 > GetKeyState(VK_CONTROL))
  1428. #else
  1429. if (dwModifiers & FCONTROL)
  1430. #endif
  1431. {
  1432. //
  1433. // Ctrl-Enter does some quick formatting.
  1434. //
  1435. _GetEditText();
  1436. _SetEditText(_QuickEnter());
  1437. }
  1438. else
  1439. {
  1440. //
  1441. // Reset the search criteria.
  1442. //
  1443. _ResetSearch();
  1444. //
  1445. // Highlight entire text.
  1446. //
  1447. Edit_SetSel(m_hwndEdit, 0, (LPARAM)-1);
  1448. }
  1449. //
  1450. // Stop any searches that are going on.
  1451. //
  1452. _StopSearch();
  1453. //
  1454. // For intelliforms, if the dropdown is visible and something
  1455. // is selected in the dropdown, we simulate an activation event.
  1456. //
  1457. if (m_hwndList)
  1458. {
  1459. int iCurSel = ListView_GetNextItem(m_hwndList, -1, LVNI_SELECTED);
  1460. if ((iCurSel != -1) && m_hwndDropDown && IsWindowVisible(m_hwndDropDown))
  1461. {
  1462. WCHAR szBuf[MAX_URL_STRING];
  1463. _GetItem(iCurSel, szBuf, ARRAYSIZE(szBuf), FALSE);
  1464. SendMessage(m_hwndEdit, m_uMsgItemActivate, 0, (LPARAM)szBuf);
  1465. }
  1466. }
  1467. //
  1468. // Hide the dropdown
  1469. //
  1470. _HideDropDown();
  1471. // APPCOMPAT: For some reason, the original windproc is ignoring the return key.
  1472. // It should hide the dropdown!
  1473. if (m_hwndCombo)
  1474. {
  1475. SendMessage(m_hwndCombo, CB_SHOWDROPDOWN, FALSE, 0);
  1476. }
  1477. //
  1478. // Let the original wndproc handle it.
  1479. //
  1480. break;
  1481. }
  1482. case VK_ESCAPE:
  1483. _StopSearch();
  1484. _HideDropDown();
  1485. // APPCOMPAT: For some reason, the original windproc is ignoring the enter key.
  1486. // It should hide the dropdown!
  1487. if (m_hwndCombo)
  1488. {
  1489. SendMessage(m_hwndCombo, CB_SHOWDROPDOWN, FALSE, 0);
  1490. }
  1491. break;
  1492. case VK_LEFT:
  1493. case VK_RIGHT:
  1494. // We do our own cursor movement on win9x because EM_SETWORDBREAKPROC is broken.
  1495. if (!g_fRunningOnNT)
  1496. {
  1497. return _CursorMovement(wParam);
  1498. }
  1499. break;
  1500. case VK_PRIOR:
  1501. case VK_UP:
  1502. if (!(m_dwFlags & ACF_IGNOREUPDOWN) && !_IsComboboxDropped())
  1503. {
  1504. //
  1505. // If the dropdown is visible, the up-down keys navigate our list
  1506. //
  1507. if (m_hwndDropDown && IsWindowVisible(m_hwndDropDown))
  1508. {
  1509. int iCurSel = ListView_GetNextItem(m_hwndList, -1, LVNI_SELECTED);
  1510. if (iCurSel == 0)
  1511. {
  1512. // If at top, move back up into the edit box
  1513. // Deselect the dropdown and select the edit box
  1514. ListView_SetItemState(m_hwndList, 0, 0, 0x000f);
  1515. if (m_pszCurrent)
  1516. {
  1517. // Restore original text if they arrow out of listview
  1518. _SetEditText(m_pszCurrent);
  1519. }
  1520. Edit_SetSel(m_hwndEdit, MAX_URL_STRING, MAX_URL_STRING);
  1521. }
  1522. else if (iCurSel != -1)
  1523. {
  1524. // If in middle or at bottom, move up
  1525. SendMessage(m_hwndList, WM_KEYDOWN, wParam, 0);
  1526. SendMessage(m_hwndList, WM_KEYUP, wParam, 0);
  1527. }
  1528. else
  1529. {
  1530. int iSelect = ListView_GetItemCount(m_hwndList)-1;
  1531. // If in edit box, move to bottom
  1532. ListView_SetItemState(m_hwndList, iSelect, LVIS_SELECTED|LVIS_FOCUSED, 0x000f);
  1533. ListView_EnsureVisible(m_hwndList, iSelect, FALSE);
  1534. }
  1535. return FALSE;
  1536. }
  1537. //
  1538. // If Autosuggest drop-down enabled but not popped up then start a search
  1539. // based on the current edit box contents. If the edit box is empty,
  1540. // search for everything.
  1541. //
  1542. else if ((m_dwOptions & ACO_UPDOWNKEYDROPSLIST) && _IsAutoSuggestEnabled())
  1543. {
  1544. // Ensure the background thread knows we have focus
  1545. _GotFocus();
  1546. _StartCompletion(FALSE, TRUE);
  1547. return FALSE;
  1548. }
  1549. //
  1550. // Otherwise we see if we should append the completions in place
  1551. //
  1552. else if (_IsAutoAppendEnabled())
  1553. {
  1554. if (_AppendPrevious(FALSE))
  1555. {
  1556. return FALSE;
  1557. }
  1558. }
  1559. }
  1560. break;
  1561. case VK_NEXT:
  1562. case VK_DOWN:
  1563. if (!(m_dwFlags & ACF_IGNOREUPDOWN) && !_IsComboboxDropped())
  1564. {
  1565. //
  1566. // If the dropdown is visible, the up-down keys navigate our list
  1567. //
  1568. if (m_hwndDropDown && IsWindowVisible(m_hwndDropDown))
  1569. {
  1570. ASSERT(m_hdpa);
  1571. ASSERT(DPA_GetPtrCount(m_hdpa) != 0);
  1572. ASSERT(m_iFirstMatch != -1);
  1573. int iCurSel = ListView_GetNextItem(m_hwndList, -1, LVNI_SELECTED);
  1574. if (iCurSel == -1)
  1575. {
  1576. // If no item selected, first down arrow selects first item
  1577. ListView_SetItemState(m_hwndList, 0, LVIS_SELECTED | LVIS_FOCUSED, 0x000f);
  1578. ListView_EnsureVisible(m_hwndList, 0, FALSE);
  1579. }
  1580. else if (iCurSel == ListView_GetItemCount(m_hwndList)-1)
  1581. {
  1582. // If last item selected, down arrow goes into edit box
  1583. ListView_SetItemState(m_hwndList, iCurSel, 0, 0x000f);
  1584. if (m_pszCurrent)
  1585. {
  1586. // Restore original text if they arrow out of listview
  1587. _SetEditText(m_pszCurrent);
  1588. }
  1589. Edit_SetSel(m_hwndEdit, MAX_URL_STRING, MAX_URL_STRING);
  1590. }
  1591. else
  1592. {
  1593. // If first or middle item selected, down arrow selects next item
  1594. SendMessage(m_hwndList, WM_KEYDOWN, wParam, 0);
  1595. SendMessage(m_hwndList, WM_KEYUP, wParam, 0);
  1596. }
  1597. return FALSE;
  1598. }
  1599. //
  1600. // If Autosuggest drop-down enabled but not popped up then start a search
  1601. // based on the current edit box contents. If the edit box is empty,
  1602. // search for everything.
  1603. //
  1604. else if ((m_dwOptions & ACO_UPDOWNKEYDROPSLIST) && _IsAutoSuggestEnabled())
  1605. {
  1606. // Ensure the background thread knows we have focus
  1607. _GotFocus();
  1608. _StartCompletion(FALSE, TRUE);
  1609. return FALSE;
  1610. }
  1611. //
  1612. // Otherwise we see if we should append the completions in place
  1613. //
  1614. else if (_IsAutoAppendEnabled())
  1615. {
  1616. if (_AppendNext(FALSE))
  1617. {
  1618. return FALSE;
  1619. }
  1620. }
  1621. }
  1622. break;
  1623. case VK_END:
  1624. case VK_HOME:
  1625. _ResetSearch();
  1626. break;
  1627. case VK_BACK:
  1628. //
  1629. // Indicate that selection doesn't match m_psrCurrentlyDisplayed.
  1630. //
  1631. #ifndef UNIX
  1632. if (0 > GetKeyState(VK_CONTROL))
  1633. #else
  1634. if (dwModifiers & FCONTROL)
  1635. #endif
  1636. {
  1637. //
  1638. // Handle Ctrl-Backspace to delete word.
  1639. //
  1640. int iStart, iEnd;
  1641. SendMessage(m_hwndEdit, EM_GETSEL, (WPARAM)&iStart, (LPARAM)&iEnd);
  1642. //
  1643. // Nothing else must be selected.
  1644. //
  1645. if (iStart == iEnd)
  1646. {
  1647. _GetEditText();
  1648. if (!m_pszCurrent)
  1649. {
  1650. //
  1651. // We didn't handle it, let the
  1652. // other wndprocs try.
  1653. //
  1654. return TRUE;
  1655. }
  1656. //
  1657. // Erase the "word".
  1658. //
  1659. iStart = EditWordBreakProcW(m_pszCurrent, iStart, iStart+1, WB_LEFT);
  1660. Edit_SetSel(m_hwndEdit, iStart, iEnd);
  1661. Edit_ReplaceSel(m_hwndEdit, TEXT(""));
  1662. }
  1663. //
  1664. // We handled it.
  1665. //
  1666. return FALSE;
  1667. }
  1668. break;
  1669. }
  1670. //
  1671. // Let the original wndproc handle it.
  1672. //
  1673. return TRUE;
  1674. }
  1675. LRESULT CAutoComplete::_OnChar(WPARAM wParam, LPARAM lParam)
  1676. {
  1677. LRESULT lres = 0; // means nothing, but we handled the call
  1678. TCHAR cKey = (TCHAR) wParam;
  1679. if (wParam == VK_TAB)
  1680. {
  1681. // Ignore tab characters
  1682. return 0;
  1683. }
  1684. // Ensure the background thread knows we have focus
  1685. _GotFocus();
  1686. if (m_pThread->IsDisabled())
  1687. {
  1688. //
  1689. // Just follow the chain.
  1690. //
  1691. return DefSubclassProc(m_hwndEdit, WM_CHAR, wParam, lParam);
  1692. }
  1693. if (cKey != 127 && cKey != VK_ESCAPE && cKey != VK_RETURN && cKey != 0x0a) // control-backspace is ignored
  1694. {
  1695. // let the default edit wndproc do its thing first
  1696. lres = DefSubclassProc(m_hwndEdit, WM_CHAR, wParam, lParam);
  1697. // ctrl-c is generating a VK_CANCEL. Don't bring up autosuggest in this case.
  1698. if (cKey != VK_CANCEL)
  1699. {
  1700. BOOL fAppend = (cKey != VK_BACK);
  1701. _StartCompletion(fAppend);
  1702. }
  1703. }
  1704. else
  1705. {
  1706. _StopSearch();
  1707. _HideDropDown();
  1708. }
  1709. return lres;
  1710. }
  1711. //+-------------------------------------------------------------------------
  1712. // Starts autocomplete based on the current editbox contents
  1713. //--------------------------------------------------------------------------
  1714. void CAutoComplete::_StartCompletion
  1715. (
  1716. BOOL fAppend, // Ok to append completion in edit box
  1717. BOOL fEvenIfEmpty // = FALSE, Completes to everything if edit box is empty
  1718. )
  1719. {
  1720. // Get the text typed in
  1721. WCHAR szCurrent[MAX_URL_STRING];
  1722. int cchCurrent = GetWindowText(m_hwndEdit, szCurrent, ARRAYSIZE(szCurrent));
  1723. // See if we want a wildcard search
  1724. if (fEvenIfEmpty && cchCurrent == 0)
  1725. {
  1726. cchCurrent = 1;
  1727. szCurrent[0] = CH_WILDCARD;
  1728. szCurrent[1] = 0;
  1729. }
  1730. // If unchanged, we are done
  1731. if (m_pszLastSearch && m_pszCurrent && StrCmpI(m_pszCurrent, szCurrent) == 0)
  1732. {
  1733. if (!(m_hwndDropDown && IsWindowVisible(m_hwndDropDown)) &&
  1734. (-1 != m_iFirstMatch) && _IsAutoSuggestEnabled() &&
  1735. // Don't show drop-down if only one exact match (IForms)
  1736. (m_hdpa &&
  1737. ((m_iLastMatch != m_iFirstMatch) || (((CACString*)DPA_GetPtr(m_hdpa, m_iFirstMatch))->StrCmpI(szCurrent) != 0))))
  1738. {
  1739. _ShowDropDown();
  1740. }
  1741. return;
  1742. }
  1743. // Save the current text
  1744. if (szCurrent[0] == CH_WILDCARD)
  1745. {
  1746. SetStr(&m_pszCurrent, szCurrent);
  1747. }
  1748. else
  1749. {
  1750. _GetEditText();
  1751. }
  1752. //
  1753. // Deselect the current selection in the dropdown
  1754. //
  1755. if (m_hwndList)
  1756. {
  1757. int iCurSel = ListView_GetNextItem(m_hwndList, -1, LVNI_SELECTED);
  1758. if (iCurSel != -1)
  1759. {
  1760. ListView_SetItemState(m_hwndList, iCurSel, 0, 0x000f);
  1761. }
  1762. }
  1763. //
  1764. // If nothing typed in, stop any pending search
  1765. //
  1766. if (cchCurrent == 0)
  1767. {
  1768. if (m_pszCurrent)
  1769. {
  1770. _StopSearch();
  1771. if (m_pszCurrent)
  1772. {
  1773. SetStr(&m_pszCurrent, NULL);
  1774. }
  1775. // Free last completion
  1776. _HideDropDown();
  1777. }
  1778. }
  1779. //
  1780. // See if we need to generate a new list
  1781. //
  1782. else
  1783. {
  1784. int iCompleted = m_pszLastSearch ? lstrlen(m_pszLastSearch) : 0;
  1785. int iScheme = URL_SCHEME_UNKNOWN;
  1786. // Get length of common prefix (if any)
  1787. int cchPrefix = IsFlagSet(m_dwOptions, ACO_FILTERPREFIXES) ?
  1788. CACThread::GetSpecialPrefixLen(szCurrent) : 0;
  1789. if (
  1790. // If no previous completion, start a new search
  1791. (0 == iCompleted) ||
  1792. // If the list was truncated (reached limit), we need to refetch
  1793. m_fNeedNewList ||
  1794. // We purge matches to common prefixes ("www.", "http://" etc). If the
  1795. // last search may have resulted in items being filtered out, and the
  1796. // new string will not, then we need to refetch.
  1797. (cchPrefix > 0 && cchPrefix < cchCurrent && CACThread::MatchesSpecialPrefix(m_pszLastSearch)) ||
  1798. // If the portion we last completed to was altered, we need to refetch
  1799. (StrCmpNI(m_pszLastSearch, szCurrent, iCompleted) != 0) ||
  1800. // If we have entered a new folder, we need to refetch
  1801. (StrChrI(szCurrent + iCompleted, DIR_SEPARATOR_CHAR) != NULL) ||
  1802. // If we have entered a url folder, we need to refetch (ftp://shapitst/Bryanst/)
  1803. ((StrChrI(szCurrent + iCompleted, URL_SEPARATOR_CHAR) != NULL) &&
  1804. (URL_SCHEME_FTP == (iScheme = GetUrlScheme(szCurrent))))
  1805. )
  1806. {
  1807. // If the last search was truncated, make sure we try the next search with more characters
  1808. int cchMin = cchPrefix + 1;
  1809. if (m_fNeedNewList)
  1810. {
  1811. cchMin = iCompleted + 1;
  1812. }
  1813. // Find the last '\\' (or '/' for ftp)
  1814. int i = cchCurrent - 1;
  1815. while ((szCurrent[i] != DIR_SEPARATOR_CHAR) &&
  1816. !((szCurrent[i] == URL_SEPARATOR_CHAR) && (iScheme == URL_SCHEME_FTP)) &&
  1817. (i >= cchMin))
  1818. {
  1819. --i;
  1820. }
  1821. // Start a new search
  1822. szCurrent[i+1] = 0;
  1823. if (_StartSearch(szCurrent))
  1824. SetStr(&m_pszLastSearch, szCurrent);
  1825. }
  1826. // Otherwise we can simply update from our last completion list
  1827. else
  1828. {
  1829. //
  1830. if (m_hdpa)
  1831. {
  1832. _UpdateCompletion(szCurrent, -1, fAppend);
  1833. }
  1834. else
  1835. {
  1836. // Awaiting completion, cache new match...
  1837. }
  1838. }
  1839. }
  1840. }
  1841. //+-------------------------------------------------------------------------
  1842. // Get the background thread to start a new search
  1843. //--------------------------------------------------------------------------
  1844. BOOL CAutoComplete::_StartSearch(LPCWSTR pszSeatch)
  1845. {
  1846. // Empty the dropdown list. To minimize flash, we don't hide it unless
  1847. // the search comes up empty
  1848. if (m_hwndList)
  1849. {
  1850. ListView_SetItemCountEx(m_hwndList, 0, 0);
  1851. }
  1852. return m_pThread->StartSearch(pszSeatch, m_dwOptions);
  1853. }
  1854. //+-------------------------------------------------------------------------
  1855. // Get the background thread to abort the last search
  1856. //--------------------------------------------------------------------------
  1857. void CAutoComplete::_StopSearch()
  1858. {
  1859. SetStr(&m_pszLastSearch, NULL);
  1860. m_pThread->StopSearch();
  1861. }
  1862. //+-------------------------------------------------------------------------
  1863. // Informs the background thread that we have focus.
  1864. //--------------------------------------------------------------------------
  1865. void CAutoComplete::_GotFocus()
  1866. {
  1867. if (!m_pThread->HasFocus())
  1868. {
  1869. m_pThread->GotFocus();
  1870. }
  1871. }
  1872. //+-------------------------------------------------------------------------
  1873. // Message from background thread indicating that the search was completed
  1874. //--------------------------------------------------------------------------
  1875. void CAutoComplete::_OnSearchComplete
  1876. (
  1877. HDPA hdpa, // New completion list
  1878. DWORD dwSearchStatus // see SRCH_* flags
  1879. )
  1880. {
  1881. _FreeDPAPtrs(m_hdpa);
  1882. m_hdpa = hdpa;
  1883. m_fNeedNewList = IsFlagSet(dwSearchStatus, SRCH_LIMITREACHED);
  1884. if (IsFlagSet(dwSearchStatus, SRCH_USESORTINDEX))
  1885. {
  1886. if (NULL == m_hdpaSortIndex)
  1887. {
  1888. m_hdpaSortIndex = DPA_Create(AC_LIST_GROWTH_CONST);
  1889. }
  1890. }
  1891. else if (m_hdpaSortIndex)
  1892. {
  1893. DPA_Destroy(m_hdpaSortIndex);
  1894. m_hdpaSortIndex = NULL;
  1895. }
  1896. // Was it a wildcard search?
  1897. BOOL fWildCard = m_pszLastSearch && (m_pszLastSearch[0] == CH_WILDCARD) && (m_pszLastSearch[1] == L'\0');
  1898. //
  1899. // See if we should add "Search for <stuff typed in>" to the end of
  1900. // the list.
  1901. //
  1902. m_fSearchForAdded = FALSE;
  1903. if (!fWildCard && (m_dwOptions & ACO_SEARCH))
  1904. {
  1905. // Add "Search for <stuff typed in>" to the end of the list
  1906. // First make sure we have a dpa
  1907. if (m_hdpa == NULL)
  1908. {
  1909. m_hdpa = DPA_Create(AC_LIST_GROWTH_CONST);
  1910. }
  1911. if (m_hdpa)
  1912. {
  1913. // Create a bogus entry and add to the end of the list. This place
  1914. // holder makes sure the drop-down does not go away when there are no
  1915. // matching entries.
  1916. CACString* pStr = CreateACString(L"", 0, 0);
  1917. if (pStr)
  1918. {
  1919. if (DPA_AppendPtr(m_hdpa, pStr) != -1)
  1920. {
  1921. m_fSearchForAdded = TRUE;
  1922. }
  1923. else
  1924. {
  1925. pStr->Release();
  1926. }
  1927. }
  1928. }
  1929. }
  1930. // If no search results, hide our dropdown
  1931. if (NULL == m_hdpa || 0 == DPA_GetPtrCount(m_hdpa))
  1932. {
  1933. _HideDropDown();
  1934. if (m_hwndList)
  1935. {
  1936. ListView_SetItemCountEx(m_hwndList, 0, 0);
  1937. }
  1938. m_iFirstMatch = -1;
  1939. }
  1940. else
  1941. {
  1942. if (m_pszCurrent)
  1943. {
  1944. // If we are still waiting for a completion, then update the completion list
  1945. if (m_pszLastSearch)
  1946. {
  1947. _UpdateCompletion(m_pszCurrent, -1, TRUE);
  1948. }
  1949. }
  1950. if (m_hwndDropDown && IsWindowVisible(m_hwndDropDown))
  1951. {
  1952. _PositionDropDown(); // Resize based on number of hits
  1953. _UpdateScrollbar();
  1954. }
  1955. }
  1956. }
  1957. //+-------------------------------------------------------------------------
  1958. // Returns the text for an item in the autocomplete list
  1959. //--------------------------------------------------------------------------
  1960. BOOL CAutoComplete::_GetItem
  1961. (
  1962. int iIndex, // zero-based index
  1963. LPWSTR pszText, // location to return text
  1964. int cchMax, // size of pszText buffer
  1965. BOOL fDisplayName // TRUE = return name to display
  1966. // FALSE = return name to go to edit box
  1967. )
  1968. {
  1969. // Check for special "Search for <typed in>" entry at end of the list
  1970. if (m_fSearchFor && iIndex == m_iLastMatch - m_iFirstMatch)
  1971. {
  1972. ASSERT(NULL != m_pszCurrent);
  1973. WCHAR szFormat[MAX_PATH];
  1974. int id = fDisplayName ? IDS_SEARCHFOR : IDS_SEARCHFORCMD;
  1975. MLLoadString(id, szFormat, ARRAYSIZE(szFormat));
  1976. wnsprintf(pszText, cchMax, szFormat, m_pszCurrent);
  1977. }
  1978. // Normal list entry
  1979. else
  1980. {
  1981. CACString* pStr;
  1982. // If we're using Sorting indicies, then we are retrieving our entries
  1983. // out of m_hdpaPrefix sort, which contains only matched entries
  1984. if (m_hdpaSortIndex)
  1985. {
  1986. pStr = (CACString *)DPA_GetPtr(m_hdpaSortIndex, iIndex);
  1987. }
  1988. else
  1989. {
  1990. pStr = (CACString *)DPA_GetPtr(m_hdpa, iIndex + m_iFirstMatch);
  1991. }
  1992. if (pStr)
  1993. {
  1994. StrCpyN(pszText, pStr->GetStr(), cchMax);
  1995. }
  1996. else if (cchMax >= 1)
  1997. {
  1998. pszText[0] = 0;
  1999. }
  2000. }
  2001. return TRUE;
  2002. }
  2003. //+-------------------------------------------------------------------------
  2004. // Frees an item in our autocomplete list
  2005. //--------------------------------------------------------------------------
  2006. int CAutoComplete::_DPADestroyCallback(LPVOID p, LPVOID d)
  2007. {
  2008. ((CACString*)p)->Release();
  2009. return 1;
  2010. }
  2011. //+-------------------------------------------------------------------------
  2012. // Frees our last completion list
  2013. //--------------------------------------------------------------------------
  2014. void CAutoComplete::_FreeDPAPtrs(HDPA hdpa)
  2015. {
  2016. TraceMsg(AC_GENERAL, "CAutoComplete::_FreeDPAPtrs(hdpa = 0x%x)", hdpa);
  2017. if (hdpa)
  2018. {
  2019. DPA_DestroyCallback(hdpa, _DPADestroyCallback, 0);
  2020. hdpa = NULL;
  2021. }
  2022. }
  2023. //+-------------------------------------------------------------------------
  2024. // DPA callback used to order the matches by the sort index
  2025. //--------------------------------------------------------------------------
  2026. int CALLBACK CAutoComplete::_DPACompareSortIndex(LPVOID p1, LPVOID p2, LPARAM lParam)
  2027. {
  2028. CACString* ps1 = (CACString*)p1;
  2029. CACString* ps2 = (CACString*)p2;
  2030. return ps1->CompareSortingIndex(*ps2);
  2031. }
  2032. //+-------------------------------------------------------------------------
  2033. // Updates the matching completion
  2034. //--------------------------------------------------------------------------
  2035. void CAutoComplete::_UpdateCompletion
  2036. (
  2037. LPCWSTR pszTyped, // typed in string to match
  2038. int iChanged, // char added since last update or -1
  2039. BOOL fAppend // ok to append completion
  2040. )
  2041. {
  2042. int iFirstMatch = -1;
  2043. int iLastMatch = -1;
  2044. int nChars = lstrlen(pszTyped);
  2045. // Was it a wildcard search?
  2046. BOOL fWildCard = pszTyped && (pszTyped[0] == CH_WILDCARD) && (pszTyped[1] == L'\0');
  2047. if (fWildCard && DPA_GetPtrCount(m_hdpa))
  2048. {
  2049. // Everything matches
  2050. iFirstMatch = 0;
  2051. iLastMatch = DPA_GetPtrCount(m_hdpa) - 1;
  2052. }
  2053. else
  2054. {
  2055. // PERF: Special case where current == search string
  2056. /*
  2057. //
  2058. // Find the first matching index
  2059. //
  2060. if (iChanged > 0)
  2061. {
  2062. // PERF: Get UC and LC versions of WC for compare below?
  2063. WCHAR wc = pszTyped[iChanged];
  2064. // A character was added so search from current location
  2065. for (int i = m_iFirstMatch; i < DPA_GetPtrCount(m_hdpa); ++i)
  2066. {
  2067. CACString* pStr;
  2068. pStr = (CACString*)DPA_GetPtr(m_hdpa, i);
  2069. ASSERT(pStr);
  2070. if (pStr && pStr->GetLength() >= iChanged && ChrCmpI((*pStr)[iChanged], wc) == 0)
  2071. {
  2072. // This is the first match
  2073. iFirstMatch = i;
  2074. break;
  2075. }
  2076. }
  2077. }
  2078. else
  2079. */
  2080. {
  2081. // We have to search the whole list
  2082. // PERF: Switch to a binary search.
  2083. for (int i = 0; i < DPA_GetPtrCount(m_hdpa); ++i)
  2084. {
  2085. CACString* pStr;
  2086. pStr = (CACString*)DPA_GetPtr(m_hdpa, i);
  2087. ASSERT(pStr);
  2088. if (pStr &&
  2089. (pStr->StrCmpNI(pszTyped, nChars) == 0))
  2090. {
  2091. iFirstMatch = i;
  2092. break;
  2093. }
  2094. }
  2095. }
  2096. if (-1 != iFirstMatch)
  2097. {
  2098. //
  2099. // Find the last match
  2100. //
  2101. // PERF: Should we binary search up to the last end of list?
  2102. for (iLastMatch = iFirstMatch; iLastMatch + 1 < DPA_GetPtrCount(m_hdpa); ++iLastMatch)
  2103. {
  2104. CACString* pStr;
  2105. pStr = (CACString*)DPA_GetPtr(m_hdpa, iLastMatch + 1);
  2106. ASSERT(pStr);
  2107. if (NULL == pStr || (pStr->StrCmpNI(pszTyped, nChars) != 0))
  2108. {
  2109. break;
  2110. }
  2111. }
  2112. }
  2113. }
  2114. //
  2115. // See if we should add "Search for <stuff typed in>" to the end of
  2116. // the list.
  2117. //
  2118. int iSearchFor = 0;
  2119. int nScheme;
  2120. if (m_fSearchForAdded &&
  2121. // Not a drive letter
  2122. (*pszTyped && pszTyped[1] != L':') &&
  2123. // Not a UNC path
  2124. (pszTyped[0] != L'\\' && pszTyped[1] != L'\\') &&
  2125. // Not a known scheme
  2126. ((nScheme = GetUrlScheme(pszTyped)) == URL_SCHEME_UNKNOWN ||
  2127. nScheme == URL_SCHEME_INVALID) &&
  2128. // Ignore anything theat begins with "www"
  2129. !(pszTyped[0] == L'w' && pszTyped[1] == L'w' && pszTyped[2] == L'w')
  2130. // Not a search keyword
  2131. // !Is
  2132. )
  2133. {
  2134. // Add "Search for <stuff typed in>"
  2135. iSearchFor = 1;
  2136. }
  2137. m_fSearchFor = iSearchFor;
  2138. m_iLastMatch = iLastMatch + iSearchFor;
  2139. m_iFirstMatch = iFirstMatch;
  2140. if (iSearchFor && iFirstMatch == -1)
  2141. {
  2142. // There is one entry - the special "search for <>" entry
  2143. m_iFirstMatch = 0;
  2144. }
  2145. // Sort the matches using the sort index
  2146. if (m_hdpaSortIndex && iFirstMatch != -1)
  2147. {
  2148. // First put the matches in this sorted dpa
  2149. DPA_GetPtrCount(m_hdpaSortIndex) = 0;
  2150. for (int i=0; i <= m_iLastMatch-m_iFirstMatch; i++)
  2151. {
  2152. CACString *pStr = (CACString *)DPA_GetPtr(m_hdpa, m_iFirstMatch+i);
  2153. DPA_InsertPtr(m_hdpaSortIndex, i, (LPVOID)pStr);
  2154. }
  2155. // Now order it by sort index (rather than alphabetically)
  2156. DPA_Sort(m_hdpaSortIndex, _DPACompareSortIndex, 0);
  2157. }
  2158. if (_IsAutoSuggestEnabled())
  2159. {
  2160. // Update our drop-down list
  2161. if ((m_iFirstMatch == -1) || // Hide if there are no matches
  2162. ((m_iLastMatch == m_iFirstMatch) && // Or if only one match which we've already typed (IForms)
  2163. (((CACString*)DPA_GetPtr(m_hdpa, m_iFirstMatch))->StrCmpI(pszTyped) == 0)))
  2164. {
  2165. _HideDropDown();
  2166. }
  2167. else
  2168. {
  2169. if (m_hwndList)
  2170. {
  2171. int cItems = m_iLastMatch - m_iFirstMatch + 1;
  2172. ListView_SetItemCountEx(m_hwndList, cItems, 0);
  2173. }
  2174. _ShowDropDown();
  2175. _UpdateScrollbar();
  2176. }
  2177. }
  2178. if (_IsAutoAppendEnabled() && fAppend && m_iFirstMatch != -1 && !fWildCard)
  2179. {
  2180. // If caret is not at the end of the string, don't append
  2181. DWORD dwSel = Edit_GetSel(m_hwndEdit);
  2182. int iStartSel = LOWORD(dwSel);
  2183. int iEndSel = HIWORD(dwSel);
  2184. int iLen = lstrlen(pszTyped);
  2185. if (iStartSel == iStartSel && iStartSel != iLen)
  2186. {
  2187. return;
  2188. }
  2189. // FEATURE: Should use the shortest match
  2190. m_iAppended = -1;
  2191. _AppendNext(TRUE);
  2192. }
  2193. }
  2194. //+-------------------------------------------------------------------------
  2195. // Appends the next completion to the current edit text. Returns TRUE if
  2196. // successful.
  2197. //--------------------------------------------------------------------------
  2198. BOOL CAutoComplete::_AppendNext
  2199. (
  2200. BOOL fAppendToWhack // Apend to next whack (false = append entire match)
  2201. )
  2202. {
  2203. // Nothing to complete?
  2204. if (NULL == m_hdpa || 0 == DPA_GetPtrCount(m_hdpa) ||
  2205. m_iFirstMatch == -1 || !_WantToAppendResults())
  2206. return FALSE;
  2207. //
  2208. // If nothing currently appended, init to the
  2209. // last item so that we will wrap around to the
  2210. // first item
  2211. //
  2212. if (m_iAppended == -1)
  2213. {
  2214. m_iAppended = m_iLastMatch;
  2215. }
  2216. int iAppend = m_iAppended;
  2217. CACString* pStr;
  2218. //
  2219. // Loop through the items until we find one without a prefix
  2220. //
  2221. do
  2222. {
  2223. if (++iAppend > m_iLastMatch)
  2224. {
  2225. iAppend = m_iFirstMatch;
  2226. }
  2227. pStr = (CACString*)DPA_GetPtr(m_hdpa, iAppend);
  2228. if (pStr &&
  2229. // Don't append if match has as www. prefix
  2230. (pStr->PrefixLength() < 4 || StrCmpNI(pStr->GetStr() + pStr->PrefixLength() - 4, L"www.", 4) != 0) &&
  2231. // Ignore the "Search for" if present
  2232. !(m_fSearchFor && iAppend == m_iLastMatch))
  2233. {
  2234. // We found one so append it
  2235. _Append(*pStr, fAppendToWhack);
  2236. m_iAppended = iAppend;
  2237. }
  2238. }
  2239. while (iAppend != m_iAppended);
  2240. return TRUE;
  2241. }
  2242. //+-------------------------------------------------------------------------
  2243. // Appends the previous completion to the current edit text. Returns TRUE
  2244. // if successful.
  2245. //--------------------------------------------------------------------------
  2246. BOOL CAutoComplete::_AppendPrevious
  2247. (
  2248. BOOL fAppendToWhack // Append to next whack (false = append entire match)
  2249. )
  2250. {
  2251. // Nothing to complete?
  2252. if (NULL == m_hdpa || 0 == DPA_GetPtrCount(m_hdpa) ||
  2253. m_iFirstMatch == -1 || !_WantToAppendResults())
  2254. return FALSE;
  2255. //
  2256. // If nothing currently appended, init to the
  2257. // first item so that we will wrap around to the
  2258. // last item
  2259. //
  2260. if (m_iAppended == -1)
  2261. {
  2262. m_iAppended = m_iFirstMatch;
  2263. }
  2264. int iAppend = m_iAppended;
  2265. CACString* pStr;
  2266. //
  2267. // Loop through the items until we find one without a prefix
  2268. //
  2269. do
  2270. {
  2271. if (--iAppend < m_iFirstMatch)
  2272. {
  2273. iAppend = m_iLastMatch;
  2274. }
  2275. pStr = (CACString*)DPA_GetPtr(m_hdpa, iAppend);
  2276. if (pStr &&
  2277. // Don't append if match has as www. prefix
  2278. (pStr->PrefixLength() < 4 || StrCmpNI(pStr->GetStr() + pStr->PrefixLength() - 4, L"www.", 4) != 0) &&
  2279. // Ignore the "Search for" if present
  2280. !(m_fSearchFor && iAppend == m_iLastMatch))
  2281. {
  2282. // We found one so append it
  2283. _Append(*pStr, fAppendToWhack);
  2284. m_iAppended = iAppend;
  2285. }
  2286. }
  2287. while (iAppend != m_iAppended);
  2288. return TRUE;
  2289. }
  2290. //+-------------------------------------------------------------------------
  2291. // Appends the completion to the current edit text
  2292. //--------------------------------------------------------------------------
  2293. void CAutoComplete::_Append
  2294. (
  2295. CACString& rStr, // item to append to the editbox text
  2296. BOOL fAppendToWhack // Apend to next whack (false = append entire match)
  2297. )
  2298. {
  2299. ASSERT(_IsAutoAppendEnabled());
  2300. if (m_pszCurrent)
  2301. {
  2302. int cchCurrent = lstrlen(m_pszCurrent);
  2303. LPCWSTR pszAppend = rStr.GetStrToCompare() + cchCurrent;
  2304. int cchAppend;
  2305. if (fAppendToWhack)
  2306. {
  2307. //
  2308. // Advance to the whacks.
  2309. //
  2310. const WCHAR *pch = pszAppend;
  2311. cchAppend = 0;
  2312. while (*pch && !_IsWhack(*pch))
  2313. {
  2314. ++cchAppend;
  2315. pch++;
  2316. }
  2317. //
  2318. // Advance past the whacks.
  2319. //
  2320. while (*pch && _IsWhack(*pch))
  2321. {
  2322. ++cchAppend;
  2323. pch++;
  2324. }
  2325. }
  2326. else
  2327. {
  2328. // Append entire match
  2329. cchAppend = lstrlen(pszAppend);
  2330. }
  2331. WCHAR szAppend[MAX_URL_STRING];
  2332. StrCpyN(szAppend, pszAppend, cchAppend + 1);
  2333. _UpdateText(cchCurrent, cchCurrent + cchAppend, m_pszCurrent, szAppend);
  2334. m_fAppended = TRUE;
  2335. }
  2336. }
  2337. //+-------------------------------------------------------------------------
  2338. // Hides the AutoSuggest dropdown
  2339. //--------------------------------------------------------------------------
  2340. void CAutoComplete::_HideDropDown()
  2341. {
  2342. if (m_hwndDropDown)
  2343. {
  2344. ShowWindow(m_hwndDropDown, SW_HIDE);
  2345. }
  2346. }
  2347. //+-------------------------------------------------------------------------
  2348. // Shows and positions the autocomplete dropdown
  2349. //--------------------------------------------------------------------------
  2350. void CAutoComplete::_ShowDropDown()
  2351. {
  2352. if (m_hwndDropDown && !_IsComboboxDropped() && !m_fImeCandidateOpen)
  2353. {
  2354. // If the edit window is visible, it better have focus!
  2355. // (Intelliforms uses an invisible window that doesn't
  2356. // get focus.)
  2357. if (IsWindowVisible(m_hwndEdit) && m_hwndEdit != GetFocus())
  2358. {
  2359. ShowWindow(m_hwndDropDown, SW_HIDE);
  2360. return;
  2361. }
  2362. if (!IsWindowVisible(m_hwndDropDown))
  2363. {
  2364. // It should not be possible to open a new dropdown while
  2365. // another dropdown is visible! But to be safe we'll check ...
  2366. if (s_hwndDropDown)
  2367. {
  2368. ASSERT(FALSE);
  2369. ShowWindow(s_hwndDropDown, SW_HIDE);
  2370. }
  2371. s_hwndDropDown = m_hwndDropDown;
  2372. //
  2373. // Install a thread hook so that we can detect when something
  2374. // happens that should hide the dropdown.
  2375. //
  2376. ENTERCRITICAL;
  2377. if (s_hhookMouse)
  2378. {
  2379. // Should never happen because the hook is removed when the dropdown
  2380. // is hidden. But we can't afford to orphan a hook so we check just
  2381. // in case!
  2382. ASSERT(FALSE);
  2383. UnhookWindowsHookEx(s_hhookMouse);
  2384. }
  2385. s_hhookMouse = SetWindowsHookEx(WH_MOUSE, s_MouseHook, HINST_THISDLL, NULL);
  2386. LEAVECRITICAL;
  2387. //
  2388. // Subclass the parent windows so that we can detect when something
  2389. // happens that should hide the dropdown
  2390. //
  2391. _SubClassParent(m_hwndEdit);
  2392. }
  2393. _PositionDropDown();
  2394. }
  2395. }
  2396. //+-------------------------------------------------------------------------
  2397. // Positions dropdown based on edit window position
  2398. //--------------------------------------------------------------------------
  2399. void CAutoComplete::_PositionDropDown()
  2400. {
  2401. RECT rcEdit;
  2402. GetWindowRect(m_hwndEdit, &rcEdit);
  2403. int x = rcEdit.left;
  2404. int y = rcEdit.bottom;
  2405. // Don't resize if user already has
  2406. if (!m_fDropDownResized)
  2407. {
  2408. #ifndef UNIX
  2409. m_nDropHeight = 100;
  2410. #else
  2411. m_nDropHeight = 150;
  2412. #endif
  2413. MINMAXINFO mmi = {0};
  2414. SendMessage(m_hwndDropDown, WM_GETMINMAXINFO, 0, (LPARAM)&mmi);
  2415. m_nDropWidth = max(RECTWIDTH(rcEdit), mmi.ptMinTrackSize.x);
  2416. // Calculate dropdown height based on number of string matches
  2417. if (m_hdpa)
  2418. {
  2419. /*
  2420. int iDropDownHeight =
  2421. m_nStatusHeight +
  2422. ListView_GetItemSpacing(m_hwndList, FALSE) * DPA_GetPtrCount(m_hdpa);
  2423. */
  2424. int iDropDownHeight =
  2425. m_nStatusHeight - GetSystemMetrics(SM_CYBORDER) +
  2426. HIWORD(ListView_ApproximateViewRect(m_hwndList, -1, -1, -1));
  2427. if (m_nDropHeight > iDropDownHeight)
  2428. {
  2429. m_nDropHeight = iDropDownHeight;
  2430. }
  2431. }
  2432. }
  2433. int w = m_nDropWidth;
  2434. int h = m_nDropHeight;
  2435. BOOL fDroppedUp = FALSE;
  2436. //
  2437. // Make sure we don't go off the screen
  2438. //
  2439. HMONITOR hMonitor = MonitorFromWindow(m_hwndEdit, MONITOR_DEFAULTTONEAREST);
  2440. if (hMonitor)
  2441. {
  2442. MONITORINFO mi;
  2443. mi.cbSize = sizeof(mi);
  2444. if (GetMonitorInfo(hMonitor, &mi))
  2445. {
  2446. RECT rcMon = mi.rcMonitor;
  2447. int cxMax = rcMon.right - rcMon.left;
  2448. if (w > cxMax)
  2449. {
  2450. w = cxMax;
  2451. }
  2452. /*
  2453. if (x < rcMon.left)
  2454. {
  2455. // Off the left edge, so move right
  2456. x += rcMon.left - x;
  2457. }
  2458. else if (x + w > rcMon.right)
  2459. {
  2460. // Off the right edge, so move left
  2461. x -= (x + w - rcMon.right);
  2462. }
  2463. */
  2464. int cyMax = (RECTHEIGHT(rcMon) - RECTHEIGHT(rcEdit));
  2465. if (h > cyMax)
  2466. {
  2467. h = cyMax;
  2468. }
  2469. if (y + h > rcMon.bottom
  2470. #ifdef ALLOW_ALWAYS_DROP_UP
  2471. || m_fAlwaysDropUp
  2472. #endif
  2473. )
  2474. {
  2475. // Off the bottom of the screen, so see if there is more
  2476. // room in the up direction
  2477. if (rcEdit.top > rcMon.bottom - rcEdit.bottom)
  2478. {
  2479. // There's more room to pop up
  2480. y = max(rcEdit.top - h, 0);
  2481. h = rcEdit.top - y;
  2482. fDroppedUp = TRUE;
  2483. }
  2484. else
  2485. {
  2486. // Don't let it go past the bottom
  2487. h = rcMon.bottom - y;
  2488. }
  2489. }
  2490. }
  2491. }
  2492. BOOL fFlipped = BOOLIFY(m_fDroppedUp) ^ BOOLIFY(fDroppedUp);
  2493. m_fDroppedUp = fDroppedUp;
  2494. SetWindowPos(m_hwndDropDown, HWND_TOP, x, y, w, h, SWP_SHOWWINDOW | SWP_NOACTIVATE);
  2495. if (fFlipped)
  2496. {
  2497. _UpdateGrip();
  2498. }
  2499. }
  2500. //+-------------------------------------------------------------------------
  2501. // Window procedure for the subclassed edit box
  2502. //--------------------------------------------------------------------------
  2503. LRESULT CAutoComplete::_EditWndProc(UINT uMsg, WPARAM wParam, LPARAM lParam)
  2504. {
  2505. switch (uMsg)
  2506. {
  2507. case WM_SETTEXT:
  2508. //
  2509. // If the text is changed programmatically, we hide the dropdown.
  2510. // This fixed a bug in the dialog at:
  2511. //
  2512. // Internet Options\security\Local Intranet\Sites\Advanced
  2513. //
  2514. // - If you select something in the dropdown the press enter,
  2515. // the enter key is intercepeted by the dialog which clears
  2516. // the edit field, but the drop-down is not hidden.
  2517. //
  2518. if (!m_fSettingText)
  2519. {
  2520. _HideDropDown();
  2521. }
  2522. break;
  2523. case WM_GETDLGCODE:
  2524. {
  2525. //
  2526. // If the auto-suggest drop-down if up, we process
  2527. // the tab key.
  2528. //
  2529. BOOL fDropDownVisible = m_hwndDropDown && IsWindowVisible(m_hwndDropDown);
  2530. if (wParam == VK_TAB && IsFlagSet(m_dwOptions, ACO_USETAB))
  2531. {
  2532. #ifndef UNIX
  2533. if ((GetKeyState(VK_CONTROL) < 0) ||
  2534. !fDropDownVisible)
  2535. {
  2536. break;
  2537. }
  2538. #else
  2539. //
  2540. // On unix, an unmodified tab key is processed even if autosuggest
  2541. // is not visible.
  2542. //
  2543. if ((GetKeyState(VK_CONTROL) < 0) ||
  2544. ((GetKeyState(VK_SHIFT) < 0) && !fDropDownVisible))
  2545. {
  2546. break;
  2547. }
  2548. if (!fDropDownVisible)
  2549. {
  2550. //
  2551. // Make tab key autocomplete key if at end of edit control
  2552. //
  2553. UINT cchTotal = SendMessage(m_hwndEdit, EM_LINELENGTH, 0, 0L);
  2554. DWORD ichMinSel;
  2555. SendMessage(m_hwndEdit, EM_GETSEL, (WPARAM) &ichMinSel, 0L);
  2556. if (ichMinSel == cchTotal)
  2557. {
  2558. break;
  2559. }
  2560. }
  2561. #endif // UNIX
  2562. // We want the tab key
  2563. return DLGC_WANTTAB;
  2564. }
  2565. else if (wParam == VK_ESCAPE && fDropDownVisible)
  2566. {
  2567. // eat escape so that dialog boxes (e.g. File Open) are not closed
  2568. return DLGC_WANTALLKEYS;
  2569. }
  2570. break;
  2571. }
  2572. case WM_KEYDOWN:
  2573. if (wParam == VK_TAB)
  2574. {
  2575. BOOL fDropDownVisible = m_hwndDropDown && IsWindowVisible(m_hwndDropDown);
  2576. #ifdef UNIX
  2577. if (!fDropDownVisible &&
  2578. (GetKeyState(VK_CONTROL) >= 0) &&
  2579. (GetKeyState(VK_SHIFT) >= 0)
  2580. )
  2581. {
  2582. wParam = VK_END;
  2583. }
  2584. else
  2585. #endif
  2586. if (fDropDownVisible &&
  2587. GetKeyState(VK_CONTROL) >= 0)
  2588. {
  2589. // Map tab to down-arrow and shift-tab to up-arrow
  2590. wParam = (GetKeyState(VK_SHIFT) >= 0) ? VK_DOWN : VK_UP;
  2591. }
  2592. else
  2593. {
  2594. return 0;
  2595. }
  2596. }
  2597. // Ensure the background thread knows we have focus
  2598. _GotFocus();
  2599. // ASSERT(m_hThread || m_pThread->IsDisabled()); // If this occurs then we didn't process a WM_SETFOCUS when we should have. BryanSt.
  2600. if (_OnKeyDown(wParam) == 0)
  2601. {
  2602. //
  2603. // We handled it.
  2604. //
  2605. return 0;
  2606. }
  2607. if (wParam == VK_DELETE)
  2608. {
  2609. LRESULT lRes = DefSubclassProc(m_hwndEdit, uMsg, wParam, lParam);
  2610. _StartCompletion(FALSE);
  2611. return lRes;
  2612. }
  2613. break;
  2614. case WM_CHAR:
  2615. return _OnChar(wParam, lParam);
  2616. case WM_CUT:
  2617. case WM_PASTE:
  2618. case WM_CLEAR:
  2619. {
  2620. LRESULT lRet = DefSubclassProc(m_hwndEdit, uMsg, wParam, lParam);
  2621. // See if we need to update the completion
  2622. if (!m_pThread->IsDisabled())
  2623. {
  2624. _GotFocus();
  2625. _StartCompletion(TRUE);
  2626. }
  2627. return lRet;
  2628. }
  2629. case WM_SETFOCUS:
  2630. m_pThread->GotFocus();
  2631. break;
  2632. case WM_KILLFOCUS:
  2633. {
  2634. HWND hwndGetFocus = (HWND)wParam;
  2635. // Ignore focus change to ourselves
  2636. if (m_hwndEdit != hwndGetFocus)
  2637. {
  2638. if (m_hwndDropDown && GetFocus() != m_hwndDropDown)
  2639. {
  2640. _HideDropDown();
  2641. }
  2642. m_pThread->LostFocus();
  2643. }
  2644. break;
  2645. }
  2646. case WM_DESTROY:
  2647. {
  2648. HWND hwndEdit = m_hwndEdit;
  2649. TraceMsg(AC_GENERAL, "CAutoComplete::_WndProc(WM_DESTROY) releasing subclass.");
  2650. RemoveWindowSubclass(hwndEdit, s_EditWndProc, 0);
  2651. if (m_hwndDropDown)
  2652. {
  2653. //
  2654. // If the dropdown and its associated listview are fully created, we can simply
  2655. // destroy them.
  2656. //
  2657. if (m_hwndList)
  2658. {
  2659. DestroyWindow(m_hwndDropDown);
  2660. }
  2661. //
  2662. // We are likely in the middle of creating the listview and destroy the dropdown now
  2663. // can result in a crash (another thread probably destroyed the edit window). So we
  2664. // post a message to get the dropdown to destroy itself. The background thread will
  2665. // hold this dll in memory until the dropdown is gone.
  2666. //
  2667. else
  2668. {
  2669. // Don't call DestroyWindow. See AM_DESTROY comment in dropdown's wndproc.
  2670. _GotFocus();
  2671. if (m_pThread->HasFocus())
  2672. {
  2673. PostMessage(m_hwndDropDown, AM_DESTROY, 0, 0);
  2674. }
  2675. else
  2676. {
  2677. // If the background thread is not running, we can't rely on it to
  2678. // keep us in memory during shutdown, so synchronously destroy the
  2679. // window and cross your fingers.
  2680. DestroyWindow(m_hwndDropDown);
  2681. }
  2682. }
  2683. }
  2684. m_pThread->SyncShutDownBGThread();
  2685. SAFERELEASE(m_pThread);
  2686. Release(); // Release subclass Ref.
  2687. // Pass it onto the old wndproc.
  2688. return DefSubclassProc(hwndEdit, uMsg, wParam, lParam);
  2689. }
  2690. break;
  2691. case WM_MOVE:
  2692. {
  2693. if (m_hwndDropDown && IsWindowVisible(m_hwndDropDown))
  2694. {
  2695. // Follow edit window, for example when intelliforms window scrolls w/intellimouse
  2696. _PositionDropDown();
  2697. }
  2698. }
  2699. break;
  2700. /*
  2701. case WM_COMMAND:
  2702. if (m_pThread->IsDisabled())
  2703. {
  2704. break;
  2705. }
  2706. return _OnCommand(wParam, lParam);
  2707. */
  2708. /*
  2709. case WM_CONTEXTMENU:
  2710. if (m_pThread->IsDisabled())
  2711. {
  2712. break;
  2713. }
  2714. return _ContextMenu(GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam));
  2715. */
  2716. #ifndef UNIX
  2717. case WM_LBUTTONDBLCLK:
  2718. {
  2719. //
  2720. // Bypass our word break routine. We only register this callback on NT because it
  2721. // doesn't work right on win9x.
  2722. //
  2723. if (m_fEditControlUnicode)
  2724. {
  2725. //
  2726. // We break words at url delimiters for ctrl-left & ctrl-right, but
  2727. // we want double-click to use standard word selection so that it is easy
  2728. // to select the URL.
  2729. //
  2730. SendMessage(m_hwndEdit, EM_SETWORDBREAKPROC, 0, (DWORD_PTR)m_oldEditWordBreakProc);
  2731. LRESULT lres = DefSubclassProc(m_hwndEdit, uMsg, wParam, lParam);
  2732. // Restore our word-break callback
  2733. SendMessage(m_hwndEdit, EM_SETWORDBREAKPROC, 0, (DWORD_PTR)EditWordBreakProcW);
  2734. return lres;
  2735. }
  2736. break;
  2737. }
  2738. #endif // !UNIX
  2739. case WM_SETFONT:
  2740. {
  2741. // If we have a dropdown, recreate it with the latest font
  2742. m_hfontListView = (HFONT)wParam;
  2743. if (m_hwndDropDown)
  2744. {
  2745. _StopSearch();
  2746. DestroyWindow(m_hwndDropDown);
  2747. m_hwndDropDown = NULL;
  2748. _SeeWhatsEnabled();
  2749. }
  2750. break;
  2751. }
  2752. case WM_IME_NOTIFY:
  2753. {
  2754. // We don't want autocomplete to obsure the IME candidate window
  2755. DWORD dwCommand = (DWORD)wParam;
  2756. if (dwCommand == IMN_OPENCANDIDATE)
  2757. {
  2758. m_fImeCandidateOpen = TRUE;
  2759. _HideDropDown();
  2760. }
  2761. else if (dwCommand == IMN_CLOSECANDIDATE)
  2762. {
  2763. m_fImeCandidateOpen = FALSE;
  2764. }
  2765. }
  2766. break;
  2767. default:
  2768. // Handle registered messages
  2769. if (uMsg == m_uMsgSearchComplete)
  2770. {
  2771. _OnSearchComplete((HDPA)lParam, (BOOL)wParam);
  2772. return 0;
  2773. }
  2774. // Pass mouse wheel messages to the drop-down if it is visible
  2775. else if ((uMsg == WM_MOUSEWHEEL || uMsg == g_msgMSWheel) &&
  2776. m_hwndDropDown && IsWindowVisible(m_hwndDropDown))
  2777. {
  2778. SendMessage(m_hwndList, uMsg, wParam, lParam);
  2779. return 0;
  2780. }
  2781. break;
  2782. }
  2783. return DefSubclassProc(m_hwndEdit, uMsg, wParam, lParam);
  2784. }
  2785. //+-------------------------------------------------------------------------
  2786. // Static window procedure for the subclassed edit box
  2787. //--------------------------------------------------------------------------
  2788. LRESULT CALLBACK CAutoComplete::s_EditWndProc
  2789. (
  2790. HWND hwnd,
  2791. UINT uMsg,
  2792. WPARAM wParam,
  2793. LPARAM lParam,
  2794. UINT_PTR uIdSubclass, // always zero for us
  2795. DWORD_PTR dwRefData // -> CAutoComplete
  2796. )
  2797. {
  2798. CAutoComplete* pac = (CAutoComplete*)dwRefData;
  2799. if (pac)
  2800. {
  2801. ASSERT(pac->m_hwndEdit == hwnd);
  2802. return pac->_EditWndProc(uMsg, wParam, lParam);
  2803. }
  2804. else
  2805. {
  2806. TraceMsg(TF_WARNING, "CAutoComplete::s_EditWndProc() --- pac == NULL");
  2807. return DefWindowProcWrap(hwnd, uMsg, wParam, lParam);
  2808. }
  2809. }
  2810. //+-------------------------------------------------------------------------
  2811. // Draws the sizing grip. We do this ourselves rather than call
  2812. // DrawFrameControl because the standard API does not flip upside down on
  2813. // all platforms. (NT and win98 seem to use a font and thus ignore the map
  2814. // mode)
  2815. //--------------------------------------------------------------------------
  2816. BOOL DrawGrip(register HDC hdc, LPRECT lprc, BOOL fEraseBackground)
  2817. {
  2818. int x, y;
  2819. int xMax, yMax;
  2820. int dMin;
  2821. HBRUSH hbrOld;
  2822. HPEN hpen, hpenOld;
  2823. DWORD rgbHilight, rgbShadow;
  2824. //
  2825. // The grip is really a pattern of 4 repeating diagonal lines:
  2826. // One glare
  2827. // Two raised
  2828. // One empty
  2829. // These lines run from bottom left to top right, in the bottom right
  2830. // corner of the square given by (lprc->left, lprc->top, dMin by dMin.
  2831. //
  2832. dMin = min(lprc->right-lprc->left, lprc->bottom-lprc->top);
  2833. xMax = lprc->left + dMin;
  2834. yMax = lprc->top + dMin;
  2835. //
  2836. // Setup colors
  2837. //
  2838. hbrOld = GetSysColorBrush(COLOR_3DFACE);
  2839. rgbHilight = GetSysColor(COLOR_3DHILIGHT);
  2840. rgbShadow = GetSysColor(COLOR_3DSHADOW);
  2841. //
  2842. // Fill in background of ENTIRE rect
  2843. //
  2844. if (fEraseBackground)
  2845. {
  2846. hbrOld = SelectBrush(hdc, hbrOld);
  2847. PatBlt(hdc, lprc->left, lprc->top, lprc->right-lprc->left,
  2848. lprc->bottom-lprc->top, PATCOPY);
  2849. SelectBrush(hdc, hbrOld);
  2850. }
  2851. else
  2852. {
  2853. /*
  2854. hpen = CreatePen(PS_SOLID, 1, GetSysColor(COLOR_WINDOW));
  2855. if (hpen == NULL)
  2856. return FALSE;
  2857. hpenOld = SelectPen(hdc, hpen);
  2858. x = lprc->left - 1;
  2859. y = lprc->top - 1;
  2860. MoveToEx(hdc, x, yMax, NULL);
  2861. LineTo(hdc, xMax, y);
  2862. SelectPen(hdc, hpenOld);
  2863. DeletePen(hpen);
  2864. */
  2865. //
  2866. // Draw background color directly under grip:
  2867. //
  2868. hpen = CreatePen(PS_SOLID, 1, GetSysColor(COLOR_3DFACE));
  2869. if (hpen == NULL)
  2870. return FALSE;
  2871. hpenOld = SelectPen(hdc, hpen);
  2872. x = lprc->left + 3;
  2873. y = lprc->top + 3;
  2874. while (x < xMax)
  2875. {
  2876. //
  2877. // Since dMin is the same horz and vert, x < xMax and y < yMax
  2878. // are interchangeable...
  2879. //
  2880. MoveToEx(hdc, x, yMax, NULL);
  2881. LineTo(hdc, xMax, y);
  2882. // Skip 3 lines in between
  2883. x += 4;
  2884. y += 4;
  2885. }
  2886. SelectPen(hdc, hpenOld);
  2887. DeletePen(hpen);
  2888. }
  2889. //
  2890. // Draw glare with COLOR_3DHILIGHT:
  2891. // Create proper pen
  2892. // Select into hdc
  2893. // Starting at lprc->left, draw a diagonal line then skip the
  2894. // next 3
  2895. // Select out of hdc
  2896. //
  2897. hpen = CreatePen(PS_SOLID, 1, rgbHilight);
  2898. if (hpen == NULL)
  2899. return FALSE;
  2900. hpenOld = SelectPen(hdc, hpen);
  2901. x = lprc->left;
  2902. y = lprc->top;
  2903. while (x < xMax)
  2904. {
  2905. //
  2906. // Since dMin is the same horz and vert, x < xMax and y < yMax
  2907. // are interchangeable...
  2908. //
  2909. MoveToEx(hdc, x, yMax, NULL);
  2910. LineTo(hdc, xMax, y);
  2911. // Skip 3 lines in between
  2912. x += 4;
  2913. y += 4;
  2914. }
  2915. SelectPen(hdc, hpenOld);
  2916. DeletePen(hpen);
  2917. //
  2918. // Draw raised part with COLOR_3DSHADOW:
  2919. // Create proper pen
  2920. // Select into hdc
  2921. // Starting at lprc->left+1, draw 2 diagonal lines, then skip
  2922. // the next 2
  2923. // Select outof hdc
  2924. //
  2925. hpen = CreatePen(PS_SOLID, 1, rgbShadow);
  2926. if (hpen == NULL)
  2927. return FALSE;
  2928. hpenOld = SelectPen(hdc, hpen);
  2929. x = lprc->left+1;
  2930. y = lprc->top+1;
  2931. while (x < xMax)
  2932. {
  2933. //
  2934. // Draw two diagonal lines touching each other.
  2935. //
  2936. MoveToEx(hdc, x, yMax, NULL);
  2937. LineTo(hdc, xMax, y);
  2938. x++;
  2939. y++;
  2940. MoveToEx(hdc, x, yMax, NULL);
  2941. LineTo(hdc, xMax, y);
  2942. //
  2943. // Skip 2 lines inbetween
  2944. //
  2945. x += 3;
  2946. y += 3;
  2947. }
  2948. SelectPen(hdc, hpenOld);
  2949. DeletePen(hpen);
  2950. return TRUE;
  2951. }
  2952. //+-------------------------------------------------------------------------
  2953. // Update the visible characteristics of the gripper depending on whether
  2954. // the dropdown is "dropped up" or the scrollbar is visible
  2955. //--------------------------------------------------------------------------
  2956. void CAutoComplete::_UpdateGrip()
  2957. {
  2958. if (m_hwndGrip)
  2959. {
  2960. //
  2961. // If we have a scrollbar the gripper has a rectangular shape.
  2962. //
  2963. if (m_hwndScroll && IsWindowVisible(m_hwndScroll))
  2964. {
  2965. SetWindowRgn(m_hwndGrip, NULL, FALSE);
  2966. }
  2967. //
  2968. // Otherwise, give it a trinagular window region
  2969. //
  2970. else
  2971. {
  2972. int nWidth = GetSystemMetrics(SM_CXVSCROLL);
  2973. int nHeight = GetSystemMetrics(SM_CYHSCROLL);
  2974. POINT rgpt[3] =
  2975. {
  2976. {nWidth, 0},
  2977. {nWidth, nHeight},
  2978. {0, nHeight},
  2979. };
  2980. //
  2981. // If dropped up, convert the "bottom-Right" tringle into
  2982. // a "top-right" triangle
  2983. //
  2984. if (m_fDroppedUp)
  2985. {
  2986. rgpt[2].y = 0;
  2987. }
  2988. HRGN hrgn = CreatePolygonRgn(rgpt, ARRAYSIZE(rgpt), WINDING);
  2989. if (hrgn && !SetWindowRgn(m_hwndGrip, hrgn, TRUE))
  2990. DeleteObject(hrgn);
  2991. }
  2992. InvalidateRect(m_hwndGrip, NULL, TRUE);
  2993. }
  2994. }
  2995. //+-------------------------------------------------------------------------
  2996. // Transfer the listview scroll info into our scrollbar control
  2997. //--------------------------------------------------------------------------
  2998. void CAutoComplete::_UpdateScrollbar()
  2999. {
  3000. if (m_hwndScroll)
  3001. {
  3002. SCROLLINFO si;
  3003. si.cbSize = sizeof(si);
  3004. si.fMask = SIF_ALL;
  3005. BOOL fScrollVisible = IsWindowVisible(m_hwndScroll);
  3006. if (GetScrollInfo(m_hwndList, SB_VERT, &si))
  3007. {
  3008. SetScrollInfo(m_hwndScroll, SB_CTL, &si, TRUE);
  3009. UINT nRange = si.nMax - si.nMin;
  3010. BOOL fShow = (nRange != 0) && (nRange != (UINT)(si.nPage - 1));
  3011. ShowScrollBar(m_hwndScroll, SB_CTL, fShow);
  3012. if (BOOLIFY(fScrollVisible) ^ BOOLIFY(fShow))
  3013. {
  3014. _UpdateGrip();
  3015. }
  3016. }
  3017. }
  3018. }
  3019. //+-------------------------------------------------------------------------
  3020. // Window procedure for the AutoSuggest drop-down
  3021. //--------------------------------------------------------------------------
  3022. LRESULT CAutoComplete::_DropDownWndProc(UINT uMsg, WPARAM wParam, LPARAM lParam)
  3023. {
  3024. switch (uMsg)
  3025. {
  3026. case WM_NCCREATE:
  3027. {
  3028. //
  3029. // Add a listview to the dropdown
  3030. //
  3031. m_hwndList = CreateWindowEx(0,
  3032. WC_LISTVIEW,
  3033. c_szAutoSuggestTitle,
  3034. WS_CHILD | WS_VISIBLE | WS_CLIPSIBLINGS | LVS_REPORT | LVS_NOCOLUMNHEADER | LVS_SINGLESEL | LVS_OWNERDATA | LVS_OWNERDRAWFIXED,
  3035. 0, 0, 30000, 30000,
  3036. m_hwndDropDown, NULL, HINST_THISDLL, NULL);
  3037. if (m_hwndList)
  3038. {
  3039. SetWindowTheme(m_hwndList, L"AutoComplete", NULL);
  3040. // Subclass the listview window
  3041. if (SetProp(m_hwndList, c_szAutoCompleteProp, this))
  3042. {
  3043. // point it to our wndproc and save the old one
  3044. m_pOldListViewWndProc = (WNDPROC)SetWindowLongPtr(m_hwndList, GWLP_WNDPROC, (LONG_PTR) &s_ListViewWndProc);
  3045. }
  3046. ListView_SetExtendedListViewStyle(m_hwndList, LVS_EX_FULLROWSELECT | LVS_EX_ONECLICKACTIVATE | LVS_EX_TRACKSELECT);
  3047. LV_COLUMN lvColumn;
  3048. lvColumn.mask = LVCF_FMT | LVCF_WIDTH;
  3049. lvColumn.fmt = LVCFMT_LEFT;
  3050. lvColumn.cx = LISTVIEW_COLUMN_WIDTH;
  3051. ListView_InsertColumn(m_hwndList, 0, &lvColumn);
  3052. // We'll get the default dimensions when we first show it
  3053. m_nDropWidth = 0;
  3054. m_nDropHeight = 0;
  3055. // Add a scrollbar
  3056. m_hwndScroll = CreateWindowEx(0, WC_SCROLLBAR, NULL,
  3057. WS_CHILD | SBS_VERT | SBS_RIGHTALIGN,
  3058. 0, 0, 20, 100, m_hwndDropDown, 0, HINST_THISDLL, NULL);
  3059. SetWindowTheme(m_hwndScroll, L"AutoComplete", NULL);
  3060. // Add a sizebox
  3061. m_hwndGrip = CreateWindowEx(0, WC_SCROLLBAR, NULL,
  3062. WS_CHILD | WS_VISIBLE | SBS_SIZEBOX | SBS_SIZEBOXBOTTOMRIGHTALIGN,
  3063. 0, 0, 20, 100, m_hwndDropDown, 0, HINST_THISDLL, NULL);
  3064. if (m_hwndGrip)
  3065. {
  3066. SetWindowSubclass(m_hwndGrip, s_GripperWndProc, 0, (ULONG_PTR)this);
  3067. _UpdateGrip();
  3068. SetWindowTheme(m_hwndGrip, L"AutoComplete", NULL);
  3069. }
  3070. }
  3071. return (m_hwndList != NULL);
  3072. }
  3073. case WM_DESTROY:
  3074. {
  3075. //
  3076. // I'm paranoid - should happen when we're hidden
  3077. //
  3078. if (s_hwndDropDown != NULL && s_hwndDropDown == m_hwndDropDown)
  3079. {
  3080. // Should never happen, but we take extra care not to leak a window hook!
  3081. ASSERT(FALSE);
  3082. ENTERCRITICAL;
  3083. if (s_hhookMouse)
  3084. {
  3085. UnhookWindowsHookEx(s_hhookMouse);
  3086. s_hhookMouse = NULL;
  3087. }
  3088. LEAVECRITICAL;
  3089. s_hwndDropDown = NULL;
  3090. }
  3091. _UnSubClassParent(m_hwndEdit);
  3092. // Unsubclass this window
  3093. SetWindowLongPtr(m_hwndDropDown, GWLP_USERDATA, (LONG_PTR)NULL);
  3094. HWND hwnd = m_hwndDropDown;
  3095. m_hwndDropDown = NULL;
  3096. if (m_hwndList)
  3097. {
  3098. DestroyWindow(m_hwndList);
  3099. m_hwndList = NULL;
  3100. }
  3101. if (m_hwndScroll)
  3102. {
  3103. DestroyWindow(m_hwndScroll);
  3104. m_hwndScroll = NULL;
  3105. }
  3106. if (m_hwndGrip)
  3107. {
  3108. DestroyWindow(m_hwndGrip);
  3109. m_hwndGrip = NULL;
  3110. }
  3111. // The dropdown incremented the autocomplete object's ref count.
  3112. Release();
  3113. return DefWindowProcWrap(hwnd, uMsg, wParam, lParam);
  3114. }
  3115. case WM_SYSCOLORCHANGE:
  3116. SendMessage(m_hwndList, uMsg, wParam, lParam);
  3117. break;
  3118. case WM_WININICHANGE:
  3119. SendMessage(m_hwndList, uMsg, wParam, lParam);
  3120. if (wParam == SPI_SETNONCLIENTMETRICS)
  3121. {
  3122. _UpdateGrip();
  3123. }
  3124. break;
  3125. case WM_GETMINMAXINFO:
  3126. {
  3127. //
  3128. // Don't shrink smaller than the size of the gripper
  3129. //
  3130. LPMINMAXINFO pMmi = (LPMINMAXINFO)lParam;
  3131. pMmi->ptMinTrackSize.x = GetSystemMetrics(SM_CXVSCROLL);
  3132. pMmi->ptMinTrackSize.y = GetSystemMetrics(SM_CYHSCROLL);
  3133. return 0;
  3134. }
  3135. case WM_MOVE:
  3136. {
  3137. //
  3138. // Reposition the list view in case we switch between dropping-down
  3139. // and dropping up.
  3140. //
  3141. RECT rc;
  3142. GetClientRect(m_hwndDropDown, &rc);
  3143. int nWidth = RECTWIDTH(rc);
  3144. int nHeight = RECTHEIGHT(rc);
  3145. int cxGrip = GetSystemMetrics(SM_CXVSCROLL);
  3146. int cyGrip = GetSystemMetrics(SM_CYHSCROLL);
  3147. if (m_fDroppedUp)
  3148. {
  3149. SetWindowPos(m_hwndGrip, HWND_TOP, nWidth - cxGrip, 0, cxGrip, cyGrip, SWP_NOACTIVATE);
  3150. SetWindowPos(m_hwndScroll, HWND_TOP, nWidth - cxGrip, cyGrip, cxGrip, nHeight-cyGrip, SWP_NOACTIVATE);
  3151. }
  3152. else
  3153. {
  3154. SetWindowPos(m_hwndGrip, HWND_TOP, nWidth - cxGrip, nHeight - cyGrip, cxGrip, cyGrip, SWP_NOACTIVATE);
  3155. SetWindowPos(m_hwndScroll, HWND_TOP, nWidth - cxGrip, 0, cxGrip, nHeight-cyGrip, SWP_NOACTIVATE);
  3156. }
  3157. break;
  3158. }
  3159. case WM_SIZE:
  3160. {
  3161. int nWidth = LOWORD(lParam);
  3162. int nHeight = HIWORD(lParam);
  3163. int cxGrip = GetSystemMetrics(SM_CXVSCROLL);
  3164. int cyGrip = GetSystemMetrics(SM_CYHSCROLL);
  3165. if (m_fDroppedUp)
  3166. {
  3167. SetWindowPos(m_hwndGrip, HWND_TOP, nWidth - cxGrip, 0, cxGrip, cyGrip, SWP_NOACTIVATE);
  3168. SetWindowPos(m_hwndScroll, HWND_TOP, nWidth - cxGrip, cyGrip, cxGrip, nHeight-cyGrip, SWP_NOACTIVATE);
  3169. }
  3170. else
  3171. {
  3172. SetWindowPos(m_hwndGrip, HWND_TOP, nWidth - cxGrip, nHeight - cyGrip, cxGrip, cyGrip, SWP_NOACTIVATE);
  3173. SetWindowPos(m_hwndScroll, HWND_TOP, nWidth - cxGrip, 0, cxGrip, nHeight-cyGrip, SWP_NOACTIVATE);
  3174. }
  3175. // Save the new dimensions
  3176. m_nDropWidth = nWidth + 2*GetSystemMetrics(SM_CXBORDER);
  3177. m_nDropHeight = nHeight + 2*GetSystemMetrics(SM_CYBORDER);
  3178. MoveWindow(m_hwndList, 0, 0, LISTVIEW_COLUMN_WIDTH + 10*cxGrip, nHeight, TRUE);
  3179. _UpdateScrollbar();
  3180. InvalidateRect(m_hwndList, NULL, FALSE);
  3181. break;
  3182. }
  3183. case WM_NCHITTEST:
  3184. {
  3185. RECT rc;
  3186. POINT pt = {GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam)};
  3187. // If the in the grip, show the sizing cursor
  3188. if (m_hwndGrip)
  3189. {
  3190. GetWindowRect(m_hwndGrip, &rc);
  3191. if (PtInRect(&rc, pt))
  3192. {
  3193. if(IS_WINDOW_RTL_MIRRORED(m_hwndDropDown))
  3194. {
  3195. return (m_fDroppedUp) ? HTTOPLEFT : HTBOTTOMLEFT;
  3196. }
  3197. else
  3198. {
  3199. return (m_fDroppedUp) ? HTTOPRIGHT : HTBOTTOMRIGHT;
  3200. }
  3201. }
  3202. }
  3203. }
  3204. break;
  3205. case WM_SHOWWINDOW:
  3206. {
  3207. s_fNoActivate = FALSE;
  3208. BOOL fShow = (BOOL)wParam;
  3209. if (!fShow)
  3210. {
  3211. //
  3212. // We are being hidden so we no longer need to
  3213. // subclass the parent windows.
  3214. //
  3215. _UnSubClassParent(m_hwndEdit);
  3216. //
  3217. // Remove the mouse hook. We shouldn't need to protect this global with
  3218. // a critical section because another dropdown cannot be shown
  3219. // before we are hidden. But we don't want to chance orphaning a hook
  3220. // so to be safe we protect write access to this variable.
  3221. //
  3222. ENTERCRITICAL;
  3223. if (s_hhookMouse)
  3224. {
  3225. UnhookWindowsHookEx(s_hhookMouse);
  3226. s_hhookMouse = NULL;
  3227. }
  3228. LEAVECRITICAL;
  3229. s_hwndDropDown = NULL;
  3230. // Deselect the current selection
  3231. int iCurSel = ListView_GetNextItem(m_hwndList, -1, LVNI_SELECTED);
  3232. if (iCurSel)
  3233. {
  3234. ListView_SetItemState(m_hwndList, iCurSel, 0, 0x000f);
  3235. }
  3236. }
  3237. }
  3238. break;
  3239. case WM_MOUSEACTIVATE:
  3240. //
  3241. // We don't want mouse clicks to activate us and
  3242. // take focus from the edit box.
  3243. //
  3244. return (LRESULT)MA_NOACTIVATE;
  3245. case WM_NCLBUTTONDOWN:
  3246. //
  3247. // We don't want resizing to activate us and deactivate the app.
  3248. // The WM_MOUSEACTIVATE message above prevents mouse downs from
  3249. // activating us, but mouse up after a resize still activates us.
  3250. //
  3251. if (wParam == HTBOTTOMRIGHT ||
  3252. wParam == HTTOPRIGHT)
  3253. {
  3254. s_fNoActivate = TRUE;
  3255. }
  3256. break;
  3257. case WM_VSCROLL:
  3258. {
  3259. ASSERT(m_hwndScroll);
  3260. //
  3261. // Pass the scroll messages from our control to the listview
  3262. //
  3263. WORD nScrollCode = LOWORD(wParam);
  3264. if (nScrollCode == SB_THUMBTRACK || nScrollCode == SB_THUMBPOSITION)
  3265. {
  3266. //
  3267. // The listview ignores the 16-bit position passed in and
  3268. // queries the internal window scrollbar for the tracking
  3269. // position. Since this returns the wrong track position,
  3270. // we have to handle thumb tracking ourselves.
  3271. //
  3272. WORD nPos = HIWORD(wParam);
  3273. SCROLLINFO si;
  3274. si.cbSize = sizeof(si);
  3275. si.fMask = SIF_ALL;
  3276. if (GetScrollInfo(m_hwndScroll, SB_CTL, &si))
  3277. {
  3278. //
  3279. // The track position is always at the top of the list.
  3280. // So, if we are scrolling up, make sure that the track
  3281. // position is visible. Otherwise we need to ensure
  3282. // that a full page is visible below the track positon.
  3283. //
  3284. int nEnsureVisible = si.nTrackPos;
  3285. if (si.nTrackPos > si.nPos)
  3286. {
  3287. nEnsureVisible += si.nPage - 1;
  3288. }
  3289. SendMessage(m_hwndList, LVM_ENSUREVISIBLE, nEnsureVisible, FALSE);
  3290. }
  3291. }
  3292. else
  3293. {
  3294. // Let listview handle it
  3295. SendMessage(m_hwndList, uMsg, wParam, lParam);
  3296. }
  3297. _UpdateScrollbar();
  3298. return 0;
  3299. }
  3300. case WM_EXITSIZEMOVE:
  3301. //
  3302. // Resize operation is over so permit the app to lose acitvation
  3303. //
  3304. s_fNoActivate = FALSE;
  3305. m_fDropDownResized = TRUE;
  3306. return 0;
  3307. case WM_DRAWITEM:
  3308. _DropDownDrawItem((LPDRAWITEMSTRUCT)lParam);
  3309. break;
  3310. case WM_NOTIFY:
  3311. if (_DropDownNotify((LPNMHDR)lParam))
  3312. {
  3313. return TRUE;
  3314. }
  3315. break;
  3316. case AM_UPDATESCROLLPOS:
  3317. {
  3318. if (m_hwndScroll)
  3319. {
  3320. int nTop = ListView_GetTopIndex(m_hwndList);
  3321. SetScrollPos(m_hwndScroll, SB_CTL, nTop, TRUE);
  3322. }
  3323. return 0;
  3324. }
  3325. case AM_BUTTONCLICK:
  3326. {
  3327. //
  3328. // This message is sent by the thread hook when a mouse click is detected outside
  3329. // the drop-down window. Unless the click occurred inside the combobox, we will
  3330. // hide the dropdown.
  3331. //
  3332. MOUSEHOOKSTRUCT *pmhs = (MOUSEHOOKSTRUCT*)lParam;
  3333. HWND hwnd = pmhs->hwnd;
  3334. RECT rc;
  3335. if (hwnd != m_hwndCombo && hwnd != m_hwndEdit &&
  3336. // See if we clicked within the bounds of the editbox. This is
  3337. // necessary for intelliforms.
  3338. // FEATURE: This assumes that the editbox is entirely visible!
  3339. GetWindowRect(m_hwndEdit, &rc) && !PtInRect(&rc, pmhs->pt))
  3340. {
  3341. _HideDropDown();
  3342. }
  3343. return 0;
  3344. }
  3345. case AM_DESTROY:
  3346. {
  3347. //
  3348. // We post this message to destroy the dropdown to avoid a strange crash
  3349. // that happens if we call DestroyWindow when the edit window is destroyed.
  3350. // The crash happens when a parent of the endit window is on another thread
  3351. // and that parent is destroyed in the middle of us creating a listview
  3352. // child of the dropdown.
  3353. //
  3354. DestroyWindow(m_hwndDropDown);
  3355. return 0;
  3356. }
  3357. }
  3358. return DefWindowProcWrap(m_hwndDropDown, uMsg, wParam, lParam);
  3359. }
  3360. void CAutoComplete::_DropDownDrawItem(LPDRAWITEMSTRUCT pdis)
  3361. {
  3362. //
  3363. // We need to draw the contents of the list view ourselves
  3364. // so that we can show items in the selected state even
  3365. // when the edit control has focus.
  3366. //
  3367. if (pdis->itemID != -1)
  3368. {
  3369. HDC hdc = pdis->hDC;
  3370. RECT rc = pdis->rcItem;
  3371. BOOL fTextHighlight = pdis->itemState & ODS_SELECTED;
  3372. // Setup the dc before we use it.
  3373. BOOL fRTLReading = GetWindowLong(pdis->hwndItem, GWL_EXSTYLE) & WS_EX_RTLREADING;
  3374. UINT uiOldTextAlign;
  3375. if (fRTLReading)
  3376. {
  3377. uiOldTextAlign = GetTextAlign(hdc);
  3378. SetTextAlign(hdc, uiOldTextAlign | TA_RTLREADING);
  3379. }
  3380. if (m_hfontListView)
  3381. {
  3382. SelectObject(hdc, m_hfontListView);
  3383. }
  3384. SetBkColor(hdc, GetSysColor(fTextHighlight ?
  3385. COLOR_HIGHLIGHT : COLOR_WINDOW));
  3386. SetTextColor(hdc, GetSysColor(fTextHighlight ?
  3387. COLOR_HIGHLIGHTTEXT : COLOR_WINDOWTEXT));
  3388. // Center the string vertically within rc
  3389. SIZE sizeText;
  3390. WCHAR szText[MAX_URL_STRING];
  3391. _GetItem(pdis->itemID, szText, ARRAYSIZE(szText), TRUE);
  3392. int cch = lstrlen(szText);
  3393. GetTextExtentPoint(hdc, szText, cch, &sizeText);
  3394. int yMid = (rc.top + rc.bottom) / 2;
  3395. int yString = yMid - (sizeText.cy/2);
  3396. int xString = 5;
  3397. //
  3398. // If this is a .url string, don't display the extension
  3399. //
  3400. if (cch > 4 && StrCmpNI(szText + cch - 4, L".url", 4) == 0)
  3401. {
  3402. cch -= 4;
  3403. }
  3404. ExtTextOut(hdc, xString, yString, ETO_OPAQUE | ETO_CLIPPED, &rc, szText, cch, NULL);
  3405. // Restore the text align in the dc.
  3406. if (fRTLReading)
  3407. {
  3408. SetTextAlign(hdc, uiOldTextAlign);
  3409. }
  3410. }
  3411. }
  3412. BOOL CAutoComplete::_DropDownNotify(LPNMHDR pnmhdr)
  3413. {
  3414. WCHAR szBuf[MAX_URL_STRING]; // Just one instance of the buffer
  3415. //
  3416. // Respond to notification messages from the list view
  3417. //
  3418. switch (pnmhdr->code)
  3419. {
  3420. case LVN_GETDISPINFO:
  3421. {
  3422. //
  3423. // Return the text for an autosuggest item
  3424. //
  3425. ASSERT(pnmhdr->hwndFrom == m_hwndList);
  3426. LV_DISPINFO* pdi = (LV_DISPINFO*)pnmhdr;
  3427. if (pdi->item.mask & LVIF_TEXT)
  3428. {
  3429. _GetItem(pdi->item.iItem, pdi->item.pszText, pdi->item.cchTextMax, TRUE);
  3430. }
  3431. break;
  3432. }
  3433. case LVN_GETDISPINFOA:
  3434. {
  3435. //
  3436. // For win9x support (added for automated testing)
  3437. //
  3438. ASSERT(pnmhdr->hwndFrom == m_hwndList);
  3439. LV_DISPINFOA* pdi = (LV_DISPINFOA*)pnmhdr;
  3440. if (pdi->item.mask & LVIF_TEXT)
  3441. {
  3442. _GetItem(pdi->item.iItem, szBuf, ARRAYSIZE(szBuf), TRUE);
  3443. SHUnicodeToAnsi(szBuf, pdi->item.pszText, pdi->item.cchTextMax);
  3444. }
  3445. break;
  3446. }
  3447. case LVN_ITEMCHANGED:
  3448. {
  3449. //
  3450. // When an item is selected in the list view, we transfer it to the
  3451. // edit control. But only if the selection was not caused by the
  3452. // mouse passing over an element (hot tracking).
  3453. //
  3454. if (!m_fInHotTracking)
  3455. {
  3456. LPNMLISTVIEW pnmv = (LPNMLISTVIEW)pnmhdr;
  3457. if ((pnmv->uChanged & LVIF_STATE) && (pnmv->uNewState & (LVIS_FOCUSED | LVIS_SELECTED)))
  3458. {
  3459. _GetItem(pnmv->iItem, szBuf, ARRAYSIZE(szBuf), FALSE);
  3460. // Copy the selection to the edit box and place caret at the end
  3461. _SetEditText(szBuf);
  3462. int cch = lstrlen(szBuf);
  3463. Edit_SetSel(m_hwndEdit, cch, cch);
  3464. }
  3465. }
  3466. //
  3467. // Update the scrollbar. Note that we have to post a message to do this
  3468. // after returning from this function. Otherwise we get old info
  3469. // from the listview about the scroll positon.
  3470. //
  3471. PostMessage(m_hwndDropDown, AM_UPDATESCROLLPOS, 0, 0);
  3472. break;
  3473. }
  3474. case LVN_ITEMACTIVATE:
  3475. {
  3476. //
  3477. // Someone activated an item in the listview. We want to make sure that
  3478. // the items is selected (without hot tracking) so that the contents
  3479. // are moved to the edit box, and then simulate and enter key press.
  3480. //
  3481. LPNMITEMACTIVATE lpnmia = (LPNMITEMACTIVATE)pnmhdr;
  3482. _GetItem(lpnmia->iItem, szBuf, ARRAYSIZE(szBuf), FALSE);
  3483. // Copy the selection to the edit box and place caret at the end
  3484. _SetEditText(szBuf);
  3485. int cch = lstrlen(szBuf);
  3486. Edit_SetSel(m_hwndEdit, cch, cch);
  3487. //
  3488. // Intelliforms don't want an enter key because this would submit the
  3489. // form, so first we try sending a notification.
  3490. //
  3491. if (SendMessage(m_hwndEdit, m_uMsgItemActivate, 0, (LPARAM)szBuf) == 0)
  3492. {
  3493. // Not an intelliform, so simulate an enter key instead.
  3494. SendMessage(m_hwndEdit, WM_KEYDOWN, VK_RETURN, 0);
  3495. SendMessage(m_hwndEdit, WM_KEYUP, VK_RETURN, 0);
  3496. }
  3497. _HideDropDown();
  3498. break;
  3499. }
  3500. case LVN_HOTTRACK:
  3501. {
  3502. //
  3503. // Select items as we mouse-over them
  3504. //
  3505. LPNMLISTVIEW lpnmlv = (LPNMLISTVIEW)pnmhdr;
  3506. LVHITTESTINFO lvh;
  3507. lvh.pt = lpnmlv->ptAction;
  3508. int iItem = ListView_HitTest(m_hwndList, &lvh);
  3509. if (iItem != -1)
  3510. {
  3511. // Update the current selection. The m_fInHotTracking flag prevents the
  3512. // edit box contents from being updated
  3513. m_fInHotTracking = TRUE;
  3514. ListView_SetItemState(m_hwndList, iItem, LVIS_SELECTED|LVIS_FOCUSED, 0x000f);
  3515. SendMessage(m_hwndList, LVM_ENSUREVISIBLE, iItem, FALSE);
  3516. m_fInHotTracking = FALSE;
  3517. }
  3518. // We processed this...
  3519. return TRUE;
  3520. }
  3521. }
  3522. return FALSE;
  3523. }
  3524. //+-------------------------------------------------------------------------
  3525. // Static window procedure for the AutoSuggest drop-down
  3526. //--------------------------------------------------------------------------
  3527. LRESULT CALLBACK CAutoComplete::s_DropDownWndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
  3528. {
  3529. CAutoComplete* pThis;
  3530. if (uMsg == WM_NCCREATE)
  3531. {
  3532. pThis = (CAutoComplete*)((LPCREATESTRUCT)lParam)->lpCreateParams;
  3533. SetWindowLongPtr(hwnd, GWLP_USERDATA, (LONG_PTR) pThis);
  3534. pThis->m_hwndDropDown = hwnd;
  3535. }
  3536. else
  3537. {
  3538. pThis = (CAutoComplete*)GetWindowLongPtr(hwnd, GWLP_USERDATA);
  3539. }
  3540. if (pThis && (pThis->m_hwndDropDown == hwnd))
  3541. {
  3542. return pThis->_DropDownWndProc(uMsg, wParam, lParam);
  3543. }
  3544. else
  3545. {
  3546. return DefWindowProcWrap(hwnd, uMsg, wParam, lParam);
  3547. }
  3548. }
  3549. //+-------------------------------------------------------------------------
  3550. // We subclass the listview to prevent it from activating the drop-down
  3551. // when someone clicks on it.
  3552. //--------------------------------------------------------------------------
  3553. LRESULT CAutoComplete::_ListViewWndProc(UINT uMsg, WPARAM wParam, LPARAM lParam)
  3554. {
  3555. LRESULT lRet;
  3556. switch (uMsg)
  3557. {
  3558. case WM_LBUTTONDOWN:
  3559. case WM_RBUTTONDOWN:
  3560. case WM_MBUTTONDOWN:
  3561. case WM_LBUTTONUP:
  3562. case WM_RBUTTONUP:
  3563. case WM_MBUTTONUP:
  3564. //
  3565. // Prevent mouse clicks from activating this view
  3566. //
  3567. s_fNoActivate = TRUE;
  3568. lRet = CallWindowProc(m_pOldListViewWndProc, m_hwndList, uMsg, wParam, lParam);
  3569. s_fNoActivate = FALSE;
  3570. return 0;
  3571. case WM_DESTROY:
  3572. // Restore old wndproc.
  3573. RemoveProp(m_hwndList, c_szAutoCompleteProp);
  3574. if (m_pOldListViewWndProc)
  3575. {
  3576. SetWindowLongPtr(m_hwndList, GWLP_WNDPROC, (LONG_PTR) m_pOldListViewWndProc);
  3577. lRet = CallWindowProc(m_pOldListViewWndProc, m_hwndList, uMsg, wParam, lParam);
  3578. m_pOldListViewWndProc = NULL;
  3579. }
  3580. return 0;
  3581. case WM_GETOBJECT:
  3582. if (lParam == OBJID_CLIENT)
  3583. {
  3584. SAFERELEASE(m_pDelegateAccObj);
  3585. if (SUCCEEDED(CreateStdAccessibleObject(m_hwndList,
  3586. OBJID_CLIENT,
  3587. IID_IAccessible,
  3588. (void **)&m_pDelegateAccObj)))
  3589. {
  3590. return LresultFromObject(IID_IAccessible, wParam, SAFECAST(this, IAccessible *));
  3591. }
  3592. }
  3593. break;
  3594. case WM_NCHITTEST:
  3595. {
  3596. RECT rc;
  3597. POINT pt = {GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam)};
  3598. // If in the grip area, let our parent handle it
  3599. if (m_hwndGrip)
  3600. {
  3601. GetWindowRect(m_hwndGrip, &rc);
  3602. if (PtInRect(&rc, pt))
  3603. {
  3604. return HTTRANSPARENT;
  3605. }
  3606. }
  3607. break;
  3608. }
  3609. }
  3610. lRet = CallWindowProc(m_pOldListViewWndProc, m_hwndList, uMsg, wParam, lParam);
  3611. return lRet;
  3612. }
  3613. //+-------------------------------------------------------------------------
  3614. // Static window procedure for the subclassed listview
  3615. //--------------------------------------------------------------------------
  3616. LRESULT CAutoComplete::s_ListViewWndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
  3617. {
  3618. CAutoComplete* pac = (CAutoComplete*)GetProp(hwnd, c_szAutoCompleteProp);
  3619. if (pac)
  3620. {
  3621. return pac->_ListViewWndProc(uMsg, wParam, lParam);
  3622. }
  3623. else
  3624. {
  3625. TraceMsg(TF_WARNING, "CAutoComplete::s_ListViewWndProc() --- pac == NULL");
  3626. return DefWindowProcWrap(hwnd, uMsg, wParam, lParam);
  3627. }
  3628. }
  3629. //+-------------------------------------------------------------------------
  3630. // This message hook is only installed when the AutoSuggest dropdown
  3631. // is visible. It hides the dropdown if you click on any window other than
  3632. // the dropdown or associated editbox/combobox.
  3633. //--------------------------------------------------------------------------
  3634. LRESULT CALLBACK CAutoComplete::s_MouseHook(int nCode, WPARAM wParam, LPARAM lParam)
  3635. {
  3636. if (nCode >= 0)
  3637. {
  3638. MOUSEHOOKSTRUCT *pmhs = (MOUSEHOOKSTRUCT*)lParam;
  3639. ASSERT(pmhs);
  3640. switch (wParam)
  3641. {
  3642. case WM_LBUTTONDOWN:
  3643. case WM_MBUTTONDOWN:
  3644. case WM_RBUTTONDOWN:
  3645. case WM_NCLBUTTONDOWN:
  3646. case WM_NCMBUTTONDOWN:
  3647. case WM_NCRBUTTONDOWN:
  3648. {
  3649. HWND hwnd = pmhs->hwnd;
  3650. // If the click was outside the edit/combobox/dropdown, then
  3651. // hide the dropdown.
  3652. if (hwnd != s_hwndDropDown)
  3653. {
  3654. // Ignore if the button was clicked in the dropdown
  3655. RECT rc;
  3656. if (GetWindowRect(s_hwndDropDown, &rc) && !PtInRect(&rc, pmhs->pt))
  3657. {
  3658. // Inform the dropdown
  3659. SendMessage(s_hwndDropDown, AM_BUTTONCLICK, 0, (LPARAM)pmhs);
  3660. }
  3661. }
  3662. break;
  3663. }
  3664. }
  3665. }
  3666. return CallNextHookEx(s_hhookMouse, nCode, wParam, lParam);
  3667. }
  3668. //+-------------------------------------------------------------------------
  3669. // Subclasses all of the parents of hwnd so we can determine when they
  3670. // are moved, deactivated, or clicked on. We use these events to signal
  3671. // the window that has focus to hide its autocomplete dropdown. This is
  3672. // similar to the CB_SHOWDROPDOWN message sent to comboboxes, but we cannot
  3673. // assume that we are autocompleting a combobox.
  3674. //--------------------------------------------------------------------------
  3675. void CAutoComplete::_SubClassParent
  3676. (
  3677. HWND hwnd // window to notify of events
  3678. )
  3679. {
  3680. //
  3681. // Subclass all the parent windows because any of them could cause
  3682. // the position of hwnd to change which should hide the dropdown.
  3683. //
  3684. HWND hwndParent = hwnd;
  3685. DWORD dwThread = GetCurrentThreadId();
  3686. while (hwndParent = GetParent(hwndParent))
  3687. {
  3688. // Only subclass if this window is owned by our thread
  3689. if (dwThread == GetWindowThreadProcessId(hwndParent, NULL))
  3690. {
  3691. SetWindowSubclass(hwndParent, s_ParentWndProc, 0, (ULONG_PTR)this);
  3692. }
  3693. }
  3694. }
  3695. //+-------------------------------------------------------------------------
  3696. // Unsubclasses all of the parents of hwnd. We use the helper functions in
  3697. // comctl32 to safely unsubclass a window even if someone else subclassed
  3698. // the window after us.
  3699. //--------------------------------------------------------------------------
  3700. void CAutoComplete::_UnSubClassParent
  3701. (
  3702. HWND hwnd // window to notify of events
  3703. )
  3704. {
  3705. HWND hwndParent = hwnd;
  3706. DWORD dwThread = GetCurrentThreadId();
  3707. while (hwndParent = GetParent(hwndParent))
  3708. {
  3709. // Only need to unsubclass if this window is owned by our thread
  3710. if (dwThread == GetWindowThreadProcessId(hwndParent, NULL))
  3711. {
  3712. RemoveWindowSubclass(hwndParent, s_ParentWndProc, 0);
  3713. }
  3714. }
  3715. }
  3716. //+-------------------------------------------------------------------------
  3717. // Subclassed window procedure of the parents ot the editbox being
  3718. // autocompleted. This intecepts messages that should case the autocomplete
  3719. // dropdown to be hidden.
  3720. //--------------------------------------------------------------------------
  3721. LRESULT CALLBACK CAutoComplete::s_ParentWndProc
  3722. (
  3723. HWND hwnd,
  3724. UINT uMsg,
  3725. WPARAM wParam,
  3726. LPARAM lParam,
  3727. UINT_PTR uIdSubclass, // always zero for us
  3728. DWORD_PTR dwRefData // -> CParentWindow
  3729. )
  3730. {
  3731. CAutoComplete* pThis = (CAutoComplete*)dwRefData;
  3732. if (!pThis)
  3733. return DefSubclassProc(hwnd, uMsg, wParam, lParam);
  3734. switch (uMsg)
  3735. {
  3736. case WM_WINDOWPOSCHANGED:
  3737. {
  3738. //
  3739. // Check the elapsed time since this was last called. We want to avoid an infinite loop
  3740. // with another window that also wants to be on top.
  3741. //
  3742. static DWORD s_dwTicks = 0;
  3743. DWORD dwTicks = GetTickCount();
  3744. DWORD dwEllapsed = dwTicks - s_dwTicks;
  3745. s_dwTicks = dwTicks;
  3746. if (dwEllapsed > 100)
  3747. {
  3748. // Make sure our dropdown stays on top
  3749. LPWINDOWPOS pwp = (LPWINDOWPOS)lParam;
  3750. if (IsFlagClear(pwp->flags, SWP_NOZORDER) && IsWindowVisible(pThis->m_hwndDropDown))
  3751. {
  3752. SetWindowPos(pThis->m_hwndDropDown, HWND_TOP, 0, 0, 0, 0, SWP_NOSIZE | SWP_NOMOVE | SWP_NOACTIVATE);
  3753. }
  3754. }
  3755. break;
  3756. }
  3757. case WM_ACTIVATE:
  3758. {
  3759. // Ignore if we are not being deactivated
  3760. WORD fActive = LOWORD(wParam);
  3761. if (fActive != WA_INACTIVE)
  3762. {
  3763. break;
  3764. }
  3765. // Drop through
  3766. }
  3767. case WM_MOVING:
  3768. case WM_SIZE:
  3769. case WM_LBUTTONDOWN:
  3770. case WM_RBUTTONDOWN:
  3771. case WM_MBUTTONDOWN:
  3772. pThis->_HideDropDown();
  3773. break;
  3774. case WM_NCACTIVATE:
  3775. //
  3776. // While clicking on the autosuggest dropdown, we
  3777. // want to prevent the dropdown from being activated.
  3778. //
  3779. if (s_fNoActivate)
  3780. return FALSE;
  3781. break;
  3782. case WM_DESTROY:
  3783. RemoveWindowSubclass(hwnd, s_ParentWndProc, 0);
  3784. break;
  3785. default:
  3786. break;
  3787. }
  3788. return DefSubclassProc(hwnd, uMsg, wParam, lParam);
  3789. }
  3790. //+-------------------------------------------------------------------------
  3791. // Subclassed window procedure fir the grip re-sizer control
  3792. //--------------------------------------------------------------------------
  3793. LRESULT CALLBACK CAutoComplete::s_GripperWndProc
  3794. (
  3795. HWND hwnd,
  3796. UINT uMsg,
  3797. WPARAM wParam,
  3798. LPARAM lParam,
  3799. UINT_PTR uIdSubclass, // always zero for us
  3800. DWORD_PTR dwRefData // -> CParentWindow
  3801. )
  3802. {
  3803. CAutoComplete* pThis = (CAutoComplete*)dwRefData;
  3804. if (!pThis)
  3805. return DefSubclassProc(hwnd, uMsg, wParam, lParam);
  3806. switch (uMsg)
  3807. {
  3808. case WM_NCHITTEST:
  3809. return HTTRANSPARENT;
  3810. case WM_PAINT:
  3811. PAINTSTRUCT ps;
  3812. BeginPaint(hwnd, &ps);
  3813. EndPaint(hwnd, &ps);
  3814. break;
  3815. case WM_ERASEBKGND:
  3816. {
  3817. HDC hdc = (HDC)wParam;
  3818. RECT rc;
  3819. GetClientRect(hwnd, &rc);
  3820. int nOldMapMode = 0;
  3821. BOOL fScrollVisible = pThis->m_hwndScroll && IsWindowVisible(pThis->m_hwndScroll);
  3822. //
  3823. // See if we need to vertically flip the grip
  3824. //
  3825. if (pThis->m_fDroppedUp)
  3826. {
  3827. nOldMapMode = SetMapMode(hdc, MM_ANISOTROPIC);
  3828. SetWindowOrgEx(hdc, 0, 0, NULL);
  3829. SetWindowExtEx(hdc, 1, 1, NULL);
  3830. SetViewportOrgEx(hdc, 0, GetSystemMetrics(SM_CYHSCROLL), NULL);
  3831. SetViewportExtEx(hdc, 1, -1, NULL);
  3832. }
  3833. // The standard DrawFrameControl API does not draw upside down on all platforms
  3834. // DrawFrameControl(hdc, &rc, DFC_SCROLL, DFCS_SCROLLSIZEGRIP);
  3835. DrawGrip(hdc, &rc, fScrollVisible);
  3836. if (nOldMapMode)
  3837. {
  3838. SetViewportOrgEx(hdc, 0, 0, NULL);
  3839. SetViewportExtEx(hdc, 1, 1, NULL);
  3840. SetMapMode(hdc, nOldMapMode);
  3841. }
  3842. return 1;
  3843. }
  3844. case WM_DESTROY:
  3845. RemoveWindowSubclass(hwnd, s_GripperWndProc, 0);
  3846. break;
  3847. default:
  3848. break;
  3849. }
  3850. return DefSubclassProc(hwnd, uMsg, wParam, lParam);
  3851. }