Fix typos
[TortoiseGit.git] / src / TortoiseProc / StatGraphDlg.cpp
blob9b33eadaed59c6210533f3490071c064491f6781
1 // TortoiseGit - a Windows shell extension for easy version control
3 // Copyright (C) 2008-2021, 2023-2024 - TortoiseGit
4 // Copyright (C) 2003-2011, 2014-2016, 2018 - 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 {
39 public:
40 using MapType = std::map<std::wstring, DataType>;
41 MoreCommitsThan(MapType &author_commits) : m_authorCommits(author_commits) {}
43 bool operator()(const std::wstring& lhs, const std::wstring& rhs)
45 return (m_authorCommits)[lhs] > (m_authorCommits)[rhs];
48 private:
49 MapType &m_authorCommits;
53 IMPLEMENT_DYNAMIC(CStatGraphDlg, CResizableStandAloneDialog)
54 CStatGraphDlg::CStatGraphDlg(CWnd* pParent /*=nullptr*/)
55 : CResizableStandAloneDialog(CStatGraphDlg::IDD, pParent)
56 , m_bStacked(FALSE)
57 , m_bAuthorsCaseSensitive(TRUE)
58 , m_bSortByCommitCount(TRUE)
59 , m_bUseCommitterNames(FALSE)
60 , m_bUseCommitDates(TRUE)
64 CStatGraphDlg::~CStatGraphDlg()
66 ClearGraph();
69 void CStatGraphDlg::OnOK() {
70 StoreCurrentGraphType();
71 __super::OnOK();
74 void CStatGraphDlg::OnCancel() {
75 StoreCurrentGraphType();
76 __super::OnCancel();
79 void CStatGraphDlg::DoDataExchange(CDataExchange* pDX)
81 CResizableStandAloneDialog::DoDataExchange(pDX);
82 DDX_Control(pDX, IDC_GRAPH, m_graph);
83 DDX_Control(pDX, IDC_GRAPHCOMBO, m_cGraphType);
84 DDX_Control(pDX, IDC_SKIPPER, m_Skipper);
85 DDX_Check(pDX, IDC_AUTHORSCASESENSITIVE, m_bAuthorsCaseSensitive);
86 DDX_Check(pDX, IDC_SORTBYCOMMITCOUNT, m_bSortByCommitCount);
87 DDX_Check(pDX, IDC_COMMITTERNAMES, m_bUseCommitterNames);
88 DDX_Check(pDX, IDC_COMMITDATES, m_bUseCommitDates);
89 DDX_Control(pDX, IDC_GRAPHBARBUTTON, m_btnGraphBar);
90 DDX_Control(pDX, IDC_GRAPHBARSTACKEDBUTTON, m_btnGraphBarStacked);
91 DDX_Control(pDX, IDC_GRAPHLINEBUTTON, m_btnGraphLine);
92 DDX_Control(pDX, IDC_GRAPHLINESTACKEDBUTTON, m_btnGraphLineStacked);
93 DDX_Control(pDX, IDC_GRAPHPIEBUTTON, m_btnGraphPie);
97 BEGIN_MESSAGE_MAP(CStatGraphDlg, CResizableStandAloneDialog)
98 ON_CBN_SELCHANGE(IDC_GRAPHCOMBO, OnCbnSelchangeGraphcombo)
99 ON_WM_HSCROLL()
100 ON_NOTIFY(TTN_NEEDTEXT, nullptr, OnNeedText)
101 ON_BN_CLICKED(IDC_AUTHORSCASESENSITIVE, &CStatGraphDlg::AuthorsCaseSensitiveChanged)
102 ON_BN_CLICKED(IDC_SORTBYCOMMITCOUNT, &CStatGraphDlg::SortModeChanged)
103 ON_BN_CLICKED(IDC_GRAPHBARBUTTON, &CStatGraphDlg::OnBnClickedGraphbarbutton)
104 ON_BN_CLICKED(IDC_GRAPHBARSTACKEDBUTTON, &CStatGraphDlg::OnBnClickedGraphbarstackedbutton)
105 ON_BN_CLICKED(IDC_GRAPHLINEBUTTON, &CStatGraphDlg::OnBnClickedGraphlinebutton)
106 ON_BN_CLICKED(IDC_GRAPHLINESTACKEDBUTTON, &CStatGraphDlg::OnBnClickedGraphlinestackedbutton)
107 ON_BN_CLICKED(IDC_GRAPHPIEBUTTON, &CStatGraphDlg::OnBnClickedGraphpiebutton)
108 ON_COMMAND(ID_FILE_SAVESTATGRAPHAS, &CStatGraphDlg::OnFileSavestatgraphas)
109 ON_BN_CLICKED(IDC_CALC_DIFF, &CStatGraphDlg::OnBnClickedFetchDiff)
110 ON_BN_CLICKED(IDC_COMMITTERNAMES, &CStatGraphDlg::OnBnClickedCommitternames)
111 ON_BN_CLICKED(IDC_COMMITDATES, &CStatGraphDlg::OnBnClickedCommitdates)
112 END_MESSAGE_MAP()
114 void CStatGraphDlg::LoadStatQueries (__in UINT curStr, Metrics loadMetric, bool setDef /* = false */)
116 CString temp;
117 temp.LoadString(curStr);
118 int sel = m_cGraphType.AddString(temp);
119 m_cGraphType.SetItemData(sel, loadMetric);
121 if (setDef) m_cGraphType.SetCurSel(sel);
124 void CStatGraphDlg::SetSkipper (bool reloadSkiper)
126 // We need to limit the number of authors due to GUI resource limitation.
127 // However, since author #251 will properly have < 1000th of the commits,
128 // the resolution limit of the screen will already not allow for displaying
129 // it in a reasonable way
131 int max_authors_count = max(1, static_cast<int>(min(m_authorNames.size(), size_t(250))));
132 m_Skipper.SetRange (1, max_authors_count);
133 m_Skipper.SetPageSize(5);
135 if (reloadSkiper)
136 m_Skipper.SetPos (max_authors_count);
139 BOOL CStatGraphDlg::OnInitDialog()
141 CResizableStandAloneDialog::OnInitDialog();
143 m_tooltips.AddTool(&m_btnGraphPie, IDS_STATGRAPH_PIEBUTTON_TT);
144 m_tooltips.AddTool(&m_btnGraphLineStacked, IDS_STATGRAPH_LINESTACKEDBUTTON_TT);
145 m_tooltips.AddTool(&m_btnGraphLine, IDS_STATGRAPH_LINEBUTTON_TT);
146 m_tooltips.AddTool(&m_btnGraphBarStacked, IDS_STATGRAPH_BARSTACKEDBUTTON_TT);
147 m_tooltips.AddTool(&m_btnGraphBar, IDS_STATGRAPH_BARBUTTON_TT);
148 m_tooltips.Activate(TRUE);
150 m_bAuthorsCaseSensitive = DWORD(CRegDWORD(L"Software\\TortoiseGit\\StatAuthorsCaseSensitive", m_bAuthorsCaseSensitive));
151 m_bSortByCommitCount = DWORD(CRegDWORD(L"Software\\TortoiseGit\\StatSortByCommitCount", m_bSortByCommitCount));
152 m_bUseCommitterNames = DWORD(CRegDWORD(L"Software\\TortoiseGit\\StatCommiterNames", m_bUseCommitterNames));
153 m_bUseCommitDates = DWORD(CRegDWORD(L"Software\\TortoiseGit\\StatCommitDates", m_bUseCommitDates));
154 UpdateData(FALSE);
156 // gather statistics data, only needs to be updated when the checkbox with
157 // the case sensitivity of author names is changed
158 GatherData();
160 //Load statistical queries
161 LoadStatQueries(IDS_STATGRAPH_STATS, AllStat, true);
162 LoadStatQueries(IDS_STATGRAPH_COMMITSBYDATE, CommitsByDate);
163 LoadStatQueries(IDS_STATGRAPH_COMMITSBYAUTHOR, CommitsByAuthor);
164 LoadStatQueries(IDS_STATGRAPH_PERCENTAGE_OF_AUTHORSHIP, PercentageOfAuthorship);
165 LoadStatQueries(IDS_STATGRAPH_LINES_BYDATE_W, LinesWByDate);
166 LoadStatQueries(IDS_STATGRAPH_LINES_BYDATE_WO, LinesWOByDate);
168 // set the dialog title to "Statistics - path/to/whatever/we/show/the/statistics/for"
169 if (m_path.IsEmpty())
170 CAppUtils::SetWindowTitle(*this, g_Git.m_CurrentDir);
171 else
172 CAppUtils::SetWindowTitle(*this, m_path.GetUIPathString());
174 int iconWidth = GetSystemMetrics(SM_CXSMICON);
175 int iconHeight = GetSystemMetrics(SM_CYSMICON);
176 m_btnGraphBar.SetImage(CCommonAppUtils::LoadIconEx(IDI_GRAPHBAR, iconWidth, iconHeight));
177 m_btnGraphBar.SizeToContent();
178 m_btnGraphBar.Invalidate();
179 m_btnGraphBarStacked.SetImage(CCommonAppUtils::LoadIconEx(IDI_GRAPHBARSTACKED, iconWidth, iconHeight));
180 m_btnGraphBarStacked.SizeToContent();
181 m_btnGraphBarStacked.Invalidate();
182 m_btnGraphLine.SetImage(CCommonAppUtils::LoadIconEx(IDI_GRAPHLINE, iconWidth, iconHeight));
183 m_btnGraphLine.SizeToContent();
184 m_btnGraphLine.Invalidate();
185 m_btnGraphLineStacked.SetImage(CCommonAppUtils::LoadIconEx(IDI_GRAPHLINESTACKED, iconWidth, iconHeight));
186 m_btnGraphLineStacked.SizeToContent();
187 m_btnGraphLineStacked.Invalidate();
188 m_btnGraphPie.SetImage(CCommonAppUtils::LoadIconEx(IDI_GRAPHPIE, iconWidth, iconHeight));
189 m_btnGraphPie.SizeToContent();
190 m_btnGraphPie.Invalidate();
192 AdjustControlSize(IDC_AUTHORSCASESENSITIVE);
193 AdjustControlSize(IDC_SORTBYCOMMITCOUNT);
194 AdjustControlSize(IDC_COMMITTERNAMES);
195 AdjustControlSize(IDC_COMMITDATES);
197 AddAnchor(IDC_GRAPHTYPELABEL, TOP_LEFT);
198 AddAnchor(IDC_GRAPH, TOP_LEFT, BOTTOM_RIGHT);
199 AddAnchor(IDC_GRAPHCOMBO, TOP_LEFT, TOP_RIGHT);
201 AddAnchor(IDC_NUMWEEK, TOP_LEFT);
202 AddAnchor(IDC_NUMWEEKVALUE, TOP_RIGHT);
203 AddAnchor(IDC_NUMAUTHOR, TOP_LEFT);
204 AddAnchor(IDC_NUMAUTHORVALUE, TOP_RIGHT);
205 AddAnchor(IDC_NUMCOMMITS, TOP_LEFT);
206 AddAnchor(IDC_NUMCOMMITSVALUE, TOP_RIGHT);
207 AddAnchor(IDC_NUMFILECHANGES, TOP_LEFT);
208 AddAnchor(IDC_NUMFILECHANGESVALUE, TOP_RIGHT);
210 AddAnchor(IDC_TOTAL_LINE_WITHOUT_NEW_DEL, TOP_LEFT);
211 AddAnchor(IDC_TOTAL_LINE_WITHOUT_NEW_DEL_VALUE, TOP_RIGHT);
212 AddAnchor(IDC_TOTAL_LINE_WITH_NEW_DEL, TOP_LEFT);
213 AddAnchor(IDC_TOTAL_LINE_WITH_NEW_DEL_VALUE, TOP_RIGHT);
215 AddAnchor(IDC_CALC_DIFF, TOP_RIGHT);
217 AddAnchor(IDC_AVG, TOP_RIGHT);
218 AddAnchor(IDC_MIN, TOP_RIGHT);
219 AddAnchor(IDC_MAX, TOP_RIGHT);
220 AddAnchor(IDC_COMMITSEACHWEEK, TOP_LEFT);
221 AddAnchor(IDC_MOSTACTIVEAUTHOR, TOP_LEFT);
222 AddAnchor(IDC_LEASTACTIVEAUTHOR, TOP_LEFT);
223 AddAnchor(IDC_MOSTACTIVEAUTHORNAME, TOP_LEFT, TOP_RIGHT);
224 AddAnchor(IDC_LEASTACTIVEAUTHORNAME, TOP_LEFT, TOP_RIGHT);
225 AddAnchor(IDC_FILECHANGESEACHWEEK, TOP_LEFT);
226 AddAnchor(IDC_COMMITSEACHWEEKAVG, TOP_RIGHT);
227 AddAnchor(IDC_COMMITSEACHWEEKMIN, TOP_RIGHT);
228 AddAnchor(IDC_COMMITSEACHWEEKMAX, TOP_RIGHT);
229 AddAnchor(IDC_MOSTACTIVEAUTHORAVG, TOP_RIGHT);
230 AddAnchor(IDC_MOSTACTIVEAUTHORMIN, TOP_RIGHT);
231 AddAnchor(IDC_MOSTACTIVEAUTHORMAX, TOP_RIGHT);
232 AddAnchor(IDC_LEASTACTIVEAUTHORAVG, TOP_RIGHT);
233 AddAnchor(IDC_LEASTACTIVEAUTHORMIN, TOP_RIGHT);
234 AddAnchor(IDC_LEASTACTIVEAUTHORMAX, TOP_RIGHT);
235 AddAnchor(IDC_FILECHANGESEACHWEEKAVG, TOP_RIGHT);
236 AddAnchor(IDC_FILECHANGESEACHWEEKMIN, TOP_RIGHT);
237 AddAnchor(IDC_FILECHANGESEACHWEEKMAX, TOP_RIGHT);
239 AddAnchor(IDC_GRAPHBARBUTTON, BOTTOM_RIGHT);
240 AddAnchor(IDC_GRAPHBARSTACKEDBUTTON, BOTTOM_RIGHT);
241 AddAnchor(IDC_GRAPHLINEBUTTON, BOTTOM_RIGHT);
242 AddAnchor(IDC_GRAPHLINESTACKEDBUTTON, BOTTOM_RIGHT);
243 AddAnchor(IDC_GRAPHPIEBUTTON, BOTTOM_RIGHT);
245 AddAnchor(IDC_AUTHORSCASESENSITIVE, BOTTOM_LEFT);
246 AddAnchor(IDC_SORTBYCOMMITCOUNT, BOTTOM_LEFT);
247 AddAnchor(IDC_COMMITTERNAMES, BOTTOM_LEFT);
248 AddAnchor(IDC_COMMITDATES, BOTTOM_LEFT);
249 AddAnchor(IDC_SKIPPER, BOTTOM_LEFT, BOTTOM_RIGHT);
250 AddAnchor(IDC_SKIPPERLABEL, BOTTOM_LEFT);
251 AddAnchor(IDOK, BOTTOM_RIGHT);
252 EnableSaveRestore(L"StatGraphDlg");
254 // set the min/max values on the skipper
255 SetSkipper (true);
257 // we use a stats page encoding here, 0 stands for the statistics dialog
258 CRegDWORD lastStatsPage(L"Software\\TortoiseGit\\LastViewedStatsPage", 0);
260 // open last viewed statistics page as first page
261 int graphtype = lastStatsPage / 10;
262 for (int i = 0; i < m_cGraphType.GetCount(); i++)
264 if (static_cast<int>(m_cGraphType.GetItemData(i)) == graphtype)
266 m_cGraphType.SetCurSel(i);
267 break;
271 OnCbnSelchangeGraphcombo();
273 int statspage = lastStatsPage % 10;
274 switch (statspage) {
275 case 1 :
276 m_GraphType = MyGraph::GraphType::Bar;
277 m_bStacked = true;
278 break;
279 case 2 :
280 m_GraphType = MyGraph::GraphType::Bar;
281 m_bStacked = false;
282 break;
283 case 3 :
284 m_GraphType = MyGraph::GraphType::Line;
285 m_bStacked = true;
286 break;
287 case 4 :
288 m_GraphType = MyGraph::GraphType::Line;
289 m_bStacked = false;
290 break;
291 case 5 :
292 m_GraphType = MyGraph::GraphType::PieChart;
293 break;
295 default : return TRUE;
298 LCID m_locale = MAKELCID(static_cast<DWORD>(CRegStdDWORD(L"Software\\TortoiseGit\\LanguageID", MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT))), SORT_DEFAULT);
300 bool bUseSystemLocale = !!static_cast<DWORD>(CRegStdDWORD(L"Software\\TortoiseGit\\UseSystemLocaleForDates", TRUE));
301 LCID locale = bUseSystemLocale ? MAKELCID(MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), SORT_DEFAULT) : m_locale;
303 wchar_t langBuf[11] = { 0 };
304 GetLocaleInfo(locale, LOCALE_IDATE, langBuf, _countof(langBuf));
306 m_langOrder = _wtoi(langBuf);
308 return TRUE;
311 void CStatGraphDlg::ShowLabels(BOOL bShow)
313 if (m_parAuthors.IsEmpty() || m_parDates.IsEmpty() || m_parFileChanges.IsEmpty())
314 return;
316 int nCmdShow = bShow ? SW_SHOW : SW_HIDE;
318 GetDlgItem(IDC_GRAPH)->ShowWindow(bShow ? SW_HIDE : SW_SHOW);
319 GetDlgItem(IDC_NUMWEEK)->ShowWindow(nCmdShow);
320 GetDlgItem(IDC_NUMWEEKVALUE)->ShowWindow(nCmdShow);
321 GetDlgItem(IDC_NUMAUTHOR)->ShowWindow(nCmdShow);
322 GetDlgItem(IDC_NUMAUTHORVALUE)->ShowWindow(nCmdShow);
323 GetDlgItem(IDC_NUMCOMMITS)->ShowWindow(nCmdShow);
324 GetDlgItem(IDC_NUMCOMMITSVALUE)->ShowWindow(nCmdShow);
325 GetDlgItem(IDC_NUMFILECHANGES)->ShowWindow(nCmdShow);
326 GetDlgItem(IDC_NUMFILECHANGESVALUE)->ShowWindow(nCmdShow);
327 GetDlgItem(IDC_TOTAL_LINE_WITHOUT_NEW_DEL)->ShowWindow(nCmdShow);
328 GetDlgItem(IDC_TOTAL_LINE_WITHOUT_NEW_DEL_VALUE)->ShowWindow(nCmdShow);
329 GetDlgItem(IDC_TOTAL_LINE_WITH_NEW_DEL)->ShowWindow(nCmdShow);
330 GetDlgItem(IDC_TOTAL_LINE_WITH_NEW_DEL_VALUE)->ShowWindow(nCmdShow);
331 GetDlgItem(IDC_CALC_DIFF)->ShowWindow(nCmdShow && !m_bDiffFetched);
333 GetDlgItem(IDC_AVG)->ShowWindow(nCmdShow);
334 GetDlgItem(IDC_MIN)->ShowWindow(nCmdShow);
335 GetDlgItem(IDC_MAX)->ShowWindow(nCmdShow);
336 GetDlgItem(IDC_COMMITSEACHWEEK)->ShowWindow(nCmdShow);
337 GetDlgItem(IDC_MOSTACTIVEAUTHOR)->ShowWindow(nCmdShow);
338 GetDlgItem(IDC_LEASTACTIVEAUTHOR)->ShowWindow(nCmdShow);
339 GetDlgItem(IDC_MOSTACTIVEAUTHORNAME)->ShowWindow(nCmdShow);
340 GetDlgItem(IDC_LEASTACTIVEAUTHORNAME)->ShowWindow(nCmdShow);
341 //GetDlgItem(IDC_FILECHANGESEACHWEEK)->ShowWindow(nCmdShow);
342 GetDlgItem(IDC_COMMITSEACHWEEKAVG)->ShowWindow(nCmdShow);
343 GetDlgItem(IDC_COMMITSEACHWEEKMIN)->ShowWindow(nCmdShow);
344 GetDlgItem(IDC_COMMITSEACHWEEKMAX)->ShowWindow(nCmdShow);
345 GetDlgItem(IDC_MOSTACTIVEAUTHORAVG)->ShowWindow(nCmdShow);
346 GetDlgItem(IDC_MOSTACTIVEAUTHORMIN)->ShowWindow(nCmdShow);
347 GetDlgItem(IDC_MOSTACTIVEAUTHORMAX)->ShowWindow(nCmdShow);
348 GetDlgItem(IDC_LEASTACTIVEAUTHORAVG)->ShowWindow(nCmdShow);
349 GetDlgItem(IDC_LEASTACTIVEAUTHORMIN)->ShowWindow(nCmdShow);
350 GetDlgItem(IDC_LEASTACTIVEAUTHORMAX)->ShowWindow(nCmdShow);
351 GetDlgItem(IDC_FILECHANGESEACHWEEKAVG)->ShowWindow(nCmdShow);
352 GetDlgItem(IDC_FILECHANGESEACHWEEKMIN)->ShowWindow(nCmdShow);
353 GetDlgItem(IDC_FILECHANGESEACHWEEKMAX)->ShowWindow(nCmdShow);
355 GetDlgItem(IDC_SORTBYCOMMITCOUNT)->ShowWindow(bShow ? SW_HIDE : SW_SHOW);
356 GetDlgItem(IDC_SKIPPER)->ShowWindow(bShow ? SW_HIDE : SW_SHOW);
357 GetDlgItem(IDC_SKIPPERLABEL)->ShowWindow(bShow ? SW_HIDE : SW_SHOW);
358 m_btnGraphBar.ShowWindow(bShow ? SW_HIDE : SW_SHOW);
359 m_btnGraphBarStacked.ShowWindow(bShow ? SW_HIDE : SW_SHOW);
360 m_btnGraphLine.ShowWindow(bShow ? SW_HIDE : SW_SHOW);
361 m_btnGraphLineStacked.ShowWindow(bShow ? SW_HIDE : SW_SHOW);
362 m_btnGraphPie.ShowWindow(bShow ? SW_HIDE : SW_SHOW);
365 void CStatGraphDlg::UpdateWeekCount()
367 // Sanity check
368 if (m_parDates.IsEmpty())
369 return;
371 // Already updated? No need to do it again.
372 if (m_nWeeks >= 0)
373 return;
375 // Determine first and last date in dates array
376 __time64_t min_date = static_cast<__time64_t>(m_parDates.GetAt(0));
377 __time64_t max_date = min_date;
378 INT_PTR count = m_parDates.GetCount();
379 for (INT_PTR i=0; i<count; ++i)
381 __time64_t d = static_cast<__time64_t>(m_parDates.GetAt(i));
382 if (d < min_date) min_date = d;
383 else if (d > max_date) max_date = d;
386 // Store start date of the interval in the member variable m_minDate
387 m_minDate = min_date;
388 m_maxDate = max_date;
390 // How many weeks does the time period cover?
392 // Get time difference between start and end date
393 double secs = _difftime64(max_date, m_minDate);
395 m_nWeeks = static_cast<int>(ceil(secs / static_cast<double>(m_SecondsInWeek)));
396 m_nDays = static_cast<int>(ceil(secs / static_cast<double>(m_SecondsInDay)));
399 int CStatGraphDlg::GetCalendarWeek(const CTime& time)
401 // Note:
402 // the calculation of the calendar week is wrong if DST is in effect
403 // and the date to calculate the week for is in DST and within the range
404 // of the DST offset (e.g. one hour).
405 // For example, if DST starts on Sunday march 30 and the date to get the week for
406 // is Monday, march 31, 0:30:00, then the returned week is one week less than
407 // the real week.
408 // TODO: ?
409 // write a function
410 // getDSTOffset(const CTime& time)
411 // which returns the DST offset for a given time/date. Then we can use this offset
412 // to correct our GetDays() calculation to get the correct week again
413 // This of course won't work for 'history' dates, because Windows doesn't have
414 // that information (only Vista has such a function: GetTimeZoneInformationForYear() )
415 int iWeekOfYear = 0;
417 int iYear = time.GetYear();
418 int iFirstDayOfWeek = 0;
419 int iFirstWeekOfYear = 0;
420 wchar_t loc[2] = { 0 };
421 GetLocaleInfo(LOCALE_USER_DEFAULT, LOCALE_IFIRSTDAYOFWEEK, loc, _countof(loc));
422 iFirstDayOfWeek = int(loc[0]-'0');
423 GetLocaleInfo(LOCALE_USER_DEFAULT, LOCALE_IFIRSTWEEKOFYEAR, loc, _countof(loc));
424 iFirstWeekOfYear = int(loc[0]-'0');
425 CTime dDateFirstJanuary(iYear,1,1,0,0,0);
426 int iDayOfWeek = (dDateFirstJanuary.GetDayOfWeek()+5+iFirstDayOfWeek)%7;
428 // Select mode
429 // 0 Week containing 1/1 is the first week of that year.
430 // 1 First full week following 1/1 is the first week of that year.
431 // 2 First week containing at least four days is the first week of that year.
432 switch (iFirstWeekOfYear)
434 case 0:
436 // Week containing 1/1 is the first week of that year.
438 // check if this week reaches into the next year
439 dDateFirstJanuary = CTime(iYear+1,1,1,0,0,0);
441 // Get start of week
444 iDayOfWeek = (time.GetDayOfWeek()+5+iFirstDayOfWeek)%7;
446 catch (CAtlException)
449 CTime dStartOfWeek = time-CTimeSpan(iDayOfWeek,0,0,0);
451 // If this week spans over to 1/1 this is week 1
452 if (dStartOfWeek + CTimeSpan(6,0,0,0) >= dDateFirstJanuary)
454 // we are in the last week of the year that spans over 1/1
455 iWeekOfYear = 1;
457 else
459 // Get week day of 1/1
460 dDateFirstJanuary = CTime(iYear,1,1,0,0,0);
461 iDayOfWeek = (dDateFirstJanuary.GetDayOfWeek() +5 + iFirstDayOfWeek) % 7;
462 // Just count from 1/1
463 iWeekOfYear = static_cast<int>(((time-dDateFirstJanuary).GetDays() + iDayOfWeek) / 7) + 1;
466 break;
467 case 1:
469 // First full week following 1/1 is the first week of that year.
471 // If the 1.1 is the start of the week everything is ok
472 // else we need the next week is the correct result
473 iWeekOfYear =
474 static_cast<int>(((time-dDateFirstJanuary).GetDays() + iDayOfWeek) / 7) +
475 (iDayOfWeek==0 ? 1:0);
477 // If we are in week 0 we are in the first not full week
478 // calculate from the last year
479 if (iWeekOfYear==0)
481 // Special case: we are in the week of 1.1 but 1.1. is not on the
482 // start of week. Calculate based on the last year
483 dDateFirstJanuary = CTime(iYear-1,1,1,0,0,0);
484 iDayOfWeek =
485 (dDateFirstJanuary.GetDayOfWeek()+5+iFirstDayOfWeek)%7;
486 // and we correct this in the same we we done this before but
487 // the result is now 52 or 53 and not 0
488 iWeekOfYear =
489 static_cast<int>(((time-dDateFirstJanuary).GetDays()+iDayOfWeek) / 7) +
490 (iDayOfWeek<=3 ? 1:0);
493 break;
494 case 2:
496 // First week containing at least four days is the first week of that year.
498 // Each year can start with any day of the week. But our
499 // weeks always start with Monday. So we add the day of week
500 // before calculation of the final week of year.
501 // Rule: is the 1.1 a Mo,Tu,We,Th than the week starts on the 1.1 with
502 // week==1, else a week later, so we add one for all those days if
503 // day is less <=3 Mo,Tu,We,Th. Otherwise 1.1 is in the last week of the
504 // previous year
505 iWeekOfYear =
506 static_cast<int>(((time-dDateFirstJanuary).GetDays()+iDayOfWeek) / 7) +
507 (iDayOfWeek<=3 ? 1:0);
509 // special cases
510 if (iWeekOfYear==0)
512 // special case week 0. We got a day before the 1.1, 2.1 or 3.1, were the
513 // 1.1. is not a Mo, Tu, We, Th. So the week 1 does not start with the 1.1.
514 // So we calculate the week according to the 1.1 of the year before
516 dDateFirstJanuary = CTime(iYear-1,1,1,0,0,0);
517 iDayOfWeek =
518 (dDateFirstJanuary.GetDayOfWeek()+5+iFirstDayOfWeek)%7;
519 // and we correct this in the same we we done this before but the result
520 // is now 52 or 53 and not 0
521 iWeekOfYear =
522 static_cast<int>(((time-dDateFirstJanuary).GetDays()+iDayOfWeek) / 7) +
523 (iDayOfWeek<=3 ? 1:0);
525 else if (iWeekOfYear==53)
527 // special case week 53. Either we got the correct week 53 or we just got the
528 // week 1 of the next year. So is the 1.1.(year+1) also a Mo, Tu, We, Th than
529 // we already have the week 1, otherwise week 53 is correct
531 dDateFirstJanuary = CTime(iYear+1,1,1,0,0,0);
532 iDayOfWeek =
533 (dDateFirstJanuary.GetDayOfWeek()+5+iFirstDayOfWeek)%7;
534 // 1.1. in week 1 or week 53?
535 iWeekOfYear = iDayOfWeek<=3 ? 1:53;
538 break;
539 default:
540 ASSERT(FALSE);
541 break;
543 // return result
544 return iWeekOfYear;
547 int CStatGraphDlg::GatherData(BOOL fetchdiff, BOOL keepFetchedData)
549 m_parAuthors.RemoveAll();
550 m_parDates.RemoveAll();
551 if (m_parFileChanges2.IsEmpty()) // Fixes issue #1948
552 keepFetchedData = FALSE;
553 if (!keepFetchedData)
555 m_parFileChanges.RemoveAll();
556 m_lineInc.RemoveAll();
557 m_lineDec.RemoveAll();
558 m_lineDel.RemoveAll();
559 m_lineNew.RemoveAll();
561 else
563 m_parFileChanges.Copy(m_parFileChanges2);
564 m_lineNew.Copy(m_lineNew2);
565 m_lineDel.Copy(m_lineDel2);
566 m_lineInc.Copy(m_lineInc2);
567 m_lineDec.Copy(m_lineDec2);
570 CSysProgressDlg progress;
571 if (fetchdiff)
573 progress.SetTitle(CString(MAKEINTRESOURCE(IDS_PROGS_TITLE_GATHERSTATISTICS)));
574 progress.FormatNonPathLine(1, IDS_PROC_STATISTICS_DIFF);
575 progress.SetTime(true);
576 progress.ShowModeless(this);
579 // create arrays which are aware of the current filter
580 ULONGLONG starttime = GetTickCount64();
582 if (m_bUseCommitDates)
583 std::sort(m_ShowList.begin(), m_ShowList.end(), [](GitRevLoglist* pLhs, GitRevLoglist* pRhs) { return pLhs->GetCommitterDate() > pRhs->GetCommitterDate(); });
584 else
585 std::sort(m_ShowList.begin(), m_ShowList.end(), [](GitRevLoglist* pLhs, GitRevLoglist* pRhs) { return pLhs->GetAuthorDate() > pRhs->GetAuthorDate(); });
587 for (size_t i = 0; i < m_ShowList.size(); ++i)
589 auto pLogEntry = m_ShowList[i];
590 int inc, dec, incnewfile, decdeletedfile, files;
591 inc = dec = incnewfile = decdeletedfile = files= 0;
593 CString strAuthor = m_bUseCommitterNames ? pLogEntry->GetCommitterName() : pLogEntry->GetAuthorName();
594 if (strAuthor.IsEmpty())
595 strAuthor.LoadString(IDS_STATGRAPH_EMPTYAUTHOR);
596 m_parAuthors.Add(strAuthor);
597 if (m_bUseCommitDates)
598 m_parDates.Add(static_cast<DWORD>(pLogEntry->GetCommitterDate().GetTime()));
599 else
600 m_parDates.Add(static_cast<DWORD>(pLogEntry->GetAuthorDate().GetTime()));
602 if (fetchdiff && (pLogEntry->m_ParentHash.size() <= 1))
604 auto list = pLogEntry->GetFiles(nullptr);
605 files = list.GetCount();
607 for (int j = 0; j < files; j++)
609 if (list[j].m_Action & CTGitPath::LOGACTIONS_DELETED)
610 decdeletedfile += _wtol(list[j].m_StatDel);
611 else if(list[j].m_Action & CTGitPath::LOGACTIONS_ADDED)
612 incnewfile += _wtol(list[j].m_StatAdd);
613 else
615 inc += _wtol(list[j].m_StatAdd);
616 dec += _wtol(list[j].m_StatDel);
619 if (progress.HasUserCancelled())
620 return -1;
623 if (!keepFetchedData)
625 m_parFileChanges.Add(files);
626 m_lineInc.Add(inc);
627 m_lineDec.Add(dec);
628 m_lineDel.Add(decdeletedfile);
629 m_lineNew.Add(incnewfile);
632 if (progress.IsVisible() && (GetTickCount64() - starttime > 100UL))
634 progress.FormatNonPathLine(2, L"%s: %s", static_cast<LPCWSTR>(pLogEntry->m_CommitHash.ToString(g_Git.GetShortHASHLength())), static_cast<LPCWSTR>(pLogEntry->GetSubject()));
635 progress.SetProgress64(i, m_ShowList.size());
636 starttime = GetTickCount64();
641 if (fetchdiff)
643 m_parFileChanges2.Copy(m_parFileChanges);
644 m_lineNew2.Copy(m_lineNew);
645 m_lineDel2.Copy(m_lineDel);
646 m_lineInc2.Copy(m_lineInc);
647 m_lineDec2.Copy(m_lineDec);
650 m_nTotalCommits = m_parAuthors.GetCount();
651 m_nTotalFileChanges = 0;
653 // Update m_nWeeks and m_minDate
654 UpdateWeekCount();
656 // Now create a mapping that holds the information per week.
657 m_commitsPerUnitAndAuthor.clear();
658 m_filechangesPerUnitAndAuthor.clear();
659 m_commitsPerAuthor.clear();
660 m_PercentageOfAuthorship.clear();
661 m_LinesWPerUnitAndAuthor.clear();
662 m_LinesWOPerUnitAndAuthor.clear();
664 int interval = 0;
665 __time64_t d = static_cast<__time64_t>(m_parDates.GetAt(0));
666 int nLastUnit = GetUnit(d);
667 double AllContributionAuthor = 0;
669 m_nTotalLinesInc = m_nTotalLinesDec = m_nTotalLinesNew = m_nTotalLinesDel =0;
671 // Now loop over all weeks and gather the info
672 for (LONG i=0; i<m_nTotalCommits; ++i)
674 // Find the interval number
675 __time64_t commitDate = static_cast<__time64_t>(m_parDates.GetAt(i));
676 int u = GetUnit(commitDate);
677 if (nLastUnit != u)
678 interval++;
679 nLastUnit = u;
680 // Find the authors name
681 CString sAuth = m_parAuthors.GetAt(i);
682 if (!m_bAuthorsCaseSensitive)
683 sAuth = sAuth.MakeLower();
684 std::wstring author = std::wstring(sAuth);
685 // Increase total commit count for this author
686 m_commitsPerAuthor[author]++;
687 // Increase the commit count for this author in this week
688 m_commitsPerUnitAndAuthor[interval][author]++;
690 m_LinesWPerUnitAndAuthor[interval][author] += m_lineInc.GetAt(i) + m_lineDec.GetAt(i) + m_lineNew.GetAt(i) + + m_lineDel.GetAt(i);
691 m_LinesWOPerUnitAndAuthor[interval][author] += m_lineInc.GetAt(i) + m_lineDec.GetAt(i);
693 CTime t = m_parDates.GetAt(i);
694 m_unitNames[interval] = GetUnitLabel(nLastUnit, t);
695 // Increase the file change count for this author in this week
696 int fileChanges = m_parFileChanges.GetAt(i);
697 m_filechangesPerUnitAndAuthor[interval][author] += fileChanges;
698 m_nTotalFileChanges += fileChanges;
700 //calculate Contribution Author
701 double contributionAuthor = CoeffContribution(static_cast<int>(m_nTotalCommits) - i -1) * (fileChanges ? fileChanges : 1);
702 AllContributionAuthor += contributionAuthor;
703 m_PercentageOfAuthorship[author] += contributionAuthor;
705 m_nTotalLinesInc += m_lineInc.GetAt(i);
706 m_nTotalLinesDec += m_lineDec.GetAt(i);
707 m_nTotalLinesNew += m_lineNew.GetAt(i);
708 m_nTotalLinesDel += m_lineDel.GetAt(i);
711 // Find first and last interval number.
712 if (!m_commitsPerUnitAndAuthor.empty())
714 IntervalDataMap::iterator interval_it = m_commitsPerUnitAndAuthor.begin();
715 m_firstInterval = interval_it->first;
716 interval_it = m_commitsPerUnitAndAuthor.end();
717 --interval_it;
718 m_lastInterval = interval_it->first;
719 // Sanity check - if m_lastInterval is too large it could freeze TSVN and take up all memory!!!
720 assert(m_lastInterval >= 0 && m_lastInterval < 10000);
722 else
724 m_firstInterval = 0;
725 m_lastInterval = -1;
728 // Get a list of authors names
729 LoadListOfAuthors(m_commitsPerAuthor);
731 // Calculate percent of Contribution Authors
732 for (auto it = m_authorNames.begin(); it != m_authorNames.end(); ++it)
734 m_PercentageOfAuthorship[*it] = (m_PercentageOfAuthorship[*it] *100)/ AllContributionAuthor;
737 // All done, now the statistics pages can retrieve the data and
738 // extract the information to be shown.
740 return 0;
743 void CStatGraphDlg::FilterSkippedAuthors(std::list<std::wstring>& included_authors, std::list<std::wstring>& skipped_authors)
745 included_authors.clear();
746 skipped_authors.clear();
748 unsigned int included_authors_count = m_Skipper.GetPos();
749 // if we only leave out one author, still include him with his name
750 if (included_authors_count + 1 == m_authorNames.size())
751 ++included_authors_count;
753 // add the included authors first
754 auto author_it = m_authorNames.begin();
755 while (included_authors_count > 0 && author_it != m_authorNames.end())
757 // Add him/her to the included list
758 included_authors.push_back(*author_it);
759 ++author_it;
760 --included_authors_count;
763 // If we haven't reached the end yet, copy all remaining authors into the
764 // skipped author list.
765 std::copy(author_it, m_authorNames.end(), std::back_inserter(skipped_authors) );
767 // Sort authors alphabetically if user wants that.
768 if (!m_bSortByCommitCount)
769 included_authors.sort();
772 bool CStatGraphDlg::PreViewStat(bool fShowLabels)
774 if (m_parAuthors.IsEmpty() || m_parDates.IsEmpty() || m_parFileChanges.IsEmpty())
775 return false;
776 ShowLabels(fShowLabels);
778 //If view graphic
779 if (!fShowLabels) ClearGraph();
781 // This function relies on a previous call of GatherData().
782 // This can be detected by checking the week count.
783 // If the week count is equal to -1, it hasn't been called before.
784 if (m_nWeeks == -1)
785 GatherData(FALSE, TRUE);
786 // If week count is still -1, something bad has happened, probably invalid data!
787 if (m_nWeeks == -1)
788 return false;
790 return true;
793 MyGraphSeries *CStatGraphDlg::PreViewGraph(__in UINT GraphTitle, __in UINT YAxisLabel, __in UINT XAxisLabel /*= nullptr*/)
795 if(!PreViewStat(false))
796 return nullptr;
798 // We need at least one author
799 if (m_authorNames.empty())
800 return nullptr;
802 // Add a single series to the chart
803 MyGraphSeries * graphData = new MyGraphSeries();
804 m_graph.AddSeries(*graphData);
805 m_graphDataArray.Add(graphData);
807 // Set up the graph.
808 CString temp;
809 UpdateData();
810 m_graph.SetGraphType(m_GraphType, m_bStacked);
811 temp.LoadString(YAxisLabel);
812 m_graph.SetYAxisLabel(temp);
813 temp.LoadString(XAxisLabel);
814 m_graph.SetXAxisLabel(temp);
815 temp.LoadString(GraphTitle);
816 m_graph.SetGraphTitle(temp);
818 return graphData;
821 void CStatGraphDlg::ShowPercentageOfAuthorship()
823 // Set up the graph.
824 MyGraphSeries * graphData = PreViewGraph(IDS_STATGRAPH_PERCENTAGE_OF_AUTHORSHIP,
825 IDS_STATGRAPH_PERCENTAGE_OF_AUTHORSHIPY,
826 IDS_STATGRAPH_COMMITSBYAUTHORMOREX);
827 if (!graphData) return;
829 // Find out which authors are to be shown and which are to be skipped.
830 std::list<std::wstring> authors;
831 std::list<std::wstring> others;
834 FilterSkippedAuthors(authors, others);
836 // Loop over all authors in the authors list and
837 // add them to the graph.
839 if (!authors.empty())
841 for (auto it = authors.begin(); it != authors.end(); ++it)
843 int group = m_graph.AppendGroup(it->c_str());
844 graphData->SetData(group, RollPercentageOfAuthorship(m_PercentageOfAuthorship[*it]));
848 // If we have other authors, count them and their commits.
849 if (!others.empty())
850 DrawOthers(others, graphData, m_PercentageOfAuthorship);
852 // Paint the graph now that we're through.
853 m_graph.Invalidate();
856 void CStatGraphDlg::ShowCommitsByAuthor()
858 // Set up the graph.
859 MyGraphSeries * graphData = PreViewGraph(IDS_STATGRAPH_COMMITSBYAUTHOR,
860 IDS_STATGRAPH_COMMITSBYAUTHORY,
861 IDS_STATGRAPH_COMMITSBYAUTHORX);
862 if (!graphData) return;
864 // Find out which authors are to be shown and which are to be skipped.
865 std::list<std::wstring> authors;
866 std::list<std::wstring> others;
867 FilterSkippedAuthors(authors, others);
869 // Loop over all authors in the authors list and
870 // add them to the graph.
872 if (!authors.empty())
874 for (auto it = authors.begin(); it != authors.end(); ++it)
876 int group = m_graph.AppendGroup(it->c_str());
877 graphData->SetData(group, m_commitsPerAuthor[*it]);
881 // If we have other authors, count them and their commits.
882 if (!others.empty())
883 DrawOthers(others, graphData, m_commitsPerAuthor);
885 // Paint the graph now that we're through.
886 m_graph.Invalidate();
889 void CStatGraphDlg::ShowByDate(int stringx, int title, IntervalDataMap &data)
891 if(!PreViewStat(false)) return;
893 // We need at least one author
894 if (m_authorNames.empty()) return;
896 // Set up the graph.
897 CString temp;
898 UpdateData();
899 m_graph.SetGraphType(m_GraphType, m_bStacked);
900 temp.LoadString(stringx);
901 m_graph.SetYAxisLabel(temp);
902 temp.LoadString(title);
903 m_graph.SetGraphTitle(temp);
905 m_graph.SetXAxisLabel(GetUnitString());
907 // Find out which authors are to be shown and which are to be skipped.
908 std::list<std::wstring> authors;
909 std::list<std::wstring> others;
910 FilterSkippedAuthors(authors, others);
912 // Add a graph series for each author.
913 AuthorDataMap authorGraphMap;
914 for (auto it = authors.begin(); it != authors.end(); ++it)
915 authorGraphMap[*it] = m_graph.AppendGroup(it->c_str());
916 // If we have skipped authors, add a graph series for all those.
917 CString sOthers(MAKEINTRESOURCE(IDS_STATGRAPH_OTHERGROUP));
918 std::wstring othersName;
919 if (!others.empty())
921 sOthers.AppendFormat(L" (%Iu)", others.size());
922 othersName = static_cast<LPCWSTR>(sOthers);
923 authorGraphMap[othersName] = m_graph.AppendGroup(sOthers);
926 // Mapping to collect commit counts in each interval
927 AuthorDataMap commitCount;
929 // Loop over all intervals/weeks and collect filtered data.
930 // Sum up data in each interval until the time unit changes.
931 for (int i=m_lastInterval; i>=m_firstInterval; --i)
933 // Collect data for authors listed by name.
934 if (!authors.empty())
936 for (auto it = authors.begin(); it != authors.end(); ++it)
938 // Do we have some data for the current author in the current interval?
939 AuthorDataMap::const_iterator data_it = data[i].find(*it);
940 if (data_it == data[i].end())
941 continue;
942 commitCount[*it] += data_it->second;
945 // Collect data for all skipped authors.
946 if (!others.empty())
948 for (auto it = others.begin(); it != others.end(); ++it)
950 // Do we have some data for the author in the current interval?
951 AuthorDataMap::const_iterator data_it = data[i].find(*it);
952 if (data_it == data[i].end())
953 continue;
954 commitCount[othersName] += data_it->second;
958 // Create a new data series for this unit/interval.
959 MyGraphSeries * graphData = new MyGraphSeries();
960 // Loop over all created graphs and set the corresponding data.
961 if (!authorGraphMap.empty())
963 for (AuthorDataMap::const_iterator it = authorGraphMap.begin(); it != authorGraphMap.end(); ++it)
965 graphData->SetData(it->second, commitCount[it->first]);
968 graphData->SetLabel(m_unitNames[i].c_str());
969 m_graph.AddSeries(*graphData);
970 m_graphDataArray.Add(graphData);
972 // Reset commit count mapping.
973 commitCount.clear();
976 // Paint the graph now that we're through.
977 m_graph.Invalidate();
980 void CStatGraphDlg::ShowStats()
982 if(!PreViewStat(true)) return;
984 // Now we can use the gathered data to update the stats dialog.
985 size_t nAuthors = m_authorNames.size();
987 // Find most and least active author names.
988 std::wstring mostActiveAuthor;
989 std::wstring leastActiveAuthor;
990 if (nAuthors > 0)
992 mostActiveAuthor = m_authorNames.front();
993 leastActiveAuthor = m_authorNames.back();
996 // Obtain the statistics for the table.
997 long nCommitsMin = -1;
998 long nCommitsMax = -1;
999 long nFileChangesMin = -1;
1000 long nFileChangesMax = -1;
1002 long nMostActiveMaxCommits = -1;
1003 long nMostActiveMinCommits = -1;
1004 long nLeastActiveMaxCommits = -1;
1005 long nLeastActiveMinCommits = -1;
1007 // Loop over all intervals and find min and max values for commit count and file changes.
1008 // Also store the stats for the most and least active authors.
1009 for (int i=m_firstInterval; i<=m_lastInterval; ++i)
1011 // Loop over all commits in this interval and count the number of commits by all authors.
1012 int commitCount = 0;
1013 AuthorDataMap::iterator commit_endit = m_commitsPerUnitAndAuthor[i].end();
1014 for (AuthorDataMap::iterator commit_it = m_commitsPerUnitAndAuthor[i].begin();
1015 commit_it != commit_endit; ++commit_it)
1017 commitCount += commit_it->second;
1019 if (nCommitsMin == -1 || commitCount < nCommitsMin)
1020 nCommitsMin = commitCount;
1021 if (nCommitsMax == -1 || commitCount > nCommitsMax)
1022 nCommitsMax = commitCount;
1024 // Loop over all commits in this interval and count the number of file changes by all authors.
1025 int fileChangeCount = 0;
1026 AuthorDataMap::iterator filechange_endit = m_filechangesPerUnitAndAuthor[i].end();
1027 for (AuthorDataMap::iterator filechange_it = m_filechangesPerUnitAndAuthor[i].begin();
1028 filechange_it != filechange_endit; ++filechange_it)
1030 fileChangeCount += filechange_it->second;
1032 if (nFileChangesMin == -1 || fileChangeCount < nFileChangesMin)
1033 nFileChangesMin = fileChangeCount;
1034 if (nFileChangesMax == -1 || fileChangeCount > nFileChangesMax)
1035 nFileChangesMax = fileChangeCount;
1037 // also get min/max data for most and least active authors
1038 if (nAuthors > 0)
1040 // check if author is present in this interval
1041 AuthorDataMap::iterator author_it = m_commitsPerUnitAndAuthor[i].find(mostActiveAuthor);
1042 long authorCommits;
1043 if (author_it == m_commitsPerUnitAndAuthor[i].end())
1044 authorCommits = 0;
1045 else
1046 authorCommits = author_it->second;
1047 if (nMostActiveMaxCommits == -1 || authorCommits > nMostActiveMaxCommits)
1048 nMostActiveMaxCommits = authorCommits;
1049 if (nMostActiveMinCommits == -1 || authorCommits < nMostActiveMinCommits)
1050 nMostActiveMinCommits = authorCommits;
1052 author_it = m_commitsPerUnitAndAuthor[i].find(leastActiveAuthor);
1053 if (author_it == m_commitsPerUnitAndAuthor[i].end())
1054 authorCommits = 0;
1055 else
1056 authorCommits = author_it->second;
1057 if (nLeastActiveMaxCommits == -1 || authorCommits > nLeastActiveMaxCommits)
1058 nLeastActiveMaxCommits = authorCommits;
1059 if (nLeastActiveMinCommits == -1 || authorCommits < nLeastActiveMinCommits)
1060 nLeastActiveMinCommits = authorCommits;
1063 if (nMostActiveMaxCommits == -1) nMostActiveMaxCommits = 0;
1064 if (nMostActiveMinCommits == -1) nMostActiveMinCommits = 0;
1065 if (nLeastActiveMaxCommits == -1) nLeastActiveMaxCommits = 0;
1066 if (nLeastActiveMinCommits == -1) nLeastActiveMinCommits = 0;
1068 int nWeeks = m_lastInterval-m_firstInterval;
1069 if (nWeeks == 0)
1070 nWeeks = 1;
1071 // Adjust the labels with the unit type (week, month, ...)
1072 CString labelText;
1073 labelText.Format(IDS_STATGRAPH_NUMBEROFUNIT, static_cast<LPCWSTR>(GetUnitString()));
1074 SetDlgItemText(IDC_NUMWEEK, labelText);
1075 labelText.Format(IDS_STATGRAPH_COMMITSBYUNIT, static_cast<LPCWSTR>(GetUnitString()));
1076 SetDlgItemText(IDC_COMMITSEACHWEEK, labelText);
1077 labelText.Format(IDS_STATGRAPH_FILECHANGESBYUNIT, static_cast<LPCWSTR>(GetUnitString()));
1078 SetDlgItemText(IDC_FILECHANGESEACHWEEK, static_cast<LPCWSTR>(labelText));
1079 // We have now all data we want and we can fill in the labels...
1080 CString number;
1081 number.Format(L"%d", nWeeks);
1082 SetDlgItemText(IDC_NUMWEEKVALUE, number);
1083 number.Format(L"%Iu", nAuthors);
1084 SetDlgItemText(IDC_NUMAUTHORVALUE, number);
1085 number.Format(L"%Id", m_nTotalCommits);
1086 SetDlgItemText(IDC_NUMCOMMITSVALUE, number);
1087 number.Format(L"%ld", m_nTotalFileChanges);
1088 if (m_bDiffFetched)
1089 SetDlgItemText(IDC_NUMFILECHANGESVALUE, number);
1091 number.Format(L"%Id", m_parAuthors.GetCount() / nWeeks);
1092 SetDlgItemText(IDC_COMMITSEACHWEEKAVG, number);
1093 number.Format(L"%ld", nCommitsMax);
1094 SetDlgItemText(IDC_COMMITSEACHWEEKMAX, number);
1095 number.Format(L"%ld", nCommitsMin);
1096 SetDlgItemText(IDC_COMMITSEACHWEEKMIN, number);
1098 number.Format(L"%ld", m_nTotalFileChanges / nWeeks);
1099 //SetDlgItemText(IDC_FILECHANGESEACHWEEKAVG, number);
1100 number.Format(L"%ld", nFileChangesMax);
1101 //SetDlgItemText(IDC_FILECHANGESEACHWEEKMAX, number);
1102 number.Format(L"%ld", nFileChangesMin);
1103 //SetDlgItemText(IDC_FILECHANGESEACHWEEKMIN, number);
1105 number.Format(L"%ld (%ld (+) %ld (-))", m_nTotalLinesInc + m_nTotalLinesDec, m_nTotalLinesInc, m_nTotalLinesDec);
1106 if (m_bDiffFetched)
1107 SetDlgItemText(IDC_TOTAL_LINE_WITHOUT_NEW_DEL_VALUE, number);
1108 number.Format(L"%ld (%ld (+) %ld (-))", m_nTotalLinesInc + m_nTotalLinesDec + m_nTotalLinesNew + m_nTotalLinesDel,
1109 m_nTotalLinesInc + m_nTotalLinesNew, m_nTotalLinesDec + m_nTotalLinesDel);
1110 if (m_bDiffFetched)
1111 SetDlgItemText(IDC_TOTAL_LINE_WITH_NEW_DEL_VALUE, number);
1113 if (nAuthors == 0)
1115 SetDlgItemText(IDC_MOSTACTIVEAUTHORNAME, L"");
1116 SetDlgItemText(IDC_MOSTACTIVEAUTHORAVG, L"0");
1117 SetDlgItemText(IDC_MOSTACTIVEAUTHORMAX, L"0");
1118 SetDlgItemText(IDC_MOSTACTIVEAUTHORMIN, L"0");
1119 SetDlgItemText(IDC_LEASTACTIVEAUTHORNAME, L"");
1120 SetDlgItemText(IDC_LEASTACTIVEAUTHORAVG, L"0");
1121 SetDlgItemText(IDC_LEASTACTIVEAUTHORMAX, L"0");
1122 SetDlgItemText(IDC_LEASTACTIVEAUTHORMIN, L"0");
1124 else
1126 SetDlgItemText(IDC_MOSTACTIVEAUTHORNAME, mostActiveAuthor.c_str());
1127 number.Format(L"%ld", m_commitsPerAuthor[mostActiveAuthor] / nWeeks);
1128 SetDlgItemText(IDC_MOSTACTIVEAUTHORAVG, number);
1129 number.Format(L"%ld", nMostActiveMaxCommits);
1130 SetDlgItemText(IDC_MOSTACTIVEAUTHORMAX, number);
1131 number.Format(L"%ld", nMostActiveMinCommits);
1132 SetDlgItemText(IDC_MOSTACTIVEAUTHORMIN, number);
1134 SetDlgItemText(IDC_LEASTACTIVEAUTHORNAME, leastActiveAuthor.c_str());
1135 number.Format(L"%ld", m_commitsPerAuthor[leastActiveAuthor] / nWeeks);
1136 SetDlgItemText(IDC_LEASTACTIVEAUTHORAVG, number);
1137 number.Format(L"%ld", nLeastActiveMaxCommits);
1138 SetDlgItemText(IDC_LEASTACTIVEAUTHORMAX, number);
1139 number.Format(L"%ld", nLeastActiveMinCommits);
1140 SetDlgItemText(IDC_LEASTACTIVEAUTHORMIN, number);
1144 int CStatGraphDlg::RollPercentageOfAuthorship(double it)
1146 return static_cast<int>(it) + (it - static_cast<int>(it) >= 0.5);
1149 void CStatGraphDlg::OnCbnSelchangeGraphcombo()
1151 UpdateData();
1153 Metrics useMetric = static_cast<Metrics>(m_cGraphType.GetItemData(m_cGraphType.GetCurSel()));
1154 switch (useMetric )
1156 case AllStat:
1157 case CommitsByDate:
1158 // by date
1159 m_btnGraphLine.EnableWindow(TRUE);
1160 m_btnGraphLineStacked.EnableWindow(TRUE);
1161 m_btnGraphPie.EnableWindow(TRUE);
1162 m_GraphType = MyGraph::GraphType::Line;
1163 m_bStacked = false;
1164 break;
1165 case PercentageOfAuthorship:
1166 case CommitsByAuthor:
1167 // by author
1168 m_btnGraphLine.EnableWindow(FALSE);
1169 m_btnGraphLineStacked.EnableWindow(FALSE);
1170 m_btnGraphPie.EnableWindow(TRUE);
1171 m_GraphType = MyGraph::GraphType::Bar;
1172 m_bStacked = false;
1173 break;
1175 RedrawGraph();
1178 int CStatGraphDlg::GetUnit(const CTime& time)
1180 if (m_nDays < 8)
1181 return time.GetMonth()*100 + time.GetDay(); // month*100+day as the unit
1182 if (m_nWeeks < 15)
1183 return GetCalendarWeek(time);
1184 if (m_nWeeks < 80)
1185 return time.GetMonth();
1186 if (m_nWeeks < 320)
1187 return ((time.GetMonth()-1)/3)+1; // quarters
1188 return time.GetYear();
1191 CStatGraphDlg::UnitType CStatGraphDlg::GetUnitType()
1193 if (m_nDays < 8)
1194 return Days;
1195 if (m_nWeeks < 15)
1196 return Weeks;
1197 if (m_nWeeks < 80)
1198 return Months;
1199 if (m_nWeeks < 320)
1200 return Quarters;
1201 return Years;
1204 CString CStatGraphDlg::GetUnitString()
1206 if (m_nDays < 8)
1207 return CString(MAKEINTRESOURCE(IDS_STATGRAPH_COMMITSBYDATEXDAY));
1208 if (m_nWeeks < 15)
1209 return CString(MAKEINTRESOURCE(IDS_STATGRAPH_COMMITSBYDATEXWEEK));
1210 if (m_nWeeks < 80)
1211 return CString(MAKEINTRESOURCE(IDS_STATGRAPH_COMMITSBYDATEXMONTH));
1212 if (m_nWeeks < 320)
1213 return CString(MAKEINTRESOURCE(IDS_STATGRAPH_COMMITSBYDATEXQUARTER));
1214 return CString(MAKEINTRESOURCE(IDS_STATGRAPH_COMMITSBYDATEXYEAR));
1217 CString CStatGraphDlg::GetUnitLabel(int unit, CTime &lasttime)
1219 CString temp;
1220 switch (GetUnitType())
1222 case Days:
1224 // month*100+day as the unit
1225 int day = unit % 100;
1226 int month = unit / 100;
1227 switch (m_langOrder)
1229 case 0: // month day year
1230 temp.Format(L"%d/%d/%.2d", month, day, lasttime.GetYear() % 100);
1231 break;
1232 case 1: // day month year
1233 default:
1234 temp.Format(L"%d/%d/%.2d", day, month, lasttime.GetYear() % 100);
1235 break;
1236 case 2: // year month day
1237 temp.Format(L"%.2d/%d/%d", lasttime.GetYear() % 100, month, day);
1238 break;
1241 break;
1242 case Weeks:
1244 int year = lasttime.GetYear();
1245 if ((unit == 1)&&(lasttime.GetMonth() == 12))
1246 year += 1;
1248 switch (m_langOrder)
1250 case 0: // month day year
1251 case 1: // day month year
1252 default:
1253 temp.Format(L"%d/%.2d", unit, year % 100);
1254 break;
1255 case 2: // year month day
1256 temp.Format(L"%.2d/%d", year % 100, unit);
1257 break;
1260 break;
1261 case Months:
1262 switch (m_langOrder)
1264 case 0: // month day year
1265 case 1: // day month year
1266 default:
1267 temp.Format(L"%d/%.2d", unit, lasttime.GetYear() % 100);
1268 break;
1269 case 2: // year month day
1270 temp.Format(L"%.2d/%d", lasttime.GetYear() % 100, unit);
1271 break;
1273 break;
1274 case Quarters:
1275 switch (m_langOrder)
1277 case 0: // month day year
1278 case 1: // day month year
1279 default:
1280 temp.Format(L"%d/%.2d", unit, lasttime.GetYear() % 100);
1281 break;
1282 case 2: // year month day
1283 temp.Format(L"%.2d/%d", lasttime.GetYear() % 100, unit);
1284 break;
1286 break;
1287 case Years:
1288 temp.Format(L"%d", unit);
1289 break;
1291 return temp;
1294 void CStatGraphDlg::OnHScroll(UINT nSBCode, UINT nPos, CScrollBar* pScrollBar)
1296 if (nSBCode == TB_THUMBTRACK)
1297 return CDialog::OnHScroll(nSBCode, nPos, pScrollBar);
1299 ShowSelectStat(static_cast<Metrics>(m_cGraphType.GetItemData(m_cGraphType.GetCurSel())));
1300 CDialog::OnHScroll(nSBCode, nPos, pScrollBar);
1303 void CStatGraphDlg::OnNeedText(NMHDR *pnmh, LRESULT * /*pResult*/)
1305 auto pttt = reinterpret_cast<TOOLTIPTEXT*>(pnmh);
1306 if (pttt->hdr.idFrom == reinterpret_cast<UINT_PTR>(m_Skipper.GetSafeHwnd()))
1308 size_t included_authors_count = m_Skipper.GetPos();
1309 // if we only leave out one author, still include him with his name
1310 if (included_authors_count + 1 == m_authorNames.size())
1311 ++included_authors_count;
1313 // find the minimum number of commits that the shown authors have
1314 int min_commits = 0;
1315 included_authors_count = min(included_authors_count, m_authorNames.size());
1316 auto author_it = m_authorNames.begin();
1317 advance(author_it, included_authors_count);
1318 if (author_it != m_authorNames.begin())
1319 min_commits = m_commitsPerAuthor[ *(--author_it) ];
1321 CString string;
1322 int percentage = int(min_commits*100.0/(m_nTotalCommits ? m_nTotalCommits : 1));
1323 string.FormatMessage(IDS_STATGRAPH_AUTHORSLIDER_TT, m_Skipper.GetPos(), min_commits, percentage);
1324 StringCchCopy(pttt->szText, _countof(pttt->szText), static_cast<LPCWSTR>(string));
1328 void CStatGraphDlg::AuthorsCaseSensitiveChanged()
1330 UpdateData(); // update checkbox state
1331 GatherData(FALSE, TRUE); // first regenerate the statistics data
1332 RedrawGraph(); // then update the current statistics page
1335 void CStatGraphDlg::SortModeChanged()
1337 UpdateData(); // update checkbox state
1338 RedrawGraph(); // then update the current statistics page
1341 void CStatGraphDlg::OnBnClickedCommitternames()
1343 UpdateData(); // update checkbox state
1344 GatherData(FALSE, TRUE); // first regenerate the statistics data
1345 RedrawGraph(); // then update the current statistics page
1348 void CStatGraphDlg::OnBnClickedCommitdates()
1350 UpdateData(); // update checkbox state
1351 GatherData(FALSE, TRUE); // first regenerate the statistics data
1352 RedrawGraph(); // then update the current statistics page
1355 void CStatGraphDlg::ClearGraph()
1357 m_graph.Clear();
1358 for (int j=0; j<m_graphDataArray.GetCount(); ++j)
1359 delete static_cast<MyGraphSeries*>(m_graphDataArray.GetAt(j));
1360 m_graphDataArray.RemoveAll();
1363 void CStatGraphDlg::RedrawGraph()
1365 EnableDisableMenu();
1366 m_btnGraphBar.SetState(BST_UNCHECKED);
1367 m_btnGraphBarStacked.SetState(BST_UNCHECKED);
1368 m_btnGraphLine.SetState(BST_UNCHECKED);
1369 m_btnGraphLineStacked.SetState(BST_UNCHECKED);
1370 m_btnGraphPie.SetState(BST_UNCHECKED);
1372 if ((m_GraphType == MyGraph::GraphType::Bar) && (m_bStacked))
1374 m_btnGraphBarStacked.SetState(BST_CHECKED);
1376 if ((m_GraphType == MyGraph::GraphType::Bar) && (!m_bStacked))
1378 m_btnGraphBar.SetState(BST_CHECKED);
1380 if ((m_GraphType == MyGraph::GraphType::Line) && (m_bStacked))
1382 m_btnGraphLineStacked.SetState(BST_CHECKED);
1384 if ((m_GraphType == MyGraph::GraphType::Line) && (!m_bStacked))
1386 m_btnGraphLine.SetState(BST_CHECKED);
1388 if (m_GraphType == MyGraph::GraphType::PieChart)
1390 m_btnGraphPie.SetState(BST_CHECKED);
1393 UpdateData();
1394 ShowSelectStat(static_cast<Metrics>(m_cGraphType.GetItemData(m_cGraphType.GetCurSel())), true);
1396 void CStatGraphDlg::OnBnClickedGraphbarbutton()
1398 m_GraphType = MyGraph::GraphType::Bar;
1399 m_bStacked = false;
1400 RedrawGraph();
1403 void CStatGraphDlg::OnBnClickedGraphbarstackedbutton()
1405 m_GraphType = MyGraph::GraphType::Bar;
1406 m_bStacked = true;
1407 RedrawGraph();
1410 void CStatGraphDlg::OnBnClickedGraphlinebutton()
1412 m_GraphType = MyGraph::GraphType::Line;
1413 m_bStacked = false;
1414 RedrawGraph();
1417 void CStatGraphDlg::OnBnClickedGraphlinestackedbutton()
1419 m_GraphType = MyGraph::GraphType::Line;
1420 m_bStacked = true;
1421 RedrawGraph();
1424 void CStatGraphDlg::OnBnClickedGraphpiebutton()
1426 m_GraphType = MyGraph::GraphType::PieChart;
1427 m_bStacked = false;
1428 RedrawGraph();
1431 void CStatGraphDlg::EnableDisableMenu()
1433 UINT nEnable = MF_BYCOMMAND;
1435 auto SelectMetric = static_cast<Metrics>(m_cGraphType.GetItemData(m_cGraphType.GetCurSel()));
1437 nEnable |= (SelectMetric > TextStatStart && SelectMetric < TextStatEnd)
1438 ? (MF_DISABLED | MF_GRAYED) : MF_ENABLED;
1440 GetMenu()->EnableMenuItem(ID_FILE_SAVESTATGRAPHAS, nEnable);
1443 void CStatGraphDlg::OnFileSavestatgraphas()
1445 CString tempfile;
1446 int filterindex = 0;
1447 if (CAppUtils::FileOpenSave(tempfile, &filterindex, IDS_REVGRAPH_SAVEPIC, IDS_STATPICFILEFILTER, false, m_hWnd))
1449 // if the user doesn't specify a file extension, default to
1450 // wmf and add that extension to the filename. But only if the
1451 // user chose the 'pictures' filter. The filename isn't changed
1452 // if the 'All files' filter was chosen.
1453 CString extension;
1454 int dotPos = tempfile.ReverseFind('.');
1455 int slashPos = tempfile.ReverseFind('\\');
1456 if (dotPos > slashPos)
1457 extension = tempfile.Mid(dotPos);
1458 if ((filterindex == 1)&&(extension.IsEmpty()))
1460 extension = L".wmf";
1461 tempfile += extension;
1463 SaveGraph(tempfile);
1467 void CStatGraphDlg::SaveGraph(CString sFilename)
1469 CString extension = CPathUtils::GetFileExtFromPath(sFilename);
1470 if (extension.CompareNoCase(L".wmf") == 0)
1472 // save the graph as an enhanced meta file
1473 CMyMetaFileDC wmfDC;
1474 wmfDC.CreateEnhanced(nullptr, sFilename, nullptr, L"TortoiseGit\0Statistics\0\0");
1475 wmfDC.SetAttribDC(GetDC()->GetSafeHdc());
1476 RedrawGraph();
1477 m_graph.DrawGraph(wmfDC);
1478 HENHMETAFILE hemf = wmfDC.CloseEnhanced();
1479 DeleteEnhMetaFile(hemf);
1481 else
1483 // save the graph as a pixel picture instead of a vector picture
1484 // create dc to paint on
1487 CWindowDC ddc(this);
1488 CDC dc;
1489 if (!dc.CreateCompatibleDC(&ddc))
1491 ShowErrorMessage();
1492 return;
1494 CRect rect;
1495 GetDlgItem(IDC_GRAPH)->GetClientRect(&rect);
1496 HBITMAP hbm = ::CreateCompatibleBitmap(ddc.m_hDC, rect.Width(), rect.Height());
1497 if (!hbm)
1499 ShowErrorMessage();
1500 return;
1502 auto oldbm = static_cast<HBITMAP>(dc.SelectObject(hbm));
1503 // paint the whole graph
1504 RedrawGraph();
1505 m_graph.DrawGraph(dc);
1506 // now use GDI+ to save the picture
1507 CLSID encoderClsid;
1508 GdiplusStartupInput gdiplusStartupInput;
1509 ULONG_PTR gdiplusToken;
1510 CString sErrormessage;
1511 if (GdiplusStartup(&gdiplusToken, &gdiplusStartupInput, nullptr) == Ok)
1514 Bitmap bitmap(hbm, nullptr);
1515 if (bitmap.GetLastStatus()==Ok)
1517 // Get the CLSID of the encoder.
1518 int ret = 0;
1519 if (CPathUtils::GetFileExtFromPath(sFilename).CompareNoCase(L".png") == 0)
1520 ret = GetEncoderClsid(L"image/png", &encoderClsid);
1521 else if (CPathUtils::GetFileExtFromPath(sFilename).CompareNoCase(L".jpg") == 0)
1522 ret = GetEncoderClsid(L"image/jpeg", &encoderClsid);
1523 else if (CPathUtils::GetFileExtFromPath(sFilename).CompareNoCase(L".jpeg") == 0)
1524 ret = GetEncoderClsid(L"image/jpeg", &encoderClsid);
1525 else if (CPathUtils::GetFileExtFromPath(sFilename).CompareNoCase(L".bmp") == 0)
1526 ret = GetEncoderClsid(L"image/bmp", &encoderClsid);
1527 else if (CPathUtils::GetFileExtFromPath(sFilename).CompareNoCase(L".gif") == 0)
1528 ret = GetEncoderClsid(L"image/gif", &encoderClsid);
1529 else
1531 sFilename += L".jpg";
1532 ret = GetEncoderClsid(L"image/jpeg", &encoderClsid);
1534 if (ret >= 0)
1536 CStringW tfile = CStringW(sFilename);
1537 bitmap.Save(tfile, &encoderClsid, nullptr);
1539 else
1540 sErrormessage.Format(IDS_REVGRAPH_ERR_NOENCODER, static_cast<LPCWSTR>(CPathUtils::GetFileExtFromPath(sFilename)));
1542 else
1543 sErrormessage.LoadString(IDS_REVGRAPH_ERR_NOBITMAP);
1545 GdiplusShutdown(gdiplusToken);
1547 else
1548 sErrormessage.LoadString(IDS_REVGRAPH_ERR_GDIINIT);
1549 dc.SelectObject(oldbm);
1550 dc.DeleteDC();
1551 if (!sErrormessage.IsEmpty())
1552 ::MessageBox(m_hWnd, sErrormessage, L"TortoiseGit", MB_ICONERROR);
1554 catch (CException * pE)
1556 wchar_t szErrorMsg[2048] = { 0 };
1557 pE->GetErrorMessage(szErrorMsg, 2048);
1558 pE->Delete();
1559 ::MessageBox(m_hWnd, szErrorMsg, L"TortoiseGit", MB_ICONERROR);
1564 int CStatGraphDlg::GetEncoderClsid(const WCHAR* format, CLSID* pClsid)
1566 UINT num = 0; // number of image encoders
1567 UINT size = 0; // size of the image encoder array in bytes
1569 if (GetImageEncodersSize(&num, &size)!=Ok)
1570 return -1;
1571 if (size == 0)
1572 return -1; // Failure
1574 auto pMem = std::make_unique<BYTE[]>(size);
1575 auto pImageCodecInfo = reinterpret_cast<ImageCodecInfo*>(pMem.get());
1576 if (!pImageCodecInfo)
1577 return -1; // Failure
1579 if (GetImageEncoders(num, size, pImageCodecInfo)==Ok)
1581 for (UINT j = 0; j < num; ++j)
1583 if (wcscmp(pImageCodecInfo[j].MimeType, format) == 0)
1585 *pClsid = pImageCodecInfo[j].Clsid;
1586 return j; // Success
1590 return -1; // Failure
1593 void CStatGraphDlg::StoreCurrentGraphType()
1595 UpdateData();
1596 DWORD graphtype = static_cast<DWORD>(m_cGraphType.GetItemData(m_cGraphType.GetCurSel()));
1597 // encode the current chart type
1598 DWORD statspage = graphtype*10;
1599 if ((m_GraphType == MyGraph::GraphType::Bar) && (m_bStacked))
1600 statspage += 1;
1601 if ((m_GraphType == MyGraph::GraphType::Bar) && (!m_bStacked))
1602 statspage += 2;
1603 if ((m_GraphType == MyGraph::GraphType::Line) && (m_bStacked))
1604 statspage += 3;
1605 if ((m_GraphType == MyGraph::GraphType::Line) && (!m_bStacked))
1606 statspage += 4;
1607 if (m_GraphType == MyGraph::GraphType::PieChart)
1608 statspage += 5;
1610 // store current chart type in registry
1611 CRegDWORD lastStatsPage(L"Software\\TortoiseGit\\LastViewedStatsPage", 0);
1612 lastStatsPage = statspage;
1614 CRegDWORD regAuthors(L"Software\\TortoiseGit\\StatAuthorsCaseSensitive");
1615 regAuthors = m_bAuthorsCaseSensitive;
1617 CRegDWORD regSort(L"Software\\TortoiseGit\\StatSortByCommitCount");
1618 regSort = m_bSortByCommitCount;
1620 CRegDWORD regCommitterName(L"Software\\TortoiseGit\\StatCommiterNames");
1621 regCommitterName = m_bUseCommitterNames;
1623 CRegDWORD regCommitDates(L"Software\\TortoiseGit\\StatCommitDates");
1624 regCommitDates = m_bUseCommitDates;
1627 void CStatGraphDlg::ShowErrorMessage()
1629 CFormatMessageWrapper errorDetails;
1630 if (errorDetails)
1631 MessageBox(errorDetails, L"Error", MB_OK | MB_ICONINFORMATION);
1634 void CStatGraphDlg::ShowSelectStat(Metrics SelectedMetric, bool reloadSkiper /* = false */)
1636 switch (SelectedMetric)
1638 case AllStat:
1639 LoadListOfAuthors(m_commitsPerAuthor, reloadSkiper);
1640 ShowStats();
1641 break;
1642 case CommitsByDate:
1643 LoadListOfAuthors(m_commitsPerAuthor, reloadSkiper);
1644 ShowByDate(IDS_STATGRAPH_COMMITSBYDATEY, IDS_STATGRAPH_COMMITSBYDATE, m_commitsPerUnitAndAuthor);
1645 break;
1646 case LinesWByDate:
1647 OnBnClickedFetchDiff();
1648 LoadListOfAuthors(m_commitsPerAuthor, reloadSkiper);
1649 ShowByDate(IDS_STATGRAPH_LINES_BYDATE_W_Y, IDS_STATGRAPH_LINES_BYDATE_W, m_LinesWPerUnitAndAuthor);
1650 break;
1651 case LinesWOByDate:
1652 OnBnClickedFetchDiff();
1653 LoadListOfAuthors(m_commitsPerAuthor, reloadSkiper);
1654 ShowByDate(IDS_STATGRAPH_LINES_BYDATE_WO_Y, IDS_STATGRAPH_LINES_BYDATE_WO, m_LinesWOPerUnitAndAuthor);
1655 break;
1656 case CommitsByAuthor:
1657 LoadListOfAuthors(m_commitsPerAuthor, reloadSkiper);
1658 ShowCommitsByAuthor();
1659 break;
1660 case PercentageOfAuthorship:
1661 OnBnClickedFetchDiff();
1662 LoadListOfAuthors(m_PercentageOfAuthorship, reloadSkiper, true);
1663 ShowPercentageOfAuthorship();
1664 break;
1665 default:
1666 ShowErrorMessage();
1670 double CStatGraphDlg::CoeffContribution(int distFromEnd) { return distFromEnd ? 1.0 / m_CoeffAuthorShip * distFromEnd : 1;}
1673 template <class MAP>
1674 void CStatGraphDlg::DrawOthers(const std::list<std::wstring>& others, MyGraphSeries* graphData, MAP& map)
1676 int nCommits = 0;
1677 for (auto it = others.cbegin(); it != others.cend(); ++it)
1678 nCommits += RollPercentageOfAuthorship(map[*it]);
1680 CString sOthers(MAKEINTRESOURCE(IDS_STATGRAPH_OTHERGROUP));
1681 sOthers.AppendFormat(L" (%Iu)", others.size());
1682 int group = m_graph.AppendGroup(sOthers);
1683 graphData->SetData(group, static_cast<int>(nCommits));
1687 template <class MAP>
1688 void CStatGraphDlg::LoadListOfAuthors (MAP &map, bool reloadSkiper/*= false*/, bool compare /*= false*/)
1690 m_authorNames.clear();
1691 if (!map.empty())
1693 for (auto it = map.begin(); it != map.end(); ++it)
1695 if ((compare && RollPercentageOfAuthorship(map[it->first]) != 0) || !compare)
1696 m_authorNames.push_back(it->first);
1700 // Sort the list of authors based on commit count
1701 m_authorNames.sort(MoreCommitsThan<typename MAP::mapped_type>(map));
1703 // Set Skipper
1704 SetSkipper(reloadSkiper);
1708 void CStatGraphDlg::OnBnClickedFetchDiff()
1710 if (m_bDiffFetched)
1711 return;
1712 if (GatherData(TRUE))
1713 return;
1714 m_bDiffFetched = true;
1715 GetDlgItem(IDC_CALC_DIFF)->ShowWindow(!m_bDiffFetched);
1717 ShowStats();