* Add a binary editor to allow editing (var)binary and (tiny|medium|long)blob fields without messing up NULs etc.

* Add a few notes about where AVs seem to randomly occur when GridPost{Insert,Update,Delete} and EnsureDataLoaded invokes the query logic from outside the main thread.

Fixes issue #659 as far as the grids go,  but not for export functions.
This commit is contained in:
rosenfield.albert
2008-09-01 21:46:28 +00:00
parent 6a4c1e7637
commit cc2f93b24e
9 changed files with 399 additions and 30 deletions

View File

@ -39,6 +39,7 @@ uses
view in '..\..\source\view.pas' {frmView}, view in '..\..\source\view.pas' {frmView},
selectdbobject in '..\..\source\selectdbobject.pas' {frmSelectDBObject}, selectdbobject in '..\..\source\selectdbobject.pas' {frmSelectDBObject},
texteditor in '..\..\source\texteditor.pas' {frmTextEditor}, texteditor in '..\..\source\texteditor.pas' {frmTextEditor},
bineditor in '..\..\source\bineditor.pas' {frmBinEditor},
grideditlinks in '..\..\source\grideditlinks.pas', grideditlinks in '..\..\source\grideditlinks.pas',
uVistaFuncs in '..\..\source\uVistaFuncs.pas'; uVistaFuncs in '..\..\source\uVistaFuncs.pas';

View File

@ -63,6 +63,9 @@
<DCCReference Include="..\..\source\about.pas"> <DCCReference Include="..\..\source\about.pas">
<Form>AboutBox</Form> <Form>AboutBox</Form>
</DCCReference> </DCCReference>
<DCCReference Include="..\..\source\bineditor.pas">
<Form>frmBinEditor</Form>
</DCCReference>
<DCCReference Include="..\..\source\childwin.pas"> <DCCReference Include="..\..\source\childwin.pas">
<Form>MDIChild</Form> <Form>MDIChild</Form>
</DCCReference> </DCCReference>
@ -109,7 +112,6 @@
<Form>MainForm</Form> <Form>MainForm</Form>
</DCCReference> </DCCReference>
<DCCReference Include="..\..\source\mysql_structures.pas" /> <DCCReference Include="..\..\source\mysql_structures.pas" />
<DCCReference Include="..\..\source\memoeditor.pas">
<Form>frmMemoEditor</Form> <Form>frmMemoEditor</Form>
</DCCReference> </DCCReference>
<DCCReference Include="..\..\source\mysqlconn.pas" /> <DCCReference Include="..\..\source\mysqlconn.pas" />
@ -140,6 +142,9 @@
<DCCReference Include="..\..\source\tbl_properties.pas"> <DCCReference Include="..\..\source\tbl_properties.pas">
<Form>tbl_properties_form</Form> <Form>tbl_properties_form</Form>
</DCCReference> </DCCReference>
<DCCReference Include="..\..\source\texteditor.pas">
<Form>frmTextEditor</Form>
</DCCReference>
<DCCReference Include="..\..\source\threading.pas" /> <DCCReference Include="..\..\source\threading.pas" />
<DCCReference Include="..\..\source\updatecheck.pas"> <DCCReference Include="..\..\source\updatecheck.pas">
<Form>frmUpdateCheck</Form> <Form>frmUpdateCheck</Form>
@ -152,4 +157,4 @@
<Form>frmView</Form> <Form>frmView</Form>
</DCCReference> </DCCReference>
</ItemGroup> </ItemGroup>
</Project> </Project>

97
source/bineditor.dfm Normal file
View File

