xref: /reactos/dll/win32/browseui/travellog.cpp (revision 60eea2d7)
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 }
584