From 7cfdbd74c8c193ca4e379799521871e012a8e96a Mon Sep 17 00:00:00 2001
From: Bart <9132501-flyingsheep@users.noreply.gitlab.com>
Date: Sun, 5 Jan 2025 15:02:11 +0100
Subject: [PATCH] Dialogs: implement TOpenOptionEx.ofUseXPStyleAsFallBack
 (obviously Windows only). Resolves issue #40298.

---
 lcl/dialogs.pp                         |  3 +-
 lcl/interfaces/win32/win32wsdialogs.pp | 92 +++++++++++++++++++++-----
 2 files changed, 76 insertions(+), 19 deletions(-)

diff --git a/lcl/dialogs.pp b/lcl/dialogs.pp
index 24c31cebf2..77840dfdca 100644
--- a/lcl/dialogs.pp
+++ b/lcl/dialogs.pp
@@ -222,8 +222,9 @@ type
     ofPickFolders,              //Windows Vista+ Turns the dialog into a TSelectDirectoryDialog
     ofOkButtonNeedsInteraction, //Windows Vista+ The OK button will be disabled until the user navigates the view or edits the filename (if applicable).
     ofForceFileSystem,          //Windows Vista+ Ensures that returned items are file system items
-    ofAllNonStorageItems        //Windows Vista+ Enables the user to choose any item in the Shell namespace, not just those with SFGAO_STREAM or SFAGO_FILESYSTEM attributes.
+    ofAllNonStorageItems,       //Windows Vista+ Enables the user to choose any item in the Shell namespace, not just those with SFGAO_STREAM or SFAGO_FILESYSTEM attributes.
                                 //               This flag cannot be combined with FOS_FORCEFILESYSTEM.
+    ofUseXPStyleAsFallBack      //Windows Vista+ Use XP style dialog if creating Vista style dialog fails (e.g. when running under Windows Recovery)
     // Intentionally not supported: ofDefaultNoMiniMode, ofHideMruPlaces: these values are not supported as of Windows 7.
   );
   TOpenOptionsEx = set of TOpenOptionEx;
diff --git a/lcl/interfaces/win32/win32wsdialogs.pp b/lcl/interfaces/win32/win32wsdialogs.pp
index 62b3ac5d5e..89eb0ab369 100644
--- a/lcl/interfaces/win32/win32wsdialogs.pp
+++ b/lcl/interfaces/win32/win32wsdialogs.pp
@@ -193,7 +193,7 @@ var
 implementation
 
 uses
-  CommCtrl, TaskDlgEmulation;
+  CommCtrl, TaskDlgEmulation, contnrs;
 
 function SaveApplicationState: TApplicationState;
 begin
@@ -354,15 +354,6 @@ begin
     ACommonDialog.UserChoice := mrCancel;
 end;
 
-function CanUseVistaDialogs(const AOpenDialog: TOpenDialog): Boolean;
-begin
-  {$IFnDEF DisableVistaDialogs}
-  Result := (WindowsVersion >= wvVista) and not (ofOldStyleDialog in AOpenDialog.Options);
-  {$ELSE}
-  Result := False;
-  {$ENDIF}
-end;
-
 
 { TWin32WSColorDialog }
 
@@ -788,6 +779,47 @@ end;
 
 { TWin32WSOpenDialog }
 
+var
+  XPStyleFallBackList: TFPObjectList = nil;
+
+procedure MaybeInitXPStyleFallBackList;
+begin
+  if not Assigned(XPStyleFallBackList) then
+    XPStyleFallBackList := TFPObjectList.Create(False); //don't free objects
+end;
+
+procedure FreeXPStyleFallBackList;
+begin
+  if Assigned(XPStyleFallBackList) then
+    FreeAndNil(XPStyleFallBackList);
+end;
+
+function IsXPStyleFallBack(const AOpenDialog: TOpenDialog): Boolean;
+var
+  Idx: Integer;
+begin
+  if not Assigned(XPStyleFallBackList) or not (ofUseXPStyleAsFallBack in AOpenDialog.OptionsEx) then
+    Exit(False);
+  Idx := XPStyleFallBackList.IndexOf(AOpenDialog);
+  Result := (Idx <> -1);
+end;
+
+function IsXPStyleFallBack(const AOpenDialog: TOpenDialog; out Idx: Integer): Boolean;
+begin
+  if not Assigned(XPStyleFallBackList) or not (ofUseXPStyleAsFallBack in AOpenDialog.OptionsEx) then
+    Exit(False);
+  Idx := XPStyleFallBackList.IndexOf(AOpenDialog);
+  Result := (Idx <> -1);
+end;
+
+function CanUseVistaDialogs(const AOpenDialog: TOpenDialog): Boolean;
+begin
+  {$IFnDEF DisableVistaDialogs}
+  Result := (WindowsVersion >= wvVista) and not (ofOldStyleDialog in AOpenDialog.Options);
+  {$ELSE}
+  Result := False;
+  {$ENDIF}
+end;
 
 class procedure TWin32WSOpenDialog.SetupVistaFileDialog(ADialog: IFileDialog; const AOpenDialog: TOpenDialog);
 var
