1 // TortoiseGit - a Windows shell extension for easy version control
3 // Copyright (C) 2008-2012 - TortoiseGit
4 // Copyright (C) 2003-2011 - TortoiseSVN
6 // This program is free software; you can redistribute it and/or
7 // modify it under the terms of the GNU General Public License
8 // as published by the Free Software Foundation; either version 2
9 // of the License, or (at your option) any later version.
11 // This program is distributed in the hope that it will be useful,
12 // but WITHOUT ANY WARRANTY; without even the implied warranty of
13 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 // GNU General Public License for more details.
16 // You should have received a copy of the GNU General Public License
17 // along with this program; if not, write to the Free Software Foundation,
18 // 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
21 #include "TortoiseProc.h"
22 #include "StatGraphDlg.h"
25 #include "StringUtils.h"
26 #include "PathUtils.h"
27 #include "MessageBox.h"
29 #include "FormatMessageWrapper.h"
37 using namespace Gdiplus
;
39 // BinaryPredicate for comparing authors based on their commit count
40 template<class DataType
>
41 class MoreCommitsThan
: public std::binary_function
<tstring
, tstring
, bool> {
43 typedef std::map
<tstring
, DataType
> MapType
;
44 MoreCommitsThan(MapType
&author_commits
) : m_authorCommits(author_commits
) {}
46 bool operator()(const tstring
& lhs
, const tstring
& rhs
) {
47 return (m_authorCommits
)[lhs
] > (m_authorCommits
)[rhs
];
51 MapType
&m_authorCommits
;
55 IMPLEMENT_DYNAMIC(CStatGraphDlg
, CResizableStandAloneDialog
)
56 CStatGraphDlg::CStatGraphDlg(CWnd
* pParent
/*=NULL*/)
57 : CResizableStandAloneDialog(CStatGraphDlg::IDD
, pParent
)
59 , m_GraphType(MyGraph::Bar
)
60 , m_bAuthorsCaseSensitive(TRUE
)
61 , m_bSortByCommitCount(TRUE
)
70 m_parFileChanges
= NULL
;
75 CStatGraphDlg::~CStatGraphDlg()
81 void CStatGraphDlg::OnOK() {
82 StoreCurrentGraphType();
86 void CStatGraphDlg::OnCancel() {
87 StoreCurrentGraphType();
91 void CStatGraphDlg::DoDataExchange(CDataExchange
* pDX
)
93 CResizableStandAloneDialog::DoDataExchange(pDX
);
94 DDX_Control(pDX
, IDC_GRAPH
, m_graph
);
95 DDX_Control(pDX
, IDC_GRAPHCOMBO
, m_cGraphType
);
96 DDX_Control(pDX
, IDC_SKIPPER
, m_Skipper
);
97 DDX_Check(pDX
, IDC_AUTHORSCASESENSITIVE
, m_bAuthorsCaseSensitive
);
98 DDX_Check(pDX
, IDC_SORTBYCOMMITCOUNT
, m_bSortByCommitCount
);
99 DDX_Control(pDX
, IDC_GRAPHBARBUTTON
, m_btnGraphBar
);
100 DDX_Control(pDX
, IDC_GRAPHBARSTACKEDBUTTON
, m_btnGraphBarStacked
);
101 DDX_Control(pDX
, IDC_GRAPHLINEBUTTON
, m_btnGraphLine
);
102 DDX_Control(pDX
, IDC_GRAPHLINESTACKEDBUTTON
, m_btnGraphLineStacked
);
103 DDX_Control(pDX
, IDC_GRAPHPIEBUTTON
, m_btnGraphPie
);
107 BEGIN_MESSAGE_MAP(CStatGraphDlg
, CResizableStandAloneDialog
)
108 ON_CBN_SELCHANGE(IDC_GRAPHCOMBO
, OnCbnSelchangeGraphcombo
)
110 ON_NOTIFY(TTN_NEEDTEXT
, NULL
, OnNeedText
)
111 ON_BN_CLICKED(IDC_AUTHORSCASESENSITIVE
, &CStatGraphDlg::AuthorsCaseSensitiveChanged
)
112 ON_BN_CLICKED(IDC_SORTBYCOMMITCOUNT
, &CStatGraphDlg::SortModeChanged
)
113 ON_BN_CLICKED(IDC_GRAPHBARBUTTON
, &CStatGraphDlg::OnBnClickedGraphbarbutton
)
114 ON_BN_CLICKED(IDC_GRAPHBARSTACKEDBUTTON
, &CStatGraphDlg::OnBnClickedGraphbarstackedbutton
)
115 ON_BN_CLICKED(IDC_GRAPHLINEBUTTON
, &CStatGraphDlg::OnBnClickedGraphlinebutton
)
116 ON_BN_CLICKED(IDC_GRAPHLINESTACKEDBUTTON
, &CStatGraphDlg::OnBnClickedGraphlinestackedbutton
)
117 ON_BN_CLICKED(IDC_GRAPHPIEBUTTON
, &CStatGraphDlg::OnBnClickedGraphpiebutton
)
118 ON_COMMAND(ID_FILE_SAVESTATGRAPHAS
, &CStatGraphDlg::OnFileSavestatgraphas
)
121 void CStatGraphDlg::LoadStatQueries (__in UINT curStr
, Metrics loadMetric
, bool setDef
/* = false */)
124 temp
.LoadString(curStr
);
125 int sel
= m_cGraphType
.AddString(temp
);
126 m_cGraphType
.SetItemData(sel
, loadMetric
);
128 if (setDef
) m_cGraphType
.SetCurSel(sel
);
131 void CStatGraphDlg::SetSkipper (bool reloadSkiper
)
133 // We need to limit the number of authors due to GUI resource limitation.
134 // However, since author #251 will properly have < 1000th of the commits,
135 // the resolution limit of the screen will already not allow for displaying
136 // it in a reasonable way
138 int max_authors_count
= max(1, (int)min(m_authorNames
.size(), 250) );
139 m_Skipper
.SetRange (1, max_authors_count
);
140 m_Skipper
.SetPageSize(5);
143 m_Skipper
.SetPos (max_authors_count
);
146 BOOL
CStatGraphDlg::OnInitDialog()
148 CResizableStandAloneDialog::OnInitDialog();
150 m_pToolTip
= new CToolTipCtrl
;
151 if (m_pToolTip
->Create(this))
153 m_pToolTip
->AddTool(&m_btnGraphPie
, IDS_STATGRAPH_PIEBUTTON_TT
);
154 m_pToolTip
->AddTool(&m_btnGraphLineStacked
, IDS_STATGRAPH_LINESTACKEDBUTTON_TT
);
155 m_pToolTip
->AddTool(&m_btnGraphLine
, IDS_STATGRAPH_LINEBUTTON_TT
);
156 m_pToolTip
->AddTool(&m_btnGraphBarStacked
, IDS_STATGRAPH_BARSTACKEDBUTTON_TT
);
157 m_pToolTip
->AddTool(&m_btnGraphBar
, IDS_STATGRAPH_BARBUTTON_TT
);
159 m_pToolTip
->Activate(TRUE
);
162 m_bAuthorsCaseSensitive
= DWORD(CRegDWORD(_T("Software\\TortoiseGit\\StatAuthorsCaseSensitive")));
163 m_bSortByCommitCount
= DWORD(CRegDWORD(_T("Software\\TortoiseGit\\StatSortByCommitCount")));
166 //Load statistical queries
167 LoadStatQueries(IDS_STATGRAPH_STATS
, AllStat
, true);
168 LoadStatQueries(IDS_STATGRAPH_COMMITSBYDATE
, CommitsByDate
);
169 LoadStatQueries(IDS_STATGRAPH_COMMITSBYAUTHOR
, CommitsByAuthor
);
170 LoadStatQueries(IDS_STATGRAPH_PERCENTAGE_OF_AUTHORSHIP
, PercentageOfAuthorship
);
172 // set the dialog title to "Statistics - path/to/whatever/we/show/the/statistics/for"
174 GetWindowText(sTitle
);
175 CAppUtils::SetWindowTitle(m_hWnd
, m_path
.GetUIPathString(), sTitle
);
177 m_btnGraphBar
.SetImage((HICON
)LoadImage(AfxGetResourceHandle(), MAKEINTRESOURCE(IDI_GRAPHBAR
), IMAGE_ICON
, 16, 16, LR_DEFAULTCOLOR
));
178 m_btnGraphBar
.SizeToContent();
179 m_btnGraphBar
.Invalidate();
180 m_btnGraphBarStacked
.SetImage((HICON
)LoadImage(AfxGetResourceHandle(), MAKEINTRESOURCE(IDI_GRAPHBARSTACKED
), IMAGE_ICON
, 16, 16, LR_DEFAULTCOLOR
));
181 m_btnGraphBarStacked
.SizeToContent();
182 m_btnGraphBarStacked
.Invalidate();
183 m_btnGraphLine
.SetImage((HICON
)LoadImage(AfxGetResourceHandle(), MAKEINTRESOURCE(IDI_GRAPHLINE
), IMAGE_ICON
, 16, 16, LR_DEFAULTCOLOR
));
184 m_btnGraphLine
.SizeToContent();
185 m_btnGraphLine
.Invalidate();
186 m_btnGraphLineStacked
.SetImage((HICON
)LoadImage(AfxGetResourceHandle(), MAKEINTRESOURCE(IDI_GRAPHLINESTACKED
), IMAGE_ICON
, 16, 16, LR_DEFAULTCOLOR
));
187 m_btnGraphLineStacked
.SizeToContent();
188 m_btnGraphLineStacked
.Invalidate();
189 m_btnGraphPie
.SetImage((HICON
)LoadImage(AfxGetResourceHandle(), MAKEINTRESOURCE(IDI_GRAPHPIE
), IMAGE_ICON
, 16, 16, LR_DEFAULTCOLOR
));
190 m_btnGraphPie
.SizeToContent();
191 m_btnGraphPie
.Invalidate();
193 AddAnchor(IDC_GRAPHTYPELABEL
, TOP_LEFT
);
194 AddAnchor(IDC_GRAPH
, TOP_LEFT
, BOTTOM_RIGHT
);
195 AddAnchor(IDC_GRAPHCOMBO
, TOP_LEFT
, TOP_RIGHT
);
197 AddAnchor(IDC_NUMWEEK
, TOP_LEFT
);
198 AddAnchor(IDC_NUMWEEKVALUE
, TOP_RIGHT
);
199 AddAnchor(IDC_NUMAUTHOR
, TOP_LEFT
);
200 AddAnchor(IDC_NUMAUTHORVALUE
, TOP_RIGHT
);
201 AddAnchor(IDC_NUMCOMMITS
, TOP_LEFT
);
202 AddAnchor(IDC_NUMCOMMITSVALUE
, TOP_RIGHT
);
203 AddAnchor(IDC_NUMFILECHANGES
, TOP_LEFT
);
204 AddAnchor(IDC_NUMFILECHANGESVALUE
, TOP_RIGHT
);
206 AddAnchor(IDC_AVG
, TOP_RIGHT
);
207 AddAnchor(IDC_MIN
, TOP_RIGHT
);
208 AddAnchor(IDC_MAX
, TOP_RIGHT
);
209 AddAnchor(IDC_COMMITSEACHWEEK
, TOP_LEFT
);
210 AddAnchor(IDC_MOSTACTIVEAUTHOR
, TOP_LEFT
);
211 AddAnchor(IDC_LEASTACTIVEAUTHOR
, TOP_LEFT
);
212 AddAnchor(IDC_MOSTACTIVEAUTHORNAME
, TOP_LEFT
, TOP_RIGHT
);
213 AddAnchor(IDC_LEASTACTIVEAUTHORNAME
, TOP_LEFT
, TOP_RIGHT
);
214 AddAnchor(IDC_FILECHANGESEACHWEEK
, TOP_LEFT
);
215 AddAnchor(IDC_COMMITSEACHWEEKAVG
, TOP_RIGHT
);
216 AddAnchor(IDC_COMMITSEACHWEEKMIN
, TOP_RIGHT
);
217 AddAnchor(IDC_COMMITSEACHWEEKMAX
, TOP_RIGHT
);
218 AddAnchor(IDC_MOSTACTIVEAUTHORAVG
, TOP_RIGHT
);
219 AddAnchor(IDC_MOSTACTIVEAUTHORMIN
, TOP_RIGHT
);
220 AddAnchor(IDC_MOSTACTIVEAUTHORMAX
, TOP_RIGHT
);
221 AddAnchor(IDC_LEASTACTIVEAUTHORAVG
, TOP_RIGHT
);
222 AddAnchor(IDC_LEASTACTIVEAUTHORMIN
, TOP_RIGHT
);
223 AddAnchor(IDC_LEASTACTIVEAUTHORMAX
, TOP_RIGHT
);
224 AddAnchor(IDC_FILECHANGESEACHWEEKAVG
, TOP_RIGHT
);
225 AddAnchor(IDC_FILECHANGESEACHWEEKMIN
, TOP_RIGHT
);
226 AddAnchor(IDC_FILECHANGESEACHWEEKMAX
, TOP_RIGHT
);
228 AddAnchor(IDC_GRAPHBARBUTTON
, BOTTOM_RIGHT
);
229 AddAnchor(IDC_GRAPHBARSTACKEDBUTTON
, BOTTOM_RIGHT
);
230 AddAnchor(IDC_GRAPHLINEBUTTON
, BOTTOM_RIGHT
);
231 AddAnchor(IDC_GRAPHLINESTACKEDBUTTON
, BOTTOM_RIGHT
);
232 AddAnchor(IDC_GRAPHPIEBUTTON
, BOTTOM_RIGHT
);
234 AddAnchor(IDC_AUTHORSCASESENSITIVE
, BOTTOM_LEFT
);
235 AddAnchor(IDC_SORTBYCOMMITCOUNT
, BOTTOM_LEFT
);
236 AddAnchor(IDC_SKIPPER
, BOTTOM_LEFT
, BOTTOM_RIGHT
);
237 AddAnchor(IDC_SKIPPERLABEL
, BOTTOM_LEFT
);
238 AddAnchor(IDOK
, BOTTOM_RIGHT
);
239 EnableSaveRestore(_T("StatGraphDlg"));
241 // gather statistics data, only needs to be updated when the checkbox with
242 // the case sensitivity of author names is changed
245 // set the min/max values on the skipper
248 // we use a stats page encoding here, 0 stands for the statistics dialog
249 CRegDWORD lastStatsPage
= CRegDWORD(_T("Software\\TortoiseGit\\LastViewedStatsPage"), 0);
251 // open last viewed statistics page as first page
252 int graphtype
= lastStatsPage
/ 10;
253 for (int i
= 0; i
< m_cGraphType
.GetCount(); i
++)
255 if ((int)m_cGraphType
.GetItemData(i
) == graphtype
)
257 m_cGraphType
.SetCurSel(i
);
262 OnCbnSelchangeGraphcombo();
264 int statspage
= lastStatsPage
% 10;
267 m_GraphType
= MyGraph::Bar
;
271 m_GraphType
= MyGraph::Bar
;
275 m_GraphType
= MyGraph::Line
;
279 m_GraphType
= MyGraph::Line
;
283 m_GraphType
= MyGraph::PieChart
;
286 default : return TRUE
;
289 LCID m_locale
= MAKELCID((DWORD
)CRegStdDWORD(_T("Software\\TortoiseGit\\LanguageID"), MAKELANGID(LANG_NEUTRAL
, SUBLANG_DEFAULT
)), SORT_DEFAULT
);
291 bool bUseSystemLocale
= !!(DWORD
)CRegStdDWORD(_T("Software\\TortoiseGit\\UseSystemLocaleForDates"), TRUE
);
292 LCID locale
= bUseSystemLocale
? MAKELCID(MAKELANGID(LANG_NEUTRAL
, SUBLANG_DEFAULT
), SORT_DEFAULT
) : m_locale
;
295 GetLocaleInfo(locale
, LOCALE_IDATE
, &l
, sizeof(TCHAR
));
297 m_langOrder
= (l
> 0) ? l
- '0' : -1;
304 void CStatGraphDlg::ShowLabels(BOOL bShow
)
306 if ((m_parAuthors
==NULL
)||(m_parDates
==NULL
)||(m_parFileChanges
==NULL
))
309 int nCmdShow
= bShow
? SW_SHOW
: SW_HIDE
;
311 GetDlgItem(IDC_GRAPH
)->ShowWindow(bShow
? SW_HIDE
: SW_SHOW
);
312 GetDlgItem(IDC_NUMWEEK
)->ShowWindow(nCmdShow
);
313 GetDlgItem(IDC_NUMWEEKVALUE
)->ShowWindow(nCmdShow
);
314 GetDlgItem(IDC_NUMAUTHOR
)->ShowWindow(nCmdShow
);
315 GetDlgItem(IDC_NUMAUTHORVALUE
)->ShowWindow(nCmdShow
);
316 GetDlgItem(IDC_NUMCOMMITS
)->ShowWindow(nCmdShow
);
317 GetDlgItem(IDC_NUMCOMMITSVALUE
)->ShowWindow(nCmdShow
);
318 //GetDlgItem(IDC_NUMFILECHANGES)->ShowWindow(nCmdShow);
319 GetDlgItem(IDC_NUMFILECHANGESVALUE
)->ShowWindow(nCmdShow
);
321 GetDlgItem(IDC_AVG
)->ShowWindow(nCmdShow
);
322 GetDlgItem(IDC_MIN
)->ShowWindow(nCmdShow
);
323 GetDlgItem(IDC_MAX
)->ShowWindow(nCmdShow
);
324 GetDlgItem(IDC_COMMITSEACHWEEK
)->ShowWindow(nCmdShow
);
325 GetDlgItem(IDC_MOSTACTIVEAUTHOR
)->ShowWindow(nCmdShow
);
326 GetDlgItem(IDC_LEASTACTIVEAUTHOR
)->ShowWindow(nCmdShow
);
327 GetDlgItem(IDC_MOSTACTIVEAUTHORNAME
)->ShowWindow(nCmdShow
);
328 GetDlgItem(IDC_LEASTACTIVEAUTHORNAME
)->ShowWindow(nCmdShow
);
329 //GetDlgItem(IDC_FILECHANGESEACHWEEK)->ShowWindow(nCmdShow);
330 GetDlgItem(IDC_COMMITSEACHWEEKAVG
)->ShowWindow(nCmdShow
);
331 GetDlgItem(IDC_COMMITSEACHWEEKMIN
)->ShowWindow(nCmdShow
);
332 GetDlgItem(IDC_COMMITSEACHWEEKMAX
)->ShowWindow(nCmdShow
);
333 GetDlgItem(IDC_MOSTACTIVEAUTHORAVG
)->ShowWindow(nCmdShow
);
334 GetDlgItem(IDC_MOSTACTIVEAUTHORMIN
)->ShowWindow(nCmdShow
);
335 GetDlgItem(IDC_MOSTACTIVEAUTHORMAX
)->ShowWindow(nCmdShow
);
336 GetDlgItem(IDC_LEASTACTIVEAUTHORAVG
)->ShowWindow(nCmdShow
);
337 GetDlgItem(IDC_LEASTACTIVEAUTHORMIN
)->ShowWindow(nCmdShow
);
338 GetDlgItem(IDC_LEASTACTIVEAUTHORMAX
)->ShowWindow(nCmdShow
);
339 GetDlgItem(IDC_FILECHANGESEACHWEEKAVG
)->ShowWindow(nCmdShow
);
340 GetDlgItem(IDC_FILECHANGESEACHWEEKMIN
)->ShowWindow(nCmdShow
);
341 GetDlgItem(IDC_FILECHANGESEACHWEEKMAX
)->ShowWindow(nCmdShow
);
343 GetDlgItem(IDC_SORTBYCOMMITCOUNT
)->ShowWindow(bShow
? SW_HIDE
: SW_SHOW
);
344 GetDlgItem(IDC_SKIPPER
)->ShowWindow(bShow
? SW_HIDE
: SW_SHOW
);
345 GetDlgItem(IDC_SKIPPERLABEL
)->ShowWindow(bShow
? SW_HIDE
: SW_SHOW
);
346 m_btnGraphBar
.ShowWindow(bShow
? SW_HIDE
: SW_SHOW
);
347 m_btnGraphBarStacked
.ShowWindow(bShow
? SW_HIDE
: SW_SHOW
);
348 m_btnGraphLine
.ShowWindow(bShow
? SW_HIDE
: SW_SHOW
);
349 m_btnGraphLineStacked
.ShowWindow(bShow
? SW_HIDE
: SW_SHOW
);
350 m_btnGraphPie
.ShowWindow(bShow
? SW_HIDE
: SW_SHOW
);
353 void CStatGraphDlg::UpdateWeekCount()
356 if ((!m_parDates
)||(m_parDates
->GetCount()==0))
359 // Already updated? No need to do it again.
363 // Determine first and last date in dates array
364 __time64_t min_date
= (__time64_t
)m_parDates
->GetAt(0);
365 __time64_t max_date
= min_date
;
366 INT_PTR count
= m_parDates
->GetCount();
367 for (INT_PTR i
=0; i
<count
; ++i
)
369 __time64_t d
= (__time64_t
)m_parDates
->GetAt(i
);
370 if (d
< min_date
) min_date
= d
;
371 else if (d
> max_date
) max_date
= d
;
374 // Store start date of the interval in the member variable m_minDate
375 m_minDate
= min_date
;
376 m_maxDate
= max_date
;
378 // How many weeks does the time period cover?
380 // Get time difference between start and end date
381 double secs
= _difftime64(max_date
, m_minDate
);
383 m_nWeeks
= (int)ceil(secs
/ (double) m_SecondsInWeek
);
384 m_nDays
= (int)ceil(secs
/ (double) m_SecondsInDay
);
387 int CStatGraphDlg::GetCalendarWeek(const CTime
& time
)
390 // the calculation of the calendar week is wrong if DST is in effect
391 // and the date to calculate the week for is in DST and within the range
392 // of the DST offset (e.g. one hour).
393 // For example, if DST starts on Sunday march 30 and the date to get the week for
394 // is Monday, march 31, 0:30:00, then the returned week is one week less than
398 // getDSTOffset(const CTime& time)
399 // which returns the DST offset for a given time/date. Then we can use this offset
400 // to correct our GetDays() calculation to get the correct week again
401 // This of course won't work for 'history' dates, because Windows doesn't have
402 // that information (only Vista has such a function: GetTimeZoneInformationForYear() )
405 int iYear
= time
.GetYear();
406 int iFirstDayOfWeek
= 0;
407 int iFirstWeekOfYear
= 0;
409 GetLocaleInfo(LOCALE_USER_DEFAULT
, LOCALE_IFIRSTDAYOFWEEK
, loc
, _countof(loc
));
410 iFirstDayOfWeek
= int(loc
[0]-'0');
411 GetLocaleInfo(LOCALE_USER_DEFAULT
, LOCALE_IFIRSTWEEKOFYEAR
, loc
, _countof(loc
));
412 iFirstWeekOfYear
= int(loc
[0]-'0');
413 CTime
dDateFirstJanuary(iYear
,1,1,0,0,0);
414 int iDayOfWeek
= (dDateFirstJanuary
.GetDayOfWeek()+5+iFirstDayOfWeek
)%7;
417 // 0 Week containing 1/1 is the first week of that year.
418 // 1 First full week following 1/1 is the first week of that year.
419 // 2 First week containing at least four days is the first week of that year.
420 switch (iFirstWeekOfYear
)
424 // Week containing 1/1 is the first week of that year.
426 // check if this week reaches into the next year
427 dDateFirstJanuary
= CTime(iYear
+1,1,1,0,0,0);
432 iDayOfWeek
= (time
.GetDayOfWeek()+5+iFirstDayOfWeek
)%7;
434 catch (CException
* e
)
438 CTime dStartOfWeek
= time
-CTimeSpan(iDayOfWeek
,0,0,0);
440 // If this week spans over to 1/1 this is week 1
441 if (dStartOfWeek
+ CTimeSpan(6,0,0,0) >= dDateFirstJanuary
)
443 // we are in the last week of the year that spans over 1/1
448 // Get week day of 1/1
449 dDateFirstJanuary
= CTime(iYear
,1,1,0,0,0);
450 iDayOfWeek
= (dDateFirstJanuary
.GetDayOfWeek() +5 + iFirstDayOfWeek
) % 7;
451 // Just count from 1/1
452 iWeekOfYear
= (int)(((time
-dDateFirstJanuary
).GetDays() + iDayOfWeek
) / 7) + 1;
458 // First full week following 1/1 is the first week of that year.
460 // If the 1.1 is the start of the week everything is ok
461 // else we need the next week is the correct result
463 (int)(((time
-dDateFirstJanuary
).GetDays() + iDayOfWeek
) / 7) +
464 (iDayOfWeek
==0 ? 1:0);
466 // If we are in week 0 we are in the first not full week
467 // calculate from the last year
470 // Special case: we are in the week of 1.1 but 1.1. is not on the
471 // start of week. Calculate based on the last year
472 dDateFirstJanuary
= CTime(iYear
-1,1,1,0,0,0);
474 (dDateFirstJanuary
.GetDayOfWeek()+5+iFirstDayOfWeek
)%7;
475 // and we correct this in the same we we done this before but
476 // the result is now 52 or 53 and not 0
478 (int)(((time
-dDateFirstJanuary
).GetDays()+iDayOfWeek
) / 7) +
479 (iDayOfWeek
<=3 ? 1:0);
485 // First week containing at least four days is the first week of that year.
487 // Each year can start with any day of the week. But our
488 // weeks always start with Monday. So we add the day of week
489 // before calculation of the final week of year.
490 // Rule: is the 1.1 a Mo,Tu,We,Th than the week starts on the 1.1 with
491 // week==1, else a week later, so we add one for all those days if
492 // day is less <=3 Mo,Tu,We,Th. Otherwise 1.1 is in the last week of the
495 (int)(((time
-dDateFirstJanuary
).GetDays()+iDayOfWeek
) / 7) +
496 (iDayOfWeek
<=3 ? 1:0);
501 // special case week 0. We got a day before the 1.1, 2.1 or 3.1, were the
502 // 1.1. is not a Mo, Tu, We, Th. So the week 1 does not start with the 1.1.
503 // So we calculate the week according to the 1.1 of the year before
505 dDateFirstJanuary
= CTime(iYear
-1,1,1,0,0,0);
507 (dDateFirstJanuary
.GetDayOfWeek()+5+iFirstDayOfWeek
)%7;
508 // and we correct this in the same we we done this before but the result
509 // is now 52 or 53 and not 0
511 (int)(((time
-dDateFirstJanuary
).GetDays()+iDayOfWeek
) / 7) +
512 (iDayOfWeek
<=3 ? 1:0);
514 else if (iWeekOfYear
==53)
516 // special case week 53. Either we got the correct week 53 or we just got the
517 // week 1 of the next year. So is the 1.1.(year+1) also a Mo, Tu, We, Th than
518 // we already have the week 1, otherwise week 53 is correct
520 dDateFirstJanuary
= CTime(iYear
+1,1,1,0,0,0);
522 (dDateFirstJanuary
.GetDayOfWeek()+5+iFirstDayOfWeek
)%7;
523 // 1.1. in week 1 or week 53?
524 iWeekOfYear
= iDayOfWeek
<=3 ? 1:53;
536 void CStatGraphDlg::GatherData()
539 if ((m_parAuthors
==NULL
)||(m_parDates
==NULL
)||(m_parFileChanges
==NULL
))
541 m_nTotalCommits
= m_parAuthors
->GetCount();
542 m_nTotalFileChanges
= 0;
544 // Update m_nWeeks and m_minDate
547 // Now create a mapping that holds the information per week.
548 m_commitsPerUnitAndAuthor
.clear();
549 m_filechangesPerUnitAndAuthor
.clear();
550 m_commitsPerAuthor
.clear();
551 m_PercentageOfAuthorship
.clear();
554 __time64_t d
= (__time64_t
)m_parDates
->GetAt(0);
555 int nLastUnit
= GetUnit(d
);
556 double AllContributionAuthor
= 0;
558 // Now loop over all weeks and gather the info
559 for (LONG i
=0; i
<m_nTotalCommits
; ++i
)
561 // Find the interval number
562 __time64_t commitDate
= (__time64_t
)m_parDates
->GetAt(i
);
563 int u
= GetUnit(commitDate
);
567 // Find the authors name
568 CString sAuth
= m_parAuthors
->GetAt(i
);
569 if (!m_bAuthorsCaseSensitive
)
570 sAuth
= sAuth
.MakeLower();
571 tstring author
= tstring(sAuth
);
572 // Increase total commit count for this author
573 m_commitsPerAuthor
[author
]++;
574 // Increase the commit count for this author in this week
575 m_commitsPerUnitAndAuthor
[interval
][author
]++;
576 CTime t
= m_parDates
->GetAt(i
);
577 m_unitNames
[interval
] = GetUnitLabel(nLastUnit
, t
);
578 // Increase the file change count for this author in this week
579 int fileChanges
= m_parFileChanges
->GetAt(i
);
580 m_filechangesPerUnitAndAuthor
[interval
][author
] += fileChanges
;
581 m_nTotalFileChanges
+= fileChanges
;
583 //calculate Contribution Author
584 double contributionAuthor
= CoeffContribution((int)m_nTotalCommits
- i
-1) * fileChanges
;
585 AllContributionAuthor
+= contributionAuthor
;
586 m_PercentageOfAuthorship
[author
] += contributionAuthor
;
589 // Find first and last interval number.
590 if (!m_commitsPerUnitAndAuthor
.empty())
592 IntervalDataMap::iterator interval_it
= m_commitsPerUnitAndAuthor
.begin();
593 m_firstInterval
= interval_it
->first
;
594 interval_it
= m_commitsPerUnitAndAuthor
.end();
596 m_lastInterval
= interval_it
->first
;
597 // Sanity check - if m_lastInterval is too large it could freeze TSVN and take up all memory!!!
598 assert(m_lastInterval
>= 0 && m_lastInterval
< 10000);
606 // Get a list of authors names
607 LoadListOfAuthors(m_commitsPerAuthor
);
609 // Calculate percent of Contribution Authors
610 for (std::list
<tstring
>::iterator it
= m_authorNames
.begin(); it
!= m_authorNames
.end(); ++it
)
612 m_PercentageOfAuthorship
[*it
] = (m_PercentageOfAuthorship
[*it
] *100)/ AllContributionAuthor
;
615 // All done, now the statistics pages can retrieve the data and
616 // extract the information to be shown.
620 void CStatGraphDlg::FilterSkippedAuthors(std::list
<tstring
>& included_authors
,
621 std::list
<tstring
>& skipped_authors
)
623 included_authors
.clear();
624 skipped_authors
.clear();
626 unsigned int included_authors_count
= m_Skipper
.GetPos();
627 // if we only leave out one author, still include him with his name
628 if (included_authors_count
+ 1 == m_authorNames
.size())
629 ++included_authors_count
;
631 // add the included authors first
632 std::list
<tstring
>::iterator author_it
= m_authorNames
.begin();
633 while (included_authors_count
> 0 && author_it
!= m_authorNames
.end())
635 // Add him/her to the included list
636 included_authors
.push_back(*author_it
);
638 --included_authors_count
;
641 // If we haven't reached the end yet, copy all remaining authors into the
642 // skipped author list.
643 std::copy(author_it
, m_authorNames
.end(), std::back_inserter(skipped_authors
) );
645 // Sort authors alphabetically if user wants that.
646 if (!m_bSortByCommitCount
)
647 included_authors
.sort();
650 bool CStatGraphDlg::PreViewStat(bool fShowLabels
)
652 if ((m_parAuthors
==NULL
)||(m_parDates
==NULL
)||(m_parFileChanges
==NULL
))
654 ShowLabels(fShowLabels
);
657 if (!fShowLabels
) ClearGraph();
659 // This function relies on a previous call of GatherData().
660 // This can be detected by checking the week count.
661 // If the week count is equal to -1, it hasn't been called before.
664 // If week count is still -1, something bad has happened, probably invalid data!
671 MyGraphSeries
*CStatGraphDlg::PreViewGraph(__in UINT GraphTitle
, __in UINT YAxisLabel
, __in UINT XAxisLabel
/*= NULL*/)
673 if(!PreViewStat(false))
676 // We need at least one author
677 if (m_authorNames
.empty())
680 // Add a single series to the chart
681 MyGraphSeries
* graphData
= new MyGraphSeries();
682 m_graph
.AddSeries(*graphData
);
683 m_graphDataArray
.Add(graphData
);
688 m_graph
.SetGraphType(m_GraphType
, m_bStacked
);
689 temp
.LoadString(YAxisLabel
);
690 m_graph
.SetYAxisLabel(temp
);
691 temp
.LoadString(XAxisLabel
);
692 m_graph
.SetXAxisLabel(temp
);
693 temp
.LoadString(GraphTitle
);
694 m_graph
.SetGraphTitle(temp
);
699 void CStatGraphDlg::ShowPercentageOfAuthorship()
702 MyGraphSeries
* graphData
= PreViewGraph(IDS_STATGRAPH_PERCENTAGE_OF_AUTHORSHIP
,
703 IDS_STATGRAPH_PERCENTAGE_OF_AUTHORSHIPY
,
704 IDS_STATGRAPH_COMMITSBYAUTHORX
);
705 if(graphData
== NULL
) return;
707 // Find out which authors are to be shown and which are to be skipped.
708 std::list
<tstring
> authors
;
709 std::list
<tstring
> others
;
712 FilterSkippedAuthors(authors
, others
);
714 // Loop over all authors in the authors list and
715 // add them to the graph.
717 if (!authors
.empty())
719 for (std::list
<tstring
>::iterator it
= authors
.begin(); it
!= authors
.end(); ++it
)
721 int group
= m_graph
.AppendGroup(it
->c_str());
722 graphData
->SetData(group
, RollPercentageOfAuthorship(m_PercentageOfAuthorship
[*it
]));
726 // If we have other authors, count them and their commits.
728 DrawOthers(others
, graphData
, m_PercentageOfAuthorship
);
730 // Paint the graph now that we're through.
731 m_graph
.Invalidate();
734 void CStatGraphDlg::ShowCommitsByAuthor()
737 MyGraphSeries
* graphData
= PreViewGraph(IDS_STATGRAPH_COMMITSBYAUTHOR
,
738 IDS_STATGRAPH_COMMITSBYAUTHORY
,
739 IDS_STATGRAPH_COMMITSBYAUTHORX
);
740 if(graphData
== NULL
) return;
742 // Find out which authors are to be shown and which are to be skipped.
743 std::list
<tstring
> authors
;
744 std::list
<tstring
> others
;
745 FilterSkippedAuthors(authors
, others
);
747 // Loop over all authors in the authors list and
748 // add them to the graph.
750 if (!authors
.empty())
752 for (std::list
<tstring
>::iterator it
= authors
.begin(); it
!= authors
.end(); ++it
)
754 int group
= m_graph
.AppendGroup(it
->c_str());
755 graphData
->SetData(group
, m_commitsPerAuthor
[*it
]);
759 // If we have other authors, count them and their commits.
761 DrawOthers(others
, graphData
, m_commitsPerAuthor
);
763 // Paint the graph now that we're through.
764 m_graph
.Invalidate();
767 void CStatGraphDlg::ShowCommitsByDate()
769 if(!PreViewStat(false)) return;
771 // We need at least one author
772 if (m_authorNames
.empty()) return;
777 m_graph
.SetGraphType(m_GraphType
, m_bStacked
);
778 temp
.LoadString(IDS_STATGRAPH_COMMITSBYDATEY
);
779 m_graph
.SetYAxisLabel(temp
);
780 temp
.LoadString(IDS_STATGRAPH_COMMITSBYDATE
);
781 m_graph
.SetGraphTitle(temp
);
783 m_graph
.SetXAxisLabel(GetUnitString());
785 // Find out which authors are to be shown and which are to be skipped.
786 std::list
<tstring
> authors
;
787 std::list
<tstring
> others
;
788 FilterSkippedAuthors(authors
, others
);
790 // Add a graph series for each author.
791 AuthorDataMap authorGraphMap
;
792 for (std::list
<tstring
>::iterator it
= authors
.begin(); it
!= authors
.end(); ++it
)
793 authorGraphMap
[*it
] = m_graph
.AppendGroup(it
->c_str());
794 // If we have skipped authors, add a graph series for all those.
795 CString
sOthers(MAKEINTRESOURCE(IDS_STATGRAPH_OTHERGROUP
));
799 temp
.Format(_T(" (%ld)"), others
.size());
801 othersName
= (LPCWSTR
)sOthers
;
802 authorGraphMap
[othersName
] = m_graph
.AppendGroup(sOthers
);
805 // Mapping to collect commit counts in each interval
806 AuthorDataMap commitCount
;
808 // Loop over all intervals/weeks and collect filtered data.
809 // Sum up data in each interval until the time unit changes.
810 for (int i
=m_lastInterval
; i
>=m_firstInterval
; --i
)
812 // Collect data for authors listed by name.
813 if (!authors
.empty())
815 for (std::list
<tstring
>::iterator it
= authors
.begin(); it
!= authors
.end(); ++it
)
817 // Do we have some data for the current author in the current interval?
818 AuthorDataMap::const_iterator data_it
= m_commitsPerUnitAndAuthor
[i
].find(*it
);
819 if (data_it
== m_commitsPerUnitAndAuthor
[i
].end())
821 commitCount
[*it
] += data_it
->second
;
824 // Collect data for all skipped authors.
827 for (std::list
<tstring
>::iterator it
= others
.begin(); it
!= others
.end(); ++it
)
829 // Do we have some data for the author in the current interval?
830 AuthorDataMap::const_iterator data_it
= m_commitsPerUnitAndAuthor
[i
].find(*it
);
831 if (data_it
== m_commitsPerUnitAndAuthor
[i
].end())
833 commitCount
[othersName
] += data_it
->second
;
837 // Create a new data series for this unit/interval.
838 MyGraphSeries
* graphData
= new MyGraphSeries();
839 // Loop over all created graphs and set the corresponding data.
840 if (!authorGraphMap
.empty())
842 for (AuthorDataMap::const_iterator it
= authorGraphMap
.begin(); it
!= authorGraphMap
.end(); ++it
)
844 graphData
->SetData(it
->second
, commitCount
[it
->first
]);
847 graphData
->SetLabel(m_unitNames
[i
].c_str());
848 m_graph
.AddSeries(*graphData
);
849 m_graphDataArray
.Add(graphData
);
851 // Reset commit count mapping.
855 // Paint the graph now that we're through.
856 m_graph
.Invalidate();
859 void CStatGraphDlg::ShowStats()
861 if(!PreViewStat(true)) return;
863 // Now we can use the gathered data to update the stats dialog.
864 size_t nAuthors
= m_authorNames
.size();
866 // Find most and least active author names.
867 tstring mostActiveAuthor
;
868 tstring leastActiveAuthor
;
871 mostActiveAuthor
= m_authorNames
.front();
872 leastActiveAuthor
= m_authorNames
.back();
875 // Obtain the statistics for the table.
876 long nCommitsMin
= -1;
877 long nCommitsMax
= -1;
878 long nFileChangesMin
= -1;
879 long nFileChangesMax
= -1;
881 long nMostActiveMaxCommits
= -1;
882 long nMostActiveMinCommits
= -1;
883 long nLeastActiveMaxCommits
= -1;
884 long nLeastActiveMinCommits
= -1;
886 // Loop over all intervals and find min and max values for commit count and file changes.
887 // Also store the stats for the most and least active authors.
888 for (int i
=m_firstInterval
; i
<=m_lastInterval
; ++i
)
890 // Loop over all commits in this interval and count the number of commits by all authors.
892 AuthorDataMap::iterator commit_endit
= m_commitsPerUnitAndAuthor
[i
].end();
893 for (AuthorDataMap::iterator commit_it
= m_commitsPerUnitAndAuthor
[i
].begin();
894 commit_it
!= commit_endit
; ++commit_it
)
896 commitCount
+= commit_it
->second
;
898 if (nCommitsMin
== -1 || commitCount
< nCommitsMin
)
899 nCommitsMin
= commitCount
;
900 if (nCommitsMax
== -1 || commitCount
> nCommitsMax
)
901 nCommitsMax
= commitCount
;
903 // Loop over all commits in this interval and count the number of file changes by all authors.
904 int fileChangeCount
= 0;
905 AuthorDataMap::iterator filechange_endit
= m_filechangesPerUnitAndAuthor
[i
].end();
906 for (AuthorDataMap::iterator filechange_it
= m_filechangesPerUnitAndAuthor
[i
].begin();
907 filechange_it
!= filechange_endit
; ++filechange_it
)
909 fileChangeCount
+= filechange_it
->second
;
911 if (nFileChangesMin
== -1 || fileChangeCount
< nFileChangesMin
)
912 nFileChangesMin
= fileChangeCount
;
913 if (nFileChangesMax
== -1 || fileChangeCount
> nFileChangesMax
)
914 nFileChangesMax
= fileChangeCount
;
916 // also get min/max data for most and least active authors
919 // check if author is present in this interval
920 AuthorDataMap::iterator author_it
= m_commitsPerUnitAndAuthor
[i
].find(mostActiveAuthor
);
922 if (author_it
== m_commitsPerUnitAndAuthor
[i
].end())
925 authorCommits
= author_it
->second
;
926 if (nMostActiveMaxCommits
== -1 || authorCommits
> nMostActiveMaxCommits
)
927 nMostActiveMaxCommits
= authorCommits
;
928 if (nMostActiveMinCommits
== -1 || authorCommits
< nMostActiveMinCommits
)
929 nMostActiveMinCommits
= authorCommits
;
931 author_it
= m_commitsPerUnitAndAuthor
[i
].find(leastActiveAuthor
);
932 if (author_it
== m_commitsPerUnitAndAuthor
[i
].end())
935 authorCommits
= author_it
->second
;
936 if (nLeastActiveMaxCommits
== -1 || authorCommits
> nLeastActiveMaxCommits
)
937 nLeastActiveMaxCommits
= authorCommits
;
938 if (nLeastActiveMinCommits
== -1 || authorCommits
< nLeastActiveMinCommits
)
939 nLeastActiveMinCommits
= authorCommits
;
942 if (nMostActiveMaxCommits
== -1) nMostActiveMaxCommits
= 0;
943 if (nMostActiveMinCommits
== -1) nMostActiveMinCommits
= 0;
944 if (nLeastActiveMaxCommits
== -1) nLeastActiveMaxCommits
= 0;
945 if (nLeastActiveMinCommits
== -1) nLeastActiveMinCommits
= 0;
947 int nWeeks
= m_lastInterval
-m_firstInterval
;
950 // Adjust the labels with the unit type (week, month, ...)
952 labelText
.Format(IDS_STATGRAPH_NUMBEROFUNIT
, GetUnitString());
953 SetDlgItemText(IDC_NUMWEEK
, labelText
);
954 labelText
.Format(IDS_STATGRAPH_COMMITSBYUNIT
, GetUnitString());
955 SetDlgItemText(IDC_COMMITSEACHWEEK
, labelText
);
956 labelText
.Format(IDS_STATGRAPH_FILECHANGESBYUNIT
, GetUnitString());
957 SetDlgItemText(IDC_FILECHANGESEACHWEEK
, labelText
);
958 // We have now all data we want and we can fill in the labels...
960 number
.Format(_T("%ld"), nWeeks
);
961 SetDlgItemText(IDC_NUMWEEKVALUE
, number
);
962 number
.Format(_T("%ld"), nAuthors
);
963 SetDlgItemText(IDC_NUMAUTHORVALUE
, number
);
964 number
.Format(_T("%ld"), m_nTotalCommits
);
965 SetDlgItemText(IDC_NUMCOMMITSVALUE
, number
);
966 number
.Format(_T("%ld"), m_nTotalFileChanges
);
967 //SetDlgItemText(IDC_NUMFILECHANGESVALUE, number);
969 number
.Format(_T("%ld"), m_parAuthors
->GetCount() / nWeeks
);
970 SetDlgItemText(IDC_COMMITSEACHWEEKAVG
, number
);
971 number
.Format(_T("%ld"), nCommitsMax
);
972 SetDlgItemText(IDC_COMMITSEACHWEEKMAX
, number
);
973 number
.Format(_T("%ld"), nCommitsMin
);
974 SetDlgItemText(IDC_COMMITSEACHWEEKMIN
, number
);
976 number
.Format(_T("%ld"), m_nTotalFileChanges
/ nWeeks
);
977 //SetDlgItemText(IDC_FILECHANGESEACHWEEKAVG, number);
978 number
.Format(_T("%ld"), nFileChangesMax
);
979 //SetDlgItemText(IDC_FILECHANGESEACHWEEKMAX, number);
980 number
.Format(_T("%ld"), nFileChangesMin
);
981 //SetDlgItemText(IDC_FILECHANGESEACHWEEKMIN, number);
985 SetDlgItemText(IDC_MOSTACTIVEAUTHORNAME
, _T(""));
986 SetDlgItemText(IDC_MOSTACTIVEAUTHORAVG
, _T("0"));
987 SetDlgItemText(IDC_MOSTACTIVEAUTHORMAX
, _T("0"));
988 SetDlgItemText(IDC_MOSTACTIVEAUTHORMIN
, _T("0"));
989 SetDlgItemText(IDC_LEASTACTIVEAUTHORNAME
, _T(""));
990 SetDlgItemText(IDC_LEASTACTIVEAUTHORAVG
, _T("0"));
991 SetDlgItemText(IDC_LEASTACTIVEAUTHORMAX
, _T("0"));
992 SetDlgItemText(IDC_LEASTACTIVEAUTHORMIN
, _T("0"));
996 SetDlgItemText(IDC_MOSTACTIVEAUTHORNAME
, mostActiveAuthor
.c_str());
997 number
.Format(_T("%ld"), m_commitsPerAuthor
[mostActiveAuthor
] / nWeeks
);
998 SetDlgItemText(IDC_MOSTACTIVEAUTHORAVG
, number
);
999 number
.Format(_T("%ld"), nMostActiveMaxCommits
);
1000 SetDlgItemText(IDC_MOSTACTIVEAUTHORMAX
, number
);
1001 number
.Format(_T("%ld"), nMostActiveMinCommits
);
1002 SetDlgItemText(IDC_MOSTACTIVEAUTHORMIN
, number
);
1004 SetDlgItemText(IDC_LEASTACTIVEAUTHORNAME
, leastActiveAuthor
.c_str());
1005 number
.Format(_T("%ld"), m_commitsPerAuthor
[leastActiveAuthor
] / nWeeks
);
1006 SetDlgItemText(IDC_LEASTACTIVEAUTHORAVG
, number
);
1007 number
.Format(_T("%ld"), nLeastActiveMaxCommits
);
1008 SetDlgItemText(IDC_LEASTACTIVEAUTHORMAX
, number
);
1009 number
.Format(_T("%ld"), nLeastActiveMinCommits
);
1010 SetDlgItemText(IDC_LEASTACTIVEAUTHORMIN
, number
);
1014 int CStatGraphDlg::RollPercentageOfAuthorship(double it
)
1015 { return (int)it
+ (it
- (int)it
>= 0.5);}
1017 void CStatGraphDlg::OnCbnSelchangeGraphcombo()
1021 Metrics useMetric
= (Metrics
) m_cGraphType
.GetItemData(m_cGraphType
.GetCurSel());
1027 m_btnGraphLine
.EnableWindow(TRUE
);
1028 m_btnGraphLineStacked
.EnableWindow(TRUE
);
1029 m_btnGraphPie
.EnableWindow(TRUE
);
1030 m_GraphType
= MyGraph::Line
;
1033 case PercentageOfAuthorship
:
1034 case CommitsByAuthor
:
1036 m_btnGraphLine
.EnableWindow(FALSE
);
1037 m_btnGraphLineStacked
.EnableWindow(FALSE
);
1038 m_btnGraphPie
.EnableWindow(TRUE
);
1039 m_GraphType
= MyGraph::Bar
;
1047 int CStatGraphDlg::GetUnitCount()
1054 return (m_nWeeks
/4)+1;
1056 return (m_nWeeks
/13)+1; // quarters
1057 return (m_nWeeks
/52)+1;
1060 int CStatGraphDlg::GetUnit(const CTime
& time
)
1063 return time
.GetMonth()*100 + time
.GetDay(); // month*100+day as the unit
1065 return GetCalendarWeek(time
);
1067 return time
.GetMonth();
1069 return ((time
.GetMonth()-1)/3)+1; // quarters
1070 return time
.GetYear();
1073 CStatGraphDlg::UnitType
CStatGraphDlg::GetUnitType()
1086 CString
CStatGraphDlg::GetUnitString()
1089 return CString(MAKEINTRESOURCE(IDS_STATGRAPH_COMMITSBYDATEXDAY
));
1091 return CString(MAKEINTRESOURCE(IDS_STATGRAPH_COMMITSBYDATEXWEEK
));
1093 return CString(MAKEINTRESOURCE(IDS_STATGRAPH_COMMITSBYDATEXMONTH
));
1095 return CString(MAKEINTRESOURCE(IDS_STATGRAPH_COMMITSBYDATEXQUARTER
));
1096 return CString(MAKEINTRESOURCE(IDS_STATGRAPH_COMMITSBYDATEXYEAR
));
1099 CString
CStatGraphDlg::GetUnitLabel(int unit
, CTime
&lasttime
)
1102 switch (GetUnitType())
1106 // month*100+day as the unit
1107 int day
= unit
% 100;
1108 int month
= unit
/ 100;
1109 switch (m_langOrder
)
1111 case 0: // month day year
1112 temp
.Format(_T("%d/%d/%.2d"), month
, day
, lasttime
.GetYear()%100);
1114 case 1: // day month year
1116 temp
.Format(_T("%d/%d/%.2d"), day
, month
, lasttime
.GetYear()%100);
1118 case 2: // year month day
1119 temp
.Format(_T("%.2d/%d/%d"), lasttime
.GetYear()%100, month
, day
);
1126 int year
= lasttime
.GetYear();
1127 if ((unit
== 1)&&(lasttime
.GetMonth() == 12))
1130 switch (m_langOrder
)
1132 case 0: // month day year
1133 case 1: // day month year
1135 temp
.Format(_T("%d/%.2d"), unit
, year
%100);
1137 case 2: // year month day
1138 temp
.Format(_T("%.2d/%d"), year
%100, unit
);
1144 switch (m_langOrder
)
1146 case 0: // month day year
1147 case 1: // day month year
1149 temp
.Format(_T("%d/%.2d"), unit
, lasttime
.GetYear()%100);
1151 case 2: // year month day
1152 temp
.Format(_T("%.2d/%d"), lasttime
.GetYear()%100, unit
);
1157 switch (m_langOrder
)
1159 case 0: // month day year
1160 case 1: // day month year
1162 temp
.Format(_T("%d/%.2d"), unit
, lasttime
.GetYear()%100);
1164 case 2: // year month day
1165 temp
.Format(_T("%.2d/%d"), lasttime
.GetYear()%100, unit
);
1170 temp
.Format(_T("%d"), unit
);
1176 void CStatGraphDlg::OnHScroll(UINT nSBCode
, UINT nPos
, CScrollBar
* pScrollBar
)
1178 if (nSBCode
== TB_THUMBTRACK
)
1179 return CDialog::OnHScroll(nSBCode
, nPos
, pScrollBar
);
1181 ShowSelectStat((Metrics
) m_cGraphType
.GetItemData(m_cGraphType
.GetCurSel()));
1182 CDialog::OnHScroll(nSBCode
, nPos
, pScrollBar
);
1185 void CStatGraphDlg::OnNeedText(NMHDR
*pnmh
, LRESULT
* /*pResult*/)
1187 TOOLTIPTEXT
* pttt
= (TOOLTIPTEXT
*) pnmh
;
1188 if (pttt
->hdr
.idFrom
== (UINT_PTR
) m_Skipper
.GetSafeHwnd())
1190 size_t included_authors_count
= m_Skipper
.GetPos();
1191 // if we only leave out one author, still include him with his name
1192 if (included_authors_count
+ 1 == m_authorNames
.size())
1193 ++included_authors_count
;
1195 // find the minimum number of commits that the shown authors have
1196 int min_commits
= 0;
1197 included_authors_count
= min(included_authors_count
, m_authorNames
.size());
1198 std::list
<tstring
>::iterator author_it
= m_authorNames
.begin();
1199 advance(author_it
, included_authors_count
);
1200 if (author_it
!= m_authorNames
.begin())
1201 min_commits
= m_commitsPerAuthor
[ *(--author_it
) ];
1204 int percentage
= int(min_commits
*100.0/(m_nTotalCommits
? m_nTotalCommits
: 1));
1205 string
.Format(IDS_STATGRAPH_AUTHORSLIDER_TT
, m_Skipper
.GetPos(), min_commits
, percentage
);
1206 ::lstrcpy(pttt
->szText
, (LPCTSTR
) string
);
1210 void CStatGraphDlg::AuthorsCaseSensitiveChanged()
1212 UpdateData(); // update checkbox state
1213 GatherData(); // first regenerate the statistics data
1214 RedrawGraph(); // then update the current statistics page
1217 void CStatGraphDlg::SortModeChanged()
1219 UpdateData(); // update checkbox state
1220 RedrawGraph(); // then update the current statistics page
1223 void CStatGraphDlg::ClearGraph()
1226 for (int j
=0; j
<m_graphDataArray
.GetCount(); ++j
)
1227 delete ((MyGraphSeries
*)m_graphDataArray
.GetAt(j
));
1228 m_graphDataArray
.RemoveAll();
1231 void CStatGraphDlg::RedrawGraph()
1233 EnableDisableMenu();
1234 m_btnGraphBar
.SetState(BST_UNCHECKED
);
1235 m_btnGraphBarStacked
.SetState(BST_UNCHECKED
);
1236 m_btnGraphLine
.SetState(BST_UNCHECKED
);
1237 m_btnGraphLineStacked
.SetState(BST_UNCHECKED
);
1238 m_btnGraphPie
.SetState(BST_UNCHECKED
);
1240 if ((m_GraphType
== MyGraph::Bar
)&&(m_bStacked
))
1242 m_btnGraphBarStacked
.SetState(BST_CHECKED
);
1244 if ((m_GraphType
== MyGraph::Bar
)&&(!m_bStacked
))
1246 m_btnGraphBar
.SetState(BST_CHECKED
);
1248 if ((m_GraphType
== MyGraph::Line
)&&(m_bStacked
))
1250 m_btnGraphLineStacked
.SetState(BST_CHECKED
);
1252 if ((m_GraphType
== MyGraph::Line
)&&(!m_bStacked
))
1254 m_btnGraphLine
.SetState(BST_CHECKED
);
1256 if (m_GraphType
== MyGraph::PieChart
)
1258 m_btnGraphPie
.SetState(BST_CHECKED
);
1262 ShowSelectStat((Metrics
) m_cGraphType
.GetItemData(m_cGraphType
.GetCurSel()), true);
1264 void CStatGraphDlg::OnBnClickedGraphbarbutton()
1266 m_GraphType
= MyGraph::Bar
;
1271 void CStatGraphDlg::OnBnClickedGraphbarstackedbutton()
1273 m_GraphType
= MyGraph::Bar
;
1278 void CStatGraphDlg::OnBnClickedGraphlinebutton()
1280 m_GraphType
= MyGraph::Line
;
1285 void CStatGraphDlg::OnBnClickedGraphlinestackedbutton()
1287 m_GraphType
= MyGraph::Line
;
1292 void CStatGraphDlg::OnBnClickedGraphpiebutton()
1294 m_GraphType
= MyGraph::PieChart
;
1299 BOOL
CStatGraphDlg::PreTranslateMessage(MSG
* pMsg
)
1301 if (NULL
!= m_pToolTip
)
1302 m_pToolTip
->RelayEvent(pMsg
);
1304 return CStandAloneDialogTmpl
<CResizableDialog
>::PreTranslateMessage(pMsg
);
1307 void CStatGraphDlg::EnableDisableMenu()
1309 UINT nEnable
= MF_BYCOMMAND
;
1311 Metrics SelectMetric
= (Metrics
) m_cGraphType
.GetItemData(m_cGraphType
.GetCurSel());
1313 nEnable
|= (SelectMetric
> TextStatStart
&& SelectMetric
< TextStatEnd
)
1314 ? (MF_DISABLED
| MF_GRAYED
) : MF_ENABLED
;
1316 GetMenu()->EnableMenuItem(ID_FILE_SAVESTATGRAPHAS
, nEnable
);
1319 void CStatGraphDlg::OnFileSavestatgraphas()
1322 int filterindex
= 0;
1323 if (CAppUtils::FileOpenSave(tempfile
, &filterindex
, IDS_REVGRAPH_SAVEPIC
, IDS_PICTUREFILEFILTER
, false, m_hWnd
))
1325 // if the user doesn't specify a file extension, default to
1326 // wmf and add that extension to the filename. But only if the
1327 // user chose the 'pictures' filter. The filename isn't changed
1328 // if the 'All files' filter was chosen.
1330 int dotPos
= tempfile
.ReverseFind('.');
1331 int slashPos
= tempfile
.ReverseFind('\\');
1332 if (dotPos
> slashPos
)
1333 extension
= tempfile
.Mid(dotPos
);
1334 if ((filterindex
== 1)&&(extension
.IsEmpty()))
1336 extension
= _T(".wmf");
1337 tempfile
+= extension
;
1339 SaveGraph(tempfile
);
1343 void CStatGraphDlg::SaveGraph(CString sFilename
)
1345 CString extension
= CPathUtils::GetFileExtFromPath(sFilename
);
1346 if (extension
.CompareNoCase(_T(".wmf"))==0)
1348 // save the graph as an enhanced meta file
1349 CMyMetaFileDC wmfDC
;
1350 wmfDC
.CreateEnhanced(NULL
, sFilename
, NULL
, _T("TortoiseGit\0Statistics\0\0"));
1351 wmfDC
.SetAttribDC(GetDC()->GetSafeHdc());
1353 m_graph
.DrawGraph(wmfDC
);
1354 HENHMETAFILE hemf
= wmfDC
.CloseEnhanced();
1355 DeleteEnhMetaFile(hemf
);
1359 // save the graph as a pixel picture instead of a vector picture
1360 // create dc to paint on
1363 CWindowDC
ddc(this);
1365 if (!dc
.CreateCompatibleDC(&ddc
))
1371 GetDlgItem(IDC_GRAPH
)->GetClientRect(&rect
);
1372 HBITMAP hbm
= ::CreateCompatibleBitmap(ddc
.m_hDC
, rect
.Width(), rect
.Height());
1378 HBITMAP oldbm
= (HBITMAP
)dc
.SelectObject(hbm
);
1379 // paint the whole graph
1381 m_graph
.DrawGraph(dc
);
1382 // now use GDI+ to save the picture
1384 GdiplusStartupInput gdiplusStartupInput
;
1385 ULONG_PTR gdiplusToken
;
1386 CString sErrormessage
;
1387 if (GdiplusStartup( &gdiplusToken
, &gdiplusStartupInput
, NULL
)==Ok
)
1390 Bitmap
bitmap(hbm
, NULL
);
1391 if (bitmap
.GetLastStatus()==Ok
)
1393 // Get the CLSID of the encoder.
1395 if (CPathUtils::GetFileExtFromPath(sFilename
).CompareNoCase(_T(".png"))==0)
1396 ret
= GetEncoderClsid(L
"image/png", &encoderClsid
);
1397 else if (CPathUtils::GetFileExtFromPath(sFilename
).CompareNoCase(_T(".jpg"))==0)
1398 ret
= GetEncoderClsid(L
"image/jpeg", &encoderClsid
);
1399 else if (CPathUtils::GetFileExtFromPath(sFilename
).CompareNoCase(_T(".jpeg"))==0)
1400 ret
= GetEncoderClsid(L
"image/jpeg", &encoderClsid
);
1401 else if (CPathUtils::GetFileExtFromPath(sFilename
).CompareNoCase(_T(".bmp"))==0)
1402 ret
= GetEncoderClsid(L
"image/bmp", &encoderClsid
);
1403 else if (CPathUtils::GetFileExtFromPath(sFilename
).CompareNoCase(_T(".gif"))==0)
1404 ret
= GetEncoderClsid(L
"image/gif", &encoderClsid
);
1407 sFilename
+= _T(".jpg");
1408 ret
= GetEncoderClsid(L
"image/jpeg", &encoderClsid
);
1412 CStringW tfile
= CStringW(sFilename
);
1413 bitmap
.Save(tfile
, &encoderClsid
, NULL
);
1417 sErrormessage
.Format(IDS_REVGRAPH_ERR_NOENCODER
, CPathUtils::GetFileExtFromPath(sFilename
));
1422 sErrormessage
.LoadString(IDS_REVGRAPH_ERR_NOBITMAP
);
1425 GdiplusShutdown(gdiplusToken
);
1429 sErrormessage
.LoadString(IDS_REVGRAPH_ERR_GDIINIT
);
1431 dc
.SelectObject(oldbm
);
1433 if (!sErrormessage
.IsEmpty())
1435 ::MessageBox(m_hWnd
, sErrormessage
, _T("TortoiseGit"), MB_ICONERROR
);
1438 catch (CException
* pE
)
1440 TCHAR szErrorMsg
[2048];
1441 pE
->GetErrorMessage(szErrorMsg
, 2048);
1443 ::MessageBox(m_hWnd
, szErrorMsg
, _T("TortoiseGit"), MB_ICONERROR
);
1448 int CStatGraphDlg::GetEncoderClsid(const WCHAR
* format
, CLSID
* pClsid
)
1450 UINT num
= 0; // number of image encoders
1451 UINT size
= 0; // size of the image encoder array in bytes
1453 ImageCodecInfo
* pImageCodecInfo
= NULL
;
1455 if (GetImageEncodersSize(&num
, &size
)!=Ok
)
1458 return -1; // Failure
1460 pImageCodecInfo
= (ImageCodecInfo
*)(malloc(size
));
1461 if (pImageCodecInfo
== NULL
)
1462 return -1; // Failure
1464 if (GetImageEncoders(num
, size
, pImageCodecInfo
)==Ok
)
1466 for (UINT j
= 0; j
< num
; ++j
)
1468 if (wcscmp(pImageCodecInfo
[j
].MimeType
, format
) == 0)
1470 *pClsid
= pImageCodecInfo
[j
].Clsid
;
1471 free(pImageCodecInfo
);
1472 return j
; // Success
1476 free (pImageCodecInfo
);
1477 return -1; // Failure
1480 void CStatGraphDlg::StoreCurrentGraphType()
1483 DWORD graphtype
= static_cast<DWORD
>(m_cGraphType
.GetItemData(m_cGraphType
.GetCurSel()));
1484 // encode the current chart type
1485 DWORD statspage
= graphtype
*10;
1486 if ((m_GraphType
== MyGraph::Bar
)&&(m_bStacked
))
1490 if ((m_GraphType
== MyGraph::Bar
)&&(!m_bStacked
))
1494 if ((m_GraphType
== MyGraph::Line
)&&(m_bStacked
))
1498 if ((m_GraphType
== MyGraph::Line
)&&(!m_bStacked
))
1502 if (m_GraphType
== MyGraph::PieChart
)
1507 // store current chart type in registry
1508 CRegDWORD lastStatsPage
= CRegDWORD(_T("Software\\TortoiseGit\\LastViewedStatsPage"), 0);
1509 lastStatsPage
= statspage
;
1511 CRegDWORD regAuthors
= CRegDWORD(_T("Software\\TortoiseGit\\StatAuthorsCaseSensitive"));
1512 regAuthors
= m_bAuthorsCaseSensitive
;
1514 CRegDWORD regSort
= CRegDWORD(_T("Software\\TortoiseGit\\StatSortByCommitCount"));
1515 regSort
= m_bSortByCommitCount
;
1518 void CStatGraphDlg::ShowErrorMessage()
1520 CFormatMessageWrapper errorDetails
;
1522 MessageBox( errorDetails
, _T("Error"), MB_OK
| MB_ICONINFORMATION
);
1525 void CStatGraphDlg::ShowSelectStat(Metrics SelectedMetric
, bool reloadSkiper
/* = false */)
1527 switch (SelectedMetric
)
1530 LoadListOfAuthors(m_commitsPerAuthor
, reloadSkiper
);
1534 LoadListOfAuthors(m_commitsPerAuthor
, reloadSkiper
);
1535 ShowCommitsByDate();
1537 case CommitsByAuthor
:
1538 LoadListOfAuthors(m_commitsPerAuthor
, reloadSkiper
);
1539 ShowCommitsByAuthor();
1541 case PercentageOfAuthorship
:
1542 LoadListOfAuthors(m_PercentageOfAuthorship
, reloadSkiper
, true);
1543 ShowPercentageOfAuthorship();
1550 double CStatGraphDlg::CoeffContribution(int distFromEnd
) { return distFromEnd
? 1.0 / m_CoeffAuthorShip
* distFromEnd
: 1;}
1553 template <class MAP
>
1554 void CStatGraphDlg::DrawOthers(const std::list
<tstring
> &others
, MyGraphSeries
*graphData
, MAP
&map
)
1557 for (std::list
<tstring
>::const_iterator it
= others
.begin(); it
!= others
.end(); ++it
)
1559 nCommits
+= RollPercentageOfAuthorship(map
[*it
]);
1563 temp
.Format(_T(" (%ld)"), others
.size());
1565 CString
sOthers(MAKEINTRESOURCE(IDS_STATGRAPH_OTHERGROUP
));
1567 int group
= m_graph
.AppendGroup(sOthers
);
1568 graphData
->SetData(group
, (int)nCommits
);
1572 template <class MAP
>
1573 void CStatGraphDlg::LoadListOfAuthors (MAP
&map
, bool reloadSkiper
/*= false*/, bool compare
/*= false*/)
1575 m_authorNames
.clear();
1578 for (MAP::const_iterator it
= map
.begin(); it
!= map
.end(); ++it
)
1580 if ((compare
&& RollPercentageOfAuthorship(map
[it
->first
]) != 0) || !compare
)
1581 m_authorNames
.push_back(it
->first
);
1585 // Sort the list of authors based on commit count
1586 m_authorNames
.sort(MoreCommitsThan
< MAP::referent_type
>(map
));
1589 SetSkipper(reloadSkiper
);