Upgraded libgit2
[TortoiseGit.git] / src / TortoiseShell / GITPropertyPage.cpp
blob4687d509c1cf4e7d343d289615e34e38f73d039c
1 // TortoiseGit - a Windows shell extension for easy version control
3 // Copyright (C) 2003-2008 - TortoiseSVN
4 // Copyright (C) 2008-2012 - TortoiseGit
6 // This program is free software; you can redistribute it and/or
7 // modify it under the terms of the GNU General Public License
8 // as published by the Free Software Foundation; either version 2
9 // of the License, or (at your option) any later version.
11 // This program is distributed in the hope that it will be useful,
12 // but WITHOUT ANY WARRANTY; without even the implied warranty of
13 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 // GNU General Public License for more details.
16 // You should have received a copy of the GNU General Public License
17 // along with this program; if not, write to the Free Software Foundation,
18 // 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
20 #include "stdafx.h"
22 #include "ShellExt.h"
23 #include "gitpropertypage.h"
24 #include "UnicodeUtils.h"
25 #include "PathUtils.h"
26 #include "UnicodeUtils.h"
27 #include "CreateProcessHelper.h"
28 #include "FormatMessageWrapper.h"
30 #define MAX_STRING_LENGTH 4096 //should be big enough
32 // Nonmember function prototypes
33 BOOL CALLBACK PageProc (HWND, UINT, WPARAM, LPARAM);
34 UINT CALLBACK PropPageCallbackProc ( HWND hwnd, UINT uMsg, LPPROPSHEETPAGE ppsp );
36 /////////////////////////////////////////////////////////////////////////////
37 // Dialog procedures and other callback functions
39 BOOL CALLBACK PageProc (HWND hwnd, UINT uMessage, WPARAM wParam, LPARAM lParam)
41 CGitPropertyPage * sheetpage;
43 if (uMessage == WM_INITDIALOG)
45 sheetpage = (CGitPropertyPage*) ((LPPROPSHEETPAGE) lParam)->lParam;
46 SetWindowLongPtr (hwnd, GWLP_USERDATA, (LONG_PTR) sheetpage);
47 sheetpage->SetHwnd(hwnd);
49 else
51 sheetpage = (CGitPropertyPage*) GetWindowLongPtr (hwnd, GWLP_USERDATA);
54 if (sheetpage != 0L)
55 return sheetpage->PageProc(hwnd, uMessage, wParam, lParam);
56 else
57 return FALSE;
60 UINT CALLBACK PropPageCallbackProc ( HWND /*hwnd*/, UINT uMsg, LPPROPSHEETPAGE ppsp )
62 // Delete the page before closing.
63 if (PSPCB_RELEASE == uMsg)
65 CGitPropertyPage* sheetpage = (CGitPropertyPage*) ppsp->lParam;
66 if (sheetpage != NULL)
67 delete sheetpage;
69 return 1;
72 // *********************** CGitPropertyPage *************************
74 CGitPropertyPage::CGitPropertyPage(const std::vector<stdstring> &newFilenames)
75 :filenames(newFilenames)
76 ,m_bChanged(false)
80 CGitPropertyPage::~CGitPropertyPage(void)
84 void CGitPropertyPage::SetHwnd(HWND newHwnd)
86 m_hwnd = newHwnd;
89 BOOL CGitPropertyPage::PageProc (HWND /*hwnd*/, UINT uMessage, WPARAM wParam, LPARAM lParam)
91 switch (uMessage)
93 case WM_INITDIALOG:
95 InitWorkfileView();
96 return TRUE;
98 case WM_NOTIFY:
100 LPNMHDR point = (LPNMHDR)lParam;
101 int code = point->code;
103 // Respond to notifications.
105 if (code == PSN_APPLY && filenames.size() == 1 && m_bChanged)
109 CTGitPath path(filenames.front().c_str());
110 CString projectTopDir;
111 if(!path.HasAdminDir(&projectTopDir) || path.IsDirectory())
112 break;
114 CTGitPath relatepath;
115 CString strpath=path.GetWinPathString();
117 if(projectTopDir[projectTopDir.GetLength() - 1] == _T('\\'))
118 relatepath.SetFromWin(strpath.Right(strpath.GetLength() - projectTopDir.GetLength()));
119 else
120 relatepath.SetFromWin(strpath.Right(strpath.GetLength() - projectTopDir.GetLength() - 1));
122 CStringA gitdir = CUnicodeUtils::GetMulti(projectTopDir, CP_UTF8);
123 git_repository *repository = NULL;
124 git_index *index = NULL;
126 int ret = git_repository_open(&repository, gitdir.GetBuffer());
127 gitdir.ReleaseBuffer();
128 if (ret)
129 break;
131 if (git_repository_index(&index, repository))
133 git_repository_free(repository);
134 break;
137 bool changed = false;
139 CStringA pathA = CUnicodeUtils::GetMulti(relatepath.GetGitPathString(), CP_UTF8);
140 int idx = git_index_find(index, pathA);
141 if (idx >= 0)
143 git_index_entry *e = const_cast<git_index_entry *>(git_index_get_byindex(index, idx)); // HACK
145 if (SendMessage(GetDlgItem(m_hwnd, IDC_ASSUMEVALID), BM_GETCHECK, 0, 0) == BST_CHECKED)
147 if (!(e->flags & GIT_IDXENTRY_VALID))
149 e->flags |= GIT_IDXENTRY_VALID;
150 changed = true;
153 else
155 if (e->flags & GIT_IDXENTRY_VALID)
157 e->flags &= ~GIT_IDXENTRY_VALID;
158 changed = true;
161 if (SendMessage(GetDlgItem(m_hwnd, IDC_SKIPWORKTREE), BM_GETCHECK, 0, 0) == BST_CHECKED)
163 if (!(e->flags_extended & GIT_IDXENTRY_SKIP_WORKTREE))
165 e->flags_extended |= GIT_IDXENTRY_SKIP_WORKTREE;
166 changed = true;
169 else
171 if (e->flags_extended & GIT_IDXENTRY_SKIP_WORKTREE)
173 e->flags_extended &= ~GIT_IDXENTRY_SKIP_WORKTREE;
174 changed = true;
177 if (SendMessage(GetDlgItem(m_hwnd, IDC_EXECUTABLE), BM_GETCHECK, 0, 0) == BST_CHECKED)
179 if (!(e->mode & 0111))
181 e->mode |= 0111;
182 changed = true;
185 else
187 if (e->mode & 0111)
189 e->mode &= ~0111;
190 changed = true;
193 if (changed)
194 git_index_add(index, e);
197 if (changed)
199 if (!git_index_write(index))
200 m_bChanged = false;
203 git_index_free(index);
204 } while (0);
206 SetWindowLongPtr (m_hwnd, DWLP_MSGRESULT, FALSE);
207 return TRUE;
210 case WM_DESTROY:
211 return TRUE;
213 case WM_COMMAND:
214 PageProcOnCommand(wParam);
215 break;
216 } // switch (uMessage)
217 return FALSE;
219 void CGitPropertyPage::PageProcOnCommand(WPARAM wParam)
221 if(HIWORD(wParam) != BN_CLICKED)
222 return;
224 switch (LOWORD(wParam))
226 case IDC_SHOWLOG:
228 tstring gitCmd = _T(" /command:");
229 gitCmd += _T("log /path:\"");
230 gitCmd += filenames.front().c_str();
231 gitCmd += _T("\"");
232 RunCommand(gitCmd);
234 break;
235 case IDC_SHOWSETTINGS:
237 CTGitPath path(filenames.front().c_str());
238 CString projectTopDir;
239 if(!path.HasAdminDir(&projectTopDir))
240 return;
242 tstring gitCmd = _T(" /command:");
243 gitCmd += _T("settings /path:\"");
244 gitCmd += projectTopDir;
245 gitCmd += _T("\"");
246 RunCommand(gitCmd);
248 break;
249 case IDC_ASSUMEVALID:
250 case IDC_SKIPWORKTREE:
251 case IDC_EXECUTABLE:
252 m_bChanged = true;
253 SendMessage(GetParent(m_hwnd), PSM_CHANGED, (WPARAM)m_hwnd, 0);
254 break;
258 void CGitPropertyPage::RunCommand(const tstring& command)
260 tstring tortoiseProcPath = CPathUtils::GetAppDirectory(g_hmodThisDll) + _T("TortoiseProc.exe");
261 if (CCreateProcessHelper::CreateProcessDetached(tortoiseProcPath.c_str(), const_cast<TCHAR*>(command.c_str())))
263 // process started - exit
264 return;
267 MessageBox(NULL, CFormatMessageWrapper(), _T("TortoiseProc launch failed"), MB_OK | MB_ICONINFORMATION);
270 void CGitPropertyPage::Time64ToTimeString(__time64_t time, TCHAR * buf, size_t buflen)
272 struct tm newtime;
273 SYSTEMTIME systime;
274 TCHAR timebuf[MAX_STRING_LENGTH];
275 TCHAR datebuf[MAX_STRING_LENGTH];
277 LCID locale = (WORD)CRegStdDWORD(_T("Software\\TortoiseGit\\LanguageID"), MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT));
278 locale = MAKELCID(locale, SORT_DEFAULT);
280 *buf = '\0';
281 if (time)
283 _localtime64_s(&newtime, &time);
285 systime.wDay = (WORD)newtime.tm_mday;
286 systime.wDayOfWeek = (WORD)newtime.tm_wday;
287 systime.wHour = (WORD)newtime.tm_hour;
288 systime.wMilliseconds = 0;
289 systime.wMinute = (WORD)newtime.tm_min;
290 systime.wMonth = (WORD)newtime.tm_mon+1;
291 systime.wSecond = (WORD)newtime.tm_sec;
292 systime.wYear = (WORD)newtime.tm_year+1900;
293 if (CRegStdDWORD(_T("Software\\TortoiseGit\\LogDateFormat")) == 1)
294 GetDateFormat(locale, DATE_SHORTDATE, &systime, NULL, datebuf, MAX_STRING_LENGTH);
295 else
296 GetDateFormat(locale, DATE_LONGDATE, &systime, NULL, datebuf, MAX_STRING_LENGTH);
297 GetTimeFormat(locale, 0, &systime, NULL, timebuf, MAX_STRING_LENGTH);
298 *buf = '\0';
299 _tcsncat_s(buf, buflen, timebuf, MAX_STRING_LENGTH-1);
300 _tcsncat_s(buf, buflen, _T(", "), MAX_STRING_LENGTH-1);
301 _tcsncat_s(buf, buflen, datebuf, MAX_STRING_LENGTH-1);
305 struct TreewalkStruct
307 const char *folder;
308 const char *name;
309 git_oid oid;
312 static int TreewalkCB_FindFileRecentCommit(const char *root, const git_tree_entry *entry, void *payload)
314 TreewalkStruct *treewalkstruct = (TreewalkStruct *)payload;
315 char folder[MAX_PATH];
316 strcpy(folder, root);
317 strcat(folder, git_tree_entry_name(entry));
318 strcat(folder, "/");
319 if (strstr(treewalkstruct->folder, folder))
320 return 0;
322 if (!strcmp(treewalkstruct->folder, root))
324 if (!strcmp(git_tree_entry_name(entry), treewalkstruct->name))
326 git_oid_cpy(&treewalkstruct->oid, git_tree_entry_id(entry));
327 return -1;
330 return 1;
333 return 1;
336 static git_commit * FindFileRecentCommit(git_repository *repository, CString path)
338 git_commit *commit = NULL, *commit2 = NULL;
339 git_revwalk *walk;
340 if (git_revwalk_new(&walk, repository))
341 return NULL;
343 CStringA pathA = CUnicodeUtils::GetUTF8(path);
344 const char *pathC = pathA.GetBuffer();
345 char folder[MAX_PATH], file[MAX_PATH];
346 const char *slash = strrchr(pathC, '/');
347 if (slash)
349 strncpy(folder, pathC, slash - pathC + 1);
350 folder[slash - pathC + 1] = '\0';
351 strcpy(file, slash + 1);
353 else
355 folder[0] = '\0';
356 strcpy(file, pathC);
358 pathA.ReleaseBuffer();
360 TreewalkStruct treewalkstruct = { folder, file };
361 TreewalkStruct treewalkstruct2 = { folder, file };
365 if (git_revwalk_push_head(walk))
366 throw "git_revwalk_push_head";
368 git_oid oid;
369 while (!git_revwalk_next(&oid, walk))
371 if (commit != NULL)
373 git_commit_free(commit);
374 commit = NULL;
377 if (git_commit_lookup(&commit, repository, &oid))
379 commit = NULL;
380 throw "git_commit_lookup 1";
383 git_tree *tree;
384 if (git_commit_tree(&tree, commit))
385 throw "git_commit_tree 1";
387 memset(&treewalkstruct.oid.id, 0, sizeof(treewalkstruct.oid.id));
388 int ret = git_tree_walk(tree, GIT_TREEWALK_PRE, TreewalkCB_FindFileRecentCommit, &treewalkstruct);
389 git_tree_free(tree);
390 if (ret < 0 && ret != GIT_EUSER)
391 throw "git_tree_walk 1";
393 // check if file not found
394 if (git_oid_iszero(&treewalkstruct.oid))
396 git_commit_free(commit);
397 commit = NULL;
398 break;
401 bool diff = true;
402 // for merge point, check if it is different to all parents, if yes then there are real change in the merge point.
403 // if no parent then of course it is different
404 for (unsigned int i = 0; i < git_commit_parentcount(commit); i++)
406 if (git_commit_parent(&commit2, commit, i))
408 commit2 = NULL;
409 throw "git_commit_parent";
412 if (git_commit_tree(&tree, commit2))
413 throw "git_commit_tree 2";
415 git_commit_free(commit2);
416 memset(&treewalkstruct2.oid.id, 0, sizeof(treewalkstruct2.oid.id));
417 int ret = git_tree_walk(tree, GIT_TREEWALK_PRE, TreewalkCB_FindFileRecentCommit, &treewalkstruct2);
418 git_tree_free(tree);
419 if (ret < 0 && ret != GIT_EUSER)
420 throw "git_tree_walk 2";
422 if (!git_oid_cmp(&treewalkstruct.oid, &treewalkstruct2.oid))
423 diff = false;
424 else if (git_revwalk_hide(walk, git_commit_parent_id(commit, i)))
425 throw "git_revwalk_hide";
428 if (diff)
429 break;
432 catch (...)
434 if (commit != NULL)
436 git_commit_free(commit);
437 commit = NULL;
439 if (commit2 != NULL)
441 git_commit_free(commit2);
442 commit2 = NULL;
446 git_revwalk_free(walk);
447 return commit;
450 void CGitPropertyPage::DisplayCommit(git_commit * commit, UINT hashLabel, UINT subjectLabel, UINT authorLabel, UINT dateLabel)
452 int encode = CP_UTF8;
453 const char * encodingString = git_commit_message_encoding(commit);
454 if (encodingString != NULL)
456 CString str;
457 g_Git.StringAppend(&str, (BYTE*)encodingString, CP_UTF8);
458 encode = CUnicodeUtils::GetCPCode(str);
461 const git_signature * author = git_commit_author(commit);
462 CString authorName;
463 g_Git.StringAppend(&authorName, (BYTE*)author->name, encode);
465 CString message;
466 g_Git.StringAppend(&message, (BYTE*)git_commit_message(commit), encode);
468 int start = 0;
469 message = message.Tokenize(L"\n", start);
471 SetDlgItemText(m_hwnd, hashLabel, CGitHash((char*)(git_commit_id(commit)->id)).ToString());
472 SetDlgItemText(m_hwnd, subjectLabel, message);
473 SetDlgItemText(m_hwnd, authorLabel, authorName);
475 CString authorDate;
476 Time64ToTimeString(author->when.time, authorDate.GetBufferSetLength(200), 200);
477 SetDlgItemText(m_hwnd, dateLabel, authorDate);
480 void CGitPropertyPage::InitWorkfileView()
482 if (filenames.empty())
483 return;
485 CTGitPath path(filenames.front().c_str());
487 CString ProjectTopDir;
488 if(!path.HasAdminDir(&ProjectTopDir))
489 return;
491 CStringA gitdir = CUnicodeUtils::GetMulti(ProjectTopDir, CP_UTF8);
492 git_repository *repository = NULL;
494 int ret = git_repository_open(&repository, gitdir.GetBuffer());
495 gitdir.ReleaseBuffer();
496 if (ret)
497 return;
499 CGit git;
501 git.SetCurrentDir(ProjectTopDir);
502 CString username;
503 git.Run(_T("git.exe config user.name"), &username, CP_UTF8);
504 CString useremail;
505 git.Run(_T("git.exe config user.email"), &useremail, CP_UTF8);
506 CString autocrlf;
507 git.Run(_T("git.exe config core.autocrlf"), &autocrlf, CP_UTF8);
508 CString safecrlf;
509 git.Run(_T("git.exe config core.safecrlf"), &safecrlf, CP_UTF8);
511 CString branch;
512 CString remotebranch;
514 if (!g_Git.GetCurrentBranchFromFile(ProjectTopDir, branch))
516 CString cmd, remote;
517 cmd.Format(_T("git.exe config branch.%s.merge"), branch.Trim());
518 git.Run(cmd, &remotebranch, CP_UTF8);
519 cmd.Format(_T("git.exe config branch.%s.remote"), branch.Trim());
520 git.Run(cmd, &remote, CP_UTF8);
521 remote.Trim();
522 remotebranch.Trim();
523 if((!remote.IsEmpty()) && (!remotebranch.IsEmpty()))
525 remotebranch = remotebranch.Mid(11);
526 remotebranch = remote + _T("/") + remotebranch;
529 else
530 branch = _T("detached HEAD");
532 if (autocrlf.Trim().IsEmpty())
533 autocrlf = _T("false");
534 if (safecrlf.Trim().IsEmpty())
535 safecrlf = _T("false");
537 SetDlgItemText(m_hwnd,IDC_CONFIG_USERNAME,username.Trim());
538 SetDlgItemText(m_hwnd,IDC_CONFIG_USEREMAIL,useremail.Trim());
539 SetDlgItemText(m_hwnd,IDC_CONFIG_AUTOCRLF,autocrlf.Trim());
540 SetDlgItemText(m_hwnd,IDC_CONFIG_SAFECRLF,safecrlf.Trim());
542 SetDlgItemText(m_hwnd,IDC_SHELL_CURRENT_BRANCH,branch.Trim());
543 remotebranch.Trim().Replace(_T("\n"), _T("; "));
544 SetDlgItemText(m_hwnd,IDC_SHELL_REMOTE_BRANCH, remotebranch);
546 git_oid oid;
547 git_commit *HEADcommit = NULL;
548 if (!git_reference_name_to_id(&oid, repository, "HEAD") && !git_commit_lookup(&HEADcommit, repository, &oid) && HEADcommit != NULL)
550 DisplayCommit(HEADcommit, IDC_HEAD_HASH, IDC_HEAD_SUBJECT, IDC_HEAD_AUTHOR, IDC_HEAD_DATE);
551 git_commit_free(HEADcommit);
555 if (filenames.size() == 1)
557 CTGitPath relatepath;
558 CString strpath=path.GetWinPathString();
560 if(ProjectTopDir[ProjectTopDir.GetLength()-1] == _T('\\'))
562 relatepath.SetFromWin( strpath.Right(strpath.GetLength()-ProjectTopDir.GetLength()));
564 else
566 relatepath.SetFromWin( strpath.Right(strpath.GetLength()-ProjectTopDir.GetLength()-1));
569 git_commit *commit = FindFileRecentCommit(repository, relatepath.GetGitPathString());
570 if (commit != NULL)
572 DisplayCommit(commit, IDC_LAST_HASH, IDC_LAST_SUBJECT, IDC_LAST_AUTHOR, IDC_LAST_DATE);
573 git_commit_free(commit);
576 if (!path.IsDirectory())
578 // get assume valid flag
579 bool assumevalid = false;
580 bool skipworktree = false;
581 bool executable = false;
584 git_index *index = NULL;
586 if (git_repository_index(&index, repository))
587 break;
589 CStringA pathA = CUnicodeUtils::GetMulti(relatepath.GetGitPathString(), CP_UTF8);
590 int idx = git_index_find(index, pathA);
591 if (idx >= 0)
593 const git_index_entry *e = git_index_get_byindex(index, idx);
595 if (e->flags & GIT_IDXENTRY_VALID)
596 assumevalid = true;
598 if (e->flags_extended & GIT_IDXENTRY_SKIP_WORKTREE)
599 skipworktree = true;
601 if (e->mode & 0111)
602 executable = true;
604 else
606 // do not show checkboxes for unversioned files
607 ShowWindow(GetDlgItem(m_hwnd, IDC_ASSUMEVALID), SW_HIDE);
608 ShowWindow(GetDlgItem(m_hwnd, IDC_SKIPWORKTREE), SW_HIDE);
609 ShowWindow(GetDlgItem(m_hwnd, IDC_EXECUTABLE), SW_HIDE);
612 git_index_free(index);
613 } while (0);
614 SendMessage(GetDlgItem(m_hwnd, IDC_ASSUMEVALID), BM_SETCHECK, assumevalid ? BST_CHECKED : BST_UNCHECKED, 0);
615 SendMessage(GetDlgItem(m_hwnd, IDC_SKIPWORKTREE), BM_SETCHECK, skipworktree ? BST_CHECKED : BST_UNCHECKED, 0);
616 SendMessage(GetDlgItem(m_hwnd, IDC_EXECUTABLE), BM_SETCHECK, executable ? BST_CHECKED : BST_UNCHECKED, 0);
618 else
620 ShowWindow(GetDlgItem(m_hwnd, IDC_ASSUMEVALID), SW_HIDE);
621 ShowWindow(GetDlgItem(m_hwnd, IDC_SKIPWORKTREE), SW_HIDE);
622 ShowWindow(GetDlgItem(m_hwnd, IDC_EXECUTABLE), SW_HIDE);
625 else
627 ShowWindow(GetDlgItem(m_hwnd, IDC_ASSUMEVALID), SW_HIDE);
628 ShowWindow(GetDlgItem(m_hwnd, IDC_SKIPWORKTREE), SW_HIDE);
629 ShowWindow(GetDlgItem(m_hwnd, IDC_EXECUTABLE), SW_HIDE);
632 git_repository_free(repository);
636 // CShellExt member functions (needed for IShellPropSheetExt)
637 STDMETHODIMP CShellExt::AddPages(LPFNADDPROPSHEETPAGE lpfnAddPage, LPARAM lParam)
639 __try
641 return AddPages_Wrap(lpfnAddPage, lParam);
643 __except(CCrashReport::Instance().SendReport(GetExceptionInformation()))
646 return E_FAIL;
649 STDMETHODIMP CShellExt::AddPages_Wrap(LPFNADDPROPSHEETPAGE lpfnAddPage, LPARAM lParam)
651 CString ProjectTopDir;
653 for (std::vector<stdstring>::iterator I = files_.begin(); I != files_.end(); ++I)
656 GitStatus svn = GitStatus();
657 if (svn.GetStatus(CTGitPath(I->c_str())) == (-2))
658 return S_OK; // file/directory not under version control
660 if (svn.status->entry == NULL)
661 return S_OK;
663 if( CTGitPath(I->c_str()).HasAdminDir(&ProjectTopDir))
664 break;
665 else
666 return S_OK;
669 if (files_.empty())
670 return S_OK;
672 LoadLangDll();
673 PROPSHEETPAGE psp;
674 SecureZeroMemory(&psp, sizeof(PROPSHEETPAGE));
675 HPROPSHEETPAGE hPage;
676 CGitPropertyPage *sheetpage = new (std::nothrow) CGitPropertyPage(files_);
678 psp.dwSize = sizeof (psp);
679 psp.dwFlags = PSP_USEREFPARENT | PSP_USETITLE | PSP_USEICONID | PSP_USECALLBACK;
680 psp.hInstance = g_hResInst;
681 psp.pszTemplate = MAKEINTRESOURCE(IDD_PROPPAGE);
682 psp.pszIcon = MAKEINTRESOURCE(IDI_APPSMALL);
683 psp.pszTitle = _T("Git");
684 psp.pfnDlgProc = (DLGPROC) PageProc;
685 psp.lParam = (LPARAM) sheetpage;
686 psp.pfnCallback = PropPageCallbackProc;
687 psp.pcRefParent = (UINT*)&g_cRefThisDll;
689 hPage = CreatePropertySheetPage (&psp);
691 if (hPage != NULL)
693 if (!lpfnAddPage (hPage, lParam))
695 delete sheetpage;
696 DestroyPropertySheetPage (hPage);
700 return S_OK;
703 STDMETHODIMP CShellExt::ReplacePage (UINT /*uPageID*/, LPFNADDPROPSHEETPAGE /*lpfnReplaceWith*/, LPARAM /*lParam*/)
705 return E_FAIL;