Call GatherData after we loaded the conditions we want to show
[TortoiseGit.git] / src / TortoiseProc / StatGraphDlg.cpp
blobe4228ed1a66a39aa98ff77cb064b2e4626749fd5
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_nWeeks(-1)
60 , m_nDays(-1)
61 , m_langOrder(0)
62 , m_firstInterval(0)
63 , m_lastInterval(0)
64 , m_nTotalCommits(0)
65 , m_nTotalLinesInc(0)
66 , m_nTotalLinesDec(0)
67 , m_nTotalLinesNew(0)
68 , m_nTotalLinesDel(0)
69 , m_bDiffFetched(FALSE)
70 , m_minDate(0)
71 , m_maxDate(0)
72 , m_nTotalFileChanges(0)
76 CStatGraphDlg::~CStatGraphDlg()
78 ClearGraph();
81 void CStatGraphDlg::OnOK() {
82 StoreCurrentGraphType();
83 __super::OnOK();
86 void CStatGraphDlg::OnCancel() {
87 StoreCurrentGraphType();
88 __super::OnCancel();
91 void CStatGraphDlg::DoDataExchange(CDataExchange* pDX)
93 CResizableStandAloneDialog::DoDataExchange(pDX);
94 DDX_Control(pDX, IDC_GRAPH, m_graph);
95 DDX_Control(pDX, IDC_GRAPHCOMBO, m_cGraphType);
96 DDX_Control(pDX, IDC_SKIPPER, m_Skipper);
97 DDX_Check(pDX, IDC_AUTHORSCASESENSITIVE, m_bAuthorsCaseSensitive);
98 DDX_Check(pDX, IDC_SORTBYCOMMITCOUNT, m_bSortByCommitCount);
99 DDX_Control(pDX, IDC_GRAPHBARBUTTON, m_btnGraphBar);
100 DDX_Control(pDX, IDC_GRAPHBARSTACKEDBUTTON, m_btnGraphBarStacked);
101 DDX_Control(pDX, IDC_GRAPHLINEBUTTON, m_btnGraphLine);
102 DDX_Control(pDX, IDC_GRAPHLINESTACKEDBUTTON, m_btnGraphLineStacked);
103 DDX_Control(pDX, IDC_GRAPHPIEBUTTON, m_btnGraphPie);
107 BEGIN_MESSAGE_MAP(CStatGraphDlg, CResizableStandAloneDialog)
108 ON_CBN_SELCHANGE(IDC_GRAPHCOMBO, OnCbnSelchangeGraphcombo)
109 ON_WM_HSCROLL()
110 ON_NOTIFY(TTN_NEEDTEXT, nullptr, OnNeedText)
111 ON_BN_CLICKED(IDC_AUTHORSCASESENSITIVE, &CStatGraphDlg::AuthorsCaseSensitiveChanged)
112 ON_BN_CLICKED(IDC_SORTBYCOMMITCOUNT, &CStatGraphDlg::SortModeChanged)
113 ON_BN_CLICKED(IDC_GRAPHBARBUTTON, &CStatGraphDlg::OnBnClickedGraphbarbutton)
114 ON_BN_CLICKED(IDC_GRAPHBARSTACKEDBUTTON, &CStatGraphDlg::OnBnClickedGraphbarstackedbutton)
115 ON_BN_CLICKED(IDC_GRAPHLINEBUTTON, &CStatGraphDlg::OnBnClickedGraphlinebutton)
116 ON_BN_CLICKED(IDC_GRAPHLINESTACKEDBUTTON, &CStatGraphDlg::OnBnClickedGraphlinestackedbutton)
117 ON_BN_CLICKED(IDC_GRAPHPIEBUTTON, &CStatGraphDlg::OnBnClickedGraphpiebutton)
118 ON_COMMAND(ID_FILE_SAVESTATGRAPHAS, &CStatGraphDlg::OnFileSavestatgraphas)
119 ON_BN_CLICKED(IDC_CALC_DIFF, &CStatGraphDlg::OnBnClickedFetchDiff)
120 END_MESSAGE_MAP()
122 void CStatGraphDlg::LoadStatQueries (__in UINT curStr, Metrics loadMetric, bool setDef /* = false */)
124 CString temp;
125 temp.LoadString(curStr);
126 int sel = m_cGraphType.AddString(temp);
127 m_cGraphType.SetItemData(sel, loadMetric);
129 if (setDef) m_cGraphType.SetCurSel(sel);
132 void CStatGraphDlg::SetSkipper (bool reloadSkiper)
134 // We need to limit the number of authors due to GUI resource limitation.
135 // However, since author #251 will properly have < 1000th of the commits,
136 // the resolution limit of the screen will already not allow for displaying
137 // it in a reasonable way
139 int max_authors_count = max(1, (int)min(m_authorNames.size(), 250) );
140 m_Skipper.SetRange (1, max_authors_count);
141 m_Skipper.SetPageSize(5);
143 if (reloadSkiper)
144 m_Skipper.SetPos (max_authors_count);
147 BOOL CStatGraphDlg::OnInitDialog()
149 CResizableStandAloneDialog::OnInitDialog();
151 m_tooltips.AddTool(&m_btnGraphPie, IDS_STATGRAPH_PIEBUTTON_TT);
152 m_tooltips.AddTool(&m_btnGraphLineStacked, IDS_STATGRAPH_LINESTACKEDBUTTON_TT);
153 m_tooltips.AddTool(&m_btnGraphLine, IDS_STATGRAPH_LINEBUTTON_TT);
154 m_tooltips.AddTool(&m_btnGraphBarStacked, IDS_STATGRAPH_BARSTACKEDBUTTON_TT);
155 m_tooltips.AddTool(&m_btnGraphBar, IDS_STATGRAPH_BARBUTTON_TT);
156 m_tooltips.Activate(TRUE);
158 m_bAuthorsCaseSensitive = DWORD(CRegDWORD(L"Software\\TortoiseGit\\StatAuthorsCaseSensitive"));
159 m_bSortByCommitCount = DWORD(CRegDWORD(L"Software\\TortoiseGit\\StatSortByCommitCount"));
160 UpdateData(FALSE);
162 // gather statistics data, only needs to be updated when the checkbox with
163 // the case sensitivity of author names is changed
164 GatherData();
166 //Load statistical queries
167 LoadStatQueries(IDS_STATGRAPH_STATS, AllStat, true);
168 LoadStatQueries(IDS_STATGRAPH_COMMITSBYDATE, CommitsByDate);
169 LoadStatQueries(IDS_STATGRAPH_COMMITSBYAUTHOR, CommitsByAuthor);
170 LoadStatQueries(IDS_STATGRAPH_PERCENTAGE_OF_AUTHORSHIP, PercentageOfAuthorship);
171 LoadStatQueries(IDS_STATGRAPH_LINES_BYDATE_W, LinesWByDate);
172 LoadStatQueries(IDS_STATGRAPH_LINES_BYDATE_WO, LinesWOByDate);
174 // set the dialog title to "Statistics - path/to/whatever/we/show/the/statistics/for"
175 CString sTitle;
176 GetWindowText(sTitle);
177 if (m_path.IsEmpty())
178 CAppUtils::SetWindowTitle(m_hWnd, g_Git.m_CurrentDir, sTitle);
179 else
180 CAppUtils::SetWindowTitle(m_hWnd, m_path.GetUIPathString(), sTitle);
182 int iconWidth = GetSystemMetrics(SM_CXSMICON);
183 int iconHeight = GetSystemMetrics(SM_CYSMICON);
184 m_btnGraphBar.SetImage((HICON)LoadImage(AfxGetResourceHandle(), MAKEINTRESOURCE(IDI_GRAPHBAR), IMAGE_ICON, iconWidth, iconHeight, LR_DEFAULTCOLOR));
185 m_btnGraphBar.SizeToContent();
186 m_btnGraphBar.Invalidate();
187 m_btnGraphBarStacked.SetImage((HICON)LoadImage(AfxGetResourceHandle(), MAKEINTRESOURCE(IDI_GRAPHBARSTACKED), IMAGE_ICON, iconWidth, iconHeight, LR_DEFAULTCOLOR));
188 m_btnGraphBarStacked.SizeToContent();
189 m_btnGraphBarStacked.Invalidate();
190 m_btnGraphLine.SetImage((HICON)LoadImage(AfxGetResourceHandle(), MAKEINTRESOURCE(IDI_GRAPHLINE), IMAGE_ICON, iconWidth, iconHeight, LR_DEFAULTCOLOR));
191 m_btnGraphLine.SizeToContent();
192 m_btnGraphLine.Invalidate();
193 m_btnGraphLineStacked.SetImage((HICON)LoadImage(AfxGetResourceHandle(), MAKEINTRESOURCE(IDI_GRAPHLINESTACKED), IMAGE_ICON, iconWidth, iconHeight, LR_DEFAULTCOLOR));
194 m_btnGraphLineStacked.SizeToContent();
195 m_btnGraphLineStacked.Invalidate();
196 m_btnGraphPie.SetImage((HICON)LoadImage(AfxGetResourceHandle(), MAKEINTRESOURCE(IDI_GRAPHPIE), IMAGE_ICON, iconWidth, iconHeight, LR_DEFAULTCOLOR));
197 m_btnGraphPie.SizeToContent();
198 m_btnGraphPie.Invalidate();
200 AddAnchor(IDC_GRAPHTYPELABEL, TOP_LEFT);
201 AddAnchor(IDC_GRAPH, TOP_LEFT, BOTTOM_RIGHT);
202 AddAnchor(IDC_GRAPHCOMBO, TOP_LEFT, TOP_RIGHT);
204 AddAnchor(IDC_NUMWEEK, TOP_LEFT);
205 AddAnchor(IDC_NUMWEEKVALUE, TOP_RIGHT);
206 AddAnchor(IDC_NUMAUTHOR, TOP_LEFT);
207 AddAnchor(IDC_NUMAUTHORVALUE, TOP_RIGHT);
208 AddAnchor(IDC_NUMCOMMITS, TOP_LEFT);
209 AddAnchor(IDC_NUMCOMMITSVALUE, TOP_RIGHT);
210 AddAnchor(IDC_NUMFILECHANGES, TOP_LEFT);
211 AddAnchor(IDC_NUMFILECHANGESVALUE, TOP_RIGHT);
213 AddAnchor(IDC_TOTAL_LINE_WITHOUT_NEW_DEL, TOP_LEFT);
214 AddAnchor(IDC_TOTAL_LINE_WITHOUT_NEW_DEL_VALUE, TOP_RIGHT);
215 AddAnchor(IDC_TOTAL_LINE_WITH_NEW_DEL, TOP_LEFT);
216 AddAnchor(IDC_TOTAL_LINE_WITH_NEW_DEL_VALUE, TOP_RIGHT);
218 AddAnchor(IDC_CALC_DIFF, TOP_RIGHT);
220 AddAnchor(IDC_AVG, TOP_RIGHT);
221 AddAnchor(IDC_MIN, TOP_RIGHT);
222 AddAnchor(IDC_MAX, TOP_RIGHT);
223 AddAnchor(IDC_COMMITSEACHWEEK, TOP_LEFT);
224 AddAnchor(IDC_MOSTACTIVEAUTHOR, TOP_LEFT);
225 AddAnchor(IDC_LEASTACTIVEAUTHOR, TOP_LEFT);
226 AddAnchor(IDC_MOSTACTIVEAUTHORNAME, TOP_LEFT, TOP_RIGHT);
227 AddAnchor(IDC_LEASTACTIVEAUTHORNAME, TOP_LEFT, TOP_RIGHT);
228 AddAnchor(IDC_FILECHANGESEACHWEEK, TOP_LEFT);
229 AddAnchor(IDC_COMMITSEACHWEEKAVG, TOP_RIGHT);
230 AddAnchor(IDC_COMMITSEACHWEEKMIN, TOP_RIGHT);
231 AddAnchor(IDC_COMMITSEACHWEEKMAX, TOP_RIGHT);
232 AddAnchor(IDC_MOSTACTIVEAUTHORAVG, TOP_RIGHT);
233 AddAnchor(IDC_MOSTACTIVEAUTHORMIN, TOP_RIGHT);
234 AddAnchor(IDC_MOSTACTIVEAUTHORMAX, TOP_RIGHT);
235 AddAnchor(IDC_LEASTACTIVEAUTHORAVG, TOP_RIGHT);
236 AddAnchor(IDC_LEASTACTIVEAUTHORMIN, TOP_RIGHT);
237 AddAnchor(IDC_LEASTACTIVEAUTHORMAX, TOP_RIGHT);
238 AddAnchor(IDC_FILECHANGESEACHWEEKAVG, TOP_RIGHT);
239 AddAnchor(IDC_FILECHANGESEACHWEEKMIN, TOP_RIGHT);
240 AddAnchor(IDC_FILECHANGESEACHWEEKMAX, TOP_RIGHT);
242 AddAnchor(IDC_GRAPHBARBUTTON, BOTTOM_RIGHT);
243 AddAnchor(IDC_GRAPHBARSTACKEDBUTTON, BOTTOM_RIGHT);
244 AddAnchor(IDC_GRAPHLINEBUTTON, BOTTOM_RIGHT);
245 AddAnchor(IDC_GRAPHLINESTACKEDBUTTON, BOTTOM_RIGHT);
246 AddAnchor(IDC_GRAPHPIEBUTTON, BOTTOM_RIGHT);
248 AddAnchor(IDC_AUTHORSCASESENSITIVE, BOTTOM_LEFT);
249 AddAnchor(IDC_SORTBYCOMMITCOUNT, BOTTOM_LEFT);
250 AddAnchor(IDC_SKIPPER, BOTTOM_LEFT, BOTTOM_RIGHT);
251 AddAnchor(IDC_SKIPPERLABEL, BOTTOM_LEFT);
252 AddAnchor(IDOK, BOTTOM_RIGHT);
253 EnableSaveRestore(L"StatGraphDlg");
255 // set the min/max values on the skipper
256 SetSkipper (true);
258 // we use a stats page encoding here, 0 stands for the statistics dialog
259 CRegDWORD lastStatsPage(L"Software\\TortoiseGit\\LastViewedStatsPage", 0);
261 // open last viewed statistics page as first page
262 int graphtype = lastStatsPage / 10;
263 for (int i = 0; i < m_cGraphType.GetCount(); i++)
265 if ((int)m_cGraphType.GetItemData(i) == graphtype)
267 m_cGraphType.SetCurSel(i);
268 break;
272 OnCbnSelchangeGraphcombo();
274 int statspage = lastStatsPage % 10;
275 switch (statspage) {
276 case 1 :
277 m_GraphType = MyGraph::Bar;
278 m_bStacked = true;
279 break;
280 case 2 :
281 m_GraphType = MyGraph::Bar;
282 m_bStacked = false;
283 break;
284 case 3 :
285 m_GraphType = MyGraph::Line;
286 m_bStacked = true;
287 break;
288 case 4 :
289 m_GraphType = MyGraph::Line;
290 m_bStacked = false;
291 break;
292 case 5 :
293 m_GraphType = MyGraph::PieChart;
294 break;
296 default : return TRUE;
299 LCID m_locale = MAKELCID((DWORD)CRegStdDWORD(L"Software\\TortoiseGit\\LanguageID", MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT)), SORT_DEFAULT);
301 bool bUseSystemLocale = !!(DWORD)CRegStdDWORD(L"Software\\TortoiseGit\\UseSystemLocaleForDates", TRUE);
302 LCID locale = bUseSystemLocale ? MAKELCID(MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), SORT_DEFAULT) : m_locale;
304 TCHAR langBuf[11] = { 0 };
305 GetLocaleInfo(locale, LOCALE_IDATE, langBuf, _countof(langBuf));
307 m_langOrder = _wtoi(langBuf);
309 return TRUE;
312 void CStatGraphDlg::ShowLabels(BOOL bShow)
314 if (m_parAuthors.IsEmpty() || m_parDates.IsEmpty() || m_parFileChanges.IsEmpty())
315 return;
317 int nCmdShow = bShow ? SW_SHOW : SW_HIDE;
319 GetDlgItem(IDC_GRAPH)->ShowWindow(bShow ? SW_HIDE : SW_SHOW);
320 GetDlgItem(IDC_NUMWEEK)->ShowWindow(nCmdShow);
321 GetDlgItem(IDC_NUMWEEKVALUE)->ShowWindow(nCmdShow);
322 GetDlgItem(IDC_NUMAUTHOR)->ShowWindow(nCmdShow);
323 GetDlgItem(IDC_NUMAUTHORVALUE)->ShowWindow(nCmdShow);
324 GetDlgItem(IDC_NUMCOMMITS)->ShowWindow(nCmdShow);
325 GetDlgItem(IDC_NUMCOMMITSVALUE)->ShowWindow(nCmdShow);
326 GetDlgItem(IDC_NUMFILECHANGES)->ShowWindow(nCmdShow);
327 GetDlgItem(IDC_NUMFILECHANGESVALUE)->ShowWindow(nCmdShow);
328 GetDlgItem(IDC_TOTAL_LINE_WITHOUT_NEW_DEL)->ShowWindow(nCmdShow);
329 GetDlgItem(IDC_TOTAL_LINE_WITHOUT_NEW_DEL_VALUE)->ShowWindow(nCmdShow);
330 GetDlgItem(IDC_TOTAL_LINE_WITH_NEW_DEL)->ShowWindow(nCmdShow);
331 GetDlgItem(IDC_TOTAL_LINE_WITH_NEW_DEL_VALUE)->ShowWindow(nCmdShow);
332 GetDlgItem(IDC_CALC_DIFF)->ShowWindow(nCmdShow && !m_bDiffFetched);
334 GetDlgItem(IDC_AVG)->ShowWindow(nCmdShow);
335 GetDlgItem(IDC_MIN)->ShowWindow(nCmdShow);
336 GetDlgItem(IDC_MAX)->ShowWindow(nCmdShow);
337 GetDlgItem(IDC_COMMITSEACHWEEK)->ShowWindow(nCmdShow);
338 GetDlgItem(IDC_MOSTACTIVEAUTHOR)->ShowWindow(nCmdShow);
339 GetDlgItem(IDC_LEASTACTIVEAUTHOR)->ShowWindow(nCmdShow);
340 GetDlgItem(IDC_MOSTACTIVEAUTHORNAME)->ShowWindow(nCmdShow);
341 GetDlgItem(IDC_LEASTACTIVEAUTHORNAME)->ShowWindow(nCmdShow);
342 //GetDlgItem(IDC_FILECHANGESEACHWEEK)->ShowWindow(nCmdShow);
343 GetDlgItem(IDC_COMMITSEACHWEEKAVG)->ShowWindow(nCmdShow);
344 GetDlgItem(IDC_COMMITSEACHWEEKMIN)->ShowWindow(nCmdShow);
345 GetDlgItem(IDC_COMMITSEACHWEEKMAX)->ShowWindow(nCmdShow);
346 GetDlgItem(IDC_MOSTACTIVEAUTHORAVG)->ShowWindow(nCmdShow);
347 GetDlgItem(IDC_MOSTACTIVEAUTHORMIN)->ShowWindow(nCmdShow);
348 GetDlgItem(IDC_MOSTACTIVEAUTHORMAX)->ShowWindow(nCmdShow);
349 GetDlgItem(IDC_LEASTACTIVEAUTHORAVG)->ShowWindow(nCmdShow);
350 GetDlgItem(IDC_LEASTACTIVEAUTHORMIN)->ShowWindow(nCmdShow);
351 GetDlgItem(IDC_LEASTACTIVEAUTHORMAX)->ShowWindow(nCmdShow);
352 GetDlgItem(IDC_FILECHANGESEACHWEEKAVG)->ShowWindow(nCmdShow);
353 GetDlgItem(IDC_FILECHANGESEACHWEEKMIN)->ShowWindow(nCmdShow);
354 GetDlgItem(IDC_FILECHANGESEACHWEEKMAX)->ShowWindow(nCmdShow);
356 GetDlgItem(IDC_SORTBYCOMMITCOUNT)->ShowWindow(bShow ? SW_HIDE : SW_SHOW);
357 GetDlgItem(IDC_SKIPPER)->ShowWindow(bShow ? SW_HIDE : SW_SHOW);
358 GetDlgItem(IDC_SKIPPERLABEL)->ShowWindow(bShow ? SW_HIDE : SW_SHOW);
359 m_btnGraphBar.ShowWindow(bShow ? SW_HIDE : SW_SHOW);
360 m_btnGraphBarStacked.ShowWindow(bShow ? SW_HIDE : SW_SHOW);
361 m_btnGraphLine.ShowWindow(bShow ? SW_HIDE : SW_SHOW);
362 m_btnGraphLineStacked.ShowWindow(bShow ? SW_HIDE : SW_SHOW);
363 m_btnGraphPie.ShowWindow(bShow ? SW_HIDE : SW_SHOW);
366 void CStatGraphDlg::UpdateWeekCount()
368 // Sanity check
369 if (m_parDates.IsEmpty())
370 return;
372 // Already updated? No need to do it again.
373 if (m_nWeeks >= 0)
374 return;
376 // Determine first and last date in dates array
377 __time64_t min_date = (__time64_t)m_parDates.GetAt(0);
378 __time64_t max_date = min_date;
379 INT_PTR count = m_parDates.GetCount();
380 for (INT_PTR i=0; i<count; ++i)
382 __time64_t d = (__time64_t)m_parDates.GetAt(i);
383 if (d < min_date) min_date = d;
384 else if (d > max_date) max_date = d;
387 // Store start date of the interval in the member variable m_minDate
388 m_minDate = min_date;
389 m_maxDate = max_date;
391 // How many weeks does the time period cover?
393 // Get time difference between start and end date
394 double secs = _difftime64(max_date, m_minDate);
396 m_nWeeks = (int)ceil(secs / (double) m_SecondsInWeek);
397 m_nDays = (int)ceil(secs / (double) m_SecondsInDay);
400 int CStatGraphDlg::GetCalendarWeek(const CTime& time)
402 // Note:
403 // the calculation of the calendar week is wrong if DST is in effect
404 // and the date to calculate the week for is in DST and within the range
405 // of the DST offset (e.g. one hour).
406 // For example, if DST starts on Sunday march 30 and the date to get the week for
407 // is Monday, march 31, 0:30:00, then the returned week is one week less than
408 // the real week.
409 // TODO: ?
410 // write a function
411 // getDSTOffset(const CTime& time)
412 // which returns the DST offset for a given time/date. Then we can use this offset
413 // to correct our GetDays() calculation to get the correct week again
414 // This of course won't work for 'history' dates, because Windows doesn't have
415 // that information (only Vista has such a function: GetTimeZoneInformationForYear() )
416 int iWeekOfYear = 0;
418 int iYear = time.GetYear();
419 int iFirstDayOfWeek = 0;
420 int iFirstWeekOfYear = 0;
421 TCHAR loc[2] = { 0 };
422 GetLocaleInfo(LOCALE_USER_DEFAULT, LOCALE_IFIRSTDAYOFWEEK, loc, _countof(loc));
423 iFirstDayOfWeek = int(loc[0]-'0');
424 GetLocaleInfo(LOCALE_USER_DEFAULT, LOCALE_IFIRSTWEEKOFYEAR, loc, _countof(loc));
425 iFirstWeekOfYear = int(loc[0]-'0');
426 CTime dDateFirstJanuary(iYear,1,1,0,0,0);
427 int iDayOfWeek = (dDateFirstJanuary.GetDayOfWeek()+5+iFirstDayOfWeek)%7;
429 // Select mode
430 // 0 Week containing 1/1 is the first week of that year.
431 // 1 First full week following 1/1 is the first week of that year.
432 // 2 First week containing at least four days is the first week of that year.
433 switch (iFirstWeekOfYear)
435 case 0:
437 // Week containing 1/1 is the first week of that year.
439 // check if this week reaches into the next year
440 dDateFirstJanuary = CTime(iYear+1,1,1,0,0,0);
442 // Get start of week
445 iDayOfWeek = (time.GetDayOfWeek()+5+iFirstDayOfWeek)%7;
447 catch (CAtlException)
450 CTime dStartOfWeek = time-CTimeSpan(iDayOfWeek,0,0,0);
452 // If this week spans over to 1/1 this is week 1
453 if (dStartOfWeek + CTimeSpan(6,0,0,0) >= dDateFirstJanuary)
455 // we are in the last week of the year that spans over 1/1
456 iWeekOfYear = 1;
458 else
460 // Get week day of 1/1
461 dDateFirstJanuary = CTime(iYear,1,1,0,0,0);
462 iDayOfWeek = (dDateFirstJanuary.GetDayOfWeek() +5 + iFirstDayOfWeek) % 7;
463 // Just count from 1/1
464 iWeekOfYear = (int)(((time-dDateFirstJanuary).GetDays() + iDayOfWeek) / 7) + 1;
467 break;
468 case 1:
470 // First full week following 1/1 is the first week of that year.
472 // If the 1.1 is the start of the week everything is ok
473 // else we need the next week is the correct result
474 iWeekOfYear =
475 (int)(((time-dDateFirstJanuary).GetDays() + iDayOfWeek) / 7) +
476 (iDayOfWeek==0 ? 1:0);
478 // If we are in week 0 we are in the first not full week
479 // calculate from the last year
480 if (iWeekOfYear==0)
482 // Special case: we are in the week of 1.1 but 1.1. is not on the
483 // start of week. Calculate based on the last year
484 dDateFirstJanuary = CTime(iYear-1,1,1,0,0,0);
485 iDayOfWeek =
486 (dDateFirstJanuary.GetDayOfWeek()+5+iFirstDayOfWeek)%7;
487 // and we correct this in the same we we done this before but
488 // the result is now 52 or 53 and not 0
489 iWeekOfYear =
490 (int)(((time-dDateFirstJanuary).GetDays()+iDayOfWeek) / 7) +
491 (iDayOfWeek<=3 ? 1:0);
494 break;
495 case 2:
497 // First week containing at least four days is the first week of that year.
499 // Each year can start with any day of the week. But our
500 // weeks always start with Monday. So we add the day of week
501 // before calculation of the final week of year.
502 // Rule: is the 1.1 a Mo,Tu,We,Th than the week starts on the 1.1 with
503 // week==1, else a week later, so we add one for all those days if
504 // day is less <=3 Mo,Tu,We,Th. Otherwise 1.1 is in the last week of the
505 // previous year
506 iWeekOfYear =
507 (int)(((time-dDateFirstJanuary).GetDays()+iDayOfWeek) / 7) +
508 (iDayOfWeek<=3 ? 1:0);
510 // special cases
511 if (iWeekOfYear==0)
513 // special case week 0. We got a day before the 1.1, 2.1 or 3.1, were the
514 // 1.1. is not a Mo, Tu, We, Th. So the week 1 does not start with the 1.1.
515 // So we calculate the week according to the 1.1 of the year before
517 dDateFirstJanuary = CTime(iYear-1,1,1,0,0,0);
518 iDayOfWeek =
519 (dDateFirstJanuary.GetDayOfWeek()+5+iFirstDayOfWeek)%7;
520 // and we correct this in the same we we done this before but the result
521 // is now 52 or 53 and not 0
522 iWeekOfYear =
523 (int)(((time-dDateFirstJanuary).GetDays()+iDayOfWeek) / 7) +
524 (iDayOfWeek<=3 ? 1:0);
526 else if (iWeekOfYear==53)
528 // special case week 53. Either we got the correct week 53 or we just got the
529 // week 1 of the next year. So is the 1.1.(year+1) also a Mo, Tu, We, Th than
530 // we already have the week 1, otherwise week 53 is correct
532 dDateFirstJanuary = CTime(iYear+1,1,1,0,0,0);
533 iDayOfWeek =
534 (dDateFirstJanuary.GetDayOfWeek()+5+iFirstDayOfWeek)%7;
535 // 1.1. in week 1 or week 53?
536 iWeekOfYear = iDayOfWeek<=3 ? 1:53;
539 break;
540 default:
541 ASSERT(FALSE);
542 break;
544 // return result
545 return iWeekOfYear;
548 int CStatGraphDlg::GatherData(BOOL fetchdiff, BOOL keepFetchedData)
550 m_parAuthors.RemoveAll();
551 m_parDates.RemoveAll();
552 if (m_parFileChanges2.IsEmpty()) // Fixes issue #1948
553 keepFetchedData = FALSE;
554 if (!keepFetchedData)
556 m_parFileChanges.RemoveAll();
557 m_lineInc.RemoveAll();
558 m_lineDec.RemoveAll();
559 m_lineDel.RemoveAll();
560 m_lineNew.RemoveAll();
562 else
564 m_parFileChanges.Copy(m_parFileChanges2);
565 m_lineNew.Copy(m_lineNew2);
566 m_lineDel.Copy(m_lineDel2);
567 m_lineInc.Copy(m_lineInc2);
568 m_lineDec.Copy(m_lineDec2);
571 CSysProgressDlg progress;
572 if (fetchdiff)
574 progress.SetTitle(CString(MAKEINTRESOURCE(IDS_PROGS_TITLE_GATHERSTATISTICS)));
575 progress.FormatNonPathLine(1, IDS_PROC_STATISTICS_DIFF);
576 progress.SetTime(true);
577 progress.ShowModeless(this);
580 // create arrays which are aware of the current filter
581 ULONGLONG starttime = GetTickCount64();
583 std::sort(m_ShowList.begin(), m_ShowList.end(), [](GitRevLoglist* pLhs, GitRevLoglist* pRhs) { return pLhs->GetCommitterDate() > pRhs->GetCommitterDate(); });
585 GIT_MAILMAP mailmap = nullptr;
586 git_read_mailmap(&mailmap);
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 = pLogEntry->GetAuthorName();
594 if (strAuthor.IsEmpty())
595 strAuthor.LoadString(IDS_STATGRAPH_EMPTYAUTHOR);
596 if (mailmap)
598 CStringA email2A = CUnicodeUtils::GetUTF8(pLogEntry->GetAuthorEmail());
599 struct payload_struct { GitRev* rev; const char *authorName; };
600 payload_struct payload = { pLogEntry, nullptr };
601 const char* author1 = nullptr;
602 git_lookup_mailmap(mailmap, nullptr, &author1, email2A, &payload,
603 [](void* payload) -> const char* { return reinterpret_cast<payload_struct*>(payload)->authorName = _strdup(CUnicodeUtils::GetUTF8(reinterpret_cast<payload_struct*>(payload)->rev->GetAuthorName())); });
604 free((void *)payload.authorName);
605 if (author1)
606 strAuthor = CUnicodeUtils::GetUnicode(author1);
608 m_parAuthors.Add(strAuthor);
609 m_parDates.Add((DWORD)pLogEntry->GetCommitterDate().GetTime());
611 if (fetchdiff && (pLogEntry->m_ParentHash.size() <= 1))
613 CTGitPathList& list = pLogEntry->GetFiles(nullptr);
614 files = list.GetCount();
616 for (int j = 0; j < files; j++)
618 if (list[j].m_Action & CTGitPath::LOGACTIONS_DELETED)
619 decdeletedfile += _wtol(list[j].m_StatDel);
620 else if(list[j].m_Action & CTGitPath::LOGACTIONS_ADDED)
621 incnewfile += _wtol(list[j].m_StatAdd);
622 else
624 inc += _wtol(list[j].m_StatAdd);
625 dec += _wtol(list[j].m_StatDel);
628 if (progress.HasUserCancelled())
630 git_free_mailmap(mailmap);
631 return -1;
635 if (!keepFetchedData)
637 m_parFileChanges.Add(files);
638 m_lineInc.Add(inc);
639 m_lineDec.Add(dec);
640 m_lineDel.Add(decdeletedfile);
641 m_lineNew.Add(incnewfile);
644 if (progress.IsVisible() && (GetTickCount64() - starttime > 100UL))
646 progress.FormatNonPathLine(2, L"%s: %s", (LPCTSTR)pLogEntry->m_CommitHash.ToString().Left(g_Git.GetShortHASHLength()), (LPCTSTR)pLogEntry->GetSubject());
647 progress.SetProgress64(i, m_ShowList.size());
648 starttime = GetTickCount64();
652 git_free_mailmap(mailmap);
654 if (fetchdiff)
656 m_parFileChanges2.Copy(m_parFileChanges);
657 m_lineNew2.Copy(m_lineNew);
658 m_lineDel2.Copy(m_lineDel);
659 m_lineInc2.Copy(m_lineInc);
660 m_lineDec2.Copy(m_lineDec);
663 m_nTotalCommits = m_parAuthors.GetCount();
664 m_nTotalFileChanges = 0;
666 // Update m_nWeeks and m_minDate
667 UpdateWeekCount();
669 // Now create a mapping that holds the information per week.
670 m_commitsPerUnitAndAuthor.clear();
671 m_filechangesPerUnitAndAuthor.clear();
672 m_commitsPerAuthor.clear();
673 m_PercentageOfAuthorship.clear();
674 m_LinesWPerUnitAndAuthor.clear();
675 m_LinesWOPerUnitAndAuthor.clear();
677 int interval = 0;
678 __time64_t d = (__time64_t)m_parDates.GetAt(0);
679 int nLastUnit = GetUnit(d);
680 double AllContributionAuthor = 0;
682 m_nTotalLinesInc = m_nTotalLinesDec = m_nTotalLinesNew = m_nTotalLinesDel =0;
684 // Now loop over all weeks and gather the info
685 for (LONG i=0; i<m_nTotalCommits; ++i)
687 // Find the interval number
688 __time64_t commitDate = (__time64_t)m_parDates.GetAt(i);
689 int u = GetUnit(commitDate);
690 if (nLastUnit != u)
691 interval++;
692 nLastUnit = u;
693 // Find the authors name
694 CString sAuth = m_parAuthors.GetAt(i);
695 if (!m_bAuthorsCaseSensitive)
696 sAuth = sAuth.MakeLower();
697 tstring author = tstring(sAuth);
698 // Increase total commit count for this author
699 m_commitsPerAuthor[author]++;
700 // Increase the commit count for this author in this week
701 m_commitsPerUnitAndAuthor[interval][author]++;
703 m_LinesWPerUnitAndAuthor[interval][author] += m_lineInc.GetAt(i) + m_lineDec.GetAt(i) + m_lineNew.GetAt(i) + + m_lineDel.GetAt(i);
704 m_LinesWOPerUnitAndAuthor[interval][author] += m_lineInc.GetAt(i) + m_lineDec.GetAt(i);
706 CTime t = m_parDates.GetAt(i);
707 m_unitNames[interval] = GetUnitLabel(nLastUnit, t);
708 // Increase the file change count for this author in this week
709 int fileChanges = m_parFileChanges.GetAt(i);
710 m_filechangesPerUnitAndAuthor[interval][author] += fileChanges;
711 m_nTotalFileChanges += fileChanges;
713 //calculate Contribution Author
714 double contributionAuthor = CoeffContribution((int)m_nTotalCommits - i -1) * (fileChanges ? fileChanges : 1);
715 AllContributionAuthor += contributionAuthor;
716 m_PercentageOfAuthorship[author] += contributionAuthor;
718 m_nTotalLinesInc += m_lineInc.GetAt(i);
719 m_nTotalLinesDec += m_lineDec.GetAt(i);
720 m_nTotalLinesNew += m_lineNew.GetAt(i);
721 m_nTotalLinesDel += m_lineDel.GetAt(i);
724 // Find first and last interval number.
725 if (!m_commitsPerUnitAndAuthor.empty())
727 IntervalDataMap::iterator interval_it = m_commitsPerUnitAndAuthor.begin();
728 m_firstInterval = interval_it->first;
729 interval_it = m_commitsPerUnitAndAuthor.end();
730 --interval_it;
731 m_lastInterval = interval_it->first;
732 // Sanity check - if m_lastInterval is too large it could freeze TSVN and take up all memory!!!
733 assert(m_lastInterval >= 0 && m_lastInterval < 10000);
735 else
737 m_firstInterval = 0;
738 m_lastInterval = -1;
741 // Get a list of authors names
742 LoadListOfAuthors(m_commitsPerAuthor);
744 // Calculate percent of Contribution Authors
745 for (std::list<tstring>::iterator it = m_authorNames.begin(); it != m_authorNames.end(); ++it)
747 m_PercentageOfAuthorship[*it] = (m_PercentageOfAuthorship[*it] *100)/ AllContributionAuthor;
750 // All done, now the statistics pages can retrieve the data and
751 // extract the information to be shown.
753 return 0;
756 void CStatGraphDlg::FilterSkippedAuthors(std::list<tstring>& included_authors,
757 std::list<tstring>& skipped_authors)
759 included_authors.clear();
760 skipped_authors.clear();
762 unsigned int included_authors_count = m_Skipper.GetPos();
763 // if we only leave out one author, still include him with his name
764 if (included_authors_count + 1 == m_authorNames.size())
765 ++included_authors_count;
767 // add the included authors first
768 std::list<tstring>::iterator author_it = m_authorNames.begin();
769 while (included_authors_count > 0 && author_it != m_authorNames.end())
771 // Add him/her to the included list
772 included_authors.push_back(*author_it);
773 ++author_it;
774 --included_authors_count;
777 // If we haven't reached the end yet, copy all remaining authors into the
778 // skipped author list.
779 std::copy(author_it, m_authorNames.end(), std::back_inserter(skipped_authors) );
781 // Sort authors alphabetically if user wants that.
782 if (!m_bSortByCommitCount)
783 included_authors.sort();
786 bool CStatGraphDlg::PreViewStat(bool fShowLabels)
788 if (m_parAuthors.IsEmpty() || m_parDates.IsEmpty() || m_parFileChanges.IsEmpty())
789 return false;
790 ShowLabels(fShowLabels);
792 //If view graphic
793 if (!fShowLabels) ClearGraph();
795 // This function relies on a previous call of GatherData().
796 // This can be detected by checking the week count.
797 // If the week count is equal to -1, it hasn't been called before.
798 if (m_nWeeks == -1)
799 GatherData(FALSE, TRUE);
800 // If week count is still -1, something bad has happened, probably invalid data!
801 if (m_nWeeks == -1)
802 return false;
804 return true;
807 MyGraphSeries *CStatGraphDlg::PreViewGraph(__in UINT GraphTitle, __in UINT YAxisLabel, __in UINT XAxisLabel /*= nullptr*/)
809 if(!PreViewStat(false))
810 return nullptr;
812 // We need at least one author
813 if (m_authorNames.empty())
814 return nullptr;
816 // Add a single series to the chart
817 MyGraphSeries * graphData = new MyGraphSeries();
818 m_graph.AddSeries(*graphData);
819 m_graphDataArray.Add(graphData);
821 // Set up the graph.
822 CString temp;
823 UpdateData();
824 m_graph.SetGraphType(m_GraphType, m_bStacked);
825 temp.LoadString(YAxisLabel);
826 m_graph.SetYAxisLabel(temp);
827 temp.LoadString(XAxisLabel);
828 m_graph.SetXAxisLabel(temp);
829 temp.LoadString(GraphTitle);
830 m_graph.SetGraphTitle(temp);
832 return graphData;
835 void CStatGraphDlg::ShowPercentageOfAuthorship()
837 // Set up the graph.
838 MyGraphSeries * graphData = PreViewGraph(IDS_STATGRAPH_PERCENTAGE_OF_AUTHORSHIP,
839 IDS_STATGRAPH_PERCENTAGE_OF_AUTHORSHIPY,
840 IDS_STATGRAPH_COMMITSBYAUTHORMOREX);
841 if (!graphData) return;
843 // Find out which authors are to be shown and which are to be skipped.
844 std::list<tstring> authors;
845 std::list<tstring> others;
848 FilterSkippedAuthors(authors, others);
850 // Loop over all authors in the authors list and
851 // add them to the graph.
853 if (!authors.empty())
855 for (std::list<tstring>::iterator it = authors.begin(); it != authors.end(); ++it)
857 int group = m_graph.AppendGroup(it->c_str());
858 graphData->SetData(group, RollPercentageOfAuthorship(m_PercentageOfAuthorship[*it]));
862 // If we have other authors, count them and their commits.
863 if (!others.empty())
864 DrawOthers(others, graphData, m_PercentageOfAuthorship);
866 // Paint the graph now that we're through.
867 m_graph.Invalidate();
870 void CStatGraphDlg::ShowCommitsByAuthor()
872 // Set up the graph.
873 MyGraphSeries * graphData = PreViewGraph(IDS_STATGRAPH_COMMITSBYAUTHOR,
874 IDS_STATGRAPH_COMMITSBYAUTHORY,
875 IDS_STATGRAPH_COMMITSBYAUTHORX);
876 if (!graphData) return;
878 // Find out which authors are to be shown and which are to be skipped.
879 std::list<tstring> authors;
880 std::list<tstring> others;
881 FilterSkippedAuthors(authors, others);
883 // Loop over all authors in the authors list and
884 // add them to the graph.
886 if (!authors.empty())
888 for (std::list<tstring>::iterator it = authors.begin(); it != authors.end(); ++it)
890 int group = m_graph.AppendGroup(it->c_str());
891 graphData->SetData(group, m_commitsPerAuthor[*it]);
895 // If we have other authors, count them and their commits.
896 if (!others.empty())
897 DrawOthers(others, graphData, m_commitsPerAuthor);
899 // Paint the graph now that we're through.
900 m_graph.Invalidate();
903 void CStatGraphDlg::ShowByDate(int stringx, int title, IntervalDataMap &data)
905 if(!PreViewStat(false)) return;
907 // We need at least one author
908 if (m_authorNames.empty()) return;
910 // Set up the graph.
911 CString temp;
912 UpdateData();
913 m_graph.SetGraphType(m_GraphType, m_bStacked);
914 temp.LoadString(stringx);
915 m_graph.SetYAxisLabel(temp);
916 temp.LoadString(title);
917 m_graph.SetGraphTitle(temp);
919 m_graph.SetXAxisLabel(GetUnitString());
921 // Find out which authors are to be shown and which are to be skipped.
922 std::list<tstring> authors;
923 std::list<tstring> others;
924 FilterSkippedAuthors(authors, others);
926 // Add a graph series for each author.
927 AuthorDataMap authorGraphMap;
928 for (std::list<tstring>::iterator it = authors.begin(); it != authors.end(); ++it)
929 authorGraphMap[*it] = m_graph.AppendGroup(it->c_str());
930 // If we have skipped authors, add a graph series for all those.
931 CString sOthers(MAKEINTRESOURCE(IDS_STATGRAPH_OTHERGROUP));
932 tstring othersName;
933 if (!others.empty())
935 sOthers.AppendFormat(L" (%Iu)", others.size());
936 othersName = (LPCWSTR)sOthers;
937 authorGraphMap[othersName] = m_graph.AppendGroup(sOthers);
940 // Mapping to collect commit counts in each interval
941 AuthorDataMap commitCount;
943 // Loop over all intervals/weeks and collect filtered data.
944 // Sum up data in each interval until the time unit changes.
945 for (int i=m_lastInterval; i>=m_firstInterval; --i)
947 // Collect data for authors listed by name.
948 if (!authors.empty())
950 for (std::list<tstring>::iterator it = authors.begin(); it != authors.end(); ++it)
952 // Do we have some data for the current author in the current interval?
953 AuthorDataMap::const_iterator data_it = data[i].find(*it);
954 if (data_it == data[i].end())
955 continue;
956 commitCount[*it] += data_it->second;
959 // Collect data for all skipped authors.
960 if (!others.empty())
962 for (std::list<tstring>::iterator it = others.begin(); it != others.end(); ++it)
964 // Do we have some data for the author in the current interval?
965 AuthorDataMap::const_iterator data_it = data[i].find(*it);
966 if (data_it == data[i].end())
967 continue;
968 commitCount[othersName] += data_it->second;
972 // Create a new data series for this unit/interval.
973 MyGraphSeries * graphData = new MyGraphSeries();
974 // Loop over all created graphs and set the corresponding data.
975 if (!authorGraphMap.empty())
977 for (AuthorDataMap::const_iterator it = authorGraphMap.begin(); it != authorGraphMap.end(); ++it)
979 graphData->SetData(it->second, commitCount[it->first]);
982 graphData->SetLabel(m_unitNames[i].c_str());
983 m_graph.AddSeries(*graphData);
984 m_graphDataArray.Add(graphData);
986 // Reset commit count mapping.
987 commitCount.clear();
990 // Paint the graph now that we're through.
991 m_graph.Invalidate();
994 void CStatGraphDlg::ShowStats()
996 if(!PreViewStat(true)) return;
998 // Now we can use the gathered data to update the stats dialog.
999 size_t nAuthors = m_authorNames.size();
1001 // Find most and least active author names.
1002 tstring mostActiveAuthor;
1003 tstring leastActiveAuthor;
1004 if (nAuthors > 0)
1006 mostActiveAuthor = m_authorNames.front();
1007 leastActiveAuthor = m_authorNames.back();
1010 // Obtain the statistics for the table.
1011 long nCommitsMin = -1;
1012 long nCommitsMax = -1;
1013 long nFileChangesMin = -1;
1014 long nFileChangesMax = -1;
1016 long nMostActiveMaxCommits = -1;
1017 long nMostActiveMinCommits = -1;
1018 long nLeastActiveMaxCommits = -1;
1019 long nLeastActiveMinCommits = -1;
1021 // Loop over all intervals and find min and max values for commit count and file changes.
1022 // Also store the stats for the most and least active authors.
1023 for (int i=m_firstInterval; i<=m_lastInterval; ++i)
1025 // Loop over all commits in this interval and count the number of commits by all authors.
1026 int commitCount = 0;
1027 AuthorDataMap::iterator commit_endit = m_commitsPerUnitAndAuthor[i].end();
1028 for (AuthorDataMap::iterator commit_it = m_commitsPerUnitAndAuthor[i].begin();
1029 commit_it != commit_endit; ++commit_it)
1031 commitCount += commit_it->second;
1033 if (nCommitsMin == -1 || commitCount < nCommitsMin)
1034 nCommitsMin = commitCount;
1035 if (nCommitsMax == -1 || commitCount > nCommitsMax)
1036 nCommitsMax = commitCount;
1038 // Loop over all commits in this interval and count the number of file changes by all authors.
1039 int fileChangeCount = 0;
1040 AuthorDataMap::iterator filechange_endit = m_filechangesPerUnitAndAuthor[i].end();
1041 for (AuthorDataMap::iterator filechange_it = m_filechangesPerUnitAndAuthor[i].begin();
1042 filechange_it != filechange_endit; ++filechange_it)
1044 fileChangeCount += filechange_it->second;
1046 if (nFileChangesMin == -1 || fileChangeCount < nFileChangesMin)
1047 nFileChangesMin = fileChangeCount;
1048 if (nFileChangesMax == -1 || fileChangeCount > nFileChangesMax)
1049 nFileChangesMax = fileChangeCount;
1051 // also get min/max data for most and least active authors
1052 if (nAuthors > 0)
1054 // check if author is present in this interval
1055 AuthorDataMap::iterator author_it = m_commitsPerUnitAndAuthor[i].find(mostActiveAuthor);
1056 long authorCommits;
1057 if (author_it == m_commitsPerUnitAndAuthor[i].end())
1058 authorCommits = 0;
1059 else
1060 authorCommits = author_it->second;
1061 if (nMostActiveMaxCommits == -1 || authorCommits > nMostActiveMaxCommits)
1062 nMostActiveMaxCommits = authorCommits;
1063 if (nMostActiveMinCommits == -1 || authorCommits < nMostActiveMinCommits)
1064 nMostActiveMinCommits = authorCommits;
1066 author_it = m_commitsPerUnitAndAuthor[i].find(leastActiveAuthor);
1067 if (author_it == m_commitsPerUnitAndAuthor[i].end())
1068 authorCommits = 0;
1069 else
1070 authorCommits = author_it->second;
1071 if (nLeastActiveMaxCommits == -1 || authorCommits > nLeastActiveMaxCommits)
1072 nLeastActiveMaxCommits = authorCommits;
1073 if (nLeastActiveMinCommits == -1 || authorCommits < nLeastActiveMinCommits)
1074 nLeastActiveMinCommits = authorCommits;
1077 if (nMostActiveMaxCommits == -1) nMostActiveMaxCommits = 0;
1078 if (nMostActiveMinCommits == -1) nMostActiveMinCommits = 0;
1079 if (nLeastActiveMaxCommits == -1) nLeastActiveMaxCommits = 0;
1080 if (nLeastActiveMinCommits == -1) nLeastActiveMinCommits = 0;
1082 int nWeeks = m_lastInterval-m_firstInterval;
1083 if (nWeeks == 0)
1084 nWeeks = 1;
1085 // Adjust the labels with the unit type (week, month, ...)
1086 CString labelText;
1087 labelText.Format(IDS_STATGRAPH_NUMBEROFUNIT, (LPCTSTR)GetUnitString());
1088 SetDlgItemText(IDC_NUMWEEK, labelText);
1089 labelText.Format(IDS_STATGRAPH_COMMITSBYUNIT, (LPCTSTR)GetUnitString());
1090 SetDlgItemText(IDC_COMMITSEACHWEEK, labelText);
1091 labelText.Format(IDS_STATGRAPH_FILECHANGESBYUNIT, (LPCTSTR)GetUnitString());
1092 SetDlgItemText(IDC_FILECHANGESEACHWEEK, (LPCTSTR)labelText);
1093 // We have now all data we want and we can fill in the labels...
1094 CString number;
1095 number.Format(L"%d", nWeeks);
1096 SetDlgItemText(IDC_NUMWEEKVALUE, number);
1097 number.Format(L"%Iu", nAuthors);
1098 SetDlgItemText(IDC_NUMAUTHORVALUE, number);
1099 number.Format(L"%Id", m_nTotalCommits);
1100 SetDlgItemText(IDC_NUMCOMMITSVALUE, number);
1101 number.Format(L"%ld", m_nTotalFileChanges);
1102 if (m_bDiffFetched)
1103 SetDlgItemText(IDC_NUMFILECHANGESVALUE, number);
1105 number.Format(L"%Id", m_parAuthors.GetCount() / nWeeks);
1106 SetDlgItemText(IDC_COMMITSEACHWEEKAVG, number);
1107 number.Format(L"%ld", nCommitsMax);
1108 SetDlgItemText(IDC_COMMITSEACHWEEKMAX, number);
1109 number.Format(L"%ld", nCommitsMin);
1110 SetDlgItemText(IDC_COMMITSEACHWEEKMIN, number);
1112 number.Format(L"%ld", m_nTotalFileChanges / nWeeks);
1113 //SetDlgItemText(IDC_FILECHANGESEACHWEEKAVG, number);
1114 number.Format(L"%ld", nFileChangesMax);
1115 //SetDlgItemText(IDC_FILECHANGESEACHWEEKMAX, number);
1116 number.Format(L"%ld", nFileChangesMin);
1117 //SetDlgItemText(IDC_FILECHANGESEACHWEEKMIN, number);
1119 number.Format(L"%ld (%ld (+) %ld (-))", m_nTotalLinesInc + m_nTotalLinesDec, m_nTotalLinesInc, m_nTotalLinesDec);
1120 if (m_bDiffFetched)
1121 SetDlgItemText(IDC_TOTAL_LINE_WITHOUT_NEW_DEL_VALUE, number);
1122 number.Format(L"%ld (%ld (+) %ld (-))", m_nTotalLinesInc + m_nTotalLinesDec + m_nTotalLinesNew + m_nTotalLinesDel,
1123 m_nTotalLinesInc + m_nTotalLinesNew, m_nTotalLinesDec + m_nTotalLinesDel);
1124 if (m_bDiffFetched)
1125 SetDlgItemText(IDC_TOTAL_LINE_WITH_NEW_DEL_VALUE, number);
1127 if (nAuthors == 0)
1129 SetDlgItemText(IDC_MOSTACTIVEAUTHORNAME, L"");
1130 SetDlgItemText(IDC_MOSTACTIVEAUTHORAVG, L"0");
1131 SetDlgItemText(IDC_MOSTACTIVEAUTHORMAX, L"0");
1132 SetDlgItemText(IDC_MOSTACTIVEAUTHORMIN, L"0");
1133 SetDlgItemText(IDC_LEASTACTIVEAUTHORNAME, L"");
1134 SetDlgItemText(IDC_LEASTACTIVEAUTHORAVG, L"0");
1135 SetDlgItemText(IDC_LEASTACTIVEAUTHORMAX, L"0");
1136 SetDlgItemText(IDC_LEASTACTIVEAUTHORMIN, L"0");
1138 else
1140 SetDlgItemText(IDC_MOSTACTIVEAUTHORNAME, mostActiveAuthor.c_str());
1141 number.Format(L"%ld", m_commitsPerAuthor[mostActiveAuthor] / nWeeks);
1142 SetDlgItemText(IDC_MOSTACTIVEAUTHORAVG, number);
1143 number.Format(L"%ld", nMostActiveMaxCommits);
1144 SetDlgItemText(IDC_MOSTACTIVEAUTHORMAX, number);
1145 number.Format(L"%ld", nMostActiveMinCommits);
1146 SetDlgItemText(IDC_MOSTACTIVEAUTHORMIN, number);
1148 SetDlgItemText(IDC_LEASTACTIVEAUTHORNAME, leastActiveAuthor.c_str());
1149 number.Format(L"%ld", m_commitsPerAuthor[leastActiveAuthor] / nWeeks);
1150 SetDlgItemText(IDC_LEASTACTIVEAUTHORAVG, number);
1151 number.Format(L"%ld", nLeastActiveMaxCommits);
1152 SetDlgItemText(IDC_LEASTACTIVEAUTHORMAX, number);
1153 number.Format(L"%ld", nLeastActiveMinCommits);
1154 SetDlgItemText(IDC_LEASTACTIVEAUTHORMIN, number);
1158 int CStatGraphDlg::RollPercentageOfAuthorship(double it)
1159 { return (int)it + (it - (int)it >= 0.5);}
1161 void CStatGraphDlg::OnCbnSelchangeGraphcombo()
1163 UpdateData();
1165 Metrics useMetric = (Metrics) m_cGraphType.GetItemData(m_cGraphType.GetCurSel());
1166 switch (useMetric )
1168 case AllStat:
1169 case CommitsByDate:
1170 // by date
1171 m_btnGraphLine.EnableWindow(TRUE);
1172 m_btnGraphLineStacked.EnableWindow(TRUE);
1173 m_btnGraphPie.EnableWindow(TRUE);
1174 m_GraphType = MyGraph::Line;
1175 m_bStacked = false;
1176 break;
1177 case PercentageOfAuthorship:
1178 case CommitsByAuthor:
1179 // by author
1180 m_btnGraphLine.EnableWindow(FALSE);
1181 m_btnGraphLineStacked.EnableWindow(FALSE);
1182 m_btnGraphPie.EnableWindow(TRUE);
1183 m_GraphType = MyGraph::Bar;
1184 m_bStacked = false;
1185 break;
1187 RedrawGraph();
1191 int CStatGraphDlg::GetUnitCount()
1193 if (m_nDays < 8)
1194 return m_nDays;
1195 if (m_nWeeks < 15)
1196 return m_nWeeks;
1197 if (m_nWeeks < 80)
1198 return (m_nWeeks/4)+1;
1199 if (m_nWeeks < 320)
1200 return (m_nWeeks/13)+1; // quarters
1201 return (m_nWeeks/52)+1;
1204 int CStatGraphDlg::GetUnit(const CTime& time)
1206 if (m_nDays < 8)
1207 return time.GetMonth()*100 + time.GetDay(); // month*100+day as the unit
1208 if (m_nWeeks < 15)
1209 return GetCalendarWeek(time);
1210 if (m_nWeeks < 80)
1211 return time.GetMonth();
1212 if (m_nWeeks < 320)
1213 return ((time.GetMonth()-1)/3)+1; // quarters
1214 return time.GetYear();
1217 CStatGraphDlg::UnitType CStatGraphDlg::GetUnitType()
1219 if (m_nDays < 8)
1220 return Days;
1221 if (m_nWeeks < 15)
1222 return Weeks;
1223 if (m_nWeeks < 80)
1224 return Months;
1225 if (m_nWeeks < 320)
1226 return Quarters;
1227 return Years;
1230 CString CStatGraphDlg::GetUnitString()
1232 if (m_nDays < 8)
1233 return CString(MAKEINTRESOURCE(IDS_STATGRAPH_COMMITSBYDATEXDAY));
1234 if (m_nWeeks < 15)
1235 return CString(MAKEINTRESOURCE(IDS_STATGRAPH_COMMITSBYDATEXWEEK));
1236 if (m_nWeeks < 80)
1237 return CString(MAKEINTRESOURCE(IDS_STATGRAPH_COMMITSBYDATEXMONTH));
1238 if (m_nWeeks < 320)
1239 return CString(MAKEINTRESOURCE(IDS_STATGRAPH_COMMITSBYDATEXQUARTER));
1240 return CString(MAKEINTRESOURCE(IDS_STATGRAPH_COMMITSBYDATEXYEAR));
1243 CString CStatGraphDlg::GetUnitLabel(int unit, CTime &lasttime)
1245 CString temp;
1246 switch (GetUnitType())
1248 case Days:
1250 // month*100+day as the unit
1251 int day = unit % 100;
1252 int month = unit / 100;
1253 switch (m_langOrder)
1255 case 0: // month day year
1256 temp.Format(L"%d/%d/%.2d", month, day, lasttime.GetYear() % 100);
1257 break;
1258 case 1: // day month year
1259 default:
1260 temp.Format(L"%d/%d/%.2d", day, month, lasttime.GetYear() % 100);
1261 break;
1262 case 2: // year month day
1263 temp.Format(L"%.2d/%d/%d", lasttime.GetYear() % 100, month, day);
1264 break;
1267 break;
1268 case Weeks:
1270 int year = lasttime.GetYear();
1271 if ((unit == 1)&&(lasttime.GetMonth() == 12))
1272 year += 1;
1274 switch (m_langOrder)
1276 case 0: // month day year
1277 case 1: // day month year
1278 default:
1279 temp.Format(L"%d/%.2d", unit, year % 100);
1280 break;
1281 case 2: // year month day
1282 temp.Format(L"%.2d/%d", year % 100, unit);
1283 break;
1286 break;
1287 case Months:
1288 switch (m_langOrder)
1290 case 0: // month day year
1291 case 1: // day month year
1292 default:
1293 temp.Format(L"%d/%.2d", unit, lasttime.GetYear() % 100);
1294 break;
1295 case 2: // year month day
1296 temp.Format(L"%.2d/%d", lasttime.GetYear() % 100, unit);
1297 break;
1299 break;
1300 case Quarters:
1301 switch (m_langOrder)
1303 case 0: // month day year
1304 case 1: // day month year
1305 default:
1306 temp.Format(L"%d/%.2d", unit, lasttime.GetYear() % 100);
1307 break;
1308 case 2: // year month day
1309 temp.Format(L"%.2d/%d", lasttime.GetYear() % 100, unit);
1310 break;
1312 break;
1313 case Years:
1314 temp.Format(L"%d", unit);
1315 break;
1317 return temp;
1320 void CStatGraphDlg::OnHScroll(UINT nSBCode, UINT nPos, CScrollBar* pScrollBar)
1322 if (nSBCode == TB_THUMBTRACK)
1323 return CDialog::OnHScroll(nSBCode, nPos, pScrollBar);
1325 ShowSelectStat((Metrics) m_cGraphType.GetItemData(m_cGraphType.GetCurSel()));
1326 CDialog::OnHScroll(nSBCode, nPos, pScrollBar);
1329 void CStatGraphDlg::OnNeedText(NMHDR *pnmh, LRESULT * /*pResult*/)
1331 TOOLTIPTEXT* pttt = (TOOLTIPTEXT*) pnmh;
1332 if (pttt->hdr.idFrom == (UINT_PTR) m_Skipper.GetSafeHwnd())
1334 size_t included_authors_count = m_Skipper.GetPos();
1335 // if we only leave out one author, still include him with his name
1336 if (included_authors_count + 1 == m_authorNames.size())
1337 ++included_authors_count;
1339 // find the minimum number of commits that the shown authors have
1340 int min_commits = 0;
1341 included_authors_count = min(included_authors_count, m_authorNames.size());
1342 std::list<tstring>::iterator author_it = m_authorNames.begin();
1343 advance(author_it, included_authors_count);
1344 if (author_it != m_authorNames.begin())
1345 min_commits = m_commitsPerAuthor[ *(--author_it) ];
1347 CString string;
1348 int percentage = int(min_commits*100.0/(m_nTotalCommits ? m_nTotalCommits : 1));
1349 string.Format(IDS_STATGRAPH_AUTHORSLIDER_TT, m_Skipper.GetPos(), min_commits, percentage);
1350 StringCchCopy(pttt->szText, _countof(pttt->szText), (LPCTSTR) string);
1354 void CStatGraphDlg::AuthorsCaseSensitiveChanged()
1356 UpdateData(); // update checkbox state
1357 GatherData(FALSE, TRUE); // first regenerate the statistics data
1358 RedrawGraph(); // then update the current statistics page
1361 void CStatGraphDlg::SortModeChanged()
1363 UpdateData(); // update checkbox state
1364 RedrawGraph(); // then update the current statistics page
1367 void CStatGraphDlg::ClearGraph()
1369 m_graph.Clear();
1370 for (int j=0; j<m_graphDataArray.GetCount(); ++j)
1371 delete ((MyGraphSeries *)m_graphDataArray.GetAt(j));
1372 m_graphDataArray.RemoveAll();
1375 void CStatGraphDlg::RedrawGraph()
1377 EnableDisableMenu();
1378 m_btnGraphBar.SetState(BST_UNCHECKED);
1379 m_btnGraphBarStacked.SetState(BST_UNCHECKED);
1380 m_btnGraphLine.SetState(BST_UNCHECKED);
1381 m_btnGraphLineStacked.SetState(BST_UNCHECKED);
1382 m_btnGraphPie.SetState(BST_UNCHECKED);
1384 if ((m_GraphType == MyGraph::Bar)&&(m_bStacked))
1386 m_btnGraphBarStacked.SetState(BST_CHECKED);
1388 if ((m_GraphType == MyGraph::Bar)&&(!m_bStacked))
1390 m_btnGraphBar.SetState(BST_CHECKED);
1392 if ((m_GraphType == MyGraph::Line)&&(m_bStacked))
1394 m_btnGraphLineStacked.SetState(BST_CHECKED);
1396 if ((m_GraphType == MyGraph::Line)&&(!m_bStacked))
1398 m_btnGraphLine.SetState(BST_CHECKED);
1400 if (m_GraphType == MyGraph::PieChart)
1402 m_btnGraphPie.SetState(BST_CHECKED);
1405 UpdateData();
1406 ShowSelectStat((Metrics) m_cGraphType.GetItemData(m_cGraphType.GetCurSel()), true);
1408 void CStatGraphDlg::OnBnClickedGraphbarbutton()
1410 m_GraphType = MyGraph::Bar;
1411 m_bStacked = false;
1412 RedrawGraph();
1415 void CStatGraphDlg::OnBnClickedGraphbarstackedbutton()
1417 m_GraphType = MyGraph::Bar;
1418 m_bStacked = true;
1419 RedrawGraph();
1422 void CStatGraphDlg::OnBnClickedGraphlinebutton()
1424 m_GraphType = MyGraph::Line;
1425 m_bStacked = false;
1426 RedrawGraph();
1429 void CStatGraphDlg::OnBnClickedGraphlinestackedbutton()
1431 m_GraphType = MyGraph::Line;
1432 m_bStacked = true;
1433 RedrawGraph();
1436 void CStatGraphDlg::OnBnClickedGraphpiebutton()
1438 m_GraphType = MyGraph::PieChart;
1439 m_bStacked = false;
1440 RedrawGraph();
1443 void CStatGraphDlg::EnableDisableMenu()
1445 UINT nEnable = MF_BYCOMMAND;
1447 Metrics SelectMetric = (Metrics) m_cGraphType.GetItemData(m_cGraphType.GetCurSel());
1449 nEnable |= (SelectMetric > TextStatStart && SelectMetric < TextStatEnd)
1450 ? (MF_DISABLED | MF_GRAYED) : MF_ENABLED;
1452 GetMenu()->EnableMenuItem(ID_FILE_SAVESTATGRAPHAS, nEnable);
1455 void CStatGraphDlg::OnFileSavestatgraphas()
1457 CString tempfile;
1458 int filterindex = 0;
1459 if (CAppUtils::FileOpenSave(tempfile, &filterindex, IDS_REVGRAPH_SAVEPIC, IDS_STATPICFILEFILTER, false, m_hWnd))
1461 // if the user doesn't specify a file extension, default to
1462 // wmf and add that extension to the filename. But only if the
1463 // user chose the 'pictures' filter. The filename isn't changed
1464 // if the 'All files' filter was chosen.
1465 CString extension;
1466 int dotPos = tempfile.ReverseFind('.');
1467 int slashPos = tempfile.ReverseFind('\\');
1468 if (dotPos > slashPos)
1469 extension = tempfile.Mid(dotPos);
1470 if ((filterindex == 1)&&(extension.IsEmpty()))
1472 extension = L".wmf";
1473 tempfile += extension;
1475 SaveGraph(tempfile);
1479 void CStatGraphDlg::SaveGraph(CString sFilename)
1481 CString extension = CPathUtils::GetFileExtFromPath(sFilename);
1482 if (extension.CompareNoCase(L".wmf") == 0)
1484 // save the graph as an enhanced meta file
1485 CMyMetaFileDC wmfDC;
1486 wmfDC.CreateEnhanced(nullptr, sFilename, nullptr, L"TortoiseGit\0Statistics\0\0");
1487 wmfDC.SetAttribDC(GetDC()->GetSafeHdc());
1488 RedrawGraph();
1489 m_graph.DrawGraph(wmfDC);
1490 HENHMETAFILE hemf = wmfDC.CloseEnhanced();
1491 DeleteEnhMetaFile(hemf);
1493 else
1495 // save the graph as a pixel picture instead of a vector picture
1496 // create dc to paint on
1499 CWindowDC ddc(this);
1500 CDC dc;
1501 if (!dc.CreateCompatibleDC(&ddc))
1503 ShowErrorMessage();
1504 return;
1506 CRect rect;
1507 GetDlgItem(IDC_GRAPH)->GetClientRect(&rect);
1508 HBITMAP hbm = ::CreateCompatibleBitmap(ddc.m_hDC, rect.Width(), rect.Height());
1509 if (hbm==0)
1511 ShowErrorMessage();
1512 return;
1514 HBITMAP oldbm = (HBITMAP)dc.SelectObject(hbm);
1515 // paint the whole graph
1516 RedrawGraph();
1517 m_graph.DrawGraph(dc);
1518 // now use GDI+ to save the picture
1519 CLSID encoderClsid;
1520 GdiplusStartupInput gdiplusStartupInput;
1521 ULONG_PTR gdiplusToken;
1522 CString sErrormessage;
1523 if (GdiplusStartup(&gdiplusToken, &gdiplusStartupInput, nullptr) == Ok)
1526 Bitmap bitmap(hbm, nullptr);
1527 if (bitmap.GetLastStatus()==Ok)
1529 // Get the CLSID of the encoder.
1530 int ret = 0;
1531 if (CPathUtils::GetFileExtFromPath(sFilename).CompareNoCase(L".png") == 0)
1532 ret = GetEncoderClsid(L"image/png", &encoderClsid);
1533 else if (CPathUtils::GetFileExtFromPath(sFilename).CompareNoCase(L".jpg") == 0)
1534 ret = GetEncoderClsid(L"image/jpeg", &encoderClsid);
1535 else if (CPathUtils::GetFileExtFromPath(sFilename).CompareNoCase(L".jpeg") == 0)
1536 ret = GetEncoderClsid(L"image/jpeg", &encoderClsid);
1537 else if (CPathUtils::GetFileExtFromPath(sFilename).CompareNoCase(L".bmp") == 0)
1538 ret = GetEncoderClsid(L"image/bmp", &encoderClsid);
1539 else if (CPathUtils::GetFileExtFromPath(sFilename).CompareNoCase(L".gif") == 0)
1540 ret = GetEncoderClsid(L"image/gif", &encoderClsid);
1541 else
1543 sFilename += L".jpg";
1544 ret = GetEncoderClsid(L"image/jpeg", &encoderClsid);
1546 if (ret >= 0)
1548 CStringW tfile = CStringW(sFilename);
1549 bitmap.Save(tfile, &encoderClsid, nullptr);
1551 else
1552 sErrormessage.Format(IDS_REVGRAPH_ERR_NOENCODER, (LPCTSTR)CPathUtils::GetFileExtFromPath(sFilename));
1554 else
1555 sErrormessage.LoadString(IDS_REVGRAPH_ERR_NOBITMAP);
1557 GdiplusShutdown(gdiplusToken);
1559 else
1560 sErrormessage.LoadString(IDS_REVGRAPH_ERR_GDIINIT);
1561 dc.SelectObject(oldbm);
1562 dc.DeleteDC();
1563 if (!sErrormessage.IsEmpty())
1564 ::MessageBox(m_hWnd, sErrormessage, L"TortoiseGit", MB_ICONERROR);
1566 catch (CException * pE)
1568 TCHAR szErrorMsg[2048] = { 0 };
1569 pE->GetErrorMessage(szErrorMsg, 2048);
1570 pE->Delete();
1571 ::MessageBox(m_hWnd, szErrorMsg, L"TortoiseGit", MB_ICONERROR);
1576 int CStatGraphDlg::GetEncoderClsid(const WCHAR* format, CLSID* pClsid)
1578 UINT num = 0; // number of image encoders
1579 UINT size = 0; // size of the image encoder array in bytes
1581 if (GetImageEncodersSize(&num, &size)!=Ok)
1582 return -1;
1583 if (size == 0)
1584 return -1; // Failure
1586 auto pMem = std::make_unique<BYTE[]>(size);
1587 auto pImageCodecInfo = (ImageCodecInfo*)(pMem.get());
1588 if (!pImageCodecInfo)
1589 return -1; // Failure
1591 if (GetImageEncoders(num, size, pImageCodecInfo)==Ok)
1593 for (UINT j = 0; j < num; ++j)
1595 if (wcscmp(pImageCodecInfo[j].MimeType, format) == 0)
1597 *pClsid = pImageCodecInfo[j].Clsid;
1598 return j; // Success
1602 return -1; // Failure
1605 void CStatGraphDlg::StoreCurrentGraphType()
1607 UpdateData();
1608 DWORD graphtype = static_cast<DWORD>(m_cGraphType.GetItemData(m_cGraphType.GetCurSel()));
1609 // encode the current chart type
1610 DWORD statspage = graphtype*10;
1611 if ((m_GraphType == MyGraph::Bar)&&(m_bStacked))
1612 statspage += 1;
1613 if ((m_GraphType == MyGraph::Bar)&&(!m_bStacked))
1614 statspage += 2;
1615 if ((m_GraphType == MyGraph::Line)&&(m_bStacked))
1616 statspage += 3;
1617 if ((m_GraphType == MyGraph::Line)&&(!m_bStacked))
1618 statspage += 4;
1619 if (m_GraphType == MyGraph::PieChart)
1620 statspage += 5;
1622 // store current chart type in registry
1623 CRegDWORD lastStatsPage(L"Software\\TortoiseGit\\LastViewedStatsPage", 0);
1624 lastStatsPage = statspage;
1626 CRegDWORD regAuthors(L"Software\\TortoiseGit\\StatAuthorsCaseSensitive");
1627 regAuthors = m_bAuthorsCaseSensitive;
1629 CRegDWORD regSort(L"Software\\TortoiseGit\\StatSortByCommitCount");
1630 regSort = m_bSortByCommitCount;
1633 void CStatGraphDlg::ShowErrorMessage()
1635 CFormatMessageWrapper errorDetails;
1636 if (errorDetails)
1637 MessageBox(errorDetails, L"Error", MB_OK | MB_ICONINFORMATION);
1640 void CStatGraphDlg::ShowSelectStat(Metrics SelectedMetric, bool reloadSkiper /* = false */)
1642 switch (SelectedMetric)
1644 case AllStat:
1645 LoadListOfAuthors(m_commitsPerAuthor, reloadSkiper);
1646 ShowStats();
1647 break;
1648 case CommitsByDate:
1649 LoadListOfAuthors(m_commitsPerAuthor, reloadSkiper);
1650 ShowByDate(IDS_STATGRAPH_COMMITSBYDATEY, IDS_STATGRAPH_COMMITSBYDATE, m_commitsPerUnitAndAuthor);
1651 break;
1652 case LinesWByDate:
1653 OnBnClickedFetchDiff();
1654 LoadListOfAuthors(m_commitsPerAuthor, reloadSkiper);
1655 ShowByDate(IDS_STATGRAPH_LINES_BYDATE_W_Y, IDS_STATGRAPH_LINES_BYDATE_W, m_LinesWPerUnitAndAuthor);
1656 break;
1657 case LinesWOByDate:
1658 OnBnClickedFetchDiff();
1659 LoadListOfAuthors(m_commitsPerAuthor, reloadSkiper);
1660 ShowByDate(IDS_STATGRAPH_LINES_BYDATE_WO_Y, IDS_STATGRAPH_LINES_BYDATE_WO, m_LinesWOPerUnitAndAuthor);
1661 break;
1662 case CommitsByAuthor:
1663 LoadListOfAuthors(m_commitsPerAuthor, reloadSkiper);
1664 ShowCommitsByAuthor();
1665 break;
1666 case PercentageOfAuthorship:
1667 OnBnClickedFetchDiff();
1668 LoadListOfAuthors(m_PercentageOfAuthorship, reloadSkiper, true);
1669 ShowPercentageOfAuthorship();
1670 break;
1671 default:
1672 ShowErrorMessage();
1676 double CStatGraphDlg::CoeffContribution(int distFromEnd) { return distFromEnd ? 1.0 / m_CoeffAuthorShip * distFromEnd : 1;}
1679 template <class MAP>
1680 void CStatGraphDlg::DrawOthers(const std::list<tstring> &others, MyGraphSeries *graphData, MAP &map)
1682 int nCommits = 0;
1683 for (std::list<tstring>::const_iterator it = others.begin(); it != others.end(); ++it)
1684 nCommits += RollPercentageOfAuthorship(map[*it]);
1686 CString sOthers(MAKEINTRESOURCE(IDS_STATGRAPH_OTHERGROUP));
1687 sOthers.AppendFormat(L" (%Iu)", others.size());
1688 int group = m_graph.AppendGroup(sOthers);
1689 graphData->SetData(group, (int)nCommits);
1693 template <class MAP>
1694 void CStatGraphDlg::LoadListOfAuthors (MAP &map, bool reloadSkiper/*= false*/, bool compare /*= false*/)
1696 m_authorNames.clear();
1697 if (!map.empty())
1699 for (MAP::const_iterator it = map.begin(); it != map.end(); ++it)
1701 if ((compare && RollPercentageOfAuthorship(map[it->first]) != 0) || !compare)
1702 m_authorNames.push_back(it->first);
1706 // Sort the list of authors based on commit count
1707 m_authorNames.sort(MoreCommitsThan<MAP::mapped_type>(map));
1709 // Set Skipper
1710 SetSkipper(reloadSkiper);
1714 void CStatGraphDlg::OnBnClickedFetchDiff()
1716 if (m_bDiffFetched)
1717 return;
1718 if (GatherData(TRUE))
1719 return;
1720 this->m_bDiffFetched = TRUE;
1721 GetDlgItem(IDC_CALC_DIFF)->ShowWindow(!m_bDiffFetched);
1723 ShowStats();