OSDN Git Service

42807fbdc1bf14d633d96b3708bf36900f93ac29
[tortoisegit/TortoiseGitJp.git] / TortoiseProc / CommitDlg.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 "CommitDlg.h"\r
22 #include "DirFileEnum.h"\r
23 #include "SVNConfig.h"\r
24 #include "SVNProperties.h"\r
25 #include "MessageBox.h"\r
26 #include "AppUtils.h"\r
27 #include "PathUtils.h"\r
28 #include "SVN.h"\r
29 #include "Registry.h"\r
30 #include "SVNStatus.h"\r
31 #include "HistoryDlg.h"\r
32 #include "Hooks.h"\r
33 \r
34 #ifdef _DEBUG\r
35 #define new DEBUG_NEW\r
36 #undef THIS_FILE\r
37 static char THIS_FILE[] = __FILE__;\r
38 #endif\r
39 \r
40 UINT CCommitDlg::WM_AUTOLISTREADY = RegisterWindowMessage(_T("TORTOISESVN_AUTOLISTREADY_MSG"));\r
41 \r
42 IMPLEMENT_DYNAMIC(CCommitDlg, CResizableStandAloneDialog)\r
43 CCommitDlg::CCommitDlg(CWnd* pParent /*=NULL*/)\r
44         : CResizableStandAloneDialog(CCommitDlg::IDD, pParent)\r
45         , m_bRecursive(FALSE)\r
46         , m_bShowUnversioned(FALSE)\r
47         , m_bBlock(FALSE)\r
48         , m_bThreadRunning(FALSE)\r
49         , m_bRunThread(FALSE)\r
50         , m_pThread(NULL)\r
51         , m_bKeepLocks(FALSE)\r
52         , m_bKeepChangeList(TRUE)\r
53         , m_itemsCount(0)\r
54         , m_bSelectFilesForCommit(TRUE)\r
55 {\r
56 }\r
57 \r
58 CCommitDlg::~CCommitDlg()\r
59 {\r
60         if(m_pThread != NULL)\r
61         {\r
62                 delete m_pThread;\r
63         }\r
64 }\r
65 \r
66 void CCommitDlg::DoDataExchange(CDataExchange* pDX)\r
67 {\r
68         CResizableStandAloneDialog::DoDataExchange(pDX);\r
69         DDX_Control(pDX, IDC_FILELIST, m_ListCtrl);\r
70         DDX_Control(pDX, IDC_LOGMESSAGE, m_cLogMessage);\r
71         DDX_Check(pDX, IDC_SHOWUNVERSIONED, m_bShowUnversioned);\r
72         DDX_Control(pDX, IDC_SELECTALL, m_SelectAll);\r
73         DDX_Text(pDX, IDC_BUGID, m_sBugID);\r
74         DDX_Check(pDX, IDC_KEEPLOCK, m_bKeepLocks);\r
75         DDX_Control(pDX, IDC_SPLITTER, m_wndSplitter);\r
76         DDX_Check(pDX, IDC_KEEPLISTS, m_bKeepChangeList);\r
77 }\r
78 \r
79 BEGIN_MESSAGE_MAP(CCommitDlg, CResizableStandAloneDialog)\r
80         ON_BN_CLICKED(IDC_SELECTALL, OnBnClickedSelectall)\r
81         ON_BN_CLICKED(IDHELP, OnBnClickedHelp)\r
82         ON_BN_CLICKED(IDC_SHOWUNVERSIONED, OnBnClickedShowunversioned)\r
83         ON_BN_CLICKED(IDC_HISTORY, OnBnClickedHistory)\r
84         ON_BN_CLICKED(IDC_BUGTRAQBUTTON, OnBnClickedBugtraqbutton)\r
85         ON_EN_CHANGE(IDC_LOGMESSAGE, OnEnChangeLogmessage)\r
86         ON_REGISTERED_MESSAGE(CSVNStatusListCtrl::SVNSLNM_ITEMCOUNTCHANGED, OnSVNStatusListCtrlItemCountChanged)\r
87         ON_REGISTERED_MESSAGE(CSVNStatusListCtrl::SVNSLNM_NEEDSREFRESH, OnSVNStatusListCtrlNeedsRefresh)\r
88         ON_REGISTERED_MESSAGE(CSVNStatusListCtrl::SVNSLNM_ADDFILE, OnFileDropped)\r
89         ON_REGISTERED_MESSAGE(CSVNStatusListCtrl::SVNSLNM_CHECKCHANGED, &CCommitDlg::OnSVNStatusListCtrlCheckChanged)\r
90         ON_REGISTERED_MESSAGE(WM_AUTOLISTREADY, OnAutoListReady) \r
91         ON_WM_TIMER()\r
92     ON_WM_SIZE()\r
93         ON_STN_CLICKED(IDC_EXTERNALWARNING, &CCommitDlg::OnStnClickedExternalwarning)\r
94 END_MESSAGE_MAP()\r
95 \r
96 BOOL CCommitDlg::OnInitDialog()\r
97 {\r
98         CResizableStandAloneDialog::OnInitDialog();\r
99         \r
100         m_regAddBeforeCommit = CRegDWORD(_T("Software\\TortoiseSVN\\AddBeforeCommit"), TRUE);\r
101         m_bShowUnversioned = m_regAddBeforeCommit;\r
102 \r
103         m_History.SetMaxHistoryItems((LONG)CRegDWORD(_T("Software\\TortoiseSVN\\MaxHistoryItems"), 25));\r
104 \r
105         m_regKeepChangelists = CRegDWORD(_T("Software\\TortoiseSVN\\KeepChangeLists"), FALSE);\r
106         m_bKeepChangeList = m_regKeepChangelists;\r
107 \r
108         SVNConfig config;\r
109         m_bKeepLocks = config.KeepLocks();\r
110 \r
111         UpdateData(FALSE);\r
112         \r
113         m_ListCtrl.Init(SVNSLC_COLEXT | SVNSLC_COLTEXTSTATUS | SVNSLC_COLPROPSTATUS | SVNSLC_COLLOCK, _T("CommitDlg"));\r
114         m_ListCtrl.SetSelectButton(&m_SelectAll);\r
115         m_ListCtrl.SetStatLabel(GetDlgItem(IDC_STATISTICS));\r
116         m_ListCtrl.SetCancelBool(&m_bCancelled);\r
117         m_ListCtrl.SetEmptyString(IDS_COMMITDLG_NOTHINGTOCOMMIT);\r
118         m_ListCtrl.EnableFileDrop();\r
119         m_ListCtrl.SetBackgroundImage(IDI_COMMIT_BKG);\r
120         \r
121         m_ProjectProperties.ReadPropsPathList(m_pathList);\r
122         m_cLogMessage.Init(m_ProjectProperties);\r
123         m_cLogMessage.SetFont((CString)CRegString(_T("Software\\TortoiseSVN\\LogFontName"), _T("Courier New")), (DWORD)CRegDWORD(_T("Software\\TortoiseSVN\\LogFontSize"), 8));\r
124         m_cLogMessage.RegisterContextMenuHandler(this);\r
125 \r
126         OnEnChangeLogmessage();\r
127 \r
128         m_tooltips.Create(this);\r
129         m_tooltips.AddTool(IDC_EXTERNALWARNING, IDS_COMMITDLG_EXTERNALS);\r
130         m_tooltips.AddTool(IDC_HISTORY, IDS_COMMITDLG_HISTORY_TT);\r
131         \r
132         m_SelectAll.SetCheck(BST_INDETERMINATE);\r
133         \r
134         CBugTraqAssociations bugtraq_associations;\r
135         bugtraq_associations.Load();\r
136 \r
137         if (bugtraq_associations.FindProvider(m_pathList, &m_bugtraq_association))\r
138         {\r
139                 GetDlgItem(IDC_BUGID)->ShowWindow(SW_HIDE);\r
140                 GetDlgItem(IDC_BUGIDLABEL)->ShowWindow(SW_HIDE);\r
141 \r
142                 CComPtr<IBugTraqProvider> pProvider;\r
143                 HRESULT hr = pProvider.CoCreateInstance(m_bugtraq_association.GetProviderClass());\r
144                 if (SUCCEEDED(hr))\r
145                 {\r
146                         m_BugTraqProvider = pProvider;\r
147                         BSTR temp = NULL;\r
148                         if (SUCCEEDED(hr = pProvider->GetLinkText(GetSafeHwnd(), m_bugtraq_association.GetParameters().AllocSysString(), &temp)))\r
149                         {\r
150                                 SetDlgItemText(IDC_BUGTRAQBUTTON, temp);\r
151                                 GetDlgItem(IDC_BUGTRAQBUTTON)->EnableWindow(TRUE);\r
152                                 GetDlgItem(IDC_BUGTRAQBUTTON)->ShowWindow(SW_SHOW);\r
153                         }\r
154 \r
155                         SysFreeString(temp);\r
156                 }\r
157 \r
158                 GetDlgItem(IDC_LOGMESSAGE)->SetFocus();\r
159         }\r
160         else if (!m_ProjectProperties.sMessage.IsEmpty())\r
161         {\r
162                 GetDlgItem(IDC_BUGID)->ShowWindow(SW_SHOW);\r
163                 GetDlgItem(IDC_BUGIDLABEL)->ShowWindow(SW_SHOW);\r
164                 if (!m_ProjectProperties.sLabel.IsEmpty())\r
165                         SetDlgItemText(IDC_BUGIDLABEL, m_ProjectProperties.sLabel);\r
166                 GetDlgItem(IDC_BUGTRAQBUTTON)->ShowWindow(SW_HIDE);\r
167                 GetDlgItem(IDC_BUGTRAQBUTTON)->EnableWindow(FALSE);\r
168                 GetDlgItem(IDC_BUGID)->SetFocus();\r
169                 CString sBugID = m_ProjectProperties.GetBugIDFromLog(m_sLogMessage);\r
170                 if (!sBugID.IsEmpty())\r
171                 {\r
172                         SetDlgItemText(IDC_BUGID, sBugID);\r
173                 }\r
174         }\r
175         else\r
176         {\r
177                 GetDlgItem(IDC_BUGID)->ShowWindow(SW_HIDE);\r
178                 GetDlgItem(IDC_BUGIDLABEL)->ShowWindow(SW_HIDE);\r
179                 GetDlgItem(IDC_BUGTRAQBUTTON)->ShowWindow(SW_HIDE);\r
180                 GetDlgItem(IDC_BUGTRAQBUTTON)->EnableWindow(FALSE);\r
181                 GetDlgItem(IDC_LOGMESSAGE)->SetFocus();\r
182         }\r
183 \r
184         if (!m_sLogMessage.IsEmpty())\r
185                 m_cLogMessage.SetText(m_sLogMessage);\r
186                 \r
187         GetWindowText(m_sWindowTitle);\r
188         \r
189         AdjustControlSize(IDC_SHOWUNVERSIONED);\r
190         AdjustControlSize(IDC_SELECTALL);\r
191         AdjustControlSize(IDC_KEEPLOCK);\r
192 \r
193         GetClientRect(m_DlgOrigRect);\r
194         m_cLogMessage.GetClientRect(m_LogMsgOrigRect);\r
195 \r
196         AddAnchor(IDC_COMMITLABEL, TOP_LEFT, TOP_RIGHT);\r
197         AddAnchor(IDC_BUGIDLABEL, TOP_RIGHT);\r
198         AddAnchor(IDC_BUGID, TOP_RIGHT);\r
199         AddAnchor(IDC_BUGTRAQBUTTON, TOP_RIGHT);\r
200         AddAnchor(IDC_COMMIT_TO, TOP_LEFT, TOP_RIGHT);\r
201         AddAnchor(IDC_MESSAGEGROUP, TOP_LEFT, TOP_RIGHT);\r
202         AddAnchor(IDC_HISTORY, TOP_LEFT);\r
203         AddAnchor(IDC_LOGMESSAGE, TOP_LEFT, TOP_RIGHT);\r
204         \r
205         AddAnchor(IDC_LISTGROUP, TOP_LEFT, BOTTOM_RIGHT);\r
206         AddAnchor(IDC_SPLITTER, TOP_LEFT, TOP_RIGHT);\r
207         AddAnchor(IDC_FILELIST, TOP_LEFT, BOTTOM_RIGHT);\r
208         AddAnchor(IDC_SHOWUNVERSIONED, BOTTOM_LEFT);\r
209         AddAnchor(IDC_SELECTALL, BOTTOM_LEFT);\r
210         AddAnchor(IDC_EXTERNALWARNING, BOTTOM_RIGHT);\r
211         AddAnchor(IDC_STATISTICS, BOTTOM_LEFT, BOTTOM_RIGHT);\r
212         AddAnchor(IDC_KEEPLOCK, BOTTOM_LEFT);\r
213         AddAnchor(IDC_KEEPLISTS, BOTTOM_LEFT);\r
214         AddAnchor(IDOK, BOTTOM_RIGHT);\r
215         AddAnchor(IDCANCEL, BOTTOM_RIGHT);\r
216         AddAnchor(IDHELP, BOTTOM_RIGHT);\r
217         if (hWndExplorer)\r
218                 CenterWindow(CWnd::FromHandle(hWndExplorer));\r
219         EnableSaveRestore(_T("CommitDlg"));\r
220         DWORD yPos = CRegDWORD(_T("Software\\TortoiseSVN\\TortoiseProc\\ResizableState\\CommitDlgSizer"));\r
221         RECT rcDlg, rcLogMsg, rcFileList;\r
222         GetClientRect(&rcDlg);\r
223         m_cLogMessage.GetWindowRect(&rcLogMsg);\r
224         ScreenToClient(&rcLogMsg);\r
225         m_ListCtrl.GetWindowRect(&rcFileList);\r
226         ScreenToClient(&rcFileList);\r
227         if (yPos)\r
228         {\r
229                 RECT rectSplitter;\r
230                 m_wndSplitter.GetWindowRect(&rectSplitter);\r
231                 ScreenToClient(&rectSplitter);\r
232                 int delta = yPos - rectSplitter.top;\r
233                 if ((rcLogMsg.bottom + delta > rcLogMsg.top)&&(rcLogMsg.bottom + delta < rcFileList.bottom - 30))\r
234                 {\r
235                         m_wndSplitter.SetWindowPos(NULL, 0, yPos, 0, 0, SWP_NOSIZE);\r
236                         DoSize(delta);\r
237                 }\r
238         }\r
239 \r
240         // add all directories to the watcher\r
241         for (int i=0; i<m_pathList.GetCount(); ++i)\r
242         {\r
243                 if (m_pathList[i].IsDirectory())\r
244                         m_pathwatcher.AddPath(m_pathList[i]);\r
245         }\r
246 \r
247         m_updatedPathList = m_pathList;\r
248 \r
249         //first start a thread to obtain the file list with the status without\r
250         //blocking the dialog\r
251         InterlockedExchange(&m_bBlock, TRUE);\r
252         m_pThread = AfxBeginThread(StatusThreadEntry, this, THREAD_PRIORITY_NORMAL,0,CREATE_SUSPENDED);\r
253         if (m_pThread==NULL)\r
254         {\r
255                 CMessageBox::Show(this->m_hWnd, IDS_ERR_THREADSTARTFAILED, IDS_APPNAME, MB_OK | MB_ICONERROR);\r
256                 InterlockedExchange(&m_bBlock, FALSE);\r
257         }\r
258         else\r
259         {\r
260                 m_pThread->m_bAutoDelete = FALSE;\r
261                 m_pThread->ResumeThread();\r
262         }\r
263         CRegDWORD err = CRegDWORD(_T("Software\\TortoiseSVN\\ErrorOccurred"), FALSE);\r
264         CRegDWORD historyhint = CRegDWORD(_T("Software\\TortoiseSVN\\HistoryHintShown"), FALSE);\r
265         if ((((DWORD)err)!=FALSE)&&((((DWORD)historyhint)==FALSE)))\r
266         {\r
267                 historyhint = TRUE;\r
268                 ShowBalloon(IDC_HISTORY, IDS_COMMITDLG_HISTORYHINT_TT, IDI_INFORMATION);\r
269         }\r
270         err = FALSE;\r
271 \r
272 \r
273         return FALSE;  // return TRUE unless you set the focus to a control\r
274         // EXCEPTION: OCX Property Pages should return FALSE\r
275 }\r
276 \r
277 void CCommitDlg::OnOK()\r
278 {\r
279         if (m_bBlock)\r
280                 return;\r
281         if (m_bThreadRunning)\r
282         {\r
283                 m_bCancelled = true;\r
284                 InterlockedExchange(&m_bRunThread, FALSE);\r
285                 WaitForSingleObject(m_pThread->m_hThread, 1000);\r
286                 if (m_bThreadRunning)\r
287                 {\r
288                         // we gave the thread a chance to quit. Since the thread didn't\r
289                         // listen to us we have to kill it.\r
290                         TerminateThread(m_pThread->m_hThread, (DWORD)-1);\r
291                         InterlockedExchange(&m_bThreadRunning, FALSE);\r
292                 }\r
293         }\r
294         CString id;\r
295         GetDlgItemText(IDC_BUGID, id);\r
296         if (!m_ProjectProperties.CheckBugID(id))\r
297         {\r
298                 ShowBalloon(IDC_BUGID, IDS_COMMITDLG_ONLYNUMBERS, IDI_EXCLAMATION);\r
299                 return;\r
300         }\r
301         m_sLogMessage = m_cLogMessage.GetText();\r
302         if ((m_ProjectProperties.bWarnIfNoIssue) && (id.IsEmpty() && !m_ProjectProperties.HasBugID(m_sLogMessage)))\r
303         {\r
304                 if (CMessageBox::Show(this->m_hWnd, IDS_COMMITDLG_NOISSUEWARNING, IDS_APPNAME, MB_YESNO | MB_ICONWARNING)!=IDYES)\r
305                         return;\r
306         }\r
307 \r
308         CRegDWORD regUnversionedRecurse (_T("Software\\TortoiseSVN\\UnversionedRecurse"), TRUE);\r
309         if (!(DWORD)regUnversionedRecurse)\r
310         {\r
311                 // Find unversioned directories which are marked for commit. The user might expect them\r
312                 // to be added recursively since he cannot the the files. Let's ask the user if he knows\r
313                 // what he is doing.\r
314                 int nListItems = m_ListCtrl.GetItemCount();\r
315                 for (int j=0; j<nListItems; j++)\r
316                 {\r
317                         const CSVNStatusListCtrl::FileEntry * entry = m_ListCtrl.GetListEntry(j);\r
318                         if (entry->IsChecked() && (entry->status == svn_wc_status_unversioned) && entry->IsFolder() )\r
319                         {\r
320                                 if (CMessageBox::Show(this->m_hWnd, IDS_COMMITDLG_UNVERSIONEDFOLDERWARNING, IDS_APPNAME, MB_YESNO | MB_ICONWARNING)!=IDYES)\r
321                                         return;\r
322                         }\r
323                 }\r
324         }\r
325 \r
326         m_pathwatcher.Stop();\r
327         InterlockedExchange(&m_bBlock, TRUE);\r
328         CDWordArray arDeleted;\r
329         //first add all the unversioned files the user selected\r
330         //and check if all versioned files are selected\r
331         int nUnchecked = 0;\r
332         m_bRecursive = true;\r
333         int nListItems = m_ListCtrl.GetItemCount();\r
334 \r
335         CTSVNPathList itemsToAdd;\r
336         CTSVNPathList itemsToRemove;\r
337         bool bCheckedInExternal = false;\r
338         bool bHasConflicted = false;\r
339         std::set<CString> checkedLists;\r
340         std::set<CString> uncheckedLists;\r
341         for (int j=0; j<nListItems; j++)\r
342         {\r
343                 const CSVNStatusListCtrl::FileEntry * entry = m_ListCtrl.GetListEntry(j);\r
344                 if (entry->IsChecked())\r
345                 {\r
346                         if (entry->status == svn_wc_status_unversioned)\r
347                         {\r
348                                 itemsToAdd.AddPath(entry->GetPath());\r
349                         }\r
350                         if (entry->status == svn_wc_status_conflicted)\r
351                         {\r
352                                 bHasConflicted = true;\r
353                         }\r
354                         if (entry->status == svn_wc_status_missing)\r
355                         {\r
356                                 itemsToRemove.AddPath(entry->GetPath());\r
357                         }\r
358                         if (entry->status == svn_wc_status_deleted)\r
359                         {\r
360                                 arDeleted.Add(j);\r
361                         }\r
362                         if (entry->IsInExternal())\r
363                         {\r
364                                 bCheckedInExternal = true;\r
365                         }\r
366                         checkedLists.insert(entry->GetChangeList());\r
367                 }\r
368                 else\r
369                 {\r
370                         if ((entry->status != svn_wc_status_unversioned)        &&\r
371                                 (entry->status != svn_wc_status_ignored))\r
372                         {\r
373                                 nUnchecked++;\r
374                                 uncheckedLists.insert(entry->GetChangeList());\r
375                                 if (m_bRecursive)\r
376                                 {\r
377                                         // This algorithm is for the sake of simplicity of the complexity O(N²)\r
378                                         for (int k=0; k<nListItems; k++)\r
379                                         {\r
380                                                 const CSVNStatusListCtrl::FileEntry * entryK = m_ListCtrl.GetListEntry(k);\r
381                                                 if (entryK->IsChecked() && entryK->GetPath().IsAncestorOf(entry->GetPath())  )\r
382                                                 {\r
383                                                         // Fall back to a non-recursive commit to prevent items being\r
384                                                         // committed which aren't checked although its parent is checked\r
385                                                         // (property change, directory deletion, ... )\r
386                                                         m_bRecursive = false;\r
387                                                         break;\r
388                                                 }\r
389                                         }\r
390                                 }\r
391                         }\r
392                 }\r
393         }\r
394         if (m_pathwatcher.GetNumberOfChangedPaths() && m_bRecursive)\r
395         {\r
396                 // There are paths which got changed (touched at least).\r
397                 // We have to find out if this affects the selection in the commit dialog\r
398                 // If it could affect the selection, revert back to a non-recursive commit\r
399                 CTSVNPathList changedList = m_pathwatcher.GetChangedPaths();\r
400                 changedList.RemoveDuplicates();\r
401                 for (int i=0; i<changedList.GetCount(); ++i)\r
402                 {\r
403                         if (changedList[i].IsAdminDir())\r
404                         {\r
405                                 // something inside an admin dir was changed.\r
406                                 // if it's the entries file, then we have to fully refresh because\r
407                                 // files may have been added/removed from version control\r
408                                 if ((changedList[i].GetWinPathString().Right(7).CompareNoCase(_T("entries")) == 0) &&\r
409                                         (changedList[i].GetWinPathString().Find(_T("\\tmp\\"))<0))\r
410                                 {\r
411                                         m_bRecursive = false;\r
412                                         break;\r
413                                 }\r
414                         }\r
415                         else if (!m_ListCtrl.IsPathShown(changedList[i]))\r
416                         {\r
417                                 // a path which is not shown in the list has changed\r
418                                 CSVNStatusListCtrl::FileEntry * entry = m_ListCtrl.GetListEntry(changedList[i]);\r
419                                 if (entry)\r
420                                 {\r
421                                         // check if the changed path would get committed by a recursive commit\r
422                                         if ((!entry->IsFromDifferentRepository()) &&\r
423                                                 (!entry->IsInExternal()) &&\r
424                                                 (!entry->IsNested()) && \r
425                                                 (!entry->IsChecked()))\r
426                                         {\r
427                                                 m_bRecursive = false;\r
428                                                 break;\r
429                                         }\r
430                                 }\r
431                         }\r
432                 }\r
433         }\r
434 \r
435 \r
436         // Now, do all the adds - make sure that the list is sorted so that parents \r
437         // are added before their children\r
438         itemsToAdd.SortByPathname();\r
439         SVN svn;\r
440         if (!svn.Add(itemsToAdd, &m_ProjectProperties, svn_depth_empty, FALSE, FALSE, TRUE))\r
441         {\r
442                 CMessageBox::Show(m_hWnd, svn.GetLastErrorMessage(), _T("TortoiseSVN"), MB_ICONERROR);\r
443                 InterlockedExchange(&m_bBlock, FALSE);\r
444                 Refresh();\r
445                 return;\r
446         }\r
447 \r
448         // Remove any missing items\r
449         // Not sure that this sort is really necessary - indeed, it might be better to do a reverse sort at this point\r
450         itemsToRemove.SortByPathname();\r
451         svn.Remove(itemsToRemove, TRUE);\r
452 \r
453         //the next step: find all deleted files and check if they're \r
454         //inside a deleted folder. If that's the case, then remove those\r
455         //files from the list since they'll get deleted by the parent\r
456         //folder automatically.\r
457         m_ListCtrl.Block(TRUE, FALSE);\r
458         INT_PTR nDeleted = arDeleted.GetCount();\r
459         for (INT_PTR i=0; i<arDeleted.GetCount(); i++)\r
460         {\r
461                 if (m_ListCtrl.GetCheck(arDeleted.GetAt(i)))\r
462                 {\r
463                         const CTSVNPath& path = m_ListCtrl.GetListEntry(arDeleted.GetAt(i))->GetPath();\r
464                         if (path.IsDirectory())\r
465                         {\r
466                                 //now find all children of this directory\r
467                                 for (int j=0; j<arDeleted.GetCount(); j++)\r
468                                 {\r
469                                         if (i!=j)\r
470                                         {\r
471                                                 CSVNStatusListCtrl::FileEntry* childEntry = m_ListCtrl.GetListEntry(arDeleted.GetAt(j));\r
472                                                 if (childEntry->IsChecked())\r
473                                                 {\r
474                                                         if (path.IsAncestorOf(childEntry->GetPath()))\r
475                                                         {\r
476                                                                 m_ListCtrl.SetEntryCheck(childEntry, arDeleted.GetAt(j), false);\r
477                                                                 nDeleted--;\r
478                                                         }\r
479                                                 }\r
480                                         }\r
481                                 }\r
482                         }\r
483                 }\r
484         } \r
485         m_ListCtrl.Block(FALSE, FALSE);\r
486 \r
487         if ((nUnchecked != 0)||(bCheckedInExternal)||(bHasConflicted)||(!m_bRecursive))\r
488         {\r
489                 //save only the files the user has checked into the temporary file\r
490                 m_ListCtrl.WriteCheckedNamesToPathList(m_pathList);\r
491         }\r
492         m_ListCtrl.WriteCheckedNamesToPathList(m_selectedPathList);\r
493         // the item count is used in the progress dialog to show the overall commit\r
494         // progress.\r
495         // deleted items only send one notification event, all others send two\r
496         m_itemsCount = ((m_selectedPathList.GetCount() - nDeleted - itemsToRemove.GetCount()) * 2) + nDeleted + itemsToRemove.GetCount();\r
497 \r
498         if ((m_bRecursive)&&(checkedLists.size() == 1))\r
499         {\r
500                 // all checked items belong to the same changelist\r
501                 // find out if there are any unchecked items which belong to that changelist\r
502                 if (uncheckedLists.find(*checkedLists.begin()) == uncheckedLists.end())\r
503                         m_sChangeList = *checkedLists.begin();\r
504         }\r
505         UpdateData();\r
506         m_regAddBeforeCommit = m_bShowUnversioned;\r
507         if (!GetDlgItem(IDC_KEEPLOCK)->IsWindowEnabled())\r
508                 m_bKeepLocks = FALSE;\r
509         m_regKeepChangelists = m_bKeepChangeList;\r
510         if (!GetDlgItem(IDC_KEEPLISTS)->IsWindowEnabled())\r
511                 m_bKeepChangeList = FALSE;\r
512         InterlockedExchange(&m_bBlock, FALSE);\r
513         m_sBugID.Trim();\r
514         if (!m_sBugID.IsEmpty())\r
515         {\r
516                 m_sBugID.Replace(_T(", "), _T(","));\r
517                 m_sBugID.Replace(_T(" ,"), _T(","));\r
518                 CString sBugID = m_ProjectProperties.sMessage;\r
519                 sBugID.Replace(_T("%BUGID%"), m_sBugID);\r
520                 if (m_ProjectProperties.bAppend)\r
521                         m_sLogMessage += _T("\n") + sBugID + _T("\n");\r
522                 else\r
523                         m_sLogMessage = sBugID + _T("\n") + m_sLogMessage;\r
524         }\r
525         m_History.AddEntry(m_sLogMessage);\r
526         m_History.Save();\r
527 \r
528         SaveSplitterPos();\r
529 \r
530         CResizableStandAloneDialog::OnOK();\r
531 }\r
532 \r
533 void CCommitDlg::SaveSplitterPos()\r
534 {\r
535         if (!IsIconic())\r
536         {\r
537                 CRegDWORD regPos = CRegDWORD(_T("Software\\TortoiseSVN\\TortoiseProc\\ResizableState\\CommitDlgSizer"));\r
538                 RECT rectSplitter;\r
539                 m_wndSplitter.GetWindowRect(&rectSplitter);\r
540                 ScreenToClient(&rectSplitter);\r
541                 regPos = rectSplitter.top;\r
542         }\r
543 }\r
544 \r
545 UINT CCommitDlg::StatusThreadEntry(LPVOID pVoid)\r
546 {\r
547         return ((CCommitDlg*)pVoid)->StatusThread();\r
548 }\r
549 \r
550 UINT CCommitDlg::StatusThread()\r
551 {\r
552         //get the status of all selected file/folders recursively\r
553         //and show the ones which have to be committed to the user\r
554         //in a list control. \r
555         InterlockedExchange(&m_bBlock, TRUE);\r
556         InterlockedExchange(&m_bThreadRunning, TRUE);// so the main thread knows that this thread is still running\r
557         InterlockedExchange(&m_bRunThread, TRUE);       // if this is set to FALSE, the thread should stop\r
558         m_bCancelled = false;\r
559 \r
560         DialogEnableWindow(IDOK, false);\r
561         DialogEnableWindow(IDC_SHOWUNVERSIONED, false);\r
562         DialogEnableWindow(IDC_SELECTALL, false);\r
563         GetDlgItem(IDC_EXTERNALWARNING)->ShowWindow(SW_HIDE);\r
564         DialogEnableWindow(IDC_EXTERNALWARNING, false);\r
565 \r
566     // read the list of recent log entries before querying the WC for status\r
567     // -> the user may select one and modify / update it while we are crawling the WC\r
568         if (m_History.GetCount()==0)\r
569         {\r
570                 CString reg;\r
571                 if (m_ListCtrl.m_sUUID.IsEmpty() && m_pathList.GetCount()>0)\r
572                 {\r
573                         SVN svn;\r
574                         reg.Format(_T("Software\\TortoiseSVN\\History\\commit%s"), (LPCTSTR)svn.GetUUIDFromPath(m_pathList[0]));\r
575                 }\r
576                 else\r
577                         reg.Format(_T("Software\\TortoiseSVN\\History\\commit%s"), (LPCTSTR)m_ListCtrl.m_sUUID);\r
578                 m_History.Load(reg, _T("logmsgs"));\r
579         }\r
580 \r
581     // Initialise the list control with the status of the files/folders below us\r
582         BOOL success = m_ListCtrl.GetStatus(m_pathList);\r
583         m_ListCtrl.CheckIfChangelistsArePresent(false);\r
584 \r
585         DWORD dwShow = SVNSLC_SHOWVERSIONEDBUTNORMALANDEXTERNALSFROMDIFFERENTREPOS | SVNSLC_SHOWLOCKS | SVNSLC_SHOWINCHANGELIST;\r
586         dwShow |= DWORD(m_regAddBeforeCommit) ? SVNSLC_SHOWUNVERSIONED : 0;\r
587         if (success)\r
588         {\r
589                 if (m_checkedPathList.GetCount())\r
590                         m_ListCtrl.Show(dwShow, m_checkedPathList);\r
591                 else\r
592                 {\r
593                         DWORD dwCheck = m_bSelectFilesForCommit ? SVNSLC_SHOWDIRECTS|SVNSLC_SHOWMODIFIED|SVNSLC_SHOWADDED|SVNSLC_SHOWREMOVED|SVNSLC_SHOWREPLACED|SVNSLC_SHOWMERGED|SVNSLC_SHOWLOCKS : 0;\r
594                         m_ListCtrl.Show(dwShow, dwCheck);\r
595                         m_bSelectFilesForCommit = true;\r
596                 }\r
597 \r
598                 if (m_ListCtrl.HasExternalsFromDifferentRepos())\r
599                 {\r
600                         GetDlgItem(IDC_EXTERNALWARNING)->ShowWindow(SW_SHOW);\r
601                         DialogEnableWindow(IDC_EXTERNALWARNING, TRUE);\r
602                 }\r
603                 SetDlgItemText(IDC_COMMIT_TO, m_ListCtrl.m_sURL);\r
604                 m_tooltips.AddTool(GetDlgItem(IDC_STATISTICS), m_ListCtrl.GetStatisticsString());\r
605         }\r
606         CString logmsg;\r
607         GetDlgItemText(IDC_LOGMESSAGE, logmsg);\r
608         DialogEnableWindow(IDOK, logmsg.GetLength() >= m_ProjectProperties.nMinLogSize);\r
609         if (!success)\r
610         {\r
611                 if (!m_ListCtrl.GetLastErrorMessage().IsEmpty())\r
612                         m_ListCtrl.SetEmptyString(m_ListCtrl.GetLastErrorMessage());\r
613                 m_ListCtrl.Show(dwShow);\r
614         }\r
615         if ((m_ListCtrl.GetItemCount()==0)&&(m_ListCtrl.HasUnversionedItems()))\r
616         {\r
617                 if (CMessageBox::Show(m_hWnd, IDS_COMMITDLG_NOTHINGTOCOMMITUNVERSIONED, IDS_APPNAME, MB_ICONINFORMATION | MB_YESNO)==IDYES)\r
618                 {\r
619                         m_bShowUnversioned = TRUE;\r
620                         GetDlgItem(IDC_SHOWUNVERSIONED)->SendMessage(BM_SETCHECK, BST_CHECKED);\r
621                         DWORD dwShow = SVNSLC_SHOWVERSIONEDBUTNORMALANDEXTERNALSFROMDIFFERENTREPOS | SVNSLC_SHOWUNVERSIONED | SVNSLC_SHOWLOCKS;\r
622                         m_ListCtrl.Show(dwShow);\r
623                 }\r
624         }\r
625 \r
626         CTSVNPath commonDir = m_ListCtrl.GetCommonDirectory(false);\r
627         SetWindowText(m_sWindowTitle + _T(" - ") + commonDir.GetWinPathString());\r
628 \r
629         m_autolist.clear();\r
630         // we don't have to block the commit dialog while we fetch the\r
631         // auto completion list.\r
632         m_pathwatcher.ClearChangedPaths();\r
633         InterlockedExchange(&m_bBlock, FALSE);\r
634         if ((DWORD)CRegDWORD(_T("Software\\TortoiseSVN\\Autocompletion"), TRUE)==TRUE)\r
635         {\r
636                 m_ListCtrl.Block(TRUE, TRUE);\r
637                 GetAutocompletionList();\r
638                 m_ListCtrl.Block(FALSE, FALSE);\r
639         }\r
640         if (m_bRunThread)\r
641         {\r
642                 DialogEnableWindow(IDC_SHOWUNVERSIONED, true);\r
643                 DialogEnableWindow(IDC_SELECTALL, true);\r
644                 if (m_ListCtrl.HasChangeLists())\r
645                         DialogEnableWindow(IDC_KEEPLISTS, true);\r
646                 if (m_ListCtrl.HasLocks())\r
647                         DialogEnableWindow(IDC_KEEPLOCK, true);\r
648                 // we have the list, now signal the main thread about it\r
649                 SendMessage(WM_AUTOLISTREADY);  // only send the message if the thread wasn't told to quit!\r
650         }\r
651         InterlockedExchange(&m_bRunThread, FALSE);\r
652         InterlockedExchange(&m_bThreadRunning, FALSE);\r
653         // force the cursor to normal\r
654         RefreshCursor();\r
655         return 0;\r
656 }\r
657 \r
658 void CCommitDlg::OnCancel()\r
659 {\r
660         m_bCancelled = true;\r
661         if (m_bBlock)\r
662                 return;\r
663         m_pathwatcher.Stop();\r
664         if (m_bThreadRunning)\r
665         {\r
666                 InterlockedExchange(&m_bRunThread, FALSE);\r
667                 WaitForSingleObject(m_pThread->m_hThread, 1000);\r
668                 if (m_bThreadRunning)\r
669                 {\r
670                         // we gave the thread a chance to quit. Since the thread didn't\r
671                         // listen to us we have to kill it.\r
672                         TerminateThread(m_pThread->m_hThread, (DWORD)-1);\r
673                         InterlockedExchange(&m_bThreadRunning, FALSE);\r
674                 }\r
675         }\r
676         UpdateData();\r
677         m_sBugID.Trim();\r
678         m_sLogMessage = m_cLogMessage.GetText();\r
679         if (!m_sBugID.IsEmpty())\r
680         {\r
681                 m_sBugID.Replace(_T(", "), _T(","));\r
682                 m_sBugID.Replace(_T(" ,"), _T(","));\r
683                 CString sBugID = m_ProjectProperties.sMessage;\r
684                 sBugID.Replace(_T("%BUGID%"), m_sBugID);\r
685                 if (m_ProjectProperties.bAppend)\r
686                         m_sLogMessage += _T("\n") + sBugID + _T("\n");\r
687                 else\r
688                         m_sLogMessage = sBugID + _T("\n") + m_sLogMessage;\r
689         }\r
690         if (m_ProjectProperties.sLogTemplate.Compare(m_sLogMessage) != 0)\r
691                 m_History.AddEntry(m_sLogMessage);\r
692         m_History.Save();\r
693         SaveSplitterPos();\r
694         CResizableStandAloneDialog::OnCancel();\r
695 }\r
696 \r
697 void CCommitDlg::OnBnClickedSelectall()\r
698 {\r
699         m_tooltips.Pop();       // hide the tooltips\r
700         UINT state = (m_SelectAll.GetState() & 0x0003);\r
701         if (state == BST_INDETERMINATE)\r
702         {\r
703                 // It is not at all useful to manually place the checkbox into the indeterminate state...\r
704                 // We will force this on to the unchecked state\r
705                 state = BST_UNCHECKED;\r
706                 m_SelectAll.SetCheck(state);\r
707         }\r
708         m_ListCtrl.SelectAll(state == BST_CHECKED);\r
709 }\r
710 \r
711 BOOL CCommitDlg::PreTranslateMessage(MSG* pMsg)\r
712 {\r
713         if (!m_bBlock)\r
714                 m_tooltips.RelayEvent(pMsg);\r
715         if (pMsg->message == WM_KEYDOWN)\r
716         {\r
717                 switch (pMsg->wParam)\r
718                 {\r
719                 case VK_F5:\r
720                         {\r
721                                 if (m_bBlock)\r
722                                         return CResizableStandAloneDialog::PreTranslateMessage(pMsg);\r
723                                 Refresh();\r
724                         }\r
725                         break;\r
726                 case VK_RETURN:\r
727                         {\r
728                                 if (GetAsyncKeyState(VK_CONTROL)&0x8000)\r
729                                 {\r
730                                         if ( GetDlgItem(IDOK)->IsWindowEnabled() )\r
731                                         {\r
732                                                 PostMessage(WM_COMMAND, IDOK);\r
733                                         }\r
734                                         return TRUE;\r
735                                 }\r
736                                 if ( GetFocus()==GetDlgItem(IDC_BUGID) )\r
737                                 {\r
738                                         // Pressing RETURN in the bug id control\r
739                                         // moves the focus to the message\r
740                                         GetDlgItem(IDC_LOGMESSAGE)->SetFocus();\r
741                                         return TRUE;\r
742                                 }\r
743                         }\r
744                         break;\r
745                 }\r
746         }\r
747 \r
748         return CResizableStandAloneDialog::PreTranslateMessage(pMsg);\r
749 }\r
750 \r
751 void CCommitDlg::Refresh()\r
752 {\r
753         if (m_bThreadRunning)\r
754                 return;\r
755 \r
756         InterlockedExchange(&m_bBlock, TRUE);\r
757         m_pThread = AfxBeginThread(StatusThreadEntry, this, THREAD_PRIORITY_NORMAL,0,CREATE_SUSPENDED);\r
758         if (m_pThread==NULL)\r
759         {\r
760                 CMessageBox::Show(this->m_hWnd, IDS_ERR_THREADSTARTFAILED, IDS_APPNAME, MB_OK | MB_ICONERROR);\r
761                 InterlockedExchange(&m_bBlock, FALSE);\r
762         }\r
763         else\r
764         {\r
765                 m_pThread->m_bAutoDelete = FALSE;\r
766                 m_pThread->ResumeThread();\r
767         }\r
768 }\r
769 \r
770 void CCommitDlg::OnBnClickedHelp()\r
771 {\r
772         OnHelp();\r
773 }\r
774 \r
775 void CCommitDlg::OnBnClickedShowunversioned()\r
776 {\r
777         m_tooltips.Pop();       // hide the tooltips\r
778         UpdateData();\r
779         m_regAddBeforeCommit = m_bShowUnversioned;\r
780         if (!m_bBlock)\r
781         {\r
782                 DWORD dwShow = m_ListCtrl.GetShowFlags();\r
783                 if (DWORD(m_regAddBeforeCommit))\r
784                         dwShow |= SVNSLC_SHOWUNVERSIONED;\r
785                 else\r
786                         dwShow &= ~SVNSLC_SHOWUNVERSIONED;\r
787                 m_ListCtrl.Show(dwShow);\r
788         }\r
789 }\r
790 \r
791 void CCommitDlg::OnStnClickedExternalwarning()\r
792 {\r
793         m_tooltips.Popup();\r
794 }\r
795 \r
796 void CCommitDlg::OnEnChangeLogmessage()\r
797 {\r
798         UpdateOKButton();\r
799 }\r
800 \r
801 LRESULT CCommitDlg::OnSVNStatusListCtrlItemCountChanged(WPARAM, LPARAM)\r
802 {\r
803         if ((m_ListCtrl.GetItemCount() == 0)&&(m_ListCtrl.HasUnversionedItems())&&(!m_bShowUnversioned))\r
804         {\r
805                 if (CMessageBox::Show(*this, IDS_COMMITDLG_NOTHINGTOCOMMITUNVERSIONED, IDS_APPNAME, MB_ICONINFORMATION | MB_YESNO)==IDYES)\r
806                 {\r
807                         m_bShowUnversioned = TRUE;\r
808                         DWORD dwShow = SVNSLC_SHOWVERSIONEDBUTNORMALANDEXTERNALSFROMDIFFERENTREPOS | SVNSLC_SHOWUNVERSIONED | SVNSLC_SHOWLOCKS;\r
809                         m_ListCtrl.Show(dwShow);\r
810                         UpdateData(FALSE);\r
811                 }\r
812         }\r
813         return 0;\r
814 }\r
815 \r
816 LRESULT CCommitDlg::OnSVNStatusListCtrlNeedsRefresh(WPARAM, LPARAM)\r
817 {\r
818         Refresh();\r
819         return 0;\r
820 }\r
821 \r
822 LRESULT CCommitDlg::OnFileDropped(WPARAM, LPARAM lParam)\r
823 {\r
824         BringWindowToTop();\r
825         SetForegroundWindow();\r
826         SetActiveWindow();\r
827         // if multiple files/folders are dropped\r
828         // this handler is called for every single item\r
829         // separately.\r
830         // To avoid creating multiple refresh threads and\r
831         // causing crashes, we only add the items to the\r
832         // list control and start a timer.\r
833         // When the timer expires, we start the refresh thread,\r
834         // but only if it isn't already running - otherwise we\r
835         // restart the timer.\r
836         CTSVNPath path;\r
837         path.SetFromWin((LPCTSTR)lParam);\r
838 \r
839         // just add all the items we get here.\r
840         // if the item is versioned, the add will fail but nothing\r
841         // more will happen.\r
842         SVN svn;\r
843         svn.Add(CTSVNPathList(path), &m_ProjectProperties, svn_depth_empty, false, true, true);\r
844 \r
845         if (!m_ListCtrl.HasPath(path))\r
846         {\r
847                 if (m_pathList.AreAllPathsFiles())\r
848                 {\r
849                         m_pathList.AddPath(path);\r
850                         m_pathList.RemoveDuplicates();\r
851                         m_updatedPathList.AddPath(path);\r
852                         m_updatedPathList.RemoveDuplicates();\r
853                 }\r
854                 else\r
855                 {\r
856                         // if the path list contains folders, we have to check whether\r
857                         // our just (maybe) added path is a child of one of those. If it is\r
858                         // a child of a folder already in the list, we must not add it. Otherwise\r
859                         // that path could show up twice in the list.\r
860                         bool bHasParentInList = false;\r
861                         for (int i=0; i<m_pathList.GetCount(); ++i)\r
862                         {\r
863                                 if (m_pathList[i].IsAncestorOf(path))\r
864                                 {\r
865                                         bHasParentInList = true;\r
866                                         break;\r
867                                 }\r
868                         }\r
869                         if (!bHasParentInList)\r
870                         {\r
871                                 m_pathList.AddPath(path);\r
872                                 m_pathList.RemoveDuplicates();\r
873                                 m_updatedPathList.AddPath(path);\r
874                                 m_updatedPathList.RemoveDuplicates();\r
875                         }\r
876                 }\r
877         }\r
878         \r
879         // Always start the timer, since the status of an existing item might have changed\r
880         SetTimer(REFRESHTIMER, 200, NULL);\r
881         ATLTRACE(_T("Item %s dropped, timer started\n"), path.GetWinPath());\r
882         return 0;\r
883 }\r
884 \r
885 LRESULT CCommitDlg::OnAutoListReady(WPARAM, LPARAM)\r
886 {\r
887         m_cLogMessage.SetAutoCompletionList(m_autolist, '*');\r
888         return 0;\r
889 }\r
890 \r
891 //////////////////////////////////////////////////////////////////////////\r
892 // functions which run in the status thread\r
893 //////////////////////////////////////////////////////////////////////////\r
894 \r
895 void CCommitDlg::ParseRegexFile(const CString& sFile, std::map<CString, CString>& mapRegex)\r
896 {\r
897         CString strLine;\r
898         try\r
899         {\r
900                 CStdioFile file(sFile, CFile::typeText | CFile::modeRead | CFile::shareDenyWrite);\r
901                 while (m_bRunThread && file.ReadString(strLine))\r
902                 {\r
903                         int eqpos = strLine.Find('=');\r
904                         CString rgx;\r
905                         rgx = strLine.Mid(eqpos+1).Trim();\r
906 \r
907                         int pos = -1;\r
908                         while (((pos = strLine.Find(','))>=0)&&(pos < eqpos))\r
909                         {\r
910                                 mapRegex[strLine.Left(pos)] = rgx;\r
911                                 strLine = strLine.Mid(pos+1).Trim();\r
912                         }\r
913                         mapRegex[strLine.Left(strLine.Find('=')).Trim()] = rgx;\r
914                 }\r
915                 file.Close();\r
916         }\r
917         catch (CFileException* pE)\r
918         {\r
919                 TRACE("CFileException loading auto list regex file\n");\r
920                 pE->Delete();\r
921                 return;\r
922         }\r
923 }\r
924 void CCommitDlg::GetAutocompletionList()\r
925 {\r
926         // the auto completion list is made of strings from each selected files.\r
927         // the strings used are extracted from the files with regexes found\r
928         // in the file "autolist.txt".\r
929         // the format of that file is:\r
930         // file extensions separated with commas '=' regular expression to use\r
931         // example:\r
932         // .h, .hpp = (?<=class[\s])\b\w+\b|(\b\w+(?=[\s ]?\(\);))\r
933         // .cpp = (?<=[^\s]::)\b\w+\b\r
934         \r
935         std::map<CString, CString> mapRegex;\r
936         CString sRegexFile = CPathUtils::GetAppDirectory();\r
937         CRegDWORD regtimeout = CRegDWORD(_T("Software\\TortoiseSVN\\AutocompleteParseTimeout"), 5);\r
938         DWORD timeoutvalue = regtimeout*1000;\r
939         sRegexFile += _T("autolist.txt");\r
940         if (!m_bRunThread)\r
941                 return;\r
942         ParseRegexFile(sRegexFile, mapRegex);\r
943         SHGetFolderPath(NULL, CSIDL_APPDATA, NULL, SHGFP_TYPE_CURRENT, sRegexFile.GetBuffer(MAX_PATH+1));\r
944         sRegexFile.ReleaseBuffer();\r
945         sRegexFile += _T("\\TortoiseSVN\\autolist.txt");\r
946         if (PathFileExists(sRegexFile))\r
947         {\r
948                 ParseRegexFile(sRegexFile, mapRegex);\r
949         }\r
950         DWORD starttime = GetTickCount();\r
951 \r
952         // now we have two arrays of strings, where the first array contains all\r
953         // file extensions we can use and the second the corresponding regex strings\r
954         // to apply to those files.\r
955 \r
956         // the next step is to go over all files shown in the commit dialog\r
957         // and scan them for strings we can use\r
958         int nListItems = m_ListCtrl.GetItemCount();\r
959 \r
960         for (int i=0; i<nListItems && m_bRunThread; ++i)\r
961         {\r
962                 // stop parsing after timeout\r
963                 if ((!m_bRunThread) || (GetTickCount() - starttime > timeoutvalue))\r
964                         return;\r
965                 const CSVNStatusListCtrl::FileEntry * entry = m_ListCtrl.GetListEntry(i);\r
966                 if (!entry)\r
967                         continue;\r
968                 \r
969                 // add the path parts to the auto completion list too\r
970                 CString sPartPath = entry->GetRelativeSVNPath();\r
971                 m_autolist.insert(sPartPath);\r
972 \r
973                 int pos = 0;\r
974                 int lastPos = 0;\r
975                 while ((pos = sPartPath.Find('/', pos)) >= 0)\r
976                 {\r
977                         pos++;\r
978                         lastPos = pos;\r
979                         m_autolist.insert(sPartPath.Mid(pos));\r
980                 }\r
981 \r
982                 // Last inserted entry is a file name.\r
983                 // Some users prefer to also list file name without extension.\r
984                 if (CRegDWORD(_T("Software\\TortoiseSVN\\AutocompleteRemovesExtensions"), FALSE))\r
985                 {\r
986                         int dotPos = sPartPath.ReverseFind('.');\r
987                         if ((dotPos >= 0) && (dotPos > lastPos))\r
988                                 m_autolist.insert(sPartPath.Mid(lastPos, dotPos - lastPos));\r
989                 }\r
990 \r
991                 if ((entry->status <= svn_wc_status_normal)||(entry->status == svn_wc_status_ignored))\r
992                         continue;\r
993 \r
994                 CString sExt = entry->GetPath().GetFileExtension();\r
995                 sExt.MakeLower();\r
996                 // find the regex string which corresponds to the file extension\r
997                 CString rdata = mapRegex[sExt];\r
998                 if (rdata.IsEmpty())\r
999                         continue;\r
1000 \r
1001                 ScanFile(entry->GetPath().GetWinPathString(), rdata);\r
1002                 if ((entry->textstatus != svn_wc_status_unversioned) &&\r
1003                         (entry->textstatus != svn_wc_status_none) &&\r
1004                         (entry->textstatus != svn_wc_status_ignored) &&\r
1005                         (entry->textstatus != svn_wc_status_added) &&\r
1006                         (entry->textstatus != svn_wc_status_normal))\r
1007                 {\r
1008                         CTSVNPath basePath = SVN::GetPristinePath(entry->GetPath());\r
1009                         if (!basePath.IsEmpty())\r
1010                                 ScanFile(basePath.GetWinPathString(), rdata);\r
1011                 }\r
1012         }\r
1013         ATLTRACE(_T("Auto completion list loaded in %d msec\n"), GetTickCount() - starttime);\r
1014 }\r
1015 \r
1016 void CCommitDlg::ScanFile(const CString& sFilePath, const CString& sRegex)\r
1017 {\r
1018         wstring sFileContent;\r
1019         HANDLE hFile = CreateFile(sFilePath, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, NULL, NULL);\r
1020         if (hFile != INVALID_HANDLE_VALUE)\r
1021         {\r
1022                 DWORD size = GetFileSize(hFile, NULL);\r
1023                 if (size > 1000000L)\r
1024                 {\r
1025                         // no files bigger than 1 Meg\r
1026                         CloseHandle(hFile);\r
1027                         return;\r
1028                 }\r
1029                 // allocate memory to hold file contents\r
1030                 char * buffer = new char[size];\r
1031                 DWORD readbytes;\r
1032                 ReadFile(hFile, buffer, size, &readbytes, NULL);\r
1033                 CloseHandle(hFile);\r
1034                 int opts = 0;\r
1035                 IsTextUnicode(buffer, readbytes, &opts);\r
1036                 if (opts & IS_TEXT_UNICODE_NULL_BYTES)\r
1037                 {\r
1038                         delete [] buffer;\r
1039                         return;\r
1040                 }\r
1041                 if (opts & IS_TEXT_UNICODE_UNICODE_MASK)\r
1042                 {\r
1043                         sFileContent = wstring((wchar_t*)buffer, readbytes/sizeof(WCHAR));\r
1044                 }\r
1045                 if ((opts & IS_TEXT_UNICODE_NOT_UNICODE_MASK)||(opts == 0))\r
1046                 {\r
1047                         int ret = MultiByteToWideChar(CP_ACP, MB_PRECOMPOSED, (LPCSTR)buffer, readbytes, NULL, 0);\r
1048                         wchar_t * pWideBuf = new wchar_t[ret];\r
1049                         int ret2 = MultiByteToWideChar(CP_ACP, MB_PRECOMPOSED, (LPCSTR)buffer, readbytes, pWideBuf, ret);\r
1050                         if (ret2 == ret)\r
1051                                 sFileContent = wstring(pWideBuf, ret);\r
1052                         delete [] pWideBuf;\r
1053                 }\r
1054                 delete [] buffer;\r
1055         }\r
1056         if (sFileContent.empty()|| !m_bRunThread)\r
1057         {\r
1058                 return;\r
1059         }\r
1060 \r
1061         try\r
1062         {\r
1063                 const tr1::wregex regCheck(sRegex, tr1::regex_constants::icase | tr1::regex_constants::ECMAScript);\r
1064                 const tr1::wsregex_iterator end;\r
1065                 wstring s = sFileContent;\r
1066                 for (tr1::wsregex_iterator it(s.begin(), s.end(), regCheck); it != end; ++it)\r
1067                 {\r
1068                         const tr1::wsmatch match = *it;\r
1069                         for (size_t i=1; i<match.size(); ++i)\r
1070                         {\r
1071                                 if (match[i].second-match[i].first)\r
1072                                 {\r
1073                                         ATLTRACE(_T("matched keyword : %s\n"), wstring(match[i]).c_str());\r
1074                                         m_autolist.insert(wstring(match[i]).c_str());\r
1075                                 }\r
1076                         }\r
1077                 }\r
1078         }\r
1079         catch (exception) {}\r
1080 }\r
1081 \r
1082 // CSciEditContextMenuInterface\r
1083 void CCommitDlg::InsertMenuItems(CMenu& mPopup, int& nCmd)\r
1084 {\r
1085         CString sMenuItemText(MAKEINTRESOURCE(IDS_COMMITDLG_POPUP_PASTEFILELIST));\r
1086         m_nPopupPasteListCmd = nCmd++;\r
1087         mPopup.AppendMenu(MF_STRING | MF_ENABLED, m_nPopupPasteListCmd, sMenuItemText);\r
1088 }\r
1089 \r
1090 bool CCommitDlg::HandleMenuItemClick(int cmd, CSciEdit * pSciEdit)\r
1091 {\r
1092         if (m_bBlock)\r
1093                 return false;\r
1094         if (cmd == m_nPopupPasteListCmd)\r
1095         {\r
1096                 CString logmsg;\r
1097                 TCHAR buf[MAX_STATUS_STRING_LENGTH];\r
1098                 int nListItems = m_ListCtrl.GetItemCount();\r
1099                 for (int i=0; i<nListItems; ++i)\r
1100                 {\r
1101                         CSVNStatusListCtrl::FileEntry * entry = m_ListCtrl.GetListEntry(i);\r
1102                         if (entry->IsChecked())\r
1103                         {\r
1104                                 CString line;\r
1105                                 svn_wc_status_kind status = entry->status;\r
1106                                 if (status == svn_wc_status_unversioned)\r
1107                                         status = svn_wc_status_added;\r
1108                                 if (status == svn_wc_status_missing)\r
1109                                         status = svn_wc_status_deleted;\r
1110                                 WORD langID = (WORD)CRegStdWORD(_T("Software\\TortoiseSVN\\LanguageID"), GetUserDefaultLangID());\r
1111                                 if (m_ProjectProperties.bFileListInEnglish)\r
1112                                         langID = 1033;\r
1113                                 SVNStatus::GetStatusString(AfxGetResourceHandle(), status, buf, sizeof(buf)/sizeof(TCHAR), langID);\r
1114                                 line.Format(_T("%-10s %s\r\n"), buf, (LPCTSTR)m_ListCtrl.GetItemText(i,0));\r
1115                                 logmsg += line;\r
1116                         }\r
1117                 }\r
1118                 pSciEdit->InsertText(logmsg);\r
1119                 return true;\r
1120         }\r
1121         return false;\r
1122 }\r
1123 \r
1124 void CCommitDlg::OnTimer(UINT_PTR nIDEvent)\r
1125 {\r
1126         switch (nIDEvent)\r
1127         {\r
1128         case ENDDIALOGTIMER:\r
1129                 KillTimer(ENDDIALOGTIMER);\r
1130                 EndDialog(0);\r
1131                 break;\r
1132         case REFRESHTIMER:\r
1133                 if (m_bThreadRunning)\r
1134                 {\r
1135                         SetTimer(REFRESHTIMER, 200, NULL);\r
1136                         ATLTRACE("Wait some more before refreshing\n");\r
1137                 }\r
1138                 else\r
1139                 {\r
1140                         KillTimer(REFRESHTIMER);\r
1141                         ATLTRACE("Refreshing after items dropped\n");\r
1142                         Refresh();\r
1143                 }\r
1144                 break;\r
1145         }\r
1146         __super::OnTimer(nIDEvent);\r
1147 }\r
1148 \r
1149 void CCommitDlg::OnBnClickedHistory()\r
1150 {\r
1151         m_tooltips.Pop();       // hide the tooltips\r
1152         if (m_pathList.GetCount() == 0)\r
1153                 return;\r
1154         CHistoryDlg historyDlg;\r
1155         historyDlg.SetHistory(m_History);\r
1156         if (historyDlg.DoModal() != IDOK)\r
1157                 return;\r
1158 \r
1159         CString sMsg = historyDlg.GetSelectedText();\r
1160         if (sMsg != m_cLogMessage.GetText().Left(sMsg.GetLength()))\r
1161         {\r
1162                 CString sBugID = m_ProjectProperties.GetBugIDFromLog(sMsg);\r
1163                 if (!sBugID.IsEmpty())\r
1164                 {\r
1165                         SetDlgItemText(IDC_BUGID, sBugID);\r
1166                 }\r
1167                 if (m_ProjectProperties.sLogTemplate.Compare(m_cLogMessage.GetText())!=0)\r
1168                         m_cLogMessage.InsertText(sMsg, !m_cLogMessage.GetText().IsEmpty());\r
1169                 else\r
1170                         m_cLogMessage.SetText(sMsg);\r
1171         }\r
1172 \r
1173         UpdateOKButton();\r
1174         GetDlgItem(IDC_LOGMESSAGE)->SetFocus();\r
1175 }\r
1176 \r
1177 void CCommitDlg::OnBnClickedBugtraqbutton()\r
1178 {\r
1179         m_tooltips.Pop();       // hide the tooltips\r
1180         CString sMsg = m_cLogMessage.GetText();\r
1181 \r
1182         if (m_BugTraqProvider == NULL)\r
1183                 return;\r
1184 \r
1185         BSTR parameters = m_bugtraq_association.GetParameters().AllocSysString();\r
1186         BSTR commonRoot = SysAllocString(m_pathList.GetCommonRoot().GetDirectory().GetWinPath());\r
1187         SAFEARRAY *pathList = SafeArrayCreateVector(VT_BSTR, 0, m_pathList.GetCount());\r
1188 \r
1189         for (LONG index = 0; index < m_pathList.GetCount(); ++index)\r
1190                 SafeArrayPutElement(pathList, &index, m_pathList[index].GetSVNPathString().AllocSysString());\r
1191 \r
1192         BSTR originalMessage = sMsg.AllocSysString();\r
1193         BSTR temp = NULL;\r
1194 \r
1195         // first try the IBugTraqProvider2 interface\r
1196         CComPtr<IBugTraqProvider2> pProvider2 = NULL;\r
1197         HRESULT hr = m_BugTraqProvider.QueryInterface(&pProvider2);\r
1198         if (SUCCEEDED(hr))\r
1199         {\r
1200                 CString common = m_ListCtrl.GetCommonURL(false).GetSVNPathString();\r
1201                 BSTR repositoryRoot = common.AllocSysString();\r
1202                 if (FAILED(hr = pProvider2->GetCommitMessage2(GetSafeHwnd(), parameters, repositoryRoot, commonRoot, pathList, originalMessage, &temp)))\r
1203                 {\r
1204                         CString sErr;\r
1205                         sErr.Format(IDS_ERR_FAILEDISSUETRACKERCOM, m_bugtraq_association.GetProviderName(), _com_error(hr).ErrorMessage());\r
1206                         CMessageBox::Show(m_hWnd, sErr, _T("TortoiseSVN"), MB_ICONERROR);\r
1207                 }\r
1208                 else\r
1209                         m_cLogMessage.SetText(temp);\r
1210         }\r
1211         else\r
1212         {\r
1213                 // if IBugTraqProvider2 failed, try IBugTraqProvider\r
1214                 CComPtr<IBugTraqProvider> pProvider = NULL;\r
1215                 HRESULT hr = m_BugTraqProvider.QueryInterface(&pProvider);\r
1216                 if (FAILED(hr))\r
1217                 {\r
1218                         CString sErr;\r
1219                         sErr.Format(IDS_ERR_FAILEDISSUETRACKERCOM, (LPCTSTR)m_bugtraq_association.GetProviderName(), _com_error(hr).ErrorMessage());\r
1220                         CMessageBox::Show(m_hWnd, sErr, _T("TortoiseSVN"), MB_ICONERROR);\r
1221                         return;\r
1222                 }\r
1223 \r
1224                 if (FAILED(hr = pProvider->GetCommitMessage(GetSafeHwnd(), parameters, commonRoot, pathList, originalMessage, &temp)))\r
1225                 {\r
1226                         CString sErr;\r
1227                         sErr.Format(IDS_ERR_FAILEDISSUETRACKERCOM, m_bugtraq_association.GetProviderName(), _com_error(hr).ErrorMessage());\r
1228                         CMessageBox::Show(m_hWnd, sErr, _T("TortoiseSVN"), MB_ICONERROR);\r
1229                 }\r
1230                 else\r
1231                         m_cLogMessage.SetText(temp);\r
1232         }\r
1233 \r
1234         m_cLogMessage.SetFocus();\r
1235 \r
1236         SysFreeString(temp);\r
1237 }\r
1238 \r
1239 LRESULT CCommitDlg::OnSVNStatusListCtrlCheckChanged(WPARAM, LPARAM)\r
1240 {\r
1241         UpdateOKButton();\r
1242         return 0;\r
1243 }\r
1244 \r
1245 void CCommitDlg::UpdateOKButton()\r
1246 {\r
1247         BOOL bValidLogSize = FALSE;\r
1248 \r
1249     if (m_cLogMessage.GetText().GetLength() >= m_ProjectProperties.nMinLogSize)\r
1250                 bValidLogSize = !m_bBlock;\r
1251 \r
1252         LONG nSelectedItems = m_ListCtrl.GetSelected();\r
1253         DialogEnableWindow(IDOK, bValidLogSize && nSelectedItems>0);\r
1254 }\r
1255 \r
1256 \r
1257 LRESULT CCommitDlg::DefWindowProc(UINT message, WPARAM wParam, LPARAM lParam)\r
1258 {\r
1259         switch (message) {\r
1260         case WM_NOTIFY:\r
1261                 if (wParam == IDC_SPLITTER)\r
1262                 { \r
1263                         SPC_NMHDR* pHdr = (SPC_NMHDR*) lParam;\r
1264                         DoSize(pHdr->delta);\r
1265                 }\r
1266                 break;\r
1267         }\r
1268 \r
1269         return __super::DefWindowProc(message, wParam, lParam);\r
1270 }\r
1271 \r
1272 void CCommitDlg::SetSplitterRange()\r
1273 {\r
1274         if ((m_ListCtrl)&&(m_cLogMessage))\r
1275         {\r
1276                 CRect rcTop;\r
1277                 m_cLogMessage.GetWindowRect(rcTop);\r
1278                 ScreenToClient(rcTop);\r
1279                 CRect rcMiddle;\r
1280                 m_ListCtrl.GetWindowRect(rcMiddle);\r
1281                 ScreenToClient(rcMiddle);\r
1282                 if (rcMiddle.Height() && rcMiddle.Width())\r
1283                         m_wndSplitter.SetRange(rcTop.top+60, rcMiddle.bottom-80);\r
1284         }\r
1285 }\r
1286 \r
1287 void CCommitDlg::DoSize(int delta)\r
1288 {\r
1289         RemoveAnchor(IDC_MESSAGEGROUP);\r
1290         RemoveAnchor(IDC_LOGMESSAGE);\r
1291         RemoveAnchor(IDC_SPLITTER);\r
1292         RemoveAnchor(IDC_LISTGROUP);\r
1293         RemoveAnchor(IDC_FILELIST);\r
1294         CSplitterControl::ChangeHeight(&m_cLogMessage, delta, CW_TOPALIGN);\r
1295         CSplitterControl::ChangeHeight(GetDlgItem(IDC_MESSAGEGROUP), delta, CW_TOPALIGN);\r
1296         CSplitterControl::ChangeHeight(&m_ListCtrl, -delta, CW_BOTTOMALIGN);\r
1297         CSplitterControl::ChangeHeight(GetDlgItem(IDC_LISTGROUP), -delta, CW_BOTTOMALIGN);\r
1298         AddAnchor(IDC_MESSAGEGROUP, TOP_LEFT, TOP_RIGHT);\r
1299         AddAnchor(IDC_LOGMESSAGE, TOP_LEFT, TOP_RIGHT);\r
1300         AddAnchor(IDC_SPLITTER, TOP_LEFT, TOP_RIGHT);\r
1301         AddAnchor(IDC_LISTGROUP, TOP_LEFT, BOTTOM_RIGHT);\r
1302         AddAnchor(IDC_FILELIST, TOP_LEFT, BOTTOM_RIGHT);\r
1303         ArrangeLayout();\r
1304         // adjust the minimum size of the dialog to prevent the resizing from\r
1305         // moving the list control too far down.\r
1306         CRect rcLogMsg;\r
1307         m_cLogMessage.GetClientRect(rcLogMsg);\r
1308         SetMinTrackSize(CSize(m_DlgOrigRect.Width(), m_DlgOrigRect.Height()-m_LogMsgOrigRect.Height()+rcLogMsg.Height()));\r
1309 \r
1310         SetSplitterRange();\r
1311         m_cLogMessage.Invalidate();\r
1312         GetDlgItem(IDC_LOGMESSAGE)->Invalidate();\r
1313 }\r
1314 \r
1315 void CCommitDlg::OnSize(UINT nType, int cx, int cy)\r
1316 {\r
1317     // first, let the resizing take place\r
1318     __super::OnSize(nType, cx, cy);\r
1319 \r
1320     //set range\r
1321     SetSplitterRange();\r
1322 }\r
1323 \r