Fixed issue #3089: Show parent SHA1 on cherry picking a merge commit
[TortoiseGit.git] / src / TortoiseProc / StatGraphDlg.cpp
blob382128115e9ad4420d164efe7cbd497729f88898
1 // TortoiseGit - a Windows shell extension for easy version control
3 // Copyright (C) 2008-2017 - TortoiseGit
4 // Copyright (C) 2003-2011, 2014-2016 - 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 "TortoiseProc.h"
22 #include "StatGraphDlg.h"
23 #include "AppUtils.h"
24 #include "PathUtils.h"
25 #include "registry.h"
26 #include "FormatMessageWrapper.h"
27 #include "SysProgressDlg.h"
29 #include <cmath>
30 #include <locale>
31 #include <utility>
32 #include <strsafe.h>
34 using namespace Gdiplus;
36 // BinaryPredicate for comparing authors based on their commit count
37 template<class DataType>
38 class MoreCommitsThan : public std::binary_function<tstring, tstring, bool> {
39 public:
40 typedef std::map<tstring, DataType> MapType;
41 MoreCommitsThan(MapType &author_commits) : m_authorCommits(author_commits) {}
43 bool operator()(const tstring& lhs, const tstring& rhs) {
44 return (m_authorCommits)[lhs] > (m_authorCommits)[rhs];
47 private:
48 MapType &m_authorCommits;
52 IMPLEMENT_DYNAMIC(CStatGraphDlg, CResizableStandAloneDialog)
53 CStatGraphDlg::CStatGraphDlg(CWnd* pParent /*=nullptr*/)
54 : CResizableStandAloneDialog(CStatGraphDlg::IDD, pParent)
55 , m_bStacked(FALSE)
56 , m_GraphType(MyGraph::Bar)
57 , m_bAuthorsCaseSensitive(TRUE)
58 , m_bSortByCommitCount(TRUE)
59 , m_bUseCommitterNames(FALSE)
60 , m_bUseCommitDates(TRUE)
61 , m_nWeeks(-1)
62 , m_nDays(-1)
63 , m_langOrder(0)
64 , m_firstInterval(0)
65 , m_lastInterval(0)
66 , m_nTotalCommits(0)
67 , m_nTotalLinesInc(0)
68 , m_nTotalLinesDec(0)
69 , m_nTotalLinesNew(0)
70 , m_nTotalLinesDel(0)
71 , m_bDiffFetched(FALSE)
72 , m_minDate(0)
73 , m_maxDate(0)
74 , m_nTotalFileChanges(0)
78 CStatGraphDlg::~CStatGraphDlg()
80 ClearGraph();
83 void CStatGraphDlg::OnOK() {
84 StoreCurrentGraphType();
85 __super::OnOK();
88 void CStatGraphDlg::OnCancel() {
89 StoreCurrentGraphType();
90 __super::OnCancel();
93 void CStatGraphDlg::DoDataExchange(CDataExchange* pDX)
95 CResizableStandAloneDialog::DoDataExchange(pDX);
96 DDX_Control(pDX, IDC_GRAPH, m_graph);
97 DDX_Control(pDX, IDC_GRAPHCOMBO, m_cGraphType);
98 DDX_Control(pDX, IDC_SKIPPER, m_Skipper);
99 DDX_Check(pDX, IDC_AUTHORSCASESENSITIVE, m_bAuthorsCaseSensitive);
100 DDX_Check(pDX, IDC_SORTBYCOMMITCOUNT, m_bSortByCommitCount);
101 DDX_Check(pDX, IDC_COMMITTERNAMES, m_bUseCommitterNames);
102 DDX_Check(pDX, IDC_COMMITDATES, m_bUseCommitDates);
103 DDX_Control(pDX, IDC_GRAPHBARBUTTON, m_btnGraphBar);
104 DDX_Control(pDX, IDC_GRAPHBARSTACKEDBUTTON, m_btnGraphBarStacked);
105 DDX_Control(pDX, IDC_GRAPHLINEBUTTON, m_btnGraphLine);
106 DDX_Control(pDX, IDC_GRAPHLINESTACKEDBUTTON, m_btnGraphLineStacked);
107 DDX_Control(pDX, IDC_GRAPHPIEBUTTON, m_btnGraphPie);
111 BEGIN_MESSAGE_MAP(CStatGraphDlg, CResizableStandAloneDialog)
112 ON_CBN_SELCHANGE(IDC_GRAPHCOMBO, OnCbnSelchangeGraphcombo)
113 ON_WM_HSCROLL()
114 ON_NOTIFY(TTN_NEEDTEXT, nullptr, OnNeedText)
115 ON_BN_CLICKED(IDC_AUTHORSCASESENSITIVE, &CStatGraphDlg::AuthorsCaseSensitiveChanged)
116 ON_BN_CLICKED(IDC_SORTBYCOMMITCOUNT, &CStatGraphDlg::SortModeChanged)
117 ON_BN_CLICKED(IDC_GRAPHBARBUTTON, &CStatGraphDlg::OnBnClickedGraphbarbutton)
118 ON_BN_CLICKED(IDC_GRAPHBARSTACKEDBUTTON, &CStatGraphDlg::OnBnClickedGraphbarstackedbutton)
119 ON_BN_CLICKED(IDC_GRAPHLINEBUTTON, &CStatGraphDlg::OnBnClickedGraphlinebutton)
120 ON_BN_CLICKED(IDC_GRAPHLINESTACKEDBUTTON, &CStatGraphDlg::OnBnClickedGraphlinestackedbutton)
121 ON_BN_CLICKED(IDC_GRAPHPIEBUTTON, &CStatGraphDlg::OnBnClickedGraphpiebutton)
122 ON_COMMAND(ID_FILE_SAVESTATGRAPHAS, &CStatGraphDlg::OnFileSavestatgraphas)
123 ON_BN_CLICKED(IDC_CALC_DIFF, &CStatGraphDlg::OnBnClickedFetchDiff)
124 ON_BN_CLICKED(IDC_COMMITTERNAMES, &CStatGraphDlg::OnBnClickedCommitternames)
125 ON_BN_CLICKED(IDC_COMMITDATES, &CStatGraphDlg::OnBnClickedCommitdates)
126 END_MESSAGE_MAP()
128 void CStatGraphDlg::LoadStatQueries (__in UINT curStr, Metrics loadMetric, bool setDef /* = false */)
130 CString temp;
131 temp.LoadString(curStr);
132 int sel = m_cGraphType.AddString(temp);
133 m_cGraphType.SetItemData(sel, loadMetric);
135 if (setDef) m_cGraphType.SetCurSel(sel);
138 void CStatGraphDlg::SetSkipper (bool reloadSkiper)
140 // We need to limit the number of authors due to GUI resource limitation.
141 // However, since author #251 will properly have < 1000th of the commits,
142 // the resolution limit of the screen will already not allow for displaying
143 // it in a reasonable way
145 int max_authors_count = max(1, (int)min(m_authorNames.size(), 250) );
146 m_Skipper.SetRange (1, max_authors_count);
147 m_Skipper.SetPageSize(5);
149 if (reloadSkiper)
150 m_Skipper.SetPos (max_authors_count);
153 BOOL CStatGraphDlg::OnInitDialog()
155 CResizableStandAloneDialog::OnInitDialog();
157 m_tooltips.AddTool(&m_btnGraphPie, IDS_STATGRAPH_PIEBUTTON_TT);
158 m_tooltips.AddTool(&m_btnGraphLineStacked, IDS_STATGRAPH_LINESTACKEDBUTTON_TT);
159 m_tooltips.AddTool(&m_btnGraphLine, IDS_STATGRAPH_LINEBUTTON_TT);
160 m_tooltips.AddTool(&m_btnGraphBarStacked, IDS_STATGRAPH_BARSTACKEDBUTTON_TT);
161 m_tooltips.AddTool(&m_btnGraphBar, IDS_STATGRAPH_BARBUTTON_TT);
162 m_tooltips.Activate(TRUE);
164 m_bAuthorsCaseSensitive = DWORD(CRegDWORD(L"Software\\TortoiseGit\\StatAuthorsCaseSensitive", m_bAuthorsCaseSensitive));
165 m_bSortByCommitCount = DWORD(CRegDWORD(L"Software\\TortoiseGit\\StatSortByCommitCount", m_bSortByCommitCount));
166 m_bUseCommitterNames = DWORD(CRegDWORD(L"Software\\TortoiseGit\\StatCommiterNames", m_bUseCommitterNames));
167 m_bUseCommitDates = DWORD(CRegDWORD(L"Software\\TortoiseGit\\StatCommitDates", m_bUseCommitDates));
168 UpdateData(FALSE);
170 // gather statistics data, only needs to be updated when the checkbox with
171 // the case sensitivity of author names is changed
172 GatherData();
174 //Load statistical queries
175 LoadStatQueries(IDS_STATGRAPH_STATS, AllStat, true);
176 LoadStatQueries(IDS_STATGRAPH_COMMITSBYDATE, CommitsByDate);
177 LoadStatQueries(IDS_STATGRAPH_COMMITSBYAUTHOR, CommitsByAuthor);
178 LoadStatQueries(IDS_STATGRAPH_PERCENTAGE_OF_AUTHORSHIP, PercentageOfAuthorship);
179 LoadStatQueries(IDS_STATGRAPH_LINES_BYDATE_W, LinesWByDate);
180 LoadStatQueries(IDS_STATGRAPH_LINES_BYDATE_WO, LinesWOByDate);
182 // set the dialog title to "Statistics - path/to/whatever/we/show/the/statistics/for"
183 CString sTitle;
184 GetWindowText(sTitle);
185 if (m_path.IsEmpty())
186 CAppUtils::SetWindowTitle(m_hWnd, g_Git.m_CurrentDir, sTitle);
187 else
188 CAppUtils::SetWindowTitle(m_hWnd, m_path.GetUIPathString(), sTitle);
190 int iconWidth = GetSystemMetrics(SM_CXSMICON);
191 int iconHeight = GetSystemMetrics(SM_CYSMICON);
192 m_btnGraphBar.SetImage((HICON)LoadImage(AfxGetResourceHandle(), MAKEINTRESOURCE(IDI_GRAPHBAR), IMAGE_ICON, iconWidth, iconHeight, LR_DEFAULTCOLOR));
193 m_btnGraphBar.SizeToContent();
194 m_btnGraphBar.Invalidate();
195 m_btnGraphBarStacked.SetImage((HICON)LoadImage(AfxGetResourceHandle(), MAKEINTRESOURCE(IDI_GRAPHBARSTACKED), IMAGE_ICON, iconWidth, iconHeight, LR_DEFAULTCOLOR));
196 m_btnGraphBarStacked.SizeToContent();
197 m_btnGraphBarStacked.Invalidate();
198 m_btnGraphLine.SetImage((HICON)LoadImage(AfxGetResourceHandle(), MAKEINTRESOURCE(IDI_GRAPHLINE), IMAGE_ICON, iconWidth, iconHeight, LR_DEFAULTCOLOR));
199 m_btnGraphLine.SizeToContent();
200 m_btnGraphLine.Invalidate();
201 m_btnGraphLineStacked.SetImage((HICON)LoadImage(AfxGetResourceHandle(), MAKEINTRESOURCE(IDI_GRAPHLINESTACKED), IMAGE_ICON, iconWidth, iconHeight, LR_DEFAULTCOLOR));
202 m_btnGraphLineStacked.SizeToContent();
203 m_btnGraphLineStacked.Invalidate();
204 m_btnGraphPie.SetImage((HICON)LoadImage(AfxGetResourceHandle(), MAKEINTRESOURCE(IDI_GRAPHPIE), IMAGE_ICON, iconWidth, iconHeight, LR_DEFAULTCOLOR));
205 m_btnGraphPie.SizeToContent();
206 m_btnGraphPie.Invalidate();
208 AdjustControlSize(IDC_AUTHORSCASESENSITIVE);
209 AdjustControlSize(IDC_SORTBYCOMMITCOUNT);
210 AdjustControlSize(IDC_COMMITTERNAMES);
211 AdjustControlSize(IDC_COMMITDATES);
213 AddAnchor(IDC_GRAPHTYPELABEL, TOP_LEFT);
214 AddAnchor(IDC_GRAPH, TOP_LEFT, BOTTOM_RIGHT);
215 AddAnchor(IDC_GRAPHCOMBO, TOP_LEFT, TOP_RIGHT);
217 AddAnchor(IDC_NUMWEEK, TOP_LEFT);
218 AddAnchor(IDC_NUMWEEKVALUE, TOP_RIGHT);
219 AddAnchor(IDC_NUMAUTHOR, TOP_LEFT);
220 AddAnchor(IDC_NUMAUTHORVALUE, TOP_RIGHT);
221 AddAnchor(IDC_NUMCOMMITS, TOP_LEFT);
222 AddAnchor(IDC_NUMCOMMITSVALUE, TOP_RIGHT);
223 AddAnchor(IDC_NUMFILECHANGES, TOP_LEFT);
224 AddAnchor(IDC_NUMFILECHANGESVALUE, TOP_RIGHT);
226 AddAnchor(IDC_TOTAL_LINE_WITHOUT_NEW_DEL, TOP_LEFT);
227 AddAnchor(IDC_TOTAL_LINE_WITHOUT_NEW_DEL_VALUE, TOP_RIGHT);
228 AddAnchor(IDC_TOTAL_LINE_WITH_NEW_DEL, TOP_LEFT);
229 AddAnchor(IDC_TOTAL_LINE_WITH_NEW_DEL_VALUE, TOP_RIGHT);
231 AddAnchor(IDC_CALC_DIFF, TOP_RIGHT);
233 AddAnchor(IDC_AVG, TOP_RIGHT);
234 AddAnchor(IDC_MIN, TOP_RIGHT);
235 AddAnchor(IDC_MAX, TOP_RIGHT);
236 AddAnchor(IDC_COMMITSEACHWEEK, TOP_LEFT);
237 AddAnchor(IDC_MOSTACTIVEAUTHOR, TOP_LEFT);
238 AddAnchor(IDC_LEASTACTIVEAUTHOR, TOP_LEFT);
239 AddAnchor(IDC_MOSTACTIVEAUTHORNAME, TOP_LEFT, TOP_RIGHT);
240 AddAnchor(IDC_LEASTACTIVEAUTHORNAME, TOP_LEFT, TOP_RIGHT);
241 AddAnchor(IDC_FILECHANGESEACHWEEK, TOP_LEFT);
242 AddAnchor(IDC_COMMITSEACHWEEKAVG, TOP_RIGHT);
243 AddAnchor(IDC_COMMITSEACHWEEKMIN, TOP_RIGHT);
244 AddAnchor(IDC_COMMITSEACHWEEKMAX, TOP_RIGHT);
245 AddAnchor(IDC_MOSTACTIVEAUTHORAVG, TOP_RIGHT);
246 AddAnchor(IDC_MOSTACTIVEAUTHORMIN, TOP_RIGHT);
247 AddAnchor(IDC_MOSTACTIVEAUTHORMAX, TOP_RIGHT);
248 AddAnchor(IDC_LEASTACTIVEAUTHORAVG, TOP_RIGHT);
249 AddAnchor(IDC_LEASTACTIVEAUTHORMIN, TOP_RIGHT);
250 AddAnchor(IDC_LEASTACTIVEAUTHORMAX, TOP_RIGHT);
251 AddAnchor(IDC_FILECHANGESEACHWEEKAVG, TOP_RIGHT);
252 AddAnchor(IDC_FILECHANGESEACHWEEKMIN, TOP_RIGHT);
253 AddAnchor(IDC_FILECHANGESEACHWEEKMAX, TOP_RIGHT);
255 AddAnchor(IDC_GRAPHBARBUTTON, BOTTOM_RIGHT);
256 AddAnchor(IDC_GRAPHBARSTACKEDBUTTON, BOTTOM_RIGHT);
257 AddAnchor(IDC_GRAPHLINEBUTTON, BOTTOM_RIGHT);
258 AddAnchor(IDC_GRAPHLINESTACKEDBUTTON, BOTTOM_RIGHT);
259 AddAnchor(IDC_GRAPHPIEBUTTON, BOTTOM_RIGHT);
261 AddAnchor(IDC_AUTHORSCASESENSITIVE, BOTTOM_LEFT);
262 AddAnchor(IDC_SORTBYCOMMITCOUNT, BOTTOM_LEFT);
263 AddAnchor(IDC_COMMITTERNAMES, BOTTOM_LEFT);
264 AddAnchor(IDC_COMMITDATES, BOTTOM_LEFT);
265 AddAnchor(IDC_SKIPPER, BOTTOM_LEFT, BOTTOM_RIGHT);
266 AddAnchor(IDC_SKIPPERLABEL, BOTTOM_LEFT);
267 AddAnchor(IDOK, BOTTOM_RIGHT);
268 EnableSaveRestore(L"StatGraphDlg");
270 // set the min/max values on the skipper
271 SetSkipper (true);
273 // we use a stats page encoding here, 0 stands for the statistics dialog
274 CRegDWORD lastStatsPage(L"Software\\TortoiseGit\\LastViewedStatsPage", 0);
276 // open last viewed statistics page as first page
277 int graphtype = lastStatsPage / 10;
278 for (int i = 0; i < m_cGraphType.GetCount(); i++)
280 if ((int)m_cGraphType.GetItemData(i) == graphtype)
282 m_cGraphType.SetCurSel(i);
283 break;
287 OnCbnSelchangeGraphcombo();
289 int statspage = lastStatsPage % 10;
290 switch (statspage) {
291 case 1 :
292 m_GraphType = MyGraph::Bar;
293 m_bStacked = true;
294 break;
295 case 2 :
296 m_GraphType = MyGraph::Bar;
297 m_bStacked = false;
298 break;
299 case 3 :
300 m_GraphType = MyGraph::Line;
301 m_bStacked = true;
302 break;
303 case 4 :
304 m_GraphType = MyGraph::Line;
305 m_bStacked = false;
306 break;
307 case 5 :
308 m_GraphType = MyGraph::PieChart;
309 break;
311 default : return TRUE;
314 LCID m_locale = MAKELCID((DWORD)CRegStdDWORD(L"Software\\TortoiseGit\\LanguageID", MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT)), SORT_DEFAULT);
316 bool bUseSystemLocale = !!(DWORD)CRegStdDWORD(L"Software\\TortoiseGit\\UseSystemLocaleForDates", TRUE);
317 LCID locale = bUseSystemLocale ? MAKELCID(MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), SORT_DEFAULT) : m_locale;
319 TCHAR langBuf[11] = { 0 };
320 GetLocaleInfo(locale, LOCALE_IDATE, langBuf, _countof(langBuf));
322 m_langOrder = _wtoi(langBuf);
324 return TRUE;
327 void CStatGraphDlg::ShowLabels(BOOL bShow)
329 if (m_parAuthors.IsEmpty() || m_parDates.IsEmpty() || m_parFileChanges.IsEmpty())
330 return;
332 int nCmdShow = bShow ? SW_SHOW : SW_HIDE;
334 GetDlgItem(IDC_GRAPH)->ShowWindow(bShow ? SW_HIDE : SW_SHOW);
335 GetDlgItem(IDC_NUMWEEK)->ShowWindow(nCmdShow);
336 GetDlgItem(IDC_NUMWEEKVALUE)->ShowWindow(nCmdShow);
337 GetDlgItem(IDC_NUMAUTHOR)->ShowWindow(nCmdShow);
338 GetDlgItem(IDC_NUMAUTHORVALUE)->ShowWindow(nCmdShow);
339 GetDlgItem(IDC_NUMCOMMITS)->ShowWindow(nCmdShow);
340 GetDlgItem(IDC_NUMCOMMITSVALUE)->ShowWindow(nCmdShow);
341 GetDlgItem(IDC_NUMFILECHANGES)->ShowWindow(nCmdShow);
342 GetDlgItem(IDC_NUMFILECHANGESVALUE)->ShowWindow(nCmdShow);
343 GetDlgItem(IDC_TOTAL_LINE_WITHOUT_NEW_DEL)->ShowWindow(nCmdShow);
344 GetDlgItem(IDC_TOTAL_LINE_WITHOUT_NEW_DEL_VALUE)->ShowWindow(nCmdShow);
345 GetDlgItem(IDC_TOTAL_LINE_WITH_NEW_DEL)->ShowWindow(nCmdShow);
346 GetDlgItem(IDC_TOTAL_LINE_WITH_NEW_DEL_VALUE)->ShowWindow(nCmdShow);
347 GetDlgItem(IDC_CALC_DIFF)->ShowWindow(nCmdShow && !m_bDiffFetched);
349 GetDlgItem(IDC_AVG)->ShowWindow(nCmdShow);
350 GetDlgItem(IDC_MIN)->ShowWindow(nCmdShow);
351 GetDlgItem(IDC_MAX)->ShowWindow(nCmdShow);
352 GetDlgItem(IDC_COMMITSEACHWEEK)->ShowWindow(nCmdShow);
353 GetDlgItem(IDC_MOSTACTIVEAUTHOR)->ShowWindow(nCmdShow);
354 GetDlgItem(IDC_LEASTACTIVEAUTHOR)->ShowWindow(nCmdShow);
355 GetDlgItem(IDC_MOSTACTIVEAUTHORNAME)->ShowWindow(nCmdShow);
356 GetDlgItem(IDC_LEASTACTIVEAUTHORNAME)->ShowWindow(nCmdShow);
357 //GetDlgItem(IDC_FILECHANGESEACHWEEK)->ShowWindow(nCmdShow);
358 GetDlgItem(IDC_COMMITSEACHWEEKAVG)->ShowWindow(nCmdShow);
359 GetDlgItem(IDC_COMMITSEACHWEEKMIN)->ShowWindow(nCmdShow);
360 GetDlgItem(IDC_COMMITSEACHWEEKMAX)->ShowWindow(nCmdShow);
361 GetDlgItem(IDC_MOSTACTIVEAUTHORAVG)->ShowWindow(nCmdShow);
362 GetDlgItem(IDC_MOSTACTIVEAUTHORMIN)->ShowWindow(nCmdShow);
363 GetDlgItem(IDC_MOSTACTIVEAUTHORMAX)->ShowWindow(nCmdShow);
364 GetDlgItem(IDC_LEASTACTIVEAUTHORAVG)->ShowWindow(nCmdShow);
365 GetDlgItem(IDC_LEASTACTIVEAUTHORMIN)->ShowWindow(nCmdShow);
366 GetDlgItem(IDC_LEASTACTIVEAUTHORMAX)->ShowWindow(nCmdShow);
367 GetDlgItem(IDC_FILECHANGESEACHWEEKAVG)->ShowWindow(nCmdShow);
368 GetDlgItem(IDC_FILECHANGESEACHWEEKMIN)->ShowWindow(nCmdShow);
369 GetDlgItem(IDC_FILECHANGESEACHWEEKMAX)->ShowWindow(nCmdShow);
371 GetDlgItem(IDC_SORTBYCOMMITCOUNT)->ShowWindow(bShow ? SW_HIDE : SW_SHOW);
372 GetDlgItem(IDC_SKIPPER)->ShowWindow(bShow ? SW_HIDE : SW_SHOW);
373 GetDlgItem(IDC_SKIPPERLABEL)->ShowWindow(bShow ? SW_HIDE : SW_SHOW);
374 m_btnGraphBar.ShowWindow(bShow ? SW_HIDE : SW_SHOW);
375 m_btnGraphBarStacked.ShowWindow(bShow ? SW_HIDE : SW_SHOW);
376 m_btnGraphLine.ShowWindow(bShow ? SW_HIDE : SW_SHOW);
377 m_btnGraphLineStacked.ShowWindow(bShow ? SW_HIDE : SW_SHOW);
378 m_btnGraphPie.ShowWindow(bShow ? SW_HIDE : SW_SHOW);
381 void CStatGraphDlg::UpdateWeekCount()
383 // Sanity check
384 if (m_parDates.IsEmpty())
385 return;
387 // Already updated? No need to do it again.
388 if (m_nWeeks >= 0)
389 return;
391 // Determine first and last date in dates array
392 __time64_t min_date = (__time64_t)m_parDates.GetAt(0);
393 __time64_t max_date = min_date;
394 INT_PTR count = m_parDates.GetCount();
395 for (INT_PTR i=0; i<count; ++i)
397 __time64_t d = (__time64_t)m_parDates.GetAt(i);
398 if (d < min_date) min_date = d;
399 else if (d > max_date) max_date = d;
402 // Store start date of the interval in the member variable m_minDate
403 m_minDate = min_date;
404 m_maxDate = max_date;
406 // How many weeks does the time period cover?
408 // Get time difference between start and end date
409 double secs = _difftime64(max_date, m_minDate);
411 m_nWeeks = (int)ceil(secs / (double) m_SecondsInWeek);
412 m_nDays = (int)ceil(secs / (double) m_SecondsInDay);
415 int CStatGraphDlg::GetCalendarWeek(const CTime& time)
417 // Note:
418 // the calculation of the calendar week is wrong if DST is in effect
419 // and the date to calculate the week for is in DST and within the range
420 // of the DST offset (e.g. one hour).
421 // For example, if DST starts on Sunday march 30 and the date to get the week for
422 // is Monday, march 31, 0:30:00, then the returned week is one week less than
423 // the real week.
424 // TODO: ?
425 // write a function
426 // getDSTOffset(const CTime& time)
427 // which returns the DST offset for a given time/date. Then we can use this offset
428 // to correct our GetDays() calculation to get the correct week again
429 // This of course won't work for 'history' dates, because Windows doesn't have
430 // that information (only Vista has such a function: GetTimeZoneInformationForYear() )
431 int iWeekOfYear = 0;
433 int iYear = time.GetYear();
434 int iFirstDayOfWeek = 0;
435 int iFirstWeekOfYear = 0;
436 TCHAR loc[2] = { 0 };
437 GetLocaleInfo(LOCALE_USER_DEFAULT, LOCALE_IFIRSTDAYOFWEEK, loc, _countof(loc));
438 iFirstDayOfWeek = int(loc[0]-'0');
439 GetLocaleInfo(LOCALE_USER_DEFAULT, LOCALE_IFIRSTWEEKOFYEAR, loc, _countof(loc));
440 iFirstWeekOfYear = int(loc[0]-'0');
441 CTime dDateFirstJanuary(iYear,1,1,0,0,0);
442 int iDayOfWeek = (dDateFirstJanuary.GetDayOfWeek()+5+iFirstDayOfWeek)%7;
444 // Select mode
445 // 0 Week containing 1/1 is the first week of that year.
446 // 1 First full week following 1/1 is the first week of that year.
447 // 2 First week containing at least four days is the first week of that year.
448 switch (iFirstWeekOfYear)
450 case 0:
452 // Week containing 1/1 is the first week of that year.
454 // check if this week reaches into the next year
455 dDateFirstJanuary = CTime(iYear+1,1,1,0,0,0);
457 // Get start of week
460 iDayOfWeek = (time.GetDayOfWeek()+5+iFirstDayOfWeek)%7;
462 catch (CAtlException)
465 CTime dStartOfWeek = time-CTimeSpan(iDayOfWeek,0,0,0);
467 // If this week spans over to 1/1 this is week 1
468 if (dStartOfWeek + CTimeSpan(6,0,0,0) >= dDateFirstJanuary)
470 // we are in the last week of the year that spans over 1/1
471 iWeekOfYear = 1;
473 else
475 // Get week day of 1/1
476 dDateFirstJanuary = CTime(iYear,1,1,0,0,0);
477 iDayOfWeek = (dDateFirstJanuary.GetDayOfWeek() +5 + iFirstDayOfWeek) % 7;
478 // Just count from 1/1
479 iWeekOfYear = (int)(((time-dDateFirstJanuary).GetDays() + iDayOfWeek) / 7) + 1;
482 break;
483 case 1:
485 // First full week following 1/1 is the first week of that year.
487 // If the 1.1 is the start of the week everything is ok
488 // else we need the next week is the correct result
489 iWeekOfYear =
490 (int)(((time-dDateFirstJanuary).GetDays() + iDayOfWeek) / 7) +
491 (iDayOfWeek==0 ? 1:0);
493 // If we are in week 0 we are in the first not full week
494 // calculate from the last year
495 if (iWeekOfYear==0)
497 // Special case: we are in the week of 1.1 but 1.1. is not on the
498 // start of week. Calculate based on the last year
499 dDateFirstJanuary = CTime(iYear-1,1,1,0,0,0);
500 iDayOfWeek =
501 (dDateFirstJanuary.GetDayOfWeek()+5+iFirstDayOfWeek)%7;
502 // and we correct this in the same we we done this before but
503 // the result is now 52 or 53 and not 0
504 iWeekOfYear =
505 (int)(((time-dDateFirstJanuary).GetDays()+iDayOfWeek) / 7) +
506 (iDayOfWeek<=3 ? 1:0);
509 break;
510 case 2:
512 // First week containing at least four days is the first week of that year.
514 // Each year can start with any day of the week. But our
515 // weeks always start with Monday. So we add the day of week
516 // before calculation of the final week of year.
517 // Rule: is the 1.1 a Mo,Tu,We,Th than the week starts on the 1.1 with
518 // week==1, else a week later, so we add one for all those days if
519 // day is less <=3 Mo,Tu,We,Th. Otherwise 1.1 is in the last week of the
520 // previous year
521 iWeekOfYear =
522 (int)(((time-dDateFirstJanuary).GetDays()+iDayOfWeek) / 7) +
523 (iDayOfWeek<=3 ? 1:0);
525 // special cases
526 if (iWeekOfYear==0)
528 // special case week 0. We got a day before the 1.1, 2.1 or 3.1, were the
529 // 1.1. is not a Mo, Tu, We, Th. So the week 1 does not start with the 1.1.
530 // So we calculate the week according to the 1.1 of the year before
532 dDateFirstJanuary = CTime(iYear-1,1,1,0,0,0);
533 iDayOfWeek =
534 (dDateFirstJanuary.GetDayOfWeek()+5+iFirstDayOfWeek)%7;
535 // and we correct this in the same we we done this before but the result
536 // is now 52 or 53 and not 0
537 iWeekOfYear =
538 (int)(((time-dDateFirstJanuary).GetDays()+iDayOfWeek) / 7) +
539 (iDayOfWeek<=3 ? 1:0);
541 else if (iWeekOfYear==53)
543 // special case week 53. Either we got the correct week 53 or we just got the
544 // week 1 of the next year. So is the 1.1.(year+1) also a Mo, Tu, We, Th than
545 // we already have the week 1, otherwise week 53 is correct
547 dDateFirstJanuary = CTime(iYear+1,1,1,0,0,0);
548 iDayOfWeek =
549 (dDateFirstJanuary.GetDayOfWeek()+5+iFirstDayOfWeek)%7;
550 // 1.1. in week 1 or week 53?
551 iWeekOfYear = iDayOfWeek<=3 ? 1:53;
554 break;
555 default:
556 ASSERT(FALSE);
557 break;
559 // return result
560 return iWeekOfYear;
563 int CStatGraphDlg::GatherData(BOOL fetchdiff, BOOL keepFetchedData)
565 m_parAuthors.RemoveAll();
566 m_parDates.RemoveAll();
567 if (m_parFileChanges2.IsEmpty()) // Fixes issue #1948
568 keepFetchedData = FALSE;
569 if (!keepFetchedData)
571 m_parFileChanges.RemoveAll();
572 m_lineInc.RemoveAll();
573 m_lineDec.RemoveAll();
574 m_lineDel.RemoveAll();
575 m_lineNew.RemoveAll();
577 else
579 m_parFileChanges.Copy(m_parFileChanges2);
580 m_lineNew.Copy(m_lineNew2);
581 m_lineDel.Copy(m_lineDel2);
582 m_lineInc.Copy(m_lineInc2);
583 m_lineDec.Copy(m_lineDec2);
586 CSysProgressDlg progress;
587 if (fetchdiff)
589 progress.SetTitle(CString(MAKEINTRESOURCE(IDS_PROGS_TITLE_GATHERSTATISTICS)));
590 progress.FormatNonPathLine(1, IDS_PROC_STATISTICS_DIFF);
591 progress.SetTime(true);
592 progress.ShowModeless(this);
595 // create arrays which are aware of the current filter
596 ULONGLONG starttime = GetTickCount64();
598 if (m_bUseCommitDates)
599 std::sort(m_ShowList.begin(), m_ShowList.end(), [](GitRevLoglist* pLhs, GitRevLoglist* pRhs) { return pLhs->GetCommitterDate() > pRhs->GetCommitterDate(); });
600 else
601 std::sort(m_ShowList.begin(), m_ShowList.end(), [](GitRevLoglist* pLhs, GitRevLoglist* pRhs) { return pLhs->GetAuthorDate() > pRhs->GetAuthorDate(); });
603 GIT_MAILMAP mailmap = nullptr;
604 git_read_mailmap(&mailmap);
605 for (size_t i = 0; i < m_ShowList.size(); ++i)
607 auto pLogEntry = m_ShowList[i];
608 int inc, dec, incnewfile, decdeletedfile, files;
609 inc = dec = incnewfile = decdeletedfile = files= 0;
611 CString strAuthor = m_bUseCommitterNames ? pLogEntry->GetCommitterName() : pLogEntry->GetAuthorName();
612 if (mailmap)
614 CStringA email2A = CUnicodeUtils::GetUTF8(m_bUseCommitterNames ? pLogEntry->GetCommitterEmail() : pLogEntry->GetAuthorEmail());
615 struct payload_struct { GitRev* rev; const char *authorName; BOOL useCommitterNames; };
616 payload_struct payload = { pLogEntry, nullptr, m_bUseCommitterNames };
617 const char* author1 = nullptr;
618 git_lookup_mailmap(mailmap, nullptr, &author1, email2A, &payload,
619 [](void* payload) -> const char* { return reinterpret_cast<payload_struct*>(payload)->authorName = _strdup(CUnicodeUtils::GetUTF8(reinterpret_cast<payload_struct*>(payload)->useCommitterNames ? reinterpret_cast<payload_struct*>(payload)->rev->GetCommitterName() : reinterpret_cast<payload_struct*>(payload)->rev->GetAuthorName())); });
620 free((void *)payload.authorName);
621 if (author1)
622 strAuthor = CUnicodeUtils::GetUnicode(author1);
624 if (strAuthor.IsEmpty())
625 strAuthor.LoadString(IDS_STATGRAPH_EMPTYAUTHOR);
626 m_parAuthors.Add(strAuthor);
627 if (m_bUseCommitDates)
628 m_parDates.Add((DWORD)pLogEntry->GetCommitterDate().GetTime());
629 else
630 m_parDates.Add((DWORD)pLogEntry->GetAuthorDate().GetTime());
632 if (fetchdiff && (pLogEntry->m_ParentHash.size() <= 1))
634 CTGitPathList& list = pLogEntry->GetFiles(nullptr);
635 files = list.GetCount();
637 for (int j = 0; j < files; j++)
639 if (list[j].m_Action & CTGitPath::LOGACTIONS_DELETED)
640 decdeletedfile += _wtol(list[j].m_StatDel);
641 else if(list[j].m_Action & CTGitPath::LOGACTIONS_ADDED)
642 incnewfile += _wtol(list[j].m_StatAdd);
643 else
645 inc += _wtol(list[j].m_StatAdd);
646 dec += _wtol(list[j].m_StatDel);
649 if (progress.HasUserCancelled())
651 git_free_mailmap(mailmap);
652 return -1;
656 if (!keepFetchedData)
658 m_parFileChanges.Add(files);
659 m_lineInc.Add(inc);
660 m_lineDec.Add(dec);
661 m_lineDel.Add(decdeletedfile);
662 m_lineNew.Add(incnewfile);
665 if (progress.IsVisible() && (GetTickCount64() - starttime > 100UL))
667 progress.FormatNonPathLine(2, L"%s: %s", (LPCTSTR)pLogEntry->m_CommitHash.ToString().Left(g_Git.GetShortHASHLength()), (LPCTSTR)pLogEntry->GetSubject());
668 progress.SetProgress64(i, m_ShowList.size());
669 starttime = GetTickCount64();
673 git_free_mailmap(mailmap);
675 if (fetchdiff)
677 m_parFileChanges2.Copy(m_parFileChanges);
678 m_lineNew2.Copy(m_lineNew);
679 m_lineDel2.Copy(m_lineDel);
680 m_lineInc2.Copy(m_lineInc);
681 m_lineDec2.Copy(m_lineDec);
684 m_nTotalCommits = m_parAuthors.GetCount();
685 m_nTotalFileChanges = 0;
687 // Update m_nWeeks and m_minDate
688 UpdateWeekCount();
690 // Now create a mapping that holds the information per week.
691 m_commitsPerUnitAndAuthor.clear();
692 m_filechangesPerUnitAndAuthor.clear();
693 m_commitsPerAuthor.clear();
694 m_PercentageOfAuthorship.clear();
695 m_LinesWPerUnitAndAuthor.clear();
696 m_LinesWOPerUnitAndAuthor.clear();
698 int interval = 0;
699 __time64_t d = (__time64_t)m_parDates.GetAt(0);
700 int nLastUnit = GetUnit(d);
701 double AllContributionAuthor = 0;
703 m_nTotalLinesInc = m_nTotalLinesDec = m_nTotalLinesNew = m_nTotalLinesDel =0;
705 // Now loop over all weeks and gather the info
706 for (LONG i=0; i<m_nTotalCommits; ++i)
708 // Find the interval number
709 __time64_t commitDate = (__time64_t)m_parDates.GetAt(i);
710 int u = GetUnit(commitDate);
711 if (nLastUnit != u)
712 interval++;
713 nLastUnit = u;
714 // Find the authors name
715 CString sAuth = m_parAuthors.GetAt(i);
716 if (!m_bAuthorsCaseSensitive)
717 sAuth = sAuth.MakeLower();
718 tstring author = tstring(sAuth);
719 // Increase total commit count for this author
720 m_commitsPerAuthor[author]++;
721 // Increase the commit count for this author in this week
722 m_commitsPerUnitAndAuthor[interval][author]++;
724 m_LinesWPerUnitAndAuthor[interval][author] += m_lineInc.GetAt(i) + m_lineDec.GetAt(i) + m_lineNew.GetAt(i) + + m_lineDel.GetAt(i);
725 m_LinesWOPerUnitAndAuthor[interval][author] += m_lineInc.GetAt(i) + m_lineDec.GetAt(i);
727 CTime t = m_parDates.GetAt(i);
728 m_unitNames[interval] = GetUnitLabel(nLastUnit, t);
729 // Increase the file change count for this author in this week
730 int fileChanges = m_parFileChanges.GetAt(i);
731 m_filechangesPerUnitAndAuthor[interval][author] += fileChanges;
732 m_nTotalFileChanges += fileChanges;
734 //calculate Contribution Author
735 double contributionAuthor = CoeffContribution((int)m_nTotalCommits - i -1) * (fileChanges ? fileChanges : 1);
736 AllContributionAuthor += contributionAuthor;
737 m_PercentageOfAuthorship[author] += contributionAuthor;
739 m_nTotalLinesInc += m_lineInc.GetAt(i);
740 m_nTotalLinesDec += m_lineDec.GetAt(i);
741 m_nTotalLinesNew += m_lineNew.GetAt(i);
742 m_nTotalLinesDel += m_lineDel.GetAt(i);
745 // Find first and last interval number.
746 if (!m_commitsPerUnitAndAuthor.empty())
748 IntervalDataMap::iterator interval_it = m_commitsPerUnitAndAuthor.begin();
749 m_firstInterval = interval_it->first;
750 interval_it = m_commitsPerUnitAndAuthor.end();
751 --interval_it;
752 m_lastInterval = interval_it->first;
753 // Sanity check - if m_lastInterval is too large it could freeze TSVN and take up all memory!!!
754 assert(m_lastInterval >= 0 && m_lastInterval < 10000);
756 else
758 m_firstInterval = 0;
759 m_lastInterval = -1;
762 // Get a list of authors names
763 LoadListOfAuthors(m_commitsPerAuthor);
765 // Calculate percent of Contribution Authors
766 for (std::list<tstring>::iterator it = m_authorNames.begin(); it != m_authorNames.end(); ++it)
768 m_PercentageOfAuthorship[*it] = (m_PercentageOfAuthorship[*it] *100)/ AllContributionAuthor;
771 // All done, now the statistics pages can retrieve the data and
772 // extract the information to be shown.
774 return 0;
777 void CStatGraphDlg::FilterSkippedAuthors(std::list<tstring>& included_authors,
778 std::list<tstring>& skipped_authors)
780 included_authors.clear();
781 skipped_authors.clear();
783 unsigned int included_authors_count = m_Skipper.GetPos();
784 // if we only leave out one author, still include him with his name
785 if (included_authors_count + 1 == m_authorNames.size())
786 ++included_authors_count;
788 // add the included authors first
789 std::list<tstring>::iterator author_it = m_authorNames.begin();
790 while (included_authors_count > 0 && author_it != m_authorNames.end())
792 // Add him/her to the included list
793 included_authors.push_back(*author_it);
794 ++author_it;
795 --included_authors_count;
798 // If we haven't reached the end yet, copy all remaining authors into the
799 // skipped author list.
800 std::copy(author_it, m_authorNames.end(), std::back_inserter(skipped_authors) );
802 // Sort authors alphabetically if user wants that.
803 if (!m_bSortByCommitCount)
804 included_authors.sort();
807 bool CStatGraphDlg::PreViewStat(bool fShowLabels)
809 if (m_parAuthors.IsEmpty() || m_parDates.IsEmpty() || m_parFileChanges.IsEmpty())
810 return false;
811 ShowLabels(fShowLabels);
813 //If view graphic
814 if (!fShowLabels) ClearGraph();
816 // This function relies on a previous call of GatherData().
817 // This can be detected by checking the week count.
818 // If the week count is equal to -1, it hasn't been called before.
819 if (m_nWeeks == -1)
820 GatherData(FALSE, TRUE);
821 // If week count is still -1, something bad has happened, probably invalid data!
822 if (m_nWeeks == -1)
823 return false;
825 return true;
828 MyGraphSeries *CStatGraphDlg::PreViewGraph(__in UINT GraphTitle, __in UINT YAxisLabel, __in UINT XAxisLabel /*= nullptr*/)
830 if(!PreViewStat(false))
831 return nullptr;
833 // We need at least one author
834 if (m_authorNames.empty())
835 return nullptr;
837 // Add a single series to the chart
838 MyGraphSeries * graphData = new MyGraphSeries();
839 m_graph.AddSeries(*graphData);
840 m_graphDataArray.Add(graphData);
842 // Set up the graph.
843 CString temp;
844 UpdateData();
845 m_graph.SetGraphType(m_GraphType, m_bStacked);
846 temp.LoadString(YAxisLabel);
847 m_graph.SetYAxisLabel(temp);
848 temp.LoadString(XAxisLabel);
849 m_graph.SetXAxisLabel(temp);
850 temp.LoadString(GraphTitle);
851 m_graph.SetGraphTitle(temp);
853 return graphData;
856 void CStatGraphDlg::ShowPercentageOfAuthorship()
858 // Set up the graph.
859 MyGraphSeries * graphData = PreViewGraph(IDS_STATGRAPH_PERCENTAGE_OF_AUTHORSHIP,
860 IDS_STATGRAPH_PERCENTAGE_OF_AUTHORSHIPY,
861 IDS_STATGRAPH_COMMITSBYAUTHORMOREX);
862 if (!graphData) return;
864 // Find out which authors are to be shown and which are to be skipped.
865 std::list<tstring> authors;
866 std::list<tstring> others;
869 FilterSkippedAuthors(authors, others);
871 // Loop over all authors in the authors list and
872 // add them to the graph.
874 if (!authors.empty())
876 for (std::list<tstring>::iterator it = authors.begin(); it != authors.end(); ++it)
878 int group = m_graph.AppendGroup(it->c_str());
879 graphData->SetData(group, RollPercentageOfAuthorship(m_PercentageOfAuthorship[*it]));
883 // If we have other authors, count them and their commits.
884 if (!others.empty())
885 DrawOthers(others, graphData, m_PercentageOfAuthorship);
887 // Paint the graph now that we're through.
888 m_graph.Invalidate();
891 void CStatGraphDlg::ShowCommitsByAuthor()
893 // Set up the graph.
894 MyGraphSeries * graphData = PreViewGraph(IDS_STATGRAPH_COMMITSBYAUTHOR,
895 IDS_STATGRAPH_COMMITSBYAUTHORY,
896 IDS_STATGRAPH_COMMITSBYAUTHORX);
897 if (!graphData) return;
899 // Find out which authors are to be shown and which are to be skipped.
900 std::list<tstring> authors;
901 std::list<tstring> others;
902 FilterSkippedAuthors(authors, others);
904 // Loop over all authors in the authors list and
905 // add them to the graph.
907 if (!authors.empty())
909 for (std::list<tstring>::iterator it = authors.begin(); it != authors.end(); ++it)
911 int group = m_graph.AppendGroup(it->c_str());
912 graphData->SetData(group, m_commitsPerAuthor[*it]);
916 // If we have other authors, count them and their commits.
917 if (!others.empty())
918 DrawOthers(others, graphData, m_commitsPerAuthor);
920 // Paint the graph now that we're through.
921 m_graph.Invalidate();
924 void CStatGraphDlg::ShowByDate(int stringx, int title, IntervalDataMap &data)
926 if(!PreViewStat(false)) return;
928 // We need at least one author
929 if (m_authorNames.empty()) return;
931 // Set up the graph.
932 CString temp;
933 UpdateData();
934 m_graph.SetGraphType(m_GraphType, m_bStacked);
935 temp.LoadString(stringx);
936 m_graph.SetYAxisLabel(temp);
937 temp.LoadString(title);
938 m_graph.SetGraphTitle(temp);
940 m_graph.SetXAxisLabel(GetUnitString());
942 // Find out which authors are to be shown and which are to be skipped.
943 std::list<tstring> authors;
944 std::list<tstring> others;
945 FilterSkippedAuthors(authors, others);
947 // Add a graph series for each author.
948 AuthorDataMap authorGraphMap;
949 for (std::list<tstring>::iterator it = authors.begin(); it != authors.end(); ++it)
950 authorGraphMap[*it] = m_graph.AppendGroup(it->c_str());
951 // If we have skipped authors, add a graph series for all those.
952 CString sOthers(MAKEINTRESOURCE(IDS_STATGRAPH_OTHERGROUP));
953 tstring othersName;
954 if (!others.empty())
956 sOthers.AppendFormat(L" (%Iu)", others.size());
957 othersName = (LPCWSTR)sOthers;
958 authorGraphMap[othersName] = m_graph.AppendGroup(sOthers);
961 // Mapping to collect commit counts in each interval
962 AuthorDataMap commitCount;
964 // Loop over all intervals/weeks and collect filtered data.
965 // Sum up data in each interval until the time unit changes.
966 for (int i=m_lastInterval; i>=m_firstInterval; --i)
968 // Collect data for authors listed by name.
969 if (!authors.empty())
971 for (std::list<tstring>::iterator it = authors.begin(); it != authors.end(); ++it)
973 // Do we have some data for the current author in the current interval?
974 AuthorDataMap::const_iterator data_it = data[i].find(*it);
975 if (data_it == data[i].end())
976 continue;
977 commitCount[*it] += data_it->second;
980 // Collect data for all skipped authors.
981 if (!others.empty())
983 for (std::list<tstring>::iterator it = others.begin(); it != others.end(); ++it)
985 // Do we have some data for the author in the current interval?
986 AuthorDataMap::const_iterator data_it = data[i].find(*it);
987 if (data_it == data[i].end())
988 continue;
989 commitCount[othersName] += data_it->second;
993 // Create a new data series for this unit/interval.
994 MyGraphSeries * graphData = new MyGraphSeries();
995 // Loop over all created graphs and set the corresponding data.
996 if (!authorGraphMap.empty())
998 for (AuthorDataMap::const_iterator it = authorGraphMap.begin(); it != authorGraphMap.end(); ++it)
1000 graphData->SetData(it->second, commitCount[it->first]);
1003 graphData->SetLabel(m_unitNames[i].c_str());
1004 m_graph.AddSeries(*graphData);
1005 m_graphDataArray.Add(graphData);
1007 // Reset commit count mapping.
1008 commitCount.clear();
1011 // Paint the graph now that we're through.
1012 m_graph.Invalidate();
1015 void CStatGraphDlg::ShowStats()
1017 if(!PreViewStat(true)) return;
1019 // Now we can use the gathered data to update the stats dialog.
1020 size_t nAuthors = m_authorNames.size();
1022 // Find most and least active author names.
1023 tstring mostActiveAuthor;
1024 tstring leastActiveAuthor;
1025 if (nAuthors > 0)
1027 mostActiveAuthor = m_authorNames.front();
1028 leastActiveAuthor = m_authorNames.back();
1031 // Obtain the statistics for the table.
1032 long nCommitsMin = -1;
1033 long nCommitsMax = -1;
1034 long nFileChangesMin = -1;
1035 long nFileChangesMax = -1;
1037 long nMostActiveMaxCommits = -1;
1038 long nMostActiveMinCommits = -1;
1039 long nLeastActiveMaxCommits = -1;
1040 long nLeastActiveMinCommits = -1;
1042 // Loop over all intervals and find min and max values for commit count and file changes.
1043 // Also store the stats for the most and least active authors.
1044 for (int i=m_firstInterval; i<=m_lastInterval; ++i)
1046 // Loop over all commits in this interval and count the number of commits by all authors.
1047 int commitCount = 0;
1048 AuthorDataMap::iterator commit_endit = m_commitsPerUnitAndAuthor[i].end();
1049 for (AuthorDataMap::iterator commit_it = m_commitsPerUnitAndAuthor[i].begin();
1050 commit_it != commit_endit; ++commit_it)
1052 commitCount += commit_it->second;
1054 if (nCommitsMin == -1 || commitCount < nCommitsMin)
1055 nCommitsMin = commitCount;
1056 if (nCommitsMax == -1 || commitCount > nCommitsMax)
1057 nCommitsMax = commitCount;
1059 // Loop over all commits in this interval and count the number of file changes by all authors.
1060 int fileChangeCount = 0;
1061 AuthorDataMap::iterator filechange_endit = m_filechangesPerUnitAndAuthor[i].end();
1062 for (AuthorDataMap::iterator filechange_it = m_filechangesPerUnitAndAuthor[i].begin();
1063 filechange_it != filechange_endit; ++filechange_it)
1065 fileChangeCount += filechange_it->second;
1067 if (nFileChangesMin == -1 || fileChangeCount < nFileChangesMin)
1068 nFileChangesMin = fileChangeCount;
1069 if (nFileChangesMax == -1 || fileChangeCount > nFileChangesMax)
1070 nFileChangesMax = fileChangeCount;
1072 // also get min/max data for most and least active authors
1073 if (nAuthors > 0)
1075 // check if author is present in this interval
1076 AuthorDataMap::iterator author_it = m_commitsPerUnitAndAuthor[i].find(mostActiveAuthor);
1077 long authorCommits;
1078 if (author_it == m_commitsPerUnitAndAuthor[i].end())
1079 authorCommits = 0;
1080 else
1081 authorCommits = author_it->second;
1082 if (nMostActiveMaxCommits == -1 || authorCommits > nMostActiveMaxCommits)
1083 nMostActiveMaxCommits = authorCommits;
1084 if (nMostActiveMinCommits == -1 || authorCommits < nMostActiveMinCommits)
1085 nMostActiveMinCommits = authorCommits;
1087 author_it = m_commitsPerUnitAndAuthor[i].find(leastActiveAuthor);
1088 if (author_it == m_commitsPerUnitAndAuthor[i].end())
1089 authorCommits = 0;
1090 else
1091 authorCommits = author_it->second;
1092 if (nLeastActiveMaxCommits == -1 || authorCommits > nLeastActiveMaxCommits)
1093 nLeastActiveMaxCommits = authorCommits;
1094 if (nLeastActiveMinCommits == -1 || authorCommits < nLeastActiveMinCommits)
1095 nLeastActiveMinCommits = authorCommits;
1098 if (nMostActiveMaxCommits == -1) nMostActiveMaxCommits = 0;
1099 if (nMostActiveMinCommits == -1) nMostActiveMinCommits = 0;
1100 if (nLeastActiveMaxCommits == -1) nLeastActiveMaxCommits = 0;
1101 if (nLeastActiveMinCommits == -1) nLeastActiveMinCommits = 0;
1103 int nWeeks = m_lastInterval-m_firstInterval;
1104 if (nWeeks == 0)
1105 nWeeks = 1;
1106 // Adjust the labels with the unit type (week, month, ...)
1107 CString labelText;
1108 labelText.Format(IDS_STATGRAPH_NUMBEROFUNIT, (LPCTSTR)GetUnitString());
1109 SetDlgItemText(IDC_NUMWEEK, labelText);
1110 labelText.Format(IDS_STATGRAPH_COMMITSBYUNIT, (LPCTSTR)GetUnitString());
1111 SetDlgItemText(IDC_COMMITSEACHWEEK, labelText);
1112 labelText.Format(IDS_STATGRAPH_FILECHANGESBYUNIT, (LPCTSTR)GetUnitString());
1113 SetDlgItemText(IDC_FILECHANGESEACHWEEK, (LPCTSTR)labelText);
1114 // We have now all data we want and we can fill in the labels...
1115 CString number;
1116 number.Format(L"%d", nWeeks);
1117 SetDlgItemText(IDC_NUMWEEKVALUE, number);
1118 number.Format(L"%Iu", nAuthors);
1119 SetDlgItemText(IDC_NUMAUTHORVALUE, number);
1120 number.Format(L"%Id", m_nTotalCommits);
1121 SetDlgItemText(IDC_NUMCOMMITSVALUE, number);
1122 number.Format(L"%ld", m_nTotalFileChanges);
1123 if (m_bDiffFetched)
1124 SetDlgItemText(IDC_NUMFILECHANGESVALUE, number);
1126 number.Format(L"%Id", m_parAuthors.GetCount() / nWeeks);
1127 SetDlgItemText(IDC_COMMITSEACHWEEKAVG, number);
1128 number.Format(L"%ld", nCommitsMax);
1129 SetDlgItemText(IDC_COMMITSEACHWEEKMAX, number);
1130 number.Format(L"%ld", nCommitsMin);
1131 SetDlgItemText(IDC_COMMITSEACHWEEKMIN, number);
1133 number.Format(L"%ld", m_nTotalFileChanges / nWeeks);
1134 //SetDlgItemText(IDC_FILECHANGESEACHWEEKAVG, number);
1135 number.Format(L"%ld", nFileChangesMax);
1136 //SetDlgItemText(IDC_FILECHANGESEACHWEEKMAX, number);
1137 number.Format(L"%ld", nFileChangesMin);
1138 //SetDlgItemText(IDC_FILECHANGESEACHWEEKMIN, number);
1140 number.Format(L"%ld (%ld (+) %ld (-))", m_nTotalLinesInc + m_nTotalLinesDec, m_nTotalLinesInc, m_nTotalLinesDec);
1141 if (m_bDiffFetched)
1142 SetDlgItemText(IDC_TOTAL_LINE_WITHOUT_NEW_DEL_VALUE, number);
1143 number.Format(L"%ld (%ld (+) %ld (-))", m_nTotalLinesInc + m_nTotalLinesDec + m_nTotalLinesNew + m_nTotalLinesDel,
1144 m_nTotalLinesInc + m_nTotalLinesNew, m_nTotalLinesDec + m_nTotalLinesDel);
1145 if (m_bDiffFetched)
1146 SetDlgItemText(IDC_TOTAL_LINE_WITH_NEW_DEL_VALUE, number);
1148 if (nAuthors == 0)
1150 SetDlgItemText(IDC_MOSTACTIVEAUTHORNAME, L"");
1151 SetDlgItemText(IDC_MOSTACTIVEAUTHORAVG, L"0");
1152 SetDlgItemText(IDC_MOSTACTIVEAUTHORMAX, L"0");
1153 SetDlgItemText(IDC_MOSTACTIVEAUTHORMIN, L"0");
1154 SetDlgItemText(IDC_LEASTACTIVEAUTHORNAME, L"");
1155 SetDlgItemText(IDC_LEASTACTIVEAUTHORAVG, L"0");
1156 SetDlgItemText(IDC_LEASTACTIVEAUTHORMAX, L"0");
1157 SetDlgItemText(IDC_LEASTACTIVEAUTHORMIN, L"0");
1159 else
1161 SetDlgItemText(IDC_MOSTACTIVEAUTHORNAME, mostActiveAuthor.c_str());
1162 number.Format(L"%ld", m_commitsPerAuthor[mostActiveAuthor] / nWeeks);
1163 SetDlgItemText(IDC_MOSTACTIVEAUTHORAVG, number);
1164 number.Format(L"%ld", nMostActiveMaxCommits);
1165 SetDlgItemText(IDC_MOSTACTIVEAUTHORMAX, number);
1166 number.Format(L"%ld", nMostActiveMinCommits);
1167 SetDlgItemText(IDC_MOSTACTIVEAUTHORMIN, number);
1169 SetDlgItemText(IDC_LEASTACTIVEAUTHORNAME, leastActiveAuthor.c_str());
1170 number.Format(L"%ld", m_commitsPerAuthor[leastActiveAuthor] / nWeeks);
1171 SetDlgItemText(IDC_LEASTACTIVEAUTHORAVG, number);
1172 number.Format(L"%ld", nLeastActiveMaxCommits);
1173 SetDlgItemText(IDC_LEASTACTIVEAUTHORMAX, number);
1174 number.Format(L"%ld", nLeastActiveMinCommits);
1175 SetDlgItemText(IDC_LEASTACTIVEAUTHORMIN, number);
1179 int CStatGraphDlg::RollPercentageOfAuthorship(double it)
1180 { return (int)it + (it - (int)it >= 0.5);}
1182 void CStatGraphDlg::OnCbnSelchangeGraphcombo()
1184 UpdateData();
1186 Metrics useMetric = (Metrics) m_cGraphType.GetItemData(m_cGraphType.GetCurSel());
1187 switch (useMetric )
1189 case AllStat:
1190 case CommitsByDate:
1191 // by date
1192 m_btnGraphLine.EnableWindow(TRUE);
1193 m_btnGraphLineStacked.EnableWindow(TRUE);
1194 m_btnGraphPie.EnableWindow(TRUE);
1195 m_GraphType = MyGraph::Line;
1196 m_bStacked = false;
1197 break;
1198 case PercentageOfAuthorship:
1199 case CommitsByAuthor:
1200 // by author
1201 m_btnGraphLine.EnableWindow(FALSE);
1202 m_btnGraphLineStacked.EnableWindow(FALSE);
1203 m_btnGraphPie.EnableWindow(TRUE);
1204 m_GraphType = MyGraph::Bar;
1205 m_bStacked = false;
1206 break;
1208 RedrawGraph();
1212 int CStatGraphDlg::GetUnitCount()
1214 if (m_nDays < 8)
1215 return m_nDays;
1216 if (m_nWeeks < 15)
1217 return m_nWeeks;
1218 if (m_nWeeks < 80)
1219 return (m_nWeeks/4)+1;
1220 if (m_nWeeks < 320)
1221 return (m_nWeeks/13)+1; // quarters
1222 return (m_nWeeks/52)+1;
1225 int CStatGraphDlg::GetUnit(const CTime& time)
1227 if (m_nDays < 8)
1228 return time.GetMonth()*100 + time.GetDay(); // month*100+day as the unit
1229 if (m_nWeeks < 15)
1230 return GetCalendarWeek(time);
1231 if (m_nWeeks < 80)
1232 return time.GetMonth();
1233 if (m_nWeeks < 320)
1234 return ((time.GetMonth()-1)/3)+1; // quarters
1235 return time.GetYear();
1238 CStatGraphDlg::UnitType CStatGraphDlg::GetUnitType()
1240 if (m_nDays < 8)
1241 return Days;
1242 if (m_nWeeks < 15)
1243 return Weeks;
1244 if (m_nWeeks < 80)
1245 return Months;
1246 if (m_nWeeks < 320)
1247 return Quarters;
1248 return Years;
1251 CString CStatGraphDlg::GetUnitString()
1253 if (m_nDays < 8)
1254 return CString(MAKEINTRESOURCE(IDS_STATGRAPH_COMMITSBYDATEXDAY));
1255 if (m_nWeeks < 15)
1256 return CString(MAKEINTRESOURCE(IDS_STATGRAPH_COMMITSBYDATEXWEEK));
1257 if (m_nWeeks < 80)
1258 return CString(MAKEINTRESOURCE(IDS_STATGRAPH_COMMITSBYDATEXMONTH));
1259 if (m_nWeeks < 320)
1260 return CString(MAKEINTRESOURCE(IDS_STATGRAPH_COMMITSBYDATEXQUARTER));
1261 return CString(MAKEINTRESOURCE(IDS_STATGRAPH_COMMITSBYDATEXYEAR));
1264 CString CStatGraphDlg::GetUnitLabel(int unit, CTime &lasttime)
1266 CString temp;
1267 switch (GetUnitType())
1269 case Days:
1271 // month*100+day as the unit
1272 int day = unit % 100;
1273 int month = unit / 100;
1274 switch (m_langOrder)
1276 case 0: // month day year
1277 temp.Format(L"%d/%d/%.2d", month, day, lasttime.GetYear() % 100);
1278 break;
1279 case 1: // day month year
1280 default:
1281 temp.Format(L"%d/%d/%.2d", day, month, lasttime.GetYear() % 100);
1282 break;
1283 case 2: // year month day
1284 temp.Format(L"%.2d/%d/%d", lasttime.GetYear() % 100, month, day);
1285 break;
1288 break;
1289 case Weeks:
1291 int year = lasttime.GetYear();
1292 if ((unit == 1)&&(lasttime.GetMonth() == 12))
1293 year += 1;
1295 switch (m_langOrder)
1297 case 0: // month day year
1298 case 1: // day month year
1299 default:
1300 temp.Format(L"%d/%.2d", unit, year % 100);
1301 break;
1302 case 2: // year month day
1303 temp.Format(L"%.2d/%d", year % 100, unit);
1304 break;
1307 break;
1308 case Months:
1309 switch (m_langOrder)
1311 case 0: // month day year
1312 case 1: // day month year
1313 default:
1314 temp.Format(L"%d/%.2d", unit, lasttime.GetYear() % 100);
1315 break;
1316 case 2: // year month day
1317 temp.Format(L"%.2d/%d", lasttime.GetYear() % 100, unit);
1318 break;
1320 break;
1321 case Quarters:
1322 switch (m_langOrder)
1324 case 0: // month day year
1325 case 1: // day month year
1326 default:
1327 temp.Format(L"%d/%.2d", unit, lasttime.GetYear() % 100);
1328 break;
1329 case 2: // year month day
1330 temp.Format(L"%.2d/%d", lasttime.GetYear() % 100, unit);
1331 break;
1333 break;
1334 case Years:
1335 temp.Format(L"%d", unit);
1336 break;
1338 return temp;
1341 void CStatGraphDlg::OnHScroll(UINT nSBCode, UINT nPos, CScrollBar* pScrollBar)
1343 if (nSBCode == TB_THUMBTRACK)
1344 return CDialog::OnHScroll(nSBCode, nPos, pScrollBar);
1346 ShowSelectStat((Metrics) m_cGraphType.GetItemData(m_cGraphType.GetCurSel()));
1347 CDialog::OnHScroll(nSBCode, nPos, pScrollBar);
1350 void CStatGraphDlg::OnNeedText(NMHDR *pnmh, LRESULT * /*pResult*/)
1352 TOOLTIPTEXT* pttt = (TOOLTIPTEXT*) pnmh;
1353 if (pttt->hdr.idFrom == (UINT_PTR) m_Skipper.GetSafeHwnd())
1355 size_t included_authors_count = m_Skipper.GetPos();
1356 // if we only leave out one author, still include him with his name
1357 if (included_authors_count + 1 == m_authorNames.size())
1358 ++included_authors_count;
1360 // find the minimum number of commits that the shown authors have
1361 int min_commits = 0;
1362 included_authors_count = min(included_authors_count, m_authorNames.size());
1363 std::list<tstring>::iterator author_it = m_authorNames.begin();
1364 advance(author_it, included_authors_count);
1365 if (author_it != m_authorNames.begin())
1366 min_commits = m_commitsPerAuthor[ *(--author_it) ];
1368 CString string;
1369 int percentage = int(min_commits*100.0/(m_nTotalCommits ? m_nTotalCommits : 1));
1370 string.Format(IDS_STATGRAPH_AUTHORSLIDER_TT, m_Skipper.GetPos(), min_commits, percentage);
1371 StringCchCopy(pttt->szText, _countof(pttt->szText), (LPCTSTR) string);
1375 void CStatGraphDlg::AuthorsCaseSensitiveChanged()
1377 UpdateData(); // update checkbox state
1378 GatherData(FALSE, TRUE); // first regenerate the statistics data
1379 RedrawGraph(); // then update the current statistics page
1382 void CStatGraphDlg::SortModeChanged()
1384 UpdateData(); // update checkbox state
1385 RedrawGraph(); // then update the current statistics page
1388 void CStatGraphDlg::OnBnClickedCommitternames()
1390 UpdateData(); // update checkbox state
1391 GatherData(FALSE, TRUE); // first regenerate the statistics data
1392 RedrawGraph(); // then update the current statistics page
1395 void CStatGraphDlg::OnBnClickedCommitdates()
1397 UpdateData(); // update checkbox state
1398 GatherData(FALSE, TRUE); // first regenerate the statistics data
1399 RedrawGraph(); // then update the current statistics page
1402 void CStatGraphDlg::ClearGraph()
1404 m_graph.Clear();
1405 for (int j=0; j<m_graphDataArray.GetCount(); ++j)
1406 delete ((MyGraphSeries *)m_graphDataArray.GetAt(j));
1407 m_graphDataArray.RemoveAll();
1410 void CStatGraphDlg::RedrawGraph()
1412 EnableDisableMenu();
1413 m_btnGraphBar.SetState(BST_UNCHECKED);
1414 m_btnGraphBarStacked.SetState(BST_UNCHECKED);
1415 m_btnGraphLine.SetState(BST_UNCHECKED);
1416 m_btnGraphLineStacked.SetState(BST_UNCHECKED);
1417 m_btnGraphPie.SetState(BST_UNCHECKED);
1419 if ((m_GraphType == MyGraph::Bar)&&(m_bStacked))
1421 m_btnGraphBarStacked.SetState(BST_CHECKED);
1423 if ((m_GraphType == MyGraph::Bar)&&(!m_bStacked))
1425 m_btnGraphBar.SetState(BST_CHECKED);
1427 if ((m_GraphType == MyGraph::Line)&&(m_bStacked))
1429 m_btnGraphLineStacked.SetState(BST_CHECKED);
1431 if ((m_GraphType == MyGraph::Line)&&(!m_bStacked))
1433 m_btnGraphLine.SetState(BST_CHECKED);
1435 if (m_GraphType == MyGraph::PieChart)
1437 m_btnGraphPie.SetState(BST_CHECKED);
1440 UpdateData();
1441 ShowSelectStat((Metrics) m_cGraphType.GetItemData(m_cGraphType.GetCurSel()), true);
1443 void CStatGraphDlg::OnBnClickedGraphbarbutton()
1445 m_GraphType = MyGraph::Bar;
1446 m_bStacked = false;
1447 RedrawGraph();
1450 void CStatGraphDlg::OnBnClickedGraphbarstackedbutton()
1452 m_GraphType = MyGraph::Bar;
1453 m_bStacked = true;
1454 RedrawGraph();
1457 void CStatGraphDlg::OnBnClickedGraphlinebutton()
1459 m_GraphType = MyGraph::Line;
1460 m_bStacked = false;
1461 RedrawGraph();
1464 void CStatGraphDlg::OnBnClickedGraphlinestackedbutton()
1466 m_GraphType = MyGraph::Line;
1467 m_bStacked = true;
1468 RedrawGraph();
1471 void CStatGraphDlg::OnBnClickedGraphpiebutton()
1473 m_GraphType = MyGraph::PieChart;
1474 m_bStacked = false;
1475 RedrawGraph();
1478 void CStatGraphDlg::EnableDisableMenu()
1480 UINT nEnable = MF_BYCOMMAND;
1482 Metrics SelectMetric = (Metrics) m_cGraphType.GetItemData(m_cGraphType.GetCurSel());
1484 nEnable |= (SelectMetric > TextStatStart && SelectMetric < TextStatEnd)
1485 ? (MF_DISABLED | MF_GRAYED) : MF_ENABLED;
1487 GetMenu()->EnableMenuItem(ID_FILE_SAVESTATGRAPHAS, nEnable);
1490 void CStatGraphDlg::OnFileSavestatgraphas()
1492 CString tempfile;
1493 int filterindex = 0;
1494 if (CAppUtils::FileOpenSave(tempfile, &filterindex, IDS_REVGRAPH_SAVEPIC, IDS_STATPICFILEFILTER, false, m_hWnd))
1496 // if the user doesn't specify a file extension, default to
1497 // wmf and add that extension to the filename. But only if the
1498 // user chose the 'pictures' filter. The filename isn't changed
1499 // if the 'All files' filter was chosen.
1500 CString extension;
1501 int dotPos = tempfile.ReverseFind('.');
1502 int slashPos = tempfile.ReverseFind('\\');
1503 if (dotPos > slashPos)
1504 extension = tempfile.Mid(dotPos);
1505 if ((filterindex == 1)&&(extension.IsEmpty()))
1507 extension = L".wmf";
1508 tempfile += extension;
1510 SaveGraph(tempfile);
1514 void CStatGraphDlg::SaveGraph(CString sFilename)
1516 CString extension = CPathUtils::GetFileExtFromPath(sFilename);
1517 if (extension.CompareNoCase(L".wmf") == 0)
1519 // save the graph as an enhanced meta file
1520 CMyMetaFileDC wmfDC;
1521 wmfDC.CreateEnhanced(nullptr, sFilename, nullptr, L"TortoiseGit\0Statistics\0\0");
1522 wmfDC.SetAttribDC(GetDC()->GetSafeHdc());
1523 RedrawGraph();
1524 m_graph.DrawGraph(wmfDC);
1525 HENHMETAFILE hemf = wmfDC.CloseEnhanced();
1526 DeleteEnhMetaFile(hemf);
1528 else
1530 // save the graph as a pixel picture instead of a vector picture
1531 // create dc to paint on
1534 CWindowDC ddc(this);
1535 CDC dc;
1536 if (!dc.CreateCompatibleDC(&ddc))
1538 ShowErrorMessage();
1539 return;
1541 CRect rect;
1542 GetDlgItem(IDC_GRAPH)->GetClientRect(&rect);
1543 HBITMAP hbm = ::CreateCompatibleBitmap(ddc.m_hDC, rect.Width(), rect.Height());
1544 if (hbm==0)
1546 ShowErrorMessage();
1547 return;
1549 HBITMAP oldbm = (HBITMAP)dc.SelectObject(hbm);
1550 // paint the whole graph
1551 RedrawGraph();
1552 m_graph.DrawGraph(dc);
1553 // now use GDI+ to save the picture
1554 CLSID encoderClsid;
1555 GdiplusStartupInput gdiplusStartupInput;
1556 ULONG_PTR gdiplusToken;
1557 CString sErrormessage;
1558 if (GdiplusStartup(&gdiplusToken, &gdiplusStartupInput, nullptr) == Ok)
1561 Bitmap bitmap(hbm, nullptr);
1562 if (bitmap.GetLastStatus()==Ok)
1564 // Get the CLSID of the encoder.
1565 int ret = 0;
1566 if (CPathUtils::GetFileExtFromPath(sFilename).CompareNoCase(L".png") == 0)
1567 ret = GetEncoderClsid(L"image/png", &encoderClsid);
1568 else if (CPathUtils::GetFileExtFromPath(sFilename).CompareNoCase(L".jpg") == 0)
1569 ret = GetEncoderClsid(L"image/jpeg", &encoderClsid);
1570 else if (CPathUtils::GetFileExtFromPath(sFilename).CompareNoCase(L".jpeg") == 0)
1571 ret = GetEncoderClsid(L"image/jpeg", &encoderClsid);
1572 else if (CPathUtils::GetFileExtFromPath(sFilename).CompareNoCase(L".bmp") == 0)
1573 ret = GetEncoderClsid(L"image/bmp", &encoderClsid);
1574 else if (CPathUtils::GetFileExtFromPath(sFilename).CompareNoCase(L".gif") == 0)
1575 ret = GetEncoderClsid(L"image/gif", &encoderClsid);
1576 else
1578 sFilename += L".jpg";
1579 ret = GetEncoderClsid(L"image/jpeg", &encoderClsid);
1581 if (ret >= 0)
1583 CStringW tfile = CStringW(sFilename);
1584 bitmap.Save(tfile, &encoderClsid, nullptr);
1586 else
1587 sErrormessage.Format(IDS_REVGRAPH_ERR_NOENCODER, (LPCTSTR)CPathUtils::GetFileExtFromPath(sFilename));
1589 else
1590 sErrormessage.LoadString(IDS_REVGRAPH_ERR_NOBITMAP);
1592 GdiplusShutdown(gdiplusToken);
1594 else
1595 sErrormessage.LoadString(IDS_REVGRAPH_ERR_GDIINIT);
1596 dc.SelectObject(oldbm);
1597 dc.DeleteDC();
1598 if (!sErrormessage.IsEmpty())
1599 ::MessageBox(m_hWnd, sErrormessage, L"TortoiseGit", MB_ICONERROR);
1601 catch (CException * pE)
1603 TCHAR szErrorMsg[2048] = { 0 };
1604 pE->GetErrorMessage(szErrorMsg, 2048);
1605 pE->Delete();
1606 ::MessageBox(m_hWnd, szErrorMsg, L"TortoiseGit", MB_ICONERROR);
1611 int CStatGraphDlg::GetEncoderClsid(const WCHAR* format, CLSID* pClsid)
1613 UINT num = 0; // number of image encoders
1614 UINT size = 0; // size of the image encoder array in bytes
1616 if (GetImageEncodersSize(&num, &size)!=Ok)
1617 return -1;
1618 if (size == 0)
1619 return -1; // Failure
1621 auto pMem = std::make_unique<BYTE[]>(size);
1622 auto pImageCodecInfo = (ImageCodecInfo*)(pMem.get());
1623 if (!pImageCodecInfo)
1624 return -1; // Failure
1626 if (GetImageEncoders(num, size, pImageCodecInfo)==Ok)
1628 for (UINT j = 0; j < num; ++j)
1630 if (wcscmp(pImageCodecInfo[j].MimeType, format) == 0)
1632 *pClsid = pImageCodecInfo[j].Clsid;
1633 return j; // Success
1637 return -1; // Failure
1640 void CStatGraphDlg::StoreCurrentGraphType()
1642 UpdateData();
1643 DWORD graphtype = static_cast<DWORD>(m_cGraphType.GetItemData(m_cGraphType.GetCurSel()));
1644 // encode the current chart type
1645 DWORD statspage = graphtype*10;
1646 if ((m_GraphType == MyGraph::Bar)&&(m_bStacked))
1647 statspage += 1;
1648 if ((m_GraphType == MyGraph::Bar)&&(!m_bStacked))
1649 statspage += 2;
1650 if ((m_GraphType == MyGraph::Line)&&(m_bStacked))
1651 statspage += 3;
1652 if ((m_GraphType == MyGraph::Line)&&(!m_bStacked))
1653 statspage += 4;
1654 if (m_GraphType == MyGraph::PieChart)
1655 statspage += 5;
1657 // store current chart type in registry
1658 CRegDWORD lastStatsPage(L"Software\\TortoiseGit\\LastViewedStatsPage", 0);
1659 lastStatsPage = statspage;
1661 CRegDWORD regAuthors(L"Software\\TortoiseGit\\StatAuthorsCaseSensitive");
1662 regAuthors = m_bAuthorsCaseSensitive;
1664 CRegDWORD regSort(L"Software\\TortoiseGit\\StatSortByCommitCount");
1665 regSort = m_bSortByCommitCount;
1667 CRegDWORD regCommitterName(L"Software\\TortoiseGit\\StatCommiterNames");
1668 regCommitterName = m_bUseCommitterNames;
1670 CRegDWORD regCommitDates(L"Software\\TortoiseGit\\StatCommitDates");
1671 regCommitDates = m_bUseCommitDates;
1674 void CStatGraphDlg::ShowErrorMessage()
1676 CFormatMessageWrapper errorDetails;
1677 if (errorDetails)
1678 MessageBox(errorDetails, L"Error", MB_OK | MB_ICONINFORMATION);
1681 void CStatGraphDlg::ShowSelectStat(Metrics SelectedMetric, bool reloadSkiper /* = false */)
1683 switch (SelectedMetric)
1685 case AllStat:
1686 LoadListOfAuthors(m_commitsPerAuthor, reloadSkiper);
1687 ShowStats();
1688 break;
1689 case CommitsByDate:
1690 LoadListOfAuthors(m_commitsPerAuthor, reloadSkiper);
1691 ShowByDate(IDS_STATGRAPH_COMMITSBYDATEY, IDS_STATGRAPH_COMMITSBYDATE, m_commitsPerUnitAndAuthor);
1692 break;
1693 case LinesWByDate:
1694 OnBnClickedFetchDiff();
1695 LoadListOfAuthors(m_commitsPerAuthor, reloadSkiper);
1696 ShowByDate(IDS_STATGRAPH_LINES_BYDATE_W_Y, IDS_STATGRAPH_LINES_BYDATE_W, m_LinesWPerUnitAndAuthor);
1697 break;
1698 case LinesWOByDate:
1699 OnBnClickedFetchDiff();
1700 LoadListOfAuthors(m_commitsPerAuthor, reloadSkiper);
1701 ShowByDate(IDS_STATGRAPH_LINES_BYDATE_WO_Y, IDS_STATGRAPH_LINES_BYDATE_WO, m_LinesWOPerUnitAndAuthor);
1702 break;
1703 case CommitsByAuthor:
1704 LoadListOfAuthors(m_commitsPerAuthor, reloadSkiper);
1705 ShowCommitsByAuthor();
1706 break;
1707 case PercentageOfAuthorship:
1708 OnBnClickedFetchDiff();
1709 LoadListOfAuthors(m_PercentageOfAuthorship, reloadSkiper, true);
1710 ShowPercentageOfAuthorship();
1711 break;
1712 default:
1713 ShowErrorMessage();
1717 double CStatGraphDlg::CoeffContribution(int distFromEnd) { return distFromEnd ? 1.0 / m_CoeffAuthorShip * distFromEnd : 1;}
1720 template <class MAP>
1721 void CStatGraphDlg::DrawOthers(const std::list<tstring> &others, MyGraphSeries *graphData, MAP &map)
1723 int nCommits = 0;
1724 for (std::list<tstring>::const_iterator it = others.begin(); it != others.end(); ++it)
1725 nCommits += RollPercentageOfAuthorship(map[*it]);
1727 CString sOthers(MAKEINTRESOURCE(IDS_STATGRAPH_OTHERGROUP));
1728 sOthers.AppendFormat(L" (%Iu)", others.size());
1729 int group = m_graph.AppendGroup(sOthers);
1730 graphData->SetData(group, (int)nCommits);
1734 template <class MAP>
1735 void CStatGraphDlg::LoadListOfAuthors (MAP &map, bool reloadSkiper/*= false*/, bool compare /*= false*/)
1737 m_authorNames.clear();
1738 if (!map.empty())
1740 for (MAP::const_iterator it = map.begin(); it != map.end(); ++it)
1742 if ((compare && RollPercentageOfAuthorship(map[it->first]) != 0) || !compare)
1743 m_authorNames.push_back(it->first);
1747 // Sort the list of authors based on commit count
1748 m_authorNames.sort(MoreCommitsThan<MAP::mapped_type>(map));
1750 // Set Skipper
1751 SetSkipper(reloadSkiper);
1755 void CStatGraphDlg::OnBnClickedFetchDiff()
1757 if (m_bDiffFetched)
1758 return;
1759 if (GatherData(TRUE))
1760 return;
1761 this->m_bDiffFetched = TRUE;
1762 GetDlgItem(IDC_CALC_DIFF)->ShowWindow(!m_bDiffFetched);
1764 ShowStats();