Correctly store log when exiting progress dialog
[TortoiseGit.git] / src / TortoiseProc / ProgressDlg.cpp
blob85b3941e0fef2e1a071b706a2d44f8d21f277bf2
1 // TortoiseGit - a Windows shell extension for easy version control
3 // Copyright (C) 2008-2012 - 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
22 #include "stdafx.h"
23 #include "TortoiseProc.h"
24 #include "ProgressDlg.h"
25 #include "Git.h"
26 #include "atlconv.h"
27 #include "UnicodeUtils.h"
28 #include "IconMenu.h"
29 #include "LoglistCommonResource.h"
30 #include "Tlhelp32.h"
31 #include "AppUtils.h"
32 #include "SmartHandle.h"
33 #include "../TGitCache/CacheInterface.h"
35 // CProgressDlg dialog
37 IMPLEMENT_DYNAMIC(CProgressDlg, CResizableStandAloneDialog)
39 CProgressDlg::CProgressDlg(CWnd* pParent /*=NULL*/)
40 : CResizableStandAloneDialog(CProgressDlg::IDD, pParent), m_bShowCommand(true), m_bAutoCloseOnSuccess(false), m_bAbort(false), m_bDone(false)
42 m_pThread = NULL;
43 m_bAltAbortPress=false;
44 m_bBufferAll=false;
47 CProgressDlg::~CProgressDlg()
49 if(m_pThread != NULL)
51 delete m_pThread;
55 void CProgressDlg::DoDataExchange(CDataExchange* pDX)
57 CDialog::DoDataExchange(pDX);
58 DDX_Control(pDX, IDC_CURRENT, this->m_CurrentWork);
59 DDX_Control(pDX, IDC_TITLE_ANIMATE, this->m_Animate);
60 DDX_Control(pDX, IDC_RUN_PROGRESS, this->m_Progress);
61 DDX_Control(pDX, IDC_LOG, this->m_Log);
62 DDX_Control(pDX, IDC_PROGRESS_BUTTON1, this->m_ctrlPostCmd);
65 BEGIN_MESSAGE_MAP(CProgressDlg, CResizableStandAloneDialog)
66 ON_WM_CLOSE()
67 ON_MESSAGE(MSG_PROGRESSDLG_UPDATE_UI, OnProgressUpdateUI)
68 ON_BN_CLICKED(IDOK, &CProgressDlg::OnBnClickedOk)
69 ON_BN_CLICKED(IDC_PROGRESS_BUTTON1,&CProgressDlg::OnBnClickedButton1)
70 ON_REGISTERED_MESSAGE(WM_TASKBARBTNCREATED, OnTaskbarBtnCreated)
71 END_MESSAGE_MAP()
73 BOOL CProgressDlg::OnInitDialog()
75 CResizableStandAloneDialog::OnInitDialog();
77 // Let the TaskbarButtonCreated message through the UIPI filter. If we don't
78 // do this, Explorer would be unable to send that message to our window if we
79 // were running elevated. It's OK to make the call all the time, since if we're
80 // not elevated, this is a no-op.
81 CHANGEFILTERSTRUCT cfs = { sizeof(CHANGEFILTERSTRUCT) };
82 typedef BOOL STDAPICALLTYPE ChangeWindowMessageFilterExDFN(HWND hWnd, UINT message, DWORD action, PCHANGEFILTERSTRUCT pChangeFilterStruct);
83 CAutoLibrary hUser = ::LoadLibrary(_T("user32.dll"));
84 if (hUser)
86 ChangeWindowMessageFilterExDFN *pfnChangeWindowMessageFilterEx = (ChangeWindowMessageFilterExDFN*)GetProcAddress(hUser, "ChangeWindowMessageFilterEx");
87 if (pfnChangeWindowMessageFilterEx)
89 pfnChangeWindowMessageFilterEx(m_hWnd, WM_TASKBARBTNCREATED, MSGFLT_ALLOW, &cfs);
92 m_pTaskbarList.Release();
93 m_pTaskbarList.CoCreateInstance(CLSID_TaskbarList);
95 AddAnchor(IDC_TITLE_ANIMATE, TOP_LEFT, TOP_RIGHT);
96 AddAnchor(IDC_RUN_PROGRESS, TOP_LEFT,TOP_RIGHT);
97 AddAnchor(IDC_LOG, TOP_LEFT,BOTTOM_RIGHT);
99 AddAnchor(IDOK,BOTTOM_RIGHT);
100 AddAnchor(IDCANCEL,BOTTOM_RIGHT);
101 AddAnchor(IDC_PROGRESS_BUTTON1,BOTTOM_LEFT);
102 AddAnchor(IDC_CURRENT,TOP_LEFT);
104 this->GetDlgItem(IDC_PROGRESS_BUTTON1)->ShowWindow(SW_HIDE);
105 m_Animate.Open(IDR_DOWNLOAD);
107 CString InitialText;
108 if ( !m_PreText.IsEmpty() )
110 InitialText = m_PreText + _T("\r\n");
112 #if 0
113 if (m_bShowCommand && (!m_GitCmd.IsEmpty() ))
115 InitialText += m_GitCmd+_T("\r\n\r\n");
117 #endif
118 m_Log.SetWindowTextW(InitialText);
119 m_CurrentWork.SetWindowTextW(_T(""));
121 EnableSaveRestore(_T("ProgressDlg"));
123 m_pThread = AfxBeginThread(ProgressThreadEntry, this, THREAD_PRIORITY_NORMAL,0,CREATE_SUSPENDED);
124 if (m_pThread==NULL)
126 // ReportError(CString(MAKEINTRESOURCE(IDS_ERR_THREADSTARTFAILED)));
128 else
130 m_pThread->m_bAutoDelete = FALSE;
131 m_pThread->ResumeThread();
134 if(!m_Title.IsEmpty())
135 this->SetWindowText(m_Title);
137 CString sWindowTitle;
138 GetWindowText(sWindowTitle);
139 CAppUtils::SetWindowTitle(m_hWnd, g_Git.m_CurrentDir, sWindowTitle);
141 if(m_PostCmdList.GetCount()>0)
142 m_ctrlPostCmd.AddEntries(m_PostCmdList);
144 return TRUE;
147 UINT CProgressDlg::ProgressThreadEntry(LPVOID pVoid)
149 return ((CProgressDlg*)pVoid)->ProgressThread();
152 //static function, Share with SyncDialog
153 UINT CProgressDlg::RunCmdList(CWnd *pWnd,std::vector<CString> &cmdlist,bool bShowCommand,CString *pfilename,bool *bAbort,CGitByteArray *pdata)
155 UINT ret=0;
157 PROCESS_INFORMATION pi;
158 HANDLE hRead = 0;
160 memset(&pi,0,sizeof(PROCESS_INFORMATION));
162 CBlockCacheForPath cacheBlock(g_Git.m_CurrentDir);
164 pWnd->PostMessage(MSG_PROGRESSDLG_UPDATE_UI,MSG_PROGRESSDLG_START,0);
166 if(pdata)
167 pdata->clear();
169 for(int i=0;i<cmdlist.size();i++)
171 if(cmdlist[i].IsEmpty())
172 continue;
174 if (bShowCommand)
176 CStringA str = CUnicodeUtils::GetMulti(cmdlist[i].Trim() + _T("\n\n"), CP_UTF8);
177 for(int j=0;j<str.GetLength();j++)
179 if(pdata)
181 pdata->m_critSec.Lock();
182 pdata->push_back(str[j]);
183 pdata->m_critSec.Unlock();
185 else
186 pWnd->PostMessage(MSG_PROGRESSDLG_UPDATE_UI,MSG_PROGRESSDLG_RUN,str[j]);
188 if(pdata)
189 pWnd->PostMessage(MSG_PROGRESSDLG_UPDATE_UI,MSG_PROGRESSDLG_RUN,0);
192 g_Git.RunAsync(cmdlist[i].Trim(),&pi, &hRead, NULL, pfilename);
194 DWORD readnumber;
195 char byte;
196 CString output;
197 while(ReadFile(hRead,&byte,1,&readnumber,NULL))
199 if(pdata)
201 if(byte == 0)
202 byte = '\n';
204 pdata->m_critSec.Lock();
205 pdata->push_back( byte);
206 pdata->m_critSec.Unlock();
208 if(byte == '\r' || byte == '\n')
209 pWnd->PostMessage(MSG_PROGRESSDLG_UPDATE_UI,MSG_PROGRESSDLG_RUN,0);
211 else
212 pWnd->PostMessage(MSG_PROGRESSDLG_UPDATE_UI,MSG_PROGRESSDLG_RUN,byte);
215 CloseHandle(pi.hThread);
217 WaitForSingleObject(pi.hProcess, INFINITE);
219 DWORD status=0;
220 if(!GetExitCodeProcess(pi.hProcess,&status) || *bAbort)
222 CloseHandle(pi.hProcess);
224 CloseHandle(hRead);
226 pWnd->PostMessage(MSG_PROGRESSDLG_UPDATE_UI, MSG_PROGRESSDLG_FAILED, status);
227 return TGIT_GIT_ERROR_GET_EXIT_CODE;
229 ret |= status;
232 CloseHandle(pi.hProcess);
234 CloseHandle(hRead);
236 pWnd->PostMessage(MSG_PROGRESSDLG_UPDATE_UI, MSG_PROGRESSDLG_END, ret);
238 return ret;
242 UINT CProgressDlg::ProgressThread()
245 m_GitCmdList.push_back(m_GitCmd);
247 CString *pfilename;
249 if(m_LogFile.IsEmpty())
250 pfilename=NULL;
251 else
252 pfilename=&m_LogFile;
254 m_GitStatus = RunCmdList(this,m_GitCmdList,m_bShowCommand,pfilename,&m_bAbort,&this->m_Databuf);;
255 return 0;
258 LRESULT CProgressDlg::OnProgressUpdateUI(WPARAM wParam,LPARAM lParam)
260 if(wParam == MSG_PROGRESSDLG_START)
262 m_BufStart = 0 ;
263 m_Animate.Play(0,-1,-1);
264 this->DialogEnableWindow(IDOK,FALSE);
265 if (m_pTaskbarList)
267 m_pTaskbarList->SetProgressState(m_hWnd, TBPF_NORMAL);
268 m_pTaskbarList->SetProgressValue(m_hWnd, 0, 100);
271 if(wParam == MSG_PROGRESSDLG_END || wParam == MSG_PROGRESSDLG_FAILED)
273 if(m_bBufferAll)
275 m_Databuf.m_critSec.Lock();
276 m_Databuf.push_back(0);
277 m_Databuf.m_critSec.Unlock();
278 InsertCRLF();
279 m_Databuf.m_critSec.Lock();
280 m_Log.SetWindowText(Convert2UnionCode((char*)&m_Databuf[0]));
281 m_Databuf.m_critSec.Unlock();
282 m_Log.LineScroll(m_Log.GetLineCount() - m_Log.GetFirstVisibleLine() - 4);
284 m_BufStart=0;
285 m_Databuf.m_critSec.Lock();
286 this->m_Databuf.clear();
287 m_Databuf.m_critSec.Unlock();
289 m_bDone = true;
290 m_Animate.Stop();
291 m_Progress.SetPos(100);
292 this->DialogEnableWindow(IDOK,TRUE);
294 m_GitStatus = lParam;
296 // detect crashes of perl when performing git svn actions
297 if (m_GitStatus == 0 && m_GitCmd.Find(_T(" svn ")) > 1)
299 CString log;
300 m_Log.GetWindowText(log);
301 if (log.GetLength() > 18 && log.Mid(log.GetLength() - 18) == _T("perl.exe.stackdump"))
302 m_GitStatus = -1;
305 CString err;
306 err.Format(_T("\r\n\r\ngit did not exit cleanly (exit code %d)\r\n"), m_GitStatus);
307 if(this->m_GitStatus)
309 if (m_pTaskbarList)
311 m_pTaskbarList->SetProgressState(m_hWnd, TBPF_ERROR);
312 m_pTaskbarList->SetProgressValue(m_hWnd, 100, 100);
314 InsertColorText(this->m_Log,err,RGB(255,0,0));
316 else {
317 if (m_pTaskbarList)
318 m_pTaskbarList->SetProgressState(m_hWnd, TBPF_NOPROGRESS);
319 InsertColorText(this->m_Log,_T("\r\nSuccess\r\n"),RGB(0,0,255));
320 this->DialogEnableWindow(IDCANCEL,FALSE);
323 if(wParam == MSG_PROGRESSDLG_END && m_GitStatus == 0)
325 if(m_bAutoCloseOnSuccess)
327 m_Log.GetWindowText(this->m_LogText);
328 EndDialog(IDOK);
331 if(m_PostCmdList.GetCount() > 0)
333 //GetDlgItem(IDC_PROGRESS_BUTTON1)->SetWindowText(m_changeAbortButtonOnSuccessTo);
334 GetDlgItem(IDC_PROGRESS_BUTTON1)->ShowWindow(SW_SHOW);
335 //GetDlgItem(IDCANCEL)->ShowWindow(SW_HIDE);
336 //Set default button is "close" rather than "push"
337 this->SendMessage(WM_NEXTDLGCTL, (WPARAM)GetDlgItem(IDOK)->m_hWnd, TRUE);
342 if(!m_bBufferAll)
344 if(lParam == 0)
346 m_Databuf.m_critSec.Lock();
347 for(int i=this->m_BufStart;i<this->m_Databuf.size();i++)
349 char c = this->m_Databuf[m_BufStart];
350 m_BufStart++;
351 m_Databuf.m_critSec.Unlock();
352 ParserCmdOutput(c);
354 m_Databuf.m_critSec.Lock();
357 if(m_BufStart>1000)
359 m_Databuf.erase(m_Databuf.begin(), m_Databuf.begin()+m_BufStart);
360 m_BufStart =0;
362 m_Databuf.m_critSec.Unlock();
365 else
366 ParserCmdOutput((char)lParam);
368 return 0;
371 //static function, Share with SyncDialog
372 int CProgressDlg::FindPercentage(CString &log)
374 int s1=log.Find(_T('%'));
375 if(s1<0)
376 return -1;
378 int s2=s1-1;
379 for(int i=s1-1;i>=0;i--)
381 if(log[i]>=_T('0') && log[i]<=_T('9'))
382 s2=i;
383 else
384 break;
386 return _ttol(log.Mid(s2,s1-s2));
389 void CProgressDlg::ParserCmdOutput(char ch)
391 ParserCmdOutput(this->m_Log,this->m_Progress,this->m_hWnd,this->m_pTaskbarList,this->m_LogTextA,ch,&this->m_CurrentWork);
393 void CProgressDlg::ClearESC(CString &str)
395 // see http://ascii-table.com/ansi-escape-sequences.php and http://tldp.org/HOWTO/Bash-Prompt-HOWTO/c327.html
396 str.Replace(_T("\033[K"), _T("")); // erase until end of line; no need to care for this, because we always clear the whole line
398 // drop colors
399 while (true)
401 int escapePosition = str.Find(_T('\033'));
402 if (escapePosition >= 0 && str.GetLength() >= escapePosition + 3)
404 if (str.Mid(escapePosition, 2) == _T("\033["))
406 int colorEnd = str.Find(_T('m'), escapePosition + 2);
407 if (colorEnd > 0)
409 bool found = true;
410 for (int i = escapePosition + 2; i < colorEnd; i++)
412 if (str[i] != _T(';') && (str[i] < _T('0') && str[i] > _T('9')))
414 found = false;
415 break;
418 if (found)
420 if (escapePosition > 0)
421 str = str.Left(escapePosition) + str.Mid(colorEnd + 1);
422 else
423 str = str.Mid(colorEnd);
424 continue;
429 break;
432 void CProgressDlg::ParserCmdOutput(CRichEditCtrl &log,CProgressCtrl &progressctrl,HWND m_hWnd,CComPtr<ITaskbarList3> m_pTaskbarList,CStringA &oneline, char ch, CWnd *CurrentWork)
434 //TRACE(_T("%c"),ch);
435 if( ch == ('\r') || ch == ('\n'))
437 CString str;
439 // TRACE(_T("End Char %s \r\n"),ch==_T('\r')?_T("lf"):_T(""));
440 // TRACE(_T("End Char %s \r\n"),ch==_T('\n')?_T("cr"):_T(""));
442 int lines = log.GetLineCount();
443 g_Git.StringAppend(&str, (BYTE*)oneline.GetBuffer(), CP_UTF8);
444 str.Trim();
445 // TRACE(_T("%s"), str);
447 ClearESC(str);
449 if(ch == ('\r'))
451 int start=log.LineIndex(lines-1);
452 log.SetSel(start, log.GetTextLength());
453 log.ReplaceSel(str);
455 else
457 int length = log.GetWindowTextLength();
458 log.SetSel(length, length);
459 if (length > 0)
460 log.ReplaceSel(_T("\r\n") + str);
461 else
462 log.ReplaceSel(str);
465 if (lines > 500) //limited log length
467 int end=log.LineIndex(1);
468 log.SetSel(0,end);
469 log.ReplaceSel(_T(""));
471 log.LineScroll(log.GetLineCount() - log.GetFirstVisibleLine() - 4);
473 int s1=oneline.ReverseFind(_T(':'));
474 int s2=oneline.Find(_T('%'));
475 if (s1 > 0 && s2 > 0)
477 if(CurrentWork)
478 CurrentWork->SetWindowTextW(str.Left(s1));
480 int pos=FindPercentage(str);
481 TRACE(_T("Pos %d\r\n"),pos);
482 if(pos>0)
484 progressctrl.SetPos(pos);
485 if (m_pTaskbarList)
487 m_pTaskbarList->SetProgressState(m_hWnd, TBPF_NORMAL);
488 m_pTaskbarList->SetProgressValue(m_hWnd, pos, 100);
493 oneline="";
496 else
498 oneline+=ch;
501 void CProgressDlg::RemoveLastLine(CString &str)
503 int start;
504 start=str.ReverseFind(_T('\n'));
505 if(start>0)
506 str=str.Left(start);
507 return;
509 // CProgressDlg message handlers
511 void CProgressDlg::OnBnClickedOk()
513 m_Log.GetWindowText(this->m_LogText);
514 OnOK();
517 void CProgressDlg::OnBnClickedButton1()
519 this->EndDialog(IDC_PROGRESS_BUTTON1 + this->m_ctrlPostCmd.GetCurrentEntry());
522 void CProgressDlg::OnClose()
524 DialogEnableWindow(IDCANCEL, TRUE);
525 __super::OnClose();
528 void CProgressDlg::OnCancel()
530 m_bAbort = true;
531 if(m_bDone)
533 CResizableStandAloneDialog::OnCancel();
534 return;
537 if( g_Git.m_CurrentGitPi.hProcess )
539 if(::GenerateConsoleCtrlEvent(CTRL_C_EVENT,0))
541 ::WaitForSingleObject(g_Git.m_CurrentGitPi.hProcess ,10000);
543 else
545 GetLastError();
548 KillProcessTree(g_Git.m_CurrentGitPi.dwProcessId);
551 ::WaitForSingleObject(g_Git.m_CurrentGitPi.hProcess ,10000);
552 CResizableStandAloneDialog::OnCancel();
555 void CProgressDlg::KillProcessTree(DWORD dwProcessId)
557 // recursively kills a process tree
558 // This is not optimized, but works and isn't called very often ;)
559 PROCESSENTRY32 pe;
560 memset(&pe, 0, sizeof(PROCESSENTRY32));
561 pe.dwSize = sizeof(PROCESSENTRY32);
563 HANDLE hSnap = ::CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
565 if (::Process32First(hSnap, &pe))
569 if (pe.th32ParentProcessID == dwProcessId)
570 KillProcessTree(pe.th32ProcessID);
571 } while (::Process32Next(hSnap, &pe));
573 HANDLE hProc = ::OpenProcess(PROCESS_TERMINATE, FALSE, dwProcessId);
574 if (hProc)
576 ::TerminateProcess(hProc, 1);
577 ::CloseHandle(hProc);
580 ::CloseHandle(hSnap);
583 void CProgressDlg::InsertCRLF()
585 m_Databuf.m_critSec.Lock();
586 for(int i=0;i<m_Databuf.size();i++)
588 if(m_Databuf[i]==('\n'))
590 if(i==0 || m_Databuf[i-1]!= ('\r'))
592 m_Databuf.insert(m_Databuf.begin()+i,('\r'));
593 i++;
597 m_Databuf.m_critSec.Unlock();
600 void CProgressDlg::InsertColorText(CRichEditCtrl &edit,CString text,COLORREF rgb)
602 CHARFORMAT old,cf;
603 edit.GetDefaultCharFormat(cf);
604 old=cf;
605 cf.dwMask|=CFM_COLOR;
606 cf.crTextColor=rgb;
607 cf.dwEffects|=CFE_BOLD;
608 cf.dwEffects &= ~CFE_AUTOCOLOR ;
609 edit.SetSel(edit.GetTextLength()-1,edit.GetTextLength());
610 edit.ReplaceSel(text);
611 edit.SetSel(edit.LineIndex(edit.GetLineCount()-2),edit.GetTextLength());
612 edit.SetSelectionCharFormat(cf);
613 edit.SetSel(edit.GetTextLength(),edit.GetTextLength());
614 edit.SetDefaultCharFormat(old);
615 edit.LineScroll(edit.GetLineCount() - edit.GetFirstVisibleLine() - 4);
618 CString CCommitProgressDlg::Convert2UnionCode(char *buff, int size)
620 CString str;
622 CString cmd, output;
623 int cp=CP_UTF8;
625 cmd=_T("git.exe config i18n.logOutputEncoding");
626 if (g_Git.Run(cmd, &output, NULL, CP_UTF8))
627 cp=CP_UTF8;
629 int start=0;
630 output=output.Tokenize(_T("\n"),start);
631 cp=CUnicodeUtils::GetCPCode(output);
633 start =0;
634 if(size == -1)
635 size=strlen(buff);
637 for(int i=0;i<size;i++)
639 if(buff[i] == ']')
640 start = i;
641 if( start >0 && buff[i] =='\n' )
643 start =i;
644 break;
648 str.Empty();
649 g_Git.StringAppend(&str, (BYTE*)buff, cp, start);
650 g_Git.StringAppend(&str, (BYTE*)buff + start, CP_UTF8, size - start);
652 ClearESC(str);
654 return str;
657 LRESULT CProgressDlg::OnTaskbarBtnCreated(WPARAM /*wParam*/, LPARAM /*lParam*/)
659 m_pTaskbarList.Release();
660 m_pTaskbarList.CoCreateInstance(CLSID_TaskbarList);
661 return 0;
664 BOOL CProgressDlg::PreTranslateMessage(MSG* pMsg)
666 if (pMsg->message == WM_KEYDOWN)
668 if (pMsg->wParam == VK_ESCAPE)
670 // pressing the ESC key should close the dialog. But since we disabled the escape
671 // key (so the user doesn't get the idea that he could simply undo an e.g. update)
672 // this won't work.
673 // So if the user presses the ESC key, change it to VK_RETURN so the dialog gets
674 // the impression that the OK button was pressed.
675 if ((!GetDlgItem(IDCANCEL)->IsWindowEnabled())
676 &&(GetDlgItem(IDOK)->IsWindowEnabled())&&(GetDlgItem(IDOK)->IsWindowVisible()))
678 // since we convert ESC to RETURN, make sure the OK button has the focus.
679 GetDlgItem(IDOK)->SetFocus();
680 pMsg->wParam = VK_RETURN;
684 else if (pMsg->message == WM_CONTEXTMENU || pMsg->message == WM_RBUTTONDOWN)
686 CWnd * pWnd = (CWnd*) GetDlgItem(IDC_LOG);
687 if (pWnd == GetFocus())
689 CIconMenu popup;
690 if (popup.CreatePopupMenu())
692 popup.AppendMenuIcon(WM_COPY, IDS_SCIEDIT_COPY, IDI_COPYCLIP);
693 if (m_Log.GetSelText().IsEmpty())
694 popup.EnableMenuItem(WM_COPY, MF_BYCOMMAND | MF_DISABLED | MF_GRAYED);
695 popup.AppendMenu(MF_SEPARATOR);
696 popup.AppendMenuIcon(EM_SETSEL, IDS_SCIEDIT_SELECTALL);
697 int cmd = popup.TrackPopupMenu(TPM_RETURNCMD | TPM_LEFTALIGN | TPM_NONOTIFY, pMsg->pt.x, pMsg->pt.y, this);
698 switch (cmd)
700 case 0: // no command selected
701 break;
702 case EM_SETSEL:
703 case WM_COPY:
704 ::SendMessage(GetDlgItem(IDC_LOG)->GetSafeHwnd(), cmd, 0, -1);
705 break;
707 return TRUE;
711 return __super::PreTranslateMessage(pMsg);