OSDN Git Service

Share resource file between TortoiseGitBlame and TortoiseProc
[tortoisegit/TortoiseGitJp.git] / src / TortoiseProc / StatGraphDlg.cpp
1 // TortoiseSVN - a Windows shell extension for easy version control\r
2 \r
3 // Copyright (C) 2003-2008 - TortoiseSVN\r
4 \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
9 \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
14 \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
18 //\r
19 #include "stdafx.h"\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
29 #include <cmath>\r
30 #include <locale>\r
31 #include <list>\r
32 #include <utility>\r
33 \r
34 using namespace Gdiplus;\r
35 \r
36 // BinaryPredicate for comparing authors based on their commit count\r
37 class MoreCommitsThan : public std::binary_function<stdstring, stdstring, bool> {\r
38 public:\r
39         MoreCommitsThan(std::map<stdstring, LONG> * author_commits) : m_authorCommits(author_commits) {}\r
40 \r
41         bool operator()(const stdstring& lhs, const stdstring& rhs) {\r
42                 return (*m_authorCommits)[lhs] > (*m_authorCommits)[rhs];\r
43         }\r
44 \r
45 private:\r
46         std::map<stdstring, LONG> * m_authorCommits;\r
47 };\r
48 \r
49 \r
50 IMPLEMENT_DYNAMIC(CStatGraphDlg, CResizableStandAloneDialog)\r
51 CStatGraphDlg::CStatGraphDlg(CWnd* pParent /*=NULL*/)\r
52         : CResizableStandAloneDialog(CStatGraphDlg::IDD, pParent)\r
53         , m_bStacked(FALSE)\r
54         , m_GraphType(MyGraph::Bar)\r
55         , m_bAuthorsCaseSensitive(TRUE)\r
56         , m_bSortByCommitCount(TRUE)\r
57         , m_nWeeks(-1)\r
58 {\r
59         m_parDates = NULL;\r
60         m_parFileChanges = NULL;\r
61         m_parAuthors = NULL;\r
62         m_pToolTip = NULL;\r
63 }\r
64 \r
65 CStatGraphDlg::~CStatGraphDlg()\r
66 {\r
67         ClearGraph();\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
73         delete m_pToolTip;\r
74 }\r
75 \r
76 void CStatGraphDlg::OnOK() {\r
77         StoreCurrentGraphType();\r
78         __super::OnOK();\r
79 }\r
80 \r
81 void CStatGraphDlg::OnCancel() {\r
82         StoreCurrentGraphType();\r
83         __super::OnCancel();\r
84 }\r
85 \r
86 void CStatGraphDlg::DoDataExchange(CDataExchange* pDX)\r
87 {\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
99 }\r
100 \r
101 \r
102 BEGIN_MESSAGE_MAP(CStatGraphDlg, CResizableStandAloneDialog)\r
103         ON_CBN_SELCHANGE(IDC_GRAPHCOMBO, OnCbnSelchangeGraphcombo)\r
104         ON_WM_HSCROLL()\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
114 END_MESSAGE_MAP()\r
115 \r
116 BOOL CStatGraphDlg::OnInitDialog()\r
117 {\r
118         CResizableStandAloneDialog::OnInitDialog();\r
119 \r
120         m_pToolTip = new CToolTipCtrl;\r
121         if (m_pToolTip->Create(this))\r
122         {\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
128 \r
129                 m_pToolTip->Activate(TRUE);\r
130         }\r
131         \r
132         m_bAuthorsCaseSensitive = DWORD(CRegDWORD(_T("Software\\TortoiseGit\\StatAuthorsCaseSensitive")));\r
133         m_bSortByCommitCount = DWORD(CRegDWORD(_T("Software\\TortoiseGit\\StatSortByCommitCount")));\r
134         UpdateData(FALSE);\r
135 \r
136         CString temp;\r
137         int sel = 0;\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
148 \r
149         // set the dialog title to "Statistics - path/to/whatever/we/show/the/statistics/for"\r
150         CString sTitle;\r
151         GetWindowText(sTitle);\r
152         if(m_path.IsDirectory())\r
153                 SetWindowText(sTitle + _T(" - ") + m_path.GetWinPathString());\r
154         else\r
155                 SetWindowText(sTitle + _T(" - ") + m_path.GetFilename());\r
156 \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
162 \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
173 \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
177 \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
186 \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
208 \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
214 \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
221 \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
224         GatherData();\r
225 \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
232 \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
235 \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
240 \r
241         OnCbnSelchangeGraphcombo();\r
242 \r
243         int statspage = lastStatsPage % 10;\r
244         switch (statspage) {\r
245                 case 1 : \r
246                         m_GraphType = MyGraph::Bar;\r
247                         m_bStacked = true;\r
248                         break;\r
249                 case 2 : \r
250                         m_GraphType = MyGraph::Bar;\r
251                         m_bStacked = false;\r
252                         break;\r
253                 case 3 : \r
254                         m_GraphType = MyGraph::Line;\r
255                         m_bStacked = true;\r
256                         break;\r
257                 case 4 : \r
258                         m_GraphType = MyGraph::Line;\r
259                         m_bStacked = false;\r
260                         break;\r
261                 case 5 : \r
262                         m_GraphType = MyGraph::PieChart;\r
263                         break;\r
264 \r
265                 default : return TRUE;\r
266         }\r
267 \r
268         LCID m_locale = MAKELCID((DWORD)CRegStdWORD(_T("Software\\TortoiseGit\\LanguageID"), MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT)), SORT_DEFAULT);\r
269 \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
272 \r
273         TCHAR l = 0;\r
274         GetLocaleInfo(locale, LOCALE_IDATE, &l, sizeof(TCHAR));\r
275 \r
276         if (l > 0)\r
277                 m_langOrder = l-'0';\r
278         else\r
279                 m_langOrder = -1;\r
280 \r
281         RedrawGraph();\r
282 \r
283         return TRUE;\r
284 }\r
285 \r
286 void CStatGraphDlg::ShowLabels(BOOL bShow)\r
287 {\r
288         if ((m_parAuthors==NULL)||(m_parDates==NULL)||(m_parFileChanges==NULL))\r
289                 return;\r
290         int nCmdShow = SW_SHOW;\r
291         if (!bShow)\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
302 \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
324 \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
333 }\r
334 \r
335 void CStatGraphDlg::UpdateWeekCount()\r
336 {\r
337         // Sanity check\r
338         if ((!m_parDates)&&(m_parDates->GetCount()))\r
339         {\r
340                 return;\r
341         }\r
342         // Already updated? No need to do it again.\r
343         if (m_nWeeks >= 0)\r
344                 return;\r
345 \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
351         {\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
355         }\r
356 \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
360         \r
361         // How many weeks does the time period cover?\r
362 \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
367 }\r
368 \r
369 int CStatGraphDlg::GetCalendarWeek(const CTime& time)\r
370 {\r
371         // Note:\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
377         // the real week.\r
378         // TODO: ?\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
386 \r
387         int iYear = time.GetYear();\r
388         int iFirstDayOfWeek = 0;\r
389         int iFirstWeekOfYear = 0;\r
390         TCHAR loc[2];\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
397 \r
398         // Select mode\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
403         {\r
404         case 0:\r
405                 {\r
406                         // Week containing 1/1 is the first week of that year.\r
407 \r
408                         // check if this week reaches into the next year\r
409                         dDateFirstJanuary = CTime(iYear+1,1,1,0,0,0);\r
410 \r
411                         // Get start of week\r
412                         try\r
413                         {\r
414                                 iDayOfWeek = (time.GetDayOfWeek()+5+iFirstDayOfWeek)%7;\r
415                         }\r
416                         catch (CException* e)\r
417                         {\r
418                                 e->Delete();\r
419                         }\r
420                         CTime dStartOfWeek = time-CTimeSpan(iDayOfWeek,0,0,0);\r
421 \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
424                         {\r
425                                 // we are in the last week of the year that spans over 1/1\r
426                                 iWeekOfYear = 1;\r
427                         }\r
428                         else\r
429                         {\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
435                         }\r
436                 }\r
437                 break;\r
438         case 1:\r
439                 {\r
440                         // First full week following 1/1 is the first week of that year.\r
441 \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
444                         iWeekOfYear =\r
445                                 (int)(((time-dDateFirstJanuary).GetDays()+iDayOfWeek) / 7) +\r
446                                 (iDayOfWeek==0 ? 1:0);\r
447 \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
451                         {\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
455                                 iDayOfWeek =\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
459                                 iWeekOfYear =\r
460                                         (int)(((time-dDateFirstJanuary).GetDays()+iDayOfWeek) / 7) +\r
461                                         (iDayOfWeek<=3 ? 1:0);\r
462                         }\r
463                 }\r
464                 break;\r
465         case 2:\r
466                 {\r
467                         // First week containing at least four days is the first week of that year.\r
468 \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
475                         // previous year\r
476                         iWeekOfYear =\r
477                                 (int)(((time-dDateFirstJanuary).GetDays()+iDayOfWeek) / 7) +\r
478                                 (iDayOfWeek<=3 ? 1:0);\r
479 \r
480                         // special cases\r
481                         if (iWeekOfYear==0)\r
482                         {\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
486 \r
487                                 dDateFirstJanuary = CTime(iYear-1,1,1,0,0,0);\r
488                                 iDayOfWeek =\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
492                                 iWeekOfYear =\r
493                                         (int)(((time-dDateFirstJanuary).GetDays()+iDayOfWeek) / 7) +\r
494                                         (iDayOfWeek<=3 ? 1:0);\r
495                         }\r
496                         else if (iWeekOfYear==53)\r
497                         {\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
501 \r
502                                 dDateFirstJanuary = CTime(iYear+1,1,1,0,0,0);\r
503                                 iDayOfWeek =\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
507                         }\r
508                 }\r
509                 break;\r
510         default:\r
511                 ASSERT(FALSE);\r
512                 break;\r
513         }\r
514         // return result\r
515         return iWeekOfYear;\r
516 }\r
517 \r
518 void CStatGraphDlg::GatherData() \r
519 {\r
520         // Sanity check\r
521         if ((m_parAuthors==NULL)||(m_parDates==NULL)||(m_parFileChanges==NULL))\r
522                 return;\r
523         m_nTotalCommits = m_parAuthors->GetCount();\r
524         m_nTotalFileChanges = 0;\r
525 \r
526         // Update m_nWeeks and m_minDate\r
527         UpdateWeekCount();\r
528 \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
533 \r
534         int interval = 0;\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
539         {\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
544                         interval++;\r
545                 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
561         }\r
562 \r
563         // Find first and last interval number.\r
564         if (!m_commitsPerUnitAndAuthor.empty()) \r
565         {\r
566                 IntervalDataMap::iterator interval_it = m_commitsPerUnitAndAuthor.begin();\r
567                 m_firstInterval = interval_it->first;\r
568                 interval_it = m_commitsPerUnitAndAuthor.end();\r
569                 --interval_it;\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
573         }\r
574         else\r
575         {\r
576                 m_firstInterval = 0;\r
577                 m_lastInterval = -1;\r
578         }\r
579 \r
580         // Get a list of authors names\r
581         m_authorNames.clear();\r
582         if (m_commitsPerAuthor.size())\r
583         {\r
584                 for (std::map<stdstring, LONG>::iterator it = m_commitsPerAuthor.begin(); \r
585                         it != m_commitsPerAuthor.end(); ++it) \r
586                 {\r
587                         m_authorNames.push_back(it->first);\r
588                 }\r
589         }\r
590 \r
591         // Sort the list of authors based on commit count\r
592         m_authorNames.sort( MoreCommitsThan(&m_commitsPerAuthor) );\r
593 \r
594         // All done, now the statistics pages can retrieve the data and \r
595         // extract the information to be shown.\r
596 }\r
597 \r
598 void CStatGraphDlg::FilterSkippedAuthors(std::list<stdstring>& included_authors, \r
599                                                                                  std::list<stdstring>& skipped_authors)\r
600 {\r
601         included_authors.clear();\r
602         skipped_authors.clear();\r
603 \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
608 \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
612         {\r
613                 // Add him/her to the included list\r
614                 included_authors.push_back(*author_it);\r
615                 ++author_it;\r
616                 --included_authors_count;\r
617         }\r
618 \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
622 \r
623         // Sort authors alphabetically if user wants that.\r
624         if (!m_bSortByCommitCount) \r
625         {\r
626                 included_authors.sort();\r
627         }\r
628 }\r
629 \r
630 void CStatGraphDlg::ShowCommitsByAuthor()\r
631 {\r
632         if ((m_parAuthors==NULL)||(m_parDates==NULL)||(m_parFileChanges==NULL))\r
633                 return;\r
634         ShowLabels(FALSE);\r
635         ClearGraph();\r
636 \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
641                 GatherData();\r
642         // If week count is still -1, something bad has happened, probably invalid data!\r
643         if (m_nWeeks == -1)\r
644                 return;\r
645 \r
646         // We need at least one author\r
647         if (m_authorNames.empty()) \r
648                 return;\r
649 \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
654 \r
655         // Set up the graph.\r
656         CString temp;\r
657         UpdateData();\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
665 \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
670 \r
671         // Loop over all authors in the authors list and \r
672         // add them to the graph.\r
673 \r
674         if (authors.size())\r
675         {\r
676                 for (std::list<stdstring>::iterator it = authors.begin(); it != authors.end(); ++it)\r
677                 {\r
678                         int group = m_graph.AppendGroup(it->c_str());\r
679                         graphData->SetData(group, m_commitsPerAuthor[*it]);\r
680                 }\r
681         }\r
682 \r
683         // If we have other authors, count them and their commits.\r
684         size_t nOthers = others.size();\r
685         if (nOthers != 0)\r
686         {\r
687                 int nCommits = 0;\r
688                 for (std::list<stdstring>::iterator it = others.begin(); it != others.end(); ++it)\r
689                 {\r
690                         nCommits += m_commitsPerAuthor[*it];\r
691                 }\r
692                 CString sOthers(MAKEINTRESOURCE(IDS_STATGRAPH_OTHERGROUP));\r
693                 CString temp;\r
694                 temp.Format(_T(" (%ld)"), nOthers);\r
695                 sOthers += temp;\r
696                 int group = m_graph.AppendGroup(sOthers);\r
697                 graphData->SetData(group, nCommits);\r
698         }\r
699 \r
700         // Paint the graph now that we're through.\r
701         m_graph.Invalidate();\r
702 }\r
703 \r
704 void CStatGraphDlg::ShowCommitsByDate()\r
705 {\r
706         if ((m_parAuthors==NULL)||(m_parDates==NULL)||(m_parFileChanges==NULL))\r
707                 return;\r
708         ShowLabels(FALSE);\r
709         ClearGraph();\r
710 \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
715                 GatherData();\r
716         // If week count is still -1, something bad has happened, probably invalid data!\r
717         if (m_nWeeks == -1)\r
718                 return;\r
719 \r
720         // We need at least one author\r
721         if (m_authorNames.empty()) return;\r
722 \r
723         // Set up the graph.\r
724         CString temp;\r
725         UpdateData();\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
731 \r
732         m_graph.SetXAxisLabel(GetUnitString());\r
733 \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
738 \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
747         {\r
748                 temp.Format(_T(" (%ld)"), others.size());\r
749                 sOthers += temp;\r
750                 othersName = (LPCWSTR)sOthers;\r
751                 authorGraphMap[othersName] = m_graph.AppendGroup(sOthers);\r
752         }\r
753 \r
754         // Mapping to collect commit counts in each interval\r
755         AuthorDataMap   commitCount;\r
756 \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
760         {\r
761                 // Collect data for authors listed by name.\r
762                 if (authors.size())\r
763                 {\r
764                         for (std::list<stdstring>::iterator it = authors.begin(); it != authors.end(); ++it)\r
765                         {\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
769                                         continue;\r
770                                 commitCount[*it] += data_it->second;\r
771                         }\r
772                 }\r
773                 // Collect data for all skipped authors.\r
774                 if (others.size())\r
775                 {\r
776                         for (std::list<stdstring>::iterator it = others.begin(); it != others.end(); ++it)\r
777                         {\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
781                                         continue;\r
782                                 commitCount[othersName] += data_it->second;\r
783                         }\r
784                 }\r
785 \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
790                 {\r
791                         for (AuthorDataMap::const_iterator it = authorGraphMap.begin(); it != authorGraphMap.end(); ++it) \r
792                         {\r
793                                 graphData->SetData(it->second, commitCount[it->first]);\r
794                         }\r
795                 }\r
796                 graphData->SetLabel(m_unitNames[i].c_str());\r
797                 m_graph.AddSeries(*graphData);\r
798                 m_graphDataArray.Add(graphData);\r
799 \r
800                 // Reset commit count mapping.\r
801                 commitCount.clear();\r
802         }\r
803 \r
804         // Paint the graph now that we're through.\r
805         m_graph.Invalidate();\r
806 }\r
807 \r
808 void CStatGraphDlg::ShowStats()\r
809 {\r
810         if ((m_parAuthors==NULL)||(m_parDates==NULL)||(m_parFileChanges==NULL))\r
811                 return;\r
812         ShowLabels(TRUE);\r
813 \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
818                 GatherData();\r
819         // If week count is still -1, something bad has happened, probably invalid data!\r
820         if (m_nWeeks == -1)\r
821                 return;\r
822 \r
823         // Now we can use the gathered data to update the stats dialog.\r
824         size_t nAuthors = m_authorNames.size();\r
825 \r
826         // Find most and least active author names.\r
827         stdstring mostActiveAuthor;\r
828         stdstring leastActiveAuthor;\r
829         if (nAuthors > 0) \r
830         {\r
831                 mostActiveAuthor = m_authorNames.front();\r
832                 leastActiveAuthor = m_authorNames.back();\r
833         }\r
834 \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
840 \r
841         long nMostActiveMaxCommits = -1;\r
842         long nMostActiveMinCommits = -1;\r
843         long nLeastActiveMaxCommits = -1;\r
844         long nLeastActiveMinCommits = -1;\r
845 \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
849         {\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
855                 {\r
856                         commitCount += commit_it->second;\r
857                 }\r
858                 if (nCommitsMin == -1 || commitCount < nCommitsMin)\r
859                         nCommitsMin = commitCount;\r
860                 if (nCommitsMax == -1 || commitCount > nCommitsMax)\r
861                         nCommitsMax = commitCount;\r
862 \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
868                 {\r
869                         fileChangeCount += filechange_it->second;\r
870                 }\r
871                 if (nFileChangesMin == -1 || fileChangeCount < nFileChangesMin)\r
872                         nFileChangesMin = fileChangeCount;\r
873                 if (nFileChangesMax == -1 || fileChangeCount > nFileChangesMax)\r
874                         nFileChangesMax = fileChangeCount;\r
875 \r
876                 // also get min/max data for most and least active authors\r
877                 if (nAuthors > 0) \r
878                 {\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
883                                 authorCommits = 0;\r
884                         else\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
890 \r
891                         author_it = m_commitsPerUnitAndAuthor[i].find(leastActiveAuthor);\r
892                         if (author_it == m_commitsPerUnitAndAuthor[i].end())\r
893                                 authorCommits = 0;\r
894                         else\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
900                 }\r
901         }\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
906 \r
907         int nWeeks = m_nWeeks;\r
908         if (nWeeks == 0)\r
909                 nWeeks = 1;\r
910         // We have now all data we want and we can fill in the labels...\r
911         CString number;\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
920 \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
927 \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
934 \r
935         if (nAuthors == 0)\r
936         {\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
945         }\r
946         else\r
947         {\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
955 \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
963         }\r
964 }\r
965 \r
966 void CStatGraphDlg::OnCbnSelchangeGraphcombo()\r
967 {\r
968         UpdateData();\r
969         DWORD_PTR graphtype = m_cGraphType.GetItemData(m_cGraphType.GetCurSel());\r
970         switch (graphtype)\r
971         {\r
972         case 1:\r
973                 // labels\r
974                 // intended fall through\r
975         case 2:\r
976                 // by date\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
982                 break;\r
983         case 3:\r
984                 // by author\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
990                 break;\r
991         }\r
992         RedrawGraph();\r
993 }\r
994 \r
995 \r
996 int CStatGraphDlg::GetUnitCount()\r
997 {\r
998         if (m_nWeeks < 15)\r
999                 return m_nWeeks;\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
1005 }\r
1006 \r
1007 int CStatGraphDlg::GetUnit(const CTime& time)\r
1008 {\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
1016 }\r
1017 \r
1018 CStatGraphDlg::UnitType CStatGraphDlg::GetUnitType()\r
1019 {\r
1020         if (m_nWeeks < 15)\r
1021                 return Weeks;\r
1022         if (m_nWeeks < 80)\r
1023                 return Months;\r
1024         if (m_nWeeks < 320)\r
1025                 return Quarters;\r
1026         return Years;\r
1027 }\r
1028 \r
1029 CString CStatGraphDlg::GetUnitString()\r
1030 {\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
1038 }\r
1039 \r
1040 CString CStatGraphDlg::GetUnitLabel(int unit, CTime &lasttime)\r
1041 {\r
1042         CString temp;\r
1043         switch (GetUnitType())\r
1044         {\r
1045         case Weeks:\r
1046                 {\r
1047                         int year = lasttime.GetYear();\r
1048                         if ((unit == 1)&&(lasttime.GetMonth() == 12))\r
1049                                 year += 1;\r
1050 \r
1051                         switch (m_langOrder)\r
1052                         {\r
1053                         case 0: // month day year\r
1054                         case 1: // day month year\r
1055                         default:\r
1056                                 temp.Format(_T("%d/%.2d"), unit, year%100);\r
1057                                 break;\r
1058                         case 2: // year month day\r
1059                                 temp.Format(_T("%.2d/%d"), year%100, unit);\r
1060                                 break;\r
1061                         }\r
1062                 }\r
1063                 break;\r
1064         case Months:\r
1065                 switch (m_langOrder)\r
1066                 {\r
1067                 case 0: // month day year\r
1068                 case 1: // day month year\r
1069                 default:\r
1070                         temp.Format(_T("%d/%.2d"), unit, lasttime.GetYear()%100);\r
1071                         break;\r
1072                 case 2: // year month day\r
1073                         temp.Format(_T("%.2d/%d"), lasttime.GetYear()%100, unit);\r
1074                         break;\r
1075                 }\r
1076                 break;\r
1077         case Quarters:\r
1078                 switch (m_langOrder)\r
1079                 {\r
1080                 case 0: // month day year\r
1081                 case 1: // day month year\r
1082                 default:\r
1083                         temp.Format(_T("%d/%.2d"), unit, lasttime.GetYear()%100);\r
1084                         break;\r
1085                 case 2: // year month day\r
1086                         temp.Format(_T("%.2d/%d"), lasttime.GetYear()%100, unit);\r
1087                         break;\r
1088                 }\r
1089                 break;\r
1090         case Years:\r
1091                 temp.Format(_T("%d"), unit);\r
1092                 break;\r
1093         }\r
1094         return temp;\r
1095 }\r
1096 \r
1097 void CStatGraphDlg::OnHScroll(UINT nSBCode, UINT nPos, CScrollBar* pScrollBar)\r
1098 {\r
1099         if (nSBCode == TB_THUMBTRACK)\r
1100                 return CDialog::OnHScroll(nSBCode, nPos, pScrollBar);\r
1101 \r
1102         switch (m_cGraphType.GetItemData(m_cGraphType.GetCurSel()))\r
1103         {\r
1104         case 1:\r
1105                 ShowStats();\r
1106                 break;\r
1107         case 2:\r
1108                 ShowCommitsByDate();\r
1109                 break;\r
1110         case 3:\r
1111                 ShowCommitsByAuthor();\r
1112                 break;\r
1113         }\r
1114 \r
1115         CDialog::OnHScroll(nSBCode, nPos, pScrollBar);\r
1116 }\r
1117 \r
1118 void CStatGraphDlg::OnNeedText(NMHDR *pnmh, LRESULT * /*pResult*/)\r
1119 {\r
1120         TOOLTIPTEXT* pttt = (TOOLTIPTEXT*) pnmh;\r
1121         if (pttt->hdr.idFrom == (UINT) m_Skipper.GetSafeHwnd())\r
1122         {\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
1127 \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
1135 \r
1136                 CString string;\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
1140         }\r
1141 }\r
1142 \r
1143 void CStatGraphDlg::AuthorsCaseSensitiveChanged() \r
1144 {\r
1145         UpdateData();   // update checkbox state\r
1146         GatherData();   // first regenerate the statistics data\r
1147         RedrawGraph();  // then update the current statistics page\r
1148 }\r
1149 \r
1150 void CStatGraphDlg::SortModeChanged() \r
1151 {\r
1152         UpdateData();   // update checkbox state\r
1153         RedrawGraph();  // then update the current statistics page\r
1154 }\r
1155 \r
1156 void CStatGraphDlg::ClearGraph() \r
1157 {\r
1158         m_graph.Clear();\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
1162 }\r
1163 \r
1164 void CStatGraphDlg::RedrawGraph()\r
1165 {\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
1172 \r
1173         if ((m_GraphType == MyGraph::Bar)&&(m_bStacked))\r
1174         {\r
1175                 m_btnGraphBarStacked.SetState(BST_CHECKED);\r
1176         }\r
1177         if ((m_GraphType == MyGraph::Bar)&&(!m_bStacked))\r
1178         {\r
1179                 m_btnGraphBar.SetState(BST_CHECKED);\r
1180         }\r
1181         if ((m_GraphType == MyGraph::Line)&&(m_bStacked))\r
1182         {\r
1183                 m_btnGraphLineStacked.SetState(BST_CHECKED);\r
1184         }\r
1185         if ((m_GraphType == MyGraph::Line)&&(!m_bStacked))\r
1186         {\r
1187                 m_btnGraphLine.SetState(BST_CHECKED);\r
1188         }\r
1189         if (m_GraphType == MyGraph::PieChart)\r
1190         {\r
1191                 m_btnGraphPie.SetState(BST_CHECKED);\r
1192         }\r
1193 \r
1194 \r
1195         UpdateData();\r
1196         switch (m_cGraphType.GetItemData(m_cGraphType.GetCurSel()))\r
1197         {\r
1198         case 1:\r
1199                 ShowStats();\r
1200                 break;\r
1201         case 2:\r
1202                 ShowCommitsByDate();\r
1203                 break;\r
1204         case 3:\r
1205                 ShowCommitsByAuthor();\r
1206                 break;\r
1207         }\r
1208 }\r
1209 void CStatGraphDlg::OnBnClickedGraphbarbutton()\r
1210 {\r
1211         m_GraphType = MyGraph::Bar;\r
1212         m_bStacked = false;\r
1213         RedrawGraph();\r
1214 }\r
1215 \r
1216 void CStatGraphDlg::OnBnClickedGraphbarstackedbutton()\r
1217 {\r
1218         m_GraphType = MyGraph::Bar;\r
1219         m_bStacked = true;\r
1220         RedrawGraph();\r
1221 }\r
1222 \r
1223 void CStatGraphDlg::OnBnClickedGraphlinebutton()\r
1224 {\r
1225         m_GraphType = MyGraph::Line;\r
1226         m_bStacked = false;\r
1227         RedrawGraph();\r
1228 }\r
1229 \r
1230 void CStatGraphDlg::OnBnClickedGraphlinestackedbutton()\r
1231 {\r
1232         m_GraphType = MyGraph::Line;\r
1233         m_bStacked = true;\r
1234         RedrawGraph();\r
1235 }\r
1236 \r
1237 void CStatGraphDlg::OnBnClickedGraphpiebutton()\r
1238 {\r
1239         m_GraphType = MyGraph::PieChart;\r
1240         m_bStacked = false;\r
1241         RedrawGraph();\r
1242 }\r
1243 \r
1244 BOOL CStatGraphDlg::PreTranslateMessage(MSG* pMsg)\r
1245 {\r
1246         if (NULL != m_pToolTip)\r
1247                 m_pToolTip->RelayEvent(pMsg);\r
1248 \r
1249         return CStandAloneDialogTmpl<CResizableDialog>::PreTranslateMessage(pMsg);\r
1250 }\r
1251 \r
1252 void CStatGraphDlg::EnableDisableMenu()\r
1253 {\r
1254         UINT nEnable = MF_BYCOMMAND;\r
1255         if (m_cGraphType.GetItemData(m_cGraphType.GetCurSel()) == 1)\r
1256                 nEnable |= (MF_DISABLED | MF_GRAYED);\r
1257         else\r
1258                 nEnable |= MF_ENABLED;\r
1259         GetMenu()->EnableMenuItem(ID_FILE_SAVESTATGRAPHAS, nEnable);\r
1260 }\r
1261 \r
1262 void CStatGraphDlg::OnFileSavestatgraphas()\r
1263 {\r
1264         CString tempfile;\r
1265         int filterindex = 0;\r
1266         if (CAppUtils::FileOpenSave(tempfile, &filterindex, IDS_REVGRAPH_SAVEPIC, IDS_PICTUREFILEFILTER, false, m_hWnd))\r
1267         {\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
1278                 {\r
1279                         extension = _T(".wmf");\r
1280                         tempfile += extension;\r
1281                 }\r
1282                 SaveGraph(tempfile);\r
1283         }\r
1284 }\r
1285 \r
1286 void CStatGraphDlg::SaveGraph(CString sFilename)\r
1287 {\r
1288         CString extension = CPathUtils::GetFileExtFromPath(sFilename);\r
1289         if (extension.CompareNoCase(_T(".wmf"))==0)\r
1290         {\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
1295                 RedrawGraph();\r
1296                 m_graph.DrawGraph(wmfDC);\r
1297                 HENHMETAFILE hemf = wmfDC.CloseEnhanced();\r
1298                 DeleteEnhMetaFile(hemf);\r
1299         }\r
1300         else\r
1301         {\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
1308                 {\r
1309                         ATLTRACE("gdi plus found!");\r
1310                 }\r
1311                 else\r
1312                 {\r
1313                         ATLTRACE("gdi plus not found!");\r
1314                         CMessageBox::Show(m_hWnd, IDS_ERR_GDIPLUS_MISSING, IDS_APPNAME, MB_ICONERROR);\r
1315                         return;\r
1316                 }\r
1317 \r
1318                 // save the graph as a pixel picture instead of a vector picture\r
1319                 // create dc to paint on\r
1320                 try\r
1321                 {\r
1322                         CWindowDC ddc(this);\r
1323                         CDC dc;\r
1324                         if (!dc.CreateCompatibleDC(&ddc))\r
1325                         {\r
1326                                 LPVOID lpMsgBuf;\r
1327                                 if (!FormatMessage( \r
1328                                         FORMAT_MESSAGE_ALLOCATE_BUFFER | \r
1329                                         FORMAT_MESSAGE_FROM_SYSTEM | \r
1330                                         FORMAT_MESSAGE_IGNORE_INSERTS,\r
1331                                         NULL,\r
1332                                         GetLastError(),\r
1333                                         MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), // Default language\r
1334                                         (LPTSTR) &lpMsgBuf,\r
1335                                         0,\r
1336                                         NULL ))\r
1337                                 {\r
1338                                         return;\r
1339                                 }\r
1340                                 MessageBox( (LPCTSTR)lpMsgBuf, _T("Error"), MB_OK | MB_ICONINFORMATION );\r
1341                                 LocalFree( lpMsgBuf );\r
1342                                 return;\r
1343                         }\r
1344                         CRect rect;\r
1345                         GetDlgItem(IDC_GRAPH)->GetClientRect(&rect);\r
1346                         HBITMAP hbm = ::CreateCompatibleBitmap(ddc.m_hDC, rect.Width(), rect.Height());\r
1347                         if (hbm==0)\r
1348                         {\r
1349                                 LPVOID lpMsgBuf;\r
1350                                 if (!FormatMessage( \r
1351                                         FORMAT_MESSAGE_ALLOCATE_BUFFER | \r
1352                                         FORMAT_MESSAGE_FROM_SYSTEM | \r
1353                                         FORMAT_MESSAGE_IGNORE_INSERTS,\r
1354                                         NULL,\r
1355                                         GetLastError(),\r
1356                                         MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), // Default language\r
1357                                         (LPTSTR) &lpMsgBuf,\r
1358                                         0,\r
1359                                         NULL ))\r
1360                                 {\r
1361                                         return;\r
1362                                 }\r
1363                                 MessageBox( (LPCTSTR)lpMsgBuf, _T("Error"), MB_OK | MB_ICONINFORMATION );\r
1364                                 LocalFree( lpMsgBuf );\r
1365                                 return;\r
1366                         }\r
1367                         HBITMAP oldbm = (HBITMAP)dc.SelectObject(hbm);\r
1368                         // paint the whole graph\r
1369                         RedrawGraph();\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
1377                         {   \r
1378                                 {\r
1379                                         Bitmap bitmap(hbm, NULL);\r
1380                                         if (bitmap.GetLastStatus()==Ok)\r
1381                                         {\r
1382                                                 // Get the CLSID of the encoder.\r
1383                                                 int ret = 0;\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
1394                                                 else\r
1395                                                 {\r
1396                                                         sFilename += _T(".jpg");\r
1397                                                         ret = GetEncoderClsid(L"image/jpeg", &encoderClsid);\r
1398                                                 }\r
1399                                                 if (ret >= 0)\r
1400                                                 {\r
1401                                                         CStringW tfile = CStringW(sFilename);\r
1402                                                         bitmap.Save(tfile, &encoderClsid, NULL);\r
1403                                                 }\r
1404                                                 else\r
1405                                                 {\r
1406                                                         sErrormessage.Format(IDS_REVGRAPH_ERR_NOENCODER, CPathUtils::GetFileExtFromPath(sFilename));\r
1407                                                 }\r
1408                                         }\r
1409                                         else\r
1410                                         {\r
1411                                                 sErrormessage.LoadString(IDS_REVGRAPH_ERR_NOBITMAP);\r
1412                                         }\r
1413                                 }\r
1414                                 GdiplusShutdown(gdiplusToken);\r
1415                         }\r
1416                         else\r
1417                         {\r
1418                                 sErrormessage.LoadString(IDS_REVGRAPH_ERR_GDIINIT);\r
1419                         }\r
1420                         dc.SelectObject(oldbm);\r
1421                         dc.DeleteDC();\r
1422                         if (!sErrormessage.IsEmpty())\r
1423                         {\r
1424                                 CMessageBox::Show(m_hWnd, sErrormessage, _T("TortoiseSVN"), MB_ICONERROR);\r
1425                         }\r
1426                 }\r
1427                 catch (CException * pE)\r
1428                 {\r
1429                         TCHAR szErrorMsg[2048];\r
1430                         pE->GetErrorMessage(szErrorMsg, 2048);\r
1431                         CMessageBox::Show(m_hWnd, szErrorMsg, _T("TortoiseSVN"), MB_ICONERROR);\r
1432                 }\r
1433         }\r
1434 }\r
1435 \r
1436 int CStatGraphDlg::GetEncoderClsid(const WCHAR* format, CLSID* pClsid)\r
1437 {\r
1438         UINT  num = 0;          // number of image encoders\r
1439         UINT  size = 0;         // size of the image encoder array in bytes\r
1440 \r
1441         ImageCodecInfo* pImageCodecInfo = NULL;\r
1442 \r
1443         if (GetImageEncodersSize(&num, &size)!=Ok)\r
1444                 return -1;\r
1445         if (size == 0)\r
1446                 return -1;  // Failure\r
1447 \r
1448         pImageCodecInfo = (ImageCodecInfo*)(malloc(size));\r
1449         if (pImageCodecInfo == NULL)\r
1450                 return -1;  // Failure\r
1451 \r
1452         if (GetImageEncoders(num, size, pImageCodecInfo)==Ok)\r
1453         {\r
1454                 for (UINT j = 0; j < num; ++j)\r
1455                 {\r
1456                         if (wcscmp(pImageCodecInfo[j].MimeType, format) == 0)\r
1457                         {\r
1458                                 *pClsid = pImageCodecInfo[j].Clsid;\r
1459                                 free(pImageCodecInfo);\r
1460                                 return j;  // Success\r
1461                         }\r
1462                 }\r
1463         }\r
1464         free (pImageCodecInfo);\r
1465         return -1;  // Failure\r
1466 }\r
1467 \r
1468 void CStatGraphDlg::StoreCurrentGraphType()\r
1469 {\r
1470         UpdateData();\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
1475         {\r
1476                 statspage += 1;\r
1477         }\r
1478         if ((m_GraphType == MyGraph::Bar)&&(!m_bStacked))\r
1479         {\r
1480                 statspage += 2;\r
1481         }\r
1482         if ((m_GraphType == MyGraph::Line)&&(m_bStacked))\r
1483         {\r
1484                 statspage += 3;\r
1485         }\r
1486         if ((m_GraphType == MyGraph::Line)&&(!m_bStacked))\r
1487         {\r
1488                 statspage += 4;\r
1489         }\r
1490         if (m_GraphType == MyGraph::PieChart)\r
1491         {\r
1492                 statspage += 5;\r
1493         }\r
1494         \r
1495         // store current chart type in registry\r
1496         CRegDWORD lastStatsPage = CRegDWORD(_T("Software\\TortoiseGit\\LastViewedStatsPage"), 0);\r
1497         lastStatsPage = statspage;\r
1498 \r
1499         CRegDWORD regAuthors = CRegDWORD(_T("Software\\TortoiseGit\\StatAuthorsCaseSensitive"));\r
1500         regAuthors = m_bAuthorsCaseSensitive;\r
1501 \r
1502         CRegDWORD regSort = CRegDWORD(_T("Software\\TortoiseGit\\StatSortByCommitCount"));\r
1503         regSort = m_bSortByCommitCount;\r
1504 }\r