1
// TortoiseGit - a Windows shell extension for easy version control
3 // Copyright (C) 2008-2020 - 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, static_cast<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_iBufferAllImmediateLines(0)
56 , m_GitStatus(DWORD(-1))
58 int autoClose
= CRegDWORD(L
"Software\\TortoiseGit\\AutoCloseGitProgress", 0);
59 CCmdLineParser
parser(AfxGetApp()->m_lpCmdLine
);
60 if (parser
.HasKey(L
"closeonend"))
61 autoClose
= parser
.GetLongVal(L
"closeonend");
65 m_AutoClose
= AUTOCLOSE_IF_NO_OPTIONS
;
68 m_AutoClose
= AUTOCLOSE_IF_NO_ERRORS
;
71 m_AutoClose
= AUTOCLOSE_NO
;
76 CProgressDlg::~CProgressDlg()
79 DestroyAcceleratorTable(m_hAccel
);
83 void CProgressDlg::DoDataExchange(CDataExchange
* pDX
)
85 CDialog::DoDataExchange(pDX
);
86 DDX_Control(pDX
, IDC_CURRENT
, this->m_CurrentWork
);
87 DDX_Control(pDX
, IDC_TITLE_ANIMATE
, this->m_Animate
);
88 DDX_Control(pDX
, IDC_RUN_PROGRESS
, this->m_Progress
);
89 DDX_Control(pDX
, IDC_LOG
, this->m_Log
);
90 DDX_Control(pDX
, IDC_PROGRESS_BUTTON1
, this->m_ctrlPostCmd
);
93 BEGIN_MESSAGE_MAP(CProgressDlg
, CResizableStandAloneDialog
)
95 ON_MESSAGE(MSG_PROGRESSDLG_UPDATE_UI
, OnProgressUpdateUI
)
96 ON_BN_CLICKED(IDOK
, &CProgressDlg::OnBnClickedOk
)
97 ON_BN_CLICKED(IDC_PROGRESS_BUTTON1
, &CProgressDlg::OnBnClickedButton1
)
98 ON_REGISTERED_MESSAGE(TaskBarButtonCreated
, OnTaskbarBtnCreated
)
99 ON_NOTIFY(EN_LINK
, IDC_LOG
, OnEnLinkLog
)
100 ON_EN_VSCROLL(IDC_LOG
, OnEnscrollLog
)
101 ON_EN_HSCROLL(IDC_LOG
, OnEnscrollLog
)
104 BOOL
CProgressDlg::OnInitDialog()
106 CResizableStandAloneDialog::OnInitDialog();
108 // Let the TaskbarButtonCreated message through the UIPI filter. If we don't
109 // do this, Explorer would be unable to send that message to our window if we
110 // were running elevated. It's OK to make the call all the time, since if we're
111 // not elevated, this is a no-op.
112 CHANGEFILTERSTRUCT cfs
= { sizeof(CHANGEFILTERSTRUCT
) };
113 typedef BOOL STDAPICALLTYPE
ChangeWindowMessageFilterExDFN(HWND hWnd
, UINT message
, DWORD action
, PCHANGEFILTERSTRUCT pChangeFilterStruct
);
114 CAutoLibrary hUser
= AtlLoadSystemLibraryUsingFullPath(L
"user32.dll");
117 auto pfnChangeWindowMessageFilterEx
= reinterpret_cast<ChangeWindowMessageFilterExDFN
*>(GetProcAddress(hUser
, "ChangeWindowMessageFilterEx"));
118 if (pfnChangeWindowMessageFilterEx
)
119 pfnChangeWindowMessageFilterEx(m_hWnd
, TaskBarButtonCreated
, MSGFLT_ALLOW
, &cfs
);
121 m_pTaskbarList
.Release();
122 if (FAILED(m_pTaskbarList
.CoCreateInstance(CLSID_TaskbarList
)))
123 m_pTaskbarList
= nullptr;
125 AddAnchor(IDC_TITLE_ANIMATE
, TOP_LEFT
, TOP_RIGHT
);
126 AddAnchor(IDC_RUN_PROGRESS
, TOP_LEFT
, TOP_RIGHT
);
127 AddAnchor(IDC_LOG
, TOP_LEFT
, BOTTOM_RIGHT
);
129 AddAnchor(IDOK
, BOTTOM_RIGHT
);
130 AddAnchor(IDCANCEL
, BOTTOM_RIGHT
);
131 AddAnchor(IDC_PROGRESS_BUTTON1
, BOTTOM_LEFT
);
132 AddAnchor(IDC_CURRENT
, TOP_LEFT
);
134 this->GetDlgItem(IDC_PROGRESS_BUTTON1
)->ShowWindow(SW_HIDE
);
135 m_Animate
.Open(IDR_DOWNLOAD
);
137 SetupLogMessageViewControl();
140 if (!m_PreText
.IsEmpty())
141 InitialText
= m_PreText
+ L
"\r\n";
143 if (m_bShowCommand
&& (!m_GitCmd
.IsEmpty() ))
144 InitialText
+= m_GitCmd
+ L
"\r\n\r\n";
146 m_Log
.SetWindowTextW(InitialText
);
147 m_CurrentWork
.SetWindowText(L
"");
149 if (!m_PreFailText
.IsEmpty())
150 InsertColorText(this->m_Log
, m_PreFailText
, CTheme::Instance().IsDarkTheme() ? RGB(207, 47, 47) : RGB(255, 0, 0));
152 EnableSaveRestore(L
"ProgressDlg");
154 m_pThread
= AfxBeginThread(ProgressThreadEntry
, this, THREAD_PRIORITY_NORMAL
, 0, CREATE_SUSPENDED
);
157 CMessageBox::Show(this->m_hWnd
, IDS_ERR_THREADSTARTFAILED
, IDS_APPNAME
, MB_OK
| MB_ICONERROR
);
158 DialogEnableWindow(IDCANCEL
, TRUE
);
162 m_pThread
->m_bAutoDelete
= FALSE
;
163 m_pThread
->ResumeThread();
166 CString sWindowTitle
;
167 GetWindowText(sWindowTitle
);
168 CAppUtils::SetWindowTitle(m_hWnd
, m_Git
->m_CurrentDir
, sWindowTitle
);
170 // Make sure this dialog is shown in foreground (see issue #1536)
171 SetForegroundWindow();
176 static void EnsurePostMessage(CWnd
* pWnd
, UINT Msg
, WPARAM wParam
, LPARAM lParam
)
179 if (!pWnd
->PostMessage(Msg
, wParam
, lParam
))
181 if (GetLastError() == ERROR_NOT_ENOUGH_QUOTA
)
187 CTraceToOutputDebugString::Instance()(_T(__FUNCTION__
) L
": Message %d-%d could not be sent (error %d; %s)\n", wParam
, lParam
, GetLastError(), static_cast<LPCTSTR
>(CFormatMessageWrapper()));
191 UINT
CProgressDlg::ProgressThreadEntry(LPVOID pVoid
)
193 return static_cast<CProgressDlg
*>(pVoid
)->ProgressThread();
196 //static function, Share with SyncDialog
197 UINT
CProgressDlg::RunCmdList(CWnd
* pWnd
, STRING_VECTOR
& cmdlist
, STRING_VECTOR
& dirlist
, bool bShowCommand
, CString
* pfilename
, volatile bool* bAbort
, CGitGuardedByteArray
* pdata
, CGit
* git
)
201 std::vector
<std::unique_ptr
<CBlockCacheForPath
>> cacheBlockList
;
202 std::vector
<std::unique_ptr
<CGit
>> gitList
;
204 cacheBlockList
.push_back(std::make_unique
<CBlockCacheForPath
>(git
->m_CurrentDir
));
207 for (const auto& dir
: dirlist
)
209 auto pGit
= std::make_unique
<CGit
>();
210 pGit
->m_CurrentDir
= dir
;
211 gitList
.push_back(std::move(pGit
));
212 cacheBlockList
.push_back(std::make_unique
<CBlockCacheForPath
>(dir
));
216 EnsurePostMessage(pWnd
, MSG_PROGRESSDLG_UPDATE_UI
, MSG_PROGRESSDLG_START
, 0);
221 for (size_t i
= 0; i
< cmdlist
.size(); ++i
)
223 if (cmdlist
[i
].IsEmpty())
229 if (gitList
.empty() || gitList
.size() == 1 && gitList
[0]->m_CurrentDir
== git
->m_CurrentDir
)
230 str
= CUnicodeUtils::GetUTF8((i
> 0 ? L
"\r\n" : L
"") + cmdlist
[i
].Trim() + L
"\r\n");
232 str
= CUnicodeUtils::GetUTF8((i
> 0 ? L
"\r\n" : L
"") + gitList
[i
]->m_CurrentDir
+ L
"\r\n" + cmdlist
[i
].Trim() + L
"\r\n");
233 for (int j
= 0; j
< str
.GetLength(); ++j
)
237 pdata
->m_critSec
.Lock();
238 pdata
->push_back(str
[j
]);
239 pdata
->m_critSec
.Unlock();
242 pWnd
->PostMessage(MSG_PROGRESSDLG_UPDATE_UI
, MSG_PROGRESSDLG_RUN
, str
[j
]);
245 pWnd
->PostMessage(MSG_PROGRESSDLG_UPDATE_UI
, MSG_PROGRESSDLG_RUN
, 0);
248 PROCESS_INFORMATION pi
;
249 CAutoGeneralHandle hRead
;
250 int runAsyncRet
= -1;
252 runAsyncRet
= git
->RunAsync(cmdlist
[i
].Trim(), &pi
, hRead
.GetPointer(), nullptr, pfilename
);
254 runAsyncRet
= gitList
[i
]->RunAsync(cmdlist
[i
].Trim(), &pi
, hRead
.GetPointer(), nullptr, pfilename
);
257 EnsurePostMessage(pWnd
, MSG_PROGRESSDLG_UPDATE_UI
, MSG_PROGRESSDLG_FAILED
, -1 * runAsyncRet
);
261 CAutoGeneralHandle
piProcess(std::move(pi
.hProcess
));
262 CAutoGeneralHandle
piThread(std::move(pi
.hThread
));
264 char lastByte
= '\0';
267 while (ReadFile(hRead
, &byte
, 1, &readnumber
, nullptr))
274 pdata
->m_critSec
.Lock();
275 if (byte
== '\n' && lastByte
!= '\r')
276 pdata
->push_back('\r');
277 pdata
->push_back(byte
);
279 pdata
->m_critSec
.Unlock();
281 if (byte
== '\r' || byte
== '\n')
282 pWnd
->PostMessage(MSG_PROGRESSDLG_UPDATE_UI
, MSG_PROGRESSDLG_RUN
, 0);
285 pWnd
->PostMessage(MSG_PROGRESSDLG_UPDATE_UI
, MSG_PROGRESSDLG_RUN
, byte
);
289 pdata
->m_critSec
.Lock();
290 bool post
= !pdata
->empty();
291 pdata
->m_critSec
.Unlock();
293 EnsurePostMessage(pWnd
, MSG_PROGRESSDLG_UPDATE_UI
, MSG_PROGRESSDLG_RUN
, 0);
296 CTraceToOutputDebugString::Instance()(_T(__FUNCTION__
) L
": waiting for process to finish (%s), aborted: %d\n", static_cast<LPCTSTR
>(cmdlist
[i
]), *bAbort
);
298 WaitForSingleObject(pi
.hProcess
, INFINITE
);
301 if (!GetExitCodeProcess(pi
.hProcess
, &status
) || *bAbort
)
303 CTraceToOutputDebugString::Instance()(_T(__FUNCTION__
) L
": process %s finished, status code could not be fetched, (error %d; %s), aborted: %d\n", static_cast<LPCTSTR
>(cmdlist
[i
]), GetLastError(), static_cast<LPCTSTR
>(CFormatMessageWrapper()), *bAbort
);
305 EnsurePostMessage(pWnd
, MSG_PROGRESSDLG_UPDATE_UI
, MSG_PROGRESSDLG_FAILED
, status
);
306 return TGIT_GIT_ERROR_GET_EXIT_CODE
;
308 CTraceToOutputDebugString::Instance()(_T(__FUNCTION__
) L
": process %s finished with code %d\n", static_cast<LPCTSTR
>(cmdlist
[i
]), status
);
312 EnsurePostMessage(pWnd
, MSG_PROGRESSDLG_UPDATE_UI
, MSG_PROGRESSDLG_END
, ret
);
317 UINT
CProgressDlg::ProgressThread()
319 if (!m_GitCmd
.IsEmpty())
320 m_GitCmdList
.push_back(m_GitCmd
);
324 if (m_LogFile
.IsEmpty())
327 pfilename
= &m_LogFile
;
329 m_startTick
= GetTickCount64();
330 m_GitStatus
= RunCmdList(this, m_GitCmdList
, m_GitDirList
, m_bShowCommand
, pfilename
, &m_bAbort
, &this->m_Databuf
, m_Git
);
334 LRESULT
CProgressDlg::OnProgressUpdateUI(WPARAM wParam
, LPARAM lParam
)
336 if (wParam
== MSG_PROGRESSDLG_START
)
339 if (CRegDWORD(L
"Software\\TortoiseGit\\DownloadAnimation", TRUE
) == TRUE
)
340 m_Animate
.Play(0, INT_MAX
, INT_MAX
);
341 DialogEnableWindow(IDCANCEL
, TRUE
);
344 m_pTaskbarList
->SetProgressState(m_hWnd
, TBPF_NORMAL
);
345 m_pTaskbarList
->SetProgressValue(m_hWnd
, 0, 100);
348 if (wParam
== MSG_PROGRESSDLG_END
|| wParam
== MSG_PROGRESSDLG_FAILED
)
350 CTraceToOutputDebugString::Instance()(_T(__FUNCTION__
) L
": got message: %d\n", wParam
);
351 ULONGLONG tickSpent
= GetTickCount64() - m_startTick
;
352 CString strEndTime
= CLoglistUtils::FormatDateAndTime(CTime::GetCurrentTime(), DATE_SHORTDATE
, true, false);
356 m_Databuf
.m_critSec
.Lock();
357 m_Databuf
.push_back(0);
358 m_Log
.SetWindowText(Convert2UnionCode(reinterpret_cast<char*>(m_Databuf
.data())));
359 m_Databuf
.m_critSec
.Unlock();
362 m_Databuf
.m_critSec
.Lock();
363 this->m_Databuf
.clear();
364 m_Databuf
.m_critSec
.Unlock();
367 m_Progress
.SetPos(100);
368 this->DialogEnableWindow(IDOK
, TRUE
);
370 m_GitStatus
= static_cast<DWORD
>(lParam
);
371 if (m_GitCmd
.IsEmpty() && m_GitCmdList
.empty())
372 m_GitStatus
= DWORD(-1);
374 // detect crashes of perl when performing git svn actions
375 if (m_GitStatus
== 0 && m_GitCmd
.Find(L
" svn ") > 1)
378 m_Log
.GetWindowText(log
);
379 if (log
.GetLength() > 18 && log
.Mid(log
.GetLength() - 18) == L
"perl.exe.stackdump")
380 m_GitStatus
= DWORD(-1);
383 if (m_PostExecCallback
)
386 m_PostExecCallback(m_GitStatus
, extraMsg
);
387 if (!extraMsg
.IsEmpty())
389 int start
= m_Log
.GetTextLength();
390 m_Log
.SetSel(start
, start
);
391 m_Log
.ReplaceSel(extraMsg
);
396 m_Log
.GetWindowText(text
);
398 CAppUtils::StyleURLs(text
, &m_Log
);
401 if (m_Animate
.IsPlaying() && !m_GitStatus
)
402 m_Animate
.Play(28, 29, 1); // IDR_DOWNLOAD Frame No. Range 0 ~ 29 (Memo: -1 does not work for last frame, but 32767(WORD_MAX/2) does.)
406 if (this->m_GitStatus
)
408 m_Progress
.SetState(PBST_ERROR
);
411 m_pTaskbarList
->SetProgressState(m_hWnd
, TBPF_ERROR
);
412 m_pTaskbarList
->SetProgressValue(m_hWnd
, 100, 100);
415 log
.Format(IDS_PROC_PROGRESS_GITUNCLEANEXIT
, m_GitStatus
);
417 if (CRegDWORD(L
"Software\\TortoiseGit\\ShowGitexeTimings", TRUE
))
418 err
.Format(L
"\r\n\r\n%s (%I64u ms @ %s)\r\n", static_cast<LPCTSTR
>(log
), tickSpent
, static_cast<LPCTSTR
>(strEndTime
));
420 err
.Format(L
"\r\n\r\n%s\r\n", static_cast<LPCTSTR
>(log
));
421 if (!m_GitCmd
.IsEmpty() || !m_GitCmdList
.empty())
422 InsertColorText(this->m_Log
, err
, CTheme::Instance().IsDarkTheme() ? RGB(207, 47, 47) : RGB(255, 0, 0));
423 if (CRegDWORD(L
"Software\\TortoiseGit\\NoSounds", FALSE
) == FALSE
)
424 PlaySound(reinterpret_cast<LPCTSTR
>(SND_ALIAS_SYSTEMEXCLAMATION
), nullptr, SND_ALIAS_ID
| SND_ASYNC
);
428 m_pTaskbarList
->SetProgressState(m_hWnd
, TBPF_NOPROGRESS
);
430 temp
.LoadString(IDS_SUCCESS
);
432 if (CRegDWORD(L
"Software\\TortoiseGit\\ShowGitexeTimings", TRUE
))
433 log
.Format(L
"\r\n%s (%I64u ms @ %s)\r\n", static_cast<LPCTSTR
>(temp
), tickSpent
, static_cast<LPCTSTR
>(strEndTime
));
435 log
.Format(L
"\r\n%s\r\n", static_cast<LPCTSTR
>(temp
));
436 if (CTheme::Instance().IsHighContrastMode())
437 InsertColorText(this->m_Log
, log
, ::GetSysColor(COLOR_WINDOWTEXT
));
439 InsertColorText(this->m_Log
, log
, CTheme::Instance().IsDarkTheme() ? RGB(0, 178, 255) : RGB(0, 0, 255));
440 this->DialogEnableWindow(IDCANCEL
, FALSE
);
443 m_Log
.PostMessage(WM_VSCROLL
, SB_BOTTOM
, 0);
445 if (wParam
== MSG_PROGRESSDLG_END
)
447 if (m_PostCmdCallback
) // new handling method using callback
449 m_PostCmdCallback(m_GitStatus
, m_PostCmdList
);
451 if (!m_PostCmdList
.empty())
454 for (const auto& entry
: m_PostCmdList
)
457 m_ctrlPostCmd
.AddEntry(entry
.label
, entry
.icon
);
458 TCHAR accellerator
= CStringUtils::GetAccellerator(entry
.label
);
459 if (accellerator
== L
'\0')
461 ++m_accellerators
[accellerator
].cnt
;
462 if (m_accellerators
[accellerator
].cnt
> 1)
463 m_accellerators
[accellerator
].id
= -1;
465 m_accellerators
[accellerator
].id
= i
- 1;
468 if (m_accellerators
.size())
470 auto lpaccelNew
= static_cast<LPACCEL
>(LocalAlloc(LPTR
, m_accellerators
.size() * sizeof(ACCEL
)));
471 SCOPE_EXIT
{ LocalFree(lpaccelNew
); };
473 for (auto& entry
: m_accellerators
)
475 lpaccelNew
[i
].cmd
= static_cast<WORD
>(WM_USER
+ 1 + entry
.second
.id
);
476 lpaccelNew
[i
].fVirt
= FVIRTKEY
| FALT
;
477 lpaccelNew
[i
].key
= entry
.first
;
478 entry
.second
.wmid
= lpaccelNew
[i
].cmd
;
481 m_hAccel
= CreateAcceleratorTable(lpaccelNew
, static_cast<int>(m_accellerators
.size()));
483 GetDlgItem(IDC_PROGRESS_BUTTON1
)->ShowWindow(SW_SHOW
);
488 if (wParam
== MSG_PROGRESSDLG_END
&& m_GitStatus
== 0)
490 if (m_AutoClose
== AUTOCLOSE_IF_NO_OPTIONS
&& m_PostCmdList
.empty() || m_AutoClose
== AUTOCLOSE_IF_NO_ERRORS
)
491 PostMessage(WM_COMMAND
, 1, reinterpret_cast<LPARAM
>(GetDlgItem(IDOK
)->m_hWnd
));
495 #define IMMEDIATELINES_LIMIT 10
496 if (!m_bBufferAll
|| m_iBufferAllImmediateLines
< IMMEDIATELINES_LIMIT
)
500 m_Databuf
.m_critSec
.Lock();
501 for (size_t i
= this->m_BufStart
; i
< this->m_Databuf
.size(); ++i
)
503 char c
= this->m_Databuf
[m_BufStart
];
505 m_Databuf
.m_critSec
.Unlock();
507 if (m_bBufferAll
&& (c
== '\r' || c
== '\n'))
509 ++m_iBufferAllImmediateLines
;
510 if (m_iBufferAllImmediateLines
>= IMMEDIATELINES_LIMIT
)
514 m_Databuf
.m_critSec
.Lock();
517 if(m_BufStart
> 1000)
519 m_Databuf
.erase(m_Databuf
.cbegin(), m_Databuf
.cbegin() + m_BufStart
);
522 m_Databuf
.m_critSec
.Unlock();
526 ParserCmdOutput(static_cast<char>(lParam
));
531 //static function, Share with SyncDialog
532 int CProgressDlg::ParsePercentage(CString
& log
, int s1
)
535 for (int i
= s1
- 1; i
>= 0; i
--)
537 if (log
[i
] >= L
'0' && log
[i
] <= L
'9')
542 return _wtol(log
.Mid(s2
, s1
- s2
));
545 void CProgressDlg::ParserCmdOutput(char ch
)
547 ParserCmdOutput(this->m_Log
, this->m_Progress
, this->m_hWnd
, this->m_pTaskbarList
, this->m_LogTextA
, ch
, &this->m_CurrentWork
);
549 void CProgressDlg::ClearESC(CString
& str
)
551 // see http://ascii-table.com/ansi-escape-sequences.php and http://tldp.org/HOWTO/Bash-Prompt-HOWTO/c327.html
552 str
.Replace(L
"\033[K", L
""); // erase until end of line; no need to care for this, because we always clear the whole line
557 int escapePosition
= str
.Find(L
'\033');
558 if (escapePosition
>= 0 && str
.GetLength() >= escapePosition
+ 3)
560 if (str
.Mid(escapePosition
, 2) == L
"\033[")
562 int colorEnd
= str
.Find(L
'm', escapePosition
+ 2);
566 for (int i
= escapePosition
+ 2; i
< colorEnd
; ++i
)
568 if (str
[i
] != L
';' && (str
[i
] < L
'0' && str
[i
] > L
'9'))
576 if (escapePosition
> 0)
577 str
= str
.Left(escapePosition
) + str
.Mid(colorEnd
+ 1);
579 str
= str
.Mid(colorEnd
);
588 void CProgressDlg::ParserCmdOutput(CRichEditCtrl
& log
, CProgressCtrl
& progressctrl
, HWND m_hWnd
, CComPtr
<ITaskbarList3
> m_pTaskbarList
, CStringA
& oneline
, char ch
, CWnd
* CurrentWork
)
591 if (ch
== ('\r') || ch
== ('\n'))
593 CString str
= CUnicodeUtils::GetUnicode(oneline
);
595 // TRACE(L"End Char %s \r\n", ch == L'\r' ? L"lf" : L"");
596 // TRACE(L"End Char %s \r\n", ch == L'\n' ? L"cr" : L"");
598 int lines
= log
.GetLineCount();
600 // TRACE(L"%s", str);
606 int start
= log
.LineIndex(lines
- 1);
607 log
.SetSel(start
, log
.GetTextLength());
612 int length
= log
.GetWindowTextLength();
613 log
.SetSel(length
, length
);
615 log
.ReplaceSel(L
"\r\n" + str
);
620 if (lines
> s_iProgressLinesLimit
) //limited log length
622 int end
= log
.LineIndex(1);
626 log
.PostMessage(WM_VSCROLL
, SB_BOTTOM
, 0);
628 int s1
= str
.ReverseFind(L
':');
629 int s2
= str
.Find(L
'%');
630 if (s1
> 0 && s2
> 0)
633 CurrentWork
->SetWindowTextW(str
.Left(s1
));
635 int pos
= ParsePercentage(str
, s2
);
636 TRACE(L
"Pos %d\r\n", pos
);
639 progressctrl
.SetPos(pos
);
642 m_pTaskbarList
->SetProgressState(m_hWnd
, TBPF_NORMAL
);
643 m_pTaskbarList
->SetProgressValue(m_hWnd
, pos
, 100);
653 // CProgressDlg message handlers
655 void CProgressDlg::WriteLog() const
657 CLogFile
logfile(g_Git
.m_CurrentDir
);
660 logfile
.AddTimeLine();
661 CString text
= GetLogText();
662 LPCTSTR psz_string
= text
;
665 size_t i_len
= wcscspn(psz_string
, L
"\r\n");
666 logfile
.AddLine(CString(psz_string
, static_cast<int>(i_len
)));
668 if (*psz_string
== '\r')
671 if (*psz_string
== '\n')
674 else if (*psz_string
== '\n')
680 canceled
.LoadString(IDS_USERCANCELLED
);
681 logfile
.AddLine(canceled
);
687 void CProgressDlg::OnBnClickedOk()
689 if (m_pThread
) // added here because Close-button is "called" from thread by PostMessage
690 ::WaitForSingleObject(m_pThread
->m_hThread
, 5000);
691 m_Log
.GetWindowText(this->m_LogText
);
696 void CProgressDlg::OnBnClickedButton1()
700 m_PostCmdList
.at(m_ctrlPostCmd
.GetCurrentEntry()).action();
704 void CProgressDlg::OnClose()
706 DialogEnableWindow(IDCANCEL
, TRUE
);
710 void CProgressDlg::OnCancel()
712 CTraceToOutputDebugString::Instance()(_T(__FUNCTION__
) L
": User canceled\n");
717 CResizableStandAloneDialog::OnCancel();
721 if (m_Git
->m_CurrentGitPi
.hProcess
)
723 DWORD dwConfirmKillProcess
= CRegDWORD(L
"Software\\TortoiseGit\\ConfirmKillProcess");
724 if (dwConfirmKillProcess
&& CMessageBox::Show(m_hWnd
, IDS_PROC_CONFIRMKILLPROCESS
, IDS_APPNAME
, MB_YESNO
| MB_ICONQUESTION
) != IDYES
)
726 if (::GenerateConsoleCtrlEvent(CTRL_C_EVENT
, 0))
728 ::WaitForSingleObject(m_Git
->m_CurrentGitPi
.hProcess
, 10000);
731 KillProcessTree(m_Git
->m_CurrentGitPi
.dwProcessId
);
734 ::WaitForSingleObject(m_Git
->m_CurrentGitPi
.hProcess
, 10000);
737 if (::WaitForSingleObject(m_pThread
->m_hThread
, 5000) == WAIT_TIMEOUT
)
738 g_Git
.KillRelatedThreads(m_pThread
);
742 CResizableStandAloneDialog::OnCancel();
745 void CProgressDlg::KillProcessTree(DWORD dwProcessId
, unsigned int depth
)
747 // recursively kills a process tree
748 // This is not optimized, but works and isn't called very often ;)
750 if (!dwProcessId
|| depth
> 20)
753 PROCESSENTRY32 pe
= { 0 };
754 pe
.dwSize
= sizeof(PROCESSENTRY32
);
756 CAutoGeneralHandle hSnap
= ::CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS
, 0);
758 if (::Process32First(hSnap
, &pe
))
762 if (pe
.th32ParentProcessID
== dwProcessId
)
763 KillProcessTree(pe
.th32ProcessID
, depth
+ 1);
764 } while (::Process32Next(hSnap
, &pe
));
766 CAutoGeneralHandle hProc
= ::OpenProcess(PROCESS_TERMINATE
, FALSE
, dwProcessId
);
768 ::TerminateProcess(hProc
, 1);
772 void CProgressDlg::InsertColorText(CRichEditCtrl
& edit
, CString text
, COLORREF rgb
)
775 edit
.GetDefaultCharFormat(cf
);
777 cf
.dwMask
|= CFM_COLOR
;
778 cf
.crTextColor
= rgb
;
779 cf
.dwEffects
|= CFE_BOLD
;
780 cf
.dwEffects
&= ~CFE_AUTOCOLOR
;
781 edit
.SetSel(edit
.GetTextLength() - 1, edit
.GetTextLength());
782 edit
.ReplaceSel(text
);
783 edit
.SetSel(edit
.LineIndex(edit
.GetLineCount() - 2), edit
.GetTextLength());
784 edit
.SetSelectionCharFormat(cf
);
785 edit
.SetSel(edit
.GetTextLength(), edit
.GetTextLength());
786 edit
.SetDefaultCharFormat(old
);
789 CString
CCommitProgressDlg::Convert2UnionCode(char* buff
)
792 int size
= static_cast<int>(strlen(buff
));
795 if (g_Git
.m_LogEncode
!= CP_UTF8
)
797 // git.exe commit output begings with "[BRANCH] SUBJECT" whereas SUBJECT is encoded using m_LogEncode
798 for (int i
= 0; i
< size
; ++i
)
802 else if (start
> 0 && buff
[i
] == '\n')
809 CGit::StringAppend(&str
, buff
, g_Git
.m_LogEncode
, start
);
812 CGit::StringAppend(&str
, buff
+ start
, CP_UTF8
, size
- start
);
819 LRESULT
CProgressDlg::OnTaskbarBtnCreated(WPARAM wParam
, LPARAM lParam
)
821 m_pTaskbarList
.Release();
822 m_pTaskbarList
.CoCreateInstance(CLSID_TaskbarList
);
823 return __super::OnTaskbarButtonCreated(wParam
, lParam
);
826 BOOL
CProgressDlg::PreTranslateMessage(MSG
* pMsg
)
828 if (m_hAccel
&& TranslateAccelerator(m_hWnd
, m_hAccel
, pMsg
))
830 if (pMsg
->message
== WM_KEYDOWN
)
832 if (pMsg
->wParam
== VK_ESCAPE
)
834 // pressing the ESC key should close the dialog. But since we disabled the escape
835 // key (so the user doesn't get the idea that he could simply undo an e.g. update)
837 // So if the user presses the ESC key, change it to VK_RETURN so the dialog gets
838 // the impression that the OK button was pressed.
839 if ((!GetDlgItem(IDCANCEL
)->IsWindowEnabled())
840 &&(GetDlgItem(IDOK
)->IsWindowEnabled())&&(GetDlgItem(IDOK
)->IsWindowVisible()))
842 // since we convert ESC to RETURN, make sure the OK button has the focus.
843 GetDlgItem(IDOK
)->SetFocus();
844 // make sure the RETURN is not handled by the RichEdit
845 pMsg
->hwnd
= GetSafeHwnd();
846 pMsg
->wParam
= VK_RETURN
;
850 else if (pMsg
->message
== WM_CONTEXTMENU
|| pMsg
->message
== WM_RBUTTONDOWN
)
852 auto pWnd
= static_cast<CWnd
*>(GetDlgItem(IDC_LOG
));
853 if (pWnd
== GetFocus())
856 if (popup
.CreatePopupMenu())
858 long start
= -1, end
= -1;
859 auto pEdit
= static_cast<CRichEditCtrl
*>(GetDlgItem(IDC_LOG
));
860 pEdit
->GetSel(start
, end
);
861 popup
.AppendMenuIcon(WM_COPY
, IDS_SCIEDIT_COPY
, IDI_COPYCLIP
);
863 popup
.EnableMenuItem(WM_COPY
, MF_BYCOMMAND
| MF_DISABLED
| MF_GRAYED
);
864 popup
.AppendMenu(MF_SEPARATOR
);
865 popup
.AppendMenuIcon(EM_SETSEL
, IDS_STATUSLIST_CONTEXT_COPYEXT
, IDI_COPYCLIP
);
866 int cmd
= popup
.TrackPopupMenu(TPM_RETURNCMD
| TPM_LEFTALIGN
| TPM_NONOTIFY
, pMsg
->pt
.x
, pMsg
->pt
.y
, this);
869 case 0: // no command selected
873 pEdit
->SetRedraw(FALSE
);
874 int oldLine
= pEdit
->GetFirstVisibleLine();
875 pEdit
->SetSel(0, -1);
877 pEdit
->SetSel(start
, end
);
878 int newLine
= pEdit
->GetFirstVisibleLine();
879 pEdit
->LineScroll(oldLine
- newLine
);
880 pEdit
->SetRedraw(TRUE
);
881 pEdit
->RedrawWindow();
885 ::SendMessage(GetDlgItem(IDC_LOG
)->GetSafeHwnd(), cmd
, 0, -1);
892 return __super::PreTranslateMessage(pMsg
);
895 LRESULT
CProgressDlg::DefWindowProc(UINT message
, WPARAM wParam
, LPARAM lParam
)
897 if (m_hAccel
&& message
== WM_COMMAND
&& LOWORD(wParam
) >= WM_USER
&& LOWORD(wParam
) <= WM_USER
+ m_accellerators
.size())
899 for (const auto& entry
: m_accellerators
)
901 if (entry
.second
.wmid
!= LOWORD(wParam
))
903 if (entry
.second
.id
== -1)
904 m_ctrlPostCmd
.PostMessage(WM_KEYDOWN
, VK_F4
, NULL
);
907 m_ctrlPostCmd
.SetCurrentEntry(entry
.second
.id
);
908 OnBnClickedButton1();
914 return __super::DefWindowProc(message
, wParam
, lParam
);
917 void CProgressDlg::OnEnLinkLog(NMHDR
* pNMHDR
, LRESULT
* pResult
)
919 // similar code in SyncDlg.cpp and LogDlg.cpp
920 ENLINK
* pEnLink
= reinterpret_cast<ENLINK
*>(pNMHDR
);
921 if ((pEnLink
->msg
== WM_LBUTTONUP
) || (pEnLink
->msg
== WM_SETCURSOR
))
924 GetDlgItemText(IDC_LOG
, msg
);
925 msg
.Replace(L
"\r\n", L
"\n");
926 CString url
= msg
.Mid(pEnLink
->chrg
.cpMin
, pEnLink
->chrg
.cpMax
- pEnLink
->chrg
.cpMin
);
927 // check if it's an email address
928 auto atpos
= url
.Find(L
'@');
929 if ((atpos
> 0) && (url
.ReverseFind(L
'.') > atpos
) && !::PathIsURL(url
))
930 url
= L
"mailto:" + url
;
931 if (::PathIsURL(url
))
933 if (pEnLink
->msg
== WM_LBUTTONUP
)
934 ShellExecute(GetSafeHwnd(), L
"open", url
, nullptr, nullptr, SW_SHOWDEFAULT
);
937 static RECT prevRect
= { 0 };
938 CWnd
* pMsgView
= GetDlgItem(IDC_LOG
);
943 pMsgView
->SendMessage(EM_POSFROMCHAR
, reinterpret_cast<WPARAM
>(&pt
), pEnLink
->chrg
.cpMin
);
946 pMsgView
->SendMessage(EM_POSFROMCHAR
, reinterpret_cast<WPARAM
>(&pt
), pEnLink
->chrg
.cpMax
);
948 rc
.bottom
= pt
.y
+ 12;
949 if ((prevRect
.left
!= rc
.left
) || (prevRect
.top
!= rc
.top
))
951 m_tooltips
.DelTool(pMsgView
, 1);
952 m_tooltips
.AddTool(pMsgView
, url
, &rc
, 1);
962 void CProgressDlg::OnEnscrollLog()
964 m_tooltips
.DelTool(GetDlgItem(IDC_LOG
), 1);
967 void CProgressDlg::SetupLogMessageViewControl()
970 CAppUtils::CreateFontForLogs(logFont
);
971 m_Log
.SetFont(&logFont
);
972 // make the log message rich edit control send a message when the mouse pointer is over a link
973 m_Log
.SendMessage(EM_SETEVENTMASK
, NULL
, ENM_LINK
| ENM_SCROLL
);
975 CHARFORMAT2 format
= { 0 };
976 format
.cbSize
= sizeof(CHARFORMAT2
);
977 format
.dwMask
= CFM_COLOR
| CFM_BACKCOLOR
;
978 if (CTheme::Instance().IsDarkTheme())
980 format
.crTextColor
= CTheme::darkTextColor
;
981 format
.crBackColor
= CTheme::darkBkColor
;
985 format
.crTextColor
= ::GetSysColor(COLOR_WINDOWTEXT
);
986 format
.crBackColor
= ::GetSysColor(COLOR_WINDOW
);
988 m_Log
.SendMessage(EM_SETCHARFORMAT
, SCF_ALL
, reinterpret_cast<LPARAM
>(&format
));
989 m_Log
.SendMessage(EM_SETBKGNDCOLOR
, 0, format
.crBackColor
);