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