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.
21 #include "TortoiseProc.h"
22 #include "StatGraphDlg.h"
24 #include "PathUtils.h"
26 #include "FormatMessageWrapper.h"
27 #include "SysProgressDlg.h"
34 using namespace Gdiplus
;
36 // BinaryPredicate for comparing authors based on their commit count
37 template<class DataType
>
38 class MoreCommitsThan
{
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
];
49 MapType
&m_authorCommits
;
53 IMPLEMENT_DYNAMIC(CStatGraphDlg
, CResizableStandAloneDialog
)
54 CStatGraphDlg::CStatGraphDlg(CWnd
* pParent
/*=nullptr*/)
55 : CResizableStandAloneDialog(CStatGraphDlg::IDD
, pParent
)
57 , m_bAuthorsCaseSensitive(TRUE
)
58 , m_bSortByCommitCount(TRUE
)
59 , m_bUseCommitterNames(FALSE
)
60 , m_bUseCommitDates(TRUE
)
64 CStatGraphDlg::~CStatGraphDlg()
69 void CStatGraphDlg::OnOK() {
70 StoreCurrentGraphType();
74 void CStatGraphDlg::OnCancel() {
75 StoreCurrentGraphType();
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
)
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
)
114 void CStatGraphDlg::LoadStatQueries (__in UINT curStr
, Metrics loadMetric
, bool setDef
/* = false */)
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);
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
));
156 // gather statistics data, only needs to be updated when the checkbox with
157 // the case sensitivity of author names is changed
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
);
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
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
);
271 OnCbnSelchangeGraphcombo();
273 int statspage
= lastStatsPage
% 10;
276 m_GraphType
= MyGraph::GraphType::Bar
;
280 m_GraphType
= MyGraph::GraphType::Bar
;
284 m_GraphType
= MyGraph::GraphType::Line
;
288 m_GraphType
= MyGraph::GraphType::Line
;
292 m_GraphType
= MyGraph::GraphType::PieChart
;
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
);
311 void CStatGraphDlg::ShowLabels(BOOL bShow
)
313 if (m_parAuthors
.IsEmpty() || m_parDates
.IsEmpty() || m_parFileChanges
.IsEmpty())
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()
368 if (m_parDates
.IsEmpty())
371 // Already updated? No need to do it again.
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
)
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
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() )
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;
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
)
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);
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
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;
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
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
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);
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
489 static_cast<int>(((time
-dDateFirstJanuary
).GetDays()+iDayOfWeek
) / 7) +
490 (iDayOfWeek
<=3 ? 1:0);
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
506 static_cast<int>(((time
-dDateFirstJanuary
).GetDays()+iDayOfWeek
) / 7) +
507 (iDayOfWeek
<=3 ? 1: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);
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
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);
533 (dDateFirstJanuary
.GetDayOfWeek()+5+iFirstDayOfWeek
)%7;
534 // 1.1. in week 1 or week 53?
535 iWeekOfYear
= iDayOfWeek
<=3 ? 1:53;
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();
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
;
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(); });
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()));
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
);
615 inc
+= _wtol(list
[j
].m_StatAdd
);
616 dec
+= _wtol(list
[j
].m_StatDel
);
619 if (progress
.HasUserCancelled())
623 if (!keepFetchedData
)
625 m_parFileChanges
.Add(files
);
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();
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
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();
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
);
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();
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);
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.
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
);
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())
776 ShowLabels(fShowLabels
);
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.
785 GatherData(FALSE
, TRUE
);
786 // If week count is still -1, something bad has happened, probably invalid data!
793 MyGraphSeries
*CStatGraphDlg::PreViewGraph(__in UINT GraphTitle
, __in UINT YAxisLabel
, __in UINT XAxisLabel
/*= nullptr*/)
795 if(!PreViewStat(false))
798 // We need at least one author
799 if (m_authorNames
.empty())
802 // Add a single series to the chart
803 MyGraphSeries
* graphData
= new MyGraphSeries();
804 m_graph
.AddSeries(*graphData
);
805 m_graphDataArray
.Add(graphData
);
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
);
821 void CStatGraphDlg::ShowPercentageOfAuthorship()
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.
850 DrawOthers(others
, graphData
, m_PercentageOfAuthorship
);
852 // Paint the graph now that we're through.
853 m_graph
.Invalidate();
856 void CStatGraphDlg::ShowCommitsByAuthor()
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.
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;
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
;
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())
942 commitCount
[*it
] += data_it
->second
;
945 // Collect data for all skipped authors.
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())
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.
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
;
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
1040 // check if author is present in this interval
1041 AuthorDataMap::iterator author_it
= m_commitsPerUnitAndAuthor
[i
].find(mostActiveAuthor
);
1043 if (author_it
== m_commitsPerUnitAndAuthor
[i
].end())
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())
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
;
1071 // Adjust the labels with the unit type (week, month, ...)
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...
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
);
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
);
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
);
1111 SetDlgItemText(IDC_TOTAL_LINE_WITH_NEW_DEL_VALUE
, number
);
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");
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()
1153 Metrics useMetric
= static_cast<Metrics
>(m_cGraphType
.GetItemData(m_cGraphType
.GetCurSel()));
1159 m_btnGraphLine
.EnableWindow(TRUE
);
1160 m_btnGraphLineStacked
.EnableWindow(TRUE
);
1161 m_btnGraphPie
.EnableWindow(TRUE
);
1162 m_GraphType
= MyGraph::GraphType::Line
;
1165 case PercentageOfAuthorship
:
1166 case CommitsByAuthor
:
1168 m_btnGraphLine
.EnableWindow(FALSE
);
1169 m_btnGraphLineStacked
.EnableWindow(FALSE
);
1170 m_btnGraphPie
.EnableWindow(TRUE
);
1171 m_GraphType
= MyGraph::GraphType::Bar
;
1178 int CStatGraphDlg::GetUnit(const CTime
& time
)
1181 return time
.GetMonth()*100 + time
.GetDay(); // month*100+day as the unit
1183 return GetCalendarWeek(time
);
1185 return time
.GetMonth();
1187 return ((time
.GetMonth()-1)/3)+1; // quarters
1188 return time
.GetYear();
1191 CStatGraphDlg::UnitType
CStatGraphDlg::GetUnitType()
1204 CString
CStatGraphDlg::GetUnitString()
1207 return CString(MAKEINTRESOURCE(IDS_STATGRAPH_COMMITSBYDATEXDAY
));
1209 return CString(MAKEINTRESOURCE(IDS_STATGRAPH_COMMITSBYDATEXWEEK
));
1211 return CString(MAKEINTRESOURCE(IDS_STATGRAPH_COMMITSBYDATEXMONTH
));
1213 return CString(MAKEINTRESOURCE(IDS_STATGRAPH_COMMITSBYDATEXQUARTER
));
1214 return CString(MAKEINTRESOURCE(IDS_STATGRAPH_COMMITSBYDATEXYEAR
));
1217 CString
CStatGraphDlg::GetUnitLabel(int unit
, CTime
&lasttime
)
1220 switch (GetUnitType())
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);
1232 case 1: // day month year
1234 temp
.Format(L
"%d/%d/%.2d", day
, month
, lasttime
.GetYear() % 100);
1236 case 2: // year month day
1237 temp
.Format(L
"%.2d/%d/%d", lasttime
.GetYear() % 100, month
, day
);
1244 int year
= lasttime
.GetYear();
1245 if ((unit
== 1)&&(lasttime
.GetMonth() == 12))
1248 switch (m_langOrder
)
1250 case 0: // month day year
1251 case 1: // day month year
1253 temp
.Format(L
"%d/%.2d", unit
, year
% 100);
1255 case 2: // year month day
1256 temp
.Format(L
"%.2d/%d", year
% 100, unit
);
1262 switch (m_langOrder
)
1264 case 0: // month day year
1265 case 1: // day month year
1267 temp
.Format(L
"%d/%.2d", unit
, lasttime
.GetYear() % 100);
1269 case 2: // year month day
1270 temp
.Format(L
"%.2d/%d", lasttime
.GetYear() % 100, unit
);
1275 switch (m_langOrder
)
1277 case 0: // month day year
1278 case 1: // day month year
1280 temp
.Format(L
"%d/%.2d", unit
, lasttime
.GetYear() % 100);
1282 case 2: // year month day
1283 temp
.Format(L
"%.2d/%d", lasttime
.GetYear() % 100, unit
);
1288 temp
.Format(L
"%d", unit
);
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
) ];
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()
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
);
1394 ShowSelectStat(static_cast<Metrics
>(m_cGraphType
.GetItemData(m_cGraphType
.GetCurSel())), true);
1396 void CStatGraphDlg::OnBnClickedGraphbarbutton()
1398 m_GraphType
= MyGraph::GraphType::Bar
;
1403 void CStatGraphDlg::OnBnClickedGraphbarstackedbutton()
1405 m_GraphType
= MyGraph::GraphType::Bar
;
1410 void CStatGraphDlg::OnBnClickedGraphlinebutton()
1412 m_GraphType
= MyGraph::GraphType::Line
;
1417 void CStatGraphDlg::OnBnClickedGraphlinestackedbutton()
1419 m_GraphType
= MyGraph::GraphType::Line
;
1424 void CStatGraphDlg::OnBnClickedGraphpiebutton()
1426 m_GraphType
= MyGraph::GraphType::PieChart
;
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()
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.
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());
1477 m_graph
.DrawGraph(wmfDC
);
1478 HENHMETAFILE hemf
= wmfDC
.CloseEnhanced();
1479 DeleteEnhMetaFile(hemf
);
1483 // save the graph as a pixel picture instead of a vector picture
1484 // create dc to paint on
1487 CWindowDC
ddc(this);
1489 if (!dc
.CreateCompatibleDC(&ddc
))
1495 GetDlgItem(IDC_GRAPH
)->GetClientRect(&rect
);
1496 HBITMAP hbm
= ::CreateCompatibleBitmap(ddc
.m_hDC
, rect
.Width(), rect
.Height());
1502 auto oldbm
= static_cast<HBITMAP
>(dc
.SelectObject(hbm
));
1503 // paint the whole graph
1505 m_graph
.DrawGraph(dc
);
1506 // now use GDI+ to save the picture
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.
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
);
1531 sFilename
+= L
".jpg";
1532 ret
= GetEncoderClsid(L
"image/jpeg", &encoderClsid
);
1536 CStringW tfile
= CStringW(sFilename
);
1537 bitmap
.Save(tfile
, &encoderClsid
, nullptr);
1540 sErrormessage
.Format(IDS_REVGRAPH_ERR_NOENCODER
, static_cast<LPCWSTR
>(CPathUtils::GetFileExtFromPath(sFilename
)));
1543 sErrormessage
.LoadString(IDS_REVGRAPH_ERR_NOBITMAP
);
1545 GdiplusShutdown(gdiplusToken
);
1548 sErrormessage
.LoadString(IDS_REVGRAPH_ERR_GDIINIT
);
1549 dc
.SelectObject(oldbm
);
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);
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
)
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()
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
))
1601 if ((m_GraphType
== MyGraph::GraphType::Bar
) && (!m_bStacked
))
1603 if ((m_GraphType
== MyGraph::GraphType::Line
) && (m_bStacked
))
1605 if ((m_GraphType
== MyGraph::GraphType::Line
) && (!m_bStacked
))
1607 if (m_GraphType
== MyGraph::GraphType::PieChart
)
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
;
1631 MessageBox(errorDetails
, L
"Error", MB_OK
| MB_ICONINFORMATION
);
1634 void CStatGraphDlg::ShowSelectStat(Metrics SelectedMetric
, bool reloadSkiper
/* = false */)
1636 switch (SelectedMetric
)
1639 LoadListOfAuthors(m_commitsPerAuthor
, reloadSkiper
);
1643 LoadListOfAuthors(m_commitsPerAuthor
, reloadSkiper
);
1644 ShowByDate(IDS_STATGRAPH_COMMITSBYDATEY
, IDS_STATGRAPH_COMMITSBYDATE
, m_commitsPerUnitAndAuthor
);
1647 OnBnClickedFetchDiff();
1648 LoadListOfAuthors(m_commitsPerAuthor
, reloadSkiper
);
1649 ShowByDate(IDS_STATGRAPH_LINES_BYDATE_W_Y
, IDS_STATGRAPH_LINES_BYDATE_W
, m_LinesWPerUnitAndAuthor
);
1652 OnBnClickedFetchDiff();
1653 LoadListOfAuthors(m_commitsPerAuthor
, reloadSkiper
);
1654 ShowByDate(IDS_STATGRAPH_LINES_BYDATE_WO_Y
, IDS_STATGRAPH_LINES_BYDATE_WO
, m_LinesWOPerUnitAndAuthor
);
1656 case CommitsByAuthor
:
1657 LoadListOfAuthors(m_commitsPerAuthor
, reloadSkiper
);
1658 ShowCommitsByAuthor();
1660 case PercentageOfAuthorship
:
1661 OnBnClickedFetchDiff();
1662 LoadListOfAuthors(m_PercentageOfAuthorship
, reloadSkiper
, true);
1663 ShowPercentageOfAuthorship();
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
)
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();
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
));
1704 SetSkipper(reloadSkiper
);
1708 void CStatGraphDlg::OnBnClickedFetchDiff()
1712 if (GatherData(TRUE
))
1714 m_bDiffFetched
= true;
1715 GetDlgItem(IDC_CALC_DIFF
)->ShowWindow(!m_bDiffFetched
);