Mercurial > foo_out_sdl
comparison foosdk/sdk/libPPUI/InPlaceCombo.cpp @ 1:20d02a178406 default tip
*: check in everything else
yay
| author | Paper <paper@tflc.us> |
|---|---|
| date | Mon, 05 Jan 2026 02:15:46 -0500 |
| parents | |
| children |
comparison
equal
deleted
inserted
replaced
| 0:e9bb126753e7 | 1:20d02a178406 |
|---|---|
| 1 #include "stdafx.h" | |
| 2 | |
| 3 #include "InPlaceEdit.h" | |
| 4 #include "wtl-pp.h" | |
| 5 #include "win32_op.h" | |
| 6 | |
| 7 #include "AutoComplete.h" | |
| 8 #include "CWindowCreateAndDelete.h" | |
| 9 #include "win32_utility.h" | |
| 10 #include "listview_helper.h" // ListView_GetColumnCount | |
| 11 #include "clipboard.h" | |
| 12 #include "DarkMode.h" | |
| 13 | |
| 14 #include <forward_list> | |
| 15 | |
| 16 using namespace InPlaceEdit; | |
| 17 | |
| 18 | |
| 19 namespace { | |
| 20 | |
| 21 enum { | |
| 22 MSG_COMPLETION = WM_USER, | |
| 23 MSG_DISABLE_EDITING | |
| 24 }; | |
| 25 | |
| 26 | |
| 27 // Rationale: more than one HWND on the list is extremely uncommon, hence forward_list | |
| 28 static std::forward_list<HWND> g_editboxes; | |
| 29 static HHOOK g_hook = NULL /*, g_keyHook = NULL*/; | |
| 30 | |
| 31 static void GAbortEditing(HWND edit, t_uint32 code) { | |
| 32 CWindow parent = ::GetParent(edit); | |
| 33 parent.SendMessage(MSG_DISABLE_EDITING); | |
| 34 parent.PostMessage(MSG_COMPLETION, code, 0); | |
| 35 } | |
| 36 | |
| 37 #if 0 | |
| 38 static void GAbortEditing(t_uint32 code) { | |
| 39 for (auto walk = g_editboxes.begin(); walk != g_editboxes.end(); ++walk) { | |
| 40 GAbortEditing(*walk, code); | |
| 41 } | |
| 42 } | |
| 43 #endif | |
| 44 | |
| 45 static bool IsSamePopup(CWindow wnd1, CWindow wnd2) { | |
| 46 return pfc::findOwningPopup(wnd1) == pfc::findOwningPopup(wnd2); | |
| 47 } | |
| 48 | |
| 49 static void MouseEventTest(HWND target, CPoint pt, bool isWheel) { | |
| 50 for (auto walk = g_editboxes.begin(); walk != g_editboxes.end(); ++walk) { | |
| 51 CWindow edit(*walk); | |
| 52 bool cancel = false; | |
| 53 if (target != edit && IsSamePopup(target, edit)) { | |
| 54 cancel = true; | |
| 55 } else if (isWheel) { | |
| 56 CWindow target2 = WindowFromPoint(pt); | |
| 57 if (target2 != edit && IsSamePopup(target2, edit)) { | |
| 58 cancel = true; | |
| 59 } | |
| 60 } | |
| 61 | |
| 62 if (cancel) GAbortEditing(edit, KEditLostFocus); | |
| 63 } | |
| 64 } | |
| 65 | |
| 66 static LRESULT CALLBACK GMouseProc(int nCode, WPARAM wParam, LPARAM lParam) { | |
| 67 if (nCode == HC_ACTION) { | |
| 68 const MOUSEHOOKSTRUCT * mhs = reinterpret_cast<const MOUSEHOOKSTRUCT *>(lParam); | |
| 69 switch (wParam) { | |
| 70 case WM_NCLBUTTONDOWN: | |
| 71 case WM_NCRBUTTONDOWN: | |
| 72 case WM_NCMBUTTONDOWN: | |
| 73 case WM_NCXBUTTONDOWN: | |
| 74 case WM_LBUTTONDOWN: | |
| 75 case WM_RBUTTONDOWN: | |
| 76 case WM_MBUTTONDOWN: | |
| 77 case WM_XBUTTONDOWN: | |
| 78 MouseEventTest(mhs->hwnd, mhs->pt, false); | |
| 79 break; | |
| 80 case WM_MOUSEWHEEL: | |
| 81 case WM_MOUSEHWHEEL: | |
| 82 MouseEventTest(mhs->hwnd, mhs->pt, true); | |
| 83 break; | |
| 84 } | |
| 85 } | |
| 86 return CallNextHookEx(g_hook, nCode, wParam, lParam); | |
| 87 } | |
| 88 | |
| 89 static void on_editbox_creation(HWND p_editbox) { | |
| 90 // PFC_ASSERT(core_api::is_main_thread()); | |
| 91 g_editboxes.push_front(p_editbox); | |
| 92 if (g_hook == NULL) { | |
| 93 g_hook = SetWindowsHookEx(WH_MOUSE, GMouseProc, NULL, GetCurrentThreadId()); | |
| 94 } | |
| 95 /*if (g_keyHook == NULL) { | |
| 96 g_keyHook = SetWindowsHookEx(WH_KEYBOARD, GKeyboardProc, NULL, GetCurrentThreadId()); | |
| 97 }*/ | |
| 98 } | |
| 99 static void UnhookHelper(HHOOK & hook) { | |
| 100 HHOOK v = pfc::replace_null_t(hook); | |
| 101 if (v != NULL) UnhookWindowsHookEx(v); | |
| 102 } | |
| 103 static void on_editbox_destruction(HWND p_editbox) { | |
| 104 // PFC_ASSERT(core_api::is_main_thread()); | |
| 105 g_editboxes.remove(p_editbox); | |
| 106 if (g_editboxes.empty()) { | |
| 107 UnhookHelper(g_hook); /*UnhookHelper(g_keyHook);*/ | |
| 108 } | |
| 109 } | |
| 110 | |
| 111 class CInPlaceComboBox : public CWindowImpl<CInPlaceComboBox, CComboBox> { | |
| 112 public: | |
| 113 BEGIN_MSG_MAP_EX(CInPlaceComboBox) | |
| 114 //MSG_WM_CREATE(OnCreate) | |
| 115 MSG_WM_DESTROY(OnDestroy) | |
| 116 MSG_WM_GETDLGCODE(OnGetDlgCode) | |
| 117 // MSG_WM_KILLFOCUS(OnKillFocus) | |
| 118 MSG_WM_KEYDOWN(OnKeyDown) | |
| 119 END_MSG_MAP() | |
| 120 | |
| 121 void OnCreation() { | |
| 122 on_editbox_creation(m_hWnd); | |
| 123 } | |
| 124 private: | |
| 125 void OnDestroy() { | |
| 126 m_selfDestruct = true; | |
| 127 on_editbox_destruction(m_hWnd); | |
| 128 SetMsgHandled(FALSE); | |
| 129 } | |
| 130 int OnCreate(LPCREATESTRUCT) { | |
| 131 OnCreation(); | |
| 132 SetMsgHandled(FALSE); | |
| 133 return 0; | |
| 134 } | |
| 135 UINT OnGetDlgCode(LPMSG lpMsg) { | |
| 136 if (lpMsg == NULL) { | |
| 137 SetMsgHandled(FALSE); return 0; | |
| 138 } else { | |
| 139 switch (lpMsg->message) { | |
| 140 case WM_KEYDOWN: | |
| 141 case WM_SYSKEYDOWN: | |
| 142 switch (lpMsg->wParam) { | |
| 143 case VK_TAB: | |
| 144 case VK_ESCAPE: | |
| 145 case VK_RETURN: | |
| 146 return DLGC_WANTMESSAGE; | |
| 147 default: | |
| 148 SetMsgHandled(FALSE); return 0; | |
| 149 } | |
| 150 default: | |
| 151 SetMsgHandled(FALSE); return 0; | |
| 152 | |
| 153 } | |
| 154 } | |
| 155 } | |
| 156 void OnKillFocus(CWindow wndFocus) { | |
| 157 if ( wndFocus != NULL ) ForwardCompletion(KEditLostFocus); | |
| 158 SetMsgHandled(FALSE); | |
| 159 } | |
| 160 | |
| 161 void OnKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags) { | |
| 162 (void)nRepCnt; | |
| 163 m_suppressChar = nFlags & 0xFF; | |
| 164 switch (nChar) { | |
| 165 case VK_TAB: | |
| 166 ForwardCompletion(IsKeyPressed(VK_SHIFT) ? KEditShiftTab : KEditTab); | |
| 167 return; | |
| 168 case VK_ESCAPE: | |
| 169 ForwardCompletion(KEditAborted); | |
| 170 return; | |
| 171 } | |
| 172 m_suppressChar = 0; | |
| 173 SetMsgHandled(FALSE); | |
| 174 } | |
| 175 | |
| 176 void ForwardCompletion(t_uint32 code) { | |
| 177 if (IsWindowEnabled()) { | |
| 178 CWindow owner = GetParent(); | |
| 179 owner.SendMessage(MSG_DISABLE_EDITING); | |
| 180 owner.PostMessage(MSG_COMPLETION, code, 0); | |
| 181 EnableWindow(FALSE); | |
| 182 } | |
| 183 } | |
| 184 | |
| 185 bool m_selfDestruct = false; | |
| 186 UINT m_suppressChar = 0; | |
| 187 }; | |
| 188 | |
| 189 class InPlaceComboContainer : public CWindowImpl<InPlaceComboContainer> { | |
| 190 public: | |
| 191 DECLARE_WND_CLASS_EX(_T("{18D85006-0CDB-49AB-A563-6A42014309A3}"), 0, -1); | |
| 192 | |
| 193 const pfc::string_list_const * m_initData; | |
| 194 const unsigned m_iDefault; | |
| 195 | |
| 196 HWND Create(CWindow parent) { | |
| 197 | |
| 198 RECT rect_cropped; | |
| 199 { | |
| 200 RECT client; | |
| 201 WIN32_OP_D(parent.GetClientRect(&client)); | |
| 202 IntersectRect(&rect_cropped, &client, &m_initRect); | |
| 203 } | |
| 204 const DWORD containerStyle = WS_BORDER | WS_CHILD; | |
| 205 AdjustWindowRect(&rect_cropped, containerStyle, FALSE); | |
| 206 | |
| 207 | |
| 208 | |
| 209 WIN32_OP(__super::Create(parent, rect_cropped, NULL, containerStyle) != NULL); | |
| 210 | |
| 211 try { | |
| 212 CRect rcClient; | |
| 213 WIN32_OP_D(GetClientRect(rcClient)); | |
| 214 | |
| 215 | |
| 216 DWORD style = WS_CHILD | WS_VISIBLE;//parent is invisible now | |
| 217 | |
| 218 style |= CBS_DROPDOWNLIST; | |
| 219 | |
| 220 | |
| 221 CComboBox edit; | |
| 222 | |
| 223 WIN32_OP(edit.Create(*this, rcClient, NULL, style, 0, ID_MYEDIT) != NULL); | |
| 224 edit.SetFont(parent.GetFont()); | |
| 225 | |
| 226 if ((m_flags & KFlagDark) != 0) DarkMode::DarkenComboLite(edit); | |
| 227 | |
| 228 m_edit.SubclassWindow(edit); | |
| 229 m_edit.OnCreation(); | |
| 230 | |
| 231 | |
| 232 #if 0 // doesn't quite work | |
| 233 if (m_flags & (KFlagAlignCenter | KFlagAlignRight)) { | |
| 234 COMBOBOXINFO info = {sizeof(info)}; | |
| 235 if (m_edit.GetComboBoxInfo(&info)) { | |
| 236 CEdit edit2 = info.hwndList; | |
| 237 if (edit2) { | |
| 238 if (m_flags & KFlagAlignCenter) edit2.ModifyStyle(0, ES_CENTER); | |
| 239 else if (m_flags & KFlagAlignRight) edit2.ModifyStyle(0, ES_RIGHT); | |
| 240 } | |
| 241 } | |
| 242 } | |
| 243 #endif | |
| 244 | |
| 245 | |
| 246 if (m_initData != nullptr) { | |
| 247 const size_t count = m_initData->get_count(); | |
| 248 for (size_t walk = 0; walk < count; ++walk) { | |
| 249 m_edit.AddString(pfc::stringcvt::string_os_from_utf8(m_initData->get_item(walk))); | |
| 250 } | |
| 251 if (m_iDefault < count) m_edit.SetCurSel(m_iDefault); | |
| 252 } | |
| 253 } catch (...) { | |
| 254 PostMessage(MSG_COMPLETION, InPlaceEdit::KEditAborted, 0); | |
| 255 return m_hWnd; | |
| 256 } | |
| 257 | |
| 258 ShowWindow(SW_SHOW); | |
| 259 m_edit.SetFocus(); | |
| 260 | |
| 261 m_initialized = true; | |
| 262 | |
| 263 m_edit.ShowDropDown(); | |
| 264 | |
| 265 PFC_ASSERT(m_hWnd != NULL); | |
| 266 | |
| 267 return m_hWnd; | |
| 268 } | |
| 269 | |
| 270 InPlaceComboContainer(const RECT & p_rect, unsigned p_flags, pfc::string_list_const * initData, unsigned iDefault, comboReply_t p_notify) : m_notify(p_notify), m_initData(initData), m_iDefault(iDefault), m_initRect(p_rect), m_flags(p_flags) { } | |
| 271 | |
| 272 enum { ID_MYEDIT = 666 }; | |
| 273 | |
| 274 BEGIN_MSG_MAP_EX(InPlaceEditContainer) | |
| 275 MESSAGE_HANDLER_EX(WM_CTLCOLOREDIT, MsgForwardToParent) | |
| 276 MESSAGE_HANDLER_EX(WM_CTLCOLORSTATIC, MsgForwardToParent) | |
| 277 MESSAGE_HANDLER_EX(WM_MOUSEWHEEL, MsgLostFocus) | |
| 278 MESSAGE_HANDLER_EX(WM_MOUSEHWHEEL, MsgLostFocus) | |
| 279 MESSAGE_HANDLER_SIMPLE(MSG_DISABLE_EDITING, OnMsgDisableEditing) | |
| 280 MESSAGE_HANDLER_EX(MSG_COMPLETION, OnMsgCompletion) | |
| 281 COMMAND_ID_HANDLER_EX(ID_MYEDIT, OnComboMsg) | |
| 282 MSG_WM_DESTROY(OnDestroy) | |
| 283 END_MSG_MAP() | |
| 284 | |
| 285 HWND GetEditBox() const { return m_edit; } | |
| 286 | |
| 287 private: | |
| 288 void OnDestroy() { m_selfDestruct = true; } | |
| 289 | |
| 290 LRESULT MsgForwardToParent(UINT msg, WPARAM wParam, LPARAM lParam) { | |
| 291 return GetParent().SendMessage(msg, wParam, lParam); | |
| 292 } | |
| 293 LRESULT MsgLostFocus(UINT, WPARAM, LPARAM) { | |
| 294 PostMessage(MSG_COMPLETION, InPlaceEdit::KEditLostFocus, 0); | |
| 295 return 0; | |
| 296 } | |
| 297 void OnMsgDisableEditing() { | |
| 298 ShowWindow(SW_HIDE); | |
| 299 GetParent().UpdateWindow(); | |
| 300 } | |
| 301 LRESULT OnMsgCompletion(UINT, WPARAM wParam, LPARAM) { | |
| 302 PFC_ASSERT(m_initialized); | |
| 303 if ((wParam & KEditMaskReason) != KEditLostFocus) { | |
| 304 GetParent().SetFocus(); | |
| 305 } | |
| 306 OnCompletion((unsigned) wParam ); | |
| 307 if (!m_selfDestruct) { | |
| 308 m_selfDestruct = true; | |
| 309 DestroyWindow(); | |
| 310 } | |
| 311 return 0; | |
| 312 } | |
| 313 void OnComboMsg(UINT code, int, CWindow) { | |
| 314 if (m_initialized && (code == CBN_SELENDOK || code == CBN_SELENDCANCEL) ) { | |
| 315 PostMessage(MSG_COMPLETION, InPlaceEdit::KEditLostFocus, 0); | |
| 316 } | |
| 317 } | |
| 318 | |
| 319 private: | |
| 320 | |
| 321 void OnCompletion(unsigned p_status) { | |
| 322 if (!m_completed) { | |
| 323 m_completed = true; | |
| 324 if (m_notify) m_notify( p_status, m_edit.GetCurSel() ); | |
| 325 } | |
| 326 } | |
| 327 | |
| 328 const comboReply_t m_notify; | |
| 329 bool m_completed = false; | |
| 330 bool m_initialized = false; | |
| 331 bool m_selfDestruct = false; | |
| 332 const CRect m_initRect; | |
| 333 CInPlaceComboBox m_edit; | |
| 334 const unsigned m_flags; | |
| 335 }; | |
| 336 | |
| 337 } | |
| 338 | |
| 339 static void fail(comboReply_t p_notify) { | |
| 340 p_notify(KEditAborted, UINT_MAX); | |
| 341 } | |
| 342 | |
| 343 HWND InPlaceEdit::StartCombo(HWND p_parentwnd, const RECT & p_rect, unsigned p_flags, pfc::string_list_const & data, unsigned iDefault, comboReply_t p_notify) { | |
| 344 try { | |
| 345 PFC_ASSERT((CWindow(p_parentwnd).GetWindowLong(GWL_STYLE) & WS_CLIPCHILDREN) != 0); | |
| 346 return (new CWindowCreateAndDelete<InPlaceComboContainer>(p_parentwnd, p_rect, p_flags, &data, iDefault, p_notify))->GetEditBox(); | |
| 347 } catch (...) { | |
| 348 fail(p_notify); | |
| 349 return NULL; | |
| 350 } | |
| 351 } |
