/// Form handling queries to a User Interface Grid 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 mORMotUIQuery; (* 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): 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.5 - first implementation of the TQueryForm class: handle default and custom queries Version 1.9 - custom queries can now handle standard operators, following a specified set - TSQLQueryEvent() usage has therefore been modified for custom queries Version 1.15 - now use TMS component pack only if USETMSPACK global conditional is defined: by default, will use only VCL components (i.e. TSynButton) for the form - handle TModTime published property / sftModTime SQL field - handle TCreateTime published property / sftCreateTime SQL field Version 1.18 - renamed SQLite3UIQuery.pas to mORMotUIQuery.pas *) interface uses Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms, SynCommons, SynTaskDialog, mORMot, mORMotUI, mORMotUILogin, mORMoti18n, StdCtrls, ComCtrls; type /// this Form perform simple Visual queries to a Grid // - mark or unmark items, depending of the input of the User on this form // - use TSQLRest.QueryIsTrue() method for standard fields and parameters // - use TSQLQueryCustom records previously registered to the TSQLRest class, // by the TSQLRest.QueryAddCustom() method, to add some custom field search // (e.g. to search into fields not available on the grid, or some data // embedded inside a field - like .INI-like section entries) // - in practice, the query is very fast (immediate for standard fields and // parameters), but can demand some bandwith for custom field search (since // data has to be retrieved from the server to search within) TQueryForm = class(TVistaForm) Label1: TLabel; Where: TComboBox; Label2: TLabel; Operation: TComboBox; Label3: TLabel; Label4: TLabel; MarkedOnly: TCheckBox; procedure FormShow(Sender: TObject); procedure WhereChange(Sender: TObject); procedure BtnMarkClick(Sender: TObject); protected Table: TSQLTable; TableClass: TSQLRecordClass; TableToGrid: TSQLTableToGrid; Client: TSQLRest; FieldType: TSQLFieldType; FieldIndex: integer; Reference: TWinControl; BtnMark: TSynButton; BtnUnmark: TSynButton; BtnCancel: TSynButton; QueryOpEnumType: PEnumType; /// override this if you want to select which enumerates values must // appear in the "What to search" criteria // - return nil by default, i.e. all enumerates must be added // - can return a pointer to a set of enumerates to be shown function EnumerateUsedBits(EnumType: PEnumType): pointer; virtual; public /// create the window instance // - all parameters (especially TSQLRest instance to use for custom search) // are retrieved via the supplied TSQLTableToGrid // - caller must have used TSQLRest.QueryAddCustom() method to register // some custom queries, if necessary constructor Create(aOwner: TComponent; aTableToGrid: TSQLTableToGrid); reintroduce; end; implementation {$R *.dfm} resourcestring sBtnMark = 'MARK if match'; sBtnUnMark = 'UNMARK if match'; constructor TQueryForm.Create(aOwner: TComponent; aTableToGrid: TSQLTableToGrid); var i: integer; begin inherited Create(aOwner); BtnMark := TSynButton.Create(Self); with BtnMark do begin Parent := self; SetBounds(48,264,145,33); Caption := sBtnMark; ModalResult := mrOk; TabOrder := 3; OnClick := BtnMarkClick end; BtnUnMark := TSynButton.Create(Self); with BtnUnMark do begin Parent := self; SetBounds(48,304,145,33); Caption := sBtnUnMark; ModalResult := mrOk; TabOrder := 4; OnClick := BtnMarkClick end; BtnCancel := TSynButton.CreateKind(self,cbCancel,200,304,89,33); if aTableToGrid=nil then exit; TableToGrid := aTableToGrid; Table := aTableToGrid.Table; Client := aTableToGrid.Client; for i := 0 to Table.FieldCount-1 do Where.Items.Add(Table.GetCaption(0,i)); if Assigned(Table.QueryTables) then begin TableClass := TSQLRecordClass(Table.QueryTables[0]); for i := 0 to high(Client.QueryCustom) do with Client.QueryCustom[i] do if Event(TableClass,0,sftUnknown,nil,i,nil) then // if this custom query is available to this table Where.Items.Add(EnumType^.GetCaption(EnumIndex)); end; MarkedOnly.Enabled := TableToGrid.MarkAvailable; QueryOpEnumType := PTypeInfo(TypeInfo(TSQLQueryOperator))^.EnumBaseType; end; procedure TQueryForm.FormShow(Sender: TObject); begin if Where.Items.Count>0 then begin Where.ItemIndex := 0; WhereChange(nil); end else begin BtnMark.Enabled := false; BtnUnMark.Enabled := false; end; SetStyle(self); end; procedure TQueryForm.WhereChange(Sender: TObject); var i, max: integer; EnumType: PEnumType; none: TSQLQueryOperator; begin Operation.Clear; FieldIndex := Where.ItemIndex; if (Table=nil) or (cardinal(FieldIndex)>=cardinal(Table.FieldCount)) then begin // QueryCustom[] or no Field -> add specified Operator(s) FieldType := sftUTF8Text; Dec(FieldIndex,Table.FieldCount); if cardinal(FieldIndex)<=cardinal(high(Client.QueryCustom)) then for i := Ord(qoEqualTo) to QueryOpEnumType^.MaxValue do if TSQLQueryOperator(i) in Client.QueryCustom[FieldIndex].Operators then Operation.Items.AddObject(QueryOpEnumType^.GetCaption(i),pointer(i)); Operation.Enabled := Operation.Items.Count>0; if not Operation.Enabled then begin none := qoNone; Operation.Items.Add(QueryOpEnumType^.GetCaption(none)); end; Label2.Enabled := Operation.Enabled; FieldIndex := -1; // mark custom field end else begin // add "basic" operations from standard TSQLTable Field FieldType := Table.FieldType(FieldIndex,@EnumType); Operation.Enabled := true; Label2.Enabled := true; if FieldType in [sftAnsiText,sftUTF8Text,sftObject,sftVariant] then max := QueryOpEnumType^.MaxValue else // allow textual operations max := ord(qoGreaterThanOrEqualTo); // numeric operations only for i := ord(qoEqualTo) to max do Operation.Items.AddObject(QueryOpEnumType^.GetCaption(i),pointer(i)); end; Operation.ItemIndex := 0; // create corresponding Reference field FreeAndNil(Reference); case FieldType of sftDateTime, sftDateTimeMS, sftTimeLog, sftModTime, sftCreateTime, sftUnixTime, sftUnixMSTime: begin Reference := TDateTimePicker.Create(self); Reference.Parent := self; end; sftEnumerate: if EnumType<>nil then begin Reference := TComboBox.Create(self); with TComboBox(Reference) do begin Parent := self; Style := csDropDownList; EnumType.AddCaptionStrings(Items,EnumerateUsedBits(EnumType)); ItemIndex := 0; DropDownCount := 32; end; end; end; if Reference=nil then begin // default reference Reference := TEdit.Create(self); Reference.Parent := self; end; Reference.SetBounds(48,200,169,21); end; procedure TQueryForm.BtnMarkClick(Sender: TObject); var Row, i: integer; SoundEx: TSynSoundEx; Ope: integer; Ref: RawUTF8; R, V: PUTF8Char; aEvent: TSQLQueryEvent; AllRows, OK, ToBeMarked: boolean; const CONVERT: array[qoSoundsLikeEnglish..qoSoundsLikeSpanish] of TSynSoundExPronunciation = (sndxEnglish,sndxFrench,sndxSpanish); begin if Table=nil then exit; ToBeMarked := (Sender=BtnMark); // other possibility is BtnUnmark -> false AllRows := not (MarkedOnly.Enabled and MarkedOnly.Checked); if Operation.Enabled then begin // normal operator search Ope := integer(Operation.Items.Objects[Operation.ItemIndex]); if Reference.InheritsFrom(TEdit) then Ref := StringToUTF8(TEdit(Reference).Text) else if Reference.InheritsFrom(TComboBox) then begin i := TComboBox(Reference).ItemIndex; if i<0 then Exit; // avoid out of range error Int32ToUtf8(Integer(TComboBox(Reference).Items.Objects[i]),Ref); end else if Reference.InheritsFrom(TDateTimePicker) then with TDateTimePicker(Reference) do case FieldType of sftDateTime, sftDateTimeMS: Ref := DateTimeToIso8601(DateTime,false,'T',FieldType=sftDateTimeMS); sftTimeLog, sftModTime, sftCreateTime: Int64ToUtf8(TimeLogFromDateTime(DateTime),Ref); sftUnixTime: Int64ToUtf8(DateTimeToUnixTime(DateTime),Ref); sftUnixMSTime: Int64ToUtf8(DateTimeToUnixMSTime(DateTime),Ref); end; Ref := Trim(Ref); if Ref='' then begin ModalResult := mrNone; exit; end; R := pointer(Ref); case TSQLQueryOperator(Ope) of qoContains, qoBeginWith: begin // expected to be already uppercase Ref := UpperCase(Ref); R := pointer(Ref); end; qoSoundsLikeEnglish..qoSoundsLikeSpanish: begin Ref := UpperCase(Ref); SoundEx.Prepare(pointer(Ref),CONVERT[TSQLQueryOperator(Ope)]); R := pointer(@Soundex); // typecast of the prepared TSynSoundEx object end; end; end else begin // custom query with no Operator if not Reference.InheritsFrom(TEdit) then exit; Ope := 0; Ref := StringToUTF8(Trim(TEdit(Reference).Text)); R := pointer(Ref); assert(FieldIndex<0); end; if FieldIndex<0 then begin i := Where.ItemIndex-Table.FieldCount; if cardinal(i)>cardinal(high(Client.QueryCustom)) then exit; with Client.QueryCustom[i] do begin FieldType := TSQLFieldType(EnumIndex+64); aEvent := Event; end; end else begin aEvent := Client.QueryIsTrue; end; V := R; // we need Value<>nil Screen.Cursor := crHourGlass; // immediate for standard query, but how knows? try for Row := 1 to Table.RowCount do if AllRows or TableToGrid.Marked[Row] then begin if FieldIndex>=0 then V := Table.Get(Row,FieldIndex); OK := aEvent(TableClass,Table.IDColumnHiddenValue(Row), FieldType, V, Ope, R); if not AllRows and ToBeMarked then // BtnMark + MarkOnly.Checked -> reset TableToGrid.Marked[Row] := OK else if OK then TableToGrid.Marked[Row] := ToBeMarked; // set/reset from BtnMark/BtnUnmark end; TableToGrid.DrawGrid.Invalidate; // refresh screen finally Screen.Cursor := crDefault; end; end; function TQueryForm.EnumerateUsedBits(EnumType: PEnumType): pointer; begin result := nil; // no custom selection by default end; end.