Fix issue #816: Output in Git Command Progress is broken
[TortoiseGit.git] / src / TortoiseProc / ProgressDlg.cpp
blobc1eae9a97a31c0f31719ca61b136359cee256839
1 // TortoiseGit - a Windows shell extension for easy version control
3 // Copyright (C) 2008-2011 - 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 "CommonResource.h"
31 // CProgressDlg dialog
33 IMPLEMENT_DYNAMIC(CProgressDlg, CResizableStandAloneDialog)
35 CProgressDlg::CProgressDlg(CWnd* pParent /*=NULL*/)
36 : CResizableStandAloneDialog(CProgressDlg::IDD, pParent), m_bShowCommand(true), m_bAutoCloseOnSuccess(false), m_bAbort(false), m_bDone(false)
38 m_pThread = NULL;
39 m_bAltAbortPress=false;
40 m_bBufferAll=false;
43 CProgressDlg::~CProgressDlg()
45 if(m_pThread != NULL)
47 delete m_pThread;
51 void CProgressDlg::DoDataExchange(CDataExchange* pDX)
53 CDialog::DoDataExchange(pDX);
54 DDX_Control(pDX, IDC_CURRENT, this->m_CurrentWork);
55 DDX_Control(pDX, IDC_TITLE_ANIMATE, this->m_Animate);
56 DDX_Control(pDX, IDC_RUN_PROGRESS, this->m_Progress);
57 DDX_Control(pDX, IDC_LOG, this->m_Log);
58 DDX_Control(pDX, IDC_PROGRESS_BUTTON1, this->m_ctrlPostCmd);
61 BEGIN_MESSAGE_MAP(CProgressDlg, CResizableStandAloneDialog)
62 ON_MESSAGE(MSG_PROGRESSDLG_UPDATE_UI, OnProgressUpdateUI)
63 ON_BN_CLICKED(IDOK, &CProgressDlg::OnBnClickedOk)
64 ON_BN_CLICKED(IDC_PROGRESS_BUTTON1,&CProgressDlg::OnBnClickedButton1)
65 ON_REGISTERED_MESSAGE(WM_TASKBARBTNCREATED, OnTaskbarBtnCreated)
66 END_MESSAGE_MAP()
68 BOOL CProgressDlg::OnInitDialog()
70 CResizableStandAloneDialog::OnInitDialog();
72 // Let the TaskbarButtonCreated message through the UIPI filter. If we don't
73 // do this, Explorer would be unable to send that message to our window if we
74 // were running elevated. It's OK to make the call all the time, since if we're
75 // not elevated, this is a no-op.
76 CHANGEFILTERSTRUCT cfs = { sizeof(CHANGEFILTERSTRUCT) };
77 typedef BOOL STDAPICALLTYPE ChangeWindowMessageFilterExDFN(HWND hWnd, UINT message, DWORD action, PCHANGEFILTERSTRUCT pChangeFilterStruct);
78 HMODULE hUser = ::LoadLibrary(_T("user32.dll"));
79 if (hUser)
81 ChangeWindowMessageFilterExDFN *pfnChangeWindowMessageFilterEx = (ChangeWindowMessageFilterExDFN*)GetProcAddress(hUser, "ChangeWindowMessageFilterEx");
82 if (pfnChangeWindowMessageFilterEx)
84 pfnChangeWindowMessageFilterEx(m_hWnd, WM_TASKBARBTNCREATED, MSGFLT_ALLOW, &cfs);
86 FreeLibrary(hUser);
88 m_pTaskbarList.Release();
89 m_pTaskbarList.CoCreateInstance(CLSID_TaskbarList);
91 AddAnchor(IDC_TITLE_ANIMATE, TOP_LEFT, TOP_RIGHT);
92 AddAnchor(IDC_RUN_PROGRESS, TOP_LEFT,TOP_RIGHT);
93 AddAnchor(IDC_LOG, TOP_LEFT,BOTTOM_RIGHT);
95 AddAnchor(IDOK,BOTTOM_RIGHT);
96 AddAnchor(IDCANCEL,BOTTOM_RIGHT);
97 AddAnchor(IDC_PROGRESS_BUTTON1,BOTTOM_LEFT);
98 AddAnchor(IDC_CURRENT,TOP_LEFT);
100 this->GetDlgItem(IDC_PROGRESS_BUTTON1)->ShowWindow(SW_HIDE);
101 m_Animate.Open(IDR_DOWNLOAD);
103 CString InitialText;
104 if ( !m_PreText.IsEmpty() )
106 InitialText = m_PreText + _T("\r\n");
108 #if 0
109 if (m_bShowCommand && (!m_GitCmd.IsEmpty() ))
111 InitialText += m_GitCmd+_T("\r\n\r\n");
113 #endif
114 m_Log.SetWindowTextW(InitialText);
115 m_CurrentWork.SetWindowTextW(_T(""));
117 EnableSaveRestore(_T("ProgressDlg"));
119 m_pThread = AfxBeginThread(ProgressThreadEntry, this, THREAD_PRIORITY_NORMAL,0,CREATE_SUSPENDED);
120 if (m_pThread==NULL)
122 // ReportError(CString(MAKEINTRESOURCE(IDS_ERR_THREADSTARTFAILED)));
124 else
126 m_pThread->m_bAutoDelete = FALSE;
127 m_pThread->ResumeThread();
130 if(!m_Title.IsEmpty())
131 this->SetWindowText(m_Title);
133 if(m_PostCmdList.GetCount()>0)
134 m_ctrlPostCmd.AddEntries(m_PostCmdList);
136 return TRUE;
139 UINT CProgressDlg::ProgressThreadEntry(LPVOID pVoid)
141 return ((CProgressDlg*)pVoid)->ProgressThread();
144 //static function, Share with SyncDialog
145 UINT CProgressDlg::RunCmdList(CWnd *pWnd,std::vector<CString> &cmdlist,bool bShowCommand,CString *pfilename,bool *bAbort,CGitByteArray *pdata)
147 UINT ret=0;
149 PROCESS_INFORMATION pi;
150 HANDLE hRead = 0;
152 memset(&pi,0,sizeof(PROCESS_INFORMATION));
154 pWnd->PostMessage(MSG_PROGRESSDLG_UPDATE_UI,MSG_PROGRESSDLG_START,0);
156 if(pdata)
157 pdata->clear();
159 for(int i=0;i<cmdlist.size();i++)
161 if(cmdlist[i].IsEmpty())
162 continue;
164 if (bShowCommand)
166 CStringA str(cmdlist[i].Trim()+_T("\n\n"));
167 for(int j=0;j<str.GetLength();j++)
169 if(pdata)
171 pdata->m_critSec.Lock();
172 pdata->push_back(str[j]);
173 pdata->m_critSec.Unlock();
175 else
176 pWnd->PostMessage(MSG_PROGRESSDLG_UPDATE_UI,MSG_PROGRESSDLG_RUN,str[j]);
178 if(pdata)
179 pWnd->PostMessage(MSG_PROGRESSDLG_UPDATE_UI,MSG_PROGRESSDLG_RUN,0);
182 g_Git.RunAsync(cmdlist[i].Trim(),&pi, &hRead,pfilename);
184 DWORD readnumber;
185 char byte;
186 CString output;
187 while(ReadFile(hRead,&byte,1,&readnumber,NULL))
189 if(pdata)
191 if(byte == 0)
192 byte = '\n';
194 pdata->m_critSec.Lock();
195 pdata->push_back( byte);
196 pdata->m_critSec.Unlock();
198 if(byte == '\r' || byte == '\n')
199 pWnd->PostMessage(MSG_PROGRESSDLG_UPDATE_UI,MSG_PROGRESSDLG_RUN,0);
200 }else
201 pWnd->PostMessage(MSG_PROGRESSDLG_UPDATE_UI,MSG_PROGRESSDLG_RUN,byte);
204 CloseHandle(pi.hThread);
206 WaitForSingleObject(pi.hProcess, INFINITE);
208 DWORD status=0;
209 if(!GetExitCodeProcess(pi.hProcess,&status) || *bAbort)
211 CloseHandle(pi.hProcess);
213 CloseHandle(hRead);
215 pWnd->PostMessage(MSG_PROGRESSDLG_UPDATE_UI,MSG_PROGRESSDLG_FAILED,0);
216 return GIT_ERROR_GET_EXIT_CODE;
218 ret |= status;
221 CloseHandle(pi.hProcess);
223 CloseHandle(hRead);
225 pWnd->PostMessage(MSG_PROGRESSDLG_UPDATE_UI,MSG_PROGRESSDLG_END,0);
227 return ret;
231 UINT CProgressDlg::ProgressThread()
234 m_GitCmdList.push_back(m_GitCmd);
236 CString *pfilename;
238 if(m_LogFile.IsEmpty())
239 pfilename=NULL;
240 else
241 pfilename=&m_LogFile;
243 m_GitStatus = RunCmdList(this,m_GitCmdList,m_bShowCommand,pfilename,&m_bAbort,&this->m_Databuf);;
244 return 0;
247 LRESULT CProgressDlg::OnProgressUpdateUI(WPARAM wParam,LPARAM lParam)
249 if(wParam == MSG_PROGRESSDLG_START)
251 m_BufStart = 0 ;
252 m_Animate.Play(0,-1,-1);
253 this->DialogEnableWindow(IDOK,FALSE);
254 if (m_pTaskbarList)
256 m_pTaskbarList->SetProgressState(m_hWnd, TBPF_NORMAL);
257 m_pTaskbarList->SetProgressValue(m_hWnd, 0, 100);
260 if(wParam == MSG_PROGRESSDLG_END || wParam == MSG_PROGRESSDLG_FAILED)
262 if(m_bBufferAll)
264 m_Databuf.m_critSec.Lock();
265 m_Databuf.push_back(0);
266 m_Databuf.m_critSec.Unlock();
267 InsertCRLF();
268 m_Databuf.m_critSec.Lock();
269 m_Log.SetWindowText(Convert2UnionCode((char*)&m_Databuf[0]));
270 m_Databuf.m_critSec.Unlock();
271 m_Log.LineScroll(m_Log.GetLineCount() - m_Log.GetFirstVisibleLine() - 4);
273 m_BufStart=0;
274 m_Databuf.m_critSec.Lock();
275 this->m_Databuf.clear();
276 m_Databuf.m_critSec.Unlock();
278 m_bDone = true;
279 m_Animate.Stop();
280 m_Progress.SetPos(100);
281 this->DialogEnableWindow(IDOK,TRUE);
283 CString err;
284 err.Format(_T("\r\nFailed 0x%x (git returned a wrong return code at some time)\r\n"),m_GitStatus);
285 if(this->m_GitStatus)
287 if (m_pTaskbarList)
289 m_pTaskbarList->SetProgressState(m_hWnd, TBPF_ERROR);
290 m_pTaskbarList->SetProgressValue(m_hWnd, 100, 100);
292 //InsertColorText(this->m_Log,err,RGB(255,0,0));
294 else {
295 if (m_pTaskbarList)
296 m_pTaskbarList->SetProgressState(m_hWnd, TBPF_NOPROGRESS);
297 InsertColorText(this->m_Log,_T("\r\nSuccess\r\n"),RGB(0,0,255));
298 this->DialogEnableWindow(IDCANCEL,FALSE);
301 if(wParam == MSG_PROGRESSDLG_END && m_GitStatus == 0)
303 if(m_bAutoCloseOnSuccess)
304 EndDialog(IDOK);
306 if(m_PostCmdList.GetCount() > 0)
308 //GetDlgItem(IDC_PROGRESS_BUTTON1)->SetWindowText(m_changeAbortButtonOnSuccessTo);
309 GetDlgItem(IDC_PROGRESS_BUTTON1)->ShowWindow(SW_SHOW);
310 //GetDlgItem(IDCANCEL)->ShowWindow(SW_HIDE);
311 //Set default button is "close" rather than "push"
312 this->SendMessage(WM_NEXTDLGCTL, (WPARAM)GetDlgItem(IDOK)->m_hWnd, TRUE);
317 if(!m_bBufferAll)
319 if(lParam == 0)
321 m_Databuf.m_critSec.Lock();
322 for(int i=this->m_BufStart;i<this->m_Databuf.size();i++)
324 char c = this->m_Databuf[m_BufStart];
325 m_BufStart++;
326 m_Databuf.m_critSec.Unlock();
327 ParserCmdOutput(c);
329 m_Databuf.m_critSec.Lock();
332 if(m_BufStart>1000)
334 m_Databuf.erase(m_Databuf.begin(), m_Databuf.begin()+m_BufStart);
335 m_BufStart =0;
337 m_Databuf.m_critSec.Unlock();
339 }else
340 ParserCmdOutput((char)lParam);
342 return 0;
345 //static function, Share with SyncDialog
346 int CProgressDlg::FindPercentage(CString &log)
348 int s1=log.Find(_T('%'));
349 if(s1<0)
350 return -1;
352 int s2=s1-1;
353 for(int i=s1-1;i>=0;i--)
355 if(log[i]>=_T('0') && log[i]<=_T('9'))
356 s2=i;
357 else
358 break;
360 return _ttol(log.Mid(s2,s1-s2));
363 void CProgressDlg::ParserCmdOutput(char ch)
365 ParserCmdOutput(this->m_Log,this->m_Progress,this->m_hWnd,this->m_pTaskbarList,this->m_LogTextA,ch,&this->m_CurrentWork);
367 int CProgressDlg::ClearESC(CStringA &str)
369 return str.Replace("\033[K","");
371 void CProgressDlg::ParserCmdOutput(CRichEditCtrl &log,CProgressCtrl &progressctrl,HWND m_hWnd,CComPtr<ITaskbarList3> m_pTaskbarList,CStringA &oneline, char ch, CWnd *CurrentWork)
373 //TRACE(_T("%c"),ch);
374 if( ch == ('\r') || ch == ('\n'))
376 CString str;
378 // TRACE(_T("End Char %s \r\n"),ch==_T('\r')?_T("lf"):_T(""));
379 // TRACE(_T("End Char %s \r\n"),ch==_T('\n')?_T("cr"):_T(""));
381 if(ClearESC(oneline))
383 ch = ('\r');
386 int lines = log.GetLineCount();
387 g_Git.StringAppend(&str,(BYTE*)oneline.GetBuffer(),CP_ACP);
388 str.Trim();
389 // TRACE(_T("%s"), str);
391 if(ch == ('\r'))
393 int start=log.LineIndex(lines-1);
394 int length=log.LineLength(lines-1)+1;
395 log.SetSel(start, start + length);
396 log.ReplaceSel(str);
398 else
400 int length = log.GetWindowTextLength();
401 log.SetSel(length, length);
402 if (length > 0)
403 log.ReplaceSel(_T("\r\n") + str);
404 else
405 log.ReplaceSel(str);
408 if (lines > 500) //limited log length
410 int end=log.LineIndex(1);
411 log.SetSel(0,end);
412 log.ReplaceSel(_T(""));
414 log.LineScroll(log.GetLineCount() - log.GetFirstVisibleLine() - 4);
416 int s1=oneline.ReverseFind(_T(':'));
417 int s2=oneline.Find(_T('%'));
418 if (s1 > 0 && s2 > 0)
420 if(CurrentWork)
421 CurrentWork->SetWindowTextW(str.Left(s1));
423 int pos=FindPercentage(str);
424 TRACE(_T("Pos %d\r\n"),pos);
425 if(pos>0)
427 progressctrl.SetPos(pos);
428 if (m_pTaskbarList)
430 m_pTaskbarList->SetProgressState(m_hWnd, TBPF_NORMAL);
431 m_pTaskbarList->SetProgressValue(m_hWnd, pos, 100);
436 oneline="";
439 else
441 oneline+=ch;
444 void CProgressDlg::RemoveLastLine(CString &str)
446 int start;
447 start=str.ReverseFind(_T('\n'));
448 if(start>0)
449 str=str.Left(start);
450 return;
452 // CProgressDlg message handlers
454 void CProgressDlg::OnBnClickedOk()
456 m_Log.GetWindowText(this->m_LogText);
457 OnOK();
460 void CProgressDlg::OnBnClickedButton1()
462 this->EndDialog(IDC_PROGRESS_BUTTON1 + this->m_ctrlPostCmd.GetCurrentEntry());
464 void CProgressDlg::OnCancel()
466 m_bAbort = true;
467 if(m_bDone)
469 CResizableStandAloneDialog::OnCancel();
470 return;
473 if( g_Git.m_CurrentGitPi.hProcess )
475 if(::GenerateConsoleCtrlEvent(CTRL_C_EVENT,0))
477 ::WaitForSingleObject(g_Git.m_CurrentGitPi.hProcess ,10000);
478 }else
480 GetLastError();
483 HANDLE hProcessHandle = ::OpenProcess( PROCESS_TERMINATE, FALSE,g_Git.m_CurrentGitPi.dwProcessId);
484 if( hProcessHandle )
485 if(!::TerminateProcess(hProcessHandle,-1) )
487 GetLastError();
491 ::WaitForSingleObject(g_Git.m_CurrentGitPi.hProcess ,10000);
492 CResizableStandAloneDialog::OnCancel();
496 void CProgressDlg::InsertCRLF()
498 m_Databuf.m_critSec.Lock();
499 for(int i=0;i<m_Databuf.size();i++)
501 if(m_Databuf[i]==('\n'))
503 if(i==0 || m_Databuf[i-1]!= ('\r'))
505 m_Databuf.insert(m_Databuf.begin()+i,('\r'));
506 i++;
510 m_Databuf.m_critSec.Unlock();
513 void CProgressDlg::InsertColorText(CRichEditCtrl &edit,CString text,COLORREF rgb)
515 CHARFORMAT old,cf;
516 edit.GetDefaultCharFormat(cf);
517 old=cf;
518 cf.dwMask|=CFM_COLOR;
519 cf.crTextColor=rgb;
520 cf.dwEffects|=CFE_BOLD;
521 cf.dwEffects &= ~CFE_AUTOCOLOR ;
522 edit.SetSel(edit.GetTextLength()-1,edit.GetTextLength());
523 edit.ReplaceSel(text);
524 edit.SetSel(edit.LineIndex(edit.GetLineCount()-2),edit.GetTextLength());
525 edit.SetSelectionCharFormat(cf);
526 edit.SetSel(edit.GetTextLength(),edit.GetTextLength());
527 edit.SetDefaultCharFormat(old);
528 edit.LineScroll(edit.GetLineCount() - edit.GetFirstVisibleLine() - 4);
531 CString CCommitProgressDlg::Convert2UnionCode(char *buff, int size)
533 CString str;
535 CString cmd,output;
536 int cp=CP_UTF8;
538 cmd=_T("git.exe config i18n.logOutputEncoding");
539 if(g_Git.Run(cmd,&output,CP_ACP))
540 cp=CP_UTF8;
542 int start=0;
543 output=output.Tokenize(_T("\n"),start);
544 cp=CUnicodeUtils::GetCPCode(output);
546 start =0;
547 if(size == -1)
548 size=strlen(buff);
550 for(int i=0;i<size;i++)
552 if(buff[i] == ']')
553 start = i;
554 if( start >0 && buff[i] =='\n' )
556 start =i;
557 break;
561 str.Empty();
562 g_Git.StringAppend(&str, (BYTE*)buff, cp, start);
563 g_Git.StringAppend(&str, (BYTE*)buff+start, CP_ACP,size - start);
565 return str;
568 LRESULT CProgressDlg::OnTaskbarBtnCreated(WPARAM /*wParam*/, LPARAM /*lParam*/)
570 m_pTaskbarList.Release();
571 m_pTaskbarList.CoCreateInstance(CLSID_TaskbarList);
572 return 0;
575 BOOL CProgressDlg::PreTranslateMessage(MSG* pMsg)
577 if (pMsg->message == WM_CONTEXTMENU || pMsg->message == WM_RBUTTONDOWN)
579 CWnd * pWnd = (CWnd*) GetDlgItem(IDC_LOG);
580 if (pWnd == GetFocus())
582 CIconMenu popup;
583 if (popup.CreatePopupMenu())
585 popup.AppendMenuIcon(WM_COPY, IDS_SCIEDIT_COPY, IDI_COPYCLIP);
586 if (m_Log.GetSelText().IsEmpty())
587 popup.EnableMenuItem(WM_COPY, MF_BYCOMMAND | MF_DISABLED | MF_GRAYED);
588 popup.AppendMenu(MF_SEPARATOR);
589 popup.AppendMenuIcon(EM_SETSEL, IDS_SCIEDIT_SELECTALL);
590 int cmd = popup.TrackPopupMenu(TPM_RETURNCMD | TPM_LEFTALIGN | TPM_NONOTIFY, pMsg->pt.x, pMsg->pt.y, this);
591 switch (cmd)
593 case 0: // no command selected
594 break;
595 case EM_SETSEL:
596 case WM_COPY:
597 ::SendMessage(GetDlgItem(IDC_LOG)->GetSafeHwnd(), cmd, 0, -1);
598 break;
600 return TRUE;
604 return __super::PreTranslateMessage(pMsg);