Allow to move item past last item
[TortoiseGit.git] / src / TortoiseProc / StatGraphDlg.cpp
blob170f5908167014b348f65309a00d493084c0bcbf
1 // TortoiseGit - a Windows shell extension for easy version control
3 // Copyright (C) 2008-2016 - 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 <GdiPlus.h>
24 #include "AppUtils.h"
25 #include "PathUtils.h"
26 #include "registry.h"
27 #include "FormatMessageWrapper.h"
28 #include "SysProgressDlg.h"
30 #include <iterator>
31 #include <cmath>
32 #include <locale>
33 #include <list>
34 #include <utility>
35 #include <strsafe.h>
37 using namespace Gdiplus;
39 // BinaryPredicate for comparing authors based on their commit count
40 template<class DataType>
41 class MoreCommitsThan : public std::binary_function<tstring, tstring, bool> {
42 public:
43 typedef std::map<tstring, DataType> MapType;
44 MoreCommitsThan(MapType &author_commits) : m_authorCommits(author_commits) {}
46 bool operator()(const tstring& lhs, const tstring& rhs) {
47 return (m_authorCommits)[lhs] > (m_authorCommits)[rhs];
50 private:
51 MapType &m_authorCommits;
55 IMPLEMENT_DYNAMIC(CStatGraphDlg, CResizableStandAloneDialog)
56 CStatGraphDlg::CStatGraphDlg(CWnd* pParent /*=nullptr*/)
57 : CResizableStandAloneDialog(CStatGraphDlg::IDD, pParent)
58 , m_bStacked(FALSE)
59 , m_GraphType(MyGraph::Bar)
60 , m_bAuthorsCaseSensitive(TRUE)
61 , m_bSortByCommitCount(TRUE)
62 , m_nWeeks(-1)
63 , m_nDays(-1)
64 , m_langOrder(0)
65 , m_firstInterval(0)
66 , m_lastInterval(0)
67 , m_nTotalCommits(0)
68 , m_nTotalLinesInc(0)
69 , m_nTotalLinesDec(0)
70 , m_nTotalLinesNew(0)
71 , m_nTotalLinesDel(0)
72 , m_bDiffFetched(FALSE)
73 , m_ShowList(nullptr)
74 , m_minDate(0)
75 , m_maxDate(0)
76 , m_nTotalFileChanges(0)
80 CStatGraphDlg::~CStatGraphDlg()
82 ClearGraph();
85 void CStatGraphDlg::OnOK() {
86 StoreCurrentGraphType();
87 __super::OnOK();
90 void CStatGraphDlg::OnCancel() {
91 StoreCurrentGraphType();
92 __super::OnCancel();
95 void CStatGraphDlg::DoDataExchange(CDataExchange* pDX)
97 CResizableStandAloneDialog::DoDataExchange(pDX);
98 DDX_Control(pDX, IDC_GRAPH, m_graph);
99 DDX_Control(pDX, IDC_GRAPHCOMBO, m_cGraphType);
100 DDX_Control(pDX, IDC_SKIPPER, m_Skipper);
101 DDX_Check(pDX, IDC_AUTHORSCASESENSITIVE, m_bAuthorsCaseSensitive);
102 DDX_Check(pDX, IDC_SORTBYCOMMITCOUNT, m_bSortByCommitCount);
103 DDX_Control(pDX, IDC_GRAPHBARBUTTON, m_btnGraphBar);
104 DDX_Control(pDX, IDC_GRAPHBARSTACKEDBUTTON, m_btnGraphBarStacked);
105 DDX_Control(pDX, IDC_GRAPHLINEBUTTON, m_btnGraphLine);
106 DDX_Control(pDX, IDC_GRAPHLINESTACKEDBUTTON, m_btnGraphLineStacked);
107 DDX_Control(pDX, IDC_GRAPHPIEBUTTON, m_btnGraphPie);
111 BEGIN_MESSAGE_MAP(CStatGraphDlg, CResizableStandAloneDialog)
112 ON_CBN_SELCHANGE(IDC_GRAPHCOMBO, OnCbnSelchangeGraphcombo)
113 ON_WM_HSCROLL()
114 ON_NOTIFY(TTN_NEEDTEXT, nullptr, OnNeedText)
115 ON_BN_CLICKED(IDC_AUTHORSCASESENSITIVE, &CStatGraphDlg::AuthorsCaseSensitiveChanged)
116 ON_BN_CLICKED(IDC_SORTBYCOMMITCOUNT, &CStatGraphDlg::SortModeChanged)
117 ON_BN_CLICKED(IDC_GRAPHBARBUTTON, &CStatGraphDlg::OnBnClickedGraphbarbutton)
118 ON_BN_CLICKED(IDC_GRAPHBARSTACKEDBUTTON, &CStatGraphDlg::OnBnClickedGraphbarstackedbutton)
119 ON_BN_CLICKED(IDC_GRAPHLINEBUTTON, &CStatGraphDlg::OnBnClickedGraphlinebutton)
120 ON_BN_CLICKED(IDC_GRAPHLINESTACKEDBUTTON, &CStatGraphDlg::OnBnClickedGraphlinestackedbutton)
121 ON_BN_CLICKED(IDC_GRAPHPIEBUTTON, &CStatGraphDlg::OnBnClickedGraphpiebutton)
122 ON_COMMAND(ID_FILE_SAVESTATGRAPHAS, &CStatGraphDlg::OnFileSavestatgraphas)
123 ON_BN_CLICKED(IDC_CALC_DIFF, &CStatGraphDlg::OnBnClickedFetchDiff)
124 END_MESSAGE_MAP()
126 void CStatGraphDlg::LoadStatQueries (__in UINT curStr, Metrics loadMetric, bool setDef /* = false */)
128 CString temp;
129 temp.LoadString(curStr);
130 int sel = m_cGraphType.AddString(temp);
131 m_cGraphType.SetItemData(sel, loadMetric);
133 if (setDef) m_cGraphType.SetCurSel(sel);
136 void CStatGraphDlg::SetSkipper (bool reloadSkiper)
138 // We need to limit the number of authors due to GUI resource limitation.
139 // However, since author #251 will properly have < 1000th of the commits,
140 // the resolution limit of the screen will already not allow for displaying
141 // it in a reasonable way
143 int max_authors_count = max(1, (int)min(m_authorNames.size(), 250) );
144 m_Skipper.SetRange (1, max_authors_count);
145 m_Skipper.SetPageSize(5);
147 if (reloadSkiper)
148 m_Skipper.SetPos (max_authors_count);
151 BOOL CStatGraphDlg::OnInitDialog()
153 CResizableStandAloneDialog::OnInitDialog();
155 // gather statistics data, only needs to be updated when the checkbox with
156 // the case sensitivity of author names is changed
157 GatherData();
159 m_tooltips.AddTool(&m_btnGraphPie, IDS_STATGRAPH_PIEBUTTON_TT);
160 m_tooltips.AddTool(&m_btnGraphLineStacked, IDS_STATGRAPH_LINESTACKEDBUTTON_TT);
161 m_tooltips.AddTool(&m_btnGraphLine, IDS_STATGRAPH_LINEBUTTON_TT);
162 m_tooltips.AddTool(&m_btnGraphBarStacked, IDS_STATGRAPH_BARSTACKEDBUTTON_TT);
163 m_tooltips.AddTool(&m_btnGraphBar, IDS_STATGRAPH_BARBUTTON_TT);
164 m_tooltips.Activate(TRUE);
166 m_bAuthorsCaseSensitive = DWORD(CRegDWORD(L"Software\\TortoiseGit\\StatAuthorsCaseSensitive"));
167 m_bSortByCommitCount = DWORD(CRegDWORD(L"Software\\TortoiseGit\\StatSortByCommitCount"));
168 UpdateData(FALSE);
170 //Load statistical queries
171 LoadStatQueries(IDS_STATGRAPH_STATS, AllStat, true);
172 LoadStatQueries(IDS_STATGRAPH_COMMITSBYDATE, CommitsByDate);
173 LoadStatQueries(IDS_STATGRAPH_COMMITSBYAUTHOR, CommitsByAuthor);
174 LoadStatQueries(IDS_STATGRAPH_PERCENTAGE_OF_AUTHORSHIP, PercentageOfAuthorship);
175 LoadStatQueries(IDS_STATGRAPH_LINES_BYDATE_W, LinesWByDate);
176 LoadStatQueries(IDS_STATGRAPH_LINES_BYDATE_WO, LinesWOByDate);
178 // set the dialog title to "Statistics - path/to/whatever/we/show/the/statistics/for"
179 CString sTitle;
180 GetWindowText(sTitle);
181 if (m_path.IsEmpty())
182 CAppUtils::SetWindowTitle(m_hWnd, g_Git.m_CurrentDir, sTitle);
183 else
184 CAppUtils::SetWindowTitle(m_hWnd, m_path.GetUIPathString(), sTitle);
186 int iconWidth = GetSystemMetrics(SM_CXSMICON);
187 int iconHeight = GetSystemMetrics(SM_CYSMICON);
188 m_btnGraphBar.SetImage((HICON)LoadImage(AfxGetResourceHandle(), MAKEINTRESOURCE(IDI_GRAPHBAR), IMAGE_ICON, iconWidth, iconHeight, LR_DEFAULTCOLOR));
189 m_btnGraphBar.SizeToContent();
190 m_btnGraphBar.Invalidate();
191 m_btnGraphBarStacked.SetImage((HICON)LoadImage(AfxGetResourceHandle(), MAKEINTRESOURCE(IDI_GRAPHBARSTACKED), IMAGE_ICON, iconWidth, iconHeight, LR_DEFAULTCOLOR));
192 m_btnGraphBarStacked.SizeToContent();
193 m_btnGraphBarStacked.Invalidate();
194 m_btnGraphLine.SetImage((HICON)LoadImage(AfxGetResourceHandle(), MAKEINTRESOURCE(IDI_GRAPHLINE), IMAGE_ICON, iconWidth, iconHeight, LR_DEFAULTCOLOR));
195 m_btnGraphLine.SizeToContent();
196 m_btnGraphLine.Invalidate();
197 m_btnGraphLineStacked.SetImage((HICON)LoadImage(AfxGetResourceHandle(), MAKEINTRESOURCE(IDI_GRAPHLINESTACKED), IMAGE_ICON, iconWidth, iconHeight, LR_DEFAULTCOLOR));
198 m_btnGraphLineStacked.SizeToContent();
199 m_btnGraphLineStacked.Invalidate();
200 m_btnGraphPie.SetImage((HICON)LoadImage(AfxGetResourceHandle(), MAKEINTRESOURCE(IDI_GRAPHPIE), IMAGE_ICON, iconWidth, iconHeight, LR_DEFAULTCOLOR));
201 m_btnGraphPie.SizeToContent();
202 m_btnGraphPie.Invalidate();
204 AddAnchor(IDC_GRAPHTYPELABEL, TOP_LEFT);
205 AddAnchor(IDC_GRAPH, TOP_LEFT, BOTTOM_RIGHT);
206 AddAnchor(IDC_GRAPHCOMBO, TOP_LEFT, TOP_RIGHT);
208 AddAnchor(IDC_NUMWEEK, TOP_LEFT);
209 AddAnchor(IDC_NUMWEEKVALUE, TOP_RIGHT);
210 AddAnchor(IDC_NUMAUTHOR, TOP_LEFT);
211 AddAnchor(IDC_NUMAUTHORVALUE, TOP_RIGHT);
212 AddAnchor(IDC_NUMCOMMITS, TOP_LEFT);
213 AddAnchor(IDC_NUMCOMMITSVALUE, TOP_RIGHT);
214 AddAnchor(IDC_NUMFILECHANGES, TOP_LEFT);
215 AddAnchor(IDC_NUMFILECHANGESVALUE, TOP_RIGHT);
217 AddAnchor(IDC_TOTAL_LINE_WITHOUT_NEW_DEL, TOP_LEFT);
218 AddAnchor(IDC_TOTAL_LINE_WITHOUT_NEW_DEL_VALUE, TOP_RIGHT);
219 AddAnchor(IDC_TOTAL_LINE_WITH_NEW_DEL, TOP_LEFT);
220 AddAnchor(IDC_TOTAL_LINE_WITH_NEW_DEL_VALUE, TOP_RIGHT);
222 AddAnchor(IDC_CALC_DIFF, TOP_RIGHT);
224 AddAnchor(IDC_AVG, TOP_RIGHT);
225 AddAnchor(IDC_MIN, TOP_RIGHT);
226 AddAnchor(IDC_MAX, TOP_RIGHT);
227 AddAnchor(IDC_COMMITSEACHWEEK, TOP_LEFT);
228 AddAnchor(IDC_MOSTACTIVEAUTHOR, TOP_LEFT);
229 AddAnchor(IDC_LEASTACTIVEAUTHOR, TOP_LEFT);
230 AddAnchor(IDC_MOSTACTIVEAUTHORNAME, TOP_LEFT, TOP_RIGHT);
231 AddAnchor(IDC_LEASTACTIVEAUTHORNAME, TOP_LEFT, TOP_RIGHT);
232 AddAnchor(IDC_FILECHANGESEACHWEEK, TOP_LEFT);
233 AddAnchor(IDC_COMMITSEACHWEEKAVG, TOP_RIGHT);
234 AddAnchor(IDC_COMMITSEACHWEEKMIN, TOP_RIGHT);
235 AddAnchor(IDC_COMMITSEACHWEEKMAX, TOP_RIGHT);
236 AddAnchor(IDC_MOSTACTIVEAUTHORAVG, TOP_RIGHT);
237 AddAnchor(IDC_MOSTACTIVEAUTHORMIN, TOP_RIGHT);
238 AddAnchor(IDC_MOSTACTIVEAUTHORMAX, TOP_RIGHT);
239 AddAnchor(IDC_LEASTACTIVEAUTHORAVG, TOP_RIGHT);
240 AddAnchor(IDC_LEASTACTIVEAUTHORMIN, TOP_RIGHT);
241 AddAnchor(IDC_LEASTACTIVEAUTHORMAX, TOP_RIGHT);
242 AddAnchor(IDC_FILECHANGESEACHWEEKAVG, TOP_RIGHT);
243 AddAnchor(IDC_FILECHANGESEACHWEEKMIN, TOP_RIGHT);
244 AddAnchor(IDC_FILECHANGESEACHWEEKMAX, TOP_RIGHT);
246 AddAnchor(IDC_GRAPHBARBUTTON, BOTTOM_RIGHT);
247 AddAnchor(IDC_GRAPHBARSTACKEDBUTTON, BOTTOM_RIGHT);
248 AddAnchor(IDC_GRAPHLINEBUTTON, BOTTOM_RIGHT);
249 AddAnchor(IDC_GRAPHLINESTACKEDBUTTON, BOTTOM_RIGHT);
250 AddAnchor(IDC_GRAPHPIEBUTTON, BOTTOM_RIGHT);
252 AddAnchor(IDC_AUTHORSCASESENSITIVE, BOTTOM_LEFT);
253 AddAnchor(IDC_SORTBYCOMMITCOUNT, BOTTOM_LEFT);
254 AddAnchor(IDC_SKIPPER, BOTTOM_LEFT, BOTTOM_RIGHT);
255 AddAnchor(IDC_SKIPPERLABEL, BOTTOM_LEFT);
256 AddAnchor(IDOK, BOTTOM_RIGHT);
257 EnableSaveRestore(L"StatGraphDlg");
259 // set the min/max values on the skipper
260 SetSkipper (true);
262 // we use a stats page encoding here, 0 stands for the statistics dialog
263 CRegDWORD lastStatsPage(L"Software\\TortoiseGit\\LastViewedStatsPage", 0);
265 // open last viewed statistics page as first page
266 int graphtype = lastStatsPage / 10;
267 for (int i = 0; i < m_cGraphType.GetCount(); i++)
269 if ((int)m_cGraphType.GetItemData(i) == graphtype)
271 m_cGraphType.SetCurSel(i);
272 break;
276 OnCbnSelchangeGraphcombo();
278 int statspage = lastStatsPage % 10;
279 switch (statspage) {
280 case 1 :
281 m_GraphType = MyGraph::Bar;
282 m_bStacked = true;
283 break;
284 case 2 :
285 m_GraphType = MyGraph::Bar;
286 m_bStacked = false;
287 break;
288 case 3 :
289 m_GraphType = MyGraph::Line;
290 m_bStacked = true;
291 break;
292 case 4 :
293 m_GraphType = MyGraph::Line;
294 m_bStacked = false;
295 break;
296 case 5 :
297 m_GraphType = MyGraph::PieChart;
298 break;
300 default : return TRUE;
303 LCID m_locale = MAKELCID((DWORD)CRegStdDWORD(L"Software\\TortoiseGit\\LanguageID", MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT)), SORT_DEFAULT);
305 bool bUseSystemLocale = !!(DWORD)CRegStdDWORD(L"Software\\TortoiseGit\\UseSystemLocaleForDates", TRUE);
306 LCID locale = bUseSystemLocale ? MAKELCID(MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), SORT_DEFAULT) : m_locale;
308 TCHAR langBuf[11] = { 0 };
309 GetLocaleInfo(locale, LOCALE_IDATE, langBuf, _countof(langBuf));
311 m_langOrder = _wtoi(langBuf);
313 return TRUE;
316 void CStatGraphDlg::ShowLabels(BOOL bShow)
318 if (m_parAuthors.IsEmpty() || m_parDates.IsEmpty() || m_parFileChanges.IsEmpty())
319 return;
321 int nCmdShow = bShow ? SW_SHOW : SW_HIDE;
323 GetDlgItem(IDC_GRAPH)->ShowWindow(bShow ? SW_HIDE : SW_SHOW);
324 GetDlgItem(IDC_NUMWEEK)->ShowWindow(nCmdShow);
325 GetDlgItem(IDC_NUMWEEKVALUE)->ShowWindow(nCmdShow);
326 GetDlgItem(IDC_NUMAUTHOR)->ShowWindow(nCmdShow);
327 GetDlgItem(IDC_NUMAUTHORVALUE)->ShowWindow(nCmdShow);
328 GetDlgItem(IDC_NUMCOMMITS)->ShowWindow(nCmdShow);
329 GetDlgItem(IDC_NUMCOMMITSVALUE)->ShowWindow(nCmdShow);
330 GetDlgItem(IDC_NUMFILECHANGES)->ShowWindow(nCmdShow);
331 GetDlgItem(IDC_NUMFILECHANGESVALUE)->ShowWindow(nCmdShow);
332 GetDlgItem(IDC_TOTAL_LINE_WITHOUT_NEW_DEL)->ShowWindow(nCmdShow);
333 GetDlgItem(IDC_TOTAL_LINE_WITHOUT_NEW_DEL_VALUE)->ShowWindow(nCmdShow);
334 GetDlgItem(IDC_TOTAL_LINE_WITH_NEW_DEL)->ShowWindow(nCmdShow);
335 GetDlgItem(IDC_TOTAL_LINE_WITH_NEW_DEL_VALUE)->ShowWindow(nCmdShow);
336 GetDlgItem(IDC_CALC_DIFF)->ShowWindow(nCmdShow && !m_bDiffFetched);
338 GetDlgItem(IDC_AVG)->ShowWindow(nCmdShow);
339 GetDlgItem(IDC_MIN)->ShowWindow(nCmdShow);
340 GetDlgItem(IDC_MAX)->ShowWindow(nCmdShow);
341 GetDlgItem(IDC_COMMITSEACHWEEK)->ShowWindow(nCmdShow);
342 GetDlgItem(IDC_MOSTACTIVEAUTHOR)->ShowWindow(nCmdShow);
343 GetDlgItem(IDC_LEASTACTIVEAUTHOR)->ShowWindow(nCmdShow);
344 GetDlgItem(IDC_MOSTACTIVEAUTHORNAME)->ShowWindow(nCmdShow);
345 GetDlgItem(IDC_LEASTACTIVEAUTHORNAME)->ShowWindow(nCmdShow);
346 //GetDlgItem(IDC_FILECHANGESEACHWEEK)->ShowWindow(nCmdShow);
347 GetDlgItem(IDC_COMMITSEACHWEEKAVG)->ShowWindow(nCmdShow);
348 GetDlgItem(IDC_COMMITSEACHWEEKMIN)->ShowWindow(nCmdShow);
349 GetDlgItem(IDC_COMMITSEACHWEEKMAX)->ShowWindow(nCmdShow);
350 GetDlgItem(IDC_MOSTACTIVEAUTHORAVG)->ShowWindow(nCmdShow);
351 GetDlgItem(IDC_MOSTACTIVEAUTHORMIN)->ShowWindow(nCmdShow);
352 GetDlgItem(IDC_MOSTACTIVEAUTHORMAX)->ShowWindow(nCmdShow);
353 GetDlgItem(IDC_LEASTACTIVEAUTHORAVG)->ShowWindow(nCmdShow);
354 GetDlgItem(IDC_LEASTACTIVEAUTHORMIN)->ShowWindow(nCmdShow);
355 GetDlgItem(IDC_LEASTACTIVEAUTHORMAX)->ShowWindow(nCmdShow);
356 GetDlgItem(IDC_FILECHANGESEACHWEEKAVG)->ShowWindow(nCmdShow);
357 GetDlgItem(IDC_FILECHANGESEACHWEEKMIN)->ShowWindow(nCmdShow);
358 GetDlgItem(IDC_FILECHANGESEACHWEEKMAX)->ShowWindow(nCmdShow);
360 GetDlgItem(IDC_SORTBYCOMMITCOUNT)->ShowWindow(bShow ? SW_HIDE : SW_SHOW);
361 GetDlgItem(IDC_SKIPPER)->ShowWindow(bShow ? SW_HIDE : SW_SHOW);
362 GetDlgItem(IDC_SKIPPERLABEL)->ShowWindow(bShow ? SW_HIDE : SW_SHOW);
363 m_btnGraphBar.ShowWindow(bShow ? SW_HIDE : SW_SHOW);
364 m_btnGraphBarStacked.ShowWindow(bShow ? SW_HIDE : SW_SHOW);
365 m_btnGraphLine.ShowWindow(bShow ? SW_HIDE : SW_SHOW);
366 m_btnGraphLineStacked.ShowWindow(bShow ? SW_HIDE : SW_SHOW);
367 m_btnGraphPie.ShowWindow(bShow ? SW_HIDE : SW_SHOW);
370 void CStatGraphDlg::UpdateWeekCount()
372 // Sanity check
373 if (m_parDates.IsEmpty())
374 return;
376 // Already updated? No need to do it again.
377 if (m_nWeeks >= 0)
378 return;
380 // Determine first and last date in dates array
381 __time64_t min_date = (__time64_t)m_parDates.GetAt(0);
382 __time64_t max_date = min_date;
383 INT_PTR count = m_parDates.GetCount();
384 for (INT_PTR i=0; i<count; ++i)
386 __time64_t d = (__time64_t)m_parDates.GetAt(i);
387 if (d < min_date) min_date = d;
388 else if (d > max_date) max_date = d;
391 // Store start date of the interval in the member variable m_minDate
392 m_minDate = min_date;
393 m_maxDate = max_date;
395 // How many weeks does the time period cover?
397 // Get time difference between start and end date
398 double secs = _difftime64(max_date, m_minDate);
400 m_nWeeks = (int)ceil(secs / (double) m_SecondsInWeek);
401 m_nDays = (int)ceil(secs / (double) m_SecondsInDay);
404 int CStatGraphDlg::GetCalendarWeek(const CTime& time)
406 // Note:
407 // the calculation of the calendar week is wrong if DST is in effect
408 // and the date to calculate the week for is in DST and within the range
409 // of the DST offset (e.g. one hour).
410 // For example, if DST starts on Sunday march 30 and the date to get the week for
411 // is Monday, march 31, 0:30:00, then the returned week is one week less than
412 // the real week.
413 // TODO: ?
414 // write a function
415 // getDSTOffset(const CTime& time)
416 // which returns the DST offset for a given time/date. Then we can use this offset
417 // to correct our GetDays() calculation to get the correct week again
418 // This of course won't work for 'history' dates, because Windows doesn't have
419 // that information (only Vista has such a function: GetTimeZoneInformationForYear() )
420 int iWeekOfYear = 0;
422 int iYear = time.GetYear();
423 int iFirstDayOfWeek = 0;
424 int iFirstWeekOfYear = 0;
425 TCHAR loc[2] = { 0 };
426 GetLocaleInfo(LOCALE_USER_DEFAULT, LOCALE_IFIRSTDAYOFWEEK, loc, _countof(loc));
427 iFirstDayOfWeek = int(loc[0]-'0');
428 GetLocaleInfo(LOCALE_USER_DEFAULT, LOCALE_IFIRSTWEEKOFYEAR, loc, _countof(loc));
429 iFirstWeekOfYear = int(loc[0]-'0');
430 CTime dDateFirstJanuary(iYear,1,1,0,0,0);
431 int iDayOfWeek = (dDateFirstJanuary.GetDayOfWeek()+5+iFirstDayOfWeek)%7;
433 // Select mode
434 // 0 Week containing 1/1 is the first week of that year.
435 // 1 First full week following 1/1 is the first week of that year.
436 // 2 First week containing at least four days is the first week of that year.
437 switch (iFirstWeekOfYear)
439 case 0:
441 // Week containing 1/1 is the first week of that year.
443 // check if this week reaches into the next year
444 dDateFirstJanuary = CTime(iYear+1,1,1,0,0,0);
446 // Get start of week
449 iDayOfWeek = (time.GetDayOfWeek()+5+iFirstDayOfWeek)%7;
451 catch (CAtlException)
454 CTime dStartOfWeek = time-CTimeSpan(iDayOfWeek,0,0,0);
456 // If this week spans over to 1/1 this is week 1
457 if (dStartOfWeek + CTimeSpan(6,0,0,0) >= dDateFirstJanuary)
459 // we are in the last week of the year that spans over 1/1
460 iWeekOfYear = 1;
462 else
464 // Get week day of 1/1
465 dDateFirstJanuary = CTime(iYear,1,1,0,0,0);
466 iDayOfWeek = (dDateFirstJanuary.GetDayOfWeek() +5 + iFirstDayOfWeek) % 7;
467 // Just count from 1/1
468 iWeekOfYear = (int)(((time-dDateFirstJanuary).GetDays() + iDayOfWeek) / 7) + 1;
471 break;
472 case 1:
474 // First full week following 1/1 is the first week of that year.
476 // If the 1.1 is the start of the week everything is ok
477 // else we need the next week is the correct result
478 iWeekOfYear =
479 (int)(((time-dDateFirstJanuary).GetDays() + iDayOfWeek) / 7) +
480 (iDayOfWeek==0 ? 1:0);
482 // If we are in week 0 we are in the first not full week
483 // calculate from the last year
484 if (iWeekOfYear==0)
486 // Special case: we are in the week of 1.1 but 1.1. is not on the
487 // start of week. Calculate based on the last year
488 dDateFirstJanuary = CTime(iYear-1,1,1,0,0,0);
489 iDayOfWeek =
490 (dDateFirstJanuary.GetDayOfWeek()+5+iFirstDayOfWeek)%7;
491 // and we correct this in the same we we done this before but
492 // the result is now 52 or 53 and not 0
493 iWeekOfYear =
494 (int)(((time-dDateFirstJanuary).GetDays()+iDayOfWeek) / 7) +
495 (iDayOfWeek<=3 ? 1:0);
498 break;
499 case 2:
501 // First week containing at least four days is the first week of that year.
503 // Each year can start with any day of the week. But our
504 // weeks always start with Monday. So we add the day of week
505 // before calculation of the final week of year.
506 // Rule: is the 1.1 a Mo,Tu,We,Th than the week starts on the 1.1 with
507 // week==1, else a week later, so we add one for all those days if
508 // day is less <=3 Mo,Tu,We,Th. Otherwise 1.1 is in the last week of the
509 // previous year
510 iWeekOfYear =
511 (int)(((time-dDateFirstJanuary).GetDays()+iDayOfWeek) / 7) +
512 (iDayOfWeek<=3 ? 1:0);
514 // special cases
515 if (iWeekOfYear==0)
517 // special case week 0. We got a day before the 1.1, 2.1 or 3.1, were the
518 // 1.1. is not a Mo, Tu, We, Th. So the week 1 does not start with the 1.1.
519 // So we calculate the week according to the 1.1 of the year before
521 dDateFirstJanuary = CTime(iYear-1,1,1,0,0,0);
522 iDayOfWeek =
523 (dDateFirstJanuary.GetDayOfWeek()+5+iFirstDayOfWeek)%7;
524 // and we correct this in the same we we done this before but the result
525 // is now 52 or 53 and not 0
526 iWeekOfYear =
527 (int)(((time-dDateFirstJanuary).GetDays()+iDayOfWeek) / 7) +
528 (iDayOfWeek<=3 ? 1:0);
530 else if (iWeekOfYear==53)
532 // special case week 53. Either we got the correct week 53 or we just got the
533 // week 1 of the next year. So is the 1.1.(year+1) also a Mo, Tu, We, Th than
534 // we already have the week 1, otherwise week 53 is correct
536 dDateFirstJanuary = CTime(iYear+1,1,1,0,0,0);
537 iDayOfWeek =
538 (dDateFirstJanuary.GetDayOfWeek()+5+iFirstDayOfWeek)%7;
539 // 1.1. in week 1 or week 53?
540 iWeekOfYear = iDayOfWeek<=3 ? 1:53;
543 break;
544 default:
545 ASSERT(FALSE);
546 break;
548 // return result
549 return iWeekOfYear;
552 class CDateSorter
554 public:
555 class CCommitPointer
557 public:
558 CCommitPointer()
559 : m_cont(nullptr)
560 , m_place(0)
561 , m_Date(0)
562 , m_Changes(0)
563 , m_lineInc(0)
564 , m_lineDec(0)
565 , m_lineNew(0)
566 , m_lineDel(0)
568 CCommitPointer(const CCommitPointer& P_Right)
569 : m_cont(nullptr)
570 , m_place(0)
571 , m_Date(0)
572 , m_Changes(0)
573 , m_lineInc(0)
574 , m_lineDec(0)
575 , m_lineNew(0)
576 , m_lineDel(0)
578 *this = P_Right;
581 CCommitPointer& operator = (const CCommitPointer& P_Right)
583 if(IsPointer())
585 (*m_cont->m_parDates)[m_place] = P_Right.GetDate();
586 (*m_cont->m_parFileChanges)[m_place] = P_Right.GetChanges();
587 (*m_cont->m_parAuthors)[m_place] = P_Right.GetAuthor();
588 (*m_cont->m_lineInc)[m_place] = P_Right.GetLineInc();
589 (*m_cont->m_lineDec)[m_place] = P_Right.GetLineDec();
590 (*m_cont->m_lineNew)[m_place] = P_Right.GetLineNew();
591 (*m_cont->m_lineDel)[m_place] = P_Right.GetLineDel();
593 else
595 m_Date = P_Right.GetDate();
596 m_Changes = P_Right.GetChanges();
597 m_csAuthor = P_Right.GetAuthor();
598 m_lineInc = P_Right.GetLineInc();
599 m_lineDec = P_Right.GetLineDec();
600 m_lineNew = P_Right.GetLineNew();
601 m_lineDel = P_Right.GetLineDel();
603 return *this;
606 void Clone(const CCommitPointer& P_Right)
608 m_cont = P_Right.m_cont;
609 m_place = P_Right.m_place;
610 m_Date = P_Right.m_Date;
611 m_Changes = P_Right.m_Changes;
612 m_csAuthor = P_Right.m_csAuthor;
615 DWORD GetDate() const {return IsPointer() ? (*m_cont->m_parDates)[m_place] : m_Date;}
616 DWORD GetChanges() const {return IsPointer() ? (*m_cont->m_parFileChanges)[m_place] : m_Changes;}
617 DWORD GetLineInc() const {return IsPointer() ? (*m_cont->m_lineInc)[m_place] : m_lineInc;}
618 DWORD GetLineDec() const {return IsPointer() ? (*m_cont->m_lineDec)[m_place] : m_lineDec;}
619 DWORD GetLineNew() const {return IsPointer() ? (*m_cont->m_lineNew)[m_place] : m_lineNew;}
620 DWORD GetLineDel() const {return IsPointer() ? (*m_cont->m_lineDel)[m_place] : m_lineDel;}
621 CString GetAuthor() const {return IsPointer() ? (*m_cont->m_parAuthors)[m_place] : m_csAuthor;}
623 bool IsPointer() const { return m_cont != nullptr; }
624 //When pointer
625 CDateSorter* m_cont;
626 int m_place;
628 //When element
629 DWORD m_Date;
630 DWORD m_Changes;
631 DWORD m_lineInc;
632 DWORD m_lineDec;
633 DWORD m_lineNew;
634 DWORD m_lineDel;
635 CString m_csAuthor;
638 class iterator : public std::iterator<std::random_access_iterator_tag, CCommitPointer>
640 public:
641 CCommitPointer m_ptr;
643 iterator(){}
644 iterator(const iterator& P_Right){*this = P_Right;}
645 iterator& operator=(const iterator& P_Right)
647 m_ptr.Clone(P_Right.m_ptr);
648 return *this;
651 CCommitPointer& operator*(){return m_ptr;}
652 CCommitPointer* operator->(){return &m_ptr;}
653 const CCommitPointer& operator*()const{return m_ptr;}
654 const CCommitPointer* operator->()const{return &m_ptr;}
656 iterator& operator+=(size_t P_iOffset){m_ptr.m_place += (int)P_iOffset;return *this;}
657 iterator& operator-=(size_t P_iOffset){m_ptr.m_place -= (int)P_iOffset;return *this;}
658 iterator operator+(size_t P_iOffset)const{iterator it(*this); it += P_iOffset;return it;}
659 iterator operator-(size_t P_iOffset)const{iterator it(*this); it -= P_iOffset;return it;}
661 iterator& operator++(){++m_ptr.m_place;return *this;}
662 iterator& operator--(){--m_ptr.m_place;return *this;}
663 iterator operator++(int){iterator it(*this);++*this;return it;}
664 iterator operator--(int){iterator it(*this);--*this;return it;}
666 size_t operator-(const iterator& P_itRight)const{return m_ptr.m_place - P_itRight->m_place;}
668 bool operator<(const iterator& P_itRight)const{return m_ptr.m_place < P_itRight->m_place;}
669 bool operator!=(const iterator& P_itRight)const{return m_ptr.m_place != P_itRight->m_place;}
670 bool operator==(const iterator& P_itRight)const{return m_ptr.m_place == P_itRight->m_place;}
671 bool operator>(const iterator& P_itRight)const{return m_ptr.m_place > P_itRight->m_place;}
673 iterator begin()
675 iterator it;
676 it->m_place = 0;
677 it->m_cont = this;
678 return it;
680 iterator end()
682 iterator it;
683 it->m_place = (int)m_parDates->GetCount();
684 it->m_cont = this;
685 return it;
688 CDWordArray * m_parDates;
689 CDWordArray * m_parFileChanges;
690 CDWordArray * m_lineInc;
691 CDWordArray * m_lineDec;
692 CDWordArray * m_lineNew;
693 CDWordArray * m_lineDel;
694 CStringArray * m_parAuthors;
697 class CDateSorterLess
699 public:
700 bool operator () (const CDateSorter::CCommitPointer& P_Left, const CDateSorter::CCommitPointer& P_Right) const
702 return P_Left.GetDate() > P_Right.GetDate(); //Last date first
706 int CStatGraphDlg::GatherData(BOOL fetchdiff, BOOL keepFetchedData)
708 m_parAuthors.RemoveAll();
709 m_parDates.RemoveAll();
710 if (m_parFileChanges2.IsEmpty()) // Fixes issue #1948
711 keepFetchedData = FALSE;
712 if (!keepFetchedData)
714 m_parFileChanges.RemoveAll();
715 m_lineInc.RemoveAll();
716 m_lineDec.RemoveAll();
717 m_lineDel.RemoveAll();
718 m_lineNew.RemoveAll();
720 else
722 m_parFileChanges.Copy(m_parFileChanges2);
723 m_lineNew.Copy(m_lineNew2);
724 m_lineDel.Copy(m_lineDel2);
725 m_lineInc.Copy(m_lineInc2);
726 m_lineDec.Copy(m_lineDec2);
729 CSysProgressDlg progress;
730 if (fetchdiff)
732 progress.SetTitle(CString(MAKEINTRESOURCE(IDS_PROGS_TITLE_GATHERSTATISTICS)));
733 progress.FormatNonPathLine(1, IDS_PROC_STATISTICS_DIFF);
734 progress.SetAnimation(IDR_MOVEANI);
735 progress.SetTime(true);
736 progress.ShowModeless(this);
739 // create arrays which are aware of the current filter
740 ULONGLONG starttime = GetTickCount64();
742 GIT_MAILMAP mailmap = nullptr;
743 git_read_mailmap(&mailmap);
744 for (size_t i = 0; i < m_ShowList.size(); ++i)
746 GitRevLoglist* pLogEntry = m_ShowList.SafeGetAt(i);
747 int inc, dec, incnewfile, decdeletedfile, files;
748 inc = dec = incnewfile = decdeletedfile = files= 0;
750 // do not take working dir changes into statistics
751 if (pLogEntry->m_CommitHash.IsEmpty()) {
752 continue;
755 CString strAuthor = pLogEntry->GetAuthorName();
756 if (strAuthor.IsEmpty())
757 strAuthor.LoadString(IDS_STATGRAPH_EMPTYAUTHOR);
758 if (mailmap)
760 CStringA email2A = CUnicodeUtils::GetUTF8(pLogEntry->GetAuthorEmail());
761 struct payload_struct { GitRev* rev; const char *authorName; };
762 payload_struct payload = { pLogEntry, nullptr };
763 const char* author1 = nullptr;
764 git_lookup_mailmap(mailmap, nullptr, &author1, email2A, &payload,
765 [](void* payload) -> const char* { return reinterpret_cast<payload_struct*>(payload)->authorName = _strdup(CUnicodeUtils::GetUTF8(reinterpret_cast<payload_struct*>(payload)->rev->GetAuthorName())); });
766 free((void *)payload.authorName);
767 if (author1)
768 strAuthor = CUnicodeUtils::GetUnicode(author1);
770 m_parAuthors.Add(strAuthor);
771 m_parDates.Add((DWORD)pLogEntry->GetCommitterDate().GetTime());
773 if (fetchdiff && (pLogEntry->m_ParentHash.size() <= 1))
775 CTGitPathList& list = pLogEntry->GetFiles(nullptr);
776 files = list.GetCount();
778 for (int j = 0; j < files; j++)
780 if (list[j].m_Action & CTGitPath::LOGACTIONS_DELETED)
781 decdeletedfile += _wtol(list[j].m_StatDel);
782 else if(list[j].m_Action & CTGitPath::LOGACTIONS_ADDED)
783 incnewfile += _wtol(list[j].m_StatAdd);
784 else
786 inc += _wtol(list[j].m_StatAdd);
787 dec += _wtol(list[j].m_StatDel);
790 if (progress.HasUserCancelled())
792 git_free_mailmap(mailmap);
793 return -1;
797 if (!keepFetchedData)
799 m_parFileChanges.Add(files);
800 m_lineInc.Add(inc);
801 m_lineDec.Add(dec);
802 m_lineDel.Add(decdeletedfile);
803 m_lineNew.Add(incnewfile);
806 if (progress.IsVisible() && (GetTickCount64() - starttime > 100UL))
808 progress.FormatNonPathLine(2, L"%s: %s", (LPCTSTR)pLogEntry->m_CommitHash.ToString().Left(g_Git.GetShortHASHLength()), (LPCTSTR)pLogEntry->GetSubject());
809 progress.SetProgress64(i, m_ShowList.size());
810 starttime = GetTickCount64();
814 git_free_mailmap(mailmap);
816 if (fetchdiff)
818 m_parFileChanges2.Copy(m_parFileChanges);
819 m_lineNew2.Copy(m_lineNew);
820 m_lineDel2.Copy(m_lineDel);
821 m_lineInc2.Copy(m_lineInc);
822 m_lineDec2.Copy(m_lineDec);
825 CDateSorter W_Sorter;
826 W_Sorter.m_parAuthors = &m_parAuthors;
827 W_Sorter.m_parDates = &m_parDates;
828 W_Sorter.m_parFileChanges = &m_parFileChanges;
829 W_Sorter.m_lineNew = &m_lineNew;
830 W_Sorter.m_lineDel = &m_lineDel;
831 W_Sorter.m_lineInc = &m_lineInc;
832 W_Sorter.m_lineDec = &m_lineDec;
834 std::sort(W_Sorter.begin(), W_Sorter.end(), CDateSorterLess());
836 m_nTotalCommits = m_parAuthors.GetCount();
837 m_nTotalFileChanges = 0;
839 // Update m_nWeeks and m_minDate
840 UpdateWeekCount();
842 // Now create a mapping that holds the information per week.
843 m_commitsPerUnitAndAuthor.clear();
844 m_filechangesPerUnitAndAuthor.clear();
845 m_commitsPerAuthor.clear();
846 m_PercentageOfAuthorship.clear();
847 m_LinesWPerUnitAndAuthor.clear();
848 m_LinesWOPerUnitAndAuthor.clear();
850 int interval = 0;
851 __time64_t d = (__time64_t)m_parDates.GetAt(0);
852 int nLastUnit = GetUnit(d);
853 double AllContributionAuthor = 0;
855 m_nTotalLinesInc = m_nTotalLinesDec = m_nTotalLinesNew = m_nTotalLinesDel =0;
857 // Now loop over all weeks and gather the info
858 for (LONG i=0; i<m_nTotalCommits; ++i)
860 // Find the interval number
861 __time64_t commitDate = (__time64_t)m_parDates.GetAt(i);
862 int u = GetUnit(commitDate);
863 if (nLastUnit != u)
864 interval++;
865 nLastUnit = u;
866 // Find the authors name
867 CString sAuth = m_parAuthors.GetAt(i);
868 if (!m_bAuthorsCaseSensitive)
869 sAuth = sAuth.MakeLower();
870 tstring author = tstring(sAuth);
871 // Increase total commit count for this author
872 m_commitsPerAuthor[author]++;
873 // Increase the commit count for this author in this week
874 m_commitsPerUnitAndAuthor[interval][author]++;
876 m_LinesWPerUnitAndAuthor[interval][author] += m_lineInc.GetAt(i) + m_lineDec.GetAt(i) + m_lineNew.GetAt(i) + + m_lineDel.GetAt(i);
877 m_LinesWOPerUnitAndAuthor[interval][author] += m_lineInc.GetAt(i) + m_lineDec.GetAt(i);
879 CTime t = m_parDates.GetAt(i);
880 m_unitNames[interval] = GetUnitLabel(nLastUnit, t);
881 // Increase the file change count for this author in this week
882 int fileChanges = m_parFileChanges.GetAt(i);
883 m_filechangesPerUnitAndAuthor[interval][author] += fileChanges;
884 m_nTotalFileChanges += fileChanges;
886 //calculate Contribution Author
887 double contributionAuthor = CoeffContribution((int)m_nTotalCommits - i -1) * (fileChanges ? fileChanges : 1);
888 AllContributionAuthor += contributionAuthor;
889 m_PercentageOfAuthorship[author] += contributionAuthor;
891 m_nTotalLinesInc += m_lineInc.GetAt(i);
892 m_nTotalLinesDec += m_lineDec.GetAt(i);
893 m_nTotalLinesNew += m_lineNew.GetAt(i);
894 m_nTotalLinesDel += m_lineDel.GetAt(i);
897 // Find first and last interval number.
898 if (!m_commitsPerUnitAndAuthor.empty())
900 IntervalDataMap::iterator interval_it = m_commitsPerUnitAndAuthor.begin();
901 m_firstInterval = interval_it->first;
902 interval_it = m_commitsPerUnitAndAuthor.end();
903 --interval_it;
904 m_lastInterval = interval_it->first;
905 // Sanity check - if m_lastInterval is too large it could freeze TSVN and take up all memory!!!
906 assert(m_lastInterval >= 0 && m_lastInterval < 10000);
908 else
910 m_firstInterval = 0;
911 m_lastInterval = -1;
914 // Get a list of authors names
915 LoadListOfAuthors(m_commitsPerAuthor);
917 // Calculate percent of Contribution Authors
918 for (std::list<tstring>::iterator it = m_authorNames.begin(); it != m_authorNames.end(); ++it)
920 m_PercentageOfAuthorship[*it] = (m_PercentageOfAuthorship[*it] *100)/ AllContributionAuthor;
923 // All done, now the statistics pages can retrieve the data and
924 // extract the information to be shown.
926 return 0;
929 void CStatGraphDlg::FilterSkippedAuthors(std::list<tstring>& included_authors,
930 std::list<tstring>& skipped_authors)
932 included_authors.clear();
933 skipped_authors.clear();
935 unsigned int included_authors_count = m_Skipper.GetPos();
936 // if we only leave out one author, still include him with his name
937 if (included_authors_count + 1 == m_authorNames.size())
938 ++included_authors_count;
940 // add the included authors first
941 std::list<tstring>::iterator author_it = m_authorNames.begin();
942 while (included_authors_count > 0 && author_it != m_authorNames.end())
944 // Add him/her to the included list
945 included_authors.push_back(*author_it);
946 ++author_it;
947 --included_authors_count;
950 // If we haven't reached the end yet, copy all remaining authors into the
951 // skipped author list.
952 std::copy(author_it, m_authorNames.end(), std::back_inserter(skipped_authors) );
954 // Sort authors alphabetically if user wants that.
955 if (!m_bSortByCommitCount)
956 included_authors.sort();
959 bool CStatGraphDlg::PreViewStat(bool fShowLabels)
961 if (m_parAuthors.IsEmpty() || m_parDates.IsEmpty() || m_parFileChanges.IsEmpty())
962 return false;
963 ShowLabels(fShowLabels);
965 //If view graphic
966 if (!fShowLabels) ClearGraph();
968 // This function relies on a previous call of GatherData().
969 // This can be detected by checking the week count.
970 // If the week count is equal to -1, it hasn't been called before.
971 if (m_nWeeks == -1)
972 GatherData(FALSE, TRUE);
973 // If week count is still -1, something bad has happened, probably invalid data!
974 if (m_nWeeks == -1)
975 return false;
977 return true;
980 MyGraphSeries *CStatGraphDlg::PreViewGraph(__in UINT GraphTitle, __in UINT YAxisLabel, __in UINT XAxisLabel /*= nullptr*/)
982 if(!PreViewStat(false))
983 return nullptr;
985 // We need at least one author
986 if (m_authorNames.empty())
987 return nullptr;
989 // Add a single series to the chart
990 MyGraphSeries * graphData = new MyGraphSeries();
991 m_graph.AddSeries(*graphData);
992 m_graphDataArray.Add(graphData);
994 // Set up the graph.
995 CString temp;
996 UpdateData();
997 m_graph.SetGraphType(m_GraphType, m_bStacked);
998 temp.LoadString(YAxisLabel);
999 m_graph.SetYAxisLabel(temp);
1000 temp.LoadString(XAxisLabel);
1001 m_graph.SetXAxisLabel(temp);
1002 temp.LoadString(GraphTitle);
1003 m_graph.SetGraphTitle(temp);
1005 return graphData;
1008 void CStatGraphDlg::ShowPercentageOfAuthorship()
1010 // Set up the graph.
1011 MyGraphSeries * graphData = PreViewGraph(IDS_STATGRAPH_PERCENTAGE_OF_AUTHORSHIP,
1012 IDS_STATGRAPH_PERCENTAGE_OF_AUTHORSHIPY,
1013 IDS_STATGRAPH_COMMITSBYAUTHORMOREX);
1014 if (!graphData) return;
1016 // Find out which authors are to be shown and which are to be skipped.
1017 std::list<tstring> authors;
1018 std::list<tstring> others;
1021 FilterSkippedAuthors(authors, others);
1023 // Loop over all authors in the authors list and
1024 // add them to the graph.
1026 if (!authors.empty())
1028 for (std::list<tstring>::iterator it = authors.begin(); it != authors.end(); ++it)
1030 int group = m_graph.AppendGroup(it->c_str());
1031 graphData->SetData(group, RollPercentageOfAuthorship(m_PercentageOfAuthorship[*it]));
1035 // If we have other authors, count them and their commits.
1036 if (!others.empty())
1037 DrawOthers(others, graphData, m_PercentageOfAuthorship);
1039 // Paint the graph now that we're through.
1040 m_graph.Invalidate();
1043 void CStatGraphDlg::ShowCommitsByAuthor()
1045 // Set up the graph.
1046 MyGraphSeries * graphData = PreViewGraph(IDS_STATGRAPH_COMMITSBYAUTHOR,
1047 IDS_STATGRAPH_COMMITSBYAUTHORY,
1048 IDS_STATGRAPH_COMMITSBYAUTHORX);
1049 if (!graphData) return;
1051 // Find out which authors are to be shown and which are to be skipped.
1052 std::list<tstring> authors;
1053 std::list<tstring> others;
1054 FilterSkippedAuthors(authors, others);
1056 // Loop over all authors in the authors list and
1057 // add them to the graph.
1059 if (!authors.empty())
1061 for (std::list<tstring>::iterator it = authors.begin(); it != authors.end(); ++it)
1063 int group = m_graph.AppendGroup(it->c_str());
1064 graphData->SetData(group, m_commitsPerAuthor[*it]);
1068 // If we have other authors, count them and their commits.
1069 if (!others.empty())
1070 DrawOthers(others, graphData, m_commitsPerAuthor);
1072 // Paint the graph now that we're through.
1073 m_graph.Invalidate();
1076 void CStatGraphDlg::ShowByDate(int stringx, int title, IntervalDataMap &data)
1078 if(!PreViewStat(false)) return;
1080 // We need at least one author
1081 if (m_authorNames.empty()) return;
1083 // Set up the graph.
1084 CString temp;
1085 UpdateData();
1086 m_graph.SetGraphType(m_GraphType, m_bStacked);
1087 temp.LoadString(stringx);
1088 m_graph.SetYAxisLabel(temp);
1089 temp.LoadString(title);
1090 m_graph.SetGraphTitle(temp);
1092 m_graph.SetXAxisLabel(GetUnitString());
1094 // Find out which authors are to be shown and which are to be skipped.
1095 std::list<tstring> authors;
1096 std::list<tstring> others;
1097 FilterSkippedAuthors(authors, others);
1099 // Add a graph series for each author.
1100 AuthorDataMap authorGraphMap;
1101 for (std::list<tstring>::iterator it = authors.begin(); it != authors.end(); ++it)
1102 authorGraphMap[*it] = m_graph.AppendGroup(it->c_str());
1103 // If we have skipped authors, add a graph series for all those.
1104 CString sOthers(MAKEINTRESOURCE(IDS_STATGRAPH_OTHERGROUP));
1105 tstring othersName;
1106 if (!others.empty())
1108 sOthers.AppendFormat(L" (%Iu)", others.size());
1109 othersName = (LPCWSTR)sOthers;
1110 authorGraphMap[othersName] = m_graph.AppendGroup(sOthers);
1113 // Mapping to collect commit counts in each interval
1114 AuthorDataMap commitCount;
1116 // Loop over all intervals/weeks and collect filtered data.
1117 // Sum up data in each interval until the time unit changes.
1118 for (int i=m_lastInterval; i>=m_firstInterval; --i)
1120 // Collect data for authors listed by name.
1121 if (!authors.empty())
1123 for (std::list<tstring>::iterator it = authors.begin(); it != authors.end(); ++it)
1125 // Do we have some data for the current author in the current interval?
1126 AuthorDataMap::const_iterator data_it = data[i].find(*it);
1127 if (data_it == data[i].end())
1128 continue;
1129 commitCount[*it] += data_it->second;
1132 // Collect data for all skipped authors.
1133 if (!others.empty())
1135 for (std::list<tstring>::iterator it = others.begin(); it != others.end(); ++it)
1137 // Do we have some data for the author in the current interval?
1138 AuthorDataMap::const_iterator data_it = data[i].find(*it);
1139 if (data_it == data[i].end())
1140 continue;
1141 commitCount[othersName] += data_it->second;
1145 // Create a new data series for this unit/interval.
1146 MyGraphSeries * graphData = new MyGraphSeries();
1147 // Loop over all created graphs and set the corresponding data.
1148 if (!authorGraphMap.empty())
1150 for (AuthorDataMap::const_iterator it = authorGraphMap.begin(); it != authorGraphMap.end(); ++it)
1152 graphData->SetData(it->second, commitCount[it->first]);
1155 graphData->SetLabel(m_unitNames[i].c_str());
1156 m_graph.AddSeries(*graphData);
1157 m_graphDataArray.Add(graphData);
1159 // Reset commit count mapping.
1160 commitCount.clear();
1163 // Paint the graph now that we're through.
1164 m_graph.Invalidate();
1167 void CStatGraphDlg::ShowStats()
1169 if(!PreViewStat(true)) return;
1171 // Now we can use the gathered data to update the stats dialog.
1172 size_t nAuthors = m_authorNames.size();
1174 // Find most and least active author names.
1175 tstring mostActiveAuthor;
1176 tstring leastActiveAuthor;
1177 if (nAuthors > 0)
1179 mostActiveAuthor = m_authorNames.front();
1180 leastActiveAuthor = m_authorNames.back();
1183 // Obtain the statistics for the table.
1184 long nCommitsMin = -1;
1185 long nCommitsMax = -1;
1186 long nFileChangesMin = -1;
1187 long nFileChangesMax = -1;
1189 long nMostActiveMaxCommits = -1;
1190 long nMostActiveMinCommits = -1;
1191 long nLeastActiveMaxCommits = -1;
1192 long nLeastActiveMinCommits = -1;
1194 // Loop over all intervals and find min and max values for commit count and file changes.
1195 // Also store the stats for the most and least active authors.
1196 for (int i=m_firstInterval; i<=m_lastInterval; ++i)
1198 // Loop over all commits in this interval and count the number of commits by all authors.
1199 int commitCount = 0;
1200 AuthorDataMap::iterator commit_endit = m_commitsPerUnitAndAuthor[i].end();
1201 for (AuthorDataMap::iterator commit_it = m_commitsPerUnitAndAuthor[i].begin();
1202 commit_it != commit_endit; ++commit_it)
1204 commitCount += commit_it->second;
1206 if (nCommitsMin == -1 || commitCount < nCommitsMin)
1207 nCommitsMin = commitCount;
1208 if (nCommitsMax == -1 || commitCount > nCommitsMax)
1209 nCommitsMax = commitCount;
1211 // Loop over all commits in this interval and count the number of file changes by all authors.
1212 int fileChangeCount = 0;
1213 AuthorDataMap::iterator filechange_endit = m_filechangesPerUnitAndAuthor[i].end();
1214 for (AuthorDataMap::iterator filechange_it = m_filechangesPerUnitAndAuthor[i].begin();
1215 filechange_it != filechange_endit; ++filechange_it)
1217 fileChangeCount += filechange_it->second;
1219 if (nFileChangesMin == -1 || fileChangeCount < nFileChangesMin)
1220 nFileChangesMin = fileChangeCount;
1221 if (nFileChangesMax == -1 || fileChangeCount > nFileChangesMax)
1222 nFileChangesMax = fileChangeCount;
1224 // also get min/max data for most and least active authors
1225 if (nAuthors > 0)
1227 // check if author is present in this interval
1228 AuthorDataMap::iterator author_it = m_commitsPerUnitAndAuthor[i].find(mostActiveAuthor);
1229 long authorCommits;
1230 if (author_it == m_commitsPerUnitAndAuthor[i].end())
1231 authorCommits = 0;
1232 else
1233 authorCommits = author_it->second;
1234 if (nMostActiveMaxCommits == -1 || authorCommits > nMostActiveMaxCommits)
1235 nMostActiveMaxCommits = authorCommits;
1236 if (nMostActiveMinCommits == -1 || authorCommits < nMostActiveMinCommits)
1237 nMostActiveMinCommits = authorCommits;
1239 author_it = m_commitsPerUnitAndAuthor[i].find(leastActiveAuthor);
1240 if (author_it == m_commitsPerUnitAndAuthor[i].end())
1241 authorCommits = 0;
1242 else
1243 authorCommits = author_it->second;
1244 if (nLeastActiveMaxCommits == -1 || authorCommits > nLeastActiveMaxCommits)
1245 nLeastActiveMaxCommits = authorCommits;
1246 if (nLeastActiveMinCommits == -1 || authorCommits < nLeastActiveMinCommits)
1247 nLeastActiveMinCommits = authorCommits;
1250 if (nMostActiveMaxCommits == -1) nMostActiveMaxCommits = 0;
1251 if (nMostActiveMinCommits == -1) nMostActiveMinCommits = 0;
1252 if (nLeastActiveMaxCommits == -1) nLeastActiveMaxCommits = 0;
1253 if (nLeastActiveMinCommits == -1) nLeastActiveMinCommits = 0;
1255 int nWeeks = m_lastInterval-m_firstInterval;
1256 if (nWeeks == 0)
1257 nWeeks = 1;
1258 // Adjust the labels with the unit type (week, month, ...)
1259 CString labelText;
1260 labelText.Format(IDS_STATGRAPH_NUMBEROFUNIT, (LPCTSTR)GetUnitString());
1261 SetDlgItemText(IDC_NUMWEEK, labelText);
1262 labelText.Format(IDS_STATGRAPH_COMMITSBYUNIT, (LPCTSTR)GetUnitString());
1263 SetDlgItemText(IDC_COMMITSEACHWEEK, labelText);
1264 labelText.Format(IDS_STATGRAPH_FILECHANGESBYUNIT, (LPCTSTR)GetUnitString());
1265 SetDlgItemText(IDC_FILECHANGESEACHWEEK, (LPCTSTR)labelText);
1266 // We have now all data we want and we can fill in the labels...
1267 CString number;
1268 number.Format(L"%d", nWeeks);
1269 SetDlgItemText(IDC_NUMWEEKVALUE, number);
1270 number.Format(L"%Iu", nAuthors);
1271 SetDlgItemText(IDC_NUMAUTHORVALUE, number);
1272 number.Format(L"%Id", m_nTotalCommits);
1273 SetDlgItemText(IDC_NUMCOMMITSVALUE, number);
1274 number.Format(L"%ld", m_nTotalFileChanges);
1275 if (m_bDiffFetched)
1276 SetDlgItemText(IDC_NUMFILECHANGESVALUE, number);
1278 number.Format(L"%Id", m_parAuthors.GetCount() / nWeeks);
1279 SetDlgItemText(IDC_COMMITSEACHWEEKAVG, number);
1280 number.Format(L"%ld", nCommitsMax);
1281 SetDlgItemText(IDC_COMMITSEACHWEEKMAX, number);
1282 number.Format(L"%ld", nCommitsMin);
1283 SetDlgItemText(IDC_COMMITSEACHWEEKMIN, number);
1285 number.Format(L"%ld", m_nTotalFileChanges / nWeeks);
1286 //SetDlgItemText(IDC_FILECHANGESEACHWEEKAVG, number);
1287 number.Format(L"%ld", nFileChangesMax);
1288 //SetDlgItemText(IDC_FILECHANGESEACHWEEKMAX, number);
1289 number.Format(L"%ld", nFileChangesMin);
1290 //SetDlgItemText(IDC_FILECHANGESEACHWEEKMIN, number);
1292 number.Format(L"%ld (%ld (+) %ld (-))", m_nTotalLinesInc + m_nTotalLinesDec, m_nTotalLinesInc, m_nTotalLinesDec);
1293 if (m_bDiffFetched)
1294 SetDlgItemText(IDC_TOTAL_LINE_WITHOUT_NEW_DEL_VALUE, number);
1295 number.Format(L"%ld (%ld (+) %ld (-))", m_nTotalLinesInc + m_nTotalLinesDec + m_nTotalLinesNew + m_nTotalLinesDel,
1296 m_nTotalLinesInc + m_nTotalLinesNew, m_nTotalLinesDec + m_nTotalLinesDel);
1297 if (m_bDiffFetched)
1298 SetDlgItemText(IDC_TOTAL_LINE_WITH_NEW_DEL_VALUE, number);
1300 if (nAuthors == 0)
1302 SetDlgItemText(IDC_MOSTACTIVEAUTHORNAME, L"");
1303 SetDlgItemText(IDC_MOSTACTIVEAUTHORAVG, L"0");
1304 SetDlgItemText(IDC_MOSTACTIVEAUTHORMAX, L"0");
1305 SetDlgItemText(IDC_MOSTACTIVEAUTHORMIN, L"0");
1306 SetDlgItemText(IDC_LEASTACTIVEAUTHORNAME, L"");
1307 SetDlgItemText(IDC_LEASTACTIVEAUTHORAVG, L"0");
1308 SetDlgItemText(IDC_LEASTACTIVEAUTHORMAX, L"0");
1309 SetDlgItemText(IDC_LEASTACTIVEAUTHORMIN, L"0");
1311 else
1313 SetDlgItemText(IDC_MOSTACTIVEAUTHORNAME, mostActiveAuthor.c_str());
1314 number.Format(L"%ld", m_commitsPerAuthor[mostActiveAuthor] / nWeeks);
1315 SetDlgItemText(IDC_MOSTACTIVEAUTHORAVG, number);
1316 number.Format(L"%ld", nMostActiveMaxCommits);
1317 SetDlgItemText(IDC_MOSTACTIVEAUTHORMAX, number);
1318 number.Format(L"%ld", nMostActiveMinCommits);
1319 SetDlgItemText(IDC_MOSTACTIVEAUTHORMIN, number);
1321 SetDlgItemText(IDC_LEASTACTIVEAUTHORNAME, leastActiveAuthor.c_str());
1322 number.Format(L"%ld", m_commitsPerAuthor[leastActiveAuthor] / nWeeks);
1323 SetDlgItemText(IDC_LEASTACTIVEAUTHORAVG, number);
1324 number.Format(L"%ld", nLeastActiveMaxCommits);
1325 SetDlgItemText(IDC_LEASTACTIVEAUTHORMAX, number);
1326 number.Format(L"%ld", nLeastActiveMinCommits);
1327 SetDlgItemText(IDC_LEASTACTIVEAUTHORMIN, number);
1331 int CStatGraphDlg::RollPercentageOfAuthorship(double it)
1332 { return (int)it + (it - (int)it >= 0.5);}
1334 void CStatGraphDlg::OnCbnSelchangeGraphcombo()
1336 UpdateData();
1338 Metrics useMetric = (Metrics) m_cGraphType.GetItemData(m_cGraphType.GetCurSel());
1339 switch (useMetric )
1341 case AllStat:
1342 case CommitsByDate:
1343 // by date
1344 m_btnGraphLine.EnableWindow(TRUE);
1345 m_btnGraphLineStacked.EnableWindow(TRUE);
1346 m_btnGraphPie.EnableWindow(TRUE);
1347 m_GraphType = MyGraph::Line;
1348 m_bStacked = false;
1349 break;
1350 case PercentageOfAuthorship:
1351 case CommitsByAuthor:
1352 // by author
1353 m_btnGraphLine.EnableWindow(FALSE);
1354 m_btnGraphLineStacked.EnableWindow(FALSE);
1355 m_btnGraphPie.EnableWindow(TRUE);
1356 m_GraphType = MyGraph::Bar;
1357 m_bStacked = false;
1358 break;
1360 RedrawGraph();
1364 int CStatGraphDlg::GetUnitCount()
1366 if (m_nDays < 8)
1367 return m_nDays;
1368 if (m_nWeeks < 15)
1369 return m_nWeeks;
1370 if (m_nWeeks < 80)
1371 return (m_nWeeks/4)+1;
1372 if (m_nWeeks < 320)
1373 return (m_nWeeks/13)+1; // quarters
1374 return (m_nWeeks/52)+1;
1377 int CStatGraphDlg::GetUnit(const CTime& time)
1379 if (m_nDays < 8)
1380 return time.GetMonth()*100 + time.GetDay(); // month*100+day as the unit
1381 if (m_nWeeks < 15)
1382 return GetCalendarWeek(time);
1383 if (m_nWeeks < 80)
1384 return time.GetMonth();
1385 if (m_nWeeks < 320)
1386 return ((time.GetMonth()-1)/3)+1; // quarters
1387 return time.GetYear();
1390 CStatGraphDlg::UnitType CStatGraphDlg::GetUnitType()
1392 if (m_nDays < 8)
1393 return Days;
1394 if (m_nWeeks < 15)
1395 return Weeks;
1396 if (m_nWeeks < 80)
1397 return Months;
1398 if (m_nWeeks < 320)
1399 return Quarters;
1400 return Years;
1403 CString CStatGraphDlg::GetUnitString()
1405 if (m_nDays < 8)
1406 return CString(MAKEINTRESOURCE(IDS_STATGRAPH_COMMITSBYDATEXDAY));
1407 if (m_nWeeks < 15)
1408 return CString(MAKEINTRESOURCE(IDS_STATGRAPH_COMMITSBYDATEXWEEK));
1409 if (m_nWeeks < 80)
1410 return CString(MAKEINTRESOURCE(IDS_STATGRAPH_COMMITSBYDATEXMONTH));
1411 if (m_nWeeks < 320)
1412 return CString(MAKEINTRESOURCE(IDS_STATGRAPH_COMMITSBYDATEXQUARTER));
1413 return CString(MAKEINTRESOURCE(IDS_STATGRAPH_COMMITSBYDATEXYEAR));
1416 CString CStatGraphDlg::GetUnitLabel(int unit, CTime &lasttime)
1418 CString temp;
1419 switch (GetUnitType())
1421 case Days:
1423 // month*100+day as the unit
1424 int day = unit % 100;
1425 int month = unit / 100;
1426 switch (m_langOrder)
1428 case 0: // month day year
1429 temp.Format(L"%d/%d/%.2d", month, day, lasttime.GetYear() % 100);
1430 break;
1431 case 1: // day month year
1432 default:
1433 temp.Format(L"%d/%d/%.2d", day, month, lasttime.GetYear() % 100);
1434 break;
1435 case 2: // year month day
1436 temp.Format(L"%.2d/%d/%d", lasttime.GetYear() % 100, month, day);
1437 break;
1440 break;
1441 case Weeks:
1443 int year = lasttime.GetYear();
1444 if ((unit == 1)&&(lasttime.GetMonth() == 12))
1445 year += 1;
1447 switch (m_langOrder)
1449 case 0: // month day year
1450 case 1: // day month year
1451 default:
1452 temp.Format(L"%d/%.2d", unit, year % 100);
1453 break;
1454 case 2: // year month day
1455 temp.Format(L"%.2d/%d", year % 100, unit);
1456 break;
1459 break;
1460 case Months:
1461 switch (m_langOrder)
1463 case 0: // month day year
1464 case 1: // day month year
1465 default:
1466 temp.Format(L"%d/%.2d", unit, lasttime.GetYear() % 100);
1467 break;
1468 case 2: // year month day
1469 temp.Format(L"%.2d/%d", lasttime.GetYear() % 100, unit);
1470 break;
1472 break;
1473 case Quarters:
1474 switch (m_langOrder)
1476 case 0: // month day year
1477 case 1: // day month year
1478 default:
1479 temp.Format(L"%d/%.2d", unit, lasttime.GetYear() % 100);
1480 break;
1481 case 2: // year month day
1482 temp.Format(L"%.2d/%d", lasttime.GetYear() % 100, unit);
1483 break;
1485 break;
1486 case Years:
1487 temp.Format(L"%d", unit);
1488 break;
1490 return temp;
1493 void CStatGraphDlg::OnHScroll(UINT nSBCode, UINT nPos, CScrollBar* pScrollBar)
1495 if (nSBCode == TB_THUMBTRACK)
1496 return CDialog::OnHScroll(nSBCode, nPos, pScrollBar);
1498 ShowSelectStat((Metrics) m_cGraphType.GetItemData(m_cGraphType.GetCurSel()));
1499 CDialog::OnHScroll(nSBCode, nPos, pScrollBar);
1502 void CStatGraphDlg::OnNeedText(NMHDR *pnmh, LRESULT * /*pResult*/)
1504 TOOLTIPTEXT* pttt = (TOOLTIPTEXT*) pnmh;
1505 if (pttt->hdr.idFrom == (UINT_PTR) m_Skipper.GetSafeHwnd())
1507 size_t included_authors_count = m_Skipper.GetPos();
1508 // if we only leave out one author, still include him with his name
1509 if (included_authors_count + 1 == m_authorNames.size())
1510 ++included_authors_count;
1512 // find the minimum number of commits that the shown authors have
1513 int min_commits = 0;
1514 included_authors_count = min(included_authors_count, m_authorNames.size());
1515 std::list<tstring>::iterator author_it = m_authorNames.begin();
1516 advance(author_it, included_authors_count);
1517 if (author_it != m_authorNames.begin())
1518 min_commits = m_commitsPerAuthor[ *(--author_it) ];
1520 CString string;
1521 int percentage = int(min_commits*100.0/(m_nTotalCommits ? m_nTotalCommits : 1));
1522 string.Format(IDS_STATGRAPH_AUTHORSLIDER_TT, m_Skipper.GetPos(), min_commits, percentage);
1523 StringCchCopy(pttt->szText, _countof(pttt->szText), (LPCTSTR) string);
1527 void CStatGraphDlg::AuthorsCaseSensitiveChanged()
1529 UpdateData(); // update checkbox state
1530 GatherData(FALSE, TRUE); // first regenerate the statistics data
1531 RedrawGraph(); // then update the current statistics page
1534 void CStatGraphDlg::SortModeChanged()
1536 UpdateData(); // update checkbox state
1537 RedrawGraph(); // then update the current statistics page
1540 void CStatGraphDlg::ClearGraph()
1542 m_graph.Clear();
1543 for (int j=0; j<m_graphDataArray.GetCount(); ++j)
1544 delete ((MyGraphSeries *)m_graphDataArray.GetAt(j));
1545 m_graphDataArray.RemoveAll();
1548 void CStatGraphDlg::RedrawGraph()
1550 EnableDisableMenu();
1551 m_btnGraphBar.SetState(BST_UNCHECKED);
1552 m_btnGraphBarStacked.SetState(BST_UNCHECKED);
1553 m_btnGraphLine.SetState(BST_UNCHECKED);
1554 m_btnGraphLineStacked.SetState(BST_UNCHECKED);
1555 m_btnGraphPie.SetState(BST_UNCHECKED);
1557 if ((m_GraphType == MyGraph::Bar)&&(m_bStacked))
1559 m_btnGraphBarStacked.SetState(BST_CHECKED);
1561 if ((m_GraphType == MyGraph::Bar)&&(!m_bStacked))
1563 m_btnGraphBar.SetState(BST_CHECKED);
1565 if ((m_GraphType == MyGraph::Line)&&(m_bStacked))
1567 m_btnGraphLineStacked.SetState(BST_CHECKED);
1569 if ((m_GraphType == MyGraph::Line)&&(!m_bStacked))
1571 m_btnGraphLine.SetState(BST_CHECKED);
1573 if (m_GraphType == MyGraph::PieChart)
1575 m_btnGraphPie.SetState(BST_CHECKED);
1578 UpdateData();
1579 ShowSelectStat((Metrics) m_cGraphType.GetItemData(m_cGraphType.GetCurSel()), true);
1581 void CStatGraphDlg::OnBnClickedGraphbarbutton()
1583 m_GraphType = MyGraph::Bar;
1584 m_bStacked = false;
1585 RedrawGraph();
1588 void CStatGraphDlg::OnBnClickedGraphbarstackedbutton()
1590 m_GraphType = MyGraph::Bar;
1591 m_bStacked = true;
1592 RedrawGraph();
1595 void CStatGraphDlg::OnBnClickedGraphlinebutton()
1597 m_GraphType = MyGraph::Line;
1598 m_bStacked = false;
1599 RedrawGraph();
1602 void CStatGraphDlg::OnBnClickedGraphlinestackedbutton()
1604 m_GraphType = MyGraph::Line;
1605 m_bStacked = true;
1606 RedrawGraph();
1609 void CStatGraphDlg::OnBnClickedGraphpiebutton()
1611 m_GraphType = MyGraph::PieChart;
1612 m_bStacked = false;
1613 RedrawGraph();
1616 void CStatGraphDlg::EnableDisableMenu()
1618 UINT nEnable = MF_BYCOMMAND;
1620 Metrics SelectMetric = (Metrics) m_cGraphType.GetItemData(m_cGraphType.GetCurSel());
1622 nEnable |= (SelectMetric > TextStatStart && SelectMetric < TextStatEnd)
1623 ? (MF_DISABLED | MF_GRAYED) : MF_ENABLED;
1625 GetMenu()->EnableMenuItem(ID_FILE_SAVESTATGRAPHAS, nEnable);
1628 void CStatGraphDlg::OnFileSavestatgraphas()
1630 CString tempfile;
1631 int filterindex = 0;
1632 if (CAppUtils::FileOpenSave(tempfile, &filterindex, IDS_REVGRAPH_SAVEPIC, IDS_STATPICFILEFILTER, false, m_hWnd))
1634 // if the user doesn't specify a file extension, default to
1635 // wmf and add that extension to the filename. But only if the
1636 // user chose the 'pictures' filter. The filename isn't changed
1637 // if the 'All files' filter was chosen.
1638 CString extension;
1639 int dotPos = tempfile.ReverseFind('.');
1640 int slashPos = tempfile.ReverseFind('\\');
1641 if (dotPos > slashPos)
1642 extension = tempfile.Mid(dotPos);
1643 if ((filterindex == 1)&&(extension.IsEmpty()))
1645 extension = L".wmf";
1646 tempfile += extension;
1648 SaveGraph(tempfile);
1652 void CStatGraphDlg::SaveGraph(CString sFilename)
1654 CString extension = CPathUtils::GetFileExtFromPath(sFilename);
1655 if (extension.CompareNoCase(L".wmf") == 0)
1657 // save the graph as an enhanced meta file
1658 CMyMetaFileDC wmfDC;
1659 wmfDC.CreateEnhanced(nullptr, sFilename, nullptr, L"TortoiseGit\0Statistics\0\0");
1660 wmfDC.SetAttribDC(GetDC()->GetSafeHdc());
1661 RedrawGraph();
1662 m_graph.DrawGraph(wmfDC);
1663 HENHMETAFILE hemf = wmfDC.CloseEnhanced();
1664 DeleteEnhMetaFile(hemf);
1666 else
1668 // save the graph as a pixel picture instead of a vector picture
1669 // create dc to paint on
1672 CWindowDC ddc(this);
1673 CDC dc;
1674 if (!dc.CreateCompatibleDC(&ddc))
1676 ShowErrorMessage();
1677 return;
1679 CRect rect;
1680 GetDlgItem(IDC_GRAPH)->GetClientRect(&rect);
1681 HBITMAP hbm = ::CreateCompatibleBitmap(ddc.m_hDC, rect.Width(), rect.Height());
1682 if (hbm==0)
1684 ShowErrorMessage();
1685 return;
1687 HBITMAP oldbm = (HBITMAP)dc.SelectObject(hbm);
1688 // paint the whole graph
1689 RedrawGraph();
1690 m_graph.DrawGraph(dc);
1691 // now use GDI+ to save the picture
1692 CLSID encoderClsid;
1693 GdiplusStartupInput gdiplusStartupInput;
1694 ULONG_PTR gdiplusToken;
1695 CString sErrormessage;
1696 if (GdiplusStartup(&gdiplusToken, &gdiplusStartupInput, nullptr) == Ok)
1699 Bitmap bitmap(hbm, nullptr);
1700 if (bitmap.GetLastStatus()==Ok)
1702 // Get the CLSID of the encoder.
1703 int ret = 0;
1704 if (CPathUtils::GetFileExtFromPath(sFilename).CompareNoCase(L".png") == 0)
1705 ret = GetEncoderClsid(L"image/png", &encoderClsid);
1706 else if (CPathUtils::GetFileExtFromPath(sFilename).CompareNoCase(L".jpg") == 0)
1707 ret = GetEncoderClsid(L"image/jpeg", &encoderClsid);
1708 else if (CPathUtils::GetFileExtFromPath(sFilename).CompareNoCase(L".jpeg") == 0)
1709 ret = GetEncoderClsid(L"image/jpeg", &encoderClsid);
1710 else if (CPathUtils::GetFileExtFromPath(sFilename).CompareNoCase(L".bmp") == 0)
1711 ret = GetEncoderClsid(L"image/bmp", &encoderClsid);
1712 else if (CPathUtils::GetFileExtFromPath(sFilename).CompareNoCase(L".gif") == 0)
1713 ret = GetEncoderClsid(L"image/gif", &encoderClsid);
1714 else
1716 sFilename += L".jpg";
1717 ret = GetEncoderClsid(L"image/jpeg", &encoderClsid);
1719 if (ret >= 0)
1721 CStringW tfile = CStringW(sFilename);
1722 bitmap.Save(tfile, &encoderClsid, nullptr);
1724 else
1725 sErrormessage.Format(IDS_REVGRAPH_ERR_NOENCODER, (LPCTSTR)CPathUtils::GetFileExtFromPath(sFilename));
1727 else
1728 sErrormessage.LoadString(IDS_REVGRAPH_ERR_NOBITMAP);
1730 GdiplusShutdown(gdiplusToken);
1732 else
1733 sErrormessage.LoadString(IDS_REVGRAPH_ERR_GDIINIT);
1734 dc.SelectObject(oldbm);
1735 dc.DeleteDC();
1736 if (!sErrormessage.IsEmpty())
1737 ::MessageBox(m_hWnd, sErrormessage, L"TortoiseGit", MB_ICONERROR);
1739 catch (CException * pE)
1741 TCHAR szErrorMsg[2048] = { 0 };
1742 pE->GetErrorMessage(szErrorMsg, 2048);
1743 pE->Delete();
1744 ::MessageBox(m_hWnd, szErrorMsg, L"TortoiseGit", MB_ICONERROR);
1749 int CStatGraphDlg::GetEncoderClsid(const WCHAR* format, CLSID* pClsid)
1751 UINT num = 0; // number of image encoders
1752 UINT size = 0; // size of the image encoder array in bytes
1754 if (GetImageEncodersSize(&num, &size)!=Ok)
1755 return -1;
1756 if (size == 0)
1757 return -1; // Failure
1759 auto pMem = std::make_unique<BYTE[]>(size);
1760 auto pImageCodecInfo = (ImageCodecInfo*)(pMem.get());
1761 if (!pImageCodecInfo)
1762 return -1; // Failure
1764 if (GetImageEncoders(num, size, pImageCodecInfo)==Ok)
1766 for (UINT j = 0; j < num; ++j)
1768 if (wcscmp(pImageCodecInfo[j].MimeType, format) == 0)
1770 *pClsid = pImageCodecInfo[j].Clsid;
1771 return j; // Success
1775 return -1; // Failure
1778 void CStatGraphDlg::StoreCurrentGraphType()
1780 UpdateData();
1781 DWORD graphtype = static_cast<DWORD>(m_cGraphType.GetItemData(m_cGraphType.GetCurSel()));
1782 // encode the current chart type
1783 DWORD statspage = graphtype*10;
1784 if ((m_GraphType == MyGraph::Bar)&&(m_bStacked))
1785 statspage += 1;
1786 if ((m_GraphType == MyGraph::Bar)&&(!m_bStacked))
1787 statspage += 2;
1788 if ((m_GraphType == MyGraph::Line)&&(m_bStacked))
1789 statspage += 3;
1790 if ((m_GraphType == MyGraph::Line)&&(!m_bStacked))
1791 statspage += 4;
1792 if (m_GraphType == MyGraph::PieChart)
1793 statspage += 5;
1795 // store current chart type in registry
1796 CRegDWORD lastStatsPage(L"Software\\TortoiseGit\\LastViewedStatsPage", 0);
1797 lastStatsPage = statspage;
1799 CRegDWORD regAuthors(L"Software\\TortoiseGit\\StatAuthorsCaseSensitive");
1800 regAuthors = m_bAuthorsCaseSensitive;
1802 CRegDWORD regSort(L"Software\\TortoiseGit\\StatSortByCommitCount");
1803 regSort = m_bSortByCommitCount;
1806 void CStatGraphDlg::ShowErrorMessage()
1808 CFormatMessageWrapper errorDetails;
1809 if (errorDetails)
1810 MessageBox(errorDetails, L"Error", MB_OK | MB_ICONINFORMATION);
1813 void CStatGraphDlg::ShowSelectStat(Metrics SelectedMetric, bool reloadSkiper /* = false */)
1815 switch (SelectedMetric)
1817 case AllStat:
1818 LoadListOfAuthors(m_commitsPerAuthor, reloadSkiper);
1819 ShowStats();
1820 break;
1821 case CommitsByDate:
1822 LoadListOfAuthors(m_commitsPerAuthor, reloadSkiper);
1823 ShowByDate(IDS_STATGRAPH_COMMITSBYDATEY, IDS_STATGRAPH_COMMITSBYDATE, m_commitsPerUnitAndAuthor);
1824 break;
1825 case LinesWByDate:
1826 OnBnClickedFetchDiff();
1827 LoadListOfAuthors(m_commitsPerAuthor, reloadSkiper);
1828 ShowByDate(IDS_STATGRAPH_LINES_BYDATE_W_Y, IDS_STATGRAPH_LINES_BYDATE_W, m_LinesWPerUnitAndAuthor);
1829 break;
1830 case LinesWOByDate:
1831 OnBnClickedFetchDiff();
1832 LoadListOfAuthors(m_commitsPerAuthor, reloadSkiper);
1833 ShowByDate(IDS_STATGRAPH_LINES_BYDATE_WO_Y, IDS_STATGRAPH_LINES_BYDATE_WO, m_LinesWOPerUnitAndAuthor);
1834 break;
1835 case CommitsByAuthor:
1836 LoadListOfAuthors(m_commitsPerAuthor, reloadSkiper);
1837 ShowCommitsByAuthor();
1838 break;
1839 case PercentageOfAuthorship:
1840 OnBnClickedFetchDiff();
1841 LoadListOfAuthors(m_PercentageOfAuthorship, reloadSkiper, true);
1842 ShowPercentageOfAuthorship();
1843 break;
1844 default:
1845 ShowErrorMessage();
1849 double CStatGraphDlg::CoeffContribution(int distFromEnd) { return distFromEnd ? 1.0 / m_CoeffAuthorShip * distFromEnd : 1;}
1852 template <class MAP>
1853 void CStatGraphDlg::DrawOthers(const std::list<tstring> &others, MyGraphSeries *graphData, MAP &map)
1855 int nCommits = 0;
1856 for (std::list<tstring>::const_iterator it = others.begin(); it != others.end(); ++it)
1857 nCommits += RollPercentageOfAuthorship(map[*it]);
1859 CString sOthers(MAKEINTRESOURCE(IDS_STATGRAPH_OTHERGROUP));
1860 sOthers.AppendFormat(L" (%Iu)", others.size());
1861 int group = m_graph.AppendGroup(sOthers);
1862 graphData->SetData(group, (int)nCommits);
1866 template <class MAP>
1867 void CStatGraphDlg::LoadListOfAuthors (MAP &map, bool reloadSkiper/*= false*/, bool compare /*= false*/)
1869 m_authorNames.clear();
1870 if (!map.empty())
1872 for (MAP::const_iterator it = map.begin(); it != map.end(); ++it)
1874 if ((compare && RollPercentageOfAuthorship(map[it->first]) != 0) || !compare)
1875 m_authorNames.push_back(it->first);
1879 // Sort the list of authors based on commit count
1880 m_authorNames.sort(MoreCommitsThan<MAP::mapped_type>(map));
1882 // Set Skipper
1883 SetSkipper(reloadSkiper);
1887 void CStatGraphDlg::OnBnClickedFetchDiff()
1889 if (m_bDiffFetched)
1890 return;
1891 if (GatherData(TRUE))
1892 return;
1893 this->m_bDiffFetched = TRUE;
1894 GetDlgItem(IDC_CALC_DIFF)->ShowWindow(!m_bDiffFetched);
1896 ShowStats();