mirror of
https://github.com/HeidiSQL/HeidiSQL.git
synced 2025-08-06 18:24:26 +08:00

* Replace the pulldown by a list to get a better overview * Display last connected and created date in details * Prompt user on form closing if modifications should be saved * Remove moreorless useless image * Remove options "timeout" and "sort database" TODO: Display some help text on the right side when no session at all is existant to help new users.
1826 lines
64 KiB
ObjectPascal
1826 lines
64 KiB
ObjectPascal
unit table_editor;
|
|
|
|
interface
|
|
|
|
uses
|
|
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
|
|
Dialogs, StdCtrls, TntStdCtrls, ComCtrls, ToolWin, VirtualTrees, WideStrings,
|
|
SynRegExpr, ActiveX, DB, ExtCtrls, ImgList, SynEdit, SynMemo, Menus, WideStrUtils;
|
|
|
|
type
|
|
TfrmTableEditor = class(TFrame)
|
|
btnSave: TButton;
|
|
btnDiscard: TButton;
|
|
btnHelp: TButton;
|
|
listColumns: TVirtualStringTree;
|
|
PageControlMain: TPageControl;
|
|
tabBasic: TTabSheet;
|
|
tabIndexes: TTabSheet;
|
|
tabOptions: TTabSheet;
|
|
lblName: TLabel;
|
|
editName: TTntEdit;
|
|
memoComment: TTntMemo;
|
|
lblComment: TLabel;
|
|
lblAutoinc: TLabel;
|
|
lblAvgRowLen: TLabel;
|
|
lblInsertMethod: TLabel;
|
|
lblUnion: TLabel;
|
|
lblMaxRows: TLabel;
|
|
lblRowFormat: TLabel;
|
|
editAutoInc: TEdit;
|
|
editAvgRowLen: TEdit;
|
|
editMaxRows: TEdit;
|
|
chkChecksum: TCheckBox;
|
|
comboRowFormat: TComboBox;
|
|
memoUnionTables: TTntMemo;
|
|
comboInsertMethod: TComboBox;
|
|
lblCollation: TLabel;
|
|
comboCollation: TTNTComboBox;
|
|
lblEngine: TLabel;
|
|
comboEngine: TComboBox;
|
|
treeIndexes: TVirtualStringTree;
|
|
tlbIndexes: TToolBar;
|
|
btnAddIndex: TToolButton;
|
|
btnRemoveIndex: TToolButton;
|
|
btnClearIndexes: TToolButton;
|
|
btnMoveUpIndex: TToolButton;
|
|
btnMoveDownIndex: TToolButton;
|
|
StaticText1: TStaticText;
|
|
pnlColumnsTop: TPanel;
|
|
tlbColumns: TToolBar;
|
|
btnAddColumn: TToolButton;
|
|
btnRemoveColumn: TToolButton;
|
|
btnMoveUpColumn: TToolButton;
|
|
btnMoveDownColumn: TToolButton;
|
|
SplitterTopBottom: TSplitter;
|
|
tabCREATEcode: TTabSheet;
|
|
tabALTERCode: TTabSheet;
|
|
SynMemoCREATEcode: TSynMemo;
|
|
SynMemoALTERcode: TSynMemo;
|
|
popupIndexes: TPopupMenu;
|
|
menuAddIndex: TMenuItem;
|
|
menuAddIndexColumn: TMenuItem;
|
|
menuRemoveIndex: TMenuItem;
|
|
menuMoveUpIndex: TMenuItem;
|
|
menuMoveDownIndex: TMenuItem;
|
|
menuClearIndexes: TMenuItem;
|
|
popupColumns: TPopupMenu;
|
|
menuAddColumn: TMenuItem;
|
|
menuRemoveColumn: TMenuItem;
|
|
menuMoveUpColumn: TMenuItem;
|
|
menuMoveDownColumn: TMenuItem;
|
|
chkCharsetConvert: TCheckBox;
|
|
N1: TMenuItem;
|
|
menuCreateIndex: TMenuItem;
|
|
menuAddToIndex: TMenuItem;
|
|
procedure editNameChange(Sender: TObject);
|
|
procedure Modification(Sender: TObject);
|
|
procedure btnAddColumnClick(Sender: TObject);
|
|
procedure btnRemoveColumnClick(Sender: TObject);
|
|
procedure listColumnsBeforePaint(Sender: TBaseVirtualTree; TargetCanvas: TCanvas);
|
|
procedure listColumnsFocusChanged(Sender: TBaseVirtualTree; Node: PVirtualNode; Column: TColumnIndex);
|
|
procedure btnHelpClick(Sender: TObject);
|
|
procedure listColumnsGetText(Sender: TBaseVirtualTree; Node: PVirtualNode;
|
|
Column: TColumnIndex; TextType: TVSTTextType; var CellText: WideString);
|
|
procedure listColumnsEditing(Sender: TBaseVirtualTree; Node: PVirtualNode; Column: TColumnIndex; var Allowed: Boolean);
|
|
procedure listColumnsNewText(Sender: TBaseVirtualTree; Node: PVirtualNode; Column: TColumnIndex; NewText: WideString);
|
|
procedure btnMoveUpColumnClick(Sender: TObject);
|
|
procedure btnMoveDownColumnClick(Sender: TObject);
|
|
procedure listColumnsDragOver(Sender: TBaseVirtualTree; Source: TObject; Shift: TShiftState; State: TDragState;
|
|
Pt: TPoint; Mode: TDropMode; var Effect: Integer; var Accept: Boolean);
|
|
procedure listColumnsDragDrop(Sender: TBaseVirtualTree; Source: TObject; DataObject: IDataObject; Formats: TFormatArray;
|
|
Shift: TShiftState; Pt: TPoint; var Effect: Integer; Mode: TDropMode);
|
|
procedure listColumnsPaintText(Sender: TBaseVirtualTree; const TargetCanvas: TCanvas; Node: PVirtualNode;
|
|
Column: TColumnIndex; TextType: TVSTTextType);
|
|
procedure listColumnsCreateEditor(Sender: TBaseVirtualTree; Node: PVirtualNode; Column: TColumnIndex; out EditLink: IVTEditLink);
|
|
procedure treeIndexesInitNode(Sender: TBaseVirtualTree; ParentNode, Node: PVirtualNode; var InitialStates: TVirtualNodeInitStates);
|
|
procedure treeIndexesGetText(Sender: TBaseVirtualTree; Node: PVirtualNode; Column: TColumnIndex; TextType: TVSTTextType;
|
|
var CellText: WideString);
|
|
procedure treeIndexesBeforePaint(Sender: TBaseVirtualTree; TargetCanvas: TCanvas);
|
|
procedure treeIndexesInitChildren(Sender: TBaseVirtualTree; Node: PVirtualNode; var ChildCount: Cardinal);
|
|
procedure treeIndexesGetImageIndex(Sender: TBaseVirtualTree; Node: PVirtualNode; Kind: TVTImageKind; Column: TColumnIndex;
|
|
var Ghosted: Boolean; var ImageIndex: Integer);
|
|
procedure btnClearIndexesClick(Sender: TObject);
|
|
procedure btnSaveClick(Sender: TObject);
|
|
procedure editNumEditChange(Sender: TObject);
|
|
procedure comboEngineSelect(Sender: TObject);
|
|
procedure listColumnsClick(Sender: TObject);
|
|
procedure listColumnsBeforeCellPaint(Sender: TBaseVirtualTree;
|
|
TargetCanvas: TCanvas; Node: PVirtualNode; Column: TColumnIndex;
|
|
CellPaintMode: TVTCellPaintMode; CellRect: TRect; var ContentRect: TRect);
|
|
procedure listColumnsAfterCellPaint(Sender: TBaseVirtualTree;
|
|
TargetCanvas: TCanvas; Node: PVirtualNode; Column: TColumnIndex;
|
|
CellRect: TRect);
|
|
procedure btnAddIndexClick(Sender: TObject);
|
|
procedure treeIndexesDragOver(Sender: TBaseVirtualTree; Source: TObject;
|
|
Shift: TShiftState; State: TDragState; Pt: TPoint; Mode: TDropMode;
|
|
var Effect: Integer; var Accept: Boolean);
|
|
procedure treeIndexesDragDrop(Sender: TBaseVirtualTree; Source: TObject;
|
|
DataObject: IDataObject; Formats: TFormatArray; Shift: TShiftState;
|
|
Pt: TPoint; var Effect: Integer; Mode: TDropMode);
|
|
procedure treeIndexesNewText(Sender: TBaseVirtualTree; Node: PVirtualNode; Column: TColumnIndex; NewText: WideString);
|
|
procedure treeIndexesEditing(Sender: TBaseVirtualTree; Node: PVirtualNode; Column: TColumnIndex; var Allowed: Boolean);
|
|
procedure treeIndexesFocusChanged(Sender: TBaseVirtualTree; Node: PVirtualNode; Column: TColumnIndex);
|
|
procedure treeIndexesCreateEditor(Sender: TBaseVirtualTree; Node: PVirtualNode; Column: TColumnIndex; out EditLink: IVTEditLink);
|
|
procedure btnMoveUpIndexClick(Sender: TObject);
|
|
procedure btnMoveDownIndexClick(Sender: TObject);
|
|
procedure btnRemoveIndexClick(Sender: TObject);
|
|
procedure menuAddIndexColumnClick(Sender: TObject);
|
|
procedure PageControlMainChange(Sender: TObject);
|
|
procedure chkCharsetConvertClick(Sender: TObject);
|
|
procedure treeIndexesClick(Sender: TObject);
|
|
procedure btnDiscardClick(Sender: TObject);
|
|
procedure popupColumnsPopup(Sender: TObject);
|
|
procedure AddIndexByColumn(Sender: TObject);
|
|
procedure listColumnsGetImageIndex(Sender: TBaseVirtualTree;
|
|
Node: PVirtualNode; Kind: TVTImageKind; Column: TColumnIndex;
|
|
var Ghosted: Boolean; var ImageIndex: Integer);
|
|
private
|
|
{ Private declarations }
|
|
FModified: Boolean;
|
|
FLoaded: Boolean;
|
|
FAlterTableName: WideString;
|
|
CreateCodeValid, AlterCodeValid: Boolean;
|
|
Columns, ColumnNames, ColumnsChanges,
|
|
Indexes, OldIndexes: TWideStringList;
|
|
procedure ColumnsChange(Sender: TObject);
|
|
procedure IndexesChange(Sender: TObject);
|
|
procedure ValidateColumnControls;
|
|
procedure ValidateIndexControls;
|
|
procedure MoveFocusedColumn(NewIdx: Cardinal);
|
|
procedure MoveFocusedIndexPart(NewIdx: Cardinal);
|
|
procedure SetModified(Value: Boolean);
|
|
procedure ResetModificationFlags;
|
|
function ComposeCreateStatement: WideString;
|
|
function ComposeAlterStatement: WideString;
|
|
function GetIndexSQL(IndexList: TWideStringlist; idx: Integer): WideString;
|
|
property Modified: Boolean read FModified write SetModified;
|
|
procedure UpdateSQLcode;
|
|
function CellEditingAllowed(Node: PVirtualNode; Column: TColumnIndex): Boolean;
|
|
function GetIndexIcon(idx: Integer): Integer;
|
|
procedure GetIndexInfo(idx: Integer; var IndexName, IndexType: WideString);
|
|
public
|
|
{ Public declarations }
|
|
constructor Create(AOwner: TComponent); override;
|
|
destructor Destroy; override;
|
|
procedure Init(AlterTableName: WideString='');
|
|
end;
|
|
|
|
|
|
implementation
|
|
|
|
uses main, helpers, grideditlinks, mysql_structures;
|
|
|
|
const
|
|
PKEY = 'PRIMARY';
|
|
KEY = 'KEY';
|
|
UKEY = 'UNIQUE';
|
|
FKEY = 'FULLTEXT';
|
|
SKEY = 'SPATIAL';
|
|
NotModifiedFlag : Byte = 0;
|
|
ModifiedFlag : Byte = 1;
|
|
|
|
|
|
{$R *.dfm}
|
|
|
|
|
|
constructor TfrmTableEditor.Create(AOwner: TComponent);
|
|
begin
|
|
inherited Create(AOwner);
|
|
// Do not set alClient via DFM! In conjunction with ExplicitXXX properties that
|
|
// repeatedly breaks the GUI layout when you reload the project
|
|
Align := alClient;
|
|
PageControlMain.Height := GetRegValue(REGNAME_TABLEEDITOR_TABSHEIGHT, PageControlMain.Height);
|
|
InheritFont(Font);
|
|
FixVT(listColumns);
|
|
FixVT(treeIndexes);
|
|
// Try the best to auto fit various column widths, respecting a custom DPI setting and a pulldown arrow
|
|
listColumns.Header.Columns[2].Width := Mainform.Canvas.TextWidth('GEOMETRYCOLLECTION') + 6*listColumns.TextMargin;
|
|
listColumns.Header.Columns[6].Width := Mainform.Canvas.TextWidth('AUTO_INCREMENT') + 4*listColumns.TextMargin;
|
|
listColumns.Header.Columns[8].Width := Mainform.Canvas.TextWidth('macroman_general_ci') + 6*listColumns.TextMargin;
|
|
// Overide column widths by custom values
|
|
Mainform.RestoreListSetup(listColumns);
|
|
Mainform.RestoreListSetup(treeIndexes);
|
|
comboRowFormat.Items.CommaText := 'DEFAULT,DYNAMIC,FIXED,COMPRESSED,REDUNDANT,COMPACT';
|
|
comboInsertMethod.Items.CommaText := 'NO,FIRST,LAST';
|
|
SynMemoALTERcode.TabWidth := Mainform.SynMemoQuery.TabWidth;
|
|
SynMemoALTERcode.Font.Name := Mainform.SynMemoQuery.Font.Name;
|
|
SynMemoALTERcode.Font.Size := Mainform.SynMemoQuery.Font.Size;
|
|
SynMemoCREATEcode.TabWidth := Mainform.SynMemoQuery.TabWidth;
|
|
SynMemoCREATEcode.Font.Name := Mainform.SynMemoQuery.Font.Name;
|
|
SynMemoCREATEcode.Font.Size := Mainform.SynMemoQuery.Font.Size;
|
|
Columns := TWideStringList.Create;
|
|
Columns.OnChange := ColumnsChange;
|
|
ColumnNames := TWideStringList.Create;
|
|
ColumnsChanges := TWideStringList.Create;
|
|
Indexes := TWideStringList.Create;
|
|
Indexes.OnChange := IndexesChange;
|
|
OldIndexes := TWideStringList.Create;
|
|
end;
|
|
|
|
|
|
destructor TfrmTableEditor.Destroy;
|
|
begin
|
|
// Store GUI setup
|
|
OpenRegistry;
|
|
MainReg.WriteInteger(REGNAME_TABLEEDITOR_TABSHEIGHT, PageControlMain.Height);
|
|
Mainform.SaveListSetup(listColumns);
|
|
Mainform.SaveListSetup(treeIndexes);
|
|
Inherited;
|
|
end;
|
|
|
|
|
|
procedure TfrmTableEditor.Init(AlterTableName: WideString='');
|
|
var
|
|
ds: TDataset;
|
|
Props: TWideStringlist;
|
|
IndexType: String;
|
|
LastKeyName, CreateTable, engine, ColSpec: WideString;
|
|
rx, rxCol: TRegExpr;
|
|
i: Integer;
|
|
InLiteral: Boolean;
|
|
ColDefaultType: TColumnDefaultType;
|
|
ColDefaultText: WideString;
|
|
begin
|
|
Mainform.showstatus('Initializing editor ...');
|
|
Screen.Cursor := crHourglass;
|
|
FLoaded := False;
|
|
// Start with "basic" tab activated when just called
|
|
if FAlterTableName <> AlterTableName then
|
|
PageControlMain.ActivePage := tabBasic;
|
|
Mainform.TableEnginesCombo(comboEngine);
|
|
comboCollation.Items.Clear;
|
|
Mainform.GetCollations(comboCollation.Items);
|
|
FAlterTableName := AlterTableName;
|
|
Columns.Clear;
|
|
btnClearIndexesClick(Self);
|
|
tabALTERcode.TabVisible := FAlterTableName <> '';
|
|
|
|
if FAlterTableName = '' then begin
|
|
// Creating new table
|
|
editName.Clear;
|
|
Mainform.SetEditorTabCaption(Self, '');
|
|
memoComment.Text := '';
|
|
editAutoInc.Text := '';
|
|
editAvgRowLen.Text := '';
|
|
editMaxRows.Text := '';
|
|
chkChecksum.Checked := False;
|
|
comboRowFormat.ItemIndex := 0;
|
|
comboCollation.ItemIndex := comboCollation.Items.IndexOf(Mainform.GetVar('SHOW VARIABLES LIKE ''collation_database''', 1));
|
|
memoUnionTables.Clear;
|
|
comboInsertMethod.ItemIndex := -1;
|
|
|
|
PageControlMain.ActivePage := tabBasic;
|
|
|
|
end else begin
|
|
// Editing existing table
|
|
editName.Text := FAlterTableName;
|
|
Mainform.SetEditorTabCaption(Self, FAlterTableName);
|
|
ds := Mainform.GetResults('SHOW TABLE STATUS LIKE '+esc(FAlterTableName));
|
|
memoComment.Text := ds.FieldByName(DBO_COMMENT).AsWideString;
|
|
if ds.FindField(DBO_ENGINE) <> nil then
|
|
engine := ds.FieldByName(DBO_ENGINE).AsString
|
|
else
|
|
engine := ds.FieldByName(DBO_TYPE).AsString;
|
|
comboEngine.ItemIndex := comboEngine.Items.IndexOf(engine);
|
|
if ds.FindField(DBO_COLLATION) <> nil then
|
|
comboCollation.ItemIndex := comboCollation.Items.IndexOf(ds.FieldByName(DBO_COLLATION).AsWideString);
|
|
editAutoInc.Text := ds.FieldByName(DBO_AUTOINC).AsString;
|
|
editAvgRowLen.Text := ds.FieldByName(DBO_AVGROWLEN).AsString;
|
|
comboRowFormat.ItemIndex := comboRowFormat.Items.IndexOf(ds.FieldByName(DBO_ROWFORMAT).AsString);
|
|
FreeAndNil(ds);
|
|
CreateTable := Mainform.GetVar('SHOW CREATE TABLE '+Mainform.mask(FAlterTableName), 1);
|
|
rx := TRegExpr.Create;
|
|
rx.ModifierI := True;
|
|
rx.Expression := '\bUNION=\((.+)\)';
|
|
if rx.Exec(CreateTable) then
|
|
memoUnionTables.Text := rx.Match[1]
|
|
else
|
|
memoUnionTables.Clear;
|
|
|
|
|
|
listColumns.BeginUpdate;
|
|
rx.ModifierS := False;
|
|
rx.Expression := '\s+`([^`]+)`\s(\w+)(.*)';
|
|
rxCol := TRegExpr.Create;
|
|
rxCol.ModifierI := True;
|
|
if rx.Exec(CreateTable) then while true do begin
|
|
Props := TWideStringlist.Create;
|
|
Props.OnChange := ColumnsChange;
|
|
// Create needed number of property columns
|
|
Props.CommaText := ',,,,,,';
|
|
ColSpec := rx.Match[3];
|
|
|
|
// Strip trailing comma
|
|
if (ColSpec <> '') and (ColSpec[Length(ColSpec)] = ',') then
|
|
Delete(ColSpec, Length(ColSpec), 1);
|
|
|
|
// Datatype
|
|
Props[0] := UpperCase(rx.Match[2]);
|
|
|
|
// Length / Set
|
|
// Various datatypes, e.g. BLOBs, don't have any length property
|
|
InLiteral := False;
|
|
if (ColSpec <> '') and (ColSpec[1] = '(') then begin
|
|
for i:=2 to Length(ColSpec) do begin
|
|
if (ColSpec[i] = ')') and (not InLiteral) then
|
|
break;
|
|
if ColSpec[i] = '''' then
|
|
InLiteral := not InLiteral;
|
|
end;
|
|
Props[1] := Copy(ColSpec, 2, i-2);
|
|
Delete(ColSpec, 1, i);
|
|
end;
|
|
ColSpec := Trim(ColSpec);
|
|
|
|
// Unsigned
|
|
if UpperCase(Copy(ColSpec, 1, 8)) = 'UNSIGNED' then begin
|
|
Props[2] := BoolToStr(True);
|
|
Delete(ColSpec, 1, 9);
|
|
end else
|
|
Props[2] := BoolToStr(False);
|
|
|
|
// Collation
|
|
rxCol.Expression := '^(CHARACTER SET \w+\s+)?COLLATE (\w+)\b';
|
|
if rxCol.Exec(ColSpec) then begin
|
|
Props[6] := rxCol.Match[2];
|
|
Delete(ColSpec, 1, rxCol.MatchLen[0]+1);
|
|
end;
|
|
|
|
// Allow NULL
|
|
if UpperCase(Copy(ColSpec, 1, 8)) = 'NOT NULL' then begin
|
|
Props[3] := BoolToStr(False);
|
|
Delete(ColSpec, 1, 9);
|
|
ColDefaultType := cdtText;
|
|
ColDefaultText := '';
|
|
end else begin
|
|
Props[3] := BoolToStr(True);
|
|
ColDefaultType := cdtNull;
|
|
ColDefaultText := 'NULL';
|
|
// Sporadically there is a "NULL" found at this position.
|
|
if UpperCase(Copy(ColSpec, 1, 4)) = 'NULL' then
|
|
Delete(ColSpec, 1, 5);
|
|
end;
|
|
|
|
// Default value
|
|
if UpperCase(Copy(ColSpec, 1, 14)) = 'AUTO_INCREMENT' then begin
|
|
ColDefaultType := cdtAutoInc;
|
|
ColDefaultText := 'AUTO_INCREMENT';
|
|
Delete(ColSpec, 1, 15);
|
|
end else if UpperCase(Copy(ColSpec, 1, 8)) = 'DEFAULT ' then begin
|
|
Delete(ColSpec, 1, 8);
|
|
if UpperCase(Copy(ColSpec, 1, 4)) = 'NULL' then begin
|
|
ColDefaultType := cdtNull;
|
|
ColDefaultText := 'NULL';
|
|
Delete(ColSpec, 1, 5);
|
|
end else if UpperCase(Copy(ColSpec, 1, 17)) = 'CURRENT_TIMESTAMP' then begin
|
|
ColDefaultType := cdtCurTS;
|
|
ColDefaultText := 'CURRENT_TIMESTAMP';
|
|
Delete(ColSpec, 1, 18);
|
|
end else if ColSpec[1] = '''' then begin
|
|
InLiteral := True;
|
|
for i:=2 to Length(ColSpec) do begin
|
|
if ColSpec[i] = '''' then
|
|
InLiteral := not InLiteral
|
|
else if not InLiteral then
|
|
break;
|
|
end;
|
|
ColDefaultType := cdtText;
|
|
ColDefaultText := Copy(ColSpec, 2, i-3);
|
|
// A single quote gets escaped by single quote - remove the escape char - escaping is done in Save action afterwards
|
|
ColDefaultText := WideStringReplace(ColDefaultText, '''''', '''', [rfReplaceAll]);
|
|
Delete(ColSpec, 1, i);
|
|
end;
|
|
end;
|
|
if UpperCase(Copy(ColSpec, 1, 27)) = 'ON UPDATE CURRENT_TIMESTAMP' then begin
|
|
// Adjust default type
|
|
case ColDefaultType of
|
|
cdtText: ColDefaultType := cdtTextUpdateTS;
|
|
cdtNull: ColDefaultType := cdtNullUpdateTS;
|
|
cdtCurTS: ColDefaultType := cdtCurTSUpdateTS;
|
|
end;
|
|
Delete(ColSpec, 1, 28);
|
|
end;
|
|
Props[4] := IntToStr(Integer(ColDefaultType)) + ColDefaultText;
|
|
|
|
// Comment
|
|
if UpperCase(Copy(ColSpec, 1, 9)) = 'COMMENT ''' then begin
|
|
InLiteral := True;
|
|
for i:=10 to Length(ColSpec) do begin
|
|
if ColSpec[i] = '''' then
|
|
InLiteral := not InLiteral
|
|
else if not InLiteral then
|
|
break;
|
|
end;
|
|
Props[5] := Copy(ColSpec, 10, i-11);
|
|
Props[5] := WideStringReplace(Props[5], '''''', '''', [rfReplaceAll]);
|
|
Delete(ColSpec, 1, i);
|
|
end;
|
|
|
|
Columns.AddObject(rx.Match[1], Props);
|
|
if not rx.ExecNext then
|
|
break;
|
|
end;
|
|
FreeAndNil(rx);
|
|
FreeAndNil(rxCol);
|
|
listColumns.EndUpdate;
|
|
|
|
ds := Mainform.GetResults('SHOW KEYS FROM '+Mainform.mask(FAlterTableName));
|
|
LastKeyName := '';
|
|
Props := nil;
|
|
while not ds.Eof do begin
|
|
if LastKeyName <> ds.FieldByName('Key_name').AsWideString then begin
|
|
Props := TWideStringlist.Create;
|
|
Props.OnChange := IndexesChange;
|
|
IndexType := ds.FieldByName('Key_name').AsString;
|
|
if (ds.FindField('Index_type') <> nil) and (
|
|
(ds.FieldByName('Index_type').AsString = FKEY) or (ds.FieldByName('Index_type').AsString = SKEY)) then
|
|
IndexType := ds.FieldByName('Index_type').AsString
|
|
else if (IndexType <> PKEY) and (ds.FieldByName('Non_unique').AsInteger = 0) then
|
|
IndexType := UKEY
|
|
else if ds.FieldByName('Non_unique').AsInteger = 1 then
|
|
IndexType := KEY;
|
|
Indexes.AddObject(ds.FieldByName('Key_name').AsWideString+REGDELIM+IndexType, Props);
|
|
end;
|
|
Props.Add(ds.FieldByName('Column_name').AsWideString);
|
|
if ds.FieldByName('Sub_part').AsString <> '' then
|
|
Props[Props.Count-1] := Props[Props.Count-1] + '('+ds.FieldByName('Sub_part').AsString+')';
|
|
LastKeyName := ds.FieldByName('Key_name').AsWideString;
|
|
ds.Next;
|
|
end;
|
|
FreeAndNil(ds);
|
|
|
|
end;
|
|
// Validate controls
|
|
ColumnsChange(Self);
|
|
comboEngineSelect(comboEngine);
|
|
ValidateColumnControls;
|
|
ValidateIndexControls;
|
|
ResetModificationFlags;
|
|
// Indicate change mechanisms can call their events now. See Modification().
|
|
FLoaded := True;
|
|
// Empty status panel
|
|
Mainform.showstatus;
|
|
Screen.Cursor := crDefault;
|
|
end;
|
|
|
|
|
|
procedure TfrmTableEditor.btnDiscardClick(Sender: TObject);
|
|
begin
|
|
// Reinit GUI, discarding changes
|
|
Init(FAlterTableName);
|
|
end;
|
|
|
|
|
|
procedure TfrmTableEditor.btnSaveClick(Sender: TObject);
|
|
var
|
|
sql: WideString;
|
|
i: Integer;
|
|
Props: TWideStringlist;
|
|
begin
|
|
// Create or alter table
|
|
if FAlterTableName = '' then
|
|
sql := ComposeCreateStatement
|
|
else
|
|
sql := ComposeAlterStatement;
|
|
Mainform.ExecUpdateQuery(sql);
|
|
// Set table name for altering if Apply was clicked
|
|
FAlterTableName := editName.Text;
|
|
tabALTERcode.TabVisible := FAlterTableName <> '';
|
|
Mainform.SetEditorTabCaption(Self, FAlterTableName);
|
|
Mainform.tabData.TabVisible := True;
|
|
if chkCharsetConvert.Checked then begin
|
|
// Autoadjust column collations
|
|
for i:=0 to Columns.Count-1 do begin
|
|
Props := TWideStringlist(Columns.Objects[i]);
|
|
if Props[6] <> '' then begin
|
|
Props.OnChange := nil;
|
|
Props[6] := comboCollation.Text;
|
|
Props.OnChange := ColumnsChange;
|
|
end;
|
|
end;
|
|
end;
|
|
ResetModificationFlags;
|
|
Mainform.ResetSelectedTableStuff;
|
|
end;
|
|
|
|
|
|
procedure TfrmTableEditor.ResetModificationFlags;
|
|
var
|
|
i: Integer;
|
|
Parts: TWideStringlist;
|
|
begin
|
|
// Enable converting data for an existing table
|
|
chkCharsetConvertClick(comboCollation);
|
|
// Assist the user in auto unchecking this checkbox so data doesn't get converted more than once accidently
|
|
chkCharsetConvert.Checked := False;
|
|
// Reset modification flags of TEdits and TMemos
|
|
for i:=0 to ComponentCount-1 do
|
|
Components[i].Tag := NotModifiedFlag;
|
|
// Reset name value pairs for column changes
|
|
ColumnNames.Clear;
|
|
ColumnsChanges.Clear;
|
|
for i:=0 to Columns.Count-1 do
|
|
ColumnNames.Add(Columns[i] + ColumnNames.NameValueSeparator + Columns[i]);
|
|
// Copy index list so we have one to work with and one which can later be used to detect changes
|
|
OldIndexes.Clear;
|
|
for i:=0 to Indexes.Count-1 do begin
|
|
Parts := TWideStringlist.Create;
|
|
Parts.Assign(TWideStringlist(Indexes.Objects[i]));
|
|
OldIndexes.AddObject(Indexes[i], Parts);
|
|
end;
|
|
Modified := False;
|
|
end;
|
|
|
|
|
|
function TfrmTableEditor.ComposeAlterStatement: WideString;
|
|
var
|
|
Specs, Props, IndexesComposed, OldIndexesComposed: TWideStringlist;
|
|
ColSpec, IndexSQL, DefaultText: WideString;
|
|
i, j: Integer;
|
|
AddIt, DropIt: Boolean;
|
|
dt: TDatatype;
|
|
DefaultType: TColumnDefaultType;
|
|
ds: TDataset;
|
|
begin
|
|
// Compose ALTER query, called by buttons and for SQL code tab
|
|
Mainform.showstatus('Composing ALTER statement ...');
|
|
Screen.Cursor := crHourglass;
|
|
Specs := TWideStringlist.Create;
|
|
if editName.Text <> FAlterTableName then
|
|
Specs.Add('RENAME TO ' + Mainform.mask(editName.Text));
|
|
if memoComment.Tag = ModifiedFlag then
|
|
Specs.Add('COMMENT=' + esc(memoComment.Text));
|
|
if (comboCollation.Tag = ModifiedFlag) or (chkCharsetConvert.Checked) then
|
|
Specs.Add('COLLATE=' + comboCollation.Text);
|
|
if comboEngine.Tag = ModifiedFlag then
|
|
Specs.Add('ENGINE=' + comboEngine.Text);
|
|
if comboRowFormat.Tag = ModifiedFlag then
|
|
Specs.Add('ROW_FORMAT=' + comboRowFormat.Text);
|
|
if chkChecksum.Tag = ModifiedFlag then
|
|
Specs.Add('CHECKSUM=' + IntToStr(Integer(chkChecksum.Checked)));
|
|
if editAutoInc.Tag = ModifiedFlag then
|
|
Specs.Add('AUTO_INCREMENT=' + IntToStr(MakeInt(editAutoInc.Text)));
|
|
if editAvgRowLen.Tag = ModifiedFlag then
|
|
Specs.Add('AVG_ROW_LENGTH=' + IntToStr(MakeInt(editAvgRowLen.Text)));
|
|
if editMaxRows.Tag = ModifiedFlag then
|
|
Specs.Add('MAX_ROWS=' + IntToStr(MakeInt(editMaxRows.Text)));
|
|
if memoUnionTables.Enabled and (memoUnionTables.Tag = ModifiedFlag) and (memoUnionTables.Text <> '') then
|
|
Specs.Add('UNION=('+memoUnionTables.Text+')');
|
|
if comboInsertMethod.Enabled and (comboInsertMethod.Tag = ModifiedFlag) and (comboInsertMethod.Text <> '') then
|
|
Specs.Add('INSERT_METHOD='+comboInsertMethod.Text);
|
|
if chkCharsetConvert.Checked then begin
|
|
ds := Mainform.GetCollations;
|
|
while not ds.Eof do begin
|
|
if ds.FieldByName('Collation').AsWideString = comboCollation.Text then begin
|
|
Specs.Add('CONVERT TO CHARSET '+ds.FieldByName('Charset').AsWideString);
|
|
break;
|
|
end;
|
|
ds.Next;
|
|
end;
|
|
end;
|
|
|
|
// Update columns
|
|
EnableProgressBar(Columns.Count + OldIndexes.Count + Indexes.Count);
|
|
for i:=0 to Columns.Count - 1 do begin
|
|
Mainform.ProgressBarStatus.StepIt;
|
|
AddIt := True;
|
|
ColSpec := ' ' + Mainform.mask(Columns[i]);
|
|
Props := TWideStringlist(Columns.Objects[i]);
|
|
ColSpec := ColSpec + ' ' +Props[0];
|
|
if Props[1] <> '' then
|
|
ColSpec := ColSpec + '(' + Props[1] + ')';
|
|
dt := GetDatatypeByName(Props[0]);
|
|
if dt.HasUnsigned and StrToBool(Props[2]) then
|
|
ColSpec := ColSpec + ' UNSIGNED';
|
|
if not StrToBool(Props[3]) then
|
|
ColSpec := ColSpec + ' NOT';
|
|
ColSpec := ColSpec + ' NULL';
|
|
if Props[4] <> '' then begin
|
|
DefaultText := Props[4];
|
|
DefaultType := GetColumnDefaultType(DefaultText);
|
|
ColSpec := ColSpec + ' ' + GetColumnDefaultClause(DefaultType, DefaultText);
|
|
end;
|
|
if Props[5] <> '' then
|
|
ColSpec := ColSpec + ' COMMENT '+esc(Props[5]);
|
|
if Props[6] <> '' then begin
|
|
ColSpec := ColSpec + ' COLLATE ';
|
|
if chkCharsetConvert.Checked then
|
|
ColSpec := ColSpec + comboCollation.Text
|
|
else
|
|
ColSpec := ColSpec + Props[6];
|
|
end;
|
|
// Server version requirement, see http://dev.mysql.com/doc/refman/4.1/en/alter-table.html
|
|
if Mainform.mysql_version >= 40001 then begin
|
|
if i = 0 then
|
|
ColSpec := ColSpec + ' FIRST'
|
|
else
|
|
ColSpec := ColSpec + ' AFTER '+Mainform.mask(Columns[i-1]);
|
|
end;
|
|
for j:=0 to ColumnNames.Count - 1 do begin
|
|
if ColumnNames.ValueFromIndex[j] = Columns[i] then begin
|
|
AddIt := False;
|
|
// Only touch column if it was changed
|
|
if ColumnsChanges.IndexOf(Columns[i]) > -1 then
|
|
Specs.Add('CHANGE COLUMN '+Mainform.mask(ColumnNames.Names[j]) + ColSpec);
|
|
break;
|
|
end;
|
|
end;
|
|
if AddIt then
|
|
Specs.Add('ADD COLUMN '+ColSpec);
|
|
end;
|
|
// Delete columns
|
|
for i:=0 to ColumnNames.Count-1 do begin
|
|
if ColumnNames.ValueFromIndex[i] = '' then
|
|
Specs.Add('DROP COLUMN '+Mainform.mask(ColumnNames.Names[i]));
|
|
end;
|
|
|
|
// Prepare ADD INDEX ... clauses once so we don't call GetIndexSQL() too often
|
|
IndexesComposed := TWideStringlist.Create;
|
|
for i:=0 to Indexes.Count-1 do
|
|
IndexesComposed.Add(GetIndexSQL(Indexes, i));
|
|
OldIndexesComposed := TWideStringlist.Create;
|
|
for i:=0 to OldIndexes.Count-1 do
|
|
OldIndexesComposed.Add(GetIndexSQL(OldIndexes, i));
|
|
// Drop indexes
|
|
for i:=0 to OldIndexesComposed.Count-1 do begin
|
|
Mainform.ProgressBarStatus.StepIt;
|
|
DropIt := IndexesComposed.IndexOf(OldIndexesComposed[i]) = -1;
|
|
if not DropIt then
|
|
Continue;
|
|
IndexSQL := Copy(OldIndexes[i], LastPos(REGDELIM, OldIndexes[i])+1, Length(OldIndexes[i]));
|
|
if IndexSQL = PKEY then
|
|
IndexSQL := 'PRIMARY KEY'
|
|
else
|
|
IndexSQL := 'INDEX ' + Mainform.Mask(Copy(OldIndexes[i], 1, LastPos(REGDELIM, OldIndexes[i])-1));
|
|
Specs.Add('DROP '+IndexSQL);
|
|
end;
|
|
// Add changed or added indexes
|
|
for i:=0 to IndexesComposed.Count-1 do begin
|
|
Mainform.ProgressBarStatus.StepIt;
|
|
if (OldIndexesComposed.IndexOf(IndexesComposed[i]) = -1) and (IndexesComposed[i] <> '') then
|
|
Specs.Add('ADD '+IndexesComposed[i]);
|
|
end;
|
|
|
|
Result := 'ALTER TABLE '+Mainform.mask(FAlterTableName) + CRLF + #9 + ImplodeStr(',' + CRLF + #9, Specs);
|
|
Result := Trim(Result);
|
|
FreeAndNil(Specs);
|
|
FreeAndNil(IndexesComposed);
|
|
FreeAndNil(OldIndexesComposed);
|
|
Mainform.showstatus;
|
|
Mainform.ProgressBarStatus.Hide;
|
|
Screen.Cursor := crDefault;
|
|
end;
|
|
|
|
|
|
function TfrmTableEditor.ComposeCreateStatement: WideString;
|
|
var
|
|
i, IndexCount: Integer;
|
|
ColProps: TWideStringlist;
|
|
dt: TDatatype;
|
|
DefaultType: TColumnDefaultType;
|
|
DefaultText, tmp: WideString;
|
|
begin
|
|
// Compose CREATE query, called by buttons and for SQL code tab
|
|
Result := 'CREATE TABLE '+Mainform.mask(editName.Text)+' ('+CRLF;
|
|
for i:=0 to Columns.Count - 1 do begin
|
|
ColProps := TWideStringlist(Columns.Objects[i]);
|
|
Result := Result + #9 + Mainform.mask(Columns[i]) + ' ' +ColProps[0];
|
|
if ColProps[1] <> '' then
|
|
Result := Result + '(' + ColProps[1] + ')';
|
|
dt := GetDatatypeByName(ColProps[0]);
|
|
if dt.HasUnsigned and StrToBool(ColProps[2]) then
|
|
Result := Result + ' UNSIGNED';
|
|
if not StrToBool(ColProps[3]) then
|
|
Result := Result + ' NOT';
|
|
Result := Result + ' NULL';
|
|
if ColProps[4] <> '' then begin
|
|
DefaultText := ColProps[4];
|
|
DefaultType := GetColumnDefaultType(DefaultText);
|
|
Result := Result + ' ' + GetColumnDefaultClause(DefaultType, DefaultText);
|
|
end;
|
|
if ColProps[5] <> '' then
|
|
Result := Result + ' COMMENT '+esc(ColProps[5]);
|
|
if ColProps[6] <> '' then
|
|
Result := Result + ' COLLATE '+ColProps[6];
|
|
Result := Result + ','+CRLF;
|
|
end;
|
|
|
|
IndexCount := 0;
|
|
for i:=0 to Indexes.Count - 1 do begin
|
|
tmp := GetIndexSQL(Indexes, i);
|
|
if tmp <> '' then begin
|
|
Result := Result + #9 + GetIndexSQL(Indexes, i) + ','+CRLF;
|
|
Inc(IndexCount);
|
|
end;
|
|
end;
|
|
if Columns.Count + IndexCount > 0 then
|
|
Delete(Result, Length(Result)-2, 3);
|
|
|
|
Result := Result + CRLF + ')' + CRLF;
|
|
if memoComment.Text <> '' then
|
|
Result := Result + 'COMMENT='+esc(memoComment.Text) + CRLF;
|
|
if comboCollation.Text <> '' then
|
|
Result := Result + 'COLLATE='+comboCollation.Text + CRLF;
|
|
if comboEngine.Text <> '' then
|
|
Result := Result + 'ENGINE='+comboEngine.Text + CRLF;
|
|
if comboRowFormat.Text <> '' then
|
|
Result := Result + 'ROW_FORMAT='+comboRowFormat.Text + CRLF;
|
|
if chkChecksum.Checked then
|
|
Result := Result + 'CHECKSUM='+IntToStr(Integer(chkChecksum.Checked)) + CRLF;
|
|
if editAutoInc.Text <> '' then
|
|
Result := Result + 'AUTO_INCREMENT='+editAutoInc.Text + CRLF;
|
|
if editAvgRowLen.Text <> '' then
|
|
Result := Result + 'AVG_ROW_LENGTH='+editAvgRowLen.Text + CRLF;
|
|
if editMaxRows.Text <> '' then
|
|
Result := Result + 'MAX_ROWS='+editMaxRows.Text + CRLF;
|
|
if memoUnionTables.Enabled and (memoUnionTables.Text <> '') then
|
|
Result := Result + 'UNION=('+memoUnionTables.Text+')' + CRLF;
|
|
if comboInsertMethod.Enabled and (comboInsertMethod.Text <> '') then
|
|
Result := Result + 'INSERT_METHOD='+comboInsertMethod.Text + CRLF;
|
|
Result := Trim(Result);
|
|
end;
|
|
|
|
|
|
function TfrmTableEditor.GetIndexSQL(IndexList: TWideStringlist; idx: Integer): WideString;
|
|
var
|
|
IndexName, IndexType, ColName: WideString;
|
|
Parts: TWideStringlist;
|
|
i, p: Integer;
|
|
begin
|
|
Result := '';
|
|
Parts := TWideStringlist(IndexList.Objects[idx]);
|
|
// Supress SQL error trying index creation with 0 column
|
|
if Parts.Count = 0 then
|
|
Exit;
|
|
IndexName := Copy(IndexList[idx], 1, LastPos(REGDELIM, IndexList[idx])-1);
|
|
IndexType := Copy(IndexList[idx], LastPos(REGDELIM, IndexList[idx])+1, Length(IndexList[idx]));
|
|
if IndexType = PKEY then
|
|
Result := Result + 'PRIMARY KEY '
|
|
else begin
|
|
if IndexType <> KEY then
|
|
Result := Result + IndexType + ' ';
|
|
Result := Result + 'INDEX ' + Mainform.Mask(IndexName) + ' ';
|
|
end;
|
|
Result := Result + '(';
|
|
for i:=0 to Parts.Count - 1 do begin
|
|
ColName := Parts[i];
|
|
p := LastPos('(', ColName);
|
|
if p > 1 then
|
|
Result := Result + Mainform.Mask(Copy(ColName, 1, p-1)) + Copy(ColName, p, Length(ColName)-p+1)
|
|
else
|
|
Result := Result + Mainform.Mask(ColName);
|
|
Result := Result + ', ';
|
|
end;
|
|
if Parts.Count > 0 then
|
|
Delete(Result, Length(Result)-1, 2);
|
|
|
|
Result := Result + ')';
|
|
end;
|
|
|
|
|
|
procedure TfrmTableEditor.editNameChange(Sender: TObject);
|
|
begin
|
|
// Name edited
|
|
editName.Font.Color := clWindowText;
|
|
editName.Color := clWindow;
|
|
try
|
|
ensureValidIdentifier( editName.Text );
|
|
except
|
|
// Invalid name
|
|
if editName.Text <> '' then begin
|
|
editName.Font.Color := clRed;
|
|
editName.Color := clYellow;
|
|
end;
|
|
end;
|
|
Modification(Sender);
|
|
end;
|
|
|
|
|
|
procedure TfrmTableEditor.Modification(Sender: TObject);
|
|
begin
|
|
// Memorize modified status
|
|
if FLoaded then begin
|
|
if Sender is TComponent then
|
|
TComponent(Sender).Tag := ModifiedFlag;
|
|
Modified := True;
|
|
end;
|
|
end;
|
|
|
|
|
|
procedure TfrmTableEditor.btnAddColumnClick(Sender: TObject);
|
|
var
|
|
DefProperties, Properties: TWideStringList;
|
|
fn: PVirtualNode;
|
|
idx: Integer;
|
|
begin
|
|
// Add new column after selected one
|
|
fn := listColumns.FocusedNode;
|
|
Properties := TWideStringList.Create;
|
|
Properties.OnChange := ColumnsChange;
|
|
if Assigned(fn) then begin
|
|
idx := fn.Index+1;
|
|
// Copy properties from focused node
|
|
DefProperties := TWideStringlist(Columns.Objects[fn.Index]);
|
|
Properties.Assign(DefProperties);
|
|
end else begin
|
|
idx := Columns.Count;
|
|
Properties.CommaText := 'INT,10,'+BoolToStr(False)+','+BoolToStr(True)+','+IntToStr(Integer(cdtNull))+'NULL,,';
|
|
end;
|
|
Columns.InsertObject(idx, 'Column '+IntToStr(idx+1), Properties);
|
|
SelectNode(listColumns, idx);
|
|
listColumns.EditNode(listColumns.FocusedNode, 1);
|
|
end;
|
|
|
|
|
|
procedure TfrmTableEditor.btnRemoveColumnClick(Sender: TObject);
|
|
var
|
|
i, FocusIndex: Integer;
|
|
SelCols: TWideStringList;
|
|
begin
|
|
// Remove selected column(s)
|
|
SelCols := GetVTCaptions(listColumns, true, 1);
|
|
// Remember focused node index
|
|
if Assigned(listColumns.FocusedNode) then
|
|
FocusIndex := listColumns.FocusedNode.Index
|
|
else
|
|
FocusIndex := listColumns.GetFirstSelected.Index;
|
|
|
|
// Set empty value for name=val column name pairs
|
|
for i:=0 to ColumnNames.Count-1 do begin
|
|
if SelCols.IndexOf(ColumnNames.ValueFromIndex[i]) > -1 then begin
|
|
ColumnNames[i] := ColumnNames.Names[i] + ColumnNames.NameValueSeparator;
|
|
end;
|
|
end;
|
|
for i:=0 to SelCols.Count-1 do begin
|
|
Columns.Delete(Columns.IndexOf(SelCols[i]));
|
|
end;
|
|
if Columns.Count > 0 then
|
|
SelectNode(listColumns, Min(FocusIndex, Columns.Count-1));
|
|
ValidateColumnControls;
|
|
end;
|
|
|
|
|
|
procedure TfrmTableEditor.btnMoveUpColumnClick(Sender: TObject);
|
|
begin
|
|
// Move column up
|
|
MoveFocusedColumn(listColumns.FocusedNode.Index-1);
|
|
end;
|
|
|
|
|
|
procedure TfrmTableEditor.btnMoveDownColumnClick(Sender: TObject);
|
|
begin
|
|
// Move column down
|
|
MoveFocusedColumn(listColumns.FocusedNode.Index+1);
|
|
end;
|
|
|
|
|
|
procedure TfrmTableEditor.listColumnsDragOver(Sender: TBaseVirtualTree;
|
|
Source: TObject; Shift: TShiftState; State: TDragState; Pt: TPoint;
|
|
Mode: TDropMode; var Effect: Integer; var Accept: Boolean);
|
|
begin
|
|
Accept := (Source = Sender) and (Mode <> dmNowhere);
|
|
// Not sure what this effect does, probably show a specific mouse cursor?
|
|
Effect := DROPEFFECT_MOVE;
|
|
end;
|
|
|
|
|
|
procedure TfrmTableEditor.listColumnsDragDrop(Sender: TBaseVirtualTree;
|
|
Source: TObject; DataObject: IDataObject; Formats: TFormatArray;
|
|
Shift: TShiftState; Pt: TPoint; var Effect: Integer; Mode: TDropMode);
|
|
var
|
|
Node: PVirtualNode;
|
|
begin
|
|
Node := Sender.GetNodeAt(Pt.X, Pt.Y);
|
|
if Assigned(Node) then
|
|
MoveFocusedColumn(Node.Index);
|
|
end;
|
|
|
|
|
|
procedure TfrmTableEditor.listColumnsBeforeCellPaint(Sender: TBaseVirtualTree;
|
|
TargetCanvas: TCanvas; Node: PVirtualNode; Column: TColumnIndex;
|
|
CellPaintMode: TVTCellPaintMode; CellRect: TRect; var ContentRect: TRect);
|
|
begin
|
|
// Darken cell background to signalize it doesn't allow length/set
|
|
// Exclude non editable checkbox columns - grey looks ugly there.
|
|
if (not CellEditingAllowed(Node, Column)) and (not (Column in [4, 5])) then begin
|
|
TargetCanvas.Brush.Color := clBtnFace;
|
|
TargetCanvas.FillRect(CellRect);
|
|
end;
|
|
end;
|
|
|
|
|
|
procedure TfrmTableEditor.listColumnsAfterCellPaint(Sender: TBaseVirtualTree;
|
|
TargetCanvas: TCanvas; Node: PVirtualNode; Column: TColumnIndex;
|
|
CellRect: TRect);
|
|
var
|
|
Props: TWideStringlist;
|
|
ImageIndex, X, Y: Integer;
|
|
VT: TVirtualStringTree;
|
|
begin
|
|
// Paint checkbox image in certain columns
|
|
// while restricting "Allow NULL" checkbox to numeric datatypes
|
|
if (Column in [4, 5]) and CellEditingAllowed(Node, Column) then begin
|
|
Props := TWideStringlist(Columns.Objects[Node.Index]);
|
|
if StrToBool(Props[Column-2]) then ImageIndex := 128
|
|
else ImageIndex := 127;
|
|
VT := TVirtualStringTree(Sender);
|
|
X := CellRect.Left + (VT.Header.Columns[Column].Width div 2) - (VT.Images.Width div 2);
|
|
Y := CellRect.Top + Integer(VT.NodeHeight[Node] div 2) - (VT.Images.Height div 2);
|
|
VT.Images.Draw(TargetCanvas, X, Y, ImageIndex);
|
|
end;
|
|
end;
|
|
|
|
|
|
procedure TfrmTableEditor.listColumnsBeforePaint(Sender: TBaseVirtualTree;
|
|
TargetCanvas: TCanvas);
|
|
var
|
|
VT: TVirtualStringTree;
|
|
OldNodeCount: Cardinal;
|
|
begin
|
|
// (Re)paint column list. Adjust number of nodes and autofit both counter and name column.
|
|
VT := Sender as TVirtualStringTree;
|
|
OldNodeCount := VT.RootNodeCount;
|
|
VT.RootNodeCount := Columns.Count;
|
|
if OldNodeCount <> VT.RootNodeCount then
|
|
VT.Header.AutoFitColumns(False, smaUseColumnOption, 0, 1);
|
|
end;
|
|
|
|
|
|
procedure TfrmTableEditor.listColumnsFocusChanged(Sender: TBaseVirtualTree;
|
|
Node: PVirtualNode; Column: TColumnIndex);
|
|
begin
|
|
// Column focus changed
|
|
ValidateColumnControls;
|
|
end;
|
|
|
|
|
|
procedure TfrmTableEditor.ValidateColumnControls;
|
|
var Node: PVirtualNode;
|
|
begin
|
|
Node := listColumns.FocusedNode;
|
|
btnRemoveColumn.Enabled := listColumns.SelectedCount > 0;
|
|
btnMoveUpColumn.Enabled := Assigned(Node) and (Node <> listColumns.GetFirst);
|
|
btnMoveDownColumn.Enabled := Assigned(Node) and (Node <> listColumns.GetLast);
|
|
|
|
menuRemoveColumn.Enabled := btnRemoveColumn.Enabled;
|
|
menuMoveUpColumn.Enabled := btnMoveUpColumn.Enabled;
|
|
menuMoveDownColumn.Enabled := btnMoveDownColumn.Enabled;
|
|
end;
|
|
|
|
|
|
procedure TfrmTableEditor.listColumnsEditing(Sender: TBaseVirtualTree;
|
|
Node: PVirtualNode; Column: TColumnIndex; var Allowed: Boolean);
|
|
begin
|
|
// Allow text editing? Explicitely block that in checkbox columns
|
|
Allowed := CellEditingAllowed(Node, Column) and (not (Column in [4,5]));
|
|
end;
|
|
|
|
|
|
function TfrmTableEditor.CellEditingAllowed(Node: PVirtualNode; Column: TColumnIndex): Boolean;
|
|
var
|
|
Props: TWideStringlist;
|
|
dt: TDatatype;
|
|
begin
|
|
Props := TWideStringlist(Columns.Objects[Node.Index]);
|
|
dt := GetDatatypeByName(Props[0]);
|
|
case Column of
|
|
// No editor for very first column and checkbox columns
|
|
0: Result := False;
|
|
3: Result := dt.HasLength;
|
|
4: Result := dt.HasUnsigned;
|
|
// No editing of collation allowed if "Convert data" was checked
|
|
8: Result := not chkCharsetConvert.Checked;
|
|
else Result := True;
|
|
end;
|
|
end;
|
|
|
|
|
|
procedure TfrmTableEditor.listColumnsGetText(Sender: TBaseVirtualTree;
|
|
Node: PVirtualNode; Column: TColumnIndex; TextType: TVSTTextType;
|
|
var CellText: WideString);
|
|
var
|
|
Properties: TWideStringList;
|
|
begin
|
|
// Display column text
|
|
case Column of
|
|
0: CellText := IntToStr(Node.Index+1);
|
|
1: CellText := Columns[Node.Index];
|
|
4, 5: CellText := ''; // Checkbox
|
|
else begin
|
|
Properties := TWideStringList(Columns.Objects[Node.Index]);
|
|
CellText := Properties[Column-2];
|
|
if (Column = 8) and (CellText <> '') and (chkCharsetConvert.Checked) then
|
|
CellText := comboCollation.Text;
|
|
end;
|
|
end;
|
|
if Column = 6 then
|
|
CellText := Copy(CellText, 2, Length(CellText)-1);
|
|
end;
|
|
|
|
|
|
procedure TfrmTableEditor.listColumnsGetImageIndex(Sender: TBaseVirtualTree;
|
|
Node: PVirtualNode; Kind: TVTImageKind; Column: TColumnIndex;
|
|
var Ghosted: Boolean; var ImageIndex: Integer);
|
|
var
|
|
IndexName, IndexType: WideString;
|
|
Props: TWideStringlist;
|
|
i: Integer;
|
|
begin
|
|
// Primary key icon
|
|
if Column <> 0 then Exit;
|
|
|
|
for i:=0 to Indexes.Count-1 do begin
|
|
GetIndexInfo(i, IndexName, IndexType);
|
|
Props := TWideStringlist(Indexes.Objects[i]);
|
|
if Props.IndexOf(Columns[Node.Index]) > -1 then begin
|
|
if IndexType = PKEY then ImageIndex := ICONINDEX_PRIMARYKEY
|
|
else if IndexType = UKEY then ImageIndex := ICONINDEX_UNIQUEKEY
|
|
else if IndexType = KEY then ImageIndex := ICONINDEX_INDEXKEY
|
|
else if IndexType = FKEY then ImageIndex := ICONINDEX_FULLTEXTKEY
|
|
else if IndexType = SKEY then ImageIndex := ICONINDEX_SPATIALKEY;
|
|
end;
|
|
// Priority for PK columns. We cannot display more than one icon anyway.
|
|
if ImageIndex > -1 then
|
|
break;
|
|
end;
|
|
end;
|
|
|
|
|
|
procedure TfrmTableEditor.listColumnsPaintText(Sender: TBaseVirtualTree;
|
|
const TargetCanvas: TCanvas; Node: PVirtualNode; Column: TColumnIndex;
|
|
TextType: TVSTTextType);
|
|
var
|
|
Default: WideString;
|
|
TextColor: TColor;
|
|
dt: TDatatype;
|
|
Props: TWideStringlist;
|
|
i: Integer;
|
|
IndexName, IndexType: WideString;
|
|
begin
|
|
// Bold font for primary key columns
|
|
for i:=0 to Indexes.Count-1 do begin
|
|
GetIndexInfo(i, IndexName, IndexType);
|
|
if IndexType = PKEY then begin
|
|
if TWideStringlist(Indexes.Objects[i]).IndexOf(Columns[Node.Index]) > -1 then
|
|
TargetCanvas.Font.Style := TargetCanvas.Font.Style + [fsBold];
|
|
break;
|
|
end;
|
|
end;
|
|
|
|
// No specific colors for selected nodes, would interfere with blue selection background
|
|
if vsSelected in Node.States then Exit;
|
|
// Break early if nothing to do
|
|
if not (Column in [2, 6]) then Exit;
|
|
|
|
// Give datatype column specific color, as set in preferences
|
|
Props := TWideStringlist(Columns.Objects[Node.Index]);
|
|
dt := GetDatatypeByName(Props[0]);
|
|
Default := Props[4];
|
|
TextColor := TargetCanvas.Font.Color;
|
|
case Column of
|
|
2: TextColor := DatatypeCategories[Integer(dt.Category)].Color;
|
|
|
|
6: case GetColumnDefaultType(Default) of
|
|
cdtNull, cdtNullUpdateTS:
|
|
TextColor := clGray;
|
|
cdtCurTS, cdtCurTSUpdateTS:
|
|
TextColor := DatatypeCategories[Integer(dtcTemporal)].Color;
|
|
cdtAutoInc:
|
|
TextColor := clNavy;
|
|
end;
|
|
end;
|
|
TargetCanvas.Font.Color := TextColor;
|
|
end;
|
|
|
|
|
|
procedure TfrmTableEditor.listColumnsNewText(Sender: TBaseVirtualTree;
|
|
Node: PVirtualNode; Column: TColumnIndex; NewText: WideString);
|
|
var
|
|
Properties: TWideStringList;
|
|
idx, i: Integer;
|
|
dt: TDatatype;
|
|
begin
|
|
// Column property edited
|
|
if Column = 1 then begin
|
|
idx := Columns.IndexOf(NewText);
|
|
if (idx > -1) and (idx <> Integer(Node.Index)) then begin
|
|
MessageDlg('Column "'+NewText+'" already exists.', mtError, [mbOk], 0);
|
|
Sender.EditNode(Node, Column);
|
|
Exit;
|
|
end;
|
|
|
|
for i:=0 to ColumnNames.Count -1 do begin
|
|
if ColumnNames.ValueFromIndex[i] = Columns[Node.Index] then begin
|
|
ColumnNames.ValueFromIndex[i] := NewText;
|
|
break;
|
|
end;
|
|
end;
|
|
|
|
ColumnsChanges.Add(NewText);
|
|
Columns[Node.Index] := NewText;
|
|
end else if Column > 1 then begin
|
|
ColumnsChanges.Add(Columns[Node.Index]);
|
|
Properties := TWideStringList(Columns.Objects[Node.Index]);
|
|
Properties[Column-2] := NewText;
|
|
if Column = 2 then begin
|
|
dt := GetDatatypeByName(NewText);
|
|
// Reset length/set for column types which don't support that
|
|
if not dt.HasLength then
|
|
Properties[1] := '';
|
|
// Suggest length/set if required
|
|
if dt.RequiresLength and (Properties[1] = '') then
|
|
Properties[1] := dt.DefLengthSet;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
|
|
procedure TfrmTableEditor.listColumnsClick(Sender: TObject);
|
|
var
|
|
VT: TVirtualStringTree;
|
|
Props: TWideStringlist;
|
|
Click: THitInfo;
|
|
Default: WideString;
|
|
begin
|
|
// Handle click event
|
|
VT := Sender as TVirtualStringTree;
|
|
VT.GetHitTestInfoAt(Mouse.CursorPos.X-VT.ClientOrigin.X, Mouse.CursorPos.Y-VT.ClientOrigin.Y, True, Click);
|
|
if not Assigned(Click.HitNode) then
|
|
Exit;
|
|
if Click.HitColumn in [4, 5] then begin
|
|
// For checkboxes, cell editors are disabled, instead toggle their state
|
|
Props := TWideStringList(Columns.Objects[Click.HitNode.Index]);
|
|
if CellEditingAllowed(Click.HitNode, Click.HitColumn) then begin
|
|
ColumnsChanges.Add(Columns[Click.HitNode.Index]);
|
|
Props[Click.HitColumn-2] := BoolToStr(not StrToBool(Props[Click.HitColumn-2]));
|
|
// Switch default value from NULL to Text if Allow Null is off
|
|
if (Click.HitColumn = 5) and (not StrToBool(Props[Click.HitColumn-2])) then begin
|
|
Default := Props[4];
|
|
if GetColumnDefaultType(Default) in [cdtNull, cdtNullUpdateTS] then
|
|
Props[4] := IntToStr(Integer(cdtText));
|
|
end;
|
|
VT.InvalidateNode(Click.HitNode);
|
|
end;
|
|
end else begin
|
|
// All other cells go into edit mode please
|
|
// Explicitely done on OnClick, not in OnFocusChanged which seemed annoying for keyboard users
|
|
if Assigned(Click.HitNode) and (Click.HitColumn > NoColumn) then
|
|
VT.EditNode(Click.HitNode, Click.HitColumn);
|
|
end;
|
|
end;
|
|
|
|
|
|
procedure TfrmTableEditor.listColumnsCreateEditor(Sender: TBaseVirtualTree;
|
|
Node: PVirtualNode; Column: TColumnIndex; out EditLink: IVTEditLink);
|
|
var
|
|
VT: TVirtualStringTree;
|
|
EnumEditor: TEnumEditorLink;
|
|
DefaultEditor: TColumnDefaultEditorLink;
|
|
DatatypeEditor: TDatatypeEditorLink;
|
|
Props: TWideStringlist;
|
|
begin
|
|
// Start cell editor
|
|
VT := Sender as TVirtualStringTree;
|
|
case Column of
|
|
2: begin // Datatype pulldown
|
|
DatatypeEditor := TDatatypeEditorLink.Create(VT);
|
|
DatatypeEditor.Datatype := dtDateTime;
|
|
EditLink := DataTypeEditor;
|
|
end;
|
|
8: begin // Collation pulldown
|
|
EnumEditor := TEnumEditorLink.Create(VT);
|
|
EnumEditor.ValueList := TWideStringList.Create;
|
|
Mainform.GetCollations(EnumEditor.ValueList);
|
|
EditLink := EnumEditor;
|
|
end;
|
|
6: begin
|
|
DefaultEditor := TColumnDefaultEditorLink.Create(VT);
|
|
Props := TWideStringlist(Columns.Objects[Node.Index]);
|
|
DefaultEditor.DefaultText := Props[Column-2];
|
|
DefaultEditor.DefaultType := GetColumnDefaultType(DefaultEditor.DefaultText);
|
|
EditLink := DefaultEditor;
|
|
end
|
|
else
|
|
EditLink := TInplaceEditorLink.Create(VT);
|
|
end;
|
|
end;
|
|
|
|
|
|
procedure TfrmTableEditor.SetModified(Value: Boolean);
|
|
begin
|
|
// Some value has changed
|
|
FModified := Value;
|
|
btnSave.Enabled := FModified;
|
|
btnDiscard.Enabled := FModified;
|
|
CreateCodeValid := False;
|
|
AlterCodeValid := False;
|
|
UpdateSQLcode;
|
|
end;
|
|
|
|
|
|
procedure TfrmTableEditor.ColumnsChange(Sender: TObject);
|
|
begin
|
|
Modification(Sender);
|
|
listColumns.Repaint;
|
|
end;
|
|
|
|
|
|
procedure TfrmTableEditor.MoveFocusedColumn(NewIdx: Cardinal);
|
|
begin
|
|
if listColumns.IsEditing then
|
|
listColumns.EndEditNode;
|
|
listColumns.FocusedColumn := listColumns.Header.MainColumn;
|
|
ColumnsChanges.Add(Columns[listColumns.FocusedNode.Index]);
|
|
Columns.Move(listColumns.FocusedNode.Index, NewIdx);
|
|
SelectNode(listColumns, NewIdx);
|
|
end;
|
|
|
|
|
|
procedure TfrmTableEditor.editNumEditChange(Sender: TObject);
|
|
var
|
|
ed: TEdit;
|
|
ShouldBe: String;
|
|
CursorPos: Integer;
|
|
begin
|
|
// Only numbers allowed in this TEdit
|
|
Modification(Sender);
|
|
ed := Sender as TEdit;
|
|
ShouldBe := IntToStr(Makeint(ed.Text));
|
|
if (ed.Text = ShouldBe) or (ed.Text = '') then Exit;
|
|
MessageBeep(MB_ICONEXCLAMATION);
|
|
CursorPos := ed.SelStart;
|
|
ed.OnChange := nil;
|
|
ed.Text := ShouldBe;
|
|
ed.SelStart := CursorPos;
|
|
ed.OnChange := editNumEditChange;
|
|
end;
|
|
|
|
|
|
procedure TfrmTableEditor.comboEngineSelect(Sender: TObject);
|
|
var
|
|
IsMerge: Boolean;
|
|
begin
|
|
// Enable/disable engine specific option controls
|
|
IsMerge := (Sender as TComboBox).Text = 'MRG_MYISAM';
|
|
memoUnionTables.Enabled := IsMerge;
|
|
comboInsertMethod.Enabled := IsMerge;
|
|
Modification(Sender);
|
|
end;
|
|
|
|
|
|
procedure TfrmTableEditor.IndexesChange(Sender: TObject);
|
|
begin
|
|
Modification(Sender);
|
|
treeIndexes.Invalidate;
|
|
end;
|
|
|
|
|
|
procedure TfrmTableEditor.btnAddIndexClick(Sender: TObject);
|
|
var
|
|
IndexParts: TWideStringlist;
|
|
Name: WideString;
|
|
begin
|
|
// Add new index
|
|
IndexParts := TWideStringlist.Create;
|
|
IndexParts.OnChange := IndexesChange;
|
|
Name := 'Index '+IntToStr(Indexes.Count+1);
|
|
Indexes.AddObject(Name+REGDELIM+KEY, IndexParts);
|
|
SelectNode(treeIndexes, Indexes.Count-1);
|
|
end;
|
|
|
|
|
|
procedure TfrmTableEditor.menuAddIndexColumnClick(Sender: TObject);
|
|
var
|
|
IndexParts: TWideStringlist;
|
|
Node: PVirtualNode;
|
|
i, j, p: Integer;
|
|
Col, NewCol: WideString;
|
|
ColExists: Boolean;
|
|
begin
|
|
// Add column to index
|
|
Node := treeIndexes.FocusedNode;
|
|
if not Assigned(Node) then
|
|
Exit;
|
|
if treeIndexes.GetNodeLevel(Node) = 1 then
|
|
Node := Node.Parent;
|
|
IndexParts := TWideStringlist(Indexes.Objects[Node.Index]);
|
|
ColExists := False;
|
|
NewCol := '';
|
|
for i:=0 to Columns.Count - 1 do begin
|
|
for j:=0 to IndexParts.Count - 1 do begin
|
|
Col := IndexParts[j];
|
|
p := LastPos('(', Col);
|
|
if p > 0 then
|
|
Col := Copy(Col, 0, p-1);
|
|
ColExists := Col = Columns[i];
|
|
if ColExists then
|
|
break;
|
|
end;
|
|
if not ColExists then begin
|
|
NewCol := Columns[i];
|
|
break;
|
|
end;
|
|
end;
|
|
treeIndexes.AddChild(Node);
|
|
IndexParts.Add(NewCol);
|
|
SelectNode(treeIndexes, IndexParts.Count-1, Node);
|
|
end;
|
|
|
|
|
|
procedure TfrmTableEditor.btnRemoveIndexClick(Sender: TObject);
|
|
var
|
|
IndexParts: TWideStringlist;
|
|
idx: Integer;
|
|
NewSelectNode: PVirtualNode;
|
|
begin
|
|
// Remove index or part
|
|
case treeIndexes.GetNodeLevel(treeIndexes.FocusedNode) of
|
|
0: begin
|
|
idx := treeIndexes.FocusedNode.Index;
|
|
Indexes.Objects[idx].Free;
|
|
Indexes.Delete(idx);
|
|
end;
|
|
1: begin
|
|
idx := treeIndexes.FocusedNode.Parent.Index;
|
|
IndexParts := TWideStringlist(Indexes.Objects[idx]);
|
|
IndexParts.Delete(treeIndexes.FocusedNode.Index);
|
|
treeIndexes.DeleteNode(treeIndexes.FocusedNode);
|
|
end;
|
|
end;
|
|
NewSelectNode := treeIndexes.GetPreviousVisible(treeIndexes.FocusedNode);
|
|
if Assigned(NewSelectNode) then
|
|
SelectNode(treeIndexes, NewSelectNode.Index, NewSelectNode.Parent);
|
|
end;
|
|
|
|
|
|
procedure TfrmTableEditor.btnClearIndexesClick(Sender: TObject);
|
|
var i: Integer;
|
|
begin
|
|
// Clear all indexes
|
|
// Column data gets freed below - end any editor which could cause AV's
|
|
if treeIndexes.IsEditing then
|
|
treeIndexes.CancelEditNode;
|
|
for i := 0 to Indexes.Count - 1 do
|
|
Indexes.Objects[i].Free;
|
|
Indexes.Clear;
|
|
treeIndexes.Clear;
|
|
end;
|
|
|
|
|
|
procedure TfrmTableEditor.treeIndexesGetImageIndex(Sender: TBaseVirtualTree;
|
|
Node: PVirtualNode; Kind: TVTImageKind; Column: TColumnIndex;
|
|
var Ghosted: Boolean; var ImageIndex: Integer);
|
|
var
|
|
IndexType: String;
|
|
VT: TVirtualStringTree;
|
|
begin
|
|
// Icon image showing type of index
|
|
VT := Sender as TVirtualStringTree;
|
|
if Column <> 0 then
|
|
Exit;
|
|
case VT.GetNodeLevel(Node) of
|
|
0: begin
|
|
IndexType := VT.Text[Node, 1];
|
|
ImageIndex := GetIndexIcon(Node.Index);
|
|
end;
|
|
1: ImageIndex := 42;
|
|
end;
|
|
end;
|
|
|
|
|
|
procedure TfrmTableEditor.treeIndexesGetText(Sender: TBaseVirtualTree;
|
|
Node: PVirtualNode; Column: TColumnIndex; TextType: TVSTTextType;
|
|
var CellText: WideString);
|
|
var
|
|
IndexProps: TWideStringlist;
|
|
IndexName, IndexType: WideString;
|
|
begin
|
|
// Index tree showing cell text
|
|
case Sender.GetNodeLevel(Node) of
|
|
0: begin
|
|
GetIndexInfo(Node.Index, IndexName, IndexType);
|
|
case Column of
|
|
0: if IndexType = PKEY then
|
|
CellText := IndexType + ' KEY' // Fixed name "PRIMARY KEY", cannot be changed
|
|
else
|
|
CellText := IndexName;
|
|
1: CellText := IndexType;
|
|
end;
|
|
end;
|
|
1: case Column of
|
|
0: begin
|
|
IndexProps := TWideStringlist(Indexes.Objects[Node.Parent.Index]);
|
|
CellText := IndexProps[Node.Index];
|
|
end;
|
|
else CellText := '';
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
|
|
procedure TfrmTableEditor.treeIndexesInitChildren(Sender: TBaseVirtualTree;
|
|
Node: PVirtualNode; var ChildCount: Cardinal);
|
|
var
|
|
IndexParts: TWideStringlist;
|
|
begin
|
|
// Tell number of columns contained in index
|
|
IndexParts := TWideStringlist(Indexes.Objects[Node.Index]);
|
|
ChildCount := IndexParts.Count;
|
|
end;
|
|
|
|
|
|
procedure TfrmTableEditor.treeIndexesInitNode(Sender: TBaseVirtualTree;
|
|
ParentNode, Node: PVirtualNode; var InitialStates: TVirtualNodeInitStates);
|
|
begin
|
|
// Show plus sign on first level nodes
|
|
if Sender.GetNodeLevel(Node) = 0 then
|
|
Include( InitialStates, ivsHasChildren);
|
|
end;
|
|
|
|
|
|
procedure TfrmTableEditor.treeIndexesFocusChanged(Sender: TBaseVirtualTree;
|
|
Node: PVirtualNode; Column: TColumnIndex);
|
|
begin
|
|
ValidateIndexControls;
|
|
end;
|
|
|
|
|
|
procedure TfrmTableEditor.treeIndexesClick(Sender: TObject);
|
|
var
|
|
VT: TVirtualStringTree;
|
|
Click: THitInfo;
|
|
begin
|
|
// Handle click event
|
|
VT := Sender as TVirtualStringTree;
|
|
VT.GetHitTestInfoAt(Mouse.CursorPos.X-VT.ClientOrigin.X, Mouse.CursorPos.Y-VT.ClientOrigin.Y, True, Click);
|
|
if Assigned(Click.HitNode) then
|
|
VT.EditNode(Click.HitNode, Click.HitColumn);
|
|
end;
|
|
|
|
|
|
procedure TfrmTableEditor.ValidateIndexControls;
|
|
var
|
|
Node: PVirtualNode;
|
|
HasNode: Boolean;
|
|
Level: Integer;
|
|
begin
|
|
// Enable/disable buttons
|
|
Node := treeIndexes.FocusedNode;
|
|
HasNode := Assigned(Node);
|
|
if HasNode then Level := treeIndexes.GetNodeLevel(Node)
|
|
else Level := -1;
|
|
|
|
btnRemoveIndex.Enabled := HasNode;
|
|
btnClearIndexes.Enabled := Indexes.Count > 0;
|
|
btnMoveUpIndex.Enabled := HasNode and (Level = 1) and (Node <> treeIndexes.GetFirstChild(Node.Parent));
|
|
btnMoveDownIndex.Enabled := HasNode and (Level = 1) and (Node <> treeIndexes.GetLastChild(Node.Parent));
|
|
|
|
menuAddIndexColumn.Enabled := HasNode;
|
|
menuRemoveIndex.Enabled := btnRemoveIndex.Enabled;
|
|
menuClearIndexes.Enabled := btnClearIndexes.Enabled;
|
|
menuMoveUpIndex.Enabled := btnMoveUpIndex.Enabled;
|
|
menuMoveDownIndex.Enabled := btnMoveDownIndex.Enabled;
|
|
end;
|
|
|
|
|
|
procedure TfrmTableEditor.treeIndexesEditing(Sender: TBaseVirtualTree;
|
|
Node: PVirtualNode; Column: TColumnIndex; var Allowed: Boolean);
|
|
var
|
|
VT: TVirtualStringtree;
|
|
begin
|
|
// Disallow renaming primary key
|
|
VT := Sender as TVirtualStringtree;
|
|
if (VT.GetNodeLevel(Node) = 0) and (Column = VT.Header.MainColumn) and (VT.Text[Node, Column+1] = PKEY) then
|
|
Allowed := False
|
|
else if (VT.GetNodeLevel(Node) = 1) and (Column = 1) then
|
|
Allowed := False
|
|
else
|
|
Allowed := True;
|
|
end;
|
|
|
|
|
|
procedure TfrmTableEditor.treeIndexesCreateEditor(Sender: TBaseVirtualTree;
|
|
Node: PVirtualNode; Column: TColumnIndex; out EditLink: IVTEditLink);
|
|
var
|
|
VT: TVirtualStringTree;
|
|
EnumEditor: TEnumEditorLink;
|
|
Level: Cardinal;
|
|
begin
|
|
// Start cell editor
|
|
VT := Sender as TVirtualStringTree;
|
|
Level := (Sender as TVirtualStringtree).GetNodeLevel(Node);
|
|
if (Level = 0) and (Column = 1) then begin
|
|
// Index type pulldown
|
|
EnumEditor := TEnumEditorLink.Create(VT);
|
|
EnumEditor.ValueList := TWideStringList.Create;
|
|
EnumEditor.ValueList.CommaText := PKEY +','+ KEY +','+ UKEY +','+ FKEY +','+ SKEY;
|
|
EditLink := EnumEditor;
|
|
end else if (Level = 1) and (Column = 0) then begin
|
|
// Column names pulldown
|
|
EnumEditor := TEnumEditorLink.Create(VT);
|
|
EnumEditor.ValueList := Columns;
|
|
EnumEditor.AllowCustomText := True; // Allows adding a subpart in index parts: "TextCol(20)"
|
|
EditLink := EnumEditor;
|
|
end else
|
|
EditLink := TInplaceEditorLink.Create(VT);
|
|
end;
|
|
|
|
|
|
procedure TfrmTableEditor.treeIndexesNewText(Sender: TBaseVirtualTree;
|
|
Node: PVirtualNode; Column: TColumnIndex; NewText: WideString);
|
|
var
|
|
VT: TVirtualStringtree;
|
|
IndexParts: TWideStringlist;
|
|
begin
|
|
// Rename index of column
|
|
VT := Sender as TVirtualStringtree;
|
|
case VT.GetNodeLevel(Node) of
|
|
0: case Column of
|
|
0: Indexes[Node.Index] := NewText + REGDELIM + VT.Text[Node, Column+1];
|
|
1: Indexes[Node.Index] := VT.Text[Node, Column-1] + REGDELIM + NewText;
|
|
end;
|
|
1: begin
|
|
IndexParts := TWideStringlist(Indexes.Objects[Node.Parent.Index]);
|
|
IndexParts[Node.Index] := NewText;
|
|
end;
|
|
end;
|
|
VT.RepaintNode(Node);
|
|
end;
|
|
|
|
|
|
procedure TfrmTableEditor.treeIndexesBeforePaint(Sender: TBaseVirtualTree;
|
|
TargetCanvas: TCanvas);
|
|
begin
|
|
// (Re)paint index list
|
|
(Sender as TVirtualStringTree).RootNodeCount := Indexes.Count;
|
|
end;
|
|
|
|
|
|
procedure TfrmTableEditor.treeIndexesDragOver(Sender: TBaseVirtualTree;
|
|
Source: TObject; Shift: TShiftState; State: TDragState; Pt: TPoint;
|
|
Mode: TDropMode; var Effect: Integer; var Accept: Boolean);
|
|
var
|
|
Node: PVirtualNode;
|
|
begin
|
|
// Accept nodes from the column list and allow column moving
|
|
if Source = listColumns then begin
|
|
Accept := True;
|
|
Exit;
|
|
end else if Source = Sender then begin
|
|
Node := Sender.GetNodeAt(Pt.X, Pt.Y);
|
|
Accept := Assigned(Node) and (Sender.GetNodeLevel(Node) = 1) and
|
|
(Node <> Sender.FocusedNode) and (Node.Parent = Sender.FocusedNode.Parent);
|
|
end;
|
|
end;
|
|
|
|
|
|
procedure TfrmTableEditor.treeIndexesDragDrop(Sender: TBaseVirtualTree;
|
|
Source: TObject; DataObject: IDataObject; Formats: TFormatArray;
|
|
Shift: TShiftState; Pt: TPoint; var Effect: Integer; Mode: TDropMode);
|
|
var
|
|
Node: PVirtualNode;
|
|
IndexParts: TWideStringlist;
|
|
ColName: WideString;
|
|
ColPos: Integer;
|
|
VT: TVirtualStringtree;
|
|
begin
|
|
// Column node dropped here
|
|
VT := Sender as TVirtualStringtree;
|
|
Node := VT.GetNodeAt(Pt.X, Pt.Y);
|
|
if not Assigned(Node) then begin
|
|
MessageBeep(MB_ICONEXCLAMATION);
|
|
Exit;
|
|
end;
|
|
if VT.GetNodeLevel(Node) = 1 then begin
|
|
ColPos := Node.Index;
|
|
if Mode = dmBelow then Inc(ColPos);
|
|
Node := Node.Parent;
|
|
end else
|
|
ColPos := MaxInt;
|
|
|
|
if Source = Sender then
|
|
MoveFocusedIndexPart(ColPos)
|
|
else begin
|
|
IndexParts := TWideStringlist(Indexes.Objects[Node.Index]);
|
|
ColName := Columns[(Source as TVirtualStringTree).FocusedNode.Index];
|
|
if IndexParts.IndexOf(ColName) > -1 then begin
|
|
if MessageDlg('Index "'+VT.Text[Node, 0]+'" already contains the column "'+ColName+'". It is possible to add a column twice into a index, but total nonsense in practice.'+CRLF+CRLF+'Add anyway?',
|
|
mtConfirmation, [mbYes, mbNo], 0) = mrNo then
|
|
Exit;
|
|
end;
|
|
|
|
if ColPos >= IndexParts.Count then
|
|
IndexParts.Add(ColName)
|
|
else
|
|
IndexParts.Insert(ColPos, ColName);
|
|
Node.States := Node.States + [vsHasChildren, vsExpanded];
|
|
end;
|
|
// Finally tell parent node to update its children
|
|
VT.ReinitChildren(Node, False);
|
|
end;
|
|
|
|
|
|
procedure TfrmTableEditor.btnMoveUpIndexClick(Sender: TObject);
|
|
begin
|
|
// Move index part up
|
|
MoveFocusedIndexPart(treeIndexes.FocusedNode.Index-1);
|
|
end;
|
|
|
|
|
|
procedure TfrmTableEditor.btnMoveDownIndexClick(Sender: TObject);
|
|
begin
|
|
// Move index part down
|
|
MoveFocusedIndexPart(treeIndexes.FocusedNode.Index+1);
|
|
end;
|
|
|
|
|
|
procedure TfrmTableEditor.MoveFocusedIndexPart(NewIdx: Cardinal);
|
|
var
|
|
IndexParts: TWideStringlist;
|
|
begin
|
|
// Move focused index or index part
|
|
if treeIndexes.IsEditing then
|
|
treeIndexes.EndEditNode;
|
|
IndexParts := TWideStringlist(Indexes.Objects[treeIndexes.FocusedNode.Parent.Index]);
|
|
IndexParts.Move(treeIndexes.FocusedNode.Index, NewIdx);
|
|
SelectNode(treeIndexes, NewIdx, treeIndexes.FocusedNode.Parent);
|
|
end;
|
|
|
|
|
|
procedure TfrmTableEditor.PageControlMainChange(Sender: TObject);
|
|
begin
|
|
if treeIndexes.IsEditing then
|
|
treeIndexes.EndEditNode;
|
|
UpdateSQLcode;
|
|
end;
|
|
|
|
|
|
procedure TfrmTableEditor.UpdateSQLcode;
|
|
var
|
|
OldTopLine: Integer;
|
|
begin
|
|
if (PageControlMain.ActivePage = tabALTERCode) and (not AlterCodeValid) then begin
|
|
SynMemoALTERcode.BeginUpdate;
|
|
OldTopLine := SynMemoALTERcode.TopLine;
|
|
SynMemoALTERcode.Text := ComposeAlterStatement;
|
|
SynMemoALTERcode.TopLine := OldTopLine;
|
|
SynMemoALTERcode.EndUpdate;
|
|
AlterCodeValid := True;
|
|
end else if (PageControlMain.ActivePage = tabCREATECode) and (not CreateCodeValid) then begin
|
|
SynMemoCREATEcode.BeginUpdate;
|
|
OldTopLine := SynMemoCREATEcode.TopLine;
|
|
SynMemoCREATEcode.Text := ComposeCreateStatement;
|
|
SynMemoCREATEcode.TopLine := OldTopLine;
|
|
SynMemoCREATEcode.EndUpdate;
|
|
CreateCodeValid := True;
|
|
end;
|
|
end;
|
|
|
|
|
|
procedure TfrmTableEditor.chkCharsetConvertClick(Sender: TObject);
|
|
begin
|
|
chkCharsetConvert.Enabled := (FAlterTablename <> '') and (comboCollation.ItemIndex > -1);
|
|
listColumns.Repaint;
|
|
Modification(Sender);
|
|
end;
|
|
|
|
|
|
procedure TfrmTableEditor.popupColumnsPopup(Sender: TObject);
|
|
function AddItem(Parent: TMenuItem; Caption: String; ImageIndex: Integer): TMenuItem;
|
|
begin
|
|
Result := TMenuItem.Create(Parent.GetParentMenu);
|
|
Result.Caption := Caption;
|
|
Result.ImageIndex := ImageIndex;
|
|
Result.OnClick := AddIndexByColumn;
|
|
Parent.Add(Result);
|
|
end;
|
|
var
|
|
i: Integer;
|
|
IndexName, IndexType: WideString;
|
|
Item: TMenuItem;
|
|
PrimaryKeyExists,
|
|
ColumnsSelected: Boolean;
|
|
IndexParts: TWideStringList;
|
|
Node: PVirtualNode;
|
|
begin
|
|
ColumnsSelected := ListColumns.SelectedCount > 0;
|
|
menuAddToIndex.Clear;
|
|
menuCreateIndex.Clear;
|
|
menuAddToIndex.Enabled := ColumnsSelected;
|
|
menuCreateIndex.Enabled := ColumnsSelected;
|
|
if not ColumnsSelected then
|
|
Exit;
|
|
|
|
// Auto create submenu items for "Add to index" ...
|
|
PrimaryKeyExists := False;
|
|
for i:=0 to Indexes.Count-1 do begin
|
|
GetIndexInfo(i, IndexName, IndexType);
|
|
if IndexType = PKEY then
|
|
PrimaryKeyExists := True
|
|
else
|
|
IndexName := IndexName + ' ('+IndexType+')';
|
|
Item := AddItem(menuAddToIndex, IndexName, GetIndexIcon(i));
|
|
// Disable menuitem if all selected columns are already part of this index,
|
|
// enable it if one or more selected columns are not.
|
|
Item.Enabled := False;
|
|
IndexParts := TWideStringList(Indexes.Objects[i]);
|
|
Node := listColumns.GetFirstSelected;
|
|
while Assigned(Node) do begin
|
|
if IndexParts.IndexOf(Columns[Node.Index]) = -1 then begin
|
|
Item.Enabled := True;
|
|
Break;
|
|
end;
|
|
Node := listColumns.GetNextSelected(Node);
|
|
end;
|
|
end;
|
|
menuAddToIndex.Enabled := menuAddToIndex.Count > 0;
|
|
|
|
// ... and for item "Create index"
|
|
Item := AddItem(menuCreateIndex, PKEY, ICONINDEX_PRIMARYKEY);
|
|
Item.Enabled := not PrimaryKeyExists;
|
|
AddItem(menuCreateIndex, KEY, ICONINDEX_INDEXKEY);
|
|
AddItem(menuCreateIndex, UKEY, ICONINDEX_UNIQUEKEY);
|
|
AddItem(menuCreateIndex, FKEY, ICONINDEX_FULLTEXTKEY);
|
|
AddItem(menuCreateIndex, SKEY, ICONINDEX_SPATIALKEY);
|
|
end;
|
|
|
|
|
|
procedure TfrmTableEditor.AddIndexByColumn(Sender: TObject);
|
|
var
|
|
Item: TMenuItem;
|
|
i: Integer;
|
|
IndexName, IndexType,
|
|
NewName, NewType: WideString;
|
|
IndexParts, NewParts: TWideStringlist;
|
|
begin
|
|
// Auto create index or add columns to existing one by rightclicking a column
|
|
Item := (Sender as TMenuItem);
|
|
NewParts := GetVTCaptions(listColumns, True, 1);
|
|
if Item.Parent = menuCreateIndex then begin
|
|
NewName := 'Index '+IntToStr(Indexes.Count+1);
|
|
// Remove auto hotkeys
|
|
NewType := StringReplace(Item.Caption, '&', '', [rfReplaceAll]);
|
|
// Avoid creating a second key with the same columns
|
|
for i:=0 to Indexes.Count-1 do begin
|
|
IndexParts := TWideStringList(Indexes.Objects[i]);
|
|
GetIndexInfo(i, IndexName, IndexType);
|
|
if (IndexType = NewType) and (IndexParts.Text = NewParts.Text) then begin
|
|
if MessageDlg('Key already exists. Really create another identical one?'+CRLF+CRLF+
|
|
'This will increase disk usage and probably slow down queries on this table.',
|
|
mtConfirmation, [mbYes, mbNo], 0) = mrNo then
|
|
Exit;
|
|
break;
|
|
end;
|
|
end;
|
|
NewParts.OnChange := IndexesChange;
|
|
Indexes.AddObject(NewName+REGDELIM+NewType, NewParts);
|
|
PageControlMain.ActivePage := tabIndexes;
|
|
treeIndexes.Repaint;
|
|
SelectNode(treeIndexes, Indexes.Count-1);
|
|
SelectNode(treeIndexes, 0, treeIndexes.FocusedNode);
|
|
end else begin
|
|
PageControlMain.ActivePage := tabIndexes;
|
|
IndexParts := TWideStringlist(Indexes.Objects[Item.MenuIndex]);
|
|
for i:=0 to NewParts.Count-1 do begin
|
|
if IndexParts.IndexOf(NewParts[i]) = -1 then
|
|
IndexParts.Add(NewParts[i]);
|
|
end;
|
|
SelectNode(treeIndexes, Item.MenuIndex);
|
|
treeIndexes.ReinitChildren(treeIndexes.FocusedNode, False);
|
|
SelectNode(treeIndexes, IndexParts.Count-1, treeIndexes.FocusedNode);
|
|
end;
|
|
end;
|
|
|
|
|
|
function TfrmTableEditor.GetIndexIcon(idx: Integer): Integer;
|
|
var
|
|
IndexType, IndexName: WideString;
|
|
begin
|
|
// Detect key icon index for specified index
|
|
GetIndexInfo(idx, IndexName, IndexType);
|
|
if IndexType = PKEY then Result := ICONINDEX_PRIMARYKEY
|
|
else if IndexType = KEY then Result := ICONINDEX_INDEXKEY
|
|
else if IndexType = UKEY then Result := ICONINDEX_UNIQUEKEY
|
|
else if IndexType = FKEY then Result := ICONINDEX_FULLTEXTKEY
|
|
else if IndexType = SKEY then Result := ICONINDEX_SPATIALKEY
|
|
else Result := -1;
|
|
end;
|
|
|
|
|
|
procedure TfrmTableEditor.GetIndexInfo(idx: Integer; var IndexName, IndexType: WideString);
|
|
begin
|
|
IndexName := Copy(Indexes[idx], 1, LastPos(REGDELIM, Indexes[idx])-1);
|
|
IndexType := Copy(Indexes[idx], LastPos(REGDELIM, Indexes[idx])+1, Length(Indexes[idx]));
|
|
end;
|
|
|
|
|
|
procedure TfrmTableEditor.btnHelpClick(Sender: TObject);
|
|
begin
|
|
// Help button
|
|
Mainform.CallSQLHelpWithKeyword('CREATE TABLE');
|
|
end;
|
|
|
|
|
|
end.
|