xref: /reactos/dll/win32/browseui/travellog.cpp (revision 53221834)
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 : 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 
171     WCHAR wch[MAX_PATH * 2];
172     GetToolTipText(punk, wch);
173     TRACE("Updating entry with display name: %S\n", wch);
174 
175     ZeroMemory(&windowData, sizeof(WINDOWDATA));
176     ILFree(fPIDL);
177     fPIDL = NULL;
178     GlobalFree(fPersistState);
179     fPersistState = NULL;
180     hResult = punk->QueryInterface(IID_PPV_ARG(ITravelLogClient, &travelLogClient));
181     if (FAILED_UNEXPECTEDLY(hResult))
182         return hResult;
183     hResult = punk->QueryInterface(IID_PPV_ARG(IPersistHistory, &persistHistory));
184     if (FAILED_UNEXPECTEDLY(hResult))
185         return hResult;
186     hResult = CreateStreamOnHGlobal(NULL, FALSE, &globalStream);
187     if (FAILED_UNEXPECTEDLY(hResult))
188         return hResult;
189     hResult = persistHistory->SaveHistory(globalStream);
190     if (FAILED_UNEXPECTEDLY(hResult))
191         return hResult;
192     hResult = travelLogClient->GetWindowData(globalStream, &windowData);
193     if (FAILED_UNEXPECTEDLY(hResult))
194         return hResult;
195     fPIDL = windowData.pidl;
196     // TODO: Properly free the windowData
197     hResult = GetHGlobalFromStream(globalStream, &fPersistState);
198     if (FAILED_UNEXPECTEDLY(hResult))
199         return hResult;
200 
201     GetToolTipText(punk, wch);
202     TRACE("Updated entry display name is now: %S\n", wch);
203 
204     return S_OK;
205 }
206 
207 HRESULT STDMETHODCALLTYPE CTravelEntry::GetPidl(LPITEMIDLIST *ppidl)
208 {
209     if (ppidl == NULL)
210         return E_POINTER;
211     *ppidl = ILClone(fPIDL);
212     if (*ppidl == NULL)
213         return E_OUTOFMEMORY;
214 
215     TRACE("CTravelEntry::GetPidl returning ppidl=%p\n", *ppidl);
216 
217     return S_OK;
218 }
219 
220 CTravelLog::CTravelLog()
221 {
222     fListHead = NULL;
223     fListTail = NULL;
224     fCurrentEntry = NULL;
225     fMaximumSize = 0;
226     fCurrentSize = 0;
227     fEntryCount = 0;
228     TRACE("CTravelLog created\n");
229 }
230 
231 CTravelLog::~CTravelLog()
232 {
233     CTravelEntry                            *anEntry;
234     CTravelEntry                            *next;
235 
236     anEntry = fListHead;
237     while (anEntry != NULL)
238     {
239         next = anEntry->fNextEntry;
240         anEntry->Release();
241         anEntry = next;
242     }
243     TRACE("CTravelLog destroyed\n");
244 }
245 
246 HRESULT CTravelLog::Initialize()
247 {
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(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         if(LoadStringW(_AtlBaseModule.GetResourceInstance(),
446                             IDS_BACK, templateString, sizeof(templateString) / sizeof(wchar_t)) == 0)
447             return HRESULT_FROM_WIN32(GetLastError());
448     }
449     else
450     {
451         if(LoadStringW(_AtlBaseModule.GetResourceInstance(),
452                             IDS_FORWARD, templateString, sizeof(templateString) / sizeof(wchar_t)) == 0)
453             return HRESULT_FROM_WIN32(GetLastError());
454     }
455     _snwprintf(pwzText, cchText, templateString, tempString);
456 
457     TRACE("CTravelLog::GetToolTipText for IUnknown punk=%p at offset=%d returning L\"%S\"\n", punk, iOffset, pwzText);
458 
459     return S_OK;
460 }
461 
462 static void FixAmpersands(wchar_t *buffer)
463 {
464     wchar_t                                 tempBuffer[MAX_PATH * 2];
465     wchar_t                                 ch;
466     wchar_t                                 *srcPtr;
467     wchar_t                                 *dstPtr;
468 
469     srcPtr = buffer;
470     dstPtr = tempBuffer;
471     while (*srcPtr != 0)
472     {
473         ch = *srcPtr++;
474         *dstPtr++ = ch;
475         if (ch == '&')
476             *dstPtr++ = '&';
477     }
478     *dstPtr = 0;
479     wcscpy(buffer, tempBuffer);
480 }
481 
482 HRESULT STDMETHODCALLTYPE CTravelLog::InsertMenuEntries(IUnknown *punk, HMENU hmenu,
483     int nPos, int idFirst, int idLast, DWORD dwFlags)
484 {
485     CTravelEntry                            *currentItem;
486     MENUITEMINFO                            menuItemInfo;
487     wchar_t                                 itemTextBuffer[MAX_PATH * 2];
488     HRESULT                                 hResult;
489 
490     TRACE("CTravelLog::InsertMenuEntries for IUnknown punk=%p, nPos=%d, idFirst=%d, idLast=%d\n", punk);
491 
492     // TLMENUF_BACK - include back entries
493     // TLMENUF_INCLUDECURRENT - include current entry, if TLMENUF_CHECKCURRENT then check the entry
494     // TLMENUF_FORE - include next entries
495     // if fore+back, list from oldest to newest
496     // if back, list from newest to oldest
497     // if fore, list from newest to oldest
498 
499     // don't forget to patch ampersands before adding to menu
500     if (punk == NULL)
501         return E_INVALIDARG;
502     if (idLast <= idFirst)
503         return E_INVALIDARG;
504     menuItemInfo.cbSize = sizeof(menuItemInfo);
505     menuItemInfo.fMask = MIIM_FTYPE | MIIM_ID | MIIM_STATE | MIIM_STRING;
506     menuItemInfo.fType = MFT_STRING;
507     menuItemInfo.wID = idFirst;
508     menuItemInfo.fState = MFS_ENABLED;
509     menuItemInfo.dwTypeData = itemTextBuffer;
510     if ((dwFlags & TLMENUF_BACK) != 0)
511     {
512         if ((dwFlags & TLMENUF_FORE) != 0)
513         {
514             currentItem = fCurrentEntry;
515             if (currentItem != NULL)
516             {
517                 while (currentItem->fPreviousEntry != NULL)
518                     currentItem = currentItem->fPreviousEntry;
519             }
520             while (currentItem != fCurrentEntry)
521             {
522                 hResult = currentItem->GetToolTipText(punk, itemTextBuffer);
523                 if (SUCCEEDED(hResult))
524                 {
525                     FixAmpersands(itemTextBuffer);
526                     TRACE("CTravelLog::InsertMenuEntries adding entry L\"%S\"/L\"%S\" with id %d\n", itemTextBuffer, menuItemInfo.dwTypeData, menuItemInfo.wID);
527                     if (InsertMenuItem(hmenu, nPos, TRUE, &menuItemInfo))
528                     {
529                         nPos++;
530                         menuItemInfo.wID++;
531                     }
532                 }
533                 currentItem = currentItem->fNextEntry;
534             }
535         }
536         else
537         {
538             currentItem = fCurrentEntry;
539             if (currentItem != NULL)
540                 currentItem = currentItem->fPreviousEntry;
541             while (currentItem != NULL)
542             {
543                 hResult = currentItem->GetToolTipText(punk, itemTextBuffer);
544                 if (SUCCEEDED(hResult))
545                 {
546                     FixAmpersands(itemTextBuffer);
547                     TRACE("CTravelLog::InsertMenuEntries adding entry L\"%S\"/L\"%S\" with id %d\n", itemTextBuffer, menuItemInfo.dwTypeData, menuItemInfo.wID);
548                     if (InsertMenuItem(hmenu, nPos, TRUE, &menuItemInfo))
549                     {
550                         nPos++;
551                         menuItemInfo.wID++;
552                     }
553                 }
554                 currentItem = currentItem->fPreviousEntry;
555             }
556         }
557     }
558     if ((dwFlags & TLMENUF_INCLUDECURRENT) != 0)
559     {
560         if (fCurrentEntry != NULL)
561         {
562             hResult = fCurrentEntry->GetToolTipText(punk, itemTextBuffer);
563             if (SUCCEEDED(hResult))
564             {
565                 FixAmpersands(itemTextBuffer);
566                 if ((dwFlags & TLMENUF_CHECKCURRENT) != 0)
567                     menuItemInfo.fState |= MFS_CHECKED;
568                 TRACE("CTravelLog::InsertMenuEntries adding entry L\"%S\"/L\"%S\" with id %d\n", itemTextBuffer, menuItemInfo.dwTypeData, menuItemInfo.wID);
569                 if (InsertMenuItem(hmenu, nPos, TRUE, &menuItemInfo))
570                 {
571                     nPos++;
572                     menuItemInfo.wID++;
573                 }
574                 menuItemInfo.fState &= ~MFS_CHECKED;
575             }
576         }
577     }
578     if ((dwFlags & TLMENUF_FORE) != 0)
579     {
580         currentItem = fCurrentEntry;
581         if (currentItem != NULL)
582             currentItem = currentItem->fNextEntry;
583         while (currentItem != NULL)
584         {
585             hResult = currentItem->GetToolTipText(punk, itemTextBuffer);
586             if (SUCCEEDED(hResult))
587             {
588                 FixAmpersands(itemTextBuffer);
589                 TRACE("CTravelLog::InsertMenuEntries adding entry L\"%S\"/L\"%S\" with id %d\n", itemTextBuffer, menuItemInfo.dwTypeData, menuItemInfo.wID);
590                 if (InsertMenuItem(hmenu, nPos, TRUE, &menuItemInfo))
591                 {
592                     nPos++;
593                     menuItemInfo.wID++;
594                 }
595             }
596             currentItem = currentItem->fNextEntry;
597         }
598     }
599     return S_OK;
600 }
601 
602 HRESULT STDMETHODCALLTYPE CTravelLog::Clone(ITravelLog **pptl)
603 {
604     if (pptl == NULL)
605         return E_POINTER;
606     *pptl = NULL;
607     // duplicate the log
608     UNIMPLEMENTED;
609     return E_NOTIMPL;
610 }
611 
612 DWORD STDMETHODCALLTYPE CTravelLog::CountEntries(IUnknown *punk)
613 {
614     if (punk == NULL)
615         return E_INVALIDARG;
616     return fEntryCount;
617 }
618 
619 HRESULT STDMETHODCALLTYPE CTravelLog::Revert()
620 {
621     // remove the current entry?
622     UNIMPLEMENTED;
623     return E_NOTIMPL;
624 }
625 
626 HRESULT CTravelLog_CreateInstance(REFIID riid, void **ppv)
627 {
628     return ShellObjectCreatorInit<CTravelLog>(riid, ppv);
629 }
630