Cleanup
[TortoiseGit.git] / src / TortoiseProc / StatGraphDlg.cpp
blob9e67cac6a9c64367fcc128c666e7a8530f796c3c
1 // TortoiseGit - a Windows shell extension for easy version control
3 // Copyright (C) 2008-2012 - TortoiseGit
4 // Copyright (C) 2003-2011 - 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"
31 #include <iterator>
32 #include <cmath>
33 #include <locale>
34 #include <list>
35 #include <utility>
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 /*=NULL*/)
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)
68 m_parDates = NULL;
69 m_parFileChanges = NULL;
70 m_parAuthors = NULL;
71 m_pToolTip = NULL;
74 CStatGraphDlg::~CStatGraphDlg()
76 ClearGraph();
77 delete m_pToolTip;
80 void CStatGraphDlg::OnOK() {
81 StoreCurrentGraphType();
82 __super::OnOK();
85 void CStatGraphDlg::OnCancel() {
86 StoreCurrentGraphType();
87 __super::OnCancel();
90 void CStatGraphDlg::DoDataExchange(CDataExchange* pDX)
92 CResizableStandAloneDialog::DoDataExchange(pDX);
93 DDX_Control(pDX, IDC_GRAPH, m_graph);
94 DDX_Control(pDX, IDC_GRAPHCOMBO, m_cGraphType);
95 DDX_Control(pDX, IDC_SKIPPER, m_Skipper);
96 DDX_Check(pDX, IDC_AUTHORSCASESENSITIVE, m_bAuthorsCaseSensitive);
97 DDX_Check(pDX, IDC_SORTBYCOMMITCOUNT, m_bSortByCommitCount);
98 DDX_Control(pDX, IDC_GRAPHBARBUTTON, m_btnGraphBar);
99 DDX_Control(pDX, IDC_GRAPHBARSTACKEDBUTTON, m_btnGraphBarStacked);
100 DDX_Control(pDX, IDC_GRAPHLINEBUTTON, m_btnGraphLine);
101 DDX_Control(pDX, IDC_GRAPHLINESTACKEDBUTTON, m_btnGraphLineStacked);
102 DDX_Control(pDX, IDC_GRAPHPIEBUTTON, m_btnGraphPie);
106 BEGIN_MESSAGE_MAP(CStatGraphDlg, CResizableStandAloneDialog)
107 ON_CBN_SELCHANGE(IDC_GRAPHCOMBO, OnCbnSelchangeGraphcombo)
108 ON_WM_HSCROLL()
109 ON_NOTIFY(TTN_NEEDTEXT, NULL, OnNeedText)
110 ON_BN_CLICKED(IDC_AUTHORSCASESENSITIVE, &CStatGraphDlg::AuthorsCaseSensitiveChanged)
111 ON_BN_CLICKED(IDC_SORTBYCOMMITCOUNT, &CStatGraphDlg::SortModeChanged)
112 ON_BN_CLICKED(IDC_GRAPHBARBUTTON, &CStatGraphDlg::OnBnClickedGraphbarbutton)
113 ON_BN_CLICKED(IDC_GRAPHBARSTACKEDBUTTON, &CStatGraphDlg::OnBnClickedGraphbarstackedbutton)
114 ON_BN_CLICKED(IDC_GRAPHLINEBUTTON, &CStatGraphDlg::OnBnClickedGraphlinebutton)
115 ON_BN_CLICKED(IDC_GRAPHLINESTACKEDBUTTON, &CStatGraphDlg::OnBnClickedGraphlinestackedbutton)
116 ON_BN_CLICKED(IDC_GRAPHPIEBUTTON, &CStatGraphDlg::OnBnClickedGraphpiebutton)
117 ON_COMMAND(ID_FILE_SAVESTATGRAPHAS, &CStatGraphDlg::OnFileSavestatgraphas)
118 END_MESSAGE_MAP()
120 void CStatGraphDlg::LoadStatQueries (__in UINT curStr, Metrics loadMetric, bool setDef /* = false */)
122 CString temp;
123 temp.LoadString(curStr);
124 int sel = m_cGraphType.AddString(temp);
125 m_cGraphType.SetItemData(sel, loadMetric);
127 if (setDef) m_cGraphType.SetCurSel(sel);
130 void CStatGraphDlg::SetSkipper (bool reloadSkiper)
132 // We need to limit the number of authors due to GUI resource limitation.
133 // However, since author #251 will properly have < 1000th of the commits,
134 // the resolution limit of the screen will already not allow for displaying
135 // it in a reasonable way
137 int max_authors_count = max(1, (int)min(m_authorNames.size(), 250) );
138 m_Skipper.SetRange (1, max_authors_count);
139 m_Skipper.SetPageSize(5);
141 if (reloadSkiper)
142 m_Skipper.SetPos (max_authors_count);
145 BOOL CStatGraphDlg::OnInitDialog()
147 CResizableStandAloneDialog::OnInitDialog();
149 m_pToolTip = new CToolTipCtrl;
150 if (m_pToolTip->Create(this))
152 m_pToolTip->AddTool(&m_btnGraphPie, IDS_STATGRAPH_PIEBUTTON_TT);
153 m_pToolTip->AddTool(&m_btnGraphLineStacked, IDS_STATGRAPH_LINESTACKEDBUTTON_TT);
154 m_pToolTip->AddTool(&m_btnGraphLine, IDS_STATGRAPH_LINEBUTTON_TT);
155 m_pToolTip->AddTool(&m_btnGraphBarStacked, IDS_STATGRAPH_BARSTACKEDBUTTON_TT);
156 m_pToolTip->AddTool(&m_btnGraphBar, IDS_STATGRAPH_BARBUTTON_TT);
158 m_pToolTip->Activate(TRUE);
161 m_bAuthorsCaseSensitive = DWORD(CRegDWORD(_T("Software\\TortoiseGit\\StatAuthorsCaseSensitive")));
162 m_bSortByCommitCount = DWORD(CRegDWORD(_T("Software\\TortoiseGit\\StatSortByCommitCount")));
163 UpdateData(FALSE);
165 //Load statistical queries
166 LoadStatQueries(IDS_STATGRAPH_STATS, AllStat, true);
167 LoadStatQueries(IDS_STATGRAPH_COMMITSBYDATE, CommitsByDate);
168 LoadStatQueries(IDS_STATGRAPH_COMMITSBYAUTHOR, CommitsByAuthor);
169 LoadStatQueries(IDS_STATGRAPH_PERCENTAGE_OF_AUTHORSHIP, PercentageOfAuthorship);
171 // set the dialog title to "Statistics - path/to/whatever/we/show/the/statistics/for"
172 CString sTitle;
173 GetWindowText(sTitle);
174 CAppUtils::SetWindowTitle(m_hWnd, m_path.GetUIPathString(), sTitle);
176 m_btnGraphBar.SetImage((HICON)LoadImage(AfxGetResourceHandle(), MAKEINTRESOURCE(IDI_GRAPHBAR), IMAGE_ICON, 16, 16, LR_DEFAULTCOLOR));
177 m_btnGraphBar.SizeToContent();
178 m_btnGraphBar.Invalidate();
179 m_btnGraphBarStacked.SetImage((HICON)LoadImage(AfxGetResourceHandle(), MAKEINTRESOURCE(IDI_GRAPHBARSTACKED), IMAGE_ICON, 16, 16, LR_DEFAULTCOLOR));
180 m_btnGraphBarStacked.SizeToContent();
181 m_btnGraphBarStacked.Invalidate();
182 m_btnGraphLine.SetImage((HICON)LoadImage(AfxGetResourceHandle(), MAKEINTRESOURCE(IDI_GRAPHLINE), IMAGE_ICON, 16, 16, LR_DEFAULTCOLOR));
183 m_btnGraphLine.SizeToContent();
184 m_btnGraphLine.Invalidate();
185 m_btnGraphLineStacked.SetImage((HICON)LoadImage(AfxGetResourceHandle(), MAKEINTRESOURCE(IDI_GRAPHLINESTACKED), IMAGE_ICON, 16, 16, LR_DEFAULTCOLOR));
186 m_btnGraphLineStacked.SizeToContent();
187 m_btnGraphLineStacked.Invalidate();
188 m_btnGraphPie.SetImage((HICON)LoadImage(AfxGetResourceHandle(), MAKEINTRESOURCE(IDI_GRAPHPIE), IMAGE_ICON, 16, 16, LR_DEFAULTCOLOR));
189 m_btnGraphPie.SizeToContent();
190 m_btnGraphPie.Invalidate();
192 AddAnchor(IDC_GRAPHTYPELABEL, TOP_LEFT);
193 AddAnchor(IDC_GRAPH, TOP_LEFT, BOTTOM_RIGHT);
194 AddAnchor(IDC_GRAPHCOMBO, TOP_LEFT, TOP_RIGHT);
196 AddAnchor(IDC_NUMWEEK, TOP_LEFT);
197 AddAnchor(IDC_NUMWEEKVALUE, TOP_RIGHT);
198 AddAnchor(IDC_NUMAUTHOR, TOP_LEFT);
199 AddAnchor(IDC_NUMAUTHORVALUE, TOP_RIGHT);
200 AddAnchor(IDC_NUMCOMMITS, TOP_LEFT);
201 AddAnchor(IDC_NUMCOMMITSVALUE, TOP_RIGHT);
202 AddAnchor(IDC_NUMFILECHANGES, TOP_LEFT);
203 AddAnchor(IDC_NUMFILECHANGESVALUE, TOP_RIGHT);
205 AddAnchor(IDC_AVG, TOP_RIGHT);
206 AddAnchor(IDC_MIN, TOP_RIGHT);
207 AddAnchor(IDC_MAX, TOP_RIGHT);
208 AddAnchor(IDC_COMMITSEACHWEEK, TOP_LEFT);
209 AddAnchor(IDC_MOSTACTIVEAUTHOR, TOP_LEFT);
210 AddAnchor(IDC_LEASTACTIVEAUTHOR, TOP_LEFT);
211 AddAnchor(IDC_MOSTACTIVEAUTHORNAME, TOP_LEFT, TOP_RIGHT);
212 AddAnchor(IDC_LEASTACTIVEAUTHORNAME, TOP_LEFT, TOP_RIGHT);
213 AddAnchor(IDC_FILECHANGESEACHWEEK, TOP_LEFT);
214 AddAnchor(IDC_COMMITSEACHWEEKAVG, TOP_RIGHT);
215 AddAnchor(IDC_COMMITSEACHWEEKMIN, TOP_RIGHT);
216 AddAnchor(IDC_COMMITSEACHWEEKMAX, TOP_RIGHT);
217 AddAnchor(IDC_MOSTACTIVEAUTHORAVG, TOP_RIGHT);
218 AddAnchor(IDC_MOSTACTIVEAUTHORMIN, TOP_RIGHT);
219 AddAnchor(IDC_MOSTACTIVEAUTHORMAX, TOP_RIGHT);
220 AddAnchor(IDC_LEASTACTIVEAUTHORAVG, TOP_RIGHT);
221 AddAnchor(IDC_LEASTACTIVEAUTHORMIN, TOP_RIGHT);
222 AddAnchor(IDC_LEASTACTIVEAUTHORMAX, TOP_RIGHT);
223 AddAnchor(IDC_FILECHANGESEACHWEEKAVG, TOP_RIGHT);
224 AddAnchor(IDC_FILECHANGESEACHWEEKMIN, TOP_RIGHT);
225 AddAnchor(IDC_FILECHANGESEACHWEEKMAX, TOP_RIGHT);
227 AddAnchor(IDC_GRAPHBARBUTTON, BOTTOM_RIGHT);
228 AddAnchor(IDC_GRAPHBARSTACKEDBUTTON, BOTTOM_RIGHT);
229 AddAnchor(IDC_GRAPHLINEBUTTON, BOTTOM_RIGHT);
230 AddAnchor(IDC_GRAPHLINESTACKEDBUTTON, BOTTOM_RIGHT);
231 AddAnchor(IDC_GRAPHPIEBUTTON, BOTTOM_RIGHT);
233 AddAnchor(IDC_AUTHORSCASESENSITIVE, BOTTOM_LEFT);
234 AddAnchor(IDC_SORTBYCOMMITCOUNT, BOTTOM_LEFT);
235 AddAnchor(IDC_SKIPPER, BOTTOM_LEFT, BOTTOM_RIGHT);
236 AddAnchor(IDC_SKIPPERLABEL, BOTTOM_LEFT);
237 AddAnchor(IDOK, BOTTOM_RIGHT);
238 EnableSaveRestore(_T("StatGraphDlg"));
240 // gather statistics data, only needs to be updated when the checkbox with
241 // the case sensitivity of author names is changed
242 GatherData();
244 // set the min/max values on the skipper
245 SetSkipper (true);
247 // we use a stats page encoding here, 0 stands for the statistics dialog
248 CRegDWORD lastStatsPage = CRegDWORD(_T("Software\\TortoiseGit\\LastViewedStatsPage"), 0);
250 // open last viewed statistics page as first page
251 int graphtype = lastStatsPage / 10;
252 for (int i = 0; i < m_cGraphType.GetCount(); i++)
254 if ((int)m_cGraphType.GetItemData(i) == graphtype)
256 m_cGraphType.SetCurSel(i);
257 break;
261 OnCbnSelchangeGraphcombo();
263 int statspage = lastStatsPage % 10;
264 switch (statspage) {
265 case 1 :
266 m_GraphType = MyGraph::Bar;
267 m_bStacked = true;
268 break;
269 case 2 :
270 m_GraphType = MyGraph::Bar;
271 m_bStacked = false;
272 break;
273 case 3 :
274 m_GraphType = MyGraph::Line;
275 m_bStacked = true;
276 break;
277 case 4 :
278 m_GraphType = MyGraph::Line;
279 m_bStacked = false;
280 break;
281 case 5 :
282 m_GraphType = MyGraph::PieChart;
283 break;
285 default : return TRUE;
288 LCID m_locale = MAKELCID((DWORD)CRegStdDWORD(_T("Software\\TortoiseGit\\LanguageID"), MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT)), SORT_DEFAULT);
290 bool bUseSystemLocale = !!(DWORD)CRegStdDWORD(_T("Software\\TortoiseGit\\UseSystemLocaleForDates"), TRUE);
291 LCID locale = bUseSystemLocale ? MAKELCID(MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), SORT_DEFAULT) : m_locale;
293 TCHAR l = 0;
294 GetLocaleInfo(locale, LOCALE_IDATE, &l, sizeof(TCHAR));
296 m_langOrder = (l > 0) ? l - '0' : -1;
298 RedrawGraph();
300 return TRUE;
303 void CStatGraphDlg::ShowLabels(BOOL bShow)
305 if ((m_parAuthors==NULL)||(m_parDates==NULL)||(m_parFileChanges==NULL))
306 return;
308 int nCmdShow = bShow ? SW_SHOW : SW_HIDE;
310 GetDlgItem(IDC_GRAPH)->ShowWindow(bShow ? SW_HIDE : SW_SHOW);
311 GetDlgItem(IDC_NUMWEEK)->ShowWindow(nCmdShow);
312 GetDlgItem(IDC_NUMWEEKVALUE)->ShowWindow(nCmdShow);
313 GetDlgItem(IDC_NUMAUTHOR)->ShowWindow(nCmdShow);
314 GetDlgItem(IDC_NUMAUTHORVALUE)->ShowWindow(nCmdShow);
315 GetDlgItem(IDC_NUMCOMMITS)->ShowWindow(nCmdShow);
316 GetDlgItem(IDC_NUMCOMMITSVALUE)->ShowWindow(nCmdShow);
317 //GetDlgItem(IDC_NUMFILECHANGES)->ShowWindow(nCmdShow);
318 GetDlgItem(IDC_NUMFILECHANGESVALUE)->ShowWindow(nCmdShow);
320 GetDlgItem(IDC_AVG)->ShowWindow(nCmdShow);
321 GetDlgItem(IDC_MIN)->ShowWindow(nCmdShow);
322 GetDlgItem(IDC_MAX)->ShowWindow(nCmdShow);
323 GetDlgItem(IDC_COMMITSEACHWEEK)->ShowWindow(nCmdShow);
324 GetDlgItem(IDC_MOSTACTIVEAUTHOR)->ShowWindow(nCmdShow);
325 GetDlgItem(IDC_LEASTACTIVEAUTHOR)->ShowWindow(nCmdShow);
326 GetDlgItem(IDC_MOSTACTIVEAUTHORNAME)->ShowWindow(nCmdShow);
327 GetDlgItem(IDC_LEASTACTIVEAUTHORNAME)->ShowWindow(nCmdShow);
328 //GetDlgItem(IDC_FILECHANGESEACHWEEK)->ShowWindow(nCmdShow);
329 GetDlgItem(IDC_COMMITSEACHWEEKAVG)->ShowWindow(nCmdShow);
330 GetDlgItem(IDC_COMMITSEACHWEEKMIN)->ShowWindow(nCmdShow);
331 GetDlgItem(IDC_COMMITSEACHWEEKMAX)->ShowWindow(nCmdShow);
332 GetDlgItem(IDC_MOSTACTIVEAUTHORAVG)->ShowWindow(nCmdShow);
333 GetDlgItem(IDC_MOSTACTIVEAUTHORMIN)->ShowWindow(nCmdShow);
334 GetDlgItem(IDC_MOSTACTIVEAUTHORMAX)->ShowWindow(nCmdShow);
335 GetDlgItem(IDC_LEASTACTIVEAUTHORAVG)->ShowWindow(nCmdShow);
336 GetDlgItem(IDC_LEASTACTIVEAUTHORMIN)->ShowWindow(nCmdShow);
337 GetDlgItem(IDC_LEASTACTIVEAUTHORMAX)->ShowWindow(nCmdShow);
338 GetDlgItem(IDC_FILECHANGESEACHWEEKAVG)->ShowWindow(nCmdShow);
339 GetDlgItem(IDC_FILECHANGESEACHWEEKMIN)->ShowWindow(nCmdShow);
340 GetDlgItem(IDC_FILECHANGESEACHWEEKMAX)->ShowWindow(nCmdShow);
342 GetDlgItem(IDC_SORTBYCOMMITCOUNT)->ShowWindow(bShow ? SW_HIDE : SW_SHOW);
343 GetDlgItem(IDC_SKIPPER)->ShowWindow(bShow ? SW_HIDE : SW_SHOW);
344 GetDlgItem(IDC_SKIPPERLABEL)->ShowWindow(bShow ? SW_HIDE : SW_SHOW);
345 m_btnGraphBar.ShowWindow(bShow ? SW_HIDE : SW_SHOW);
346 m_btnGraphBarStacked.ShowWindow(bShow ? SW_HIDE : SW_SHOW);
347 m_btnGraphLine.ShowWindow(bShow ? SW_HIDE : SW_SHOW);
348 m_btnGraphLineStacked.ShowWindow(bShow ? SW_HIDE : SW_SHOW);
349 m_btnGraphPie.ShowWindow(bShow ? SW_HIDE : SW_SHOW);
352 void CStatGraphDlg::UpdateWeekCount()
354 // Sanity check
355 if ((!m_parDates)||(m_parDates->GetCount()==0))
356 return;
358 // Already updated? No need to do it again.
359 if (m_nWeeks >= 0)
360 return;
362 // Determine first and last date in dates array
363 __time64_t min_date = (__time64_t)m_parDates->GetAt(0);
364 __time64_t max_date = min_date;
365 INT_PTR count = m_parDates->GetCount();
366 for (INT_PTR i=0; i<count; ++i)
368 __time64_t d = (__time64_t)m_parDates->GetAt(i);
369 if (d < min_date) min_date = d;
370 else if (d > max_date) max_date = d;
373 // Store start date of the interval in the member variable m_minDate
374 m_minDate = min_date;
375 m_maxDate = max_date;
377 // How many weeks does the time period cover?
379 // Get time difference between start and end date
380 double secs = _difftime64(max_date, m_minDate);
382 m_nWeeks = (int)ceil(secs / (double) m_SecondsInWeek);
383 m_nDays = (int)ceil(secs / (double) m_SecondsInDay);
386 int CStatGraphDlg::GetCalendarWeek(const CTime& time)
388 // Note:
389 // the calculation of the calendar week is wrong if DST is in effect
390 // and the date to calculate the week for is in DST and within the range
391 // of the DST offset (e.g. one hour).
392 // For example, if DST starts on Sunday march 30 and the date to get the week for
393 // is Monday, march 31, 0:30:00, then the returned week is one week less than
394 // the real week.
395 // TODO: ?
396 // write a function
397 // getDSTOffset(const CTime& time)
398 // which returns the DST offset for a given time/date. Then we can use this offset
399 // to correct our GetDays() calculation to get the correct week again
400 // This of course won't work for 'history' dates, because Windows doesn't have
401 // that information (only Vista has such a function: GetTimeZoneInformationForYear() )
402 int iWeekOfYear = 0;
404 int iYear = time.GetYear();
405 int iFirstDayOfWeek = 0;
406 int iFirstWeekOfYear = 0;
407 TCHAR loc[2];
408 GetLocaleInfo(LOCALE_USER_DEFAULT, LOCALE_IFIRSTDAYOFWEEK, loc, _countof(loc));
409 iFirstDayOfWeek = int(loc[0]-'0');
410 GetLocaleInfo(LOCALE_USER_DEFAULT, LOCALE_IFIRSTWEEKOFYEAR, loc, _countof(loc));
411 iFirstWeekOfYear = int(loc[0]-'0');
412 CTime dDateFirstJanuary(iYear,1,1,0,0,0);
413 int iDayOfWeek = (dDateFirstJanuary.GetDayOfWeek()+5+iFirstDayOfWeek)%7;
415 // Select mode
416 // 0 Week containing 1/1 is the first week of that year.
417 // 1 First full week following 1/1 is the first week of that year.
418 // 2 First week containing at least four days is the first week of that year.
419 switch (iFirstWeekOfYear)
421 case 0:
423 // Week containing 1/1 is the first week of that year.
425 // check if this week reaches into the next year
426 dDateFirstJanuary = CTime(iYear+1,1,1,0,0,0);
428 // Get start of week
431 iDayOfWeek = (time.GetDayOfWeek()+5+iFirstDayOfWeek)%7;
433 catch (CException* e)
435 e->Delete();
437 CTime dStartOfWeek = time-CTimeSpan(iDayOfWeek,0,0,0);
439 // If this week spans over to 1/1 this is week 1
440 if (dStartOfWeek + CTimeSpan(6,0,0,0) >= dDateFirstJanuary)
442 // we are in the last week of the year that spans over 1/1
443 iWeekOfYear = 1;
445 else
447 // Get week day of 1/1
448 dDateFirstJanuary = CTime(iYear,1,1,0,0,0);
449 iDayOfWeek = (dDateFirstJanuary.GetDayOfWeek() +5 + iFirstDayOfWeek) % 7;
450 // Just count from 1/1
451 iWeekOfYear = (int)(((time-dDateFirstJanuary).GetDays() + iDayOfWeek) / 7) + 1;
454 break;
455 case 1:
457 // First full week following 1/1 is the first week of that year.
459 // If the 1.1 is the start of the week everything is ok
460 // else we need the next week is the correct result
461 iWeekOfYear =
462 (int)(((time-dDateFirstJanuary).GetDays() + iDayOfWeek) / 7) +
463 (iDayOfWeek==0 ? 1:0);
465 // If we are in week 0 we are in the first not full week
466 // calculate from the last year
467 if (iWeekOfYear==0)
469 // Special case: we are in the week of 1.1 but 1.1. is not on the
470 // start of week. Calculate based on the last year
471 dDateFirstJanuary = CTime(iYear-1,1,1,0,0,0);
472 iDayOfWeek =
473 (dDateFirstJanuary.GetDayOfWeek()+5+iFirstDayOfWeek)%7;
474 // and we correct this in the same we we done this before but
475 // the result is now 52 or 53 and not 0
476 iWeekOfYear =
477 (int)(((time-dDateFirstJanuary).GetDays()+iDayOfWeek) / 7) +
478 (iDayOfWeek<=3 ? 1:0);
481 break;
482 case 2:
484 // First week containing at least four days is the first week of that year.
486 // Each year can start with any day of the week. But our
487 // weeks always start with Monday. So we add the day of week
488 // before calculation of the final week of year.
489 // Rule: is the 1.1 a Mo,Tu,We,Th than the week starts on the 1.1 with
490 // week==1, else a week later, so we add one for all those days if
491 // day is less <=3 Mo,Tu,We,Th. Otherwise 1.1 is in the last week of the
492 // previous year
493 iWeekOfYear =
494 (int)(((time-dDateFirstJanuary).GetDays()+iDayOfWeek) / 7) +
495 (iDayOfWeek<=3 ? 1:0);
497 // special cases
498 if (iWeekOfYear==0)
500 // special case week 0. We got a day before the 1.1, 2.1 or 3.1, were the
501 // 1.1. is not a Mo, Tu, We, Th. So the week 1 does not start with the 1.1.
502 // So we calculate the week according to the 1.1 of the year before
504 dDateFirstJanuary = CTime(iYear-1,1,1,0,0,0);
505 iDayOfWeek =
506 (dDateFirstJanuary.GetDayOfWeek()+5+iFirstDayOfWeek)%7;
507 // and we correct this in the same we we done this before but the result
508 // is now 52 or 53 and not 0
509 iWeekOfYear =
510 (int)(((time-dDateFirstJanuary).GetDays()+iDayOfWeek) / 7) +
511 (iDayOfWeek<=3 ? 1:0);
513 else if (iWeekOfYear==53)
515 // special case week 53. Either we got the correct week 53 or we just got the
516 // week 1 of the next year. So is the 1.1.(year+1) also a Mo, Tu, We, Th than
517 // we already have the week 1, otherwise week 53 is correct
519 dDateFirstJanuary = CTime(iYear+1,1,1,0,0,0);
520 iDayOfWeek =
521 (dDateFirstJanuary.GetDayOfWeek()+5+iFirstDayOfWeek)%7;
522 // 1.1. in week 1 or week 53?
523 iWeekOfYear = iDayOfWeek<=3 ? 1:53;
526 break;
527 default:
528 ASSERT(FALSE);
529 break;
531 // return result
532 return iWeekOfYear;
535 void CStatGraphDlg::GatherData()
537 // Sanity check
538 if ((m_parAuthors==NULL)||(m_parDates==NULL)||(m_parFileChanges==NULL))
539 return;
540 m_nTotalCommits = m_parAuthors->GetCount();
541 m_nTotalFileChanges = 0;
543 // Update m_nWeeks and m_minDate
544 UpdateWeekCount();
546 // Now create a mapping that holds the information per week.
547 m_commitsPerUnitAndAuthor.clear();
548 m_filechangesPerUnitAndAuthor.clear();
549 m_commitsPerAuthor.clear();
550 m_PercentageOfAuthorship.clear();
552 int interval = 0;
553 __time64_t d = (__time64_t)m_parDates->GetAt(0);
554 int nLastUnit = GetUnit(d);
555 double AllContributionAuthor = 0;
557 // Now loop over all weeks and gather the info
558 for (LONG i=0; i<m_nTotalCommits; ++i)
560 // Find the interval number
561 __time64_t commitDate = (__time64_t)m_parDates->GetAt(i);
562 int u = GetUnit(commitDate);
563 if (nLastUnit != u)
564 interval++;
565 nLastUnit = u;
566 // Find the authors name
567 CString sAuth = m_parAuthors->GetAt(i);
568 if (!m_bAuthorsCaseSensitive)
569 sAuth = sAuth.MakeLower();
570 tstring author = tstring(sAuth);
571 // Increase total commit count for this author
572 m_commitsPerAuthor[author]++;
573 // Increase the commit count for this author in this week
574 m_commitsPerUnitAndAuthor[interval][author]++;
575 CTime t = m_parDates->GetAt(i);
576 m_unitNames[interval] = GetUnitLabel(nLastUnit, t);
577 // Increase the file change count for this author in this week
578 int fileChanges = m_parFileChanges->GetAt(i);
579 m_filechangesPerUnitAndAuthor[interval][author] += fileChanges;
580 m_nTotalFileChanges += fileChanges;
582 //calculate Contribution Author
583 double contributionAuthor = CoeffContribution((int)m_nTotalCommits - i -1) * fileChanges;
584 AllContributionAuthor += contributionAuthor;
585 m_PercentageOfAuthorship[author] += contributionAuthor;
588 // Find first and last interval number.
589 if (!m_commitsPerUnitAndAuthor.empty())
591 IntervalDataMap::iterator interval_it = m_commitsPerUnitAndAuthor.begin();
592 m_firstInterval = interval_it->first;
593 interval_it = m_commitsPerUnitAndAuthor.end();
594 --interval_it;
595 m_lastInterval = interval_it->first;
596 // Sanity check - if m_lastInterval is too large it could freeze TSVN and take up all memory!!!
597 assert(m_lastInterval >= 0 && m_lastInterval < 10000);
599 else
601 m_firstInterval = 0;
602 m_lastInterval = -1;
605 // Get a list of authors names
606 LoadListOfAuthors(m_commitsPerAuthor);
608 // Calculate percent of Contribution Authors
609 for (std::list<tstring>::iterator it = m_authorNames.begin(); it != m_authorNames.end(); ++it)
611 m_PercentageOfAuthorship[*it] = (m_PercentageOfAuthorship[*it] *100)/ AllContributionAuthor;
614 // All done, now the statistics pages can retrieve the data and
615 // extract the information to be shown.
619 void CStatGraphDlg::FilterSkippedAuthors(std::list<tstring>& included_authors,
620 std::list<tstring>& skipped_authors)
622 included_authors.clear();
623 skipped_authors.clear();
625 unsigned int included_authors_count = m_Skipper.GetPos();
626 // if we only leave out one author, still include him with his name
627 if (included_authors_count + 1 == m_authorNames.size())
628 ++included_authors_count;
630 // add the included authors first
631 std::list<tstring>::iterator author_it = m_authorNames.begin();
632 while (included_authors_count > 0 && author_it != m_authorNames.end())
634 // Add him/her to the included list
635 included_authors.push_back(*author_it);
636 ++author_it;
637 --included_authors_count;
640 // If we haven't reached the end yet, copy all remaining authors into the
641 // skipped author list.
642 std::copy(author_it, m_authorNames.end(), std::back_inserter(skipped_authors) );
644 // Sort authors alphabetically if user wants that.
645 if (!m_bSortByCommitCount)
646 included_authors.sort();
649 bool CStatGraphDlg::PreViewStat(bool fShowLabels)
651 if ((m_parAuthors==NULL)||(m_parDates==NULL)||(m_parFileChanges==NULL))
652 return false;
653 ShowLabels(fShowLabels);
655 //If view graphic
656 if (!fShowLabels) ClearGraph();
658 // This function relies on a previous call of GatherData().
659 // This can be detected by checking the week count.
660 // If the week count is equal to -1, it hasn't been called before.
661 if (m_nWeeks == -1)
662 GatherData();
663 // If week count is still -1, something bad has happened, probably invalid data!
664 if (m_nWeeks == -1)
665 return false;
667 return true;
670 MyGraphSeries *CStatGraphDlg::PreViewGraph(__in UINT GraphTitle, __in UINT YAxisLabel, __in UINT XAxisLabel /*= NULL*/)
672 if(!PreViewStat(false))
673 return NULL;
675 // We need at least one author
676 if (m_authorNames.empty())
677 return NULL;
679 // Add a single series to the chart
680 MyGraphSeries * graphData = new MyGraphSeries();
681 m_graph.AddSeries(*graphData);
682 m_graphDataArray.Add(graphData);
684 // Set up the graph.
685 CString temp;
686 UpdateData();
687 m_graph.SetGraphType(m_GraphType, m_bStacked);
688 temp.LoadString(YAxisLabel);
689 m_graph.SetYAxisLabel(temp);
690 temp.LoadString(XAxisLabel);
691 m_graph.SetXAxisLabel(temp);
692 temp.LoadString(GraphTitle);
693 m_graph.SetGraphTitle(temp);
695 return graphData;
698 void CStatGraphDlg::ShowPercentageOfAuthorship()
700 // Set up the graph.
701 MyGraphSeries * graphData = PreViewGraph(IDS_STATGRAPH_PERCENTAGE_OF_AUTHORSHIP,
702 IDS_STATGRAPH_PERCENTAGE_OF_AUTHORSHIPY,
703 IDS_STATGRAPH_COMMITSBYAUTHORX);
704 if(graphData == NULL) return;
706 // Find out which authors are to be shown and which are to be skipped.
707 std::list<tstring> authors;
708 std::list<tstring> others;
711 FilterSkippedAuthors(authors, others);
713 // Loop over all authors in the authors list and
714 // add them to the graph.
716 if (!authors.empty())
718 for (std::list<tstring>::iterator it = authors.begin(); it != authors.end(); ++it)
720 int group = m_graph.AppendGroup(it->c_str());
721 graphData->SetData(group, RollPercentageOfAuthorship(m_PercentageOfAuthorship[*it]));
725 // If we have other authors, count them and their commits.
726 if (!others.empty())
727 DrawOthers(others, graphData, m_PercentageOfAuthorship);
729 // Paint the graph now that we're through.
730 m_graph.Invalidate();
733 void CStatGraphDlg::ShowCommitsByAuthor()
735 // Set up the graph.
736 MyGraphSeries * graphData = PreViewGraph(IDS_STATGRAPH_COMMITSBYAUTHOR,
737 IDS_STATGRAPH_COMMITSBYAUTHORY,
738 IDS_STATGRAPH_COMMITSBYAUTHORX);
739 if(graphData == NULL) return;
741 // Find out which authors are to be shown and which are to be skipped.
742 std::list<tstring> authors;
743 std::list<tstring> others;
744 FilterSkippedAuthors(authors, others);
746 // Loop over all authors in the authors list and
747 // add them to the graph.
749 if (!authors.empty())
751 for (std::list<tstring>::iterator it = authors.begin(); it != authors.end(); ++it)
753 int group = m_graph.AppendGroup(it->c_str());
754 graphData->SetData(group, m_commitsPerAuthor[*it]);
758 // If we have other authors, count them and their commits.
759 if (!others.empty())
760 DrawOthers(others, graphData, m_commitsPerAuthor);
762 // Paint the graph now that we're through.
763 m_graph.Invalidate();
766 void CStatGraphDlg::ShowCommitsByDate()
768 if(!PreViewStat(false)) return;
770 // We need at least one author
771 if (m_authorNames.empty()) return;
773 // Set up the graph.
774 CString temp;
775 UpdateData();
776 m_graph.SetGraphType(m_GraphType, m_bStacked);
777 temp.LoadString(IDS_STATGRAPH_COMMITSBYDATEY);
778 m_graph.SetYAxisLabel(temp);
779 temp.LoadString(IDS_STATGRAPH_COMMITSBYDATE);
780 m_graph.SetGraphTitle(temp);
782 m_graph.SetXAxisLabel(GetUnitString());
784 // Find out which authors are to be shown and which are to be skipped.
785 std::list<tstring> authors;
786 std::list<tstring> others;
787 FilterSkippedAuthors(authors, others);
789 // Add a graph series for each author.
790 AuthorDataMap authorGraphMap;
791 for (std::list<tstring>::iterator it = authors.begin(); it != authors.end(); ++it)
792 authorGraphMap[*it] = m_graph.AppendGroup(it->c_str());
793 // If we have skipped authors, add a graph series for all those.
794 CString sOthers(MAKEINTRESOURCE(IDS_STATGRAPH_OTHERGROUP));
795 tstring othersName;
796 if (!others.empty())
798 temp.Format(_T(" (%ld)"), others.size());
799 sOthers += temp;
800 othersName = (LPCWSTR)sOthers;
801 authorGraphMap[othersName] = m_graph.AppendGroup(sOthers);
804 // Mapping to collect commit counts in each interval
805 AuthorDataMap commitCount;
807 // Loop over all intervals/weeks and collect filtered data.
808 // Sum up data in each interval until the time unit changes.
809 for (int i=m_lastInterval; i>=m_firstInterval; --i)
811 // Collect data for authors listed by name.
812 if (!authors.empty())
814 for (std::list<tstring>::iterator it = authors.begin(); it != authors.end(); ++it)
816 // Do we have some data for the current author in the current interval?
817 AuthorDataMap::const_iterator data_it = m_commitsPerUnitAndAuthor[i].find(*it);
818 if (data_it == m_commitsPerUnitAndAuthor[i].end())
819 continue;
820 commitCount[*it] += data_it->second;
823 // Collect data for all skipped authors.
824 if (!others.empty())
826 for (std::list<tstring>::iterator it = others.begin(); it != others.end(); ++it)
828 // Do we have some data for the author in the current interval?
829 AuthorDataMap::const_iterator data_it = m_commitsPerUnitAndAuthor[i].find(*it);
830 if (data_it == m_commitsPerUnitAndAuthor[i].end())
831 continue;
832 commitCount[othersName] += data_it->second;
836 // Create a new data series for this unit/interval.
837 MyGraphSeries * graphData = new MyGraphSeries();
838 // Loop over all created graphs and set the corresponding data.
839 if (!authorGraphMap.empty())
841 for (AuthorDataMap::const_iterator it = authorGraphMap.begin(); it != authorGraphMap.end(); ++it)
843 graphData->SetData(it->second, commitCount[it->first]);
846 graphData->SetLabel(m_unitNames[i].c_str());
847 m_graph.AddSeries(*graphData);
848 m_graphDataArray.Add(graphData);
850 // Reset commit count mapping.
851 commitCount.clear();
854 // Paint the graph now that we're through.
855 m_graph.Invalidate();
858 void CStatGraphDlg::ShowStats()
860 if(!PreViewStat(true)) return;
862 // Now we can use the gathered data to update the stats dialog.
863 size_t nAuthors = m_authorNames.size();
865 // Find most and least active author names.
866 tstring mostActiveAuthor;
867 tstring leastActiveAuthor;
868 if (nAuthors > 0)
870 mostActiveAuthor = m_authorNames.front();
871 leastActiveAuthor = m_authorNames.back();
874 // Obtain the statistics for the table.
875 long nCommitsMin = -1;
876 long nCommitsMax = -1;
877 long nFileChangesMin = -1;
878 long nFileChangesMax = -1;
880 long nMostActiveMaxCommits = -1;
881 long nMostActiveMinCommits = -1;
882 long nLeastActiveMaxCommits = -1;
883 long nLeastActiveMinCommits = -1;
885 // Loop over all intervals and find min and max values for commit count and file changes.
886 // Also store the stats for the most and least active authors.
887 for (int i=m_firstInterval; i<=m_lastInterval; ++i)
889 // Loop over all commits in this interval and count the number of commits by all authors.
890 int commitCount = 0;
891 AuthorDataMap::iterator commit_endit = m_commitsPerUnitAndAuthor[i].end();
892 for (AuthorDataMap::iterator commit_it = m_commitsPerUnitAndAuthor[i].begin();
893 commit_it != commit_endit; ++commit_it)
895 commitCount += commit_it->second;
897 if (nCommitsMin == -1 || commitCount < nCommitsMin)
898 nCommitsMin = commitCount;
899 if (nCommitsMax == -1 || commitCount > nCommitsMax)
900 nCommitsMax = commitCount;
902 // Loop over all commits in this interval and count the number of file changes by all authors.
903 int fileChangeCount = 0;
904 AuthorDataMap::iterator filechange_endit = m_filechangesPerUnitAndAuthor[i].end();
905 for (AuthorDataMap::iterator filechange_it = m_filechangesPerUnitAndAuthor[i].begin();
906 filechange_it != filechange_endit; ++filechange_it)
908 fileChangeCount += filechange_it->second;
910 if (nFileChangesMin == -1 || fileChangeCount < nFileChangesMin)
911 nFileChangesMin = fileChangeCount;
912 if (nFileChangesMax == -1 || fileChangeCount > nFileChangesMax)
913 nFileChangesMax = fileChangeCount;
915 // also get min/max data for most and least active authors
916 if (nAuthors > 0)
918 // check if author is present in this interval
919 AuthorDataMap::iterator author_it = m_commitsPerUnitAndAuthor[i].find(mostActiveAuthor);
920 long authorCommits;
921 if (author_it == m_commitsPerUnitAndAuthor[i].end())
922 authorCommits = 0;
923 else
924 authorCommits = author_it->second;
925 if (nMostActiveMaxCommits == -1 || authorCommits > nMostActiveMaxCommits)
926 nMostActiveMaxCommits = authorCommits;
927 if (nMostActiveMinCommits == -1 || authorCommits < nMostActiveMinCommits)
928 nMostActiveMinCommits = authorCommits;
930 author_it = m_commitsPerUnitAndAuthor[i].find(leastActiveAuthor);
931 if (author_it == m_commitsPerUnitAndAuthor[i].end())
932 authorCommits = 0;
933 else
934 authorCommits = author_it->second;
935 if (nLeastActiveMaxCommits == -1 || authorCommits > nLeastActiveMaxCommits)
936 nLeastActiveMaxCommits = authorCommits;
937 if (nLeastActiveMinCommits == -1 || authorCommits < nLeastActiveMinCommits)
938 nLeastActiveMinCommits = authorCommits;
941 if (nMostActiveMaxCommits == -1) nMostActiveMaxCommits = 0;
942 if (nMostActiveMinCommits == -1) nMostActiveMinCommits = 0;
943 if (nLeastActiveMaxCommits == -1) nLeastActiveMaxCommits = 0;
944 if (nLeastActiveMinCommits == -1) nLeastActiveMinCommits = 0;
946 int nWeeks = m_lastInterval-m_firstInterval;
947 if (nWeeks == 0)
948 nWeeks = 1;
949 // Adjust the labels with the unit type (week, month, ...)
950 CString labelText;
951 labelText.Format(IDS_STATGRAPH_NUMBEROFUNIT, GetUnitString());
952 SetDlgItemText(IDC_NUMWEEK, labelText);
953 labelText.Format(IDS_STATGRAPH_COMMITSBYUNIT, GetUnitString());
954 SetDlgItemText(IDC_COMMITSEACHWEEK, labelText);
955 labelText.Format(IDS_STATGRAPH_FILECHANGESBYUNIT, GetUnitString());
956 SetDlgItemText(IDC_FILECHANGESEACHWEEK, labelText);
957 // We have now all data we want and we can fill in the labels...
958 CString number;
959 number.Format(_T("%ld"), nWeeks);
960 SetDlgItemText(IDC_NUMWEEKVALUE, number);
961 number.Format(_T("%ld"), nAuthors);
962 SetDlgItemText(IDC_NUMAUTHORVALUE, number);
963 number.Format(_T("%ld"), m_nTotalCommits);
964 SetDlgItemText(IDC_NUMCOMMITSVALUE, number);
965 number.Format(_T("%ld"), m_nTotalFileChanges);
966 //SetDlgItemText(IDC_NUMFILECHANGESVALUE, number);
968 number.Format(_T("%ld"), m_parAuthors->GetCount() / nWeeks);
969 SetDlgItemText(IDC_COMMITSEACHWEEKAVG, number);
970 number.Format(_T("%ld"), nCommitsMax);
971 SetDlgItemText(IDC_COMMITSEACHWEEKMAX, number);
972 number.Format(_T("%ld"), nCommitsMin);
973 SetDlgItemText(IDC_COMMITSEACHWEEKMIN, number);
975 number.Format(_T("%ld"), m_nTotalFileChanges / nWeeks);
976 //SetDlgItemText(IDC_FILECHANGESEACHWEEKAVG, number);
977 number.Format(_T("%ld"), nFileChangesMax);
978 //SetDlgItemText(IDC_FILECHANGESEACHWEEKMAX, number);
979 number.Format(_T("%ld"), nFileChangesMin);
980 //SetDlgItemText(IDC_FILECHANGESEACHWEEKMIN, number);
982 if (nAuthors == 0)
984 SetDlgItemText(IDC_MOSTACTIVEAUTHORNAME, _T(""));
985 SetDlgItemText(IDC_MOSTACTIVEAUTHORAVG, _T("0"));
986 SetDlgItemText(IDC_MOSTACTIVEAUTHORMAX, _T("0"));
987 SetDlgItemText(IDC_MOSTACTIVEAUTHORMIN, _T("0"));
988 SetDlgItemText(IDC_LEASTACTIVEAUTHORNAME, _T(""));
989 SetDlgItemText(IDC_LEASTACTIVEAUTHORAVG, _T("0"));
990 SetDlgItemText(IDC_LEASTACTIVEAUTHORMAX, _T("0"));
991 SetDlgItemText(IDC_LEASTACTIVEAUTHORMIN, _T("0"));
993 else
995 SetDlgItemText(IDC_MOSTACTIVEAUTHORNAME, mostActiveAuthor.c_str());
996 number.Format(_T("%ld"), m_commitsPerAuthor[mostActiveAuthor] / nWeeks);
997 SetDlgItemText(IDC_MOSTACTIVEAUTHORAVG, number);
998 number.Format(_T("%ld"), nMostActiveMaxCommits);
999 SetDlgItemText(IDC_MOSTACTIVEAUTHORMAX, number);
1000 number.Format(_T("%ld"), nMostActiveMinCommits);
1001 SetDlgItemText(IDC_MOSTACTIVEAUTHORMIN, number);
1003 SetDlgItemText(IDC_LEASTACTIVEAUTHORNAME, leastActiveAuthor.c_str());
1004 number.Format(_T("%ld"), m_commitsPerAuthor[leastActiveAuthor] / nWeeks);
1005 SetDlgItemText(IDC_LEASTACTIVEAUTHORAVG, number);
1006 number.Format(_T("%ld"), nLeastActiveMaxCommits);
1007 SetDlgItemText(IDC_LEASTACTIVEAUTHORMAX, number);
1008 number.Format(_T("%ld"), nLeastActiveMinCommits);
1009 SetDlgItemText(IDC_LEASTACTIVEAUTHORMIN, number);
1013 int CStatGraphDlg::RollPercentageOfAuthorship(double it)
1014 { return (int)it + (it - (int)it >= 0.5);}
1016 void CStatGraphDlg::OnCbnSelchangeGraphcombo()
1018 UpdateData();
1020 Metrics useMetric = (Metrics) m_cGraphType.GetItemData(m_cGraphType.GetCurSel());
1021 switch (useMetric )
1023 case AllStat:
1024 case CommitsByDate:
1025 // by date
1026 m_btnGraphLine.EnableWindow(TRUE);
1027 m_btnGraphLineStacked.EnableWindow(TRUE);
1028 m_btnGraphPie.EnableWindow(TRUE);
1029 m_GraphType = MyGraph::Line;
1030 m_bStacked = false;
1031 break;
1032 case PercentageOfAuthorship:
1033 case CommitsByAuthor:
1034 // by author
1035 m_btnGraphLine.EnableWindow(FALSE);
1036 m_btnGraphLineStacked.EnableWindow(FALSE);
1037 m_btnGraphPie.EnableWindow(TRUE);
1038 m_GraphType = MyGraph::Bar;
1039 m_bStacked = false;
1040 break;
1042 RedrawGraph();
1046 int CStatGraphDlg::GetUnitCount()
1048 if (m_nDays < 8)
1049 return m_nDays;
1050 if (m_nWeeks < 15)
1051 return m_nWeeks;
1052 if (m_nWeeks < 80)
1053 return (m_nWeeks/4)+1;
1054 if (m_nWeeks < 320)
1055 return (m_nWeeks/13)+1; // quarters
1056 return (m_nWeeks/52)+1;
1059 int CStatGraphDlg::GetUnit(const CTime& time)
1061 if (m_nDays < 8)
1062 return time.GetMonth()*100 + time.GetDay(); // month*100+day as the unit
1063 if (m_nWeeks < 15)
1064 return GetCalendarWeek(time);
1065 if (m_nWeeks < 80)
1066 return time.GetMonth();
1067 if (m_nWeeks < 320)
1068 return ((time.GetMonth()-1)/3)+1; // quarters
1069 return time.GetYear();
1072 CStatGraphDlg::UnitType CStatGraphDlg::GetUnitType()
1074 if (m_nDays < 8)
1075 return Days;
1076 if (m_nWeeks < 15)
1077 return Weeks;
1078 if (m_nWeeks < 80)
1079 return Months;
1080 if (m_nWeeks < 320)
1081 return Quarters;
1082 return Years;
1085 CString CStatGraphDlg::GetUnitString()
1087 if (m_nDays < 8)
1088 return CString(MAKEINTRESOURCE(IDS_STATGRAPH_COMMITSBYDATEXDAY));
1089 if (m_nWeeks < 15)
1090 return CString(MAKEINTRESOURCE(IDS_STATGRAPH_COMMITSBYDATEXWEEK));
1091 if (m_nWeeks < 80)
1092 return CString(MAKEINTRESOURCE(IDS_STATGRAPH_COMMITSBYDATEXMONTH));
1093 if (m_nWeeks < 320)
1094 return CString(MAKEINTRESOURCE(IDS_STATGRAPH_COMMITSBYDATEXQUARTER));
1095 return CString(MAKEINTRESOURCE(IDS_STATGRAPH_COMMITSBYDATEXYEAR));
1098 CString CStatGraphDlg::GetUnitLabel(int unit, CTime &lasttime)
1100 CString temp;
1101 switch (GetUnitType())
1103 case Days:
1105 // month*100+day as the unit
1106 int day = unit % 100;
1107 int month = unit / 100;
1108 switch (m_langOrder)
1110 case 0: // month day year
1111 temp.Format(_T("%d/%d/%.2d"), month, day, lasttime.GetYear()%100);
1112 break;
1113 case 1: // day month year
1114 default:
1115 temp.Format(_T("%d/%d/%.2d"), day, month, lasttime.GetYear()%100);
1116 break;
1117 case 2: // year month day
1118 temp.Format(_T("%.2d/%d/%d"), lasttime.GetYear()%100, month, day);
1119 break;
1122 break;
1123 case Weeks:
1125 int year = lasttime.GetYear();
1126 if ((unit == 1)&&(lasttime.GetMonth() == 12))
1127 year += 1;
1129 switch (m_langOrder)
1131 case 0: // month day year
1132 case 1: // day month year
1133 default:
1134 temp.Format(_T("%d/%.2d"), unit, year%100);
1135 break;
1136 case 2: // year month day
1137 temp.Format(_T("%.2d/%d"), year%100, unit);
1138 break;
1141 break;
1142 case Months:
1143 switch (m_langOrder)
1145 case 0: // month day year
1146 case 1: // day month year
1147 default:
1148 temp.Format(_T("%d/%.2d"), unit, lasttime.GetYear()%100);
1149 break;
1150 case 2: // year month day
1151 temp.Format(_T("%.2d/%d"), lasttime.GetYear()%100, unit);
1152 break;
1154 break;
1155 case Quarters:
1156 switch (m_langOrder)
1158 case 0: // month day year
1159 case 1: // day month year
1160 default:
1161 temp.Format(_T("%d/%.2d"), unit, lasttime.GetYear()%100);
1162 break;
1163 case 2: // year month day
1164 temp.Format(_T("%.2d/%d"), lasttime.GetYear()%100, unit);
1165 break;
1167 break;
1168 case Years:
1169 temp.Format(_T("%d"), unit);
1170 break;
1172 return temp;
1175 void CStatGraphDlg::OnHScroll(UINT nSBCode, UINT nPos, CScrollBar* pScrollBar)
1177 if (nSBCode == TB_THUMBTRACK)
1178 return CDialog::OnHScroll(nSBCode, nPos, pScrollBar);
1180 ShowSelectStat((Metrics) m_cGraphType.GetItemData(m_cGraphType.GetCurSel()));
1181 CDialog::OnHScroll(nSBCode, nPos, pScrollBar);
1184 void CStatGraphDlg::OnNeedText(NMHDR *pnmh, LRESULT * /*pResult*/)
1186 TOOLTIPTEXT* pttt = (TOOLTIPTEXT*) pnmh;
1187 if (pttt->hdr.idFrom == (UINT_PTR) m_Skipper.GetSafeHwnd())
1189 size_t included_authors_count = m_Skipper.GetPos();
1190 // if we only leave out one author, still include him with his name
1191 if (included_authors_count + 1 == m_authorNames.size())
1192 ++included_authors_count;
1194 // find the minimum number of commits that the shown authors have
1195 int min_commits = 0;
1196 included_authors_count = min(included_authors_count, m_authorNames.size());
1197 std::list<tstring>::iterator author_it = m_authorNames.begin();
1198 advance(author_it, included_authors_count);
1199 if (author_it != m_authorNames.begin())
1200 min_commits = m_commitsPerAuthor[ *(--author_it) ];
1202 CString string;
1203 int percentage = int(min_commits*100.0/(m_nTotalCommits ? m_nTotalCommits : 1));
1204 string.Format(IDS_STATGRAPH_AUTHORSLIDER_TT, m_Skipper.GetPos(), min_commits, percentage);
1205 ::lstrcpy(pttt->szText, (LPCTSTR) string);
1209 void CStatGraphDlg::AuthorsCaseSensitiveChanged()
1211 UpdateData(); // update checkbox state
1212 GatherData(); // first regenerate the statistics data
1213 RedrawGraph(); // then update the current statistics page
1216 void CStatGraphDlg::SortModeChanged()
1218 UpdateData(); // update checkbox state
1219 RedrawGraph(); // then update the current statistics page
1222 void CStatGraphDlg::ClearGraph()
1224 m_graph.Clear();
1225 for (int j=0; j<m_graphDataArray.GetCount(); ++j)
1226 delete ((MyGraphSeries *)m_graphDataArray.GetAt(j));
1227 m_graphDataArray.RemoveAll();
1230 void CStatGraphDlg::RedrawGraph()
1232 EnableDisableMenu();
1233 m_btnGraphBar.SetState(BST_UNCHECKED);
1234 m_btnGraphBarStacked.SetState(BST_UNCHECKED);
1235 m_btnGraphLine.SetState(BST_UNCHECKED);
1236 m_btnGraphLineStacked.SetState(BST_UNCHECKED);
1237 m_btnGraphPie.SetState(BST_UNCHECKED);
1239 if ((m_GraphType == MyGraph::Bar)&&(m_bStacked))
1241 m_btnGraphBarStacked.SetState(BST_CHECKED);
1243 if ((m_GraphType == MyGraph::Bar)&&(!m_bStacked))
1245 m_btnGraphBar.SetState(BST_CHECKED);
1247 if ((m_GraphType == MyGraph::Line)&&(m_bStacked))
1249 m_btnGraphLineStacked.SetState(BST_CHECKED);
1251 if ((m_GraphType == MyGraph::Line)&&(!m_bStacked))
1253 m_btnGraphLine.SetState(BST_CHECKED);
1255 if (m_GraphType == MyGraph::PieChart)
1257 m_btnGraphPie.SetState(BST_CHECKED);
1260 UpdateData();
1261 ShowSelectStat((Metrics) m_cGraphType.GetItemData(m_cGraphType.GetCurSel()), true);
1263 void CStatGraphDlg::OnBnClickedGraphbarbutton()
1265 m_GraphType = MyGraph::Bar;
1266 m_bStacked = false;
1267 RedrawGraph();
1270 void CStatGraphDlg::OnBnClickedGraphbarstackedbutton()
1272 m_GraphType = MyGraph::Bar;
1273 m_bStacked = true;
1274 RedrawGraph();
1277 void CStatGraphDlg::OnBnClickedGraphlinebutton()
1279 m_GraphType = MyGraph::Line;
1280 m_bStacked = false;
1281 RedrawGraph();
1284 void CStatGraphDlg::OnBnClickedGraphlinestackedbutton()
1286 m_GraphType = MyGraph::Line;
1287 m_bStacked = true;
1288 RedrawGraph();
1291 void CStatGraphDlg::OnBnClickedGraphpiebutton()
1293 m_GraphType = MyGraph::PieChart;
1294 m_bStacked = false;
1295 RedrawGraph();
1298 BOOL CStatGraphDlg::PreTranslateMessage(MSG* pMsg)
1300 if (NULL != m_pToolTip)
1301 m_pToolTip->RelayEvent(pMsg);
1303 return CStandAloneDialogTmpl<CResizableDialog>::PreTranslateMessage(pMsg);
1306 void CStatGraphDlg::EnableDisableMenu()
1308 UINT nEnable = MF_BYCOMMAND;
1310 Metrics SelectMetric = (Metrics) m_cGraphType.GetItemData(m_cGraphType.GetCurSel());
1312 nEnable |= (SelectMetric > TextStatStart && SelectMetric < TextStatEnd)
1313 ? (MF_DISABLED | MF_GRAYED) : MF_ENABLED;
1315 GetMenu()->EnableMenuItem(ID_FILE_SAVESTATGRAPHAS, nEnable);
1318 void CStatGraphDlg::OnFileSavestatgraphas()
1320 CString tempfile;
1321 int filterindex = 0;
1322 if (CAppUtils::FileOpenSave(tempfile, &filterindex, IDS_REVGRAPH_SAVEPIC, IDS_PICTUREFILEFILTER, false, m_hWnd))
1324 // if the user doesn't specify a file extension, default to
1325 // wmf and add that extension to the filename. But only if the
1326 // user chose the 'pictures' filter. The filename isn't changed
1327 // if the 'All files' filter was chosen.
1328 CString extension;
1329 int dotPos = tempfile.ReverseFind('.');
1330 int slashPos = tempfile.ReverseFind('\\');
1331 if (dotPos > slashPos)
1332 extension = tempfile.Mid(dotPos);
1333 if ((filterindex == 1)&&(extension.IsEmpty()))
1335 extension = _T(".wmf");
1336 tempfile += extension;
1338 SaveGraph(tempfile);
1342 void CStatGraphDlg::SaveGraph(CString sFilename)
1344 CString extension = CPathUtils::GetFileExtFromPath(sFilename);
1345 if (extension.CompareNoCase(_T(".wmf"))==0)
1347 // save the graph as an enhanced meta file
1348 CMyMetaFileDC wmfDC;
1349 wmfDC.CreateEnhanced(NULL, sFilename, NULL, _T("TortoiseGit\0Statistics\0\0"));
1350 wmfDC.SetAttribDC(GetDC()->GetSafeHdc());
1351 RedrawGraph();
1352 m_graph.DrawGraph(wmfDC);
1353 HENHMETAFILE hemf = wmfDC.CloseEnhanced();
1354 DeleteEnhMetaFile(hemf);
1356 else
1358 // save the graph as a pixel picture instead of a vector picture
1359 // create dc to paint on
1362 CWindowDC ddc(this);
1363 CDC dc;
1364 if (!dc.CreateCompatibleDC(&ddc))
1366 ShowErrorMessage();
1367 return;
1369 CRect rect;
1370 GetDlgItem(IDC_GRAPH)->GetClientRect(&rect);
1371 HBITMAP hbm = ::CreateCompatibleBitmap(ddc.m_hDC, rect.Width(), rect.Height());
1372 if (hbm==0)
1374 ShowErrorMessage();
1375 return;
1377 HBITMAP oldbm = (HBITMAP)dc.SelectObject(hbm);
1378 // paint the whole graph
1379 RedrawGraph();
1380 m_graph.DrawGraph(dc);
1381 // now use GDI+ to save the picture
1382 CLSID encoderClsid;
1383 GdiplusStartupInput gdiplusStartupInput;
1384 ULONG_PTR gdiplusToken;
1385 CString sErrormessage;
1386 if (GdiplusStartup( &gdiplusToken, &gdiplusStartupInput, NULL )==Ok)
1389 Bitmap bitmap(hbm, NULL);
1390 if (bitmap.GetLastStatus()==Ok)
1392 // Get the CLSID of the encoder.
1393 int ret = 0;
1394 if (CPathUtils::GetFileExtFromPath(sFilename).CompareNoCase(_T(".png"))==0)
1395 ret = GetEncoderClsid(L"image/png", &encoderClsid);
1396 else if (CPathUtils::GetFileExtFromPath(sFilename).CompareNoCase(_T(".jpg"))==0)
1397 ret = GetEncoderClsid(L"image/jpeg", &encoderClsid);
1398 else if (CPathUtils::GetFileExtFromPath(sFilename).CompareNoCase(_T(".jpeg"))==0)
1399 ret = GetEncoderClsid(L"image/jpeg", &encoderClsid);
1400 else if (CPathUtils::GetFileExtFromPath(sFilename).CompareNoCase(_T(".bmp"))==0)
1401 ret = GetEncoderClsid(L"image/bmp", &encoderClsid);
1402 else if (CPathUtils::GetFileExtFromPath(sFilename).CompareNoCase(_T(".gif"))==0)
1403 ret = GetEncoderClsid(L"image/gif", &encoderClsid);
1404 else
1406 sFilename += _T(".jpg");
1407 ret = GetEncoderClsid(L"image/jpeg", &encoderClsid);
1409 if (ret >= 0)
1411 CStringW tfile = CStringW(sFilename);
1412 bitmap.Save(tfile, &encoderClsid, NULL);
1414 else
1416 sErrormessage.Format(IDS_REVGRAPH_ERR_NOENCODER, CPathUtils::GetFileExtFromPath(sFilename));
1419 else
1421 sErrormessage.LoadString(IDS_REVGRAPH_ERR_NOBITMAP);
1424 GdiplusShutdown(gdiplusToken);
1426 else
1428 sErrormessage.LoadString(IDS_REVGRAPH_ERR_GDIINIT);
1430 dc.SelectObject(oldbm);
1431 dc.DeleteDC();
1432 if (!sErrormessage.IsEmpty())
1434 ::MessageBox(m_hWnd, sErrormessage, _T("TortoiseGit"), MB_ICONERROR);
1437 catch (CException * pE)
1439 TCHAR szErrorMsg[2048];
1440 pE->GetErrorMessage(szErrorMsg, 2048);
1441 pE->Delete();
1442 ::MessageBox(m_hWnd, szErrorMsg, _T("TortoiseGit"), MB_ICONERROR);
1447 int CStatGraphDlg::GetEncoderClsid(const WCHAR* format, CLSID* pClsid)
1449 UINT num = 0; // number of image encoders
1450 UINT size = 0; // size of the image encoder array in bytes
1452 ImageCodecInfo* pImageCodecInfo = NULL;
1454 if (GetImageEncodersSize(&num, &size)!=Ok)
1455 return -1;
1456 if (size == 0)
1457 return -1; // Failure
1459 pImageCodecInfo = (ImageCodecInfo*)(malloc(size));
1460 if (pImageCodecInfo == NULL)
1461 return -1; // Failure
1463 if (GetImageEncoders(num, size, pImageCodecInfo)==Ok)
1465 for (UINT j = 0; j < num; ++j)
1467 if (wcscmp(pImageCodecInfo[j].MimeType, format) == 0)
1469 *pClsid = pImageCodecInfo[j].Clsid;
1470 free(pImageCodecInfo);
1471 return j; // Success
1475 free (pImageCodecInfo);
1476 return -1; // Failure
1479 void CStatGraphDlg::StoreCurrentGraphType()
1481 UpdateData();
1482 DWORD graphtype = static_cast<DWORD>(m_cGraphType.GetItemData(m_cGraphType.GetCurSel()));
1483 // encode the current chart type
1484 DWORD statspage = graphtype*10;
1485 if ((m_GraphType == MyGraph::Bar)&&(m_bStacked))
1487 statspage += 1;
1489 if ((m_GraphType == MyGraph::Bar)&&(!m_bStacked))
1491 statspage += 2;
1493 if ((m_GraphType == MyGraph::Line)&&(m_bStacked))
1495 statspage += 3;
1497 if ((m_GraphType == MyGraph::Line)&&(!m_bStacked))
1499 statspage += 4;
1501 if (m_GraphType == MyGraph::PieChart)
1503 statspage += 5;
1506 // store current chart type in registry
1507 CRegDWORD lastStatsPage = CRegDWORD(_T("Software\\TortoiseGit\\LastViewedStatsPage"), 0);
1508 lastStatsPage = statspage;
1510 CRegDWORD regAuthors = CRegDWORD(_T("Software\\TortoiseGit\\StatAuthorsCaseSensitive"));
1511 regAuthors = m_bAuthorsCaseSensitive;
1513 CRegDWORD regSort = CRegDWORD(_T("Software\\TortoiseGit\\StatSortByCommitCount"));
1514 regSort = m_bSortByCommitCount;
1517 void CStatGraphDlg::ShowErrorMessage()
1519 CFormatMessageWrapper errorDetails;
1520 if (errorDetails)
1521 MessageBox( errorDetails, _T("Error"), MB_OK | MB_ICONINFORMATION );
1524 void CStatGraphDlg::ShowSelectStat(Metrics SelectedMetric, bool reloadSkiper /* = false */)
1526 switch (SelectedMetric)
1528 case AllStat:
1529 LoadListOfAuthors(m_commitsPerAuthor, reloadSkiper);
1530 ShowStats();
1531 break;
1532 case CommitsByDate:
1533 LoadListOfAuthors(m_commitsPerAuthor, reloadSkiper);
1534 ShowCommitsByDate();
1535 break;
1536 case CommitsByAuthor:
1537 LoadListOfAuthors(m_commitsPerAuthor, reloadSkiper);
1538 ShowCommitsByAuthor();
1539 break;
1540 case PercentageOfAuthorship:
1541 LoadListOfAuthors(m_PercentageOfAuthorship, reloadSkiper, true);
1542 ShowPercentageOfAuthorship();
1543 break;
1544 default:
1545 ShowErrorMessage();
1549 double CStatGraphDlg::CoeffContribution(int distFromEnd) { return distFromEnd ? 1.0 / m_CoeffAuthorShip * distFromEnd : 1;}
1552 template <class MAP>
1553 void CStatGraphDlg::DrawOthers(const std::list<tstring> &others, MyGraphSeries *graphData, MAP &map)
1555 int nCommits = 0;
1556 for (std::list<tstring>::const_iterator it = others.begin(); it != others.end(); ++it)
1558 nCommits += RollPercentageOfAuthorship(map[*it]);
1561 CString temp;
1562 temp.Format(_T(" (%ld)"), others.size());
1564 CString sOthers(MAKEINTRESOURCE(IDS_STATGRAPH_OTHERGROUP));
1565 sOthers += temp;
1566 int group = m_graph.AppendGroup(sOthers);
1567 graphData->SetData(group, (int)nCommits);
1571 template <class MAP>
1572 void CStatGraphDlg::LoadListOfAuthors (MAP &map, bool reloadSkiper/*= false*/, bool compare /*= false*/)
1574 m_authorNames.clear();
1575 if (!map.empty())
1577 for (MAP::const_iterator it = map.begin(); it != map.end(); ++it)
1579 if ((compare && RollPercentageOfAuthorship(map[it->first]) != 0) || !compare)
1580 m_authorNames.push_back(it->first);
1584 // Sort the list of authors based on commit count
1585 m_authorNames.sort(MoreCommitsThan< MAP::referent_type>(map));
1587 // Set Skipper
1588 SetSkipper(reloadSkiper);