1 // TortoiseGit - a Windows shell extension for easy version control
3 // Copyright (C) 2008-2013 - TortoiseGit
5 // This program is free software; you can redistribute it and/or
6 // modify it under the terms of the GNU General Public License
7 // as published by the Free Software Foundation; either version 2
8 // of the License, or (at your option) any later version.
10 // This program is distributed in the hope that it will be useful,
11 // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 // GNU General Public License for more details.
15 // You should have received a copy of the GNU General Public License
16 // along with this program; if not, write to the Free Software Foundation,
17 // 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
19 // ProgressDlg.cpp : implementation file
23 #include "TortoiseProc.h"
24 #include "ProgressDlg.h"
27 #include "UnicodeUtils.h"
29 #include "LoglistCommonResource.h"
32 #include "SmartHandle.h"
33 #include "../TGitCache/CacheInterface.h"
34 #include "LoglistUtils.h"
36 // CProgressDlg dialog
38 IMPLEMENT_DYNAMIC(CProgressDlg
, CResizableStandAloneDialog
)
40 CProgressDlg::CProgressDlg(CWnd
* pParent
/*=NULL*/)
41 : CResizableStandAloneDialog(CProgressDlg::IDD
, pParent
), m_bShowCommand(true), m_bAutoCloseOnSuccess(false), m_bAbort(false), m_bDone(false), m_startTick(GetTickCount())
44 m_PostCmdCallback
= NULL
;
46 m_bAltAbortPress
=false;
51 CProgressDlg::~CProgressDlg()
59 void CProgressDlg::DoDataExchange(CDataExchange
* pDX
)
61 CDialog::DoDataExchange(pDX
);
62 DDX_Control(pDX
, IDC_CURRENT
, this->m_CurrentWork
);
63 DDX_Control(pDX
, IDC_TITLE_ANIMATE
, this->m_Animate
);
64 DDX_Control(pDX
, IDC_RUN_PROGRESS
, this->m_Progress
);
65 DDX_Control(pDX
, IDC_LOG
, this->m_Log
);
66 DDX_Control(pDX
, IDC_PROGRESS_BUTTON1
, this->m_ctrlPostCmd
);
69 BEGIN_MESSAGE_MAP(CProgressDlg
, CResizableStandAloneDialog
)
71 ON_MESSAGE(MSG_PROGRESSDLG_UPDATE_UI
, OnProgressUpdateUI
)
72 ON_BN_CLICKED(IDOK
, &CProgressDlg::OnBnClickedOk
)
73 ON_BN_CLICKED(IDC_PROGRESS_BUTTON1
,&CProgressDlg::OnBnClickedButton1
)
74 ON_REGISTERED_MESSAGE(WM_TASKBARBTNCREATED
, OnTaskbarBtnCreated
)
77 BOOL
CProgressDlg::OnInitDialog()
79 CResizableStandAloneDialog::OnInitDialog();
81 // Let the TaskbarButtonCreated message through the UIPI filter. If we don't
82 // do this, Explorer would be unable to send that message to our window if we
83 // were running elevated. It's OK to make the call all the time, since if we're
84 // not elevated, this is a no-op.
85 CHANGEFILTERSTRUCT cfs
= { sizeof(CHANGEFILTERSTRUCT
) };
86 typedef BOOL STDAPICALLTYPE
ChangeWindowMessageFilterExDFN(HWND hWnd
, UINT message
, DWORD action
, PCHANGEFILTERSTRUCT pChangeFilterStruct
);
87 CAutoLibrary hUser
= AtlLoadSystemLibraryUsingFullPath(_T("user32.dll"));
90 ChangeWindowMessageFilterExDFN
*pfnChangeWindowMessageFilterEx
= (ChangeWindowMessageFilterExDFN
*)GetProcAddress(hUser
, "ChangeWindowMessageFilterEx");
91 if (pfnChangeWindowMessageFilterEx
)
93 pfnChangeWindowMessageFilterEx(m_hWnd
, WM_TASKBARBTNCREATED
, MSGFLT_ALLOW
, &cfs
);
96 m_pTaskbarList
.Release();
97 if (FAILED(m_pTaskbarList
.CoCreateInstance(CLSID_TaskbarList
)))
98 m_pTaskbarList
= nullptr;
100 AddAnchor(IDC_TITLE_ANIMATE
, TOP_LEFT
, TOP_RIGHT
);
101 AddAnchor(IDC_RUN_PROGRESS
, TOP_LEFT
,TOP_RIGHT
);
102 AddAnchor(IDC_LOG
, TOP_LEFT
,BOTTOM_RIGHT
);
104 AddAnchor(IDOK
,BOTTOM_RIGHT
);
105 AddAnchor(IDCANCEL
,BOTTOM_RIGHT
);
106 AddAnchor(IDC_PROGRESS_BUTTON1
,BOTTOM_LEFT
);
107 AddAnchor(IDC_CURRENT
,TOP_LEFT
);
109 this->GetDlgItem(IDC_PROGRESS_BUTTON1
)->ShowWindow(SW_HIDE
);
110 m_Animate
.Open(IDR_DOWNLOAD
);
113 CAppUtils::CreateFontForLogs(m_logFont
);
114 //GetDlgItem(IDC_CMD_LOG)->SetFont(&m_logFont);
115 m_Log
.SetFont(&m_logFont
);
118 if ( !m_PreText
.IsEmpty() )
120 InitialText
= m_PreText
+ _T("\r\n");
123 if (m_bShowCommand
&& (!m_GitCmd
.IsEmpty() ))
125 InitialText
+= m_GitCmd
+_T("\r\n\r\n");
128 m_Log
.SetWindowTextW(InitialText
);
129 m_CurrentWork
.SetWindowTextW(_T(""));
131 EnableSaveRestore(_T("ProgressDlg"));
133 m_pThread
= AfxBeginThread(ProgressThreadEntry
, this, THREAD_PRIORITY_NORMAL
,0,CREATE_SUSPENDED
);
136 // ReportError(CString(MAKEINTRESOURCE(IDS_ERR_THREADSTARTFAILED)));
140 m_pThread
->m_bAutoDelete
= FALSE
;
141 m_pThread
->ResumeThread();
144 if(!m_Title
.IsEmpty())
145 this->SetWindowText(m_Title
);
147 CString sWindowTitle
;
148 GetWindowText(sWindowTitle
);
149 CAppUtils::SetWindowTitle(m_hWnd
, g_Git
.m_CurrentDir
, sWindowTitle
);
151 // Make sure this dialog is shown in foreground (see issue #1536)
152 SetForegroundWindow();
157 UINT
CProgressDlg::ProgressThreadEntry(LPVOID pVoid
)
159 return ((CProgressDlg
*)pVoid
)->ProgressThread();
162 //static function, Share with SyncDialog
163 UINT
CProgressDlg::RunCmdList(CWnd
*pWnd
,std::vector
<CString
> &cmdlist
,bool bShowCommand
,CString
*pfilename
,bool *bAbort
,CGitByteArray
*pdata
)
167 PROCESS_INFORMATION pi
;
170 memset(&pi
,0,sizeof(PROCESS_INFORMATION
));
172 CBlockCacheForPath
cacheBlock(g_Git
.m_CurrentDir
);
174 pWnd
->PostMessage(MSG_PROGRESSDLG_UPDATE_UI
,MSG_PROGRESSDLG_START
,0);
179 for(int i
=0;i
<cmdlist
.size();i
++)
181 if(cmdlist
[i
].IsEmpty())
186 CStringA str
= CUnicodeUtils::GetMulti(cmdlist
[i
].Trim() + _T("\n\n"), CP_UTF8
);
187 for(int j
=0;j
<str
.GetLength();j
++)
191 pdata
->m_critSec
.Lock();
192 pdata
->push_back(str
[j
]);
193 pdata
->m_critSec
.Unlock();
196 pWnd
->PostMessage(MSG_PROGRESSDLG_UPDATE_UI
,MSG_PROGRESSDLG_RUN
,str
[j
]);
199 pWnd
->PostMessage(MSG_PROGRESSDLG_UPDATE_UI
,MSG_PROGRESSDLG_RUN
,0);
202 g_Git
.RunAsync(cmdlist
[i
].Trim(),&pi
, &hRead
, NULL
, pfilename
);
207 while(ReadFile(hRead
,&byte
,1,&readnumber
,NULL
))
214 pdata
->m_critSec
.Lock();
215 pdata
->push_back( byte
);
216 pdata
->m_critSec
.Unlock();
218 if(byte
== '\r' || byte
== '\n')
219 pWnd
->PostMessage(MSG_PROGRESSDLG_UPDATE_UI
,MSG_PROGRESSDLG_RUN
,0);
222 pWnd
->PostMessage(MSG_PROGRESSDLG_UPDATE_UI
,MSG_PROGRESSDLG_RUN
,byte
);
225 CloseHandle(pi
.hThread
);
227 WaitForSingleObject(pi
.hProcess
, INFINITE
);
230 if(!GetExitCodeProcess(pi
.hProcess
,&status
) || *bAbort
)
232 CloseHandle(pi
.hProcess
);
236 pWnd
->PostMessage(MSG_PROGRESSDLG_UPDATE_UI
, MSG_PROGRESSDLG_FAILED
, status
);
237 return TGIT_GIT_ERROR_GET_EXIT_CODE
;
242 CloseHandle(pi
.hProcess
);
246 pWnd
->PostMessage(MSG_PROGRESSDLG_UPDATE_UI
, MSG_PROGRESSDLG_END
, ret
);
252 UINT
CProgressDlg::ProgressThread()
255 m_GitCmdList
.push_back(m_GitCmd
);
259 if(m_LogFile
.IsEmpty())
262 pfilename
=&m_LogFile
;
264 m_startTick
= GetTickCount();
265 m_GitStatus
= RunCmdList(this,m_GitCmdList
,m_bShowCommand
,pfilename
,&m_bAbort
,&this->m_Databuf
);;
269 LRESULT
CProgressDlg::OnProgressUpdateUI(WPARAM wParam
,LPARAM lParam
)
271 if(wParam
== MSG_PROGRESSDLG_START
)
274 m_Animate
.Play(0, INT_MAX
, INT_MAX
);
275 this->DialogEnableWindow(IDOK
,FALSE
);
278 m_pTaskbarList
->SetProgressState(m_hWnd
, TBPF_NORMAL
);
279 m_pTaskbarList
->SetProgressValue(m_hWnd
, 0, 100);
282 if(wParam
== MSG_PROGRESSDLG_END
|| wParam
== MSG_PROGRESSDLG_FAILED
)
284 DWORD tickSpent
= GetTickCount() - m_startTick
;
285 CString strEndTime
= CLoglistUtils::FormatDateAndTime(CTime::GetCurrentTime(), DATE_SHORTDATE
, true, false);
289 m_Databuf
.m_critSec
.Lock();
290 m_Databuf
.push_back(0);
291 m_Databuf
.m_critSec
.Unlock();
293 m_Databuf
.m_critSec
.Lock();
294 m_Log
.SetWindowText(Convert2UnionCode((char*)&m_Databuf
[0]));
295 m_Databuf
.m_critSec
.Unlock();
296 m_Log
.LineScroll(m_Log
.GetLineCount() - m_Log
.GetFirstVisibleLine() - 4);
299 m_Databuf
.m_critSec
.Lock();
300 this->m_Databuf
.clear();
301 m_Databuf
.m_critSec
.Unlock();
305 m_Progress
.SetPos(100);
306 this->DialogEnableWindow(IDOK
,TRUE
);
308 m_GitStatus
= (DWORD
)lParam
;
310 // detect crashes of perl when performing git svn actions
311 if (m_GitStatus
== 0 && m_GitCmd
.Find(_T(" svn ")) > 1)
314 m_Log
.GetWindowText(log
);
315 if (log
.GetLength() > 18 && log
.Mid(log
.GetLength() - 18) == _T("perl.exe.stackdump"))
316 m_GitStatus
= (DWORD
)-1;
319 if(this->m_GitStatus
)
323 m_pTaskbarList
->SetProgressState(m_hWnd
, TBPF_ERROR
);
324 m_pTaskbarList
->SetProgressValue(m_hWnd
, 100, 100);
327 log
.Format(IDS_PROC_PROGRESS_GITUNCLEANEXIT
, m_GitStatus
);
329 err
.Format(_T("\r\n\r\n%s (%d ms @ %s)\r\n"), log
, tickSpent
, strEndTime
);
330 InsertColorText(this->m_Log
, err
, RGB(255,0,0));
334 m_pTaskbarList
->SetProgressState(m_hWnd
, TBPF_NOPROGRESS
);
336 temp
.LoadString(IDS_SUCCESS
);
338 log
.Format(_T("\r\n%s (%d ms @ %s)\r\n"), temp
, tickSpent
, strEndTime
);
339 InsertColorText(this->m_Log
, log
, RGB(0,0,255));
340 this->DialogEnableWindow(IDCANCEL
,FALSE
);
343 if (wParam
== MSG_PROGRESSDLG_END
)
345 if (m_PostCmdCallback
) // new handling method using callback
347 m_PostCmdCallback(this, m_caller
, m_GitStatus
);
349 if (m_PostCmdList
.GetCount() > 0)
351 m_ctrlPostCmd
.AddEntries(m_PostCmdList
);
352 GetDlgItem(IDC_PROGRESS_BUTTON1
)->ShowWindow(SW_SHOW
);
355 else if (m_GitStatus
== 0) // default old behaviour on success
357 if (m_PostCmdList
.GetCount() > 0)
359 m_ctrlPostCmd
.AddEntries(m_PostCmdList
);
360 GetDlgItem(IDC_PROGRESS_BUTTON1
)->ShowWindow(SW_SHOW
);
363 else // simple method to show buttons on failed
365 if (m_PostFailCmdList
.GetCount() > 0)
367 m_ctrlPostCmd
.AddEntries(m_PostFailCmdList
);
368 GetDlgItem(IDC_PROGRESS_BUTTON1
)->ShowWindow(SW_SHOW
);
373 if(wParam
== MSG_PROGRESSDLG_END
&& m_GitStatus
== 0)
375 if(m_bAutoCloseOnSuccess
)
377 m_Log
.GetWindowText(this->m_LogText
);
387 m_Databuf
.m_critSec
.Lock();
388 for(int i
=this->m_BufStart
;i
<this->m_Databuf
.size();i
++)
390 char c
= this->m_Databuf
[m_BufStart
];
392 m_Databuf
.m_critSec
.Unlock();
395 m_Databuf
.m_critSec
.Lock();
400 m_Databuf
.erase(m_Databuf
.begin(), m_Databuf
.begin()+m_BufStart
);
403 m_Databuf
.m_critSec
.Unlock();
407 ParserCmdOutput((char)lParam
);
412 //static function, Share with SyncDialog
413 int CProgressDlg::FindPercentage(CString
&log
)
415 int s1
=log
.Find(_T('%'));
420 for(int i
=s1
-1;i
>=0;i
--)
422 if(log
[i
]>=_T('0') && log
[i
]<=_T('9'))
427 return _ttol(log
.Mid(s2
,s1
-s2
));
430 void CProgressDlg::ParserCmdOutput(char ch
)
432 ParserCmdOutput(this->m_Log
,this->m_Progress
,this->m_hWnd
,this->m_pTaskbarList
,this->m_LogTextA
,ch
,&this->m_CurrentWork
);
434 void CProgressDlg::ClearESC(CString
&str
)
436 // see http://ascii-table.com/ansi-escape-sequences.php and http://tldp.org/HOWTO/Bash-Prompt-HOWTO/c327.html
437 str
.Replace(_T("\033[K"), _T("")); // erase until end of line; no need to care for this, because we always clear the whole line
442 int escapePosition
= str
.Find(_T('\033'));
443 if (escapePosition
>= 0 && str
.GetLength() >= escapePosition
+ 3)
445 if (str
.Mid(escapePosition
, 2) == _T("\033["))
447 int colorEnd
= str
.Find(_T('m'), escapePosition
+ 2);
451 for (int i
= escapePosition
+ 2; i
< colorEnd
; i
++)
453 if (str
[i
] != _T(';') && (str
[i
] < _T('0') && str
[i
] > _T('9')))
461 if (escapePosition
> 0)
462 str
= str
.Left(escapePosition
) + str
.Mid(colorEnd
+ 1);
464 str
= str
.Mid(colorEnd
);
473 void CProgressDlg::ParserCmdOutput(CRichEditCtrl
&log
,CProgressCtrl
&progressctrl
,HWND m_hWnd
,CComPtr
<ITaskbarList3
> m_pTaskbarList
,CStringA
&oneline
, char ch
, CWnd
*CurrentWork
)
475 //TRACE(_T("%c"),ch);
476 if( ch
== ('\r') || ch
== ('\n'))
480 // TRACE(_T("End Char %s \r\n"),ch==_T('\r')?_T("lf"):_T(""));
481 // TRACE(_T("End Char %s \r\n"),ch==_T('\n')?_T("cr"):_T(""));
483 int lines
= log
.GetLineCount();
484 g_Git
.StringAppend(&str
, (BYTE
*)oneline
.GetBuffer(), CP_UTF8
);
486 // TRACE(_T("%s"), str);
492 int start
=log
.LineIndex(lines
-1);
493 log
.SetSel(start
, log
.GetTextLength());
498 int length
= log
.GetWindowTextLength();
499 log
.SetSel(length
, length
);
501 log
.ReplaceSel(_T("\r\n") + str
);
506 if (lines
> 500) //limited log length
508 int end
=log
.LineIndex(1);
510 log
.ReplaceSel(_T(""));
512 log
.LineScroll(log
.GetLineCount() - log
.GetFirstVisibleLine() - 4);
514 int s1
=oneline
.ReverseFind(_T(':'));
515 int s2
=oneline
.Find(_T('%'));
516 if (s1
> 0 && s2
> 0)
519 CurrentWork
->SetWindowTextW(str
.Left(s1
));
521 int pos
=FindPercentage(str
);
522 TRACE(_T("Pos %d\r\n"),pos
);
525 progressctrl
.SetPos(pos
);
528 m_pTaskbarList
->SetProgressState(m_hWnd
, TBPF_NORMAL
);
529 m_pTaskbarList
->SetProgressValue(m_hWnd
, pos
, 100);
542 void CProgressDlg::RemoveLastLine(CString
&str
)
545 start
=str
.ReverseFind(_T('\n'));
550 // CProgressDlg message handlers
552 void CProgressDlg::OnBnClickedOk()
554 m_Log
.GetWindowText(this->m_LogText
);
558 void CProgressDlg::OnBnClickedButton1()
560 this->EndDialog((int)(IDC_PROGRESS_BUTTON1
+ this->m_ctrlPostCmd
.GetCurrentEntry()));
563 void CProgressDlg::OnClose()
565 DialogEnableWindow(IDCANCEL
, TRUE
);
569 void CProgressDlg::OnCancel()
574 CResizableStandAloneDialog::OnCancel();
578 if( g_Git
.m_CurrentGitPi
.hProcess
)
580 if(::GenerateConsoleCtrlEvent(CTRL_C_EVENT
,0))
582 ::WaitForSingleObject(g_Git
.m_CurrentGitPi
.hProcess
,10000);
589 KillProcessTree(g_Git
.m_CurrentGitPi
.dwProcessId
);
592 ::WaitForSingleObject(g_Git
.m_CurrentGitPi
.hProcess
,10000);
593 CResizableStandAloneDialog::OnCancel();
596 void CProgressDlg::KillProcessTree(DWORD dwProcessId
, unsigned int depth
)
598 // recursively kills a process tree
599 // This is not optimized, but works and isn't called very often ;)
601 if (!dwProcessId
|| depth
> 20)
605 memset(&pe
, 0, sizeof(PROCESSENTRY32
));
606 pe
.dwSize
= sizeof(PROCESSENTRY32
);
608 CAutoGeneralHandle hSnap
= ::CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS
, 0);
610 if (::Process32First(hSnap
, &pe
))
614 if (pe
.th32ParentProcessID
== dwProcessId
)
615 KillProcessTree(pe
.th32ProcessID
, depth
+ 1);
616 } while (::Process32Next(hSnap
, &pe
));
618 HANDLE hProc
= ::OpenProcess(PROCESS_TERMINATE
, FALSE
, dwProcessId
);
620 ::TerminateProcess(hProc
, 1);
624 void CProgressDlg::InsertCRLF()
626 m_Databuf
.m_critSec
.Lock();
627 for(int i
=0;i
<m_Databuf
.size();i
++)
629 if(m_Databuf
[i
]==('\n'))
631 if(i
==0 || m_Databuf
[i
-1]!= ('\r'))
633 m_Databuf
.insert(m_Databuf
.begin()+i
,('\r'));
638 m_Databuf
.m_critSec
.Unlock();
641 void CProgressDlg::InsertColorText(CRichEditCtrl
&edit
,CString text
,COLORREF rgb
)
644 edit
.GetDefaultCharFormat(cf
);
646 cf
.dwMask
|=CFM_COLOR
;
648 cf
.dwEffects
|=CFE_BOLD
;
649 cf
.dwEffects
&= ~CFE_AUTOCOLOR
;
650 edit
.SetSel(edit
.GetTextLength()-1,edit
.GetTextLength());
651 edit
.ReplaceSel(text
);
652 edit
.SetSel(edit
.LineIndex(edit
.GetLineCount()-2),edit
.GetTextLength());
653 edit
.SetSelectionCharFormat(cf
);
654 edit
.SetSel(edit
.GetTextLength(),edit
.GetTextLength());
655 edit
.SetDefaultCharFormat(old
);
656 edit
.LineScroll(edit
.GetLineCount() - edit
.GetFirstVisibleLine() - 4);
659 CString
CCommitProgressDlg::Convert2UnionCode(char *buff
, int size
)
666 cmd
=_T("git.exe config i18n.logOutputEncoding");
667 if (g_Git
.Run(cmd
, &output
, NULL
, CP_UTF8
))
671 output
=output
.Tokenize(_T("\n"),start
);
672 cp
=CUnicodeUtils::GetCPCode(output
);
676 size
= (int)strlen(buff
);
678 for(int i
=0;i
<size
;i
++)
682 if( start
>0 && buff
[i
] =='\n' )
690 g_Git
.StringAppend(&str
, (BYTE
*)buff
, cp
, start
);
691 g_Git
.StringAppend(&str
, (BYTE
*)buff
+ start
, CP_UTF8
, size
- start
);
698 LRESULT
CProgressDlg::OnTaskbarBtnCreated(WPARAM
/*wParam*/, LPARAM
/*lParam*/)
700 m_pTaskbarList
.Release();
701 m_pTaskbarList
.CoCreateInstance(CLSID_TaskbarList
);
705 BOOL
CProgressDlg::PreTranslateMessage(MSG
* pMsg
)
707 if (pMsg
->message
== WM_KEYDOWN
)
709 if (pMsg
->wParam
== VK_ESCAPE
)
711 // pressing the ESC key should close the dialog. But since we disabled the escape
712 // key (so the user doesn't get the idea that he could simply undo an e.g. update)
714 // So if the user presses the ESC key, change it to VK_RETURN so the dialog gets
715 // the impression that the OK button was pressed.
716 if ((!GetDlgItem(IDCANCEL
)->IsWindowEnabled())
717 &&(GetDlgItem(IDOK
)->IsWindowEnabled())&&(GetDlgItem(IDOK
)->IsWindowVisible()))
719 // since we convert ESC to RETURN, make sure the OK button has the focus.
720 GetDlgItem(IDOK
)->SetFocus();
721 pMsg
->wParam
= VK_RETURN
;
725 else if (pMsg
->message
== WM_CONTEXTMENU
|| pMsg
->message
== WM_RBUTTONDOWN
)
727 CWnd
* pWnd
= (CWnd
*) GetDlgItem(IDC_LOG
);
728 if (pWnd
== GetFocus())
731 if (popup
.CreatePopupMenu())
733 popup
.AppendMenuIcon(WM_COPY
, IDS_SCIEDIT_COPY
, IDI_COPYCLIP
);
734 if (m_Log
.GetSelText().IsEmpty())
735 popup
.EnableMenuItem(WM_COPY
, MF_BYCOMMAND
| MF_DISABLED
| MF_GRAYED
);
736 popup
.AppendMenu(MF_SEPARATOR
);
737 popup
.AppendMenuIcon(EM_SETSEL
, IDS_SCIEDIT_SELECTALL
);
738 int cmd
= popup
.TrackPopupMenu(TPM_RETURNCMD
| TPM_LEFTALIGN
| TPM_NONOTIFY
, pMsg
->pt
.x
, pMsg
->pt
.y
, this);
741 case 0: // no command selected
745 ::SendMessage(GetDlgItem(IDC_LOG
)->GetSafeHwnd(), cmd
, 0, -1);
752 return __super::PreTranslateMessage(pMsg
);