@ -0,0 +1,97 @@
object frmBinEditor: TfrmBinEditor
Left = 0
Top = 0
BorderStyle = bsSizeToolWin
Caption = 'Binary editor'
ClientHeight = 95
ClientWidth = 215
Color = clBtnFace
Constraints.MinHeight = 100
Constraints.MinWidth = 130
Font.Charset = DEFAULT_CHARSET
Font.Color = clWindowText
Font.Height = -11
Font.Name = 'Tahoma'
Font.Style = []
OldCreateOrder = False
Position = poMainFormCenter
OnCloseQuery = FormCloseQuery
OnCreate = FormCreate
OnDestroy = FormDestroy
OnShow = FormShow
DesignSize = (
215
95)
PixelsPerInch = 96
TextHeight = 13
object lblTextLength: TLabel
Left = 103
Top = 77
Width = 65
Height = 13
Anchors = [akLeft, akBottom]
BiDiMode = bdLeftToRight
Caption = 'lblTextLength'
ParentBiDiMode = False
end
object memoText: TTntMemo
Left = 0
Top = 0
Width = 215
Height = 72
Align = alTop
Anchors = [akLeft, akTop, akRight, akBottom]
Lines.Strings = (
'memoText')
ScrollBars = ssBoth
TabOrder = 0
WantTabs = True
OnChange = memoTextChange
OnKeyDown = memoTextKeyDown
end
object tlbStandard: TToolBar
Left = 0
Top = 73
Width = 97
Height = 22
Align = alNone
Anchors = [akLeft, akBottom]
Caption = 'tlbStandard'
Images = MainForm.PngImageListMain
ParentShowHint = False
ShowHint = True
TabOrder = 1
object btnWrap: TToolButton
Left = 0
Top = 0
Hint = 'Wrap long lines'
Caption = 'Wrap long lines'
ImageIndex = 62
OnClick = btnWrapClick
end
object btnLoadBinary: TToolButton
Left = 23
Top = 0
Hint = 'Load binary file'
Caption = 'Load binary file'
ImageIndex = 52
OnClick = btnLoadBinaryClick
end
object btnCancel: TToolButton
Left = 46
Top = 0
Hint = 'Cancel'
Caption = 'Cancel'
ImageIndex = 26
OnClick = btnCancelClick
end
object btnApply: TToolButton
Left = 69
Top = 0
Hint = 'Apply changes'
Caption = 'Apply changes'
ImageIndex = 55
OnClick = btnApplyClick
end
end
end

198
source/bineditor.pas Normal file
View File

