Provide (experimental) clang-format file
[TortoiseGit.git] / src / TortoiseProc / ProgressDlg.cpp
blob0b1cff263f4ecba8e3a6c92a73f18d0f14af3ea3
1 // TortoiseGit - a Windows shell extension for easy version control
3 // Copyright (C) 2008-2017 - 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 "UnicodeUtils.h"
26 #include "IconMenu.h"
27 #include "LoglistCommonResource.h"
28 #include <Tlhelp32.h>
29 #include "AppUtils.h"
30 #include "SmartHandle.h"
31 #include "../TGitCache/CacheInterface.h"
32 #include "LoglistUtils.h"
33 #include "MessageBox.h"
34 #include "LogFile.h"
35 #include "CmdLineParser.h"
36 #include "StringUtils.h"
38 // CProgressDlg dialog
40 IMPLEMENT_DYNAMIC(CProgressDlg, CResizableStandAloneDialog)
42 const int CProgressDlg::s_iProgressLinesLimit = max(50, (int)CRegDWORD(L"Software\\TortoiseGit\\ProgressDlgLinesLimit", 50000));
44 CProgressDlg::CProgressDlg(CWnd* pParent /*=nullptr*/)
45 : CResizableStandAloneDialog(CProgressDlg::IDD, pParent)
46 , m_bShowCommand(true)
47 , m_bAbort(false)
48 , m_bDone(false)
49 , m_startTick(GetTickCount64())
50 , m_BufStart(0)
51 , m_Git(&g_Git)
52 , m_hAccel(nullptr)
53 , m_pThread(nullptr)
54 , m_bBufferAll(false)
55 , m_GitStatus((DWORD)-1)
57 int autoClose = CRegDWORD(L"Software\\TortoiseGit\\AutoCloseGitProgress", 0);
58 CCmdLineParser parser(AfxGetApp()->m_lpCmdLine);
59 if (parser.HasKey(L"closeonend"))
60 autoClose = parser.GetLongVal(L"closeonend");
61 switch (autoClose)
63 case 1:
64 m_AutoClose = AUTOCLOSE_IF_NO_OPTIONS;
65 break;
66 case 2:
67 m_AutoClose = AUTOCLOSE_IF_NO_ERRORS;
68 break;
69 default:
70 m_AutoClose = AUTOCLOSE_NO;
71 break;
75 CProgressDlg::~CProgressDlg()
77 if (m_hAccel)
78 DestroyAcceleratorTable(m_hAccel);
79 delete m_pThread;
82 void CProgressDlg::DoDataExchange(CDataExchange* pDX)
84 CDialog::DoDataExchange(pDX);
85 DDX_Control(pDX, IDC_CURRENT, this->m_CurrentWork);
86 DDX_Control(pDX, IDC_TITLE_ANIMATE, this->m_Animate);
87 DDX_Control(pDX, IDC_RUN_PROGRESS, this->m_Progress);
88 DDX_Control(pDX, IDC_LOG, this->m_Log);
89 DDX_Control(pDX, IDC_PROGRESS_BUTTON1, this->m_ctrlPostCmd);
92 BEGIN_MESSAGE_MAP(CProgressDlg, CResizableStandAloneDialog)
93 ON_WM_CLOSE()
94 ON_MESSAGE(MSG_PROGRESSDLG_UPDATE_UI, OnProgressUpdateUI)
95 ON_BN_CLICKED(IDOK, &CProgressDlg::OnBnClickedOk)
96 ON_BN_CLICKED(IDC_PROGRESS_BUTTON1,&CProgressDlg::OnBnClickedButton1)
97 ON_REGISTERED_MESSAGE(TaskBarButtonCreated, OnTaskbarBtnCreated)
98 ON_NOTIFY(EN_LINK, IDC_LOG, OnEnLinkLog)
99 ON_EN_VSCROLL(IDC_LOG, OnEnscrollLog)
100 ON_EN_HSCROLL(IDC_LOG, OnEnscrollLog)
101 END_MESSAGE_MAP()
103 BOOL CProgressDlg::OnInitDialog()
105 CResizableStandAloneDialog::OnInitDialog();
107 // Let the TaskbarButtonCreated message through the UIPI filter. If we don't
108 // do this, Explorer would be unable to send that message to our window if we
109 // were running elevated. It's OK to make the call all the time, since if we're
110 // not elevated, this is a no-op.
111 CHANGEFILTERSTRUCT cfs = { sizeof(CHANGEFILTERSTRUCT) };
112 typedef BOOL STDAPICALLTYPE ChangeWindowMessageFilterExDFN(HWND hWnd, UINT message, DWORD action, PCHANGEFILTERSTRUCT pChangeFilterStruct);
113 CAutoLibrary hUser = AtlLoadSystemLibraryUsingFullPath(L"user32.dll");
114 if (hUser)
116 ChangeWindowMessageFilterExDFN *pfnChangeWindowMessageFilterEx = (ChangeWindowMessageFilterExDFN*)GetProcAddress(hUser, "ChangeWindowMessageFilterEx");
117 if (pfnChangeWindowMessageFilterEx)
118 pfnChangeWindowMessageFilterEx(m_hWnd, TaskBarButtonCreated, MSGFLT_ALLOW, &cfs);
120 m_pTaskbarList.Release();
121 if (FAILED(m_pTaskbarList.CoCreateInstance(CLSID_TaskbarList)))
122 m_pTaskbarList = nullptr;
124 AddAnchor(IDC_TITLE_ANIMATE, TOP_LEFT, TOP_RIGHT);
125 AddAnchor(IDC_RUN_PROGRESS, TOP_LEFT,TOP_RIGHT);
126 AddAnchor(IDC_LOG, TOP_LEFT,BOTTOM_RIGHT);
128 AddAnchor(IDOK,BOTTOM_RIGHT);
129 AddAnchor(IDCANCEL,BOTTOM_RIGHT);
130 AddAnchor(IDC_PROGRESS_BUTTON1,BOTTOM_LEFT);
131 AddAnchor(IDC_CURRENT,TOP_LEFT);
133 this->GetDlgItem(IDC_PROGRESS_BUTTON1)->ShowWindow(SW_HIDE);
134 m_Animate.Open(IDR_DOWNLOAD);
136 CFont m_logFont;
137 CAppUtils::CreateFontForLogs(m_logFont);
138 m_Log.SetFont(&m_logFont);
139 // make the log message rich edit control send a message when the mouse pointer is over a link
140 m_Log.SendMessage(EM_SETEVENTMASK, NULL, ENM_LINK | ENM_SCROLL);
142 CString InitialText;
143 if ( !m_PreText.IsEmpty() )
144 InitialText = m_PreText + L"\r\n";
145 #if 0
146 if (m_bShowCommand && (!m_GitCmd.IsEmpty() ))
147 InitialText += m_GitCmd + L"\r\n\r\n";
148 #endif
149 m_Log.SetWindowTextW(InitialText);
150 m_CurrentWork.SetWindowText(L"");
152 if (!m_PreFailText.IsEmpty())
153 InsertColorText(this->m_Log, m_PreFailText, RGB(255, 0, 0));
155 EnableSaveRestore(L"ProgressDlg");
157 m_pThread = AfxBeginThread(ProgressThreadEntry, this, THREAD_PRIORITY_NORMAL,0,CREATE_SUSPENDED);
158 if (!m_pThread)
160 CMessageBox::Show(this->m_hWnd, IDS_ERR_THREADSTARTFAILED, IDS_APPNAME, MB_OK | MB_ICONERROR);
161 DialogEnableWindow(IDCANCEL, TRUE);
163 else
165 m_pThread->m_bAutoDelete = FALSE;
166 m_pThread->ResumeThread();
169 CString sWindowTitle;
170 GetWindowText(sWindowTitle);
171 CAppUtils::SetWindowTitle(m_hWnd, m_Git->m_CurrentDir, sWindowTitle);
173 // Make sure this dialog is shown in foreground (see issue #1536)
174 SetForegroundWindow();
176 return TRUE;
179 static void EnsurePostMessage(CWnd *pWnd, UINT Msg, WPARAM wParam, LPARAM lParam)
181 redo:
182 if (!pWnd->PostMessage(Msg, wParam, lParam))
184 if (GetLastError() == ERROR_NOT_ENOUGH_QUOTA)
186 Sleep(20);
187 goto redo;
189 else
190 CTraceToOutputDebugString::Instance()(_T(__FUNCTION__) L": Message %d-%d could not be sent (error %d; %s)\n", wParam, lParam, GetLastError(), (LPCTSTR)CFormatMessageWrapper());
194 UINT CProgressDlg::ProgressThreadEntry(LPVOID pVoid)
196 return reinterpret_cast<CProgressDlg*>(pVoid)->ProgressThread();
199 //static function, Share with SyncDialog
200 UINT CProgressDlg::RunCmdList(CWnd* pWnd, STRING_VECTOR& cmdlist, STRING_VECTOR& dirlist, bool bShowCommand, CString* pfilename, volatile bool* bAbort, CGitGuardedByteArray* pdata, CGit* git)
202 UINT ret=0;
204 std::vector<std::unique_ptr<CBlockCacheForPath>> cacheBlockList;
205 std::vector<std::unique_ptr<CGit>> gitList;
206 if (dirlist.empty())
207 cacheBlockList.push_back(std::make_unique<CBlockCacheForPath>(git->m_CurrentDir));
208 else
210 for (const auto& dir : dirlist)
212 auto pGit = std::make_unique<CGit>();
213 pGit->m_CurrentDir = dir;
214 gitList.push_back(std::move(pGit));
215 cacheBlockList.push_back(std::make_unique<CBlockCacheForPath>(dir));
219 EnsurePostMessage(pWnd, MSG_PROGRESSDLG_UPDATE_UI, MSG_PROGRESSDLG_START, 0);
221 if(pdata)
222 pdata->clear();
224 for (size_t i = 0; i < cmdlist.size(); ++i)
226 if(cmdlist[i].IsEmpty())
227 continue;
229 if (bShowCommand)
231 CStringA str;
232 if (gitList.empty() || gitList.size() == 1 && gitList[0]->m_CurrentDir == git->m_CurrentDir)
233 str = CUnicodeUtils::GetMulti(cmdlist[i].Trim() + L"\r\n\r\n", CP_UTF8);
234 else
235 str = CUnicodeUtils::GetMulti((i > 0 ? L"\r\n" : L"") + gitList[i]->m_CurrentDir + L"\r\n" + cmdlist[i].Trim() + L"\r\n\r\n", CP_UTF8);
236 for (int j = 0; j < str.GetLength(); ++j)
238 if(pdata)
240 pdata->m_critSec.Lock();
241 pdata->push_back(str[j]);
242 pdata->m_critSec.Unlock();
244 else
245 pWnd->PostMessage(MSG_PROGRESSDLG_UPDATE_UI,MSG_PROGRESSDLG_RUN,str[j]);
247 if(pdata)
248 pWnd->PostMessage(MSG_PROGRESSDLG_UPDATE_UI,MSG_PROGRESSDLG_RUN,0);
251 PROCESS_INFORMATION pi;
252 CAutoGeneralHandle hRead;
253 int runAsyncRet = -1;
254 if (gitList.empty())
255 runAsyncRet = git->RunAsync(cmdlist[i].Trim(), &pi, hRead.GetPointer(), nullptr, pfilename);
256 else
257 runAsyncRet = gitList[i]->RunAsync(cmdlist[i].Trim(), &pi, hRead.GetPointer(), nullptr, pfilename);
258 if (runAsyncRet)
260 EnsurePostMessage(pWnd, MSG_PROGRESSDLG_UPDATE_UI, MSG_PROGRESSDLG_FAILED, -1 * runAsyncRet);
261 return runAsyncRet;
264 CAutoGeneralHandle piProcess(std::move(pi.hProcess));
265 CAutoGeneralHandle piThread(std::move(pi.hThread));
266 DWORD readnumber;
267 char lastByte = '\0';
268 char byte;
269 CString output;
270 while (ReadFile(hRead, &byte, 1, &readnumber, nullptr))
272 if(pdata)
274 if(byte == 0)
275 byte = '\n';
277 pdata->m_critSec.Lock();
278 if (byte == '\n' && lastByte != '\r')
279 pdata->push_back('\r');
280 pdata->push_back( byte);
281 lastByte = byte;
282 pdata->m_critSec.Unlock();
284 if(byte == '\r' || byte == '\n')
285 pWnd->PostMessage(MSG_PROGRESSDLG_UPDATE_UI,MSG_PROGRESSDLG_RUN,0);
287 else
288 pWnd->PostMessage(MSG_PROGRESSDLG_UPDATE_UI,MSG_PROGRESSDLG_RUN,byte);
290 if (pdata)
292 pdata->m_critSec.Lock();
293 bool post = !pdata->empty();
294 pdata->m_critSec.Unlock();
295 if (post)
296 EnsurePostMessage(pWnd, MSG_PROGRESSDLG_UPDATE_UI, MSG_PROGRESSDLG_RUN, 0);
299 CTraceToOutputDebugString::Instance()(_T(__FUNCTION__) L": waiting for process to finish (%s), aborted: %d\n", (LPCTSTR)cmdlist[i], *bAbort);
301 WaitForSingleObject(pi.hProcess, INFINITE);
303 DWORD status=0;
304 if(!GetExitCodeProcess(pi.hProcess,&status) || *bAbort)
306 CTraceToOutputDebugString::Instance()(_T(__FUNCTION__) L": process %s finished, status code could not be fetched, (error %d; %s), aborted: %d\n", (LPCTSTR)cmdlist[i], GetLastError(), (LPCTSTR)CFormatMessageWrapper(), *bAbort);
308 EnsurePostMessage(pWnd, MSG_PROGRESSDLG_UPDATE_UI, MSG_PROGRESSDLG_FAILED, status);
309 return TGIT_GIT_ERROR_GET_EXIT_CODE;
311 CTraceToOutputDebugString::Instance()(_T(__FUNCTION__) L": process %s finished with code %d\n", (LPCTSTR)cmdlist[i], status);
312 ret |= status;
315 EnsurePostMessage(pWnd, MSG_PROGRESSDLG_UPDATE_UI, MSG_PROGRESSDLG_END, ret);
317 return ret;
320 UINT CProgressDlg::ProgressThread()
322 if (!m_GitCmd.IsEmpty())
323 m_GitCmdList.push_back(m_GitCmd);
325 CString *pfilename;
327 if(m_LogFile.IsEmpty())
328 pfilename = nullptr;
329 else
330 pfilename=&m_LogFile;
332 m_startTick = GetTickCount64();
333 m_GitStatus = RunCmdList(this, m_GitCmdList, m_GitDirList, m_bShowCommand, pfilename, &m_bAbort, &this->m_Databuf, m_Git);
334 return 0;
337 LRESULT CProgressDlg::OnProgressUpdateUI(WPARAM wParam,LPARAM lParam)
339 if(wParam == MSG_PROGRESSDLG_START)
341 m_BufStart = 0 ;
342 m_Animate.Play(0, INT_MAX, INT_MAX);
343 DialogEnableWindow(IDCANCEL, TRUE);
344 if (m_pTaskbarList)
346 m_pTaskbarList->SetProgressState(m_hWnd, TBPF_NORMAL);
347 m_pTaskbarList->SetProgressValue(m_hWnd, 0, 100);
350 if(wParam == MSG_PROGRESSDLG_END || wParam == MSG_PROGRESSDLG_FAILED)
352 CTraceToOutputDebugString::Instance()(_T(__FUNCTION__) L": got message: %d\n", wParam);
353 ULONGLONG tickSpent = GetTickCount64() - m_startTick;
354 CString strEndTime = CLoglistUtils::FormatDateAndTime(CTime::GetCurrentTime(), DATE_SHORTDATE, true, false);
356 if(m_bBufferAll)
358 m_Databuf.m_critSec.Lock();
359 m_Databuf.push_back(0);
360 m_Log.SetWindowText(Convert2UnionCode((char*)m_Databuf.data()));
361 m_Databuf.m_critSec.Unlock();
363 m_BufStart=0;
364 m_Databuf.m_critSec.Lock();
365 this->m_Databuf.clear();
366 m_Databuf.m_critSec.Unlock();
368 m_bDone = true;
369 m_Animate.Stop();
370 m_Progress.SetPos(100);
371 this->DialogEnableWindow(IDOK,TRUE);
373 m_GitStatus = (DWORD)lParam;
374 if (m_GitCmd.IsEmpty() && m_GitCmdList.empty())
375 m_GitStatus = (DWORD)-1;
377 // detect crashes of perl when performing git svn actions
378 if (m_GitStatus == 0 && m_GitCmd.Find(L" svn ") > 1)
380 CString log;
381 m_Log.GetWindowText(log);
382 if (log.GetLength() > 18 && log.Mid(log.GetLength() - 18) == L"perl.exe.stackdump")
383 m_GitStatus = (DWORD)-1;
386 if (m_PostExecCallback)
388 CString extraMsg;
389 m_PostExecCallback(m_GitStatus, extraMsg);
390 if (!extraMsg.IsEmpty())
392 int start = m_Log.GetTextLength();
393 m_Log.SetSel(start, start);
394 m_Log.ReplaceSel(extraMsg);
398 CString text;
399 m_Log.GetWindowText(text);
400 text.Remove('\r');
401 CAppUtils::StyleURLs(text, &m_Log);
403 if(this->m_GitStatus)
405 if (m_pTaskbarList)
407 m_pTaskbarList->SetProgressState(m_hWnd, TBPF_ERROR);
408 m_pTaskbarList->SetProgressValue(m_hWnd, 100, 100);
410 CString log;
411 log.Format(IDS_PROC_PROGRESS_GITUNCLEANEXIT, m_GitStatus);
412 CString err;
413 if (CRegDWORD(L"Software\\TortoiseGit\\ShowGitexeTimings", TRUE))
414 err.Format(L"\r\n\r\n%s (%I64u ms @ %s)\r\n", (LPCTSTR)log, tickSpent, (LPCTSTR)strEndTime);
415 else
416 err.Format(L"\r\n\r\n%s\r\n", (LPCTSTR)log);
417 if (!m_GitCmd.IsEmpty() || !m_GitCmdList.empty())
418 InsertColorText(this->m_Log, err, RGB(255,0,0));
419 if (CRegDWORD(L"Software\\TortoiseGit\\NoSounds", FALSE) == FALSE)
420 PlaySound((LPCTSTR)SND_ALIAS_SYSTEMEXCLAMATION, nullptr, SND_ALIAS_ID | SND_ASYNC);
422 else {
423 if (m_pTaskbarList)
424 m_pTaskbarList->SetProgressState(m_hWnd, TBPF_NOPROGRESS);
425 CString temp;
426 temp.LoadString(IDS_SUCCESS);
427 CString log;
428 if (CRegDWORD(L"Software\\TortoiseGit\\ShowGitexeTimings", TRUE))
429 log.Format(L"\r\n%s (%I64u ms @ %s)\r\n", (LPCTSTR)temp, tickSpent, (LPCTSTR)strEndTime);
430 else
431 log.Format(L"\r\n%s\r\n", (LPCTSTR)temp);
432 InsertColorText(this->m_Log, log, RGB(0,0,255));
433 this->DialogEnableWindow(IDCANCEL,FALSE);
436 m_Log.PostMessage(WM_VSCROLL, SB_BOTTOM, 0);
438 if (wParam == MSG_PROGRESSDLG_END)
440 if (m_PostCmdCallback) // new handling method using callback
442 m_PostCmdCallback(m_GitStatus, m_PostCmdList);
444 if (!m_PostCmdList.empty())
446 int i = 0;
447 for (const auto& entry : m_PostCmdList)
449 ++i;
450 m_ctrlPostCmd.AddEntry(entry.label, entry.icon);
451 TCHAR accellerator = CStringUtils::GetAccellerator(entry.label);
452 if (accellerator == L'\0')
453 continue;
454 ++m_accellerators[accellerator].cnt;
455 if (m_accellerators[accellerator].cnt > 1)
456 m_accellerators[accellerator].id = -1;
457 else
458 m_accellerators[accellerator].id = i - 1;
461 if (m_accellerators.size())
463 LPACCEL lpaccelNew = (LPACCEL)LocalAlloc(LPTR, m_accellerators.size() * sizeof(ACCEL));
464 SCOPE_EXIT { LocalFree(lpaccelNew); };
465 i = 0;
466 for (auto& entry : m_accellerators)
468 lpaccelNew[i].cmd = (WORD)(WM_USER + 1 + entry.second.id);
469 lpaccelNew[i].fVirt = FVIRTKEY | FALT;
470 lpaccelNew[i].key = entry.first;
471 entry.second.wmid = lpaccelNew[i].cmd;
472 ++i;
474 m_hAccel = CreateAcceleratorTable(lpaccelNew, (int)m_accellerators.size());
476 GetDlgItem(IDC_PROGRESS_BUTTON1)->ShowWindow(SW_SHOW);
481 if(wParam == MSG_PROGRESSDLG_END && m_GitStatus == 0)
483 if (m_AutoClose == AUTOCLOSE_IF_NO_OPTIONS && m_PostCmdList.empty() || m_AutoClose == AUTOCLOSE_IF_NO_ERRORS)
484 PostMessage(WM_COMMAND, 1, (LPARAM)GetDlgItem(IDOK)->m_hWnd);
488 if(!m_bBufferAll)
490 if(lParam == 0)
492 m_Databuf.m_critSec.Lock();
493 for (size_t i = this->m_BufStart; i < this->m_Databuf.size(); ++i)
495 char c = this->m_Databuf[m_BufStart];
496 ++m_BufStart;
497 m_Databuf.m_critSec.Unlock();
498 ParserCmdOutput(c);
500 m_Databuf.m_critSec.Lock();
503 if(m_BufStart>1000)
505 m_Databuf.erase(m_Databuf.cbegin(), m_Databuf.cbegin() + m_BufStart);
506 m_BufStart =0;
508 m_Databuf.m_critSec.Unlock();
511 else
512 ParserCmdOutput((char)lParam);
514 return 0;
517 //static function, Share with SyncDialog
518 int CProgressDlg::ParsePercentage(CString &log, int s1)
520 int s2=s1-1;
521 for(int i=s1-1;i>=0;i--)
523 if (log[i] >= L'0' && log[i] <= L'9')
524 s2=i;
525 else
526 break;
528 return _wtol(log.Mid(s2, s1 - s2));
531 void CProgressDlg::ParserCmdOutput(char ch)
533 ParserCmdOutput(this->m_Log,this->m_Progress,this->m_hWnd,this->m_pTaskbarList,this->m_LogTextA,ch,&this->m_CurrentWork);
535 void CProgressDlg::ClearESC(CString &str)
537 // see http://ascii-table.com/ansi-escape-sequences.php and http://tldp.org/HOWTO/Bash-Prompt-HOWTO/c327.html
538 str.Replace(L"\033[K", L""); // erase until end of line; no need to care for this, because we always clear the whole line
540 // drop colors
541 while (true)
543 int escapePosition = str.Find(L'\033');
544 if (escapePosition >= 0 && str.GetLength() >= escapePosition + 3)
546 if (str.Mid(escapePosition, 2) == L"\033[")
548 int colorEnd = str.Find(L'm', escapePosition + 2);
549 if (colorEnd > 0)
551 bool found = true;
552 for (int i = escapePosition + 2; i < colorEnd; ++i)
554 if (str[i] != L';' && (str[i] < L'0' && str[i] > L'9'))
556 found = false;
557 break;
560 if (found)
562 if (escapePosition > 0)
563 str = str.Left(escapePosition) + str.Mid(colorEnd + 1);
564 else
565 str = str.Mid(colorEnd);
566 continue;
571 break;
574 void CProgressDlg::ParserCmdOutput(CRichEditCtrl &log,CProgressCtrl &progressctrl,HWND m_hWnd,CComPtr<ITaskbarList3> m_pTaskbarList,CStringA &oneline, char ch, CWnd *CurrentWork)
576 //TRACE(L"%c",ch);
577 if( ch == ('\r') || ch == ('\n'))
579 CString str = CUnicodeUtils::GetUnicode(oneline);
581 // TRACE(L"End Char %s \r\n", ch == L'\r' ? L"lf" : L"");
582 // TRACE(L"End Char %s \r\n", ch == L'\n' ? L"cr" : L"");
584 int lines = log.GetLineCount();
585 str.Trim();
586 // TRACE(L"%s", str);
588 ClearESC(str);
590 if(ch == ('\r'))
592 int start=log.LineIndex(lines-1);
593 log.SetSel(start, log.GetTextLength());
594 log.ReplaceSel(str);
596 else
598 int length = log.GetWindowTextLength();
599 log.SetSel(length, length);
600 if (length > 0)
601 log.ReplaceSel(L"\r\n" + str);
602 else
603 log.ReplaceSel(str);
606 if (lines > s_iProgressLinesLimit) //limited log length
608 int end=log.LineIndex(1);
609 log.SetSel(0,end);
610 log.ReplaceSel(L"");
612 log.PostMessage(WM_VSCROLL, SB_BOTTOM, 0);
614 int s1 = str.ReverseFind(L':');
615 int s2 = str.Find(L'%');
616 if (s1 > 0 && s2 > 0)
618 if(CurrentWork)
619 CurrentWork->SetWindowTextW(str.Left(s1));
621 int pos = ParsePercentage(str, s2);
622 TRACE(L"Pos %d\r\n", pos);
623 if(pos>0)
625 progressctrl.SetPos(pos);
626 if (m_pTaskbarList)
628 m_pTaskbarList->SetProgressState(m_hWnd, TBPF_NORMAL);
629 m_pTaskbarList->SetProgressValue(m_hWnd, pos, 100);
634 oneline.Empty();
636 else
637 oneline+=ch;
639 void CProgressDlg::RemoveLastLine(CString &str)
641 int start = str.ReverseFind(L'\n');
642 if(start>0)
643 str=str.Left(start);
645 // CProgressDlg message handlers
647 void CProgressDlg::WriteLog() const
649 CLogFile logfile(g_Git.m_CurrentDir);
650 if (logfile.Open())
652 logfile.AddTimeLine();
653 CString text = GetLogText();
654 LPCTSTR psz_string = text;
655 while (*psz_string)
657 size_t i_len = wcscspn(psz_string, L"\r\n");
658 logfile.AddLine(CString(psz_string, (int)i_len));
659 psz_string += i_len;
660 if (*psz_string == '\r')
662 ++psz_string;
663 if (*psz_string == '\n')
664 ++psz_string;
666 else if (*psz_string == '\n')
667 ++psz_string;
669 if (m_bAbort)
671 CString canceled;
672 canceled.LoadString(IDS_USERCANCELLED);
673 logfile.AddLine(canceled);
675 logfile.Close();
679 void CProgressDlg::OnBnClickedOk()
681 if (m_pThread) // added here because Close-button is "called" from thread by PostMessage
682 ::WaitForSingleObject(m_pThread->m_hThread, 5000);
683 m_Log.GetWindowText(this->m_LogText);
684 WriteLog();
685 OnOK();
688 void CProgressDlg::OnBnClickedButton1()
690 WriteLog();
691 ShowWindow(SW_HIDE);
692 m_PostCmdList.at(m_ctrlPostCmd.GetCurrentEntry()).action();
693 EndDialog(IDOK);
696 void CProgressDlg::OnClose()
698 DialogEnableWindow(IDCANCEL, TRUE);
699 __super::OnClose();
702 void CProgressDlg::OnCancel()
704 CTraceToOutputDebugString::Instance()(_T(__FUNCTION__) L": User canceled\n");
705 m_bAbort = true;
706 if(m_bDone)
708 WriteLog();
709 CResizableStandAloneDialog::OnCancel();
710 return;
713 if( m_Git->m_CurrentGitPi.hProcess )
715 DWORD dwConfirmKillProcess = CRegDWORD(L"Software\\TortoiseGit\\ConfirmKillProcess");
716 if (dwConfirmKillProcess && CMessageBox::Show(m_hWnd, IDS_PROC_CONFIRMKILLPROCESS, IDS_APPNAME, MB_YESNO | MB_ICONQUESTION) != IDYES)
717 return;
718 if(::GenerateConsoleCtrlEvent(CTRL_C_EVENT,0))
720 ::WaitForSingleObject(m_Git->m_CurrentGitPi.hProcess ,10000);
723 KillProcessTree(m_Git->m_CurrentGitPi.dwProcessId);
726 ::WaitForSingleObject(m_Git->m_CurrentGitPi.hProcess ,10000);
727 if (m_pThread)
729 if (::WaitForSingleObject(m_pThread->m_hThread, 5000) == WAIT_TIMEOUT)
730 g_Git.KillRelatedThreads(m_pThread);
733 WriteLog();
734 CResizableStandAloneDialog::OnCancel();
737 void CProgressDlg::KillProcessTree(DWORD dwProcessId, unsigned int depth)
739 // recursively kills a process tree
740 // This is not optimized, but works and isn't called very often ;)
742 if (!dwProcessId || depth > 20)
743 return;
745 PROCESSENTRY32 pe = { 0 };
746 pe.dwSize = sizeof(PROCESSENTRY32);
748 CAutoGeneralHandle hSnap = ::CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
750 if (::Process32First(hSnap, &pe))
754 if (pe.th32ParentProcessID == dwProcessId)
755 KillProcessTree(pe.th32ProcessID, depth + 1);
756 } while (::Process32Next(hSnap, &pe));
758 CAutoGeneralHandle hProc = ::OpenProcess(PROCESS_TERMINATE, FALSE, dwProcessId);
759 if (hProc)
760 ::TerminateProcess(hProc, 1);
764 void CProgressDlg::InsertColorText(CRichEditCtrl &edit,CString text,COLORREF rgb)
766 CHARFORMAT old,cf;
767 edit.GetDefaultCharFormat(cf);
768 old=cf;
769 cf.dwMask|=CFM_COLOR;
770 cf.crTextColor=rgb;
771 cf.dwEffects|=CFE_BOLD;
772 cf.dwEffects &= ~CFE_AUTOCOLOR ;
773 edit.SetSel(edit.GetTextLength()-1,edit.GetTextLength());
774 edit.ReplaceSel(text);
775 edit.SetSel(edit.LineIndex(edit.GetLineCount()-2),edit.GetTextLength());
776 edit.SetSelectionCharFormat(cf);
777 edit.SetSel(edit.GetTextLength(),edit.GetTextLength());
778 edit.SetDefaultCharFormat(old);
781 CString CCommitProgressDlg::Convert2UnionCode(char *buff, int size)
783 int start=0;
784 if(size == -1)
785 size = (int)strlen(buff);
787 for (int i = 0; i < size; ++i)
789 if(buff[i] == ']')
790 start = i;
791 if( start >0 && buff[i] =='\n' )
793 start =i;
794 break;
798 CString str;
799 CGit::StringAppend(&str, (BYTE*)buff, g_Git.m_LogEncode, start);
800 CGit::StringAppend(&str, (BYTE*)buff + start, CP_UTF8, size - start);
802 ClearESC(str);
804 return str;
807 LRESULT CProgressDlg::OnTaskbarBtnCreated(WPARAM wParam, LPARAM lParam)
809 m_pTaskbarList.Release();
810 m_pTaskbarList.CoCreateInstance(CLSID_TaskbarList);
811 return __super::OnTaskbarButtonCreated(wParam, lParam);
814 BOOL CProgressDlg::PreTranslateMessage(MSG* pMsg)
816 if (m_hAccel && TranslateAccelerator(m_hWnd, m_hAccel, pMsg))
817 return TRUE;
818 if (pMsg->message == WM_KEYDOWN)
820 if (pMsg->wParam == VK_ESCAPE)
822 // pressing the ESC key should close the dialog. But since we disabled the escape
823 // key (so the user doesn't get the idea that he could simply undo an e.g. update)
824 // this won't work.
825 // So if the user presses the ESC key, change it to VK_RETURN so the dialog gets
826 // the impression that the OK button was pressed.
827 if ((!GetDlgItem(IDCANCEL)->IsWindowEnabled())
828 &&(GetDlgItem(IDOK)->IsWindowEnabled())&&(GetDlgItem(IDOK)->IsWindowVisible()))
830 // since we convert ESC to RETURN, make sure the OK button has the focus.
831 GetDlgItem(IDOK)->SetFocus();
832 // make sure the RETURN is not handled by the RichEdit
833 pMsg->hwnd = GetSafeHwnd();
834 pMsg->wParam = VK_RETURN;
838 else if (pMsg->message == WM_CONTEXTMENU || pMsg->message == WM_RBUTTONDOWN)
840 CWnd * pWnd = (CWnd*) GetDlgItem(IDC_LOG);
841 if (pWnd == GetFocus())
843 CIconMenu popup;
844 if (popup.CreatePopupMenu())
846 long start = -1, end = -1;
847 auto pEdit = (CRichEditCtrl *)GetDlgItem(IDC_LOG);
848 pEdit->GetSel(start, end);
849 popup.AppendMenuIcon(WM_COPY, IDS_SCIEDIT_COPY, IDI_COPYCLIP);
850 if (start >= end)
851 popup.EnableMenuItem(WM_COPY, MF_BYCOMMAND | MF_DISABLED | MF_GRAYED);
852 popup.AppendMenu(MF_SEPARATOR);
853 popup.AppendMenuIcon(EM_SETSEL, IDS_STATUSLIST_CONTEXT_COPYEXT, IDI_COPYCLIP);
854 int cmd = popup.TrackPopupMenu(TPM_RETURNCMD | TPM_LEFTALIGN | TPM_NONOTIFY, pMsg->pt.x, pMsg->pt.y, this);
855 switch (cmd)
857 case 0: // no command selected
858 break;
859 case EM_SETSEL:
861 pEdit->SetRedraw(FALSE);
862 int oldLine = pEdit->GetFirstVisibleLine();
863 pEdit->SetSel(0, -1);
864 pEdit->Copy();
865 pEdit->SetSel(start, end);
866 int newLine = pEdit->GetFirstVisibleLine();
867 pEdit->LineScroll(oldLine - newLine);
868 pEdit->SetRedraw(TRUE);
869 pEdit->RedrawWindow();
871 break;
872 case WM_COPY:
873 ::SendMessage(GetDlgItem(IDC_LOG)->GetSafeHwnd(), cmd, 0, -1);
874 break;
876 return TRUE;
880 return __super::PreTranslateMessage(pMsg);
883 LRESULT CProgressDlg::DefWindowProc(UINT message, WPARAM wParam, LPARAM lParam)
885 if (m_hAccel && message == WM_COMMAND && LOWORD(wParam) >= WM_USER && LOWORD(wParam) <= WM_USER + m_accellerators.size())
887 for (const auto& entry : m_accellerators)
889 if (entry.second.wmid != LOWORD(wParam))
890 continue;
891 if (entry.second.id == -1)
892 m_ctrlPostCmd.PostMessage(WM_KEYDOWN, VK_F4, NULL);
893 else
895 m_ctrlPostCmd.SetCurrentEntry(entry.second.id);
896 OnBnClickedButton1();
898 return 0;
902 return __super::DefWindowProc(message, wParam, lParam);
905 void CProgressDlg::OnEnLinkLog(NMHDR *pNMHDR, LRESULT *pResult)
907 // similar code in SyncDlg.cpp and LogDlg.cpp
908 ENLINK *pEnLink = reinterpret_cast<ENLINK *>(pNMHDR);
909 if ((pEnLink->msg == WM_LBUTTONUP) || (pEnLink->msg == WM_SETCURSOR))
911 CString msg;
912 GetDlgItemText(IDC_LOG, msg);
913 msg.Replace(L"\r\n", L"\n");
914 CString url = msg.Mid(pEnLink->chrg.cpMin, pEnLink->chrg.cpMax - pEnLink->chrg.cpMin);
915 // check if it's an email address
916 auto atpos = url.Find(L'@');
917 if ((atpos > 0) && (url.ReverseFind(L'.') > atpos) && !::PathIsURL(url))
918 url = L"mailto:" + url;
919 if (::PathIsURL(url))
921 if (pEnLink->msg == WM_LBUTTONUP)
922 ShellExecute(GetSafeHwnd(), L"open", url, nullptr, nullptr, SW_SHOWDEFAULT);
923 else
925 static RECT prevRect = { 0 };
926 CWnd* pMsgView = GetDlgItem(IDC_LOG);
927 if (pMsgView)
929 RECT rc;
930 POINTL pt;
931 pMsgView->SendMessage(EM_POSFROMCHAR, (WPARAM)&pt, pEnLink->chrg.cpMin);
932 rc.left = pt.x;
933 rc.top = pt.y;
934 pMsgView->SendMessage(EM_POSFROMCHAR, (WPARAM)&pt, pEnLink->chrg.cpMax);
935 rc.right = pt.x;
936 rc.bottom = pt.y + 12;
937 if ((prevRect.left != rc.left) || (prevRect.top != rc.top))
939 m_tooltips.DelTool(pMsgView, 1);
940 m_tooltips.AddTool(pMsgView, url, &rc, 1);
941 prevRect = rc;
947 *pResult = 0;
950 void CProgressDlg::OnEnscrollLog()
952 m_tooltips.DelTool(GetDlgItem(IDC_LOG), 1);