xtool/contrib/mORMot/SQLite3/mORMotUI.pas

2371 lines
82 KiB
ObjectPascal

/// Grid to display database content for mORMot
// - this unit is a part of the freeware mORMot framework,
// licensed under a MPL/GPL/LGPL tri-license; version 1.18
unit mORMotUI;
(*
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):
- kevinday
- MartinEckes
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 *****
Fill a TDrawGrid with data from our database engine
*****************************************************
- associate TSQLTable content to a TDrawGrid
- display UTF-8 values with true unicode characters
- column size is calculated from data size
- field sort by left/right arrow keys or clicking on first row
- incremental key lookup for direct search inside displayed values
- Ctrl + click on a cell to display its full unicode content
- ID column can be hidden on demand, but IDs still remaining in memory
Initial version: 2008 March, by Arnaud Bouchez
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 19, 2010
- User Interface Query action implementation
Version 1.8
- added TSynIntegerLabeledEdit component (e.g. used in SQLite3UIOptions)
Version 1.9
- improved Delphi 2009/2010 UnicodeString compatibility
- fix some issues, and complete implementation of marking from time elapsed
Version 1.9.2
- new ForceRefresh parameter to the TSQLTableToGrid.Refresh method
Version 1.13
- now compiles with Delphi 6
- new TSQLTableToGrid.SelectedRecordCreate method
- added generic TSynLabeledEdit to handle Integer, Int64, Currency and
double kind of values
Version 1.14
- fixed issue with MaxValue if RangeChecking not enabled
Version 1.15
- compatibility with Delphi XE2
- new TSQLTableToGrid.SetFieldFixedWidth method
- new TSQLTableToGrid.FieldTitleTruncatedNotShownAsHint property
- fixed issue on TDrawGrid events when TSQLTableToGrid is destroyed
Version 1.16
- new FillStringGrid() function, ready to fill a regular TStringGrid
- includes new TSynAnsiConvert classes for handling Ansi charsets
Version 1.17
- global functions AddApplicationToFirewall() and AddPortToFirewall()
are now compatible with Windows XP, Vista and Seven - renamed on purpose
Version 1.18
- renamed SQLite3UI.pas to mORMotUI.pas
- introducing TSQLPropInfo* classes to decouple ORM definitions from RTTI
- fix TSynLabeledEdit.IsValid for currency kind of values
- ensure TSQLTableToGrid.Create() uses TDrawGrid font to compute row height
- new TSQLTableToGrid.Aligned[] property: replace former SetCentered() method,
allowing right cell alignment - and associated SetAligned() /
SetAlignedByType() methods as expected by feature request [749dfbdb6a]
- new TSQLTableToGrid.CustomFormat[] format, for custom numerical or
date/time mask - and associated SetCustomFormatByType() methods
- added TSQLTableToGrid.HeaderCheckboxSelectsInsteadOfSort property to
toggle global selection instead of sorting the marked items first - see
feature request [a41b5dd805]
- added AutoResizeColumns optional parameter to TSQLTableToGrid.Refresh() -
see feature request [c2e1ff324b]
- added TSQLTableToGrid.OnSort event handler - see request [bffff9b4c3]
- added some values to TSQLAction - thanks kevinday for the patch
- get rid of mORMoti18n dependency
*)
interface
{$I Synopse.inc}
uses
{$ifdef MSWINDOWS}
Windows,
{$endif MSWINDOWS}
{$ifdef FPC}
LCLType, LCLProc, LCLIntf, LMessages,
{$endif FPC}
Types, DateUtils,
SynCommons, mORMot,
SysUtils, Classes, Messages, Variants,
{$ifdef WITHUXTHEME}
Themes,
{$endif WITHUXTHEME}
Graphics, StdCtrls, Controls, Grids, Buttons, ExtCtrls, Forms;
type
/// a THintWindow descendant, with an internal delay to auto-hide
// - this component can be used directly with the hint text to be displayed
// (companion to the controls Hint properties and Application.ShowHint)
// - you can specify a time interval for the popup window to be hidden
// - this component expects UTF-8 encoded text, and displays it as Unicode
THintWindowDelayed = class(THintWindow)
private
fRow: integer;
fCol: integer;
protected
fTimerEnabled: boolean;
fFontColor: TColor;
fUTF8Text: RawUTF8;
/// called after a Hide call
procedure VisibleChanging; override;
/// used to hide the popup hint after a delay
{$ifdef FPC}
procedure WMTimer(var Message: TLMTimer); message LM_TIMER;
{$else}
procedure WMTimer(var Msg: TWMTimer); message WM_TIMER;
{$endif}
/// overridden method, Unicode ready
procedure Paint; override;
public
/// initializes the component
constructor Create(aOwner: TComponent); override;
/// releases component resources and memory
destructor Destroy; override;
/// displays the appropriate Hint Text at a specified screen position
// - Text is decoded from UTF-8 to Unicode before display
// - Time is the maximum text display delay, in milliseconds
procedure ShowDelayedUTF8(const Text: RawUTF8; X,Y,Time: integer;
FontColor: TColor; AlignLeft: boolean=false); overload;
/// displays the appropriate Hint Text at a position relative to a control
// - Text is decoded from UTF-8 to Unicode before display
// - Time is the maximum text display delay, in milliseconds
procedure ShowDelayedUTF8(const Text: RawUTF8; Origin: TControl;
X,Y,Time: integer; FontColor: TColor; AlignLeft: boolean=false); overload;
/// displays the appropriate Hint Text at a specified screen position
// - if string is AnsiString (i.e. for Delphi 2 to 2007), Text is decoded into
// Unicode (using the current i18n code page) before display
// - Time is the maximum text display delay, in milliseconds
procedure ShowDelayedString(const Text: string; X,Y,Time: integer;
FontColor: TColor; AlignLeft: boolean=false); overload;
{$ifdef HASINLINE}inline;{$endif}
/// displays the appropriate Hint Text at a position relative to a control
// - Text is decoded from Ansi to Unicode (using the current i18n code page) before display
// - Time is the maximum text display delay, in milliseconds
procedure ShowDelayedString(const Text: string; Origin: TControl;
X,Y,Time: integer; FontColor: TColor; AlignLeft: boolean=false); overload;
{$ifdef HASINLINE}inline;{$endif}
/// overridden method, Unicode ready
function CalcHintRect(MaxWidth: Integer; const AHint: RawUTF8; AData: Pointer): TRect; reintroduce;
/// the column number when the hint is displayed
property Col: integer read fCol;
/// the row number when the hint is displayed
property Row: integer read fRow;
end;
/// kind of event used to change some text on the fly for grid display
// - expect generic string Text, i.e. UnicodeString for Delphi 2009/2010,
// ready to be used with the VCL for all Delphi compiler versions
// - if the cell at FiieldIndex/RowIndex is to have a custom content,
// shall set the Text variable content and return TRUE
// - if returns FALSE, the default content will be displayed
TValueTextEvent = function(Sender: TSQLTable; FieldIndex, RowIndex: Integer; var Text: string): boolean of object;
/// kind of event used to change some text on the fly for popup hint
// - expect generic string Text, i.e. UnicodeString for Delphi 2009/2010,
// ready to be used with the VCL for all Delphi compiler versions
THintTextEvent = function(Sender: TSQLTable; FieldIndex, RowIndex: Integer; var Text: string): boolean of object;
/// kind of event used to display a menu on a cell right click
TRightClickCellEvent = procedure(Sender: TSQLTable; ACol, ARow, MouseX, MouseY: Integer) of object;
/// the available alignments of a TSQLTableToGrid cell
TSQLTableToGridAlign = (alLeft, alCenter, alRight);
/// a hidden component, used for displaying a TSQLTable in a TDrawGrid
// - just call TSQLTableToGrid.Create(Grid,Table) to initiate the association
// - the Table will be released when no longer necessary
// - any former association by TSQLTableToGrid.Create() will be overridden
// - handle unicode, column size, field sort, incremental key lookup, hide ID
// - Ctrl + click on a cell to display its full unicode content
TSQLTableToGrid = class(TComponent)
private
{$ifdef FPC}
fOnSelectCell: TOnSelectCellEvent;
{$else}
fOnSelectCell: TSelectCellEvent;
{$endif}
fOnRightClickCell: TRightClickCellEvent;
fClient: TSQLRestClientURI;
{$ifdef FPC}
fOnDrawCellBackground: TOnDrawCell;
{$else}
fOnDrawCellBackground: TDrawCellEvent;
{$endif}
fMarked: TByteDynArray;
fMarkAllowed: boolean;
fMouseDownMarkedValue: (markNone,markOn,markOff);
fTruncAsHint: Boolean;
fHeaderCheckboxSelectsInsteadOfSort: Boolean;
fOnSelectCellProcessing: boolean;
fFieldIndexTimeLogForMark: integer;
function GetMarked(RowIndex: integer): boolean;
procedure SetMarked(RowIndex: integer; const Value: boolean);
function GetMarkAvailable: boolean;
function GetDrawGrid: TDrawGrid;
function GetMarkedIsOnlyCurrrent: boolean;
function GetMarkedTotalCount: integer;
// function because field information may be set manually after Create
function GetFieldIndexTimeLogForMark: integer;
function GetAlign(aCol: cardinal): TSQLTableToGridAlign;
procedure SetAlign(aCol: cardinal; Value: TSQLTableToGridAlign);
function GetCustomFormat(aCol: cardinal): string;
procedure SetCustomFormat(aCol: cardinal; const Value: string);
function GetGridColumnWidths: RawUTF8;
procedure SetGridColumnWidths(const Value: RawUTF8);
protected
fOnValueText: TValueTextEvent;
fOnHintText: THintTextEvent;
fOnSort: TNotifyEvent;
/// associated TSQLTable result to be displayed
fTable: TSQLTable;
/// true if the specific field is in Ascending order
fFieldOrder: array of boolean;
/// current field number used for field
fCurrentFieldOrder: integer;
/// avoid resizing columns on height change only
fLastWidth: cardinal;
/// contain the key to be searched
fIncrementalSearch: RawUTF8;
/// true if row is changed by incremental key lookup
fIncrementalSearchMove: boolean;
/// used to display some hint text
fHint: THintWindowDelayed;
/// text of this field must be aligned as set by Aligned[] property
fAligned: array of TSQLTableToGridAlign;
/// custom formats as set by CustomFormat[] property
fCustomFormat: array of string;
/// text of this column/field name has been truncated
fFieldNameTruncated: Int64;
/// used by OnTableUpdate() event
fOnTableUpdateID: TIDDynArray;
/// return true if a GPF may occur
function NotDefined: boolean;
public
/// fill a TDrawGrid with the results contained in a TSQLTable
constructor Create(aOwner: TDrawGrid; aTable: TSQLTable; aClient: TSQLRestClientURI); reintroduce;
/// release the hidden object
// - will be called by the parent Grid when it is destroyed
// - will be called by any future TSQLTableToGrid.Create() association
// - free the associated TSQLTable and its memory content
// - will reset the Grid overridden events to avoid GPF
destructor Destroy; override;
/// called by the owner TDrawGrid to draw a Cell from the TSQLTable data
// - the cell is drawn using direct Win32 Unicode API
// - the first row (fixed) is drawn as field name (centered bold text with
// sorting order displayed with a triangular arrow)
procedure DrawCell(Sender: TObject; ACol, ARow: Longint; Rect: TRect; State: TGridDrawState);
/// called by the owner TDrawGrid when a Cell is selected
procedure DrawGridSelectCell(Sender: TObject; ACol, ARow: Integer; var CanSelect: Boolean);
/// called by the owner TDrawGrid when a Cell is clicked by the mouse
// - check if the first (fixed) row is clicked: then change sort order
// - Ctrl + click to display its full unicode content (see HintText to customize it)
procedure DrawGridMouseDown(Sender: TObject; Button: TMouseButton;
Shift: TShiftState; X, Y: Integer);
/// called by the owner TDrawGrid when the mouse is unclicked over a Cell
procedure DrawGridMouseUp(Sender: TObject; Button: TMouseButton;
Shift: TShiftState; X, Y: Integer);
/// called by the owner TDrawGrid when the mouse is over a Cell
procedure DrawGridMouseMove(Sender: TObject; Shift: TShiftState; X, Y: Integer);
/// called by the owner TDrawGrid when the user presses a key
// - used for incremental key lookup
procedure DrawGridKeyPress(Sender: TObject; var Key: Char);
/// called by the owner TDrawGrid when the user presses a key
// - used for LEFT/RIGHT ARROW column order change
procedure DrawGridKeyDown(Sender: TObject; var Key: Word; Shift: TShiftState);
/// call this procedure to automaticaly resize the TDrawString columns
// - can be used as TSQLTableToGrid.From(DrawGrid).Resize();
procedure Resize(Sender: TObject);
/// display a popup Hint window at a specified Cell position
// - expect generic string Text, i.e. UnicodeString for Delphi 2009/2010,
// ready to be used with the VCL for all Delphi compiler versions
procedure ShowHintString(const Text: string; ACol, ARow, Time: integer;
FontColor: TColor=clBlack);
/// if the ID column is available, hides it from the grid
procedure IDColumnHide;
/// toggle the sort order of a specified column
procedure SortChange(ACol: integer);
/// set a specified column for sorting
// - if ACol=-1, then the Marked[] rows are shown first, in current sort
procedure SortForce(ACol: integer; Ascending: boolean; ARow: integer=-1);
/// get the ID of the first selected row, 0 on error (no ID field e.g.)
// - useful even if ID column was hidden with IDColumnHide
function SelectedID: TID;
/// retrieve the record content of the first selected row, nil on error
// - record type is retrieved via Table.QueryTables[0] (if defined)
// - warning: it's up to the caller to Free the created instance after use
// (you should e.g. embedd the process in a try...finally block):
// ! Rec := Grid.SelectedRecordCreate;
// ! if Rec<>nil then
// ! try
// ! DoSomethingWith(Rec);
// ! finally
// ! Rec.Free;
// ! end;
// - useful even if ID column was hidden with IDColumnHide
function SelectedRecordCreate: TSQLRecord;
/// set individual column alignment
property Aligned[aCol: cardinal]: TSQLTableToGridAlign read GetAlign write SetAlign;
/// set columns number which must be aligned to non default left layout
// - a faster overload to Aligned[] property
procedure SetAligned(const aCols: array of cardinal; aAlign: TSQLTableToGridAlign);
/// set column alignment for a given type
// - a faster overload to Aligned[] property
procedure SetAlignedByType(aFieldType: TSQLFieldType; aAlign: TSQLTableToGridAlign);
/// set individual column custom format
// - as handled by TSQLTable.ExpandAsString() method, i.e. Format() or
// FormatFloat()/FormatCurrency() mask for sftFloat or sftCurrency, or
// FormatDateTime() mask for sftDateTime, sftDateTimeMS, sftTimeLog, sftModTime,
// sftCreateTime, sftUnixTime, sftUnixMSTime)
property CustomFormat[aCol: cardinal]: string read GetCustomFormat write SetCustomFormat;
/// set a custom format for all columns of a given type
// - a faster overload to CustomFormat[] property
// - only support the field types and formats handled by CustomFormat[] property
procedure SetCustomFormatByType(aFieldType: TSQLFieldType; const aCustomFormat: string);
/// force the mean of characters length for every field
// - supply a string with every character value is proportionate to
// the corresponding column width
// - if the character is lowercase, the column is set as centered
// - if aMarkAllowed is set, a first checkbox column is added, for
// reflecting and updating the Marked[] field values e.g.
// - if Lengths='', will set some uniform width, left aligned
procedure SetFieldLengthMean(const Lengths: RawUTF8; aMarkAllowed: boolean);
/// force all columns to have a specified width, in pixels
procedure SetFieldFixedWidth(aColumnWidth: integer);
/// force refresh paint of Grid from Table data
// - return true if Table data has been successfully retrieved from Client
// and if data was refreshed because changed since last time
// - if ForceRefresh is TRUE, the Client is not used to retrieve the data,
// which must be already refreshed before this call
// - if AutoResizeColumns is TRUE, the column visual width will be re-computed
// from the actual content - set it to FALSE to avoid it
function Refresh(ForceRefresh: Boolean=false; AutoResizeColumns: Boolean=true): boolean;
/// call this procedure after a refresh of the data
// - current Row will be set back to aID
// - called internal by Refresh function above
procedure AfterRefresh(const aID: TID; AutoResizeColumns: boolean);
/// you can call this method when the list is no more on the screen
// - it will hide any pending popup Hint windows, for example
procedure PageChanged;
/// perform the corresponding Mark/Unmark[All] Action
procedure SetMark(aAction: TSQLAction);
/// retrieve the Marked[] bits array
function GetMarkedBits: pointer;
/// read-only access to a particular row values, as VCL text
// - Model is one TSQLModel instance (used to display TRecordReference)
// - returns the text as generic string, ready to be displayed via the VCL
// after translation, for sftEnumerate, sftTimeLog, sftRecord and all other
// properties
// - uses OnValueText property Event if defined by caller
function ExpandRowAsString(Row: integer; Client: TObject): string;
/// retrieve the associated TSQLTableToGrid from a specific TDrawGrid
class function From(Grid: TDrawGrid): TSQLTableToGrid;
/// used by TSQLRestClientURI.UpdateFromServer() to let the client
// perform the rows update (for Marked[])
procedure OnTableUpdate(State: TOnTableUpdateState);
/// associated TDrawGrid
// - just typecast the Owner as TDrawGrid
property DrawGrid: TDrawGrid read GetDrawGrid;
/// associated TSQLTable to be displayed
property Table: TSQLTable read fTable;
/// associated Client used to retrieved the Table data
property Client: TSQLRestClientURI read fClient;
/// used to display some hint text
property Hint: THintWindowDelayed read fHint;
/// assign an event here to customize the background drawing of a cell
{$ifdef FPC}
property OnDrawCellBackground: TOnDrawCell read fOnDrawCellBackground
write fOnDrawCellBackground;
{$else}
property OnDrawCellBackground: TDrawCellEvent read fOnDrawCellBackground
write fOnDrawCellBackground;
{$endif}
/// true if Marked[] is available (add checkboxes at the left side of every row)
property MarkAllowed: boolean read fMarkAllowed;
/// true if any Marked[] is checked
property MarkAvailable: boolean read GetMarkAvailable;
/// true if only one entry is in Marked[], and it is the current one
property MarkedIsOnlyCurrrent: boolean read GetMarkedIsOnlyCurrrent;
/// returns the number of item marked or selected
// - if no item is marked, it return 0 even if a row is currently selected
property MarkedTotalCount: integer read GetMarkedTotalCount;
/// retrieves if a row was previously marked
// - first data row index is 1
property Marked[RowIndex: integer]: boolean read GetMarked write SetMarked;
/// retrieve or define the column widths of this grid, as text
// - as a CSV list of the associated DrawGrid.ColWidths[] values
property GridColumnWidths: RawUTF8
read GetGridColumnWidths write SetGridColumnWidths;
/// retrieves the index of the sftTimeLog first field
// - i.e. the field index which can be used for Marked actions
// - equals -1 if not such field exists
property FieldIndexTimeLogForMark: integer read GetFieldIndexTimeLogForMark;
/// current field number used for current table sorting
property CurrentFieldOrder: integer read fCurrentFieldOrder;
/// set to FALSE to display the column title as hint when truncated on screen
property FieldTitleTruncatedNotShownAsHint: boolean read fTruncAsHint write fTruncAsHint;
/// set to TRUE to let the header check box select/unselect all rows
// instead of sorting them
// - may be more conventional use of this header check box
property HeaderCheckboxSelectsInsteadOfSort: boolean
read fHeaderCheckboxSelectsInsteadOfSort write fHeaderCheckboxSelectsInsteadOfSort;
/// override this event to customize the text display in the table
property OnValueText: TValueTextEvent read fOnValueText write fOnValueText;
/// override this event to customize the Ctrl+Mouse click popup text
property OnHintText: THintTextEvent read fOnHintText write fOnHintText;
/// override this event to customize the Mouse click on a data cell
{$ifdef FPC}
property OnSelectCell: TOnSelectCellEvent read fOnSelectCell write fOnSelectCell;
{$else}
property OnSelectCell: TSelectCellEvent read fOnSelectCell write fOnSelectCell;
{$endif}
/// override this event to customize the Mouse right click on a data cell
property OnRightClickCell: TRightClickCellEvent read fOnRightClickCell write fOnRightClickCell;
/// override this event to be notified when the content is sorted
property OnSort: TNotifyEvent read fOnsort write fOnsort;
end;
type
/// exception class raised by TSynIntegerLabeledEdit
ESynLabeledEdit = class(Exception);
/// diverse kind of values which may be edited by a TSynLabeledEdit
TSynLabeledEditKind = (sleInteger, sleInt64, sleCurrency, sleDouble);
/// TLabeledEdit with optional boundaries check of a Variant value
TSynLabeledEdit = class(TLabeledEdit)
protected
FMaxValue: Variant;
FMinValue: Variant;
FAdditionalHint: string;
FKind: TSynLabeledEditKind;
FRangeChecking: boolean;
function IsValid(const Txt: string; var ToValue: Variant): Boolean;
procedure SetValue(const Value: Variant);
function GetValue: Variant;
procedure KeyPress(var Key: char); override;
procedure MouseMove(Shift: TShiftState; X, Y: integer); override;
public
/// if true, GetValue() will raise an ESynVariantLabeledEdit exception on
// any Variant value range error, when the Value property is read
RaiseExceptionOnError: boolean;
/// convert the entered Variant value into a textual representation
function ToString(NumberOfDigits: integer): string; reintroduce;
/// return TRUE if the entered value is inside the boundaries
function ValidateValue: boolean;
/// create the component instance
constructor Create(AOwner: TComponent); override;
published
/// the kind of value which is currently edited by this TSynLabeledEdit
property Kind: TSynLabeledEditKind read fKind write fKind default sleInteger;
/// the entered value
// - getting this property will check for in range according to the
// current MinValue/MaxValue boundaries, if RangeChecking is set
// - if RangeChecking is not set, could return a NULL variant for no data
// - it will sound a beep in case of any out of range
// - it will also raise a ESynVariantLabeledEdit exception if
// RaiseExceptionOnError is set to TRUE (equals FALSE by default)
property Value: Variant read GetValue write SetValue;
/// set to TRUE if MinValue/MaxValue properties must be checked when
// reading Value property
property RangeChecking: boolean read fRangeChecking write fRangeChecking;
/// lowest allowed Variant value
property MinValue: Variant read FMinValue write FMinValue;
/// highest allowed Variant value
property MaxValue: Variant read FMaxValue write FMaxValue;
/// some additional popup hint to be displayed
// - by default, the allowed range is displayed: 'Min. Value: #, Max. Value: #'
// - you can specify here some additional text to be displayed when the mouse
// is hover the component
property AdditionalHint: string read FAdditionalHint write FAdditionalHint;
end;
/// allow to track and load/save UI components as JSON
// - may be used to persist TEdit / TCheckBox / TComboBox values on a form
// when the application leaves
TUIComponentsPersist = class
protected
fTracked: array of TComponent;
fFileName: TFileName;
fLoadedJson: RawUTF8;
function GetFileName: TFileName;
public
/// would track .Text and .Checked properties only
procedure TrackControls(const ctrls: array of TComponent);
/// fill all tracked controls properties from the supplied JSON object
procedure LoadFromVariant(const aDoc: variant);
/// save all tracked controls properties as a JSON object
function SaveToVariant: variant;
/// fill all tracked controls properties from a local JSON file
procedure LoadFromFile;
/// save all tracked controls properties as JSON in a local file
procedure SaveToFile;
/// the local JSON file used for persistence
// - is set to 'executablename.default' if none is specified
property FileName: TFileName read GetFileName write fFileName;
end;
/// register the TSynIntegerLabeledEdit component in the IDE toolbar
// - not necessary for the mORMot framework to run: since all User Interface
// is created from code, and not from the Delphi IDE, you don't have to register
// anything unless you define your own forms including those components
procedure Register;
resourcestring
SErrorFieldNotValid = 'Field "%s"'#13'does not contain a valid %s value';
SErrorFieldTooSmall = 'Field "%s"'#13'is too small, value must be >= %s';
SErrorFieldTooLarge = 'Field "%s"'#13'is too large, value must be <= %s';
SMinMaxValue = 'Min. Value: %s, Max. Value: %s';
{$ifndef FPC}
{$define VISTAFORM}
{$endif}
{$ifdef VISTAFORM}
{$R SQLite3UI.RES}
type
/// Vista-enabled TForm descendant
// - this form will have a button in the TaskBar
// - this form will hide the default Delphi application virtual form
// - this form can be with no caption bar using SetNoCaption method
TVistaForm = class(TForm)
protected
fNoCaption: TPanel;
fNoCaptionLabel: TLabel;
fMinimizeBtn: TSpeedButton;
fMaximizeBtn: TSpeedButton;
fCloseBtn: TSpeedButton;
procedure NoCaptionMouseDown(Sender: TObject; Button: TMouseButton;
Shift: TShiftState; X, Y: Integer);
procedure BtnClick(Sender: TObject);
procedure CreateParams(var Params: TCreateParams); override;
procedure WMSyscommand(var M: TMessage); message WM_SYSCOMMAND;
public
/// call this method to hide the Caption bar and replace it with a TPanel
procedure SetNoCaption(aTopMostPanel: TPanel; aLabelLeft: integer);
/// the TPanel instance replacing the Caption bar
property NoCaptionPanel: TPanel read fNoCaption;
/// the TLabel instance created on NoCaptionPanel to replace the Caption bar
property NoCaptionLabel: TLabel read fNoCaptionLabel;
end;
/// low level VCL routine in order to hide the application from Windows task bar
// - don't use it directly: it's called by TVistaForm.CreateParams()
procedure HideAppFormTaskBarButton;
{$endif}
/// draw a CheckBox in the Canvas Handle of the Wwindow hWnd, in the middle
// of the Rect coordinates
// - use theming under XP, Vista and Seven
procedure DrawCheckBox(hWnd: THandle; Handle: HDC; const Rect: TRect; Checked: boolean);
{$ifdef MSWINDOWS}
/// test if the ClearType is enabled for font display
// - ClearType is a software technology that improves the readability of text
// on liquid crystal display (LCD) monitors
function IsClearTypeEnabled: boolean;
/// enable the ClearType font display
// - under Windows 2000, standard font smoothing is forced, since Clear Type
// was introduced with XP
procedure ClearTypeEnable;
/// create an Icon
// - return the .lnk file name (i.e. Name+'.lnk')
function CreateAnIcon (const Name, Description, Path, Parameters,
WorkingDir, IconFilename: TFileName; const IconIndex: Integer;
const RunMinimized: Boolean = false): TFileName;
/// get the corresponding windows folder, from its ID
function GetShellFolderPath(const FolderID: Integer): string;
const
CSIDL_PROGRAMS = $0002;
CSIDL_DOCUMENTS = $0005;
CSIDL_COMMON_PROGRAMS = $0017;
CSIDL_COMMON_STARTMENU = $0016;
CSIDL_COMMON_DESKTOPDIRECTORY = $0019;
/// allow an application to access the network through the Windows firewall
// - works on Windows WP, Vista and Seven
// - caller process must have the administrator rights (this is the case
// for a setup program)
procedure AddApplicationToFirewall(const EntryName, ApplicationPathAndExe: string);
/// open a firewall port on the current computer
// - works on Windows XP, Vista and Seven
// - caller process must have the administrator rights (this is the case
// for a setup program)
procedure AddPortToFirewall(const EntryName: string; PortNumber: cardinal);
{$endif MSWINDOWS}
/// fill TStringGrid.Cells[] with the supplied data
// - will be slower than the TSQLTableToGrid method, but will work on
// a non standard TDrawGrid component
// - it will display date & time and enumerates as plain text, and handle
// the header properly (using the current mORMoti18n.pas language settings, if any)
// - the Client optional parameter will be used to display any RecordRef column
// - all data will be stored within the TStringGrid: you can safely release the
// Source data after having called this procedure
procedure FillStringGrid(Source: TSQLTable; Dest: TStringGrid; Client: TSQLRest=nil);
implementation
uses
{$ifdef ISDELPHIXE3}System.UITypes,{$endif}
{$ifdef MSWINDOWS}ShellApi, ComObj, Activex,Shlobj, {$endif}
VarUtils;
{$ifdef MSWINDOWS}
procedure CreateShellLink (const Filename, Description, ShortcutTo, Parameters,
WorkingDir, IconFilename: String; const IconIndex: Integer;
const RunMinimized: Boolean);
{ Creates a lnk file named Filename, with a description of Description, which
points to ShortcutTo. }
var
Obj: IUnknown;
SL: IShellLink;
PF: IPersistFile;
WideFilename: WideString;
begin
Obj := CreateComObject(CLSID_ShellLink);
SL := Obj as IShellLink;
SL.SetPath(pointer(ShortcutTo));
SL.SetArguments(pointer(Parameters));
if WorkingDir <> '' then
SL.SetWorkingDirectory(pointer(WorkingDir));
if IconFilename <> '' then
SL.SetIconLocation(pointer(IconFilename), IconIndex);
SL.SetDescription(pointer(Description));
PF := Obj as IPersistFile;
WideFilename := Filename;
PF.Save(PWideChar(WideFilename), True);
{ Delphi 3+ automatically releases COM objects when they go out of scope }
end;
function GetShellFolderPath(const FolderID: Integer): string;
var pidl: PItemIDList;
Buffer: array[0..MAX_PATH-1] of Char;
Malloc: IMalloc;
begin
Result := '';
if Win32MajorVersion<4 then Exit;
if SUCCEEDED(SHGetSpecialFolderLocation(0, FolderID, pidl)) then begin
if SHGetPathFromIDList(pidl, Buffer) then begin
Result := Buffer;
if Result[length(Result)]<>'\' then
Result := Result+'\';
end;
if not FAILED(SHGetMalloc(Malloc)) then
Malloc.Free(pidl);
end;
end;
function CreateAnIcon(const Name, Description, Path, Parameters,
WorkingDir, IconFilename: TFileName; const IconIndex: Integer;
const RunMinimized: Boolean = false): TFileName;
var Dir: TFileName;
begin
result := Name + '.lnk';
Dir := ExtractFilePath(result);
if Dir='' then begin
if Win32Platform=VER_PLATFORM_WIN32_NT then
Dir := GetShellFolderPath(CSIDL_COMMON_DESKTOPDIRECTORY) else
Dir := GetShellFolderPath(CSIDL_DESKTOPDIRECTORY);
if Dir[length(Dir)]='\' then
result := Dir+result else
result := Dir+'\'+result;
end;
if not DirectoryExists(Dir) then begin
Dir := ExpandFilename(Dir);
if not CreateDirectory(pointer(Dir), nil) then exit;
SHChangeNotify(SHCNE_MKDIR, SHCNF_PATH, pointer(Dir), nil);
SHChangeNotify(SHCNE_UPDATEDIR, SHCNF_PATH or SHCNF_FLUSH,
pointer(ExtractFilePath(Dir)), nil);
end;
CreateShellLink(result, Description, Path, Parameters, WorkingDir,
IconFilename, IconIndex, RunMinimized);
SHChangeNotify(SHCNE_CREATE, SHCNF_PATH, pointer(result), nil);
SHChangeNotify(SHCNE_UPDATEDIR, SHCNF_PATH or SHCNF_FLUSH,
pointer(ExtractFilePath(result)), nil);
end;
const
NET_FW_PROFILE_DOMAIN = 0;
NET_FW_PROFILE_STANDARD = 1;
NET_FW_PROFILE2_PRIVATE = 2;
NET_FW_PROFILE2_PUBLIC = 4;
NET_FW_IP_VERSION_ANY = 2;
NET_FW_IP_PROTOCOL_UDP = 17;
NET_FW_IP_PROTOCOL_TCP = 6;
NET_FW_SCOPE_ALL = 0;
NET_FW_SCOPE_LOCAL_SUBNET = 1;
NET_FW_ACTION_ALLOW = 1;
function GetXPFirewall(var fwMgr, profile: OleVariant): boolean;
begin
result := (Win32Platform=VER_PLATFORM_WIN32_NT) and
(Win32MajorVersion>5) or ((Win32MajorVersion=5) and (Win32MinorVersion>0));
if result then // need Windows XP at least
try
fwMgr := CreateOleObject('HNetCfg.FwMgr');
profile := fwMgr.LocalPolicy.CurrentProfile;
except
on E: Exception do
result := false;
end;
end;
function GetVistaSevenFirewall(var fwMgr, rule: OleVariant; const Description: string): boolean;
begin
result := (Win32Platform=VER_PLATFORM_WIN32_NT) and (Win32MajorVersion>5);
if result then // need Windows Vista at least
try
fwMgr := CreateOleObject('HNetCfg.FwPolicy2');
rule := CreateOleObject('HNetCfg.FWRule');
rule.Name := Description;
rule.Description := Description;
rule.Protocol := NET_FW_IP_PROTOCOL_TCP;
rule.Enabled := true;
rule.Profiles := NET_FW_PROFILE2_PRIVATE OR NET_FW_PROFILE2_PUBLIC;
rule.Action := NET_FW_ACTION_ALLOW;
except
on E: Exception do
result := false;
end;
end;
procedure AddApplicationToFirewall(const EntryName, ApplicationPathAndExe: string);
var fwMgr, profile, app, rule: OleVariant;
begin
if Win32MajorVersion<6 then begin
if GetXPFirewall(fwMgr,profile) and profile.FirewallEnabled then begin
app := CreateOLEObject('HNetCfg.FwAuthorizedApplication');
app.ProcessImageFileName := ApplicationPathAndExe;
app.Name := EntryName;
app.Scope := NET_FW_SCOPE_ALL;
app.IpVersion := NET_FW_IP_VERSION_ANY;
app.Enabled := true;
profile.AuthorizedApplications.Add(app);
end;
end else
if GetVistaSevenFirewall(fwMgr,rule,EntryName) then begin
rule.ApplicationName := ApplicationPathAndExe;
fwMgr.Rules.Add(rule);
end;
end;
procedure AddPortToFirewall(const EntryName: string; PortNumber: cardinal);
var fwMgr, profile, port, rule: OleVariant;
begin
if Win32MajorVersion<6 then begin
if GetXPFirewall(fwMgr,profile) and profile.FirewallEnabled then begin
port := CreateOLEObject('HNetCfg.FWOpenPort');
port.Name := EntryName;
port.Protocol := NET_FW_IP_PROTOCOL_TCP;
port.Port := PortNumber;
port.Scope := NET_FW_SCOPE_ALL;
port.Enabled := true;
profile.GloballyOpenPorts.Add(port);
end;
end else
if GetVistaSevenFirewall(fwMgr,rule,EntryName) then begin
rule.LocalPorts := PortNumber;
fwMgr.Rules.Add(rule);
end;
end;
const // for Delphi 6 compilation
SPI_GETFONTSMOOTHINGTYPE = $200A;
SPI_SETFONTSMOOTHINGTYPE = $200B;
FE_FONTSMOOTHINGSTANDARD = $0001;
FE_FONTSMOOTHINGCLEARTYPE = $0002;
function IsClearTypeEnabled: boolean;
// see http://blogs.msdn.com/michkap/archive/2008/03/01/7971061.aspx
var MType, SmoothFonts: DWORD;
begin
SmoothFonts := 0;
SystemParametersInfo(SPI_GETFONTSMOOTHING, 1, @SmoothFonts, 0);
SystemParametersInfo(SPI_GETFONTSMOOTHINGTYPE, 0, @MType, 0);
result := boolean(SmoothFonts) and (MType=FE_FONTSMOOTHINGCLEARTYPE);
end;
procedure ClearTypeEnable;
var MType, SmoothFonts: DWORD;
begin
if (Win32MajorVersion<5) or IsClearTypeEnabled then
exit; // no font smoothing before Win2K
SystemParametersInfo(SPI_GETFONTSMOOTHING, 1, @SmoothFonts, 0);
if not boolean(SmoothFonts) then
SystemParametersInfo(SPI_SETFONTSMOOTHING, 1, nil, SPIF_UPDATEINIFILE or
SPIF_SENDCHANGE);
if (Win32MajorVersion=5) and (Win32MinorVersion=0) then
MType := FE_FONTSMOOTHINGSTANDARD else // no Clear Type on Win2K
MType := FE_FONTSMOOTHINGCLEARTYPE;
SystemParametersInfo(SPI_SETFONTSMOOTHINGTYPE, 0, Pointer(MType),
SPIF_UPDATEINIFILE or SPIF_SENDCHANGE);
end;
/// attempt to change the scroll bar size -> need Grids.pas rewrite -> buggy
procedure SetScrollVPage(Handle: HWND; aPage, aMax: Integer);
var ScrollInfo: TScrollInfo;
begin
if Handle=0 then
exit;
with ScrollInfo do begin
cbSize := SizeOf(ScrollInfo);
fMask := SIF_ALL;
GetScrollInfo(Handle, SB_VERT, ScrollInfo);
if nMin>=nMax then exit;
if nMax<>127 then exit;
fMask := SIF_DISABLENOSCROLL or SIF_PAGE or SIF_RANGE;
if aMax<=0 then
nPage := 1 else begin
nPage := 30;
inc(nMax,nPage);
end;
end;
SetScrollInfo(Handle, SB_VERT, ScrollInfo, true);
end;
{$endif MSWINDOWS}
{ THintWindowDelayed }
constructor THintWindowDelayed.Create(aOwner: TComponent);
begin
inherited Create(aOwner);
Color := $C0FFFF;
fFontColor := clBlack;
{ if aOwner.InheritsFrom(TSQLTableToGrid) and // done by ParentFont := true
not TSQLTableToGrid(aOwner).NotDefined then
Canvas.Font := TDrawGrid(TSQLTableToGrid(aOwner).Owner).Canvas.Font; }
end;
destructor THintWindowDelayed.Destroy;
begin
Hide;
inherited;
end;
procedure THintWindowDelayed.ShowDelayedUTF8(const Text: RawUTF8; X, Y, Time: integer;
FontColor: TColor; AlignLeft: boolean=false);
var R: TRect;
begin
if self=nil then
exit;
Hide;
if Text='' then
exit; // no text to show
R := CalcHintRect(512, Text, nil);
if not AlignLeft then
dec(X,R.Right-R.Left); // align right
inc(R.Left,X);
inc(R.Right,X);
inc(R.Top,Y);
inc(R.Bottom,Y);
ActivateHint(R,UTF8ToString(Text)); // perform Caption := Text
fUTF8Text := Text; // so it will work with Delphi 2009/2010
if Time<>0 then begin
if fTimerEnabled then
KillTimer(Handle,1) else
fTimerEnabled := true;
SetTimer(Handle,1,Time,nil);
end;
fFontColor := FontColor;
Show;
end;
procedure THintWindowDelayed.ShowDelayedUTF8(const Text: RawUTF8; Origin: TControl;
X, Y, Time: integer; FontColor: TColor; AlignLeft: boolean=false);
begin
with Origin.ClientToScreen(Point(X,Y)) do
ShowDelayedUTF8(Text,X,Y,Time,FontColor,AlignLeft);
end;
function THintWindowDelayed.CalcHintRect(MaxWidth: Integer;
const AHint: RawUTF8; AData: Pointer): TRect;
var U: RawUnicode; // faster than a WideString
begin // unicode version
Result := Rect(0, 0, MaxWidth, 0);
U := Utf8DecodeToRawUnicode(AHint);
{$ifdef MSWINDOWS}DrawTextW{$else}DrawText{$endif}(Canvas.Handle, pointer(U), length(U) shr 1, Result, DT_CALCRECT or DT_LEFT or
DT_WORDBREAK or DT_NOPREFIX {$ifndef FPC}or DrawTextBiDiModeFlagsReadingOnly{$endif});
Inc(Result.Right, 6);
Inc(Result.Bottom, 2);
end;
procedure THintWindowDelayed.Paint;
var R: TRect;
U: RawUnicode; // faster than a WideString
begin // unicode version
R := ClientRect;
Inc(R.Left, 2);
Inc(R.Top, 2);
Canvas.Font.Color := fFontColor;
U := Utf8DecodeToRawUnicodeUI(fUTF8Text);
{$ifdef MSWINDOWS}DrawTextW{$else}DrawText{$endif}(Canvas.Handle, pointer(U), -1, R, DT_LEFT or DT_NOPREFIX or
DT_WORDBREAK {$ifndef FPC}or DrawTextBiDiModeFlagsReadingOnly{$endif});
end;
procedure THintWindowDelayed.VisibleChanging;
begin
try
if fTimerEnabled and Visible then begin // are we in a Hide process?
KillTimer(Handle,1);
fTimerEnabled := false;
fRow := -1;
if HandleAllocated then
ReleaseHandle;
end;
finally
inherited VisibleChanging;
end;
end;
{$ifdef FPC}
procedure THintWindowDelayed.WMTimer(var Message: TLMTimer);
{$else}
procedure THintWindowDelayed.WMTimer(var Msg: TWMTimer);
{$endif}
begin
Hide;
fRow := -1;
end;
procedure THintWindowDelayed.ShowDelayedString(const Text: string; X, Y,
Time: integer; FontColor: TColor; AlignLeft: boolean=false);
begin
ShowDelayedUTF8(StringToUTF8(Text),X,Y,Time,FontColor,AlignLeft);
end;
procedure THintWindowDelayed.ShowDelayedString(const Text: string;
Origin: TControl; X, Y, Time: integer; FontColor: TColor; AlignLeft: boolean=false);
begin
with Origin.ClientToScreen(Point(X,Y)) do
ShowDelayedUTF8(StringToUTF8(Text),X,Y,Time,FontColor,AlignLeft);
end;
{ TSQLTableToGrid }
resourcestring
sErrorTSQLTableToGridNoData = '%s didn''t receive any data for %s';
constructor TSQLTableToGrid.Create(aOwner: TDrawGrid; aTable: TSQLTable;
aClient: TSQLRestClientURI);
begin
if aTable=nil then
raise Exception.CreateFmt(sErrorTSQLTableToGridNoData,[ClassName,aOwner.Name]);
From(aOwner).Free; // any old association will be overridden by this instance
inherited Create(aOwner);
fTable := aTable;
fClient := aClient;
fHint := THintWindowDelayed.Create(self);
aOwner.RowCount := 2; // first reset row count, to avoid flicking
aOwner.FixedRows := 1;
aOwner.Canvas.Font := aOwner.Font;
with aOwner.Canvas.TextExtent('jQH'#$00B0';') do
aOwner.DefaultRowHeight := cy+4;
aOwner.Options := [goFixedHorzLine,goFixedVertLine,goVertLine,
goHorzLine,goColSizing,goRowSelect,goThumbTracking]; // no goRangeSelect
aOwner.FixedCols := 0;
aOwner.ColCount := Table.FieldCount;
SetLength(fFieldOrder,Table.FieldCount); // auto filled to false
fCurrentFieldOrder := Table.FieldIndex(
pointer(SQLGetOrder(Table.QuerySQL))); // get 'ORDER BY' field index
if fCurrentFieldOrder>=0 then
fFieldOrder[fCurrentFieldOrder] := true; // mark 'ORDER BY' field ascending
fFieldIndexTimeLogForMark := -2; // so GetFieldIndexTimeLogForMark will get it
aOwner.OnDrawCell := DrawCell;
aOwner.OnMouseMove := DrawGridMouseMove;
aOwner.OnMouseDown := DrawGridMouseDown;
aOwner.OnMouseUp := DrawGridMouseUp;
aOwner.OnSelectCell := DrawGridSelectCell;
aOwner.OnKeyPress := DrawGridKeyPress;
aOwner.OnKeyDown := DrawGridKeyDown;
if Table.RowCount>0 then
aOwner.RowCount := Table.RowCount+1;
end;
destructor TSQLTableToGrid.Destroy;
begin
if (Owner<>nil) and Owner.InheritsFrom(TDrawGrid) then
with TDrawGrid(Owner) do begin // reset the Grid overridden events to avoid GPF
OnDrawCell := nil;
OnMouseMove := nil;
OnMouseDown := nil;
OnMouseUp := nil;
OnSelectCell := nil;
OnKeyPress := nil;
OnKeyDown := nil;
end;
FreeAndNil(fTable);
inherited;
end;
class function TSQLTableToGrid.From(Grid: TDrawGrid): TSQLTableToGrid;
var i: integer;
begin
if Grid<>nil then
for i := 0 to Grid.ComponentCount-1 do begin
result := pointer(Grid.Components[i]);
if result.InheritsFrom(TSQLTableToGrid) and (result.Owner=Grid) then
exit;
end;
result := nil;
end;
const
CheckBoxWidth = 13;
{$WARN SYMBOL_DEPRECATED OFF} // for ThemeServices
procedure DrawCheckBox(hWnd: THandle; Handle: HDC; const Rect: TRect; Checked: boolean);
const
{$ifdef WITHUXTHEME}
XPState: array[boolean] of TThemedButton = (
tbCheckBoxUncheckedNormal, tbCheckBoxCheckedNormal);
{$endif}
Win32State: array[boolean] of cardinal = (
DFCS_BUTTONCHECK, DFCS_BUTTONCHECK or DFCS_CHECKED);
var DrawRect: TRect;
begin
DrawRect.Left := Rect.Left+(Rect.Right-Rect.Left-CheckBoxWidth)shr 1;
DrawRect.Top:= Rect.Top+2;
DrawRect.Right := DrawRect.Left + CheckBoxWidth;
DrawRect.Bottom := DrawRect.Top + CheckBoxWidth;
{$ifdef WITHUXTHEME}
if ThemeServices.ThemesEnabled then begin // Windows XP and later: use theming
ThemeServices.DrawElement(handle,
ThemeServices.GetElementDetails(XPState[Checked]), DrawRect);
end else
{$endif}
DrawFrameControl(Handle,DrawRect,DFC_BUTTON,Win32State[Checked]);
end;
{$ifndef MSWINDOWS}
function ExtTextOutW(DC: HDC; X, Y: Integer; Options: LongInt; Rect: PRect;
Str: PWideChar; Count: LongInt; Dx: ObjPas.PInteger): Boolean;
var
TempStr: UTF8String;
L:integer;
begin
TempStr := RawUnicodeToUtf8(Str,Count,L);
Result := ExtTextOut(DC, X, Y, Options, Rect, PChar(TempStr),L, Dx);
end;
{$endif}
procedure TSQLTableToGrid.DrawCell(Sender: TObject; ACol, ARow: Integer;
Rect: TRect; State: TGridDrawState);
var Options, x,y, L, i, XInc: integer;
StringValue: string; // generic string type, VCL ready
Points: array[0..2] of TPoint;
WithMark: boolean;
Aligned: TSQLTableToGridAlign;
tmp: array[0..255] of WideChar; // 255 chars is wide enough inside a cell
{$ifndef MSWINDOWS}
aTextStyle: TTextStyle;
{$endif}
begin
// default cell draw
if NotDefined then
exit;
if Assigned(OnDrawCellBackground) then
OnDrawCellBackground(Owner,ACol,ARow,Rect,State);
if (cardinal(ARow)>cardinal(Table.RowCount)) or
(cardinal(ACol)>=cardinal(Table.FieldCount)) then // avoid any possible GPF
exit;
with TDrawGrid(Owner).Canvas do begin
{$ifndef MSWINDOWS}
aTextStyle := TextStyle;
{$endif}
Options := ETO_CLIPPED {$ifndef FPC}or TextFlags{$endif};
if Brush.Style <> bsClear then
Options := Options or ETO_OPAQUE;
WithMark := fMarkAllowed and (ACol=0);
if ARow=0 then begin
// 1. 1st row = field name: bold + centered translated text, with sort indicator
if not Assigned(OnValueText) or
not OnValueText(Table,ACol,0,StringValue) then
StringValue := Table.GetCaption(0,ACol); // auto translated
if not Assigned(OnDrawCellBackground) then
Font.Style := [fsBold];
L := Rect.Right-Rect.Left;
if WithMark then
dec(L,CheckBoxWidth+4);
if TextWidth(StringValue)<L then
Aligned := alCenter else
Aligned := alLeft;
if Aligned=alCenter then begin
UnSetBit64(fFieldNameTruncated,ACol);
XInc := L shr 1;
{$ifdef MSWINDOWS}
SetTextAlign(Handle,TA_CENTER);
{$else}
aTextStyle.Alignment := taCenter;
{$endif}
end else begin
SetBit64(fFieldNameTruncated,ACol);
XInc := 2;
end;
if WithMark then
inc(XInc,CheckBoxWidth+4);
{$ifndef MSWINDOWS}
TextStyle := aTextStyle;
TextRect(Rect,Rect.Left+XInc,Rect.Top+2,StringValue);
{$else}
ExtTextOut(Handle, Rect.Left+XInc,Rect.Top+2, Options, @Rect, pointer(StringValue),
length(StringValue), nil); // direct translated text centered draw
{$endif}
if Aligned=alCenter then
{$ifdef MSWINDOWS}
SetTextAlign(Handle,TA_LEFT);
{$else}
aTextStyle.Alignment := taLeftJustify;
{$endif}
Font.Style := [];
if fCurrentFieldOrder=ACol then begin
// sorted field: draw sort indicator
x := Rect.Right-8;
if fFieldOrder[ACol] then begin
Points[0].X := x+5; // ascending order arrow
Points[1].X := x;
Points[2].X := x-5;
end else begin
Points[0].X := x-5; // descending order arrow
Points[1].X := x+5;
Points[2].X := x;
end;
y := Rect.Bottom-9;
if fFieldOrder[ACol] then begin
Points[0].Y := y-5; // ascending order arrow
Points[1].Y := y+5;
Points[2].Y := y-5
end else begin
Points[0].Y := y+5; // descending order arrow
Points[1].Y := y+5;
Points[2].Y := y-5
end;
Brush.Color := clWhite; // fill the arrow content
Polygon(Points);
Pen.Color := clLtGray; // draw the arrow border
MoveTo(Points[0].X,Points[0].Y);
LineTo(Points[1].X,Points[1].Y);
Pen.Color := clGray;
LineTo(Points[2].X,Points[2].Y);
LineTo(Points[0].X,Points[0].Y);
end;
end else begin
// 2. field value rows
L := Rect.Right-Rect.Left;
if WithMark then
dec(L,CheckBoxWidth+4);
Aligned := self.Aligned[ACol];
case Aligned of
alCenter: begin
{$ifdef MSWINDOWS}
SetTextAlign(Handle,TA_CENTER);
{$else}
aTextStyle.Alignment := taCenter;
{$endif}
XInc := L shr 1;
end;
alRight: begin
{$ifdef MSWINDOWS}
SetTextAlign(Handle,TA_RIGHT);
{$else}
aTextStyle.Alignment := taRightJustify;
{$endif}
XInc := L-4;
end else
XInc := 4;
end;
if WithMark then
inc(XInc,CheckBoxWidth+4);
if Assigned(OnValueText) and OnValueText(Table,ACol,ARow,StringValue) then begin
L := length(StringValue);
if L>255 then
L := 255; // avoid blank cell drawing for huge content
{$ifndef MSWINDOWS}
TextStyle := aTextStyle;
TextRect(Rect,Rect.Left+XInc,Rect.Top+2,StringValue);
{$else}
ExtTextOut(Handle, Rect.Left+XInc, Rect.Top+2, Options, @Rect,
pointer(StringValue), L, nil); // translated text
{$endif}
end else
case Table.ExpandAsString(ARow,ACol,Client,StringValue,GetCustomFormat(ACol)) of
// very fast response (calculated once)
sftBoolean:
// display boolean as checkbox
if Assigned(Table.Get(ARow,ACol)) then
DrawCheckBox(TDrawGrid(Owner).Handle, Handle, Rect,
PWord(Table.Get(ARow,ACol))^<>ord('0')) // fast StrComp(,'0')
else
DrawCheckBox(TDrawGrid(Owner).Handle, Handle, Rect, False);
sftInteger, sftFloat, sftCurrency,
sftEnumerate, sftTimeLog, sftRecord,
sftDateTime, sftDateTimeMS, sftUnixTime, sftUnixMSTime:
begin
{$ifndef MSWINDOWS}
TextStyle := aTextStyle;
TextRect(Rect,Rect.Left+XInc,Rect.Top+2,StringValue);
{$else}
ExtTextOut(Handle, Rect.Left+XInc, Rect.Top+2, Options, @Rect, pointer(StringValue),
length(StringValue), nil); // translated short text
{$endif}
end;
//sftID,sftTID:
// proposal: display ID as TSQLRecord content? better compute it in SELECT
else begin
// normal field value: unicode text (even with Delphi 2-2007 VCL), left aligned
{$ifndef MSWINDOWS}
StringValue := Table.GetU(ARow,ACol);
for i := 0 to Length(StringValue)-1 do // replace #13,#10 chars in the grid with spaces
if StringValue[i]<' ' then
StringValue[i] := ' ';
TextStyle := aTextStyle;
TextRect(Rect,Rect.Left+XInc,Rect.Top+2,StringValue);
{$else}
L := Table.GetWP(ARow,ACol,tmp,high(tmp));
for i := 0 to L-1 do // replace #13,#10 chars in the grid with spaces
if tmp[i]<' ' then
tmp[i] := ' ';
// direct unicode text draw
ExtTextOutW(Handle, Rect.Left+XInc, Rect.Top+2, Options, @Rect, tmp, L, nil);
{$endif}
end;
end;
if Aligned<>alLeft then
{$ifdef MSWINDOWS}
SetTextAlign(Handle,TA_LEFT);
{$else}
aTextStyle.Alignment := taLeftJustify;
{$endif}
end;
if WithMark then begin // draw left side checkbox with Marked[] value
inc(Rect.Left,2);
Rect.Right := Rect.Left+CheckBoxWidth;
if ARow=0 then
WithMark := HeaderCheckboxSelectsInsteadOfSort and
(MarkedTotalCount+1=TDrawGrid(Owner).RowCount) else
WithMark := Marked[ARow];
DrawCheckBox(TDrawGrid(Owner).Handle, Handle,Rect,WithMark);
end;
end;
end;
procedure TSQLTableToGrid.SortForce(ACol: integer; Ascending: boolean;
ARow: integer=-1);
var MIDs: TIDDynArray;
begin
if NotDefined or (ACol>=Table.FieldCount) then // we allow ACol<0 (see below)
exit;
if ARow<0 then
ARow := TDrawGrid(Owner).Row; // keep current row selected if none specified
if ACol<0 then begin
// if ACol=-1, then the Marked[] rows are shown first, in current sort
if fMarked=nil then
exit; // no Marked[] rows to put in first place
ARow := Table.IDColumnHiddenValue(ARow); // current row
Table.SortBitsFirst(fMarked[0]);
TDrawGrid(Owner).Row := Table.RowFromID(ARow);
// no PageChanged here
end else begin
if fMarked<>nil then
Table.IDArrayFromBits(fMarked[0],MIDs); // save current marked entries
fFieldOrder[ACol] := Ascending;
fCurrentFieldOrder := ACol;
Table.SortFields(ACol,fFieldOrder[ACol],@ARow);
if MIDs<>nil then
Table.IDArrayToBits(fMarked[0],MIDs); // restore marked entries
TDrawGrid(Owner).Row := ARow; // reselect row after sort (+ Invalidate)
PageChanged; // hide any pending popup Hint e.g.
end;
if Assigned(OnSort) then
OnSort(TDrawGrid(Owner));
end;
procedure TSQLTableToGrid.DrawGridMouseDown(Sender: TObject;
Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
var ACol, ARow: integer;
Hint: string; // generic string type, for VCL
begin
if NotDefined then // avoid any possible GPF
exit;
fMouseDownMarkedValue := markNone;
TDrawGrid(Owner).MouseToCell(X, Y, ACol, ARow);
if cardinal(ACol)<cardinal(Table.FieldCount) then
if ARow=0 then
if (ssCtrl in Shift) or (Button<>mbLeft) then begin
// Ctrl or right button pressed -> display first row as hint
ShowHintString(UTF8ToString(Table.Get(ARow,ACol)),ACol,ARow,4000);
end else begin
// first row -> sort fields
if fMarkAllowed and (X<CheckBoxWidth+4) then
if HeaderCheckboxSelectsInsteadOfSort then
// toggle selection
if MarkAvailable then
SetMark(actUnmarkAll) else
SetMark(actmarkAllEntries) else
// sort Marked[] first
SortForce(-1,true) else
if fCurrentFieldOrder=ACol then
// same column -> toggle sorting order
SortForce(ACol,not fFieldOrder[ACol]) else
// column changed -> sort ascending first
SortForce(ACol,true);
end else
// not first row: data
if (Button=mbRight) and (ssRight in Shift) and Assigned(OnRightClickCell) then
OnRightClickCell(Table,ACol,ARow,X,Y) else
if (ssCtrl in Shift) or (Button<>mbLeft) then begin
if not Assigned(OnHintText) or
not OnHintText(Table,ACol,ARow,Hint) then
Table.ExpandAsString(ARow,ACol,Client,Hint);
// Hint := IntToStr(SelectedID);
ShowHintString(Hint,ACol,ARow,4000);
end else
if (Button=mbLeft) and (ACol=0) and fMarkAllowed and (X<CheckBoxWidth+4) then begin
if Marked[ARow] then // on click: invert current Marked[] checkbox state
fMouseDownMarkedValue := markOff else
fMouseDownMarkedValue := markOn;
Marked[ARow] := (fMouseDownMarkedValue=markOn);
end;
TDrawGrid(Owner).Invalidate;
end;
resourcestring
sPutMarkedRowFirst = 'Sort marked rows first';
procedure TSQLTableToGrid.DrawGridMouseMove(Sender: TObject;
Shift: TShiftState; X, Y: Integer);
var ACol, ARow: integer;
begin
if NotDefined then // avoid any possible GPF
exit;
TDrawGrid(Owner).MouseToCell(X, Y, ACol, ARow);
if cardinal(ACol)>=cardinal(Table.FieldCount) then
exit;
if ARow=0 then begin
// over the checkbox left of the first row: show appropriate hint
if (ACol=0) and fMarkAllowed and (fMarked<>nil) and (X<CheckBoxWidth+4) and
((Hint=nil) or (Hint.Col<>-1) or (Hint.Row<>0)) then begin
if not HeaderCheckboxSelectsInsteadOfSort then begin
ShowHintString(sPutMarkedRowFirst,0,0,1000);
Hint.fCol := -1; // column = -1 for checkbox
end;
end else
// over the first row, i.e. column name: show hint if name was truncated
if (not FieldTitleTruncatedNotShownAsHint) and GetBit64(fFieldNameTruncated,ACol) and
((Hint=nil) or (Hint.Col<>ACol) or (Hint.Row<>0)) then
ShowHintString(Table.GetCaption(0,ACol),ACol,0,1000);
end else
// select/unselect checkbox left of data rows
if (ACol=0) and fMarkAllowed and (fMouseDownMarkedValue<>markNone) and
(X<=CheckBoxWidth+4) then
Marked[ARow] := (fMouseDownMarkedValue=markOn);
end;
procedure TSQLTableToGrid.DrawGridMouseUp(Sender: TObject;
Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
begin
fMouseDownMarkedValue := markNone; // reset Marked[] checkbox state
end;
procedure TSQLTableToGrid.DrawGridSelectCell(Sender: TObject;
ACol, ARow: Integer; var CanSelect: Boolean);
begin
if NotDefined then // avoid any possible GPF
exit;
if not fIncrementalSearchMove then
fIncrementalSearch := ''; // reset incremental key lookup
if Assigned(OnSelectCell) and not fOnSelectCellProcessing then
try
fOnSelectCellProcessing := true; // avoid endless loop or GPF
OnSelectCell(Sender,ACol,ARow,CanSelect);
finally
fOnSelectCellProcessing := false;
end;
end;
procedure TSQLTableToGrid.Resize(Sender: TObject);
var i: integer;
width,tot: cardinal;
begin
if NotDefined then // avoid any possible GPF
exit;
width := TDrawGrid(Owner).ClientWidth-GetSystemMetrics(SM_CXBORDER)*4;
if (width=fLastWidth) then
exit; // draw if necessary
fLastWidth := width;
tot := Table.FieldLengthMeanSum;
for i := 0 to Table.FieldCount-1 do
TDrawGrid(Owner).ColWidths[i] := (width*Table.FieldLengthMean(i))div tot;
// with TDrawGrid(Owner) do SetScrollVPage(Handle,ClientHeight div DefaultRowHeight,RowCount-FixedRows);
end;
function TSQLTableToGrid.NotDefined: boolean;
begin
result := (self=nil) or (Owner=nil) or (Table=nil) or
not Owner.InheritsFrom(TDrawGrid);
end;
procedure TSQLTableToGrid.DrawGridKeyPress(Sender: TObject; var Key: Char);
var F,R: integer;
begin // incremental key lookup
if NotDefined then // avoid any possible GPF
exit;
if Key=#27 then // ESC key reset the lookup string
fIncrementalSearch := '' else
if Key=#8 then begin // BACKDEL key delete last lookup char
if fIncrementalSearch<>'' then
SetLength(fIncrementalSearch,length(fIncrementalSearch)-1);
end else
if Key>=' ' then
if (Key=' ') and (fIncrementalSearch='') then begin
// space with no lookup key -> allow mark/unmark current one
R := TDrawGrid(Owner).Row;
if fMarkAllowed and (R>0) then begin
Marked[R] := not Marked[R];
inc(R);
if R<=Table.RowCount then
TDrawGrid(Owner).Row := R else // and go to next row
TDrawGrid(Owner).Invalidate;
end;
exit;
end else
fIncrementalSearch := fIncrementalSearch+RawUTF8(NormToUpper[AnsiChar(Key)]);
if fIncrementalSearch='' then begin
if fHint<>nil then
fHint.Hide;
exit; // nothing to search
end;
// search from the next row
F := fCurrentFieldOrder;
R := Table.SearchValue(fIncrementalSearch,TDrawGrid(Owner).Row+1,fCurrentFieldOrder,Client);
if R=0 then begin // not found: search from the beginning
R := Table.SearchValue(fIncrementalSearch,1,fCurrentFieldOrder,Client);
if R=0 then begin // not found in this field: search in all fields
R := Table.SearchValue(fIncrementalSearch,TDrawGrid(Owner).Row+1,@F,Client);
if R=0 then // not found: search from the beginning
R := Table.SearchValue(fIncrementalSearch,1,@F,Client);
end;
end;
if R>0 then begin
fIncrementalSearchMove := true; // DrawGridSelectCell() won't reset fIncremental
TDrawGrid(Owner).Row := R;
fIncrementalSearchMove := false;
ShowHintString(UTF8ToString(fIncrementalSearch),F,R,2000,clNavy);
end else // not found: display searched string in red
ShowHintString(UTF8ToString(fIncrementalSearch)+'?',fCurrentFieldOrder,
TDrawGrid(Owner).Row,2000,clRed);
end;
procedure TSQLTableToGrid.ShowHintString(const Text: string; ACol, ARow, Time: integer;
FontColor: TColor=clBlack);
begin
if NotDefined then // avoid any possible GPF
exit;
if Text='' then begin
if fHint<>nil then
fHint.Hide;
exit;
end;
fHint.fCol := ACol;
fHint.fRow := ARow;
with TDrawGrid(Owner).CellRect(ACol,ARow) do
fHint.ShowDelayedString(Text,TDrawGrid(Owner),Right,Top+2,Time,FontColor);
end;
procedure TSQLTableToGrid.DrawGridKeyDown(Sender: TObject; var Key: Word;
Shift: TShiftState);
var F: integer;
begin
if NotDefined or (Shift<>[]) then // avoid any possible GPF
exit;
case Key of
VK_LEFT: // LEFT ARROW key sort previous column
if fCurrentFieldOrder>0 then
F := fCurrentFieldOrder-1 else
F := Table.FieldCount-1;
VK_RIGHT: // RIGHT ARROW key sort next column
if fCurrentFieldOrder>=Table.FieldCount-1 then
F := 0 else
F := fCurrentFieldOrder+1;
else exit;
end;
SortChange(F);
Key := 0; // we proceed this key -> caller will ignore it
end;
procedure TSQLTableToGrid.SortChange(ACol: integer);
begin
if not NotDefined and (cardinal(ACol)<cardinal(Table.FieldCount)) then
if fCurrentFieldOrder=ACol then
// same column -> toggle sorting order
SortForce(ACol,not fFieldOrder[ACol]) else
// column changed -> sort ascending first
SortForce(ACol,true);
end;
procedure TSQLTableToGrid.IDColumnHide;
begin
if NotDefined or not Table.IDColumnHide then
exit;
TDrawGrid(Owner).ColCount := Table.FieldCount; // we loose one column
fCurrentFieldOrder := -1; // force no previous column -> always ascending order
SortChange(0);
end;
function TSQLTableToGrid.SelectedID: TID;
begin
if NotDefined then
result := 0 else
result := Table.IDColumnHiddenValue(TDrawGrid(Owner).Row);
end;
function TSQLTableToGrid.SelectedRecordCreate: TSQLRecord;
var aID: integer;
RecordType: TSQLRecordClass;
begin
aID := SelectedID;
if (aID<=0) or (fClient=nil) then
result := nil else begin
RecordType := TSQLRecordClass(Table.QueryRecordType);
if (RecordType=nil) or not RecordType.InheritsFrom(TSQLRecord) then
result := nil else
result := RecordType.Create(fClient,aID);
end;
end;
function TSQLTableToGrid.GetAlign(aCol: cardinal): TSQLTableToGridAlign;
begin
if (self=nil) or (Table=nil) or (aCol>=cardinal(length(fAligned))) or
(aCol>=Cardinal(Table.FieldCount)) then
result := alLeft else
result := fAligned[aCol];
end;
procedure TSQLTableToGrid.SetAlign(aCol: cardinal; Value: TSQLTableToGridAlign);
begin
if (self=nil) or (Table=nil) or (aCol>=Cardinal(Table.FieldCount)) then
exit;
if length(fAligned)<Table.FieldCount then
SetLength(fAligned,Table.FieldCount);
fAligned[aCol] := Value;
end;
function TSQLTableToGrid.GetCustomFormat(aCol: cardinal): string;
begin
if (self=nil) or (Table=nil) or (aCol>=cardinal(length(fCustomFormat))) or
(aCol>=Cardinal(Table.FieldCount)) then
result := '' else
result := fCustomFormat[aCol];
end;
procedure TSQLTableToGrid.SetCustomFormat(aCol: cardinal; const Value: string);
begin
if (self=nil) or (Table=nil) or (aCol>=Cardinal(Table.FieldCount)) then
exit;
if length(fCustomFormat)<Table.FieldCount then
SetLength(fCustomFormat,Table.FieldCount);
fCustomFormat[aCol] := Value;
end;
procedure TSQLTableToGrid.SetAligned(const aCols: array of cardinal; aAlign: TSQLTableToGridAlign);
var i: integer;
begin
if Table<>nil then
for i := 0 to high(aCols) do
SetAlign(aCols[i],aAlign);
end;
procedure TSQLTableToGrid.SetAlignedByType(aFieldType: TSQLFieldType;
aAlign: TSQLTableToGridAlign);
var i: integer;
begin
if (self=nil) or (Table=nil) then
exit;
for i := 0 to Table.FieldCount-1 do
if Table.FieldType(i)=aFieldType then
SetAlign(i,aAlign);
end;
procedure TSQLTableToGrid.SetCustomFormatByType(aFieldType: TSQLFieldType;
const aCustomFormat: string);
var i: integer;
begin
if (self=nil) or (Table=nil) then
exit;
for i := 0 to Table.FieldCount-1 do
if Table.FieldType(i)=aFieldType then
SetCustomFormat(i,aCustomFormat);
end;
procedure TSQLTableToGrid.PageChanged;
begin
if (Self<>nil) and (Hint<>nil) then
Hint.Hide;
end;
function TSQLTableToGrid.Refresh(ForceRefresh: Boolean=false;
AutoResizeColumns: Boolean=true): boolean;
var Refreshed: boolean;
aID: integer;
begin
if self=nil then
result := false else begin
aID := Table.IDColumnHiddenValue(TDrawGrid(Owner).Row);
if ForceRefresh then
result := true else
result := Client.UpdateFromServer([Table],Refreshed) and Refreshed;
if result then
AfterRefresh(aID,AutoResizeColumns);
end;
end;
procedure TSQLTableToGrid.AfterRefresh(const aID: TID; AutoResizeColumns: boolean);
var CurrentRow: integer;
Bulk: boolean;
begin
with TDrawGrid(Owner) do begin
if Table.RowCount=0 then
RowCount := 2 else
RowCount := Table.RowCount+1;
if Table.FieldCount<>ColCount then begin
// we get results from a void table for the first time
ColCount := Table.FieldCount;
SetLength(fFieldOrder,Table.FieldCount);
end;
CurrentRow := Table.RowFromID(aID);
if CurrentRow=0 then
CurrentRow := 1;
Row := CurrentRow;
TopRow := 1;
Invalidate;
end;
if AutoResizeColumns then
Resize(nil);
if Assigned(OnSelectCell) then
OnSelectCell(Owner,0,CurrentRow,Bulk); // refresh details
end;
procedure TSQLTableToGrid.SetFieldLengthMean(const Lengths: RawUTF8; aMarkAllowed: boolean);
var L, i: integer;
c: AnsiChar;
Means: array of cardinal;
begin
if self=nil then Exit;
fMarkAllowed := aMarkAllowed;
L := length(Lengths);
if L=0 then begin
SetLength(Means,Table.FieldCount);
for i := 0 to Table.FieldCount-1 do
Means[i] := 10; // some fixed width
end else
if Table.FieldCount=L then begin
SetLength(Means,L);
for i := 0 to L-1 do begin
c := Lengths[i+1];
if c in ['a'..'z'] then begin
Aligned[i] := alCenter;
dec(c,32);
end;
Means[i] := ord(c)+(1-ord('A'));
end;
Table.SetFieldLengthMean(Means);
end;
if aMarkAllowed then
Table.FieldLengthMeanIncrease(0,2); // space for Marked[] checkbox e.g.
end;
procedure TSQLTableToGrid.SetFieldFixedWidth(aColumnWidth: integer);
var i: integer;
begin
with TDrawGrid(Owner) do
for i := 0 to ColCount-1 do
ColWidths[i] := aColumnWidth;
end;
function TSQLTableToGrid.GetGridColumnWidths: RawUTF8;
var i: integer;
w: RawUTF8;
begin
result := '';
if self<>nil then
with TDrawGrid(Owner) do
for i := 0 to ColCount-1 do begin
Int32ToUtf8(ColWidths[i],w);
if i=0 then
result := w else
result := result+','+w;
end;
end;
procedure TSQLTableToGrid.SetGridColumnWidths(const Value: RawUTF8);
var P: PUTF8Char;
i,w: integer;
begin
if self=nil then
exit;
P := pointer(Value);
with TDrawGrid(Owner) do
for i := 0 to ColCount-1 do begin
w := GetNextItemCardinal(P);
if w=0 then
w := 100;
ColWidths[i] := w;
end;
end;
function TSQLTableToGrid.GetMarked(RowIndex: integer): boolean;
begin
dec(RowIndex);
if (self=nil) or (fMarked=nil) or
(cardinal(RowIndex)>=cardinal(length(fMarked)shl 3)) then
result := false else
result := GetBitPtr(pointer(fMarked),RowIndex);
end;
procedure TSQLTableToGrid.SetMarked(RowIndex: integer;
const Value: boolean);
var n: integer;
begin
dec(RowIndex);
if (self=nil) or (cardinal(RowIndex)>=cardinal(Table.RowCount)) then
exit;
n := (Table.RowCount shr 3)+1;
if length(fMarked)<n then // need to allocate/expand fMarked[] array?
SetLength(fMarked,n); // initializes all expanded bytes to 0
if Value then
SetBitPtr(pointer(fMarked),RowIndex) else
UnSetBitPtr(pointer(fMarked),RowIndex)
end;
procedure TSQLTableToGrid.SetMark(aAction: TSQLAction);
var i: integer;
V: Int64;
current: TDateTime;
TimeMin, TimeMax: TTimeLogBits;
const
DIFFTIME: array[actMarkOlderThanOneDay..actMarkOlderThanOneYear] of double =
(1,7,31,183,365); // 183 = more or less half a year
begin
if NotDefined then
exit;
with TDrawGrid(Owner) do
case aAction of
actMarkAllEntries:
for i := 1 to RowCount do
Marked[i] := true;
actUnMarkAll:
if fMarked<>nil then
Finalize(fMarked);
actmarkInverse:
for i := 1 to RowCount do
Marked[i] := not Marked[i];
else
if FieldIndexTimeLogForMark >= 0 then begin
// use TDateTime calculation because TTimeLog is not duration compatible
current := Trunc(NowUTC);
case aAction of
actMarkToday: begin
TimeMin.From(current, true);
TimeMax.From(current + 1, true);
end;
actMarkThisWeek: begin
TimeMin.From(StartOfTheWeek(current), true);
TimeMax.From(EndOfTheWeek(current) + 1, true);
end;
actMarkThisMonth: begin
TimeMin.From(StartOfTheMonth(current), true);
TimeMax.From(EndOfTheMonth(current) + 1, true);
end;
actMarkYesterday: begin
TimeMin.From(current - 1, true);
TimeMax.From(current, true);
end;
actMarkLastWeek: begin
TimeMin.From(IncWeek(StartOfTheWeek(current), -1), true);
TimeMax.From(StartOfTheWeek(current), true);
end;
actMarkLastMonth: begin
TimeMin.From(IncMonth(StartOfTheMonth(current), -1), true);
TimeMax.From(StartOfTheMonth(current), true);
end;
actMarkOlderThanOneDay..actMarkOlderThanOneYear: begin
TimeMin.Value := 1; // = 1 second after Jesus' birth = not <> 0
TimeMax.From(NowUTC - DIFFTIME[aAction],true);
end;
else exit;
end;
for i := 1 to RowCount do begin
SetInt64(Table.Get(i, fFieldIndexTimeLogForMark), V);
if (V>=TimeMin.Value) and (V<=TimeMax.Value) then
Marked[i] := true;
end;
end;
end;
TDrawGrid(Owner).Invalidate; // refresh screen
end;
function TSQLTableToGrid.GetMarkAvailable: boolean;
var i: integer;
begin
result := fMarkAllowed and (fMarked<>nil);
if not result then
exit;
for i := 0 to Table.RowCount-1 do // very any bit is realy set
if GetBitPtr(pointer(fMarked),i) then
exit;
result := false;
end;
function TSQLTableToGrid.GetMarkedIsOnlyCurrrent: boolean;
begin
with TDrawGrid(Owner) do
result := fMarkAllowed and (fMarked<>nil) and Marked[Row] and
(GetBitsCount(fMarked[0],RowCount)=1);
end;
function TSQLTableToGrid.GetMarkedTotalCount: integer;
begin
with TDrawGrid(Owner) do
if not fMarkAllowed or (fMarked=nil) then
result := 0 else
result := GetBitsCount(fMarked[0],RowCount);
end;
function TSQLTableToGrid.ExpandRowAsString(Row: integer; Client: TObject): string;
var F, i: integer;
Text: string; // generic VCL-ready string
begin
result := '';
if (self=nil) or (cardinal(Row)>cardinal(Table.RowCount)) or (Table.FieldCount<=0) then
exit;
for F := 0 to Table.FieldCount-1 do begin
if (not Assigned(OnValueText)) or
(not OnValueText(Table,F,Row,text)) then
Table.ExpandAsString(Row,F,Client,Text);
i := pos(#13,Text); // trim multi-line text to first line
if i>0 then
SetLength(Text,i-1);
if (F>0) and (text<>'') then
text := ' '+text;
result := result+text;
end;
end;
procedure TSQLTableToGrid.OnTableUpdate(State: TOnTableUpdateState);
begin
if (self=nil) or (fMarked=nil) then
exit; // wrong parameters
case State of
tusPrepare:
// save current marked entries
if fMarked<>nil then begin
Table.IDArrayFromBits(fMarked[0],fOnTableUpdateID);
exit; // don't Finalize(fOnTableUpdateID)
end;
tusChanged:
// restore marked entries
if fOnTableUpdateID<>nil then
Table.IDArrayToBits(fMarked[0],fOnTableUpdateID);
end;
// tusNoChange or tusChanged: release IDs memory
if fOnTableUpdateID<>nil then
Finalize(fOnTableUpdateID);
end;
function TSQLTableToGrid.GetMarkedBits: pointer;
begin
result := fMarked;
end;
function TSQLTableToGrid.GetDrawGrid: TDrawGrid;
begin
if self=nil then
result := nil else
result := TDrawGrid(Owner);
end;
function TSQLTableToGrid.GetFieldIndexTimeLogForMark: integer;
var F: integer;
begin
if Self=nil then begin
result := -1;
exit;
end;
if fFieldIndexTimeLogForMark=-2 then begin
fFieldIndexTimeLogForMark := -1;
for F := 0 to Table.FieldCount-1 do
if Table.FieldType(F)=sftTimeLog then begin
fFieldIndexTimeLogForMark := F;
break;
end;
end;
result := fFieldIndexTimeLogForMark;
end;
{ TVistaForm }
{$ifdef VISTAFORM}
var
AppFormTaskBarButtonHidden: boolean = false;
procedure HideAppFormTaskBarButton;
{$ifdef ISDELPHI2007ANDUP}
begin
end;
{$else}
var ExtendedStyle: Integer;
begin
if AppFormTaskBarButtonHidden then
exit;
AppFormTaskBarButtonHidden := true;
ShowWindow(Application.Handle, SW_HIDE);
ExtendedStyle := GetWindowLong(Application.Handle, GWL_EXSTYLE);
SetWindowLong(Application.Handle, GWL_EXSTYLE,
ExtendedStyle and not WS_EX_APPWINDOW or WS_EX_TOOLWINDOW);
ShowWindow(Application.Handle, SW_SHOW);
Application.DialogHandle := Application.Handle;
end;
{$endif}
procedure TVistaForm.BtnClick(Sender: TObject);
begin
if Sender=fCloseBtn then
Close else
if Sender=fMinimizeBtn then
WindowState := wsMinimized else
if (Sender=fMaximizeBtn) or Sender.InheritsFrom(TLabel) then
if WindowState=wsMaximized then
WindowState := wsNormal else
WindowState := wsMaximized;
end;
procedure TVistaForm.CreateParams(var Params: TCreateParams);
begin
HideAppFormTaskBarButton; // check if not already hidden
inherited;
{$ifndef ISDELPHI2007ANDUP}
Params.ExStyle := Params.ExStyle and not WS_EX_TOOLWINDOW or
WS_EX_APPWINDOW; // this form will appear in the TaskBar
{$endif}
if fNoCaption<>nil then begin
Params.ExStyle := Params.ExStyle or WS_EX_STATICEDGE;
Params.Style := Params.Style or WS_SIZEBOX;
end;
end;
procedure TVistaForm.NoCaptionMouseDown(Sender: TObject;
Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
const
SC_DRAGMOVE = $F012;
begin
if Button = mbLeft then
begin
ReleaseCapture;
Perform(WM_SYSCOMMAND, SC_DRAGMOVE, 0);
end;
end;
procedure TVistaForm.SetNoCaption(aTopMostPanel: TPanel; aLabelLeft: integer);
const BUT_SIZE = 16;
procedure SetEvent(Event: TMouseEvent);
var i: integer;
begin
if fNoCaption<>nil then begin
fNoCaption.OnMouseDown := Event;
for i := 0 to fNoCaption.ComponentCount-1 do
if fNoCaption.Components[i].InheritsFrom(TLabel) then
with TLabel(fNoCaption.Components[i]) do
if not Assigned(OnClick) then
OnMouseDown := Event;
end;
end;
var R: integer;
function Btn(const ResName: string): TSpeedButton;
begin
dec(R,BUT_SIZE+1);
result := TSpeedButton.Create(aTopMostPanel);
result.Parent := aTopMostPanel;
result.SetBounds(R,2,BUT_SIZE+1,BUT_SIZE);
result.Anchors := [akRight, akTop];
result.Glyph.LoadFromResourceName(HInstance,ResName);
result.OnClick := BtnClick;
result.Flat := true;
end;
begin
if aTopMostPanel=fNoCaption then
exit;
SetEvent(nil);
fNoCaption := aTopMostPanel;
if aTopMostPanel<>nil then begin
fNoCaptionLabel := TLabel.Create(aTopMostPanel);
R := aTopMostPanel.ClientWidth-4;
fCloseBtn := Btn('ButClose');
fMaximizeBtn := Btn('ButMax');
fMinimizeBtn := Btn('ButMin');
with fNoCaptionLabel do begin
Parent := aTopMostPanel;
Transparent := true;
AutoSize := false;
SetBounds(aLabelLeft,2,R-aLabelLeft,20);
Anchors := [akLeft, akRight, akBottom, akTop];
Alignment := Classes.taCenter;
OnDblClick := BtnClick;
end;
SetEvent(NoCaptionMouseDown);
BorderStyle := bsNone;
{$ifdef ISDELPHI2007ANDUP}
RecreateWnd;
{$endif}
end else
BorderStyle := bsSizeable;
end;
procedure TVistaForm.WMSyscommand(var M: TMessage);
begin
{$ifdef ISDELPHI2007ANDUP}
inherited;
{$else}
case (M.WParam and $FFF0) of
SC_MINIMIZE, SC_RESTORE, SC_MAXIMIZE: begin
M.Result := DefWindowProc(Handle, M.Msg, M.WParam, M.LParam);
ShowWindow(Application.Handle, SW_HIDE);
end;
else
inherited;
end;
{$endif}
end;
{$endif}
{ TSynLabeledEdit }
constructor TSynLabeledEdit.Create(AOwner: TComponent);
begin
inherited;
ShowHint := True;
MaxValue := 100;
MinValue := 1;
Text := '';
end;
function TSynLabeledEdit.GetValue: Variant;
var Txt: string;
begin
Txt := trim(Text);
if Txt='' then
if RangeChecking then begin
result := MinValue;
Text := MinValue;
end else
VarClear(result) else begin
if not IsValid(Txt,result) then begin
if RaiseExceptionOnError then
raise ESynLabeledEdit.CreateFmt(SErrorFieldNotValid, [EditLabel.Caption,
GetEnumCaption(TypeInfo(TSynLabeledEditKind),Kind)]);
if RangeChecking then
result := MinValue else
VarClear(result);
end;
end;
if RangeChecking and (Result<MinValue) then begin
Text := MinValue;
if RaiseExceptionOnError then
raise ESynLabeledEdit.CreateFmt(SErrorFieldTooSmall,
[EditLabel.Caption,string(MinValue)]);
end;
if RangeChecking and (Result>MaxValue) then begin
Text := MaxValue;
if RaiseExceptionOnError then
raise ESynLabeledEdit.CreateFmt(SErrorFieldTooLarge,
[EditLabel.Caption,string(MaxValue)]);
end;
end;
function TSynLabeledEdit.IsValid(const Txt: string; var ToValue: Variant): Boolean;
var err: integer;
resInt32: integer;
resInt64: Int64;
resDouble: Double;
resCurrency: Currency;
begin
result := false;
case Kind of
sleInteger: begin
val(Txt,resInt32,err);
if err<>0 then exit;
ToValue := resInt32;
end;
sleInt64: begin
val(Txt,resInt64,err);
if err<>0 then exit;
ToValue := resInt64;
end;
sleCurrency: begin
val(Txt,resDouble,err);
if err<>0 then exit;
resCurrency := resDouble;
ToValue := resCurrency;
end;
sleDouble: begin
val(Txt,resDouble,err);
if err<>0 then exit;
ToValue := resDouble;
end;
end;
result := true;
end;
procedure TSynLabeledEdit.KeyPress(var Key: char);
Var Temp: Variant;
TempString: string;
begin
inherited;
if Key=#8 then exit;
if Key=',' then
Key := '.';
if (Kind in [sleInteger,sleInt64]) and (Key='.') then
Key := #0;
if ((Key<'0') or (Key>'9')) and (Key<>'.') then begin
Key := #0;
Beep;
exit;
end;
TempString := Text;
if (TempString=#0) or (Self.SelText=TempString) then
exit;
Insert(Key, TempString, Self.SelStart + 1);
if IsValid(TempString,Temp) and
RangeChecking and (Temp>MaxValue) then begin
Key := #0;
Beep;
end;
end;
procedure TSynLabeledEdit.MouseMove(Shift: TShiftState; X,Y: integer);
var H: string;
begin
inherited;
if RangeChecking then
H := format(SMinMaxValue,[string(FMinValue),string(FMaxValue)]);
if FAdditionalHint <> '' then
H := trim(FAdditionalHint + #13#10 + H);
Hint := H;
end;
procedure TSynLabeledEdit.SetValue(const Value: Variant);
begin
Text := Value;
end;
function TSynLabeledEdit.ToString(NumberOfDigits: integer): string;
var numberOfMissingDigits: integer;
begin
Result := Value;
numberOfMissingDigits := NumberOfDigits - Length(Result);
if numberOfMissingDigits > 0 then
Result := StringOfChar('0', numberOfMissingDigits) + Result;
end;
function TSynLabeledEdit.ValidateValue: boolean;
var V: Variant;
begin
result := IsValid(Text,V);
if RangeChecking and result then
result := (V>=MinValue) and (V<=MaxValue);
end;
procedure FillStringGrid(Source: TSQLTable; Dest: TStringGrid; Client: TSQLRest);
var C,R: integer;
s: string;
begin
if (Source=nil) or (Dest=nil) then
exit; // avoid GPF
Dest.ColCount := Source.FieldCount;
Dest.RowCount := Source.RowCount+1;
for R := 0 to Source.RowCount+1 do
for C := 0 to Source.FieldCount-1 do begin
Source.ExpandAsString(R,C,Client,s); // will do all the magic
Dest.Cells[C,R] := s;
end;
end;
{ TUIComponentsPersist }
function TUIComponentsPersist.GetFileName: TFileName;
begin
if fFileName = '' then
fFileName := ChangeFileExt(ExeVersion.ProgramFileName, '.default');
result := fFileName;
end;
procedure TUIComponentsPersist.LoadFromFile;
begin
fLoadedJson := StringFromFile(FileName);
LoadFromVariant(_JsonFast(fLoadedJson));
end;
procedure TUIComponentsPersist.LoadFromVariant(const aDoc: variant);
var
i: integer;
prop: PPropInfo;
doc: PDocVariantData;
v: PVariant;
function HasProp(const PropName: ShortString): boolean;
begin
result := false;
if not doc^.GetAsPVariant(ToUTF8(fTracked[i].Name), v) then
exit;
prop := ClassFieldPropWithParents(fTracked[i].ClassType, PropName);
result := prop <> nil;
end;
begin
doc := _Safe(aDoc);
if doc^.Count = 0 then
exit;
for i := 0 to high(fTracked) do
if HasProp('Text') then
Prop^.SetGenericStringValue(fTracked[i], SynCommons.VariantToString(v^))
else if HasProp('Checked') then
Prop^.SetOrdValue(fTracked[i], ord(boolean(v^)));
end;
procedure TUIComponentsPersist.SaveToFile;
var
json: RawUTF8;
begin
json := _Safe(SaveToVariant)^.ToJSON('', '', jsonHumanReadable);
if json <> fLoadedJson then begin
FileFromString(json, FileName);
fLoadedJson := json;
end;
end;
function TUIComponentsPersist.SaveToVariant: variant;
var
i: integer;
prop: PPropInfo;
doc: TDocVariantData;
name: RawUTF8;
function HasProp(const PropName: ShortString): boolean;
begin
prop := ClassFieldPropWithParents(fTracked[i].ClassType, PropName);
result := prop <> nil;
if result then
name := ToUTF8(fTracked[i].Name);
end;
begin
doc.InitFast;
for i := 0 to high(fTracked) do
if HasProp('Text') then
doc.AddValue(name, ToUTF8(Prop^.GetGenericStringValue(fTracked[i])))
else if HasProp('Checked') then
doc.AddValue(name, boolean(Prop^.GetOrdValue(fTracked[i])));
result := variant(doc);
end;
procedure TUIComponentsPersist.TrackControls(const ctrls: array of TComponent);
var
i: PtrInt;
begin
for i := 0 to high(ctrls) do
ObjArrayAddOnce(fTracked, ctrls[i]);
end;
procedure Register;
begin
RegisterComponents('Synopse',[TSynLabeledEdit]);
end;
initialization
{$ifdef FPC}
// LCL/Lazarus components expect UTF-8 encoding for strings
CurrentAnsiConvert := UTF8AnsiConvert;
{$endif FPC}
end.