Make sure we also kill AsyncReadStdErrThread when we kill a thread
[TortoiseGit.git] / src / TortoiseProc / GitProgressList.cpp
blob9488568941c95763f075c259b3b1bd956277f441
1 // TortoiseGit - a Windows shell extension for easy version control
3 // Copyright (C) 2008-2016 - TortoiseGit
4 // Copyright (C) 2003-2008 - TortoiseSVN
6 // This program is free software; you can redistribute it and/or
7 // modify it under the terms of the GNU General Public License
8 // as published by the Free Software Foundation; either version 2
9 // of the License, or (at your option) any later version.
11 // This program is distributed in the hope that it will be useful,
12 // but WITHOUT ANY WARRANTY; without even the implied warranty of
13 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 // GNU General Public License for more details.
16 // You should have received a copy of the GNU General Public License
17 // along with this program; if not, write to the Free Software Foundation,
18 // 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
20 #include "stdafx.h"
21 #include "GitProgressList.h"
22 #include "TortoiseProc.h"
23 #include "registry.h"
24 #include "AppUtils.h"
25 #include "StringUtils.h"
26 #include "LogFile.h"
27 #include "LoglistUtils.h"
28 #include "AppUtils.h"
30 BOOL CGitProgressList::m_bAscending = FALSE;
31 int CGitProgressList::m_nSortedColumn = -1;
33 #define TRANSFERTIMER 100
34 #define VISIBLETIMER 101
35 // CGitProgressList
37 IMPLEMENT_DYNAMIC(CGitProgressList, CListCtrl)
39 CGitProgressList::CGitProgressList():CListCtrl()
40 , m_bCancelled(FALSE)
41 , m_pThread(nullptr)
42 , m_bErrorsOccurred(false)
43 , m_options(ProgOptNone)
44 , m_bSetTitle(false)
45 , m_pTaskbarList(nullptr)
46 , m_Command(nullptr)
47 , m_bThreadRunning(FALSE)
48 , iFirstResized(0)
49 , bSecondResized(false)
50 , nEnsureVisibleCount(0)
51 , m_TotalBytesTransferred(0)
52 , m_bFinishedItemAdded(false)
53 , m_bLastVisible(false)
54 , m_itemCount(-1)
55 , m_itemCountTotal(-1)
57 m_pInfoCtrl = nullptr;
58 m_pAnimate = nullptr;
59 m_pProgControl = nullptr;
60 m_pProgressLabelCtrl = nullptr;
61 m_pPostWnd = nullptr;
62 m_columnbuf[0] = 0;
65 CGitProgressList::~CGitProgressList()
67 for (size_t i = 0; i < m_arData.size(); ++i)
68 delete m_arData[i];
69 delete m_pThread;
73 BEGIN_MESSAGE_MAP(CGitProgressList, CListCtrl)
74 ON_NOTIFY_REFLECT(NM_CUSTOMDRAW, OnNMCustomdrawSvnprogress)
75 ON_NOTIFY_REFLECT(NM_DBLCLK, OnNMDblclkSvnprogress)
76 ON_NOTIFY_REFLECT(HDN_ITEMCLICK, OnHdnItemclickSvnprogress)
77 ON_NOTIFY_REFLECT(LVN_BEGINDRAG, OnLvnBegindragSvnprogress)
78 ON_NOTIFY_REFLECT(LVN_GETDISPINFO, OnLvnGetdispinfoSvnprogress)
79 ON_MESSAGE(WM_SHOWCONFLICTRESOLVER, OnShowConflictResolver)
80 ON_WM_SIZE()
81 ON_WM_TIMER()
82 ON_WM_CONTEXTMENU()
83 ON_WM_CLOSE()
84 END_MESSAGE_MAP()
86 void CGitProgressList::Cancel()
88 m_bCancelled = TRUE;
93 // CGitProgressList message handlers
96 LRESULT CGitProgressList::OnShowConflictResolver(WPARAM /*wParam*/, LPARAM /*lParam*/)
98 #if 0
99 CConflictResolveDlg dlg(this);
100 const svn_wc_conflict_description_t *description = (svn_wc_conflict_description_t *)lParam;
101 if (description)
103 dlg.SetConflictDescription(description);
104 if (m_pTaskbarList)
106 m_pTaskbarList->SetProgressState(m_hWnd, TBPF_PAUSED);
108 if (dlg.DoModal() == IDOK)
110 if (dlg.GetResult() == svn_wc_conflict_choose_postpone)
112 // if the result is conflicted and the dialog returned IDOK,
113 // that means we should not ask again in case of a conflict
114 m_AlwaysConflicted = true;
115 ::SendMessage(GetDlgItem(IDC_NONINTERACTIVE)->GetSafeHwnd(), BM_SETCHECK, BST_CHECKED, 0);
118 m_mergedfile = dlg.GetMergedFile();
119 m_bCancelled = dlg.IsCancelled();
120 if (m_pTaskbarList)
121 m_pTaskbarList->SetProgressState(m_hWnd, TBPF_INDETERMINATE);
122 return dlg.GetResult();
125 return svn_wc_conflict_choose_postpone;
126 #endif
127 return 0;
129 #if 0
130 svn_wc_conflict_choice_t CGitProgressList::ConflictResolveCallback(const svn_wc_conflict_description_t *description, CString& mergedfile)
132 // we only bother the user when merging
133 if (((m_Command == GitProgress_Merge)||(m_Command == GitProgress_MergeAll)||(m_Command == GitProgress_MergeReintegrate))&&(!m_AlwaysConflicted)&&(description))
135 // we're in a worker thread here. That means we must not show a dialog from the thread
136 // but let the UI thread do it.
137 // To do that, we send a message to the UI thread and let it show the conflict resolver dialog.
138 LRESULT dlgResult = ::SendMessage(GetSafeHwnd(), WM_SHOWCONFLICTRESOLVER, 0, (LPARAM)description);
139 mergedfile = m_mergedfile;
140 return (svn_wc_conflict_choice_t)dlgResult;
143 return svn_wc_conflict_choose_postpone;
145 #endif
146 void CGitProgressList::AddItemToList()
148 int totalcount = GetItemCount();
150 SetItemCountEx(totalcount+1, LVSICF_NOSCROLL|LVSICF_NOINVALIDATEALL);
151 // make columns width fit
152 if (iFirstResized < 30)
154 // only resize the columns for the first 30 or so entries.
155 // after that, don't resize them anymore because that's an
156 // expensive function call and the columns will be sized
157 // close enough already.
158 ResizeColumns();
159 ++iFirstResized;
162 // Make sure the item is *entirely* visible even if the horizontal
163 // scroll bar is visible.
164 int count = GetCountPerPage();
165 if (totalcount <= (GetTopIndex() + count + nEnsureVisibleCount + 2))
167 ++nEnsureVisibleCount;
168 m_bLastVisible = true;
170 else
172 nEnsureVisibleCount = 0;
173 if (IsIconic() == 0)
174 m_bLastVisible = false;
178 CString CGitProgressList::BuildInfoString()
180 CString infotext;
181 m_Command->ShowInfo(infotext);
183 #if 0
185 CString temp;
186 int added = 0;
187 int copied = 0;
188 int deleted = 0;
189 int restored = 0;
190 int reverted = 0;
191 int resolved = 0;
192 int conflicted = 0;
193 int updated = 0;
194 int merged = 0;
195 int modified = 0;
196 int skipped = 0;
197 int replaced = 0;
199 for (size_t i=0; i<m_arData.size(); ++i)
201 const NotificationData * dat = m_arData[i];
202 switch (dat->action)
204 case svn_wc_notify_add:
205 case svn_wc_notify_update_add:
206 case svn_wc_notify_commit_added:
207 if (dat->bConflictedActionItem)
208 ++conflicted;
209 else
210 ++added;
211 break;
212 case svn_wc_notify_copy:
213 ++copied;
214 break;
215 case svn_wc_notify_delete:
216 case svn_wc_notify_update_delete:
217 case svn_wc_notify_commit_deleted:
218 ++deleted;
219 break;
220 case svn_wc_notify_restore:
221 ++restored;
222 break;
223 case svn_wc_notify_revert:
224 ++reverted;
225 break;
226 case svn_wc_notify_resolved:
227 ++resolved;
228 break;
229 case svn_wc_notify_update_update:
230 if (dat->bConflictedActionItem)
231 ++conflicted;
232 else if ((dat->content_state == svn_wc_notify_state_merged) || (dat->prop_state == svn_wc_notify_state_merged))
233 ++merged;
234 else
235 ++updated;
236 break;
237 case svn_wc_notify_commit_modified:
238 ++modified;
239 break;
240 case svn_wc_notify_skip:
241 ++skipped;
242 break;
243 case svn_wc_notify_commit_replaced:
244 ++replaced;
245 break;
248 if (conflicted)
250 temp.LoadString(IDS_SVNACTION_CONFLICTED);
251 infotext += temp;
252 temp.Format(_T(":%d "), conflicted);
253 infotext += temp;
255 if (skipped)
257 temp.LoadString(IDS_SVNACTION_SKIP);
258 infotext += temp;
259 infotext.AppendFormat(_T(":%d "), skipped);
261 if (merged)
263 temp.LoadString(IDS_SVNACTION_MERGED);
264 infotext += temp;
265 infotext.AppendFormat(_T(":%d "), merged);
267 if (added)
269 temp.LoadString(IDS_SVNACTION_ADD);
270 infotext += temp;
271 infotext.AppendFormat(_T(":%d "), added);
273 if (deleted)
275 temp.LoadString(IDS_SVNACTION_DELETE);
276 infotext += temp;
277 infotext.AppendFormat(_T(":%d "), deleted);
279 if (modified)
281 temp.LoadString(IDS_SVNACTION_MODIFIED);
282 infotext += temp;
283 infotext.AppendFormat(_T(":%d "), modified);
285 if (copied)
287 temp.LoadString(IDS_SVNACTION_COPY);
288 infotext += temp;
289 infotext.AppendFormat(_T(":%d "), copied);
291 if (replaced)
293 temp.LoadString(IDS_SVNACTION_REPLACED);
294 infotext += temp;
295 infotext.AppendFormat(_T(":%d "), replaced);
297 if (updated)
299 temp.LoadString(IDS_SVNACTION_UPDATE);
300 infotext += temp;
301 infotext.AppendFormat(_T(":%d "), updated);
303 if (restored)
305 temp.LoadString(IDS_SVNACTION_RESTORE);
306 infotext += temp;
307 infotext.AppendFormat(_T(":%d "), restored);
309 if (reverted)
311 temp.LoadString(IDS_SVNACTION_REVERT);
312 infotext += temp;
313 infotext.AppendFormat(_T(":%d "), reverted);
315 if (resolved)
317 temp.LoadString(IDS_SVNACTION_RESOLVE);
318 infotext += temp;
319 infotext.AppendFormat(_T(":%d "), resolved);
321 #endif
322 return infotext;
325 void CGitProgressList::ResizeColumns()
327 SetRedraw(FALSE);
329 TCHAR textbuf[MAX_PATH] = {0};
331 CHeaderCtrl * pHeaderCtrl = (CHeaderCtrl*)(GetDlgItem(0));
332 if (pHeaderCtrl)
334 int maxcol = pHeaderCtrl->GetItemCount()-1;
335 for (int col = 0; col <= maxcol; ++col)
337 // find the longest width of all items
338 int count = min((int)m_arData.size(), GetItemCount());
339 HDITEM hdi = {0};
340 hdi.mask = HDI_TEXT;
341 hdi.pszText = textbuf;
342 hdi.cchTextMax = _countof(textbuf);
343 pHeaderCtrl->GetItem(col, &hdi);
344 int cx = GetStringWidth(hdi.pszText)+20; // 20 pixels for col separator and margin
346 for (int index = 0; index<count; ++index)
348 // get the width of the string and add 12 pixels for the column separator and margins
349 int linewidth = cx;
350 switch (col)
352 case 0:
353 linewidth = GetStringWidth(m_arData[index]->sActionColumnText) + 12;
354 break;
355 case 1:
356 linewidth = GetStringWidth(m_arData[index]->sPathColumnText) + 12;
357 break;
359 if (cx < linewidth)
360 cx = linewidth;
362 SetColumnWidth(col, cx);
366 SetRedraw(TRUE);
369 bool CGitProgressList::SetBackgroundImage(UINT nID)
371 return CAppUtils::SetListCtrlBackgroundImage(GetSafeHwnd(), nID);
374 void CGitProgressList::ReportGitError()
376 ReportError(CGit::GetLibGit2LastErr());
379 void CGitProgressList::ReportUserCanceled()
381 ReportError(CString(MAKEINTRESOURCE(IDS_USERCANCELLED)));
384 void CGitProgressList::ReportError(const CString& sError)
386 if (CRegDWORD(_T("Software\\TortoiseGit\\NoSounds"), FALSE) == FALSE)
387 PlaySound((LPCTSTR)SND_ALIAS_SYSTEMEXCLAMATION, nullptr, SND_ALIAS_ID | SND_ASYNC);
388 ReportString(sError, CString(MAKEINTRESOURCE(IDS_ERR_ERROR)), m_Colors.GetColor(CColors::Conflict));
389 m_bErrorsOccurred = true;
392 void CGitProgressList::ReportWarning(const CString& sWarning)
394 if (CRegDWORD(_T("Software\\TortoiseGit\\NoSounds"), FALSE) == FALSE)
395 PlaySound((LPCTSTR)SND_ALIAS_SYSTEMDEFAULT, nullptr, SND_ALIAS_ID | SND_ASYNC);
396 ReportString(sWarning, CString(MAKEINTRESOURCE(IDS_WARN_WARNING)), m_Colors.GetColor(CColors::Conflict));
399 void CGitProgressList::ReportNotification(const CString& sNotification)
401 if (CRegDWORD(_T("Software\\TortoiseGit\\NoSounds"), FALSE) == FALSE)
402 PlaySound((LPCTSTR)SND_ALIAS_SYSTEMDEFAULT, nullptr, SND_ALIAS_ID | SND_ASYNC);
403 ReportString(sNotification, CString(MAKEINTRESOURCE(IDS_WARN_NOTE)));
406 void CGitProgressList::ReportCmd(const CString& sCmd)
408 ReportString(sCmd, CString(MAKEINTRESOURCE(IDS_PROGRS_CMDINFO)), m_Colors.GetColor(CColors::Cmd));
411 void CGitProgressList::ReportString(CString sMessage, const CString& sMsgKind, COLORREF color)
413 // instead of showing a dialog box with the error message or notification,
414 // just insert the error text into the list control.
415 // that way the user isn't 'interrupted' by a dialog box popping up!
417 // the message may be split up into different lines
418 // so add a new entry for each line of the message
419 while (!sMessage.IsEmpty())
421 NotificationData * data = new NotificationData();
422 data->bAuxItem = true;
423 data->sActionColumnText = sMsgKind;
424 if (sMessage.Find('\n')>=0)
425 data->sPathColumnText = sMessage.Left(sMessage.Find('\n'));
426 else
427 data->sPathColumnText = sMessage;
428 data->sPathColumnText.Trim(_T("\n\r"));
429 data->color = color;
430 if (sMessage.Find('\n')>=0)
432 sMessage = sMessage.Mid(sMessage.Find('\n'));
433 sMessage.Trim(_T("\n\r"));
435 else
436 sMessage.Empty();
437 AddNotify(data);
441 UINT CGitProgressList::ProgressThreadEntry(LPVOID pVoid)
443 return ((CGitProgressList*)pVoid)->ProgressThread();
446 UINT CGitProgressList::ProgressThread()
448 // The SetParams function should have loaded something for us
450 CString temp;
451 CString sWindowTitle;
452 bool bSuccess = false;
454 if(m_pPostWnd)
455 m_pPostWnd->PostMessage(WM_PROG_CMD_START, (WPARAM)m_Command);
457 if(m_pProgressLabelCtrl)
459 m_pProgressLabelCtrl->ShowWindow(SW_SHOW);
460 m_pProgressLabelCtrl->SetWindowText(_T(""));
463 // SetAndClearProgressInfo(m_hWnd);
464 m_itemCount = m_itemCountTotal;
466 InterlockedExchange(&m_bThreadRunning, TRUE);
467 iFirstResized = 0;
468 bSecondResized = FALSE;
469 m_bFinishedItemAdded = false;
470 DWORD startTime = GetCurrentTime();
472 if (m_pTaskbarList && m_pPostWnd)
473 m_pTaskbarList->SetProgressState(m_pPostWnd->GetSafeHwnd(), TBPF_INDETERMINATE);
475 m_TotalBytesTransferred = 0;
476 if (m_Command)
477 bSuccess = m_Command->Run(this, sWindowTitle, m_itemCountTotal, m_itemCount);
478 else
479 bSuccess = false;
481 if (!bSuccess)
482 temp.LoadString(IDS_PROGRS_TITLEFAILED);
483 else
484 temp.LoadString(IDS_PROGRS_TITLEFIN);
485 sWindowTitle = sWindowTitle + _T(" ") + temp;
486 if (m_bSetTitle && m_pPostWnd)
487 ::SetWindowText(m_pPostWnd->GetSafeHwnd(), sWindowTitle);
489 KillTimer(TRANSFERTIMER);
490 KillTimer(VISIBLETIMER);
492 if (m_pTaskbarList && m_pPostWnd)
494 if (DidErrorsOccur())
496 m_pTaskbarList->SetProgressState(m_pPostWnd->GetSafeHwnd(), TBPF_ERROR);
497 m_pTaskbarList->SetProgressValue(m_pPostWnd->GetSafeHwnd(), 100, 100);
499 else
500 m_pTaskbarList->SetProgressState(m_pPostWnd->GetSafeHwnd(), TBPF_NOPROGRESS);
503 if (m_pInfoCtrl)
505 CString info;
506 if (!bSuccess)
507 info.LoadString(IDS_PROGRS_INFOFAILED);
508 else // this implies that command is not nullptr
509 info = BuildInfoString();
510 m_pInfoCtrl->SetWindowText(info);
513 ResizeColumns();
515 DWORD time = GetCurrentTime() - startTime;
517 CString sFinalInfo;
518 if (!m_sTotalBytesTransferred.IsEmpty())
520 temp.Format(IDS_PROGRS_TIME, (time / 1000) / 60, (time / 1000) % 60);
521 sFinalInfo.Format(IDS_PROGRS_FINALINFO, m_sTotalBytesTransferred, (LPCTSTR)temp);
522 if (m_pProgressLabelCtrl)
523 m_pProgressLabelCtrl->SetWindowText(sFinalInfo);
525 else
527 if (m_pProgressLabelCtrl)
528 m_pProgressLabelCtrl->ShowWindow(SW_HIDE);
531 if (m_pProgControl)
532 m_pProgControl->ShowWindow(SW_HIDE);
534 if (!m_bFinishedItemAdded)
536 CString log, str;
537 if (bSuccess)
538 str.LoadString(IDS_SUCCESS);
539 else
540 str.LoadString(IDS_FAIL);
541 log.Format(_T("%s (%lu ms @ %s)"), (LPCTSTR)str, time, (LPCTSTR)CLoglistUtils::FormatDateAndTime(CTime::GetCurrentTime(), DATE_SHORTDATE, true, false));
543 // there's no "finished: xxx" line at the end. We add one here to make
544 // sure the user sees that the command is actually finished.
545 ReportString(log, CString(MAKEINTRESOURCE(IDS_PROGRS_FINISHED)), bSuccess? RGB(0,0,255) : RGB(255,0,0));
548 int count = GetItemCount();
549 if ((count > 0)&&(m_bLastVisible))
550 EnsureVisible(count-1, FALSE);
552 CLogFile logfile(g_Git.m_CurrentDir);
553 if (logfile.Open())
555 logfile.AddTimeLine();
556 for (size_t i = 0; i < m_arData.size(); ++i)
558 NotificationData * data = m_arData[i];
559 temp.Format(_T("%-20s : %s"), (LPCTSTR)data->sActionColumnText, (LPCTSTR)data->sPathColumnText);
560 logfile.AddLine(temp);
562 if (!sFinalInfo.IsEmpty())
563 logfile.AddLine(sFinalInfo);
564 logfile.Close();
567 m_bCancelled = TRUE;
568 InterlockedExchange(&m_bThreadRunning, FALSE);
570 if (m_pPostWnd)
571 m_pPostWnd->PostMessage(WM_PROG_CMD_FINISH, (WPARAM)m_Command, 0L);
573 //Don't do anything here which might cause messages to be sent to the window
574 //The window thread is probably now blocked in OnOK if we've done an auto close
575 return 0;
578 void CGitProgressList::OnLvnGetdispinfoSvnprogress(NMHDR *pNMHDR, LRESULT *pResult)
580 NMLVDISPINFO *pDispInfo = reinterpret_cast<NMLVDISPINFO*>(pNMHDR);
582 if (pDispInfo)
584 if (pDispInfo->item.mask & LVIF_TEXT)
586 if (pDispInfo->item.iItem < (int)m_arData.size())
588 const NotificationData * data = m_arData[pDispInfo->item.iItem];
589 switch (pDispInfo->item.iSubItem)
591 case 0:
592 lstrcpyn(m_columnbuf, data->sActionColumnText, MAX_PATH);
593 break;
594 case 1:
595 lstrcpyn(m_columnbuf, data->sPathColumnText, pDispInfo->item.cchTextMax - 1);
596 if (!data->bAuxItem)
598 int cWidth = GetColumnWidth(1);
599 cWidth = max(12, cWidth-12);
600 CDC * pDC = GetDC();
601 if (pDC)
603 CFont * pFont = pDC->SelectObject(GetFont());
604 PathCompactPath(pDC->GetSafeHdc(), m_columnbuf, cWidth);
605 pDC->SelectObject(pFont);
606 ReleaseDC(pDC);
609 break;
610 default:
611 m_columnbuf[0] = 0;
613 pDispInfo->item.pszText = m_columnbuf;
617 *pResult = 0;
620 void CGitProgressList::OnNMCustomdrawSvnprogress(NMHDR *pNMHDR, LRESULT *pResult)
622 NMLVCUSTOMDRAW* pLVCD = reinterpret_cast<NMLVCUSTOMDRAW*>( pNMHDR );
624 // Take the default processing unless we set this to something else below.
625 *pResult = CDRF_DODEFAULT;
627 // First thing - check the draw stage. If it's the control's prepaint
628 // stage, then tell Windows we want messages for every item.
630 if ( CDDS_PREPAINT == pLVCD->nmcd.dwDrawStage )
632 *pResult = CDRF_NOTIFYITEMDRAW;
634 else if ( CDDS_ITEMPREPAINT == pLVCD->nmcd.dwDrawStage )
636 // This is the prepaint stage for an item. Here's where we set the
637 // item's text color. Our return value will tell Windows to draw the
638 // item itself, but it will use the new color we set here.
640 // Tell Windows to paint the control itself.
641 *pResult = CDRF_DODEFAULT;
643 ASSERT(pLVCD->nmcd.dwItemSpec < m_arData.size());
644 if(pLVCD->nmcd.dwItemSpec >= m_arData.size())
646 return;
648 const NotificationData * data = m_arData[pLVCD->nmcd.dwItemSpec];
649 ASSERT(data);
650 if (!data)
651 return;
653 // Store the color back in the NMLVCUSTOMDRAW struct.
654 pLVCD->clrText = data->color;
658 void CGitProgressList::OnNMDblclkSvnprogress(NMHDR* pNMHDR, LRESULT* pResult)
660 LPNMLISTVIEW pNMLV = reinterpret_cast<LPNMLISTVIEW>(pNMHDR);
661 *pResult = 0;
662 if (pNMLV->iItem < 0)
663 return;
664 if (m_options & ProgOptDryRun || m_bThreadRunning)
665 return; //don't do anything in a dry-run.
667 const NotificationData* data = m_arData[pNMLV->iItem];
668 if (!data)
669 return;
671 data->HandleDblClick();
674 void CGitProgressList::OnHdnItemclickSvnprogress(NMHDR *pNMHDR, LRESULT *pResult)
676 LPNMHEADER phdr = reinterpret_cast<LPNMHEADER>(pNMHDR);
677 if (m_bThreadRunning)
678 return;
679 if (m_nSortedColumn == phdr->iItem)
680 m_bAscending = !m_bAscending;
681 else
682 m_bAscending = TRUE;
683 m_nSortedColumn = phdr->iItem;
684 Sort();
686 CString temp;
687 SetRedraw(FALSE);
688 DeleteAllItems();
689 SetItemCountEx (static_cast<int>(m_arData.size()));
691 SetRedraw(TRUE);
693 *pResult = 0;
696 bool CGitProgressList::NotificationDataIsAux(const NotificationData* pData)
698 return pData->bAuxItem;
701 void CGitProgressList::AddNotify(NotificationData* data, CColors::Colors color)
703 if (color != CColors::COLOR_END)
704 data->color = m_Colors.GetColor(color);
705 else
706 data->SetColorCode(m_Colors);
708 m_arData.push_back(data);
709 AddItemToList();
711 if ((!data->bAuxItem) && (m_itemCount > 0))
713 if (m_pProgControl)
715 m_pProgControl->ShowWindow(SW_SHOW);
716 m_pProgControl->SetPos(m_itemCount);
717 m_pProgControl->SetRange32(0, m_itemCountTotal);
719 if (m_pTaskbarList && m_pPostWnd)
721 m_pTaskbarList->SetProgressState(m_pPostWnd->GetSafeHwnd(), TBPF_NORMAL);
722 m_pTaskbarList->SetProgressValue(m_pPostWnd->GetSafeHwnd(), m_itemCount, m_itemCountTotal);
726 // needed as long as RemoteProgressCommand::RemoteCompletionCallback never gets called by libgit2
727 if (m_pAnimate)
728 m_pAnimate->ShowWindow(SW_HIDE);
731 int CGitProgressList::UpdateProgress(const git_transfer_progress* stat)
733 static unsigned int start = 0;
734 unsigned int dt = GetCurrentTime() - start;
735 double speed = 0;
737 if (m_bCancelled)
739 giterr_set_str(GITERR_NONE, "User cancelled.");
740 return GIT_EUSER;
743 if (dt > 100)
745 start = GetCurrentTime();
746 size_t ds = stat->received_bytes - m_TotalBytesTransferred;
747 speed = ds * 1000.0/dt;
748 m_TotalBytesTransferred = stat->received_bytes;
750 else
751 return 0;
753 int progress;
754 progress = stat->received_objects + stat->indexed_objects;
756 if ((stat->total_objects > 1000) && m_pProgControl && (!m_pProgControl->IsWindowVisible()))
757 m_pProgControl->ShowWindow(SW_SHOW);
759 if (m_pProgressLabelCtrl && m_pProgressLabelCtrl->IsWindowVisible())
760 m_pProgressLabelCtrl->ShowWindow(SW_SHOW);
762 if (m_pProgControl)
764 m_pProgControl->SetPos(progress);
765 m_pProgControl->SetRange32(0, 2 * stat->total_objects);
767 if (m_pTaskbarList && m_pPostWnd)
769 m_pTaskbarList->SetProgressState(m_pPostWnd->GetSafeHwnd(), TBPF_NORMAL);
770 m_pTaskbarList->SetProgressValue(m_pPostWnd->GetSafeHwnd(), progress, 2 * stat->total_objects);
773 CString progText;
774 if (stat->received_bytes < 1024)
775 m_sTotalBytesTransferred.Format(IDS_SVN_PROGRESS_TOTALBYTESTRANSFERRED, (int64_t)stat->received_bytes);
776 else if (stat->received_bytes < 1200000)
777 m_sTotalBytesTransferred.Format(IDS_SVN_PROGRESS_TOTALTRANSFERRED, (int64_t)stat->received_bytes / 1024);
778 else
779 m_sTotalBytesTransferred.Format(IDS_SVN_PROGRESS_TOTALMBTRANSFERRED, (double)((double)stat->received_bytes / 1048576.0));
781 CString str;
782 if(speed < 1024)
783 str.Format(_T("%.0f B/s"), speed);
784 else if(speed < 1024 * 1024)
785 str.Format(_T("%.2f KiB/s"), speed / 1024);
786 else
787 str.Format(_T("%.2f MiB/s"), speed / 1048576.0);
789 progText.Format(IDS_SVN_PROGRESS_TOTALANDSPEED, (LPCTSTR)m_sTotalBytesTransferred, (LPCTSTR)str);
790 if (m_pProgressLabelCtrl)
791 m_pProgressLabelCtrl->SetWindowText(progText);
793 SetTimer(TRANSFERTIMER, 2000, nullptr);
795 return 0;
798 void CGitProgressList::OnTimer(UINT_PTR nIDEvent)
800 if (nIDEvent == TRANSFERTIMER)
802 CString progText;
803 CString progSpeed = _T("0 B/s");
804 progText.Format(IDS_SVN_PROGRESS_TOTALANDSPEED, (LPCTSTR)m_sTotalBytesTransferred, (LPCTSTR)progSpeed);
805 if (m_pProgressLabelCtrl)
806 m_pProgressLabelCtrl->SetWindowText(progText);
808 KillTimer(TRANSFERTIMER);
810 if (nIDEvent == VISIBLETIMER)
812 if (nEnsureVisibleCount)
813 EnsureVisible(GetItemCount()-1, false);
814 nEnsureVisibleCount = 0;
818 void CGitProgressList::Sort()
820 if(m_arData.size() < 2)
821 return;
823 // We need to sort the blocks which lie between the auxiliary entries
824 // This is so that any aux data stays where it was
825 NotificationDataVect::iterator actionBlockBegin;
826 NotificationDataVect::iterator actionBlockEnd = m_arData.begin(); // We start searching from here
828 for(;;)
830 // Search to the start of the non-aux entry in the next block
831 actionBlockBegin = std::find_if(actionBlockEnd, m_arData.end(), std::not1(std::ptr_fun(&CGitProgressList::NotificationDataIsAux)));
832 if(actionBlockBegin == m_arData.end())
834 // There are no more actions
835 break;
837 // Now search to find the end of the block
838 actionBlockEnd = std::find_if(actionBlockBegin+1, m_arData.end(), std::ptr_fun(&CGitProgressList::NotificationDataIsAux));
839 // Now sort the block
840 std::sort(actionBlockBegin, actionBlockEnd, &CGitProgressList::SortCompare);
844 bool CGitProgressList::SortCompare(const NotificationData * pData1, const NotificationData * pData2)
846 int result = 0;
847 switch (m_nSortedColumn)
849 case 0: //action column
850 result = pData1->sActionColumnText.Compare(pData2->sActionColumnText);
851 break;
852 case 1: //path column
853 // Compare happens after switch()
854 break;
855 default:
856 break;
859 // Sort by path if everything else is equal
860 if (result == 0)
861 result = CTGitPath::Compare(pData1->path, pData2->path);
863 if (!m_bAscending)
864 result = -result;
865 return result < 0;
868 void CGitProgressList::OnContextMenu(CWnd* pWnd, CPoint point)
870 if (m_options & ProgOptDryRun)
871 return; // don't do anything in a dry-run.
873 if (pWnd != this)
874 return;
876 int selIndex = GetSelectionMark();
877 if ((point.x == -1) && (point.y == -1))
879 // Menu was invoked from the keyboard rather than by right-clicking
880 CRect rect;
881 GetItemRect(selIndex, &rect, LVIR_LABEL);
882 ClientToScreen(&rect);
883 point = rect.CenterPoint();
886 if ((selIndex < 0) || m_bThreadRunning || GetSelectedCount() == 0)
887 return;
889 // entry is selected, thread has finished with updating so show the popup menu
890 CIconMenu popup;
891 if (!popup.CreatePopupMenu())
892 return;
894 ContextMenuActionList actions;
895 NotificationData* data = m_arData[selIndex];
896 if (data && GetSelectedCount() == 1)
897 data->GetContextMenu(popup, actions);
899 if (!actions.empty())
900 popup.AppendMenu(MF_SEPARATOR, NULL);
901 actions.push_back([&]()
903 CString sLines;
904 POSITION pos = GetFirstSelectedItemPosition();
905 while (pos)
907 int nItem = GetNextSelectedItem(pos);
908 NotificationData* data = m_arData[nItem];
909 if (data)
911 sLines += data->sPathColumnText;
912 sLines += _T("\r\n");
915 sLines.TrimRight();
916 if (!sLines.IsEmpty())
917 CStringUtils::WriteAsciiStringToClipboard(sLines, GetSafeHwnd());
919 popup.AppendMenuIcon(actions.size(), IDS_LOG_POPUP_COPYTOCLIPBOARD, IDI_COPYCLIP);
921 if (actions.empty())
922 return;
924 int cmd = popup.TrackPopupMenu(TPM_RETURNCMD | TPM_LEFTALIGN | TPM_NONOTIFY, point.x, point.y, this, 0);
926 if (cmd <= 0 || (size_t)cmd > actions.size())
927 return;
929 theApp.DoWaitCursor(1);
930 actions.at(cmd - 1)();
931 theApp.DoWaitCursor(-1);
934 void CGitProgressList::OnLvnBegindragSvnprogress(NMHDR* , LRESULT *pResult)
936 //LPNMLISTVIEW pNMLV = reinterpret_cast<LPNMLISTVIEW>(pNMHDR);
937 #if 0
938 int selIndex = GetSelectionMark();
939 if (selIndex < 0)
940 return;
942 CDropFiles dropFiles; // class for creating DROPFILES struct
944 int index;
945 POSITION pos = GetFirstSelectedItemPosition();
946 while ( (index = GetNextSelectedItem(pos)) >= 0 )
948 NotificationData * data = m_arData[index];
950 if ( data->kind==svn_node_file || data->kind==svn_node_dir )
952 CString sPath = GetPathFromColumnText(data->sPathColumnText);
954 dropFiles.AddFile( sPath );
958 if (!dropFiles.IsEmpty())
960 dropFiles.CreateStructure();
962 #endif
963 *pResult = 0;
966 void CGitProgressList::OnSize(UINT nType, int cx, int cy)
968 CListCtrl::OnSize(nType, cx, cy);
969 if ((nType == SIZE_RESTORED)&&(m_bLastVisible))
971 if(!m_hWnd)
972 return;
974 int count = GetItemCount();
975 if (count > 0)
976 EnsureVisible(count-1, false);
980 void CGitProgressList::Init()
982 SetExtendedStyle (LVS_EX_FULLROWSELECT | LVS_EX_DOUBLEBUFFER);
984 DeleteAllItems();
985 int c = ((CHeaderCtrl*)(GetDlgItem(0)))->GetItemCount()-1;
986 while (c>=0)
987 DeleteColumn(c--);
989 CString temp;
990 temp.LoadString(IDS_PROGRS_ACTION);
991 InsertColumn(0, temp);
992 temp.LoadString(IDS_PROGRS_PATH);
993 InsertColumn(1, temp);
995 m_pThread = AfxBeginThread(ProgressThreadEntry, this, THREAD_PRIORITY_NORMAL,0,CREATE_SUSPENDED);
996 if (!m_pThread)
997 ReportError(CString(MAKEINTRESOURCE(IDS_ERR_THREADSTARTFAILED)));
998 else
1000 m_pThread->m_bAutoDelete = FALSE;
1001 m_pThread->ResumeThread();
1004 // Call this early so that the column headings aren't hidden before any
1005 // text gets added.
1006 ResizeColumns();
1008 SetTimer(VISIBLETIMER, 300, nullptr);
1012 void CGitProgressList::OnClose()
1014 if (m_bCancelled)
1016 g_Git.KillRelatedThreads(m_pThread);
1017 InterlockedExchange(&m_bThreadRunning, FALSE);
1019 else
1021 m_bCancelled = TRUE;
1022 return;
1024 CListCtrl::OnClose();
1027 BOOL CGitProgressList::PreTranslateMessage(MSG* pMsg)
1029 if (pMsg->message == WM_KEYDOWN)
1031 if (pMsg->wParam == 'A')
1033 if (GetKeyState(VK_CONTROL)&0x8000)
1035 // Ctrl-A -> select all
1036 SetSelectionMark(0);
1037 for (int i=0; i<GetItemCount(); ++i)
1038 SetItemState(i, LVIS_SELECTED, LVIS_SELECTED);
1041 if ((pMsg->wParam == 'C')||(pMsg->wParam == VK_INSERT))
1043 int selIndex = GetSelectionMark();
1044 if (selIndex >= 0)
1046 if (GetKeyState(VK_CONTROL)&0x8000)
1048 //Ctrl-C -> copy to clipboard
1049 CString sClipdata;
1050 POSITION pos = GetFirstSelectedItemPosition();
1051 if (pos)
1053 while (pos)
1055 int nItem = GetNextSelectedItem(pos);
1056 CString sAction = GetItemText(nItem, 0);
1057 CString sPath = GetItemText(nItem, 1);
1058 CString sMime = GetItemText(nItem, 2);
1059 sClipdata.AppendFormat(L"%s: %s %s\r\n", (LPCTSTR)sAction, (LPCTSTR)sPath, (LPCTSTR)sMime);
1061 CStringUtils::WriteAsciiStringToClipboard(sClipdata);
1066 } // if (pMsg->message == WM_KEYDOWN)
1067 return CListCtrl::PreTranslateMessage(pMsg);
1070 void CGitProgressList::SetWindowTitle(UINT id, const CString& urlorpath, CString& dialogname)
1072 if (!m_bSetTitle || !m_pPostWnd)
1073 return;
1075 dialogname.LoadString(id);
1076 CAppUtils::SetWindowTitle(m_pPostWnd->GetSafeHwnd(), urlorpath, dialogname);
1079 void CGitProgressList::ShowProgressBar()
1081 if (m_pProgControl)
1083 m_pProgControl->ShowWindow(SW_SHOW);
1084 m_pProgControl->SetPos(0);
1085 m_pProgControl->SetRange32(0, 1);
1089 void CGitProgressList::SetProgressLabelText(const CString& str)
1091 if (m_pProgressLabelCtrl)
1092 m_pProgressLabelCtrl->SetWindowText(str);
1095 CGitProgressList::WC_File_NotificationData::WC_File_NotificationData(const CTGitPath& path, git_wc_notify_action_t action)
1096 : NotificationData()
1098 this->action = action;
1099 this->path = path;
1100 sPathColumnText = path.GetGitPathString();
1102 switch (action)
1104 case git_wc_notify_add:
1105 sActionColumnText.LoadString(IDS_SVNACTION_ADD);
1106 break;
1107 case git_wc_notify_resolved:
1108 sActionColumnText.LoadString(IDS_SVNACTION_RESOLVE);
1109 break;
1110 case git_wc_notify_revert:
1111 sActionColumnText.LoadString(IDS_SVNACTION_REVERT);
1112 break;
1113 case git_wc_notify_checkout:
1114 sActionColumnText.LoadString(IDS_PROGRS_CMD_CHECKOUT);
1115 break;
1116 default:
1117 break;
1121 void CGitProgressList::WC_File_NotificationData::SetColorCode(CColors& colors)
1123 switch (action)
1125 case git_wc_notify_checkout: // fall-through
1126 case git_wc_notify_add:
1127 color = colors.GetColor(CColors::Added);
1128 break;
1130 default:
1131 break;
1135 void CGitProgressList::WC_File_NotificationData::GetContextMenu(CIconMenu& popup, ContextMenuActionList& actions)
1137 if ((action == git_wc_notify_add) ||
1138 (action == git_wc_notify_revert) ||
1139 (action == git_wc_notify_resolved) ||
1140 (action == git_wc_notify_checkout))
1142 actions.push_back([&]()
1144 CString cmd = _T("/command:log");
1145 CString sPath = g_Git.CombinePath(path);
1146 cmd += _T(" /path:\"") + sPath + _T("\"");
1147 CAppUtils::RunTortoiseGitProc(cmd);
1149 popup.AppendMenuIcon(actions.size(), IDS_MENULOG, IDI_LOG);
1151 popup.AppendMenu(MF_SEPARATOR, NULL);
1152 if (!PathIsDirectory(g_Git.CombinePath(path)))
1154 actions.push_back([&]{ CAppUtils::ShellOpen(g_Git.CombinePath(path)); });
1155 popup.AppendMenuIcon(actions.size(), IDS_LOG_POPUP_OPEN, IDI_OPEN);
1156 actions.push_back([&]{ CAppUtils::ShowOpenWithDialog(g_Git.CombinePath(path)); });
1157 popup.AppendMenuIcon(actions.size(), IDS_LOG_POPUP_OPENWITH, IDI_OPEN);
1160 actions.push_back([&]{ CAppUtils::ExploreTo(nullptr, g_Git.CombinePath(path)); });
1161 popup.AppendMenuIcon(actions.size(), IDS_STATUSLIST_CONTEXT_EXPLORE, IDI_EXPLORER);
1165 void CGitProgressList::WC_File_NotificationData::HandleDblClick() const
1167 CString sWinPath = g_Git.CombinePath(path);
1168 if (PathIsDirectory(sWinPath))
1170 CAppUtils::ExploreTo(nullptr, sWinPath);
1171 return;
1173 CAppUtils::ShellOpen(sWinPath);