PullDlg: Pull default tracked branch with rebase might fail
[TortoiseGit.git] / src / TortoiseProc / GitProgressList.cpp
blob45d1bfba6cb88a303be1d09e5e0f607adbde0bcb
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)
56 , m_nBackgroundImageID(0)
58 m_pInfoCtrl = nullptr;
59 m_pAnimate = nullptr;
60 m_pProgControl = nullptr;
61 m_pProgressLabelCtrl = nullptr;
62 m_pPostWnd = nullptr;
63 m_columnbuf[0] = L'\0';
66 CGitProgressList::~CGitProgressList()
68 for (size_t i = 0; i < m_arData.size(); ++i)
69 delete m_arData[i];
70 delete m_pThread;
74 BEGIN_MESSAGE_MAP(CGitProgressList, CListCtrl)
75 ON_NOTIFY_REFLECT(NM_CUSTOMDRAW, OnNMCustomdrawSvnprogress)
76 ON_NOTIFY_REFLECT(NM_DBLCLK, OnNMDblclkSvnprogress)
77 ON_NOTIFY_REFLECT(HDN_ITEMCLICK, OnHdnItemclickSvnprogress)
78 ON_NOTIFY_REFLECT(LVN_BEGINDRAG, OnLvnBegindragSvnprogress)
79 ON_NOTIFY_REFLECT(LVN_GETDISPINFO, OnLvnGetdispinfoSvnprogress)
80 ON_MESSAGE(WM_SHOWCONFLICTRESOLVER, OnShowConflictResolver)
81 ON_WM_SIZE()
82 ON_WM_TIMER()
83 ON_WM_CONTEXTMENU()
84 ON_WM_CLOSE()
85 END_MESSAGE_MAP()
87 void CGitProgressList::Cancel()
89 m_bCancelled = TRUE;
94 // CGitProgressList message handlers
97 LRESULT CGitProgressList::OnShowConflictResolver(WPARAM /*wParam*/, LPARAM /*lParam*/)
99 #if 0
100 CConflictResolveDlg dlg(this);
101 const svn_wc_conflict_description_t *description = (svn_wc_conflict_description_t *)lParam;
102 if (description)
104 dlg.SetConflictDescription(description);
105 if (m_pTaskbarList)
107 m_pTaskbarList->SetProgressState(m_hWnd, TBPF_PAUSED);
109 if (dlg.DoModal() == IDOK)
111 if (dlg.GetResult() == svn_wc_conflict_choose_postpone)
113 // if the result is conflicted and the dialog returned IDOK,
114 // that means we should not ask again in case of a conflict
115 m_AlwaysConflicted = true;
116 ::SendMessage(GetDlgItem(IDC_NONINTERACTIVE)->GetSafeHwnd(), BM_SETCHECK, BST_CHECKED, 0);
119 m_mergedfile = dlg.GetMergedFile();
120 m_bCancelled = dlg.IsCancelled();
121 if (m_pTaskbarList)
122 m_pTaskbarList->SetProgressState(m_hWnd, TBPF_INDETERMINATE);
123 return dlg.GetResult();
126 return svn_wc_conflict_choose_postpone;
127 #endif
128 return 0;
130 #if 0
131 svn_wc_conflict_choice_t CGitProgressList::ConflictResolveCallback(const svn_wc_conflict_description_t *description, CString& mergedfile)
133 // we only bother the user when merging
134 if (((m_Command == GitProgress_Merge)||(m_Command == GitProgress_MergeAll)||(m_Command == GitProgress_MergeReintegrate))&&(!m_AlwaysConflicted)&&(description))
136 // we're in a worker thread here. That means we must not show a dialog from the thread
137 // but let the UI thread do it.
138 // To do that, we send a message to the UI thread and let it show the conflict resolver dialog.
139 LRESULT dlgResult = ::SendMessage(GetSafeHwnd(), WM_SHOWCONFLICTRESOLVER, 0, (LPARAM)description);
140 mergedfile = m_mergedfile;
141 return (svn_wc_conflict_choice_t)dlgResult;
144 return svn_wc_conflict_choose_postpone;
146 #endif
147 void CGitProgressList::AddItemToList()
149 int totalcount = GetItemCount();
151 SetItemCountEx(totalcount+1, LVSICF_NOSCROLL|LVSICF_NOINVALIDATEALL);
152 // make columns width fit
153 if (iFirstResized < 30)
155 // only resize the columns for the first 30 or so entries.
156 // after that, don't resize them anymore because that's an
157 // expensive function call and the columns will be sized
158 // close enough already.
159 ResizeColumns();
160 ++iFirstResized;
163 // Make sure the item is *entirely* visible even if the horizontal
164 // scroll bar is visible.
165 int count = GetCountPerPage();
166 if (totalcount <= (GetTopIndex() + count + nEnsureVisibleCount + 2))
168 ++nEnsureVisibleCount;
169 m_bLastVisible = true;
171 else
173 nEnsureVisibleCount = 0;
174 if (IsIconic() == 0)
175 m_bLastVisible = false;
179 CString CGitProgressList::BuildInfoString()
181 CString infotext;
182 m_Command->ShowInfo(infotext);
184 #if 0
186 CString temp;
187 int added = 0;
188 int copied = 0;
189 int deleted = 0;
190 int restored = 0;
191 int reverted = 0;
192 int resolved = 0;
193 int conflicted = 0;
194 int updated = 0;
195 int merged = 0;
196 int modified = 0;
197 int skipped = 0;
198 int replaced = 0;
200 for (size_t i=0; i<m_arData.size(); ++i)
202 const NotificationData * dat = m_arData[i];
203 switch (dat->action)
205 case svn_wc_notify_add:
206 case svn_wc_notify_update_add:
207 case svn_wc_notify_commit_added:
208 if (dat->bConflictedActionItem)
209 ++conflicted;
210 else
211 ++added;
212 break;
213 case svn_wc_notify_copy:
214 ++copied;
215 break;
216 case svn_wc_notify_delete:
217 case svn_wc_notify_update_delete:
218 case svn_wc_notify_commit_deleted:
219 ++deleted;
220 break;
221 case svn_wc_notify_restore:
222 ++restored;
223 break;
224 case svn_wc_notify_revert:
225 ++reverted;
226 break;
227 case svn_wc_notify_resolved:
228 ++resolved;
229 break;
230 case svn_wc_notify_update_update:
231 if (dat->bConflictedActionItem)
232 ++conflicted;
233 else if ((dat->content_state == svn_wc_notify_state_merged) || (dat->prop_state == svn_wc_notify_state_merged))
234 ++merged;
235 else
236 ++updated;
237 break;
238 case svn_wc_notify_commit_modified:
239 ++modified;
240 break;
241 case svn_wc_notify_skip:
242 ++skipped;
243 break;
244 case svn_wc_notify_commit_replaced:
245 ++replaced;
246 break;
249 if (conflicted)
251 temp.LoadString(IDS_SVNACTION_CONFLICTED);
252 infotext += temp;
253 temp.Format(L":%d ", conflicted);
254 infotext += temp;
256 if (skipped)
258 temp.LoadString(IDS_SVNACTION_SKIP);
259 infotext += temp;
260 infotext.AppendFormat(L":%d ", skipped);
262 if (merged)
264 temp.LoadString(IDS_SVNACTION_MERGED);
265 infotext += temp;
266 infotext.AppendFormat(L":%d ", merged);
268 if (added)
270 temp.LoadString(IDS_SVNACTION_ADD);
271 infotext += temp;
272 infotext.AppendFormat(L":%d ", added);
274 if (deleted)
276 temp.LoadString(IDS_SVNACTION_DELETE);
277 infotext += temp;
278 infotext.AppendFormat(L":%d ", deleted);
280 if (modified)
282 temp.LoadString(IDS_SVNACTION_MODIFIED);
283 infotext += temp;
284 infotext.AppendFormat(L":%d ", modified);
286 if (copied)
288 temp.LoadString(IDS_SVNACTION_COPY);
289 infotext += temp;
290 infotext.AppendFormat(L":%d ", copied);
292 if (replaced)
294 temp.LoadString(IDS_SVNACTION_REPLACED);
295 infotext += temp;
296 infotext.AppendFormat(L":%d ", replaced);
298 if (updated)
300 temp.LoadString(IDS_SVNACTION_UPDATE);
301 infotext += temp;
302 infotext.AppendFormat(L":%d ", updated);
304 if (restored)
306 temp.LoadString(IDS_SVNACTION_RESTORE);
307 infotext += temp;
308 infotext.AppendFormat(L":%d ", restored);
310 if (reverted)
312 temp.LoadString(IDS_SVNACTION_REVERT);
313 infotext += temp;
314 infotext.AppendFormat(L":%d ", reverted);
316 if (resolved)
318 temp.LoadString(IDS_SVNACTION_RESOLVE);
319 infotext += temp;
320 infotext.AppendFormat(L":%d ", resolved);
322 #endif
323 return infotext;
326 void CGitProgressList::ResizeColumns()
328 SetRedraw(FALSE);
330 TCHAR textbuf[MAX_PATH] = {0};
332 CHeaderCtrl * pHeaderCtrl = (CHeaderCtrl*)(GetDlgItem(0));
333 if (pHeaderCtrl)
335 int maxcol = pHeaderCtrl->GetItemCount()-1;
336 for (int col = 0; col <= maxcol; ++col)
338 // find the longest width of all items
339 int count = min((int)m_arData.size(), GetItemCount());
340 HDITEM hdi = {0};
341 hdi.mask = HDI_TEXT;
342 hdi.pszText = textbuf;
343 hdi.cchTextMax = _countof(textbuf);
344 pHeaderCtrl->GetItem(col, &hdi);
345 int cx = GetStringWidth(hdi.pszText)+20; // 20 pixels for col separator and margin
347 for (int index = 0; index<count; ++index)
349 // get the width of the string and add 12 pixels for the column separator and margins
350 int linewidth = cx;
351 switch (col)
353 case 0:
354 linewidth = GetStringWidth(m_arData[index]->sActionColumnText) + 12;
355 break;
356 case 1:
357 linewidth = GetStringWidth(m_arData[index]->sPathColumnText) + 12;
358 break;
360 if (cx < linewidth)
361 cx = linewidth;
363 SetColumnWidth(col, cx);
367 SetRedraw(TRUE);
370 bool CGitProgressList::SetBackgroundImage(UINT nID)
372 m_nBackgroundImageID = nID;
373 return CAppUtils::SetListCtrlBackgroundImage(GetSafeHwnd(), nID);
376 void CGitProgressList::ReportGitError()
378 ReportError(CGit::GetLibGit2LastErr());
381 void CGitProgressList::ReportUserCanceled()
383 ReportError(CString(MAKEINTRESOURCE(IDS_USERCANCELLED)));
386 void CGitProgressList::ReportError(const CString& sError)
388 if (CRegDWORD(L"Software\\TortoiseGit\\NoSounds", FALSE) == FALSE)
389 PlaySound((LPCTSTR)SND_ALIAS_SYSTEMEXCLAMATION, nullptr, SND_ALIAS_ID | SND_ASYNC);
390 ReportString(sError, CString(MAKEINTRESOURCE(IDS_ERR_ERROR)), m_Colors.GetColor(CColors::Conflict));
391 m_bErrorsOccurred = true;
394 void CGitProgressList::ReportWarning(const CString& sWarning)
396 if (CRegDWORD(L"Software\\TortoiseGit\\NoSounds", FALSE) == FALSE)
397 PlaySound((LPCTSTR)SND_ALIAS_SYSTEMDEFAULT, nullptr, SND_ALIAS_ID | SND_ASYNC);
398 ReportString(sWarning, CString(MAKEINTRESOURCE(IDS_WARN_WARNING)), m_Colors.GetColor(CColors::Conflict));
401 void CGitProgressList::ReportNotification(const CString& sNotification)
403 if (CRegDWORD(L"Software\\TortoiseGit\\NoSounds", FALSE) == FALSE)
404 PlaySound((LPCTSTR)SND_ALIAS_SYSTEMDEFAULT, nullptr, SND_ALIAS_ID | SND_ASYNC);
405 ReportString(sNotification, CString(MAKEINTRESOURCE(IDS_WARN_NOTE)));
408 void CGitProgressList::ReportCmd(const CString& sCmd)
410 ReportString(sCmd, CString(MAKEINTRESOURCE(IDS_PROGRS_CMDINFO)), m_Colors.GetColor(CColors::Cmd));
413 void CGitProgressList::ReportString(CString sMessage, const CString& sMsgKind, COLORREF color)
415 // instead of showing a dialog box with the error message or notification,
416 // just insert the error text into the list control.
417 // that way the user isn't 'interrupted' by a dialog box popping up!
419 // the message may be split up into different lines
420 // so add a new entry for each line of the message
421 while (!sMessage.IsEmpty())
423 NotificationData * data = new NotificationData();
424 data->bAuxItem = true;
425 data->sActionColumnText = sMsgKind;
426 if (sMessage.Find('\n')>=0)
427 data->sPathColumnText = sMessage.Left(sMessage.Find('\n'));
428 else
429 data->sPathColumnText = sMessage;
430 data->sPathColumnText.Trim(L"\n\r");
431 data->color = color;
432 if (sMessage.Find('\n')>=0)
434 sMessage = sMessage.Mid(sMessage.Find('\n'));
435 sMessage.Trim(L"\n\r");
437 else
438 sMessage.Empty();
439 AddNotify(data);
443 UINT CGitProgressList::ProgressThreadEntry(LPVOID pVoid)
445 return reinterpret_cast<CGitProgressList*>(pVoid)->ProgressThread();
448 UINT CGitProgressList::ProgressThread()
450 // The SetParams function should have loaded something for us
452 CString temp;
453 CString sWindowTitle;
454 bool bSuccess = false;
456 if(m_pPostWnd)
457 m_pPostWnd->PostMessage(WM_PROG_CMD_START, (WPARAM)m_Command);
459 if(m_pProgressLabelCtrl)
461 m_pProgressLabelCtrl->ShowWindow(SW_SHOW);
462 m_pProgressLabelCtrl->SetWindowText(L"");
465 // SetAndClearProgressInfo(m_hWnd);
466 m_itemCount = m_itemCountTotal;
468 InterlockedExchange(&m_bThreadRunning, TRUE);
469 iFirstResized = 0;
470 bSecondResized = FALSE;
471 m_bFinishedItemAdded = false;
472 DWORD startTime = GetCurrentTime();
474 if (m_pTaskbarList && m_pPostWnd)
475 m_pTaskbarList->SetProgressState(m_pPostWnd->GetSafeHwnd(), TBPF_INDETERMINATE);
477 m_TotalBytesTransferred = 0;
478 if (m_Command)
479 bSuccess = m_Command->Run(this, sWindowTitle, m_itemCountTotal, m_itemCount);
480 else
481 bSuccess = false;
483 if (!bSuccess)
484 temp.LoadString(IDS_PROGRS_TITLEFAILED);
485 else
486 temp.LoadString(IDS_PROGRS_TITLEFIN);
487 sWindowTitle = sWindowTitle + L' ' + temp;
488 if (m_bSetTitle && m_pPostWnd)
489 ::SetWindowText(m_pPostWnd->GetSafeHwnd(), sWindowTitle);
491 KillTimer(TRANSFERTIMER);
492 KillTimer(VISIBLETIMER);
494 if (m_pTaskbarList && m_pPostWnd)
496 if (DidErrorsOccur())
498 m_pTaskbarList->SetProgressState(m_pPostWnd->GetSafeHwnd(), TBPF_ERROR);
499 m_pTaskbarList->SetProgressValue(m_pPostWnd->GetSafeHwnd(), 100, 100);
501 else
502 m_pTaskbarList->SetProgressState(m_pPostWnd->GetSafeHwnd(), TBPF_NOPROGRESS);
505 if (m_pInfoCtrl)
507 CString info;
508 if (!bSuccess)
509 info.LoadString(IDS_PROGRS_INFOFAILED);
510 else // this implies that command is not nullptr
511 info = BuildInfoString();
512 m_pInfoCtrl->SetWindowText(info);
515 ResizeColumns();
517 DWORD time = GetCurrentTime() - startTime;
519 CString sFinalInfo;
520 if (!m_sTotalBytesTransferred.IsEmpty())
522 temp.Format(IDS_PROGRS_TIME, (time / 1000) / 60, (time / 1000) % 60);
523 sFinalInfo.Format(IDS_PROGRS_FINALINFO, m_sTotalBytesTransferred, (LPCTSTR)temp);
524 if (m_pProgressLabelCtrl)
525 m_pProgressLabelCtrl->SetWindowText(sFinalInfo);
527 else
529 if (m_pProgressLabelCtrl)
530 m_pProgressLabelCtrl->ShowWindow(SW_HIDE);
533 if (m_pProgControl)
534 m_pProgControl->ShowWindow(SW_HIDE);
536 if (!m_bFinishedItemAdded)
538 CString log, str;
539 if (bSuccess)
540 str.LoadString(IDS_SUCCESS);
541 else
542 str.LoadString(IDS_FAIL);
543 log.Format(L"%s (%lu ms @ %s)", (LPCTSTR)str, time, (LPCTSTR)CLoglistUtils::FormatDateAndTime(CTime::GetCurrentTime(), DATE_SHORTDATE, true, false));
545 // there's no "finished: xxx" line at the end. We add one here to make
546 // sure the user sees that the command is actually finished.
547 ReportString(log, CString(MAKEINTRESOURCE(IDS_PROGRS_FINISHED)), bSuccess? RGB(0,0,255) : RGB(255,0,0));
550 int count = GetItemCount();
551 if ((count > 0)&&(m_bLastVisible))
552 EnsureVisible(count-1, FALSE);
554 CLogFile logfile(g_Git.m_CurrentDir);
555 if (logfile.Open())
557 logfile.AddTimeLine();
558 for (size_t i = 0; i < m_arData.size(); ++i)
560 NotificationData * data = m_arData[i];
561 temp.Format(L"%-20s : %s", (LPCTSTR)data->sActionColumnText, (LPCTSTR)data->sPathColumnText);
562 logfile.AddLine(temp);
564 if (!sFinalInfo.IsEmpty())
565 logfile.AddLine(sFinalInfo);
566 logfile.Close();
569 m_bCancelled = TRUE;
570 InterlockedExchange(&m_bThreadRunning, FALSE);
572 if (m_pPostWnd)
573 m_pPostWnd->PostMessage(WM_PROG_CMD_FINISH, (WPARAM)m_Command, 0L);
575 //Don't do anything here which might cause messages to be sent to the window
576 //The window thread is probably now blocked in OnOK if we've done an auto close
577 return 0;
580 void CGitProgressList::OnLvnGetdispinfoSvnprogress(NMHDR *pNMHDR, LRESULT *pResult)
582 NMLVDISPINFO *pDispInfo = reinterpret_cast<NMLVDISPINFO*>(pNMHDR);
584 if (pDispInfo)
586 if (pDispInfo->item.mask & LVIF_TEXT)
588 if (pDispInfo->item.iItem < (int)m_arData.size())
590 const NotificationData * data = m_arData[pDispInfo->item.iItem];
591 switch (pDispInfo->item.iSubItem)
593 case 0:
594 lstrcpyn(m_columnbuf, data->sActionColumnText, MAX_PATH);
595 break;
596 case 1:
597 lstrcpyn(m_columnbuf, data->sPathColumnText, pDispInfo->item.cchTextMax - 1);
598 if (!data->bAuxItem)
600 int cWidth = GetColumnWidth(1);
601 cWidth = max(12, cWidth-12);
602 CDC * pDC = GetDC();
603 if (pDC)
605 CFont * pFont = pDC->SelectObject(GetFont());
606 PathCompactPath(pDC->GetSafeHdc(), m_columnbuf, cWidth);
607 pDC->SelectObject(pFont);
608 ReleaseDC(pDC);
611 break;
612 default:
613 m_columnbuf[0] = L'\0';
615 pDispInfo->item.pszText = m_columnbuf;
619 *pResult = 0;
622 void CGitProgressList::OnNMCustomdrawSvnprogress(NMHDR *pNMHDR, LRESULT *pResult)
624 NMLVCUSTOMDRAW* pLVCD = reinterpret_cast<NMLVCUSTOMDRAW*>( pNMHDR );
626 // Take the default processing unless we set this to something else below.
627 *pResult = CDRF_DODEFAULT;
629 // First thing - check the draw stage. If it's the control's prepaint
630 // stage, then tell Windows we want messages for every item.
632 if ( CDDS_PREPAINT == pLVCD->nmcd.dwDrawStage )
634 *pResult = CDRF_NOTIFYITEMDRAW;
636 else if ( CDDS_ITEMPREPAINT == pLVCD->nmcd.dwDrawStage )
638 // This is the prepaint stage for an item. Here's where we set the
639 // item's text color. Our return value will tell Windows to draw the
640 // item itself, but it will use the new color we set here.
642 // Tell Windows to paint the control itself.
643 *pResult = CDRF_DODEFAULT;
645 ASSERT(pLVCD->nmcd.dwItemSpec < m_arData.size());
646 if(pLVCD->nmcd.dwItemSpec >= m_arData.size())
648 return;
650 const NotificationData * data = m_arData[pLVCD->nmcd.dwItemSpec];
651 ASSERT(data);
652 if (!data)
653 return;
655 // Store the color back in the NMLVCUSTOMDRAW struct.
656 pLVCD->clrText = data->color;
660 void CGitProgressList::OnNMDblclkSvnprogress(NMHDR* pNMHDR, LRESULT* pResult)
662 LPNMLISTVIEW pNMLV = reinterpret_cast<LPNMLISTVIEW>(pNMHDR);
663 *pResult = 0;
664 if (pNMLV->iItem < 0)
665 return;
666 if (m_options & ProgOptDryRun || m_bThreadRunning)
667 return; //don't do anything in a dry-run.
669 const NotificationData* data = m_arData[pNMLV->iItem];
670 if (!data)
671 return;
673 data->HandleDblClick();
676 void CGitProgressList::OnHdnItemclickSvnprogress(NMHDR *pNMHDR, LRESULT *pResult)
678 LPNMHEADER phdr = reinterpret_cast<LPNMHEADER>(pNMHDR);
679 if (m_bThreadRunning)
680 return;
681 if (m_nSortedColumn == phdr->iItem)
682 m_bAscending = !m_bAscending;
683 else
684 m_bAscending = TRUE;
685 m_nSortedColumn = phdr->iItem;
686 Sort();
688 CString temp;
689 SetRedraw(FALSE);
690 DeleteAllItems();
691 SetItemCountEx (static_cast<int>(m_arData.size()));
693 SetRedraw(TRUE);
695 *pResult = 0;
698 bool CGitProgressList::NotificationDataIsAux(const NotificationData* pData)
700 return pData->bAuxItem;
703 void CGitProgressList::AddNotify(NotificationData* data, CColors::Colors color)
705 if (color != CColors::COLOR_END)
706 data->color = m_Colors.GetColor(color);
707 else
708 data->SetColorCode(m_Colors);
710 m_arData.push_back(data);
711 AddItemToList();
713 if ((!data->bAuxItem) && (m_itemCount > 0))
715 if (m_pProgControl)
717 m_pProgControl->ShowWindow(SW_SHOW);
718 m_pProgControl->SetPos(m_itemCount);
719 m_pProgControl->SetRange32(0, m_itemCountTotal);
721 if (m_pTaskbarList && m_pPostWnd)
723 m_pTaskbarList->SetProgressState(m_pPostWnd->GetSafeHwnd(), TBPF_NORMAL);
724 m_pTaskbarList->SetProgressValue(m_pPostWnd->GetSafeHwnd(), m_itemCount, m_itemCountTotal);
728 // needed as long as RemoteProgressCommand::RemoteCompletionCallback never gets called by libgit2
729 if (m_pAnimate)
730 m_pAnimate->ShowWindow(SW_HIDE);
733 int CGitProgressList::UpdateProgress(const git_transfer_progress* stat)
735 static unsigned int start = 0;
736 unsigned int dt = GetCurrentTime() - start;
737 double speed = 0;
739 if (m_bCancelled)
741 giterr_set_str(GITERR_NONE, "User cancelled.");
742 return GIT_EUSER;
745 if (dt > 100)
747 start = GetCurrentTime();
748 size_t ds = stat->received_bytes - m_TotalBytesTransferred;
749 speed = ds * 1000.0/dt;
750 m_TotalBytesTransferred = stat->received_bytes;
752 else
753 return 0;
755 int progress;
756 progress = stat->received_objects + stat->indexed_objects;
758 if ((stat->total_objects > 1000) && m_pProgControl && (!m_pProgControl->IsWindowVisible()))
759 m_pProgControl->ShowWindow(SW_SHOW);
761 if (m_pProgressLabelCtrl && m_pProgressLabelCtrl->IsWindowVisible())
762 m_pProgressLabelCtrl->ShowWindow(SW_SHOW);
764 if (m_pProgControl)
766 m_pProgControl->SetPos(progress);
767 m_pProgControl->SetRange32(0, 2 * stat->total_objects);
769 if (m_pTaskbarList && m_pPostWnd)
771 m_pTaskbarList->SetProgressState(m_pPostWnd->GetSafeHwnd(), TBPF_NORMAL);
772 m_pTaskbarList->SetProgressValue(m_pPostWnd->GetSafeHwnd(), progress, 2 * stat->total_objects);
775 CString progText;
776 if (stat->received_bytes < 1024)
777 m_sTotalBytesTransferred.Format(IDS_SVN_PROGRESS_TOTALBYTESTRANSFERRED, (int64_t)stat->received_bytes);
778 else if (stat->received_bytes < 1200000)
779 m_sTotalBytesTransferred.Format(IDS_SVN_PROGRESS_TOTALTRANSFERRED, (int64_t)stat->received_bytes / 1024);
780 else
781 m_sTotalBytesTransferred.Format(IDS_SVN_PROGRESS_TOTALMBTRANSFERRED, (double)((double)stat->received_bytes / 1048576.0));
783 CString str;
784 if(speed < 1024)
785 str.Format(L"%.0f B/s", speed);
786 else if(speed < 1024 * 1024)
787 str.Format(L"%.2f KiB/s", speed / 1024);
788 else
789 str.Format(L"%.2f MiB/s", speed / 1048576.0);
791 progText.Format(IDS_SVN_PROGRESS_TOTALANDSPEED, (LPCTSTR)m_sTotalBytesTransferred, (LPCTSTR)str);
792 if (m_pProgressLabelCtrl)
793 m_pProgressLabelCtrl->SetWindowText(progText);
795 SetTimer(TRANSFERTIMER, 2000, nullptr);
797 return 0;
800 void CGitProgressList::OnTimer(UINT_PTR nIDEvent)
802 if (nIDEvent == TRANSFERTIMER)
804 CString progText;
805 CString progSpeed = L"0 B/s";
806 progText.Format(IDS_SVN_PROGRESS_TOTALANDSPEED, (LPCTSTR)m_sTotalBytesTransferred, (LPCTSTR)progSpeed);
807 if (m_pProgressLabelCtrl)
808 m_pProgressLabelCtrl->SetWindowText(progText);
810 KillTimer(TRANSFERTIMER);
812 if (nIDEvent == VISIBLETIMER)
814 if (nEnsureVisibleCount)
815 EnsureVisible(GetItemCount()-1, false);
816 nEnsureVisibleCount = 0;
820 void CGitProgressList::Sort()
822 if(m_arData.size() < 2)
823 return;
825 // We need to sort the blocks which lie between the auxiliary entries
826 // This is so that any aux data stays where it was
827 NotificationDataVect::iterator actionBlockBegin;
828 NotificationDataVect::iterator actionBlockEnd = m_arData.begin(); // We start searching from here
830 for(;;)
832 // Search to the start of the non-aux entry in the next block
833 actionBlockBegin = std::find_if(actionBlockEnd, m_arData.end(), std::not1(std::ptr_fun(&CGitProgressList::NotificationDataIsAux)));
834 if(actionBlockBegin == m_arData.end())
836 // There are no more actions
837 break;
839 // Now search to find the end of the block
840 actionBlockEnd = std::find_if(actionBlockBegin+1, m_arData.end(), std::ptr_fun(&CGitProgressList::NotificationDataIsAux));
841 // Now sort the block
842 std::sort(actionBlockBegin, actionBlockEnd, &CGitProgressList::SortCompare);
846 bool CGitProgressList::SortCompare(const NotificationData * pData1, const NotificationData * pData2)
848 int result = 0;
849 switch (m_nSortedColumn)
851 case 0: //action column
852 result = pData1->sActionColumnText.Compare(pData2->sActionColumnText);
853 break;
854 case 1: //path column
855 // Compare happens after switch()
856 break;
857 default:
858 break;
861 // Sort by path if everything else is equal
862 if (result == 0)
863 result = CTGitPath::Compare(pData1->path, pData2->path);
865 if (!m_bAscending)
866 result = -result;
867 return result < 0;
870 void CGitProgressList::OnContextMenu(CWnd* pWnd, CPoint point)
872 if (m_options & ProgOptDryRun)
873 return; // don't do anything in a dry-run.
875 if (pWnd != this)
876 return;
878 int selIndex = GetSelectionMark();
879 if ((point.x == -1) && (point.y == -1))
881 // Menu was invoked from the keyboard rather than by right-clicking
882 CRect rect;
883 GetItemRect(selIndex, &rect, LVIR_LABEL);
884 ClientToScreen(&rect);
885 point = rect.CenterPoint();
888 if ((selIndex < 0) || m_bThreadRunning || GetSelectedCount() == 0)
889 return;
891 // entry is selected, thread has finished with updating so show the popup menu
892 CIconMenu popup;
893 if (!popup.CreatePopupMenu())
894 return;
896 ContextMenuActionList actions;
897 NotificationData* data = m_arData[selIndex];
898 if (data && GetSelectedCount() == 1)
899 data->GetContextMenu(popup, actions);
901 if (!actions.empty())
902 popup.AppendMenu(MF_SEPARATOR, NULL);
903 actions.push_back([&]()
905 CString sLines;
906 POSITION pos = GetFirstSelectedItemPosition();
907 while (pos)
909 int nItem = GetNextSelectedItem(pos);
910 NotificationData* data = m_arData[nItem];
911 if (data)
913 sLines += data->sPathColumnText;
914 sLines += L"\r\n";
917 sLines.TrimRight();
918 if (!sLines.IsEmpty())
919 CStringUtils::WriteAsciiStringToClipboard(sLines, GetSafeHwnd());
921 popup.AppendMenuIcon(actions.size(), IDS_LOG_POPUP_COPYTOCLIPBOARD, IDI_COPYCLIP);
923 if (actions.empty())
924 return;
926 int cmd = popup.TrackPopupMenu(TPM_RETURNCMD | TPM_LEFTALIGN | TPM_NONOTIFY, point.x, point.y, this);
928 if (cmd <= 0 || (size_t)cmd > actions.size())
929 return;
931 theApp.DoWaitCursor(1);
932 actions.at(cmd - 1)();
933 theApp.DoWaitCursor(-1);
936 void CGitProgressList::OnLvnBegindragSvnprogress(NMHDR* , LRESULT *pResult)
938 //LPNMLISTVIEW pNMLV = reinterpret_cast<LPNMLISTVIEW>(pNMHDR);
939 #if 0
940 int selIndex = GetSelectionMark();
941 if (selIndex < 0)
942 return;
944 CDropFiles dropFiles; // class for creating DROPFILES struct
946 int index;
947 POSITION pos = GetFirstSelectedItemPosition();
948 while ( (index = GetNextSelectedItem(pos)) >= 0 )
950 NotificationData * data = m_arData[index];
952 if ( data->kind==svn_node_file || data->kind==svn_node_dir )
954 CString sPath = GetPathFromColumnText(data->sPathColumnText);
956 dropFiles.AddFile( sPath );
960 if (!dropFiles.IsEmpty())
962 dropFiles.CreateStructure();
964 #endif
965 *pResult = 0;
968 void CGitProgressList::OnSize(UINT nType, int cx, int cy)
970 CListCtrl::OnSize(nType, cx, cy);
971 if ((nType == SIZE_RESTORED)&&(m_bLastVisible))
973 if(!m_hWnd)
974 return;
976 int count = GetItemCount();
977 if (count > 0)
978 EnsureVisible(count-1, false);
982 void CGitProgressList::Init()
984 SetExtendedStyle((CRegDWORD(L"Software\\TortoiseGit\\FullRowSelect", TRUE) ? LVS_EX_FULLROWSELECT : 0) | LVS_EX_DOUBLEBUFFER);
986 DeleteAllItems();
987 int c = ((CHeaderCtrl*)(GetDlgItem(0)))->GetItemCount()-1;
988 while (c>=0)
989 DeleteColumn(c--);
991 CString temp;
992 temp.LoadString(IDS_PROGRS_ACTION);
993 InsertColumn(0, temp);
994 temp.LoadString(IDS_PROGRS_PATH);
995 InsertColumn(1, temp);
997 m_pThread = AfxBeginThread(ProgressThreadEntry, this, THREAD_PRIORITY_NORMAL,0,CREATE_SUSPENDED);
998 if (!m_pThread)
999 ReportError(CString(MAKEINTRESOURCE(IDS_ERR_THREADSTARTFAILED)));
1000 else
1002 m_pThread->m_bAutoDelete = FALSE;
1003 m_pThread->ResumeThread();
1006 // Call this early so that the column headings aren't hidden before any
1007 // text gets added.
1008 ResizeColumns();
1010 SetTimer(VISIBLETIMER, 300, nullptr);
1014 void CGitProgressList::OnClose()
1016 if (m_bCancelled)
1018 g_Git.KillRelatedThreads(m_pThread);
1019 InterlockedExchange(&m_bThreadRunning, FALSE);
1021 else
1023 m_bCancelled = TRUE;
1024 return;
1026 CListCtrl::OnClose();
1029 BOOL CGitProgressList::PreTranslateMessage(MSG* pMsg)
1031 if (pMsg->message == WM_KEYDOWN)
1033 if (pMsg->wParam == 'A')
1035 if (GetKeyState(VK_CONTROL)&0x8000)
1037 // Ctrl-A -> select all
1038 SetSelectionMark(0);
1039 for (int i=0; i<GetItemCount(); ++i)
1040 SetItemState(i, LVIS_SELECTED, LVIS_SELECTED);
1043 if ((pMsg->wParam == 'C')||(pMsg->wParam == VK_INSERT))
1045 int selIndex = GetSelectionMark();
1046 if (selIndex >= 0)
1048 if (GetKeyState(VK_CONTROL)&0x8000)
1050 //Ctrl-C -> copy to clipboard
1051 CString sClipdata;
1052 POSITION pos = GetFirstSelectedItemPosition();
1053 if (pos)
1055 while (pos)
1057 int nItem = GetNextSelectedItem(pos);
1058 CString sAction = GetItemText(nItem, 0);
1059 CString sPath = GetItemText(nItem, 1);
1060 CString sMime = GetItemText(nItem, 2);
1061 sClipdata.AppendFormat(L"%s: %s %s\r\n", (LPCTSTR)sAction, (LPCTSTR)sPath, (LPCTSTR)sMime);
1063 CStringUtils::WriteAsciiStringToClipboard(sClipdata);
1068 } // if (pMsg->message == WM_KEYDOWN)
1069 return CListCtrl::PreTranslateMessage(pMsg);
1072 void CGitProgressList::SetWindowTitle(UINT id, const CString& urlorpath, CString& dialogname)
1074 if (!m_bSetTitle || !m_pPostWnd)
1075 return;
1077 dialogname.LoadString(id);
1078 CAppUtils::SetWindowTitle(m_pPostWnd->GetSafeHwnd(), urlorpath, dialogname);
1081 void CGitProgressList::ShowProgressBar()
1083 if (m_pProgControl)
1085 m_pProgControl->ShowWindow(SW_SHOW);
1086 m_pProgControl->SetPos(0);
1087 m_pProgControl->SetRange32(0, 1);
1091 void CGitProgressList::SetProgressLabelText(const CString& str)
1093 if (m_pProgressLabelCtrl)
1094 m_pProgressLabelCtrl->SetWindowText(str);
1097 CGitProgressList::WC_File_NotificationData::WC_File_NotificationData(const CTGitPath& path, git_wc_notify_action_t action)
1098 : NotificationData()
1099 , action(action)
1101 this->path = path;
1102 sPathColumnText = path.GetGitPathString();
1104 switch (action)
1106 case git_wc_notify_add:
1107 sActionColumnText.LoadString(IDS_SVNACTION_ADD);
1108 break;
1109 case git_wc_notify_resolved:
1110 sActionColumnText.LoadString(IDS_SVNACTION_RESOLVE);
1111 break;
1112 case git_wc_notify_revert:
1113 sActionColumnText.LoadString(IDS_SVNACTION_REVERT);
1114 break;
1115 case git_wc_notify_checkout:
1116 sActionColumnText.LoadString(IDS_PROGRS_CMD_CHECKOUT);
1117 break;
1118 default:
1119 break;
1123 void CGitProgressList::WC_File_NotificationData::SetColorCode(CColors& colors)
1125 switch (action)
1127 case git_wc_notify_checkout: // fall-through
1128 case git_wc_notify_add:
1129 color = colors.GetColor(CColors::Added);
1130 break;
1132 default:
1133 break;
1137 void CGitProgressList::WC_File_NotificationData::GetContextMenu(CIconMenu& popup, ContextMenuActionList& actions)
1139 if ((action == git_wc_notify_add) ||
1140 (action == git_wc_notify_revert) ||
1141 (action == git_wc_notify_resolved) ||
1142 (action == git_wc_notify_checkout))
1144 actions.push_back([&]()
1146 CString cmd = L"/command:log";
1147 CString sPath = g_Git.CombinePath(path);
1148 cmd += L" /path:\"" + sPath + L'"';
1149 CAppUtils::RunTortoiseGitProc(cmd);
1151 popup.AppendMenuIcon(actions.size(), IDS_MENULOG, IDI_LOG);
1153 popup.AppendMenu(MF_SEPARATOR, NULL);
1154 if (!PathIsDirectory(g_Git.CombinePath(path)))
1156 actions.push_back([&]{ CAppUtils::ShellOpen(g_Git.CombinePath(path)); });
1157 popup.AppendMenuIcon(actions.size(), IDS_LOG_POPUP_OPEN, IDI_OPEN);
1158 actions.push_back([&]{ CAppUtils::ShowOpenWithDialog(g_Git.CombinePath(path)); });
1159 popup.AppendMenuIcon(actions.size(), IDS_LOG_POPUP_OPENWITH, IDI_OPEN);
1162 actions.push_back([&]{ CAppUtils::ExploreTo(nullptr, g_Git.CombinePath(path)); });
1163 popup.AppendMenuIcon(actions.size(), IDS_STATUSLIST_CONTEXT_EXPLORE, IDI_EXPLORER);
1167 void CGitProgressList::WC_File_NotificationData::HandleDblClick() const
1169 CString sWinPath = g_Git.CombinePath(path);
1170 if (PathIsDirectory(sWinPath))
1172 CAppUtils::ExploreTo(nullptr, sWinPath);
1173 return;
1175 CAppUtils::ShellOpen(sWinPath);
1178 void CGitProgressList::OnSysColorChange()
1180 __super::OnSysColorChange();
1181 if (m_nBackgroundImageID)
1182 CAppUtils::SetListCtrlBackgroundImage(GetSafeHwnd(), m_nBackgroundImageID);