If there are submodule changes in the file list when resolving conflicts while rebasi...
[TortoiseGit.git] / src / TortoiseProc / StatGraphDlg.cpp
blobd918a082d817b21fc0be1cee3a69595da95ba68f
1 // TortoiseGit - a Windows shell extension for easy version control
3 // Copyright (C) 2008-2013 - 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"
30 #include "SysProgressDlg.h"
32 #include <iterator>
33 #include <cmath>
34 #include <locale>
35 #include <list>
36 #include <utility>
38 using namespace Gdiplus;
40 // BinaryPredicate for comparing authors based on their commit count
41 template<class DataType>
42 class MoreCommitsThan : public std::binary_function<tstring, tstring, bool> {
43 public:
44 typedef std::map<tstring, DataType> MapType;
45 MoreCommitsThan(MapType &author_commits) : m_authorCommits(author_commits) {}
47 bool operator()(const tstring& lhs, const tstring& rhs) {
48 return (m_authorCommits)[lhs] > (m_authorCommits)[rhs];
51 private:
52 MapType &m_authorCommits;
56 IMPLEMENT_DYNAMIC(CStatGraphDlg, CResizableStandAloneDialog)
57 CStatGraphDlg::CStatGraphDlg(CWnd* pParent /*=NULL*/)
58 : CResizableStandAloneDialog(CStatGraphDlg::IDD, pParent)
59 , m_bStacked(FALSE)
60 , m_GraphType(MyGraph::Bar)
61 , m_bAuthorsCaseSensitive(TRUE)
62 , m_bSortByCommitCount(TRUE)
63 , m_nWeeks(-1)
64 , m_nDays(-1)
65 , m_langOrder(0)
66 , m_firstInterval(0)
67 , m_lastInterval(0)
68 , m_nTotalCommits(0)
69 , m_nTotalLinesInc(0)
70 , m_nTotalLinesDec(0)
71 , m_nTotalLinesNew(0)
72 , m_nTotalLinesDel(0)
73 , m_bDiffFetched(FALSE)
74 , m_ShowList(NULL)
76 m_pToolTip = NULL;
79 CStatGraphDlg::~CStatGraphDlg()
81 ClearGraph();
82 delete m_pToolTip;
85 void CStatGraphDlg::OnOK() {
86 StoreCurrentGraphType();
87 __super::OnOK();
90 void CStatGraphDlg::OnCancel() {
91 StoreCurrentGraphType();
92 __super::OnCancel();
95 void CStatGraphDlg::DoDataExchange(CDataExchange* pDX)
97 CResizableStandAloneDialog::DoDataExchange(pDX);
98 DDX_Control(pDX, IDC_GRAPH, m_graph);
99 DDX_Control(pDX, IDC_GRAPHCOMBO, m_cGraphType);
100 DDX_Control(pDX, IDC_SKIPPER, m_Skipper);
101 DDX_Check(pDX, IDC_AUTHORSCASESENSITIVE, m_bAuthorsCaseSensitive);
102 DDX_Check(pDX, IDC_SORTBYCOMMITCOUNT, m_bSortByCommitCount);
103 DDX_Control(pDX, IDC_GRAPHBARBUTTON, m_btnGraphBar);
104 DDX_Control(pDX, IDC_GRAPHBARSTACKEDBUTTON, m_btnGraphBarStacked);
105 DDX_Control(pDX, IDC_GRAPHLINEBUTTON, m_btnGraphLine);
106 DDX_Control(pDX, IDC_GRAPHLINESTACKEDBUTTON, m_btnGraphLineStacked);
107 DDX_Control(pDX, IDC_GRAPHPIEBUTTON, m_btnGraphPie);
111 BEGIN_MESSAGE_MAP(CStatGraphDlg, CResizableStandAloneDialog)
112 ON_CBN_SELCHANGE(IDC_GRAPHCOMBO, OnCbnSelchangeGraphcombo)
113 ON_WM_HSCROLL()
114 ON_NOTIFY(TTN_NEEDTEXT, NULL, OnNeedText)
115 ON_BN_CLICKED(IDC_AUTHORSCASESENSITIVE, &CStatGraphDlg::AuthorsCaseSensitiveChanged)
116 ON_BN_CLICKED(IDC_SORTBYCOMMITCOUNT, &CStatGraphDlg::SortModeChanged)
117 ON_BN_CLICKED(IDC_GRAPHBARBUTTON, &CStatGraphDlg::OnBnClickedGraphbarbutton)
118 ON_BN_CLICKED(IDC_GRAPHBARSTACKEDBUTTON, &CStatGraphDlg::OnBnClickedGraphbarstackedbutton)
119 ON_BN_CLICKED(IDC_GRAPHLINEBUTTON, &CStatGraphDlg::OnBnClickedGraphlinebutton)
120 ON_BN_CLICKED(IDC_GRAPHLINESTACKEDBUTTON, &CStatGraphDlg::OnBnClickedGraphlinestackedbutton)
121 ON_BN_CLICKED(IDC_GRAPHPIEBUTTON, &CStatGraphDlg::OnBnClickedGraphpiebutton)
122 ON_COMMAND(ID_FILE_SAVESTATGRAPHAS, &CStatGraphDlg::OnFileSavestatgraphas)
123 ON_BN_CLICKED(IDC_CALC_DIFF, &CStatGraphDlg::OnBnClickedFetchDiff)
124 END_MESSAGE_MAP()
126 void CStatGraphDlg::LoadStatQueries (__in UINT curStr, Metrics loadMetric, bool setDef /* = false */)
128 CString temp;
129 temp.LoadString(curStr);
130 int sel = m_cGraphType.AddString(temp);
131 m_cGraphType.SetItemData(sel, loadMetric);
133 if (setDef) m_cGraphType.SetCurSel(sel);
136 void CStatGraphDlg::SetSkipper (bool reloadSkiper)
138 // We need to limit the number of authors due to GUI resource limitation.
139 // However, since author #251 will properly have < 1000th of the commits,
140 // the resolution limit of the screen will already not allow for displaying
141 // it in a reasonable way
143 int max_authors_count = max(1, (int)min(m_authorNames.size(), 250) );
144 m_Skipper.SetRange (1, max_authors_count);
145 m_Skipper.SetPageSize(5);
147 if (reloadSkiper)
148 m_Skipper.SetPos (max_authors_count);
151 BOOL CStatGraphDlg::OnInitDialog()
153 CResizableStandAloneDialog::OnInitDialog();
155 // gather statistics data, only needs to be updated when the checkbox with
156 // the case sensitivity of author names is changed
157 GatherData();
159 m_pToolTip = new CToolTipCtrl;
160 if (m_pToolTip->Create(this))
162 m_pToolTip->AddTool(&m_btnGraphPie, IDS_STATGRAPH_PIEBUTTON_TT);
163 m_pToolTip->AddTool(&m_btnGraphLineStacked, IDS_STATGRAPH_LINESTACKEDBUTTON_TT);
164 m_pToolTip->AddTool(&m_btnGraphLine, IDS_STATGRAPH_LINEBUTTON_TT);
165 m_pToolTip->AddTool(&m_btnGraphBarStacked, IDS_STATGRAPH_BARSTACKEDBUTTON_TT);
166 m_pToolTip->AddTool(&m_btnGraphBar, IDS_STATGRAPH_BARBUTTON_TT);
168 m_pToolTip->Activate(TRUE);
171 m_bAuthorsCaseSensitive = DWORD(CRegDWORD(_T("Software\\TortoiseGit\\StatAuthorsCaseSensitive")));
172 m_bSortByCommitCount = DWORD(CRegDWORD(_T("Software\\TortoiseGit\\StatSortByCommitCount")));
173 UpdateData(FALSE);
175 //Load statistical queries
176 LoadStatQueries(IDS_STATGRAPH_STATS, AllStat, true);
177 LoadStatQueries(IDS_STATGRAPH_COMMITSBYDATE, CommitsByDate);
178 LoadStatQueries(IDS_STATGRAPH_COMMITSBYAUTHOR, CommitsByAuthor);
179 LoadStatQueries(IDS_STATGRAPH_PERCENTAGE_OF_AUTHORSHIP, PercentageOfAuthorship);
180 LoadStatQueries(IDS_STATGRAPH_LINES_BYDATE_W, LinesWByDate);
181 LoadStatQueries(IDS_STATGRAPH_LINES_BYDATE_WO, LinesWOByDate);
183 // set the dialog title to "Statistics - path/to/whatever/we/show/the/statistics/for"
184 CString sTitle;
185 GetWindowText(sTitle);
186 CAppUtils::SetWindowTitle(m_hWnd, m_path.GetUIPathString(), sTitle);
188 m_btnGraphBar.SetImage((HICON)LoadImage(AfxGetResourceHandle(), MAKEINTRESOURCE(IDI_GRAPHBAR), IMAGE_ICON, 16, 16, LR_DEFAULTCOLOR));
189 m_btnGraphBar.SizeToContent();
190 m_btnGraphBar.Invalidate();
191 m_btnGraphBarStacked.SetImage((HICON)LoadImage(AfxGetResourceHandle(), MAKEINTRESOURCE(IDI_GRAPHBARSTACKED), IMAGE_ICON, 16, 16, LR_DEFAULTCOLOR));
192 m_btnGraphBarStacked.SizeToContent();
193 m_btnGraphBarStacked.Invalidate();
194 m_btnGraphLine.SetImage((HICON)LoadImage(AfxGetResourceHandle(), MAKEINTRESOURCE(IDI_GRAPHLINE), IMAGE_ICON, 16, 16, LR_DEFAULTCOLOR));
195 m_btnGraphLine.SizeToContent();
196 m_btnGraphLine.Invalidate();
197 m_btnGraphLineStacked.SetImage((HICON)LoadImage(AfxGetResourceHandle(), MAKEINTRESOURCE(IDI_GRAPHLINESTACKED), IMAGE_ICON, 16, 16, LR_DEFAULTCOLOR));
198 m_btnGraphLineStacked.SizeToContent();
199 m_btnGraphLineStacked.Invalidate();
200 m_btnGraphPie.SetImage((HICON)LoadImage(AfxGetResourceHandle(), MAKEINTRESOURCE(IDI_GRAPHPIE), IMAGE_ICON, 16, 16, LR_DEFAULTCOLOR));
201 m_btnGraphPie.SizeToContent();
202 m_btnGraphPie.Invalidate();
204 AddAnchor(IDC_GRAPHTYPELABEL, TOP_LEFT);
205 AddAnchor(IDC_GRAPH, TOP_LEFT, BOTTOM_RIGHT);
206 AddAnchor(IDC_GRAPHCOMBO, TOP_LEFT, TOP_RIGHT);
208 AddAnchor(IDC_NUMWEEK, TOP_LEFT);
209 AddAnchor(IDC_NUMWEEKVALUE, TOP_RIGHT);
210 AddAnchor(IDC_NUMAUTHOR, TOP_LEFT);
211 AddAnchor(IDC_NUMAUTHORVALUE, TOP_RIGHT);
212 AddAnchor(IDC_NUMCOMMITS, TOP_LEFT);
213 AddAnchor(IDC_NUMCOMMITSVALUE, TOP_RIGHT);
214 AddAnchor(IDC_NUMFILECHANGES, TOP_LEFT);
215 AddAnchor(IDC_NUMFILECHANGESVALUE, TOP_RIGHT);
217 AddAnchor(IDC_TOTAL_LINE_WITHOUT_NEW_DEL, TOP_LEFT);
218 AddAnchor(IDC_TOTAL_LINE_WITHOUT_NEW_DEL_VALUE, TOP_RIGHT);
219 AddAnchor(IDC_TOTAL_LINE_WITH_NEW_DEL, TOP_LEFT);
220 AddAnchor(IDC_TOTAL_LINE_WITH_NEW_DEL_VALUE, TOP_RIGHT);
222 AddAnchor(IDC_CALC_DIFF, TOP_RIGHT);
224 AddAnchor(IDC_AVG, TOP_RIGHT);
225 AddAnchor(IDC_MIN, TOP_RIGHT);
226 AddAnchor(IDC_MAX, TOP_RIGHT);
227 AddAnchor(IDC_COMMITSEACHWEEK, TOP_LEFT);
228 AddAnchor(IDC_MOSTACTIVEAUTHOR, TOP_LEFT);
229 AddAnchor(IDC_LEASTACTIVEAUTHOR, TOP_LEFT);
230 AddAnchor(IDC_MOSTACTIVEAUTHORNAME, TOP_LEFT, TOP_RIGHT);
231 AddAnchor(IDC_LEASTACTIVEAUTHORNAME, TOP_LEFT, TOP_RIGHT);
232 AddAnchor(IDC_FILECHANGESEACHWEEK, TOP_LEFT);
233 AddAnchor(IDC_COMMITSEACHWEEKAVG, TOP_RIGHT);
234 AddAnchor(IDC_COMMITSEACHWEEKMIN, TOP_RIGHT);
235 AddAnchor(IDC_COMMITSEACHWEEKMAX, TOP_RIGHT);
236 AddAnchor(IDC_MOSTACTIVEAUTHORAVG, TOP_RIGHT);
237 AddAnchor(IDC_MOSTACTIVEAUTHORMIN, TOP_RIGHT);
238 AddAnchor(IDC_MOSTACTIVEAUTHORMAX, TOP_RIGHT);
239 AddAnchor(IDC_LEASTACTIVEAUTHORAVG, TOP_RIGHT);
240 AddAnchor(IDC_LEASTACTIVEAUTHORMIN, TOP_RIGHT);
241 AddAnchor(IDC_LEASTACTIVEAUTHORMAX, TOP_RIGHT);
242 AddAnchor(IDC_FILECHANGESEACHWEEKAVG, TOP_RIGHT);
243 AddAnchor(IDC_FILECHANGESEACHWEEKMIN, TOP_RIGHT);
244 AddAnchor(IDC_FILECHANGESEACHWEEKMAX, TOP_RIGHT);
246 AddAnchor(IDC_GRAPHBARBUTTON, BOTTOM_RIGHT);
247 AddAnchor(IDC_GRAPHBARSTACKEDBUTTON, BOTTOM_RIGHT);
248 AddAnchor(IDC_GRAPHLINEBUTTON, BOTTOM_RIGHT);
249 AddAnchor(IDC_GRAPHLINESTACKEDBUTTON, BOTTOM_RIGHT);
250 AddAnchor(IDC_GRAPHPIEBUTTON, BOTTOM_RIGHT);
252 AddAnchor(IDC_AUTHORSCASESENSITIVE, BOTTOM_LEFT);
253 AddAnchor(IDC_SORTBYCOMMITCOUNT, BOTTOM_LEFT);
254 AddAnchor(IDC_SKIPPER, BOTTOM_LEFT, BOTTOM_RIGHT);
255 AddAnchor(IDC_SKIPPERLABEL, BOTTOM_LEFT);
256 AddAnchor(IDOK, BOTTOM_RIGHT);
257 EnableSaveRestore(_T("StatGraphDlg"));
259 // set the min/max values on the skipper
260 SetSkipper (true);
262 // we use a stats page encoding here, 0 stands for the statistics dialog
263 CRegDWORD lastStatsPage = CRegDWORD(_T("Software\\TortoiseGit\\LastViewedStatsPage"), 0);
265 // open last viewed statistics page as first page
266 int graphtype = lastStatsPage / 10;
267 for (int i = 0; i < m_cGraphType.GetCount(); i++)
269 if ((int)m_cGraphType.GetItemData(i) == graphtype)
271 m_cGraphType.SetCurSel(i);
272 break;
276 OnCbnSelchangeGraphcombo();
278 int statspage = lastStatsPage % 10;
279 switch (statspage) {
280 case 1 :
281 m_GraphType = MyGraph::Bar;
282 m_bStacked = true;
283 break;
284 case 2 :
285 m_GraphType = MyGraph::Bar;
286 m_bStacked = false;
287 break;
288 case 3 :
289 m_GraphType = MyGraph::Line;
290 m_bStacked = true;
291 break;
292 case 4 :
293 m_GraphType = MyGraph::Line;
294 m_bStacked = false;
295 break;
296 case 5 :
297 m_GraphType = MyGraph::PieChart;
298 break;
300 default : return TRUE;
303 LCID m_locale = MAKELCID((DWORD)CRegStdDWORD(_T("Software\\TortoiseGit\\LanguageID"), MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT)), SORT_DEFAULT);
305 bool bUseSystemLocale = !!(DWORD)CRegStdDWORD(_T("Software\\TortoiseGit\\UseSystemLocaleForDates"), TRUE);
306 LCID locale = bUseSystemLocale ? MAKELCID(MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), SORT_DEFAULT) : m_locale;
308 TCHAR langBuf[11];
309 memset(langBuf, 0, sizeof(langBuf));
310 GetLocaleInfo(locale, LOCALE_IDATE, langBuf, _countof(langBuf));
312 m_langOrder = _ttoi(langBuf);
314 return TRUE;
317 void CStatGraphDlg::ShowLabels(BOOL bShow)
319 if (m_parAuthors.IsEmpty() || m_parDates.IsEmpty() || m_parFileChanges.IsEmpty())
320 return;
322 int nCmdShow = bShow ? SW_SHOW : SW_HIDE;
324 GetDlgItem(IDC_GRAPH)->ShowWindow(bShow ? SW_HIDE : SW_SHOW);
325 GetDlgItem(IDC_NUMWEEK)->ShowWindow(nCmdShow);
326 GetDlgItem(IDC_NUMWEEKVALUE)->ShowWindow(nCmdShow);
327 GetDlgItem(IDC_NUMAUTHOR)->ShowWindow(nCmdShow);
328 GetDlgItem(IDC_NUMAUTHORVALUE)->ShowWindow(nCmdShow);
329 GetDlgItem(IDC_NUMCOMMITS)->ShowWindow(nCmdShow);
330 GetDlgItem(IDC_NUMCOMMITSVALUE)->ShowWindow(nCmdShow);
331 GetDlgItem(IDC_NUMFILECHANGES)->ShowWindow(nCmdShow);
332 GetDlgItem(IDC_NUMFILECHANGESVALUE)->ShowWindow(nCmdShow);
333 GetDlgItem(IDC_TOTAL_LINE_WITHOUT_NEW_DEL)->ShowWindow(nCmdShow);
334 GetDlgItem(IDC_TOTAL_LINE_WITHOUT_NEW_DEL_VALUE)->ShowWindow(nCmdShow);
335 GetDlgItem(IDC_TOTAL_LINE_WITH_NEW_DEL)->ShowWindow(nCmdShow);
336 GetDlgItem(IDC_TOTAL_LINE_WITH_NEW_DEL_VALUE)->ShowWindow(nCmdShow);
337 GetDlgItem(IDC_CALC_DIFF)->ShowWindow(nCmdShow && !m_bDiffFetched);
339 GetDlgItem(IDC_AVG)->ShowWindow(nCmdShow);
340 GetDlgItem(IDC_MIN)->ShowWindow(nCmdShow);
341 GetDlgItem(IDC_MAX)->ShowWindow(nCmdShow);
342 GetDlgItem(IDC_COMMITSEACHWEEK)->ShowWindow(nCmdShow);
343 GetDlgItem(IDC_MOSTACTIVEAUTHOR)->ShowWindow(nCmdShow);
344 GetDlgItem(IDC_LEASTACTIVEAUTHOR)->ShowWindow(nCmdShow);
345 GetDlgItem(IDC_MOSTACTIVEAUTHORNAME)->ShowWindow(nCmdShow);
346 GetDlgItem(IDC_LEASTACTIVEAUTHORNAME)->ShowWindow(nCmdShow);
347 //GetDlgItem(IDC_FILECHANGESEACHWEEK)->ShowWindow(nCmdShow);
348 GetDlgItem(IDC_COMMITSEACHWEEKAVG)->ShowWindow(nCmdShow);
349 GetDlgItem(IDC_COMMITSEACHWEEKMIN)->ShowWindow(nCmdShow);
350 GetDlgItem(IDC_COMMITSEACHWEEKMAX)->ShowWindow(nCmdShow);
351 GetDlgItem(IDC_MOSTACTIVEAUTHORAVG)->ShowWindow(nCmdShow);
352 GetDlgItem(IDC_MOSTACTIVEAUTHORMIN)->ShowWindow(nCmdShow);
353 GetDlgItem(IDC_MOSTACTIVEAUTHORMAX)->ShowWindow(nCmdShow);
354 GetDlgItem(IDC_LEASTACTIVEAUTHORAVG)->ShowWindow(nCmdShow);
355 GetDlgItem(IDC_LEASTACTIVEAUTHORMIN)->ShowWindow(nCmdShow);
356 GetDlgItem(IDC_LEASTACTIVEAUTHORMAX)->ShowWindow(nCmdShow);
357 GetDlgItem(IDC_FILECHANGESEACHWEEKAVG)->ShowWindow(nCmdShow);
358 GetDlgItem(IDC_FILECHANGESEACHWEEKMIN)->ShowWindow(nCmdShow);
359 GetDlgItem(IDC_FILECHANGESEACHWEEKMAX)->ShowWindow(nCmdShow);
361 GetDlgItem(IDC_SORTBYCOMMITCOUNT)->ShowWindow(bShow ? SW_HIDE : SW_SHOW);
362 GetDlgItem(IDC_SKIPPER)->ShowWindow(bShow ? SW_HIDE : SW_SHOW);
363 GetDlgItem(IDC_SKIPPERLABEL)->ShowWindow(bShow ? SW_HIDE : SW_SHOW);
364 m_btnGraphBar.ShowWindow(bShow ? SW_HIDE : SW_SHOW);
365 m_btnGraphBarStacked.ShowWindow(bShow ? SW_HIDE : SW_SHOW);
366 m_btnGraphLine.ShowWindow(bShow ? SW_HIDE : SW_SHOW);
367 m_btnGraphLineStacked.ShowWindow(bShow ? SW_HIDE : SW_SHOW);
368 m_btnGraphPie.ShowWindow(bShow ? SW_HIDE : SW_SHOW);
371 void CStatGraphDlg::UpdateWeekCount()
373 // Sanity check
374 if (m_parDates.IsEmpty())
375 return;
377 // Already updated? No need to do it again.
378 if (m_nWeeks >= 0)
379 return;
381 // Determine first and last date in dates array
382 __time64_t min_date = (__time64_t)m_parDates.GetAt(0);
383 __time64_t max_date = min_date;
384 INT_PTR count = m_parDates.GetCount();
385 for (INT_PTR i=0; i<count; ++i)
387 __time64_t d = (__time64_t)m_parDates.GetAt(i);
388 if (d < min_date) min_date = d;
389 else if (d > max_date) max_date = d;
392 // Store start date of the interval in the member variable m_minDate
393 m_minDate = min_date;
394 m_maxDate = max_date;
396 // How many weeks does the time period cover?
398 // Get time difference between start and end date
399 double secs = _difftime64(max_date, m_minDate);
401 m_nWeeks = (int)ceil(secs / (double) m_SecondsInWeek);
402 m_nDays = (int)ceil(secs / (double) m_SecondsInDay);
405 int CStatGraphDlg::GetCalendarWeek(const CTime& time)
407 // Note:
408 // the calculation of the calendar week is wrong if DST is in effect
409 // and the date to calculate the week for is in DST and within the range
410 // of the DST offset (e.g. one hour).
411 // For example, if DST starts on Sunday march 30 and the date to get the week for
412 // is Monday, march 31, 0:30:00, then the returned week is one week less than
413 // the real week.
414 // TODO: ?
415 // write a function
416 // getDSTOffset(const CTime& time)
417 // which returns the DST offset for a given time/date. Then we can use this offset
418 // to correct our GetDays() calculation to get the correct week again
419 // This of course won't work for 'history' dates, because Windows doesn't have
420 // that information (only Vista has such a function: GetTimeZoneInformationForYear() )
421 int iWeekOfYear = 0;
423 int iYear = time.GetYear();
424 int iFirstDayOfWeek = 0;
425 int iFirstWeekOfYear = 0;
426 TCHAR loc[2];
427 GetLocaleInfo(LOCALE_USER_DEFAULT, LOCALE_IFIRSTDAYOFWEEK, loc, _countof(loc));
428 iFirstDayOfWeek = int(loc[0]-'0');
429 GetLocaleInfo(LOCALE_USER_DEFAULT, LOCALE_IFIRSTWEEKOFYEAR, loc, _countof(loc));
430 iFirstWeekOfYear = int(loc[0]-'0');
431 CTime dDateFirstJanuary(iYear,1,1,0,0,0);
432 int iDayOfWeek = (dDateFirstJanuary.GetDayOfWeek()+5+iFirstDayOfWeek)%7;
434 // Select mode
435 // 0 Week containing 1/1 is the first week of that year.
436 // 1 First full week following 1/1 is the first week of that year.
437 // 2 First week containing at least four days is the first week of that year.
438 switch (iFirstWeekOfYear)
440 case 0:
442 // Week containing 1/1 is the first week of that year.
444 // check if this week reaches into the next year
445 dDateFirstJanuary = CTime(iYear+1,1,1,0,0,0);
447 // Get start of week
450 iDayOfWeek = (time.GetDayOfWeek()+5+iFirstDayOfWeek)%7;
452 catch (CException* e)
454 e->Delete();
456 CTime dStartOfWeek = time-CTimeSpan(iDayOfWeek,0,0,0);
458 // If this week spans over to 1/1 this is week 1
459 if (dStartOfWeek + CTimeSpan(6,0,0,0) >= dDateFirstJanuary)
461 // we are in the last week of the year that spans over 1/1
462 iWeekOfYear = 1;
464 else
466 // Get week day of 1/1
467 dDateFirstJanuary = CTime(iYear,1,1,0,0,0);
468 iDayOfWeek = (dDateFirstJanuary.GetDayOfWeek() +5 + iFirstDayOfWeek) % 7;
469 // Just count from 1/1
470 iWeekOfYear = (int)(((time-dDateFirstJanuary).GetDays() + iDayOfWeek) / 7) + 1;
473 break;
474 case 1:
476 // First full week following 1/1 is the first week of that year.
478 // If the 1.1 is the start of the week everything is ok
479 // else we need the next week is the correct result
480 iWeekOfYear =
481 (int)(((time-dDateFirstJanuary).GetDays() + iDayOfWeek) / 7) +
482 (iDayOfWeek==0 ? 1:0);
484 // If we are in week 0 we are in the first not full week
485 // calculate from the last year
486 if (iWeekOfYear==0)
488 // Special case: we are in the week of 1.1 but 1.1. is not on the
489 // start of week. Calculate based on the last year
490 dDateFirstJanuary = CTime(iYear-1,1,1,0,0,0);
491 iDayOfWeek =
492 (dDateFirstJanuary.GetDayOfWeek()+5+iFirstDayOfWeek)%7;
493 // and we correct this in the same we we done this before but
494 // the result is now 52 or 53 and not 0
495 iWeekOfYear =
496 (int)(((time-dDateFirstJanuary).GetDays()+iDayOfWeek) / 7) +
497 (iDayOfWeek<=3 ? 1:0);
500 break;
501 case 2:
503 // First week containing at least four days is the first week of that year.
505 // Each year can start with any day of the week. But our
506 // weeks always start with Monday. So we add the day of week
507 // before calculation of the final week of year.
508 // Rule: is the 1.1 a Mo,Tu,We,Th than the week starts on the 1.1 with
509 // week==1, else a week later, so we add one for all those days if
510 // day is less <=3 Mo,Tu,We,Th. Otherwise 1.1 is in the last week of the
511 // previous year
512 iWeekOfYear =
513 (int)(((time-dDateFirstJanuary).GetDays()+iDayOfWeek) / 7) +
514 (iDayOfWeek<=3 ? 1:0);
516 // special cases
517 if (iWeekOfYear==0)
519 // special case week 0. We got a day before the 1.1, 2.1 or 3.1, were the
520 // 1.1. is not a Mo, Tu, We, Th. So the week 1 does not start with the 1.1.
521 // So we calculate the week according to the 1.1 of the year before
523 dDateFirstJanuary = CTime(iYear-1,1,1,0,0,0);
524 iDayOfWeek =
525 (dDateFirstJanuary.GetDayOfWeek()+5+iFirstDayOfWeek)%7;
526 // and we correct this in the same we we done this before but the result
527 // is now 52 or 53 and not 0
528 iWeekOfYear =
529 (int)(((time-dDateFirstJanuary).GetDays()+iDayOfWeek) / 7) +
530 (iDayOfWeek<=3 ? 1:0);
532 else if (iWeekOfYear==53)
534 // special case week 53. Either we got the correct week 53 or we just got the
535 // week 1 of the next year. So is the 1.1.(year+1) also a Mo, Tu, We, Th than
536 // we already have the week 1, otherwise week 53 is correct
538 dDateFirstJanuary = CTime(iYear+1,1,1,0,0,0);
539 iDayOfWeek =
540 (dDateFirstJanuary.GetDayOfWeek()+5+iFirstDayOfWeek)%7;
541 // 1.1. in week 1 or week 53?
542 iWeekOfYear = iDayOfWeek<=3 ? 1:53;
545 break;
546 default:
547 ASSERT(FALSE);
548 break;
550 // return result
551 return iWeekOfYear;
554 class CDateSorter
556 public:
557 class CCommitPointer
559 public:
560 CCommitPointer():m_cont(NULL){}
561 CCommitPointer(const CCommitPointer& P_Right)
562 : m_cont(NULL)
564 *this = P_Right;
567 CCommitPointer& operator = (const CCommitPointer& P_Right)
569 if(IsPointer())
571 (*m_cont->m_parDates)[m_place] = P_Right.GetDate();
572 (*m_cont->m_parFileChanges)[m_place] = P_Right.GetChanges();
573 (*m_cont->m_parAuthors)[m_place] = P_Right.GetAuthor();
574 (*m_cont->m_lineInc)[m_place] = P_Right.GetLineInc();
575 (*m_cont->m_lineDec)[m_place] = P_Right.GetLineDec();
576 (*m_cont->m_lineNew)[m_place] = P_Right.GetLineNew();
577 (*m_cont->m_lineDel)[m_place] = P_Right.GetLineDel();
579 else
581 m_Date = P_Right.GetDate();
582 m_Changes = P_Right.GetChanges();
583 m_csAuthor = P_Right.GetAuthor();
584 m_lineInc = P_Right.GetLineInc();
585 m_lineDec = P_Right.GetLineDec();
586 m_lineNew = P_Right.GetLineNew();
587 m_lineDel = P_Right.GetLineDel();
589 return *this;
592 void Clone(const CCommitPointer& P_Right)
594 m_cont = P_Right.m_cont;
595 m_place = P_Right.m_place;
596 m_Date = P_Right.m_Date;
597 m_Changes = P_Right.m_Changes;
598 m_csAuthor = P_Right.m_csAuthor;
601 DWORD GetDate() const {return IsPointer() ? (*m_cont->m_parDates)[m_place] : m_Date;}
602 DWORD GetChanges() const {return IsPointer() ? (*m_cont->m_parFileChanges)[m_place] : m_Changes;}
603 DWORD GetLineInc() const {return IsPointer() ? (*m_cont->m_lineInc)[m_place] : m_lineInc;}
604 DWORD GetLineDec() const {return IsPointer() ? (*m_cont->m_lineDec)[m_place] : m_lineDec;}
605 DWORD GetLineNew() const {return IsPointer() ? (*m_cont->m_lineNew)[m_place] : m_lineNew;}
606 DWORD GetLineDel() const {return IsPointer() ? (*m_cont->m_lineDel)[m_place] : m_lineDel;}
607 CString GetAuthor() const {return IsPointer() ? (*m_cont->m_parAuthors)[m_place] : m_csAuthor;}
609 bool IsPointer() const {return m_cont != NULL;}
610 //When pointer
611 CDateSorter* m_cont;
612 int m_place;
614 //When element
615 DWORD m_Date;
616 DWORD m_Changes;
617 DWORD m_lineInc;
618 DWORD m_lineDec;
619 DWORD m_lineNew;
620 DWORD m_lineDel;
621 CString m_csAuthor;
624 class iterator : public std::iterator<std::random_access_iterator_tag, CCommitPointer>
626 public:
627 CCommitPointer m_ptr;
629 iterator(){}
630 iterator(const iterator& P_Right){*this = P_Right;}
631 iterator& operator=(const iterator& P_Right)
633 m_ptr.Clone(P_Right.m_ptr);
634 return *this;
637 CCommitPointer& operator*(){return m_ptr;}
638 CCommitPointer* operator->(){return &m_ptr;}
639 const CCommitPointer& operator*()const{return m_ptr;}
640 const CCommitPointer* operator->()const{return &m_ptr;}
642 iterator& operator+=(size_t P_iOffset){m_ptr.m_place += (int)P_iOffset;return *this;}
643 iterator& operator-=(size_t P_iOffset){m_ptr.m_place -= (int)P_iOffset;return *this;}
644 iterator operator+(size_t P_iOffset)const{iterator it(*this); it += P_iOffset;return it;}
645 iterator operator-(size_t P_iOffset)const{iterator it(*this); it -= P_iOffset;return it;}
647 iterator& operator++(){++m_ptr.m_place;return *this;}
648 iterator& operator--(){--m_ptr.m_place;return *this;}
649 iterator operator++(int){iterator it(*this);++*this;return it;}
650 iterator operator--(int){iterator it(*this);--*this;return it;}
652 size_t operator-(const iterator& P_itRight)const{return m_ptr.m_place - P_itRight->m_place;}
654 bool operator<(const iterator& P_itRight)const{return m_ptr.m_place < P_itRight->m_place;}
655 bool operator!=(const iterator& P_itRight)const{return m_ptr.m_place != P_itRight->m_place;}
656 bool operator==(const iterator& P_itRight)const{return m_ptr.m_place == P_itRight->m_place;}
657 bool operator>(const iterator& P_itRight)const{return m_ptr.m_place > P_itRight->m_place;}
659 iterator begin()
661 iterator it;
662 it->m_place = 0;
663 it->m_cont = this;
664 return it;
666 iterator end()
668 iterator it;
669 it->m_place = (int)m_parDates->GetCount();
670 it->m_cont = this;
671 return it;
674 CDWordArray * m_parDates;
675 CDWordArray * m_parFileChanges;
676 CDWordArray * m_lineInc;
677 CDWordArray * m_lineDec;
678 CDWordArray * m_lineNew;
679 CDWordArray * m_lineDel;
680 CStringArray * m_parAuthors;
683 class CDateSorterLess
685 public:
686 bool operator () (const CDateSorter::CCommitPointer& P_Left, const CDateSorter::CCommitPointer& P_Right) const
688 return P_Left.GetDate() > P_Right.GetDate(); //Last date first
693 int CStatGraphDlg::GatherData(BOOL fetchdiff)
695 m_parAuthors.RemoveAll();
696 m_parDates.RemoveAll();
697 m_parFileChanges.RemoveAll();
698 m_lineInc.RemoveAll();
699 m_lineDec.RemoveAll();
700 m_lineDel.RemoveAll();
701 m_lineNew.RemoveAll();
703 CSysProgressDlg progress;
704 if (fetchdiff)
706 progress.SetTitle(CString(MAKEINTRESOURCE(IDS_PROGS_TITLE_GATHERSTATISTICS)));
707 progress.FormatNonPathLine(1, IDS_PROC_STATISTICS_DIFF);
708 progress.SetAnimation(IDR_MOVEANI);
709 progress.SetTime(true);
710 progress.ShowModeless(this);
713 // create arrays which are aware of the current filter
714 DWORD starttime = GetTickCount();
715 for (INT_PTR i = 0; i < m_ShowList.GetCount(); ++i)
717 GitRev* pLogEntry = reinterpret_cast<GitRev*>(m_ShowList.SafeGetAt(i));
718 int inc, dec, incnewfile, decdeletedfile, files;
719 inc = dec = incnewfile = decdeletedfile = files= 0;
721 // do not take working dir changes into statistics
722 if (pLogEntry->m_CommitHash.IsEmpty()) {
723 continue;
726 CString strAuthor = pLogEntry->GetAuthorName();
727 if (strAuthor.IsEmpty())
729 strAuthor.LoadString(IDS_STATGRAPH_EMPTYAUTHOR);
731 m_parAuthors.Add(strAuthor);
732 m_parDates.Add(pLogEntry->GetCommitterDate().GetTime());
734 if (fetchdiff && (pLogEntry->m_ParentHash.size() <= 1))
736 CTGitPathList &list = pLogEntry->GetFiles(NULL);
737 files = list.GetCount();
739 for (int j = 0; j < files; j++)
741 if (list[j].m_Action & CTGitPath::LOGACTIONS_DELETED)
742 decdeletedfile += _tstol(list[j].m_StatDel);
743 else if(list[j].m_Action & CTGitPath::LOGACTIONS_ADDED)
744 incnewfile += _tstol(list[j].m_StatAdd);
745 else
747 inc += _tstol(list[j].m_StatAdd);
748 dec += _tstol(list[j].m_StatDel);
751 if (progress.HasUserCancelled())
753 return -1;
757 m_parFileChanges.Add(files);
758 m_lineInc.Add(inc);
759 m_lineDec.Add(dec);
760 m_lineDel.Add(decdeletedfile);
761 m_lineNew.Add(incnewfile);
763 if (progress.IsVisible() && (GetTickCount() - starttime > 100))
765 progress.FormatNonPathLine(2, _T("%s: %s"), pLogEntry->m_CommitHash.ToString().Left(g_Git.GetShortHASHLength()), pLogEntry->GetSubject());
766 progress.SetProgress64(i, m_ShowList.GetCount());
767 starttime = GetTickCount();
772 CDateSorter W_Sorter;
773 W_Sorter.m_parAuthors = &m_parAuthors;
774 W_Sorter.m_parDates = &m_parDates;
775 W_Sorter.m_parFileChanges = &m_parFileChanges;
776 W_Sorter.m_lineNew = &m_lineNew;
777 W_Sorter.m_lineDel = &m_lineDel;
778 W_Sorter.m_lineInc = &m_lineInc;
779 W_Sorter.m_lineDec = &m_lineDec;
781 std::sort(W_Sorter.begin(), W_Sorter.end(), CDateSorterLess());
783 m_nTotalCommits = m_parAuthors.GetCount();
784 m_nTotalFileChanges = 0;
786 // Update m_nWeeks and m_minDate
787 UpdateWeekCount();
789 // Now create a mapping that holds the information per week.
790 m_commitsPerUnitAndAuthor.clear();
791 m_filechangesPerUnitAndAuthor.clear();
792 m_commitsPerAuthor.clear();
793 m_PercentageOfAuthorship.clear();
794 m_LinesWPerUnitAndAuthor.clear();
795 m_LinesWOPerUnitAndAuthor.clear();
797 int interval = 0;
798 __time64_t d = (__time64_t)m_parDates.GetAt(0);
799 int nLastUnit = GetUnit(d);
800 double AllContributionAuthor = 0;
802 m_nTotalLinesInc = m_nTotalLinesDec = m_nTotalLinesNew = m_nTotalLinesDel =0;
804 // Now loop over all weeks and gather the info
805 for (LONG i=0; i<m_nTotalCommits; ++i)
807 // Find the interval number
808 __time64_t commitDate = (__time64_t)m_parDates.GetAt(i);
809 int u = GetUnit(commitDate);
810 if (nLastUnit != u)
811 interval++;
812 nLastUnit = u;
813 // Find the authors name
814 CString sAuth = m_parAuthors.GetAt(i);
815 if (!m_bAuthorsCaseSensitive)
816 sAuth = sAuth.MakeLower();
817 tstring author = tstring(sAuth);
818 // Increase total commit count for this author
819 m_commitsPerAuthor[author]++;
820 // Increase the commit count for this author in this week
821 m_commitsPerUnitAndAuthor[interval][author]++;
823 m_LinesWPerUnitAndAuthor[interval][author] += m_lineInc.GetAt(i) + m_lineDec.GetAt(i) + m_lineNew.GetAt(i) + + m_lineDel.GetAt(i);
824 m_LinesWOPerUnitAndAuthor[interval][author] += m_lineInc.GetAt(i) + m_lineDec.GetAt(i);
826 CTime t = m_parDates.GetAt(i);
827 m_unitNames[interval] = GetUnitLabel(nLastUnit, t);
828 // Increase the file change count for this author in this week
829 int fileChanges = m_parFileChanges.GetAt(i);
830 m_filechangesPerUnitAndAuthor[interval][author] += fileChanges;
831 m_nTotalFileChanges += fileChanges;
833 //calculate Contribution Author
834 double contributionAuthor = CoeffContribution((int)m_nTotalCommits - i -1) * (fileChanges ? fileChanges : 1);
835 AllContributionAuthor += contributionAuthor;
836 m_PercentageOfAuthorship[author] += contributionAuthor;
838 m_nTotalLinesInc += m_lineInc.GetAt(i);
839 m_nTotalLinesDec += m_lineDec.GetAt(i);
840 m_nTotalLinesNew += m_lineNew.GetAt(i);
841 m_nTotalLinesDel += m_lineDel.GetAt(i);
844 // Find first and last interval number.
845 if (!m_commitsPerUnitAndAuthor.empty())
847 IntervalDataMap::iterator interval_it = m_commitsPerUnitAndAuthor.begin();
848 m_firstInterval = interval_it->first;
849 interval_it = m_commitsPerUnitAndAuthor.end();
850 --interval_it;
851 m_lastInterval = interval_it->first;
852 // Sanity check - if m_lastInterval is too large it could freeze TSVN and take up all memory!!!
853 assert(m_lastInterval >= 0 && m_lastInterval < 10000);
855 else
857 m_firstInterval = 0;
858 m_lastInterval = -1;
861 // Get a list of authors names
862 LoadListOfAuthors(m_commitsPerAuthor);
864 // Calculate percent of Contribution Authors
865 for (std::list<tstring>::iterator it = m_authorNames.begin(); it != m_authorNames.end(); ++it)
867 m_PercentageOfAuthorship[*it] = (m_PercentageOfAuthorship[*it] *100)/ AllContributionAuthor;
870 // All done, now the statistics pages can retrieve the data and
871 // extract the information to be shown.
873 return 0;
877 void CStatGraphDlg::FilterSkippedAuthors(std::list<tstring>& included_authors,
878 std::list<tstring>& skipped_authors)
880 included_authors.clear();
881 skipped_authors.clear();
883 unsigned int included_authors_count = m_Skipper.GetPos();
884 // if we only leave out one author, still include him with his name
885 if (included_authors_count + 1 == m_authorNames.size())
886 ++included_authors_count;
888 // add the included authors first
889 std::list<tstring>::iterator author_it = m_authorNames.begin();
890 while (included_authors_count > 0 && author_it != m_authorNames.end())
892 // Add him/her to the included list
893 included_authors.push_back(*author_it);
894 ++author_it;
895 --included_authors_count;
898 // If we haven't reached the end yet, copy all remaining authors into the
899 // skipped author list.
900 std::copy(author_it, m_authorNames.end(), std::back_inserter(skipped_authors) );
902 // Sort authors alphabetically if user wants that.
903 if (!m_bSortByCommitCount)
904 included_authors.sort();
907 bool CStatGraphDlg::PreViewStat(bool fShowLabels)
909 if (m_parAuthors.IsEmpty() || m_parDates.IsEmpty() || m_parFileChanges.IsEmpty())
910 return false;
911 ShowLabels(fShowLabels);
913 //If view graphic
914 if (!fShowLabels) ClearGraph();
916 // This function relies on a previous call of GatherData().
917 // This can be detected by checking the week count.
918 // If the week count is equal to -1, it hasn't been called before.
919 if (m_nWeeks == -1)
920 GatherData();
921 // If week count is still -1, something bad has happened, probably invalid data!
922 if (m_nWeeks == -1)
923 return false;
925 return true;
928 MyGraphSeries *CStatGraphDlg::PreViewGraph(__in UINT GraphTitle, __in UINT YAxisLabel, __in UINT XAxisLabel /*= NULL*/)
930 if(!PreViewStat(false))
931 return NULL;
933 // We need at least one author
934 if (m_authorNames.empty())
935 return NULL;
937 // Add a single series to the chart
938 MyGraphSeries * graphData = new MyGraphSeries();
939 m_graph.AddSeries(*graphData);
940 m_graphDataArray.Add(graphData);
942 // Set up the graph.
943 CString temp;
944 UpdateData();
945 m_graph.SetGraphType(m_GraphType, m_bStacked);
946 temp.LoadString(YAxisLabel);
947 m_graph.SetYAxisLabel(temp);
948 temp.LoadString(XAxisLabel);
949 m_graph.SetXAxisLabel(temp);
950 temp.LoadString(GraphTitle);
951 m_graph.SetGraphTitle(temp);
953 return graphData;
956 void CStatGraphDlg::ShowPercentageOfAuthorship()
958 // Set up the graph.
959 MyGraphSeries * graphData = PreViewGraph(IDS_STATGRAPH_PERCENTAGE_OF_AUTHORSHIP,
960 IDS_STATGRAPH_PERCENTAGE_OF_AUTHORSHIPY,
961 IDS_STATGRAPH_COMMITSBYAUTHORX);
962 if(graphData == NULL) return;
964 // Find out which authors are to be shown and which are to be skipped.
965 std::list<tstring> authors;
966 std::list<tstring> others;
969 FilterSkippedAuthors(authors, others);
971 // Loop over all authors in the authors list and
972 // add them to the graph.
974 if (!authors.empty())
976 for (std::list<tstring>::iterator it = authors.begin(); it != authors.end(); ++it)
978 int group = m_graph.AppendGroup(it->c_str());
979 graphData->SetData(group, RollPercentageOfAuthorship(m_PercentageOfAuthorship[*it]));
983 // If we have other authors, count them and their commits.
984 if (!others.empty())
985 DrawOthers(others, graphData, m_PercentageOfAuthorship);
987 // Paint the graph now that we're through.
988 m_graph.Invalidate();
991 void CStatGraphDlg::ShowCommitsByAuthor()
993 // Set up the graph.
994 MyGraphSeries * graphData = PreViewGraph(IDS_STATGRAPH_COMMITSBYAUTHOR,
995 IDS_STATGRAPH_COMMITSBYAUTHORY,
996 IDS_STATGRAPH_COMMITSBYAUTHORX);
997 if(graphData == NULL) return;
999 // Find out which authors are to be shown and which are to be skipped.
1000 std::list<tstring> authors;
1001 std::list<tstring> others;
1002 FilterSkippedAuthors(authors, others);
1004 // Loop over all authors in the authors list and
1005 // add them to the graph.
1007 if (!authors.empty())
1009 for (std::list<tstring>::iterator it = authors.begin(); it != authors.end(); ++it)
1011 int group = m_graph.AppendGroup(it->c_str());
1012 graphData->SetData(group, m_commitsPerAuthor[*it]);
1016 // If we have other authors, count them and their commits.
1017 if (!others.empty())
1018 DrawOthers(others, graphData, m_commitsPerAuthor);
1020 // Paint the graph now that we're through.
1021 m_graph.Invalidate();
1024 void CStatGraphDlg::ShowByDate(int stringx, int title, IntervalDataMap &data)
1026 if(!PreViewStat(false)) return;
1028 // We need at least one author
1029 if (m_authorNames.empty()) return;
1031 // Set up the graph.
1032 CString temp;
1033 UpdateData();
1034 m_graph.SetGraphType(m_GraphType, m_bStacked);
1035 temp.LoadString(stringx);
1036 m_graph.SetYAxisLabel(temp);
1037 temp.LoadString(title);
1038 m_graph.SetGraphTitle(temp);
1040 m_graph.SetXAxisLabel(GetUnitString());
1042 // Find out which authors are to be shown and which are to be skipped.
1043 std::list<tstring> authors;
1044 std::list<tstring> others;
1045 FilterSkippedAuthors(authors, others);
1047 // Add a graph series for each author.
1048 AuthorDataMap authorGraphMap;
1049 for (std::list<tstring>::iterator it = authors.begin(); it != authors.end(); ++it)
1050 authorGraphMap[*it] = m_graph.AppendGroup(it->c_str());
1051 // If we have skipped authors, add a graph series for all those.
1052 CString sOthers(MAKEINTRESOURCE(IDS_STATGRAPH_OTHERGROUP));
1053 tstring othersName;
1054 if (!others.empty())
1056 temp.Format(_T(" (%ld)"), others.size());
1057 sOthers += temp;
1058 othersName = (LPCWSTR)sOthers;
1059 authorGraphMap[othersName] = m_graph.AppendGroup(sOthers);
1062 // Mapping to collect commit counts in each interval
1063 AuthorDataMap commitCount;
1065 // Loop over all intervals/weeks and collect filtered data.
1066 // Sum up data in each interval until the time unit changes.
1067 for (int i=m_lastInterval; i>=m_firstInterval; --i)
1069 // Collect data for authors listed by name.
1070 if (!authors.empty())
1072 for (std::list<tstring>::iterator it = authors.begin(); it != authors.end(); ++it)
1074 // Do we have some data for the current author in the current interval?
1075 AuthorDataMap::const_iterator data_it = data[i].find(*it);
1076 if (data_it == data[i].end())
1077 continue;
1078 commitCount[*it] += data_it->second;
1081 // Collect data for all skipped authors.
1082 if (!others.empty())
1084 for (std::list<tstring>::iterator it = others.begin(); it != others.end(); ++it)
1086 // Do we have some data for the author in the current interval?
1087 AuthorDataMap::const_iterator data_it = data[i].find(*it);
1088 if (data_it == data[i].end())
1089 continue;
1090 commitCount[othersName] += data_it->second;
1094 // Create a new data series for this unit/interval.
1095 MyGraphSeries * graphData = new MyGraphSeries();
1096 // Loop over all created graphs and set the corresponding data.
1097 if (!authorGraphMap.empty())
1099 for (AuthorDataMap::const_iterator it = authorGraphMap.begin(); it != authorGraphMap.end(); ++it)
1101 graphData->SetData(it->second, commitCount[it->first]);
1104 graphData->SetLabel(m_unitNames[i].c_str());
1105 m_graph.AddSeries(*graphData);
1106 m_graphDataArray.Add(graphData);
1108 // Reset commit count mapping.
1109 commitCount.clear();
1112 // Paint the graph now that we're through.
1113 m_graph.Invalidate();
1116 void CStatGraphDlg::ShowStats()
1118 if(!PreViewStat(true)) return;
1120 // Now we can use the gathered data to update the stats dialog.
1121 size_t nAuthors = m_authorNames.size();
1123 // Find most and least active author names.
1124 tstring mostActiveAuthor;
1125 tstring leastActiveAuthor;
1126 if (nAuthors > 0)
1128 mostActiveAuthor = m_authorNames.front();
1129 leastActiveAuthor = m_authorNames.back();
1132 // Obtain the statistics for the table.
1133 long nCommitsMin = -1;
1134 long nCommitsMax = -1;
1135 long nFileChangesMin = -1;
1136 long nFileChangesMax = -1;
1138 long nMostActiveMaxCommits = -1;
1139 long nMostActiveMinCommits = -1;
1140 long nLeastActiveMaxCommits = -1;
1141 long nLeastActiveMinCommits = -1;
1143 // Loop over all intervals and find min and max values for commit count and file changes.
1144 // Also store the stats for the most and least active authors.
1145 for (int i=m_firstInterval; i<=m_lastInterval; ++i)
1147 // Loop over all commits in this interval and count the number of commits by all authors.
1148 int commitCount = 0;
1149 AuthorDataMap::iterator commit_endit = m_commitsPerUnitAndAuthor[i].end();
1150 for (AuthorDataMap::iterator commit_it = m_commitsPerUnitAndAuthor[i].begin();
1151 commit_it != commit_endit; ++commit_it)
1153 commitCount += commit_it->second;
1155 if (nCommitsMin == -1 || commitCount < nCommitsMin)
1156 nCommitsMin = commitCount;
1157 if (nCommitsMax == -1 || commitCount > nCommitsMax)
1158 nCommitsMax = commitCount;
1160 // Loop over all commits in this interval and count the number of file changes by all authors.
1161 int fileChangeCount = 0;
1162 AuthorDataMap::iterator filechange_endit = m_filechangesPerUnitAndAuthor[i].end();
1163 for (AuthorDataMap::iterator filechange_it = m_filechangesPerUnitAndAuthor[i].begin();
1164 filechange_it != filechange_endit; ++filechange_it)
1166 fileChangeCount += filechange_it->second;
1168 if (nFileChangesMin == -1 || fileChangeCount < nFileChangesMin)
1169 nFileChangesMin = fileChangeCount;
1170 if (nFileChangesMax == -1 || fileChangeCount > nFileChangesMax)
1171 nFileChangesMax = fileChangeCount;
1173 // also get min/max data for most and least active authors
1174 if (nAuthors > 0)
1176 // check if author is present in this interval
1177 AuthorDataMap::iterator author_it = m_commitsPerUnitAndAuthor[i].find(mostActiveAuthor);
1178 long authorCommits;
1179 if (author_it == m_commitsPerUnitAndAuthor[i].end())
1180 authorCommits = 0;
1181 else
1182 authorCommits = author_it->second;
1183 if (nMostActiveMaxCommits == -1 || authorCommits > nMostActiveMaxCommits)
1184 nMostActiveMaxCommits = authorCommits;
1185 if (nMostActiveMinCommits == -1 || authorCommits < nMostActiveMinCommits)
1186 nMostActiveMinCommits = authorCommits;
1188 author_it = m_commitsPerUnitAndAuthor[i].find(leastActiveAuthor);
1189 if (author_it == m_commitsPerUnitAndAuthor[i].end())
1190 authorCommits = 0;
1191 else
1192 authorCommits = author_it->second;
1193 if (nLeastActiveMaxCommits == -1 || authorCommits > nLeastActiveMaxCommits)
1194 nLeastActiveMaxCommits = authorCommits;
1195 if (nLeastActiveMinCommits == -1 || authorCommits < nLeastActiveMinCommits)
1196 nLeastActiveMinCommits = authorCommits;
1199 if (nMostActiveMaxCommits == -1) nMostActiveMaxCommits = 0;
1200 if (nMostActiveMinCommits == -1) nMostActiveMinCommits = 0;
1201 if (nLeastActiveMaxCommits == -1) nLeastActiveMaxCommits = 0;
1202 if (nLeastActiveMinCommits == -1) nLeastActiveMinCommits = 0;
1204 int nWeeks = m_lastInterval-m_firstInterval;
1205 if (nWeeks == 0)
1206 nWeeks = 1;
1207 // Adjust the labels with the unit type (week, month, ...)
1208 CString labelText;
1209 labelText.Format(IDS_STATGRAPH_NUMBEROFUNIT, GetUnitString());
1210 SetDlgItemText(IDC_NUMWEEK, labelText);
1211 labelText.Format(IDS_STATGRAPH_COMMITSBYUNIT, GetUnitString());
1212 SetDlgItemText(IDC_COMMITSEACHWEEK, labelText);
1213 labelText.Format(IDS_STATGRAPH_FILECHANGESBYUNIT, GetUnitString());
1214 SetDlgItemText(IDC_FILECHANGESEACHWEEK, labelText);
1215 // We have now all data we want and we can fill in the labels...
1216 CString number;
1217 number.Format(_T("%ld"), nWeeks);
1218 SetDlgItemText(IDC_NUMWEEKVALUE, number);
1219 number.Format(_T("%ld"), nAuthors);
1220 SetDlgItemText(IDC_NUMAUTHORVALUE, number);
1221 number.Format(_T("%ld"), m_nTotalCommits);
1222 SetDlgItemText(IDC_NUMCOMMITSVALUE, number);
1223 number.Format(_T("%ld"), m_nTotalFileChanges);
1224 if (m_bDiffFetched)
1225 SetDlgItemText(IDC_NUMFILECHANGESVALUE, number);
1227 number.Format(_T("%ld"), m_parAuthors.GetCount() / nWeeks);
1228 SetDlgItemText(IDC_COMMITSEACHWEEKAVG, number);
1229 number.Format(_T("%ld"), nCommitsMax);
1230 SetDlgItemText(IDC_COMMITSEACHWEEKMAX, number);
1231 number.Format(_T("%ld"), nCommitsMin);
1232 SetDlgItemText(IDC_COMMITSEACHWEEKMIN, number);
1234 number.Format(_T("%ld"), m_nTotalFileChanges / nWeeks);
1235 //SetDlgItemText(IDC_FILECHANGESEACHWEEKAVG, number);
1236 number.Format(_T("%ld"), nFileChangesMax);
1237 //SetDlgItemText(IDC_FILECHANGESEACHWEEKMAX, number);
1238 number.Format(_T("%ld"), nFileChangesMin);
1239 //SetDlgItemText(IDC_FILECHANGESEACHWEEKMIN, number);
1241 number.Format(_T("%ld (%ld (+) %ld (-))"), m_nTotalLinesInc + m_nTotalLinesDec, m_nTotalLinesInc, m_nTotalLinesDec);
1242 if (m_bDiffFetched)
1243 SetDlgItemText(IDC_TOTAL_LINE_WITHOUT_NEW_DEL_VALUE, number);
1244 number.Format(_T("%ld (%ld (+) %ld (-))"), m_nTotalLinesInc + m_nTotalLinesDec + m_nTotalLinesNew + m_nTotalLinesDel,
1245 m_nTotalLinesInc + m_nTotalLinesNew, m_nTotalLinesDec + m_nTotalLinesDel);
1246 if (m_bDiffFetched)
1247 SetDlgItemText(IDC_TOTAL_LINE_WITH_NEW_DEL_VALUE, number);
1249 if (nAuthors == 0)
1251 SetDlgItemText(IDC_MOSTACTIVEAUTHORNAME, _T(""));
1252 SetDlgItemText(IDC_MOSTACTIVEAUTHORAVG, _T("0"));
1253 SetDlgItemText(IDC_MOSTACTIVEAUTHORMAX, _T("0"));
1254 SetDlgItemText(IDC_MOSTACTIVEAUTHORMIN, _T("0"));
1255 SetDlgItemText(IDC_LEASTACTIVEAUTHORNAME, _T(""));
1256 SetDlgItemText(IDC_LEASTACTIVEAUTHORAVG, _T("0"));
1257 SetDlgItemText(IDC_LEASTACTIVEAUTHORMAX, _T("0"));
1258 SetDlgItemText(IDC_LEASTACTIVEAUTHORMIN, _T("0"));
1260 else
1262 SetDlgItemText(IDC_MOSTACTIVEAUTHORNAME, mostActiveAuthor.c_str());
1263 number.Format(_T("%ld"), m_commitsPerAuthor[mostActiveAuthor] / nWeeks);
1264 SetDlgItemText(IDC_MOSTACTIVEAUTHORAVG, number);
1265 number.Format(_T("%ld"), nMostActiveMaxCommits);
1266 SetDlgItemText(IDC_MOSTACTIVEAUTHORMAX, number);
1267 number.Format(_T("%ld"), nMostActiveMinCommits);
1268 SetDlgItemText(IDC_MOSTACTIVEAUTHORMIN, number);
1270 SetDlgItemText(IDC_LEASTACTIVEAUTHORNAME, leastActiveAuthor.c_str());
1271 number.Format(_T("%ld"), m_commitsPerAuthor[leastActiveAuthor] / nWeeks);
1272 SetDlgItemText(IDC_LEASTACTIVEAUTHORAVG, number);
1273 number.Format(_T("%ld"), nLeastActiveMaxCommits);
1274 SetDlgItemText(IDC_LEASTACTIVEAUTHORMAX, number);
1275 number.Format(_T("%ld"), nLeastActiveMinCommits);
1276 SetDlgItemText(IDC_LEASTACTIVEAUTHORMIN, number);
1280 int CStatGraphDlg::RollPercentageOfAuthorship(double it)
1281 { return (int)it + (it - (int)it >= 0.5);}
1283 void CStatGraphDlg::OnCbnSelchangeGraphcombo()
1285 UpdateData();
1287 Metrics useMetric = (Metrics) m_cGraphType.GetItemData(m_cGraphType.GetCurSel());
1288 switch (useMetric )
1290 case AllStat:
1291 case CommitsByDate:
1292 // by date
1293 m_btnGraphLine.EnableWindow(TRUE);
1294 m_btnGraphLineStacked.EnableWindow(TRUE);
1295 m_btnGraphPie.EnableWindow(TRUE);
1296 m_GraphType = MyGraph::Line;
1297 m_bStacked = false;
1298 break;
1299 case PercentageOfAuthorship:
1300 case CommitsByAuthor:
1301 // by author
1302 m_btnGraphLine.EnableWindow(FALSE);
1303 m_btnGraphLineStacked.EnableWindow(FALSE);
1304 m_btnGraphPie.EnableWindow(TRUE);
1305 m_GraphType = MyGraph::Bar;
1306 m_bStacked = false;
1307 break;
1309 RedrawGraph();
1313 int CStatGraphDlg::GetUnitCount()
1315 if (m_nDays < 8)
1316 return m_nDays;
1317 if (m_nWeeks < 15)
1318 return m_nWeeks;
1319 if (m_nWeeks < 80)
1320 return (m_nWeeks/4)+1;
1321 if (m_nWeeks < 320)
1322 return (m_nWeeks/13)+1; // quarters
1323 return (m_nWeeks/52)+1;
1326 int CStatGraphDlg::GetUnit(const CTime& time)
1328 if (m_nDays < 8)
1329 return time.GetMonth()*100 + time.GetDay(); // month*100+day as the unit
1330 if (m_nWeeks < 15)
1331 return GetCalendarWeek(time);
1332 if (m_nWeeks < 80)
1333 return time.GetMonth();
1334 if (m_nWeeks < 320)
1335 return ((time.GetMonth()-1)/3)+1; // quarters
1336 return time.GetYear();
1339 CStatGraphDlg::UnitType CStatGraphDlg::GetUnitType()
1341 if (m_nDays < 8)
1342 return Days;
1343 if (m_nWeeks < 15)
1344 return Weeks;
1345 if (m_nWeeks < 80)
1346 return Months;
1347 if (m_nWeeks < 320)
1348 return Quarters;
1349 return Years;
1352 CString CStatGraphDlg::GetUnitString()
1354 if (m_nDays < 8)
1355 return CString(MAKEINTRESOURCE(IDS_STATGRAPH_COMMITSBYDATEXDAY));
1356 if (m_nWeeks < 15)
1357 return CString(MAKEINTRESOURCE(IDS_STATGRAPH_COMMITSBYDATEXWEEK));
1358 if (m_nWeeks < 80)
1359 return CString(MAKEINTRESOURCE(IDS_STATGRAPH_COMMITSBYDATEXMONTH));
1360 if (m_nWeeks < 320)
1361 return CString(MAKEINTRESOURCE(IDS_STATGRAPH_COMMITSBYDATEXQUARTER));
1362 return CString(MAKEINTRESOURCE(IDS_STATGRAPH_COMMITSBYDATEXYEAR));
1365 CString CStatGraphDlg::GetUnitLabel(int unit, CTime &lasttime)
1367 CString temp;
1368 switch (GetUnitType())
1370 case Days:
1372 // month*100+day as the unit
1373 int day = unit % 100;
1374 int month = unit / 100;
1375 switch (m_langOrder)
1377 case 0: // month day year
1378 temp.Format(_T("%d/%d/%.2d"), month, day, lasttime.GetYear()%100);
1379 break;
1380 case 1: // day month year
1381 default:
1382 temp.Format(_T("%d/%d/%.2d"), day, month, lasttime.GetYear()%100);
1383 break;
1384 case 2: // year month day
1385 temp.Format(_T("%.2d/%d/%d"), lasttime.GetYear()%100, month, day);
1386 break;
1389 break;
1390 case Weeks:
1392 int year = lasttime.GetYear();
1393 if ((unit == 1)&&(lasttime.GetMonth() == 12))
1394 year += 1;
1396 switch (m_langOrder)
1398 case 0: // month day year
1399 case 1: // day month year
1400 default:
1401 temp.Format(_T("%d/%.2d"), unit, year%100);
1402 break;
1403 case 2: // year month day
1404 temp.Format(_T("%.2d/%d"), year%100, unit);
1405 break;
1408 break;
1409 case Months:
1410 switch (m_langOrder)
1412 case 0: // month day year
1413 case 1: // day month year
1414 default:
1415 temp.Format(_T("%d/%.2d"), unit, lasttime.GetYear()%100);
1416 break;
1417 case 2: // year month day
1418 temp.Format(_T("%.2d/%d"), lasttime.GetYear()%100, unit);
1419 break;
1421 break;
1422 case Quarters:
1423 switch (m_langOrder)
1425 case 0: // month day year
1426 case 1: // day month year
1427 default:
1428 temp.Format(_T("%d/%.2d"), unit, lasttime.GetYear()%100);
1429 break;
1430 case 2: // year month day
1431 temp.Format(_T("%.2d/%d"), lasttime.GetYear()%100, unit);
1432 break;
1434 break;
1435 case Years:
1436 temp.Format(_T("%d"), unit);
1437 break;
1439 return temp;
1442 void CStatGraphDlg::OnHScroll(UINT nSBCode, UINT nPos, CScrollBar* pScrollBar)
1444 if (nSBCode == TB_THUMBTRACK)
1445 return CDialog::OnHScroll(nSBCode, nPos, pScrollBar);
1447 ShowSelectStat((Metrics) m_cGraphType.GetItemData(m_cGraphType.GetCurSel()));
1448 CDialog::OnHScroll(nSBCode, nPos, pScrollBar);
1451 void CStatGraphDlg::OnNeedText(NMHDR *pnmh, LRESULT * /*pResult*/)
1453 TOOLTIPTEXT* pttt = (TOOLTIPTEXT*) pnmh;
1454 if (pttt->hdr.idFrom == (UINT_PTR) m_Skipper.GetSafeHwnd())
1456 size_t included_authors_count = m_Skipper.GetPos();
1457 // if we only leave out one author, still include him with his name
1458 if (included_authors_count + 1 == m_authorNames.size())
1459 ++included_authors_count;
1461 // find the minimum number of commits that the shown authors have
1462 int min_commits = 0;
1463 included_authors_count = min(included_authors_count, m_authorNames.size());
1464 std::list<tstring>::iterator author_it = m_authorNames.begin();
1465 advance(author_it, included_authors_count);
1466 if (author_it != m_authorNames.begin())
1467 min_commits = m_commitsPerAuthor[ *(--author_it) ];
1469 CString string;
1470 int percentage = int(min_commits*100.0/(m_nTotalCommits ? m_nTotalCommits : 1));
1471 string.Format(IDS_STATGRAPH_AUTHORSLIDER_TT, m_Skipper.GetPos(), min_commits, percentage);
1472 ::lstrcpy(pttt->szText, (LPCTSTR) string);
1476 void CStatGraphDlg::AuthorsCaseSensitiveChanged()
1478 UpdateData(); // update checkbox state
1479 GatherData(); // first regenerate the statistics data
1480 RedrawGraph(); // then update the current statistics page
1483 void CStatGraphDlg::SortModeChanged()
1485 UpdateData(); // update checkbox state
1486 RedrawGraph(); // then update the current statistics page
1489 void CStatGraphDlg::ClearGraph()
1491 m_graph.Clear();
1492 for (int j=0; j<m_graphDataArray.GetCount(); ++j)
1493 delete ((MyGraphSeries *)m_graphDataArray.GetAt(j));
1494 m_graphDataArray.RemoveAll();
1497 void CStatGraphDlg::RedrawGraph()
1499 EnableDisableMenu();
1500 m_btnGraphBar.SetState(BST_UNCHECKED);
1501 m_btnGraphBarStacked.SetState(BST_UNCHECKED);
1502 m_btnGraphLine.SetState(BST_UNCHECKED);
1503 m_btnGraphLineStacked.SetState(BST_UNCHECKED);
1504 m_btnGraphPie.SetState(BST_UNCHECKED);
1506 if ((m_GraphType == MyGraph::Bar)&&(m_bStacked))
1508 m_btnGraphBarStacked.SetState(BST_CHECKED);
1510 if ((m_GraphType == MyGraph::Bar)&&(!m_bStacked))
1512 m_btnGraphBar.SetState(BST_CHECKED);
1514 if ((m_GraphType == MyGraph::Line)&&(m_bStacked))
1516 m_btnGraphLineStacked.SetState(BST_CHECKED);
1518 if ((m_GraphType == MyGraph::Line)&&(!m_bStacked))
1520 m_btnGraphLine.SetState(BST_CHECKED);
1522 if (m_GraphType == MyGraph::PieChart)
1524 m_btnGraphPie.SetState(BST_CHECKED);
1527 UpdateData();
1528 ShowSelectStat((Metrics) m_cGraphType.GetItemData(m_cGraphType.GetCurSel()), true);
1530 void CStatGraphDlg::OnBnClickedGraphbarbutton()
1532 m_GraphType = MyGraph::Bar;
1533 m_bStacked = false;
1534 RedrawGraph();
1537 void CStatGraphDlg::OnBnClickedGraphbarstackedbutton()
1539 m_GraphType = MyGraph::Bar;
1540 m_bStacked = true;
1541 RedrawGraph();
1544 void CStatGraphDlg::OnBnClickedGraphlinebutton()
1546 m_GraphType = MyGraph::Line;
1547 m_bStacked = false;
1548 RedrawGraph();
1551 void CStatGraphDlg::OnBnClickedGraphlinestackedbutton()
1553 m_GraphType = MyGraph::Line;
1554 m_bStacked = true;
1555 RedrawGraph();
1558 void CStatGraphDlg::OnBnClickedGraphpiebutton()
1560 m_GraphType = MyGraph::PieChart;
1561 m_bStacked = false;
1562 RedrawGraph();
1565 BOOL CStatGraphDlg::PreTranslateMessage(MSG* pMsg)
1567 if (NULL != m_pToolTip)
1568 m_pToolTip->RelayEvent(pMsg);
1570 return CStandAloneDialogTmpl<CResizableDialog>::PreTranslateMessage(pMsg);
1573 void CStatGraphDlg::EnableDisableMenu()
1575 UINT nEnable = MF_BYCOMMAND;
1577 Metrics SelectMetric = (Metrics) m_cGraphType.GetItemData(m_cGraphType.GetCurSel());
1579 nEnable |= (SelectMetric > TextStatStart && SelectMetric < TextStatEnd)
1580 ? (MF_DISABLED | MF_GRAYED) : MF_ENABLED;
1582 GetMenu()->EnableMenuItem(ID_FILE_SAVESTATGRAPHAS, nEnable);
1585 void CStatGraphDlg::OnFileSavestatgraphas()
1587 CString tempfile;
1588 int filterindex = 0;
1589 if (CAppUtils::FileOpenSave(tempfile, &filterindex, IDS_REVGRAPH_SAVEPIC, IDS_STATPICFILEFILTER, false, m_hWnd))
1591 // if the user doesn't specify a file extension, default to
1592 // wmf and add that extension to the filename. But only if the
1593 // user chose the 'pictures' filter. The filename isn't changed
1594 // if the 'All files' filter was chosen.
1595 CString extension;
1596 int dotPos = tempfile.ReverseFind('.');
1597 int slashPos = tempfile.ReverseFind('\\');
1598 if (dotPos > slashPos)
1599 extension = tempfile.Mid(dotPos);
1600 if ((filterindex == 1)&&(extension.IsEmpty()))
1602 extension = _T(".wmf");
1603 tempfile += extension;
1605 SaveGraph(tempfile);
1609 void CStatGraphDlg::SaveGraph(CString sFilename)
1611 CString extension = CPathUtils::GetFileExtFromPath(sFilename);
1612 if (extension.CompareNoCase(_T(".wmf"))==0)
1614 // save the graph as an enhanced meta file
1615 CMyMetaFileDC wmfDC;
1616 wmfDC.CreateEnhanced(NULL, sFilename, NULL, _T("TortoiseGit\0Statistics\0\0"));
1617 wmfDC.SetAttribDC(GetDC()->GetSafeHdc());
1618 RedrawGraph();
1619 m_graph.DrawGraph(wmfDC);
1620 HENHMETAFILE hemf = wmfDC.CloseEnhanced();
1621 DeleteEnhMetaFile(hemf);
1623 else
1625 // save the graph as a pixel picture instead of a vector picture
1626 // create dc to paint on
1629 CWindowDC ddc(this);
1630 CDC dc;
1631 if (!dc.CreateCompatibleDC(&ddc))
1633 ShowErrorMessage();
1634 return;
1636 CRect rect;
1637 GetDlgItem(IDC_GRAPH)->GetClientRect(&rect);
1638 HBITMAP hbm = ::CreateCompatibleBitmap(ddc.m_hDC, rect.Width(), rect.Height());
1639 if (hbm==0)
1641 ShowErrorMessage();
1642 return;
1644 HBITMAP oldbm = (HBITMAP)dc.SelectObject(hbm);
1645 // paint the whole graph
1646 RedrawGraph();
1647 m_graph.DrawGraph(dc);
1648 // now use GDI+ to save the picture
1649 CLSID encoderClsid;
1650 GdiplusStartupInput gdiplusStartupInput;
1651 ULONG_PTR gdiplusToken;
1652 CString sErrormessage;
1653 if (GdiplusStartup( &gdiplusToken, &gdiplusStartupInput, NULL )==Ok)
1656 Bitmap bitmap(hbm, NULL);
1657 if (bitmap.GetLastStatus()==Ok)
1659 // Get the CLSID of the encoder.
1660 int ret = 0;
1661 if (CPathUtils::GetFileExtFromPath(sFilename).CompareNoCase(_T(".png"))==0)
1662 ret = GetEncoderClsid(L"image/png", &encoderClsid);
1663 else if (CPathUtils::GetFileExtFromPath(sFilename).CompareNoCase(_T(".jpg"))==0)
1664 ret = GetEncoderClsid(L"image/jpeg", &encoderClsid);
1665 else if (CPathUtils::GetFileExtFromPath(sFilename).CompareNoCase(_T(".jpeg"))==0)
1666 ret = GetEncoderClsid(L"image/jpeg", &encoderClsid);
1667 else if (CPathUtils::GetFileExtFromPath(sFilename).CompareNoCase(_T(".bmp"))==0)
1668 ret = GetEncoderClsid(L"image/bmp", &encoderClsid);
1669 else if (CPathUtils::GetFileExtFromPath(sFilename).CompareNoCase(_T(".gif"))==0)
1670 ret = GetEncoderClsid(L"image/gif", &encoderClsid);
1671 else
1673 sFilename += _T(".jpg");
1674 ret = GetEncoderClsid(L"image/jpeg", &encoderClsid);
1676 if (ret >= 0)
1678 CStringW tfile = CStringW(sFilename);
1679 bitmap.Save(tfile, &encoderClsid, NULL);
1681 else
1683 sErrormessage.Format(IDS_REVGRAPH_ERR_NOENCODER, CPathUtils::GetFileExtFromPath(sFilename));
1686 else
1688 sErrormessage.LoadString(IDS_REVGRAPH_ERR_NOBITMAP);
1691 GdiplusShutdown(gdiplusToken);
1693 else
1695 sErrormessage.LoadString(IDS_REVGRAPH_ERR_GDIINIT);
1697 dc.SelectObject(oldbm);
1698 dc.DeleteDC();
1699 if (!sErrormessage.IsEmpty())
1701 ::MessageBox(m_hWnd, sErrormessage, _T("TortoiseGit"), MB_ICONERROR);
1704 catch (CException * pE)
1706 TCHAR szErrorMsg[2048];
1707 pE->GetErrorMessage(szErrorMsg, 2048);
1708 pE->Delete();
1709 ::MessageBox(m_hWnd, szErrorMsg, _T("TortoiseGit"), MB_ICONERROR);
1714 int CStatGraphDlg::GetEncoderClsid(const WCHAR* format, CLSID* pClsid)
1716 UINT num = 0; // number of image encoders
1717 UINT size = 0; // size of the image encoder array in bytes
1719 ImageCodecInfo* pImageCodecInfo = NULL;
1721 if (GetImageEncodersSize(&num, &size)!=Ok)
1722 return -1;
1723 if (size == 0)
1724 return -1; // Failure
1726 pImageCodecInfo = (ImageCodecInfo*)(malloc(size));
1727 if (pImageCodecInfo == NULL)
1728 return -1; // Failure
1730 if (GetImageEncoders(num, size, pImageCodecInfo)==Ok)
1732 for (UINT j = 0; j < num; ++j)
1734 if (wcscmp(pImageCodecInfo[j].MimeType, format) == 0)
1736 *pClsid = pImageCodecInfo[j].Clsid;
1737 free(pImageCodecInfo);
1738 return j; // Success
1742 free (pImageCodecInfo);
1743 return -1; // Failure
1746 void CStatGraphDlg::StoreCurrentGraphType()
1748 UpdateData();
1749 DWORD graphtype = static_cast<DWORD>(m_cGraphType.GetItemData(m_cGraphType.GetCurSel()));
1750 // encode the current chart type
1751 DWORD statspage = graphtype*10;
1752 if ((m_GraphType == MyGraph::Bar)&&(m_bStacked))
1754 statspage += 1;
1756 if ((m_GraphType == MyGraph::Bar)&&(!m_bStacked))
1758 statspage += 2;
1760 if ((m_GraphType == MyGraph::Line)&&(m_bStacked))
1762 statspage += 3;
1764 if ((m_GraphType == MyGraph::Line)&&(!m_bStacked))
1766 statspage += 4;
1768 if (m_GraphType == MyGraph::PieChart)
1770 statspage += 5;
1773 // store current chart type in registry
1774 CRegDWORD lastStatsPage = CRegDWORD(_T("Software\\TortoiseGit\\LastViewedStatsPage"), 0);
1775 lastStatsPage = statspage;
1777 CRegDWORD regAuthors = CRegDWORD(_T("Software\\TortoiseGit\\StatAuthorsCaseSensitive"));
1778 regAuthors = m_bAuthorsCaseSensitive;
1780 CRegDWORD regSort = CRegDWORD(_T("Software\\TortoiseGit\\StatSortByCommitCount"));
1781 regSort = m_bSortByCommitCount;
1784 void CStatGraphDlg::ShowErrorMessage()
1786 CFormatMessageWrapper errorDetails;
1787 if (errorDetails)
1788 MessageBox( errorDetails, _T("Error"), MB_OK | MB_ICONINFORMATION );
1791 void CStatGraphDlg::ShowSelectStat(Metrics SelectedMetric, bool reloadSkiper /* = false */)
1793 switch (SelectedMetric)
1795 case AllStat:
1796 LoadListOfAuthors(m_commitsPerAuthor, reloadSkiper);
1797 ShowStats();
1798 break;
1799 case CommitsByDate:
1800 LoadListOfAuthors(m_commitsPerAuthor, reloadSkiper);
1801 ShowByDate(IDS_STATGRAPH_COMMITSBYDATEY, IDS_STATGRAPH_COMMITSBYDATE, m_commitsPerUnitAndAuthor);
1802 break;
1803 case LinesWByDate:
1804 OnBnClickedFetchDiff();
1805 LoadListOfAuthors(m_commitsPerAuthor, reloadSkiper);
1806 ShowByDate(IDS_STATGRAPH_LINES_BYDATE_W_Y, IDS_STATGRAPH_LINES_BYDATE_W, m_LinesWPerUnitAndAuthor);
1807 break;
1808 case LinesWOByDate:
1809 OnBnClickedFetchDiff();
1810 LoadListOfAuthors(m_commitsPerAuthor, reloadSkiper);
1811 ShowByDate(IDS_STATGRAPH_LINES_BYDATE_WO_Y, IDS_STATGRAPH_LINES_BYDATE_WO, m_LinesWOPerUnitAndAuthor);
1812 break;
1813 case CommitsByAuthor:
1814 LoadListOfAuthors(m_commitsPerAuthor, reloadSkiper);
1815 ShowCommitsByAuthor();
1816 break;
1817 case PercentageOfAuthorship:
1818 OnBnClickedFetchDiff();
1819 LoadListOfAuthors(m_PercentageOfAuthorship, reloadSkiper, true);
1820 ShowPercentageOfAuthorship();
1821 break;
1822 default:
1823 ShowErrorMessage();
1827 double CStatGraphDlg::CoeffContribution(int distFromEnd) { return distFromEnd ? 1.0 / m_CoeffAuthorShip * distFromEnd : 1;}
1830 template <class MAP>
1831 void CStatGraphDlg::DrawOthers(const std::list<tstring> &others, MyGraphSeries *graphData, MAP &map)
1833 int nCommits = 0;
1834 for (std::list<tstring>::const_iterator it = others.begin(); it != others.end(); ++it)
1836 nCommits += RollPercentageOfAuthorship(map[*it]);
1839 CString temp;
1840 temp.Format(_T(" (%ld)"), others.size());
1842 CString sOthers(MAKEINTRESOURCE(IDS_STATGRAPH_OTHERGROUP));
1843 sOthers += temp;
1844 int group = m_graph.AppendGroup(sOthers);
1845 graphData->SetData(group, (int)nCommits);
1849 template <class MAP>
1850 void CStatGraphDlg::LoadListOfAuthors (MAP &map, bool reloadSkiper/*= false*/, bool compare /*= false*/)
1852 m_authorNames.clear();
1853 if (!map.empty())
1855 for (MAP::const_iterator it = map.begin(); it != map.end(); ++it)
1857 if ((compare && RollPercentageOfAuthorship(map[it->first]) != 0) || !compare)
1858 m_authorNames.push_back(it->first);
1862 // Sort the list of authors based on commit count
1863 m_authorNames.sort(MoreCommitsThan< MAP::referent_type>(map));
1865 // Set Skipper
1866 SetSkipper(reloadSkiper);
1870 void CStatGraphDlg::OnBnClickedFetchDiff()
1872 if (m_bDiffFetched)
1873 return;
1874 if (GatherData(TRUE))
1875 return;
1876 this->m_bDiffFetched = TRUE;
1877 GetDlgItem(IDC_CALC_DIFF)->ShowWindow(!m_bDiffFetched);
1879 ShowStats();