@ -0,0 +1,198 @@
unit bineditor;
interface
uses
Windows, Classes, Graphics, Forms, Controls, helpers, StdCtrls, TntStdCtrls, Registry, VirtualTrees,
ComCtrls, ToolWin, Dialogs, SysUtils;
{$I const.inc}
type
TfrmBinEditor = class(TMemoEditor)
memoText: TTntMemo;
tlbStandard: TToolBar;
btnWrap: TToolButton;
btnLoadBinary: TToolButton;
btnApply: TToolButton;
btnCancel: TToolButton;
lblTextLength: TLabel;
procedure btnApplyClick(Sender: TObject);
procedure btnCancelClick(Sender: TObject);
procedure btnLoadBinaryClick(Sender: TObject);
procedure btnWrapClick(Sender: TObject);
procedure FormDestroy(Sender: TObject);
procedure FormShow(Sender: TObject);
procedure memoTextChange(Sender: TObject);
procedure memoTextKeyDown(Sender: TObject; var Key: Word; Shift: TShiftState);
procedure FormCloseQuery(Sender: TObject; var CanClose: Boolean);
procedure FormCreate(Sender: TObject);
private
{ Private declarations }
FModified: Boolean;
procedure SetModified(NewVal: Boolean);
property Modified: Boolean read FModified write SetModified;
public
function GetText: WideString; override;
procedure SetText(text: WideString); override;
procedure SetMaxLength(len: integer); override;
procedure SetFont(font: TFont); override;
end;
implementation
uses main;
{$R *.dfm}
function TfrmBinEditor.GetText: WideString;
begin
Result := '0x' + memoText.Text;
end;
procedure TfrmBinEditor.SetText(text: WideString);
begin
// Skip '0x'.
memoText.Text := Copy(text, 3);
end;
procedure TfrmBinEditor.SetMaxLength(len: integer);
begin
// Input: Length in bytes.
memoText.MaxLength := len * 2;
end;
procedure TfrmBinEditor.SetFont(font: TFont);
begin
memoText.Font := font;
end;
procedure TfrmBinEditor.FormCreate(Sender: TObject);
begin
InheritFont(Font);
end;
procedure TfrmBinEditor.FormDestroy(Sender: TObject);
var
reg: TRegistry;
begin
reg := TRegistry.Create;
if reg.OpenKey(REGPATH, False) then begin
reg.WriteInteger( REGNAME_EDITOR_WIDTH, Width );
reg.WriteInteger( REGNAME_EDITOR_HEIGHT, Height );
reg.CloseKey;
end;
reg.Free;
end;
procedure TfrmBinEditor.FormShow(Sender: TObject);
begin
// Restore form dimensions
Width := Mainform.GetRegValue(REGNAME_EDITOR_WIDTH, DEFAULT_EDITOR_WIDTH);
Height := Mainform.GetRegValue(REGNAME_EDITOR_HEIGHT, DEFAULT_EDITOR_HEIGHT);
// Fix label position:
lblTextLength.Top := tlbStandard.Top + (tlbStandard.Height-lblTextLength.Height) div 2;
SetWindowSizeGrip(Handle, True);
memoText.SelectAll;
memoText.SetFocus;
memoTextChange(Sender);
Modified := False;
end;
procedure TfrmBinEditor.memoTextKeyDown(Sender: TObject; var Key: Word; Shift:
TShiftState);
begin
case Key of
// Cancel by Escape
VK_ESCAPE: btnCancelClick(Sender);
// Apply changes and end editing by Ctrl + Enter
VK_RETURN: if ssCtrl in Shift then btnApplyClick(Sender);
end;
end;
procedure TfrmBinEditor.btnWrapClick(Sender: TObject);
var
WasModified: Boolean;
begin
Screen.Cursor := crHourglass;
// Changing the scrollbars invoke the OnChange event. We avoid thinking the text was really modified.
WasModified := Modified;
if memoText.ScrollBars = ssBoth then
memoText.ScrollBars := ssVertical
else
memoText.ScrollBars := ssBoth;
TToolbutton(Sender).Down := memoText.ScrollBars = ssVertical;
Modified := WasModified;
Screen.Cursor := crDefault;
end;
procedure TfrmBinEditor.btnLoadBinaryClick(Sender: TObject);
var
d: TOpenDialog;
begin
d := TOpenDialog.Create(Self);
d.Filter := 'All binary files (*.*)|*.*';
d.FilterIndex := 0;
if d.Execute then try
Screen.Cursor := crHourglass;
memoText.Text := BinToWideHex(ReadBinaryFile(d.FileName, memoText.MaxLength));
finally
Screen.Cursor := crDefault;
end;
d.Free;
end;
procedure TfrmBinEditor.btnCancelClick(Sender: TObject);
var
DoPost: Boolean;
begin
if Modified then
DoPost := MessageDlg('Apply modifications?', mtConfirmation, [mbYes, mbNo], 0) = mrYes
else
DoPost := False;
if DoPost then
TCustomVirtualStringTree(Owner).EndEditNode
else
TCustomVirtualStringTree(Owner).CancelEditNode;
end;
procedure TfrmBinEditor.FormCloseQuery(Sender: TObject; var CanClose: Boolean);
begin
btnCancelClick(Sender);
CanClose := False; // Done by editor link
end;
procedure TfrmBinEditor.btnApplyClick(Sender: TObject);
begin
TCustomVirtualStringTree(Owner).EndEditNode;
end;
procedure TfrmBinEditor.memoTextChange(Sender: TObject);
begin
lblTextLength.Caption := FormatNumber(Length(memoText.Text) / 2) + ' bytes.';
if memoText.MaxLength > 0 then
lblTextLength.Caption := lblTextLength.Caption + ' (Max: '+FormatNumber(memoText.MaxLength / 2)+')';
Modified := True;
end;
procedure TfrmBinEditor.SetModified(NewVal: Boolean);
begin
if FModified <> NewVal then begin
FModified := NewVal;
btnApply.Enabled := FModified;
end;
end;
end.

View File

