1 // TortoiseSVN - a Windows shell extension for easy version control
3 // Copyright (C) 2003-2008 - TortoiseSVN
5 // This program is free software; you can redistribute it and/or
6 // modify it under the terms of the GNU General Public License
7 // as published by the Free Software Foundation; either version 2
8 // of the License, or (at your option) any later version.
10 // This program is distributed in the hope that it will be useful,
11 // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 // GNU General Public License for more details.
15 // You should have received a copy of the GNU General Public License
16 // along with this program; if not, write to the Free Software Foundation,
17 // 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
20 #include "TortoiseProc.h"
21 #include "StatGraphDlg.h"
24 #include "StringUtils.h"
25 #include "PathUtils.h"
26 #include "MessageBox.h"
28 #include "CommonResource.h"
34 using namespace Gdiplus
;
36 // BinaryPredicate for comparing authors based on their commit count
37 class MoreCommitsThan
: public std::binary_function
<stdstring
, stdstring
, bool> {
39 MoreCommitsThan(std::map
<stdstring
, LONG
> * author_commits
) : m_authorCommits(author_commits
) {}
41 bool operator()(const stdstring
& lhs
, const stdstring
& rhs
) {
42 return (*m_authorCommits
)[lhs
] > (*m_authorCommits
)[rhs
];
46 std::map
<stdstring
, LONG
> * m_authorCommits
;
50 IMPLEMENT_DYNAMIC(CStatGraphDlg
, CResizableStandAloneDialog
)
51 CStatGraphDlg::CStatGraphDlg(CWnd
* pParent
/*=NULL*/)
52 : CResizableStandAloneDialog(CStatGraphDlg::IDD
, pParent
)
54 , m_GraphType(MyGraph::Bar
)
55 , m_bAuthorsCaseSensitive(TRUE
)
56 , m_bSortByCommitCount(TRUE
)
60 m_parFileChanges
= NULL
;
65 CStatGraphDlg::~CStatGraphDlg()
68 DestroyIcon(m_hGraphBarIcon
);
69 DestroyIcon(m_hGraphBarStackedIcon
);
70 DestroyIcon(m_hGraphLineIcon
);
71 DestroyIcon(m_hGraphLineStackedIcon
);
72 DestroyIcon(m_hGraphPieIcon
);
76 void CStatGraphDlg::OnOK() {
77 StoreCurrentGraphType();
81 void CStatGraphDlg::OnCancel() {
82 StoreCurrentGraphType();
86 void CStatGraphDlg::DoDataExchange(CDataExchange
* pDX
)
88 CResizableStandAloneDialog::DoDataExchange(pDX
);
89 DDX_Control(pDX
, IDC_GRAPH
, m_graph
);
90 DDX_Control(pDX
, IDC_GRAPHCOMBO
, m_cGraphType
);
91 DDX_Control(pDX
, IDC_SKIPPER
, m_Skipper
);
92 DDX_Check(pDX
, IDC_AUTHORSCASESENSITIVE
, m_bAuthorsCaseSensitive
);
93 DDX_Check(pDX
, IDC_SORTBYCOMMITCOUNT
, m_bSortByCommitCount
);
94 DDX_Control(pDX
, IDC_GRAPHBARBUTTON
, m_btnGraphBar
);
95 DDX_Control(pDX
, IDC_GRAPHBARSTACKEDBUTTON
, m_btnGraphBarStacked
);
96 DDX_Control(pDX
, IDC_GRAPHLINEBUTTON
, m_btnGraphLine
);
97 DDX_Control(pDX
, IDC_GRAPHLINESTACKEDBUTTON
, m_btnGraphLineStacked
);
98 DDX_Control(pDX
, IDC_GRAPHPIEBUTTON
, m_btnGraphPie
);
102 BEGIN_MESSAGE_MAP(CStatGraphDlg
, CResizableStandAloneDialog
)
103 ON_CBN_SELCHANGE(IDC_GRAPHCOMBO
, OnCbnSelchangeGraphcombo
)
105 ON_NOTIFY(TTN_NEEDTEXT
, NULL
, OnNeedText
)
106 ON_BN_CLICKED(IDC_AUTHORSCASESENSITIVE
, &CStatGraphDlg::AuthorsCaseSensitiveChanged
)
107 ON_BN_CLICKED(IDC_SORTBYCOMMITCOUNT
, &CStatGraphDlg::SortModeChanged
)
108 ON_BN_CLICKED(IDC_GRAPHBARBUTTON
, &CStatGraphDlg::OnBnClickedGraphbarbutton
)
109 ON_BN_CLICKED(IDC_GRAPHBARSTACKEDBUTTON
, &CStatGraphDlg::OnBnClickedGraphbarstackedbutton
)
110 ON_BN_CLICKED(IDC_GRAPHLINEBUTTON
, &CStatGraphDlg::OnBnClickedGraphlinebutton
)
111 ON_BN_CLICKED(IDC_GRAPHLINESTACKEDBUTTON
, &CStatGraphDlg::OnBnClickedGraphlinestackedbutton
)
112 ON_BN_CLICKED(IDC_GRAPHPIEBUTTON
, &CStatGraphDlg::OnBnClickedGraphpiebutton
)
113 ON_COMMAND(ID_FILE_SAVESTATGRAPHAS
, &CStatGraphDlg::OnFileSavestatgraphas
)
116 BOOL
CStatGraphDlg::OnInitDialog()
118 CResizableStandAloneDialog::OnInitDialog();
120 m_pToolTip
= new CToolTipCtrl
;
121 if (m_pToolTip
->Create(this))
123 m_pToolTip
->AddTool(&m_btnGraphPie
, IDS_STATGRAPH_PIEBUTTON_TT
);
124 m_pToolTip
->AddTool(&m_btnGraphLineStacked
, IDS_STATGRAPH_LINESTACKEDBUTTON_TT
);
125 m_pToolTip
->AddTool(&m_btnGraphLine
, IDS_STATGRAPH_LINEBUTTON_TT
);
126 m_pToolTip
->AddTool(&m_btnGraphBarStacked
, IDS_STATGRAPH_BARSTACKEDBUTTON_TT
);
127 m_pToolTip
->AddTool(&m_btnGraphBar
, IDS_STATGRAPH_BARBUTTON_TT
);
129 m_pToolTip
->Activate(TRUE
);
132 m_bAuthorsCaseSensitive
= DWORD(CRegDWORD(_T("Software\\TortoiseGit\\StatAuthorsCaseSensitive")));
133 m_bSortByCommitCount
= DWORD(CRegDWORD(_T("Software\\TortoiseGit\\StatSortByCommitCount")));
138 temp
.LoadString(IDS_STATGRAPH_STATS
);
139 sel
= m_cGraphType
.AddString(temp
);
140 m_cGraphType
.SetItemData(sel
, 1);
141 m_cGraphType
.SetCurSel(sel
);
142 temp
.LoadString(IDS_STATGRAPH_COMMITSBYDATE
);
143 sel
= m_cGraphType
.AddString(temp
);
144 m_cGraphType
.SetItemData(sel
, 2);
145 temp
.LoadString(IDS_STATGRAPH_COMMITSBYAUTHOR
);
146 sel
= m_cGraphType
.AddString(temp
);
147 m_cGraphType
.SetItemData(sel
, 3);
149 // set the dialog title to "Statistics - path/to/whatever/we/show/the/statistics/for"
151 GetWindowText(sTitle
);
152 if(m_path
.IsDirectory())
153 SetWindowText(sTitle
+ _T(" - ") + m_path
.GetWinPathString());
155 SetWindowText(sTitle
+ _T(" - ") + m_path
.GetFilename());
157 m_hGraphBarIcon
= (HICON
)LoadImage(AfxGetResourceHandle(), MAKEINTRESOURCE(IDI_GRAPHBAR
), IMAGE_ICON
, 16, 16, LR_DEFAULTCOLOR
);
158 m_hGraphBarStackedIcon
= (HICON
)LoadImage(AfxGetResourceHandle(), MAKEINTRESOURCE(IDI_GRAPHBARSTACKED
), IMAGE_ICON
, 16, 16, LR_DEFAULTCOLOR
);
159 m_hGraphLineIcon
= (HICON
)LoadImage(AfxGetResourceHandle(), MAKEINTRESOURCE(IDI_GRAPHLINE
), IMAGE_ICON
, 16, 16, LR_DEFAULTCOLOR
);
160 m_hGraphLineStackedIcon
= (HICON
)LoadImage(AfxGetResourceHandle(), MAKEINTRESOURCE(IDI_GRAPHLINESTACKED
), IMAGE_ICON
, 16, 16, LR_DEFAULTCOLOR
);
161 m_hGraphPieIcon
= (HICON
)LoadImage(AfxGetResourceHandle(), MAKEINTRESOURCE(IDI_GRAPHPIE
), IMAGE_ICON
, 16, 16, LR_DEFAULTCOLOR
);
163 m_btnGraphBar
.SetIcon(m_hGraphBarIcon
);
164 m_btnGraphBar
.SetButtonStyle(WS_CHILD
|WS_VISIBLE
|BS_PUSHBUTTON
);
165 m_btnGraphBarStacked
.SetIcon(m_hGraphBarStackedIcon
);
166 m_btnGraphBarStacked
.SetButtonStyle(WS_CHILD
|WS_VISIBLE
|BS_PUSHBUTTON
);
167 m_btnGraphLine
.SetIcon(m_hGraphLineIcon
);
168 m_btnGraphLine
.SetButtonStyle(WS_CHILD
|WS_VISIBLE
|BS_PUSHBUTTON
);
169 m_btnGraphLineStacked
.SetIcon(m_hGraphLineStackedIcon
);
170 m_btnGraphLineStacked
.SetButtonStyle(WS_CHILD
|WS_VISIBLE
|BS_PUSHBUTTON
);
171 m_btnGraphPie
.SetIcon(m_hGraphPieIcon
);
172 m_btnGraphPie
.SetButtonStyle(WS_CHILD
|WS_VISIBLE
|BS_PUSHBUTTON
);
174 AddAnchor(IDC_GRAPHTYPELABEL
, TOP_LEFT
);
175 AddAnchor(IDC_GRAPH
, TOP_LEFT
, BOTTOM_RIGHT
);
176 AddAnchor(IDC_GRAPHCOMBO
, TOP_LEFT
, TOP_RIGHT
);
178 AddAnchor(IDC_NUMWEEK
, TOP_LEFT
);
179 AddAnchor(IDC_NUMWEEKVALUE
, TOP_RIGHT
);
180 AddAnchor(IDC_NUMAUTHOR
, TOP_LEFT
);
181 AddAnchor(IDC_NUMAUTHORVALUE
, TOP_RIGHT
);
182 AddAnchor(IDC_NUMCOMMITS
, TOP_LEFT
);
183 AddAnchor(IDC_NUMCOMMITSVALUE
, TOP_RIGHT
);
184 AddAnchor(IDC_NUMFILECHANGES
, TOP_LEFT
);
185 AddAnchor(IDC_NUMFILECHANGESVALUE
, TOP_RIGHT
);
187 AddAnchor(IDC_AVG
, TOP_RIGHT
);
188 AddAnchor(IDC_MIN
, TOP_RIGHT
);
189 AddAnchor(IDC_MAX
, TOP_RIGHT
);
190 AddAnchor(IDC_COMMITSEACHWEEK
, TOP_LEFT
);
191 AddAnchor(IDC_MOSTACTIVEAUTHOR
, TOP_LEFT
);
192 AddAnchor(IDC_LEASTACTIVEAUTHOR
, TOP_LEFT
);
193 AddAnchor(IDC_MOSTACTIVEAUTHORNAME
, TOP_LEFT
, TOP_RIGHT
);
194 AddAnchor(IDC_LEASTACTIVEAUTHORNAME
, TOP_LEFT
, TOP_RIGHT
);
195 AddAnchor(IDC_FILECHANGESEACHWEEK
, TOP_LEFT
);
196 AddAnchor(IDC_COMMITSEACHWEEKAVG
, TOP_RIGHT
);
197 AddAnchor(IDC_COMMITSEACHWEEKMIN
, TOP_RIGHT
);
198 AddAnchor(IDC_COMMITSEACHWEEKMAX
, TOP_RIGHT
);
199 AddAnchor(IDC_MOSTACTIVEAUTHORAVG
, TOP_RIGHT
);
200 AddAnchor(IDC_MOSTACTIVEAUTHORMIN
, TOP_RIGHT
);
201 AddAnchor(IDC_MOSTACTIVEAUTHORMAX
, TOP_RIGHT
);
202 AddAnchor(IDC_LEASTACTIVEAUTHORAVG
, TOP_RIGHT
);
203 AddAnchor(IDC_LEASTACTIVEAUTHORMIN
, TOP_RIGHT
);
204 AddAnchor(IDC_LEASTACTIVEAUTHORMAX
, TOP_RIGHT
);
205 AddAnchor(IDC_FILECHANGESEACHWEEKAVG
, TOP_RIGHT
);
206 AddAnchor(IDC_FILECHANGESEACHWEEKMIN
, TOP_RIGHT
);
207 AddAnchor(IDC_FILECHANGESEACHWEEKMAX
, TOP_RIGHT
);
209 AddAnchor(IDC_GRAPHBARBUTTON
, BOTTOM_RIGHT
);
210 AddAnchor(IDC_GRAPHBARSTACKEDBUTTON
, BOTTOM_RIGHT
);
211 AddAnchor(IDC_GRAPHLINEBUTTON
, BOTTOM_RIGHT
);
212 AddAnchor(IDC_GRAPHLINESTACKEDBUTTON
, BOTTOM_RIGHT
);
213 AddAnchor(IDC_GRAPHPIEBUTTON
, BOTTOM_RIGHT
);
215 AddAnchor(IDC_AUTHORSCASESENSITIVE
, BOTTOM_LEFT
);
216 AddAnchor(IDC_SORTBYCOMMITCOUNT
, BOTTOM_LEFT
);
217 AddAnchor(IDC_SKIPPER
, BOTTOM_LEFT
, BOTTOM_RIGHT
);
218 AddAnchor(IDC_SKIPPERLABEL
, BOTTOM_LEFT
);
219 AddAnchor(IDOK
, BOTTOM_RIGHT
);
220 EnableSaveRestore(_T("StatGraphDlg"));
222 // gather statistics data, only needs to be updated when the checkbox with
223 // the case sensitivity of author names is changed
226 // set the min/max values on the skipper
227 int max_authors_count
= max(1, (int)min(m_authorNames
.size(),100) );
228 // TODO : limit the max count based on the resolution, for now we use 100
229 m_Skipper
.SetRange(1, max_authors_count
);
230 m_Skipper
.SetPos( min(max_authors_count
, 10) );
231 m_Skipper
.SetPageSize(5);
233 // we use a stats page encoding here, 0 stands for the statistics dialog
234 CRegDWORD lastStatsPage
= CRegDWORD(_T("Software\\TortoiseGit\\LastViewedStatsPage"), 0);
236 // open last viewed statistics page as first page
237 int graphtype
= lastStatsPage
/ 10;
238 graphtype
= max(1, min(3, graphtype
));
239 m_cGraphType
.SetCurSel(graphtype
-1);
241 OnCbnSelchangeGraphcombo();
243 int statspage
= lastStatsPage
% 10;
246 m_GraphType
= MyGraph::Bar
;
250 m_GraphType
= MyGraph::Bar
;
254 m_GraphType
= MyGraph::Line
;
258 m_GraphType
= MyGraph::Line
;
262 m_GraphType
= MyGraph::PieChart
;
265 default : return TRUE
;
268 LCID m_locale
= MAKELCID((DWORD
)CRegStdWORD(_T("Software\\TortoiseGit\\LanguageID"), MAKELANGID(LANG_NEUTRAL
, SUBLANG_DEFAULT
)), SORT_DEFAULT
);
270 bool bUseSystemLocale
= !!(DWORD
)CRegStdWORD(_T("Software\\TortoiseGit\\UseSystemLocaleForDates"), TRUE
);
271 LCID locale
= bUseSystemLocale
? MAKELCID(MAKELANGID(LANG_NEUTRAL
, SUBLANG_DEFAULT
), SORT_DEFAULT
) : m_locale
;
274 GetLocaleInfo(locale
, LOCALE_IDATE
, &l
, sizeof(TCHAR
));
286 void CStatGraphDlg::ShowLabels(BOOL bShow
)
288 if ((m_parAuthors
==NULL
)||(m_parDates
==NULL
)||(m_parFileChanges
==NULL
))
290 int nCmdShow
= SW_SHOW
;
293 GetDlgItem(IDC_GRAPH
)->ShowWindow(bShow
? SW_HIDE
: SW_SHOW
);
294 GetDlgItem(IDC_NUMWEEK
)->ShowWindow(nCmdShow
);
295 GetDlgItem(IDC_NUMWEEKVALUE
)->ShowWindow(nCmdShow
);
296 GetDlgItem(IDC_NUMAUTHOR
)->ShowWindow(nCmdShow
);
297 GetDlgItem(IDC_NUMAUTHORVALUE
)->ShowWindow(nCmdShow
);
298 GetDlgItem(IDC_NUMCOMMITS
)->ShowWindow(nCmdShow
);
299 GetDlgItem(IDC_NUMCOMMITSVALUE
)->ShowWindow(nCmdShow
);
300 GetDlgItem(IDC_NUMFILECHANGES
)->ShowWindow(nCmdShow
);
301 GetDlgItem(IDC_NUMFILECHANGESVALUE
)->ShowWindow(nCmdShow
);
303 GetDlgItem(IDC_AVG
)->ShowWindow(nCmdShow
);
304 GetDlgItem(IDC_MIN
)->ShowWindow(nCmdShow
);
305 GetDlgItem(IDC_MAX
)->ShowWindow(nCmdShow
);
306 GetDlgItem(IDC_COMMITSEACHWEEK
)->ShowWindow(nCmdShow
);
307 GetDlgItem(IDC_MOSTACTIVEAUTHOR
)->ShowWindow(nCmdShow
);
308 GetDlgItem(IDC_LEASTACTIVEAUTHOR
)->ShowWindow(nCmdShow
);
309 GetDlgItem(IDC_MOSTACTIVEAUTHORNAME
)->ShowWindow(nCmdShow
);
310 GetDlgItem(IDC_LEASTACTIVEAUTHORNAME
)->ShowWindow(nCmdShow
);
311 GetDlgItem(IDC_FILECHANGESEACHWEEK
)->ShowWindow(nCmdShow
);
312 GetDlgItem(IDC_COMMITSEACHWEEKAVG
)->ShowWindow(nCmdShow
);
313 GetDlgItem(IDC_COMMITSEACHWEEKMIN
)->ShowWindow(nCmdShow
);
314 GetDlgItem(IDC_COMMITSEACHWEEKMAX
)->ShowWindow(nCmdShow
);
315 GetDlgItem(IDC_MOSTACTIVEAUTHORAVG
)->ShowWindow(nCmdShow
);
316 GetDlgItem(IDC_MOSTACTIVEAUTHORMIN
)->ShowWindow(nCmdShow
);
317 GetDlgItem(IDC_MOSTACTIVEAUTHORMAX
)->ShowWindow(nCmdShow
);
318 GetDlgItem(IDC_LEASTACTIVEAUTHORAVG
)->ShowWindow(nCmdShow
);
319 GetDlgItem(IDC_LEASTACTIVEAUTHORMIN
)->ShowWindow(nCmdShow
);
320 GetDlgItem(IDC_LEASTACTIVEAUTHORMAX
)->ShowWindow(nCmdShow
);
321 GetDlgItem(IDC_FILECHANGESEACHWEEKAVG
)->ShowWindow(nCmdShow
);
322 GetDlgItem(IDC_FILECHANGESEACHWEEKMIN
)->ShowWindow(nCmdShow
);
323 GetDlgItem(IDC_FILECHANGESEACHWEEKMAX
)->ShowWindow(nCmdShow
);
325 GetDlgItem(IDC_SORTBYCOMMITCOUNT
)->ShowWindow(bShow
? SW_HIDE
: SW_SHOW
);
326 GetDlgItem(IDC_SKIPPER
)->ShowWindow(bShow
? SW_HIDE
: SW_SHOW
);
327 GetDlgItem(IDC_SKIPPERLABEL
)->ShowWindow(bShow
? SW_HIDE
: SW_SHOW
);
328 m_btnGraphBar
.ShowWindow(bShow
? SW_HIDE
: SW_SHOW
);
329 m_btnGraphBarStacked
.ShowWindow(bShow
? SW_HIDE
: SW_SHOW
);
330 m_btnGraphLine
.ShowWindow(bShow
? SW_HIDE
: SW_SHOW
);
331 m_btnGraphLineStacked
.ShowWindow(bShow
? SW_HIDE
: SW_SHOW
);
332 m_btnGraphPie
.ShowWindow(bShow
? SW_HIDE
: SW_SHOW
);
335 void CStatGraphDlg::UpdateWeekCount()
338 if ((!m_parDates
)&&(m_parDates
->GetCount()))
342 // Already updated? No need to do it again.
346 // Determine first and last date in dates array
347 __time64_t min_date
= (__time64_t
)m_parDates
->GetAt(0);
348 __time64_t max_date
= min_date
;
349 INT_PTR count
= m_parDates
->GetCount();
350 for (INT_PTR i
=0; i
<count
; ++i
)
352 __time64_t d
= (__time64_t
)m_parDates
->GetAt(i
);
353 if (d
< min_date
) min_date
= d
;
354 else if (d
> max_date
) max_date
= d
;
357 // Store start date of the interval in the member variable m_minDate
358 m_minDate
= min_date
;
359 m_maxDate
= max_date
;
361 // How many weeks does the time period cover?
363 // Get time difference between start and end date
364 double secs
= _difftime64(max_date
, m_minDate
);
365 // ... a week has 604800 seconds
366 m_nWeeks
= (int)ceil(secs
/ 604800.0);
369 int CStatGraphDlg::GetCalendarWeek(const CTime
& time
)
372 // the calculation of the calendar week is wrong if DST is in effect
373 // and the date to calculate the week for is in DST and within the range
374 // of the DST offset (e.g. one hour).
375 // For example, if DST starts on Sunday march 30 and the date to get the week for
376 // is Monday, march 31, 0:30:00, then the returned week is one week less than
380 // getDSTOffset(const CTime& time)
381 // which returns the DST offset for a given time/date. Then we can use this offset
382 // to correct our GetDays() calculation to get the correct week again
383 // This of course won't work for 'history' dates, because Windows doesn't have
384 // that information (only Vista has such a function: GetTimeZoneInformationForYear() )
387 int iYear
= time
.GetYear();
388 int iFirstDayOfWeek
= 0;
389 int iFirstWeekOfYear
= 0;
391 GetLocaleInfo(LOCALE_USER_DEFAULT
, LOCALE_IFIRSTDAYOFWEEK
, loc
, sizeof(loc
));
392 iFirstDayOfWeek
= int(loc
[0]-'0');
393 GetLocaleInfo(LOCALE_USER_DEFAULT
, LOCALE_IFIRSTWEEKOFYEAR
, loc
, sizeof(loc
));
394 iFirstWeekOfYear
= int(loc
[0]-'0');
395 CTime
dDateFirstJanuary(iYear
,1,1,0,0,0);
396 int iDayOfWeek
= (dDateFirstJanuary
.GetDayOfWeek()+5+iFirstDayOfWeek
)%7;
399 // 0 Week containing 1/1 is the first week of that year.
400 // 1 First full week following 1/1 is the first week of that year.
401 // 2 First week containing at least four days is the first week of that year.
402 switch (iFirstWeekOfYear
)
406 // Week containing 1/1 is the first week of that year.
408 // check if this week reaches into the next year
409 dDateFirstJanuary
= CTime(iYear
+1,1,1,0,0,0);
414 iDayOfWeek
= (time
.GetDayOfWeek()+5+iFirstDayOfWeek
)%7;
416 catch (CException
* e
)
420 CTime dStartOfWeek
= time
-CTimeSpan(iDayOfWeek
,0,0,0);
422 // If this week spans over to 1/1 this is week 1
423 if (dStartOfWeek
+CTimeSpan(6,0,0,0)>=dDateFirstJanuary
)
425 // we are in the last week of the year that spans over 1/1
430 // Get week day of 1/1
431 dDateFirstJanuary
= CTime(iYear
,1,1,0,0,0);
432 iDayOfWeek
= (dDateFirstJanuary
.GetDayOfWeek()+5+iFirstDayOfWeek
)%7;
433 // Just count from 1/1
434 iWeekOfYear
= (int)(((time
-dDateFirstJanuary
).GetDays()+iDayOfWeek
) / 7) + 1;
440 // First full week following 1/1 is the first week of that year.
442 // If the 1.1 is the start of the week everything is ok
443 // else we need the next week is the correct result
445 (int)(((time
-dDateFirstJanuary
).GetDays()+iDayOfWeek
) / 7) +
446 (iDayOfWeek
==0 ? 1:0);
448 // If we are in week 0 we are in the first not full week
449 // calculate from the last year
452 // Special case: we are in the week of 1.1 but 1.1. is not on the
453 // start of week. Calculate based on the last year
454 dDateFirstJanuary
= CTime(iYear
-1,1,1,0,0,0);
456 (dDateFirstJanuary
.GetDayOfWeek()+5+iFirstDayOfWeek
)%7;
457 // and we correct this in the same we we done this before but
458 // the result is now 52 or 53 and not 0
460 (int)(((time
-dDateFirstJanuary
).GetDays()+iDayOfWeek
) / 7) +
461 (iDayOfWeek
<=3 ? 1:0);
467 // First week containing at least four days is the first week of that year.
469 // Each year can start with any day of the week. But our
470 // weeks always start with Monday. So we add the day of week
471 // before calculation of the final week of year.
472 // Rule: is the 1.1 a Mo,Tu,We,Th than the week starts on the 1.1 with
473 // week==1, else a week later, so we add one for all those days if
474 // day is less <=3 Mo,Tu,We,Th. Otherwise 1.1 is in the last week of the
477 (int)(((time
-dDateFirstJanuary
).GetDays()+iDayOfWeek
) / 7) +
478 (iDayOfWeek
<=3 ? 1:0);
483 // special case week 0. We got a day before the 1.1, 2.1 or 3.1, were the
484 // 1.1. is not a Mo, Tu, We, Th. So the week 1 does not start with the 1.1.
485 // So we calculate the week according to the 1.1 of the year before
487 dDateFirstJanuary
= CTime(iYear
-1,1,1,0,0,0);
489 (dDateFirstJanuary
.GetDayOfWeek()+5+iFirstDayOfWeek
)%7;
490 // and we correct this in the same we we done this before but the result
491 // is now 52 or 53 and not 0
493 (int)(((time
-dDateFirstJanuary
).GetDays()+iDayOfWeek
) / 7) +
494 (iDayOfWeek
<=3 ? 1:0);
496 else if (iWeekOfYear
==53)
498 // special case week 53. Either we got the correct week 53 or we just got the
499 // week 1 of the next year. So is the 1.1.(year+1) also a Mo, Tu, We, Th than
500 // we already have the week 1, otherwise week 53 is correct
502 dDateFirstJanuary
= CTime(iYear
+1,1,1,0,0,0);
504 (dDateFirstJanuary
.GetDayOfWeek()+5+iFirstDayOfWeek
)%7;
505 // 1.1. in week 1 or week 53?
506 iWeekOfYear
= iDayOfWeek
<=3 ? 1:53;
518 void CStatGraphDlg::GatherData()
521 if ((m_parAuthors
==NULL
)||(m_parDates
==NULL
)||(m_parFileChanges
==NULL
))
523 m_nTotalCommits
= m_parAuthors
->GetCount();
524 m_nTotalFileChanges
= 0;
526 // Update m_nWeeks and m_minDate
529 // Now create a mapping that holds the information per week.
530 m_commitsPerUnitAndAuthor
.clear();
531 m_filechangesPerUnitAndAuthor
.clear();
532 m_commitsPerAuthor
.clear();
535 __time64_t d
= (__time64_t
)m_parDates
->GetAt(0);
536 int nLastUnit
= GetUnit(d
);
537 // Now loop over all weeks and gather the info
538 for (LONG i
=0; i
<m_nTotalCommits
; ++i
)
540 // Find the interval number
541 __time64_t commitDate
= (__time64_t
)m_parDates
->GetAt(i
);
542 int u
= GetUnit(commitDate
);
546 // Find the authors name
547 CString sAuth
= m_parAuthors
->GetAt(i
);
548 if (!m_bAuthorsCaseSensitive
)
549 sAuth
= sAuth
.MakeLower();
550 stdstring author
= stdstring(sAuth
);
551 // Increase total commit count for this author
552 m_commitsPerAuthor
[author
]++;
553 // Increase the commit count for this author in this week
554 m_commitsPerUnitAndAuthor
[interval
][author
]++;
555 CTime t
= m_parDates
->GetAt(i
);
556 m_unitNames
[interval
] = GetUnitLabel(nLastUnit
, t
);
557 // Increase the file change count for this author in this week
558 int fileChanges
= m_parFileChanges
->GetAt(i
);
559 m_filechangesPerUnitAndAuthor
[interval
][author
] += fileChanges
;
560 m_nTotalFileChanges
+= fileChanges
;
563 // Find first and last interval number.
564 if (!m_commitsPerUnitAndAuthor
.empty())
566 IntervalDataMap::iterator interval_it
= m_commitsPerUnitAndAuthor
.begin();
567 m_firstInterval
= interval_it
->first
;
568 interval_it
= m_commitsPerUnitAndAuthor
.end();
570 m_lastInterval
= interval_it
->first
;
571 // Sanity check - if m_lastInterval is too large it could freeze TSVN and take up all memory!!!
572 assert(m_lastInterval
>= 0 && m_lastInterval
< 10000);
580 // Get a list of authors names
581 m_authorNames
.clear();
582 if (m_commitsPerAuthor
.size())
584 for (std::map
<stdstring
, LONG
>::iterator it
= m_commitsPerAuthor
.begin();
585 it
!= m_commitsPerAuthor
.end(); ++it
)
587 m_authorNames
.push_back(it
->first
);
591 // Sort the list of authors based on commit count
592 m_authorNames
.sort( MoreCommitsThan(&m_commitsPerAuthor
) );
594 // All done, now the statistics pages can retrieve the data and
595 // extract the information to be shown.
598 void CStatGraphDlg::FilterSkippedAuthors(std::list
<stdstring
>& included_authors
,
599 std::list
<stdstring
>& skipped_authors
)
601 included_authors
.clear();
602 skipped_authors
.clear();
604 unsigned int included_authors_count
= m_Skipper
.GetPos();
605 // if we only leave out one author, still include him with his name
606 if (included_authors_count
+ 1 == m_authorNames
.size())
607 ++included_authors_count
;
609 // add the included authors first
610 std::list
<stdstring
>::iterator author_it
= m_authorNames
.begin();
611 while (included_authors_count
> 0 && author_it
!= m_authorNames
.end())
613 // Add him/her to the included list
614 included_authors
.push_back(*author_it
);
616 --included_authors_count
;
619 // If we haven't reached the end yet, copy all remaining authors into the
620 // skipped author list.
621 std::copy(author_it
, m_authorNames
.end(), std::back_inserter(skipped_authors
) );
623 // Sort authors alphabetically if user wants that.
624 if (!m_bSortByCommitCount
)
626 included_authors
.sort();
630 void CStatGraphDlg::ShowCommitsByAuthor()
632 if ((m_parAuthors
==NULL
)||(m_parDates
==NULL
)||(m_parFileChanges
==NULL
))
637 // This function relies on a previous call of GatherData().
638 // This can be detected by checking the week count.
639 // If the week count is equal to -1, it hasn't been called before.
642 // If week count is still -1, something bad has happened, probably invalid data!
646 // We need at least one author
647 if (m_authorNames
.empty())
650 // Add a single series to the chart
651 MyGraphSeries
* graphData
= new MyGraphSeries();
652 m_graph
.AddSeries(*graphData
);
653 m_graphDataArray
.Add(graphData
);
658 m_graph
.SetGraphType(m_GraphType
, m_bStacked
);
659 temp
.LoadString(IDS_STATGRAPH_COMMITSBYAUTHORY
);
660 m_graph
.SetYAxisLabel(temp
);
661 temp
.LoadString(IDS_STATGRAPH_COMMITSBYAUTHORX
);
662 m_graph
.SetXAxisLabel(temp
);
663 temp
.LoadString(IDS_STATGRAPH_COMMITSBYAUTHOR
);
664 m_graph
.SetGraphTitle(temp
);
666 // Find out which authors are to be shown and which are to be skipped.
667 std::list
<stdstring
> authors
;
668 std::list
<stdstring
> others
;
669 FilterSkippedAuthors(authors
, others
);
671 // Loop over all authors in the authors list and
672 // add them to the graph.
676 for (std::list
<stdstring
>::iterator it
= authors
.begin(); it
!= authors
.end(); ++it
)
678 int group
= m_graph
.AppendGroup(it
->c_str());
679 graphData
->SetData(group
, m_commitsPerAuthor
[*it
]);
683 // If we have other authors, count them and their commits.
684 size_t nOthers
= others
.size();
688 for (std::list
<stdstring
>::iterator it
= others
.begin(); it
!= others
.end(); ++it
)
690 nCommits
+= m_commitsPerAuthor
[*it
];
692 CString
sOthers(MAKEINTRESOURCE(IDS_STATGRAPH_OTHERGROUP
));
694 temp
.Format(_T(" (%ld)"), nOthers
);
696 int group
= m_graph
.AppendGroup(sOthers
);
697 graphData
->SetData(group
, nCommits
);
700 // Paint the graph now that we're through.
701 m_graph
.Invalidate();
704 void CStatGraphDlg::ShowCommitsByDate()
706 if ((m_parAuthors
==NULL
)||(m_parDates
==NULL
)||(m_parFileChanges
==NULL
))
711 // This function relies on a previous call of GatherData().
712 // This can be detected by checking the week count.
713 // If the week count is equal to -1, it hasn't been called before.
716 // If week count is still -1, something bad has happened, probably invalid data!
720 // We need at least one author
721 if (m_authorNames
.empty()) return;
726 m_graph
.SetGraphType(m_GraphType
, m_bStacked
);
727 temp
.LoadString(IDS_STATGRAPH_COMMITSBYDATEY
);
728 m_graph
.SetYAxisLabel(temp
);
729 temp
.LoadString(IDS_STATGRAPH_COMMITSBYDATE
);
730 m_graph
.SetGraphTitle(temp
);
732 m_graph
.SetXAxisLabel(GetUnitString());
734 // Find out which authors are to be shown and which are to be skipped.
735 std::list
<stdstring
> authors
;
736 std::list
<stdstring
> others
;
737 FilterSkippedAuthors(authors
, others
);
739 // Add a graph series for each author.
740 AuthorDataMap authorGraphMap
;
741 for (std::list
<stdstring
>::iterator it
= authors
.begin(); it
!= authors
.end(); ++it
)
742 authorGraphMap
[*it
] = m_graph
.AppendGroup(it
->c_str());
743 // If we have skipped authors, add a graph series for all those.
744 CString
sOthers(MAKEINTRESOURCE(IDS_STATGRAPH_OTHERGROUP
));
745 stdstring othersName
;
748 temp
.Format(_T(" (%ld)"), others
.size());
750 othersName
= (LPCWSTR
)sOthers
;
751 authorGraphMap
[othersName
] = m_graph
.AppendGroup(sOthers
);
754 // Mapping to collect commit counts in each interval
755 AuthorDataMap commitCount
;
757 // Loop over all intervals/weeks and collect filtered data.
758 // Sum up data in each interval until the time unit changes.
759 for (int i
=m_lastInterval
; i
>=m_firstInterval
; --i
)
761 // Collect data for authors listed by name.
764 for (std::list
<stdstring
>::iterator it
= authors
.begin(); it
!= authors
.end(); ++it
)
766 // Do we have some data for the current author in the current interval?
767 AuthorDataMap::const_iterator data_it
= m_commitsPerUnitAndAuthor
[i
].find(*it
);
768 if (data_it
== m_commitsPerUnitAndAuthor
[i
].end())
770 commitCount
[*it
] += data_it
->second
;
773 // Collect data for all skipped authors.
776 for (std::list
<stdstring
>::iterator it
= others
.begin(); it
!= others
.end(); ++it
)
778 // Do we have some data for the author in the current interval?
779 AuthorDataMap::const_iterator data_it
= m_commitsPerUnitAndAuthor
[i
].find(*it
);
780 if (data_it
== m_commitsPerUnitAndAuthor
[i
].end())
782 commitCount
[othersName
] += data_it
->second
;
786 // Create a new data series for this unit/interval.
787 MyGraphSeries
* graphData
= new MyGraphSeries();
788 // Loop over all created graphs and set the corresponding data.
789 if (authorGraphMap
.size())
791 for (AuthorDataMap::const_iterator it
= authorGraphMap
.begin(); it
!= authorGraphMap
.end(); ++it
)
793 graphData
->SetData(it
->second
, commitCount
[it
->first
]);
796 graphData
->SetLabel(m_unitNames
[i
].c_str());
797 m_graph
.AddSeries(*graphData
);
798 m_graphDataArray
.Add(graphData
);
800 // Reset commit count mapping.
804 // Paint the graph now that we're through.
805 m_graph
.Invalidate();
808 void CStatGraphDlg::ShowStats()
810 if ((m_parAuthors
==NULL
)||(m_parDates
==NULL
)||(m_parFileChanges
==NULL
))
814 // This function relies on a previous call of GatherData().
815 // This can be detected by checking the week count.
816 // If the week count is equal to -1, it hasn't been called before.
819 // If week count is still -1, something bad has happened, probably invalid data!
823 // Now we can use the gathered data to update the stats dialog.
824 size_t nAuthors
= m_authorNames
.size();
826 // Find most and least active author names.
827 stdstring mostActiveAuthor
;
828 stdstring leastActiveAuthor
;
831 mostActiveAuthor
= m_authorNames
.front();
832 leastActiveAuthor
= m_authorNames
.back();
835 // Obtain the statistics for the table.
836 long nCommitsMin
= -1;
837 long nCommitsMax
= -1;
838 long nFileChangesMin
= -1;
839 long nFileChangesMax
= -1;
841 long nMostActiveMaxCommits
= -1;
842 long nMostActiveMinCommits
= -1;
843 long nLeastActiveMaxCommits
= -1;
844 long nLeastActiveMinCommits
= -1;
846 // Loop over all intervals and find min and max values for commit count and file changes.
847 // Also store the stats for the most and least active authors.
848 for (int i
=m_firstInterval
; i
<=m_lastInterval
; ++i
)
850 // Loop over all commits in this interval and count the number of commits by all authors.
852 AuthorDataMap::iterator commit_endit
= m_commitsPerUnitAndAuthor
[i
].end();
853 for (AuthorDataMap::iterator commit_it
= m_commitsPerUnitAndAuthor
[i
].begin();
854 commit_it
!= commit_endit
; ++commit_it
)
856 commitCount
+= commit_it
->second
;
858 if (nCommitsMin
== -1 || commitCount
< nCommitsMin
)
859 nCommitsMin
= commitCount
;
860 if (nCommitsMax
== -1 || commitCount
> nCommitsMax
)
861 nCommitsMax
= commitCount
;
863 // Loop over all commits in this interval and count the number of file changes by all authors.
864 int fileChangeCount
= 0;
865 AuthorDataMap::iterator filechange_endit
= m_filechangesPerUnitAndAuthor
[i
].end();
866 for (AuthorDataMap::iterator filechange_it
= m_filechangesPerUnitAndAuthor
[i
].begin();
867 filechange_it
!= filechange_endit
; ++filechange_it
)
869 fileChangeCount
+= filechange_it
->second
;
871 if (nFileChangesMin
== -1 || fileChangeCount
< nFileChangesMin
)
872 nFileChangesMin
= fileChangeCount
;
873 if (nFileChangesMax
== -1 || fileChangeCount
> nFileChangesMax
)
874 nFileChangesMax
= fileChangeCount
;
876 // also get min/max data for most and least active authors
879 // check if author is present in this interval
880 AuthorDataMap::iterator author_it
= m_commitsPerUnitAndAuthor
[i
].find(mostActiveAuthor
);
882 if (author_it
== m_commitsPerUnitAndAuthor
[i
].end())
885 authorCommits
= author_it
->second
;
886 if (nMostActiveMaxCommits
== -1 || authorCommits
> nMostActiveMaxCommits
)
887 nMostActiveMaxCommits
= authorCommits
;
888 if (nMostActiveMinCommits
== -1 || authorCommits
< nMostActiveMinCommits
)
889 nMostActiveMinCommits
= authorCommits
;
891 author_it
= m_commitsPerUnitAndAuthor
[i
].find(leastActiveAuthor
);
892 if (author_it
== m_commitsPerUnitAndAuthor
[i
].end())
895 authorCommits
= author_it
->second
;
896 if (nLeastActiveMaxCommits
== -1 || authorCommits
> nLeastActiveMaxCommits
)
897 nLeastActiveMaxCommits
= authorCommits
;
898 if (nLeastActiveMinCommits
== -1 || authorCommits
< nLeastActiveMinCommits
)
899 nLeastActiveMinCommits
= authorCommits
;
902 if (nMostActiveMaxCommits
== -1) nMostActiveMaxCommits
= 0;
903 if (nMostActiveMinCommits
== -1) nMostActiveMinCommits
= 0;
904 if (nLeastActiveMaxCommits
== -1) nLeastActiveMaxCommits
= 0;
905 if (nLeastActiveMinCommits
== -1) nLeastActiveMinCommits
= 0;
907 int nWeeks
= m_nWeeks
;
910 // We have now all data we want and we can fill in the labels...
912 number
.Format(_T("%ld"), nWeeks
);
913 SetDlgItemText(IDC_NUMWEEKVALUE
, number
);
914 number
.Format(_T("%ld"), nAuthors
);
915 SetDlgItemText(IDC_NUMAUTHORVALUE
, number
);
916 number
.Format(_T("%ld"), m_nTotalCommits
);
917 SetDlgItemText(IDC_NUMCOMMITSVALUE
, number
);
918 number
.Format(_T("%ld"), m_nTotalFileChanges
);
919 SetDlgItemText(IDC_NUMFILECHANGESVALUE
, number
);
921 number
.Format(_T("%ld"), m_parAuthors
->GetCount() / nWeeks
);
922 SetDlgItemText(IDC_COMMITSEACHWEEKAVG
, number
);
923 number
.Format(_T("%ld"), nCommitsMax
);
924 SetDlgItemText(IDC_COMMITSEACHWEEKMAX
, number
);
925 number
.Format(_T("%ld"), nCommitsMin
);
926 SetDlgItemText(IDC_COMMITSEACHWEEKMIN
, number
);
928 number
.Format(_T("%ld"), m_nTotalFileChanges
/ nWeeks
);
929 SetDlgItemText(IDC_FILECHANGESEACHWEEKAVG
, number
);
930 number
.Format(_T("%ld"), nFileChangesMax
);
931 SetDlgItemText(IDC_FILECHANGESEACHWEEKMAX
, number
);
932 number
.Format(_T("%ld"), nFileChangesMin
);
933 SetDlgItemText(IDC_FILECHANGESEACHWEEKMIN
, number
);
937 SetDlgItemText(IDC_MOSTACTIVEAUTHORNAME
, _T(""));
938 SetDlgItemText(IDC_MOSTACTIVEAUTHORAVG
, _T("0"));
939 SetDlgItemText(IDC_MOSTACTIVEAUTHORMAX
, _T("0"));
940 SetDlgItemText(IDC_MOSTACTIVEAUTHORMIN
, _T("0"));
941 SetDlgItemText(IDC_LEASTACTIVEAUTHORNAME
, _T(""));
942 SetDlgItemText(IDC_LEASTACTIVEAUTHORAVG
, _T("0"));
943 SetDlgItemText(IDC_LEASTACTIVEAUTHORMAX
, _T("0"));
944 SetDlgItemText(IDC_LEASTACTIVEAUTHORMIN
, _T("0"));
948 SetDlgItemText(IDC_MOSTACTIVEAUTHORNAME
, mostActiveAuthor
.c_str());
949 number
.Format(_T("%ld"), m_commitsPerAuthor
[mostActiveAuthor
] / nWeeks
);
950 SetDlgItemText(IDC_MOSTACTIVEAUTHORAVG
, number
);
951 number
.Format(_T("%ld"), nMostActiveMaxCommits
);
952 SetDlgItemText(IDC_MOSTACTIVEAUTHORMAX
, number
);
953 number
.Format(_T("%ld"), nMostActiveMinCommits
);
954 SetDlgItemText(IDC_MOSTACTIVEAUTHORMIN
, number
);
956 SetDlgItemText(IDC_LEASTACTIVEAUTHORNAME
, leastActiveAuthor
.c_str());
957 number
.Format(_T("%ld"), m_commitsPerAuthor
[leastActiveAuthor
] / nWeeks
);
958 SetDlgItemText(IDC_LEASTACTIVEAUTHORAVG
, number
);
959 number
.Format(_T("%ld"), nLeastActiveMaxCommits
);
960 SetDlgItemText(IDC_LEASTACTIVEAUTHORMAX
, number
);
961 number
.Format(_T("%ld"), nLeastActiveMinCommits
);
962 SetDlgItemText(IDC_LEASTACTIVEAUTHORMIN
, number
);
966 void CStatGraphDlg::OnCbnSelchangeGraphcombo()
969 DWORD_PTR graphtype
= m_cGraphType
.GetItemData(m_cGraphType
.GetCurSel());
974 // intended fall through
977 m_btnGraphLine
.EnableWindow(TRUE
);
978 m_btnGraphLineStacked
.EnableWindow(TRUE
);
979 m_btnGraphPie
.EnableWindow(TRUE
);
980 m_GraphType
= MyGraph::Line
;
985 m_btnGraphLine
.EnableWindow(FALSE
);
986 m_btnGraphLineStacked
.EnableWindow(FALSE
);
987 m_btnGraphPie
.EnableWindow(TRUE
);
988 m_GraphType
= MyGraph::Bar
;
996 int CStatGraphDlg::GetUnitCount()
1001 return (m_nWeeks
/4)+1;
1003 return (m_nWeeks
/13)+1; // quarters
1004 return (m_nWeeks
/52)+1;
1007 int CStatGraphDlg::GetUnit(const CTime
& time
)
1010 return GetCalendarWeek(time
);
1012 return time
.GetMonth();
1014 return ((time
.GetMonth()-1)/3)+1; // quarters
1015 return time
.GetYear();
1018 CStatGraphDlg::UnitType
CStatGraphDlg::GetUnitType()
1029 CString
CStatGraphDlg::GetUnitString()
1032 return CString(MAKEINTRESOURCE(IDS_STATGRAPH_COMMITSBYDATEXWEEK
));
1034 return CString(MAKEINTRESOURCE(IDS_STATGRAPH_COMMITSBYDATEXMONTH
));
1036 return CString(MAKEINTRESOURCE(IDS_STATGRAPH_COMMITSBYDATEXQUARTER
));
1037 return CString(MAKEINTRESOURCE(IDS_STATGRAPH_COMMITSBYDATEXYEAR
));
1040 CString
CStatGraphDlg::GetUnitLabel(int unit
, CTime
&lasttime
)
1043 switch (GetUnitType())
1047 int year
= lasttime
.GetYear();
1048 if ((unit
== 1)&&(lasttime
.GetMonth() == 12))
1051 switch (m_langOrder
)
1053 case 0: // month day year
1054 case 1: // day month year
1056 temp
.Format(_T("%d/%.2d"), unit
, year
%100);
1058 case 2: // year month day
1059 temp
.Format(_T("%.2d/%d"), year
%100, unit
);
1065 switch (m_langOrder
)
1067 case 0: // month day year
1068 case 1: // day month year
1070 temp
.Format(_T("%d/%.2d"), unit
, lasttime
.GetYear()%100);
1072 case 2: // year month day
1073 temp
.Format(_T("%.2d/%d"), lasttime
.GetYear()%100, unit
);
1078 switch (m_langOrder
)
1080 case 0: // month day year
1081 case 1: // day month year
1083 temp
.Format(_T("%d/%.2d"), unit
, lasttime
.GetYear()%100);
1085 case 2: // year month day
1086 temp
.Format(_T("%.2d/%d"), lasttime
.GetYear()%100, unit
);
1091 temp
.Format(_T("%d"), unit
);
1097 void CStatGraphDlg::OnHScroll(UINT nSBCode
, UINT nPos
, CScrollBar
* pScrollBar
)
1099 if (nSBCode
== TB_THUMBTRACK
)
1100 return CDialog::OnHScroll(nSBCode
, nPos
, pScrollBar
);
1102 switch (m_cGraphType
.GetItemData(m_cGraphType
.GetCurSel()))
1108 ShowCommitsByDate();
1111 ShowCommitsByAuthor();
1115 CDialog::OnHScroll(nSBCode
, nPos
, pScrollBar
);
1118 void CStatGraphDlg::OnNeedText(NMHDR
*pnmh
, LRESULT
* /*pResult*/)
1120 TOOLTIPTEXT
* pttt
= (TOOLTIPTEXT
*) pnmh
;
1121 if (pttt
->hdr
.idFrom
== (UINT
) m_Skipper
.GetSafeHwnd())
1123 size_t included_authors_count
= m_Skipper
.GetPos();
1124 // if we only leave out one author, still include him with his name
1125 if (included_authors_count
+ 1 == m_authorNames
.size())
1126 ++included_authors_count
;
1128 // find the minimum number of commits that the shown authors have
1129 int min_commits
= 0;
1130 included_authors_count
= min(included_authors_count
, m_authorNames
.size());
1131 std::list
<stdstring
>::iterator author_it
= m_authorNames
.begin();
1132 advance(author_it
, included_authors_count
);
1133 if (author_it
!= m_authorNames
.begin())
1134 min_commits
= m_commitsPerAuthor
[ *(--author_it
) ];
1137 int percentage
= int(min_commits
*100.0/(m_nTotalCommits
? m_nTotalCommits
: 1));
1138 string
.Format(IDS_STATGRAPH_AUTHORSLIDER_TT
, m_Skipper
.GetPos(), min_commits
, percentage
);
1139 ::lstrcpy(pttt
->szText
, (LPCTSTR
) string
);
1143 void CStatGraphDlg::AuthorsCaseSensitiveChanged()
1145 UpdateData(); // update checkbox state
1146 GatherData(); // first regenerate the statistics data
1147 RedrawGraph(); // then update the current statistics page
1150 void CStatGraphDlg::SortModeChanged()
1152 UpdateData(); // update checkbox state
1153 RedrawGraph(); // then update the current statistics page
1156 void CStatGraphDlg::ClearGraph()
1159 for (int j
=0; j
<m_graphDataArray
.GetCount(); ++j
)
1160 delete ((MyGraphSeries
*)m_graphDataArray
.GetAt(j
));
1161 m_graphDataArray
.RemoveAll();
1164 void CStatGraphDlg::RedrawGraph()
1166 EnableDisableMenu();
1167 m_btnGraphBar
.SetState(BST_UNCHECKED
);
1168 m_btnGraphBarStacked
.SetState(BST_UNCHECKED
);
1169 m_btnGraphLine
.SetState(BST_UNCHECKED
);
1170 m_btnGraphLineStacked
.SetState(BST_UNCHECKED
);
1171 m_btnGraphPie
.SetState(BST_UNCHECKED
);
1173 if ((m_GraphType
== MyGraph::Bar
)&&(m_bStacked
))
1175 m_btnGraphBarStacked
.SetState(BST_CHECKED
);
1177 if ((m_GraphType
== MyGraph::Bar
)&&(!m_bStacked
))
1179 m_btnGraphBar
.SetState(BST_CHECKED
);
1181 if ((m_GraphType
== MyGraph::Line
)&&(m_bStacked
))
1183 m_btnGraphLineStacked
.SetState(BST_CHECKED
);
1185 if ((m_GraphType
== MyGraph::Line
)&&(!m_bStacked
))
1187 m_btnGraphLine
.SetState(BST_CHECKED
);
1189 if (m_GraphType
== MyGraph::PieChart
)
1191 m_btnGraphPie
.SetState(BST_CHECKED
);
1196 switch (m_cGraphType
.GetItemData(m_cGraphType
.GetCurSel()))
1202 ShowCommitsByDate();
1205 ShowCommitsByAuthor();
1209 void CStatGraphDlg::OnBnClickedGraphbarbutton()
1211 m_GraphType
= MyGraph::Bar
;
1216 void CStatGraphDlg::OnBnClickedGraphbarstackedbutton()
1218 m_GraphType
= MyGraph::Bar
;
1223 void CStatGraphDlg::OnBnClickedGraphlinebutton()
1225 m_GraphType
= MyGraph::Line
;
1230 void CStatGraphDlg::OnBnClickedGraphlinestackedbutton()
1232 m_GraphType
= MyGraph::Line
;
1237 void CStatGraphDlg::OnBnClickedGraphpiebutton()
1239 m_GraphType
= MyGraph::PieChart
;
1244 BOOL
CStatGraphDlg::PreTranslateMessage(MSG
* pMsg
)
1246 if (NULL
!= m_pToolTip
)
1247 m_pToolTip
->RelayEvent(pMsg
);
1249 return CStandAloneDialogTmpl
<CResizableDialog
>::PreTranslateMessage(pMsg
);
1252 void CStatGraphDlg::EnableDisableMenu()
1254 UINT nEnable
= MF_BYCOMMAND
;
1255 if (m_cGraphType
.GetItemData(m_cGraphType
.GetCurSel()) == 1)
1256 nEnable
|= (MF_DISABLED
| MF_GRAYED
);
1258 nEnable
|= MF_ENABLED
;
1259 GetMenu()->EnableMenuItem(ID_FILE_SAVESTATGRAPHAS
, nEnable
);
1262 void CStatGraphDlg::OnFileSavestatgraphas()
1265 int filterindex
= 0;
1266 if (CAppUtils::FileOpenSave(tempfile
, &filterindex
, IDS_REVGRAPH_SAVEPIC
, IDS_PICTUREFILEFILTER
, false, m_hWnd
))
1268 // if the user doesn't specify a file extension, default to
1269 // wmf and add that extension to the filename. But only if the
1270 // user chose the 'pictures' filter. The filename isn't changed
1271 // if the 'All files' filter was chosen.
1273 int dotPos
= tempfile
.ReverseFind('.');
1274 int slashPos
= tempfile
.ReverseFind('\\');
1275 if (dotPos
> slashPos
)
1276 extension
= tempfile
.Mid(dotPos
);
1277 if ((filterindex
== 1)&&(extension
.IsEmpty()))
1279 extension
= _T(".wmf");
1280 tempfile
+= extension
;
1282 SaveGraph(tempfile
);
1286 void CStatGraphDlg::SaveGraph(CString sFilename
)
1288 CString extension
= CPathUtils::GetFileExtFromPath(sFilename
);
1289 if (extension
.CompareNoCase(_T(".wmf"))==0)
1291 // save the graph as an enhanced meta file
1292 CMyMetaFileDC wmfDC
;
1293 wmfDC
.CreateEnhanced(NULL
, sFilename
, NULL
, _T("TortoiseGit\0Statistics\0\0"));
1294 wmfDC
.SetAttribDC(GetDC()->GetSafeHdc());
1296 m_graph
.DrawGraph(wmfDC
);
1297 HENHMETAFILE hemf
= wmfDC
.CloseEnhanced();
1298 DeleteEnhMetaFile(hemf
);
1302 // to save the graph as a pixel picture (e.g. gif, png, jpeg, ...)
1303 // the user needs to have GDI+ installed. So check if GDI+ is
1304 // available before we start using it.
1305 TCHAR gdifindbuf
[MAX_PATH
];
1306 _tcscpy_s(gdifindbuf
, MAX_PATH
, _T("gdiplus.dll"));
1307 if (PathFindOnPath(gdifindbuf
, NULL
))
1309 ATLTRACE("gdi plus found!");
1313 ATLTRACE("gdi plus not found!");
1314 CMessageBox::Show(m_hWnd
, IDS_ERR_GDIPLUS_MISSING
, IDS_APPNAME
, MB_ICONERROR
);
1318 // save the graph as a pixel picture instead of a vector picture
1319 // create dc to paint on
1322 CWindowDC
ddc(this);
1324 if (!dc
.CreateCompatibleDC(&ddc
))
1328 FORMAT_MESSAGE_ALLOCATE_BUFFER
|
1329 FORMAT_MESSAGE_FROM_SYSTEM
|
1330 FORMAT_MESSAGE_IGNORE_INSERTS
,
1333 MAKELANGID(LANG_NEUTRAL
, SUBLANG_DEFAULT
), // Default language
1340 MessageBox( (LPCTSTR
)lpMsgBuf
, _T("Error"), MB_OK
| MB_ICONINFORMATION
);
1341 LocalFree( lpMsgBuf
);
1345 GetDlgItem(IDC_GRAPH
)->GetClientRect(&rect
);
1346 HBITMAP hbm
= ::CreateCompatibleBitmap(ddc
.m_hDC
, rect
.Width(), rect
.Height());
1351 FORMAT_MESSAGE_ALLOCATE_BUFFER
|
1352 FORMAT_MESSAGE_FROM_SYSTEM
|
1353 FORMAT_MESSAGE_IGNORE_INSERTS
,
1356 MAKELANGID(LANG_NEUTRAL
, SUBLANG_DEFAULT
), // Default language
1363 MessageBox( (LPCTSTR
)lpMsgBuf
, _T("Error"), MB_OK
| MB_ICONINFORMATION
);
1364 LocalFree( lpMsgBuf
);
1367 HBITMAP oldbm
= (HBITMAP
)dc
.SelectObject(hbm
);
1368 // paint the whole graph
1370 m_graph
.DrawGraph(dc
);
1371 // now use GDI+ to save the picture
1373 GdiplusStartupInput gdiplusStartupInput
;
1374 ULONG_PTR gdiplusToken
;
1375 CString sErrormessage
;
1376 if (GdiplusStartup( &gdiplusToken
, &gdiplusStartupInput
, NULL
)==Ok
)
1379 Bitmap
bitmap(hbm
, NULL
);
1380 if (bitmap
.GetLastStatus()==Ok
)
1382 // Get the CLSID of the encoder.
1384 if (CPathUtils::GetFileExtFromPath(sFilename
).CompareNoCase(_T(".png"))==0)
1385 ret
= GetEncoderClsid(L
"image/png", &encoderClsid
);
1386 else if (CPathUtils::GetFileExtFromPath(sFilename
).CompareNoCase(_T(".jpg"))==0)
1387 ret
= GetEncoderClsid(L
"image/jpeg", &encoderClsid
);
1388 else if (CPathUtils::GetFileExtFromPath(sFilename
).CompareNoCase(_T(".jpeg"))==0)
1389 ret
= GetEncoderClsid(L
"image/jpeg", &encoderClsid
);
1390 else if (CPathUtils::GetFileExtFromPath(sFilename
).CompareNoCase(_T(".bmp"))==0)
1391 ret
= GetEncoderClsid(L
"image/bmp", &encoderClsid
);
1392 else if (CPathUtils::GetFileExtFromPath(sFilename
).CompareNoCase(_T(".gif"))==0)
1393 ret
= GetEncoderClsid(L
"image/gif", &encoderClsid
);
1396 sFilename
+= _T(".jpg");
1397 ret
= GetEncoderClsid(L
"image/jpeg", &encoderClsid
);
1401 CStringW tfile
= CStringW(sFilename
);
1402 bitmap
.Save(tfile
, &encoderClsid
, NULL
);
1406 sErrormessage
.Format(IDS_REVGRAPH_ERR_NOENCODER
, CPathUtils::GetFileExtFromPath(sFilename
));
1411 sErrormessage
.LoadString(IDS_REVGRAPH_ERR_NOBITMAP
);
1414 GdiplusShutdown(gdiplusToken
);
1418 sErrormessage
.LoadString(IDS_REVGRAPH_ERR_GDIINIT
);
1420 dc
.SelectObject(oldbm
);
1422 if (!sErrormessage
.IsEmpty())
1424 CMessageBox::Show(m_hWnd
, sErrormessage
, _T("TortoiseGit"), MB_ICONERROR
);
1427 catch (CException
* pE
)
1429 TCHAR szErrorMsg
[2048];
1430 pE
->GetErrorMessage(szErrorMsg
, 2048);
1431 CMessageBox::Show(m_hWnd
, szErrorMsg
, _T("TortoiseGit"), MB_ICONERROR
);
1436 int CStatGraphDlg::GetEncoderClsid(const WCHAR
* format
, CLSID
* pClsid
)
1438 UINT num
= 0; // number of image encoders
1439 UINT size
= 0; // size of the image encoder array in bytes
1441 ImageCodecInfo
* pImageCodecInfo
= NULL
;
1443 if (GetImageEncodersSize(&num
, &size
)!=Ok
)
1446 return -1; // Failure
1448 pImageCodecInfo
= (ImageCodecInfo
*)(malloc(size
));
1449 if (pImageCodecInfo
== NULL
)
1450 return -1; // Failure
1452 if (GetImageEncoders(num
, size
, pImageCodecInfo
)==Ok
)
1454 for (UINT j
= 0; j
< num
; ++j
)
1456 if (wcscmp(pImageCodecInfo
[j
].MimeType
, format
) == 0)
1458 *pClsid
= pImageCodecInfo
[j
].Clsid
;
1459 free(pImageCodecInfo
);
1460 return j
; // Success
1464 free (pImageCodecInfo
);
1465 return -1; // Failure
1468 void CStatGraphDlg::StoreCurrentGraphType()
1471 DWORD graphtype
= static_cast<DWORD
>(m_cGraphType
.GetItemData(m_cGraphType
.GetCurSel()));
1472 // encode the current chart type
1473 DWORD statspage
= graphtype
*10;
1474 if ((m_GraphType
== MyGraph::Bar
)&&(m_bStacked
))
1478 if ((m_GraphType
== MyGraph::Bar
)&&(!m_bStacked
))
1482 if ((m_GraphType
== MyGraph::Line
)&&(m_bStacked
))
1486 if ((m_GraphType
== MyGraph::Line
)&&(!m_bStacked
))
1490 if (m_GraphType
== MyGraph::PieChart
)
1495 // store current chart type in registry
1496 CRegDWORD lastStatsPage
= CRegDWORD(_T("Software\\TortoiseGit\\LastViewedStatsPage"), 0);
1497 lastStatsPage
= statspage
;
1499 CRegDWORD regAuthors
= CRegDWORD(_T("Software\\TortoiseGit\\StatAuthorsCaseSensitive"));
1500 regAuthors
= m_bAuthorsCaseSensitive
;
1502 CRegDWORD regSort
= CRegDWORD(_T("Software\\TortoiseGit\\StatSortByCommitCount"));
1503 regSort
= m_bSortByCommitCount
;