1040 lines
32 KiB
ObjectPascal
1040 lines
32 KiB
ObjectPascal
/// some common User Interface functions and dialogs for mORMot
|
|
// - this unit is a part of the freeware Synopse mORMot framework,
|
|
// licensed under a MPL/GPL/LGPL tri-license; version 1.18
|
|
unit mORMotUILogin;
|
|
|
|
(*
|
|
This file is part of Synopse mORMot framework.
|
|
|
|
Synopse mORMot framework. Copyright (C) 2022 Arnaud Bouchez
|
|
Synopse Informatique - https://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 mORMot framework.
|
|
|
|
The Initial Developer of the Original Code is Arnaud Bouchez.
|
|
|
|
Portions created by the Initial Developer are Copyright (C) 2022
|
|
the Initial Developer. All Rights Reserved.
|
|
|
|
Contributor(s):
|
|
- igors233
|
|
|
|
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.4 - February 8, 2010
|
|
- whole Synopse SQLite3 database framework released under the GNU Lesser
|
|
General Public License version 3, instead of generic "Public Domain"
|
|
|
|
Version 1.5 - February 18, 2010
|
|
- allow to pickup the User Name from a Combo Box
|
|
|
|
Version 1.9
|
|
- improved Delphi 2009/2010 UnicodeString compatibility
|
|
- new PassWord method to enter a password e.g. for Cypher (i.e. with no UserName)
|
|
|
|
Version 1.13
|
|
- now use TMS component pack only if USETMSPACK global conditional is defined
|
|
- introducing new TSynButton, mapping either to the default TBitButton,
|
|
either to TAdvGlowButton (if USETMSPACK conditional is defined)
|
|
- introducing a new TTaskDialog record/object, to access the new Vista/Seven
|
|
TaskDialog, with a fallback dialog written in Delphi under XP
|
|
- use a best available font (Calibri or Tahoma), and useful bitmaps
|
|
|
|
Version 1.15
|
|
- new InputBox global function
|
|
- new QueryMasked parameter to display * in InputBox/InputQuery editor field
|
|
|
|
Version 1.16
|
|
- InputBox function will now focus the input field component by default
|
|
|
|
Version 1.18
|
|
- renamed SQLite3UILogin.pas to mORMotUILogin.pas
|
|
- added ShowLastClientError() and ShowException() functions
|
|
- added TLoginForm.OnIdleProcess and OnIdleProcessForm callback events,
|
|
ready to be assigned to TSQLRestClientURI.OnIdle property
|
|
|
|
*)
|
|
|
|
interface
|
|
|
|
{$I Synopse.inc} // define HASINLINE CPU32 CPU64
|
|
|
|
uses
|
|
{$ifdef MSWINDOWS}
|
|
Windows,
|
|
CommCtrl,
|
|
{$endif}
|
|
{$ifdef FPC}
|
|
//LCLProc, LCLIntf, LCLType,
|
|
LCLType,
|
|
LCLIntf,
|
|
LResources,
|
|
SynTaskDialog, Controls, StdCtrls, ExtCtrls, Classes in '.\Samples\ThirdPartyDemos\Ondrej\SynTaskDialog4Lazarus\SynTaskDialog.pas',
|
|
{$else}
|
|
Consts,
|
|
PsAPI,
|
|
SynTaskDialog,
|
|
{$endif FPC}
|
|
Messages,
|
|
SysUtils,
|
|
Classes,
|
|
Graphics,
|
|
Controls,
|
|
Forms,
|
|
StdCtrls,
|
|
ExtCtrls,
|
|
Buttons,
|
|
{$ifdef USETMSPACK}
|
|
AdvGlowButton,
|
|
TaskDialog,
|
|
TaskDialogEx,
|
|
AdvToolBarStylers,
|
|
AdvToolBar,
|
|
{$endif USETMSPACK}
|
|
SynGdiPlus,
|
|
SynCommons,
|
|
SynTable,
|
|
mORMot,
|
|
mORMotUI;
|
|
|
|
type
|
|
/// Form used to Log User and enter its password
|
|
TLoginForm = class(TForm)
|
|
Label1: TLabel;
|
|
Label2: TLabel;
|
|
Edit2: TEdit;
|
|
Image1: TImage;
|
|
Label3: TLabel;
|
|
Bevel1: TBevel;
|
|
procedure FormCreate(Sender: TObject);
|
|
procedure FormShow(Sender: TObject);
|
|
protected
|
|
Edit: TWinControl;
|
|
function EditText: string;
|
|
class procedure HandleApplicationException(Sender: TObject; E: Exception);
|
|
public
|
|
/// display the Login dialog window
|
|
class function Login(const aTitle, aText: string; var aUserName, aPassWord: string;
|
|
AllowUserNameChange: boolean; const CSVComboValues: string): boolean;
|
|
/// display the password dialog window
|
|
class function PassWord(const aTitle, aText: string; var aPassWord: string): boolean;
|
|
public
|
|
/// TOnIdleSQLRestClient-like event to process Windows Messages
|
|
// - to be assigned e.g. to TSQLRestClientURI.OnIdle property
|
|
// - global OnIdleProcessCursorChangeTimeout variable is used to display
|
|
// the crHourGlass cursor after a given time elapsed
|
|
class procedure OnIdleProcess(Sender: TSynBackgroundThreadAbstract; ElapsedMS: Integer);
|
|
/// TOnIdleSQLRestClient-like event to process Windows Messages and write a
|
|
// temporary form on screen if it takes too long
|
|
// - to be assigned e.g. to TSQLRestClientURI.OnIdle property
|
|
// - global OnIdleProcessCursorChangeTimeout variable is used to display
|
|
// the crHourGlass cursor after a given time elapsed
|
|
// - global OnIdleProcessTemporaryFormTimeout variable is used to display
|
|
// a temporary form after a given time elapsed
|
|
class procedure OnIdleProcessForm(Sender: TSynBackgroundThreadAbstract; ElapsedMS: Integer);
|
|
end;
|
|
|
|
var
|
|
/// define when TLoginForm.OnIdleProcess() has to display the crHourGlass cursor
|
|
// after a given time elapsed, in milliseconds
|
|
// - default is 100 ms
|
|
OnIdleProcessCursorChangeTimeout: integer = 100;
|
|
|
|
/// define when TLoginForm.OnIdleProcessForm() has to display the temporary
|
|
// form after a given time elapsed, in milliseconds
|
|
// - default is 2000 ms, i.e. 2 seconds
|
|
OnIdleProcessTemporaryFormTimeout: integer = 2000;
|
|
|
|
/// define the message text displayed by TLoginForm.OnIdleProcessForm()
|
|
// - default is sOnIdleProcessFormMessage resourcestring, i.e. 'Please wait...'
|
|
OnIdleProcessTemporaryFormMessage: string;
|
|
|
|
|
|
{$ifdef USETMSPACK}
|
|
|
|
/// create a temporary AdvTaskDialog component, with the current style
|
|
function CreateAdvTaskDialog: TAdvTaskDialogEx;
|
|
|
|
/// create a temporary AdvInputTaskDialog component, with the current style
|
|
function CreateAdvInputTaskDialog: TAdvInputTaskDialogEx;
|
|
|
|
/// retrieve the main toolbar from main form
|
|
function GetMainPager: TAdvToolBarPager;
|
|
|
|
/// retrieve the main styler from main form
|
|
function GetMainStyler: TAdvToolBarOfficeStyler;
|
|
|
|
/// set the style for a form and a its buttons
|
|
// - return the toolbar styler found, if any
|
|
function SetStyle(Form: TComponent; Styler: TAdvToolBarOfficeStyler=nil): TAdvToolBarOfficeStyler;
|
|
|
|
{$else}
|
|
|
|
/// set the style for a form and a its buttons
|
|
// - set the Default Font for all components, i.e. Calibri if available
|
|
procedure SetStyle(Form: TComponent);
|
|
|
|
{$endif USETMSPACK}
|
|
|
|
|
|
/// show an (error) message, using a Vista-Style dialog box
|
|
procedure ShowMessage(const Msg: string; Error: boolean=false); overload;
|
|
|
|
/// convert an error message into html compatible equivalency
|
|
// - allow to display < > & correctly
|
|
function HtmlEscape(const Msg: string): string;
|
|
|
|
/// show an (error) message, using a Vista-Style dialog box
|
|
procedure ShowMessage(const Msg, Inst: string; Error: boolean=false); overload;
|
|
|
|
/// show an error dialog box, with the corresponding Client-Side information
|
|
// - retrieve last error message from Client.LastError* values
|
|
function ShowLastClientError(Client: TSQLRestClientURI; const ContextMessage: string='';
|
|
CommonButtons: TCommonButtons=[cbOk]): integer;
|
|
|
|
/// show an error dialog box, corresponding to a specified exception
|
|
function ShowException(E: Exception; CommonButtons: TCommonButtons=[cbOk];
|
|
const ContextMessage: string=''): integer;
|
|
|
|
/// ask the User to choose Yes or No [and Cancel], using a Vista-Style dialog box
|
|
function YesNo(const aQuestion: string; const aConfirm: string =''; withCancel: boolean=true;
|
|
Warning: boolean=false): integer;
|
|
|
|
/// ask the User to enter some string value
|
|
// - if QueryMasked=TRUE, will mask the prompt with '*' chars (e.g. for entering
|
|
// a password)
|
|
function InputQuery(const ACaption, APrompt: string; var Value: string;
|
|
QueryMasked: boolean=false): Boolean;
|
|
|
|
/// ask the User to enter some string value
|
|
// - if QueryMasked=TRUE, will mask the prompt with '*' chars (e.g. for entering
|
|
// a password)
|
|
function InputBox(const ACaption, APrompt, ADefault: string; QueryMasked: boolean=false): string;
|
|
|
|
/// ask the User to select one item from an array of strings
|
|
// - return the selected index, -1 if Cancel button was pressed
|
|
function InputSelect(const ACaption, APrompt, AItemsText, ASelectedText: string): integer;
|
|
|
|
/// ask the User to select one enumerate item
|
|
// - use internally TEnumType.GetCaption() to retrieve the text to be displayed
|
|
// - Index must be an instance of this enumeration type (internaly mapped to a PByte)
|
|
function InputSelectEnum(const ACaption, APrompt: string; EnumTypeInfo: PTypeInfo;
|
|
var Index): boolean;
|
|
|
|
/// ask the User to choose between some Commands
|
|
// - return the selected command index, starting numerotation at 100
|
|
function Choose(const aTitle, aContent, aFooter: string; const Commands: array of string;
|
|
aFooterIcon: TTaskDialogFooterIcon=tfiInformation): integer; overload;
|
|
|
|
/// ask the User to choose between some Commands
|
|
// - return the selected command index, starting numerotation at 100
|
|
// - this overloaded function expect the Content and the Commands to be
|
|
// supplied as CSV string (Content as first CSV, then commands)
|
|
function Choose(const aTitle, aCSVContent: string): integer; overload;
|
|
|
|
resourcestring
|
|
sClientServerError = 'An error occured during Client-Server process';
|
|
|
|
sOnIdleProcessFormMessage = 'Please wait...';
|
|
|
|
|
|
/// ensure that the program is launched once
|
|
// - the main project .dpr source file must contain:
|
|
// !begin
|
|
// ! Application.Initialize;
|
|
// ! EnsureSingleInstance; // program is launched once
|
|
// ! Application.CreateForm(TMainForm, MainForm);
|
|
// ! ....
|
|
procedure EnsureSingleInstance;
|
|
|
|
type
|
|
PTPanel = ^TPanel;
|
|
|
|
/// popup a temporary form with a message over all forms
|
|
// - caller should execute result.Free and reset the cursor if needed
|
|
function CreateTempForm(const aCaption: string;
|
|
aPanelReference: PTPanel=nil; ScreenCursorHourGlass: boolean=false;
|
|
aCaptionColor: integer=clNavy; aCaptionSize: integer=12): TForm;
|
|
|
|
|
|
|
|
implementation
|
|
|
|
{$ifdef FPC}
|
|
{$R *.lfm}
|
|
{$else}
|
|
{$R *.dfm}
|
|
{$endif}
|
|
|
|
{$R SQLite3UILogin.res}
|
|
|
|
/// popup a temporary form with a message over all forms
|
|
function CreateTempForm(const aCaption: string;
|
|
aPanelReference: PTPanel=nil; ScreenCursorHourGlass: boolean=false;
|
|
aCaptionColor: integer=clNavy; aCaptionSize: integer=12): TForm;
|
|
var P: TPanel;
|
|
begin
|
|
if ScreenCursorHourGlass then
|
|
Screen.Cursor := crHourGlass;
|
|
Result := TForm.Create(nil);
|
|
Result.Width := 400; Result.Height := 100;
|
|
Result.Position := poScreenCenter;
|
|
Result.FormStyle := fsStayOnTop;
|
|
Result.BorderStyle := bsNone;
|
|
P := TPanel.Create(Result);
|
|
with P do begin
|
|
Color := clWhite;
|
|
BorderStyle := bsSingle;
|
|
Parent := Result;
|
|
Font.Name := 'Tahoma'; Font.Size := aCaptionSize;
|
|
Font.Color := aCaptionColor; Font.Style := [fsBold];
|
|
Caption := aCaption;
|
|
Align := alClient;
|
|
end;
|
|
if aPanelReference<>nil then
|
|
aPanelReference^ := P;
|
|
{ with TImage.Create(Result) do begin
|
|
Parent := P;
|
|
Transparent := true;
|
|
Picture.Bitmap.LoadFromResourceName(hInstance,'ROC');
|
|
SetBounds((330-156)div 2,20,156,73);
|
|
end;
|
|
with TLabel.Create(Result) do begin
|
|
Parent := P;
|
|
Font.Name := 'Tahoma'; Font.Size := 11;
|
|
Font.Color := clNavy; Font.Style := [fsBold];
|
|
Alignment := taCenter;
|
|
Caption := aCaption;
|
|
SetBounds(0,120,330,100);
|
|
end; }
|
|
Result.Show;
|
|
Application.ProcessMessages;
|
|
end;
|
|
|
|
procedure ShowMessage(const Msg: string; Error: boolean=false);
|
|
begin
|
|
if Error then
|
|
ShowMessage(HtmlEscape(Msg),SMsgDlgError,Error) else
|
|
ShowMessage(HtmlEscape(Msg),SMsgDlgInformation,Error);
|
|
end;
|
|
|
|
function HtmlEscape(const Msg: string): string;
|
|
{$ifdef USETMSPACK}
|
|
var i: integer;
|
|
ins: string;
|
|
{$endif}
|
|
begin
|
|
result := Msg;
|
|
{$ifdef USETMSPACK}
|
|
for i := length(Msg) downto 1 do begin
|
|
case Msg[i] of
|
|
'"': ins := '"';
|
|
'&': ins := '&';
|
|
'<': ins := '<';
|
|
'>': ins := '>';
|
|
else Continue;
|
|
end;
|
|
result[i] := ';';
|
|
insert(ins,result,i);
|
|
end;
|
|
{$endif}
|
|
end;
|
|
|
|
|
|
{$ifdef USETMSPACK}
|
|
|
|
function CreateAdvTaskDialog: TAdvTaskDialogEx;
|
|
var Style: TAdvToolBarOfficeStyler;
|
|
begin
|
|
result := TAdvTaskDialogEx.Create(Application);
|
|
if Application.MainForm=nil then
|
|
result.Title := Application.Title else
|
|
result.Title := Application.MainForm.Caption;
|
|
result.Options := result.Options+[doHyperlinks];
|
|
result.NonNativeDialog := nndAlways; // we need hyperlinks=HTML -> non native!
|
|
Style := GetMainStyler;
|
|
if Style<>nil then
|
|
result.Appearance := Style.GlowButtonAppearance;
|
|
end;
|
|
|
|
function Choose(const aTitle, aContent, aFooter: string; const Commands: array of string;
|
|
aFooterIcon: TTaskDialogFooterIcon=tfiInformation): integer;
|
|
var i: integer;
|
|
begin
|
|
with TAdvTaskDialog(CreateAdvTaskDialog) do
|
|
try
|
|
Instruction := aTitle;
|
|
Content := aContent;
|
|
Icon := tiQuestion;
|
|
CustomButtons.Clear;
|
|
for i := 0 to high(Commands) do
|
|
CustomButtons.Add(Commands[i]);
|
|
DefaultButton := 100;
|
|
Options := Options+[doCommandLinks];
|
|
if aFooter<>'' then begin
|
|
FooterIcon := aFooterIcon;
|
|
if aFooterIcon=tfiWarning then
|
|
Footer := '<b>'+SMsgDlgWarning+'</b>:'#13+aFooter else
|
|
Footer := aFooter;
|
|
end;
|
|
result := Execute;
|
|
finally
|
|
Free;
|
|
end;
|
|
end;
|
|
|
|
type
|
|
/// we definitively NEED these properties
|
|
TAdvInputTaskDialogPublished = class(TAdvInputTaskDialogEx)
|
|
published
|
|
property Options;
|
|
property NonNativeDialog;
|
|
end;
|
|
|
|
function CreateAdvInputTaskDialog: TAdvInputTaskDialogEx;
|
|
var Style: TAdvToolBarOfficeStyler;
|
|
begin
|
|
result := TAdvInputTaskDialogPublished.Create(Application);
|
|
result.Title := Application.MainForm.Caption;
|
|
TAdvInputTaskDialogPublished(result).Options := TAdvInputTaskDialogPublished(result).Options+[doHyperlinks];
|
|
TAdvInputTaskDialogPublished(result).NonNativeDialog := nndAlways; // we need hyperlinks=HTML -> non native!
|
|
Style := GetMainStyler;
|
|
if Style<>nil then
|
|
result.Appearance := Style.GlowButtonAppearance;
|
|
end;
|
|
|
|
function InputQuery(const ACaption, APrompt: string; var Value: string;
|
|
QueryMasked: boolean=false): Boolean;
|
|
begin
|
|
with CreateAdvInputTaskDialog do
|
|
try
|
|
Instruction := ACaption;
|
|
Content := APrompt;
|
|
InputType := itEdit;
|
|
CommonButtons := [cbOk,cbCancel];
|
|
InputText := Value;
|
|
result := false;
|
|
if Execute<>mrOk then
|
|
exit;
|
|
result := true;
|
|
Value := InputText;
|
|
finally
|
|
Free;
|
|
end;
|
|
end;
|
|
|
|
function InputSelect(const ACaption, APrompt, AItemsText, ASelectedText: string): integer;
|
|
var Items: TStringList;
|
|
begin
|
|
result := -1;
|
|
if AItemsText<>'' then
|
|
with CreateAdvInputTaskDialog do
|
|
try
|
|
Instruction := ACaption;
|
|
Content := APrompt;
|
|
InputType := itComboList;
|
|
CommonButtons := [cbOk,cbCancel];
|
|
Items := TStringList.Create;
|
|
try
|
|
Items.Text := AItemsText;
|
|
InputItems := Items;
|
|
InputText := ASelectedText;
|
|
if Execute<>mrOk then
|
|
exit;
|
|
result := Items.IndexOf(InputText);
|
|
finally
|
|
Items.Free;
|
|
end;
|
|
finally
|
|
Free;
|
|
end;
|
|
end;
|
|
|
|
function Choose(const aTitle, aCSVContent: string): integer; overload;
|
|
var Commands: array of string;
|
|
Content: string;
|
|
P: PChar;
|
|
begin
|
|
P := pointer(aCSVContent);
|
|
Content := GetNextItemString(P);
|
|
while P<>nil do begin
|
|
SetLength(Commands,length(Commands)+1);
|
|
Commands[high(Commands)] := GetNextItemString(P);
|
|
end;
|
|
result := Choose(aTitle,Content,'',Commands);
|
|
end;
|
|
|
|
{$else}
|
|
|
|
function Choose(const aTitle, aContent, aFooter: string; const Commands: array of string;
|
|
aFooterIcon: TTaskDialogFooterIcon=tfiInformation): integer;
|
|
var Task: TTaskDialog;
|
|
i: integer;
|
|
begin
|
|
Task.Inst := aTitle;
|
|
Task.Content := aContent;
|
|
Task.Footer := aFooter;
|
|
for i := 0 to high(Commands) do
|
|
Task.Buttons := Task.Buttons+Commands[i]+#10;
|
|
result := Task.Execute([],100,[tdfUseCommandLinks],tiQuestion,aFooterIcon);
|
|
if result<100 then
|
|
result := -1 else
|
|
dec(result,100);
|
|
end;
|
|
|
|
function InputQuery(const ACaption, APrompt: string; var Value: string;
|
|
QueryMasked: boolean=false): Boolean;
|
|
const FLAGS: array[boolean] of TTaskDialogFlags = (
|
|
[tdfQuery,tdfQueryFieldFocused],[tdfQuery,tdfQueryMasked,tdfQueryFieldFocused]);
|
|
var Task: TTaskDialog;
|
|
begin
|
|
Task.Inst := ACaption;
|
|
Task.Content := APrompt;
|
|
Task.Query := Value;
|
|
result := Task.Execute([cbOk,cbCancel],0,FLAGS[QueryMasked],tiQuestion)=mrOk;
|
|
if result then
|
|
Value := Task.Query;
|
|
end;
|
|
|
|
function InputSelect(const ACaption, APrompt, AItemsText, ASelectedText: string): integer;
|
|
var Task: TTaskDialog;
|
|
begin
|
|
result := -1;
|
|
if AItemsText='' then
|
|
exit;
|
|
Task.Inst := ACaption;
|
|
Task.Content := APrompt;
|
|
Task.Selection := AItemsText;
|
|
Task.Query := ASelectedText;
|
|
if Task.Execute([cbOk,cbCancel],0,[],tiQuestion)=mrOk then
|
|
result := Task.SelectionRes;
|
|
end;
|
|
|
|
function Choose(const aTitle, aCSVContent: string): integer; overload;
|
|
var Task: TTaskDialog;
|
|
P: PChar;
|
|
begin
|
|
Task.Inst := aTitle;
|
|
P := pointer(aCSVContent);
|
|
Task.Content := GetNextItemString(P);
|
|
while P<>nil do
|
|
Task.Buttons := Task.Buttons+GetNextItemString(P)+#10;
|
|
result := Task.Execute([],100,[tdfUseCommandLinks],tiQuestion);
|
|
if result<100 then
|
|
result := -1 else
|
|
dec(result,100);
|
|
end;
|
|
|
|
{$endif USETMSPACK}
|
|
|
|
function InputBox(const ACaption, APrompt, ADefault: string; QueryMasked: boolean=false): string;
|
|
begin
|
|
result := ADefault;
|
|
if not InputQuery(ACaption,APrompt,Result,QueryMasked) then
|
|
result := ADefault;
|
|
end;
|
|
|
|
function InputSelectEnum(const ACaption, APrompt: string; EnumTypeInfo: PTypeInfo;
|
|
var Index): boolean;
|
|
var i: integer;
|
|
begin
|
|
result := false;
|
|
if (EnumTypeInfo<>nil) and (EnumTypeInfo^.Kind=tkEnumeration) then begin
|
|
i := InputSelect(ACaption,APrompt,EnumTypeInfo^.EnumBaseType^.GetCaptionStrings,
|
|
EnumTypeInfo^.EnumBaseType^.GetCaption(PByte(@Index)^));
|
|
if i>=0 then begin
|
|
PByte(@Index)^ := i;
|
|
result := true;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
procedure ShowMessage(const Msg, Inst: string; Error: boolean=false);
|
|
const
|
|
IconError: array[boolean] of TTaskDialogIcon = (tiInformation, tiError);
|
|
{$ifndef USETMSPACK}
|
|
var Task: TTaskDialog;
|
|
{$endif}
|
|
begin
|
|
{$ifdef USETMSPACK}
|
|
with CreateAdvTaskDialog do
|
|
try
|
|
Instruction := Inst;
|
|
Icon := IconError[Error];
|
|
Content := Msg;
|
|
CommonButtons := [cbOk];
|
|
Execute;
|
|
finally
|
|
Free;
|
|
end;
|
|
{$else}
|
|
Task.Inst := Inst;
|
|
Task.Content := Msg;
|
|
Task.Execute([cbOK],mrOk,[],IconError[Error]);
|
|
{$endif}
|
|
end;
|
|
|
|
function ShowLastClientError(Client: TSQLRestClientURI; const ContextMessage: string='';
|
|
CommonButtons: TCommonButtons=[cbOk]): integer;
|
|
var details: string;
|
|
{$ifndef USETMSPACK}
|
|
Task: TTaskDialog;
|
|
{$endif}
|
|
begin
|
|
if Client<>nil then begin
|
|
if Client.LastErrorMessage<>'' then
|
|
details := UTF8ToString(Client.LastErrorMessage)+#13#10;
|
|
if Client.LastErrorException<>nil then
|
|
details := Format('%sException %s raised - ErrorCode=%d',
|
|
[details,Client.LastErrorException.ClassName,Client.LastErrorCode]);
|
|
end;
|
|
{$ifdef USETMSPACK}
|
|
with CreateAdvTaskDialog do
|
|
try
|
|
Instruction := sClientServerError;
|
|
Content := ContextMessage;
|
|
Icon := tiError;
|
|
CommonButtons := [cbOk];
|
|
result := Execute;
|
|
finally
|
|
Free;
|
|
end;
|
|
{$else}
|
|
if ContextMessage='' then
|
|
Task.Content := sClientServerError else
|
|
Task.Content := ContextMessage;
|
|
Task.Info := details;
|
|
result := Task.Execute([cbOK],mrOk,[],tiError);
|
|
{$endif}
|
|
end;
|
|
|
|
function ShowException(E: Exception; CommonButtons: TCommonButtons;
|
|
const ContextMessage: string): integer;
|
|
var details: string;
|
|
{$ifndef USETMSPACK}
|
|
Task: TTaskDialog;
|
|
{$endif}
|
|
begin
|
|
if E=nil then begin
|
|
ShowMessage(ContextMessage,true);
|
|
result := mrNone;
|
|
exit;
|
|
end;
|
|
details := UTF8ToString(ObjectToJSONDebug(E));
|
|
{$ifdef USETMSPACK}
|
|
with CreateAdvTaskDialog do
|
|
try
|
|
Instruction := sClientServerError;
|
|
Content := ContextMessage;
|
|
Icon := tiError;
|
|
CommonButtons := [cbOk];
|
|
result := Execute;
|
|
finally
|
|
Free;
|
|
end;
|
|
{$else}
|
|
Task.Content := E.Message;
|
|
Task.Inst := ContextMessage;
|
|
Task.Info := details;
|
|
result := Task.Execute(CommonButtons,mrOk,[],tiError);
|
|
{$endif}
|
|
end;
|
|
|
|
function YesNo(const aQuestion, aConfirm: string; withCancel: boolean; Warning: boolean): integer;
|
|
var Confirm: string;
|
|
{$ifndef USETMSPACK}
|
|
Task: TTaskDialog;
|
|
{$endif}
|
|
const
|
|
IconWarning: array[boolean] of TTaskDialogIcon = (
|
|
tiQuestion, tiWarning);
|
|
BtsCancel: array[boolean] of TCommonButtons = (
|
|
[cbYes, cbNo], [cbYes, cbNo, cbCancel]);
|
|
begin
|
|
if aConfirm='' then
|
|
Confirm := SMsgDlgConfirm else
|
|
Confirm := aConfirm;
|
|
{$ifdef USETMSPACK}
|
|
with CreateAdvTaskDialog do
|
|
try
|
|
Instruction := Confirm;
|
|
Icon := IconWarning[Warning];
|
|
Content := aQuestion;
|
|
CommonButtons := BtsCancel[withCancel];
|
|
result := Execute;
|
|
finally
|
|
Free;
|
|
end;
|
|
{$else}
|
|
Task.Inst := Confirm;
|
|
Task.Content := aQuestion;
|
|
result := Task.Execute(BtsCancel[withCancel],0,[],IconWarning[Warning]);
|
|
{$endif}
|
|
end;
|
|
|
|
{$ifdef Crazy}
|
|
function FileNameFromWndMatch(const Wnd: HWnd; const AppFileName: string): boolean;
|
|
var ProcessID: DWORD;
|
|
hProc: THandle;
|
|
tmp: array[0..MAX_PATH] of char;
|
|
begin
|
|
GetWindowThreadProcessId(Wnd, @ProcessId);
|
|
hProc := OpenProcess(PROCESS_QUERY_INFORMATION or PROCESS_VM_READ, false, ProcessId);
|
|
if hProc <> 0 then begin
|
|
{$ifdef FPC}
|
|
if GetModuleFileName(hProc, tmp, MAX_PATH) = 0 then
|
|
{$else}
|
|
if GetModuleFileNameEx(hProc, 0, tmp, MAX_PATH) = 0 then
|
|
{$endif}
|
|
tmp[0] := #0;
|
|
CloseHandle(hProc);
|
|
result := ExtractFileName(string(tmp)) = AppFileName;
|
|
end else
|
|
result := false;
|
|
end;
|
|
{$endif Crazy}
|
|
|
|
procedure EnsureSingleInstance;
|
|
var Wnd: HWnd;
|
|
ToFindClass, WndClass, AppFileName: string;
|
|
tmp: array[byte] of char;
|
|
function FileNameFromWndMatch: boolean;
|
|
var ProcessID: DWORD;
|
|
hProc: THandle;
|
|
tmp: array[0..MAX_PATH] of char;
|
|
begin
|
|
GetWindowThreadProcessId(Wnd, @ProcessId);
|
|
hProc := OpenProcess(PROCESS_QUERY_INFORMATION or PROCESS_VM_READ, false, ProcessId);
|
|
if hProc <> 0 then begin
|
|
{$ifdef FPC}
|
|
if GetModuleFileName(hProc, tmp, MAX_PATH) = 0 then
|
|
{$else}
|
|
if GetModuleFileNameEx(hProc, 0, tmp, MAX_PATH) = 0 then
|
|
{$endif}
|
|
tmp[0] := #0;
|
|
CloseHandle(hProc);
|
|
result := ExtractFileName(string(tmp)) = AppFileName;
|
|
end else
|
|
result := false;
|
|
end;
|
|
begin
|
|
if Application = nil then
|
|
exit;
|
|
{ Try and create a semaphore. If we succeed, then check if the semaphore was
|
|
already present. If it was then a previous instance is floating around.
|
|
Note the OS will free the returned semaphore handle when the app shuts
|
|
so we can forget about it }
|
|
AppFileName := ExtractFileName(ExeVersion.ProgramFileName);
|
|
if (CreateSemaphore(nil, 0, 1, Pointer(AppFileName)) <> 0) and
|
|
(GetLastError = ERROR_ALREADY_EXISTS) then begin
|
|
if GetClassName({$ifdef FPC}Application.MainFormHandle{$else}Application.Handle{$endif}, tmp, high(tmp)) = 0 then
|
|
exit;
|
|
ToFindClass := tmp;
|
|
Wnd := GetWindow({$ifdef FPC}Application.MainFormHandle{$else}Application.Handle{$endif}, GW_HWNDFIRST);
|
|
while Wnd <> 0 do begin
|
|
{ Look for the other TApplication window out there }
|
|
if Wnd <> {$ifdef FPC}Application.MainFormHandle{$else}Application.Handle{$endif} then begin
|
|
{ Check if got the same class and filename }
|
|
if GetClassName(Wnd, tmp, high(tmp)) = 0 then
|
|
tmp[0] := #0;
|
|
WndClass := tmp;
|
|
if (ToFindClass = WndClass) and FileNameFromWndMatch then begin
|
|
{ This technique is used by the VCL: post a message then bring the
|
|
window to the top, before the message gets processed }
|
|
PostMessage(Wnd, WM_SYSCOMMAND, SC_RESTORE, 0);
|
|
SetForegroundWindow(Wnd);
|
|
break;
|
|
end
|
|
end;
|
|
Wnd := GetWindow(Wnd, GW_HWNDNEXT);
|
|
end;
|
|
AppFileName := ''; // avoid memory leak due to Halt brutal execution
|
|
WndClass := '';
|
|
ToFindClass := '';
|
|
Halt; // Stop this new instance
|
|
end
|
|
end;
|
|
|
|
procedure TLoginForm.FormCreate(Sender: TObject);
|
|
var P: TSynPicture;
|
|
begin
|
|
SetStyle(self);
|
|
TSynButton.CreateKind(Self,cbOK,136,163,75,30).Anchors := [akLeft,akBottom];
|
|
TSynButton.CreateKind(Self,cbCancel,224,163,75,30).Anchors := [akLeft,akBottom];
|
|
P := TSynPicture.Create;
|
|
try
|
|
P.LoadFromResourceName(HInstance,'UILogin'); // SQLite3uilogin.png
|
|
Image1.Picture.Assign(P);
|
|
finally
|
|
P.Free;
|
|
end;
|
|
end;
|
|
|
|
class function TLoginForm.PassWord(const aTitle, aText: string;
|
|
var aPassWord: string): boolean;
|
|
var LoginForm: TLoginForm;
|
|
begin
|
|
Application.CreateForm(TLoginForm,LoginForm);
|
|
try
|
|
with LoginForm do begin
|
|
Label1.Hide;
|
|
Label3.Caption := aText;
|
|
Label3.Width := LoginForm.ClientWidth;
|
|
Bevel1.Hide;
|
|
ClientHeight := 180;
|
|
LoginForm.Caption := ' '+aTitle;
|
|
result := (LoginForm.ShowModal=mrOk);
|
|
if result then
|
|
aPassWord := SysUtils.trim(LoginForm.Edit2.Text);
|
|
end;
|
|
finally
|
|
LoginForm.Free;
|
|
end;
|
|
end;
|
|
|
|
class function TLoginForm.Login(const aTitle, aText: string; var aUserName, aPassWord: string;
|
|
AllowUserNameChange: boolean; const CSVComboValues: string): boolean;
|
|
var T: string;
|
|
i: integer;
|
|
LoginForm: TLoginForm;
|
|
begin
|
|
Application.CreateForm(TLoginForm,LoginForm);
|
|
with LoginForm do
|
|
try
|
|
Label3.Caption := aText;
|
|
Label3.Width := LoginForm.ClientWidth;
|
|
Bevel1.Visible := aText<>'';
|
|
Height := 240+Label3.Height;
|
|
Bevel1.Height := Label3.Height;
|
|
if not AllowUserNameChange or (CSVComboValues='') then begin
|
|
Edit := TEdit.Create(LoginForm);
|
|
Edit.Parent := LoginForm;
|
|
TEdit(Edit).Text := aUserName;
|
|
end else begin
|
|
Edit := TComboBox.Create(LoginForm);
|
|
Edit.Parent := LoginForm;
|
|
with TComboBox(Edit) do begin
|
|
Text := aUserName;
|
|
Style := csDropDownList;
|
|
Items.Text := StringReplace(CSVComboValues,',',#13#10,[rfReplaceAll]);
|
|
ItemIndex := Items.IndexOf(aUserName);
|
|
end;
|
|
end;
|
|
{$ifdef FPC}
|
|
// strange, but needed
|
|
Edit.SetBounds(104,Height-Edit2.Height-84,193,22);
|
|
{$else}
|
|
Edit.SetBounds(104,Edit2.Top-32,193,22);
|
|
{$endif}
|
|
Edit.Anchors := [akLeft,akBottom];
|
|
Edit.Enabled := AllowUserNameChange;
|
|
Edit2.Text := '';
|
|
if aTitle='' then
|
|
if Application.MainForm=nil then
|
|
T := Application.Title else
|
|
T := Application.MainForm.Caption else begin
|
|
T := aTitle;
|
|
for i := 1 to length(T) do
|
|
if T[i]<' ' then
|
|
T[i] := ' ';
|
|
end;
|
|
Caption := ' '+T;
|
|
result := (ShowModal=mrOk);
|
|
if result then begin
|
|
aPassWord := SysUtils.trim(Edit2.Text);
|
|
if Edit.Enabled then
|
|
aUserName := SysUtils.trim(EditText);
|
|
end;
|
|
finally
|
|
LoginForm.Free;
|
|
end;
|
|
end;
|
|
|
|
{$ifdef USETMSPACK}
|
|
|
|
function GetMainPager: TAdvToolBarPager;
|
|
var C: integer;
|
|
begin
|
|
with Application.MainForm do
|
|
for C := 0 to ComponentCount-1 do begin
|
|
result := pointer(Components[C]);
|
|
if result.InheritsFrom(TAdvToolBarPager) then
|
|
exit;
|
|
end;
|
|
result := nil;
|
|
end;
|
|
|
|
var
|
|
fMainStyler: TAdvToolBarOfficeStyler = nil;
|
|
|
|
function GetMainStyler: TAdvToolBarOfficeStyler;
|
|
var C: integer;
|
|
begin
|
|
result := fMainStyler;
|
|
if (result<>nil) or (Application.MainForm=nil) then
|
|
exit;
|
|
with Application.MainForm do
|
|
for C := 0 to ComponentCount-1 do begin
|
|
result := pointer(Components[C]);
|
|
if result.InheritsFrom(TAdvToolBarOfficeStyler) then begin
|
|
fMainStyler := result;
|
|
exit;
|
|
end;
|
|
end;
|
|
result := nil;
|
|
end;
|
|
|
|
function SetStyle(Form: TComponent; Styler: TAdvToolBarOfficeStyler=nil): TAdvToolBarOfficeStyler;
|
|
var i: integer;
|
|
C: TComponent;
|
|
begin
|
|
if Styler=nil then
|
|
result := GetMainStyler else
|
|
result := Styler;
|
|
if (result=nil) or (Form=nil) then
|
|
exit;
|
|
for i := 0 to Form.ComponentCount-1 do begin
|
|
C := Form.Components[i];
|
|
{ if C.InheritsFrom(TAdvToolBarOfficeStyler) then // buggy: TMS :(
|
|
TAdvToolBarOfficeStyler(C).Assign(Style) else }
|
|
if C.InheritsFrom(TAdvGlowButton) then
|
|
TAdvGlowButton(C).Appearance := result.GlowButtonAppearance;
|
|
SetStyle(C,result);
|
|
end;
|
|
if Form.InheritsFrom(TCustomForm) then
|
|
TCustomForm(Form).Color := result.PageAppearance.Color;
|
|
if Form.InheritsFrom(TAdvGlowButton) then
|
|
TAdvGlowButton(Form).Appearance := result.GlowButtonAppearance;
|
|
end;
|
|
|
|
{$else}
|
|
|
|
procedure SetStyle(Form: TComponent);
|
|
var i, f: integer;
|
|
C: TComponent;
|
|
Ctrl: TControl absolute C;
|
|
CL: TClass;
|
|
P: PPropInfo;
|
|
Obj: TObject;
|
|
begin
|
|
if Form=nil then
|
|
exit;
|
|
// set form font to global Default Font
|
|
if Form.InheritsFrom(TCustomForm) then
|
|
TCustomForm(Form).Font := DefaultFont;
|
|
// for any component with ParentFont=false, force Calibri if necessary
|
|
if DefaultFont.Name='Calibri' then
|
|
for i := 0 to Form.ComponentCount-1 do begin
|
|
C := Form.Components[i];
|
|
if C.InheritsFrom(TControl) then
|
|
if not TButton(C).ParentFont then begin // trick to access TControl.FParentFont
|
|
CL := PPointer(C)^;
|
|
while (CL<>nil) and (CL<>TComponent) and (CL<>TObject) do begin
|
|
for f := 1 to InternalClassPropInfo(CL,P) do begin
|
|
with P^.PropType^{$ifndef HASDIRECTTYPEINFO}^{$endif} do
|
|
if (Kind=tkClass) and ClassType^.InheritsFrom(TFont) then begin
|
|
Obj := P^.GetObjProp(C);
|
|
if Obj<>nil then
|
|
with TFont(Obj) do
|
|
if Name<>DefaultFont.Name then begin
|
|
Name := DefaultFont.Name;
|
|
Height := Height+(Height div 5);
|
|
end;
|
|
end;
|
|
P := P^.Next;
|
|
end;
|
|
CL := CL.ClassParent; // handle parent published properties
|
|
end;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
{$endif USETMSPACK}
|
|
|
|
procedure TLoginForm.FormShow(Sender: TObject);
|
|
begin
|
|
if (Edit<>nil) and Edit.Enabled and (EditText='') then
|
|
Edit.SetFocus else
|
|
Edit2.SetFocus;
|
|
SetStyle(self);
|
|
end;
|
|
|
|
function TLoginForm.EditText: string;
|
|
begin
|
|
if Edit.InheritsFrom(TComboBox) then
|
|
result := TComboBox(Edit).Text else
|
|
result := TEdit(Edit).Text;
|
|
result := SysUtils.trim(result);
|
|
end;
|
|
|
|
class procedure TLoginForm.HandleApplicationException(Sender: TObject; E: Exception);
|
|
begin // so that exception will be shown with new User Interface
|
|
ShowMessage(E.Message,true);
|
|
end;
|
|
|
|
var
|
|
OnIdleProcessCursor: TCursor = crNone;
|
|
OnIdleProcessTempForm: TForm;
|
|
|
|
class procedure TLoginForm.OnIdleProcess(Sender: TSynBackgroundThreadAbstract; ElapsedMS: Integer);
|
|
begin
|
|
Application.ProcessMessages;
|
|
if (ElapsedMS>OnIdleProcessCursorChangeTimeout) and (OnIdleProcessCursor=crNone) then begin
|
|
OnIdleProcessCursor := Screen.Cursor;
|
|
Screen.Cursor := crHourGlass;
|
|
end else
|
|
if (ElapsedMS<0) and (OnIdleProcessCursor<>crNone) then begin
|
|
Screen.Cursor := OnIdleProcessCursor;
|
|
OnIdleProcessCursor := crNone;
|
|
end;
|
|
end;
|
|
|
|
class procedure TLoginForm.OnIdleProcessForm(Sender: TSynBackgroundThreadAbstract; ElapsedMS: Integer);
|
|
var msg: string;
|
|
begin
|
|
TLoginForm.OnIdleProcess(Sender,ElapsedMS);
|
|
if (ElapsedMS>OnIdleProcessTemporaryFormTimeout) and
|
|
not Assigned(OnIdleProcessTempForm) then begin
|
|
if OnIdleProcessTemporaryFormMessage='' then
|
|
msg := sOnIdleProcessFormMessage else
|
|
msg := OnIdleProcessTemporaryFormMessage;
|
|
OnIdleProcessTempForm := CreateTempForm(msg);
|
|
end else
|
|
if (ElapsedMS<0) and Assigned(OnIdleProcessTempForm) then
|
|
FreeAndNil(OnIdleProcessTempForm);
|
|
end;
|
|
|
|
initialization
|
|
Gdip.RegisterPictures; // will initialize the Gdip library if necessary
|
|
Application.OnException := TLoginForm.HandleApplicationException;
|
|
|
|
end.
|
|
|
|
|