[BROWSEUI:FORMATTING]
[reactos.git] / reactos / 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, reinterpret_cast<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, reinterpret_cast<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, reinterpret_cast<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, reinterpret_cast<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,
428 int nPos, int idFirst, int idLast, DWORD dwFlags)
429 {
430 CTravelEntry *currentItem;
431 MENUITEMINFO menuItemInfo;
432 wchar_t itemTextBuffer[MAX_PATH * 2];
433 HRESULT hResult;
434
435 // TLMENUF_BACK - include back entries
436 // TLMENUF_INCLUDECURRENT - include current entry, if TLMENUF_CHECKCURRENT then check the entry
437 // TLMENUF_FORE - include next entries
438 // if fore+back, list from oldest to newest
439 // if back, list from newest to oldest
440 // if fore, list from newest to oldest
441
442 // don't forget to patch ampersands before adding to menu
443 if (punk == NULL)
444 return E_INVALIDARG;
445 if (idLast <= idFirst)
446 return E_INVALIDARG;
447 menuItemInfo.cbSize = sizeof(menuItemInfo);
448 menuItemInfo.fMask = MIIM_FTYPE | MIIM_ID | MIIM_STATE | MIIM_STRING;
449 menuItemInfo.fType = MFT_STRING;
450 menuItemInfo.wID = idFirst;
451 menuItemInfo.fState = MFS_ENABLED;
452 menuItemInfo.dwTypeData = itemTextBuffer;
453 if ((dwFlags & TLMENUF_BACK) != 0)
454 {
455 if ((dwFlags & TLMENUF_FORE) != 0)
456 {
457 currentItem = fCurrentEntry;
458 if (currentItem != NULL)
459 {
460 while (currentItem->fPreviousEntry != NULL)
461 currentItem = currentItem->fPreviousEntry;
462 }
463 while (currentItem != fCurrentEntry)
464 {
465 hResult = currentItem->GetToolTipText(punk, itemTextBuffer);
466 if (SUCCEEDED(hResult))
467 {
468 FixAmpersands(itemTextBuffer);
469 if (InsertMenuItem(hmenu, nPos, TRUE, &menuItemInfo))
470 {
471 nPos++;
472 menuItemInfo.wID++;
473 }
474 }
475 currentItem = currentItem->fNextEntry;
476 }
477 }
478 else
479 {
480 currentItem = fCurrentEntry;
481 if (currentItem != NULL)
482 currentItem = currentItem->fPreviousEntry;
483 while (currentItem != NULL)
484 {
485 hResult = currentItem->GetToolTipText(punk, itemTextBuffer);
486 if (SUCCEEDED(hResult))
487 {
488 FixAmpersands(itemTextBuffer);
489 if (InsertMenuItem(hmenu, nPos, TRUE, &menuItemInfo))
490 {
491 nPos++;
492 menuItemInfo.wID++;
493 }
494 }
495 currentItem = currentItem->fPreviousEntry;
496 }
497 }
498 }
499 if ((dwFlags & TLMENUF_INCLUDECURRENT) != 0)
500 {
501 if (fCurrentEntry != NULL)
502 {
503 hResult = fCurrentEntry->GetToolTipText(punk, itemTextBuffer);
504 if (SUCCEEDED(hResult))
505 {
506 FixAmpersands(itemTextBuffer);
507 if ((dwFlags & TLMENUF_CHECKCURRENT) != 0)
508 menuItemInfo.fState |= MFS_CHECKED;
509 if (InsertMenuItem(hmenu, nPos, TRUE, &menuItemInfo))
510 {
511 nPos++;
512 menuItemInfo.wID++;
513 }
514 menuItemInfo.fState &= ~MFS_CHECKED;
515 }
516 }
517 }
518 if ((dwFlags & TLMENUF_FORE) != 0)
519 {
520 currentItem = fCurrentEntry;
521 if (currentItem != NULL)
522 currentItem = currentItem->fNextEntry;
523 while (currentItem != NULL)
524 {
525 hResult = currentItem->GetToolTipText(punk, itemTextBuffer);
526 if (SUCCEEDED(hResult))
527 {
528 FixAmpersands(itemTextBuffer);
529 if (InsertMenuItem(hmenu, nPos, TRUE, &menuItemInfo))
530 {
531 nPos++;
532 menuItemInfo.wID++;
533 }
534 }
535 currentItem = currentItem->fNextEntry;
536 }
537 }
538 return S_OK;
539 }
540
541 HRESULT STDMETHODCALLTYPE CTravelLog::Clone(ITravelLog **pptl)
542 {
543 if (pptl == NULL)
544 return E_POINTER;
545 *pptl = NULL;
546 // duplicate the log
547 return E_NOTIMPL;
548 }
549
550 DWORD STDMETHODCALLTYPE CTravelLog::CountEntries(IUnknown *punk)
551 {
552 if (punk == NULL)
553 return E_INVALIDARG;
554 return fEntryCount;
555 }
556
557 HRESULT STDMETHODCALLTYPE CTravelLog::Revert()
558 {
559 // remove the current entry?
560 return E_NOTIMPL;
561 }
562
563 HRESULT CreateTravelLog(REFIID riid, void **ppv)
564 {
565 CComObject<CTravelLog> *theTravelLog;
566 HRESULT hResult;
567
568 if (ppv == NULL)
569 return E_POINTER;
570 *ppv = NULL;
571 ATLTRY (theTravelLog = new CComObject<CTravelLog>);
572 if (theTravelLog == NULL)
573 return E_OUTOFMEMORY;
574 hResult = theTravelLog->QueryInterface(riid, reinterpret_cast<void **>(ppv));
575 if (FAILED(hResult))
576 {
577 delete theTravelLog;
578 return hResult;
579 }
580 hResult = theTravelLog->Initialize();
581 if (FAILED(hResult))
582 return hResult;
583 return S_OK;
584 }