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