You are not logged in.
Pages: 1
Thanks for the info! I'll stick with r35095 for now.
When initializing mORMot with current FPC trunk the following exception is raised:
[Debugger Exception Notification]
Project Server raised exception class 'ESynException' with message:
TJSONRecordTextDefinition.Create: TSynMapSymbol text definition is not accurate, or the type has not been defined as PACKED record: RTTI size is 7735812 bytes but text definition generated 12 bytes
In file '..\..\units\SynCommons.pas' at line 38490
Backtrace:
#0 fpc_raiseexception(0x760a28, 0x1, 0x18655f8) at ..\inc\except.inc:158
#1 CREATE(0x18655f8, 0x1, 0x760a28, 0x760948 'Name:RawUTF8 Start,Stop:integer') at ..\..\units\SynCommons.pas:38490
#2 FROMCACHE(0x754994, 0x760a28, 0x760948 'Name:RawUTF8 Start,Stop:integer') at ..\..\units\SynCommons.pas:38471
#3 REGISTERFROMTEXT(0x180a920, 0x760a28, 0x760948 'Name:RawUTF8 Start,Stop:integer') at ..\..\units\SynCommons.pas:37428
#4 REGISTERCUSTOMJSONSERIALIZERFROMTEXT(0x754650, {}, 3) at ..\..\units\SynCommons.pas:46831
#5 SYNLOG_$$_init at :5500
#6 fpc_initializeunits at ..\inc\system.inc:914
#7 main at Server.lpr:16
This issue was opened somewhere in revisions 35123-35128 by svenbarth, which basically were patches by Maciej Izak for http://mantis.freepascal.org/view.php?id=30687.
Any chance to get this working?
+ SynSSPI.pas doesn't compile in FPC either (easy to fix: semicolons are missing).
Thank you both. I included it as LCLTaskDialog.pas: http://mantis.freepascal.org/view.php?id=30625
I removed the Delphi-specific IFDEFs and reused LCL icons. I also added a LCL header. If you don't like any of my modifications, please tell me so.
Thank you!
Great! Do you have a contact to Ulrich as well?
Hello Arnauld!
The Lazarus team is interested in including the cross-platform SynTaskDialog ( https://github.com/synopse/mORMot/tree/ … og4Lazarus ) into the LCL core. I'd like to write a Delphi-TTaskDialog clone based upon SynTaskDialog. The file has to be renamed (probably to TaskDialog.pas or TaskDlg.pas) and the license has to be enhanced to the LCL license (FPC modified LGPL).
The reason to include SynTaskDialog into core LCL is that a lot of users need such a basic dialog and the LCL itself needs a cross-platforms TTaskDialog clone.
Are you willing to relicense SynTaskDialog.pas (we need permission from Ulrich Gerhardt as well; we don't need the FMX port from Gergely Kovacs) and give us permission to include it into the LCL? Of course you'll be acknowledged as the original author and all the kudos will stay with you
Ondrej
I sent the full source to Arnauld already. If you want me to send it to you as well, write me an email through the forum.
Allow me one more question: what about initializing the TTaskDialog to an empty object? Doesn't it have to be done? What if there is some garbage data from previous calls?
procedure Test;
var Task: TTaskDialog;
begin
Task.Inst := 'Saving application settings';
Task.Content := 'This is the content';
Task.Radios := 'Store settings in registry'#10'Store settings in XML file';
Task.Verify := 'Do no ask for this setting next time';
Task.VerifyChecked := true;
Task.Footer := 'XML file is perhaps a better choice';
Task.Execute([],0,[],tiQuestion,tfiInformation,200);
ShowMessage(IntToStr(Task.RadioRes)); // 200=Registry, 201=XML
if Task.VerifyChecked then
ShowMessage(Task.Verify);
end;
Hello Arnauld!
I modified your SynTaskDialog.pas to be compatible with Lazarus (Windows, Linux, OSX). I also fixed some small issues. I would be happy if you added my changes to the trunk so that I have them available when I update Synopse!
Here is the patch code (if you want me to send you the whole file, I can do so as well).
3c3
< // licensed under a MPL/GPL/LGPL tri-license; version 1.18
---
> // licensed under a MPL/GPL/LGPL tri-license; version 1.19
32a33
> - Ondrej Pokorny (reddwarf)
70a72,79
> Version 1.19 (Ondrej Pokorny)
> - added Lazarus support (native on Windows Vista+, emulated on all other
> platforms - Windows, Linux and OSX tested)
> - added external translation function for the emulated dialog
> (TaskDialog_Translate)
> - Alt+F4 is blocked now in emulated dialog (same behaviour in native)
> - TODO: platform-independent icons
>
78a88,100
> {$IF (CompilerVersion >= 25.0)}// Delphi XE4 UP
> {$LEGACYIFEND ON}
> {$IFEND}
> {$IF CompilerVersion >= 20}// Delphi 2009 IP
> {$define WITHPOPUPPARENT}
> {$define WITHNATIVEINT}
> {$IFEND}
> {$ENDIF}
> {$IFDEF FPC}
> {$MODE DELPHI}
> {$define WITHUXTHEME}
> {$define WITHPOPUPPARENT}
> {$define WITHNATIVEINT}
82c104,111
< Windows, CommCtrl, Classes, SysUtils, Consts, Messages,
---
> {$IFDEF FPC}
> LCLType, LCLStrConsts, LCLIntf,
> {$ENDIF}
> {$IFDEF MSWINDOWS}
> Windows, CommCtrl, Messages,
> {$ENDIF}
> Classes, SysUtils,
> {$IFNDEF FPC}Consts,{$ENDIF}
100a130
> {$IFDEF MSWINDOWS}
106c136
<
---
> {$ENDIF}
164a195,198
> procedure KeyDown(var Key: Word; Shift: TShiftState); override;
>
> constructor CreateNew(AOwner: TComponent; Num: Integer = 0); override;
> public
278c312
< VerifyChecked: BOOL;
---
> VerifyChecked: Boolean;
301c335
< aFooterIcon: TTaskDialogFooterIcon=tfiWarning;
---
> {%H-}aFooterIcon: TTaskDialogFooterIcon=tfiWarning;
303c337
< aNonNative: boolean=false; aEmulateClassicStyle: boolean = false;
---
> {%H-}aNonNative: boolean=false; aEmulateClassicStyle: boolean = false;
424a459,464
> //function for translating the captions
> type
> TTaskDialogTranslate = function(const aString: string): string;
> var
> TaskDialog_Translate: TTaskDialogTranslate;
>
426a467
> {$IFDEF MSWINDOWS}
428c469
<
---
> {$ENDIF}
437,442c478,483
< cbOK: result := @SMsgDlgOK;
< cbYes: result := @SMsgDlgYes;
< cbNo: result := @SMsgDlgNo;
< cbCancel: result := @SMsgDlgCancel;
< cbRetry: result := @SMsgDlgRetry;
< cbClose: result := @SCloseButton;
---
> cbOK: result := {$ifndef fpc}@SMsgDlgOK{$else}@rsMbOK{$endif};
> cbYes: result := {$ifndef fpc}@SMsgDlgYes{$else}@rsMbYes{$endif};
> cbNo: result := {$ifndef fpc}@SMsgDlgNo{$else}@rsMbNo{$endif};
> cbCancel: result := {$ifndef fpc}@SMsgDlgCancel{$else}@rsMbCancel{$endif};
> cbRetry: result := {$ifndef fpc}@SMsgDlgRetry{$else}@rsMbRetry{$endif};
> cbClose: result := {$ifndef fpc}@SCloseButton{$else}@rsMbClose{$endif};
446a488,495
> function TD_Trans(const aString: string): string;
> begin
> if Assigned(TaskDialog_Translate) then
> Result := TaskDialog_Translate(aString)
> else
> Result := aString;
> end;
>
455c504,505
< Caption := LoadResString(TD_BTNS(Btn));
---
> Caption := TD_Trans(LoadResString(TD_BTNS(Btn)));
>
489a540,559
> {$IFDEF FPC}
> function StripHotkey(const Text: string): string;
> var
> I: Integer;
> begin
> Result := Text;
> I := 1;
> while I <= Length(Result) do
> begin
> if Result[i] = cHotkeyPrefix then
> if SysLocale.FarEast and
> ((I > 1) and (Length(Result)-I >= 2) and
> (Result[I-1] = '(') and (Result[I+2] = ')')) then
> Delete(Result, I-1, 4)
> else
> Delete(Result, I, 1);
> Inc(I);
> end;
> end;
> {$ENDIF}
491c561
< result := StripHotkey(s);
---
> Result := StripHotkey(s);
496a567
> {$IFDEF MSWINDOWS}
497a569,591
> {$IFDEF FPC}
> {$EXTERNALSYM IDI_APPLICATION}
> IDI_APPLICATION = MakeIntResource(32512);
> {$EXTERNALSYM IDI_HAND}
> IDI_HAND = MakeIntResource(32513);
> {$EXTERNALSYM IDI_QUESTION}
> IDI_QUESTION = MakeIntResource(32514);
> {$EXTERNALSYM IDI_EXCLAMATION}
> IDI_EXCLAMATION = MakeIntResource(32515);
> {$EXTERNALSYM IDI_ASTERISK}
> IDI_ASTERISK = MakeIntResource(32516);
> {$EXTERNALSYM IDI_WINLOGO}
> IDI_WINLOGO = MakeIntResource(32517);
> {$EXTERNALSYM IDI_SHIELD}
> IDI_SHIELD = MakeIntResource(32518);
> {$EXTERNALSYM IDI_WARNING}
> IDI_WARNING = IDI_EXCLAMATION;
> {$EXTERNALSYM IDI_ERROR}
> IDI_ERROR = IDI_HAND;
> {$EXTERNALSYM IDI_INFORMATION}
> IDI_INFORMATION = IDI_ASTERISK;
> {$ENDIF}
>
505a600
> {$ENDIF MSWINDOWS}
510,513c605,608
< tiWarning: result := SMsgDlgWarning;
< tiQuestion: result := SMsgDlgConfirm;
< tiError: result := SMsgDlgError;
< tiInformation, tiShield: result := SMsgDlgInformation;
---
> tiWarning: result := {$ifndef fpc}SMsgDlgWarning{$else}rsMtWarning{$endif};
> tiQuestion: result := {$ifndef fpc}SMsgDlgConfirm{$else}rsMtConfirmation{$endif};
> tiError: result := {$ifndef fpc}SMsgDlgError{$else}rsMtError{$endif};
> tiInformation, tiShield: result := {$ifndef fpc}SMsgDlgInformation{$else}rsMtInformation{$endif};
515a611
> result := TD_Trans(result);
517a614
> {$IFDEF MSWINDOWS}
526a624
> {$ENDIF}
531a630,638
> function _WS(const aString: string): WideString;
> begin
> {$IFDEF FPC}
> Result := UTF8Decode(aString);
> {$ELSE}
> Result := WS(aString);
> {$ENDIF}
> end;
>
541a649
> {$IFDEF MSWINDOWS}
597a706
> {$ENDIF}
618c727
< result := WideString(CR(tmp));
---
> result := _WS(CR(tmp));
642c751
< result := WS(CR(aText));
---
> result := _WS(CR(aText));
643a753
> {$IFDEF MSWINDOWS}
668c778,784
< var Config: TTASKDIALOGCONFIG;
---
> {$ENDIF}
> var
> {$IFDEF MSWINDOWS}
> Config: TTASKDIALOGCONFIG;
> Pic: TIcon;
> Bmp: TBitmap;
> {$ENDIF}
674,675d789
< Pic: TIcon;
< Bmp: TBitmap;
704c818,819
< R.Bottom := DrawText(result.Canvas.Handle,pointer(Text),-1,R,DT_CALCRECT or DT_WORDBREAK);
---
> DrawText(result.Canvas.Handle,PChar(Text),Length(Text),R,DT_CALCRECT or DT_WORDBREAK);//lazarus does not return box height on OSX (Lazarus bug), the height is stored in the rect in all cases, so we don't need to use the result
>
766c881
< aParent := Application.Handle;
---
> aParent := {$ifndef fpc}Application.Handle{$else}0{$endif};
767a883
> {$ifdef MSWINDOWS}
805a922
> {$endif MSWINDOWS}
817a935,938
> {$IFDEF FPC}
> if FontHeight = 0 then
> FontHeight := Screen.SystemFont.Height;
> {$ENDIF}
840c961
< {$ifdef WITHUXTHEME}
---
> {$ifdef WITHUXTHEME}{$ifndef FPC}
842c963
< {$endif}
---
> {$endif}{$endif}
848a970
> {$IFDEF MSWINDOWS}//TODO: custom icons for other platforms
858c980,982
< end else begin
---
> end else
> {$ENDIF}
> begin
994c1118
< AddButton(LoadResString(TD_BTNS(B)), TD_BTNMOD[b]);
---
> AddButton(TD_Trans(LoadResString(TD_BTNS(B))), TD_BTNMOD[b]);
1015a1140
> {$IFDEF MSWINDOWS}//TODO: custom icons for other platforms
1036a1162,1163
> {$ENDIF}
> begin
1037a1165
> end;
1041a1170,1187
>
> //set form parent
> {$IFDEF WITHPOPUPPARENT}
> if aParent <> 0 then
> for I := 0 to Screen.CustomFormCount-1 do
> if Screen.CustomForms[i].Handle = aParent then
> begin
> Dialog.Form.PopupParent := Screen.CustomForms[i];
> Break;
> end;
> if not Assigned(Dialog.Form.PopupParent) then
> Dialog.Form.PopupParent := Screen.ActiveCustomForm;
> if Assigned(Dialog.Form.PopupParent) then
> begin
> Dialog.Form.PopupMode := pmExplicit;
> end;
> {$ENDIF}
>
1061a1208
> {$IFDEF MSWINDOWS}
1063a1211
> {$ENDIF}
1068c1216,1218
< Dialog.Form.Element[element].Caption := CR(Text) else
---
> Dialog.Form.Element[element].Caption := CR(Text)
> {$IFDEF MSWINDOWS}
> else
1070c1220,1221
< {$ifdef UNICODE}NativeInt{$else}integer{$endif}(pointer(WS(Text))));
---
> {$ifdef WITHNATIVEINT}NativeInt{$else}integer{$endif}(pointer(_WS(Text))))
> {$ENDIF};
1082a1234,1240
> constructor TEmulatedTaskDialog.CreateNew(AOwner: TComponent; Num: Integer);
> begin
> inherited CreateNew(AOwner, Num);
>
> KeyPreview := True;
> end;
>
1094a1253,1260
> procedure TEmulatedTaskDialog.KeyDown(var Key: Word; Shift: TShiftState);
> begin
> if (Key = VK_F4) and (ssAlt in Shift) then//IMPORTANT: native task dialog blocks Alt+F4 to close the dialog -> we have to block it as well
> Key := 0;
>
> inherited KeyDown(Key, Shift);
> end;
>
1115a1282,1284
> {$IFDEF FPC}
> DefaultFont.Size := 11;//Lazarus seems not to like the height property -> we must use Size
> {$ELSE}
1116a1286
> {$ENDIF}
1121a1292,1294
> {$IFDEF FPC}
> DefaultFont.Size := 10;//Lazarus seems not to like the height property -> we must use Size
> {$ELSE}
1122a1296
> {$ENDIF}
1124a1299
> {$IFDEF MSWINDOWS}
1125a1301
> {$ENDIF}
1128a1305
> {$IFDEF MSWINDOWS}//TODO: lazarus resource
1129a1307
> {$ENDIF}
1131a1310
> {$IFDEF MSWINDOWS}//TODO: lazarus resource
1132a1312
> {$ENDIF}
Thanks Arnauld, I'll try to add this functionality by myself and if I succeed, I'll send you the modifications back!
Thanks a lot for your answers, Arnauld!
I read all the information around TSQLRecordMany, but I must be dumb
I use this code:
xUserApp := TSQLUserApplication.Create;
xUserApp.FillMany(
DBConnection,
UserId,
MyWhere);
while xUserApp.FillOne do
begin
xUserApp.Dest.DoSomething;
end;
The xUserApp.Dest contains the Integer value of ID -> how do I get the Dest object quickly and easily?
Thank you very much for your fast answer!
You are right, when I try to get a list of users with a linked application, it works fine:
Result := TSQLUser.CreateAndFillPrepareMany(
fDBConnection,
PUTF8Char('(%.%=?)'),
[TSQLUserApplication.SQLTableName, TSQLUserApplication.Consts.DestApplication],
[fApplicationId]
);
But I need it the other way, too.
+ I don't need mORMot to handle it automatically, if it's too much work. It would be OK for me if I could add a custom table JOIN definition to CreateAndFillPrepare (like you have e.g. the custom WHERE clause).
(+ Regarding PUTF8Char: I am probably not using the latest version of mORMot. I'll update ASAP.)
The requested information:
fRecordManySourceProp.ObjectClass.ClassName = TSQLUser
fRecordManyDestProp.ObjectClass.ClassName = TSQLApplication
That means that fRecordManySourceProp.ObjectClass.ClassName=PClass(self)^ is false (self is TSQLApplication).
Hello Arnaud,
I am having trouble with many-to-many relation (TSQLUser = Source, TSQLApplication = Dest):
TSQLUser = class;
TSQLApplication = class;
TSQLUserApplication = class;
TSQLUser = class(TSQLRecord)
private
fFirstName: DBString;
fLastName: DBString;
fApplicationList: TSQLUserApplication;
public type
Consts = class(Cols)
public const
PluralLC = 'users';
SingularLC = 'user';
FirstName = 'FirstName';
LastName = 'LastName';
end;
published
property FirstName: DBString read fFirstName write fFirstName;
property LastName: DBString read fLastName write fLastName;
property ApplicationList: TSQLUserApplication read fApplicationList;
end;
TSQLApplication = class(TSQLRecord)
private
fCompanyName: DBString;
fUserList: TSQLUserApplication;
public type
Consts = class(Cols)
public const
PluralLC = 'applications';
SingularLC = 'application';
CompanyName = 'CompanyName';
end;
published
property CompanyName: DBString read fCompanyName write fCompanyName;
property UserList: TSQLUserApplication read fUserList;
end;
TSQLUserApplication = class(TSQLRecordMany)
public type
Consts = class(Cols)
public const
SourceUser = 'Source';
DestApplication = 'Dest';
end;
private
fSource: TSQLUser;
fDest: TSQLApplication;
published
property Source: TSQLUser read fSource write fSource;
property Dest: TSQLApplication read fDest write fDest;
end;
I want to get a list of applications linked with a user. I do it by this code:
Result := TSQLApplication.CreateAndFillPrepareMany(
fDBConnection,
PUTF8Char('(%.%=?)'),
[TSQLUserApplication.SQLTableName, TSQLUserApplication.Consts.SourceUser],
[myUserId]
);
However, I am getting an assertion failure in TSQLRecord.FillPrepareMany on this line:
Assert((fRecordManySourceProp.ObjectClass=PClass(self)^)
and (fRecordManyDestProp.ObjectClass<>nil));
Could you please tell me what I am doing wrong and point me to the correct direction?
BTW, I'll need also to get a list of users linked with an application.
I'd have another suggestion according to SynTaskDialog. Your solution is not very good if task dialog is shown in modal windows - it can be hidden behind the modal window, unless you use aParent and run Win7. I'd change the aParent parameter in Execute() to TControl (aParent: TControl=nil;) and then change this code
if aParent=0 then
aParent := Application.Handle;
to
var xParentForm: TCustomForm;
begin
[ ... ]
if not Assigned(aParent) then
xParentForm := Screen.ActiveCustomForm
else if (aParent is TCustomForm) then
xParentForm := TCustomForm(aParent)
else
xParentForm := GetParentForm(aParent);
I use TControl because then aParent can easily be used as Self even from TFrame or custom components etc. GetParentForm() does the job automatically.
Then you change
Config.hwndParent := aParent;
to
Config.hwndParent := xParentForm.Handle;
and
result := Form.ShowModal;
to
Form.PopupParent := xParentForm;
Form.PopupMode := pmExplicit;
result := Form.ShowModal;
These changes become obvious if you run this code - your task dialog will hide behind a modal window and you can click the button even twice:
procedure TForm1.ShowTaskDialog(Sender: TObject);
var Task: SynTaskDialog.TTaskDialog;
begin
Task.Inst := 'Saving application settings';
Task.Content := 'This is the content';
Task.Radios := 'Store settings in registry'#10'Store settings in XML file';
Task.Verify := 'Do no ask for this setting next time';
Task.VerifyChecked := true;
Task.Footer := 'XML file is perhaps a better choice';
Task.Execute([],0,[],tiQuestion,tfiInformation,200);
end;
procedure TForm1.BtnShowParentModalClick(Sender: TObject);
var xMF: TForm;
xBtn: TButton;
begin
xMF := TForm.CreateNew(TControl(Sender));
try
xMF.Left := Self.Left;
xMF.Top := Self.Top;
xMF.Width := 1000;
xMF.Height := 500;
//xMF.WindowState := wsMaximized;
xBtn := TButton.Create(xMF);
xBtn.Parent := xMF;
xBtn.Caption := 'Show Task Dialog';
xBtn.Width := 200;
xBtn.OnClick := ShowTaskDialog;
xMF.PopupParent := Self;
xMF.PopupMode := pmExplicit;
xMF.ShowModal;
finally
xMF.Free;
end;
end;
Some parts of code (PopupParent := ...; PopupMode := ...;) may have to be excluded with conditions for older delphi versions.
Of course you can use the old aParent parameter, but if the dialog must be emulated, setting the PopupParent and PopupMode is very useful. The TControl parameter is also much more comfortable in my opinion.
+ btw I started using your logging system. It's great code and great help for me! Thanks!
Hello!
I decided to use your SynTaskDialog in my projects now and I faced the problem of unicode support lack for non-unicode Delphi versions. I updated your code - tested under D2007 and XE2. Now nice unicode dialogs can be shown in D2007 and WinXP.
I use TntUnicodeControls for D2007 and emulation on XP. It can be switched off with the USETNTPACK directive (then, of course, D2007 unicode support for emulated dialog is lost, but the Win7 dialog under D2007 is still shown fine).
Honestly, I nearly haven't changed your code, only the type definitions and voila, everything works fine Good job from you!
Best
SynTaskDialog.pas:
/// implement TaskDialog window (native on Vista/Seven, emulated on XP)
// - this unit is a part of the freeware Synopse framework,
// licensed under a MPL/GPL/LGPL tri-license; version 1.16
unit SynTaskDialog;
{
This file is part of Synopse framework.
Synopse framework. Copyright (C) 2012 Arnaud Bouchez
Synopse Informatique - http://synopse.info
*** BEGIN LICENSE BLOCK *****
Version: MPL 1.1/GPL 2.0/LGPL 2.1
The contents of this file are subject to the Mozilla Public License Version
1.1 (the "License"); you may not use this file except in compliance with
the License. You may obtain a copy of the License at
http://www.mozilla.org/MPL
Software distributed under the License is distributed on an "AS IS" basis,
WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
for the specific language governing rights and limitations under the License.
The Original Code is Synopse framework.
The Initial Developer of the Original Code is Arnaud Bouchez.
Portions created by the Initial Developer are Copyright (C) 2012
the Initial Developer. All Rights Reserved.
Contributor(s):
Alternatively, the contents of this file may be used under the terms of
either the GNU General Public License Version 2 or later (the "GPL"), or
the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
in which case the provisions of the GPL or the LGPL are applicable instead
of those above. If you wish to allow use of your version of this file only
under the terms of either the GPL or the LGPL, and not to allow others to
use your version of this file under the terms of the MPL, indicate your
decision by deleting the provisions above and replace them with the notice
and other provisions required by the GPL or the LGPL. If you do not delete
the provisions above, a recipient may use your version of this file under
the terms of any one of the MPL, the GPL or the LGPL.
***** END LICENSE BLOCK *****
Version 1.13
- initial release
Version 1.15
- new tdfQueryMasked function to display * in the tdfQuery editor field
Version 1.16
- fixed issue when changing the current application with Alt+Tab - see
http://synopse.info/fossil/tktview?name=01395e5932
- fixed compiler error when using the unit with runtime packages enabled
(known compiler issue about string resources, referenced as E2201)
- some aesthetical rendering changes and code clean-up (e.g. no temporary
form necessary), thanks to uligerhardt proposals
Ondrej 2012-02-26
- added WideString Support even for Delphi<=D2007 + Win 7
- added TntControls for true unicode support even for Delphi<=D2007 + Win XP
}
interface
{$IFDEF CONDITIONALEXPRESSIONS} // Delphi 6 or newer
{$ifndef VER140} // Delphi 6
{$define WITHUXTHEME} // Themes unit exists till Delphi 7
{$endif}
{$ENDIF}
{$DEFINE USETNTPACK}//TRUE UNICODE SUPPORT FOR <Delphi2007 and WinXP
uses
Windows, Classes, SysUtils, Consts, SynWideSupp,
{$ifdef USETMSPACK}
AdvGlowButton, AdvMenus, TaskDialog, TaskDialogEx,
{$else}
{$ifdef USETNTPACK}
TntClasses, TntMenus, TntStdCtrls, TntButtons, TntExtCtrls, TntForms,
{$else}
Menus,
{$endif USETNTPACK}
{$endif USETMSPACK}
{$ifndef UNICODE}WideStrUtils, {$endif}
Graphics, Forms, Controls, StdCtrls, ExtCtrls, Buttons;
var
/// will map a generic OK picture from SynTaskDialog.res
BitmapOK: TBitmap;
/// will map a generic Arrow picture from SynTaskDialog.res
BitmapArrow: TBitmap;
/// will map a default font, according to the available
// - if Calibri is installed, will use it
// - will fall back to Tahoma otherwise
DefaultFont: TFont;
{$ifndef USETMSPACK}
/// is filled once in the initialization block below
// - you can set this reference to nil to force Delphi dialogs even
// on Vista/Seven (e.g. make sense if TaskDialogBiggerButtons=true)
TaskDialogIndirect: function(AConfig: pointer; Res: PInteger;
ResRadio: PInteger; VerifyFlag: PBOOL): HRESULT; stdcall;
type
/// the standard kind of common buttons handled by the Task Dialog
TCommonButton = (
cbOK, cbYes, cbNo, cbCancel, cbRetry, cbClose);
/// set of standard kind of common buttons handled by the Task Dialog
TCommonButtons = set of TCommonButton;
/// the available main icons for the Task Dialog
TTaskDialogIcon = (
tiBlank, tiWarning, tiQuestion, tiError, tiInformation, tiNotUsed, tiShield);
/// the available footer icons for the Task Dialog
TTaskDialogFooterIcon = (
tfiBlank, tfiWarning, tfiQuestion, tfiError, tfiInformation, tfiShield);
/// the available configuration flags for the Task Dialog
// - most are standard TDF_* flags used for Vista/Seven native API
// (see http://msdn.microsoft.com/en-us/library/bb787473(v=vs.85).aspx
// for TASKDIALOG_FLAGS)
// - tdfQuery and tdfQueryMasked are custom flags, implemented in pure Delphi
// code to handle input query
// - our emulation code will handle only tdfUseCommandLinks,
// tdfUseCommandLinksNoIcon, and tdfQuery options
TTaskDialogFlag = (
tdfEnableHyperLinks, tdfUseHIconMain, tdfUseHIconFooter,
tdfAllowDialogCancellation, tdfUseCommandLinks, tdfUseCommandLinksNoIcon,
tdfExpandFooterArea, tdfExpandByDefault, tdfVerificationFlagChecked,
tdfShowProgressBar, tdfShowMarqueeProgressBar, tdfCallbackTimer,
tdfPositionRelativeToWindow, tdfRtlLayout, tdfNoDefaultRadioButton,
tdfCanBeMinimized, tdfQuery, tdfQueryMasked);
/// set of available configuration flags for the Task Dialog
TTaskDialogFlags = set of TTaskDialogFlag;
/// implements a TaskDialog
// - will use the new TaskDialog API under Vista/Seven, and emulate it with
// pure Delphi code and standard themed VCL components under XP or 2K
// - create a TTaskDialog object/record on the stack will initialize all
// its string parameters to '' (it's a SHAME that since Delphi 2009, objects
// are not initialized any more: we have to define this type as object before
// Delphi 2009, and as record starting with Delphi 2009)
// - set the appropriate string parameters, then call Execute() with all
// additional parameters
// - RadioRes/SelectionRes/VerifyChecked will be used to reflect the state
// after dialog execution
// - here is a typical usage:
// !var Task: TTaskDialog;
// !begin
// ! Task.Inst := 'Saving application settings';
// ! Task.Content := 'This is the content';
// ! Task.Radios := 'Store settings in registry'#10'Store settings in XML file';
// ! Task.Verify := 'Do no ask for this setting next time';
// ! Task.VerifyChecked := true;
// ! Task.Footer := 'XML file is perhaps a better choice';
// ! Task.Execute([],0,[],tiQuestion,tfiInformation,200);
// ! ShowMessage(IntToStr(Task.RadioRes)); // 200=Registry, 201=XML
// ! if Task.VerifyChecked then
// ! ShowMessage(Task.Verify);
// !end;
TTaskDialog = {$ifdef UNICODE}record{$else}object{$endif}
/// the main title of the dialog window
// - if left void, the title of the application main form is used
Title: SynWideString;
/// the main instruction (first line on top of window)
// - any '\n' will be converted into a line feed
// - if left void, the text is taken from the current dialog icon kind
Inst: SynWideString;
/// the dialog's primary content content text
// - any '\n' will be converted into a line feed
Content: SynWideString;
/// a #13#10 or #10 separated list of custom buttons
// - they will be identified with an ID number starting at 100
// - by default, the buttons will be created at the dialog bottom, just
// like the common buttons
// - if tdfUseCommandLinks flag is set, the custom buttons will be created
// as big button in the middle of the dialog window; in this case, any
// '\n' will be converted as note text (shown with smaller text under native
// Vista/Seven TaskDialog, or as popup hint within Delphi emulation)
Buttons: SynWideString;
/// a #13#10 or #10 separated list of custom radio buttons
// - they will be identified with an ID number starting at 200
// - aRadioDef parameter can be set to define the default selected value
// - '\n' will be converted as note text (shown with smaller text under
// native Vista/Seven TaskDialog, or as popup hint within Delphi emulation)
Radios: SynWideString;
/// the expanded information content text
// - any '\n' will be converted into a line feed
// - the Delphi emulation will always show the Info content (there is no
// collapse/expand button)
Info: SynWideString;
/// the button caption to be displayed when the information is collapsed
// - not used under XP: the Delphi emulation will always show the Info content
InfoExpanded: SynWideString;
/// the button caption to be displayed when the information is expanded
// - not used under XP: the Delphi emulation will always show the Info content
InfoCollapse: SynWideString;
/// the footer content text
// - any '\n' will be converted into a line feed
Footer: SynWideString;
/// the text of the bottom most optional checkbox
Verify: SynWideString;
/// a #13#10 or #10 separated list of items to be selected
// - if set, a Combo Box will be displayed to select
// - if tdfQuery is in the flags, the combo box will be in edition mode,
// and the user will be able to edit the Query text or fill the field
// with one item of the selection
// - this selection is not handled via the Vista/Seven TaskDialog, but
// with our Delphi emulation code (via a TComboBox)
Selection: SynWideString;
/// some text to be edited
// - if tdfQuery is in the flags, will contain the default query text
// - if Selection is set, the
Query: SynWideString;
/// the selected radio item
// - first is numeroted 0
RadioRes: integer;
/// after execution, contains the selected item from the Selection list
SelectionRes: integer;
/// reflect the the bottom most optional checkbox state
// - if Verify is not '', should be set before execution
// - after execution, will contain the final checkbox state
VerifyChecked: BOOL;
/// launch the TaskDialog form
// - some common buttons can be set via aCommonButtons
// - in emulation mode, aFlags will handle only tdfUseCommandLinks,
// tdfUseCommandLinksNoIcon, and tdfQuery options
// - will return 0 on error, or the Button ID (e.g. mrOk for the OK button
// or 100 for the first custom button defined in Buttons string)
// - if Buttons was defined, aButtonDef can set the selected Button ID
// - if Radios was defined, aRadioDef can set the selected Radio ID
// - aDialogIcon and aFooterIcon are used to specify the displayed icons
// - aWidth can be used to force a custom form width (in pixels)
// - aParent can be set to any HWND - by default, Application.DialogHandle
// - if aNonNative is TRUE, the Delphi emulation code will always be used
// - aEmulateClassicStyle can be set to enforce conformity with the non themed
// user interface - see @http://synopse.info/forum/viewtopic.php?pid=2867#p2867
function Execute(aCommonButtons: TCommonButtons=[];
aButtonDef: integer=0; aFlags: TTaskDialogFlags=[];
aDialogIcon: TTaskDialogIcon=tiInformation;
aFooterIcon: TTaskDialogFooterIcon=tfiWarning;
aRadioDef: integer=0; aWidth: integer=0; aParent: HWND=0;
aNonNative: boolean=false; aEmulateClassicStyle: boolean = false): integer;
end;
/// a wrapper around the TTaskDialog.Execute method
// - used to provide a "flat" access to task dialog parameters
TTaskDialogEx = {$ifdef UNICODE}record{$else}object{$endif}
/// the associated main TTaskDialog instance
Base: TTaskDialog;
/// some common buttons to be displayed
CommonButtons: TCommonButtons;
/// the default button ID
ButtonDef: integer;
/// the associated configuration flags for this Task Dialog
// - in emulation mode, aFlags will handle only tdfUseCommandLinks,
// tdfUseCommandLinksNoIcon, and tdfQuery options
Flags: TTaskDialogFlags;
/// used to specify the dialog icon
DialogIcon: TTaskDialogIcon;
/// used to specify the footer icon
FooterIcon: TTaskDialogFooterIcon;
/// the default radio button ID
RadioDef: integer;
/// can be used to force a custom form width (in pixels)
Width: integer;
/// if TRUE, the Delphi emulation code will always be used
NonNative: boolean;
/// can be used to enforce conformity with the non themed user interface
EmulateClassicStyle: boolean;
/// main (and unique) method showing the dialog itself
// - is in fact a wrapper around the TTaskDialog.Execute method
function Execute(aParent: HWND=0): integer;
end;
{$endif USETMSPACK}
type
{$ifdef USETMSPACK}
/// a TMS PopupMenu
TSynPopupMenu = TAdvPopupMenu;
TSynButtonParent = TAdvGlowButton;
{$else}
{$ifdef USETNTPACK}
TSynPopupMenu = TTntPopupMenu;
TSynButtonParent = {$ifdef WITHUXTHEME}TTntBitBtn{$else}TTntButton{$endif};
{$else}
/// a generic VCL popup menu
TSynPopupMenu = TPopupMenu;
TSynButtonParent = {$ifdef WITHUXTHEME}TBitBtn{$else}TButton{$endif};
{$endif USETNTPACK}
{$endif USETMSPACK}
{$ifdef USETNTPACK}
TSynEdit = TTntEdit;
TSynPanel = TTntPanel;
TSynCheckBox = TTntCheckBox;
TSynComboBox = TTntComboBox;
TSynRadioButton = TTntRadioButton;
TSynLabel = TTntLabel;
TSynForm = TTntForm;
TSynWideStrings = TTntStrings;
TSynWideStringList = TTntStringList;
{$else}
TSynEdit = TEdit;
TSynPanel = TPanel;
TSynCheckBox = TCheckBox;
TSynComboBox = TComboBox;
TSynRadioButton = TRadioButton;
TSynLabel = TLabel;
TSynForm = TForm;
TSynWideStrings = TStrings;
TSynWideStringList = TStringList;
{$endif USETNTPACK}
/// a generic Button to be used in the User Interface
// - is always a Themed button: under Delphi 6, since TBitBtn is not themed,
// it will be a row TButton with no glyph... never mind...
TSynButton = class(TSynButtonParent)
protected
{$ifndef USETMSPACK}
fDropDownMenu: TSynPopupMenu;
{$endif}
public
/// create a standard button instance
// - ModalResult/Default/Cancel properties will be set as exepcted for this
// kind of button
constructor CreateKind(Owner: TWinControl; Btn: TCommonButton;
Left, Right, Width, Height: integer);
/// set the glyph of the button
// - set nothing under Delphi 6
procedure SetBitmap(Bmp: TBitmap);
{$ifndef USETMSPACK}
/// drop down the associated Popup Menu
procedure DoDropDown;
/// the associated Popup Menu to drop down
property DropDownMenu: TSynPopupMenu read fDropDownMenu write fDropDownMenu;
{$endif}
end;
/// return the text without the '&' characters within
function UnAmp(const s: SynWideString): SynWideString;
{$ifndef USETMSPACK}
var
{
/// if set to TRUE, buttons will be bigger than default
// - can be useful e.g. for touch screens
// - will work only for the Delphi emulated version (aNonNative=true) of
// TSynTask - could be combined with @TaskDialogIndirect := nil;
TaskDialogBiggerButtons: boolean = false;
}
/// a default Task Dialog wrapper instance
// - can be used to display some information with less parameters
DefaultTaskDialog: TTaskDialogEx = (
(*
CommonButtons: [];
ButtonDef: 0;
Flags: [];
*)
DialogIcon: tiInformation;
FooterIcon: tfiWarning;
(*
RadioDef: 0;
Width: 0;
NonNative: false;
EmulateClassicStyle: false;
*)
);
{$endif}
implementation
{$R SynTaskDialog.res}
const
TD_BTNS: array[TCommonButton] of string = (
SMsgDlgOK, SMsgDlgYes, SMsgDlgNo, SMsgDlgCancel, SMsgDlgRetry,
SCloseButton);
TD_BTNMOD: array[TCommonButton] of Integer = (
mrOk, mrYes, mrNo, mrCancel, mrRetry, mrAbort);
{ TSynButton }
constructor TSynButton.CreateKind(Owner: TWinControl; Btn: TCommonButton;
Left, Right, Width, Height: integer);
begin
Create(Owner);
Parent := Owner;
SetBounds(Left,Right,Width,Height);
Caption := TD_BTNS[Btn];
ModalResult := TD_BTNMOD[Btn];
case Btn of
cbOK: Default := true;
cbCancel: Cancel := true;
end;
case Btn of
cbOK: SetBitmap(BitmapOK);
end;
end;
{$ifndef USETMSPACK}
procedure TSynButton.DoDropDown;
begin
if DropDownMenu<>nil then
with ClientToScreen(BoundsRect.TopLeft) do
DropDownMenu.Popup(X,Y+Height);
end;
{$endif}
procedure TSynButton.SetBitmap(Bmp: TBitmap);
begin
if Bmp<>nil then
{$ifdef USETMSPACK}
Picture.Assign(Bmp);
{$else}
{$ifdef WITHUXTHEME}
Glyph := Bmp;
{$else}
// Delphi 6 TBitBtn has no theming -> use generic TButton without glyph
{$endif}
{$endif}
end;
function UnAmp(const s: SynWideString): SynWideString;
var i: integer;
begin
result := s;
repeat
i := pos('&',result);
if i=0 then
exit;
delete(result,i,1);
until false;
end;
{$ifndef USETMSPACK}
const
TD_ICONS: array[TTaskDialogIcon] of integer = (
17, 84, 99, 98, 81, 0, 78);
TD_ICONS_IDENT: array[TTaskDialogIcon] of string = (
'', SMsgDlgWarning, SMsgDlgConfirm, SMsgDlgError, SMsgDlgInformation,
'', SMsgDlgInformation);
TD_FOOTERICONS: array[TTaskDialogFooterIcon] of integer = (
17, 84, 99, 98, 65533, 65532);
WIN_ICONS: array[TTaskDialogIcon] of PChar = (
nil, IDI_WARNING, IDI_QUESTION, IDI_ERROR, IDI_INFORMATION, nil, IDI_WINLOGO);
WIN_FOOTERICONS: array[TTaskDialogFooterIcon] of PChar = (
nil, IDI_WARNING, IDI_QUESTION, IDI_ERROR, IDI_INFORMATION, IDI_WINLOGO);
procedure InitComCtl6;
var OSVersionInfo: TOSVersionInfo;
begin
OSVersionInfo.dwOSVersionInfoSize := sizeof(OSVersionInfo);
GetVersionEx(OSVersionInfo);
if OSVersionInfo.dwMajorVersion<6 then
@TaskDialogIndirect := nil else
@TaskDialogIndirect := GetProcAddress(GetModuleHandle(comctl32),'TaskDialogIndirect');
end;
{ TTaskDialog }
type
// see http://msdn.microsoft.com/en-us/library/bb787473(v=VS.85).aspx
PTASKDIALOG_BUTTON = ^TTASKDIALOG_BUTTON;
TTASKDIALOG_BUTTON = packed record
nButtonID: integer;
pszButtonText: PWideChar;
end;
TTASKDIALOGCONFIG = packed record
cbSize: integer;
hwndParent: HWND;
hInstance: THandle;
dwFlags: integer;
dwCommonButtons: integer;
pszWindowTitle: PWideChar;
hMainIcon: integer;
pszMainInstruction: PWideChar;
pszContent: PWideChar;
cButtons: integer;
pButtons: PTASKDIALOG_BUTTON;
nDefaultButton: integer;
cRadioButtons: integer;
pRadioButtons: PTASKDIALOG_BUTTON;
nDefaultRadioButton: integer;
pszVerificationText: PWideChar;
pszExpandedInformation: PWideChar;
pszExpandedControlText: PWideChar;
pszCollapsedControlText: PWideChar;
hFooterIcon: HICON;
pszFooter: PWideChar;
pfCallback: pointer;
lpCallbackData: pointer;
cxWidth: integer;
end;
function TTaskDialog.Execute(aCommonButtons: TCommonButtons;
aButtonDef: integer; aFlags: TTaskDialogFlags;
aDialogIcon: TTaskDialogIcon; aFooterIcon: TTaskDialogFooterIcon;
aRadioDef, aWidth: integer; aParent: HWND; aNonNative: boolean;
aEmulateClassicStyle: boolean): integer;
function CR(const aText: SynWideString): SynWideString;
begin
if pos('\n',aText)=0 then
result := aText else
result := SynWideStringReplace(aText,'\n',#10,[rfReplaceAll]);
end;
function GetNextStringLineToWS(var P: PSynWideChar): SynWideString;
var S: PSynWideChar;
begin
if P=nil then
result := '' else begin
S := P;
while S[0]>=' ' do
inc(S);
SetString(result,P,S-P);
result := CR(result);
while (S^<>#0) and (S^<' ') do inc(S); // ignore e.g. #13 or #10
if S^<>#0 then
P := S else
P := nil;
end;
end;
var aHint: SynWideString;
function NoCR(const aText: SynWideString): SynWideString;
var i: integer;
begin
result := aText;
aHint := '';
i := pos('\n',result);
if i>0 then begin
aHint := CR(copy(result,i+2,maxInt));
SetLength(result,i-1);
end;
end;
function N(const aText: SynWideString): SynWideString;
begin
if aText='' then
result := '' else
result := CR(aText);
end;
var RU: array of SynWideString;
RUCount: integer;
But: array of TTASKDIALOG_BUTTON;
procedure AddRU(Text: SynWideString; var n: integer; firstID: integer);
var P: PSynWideChar;
begin
if Text='' then
exit;
Text := SysUtils.trim(Text);
P := @Text[1]; // '\n' handling in GetNextStringLineToWS(P) will change P^
while P<>nil do begin
if length(RU)<=RUCount then begin
SetLength(RU,RUCount+16);
SetLength(But,RUCount+16);
end;
RU[RUCount] := GetNextStringLineToWS(P);
with But[RUCount] do begin
nButtonID := n+firstID;
pszButtonText := pointer(RU[RUCount]);
end;
inc(n);
inc(RUCount);
end;
end;
var Config: TTASKDIALOGCONFIG;
i, X, Y, XB, IconBorder, FontHeight: integer;
Par: TWinControl;
Panel: TSynPanel;
CurrTabOrder: TTabOrder;
Form: TSynForm;
Image: TImage;
Pic: TIcon;
Bmp: TBitmap;
Edit: TSynEdit;
Combo: TSynComboBox;
List: TSynWideStrings;
B: TCommonButton;
CommandLink: TSynButton;
Rad: array of TSynRadioButton;
Verif: TSynCheckBox;
function AddLabel(const Text: SynWideString; BigFont: boolean): TSynLabel;
begin
result := TSynLabel.Create(Form);
result.Parent := Par;
result.WordWrap := true;
if BigFont then begin
if aEmulateClassicStyle then begin
result.Font.Height := FontHeight-2;
result.Font.Style := [fsBold]
end else begin
result.Font.Height := FontHeight-4;
result.Font.Color := $B00000;
end;
end else
result.Font.Height := FontHeight;
result.Left := X;
result.Top := Y;
result.Width := aWidth-X-8;
result.Caption := CR(Text);
inc(Y,result.Height+16);
end;
procedure AddBevel;
var BX: integer;
begin
with TBevel.Create(Form) do begin
Parent := Par;
if (Image<>nil) and (Y<Image.Top+Image.Height) then
BX := X else
BX := 2;
SetBounds(BX,Y,aWidth-BX-2,2);
end;
inc(Y,16);
end;
function AddButton(s: SynWideString; ModalResult: integer): TSynButton;
var WB: integer;
begin
s := UnAmp(s);
WB := Form.Canvas.TextWidth(s)+52;
dec(XB,WB);
if XB<X shr 1 then begin
XB := aWidth-WB;
inc(Y,32);
end;
result := TSynButton.Create(Form);
result.Parent := Par;
if aEmulateClassicStyle then
result.SetBounds(XB,Y,WB-10,22) else
result.SetBounds(XB,Y,WB-12,28);
result.Caption := s;
result.ModalResult := ModalResult;
result.TabOrder := CurrTabOrder;
case ModalResult of
mrOk: begin
result.Default := true;
if aCommonButtons=[cbOk] then
result.Cancel := true;
end;
mrCancel: result.Cancel := true;
end;
if ModalResult=aButtonDef then
Form.ActiveControl := result;
end;
begin
if (byte(aCommonButtons)=0) and (Buttons='') then begin
aCommonButtons := [cbOk];
if aButtonDef=0 then
aButtonDef := mrOk;
end;
if Title='' then
if Application.MainForm=nil then
Title := Application.Title else
Title := Application.MainForm.Caption;
if (Inst='') and (TD_ICONS_IDENT[aDialogIcon]<>'') then
Inst := TD_ICONS_IDENT[aDialogIcon];
if aParent=0 then
aParent := Application.Handle;
if Assigned(TaskDialogIndirect) and not aNonNative and
not (tdfQuery in aFlags) and (Selection='') then begin
// use Vista/Seven TaskDialog implementation (not tdfQuery nor Selection)
FillChar(Config,sizeof(Config),0);
Config.cbSize := sizeof(Config);
Config.hwndParent := aParent;
Config.pszWindowTitle := pointer(N(Title));
Config.pszMainInstruction := pointer(N(Inst));
Config.pszContent := pointer(N(Content));
RUCount := 0;
AddRU(Buttons,Config.cButtons,100);
AddRU(Radios,Config.cRadioButtons,200);
if Config.cButtons>0 then
Config.pButtons := @But[0];
if Config.cRadioButtons>0 then
Config.pRadioButtons := @But[Config.cButtons];
Config.pszVerificationText := pointer(N(Verify));
Config.pszExpandedInformation := pointer(N(Info));
Config.pszExpandedControlText := pointer(N(InfoExpanded));
Config.pszCollapsedControlText := pointer(N(InfoCollapse));
Config.pszFooter := pointer(N(Footer));
Config.dwCommonButtons := byte(aCommonButtons);
if (Verify<>'') and VerifyChecked then
include(aFlags,tdfVerificationFlagChecked);
if (Config.cButtons=0) and (aCommonButtons=[cbOk]) then
Include(aFlags,tdfAllowDialogCancellation); // just OK -> Esc/Alt+F4 close
Config.dwFlags := integer(aFlags);
Config.hMainIcon := TD_ICONS[aDialogIcon];
Config.hFooterIcon := TD_FOOTERICONS[aFooterIcon];
Config.nDefaultButton := aButtonDef;
Config.nDefaultRadioButton := aRadioDef;
Config.cxWidth := aWidth;
if TaskDialogIndirect(@Config,@result,@RadioRes,@VerifyChecked)<>S_OK then
result := 0; // error (mostly invalid argument)
end else begin
// use our native (naive?) Delphi implementation
Verif := nil;
Combo := nil;
Edit := nil;
Form := TSynForm.Create(Application);
try
// initialize form properties
Form.BorderStyle := bsDialog;
Form.BorderIcons := [];
Form.Position := poScreenCenter;
if not aEmulateClassicStyle then
Form.Font := DefaultFont;
FontHeight := Form.Font.Height;
if aWidth=0 then begin
aWidth := Form.Canvas.TextWidth(Inst);
if (aWidth>300) or (Form.Canvas.TextWidth(Content)>300) or
(length(Buttons)>40) then
aWidth := 480 else
aWidth := 420;
end;
Form.ClientWidth := aWidth;
Form.Height := 200;
Form.Caption := Title;
// create a white panel for the main dialog part
Panel := TSynPanel.Create(Form);
Panel.Parent := Form;
Panel.Align := alTop;
Panel.BorderStyle := bsNone;
Panel.BevelOuter := bvNone;
if not aEmulateClassicStyle then begin
{$ifdef HASINLINE}
Panel.BevelEdges := [beBottom];
Panel.BevelKind := bkFlat;
{$endif}
Panel.Color := clWhite;
{$ifdef WITHUXTHEME}
Panel.ParentBackground := false; // clWhite not used otherwise
{$endif}
end;
Par := Panel;
// handle main dialog icon
if aEmulateClassicStyle then
IconBorder := 10 else
IconBorder := 24;
if WIN_ICONS[aDialogIcon]<>nil then begin
Image := TImage.Create(Form);
Image.Parent := Par;
Image.Picture.Icon.Handle := LoadIcon(0,WIN_ICONS[aDialogIcon]);
Image.SetBounds(IconBorder,IconBorder,Image.Picture.Icon.Width,Image.Picture.Icon.Height);
X := Image.Width+IconBorder*2;
Y := Image.Top;
if aEmulateClassicStyle then
inc(Y, 8);
end else begin
Image := nil;
if not aEmulateClassicStyle then
IconBorder := IconBorder*2;
X := IconBorder;
Y := IconBorder;
end;
// add main texts (Instruction, Content, Information)
AddLabel(Inst,true);
AddLabel(Content,false);
if Info<>'' then
// no information collapse/expand yet: it's always expanded
AddLabel(Info,false);
// add command links buttons
if (tdfUseCommandLinks in aFlags) and (Buttons<>'') then
with TSynWideStringList.Create do
try
inc(Y,8);
Text := SysUtils.trim(Buttons);
for i := 0 to Count-1 do begin
CommandLink := TSynButton.Create(Form);
with CommandLink do begin
Parent := Par;
Font.Height := FontHeight-3;
if aEmulateClassicStyle then
SetBounds(X,Y,aWidth-10-X,40) else
SetBounds(X,Y,aWidth-16-X,40);
Caption := NoCR(Strings[i]);
if aHint<>'' then begin
ShowHint := true;
Hint := aHint; // note shown as Hint
end;
inc(Y,Height+2);
ModalResult := i+100;
if ModalResult=aButtonDef then
Form.ActiveControl := CommandLink;
if aEmulateClassicStyle then begin
Font.Height := FontHeight - 2;
Font.Style := [fsBold]
end;
{$ifdef WITHUXTHEME}
if aEmulateClassicStyle then begin
Margin := 7;
Spacing := 7;
end else begin
Margin := 24;
Spacing := 10;
end;
if not (tdfUseCommandLinksNoIcon in aFlags) then
SetBitmap(BitmapArrow);
{$endif}
end;
end;
inc(Y,24);
finally
Free;
end;
// add radio buttons
if Radios<>'' then
with TSynWideStringList.Create do
try
Text := SysUtils.trim(Radios);
SetLength(Rad,Count);
for i := 0 to Count-1 do begin
Rad[i] := TSynRadioButton.Create(Form);
with Rad[i] do begin
Parent := Par;
SetBounds(X+16,Y,aWidth-32-X,6-FontHeight);
Caption := NoCR(Strings[i]);
if aHint<>'' then begin
ShowHint := true;
Hint := aHint; // note shown as Hint
end;
inc(Y,Height);
if (i=0) or (i+200=aRadioDef) then
Checked := true;
end;
end;
inc(Y,24);
finally
Free;
end;
// add selection list or query editor
if Selection<>'' then begin
List := TSynWideStringList.Create;
try
Combo := TSynComboBox.Create(Form);
Combo.Parent := Par;
Combo.SetBounds(X,Y,aWidth-32-X,22);
if tdfQuery in aFlags then
Combo.Style := csDropDown else
Combo.Style := csDropDownList;
List.Text := trim(Selection);
Combo.Items.Assign(List);
Combo.ItemIndex := List.IndexOf(Query);
inc(Y,42);
finally
List.Free;
end;
end else
if tdfQuery in aFlags then begin
Edit := TSynEdit.Create(Form);
Edit.Parent := Par;
Edit.SetBounds(X,Y,aWidth-16-X,22);
Edit.Text := Query;
if tdfQueryMasked in aFlags then
Edit.PasswordChar := '*';
inc(Y,42);
end;
// from now we won't add components to the white panel, but to the form
Panel.Height := Y;
Par := Form;
// add buttons and verification checkbox
if (byte(aCommonButtons)<>0) or (Verify<>'') or
((Buttons<>'') and not (tdfUseCommandLinks in aFlags)) then begin
CurrTabOrder := Panel.TabOrder;
inc(Y, 16);
XB := aWidth;
if not (tdfUseCommandLinks in aFlags) then
with TSynWideStringList.Create do
try
Text := SysUtils.trim(Buttons);
for i := Count-1 downto 0 do
AddButton(Strings[i],i+100);
finally
Free;
end;
for B := high(B) downto low(B) do
if B in aCommonButtons then
AddButton(TD_BTNS[b], TD_BTNMOD[b]);
if Verify<>'' then begin
Verif := TSynCheckBox.Create(Form);
with Verif do begin
Parent := Par;
if X+16+Form.Canvas.TextWidth(Verify)>XB then begin
inc(Y,32);
XB := aWidth;
end;
SetBounds(X,Y,XB-X,24);
Caption := Verify;
Checked := VerifyChecked;
end;
end;
inc(Y,36);
end else
XB := 0;
// add footer text with optional icon
if Footer<>'' then begin
if XB<>0 then
AddBevel else
inc(Y,16);
if WIN_FOOTERICONS[aFooterIcon]<>nil then begin
Image := TImage.Create(Form);
Image.Parent := Par;
Pic := TIcon.Create;
Bmp := TBitmap.Create;
try
Pic.Handle := LoadIcon(0,WIN_FOOTERICONS[aFooterIcon]);
Bmp.Transparent := true;
Bmp.Canvas.Brush.Color := Form.Color;
Bmp.Width := Pic.Width shr 1;
Bmp.Height := Pic.Height shr 1;
DrawIconEx(Bmp.Canvas.Handle,0,0,Pic.Handle,Bmp.Width,Bmp.Height,0,
Bmp.Canvas.Brush.Handle,DI_NORMAL);
Image.Picture.Bitmap := Bmp;
Image.SetBounds(24,Y,Bmp.Width,Bmp.Height);
X := 40+Bmp.Width;
finally
Bmp.Free;
Pic.Free;
end;
end else
X := 24;
AddLabel(Footer,false);
end;
// display the form
Form.ClientHeight := Y;
// retrieve the results
result := Form.ShowModal;
if Combo<>nil then begin
SelectionRes := Combo.ItemIndex;
Query := Combo.Text;
end else
if Edit<>nil then
Query := Edit.Text;
if Verif<>nil then
VerifyChecked := Verif.Checked;
RadioRes := 0;
for i := 0 to high(Rad) do
if Rad[i].Checked then
RadioRes := i+200;
finally
Form.Free;
end;
end;
end;
{ TTaskDialogEx }
function TTaskDialogEx.Execute(aParent: HWND): integer;
begin
Result := Base.Execute(CommonButtons, ButtonDef, Flags, DialogIcon, FooterIcon,
RadioDef, Width, aParent, NonNative, EmulateClassicStyle);
end;
{$endif USETMSPACK}
initialization
DefaultFont := TFont.Create;
DefaultFont.Style := [];
if Screen.Fonts.IndexOf('Calibri')>=0 then begin
DefaultFont.Height := -14;
DefaultFont.Name := 'Calibri';
end else begin
if Screen.Fonts.IndexOf('Tahoma')>=0 then
DefaultFont.Name := 'Tahoma' else
DefaultFont.Name := 'Arial';
DefaultFont.Height := -13;
end;
{$ifndef USETMSPACK}
InitComCtl6;
assert(ord(tdfCanBeMinimized)=15);
{$endif USETMSPACK}
BitmapOK := TBitmap.Create;
BitmapOK.LoadFromResourceName(HInstance,'btnOk'); // SQLite3btnok.bmp
BitmapOK.Transparent := true;
BitmapArrow := TBitmap.Create;
BitmapArrow.LoadFromResourceName(HInstance,'btnArrow'); // SQLite3btnArrow.bmp
BitmapArrow.Transparent := true;
finalization
DefaultFont.Free;
BitmapArrow.Free;
BitmapOK.Free;
end.
SynWideSupp.pas:
unit SynWideSupp;
interface
uses SysUtils
{$IFNDEF UNICODE}, WideStrUtils{$ENDIF}
;
type
{$IFDEF UNICODE}
SynWideString = String;
PSynWideChar = PChar;
{$ELSE}
SynWideString = WideString;
PSynWideChar = PWideChar;
{$ENDIF}
function SynWideStringReplace(const S, OldPattern, NewPattern: SynWideString;
Flags: TReplaceFlags): SynWideString; inline;
implementation
function SynWideStringReplace(const S, OldPattern, NewPattern: SynWideString;
Flags: TReplaceFlags): SynWideString;
begin
{$IFDEF UNICODE}
Result := StringReplace(S, OldPattern, NewPattern, Flags);
{$ELSE}
Result := WideStringReplace(S, OldPattern, NewPattern, Flags);
{$ENDIF}
end;
end.
this is a problem of Excel 2007, as the file "import-ods.ods" was created directly in LibreOffice and not by my code.
try to open "my-export.ods" - this is a file generated by my application. it can be opened without problems in my excel 2007.
as I promised, I released oExport, my native Delphi2007 XLSX/ODS suite. it can import/export XLSX (Excel 2007) and ODS (OpenOffice Calc) files.
license is the same as SynPDF (MPL/GPL/LGPL tri-license)
(+) NATIVE: no external libraries or excel/calc installation needed.
(-) only Delphi 2007... but someone can help rewrite the code for D2007+
http://www.kluug.at/xlsx-ods-delphi.php
enjoy
thanks!
most of the external libraries are needed only for the audio and only by the recorder...
the player needs only the ogg/mp3 codec libraries to play audio - there is no chance to get around that, to my mind... if you don't need any audio, you don't need the dll libraries, of course.
the file format is just a zip archive with an xml definition and an audio and png files simple
I wrote a screencasting software in Delphi and I'll release the source code for it in some days. If you find it interesting, you can take a look at the current progress at http://www.kluug.at/videotutorial.php . You can play with a dll for C++/Delphi and with the recording application.
I think it is a good alternative to other screencasting software since it offers loseless video compression and a simple native video format.
that's interesting - I downloaded your demo program and it does exactly what it should - the correct formatting is preserved in the PDF - bold, alignment, underline, font size... (of course only in "Report PDF" Button, "Demo PDF" does not render the pdf text)...
everything works for me.
I use Delphi 2007
+ I am sorry to say that I have not Delphi 7 and so I can't check your code with it, but in Delphi 2007 the code works fine
Hi, for RTF you need to draw it on a TMetafileCanvas and then you draw the metafile on the VCLCanvas. This creates the best results, i think.
There is some example code in this forum - you have just to search a little bit...
Sorry, I'm now very happy with SynPDF as it is. Everybody has a restricted time capacity
Ondrej
eraldo: i don't think so - just follow my/arnauld's instructions and you will be able to generate a pdf barcode with very little effort. look at the examples in TPSysTools and do what you want. there are so many different barcode types, for example. the demos are very straightforward.
to arnauld: the problem with wmf/emf originally generated with TPSysTools is that when they are scaled, "new white lines" can occur in the barcode because of rounding errors when scaling. this is because a bar with the width of 2px is generated with 2 bars with the width of 1px. that's the problem. but maybe someone changed it since i had the problem
use TPSysTools to generate a barcode + export it to bitmap/WMF + draw it onto PDFCanvas.
but I had best results by generating a 1:1 barcode with TPSysTools onto a bitmap - then you have to scan the image for each pixel and generate a rectangle over each "black area". this is needed because TPSysTools generate one rectangle per barcode pixel which can end up in non consistent "black areas" on the print because the rectangles are scaled and so rounding errors can occure. I know it is not the most efficient method, but quick-and-dirty is sometimes good enough
I tried MSEgui some months ago and I wasn't very impressed by the whole usability. But maybe it's because I just didn't go very deeply into it. As I see, the project is updated quite often, so I'll have to give it another try.
If you find of a better solution, that's fine!
=> the author is always right
no - I am really happy that we solved the 2 issues! have a nice weekend!
I downloaded your code and everything is working fine. IMHO it is not necessary to have the 3 new parameters defined at 2 places, but the most important is that it's working
I downloaded Acrobat Reader X and checked the new generated pdfs and they are valid.
+ I am sure that you worked much longer than 2 hours to give us SynPDF, so I am glad that I could help you a little. The only thing is, that changing 3 characters of code makes such a difference
In the last month, there was made a very important step in Delphi's development. I am talking about www.ksdev.com - I was about to buy the VGScene units because I wanted to write programs for all platforms - but Codegear was quicker. They did not only buy the components, they bought all intelectual property from ksdev.com. So the dream of using open-source Lazarus with profesional (and relatively cheap) widgets was all gone. The only hope I have is that Codegear will bring Delphi further at last - to my mind there has been more false and strange development since Delphi 7 than real improvement. What a shame that Codegear introduced an out-of-box unicode-ready Delphi 10 years after Microsoft has fully supported it in Windows 2000... When everybody who needed unicode uses TntComponents, which makes porting old code to Delphi 2009+ a nightmare...
I hope that at some point Lazarus will be a real competitor to Delphi. But to my mind it was not a good decision to write the LCL. Some lightweight, easy and complete widgetset would be much better. LCL is just going to be very complicated for custom component writing. Maybe fpGUI at some point...
PS: everybody who wants to see all features of VGScene, use this link: http://www.ksdev.com/vgscene/index.html - the author hasn't deleted the main page yet.
this is a code example where I need UseSetTextJustification := False;
procedure TForm1.BtnTestFontClick(Sender: TObject);
var
xRect: TRect;
I: Integer;
const
Text: WideString = 'Ondrej, RedDwarf:';
begin
with TPdfDocumentGDI.Create do
try
KerningHScaleBottomLimit := 100;
KerningHScaleTopLimit := 100;
UseSetTextJustification := False;//try to use True
AddPage;
with VCLCanvas do begin
Font.Name := 'Tahoma';//'Times New Roman';
Font.Style := [fsBold, fsUnderline];
Pen.Color := $AAAAAA;
Img1.Canvas.Font.Assign(Font);
Img1.Canvas.Pen.Assign(Pen);
Img1.Canvas.Brush.Style := bsClear;
for I := 5 to 15 do begin
Font.Size := I;
xRect := Rect(0, 0, TextWidthXP(Text), TextHeightXP(Text));
OffsetRect(xRect, 100, 100+(I-5)*30);
Rectangle(xRect);
Windows.ExtTextOutW(Handle, xRect.Left, xRect.Top, ETO_CLIPPED,
@xRect, PWideChar(Text), Length(Text), nil);
//just to check with an image canvas
Img1.Canvas.Font.Assign(Font);
Img1.Canvas.Rectangle(xRect);
Windows.ExtTextOutW(Img1.Canvas.Handle, xRect.Left, xRect.Top, ETO_CLIPPED,
@xRect, PWideChar(Text), Length(Text), nil);
end;
end;
SaveToFile('TestVcl.pdf');
finally
Free;
end;
end;
Hello Arnauld,
thanks for checking my suggestions.
Canvas.MoveToI(Left-1, Top+1);
Canvas.LineToI(Left-1, Bottom+1);
Canvas.LineToI(Right-1, Bottom+1);
Canvas.LineToI(Right-1, Top+1);
this is fine - I just did not look at that precisely.
SetWordSpace() problem:
I am sure that you know more background information than me and therefore I suppose that your code is better. So keep it as you want. I will test it and perhaps I'll come with some clear example where I'd like to have another behaviour. Until then, keep your code.
To be honest, I don't use align=justify (since standard delphi doesn't like it either) so I did not realise this purpose.
I'd like to make a suggestion regarding the SetHorizontalScaling hysteresis:
I think you could add a property to TPdfDocument that would define the range the programmer prefers. then you could use something like:
if (hscale<Canvas.FDoc.IgnoreHScaleBottomLimit) or (hscale>Canvas.FDoc.IgnoreHScaleTopLimit) then
Canvas.SetHorizontalScaling(hscale) else
hscale := 100;
I think it is the best solution to offer each programmer the possibility to change the range.
+ Opening PDF Files: I have always used Adobe Acrobat 9 Pro and I am sorry to say that I have observed the problem with "asking to save document at close" since the very first version of SynPDF I used. Additionally, Adobe Acrobat shows a message that the file has to be repaired when opening a SynPDF generated file (but only for 0-2 seconds and then it automatically closes). I haven't tested other versions of Acrobat since I've bought just 9 Pro.
To be honest, I have no idea what Acrobat doesn't like about your PDF source. Everything I know about PDF is what I have been able to learn from your code... I tried the Adobe Preflight but without success...
As a second program I use SumatraPDF which is a tiny freeware pdf viewer (http://blog.kowalczyk.info/software/sum … eader.html) and it has absolutely no problem viewing your PDFs.
BEST
Ondrej
+ yesss delphi rocks
Arnauld, it is clear, that you are the great guru here. Your starting point (2) was the right solution! I played with it a little bit and came to a result that satisfies me completely. Particularly I made these additional changes to your code so that the characters are printed exactly 1:1:
A) I changed the HorizontalScaling property to SINGLE for more precision
B) I used the HorizontalScaling for all fonts, not only for: if (hscale<99) or (hscale>106)
with these 2 small changes, the string positions are EXACT. Thanks for help! (I send you the changes by email, but there are not many )
I also had the idea to draw each character separately, but as you said, the resulting file is just toooo big when using this method for all texts although the characters are placed (nearly) exactly.
something like this:
procedure PDFExactTextOut(aCanvas: TCanvas; aText: String; aRect: TRect);
var I, xX: Integer;
xS: String;
begin
for I := 1 to Length(aText) do begin
xX := aCanvas.TextWidth(Copy(aText, 1, I-1));
xS := Copy(aText, I, 1);
Windows.ExtTextOut(aCanvas.Handle, aRect.Left+xX, aRect.Top, ETO_CLIPPED,
@aRect, PChar(xS), 1, nil);
end;
end;
do you think is there any way how to get the string length without kerning?
I tried to compute the width of each character and to sum them, but it did not help.
The problem can be observed also when using underline/strikeout font:
procedure TForm1.BtnTestUnderlineClick(Sender: TObject);
const
Text: String = 'RERERERE:';
begin
with TPdfDocumentGDI.Create do
try
AddPage;
//UseUniScribe := true; //uniscribe does not change anything about the problem
with VCLCanvas do begin
Font.Name := 'Tahoma';
Font.Size := 8;
Font.Style := [fsBold, fsUnderline];
TextOut(100, 100, Text);
end;
SaveToFile('TestUnderline.pdf');
finally
Free;
end;
end;
I don't need kerning. But what would be great is to get the exact string width as the string is shown in the pdf - if it is possible. I am sorry to say that I cannot solve this problem by myself.
Hi Arnaud,
I am facing a similar problem as Pavel in his "Problems with Courier New font" thread.
The thing is, that for example the Tahoma bold 8pt font is not drawn exactly 1:1 as on normal window canvas. Particularly the character width is wrong. This results in displaced words when exporting RTF or words being clipped when using ExtTextOutW with ETO_CLIPPED. The problem can be observed when importing the PDF into Adobe InDesign (I tested CS5), in which the characters are shown twice and are overlapping.
Look at this code. With SynPdf v1.12 the last ":" is cut off from the rectangle (8pt font):
procedure TForm1.BtnTestFontClick(Sender: TObject);
var
xRect: TRect;
const
Text: WideString = 'RERERERE:';
begin
with TPdfDocumentGDI.Create do
try
AddPage;
UseUniScribe := true; //uniscribe does not change anything about the problem
with VCLCanvas do begin
Font.Name := 'Tahoma';
Font.Size := 8;
Font.Style := [fsBold];
Pen.Color := $AAAAAA;
xRect := Rect(0, 0, TextWidth(Text), TextHeight(Text));
OffsetRect(xRect, 100, 100);
Rectangle(xRect);
Windows.ExtTextOutW(Handle, xRect.Left, xRect.Top, ETO_CLIPPED,
@xRect, PWideChar(Text), Length(Text), nil);
Font.Size := 12;
xRect := Rect(0, 0, TextWidth(Text), TextHeight(Text));
OffsetRect(xRect, 100, 200);
Rectangle(xRect);
Windows.ExtTextOutW(Handle, xRect.Left, xRect.Top, ETO_CLIPPED,
@xRect, PWideChar(Text), Length(Text), nil);
end;
SaveToFile('TestVcl.pdf');
finally
Free;
end;
end;
Thanks for great PDF library!
this is an interesting post. some time ago I had a similar problem. but I used normal printer begindoc, canvas, endoc etc. I wanted to print the first page of a document as landscape and the second page as portrait and I did not find the way how to do it in a single print job. the only possibility I found was to split the document into 2 print jobs. this solution is, of course, not ideal because e.g. if you select the "adobe pdf" printer you get 2 files and you have to join them manually afterwards.
do you know if/how it is possible to change paper settings during the printer job when using standard printing procedures in delphi? to my mind, it must be possible because for example adobe reader can handle it...
I doubt it's easily possible with SynPDF now, you have to write a PDF parser based on SynPDF and append the page manually...
yes, you are right. your code is easier, but does the thing a little bit differently. therefore someone may find it useful and now i remember where i took inspiration - in the original delphi source code
ok, let me be a little bit useful again... maybe i should have posted this code earlier... i have found some code on the internet how to paint rich text on canvas. but it was buggy so i modified it heavily. i also added more functionality to that. the best one is the calculation of actual height of the printed rtf text [you miss ]. it is possible to paint rich text on more pages (always use the result value for the next "FromChar" parameter).
info for ab: i did test neither your new code, nor your rtf text drawing. sorry, no time, a lot of work so my example was compiled with your 1.8.0? version [maybe 1 month old ]... but it works pretty fine for me.
there are some other functions you can just omit if you don't need them.
+ it works with TTntRichEdit, if you wish the normal TRichEdit, just rename everything...
ENJOY:
unit PrintRichText:
unit PrintRichText;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, ComCtrls, TntComCtrls, TntControls, TntSysUtils;
type
TPrintRichEdit = class helper for TTntRichEdit
private
function GetRawRTF: String;
procedure SetRawRTF(const Value: String);
public
function DrawTo(ACanvas: TCanvas; aRect: TRect;
DPI: Integer = 0; FromChar: Integer = 0; ToChar: Integer = -1): Longint;
//WARNING MyFindText COUNTS FROM 0!!!! if found at start return value = 0 !!!
function MyFindText(aStr: WideString; FromPosition: Integer = 1): Integer;
function GetLineBreakStyle: TTntTextLineBreakStyle;
function GetTextLenW: Integer;
procedure MyDelete(aPos, aLength: Integer);
procedure ReplaceText(const FromStr, ToStr: WideString);
procedure ProcessIfStatement(const IfStatement: WideString; const aBoolean: Boolean); overload;
procedure ProcessIfStatement(const IfStatement, ElseStatement, EndIfStatement: WideString; const aBoolean: Boolean); overload;
property RawRTF: String read GetRawRTF write SetRawRTF;
end;
TTntRichEditAccessProtected = class(TTntRichEdit)
public
function LineBreakStyle: TTntTextLineBreakStyle; reintroduce;
end;
function DefPrintRTFToCanvas(aRichEdit: TCustomRichEdit; aCanvas: TCanvas; aRect: TRect;
FromChar: Integer = 0; ToChar: Integer = -1): Longint;
function DefPrintRTFToCanvasTransparent(aRichEdit: TCustomRichEdit; aCanvas: TCanvas; aRect: TRect;
Rop: Cardinal; FromChar: Integer = 0; ToChar: Integer = -1): Longint;
function DefCalculateRTFHeight(aRichEdit: TCustomRichEdit; aCanvas: TCanvas; aRect: TRect;
DPI: Integer = 0; FromChar: Integer = 0; ToChar: Integer = -1): Longint;
function PrintRTFToCanvas(aRichEdit: TCustomRichEdit; aCanvas: TCanvas; aRect: TRect;
DPI: Integer = 0; FromChar: Integer = 0; ToChar: Integer = -1): Longint; overload;
function PrintRTFToCanvas(aRichRawText: String; aCanvas: TCanvas; aRect: TRect;
DPI: Integer = 0; FromChar: Integer = 0; ToChar: Integer = -1): Longint; overload;
function CalculateRTFHeight(aRichEdit: TCustomRichEdit; aCanvas: TCanvas; aRect: TRect;
DPI: Integer = 0; FromChar: Integer = 0; ToChar: Integer = -1): Longint; overload;
function CalculateRTFHeight(aRichRawText: String; aCanvas: TCanvas; aRect: TRect;
DPI: Integer = 0; FromChar: Integer = 0; ToChar: Integer = -1): Longint; overload;
procedure SetRawRTF(RE: TTntRichEdit; RawRTF: String);
function GetRawRTF(RE: TTntRichEdit): String;
function ReplaceRTFText(aRichRawText: String; aFromText, aToText: WideString): String;
function DrawRichEdit: TTntRichEdit;
implementation
uses RichEdit, Types, WideStrUtils, Math;
var
XDrawRichEdit: TTntRichEdit = nil;
function DrawRichEdit: TTntRichEdit;
begin
if Application.MainForm = nil then begin
Result := nil;
exit;
end;
if not Assigned(XDrawRichEdit) then begin
XDrawRichEdit := TTntRichEdit.Create(Application.MainForm);
XDrawRichEdit.Visible := False;
XDrawRichEdit.Parent := Application.MainForm;
end;
Result := XDrawRichEdit;
end;
function GetRawRTF(RE: TTntRichEdit): String;
var
strStream: TStringStream;
begin
strStream := TStringStream.Create('') ;
try
RE.PlainText := False;
RE.Lines.SaveToStream(strStream) ;
Result := strStream.DataString;
finally
strStream.Free
end;
end;
procedure SetRawRTF(RE: TTntRichEdit; RawRTF: String);
var
strStream: TStringStream;
begin
strStream := TStringStream.Create(RawRTF) ;
try
RE.PlainText := False;
RE.Lines.LoadFromStream(strStream) ;
finally
strStream.Free
end;
end;
function PrintRTFToCanvas(aRichRawText: String; aCanvas: TCanvas; aRect: TRect;
DPI: Integer = 0; FromChar: Integer = 0; ToChar: Integer = -1): Longint;
var
RE: TTntRichEdit;
begin
RE := TTntRichEdit.Create(nil);
with RE do
try
Visible := false;
Parent := Application.MainForm;
RE.RawRTF := aRichRawText;
Result := PrintRTFToCanvas(RE, aCanvas, aRect, DPI, FromChar, ToChar);
finally
RE.Free;
end;
end;
function DefCalculateRTFHeight(aRichEdit: TCustomRichEdit; aCanvas: TCanvas; aRect: TRect;
DPI: Integer; FromChar: Integer; ToChar: Integer): Integer;
var
MTF: TMetafile;
MTFCanvas: TMetafileCanvas;
Range: TFormatRange;
rZoom: Single;
SaveMapMode, LogX, LogY: Integer;
SaveViewPort, SaveWindowExt: tagSize;
begin
if Trim(aRichEdit.Text) = '' then begin
Result := 0;
exit;
end;
MTF := TMetafile.Create;
MTFCanvas := TMetafileCanvas.Create(MTF, 0);
try
MTFCanvas.Font.Assign(aCanvas.Font);
MTF.Width := aRect.Right-aRect.Left;
MTF.Height := aRect.Bottom-aRect.Top;
if DPI = 0 then
DPI := ACanvas.Font.PixelsPerInch;
LogX := GetDeviceCaps(MTFCanvas.Handle, LOGPIXELSX);
LogY := GetDeviceCaps(MTFCanvas.Handle, LOGPIXELSY);
rZoom := DPI/MTFCanvas.Font.PixelsPerInch;
FillChar(Range, SizeOf(TFormatRange), 0);
Range.hdc := MTFCanvas.Handle;
Range.hdcTarget := MTFCanvas.Handle;
Range.rc.left := 0;
Range.rc.top := 0;
Range.rc.right := (aRect.Right-aRect.Left) * 1440 div DPI;
Range.rc.Bottom := (aRect.Bottom-aRect.Top) * 1440 div DPI;
{Range.rc.left := aRect.Left * 1440 div LogX;
Range.rc.top := aRect.Top * 1440 div LogY;
Range.rc.right := aRect.Right * 1440 div LogX;
Range.rc.Bottom := aRect.Bottom * 1440 div LogY;}
if not((FromChar = 0) and (ToChar = 0)) then begin
Range.chrg.cpMax := ToChar;
Range.chrg.cpMin := FromChar;
end else begin
Range.chrg.cpMax := -1;
Range.chrg.cpMin := 0;
end;
Range.rcPage := Range.rc;
with MTFCanvas do begin
SaveMapMode := GetMapMode(Handle);
GetWindowExtEx(Handle, SaveWindowExt);
GetViewportExtEx(Handle, SaveViewPort);
end;
try
with MTFCanvas do begin
SetMapMode(Handle, MM_TEXT);//MM_ANISOTROPIC MM_TEXT
SetWindowExtEx(Handle, LogX, LogY, nil);
SetViewportExtEx(Handle, Round(LogX * rZoom), Round(LogY * rZoom), nil);
end;
SendMessage(ARichedit.Handle, EM_FORMATRANGE, 0, Longint(@Range));
SendMessage(ARichEdit.handle, EM_FORMATRANGE, 0,0);
Result := Round(range.rc.bottom * DPI / 1440);
finally
with MTFCanvas do begin
SetMapMode(Handle, SaveMapMode);
SetWindowExtEx(Handle, SaveWindowExt.cx, SaveWindowExt.cy, nil);
SetViewportExtEx(Handle, SaveViewPort.cx, SaveViewPort.cy, nil);
end;
end;
finally
MTFCanvas.Free;
MTF.Free;
end;
end;
function DefPrintRTFToCanvasTransparent(aRichEdit: TCustomRichEdit; aCanvas: TCanvas; aRect: TRect;
Rop: Cardinal; FromChar: Integer = 0; ToChar: Integer = -1): Longint;
var B: TBitmap;
begin
B := TBitmap.Create;
try
B.Canvas.Font.Assign(aCanvas.Font);
B.Width := aRect.Right-aRect.Left;
B.Height := aRect.Bottom-aRect.Top;
B.Canvas.Brush.Color := clWhite;
B.Canvas.FillRect(Rect(0, 0, B.Width, B.Height));
Result := DefPrintRTFToCanvas(aRichEdit, B.Canvas, Rect(0, 0, B.Width, B.Height), FromChar, ToChar);
{B.TransparentColor := clWhite; DOES NOT REALLY FUNCTION BECAUSE OF ANTIALIASING
B.TransparentMode := tmFixed;
B.Transparent := True;
aCanvas.Draw(aRect.Left, aRect.Top, B);}
BitBlt(aCanvas.Handle, aRect.Left, aRect.Top, B.Width, B.Height,
B.Canvas.Handle, 0, 0, Rop);
finally
B.Free;
end;
end;
function DefPrintRTFToCanvas(aRichEdit: TCustomRichEdit; aCanvas: TCanvas; aRect: TRect;
FromChar: Integer = 0; ToChar: Integer = -1): Longint; overload;
var
Range: TFormatRange;
SaveMapMode, LogX, LogY: Integer;
//SaveViewPort, SaveWindowExt: tagSize;
//B: TBitmap;
begin
//B := TBitmap.Create;
try
//B.Canvas.Font.Assign(aCanvas.Font);
//B.Width := aRect.Right-aRect.Left;
//B.Height := aRect.Bottom-aRect.Top;
//B.Canvas.Brush.Color := clWhite;
//B.Canvas.FillRect(aRect);
LogX := GetDeviceCaps(ACanvas.Handle, LOGPIXELSX);
LogY := GetDeviceCaps(ACanvas.Handle, LOGPIXELSY);
FillChar(Range, SizeOf(TFormatRange), 0);
Range.hdc := ACanvas.Handle;
Range.hdcTarget := ACanvas.Handle;
{Range.rc.left := 0;
Range.rc.top := 0;
Range.rc.right := B.Width * 1440 div DPI;
Range.rc.Bottom := B.Height * 1440 div DPI;}
Range.rc.left := aRect.Left * 1440 div LogX;
Range.rc.top := aRect.Top * 1440 div LogY;
Range.rc.right := aRect.Right * 1440 div LogX;
Range.rc.Bottom := aRect.Bottom * 1440 div LogY;
if not((FromChar = 0) and (ToChar = 0)) then begin
Range.chrg.cpMax := ToChar;
Range.chrg.cpMin := FromChar;
end else begin
Range.chrg.cpMax := -1;
Range.chrg.cpMin := 0;
end;
with ACanvas do begin
SaveMapMode := GetMapMode(Handle);
//GetWindowExtEx(Handle, SaveWindowExt);
//GetViewportExtEx(Handle, SaveViewPort);
end;
try
with ACanvas do begin
SetMapMode(Handle, MM_TEXT);//MM_ANISOTROPIC
//SetWindowExtEx(Handle, DPI, DPI, nil);
//SetViewportExtEx(Handle, Round(DPI * rZoom), Round(DPI * rZoom), nil);
end;
{ARichedit.SelStart :=40;
ARichedit.SelLength := 15;
aRichEdit.SelAttributes.Color := clRed;}
Result := SendMessage(ARichedit.Handle, EM_FORMATRANGE, 1, Longint(@Range));
SendMessage(ARichEdit.handle, EM_FORMATRANGE, 0,0);
//aCanvas.Draw(aRect.Left, aRect.Top, B);
{BitBlt(aCanvas.Handle, aRect.Left, aRect.Top, B.Width, B.Height,
B.Canvas.Handle, 0, 0, srcAND);}
finally
with ACanvas do begin
SetMapMode(Handle, SaveMapMode);
//SetWindowExtEx(Handle, SaveWindowExt.cx, SaveWindowExt.cy, nil);
//SetViewportExtEx(Handle, SaveViewPort.cx, SaveViewPort.cy, nil);
end;
end;
finally
//B.Free;
end;
end;
{function PrintRTFToCanvas(ARichEdit: TCustomRichEdit; ACanvas: TCanvas; aRect: TRect;
DPI, FromChar, ToChar: Integer): Longint;
var
LogX: Integer;
begin
LogX := GetDeviceCaps(ACanvas.Handle, LOGPIXELSX);
if DPI = 0 then
DPI := ACanvas.Font.PixelsPerInch;
if DPI <> LogX then
result := DefPrintRTFToCanvasWithZoom(ARichEdit, ACanvas, aRect, DPI, FromChar, ToChar)
else
result := DefPrintRTFToCanvas(aRichEdit, aCanvas, aRect, FromChar, ToChar);
end;}
function CalculateRTFHeight(aRichRawText: String; aCanvas: TCanvas; aRect: TRect;
DPI: Integer = 0; FromChar: Integer = 0; ToChar: Integer = -1): Longint;
begin
DrawRichEdit.RawRTF := aRichRawText;
Result := CalculateRTFHeight(DrawRichEdit, aCanvas, aRect, DPI, FromChar, ToChar);
end;
function CalculateRTFHeight(aRichEdit: TCustomRichEdit; aCanvas: TCanvas; aRect: TRect;
DPI: Integer = 0; FromChar: Integer = 0; ToChar: Integer = -1): Longint;
{var
LogX: Integer;
MTF: TMetafile;
MTFCanvas: TMetafileCanvas;
aZoom: Single;}
begin
Result := DefCalculateRTFHeight(aRichEdit, aCanvas, Rect(0, 0, Round((aRect.Right-aRect.Left)), High(SmallInt)), DPI);
{LogX := GetDeviceCaps(aCanvas.Handle, LOGPIXELSX);
if DPI = 0 then
DPI := LogX;
MTF := TMetafile.Create;
try
MTFCanvas := TMetafileCanvas.Create(MTF, 0);
with MTFCanvas do
try
LogX := GetDeviceCaps(Handle, LOGPIXELSX);
aZoom := DPI / LogX;
if aZoom <= 0 then
aZoom := 1;
Result := Round(DefCalculateRTFHeight(aRichEdit, MTFCanvas, Rect(0, 0, Round((aRect.Right-aRect.Left) / aZoom), High(SmallInt)), 0) * aZoom);
//Result := Round(DefCalculateRTFHeight(aRichEdit, MTFCanvas, aRect, 0) * aZoom);
finally
Free;
end;
finally
MTF.Free;
end;}
end;
function PrintRTFToCanvas(ARichEdit: TCustomRichEdit; ACanvas: TCanvas; aRect: TRect;
DPI, FromChar, ToChar: Integer): Longint;
var
LogX: Integer;
MTF: TMetafile;
MTFCanvas: TMetafileCanvas;
aZoom: Single;
begin
LogX := GetDeviceCaps(aCanvas.Handle, LOGPIXELSX);
if DPI = 0 then
DPI := LogX;
if DPI = LogX then begin
result := DefPrintRTFToCanvas(aRichEdit, aCanvas, aRect, FromChar, ToChar);{}
end else begin
MTF := TMetafile.Create;
try
with TMetafileCanvas.Create(MTF, 0) do
try
LogX := GetDeviceCaps(Handle, LOGPIXELSX);
finally
Free;
end;
aZoom := DPI / LogX;
if aZoom <= 0 then
aZoom := 1;
MTF.Width := Round((aRect.Right-aRect.Left) / aZoom);
//ShowMessage(IntToStr((aRect.Right-aRect.Left))+':'+IntToStr(MTF.Width));
MTF.Height := Round((aRect.Bottom-aRect.Top) / aZoom);
MTFCanvas := TMetafileCanvas.Create(MTF, 0);
with MTFCanvas do
try
Result := DefPrintRTFToCanvas(aRichEdit, MTFCanvas, Rect(0, 0, MTF.Width, MTF.Height), FromChar, ToChar);
finally
MTFCanvas.Free;
end;
//MTF.Width := Round((aRect.Right-aRect.Left) / 1);
//ShowMessage(IntToStr((aRect.Right-aRect.Left))+':'+IntToStr(MTF.Width));
//MTF.Height := Round((aRect.Bottom-aRect.Top) / 1);
ACanvas.StretchDraw(aRect, MTF);
//ACanvas.Draw(0, 0, MTF);
finally
MTF.Free;
end;{}
end;
{LogX := GetDeviceCaps(ACanvas.Handle, LOGPIXELSX);
if DPI = 0 then
DPI := ACanvas.Font.PixelsPerInch;
if DPI <> LogX then
result := DefPrintRTFToCanvasWithZoom(ARichEdit, ACanvas, aRect, DPI, FromChar, ToChar)
else
result := DefPrintRTFToCanvas(aRichEdit, aCanvas, aRect, FromChar, ToChar);{}
end;
{ TPrintRichEdit }
function TPrintRichEdit.DrawTo(ACanvas: TCanvas; aRect: TRect; DPI, FromChar,
ToChar: Integer): Longint;
begin
Result := PrintRTFToCanvas(Self, ACanvas, aRect, DPI, FromChar, ToChar);
end;
function TPrintRichEdit.GetRawRTF: String;
begin
Result := PrintRichText.GetRawRTF(Self);
Result := TrimRight(WideReplaceText(Result, #0, ''));
end;
function TPrintRichEdit.GetTextLenW: Integer;
begin
//Result := TntAdjustLineBreaksLength(Text, GetLineBreakStyle);
Result := TntAdjustLineBreaksLength(TntControl_GetText(Self), GetLineBreakStyle);
end;
function TPrintRichEdit.GetLineBreakStyle: TTntTextLineBreakStyle;
begin
Result := TTntRichEditAccessProtected(Self).LineBreakStyle;
end;
procedure TPrintRichEdit.MyDelete(aPos, aLength: Integer);
begin
SelStart := aPos-1;
SelLength := aLength;
SelText := '';
end;
function TPrintRichEdit.MyFindText(aStr: WideString;
FromPosition: Integer): Integer;
begin
Result := FindText(aStr, FromPosition-1, Length(Text)-FromPosition+1, [])+1;
end;
procedure TPrintRichEdit.ProcessIfStatement(const IfStatement, ElseStatement,
EndIfStatement: WideString; const aBoolean: Boolean);
var pos1, pos2, pos3: Integer;
I: Integer;
begin
I := 0;
pos1 := MyFindText(IfStatement);
while (pos1 <> 0) and (I < 20) do begin
MyDelete(pos1, Length(IfStatement));
pos2 := MyFindText(ElseStatement, pos1);
pos3 := MyFindText(EndIfStatement, pos1);
pos2 := Min(pos2, pos3);
if (pos2 = 0) then
pos2 := pos3;
if aBoolean then begin
MyDelete(pos2, pos3-pos2+Length(EndIfStatement));
end else begin
MyDelete(pos1, pos2-pos1);
if (MyFindText(ElseStatement, pos1) = pos1) then
MyDelete(pos1, Length(ElseStatement));
pos1 := MyFindText(EndIfStatement, pos1);
MyDelete(pos1, Length(EndIfStatement));
end;
pos1 := MyFindText(IfStatement);
Inc(I);
end;
end;
procedure TPrintRichEdit.ProcessIfStatement(const IfStatement: WideString;
const aBoolean: Boolean);
var ElseStatement, EndIfStatement: WideString;
begin
if Length(IfStatement) > 0 then begin
ElseStatement := IfStatement[1] + 'ELSE' + IfStatement[Length(IfStatement)];
EndIfStatement := IfStatement[1] + 'ENDIF' + IfStatement[Length(IfStatement)];
ProcessIfStatement(IfStatement, ElseStatement, EndIfStatement, aBoolean);
end;
end;
procedure TPrintRichEdit.ReplaceText(const FromStr, ToStr: WideString);
var X: Integer;
begin
X := 1;
X := MyFindText(FromStr, X);
while X <> 0 do begin
SelStart := X-1;
SelLength := Length(FromStr);
SelText := ToStr;
X := MyFindText(FromStr, X + Length(ToStr));
end;
end;
procedure TPrintRichEdit.SetRawRTF(const Value: String);
begin
PrintRichText.SetRawRTF(Self, Value);
end;
function ReplaceRTFText(aRichRawText: String; aFromText, aToText: WideString): String;
var
RE: TTntRichEdit;
begin
RE := TTntRichEdit.Create(nil);
with RE do
try
Visible := false;
Parent := Application.MainForm;
RawRTF := aRichRawText;
ReplaceText(aFromText, aToText);
Result := RawRTF;
finally
RE.Free;
end;
end;
{ TTntRichEditAccessProtected }
function TTntRichEditAccessProtected.LineBreakStyle: TTntTextLineBreakStyle;
begin
Result := inherited LineBreakStyle;
end;
end.
how to use that:
unit Unit1;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls, ComCtrls, TntComCtrls;
type
TForm1 = class(TForm)
RE1: TTntRichEdit;
BtnSynopseRTFTest: TButton;
BtnMorePagesTest: TButton;
procedure BtnSynopseRTFTestClick(Sender: TObject);
procedure BtnMorePagesTestClick(Sender: TObject);
private
{ Private declarations }
protected
procedure DoCreate; override;
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
uses SynPDF, PrintRichText, TntSysUtils;
procedure TForm1.BtnMorePagesTestClick(Sender: TObject);
var
xPDF: TPdfDocumentGDI;
xRect: TRect;
xMF: TMetafile;
xMFC: TMetaFileCanvas;
xHeight, xLastChar, xMaxChar, I: Integer;
begin
xPDF := TPdfDocumentGDI.Create;
xMF := TMetafile.Create;
try
xPDF.AddPage;
xLastChar := -1;
//xMaxChar := RE1.GetTextLen;//USE FOR NORMAL RichEdit
xMaxChar := RE1.GetTextLenW;
I := 0;
repeat
xRect := Rect(100, 100+I*200, 300, 190++I*200);
xLastChar := PrintRTFToCanvas(RE1, xPDF.VCLCanvas, xRect, 0, xLastChar);
with xPDF.VCLCanvas do begin
Brush.Style := bsClear;
Pen.Width := 1;
Pen.Color := clBlack;
Rectangle(xRect);
end;
Inc(I);
until (xLastChar >= xMaxChar) or (xLastChar = -1);
xPDF.SaveToFile('a.pdf');
finally
xPDF.Free;
xMF.Free;
end;
end;
procedure TForm1.BtnSynopseRTFTestClick(Sender: TObject);
var
xPDF: TPdfDocumentGDI;
xRect: TRect;
xMF: TMetafile;
xMFC: TMetaFileCanvas;
xHeight, xLastChar: Integer;
begin
xPDF := TPdfDocumentGDI.Create;
xMF := TMetafile.Create;
try
xPDF.AddPage;
xRect := Rect(100, 100, 300, 1000);
xHeight := CalculateRTFHeight(RE1, xPDF.VCLCanvas, xRect);
xRect.Bottom := xRect.Top + xHeight;
PrintRTFToCanvas(RE1, xPDF.VCLCanvas, xRect);
with xPDF.VCLCanvas do begin
Brush.Style := bsClear;
Pen.Width := 1;
Pen.Color := clBlack;
Rectangle(xRect);
end;
xPDF.SaveToFile('a.pdf');
finally
xPDF.Free;
xMF.Free;
end;
end;
procedure TForm1.DoCreate;
var xStr: TStrings;
I: Integer;
begin
inherited;
xStr := TStringList.Create;
try
if FileExists('a.rtf') then
xStr.LoadFromFile('a.rtf');
RE1.RawRTF := xStr.Text;
finally
xStr.Free;
end;
end;
end.
EDIT: one example more
I worked on your PDF library and corrected some other issues (all concerning VCLCanvas):
Error when drawing multiple images
Automatical JPEG compression for graphics
UNDERLINE + STRIKEOUT support (also in RICH TEXT and rotated text !)
Because some developers may prefer to use the default JPEG unit in Delphi I added the directive USE_SYNGDIPLUS that can switch between GDIPLUS / JPEG
PenWidth changed to Single -> better precision (f.e. for underlined text)
... maybe some other minor corrections
I don't see where I could save an attachment, therefore I send you the whole SynPdf.pas unit in an email. All my code changes (I hope that I haven't forgotten something) are marked with ONDREJ comment.
Thanks for the great PDF Library. I found 2 issues with the VCLCanvas functions. Please check my code:
procedure TForm1.Btn1Click(Sender: TObject);
var
xPDF: TPdfDocumentGDI;
xRect: TRect;
xMF: TMetafile;
xMFC: TMetaFileCanvas;
procedure RotTextWithPoint(aC: TCanvas; aText: String; aAngle, aX, aY: Integer);
var
xLF: TLogFont;
xF: TFont;
begin
xF := TFont.Create;
try
xF.Assign(aC.Font);
GetObject(xF.Handle, SizeOf(xLF), @xLF);
xLF.lfEscapement := aAngle*10;
xLF.lfOrientation := aAngle*10;
SetBkMode(Handle, TRANSPARENT);
xF.Handle := CreateFontIndirect(xLF);
aC.Font.Assign(xF);
finally
xF.Free;
end;
aC.TextOut(aX, aY, aText);
aC.MoveTo(aX-2, aY);
aC.LineTo(aX+2,aY);
aC.MoveTo(aX,aY-2);
aC.LineTo(aX,aY+2);
end;
begin
xPDF := TPdfDocumentGDI.Create;
xMF := TMetafile.Create;
try
xMF.Width := 500;
xMF.Height := 500;
xMFC := TMetafileCanvas.Create(xMF, 0);
with xMFC do
try
Pen.Color := clBlue;
Brush.Style := bsClear;
Rectangle(0, 0, ClipRect.Right-1, ClipRect.Bottom-1);
Pen.Color := clRed;
xRect := Rect(100, 20, 150, 40);
Rectangle(xRect);
TextRect(xRect, xRect.Left, xRect.Top, 'long text is not clipped');
RotTextWithPoint(xMFC, 'rotated text is misplaced', 90, 100, 200);
RotTextWithPoint(xMFC, 'rotated text is misplaced', 180, 250, 200);
RotTextWithPoint(xMFC, 'rotated text is misplaced', 270, 300, 200);
RotTextWithPoint(xMFC, 'rotated text is misplaced', 45, 350, 200);
finally
Free;
end;
xMF.SaveToFile('a.emf');
xPDF.AddPage;
with xPDF.VCLCanvas do begin
Draw(0, 0, xMF);
end;
xPDF.SaveToFile('a.pdf');
finally
xPDF.Free;
xMF.Free;
end;
end;
1. Rotated text is misplaced - there is a wrong sign in SynPDF.pas on line 6556. (procedure TPdfEnum.TextOut(var R: TEMRExtTextOut);)
Here is the corrected code:
(...)
if font.spec.angle<>0 then begin
a := font.spec.angle*(PI/180);
acos := cos(a);
asin := sin(a);
with Canvas do
SetTextMatrix(acos, asin, -asin, acos,
I2X(R.emrtext.ptlReference.X+Round(W*acos-H*asin)),
I2Y(R.emrtext.ptlReference.Y-Round(H*acos-W*asin))); // <- minus sign REDDWARF
end else
(...)
2. Text is not cropped when using TextRect - unfortunately I did not find the solution on my own. I need this feature for drawing tables (text is clipped in a cell).
EDIT: Maybe I found a solution. This is the code:
procedure TPdfEnum.TextOut(var R: TEMRExtTextOut);
var W,H: integer;
DX: PIntegerArray; // not handled during drawing yet
ASize: single;
tmp: array of WideChar; // R.emrtext is not #0 terminated -> use tmp[]
a, acos, asin: single;
begin
with DC[nDC] do begin
SetLength(tmp,R.emrtext.nChars+1); // faster than WideString for our purpose
move(pointer(PtrUInt(@R)+R.emrtext.offString)^,tmp[0],R.emrtext.nChars*2);
// guess the font size
if font.LogFont.lfHeight<0 then
ASize := -font.LogFont.lfHeight*Canvas.fFactorY else
ASize := font.spec.cell*Canvas.fFactorY;
// ensure this font is selected (very fast if was already selected)
Canvas.SetFont(Canvas.FDoc.FDC,font.LogFont,ASize);
// calculate coordinates
if R.emrtext.offDx=0 then begin
if R.emrtext.fOptions and ETO_GLYPH_INDEX<>0 then
W := 0 else
W := Round(Canvas.UnicodeTextWidth(pointer(tmp))/Canvas.fFactorX);
end else begin
DX := pointer(cardinal(@R)+R.emrtext.offDx);
W := DXTextWidth(DX,R.emrText.nChars);
end;
if font.Align and TA_CENTER=TA_CENTER then
W := W shr 1 else
if font.Align and TA_RIGHT=0 then
W := 0;
if font.Align and TA_BASELINE<>0 then
H := 0 else
if font.Align and TA_BOTTOM<>0 then
H := font.spec.descent else
H := -font.spec.ascent;
// draw background (if any)
if (R.emrtext.fOptions and ETO_OPAQUE<>0) and not brush.null and
(font.spec.angle=0) then begin
// don't handle BkMode, since global to the page, but only specific text
// don't handle rotation here, since should not be used much
NormalizeRect(R.rclBounds);
FillRectangle(R.rclBounds);
end;
// draw text
FillColor := font.color;
{$ifdef USE_UNISCRIBE}
Canvas.RightToLeftText := R.emrtext.fOptions and ETO_RTLREADING<>0;
{$endif}
//REDDWARF BEGIN CLIPPING
with Canvas do begin
GSave;
with R.emrtext.rcl do begin
MoveTo(I2X(Left), I2Y(Top));
LineTo(I2X(Left), I2Y(Bottom-1));
LineTo(I2X(Right-1), I2Y(Bottom-1));
LineTo(I2X(Right-1), I2Y(Top));
end;
ClosePath;
Clip;
NewPath;
end;
//REDDWARF END CLIPPING
Canvas.BeginText;
if font.spec.angle<>0 then begin
a := font.spec.angle*(PI/180);
acos := cos(a);
asin := sin(a);
with Canvas do
SetTextMatrix(acos, asin, -asin, acos,
I2X(R.emrtext.ptlReference.X+Round(W*acos-H*asin)),
I2Y(R.emrtext.ptlReference.Y-Round(H*acos-W*asin))); // <- minus REDDWARF
end else
Canvas.MoveTextPoint(
Canvas.I2X(R.emrtext.ptlReference.X-W),
Canvas.I2Y(R.emrtext.ptlReference.Y-H));
if R.emrtext.fOptions and ETO_GLYPH_INDEX<>0 then
Canvas.ShowGlyph(pointer(tmp),R.emrtext.nChars) else
Canvas.ShowText(pointer(tmp));
Canvas.EndText;
Canvas.GRestore;//REDDWARF CLIPPING RESTORE
end;
end;
Pages: 1