xref: /reactos/dll/win32/browseui/travellog.cpp (revision 74113742)
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     virtual HRESULT STDMETHODCALLTYPE Invoke(IUnknown *punk);
64     virtual HRESULT STDMETHODCALLTYPE Update(IUnknown *punk, BOOL fIsLocalAnchor);
65     virtual HRESULT STDMETHODCALLTYPE GetPidl(LPITEMIDLIST *ppidl);
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     virtual HRESULT STDMETHODCALLTYPE AddEntry(IUnknown *punk, BOOL fIsLocalAnchor);
94     virtual HRESULT STDMETHODCALLTYPE UpdateEntry(IUnknown *punk, BOOL fIsLocalAnchor);
95     virtual HRESULT STDMETHODCALLTYPE UpdateExternal(IUnknown *punk, IUnknown *punkHLBrowseContext);
96     virtual HRESULT STDMETHODCALLTYPE Travel(IUnknown *punk, int iOffset);
97     virtual HRESULT STDMETHODCALLTYPE GetTravelEntry(IUnknown *punk, int iOffset, ITravelEntry **ppte);
98     virtual HRESULT STDMETHODCALLTYPE FindTravelEntry(IUnknown *punk, LPCITEMIDLIST pidl, ITravelEntry **ppte);
99     virtual HRESULT STDMETHODCALLTYPE GetToolTipText(IUnknown *punk, int iOffset, int idsTemplate, LPWSTR pwzText, DWORD cchText);
100     virtual HRESULT STDMETHODCALLTYPE InsertMenuEntries(IUnknown *punk, HMENU hmenu, int nPos, int idFirst, int idLast, DWORD dwFlags);
101     virtual HRESULT STDMETHODCALLTYPE Clone(ITravelLog **pptl);
102     virtual DWORD STDMETHODCALLTYPE CountEntries(IUnknown *punk);
103     virtual HRESULT STDMETHODCALLTYPE Revert();
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 : S_FALSE;
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 
171     WCHAR wch[MAX_PATH * 2];
172     GetToolTipText(punk, wch);
173     TRACE("Updating entry with display name: %S\n", wch);
174 
175     ILFree(fPIDL);
176     fPIDL = NULL;
177     GlobalFree(fPersistState);
178     fPersistState = NULL;
179     hResult = punk->QueryInterface(IID_PPV_ARG(ITravelLogClient, &travelLogClient));
180     if (FAILED_UNEXPECTEDLY(hResult))
181         return hResult;
182     hResult = punk->QueryInterface(IID_PPV_ARG(IPersistHistory, &persistHistory));
183     if (FAILED_UNEXPECTEDLY(hResult))
184         return hResult;
185     hResult = CreateStreamOnHGlobal(NULL, FALSE, &globalStream);
186     if (FAILED_UNEXPECTEDLY(hResult))
187         return hResult;
188     hResult = persistHistory->SaveHistory(globalStream);
189     if (FAILED_UNEXPECTEDLY(hResult))
190         return hResult;
191     hResult = travelLogClient->GetWindowData(globalStream, &windowData);
192     if (FAILED_UNEXPECTEDLY(hResult))
193         return hResult;
194     fPIDL = windowData.pidl;
195     // TODO: Properly free the windowData
196     hResult = GetHGlobalFromStream(globalStream, &fPersistState);
197     if (FAILED_UNEXPECTEDLY(hResult))
198         return hResult;
199 
200     GetToolTipText(punk, wch);
201     TRACE("Updated entry display name is now: %S\n", wch);
202 
203     return S_OK;
204 }
205 
206 HRESULT STDMETHODCALLTYPE CTravelEntry::GetPidl(LPITEMIDLIST *ppidl)
207 {
208     if (ppidl == NULL)
209         return E_POINTER;
210     *ppidl = ILClone(fPIDL);
211     if (*ppidl == NULL)
212         return E_OUTOFMEMORY;
213 
214     TRACE("CTravelEntry::GetPidl returning ppidl=%p\n", *ppidl);
215 
216     return S_OK;
217 }
218 
219 CTravelLog::CTravelLog()
220 {
221     fListHead = NULL;
222     fListTail = NULL;
223     fCurrentEntry = NULL;
224     fMaximumSize = 0;
225     fCurrentSize = 0;
226     fEntryCount = 0;
227     TRACE("CTravelLog created\n");
228 }
229 
230 CTravelLog::~CTravelLog()
231 {
232     CTravelEntry                            *anEntry;
233     CTravelEntry                            *next;
234 
235     anEntry = fListHead;
236     while (anEntry != NULL)
237     {
238         next = anEntry->fNextEntry;
239         anEntry->Release();
240         anEntry = next;
241     }
242     TRACE("CTravelLog destroyed\n");
243 }
244 
245 HRESULT CTravelLog::Initialize()
246 {
247     FIXME("CTravelLog::Initialize using hardcoded fMaximumSize.\n");
248     fMaximumSize = 1024 * 1024;         // TODO: change to read this from registry
249     // Software\Microsoft\Windows\CurrentVersion\Explorer\TravelLog
250     // MaxSize
251     return S_OK;
252 }
253 
254 HRESULT CTravelLog::FindRelativeEntry(int _offset, CTravelEntry **foundEntry)
255 {
256     CTravelEntry                            *curEntry;
257     int offset = _offset;
258 
259     if (foundEntry == NULL)
260         return E_INVALIDARG;
261 
262     *foundEntry = NULL;
263     curEntry = fCurrentEntry;
264     if (offset < 0)
265     {
266         while (offset < 0 && curEntry != NULL)
267         {
268             curEntry = curEntry->fPreviousEntry;
269             offset++;
270         }
271     }
272     else
273     {
274         while (offset > 0 && curEntry != NULL)
275         {
276             curEntry = curEntry->fNextEntry;
277             offset--;
278         }
279     }
280     if (curEntry == NULL)
281         return E_INVALIDARG;
282 
283     *foundEntry = curEntry;
284 
285     TRACE("CTravelLog::FindRelativeEntry for offset %d, returning %p\n", offset, *foundEntry);
286 
287     return S_OK;
288 }
289 
290 void CTravelLog::DeleteChain(CTravelEntry *startHere)
291 {
292     CTravelEntry                            *saveNext;
293     long                                    itemSize;
294 
295     TRACE("CTravelLog::DeleteChain deleting chain starting at %p\n", startHere);
296 
297     long startEntryCount = fEntryCount;
298 
299     if (startHere->fPreviousEntry != NULL)
300     {
301         startHere->fPreviousEntry->fNextEntry = NULL;
302         fListTail = startHere->fPreviousEntry;
303     }
304     else
305     {
306         fListHead = NULL;
307         fListTail = NULL;
308     }
309     while (startHere != NULL)
310     {
311         saveNext = startHere->fNextEntry;
312         itemSize = startHere->GetSize();
313         fCurrentSize -= itemSize;
314         startHere->Release();
315         startHere = saveNext;
316         fEntryCount--;
317     }
318 
319     TRACE("CTravelLog::DeleteChain chain of %d items deleted\n", startEntryCount - fEntryCount);
320 }
321 
322 void CTravelLog::AppendEntry(CTravelEntry *afterEntry, CTravelEntry *newEntry)
323 {
324     if (afterEntry == NULL)
325     {
326         TRACE("CTravelLog::AppendEntry appending %p after NULL. Resetting head and tail\n", newEntry);
327         fListHead = newEntry;
328         fListTail = newEntry;
329     }
330     else
331     {
332         TRACE("CTravelLog::AppendEntry appending %p after %p\n", newEntry, afterEntry);
333         newEntry->fNextEntry = afterEntry->fNextEntry;
334         afterEntry->fNextEntry = newEntry;
335         newEntry->fPreviousEntry = afterEntry;
336         if (newEntry->fNextEntry == NULL)
337             fListTail = newEntry;
338         else
339             newEntry->fNextEntry->fPreviousEntry = newEntry;
340     }
341     fEntryCount++;
342 }
343 
344 HRESULT STDMETHODCALLTYPE CTravelLog::AddEntry(IUnknown *punk, BOOL fIsLocalAnchor)
345 {
346     CComObject<CTravelEntry>                *newEntry;
347     long                                    itemSize;
348 
349     TRACE("CTravelLog::AddEntry for IUnknown punk=%p, fIsLocalAnchor=%s\n", punk, fIsLocalAnchor ? "TRUE" : "FALSE");
350 
351     if (punk == NULL)
352         return E_INVALIDARG;
353     ATLTRY (newEntry = new CComObject<CTravelEntry>);
354     if (newEntry == NULL)
355         return E_OUTOFMEMORY;
356     newEntry->AddRef();
357     if (fCurrentEntry != NULL && fCurrentEntry->fNextEntry != NULL)
358         DeleteChain(fCurrentEntry->fNextEntry);
359     AppendEntry(fCurrentEntry, newEntry);
360     itemSize = newEntry->GetSize();
361     fCurrentSize += itemSize;
362     fCurrentEntry = newEntry;
363     return S_OK;
364 }
365 
366 HRESULT STDMETHODCALLTYPE CTravelLog::UpdateEntry(IUnknown *punk, BOOL fIsLocalAnchor)
367 {
368     if (punk == NULL)
369         return E_INVALIDARG;
370     if (fCurrentEntry == NULL)
371         return E_UNEXPECTED;
372     return fCurrentEntry->Update(punk, fIsLocalAnchor);
373 }
374 
375 HRESULT STDMETHODCALLTYPE CTravelLog::UpdateExternal(IUnknown *punk, IUnknown *punkHLBrowseContext)
376 {
377     return E_NOTIMPL;
378 }
379 
380 HRESULT STDMETHODCALLTYPE CTravelLog::Travel(IUnknown *punk, int iOffset)
381 {
382     CTravelEntry                            *destinationEntry;
383     HRESULT                                 hResult;
384 
385     TRACE("CTravelLog::Travel for IUnknown punk=%p at offset=%d\n", punk, iOffset);
386 
387     hResult = FindRelativeEntry(iOffset, &destinationEntry);
388     if (FAILED_UNEXPECTEDLY(hResult))
389         return hResult;
390     fCurrentEntry = destinationEntry;
391     hResult = destinationEntry->Invoke(punk);
392     if (FAILED_UNEXPECTEDLY(hResult))
393         return hResult;
394     return S_OK;
395 }
396 
397 HRESULT STDMETHODCALLTYPE CTravelLog::GetTravelEntry(IUnknown *punk, int iOffset, ITravelEntry **ppte)
398 {
399     CTravelEntry                            *destinationEntry;
400     HRESULT                                 hResult;
401 
402     hResult = FindRelativeEntry(iOffset, &destinationEntry);
403     if (FAILED_UNEXPECTEDLY(hResult))
404         return hResult;
405     hResult = destinationEntry->QueryInterface(IID_PPV_ARG(ITravelEntry, ppte));
406     if (FAILED_UNEXPECTEDLY(hResult))
407         return hResult;
408 
409     TRACE("CTravelLog::GetTravelEntry for IUnknown punk=%p at offset=%d returning %p\n", punk, iOffset, *ppte);
410 
411     return hResult;
412 }
413 
414 HRESULT STDMETHODCALLTYPE CTravelLog::FindTravelEntry(IUnknown *punk, LPCITEMIDLIST pidl, ITravelEntry **ppte)
415 {
416     if (ppte == NULL)
417         return E_POINTER;
418     if (punk == NULL || pidl == NULL)
419         return E_INVALIDARG;
420 
421     UNIMPLEMENTED;
422 
423     return E_NOTIMPL;
424 }
425 
426 HRESULT STDMETHODCALLTYPE CTravelLog::GetToolTipText(IUnknown *punk, int iOffset, int idsTemplate, LPWSTR pwzText, DWORD cchText)
427 {
428     CTravelEntry                            *destinationEntry;
429     wchar_t                                 tempString[MAX_PATH];
430     wchar_t                                 templateString[200];
431     HRESULT                                 hResult;
432 
433     if (pwzText == NULL)
434         return E_POINTER;
435     if (punk == NULL || cchText == 0)
436         return E_INVALIDARG;
437     hResult = FindRelativeEntry(iOffset, &destinationEntry);
438     if (FAILED_UNEXPECTEDLY(hResult))
439         return hResult;
440     hResult = destinationEntry->GetToolTipText(punk, tempString);
441     if (FAILED_UNEXPECTEDLY(hResult))
442         return hResult;
443     if (iOffset < 0)
444     {
445         wcscpy(templateString, L"Back to %s");
446     }
447     else
448     {
449         wcscpy(templateString, L"Forward to %s");
450     }
451     _snwprintf(pwzText, cchText, templateString, tempString);
452 
453     TRACE("CTravelLog::GetToolTipText for IUnknown punk=%p at offset=%d returning L\"%S\"\n", punk, iOffset, pwzText);
454 
455     return S_OK;
456 }
457 
458 static void FixAmpersands(wchar_t *buffer)
459 {
460     wchar_t                                 tempBuffer[MAX_PATH * 2];
461     wchar_t                                 ch;
462     wchar_t                                 *srcPtr;
463     wchar_t                                 *dstPtr;
464 
465     srcPtr = buffer;
466     dstPtr = tempBuffer;
467     while (*srcPtr != 0)
468     {
469         ch = *srcPtr++;
470         *dstPtr++ = ch;
471         if (ch == '&')
472             *dstPtr++ = '&';
473     }
474     *dstPtr = 0;
475     wcscpy(buffer, tempBuffer);
476 }
477 
478 HRESULT STDMETHODCALLTYPE CTravelLog::InsertMenuEntries(IUnknown *punk, HMENU hmenu,
479     int nPos, int idFirst, int idLast, DWORD dwFlags)
480 {
481     CTravelEntry                            *currentItem;
482     MENUITEMINFO                            menuItemInfo;
483     wchar_t                                 itemTextBuffer[MAX_PATH * 2];
484     HRESULT                                 hResult;
485 
486     TRACE("CTravelLog::InsertMenuEntries for IUnknown punk=%p, nPos=%d, idFirst=%d, idLast=%d\n", punk);
487 
488     // TLMENUF_BACK - include back entries
489     // TLMENUF_INCLUDECURRENT - include current entry, if TLMENUF_CHECKCURRENT then check the entry
490     // TLMENUF_FORE - include next entries
491     // if fore+back, list from oldest to newest
492     // if back, list from newest to oldest
493     // if fore, list from newest to oldest
494 
495     // don't forget to patch ampersands before adding to menu
496     if (punk == NULL)
497         return E_INVALIDARG;
498     if (idLast <= idFirst)
499         return E_INVALIDARG;
500     menuItemInfo.cbSize = sizeof(menuItemInfo);
501     menuItemInfo.fMask = MIIM_FTYPE | MIIM_ID | MIIM_STATE | MIIM_STRING;
502     menuItemInfo.fType = MFT_STRING;
503     menuItemInfo.wID = idFirst;
504     menuItemInfo.fState = MFS_ENABLED;
505     menuItemInfo.dwTypeData = itemTextBuffer;
506     if ((dwFlags & TLMENUF_BACK) != 0)
507     {
508         if ((dwFlags & TLMENUF_FORE) != 0)
509         {
510             currentItem = fCurrentEntry;
511             if (currentItem != NULL)
512             {
513                 while (currentItem->fPreviousEntry != NULL)
514                     currentItem = currentItem->fPreviousEntry;
515             }
516             while (currentItem != fCurrentEntry)
517             {
518                 hResult = currentItem->GetToolTipText(punk, itemTextBuffer);
519                 if (SUCCEEDED(hResult))
520                 {
521                     FixAmpersands(itemTextBuffer);
522                     TRACE("CTravelLog::InsertMenuEntries adding entry L\"%S\"/L\"%S\" with id %d\n", itemTextBuffer, menuItemInfo.dwTypeData, menuItemInfo.wID);
523                     if (InsertMenuItem(hmenu, nPos, TRUE, &menuItemInfo))
524                     {
525                         nPos++;
526                         menuItemInfo.wID++;
527                     }
528                 }
529                 currentItem = currentItem->fNextEntry;
530             }
531         }
532         else
533         {
534             currentItem = fCurrentEntry;
535             if (currentItem != NULL)
536                 currentItem = currentItem->fPreviousEntry;
537             while (currentItem != NULL)
538             {
539                 hResult = currentItem->GetToolTipText(punk, itemTextBuffer);
540                 if (SUCCEEDED(hResult))
541                 {
542                     FixAmpersands(itemTextBuffer);
543                     TRACE("CTravelLog::InsertMenuEntries adding entry L\"%S\"/L\"%S\" with id %d\n", itemTextBuffer, menuItemInfo.dwTypeData, menuItemInfo.wID);
544                     if (InsertMenuItem(hmenu, nPos, TRUE, &menuItemInfo))
545                     {
546                         nPos++;
547                         menuItemInfo.wID++;
548                     }
549                 }
550                 currentItem = currentItem->fPreviousEntry;
551             }
552         }
553     }
554     if ((dwFlags & TLMENUF_INCLUDECURRENT) != 0)
555     {
556         if (fCurrentEntry != NULL)
557         {
558             hResult = fCurrentEntry->GetToolTipText(punk, itemTextBuffer);
559             if (SUCCEEDED(hResult))
560             {
561                 FixAmpersands(itemTextBuffer);
562                 if ((dwFlags & TLMENUF_CHECKCURRENT) != 0)
563                     menuItemInfo.fState |= MFS_CHECKED;
564                 TRACE("CTravelLog::InsertMenuEntries adding entry L\"%S\"/L\"%S\" with id %d\n", itemTextBuffer, menuItemInfo.dwTypeData, menuItemInfo.wID);
565                 if (InsertMenuItem(hmenu, nPos, TRUE, &menuItemInfo))
566                 {
567                     nPos++;
568                     menuItemInfo.wID++;
569                 }
570                 menuItemInfo.fState &= ~MFS_CHECKED;
571             }
572         }
573     }
574     if ((dwFlags & TLMENUF_FORE) != 0)
575     {
576         currentItem = fCurrentEntry;
577         if (currentItem != NULL)
578             currentItem = currentItem->fNextEntry;
579         while (currentItem != NULL)
580         {
581             hResult = currentItem->GetToolTipText(punk, itemTextBuffer);
582             if (SUCCEEDED(hResult))
583             {
584                 FixAmpersands(itemTextBuffer);
585                 TRACE("CTravelLog::InsertMenuEntries adding entry L\"%S\"/L\"%S\" with id %d\n", itemTextBuffer, menuItemInfo.dwTypeData, menuItemInfo.wID);
586                 if (InsertMenuItem(hmenu, nPos, TRUE, &menuItemInfo))
587                 {
588                     nPos++;
589                     menuItemInfo.wID++;
590                 }
591             }
592             currentItem = currentItem->fNextEntry;
593         }
594     }
595     return S_OK;
596 }
597 
598 HRESULT STDMETHODCALLTYPE CTravelLog::Clone(ITravelLog **pptl)
599 {
600     if (pptl == NULL)
601         return E_POINTER;
602     *pptl = NULL;
603     // duplicate the log
604     UNIMPLEMENTED;
605     return E_NOTIMPL;
606 }
607 
608 DWORD STDMETHODCALLTYPE CTravelLog::CountEntries(IUnknown *punk)
609 {
610     if (punk == NULL)
611         return E_INVALIDARG;
612     return fEntryCount;
613 }
614 
615 HRESULT STDMETHODCALLTYPE CTravelLog::Revert()
616 {
617     // remove the current entry?
618     UNIMPLEMENTED;
619     return E_NOTIMPL;
620 }
621 
622 HRESULT CreateTravelLog(REFIID riid, void **ppv)
623 {
624     CComObject<CTravelLog>                  *theTravelLog;
625     HRESULT                                 hResult;
626 
627     if (ppv == NULL)
628         return E_POINTER;
629     *ppv = NULL;
630     ATLTRY (theTravelLog = new CComObject<CTravelLog>);
631     if (theTravelLog == NULL)
632         return E_OUTOFMEMORY;
633     hResult = theTravelLog->QueryInterface(riid, reinterpret_cast<void **>(ppv));
634     if (FAILED_UNEXPECTEDLY(hResult))
635     {
636         delete theTravelLog;
637         return hResult;
638     }
639     hResult = theTravelLog->Initialize();
640     if (FAILED_UNEXPECTEDLY(hResult))
641         return hResult;
642     return S_OK;
643 }
644