@@ -998,8 +1030,9 @@ var
   HRes: HRESULT;
   DlgType: TIID;
   CLS_ID: TGUID;
+  AOpenDialog: TOpenDialog absolute ACommonDialog;
 begin
-  if CanUseVistaDialogs(TOpenDialog(ACommonDialog)) then
+  if CanUseVistaDialogs(AOpenDialog) then
   begin
     if (ACommonDialog is TSaveDialog) then
     begin
@@ -1015,29 +1048,49 @@ begin
     if Succeeded(HRes) and Assigned(Dialog) then
     begin
       Dialog._AddRef;
-      SetupVistaFileDialog(Dialog, TOpenDialog(ACommonDialog));
+      SetupVistaFileDialog(Dialog, AOpenDialog);
       Result := THandle(Dialog);
     end
     else
-      Result := INVALID_HANDLE_VALUE;
-  end
+    begin
+      if (ofUseXPStyleAsFallback in AOpenDialog.OptionsEx) then
+      begin
+        MaybeInitXPStyleFallBackList;
+        XPStyleFallbackList.Add(ACommonDialog);
+        //debugln(['TWin32WSOpenDialog.CreateHandle: Added ',DbgSName(AOpenDialog),' to XPStyleFallbackList']);
+        Result := CreateFileDialogHandle(AOpenDialog);
+      end
+      else
+        Result := INVALID_HANDLE_VALUE;
+    end;
+  end//CanUseVistaDialogs
   else
-    Result := CreateFileDialogHandle(TOpenDialog(ACommonDialog));
+    Result := CreateFileDialogHandle(AOpenDialog);
 end;
 
 class procedure TWin32WSOpenDialog.DestroyHandle(const ACommonDialog: TCommonDialog);
 var
   Dialog: IFileDialog;
+  Idx: Integer;
 begin
   if (ACommonDialog.Handle <> 0) and (ACommonDialog.Handle <> INVALID_HANDLE_VALUE) then
-    if CanUseVistaDialogs(TOpenDialog(ACommonDialog)) then
+  begin
+    if CanUseVistaDialogs(TOpenDialog(ACommonDialog)) and not IsXPStyleFallBack(TOpenDialog(ACommonDialog), Idx) then
     begin
       Dialog := IFileDialog(ACommonDialog.Handle);
       Dialog._Release;
       Dialog := nil;
     end
     else
+    begin
+      if (Idx <> -1) then
+      begin
+        //debugln(['TWin32WSOpenDialog.CreateHandle: Removing ',DbgSName(ACommonDialog),' from XPStyleFallbackList']);
+        XPStyleFallBackList.Delete(Idx);
+      end;
       DestroyFileDialogHandle(ACommonDialog.Handle)
+    end;
+  end;
 end;
 
 class procedure TWin32WSOpenDialog.ShowModal(const ACommonDialog: TCommonDialog);
@@ -1054,7 +1107,7 @@ begin
       lInitialDir := TOpenDialog(ACommonDialog).InitialDir;
       if lInitialDir <> '' then
         SetCurrentDirUTF8(lInitialDir);
-      if CanUseVistaDialogs(TOpenDialog(ACommonDialog)) then
+      if CanUseVistaDialogs(TOpenDialog(ACommonDialog)) and not IsXPStyleFallBack(TOpenDialog(ACommonDialog)) then
       begin
         Dialog := IFileOpenDialog(ACommonDialog.Handle);
         VistaDialogShowModal(Dialog, TOpenDialog(ACommonDialog));
@@ -1136,7 +1189,7 @@ begin
       lInitialDir := TSaveDialog(ACommonDialog).InitialDir;
       if lInitialDir <> '' then
         SetCurrentDirUTF8(lInitialDir);
-      if CanUseVistaDialogs(TOpenDialog(ACommonDialog)) then
+      if CanUseVistaDialogs(TOpenDialog(ACommonDialog)) and not IsXPStyleFallBack(TOpenDialog(ACommonDialog)) then
       begin
         Dialog := IFileSaveDialog(ACommonDialog.Handle);
         TWin32WSOpenDialog.VistaDialogShowModal(Dialog, TOpenDialog(ACommonDialog));
@@ -2080,4 +2133,7 @@ initialization
   else
     OpenFileNameSize := SizeOf(OPENFILENAME);
   InitTaskDialogIndirect;
+
+finalization
+  FreeXPStyleFallBackList
 end.