[FONTEXT] Add detail view columns to font folder
[reactos.git] / dll / shellext / fontext / CFontExt.cpp
1 /*
2 * PROJECT: ReactOS Font Shell Extension
3 * LICENSE: GPL-2.0-or-later (https://spdx.org/licenses/GPL-2.0-or-later)
4 * PURPOSE: CFontExt implementation
5 * COPYRIGHT: Copyright 2019,2020 Mark Jansen (mark.jansen@reactos.org)
6 * Copyright 2019 Katayama Hirofumi MZ (katayama.hirofumi.mz@gmail.com)
7 */
8
9 #include "precomp.h"
10
11 WINE_DEFAULT_DEBUG_CHANNEL(fontext);
12
13
14 struct FolderViewColumns
15 {
16 int iResource;
17 DWORD dwDefaultState;
18 int cxChar;
19 int fmt;
20 };
21
22 static FolderViewColumns g_ColumnDefs[] =
23 {
24 { IDS_COL_NAME, SHCOLSTATE_TYPE_STR | SHCOLSTATE_ONBYDEFAULT, 25, LVCFMT_LEFT },
25 { IDS_COL_FILENAME, SHCOLSTATE_TYPE_STR | SHCOLSTATE_ONBYDEFAULT, 20, LVCFMT_LEFT },
26 { IDS_COL_SIZE, SHCOLSTATE_TYPE_STR | SHCOLSTATE_ONBYDEFAULT, 10, LVCFMT_RIGHT },
27 { IDS_COL_MODIFIED, SHCOLSTATE_TYPE_DATE | SHCOLSTATE_ONBYDEFAULT, 15, LVCFMT_LEFT },
28 { IDS_COL_ATTR, SHCOLSTATE_TYPE_STR | SHCOLSTATE_ONBYDEFAULT, 12, LVCFMT_RIGHT },
29 };
30
31
32
33 // Should fix our headers..
34 EXTERN_C HRESULT WINAPI SHCreateFileExtractIconW(LPCWSTR pszPath, DWORD dwFileAttributes, REFIID riid, void **ppv);
35
36
37 // Helper functions to translate a guid to a readable name
38 bool GetInterfaceName(const WCHAR* InterfaceString, WCHAR* buf, size_t size)
39 {
40 WCHAR LocalBuf[100];
41 DWORD dwType = 0, dwDataSize = size * sizeof(WCHAR);
42
43 if (!SUCCEEDED(StringCchPrintfW(LocalBuf, _countof(LocalBuf), L"Interface\\%s", InterfaceString)))
44 return false;
45
46 return RegGetValueW(HKEY_CLASSES_ROOT, LocalBuf, NULL, RRF_RT_REG_SZ, &dwType, buf, &dwDataSize) == ERROR_SUCCESS;
47 }
48
49 WCHAR* g2s(REFCLSID iid)
50 {
51 static WCHAR buf[2][300];
52 static int idx = 0;
53
54 idx ^= 1;
55
56 LPOLESTR tmp;
57 HRESULT hr = ProgIDFromCLSID(iid, &tmp);
58 if (SUCCEEDED(hr))
59 {
60 wcscpy(buf[idx], tmp);
61 CoTaskMemFree(tmp);
62 return buf[idx];
63 }
64 StringFromGUID2(iid, buf[idx], _countof(buf[idx]));
65 if (GetInterfaceName(buf[idx], buf[idx], _countof(buf[idx])))
66 {
67 return buf[idx];
68 }
69 StringFromGUID2(iid, buf[idx], _countof(buf[idx]));
70
71 return buf[idx];
72 }
73
74
75 CFontExt::CFontExt()
76 {
77 InterlockedIncrement(&g_ModuleRefCnt);
78 }
79
80 CFontExt::~CFontExt()
81 {
82 InterlockedDecrement(&g_ModuleRefCnt);
83 }
84
85 // *** IShellFolder2 methods ***
86 STDMETHODIMP CFontExt::GetDefaultSearchGUID(GUID *lpguid)
87 {
88 ERR("%s() UNIMPLEMENTED\n", __FUNCTION__);
89 return E_NOTIMPL;
90 }
91
92 STDMETHODIMP CFontExt::EnumSearches(IEnumExtraSearch **ppenum)
93 {
94 ERR("%s() UNIMPLEMENTED\n", __FUNCTION__);
95 return E_NOTIMPL;
96 }
97
98 STDMETHODIMP CFontExt::GetDefaultColumn(DWORD dwReserved, ULONG *pSort, ULONG *pDisplay)
99 {
100 ERR("%s() UNIMPLEMENTED\n", __FUNCTION__);
101 return E_NOTIMPL;
102 }
103
104 STDMETHODIMP CFontExt::GetDefaultColumnState(UINT iColumn, SHCOLSTATEF *pcsFlags)
105 {
106 if (!pcsFlags || iColumn >= _countof(g_ColumnDefs))
107 return E_INVALIDARG;
108
109 *pcsFlags = g_ColumnDefs[iColumn].dwDefaultState;
110 return S_OK;
111 }
112
113 STDMETHODIMP CFontExt::GetDetailsEx(PCUITEMID_CHILD pidl, const SHCOLUMNID *pscid, VARIANT *pv)
114 {
115 ERR("%s() UNIMPLEMENTED\n", __FUNCTION__);
116 return E_NOTIMPL;
117 }
118
119 STDMETHODIMP CFontExt::GetDetailsOf(PCUITEMID_CHILD pidl, UINT iColumn, SHELLDETAILS *psd)
120 {
121 if (iColumn >= _countof(g_ColumnDefs))
122 return E_FAIL;
123
124 psd->cxChar = g_ColumnDefs[iColumn].cxChar;
125 psd->fmt = g_ColumnDefs[iColumn].fmt;
126
127 // No item requested, so return the column name
128 if (pidl == NULL)
129 {
130 return SHSetStrRet(&psd->str, _AtlBaseModule.GetResourceInstance(), g_ColumnDefs[iColumn].iResource);
131 }
132
133 // Validate that this pidl is the last one
134 PCUIDLIST_RELATIVE curpidl = ILGetNext(pidl);
135 if (curpidl->mkid.cb != 0)
136 {
137 ERR("ERROR, unhandled PIDL!\n");
138 return E_FAIL;
139 }
140
141 // Name, ReactOS specific?
142 if (iColumn == 0)
143 return GetDisplayNameOf(pidl, 0, &psd->str);
144
145 const FontPidlEntry* fontEntry = _FontFromIL(pidl);
146 if (!fontEntry)
147 {
148 ERR("ERROR, not a font PIDL!\n");
149 return E_FAIL;
150 }
151
152 // If we got here, we are in details view!
153 // Let's see if we got info about this file that we can re-use
154 if (m_LastDetailsFontName != fontEntry->Name)
155 {
156 CStringW File = g_FontCache->Filename(fontEntry, true);
157 HANDLE hFile = FindFirstFileW(File, &m_LastDetailsFileData);
158 if (hFile == INVALID_HANDLE_VALUE)
159 {
160 m_LastDetailsFontName.Empty();
161 ERR("Unable to query info about %S\n", File.GetString());
162 return HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND);
163 }
164 FindClose(hFile);
165 m_LastDetailsFontName = fontEntry->Name;
166 }
167
168 // Most code borrowed from CFSFolder::GetDetailsOf
169 FILETIME lft;
170 SYSTEMTIME time;
171 int ret;
172 LARGE_INTEGER FileSize;
173 CStringA AttrLetters;
174 switch (iColumn)
175 {
176 case 1: // Filename
177 return SHSetStrRet(&psd->str, m_LastDetailsFileData.cFileName);
178 case 2: // Size
179 psd->str.uType = STRRET_CSTR;
180 FileSize.HighPart = m_LastDetailsFileData.nFileSizeHigh;
181 FileSize.LowPart = m_LastDetailsFileData.nFileSizeLow;
182 StrFormatKBSizeA(FileSize.QuadPart, psd->str.cStr, MAX_PATH);
183 return S_OK;
184 case 3: // Modified
185 FileTimeToLocalFileTime(&m_LastDetailsFileData.ftLastWriteTime, &lft);
186 FileTimeToSystemTime (&lft, &time);
187 psd->str.uType = STRRET_CSTR;
188 ret = GetDateFormatA(LOCALE_USER_DEFAULT, DATE_SHORTDATE, &time, NULL, psd->str.cStr, MAX_PATH);
189 if (ret < 1)
190 {
191 ERR("GetDateFormatA failed\n");
192 return E_FAIL;
193 }
194 psd->str.cStr[ret-1] = ' ';
195 GetTimeFormatA(LOCALE_USER_DEFAULT, TIME_NOSECONDS, &time, NULL, &psd->str.cStr[ret], MAX_PATH - ret);
196 return S_OK;
197 case 4: // Attributes
198 AttrLetters.LoadString(IDS_COL_ATTR_LETTERS);
199 if (AttrLetters.GetLength() != 5)
200 {
201 ERR("IDS_COL_ATTR_LETTERS does not contain 5 letters!\n");
202 return E_FAIL;
203 }
204 psd->str.uType = STRRET_CSTR;
205 ret = 0;
206 if (m_LastDetailsFileData.dwFileAttributes & FILE_ATTRIBUTE_READONLY)
207 psd->str.cStr[ret++] = AttrLetters[0];
208 if (m_LastDetailsFileData.dwFileAttributes & FILE_ATTRIBUTE_HIDDEN)
209 psd->str.cStr[ret++] = AttrLetters[1];
210 if (m_LastDetailsFileData.dwFileAttributes & FILE_ATTRIBUTE_SYSTEM)
211 psd->str.cStr[ret++] = AttrLetters[2];
212 if (m_LastDetailsFileData.dwFileAttributes & FILE_ATTRIBUTE_ARCHIVE)
213 psd->str.cStr[ret++] = AttrLetters[3];
214 if (m_LastDetailsFileData.dwFileAttributes & FILE_ATTRIBUTE_COMPRESSED)
215 psd->str.cStr[ret++] = AttrLetters[4];
216 psd->str.cStr[ret] = '\0';
217 return S_OK;
218 default:
219 break;
220 }
221
222 UNIMPLEMENTED;
223 return E_NOTIMPL;
224 }
225
226 STDMETHODIMP CFontExt::MapColumnToSCID(UINT iColumn, SHCOLUMNID *pscid)
227 {
228 //ERR("%s() UNIMPLEMENTED\n", __FUNCTION__);
229 return E_NOTIMPL;
230 }
231
232 // *** IShellFolder2 methods ***
233 STDMETHODIMP CFontExt::ParseDisplayName(HWND hwndOwner, LPBC pbc, LPOLESTR lpszDisplayName, DWORD *pchEaten, PIDLIST_RELATIVE *ppidl, DWORD *pdwAttributes)
234 {
235 ERR("%s() UNIMPLEMENTED\n", __FUNCTION__);
236 return E_NOTIMPL;
237 }
238
239 STDMETHODIMP CFontExt::EnumObjects(HWND hwndOwner, DWORD dwFlags, LPENUMIDLIST *ppEnumIDList)
240 {
241 return _CEnumFonts_CreateInstance(this, dwFlags, IID_PPV_ARG(IEnumIDList, ppEnumIDList));
242 }
243
244 STDMETHODIMP CFontExt::BindToObject(PCUIDLIST_RELATIVE pidl, LPBC pbcReserved, REFIID riid, LPVOID *ppvOut)
245 {
246 ERR("%s(riid=%S) UNIMPLEMENTED\n", __FUNCTION__, g2s(riid));
247 return E_NOTIMPL;
248 }
249
250 STDMETHODIMP CFontExt::BindToStorage(PCUIDLIST_RELATIVE pidl, LPBC pbcReserved, REFIID riid, LPVOID *ppvOut)
251 {
252 ERR("%s() UNIMPLEMENTED\n", __FUNCTION__);
253 return E_NOTIMPL;
254 }
255
256 STDMETHODIMP CFontExt::CompareIDs(LPARAM lParam, PCUIDLIST_RELATIVE pidl1, PCUIDLIST_RELATIVE pidl2)
257 {
258 const FontPidlEntry* fontEntry1 = _FontFromIL(pidl1);
259 const FontPidlEntry* fontEntry2 = _FontFromIL(pidl2);
260
261 if (!fontEntry1 || !fontEntry2)
262 return E_INVALIDARG;
263
264 int result = (int)fontEntry1->Index - (int)fontEntry2->Index;
265
266 return MAKE_COMPARE_HRESULT(result);
267 }
268
269 STDMETHODIMP CFontExt::CreateViewObject(HWND hwndOwner, REFIID riid, LPVOID *ppvOut)
270 {
271 HRESULT hr = E_NOINTERFACE;
272
273 *ppvOut = NULL;
274
275 if (IsEqualIID(riid, IID_IDropTarget))
276 {
277 ERR("IDropTarget not implemented\n");
278 *ppvOut = static_cast<IDropTarget *>(this);
279 AddRef();
280 hr = S_OK;
281 }
282 else if (IsEqualIID(riid, IID_IContextMenu))
283 {
284 ERR("IContextMenu not implemented\n");
285 hr = E_NOTIMPL;
286 }
287 else if (IsEqualIID(riid, IID_IShellView))
288 {
289 // Just create a default shell folder view, and register ourself as folder
290 SFV_CREATE sfv = { sizeof(SFV_CREATE) };
291 sfv.pshf = this;
292 hr = SHCreateShellFolderView(&sfv, (IShellView**)ppvOut);
293 }
294
295 return hr;
296 }
297
298 STDMETHODIMP CFontExt::GetAttributesOf(UINT cidl, PCUITEMID_CHILD_ARRAY apidl, DWORD *rgfInOut)
299 {
300 if (!rgfInOut || !cidl || !apidl)
301 return E_INVALIDARG;
302
303 DWORD rgf = 0;
304 while (cidl > 0 && *apidl)
305 {
306 const FontPidlEntry* fontEntry = _FontFromIL(*apidl);
307 if (fontEntry)
308 {
309 // We don't support delete yet
310 rgf |= (/*SFGAO_CANDELETE |*/ SFGAO_HASPROPSHEET | SFGAO_CANCOPY | SFGAO_FILESYSTEM);
311 }
312 else
313 {
314 rgf = 0;
315 break;
316 }
317
318 apidl++;
319 cidl--;
320 }
321
322 *rgfInOut = rgf;
323 return S_OK;
324 }
325
326
327 STDMETHODIMP CFontExt::GetUIObjectOf(HWND hwndOwner, UINT cidl, PCUITEMID_CHILD_ARRAY apidl, REFIID riid, UINT * prgfInOut, LPVOID * ppvOut)
328 {
329 if (riid == IID_IContextMenu ||
330 riid == IID_IContextMenu2 ||
331 riid == IID_IContextMenu3)
332 {
333 return _CFontMenu_CreateInstance(hwndOwner, cidl, apidl, this, riid, ppvOut);
334 }
335 else if (riid == IID_IExtractIconA || riid == IID_IExtractIconW)
336 {
337 if (cidl == 1)
338 {
339 const FontPidlEntry* fontEntry = _FontFromIL(*apidl);
340 if (fontEntry)
341 {
342 DWORD dwAttributes = FILE_ATTRIBUTE_NORMAL;
343 CStringW File = g_FontCache->Filename(fontEntry);
344 // Just create a default icon extractor based on the filename
345 // We might want to create a preview with the font to get really fancy one day.
346 return SHCreateFileExtractIconW(File, dwAttributes, riid, ppvOut);
347 }
348 }
349 else
350 {
351 ERR("IID_IExtractIcon with cidl != 1 UNIMPLEMENTED\n");
352 }
353 }
354 else if (riid == IID_IDataObject)
355 {
356 if (cidl >= 1)
357 {
358 return _CDataObject_CreateInstance(m_Folder, cidl, apidl, riid, ppvOut);
359 }
360 else
361 {
362 ERR("IID_IDataObject with cidl == 0 UNIMPLEMENTED\n");
363 }
364 }
365
366 //ERR("%s(riid=%S) UNIMPLEMENTED\n", __FUNCTION__, g2s(riid));
367 return E_NOTIMPL;
368 }
369
370 STDMETHODIMP CFontExt::GetDisplayNameOf(PCUITEMID_CHILD pidl, DWORD dwFlags, LPSTRRET strRet)
371 {
372 if (!pidl)
373 return E_NOTIMPL;
374
375 // Validate that this pidl is the last one
376 PCUIDLIST_RELATIVE curpidl = ILGetNext(pidl);
377 if (curpidl->mkid.cb != 0)
378 {
379 ERR("ERROR, unhandled PIDL!\n");
380 return E_FAIL;
381 }
382
383 const FontPidlEntry* fontEntry = _FontFromIL(pidl);
384 if (!fontEntry)
385 return E_FAIL;
386
387 return SHSetStrRet(strRet, fontEntry->Name);
388 }
389
390 STDMETHODIMP CFontExt::SetNameOf(HWND hwndOwner, PCUITEMID_CHILD pidl, LPCOLESTR lpName, DWORD dwFlags, PITEMID_CHILD *pPidlOut)
391 {
392 ERR("%s() UNIMPLEMENTED\n", __FUNCTION__);
393 return E_NOTIMPL;
394 }
395
396 // *** IPersistFolder2 methods ***
397 STDMETHODIMP CFontExt::GetCurFolder(LPITEMIDLIST *ppidl)
398 {
399 if (ppidl && m_Folder)
400 {
401 *ppidl = ILClone(m_Folder);
402 return S_OK;
403 }
404
405 return E_POINTER;
406 }
407
408
409 // *** IPersistFolder methods ***
410 STDMETHODIMP CFontExt::Initialize(LPCITEMIDLIST pidl)
411 {
412 WCHAR PidlPath[MAX_PATH + 1] = {0}, FontsDir[MAX_PATH + 1];
413 if (!SHGetPathFromIDListW(pidl, PidlPath))
414 {
415 ERR("Unable to extract path from pidl\n");
416 return E_FAIL;
417 }
418
419 HRESULT hr = SHGetFolderPathW(NULL, CSIDL_FONTS, NULL, 0, FontsDir);
420 if (FAILED_UNEXPECTEDLY(hr))
421 {
422 ERR("Unable to get fonts path (0x%x)\n", hr);
423 return hr;
424 }
425
426 if (_wcsicmp(PidlPath, FontsDir))
427 {
428 ERR("CFontExt View initializing on unexpected folder: '%S'\n", PidlPath);
429 return E_FAIL;
430 }
431
432 m_Folder.Attach(ILClone(pidl));
433 StringCchCatW(FontsDir, _countof(FontsDir), L"\\");
434 g_FontCache->SetFontDir(FontsDir);
435
436 return S_OK;
437 }
438
439
440 // *** IPersist methods ***
441 STDMETHODIMP CFontExt::GetClassID(CLSID *lpClassId)
442 {
443 *lpClassId = CLSID_CFontExt;
444 return S_OK;
445 }
446
447 // *** IDropTarget methods ***
448 STDMETHODIMP CFontExt::DragEnter(IDataObject* pDataObj, DWORD grfKeyState, POINTL pt, DWORD* pdwEffect)
449 {
450 *pdwEffect = DROPEFFECT_NONE;
451
452 CComHeapPtr<CIDA> cida;
453 HRESULT hr = _GetCidlFromDataObject(pDataObj, &cida);
454 if (FAILED_UNEXPECTEDLY(hr))
455 return hr;
456
457 #if 1 // Please implement DoGetFontTitle
458 return DRAGDROP_S_CANCEL;
459 #else
460 *pdwEffect = DROPEFFECT_COPY;
461 return S_OK;
462 #endif
463 }
464
465 STDMETHODIMP CFontExt::DragOver(DWORD grfKeyState, POINTL pt, DWORD* pdwEffect)
466 {
467 return S_OK;
468 }
469
470 STDMETHODIMP CFontExt::DragLeave()
471 {
472 return S_OK;
473 }
474
475 STDMETHODIMP CFontExt::Drop(IDataObject* pDataObj, DWORD grfKeyState, POINTL pt, DWORD* pdwEffect)
476 {
477 *pdwEffect = DROPEFFECT_NONE;
478
479 CComHeapPtr<CIDA> cida;
480 HRESULT hr = _GetCidlFromDataObject(pDataObj, &cida);
481 if (FAILED_UNEXPECTEDLY(hr))
482 return hr;
483
484 PCUIDLIST_ABSOLUTE pidlParent = HIDA_GetPIDLFolder(cida);
485 if (!pidlParent)
486 {
487 ERR("pidlParent is NULL\n");
488 return E_FAIL;
489 }
490
491 BOOL bOK = TRUE;
492 CAtlArray<CStringW> FontPaths;
493 for (UINT n = 0; n < cida->cidl; ++n)
494 {
495 PCUIDLIST_RELATIVE pidlRelative = HIDA_GetPIDLItem(cida, n);
496 if (!pidlRelative)
497 continue;
498
499 PIDLIST_ABSOLUTE pidl = ILCombine(pidlParent, pidlRelative);
500 if (!pidl)
501 {
502 ERR("ILCombine failed\n");
503 bOK = FALSE;
504 break;
505 }
506
507 WCHAR szPath[MAX_PATH];
508 BOOL ret = SHGetPathFromIDListW(pidl, szPath);
509 ILFree(pidl);
510
511 if (!ret)
512 {
513 ERR("SHGetPathFromIDListW failed\n");
514 bOK = FALSE;
515 break;
516 }
517
518 if (PathIsDirectoryW(szPath))
519 {
520 ERR("PathIsDirectory\n");
521 bOK = FALSE;
522 break;
523 }
524
525 LPCWSTR pchDotExt = PathFindExtensionW(szPath);
526 if (!IsFontDotExt(pchDotExt))
527 {
528 ERR("'%S' is not supported\n", pchDotExt);
529 bOK = FALSE;
530 break;
531 }
532
533 FontPaths.Add(szPath);
534 }
535
536 if (!bOK)
537 return E_FAIL;
538
539 CRegKey keyFonts;
540 if (keyFonts.Open(FONT_HIVE, FONT_KEY, KEY_WRITE) != ERROR_SUCCESS)
541 {
542 ERR("keyFonts.Open failed\n");
543 return E_FAIL;
544 }
545
546 for (size_t iItem = 0; iItem < FontPaths.GetCount(); ++iItem)
547 {
548 HRESULT hr = DoInstallFontFile(FontPaths[iItem], g_FontCache->FontPath(), keyFonts.m_hKey);
549 if (FAILED_UNEXPECTEDLY(hr))
550 {
551 bOK = FALSE;
552 break;
553 }
554 }
555
556 // TODO: update g_FontCache
557
558 SendMessageW(HWND_BROADCAST, WM_FONTCHANGE, 0, 0);
559
560 // TODO: Show message
561
562 return bOK ? S_OK : E_FAIL;
563 }
564
565 HRESULT CFontExt::DoInstallFontFile(LPCWSTR pszFontPath, LPCWSTR pszFontsDir, HKEY hkeyFonts)
566 {
567 WCHAR szDestFile[MAX_PATH];
568 LPCWSTR pszFileTitle = PathFindFileName(pszFontPath);
569
570 WCHAR szFontName[512];
571 if (!DoGetFontTitle(pszFontPath, szFontName))
572 return E_FAIL;
573
574 RemoveFontResourceW(pszFileTitle);
575
576 StringCchCopyW(szDestFile, sizeof(szDestFile), pszFontsDir);
577 PathAppendW(szDestFile, pszFileTitle);
578 if (!CopyFileW(pszFontPath, szDestFile, FALSE))
579 {
580 ERR("CopyFileW('%S', '%S') failed\n", pszFontPath, szDestFile);
581 return E_FAIL;
582 }
583
584 if (!AddFontResourceW(pszFileTitle))
585 {
586 ERR("AddFontResourceW('%S') failed\n", pszFileTitle);
587 DeleteFileW(szDestFile);
588 return E_FAIL;
589 }
590
591 DWORD cbData = (wcslen(pszFileTitle) + 1) * sizeof(WCHAR);
592 LONG nError = RegSetValueExW(hkeyFonts, szFontName, 0, REG_SZ, (const BYTE *)szFontName, cbData);
593 if (nError)
594 {
595 ERR("RegSetValueExW failed with %ld\n", nError);
596 RemoveFontResourceW(pszFileTitle);
597 DeleteFileW(szDestFile);
598 return E_FAIL;
599 }
600
601 return S_OK;
602 }
603
604 HRESULT CFontExt::DoGetFontTitle(LPCWSTR pszFontPath, LPCWSTR pszFontName)
605 {
606 // TODO:
607 return E_FAIL;
608 }