#include "stdafx.h" #include "inplace_edit.h" #include "AutoComplete.h" #include "misc.h" #include "WTL-PP.h" #ifndef WM_MOUSEHWHEEL #define WM_MOUSEHWHEEL 0x20E #endif using namespace InPlaceEdit; namespace { enum { MSG_COMPLETION = WM_USER, MSG_DISABLE_EDITING }; static pfc::avltree_t g_editboxes; static HHOOK g_hook = NULL /*, g_keyHook = NULL*/; static void GAbortEditing(HWND edit, t_uint32 code) { CWindow parent = ::GetParent(edit); parent.SendMessage(MSG_DISABLE_EDITING); parent.PostMessage(MSG_COMPLETION, code, 0); } static void GAbortEditing(t_uint32 code) { for(auto walk = g_editboxes.cfirst(); walk.is_valid(); ++walk ) { GAbortEditing(*walk, code); } } static bool IsSamePopup(CWindow wnd1, CWindow wnd2) { return FindOwningPopup(wnd1) == FindOwningPopup(wnd2); } static void MouseEventTest(HWND target, CPoint pt, bool isWheel) { for(auto walk = g_editboxes.cfirst(); walk.is_valid(); ++walk) { CWindow edit ( *walk ); bool cancel = false; if (target != edit && IsSamePopup(target, edit)) { cancel = true; } else if (isWheel) { CWindow target2 = WindowFromPoint(pt); if (target2 != edit && IsSamePopup(target2, edit)) { cancel = true; } } if (cancel) GAbortEditing(edit, KEditLostFocus); } } static LRESULT CALLBACK GMouseProc(int nCode,WPARAM wParam,LPARAM lParam) { if (nCode == HC_ACTION) { const MOUSEHOOKSTRUCT * mhs = reinterpret_cast(lParam); switch(wParam) { case WM_NCLBUTTONDOWN: case WM_NCRBUTTONDOWN: case WM_NCMBUTTONDOWN: case WM_NCXBUTTONDOWN: case WM_LBUTTONDOWN: case WM_RBUTTONDOWN: case WM_MBUTTONDOWN: case WM_XBUTTONDOWN: MouseEventTest(mhs->hwnd, mhs->pt, false); break; case WM_MOUSEWHEEL: case WM_MOUSEHWHEEL: MouseEventTest(mhs->hwnd, mhs->pt, true); break; } } return CallNextHookEx(g_hook,nCode,wParam,lParam); } #if 0 static LRESULT CALLBACK GKeyboardProc(int code, WPARAM wp, LPARAM lp) { if (code == HC_ACTION && (lp & (1<<31)) == 0) { switch(wp) { case VK_RETURN: if (!IsKeyPressed(VK_LCONTROL) && !IsKeyPressed(VK_RCONTROL)) { GAbortEditing(KEditEnter); } break; case VK_TAB: GAbortEditing(IsKeyPressed(VK_SHIFT) ? KEditShiftTab : KEditTab); break; case VK_ESCAPE: GAbortEditing(KEditAborted); break; } } return CallNextHookEx(g_keyHook,code,wp,lp); } #endif static void on_editbox_creation(HWND p_editbox) { PFC_ASSERT( core_api::is_main_thread() ); g_editboxes.add(p_editbox); if (g_hook == NULL) { g_hook = SetWindowsHookEx(WH_MOUSE,GMouseProc,NULL,GetCurrentThreadId()); } /*if (g_keyHook == NULL) { g_keyHook = SetWindowsHookEx(WH_KEYBOARD, GKeyboardProc, NULL, GetCurrentThreadId()); }*/ } static void UnhookHelper(HHOOK & hook) { HHOOK v = pfc::replace_null_t(hook); if (v != NULL) UnhookWindowsHookEx(v); } static void on_editbox_destruction(HWND p_editbox) { PFC_ASSERT( core_api::is_main_thread() ); g_editboxes.remove_item(p_editbox); if (g_editboxes.get_count() == 0) { UnhookHelper(g_hook); /*UnhookHelper(g_keyHook);*/ } } class CInPlaceEditBox : public CContainedWindowSimpleT { public: CInPlaceEditBox() : m_selfDestruct(), m_suppressChar() {} BEGIN_MSG_MAP_EX(CInPlaceEditBox) //MSG_WM_CREATE(OnCreate) MSG_WM_DESTROY(OnDestroy) MSG_WM_GETDLGCODE(OnGetDlgCode) MSG_WM_KILLFOCUS(OnKillFocus) MSG_WM_CHAR(OnChar) MSG_WM_KEYDOWN(OnKeyDown) END_MSG_MAP() void OnCreation() { m_typableScope.Set(m_hWnd); on_editbox_creation(m_hWnd); } private: void OnDestroy() { m_selfDestruct = true; m_typableScope.Set(NULL); on_editbox_destruction(m_hWnd); SetMsgHandled(FALSE); } int OnCreate(LPCREATESTRUCT lpCreateStruct) { OnCreation(); SetMsgHandled(FALSE); return 0; } UINT OnGetDlgCode(LPMSG lpMsg) { if (lpMsg == NULL) { return DLGC_WANTALLKEYS; } else { switch(lpMsg->message) { case WM_KEYDOWN: case WM_SYSKEYDOWN: switch(lpMsg->wParam) { case VK_TAB: case VK_ESCAPE: case VK_RETURN: return DLGC_WANTALLKEYS; default: SetMsgHandled(FALSE); return 0; } default: SetMsgHandled(FALSE); return 0; } } } void OnKillFocus(CWindow wndFocus) { ForwardCompletion(KEditLostFocus); SetMsgHandled(FALSE); } void OnChar(UINT nChar, UINT nRepCnt, UINT nFlags) { if (m_suppressChar != 0) { UINT code = nFlags & 0xFF; if (code == m_suppressChar) return; } SetMsgHandled(FALSE); } void OnKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags) { m_suppressChar = nFlags & 0xFF; switch(nChar) { case VK_BACK: if (GetHotkeyModifierFlags() == MOD_CONTROL) { CEditPPHooks::DeleteLastWord( * this ); return; } break; case 'A': if (GetHotkeyModifierFlags() == MOD_CONTROL) { this->SetSelAll(); return; } break; case VK_RETURN: if (!IsKeyPressed(VK_LCONTROL) && !IsKeyPressed(VK_RCONTROL)) { ForwardCompletion(KEditEnter); return; } break; case VK_TAB: ForwardCompletion(IsKeyPressed(VK_SHIFT) ? KEditShiftTab : KEditTab); return; case VK_ESCAPE: ForwardCompletion(KEditAborted); return; } m_suppressChar = 0; SetMsgHandled(FALSE); } void ForwardCompletion(t_uint32 code) { if (IsWindowEnabled()) { CWindow owner = GetParent(); owner.SendMessage(MSG_DISABLE_EDITING); owner.PostMessage(MSG_COMPLETION,code,0); EnableWindow(FALSE); } } CTypableWindowScope m_typableScope; bool m_selfDestruct; UINT m_suppressChar; }; class InPlaceEditContainer : public CWindowImpl { public: DECLARE_WND_CLASS_EX(_T("{54340C80-248C-4b8e-8CD4-D624A8E9377B}"),0,-1); HWND Create(CWindow parent) { RECT rect_cropped; { RECT client; WIN32_OP_D(parent.GetClientRect(&client)); IntersectRect(&rect_cropped,&client,&m_initRect); } const DWORD containerStyle = WS_BORDER|WS_CHILD; AdjustWindowRect(&rect_cropped,containerStyle,FALSE); WIN32_OP( __super::Create(parent,rect_cropped, NULL, containerStyle) != NULL ); try { CRect rcClient; WIN32_OP_D(GetClientRect(rcClient)); DWORD style = WS_CHILD|WS_VISIBLE;//parent is invisible now if (m_flags & KFlagMultiLine) style |= WS_VSCROLL|ES_MULTILINE; else style |= ES_AUTOHSCROLL; if (m_flags & KFlagReadOnly) style |= ES_READONLY; if (m_flags & KFlagAlignCenter) style |= ES_CENTER; else if (m_flags & KFlagAlignRight) style |= ES_RIGHT; else style |= ES_LEFT; CEdit edit; WIN32_OP( edit.Create(*this, rcClient, NULL, style, 0, ID_MYEDIT) != NULL ); edit.SetFont(parent.GetFont()); if (m_ACData.is_valid()) InitializeSimpleAC(edit, m_ACData.get_ptr(), m_ACOpts); m_edit.SubclassWindow(edit); m_edit.OnCreation(); uSetWindowText(m_edit,*m_content); m_edit.SetSelAll(); } catch(...) { PostMessage(MSG_COMPLETION,InPlaceEdit::KEditAborted,0); return m_hWnd; } ShowWindow(SW_SHOW); m_edit.SetFocus(); m_initialized = true; PFC_ASSERT( m_hWnd != NULL ); return m_hWnd; } InPlaceEditContainer(const RECT & p_rect,t_uint32 p_flags,pfc::rcptr_t p_content,completion_notify_ptr p_notify, IUnknown * ACData, DWORD ACOpts) : 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) { } enum {ID_MYEDIT = 666}; BEGIN_MSG_MAP_EX(InPlaceEditContainer) MESSAGE_HANDLER_EX(WM_CTLCOLOREDIT, MsgForwardToParent) MESSAGE_HANDLER_EX(WM_CTLCOLORSTATIC, MsgForwardToParent) MESSAGE_HANDLER_EX(WM_MOUSEWHEEL, MsgLostFocus) MESSAGE_HANDLER_EX(WM_MOUSEHWHEEL, MsgLostFocus) MESSAGE_HANDLER_SIMPLE(MSG_DISABLE_EDITING, OnMsgDisableEditing) MESSAGE_HANDLER_EX(MSG_COMPLETION, OnMsgCompletion) COMMAND_HANDLER_EX(ID_MYEDIT, EN_CHANGE, OnEditChange) MSG_WM_DESTROY(OnDestroy) END_MSG_MAP() HWND GetEditBox() const {return m_edit;} private: void OnDestroy() {m_selfDestruct = true;} LRESULT MsgForwardToParent(UINT msg, WPARAM wParam, LPARAM lParam) { return GetParent().SendMessage(msg, wParam, lParam); } LRESULT MsgLostFocus(UINT, WPARAM, LPARAM) { PostMessage(MSG_COMPLETION,InPlaceEdit::KEditLostFocus,0); return 0; } void OnMsgDisableEditing() { ShowWindow(SW_HIDE); GetParent().UpdateWindow(); m_disable_editing = true; } LRESULT OnMsgCompletion(UINT, WPARAM wParam, LPARAM lParam) { PFC_ASSERT(m_initialized); if ((wParam & KEditMaskReason) != KEditLostFocus) { GetParent().SetFocus(); } OnCompletion(wParam); if (!m_selfDestruct) { m_selfDestruct = true; DestroyWindow(); } return 0; } void OnEditChange(UINT, int, CWindow source) { if (m_initialized && !m_disable_editing) { uGetWindowText(source,*m_content); m_changed = true; } } private: void OnCompletion(unsigned p_status) { if (!m_completed) { m_completed = true; p_status &= KEditMaskReason; unsigned code = p_status; if (m_changed && p_status != KEditAborted) code |= KEditFlagContentChanged; if (m_notify.is_valid()) m_notify->on_completion(code); } } const pfc::rcptr_t m_content; const completion_notify_ptr m_notify; bool m_completed; bool m_initialized, m_changed; bool m_disable_editing; bool m_selfDestruct; const CRect m_initRect; const t_uint32 m_flags; CInPlaceEditBox m_edit; const pfc::com_ptr_t m_ACData; const DWORD m_ACOpts; }; } static void fail(completion_notify_ptr p_notify) { completion_notify::g_signal_completion_async(p_notify,KEditAborted); } HWND InPlaceEdit::Start(HWND p_parentwnd,const RECT & p_rect,bool p_multiline,pfc::rcptr_t p_content,completion_notify_ptr p_notify) { return StartEx(p_parentwnd,p_rect,p_multiline ? KFlagMultiLine : 0, p_content,p_notify); } void InPlaceEdit::Start_FromListView(HWND p_listview,unsigned p_item,unsigned p_subitem,unsigned p_linecount,pfc::rcptr_t p_content,completion_notify_ptr p_notify) { Start_FromListViewEx(p_listview,p_item,p_subitem,p_linecount,0,p_content,p_notify); } 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) { if (p_column >= p_column_count) return false; pfc::array_t orderRev; { pfc::array_t order; const unsigned orderExCount = /*p_column_base + p_column_count*/ ListView_GetColumnCount(p_listview); PFC_ASSERT( orderExCount >= p_column_base + p_column_count ); pfc::array_t orderEx; orderEx.set_size(orderExCount); if (!ListView_GetColumnOrderArray(p_listview,orderExCount,orderEx.get_ptr())) { PFC_ASSERT(!"Should not get here - probably mis-calculated column count"); return false; } order.set_size(p_column_count); for(unsigned walk = 0; walk < p_column_count; ++walk) order[walk] = orderEx[p_column_base + walk]; orderRev.set_size(p_column_count); order_helper::g_fill(orderRev); pfc::sort_get_permutation_t(order,pfc::compare_t,p_column_count,orderRev.get_ptr()); } unsigned columnVisible = (unsigned)orderRev[p_column]; if (!TableEditAdvance(p_item,columnVisible,p_item_count,p_column_count,p_whathappened)) return false; p_column = (unsigned)order_helper::g_find_reverse(orderRev.get_ptr(),columnVisible); return true; } bool InPlaceEdit::TableEditAdvance(unsigned & p_item,unsigned & p_column, unsigned p_item_count,unsigned p_column_count, unsigned p_whathappened) { if (p_item >= p_item_count || p_column >= p_column_count) return false; int delta = 0; switch(p_whathappened & KEditMaskReason) { case KEditEnter: delta = (int) p_column_count; break; case KEditTab: delta = 1; break; case KEditShiftTab: delta = -1; break; default: return false; } while(delta > 0) { p_column++; if (p_column >= p_column_count) { p_column = 0; p_item++; if (p_item >= p_item_count) return false; } delta--; } while(delta < 0) { if (p_column == 0) { if (p_item == 0) return false; p_item--; p_column = p_column_count; } p_column--; delta++; } return true; } HWND InPlaceEdit::StartEx(HWND p_parentwnd,const RECT & p_rect,unsigned p_flags,pfc::rcptr_t p_content,completion_notify_ptr p_notify, IUnknown * ACData , DWORD ACOpts) { try { PFC_ASSERT( (CWindow(p_parentwnd).GetWindowLong(GWL_STYLE) & WS_CLIPCHILDREN) != 0 ); return (new CWindowAutoLifetime(p_parentwnd,p_rect,p_flags,p_content,p_notify, ACData, ACOpts))->GetEditBox(); } catch(...) { fail(p_notify); return NULL; } } void InPlaceEdit::Start_FromListViewEx(HWND p_listview,unsigned p_item,unsigned p_subitem,unsigned p_linecount,unsigned p_flags,pfc::rcptr_t p_content,completion_notify_ptr p_notify) { try { ListView_EnsureVisible(p_listview,p_item,FALSE); RECT itemrect; WIN32_OP_D( ListView_GetSubItemRect(p_listview,p_item,p_subitem,LVIR_LABEL,&itemrect) ); const bool multiline = p_linecount > 1; if (multiline) { itemrect.bottom = itemrect.top + (itemrect.bottom - itemrect.top) * p_linecount; } StartEx(p_listview,itemrect,p_flags | (multiline ? KFlagMultiLine : 0),p_content,p_notify); } catch(...) { fail(p_notify); } }