mirror of
https://github.com/HeidiSQL/HeidiSQL.git
synced 2026-03-13 09:24:25 +08:00
1914 lines
58 KiB
ObjectPascal
1914 lines
58 KiB
ObjectPascal
unit grideditlinks;
|
|
|
|
// The editor links, instanciated by VirtualTree.CreateEditor
|
|
|
|
{$mode delphi}{$H+}
|
|
|
|
interface
|
|
|
|
uses
|
|
Forms, Graphics, Messages, laz.VirtualTrees, ComCtrls, SysUtils, Classes,
|
|
StdCtrls, ExtCtrls, CheckLst, Controls, Types, Dialogs, Menus, MaskEdit, DateUtils, Math,
|
|
dbconnection, dbstructures, apphelpers, generic_types, texteditor, bineditor, lazaruscompat,
|
|
{$IFNDEF FREEBSD}System.UITypes,{$ENDIF} RegExpr, extra_controls, EditBtn, LCLType, LCLIntf;
|
|
|
|
type
|
|
// Radio buttons and checkboxes which do not pass <Enter> key to their parent control
|
|
// so a OnKeyDown event using <Enter> has the chance to end editing.
|
|
{TAllKeysRadioButton = class(TRadioButton)
|
|
procedure WMGetDlgCode(var Msg: TMessage); message WM_GETDLGCODE;
|
|
end;}
|
|
TAllKeysRadioButton = TRadioButton;
|
|
{TAllKeysCheckBox = class(TCheckBox)
|
|
procedure WMGetDlgCode(var Msg: TMessage); message WM_GETDLGCODE;
|
|
end;}
|
|
TAllKeysCheckBox = TCheckBox;
|
|
|
|
TBaseGridEditorLink = class(TInterfacedObject, IVTEditLink)
|
|
private
|
|
FInstanceId: Integer;
|
|
FParentForm: TWinControl; // A back reference to the main form
|
|
FTree: TVirtualStringTree; // A back reference to the tree calling.
|
|
FNode: PVirtualNode; // The node to be edited.
|
|
FColumn: TColumnIndex; // The column of the node.
|
|
FCellText: String; // Original cell text value
|
|
FCellFont: TFont; // Cosmetic
|
|
FCellBackground: TColor;
|
|
FMainControl: TWinControl; // The editor's most important component
|
|
FStopping: Boolean; // Set to True when the edit link requests stopping the edit action.
|
|
FLastKeyDown: Integer; // Set in OnKeyDown on the editor's main control
|
|
FLastShiftState: TShiftState;
|
|
FOldWindowProc: TWndMethod; // Temporary switched to TempWindowProc to be able to catch Tab key
|
|
FTableColumn: TTableColumn;
|
|
FModified: Boolean;
|
|
FAllowEdit: Boolean;
|
|
FBeginEditTime: Cardinal;
|
|
procedure Log(Msg: String);
|
|
//procedure TempWindowProc(var Message: TMessage);
|
|
procedure DoKeyDown(Sender: TObject; var Key: Word; Shift: TShiftState);
|
|
procedure DoEndEdit(Sender: TObject);
|
|
procedure AsyncEndEdit(Data: PtrInt);
|
|
procedure DoCancelEdit(Sender: TObject);
|
|
procedure AsyncCancelEdit(Data: PtrInt);
|
|
function GetCellRect(InnerTextBounds: Boolean): TRect;
|
|
public
|
|
// The table column of the cell being edited. Mostly used in data grids.
|
|
property TableColumn: TTableColumn read FTableColumn;
|
|
// The original constructor, not used any more, throws an exception if you do
|
|
constructor Create; overload;
|
|
// The right constructor, we need the Tree reference
|
|
constructor Create(Tree: TVirtualStringTree; AllowEdit: Boolean; Col: TTableColumn); overload; virtual;
|
|
destructor Destroy; override;
|
|
property Tree: TVirtualStringTree read FTree;
|
|
function PrepareEdit(Tree: TBaseVirtualTree; Node: PVirtualNode; Column: TColumnIndex): Boolean; virtual; stdcall;
|
|
function BeginEdit: Boolean; virtual; stdcall;
|
|
function CancelEdit: Boolean; virtual; stdcall;
|
|
function EndEdit: Boolean; virtual; stdcall; abstract;
|
|
function EndEditHelper(NewText: String): Boolean;
|
|
function GetBounds: TRect; virtual; stdcall; // Normally useless and unused
|
|
procedure ProcessMessage(var Message: TMessage); stdcall;
|
|
procedure SetBounds(R: TRect); virtual; stdcall; abstract;
|
|
end;
|
|
|
|
THexEditorLink = class(TBaseGridEditorLink)
|
|
private
|
|
FForm: TfrmBinEditor;
|
|
public
|
|
MaxLength: Integer;
|
|
TitleText: String;
|
|
constructor Create(Tree: TVirtualStringTree; AllowEdit: Boolean; Col: TTableColumn); override;
|
|
destructor Destroy; override;
|
|
function BeginEdit: Boolean; override;
|
|
function CancelEdit: Boolean; override;
|
|
function EndEdit: Boolean; override;
|
|
function PrepareEdit(Tree: TBaseVirtualTree; Node: PVirtualNode; Column: TColumnIndex): Boolean; override;
|
|
procedure SetBounds(R: TRect); override;
|
|
end;
|
|
|
|
TDateTimeEditorLink = class(TBaseGridEditorLink)
|
|
private
|
|
FPanel: TPanel;
|
|
FMaskEdit: TMaskEdit;
|
|
FTimer: TTimer;
|
|
FModifyOffset: Integer;
|
|
FTimerCalls: Integer;
|
|
FUpDown: TUpDown;
|
|
procedure DoKeyDown(Sender: TObject; var Key: Word; Shift: TShiftState);
|
|
procedure DoKeyUp(Sender: TObject; var Key: Word; Shift: TShiftState);
|
|
procedure UpDownChangingEx(Sender: TObject; var AllowChange: Boolean;
|
|
NewValue: SmallInt; Direction: TUpDownDirection);
|
|
procedure UpDownMouseUp(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
|
|
procedure DoOnTimer(Sender: TObject);
|
|
procedure ModifyDate(Offset: Integer);
|
|
procedure TextChange(Sender: TObject);
|
|
function MicroSecondsPrecision: Integer;
|
|
public
|
|
constructor Create(Tree: TVirtualStringTree; AllowEdit: Boolean; Col: TTableColumn); override;
|
|
destructor Destroy; override;
|
|
function BeginEdit: Boolean; override;
|
|
function EndEdit: Boolean; override;
|
|
function PrepareEdit(Tree: TBaseVirtualTree; Node: PVirtualNode; Column: TColumnIndex): Boolean; override;
|
|
procedure SetBounds(R: TRect); override;
|
|
end;
|
|
|
|
TEnumEditorLink = class(TBaseGridEditorLink)
|
|
private
|
|
FCombo: TExtComboBox;
|
|
procedure DoKeyDown(Sender: TObject; var Key: Word; Shift: TShiftState);
|
|
procedure DoSelect(Sender: TObject);
|
|
public
|
|
ValueList, DisplayList: TStringList;
|
|
AllowCustomText: Boolean;
|
|
ItemMustExist: Boolean;
|
|
constructor Create(Tree: TVirtualStringTree; AllowEdit: Boolean; Col: TTableColumn); override;
|
|
destructor Destroy; override;
|
|
function BeginEdit: Boolean; override;
|
|
function EndEdit: Boolean; override;
|
|
function PrepareEdit(Tree: TBaseVirtualTree; Node: PVirtualNode; Column: TColumnIndex): Boolean; override;
|
|
procedure SetBounds(R: TRect); override;
|
|
end;
|
|
|
|
TSetEditorLink = class(TBaseGridEditorLink)
|
|
private
|
|
FPanel: TPanel;
|
|
FCheckList: TCheckListBox;
|
|
FBtnOK, FBtnCancel: TButton;
|
|
FEndTimer: TTimer;
|
|
procedure BtnOkClick(Sender: TObject);
|
|
procedure BtnCancelClick(Sender: TObject);
|
|
public
|
|
ValueList: TStringList;
|
|
constructor Create(Tree: TVirtualStringTree; AllowEdit: Boolean; Col: TTableColumn); override;
|
|
destructor Destroy; override;
|
|
function BeginEdit: Boolean; override;
|
|
function EndEdit: Boolean; override;
|
|
function PrepareEdit(Tree: TBaseVirtualTree; Node: PVirtualNode; Column: TColumnIndex): Boolean; override;
|
|
procedure SetBounds(R: TRect); override;
|
|
end;
|
|
|
|
// Inplace editor with button
|
|
TInplaceEditorLink = class(TBaseGridEditorLink)
|
|
private
|
|
FPanel: TPanel;
|
|
FEdit: TEdit;
|
|
FButton: TButton;
|
|
FMaxLength: Integer;
|
|
procedure DoKeyDown(Sender: TObject; var Key: Word; Shift: TShiftState);
|
|
procedure ButtonClick(Sender: TObject);
|
|
public
|
|
ButtonVisible: Boolean;
|
|
TitleText: String;
|
|
constructor Create(Tree: TVirtualStringTree; AllowEdit: Boolean; Col: TTableColumn); override;
|
|
destructor Destroy; override;
|
|
function BeginEdit: Boolean; override;
|
|
function EndEdit: Boolean; override;
|
|
function PrepareEdit(Tree: TBaseVirtualTree; Node: PVirtualNode; Column: TColumnIndex): Boolean; override;
|
|
procedure SetBounds(R: TRect); override;
|
|
property MaxLength: Integer read FMaxLength write FMaxLength;
|
|
end;
|
|
|
|
TColumnDefaultEditorLink = class(TBaseGridEditorLink)
|
|
private
|
|
FPanel: TPanel;
|
|
FRadioNothing, FRadioText, FRadioNULL, FRadioExpression, FRadioAutoInc: TAllKeysRadioButton;
|
|
FlblOnUpdate: TLabel;
|
|
FTextEdit: TEditButton;
|
|
FTextDropDown: TPopupMenu;
|
|
FExpressionEdit: TComboBox;
|
|
FOnUpdateEdit: TComboBox;
|
|
FBtnOK, FBtnCancel: TButton;
|
|
FEndTimer: TTimer;
|
|
procedure RadioClick(Sender: TObject);
|
|
procedure EditChange(Sender: TObject);
|
|
procedure EditButtonClick(Sender: TObject);
|
|
procedure EditDropDownClick(Sender: TObject);
|
|
procedure BtnOkClick(Sender: TObject);
|
|
procedure BtnCancelClick(Sender: TObject);
|
|
public
|
|
DefaultType, OnUpdateType: TColumnDefaultType;
|
|
DefaultText, OnUpdateText: String;
|
|
constructor Create(Tree: TVirtualStringTree; AllowEdit: Boolean; Col: TTableColumn); override;
|
|
destructor Destroy; override;
|
|
function BeginEdit: Boolean; override;
|
|
function EndEdit: Boolean; override;
|
|
function PrepareEdit(Tree: TBaseVirtualTree; Node: PVirtualNode; Column: TColumnIndex): Boolean; override;
|
|
procedure SetBounds(R: TRect); override;
|
|
end;
|
|
|
|
TDataTypeEditorLink = class(TBaseGridEditorLink)
|
|
private
|
|
FTreeSelect: TVirtualStringTree;
|
|
FMemoHelp: TMemo;
|
|
procedure DoTreeSelectGetText(Sender: TBaseVirtualTree; Node: PVirtualNode;
|
|
Column: TColumnIndex; TextType: TVSTTextType; var CellText: String);
|
|
procedure DoTreeSelectInitNode(Sender: TBaseVirtualTree;
|
|
ParentNode, Node: PVirtualNode; var InitialStates: TVirtualNodeInitStates);
|
|
procedure DoTreeSelectInitChildren(Sender: TBaseVirtualTree;
|
|
Node: PVirtualNode; var ChildCount: Cardinal);
|
|
procedure DoTreeSelectHotChange(Sender: TBaseVirtualTree; OldNode, NewNode: PVirtualNode);
|
|
procedure DoTreeSelectPaintText(Sender: TBaseVirtualTree;
|
|
const TargetCanvas: TCanvas; Node: PVirtualNode; Column: TColumnIndex;
|
|
TextType: TVSTTextType);
|
|
procedure DoTreeSelectFocusChanging(Sender: TBaseVirtualTree; OldNode, NewNode:
|
|
PVirtualNode; OldColumn, NewColumn: TColumnIndex; var Allowed: Boolean);
|
|
procedure DoTreeSelectFocusChanged(Sender: TBaseVirtualTree; Node: PVirtualNode; Column: TColumnIndex);
|
|
public
|
|
constructor Create(Tree: TVirtualStringTree; AllowEdit: Boolean; Col: TTableColumn); override;
|
|
destructor Destroy; override;
|
|
function BeginEdit: Boolean; override;
|
|
function EndEdit: Boolean; override;
|
|
function PrepareEdit(Tree: TBaseVirtualTree; Node: PVirtualNode; Column: TColumnIndex): Boolean; override;
|
|
procedure SetBounds(R: TRect); override;
|
|
end;
|
|
|
|
|
|
var
|
|
ActiveGridEditor: TBaseGridEditorLink=nil;
|
|
|
|
implementation
|
|
|
|
uses
|
|
main;
|
|
|
|
|
|
{procedure TAllKeysRadioButton.WMGetDlgCode(var Msg: TMessage);
|
|
begin
|
|
inherited;
|
|
Msg.Result := Msg.Result or DLGC_WANTALLKEYS;
|
|
end;}
|
|
|
|
|
|
{procedure TAllKeysCheckBox.WMGetDlgCode(var Msg: TMessage);
|
|
begin
|
|
inherited;
|
|
Msg.Result := Msg.Result or DLGC_WANTALLKEYS;
|
|
end;}
|
|
|
|
procedure TBaseGridEditorLink.Log(Msg: String);
|
|
begin
|
|
MainForm.LogSQL('#'+FInstanceId.ToString+': '+Msg, lcDebug);
|
|
end;
|
|
|
|
|
|
constructor TBaseGridEditorLink.Create;
|
|
begin
|
|
raise Exception.CreateFmt(_('Wrong constructor called: %s.%s. Instead, please call the overloaded version %s.%s.'),
|
|
[Self.ClassName, 'Create', Self.ClassName, 'Create(VirtualStringTree)']);
|
|
end;
|
|
|
|
constructor TBaseGridEditorLink.Create(Tree: TVirtualStringTree; AllowEdit: Boolean; Col: TTableColumn);
|
|
begin
|
|
inherited Create;
|
|
FInstanceId := Random(100);
|
|
FTree := Tree;
|
|
// Enable mouse scrolling, plus ensure the editor component
|
|
// is not partly hidden when it pops up in a bottom cell
|
|
FParentForm := GetParentForm(FTree);
|
|
// Avoid flicker
|
|
FParentForm.Repaint;
|
|
FMainControl := nil;
|
|
FModified := False;
|
|
FAllowEdit := AllowEdit;
|
|
ActiveGridEditor := Self;
|
|
FTableColumn := Col;
|
|
end;
|
|
|
|
destructor TBaseGridEditorLink.Destroy;
|
|
var
|
|
NewColumn, FirstCol, LastCol: TColumnIndex;
|
|
NewNode: PVirtualNode;
|
|
DoPrev: Boolean;
|
|
begin
|
|
ActiveGridEditor := nil;
|
|
if Assigned(FMainControl) then begin
|
|
FMainControl.WindowProc := FOldWindowProc;
|
|
FMainControl := nil;
|
|
end;
|
|
if FLastKeyDown = VK_TAB then begin
|
|
DoPrev := ssShift in FLastShiftState;
|
|
// Advance to next/previous visible column/node.
|
|
NewNode := FNode;
|
|
NewColumn := FColumn;
|
|
FirstCol := FTree.Header.Columns.GetFirstVisibleColumn;
|
|
LastCol := FTree.Header.Columns.GetLastVisibleColumn;
|
|
while true do begin
|
|
// Find a column for the current node which can be focused.
|
|
if DoPrev then begin
|
|
if NewColumn = FirstCol then begin
|
|
NewColumn := LastCol;
|
|
NewNode := FTree.GetPreviousVisible(NewNode);
|
|
end else
|
|
NewColumn := FTree.Header.Columns.GetPreviousVisibleColumn(NewColumn);
|
|
end else begin
|
|
if NewColumn = LastCol then begin
|
|
NewColumn := FirstCol;
|
|
NewNode := FTree.GetNextVisible(NewNode);
|
|
end else
|
|
NewColumn := FTree.Header.Columns.GetNextVisibleColumn(NewColumn);
|
|
end;
|
|
if not Assigned(NewNode) then
|
|
Break;
|
|
if not FTree.CanEdit(NewNode, NewColumn) then
|
|
Continue;
|
|
FTree.ClearSelection;
|
|
FTree.Selected[NewNode] := True;
|
|
FTree.EditNode(NewNode, NewColumn);
|
|
Break;
|
|
end;
|
|
end;
|
|
inherited;
|
|
end;
|
|
|
|
function TBaseGridEditorLink.PrepareEdit(Tree: TBaseVirtualTree; Node: PVirtualNode; Column: TColumnIndex): Boolean;
|
|
var
|
|
FCellTextBounds: TRect;
|
|
HasNulls: Boolean;
|
|
begin
|
|
Result := not FStopping;
|
|
if not Result then
|
|
Exit;
|
|
FNode := Node;
|
|
FColumn := Column;
|
|
FCellFont := TFont.Create;
|
|
FCellTextBounds := Rect(0, 0, 0, 0);
|
|
FTree.GetTextInfo(FNode, FColumn, FCellFont, FCellTextBounds, FCellText);
|
|
HasNulls := False;
|
|
apphelpers.RemoveNullChars(FCellText, HasNulls);
|
|
if HasNulls and FAllowEdit then begin
|
|
FAllowEdit := False;
|
|
end;
|
|
|
|
// Not all editors have a connection assigned, e.g. session manager tree
|
|
if Assigned(FTableColumn) then begin
|
|
FCellFont.Color := AppColorSchemes.First.GridTextColors[FTableColumn.DataType.Category];
|
|
end;
|
|
FCellBackground := FTree.Header.Columns[FColumn].Color;
|
|
if Assigned(FMainControl) then begin
|
|
FOldWindowProc := FMainControl.WindowProc;
|
|
//FMainControl.WindowProc := TempWindowProc;
|
|
end;
|
|
// Adjust editor position and allow repainting mainform
|
|
SetBounds(FCellTextBounds);
|
|
FParentForm.Repaint;
|
|
end;
|
|
|
|
function TBaseGridEditorLink.BeginEdit: Boolean;
|
|
begin
|
|
Result := not FStopping;
|
|
FBeginEditTime := GetTickCount;
|
|
end;
|
|
|
|
function TBaseGridEditorLink.CancelEdit: Boolean;
|
|
begin
|
|
Result := not FStopping;
|
|
if Result then begin
|
|
FStopping := True;
|
|
FTree.CancelEditNode;
|
|
if FTree.CanFocus then
|
|
FTree.SetFocus;
|
|
end;
|
|
end;
|
|
|
|
function TBaseGridEditorLink.EndEditHelper(NewText: String): Boolean;
|
|
begin
|
|
Result := not FStopping;
|
|
if FStopping then Exit;
|
|
FStopping := True;
|
|
if FModified and FAllowEdit then
|
|
FTree.Text[FNode, FColumn] := NewText;
|
|
if FTree.CanFocus and (FLastKeyDown <> VK_TAB) then
|
|
FTree.SetFocus;
|
|
end;
|
|
|
|
{procedure TBaseGridEditorLink.TempWindowProc(var Message: TMessage);
|
|
begin
|
|
case Message.Msg of
|
|
WM_CHAR: // Catch hotkeys
|
|
if not (TWMChar(Message).CharCode = VK_TAB) then
|
|
FOldWindowProc(Message);
|
|
WM_GETDLGCODE: // "WantTabs" mode for main control
|
|
Message.Result := Message.Result or DLGC_WANTARROWS or DLGC_WANTALLKEYS or DLGC_WANTTAB;
|
|
else begin
|
|
try
|
|
FOldWindowProc(Message);
|
|
except
|
|
// EAccessViolation occurring in some cases
|
|
on E:Exception do begin
|
|
Log(E.Message+' Message CharCode:'+TWMChar(Message).CharCode.ToString+' Msg:'+Message.Msg.ToString);
|
|
end;
|
|
end;
|
|
end;
|
|
end;
|
|
end;}
|
|
|
|
procedure TBaseGridEditorLink.ProcessMessage(var Message: TMessage);
|
|
begin
|
|
if (FMainControl <> nil) and FMainControl.HandleAllocated then
|
|
FMainControl.WindowProc(Message);
|
|
end;
|
|
|
|
function TBaseGridEditorLink.GetBounds: TRect; stdcall;
|
|
begin
|
|
// Only important if the editor resizes itself.
|
|
Result := Rect(0, 0, 0, 0);
|
|
end;
|
|
|
|
function TBaseGridEditorLink.GetCellRect(InnerTextBounds: Boolean): TRect;
|
|
var
|
|
Text: String;
|
|
CellBounds, TextBounds: TRect;
|
|
Ghosted: Boolean;
|
|
ImageIndex: Integer;
|
|
f: TFont;
|
|
begin
|
|
// Return the cell's rectangle, relative to the parent form.
|
|
f := TFont.Create;
|
|
TextBounds := Rect(0, 0, 0, 0);
|
|
FTree.GetTextInfo(FNode, FColumn, f, TextBounds, Text);
|
|
CellBounds := FTree.GetDisplayRect(FNode, FColumn, False);
|
|
|
|
Inc(CellBounds.Left, Integer(FTree.GetNodeLevel(FNode)) * (Integer(FTree.Indent)+FTree.TextMargin));
|
|
if (toShowRoot in FTree.TreeOptions.PaintOptions)
|
|
and (FColumn = FTree.Header.MainColumn) then begin
|
|
// Reserve space for plus or minus button
|
|
Inc(CellBounds.Left, FTree.Indent);
|
|
end;
|
|
if Assigned(FTree.Images) and Assigned(FTree.OnGetImageIndex) then begin
|
|
// Reserve space for image
|
|
ImageIndex := -1;
|
|
Ghosted := False;
|
|
FTree.OnGetImageIndex(FTree, FNode, ikNormal, FColumn, Ghosted, ImageIndex);
|
|
if ImageIndex > -1 then
|
|
Inc(CellBounds.Left, FTree.Images.Width+2);
|
|
end;
|
|
TextBounds.Left := CellBounds.Left + 2*FTree.TextMargin;
|
|
|
|
if InnerTextBounds then begin
|
|
// Inner bounds are considered to be relative to the outer cell bounds
|
|
Result := Rect(TextBounds.Left-CellBounds.Left,
|
|
TextBounds.Top-CellBounds.Top,
|
|
CellBounds.Right-CellBounds.Left, // Far right edge of cell, not of text
|
|
CellBounds.Bottom-CellBounds.Top
|
|
);
|
|
end else begin
|
|
// Recalculate top left corner of rectangle, so it is relative to the parent form (which is FParentForm)
|
|
Result := CellBounds;
|
|
Types.OffsetRect(Result,
|
|
FTree.ClientOrigin.X - FParentForm.ClientOrigin.X,
|
|
FTree.ClientOrigin.Y - FParentForm.ClientOrigin.Y
|
|
);
|
|
Dec(Result.Bottom, 1);
|
|
end;
|
|
end;
|
|
|
|
procedure TBaseGridEditorLink.DoKeyDown(Sender: TObject; var Key: Word; Shift: TShiftState);
|
|
begin
|
|
FLastKeyDown := Key;
|
|
FLastShiftState := Shift;
|
|
case Key of
|
|
// Cancel by Escape
|
|
VK_ESCAPE: DoCancelEdit(Sender);
|
|
// Apply changes and end editing by [Ctrl +] Enter or Tab
|
|
VK_RETURN, VK_TAB: DoEndEdit(Sender);
|
|
end;
|
|
end;
|
|
|
|
procedure TBaseGridEditorLink.DoEndEdit(Sender: TObject);
|
|
begin
|
|
Application.QueueAsyncCall(AsyncEndEdit, PtrInt(FTree));
|
|
end;
|
|
|
|
procedure TBaseGridEditorLink.AsyncEndEdit(Data: PtrInt);
|
|
begin
|
|
TVirtualStringTree(Data).EndEditNode;
|
|
end;
|
|
|
|
procedure TBaseGridEditorLink.DoCancelEdit(Sender: TObject);
|
|
begin
|
|
Application.QueueAsyncCall(AsyncCancelEdit, PtrInt(FTree));
|
|
end;
|
|
|
|
procedure TBaseGridEditorLink.AsyncCancelEdit(Data: PtrInt);
|
|
begin
|
|
TVirtualStringTree(Data).CancelEditNode;
|
|
end;
|
|
|
|
|
|
|
|
constructor THexEditorLink.Create(Tree: TVirtualStringTree; AllowEdit: Boolean; Col: TTableColumn);
|
|
begin
|
|
inherited;
|
|
end;
|
|
|
|
destructor THexEditorLink.Destroy;
|
|
begin
|
|
inherited;
|
|
FForm.Close;
|
|
FreeAndNil(FForm);
|
|
end;
|
|
|
|
|
|
function THexEditorLink.PrepareEdit(Tree: TBaseVirtualTree; Node: PVirtualNode; Column: TColumnIndex): Boolean; stdcall;
|
|
begin
|
|
Result := inherited PrepareEdit(Tree, Node, Column);
|
|
if not Result then
|
|
Exit;
|
|
|
|
// Create the text editor form
|
|
FForm := TfrmBinEditor.Create(Ftree);
|
|
FForm.SetFont(FCellFont);
|
|
FForm.SetText(FCellText);
|
|
FForm.SetTitleText(TitleText);
|
|
FForm.SetMaxLength(MaxLength);
|
|
FForm.memoText.ReadOnly := not FAllowEdit;
|
|
end;
|
|
|
|
|
|
function THexEditorLink.BeginEdit: Boolean; stdcall;
|
|
begin
|
|
Result := inherited BeginEdit;
|
|
if Result then
|
|
FForm.ShowModal;
|
|
end;
|
|
|
|
|
|
function THexEditorLink.CancelEdit: Boolean;
|
|
begin
|
|
Result := inherited CancelEdit;
|
|
if Result then
|
|
FForm.Close;
|
|
end;
|
|
|
|
|
|
function THexEditorLink.EndEdit: Boolean; stdcall;
|
|
begin
|
|
FForm.Close;
|
|
FModified := FForm.Modified;
|
|
Result := EndEditHelper(FForm.GetText);
|
|
end;
|
|
|
|
|
|
procedure THexEditorLink.SetBounds(R: TRect); stdcall;
|
|
begin
|
|
// Not in use, form's position is centered on mainform
|
|
end;
|
|
|
|
|
|
|
|
{ DateTime editor }
|
|
|
|
constructor TDateTimeEditorLink.Create(Tree: TVirtualStringTree; AllowEdit: Boolean; Col: TTableColumn);
|
|
begin
|
|
inherited;
|
|
|
|
FPanel := TPanel.Create(FParentForm);
|
|
FPanel.Parent := FParentForm;
|
|
FPanel.Hide;
|
|
FPanel.ParentBackground := False;
|
|
FPanel.BevelOuter := bvNone;
|
|
FPanel.OnExit := DoEndEdit;
|
|
|
|
FMaskEdit := TMaskEdit.Create(FPanel);
|
|
FMaskEdit.Parent := FPanel;
|
|
FMaskEdit.ParentColor := True;
|
|
FMaskEdit.BorderStyle := bsNone;
|
|
FMaskEdit.OnKeyDown := DoKeyDown;
|
|
FMaskEdit.OnKeyUp := DoKeyUp;
|
|
FMaskEdit.OnChange := TextChange;
|
|
FMainControl := FMaskEdit;
|
|
|
|
FUpDown := TUpDown.Create(FPanel);
|
|
FUpDown.Parent := FPanel;
|
|
FUpDown.OnChangingEx := UpDownChangingEx;
|
|
FUpDown.OnMouseUp := UpDownMouseUp;
|
|
|
|
FTimer := TTimer.Create(FMaskEdit);
|
|
FTimer.Interval := 50;
|
|
FTimer.OnTimer := DoOnTimer;
|
|
FTimer.Enabled := False;
|
|
end;
|
|
|
|
|
|
destructor TDateTimeEditorLink.Destroy;
|
|
begin
|
|
AppSettings.WriteInt(asDateTimeEditorCursorPos, FMaskEdit.SelStart, IntToStr(Integer(FTableColumn.DataType.Category)));
|
|
FreeAndNil(FTimer);
|
|
FreeAndNil(FUpDown);
|
|
FreeAndNil(FMaskEdit);
|
|
FreeAndNil(FPanel);
|
|
inherited;
|
|
end;
|
|
|
|
|
|
function TDateTimeEditorLink.BeginEdit: Boolean; stdcall;
|
|
begin
|
|
Result := inherited BeginEdit;
|
|
if Result then begin
|
|
FPanel.Show;
|
|
FMaskEdit.SetFocus;
|
|
// Focus very last segment of date
|
|
FMaskEdit.SelStart := AppSettings.ReadInt(asDateTimeEditorCursorPos, IntToStr(Integer(FTableColumn.DataType.Category)));
|
|
FMaskEdit.SelLength := 1;
|
|
end;
|
|
end;
|
|
|
|
|
|
function TDateTimeEditorLink.EndEdit: Boolean;
|
|
begin
|
|
Result := EndEditHelper(Trim(FMaskEdit.Text));
|
|
end;
|
|
|
|
|
|
function TDateTimeEditorLink.PrepareEdit(Tree: TBaseVirtualTree; Node: PVirtualNode; Column: TColumnIndex): Boolean; stdcall;
|
|
var
|
|
MinColWidth,
|
|
ForceTextLen: Integer;
|
|
begin
|
|
Result := inherited PrepareEdit(Tree, Node, Column);
|
|
if not Result then
|
|
Exit;
|
|
FMaskEdit.ReadOnly := not FAllowEdit;
|
|
|
|
case FTableColumn.DataType.Index of
|
|
dbdtDate:
|
|
FMaskEdit.EditMask := '0000-00-00;1; ';
|
|
|
|
dbdtDatetime, dbdtDatetime2, dbdtTimestamp,
|
|
dbdtInt, dbdtBigint,
|
|
dbdtFloat, dbdtDouble, dbdtDecimal, dbdtNumeric, dbdtReal, dbdtDoublePrecision: begin
|
|
if FCellText.IsEmpty then
|
|
FCellText := DateTimeToStr(Now);
|
|
if MicroSecondsPrecision > 0 then
|
|
FMaskEdit.EditMask := '0000-00-00 00\:00\:00.'+StringOfChar('0', MicroSecondsPrecision)+';1; '
|
|
else
|
|
FMaskEdit.EditMask := '0000-00-00 00\:00\:00;1; ';
|
|
end;
|
|
|
|
dbdtTime: begin
|
|
ForceTextLen := 10;
|
|
if MicroSecondsPrecision > 0 then begin
|
|
FMaskEdit.EditMask := '#900\:00\:00.'+StringOfChar('0', MicroSecondsPrecision)+';1; ';
|
|
Inc(ForceTextLen, MicroSecondsPrecision + 1);
|
|
end else
|
|
FMaskEdit.EditMask := '#900\:00\:00;1; ';
|
|
while Length(FCellText) < ForceTextLen do
|
|
FCellText := ' ' + FCellText;
|
|
end;
|
|
|
|
dbdtYear:
|
|
FMaskEdit.EditMask := '0000;1; ';
|
|
end;
|
|
FMaskEdit.Text := FCellText;
|
|
FModified := False;
|
|
FMaskEdit.Font.Assign(FCellFont);
|
|
FPanel.Color := FCellBackground;
|
|
// Auto-enlarge current tree column so the text in the edit is not cut
|
|
MinColWidth := FTree.Canvas.TextWidth(FCellText) + FTree.TextMargin + FUpDown.Width + 5;
|
|
if FTree.Header.Columns[FColumn].Width < MinColWidth then
|
|
FTree.Header.Columns[FColumn].Width := MinColWidth;
|
|
end;
|
|
|
|
|
|
procedure TDateTimeEditorLink.SetBounds(R: TRect); stdcall;
|
|
var
|
|
EditRect: TRect;
|
|
OldSelStart, OldSelLen: Integer;
|
|
begin
|
|
FPanel.BoundsRect := GetCellRect(False);
|
|
|
|
FUpDown.Left := FPanel.Width - FUpDown.Width;
|
|
FUpDown.Height := FPanel.Height;
|
|
|
|
EditRect := GetCellRect(True);
|
|
EditRect.Right := FUpDown.Left;
|
|
FMaskEdit.BoundsRect := EditRect;
|
|
|
|
OldSelStart := FMaskEdit.SelStart;
|
|
OldSelLen := FMaskEdit.SelLength;
|
|
FMaskEdit.SelStart := 0;
|
|
FMaskEdit.SelStart := OldSelStart;
|
|
FMaskEdit.SelLength := OldSelLen;
|
|
end;
|
|
|
|
|
|
procedure TDateTimeEditorLink.DoKeyDown(Sender: TObject; var Key: Word; Shift: TShiftState);
|
|
begin
|
|
inherited DoKeyDown(Sender, Key, Shift);
|
|
if (Key in [VK_UP, VK_DOWN]) and (not FTimer.Enabled) then begin
|
|
if Key = VK_UP then FModifyOffset := 1
|
|
else FModifyOffset := -1;
|
|
FTimerCalls := 0;
|
|
DoOnTimer(Sender);
|
|
FTimer.Enabled := True;
|
|
end;
|
|
end;
|
|
|
|
|
|
procedure TDateTimeEditorLink.DoKeyUp(Sender: TObject; var Key: Word; Shift: TShiftState);
|
|
begin
|
|
FTimer.Enabled := False;
|
|
end;
|
|
|
|
|
|
procedure TDateTimeEditorLink.UpDownChangingEx(Sender: TObject; var AllowChange: Boolean;
|
|
NewValue: SmallInt; Direction: TUpDownDirection);
|
|
begin
|
|
if FTimer.Enabled then
|
|
Exit;
|
|
if Direction = updUp then FModifyOffset := 1
|
|
else FModifyOffset := -1;
|
|
FTimerCalls := 0;
|
|
DoOnTimer(Sender);
|
|
FTimer.Enabled := True;
|
|
end;
|
|
|
|
|
|
procedure TDateTimeEditorLink.UpDownMouseUp(Sender: TObject; Button: TMouseButton;
|
|
Shift: TShiftState; X, Y: Integer);
|
|
begin
|
|
FTimer.Enabled := False;
|
|
end;
|
|
|
|
|
|
procedure TDateTimeEditorLink.DoOnTimer(Sender: TObject);
|
|
var
|
|
DelayCalls: Integer;
|
|
begin
|
|
Inc(FTimerCalls);
|
|
// short delay before counting up/down
|
|
DelayCalls := 350 Div FTimer.Interval;
|
|
if (FTimerCalls > DelayCalls) or (not (Sender is TTimer)) then
|
|
ModifyDate(FModifyOffset);
|
|
// Speed up counting in steps
|
|
if FTimerCalls in [DelayCalls*5, DelayCalls*10] then begin
|
|
if FModifyOffset > 0 then
|
|
Inc(FModifyOffset, 3)
|
|
else
|
|
Dec(FModifyOffset, 3);
|
|
end;
|
|
end;
|
|
|
|
|
|
procedure TDateTimeEditorLink.ModifyDate(Offset: Integer);
|
|
var
|
|
dt: TDateTime;
|
|
d: TDate;
|
|
i, MaxSeconds, MinSeconds: Int64;
|
|
text: String;
|
|
OldSelStart, OldSelLength,
|
|
ms: Integer;
|
|
msStr, StrWithoutMs: String;
|
|
|
|
function TimeToSeconds(Str: String): Int64;
|
|
var
|
|
Hours: String;
|
|
Seconds: Int64;
|
|
begin
|
|
Hours := Trim(Copy(Str, 1, 4));
|
|
Result := MakeInt(Hours) * 60 * 60;
|
|
Seconds := MakeInt(Copy(Str, 6, 2)) * 60 + MakeInt(Copy(Str, 9, 2));
|
|
if (Result < 0) or ((Result = 0) and (Hours[1]='-')) then
|
|
Dec(Result, Seconds)
|
|
else
|
|
Inc(Result, Seconds);
|
|
end;
|
|
|
|
function SecondsToTime(Seconds: Int64): String;
|
|
var
|
|
HoursNum, Minutes: Int64;
|
|
Hours: String;
|
|
begin
|
|
HoursNum := Abs(Seconds div (60*60));
|
|
Hours := IntToStr(HoursNum);
|
|
if Length(Hours) = 1 then
|
|
Hours := '0' + Hours;
|
|
if Seconds < 0 then
|
|
Hours := '-' + Hours;
|
|
Seconds := Abs(Seconds) mod (60*60);
|
|
Minutes := Seconds div 60;
|
|
Seconds := Seconds mod 60;
|
|
Result := Format('%4s:%.2u:%.2u', [Hours, Minutes, Seconds]);
|
|
end;
|
|
begin
|
|
try
|
|
// Detect microseconds part of value if any
|
|
if MicroSecondsPrecision > 0 then begin
|
|
msStr := RegExprGetMatch('\.(\d+)$', FMaskEdit.Text, 1);
|
|
ms := MakeInt(msStr);
|
|
end else begin
|
|
ms := 0;
|
|
end;
|
|
|
|
case FTableColumn.DataType.Index of
|
|
dbdtYear: begin
|
|
i := MakeInt(FMaskEdit.Text);
|
|
i := i + Offset;
|
|
text := IntToStr(i);
|
|
end;
|
|
|
|
dbdtDate: begin
|
|
d := StrToDate(FMaskEdit.Text);
|
|
// De- or increase focused date segment
|
|
case FMaskEdit.SelStart of
|
|
0..3: d := IncYear(d, Offset);
|
|
5,6: d := IncMonth(d, Offset);
|
|
8..10: d := IncDay(d, Offset);
|
|
end;
|
|
text := DateToStr(d);
|
|
end;
|
|
|
|
dbdtDateTime, dbdtDateTime2, dbdtTimestamp,
|
|
dbdtInt, dbdtBigint,
|
|
dbdtFloat, dbdtDouble, dbdtDecimal, dbdtNumeric, dbdtReal, dbdtDoublePrecision: begin
|
|
StrWithoutMs := ReplaceRegExpr('\.\d+$', FMaskEdit.Text, '');
|
|
dt := StrToDateTime(StrWithoutMs);
|
|
case FMaskEdit.SelStart of
|
|
0..3: dt := IncYear(dt, Offset);
|
|
5,6: dt := IncMonth(dt, Offset);
|
|
8,9: dt := IncDay(dt, Offset);
|
|
11,12: dt := IncHour(dt, Offset);
|
|
14,15: dt := IncMinute(dt, Offset);
|
|
17..19: dt := IncSecond(dt, Offset);
|
|
20..26: Inc(ms, Offset);
|
|
end;
|
|
text := DateTimeToStr(dt);
|
|
if Length(text) = 10 then
|
|
text := text + ' 00:00:00';
|
|
if MicroSecondsPrecision > 0 then
|
|
text := text + '.' + Format('%.'+IntToStr(MicroSecondsPrecision)+'d', [ms]);
|
|
end;
|
|
|
|
dbdtTime: begin
|
|
i := TimeToSeconds(FMaskEdit.Text);
|
|
case FMaskEdit.SelStart of
|
|
0..3: Inc(i, Offset*60*60);
|
|
5,6: Inc(i, Offset*60);
|
|
8,9: Inc(i, Offset);
|
|
10..16: Inc(ms, Offset);
|
|
end;
|
|
// Stop at max and min values. See http://dev.mysql.com/doc/refman/5.0/en/time.html
|
|
MaxSeconds := 839*60*60-1;
|
|
MinSeconds := -(MaxSeconds);
|
|
if i > MaxSeconds then
|
|
i := MaxSeconds;
|
|
if i < MinSeconds then
|
|
i := MinSeconds;
|
|
text := SecondsToTime(i);
|
|
if MicroSecondsPrecision > 0 then
|
|
text := text + '.' + Format('%.'+IntToStr(MicroSecondsPrecision)+'d', [ms]);
|
|
end;
|
|
|
|
else text := '';
|
|
end;
|
|
|
|
if text <> '' then begin
|
|
OldSelStart := FMaskEdit.SelStart;
|
|
OldSelLength := FMaskEdit.SelLength;
|
|
FMaskEdit.Text := text;
|
|
FMaskEdit.SelStart := OldSelStart;
|
|
FMaskEdit.SelLength := OldSelLength;
|
|
end;
|
|
except
|
|
on E:Exception do begin
|
|
MainForm.LogSQL(E.Message);
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
|
|
procedure TDateTimeEditorLink.TextChange;
|
|
begin
|
|
FModified := True;
|
|
end;
|
|
|
|
|
|
function TDateTimeEditorLink.MicroSecondsPrecision: Integer;
|
|
var
|
|
rx: TRegExpr;
|
|
msStr: String;
|
|
begin
|
|
case FTableColumn.DataType.Category of
|
|
dtcTemporal: begin
|
|
if not FTableColumn.LengthSet.IsEmpty then
|
|
// Read microseconds precision from MySQL length/set
|
|
Result := MakeInt(FTableColumn.LengthSet)
|
|
else begin
|
|
// Find default length of supported microseconds in datatype definition
|
|
// See dbstructures
|
|
rx := TRegExpr.Create;
|
|
rx.Expression := '\.([^\.]+)$';
|
|
if rx.Exec(FTableColumn.DataType.Format) then
|
|
Result := rx.MatchLen[1]
|
|
else
|
|
Result := 0;
|
|
rx.Free;
|
|
end;
|
|
end;
|
|
|
|
dtcInteger: begin
|
|
// UNIX timestamps from integers
|
|
Result := 0;
|
|
end;
|
|
|
|
dtcReal: begin
|
|
// UNIX timestamps from floats
|
|
// Detect number of decimals from original cell string
|
|
msStr := RegExprGetMatch('\.(\d+)$', FCellText, 1);
|
|
Result := Length(msStr);
|
|
end;
|
|
|
|
else
|
|
Result := 0;
|
|
end;
|
|
end;
|
|
|
|
|
|
|
|
{ Enum editor }
|
|
|
|
constructor TEnumEditorLink.Create(Tree: TVirtualStringTree; AllowEdit: Boolean; Col: TTableColumn);
|
|
begin
|
|
inherited;
|
|
AllowCustomText := False;
|
|
ItemMustExist := False;
|
|
FCombo := TExtComboBox.Create(FParentForm);
|
|
FCombo.Hide;
|
|
FCombo.Parent := FParentForm;
|
|
FCombo.OnKeyDown := DoKeyDown;
|
|
FCombo.OnExit := DoEndEdit;
|
|
FCombo.OnSelect := DoSelect;
|
|
// Show some more than the default 8 items
|
|
FCombo.DropDownCount := 16;
|
|
ValueList := TStringList.Create;
|
|
DisplayList := TStringList.Create;
|
|
FMainControl := FCombo;
|
|
end;
|
|
|
|
|
|
destructor TEnumEditorLink.Destroy;
|
|
begin
|
|
FCombo.Free;
|
|
inherited;
|
|
end;
|
|
|
|
|
|
function TEnumEditorLink.BeginEdit: Boolean; stdcall;
|
|
begin
|
|
Result := inherited BeginEdit;
|
|
if Result then begin
|
|
FCombo.Show;
|
|
FCombo.SetFocus;
|
|
end;
|
|
end;
|
|
|
|
|
|
function TEnumEditorLink.EndEdit: Boolean; stdcall;
|
|
var
|
|
NewText: String;
|
|
begin
|
|
if AllowCustomText and FAllowEdit then begin
|
|
if (not ItemMustExist) or ValueList.Contains(FCombo.Text) then
|
|
NewText := FCombo.Text
|
|
else
|
|
NewText := '';
|
|
end
|
|
else if (ValueList.Count > 0) and (FCombo.ItemIndex > -1) then
|
|
NewText := ValueList[FCombo.ItemIndex]
|
|
else
|
|
NewText := '';
|
|
FModified := NewText <> FCellText;
|
|
Result := EndEditHelper(NewText);
|
|
end;
|
|
|
|
|
|
function TEnumEditorLink.PrepareEdit(Tree: TBaseVirtualTree; Node: PVirtualNode; Column: TColumnIndex): Boolean; stdcall;
|
|
begin
|
|
Result := inherited PrepareEdit(Tree, Node, Column);
|
|
if Result then begin
|
|
if DisplayList.Count = ValueList.Count then
|
|
FCombo.Items.AddStrings(DisplayList)
|
|
else
|
|
FCombo.Items.AddStrings(ValueList);
|
|
FCombo.ItemIndex := ValueList.IndexOf(FCellText);
|
|
if AllowCustomText and FAllowEdit then begin
|
|
FCombo.Style := csDropDown;
|
|
FCombo.Text := FCellText;
|
|
end else begin
|
|
// Set style to OwnerDraw, otherwise we wouldn't be able to adjust the combo's height
|
|
FCombo.Style := csOwnerDrawFixed;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
|
|
procedure TEnumEditorLink.SetBounds(R: TRect); stdcall;
|
|
begin
|
|
FCombo.BoundsRect := GetCellRect(False);
|
|
end;
|
|
|
|
|
|
procedure TEnumEditorLink.DoKeyDown(Sender: TObject; var Key: Word; Shift: TShiftState);
|
|
begin
|
|
// Work around a magic automatic TAB key arriving the editor if the user got
|
|
// into this cell via TAB. Only seen for a TComboBox with style=csDropDown.
|
|
// See issue #2809
|
|
if (not AllowCustomText) and (GetTickCount-FBeginEditTime > 200) then
|
|
inherited;
|
|
end;
|
|
|
|
|
|
procedure TEnumEditorLink.DoSelect(Sender: TObject);
|
|
begin
|
|
// Read only mode?
|
|
if not FAllowEdit then begin
|
|
FCombo.ItemIndex := ValueList.IndexOf(FCellText);
|
|
end;
|
|
end;
|
|
|
|
|
|
|
|
{ SET editor }
|
|
|
|
constructor TSetEditorLink.Create(Tree: TVirtualStringTree; AllowEdit: Boolean; Col: TTableColumn);
|
|
begin
|
|
inherited;
|
|
ValueList := TStringList.Create;
|
|
|
|
FPanel := TPanel.Create(FParentForm);
|
|
FPanel.Hide;
|
|
FPanel.Parent := FParentForm;
|
|
FPanel.ParentBackground := False;
|
|
FPanel.Height := TExtForm.ScaleSize(150, FParentForm);
|
|
FPanel.OnExit := DoEndEdit;
|
|
|
|
FCheckList := TCheckListBox.Create(FPanel);
|
|
FCheckList.Parent := FPanel;
|
|
FCheckList.OnKeyDown := DoKeyDown;
|
|
FMainControl := FCheckList;
|
|
|
|
FBtnOk := TButton.Create(FPanel);
|
|
FBtnOk.Parent := FPanel;
|
|
FBtnOk.Caption := _('OK');
|
|
FBtnOk.OnClick := BtnOkClick;
|
|
|
|
FBtnCancel := TButton.Create(FPanel);
|
|
FBtnCancel.Parent := FPanel;
|
|
FBtnCancel.Caption := _('Cancel');
|
|
FBtnCancel.OnClick := BtnCancelClick;
|
|
|
|
FEndTimer := TTimer.Create(FPanel);
|
|
FEndTimer.Interval := 50;
|
|
FEndTimer.Enabled := False;
|
|
end;
|
|
|
|
|
|
destructor TSetEditorLink.Destroy;
|
|
begin
|
|
FreeAndNil(FPanel);
|
|
inherited;
|
|
end;
|
|
|
|
|
|
function TSetEditorLink.BeginEdit: Boolean; stdcall;
|
|
begin
|
|
Result := inherited BeginEdit;
|
|
if Result then begin
|
|
FPanel.Show;
|
|
FCheckList.SetFocus;
|
|
end;
|
|
end;
|
|
|
|
|
|
function TSetEditorLink.EndEdit: Boolean; stdcall;
|
|
var
|
|
newtext: String;
|
|
i: Integer;
|
|
begin
|
|
Result := not FStopping;
|
|
if FStopping then Exit;
|
|
newText := '';
|
|
for i := 0 to FCheckList.Items.Count - 1 do
|
|
if FCheckList.Checked[i] then newText := newText + FCheckList.Items[i] + ',';
|
|
Delete(newText, Length(newText), 1);
|
|
FModified := newText <> FCellText;
|
|
Result := EndEditHelper(newText);
|
|
end;
|
|
|
|
|
|
function TSetEditorLink.PrepareEdit(Tree: TBaseVirtualTree; Node: PVirtualNode; Column: TColumnIndex): Boolean; stdcall;
|
|
var
|
|
SelValues: TStringList;
|
|
i: Integer;
|
|
begin
|
|
Result := inherited PrepareEdit(Tree, Node, Column);
|
|
if not Result then
|
|
Exit;
|
|
|
|
FCheckList.Font.Assign(FCellFont);
|
|
FCheckList.Items.Assign(ValueList);
|
|
SelValues := TStringList.Create;
|
|
SelValues.Delimiter := ',';
|
|
SelValues.StrictDelimiter := True;
|
|
SelValues.DelimitedText := FCellText;
|
|
for i:=0 to FCheckList.Items.Count-1 do begin
|
|
FCheckList.Checked[i] := SelValues.IndexOf(FCheckList.Items[i]) > -1;
|
|
FCheckList.ItemEnabled[i] := FAllowEdit;
|
|
end;
|
|
SelValues.Free;
|
|
end;
|
|
|
|
|
|
procedure TSetEditorLink.SetBounds(R: TRect); stdcall;
|
|
const
|
|
margin = 5;
|
|
begin
|
|
R := GetCellRect(False);
|
|
FPanel.Top := R.Top;
|
|
FPanel.Left := R.Left;
|
|
FPanel.Width := R.Width;
|
|
|
|
FBtnOk.Width := (FPanel.Width - 3*margin) div 2;
|
|
FBtnOk.Left := margin;
|
|
FBtnOk.Height := TExtForm.ScaleSize(24, FParentForm);
|
|
FBtnOk.Top := FPanel.Height - 2*margin - FBtnOk.Height;
|
|
FBtnOk.Enabled := FAllowEdit;
|
|
|
|
FBtnCancel.Width := FBtnOk.Width;
|
|
FBtnCancel.Left := 2*margin + FBtnOk.Width;
|
|
FBtnCancel.Height := FBtnOk.Height;
|
|
FBtnCancel.Top := FBtnOk.Top;
|
|
|
|
FCheckList.Top := margin;
|
|
FCheckList.Left := margin;
|
|
FCheckList.Width := FPanel.Width - 2*margin;
|
|
FCheckList.Height := FBtnOk.Top - margin - FCheckList.Top;
|
|
// FCheckList.Enabled := FAllowEdit; // crashes with "cannot focus if disabled"
|
|
end;
|
|
|
|
|
|
procedure TSetEditorLink.BtnOkClick(Sender: TObject);
|
|
begin
|
|
// Timer based click on OK button, to prevent crash when theming is active
|
|
FEndTimer.OnTimer := DoEndEdit;
|
|
FEndTimer.Enabled := True;
|
|
end;
|
|
|
|
|
|
procedure TSetEditorLink.BtnCancelClick(Sender: TObject);
|
|
begin
|
|
// Timer based click on Cancel button, to prevent crash when theming is active
|
|
FEndTimer.OnTimer := DoCancelEdit;
|
|
FEndTimer.Enabled := True;
|
|
end;
|
|
|
|
|
|
|
|
{ TInplaceEditorLink }
|
|
|
|
constructor TInplaceEditorLink.Create(Tree: TVirtualStringTree; AllowEdit: Boolean; Col: TTableColumn);
|
|
begin
|
|
inherited;
|
|
ButtonVisible := false;
|
|
|
|
FPanel := TPanel.Create(FParentForm);
|
|
FPanel.Parent := FParentForm;
|
|
FPanel.Hide;
|
|
FPanel.ParentBackground := False;
|
|
FPanel.BevelOuter := bvNone;
|
|
FPanel.OnExit := DoEndEdit;
|
|
|
|
FEdit := TEdit.Create(FPanel);
|
|
FEdit.Parent := FPanel;
|
|
FEdit.ParentColor := True;
|
|
FEdit.BorderStyle := bsNone;
|
|
FEdit.AnchorSideLeft.Control := FPanel;
|
|
FEdit.AnchorSideTop.Control := FPanel;
|
|
FEdit.AnchorSideBottom.Control := FPanel;
|
|
FEdit.AnchorSideBottom.Side := asrBottom;
|
|
FEdit.Anchors := [akTop, akLeft, akRight, akBottom];
|
|
FEdit.OnKeyDown := DoKeyDown;
|
|
FMainControl := FEdit;
|
|
|
|
FButton := TButton.Create(FPanel);
|
|
FButton.Parent := FPanel;
|
|
FButton.AnchorSideLeft.Side := asrBottom;
|
|
FButton.AnchorSideTop.Control := FPanel;
|
|
FButton.AnchorSideRight.Control := FPanel;
|
|
FButton.AnchorSideRight.Side := asrBottom;
|
|
FButton.AnchorSideBottom.Control := FPanel;
|
|
FButton.AnchorSideBottom.Side := asrBottom;
|
|
FButton.Anchors := [akTop, akRight, akBottom];
|
|
FButton.Constraints.MaxWidth := TExtForm.ScaleSize(20, FPanel);
|
|
FButton.TabStop := False;
|
|
FButton.Caption := '...';
|
|
FButton.Hint := _('Edit text in popup editor ...');
|
|
FButton.ShowHint := True;
|
|
FButton.OnClick := ButtonClick;
|
|
|
|
FEdit.AnchorSideRight.Control := FButton;
|
|
end;
|
|
|
|
destructor TInplaceEditorLink.Destroy;
|
|
begin
|
|
if not ((csDestroying in FPanel.ComponentState) or (csCreating in FPanel.ControlState)) then begin
|
|
FEdit.Free;
|
|
FButton.Free;
|
|
FPanel.Free;
|
|
end;
|
|
inherited;
|
|
end;
|
|
|
|
function TInplaceEditorLink.BeginEdit: Boolean;
|
|
begin
|
|
Result := inherited BeginEdit;
|
|
if Result then begin
|
|
FButton.Visible := ButtonVisible;
|
|
SetBounds(Rect(0, 0, 0, 0));
|
|
if (Length(FEdit.Text) > SIZE_KB) or (ScanLineBreaks(FEdit.Text) <> lbsNone) then
|
|
ButtonClick(FTree)
|
|
else begin
|
|
FPanel.Show;
|
|
FEdit.SetFocus;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
|
|
function TInplaceEditorLink.EndEdit: Boolean;
|
|
var
|
|
NewText: String;
|
|
begin
|
|
Result := not FStopping;
|
|
if FStopping then Exit;
|
|
NewText := FEdit.Text;
|
|
FModified := NewText <> FCellText;
|
|
Result := EndEditHelper(NewText);
|
|
end;
|
|
|
|
procedure TInplaceEditorLink.DoKeyDown(Sender: TObject; var Key: Word;
|
|
Shift: TShiftState);
|
|
begin
|
|
inherited DoKeyDown(Sender, Key, Shift);
|
|
if Key = VK_F2 then
|
|
ButtonClick(FButton);
|
|
end;
|
|
|
|
procedure TInplaceEditorLink.ButtonClick(Sender: TObject);
|
|
var
|
|
Editor: TfrmTextEditor;
|
|
begin
|
|
if not FButton.Visible then Exit; // Button was invisible, but hotkey was pressed
|
|
Editor := TfrmTextEditor.Create(FTree);
|
|
Editor.SetFont(MainForm.SynMemoQuery.Font);
|
|
Editor.SetText(FEdit.Text);
|
|
if FEdit.HandleAllocated then begin
|
|
Editor.MemoText.SelStart := FEdit.SelStart;
|
|
Editor.MemoText.SelEnd := FEdit.SelStart + FEdit.SelLength;
|
|
end;
|
|
Editor.SetTitleText(TitleText);
|
|
Editor.Modified := FEdit.Modified;
|
|
Editor.SetMaxLength(FMaxLength);
|
|
Editor.TableColumn := FTableColumn;
|
|
Editor.MemoText.ReadOnly := not FAllowEdit;
|
|
if Editor.ShowModal = mrYes then begin
|
|
FEdit.Text := Editor.GetText;
|
|
DoEndEdit(Sender);
|
|
end
|
|
else begin
|
|
DoCancelEdit(Sender);
|
|
end;
|
|
Editor.Free;
|
|
end;
|
|
|
|
function TInplaceEditorLink.PrepareEdit(Tree: TBaseVirtualTree;
|
|
Node: PVirtualNode; Column: TColumnIndex): Boolean;
|
|
begin
|
|
Result := inherited PrepareEdit(Tree, Node, Column);
|
|
if not Result then
|
|
Exit;
|
|
|
|
FEdit.ReadOnly := not FAllowEdit;
|
|
FEdit.Font.Assign(FCellFont);
|
|
FEdit.Font.Color := GetThemeColor(clWindowText);
|
|
FPanel.Color := FCellBackground;
|
|
FEdit.Text := FCellText;
|
|
FEdit.Modified := False;
|
|
end;
|
|
|
|
procedure TInplaceEditorLink.SetBounds(R: TRect);
|
|
begin
|
|
if not FStopping then begin
|
|
// Position edit control according to cell text bounds
|
|
FPanel.BoundsRect := GetCellRect(False);
|
|
// R is too big in a grid, for some reason
|
|
end;
|
|
end;
|
|
|
|
|
|
|
|
{ Column default editor }
|
|
|
|
constructor TColumnDefaultEditorLink.Create(Tree: TVirtualStringTree; AllowEdit: Boolean; Col: TTableColumn);
|
|
var
|
|
SQLFunc: TSQLFunction;
|
|
m: Integer;
|
|
begin
|
|
inherited;
|
|
|
|
// Margin between controls and to edge of panel
|
|
m := TExtForm.ScaleSize(5, FParentForm);
|
|
|
|
FPanel := TPanel.Create(FParentForm);
|
|
FPanel.Hide;
|
|
FPanel.Parent := FParentForm;
|
|
FPanel.OnExit := DoEndEdit;
|
|
FPanel.ParentBackground := False;
|
|
FPanel.Color := GetThemeColor(clWindow);
|
|
//FPanel.BevelKind := bkFlat;
|
|
FPanel.BevelOuter := bvRaised;
|
|
FPanel.DoubleBuffered := True; // Avoid flicker?
|
|
FMainControl := FPanel;
|
|
|
|
FRadioNothing := TAllKeysRadioButton.Create(FPanel);
|
|
FRadioNothing.Parent := FPanel;
|
|
FRadioNothing.Top := m;
|
|
FRadioNothing.Left := m;
|
|
FRadioNothing.Width := FRadioNothing.Parent.Width - 2 * FRadioNothing.Left;
|
|
FRadioNothing.OnClick := RadioClick;
|
|
FRadioNothing.OnKeyDown := DoKeyDown;
|
|
FRadioNothing.Caption := _('No default value');
|
|
|
|
FRadioText := TAllKeysRadioButton.Create(FPanel);
|
|
FRadioText.Parent := FPanel;
|
|
FRadioText.Top := FRadioNothing.Top + FRadioNothing.Height + m;;
|
|
FRadioText.Left := m;
|
|
FRadioText.Width := FRadioText.Parent.Width - 2 * FRadioText.Left;
|
|
FRadioText.OnClick := RadioClick;
|
|
FRadioText.OnKeyDown := DoKeyDown;
|
|
FRadioText.Caption := _('Custom text')+':';
|
|
|
|
FTextDropDown := TPopupMenu.Create(FPanel);
|
|
|
|
FTextEdit := TEditButton.Create(FPanel);
|
|
FTextEdit.Parent := FPanel;
|
|
FTextEdit.Top := FRadioText.Top + FRadioText.Height + m;
|
|
FTextEdit.Left := 2*m;
|
|
FTextEdit.Width := FTextEdit.Parent.Width - 2*FTextEdit.Left;
|
|
FTextEdit.OnChange := EditChange;
|
|
FTextEdit.Images := Tree.Images;
|
|
FTextEdit.ImageIndex := 75; // Drop down arrow
|
|
FTextEdit.OnButtonClick := EditButtonClick;
|
|
//FTextEdit.RightButton.DropDownMenu := FTextDropDown;
|
|
|
|
FRadioNull := TAllKeysRadioButton.Create(FPanel);
|
|
FRadioNull.Parent := FPanel;
|
|
FRadioNull.Top := FTextEdit.Top + FTextEdit.Height + 2*m;
|
|
FRadioNull.Left := m;
|
|
FRadioNull.Width := FRadioNull.Parent.Width - 2 * FRadioNull.Left;
|
|
FRadioNull.OnClick := RadioClick;
|
|
FRadioNull.OnKeyDown := DoKeyDown;
|
|
FRadioNull.Caption := 'NULL';
|
|
|
|
FRadioExpression := TAllKeysRadioButton.Create(FPanel);
|
|
FRadioExpression.Parent := FPanel;
|
|
FRadioExpression.Top := FRadioNull.Top + FRadioNull.Height + m;
|
|
FRadioExpression.Left := m;
|
|
FRadioExpression.Width := FRadioExpression.Parent.Width - 2 * FRadioExpression.Left;
|
|
FRadioExpression.OnClick := RadioClick;
|
|
FRadioExpression.OnKeyDown := DoKeyDown;
|
|
FRadioExpression.Caption := _('Expression')+':';
|
|
|
|
FExpressionEdit := TComboBox.Create(FPanel);
|
|
FExpressionEdit.Parent := FPanel;
|
|
FExpressionEdit.Top := FRadioExpression.Top + FRadioExpression.Height + m;
|
|
FExpressionEdit.Left := 2*m;
|
|
FExpressionEdit.Width := FExpressionEdit.Parent.Width - 2*FExpressionEdit.Left;
|
|
FExpressionEdit.OnChange := EditChange;
|
|
FExpressionEdit.DropDownCount := 20;
|
|
for SQLFunc in FTableColumn.Connection.SQLFunctions do begin
|
|
FExpressionEdit.Items.Add(SQLFunc.Name + SQLFunc.Declaration);
|
|
end;
|
|
|
|
FlblOnUpdate := TLabel.Create(FPanel);
|
|
FlblOnUpdate.Parent := FPanel;
|
|
FlblOnUpdate.Top := FExpressionEdit.Top + FExpressionEdit.Height + m;
|
|
FlblOnUpdate.Left := 2*m;
|
|
FlblOnUpdate.Width := FlblOnUpdate.Parent.Width - 2*FlblOnUpdate.Left;
|
|
FlblOnUpdate.Caption := _('On update') + ':';
|
|
|
|
FOnUpdateEdit := TComboBox.Create(FPanel);
|
|
FOnUpdateEdit.Parent := FPanel;
|
|
FOnUpdateEdit.Top := FlblOnUpdate.Top + FlblOnUpdate.Height + m;
|
|
FOnUpdateEdit.Left := 2*m;
|
|
FOnUpdateEdit.Width := FOnUpdateEdit.Parent.Width - 2*FOnUpdateEdit.Left;
|
|
FOnUpdateEdit.OnChange := EditChange;
|
|
FOnUpdateEdit.DropDownCount := 20;
|
|
for SQLFunc in FTableColumn.Connection.SQLFunctions do begin
|
|
FOnUpdateEdit.Items.Add(SQLFunc.Name + SQLFunc.Declaration);
|
|
end;
|
|
|
|
FRadioAutoInc := TAllKeysRadioButton.Create(FPanel);
|
|
FRadioAutoInc.Parent := FPanel;
|
|
FRadioAutoInc.Top := FOnUpdateEdit.Top + FOnUpdateEdit.Height + m;
|
|
FRadioAutoInc.Left := m;
|
|
FRadioAutoInc.Width := FRadioAutoInc.Parent.Width - 2 * FRadioAutoInc.Left;
|
|
FRadioAutoInc.OnClick := RadioClick;
|
|
FRadioAutoInc.OnKeyDown := DoKeyDown;
|
|
FRadioAutoInc.Caption := Col.AutoIncName;
|
|
|
|
FBtnOk := TButton.Create(FPanel);
|
|
FBtnOk.Parent := FPanel;
|
|
FBtnOk.Width := TExtForm.ScaleSize(60, FParentForm);
|
|
FBtnOk.Top := FRadioAutoInc.Top + FRadioAutoInc.Height + m;
|
|
FBtnOk.Left := FPanel.Width - 3*m - 2*FBtnOk.Width - 2*FPanel.BorderWidth;
|
|
FBtnOk.OnClick := BtnOkClick;
|
|
FBtnOk.Default := True;
|
|
FBtnOk.Caption := _('OK');
|
|
|
|
FBtnCancel := TButton.Create(FPanel);
|
|
FBtnCancel.Parent := FPanel;
|
|
FBtnCancel.Top := FBtnOk.Top;
|
|
FBtnCancel.Width := FBtnOk.Width;
|
|
FBtnCancel.Left := FBtnOk.Left + FBtnOk.Width + m;
|
|
FBtnCancel.OnClick := BtnCancelClick;
|
|
FBtnCancel.Cancel := True;
|
|
FBtnCancel.Caption := _('Cancel');
|
|
|
|
FEndTimer := TTimer.Create(FPanel);
|
|
FEndTimer.Interval := 50;
|
|
FEndTimer.Enabled := False;
|
|
|
|
// Set outer panel (minimum) dimensions. Width is set in .SetBounds()
|
|
FPanel.Height := 2*FPanel.BorderWidth + FBtnOk.Top + FBtnOk.Height + 2*m;
|
|
FPanel.Constraints.MinWidth := 2*m + FBtnOK.Width + m + FBtnCancel.Width + 2*m;
|
|
|
|
// Set anchors for all controls, so they are sticky when resizing the underlying column width
|
|
FRadioNothing.Anchors := [akLeft, akTop, akRight];
|
|
FRadioText.Anchors := [akLeft, akTop, akRight];
|
|
FTextEdit.Anchors := [akLeft, akTop, akRight, akBottom];
|
|
FRadioNull.Anchors := [akLeft, akBottom, akRight];
|
|
FRadioExpression.Anchors := [akLeft, akBottom, akRight];
|
|
FExpressionEdit.Anchors := [akLeft, akBottom, akRight];
|
|
FOnUpdateEdit.Anchors := [akLeft, akBottom, akRight];
|
|
FRadioAutoInc.Anchors := [akLeft, akBottom, akRight];
|
|
FBtnOk.Anchors := [akBottom, akRight];
|
|
FBtnCancel.Anchors := FBtnOk.Anchors;
|
|
end;
|
|
|
|
|
|
destructor TColumnDefaultEditorLink.Destroy;
|
|
begin
|
|
FPanel.Free;
|
|
inherited;
|
|
end;
|
|
|
|
|
|
function TColumnDefaultEditorLink.PrepareEdit(Tree: TBaseVirtualTree; Node: PVirtualNode; Column: TColumnIndex): Boolean; stdcall;
|
|
var
|
|
ValueList, SelectedValues: TStringList;
|
|
i: Integer;
|
|
Item: TMenuItem;
|
|
begin
|
|
inherited PrepareEdit(Tree, Node, Column);
|
|
|
|
// Check relevant radio button
|
|
FRadioNothing.Checked := DefaultType = cdtNothing;
|
|
FRadioText.Checked := DefaultType = cdtText;
|
|
FRadioNull.Checked := DefaultType = cdtNull;
|
|
FRadioExpression.Checked := DefaultType = cdtExpression;
|
|
FRadioAutoInc.Checked := DefaultType = cdtAutoInc;
|
|
|
|
if FRadioText.Checked then begin
|
|
FTextEdit.Text := DefaultText;
|
|
end;
|
|
|
|
if FRadioExpression.Checked then begin
|
|
FExpressionEdit.Text := DefaultText;
|
|
end;
|
|
|
|
FOnUpdateEdit.Text := OnUpdateText;
|
|
|
|
// Disable non working default options per data type
|
|
FRadioAutoInc.Enabled := FRadioAutoInc.Checked or (FTableColumn.DataType.Category = dtcInteger);
|
|
|
|
// Provide items with a check mark for ENUM and SET columns
|
|
if FTableColumn.DataType.Index in [dbdtEnum, dbdtSet] then begin
|
|
FTextEdit.Button.Enabled := True;
|
|
ValueList := FTableColumn.ValueList;
|
|
SelectedValues := Explode(',', FTextEdit.Text);
|
|
|
|
for i:=0 to ValueList.Count-1 do begin
|
|
Item := TMenuItem.Create(FTextDropDown);
|
|
Item.Caption := ValueList[i];
|
|
Item.RadioItem := FTableColumn.DataType.Index = dbdtEnum;
|
|
Item.Checked := SelectedValues.IndexOf(Item.Caption) > -1;
|
|
Item.OnClick := EditDropDownClick;
|
|
FTextDropDown.Items.Add(Item);
|
|
end;
|
|
|
|
ValueList.Free;
|
|
SelectedValues.Free;
|
|
end;
|
|
|
|
Result := True;
|
|
end;
|
|
|
|
|
|
procedure TColumnDefaultEditorLink.SetBounds(R: TRect); stdcall;
|
|
var
|
|
CellRect: TRect;
|
|
P: TPoint;
|
|
Room: Integer;
|
|
begin
|
|
CellRect := GetCellRect(False);
|
|
FPanel.Left := CellRect.Left;
|
|
FPanel.Top := CellRect.Top;
|
|
FPanel.Width := CellRect.Width;
|
|
|
|
// Reposition editor so it's not outside the main form
|
|
P := FParentForm.ClientToScreen(FPanel.BoundsRect.TopLeft);
|
|
Room := FParentForm.BoundsRect.Bottom - 8 {Borderwidth} - (P.Y + FPanel.Height);
|
|
if Room < 0 then
|
|
FPanel.Top := CellRect.Top + Room;
|
|
P := FParentForm.ClientToScreen(FPanel.BoundsRect.BottomRight);
|
|
Room := FParentForm.BoundsRect.Right - 8 {Borderwidth} - P.X;
|
|
if Room < 0 then
|
|
FPanel.Left := CellRect.Left + Room;
|
|
end;
|
|
|
|
|
|
function TColumnDefaultEditorLink.BeginEdit: Boolean; stdcall;
|
|
begin
|
|
Result := not FStopping;
|
|
if Result then begin
|
|
FPanel.Show;
|
|
if FRadioNothing.Checked then FRadioNothing.SetFocus
|
|
else if FRadioText.Checked then FTextEdit.SetFocus
|
|
else if FRadioNull.Checked then FRadioNull.SetFocus
|
|
else if FRadioExpression.Checked then FExpressionEdit.SetFocus
|
|
else if FRadioAutoInc.Checked then FRadioAutoInc.SetFocus;
|
|
end;
|
|
end;
|
|
|
|
|
|
function TColumnDefaultEditorLink.EndEdit: Boolean; stdcall;
|
|
var
|
|
Col: PTableColumn;
|
|
begin
|
|
Result := not FStopping;
|
|
if Result then begin
|
|
FStopping := True;
|
|
Col := FTree.GetNodeData(FNode);
|
|
|
|
if FRadioNothing.Checked then
|
|
Col.DefaultType := cdtNothing
|
|
else if FRadioText.Checked then
|
|
Col.DefaultType := cdtText
|
|
else if FRadioNull.Checked then
|
|
Col.DefaultType := cdtNull
|
|
else if FRadioExpression.Checked then
|
|
Col.DefaultType := cdtExpression
|
|
else if FRadioAutoInc.Checked then
|
|
Col.DefaultType := cdtAutoInc
|
|
else
|
|
Col.DefaultType := cdtText;
|
|
|
|
case Col.DefaultType of
|
|
cdtNothing: Col.DefaultText := '';
|
|
cdtText: Col.DefaultText := FTextEdit.Text;
|
|
cdtNull: Col.DefaultText := 'NULL';
|
|
cdtExpression: Col.DefaultText := FExpressionEdit.Text;
|
|
cdtAutoInc: Col.DefaultText := Col.AutoIncName;
|
|
end;
|
|
|
|
if FOnUpdateEdit.Text <> '' then
|
|
Col.OnUpdateType := cdtExpression
|
|
else
|
|
Col.OnUpdateType := cdtNothing;
|
|
Col.OnUpdateText := FOnUpdateEdit.Text;
|
|
|
|
FTree.Text[FNode, FColumn] := Col.DefaultText;
|
|
if FTree.CanFocus then
|
|
FTree.SetFocus;
|
|
end;
|
|
end;
|
|
|
|
|
|
procedure TColumnDefaultEditorLink.RadioClick(Sender: TObject);
|
|
begin
|
|
if not FRadioText.Checked then
|
|
FTextEdit.Color := clBtnFace
|
|
else begin
|
|
FTextEdit.Color := clWindow;
|
|
if FTextEdit.CanFocus then
|
|
FTextEdit.SetFocus;
|
|
end;
|
|
if not FRadioExpression.Checked then
|
|
FExpressionEdit.Color := clBtnFace
|
|
else begin
|
|
FExpressionEdit.Color := clWindow;
|
|
if FExpressionEdit.CanFocus then
|
|
FExpressionEdit.SetFocus;
|
|
end;
|
|
FModified := True;
|
|
end;
|
|
|
|
|
|
procedure TColumnDefaultEditorLink.EditChange(Sender: TObject);
|
|
begin
|
|
if Sender = FTextEdit then
|
|
FRadioText.Checked := True
|
|
else if Sender = FExpressionEdit then
|
|
FRadioExpression.Checked := True;
|
|
FModified := True;
|
|
end;
|
|
|
|
|
|
procedure TColumnDefaultEditorLink.EditButtonClick(Sender: TObject);
|
|
begin
|
|
TExtForm.ShowPopup(FTextEdit.Button, FTextDropDown);
|
|
end;
|
|
|
|
procedure TColumnDefaultEditorLink.EditDropDownClick(Sender: TObject);
|
|
var
|
|
Item: TMenuItem;
|
|
NewValue: String;
|
|
begin
|
|
// ENUM or SET value clicked in drop down menu
|
|
Item := Sender as TMenuItem;
|
|
Item.Checked := not Item.Checked;
|
|
NewValue := '';
|
|
for Item in Item.GetParentMenu.Items do begin
|
|
if Item.Checked then
|
|
NewValue := NewValue + StripHotkey(Item.Caption) + ',';
|
|
end;
|
|
if not NewValue.IsEmpty then
|
|
Delete(NewValue, Length(NewValue), 1);
|
|
FTextEdit.Text := NewValue;
|
|
FModified := True;
|
|
end;
|
|
|
|
procedure TColumnDefaultEditorLink.BtnOkClick(Sender: TObject);
|
|
begin
|
|
// Timer based click on OK button, to prevent crash when theming is active
|
|
FEndTimer.OnTimer := DoEndEdit;
|
|
FEndTimer.Enabled := True;
|
|
end;
|
|
|
|
procedure TColumnDefaultEditorLink.BtnCancelClick(Sender: TObject);
|
|
begin
|
|
// Timer based click on Cancel button, to prevent crash when theming is active
|
|
FEndTimer.OnTimer := DoCancelEdit;
|
|
FEndTimer.Enabled := True;
|
|
end;
|
|
|
|
|
|
|
|
|
|
{ Datatype selector }
|
|
constructor TDataTypeEditorLink.Create(Tree: TVirtualStringTree; AllowEdit: Boolean; Col: TTableColumn);
|
|
begin
|
|
inherited;
|
|
|
|
FTreeSelect := TVirtualStringTree.Create(FParentForm);
|
|
FTreeSelect.Hide;
|
|
FTreeSelect.TreeOptions.PaintOptions := FTreeSelect.TreeOptions.PaintOptions
|
|
- [toShowTreeLines, toShowButtons, toShowRoot]
|
|
+ [toHotTrack, toUseExplorerTheme, toHideTreeLinesIfThemed];
|
|
FTreeSelect.TreeOptions.SelectionOptions := FTreeSelect.TreeOptions.SelectionOptions
|
|
+ [toFullRowSelect];
|
|
FTreeSelect.Header.Columns.Add;
|
|
FTreeSelect.Parent := FParentForm;
|
|
FTreeSelect.TextMargin := 0;
|
|
//FTreeSelect.BorderStyle := bsNone;
|
|
//FTreeSelect.BevelKind := bkFlat;
|
|
//FTreeSelect.BevelInner := bvNone;
|
|
FTreeSelect.IncrementalSearch := isAll;
|
|
FTreeSelect.RootNodeCount := Length(DatatypeCategories);
|
|
FTreeSelect.OnGetText := DoTreeSelectGetText;
|
|
FTreeSelect.OnInitNode := DoTreeSelectInitNode;
|
|
FTreeSelect.OnInitChildren := DoTreeSelectInitChildren;
|
|
FTreeSelect.OnKeyDown := DoKeyDown;
|
|
FTreeSelect.OnHotChange := DoTreeSelectHotChange;
|
|
FTreeSelect.OnPaintText := DoTreeSelectPaintText;
|
|
FTreeSelect.OnExit := DoEndEdit;
|
|
// See further events in PrepareEdit
|
|
FixVT(FTreeSelect);
|
|
FMainControl := FTreeSelect;
|
|
|
|
FMemoHelp := TMemo.Create(FParentForm);
|
|
FMemoHelp.Hide;
|
|
FMemoHelp.Parent := FParentForm;
|
|
FMemoHelp.Color := clInfoBk;
|
|
FMemoHelp.Font.Color := clInfoText;
|
|
//FMemoHelp.BevelKind := bkFlat;
|
|
{if TStyleManager.IsCustomStyleActive then begin
|
|
FMemoHelp.BorderStyle := bsSingle;
|
|
FMemoHelp.BevelInner := bvNone;
|
|
end else begin
|
|
FMemoHelp.BorderStyle := bsNone;
|
|
FMemoHelp.BevelInner := bvSpace;
|
|
end;}
|
|
end;
|
|
|
|
|
|
destructor TDataTypeEditorLink.Destroy;
|
|
begin
|
|
FreeAndNil(FTreeSelect);
|
|
FreeAndNil(FMemoHelp);
|
|
inherited;
|
|
end;
|
|
|
|
|
|
function TDataTypeEditorLink.PrepareEdit(Tree: TBaseVirtualTree; Node: PVirtualNode; Column: TColumnIndex): Boolean;
|
|
var
|
|
dt: TDBDatatype;
|
|
CatNode, TypeNode: PVirtualNode;
|
|
begin
|
|
Result := inherited PrepareEdit(Tree, Node, Column);
|
|
if not Result then
|
|
Exit;
|
|
// Just use font name and size, avoid bold style and white color
|
|
FTreeSelect.Font.Name := FCellFont.Name;
|
|
FTreeSelect.Font.Size := FCellFont.Size;
|
|
|
|
// Find and select current datatype in tree
|
|
dt := FTableColumn.Connection.GetDataTypeByName(FCellText, False, FTableColumn.Name);
|
|
CatNode := FTreeSelect.GetFirst;
|
|
while Assigned(CatNode) do begin
|
|
// Since recent update to VT 5.2.1 we need to initialize root nodes by hand for some reason:
|
|
FTreeSelect.ReinitNode(CatNode, True);
|
|
if CatNode.Index = Cardinal(dt.Category) then begin
|
|
TypeNode := FTreeSelect.GetFirstChild(CatNode);
|
|
while Assigned(TypeNode) do begin
|
|
if FTreeSelect.Text[TypeNode, 0] = FCellText then begin
|
|
FTreeSelect.FocusedNode := TypeNode;
|
|
FTreeSelect.Selected[TypeNode] := True;
|
|
break;
|
|
end;
|
|
TypeNode := FTreeSelect.GetNextSibling(TypeNode);
|
|
end;
|
|
end;
|
|
CatNode := FTreeSelect.GetNextSibling(CatNode);
|
|
end;
|
|
FTreeSelect.Header.AutoFitColumns(False, smaUseColumnOption, 0, 0);
|
|
if Assigned(FTreeSelect.FocusedNode) then
|
|
FTreeSelect.ScrollIntoView(FTreeSelect.FocusedNode, True);
|
|
FTreeSelect.OnFocusChanging := DoTreeSelectFocusChanging;
|
|
FTreeSelect.OnFocusChanged := DoTreeSelectFocusChanged;
|
|
FTreeSelect.OnClick := DoEndEdit;
|
|
end;
|
|
|
|
|
|
function TDataTypeEditorLink.BeginEdit: Boolean;
|
|
begin
|
|
Result := inherited BeginEdit;
|
|
if Result then begin
|
|
FTreeSelect.Show;
|
|
FTreeSelect.SetFocus;
|
|
end;
|
|
end;
|
|
|
|
|
|
function TDataTypeEditorLink.EndEdit: Boolean;
|
|
begin
|
|
if Assigned(FTreeSelect.FocusedNode) then
|
|
Result := EndEditHelper(FTreeSelect.Text[FTreeSelect.FocusedNode, 0])
|
|
else
|
|
Result := FTree.CancelEditNode;
|
|
end;
|
|
|
|
|
|
procedure TDataTypeEditorLink.SetBounds(R: TRect);
|
|
var
|
|
CellRect: TRect;
|
|
TreeHeight: Integer;
|
|
begin
|
|
// Set position of tree. As the tree's parent is mainform, not listcolumns, add listcolumn's x + y positions
|
|
CellRect := GetCellRect(False);
|
|
// Do not exceed lower edge of mainform, as that portion would be hidden
|
|
TreeHeight := Min(250, FParentForm.ClientHeight-CellRect.Top-10);
|
|
FTreeSelect.SetBounds(CellRect.Left,
|
|
CellRect.Top,
|
|
FTreeSelect.Header.Columns[0].Width + GetSystemMetrics(SM_CXVSCROLL) + 5,
|
|
TreeHeight);
|
|
end;
|
|
|
|
|
|
procedure TDataTypeEditorLink.DoTreeSelectInitNode(Sender: TBaseVirtualTree;
|
|
ParentNode, Node: PVirtualNode; var InitialStates: TVirtualNodeInitStates);
|
|
begin
|
|
// First level nodes always expanded
|
|
if Sender.GetNodeLevel(Node) = 0 then
|
|
InitialStates := InitialStates + [ivsExpanded, ivsHasChildren];
|
|
end;
|
|
|
|
|
|
procedure TDataTypeEditorLink.DoTreeSelectInitChildren(Sender: TBaseVirtualTree;
|
|
Node: PVirtualNode; var ChildCount: Cardinal);
|
|
var
|
|
i: Integer;
|
|
begin
|
|
// Tell number of datatypes per category
|
|
ChildCount := 0;
|
|
if Sender.GetNodeLevel(Node) = 0 then for i:=0 to High(FTableColumn.Connection.Datatypes) do begin
|
|
if FTableColumn.Connection.Datatypes[i].Category = TDBDatatypeCategoryIndex(Node.Index) then
|
|
Inc(ChildCount);
|
|
end;
|
|
end;
|
|
|
|
|
|
procedure TDataTypeEditorLink.DoTreeSelectGetText(Sender: TBaseVirtualTree; Node: PVirtualNode;
|
|
Column: TColumnIndex; TextType: TVSTTextType; var CellText: String);
|
|
var
|
|
i: Integer;
|
|
Counter: Cardinal;
|
|
begin
|
|
// Get cell text
|
|
case Sender.GetNodeLevel(Node) of
|
|
0: CellText := DatatypeCategories[TDBDatatypeCategoryIndex(Node.Index)].Name;
|
|
1: begin
|
|
Counter := 0;
|
|
for i:=0 to High(FTableColumn.Connection.Datatypes) do begin
|
|
if FTableColumn.Connection.Datatypes[i].Category = TDBDatatypeCategoryIndex(Node.Parent.Index) then begin
|
|
Inc(Counter);
|
|
if Counter = Node.Index+1 then begin
|
|
CellText := FTableColumn.Connection.Datatypes[i].Name;
|
|
break;
|
|
end;
|
|
end;
|
|
end;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
|
|
procedure TDataTypeEditorLink.DoTreeSelectHotChange(Sender: TBaseVirtualTree; OldNode, NewNode: PVirtualNode);
|
|
var
|
|
R: TRect;
|
|
NodeText: String;
|
|
bmp: TBitMap;
|
|
begin
|
|
// Display help box for hovered datatype
|
|
FMemoHelp.Clear;
|
|
if Assigned(NewNode) and (Sender.GetNodeLevel(NewNode) = 1) then begin
|
|
R := FTreeSelect.GetDisplayRect(NewNode, 0, False);
|
|
NodeText := FTreeSelect.Text[NewNode, 0];
|
|
FMemoHelp.Width := Min(250, FTreeSelect.Left);
|
|
FMemoHelp.Left := FTreeSelect.Left - FMemoHelp.Width + (Integer(FTreeSelect.Indent) Div 2);
|
|
FMemoHelp.Top := FTreeSelect.Top + R.Top + 3;
|
|
FMemoHelp.Text := FTableColumn.Connection.GetDatatypeByName(NodeText, False, FTableColumn.Name).Description;
|
|
// Calc height of memo
|
|
bmp := TBitMap.Create;
|
|
bmp.Canvas.Font.Assign(FMemoHelp.Font);
|
|
R := Rect(0, 0, FMemoHelp.Width-10, 0);
|
|
DrawText(bmp.Canvas.Handle, PChar(FMemoHelp.Text), Length(FMemoHelp.Text), R, DT_WORDBREAK or DT_CALCRECT);
|
|
FreeAndNil(bmp);
|
|
FMemoHelp.Height := R.Bottom + 8;
|
|
FMemoHelp.Show;
|
|
end;
|
|
if FMemoHelp.GetTextLen = 0 then
|
|
FMemoHelp.Hide;
|
|
end;
|
|
|
|
|
|
procedure TDataTypeEditorLink.DoTreeSelectPaintText(Sender: TBaseVirtualTree;
|
|
const TargetCanvas: TCanvas; Node: PVirtualNode; Column: TColumnIndex;
|
|
TextType: TVSTTextType);
|
|
begin
|
|
// Give datatype column specific color, as set in preferences
|
|
case Sender.GetNodeLevel(Node) of
|
|
0: TargetCanvas.Font.Style := TargetCanvas.Font.Style + [fsBold];
|
|
1: if not (vsSelected in Node.States) then
|
|
TargetCanvas.Font.Color := AppColorSchemes.First.GridTextColors[TDBDatatypeCategoryIndex(Node.Parent.Index)];
|
|
end;
|
|
end;
|
|
|
|
|
|
procedure TDataTypeEditorLink.DoTreeSelectFocusChanging(Sender: TBaseVirtualTree; OldNode, NewNode:
|
|
PVirtualNode; OldColumn, NewColumn: TColumnIndex; var Allowed: Boolean);
|
|
var
|
|
JumpToNode: PVirtualNode;
|
|
begin
|
|
// Allow only 2nd level datatypes to be focused, not their category
|
|
Allowed := Sender.GetNodeLevel(NewNode) = 1;
|
|
if not Allowed then begin
|
|
JumpToNode := nil;
|
|
if FLastKeyDown = VK_UP then
|
|
JumpToNode := Sender.GetPrevious(NewNode)
|
|
else if FLastKeyDown = VK_DOWN then
|
|
JumpToNode := Sender.GetNext(NewNode);
|
|
if Assigned(JumpToNode) then
|
|
Sender.FocusedNode := JumpToNode;
|
|
end;
|
|
end;
|
|
|
|
|
|
procedure TDataTypeEditorLink.DoTreeSelectFocusChanged(Sender: TBaseVirtualTree; Node: PVirtualNode; Column: TColumnIndex);
|
|
begin
|
|
FModified := True;
|
|
end;
|
|
|
|
|
|
end.
|