foobar2000 SDK  2015-08-03
inplace_edit.cpp
Go to the documentation of this file.
1 #include "stdafx.h"
2 
3 #ifndef WM_MOUSEHWHEEL
4 #define WM_MOUSEHWHEEL 0x20E
5 #endif
6 
7 
8 using namespace InPlaceEdit;
9 
10 
11 namespace {
12 
13 enum {
14  MSG_COMPLETION = WM_USER,
15  MSG_DISABLE_EDITING
16 };
17 
18 
19 static pfc::avltree_t<HWND> g_editboxes;
20 static HHOOK g_hook = NULL /*, g_keyHook = NULL*/;
21 
22 static void GAbortEditing(HWND edit, t_uint32 code) {
23  CWindow parent = ::GetParent(edit);
24  parent.SendMessage(MSG_DISABLE_EDITING);
25  parent.PostMessage(MSG_COMPLETION, code, 0);
26 }
27 
28 static void GAbortEditing(t_uint32 code) {
29  for(pfc::const_iterator<HWND> walk = g_editboxes.first(); walk.is_valid(); ++walk ) {
30  GAbortEditing(*walk, code);
31  }
32 }
33 
34 static bool IsSamePopup(CWindow wnd1, CWindow wnd2) {
35  return FindOwningPopup(wnd1) == FindOwningPopup(wnd2);
36 }
37 
38 static void MouseEventTest(HWND target, CPoint pt, bool isWheel) {
39  for(pfc::const_iterator<HWND> walk = g_editboxes.first(); walk.is_valid(); ++walk) {
40  CWindow edit ( *walk );
41  bool cancel = false;
42  if (target != edit && IsSamePopup(target, edit)) {
43  cancel = true;
44  } else if (isWheel) {
45  CWindow target2 = WindowFromPoint(pt);
46  if (target2 != edit && IsSamePopup(target2, edit)) {
47  cancel = true;
48  }
49  }
50 
51  if (cancel) GAbortEditing(edit, KEditLostFocus);
52  }
53 }
54 
55 static LRESULT CALLBACK GMouseProc(int nCode,WPARAM wParam,LPARAM lParam) {
56  if (nCode == HC_ACTION) {
57  const MOUSEHOOKSTRUCT * mhs = reinterpret_cast<const MOUSEHOOKSTRUCT *>(lParam);
58  switch(wParam) {
59  case WM_NCLBUTTONDOWN:
60  case WM_NCRBUTTONDOWN:
61  case WM_NCMBUTTONDOWN:
62  case WM_NCXBUTTONDOWN:
63  case WM_LBUTTONDOWN:
64  case WM_RBUTTONDOWN:
65  case WM_MBUTTONDOWN:
66  case WM_XBUTTONDOWN:
67  MouseEventTest(mhs->hwnd, mhs->pt, false);
68  break;
69  case WM_MOUSEWHEEL:
70  case WM_MOUSEHWHEEL:
71  MouseEventTest(mhs->hwnd, mhs->pt, true);
72  break;
73  }
74  }
75  return CallNextHookEx(g_hook,nCode,wParam,lParam);
76 }
77 #if 0
78 static LRESULT CALLBACK GKeyboardProc(int code, WPARAM wp, LPARAM lp) {
79  if (code == HC_ACTION && (lp & (1<<31)) == 0) {
80  switch(wp) {
81  case VK_RETURN:
82  if (!IsKeyPressed(VK_LCONTROL) && !IsKeyPressed(VK_RCONTROL)) {
83  GAbortEditing(KEditEnter);
84  }
85  break;
86  case VK_TAB:
87  GAbortEditing(IsKeyPressed(VK_SHIFT) ? KEditShiftTab : KEditTab);
88  break;
89  case VK_ESCAPE:
90  GAbortEditing(KEditAborted);
91  break;
92  }
93  }
94  return CallNextHookEx(g_keyHook,code,wp,lp);
95 }
96 #endif
97 
98 static void on_editbox_creation(HWND p_editbox) {
99  PFC_ASSERT( core_api::is_main_thread() );
100  g_editboxes.add(p_editbox);
101  if (g_hook == NULL) {
102  g_hook = SetWindowsHookEx(WH_MOUSE,GMouseProc,NULL,GetCurrentThreadId());
103  }
104  /*if (g_keyHook == NULL) {
105  g_keyHook = SetWindowsHookEx(WH_KEYBOARD, GKeyboardProc, NULL, GetCurrentThreadId());
106  }*/
107 }
108 static void UnhookHelper(HHOOK & hook) {
109  HHOOK v = pfc::replace_null_t(hook);
110  if (v != NULL) UnhookWindowsHookEx(v);
111 }
112 static void on_editbox_destruction(HWND p_editbox) {
113  PFC_ASSERT( core_api::is_main_thread() );
114  g_editboxes.remove_item(p_editbox);
115  if (g_editboxes.get_count() == 0) {
116  UnhookHelper(g_hook); /*UnhookHelper(g_keyHook);*/
117  }
118 }
119 
120 class CInPlaceEditBox : public CContainedWindowSimpleT<CEdit> {
121 public:
122  CInPlaceEditBox() : m_selfDestruct() {}
123  BEGIN_MSG_MAP_EX(CInPlaceEditBox)
124  //MSG_WM_CREATE(OnCreate)
125  MSG_WM_DESTROY(OnDestroy)
126  MSG_WM_GETDLGCODE(OnGetDlgCode)
127  MSG_WM_KILLFOCUS(OnKillFocus)
128  MSG_WM_CHAR(OnChar)
129  MSG_WM_KEYDOWN(OnChar)
130  END_MSG_MAP()
131  void OnCreation() {
132  m_typableScope.Set(m_hWnd);
133  on_editbox_creation(m_hWnd);
134  }
135 private:
136  void OnDestroy() {
137  m_selfDestruct = true;
138  m_typableScope.Set(NULL);
139  on_editbox_destruction(m_hWnd);
140  SetMsgHandled(FALSE);
141  }
142  int OnCreate(LPCREATESTRUCT lpCreateStruct) {
143  OnCreation();
144  SetMsgHandled(FALSE);
145  return 0;
146  }
147  UINT OnGetDlgCode(LPMSG lpMsg) {
148  return DLGC_WANTALLKEYS;
149  }
150  void OnKillFocus(CWindow wndFocus) {
151  ForwardCompletion(KEditLostFocus);
152  SetMsgHandled(FALSE);
153  }
154 
155  void OnChar(UINT nChar, UINT nRepCnt, UINT nFlags) {
156  switch(nChar) {
157  case VK_RETURN:
158  if (!IsKeyPressed(VK_LCONTROL) && !IsKeyPressed(VK_RCONTROL)) {
159  ForwardCompletion(KEditEnter);
160  return;
161  }
162  break;
163  case VK_TAB:
164  ForwardCompletion(IsKeyPressed(VK_SHIFT) ? KEditShiftTab : KEditTab);
165  return;
166  case VK_ESCAPE:
167  ForwardCompletion(KEditAborted);
168  return;
169  }
170  SetMsgHandled(FALSE);
171  }
172 
173  void ForwardCompletion(t_uint32 code) {
174  if (IsWindowEnabled()) {
175  CWindow owner = GetParent();
176  owner.SendMessage(MSG_DISABLE_EDITING);
177  owner.PostMessage(MSG_COMPLETION,code,0);
178  EnableWindow(FALSE);
179  }
180  }
181 
182  CTypableWindowScope m_typableScope;
183  bool m_selfDestruct;
184 };
185 
186 class InPlaceEditContainer : public CWindowImpl<InPlaceEditContainer> {
187 public:
188  DECLARE_WND_CLASS_EX(_T("{54340C80-248C-4b8e-8CD4-D624A8E9377B}"),0,-1);
189 
190 
191  HWND Create(CWindow parent) {
192 
193  RECT rect_cropped;
194  {
195  RECT client;
196  WIN32_OP_D(parent.GetClientRect(&client));
197  IntersectRect(&rect_cropped,&client,&m_initRect);
198  }
199  const DWORD containerStyle = WS_BORDER|WS_CHILD;
200  AdjustWindowRect(&rect_cropped,containerStyle,FALSE);
201 
202 
203 
204  WIN32_OP( __super::Create(parent,rect_cropped, NULL, containerStyle) != NULL );
205 
206  try {
207  CRect rcClient;
208  WIN32_OP_D(GetClientRect(rcClient));
209 
210 
211  DWORD style = WS_CHILD|WS_VISIBLE;//parent is invisible now
212  if (m_flags & KFlagMultiLine) style |= WS_VSCROLL|ES_MULTILINE;
213  else style |= ES_AUTOHSCROLL;
214  if (m_flags & KFlagReadOnly) style |= ES_READONLY;
215  if (m_flags & KFlagAlignCenter) style |= ES_CENTER;
216  else if (m_flags & KFlagAlignRight) style |= ES_RIGHT;
217  else style |= ES_LEFT;
218 
219 
220  CEdit edit;
221 
222  WIN32_OP( edit.Create(*this, rcClient, NULL, style, 0, ID_MYEDIT) != NULL );
223  edit.SetFont(parent.GetFont());
224 
225  if (m_ACData.is_valid()) InitializeSimpleAC(edit, m_ACData.get_ptr(), m_ACOpts);
226  m_edit.SubclassWindow(edit);
227  m_edit.OnCreation();
228 
229  uSetWindowText(m_edit,*m_content);
230  m_edit.SetSelAll();
231  } catch(...) {
232  PostMessage(MSG_COMPLETION,InPlaceEdit::KEditAborted,0);
233  return m_hWnd;
234  }
235 
236  ShowWindow(SW_SHOW);
237  m_edit.SetFocus();
238 
239  m_initialized = true;
240 
241  PFC_ASSERT( m_hWnd != NULL );
242 
243  return m_hWnd;
244  }
245 
246  InPlaceEditContainer(const RECT & p_rect,t_uint32 p_flags,pfc::rcptr_t<pfc::string_base> p_content,completion_notify_ptr p_notify, IUnknown * ACData, DWORD ACOpts)
247  : m_content(p_content), m_notify(p_notify), m_completed(false), m_initialized(false), m_changed(false), m_disable_editing(false), m_initRect(p_rect), m_flags(p_flags), m_selfDestruct(), m_ACData(ACData), m_ACOpts(ACOpts)
248  {
249  }
250 
251  enum {ID_MYEDIT = 666};
252 
253  BEGIN_MSG_MAP_EX(InPlaceEditContainer)
254  MESSAGE_HANDLER_EX(WM_CTLCOLOREDIT, MsgForwardToParent)
255  MESSAGE_HANDLER_EX(WM_CTLCOLORSTATIC, MsgForwardToParent)
256  MESSAGE_HANDLER_EX(WM_MOUSEWHEEL, MsgLostFocus)
257  MESSAGE_HANDLER_EX(WM_MOUSEHWHEEL, MsgLostFocus)
258  MESSAGE_HANDLER_SIMPLE(MSG_DISABLE_EDITING, OnMsgDisableEditing)
259  MESSAGE_HANDLER_EX(MSG_COMPLETION, OnMsgCompletion)
260  COMMAND_HANDLER_EX(ID_MYEDIT, EN_CHANGE, OnEditChange)
261  MSG_WM_DESTROY(OnDestroy)
262  END_MSG_MAP()
263 
264  HWND GetEditBox() const {return m_edit;}
265 
266 private:
267  void OnDestroy() {m_selfDestruct = true;}
268 
269  LRESULT MsgForwardToParent(UINT msg, WPARAM wParam, LPARAM lParam) {
270  return GetParent().SendMessage(msg, wParam, lParam);
271  }
272  LRESULT MsgLostFocus(UINT, WPARAM, LPARAM) {
273  PostMessage(MSG_COMPLETION,InPlaceEdit::KEditLostFocus,0);
274  return 0;
275  }
276  void OnMsgDisableEditing() {
277  ShowWindow(SW_HIDE);
278  GetParent().UpdateWindow();
279  m_disable_editing = true;
280  }
281  LRESULT OnMsgCompletion(UINT, WPARAM wParam, LPARAM lParam) {
282  PFC_ASSERT(m_initialized);
283  if ((wParam & KEditMaskReason) != KEditLostFocus) {
284  GetParent().SetFocus();
285  }
286  OnCompletion(wParam);
287  if (!m_selfDestruct) {
288  m_selfDestruct = true;
289  DestroyWindow();
290  }
291  return 0;
292  }
293  void OnEditChange(UINT, int, CWindow source) {
294  if (m_initialized && !m_disable_editing) {
295  uGetWindowText(source,*m_content);
296  m_changed = true;
297  }
298  }
299 
300 private:
301 
302  void OnCompletion(unsigned p_status) {
303  if (!m_completed) {
304  m_completed = true;
305  p_status &= KEditMaskReason;
306  unsigned code = p_status;
307  if (m_changed && p_status != KEditAborted) code |= KEditFlagContentChanged;
308  if (m_notify.is_valid()) m_notify->on_completion(code);
309  }
310  }
311 
312  const pfc::rcptr_t<pfc::string_base> m_content;
313  const completion_notify_ptr m_notify;
314  bool m_completed;
315  bool m_initialized, m_changed;
316  bool m_disable_editing;
317  bool m_selfDestruct;
318  const CRect m_initRect;
319  const t_uint32 m_flags;
320  CInPlaceEditBox m_edit;
321 
322  const pfc::com_ptr_t<IUnknown> m_ACData;
323  const DWORD m_ACOpts;
324 };
325 
326 }
327 
328 static void fail(completion_notify_ptr p_notify) {
330 }
331 
332 HWND InPlaceEdit::Start(HWND p_parentwnd,const RECT & p_rect,bool p_multiline,pfc::rcptr_t<pfc::string_base> p_content,completion_notify_ptr p_notify) {
333  return StartEx(p_parentwnd,p_rect,p_multiline ? KFlagMultiLine : 0, p_content,p_notify);
334 }
335 
336 void InPlaceEdit::Start_FromListView(HWND p_listview,unsigned p_item,unsigned p_subitem,unsigned p_linecount,pfc::rcptr_t<pfc::string_base> p_content,completion_notify_ptr p_notify) {
337  Start_FromListViewEx(p_listview,p_item,p_subitem,p_linecount,0,p_content,p_notify);
338 }
339 
340 bool InPlaceEdit::TableEditAdvance_ListView(HWND p_listview,unsigned p_column_base,unsigned & p_item,unsigned & p_column, unsigned p_item_count,unsigned p_column_count, unsigned p_whathappened) {
341  if (p_column >= p_column_count) return false;
342 
343 
344  pfc::array_t<t_size> orderRev;
345  {
347  const unsigned orderExCount = /*p_column_base + p_column_count*/ ListView_GetColumnCount(p_listview);
348  PFC_ASSERT( orderExCount >= p_column_base + p_column_count );
349  pfc::array_t<int> orderEx; orderEx.set_size(orderExCount);
350  if (!ListView_GetColumnOrderArray(p_listview,orderExCount,orderEx.get_ptr())) {
351  PFC_ASSERT(!"Should not get here - probably mis-calculated column count");
352  return false;
353  }
354  order.set_size(p_column_count);
355  for(unsigned walk = 0; walk < p_column_count; ++walk) order[walk] = orderEx[p_column_base + walk];
356 
357  orderRev.set_size(p_column_count); order_helper::g_fill(orderRev);
358  pfc::sort_get_permutation_t(order,pfc::compare_t<unsigned,unsigned>,p_column_count,orderRev.get_ptr());
359  }
360 
361  unsigned columnVisible = (unsigned)orderRev[p_column];
362 
363 
364  if (!TableEditAdvance(p_item,columnVisible,p_item_count,p_column_count,p_whathappened)) return false;
365 
366  p_column = (unsigned)order_helper::g_find_reverse(orderRev.get_ptr(),columnVisible);
367 
368  return true;
369 }
370 
371 bool InPlaceEdit::TableEditAdvance(unsigned & p_item,unsigned & p_column, unsigned p_item_count,unsigned p_column_count, unsigned p_whathappened) {
372  if (p_item >= p_item_count || p_column >= p_column_count) return false;
373  int delta = 0;
374 
375  switch(p_whathappened & KEditMaskReason) {
376  case KEditEnter:
377  delta = (int) p_column_count;
378  break;
379  case KEditTab:
380  delta = 1;
381  break;
382  case KEditShiftTab:
383  delta = -1;
384  break;
385  default:
386  return false;
387  }
388  while(delta > 0) {
389  p_column++;
390  if (p_column >= p_column_count) {
391  p_column = 0;
392  p_item++;
393  if (p_item >= p_item_count) return false;
394  }
395  delta--;
396  }
397  while(delta < 0) {
398  if (p_column == 0) {
399  if (p_item == 0) return false;
400  p_item--;
401  p_column = p_column_count;
402  }
403  p_column--;
404  delta++;
405  }
406  return true;
407 }
408 
409 HWND InPlaceEdit::StartEx(HWND p_parentwnd,const RECT & p_rect,unsigned p_flags,pfc::rcptr_t<pfc::string_base> p_content,completion_notify_ptr p_notify, IUnknown * ACData , DWORD ACOpts) {
410  try {
411  PFC_ASSERT( (CWindow(p_parentwnd).GetWindowLong(GWL_STYLE) & WS_CLIPCHILDREN) != 0 );
412  return (new CWindowAutoLifetime<InPlaceEditContainer>(p_parentwnd,p_rect,p_flags,p_content,p_notify, ACData, ACOpts))->GetEditBox();
413  } catch(...) {
414  fail(p_notify);
415  return NULL;
416  }
417 }
418 
419 void InPlaceEdit::Start_FromListViewEx(HWND p_listview,unsigned p_item,unsigned p_subitem,unsigned p_linecount,unsigned p_flags,pfc::rcptr_t<pfc::string_base> p_content,completion_notify_ptr p_notify) {
420  try {
421  ListView_EnsureVisible(p_listview,p_item,FALSE);
422  RECT itemrect;
423  WIN32_OP_D( ListView_GetSubItemRect(p_listview,p_item,p_subitem,LVIR_LABEL,&itemrect) );
424 
425  const bool multiline = p_linecount > 1;
426  if (multiline) {
427  itemrect.bottom = itemrect.top + (itemrect.bottom - itemrect.top) * p_linecount;
428  }
429 
430  StartEx(p_listview,itemrect,p_flags | (multiline ? KFlagMultiLine : 0),p_content,p_notify);
431  } catch(...) {
432  fail(p_notify);
433  }
434 }
t_storage & add(const t_param &p_item)
Definition: avltree.h:475
static void sort_get_permutation_t(const t_container &p_data, t_compare p_compare, t_size p_count, t_permutation const &p_permutation)
Definition: sort.h:176
t_size get_count() const
Definition: avltree.h:453
const t_item * get_ptr() const
Definition: array.h:213
static void g_signal_completion_async(service_ptr_t< completion_notify > p_notify, unsigned p_code)
Helper. Checks for null ptr and calls on_completion_async when the ptr is not null.
static void g_fill(t_int *p_order, const t_size p_count)
Definition: order_helper.h:38
void Start_FromListView(HWND p_listview, unsigned p_item, unsigned p_subitem, unsigned p_linecount, pfc::rcptr_t< pfc::string_base > p_content, completion_notify_ptr p_notify)
BOOL SHARED_EXPORT uGetWindowText(HWND wnd, pfc::string_base &out)
t_type replace_null_t(t_type &p_var)
Definition: primitives.h:688
bool IsKeyPressed(unsigned vk)
static void fail(completion_notify_ptr p_notify)
bool is_valid() const
Definition: iterators.h:24
int ListView_GetColumnCount(HWND listView)
const_iterator first() const
Definition: avltree.h:485
void set_size(t_size p_size)
Definition: array.h:104
HWND StartEx(HWND p_parentwnd, const RECT &p_rect, unsigned p_flags, pfc::rcptr_t< pfc::string_base > p_content, completion_notify_ptr p_notify, IUnknown *ACData=NULL, DWORD ACOpts=0)
BOOL SHARED_EXPORT uSetWindowText(HWND wnd, const char *p_text)
bool TableEditAdvance(unsigned &p_item, unsigned &p_column, unsigned p_item_count, unsigned p_column_count, unsigned p_whathappened)
static t_size g_find_reverse(const t_size *order, t_size val)
Insecure - may deadlock or crash on invalid permutation content. In theory faster than walking the pe...
Definition: other.cpp:90
void Start_FromListViewEx(HWND p_listview, unsigned p_item, unsigned p_subitem, unsigned p_linecount, unsigned p_flags, pfc::rcptr_t< pfc::string_base > p_content, completion_notify_ptr p_notify)
HRESULT InitializeSimpleAC(HWND edit, IUnknown *vals, DWORD opts)
bool is_main_thread()
Returns true if calling thread is main app thread, false otherwise.
HWND SHARED_EXPORT FindOwningPopup(HWND p_wnd)
bool TableEditAdvance_ListView(HWND p_listview, unsigned p_column_base, unsigned &p_item, unsigned &p_column, unsigned p_item_count, unsigned p_column_count, unsigned p_whathappened)
HWND Start(HWND p_parentwnd, const RECT &p_rect, bool p_multiline, pfc::rcptr_t< pfc::string_base > p_content, completion_notify_ptr p_notify)
uint32_t t_uint32
Definition: int_types.h:5
bool remove_item(t_param const &p_item)
Definition: avltree.h:447