correctly show numbers in tooltip
[TortoiseGit.git] / src / TortoiseProc / StatGraphDlg.cpp
blobf946cbccef335174276e528fb0461f4d8fb76bd9
1 // TortoiseSVN - a Windows shell extension for easy version control
3 // Copyright (C) 2003-2011 - TortoiseSVN
5 // This program is free software; you can redistribute it and/or
6 // modify it under the terms of the GNU General Public License
7 // as published by the Free Software Foundation; either version 2
8 // of the License, or (at your option) any later version.
10 // This program is distributed in the hope that it will be useful,
11 // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 // GNU General Public License for more details.
15 // You should have received a copy of the GNU General Public License
16 // along with this program; if not, write to the Free Software Foundation,
17 // 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
19 #include "stdafx.h"
20 #include "TortoiseProc.h"
21 #include "StatGraphDlg.h"
22 #include "gdiplus.h"
23 #include "AppUtils.h"
24 #include "StringUtils.h"
25 #include "PathUtils.h"
26 #include "MessageBox.h"
27 #include "Registry.h"
28 #include "FormatMessageWrapper.h"
30 #include <cmath>
31 #include <locale>
32 #include <list>
33 #include <utility>
35 using namespace Gdiplus;
37 // BinaryPredicate for comparing authors based on their commit count
38 template<class DataType>
39 class MoreCommitsThan : public std::binary_function<tstring, tstring, bool> {
40 public:
41 typedef std::map<tstring, DataType> MapType;
42 MoreCommitsThan(MapType &author_commits) : m_authorCommits(author_commits) {}
44 bool operator()(const tstring& lhs, const tstring& rhs) {
45 return (m_authorCommits)[lhs] > (m_authorCommits)[rhs];
48 private:
49 MapType &m_authorCommits;
53 IMPLEMENT_DYNAMIC(CStatGraphDlg, CResizableStandAloneDialog)
54 CStatGraphDlg::CStatGraphDlg(CWnd* pParent /*=NULL*/)
55 : CResizableStandAloneDialog(CStatGraphDlg::IDD, pParent)
56 , m_bStacked(FALSE)
57 , m_GraphType(MyGraph::Bar)
58 , m_bAuthorsCaseSensitive(TRUE)
59 , m_bSortByCommitCount(TRUE)
60 , m_nWeeks(-1)
61 , m_nDays(-1)
62 , m_langOrder(0)
63 , m_firstInterval(0)
64 , m_lastInterval(0)
66 m_parDates = NULL;
67 m_parFileChanges = NULL;
68 m_parAuthors = NULL;
69 m_pToolTip = NULL;
72 CStatGraphDlg::~CStatGraphDlg()
74 ClearGraph();
75 delete m_pToolTip;
78 void CStatGraphDlg::OnOK() {
79 StoreCurrentGraphType();
80 __super::OnOK();
83 void CStatGraphDlg::OnCancel() {
84 StoreCurrentGraphType();
85 __super::OnCancel();
88 void CStatGraphDlg::DoDataExchange(CDataExchange* pDX)
90 CResizableStandAloneDialog::DoDataExchange(pDX);
91 DDX_Control(pDX, IDC_GRAPH, m_graph);
92 DDX_Control(pDX, IDC_GRAPHCOMBO, m_cGraphType);
93 DDX_Control(pDX, IDC_SKIPPER, m_Skipper);
94 DDX_Check(pDX, IDC_AUTHORSCASESENSITIVE, m_bAuthorsCaseSensitive);
95 DDX_Check(pDX, IDC_SORTBYCOMMITCOUNT, m_bSortByCommitCount);
96 DDX_Control(pDX, IDC_GRAPHBARBUTTON, m_btnGraphBar);
97 DDX_Control(pDX, IDC_GRAPHBARSTACKEDBUTTON, m_btnGraphBarStacked);
98 DDX_Control(pDX, IDC_GRAPHLINEBUTTON, m_btnGraphLine);
99 DDX_Control(pDX, IDC_GRAPHLINESTACKEDBUTTON, m_btnGraphLineStacked);
100 DDX_Control(pDX, IDC_GRAPHPIEBUTTON, m_btnGraphPie);
104 BEGIN_MESSAGE_MAP(CStatGraphDlg, CResizableStandAloneDialog)
105 ON_CBN_SELCHANGE(IDC_GRAPHCOMBO, OnCbnSelchangeGraphcombo)
106 ON_WM_HSCROLL()
107 ON_NOTIFY(TTN_NEEDTEXT, NULL, OnNeedText)
108 ON_BN_CLICKED(IDC_AUTHORSCASESENSITIVE, &CStatGraphDlg::AuthorsCaseSensitiveChanged)
109 ON_BN_CLICKED(IDC_SORTBYCOMMITCOUNT, &CStatGraphDlg::SortModeChanged)
110 ON_BN_CLICKED(IDC_GRAPHBARBUTTON, &CStatGraphDlg::OnBnClickedGraphbarbutton)
111 ON_BN_CLICKED(IDC_GRAPHBARSTACKEDBUTTON, &CStatGraphDlg::OnBnClickedGraphbarstackedbutton)
112 ON_BN_CLICKED(IDC_GRAPHLINEBUTTON, &CStatGraphDlg::OnBnClickedGraphlinebutton)
113 ON_BN_CLICKED(IDC_GRAPHLINESTACKEDBUTTON, &CStatGraphDlg::OnBnClickedGraphlinestackedbutton)
114 ON_BN_CLICKED(IDC_GRAPHPIEBUTTON, &CStatGraphDlg::OnBnClickedGraphpiebutton)
115 ON_COMMAND(ID_FILE_SAVESTATGRAPHAS, &CStatGraphDlg::OnFileSavestatgraphas)
116 END_MESSAGE_MAP()
118 void CStatGraphDlg::LoadStatQueries (__in UINT curStr, Metrics loadMetric, bool setDef /* = false */)
120 CString temp;
121 temp.LoadString(curStr);
122 int sel = m_cGraphType.AddString(temp);
123 m_cGraphType.SetItemData(sel, loadMetric);
125 if (setDef) m_cGraphType.SetCurSel(sel);
128 void CStatGraphDlg::SetSkipper (bool reloadSkiper)
130 // We need to limit the number of authors due to GUI resource limitation.
131 // However, since author #251 will properly have < 1000th of the commits,
132 // the resolution limit of the screen will already not allow for displaying
133 // it in a reasonable way
135 int max_authors_count = max(1, (int)min(m_authorNames.size(), 250) );
136 m_Skipper.SetRange (1, max_authors_count);
137 m_Skipper.SetPageSize(5);
139 if (reloadSkiper)
140 m_Skipper.SetPos (max_authors_count);
143 BOOL CStatGraphDlg::OnInitDialog()
145 CResizableStandAloneDialog::OnInitDialog();
147 m_pToolTip = new CToolTipCtrl;
148 if (m_pToolTip->Create(this))
150 m_pToolTip->AddTool(&m_btnGraphPie, IDS_STATGRAPH_PIEBUTTON_TT);
151 m_pToolTip->AddTool(&m_btnGraphLineStacked, IDS_STATGRAPH_LINESTACKEDBUTTON_TT);
152 m_pToolTip->AddTool(&m_btnGraphLine, IDS_STATGRAPH_LINEBUTTON_TT);
153 m_pToolTip->AddTool(&m_btnGraphBarStacked, IDS_STATGRAPH_BARSTACKEDBUTTON_TT);
154 m_pToolTip->AddTool(&m_btnGraphBar, IDS_STATGRAPH_BARBUTTON_TT);
156 m_pToolTip->Activate(TRUE);
159 m_bAuthorsCaseSensitive = DWORD(CRegDWORD(_T("Software\\TortoiseSVN\\StatAuthorsCaseSensitive")));
160 m_bSortByCommitCount = DWORD(CRegDWORD(_T("Software\\TortoiseSVN\\StatSortByCommitCount")));
161 UpdateData(FALSE);
163 //Load statistical queries
164 LoadStatQueries(IDS_STATGRAPH_STATS, AllStat, true);
165 LoadStatQueries(IDS_STATGRAPH_COMMITSBYDATE, CommitsByDate);
166 LoadStatQueries(IDS_STATGRAPH_COMMITSBYAUTHOR, CommitsByAuthor);
167 LoadStatQueries(IDS_STATGRAPH_PERCENTAGE_OF_AUTHORSHIP, PercentageOfAuthorship);
169 // set the dialog title to "Statistics - path/to/whatever/we/show/the/statistics/for"
170 CString sTitle;
171 GetWindowText(sTitle);
172 SetWindowText(sTitle + _T(" - ") +
173 ( m_path.IsDirectory() ? m_path.GetWinPathString() : m_path.GetFilename()));
175 m_btnGraphBar.SetImage((HICON)LoadImage(AfxGetResourceHandle(), MAKEINTRESOURCE(IDI_GRAPHBAR), IMAGE_ICON, 16, 16, LR_DEFAULTCOLOR));
176 m_btnGraphBar.SizeToContent();
177 m_btnGraphBar.Invalidate();
178 m_btnGraphBarStacked.SetImage((HICON)LoadImage(AfxGetResourceHandle(), MAKEINTRESOURCE(IDI_GRAPHBARSTACKED), IMAGE_ICON, 16, 16, LR_DEFAULTCOLOR));
179 m_btnGraphBarStacked.SizeToContent();
180 m_btnGraphBarStacked.Invalidate();
181 m_btnGraphLine.SetImage((HICON)LoadImage(AfxGetResourceHandle(), MAKEINTRESOURCE(IDI_GRAPHLINE), IMAGE_ICON, 16, 16, LR_DEFAULTCOLOR));
182 m_btnGraphLine.SizeToContent();
183 m_btnGraphLine.Invalidate();
184 m_btnGraphLineStacked.SetImage((HICON)LoadImage(AfxGetResourceHandle(), MAKEINTRESOURCE(IDI_GRAPHLINESTACKED), IMAGE_ICON, 16, 16, LR_DEFAULTCOLOR));
185 m_btnGraphLineStacked.SizeToContent();
186 m_btnGraphLineStacked.Invalidate();
187 m_btnGraphPie.SetImage((HICON)LoadImage(AfxGetResourceHandle(), MAKEINTRESOURCE(IDI_GRAPHPIE), IMAGE_ICON, 16, 16, LR_DEFAULTCOLOR));
188 m_btnGraphPie.SizeToContent();
189 m_btnGraphPie.Invalidate();
191 AddAnchor(IDC_GRAPHTYPELABEL, TOP_LEFT);
192 AddAnchor(IDC_GRAPH, TOP_LEFT, BOTTOM_RIGHT);
193 AddAnchor(IDC_GRAPHCOMBO, TOP_LEFT, TOP_RIGHT);
195 AddAnchor(IDC_NUMWEEK, TOP_LEFT);
196 AddAnchor(IDC_NUMWEEKVALUE, TOP_RIGHT);
197 AddAnchor(IDC_NUMAUTHOR, TOP_LEFT);
198 AddAnchor(IDC_NUMAUTHORVALUE, TOP_RIGHT);
199 AddAnchor(IDC_NUMCOMMITS, TOP_LEFT);
200 AddAnchor(IDC_NUMCOMMITSVALUE, TOP_RIGHT);
201 AddAnchor(IDC_NUMFILECHANGES, TOP_LEFT);
202 AddAnchor(IDC_NUMFILECHANGESVALUE, TOP_RIGHT);
204 AddAnchor(IDC_AVG, TOP_RIGHT);
205 AddAnchor(IDC_MIN, TOP_RIGHT);
206 AddAnchor(IDC_MAX, TOP_RIGHT);
207 AddAnchor(IDC_COMMITSEACHWEEK, TOP_LEFT);
208 AddAnchor(IDC_MOSTACTIVEAUTHOR, TOP_LEFT);
209 AddAnchor(IDC_LEASTACTIVEAUTHOR, TOP_LEFT);
210 AddAnchor(IDC_MOSTACTIVEAUTHORNAME, TOP_LEFT, TOP_RIGHT);
211 AddAnchor(IDC_LEASTACTIVEAUTHORNAME, TOP_LEFT, TOP_RIGHT);
212 AddAnchor(IDC_FILECHANGESEACHWEEK, TOP_LEFT);
213 AddAnchor(IDC_COMMITSEACHWEEKAVG, TOP_RIGHT);
214 AddAnchor(IDC_COMMITSEACHWEEKMIN, TOP_RIGHT);
215 AddAnchor(IDC_COMMITSEACHWEEKMAX, TOP_RIGHT);
216 AddAnchor(IDC_MOSTACTIVEAUTHORAVG, TOP_RIGHT);
217 AddAnchor(IDC_MOSTACTIVEAUTHORMIN, TOP_RIGHT);
218 AddAnchor(IDC_MOSTACTIVEAUTHORMAX, TOP_RIGHT);
219 AddAnchor(IDC_LEASTACTIVEAUTHORAVG, TOP_RIGHT);
220 AddAnchor(IDC_LEASTACTIVEAUTHORMIN, TOP_RIGHT);
221 AddAnchor(IDC_LEASTACTIVEAUTHORMAX, TOP_RIGHT);
222 AddAnchor(IDC_FILECHANGESEACHWEEKAVG, TOP_RIGHT);
223 AddAnchor(IDC_FILECHANGESEACHWEEKMIN, TOP_RIGHT);
224 AddAnchor(IDC_FILECHANGESEACHWEEKMAX, TOP_RIGHT);
226 AddAnchor(IDC_GRAPHBARBUTTON, BOTTOM_RIGHT);
227 AddAnchor(IDC_GRAPHBARSTACKEDBUTTON, BOTTOM_RIGHT);
228 AddAnchor(IDC_GRAPHLINEBUTTON, BOTTOM_RIGHT);
229 AddAnchor(IDC_GRAPHLINESTACKEDBUTTON, BOTTOM_RIGHT);
230 AddAnchor(IDC_GRAPHPIEBUTTON, BOTTOM_RIGHT);
232 AddAnchor(IDC_AUTHORSCASESENSITIVE, BOTTOM_LEFT);
233 AddAnchor(IDC_SORTBYCOMMITCOUNT, BOTTOM_LEFT);
234 AddAnchor(IDC_SKIPPER, BOTTOM_LEFT, BOTTOM_RIGHT);
235 AddAnchor(IDC_SKIPPERLABEL, BOTTOM_LEFT);
236 AddAnchor(IDOK, BOTTOM_RIGHT);
237 EnableSaveRestore(_T("StatGraphDlg"));
239 // gather statistics data, only needs to be updated when the checkbox with
240 // the case sensitivity of author names is changed
241 GatherData();
243 // set the min/max values on the skipper
244 SetSkipper (true);
246 // we use a stats page encoding here, 0 stands for the statistics dialog
247 CRegDWORD lastStatsPage = CRegDWORD(_T("Software\\TortoiseSVN\\LastViewedStatsPage"), 0);
249 // open last viewed statistics page as first page
250 int graphtype = lastStatsPage / 10;
251 graphtype = max(1, min(3, graphtype));
252 m_cGraphType.SetCurSel(graphtype-1);
254 OnCbnSelchangeGraphcombo();
256 int statspage = lastStatsPage % 10;
257 switch (statspage) {
258 case 1 :
259 m_GraphType = MyGraph::Bar;
260 m_bStacked = true;
261 break;
262 case 2 :
263 m_GraphType = MyGraph::Bar;
264 m_bStacked = false;
265 break;
266 case 3 :
267 m_GraphType = MyGraph::Line;
268 m_bStacked = true;
269 break;
270 case 4 :
271 m_GraphType = MyGraph::Line;
272 m_bStacked = false;
273 break;
274 case 5 :
275 m_GraphType = MyGraph::PieChart;
276 break;
278 default : return TRUE;
281 LCID m_locale = MAKELCID((DWORD)CRegStdDWORD(_T("Software\\TortoiseSVN\\LanguageID"), MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT)), SORT_DEFAULT);
283 bool bUseSystemLocale = !!(DWORD)CRegStdDWORD(_T("Software\\TortoiseSVN\\UseSystemLocaleForDates"), TRUE);
284 LCID locale = bUseSystemLocale ? MAKELCID(MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), SORT_DEFAULT) : m_locale;
286 TCHAR l = 0;
287 GetLocaleInfo(locale, LOCALE_IDATE, &l, sizeof(TCHAR));
289 m_langOrder = (l > 0) ? l - '0' : -1;
291 RedrawGraph();
293 return TRUE;
296 void CStatGraphDlg::ShowLabels(BOOL bShow)
298 if ((m_parAuthors==NULL)||(m_parDates==NULL)||(m_parFileChanges==NULL))
299 return;
301 int nCmdShow = bShow ? SW_SHOW : SW_HIDE;
303 GetDlgItem(IDC_GRAPH)->ShowWindow(bShow ? SW_HIDE : SW_SHOW);
304 GetDlgItem(IDC_NUMWEEK)->ShowWindow(nCmdShow);
305 GetDlgItem(IDC_NUMWEEKVALUE)->ShowWindow(nCmdShow);
306 GetDlgItem(IDC_NUMAUTHOR)->ShowWindow(nCmdShow);
307 GetDlgItem(IDC_NUMAUTHORVALUE)->ShowWindow(nCmdShow);
308 GetDlgItem(IDC_NUMCOMMITS)->ShowWindow(nCmdShow);
309 GetDlgItem(IDC_NUMCOMMITSVALUE)->ShowWindow(nCmdShow);
310 GetDlgItem(IDC_NUMFILECHANGES)->ShowWindow(nCmdShow);
311 GetDlgItem(IDC_NUMFILECHANGESVALUE)->ShowWindow(nCmdShow);
313 GetDlgItem(IDC_AVG)->ShowWindow(nCmdShow);
314 GetDlgItem(IDC_MIN)->ShowWindow(nCmdShow);
315 GetDlgItem(IDC_MAX)->ShowWindow(nCmdShow);
316 GetDlgItem(IDC_COMMITSEACHWEEK)->ShowWindow(nCmdShow);
317 GetDlgItem(IDC_MOSTACTIVEAUTHOR)->ShowWindow(nCmdShow);
318 GetDlgItem(IDC_LEASTACTIVEAUTHOR)->ShowWindow(nCmdShow);
319 GetDlgItem(IDC_MOSTACTIVEAUTHORNAME)->ShowWindow(nCmdShow);
320 GetDlgItem(IDC_LEASTACTIVEAUTHORNAME)->ShowWindow(nCmdShow);
321 GetDlgItem(IDC_FILECHANGESEACHWEEK)->ShowWindow(nCmdShow);
322 GetDlgItem(IDC_COMMITSEACHWEEKAVG)->ShowWindow(nCmdShow);
323 GetDlgItem(IDC_COMMITSEACHWEEKMIN)->ShowWindow(nCmdShow);
324 GetDlgItem(IDC_COMMITSEACHWEEKMAX)->ShowWindow(nCmdShow);
325 GetDlgItem(IDC_MOSTACTIVEAUTHORAVG)->ShowWindow(nCmdShow);
326 GetDlgItem(IDC_MOSTACTIVEAUTHORMIN)->ShowWindow(nCmdShow);
327 GetDlgItem(IDC_MOSTACTIVEAUTHORMAX)->ShowWindow(nCmdShow);
328 GetDlgItem(IDC_LEASTACTIVEAUTHORAVG)->ShowWindow(nCmdShow);
329 GetDlgItem(IDC_LEASTACTIVEAUTHORMIN)->ShowWindow(nCmdShow);
330 GetDlgItem(IDC_LEASTACTIVEAUTHORMAX)->ShowWindow(nCmdShow);
331 GetDlgItem(IDC_FILECHANGESEACHWEEKAVG)->ShowWindow(nCmdShow);
332 GetDlgItem(IDC_FILECHANGESEACHWEEKMIN)->ShowWindow(nCmdShow);
333 GetDlgItem(IDC_FILECHANGESEACHWEEKMAX)->ShowWindow(nCmdShow);
335 GetDlgItem(IDC_SORTBYCOMMITCOUNT)->ShowWindow(bShow ? SW_HIDE : SW_SHOW);
336 GetDlgItem(IDC_SKIPPER)->ShowWindow(bShow ? SW_HIDE : SW_SHOW);
337 GetDlgItem(IDC_SKIPPERLABEL)->ShowWindow(bShow ? SW_HIDE : SW_SHOW);
338 m_btnGraphBar.ShowWindow(bShow ? SW_HIDE : SW_SHOW);
339 m_btnGraphBarStacked.ShowWindow(bShow ? SW_HIDE : SW_SHOW);
340 m_btnGraphLine.ShowWindow(bShow ? SW_HIDE : SW_SHOW);
341 m_btnGraphLineStacked.ShowWindow(bShow ? SW_HIDE : SW_SHOW);
342 m_btnGraphPie.ShowWindow(bShow ? SW_HIDE : SW_SHOW);
345 void CStatGraphDlg::UpdateWeekCount()
347 // Sanity check
348 if ((!m_parDates)||(m_parDates->GetCount()==0))
349 return;
351 // Already updated? No need to do it again.
352 if (m_nWeeks >= 0)
353 return;
355 // Determine first and last date in dates array
356 __time64_t min_date = (__time64_t)m_parDates->GetAt(0);
357 __time64_t max_date = min_date;
358 INT_PTR count = m_parDates->GetCount();
359 for (INT_PTR i=0; i<count; ++i)
361 __time64_t d = (__time64_t)m_parDates->GetAt(i);
362 if (d < min_date) min_date = d;
363 else if (d > max_date) max_date = d;
366 // Store start date of the interval in the member variable m_minDate
367 m_minDate = min_date;
368 m_maxDate = max_date;
370 // How many weeks does the time period cover?
372 // Get time difference between start and end date
373 double secs = _difftime64(max_date, m_minDate);
375 m_nWeeks = (int)ceil(secs / (double) m_SecondsInWeek);
376 m_nDays = (int)ceil(secs / (double) m_SecondsInDay);
379 int CStatGraphDlg::GetCalendarWeek(const CTime& time)
381 // Note:
382 // the calculation of the calendar week is wrong if DST is in effect
383 // and the date to calculate the week for is in DST and within the range
384 // of the DST offset (e.g. one hour).
385 // For example, if DST starts on Sunday march 30 and the date to get the week for
386 // is Monday, march 31, 0:30:00, then the returned week is one week less than
387 // the real week.
388 // TODO: ?
389 // write a function
390 // getDSTOffset(const CTime& time)
391 // which returns the DST offset for a given time/date. Then we can use this offset
392 // to correct our GetDays() calculation to get the correct week again
393 // This of course won't work for 'history' dates, because Windows doesn't have
394 // that information (only Vista has such a function: GetTimeZoneInformationForYear() )
395 int iWeekOfYear = 0;
397 int iYear = time.GetYear();
398 int iFirstDayOfWeek = 0;
399 int iFirstWeekOfYear = 0;
400 TCHAR loc[2];
401 GetLocaleInfo(LOCALE_USER_DEFAULT, LOCALE_IFIRSTDAYOFWEEK, loc, _countof(loc));
402 iFirstDayOfWeek = int(loc[0]-'0');
403 GetLocaleInfo(LOCALE_USER_DEFAULT, LOCALE_IFIRSTWEEKOFYEAR, loc, _countof(loc));
404 iFirstWeekOfYear = int(loc[0]-'0');
405 CTime dDateFirstJanuary(iYear,1,1,0,0,0);
406 int iDayOfWeek = (dDateFirstJanuary.GetDayOfWeek()+5+iFirstDayOfWeek)%7;
408 // Select mode
409 // 0 Week containing 1/1 is the first week of that year.
410 // 1 First full week following 1/1 is the first week of that year.
411 // 2 First week containing at least four days is the first week of that year.
412 switch (iFirstWeekOfYear)
414 case 0:
416 // Week containing 1/1 is the first week of that year.
418 // check if this week reaches into the next year
419 dDateFirstJanuary = CTime(iYear+1,1,1,0,0,0);
421 // Get start of week
424 iDayOfWeek = (time.GetDayOfWeek()+5+iFirstDayOfWeek)%7;
426 catch (CException* e)
428 e->Delete();
430 CTime dStartOfWeek = time-CTimeSpan(iDayOfWeek,0,0,0);
432 // If this week spans over to 1/1 this is week 1
433 if (dStartOfWeek + CTimeSpan(6,0,0,0) >= dDateFirstJanuary)
435 // we are in the last week of the year that spans over 1/1
436 iWeekOfYear = 1;
438 else
440 // Get week day of 1/1
441 dDateFirstJanuary = CTime(iYear,1,1,0,0,0);
442 iDayOfWeek = (dDateFirstJanuary.GetDayOfWeek() +5 + iFirstDayOfWeek) % 7;
443 // Just count from 1/1
444 iWeekOfYear = (int)(((time-dDateFirstJanuary).GetDays() + iDayOfWeek) / 7) + 1;
447 break;
448 case 1:
450 // First full week following 1/1 is the first week of that year.
452 // If the 1.1 is the start of the week everything is ok
453 // else we need the next week is the correct result
454 iWeekOfYear =
455 (int)(((time-dDateFirstJanuary).GetDays() + iDayOfWeek) / 7) +
456 (iDayOfWeek==0 ? 1:0);
458 // If we are in week 0 we are in the first not full week
459 // calculate from the last year
460 if (iWeekOfYear==0)
462 // Special case: we are in the week of 1.1 but 1.1. is not on the
463 // start of week. Calculate based on the last year
464 dDateFirstJanuary = CTime(iYear-1,1,1,0,0,0);
465 iDayOfWeek =
466 (dDateFirstJanuary.GetDayOfWeek()+5+iFirstDayOfWeek)%7;
467 // and we correct this in the same we we done this before but
468 // the result is now 52 or 53 and not 0
469 iWeekOfYear =
470 (int)(((time-dDateFirstJanuary).GetDays()+iDayOfWeek) / 7) +
471 (iDayOfWeek<=3 ? 1:0);
474 break;
475 case 2:
477 // First week containing at least four days is the first week of that year.
479 // Each year can start with any day of the week. But our
480 // weeks always start with Monday. So we add the day of week
481 // before calculation of the final week of year.
482 // Rule: is the 1.1 a Mo,Tu,We,Th than the week starts on the 1.1 with
483 // week==1, else a week later, so we add one for all those days if
484 // day is less <=3 Mo,Tu,We,Th. Otherwise 1.1 is in the last week of the
485 // previous year
486 iWeekOfYear =
487 (int)(((time-dDateFirstJanuary).GetDays()+iDayOfWeek) / 7) +
488 (iDayOfWeek<=3 ? 1:0);
490 // special cases
491 if (iWeekOfYear==0)
493 // special case week 0. We got a day before the 1.1, 2.1 or 3.1, were the
494 // 1.1. is not a Mo, Tu, We, Th. So the week 1 does not start with the 1.1.
495 // So we calculate the week according to the 1.1 of the year before
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 the result
501 // is now 52 or 53 and not 0
502 iWeekOfYear =
503 (int)(((time-dDateFirstJanuary).GetDays()+iDayOfWeek) / 7) +
504 (iDayOfWeek<=3 ? 1:0);
506 else if (iWeekOfYear==53)
508 // special case week 53. Either we got the correct week 53 or we just got the
509 // week 1 of the next year. So is the 1.1.(year+1) also a Mo, Tu, We, Th than
510 // we already have the week 1, otherwise week 53 is correct
512 dDateFirstJanuary = CTime(iYear+1,1,1,0,0,0);
513 iDayOfWeek =
514 (dDateFirstJanuary.GetDayOfWeek()+5+iFirstDayOfWeek)%7;
515 // 1.1. in week 1 or week 53?
516 iWeekOfYear = iDayOfWeek<=3 ? 1:53;
519 break;
520 default:
521 ASSERT(FALSE);
522 break;
524 // return result
525 return iWeekOfYear;
528 void CStatGraphDlg::GatherData()
530 // Sanity check
531 if ((m_parAuthors==NULL)||(m_parDates==NULL)||(m_parFileChanges==NULL))
532 return;
533 m_nTotalCommits = m_parAuthors->GetCount();
534 m_nTotalFileChanges = 0;
536 // Update m_nWeeks and m_minDate
537 UpdateWeekCount();
539 // Now create a mapping that holds the information per week.
540 m_commitsPerUnitAndAuthor.clear();
541 m_filechangesPerUnitAndAuthor.clear();
542 m_commitsPerAuthor.clear();
544 int interval = 0;
545 __time64_t d = (__time64_t)m_parDates->GetAt(0);
546 int nLastUnit = GetUnit(d);
547 double AllContributionAuthor = 0;
549 // Now loop over all weeks and gather the info
550 for (LONG i=0; i<m_nTotalCommits; ++i)
552 // Find the interval number
553 __time64_t commitDate = (__time64_t)m_parDates->GetAt(i);
554 int u = GetUnit(commitDate);
555 if (nLastUnit != u)
556 interval++;
557 nLastUnit = u;
558 // Find the authors name
559 CString sAuth = m_parAuthors->GetAt(i);
560 if (!m_bAuthorsCaseSensitive)
561 sAuth = sAuth.MakeLower();
562 tstring author = tstring(sAuth);
563 // Increase total commit count for this author
564 m_commitsPerAuthor[author]++;
565 // Increase the commit count for this author in this week
566 m_commitsPerUnitAndAuthor[interval][author]++;
567 CTime t = m_parDates->GetAt(i);
568 m_unitNames[interval] = GetUnitLabel(nLastUnit, t);
569 // Increase the file change count for this author in this week
570 int fileChanges = m_parFileChanges->GetAt(i);
571 m_filechangesPerUnitAndAuthor[interval][author] += fileChanges;
572 m_nTotalFileChanges += fileChanges;
574 //calculate Contribution Author
575 double contributionAuthor = CoeffContribution((int)m_nTotalCommits - i -1) * fileChanges;
576 AllContributionAuthor += contributionAuthor;
577 m_PercentageOfAuthorship[author] += contributionAuthor;
580 // Find first and last interval number.
581 if (!m_commitsPerUnitAndAuthor.empty())
583 IntervalDataMap::iterator interval_it = m_commitsPerUnitAndAuthor.begin();
584 m_firstInterval = interval_it->first;
585 interval_it = m_commitsPerUnitAndAuthor.end();
586 --interval_it;
587 m_lastInterval = interval_it->first;
588 // Sanity check - if m_lastInterval is too large it could freeze TSVN and take up all memory!!!
589 assert(m_lastInterval >= 0 && m_lastInterval < 10000);
591 else
593 m_firstInterval = 0;
594 m_lastInterval = -1;
597 // Get a list of authors names
598 LoadListOfAuthors(m_commitsPerAuthor);
600 // Calculate percent of Contribution Authors
601 for (std::list<tstring>::iterator it = m_authorNames.begin(); it != m_authorNames.end(); ++it)
603 m_PercentageOfAuthorship[*it] = (m_PercentageOfAuthorship[*it] *100)/ AllContributionAuthor;
606 // All done, now the statistics pages can retrieve the data and
607 // extract the information to be shown.
611 void CStatGraphDlg::FilterSkippedAuthors(std::list<tstring>& included_authors,
612 std::list<tstring>& skipped_authors)
614 included_authors.clear();
615 skipped_authors.clear();
617 unsigned int included_authors_count = m_Skipper.GetPos();
618 // if we only leave out one author, still include him with his name
619 if (included_authors_count + 1 == m_authorNames.size())
620 ++included_authors_count;
622 // add the included authors first
623 std::list<tstring>::iterator author_it = m_authorNames.begin();
624 while (included_authors_count > 0 && author_it != m_authorNames.end())
626 // Add him/her to the included list
627 included_authors.push_back(*author_it);
628 ++author_it;
629 --included_authors_count;
632 // If we haven't reached the end yet, copy all remaining authors into the
633 // skipped author list.
634 std::copy(author_it, m_authorNames.end(), std::back_inserter(skipped_authors) );
636 // Sort authors alphabetically if user wants that.
637 if (!m_bSortByCommitCount)
638 included_authors.sort();
641 bool CStatGraphDlg::PreViewStat(bool fShowLabels)
643 if ((m_parAuthors==NULL)||(m_parDates==NULL)||(m_parFileChanges==NULL))
644 return false;
645 ShowLabels(fShowLabels);
647 //If view graphic
648 if (!fShowLabels) ClearGraph();
650 // This function relies on a previous call of GatherData().
651 // This can be detected by checking the week count.
652 // If the week count is equal to -1, it hasn't been called before.
653 if (m_nWeeks == -1)
654 GatherData();
655 // If week count is still -1, something bad has happened, probably invalid data!
656 if (m_nWeeks == -1)
657 return false;
659 return true;
662 MyGraphSeries *CStatGraphDlg::PreViewGraph(__in UINT GraphTitle, __in UINT YAxisLabel, __in UINT XAxisLabel /*= NULL*/)
664 if(!PreViewStat(false))
665 return NULL;
667 // We need at least one author
668 if (m_authorNames.empty())
669 return NULL;
671 // Add a single series to the chart
672 MyGraphSeries * graphData = new MyGraphSeries();
673 m_graph.AddSeries(*graphData);
674 m_graphDataArray.Add(graphData);
676 // Set up the graph.
677 CString temp;
678 UpdateData();
679 m_graph.SetGraphType(m_GraphType, m_bStacked);
680 temp.LoadString(YAxisLabel);
681 m_graph.SetYAxisLabel(temp);
682 temp.LoadString(XAxisLabel);
683 m_graph.SetXAxisLabel(temp);
684 temp.LoadString(GraphTitle);
685 m_graph.SetGraphTitle(temp);
687 return graphData;
690 void CStatGraphDlg::ShowPercentageOfAuthorship()
692 // Set up the graph.
693 MyGraphSeries * graphData = PreViewGraph(IDS_STATGRAPH_PERCENTAGE_OF_AUTHORSHIP,
694 IDS_STATGRAPH_PERCENTAGE_OF_AUTHORSHIPY,
695 IDS_STATGRAPH_COMMITSBYAUTHORX);
696 if(graphData == NULL) return;
698 // Find out which authors are to be shown and which are to be skipped.
699 std::list<tstring> authors;
700 std::list<tstring> others;
703 FilterSkippedAuthors(authors, others);
705 // Loop over all authors in the authors list and
706 // add them to the graph.
708 if (authors.size())
710 for (std::list<tstring>::iterator it = authors.begin(); it != authors.end(); ++it)
712 int group = m_graph.AppendGroup(it->c_str());
713 graphData->SetData(group, RollPercentageOfAuthorship(m_PercentageOfAuthorship[*it]));
717 // If we have other authors, count them and their commits.
718 if (others.size() != 0)
719 DrawOthers(others, graphData, m_PercentageOfAuthorship);
721 // Paint the graph now that we're through.
722 m_graph.Invalidate();
725 void CStatGraphDlg::ShowCommitsByAuthor()
727 // Set up the graph.
728 MyGraphSeries * graphData = PreViewGraph(IDS_STATGRAPH_COMMITSBYAUTHOR,
729 IDS_STATGRAPH_COMMITSBYAUTHORY,
730 IDS_STATGRAPH_COMMITSBYAUTHORX);
731 if(graphData == NULL) return;
733 // Find out which authors are to be shown and which are to be skipped.
734 std::list<tstring> authors;
735 std::list<tstring> others;
736 FilterSkippedAuthors(authors, others);
738 // Loop over all authors in the authors list and
739 // add them to the graph.
741 if (authors.size())
743 for (std::list<tstring>::iterator it = authors.begin(); it != authors.end(); ++it)
745 int group = m_graph.AppendGroup(it->c_str());
746 graphData->SetData(group, m_commitsPerAuthor[*it]);
750 // If we have other authors, count them and their commits.
751 if (others.size() != 0)
752 DrawOthers(others, graphData, m_commitsPerAuthor);
754 // Paint the graph now that we're through.
755 m_graph.Invalidate();
758 void CStatGraphDlg::ShowCommitsByDate()
760 if(!PreViewStat(false)) return;
762 // We need at least one author
763 if (m_authorNames.empty()) return;
765 // Set up the graph.
766 CString temp;
767 UpdateData();
768 m_graph.SetGraphType(m_GraphType, m_bStacked);
769 temp.LoadString(IDS_STATGRAPH_COMMITSBYDATEY);
770 m_graph.SetYAxisLabel(temp);
771 temp.LoadString(IDS_STATGRAPH_COMMITSBYDATE);
772 m_graph.SetGraphTitle(temp);
774 m_graph.SetXAxisLabel(GetUnitString());
776 // Find out which authors are to be shown and which are to be skipped.
777 std::list<tstring> authors;
778 std::list<tstring> others;
779 FilterSkippedAuthors(authors, others);
781 // Add a graph series for each author.
782 AuthorDataMap authorGraphMap;
783 for (std::list<tstring>::iterator it = authors.begin(); it != authors.end(); ++it)
784 authorGraphMap[*it] = m_graph.AppendGroup(it->c_str());
785 // If we have skipped authors, add a graph series for all those.
786 CString sOthers(MAKEINTRESOURCE(IDS_STATGRAPH_OTHERGROUP));
787 tstring othersName;
788 if (!others.empty())
790 temp.Format(_T(" (%ld)"), others.size());
791 sOthers += temp;
792 othersName = (LPCWSTR)sOthers;
793 authorGraphMap[othersName] = m_graph.AppendGroup(sOthers);
796 // Mapping to collect commit counts in each interval
797 AuthorDataMap commitCount;
799 // Loop over all intervals/weeks and collect filtered data.
800 // Sum up data in each interval until the time unit changes.
801 for (int i=m_lastInterval; i>=m_firstInterval; --i)
803 // Collect data for authors listed by name.
804 if (authors.size())
806 for (std::list<tstring>::iterator it = authors.begin(); it != authors.end(); ++it)
808 // Do we have some data for the current author in the current interval?
809 AuthorDataMap::const_iterator data_it = m_commitsPerUnitAndAuthor[i].find(*it);
810 if (data_it == m_commitsPerUnitAndAuthor[i].end())
811 continue;
812 commitCount[*it] += data_it->second;
815 // Collect data for all skipped authors.
816 if (others.size())
818 for (std::list<tstring>::iterator it = others.begin(); it != others.end(); ++it)
820 // Do we have some data for the author in the current interval?
821 AuthorDataMap::const_iterator data_it = m_commitsPerUnitAndAuthor[i].find(*it);
822 if (data_it == m_commitsPerUnitAndAuthor[i].end())
823 continue;
824 commitCount[othersName] += data_it->second;
828 // Create a new data series for this unit/interval.
829 MyGraphSeries * graphData = new MyGraphSeries();
830 // Loop over all created graphs and set the corresponding data.
831 if (authorGraphMap.size())
833 for (AuthorDataMap::const_iterator it = authorGraphMap.begin(); it != authorGraphMap.end(); ++it)
835 graphData->SetData(it->second, commitCount[it->first]);
838 graphData->SetLabel(m_unitNames[i].c_str());
839 m_graph.AddSeries(*graphData);
840 m_graphDataArray.Add(graphData);
842 // Reset commit count mapping.
843 commitCount.clear();
846 // Paint the graph now that we're through.
847 m_graph.Invalidate();
850 void CStatGraphDlg::ShowStats()
852 if(!PreViewStat(true)) return;
854 // Now we can use the gathered data to update the stats dialog.
855 size_t nAuthors = m_authorNames.size();
857 // Find most and least active author names.
858 tstring mostActiveAuthor;
859 tstring leastActiveAuthor;
860 if (nAuthors > 0)
862 mostActiveAuthor = m_authorNames.front();
863 leastActiveAuthor = m_authorNames.back();
866 // Obtain the statistics for the table.
867 long nCommitsMin = -1;
868 long nCommitsMax = -1;
869 long nFileChangesMin = -1;
870 long nFileChangesMax = -1;
872 long nMostActiveMaxCommits = -1;
873 long nMostActiveMinCommits = -1;
874 long nLeastActiveMaxCommits = -1;
875 long nLeastActiveMinCommits = -1;
877 // Loop over all intervals and find min and max values for commit count and file changes.
878 // Also store the stats for the most and least active authors.
879 for (int i=m_firstInterval; i<=m_lastInterval; ++i)
881 // Loop over all commits in this interval and count the number of commits by all authors.
882 int commitCount = 0;
883 AuthorDataMap::iterator commit_endit = m_commitsPerUnitAndAuthor[i].end();
884 for (AuthorDataMap::iterator commit_it = m_commitsPerUnitAndAuthor[i].begin();
885 commit_it != commit_endit; ++commit_it)
887 commitCount += commit_it->second;
889 if (nCommitsMin == -1 || commitCount < nCommitsMin)
890 nCommitsMin = commitCount;
891 if (nCommitsMax == -1 || commitCount > nCommitsMax)
892 nCommitsMax = commitCount;
894 // Loop over all commits in this interval and count the number of file changes by all authors.
895 int fileChangeCount = 0;
896 AuthorDataMap::iterator filechange_endit = m_filechangesPerUnitAndAuthor[i].end();
897 for (AuthorDataMap::iterator filechange_it = m_filechangesPerUnitAndAuthor[i].begin();
898 filechange_it != filechange_endit; ++filechange_it)
900 fileChangeCount += filechange_it->second;
902 if (nFileChangesMin == -1 || fileChangeCount < nFileChangesMin)
903 nFileChangesMin = fileChangeCount;
904 if (nFileChangesMax == -1 || fileChangeCount > nFileChangesMax)
905 nFileChangesMax = fileChangeCount;
907 // also get min/max data for most and least active authors
908 if (nAuthors > 0)
910 // check if author is present in this interval
911 AuthorDataMap::iterator author_it = m_commitsPerUnitAndAuthor[i].find(mostActiveAuthor);
912 long authorCommits;
913 if (author_it == m_commitsPerUnitAndAuthor[i].end())
914 authorCommits = 0;
915 else
916 authorCommits = author_it->second;
917 if (nMostActiveMaxCommits == -1 || authorCommits > nMostActiveMaxCommits)
918 nMostActiveMaxCommits = authorCommits;
919 if (nMostActiveMinCommits == -1 || authorCommits < nMostActiveMinCommits)
920 nMostActiveMinCommits = authorCommits;
922 author_it = m_commitsPerUnitAndAuthor[i].find(leastActiveAuthor);
923 if (author_it == m_commitsPerUnitAndAuthor[i].end())
924 authorCommits = 0;
925 else
926 authorCommits = author_it->second;
927 if (nLeastActiveMaxCommits == -1 || authorCommits > nLeastActiveMaxCommits)
928 nLeastActiveMaxCommits = authorCommits;
929 if (nLeastActiveMinCommits == -1 || authorCommits < nLeastActiveMinCommits)
930 nLeastActiveMinCommits = authorCommits;
933 if (nMostActiveMaxCommits == -1) nMostActiveMaxCommits = 0;
934 if (nMostActiveMinCommits == -1) nMostActiveMinCommits = 0;
935 if (nLeastActiveMaxCommits == -1) nLeastActiveMaxCommits = 0;
936 if (nLeastActiveMinCommits == -1) nLeastActiveMinCommits = 0;
938 int nWeeks = m_lastInterval-m_firstInterval;
939 if (nWeeks == 0)
940 nWeeks = 1;
941 // Adjust the labels with the unit type (week, month, ...)
942 CString labelText;
943 labelText.Format(IDS_STATGRAPH_NUMBEROFUNIT, GetUnitString());
944 SetDlgItemText(IDC_NUMWEEK, labelText);
945 labelText.Format(IDS_STATGRAPH_COMMITSBYUNIT, GetUnitString());
946 SetDlgItemText(IDC_COMMITSEACHWEEK, labelText);
947 labelText.Format(IDS_STATGRAPH_FILECHANGESBYUNIT, GetUnitString());
948 SetDlgItemText(IDC_FILECHANGESEACHWEEK, labelText);
949 // We have now all data we want and we can fill in the labels...
950 CString number;
951 number.Format(_T("%ld"), nWeeks);
952 SetDlgItemText(IDC_NUMWEEKVALUE, number);
953 number.Format(_T("%ld"), nAuthors);
954 SetDlgItemText(IDC_NUMAUTHORVALUE, number);
955 number.Format(_T("%ld"), m_nTotalCommits);
956 SetDlgItemText(IDC_NUMCOMMITSVALUE, number);
957 number.Format(_T("%ld"), m_nTotalFileChanges);
958 //SetDlgItemText(IDC_NUMFILECHANGESVALUE, number);
960 number.Format(_T("%ld"), m_parAuthors->GetCount() / nWeeks);
961 SetDlgItemText(IDC_COMMITSEACHWEEKAVG, number);
962 number.Format(_T("%ld"), nCommitsMax);
963 SetDlgItemText(IDC_COMMITSEACHWEEKMAX, number);
964 number.Format(_T("%ld"), nCommitsMin);
965 SetDlgItemText(IDC_COMMITSEACHWEEKMIN, number);
967 number.Format(_T("%ld"), m_nTotalFileChanges / nWeeks);
968 //SetDlgItemText(IDC_FILECHANGESEACHWEEKAVG, number);
969 number.Format(_T("%ld"), nFileChangesMax);
970 //SetDlgItemText(IDC_FILECHANGESEACHWEEKMAX, number);
971 number.Format(_T("%ld"), nFileChangesMin);
972 //SetDlgItemText(IDC_FILECHANGESEACHWEEKMIN, number);
974 if (nAuthors == 0)
976 SetDlgItemText(IDC_MOSTACTIVEAUTHORNAME, _T(""));
977 SetDlgItemText(IDC_MOSTACTIVEAUTHORAVG, _T("0"));
978 SetDlgItemText(IDC_MOSTACTIVEAUTHORMAX, _T("0"));
979 SetDlgItemText(IDC_MOSTACTIVEAUTHORMIN, _T("0"));
980 SetDlgItemText(IDC_LEASTACTIVEAUTHORNAME, _T(""));
981 SetDlgItemText(IDC_LEASTACTIVEAUTHORAVG, _T("0"));
982 SetDlgItemText(IDC_LEASTACTIVEAUTHORMAX, _T("0"));
983 SetDlgItemText(IDC_LEASTACTIVEAUTHORMIN, _T("0"));
985 else
987 SetDlgItemText(IDC_MOSTACTIVEAUTHORNAME, mostActiveAuthor.c_str());
988 number.Format(_T("%ld"), m_commitsPerAuthor[mostActiveAuthor] / nWeeks);
989 SetDlgItemText(IDC_MOSTACTIVEAUTHORAVG, number);
990 number.Format(_T("%ld"), nMostActiveMaxCommits);
991 SetDlgItemText(IDC_MOSTACTIVEAUTHORMAX, number);
992 number.Format(_T("%ld"), nMostActiveMinCommits);
993 SetDlgItemText(IDC_MOSTACTIVEAUTHORMIN, number);
995 SetDlgItemText(IDC_LEASTACTIVEAUTHORNAME, leastActiveAuthor.c_str());
996 number.Format(_T("%ld"), m_commitsPerAuthor[leastActiveAuthor] / nWeeks);
997 SetDlgItemText(IDC_LEASTACTIVEAUTHORAVG, number);
998 number.Format(_T("%ld"), nLeastActiveMaxCommits);
999 SetDlgItemText(IDC_LEASTACTIVEAUTHORMAX, number);
1000 number.Format(_T("%ld"), nLeastActiveMinCommits);
1001 SetDlgItemText(IDC_LEASTACTIVEAUTHORMIN, number);
1005 int CStatGraphDlg::RollPercentageOfAuthorship(double it)
1006 { return (int)it + (it - (int)it >= 0.5);}
1008 void CStatGraphDlg::OnCbnSelchangeGraphcombo()
1010 UpdateData();
1012 Metrics useMetric = (Metrics) m_cGraphType.GetItemData(m_cGraphType.GetCurSel());
1013 switch (useMetric )
1015 case AllStat:
1016 case CommitsByDate:
1017 // by date
1018 m_btnGraphLine.EnableWindow(TRUE);
1019 m_btnGraphLineStacked.EnableWindow(TRUE);
1020 m_btnGraphPie.EnableWindow(TRUE);
1021 m_GraphType = MyGraph::Line;
1022 m_bStacked = false;
1023 break;
1024 case PercentageOfAuthorship:
1025 case CommitsByAuthor:
1026 // by author
1027 m_btnGraphLine.EnableWindow(FALSE);
1028 m_btnGraphLineStacked.EnableWindow(FALSE);
1029 m_btnGraphPie.EnableWindow(TRUE);
1030 m_GraphType = MyGraph::Bar;
1031 m_bStacked = false;
1032 break;
1034 RedrawGraph();
1038 int CStatGraphDlg::GetUnitCount()
1040 if (m_nDays < 8)
1041 return m_nDays;
1042 if (m_nWeeks < 15)
1043 return m_nWeeks;
1044 if (m_nWeeks < 80)
1045 return (m_nWeeks/4)+1;
1046 if (m_nWeeks < 320)
1047 return (m_nWeeks/13)+1; // quarters
1048 return (m_nWeeks/52)+1;
1051 int CStatGraphDlg::GetUnit(const CTime& time)
1053 if (m_nDays < 8)
1054 return time.GetMonth()*100 + time.GetDay(); // month*100+day as the unit
1055 if (m_nWeeks < 15)
1056 return GetCalendarWeek(time);
1057 if (m_nWeeks < 80)
1058 return time.GetMonth();
1059 if (m_nWeeks < 320)
1060 return ((time.GetMonth()-1)/3)+1; // quarters
1061 return time.GetYear();
1064 CStatGraphDlg::UnitType CStatGraphDlg::GetUnitType()
1066 if (m_nDays < 8)
1067 return Days;
1068 if (m_nWeeks < 15)
1069 return Weeks;
1070 if (m_nWeeks < 80)
1071 return Months;
1072 if (m_nWeeks < 320)
1073 return Quarters;
1074 return Years;
1077 CString CStatGraphDlg::GetUnitString()
1079 if (m_nDays < 8)
1080 return CString(MAKEINTRESOURCE(IDS_STATGRAPH_COMMITSBYDATEXDAY));
1081 if (m_nWeeks < 15)
1082 return CString(MAKEINTRESOURCE(IDS_STATGRAPH_COMMITSBYDATEXWEEK));
1083 if (m_nWeeks < 80)
1084 return CString(MAKEINTRESOURCE(IDS_STATGRAPH_COMMITSBYDATEXMONTH));
1085 if (m_nWeeks < 320)
1086 return CString(MAKEINTRESOURCE(IDS_STATGRAPH_COMMITSBYDATEXQUARTER));
1087 return CString(MAKEINTRESOURCE(IDS_STATGRAPH_COMMITSBYDATEXYEAR));
1090 CString CStatGraphDlg::GetUnitLabel(int unit, CTime &lasttime)
1092 CString temp;
1093 switch (GetUnitType())
1095 case Days:
1097 // month*100+day as the unit
1098 int day = unit % 100;
1099 int month = unit / 100;
1100 switch (m_langOrder)
1102 case 0: // month day year
1103 temp.Format(_T("%d/%d/%.2d"), month, day, lasttime.GetYear()%100);
1104 break;
1105 case 1: // day month year
1106 default:
1107 temp.Format(_T("%d/%d/%.2d"), day, month, lasttime.GetYear()%100);
1108 break;
1109 case 2: // year month day
1110 temp.Format(_T("%.2d/%d/%d"), lasttime.GetYear()%100, month, day);
1111 break;
1114 break;
1115 case Weeks:
1117 int year = lasttime.GetYear();
1118 if ((unit == 1)&&(lasttime.GetMonth() == 12))
1119 year += 1;
1121 switch (m_langOrder)
1123 case 0: // month day year
1124 case 1: // day month year
1125 default:
1126 temp.Format(_T("%d/%.2d"), unit, year%100);
1127 break;
1128 case 2: // year month day
1129 temp.Format(_T("%.2d/%d"), year%100, unit);
1130 break;
1133 break;
1134 case Months:
1135 switch (m_langOrder)
1137 case 0: // month day year
1138 case 1: // day month year
1139 default:
1140 temp.Format(_T("%d/%.2d"), unit, lasttime.GetYear()%100);
1141 break;
1142 case 2: // year month day
1143 temp.Format(_T("%.2d/%d"), lasttime.GetYear()%100, unit);
1144 break;
1146 break;
1147 case Quarters:
1148 switch (m_langOrder)
1150 case 0: // month day year
1151 case 1: // day month year
1152 default:
1153 temp.Format(_T("%d/%.2d"), unit, lasttime.GetYear()%100);
1154 break;
1155 case 2: // year month day
1156 temp.Format(_T("%.2d/%d"), lasttime.GetYear()%100, unit);
1157 break;
1159 break;
1160 case Years:
1161 temp.Format(_T("%d"), unit);
1162 break;
1164 return temp;
1167 void CStatGraphDlg::OnHScroll(UINT nSBCode, UINT nPos, CScrollBar* pScrollBar)
1169 if (nSBCode == TB_THUMBTRACK)
1170 return CDialog::OnHScroll(nSBCode, nPos, pScrollBar);
1172 ShowSelectStat((Metrics) m_cGraphType.GetItemData(m_cGraphType.GetCurSel()));
1173 CDialog::OnHScroll(nSBCode, nPos, pScrollBar);
1176 void CStatGraphDlg::OnNeedText(NMHDR *pnmh, LRESULT * /*pResult*/)
1178 TOOLTIPTEXT* pttt = (TOOLTIPTEXT*) pnmh;
1179 if (pttt->hdr.idFrom == (UINT) m_Skipper.GetSafeHwnd())
1181 size_t included_authors_count = m_Skipper.GetPos();
1182 // if we only leave out one author, still include him with his name
1183 if (included_authors_count + 1 == m_authorNames.size())
1184 ++included_authors_count;
1186 // find the minimum number of commits that the shown authors have
1187 int min_commits = 0;
1188 included_authors_count = min(included_authors_count, m_authorNames.size());
1189 std::list<tstring>::iterator author_it = m_authorNames.begin();
1190 advance(author_it, included_authors_count);
1191 if (author_it != m_authorNames.begin())
1192 min_commits = m_commitsPerAuthor[ *(--author_it) ];
1194 CString string;
1195 int percentage = int(min_commits*100.0/(m_nTotalCommits ? m_nTotalCommits : 1));
1196 string.Format(IDS_STATGRAPH_AUTHORSLIDER_TT, m_Skipper.GetPos(), min_commits, percentage);
1197 ::lstrcpy(pttt->szText, (LPCTSTR) string);
1201 void CStatGraphDlg::AuthorsCaseSensitiveChanged()
1203 UpdateData(); // update checkbox state
1204 GatherData(); // first regenerate the statistics data
1205 RedrawGraph(); // then update the current statistics page
1208 void CStatGraphDlg::SortModeChanged()
1210 UpdateData(); // update checkbox state
1211 RedrawGraph(); // then update the current statistics page
1214 void CStatGraphDlg::ClearGraph()
1216 m_graph.Clear();
1217 for (int j=0; j<m_graphDataArray.GetCount(); ++j)
1218 delete ((MyGraphSeries *)m_graphDataArray.GetAt(j));
1219 m_graphDataArray.RemoveAll();
1222 void CStatGraphDlg::RedrawGraph()
1224 EnableDisableMenu();
1225 m_btnGraphBar.SetState(BST_UNCHECKED);
1226 m_btnGraphBarStacked.SetState(BST_UNCHECKED);
1227 m_btnGraphLine.SetState(BST_UNCHECKED);
1228 m_btnGraphLineStacked.SetState(BST_UNCHECKED);
1229 m_btnGraphPie.SetState(BST_UNCHECKED);
1231 if ((m_GraphType == MyGraph::Bar)&&(m_bStacked))
1233 m_btnGraphBarStacked.SetState(BST_CHECKED);
1235 if ((m_GraphType == MyGraph::Bar)&&(!m_bStacked))
1237 m_btnGraphBar.SetState(BST_CHECKED);
1239 if ((m_GraphType == MyGraph::Line)&&(m_bStacked))
1241 m_btnGraphLineStacked.SetState(BST_CHECKED);
1243 if ((m_GraphType == MyGraph::Line)&&(!m_bStacked))
1245 m_btnGraphLine.SetState(BST_CHECKED);
1247 if (m_GraphType == MyGraph::PieChart)
1249 m_btnGraphPie.SetState(BST_CHECKED);
1252 UpdateData();
1253 ShowSelectStat((Metrics) m_cGraphType.GetItemData(m_cGraphType.GetCurSel()), true);
1255 void CStatGraphDlg::OnBnClickedGraphbarbutton()
1257 m_GraphType = MyGraph::Bar;
1258 m_bStacked = false;
1259 RedrawGraph();
1262 void CStatGraphDlg::OnBnClickedGraphbarstackedbutton()
1264 m_GraphType = MyGraph::Bar;
1265 m_bStacked = true;
1266 RedrawGraph();
1269 void CStatGraphDlg::OnBnClickedGraphlinebutton()
1271 m_GraphType = MyGraph::Line;
1272 m_bStacked = false;
1273 RedrawGraph();
1276 void CStatGraphDlg::OnBnClickedGraphlinestackedbutton()
1278 m_GraphType = MyGraph::Line;
1279 m_bStacked = true;
1280 RedrawGraph();
1283 void CStatGraphDlg::OnBnClickedGraphpiebutton()
1285 m_GraphType = MyGraph::PieChart;
1286 m_bStacked = false;
1287 RedrawGraph();
1290 BOOL CStatGraphDlg::PreTranslateMessage(MSG* pMsg)
1292 if (NULL != m_pToolTip)
1293 m_pToolTip->RelayEvent(pMsg);
1295 return CStandAloneDialogTmpl<CResizableDialog>::PreTranslateMessage(pMsg);
1298 void CStatGraphDlg::EnableDisableMenu()
1300 UINT nEnable = MF_BYCOMMAND;
1302 Metrics SelectMetric = (Metrics) m_cGraphType.GetItemData(m_cGraphType.GetCurSel());
1304 nEnable |= (SelectMetric > TextStatStart && SelectMetric < TextStatEnd)
1305 ? (MF_DISABLED | MF_GRAYED) : MF_ENABLED;
1307 GetMenu()->EnableMenuItem(ID_FILE_SAVESTATGRAPHAS, nEnable);
1310 void CStatGraphDlg::OnFileSavestatgraphas()
1312 CString tempfile;
1313 int filterindex = 0;
1314 if (CAppUtils::FileOpenSave(tempfile, &filterindex, IDS_REVGRAPH_SAVEPIC, IDS_PICTUREFILEFILTER, false, m_hWnd))
1316 // if the user doesn't specify a file extension, default to
1317 // wmf and add that extension to the filename. But only if the
1318 // user chose the 'pictures' filter. The filename isn't changed
1319 // if the 'All files' filter was chosen.
1320 CString extension;
1321 int dotPos = tempfile.ReverseFind('.');
1322 int slashPos = tempfile.ReverseFind('\\');
1323 if (dotPos > slashPos)
1324 extension = tempfile.Mid(dotPos);
1325 if ((filterindex == 1)&&(extension.IsEmpty()))
1327 extension = _T(".wmf");
1328 tempfile += extension;
1330 SaveGraph(tempfile);
1334 void CStatGraphDlg::SaveGraph(CString sFilename)
1336 CString extension = CPathUtils::GetFileExtFromPath(sFilename);
1337 if (extension.CompareNoCase(_T(".wmf"))==0)
1339 // save the graph as an enhanced meta file
1340 CMyMetaFileDC wmfDC;
1341 wmfDC.CreateEnhanced(NULL, sFilename, NULL, _T("TortoiseSVN\0Statistics\0\0"));
1342 wmfDC.SetAttribDC(GetDC()->GetSafeHdc());
1343 RedrawGraph();
1344 m_graph.DrawGraph(wmfDC);
1345 HENHMETAFILE hemf = wmfDC.CloseEnhanced();
1346 DeleteEnhMetaFile(hemf);
1348 else
1350 // save the graph as a pixel picture instead of a vector picture
1351 // create dc to paint on
1354 CWindowDC ddc(this);
1355 CDC dc;
1356 if (!dc.CreateCompatibleDC(&ddc))
1358 ShowErrorMessage();
1359 return;
1361 CRect rect;
1362 GetDlgItem(IDC_GRAPH)->GetClientRect(&rect);
1363 HBITMAP hbm = ::CreateCompatibleBitmap(ddc.m_hDC, rect.Width(), rect.Height());
1364 if (hbm==0)
1366 ShowErrorMessage();
1367 return;
1369 HBITMAP oldbm = (HBITMAP)dc.SelectObject(hbm);
1370 // paint the whole graph
1371 RedrawGraph();
1372 m_graph.DrawGraph(dc);
1373 // now use GDI+ to save the picture
1374 CLSID encoderClsid;
1375 GdiplusStartupInput gdiplusStartupInput;
1376 ULONG_PTR gdiplusToken;
1377 CString sErrormessage;
1378 if (GdiplusStartup( &gdiplusToken, &gdiplusStartupInput, NULL )==Ok)
1381 Bitmap bitmap(hbm, NULL);
1382 if (bitmap.GetLastStatus()==Ok)
1384 // Get the CLSID of the encoder.
1385 int ret = 0;
1386 if (CPathUtils::GetFileExtFromPath(sFilename).CompareNoCase(_T(".png"))==0)
1387 ret = GetEncoderClsid(L"image/png", &encoderClsid);
1388 else if (CPathUtils::GetFileExtFromPath(sFilename).CompareNoCase(_T(".jpg"))==0)
1389 ret = GetEncoderClsid(L"image/jpeg", &encoderClsid);
1390 else if (CPathUtils::GetFileExtFromPath(sFilename).CompareNoCase(_T(".jpeg"))==0)
1391 ret = GetEncoderClsid(L"image/jpeg", &encoderClsid);
1392 else if (CPathUtils::GetFileExtFromPath(sFilename).CompareNoCase(_T(".bmp"))==0)
1393 ret = GetEncoderClsid(L"image/bmp", &encoderClsid);
1394 else if (CPathUtils::GetFileExtFromPath(sFilename).CompareNoCase(_T(".gif"))==0)
1395 ret = GetEncoderClsid(L"image/gif", &encoderClsid);
1396 else
1398 sFilename += _T(".jpg");
1399 ret = GetEncoderClsid(L"image/jpeg", &encoderClsid);
1401 if (ret >= 0)
1403 CStringW tfile = CStringW(sFilename);
1404 bitmap.Save(tfile, &encoderClsid, NULL);
1406 else
1408 sErrormessage.Format(IDS_REVGRAPH_ERR_NOENCODER, CPathUtils::GetFileExtFromPath(sFilename));
1411 else
1413 sErrormessage.LoadString(IDS_REVGRAPH_ERR_NOBITMAP);
1416 GdiplusShutdown(gdiplusToken);
1418 else
1420 sErrormessage.LoadString(IDS_REVGRAPH_ERR_GDIINIT);
1422 dc.SelectObject(oldbm);
1423 dc.DeleteDC();
1424 if (!sErrormessage.IsEmpty())
1426 ::MessageBox(m_hWnd, sErrormessage, _T("TortoiseSVN"), MB_ICONERROR);
1429 catch (CException * pE)
1431 TCHAR szErrorMsg[2048];
1432 pE->GetErrorMessage(szErrorMsg, 2048);
1433 pE->Delete();
1434 ::MessageBox(m_hWnd, szErrorMsg, _T("TortoiseSVN"), MB_ICONERROR);
1439 int CStatGraphDlg::GetEncoderClsid(const WCHAR* format, CLSID* pClsid)
1441 UINT num = 0; // number of image encoders
1442 UINT size = 0; // size of the image encoder array in bytes
1444 ImageCodecInfo* pImageCodecInfo = NULL;
1446 if (GetImageEncodersSize(&num, &size)!=Ok)
1447 return -1;
1448 if (size == 0)
1449 return -1; // Failure
1451 pImageCodecInfo = (ImageCodecInfo*)(malloc(size));
1452 if (pImageCodecInfo == NULL)
1453 return -1; // Failure
1455 if (GetImageEncoders(num, size, pImageCodecInfo)==Ok)
1457 for (UINT j = 0; j < num; ++j)
1459 if (wcscmp(pImageCodecInfo[j].MimeType, format) == 0)
1461 *pClsid = pImageCodecInfo[j].Clsid;
1462 free(pImageCodecInfo);
1463 return j; // Success
1467 free (pImageCodecInfo);
1468 return -1; // Failure
1471 void CStatGraphDlg::StoreCurrentGraphType()
1473 UpdateData();
1474 DWORD graphtype = static_cast<DWORD>(m_cGraphType.GetItemData(m_cGraphType.GetCurSel()));
1475 // encode the current chart type
1476 DWORD statspage = graphtype*10;
1477 if ((m_GraphType == MyGraph::Bar)&&(m_bStacked))
1479 statspage += 1;
1481 if ((m_GraphType == MyGraph::Bar)&&(!m_bStacked))
1483 statspage += 2;
1485 if ((m_GraphType == MyGraph::Line)&&(m_bStacked))
1487 statspage += 3;
1489 if ((m_GraphType == MyGraph::Line)&&(!m_bStacked))
1491 statspage += 4;
1493 if (m_GraphType == MyGraph::PieChart)
1495 statspage += 5;
1498 // store current chart type in registry
1499 CRegDWORD lastStatsPage = CRegDWORD(_T("Software\\TortoiseSVN\\LastViewedStatsPage"), 0);
1500 lastStatsPage = statspage;
1502 CRegDWORD regAuthors = CRegDWORD(_T("Software\\TortoiseSVN\\StatAuthorsCaseSensitive"));
1503 regAuthors = m_bAuthorsCaseSensitive;
1505 CRegDWORD regSort = CRegDWORD(_T("Software\\TortoiseSVN\\StatSortByCommitCount"));
1506 regSort = m_bSortByCommitCount;
1509 void CStatGraphDlg::ShowErrorMessage()
1511 CFormatMessageWrapper errorDetails;
1512 if (errorDetails)
1513 MessageBox( errorDetails, _T("Error"), MB_OK | MB_ICONINFORMATION );
1516 void CStatGraphDlg::ShowSelectStat(Metrics SelectedMetric, bool reloadSkiper /* = false */)
1518 switch (SelectedMetric)
1520 case AllStat:
1521 LoadListOfAuthors(m_commitsPerAuthor, reloadSkiper);
1522 ShowStats();
1523 break;
1524 case CommitsByDate:
1525 LoadListOfAuthors(m_commitsPerAuthor, reloadSkiper);
1526 ShowCommitsByDate();
1527 break;
1528 case CommitsByAuthor:
1529 LoadListOfAuthors(m_commitsPerAuthor, reloadSkiper);
1530 ShowCommitsByAuthor();
1531 break;
1532 case PercentageOfAuthorship:
1533 LoadListOfAuthors(m_PercentageOfAuthorship, reloadSkiper, true);
1534 ShowPercentageOfAuthorship();
1535 break;
1536 default:
1537 ShowErrorMessage();
1541 double CStatGraphDlg::CoeffContribution(int distFromEnd) { return distFromEnd ? 1.0 / m_CoeffAuthorShip * distFromEnd : 1;}
1544 template <class MAP>
1545 void CStatGraphDlg::DrawOthers(const std::list<tstring> &others, MyGraphSeries *graphData, MAP &map)
1547 int nCommits = 0;
1548 for (std::list<tstring>::const_iterator it = others.begin(); it != others.end(); ++it)
1550 nCommits += RollPercentageOfAuthorship(map[*it]);
1553 CString temp;
1554 temp.Format(_T(" (%ld)"), others.size());
1556 CString sOthers(MAKEINTRESOURCE(IDS_STATGRAPH_OTHERGROUP));
1557 sOthers += temp;
1558 int group = m_graph.AppendGroup(sOthers);
1559 graphData->SetData(group, (int)nCommits);
1563 template <class MAP>
1564 void CStatGraphDlg::LoadListOfAuthors (MAP &map, bool reloadSkiper/*= false*/, bool compare /*= false*/)
1566 m_authorNames.clear();
1567 if (map.size())
1569 for (MAP::const_iterator it = map.begin(); it != map.end(); ++it)
1571 if ((compare && RollPercentageOfAuthorship(map[it->first]) != 0) || !compare)
1572 m_authorNames.push_back(it->first);
1576 // Sort the list of authors based on commit count
1577 m_authorNames.sort(MoreCommitsThan< MAP::referent_type>(map));
1579 // Set Skipper
1580 SetSkipper(reloadSkiper);