[SHELL] IPersistFolder::Initialize takes a PCIDLIST_ABSOLUTE. CORE-16385
[reactos.git] / dll / shellext / ntobjshex / foldercommon.h
1 /*
2 * PROJECT: ReactOS shell extensions
3 * LICENSE: GPL - See COPYING in the top level directory
4 * FILE: dll/shellext/ntobjshex/ntobjfolder.h
5 * PURPOSE: NT Object Namespace shell extension
6 * PROGRAMMERS: David Quintana <gigaherz@gmail.com>
7 */
8 #pragma once
9
10 extern const GUID CLSID_NtObjectFolder;
11
12 class CFolderViewCB :
13 public CComObjectRootEx<CComMultiThreadModelNoCS>,
14 public IShellFolderViewCB
15 {
16 IShellView* m_View;
17
18 public:
19
20 CFolderViewCB() : m_View(NULL) {}
21 virtual ~CFolderViewCB() {}
22
23 virtual HRESULT STDMETHODCALLTYPE MessageSFVCB(UINT uMsg, WPARAM wParam, LPARAM lParam)
24 {
25 switch (uMsg)
26 {
27 case SFVM_DEFVIEWMODE:
28 {
29 FOLDERVIEWMODE* pViewMode = (FOLDERVIEWMODE*)lParam;
30 *pViewMode = FVM_DETAILS;
31 return S_OK;
32 }
33 case SFVM_COLUMNCLICK:
34 return S_FALSE;
35 case SFVM_BACKGROUNDENUM:
36 return S_OK;
37 }
38
39 DbgPrint("MessageSFVCB unimplemented %d %08x %08x\n", uMsg, wParam, lParam);
40 return E_NOTIMPL;
41 }
42
43 virtual HRESULT STDMETHODCALLTYPE Initialize(IShellView* psv)
44 {
45 m_View = psv;
46 return S_OK;
47 }
48
49 DECLARE_NOT_AGGREGATABLE(CFolderViewCB)
50 DECLARE_PROTECT_FINAL_CONSTRUCT()
51
52 BEGIN_COM_MAP(CFolderViewCB)
53 COM_INTERFACE_ENTRY_IID(IID_IShellFolderViewCB, IShellFolderViewCB)
54 END_COM_MAP()
55 };
56
57 template<class TSelf, typename TItemId, class TExtractIcon>
58 class CCommonFolder :
59 public CComObjectRootEx<CComMultiThreadModelNoCS>,
60 public IShellFolder2,
61 public IPersistFolder2
62 {
63 protected:
64 WCHAR m_NtPath[MAX_PATH];
65
66 LPITEMIDLIST m_shellPidl;
67
68 public:
69
70 CCommonFolder() :
71 m_shellPidl(NULL)
72 {
73 }
74
75 virtual ~CCommonFolder()
76 {
77 if (m_shellPidl)
78 ILFree(m_shellPidl);
79 }
80
81 // IShellFolder
82 virtual HRESULT STDMETHODCALLTYPE ParseDisplayName(
83 HWND hwndOwner,
84 LPBC pbcReserved,
85 LPOLESTR lpszDisplayName,
86 ULONG *pchEaten,
87 LPITEMIDLIST *ppidl,
88 ULONG *pdwAttributes)
89 {
90 if (!ppidl)
91 return E_POINTER;
92
93 if (pchEaten)
94 *pchEaten = 0;
95
96 if (pdwAttributes)
97 *pdwAttributes = 0;
98
99 TRACE("CCommonFolder::ParseDisplayName name=%S (ntPath=%S)\n", lpszDisplayName, m_NtPath);
100
101 const TItemId * info;
102 IEnumIDList * it;
103 HRESULT hr = EnumObjects(hwndOwner, SHCONTF_FOLDERS | SHCONTF_NONFOLDERS, &it);
104 if (FAILED(hr))
105 return hr;
106
107 PWSTR end = StrChrW(lpszDisplayName, '\\');
108 int length = end ? end - lpszDisplayName : wcslen(lpszDisplayName);
109
110 while (TRUE)
111 {
112 hr = it->Next(1, ppidl, NULL);
113
114 if (FAILED(hr))
115 return hr;
116
117 if (hr != S_OK)
118 return HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND);
119
120 hr = GetInfoFromPidl(*ppidl, &info);
121 if (FAILED_UNEXPECTEDLY(hr))
122 return hr;
123
124 if (StrCmpNW(info->entryName, lpszDisplayName, length) == 0)
125 break;
126 }
127
128 // if has remaining path to parse (and the path didn't just terminate in a backslash)
129 if (end && wcslen(end) > 1)
130 {
131 CComPtr<IShellFolder> psfChild;
132 hr = BindToObject(*ppidl, pbcReserved, IID_PPV_ARG(IShellFolder, &psfChild));
133 if (FAILED_UNEXPECTEDLY(hr))
134 return hr;
135
136 LPITEMIDLIST child;
137 hr = psfChild->ParseDisplayName(hwndOwner, pbcReserved, end + 1, pchEaten, &child, pdwAttributes);
138 if (FAILED(hr))
139 return hr;
140
141 LPITEMIDLIST old = *ppidl;
142 *ppidl = ILCombine(old, child);
143 ILFree(old);
144
145 // Count the path separator
146 if (pchEaten)
147 (*pchEaten) += 1;
148 }
149 else
150 {
151 if (pdwAttributes)
152 *pdwAttributes = ConvertAttributes(info, pdwAttributes);
153 }
154
155 if (pchEaten)
156 *pchEaten += wcslen(info->entryName);
157
158 return S_OK;
159 }
160
161 virtual HRESULT STDMETHODCALLTYPE EnumObjects(
162 HWND hwndOwner,
163 SHCONTF grfFlags,
164 IEnumIDList **ppenumIDList) PURE;
165
166 virtual HRESULT STDMETHODCALLTYPE BindToObject(
167 LPCITEMIDLIST pidl,
168 LPBC pbcReserved,
169 REFIID riid,
170 void **ppvOut)
171 {
172 const TItemId * info;
173
174 if (IsEqualIID(riid, IID_IShellFolder))
175 {
176 HRESULT hr = GetInfoFromPidl(pidl, &info);
177 if (FAILED_UNEXPECTEDLY(hr))
178 return hr;
179
180 WCHAR path[MAX_PATH];
181
182 StringCbCopyW(path, _countof(path), m_NtPath);
183 PathAppendW(path, info->entryName);
184
185 LPITEMIDLIST first = ILCloneFirst(pidl);
186 LPCITEMIDLIST rest = ILGetNext(pidl);
187
188 LPITEMIDLIST fullPidl = ILCombine(m_shellPidl, first);
189
190 CComPtr<IShellFolder> psfChild;
191 hr = InternalBindToObject(path, info, first, rest, fullPidl, pbcReserved, &psfChild);
192
193 ILFree(fullPidl);
194 ILFree(first);
195
196 if (FAILED(hr))
197 return hr;
198
199 if (hr == S_FALSE)
200 return S_OK;
201
202 if (rest->mkid.cb > 0)
203 {
204 return psfChild->BindToObject(rest, pbcReserved, riid, ppvOut);
205 }
206
207 return psfChild->QueryInterface(riid, ppvOut);
208 }
209
210 return E_NOTIMPL;
211 }
212
213 protected:
214 virtual HRESULT STDMETHODCALLTYPE InternalBindToObject(
215 PWSTR path,
216 const TItemId * info,
217 LPITEMIDLIST first,
218 LPCITEMIDLIST rest,
219 LPITEMIDLIST fullPidl,
220 LPBC pbcReserved,
221 IShellFolder** ppsfChild) PURE;
222
223 virtual HRESULT STDMETHODCALLTYPE ResolveSymLink(
224 const TItemId * info,
225 LPITEMIDLIST * fullPidl)
226 {
227 return E_NOTIMPL;
228 }
229
230 public:
231
232 virtual HRESULT STDMETHODCALLTYPE BindToStorage(
233 LPCITEMIDLIST pidl,
234 LPBC pbcReserved,
235 REFIID riid,
236 void **ppvObj)
237 {
238 UNIMPLEMENTED;
239 return E_NOTIMPL;
240 }
241
242 virtual HRESULT STDMETHODCALLTYPE CompareIDs(
243 LPARAM lParam,
244 LPCITEMIDLIST pidl1,
245 LPCITEMIDLIST pidl2)
246 {
247 HRESULT hr;
248
249 TRACE("CompareIDs %d\n", lParam);
250
251 const TItemId * id1;
252 hr = GetInfoFromPidl(pidl1, &id1);
253 if (FAILED(hr))
254 return E_INVALIDARG;
255
256 const TItemId * id2;
257 hr = GetInfoFromPidl(pidl2, &id2);
258 if (FAILED(hr))
259 return E_INVALIDARG;
260
261 hr = CompareIDs(lParam, id1, id2);
262 if (hr != S_EQUAL)
263 return hr;
264
265 // The wollowing snipped is basically SHELL32_CompareChildren
266
267 PUIDLIST_RELATIVE rest1 = ILGetNext(pidl1);
268 PUIDLIST_RELATIVE rest2 = ILGetNext(pidl2);
269
270 bool isEmpty1 = (rest1->mkid.cb == 0);
271 bool isEmpty2 = (rest2->mkid.cb == 0);
272
273 if (isEmpty1 || isEmpty2)
274 return MAKE_COMPARE_HRESULT(isEmpty2 - isEmpty1);
275
276 LPCITEMIDLIST first1 = ILCloneFirst(pidl1);
277 if (!first1)
278 return E_OUTOFMEMORY;
279
280 CComPtr<IShellFolder> psfNext;
281 hr = BindToObject(first1, NULL, IID_PPV_ARG(IShellFolder, &psfNext));
282 if (FAILED_UNEXPECTEDLY(hr))
283 return hr;
284
285 return psfNext->CompareIDs(lParam, rest1, rest2);
286 }
287
288 protected:
289 virtual HRESULT STDMETHODCALLTYPE CompareName(
290 LPARAM lParam,
291 const TItemId * first,
292 const TItemId * second)
293 {
294 bool f1 = IsFolder(first);
295 bool f2 = IsFolder(second);
296
297 HRESULT hr = MAKE_COMPARE_HRESULT(f2 - f1);
298 if (hr != S_EQUAL)
299 return hr;
300
301 bool canonical = (lParam & 0xFFFF0000) == SHCIDS_CANONICALONLY;
302 if (canonical)
303 {
304 // Shortcut: avoid comparing contents if not necessary when the results are not for display.
305 hr = MAKE_COMPARE_HRESULT(second->entryNameLength - first->entryNameLength);
306 if (hr != S_EQUAL)
307 return hr;
308
309 int minlength = min(first->entryNameLength, second->entryNameLength);
310 if (minlength > 0)
311 {
312 hr = MAKE_COMPARE_HRESULT(memcmp(first->entryName, second->entryName, minlength));
313 if (hr != S_EQUAL)
314 return hr;
315 }
316
317 return S_EQUAL;
318 }
319
320 int minlength = min(first->entryNameLength, second->entryNameLength);
321 if (minlength > 0)
322 {
323 hr = MAKE_COMPARE_HRESULT(StrCmpNW(first->entryName, second->entryName, minlength / sizeof(WCHAR)));
324 if (hr != S_EQUAL)
325 return hr;
326 }
327
328 return MAKE_COMPARE_HRESULT(second->entryNameLength - first->entryNameLength);
329 }
330
331 public:
332 virtual HRESULT STDMETHODCALLTYPE CreateViewObject(
333 HWND hwndOwner,
334 REFIID riid,
335 void **ppvOut)
336 {
337 if (!IsEqualIID(riid, IID_IShellView))
338 return E_NOINTERFACE;
339
340 _CComObject<CFolderViewCB> *pcb;
341
342 HRESULT hr = _CComObject<CFolderViewCB>::CreateInstance(&pcb);
343 if (FAILED(hr))
344 return hr;
345
346 pcb->AddRef();
347
348 SFV_CREATE sfv;
349 sfv.cbSize = sizeof(sfv);
350 sfv.pshf = this;
351 sfv.psvOuter = NULL;
352 sfv.psfvcb = pcb;
353
354 IShellView* view;
355
356 hr = SHCreateShellFolderView(&sfv, &view);
357 if (FAILED(hr))
358 return hr;
359
360 pcb->Initialize(view);
361
362 pcb->Release();
363
364 *ppvOut = view;
365
366 return S_OK;
367 }
368
369 virtual HRESULT STDMETHODCALLTYPE GetAttributesOf(
370 UINT cidl,
371 PCUITEMID_CHILD_ARRAY apidl,
372 SFGAOF *rgfInOut)
373 {
374 const TItemId * info;
375
376 TRACE("GetAttributesOf %d\n", cidl);
377
378 if (cidl == 0)
379 {
380 *rgfInOut &= SFGAO_FOLDER | SFGAO_HASSUBFOLDER | SFGAO_BROWSABLE;
381 return S_OK;
382 }
383
384 for (int i = 0; i < (int)cidl; i++)
385 {
386 PCUITEMID_CHILD pidl = apidl[i];
387
388 HRESULT hr = GetInfoFromPidl(pidl, &info);
389 if (FAILED_UNEXPECTEDLY(hr))
390 return hr;
391
392 // Update attributes.
393 *rgfInOut = ConvertAttributes(info, rgfInOut);
394 }
395
396 return S_OK;
397 }
398
399 virtual HRESULT STDMETHODCALLTYPE GetUIObjectOf(
400 HWND hwndOwner,
401 UINT cidl,
402 PCUITEMID_CHILD_ARRAY apidl,
403 REFIID riid,
404 UINT *prgfInOut,
405 void **ppvOut)
406 {
407 DWORD res;
408 TRACE("GetUIObjectOf\n");
409
410 if (IsEqualIID(riid, IID_IContextMenu) ||
411 IsEqualIID(riid, IID_IContextMenu2) ||
412 IsEqualIID(riid, IID_IContextMenu3))
413 {
414 CComPtr<IContextMenu> pcm;
415
416 HKEY keys[1];
417
418 LPITEMIDLIST parent = m_shellPidl;
419
420 CComPtr<IShellFolder> psfParent = this;
421
422 LPCITEMIDLIST child;
423
424 int nkeys = _countof(keys);
425 if (cidl == 1 && IsSymLink(apidl[0]))
426 {
427 const TItemId * info;
428 HRESULT hr = GetInfoFromPidl(apidl[0], &info);
429 if (FAILED(hr))
430 return hr;
431
432 LPITEMIDLIST target;
433 hr = ResolveSymLink(info, &target);
434 if (FAILED(hr))
435 return hr;
436
437 CComPtr<IShellFolder> psfTarget;
438 hr = ::SHBindToParent(target, IID_PPV_ARG(IShellFolder, &psfTarget), &child);
439 if (FAILED(hr))
440 {
441 ILFree(target);
442 return hr;
443 }
444
445 parent = ILClone(target);
446 ILRemoveLastID(parent);
447 psfParent = psfTarget;
448
449 apidl = &child;
450 }
451
452 if (cidl == 1 && IsFolder(apidl[0]))
453 {
454 res = RegOpenKey(HKEY_CLASSES_ROOT, L"Folder", keys + 0);
455 if (!NT_SUCCESS(res))
456 return HRESULT_FROM_NT(res);
457 }
458 else
459 {
460 nkeys = 0;
461 }
462
463 HRESULT hr = CDefFolderMenu_Create2(parent, hwndOwner, cidl, apidl, psfParent, DefCtxMenuCallback, nkeys, keys, &pcm);
464 if (FAILED_UNEXPECTEDLY(hr))
465 return hr;
466
467 return pcm->QueryInterface(riid, ppvOut);
468 }
469
470 if (IsEqualIID(riid, IID_IExtractIconW))
471 {
472 return ShellObjectCreatorInit<TExtractIcon>(m_NtPath, m_shellPidl, cidl, apidl, riid, ppvOut);
473 }
474
475 if (IsEqualIID(riid, IID_IDataObject))
476 {
477 return CIDLData_CreateFromIDArray(m_shellPidl, cidl, apidl, (IDataObject**)ppvOut);
478 }
479
480 if (IsEqualIID(riid, IID_IQueryAssociations))
481 {
482 if (cidl == 1 && IsFolder(apidl[0]))
483 {
484 CComPtr<IQueryAssociations> pqa;
485 HRESULT hr = AssocCreate(CLSID_QueryAssociations, IID_PPV_ARG(IQueryAssociations, &pqa));
486 if (FAILED_UNEXPECTEDLY(hr))
487 return hr;
488
489 hr = pqa->Init(ASSOCF_INIT_DEFAULTTOFOLDER, L"NTObjShEx.NTDirectory", NULL, hwndOwner);
490 if (FAILED_UNEXPECTEDLY(hr))
491 return hr;
492
493 return pqa->QueryInterface(riid, ppvOut);
494 }
495 }
496
497 return E_NOTIMPL;
498 }
499
500 virtual HRESULT STDMETHODCALLTYPE GetDisplayNameOf(
501 LPCITEMIDLIST pidl,
502 SHGDNF uFlags,
503 STRRET *lpName)
504 {
505 const TItemId * info;
506
507 TRACE("GetDisplayNameOf %p\n", pidl);
508
509 HRESULT hr = GetInfoFromPidl(pidl, &info);
510 if (FAILED_UNEXPECTEDLY(hr))
511 return hr;
512
513 if (GET_SHGDN_FOR(uFlags) & SHGDN_FOREDITING)
514 {
515 hr = MakeStrRetFromString(info->entryName, info->entryNameLength, lpName);
516 if (FAILED_UNEXPECTEDLY(hr))
517 return hr;
518 }
519
520 WCHAR path[MAX_PATH] = { 0 };
521
522 if (GET_SHGDN_FOR(uFlags) & SHGDN_FORPARSING)
523 {
524 if (GET_SHGDN_RELATION(uFlags) != SHGDN_INFOLDER)
525 {
526 hr = GetFullName(m_shellPidl, uFlags, path, _countof(path));
527 if (FAILED_UNEXPECTEDLY(hr))
528 return hr;
529 }
530 }
531
532 PathAppendW(path, info->entryName);
533
534 LPCITEMIDLIST pidlNext = ILGetNext(pidl);
535 if (pidlNext && pidlNext->mkid.cb > 0)
536 {
537 LPITEMIDLIST pidlFirst = ILCloneFirst(pidl);
538
539 CComPtr<IShellFolder> psfChild;
540 hr = BindToObject(pidlFirst, NULL, IID_PPV_ARG(IShellFolder, &psfChild));
541 if (FAILED_UNEXPECTEDLY(hr))
542 return hr;
543
544 WCHAR temp[MAX_PATH];
545 STRRET childName;
546
547 hr = psfChild->GetDisplayNameOf(pidlNext, uFlags | SHGDN_INFOLDER, &childName);
548 if (FAILED_UNEXPECTEDLY(hr))
549 return hr;
550
551 hr = StrRetToBufW(&childName, pidlNext, temp, _countof(temp));
552 if (FAILED_UNEXPECTEDLY(hr))
553 return hr;
554
555 PathAppendW(path, temp);
556
557 ILFree(pidlFirst);
558 }
559
560 hr = MakeStrRetFromString(path, lpName);
561 if (FAILED_UNEXPECTEDLY(hr))
562 return hr;
563
564 return S_OK;
565 }
566
567 virtual HRESULT STDMETHODCALLTYPE SetNameOf(
568 HWND hwnd,
569 LPCITEMIDLIST pidl,
570 LPCOLESTR lpszName,
571 SHGDNF uFlags,
572 LPITEMIDLIST *ppidlOut)
573 {
574 UNIMPLEMENTED;
575 return E_NOTIMPL;
576 }
577
578 // IShellFolder2
579 virtual HRESULT STDMETHODCALLTYPE GetDefaultSearchGUID(
580 GUID *lpguid)
581 {
582 UNIMPLEMENTED;
583 return E_NOTIMPL;
584 }
585
586 virtual HRESULT STDMETHODCALLTYPE EnumSearches(
587 IEnumExtraSearch **ppenum)
588 {
589 UNIMPLEMENTED;
590 return E_NOTIMPL;
591 }
592
593 virtual HRESULT STDMETHODCALLTYPE GetDefaultColumn(
594 DWORD dwReserved,
595 ULONG *pSort,
596 ULONG *pDisplay)
597 {
598 if (pSort)
599 *pSort = 0;
600 if (pDisplay)
601 *pDisplay = 0;
602 return S_OK;
603 }
604
605 virtual HRESULT STDMETHODCALLTYPE GetDefaultColumnState(
606 UINT iColumn,
607 SHCOLSTATEF *pcsFlags) PURE;
608
609 virtual HRESULT STDMETHODCALLTYPE GetDetailsEx(
610 LPCITEMIDLIST pidl,
611 const SHCOLUMNID *pscid,
612 VARIANT *pv) PURE;
613
614 virtual HRESULT STDMETHODCALLTYPE GetDetailsOf(
615 LPCITEMIDLIST pidl,
616 UINT iColumn,
617 SHELLDETAILS *psd) PURE;
618
619 virtual HRESULT STDMETHODCALLTYPE MapColumnToSCID(
620 UINT iColumn,
621 SHCOLUMNID *pscid) PURE;
622
623 // IPersist
624 virtual HRESULT STDMETHODCALLTYPE GetClassID(CLSID *lpClassId)
625 {
626 if (!lpClassId)
627 return E_POINTER;
628
629 *lpClassId = CLSID_NtObjectFolder;
630 return S_OK;
631 }
632
633 // IPersistFolder
634 virtual HRESULT STDMETHODCALLTYPE Initialize(PCIDLIST_ABSOLUTE pidl)
635 {
636 m_shellPidl = ILClone(pidl);
637
638 StringCbCopy(m_NtPath, _countof(m_NtPath), L"\\");
639
640 return S_OK;
641 }
642
643 // IPersistFolder2
644 virtual HRESULT STDMETHODCALLTYPE GetCurFolder(PIDLIST_ABSOLUTE * pidl)
645 {
646 if (pidl)
647 *pidl = ILClone(m_shellPidl);
648 if (!m_shellPidl)
649 return S_FALSE;
650 return S_OK;
651 }
652
653 // Internal
654 protected:
655 virtual HRESULT STDMETHODCALLTYPE CompareIDs(
656 LPARAM lParam,
657 const TItemId * first,
658 const TItemId * second) PURE;
659
660 virtual ULONG STDMETHODCALLTYPE ConvertAttributes(
661 const TItemId * entry,
662 PULONG inMask) PURE;
663
664 virtual BOOL STDMETHODCALLTYPE IsFolder(LPCITEMIDLIST pcidl)
665 {
666 const TItemId * info;
667
668 HRESULT hr = GetInfoFromPidl(pcidl, &info);
669 if (FAILED(hr))
670 return hr;
671
672 return IsFolder(info);
673 }
674
675 virtual BOOL STDMETHODCALLTYPE IsFolder(const TItemId * info) PURE;
676
677 virtual BOOL STDMETHODCALLTYPE IsSymLink(LPCITEMIDLIST pcidl)
678 {
679 const TItemId * info;
680
681 HRESULT hr = GetInfoFromPidl(pcidl, &info);
682 if (FAILED(hr))
683 return hr;
684
685 return IsSymLink(info);
686 }
687
688 virtual BOOL STDMETHODCALLTYPE IsSymLink(const TItemId * info)
689 {
690 return FALSE;
691 }
692
693 virtual HRESULT GetInfoFromPidl(LPCITEMIDLIST pcidl, const TItemId ** pentry) PURE;
694
695 public:
696 static HRESULT CALLBACK DefCtxMenuCallback(IShellFolder * /*psf*/, HWND /*hwnd*/, IDataObject * /*pdtobj*/, UINT uMsg, WPARAM /*wParam*/, LPARAM /*lParam*/)
697 {
698 switch (uMsg)
699 {
700 case DFM_MERGECONTEXTMENU:
701 return S_OK;
702 case DFM_INVOKECOMMAND:
703 case DFM_INVOKECOMMANDEX:
704 case DFM_GETDEFSTATICID: // Required for Windows 7 to pick a default
705 return S_FALSE;
706 }
707 return E_NOTIMPL;
708 }
709
710 DECLARE_NOT_AGGREGATABLE(TSelf)
711 DECLARE_PROTECT_FINAL_CONSTRUCT()
712
713 BEGIN_COM_MAP(TSelf)
714 COM_INTERFACE_ENTRY_IID(IID_IShellFolder, IShellFolder)
715 COM_INTERFACE_ENTRY_IID(IID_IShellFolder2, IShellFolder2)
716 COM_INTERFACE_ENTRY_IID(IID_IPersist, IPersist)
717 COM_INTERFACE_ENTRY_IID(IID_IPersistFolder, IPersistFolder)
718 COM_INTERFACE_ENTRY_IID(IID_IPersistFolder2, IPersistFolder2)
719 END_COM_MAP()
720
721 };