|
1
|
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 }
|