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
23 #include "TortoiseProc.h"
24 #include "ProgressDlg.h"
25 #include "UnicodeUtils.h"
27 #include "LoglistCommonResource.h"
30 #include "SmartHandle.h"
31 #include "../TGitCache/CacheInterface.h"
32 #include "LoglistUtils.h"
33 #include "MessageBox.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)
49 , m_startTick(GetTickCount64())
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");
64 m_AutoClose
= AUTOCLOSE_IF_NO_OPTIONS
;
67 m_AutoClose
= AUTOCLOSE_IF_NO_ERRORS
;
70 m_AutoClose
= AUTOCLOSE_NO
;
75 CProgressDlg::~CProgressDlg()
78 DestroyAcceleratorTable(m_hAccel
);
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
)
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
)
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");
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
);
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
);
143 if ( !m_PreText
.IsEmpty() )
144 InitialText
= m_PreText
+ L
"\r\n";
146 if (m_bShowCommand
&& (!m_GitCmd
.IsEmpty() ))
147 InitialText
+= m_GitCmd
+ L
"\r\n\r\n";
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
);
160 CMessageBox::Show(this->m_hWnd
, IDS_ERR_THREADSTARTFAILED
, IDS_APPNAME
, MB_OK
| MB_ICONERROR
);
161 DialogEnableWindow(IDCANCEL
, TRUE
);
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();
179 static void EnsurePostMessage(CWnd
*pWnd
, UINT Msg
, WPARAM wParam
, LPARAM lParam
)
182 if (!pWnd
->PostMessage(Msg
, wParam
, lParam
))
184 if (GetLastError() == ERROR_NOT_ENOUGH_QUOTA
)
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
)
204 std::vector
<std::unique_ptr
<CBlockCacheForPath
>> cacheBlockList
;
205 std::vector
<std::unique_ptr
<CGit
>> gitList
;
207 cacheBlockList
.push_back(std::make_unique
<CBlockCacheForPath
>(git
->m_CurrentDir
));
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);
224 for (size_t i
= 0; i
< cmdlist
.size(); ++i
)
226 if(cmdlist
[i
].IsEmpty())
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
);
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
)
240 pdata
->m_critSec
.Lock();
241 pdata
->push_back(str
[j
]);
242 pdata
->m_critSec
.Unlock();
245 pWnd
->PostMessage(MSG_PROGRESSDLG_UPDATE_UI
,MSG_PROGRESSDLG_RUN
,str
[j
]);
248 pWnd
->PostMessage(MSG_PROGRESSDLG_UPDATE_UI
,MSG_PROGRESSDLG_RUN
,0);
251 PROCESS_INFORMATION pi
;
252 CAutoGeneralHandle hRead
;
253 int runAsyncRet
= -1;
255 runAsyncRet
= git
->RunAsync(cmdlist
[i
].Trim(), &pi
, hRead
.GetPointer(), nullptr, pfilename
);
257 runAsyncRet
= gitList
[i
]->RunAsync(cmdlist
[i
].Trim(), &pi
, hRead
.GetPointer(), nullptr, pfilename
);
260 EnsurePostMessage(pWnd
, MSG_PROGRESSDLG_UPDATE_UI
, MSG_PROGRESSDLG_FAILED
, -1 * runAsyncRet
);
264 CAutoGeneralHandle
piProcess(std::move(pi
.hProcess
));
265 CAutoGeneralHandle
piThread(std::move(pi
.hThread
));
267 char lastByte
= '\0';
270 while (ReadFile(hRead
, &byte
, 1, &readnumber
, nullptr))
277 pdata
->m_critSec
.Lock();
278 if (byte
== '\n' && lastByte
!= '\r')
279 pdata
->push_back('\r');
280 pdata
->push_back( byte
);
282 pdata
->m_critSec
.Unlock();
284 if(byte
== '\r' || byte
== '\n')
285 pWnd
->PostMessage(MSG_PROGRESSDLG_UPDATE_UI
,MSG_PROGRESSDLG_RUN
,0);
288 pWnd
->PostMessage(MSG_PROGRESSDLG_UPDATE_UI
,MSG_PROGRESSDLG_RUN
,byte
);
292 pdata
->m_critSec
.Lock();
293 bool post
= !pdata
->empty();
294 pdata
->m_critSec
.Unlock();
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
);
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
);
315 EnsurePostMessage(pWnd
, MSG_PROGRESSDLG_UPDATE_UI
, MSG_PROGRESSDLG_END
, ret
);
320 UINT
CProgressDlg::ProgressThread()
322 if (!m_GitCmd
.IsEmpty())
323 m_GitCmdList
.push_back(m_GitCmd
);
327 if(m_LogFile
.IsEmpty())
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
);
337 LRESULT
CProgressDlg::OnProgressUpdateUI(WPARAM wParam
,LPARAM lParam
)
339 if(wParam
== MSG_PROGRESSDLG_START
)
342 m_Animate
.Play(0, INT_MAX
, INT_MAX
);
343 DialogEnableWindow(IDCANCEL
, TRUE
);
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);
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();
364 m_Databuf
.m_critSec
.Lock();
365 this->m_Databuf
.clear();
366 m_Databuf
.m_critSec
.Unlock();
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)
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
)
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
);
399 m_Log
.GetWindowText(text
);
401 CAppUtils::StyleURLs(text
, &m_Log
);
403 if(this->m_GitStatus
)
407 m_pTaskbarList
->SetProgressState(m_hWnd
, TBPF_ERROR
);
408 m_pTaskbarList
->SetProgressValue(m_hWnd
, 100, 100);
411 log
.Format(IDS_PROC_PROGRESS_GITUNCLEANEXIT
, m_GitStatus
);
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
);
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
);
424 m_pTaskbarList
->SetProgressState(m_hWnd
, TBPF_NOPROGRESS
);
426 temp
.LoadString(IDS_SUCCESS
);
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
);
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())
447 for (const auto& entry
: m_PostCmdList
)
450 m_ctrlPostCmd
.AddEntry(entry
.label
, entry
.icon
);
451 TCHAR accellerator
= CStringUtils::GetAccellerator(entry
.label
);
452 if (accellerator
== L
'\0')
454 ++m_accellerators
[accellerator
].cnt
;
455 if (m_accellerators
[accellerator
].cnt
> 1)
456 m_accellerators
[accellerator
].id
= -1;
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
); };
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
;
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
);
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
];
497 m_Databuf
.m_critSec
.Unlock();
500 m_Databuf
.m_critSec
.Lock();
505 m_Databuf
.erase(m_Databuf
.cbegin(), m_Databuf
.cbegin() + m_BufStart
);
508 m_Databuf
.m_critSec
.Unlock();
512 ParserCmdOutput((char)lParam
);
517 //static function, Share with SyncDialog
518 int CProgressDlg::ParsePercentage(CString
&log
, int s1
)
521 for(int i
=s1
-1;i
>=0;i
--)
523 if (log
[i
] >= L
'0' && log
[i
] <= L
'9')
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
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);
552 for (int i
= escapePosition
+ 2; i
< colorEnd
; ++i
)
554 if (str
[i
] != L
';' && (str
[i
] < L
'0' && str
[i
] > L
'9'))
562 if (escapePosition
> 0)
563 str
= str
.Left(escapePosition
) + str
.Mid(colorEnd
+ 1);
565 str
= str
.Mid(colorEnd
);
574 void CProgressDlg::ParserCmdOutput(CRichEditCtrl
&log
,CProgressCtrl
&progressctrl
,HWND m_hWnd
,CComPtr
<ITaskbarList3
> m_pTaskbarList
,CStringA
&oneline
, char ch
, CWnd
*CurrentWork
)
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();
586 // TRACE(L"%s", str);
592 int start
=log
.LineIndex(lines
-1);
593 log
.SetSel(start
, log
.GetTextLength());
598 int length
= log
.GetWindowTextLength();
599 log
.SetSel(length
, length
);
601 log
.ReplaceSel(L
"\r\n" + str
);
606 if (lines
> s_iProgressLinesLimit
) //limited log length
608 int end
=log
.LineIndex(1);
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)
619 CurrentWork
->SetWindowTextW(str
.Left(s1
));
621 int pos
= ParsePercentage(str
, s2
);
622 TRACE(L
"Pos %d\r\n", pos
);
625 progressctrl
.SetPos(pos
);
628 m_pTaskbarList
->SetProgressState(m_hWnd
, TBPF_NORMAL
);
629 m_pTaskbarList
->SetProgressValue(m_hWnd
, pos
, 100);
639 void CProgressDlg::RemoveLastLine(CString
&str
)
641 int start
= str
.ReverseFind(L
'\n');
645 // CProgressDlg message handlers
647 void CProgressDlg::WriteLog() const
649 CLogFile
logfile(g_Git
.m_CurrentDir
);
652 logfile
.AddTimeLine();
653 CString text
= GetLogText();
654 LPCTSTR psz_string
= text
;
657 size_t i_len
= wcscspn(psz_string
, L
"\r\n");
658 logfile
.AddLine(CString(psz_string
, (int)i_len
));
660 if (*psz_string
== '\r')
663 if (*psz_string
== '\n')
666 else if (*psz_string
== '\n')
672 canceled
.LoadString(IDS_USERCANCELLED
);
673 logfile
.AddLine(canceled
);
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
);
688 void CProgressDlg::OnBnClickedButton1()
692 m_PostCmdList
.at(m_ctrlPostCmd
.GetCurrentEntry()).action();
696 void CProgressDlg::OnClose()
698 DialogEnableWindow(IDCANCEL
, TRUE
);
702 void CProgressDlg::OnCancel()
704 CTraceToOutputDebugString::Instance()(_T(__FUNCTION__
) L
": User canceled\n");
709 CResizableStandAloneDialog::OnCancel();
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
)
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);
729 if (::WaitForSingleObject(m_pThread
->m_hThread
, 5000) == WAIT_TIMEOUT
)
730 g_Git
.KillRelatedThreads(m_pThread
);
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)
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
);
760 ::TerminateProcess(hProc
, 1);
764 void CProgressDlg::InsertColorText(CRichEditCtrl
&edit
,CString text
,COLORREF rgb
)
767 edit
.GetDefaultCharFormat(cf
);
769 cf
.dwMask
|=CFM_COLOR
;
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
)
785 size
= (int)strlen(buff
);
787 for (int i
= 0; i
< size
; ++i
)
791 if( start
>0 && buff
[i
] =='\n' )
799 CGit::StringAppend(&str
, (BYTE
*)buff
, g_Git
.m_LogEncode
, start
);
800 CGit::StringAppend(&str
, (BYTE
*)buff
+ start
, CP_UTF8
, size
- start
);
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
))
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)
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())
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
);
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);
857 case 0: // no command selected
861 pEdit
->SetRedraw(FALSE
);
862 int oldLine
= pEdit
->GetFirstVisibleLine();
863 pEdit
->SetSel(0, -1);
865 pEdit
->SetSel(start
, end
);
866 int newLine
= pEdit
->GetFirstVisibleLine();
867 pEdit
->LineScroll(oldLine
- newLine
);
868 pEdit
->SetRedraw(TRUE
);
869 pEdit
->RedrawWindow();
873 ::SendMessage(GetDlgItem(IDC_LOG
)->GetSafeHwnd(), cmd
, 0, -1);
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
))
891 if (entry
.second
.id
== -1)
892 m_ctrlPostCmd
.PostMessage(WM_KEYDOWN
, VK_F4
, NULL
);
895 m_ctrlPostCmd
.SetCurrentEntry(entry
.second
.id
);
896 OnBnClickedButton1();
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
))
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
);
925 static RECT prevRect
= { 0 };
926 CWnd
* pMsgView
= GetDlgItem(IDC_LOG
);
931 pMsgView
->SendMessage(EM_POSFROMCHAR
, (WPARAM
)&pt
, pEnLink
->chrg
.cpMin
);
934 pMsgView
->SendMessage(EM_POSFROMCHAR
, (WPARAM
)&pt
, pEnLink
->chrg
.cpMax
);
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);
950 void CProgressDlg::OnEnscrollLog()
952 m_tooltips
.DelTool(GetDlgItem(IDC_LOG
), 1);