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