bgradialogs.pas 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487
  1. // SPDX-License-Identifier: LGPL-3.0-linking-exception
  2. {
  3. Additional dialogs to take advantage of our controls
  4. 2025-01 Massimo Magnano
  5. }
  6. unit BGRADialogs;
  7. {$mode objfpc}{$H+}
  8. {$ifdef WINDOWS}
  9. //{$define Show_PreviewControl} //THIS IS JUST FOR TESTING, It is not recommended for now under Windows
  10. {$endif}
  11. interface
  12. uses
  13. {$ifdef Show_PreviewControl}
  14. Windows, Graphics,
  15. {$endif}
  16. Classes, SysUtils, ExtDlgs, Controls, StdCtrls, ExtCtrls,
  17. BGRABitmapTypes, BCRoundedImage;
  18. resourcestring
  19. rsSelectAPreviewFile = 'Select the File to preview';
  20. type
  21. { TBGRAOpenPictureDialog }
  22. TBGRAOpenPictureDialog = class(TPreviewFileDialog)
  23. private
  24. FDefaultFilter: string;
  25. FImageCtrl: TBCRoundedImage;
  26. FPicturePanel: TPanel;
  27. FPictureDetails: TLabel;
  28. FPreviewFilename: string;
  29. protected
  30. {$ifdef Show_PreviewControl}
  31. DialogWnd,
  32. pParentWnd, pBrotherWnd : HWnd;
  33. {$endif}
  34. class procedure WSRegisterClass; override;
  35. function IsFilterStored: Boolean; virtual;
  36. procedure InitPreviewControl; override;
  37. procedure ClearPreview; virtual;
  38. procedure UpdatePreview; virtual;
  39. {$ifdef Show_PreviewControl}
  40. procedure GetDialogWnd;
  41. procedure ResizePreviewControl;
  42. {$endif}
  43. property ImageCtrl: TBCRoundedImage read FImageCtrl;
  44. property PicturePanel: TPanel read FPicturePanel;
  45. property PictureDetails: TLabel read FPictureDetails;
  46. public
  47. constructor Create(TheOwner: TComponent); override;
  48. procedure DoClose; override;
  49. procedure DoSelectionChange; override;
  50. procedure DoShow; override;
  51. function GetFilterExt: String;
  52. property DefaultFilter: string read FDefaultFilter;
  53. published
  54. property Filter stored IsFilterStored;
  55. end;
  56. { TSavePictureDialog }
  57. TBGRASavePictureDialog = class(TBGRAOpenPictureDialog)
  58. protected
  59. class procedure WSRegisterClass; override;
  60. function DefaultTitle: string; override;
  61. public
  62. constructor Create(TheOwner: TComponent); override;
  63. end;
  64. {** Get Registered Readers Filters to use in Dialogs}
  65. function BGRARegisteredImageReaderFilter: String; overload;
  66. function BGRARegisteredImageReaderFilter(AFormat: TBGRAImageFormat): String; overload;
  67. {** Get Registered Writers Filters to use in Dialogs}
  68. function BGRARegisteredImageWriterFilter: String; overload;
  69. function BGRARegisteredImageWriterFilter(AFormat: TBGRAImageFormat): String; overload;
  70. procedure Register;
  71. implementation
  72. uses
  73. WSExtDlgs, Masks, FileUtil, LazFileUtils, LCLStrConsts, LCLType;
  74. function BGRARegisteredImageReaderFilter: String;
  75. var
  76. iFormat: TBGRAImageFormat;
  77. curExt,
  78. Extensions: String;
  79. begin
  80. Result:= '';
  81. Extensions:= '';
  82. for iFormat:=ifJpeg to High(TBGRAImageFormat) do
  83. if (DefaultBGRAImageReader[iFormat] <> nil) then
  84. begin
  85. if (iFormat>ifJpeg) then
  86. begin
  87. Result:= Result + '|';
  88. Extensions:= Extensions + ';';
  89. end;
  90. curExt:= StringReplace('*'+ExtensionSeparator+BGRAImageFormat[iFormat].Extensions,
  91. ';', ';*'+ExtensionSeparator, [rfReplaceAll]);
  92. FmtStr(Result, '%s%s (%s)|%s',
  93. [Result, BGRAImageFormat[iFormat].TypeName, curExt, curExt]);
  94. Extensions:= Extensions+curExt;
  95. end;
  96. FmtStr(Result, '%s (%s)|%1:s|%s', [rsGraphic, Extensions, Result]);
  97. end;
  98. function BGRARegisteredImageReaderFilter(AFormat: TBGRAImageFormat): String;
  99. var
  100. curExt: String;
  101. begin
  102. Result:= '';
  103. if (DefaultBGRAImageReader[AFormat] <> nil) then
  104. begin
  105. curExt:= StringReplace('*'+ExtensionSeparator+BGRAImageFormat[AFormat].Extensions,
  106. ';', ';*'+ExtensionSeparator, [rfReplaceAll]);
  107. FmtStr(Result, '%s (%s)|%s',
  108. [BGRAImageFormat[AFormat].TypeName, curExt, curExt]);
  109. end;
  110. end;
  111. function BGRARegisteredImageWriterFilter: String;
  112. var
  113. iFormat: TBGRAImageFormat;
  114. curExt,
  115. Extensions: String;
  116. begin
  117. Result:= '';
  118. Extensions:= '';
  119. for iFormat:=ifJpeg to High(TBGRAImageFormat) do
  120. if (DefaultBGRAImageWriter[iFormat] <> nil) then
  121. begin
  122. if (iFormat>ifJpeg) then
  123. begin
  124. Result:= Result + '|';
  125. Extensions:= Extensions + ';';
  126. end;
  127. curExt:= StringReplace('*'+ExtensionSeparator+BGRAImageFormat[iFormat].Extensions,
  128. ';', ';*'+ExtensionSeparator, [rfReplaceAll]);
  129. FmtStr(Result, '%s%s (%s)|%s',
  130. [Result, BGRAImageFormat[iFormat].TypeName, curExt, curExt]);
  131. Extensions:= Extensions+curExt;
  132. end;
  133. FmtStr(Result, '%s (%s)|%1:s|%s', [rsGraphic, Extensions, Result]);
  134. end;
  135. function BGRARegisteredImageWriterFilter(AFormat: TBGRAImageFormat): String;
  136. var
  137. curExt: String;
  138. begin
  139. Result:= '';
  140. if (DefaultBGRAImageWriter[AFormat] <> nil) then
  141. begin
  142. curExt:= StringReplace('*'+ExtensionSeparator+BGRAImageFormat[AFormat].Extensions,
  143. ';', ';*'+ExtensionSeparator, [rfReplaceAll]);
  144. FmtStr(Result, '%s (%s)|%s',
  145. [BGRAImageFormat[AFormat].TypeName, curExt, curExt]);
  146. end;
  147. end;
  148. (*
  149. procedure BuildBGRAFilterStrings(AUseReaders: Boolean; var Descriptions, Filters: String);
  150. var
  151. iFormat: TBGRAImageFormat;
  152. Filter: String;
  153. addExt: Boolean;
  154. begin
  155. Descriptions := '';
  156. Filters := '';
  157. for iFormat:=Low(TBGRAImageFormat) to High(TBGRAImageFormat) do
  158. begin
  159. if AUseReaders
  160. then addExt:= (iFormat<>ifUnknown) and (DefaultBGRAImageReader[iFormat] <> nil)
  161. else addExt:= (iFormat<>ifUnknown) and (DefaultBGRAImageWriter[iFormat] <> nil);
  162. if addExt then
  163. begin
  164. if (iFormat>ifJpeg) then
  165. begin
  166. Descriptions := Descriptions + '|';
  167. Filters := Filters + ';';
  168. end;
  169. Filter := GetBGRAFormatFilter(iFormat);
  170. FmtStr(Descriptions, '%s%s (%s)|%s',
  171. [Descriptions, BGRAImageFormat[iFormat].TypeName, Filter, Filter]);
  172. FmtStr(Filters, '%s%s', [Filters, Filter]);
  173. end;
  174. end;
  175. FmtStr(Descriptions, '%s (%s)|%1:s|%s', [rsGraphic, Filters, Descriptions]);
  176. end;
  177. function BuildBGRAImageReaderFilter: String;
  178. var
  179. Filters: string;
  180. begin
  181. Result := '';
  182. BuildBGRAFilterStrings(True, Result, Filters);
  183. end;
  184. function BuildBGRAImageWriterFilter: String;
  185. var
  186. Filters: string;
  187. begin
  188. Result := '';
  189. BuildBGRAFilterStrings(False, Result, Filters);
  190. end;
  191. *)
  192. { TBGRAOpenPictureDialog }
  193. class procedure TBGRAOpenPictureDialog.WSRegisterClass;
  194. begin
  195. inherited WSRegisterClass;
  196. RegisterOpenPictureDialog;
  197. end;
  198. function TBGRAOpenPictureDialog.IsFilterStored: Boolean;
  199. begin
  200. Result := (Filter<>FDefaultFilter);
  201. end;
  202. procedure TBGRAOpenPictureDialog.DoClose;
  203. begin
  204. inherited DoClose;
  205. // PreviewFileControl.ParentWindow:=0;
  206. end;
  207. procedure TBGRAOpenPictureDialog.DoSelectionChange;
  208. begin
  209. UpdatePreview;
  210. inherited DoSelectionChange;
  211. end;
  212. procedure TBGRAOpenPictureDialog.DoShow;
  213. begin
  214. ClearPreview;
  215. inherited DoShow;
  216. end;
  217. procedure TBGRAOpenPictureDialog.InitPreviewControl;
  218. begin
  219. inherited InitPreviewControl;
  220. PreviewFileControl.Width:=300;
  221. PreviewFileControl.Height:=300;
  222. FPicturePanel.Parent:=PreviewFileControl;
  223. FPicturePanel.Align:=alClient;
  224. { #note -oMaxM : We create it here because the LCL assumes there is a groupbox
  225. with only an image inside and crashes if it find it before this point }
  226. FPictureDetails:=TLabel.Create(Self);
  227. with FPictureDetails do begin
  228. Name:='FPictureDetails';
  229. Parent:= FPicturePanel;
  230. Top:=PreviewFileControl.Height-20;
  231. Height:=20;
  232. Width:=PreviewFileControl.Width;
  233. Align:=alBottom;
  234. Caption:='';
  235. end;
  236. FImageCtrl.Align:=alClient;
  237. end;
  238. procedure TBGRAOpenPictureDialog.ClearPreview;
  239. begin
  240. FPicturePanel.VerticalAlignment:=taVerticalCenter;
  241. FPicturePanel.Caption:= rsSelectAPreviewFile;
  242. FImageCtrl.Bitmap:=nil;
  243. FImageCtrl.Visible:= False;
  244. FPictureDetails.Caption:='';
  245. end;
  246. procedure TBGRAOpenPictureDialog.UpdatePreview;
  247. var
  248. CurFilename: String;
  249. FileIsValid: boolean;
  250. begin
  251. {$ifdef Show_PreviewControl}
  252. if (DialogWnd = 0) then GetDialogWnd;
  253. ResizePreviewControl;
  254. {$endif}
  255. FPicturePanel.Caption:= '';
  256. FPictureDetails.Caption:='';
  257. CurFilename := FileName;
  258. if CurFilename = FPreviewFilename then exit;
  259. FPreviewFilename := CurFilename;
  260. FileIsValid := FileExistsUTF8(FPreviewFilename)
  261. and (not DirPathExists(FPreviewFilename))
  262. and FileIsReadable(FPreviewFilename);
  263. if FileIsValid then
  264. try
  265. FImageCtrl.Bitmap.LoadFromFile(FPreviewFilename);
  266. FImageCtrl.Visible:= True;
  267. FImageCtrl.Invalidate; { #todo -oMaxM : an event in TBGRBitmap might be useful }
  268. FPictureDetails.Caption:= Format('%d x %d x %d dpi', [FImageCtrl.Bitmap.Width, FImageCtrl.Bitmap.Height, Trunc(FImageCtrl.Bitmap.ResolutionX)]);
  269. except
  270. FileIsValid := False;
  271. end;
  272. if not FileIsValid then ClearPreview;
  273. end;
  274. {$ifdef Show_PreviewControl}
  275. procedure TBGRAOpenPictureDialog.GetDialogWnd;
  276. var
  277. pHandle: HWND;
  278. thID, prID, appID:DWord;
  279. begin
  280. pBrotherWnd:= 0;
  281. pParentWnd:= 0;
  282. //LCL doesn't pass us the Dialog Handle, so we have to look for it the old fashioned way
  283. appID:= GetProcessId;
  284. repeat
  285. DialogWnd:= FindWindowEx(0, DialogWnd, PChar('#32770'), nil);
  286. thID:= GetWindowThreadProcessId(DialogWnd, prID);
  287. until (DialogWnd=0) or (prID = appID);
  288. //Get Parent and Brother Control
  289. // this depends on the OS and needs to be tested as much as possible (for now it works with Windows 10)
  290. if (DialogWnd<>0) then
  291. begin
  292. pHandle:= FindWindowEx(DialogWnd, 0, PChar('DUIViewWndClassName'), nil);
  293. if (pHandle<>0) then //Windows 10
  294. begin
  295. pParentWnd:= FindWindowEx(pHandle, 0, PChar('DirectUIHWND'), nil);
  296. if (pParentWnd<>0) then
  297. begin
  298. repeat
  299. pBrotherWnd:= FindWindowEx(pParentWnd, pBrotherWnd, PChar('CtrlNotifySink'), nil);
  300. pHandle:= FindWindowEx(pBrotherWnd, 0, PChar('SHELLDLL_DefView'), nil);
  301. until (pBrotherWnd=0) or (pHandle<>0);
  302. if (pBrotherWnd<>0) and (pHandle<>0) then PreviewFileControl.ParentWindow:=pParentWnd;
  303. end;
  304. end;
  305. end;
  306. end;
  307. procedure TBGRAOpenPictureDialog.ResizePreviewControl;
  308. var
  309. rectParent, rectBrother: TRect;
  310. begin
  311. if (DialogWnd<>0) and (pParentWnd<>0) and (pBrotherWnd<>0) then
  312. begin
  313. if GetClientRect(pParentWnd, rectParent) and GetWindowRect(pBrotherWnd, rectBrother) then
  314. begin
  315. ScreenToClient(pParentWnd, rectBrother.TopLeft);
  316. ScreenToClient(pParentWnd, rectBrother.BottomRight);
  317. PreviewFileControl.SetBounds(rectBrother.Left+4+rectBrother.Width, rectBrother.Top+4,
  318. rectParent.Right-rectBrother.Right-8,
  319. rectParent.Bottom-rectBrother.Top-8);
  320. end;
  321. end;
  322. end;
  323. {$endif}
  324. constructor TBGRAOpenPictureDialog.Create(TheOwner: TComponent);
  325. begin
  326. inherited Create(TheOwner);
  327. FDefaultFilter := BGRARegisteredImageReaderFilter+'|'+
  328. Format(rsAllFiles,[GetAllFilesMask, GetAllFilesMask,'']);
  329. Filter:=FDefaultFilter;
  330. {$ifdef Show_PreviewControl}
  331. DialogWnd:= 0;
  332. pBrotherWnd:= 0;
  333. pParentWnd:= 0;
  334. {$endif}
  335. FPicturePanel:=TPanel.Create(Self);
  336. with FPicturePanel do begin
  337. Name:='FPicturePanel';
  338. BorderStyle:=bsNone;
  339. BevelOuter:=bvNone;
  340. VerticalAlignment:=taVerticalCenter;
  341. end;
  342. FImageCtrl:=TBCRoundedImage.Create(Self);
  343. with FImageCtrl do begin
  344. Name:='FImageCtrl';
  345. Parent:=FPicturePanel;
  346. Style:=isSquare;
  347. Proportional:=true;
  348. end;
  349. end;
  350. function TBGRAOpenPictureDialog.GetFilterExt: String;
  351. var
  352. ParsedFilter: TParseStringList;
  353. begin
  354. Result := '';
  355. ParsedFilter := TParseStringList.Create(Filter, '|');
  356. try
  357. if (FilterIndex > 0) and (FilterIndex * 2 <= ParsedFilter.Count) then
  358. begin
  359. Result := AnsiLowerCase(ParsedFilter[FilterIndex * 2 - 1]);
  360. // remove *.*
  361. if (Result <> '') and (Result[1] = '*') then Delete(Result, 1, 1);
  362. if (Result <> '') and (Result[1] = '.') then Delete(Result, 1, 1);
  363. if (Result <> '') and (Result[1] = '*') then Delete(Result, 1, 1);
  364. // remove all after ;
  365. if Pos(';', Result) > 0 then Delete(Result, Pos(';', Result), MaxInt);
  366. end;
  367. if Result = '' then Result := DefaultExt;
  368. finally
  369. ParsedFilter.Free;
  370. end;
  371. end;
  372. { TSavePictureDialog }
  373. class procedure TBGRASavePictureDialog.WSRegisterClass;
  374. begin
  375. inherited WSRegisterClass;
  376. RegisterSavePictureDialog;
  377. end;
  378. function TBGRASavePictureDialog.DefaultTitle: string;
  379. begin
  380. Result := rsfdFileSaveAs;
  381. end;
  382. constructor TBGRASavePictureDialog.Create(TheOwner: TComponent);
  383. begin
  384. inherited Create(TheOwner);
  385. FDefaultFilter := BGRARegisteredImageWriterFilter+'|'+
  386. Format(rsAllFiles,[GetAllFilesMask, GetAllFilesMask,'']);
  387. Filter:=FDefaultFilter;
  388. fCompStyle:=csSaveFileDialog;
  389. end;
  390. procedure Register;
  391. begin
  392. RegisterComponents('BGRA Dialogs',[TBGRAOpenPictureDialog, TBGRASavePictureDialog]);
  393. end;
  394. end.