@ -1204,7 +1204,11 @@ begin
rx.Expression := '^((tiny|medium|long)?text|(var)?char)\b(\(\d+\))?'; rx.Expression := '^((tiny|medium|long)?text|(var)?char)\b(\(\d+\))?';
if rx.Exec(ColType) then begin if rx.Exec(ColType) then begin
FDataGridResult.Columns[idx].IsText := True; FDataGridResult.Columns[idx].IsText := True;
if ColType = 'tinytext' then if rx.Match[4] <> '' then
FDataGridResult.Columns[idx].MaxLength := MakeInt(rx.Match[4])
else if ColType = 'tinytext' then
// 255 is the width in bytes. If characters that use multiple bytes are
// contained, the width in characters is decreased below this number.
FDataGridResult.Columns[idx].MaxLength := 255 FDataGridResult.Columns[idx].MaxLength := 255
else if ColType = 'text' then else if ColType = 'text' then
FDataGridResult.Columns[idx].MaxLength := 65535 FDataGridResult.Columns[idx].MaxLength := 65535
@ -1212,14 +1216,13 @@ begin
FDataGridResult.Columns[idx].MaxLength := 16777215 FDataGridResult.Columns[idx].MaxLength := 16777215
else if ColType = 'longtext' then else if ColType = 'longtext' then
FDataGridResult.Columns[idx].MaxLength := 4294967295 FDataGridResult.Columns[idx].MaxLength := 4294967295
else if rx.Match[4] <> '' then else
FDataGridResult.Columns[idx].MaxLength := MakeInt(rx.Match[4]) // Fallback for unknown column types
else // Fallback for unknown column types
FDataGridResult.Columns[idx].MaxLength := MaxInt; FDataGridResult.Columns[idx].MaxLength := MaxInt;
end; end;
rx.Expression := '^((tiny|medium|long)?blob|(var)?binary|bit)\b'; rx.Expression := '^((tiny|medium|long)?blob|(var)?binary|bit)\b';
if rx.Exec(ColType) then if rx.Exec(ColType) then
FDataGridResult.Columns[idx].IsBlob := True; FDataGridResult.Columns[idx].IsBinary := True;
if Copy(ColType, 1, 5) = 'enum(' then begin if Copy(ColType, 1, 5) = 'enum(' then begin
FDataGridResult.Columns[idx].IsEnum := True; FDataGridResult.Columns[idx].IsEnum := True;
FDataGridResult.Columns[idx].EnumVals := WideStrings.TWideStringList.Create; FDataGridResult.Columns[idx].EnumVals := WideStrings.TWideStringList.Create;
@ -2482,7 +2485,7 @@ begin
else if ds.Fields[i].DataType in [ftWideString, ftMemo, ftWideMemo] then else if ds.Fields[i].DataType in [ftWideString, ftMemo, ftWideMemo] then
FQueryGridResult.Columns[i].IsText := True FQueryGridResult.Columns[i].IsText := True
else if ds.Fields[i].DataType in [ftBlob] then else if ds.Fields[i].DataType in [ftBlob] then
FQueryGridResult.Columns[i].IsBlob := True; FQueryGridResult.Columns[i].IsBinary := True;
end; end;
SetLength(FQueryGridResult.Rows, 0); SetLength(FQueryGridResult.Rows, 0);
SetLength(FQueryGridResult.Rows, ds.RecordCount); SetLength(FQueryGridResult.Rows, ds.RecordCount);
@ -2491,8 +2494,8 @@ begin
FQueryGridResult.Rows[i].Loaded := True; FQueryGridResult.Rows[i].Loaded := True;
SetLength(FQueryGridResult.Rows[i].Cells, ds.FieldCount); SetLength(FQueryGridResult.Rows[i].Cells, ds.FieldCount);
for j:=0 to ds.FieldCount-1 do begin for j:=0 to ds.FieldCount-1 do begin
if FQueryGridResult.Columns[j].IsBlob then if FQueryGridResult.Columns[j].IsBinary then
FQueryGridResult.Rows[i].Cells[j].Text := Utf8Decode(ds.Fields[j].AsString) FQueryGridResult.Rows[i].Cells[j].Text := '0x' + BinToWideHex(ds.Fields[j].AsString)
else else
FQueryGridResult.Rows[i].Cells[j].Text := ds.Fields[j].AsWideString; FQueryGridResult.Rows[i].Cells[j].Text := ds.Fields[j].AsWideString;
FQueryGridResult.Rows[i].Cells[j].IsNull := ds.Fields[j].IsNull; FQueryGridResult.Rows[i].Cells[j].IsNull := ds.Fields[j].IsNull;
@ -3575,6 +3578,12 @@ begin
end; end;
// Create instance of the progress form (but don't show it yet) // Create instance of the progress form (but don't show it yet)
// Todo: This apparently causes an exception if invoked via an event handler?
// Classes.TStream.ReadComponent(???)
// Classes.InternalReadComponentRes(???,???,???)
// Classes.InitComponent(TfrmQueryProgress)
// Classes.InitInheritedComponent($17E0710,TForm)
// Forms.TCustomForm.Create(???)
FProgressForm := TFrmQueryProgress.Create(Self); FProgressForm := TFrmQueryProgress.Create(Self);
{ Launch a thread of execution that passes the query to the server { Launch a thread of execution that passes the query to the server
@ -5478,8 +5487,8 @@ begin
for i := start to start + limit - 1 do begin for i := start to start + limit - 1 do begin
SetLength(FDataGridResult.Rows[i].Cells, ds.Fields.Count); SetLength(FDataGridResult.Rows[i].Cells, ds.Fields.Count);
for j := 0 to ds.Fields.Count - 1 do begin for j := 0 to ds.Fields.Count - 1 do begin
if FDataGridResult.Columns[j].IsBlob then if FDataGridResult.Columns[j].IsBinary then
FDataGridResult.Rows[i].Cells[j].Text := Utf8Decode(ds.Fields[j].AsString) FDataGridResult.Rows[i].Cells[j].Text := '0x' + BinToWideHex(ds.Fields[j].AsString)
else else
FDataGridResult.Rows[i].Cells[j].Text := ds.Fields[j].AsWideString; FDataGridResult.Rows[i].Cells[j].Text := ds.Fields[j].AsWideString;
FDataGridResult.Rows[i].Cells[j].IsNull := ds.Fields[j].IsNull; FDataGridResult.Rows[i].Cells[j].IsNull := ds.Fields[j].IsNull;
@ -5489,6 +5498,7 @@ begin
end; end;
MainForm.ShowStatus( STATUS_MSG_READY ); MainForm.ShowStatus( STATUS_MSG_READY );
// Todo: Seen an AV next line when this method was invoked via an event handler.
FreeAndNil(ds); FreeAndNil(ds);
end; end;
end; end;
@ -5573,7 +5583,7 @@ begin
else if r.Columns[Column].isText then else if r.Columns[Column].isText then
if isNull then cl := $60CC60 else cl := clGreen if isNull then cl := $60CC60 else cl := clGreen
// Text field // Text field
else if r.Columns[Column].isBlob then else if r.Columns[Column].isBinary then
if isNull then cl := $CC60CC else cl := clPurple if isNull then cl := $CC60CC else cl := clPurple
else else
if isNull then cl := COLOR_NULLVALUE else cl := clWindowText; if isNull then cl := COLOR_NULLVALUE else cl := clWindowText;
@ -5723,7 +5733,8 @@ begin
if Row.Cells[i].Modified then begin if Row.Cells[i].Modified then begin
Val := Row.Cells[i].NewText; Val := Row.Cells[i].NewText;
if FDataGridResult.Columns[i].IsFloat then Val := FloatStr(Val); if FDataGridResult.Columns[i].IsFloat then Val := FloatStr(Val);
Val := esc(Val); if not FDataGridResult.Columns[i].IsBinary then Val := esc(Val);
if FDataGridResult.Columns[i].IsBinary then CheckHex(Copy(Val, 3), 'Invalid hexadecimal string given in field "' + FDataGridResult.Columns[i].Name + '".');
if Row.Cells[i].NewIsNull then Val := 'NULL'; if Row.Cells[i].NewIsNull then Val := 'NULL';
sql := sql + ' ' + mask(FDataGridResult.Columns[i].Name) + '=' + Val + ', '; sql := sql + ' ' + mask(FDataGridResult.Columns[i].Name) + '=' + Val + ', ';
end; end;
@ -5759,8 +5770,8 @@ begin
ds := ExecSelectQuery(sql); ds := ExecSelectQuery(sql);
if ds.RecordCount = 1 then begin if ds.RecordCount = 1 then begin
for i := 0 to ds.FieldCount - 1 do begin for i := 0 to ds.FieldCount - 1 do begin
if FDataGridResult.Columns[i].IsBlob then if FDataGridResult.Columns[i].IsBinary then
Row.Cells[i].Text := Utf8Decode(ds.Fields[i].AsString) Row.Cells[i].Text := '0x' + BinToWideHex(ds.Fields[i].AsString)
else else
Row.Cells[i].Text := ds.Fields[i].AsWideString; Row.Cells[i].Text := ds.Fields[i].AsWideString;
Row.Cells[i].IsNull := ds.Fields[i].IsNull; Row.Cells[i].IsNull := ds.Fields[i].IsNull;
@ -5814,7 +5825,7 @@ begin
KeyVal := Row.Cells[j].Text; KeyVal := Row.Cells[j].Text;
// Quote if needed // Quote if needed
if FDataGridResult.Columns[j].IsFloat then KeyVal := FloatStr(KeyVal); if FDataGridResult.Columns[j].IsFloat then KeyVal := FloatStr(KeyVal);
KeyVal := esc(KeyVal); if not FDataGridResult.Columns[j].IsBinary then KeyVal := esc(KeyVal);
if Row.Cells[j].IsNull then KeyVal := ' IS NULL' if Row.Cells[j].IsNull then KeyVal := ' IS NULL'
else KeyVal := '=' + KeyVal; else KeyVal := '=' + KeyVal;
Result := Result + mask(KeyCols[i]) + KeyVal + ' AND '; Result := Result + mask(KeyCols[i]) + KeyVal + ' AND ';
@ -5898,7 +5909,8 @@ begin
Cols := Cols + mask(FDataGridResult.Columns[i].Name) + ', '; Cols := Cols + mask(FDataGridResult.Columns[i].Name) + ', ';
Val := Row.Cells[i].NewText; Val := Row.Cells[i].NewText;
if FDataGridResult.Columns[i].IsFloat then Val := FloatStr(Val); if FDataGridResult.Columns[i].IsFloat then Val := FloatStr(Val);
Val := esc(Val); if not FDataGridResult.Columns[i].IsBinary then Val := esc(Val);
if FDataGridResult.Columns[i].IsBinary then CheckHex(Copy(Val, 3), 'Invalid hexadecimal string given in field "' + FDataGridResult.Columns[i].Name + '".');
if Row.Cells[i].NewIsNull then Val := 'NULL'; if Row.Cells[i].NewIsNull then Val := 'NULL';
Vals := Vals + Val + ', '; Vals := Vals + Val + ', ';
end; end;
@ -6080,7 +6092,10 @@ var
DateTimeEditor: TDateTimeEditorLink; DateTimeEditor: TDateTimeEditorLink;
EnumEditor: TEnumEditorLink; EnumEditor: TEnumEditorLink;
begin begin
if FDataGridResult.Columns[Column].IsText then begin if
FDataGridResult.Columns[Column].IsText or
FDataGridResult.Columns[Column].IsBinary
then begin
MemoEditor := TMemoEditorLink.Create; MemoEditor := TMemoEditorLink.Create;
MemoEditor.MaxLength := FDataGridResult.Columns[Column].MaxLength; MemoEditor.MaxLength := FDataGridResult.Columns[Column].MaxLength;
EditLink := MemoEditor; EditLink := MemoEditor;

View File

@ -4,8 +4,8 @@ unit grideditlinks;
interface interface
uses Windows, Forms, Graphics, messages, VirtualTrees, texteditor, ComCtrls, SysUtils, Classes, uses Windows, Forms, Graphics, messages, VirtualTrees, texteditor, bineditor, ComCtrls, SysUtils, Classes,
mysql_structures, Main, helpers, TntStdCtrls, WideStrings, StdCtrls; mysql_structures, Main, ChildWin, helpers, TntStdCtrls, WideStrings, StdCtrls;
type type
TMemoEditorLink = class(TInterfacedObject, IVTEditLink) TMemoEditorLink = class(TInterfacedObject, IVTEditLink)
@ -95,6 +95,7 @@ end;
function TMemoEditorLink.PrepareEdit(Tree: TBaseVirtualTree; Node: PVirtualNode; Column: TColumnIndex): Boolean; stdcall; function TMemoEditorLink.PrepareEdit(Tree: TBaseVirtualTree; Node: PVirtualNode; Column: TColumnIndex): Boolean; stdcall;
// Retrieves the true text bounds from the owner tree. // Retrieves the true text bounds from the owner tree.
var var
IsBinary: Boolean;
Text: WideString; Text: WideString;
F: TFont; F: TFont;
begin begin
@ -110,11 +111,15 @@ begin
F := TFont.Create; F := TFont.Create;
FTree.GetTextInfo(Node, Column, F, FTextBounds, Text); FTree.GetTextInfo(Node, Column, F, FTextBounds, Text);
IsBinary := MainForm.ChildWin.FDataGridResult.Columns[Column].IsBinary;
// Get wide text of the node. // Get wide text of the node.
Text := FTree.Text[FNode, FColumn]; Text := FTree.Text[FNode, FColumn];
// Create the editor form // Create the text editor form
FForm := TfrmTextEditor.Create(Ftree); if IsBinary then FForm := TfrmBinEditor.Create(Ftree)
else FForm := TfrmTextEditor.Create(Ftree);
FForm.SetFont(F); FForm.SetFont(F);
FForm.SetText(Text); FForm.SetText(Text);
FForm.SetMaxLength(MaxLength); FForm.SetMaxLength(MaxLength);

View File

@ -51,7 +51,7 @@ type
DataType: Byte; // @see constants in mysql_structures.pas DataType: Byte; // @see constants in mysql_structures.pas
MaxLength: Cardinal; MaxLength: Cardinal;
IsPriPart: Boolean; IsPriPart: Boolean;
IsBlob: Boolean; IsBinary: Boolean;
IsText: Boolean; IsText: Boolean;
IsEnum: Boolean; IsEnum: Boolean;
IsInt: Boolean; IsInt: Boolean;
@ -150,8 +150,12 @@ type
function GetFileCharset(Stream: TFileStream): TFileCharset; function GetFileCharset(Stream: TFileStream): TFileCharset;
function ReadTextfileChunk(Stream: TFileStream; FileCharset: TFileCharset; ChunkSize: Int64 = 0): WideString; function ReadTextfileChunk(Stream: TFileStream; FileCharset: TFileCharset; ChunkSize: Int64 = 0): WideString;
function ReadTextfile(Filename: String): WideString; function ReadTextfile(Filename: String): WideString;
function ReadBinaryFile(Filename: String; MaxBytes: Int64): string;
procedure CopyToClipboard(Value: WideString); procedure CopyToClipboard(Value: WideString);
procedure StreamToClipboard(S: TMemoryStream); procedure StreamToClipboard(S: TMemoryStream);
function WideHexToBin(text: WideString): string;
function BinToWideHex(bin: string): WideString;
procedure CheckHex(text: WideString; errorMessage: string);
var var
MYSQL_KEYWORDS : TStringList; MYSQL_KEYWORDS : TStringList;
@ -194,6 +198,38 @@ var
function WideHexToBin(text: WideString): string;
var
buf: string;
begin
// Todo: test.
buf := text;
SetLength(Result, Length(text) div 2);
HexToBin(@buf[1], @Result[1], Length(Result));
end;
function BinToWideHex(bin: string): WideString;
var
buf: string;
begin
SetLength(buf, Length(bin) * 2);
BinToHex(@bin[1], @buf[1], Length(buf));
Result := buf;
end;
procedure CheckHex(text: WideString; errorMessage: string);
const
allowed: string = '0123456789abcdefABCDEF';
var
i: Cardinal;
begin
for i := 1 to Length(text) do begin
if Pos(text[i], allowed) < 1 then begin
raise Exception.Create(errorMessage);
end;
end;
end;
{*** {***
Convert a TStringList to a string using a separator-string Convert a TStringList to a string using a separator-string
@ -2444,6 +2480,17 @@ begin
Stream.Free; Stream.Free;
end; end;
function ReadBinaryFile(Filename: String; MaxBytes: Int64): string;
var
Stream: TFileStream;
begin
Stream := TFileStream.Create(Filename, fmOpenRead or fmShareDenyNone);
Stream.Position := 0;
if (MaxBytes < 1) or (MaxBytes > Stream.Size) then MaxBytes := Stream.Size;
SetLength(Result, MaxBytes);
Stream.Read(PChar(Result)^, Length(Result));
Stream.Free;
end;
{ TUniClipboard } { TUniClipboard }

View File

@ -1230,7 +1230,7 @@ var
f : Textfile; f : Textfile;
tmppath : array[0..MAX_PATH] of char; tmppath : array[0..MAX_PATH] of char;
Content : WideString; Content : WideString;
IsBlob : Boolean; IsBinary : Boolean;
begin begin
g := ChildWin.ActiveGrid; g := ChildWin.ActiveGrid;
if g = nil then begin messagebeep(MB_ICONASTERISK); exit; end; if g = nil then begin messagebeep(MB_ICONASTERISK); exit; end;
@ -1238,19 +1238,19 @@ begin
showstatus('Saving contents to file...'); showstatus('Saving contents to file...');
if g = Childwin.DataGrid then begin if g = Childwin.DataGrid then begin
Content := Childwin.FDataGridResult.Rows[Childwin.DataGrid.FocusedNode.Index].Cells[Childwin.DataGrid.FocusedColumn].Text; Content := Childwin.FDataGridResult.Rows[Childwin.DataGrid.FocusedNode.Index].Cells[Childwin.DataGrid.FocusedColumn].Text;
IsBlob := Childwin.FDataGridResult.Columns[Childwin.DataGrid.FocusedColumn].IsBlob; IsBinary := Childwin.FDataGridResult.Columns[Childwin.DataGrid.FocusedColumn].IsBinary;
end else begin end else begin
Content := Childwin.FQueryGridResult.Rows[Childwin.QueryGrid.FocusedNode.Index].Cells[Childwin.QueryGrid.FocusedColumn].Text; Content := Childwin.FQueryGridResult.Rows[Childwin.QueryGrid.FocusedNode.Index].Cells[Childwin.QueryGrid.FocusedColumn].Text;
IsBlob := Childwin.FQueryGridResult.Columns[Childwin.QueryGrid.FocusedColumn].IsBlob; IsBinary := Childwin.FQueryGridResult.Columns[Childwin.QueryGrid.FocusedColumn].IsBinary;
end; end;
childwin.logsql(g.Name); childwin.logsql(g.Name);
GetTempPath(MAX_PATH, tmppath); GetTempPath(MAX_PATH, tmppath);
filename := tmppath; filename := tmppath;
filename := filename+'\'+APPNAME+'-preview.'; filename := filename+'\'+APPNAME+'-preview.';
if IsBlob then begin if IsBinary then begin
if pos('JFIF', copy(Content, 0, 20)) <> 0 then if pos('JFIF', copy(Content, 0, 20)) <> 0 then
filename := filename + 'jpg' filename := filename + 'jpeg'
else if StrCmpBegin('GIF', Content) then else if StrCmpBegin('GIF', Content) then
filename := filename + 'gif' filename := filename + 'gif'
else if StrCmpBegin('BM', Content) then else if StrCmpBegin('BM', Content) then

View File

@ -4,7 +4,7 @@ interface
uses uses
Windows, Classes, Graphics, Forms, Controls, helpers, StdCtrls, TntStdCtrls, Registry, VirtualTrees, Windows, Classes, Graphics, Forms, Controls, helpers, StdCtrls, TntStdCtrls, Registry, VirtualTrees,
ComCtrls, ToolWin, Dialogs; ComCtrls, ToolWin, Dialogs, SysUtils;
{$I const.inc} {$I const.inc}
@ -60,6 +60,7 @@ end;
procedure TfrmTextEditor.SetMaxLength(len: integer); procedure TfrmTextEditor.SetMaxLength(len: integer);
begin begin
// Input: Length in number of bytes.
memoText.MaxLength := len; memoText.MaxLength := len;
end; end;