Drop "SVN" from string resource IDs
[TortoiseGit.git] / src / TortoiseProc / GitProgressList.cpp
blob5c15635925098a5b13dbfae1176e056347d682f5
1 // TortoiseGit - a Windows shell extension for easy version control
3 // Copyright (C) 2008-2015 - 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(NULL)
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)
69 delete m_arData[i];
71 if(m_pThread != NULL)
73 delete m_pThread;
78 BEGIN_MESSAGE_MAP(CGitProgressList, CListCtrl)
79 ON_NOTIFY_REFLECT(NM_CUSTOMDRAW, OnNMCustomdrawSvnprogress)
80 ON_NOTIFY_REFLECT(NM_DBLCLK, OnNMDblclkSvnprogress)
81 ON_NOTIFY_REFLECT(HDN_ITEMCLICK, OnHdnItemclickSvnprogress)
82 ON_NOTIFY_REFLECT(LVN_BEGINDRAG, OnLvnBegindragSvnprogress)
83 ON_NOTIFY_REFLECT(LVN_GETDISPINFO, OnLvnGetdispinfoSvnprogress)
84 ON_MESSAGE(WM_SHOWCONFLICTRESOLVER, OnShowConflictResolver)
85 ON_WM_SIZE()
86 ON_WM_TIMER()
87 ON_WM_CONTEXTMENU()
88 ON_WM_CLOSE()
89 END_MESSAGE_MAP()
91 void CGitProgressList::Cancel()
93 m_bCancelled = TRUE;
98 // CGitProgressList message handlers
101 LRESULT CGitProgressList::OnShowConflictResolver(WPARAM /*wParam*/, LPARAM /*lParam*/)
103 #if 0
104 CConflictResolveDlg dlg(this);
105 const svn_wc_conflict_description_t *description = (svn_wc_conflict_description_t *)lParam;
106 if (description)
108 dlg.SetConflictDescription(description);
109 if (m_pTaskbarList)
111 m_pTaskbarList->SetProgressState(m_hWnd, TBPF_PAUSED);
113 if (dlg.DoModal() == IDOK)
115 if (dlg.GetResult() == svn_wc_conflict_choose_postpone)
117 // if the result is conflicted and the dialog returned IDOK,
118 // that means we should not ask again in case of a conflict
119 m_AlwaysConflicted = true;
120 ::SendMessage(GetDlgItem(IDC_NONINTERACTIVE)->GetSafeHwnd(), BM_SETCHECK, BST_CHECKED, 0);
123 m_mergedfile = dlg.GetMergedFile();
124 m_bCancelled = dlg.IsCancelled();
125 if (m_pTaskbarList)
126 m_pTaskbarList->SetProgressState(m_hWnd, TBPF_INDETERMINATE);
127 return dlg.GetResult();
130 return svn_wc_conflict_choose_postpone;
131 #endif
132 return 0;
134 #if 0
135 svn_wc_conflict_choice_t CGitProgressList::ConflictResolveCallback(const svn_wc_conflict_description_t *description, CString& mergedfile)
137 // we only bother the user when merging
138 if (((m_Command == GitProgress_Merge)||(m_Command == GitProgress_MergeAll)||(m_Command == GitProgress_MergeReintegrate))&&(!m_AlwaysConflicted)&&(description))
140 // we're in a worker thread here. That means we must not show a dialog from the thread
141 // but let the UI thread do it.
142 // To do that, we send a message to the UI thread and let it show the conflict resolver dialog.
143 LRESULT dlgResult = ::SendMessage(GetSafeHwnd(), WM_SHOWCONFLICTRESOLVER, 0, (LPARAM)description);
144 mergedfile = m_mergedfile;
145 return (svn_wc_conflict_choice_t)dlgResult;
148 return svn_wc_conflict_choose_postpone;
150 #endif
151 void CGitProgressList::AddItemToList()
153 int totalcount = GetItemCount();
155 SetItemCountEx(totalcount+1, LVSICF_NOSCROLL|LVSICF_NOINVALIDATEALL);
156 // make columns width fit
157 if (iFirstResized < 30)
159 // only resize the columns for the first 30 or so entries.
160 // after that, don't resize them anymore because that's an
161 // expensive function call and the columns will be sized
162 // close enough already.
163 ResizeColumns();
164 ++iFirstResized;
167 // Make sure the item is *entirely* visible even if the horizontal
168 // scroll bar is visible.
169 int count = GetCountPerPage();
170 if (totalcount <= (GetTopIndex() + count + nEnsureVisibleCount + 2))
172 ++nEnsureVisibleCount;
173 m_bLastVisible = true;
175 else
177 nEnsureVisibleCount = 0;
178 if (IsIconic() == 0)
179 m_bLastVisible = false;
183 CString CGitProgressList::BuildInfoString()
185 CString infotext;
186 m_Command->ShowInfo(infotext);
188 #if 0
190 CString temp;
191 int added = 0;
192 int copied = 0;
193 int deleted = 0;
194 int restored = 0;
195 int reverted = 0;
196 int resolved = 0;
197 int conflicted = 0;
198 int updated = 0;
199 int merged = 0;
200 int modified = 0;
201 int skipped = 0;
202 int replaced = 0;
204 for (size_t i=0; i<m_arData.size(); ++i)
206 const NotificationData * dat = m_arData[i];
207 switch (dat->action)
209 case svn_wc_notify_add:
210 case svn_wc_notify_update_add:
211 case svn_wc_notify_commit_added:
212 if (dat->bConflictedActionItem)
213 ++conflicted;
214 else
215 ++added;
216 break;
217 case svn_wc_notify_copy:
218 ++copied;
219 break;
220 case svn_wc_notify_delete:
221 case svn_wc_notify_update_delete:
222 case svn_wc_notify_commit_deleted:
223 ++deleted;
224 break;
225 case svn_wc_notify_restore:
226 ++restored;
227 break;
228 case svn_wc_notify_revert:
229 ++reverted;
230 break;
231 case svn_wc_notify_resolved:
232 ++resolved;
233 break;
234 case svn_wc_notify_update_update:
235 if (dat->bConflictedActionItem)
236 ++conflicted;
237 else if ((dat->content_state == svn_wc_notify_state_merged) || (dat->prop_state == svn_wc_notify_state_merged))
238 ++merged;
239 else
240 ++updated;
241 break;
242 case svn_wc_notify_commit_modified:
243 ++modified;
244 break;
245 case svn_wc_notify_skip:
246 ++skipped;
247 break;
248 case svn_wc_notify_commit_replaced:
249 ++replaced;
250 break;
253 if (conflicted)
255 temp.LoadString(IDS_SVNACTION_CONFLICTED);
256 infotext += temp;
257 temp.Format(_T(":%d "), conflicted);
258 infotext += temp;
260 if (skipped)
262 temp.LoadString(IDS_SVNACTION_SKIP);
263 infotext += temp;
264 infotext.AppendFormat(_T(":%d "), skipped);
266 if (merged)
268 temp.LoadString(IDS_SVNACTION_MERGED);
269 infotext += temp;
270 infotext.AppendFormat(_T(":%d "), merged);
272 if (added)
274 temp.LoadString(IDS_SVNACTION_ADD);
275 infotext += temp;
276 infotext.AppendFormat(_T(":%d "), added);
278 if (deleted)
280 temp.LoadString(IDS_SVNACTION_DELETE);
281 infotext += temp;
282 infotext.AppendFormat(_T(":%d "), deleted);
284 if (modified)
286 temp.LoadString(IDS_SVNACTION_MODIFIED);
287 infotext += temp;
288 infotext.AppendFormat(_T(":%d "), modified);
290 if (copied)
292 temp.LoadString(IDS_SVNACTION_COPY);
293 infotext += temp;
294 infotext.AppendFormat(_T(":%d "), copied);
296 if (replaced)
298 temp.LoadString(IDS_SVNACTION_REPLACED);
299 infotext += temp;
300 infotext.AppendFormat(_T(":%d "), replaced);
302 if (updated)
304 temp.LoadString(IDS_SVNACTION_UPDATE);
305 infotext += temp;
306 infotext.AppendFormat(_T(":%d "), updated);
308 if (restored)
310 temp.LoadString(IDS_SVNACTION_RESTORE);
311 infotext += temp;
312 infotext.AppendFormat(_T(":%d "), restored);
314 if (reverted)
316 temp.LoadString(IDS_SVNACTION_REVERT);
317 infotext += temp;
318 infotext.AppendFormat(_T(":%d "), reverted);
320 if (resolved)
322 temp.LoadString(IDS_SVNACTION_RESOLVE);
323 infotext += temp;
324 infotext.AppendFormat(_T(":%d "), resolved);
326 #endif
327 return infotext;
330 void CGitProgressList::ResizeColumns()
332 SetRedraw(FALSE);
334 TCHAR textbuf[MAX_PATH] = {0};
336 CHeaderCtrl * pHeaderCtrl = (CHeaderCtrl*)(GetDlgItem(0));
337 if (pHeaderCtrl)
339 int maxcol = pHeaderCtrl->GetItemCount()-1;
340 for (int col = 0; col <= maxcol; ++col)
342 // find the longest width of all items
343 int count = min((int)m_arData.size(), GetItemCount());
344 HDITEM hdi = {0};
345 hdi.mask = HDI_TEXT;
346 hdi.pszText = textbuf;
347 hdi.cchTextMax = _countof(textbuf);
348 pHeaderCtrl->GetItem(col, &hdi);
349 int cx = GetStringWidth(hdi.pszText)+20; // 20 pixels for col separator and margin
351 for (int index = 0; index<count; ++index)
353 // get the width of the string and add 12 pixels for the column separator and margins
354 int linewidth = cx;
355 switch (col)
357 case 0:
358 linewidth = GetStringWidth(m_arData[index]->sActionColumnText) + 12;
359 break;
360 case 1:
361 linewidth = GetStringWidth(m_arData[index]->sPathColumnText) + 12;
362 break;
364 if (cx < linewidth)
365 cx = linewidth;
367 SetColumnWidth(col, cx);
371 SetRedraw(TRUE);
374 bool CGitProgressList::SetBackgroundImage(UINT nID)
376 return CAppUtils::SetListCtrlBackgroundImage(GetSafeHwnd(), nID);
379 void CGitProgressList::ReportGitError()
381 ReportError(CGit::GetLibGit2LastErr());
384 void CGitProgressList::ReportUserCanceled()
386 ReportError(CString(MAKEINTRESOURCE(IDS_USERCANCELLED)));
389 void CGitProgressList::ReportError(const CString& sError)
391 if (CRegDWORD(_T("Software\\TortoiseGit\\NoSounds"), FALSE) == FALSE)
392 PlaySound((LPCTSTR)SND_ALIAS_SYSTEMEXCLAMATION, NULL, SND_ALIAS_ID | SND_ASYNC);
393 ReportString(sError, CString(MAKEINTRESOURCE(IDS_ERR_ERROR)), m_Colors.GetColor(CColors::Conflict));
394 m_bErrorsOccurred = true;
397 void CGitProgressList::ReportWarning(const CString& sWarning)
399 if (CRegDWORD(_T("Software\\TortoiseGit\\NoSounds"), FALSE) == FALSE)
400 PlaySound((LPCTSTR)SND_ALIAS_SYSTEMDEFAULT, NULL, SND_ALIAS_ID | SND_ASYNC);
401 ReportString(sWarning, CString(MAKEINTRESOURCE(IDS_WARN_WARNING)), m_Colors.GetColor(CColors::Conflict));
404 void CGitProgressList::ReportNotification(const CString& sNotification)
406 if (CRegDWORD(_T("Software\\TortoiseGit\\NoSounds"), FALSE) == FALSE)
407 PlaySound((LPCTSTR)SND_ALIAS_SYSTEMDEFAULT, NULL, SND_ALIAS_ID | SND_ASYNC);
408 ReportString(sNotification, CString(MAKEINTRESOURCE(IDS_WARN_NOTE)));
411 void CGitProgressList::ReportCmd(const CString& sCmd)
413 ReportString(sCmd, CString(MAKEINTRESOURCE(IDS_PROGRS_CMDINFO)), m_Colors.GetColor(CColors::Cmd));
416 void CGitProgressList::ReportString(CString sMessage, const CString& sMsgKind, COLORREF color)
418 // instead of showing a dialog box with the error message or notification,
419 // just insert the error text into the list control.
420 // that way the user isn't 'interrupted' by a dialog box popping up!
422 // the message may be split up into different lines
423 // so add a new entry for each line of the message
424 while (!sMessage.IsEmpty())
426 NotificationData * data = new NotificationData();
427 data->bAuxItem = true;
428 data->sActionColumnText = sMsgKind;
429 if (sMessage.Find('\n')>=0)
430 data->sPathColumnText = sMessage.Left(sMessage.Find('\n'));
431 else
432 data->sPathColumnText = sMessage;
433 data->sPathColumnText.Trim(_T("\n\r"));
434 data->color = color;
435 if (sMessage.Find('\n')>=0)
437 sMessage = sMessage.Mid(sMessage.Find('\n'));
438 sMessage.Trim(_T("\n\r"));
440 else
441 sMessage.Empty();
442 AddNotify(data);
446 UINT CGitProgressList::ProgressThreadEntry(LPVOID pVoid)
448 return ((CGitProgressList*)pVoid)->ProgressThread();
451 UINT CGitProgressList::ProgressThread()
453 // The SetParams function should have loaded something for us
455 CString temp;
456 CString sWindowTitle;
457 bool bSuccess = false;
459 if(m_pPostWnd)
460 m_pPostWnd->PostMessage(WM_PROG_CMD_START, (WPARAM)m_Command);
462 if(m_pProgressLabelCtrl)
464 m_pProgressLabelCtrl->ShowWindow(SW_SHOW);
465 m_pProgressLabelCtrl->SetWindowText(_T(""));
468 // SetAndClearProgressInfo(m_hWnd);
469 m_itemCount = m_itemCountTotal;
471 InterlockedExchange(&m_bThreadRunning, TRUE);
472 iFirstResized = 0;
473 bSecondResized = FALSE;
474 m_bFinishedItemAdded = false;
475 DWORD startTime = GetCurrentTime();
477 if (m_pTaskbarList && m_pPostWnd)
478 m_pTaskbarList->SetProgressState(m_pPostWnd->GetSafeHwnd(), TBPF_INDETERMINATE);
480 m_TotalBytesTransferred = 0;
481 if (m_Command)
482 bSuccess = m_Command->Run(this, sWindowTitle, m_itemCountTotal, m_itemCount);
483 else
484 bSuccess = false;
486 if (!bSuccess)
487 temp.LoadString(IDS_PROGRS_TITLEFAILED);
488 else
489 temp.LoadString(IDS_PROGRS_TITLEFIN);
490 sWindowTitle = sWindowTitle + _T(" ") + temp;
491 if (m_bSetTitle && m_pPostWnd)
492 ::SetWindowText(m_pPostWnd->GetSafeHwnd(), sWindowTitle);
494 KillTimer(TRANSFERTIMER);
495 KillTimer(VISIBLETIMER);
497 if (m_pTaskbarList && m_pPostWnd)
499 if (DidErrorsOccur())
501 m_pTaskbarList->SetProgressState(m_pPostWnd->GetSafeHwnd(), TBPF_ERROR);
502 m_pTaskbarList->SetProgressValue(m_pPostWnd->GetSafeHwnd(), 100, 100);
504 else
505 m_pTaskbarList->SetProgressState(m_pPostWnd->GetSafeHwnd(), TBPF_NOPROGRESS);
508 if (m_pInfoCtrl)
510 CString info;
511 if (!bSuccess)
512 info.LoadString(IDS_PROGRS_INFOFAILED);
513 else // this implies that command is not nullptr
514 info = BuildInfoString();
515 m_pInfoCtrl->SetWindowText(info);
518 ResizeColumns();
520 DWORD time = GetCurrentTime() - startTime;
522 CString sFinalInfo;
523 if (!m_sTotalBytesTransferred.IsEmpty())
525 temp.Format(IDS_PROGRS_TIME, (time / 1000) / 60, (time / 1000) % 60);
526 sFinalInfo.Format(IDS_PROGRS_FINALINFO, m_sTotalBytesTransferred, (LPCTSTR)temp);
527 if (m_pProgressLabelCtrl)
528 m_pProgressLabelCtrl->SetWindowText(sFinalInfo);
530 else
532 if (m_pProgressLabelCtrl)
533 m_pProgressLabelCtrl->ShowWindow(SW_HIDE);
536 if (m_pProgControl)
537 m_pProgControl->ShowWindow(SW_HIDE);
539 if (!m_bFinishedItemAdded)
541 CString log, str;
542 if (bSuccess)
543 str.LoadString(IDS_SUCCESS);
544 else
545 str.LoadString(IDS_FAIL);
546 log.Format(_T("%s (%lu ms @ %s)"), str, time, CLoglistUtils::FormatDateAndTime(CTime::GetCurrentTime(), DATE_SHORTDATE, true, false));
548 // there's no "finished: xxx" line at the end. We add one here to make
549 // sure the user sees that the command is actually finished.
550 ReportString(log, CString(MAKEINTRESOURCE(IDS_PROGRS_FINISHED)), bSuccess? RGB(0,0,255) : RGB(255,0,0));
553 int count = GetItemCount();
554 if ((count > 0)&&(m_bLastVisible))
555 EnsureVisible(count-1, FALSE);
557 CLogFile logfile(g_Git.m_CurrentDir);
558 if (logfile.Open())
560 logfile.AddTimeLine();
561 for (size_t i = 0; i < m_arData.size(); ++i)
563 NotificationData * data = m_arData[i];
564 temp.Format(_T("%-20s : %s"), (LPCTSTR)data->sActionColumnText, (LPCTSTR)data->sPathColumnText);
565 logfile.AddLine(temp);
567 if (!sFinalInfo.IsEmpty())
568 logfile.AddLine(sFinalInfo);
569 logfile.Close();
572 m_bCancelled = TRUE;
573 InterlockedExchange(&m_bThreadRunning, FALSE);
575 if (m_pPostWnd)
576 m_pPostWnd->PostMessage(WM_PROG_CMD_FINISH, (WPARAM)m_Command, 0L);
578 //Don't do anything here which might cause messages to be sent to the window
579 //The window thread is probably now blocked in OnOK if we've done an auto close
580 return 0;
583 void CGitProgressList::OnLvnGetdispinfoSvnprogress(NMHDR *pNMHDR, LRESULT *pResult)
585 NMLVDISPINFO *pDispInfo = reinterpret_cast<NMLVDISPINFO*>(pNMHDR);
587 if (pDispInfo)
589 if (pDispInfo->item.mask & LVIF_TEXT)
591 if (pDispInfo->item.iItem < (int)m_arData.size())
593 const NotificationData * data = m_arData[pDispInfo->item.iItem];
594 switch (pDispInfo->item.iSubItem)
596 case 0:
597 lstrcpyn(m_columnbuf, data->sActionColumnText, MAX_PATH);
598 break;
599 case 1:
600 lstrcpyn(m_columnbuf, data->sPathColumnText, pDispInfo->item.cchTextMax - 1);
601 if (!data->bAuxItem)
603 int cWidth = GetColumnWidth(1);
604 cWidth = max(12, cWidth-12);
605 CDC * pDC = GetDC();
606 if (pDC != NULL)
608 CFont * pFont = pDC->SelectObject(GetFont());
609 PathCompactPath(pDC->GetSafeHdc(), m_columnbuf, cWidth);
610 pDC->SelectObject(pFont);
611 ReleaseDC(pDC);
614 break;
615 default:
616 m_columnbuf[0] = 0;
618 pDispInfo->item.pszText = m_columnbuf;
622 *pResult = 0;
625 void CGitProgressList::OnNMCustomdrawSvnprogress(NMHDR *pNMHDR, LRESULT *pResult)
627 NMLVCUSTOMDRAW* pLVCD = reinterpret_cast<NMLVCUSTOMDRAW*>( pNMHDR );
629 // Take the default processing unless we set this to something else below.
630 *pResult = CDRF_DODEFAULT;
632 // First thing - check the draw stage. If it's the control's prepaint
633 // stage, then tell Windows we want messages for every item.
635 if ( CDDS_PREPAINT == pLVCD->nmcd.dwDrawStage )
637 *pResult = CDRF_NOTIFYITEMDRAW;
639 else if ( CDDS_ITEMPREPAINT == pLVCD->nmcd.dwDrawStage )
641 // This is the prepaint stage for an item. Here's where we set the
642 // item's text color. Our return value will tell Windows to draw the
643 // item itself, but it will use the new color we set here.
645 // Tell Windows to paint the control itself.
646 *pResult = CDRF_DODEFAULT;
648 ASSERT(pLVCD->nmcd.dwItemSpec < m_arData.size());
649 if(pLVCD->nmcd.dwItemSpec >= m_arData.size())
651 return;
653 const NotificationData * data = m_arData[pLVCD->nmcd.dwItemSpec];
654 ASSERT(data != NULL);
655 if (data == NULL)
656 return;
658 // Store the color back in the NMLVCUSTOMDRAW struct.
659 pLVCD->clrText = data->color;
663 void CGitProgressList::OnNMDblclkSvnprogress(NMHDR* pNMHDR, LRESULT* pResult)
665 LPNMLISTVIEW pNMLV = reinterpret_cast<LPNMLISTVIEW>(pNMHDR);
666 *pResult = 0;
667 if (pNMLV->iItem < 0)
668 return;
669 if (m_options & ProgOptDryRun || m_bThreadRunning)
670 return; //don't do anything in a dry-run.
672 const NotificationData* data = m_arData[pNMLV->iItem];
673 if (!data)
674 return;
676 data->HandleDblClick();
679 void CGitProgressList::OnHdnItemclickSvnprogress(NMHDR *pNMHDR, LRESULT *pResult)
681 LPNMHEADER phdr = reinterpret_cast<LPNMHEADER>(pNMHDR);
682 if (m_bThreadRunning)
683 return;
684 if (m_nSortedColumn == phdr->iItem)
685 m_bAscending = !m_bAscending;
686 else
687 m_bAscending = TRUE;
688 m_nSortedColumn = phdr->iItem;
689 Sort();
691 CString temp;
692 SetRedraw(FALSE);
693 DeleteAllItems();
694 SetItemCountEx (static_cast<int>(m_arData.size()));
696 SetRedraw(TRUE);
698 *pResult = 0;
701 bool CGitProgressList::NotificationDataIsAux(const NotificationData* pData)
703 return pData->bAuxItem;
706 void CGitProgressList::AddNotify(NotificationData* data, CColors::Colors color)
708 if (color != CColors::COLOR_END)
709 data->color = m_Colors.GetColor(color);
710 else
711 data->SetColorCode(m_Colors);
713 m_arData.push_back(data);
714 AddItemToList();
716 if ((!data->bAuxItem) && (m_itemCount > 0))
718 if (m_pProgControl)
720 m_pProgControl->ShowWindow(SW_SHOW);
721 m_pProgControl->SetPos(m_itemCount);
722 m_pProgControl->SetRange32(0, m_itemCountTotal);
724 if (m_pTaskbarList && m_pPostWnd)
726 m_pTaskbarList->SetProgressState(m_pPostWnd->GetSafeHwnd(), TBPF_NORMAL);
727 m_pTaskbarList->SetProgressValue(m_pPostWnd->GetSafeHwnd(), m_itemCount, m_itemCountTotal);
731 // needed as long as RemoteProgressCommand::RemoteCompletionCallback never gets called by libgit2
732 if (m_pAnimate)
733 m_pAnimate->ShowWindow(SW_HIDE);
736 int CGitProgressList::UpdateProgress(const git_transfer_progress* stat)
738 static unsigned int start = 0;
739 unsigned int dt = GetCurrentTime() - start;
740 double speed = 0;
742 if (m_bCancelled)
744 giterr_set_str(GITERR_NONE, "User cancelled.");
745 return GIT_EUSER;
748 if (dt > 100)
750 start = GetCurrentTime();
751 size_t ds = stat->received_bytes - m_TotalBytesTransferred;
752 speed = ds * 1000.0/dt;
753 m_TotalBytesTransferred = stat->received_bytes;
755 else
756 return 0;
758 int progress;
759 progress = stat->received_objects + stat->indexed_objects;
761 if ((stat->total_objects > 1000) && m_pProgControl && (!m_pProgControl->IsWindowVisible()))
762 m_pProgControl->ShowWindow(SW_SHOW);
764 if (m_pProgressLabelCtrl && m_pProgressLabelCtrl->IsWindowVisible())
765 m_pProgressLabelCtrl->ShowWindow(SW_SHOW);
767 if (m_pProgControl)
769 m_pProgControl->SetPos(progress);
770 m_pProgControl->SetRange32(0, 2 * stat->total_objects);
772 if (m_pTaskbarList && m_pPostWnd)
774 m_pTaskbarList->SetProgressState(m_pPostWnd->GetSafeHwnd(), TBPF_NORMAL);
775 m_pTaskbarList->SetProgressValue(m_pPostWnd->GetSafeHwnd(), progress, 2 * stat->total_objects);
778 CString progText;
779 if (stat->received_bytes < 1024)
780 m_sTotalBytesTransferred.Format(IDS_SVN_PROGRESS_TOTALBYTESTRANSFERRED, (int64_t)stat->received_bytes);
781 else if (stat->received_bytes < 1200000)
782 m_sTotalBytesTransferred.Format(IDS_SVN_PROGRESS_TOTALTRANSFERRED, (int64_t)stat->received_bytes / 1024);
783 else
784 m_sTotalBytesTransferred.Format(IDS_SVN_PROGRESS_TOTALMBTRANSFERRED, (double)((double)stat->received_bytes / 1048576.0));
786 CString str;
787 if(speed < 1024)
788 str.Format(_T("%.0f B/s"), speed);
789 else if(speed < 1024 * 1024)
790 str.Format(_T("%.2f KiB/s"), speed / 1024);
791 else
792 str.Format(_T("%.2f MiB/s"), speed / 1048576.0);
794 progText.Format(IDS_SVN_PROGRESS_TOTALANDSPEED, (LPCTSTR)m_sTotalBytesTransferred, (LPCTSTR)str);
795 if (m_pProgressLabelCtrl)
796 m_pProgressLabelCtrl->SetWindowText(progText);
798 SetTimer(TRANSFERTIMER, 2000, NULL);
800 return 0;
803 void CGitProgressList::OnTimer(UINT_PTR nIDEvent)
805 if (nIDEvent == TRANSFERTIMER)
807 CString progText;
808 CString progSpeed = _T("0 B/s");
809 progText.Format(IDS_SVN_PROGRESS_TOTALANDSPEED, (LPCTSTR)m_sTotalBytesTransferred, (LPCTSTR)progSpeed);
810 if (m_pProgressLabelCtrl)
811 m_pProgressLabelCtrl->SetWindowText(progText);
813 KillTimer(TRANSFERTIMER);
815 if (nIDEvent == VISIBLETIMER)
817 if (nEnsureVisibleCount)
818 EnsureVisible(GetItemCount()-1, false);
819 nEnsureVisibleCount = 0;
823 void CGitProgressList::Sort()
825 if(m_arData.size() < 2)
827 return;
830 // We need to sort the blocks which lie between the auxiliary entries
831 // This is so that any aux data stays where it was
832 NotificationDataVect::iterator actionBlockBegin;
833 NotificationDataVect::iterator actionBlockEnd = m_arData.begin(); // We start searching from here
835 for(;;)
837 // Search to the start of the non-aux entry in the next block
838 actionBlockBegin = std::find_if(actionBlockEnd, m_arData.end(), std::not1(std::ptr_fun(&CGitProgressList::NotificationDataIsAux)));
839 if(actionBlockBegin == m_arData.end())
841 // There are no more actions
842 break;
844 // Now search to find the end of the block
845 actionBlockEnd = std::find_if(actionBlockBegin+1, m_arData.end(), std::ptr_fun(&CGitProgressList::NotificationDataIsAux));
846 // Now sort the block
847 std::sort(actionBlockBegin, actionBlockEnd, &CGitProgressList::SortCompare);
851 bool CGitProgressList::SortCompare(const NotificationData * pData1, const NotificationData * pData2)
853 int result = 0;
854 switch (m_nSortedColumn)
856 case 0: //action column
857 result = pData1->sActionColumnText.Compare(pData2->sActionColumnText);
858 break;
859 case 1: //path column
860 // Compare happens after switch()
861 break;
862 default:
863 break;
866 // Sort by path if everything else is equal
867 if (result == 0)
869 result = CTGitPath::Compare(pData1->path, pData2->path);
872 if (!m_bAscending)
873 result = -result;
874 return result < 0;
877 void CGitProgressList::OnContextMenu(CWnd* pWnd, CPoint point)
879 if (m_options & ProgOptDryRun)
880 return; // don't do anything in a dry-run.
882 if (pWnd != this)
883 return;
885 int selIndex = GetSelectionMark();
886 if ((point.x == -1) && (point.y == -1))
888 // Menu was invoked from the keyboard rather than by right-clicking
889 CRect rect;
890 GetItemRect(selIndex, &rect, LVIR_LABEL);
891 ClientToScreen(&rect);
892 point = rect.CenterPoint();
895 if ((selIndex < 0) || m_bThreadRunning || GetSelectedCount() == 0)
896 return;
898 // entry is selected, thread has finished with updating so show the popup menu
899 CIconMenu popup;
900 if (!popup.CreatePopupMenu())
901 return;
903 ContextMenuActionList actions;
904 NotificationData* data = m_arData[selIndex];
905 if (data && GetSelectedCount() == 1)
906 data->GetContextMenu(popup, actions);
908 if (!actions.empty())
909 popup.AppendMenu(MF_SEPARATOR, NULL);
910 actions.push_back([&]()
912 CString sLines;
913 POSITION pos = GetFirstSelectedItemPosition();
914 while (pos)
916 int nItem = GetNextSelectedItem(pos);
917 NotificationData* data = m_arData[nItem];
918 if (data)
920 sLines += data->sPathColumnText;
921 sLines += _T("\r\n");
924 sLines.TrimRight();
925 if (!sLines.IsEmpty())
926 CStringUtils::WriteAsciiStringToClipboard(sLines, GetSafeHwnd());
928 popup.AppendMenuIcon(actions.size(), IDS_LOG_POPUP_COPYTOCLIPBOARD, IDI_COPYCLIP);
930 if (actions.empty())
931 return;
933 int cmd = popup.TrackPopupMenu(TPM_RETURNCMD | TPM_LEFTALIGN | TPM_NONOTIFY, point.x, point.y, this, 0);
935 if (cmd <= 0 || cmd > actions.size())
936 return;
938 theApp.DoWaitCursor(1);
939 actions.at(cmd - 1)();
940 theApp.DoWaitCursor(-1);
943 void CGitProgressList::OnLvnBegindragSvnprogress(NMHDR* , LRESULT *pResult)
945 //LPNMLISTVIEW pNMLV = reinterpret_cast<LPNMLISTVIEW>(pNMHDR);
946 #if 0
947 int selIndex = GetSelectionMark();
948 if (selIndex < 0)
949 return;
951 CDropFiles dropFiles; // class for creating DROPFILES struct
953 int index;
954 POSITION pos = GetFirstSelectedItemPosition();
955 while ( (index = GetNextSelectedItem(pos)) >= 0 )
957 NotificationData * data = m_arData[index];
959 if ( data->kind==svn_node_file || data->kind==svn_node_dir )
961 CString sPath = GetPathFromColumnText(data->sPathColumnText);
963 dropFiles.AddFile( sPath );
967 if (!dropFiles.IsEmpty())
969 dropFiles.CreateStructure();
971 #endif
972 *pResult = 0;
975 void CGitProgressList::OnSize(UINT nType, int cx, int cy)
977 CListCtrl::OnSize(nType, cx, cy);
978 if ((nType == SIZE_RESTORED)&&(m_bLastVisible))
980 if(!m_hWnd)
981 return;
983 int count = GetItemCount();
984 if (count > 0)
985 EnsureVisible(count-1, false);
989 void CGitProgressList::Init()
991 SetExtendedStyle (LVS_EX_FULLROWSELECT | LVS_EX_DOUBLEBUFFER);
993 DeleteAllItems();
994 int c = ((CHeaderCtrl*)(GetDlgItem(0)))->GetItemCount()-1;
995 while (c>=0)
996 DeleteColumn(c--);
998 CString temp;
999 temp.LoadString(IDS_PROGRS_ACTION);
1000 InsertColumn(0, temp);
1001 temp.LoadString(IDS_PROGRS_PATH);
1002 InsertColumn(1, temp);
1004 m_pThread = AfxBeginThread(ProgressThreadEntry, this, THREAD_PRIORITY_NORMAL,0,CREATE_SUSPENDED);
1005 if (m_pThread==NULL)
1007 ReportError(CString(MAKEINTRESOURCE(IDS_ERR_THREADSTARTFAILED)));
1009 else
1011 m_pThread->m_bAutoDelete = FALSE;
1012 m_pThread->ResumeThread();
1015 // Call this early so that the column headings aren't hidden before any
1016 // text gets added.
1017 ResizeColumns();
1019 SetTimer(VISIBLETIMER, 300, NULL);
1023 void CGitProgressList::OnClose()
1025 if (m_bCancelled)
1027 TerminateThread(m_pThread->m_hThread, (DWORD)-1);
1028 InterlockedExchange(&m_bThreadRunning, FALSE);
1030 else
1032 m_bCancelled = TRUE;
1033 return;
1035 CListCtrl::OnClose();
1039 BOOL CGitProgressList::PreTranslateMessage(MSG* pMsg)
1041 if (pMsg->message == WM_KEYDOWN)
1043 if (pMsg->wParam == 'A')
1045 if (GetKeyState(VK_CONTROL)&0x8000)
1047 // Ctrl-A -> select all
1048 SetSelectionMark(0);
1049 for (int i=0; i<GetItemCount(); ++i)
1051 SetItemState(i, LVIS_SELECTED, LVIS_SELECTED);
1055 if ((pMsg->wParam == 'C')||(pMsg->wParam == VK_INSERT))
1057 int selIndex = GetSelectionMark();
1058 if (selIndex >= 0)
1060 if (GetKeyState(VK_CONTROL)&0x8000)
1062 //Ctrl-C -> copy to clipboard
1063 CString sClipdata;
1064 POSITION pos = GetFirstSelectedItemPosition();
1065 if (pos != NULL)
1067 while (pos)
1069 int nItem = GetNextSelectedItem(pos);
1070 CString sAction = GetItemText(nItem, 0);
1071 CString sPath = GetItemText(nItem, 1);
1072 CString sMime = GetItemText(nItem, 2);
1073 CString sLogCopyText;
1074 sLogCopyText.Format(_T("%s: %s %s\r\n"),
1075 (LPCTSTR)sAction, (LPCTSTR)sPath, (LPCTSTR)sMime);
1076 sClipdata += sLogCopyText;
1078 CStringUtils::WriteAsciiStringToClipboard(sClipdata);
1083 } // if (pMsg->message == WM_KEYDOWN)
1084 return CListCtrl::PreTranslateMessage(pMsg);
1087 void CGitProgressList::SetWindowTitle(UINT id, const CString& urlorpath, CString& dialogname)
1089 if (!m_bSetTitle || !m_pPostWnd)
1090 return;
1092 dialogname.LoadString(id);
1093 CAppUtils::SetWindowTitle(m_pPostWnd->GetSafeHwnd(), urlorpath, dialogname);
1096 void CGitProgressList::ShowProgressBar()
1098 if (m_pProgControl)
1100 m_pProgControl->ShowWindow(SW_SHOW);
1101 m_pProgControl->SetPos(0);
1102 m_pProgControl->SetRange32(0, 1);
1106 void CGitProgressList::SetProgressLabelText(const CString& str)
1108 if (m_pProgressLabelCtrl)
1109 m_pProgressLabelCtrl->SetWindowText(str);
1112 CGitProgressList::WC_File_NotificationData::WC_File_NotificationData(const CTGitPath& path, git_wc_notify_action_t action)
1113 : NotificationData()
1115 this->action = action;
1116 this->path = path;
1117 sPathColumnText = path.GetGitPathString();
1119 switch (action)
1121 case git_wc_notify_add:
1122 sActionColumnText.LoadString(IDS_SVNACTION_ADD);
1123 break;
1124 case git_wc_notify_resolved:
1125 sActionColumnText.LoadString(IDS_SVNACTION_RESOLVE);
1126 break;
1127 case git_wc_notify_revert:
1128 sActionColumnText.LoadString(IDS_SVNACTION_REVERT);
1129 break;
1130 case git_wc_notify_checkout:
1131 sActionColumnText.LoadString(IDS_PROGRS_CMD_CHECKOUT);
1132 break;
1133 default:
1134 break;
1138 void CGitProgressList::WC_File_NotificationData::SetColorCode(CColors& colors)
1140 switch (action)
1142 case git_wc_notify_checkout: // fall-through
1143 case git_wc_notify_add:
1144 color = colors.GetColor(CColors::Added);
1145 break;
1147 default:
1148 break;
1152 void CGitProgressList::WC_File_NotificationData::GetContextMenu(CIconMenu& popup, ContextMenuActionList& actions)
1154 if ((action == git_wc_notify_add) ||
1155 (action == git_wc_notify_revert) ||
1156 (action == git_wc_notify_resolved) ||
1157 (action == git_wc_notify_checkout))
1159 actions.push_back([&]()
1161 CString cmd = _T("/command:log");
1162 CString sPath = g_Git.CombinePath(path);
1163 cmd += _T(" /path:\"") + sPath + _T("\"");
1164 CAppUtils::RunTortoiseGitProc(cmd);
1166 popup.AppendMenuIcon(actions.size(), IDS_MENULOG, IDI_LOG);
1168 popup.AppendMenu(MF_SEPARATOR, NULL);
1169 if (!PathIsDirectory(g_Git.CombinePath(path)))
1171 actions.push_back([&]{ CAppUtils::ShellOpen(g_Git.CombinePath(path)); });
1172 popup.AppendMenuIcon(actions.size(), IDS_LOG_POPUP_OPEN, IDI_OPEN);
1173 actions.push_back([&]{ CAppUtils::ShowOpenWithDialog(g_Git.CombinePath(path)); });
1174 popup.AppendMenuIcon(actions.size(), IDS_LOG_POPUP_OPENWITH, IDI_OPEN);
1177 actions.push_back([&]{ CAppUtils::ExploreTo(nullptr, g_Git.CombinePath(path)); });
1178 popup.AppendMenuIcon(actions.size(), IDS_STATUSLIST_CONTEXT_EXPLORE, IDI_EXPLORER);
1182 void CGitProgressList::WC_File_NotificationData::HandleDblClick() const
1184 CString sWinPath = g_Git.CombinePath(path);
1185 if (PathIsDirectory(sWinPath))
1187 CAppUtils::ExploreTo(nullptr, sWinPath);
1188 return;
1190 CAppUtils::ShellOpen(sWinPath);