[BROWSEUI]
[reactos.git] / dll / win32 / browseui / travellog.cpp
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);
129 if (FAILED(hResult))
130 return hResult;
131 return S_OK;
132 }
133
134 long CTravelEntry::GetSize() const
135 {
136 return 0;
137 }
138
139 HRESULT STDMETHODCALLTYPE CTravelEntry::Invoke(IUnknown *punk)
140 {
141 CComPtr<IPersistHistory> persistHistory;
142 CComPtr<IStream> globalStream;
143 HRESULT hResult;
144
145 hResult = punk->QueryInterface(IID_PPV_ARG(IPersistHistory, &persistHistory));
146 if (FAILED(hResult))
147 return hResult;
148 hResult = CreateStreamOnHGlobal(fPersistState, FALSE, &globalStream);
149 if (FAILED(hResult))
150 return hResult;
151 hResult = persistHistory->LoadHistory(globalStream, NULL);
152 if (FAILED(hResult))
153 return hResult;
154 return S_OK;
155 }
156
157 HRESULT STDMETHODCALLTYPE CTravelEntry::Update(IUnknown *punk, BOOL fIsLocalAnchor)
158 {
159 CComPtr<ITravelLogClient> travelLogClient;
160 CComPtr<IPersistHistory> persistHistory;
161 CComPtr<IStream> globalStream;
162 WINDOWDATA windowData;
163 HGLOBAL globalStorage;
164 HRESULT hResult;
165
166 ILFree(fPIDL);
167 fPIDL = NULL;
168 GlobalFree(fPersistState);
169 fPersistState = NULL;
170 hResult = punk->QueryInterface(IID_PPV_ARG(ITravelLogClient, &travelLogClient));
171 if (FAILED(hResult))
172 return hResult;
173 hResult = punk->QueryInterface(IID_PPV_ARG(IPersistHistory, &persistHistory));
174 if (FAILED(hResult))
175 return hResult;
176 globalStorage = GlobalAlloc(GMEM_FIXED, 0);
177 hResult = CreateStreamOnHGlobal(globalStorage, FALSE, &globalStream);
178 if (FAILED(hResult))
179 return hResult;
180 hResult = persistHistory->SaveHistory(globalStream);
181 if (FAILED(hResult))
182 return hResult;
183 hResult = travelLogClient->GetWindowData(globalStream, &windowData);
184 if (FAILED(hResult))
185 return hResult;
186 fPIDL = windowData.pidl;
187 // TODO: Properly free the windowData
188 hResult = GetHGlobalFromStream(globalStream, &fPersistState);
189 if (FAILED(hResult))
190 return hResult;
191 return S_OK;
192 }
193
194 HRESULT STDMETHODCALLTYPE CTravelEntry::GetPidl(LPITEMIDLIST *ppidl)
195 {
196 if (ppidl == NULL)
197 return E_POINTER;
198 *ppidl = ILClone(fPIDL);
199 if (*ppidl == NULL)
200 return E_OUTOFMEMORY;
201 return S_OK;
202 }
203
204 CTravelLog::CTravelLog()
205 {
206 fListHead = NULL;
207 fListTail = NULL;
208 fCurrentEntry = NULL;
209 fMaximumSize = 0;
210 fCurrentSize = 0;
211 fEntryCount = 0;
212 }
213
214 CTravelLog::~CTravelLog()
215 {
216 CTravelEntry *anEntry;
217 CTravelEntry *next;
218
219 anEntry = fListHead;
220 while (anEntry != NULL)
221 {
222 next = anEntry->fNextEntry;
223 anEntry->Release();
224 anEntry = next;
225 }
226 }
227
228 HRESULT CTravelLog::Initialize()
229 {
230 fMaximumSize = 1024 * 1024; // TODO: change to read this from registry
231 // Software\Microsoft\Windows\CurrentVersion\Explorer\TravelLog
232 // MaxSize
233 return S_OK;
234 }
235
236 HRESULT CTravelLog::FindRelativeEntry(int offset, CTravelEntry **foundEntry)
237 {
238 CTravelEntry *curEntry;
239
240 *foundEntry = NULL;
241 curEntry = fCurrentEntry;
242 if (offset < 0)
243 {
244 while (offset < 0 && curEntry != NULL)
245 {
246 curEntry = curEntry->fPreviousEntry;
247 offset++;
248 }
249 }
250 else
251 {
252 while (offset > 0 && curEntry != NULL)
253 {
254 curEntry = curEntry->fNextEntry;
255 offset--;
256 }
257 }
258 if (curEntry == NULL)
259 return E_INVALIDARG;
260 *foundEntry = curEntry;
261 return S_OK;
262 }
263
264 void CTravelLog::DeleteChain(CTravelEntry *startHere)
265 {
266 CTravelEntry *saveNext;
267 long itemSize;
268
269 if (startHere->fPreviousEntry != NULL)
270 {
271 startHere->fPreviousEntry->fNextEntry = NULL;
272 fListTail = startHere->fPreviousEntry;
273 }
274 else
275 {
276 fListHead = NULL;
277 fListTail = NULL;
278 }
279 while (startHere != NULL)
280 {
281 saveNext = startHere->fNextEntry;
282 itemSize = startHere->GetSize();
283 fCurrentSize -= itemSize;
284 startHere->Release();
285 startHere = saveNext;
286 fEntryCount--;
287 }
288 }
289
290 void CTravelLog::AppendEntry(CTravelEntry *afterEntry, CTravelEntry *newEntry)
291 {
292 if (afterEntry == NULL)
293 {
294 fListHead = newEntry;
295 fListTail = newEntry;
296 }
297 else
298 {
299 newEntry->fNextEntry = afterEntry->fNextEntry;
300 afterEntry->fNextEntry = newEntry;
301 newEntry->fPreviousEntry = afterEntry;
302 if (newEntry->fNextEntry == NULL)
303 fListTail = newEntry;
304 else
305 newEntry->fNextEntry->fPreviousEntry = newEntry;
306 }
307 fEntryCount++;
308 }
309
310 HRESULT STDMETHODCALLTYPE CTravelLog::AddEntry(IUnknown *punk, BOOL fIsLocalAnchor)
311 {
312 CComObject<CTravelEntry> *newEntry;
313 long itemSize;
314
315 if (punk == NULL)
316 return E_INVALIDARG;
317 ATLTRY (newEntry = new CComObject<CTravelEntry>);
318 if (newEntry == NULL)
319 return E_OUTOFMEMORY;
320 newEntry->AddRef();
321 if (fCurrentEntry != NULL && fCurrentEntry->fNextEntry != NULL)
322 DeleteChain(fCurrentEntry->fNextEntry);
323 AppendEntry(fCurrentEntry, newEntry);
324 itemSize = newEntry->GetSize();
325 fCurrentSize += itemSize;
326 fCurrentEntry = newEntry;
327 return S_OK;
328 }
329
330 HRESULT STDMETHODCALLTYPE CTravelLog::UpdateEntry(IUnknown *punk, BOOL fIsLocalAnchor)
331 {
332 if (punk == NULL)
333 return E_INVALIDARG;
334 if (fCurrentEntry == NULL)
335 return E_UNEXPECTED;
336 return fCurrentEntry->Update(punk, fIsLocalAnchor);
337 }
338
339 HRESULT STDMETHODCALLTYPE CTravelLog::UpdateExternal(IUnknown *punk, IUnknown *punkHLBrowseContext)
340 {
341 return E_NOTIMPL;
342 }
343
344 HRESULT STDMETHODCALLTYPE CTravelLog::Travel(IUnknown *punk, int iOffset)
345 {
346 CTravelEntry *destinationEntry;
347 HRESULT hResult;
348
349 hResult = FindRelativeEntry(iOffset, &destinationEntry);
350 if (FAILED(hResult))
351 return hResult;
352 fCurrentEntry = destinationEntry;
353 hResult = destinationEntry->Invoke(punk);
354 if (FAILED(hResult))
355 return hResult;
356 return S_OK;
357 }
358
359 HRESULT STDMETHODCALLTYPE CTravelLog::GetTravelEntry(IUnknown *punk, int iOffset, ITravelEntry **ppte)
360 {
361 CTravelEntry *destinationEntry;
362 HRESULT hResult;
363
364 hResult = FindRelativeEntry(iOffset, &destinationEntry);
365 if (FAILED(hResult))
366 return hResult;
367 return destinationEntry->QueryInterface(IID_PPV_ARG(ITravelEntry, ppte));
368 }
369
370 HRESULT STDMETHODCALLTYPE CTravelLog::FindTravelEntry(IUnknown *punk, LPCITEMIDLIST pidl, ITravelEntry **ppte)
371 {
372 if (ppte == NULL)
373 return E_POINTER;
374 if (punk == NULL || pidl == NULL)
375 return E_INVALIDARG;
376 return E_NOTIMPL;
377 }
378
379 HRESULT STDMETHODCALLTYPE CTravelLog::GetToolTipText(IUnknown *punk, int iOffset, int idsTemplate, LPWSTR pwzText, DWORD cchText)
380 {
381 CTravelEntry *destinationEntry;
382 wchar_t tempString[MAX_PATH];
383 wchar_t templateString[200];
384 HRESULT hResult;
385
386 if (pwzText == NULL)
387 return E_POINTER;
388 if (punk == NULL || cchText == 0)
389 return E_INVALIDARG;
390 hResult = FindRelativeEntry(iOffset, &destinationEntry);
391 if (FAILED(hResult))
392 return hResult;
393 hResult = destinationEntry->GetToolTipText(punk, tempString);
394 if (FAILED(hResult))
395 return hResult;
396 if (iOffset < 0)
397 {
398 wcscpy(templateString, L"Back to %s");
399 }
400 else
401 {
402 wcscpy(templateString, L"Forward to %s");
403 }
404 _snwprintf(pwzText, cchText, templateString, tempString);
405 return S_OK;
406 }
407
408 static void FixAmpersands(wchar_t *buffer)
409 {
410 wchar_t tempBuffer[MAX_PATH * 2];
411 wchar_t ch;
412 wchar_t *srcPtr;
413 wchar_t *dstPtr;
414
415 srcPtr = buffer;
416 dstPtr = tempBuffer;
417 while (*srcPtr != 0)
418 {
419 ch = *srcPtr++;
420 *dstPtr++ = ch;
421 if (ch == '&')
422 *dstPtr++ = '&';
423 }
424 *dstPtr = 0;
425 wcscpy(buffer, tempBuffer);
426 }
427
428 HRESULT STDMETHODCALLTYPE CTravelLog::InsertMenuEntries(IUnknown *punk, HMENU hmenu,
429 int nPos, int idFirst, int idLast, DWORD dwFlags)
430 {
431 CTravelEntry *currentItem;
432 MENUITEMINFO menuItemInfo;
433 wchar_t itemTextBuffer[MAX_PATH * 2];
434 HRESULT hResult;
435
436 // TLMENUF_BACK - include back entries
437 // TLMENUF_INCLUDECURRENT - include current entry, if TLMENUF_CHECKCURRENT then check the entry
438 // TLMENUF_FORE - include next entries
439 // if fore+back, list from oldest to newest
440 // if back, list from newest to oldest
441 // if fore, list from newest to oldest
442
443 // don't forget to patch ampersands before adding to menu
444 if (punk == NULL)
445 return E_INVALIDARG;
446 if (idLast <= idFirst)
447 return E_INVALIDARG;
448 menuItemInfo.cbSize = sizeof(menuItemInfo);
449 menuItemInfo.fMask = MIIM_FTYPE | MIIM_ID | MIIM_STATE | MIIM_STRING;
450 menuItemInfo.fType = MFT_STRING;
451 menuItemInfo.wID = idFirst;
452 menuItemInfo.fState = MFS_ENABLED;
453 menuItemInfo.dwTypeData = itemTextBuffer;
454 if ((dwFlags & TLMENUF_BACK) != 0)
455 {
456 if ((dwFlags & TLMENUF_FORE) != 0)
457 {
458 currentItem = fCurrentEntry;
459 if (currentItem != NULL)
460 {
461 while (currentItem->fPreviousEntry != NULL)
462 currentItem = currentItem->fPreviousEntry;
463 }
464 while (currentItem != fCurrentEntry)
465 {
466 hResult = currentItem->GetToolTipText(punk, itemTextBuffer);
467 if (SUCCEEDED(hResult))
468 {
469 FixAmpersands(itemTextBuffer);
470 if (InsertMenuItem(hmenu, nPos, TRUE, &menuItemInfo))
471 {
472 nPos++;
473 menuItemInfo.wID++;
474 }
475 }
476 currentItem = currentItem->fNextEntry;
477 }
478 }
479 else
480 {
481 currentItem = fCurrentEntry;
482 if (currentItem != NULL)
483 currentItem = currentItem->fPreviousEntry;
484 while (currentItem != NULL)
485 {
486 hResult = currentItem->GetToolTipText(punk, itemTextBuffer);
487 if (SUCCEEDED(hResult))
488 {
489 FixAmpersands(itemTextBuffer);
490 if (InsertMenuItem(hmenu, nPos, TRUE, &menuItemInfo))
491 {
492 nPos++;
493 menuItemInfo.wID++;
494 }
495 }
496 currentItem = currentItem->fPreviousEntry;
497 }
498 }
499 }
500 if ((dwFlags & TLMENUF_INCLUDECURRENT) != 0)
501 {
502 if (fCurrentEntry != NULL)
503 {
504 hResult = fCurrentEntry->GetToolTipText(punk, itemTextBuffer);
505 if (SUCCEEDED(hResult))
506 {
507 FixAmpersands(itemTextBuffer);
508 if ((dwFlags & TLMENUF_CHECKCURRENT) != 0)
509 menuItemInfo.fState |= MFS_CHECKED;
510 if (InsertMenuItem(hmenu, nPos, TRUE, &menuItemInfo))
511 {
512 nPos++;
513 menuItemInfo.wID++;
514 }
515 menuItemInfo.fState &= ~MFS_CHECKED;
516 }
517 }
518 }
519 if ((dwFlags & TLMENUF_FORE) != 0)
520 {
521 currentItem = fCurrentEntry;
522 if (currentItem != NULL)
523 currentItem = currentItem->fNextEntry;
524 while (currentItem != NULL)
525 {
526 hResult = currentItem->GetToolTipText(punk, itemTextBuffer);
527 if (SUCCEEDED(hResult))
528 {
529 FixAmpersands(itemTextBuffer);
530 if (InsertMenuItem(hmenu, nPos, TRUE, &menuItemInfo))
531 {
532 nPos++;
533 menuItemInfo.wID++;
534 }
535 }
536 currentItem = currentItem->fNextEntry;
537 }
538 }
539 return S_OK;
540 }
541
542 HRESULT STDMETHODCALLTYPE CTravelLog::Clone(ITravelLog **pptl)
543 {
544 if (pptl == NULL)
545 return E_POINTER;
546 *pptl = NULL;
547 // duplicate the log
548 return E_NOTIMPL;
549 }
550
551 DWORD STDMETHODCALLTYPE CTravelLog::CountEntries(IUnknown *punk)
552 {
553 if (punk == NULL)
554 return E_INVALIDARG;
555 return fEntryCount;
556 }
557
558 HRESULT STDMETHODCALLTYPE CTravelLog::Revert()
559 {
560 // remove the current entry?
561 return E_NOTIMPL;
562 }
563
564 HRESULT CreateTravelLog(REFIID riid, void **ppv)
565 {
566 CComObject<CTravelLog> *theTravelLog;
567 HRESULT hResult;
568
569 if (ppv == NULL)
570 return E_POINTER;
571 *ppv = NULL;
572 ATLTRY (theTravelLog = new CComObject<CTravelLog>);
573 if (theTravelLog == NULL)
574 return E_OUTOFMEMORY;
575 hResult = theTravelLog->QueryInterface(riid, reinterpret_cast<void **>(ppv));
576 if (FAILED(hResult))
577 {
578 delete theTravelLog;
579 return hResult;
580 }
581 hResult = theTravelLog->Initialize();
582 if (FAILED(hResult))
583 return hResult;
584 return S_OK;
585 }