Add PostCmdActions to GitProgressList
[TortoiseGit.git] / src / TortoiseProc / GitProgressList.cpp
bloba37c8b7ae2b2529c85ff6fdd30d6e54d08ad2a39
1 // TortoiseGit - a Windows shell extension for easy version control
3 // Copyright (C) 2008-2014 - 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 "SoundUtils.h"
27 #include "LogFile.h"
28 #include "LoglistUtils.h"
29 #include "AppUtils.h"
31 BOOL CGitProgressList::m_bAscending = FALSE;
32 int CGitProgressList::m_nSortedColumn = -1;
34 #define TRANSFERTIMER 100
35 #define VISIBLETIMER 101
36 // CGitProgressList
38 IMPLEMENT_DYNAMIC(CGitProgressList, CListCtrl)
40 CGitProgressList::CGitProgressList():CListCtrl()
41 , m_bCancelled(FALSE)
42 , m_pThread(NULL)
43 , m_bErrorsOccurred(false)
44 , m_options(ProgOptNone)
45 , m_bSetTitle(false)
46 , m_pTaskbarList(nullptr)
47 , m_Command(nullptr)
48 , m_bThreadRunning(FALSE)
49 , iFirstResized(0)
50 , bSecondResized(false)
51 , nEnsureVisibleCount(0)
52 , m_TotalBytesTransferred(0)
53 , m_bFinishedItemAdded(false)
54 , m_bLastVisible(false)
55 , m_itemCount(-1)
56 , m_itemCountTotal(-1)
58 m_pInfoCtrl = nullptr;
59 m_pAnimate = nullptr;
60 m_pProgControl = nullptr;
61 m_pProgressLabelCtrl = nullptr;
62 m_pPostWnd = nullptr;
63 m_columnbuf[0] = 0;
66 CGitProgressList::~CGitProgressList()
68 for (size_t i = 0; i < m_arData.size(); ++i)
70 delete m_arData[i];
72 if(m_pThread != NULL)
74 delete m_pThread;
79 BEGIN_MESSAGE_MAP(CGitProgressList, CListCtrl)
80 ON_NOTIFY_REFLECT(NM_CUSTOMDRAW, OnNMCustomdrawSvnprogress)
81 ON_NOTIFY_REFLECT(NM_DBLCLK, OnNMDblclkSvnprogress)
82 ON_NOTIFY_REFLECT(HDN_ITEMCLICK, OnHdnItemclickSvnprogress)
83 ON_NOTIFY_REFLECT(LVN_BEGINDRAG, OnLvnBegindragSvnprogress)
84 ON_NOTIFY_REFLECT(LVN_GETDISPINFO, OnLvnGetdispinfoSvnprogress)
85 ON_MESSAGE(WM_SHOWCONFLICTRESOLVER, OnShowConflictResolver)
86 ON_WM_SIZE()
87 ON_WM_TIMER()
88 ON_WM_CONTEXTMENU()
89 ON_WM_CLOSE()
90 END_MESSAGE_MAP()
92 void CGitProgressList::Cancel()
94 m_bCancelled = TRUE;
99 // CGitProgressList message handlers
102 LRESULT CGitProgressList::OnShowConflictResolver(WPARAM /*wParam*/, LPARAM /*lParam*/)
104 #if 0
105 CConflictResolveDlg dlg(this);
106 const svn_wc_conflict_description_t *description = (svn_wc_conflict_description_t *)lParam;
107 if (description)
109 dlg.SetConflictDescription(description);
110 if (m_pTaskbarList)
112 m_pTaskbarList->SetProgressState(m_hWnd, TBPF_PAUSED);
114 if (dlg.DoModal() == IDOK)
116 if (dlg.GetResult() == svn_wc_conflict_choose_postpone)
118 // if the result is conflicted and the dialog returned IDOK,
119 // that means we should not ask again in case of a conflict
120 m_AlwaysConflicted = true;
121 ::SendMessage(GetDlgItem(IDC_NONINTERACTIVE)->GetSafeHwnd(), BM_SETCHECK, BST_CHECKED, 0);
124 m_mergedfile = dlg.GetMergedFile();
125 m_bCancelled = dlg.IsCancelled();
126 if (m_pTaskbarList)
127 m_pTaskbarList->SetProgressState(m_hWnd, TBPF_INDETERMINATE);
128 return dlg.GetResult();
131 return svn_wc_conflict_choose_postpone;
132 #endif
133 return 0;
135 #if 0
136 svn_wc_conflict_choice_t CGitProgressList::ConflictResolveCallback(const svn_wc_conflict_description_t *description, CString& mergedfile)
138 // we only bother the user when merging
139 if (((m_Command == GitProgress_Merge)||(m_Command == GitProgress_MergeAll)||(m_Command == GitProgress_MergeReintegrate))&&(!m_AlwaysConflicted)&&(description))
141 // we're in a worker thread here. That means we must not show a dialog from the thread
142 // but let the UI thread do it.
143 // To do that, we send a message to the UI thread and let it show the conflict resolver dialog.
144 LRESULT dlgResult = ::SendMessage(GetSafeHwnd(), WM_SHOWCONFLICTRESOLVER, 0, (LPARAM)description);
145 mergedfile = m_mergedfile;
146 return (svn_wc_conflict_choice_t)dlgResult;
149 return svn_wc_conflict_choose_postpone;
151 #endif
152 void CGitProgressList::AddItemToList()
154 int totalcount = GetItemCount();
156 SetItemCountEx(totalcount+1, LVSICF_NOSCROLL|LVSICF_NOINVALIDATEALL);
157 // make columns width fit
158 if (iFirstResized < 30)
160 // only resize the columns for the first 30 or so entries.
161 // after that, don't resize them anymore because that's an
162 // expensive function call and the columns will be sized
163 // close enough already.
164 ResizeColumns();
165 ++iFirstResized;
168 // Make sure the item is *entirely* visible even if the horizontal
169 // scroll bar is visible.
170 int count = GetCountPerPage();
171 if (totalcount <= (GetTopIndex() + count + nEnsureVisibleCount + 2))
173 ++nEnsureVisibleCount;
174 m_bLastVisible = true;
176 else
178 nEnsureVisibleCount = 0;
179 if (IsIconic() == 0)
180 m_bLastVisible = false;
184 CString CGitProgressList::BuildInfoString()
186 CString infotext;
187 m_Command->ShowInfo(infotext);
189 #if 0
191 CString temp;
192 int added = 0;
193 int copied = 0;
194 int deleted = 0;
195 int restored = 0;
196 int reverted = 0;
197 int resolved = 0;
198 int conflicted = 0;
199 int updated = 0;
200 int merged = 0;
201 int modified = 0;
202 int skipped = 0;
203 int replaced = 0;
205 for (size_t i=0; i<m_arData.size(); ++i)
207 const NotificationData * dat = m_arData[i];
208 switch (dat->action)
210 case svn_wc_notify_add:
211 case svn_wc_notify_update_add:
212 case svn_wc_notify_commit_added:
213 if (dat->bConflictedActionItem)
214 ++conflicted;
215 else
216 ++added;
217 break;
218 case svn_wc_notify_copy:
219 ++copied;
220 break;
221 case svn_wc_notify_delete:
222 case svn_wc_notify_update_delete:
223 case svn_wc_notify_commit_deleted:
224 ++deleted;
225 break;
226 case svn_wc_notify_restore:
227 ++restored;
228 break;
229 case svn_wc_notify_revert:
230 ++reverted;
231 break;
232 case svn_wc_notify_resolved:
233 ++resolved;
234 break;
235 case svn_wc_notify_update_update:
236 if (dat->bConflictedActionItem)
237 ++conflicted;
238 else if ((dat->content_state == svn_wc_notify_state_merged) || (dat->prop_state == svn_wc_notify_state_merged))
239 ++merged;
240 else
241 ++updated;
242 break;
243 case svn_wc_notify_commit_modified:
244 ++modified;
245 break;
246 case svn_wc_notify_skip:
247 ++skipped;
248 break;
249 case svn_wc_notify_commit_replaced:
250 ++replaced;
251 break;
254 if (conflicted)
256 temp.LoadString(IDS_SVNACTION_CONFLICTED);
257 infotext += temp;
258 temp.Format(_T(":%d "), conflicted);
259 infotext += temp;
261 if (skipped)
263 temp.LoadString(IDS_SVNACTION_SKIP);
264 infotext += temp;
265 infotext.AppendFormat(_T(":%d "), skipped);
267 if (merged)
269 temp.LoadString(IDS_SVNACTION_MERGED);
270 infotext += temp;
271 infotext.AppendFormat(_T(":%d "), merged);
273 if (added)
275 temp.LoadString(IDS_SVNACTION_ADD);
276 infotext += temp;
277 infotext.AppendFormat(_T(":%d "), added);
279 if (deleted)
281 temp.LoadString(IDS_SVNACTION_DELETE);
282 infotext += temp;
283 infotext.AppendFormat(_T(":%d "), deleted);
285 if (modified)
287 temp.LoadString(IDS_SVNACTION_MODIFIED);
288 infotext += temp;
289 infotext.AppendFormat(_T(":%d "), modified);
291 if (copied)
293 temp.LoadString(IDS_SVNACTION_COPY);
294 infotext += temp;
295 infotext.AppendFormat(_T(":%d "), copied);
297 if (replaced)
299 temp.LoadString(IDS_SVNACTION_REPLACED);
300 infotext += temp;
301 infotext.AppendFormat(_T(":%d "), replaced);
303 if (updated)
305 temp.LoadString(IDS_SVNACTION_UPDATE);
306 infotext += temp;
307 infotext.AppendFormat(_T(":%d "), updated);
309 if (restored)
311 temp.LoadString(IDS_SVNACTION_RESTORE);
312 infotext += temp;
313 infotext.AppendFormat(_T(":%d "), restored);
315 if (reverted)
317 temp.LoadString(IDS_SVNACTION_REVERT);
318 infotext += temp;
319 infotext.AppendFormat(_T(":%d "), reverted);
321 if (resolved)
323 temp.LoadString(IDS_SVNACTION_RESOLVE);
324 infotext += temp;
325 infotext.AppendFormat(_T(":%d "), resolved);
327 #endif
328 return infotext;
331 void CGitProgressList::ResizeColumns()
333 SetRedraw(FALSE);
335 TCHAR textbuf[MAX_PATH] = {0};
337 CHeaderCtrl * pHeaderCtrl = (CHeaderCtrl*)(GetDlgItem(0));
338 if (pHeaderCtrl)
340 int maxcol = pHeaderCtrl->GetItemCount()-1;
341 for (int col = 0; col <= maxcol; ++col)
343 // find the longest width of all items
344 int count = GetItemCount();
345 HDITEM hdi = {0};
346 hdi.mask = HDI_TEXT;
347 hdi.pszText = textbuf;
348 hdi.cchTextMax = _countof(textbuf);
349 pHeaderCtrl->GetItem(col, &hdi);
350 int cx = GetStringWidth(hdi.pszText)+20; // 20 pixels for col separator and margin
352 for (int index = 0; index<count; ++index)
354 // get the width of the string and add 12 pixels for the column separator and margins
355 int linewidth = cx;
356 switch (col)
358 case 0:
359 linewidth = GetStringWidth(m_arData[index]->sActionColumnText) + 12;
360 break;
361 case 1:
362 linewidth = GetStringWidth(m_arData[index]->sPathColumnText) + 12;
363 break;
365 if (cx < linewidth)
366 cx = linewidth;
368 SetColumnWidth(col, cx);
372 SetRedraw(TRUE);
375 bool CGitProgressList::SetBackgroundImage(UINT nID)
377 return CAppUtils::SetListCtrlBackgroundImage(GetSafeHwnd(), nID);
380 void CGitProgressList::ReportGitError()
382 ReportError(CGit::GetLibGit2LastErr());
385 void CGitProgressList::ReportUserCanceled()
387 ReportError(CString(MAKEINTRESOURCE(IDS_SVN_USERCANCELLED)));
390 void CGitProgressList::ReportError(const CString& sError)
392 CSoundUtils::PlayTGitError();
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 CSoundUtils::PlayTGitWarning();
400 ReportString(sWarning, CString(MAKEINTRESOURCE(IDS_WARN_WARNING)), m_Colors.GetColor(CColors::Conflict));
403 void CGitProgressList::ReportNotification(const CString& sNotification)
405 CSoundUtils::PlayTGitNotification();
406 ReportString(sNotification, CString(MAKEINTRESOURCE(IDS_WARN_NOTE)));
409 void CGitProgressList::ReportCmd(const CString& sCmd)
411 ReportString(sCmd, CString(MAKEINTRESOURCE(IDS_PROGRS_CMDINFO)), m_Colors.GetColor(CColors::Cmd));
414 void CGitProgressList::ReportString(CString sMessage, const CString& sMsgKind, COLORREF color)
416 // instead of showing a dialog box with the error message or notification,
417 // just insert the error text into the list control.
418 // that way the user isn't 'interrupted' by a dialog box popping up!
420 // the message may be split up into different lines
421 // so add a new entry for each line of the message
422 while (!sMessage.IsEmpty())
424 NotificationData * data = new NotificationData();
425 data->bAuxItem = true;
426 data->sActionColumnText = sMsgKind;
427 if (sMessage.Find('\n')>=0)
428 data->sPathColumnText = sMessage.Left(sMessage.Find('\n'));
429 else
430 data->sPathColumnText = sMessage;
431 data->sPathColumnText.Trim(_T("\n\r"));
432 data->color = color;
433 if (sMessage.Find('\n')>=0)
435 sMessage = sMessage.Mid(sMessage.Find('\n'));
436 sMessage.Trim(_T("\n\r"));
438 else
439 sMessage.Empty();
440 AddNotify(data);
444 UINT CGitProgressList::ProgressThreadEntry(LPVOID pVoid)
446 return ((CGitProgressList*)pVoid)->ProgressThread();
449 UINT CGitProgressList::ProgressThread()
451 // The SetParams function should have loaded something for us
453 CString temp;
454 CString sWindowTitle;
455 bool bSuccess = false;
457 if(m_pPostWnd)
458 m_pPostWnd->PostMessage(WM_PROG_CMD_START, (WPARAM)m_Command);
460 if(m_pProgressLabelCtrl)
462 m_pProgressLabelCtrl->ShowWindow(SW_SHOW);
463 m_pProgressLabelCtrl->SetWindowText(_T(""));
466 // SetAndClearProgressInfo(m_hWnd);
467 m_itemCount = m_itemCountTotal;
469 InterlockedExchange(&m_bThreadRunning, TRUE);
470 iFirstResized = 0;
471 bSecondResized = FALSE;
472 m_bFinishedItemAdded = false;
473 DWORD startTime = GetCurrentTime();
475 if (m_pTaskbarList && m_pPostWnd)
476 m_pTaskbarList->SetProgressState(m_pPostWnd->GetSafeHwnd(), TBPF_INDETERMINATE);
478 m_TotalBytesTransferred = 0;
479 if (m_Command)
480 bSuccess = m_Command->Run(this, sWindowTitle, m_itemCountTotal, m_itemCount);
481 else
482 bSuccess = false;
484 if (!bSuccess)
485 temp.LoadString(IDS_PROGRS_TITLEFAILED);
486 else
487 temp.LoadString(IDS_PROGRS_TITLEFIN);
488 sWindowTitle = sWindowTitle + _T(" ") + temp;
489 if (m_bSetTitle && m_pPostWnd)
490 ::SetWindowText(m_pPostWnd->GetSafeHwnd(), sWindowTitle);
492 KillTimer(TRANSFERTIMER);
493 KillTimer(VISIBLETIMER);
495 if (m_pTaskbarList && m_pPostWnd)
497 if (DidErrorsOccur())
499 m_pTaskbarList->SetProgressState(m_pPostWnd->GetSafeHwnd(), TBPF_ERROR);
500 m_pTaskbarList->SetProgressValue(m_pPostWnd->GetSafeHwnd(), 100, 100);
502 else
503 m_pTaskbarList->SetProgressState(m_pPostWnd->GetSafeHwnd(), TBPF_NOPROGRESS);
506 if (m_pInfoCtrl)
508 CString info;
509 if (!bSuccess)
510 info.LoadString(IDS_PROGRS_INFOFAILED);
511 else // this implies that command is not nullptr
512 info = BuildInfoString();
513 m_pInfoCtrl->SetWindowText(info);
516 ResizeColumns();
518 DWORD time = GetCurrentTime() - startTime;
520 CString sFinalInfo;
521 if (!m_sTotalBytesTransferred.IsEmpty())
523 temp.Format(IDS_PROGRS_TIME, (time / 1000) / 60, (time / 1000) % 60);
524 sFinalInfo.Format(IDS_PROGRS_FINALINFO, m_sTotalBytesTransferred, (LPCTSTR)temp);
525 if (m_pProgressLabelCtrl)
526 m_pProgressLabelCtrl->SetWindowText(sFinalInfo);
528 else
530 if (m_pProgressLabelCtrl)
531 m_pProgressLabelCtrl->ShowWindow(SW_HIDE);
534 if (m_pProgControl)
535 m_pProgControl->ShowWindow(SW_HIDE);
537 if (!m_bFinishedItemAdded)
539 CString log, str;
540 if (bSuccess)
541 str.LoadString(IDS_SUCCESS);
542 else
543 str.LoadString(IDS_FAIL);
544 log.Format(_T("%s (%lu ms @ %s)"), str, time, CLoglistUtils::FormatDateAndTime(CTime::GetCurrentTime(), DATE_SHORTDATE, true, false));
546 // there's no "finished: xxx" line at the end. We add one here to make
547 // sure the user sees that the command is actually finished.
548 ReportString(log, CString(MAKEINTRESOURCE(IDS_PROGRS_FINISHED)), bSuccess? RGB(0,0,255) : RGB(255,0,0));
551 int count = GetItemCount();
552 if ((count > 0)&&(m_bLastVisible))
553 EnsureVisible(count-1, FALSE);
555 CLogFile logfile(g_Git.m_CurrentDir);
556 if (logfile.Open())
558 logfile.AddTimeLine();
559 for (size_t i = 0; i < m_arData.size(); ++i)
561 NotificationData * data = m_arData[i];
562 temp.Format(_T("%-20s : %s"), (LPCTSTR)data->sActionColumnText, (LPCTSTR)data->sPathColumnText);
563 logfile.AddLine(temp);
565 if (!sFinalInfo.IsEmpty())
566 logfile.AddLine(sFinalInfo);
567 logfile.Close();
570 m_bCancelled = TRUE;
571 InterlockedExchange(&m_bThreadRunning, FALSE);
573 if (m_pPostWnd)
574 m_pPostWnd->PostMessage(WM_PROG_CMD_FINISH, (WPARAM)m_Command, 0L);
576 //Don't do anything here which might cause messages to be sent to the window
577 //The window thread is probably now blocked in OnOK if we've done an auto close
578 return 0;
581 void CGitProgressList::OnLvnGetdispinfoSvnprogress(NMHDR *pNMHDR, LRESULT *pResult)
583 NMLVDISPINFO *pDispInfo = reinterpret_cast<NMLVDISPINFO*>(pNMHDR);
585 if (pDispInfo)
587 if (pDispInfo->item.mask & LVIF_TEXT)
589 if (pDispInfo->item.iItem < (int)m_arData.size())
591 const NotificationData * data = m_arData[pDispInfo->item.iItem];
592 switch (pDispInfo->item.iSubItem)
594 case 0:
595 lstrcpyn(m_columnbuf, data->sActionColumnText, MAX_PATH);
596 break;
597 case 1:
598 lstrcpyn(m_columnbuf, data->sPathColumnText, pDispInfo->item.cchTextMax);
599 if (!data->bAuxItem)
601 int cWidth = GetColumnWidth(1);
602 cWidth = max(12, cWidth-12);
603 CDC * pDC = GetDC();
604 if (pDC != NULL)
606 CFont * pFont = pDC->SelectObject(GetFont());
607 PathCompactPath(pDC->GetSafeHdc(), m_columnbuf, cWidth);
608 pDC->SelectObject(pFont);
609 ReleaseDC(pDC);
612 break;
613 default:
614 m_columnbuf[0] = 0;
616 pDispInfo->item.pszText = m_columnbuf;
620 *pResult = 0;
623 void CGitProgressList::OnNMCustomdrawSvnprogress(NMHDR *pNMHDR, LRESULT *pResult)
625 NMLVCUSTOMDRAW* pLVCD = reinterpret_cast<NMLVCUSTOMDRAW*>( pNMHDR );
627 // Take the default processing unless we set this to something else below.
628 *pResult = CDRF_DODEFAULT;
630 // First thing - check the draw stage. If it's the control's prepaint
631 // stage, then tell Windows we want messages for every item.
633 if ( CDDS_PREPAINT == pLVCD->nmcd.dwDrawStage )
635 *pResult = CDRF_NOTIFYITEMDRAW;
637 else if ( CDDS_ITEMPREPAINT == pLVCD->nmcd.dwDrawStage )
639 // This is the prepaint stage for an item. Here's where we set the
640 // item's text color. Our return value will tell Windows to draw the
641 // item itself, but it will use the new color we set here.
643 // Tell Windows to paint the control itself.
644 *pResult = CDRF_DODEFAULT;
646 ASSERT(pLVCD->nmcd.dwItemSpec < m_arData.size());
647 if(pLVCD->nmcd.dwItemSpec >= m_arData.size())
649 return;
651 const NotificationData * data = m_arData[pLVCD->nmcd.dwItemSpec];
652 ASSERT(data != NULL);
653 if (data == NULL)
654 return;
656 // Store the color back in the NMLVCUSTOMDRAW struct.
657 pLVCD->clrText = data->color;
661 void CGitProgressList::OnNMDblclkSvnprogress(NMHDR* pNMHDR, LRESULT* pResult)
663 LPNMLISTVIEW pNMLV = reinterpret_cast<LPNMLISTVIEW>(pNMHDR);
664 *pResult = 0;
665 if (pNMLV->iItem < 0)
666 return;
667 if (m_options & ProgOptDryRun || m_bThreadRunning)
668 return; //don't do anything in a dry-run.
670 const NotificationData* data = m_arData[pNMLV->iItem];
671 if (!data)
672 return;
674 data->HandleDblClick();
677 void CGitProgressList::OnHdnItemclickSvnprogress(NMHDR *pNMHDR, LRESULT *pResult)
679 LPNMHEADER phdr = reinterpret_cast<LPNMHEADER>(pNMHDR);
680 if (m_bThreadRunning)
681 return;
682 if (m_nSortedColumn == phdr->iItem)
683 m_bAscending = !m_bAscending;
684 else
685 m_bAscending = TRUE;
686 m_nSortedColumn = phdr->iItem;
687 Sort();
689 CString temp;
690 SetRedraw(FALSE);
691 DeleteAllItems();
692 SetItemCountEx (static_cast<int>(m_arData.size()));
694 SetRedraw(TRUE);
696 *pResult = 0;
699 bool CGitProgressList::NotificationDataIsAux(const NotificationData* pData)
701 return pData->bAuxItem;
704 void CGitProgressList::AddNotify(NotificationData* data, CColors::Colors color)
706 if (color != CColors::COLOR_END)
707 data->color = m_Colors.GetColor(color);
708 else
709 data->SetColorCode(m_Colors);
711 m_arData.push_back(data);
712 AddItemToList();
714 if ((!data->bAuxItem) && (m_itemCount > 0))
716 if (m_pProgControl)
718 m_pProgControl->ShowWindow(SW_SHOW);
719 m_pProgControl->SetPos(m_itemCount);
720 m_pProgControl->SetRange32(0, m_itemCountTotal);
722 if (m_pTaskbarList && m_pPostWnd)
724 m_pTaskbarList->SetProgressState(m_pPostWnd->GetSafeHwnd(), TBPF_NORMAL);
725 m_pTaskbarList->SetProgressValue(m_pPostWnd->GetSafeHwnd(), m_itemCount, m_itemCountTotal);
729 // needed as long as RemoteProgressCommand::RemoteCompletionCallback never gets called by libgit2
730 if (m_pAnimate)
731 m_pAnimate->ShowWindow(SW_HIDE);
734 BOOL CGitProgressList::UpdateProgress(const git_transfer_progress* stat)
736 static unsigned int start = 0;
737 unsigned int dt = GetCurrentTime() - start;
738 double speed = 0;
740 if (m_bCancelled)
741 return FALSE;
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
752 return TRUE;
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(_T("%.0f B/s"), speed);
786 else if(speed < 1024 * 1024)
787 str.Format(_T("%.2f KiB/s"), speed / 1024);
788 else
789 str.Format(_T("%.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, NULL);
797 return TRUE;
800 void CGitProgressList::OnTimer(UINT_PTR nIDEvent)
802 if (nIDEvent == TRANSFERTIMER)
804 CString progText;
805 CString progSpeed = _T("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)
824 return;
827 // We need to sort the blocks which lie between the auxiliary entries
828 // This is so that any aux data stays where it was
829 NotificationDataVect::iterator actionBlockBegin;
830 NotificationDataVect::iterator actionBlockEnd = m_arData.begin(); // We start searching from here
832 for(;;)
834 // Search to the start of the non-aux entry in the next block
835 actionBlockBegin = std::find_if(actionBlockEnd, m_arData.end(), std::not1(std::ptr_fun(&CGitProgressList::NotificationDataIsAux)));
836 if(actionBlockBegin == m_arData.end())
838 // There are no more actions
839 break;
841 // Now search to find the end of the block
842 actionBlockEnd = std::find_if(actionBlockBegin+1, m_arData.end(), std::ptr_fun(&CGitProgressList::NotificationDataIsAux));
843 // Now sort the block
844 std::sort(actionBlockBegin, actionBlockEnd, &CGitProgressList::SortCompare);
848 bool CGitProgressList::SortCompare(const NotificationData * pData1, const NotificationData * pData2)
850 int result = 0;
851 switch (m_nSortedColumn)
853 case 0: //action column
854 result = pData1->sActionColumnText.Compare(pData2->sActionColumnText);
855 break;
856 case 1: //path column
857 // Compare happens after switch()
858 break;
859 default:
860 break;
863 // Sort by path if everything else is equal
864 if (result == 0)
866 result = CTGitPath::Compare(pData1->path, pData2->path);
869 if (!m_bAscending)
870 result = -result;
871 return result < 0;
874 void CGitProgressList::OnContextMenu(CWnd* pWnd, CPoint point)
876 if (m_options & ProgOptDryRun)
877 return; // don't do anything in a dry-run.
879 if (pWnd != this)
880 return;
882 int selIndex = GetSelectionMark();
883 if ((point.x == -1) && (point.y == -1))
885 // Menu was invoked from the keyboard rather than by right-clicking
886 CRect rect;
887 GetItemRect(selIndex, &rect, LVIR_LABEL);
888 ClientToScreen(&rect);
889 point = rect.CenterPoint();
892 if ((selIndex < 0) || m_bThreadRunning || GetSelectedCount() == 0)
893 return;
895 // entry is selected, thread has finished with updating so show the popup menu
896 CIconMenu popup;
897 if (!popup.CreatePopupMenu())
898 return;
900 ContextMenuActionList actions;
901 NotificationData* data = m_arData[selIndex];
902 if (data && GetSelectedCount() == 1)
903 data->GetContextMenu(popup, actions);
905 if (!actions.empty())
906 popup.AppendMenu(MF_SEPARATOR, NULL);
907 actions.push_back([&]()
909 CString sLines;
910 POSITION pos = GetFirstSelectedItemPosition();
911 while (pos)
913 int nItem = GetNextSelectedItem(pos);
914 NotificationData* data = m_arData[nItem];
915 if (data)
917 sLines += data->sPathColumnText;
918 sLines += _T("\r\n");
921 sLines.TrimRight();
922 if (!sLines.IsEmpty())
923 CStringUtils::WriteAsciiStringToClipboard(sLines, GetSafeHwnd());
925 popup.AppendMenuIcon(actions.size(), IDS_LOG_POPUP_COPYTOCLIPBOARD, IDI_COPYCLIP);
927 if (actions.empty())
928 return;
930 int cmd = popup.TrackPopupMenu(TPM_RETURNCMD | TPM_LEFTALIGN | TPM_NONOTIFY, point.x, point.y, this, 0);
932 if (cmd <= 0 || cmd > actions.size())
933 return;
935 theApp.DoWaitCursor(1);
936 actions.at(cmd - 1)();
937 theApp.DoWaitCursor(-1);
940 void CGitProgressList::OnLvnBegindragSvnprogress(NMHDR* , LRESULT *pResult)
942 //LPNMLISTVIEW pNMLV = reinterpret_cast<LPNMLISTVIEW>(pNMHDR);
943 #if 0
944 int selIndex = GetSelectionMark();
945 if (selIndex < 0)
946 return;
948 CDropFiles dropFiles; // class for creating DROPFILES struct
950 int index;
951 POSITION pos = GetFirstSelectedItemPosition();
952 while ( (index = GetNextSelectedItem(pos)) >= 0 )
954 NotificationData * data = m_arData[index];
956 if ( data->kind==svn_node_file || data->kind==svn_node_dir )
958 CString sPath = GetPathFromColumnText(data->sPathColumnText);
960 dropFiles.AddFile( sPath );
964 if (!dropFiles.IsEmpty())
966 dropFiles.CreateStructure();
968 #endif
969 *pResult = 0;
972 void CGitProgressList::OnSize(UINT nType, int cx, int cy)
974 CListCtrl::OnSize(nType, cx, cy);
975 if ((nType == SIZE_RESTORED)&&(m_bLastVisible))
977 if(!m_hWnd)
978 return;
980 int count = GetItemCount();
981 if (count > 0)
982 EnsureVisible(count-1, false);
986 void CGitProgressList::Init()
988 SetExtendedStyle (LVS_EX_FULLROWSELECT | LVS_EX_DOUBLEBUFFER);
990 DeleteAllItems();
991 int c = ((CHeaderCtrl*)(GetDlgItem(0)))->GetItemCount()-1;
992 while (c>=0)
993 DeleteColumn(c--);
995 CString temp;
996 temp.LoadString(IDS_PROGRS_ACTION);
997 InsertColumn(0, temp);
998 temp.LoadString(IDS_PROGRS_PATH);
999 InsertColumn(1, temp);
1001 m_pThread = AfxBeginThread(ProgressThreadEntry, this, THREAD_PRIORITY_NORMAL,0,CREATE_SUSPENDED);
1002 if (m_pThread==NULL)
1004 ReportError(CString(MAKEINTRESOURCE(IDS_ERR_THREADSTARTFAILED)));
1006 else
1008 m_pThread->m_bAutoDelete = FALSE;
1009 m_pThread->ResumeThread();
1012 // Call this early so that the column headings aren't hidden before any
1013 // text gets added.
1014 ResizeColumns();
1016 SetTimer(VISIBLETIMER, 300, NULL);
1020 void CGitProgressList::OnClose()
1022 if (m_bCancelled)
1024 TerminateThread(m_pThread->m_hThread, (DWORD)-1);
1025 InterlockedExchange(&m_bThreadRunning, FALSE);
1027 else
1029 m_bCancelled = TRUE;
1030 return;
1032 CListCtrl::OnClose();
1036 BOOL CGitProgressList::PreTranslateMessage(MSG* pMsg)
1038 if (pMsg->message == WM_KEYDOWN)
1040 if (pMsg->wParam == 'A')
1042 if (GetKeyState(VK_CONTROL)&0x8000)
1044 // Ctrl-A -> select all
1045 SetSelectionMark(0);
1046 for (int i=0; i<GetItemCount(); ++i)
1048 SetItemState(i, LVIS_SELECTED, LVIS_SELECTED);
1052 if ((pMsg->wParam == 'C')||(pMsg->wParam == VK_INSERT))
1054 int selIndex = GetSelectionMark();
1055 if (selIndex >= 0)
1057 if (GetKeyState(VK_CONTROL)&0x8000)
1059 //Ctrl-C -> copy to clipboard
1060 CString sClipdata;
1061 POSITION pos = GetFirstSelectedItemPosition();
1062 if (pos != NULL)
1064 while (pos)
1066 int nItem = GetNextSelectedItem(pos);
1067 CString sAction = GetItemText(nItem, 0);
1068 CString sPath = GetItemText(nItem, 1);
1069 CString sMime = GetItemText(nItem, 2);
1070 CString sLogCopyText;
1071 sLogCopyText.Format(_T("%s: %s %s\r\n"),
1072 (LPCTSTR)sAction, (LPCTSTR)sPath, (LPCTSTR)sMime);
1073 sClipdata += sLogCopyText;
1075 CStringUtils::WriteAsciiStringToClipboard(sClipdata);
1080 } // if (pMsg->message == WM_KEYDOWN)
1081 return CListCtrl::PreTranslateMessage(pMsg);
1084 void CGitProgressList::SetWindowTitle(UINT id, const CString& urlorpath, CString& dialogname)
1086 if (!m_bSetTitle || !m_pPostWnd)
1087 return;
1089 dialogname.LoadString(id);
1090 CAppUtils::SetWindowTitle(m_pPostWnd->GetSafeHwnd(), urlorpath, dialogname);
1093 void CGitProgressList::ShowProgressBar()
1095 if (m_pProgControl)
1097 m_pProgControl->ShowWindow(SW_SHOW);
1098 m_pProgControl->SetPos(0);
1099 m_pProgControl->SetRange32(0, 1);
1103 void CGitProgressList::SetProgressLabelText(const CString& str)
1105 if (m_pProgressLabelCtrl)
1106 m_pProgressLabelCtrl->SetWindowText(str);
1109 CGitProgressList::WC_File_NotificationData::WC_File_NotificationData(const CTGitPath& path, git_wc_notify_action_t action)
1110 : NotificationData()
1112 this->action = action;
1113 this->path = path;
1114 sPathColumnText = path.GetGitPathString();
1116 switch (action)
1118 case git_wc_notify_add:
1119 sActionColumnText.LoadString(IDS_SVNACTION_ADD);
1120 break;
1121 case git_wc_notify_resolved:
1122 sActionColumnText.LoadString(IDS_SVNACTION_RESOLVE);
1123 break;
1124 case git_wc_notify_revert:
1125 sActionColumnText.LoadString(IDS_SVNACTION_REVERT);
1126 break;
1127 case git_wc_notify_checkout:
1128 sActionColumnText.LoadString(IDS_PROGRS_CMD_CHECKOUT);
1129 break;
1130 default:
1131 break;
1135 void CGitProgressList::WC_File_NotificationData::SetColorCode(CColors& colors)
1137 switch (action)
1139 case git_wc_notify_checkout: // fall-through
1140 case git_wc_notify_add:
1141 color = colors.GetColor(CColors::Added);
1142 break;
1144 default:
1145 break;
1149 void CGitProgressList::WC_File_NotificationData::GetContextMenu(CIconMenu& popup, ContextMenuActionList& actions)
1151 if ((action == git_wc_notify_add) ||
1152 (action == git_wc_notify_revert) ||
1153 (action == git_wc_notify_resolved) ||
1154 (action == git_wc_notify_checkout))
1156 actions.push_back([&]()
1158 CString cmd = _T("/command:log");
1159 CString sPath = g_Git.CombinePath(path);
1160 cmd += _T(" /path:\"") + sPath + _T("\"");
1161 CAppUtils::RunTortoiseGitProc(cmd);
1163 popup.AppendMenuIcon(actions.size(), IDS_MENULOG, IDI_LOG);
1165 popup.AppendMenu(MF_SEPARATOR, NULL);
1166 auto open = [&](bool openWith)
1168 int ret = 0;
1169 CString sWinPath = g_Git.CombinePath(path);
1170 if (!openWith)
1171 ret = (int)ShellExecute(nullptr, NULL, (LPCTSTR)sWinPath, NULL, NULL, SW_SHOWNORMAL);
1172 if ((ret <= HINSTANCE_ERROR) || openWith)
1174 CString cmd = _T("RUNDLL32 Shell32,OpenAs_RunDLL ");
1175 cmd += sWinPath;
1176 CAppUtils::LaunchApplication(cmd, NULL, false);
1179 actions.push_back([open]{ open(false); });
1180 popup.AppendMenuIcon(actions.size(), IDS_LOG_POPUP_OPEN, IDI_OPEN);
1181 actions.push_back([open]{ open(true); });
1182 popup.AppendMenuIcon(actions.size(), IDS_LOG_POPUP_OPENWITH, IDI_OPEN);
1184 actions.push_back([&]{ CAppUtils::ExploreTo(nullptr, g_Git.CombinePath(path)); });
1185 popup.AppendMenuIcon(actions.size(), IDS_STATUSLIST_CONTEXT_EXPLORE, IDI_EXPLORER);
1189 void CGitProgressList::WC_File_NotificationData::HandleDblClick() const
1191 CString sWinPath = g_Git.CombinePath(path);
1192 if (PathIsDirectory(sWinPath))
1194 CAppUtils::ExploreTo(nullptr, sWinPath);
1195 return;
1197 if ((int)ShellExecute(nullptr, NULL, (LPCTSTR)sWinPath, NULL, NULL, SW_SHOWNORMAL) <= HINSTANCE_ERROR)
1199 CString cmd = _T("RUNDLL32 Shell32,OpenAs_RunDLL ");
1200 cmd += sWinPath;
1201 CAppUtils::LaunchApplication(cmd, NULL, false);