No need to beep after displaying Commit Not Visible message
[TortoiseGit.git] / src / TortoiseProc / StatGraphDlg.cpp
blob5064cb55c16a4c4e6b2189859d374f6be5424035
1 // TortoiseGit - a Windows shell extension for easy version control
3 // Copyright (C) 2008-2014 - TortoiseGit
4 // Copyright (C) 2003-2011,2014 - 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 "StringUtils.h"
26 #include "PathUtils.h"
27 #include "MessageBox.h"
28 #include "registry.h"
29 #include "FormatMessageWrapper.h"
30 #include "SysProgressDlg.h"
32 #include <iterator>
33 #include <cmath>
34 #include <locale>
35 #include <list>
36 #include <utility>
37 #include <strsafe.h>
39 using namespace Gdiplus;
41 // BinaryPredicate for comparing authors based on their commit count
42 template<class DataType>
43 class MoreCommitsThan : public std::binary_function<tstring, tstring, bool> {
44 public:
45 typedef std::map<tstring, DataType> MapType;
46 MoreCommitsThan(MapType &author_commits) : m_authorCommits(author_commits) {}
48 bool operator()(const tstring& lhs, const tstring& rhs) {
49 return (m_authorCommits)[lhs] > (m_authorCommits)[rhs];
52 private:
53 MapType &m_authorCommits;
57 IMPLEMENT_DYNAMIC(CStatGraphDlg, CResizableStandAloneDialog)
58 CStatGraphDlg::CStatGraphDlg(CWnd* pParent /*=NULL*/)
59 : CResizableStandAloneDialog(CStatGraphDlg::IDD, pParent)
60 , m_bStacked(FALSE)
61 , m_GraphType(MyGraph::Bar)
62 , m_bAuthorsCaseSensitive(TRUE)
63 , m_bSortByCommitCount(TRUE)
64 , m_nWeeks(-1)
65 , m_nDays(-1)
66 , m_langOrder(0)
67 , m_firstInterval(0)
68 , m_lastInterval(0)
69 , m_nTotalCommits(0)
70 , m_nTotalLinesInc(0)
71 , m_nTotalLinesDec(0)
72 , m_nTotalLinesNew(0)
73 , m_nTotalLinesDel(0)
74 , m_bDiffFetched(FALSE)
75 , m_ShowList(NULL)
76 , m_minDate(0)
77 , m_maxDate(0)
78 , m_nTotalFileChanges(0)
80 m_pToolTip = NULL;
83 CStatGraphDlg::~CStatGraphDlg()
85 ClearGraph();
86 delete m_pToolTip;
89 void CStatGraphDlg::OnOK() {
90 StoreCurrentGraphType();
91 __super::OnOK();
94 void CStatGraphDlg::OnCancel() {
95 StoreCurrentGraphType();
96 __super::OnCancel();
99 void CStatGraphDlg::DoDataExchange(CDataExchange* pDX)
101 CResizableStandAloneDialog::DoDataExchange(pDX);
102 DDX_Control(pDX, IDC_GRAPH, m_graph);
103 DDX_Control(pDX, IDC_GRAPHCOMBO, m_cGraphType);
104 DDX_Control(pDX, IDC_SKIPPER, m_Skipper);
105 DDX_Check(pDX, IDC_AUTHORSCASESENSITIVE, m_bAuthorsCaseSensitive);
106 DDX_Check(pDX, IDC_SORTBYCOMMITCOUNT, m_bSortByCommitCount);
107 DDX_Control(pDX, IDC_GRAPHBARBUTTON, m_btnGraphBar);
108 DDX_Control(pDX, IDC_GRAPHBARSTACKEDBUTTON, m_btnGraphBarStacked);
109 DDX_Control(pDX, IDC_GRAPHLINEBUTTON, m_btnGraphLine);
110 DDX_Control(pDX, IDC_GRAPHLINESTACKEDBUTTON, m_btnGraphLineStacked);
111 DDX_Control(pDX, IDC_GRAPHPIEBUTTON, m_btnGraphPie);
115 BEGIN_MESSAGE_MAP(CStatGraphDlg, CResizableStandAloneDialog)
116 ON_CBN_SELCHANGE(IDC_GRAPHCOMBO, OnCbnSelchangeGraphcombo)
117 ON_WM_HSCROLL()
118 ON_NOTIFY(TTN_NEEDTEXT, NULL, OnNeedText)
119 ON_BN_CLICKED(IDC_AUTHORSCASESENSITIVE, &CStatGraphDlg::AuthorsCaseSensitiveChanged)
120 ON_BN_CLICKED(IDC_SORTBYCOMMITCOUNT, &CStatGraphDlg::SortModeChanged)
121 ON_BN_CLICKED(IDC_GRAPHBARBUTTON, &CStatGraphDlg::OnBnClickedGraphbarbutton)
122 ON_BN_CLICKED(IDC_GRAPHBARSTACKEDBUTTON, &CStatGraphDlg::OnBnClickedGraphbarstackedbutton)
123 ON_BN_CLICKED(IDC_GRAPHLINEBUTTON, &CStatGraphDlg::OnBnClickedGraphlinebutton)
124 ON_BN_CLICKED(IDC_GRAPHLINESTACKEDBUTTON, &CStatGraphDlg::OnBnClickedGraphlinestackedbutton)
125 ON_BN_CLICKED(IDC_GRAPHPIEBUTTON, &CStatGraphDlg::OnBnClickedGraphpiebutton)
126 ON_COMMAND(ID_FILE_SAVESTATGRAPHAS, &CStatGraphDlg::OnFileSavestatgraphas)
127 ON_BN_CLICKED(IDC_CALC_DIFF, &CStatGraphDlg::OnBnClickedFetchDiff)
128 END_MESSAGE_MAP()
130 void CStatGraphDlg::LoadStatQueries (__in UINT curStr, Metrics loadMetric, bool setDef /* = false */)
132 CString temp;
133 temp.LoadString(curStr);
134 int sel = m_cGraphType.AddString(temp);
135 m_cGraphType.SetItemData(sel, loadMetric);
137 if (setDef) m_cGraphType.SetCurSel(sel);
140 void CStatGraphDlg::SetSkipper (bool reloadSkiper)
142 // We need to limit the number of authors due to GUI resource limitation.
143 // However, since author #251 will properly have < 1000th of the commits,
144 // the resolution limit of the screen will already not allow for displaying
145 // it in a reasonable way
147 int max_authors_count = max(1, (int)min(m_authorNames.size(), 250) );
148 m_Skipper.SetRange (1, max_authors_count);
149 m_Skipper.SetPageSize(5);
151 if (reloadSkiper)
152 m_Skipper.SetPos (max_authors_count);
155 BOOL CStatGraphDlg::OnInitDialog()
157 CResizableStandAloneDialog::OnInitDialog();
159 // gather statistics data, only needs to be updated when the checkbox with
160 // the case sensitivity of author names is changed
161 GatherData();
163 m_pToolTip = new CToolTipCtrl;
164 if (m_pToolTip->Create(this))
166 m_pToolTip->AddTool(&m_btnGraphPie, IDS_STATGRAPH_PIEBUTTON_TT);
167 m_pToolTip->AddTool(&m_btnGraphLineStacked, IDS_STATGRAPH_LINESTACKEDBUTTON_TT);
168 m_pToolTip->AddTool(&m_btnGraphLine, IDS_STATGRAPH_LINEBUTTON_TT);
169 m_pToolTip->AddTool(&m_btnGraphBarStacked, IDS_STATGRAPH_BARSTACKEDBUTTON_TT);
170 m_pToolTip->AddTool(&m_btnGraphBar, IDS_STATGRAPH_BARBUTTON_TT);
172 m_pToolTip->Activate(TRUE);
175 m_bAuthorsCaseSensitive = DWORD(CRegDWORD(_T("Software\\TortoiseGit\\StatAuthorsCaseSensitive")));
176 m_bSortByCommitCount = DWORD(CRegDWORD(_T("Software\\TortoiseGit\\StatSortByCommitCount")));
177 UpdateData(FALSE);
179 //Load statistical queries
180 LoadStatQueries(IDS_STATGRAPH_STATS, AllStat, true);
181 LoadStatQueries(IDS_STATGRAPH_COMMITSBYDATE, CommitsByDate);
182 LoadStatQueries(IDS_STATGRAPH_COMMITSBYAUTHOR, CommitsByAuthor);
183 LoadStatQueries(IDS_STATGRAPH_PERCENTAGE_OF_AUTHORSHIP, PercentageOfAuthorship);
184 LoadStatQueries(IDS_STATGRAPH_LINES_BYDATE_W, LinesWByDate);
185 LoadStatQueries(IDS_STATGRAPH_LINES_BYDATE_WO, LinesWOByDate);
187 // set the dialog title to "Statistics - path/to/whatever/we/show/the/statistics/for"
188 CString sTitle;
189 GetWindowText(sTitle);
190 if (m_path.IsEmpty())
191 CAppUtils::SetWindowTitle(m_hWnd, g_Git.m_CurrentDir, sTitle);
192 else
193 CAppUtils::SetWindowTitle(m_hWnd, m_path.GetUIPathString(), sTitle);
195 m_btnGraphBar.SetImage((HICON)LoadImage(AfxGetResourceHandle(), MAKEINTRESOURCE(IDI_GRAPHBAR), IMAGE_ICON, 16, 16, LR_DEFAULTCOLOR));
196 m_btnGraphBar.SizeToContent();
197 m_btnGraphBar.Invalidate();
198 m_btnGraphBarStacked.SetImage((HICON)LoadImage(AfxGetResourceHandle(), MAKEINTRESOURCE(IDI_GRAPHBARSTACKED), IMAGE_ICON, 16, 16, LR_DEFAULTCOLOR));
199 m_btnGraphBarStacked.SizeToContent();
200 m_btnGraphBarStacked.Invalidate();
201 m_btnGraphLine.SetImage((HICON)LoadImage(AfxGetResourceHandle(), MAKEINTRESOURCE(IDI_GRAPHLINE), IMAGE_ICON, 16, 16, LR_DEFAULTCOLOR));
202 m_btnGraphLine.SizeToContent();
203 m_btnGraphLine.Invalidate();
204 m_btnGraphLineStacked.SetImage((HICON)LoadImage(AfxGetResourceHandle(), MAKEINTRESOURCE(IDI_GRAPHLINESTACKED), IMAGE_ICON, 16, 16, LR_DEFAULTCOLOR));
205 m_btnGraphLineStacked.SizeToContent();
206 m_btnGraphLineStacked.Invalidate();
207 m_btnGraphPie.SetImage((HICON)LoadImage(AfxGetResourceHandle(), MAKEINTRESOURCE(IDI_GRAPHPIE), IMAGE_ICON, 16, 16, LR_DEFAULTCOLOR));
208 m_btnGraphPie.SizeToContent();
209 m_btnGraphPie.Invalidate();
211 AddAnchor(IDC_GRAPHTYPELABEL, TOP_LEFT);
212 AddAnchor(IDC_GRAPH, TOP_LEFT, BOTTOM_RIGHT);
213 AddAnchor(IDC_GRAPHCOMBO, TOP_LEFT, TOP_RIGHT);
215 AddAnchor(IDC_NUMWEEK, TOP_LEFT);
216 AddAnchor(IDC_NUMWEEKVALUE, TOP_RIGHT);
217 AddAnchor(IDC_NUMAUTHOR, TOP_LEFT);
218 AddAnchor(IDC_NUMAUTHORVALUE, TOP_RIGHT);
219 AddAnchor(IDC_NUMCOMMITS, TOP_LEFT);
220 AddAnchor(IDC_NUMCOMMITSVALUE, TOP_RIGHT);
221 AddAnchor(IDC_NUMFILECHANGES, TOP_LEFT);
222 AddAnchor(IDC_NUMFILECHANGESVALUE, TOP_RIGHT);
224 AddAnchor(IDC_TOTAL_LINE_WITHOUT_NEW_DEL, TOP_LEFT);
225 AddAnchor(IDC_TOTAL_LINE_WITHOUT_NEW_DEL_VALUE, TOP_RIGHT);
226 AddAnchor(IDC_TOTAL_LINE_WITH_NEW_DEL, TOP_LEFT);
227 AddAnchor(IDC_TOTAL_LINE_WITH_NEW_DEL_VALUE, TOP_RIGHT);
229 AddAnchor(IDC_CALC_DIFF, TOP_RIGHT);
231 AddAnchor(IDC_AVG, TOP_RIGHT);
232 AddAnchor(IDC_MIN, TOP_RIGHT);
233 AddAnchor(IDC_MAX, TOP_RIGHT);
234 AddAnchor(IDC_COMMITSEACHWEEK, TOP_LEFT);
235 AddAnchor(IDC_MOSTACTIVEAUTHOR, TOP_LEFT);
236 AddAnchor(IDC_LEASTACTIVEAUTHOR, TOP_LEFT);
237 AddAnchor(IDC_MOSTACTIVEAUTHORNAME, TOP_LEFT, TOP_RIGHT);
238 AddAnchor(IDC_LEASTACTIVEAUTHORNAME, TOP_LEFT, TOP_RIGHT);
239 AddAnchor(IDC_FILECHANGESEACHWEEK, TOP_LEFT);
240 AddAnchor(IDC_COMMITSEACHWEEKAVG, TOP_RIGHT);
241 AddAnchor(IDC_COMMITSEACHWEEKMIN, TOP_RIGHT);
242 AddAnchor(IDC_COMMITSEACHWEEKMAX, TOP_RIGHT);
243 AddAnchor(IDC_MOSTACTIVEAUTHORAVG, TOP_RIGHT);
244 AddAnchor(IDC_MOSTACTIVEAUTHORMIN, TOP_RIGHT);
245 AddAnchor(IDC_MOSTACTIVEAUTHORMAX, TOP_RIGHT);
246 AddAnchor(IDC_LEASTACTIVEAUTHORAVG, TOP_RIGHT);
247 AddAnchor(IDC_LEASTACTIVEAUTHORMIN, TOP_RIGHT);
248 AddAnchor(IDC_LEASTACTIVEAUTHORMAX, TOP_RIGHT);
249 AddAnchor(IDC_FILECHANGESEACHWEEKAVG, TOP_RIGHT);
250 AddAnchor(IDC_FILECHANGESEACHWEEKMIN, TOP_RIGHT);
251 AddAnchor(IDC_FILECHANGESEACHWEEKMAX, TOP_RIGHT);
253 AddAnchor(IDC_GRAPHBARBUTTON, BOTTOM_RIGHT);
254 AddAnchor(IDC_GRAPHBARSTACKEDBUTTON, BOTTOM_RIGHT);
255 AddAnchor(IDC_GRAPHLINEBUTTON, BOTTOM_RIGHT);
256 AddAnchor(IDC_GRAPHLINESTACKEDBUTTON, BOTTOM_RIGHT);
257 AddAnchor(IDC_GRAPHPIEBUTTON, BOTTOM_RIGHT);
259 AddAnchor(IDC_AUTHORSCASESENSITIVE, BOTTOM_LEFT);
260 AddAnchor(IDC_SORTBYCOMMITCOUNT, BOTTOM_LEFT);
261 AddAnchor(IDC_SKIPPER, BOTTOM_LEFT, BOTTOM_RIGHT);
262 AddAnchor(IDC_SKIPPERLABEL, BOTTOM_LEFT);
263 AddAnchor(IDOK, BOTTOM_RIGHT);
264 EnableSaveRestore(_T("StatGraphDlg"));
266 // set the min/max values on the skipper
267 SetSkipper (true);
269 // we use a stats page encoding here, 0 stands for the statistics dialog
270 CRegDWORD lastStatsPage(_T("Software\\TortoiseGit\\LastViewedStatsPage"), 0);
272 // open last viewed statistics page as first page
273 int graphtype = lastStatsPage / 10;
274 for (int i = 0; i < m_cGraphType.GetCount(); i++)
276 if ((int)m_cGraphType.GetItemData(i) == graphtype)
278 m_cGraphType.SetCurSel(i);
279 break;
283 OnCbnSelchangeGraphcombo();
285 int statspage = lastStatsPage % 10;
286 switch (statspage) {
287 case 1 :
288 m_GraphType = MyGraph::Bar;
289 m_bStacked = true;
290 break;
291 case 2 :
292 m_GraphType = MyGraph::Bar;
293 m_bStacked = false;
294 break;
295 case 3 :
296 m_GraphType = MyGraph::Line;
297 m_bStacked = true;
298 break;
299 case 4 :
300 m_GraphType = MyGraph::Line;
301 m_bStacked = false;
302 break;
303 case 5 :
304 m_GraphType = MyGraph::PieChart;
305 break;
307 default : return TRUE;
310 LCID m_locale = MAKELCID((DWORD)CRegStdDWORD(_T("Software\\TortoiseGit\\LanguageID"), MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT)), SORT_DEFAULT);
312 bool bUseSystemLocale = !!(DWORD)CRegStdDWORD(_T("Software\\TortoiseGit\\UseSystemLocaleForDates"), TRUE);
313 LCID locale = bUseSystemLocale ? MAKELCID(MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), SORT_DEFAULT) : m_locale;
315 TCHAR langBuf[11] = { 0 };
316 memset(langBuf, 0, sizeof(langBuf));
317 GetLocaleInfo(locale, LOCALE_IDATE, langBuf, _countof(langBuf));
319 m_langOrder = _ttoi(langBuf);
321 return TRUE;
324 void CStatGraphDlg::ShowLabels(BOOL bShow)
326 if (m_parAuthors.IsEmpty() || m_parDates.IsEmpty() || m_parFileChanges.IsEmpty())
327 return;
329 int nCmdShow = bShow ? SW_SHOW : SW_HIDE;
331 GetDlgItem(IDC_GRAPH)->ShowWindow(bShow ? SW_HIDE : SW_SHOW);
332 GetDlgItem(IDC_NUMWEEK)->ShowWindow(nCmdShow);
333 GetDlgItem(IDC_NUMWEEKVALUE)->ShowWindow(nCmdShow);
334 GetDlgItem(IDC_NUMAUTHOR)->ShowWindow(nCmdShow);
335 GetDlgItem(IDC_NUMAUTHORVALUE)->ShowWindow(nCmdShow);
336 GetDlgItem(IDC_NUMCOMMITS)->ShowWindow(nCmdShow);
337 GetDlgItem(IDC_NUMCOMMITSVALUE)->ShowWindow(nCmdShow);
338 GetDlgItem(IDC_NUMFILECHANGES)->ShowWindow(nCmdShow);
339 GetDlgItem(IDC_NUMFILECHANGESVALUE)->ShowWindow(nCmdShow);
340 GetDlgItem(IDC_TOTAL_LINE_WITHOUT_NEW_DEL)->ShowWindow(nCmdShow);
341 GetDlgItem(IDC_TOTAL_LINE_WITHOUT_NEW_DEL_VALUE)->ShowWindow(nCmdShow);
342 GetDlgItem(IDC_TOTAL_LINE_WITH_NEW_DEL)->ShowWindow(nCmdShow);
343 GetDlgItem(IDC_TOTAL_LINE_WITH_NEW_DEL_VALUE)->ShowWindow(nCmdShow);
344 GetDlgItem(IDC_CALC_DIFF)->ShowWindow(nCmdShow && !m_bDiffFetched);
346 GetDlgItem(IDC_AVG)->ShowWindow(nCmdShow);
347 GetDlgItem(IDC_MIN)->ShowWindow(nCmdShow);
348 GetDlgItem(IDC_MAX)->ShowWindow(nCmdShow);
349 GetDlgItem(IDC_COMMITSEACHWEEK)->ShowWindow(nCmdShow);
350 GetDlgItem(IDC_MOSTACTIVEAUTHOR)->ShowWindow(nCmdShow);
351 GetDlgItem(IDC_LEASTACTIVEAUTHOR)->ShowWindow(nCmdShow);
352 GetDlgItem(IDC_MOSTACTIVEAUTHORNAME)->ShowWindow(nCmdShow);
353 GetDlgItem(IDC_LEASTACTIVEAUTHORNAME)->ShowWindow(nCmdShow);
354 //GetDlgItem(IDC_FILECHANGESEACHWEEK)->ShowWindow(nCmdShow);
355 GetDlgItem(IDC_COMMITSEACHWEEKAVG)->ShowWindow(nCmdShow);
356 GetDlgItem(IDC_COMMITSEACHWEEKMIN)->ShowWindow(nCmdShow);
357 GetDlgItem(IDC_COMMITSEACHWEEKMAX)->ShowWindow(nCmdShow);
358 GetDlgItem(IDC_MOSTACTIVEAUTHORAVG)->ShowWindow(nCmdShow);
359 GetDlgItem(IDC_MOSTACTIVEAUTHORMIN)->ShowWindow(nCmdShow);
360 GetDlgItem(IDC_MOSTACTIVEAUTHORMAX)->ShowWindow(nCmdShow);
361 GetDlgItem(IDC_LEASTACTIVEAUTHORAVG)->ShowWindow(nCmdShow);
362 GetDlgItem(IDC_LEASTACTIVEAUTHORMIN)->ShowWindow(nCmdShow);
363 GetDlgItem(IDC_LEASTACTIVEAUTHORMAX)->ShowWindow(nCmdShow);
364 GetDlgItem(IDC_FILECHANGESEACHWEEKAVG)->ShowWindow(nCmdShow);
365 GetDlgItem(IDC_FILECHANGESEACHWEEKMIN)->ShowWindow(nCmdShow);
366 GetDlgItem(IDC_FILECHANGESEACHWEEKMAX)->ShowWindow(nCmdShow);
368 GetDlgItem(IDC_SORTBYCOMMITCOUNT)->ShowWindow(bShow ? SW_HIDE : SW_SHOW);
369 GetDlgItem(IDC_SKIPPER)->ShowWindow(bShow ? SW_HIDE : SW_SHOW);
370 GetDlgItem(IDC_SKIPPERLABEL)->ShowWindow(bShow ? SW_HIDE : SW_SHOW);
371 m_btnGraphBar.ShowWindow(bShow ? SW_HIDE : SW_SHOW);
372 m_btnGraphBarStacked.ShowWindow(bShow ? SW_HIDE : SW_SHOW);
373 m_btnGraphLine.ShowWindow(bShow ? SW_HIDE : SW_SHOW);
374 m_btnGraphLineStacked.ShowWindow(bShow ? SW_HIDE : SW_SHOW);
375 m_btnGraphPie.ShowWindow(bShow ? SW_HIDE : SW_SHOW);
378 void CStatGraphDlg::UpdateWeekCount()
380 // Sanity check
381 if (m_parDates.IsEmpty())
382 return;
384 // Already updated? No need to do it again.
385 if (m_nWeeks >= 0)
386 return;
388 // Determine first and last date in dates array
389 __time64_t min_date = (__time64_t)m_parDates.GetAt(0);
390 __time64_t max_date = min_date;
391 INT_PTR count = m_parDates.GetCount();
392 for (INT_PTR i=0; i<count; ++i)
394 __time64_t d = (__time64_t)m_parDates.GetAt(i);
395 if (d < min_date) min_date = d;
396 else if (d > max_date) max_date = d;
399 // Store start date of the interval in the member variable m_minDate
400 m_minDate = min_date;
401 m_maxDate = max_date;
403 // How many weeks does the time period cover?
405 // Get time difference between start and end date
406 double secs = _difftime64(max_date, m_minDate);
408 m_nWeeks = (int)ceil(secs / (double) m_SecondsInWeek);
409 m_nDays = (int)ceil(secs / (double) m_SecondsInDay);
412 int CStatGraphDlg::GetCalendarWeek(const CTime& time)
414 // Note:
415 // the calculation of the calendar week is wrong if DST is in effect
416 // and the date to calculate the week for is in DST and within the range
417 // of the DST offset (e.g. one hour).
418 // For example, if DST starts on Sunday march 30 and the date to get the week for
419 // is Monday, march 31, 0:30:00, then the returned week is one week less than
420 // the real week.
421 // TODO: ?
422 // write a function
423 // getDSTOffset(const CTime& time)
424 // which returns the DST offset for a given time/date. Then we can use this offset
425 // to correct our GetDays() calculation to get the correct week again
426 // This of course won't work for 'history' dates, because Windows doesn't have
427 // that information (only Vista has such a function: GetTimeZoneInformationForYear() )
428 int iWeekOfYear = 0;
430 int iYear = time.GetYear();
431 int iFirstDayOfWeek = 0;
432 int iFirstWeekOfYear = 0;
433 TCHAR loc[2] = { 0 };
434 GetLocaleInfo(LOCALE_USER_DEFAULT, LOCALE_IFIRSTDAYOFWEEK, loc, _countof(loc));
435 iFirstDayOfWeek = int(loc[0]-'0');
436 GetLocaleInfo(LOCALE_USER_DEFAULT, LOCALE_IFIRSTWEEKOFYEAR, loc, _countof(loc));
437 iFirstWeekOfYear = int(loc[0]-'0');
438 CTime dDateFirstJanuary(iYear,1,1,0,0,0);
439 int iDayOfWeek = (dDateFirstJanuary.GetDayOfWeek()+5+iFirstDayOfWeek)%7;
441 // Select mode
442 // 0 Week containing 1/1 is the first week of that year.
443 // 1 First full week following 1/1 is the first week of that year.
444 // 2 First week containing at least four days is the first week of that year.
445 switch (iFirstWeekOfYear)
447 case 0:
449 // Week containing 1/1 is the first week of that year.
451 // check if this week reaches into the next year
452 dDateFirstJanuary = CTime(iYear+1,1,1,0,0,0);
454 // Get start of week
457 iDayOfWeek = (time.GetDayOfWeek()+5+iFirstDayOfWeek)%7;
459 catch (CException* e)
461 e->Delete();
463 CTime dStartOfWeek = time-CTimeSpan(iDayOfWeek,0,0,0);
465 // If this week spans over to 1/1 this is week 1
466 if (dStartOfWeek + CTimeSpan(6,0,0,0) >= dDateFirstJanuary)
468 // we are in the last week of the year that spans over 1/1
469 iWeekOfYear = 1;
471 else
473 // Get week day of 1/1
474 dDateFirstJanuary = CTime(iYear,1,1,0,0,0);
475 iDayOfWeek = (dDateFirstJanuary.GetDayOfWeek() +5 + iFirstDayOfWeek) % 7;
476 // Just count from 1/1
477 iWeekOfYear = (int)(((time-dDateFirstJanuary).GetDays() + iDayOfWeek) / 7) + 1;
480 break;
481 case 1:
483 // First full week following 1/1 is the first week of that year.
485 // If the 1.1 is the start of the week everything is ok
486 // else we need the next week is the correct result
487 iWeekOfYear =
488 (int)(((time-dDateFirstJanuary).GetDays() + iDayOfWeek) / 7) +
489 (iDayOfWeek==0 ? 1:0);
491 // If we are in week 0 we are in the first not full week
492 // calculate from the last year
493 if (iWeekOfYear==0)
495 // Special case: we are in the week of 1.1 but 1.1. is not on the
496 // start of week. Calculate based on the last year
497 dDateFirstJanuary = CTime(iYear-1,1,1,0,0,0);
498 iDayOfWeek =
499 (dDateFirstJanuary.GetDayOfWeek()+5+iFirstDayOfWeek)%7;
500 // and we correct this in the same we we done this before but
501 // the result is now 52 or 53 and not 0
502 iWeekOfYear =
503 (int)(((time-dDateFirstJanuary).GetDays()+iDayOfWeek) / 7) +
504 (iDayOfWeek<=3 ? 1:0);
507 break;
508 case 2:
510 // First week containing at least four days is the first week of that year.
512 // Each year can start with any day of the week. But our
513 // weeks always start with Monday. So we add the day of week
514 // before calculation of the final week of year.
515 // Rule: is the 1.1 a Mo,Tu,We,Th than the week starts on the 1.1 with
516 // week==1, else a week later, so we add one for all those days if
517 // day is less <=3 Mo,Tu,We,Th. Otherwise 1.1 is in the last week of the
518 // previous year
519 iWeekOfYear =
520 (int)(((time-dDateFirstJanuary).GetDays()+iDayOfWeek) / 7) +
521 (iDayOfWeek<=3 ? 1:0);
523 // special cases
524 if (iWeekOfYear==0)
526 // special case week 0. We got a day before the 1.1, 2.1 or 3.1, were the
527 // 1.1. is not a Mo, Tu, We, Th. So the week 1 does not start with the 1.1.
528 // So we calculate the week according to the 1.1 of the year before
530 dDateFirstJanuary = CTime(iYear-1,1,1,0,0,0);
531 iDayOfWeek =
532 (dDateFirstJanuary.GetDayOfWeek()+5+iFirstDayOfWeek)%7;
533 // and we correct this in the same we we done this before but the result
534 // is now 52 or 53 and not 0
535 iWeekOfYear =
536 (int)(((time-dDateFirstJanuary).GetDays()+iDayOfWeek) / 7) +
537 (iDayOfWeek<=3 ? 1:0);
539 else if (iWeekOfYear==53)
541 // special case week 53. Either we got the correct week 53 or we just got the
542 // week 1 of the next year. So is the 1.1.(year+1) also a Mo, Tu, We, Th than
543 // we already have the week 1, otherwise week 53 is correct
545 dDateFirstJanuary = CTime(iYear+1,1,1,0,0,0);
546 iDayOfWeek =
547 (dDateFirstJanuary.GetDayOfWeek()+5+iFirstDayOfWeek)%7;
548 // 1.1. in week 1 or week 53?
549 iWeekOfYear = iDayOfWeek<=3 ? 1:53;
552 break;
553 default:
554 ASSERT(FALSE);
555 break;
557 // return result
558 return iWeekOfYear;
561 class CDateSorter
563 public:
564 class CCommitPointer
566 public:
567 CCommitPointer()
568 : m_cont(nullptr)
569 , m_place(0)
570 , m_Date(0)
571 , m_Changes(0)
572 , m_lineInc(0)
573 , m_lineDec(0)
574 , m_lineNew(0)
575 , m_lineDel(0)
577 CCommitPointer(const CCommitPointer& P_Right)
578 : m_cont(NULL)
579 , m_place(0)
580 , m_Date(0)
581 , m_Changes(0)
582 , m_lineInc(0)
583 , m_lineDec(0)
584 , m_lineNew(0)
585 , m_lineDel(0)
587 *this = P_Right;
590 CCommitPointer& operator = (const CCommitPointer& P_Right)
592 if(IsPointer())
594 (*m_cont->m_parDates)[m_place] = P_Right.GetDate();
595 (*m_cont->m_parFileChanges)[m_place] = P_Right.GetChanges();
596 (*m_cont->m_parAuthors)[m_place] = P_Right.GetAuthor();
597 (*m_cont->m_lineInc)[m_place] = P_Right.GetLineInc();
598 (*m_cont->m_lineDec)[m_place] = P_Right.GetLineDec();
599 (*m_cont->m_lineNew)[m_place] = P_Right.GetLineNew();
600 (*m_cont->m_lineDel)[m_place] = P_Right.GetLineDel();
602 else
604 m_Date = P_Right.GetDate();
605 m_Changes = P_Right.GetChanges();
606 m_csAuthor = P_Right.GetAuthor();
607 m_lineInc = P_Right.GetLineInc();
608 m_lineDec = P_Right.GetLineDec();
609 m_lineNew = P_Right.GetLineNew();
610 m_lineDel = P_Right.GetLineDel();
612 return *this;
615 void Clone(const CCommitPointer& P_Right)
617 m_cont = P_Right.m_cont;
618 m_place = P_Right.m_place;
619 m_Date = P_Right.m_Date;
620 m_Changes = P_Right.m_Changes;
621 m_csAuthor = P_Right.m_csAuthor;
624 DWORD GetDate() const {return IsPointer() ? (*m_cont->m_parDates)[m_place] : m_Date;}
625 DWORD GetChanges() const {return IsPointer() ? (*m_cont->m_parFileChanges)[m_place] : m_Changes;}
626 DWORD GetLineInc() const {return IsPointer() ? (*m_cont->m_lineInc)[m_place] : m_lineInc;}
627 DWORD GetLineDec() const {return IsPointer() ? (*m_cont->m_lineDec)[m_place] : m_lineDec;}
628 DWORD GetLineNew() const {return IsPointer() ? (*m_cont->m_lineNew)[m_place] : m_lineNew;}
629 DWORD GetLineDel() const {return IsPointer() ? (*m_cont->m_lineDel)[m_place] : m_lineDel;}
630 CString GetAuthor() const {return IsPointer() ? (*m_cont->m_parAuthors)[m_place] : m_csAuthor;}
632 bool IsPointer() const {return m_cont != NULL;}
633 //When pointer
634 CDateSorter* m_cont;
635 int m_place;
637 //When element
638 DWORD m_Date;
639 DWORD m_Changes;
640 DWORD m_lineInc;
641 DWORD m_lineDec;
642 DWORD m_lineNew;
643 DWORD m_lineDel;
644 CString m_csAuthor;
647 class iterator : public std::iterator<std::random_access_iterator_tag, CCommitPointer>
649 public:
650 CCommitPointer m_ptr;
652 iterator(){}
653 iterator(const iterator& P_Right){*this = P_Right;}
654 iterator& operator=(const iterator& P_Right)
656 m_ptr.Clone(P_Right.m_ptr);
657 return *this;
660 CCommitPointer& operator*(){return m_ptr;}
661 CCommitPointer* operator->(){return &m_ptr;}
662 const CCommitPointer& operator*()const{return m_ptr;}
663 const CCommitPointer* operator->()const{return &m_ptr;}
665 iterator& operator+=(size_t P_iOffset){m_ptr.m_place += (int)P_iOffset;return *this;}
666 iterator& operator-=(size_t P_iOffset){m_ptr.m_place -= (int)P_iOffset;return *this;}
667 iterator operator+(size_t P_iOffset)const{iterator it(*this); it += P_iOffset;return it;}
668 iterator operator-(size_t P_iOffset)const{iterator it(*this); it -= P_iOffset;return it;}
670 iterator& operator++(){++m_ptr.m_place;return *this;}
671 iterator& operator--(){--m_ptr.m_place;return *this;}
672 iterator operator++(int){iterator it(*this);++*this;return it;}
673 iterator operator--(int){iterator it(*this);--*this;return it;}
675 size_t operator-(const iterator& P_itRight)const{return m_ptr.m_place - P_itRight->m_place;}
677 bool operator<(const iterator& P_itRight)const{return m_ptr.m_place < P_itRight->m_place;}
678 bool operator!=(const iterator& P_itRight)const{return m_ptr.m_place != P_itRight->m_place;}
679 bool operator==(const iterator& P_itRight)const{return m_ptr.m_place == P_itRight->m_place;}
680 bool operator>(const iterator& P_itRight)const{return m_ptr.m_place > P_itRight->m_place;}
682 iterator begin()
684 iterator it;
685 it->m_place = 0;
686 it->m_cont = this;
687 return it;
689 iterator end()
691 iterator it;
692 it->m_place = (int)m_parDates->GetCount();
693 it->m_cont = this;
694 return it;
697 CDWordArray * m_parDates;
698 CDWordArray * m_parFileChanges;
699 CDWordArray * m_lineInc;
700 CDWordArray * m_lineDec;
701 CDWordArray * m_lineNew;
702 CDWordArray * m_lineDel;
703 CStringArray * m_parAuthors;
706 class CDateSorterLess
708 public:
709 bool operator () (const CDateSorter::CCommitPointer& P_Left, const CDateSorter::CCommitPointer& P_Right) const
711 return P_Left.GetDate() > P_Right.GetDate(); //Last date first
716 int CStatGraphDlg::GatherData(BOOL fetchdiff, BOOL keepFetchedData)
718 m_parAuthors.RemoveAll();
719 m_parDates.RemoveAll();
720 if (m_parFileChanges2.IsEmpty()) // Fixes issue #1948
721 keepFetchedData = FALSE;
722 if (!keepFetchedData)
724 m_parFileChanges.RemoveAll();
725 m_lineInc.RemoveAll();
726 m_lineDec.RemoveAll();
727 m_lineDel.RemoveAll();
728 m_lineNew.RemoveAll();
730 else
732 m_parFileChanges.Copy(m_parFileChanges2);
733 m_lineNew.Copy(m_lineNew2);
734 m_lineDel.Copy(m_lineDel2);
735 m_lineInc.Copy(m_lineInc2);
736 m_lineDec.Copy(m_lineDec2);
739 CSysProgressDlg progress;
740 if (fetchdiff)
742 progress.SetTitle(CString(MAKEINTRESOURCE(IDS_PROGS_TITLE_GATHERSTATISTICS)));
743 progress.FormatNonPathLine(1, IDS_PROC_STATISTICS_DIFF);
744 progress.SetAnimation(IDR_MOVEANI);
745 progress.SetTime(true);
746 progress.ShowModeless(this);
749 // create arrays which are aware of the current filter
750 DWORD starttime = GetTickCount();
752 GIT_MAILMAP mailmap = nullptr;
753 git_read_mailmap(&mailmap);
754 for (INT_PTR i = 0; i < m_ShowList.GetCount(); ++i)
756 GitRev* pLogEntry = reinterpret_cast<GitRev*>(m_ShowList.SafeGetAt(i));
757 int inc, dec, incnewfile, decdeletedfile, files;
758 inc = dec = incnewfile = decdeletedfile = files= 0;
760 // do not take working dir changes into statistics
761 if (pLogEntry->m_CommitHash.IsEmpty()) {
762 continue;
765 CString strAuthor = pLogEntry->GetAuthorName();
766 if (strAuthor.IsEmpty())
768 strAuthor.LoadString(IDS_STATGRAPH_EMPTYAUTHOR);
770 if (mailmap)
772 CStringA email2A = CUnicodeUtils::GetUTF8(pLogEntry->GetAuthorEmail());
773 struct payload_struct { GitRev* rev; const char *authorName; };
774 payload_struct payload = { pLogEntry, nullptr };
775 const char *author1 = git_get_mailmap_author(mailmap, email2A, &payload,
776 [] (void *payload) -> const char * { return ((payload_struct *)payload)->authorName = _strdup(CUnicodeUtils::GetUTF8(((payload_struct *)payload)->rev->GetAuthorName())); });
777 if (payload.authorName)
778 free((void *)payload.authorName);
779 if (author1)
780 strAuthor = CUnicodeUtils::GetUnicode(author1);
782 m_parAuthors.Add(strAuthor);
783 m_parDates.Add((DWORD)pLogEntry->GetCommitterDate().GetTime());
785 if (fetchdiff && (pLogEntry->m_ParentHash.size() <= 1))
787 CTGitPathList &list = pLogEntry->GetFiles(NULL);
788 files = list.GetCount();
790 for (int j = 0; j < files; j++)
792 if (list[j].m_Action & CTGitPath::LOGACTIONS_DELETED)
793 decdeletedfile += _tstol(list[j].m_StatDel);
794 else if(list[j].m_Action & CTGitPath::LOGACTIONS_ADDED)
795 incnewfile += _tstol(list[j].m_StatAdd);
796 else
798 inc += _tstol(list[j].m_StatAdd);
799 dec += _tstol(list[j].m_StatDel);
802 if (progress.HasUserCancelled())
804 git_free_mailmap(mailmap);
805 return -1;
809 if (!keepFetchedData)
811 m_parFileChanges.Add(files);
812 m_lineInc.Add(inc);
813 m_lineDec.Add(dec);
814 m_lineDel.Add(decdeletedfile);
815 m_lineNew.Add(incnewfile);
818 if (progress.IsVisible() && (GetTickCount() - starttime > 100))
820 progress.FormatNonPathLine(2, _T("%s: %s"), pLogEntry->m_CommitHash.ToString().Left(g_Git.GetShortHASHLength()), pLogEntry->GetSubject());
821 progress.SetProgress64(i, m_ShowList.GetCount());
822 starttime = GetTickCount();
826 git_free_mailmap(mailmap);
828 if (fetchdiff)
830 m_parFileChanges2.Copy(m_parFileChanges);
831 m_lineNew2.Copy(m_lineNew);
832 m_lineDel2.Copy(m_lineDel);
833 m_lineInc2.Copy(m_lineInc);
834 m_lineDec2.Copy(m_lineDec);
837 CDateSorter W_Sorter;
838 W_Sorter.m_parAuthors = &m_parAuthors;
839 W_Sorter.m_parDates = &m_parDates;
840 W_Sorter.m_parFileChanges = &m_parFileChanges;
841 W_Sorter.m_lineNew = &m_lineNew;
842 W_Sorter.m_lineDel = &m_lineDel;
843 W_Sorter.m_lineInc = &m_lineInc;
844 W_Sorter.m_lineDec = &m_lineDec;
846 std::sort(W_Sorter.begin(), W_Sorter.end(), CDateSorterLess());
848 m_nTotalCommits = m_parAuthors.GetCount();
849 m_nTotalFileChanges = 0;
851 // Update m_nWeeks and m_minDate
852 UpdateWeekCount();
854 // Now create a mapping that holds the information per week.
855 m_commitsPerUnitAndAuthor.clear();
856 m_filechangesPerUnitAndAuthor.clear();
857 m_commitsPerAuthor.clear();
858 m_PercentageOfAuthorship.clear();
859 m_LinesWPerUnitAndAuthor.clear();
860 m_LinesWOPerUnitAndAuthor.clear();
862 int interval = 0;
863 __time64_t d = (__time64_t)m_parDates.GetAt(0);
864 int nLastUnit = GetUnit(d);
865 double AllContributionAuthor = 0;
867 m_nTotalLinesInc = m_nTotalLinesDec = m_nTotalLinesNew = m_nTotalLinesDel =0;
869 // Now loop over all weeks and gather the info
870 for (LONG i=0; i<m_nTotalCommits; ++i)
872 // Find the interval number
873 __time64_t commitDate = (__time64_t)m_parDates.GetAt(i);
874 int u = GetUnit(commitDate);
875 if (nLastUnit != u)
876 interval++;
877 nLastUnit = u;
878 // Find the authors name
879 CString sAuth = m_parAuthors.GetAt(i);
880 if (!m_bAuthorsCaseSensitive)
881 sAuth = sAuth.MakeLower();
882 tstring author = tstring(sAuth);
883 // Increase total commit count for this author
884 m_commitsPerAuthor[author]++;
885 // Increase the commit count for this author in this week
886 m_commitsPerUnitAndAuthor[interval][author]++;
888 m_LinesWPerUnitAndAuthor[interval][author] += m_lineInc.GetAt(i) + m_lineDec.GetAt(i) + m_lineNew.GetAt(i) + + m_lineDel.GetAt(i);
889 m_LinesWOPerUnitAndAuthor[interval][author] += m_lineInc.GetAt(i) + m_lineDec.GetAt(i);
891 CTime t = m_parDates.GetAt(i);
892 m_unitNames[interval] = GetUnitLabel(nLastUnit, t);
893 // Increase the file change count for this author in this week
894 int fileChanges = m_parFileChanges.GetAt(i);
895 m_filechangesPerUnitAndAuthor[interval][author] += fileChanges;
896 m_nTotalFileChanges += fileChanges;
898 //calculate Contribution Author
899 double contributionAuthor = CoeffContribution((int)m_nTotalCommits - i -1) * (fileChanges ? fileChanges : 1);
900 AllContributionAuthor += contributionAuthor;
901 m_PercentageOfAuthorship[author] += contributionAuthor;
903 m_nTotalLinesInc += m_lineInc.GetAt(i);
904 m_nTotalLinesDec += m_lineDec.GetAt(i);
905 m_nTotalLinesNew += m_lineNew.GetAt(i);
906 m_nTotalLinesDel += m_lineDel.GetAt(i);
909 // Find first and last interval number.
910 if (!m_commitsPerUnitAndAuthor.empty())
912 IntervalDataMap::iterator interval_it = m_commitsPerUnitAndAuthor.begin();
913 m_firstInterval = interval_it->first;
914 interval_it = m_commitsPerUnitAndAuthor.end();
915 --interval_it;
916 m_lastInterval = interval_it->first;
917 // Sanity check - if m_lastInterval is too large it could freeze TSVN and take up all memory!!!
918 assert(m_lastInterval >= 0 && m_lastInterval < 10000);
920 else
922 m_firstInterval = 0;
923 m_lastInterval = -1;
926 // Get a list of authors names
927 LoadListOfAuthors(m_commitsPerAuthor);
929 // Calculate percent of Contribution Authors
930 for (std::list<tstring>::iterator it = m_authorNames.begin(); it != m_authorNames.end(); ++it)
932 m_PercentageOfAuthorship[*it] = (m_PercentageOfAuthorship[*it] *100)/ AllContributionAuthor;
935 // All done, now the statistics pages can retrieve the data and
936 // extract the information to be shown.
938 return 0;
942 void CStatGraphDlg::FilterSkippedAuthors(std::list<tstring>& included_authors,
943 std::list<tstring>& skipped_authors)
945 included_authors.clear();
946 skipped_authors.clear();
948 unsigned int included_authors_count = m_Skipper.GetPos();
949 // if we only leave out one author, still include him with his name
950 if (included_authors_count + 1 == m_authorNames.size())
951 ++included_authors_count;
953 // add the included authors first
954 std::list<tstring>::iterator author_it = m_authorNames.begin();
955 while (included_authors_count > 0 && author_it != m_authorNames.end())
957 // Add him/her to the included list
958 included_authors.push_back(*author_it);
959 ++author_it;
960 --included_authors_count;
963 // If we haven't reached the end yet, copy all remaining authors into the
964 // skipped author list.
965 std::copy(author_it, m_authorNames.end(), std::back_inserter(skipped_authors) );
967 // Sort authors alphabetically if user wants that.
968 if (!m_bSortByCommitCount)
969 included_authors.sort();
972 bool CStatGraphDlg::PreViewStat(bool fShowLabels)
974 if (m_parAuthors.IsEmpty() || m_parDates.IsEmpty() || m_parFileChanges.IsEmpty())
975 return false;
976 ShowLabels(fShowLabels);
978 //If view graphic
979 if (!fShowLabels) ClearGraph();
981 // This function relies on a previous call of GatherData().
982 // This can be detected by checking the week count.
983 // If the week count is equal to -1, it hasn't been called before.
984 if (m_nWeeks == -1)
985 GatherData(FALSE, TRUE);
986 // If week count is still -1, something bad has happened, probably invalid data!
987 if (m_nWeeks == -1)
988 return false;
990 return true;
993 MyGraphSeries *CStatGraphDlg::PreViewGraph(__in UINT GraphTitle, __in UINT YAxisLabel, __in UINT XAxisLabel /*= NULL*/)
995 if(!PreViewStat(false))
996 return NULL;
998 // We need at least one author
999 if (m_authorNames.empty())
1000 return NULL;
1002 // Add a single series to the chart
1003 MyGraphSeries * graphData = new MyGraphSeries();
1004 m_graph.AddSeries(*graphData);
1005 m_graphDataArray.Add(graphData);
1007 // Set up the graph.
1008 CString temp;
1009 UpdateData();
1010 m_graph.SetGraphType(m_GraphType, m_bStacked);
1011 temp.LoadString(YAxisLabel);
1012 m_graph.SetYAxisLabel(temp);
1013 temp.LoadString(XAxisLabel);
1014 m_graph.SetXAxisLabel(temp);
1015 temp.LoadString(GraphTitle);
1016 m_graph.SetGraphTitle(temp);
1018 return graphData;
1021 void CStatGraphDlg::ShowPercentageOfAuthorship()
1023 // Set up the graph.
1024 MyGraphSeries * graphData = PreViewGraph(IDS_STATGRAPH_PERCENTAGE_OF_AUTHORSHIP,
1025 IDS_STATGRAPH_PERCENTAGE_OF_AUTHORSHIPY,
1026 IDS_STATGRAPH_COMMITSBYAUTHORX);
1027 if(graphData == NULL) return;
1029 // Find out which authors are to be shown and which are to be skipped.
1030 std::list<tstring> authors;
1031 std::list<tstring> others;
1034 FilterSkippedAuthors(authors, others);
1036 // Loop over all authors in the authors list and
1037 // add them to the graph.
1039 if (!authors.empty())
1041 for (std::list<tstring>::iterator it = authors.begin(); it != authors.end(); ++it)
1043 int group = m_graph.AppendGroup(it->c_str());
1044 graphData->SetData(group, RollPercentageOfAuthorship(m_PercentageOfAuthorship[*it]));
1048 // If we have other authors, count them and their commits.
1049 if (!others.empty())
1050 DrawOthers(others, graphData, m_PercentageOfAuthorship);
1052 // Paint the graph now that we're through.
1053 m_graph.Invalidate();
1056 void CStatGraphDlg::ShowCommitsByAuthor()
1058 // Set up the graph.
1059 MyGraphSeries * graphData = PreViewGraph(IDS_STATGRAPH_COMMITSBYAUTHOR,
1060 IDS_STATGRAPH_COMMITSBYAUTHORY,
1061 IDS_STATGRAPH_COMMITSBYAUTHORX);
1062 if(graphData == NULL) return;
1064 // Find out which authors are to be shown and which are to be skipped.
1065 std::list<tstring> authors;
1066 std::list<tstring> others;
1067 FilterSkippedAuthors(authors, others);
1069 // Loop over all authors in the authors list and
1070 // add them to the graph.
1072 if (!authors.empty())
1074 for (std::list<tstring>::iterator it = authors.begin(); it != authors.end(); ++it)
1076 int group = m_graph.AppendGroup(it->c_str());
1077 graphData->SetData(group, m_commitsPerAuthor[*it]);
1081 // If we have other authors, count them and their commits.
1082 if (!others.empty())
1083 DrawOthers(others, graphData, m_commitsPerAuthor);
1085 // Paint the graph now that we're through.
1086 m_graph.Invalidate();
1089 void CStatGraphDlg::ShowByDate(int stringx, int title, IntervalDataMap &data)
1091 if(!PreViewStat(false)) return;
1093 // We need at least one author
1094 if (m_authorNames.empty()) return;
1096 // Set up the graph.
1097 CString temp;
1098 UpdateData();
1099 m_graph.SetGraphType(m_GraphType, m_bStacked);
1100 temp.LoadString(stringx);
1101 m_graph.SetYAxisLabel(temp);
1102 temp.LoadString(title);
1103 m_graph.SetGraphTitle(temp);
1105 m_graph.SetXAxisLabel(GetUnitString());
1107 // Find out which authors are to be shown and which are to be skipped.
1108 std::list<tstring> authors;
1109 std::list<tstring> others;
1110 FilterSkippedAuthors(authors, others);
1112 // Add a graph series for each author.
1113 AuthorDataMap authorGraphMap;
1114 for (std::list<tstring>::iterator it = authors.begin(); it != authors.end(); ++it)
1115 authorGraphMap[*it] = m_graph.AppendGroup(it->c_str());
1116 // If we have skipped authors, add a graph series for all those.
1117 CString sOthers(MAKEINTRESOURCE(IDS_STATGRAPH_OTHERGROUP));
1118 tstring othersName;
1119 if (!others.empty())
1121 temp.Format(_T(" (%Iu)"), others.size());
1122 sOthers += temp;
1123 othersName = (LPCWSTR)sOthers;
1124 authorGraphMap[othersName] = m_graph.AppendGroup(sOthers);
1127 // Mapping to collect commit counts in each interval
1128 AuthorDataMap commitCount;
1130 // Loop over all intervals/weeks and collect filtered data.
1131 // Sum up data in each interval until the time unit changes.
1132 for (int i=m_lastInterval; i>=m_firstInterval; --i)
1134 // Collect data for authors listed by name.
1135 if (!authors.empty())
1137 for (std::list<tstring>::iterator it = authors.begin(); it != authors.end(); ++it)
1139 // Do we have some data for the current author in the current interval?
1140 AuthorDataMap::const_iterator data_it = data[i].find(*it);
1141 if (data_it == data[i].end())
1142 continue;
1143 commitCount[*it] += data_it->second;
1146 // Collect data for all skipped authors.
1147 if (!others.empty())
1149 for (std::list<tstring>::iterator it = others.begin(); it != others.end(); ++it)
1151 // Do we have some data for the author in the current interval?
1152 AuthorDataMap::const_iterator data_it = data[i].find(*it);
1153 if (data_it == data[i].end())
1154 continue;
1155 commitCount[othersName] += data_it->second;
1159 // Create a new data series for this unit/interval.
1160 MyGraphSeries * graphData = new MyGraphSeries();
1161 // Loop over all created graphs and set the corresponding data.
1162 if (!authorGraphMap.empty())
1164 for (AuthorDataMap::const_iterator it = authorGraphMap.begin(); it != authorGraphMap.end(); ++it)
1166 graphData->SetData(it->second, commitCount[it->first]);
1169 graphData->SetLabel(m_unitNames[i].c_str());
1170 m_graph.AddSeries(*graphData);
1171 m_graphDataArray.Add(graphData);
1173 // Reset commit count mapping.
1174 commitCount.clear();
1177 // Paint the graph now that we're through.
1178 m_graph.Invalidate();
1181 void CStatGraphDlg::ShowStats()
1183 if(!PreViewStat(true)) return;
1185 // Now we can use the gathered data to update the stats dialog.
1186 size_t nAuthors = m_authorNames.size();
1188 // Find most and least active author names.
1189 tstring mostActiveAuthor;
1190 tstring leastActiveAuthor;
1191 if (nAuthors > 0)
1193 mostActiveAuthor = m_authorNames.front();
1194 leastActiveAuthor = m_authorNames.back();
1197 // Obtain the statistics for the table.
1198 long nCommitsMin = -1;
1199 long nCommitsMax = -1;
1200 long nFileChangesMin = -1;
1201 long nFileChangesMax = -1;
1203 long nMostActiveMaxCommits = -1;
1204 long nMostActiveMinCommits = -1;
1205 long nLeastActiveMaxCommits = -1;
1206 long nLeastActiveMinCommits = -1;
1208 // Loop over all intervals and find min and max values for commit count and file changes.
1209 // Also store the stats for the most and least active authors.
1210 for (int i=m_firstInterval; i<=m_lastInterval; ++i)
1212 // Loop over all commits in this interval and count the number of commits by all authors.
1213 int commitCount = 0;
1214 AuthorDataMap::iterator commit_endit = m_commitsPerUnitAndAuthor[i].end();
1215 for (AuthorDataMap::iterator commit_it = m_commitsPerUnitAndAuthor[i].begin();
1216 commit_it != commit_endit; ++commit_it)
1218 commitCount += commit_it->second;
1220 if (nCommitsMin == -1 || commitCount < nCommitsMin)
1221 nCommitsMin = commitCount;
1222 if (nCommitsMax == -1 || commitCount > nCommitsMax)
1223 nCommitsMax = commitCount;
1225 // Loop over all commits in this interval and count the number of file changes by all authors.
1226 int fileChangeCount = 0;
1227 AuthorDataMap::iterator filechange_endit = m_filechangesPerUnitAndAuthor[i].end();
1228 for (AuthorDataMap::iterator filechange_it = m_filechangesPerUnitAndAuthor[i].begin();
1229 filechange_it != filechange_endit; ++filechange_it)
1231 fileChangeCount += filechange_it->second;
1233 if (nFileChangesMin == -1 || fileChangeCount < nFileChangesMin)
1234 nFileChangesMin = fileChangeCount;
1235 if (nFileChangesMax == -1 || fileChangeCount > nFileChangesMax)
1236 nFileChangesMax = fileChangeCount;
1238 // also get min/max data for most and least active authors
1239 if (nAuthors > 0)
1241 // check if author is present in this interval
1242 AuthorDataMap::iterator author_it = m_commitsPerUnitAndAuthor[i].find(mostActiveAuthor);
1243 long authorCommits;
1244 if (author_it == m_commitsPerUnitAndAuthor[i].end())
1245 authorCommits = 0;
1246 else
1247 authorCommits = author_it->second;
1248 if (nMostActiveMaxCommits == -1 || authorCommits > nMostActiveMaxCommits)
1249 nMostActiveMaxCommits = authorCommits;
1250 if (nMostActiveMinCommits == -1 || authorCommits < nMostActiveMinCommits)
1251 nMostActiveMinCommits = authorCommits;
1253 author_it = m_commitsPerUnitAndAuthor[i].find(leastActiveAuthor);
1254 if (author_it == m_commitsPerUnitAndAuthor[i].end())
1255 authorCommits = 0;
1256 else
1257 authorCommits = author_it->second;
1258 if (nLeastActiveMaxCommits == -1 || authorCommits > nLeastActiveMaxCommits)
1259 nLeastActiveMaxCommits = authorCommits;
1260 if (nLeastActiveMinCommits == -1 || authorCommits < nLeastActiveMinCommits)
1261 nLeastActiveMinCommits = authorCommits;
1264 if (nMostActiveMaxCommits == -1) nMostActiveMaxCommits = 0;
1265 if (nMostActiveMinCommits == -1) nMostActiveMinCommits = 0;
1266 if (nLeastActiveMaxCommits == -1) nLeastActiveMaxCommits = 0;
1267 if (nLeastActiveMinCommits == -1) nLeastActiveMinCommits = 0;
1269 int nWeeks = m_lastInterval-m_firstInterval;
1270 if (nWeeks == 0)
1271 nWeeks = 1;
1272 // Adjust the labels with the unit type (week, month, ...)
1273 CString labelText;
1274 labelText.Format(IDS_STATGRAPH_NUMBEROFUNIT, GetUnitString());
1275 SetDlgItemText(IDC_NUMWEEK, labelText);
1276 labelText.Format(IDS_STATGRAPH_COMMITSBYUNIT, GetUnitString());
1277 SetDlgItemText(IDC_COMMITSEACHWEEK, labelText);
1278 labelText.Format(IDS_STATGRAPH_FILECHANGESBYUNIT, GetUnitString());
1279 SetDlgItemText(IDC_FILECHANGESEACHWEEK, labelText);
1280 // We have now all data we want and we can fill in the labels...
1281 CString number;
1282 number.Format(_T("%d"), nWeeks);
1283 SetDlgItemText(IDC_NUMWEEKVALUE, number);
1284 number.Format(_T("%Iu"), nAuthors);
1285 SetDlgItemText(IDC_NUMAUTHORVALUE, number);
1286 number.Format(_T("%Id"), m_nTotalCommits);
1287 SetDlgItemText(IDC_NUMCOMMITSVALUE, number);
1288 number.Format(_T("%ld"), m_nTotalFileChanges);
1289 if (m_bDiffFetched)
1290 SetDlgItemText(IDC_NUMFILECHANGESVALUE, number);
1292 number.Format(_T("%Id"), m_parAuthors.GetCount() / nWeeks);
1293 SetDlgItemText(IDC_COMMITSEACHWEEKAVG, number);
1294 number.Format(_T("%ld"), nCommitsMax);
1295 SetDlgItemText(IDC_COMMITSEACHWEEKMAX, number);
1296 number.Format(_T("%ld"), nCommitsMin);
1297 SetDlgItemText(IDC_COMMITSEACHWEEKMIN, number);
1299 number.Format(_T("%ld"), m_nTotalFileChanges / nWeeks);
1300 //SetDlgItemText(IDC_FILECHANGESEACHWEEKAVG, number);
1301 number.Format(_T("%ld"), nFileChangesMax);
1302 //SetDlgItemText(IDC_FILECHANGESEACHWEEKMAX, number);
1303 number.Format(_T("%ld"), nFileChangesMin);
1304 //SetDlgItemText(IDC_FILECHANGESEACHWEEKMIN, number);
1306 number.Format(_T("%ld (%ld (+) %ld (-))"), m_nTotalLinesInc + m_nTotalLinesDec, m_nTotalLinesInc, m_nTotalLinesDec);
1307 if (m_bDiffFetched)
1308 SetDlgItemText(IDC_TOTAL_LINE_WITHOUT_NEW_DEL_VALUE, number);
1309 number.Format(_T("%ld (%ld (+) %ld (-))"), m_nTotalLinesInc + m_nTotalLinesDec + m_nTotalLinesNew + m_nTotalLinesDel,
1310 m_nTotalLinesInc + m_nTotalLinesNew, m_nTotalLinesDec + m_nTotalLinesDel);
1311 if (m_bDiffFetched)
1312 SetDlgItemText(IDC_TOTAL_LINE_WITH_NEW_DEL_VALUE, number);
1314 if (nAuthors == 0)
1316 SetDlgItemText(IDC_MOSTACTIVEAUTHORNAME, _T(""));
1317 SetDlgItemText(IDC_MOSTACTIVEAUTHORAVG, _T("0"));
1318 SetDlgItemText(IDC_MOSTACTIVEAUTHORMAX, _T("0"));
1319 SetDlgItemText(IDC_MOSTACTIVEAUTHORMIN, _T("0"));
1320 SetDlgItemText(IDC_LEASTACTIVEAUTHORNAME, _T(""));
1321 SetDlgItemText(IDC_LEASTACTIVEAUTHORAVG, _T("0"));
1322 SetDlgItemText(IDC_LEASTACTIVEAUTHORMAX, _T("0"));
1323 SetDlgItemText(IDC_LEASTACTIVEAUTHORMIN, _T("0"));
1325 else
1327 SetDlgItemText(IDC_MOSTACTIVEAUTHORNAME, mostActiveAuthor.c_str());
1328 number.Format(_T("%ld"), m_commitsPerAuthor[mostActiveAuthor] / nWeeks);
1329 SetDlgItemText(IDC_MOSTACTIVEAUTHORAVG, number);
1330 number.Format(_T("%ld"), nMostActiveMaxCommits);
1331 SetDlgItemText(IDC_MOSTACTIVEAUTHORMAX, number);
1332 number.Format(_T("%ld"), nMostActiveMinCommits);
1333 SetDlgItemText(IDC_MOSTACTIVEAUTHORMIN, number);
1335 SetDlgItemText(IDC_LEASTACTIVEAUTHORNAME, leastActiveAuthor.c_str());
1336 number.Format(_T("%ld"), m_commitsPerAuthor[leastActiveAuthor] / nWeeks);
1337 SetDlgItemText(IDC_LEASTACTIVEAUTHORAVG, number);
1338 number.Format(_T("%ld"), nLeastActiveMaxCommits);
1339 SetDlgItemText(IDC_LEASTACTIVEAUTHORMAX, number);
1340 number.Format(_T("%ld"), nLeastActiveMinCommits);
1341 SetDlgItemText(IDC_LEASTACTIVEAUTHORMIN, number);
1345 int CStatGraphDlg::RollPercentageOfAuthorship(double it)
1346 { return (int)it + (it - (int)it >= 0.5);}
1348 void CStatGraphDlg::OnCbnSelchangeGraphcombo()
1350 UpdateData();
1352 Metrics useMetric = (Metrics) m_cGraphType.GetItemData(m_cGraphType.GetCurSel());
1353 switch (useMetric )
1355 case AllStat:
1356 case CommitsByDate:
1357 // by date
1358 m_btnGraphLine.EnableWindow(TRUE);
1359 m_btnGraphLineStacked.EnableWindow(TRUE);
1360 m_btnGraphPie.EnableWindow(TRUE);
1361 m_GraphType = MyGraph::Line;
1362 m_bStacked = false;
1363 break;
1364 case PercentageOfAuthorship:
1365 case CommitsByAuthor:
1366 // by author
1367 m_btnGraphLine.EnableWindow(FALSE);
1368 m_btnGraphLineStacked.EnableWindow(FALSE);
1369 m_btnGraphPie.EnableWindow(TRUE);
1370 m_GraphType = MyGraph::Bar;
1371 m_bStacked = false;
1372 break;
1374 RedrawGraph();
1378 int CStatGraphDlg::GetUnitCount()
1380 if (m_nDays < 8)
1381 return m_nDays;
1382 if (m_nWeeks < 15)
1383 return m_nWeeks;
1384 if (m_nWeeks < 80)
1385 return (m_nWeeks/4)+1;
1386 if (m_nWeeks < 320)
1387 return (m_nWeeks/13)+1; // quarters
1388 return (m_nWeeks/52)+1;
1391 int CStatGraphDlg::GetUnit(const CTime& time)
1393 if (m_nDays < 8)
1394 return time.GetMonth()*100 + time.GetDay(); // month*100+day as the unit
1395 if (m_nWeeks < 15)
1396 return GetCalendarWeek(time);
1397 if (m_nWeeks < 80)
1398 return time.GetMonth();
1399 if (m_nWeeks < 320)
1400 return ((time.GetMonth()-1)/3)+1; // quarters
1401 return time.GetYear();
1404 CStatGraphDlg::UnitType CStatGraphDlg::GetUnitType()
1406 if (m_nDays < 8)
1407 return Days;
1408 if (m_nWeeks < 15)
1409 return Weeks;
1410 if (m_nWeeks < 80)
1411 return Months;
1412 if (m_nWeeks < 320)
1413 return Quarters;
1414 return Years;
1417 CString CStatGraphDlg::GetUnitString()
1419 if (m_nDays < 8)
1420 return CString(MAKEINTRESOURCE(IDS_STATGRAPH_COMMITSBYDATEXDAY));
1421 if (m_nWeeks < 15)
1422 return CString(MAKEINTRESOURCE(IDS_STATGRAPH_COMMITSBYDATEXWEEK));
1423 if (m_nWeeks < 80)
1424 return CString(MAKEINTRESOURCE(IDS_STATGRAPH_COMMITSBYDATEXMONTH));
1425 if (m_nWeeks < 320)
1426 return CString(MAKEINTRESOURCE(IDS_STATGRAPH_COMMITSBYDATEXQUARTER));
1427 return CString(MAKEINTRESOURCE(IDS_STATGRAPH_COMMITSBYDATEXYEAR));
1430 CString CStatGraphDlg::GetUnitLabel(int unit, CTime &lasttime)
1432 CString temp;
1433 switch (GetUnitType())
1435 case Days:
1437 // month*100+day as the unit
1438 int day = unit % 100;
1439 int month = unit / 100;
1440 switch (m_langOrder)
1442 case 0: // month day year
1443 temp.Format(_T("%d/%d/%.2d"), month, day, lasttime.GetYear()%100);
1444 break;
1445 case 1: // day month year
1446 default:
1447 temp.Format(_T("%d/%d/%.2d"), day, month, lasttime.GetYear()%100);
1448 break;
1449 case 2: // year month day
1450 temp.Format(_T("%.2d/%d/%d"), lasttime.GetYear()%100, month, day);
1451 break;
1454 break;
1455 case Weeks:
1457 int year = lasttime.GetYear();
1458 if ((unit == 1)&&(lasttime.GetMonth() == 12))
1459 year += 1;
1461 switch (m_langOrder)
1463 case 0: // month day year
1464 case 1: // day month year
1465 default:
1466 temp.Format(_T("%d/%.2d"), unit, year%100);
1467 break;
1468 case 2: // year month day
1469 temp.Format(_T("%.2d/%d"), year%100, unit);
1470 break;
1473 break;
1474 case Months:
1475 switch (m_langOrder)
1477 case 0: // month day year
1478 case 1: // day month year
1479 default:
1480 temp.Format(_T("%d/%.2d"), unit, lasttime.GetYear()%100);
1481 break;
1482 case 2: // year month day
1483 temp.Format(_T("%.2d/%d"), lasttime.GetYear()%100, unit);
1484 break;
1486 break;
1487 case Quarters:
1488 switch (m_langOrder)
1490 case 0: // month day year
1491 case 1: // day month year
1492 default:
1493 temp.Format(_T("%d/%.2d"), unit, lasttime.GetYear()%100);
1494 break;
1495 case 2: // year month day
1496 temp.Format(_T("%.2d/%d"), lasttime.GetYear()%100, unit);
1497 break;
1499 break;
1500 case Years:
1501 temp.Format(_T("%d"), unit);
1502 break;
1504 return temp;
1507 void CStatGraphDlg::OnHScroll(UINT nSBCode, UINT nPos, CScrollBar* pScrollBar)
1509 if (nSBCode == TB_THUMBTRACK)
1510 return CDialog::OnHScroll(nSBCode, nPos, pScrollBar);
1512 ShowSelectStat((Metrics) m_cGraphType.GetItemData(m_cGraphType.GetCurSel()));
1513 CDialog::OnHScroll(nSBCode, nPos, pScrollBar);
1516 void CStatGraphDlg::OnNeedText(NMHDR *pnmh, LRESULT * /*pResult*/)
1518 TOOLTIPTEXT* pttt = (TOOLTIPTEXT*) pnmh;
1519 if (pttt->hdr.idFrom == (UINT_PTR) m_Skipper.GetSafeHwnd())
1521 size_t included_authors_count = m_Skipper.GetPos();
1522 // if we only leave out one author, still include him with his name
1523 if (included_authors_count + 1 == m_authorNames.size())
1524 ++included_authors_count;
1526 // find the minimum number of commits that the shown authors have
1527 int min_commits = 0;
1528 included_authors_count = min(included_authors_count, m_authorNames.size());
1529 std::list<tstring>::iterator author_it = m_authorNames.begin();
1530 advance(author_it, included_authors_count);
1531 if (author_it != m_authorNames.begin())
1532 min_commits = m_commitsPerAuthor[ *(--author_it) ];
1534 CString string;
1535 int percentage = int(min_commits*100.0/(m_nTotalCommits ? m_nTotalCommits : 1));
1536 string.Format(IDS_STATGRAPH_AUTHORSLIDER_TT, m_Skipper.GetPos(), min_commits, percentage);
1537 StringCchCopy(pttt->szText, _countof(pttt->szText), (LPCTSTR) string);
1541 void CStatGraphDlg::AuthorsCaseSensitiveChanged()
1543 UpdateData(); // update checkbox state
1544 GatherData(FALSE, TRUE); // first regenerate the statistics data
1545 RedrawGraph(); // then update the current statistics page
1548 void CStatGraphDlg::SortModeChanged()
1550 UpdateData(); // update checkbox state
1551 RedrawGraph(); // then update the current statistics page
1554 void CStatGraphDlg::ClearGraph()
1556 m_graph.Clear();
1557 for (int j=0; j<m_graphDataArray.GetCount(); ++j)
1558 delete ((MyGraphSeries *)m_graphDataArray.GetAt(j));
1559 m_graphDataArray.RemoveAll();
1562 void CStatGraphDlg::RedrawGraph()
1564 EnableDisableMenu();
1565 m_btnGraphBar.SetState(BST_UNCHECKED);
1566 m_btnGraphBarStacked.SetState(BST_UNCHECKED);
1567 m_btnGraphLine.SetState(BST_UNCHECKED);
1568 m_btnGraphLineStacked.SetState(BST_UNCHECKED);
1569 m_btnGraphPie.SetState(BST_UNCHECKED);
1571 if ((m_GraphType == MyGraph::Bar)&&(m_bStacked))
1573 m_btnGraphBarStacked.SetState(BST_CHECKED);
1575 if ((m_GraphType == MyGraph::Bar)&&(!m_bStacked))
1577 m_btnGraphBar.SetState(BST_CHECKED);
1579 if ((m_GraphType == MyGraph::Line)&&(m_bStacked))
1581 m_btnGraphLineStacked.SetState(BST_CHECKED);
1583 if ((m_GraphType == MyGraph::Line)&&(!m_bStacked))
1585 m_btnGraphLine.SetState(BST_CHECKED);
1587 if (m_GraphType == MyGraph::PieChart)
1589 m_btnGraphPie.SetState(BST_CHECKED);
1592 UpdateData();
1593 ShowSelectStat((Metrics) m_cGraphType.GetItemData(m_cGraphType.GetCurSel()), true);
1595 void CStatGraphDlg::OnBnClickedGraphbarbutton()
1597 m_GraphType = MyGraph::Bar;
1598 m_bStacked = false;
1599 RedrawGraph();
1602 void CStatGraphDlg::OnBnClickedGraphbarstackedbutton()
1604 m_GraphType = MyGraph::Bar;
1605 m_bStacked = true;
1606 RedrawGraph();
1609 void CStatGraphDlg::OnBnClickedGraphlinebutton()
1611 m_GraphType = MyGraph::Line;
1612 m_bStacked = false;
1613 RedrawGraph();
1616 void CStatGraphDlg::OnBnClickedGraphlinestackedbutton()
1618 m_GraphType = MyGraph::Line;
1619 m_bStacked = true;
1620 RedrawGraph();
1623 void CStatGraphDlg::OnBnClickedGraphpiebutton()
1625 m_GraphType = MyGraph::PieChart;
1626 m_bStacked = false;
1627 RedrawGraph();
1630 BOOL CStatGraphDlg::PreTranslateMessage(MSG* pMsg)
1632 if (NULL != m_pToolTip)
1633 m_pToolTip->RelayEvent(pMsg);
1635 return CStandAloneDialogTmpl<CResizableDialog>::PreTranslateMessage(pMsg);
1638 void CStatGraphDlg::EnableDisableMenu()
1640 UINT nEnable = MF_BYCOMMAND;
1642 Metrics SelectMetric = (Metrics) m_cGraphType.GetItemData(m_cGraphType.GetCurSel());
1644 nEnable |= (SelectMetric > TextStatStart && SelectMetric < TextStatEnd)
1645 ? (MF_DISABLED | MF_GRAYED) : MF_ENABLED;
1647 GetMenu()->EnableMenuItem(ID_FILE_SAVESTATGRAPHAS, nEnable);
1650 void CStatGraphDlg::OnFileSavestatgraphas()
1652 CString tempfile;
1653 int filterindex = 0;
1654 if (CAppUtils::FileOpenSave(tempfile, &filterindex, IDS_REVGRAPH_SAVEPIC, IDS_STATPICFILEFILTER, false, m_hWnd))
1656 // if the user doesn't specify a file extension, default to
1657 // wmf and add that extension to the filename. But only if the
1658 // user chose the 'pictures' filter. The filename isn't changed
1659 // if the 'All files' filter was chosen.
1660 CString extension;
1661 int dotPos = tempfile.ReverseFind('.');
1662 int slashPos = tempfile.ReverseFind('\\');
1663 if (dotPos > slashPos)
1664 extension = tempfile.Mid(dotPos);
1665 if ((filterindex == 1)&&(extension.IsEmpty()))
1667 extension = _T(".wmf");
1668 tempfile += extension;
1670 SaveGraph(tempfile);
1674 void CStatGraphDlg::SaveGraph(CString sFilename)
1676 CString extension = CPathUtils::GetFileExtFromPath(sFilename);
1677 if (extension.CompareNoCase(_T(".wmf"))==0)
1679 // save the graph as an enhanced meta file
1680 CMyMetaFileDC wmfDC;
1681 wmfDC.CreateEnhanced(NULL, sFilename, NULL, _T("TortoiseGit\0Statistics\0\0"));
1682 wmfDC.SetAttribDC(GetDC()->GetSafeHdc());
1683 RedrawGraph();
1684 m_graph.DrawGraph(wmfDC);
1685 HENHMETAFILE hemf = wmfDC.CloseEnhanced();
1686 DeleteEnhMetaFile(hemf);
1688 else
1690 // save the graph as a pixel picture instead of a vector picture
1691 // create dc to paint on
1694 CWindowDC ddc(this);
1695 CDC dc;
1696 if (!dc.CreateCompatibleDC(&ddc))
1698 ShowErrorMessage();
1699 return;
1701 CRect rect;
1702 GetDlgItem(IDC_GRAPH)->GetClientRect(&rect);
1703 HBITMAP hbm = ::CreateCompatibleBitmap(ddc.m_hDC, rect.Width(), rect.Height());
1704 if (hbm==0)
1706 ShowErrorMessage();
1707 return;
1709 HBITMAP oldbm = (HBITMAP)dc.SelectObject(hbm);
1710 // paint the whole graph
1711 RedrawGraph();
1712 m_graph.DrawGraph(dc);
1713 // now use GDI+ to save the picture
1714 CLSID encoderClsid;
1715 GdiplusStartupInput gdiplusStartupInput;
1716 ULONG_PTR gdiplusToken;
1717 CString sErrormessage;
1718 if (GdiplusStartup( &gdiplusToken, &gdiplusStartupInput, NULL )==Ok)
1721 Bitmap bitmap(hbm, NULL);
1722 if (bitmap.GetLastStatus()==Ok)
1724 // Get the CLSID of the encoder.
1725 int ret = 0;
1726 if (CPathUtils::GetFileExtFromPath(sFilename).CompareNoCase(_T(".png"))==0)
1727 ret = GetEncoderClsid(L"image/png", &encoderClsid);
1728 else if (CPathUtils::GetFileExtFromPath(sFilename).CompareNoCase(_T(".jpg"))==0)
1729 ret = GetEncoderClsid(L"image/jpeg", &encoderClsid);
1730 else if (CPathUtils::GetFileExtFromPath(sFilename).CompareNoCase(_T(".jpeg"))==0)
1731 ret = GetEncoderClsid(L"image/jpeg", &encoderClsid);
1732 else if (CPathUtils::GetFileExtFromPath(sFilename).CompareNoCase(_T(".bmp"))==0)
1733 ret = GetEncoderClsid(L"image/bmp", &encoderClsid);
1734 else if (CPathUtils::GetFileExtFromPath(sFilename).CompareNoCase(_T(".gif"))==0)
1735 ret = GetEncoderClsid(L"image/gif", &encoderClsid);
1736 else
1738 sFilename += _T(".jpg");
1739 ret = GetEncoderClsid(L"image/jpeg", &encoderClsid);
1741 if (ret >= 0)
1743 CStringW tfile = CStringW(sFilename);
1744 bitmap.Save(tfile, &encoderClsid, NULL);
1746 else
1748 sErrormessage.Format(IDS_REVGRAPH_ERR_NOENCODER, CPathUtils::GetFileExtFromPath(sFilename));
1751 else
1753 sErrormessage.LoadString(IDS_REVGRAPH_ERR_NOBITMAP);
1756 GdiplusShutdown(gdiplusToken);
1758 else
1760 sErrormessage.LoadString(IDS_REVGRAPH_ERR_GDIINIT);
1762 dc.SelectObject(oldbm);
1763 dc.DeleteDC();
1764 if (!sErrormessage.IsEmpty())
1766 ::MessageBox(m_hWnd, sErrormessage, _T("TortoiseGit"), MB_ICONERROR);
1769 catch (CException * pE)
1771 TCHAR szErrorMsg[2048] = { 0 };
1772 pE->GetErrorMessage(szErrorMsg, 2048);
1773 pE->Delete();
1774 ::MessageBox(m_hWnd, szErrorMsg, _T("TortoiseGit"), MB_ICONERROR);
1779 int CStatGraphDlg::GetEncoderClsid(const WCHAR* format, CLSID* pClsid)
1781 UINT num = 0; // number of image encoders
1782 UINT size = 0; // size of the image encoder array in bytes
1784 ImageCodecInfo* pImageCodecInfo = NULL;
1786 if (GetImageEncodersSize(&num, &size)!=Ok)
1787 return -1;
1788 if (size == 0)
1789 return -1; // Failure
1791 pImageCodecInfo = (ImageCodecInfo*)(malloc(size));
1792 if (pImageCodecInfo == NULL)
1793 return -1; // Failure
1795 if (GetImageEncoders(num, size, pImageCodecInfo)==Ok)
1797 for (UINT j = 0; j < num; ++j)
1799 if (wcscmp(pImageCodecInfo[j].MimeType, format) == 0)
1801 *pClsid = pImageCodecInfo[j].Clsid;
1802 free(pImageCodecInfo);
1803 return j; // Success
1807 free (pImageCodecInfo);
1808 return -1; // Failure
1811 void CStatGraphDlg::StoreCurrentGraphType()
1813 UpdateData();
1814 DWORD graphtype = static_cast<DWORD>(m_cGraphType.GetItemData(m_cGraphType.GetCurSel()));
1815 // encode the current chart type
1816 DWORD statspage = graphtype*10;
1817 if ((m_GraphType == MyGraph::Bar)&&(m_bStacked))
1819 statspage += 1;
1821 if ((m_GraphType == MyGraph::Bar)&&(!m_bStacked))
1823 statspage += 2;
1825 if ((m_GraphType == MyGraph::Line)&&(m_bStacked))
1827 statspage += 3;
1829 if ((m_GraphType == MyGraph::Line)&&(!m_bStacked))
1831 statspage += 4;
1833 if (m_GraphType == MyGraph::PieChart)
1835 statspage += 5;
1838 // store current chart type in registry
1839 CRegDWORD lastStatsPage(_T("Software\\TortoiseGit\\LastViewedStatsPage"), 0);
1840 lastStatsPage = statspage;
1842 CRegDWORD regAuthors(_T("Software\\TortoiseGit\\StatAuthorsCaseSensitive"));
1843 regAuthors = m_bAuthorsCaseSensitive;
1845 CRegDWORD regSort(_T("Software\\TortoiseGit\\StatSortByCommitCount"));
1846 regSort = m_bSortByCommitCount;
1849 void CStatGraphDlg::ShowErrorMessage()
1851 CFormatMessageWrapper errorDetails;
1852 if (errorDetails)
1853 MessageBox( errorDetails, _T("Error"), MB_OK | MB_ICONINFORMATION );
1856 void CStatGraphDlg::ShowSelectStat(Metrics SelectedMetric, bool reloadSkiper /* = false */)
1858 switch (SelectedMetric)
1860 case AllStat:
1861 LoadListOfAuthors(m_commitsPerAuthor, reloadSkiper);
1862 ShowStats();
1863 break;
1864 case CommitsByDate:
1865 LoadListOfAuthors(m_commitsPerAuthor, reloadSkiper);
1866 ShowByDate(IDS_STATGRAPH_COMMITSBYDATEY, IDS_STATGRAPH_COMMITSBYDATE, m_commitsPerUnitAndAuthor);
1867 break;
1868 case LinesWByDate:
1869 OnBnClickedFetchDiff();
1870 LoadListOfAuthors(m_commitsPerAuthor, reloadSkiper);
1871 ShowByDate(IDS_STATGRAPH_LINES_BYDATE_W_Y, IDS_STATGRAPH_LINES_BYDATE_W, m_LinesWPerUnitAndAuthor);
1872 break;
1873 case LinesWOByDate:
1874 OnBnClickedFetchDiff();
1875 LoadListOfAuthors(m_commitsPerAuthor, reloadSkiper);
1876 ShowByDate(IDS_STATGRAPH_LINES_BYDATE_WO_Y, IDS_STATGRAPH_LINES_BYDATE_WO, m_LinesWOPerUnitAndAuthor);
1877 break;
1878 case CommitsByAuthor:
1879 LoadListOfAuthors(m_commitsPerAuthor, reloadSkiper);
1880 ShowCommitsByAuthor();
1881 break;
1882 case PercentageOfAuthorship:
1883 OnBnClickedFetchDiff();
1884 LoadListOfAuthors(m_PercentageOfAuthorship, reloadSkiper, true);
1885 ShowPercentageOfAuthorship();
1886 break;
1887 default:
1888 ShowErrorMessage();
1892 double CStatGraphDlg::CoeffContribution(int distFromEnd) { return distFromEnd ? 1.0 / m_CoeffAuthorShip * distFromEnd : 1;}
1895 template <class MAP>
1896 void CStatGraphDlg::DrawOthers(const std::list<tstring> &others, MyGraphSeries *graphData, MAP &map)
1898 int nCommits = 0;
1899 for (std::list<tstring>::const_iterator it = others.begin(); it != others.end(); ++it)
1901 nCommits += RollPercentageOfAuthorship(map[*it]);
1904 CString temp;
1905 temp.Format(_T(" (%Iu)"), others.size());
1907 CString sOthers(MAKEINTRESOURCE(IDS_STATGRAPH_OTHERGROUP));
1908 sOthers += temp;
1909 int group = m_graph.AppendGroup(sOthers);
1910 graphData->SetData(group, (int)nCommits);
1914 template <class MAP>
1915 void CStatGraphDlg::LoadListOfAuthors (MAP &map, bool reloadSkiper/*= false*/, bool compare /*= false*/)
1917 m_authorNames.clear();
1918 if (!map.empty())
1920 for (MAP::const_iterator it = map.begin(); it != map.end(); ++it)
1922 if ((compare && RollPercentageOfAuthorship(map[it->first]) != 0) || !compare)
1923 m_authorNames.push_back(it->first);
1927 // Sort the list of authors based on commit count
1928 m_authorNames.sort(MoreCommitsThan< MAP::referent_type>(map));
1930 // Set Skipper
1931 SetSkipper(reloadSkiper);
1935 void CStatGraphDlg::OnBnClickedFetchDiff()
1937 if (m_bDiffFetched)
1938 return;
1939 if (GatherData(TRUE))
1940 return;
1941 this->m_bDiffFetched = TRUE;
1942 GetDlgItem(IDC_CALC_DIFF)->ShowWindow(!m_bDiffFetched);
1944 ShowStats();