1 /* 2 * ReactOS Explorer 3 * 4 * Copyright 2009 Andrew Hill <ash77 at domain reactos.org> 5 * 6 * This library is free software; you can redistribute it and/or 7 * modify it under the terms of the GNU Lesser General Public 8 * License as published by the Free Software Foundation; either 9 * version 2.1 of the License, or (at your option) any later version. 10 * 11 * This library 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 GNU 14 * Lesser General Public License for more details. 15 * 16 * You should have received a copy of the GNU Lesser General Public 17 * License along with this library; if not, write to the Free Software 18 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 19 */ 20 21 /* 22 Implements a class that keeps track of a PIDL history and allows 23 navigation forward and backward. This really should be in shdocvw, but it 24 is not registered for external instantiation, and the entire IBrowserService 25 hierarchy that normally spans browseui and shdocvw are collapsed into one 26 hierarchy in browseui, so I am moving it to browseui for now. If someone 27 decides to refactor code later, it wouldn't be difficult to move it. 28 29 TODO: 30 ****Does original travel log update the current item in the Travel method before or after calling ITravelEntry::Invoke? 31 ****Change to load maximum size from registry 32 ****Add code to track current size 33 ****Fix InsertMenuEntries to not exceed limit of menu item ids provided. Perhaps the method should try to be intelligent and if there are 34 too many items, center around the current item? Could cause dispatch problems... 35 ****Move tool tip text templates to resources 36 **Simplify code in InsertMenuEntries 37 Implement UpdateExternal 38 Implement FindTravelEntry 39 Implement Clone 40 Implement Revert 41 42 */ 43 44 #include "precomp.h" 45 46 class CTravelEntry : 47 public CComObjectRootEx<CComMultiThreadModelNoCS>, 48 public ITravelEntry 49 { 50 public: 51 CTravelEntry *fNextEntry; 52 CTravelEntry *fPreviousEntry; 53 private: 54 LPITEMIDLIST fPIDL; 55 HGLOBAL fPersistState; 56 public: 57 CTravelEntry(); 58 ~CTravelEntry(); 59 HRESULT GetToolTipText(IUnknown *punk, LPWSTR pwzText) const; 60 long GetSize() const; 61 62 // *** ITravelEntry methods *** 63 STDMETHOD(Invoke)(IUnknown *punk) override; 64 STDMETHOD(Update)(IUnknown *punk, BOOL fIsLocalAnchor) override; 65 STDMETHOD(GetPidl)(LPITEMIDLIST *ppidl) override; 66 67 BEGIN_COM_MAP(CTravelEntry) 68 COM_INTERFACE_ENTRY_IID(IID_ITravelEntry, ITravelEntry) 69 END_COM_MAP() 70 }; 71 72 class CTravelLog : 73 public CComObjectRootEx<CComMultiThreadModelNoCS>, 74 public ITravelLog 75 { 76 private: 77 CTravelEntry *fListHead; 78 CTravelEntry *fListTail; 79 CTravelEntry *fCurrentEntry; 80 long fMaximumSize; 81 long fCurrentSize; 82 unsigned long fEntryCount; 83 public: 84 CTravelLog(); 85 ~CTravelLog(); 86 HRESULT Initialize(); 87 HRESULT FindRelativeEntry(int offset, CTravelEntry **foundEntry); 88 void DeleteChain(CTravelEntry *startHere); 89 void AppendEntry(CTravelEntry *afterEntry, CTravelEntry *newEntry); 90 public: 91 92 // *** ITravelLog methods *** 93 STDMETHOD(AddEntry)(IUnknown *punk, BOOL fIsLocalAnchor) override; 94 STDMETHOD(UpdateEntry)(IUnknown *punk, BOOL fIsLocalAnchor) override; 95 STDMETHOD(UpdateExternal)(IUnknown *punk, IUnknown *punkHLBrowseContext) override; 96 STDMETHOD(Travel)(IUnknown *punk, int iOffset) override; 97 STDMETHOD(GetTravelEntry)(IUnknown *punk, int iOffset, ITravelEntry **ppte) override; 98 STDMETHOD(FindTravelEntry)(IUnknown *punk, LPCITEMIDLIST pidl, ITravelEntry **ppte) override; 99 STDMETHOD(GetToolTipText)(IUnknown *punk, int iOffset, int idsTemplate, LPWSTR pwzText, DWORD cchText) override; 100 STDMETHOD(InsertMenuEntries)(IUnknown *punk, HMENU hmenu, int nPos, int idFirst, int idLast, DWORD dwFlags) override; 101 STDMETHOD(Clone)(ITravelLog **pptl) override; 102 STDMETHOD_(DWORD, CountEntries)(IUnknown *punk) override; 103 STDMETHOD(Revert)() override; 104 105 BEGIN_COM_MAP(CTravelLog) 106 COM_INTERFACE_ENTRY_IID(IID_ITravelLog, ITravelLog) 107 END_COM_MAP() 108 }; 109 110 CTravelEntry::CTravelEntry() 111 { 112 fNextEntry = NULL; 113 fPreviousEntry = NULL; 114 fPIDL = NULL; 115 fPersistState = NULL; 116 } 117 118 CTravelEntry::~CTravelEntry() 119 { 120 ILFree(fPIDL); 121 GlobalFree(fPersistState); 122 } 123 124 HRESULT CTravelEntry::GetToolTipText(IUnknown *punk, LPWSTR pwzText) const 125 { 126 HRESULT hResult; 127 128 hResult = ILGetDisplayNameEx(NULL, fPIDL, pwzText, ILGDN_NORMAL) ? S_OK : E_FAIL; 129 if (FAILED_UNEXPECTEDLY(hResult)) 130 return hResult; 131 132 return S_OK; 133 } 134 135 long CTravelEntry::GetSize() const 136 { 137 return 0; 138 } 139 140 HRESULT STDMETHODCALLTYPE CTravelEntry::Invoke(IUnknown *punk) 141 { 142 CComPtr<IPersistHistory> persistHistory; 143 CComPtr<IStream> globalStream; 144 HRESULT hResult; 145 146 TRACE("CTravelEntry::Invoke for IUnknown punk=%p\n", punk); 147 148 hResult = punk->QueryInterface(IID_PPV_ARG(IPersistHistory, &persistHistory)); 149 if (FAILED_UNEXPECTEDLY(hResult)) 150 return hResult; 151 hResult = CreateStreamOnHGlobal(fPersistState, FALSE, &globalStream); 152 if (FAILED_UNEXPECTEDLY(hResult)) 153 return hResult; 154 hResult = persistHistory->LoadHistory(globalStream, NULL); 155 if (FAILED_UNEXPECTEDLY(hResult)) 156 return hResult; 157 return S_OK; 158 } 159 160 HRESULT STDMETHODCALLTYPE CTravelEntry::Update(IUnknown *punk, BOOL fIsLocalAnchor) 161 { 162 CComPtr<ITravelLogClient> travelLogClient; 163 CComPtr<IPersistHistory> persistHistory; 164 CComPtr<IStream> globalStream; 165 WINDOWDATA windowData; 166 HRESULT hResult; 167 168 TRACE("CTravelEntry::Update for IUnknown punk=%p, fIsLocalAnchor=%s\n", punk, fIsLocalAnchor ? "TRUE" : "FALSE"); 169 170 if (TRACE_ON(browseui)) 171 { 172 WCHAR wch[MAX_PATH * 2]; 173 GetToolTipText(punk, wch); 174 TRACE("Updating entry with display name: %S\n", wch); 175 } 176 177 ZeroMemory(&windowData, sizeof(WINDOWDATA)); 178 ILFree(fPIDL); 179 fPIDL = NULL; 180 GlobalFree(fPersistState); 181 fPersistState = NULL; 182 hResult = punk->QueryInterface(IID_PPV_ARG(ITravelLogClient, &travelLogClient)); 183 if (FAILED_UNEXPECTEDLY(hResult)) 184 return hResult; 185 hResult = punk->QueryInterface(IID_PPV_ARG(IPersistHistory, &persistHistory)); 186 if (FAILED_UNEXPECTEDLY(hResult)) 187 return hResult; 188 hResult = CreateStreamOnHGlobal(NULL, FALSE, &globalStream); 189 if (FAILED_UNEXPECTEDLY(hResult)) 190 return hResult; 191 hResult = persistHistory->SaveHistory(globalStream); 192 if (FAILED_UNEXPECTEDLY(hResult)) 193 return hResult; 194 hResult = travelLogClient->GetWindowData(globalStream, &windowData); 195 if (FAILED_UNEXPECTEDLY(hResult)) 196 return hResult; 197 fPIDL = windowData.pidl; 198 // TODO: Properly free the windowData 199 hResult = GetHGlobalFromStream(globalStream, &fPersistState); 200 if (FAILED_UNEXPECTEDLY(hResult)) 201 return hResult; 202 203 if (TRACE_ON(browseui)) 204 { 205 WCHAR wch[MAX_PATH * 2]; 206 GetToolTipText(punk, wch); 207 TRACE("Updated entry display name is now: %S\n", wch); 208 } 209 210 return S_OK; 211 } 212 213 HRESULT STDMETHODCALLTYPE CTravelEntry::GetPidl(LPITEMIDLIST *ppidl) 214 { 215 if (ppidl == NULL) 216 return E_POINTER; 217 *ppidl = ILClone(fPIDL); 218 if (*ppidl == NULL) 219 return E_OUTOFMEMORY; 220 221 TRACE("CTravelEntry::GetPidl returning ppidl=%p\n", *ppidl); 222 223 return S_OK; 224 } 225 226 CTravelLog::CTravelLog() 227 { 228 fListHead = NULL; 229 fListTail = NULL; 230 fCurrentEntry = NULL; 231 fMaximumSize = 0; 232 fCurrentSize = 0; 233 fEntryCount = 0; 234 TRACE("CTravelLog created\n"); 235 } 236 237 CTravelLog::~CTravelLog() 238 { 239 CTravelEntry *anEntry; 240 CTravelEntry *next; 241 242 anEntry = fListHead; 243 while (anEntry != NULL) 244 { 245 next = anEntry->fNextEntry; 246 anEntry->Release(); 247 anEntry = next; 248 } 249 TRACE("CTravelLog destroyed\n"); 250 } 251 252 HRESULT CTravelLog::Initialize() 253 { 254 fMaximumSize = 1024 * 1024; // TODO: change to read this from registry 255 // Software\Microsoft\Windows\CurrentVersion\Explorer\TravelLog 256 // MaxSize 257 return S_OK; 258 } 259 260 HRESULT CTravelLog::FindRelativeEntry(int _offset, CTravelEntry **foundEntry) 261 { 262 CTravelEntry *curEntry; 263 int offset = _offset; 264 265 if (foundEntry == NULL) 266 return E_INVALIDARG; 267 268 *foundEntry = NULL; 269 curEntry = fCurrentEntry; 270 if (offset < 0) 271 { 272 while (offset < 0 && curEntry != NULL) 273 { 274 curEntry = curEntry->fPreviousEntry; 275 offset++; 276 } 277 } 278 else 279 { 280 while (offset > 0 && curEntry != NULL) 281 { 282 curEntry = curEntry->fNextEntry; 283 offset--; 284 } 285 } 286 if (curEntry == NULL) 287 return E_INVALIDARG; 288 289 *foundEntry = curEntry; 290 291 TRACE("CTravelLog::FindRelativeEntry for offset %d, returning %p\n", offset, *foundEntry); 292 293 return S_OK; 294 } 295 296 void CTravelLog::DeleteChain(CTravelEntry *startHere) 297 { 298 CTravelEntry *saveNext; 299 long itemSize; 300 301 TRACE("CTravelLog::DeleteChain deleting chain starting at %p\n", startHere); 302 303 long startEntryCount = fEntryCount; 304 305 if (startHere->fPreviousEntry != NULL) 306 { 307 startHere->fPreviousEntry->fNextEntry = NULL; 308 fListTail = startHere->fPreviousEntry; 309 } 310 else 311 { 312 fListHead = NULL; 313 fListTail = NULL; 314 } 315 while (startHere != NULL) 316 { 317 saveNext = startHere->fNextEntry; 318 itemSize = startHere->GetSize(); 319 fCurrentSize -= itemSize; 320 startHere->Release(); 321 startHere = saveNext; 322 fEntryCount--; 323 } 324 325 TRACE("CTravelLog::DeleteChain chain of %d items deleted\n", startEntryCount - fEntryCount); 326 } 327 328 void CTravelLog::AppendEntry(CTravelEntry *afterEntry, CTravelEntry *newEntry) 329 { 330 if (afterEntry == NULL) 331 { 332 TRACE("CTravelLog::AppendEntry appending %p after NULL. Resetting head and tail\n", newEntry); 333 fListHead = newEntry; 334 fListTail = newEntry; 335 } 336 else 337 { 338 TRACE("CTravelLog::AppendEntry appending %p after %p\n", newEntry, afterEntry); 339 newEntry->fNextEntry = afterEntry->fNextEntry; 340 afterEntry->fNextEntry = newEntry; 341 newEntry->fPreviousEntry = afterEntry; 342 if (newEntry->fNextEntry == NULL) 343 fListTail = newEntry; 344 else 345 newEntry->fNextEntry->fPreviousEntry = newEntry; 346 } 347 fEntryCount++; 348 } 349 350 HRESULT STDMETHODCALLTYPE CTravelLog::AddEntry(IUnknown *punk, BOOL fIsLocalAnchor) 351 { 352 CComObject<CTravelEntry> *newEntry; 353 long itemSize; 354 355 TRACE("CTravelLog::AddEntry for IUnknown punk=%p, fIsLocalAnchor=%s\n", punk, fIsLocalAnchor ? "TRUE" : "FALSE"); 356 357 if (punk == NULL) 358 return E_INVALIDARG; 359 ATLTRY (newEntry = new CComObject<CTravelEntry>); 360 if (newEntry == NULL) 361 return E_OUTOFMEMORY; 362 newEntry->AddRef(); 363 if (fCurrentEntry != NULL && fCurrentEntry->fNextEntry != NULL) 364 DeleteChain(fCurrentEntry->fNextEntry); 365 AppendEntry(fCurrentEntry, newEntry); 366 itemSize = newEntry->GetSize(); 367 fCurrentSize += itemSize; 368 fCurrentEntry = newEntry; 369 return S_OK; 370 } 371 372 HRESULT STDMETHODCALLTYPE CTravelLog::UpdateEntry(IUnknown *punk, BOOL fIsLocalAnchor) 373 { 374 if (punk == NULL) 375 return E_INVALIDARG; 376 if (fCurrentEntry == NULL) 377 return E_UNEXPECTED; 378 return fCurrentEntry->Update(punk, fIsLocalAnchor); 379 } 380 381 HRESULT STDMETHODCALLTYPE CTravelLog::UpdateExternal(IUnknown *punk, IUnknown *punkHLBrowseContext) 382 { 383 return E_NOTIMPL; 384 } 385 386 HRESULT STDMETHODCALLTYPE CTravelLog::Travel(IUnknown *punk, int iOffset) 387 { 388 CTravelEntry *destinationEntry; 389 HRESULT hResult; 390 391 TRACE("CTravelLog::Travel for IUnknown punk=%p at offset=%d\n", punk, iOffset); 392 393 hResult = FindRelativeEntry(iOffset, &destinationEntry); 394 if (FAILED_UNEXPECTEDLY(hResult)) 395 return hResult; 396 fCurrentEntry = destinationEntry; 397 hResult = destinationEntry->Invoke(punk); 398 if (FAILED_UNEXPECTEDLY(hResult)) 399 return hResult; 400 return S_OK; 401 } 402 403 HRESULT STDMETHODCALLTYPE CTravelLog::GetTravelEntry(IUnknown *punk, int iOffset, ITravelEntry **ppte) 404 { 405 CTravelEntry *destinationEntry; 406 HRESULT hResult; 407 408 hResult = FindRelativeEntry(iOffset, &destinationEntry); 409 if (FAILED(hResult)) 410 return hResult; 411 hResult = destinationEntry->QueryInterface(IID_PPV_ARG(ITravelEntry, ppte)); 412 if (FAILED_UNEXPECTEDLY(hResult)) 413 return hResult; 414 415 TRACE("CTravelLog::GetTravelEntry for IUnknown punk=%p at offset=%d returning %p\n", punk, iOffset, *ppte); 416 417 return hResult; 418 } 419 420 HRESULT STDMETHODCALLTYPE CTravelLog::FindTravelEntry(IUnknown *punk, LPCITEMIDLIST pidl, ITravelEntry **ppte) 421 { 422 if (ppte == NULL) 423 return E_POINTER; 424 if (punk == NULL || pidl == NULL) 425 return E_INVALIDARG; 426 427 UNIMPLEMENTED; 428 429 return E_NOTIMPL; 430 } 431 432 HRESULT STDMETHODCALLTYPE CTravelLog::GetToolTipText(IUnknown *punk, int iOffset, int idsTemplate, LPWSTR pwzText, DWORD cchText) 433 { 434 CTravelEntry *destinationEntry; 435 wchar_t tempString[MAX_PATH]; 436 wchar_t templateString[200]; 437 HRESULT hResult; 438 439 if (pwzText == NULL) 440 return E_POINTER; 441 if (punk == NULL || cchText == 0) 442 return E_INVALIDARG; 443 hResult = FindRelativeEntry(iOffset, &destinationEntry); 444 if (FAILED_UNEXPECTEDLY(hResult)) 445 return hResult; 446 hResult = destinationEntry->GetToolTipText(punk, tempString); 447 if (FAILED_UNEXPECTEDLY(hResult)) 448 return hResult; 449 if (iOffset < 0) 450 { 451 if(LoadStringW(_AtlBaseModule.GetResourceInstance(), 452 IDS_BACK, templateString, sizeof(templateString) / sizeof(wchar_t)) == 0) 453 return HRESULT_FROM_WIN32(GetLastError()); 454 } 455 else 456 { 457 if(LoadStringW(_AtlBaseModule.GetResourceInstance(), 458 IDS_FORWARD, templateString, sizeof(templateString) / sizeof(wchar_t)) == 0) 459 return HRESULT_FROM_WIN32(GetLastError()); 460 } 461 _snwprintf(pwzText, cchText, templateString, tempString); 462 463 TRACE("CTravelLog::GetToolTipText for IUnknown punk=%p at offset=%d returning L\"%S\"\n", punk, iOffset, pwzText); 464 465 return S_OK; 466 } 467 468 static void FixAmpersands(wchar_t *buffer) 469 { 470 wchar_t tempBuffer[MAX_PATH * 2]; 471 wchar_t ch; 472 wchar_t *srcPtr; 473 wchar_t *dstPtr; 474 475 srcPtr = buffer; 476 dstPtr = tempBuffer; 477 while (*srcPtr != 0) 478 { 479 ch = *srcPtr++; 480 *dstPtr++ = ch; 481 if (ch == '&') 482 *dstPtr++ = '&'; 483 } 484 *dstPtr = 0; 485 wcscpy(buffer, tempBuffer); 486 } 487 488 HRESULT STDMETHODCALLTYPE CTravelLog::InsertMenuEntries(IUnknown *punk, HMENU hmenu, 489 int nPos, int idFirst, int idLast, DWORD dwFlags) 490 { 491 CTravelEntry *currentItem; 492 MENUITEMINFO menuItemInfo; 493 wchar_t itemTextBuffer[MAX_PATH * 2]; 494 HRESULT hResult; 495 496 TRACE("CTravelLog::InsertMenuEntries for IUnknown punk=%p, nPos=%d, idFirst=%d, idLast=%d\n", punk); 497 498 // TLMENUF_BACK - include back entries 499 // TLMENUF_INCLUDECURRENT - include current entry, if TLMENUF_CHECKCURRENT then check the entry 500 // TLMENUF_FORE - include next entries 501 // if fore+back, list from oldest to newest 502 // if back, list from newest to oldest 503 // if fore, list from newest to oldest 504 505 // don't forget to patch ampersands before adding to menu 506 if (punk == NULL) 507 return E_INVALIDARG; 508 if (idLast <= idFirst) 509 return E_INVALIDARG; 510 menuItemInfo.cbSize = sizeof(menuItemInfo); 511 menuItemInfo.fMask = MIIM_FTYPE | MIIM_ID | MIIM_STATE | MIIM_STRING; 512 menuItemInfo.fType = MFT_STRING; 513 menuItemInfo.wID = idFirst; 514 menuItemInfo.fState = MFS_ENABLED; 515 menuItemInfo.dwTypeData = itemTextBuffer; 516 if ((dwFlags & TLMENUF_BACK) != 0) 517 { 518 if ((dwFlags & TLMENUF_FORE) != 0) 519 { 520 currentItem = fCurrentEntry; 521 if (currentItem != NULL) 522 { 523 while (currentItem->fPreviousEntry != NULL) 524 currentItem = currentItem->fPreviousEntry; 525 } 526 while (currentItem != fCurrentEntry) 527 { 528 hResult = currentItem->GetToolTipText(punk, itemTextBuffer); 529 if (SUCCEEDED(hResult)) 530 { 531 FixAmpersands(itemTextBuffer); 532 TRACE("CTravelLog::InsertMenuEntries adding entry L\"%S\"/L\"%S\" with id %d\n", itemTextBuffer, menuItemInfo.dwTypeData, menuItemInfo.wID); 533 if (InsertMenuItem(hmenu, nPos, TRUE, &menuItemInfo)) 534 { 535 nPos++; 536 menuItemInfo.wID++; 537 } 538 } 539 currentItem = currentItem->fNextEntry; 540 } 541 } 542 else 543 { 544 currentItem = fCurrentEntry; 545 if (currentItem != NULL) 546 currentItem = currentItem->fPreviousEntry; 547 while (currentItem != NULL) 548 { 549 hResult = currentItem->GetToolTipText(punk, itemTextBuffer); 550 if (SUCCEEDED(hResult)) 551 { 552 FixAmpersands(itemTextBuffer); 553 TRACE("CTravelLog::InsertMenuEntries adding entry L\"%S\"/L\"%S\" with id %d\n", itemTextBuffer, menuItemInfo.dwTypeData, menuItemInfo.wID); 554 if (InsertMenuItem(hmenu, nPos, TRUE, &menuItemInfo)) 555 { 556 nPos++; 557 menuItemInfo.wID++; 558 } 559 } 560 currentItem = currentItem->fPreviousEntry; 561 } 562 } 563 } 564 if ((dwFlags & TLMENUF_INCLUDECURRENT) != 0) 565 { 566 if (fCurrentEntry != NULL) 567 { 568 hResult = fCurrentEntry->GetToolTipText(punk, itemTextBuffer); 569 if (SUCCEEDED(hResult)) 570 { 571 FixAmpersands(itemTextBuffer); 572 if ((dwFlags & TLMENUF_CHECKCURRENT) != 0) 573 menuItemInfo.fState |= MFS_CHECKED; 574 TRACE("CTravelLog::InsertMenuEntries adding entry L\"%S\"/L\"%S\" with id %d\n", itemTextBuffer, menuItemInfo.dwTypeData, menuItemInfo.wID); 575 if (InsertMenuItem(hmenu, nPos, TRUE, &menuItemInfo)) 576 { 577 nPos++; 578 menuItemInfo.wID++; 579 } 580 menuItemInfo.fState &= ~MFS_CHECKED; 581 } 582 } 583 } 584 if ((dwFlags & TLMENUF_FORE) != 0) 585 { 586 currentItem = fCurrentEntry; 587 if (currentItem != NULL) 588 currentItem = currentItem->fNextEntry; 589 while (currentItem != NULL) 590 { 591 hResult = currentItem->GetToolTipText(punk, itemTextBuffer); 592 if (SUCCEEDED(hResult)) 593 { 594 FixAmpersands(itemTextBuffer); 595 TRACE("CTravelLog::InsertMenuEntries adding entry L\"%S\"/L\"%S\" with id %d\n", itemTextBuffer, menuItemInfo.dwTypeData, menuItemInfo.wID); 596 if (InsertMenuItem(hmenu, nPos, TRUE, &menuItemInfo)) 597 { 598 nPos++; 599 menuItemInfo.wID++; 600 } 601 } 602 currentItem = currentItem->fNextEntry; 603 } 604 } 605 return S_OK; 606 } 607 608 HRESULT STDMETHODCALLTYPE CTravelLog::Clone(ITravelLog **pptl) 609 { 610 if (pptl == NULL) 611 return E_POINTER; 612 *pptl = NULL; 613 // duplicate the log 614 UNIMPLEMENTED; 615 return E_NOTIMPL; 616 } 617 618 DWORD STDMETHODCALLTYPE CTravelLog::CountEntries(IUnknown *punk) 619 { 620 if (punk == NULL) 621 return E_INVALIDARG; 622 return fEntryCount; 623 } 624 625 HRESULT STDMETHODCALLTYPE CTravelLog::Revert() 626 { 627 // remove the current entry? 628 UNIMPLEMENTED; 629 return E_NOTIMPL; 630 } 631 632 HRESULT CTravelLog_CreateInstance(REFIID riid, void **ppv) 633 { 634 return ShellObjectCreatorInit<CTravelLog>(riid, ppv); 635 } 636