unit vcl.wwdbgrid;
{
  //
  // Components : TwwDBGrid
  //
  // Copyright (c) 1995-2001 by Woll2Woll Software
  //
  // 7/19/97 - Only readjust customedit rectangle if cell boundary left,right
  //           has changed.  Previoulsy would readjust even on height change
  //           which caused a flicker with a lookupcombo that had its icon too
  //           big to fit into the cell.
  // 8/12/97 - Optimization for screen painting.  Check if field is TBlobField
  //           before calling IsRichEditCell
  // 8/14/97 - Optimized logic to speed painting of grid in
  //           function IsRichEditCell and IsCustomEditCell
  // 9/2/97  - Override isCustomEditCell so that it ignores record-view when
  //           mapping
  // 9/4/97  - fctRichEdit same path as customEdit in SetControlType method
  // 9/15/97 - Hidecontrols needed to be changed to HideControls.
  // 9/19/97 - Allow multi-selection checkbox to be toggled even when editing is
  //           not allowed
  // 10/24/97- Only toggle checkboxes if can edit grid or multi-selection checkbox
  // 12/02/97- Added try block on gotobookmark to catch exception when bkmrk2 is invalid
  //
  // 1/9/98  - Only set focus if canFocus is true
  // 1/25/98 - Add EditControlOptions | DisableCustomControls so record-panel
  //           and grid will not both be using the same edit control.
  // 1/28/97 - Allow Change of text when there is a protected flag in richtext to handle
  //           TRichEdit bug.
  // 3/11/98 - Auto-invalidate if grid rowcount or colcount changes
  // 3/20/98 - Prevent flicker when control becomes visible by only calling update
  //           for control
  // 3/20/98 - Rely on edit control to selectAll when control is made visible
  //           with dgAlwaysShowEditor as True
  // 4/9/98 -  Clear currentcustomedit when table is made active
  // 5/13/98 - Fix memory leak for ShiftSelectBookmark
  // 8/13/98 - Fix for datetimepicker disabling bug
  // 10/6/98 - Support display of simple text even when field is defined as a rich
  //           edit text.
  // 10/27/98 -Update MouseCoord method to support bdRightToLeft - Delphi's
  //           version does not support fixed columns.
  // 11/26/98 - Respect readonly property of related field for default datetimepicker
  // 12/7/98 - Correct OnColWidthChanged event passing the wrong column
  // 1/26/99 - Prevent clearing of grid when hiding controls
  // 2/19/99 - Default datetimepicker should go through default datetimepicker
  //           specfic code when changing columns.  Fixes Readonly problem for
  //           default datetimepicker
  // 3/17/99 - Fire OnMultiSelectRecord with ShiftSelect
  // 6/28/99 - Fire OnKeyPress event even when rowselect/edit=false on custom control field
  // 9/30/99 - Handle INI file issues with datamodule and FormCreate. (PYW)
  // 2/19/00 - Create RefreshActiveControl method to call from ApplySelected
  //           This removes the dependency upon the column changing for the control
  //           information to be updated.
  // 3/6/00 - Add new fields to dynamic selected property from selected property generated by iniattributes
  // 3/9/00 - OnColEnter being fired twice
  // 4/24/00 - Don't toggle checkbox when dataset is readonly
  // 5/4/00 - publish OnStartDrag property
  // 8/11/00 - Make LoadFromIniFile and SaveToIniFile virtual
  // 4/12/01 - Don't load from ini when design-time
  // 4/12/01 - Added Grid exporting capability.
  // 8/22/01 - Don't set focus if active form is not for this grid
  //           For instance, in case there was validation in the tfield.onvalidate event
  //           which showed a message
  // 9/26/01 - Added new event to allow formating of SYLK data export when exporting to a file. -PYW
  // 10/28/01 - Otherwise mapped values not respected
  // 12/11/01 - RSW - Don't pass to inherited KeyDown as then focus can get lost causing datetimepicker to not dropdown
  // 1/9/2002 - Added patch[5] for backwards compatibility when exiting the grid error messages from onvalidate were not
  //            displayed. Using a Patch variable to provide solution while studying impact.
  // 3/14/2002- PYW - Only SelectRecord/UnSelectRecord if multiselect and RowSelect enabled and space is pressed.
  //            Otherwise space on a checkbox will not be respected thus breaking backwards compatibility.
  // 4/16/2002 - If an exception is raised in HideControls when a customedit loses focus and the grid gets it.
  // 5/8/2002 - PYW - Corrected some TCustomGrid ColSizing bugs.
  // 5/16/02 - Add test to see if the grid is the activecontrol before setting focus.
  // 4/15/03 - Publish DittoAttributes
  // 9/24/03 - When toggling checkbox in righttoleft, extra painting was done.
  // 7/29/04 - Fix bug when expanded with multi-select. It checks wrong row before
}

interface

{$I wwIfDef.pas}

uses
  SysUtils, Windows, Messages, Classes, Graphics, Controls, StdCtrls,
  Forms, Grids, {DsgnIntf,} dialogs, db, wwstr,
  wwMemo, wwcommon, wwdbigrd,
  wwdbdatetimepicker, themes,
  system.UITypes,
  wwexport,
  comctrls, wwriched,
  Menus, wwdbedit, wwtypes, dbctrls, inifiles, registry,
  system.Types;
{$C PRELOAD}

type
  TwwFieldControlType = (fctNone, fctField, fctCheckBox, fctCustom, fctBitmap,
    fctLookupCombo, fctComboBox, fctRichEdit, fctImageIndex, fctURLLink);
  TwwOnInitMemoDlgEvent = procedure(Dialog: TwwMemoDlg) of object;
  TwwMemoUserButtonEvent = procedure(Dialog: TwwMemoDlg; Memo: TMemo) of object;
  TwwCreateDTPEvent = procedure(Sender: TObject;
    ADateTimePicker: TwwDBCustomDateTimePicker) of object;
  TwwDBGrid = class;

  TwwIniAttributes = class(TPersistent)
  private
    FFormName: string;
    FFileName, FSectionName, FDelimiter: string;
    FEnabled: Boolean;
    FSaveToRegistry: Boolean;
    FCheckNewFields: Boolean;
    FIgnoreTitles: Boolean;
    {$ifdef wwDelphi2013Up}
    FUnicodeIniFile: Boolean;
    {$endif}
    procedure SetFileName(val: string);
    procedure SetSectionName(val: string);
    procedure SetDelimiter(val: string);
    procedure SetEnabled(val: Boolean);
    function GetSectionName: string;
    function GetFileName: string;
  public
    Owner: TComponent;
    property FormName: string read FFormName;
  published
    property Enabled: Boolean read FEnabled write SetEnabled default False;
    property SaveToRegistry: Boolean read FSaveToRegistry write FSaveToRegistry
      default False;
    property FileName: string read GetFileName write SetFileName;
    property SectionName: string read GetSectionName write SetSectionName;
    property Delimiter: string read FDelimiter write SetDelimiter;
    property CheckNewFields: Boolean read FCheckNewFields write FCheckNewFields
      default False;
    property IgnoreTitles: Boolean read FIgnoreTitles write FIgnoreTitles
      default False;
    {$ifdef wwDelphi2013Up}
    property UnicodeIniFile: boolean read FUnicodeIniFile write FUnicodeIniFile;
    {$endif}
  end;

  TwwMemoDialog = class(TComponent)
  private
    FFont: TFont;
    FDataLink: TFieldDataLink;
    FMemoAttributes: TwwMemoAttributes;
    FCaption: String;
    FLeft, FTop, FWidth, FHeight: integer;

    FUserButton1Click: TwwMemoUserButtonEvent;
    FUserButton2Click: TwwMemoUserButtonEvent;
    FOnInitDialog: TwwOnInitMemoDlgEvent;
    FOnCloseDialog: TwwOnInitMemoDlgEvent;
    FUserButton1Caption: string;
    FUserButton2Caption: string;
    FLines: TStrings;
    procedure SetLines(val: TStrings);
  protected
    procedure SetDataField(value: String);
    procedure SetDataSource(value: TDataSource);
    Function GetDataSource: TDataSource;
    Function GetDataField: String;
    procedure SetwwMemoAttributes(sel: TwwMemoAttributes);
    procedure SetFont(value: TFont);
    procedure SetCaption(value: String);
  public
    Form: TwwMemoDlg; { Used by TwwMemoDlg }
    Patch: Variant;
    constructor Create(AOwner: TComponent); override;
    destructor Destroy; override;
    procedure DoInitDialog; virtual; { called by wwmemo }
    procedure DoCloseDialog; virtual; { called by wwmemo }

  published
    property DataSource: TDataSource read GetDataSource write SetDataSource;
    property DataField: String read GetDataField write SetDataField;
    function Execute: Boolean; virtual;
    property Font: TFont read FFont write SetFont;
    property MemoAttributes: TwwMemoAttributes read FMemoAttributes
      write SetwwMemoAttributes default [mSizeable, mWordWrap];
    property Caption: string read FCaption write SetCaption;
    property Lines: TStrings read FLines write SetLines;

    property DlgLeft: integer read FLeft write FLeft;
    property DlgTop: integer read FTop write FTop;
    property DlgWidth: integer read FWidth write FWidth;
    property DlgHeight: integer read FHeight write FHeight;

    property OnInitDialog: TwwOnInitMemoDlgEvent read FOnInitDialog
      write FOnInitDialog;
    property OnCloseDialog: TwwOnInitMemoDlgEvent read FOnCloseDialog
      write FOnCloseDialog;
    property OnUserButton1Click: TwwMemoUserButtonEvent read FUserButton1Click
      write FUserButton1Click;
    property OnUserButton2Click: TwwMemoUserButtonEvent read FUserButton2Click
      write FUserButton2Click;
    property UserButton1Caption: string read FUserButton1Caption
      write FUserButton1Caption;
    property UserButton2Caption: string read FUserButton2Caption
      write FUserButton2Caption;

  end;

  TwwMemoOpenEvent = procedure(Grid: TwwDBGrid; MemoDialog: TwwMemoDialog)
    of object;

  TwwMemoCloseEvent = procedure(Grid: TwwDBGrid; Cancel: Boolean) of object;

  TwwSelectRecordEvent = procedure(Grid: TwwDBGrid; Selecting: Boolean;
    var Accept: Boolean) of object;

  TwwSelectAllRecordEvent = procedure(Grid: TwwDBGrid; Selecting: Boolean;
    var Accept: Boolean) of object;

  TwwExportFieldEvent = procedure(Grid: TwwDBGrid; Field: TField;
    var Accept: Boolean) of object;

  TwwExportSYLKFormatEvent = procedure(Grid: TwwDBGrid; Field: TField;
    var SYLKFormat: String) of object;
  TwwExportFormatEvent = procedure(Grid: TwwDBGrid; Field: TField;
    var Format: String) of object;

  TwwDBGrid = class(TwwCustomDBGrid)
  strict private
    class constructor Create;
  private
    FSizingIndex, FSizingPos, FSizingOfs: integer;
    FMemoAttributes: TwwMemoAttributes;
    FIniAttributes: TwwIniAttributes;

{$IFNDEF GRIDESSENTIALS}
    FExportOptions: TwwExportOptions;
{$ENDIF}
    redrawingGrid: Boolean;
    initialized: Boolean;
    doneInitControls: Boolean;
    drawingCell: Boolean;
    FOnMemoOpen: TwwMemoOpenEvent;
    FOnMemoClose: TwwMemoCloseEvent;
    FFixedCols: integer;
    FDirtyIni: Boolean;
    inLinkActive: Boolean;
    inTopLeftChanged: Boolean;
    GridIsLoaded: Boolean;
    FOnSelectRecord: TwwSelectRecordEvent;
    FOnSelectAllRecords: TwwSelectAllRecordEvent;
    FOnExportField: TwwExportFieldEvent;
    FOnExportSYLKFormat: TwwExportSYLKFormatEvent;
    FOnExportFormat: TwwExportFormatEvent;
    FOnCreateDateTimePicker: TwwCreateDTPEvent;
    FLoadAllRTF: Boolean;
    OrigSelected: TStrings;

    { Multi-selection support variables }
    // Bookmarks: TList;
    Bookmarks: TwwBookmarkList;
    FDependentComponents: Tlist;
    MakeCustomControlVisible: Boolean;
    FDisableThemes: Boolean;

    // FCustomControls: array of TwwCustomControlItem;
    Function GetTitleColor: TColor;
    procedure SetTitleColor(sel: TColor);
    // Function GetDataLineColor: TColor;
    // procedure SetDatalineColor(sel: TColor);

    Function GetDataSource: TDataSource;
    Procedure SetDataSource(val: TDataSource);

    procedure SetwwMemoAttributes(sel: TwwMemoAttributes);
    procedure CMDesignHitTest(var Msg: TCMDesignHitTest);
      message CM_DESIGNHITTEST;
    procedure WMLButtonDblClk(var Msg: TWMLButtonDblClk);
      message WM_LBUTTONDBLCLK;
    procedure CMCtl3DChanged(var Message: TMessage); message CM_CTL3DCHANGED;
    procedure WMChar(var Msg: TWMChar); message WM_CHAR;
    procedure WMPaint(var Message: TWMPaint); message WM_PAINT;

    procedure UpdateSelectedProperty;
    function GetDateTimePicker: TwwDBCustomDateTimePicker;
    procedure DrawSizingLine(const DrawInfo: TGridDrawInfo);

  protected
    SelectedRecordList: TStrings; { Internal buffers selected value }

    Function WideGetFieldValue(ACol: integer): WideString; override;
    procedure ShowCurrentControl; virtual;
    procedure AdjustLeftCol; virtual;
    procedure CalcSizingState(X, Y: integer; var State: TGridState;
      var Index: Longint; var SizingPos, SizingOfs: integer;
      var FixedInfo: TGridDrawInfo); override;
    procedure CreateWnd; override;

    function CreateDateTimePicker: TwwDBCustomDateTimePicker; virtual;
    procedure DoCreateDateTimePicker(ADateTimePicker
      : TwwDBCustomDateTimePicker); virtual;

    procedure CalcRowHeight; override;

    procedure DoExit; override;
    procedure ColumnMoved(FromIndex, ToIndex: Longint); override;
    procedure ColWidthsChanged; override;
    procedure LinkActive(value: Boolean); override;
    procedure SetFieldValue(ACol: integer; val: string);
    function CanEditShow: Boolean; override;
    procedure MouseDown(Button: TMouseButton; Shift: TShiftState;
      X, Y: integer); override;
    procedure MouseUp(Button: TMouseButton; Shift: TShiftState;
      X, Y: integer); override;
    procedure MouseMove(Shift: TShiftState; X, Y: integer); override;
    procedure ColExit; override;
    procedure TopLeftChanged; override;
    Function AllowCancelOnExit: Boolean; override;

    procedure ToggleCheckBox(col, row: integer);
    procedure InitControls;
    procedure SetFixedCols(val: integer);
    function GetFixedCols: integer;
    procedure Paint; override;
    function IsWWControl(ACol, ARow: integer): Boolean; override;
    procedure CallMemoDialog;
    Function findBookmark: TBookmark;

    Function CellColor(ACol, ARow: integer): TColor; override;
    procedure RefreshBookmarkList; override;
    procedure Scroll(Distance: integer); override;
    procedure Loaded; override;
    procedure SelectRecordRange(bkmrk1, bkmrk2: TBookmark); virtual;
    Procedure RemoveSelected(bkmrk1, bkmrk2: TBookmark); virtual;
    Function IsSelectedRow(DataRow: integer): Boolean; override;
    procedure RefreshActiveControl; virtual;
    function IsActiveControl: Boolean;

    // procedure PaintWindow(DC: HDC); override;

  public
    Patch: Variant;

    Function IsSelected: Boolean; override;
    Function IsSelectedRecord: Boolean;
    Procedure SelectRecord; override;
    Procedure UnselectRecord; override;
    Procedure SelectAll;
    Procedure UnselectAll; override;
    Procedure LoadFromIniFile; virtual;
    Procedure SaveToIniFile; virtual;
    procedure ClearControls;

    procedure FlushChanges; override;
    procedure KeyDown(var Key: word; Shift: TShiftState); override;
    Function GetRowCount: integer;
    Function GetColCount: integer;
    Function GetActiveRow: integer;
    Function GetActiveCol: integer;
    Procedure SetActiveRow(val: integer); { 10/24/96 }
    procedure ScrollCurrentToTop(currentRow: integer = 0);
    // Scroll's current record to top if possible

    Function GetActiveField: TField;
    Procedure SetActiveField(AFieldName: string);

    function IsRichEditCell(col, row: integer;
      var customEdit: TWinControl): Boolean;

    // 4/19/2001-Make following procedures public for export unit.
    procedure GetControlInfo(AFieldName: string; var AControlType: string;
      var AParameters: string); override;
    Function GetFieldValue(ACol: integer): string; override;
    procedure DoExportField(Grid: TwwDBGrid; AField: TField;
      var Accept: Boolean); virtual;
    procedure DoExportSYLKFormat(Grid: TwwDBGrid; AField: TField;
      var SYLKFormat: String); virtual;
    procedure DoExportFormat(Grid: TwwDBGrid; AField: TField;
      var Format: String);

    procedure SetScrollBars(scrollVal: System.UITypes.TScrollStyle);
    procedure RedrawGrid;
    procedure SetColumnAttributes; override;
    procedure DrawCell(ACol, ARow: Longint; ARect: TRect;
      AState: TGridDrawState); override;
    procedure HideControls; override; { InfoPower documented method }
    function MouseCoord(X, Y: integer): TGridCoord;
    { InfoPower documented method }
    // procedure SetControlType(AFieldName: string;
    // AComponentType: TwwFieldControlType;
    // AParameters: string; AAlwaysPaints: boolean = False);
    procedure SetControlType(AFieldName: string;
      AComponentType: TwwFieldControlType; AParameters: string);
    // ; AAlwaysPaints: boolean = False);
    procedure RefreshDisplay; { InfoPower documented method }
    procedure SortSelectedList; { IP 2 documented method }
    procedure RestoreDesignSelected;

    constructor Create(AOwner: TComponent); override;
    destructor Destroy; override;
    procedure ColEnter; override; { 3/29/97 }

    property DateTimePicker: TwwDBCustomDateTimePicker read GetDateTimePicker;
    property InplaceEditor; { Support in-cell editing events }
    property ColWidths;
    property GridLineWidth;
    property Canvas;
    property SelectedList: TwwBookmarkList read Bookmarks;
    property TabStops;
    Procedure AddDependent(value: TComponent);
    Procedure RemoveDependent(value: TComponent);
    procedure ApplySelected; override;
    procedure SetOrder(AFieldName: string; AClientCursor: Boolean);

  published
    property DisableThemes: Boolean read FDisableThemes write FDisableThemes
      default False;
    property DittoAttributes;
    property DisableThemesInTitle;
    property LineStyle;
    property ControlInfoInDataset;
    property ControlType;
    property PictureMaskFromDataSet;
    property PictureMasks;
    property RegexMasks;
    property Selected;
    property MemoAttributes: TwwMemoAttributes read FMemoAttributes
      write SetwwMemoAttributes default [mSizeable, mWordWrap];
    property IniAttributes: TwwIniAttributes read FIniAttributes
      write FIniAttributes;

    property TitleColor: TColor read GetTitleColor write SetTitleColor;

    property OnMemoOpen: TwwMemoOpenEvent read FOnMemoOpen write FOnMemoOpen;
    property OnMemoClose: TwwMemoCloseEvent read FOnMemoClose
      write FOnMemoClose;
    property OnMultiSelectAllRecords: TwwSelectAllRecordEvent
      read FOnSelectAllRecords write FOnSelectAllRecords;
    property OnMultiSelectRecord: TwwSelectRecordEvent read FOnSelectRecord
      write FOnSelectRecord;
    property OnLeftColChanged;
    property OnRowChanged;
    property OnCellChanged;

    property FixedCols: integer read GetFixedCols write SetFixedCols;
    property ShowHorzScrollBar;
    property ShowVertScrollBar;
    property EditControlOptions;
    property IndicatorButton;

    property Anchors;
    property BiDiMode;
    property ParentBiDiMode;
    property Align;
    property BorderStyle;
    property Color;
    property Constraints;
    property Ctl3D;
    property DataSource: TDataSource read GetDataSource write SetDataSource;
    property DefaultDrawing;
    property DragCursor;
    property DragMode;
    property EditCalculated;
    property Enabled;
    property Font;
    property ImeMode;
    // : TImeMode read getImeMode write setImeMode default imDontCare;
    property ImeName; // : string read getImeName write setImeName;
    property KeyOptions;
    property MultiSelectOptions;
    property Options;
    property ParentColor;
    property ParentCtl3D;
    property ParentFont;
    property ParentShowHint;
    property PopupMenu;
    property ReadOnly;
    property RowHeightPercent;
    property ShowHint;
    property TabOrder;
    property TabStop;
    property TitleAlignment;
    property TitleFont;
    property TitleLines;
    property TitleButtons;
    property UseTFields;
    property Visible;
    property LoadAllRTF: Boolean read FLoadAllRTF write FLoadAllRTF
      default False;
    property LineColors;
    property TitleMenuAttributes;

    property OnCalcCellColors;
    property OnCalcTitleAttributes;
    property OnColWidthChanged;
    property OnDrawGroupHeaderCell;
    property OnTitleButtonClick;
    property OnColEnter;
    property OnColExit;
    property OnDrawDataCell;
    property OnDblClick;
    property OnDragDrop;
    property OnDragOver;
    property OnEndDrag;
    property OnStartDrag; // 5/4/00 - publish OnStartDrag property
    property OnEnter;
    property OnExit;
    property OnKeyDown;
    property OnKeyPress;
    property OnKeyUp;
    property OnCreateHintWindow;

    property OnBeforeMenuSort;
    property OnAfterMenuSort;
    property OnBeforeMenuGroup;
    property OnBeforeMenuClearGroup;
    property OnBeforeMenuColumnRemove;
    property OnBeforeMenuColumnAdd;
    property OnBeforeMenuGetFilterRange;  // InfoPower 2012
    property OnBeforeMenuGetFilterValue;  // InfoPower 2012

    property OnAddSelectColumn;
    property OnCanSort;
    property OnCanFilter;
    property OnCanGroup;
    property OnQuerySortField;
    property OnCanShowTitleDropDown;
    property OnPopupTitleDropDown;
    property OnInitSelectColumnsDialog;

    property OnCanShowCustomControl;
    property OnMouseDown;
    property OnMouseMove;
    property OnMouseUp;
{$IFDEF wwDELPHI2010Up}
    property OnGesture;
{$ENDIF}
    property IndicatorColor stored False; // obsolete property
    property IndicatorIconColor;
    property OnCheckValue;
    property OnColumnMoved;
    property OnTopRowChanged;
    property OnCalcTitleImage;
    property OnDrawFooterCell;
    property OnDrawTitleCell;
    property OnFieldChanged;
    property OnUpdateFooter;
    property OnBeforePaint;
    property ImageList;
    property TitleImageList;
    property FooterColor;
    property FooterCellColor;
    property FooterHeight;
    property DragVertOffset;
    property PadColumnStyle;

    property OnURLOpen;
    property HideAllLines;
    property GroupFieldName;
    property OnCreateDateTimePicker: TwwCreateDTPEvent
      read FOnCreateDateTimePicker write FOnCreateDateTimePicker;

    property ExportOptions: TwwExportOptions read FExportOptions
      write FExportOptions;
    property OnExportField: TwwExportFieldEvent read FOnExportField
      write FOnExportField;
    property OnExportSYLKFormat: TwwExportSYLKFormatEvent
      read FOnExportSYLKFormat write FOnExportSYLKFormat;
    property OnExportFormat: TwwExportFormatEvent
      read FOnExportFormat write FOnExportFormat;
    property PaintOptions;
    property OnBeforeDrawCell;
    property OnAfterDrawCell;
    property OnDitto;

    property Touch;

    {$ifdef wwDELPHI2013Up}
    property StyleElements;
    property DoubleBuffered;
    property ParentDoubleBuffered;
    {$endif}

  end;

implementation

uses wwdbcomb,
{$IFDEF wwDelphi6Up}
  variants,
{$ENDIF}
  wwintl,
{$IFNDEF wwDelphi6Up}
  filectrl,
{$ENDIF}
{$IFNDEF GRIDESSENTIALS}
  wwcheckbox,
  wwradiogroup,
{$ENDIF}
  Clipbrd, typinfo;

const
  wwCRLF = #13#10;
  {
    function Find(Grid: TwwDBGrid;
    const Item: TBookmark; var Index: Integer): Boolean;
    var
    L, H, I, C: Integer;
    FList: TList;

    function Compare(const Item1, Item2: TBookmark): Integer;
    begin
    with Grid.Datalink.Datasource.Dataset do
    Result := CompareBookmarks(Item1, Item2);
    end;

    begin
    FList:= Grid.SelectedList;
    Result := False;
    L := 0;
    H := FList.Count - 1;
    while L <= H do
    begin
    I := (L + H) shr 1;
    C := Compare(FList[I], Item);
    if C < 0 then L := I + 1 else
    begin
    H := I - 1;
    if C = 0 then
    begin
    Result := True;
    L := I;
    end;
    end;
    end;
    Index := L;
    end;
  }

type
  TRichProtectClass = class(TObject)
    procedure RichProtectChange(Sender: TObject; StartPos, EndPos: integer;
      var AllowChange: Boolean);
  end;

function sameRect(rect1, rect2: TRect): Boolean;
begin
  result := (rect1.left = rect2.left) and (rect1.right = rect2.right) and
    (rect1.top = rect2.top) and (rect1.bottom = rect2.bottom);
end;

class constructor TwwDBGrid.Create;
begin
  TCustomStyleEngine.RegisterStyleHook(TwwDBGrid, TwwDBGridStyleHook);
end;

constructor TwwDBGrid.Create(AOwner: TComponent);
begin
  inherited Create(AOwner);

  OrigSelected := Tstringlist.Create;

  FIniAttributes := TwwIniAttributes.Create;
  with FIniAttributes do
  begin
    FileName := '';
    SectionName := '';
    Delimiter := ';;';
    Enabled := False;
    Owner := self;
  end;

{$IFNDEF GRIDESSENTIALS}
  FExportOptions := TwwExportOptions.Create(self);
{$ENDIF}
  FDirtyIni := True;
  redrawingGrid := False;
  initialized := False;
  doneInitControls := False;
  drawingCell := False;

  MemoAttributes := [mSizeable, mWordWrap];
  FFixedCols := 0;
  inLinkActive := False;

  // Bookmarks:= TList.create;
  Bookmarks := TwwBookmarkList.Create(self);

  FDependentComponents := Tlist.Create;
  SelectedRecordList := Tstringlist.Create;

  GridIsLoaded := False;
  AlwaysShowControls := False;
  CurrentCustomEdit := Nil;
  VirtualView := True;
  { 3/11/98 - Auto-invalidate if grid rowcount or colcount changes }

  Patch := VarArrayCreate([0, 13], varVariant);
  Patch[0] := False; { 9/2/98 - True means the FieldChanged was already
    called. Used by TwwDBCustomDBGrid }
  Patch[1] := False;
  { If True, then bypass code to workaround Delphi 4 showing bug }
  Patch[2] := 0; { 12/7/98 - Contains SizingColumn when resizing a column }
  Patch[3] := False;
  Patch[4] := False;
  { If true, then Hidecontrols called after inheritd ColExit }
  { This would cause the control's OnExit event to be called after
    the OnColExit event }
  Patch[5] := False; { 1/9/2002 - Added for backwards compatibility when
    exiting the grid error messages from onvalidate were not displayed.
    Using a Patch variable to provide solution while studying impact. }
  Patch[6] := 0; // Prev Col for CellChanged
  Patch[7] := 0; // Prev Row for CellChanged
  Patch[8] := False; // Not in resizing
  Patch[9] := True;
  // Set to false to use new optimized multi-select behavior (sorted list)
  Patch[10] := False; // InMouseDown
  Patch[11] := False;
  // 4/30/09 - set to true for dgAlwaysShowCellHint - hints show even if text fits in cell
  Patch[12] := False;  // 2/11/11 - Set to true to use old behavior of going to wrong record when user
                       // clicks on row after unmodified record
  Patch[13]:= false;
end;

destructor TwwDBGrid.Destroy;
var
  i: integer;
begin
  if FIniAttributes.Enabled then
    try
      SaveToIniFile;
      FIniAttributes.Enabled := False;
      // 10/25/02 - Don't need to save anymore in linkactive
    finally
    end;

  OrigSelected.Free;

  for i := 0 to Bookmarks.count - 1 do
  begin
    if (DataSource <> nil) and (DataSource.dataset <> nil) then
      DataSource.dataset.FreeBookmark(TBookmark(Bookmarks.items[i]))
      // 1/21/05 - Call FreeMem if datasource is already released
    else
    begin
{$IFNDEF wwDelphi2008Up}
      FreeMem(TBookmark(Bookmarks.items[i]));
      // No longer need to free as TBookmark is TBytes now (not a pointer)
{$ENDIF}
    end;
  end;
  Bookmarks.Free;
  Bookmarks := Nil;

  { 5/13/98 - Free shift select bookmark }
  if ShiftSelectMode and (ShiftSelectBookmark <> Nil) then
  begin
    ShiftSelectMode := False;

    if (DataSource <> nil) and (DataSource.dataset <> nil) then
    begin
      DataSource.dataset.FreeBookmark(ShiftSelectBookmark);
    end
    // 1/21/05 - Call FreeMem if datasource is already released
{$IFNDEF wwDelphi2008Up}
    else
      FreeMem(ShiftSelectBookmark);
{$ENDIF}
  end;

  for i := 0 to FDependentComponents.count - 1 do
  begin
    TwwCheatCastNotify(FDependentComponents[i]).notification(self, opRemove);
  end;
  FDependentComponents.Free;

  // for i:= 0 to FCustomControls.count-1 do TwwCustomControlItem(FCustomControls[i]).Free;
  // FCustomControls.Free;
  // if FCustomControls<>nil then
  // FreeMem(FCustomControls);
  // FCustomControls:=nil;

  SelectedRecordList.Free;

  FIniAttributes.Free;
{$IFNDEF GRIDESSENTIALS}
  FExportOptions.Free;
{$ENDIF}
  inherited Destroy;
end;

procedure TwwDBGrid.SetFixedCols(val: integer);
begin
  if (csDesigning in ComponentState) then
  begin
    if ((DataSource <> Nil) and (DataSource.dataset <> Nil) and
      (DataSource.dataset.active) and (val + xIndicatorOffset >= ColCount)) or
      (val < 0) then
    begin
      MessageDlg('Invalid value for FixedCols', mtWarning, [mbok], 0);
      exit;
    end
  end;

  if wwGridEssentials then
  begin
    if val > 0 then
      DisplayProfessionalVersionOnlyMessage(self,
        'Fixed columns only supported in InfoPower Professional');
  end;

  FFixedCols := val;
  LayoutChanged;
end;

Function TwwDBGrid.GetFixedCols: integer;
begin
  result := FFixedCols;
end;

Function TwwDBGrid.GetDataSource: TDataSource;
begin
  if ( inherited DataSource) is TDataSource then
    result := ( inherited DataSource) as TDataSource
  else
    result := Nil;
end;

Procedure TwwDBGrid.SetDataSource(val: TDataSource);
begin
  Inherited DataSource := val;
end;

procedure TwwDBGrid.SetwwMemoAttributes(sel: TwwMemoAttributes);
begin
  FMemoAttributes := sel;
  RedrawGrid;
end;

Function TwwDBGrid.GetTitleColor: TColor;
begin
  result := inherited TitleColor;
end;

procedure TwwDBGrid.SetTitleColor(sel: TColor);
begin
  if sel <> inherited TitleColor then
  begin
    inherited TitleColor := sel;
    LayoutChanged;
  end
end;

{ Function TwwDBGrid.GetDataLineColor: TColor;
  begin
  result:= inherited DataLineColor;
  end;

  procedure TwwDBGrid.SetDataLineColor(sel: TColor);
  begin
  if sel<>inherited TitleColor then
  begin
  inherited DataLineColor:= sel;
  LayoutChanged;
  end
  end;
}
procedure TwwDBGrid.SetScrollBars(scrollVal: System.UITypes.TScrollStyle);
begin
  ScrollBars := scrollVal;
end;

procedure TwwDBGrid.RedrawGrid;
var
  i: integer;
  haveCalculatedField: Boolean;
  // form: TComponent;
begin
  if redrawingGrid then
    exit;
  if Selected = Nil then
    exit;
  if (DataSource = Nil) then
    exit;
  if (DataSource.dataset = Nil) then
    exit;
  if not(wwDataSet(DataSource.dataset)) then
  begin
    MessageDlg('wwGrid must use an InfoPower compatible TDataSet.',
      mtInformation, [mbok], 0);
    DataSource := Nil;
    exit;
  end;

  ResetProportionalWidths;

  if dgProportionalColResize in Options then
    LayoutChanged; // resets number of columns

  InitControls;

  try
    DataSource.dataset.disableControls;
    CurrentCustomEdit := nil;
    { 4/9/98 - Clear currentcustomedit when table is made active }
    if ControlInfoInDataset then
      with DataSource do
        wwDataSetRemoveObsolete(dataset, wwGetLookupFields(dataset),
          wwGetLookupLinks(dataset), wwGetControlType(dataset));
    // if ecoSearchOwnerForm in FEditControlOptions then form:= wwGetOwnerFrameOrForm(self)
    // if ecoSearchOwnerForm in FEditControlOptions then form:= wwGetOwnerForm(self)
    // else form:= GetParentForm(self) as TCustomForm; { -ksw (6/22/98) }

    // if ControlInfoInDataSet then
    // wwDataSetRemoveObsoleteControls(form, datasource.dataset);

    redrawingGrid := True;

    { If no selection then don't override }
    { If running program and have calculated field then don't change visibility }
    if Selected.count > 0 then
    begin
      haveCalculatedField := False;
      for i := 0 to DataSource.dataset.fieldCount - 1 do
      begin
        if (DataSource.dataset.fields[i].calculated) then
          haveCalculatedField := True;
      end;

      if (csDesigning in ComponentState) or (not haveCalculatedField) then
      begin
        ApplySelected;
      end;

    end;

    { InitControls could change row height so update }
    if (csDesigning in ComponentState) then
    begin
      if UseTFields then
        UpdateSelectedProperty;
      UpdateRowCount;
    end;

  finally
    DataSource.dataset.enableControls;
    redrawingGrid := False;
    initialized := True;
  end;
end;

procedure TwwDBGrid.LinkActive(value: Boolean);
begin
  inLinkActive := True;
  Bookmarks.LinkActive(value);

  if csDestroying in ComponentState
  then { 9/30/99 - Added to save when using datamodule }
    if FIniAttributes.Enabled then
      SaveToIniFile;

  if (FDateTimePicker <> Nil) then
  begin
    wwSetControlDataField(FDateTimePicker, ''); { 8/5/98 }
  end;
  inherited LinkActive(value);
  if value then
  begin
    RedrawGrid;
  end;
  inLinkActive := False;
end;

{ Don't allow insertion of record to cancel if combo is showing. }
Function TwwDBGrid.AllowCancelOnExit: Boolean;
begin
  result := True;
end;

procedure TwwDBGrid.DoExportSYLKFormat(Grid: TwwDBGrid; AField: TField;
  var SYLKFormat: String);
begin
  if Assigned(FOnExportSYLKFormat) then
    FOnExportSYLKFormat(self, AField, SYLKFormat);
end;

procedure TwwDBGrid.DoExportFormat(Grid: TwwDBGrid; AField: TField;
  var Format: String);
begin
  if Assigned(FOnExportFormat) then
    FOnExportFormat(self, AField, Format);
end;

procedure TwwDBGrid.DoExportField(Grid: TwwDBGrid; AField: TField;
  var Accept: Boolean);
begin
  if Assigned(FOnExportField) then
    FOnExportField(self, AField, Accept);
end;

procedure TwwDBGrid.DoExit;
begin
  HideControls;
  HideEditor;
  inherited DoExit;
end;

procedure TwwDBGrid.UpdateSelectedProperty;
var
  pf: TCustomForm;
begin
  if redrawingGrid then
    exit;
  if Selected = Nil then
    exit;
  if DataSource = Nil then
    exit;
  if DataSource.dataset = Nil then
    exit;

  if (initialized) then
  begin
    pf := GetParentForm(self);
    if pf = nil then
      exit;

    with pf do
    begin
      if (Designer <> Nil) then
      begin
        wwDataSetUpdateSelected(DataSource.dataset, Selected);
      end;
    end;
  end;
end;

procedure TwwDBGrid.ApplySelected;
begin
  if Selected = Nil then
    exit;

  // Reset OrigColWidths
  ResetProportionalWidths;

  if UseTFields then
  begin
    if DataSource = Nil then
      exit;
    if DataSource.dataset = Nil then
      exit;
    wwDataSetUpdateFieldProperties(DataSource.dataset, Selected);
    if Selected.count > 0 then
      LayoutChanged; // 4/8/01 - If selected was changed, we need to refresh in
    // case scrollbars disappear - Otherwise rowcount is 1 too small
  end
  else
  begin
    RefreshDisplay;
  end;

  RefreshActiveControl; { 2/19/00 - Update control information }
end;

procedure TwwDBGrid.AdjustLeftCol;
var
  i: integer;
begin
  if ( inherited FixedCols <> FFixedCols + xIndicatorOffset) and
    (DataSource <> Nil) and (DataSource.dataset <> nil) and
    (DataSource.dataset.active) then
  begin
    if (FFixedCols + xIndicatorOffset < ColCount) and (FFixedCols >= 0) then
    begin
      inherited FixedCols := FFixedCols + xIndicatorOffset;
      if (dgFixedEditable in Options) and (LeftCol = inherited FixedCols) then
        SelectedIndex := 0;

      for i := 0 to FFixedCols + xIndicatorOffset do
        TabStops[i] := False;
    end
  end;
end;

procedure TwwDBGrid.SetColumnAttributes;
var
  i: integer;
  customEdit: TWinControl;
  ControlType, Parameters: wwSmallString;
begin
  if UseTFields and (not inLinkActive) then
    UpdateSelectedProperty;

  { Update fixed columns if changed }
  AdjustLeftCol;
  { if (inherited FixedCols<> FFixedCols+xIndicatorOffset) and
    (datasource<>Nil) and (datasource.dataset<>nil) and
    (datasource.dataset.active) then
    begin
    if (FFixedCols+xIndicatorOffset<ColCount) and (FFixedCols>=0) then
    begin
    inherited FixedCols:= FFixedCols + xIndicatorOffset;
    if (dgFixedEditable in Options) and (LeftCol=inherited FixedCols) then
    SelectedIndex:= 0;

    for i:= 0 to FFixedCols+xIndicatorOffset do TabStops[i]:= False;
    end
    end;
  }
  inherited SetColumnAttributes;

  { Enable tabstops for calculated columns with a lookup control attached to it }
  if not DataLink.active then
    exit;

  for i := 0 to fieldCount - 1 do
  begin
    with fields[i] do
      if calculated then
      begin
        if isCustomEditCell(i + xIndicatorOffset, 1, customEdit) then
        begin
          TabStops[i + xIndicatorOffset] := True
        end
        else if isSelectedCheckbox(i + xIndicatorOffset) then
        begin
          TabStops[i + xIndicatorOffset] := True;
        end
        else
        begin
          GetControlInfo(FieldName, ControlType, Parameters);
          if (ControlType = 'Bitmap') or (ControlType = 'ImageIndex') then
            TabStops[i + xIndicatorOffset] := True;
        end
      end
  end;

  RefreshActiveControl;
  // 1/30/01 - Need to call or first column can have problems if datetime field
end;

{
  Function TwwDBGrid.IsSelectedCheckbox(ACol: integer): boolean;
  var tempField: TField;
  begin
  if isCheckBox(ACol, 1, dummy1, dummy2) then
  begin
  tempField:=GetColField(dbCol(ACol));
  if tempField=nil then result:= False
  else result:= (lowercase(tempField.fieldName)='selected');
  end
  else result:= False;
  end;
}
function TwwDBGrid.MouseCoord(X, Y: integer): TGridCoord;
{$IFDEF wwdelphi4up}
var
  Loc: TRect;
  i: integer;
{$ENDIF}
begin
  result := inherited MouseCoord(X, Y);

{$IFDEF wwdelphi4up}  { 10/27/98 }
  if UseRightToLeftAlignment and (FixedCols > 0) then
  begin
    for i := 0 to ColCount - 1 do
    begin
      Loc := CellRect(i, result.Y);
      if Loc.left < X then
      begin
        result.X := i;
        break;
      end
    end
  end;
{$ENDIF}
end;

procedure TwwDBGrid.HideControls;
var
  TriedToSetFocus: Boolean;
begin
  if (CurrentCustomEdit <> Nil) and CurrentCustomEdit.Visible and
    (CurrentCustomEdit.parent = self)
  then { 5/1/97 - Only hide if grid is parent of control }
  begin
    TriedToSetFocus := False;
    if (CurrentCustomEdit.focused) or
      (GetParent(GetFocus) = CurrentCustomEdit.handle) then
    // i.e. inplaceeditor of datainspector
    begin
      CurrentCustomEdit.left := clientwidth + 10;
      TriedToSetFocus := True;
      setFocus;
    end
    else if (screen.activecontrol <> nil) and
      (screen.activecontrol.parent = CurrentCustomEdit) then
    begin
      CurrentCustomEdit.left := clientwidth + 10;
      TriedToSetFocus := True;
      setFocus;
    end;

    // 4/16/2002 - If an exception is raised in the onvalidate event of the tfield for a custom control
    // then the focus could still be the custom edit and this means the grid failed to
    // get focus and thus currentcustomedit should not be set to not visible.
    // 2/20/05 - Check currentCustomEdit for nil
    if focused or (not TriedToSetFocus) and (CurrentCustomEdit <> nil) then
    begin
      SkipErase := True;
      CurrentCustomEdit.Visible := False;
      SkipErase := False;
    end
    else
      SysUtils.abort;
    // Fail to set the focus so an exception was likely thrown, don't hide control
  end
end;

function TwwDBGrid.IsActiveControl: Boolean;
var
  H: Hwnd;
  ParentForm: TCustomForm;
begin
  result := False;
  ParentForm := GetParentForm(self);
  if Assigned(ParentForm) then
  begin
    if (ParentForm.activecontrol = self) then
      result := True
  end
  else
  begin
    H := GetFocus;
    while IsWindow(H) and (result = False) do
    begin
      if H = WindowHandle then
        result := True
      else
        H := GetParent(H);
    end;
  end;
end;

Procedure TwwDBGrid.Paint;
var
  tempFldName: string;
  index: integer;
begin
  RefreshBookmarkList;

  if not ShowVertScrollBar then
    SetScrollRange(self.handle, SB_VERT, 0, 0, False);

  { combo field has disappeared so hide combo control }
  if (CurrentCustomEdit <> Nil) and CurrentCustomEdit.Visible and
    (DataSource <> Nil) and (DataSource.dataset <> nil) and
    (DataSource.dataset.active) then
  begin
    tempFldName := wwGetControlDataField(CurrentCustomEdit);
    if tempFldName <> '' then
    begin
      if UseTFields then
      begin
        if (DataSource.dataset.findField(tempFldName) = Nil) or
          not DataSource.dataset.fieldByName(tempFldName).Visible then
          HideControls;
      end
      else
      begin
        if not wwFindSelected(Selected, tempFldName, index) then
          HideControls;
      end
    end
  end;
  MakeCustomControlVisible := False;

  inherited Paint;
  // ShowCurrentControl;
end;

Function TwwDBGrid.CellColor(ACol, ARow: integer): TColor;
begin
  if ACol > FFixedCols then
    result := Color
  else
    result := TitleColor;
end;

procedure TwwDBGrid.DrawCell(ACol, ARow: Longint; ARect: TRect;
  AState: TGridDrawState);
var
  value: string;
  OldActive: integer;
  EditableCell: Boolean;
  lookupControl: TWinControl;
  tempFld: TField;
  tempRect, OrigRect: TRect;

  procedure GetAbsolutePos(var ALeft, ATop: integer);
  var
    curObject: TWinControl;
    lastObject: TWinControl;
  begin
    curObject := self;
    lastObject := self;
    ALeft := curObject.left;
    ATop := curObject.top;

    while (curObject <> Nil) do
    begin
      curObject := curObject.parent;
      if curObject is TCustomForm then
        break;

      if curObject.containsControl(lastObject) then
      begin
        ALeft := ALeft + curObject.left;
        ATop := ATop + curObject.top;
      end;
    end;
  end;

  function IsEditableCell(DataCol, DataRow: integer; const value: string;
    AState: TGridDrawState): Boolean;
  begin
{$IFNDEF GRIDESSENTIALS}
    result := (gdSelected in AState) or
      ((DataRow = dbRow(row)) and (PaintOptions.ActiveRecordColor <> clNone));
{$ELSE}
    result := (gdSelected in AState);
{$ENDIF}
    if result then
    begin
      if not((dgAlwaysShowSelection in Options) or
        (FFocused and not IsDropDownGridFocused)) then
        result := False;
    end;
  end;

begin
  if drawingCell then
    exit; { Avoid recursion }
  drawingCell := True;

  if (row = ARow) and (col = ACol) and
  // ShouldShowCustomControls and
    isCustomEditCell(ACol, ARow, lookupControl) and
    (ShouldShowCustomControls or (wwIsClass(lookupControl,
    'TwwExpandButton'))) then
  begin
    CurrentCustomEdit := lookupControl;
    MakeCustomControlVisible := True;

    with Canvas do
    begin
      EditableCell := IsEditableCell(ACol, dbRow(ARow), value, AState);

      if (gdSelected in AState) and (not(csDesigning in ComponentState)) then
      begin
        if (CurrentCustomEdit <> Nil) and (CurrentCustomEdit.parent = self) and
          (ValidParentForm(self).activecontrol = CurrentCustomEdit) then
        begin
          { 7/19/97 - Avoid flicker, previously checked BoundsRect }
          tempRect := ARect;

          if AdjustBoundsRect(CurrentCustomEdit) then
            InflateRect(tempRect, -1, -1);

          if (CurrentCustomEdit.BoundsRect.left <> tempRect.left) or
            (CurrentCustomEdit.BoundsRect.right <> tempRect.right) then
          begin
            if Columns[dbCol(ACol)].DisableSizing then
            begin
              if tempRect.right - tempRect.left < CurrentCustomEdit.Width then
                tempRect.right := tempRect.left + CurrentCustomEdit.Width;
              if tempRect.bottom - tempRect.top < CurrentCustomEdit.Height then
                tempRect.bottom := tempRect.top + CurrentCustomEdit.Height;
              if tempRect.top + CurrentCustomEdit.Height >
                parent.ClientHeight then
              begin
                tempRect.top := ARect.bottom - CurrentCustomEdit.Height;
                tempRect.bottom := ARect.bottom;
              end;
            end;

{$IFDEF wwDelphi4Up}
            if (Canvas.CanvasOrientation = coRightToLeft)
            then { 9/25/98 - Support bidi mode }
            begin
              tempRect.right := clientwidth - ARect.left;
              tempRect.left := tempRect.right - (ARect.right - ARect.left) + 1;
            end;
{$ENDIF}
            CurrentCustomEdit.BoundsRect := tempRect;
          end

        end
        else if EditableCell and
          (ValidParentForm(self).activecontrol = self) then
        begin
          { Clear buffer if changing rows so old value isn't drawn at all }
          OldActive := DataLink.ActiveRecord;
          value := '';
          try
            DataLink.ActiveRecord := dbRow(ARow) + RowOffset;

            if wwIsClass(CurrentCustomEdit.classType,
              'TwwDBCustomLookupCombo') then
            begin
              tempFld := GetColField(dbCol(ACol));
              if tempFld <> Nil then
                value := tempFld.asString
            end
            else
              value := WideGetFieldValue(dbCol(ACol));

          finally
            DataLink.ActiveRecord := OldActive;
          end;

          { This is here for screen repainting reasons, otherwise screen is painted wrong then right. }
          if not(wwIsClass(CurrentCustomEdit.classType, 'TCustomRichEdit')) and
            not wwIsClass(CurrentCustomEdit.classType, 'TfcTreeCombo') and
          // 10/28/01 - Otherwise mapped values not respected
            (CurrentCustomEdit is TCustomEdit) then // 1/29/01
            TEdit(CurrentCustomEdit).Text := value;

          if (CurrentCustomEdit.parent <> self) then
          begin
            CurrentCustomEdit.parent := self;
            wwSetBoolean(CurrentCustomEdit, 'ctl3D', False);
            // TEdit(currentCustomEdit).ctl3d:= False;
            if not wwIsClass(CurrentCustomEdit.classType,
              'TwwDataInspector') then
              // TEdit(currentCustomEdit).BorderStyle:= bsNone;
              wwSetBorder(CurrentCustomEdit, False);
          end;

          { 1/25/98 - Moved to after setting ctl3d and borderstyle }
          tempRect := ARect;
          tempRect := CellRect(col, row);
          OrigRect := tempRect;
          if AdjustBoundsRect(CurrentCustomEdit) then
            InflateRect(tempRect, -1, -1);
          if not sameRect(CurrentCustomEdit.BoundsRect, tempRect) then
          begin
            if Columns[dbCol(col)].DisableSizing then
            begin
              if tempRect.right - tempRect.left < CurrentCustomEdit.Width then
                tempRect.right := tempRect.left + CurrentCustomEdit.Width;
              if tempRect.bottom - tempRect.top < CurrentCustomEdit.Height then
                tempRect.bottom := tempRect.top + CurrentCustomEdit.Height;
              if tempRect.top + CurrentCustomEdit.Height >
                parent.ClientHeight then
              begin
                tempRect.top := OrigRect.bottom - CurrentCustomEdit.Height;
                tempRect.bottom := OrigRect.bottom;
              end;
            end;

{$IFDEF wwDelphi4Up}
            if (Canvas.CanvasOrientation = coRightToLeft)
            then { 9/25/98 - Support bidi mode }
            begin
              tempRect.right := clientwidth - ARect.left;
              tempRect.left := tempRect.right - (ARect.right - ARect.left) + 1;
            end;
{$ENDIF}
            CurrentCustomEdit.BoundsRect := tempRect;
          end;
          drawingCell := False;
          exit;

        end
      end;
    end;
  end;

  inherited DrawCell(ACol, ARow, ARect, AState);

  { Make cells 3D style }
  // if DrawCellInfo.DefaultDrawHorzLines then
  { if DefaultDrawing then } Draw3DLines(ARect, ACol, ARow, AState);

  drawingCell := False;

end;

{ Override so checkboxes in grid don't show underlying text }
function TwwDBGrid.IsWWControl(ACol, ARow: integer): Boolean;
var
  fldName: string;
  ControlType, Parameters, tempName: string;
  Apos: integer;
  IsCustomControl: Boolean;
  IsDateTime: Boolean;
begin
  result := False;
  if not isValidCell(ACol, ARow) then
    exit;
  fldName := DataLink.fields[dbCol(ACol)].FieldName;
  GetControlInfo(fldName, ControlType, Parameters);
  IsCustomControl := IsWWEditControl(ControlType);

  //
  // 3/18/06 - Support TimeStamp type for Auto - DateTimePicker
  IsDateTime := (DataLink.fields[dbCol(ACol)] is TDateTimeField) or
    ((DataLink.fields[dbCol(ACol)] <> nil) and
    (DataLink.fields[dbCol(ACol)].DataType in [ftDate, ftDateTime, ftTimeStamp,
    ftTime]));

  result := (ControlType = 'Bitmap') or (ControlType = 'ImageIndex') or
    (ControlType = 'CheckBox') or IsCustomControl or
    (not(ecoDisableDateTimePicker in EditControlOptions) and { Fix - 8/13/98 }
    IsDateTime);
  // (DataLink.fields[dbCol(ACol)] is TDateTimeField));

  if IsCustomControl then
  begin
    Apos := 1;
    tempName := strGetToken(Parameters, ';', Apos);
    if GetComponent(tempName) = nil then
      result := False;
  end;

end;

function TwwDBGrid.CanEditShow: Boolean;
begin
  result := inherited CanEditShow;

  if result then
  begin
    if (dgAlwaysShowEditor in Options) and not focused and
      (InplaceEditor <> Nil) and not InplaceEditor.Visible then
      result := False { (ValidParentForm(Self).ActiveControl = Self) }
    else if IsWWControl(col, row) then
      result := False
    else if isMemoField(col, row) then
      result := False;
  end
end;

{ Update checkbox }
procedure TwwDBGrid.SetFieldValue(ACol: integer; val: string);
begin
  if (ACol >= 0) and DataLink.active and (ACol < DataLink.fieldCount) then
    DataLink.fields[ACol].Text := val;
end;

function TwwDBGrid.IsRichEditCell(col, row: integer;
  var customEdit: TWinControl): Boolean;
var
  fldName: string;
  i: integer;
  ControlType: TStrings;
  Apos: integer;
begin
  result := False;
  if not isValidCell(col, row) then
    exit;
  fldName := DataLink.fields[dbCol(col)].FieldName;

  { 8/14/97 - Optimized logic to speed painting of grid }
  ControlType := GetControlType;
  for i := 0 to ControlType.count - 1 do
  begin
    Apos := 1;
    if strGetToken(ControlType[i], ';', Apos) <> fldName then
      continue;
    if strGetToken(ControlType[i], ';', Apos) = 'RichEdit' then
    begin
      customEdit := GetComponent(strGetToken(ControlType[i], ';', Apos));
      result := True;
      break;
    end
  end
end;

// procedure TwwDBGrid.SetControlType(AFieldName: string; AComponentType: TwwFieldControlType;
// AParameters: string; AAlwaysPaints: boolean = False);
procedure TwwDBGrid.SetControlType(AFieldName: string;
  AComponentType: TwwFieldControlType; AParameters: string);
var
  componentTypeStr: string;
  customEdit: TWinControl;
  TempComponent: TComponent;
begin
  case AComponentType of
    fctNone, fctField:
      componentTypeStr := '';
    fctURLLink:
      componentTypeStr := 'URL-Link';
    fctCustom, fctComboBox, fctLookupCombo, fctRichEdit:
      begin
        { 9/4/97 - fctRichEdit same path as customEdit }
        componentTypeStr := WW_DB_EDIT;
        if AComponentType = fctRichEdit then
          componentTypeStr := 'RichEdit';

        customEdit := GetComponent(AParameters);
        { 1/22/97 - Change customEdit properties immediately }
        if (customEdit <> Nil) and
          (ShouldShowCustomControls or (wwIsClass(customEdit,
          'TwwExpandButton'))) then
        // (not (ecoDisableCustomControls in EditControlOptions)) then
        begin
          customEdit.Visible := False;
          customEdit.parent := self;

          wwSetControlDataSource(customEdit, DataSource);
          wwSetControlDataField(customEdit, AFieldName);

          { !!!!! Cheating casts to make protected properties public }
          wwSetBoolean(customEdit, 'ctl3D', False);
          // TwwDBEdit(customEdit).ctl3d:= False;
          if AComponentType <>
            fctRichEdit then { 9/18/99 - Don't set font if richedit }
            TwwDBEdit(customEdit).Font := self.Font;
          if not wwIsClass(customEdit.classType, 'TwwDataInspector') then
            wwSetBorder(customEdit, False);
          // TwwDBEdit(customEdit).BorderStyle:= bsNone;

          // Don't support scrollbars for inspector as then text gets cutoff in csPaintCopyState
          // If find way to support csPaintCopy, then we can remove this restriction
          if wwIsClass(customEdit.classType, 'TwwDataInspector') then
            TDrawGrid(customEdit).ScrollBars := ssNone;
        end
      end;
    fctCheckBox:
      componentTypeStr := 'CheckBox';
    fctBitmap:
      componentTypeStr := 'Bitmap';
    fctImageIndex:
      componentTypeStr := 'ImageIndex';
  else
    componentTypeStr := '';
  end;

  if ControlInfoInDataset then
  begin
    TempComponent := DataSource.dataset;
    if (DataSource.dataset = nil) or
      (wwGetControlType(DataSource.dataset) = nil) then
      TempComponent := self;
  end
  else
    TempComponent := self;

  // if AAlwaysPaints then AParameters:= AParameters+';T'
  // else AParameters:= AParameters + ';F';
  wwDataSet_SetControl(TempComponent, AFieldName, componentTypeStr,
    AParameters);
end;

procedure TwwDBGrid.RefreshDisplay;
var
  customEdit: TWinControl;
begin
  doneInitControls := False;
  InitControls;
  LayoutChanged;

  // 8/13/02 - Clear CurrentCustomEdit (this code deemed necessary if user
  // changes control information when changing records).
  // Otherwise if active column is being changed, the inplaceeditor
  // can get disabled
  if not(isCustomEditCell(col, row, customEdit) and (ShouldShowCustomControls or
    wwIsClass(customEdit, 'TwwExpandButton'))) then
    CurrentCustomEdit := nil;
end;

{ Initialize controls to have the following attributes }
{ 1. This grid as parent
  { 2. Visible is False
  { 3. DataSource is this grid's datasource
  { 4. Set field later at time it is shown
}
{ 4/28/97 - Allow the same control to be attached more than once }
procedure TwwDBGrid.InitControls;
var
  i: integer;
  parts: TStrings;
  dbLookupComboBox: TWinControl;
  ControlType: TStrings;
begin
  if (doneInitControls) and not(csDesigning in ComponentState) then
    exit;
  if (DataSource = Nil) then
    exit;
  if (DataSource.dataset = Nil) then
    exit;
  if (csLoading in ComponentState) then
    exit;
  if not GridIsLoaded then
    exit;
  if (ecoDisableCustomControls in EditControlOptions) then
    exit;

  parts := Tstringlist.Create;

  with DataSource do
  begin
    ControlType := GetControlType;
    for i := 0 to ControlType.count - 1 do
    begin
      strBreakapart(ControlType[i], ';', parts);
      if parts.count < 2 then
        continue;

      if IsWWEditControl(parts[1]) or (parts[1] = 'RichEdit') then
      begin
        dbLookupComboBox := GetComponent(parts[2]);
        if (dbLookupComboBox <> Nil) then
        begin
          if (csDesigning in ComponentState) then
            continue;
          dbLookupComboBox.Visible := False;
          dbLookupComboBox.parent := self;
          wwSetControlDataSource(dbLookupComboBox, self.DataSource);
          wwSetControlDataField(dbLookupComboBox, parts[0]);

          { !!!!! Cheating casts to make protected properties public }
          wwSetBoolean(dbLookupComboBox, 'ctl3D', False);
          // TwwDBEdit(dbLookupComboBox).ctl3d:= False;
          TwwDBEdit(dbLookupComboBox).Font := self.Font;
          if not wwIsClass(dbLookupComboBox.classType, 'TwwDataInspector') then
            wwSetBorder(dbLookupComboBox, False);
          // TwwDBEdit(dbLookupComboBox).BorderStyle:= bsNone;

          // Don't support scrollbars for inspector as then text gets cutoff in csPaintCopyState
          // If find way to support csPaintCopy, then we can remove this restriction
          if wwIsClass(dbLookupComboBox.classType, 'TwwDataInspector') then
            TDrawGrid(dbLookupComboBox).ScrollBars := ssNone;

        end
      end
    end;
  end;

  doneInitControls := True;
  parts.Free;
end;

procedure TwwDBGrid.CMCtl3DChanged(var Message: TMessage);
begin
  if csFramed in ControlStyle then
    RedrawGrid;
end;

procedure TwwDBGrid.ColExit;
begin
  if Patch[4] = True then
    inherited ColExit;

  if not(csDesigning in ComponentState) and
    ((ValidParentForm(self).activecontrol = self) or
    (ValidParentForm(self).activecontrol = CurrentCustomEdit))
  then { Hide customedit }
  begin
    if CurrentCustomEdit <> Nil then
    begin
      HideControls;
      CurrentCustomEdit := nil;
    end
  end;

  if Patch[4] = False then
    inherited ColExit;
end;

{ This code necessary to handle when column has changed when the combobox
  has focus }
procedure TwwDBGrid.TopLeftChanged;
var
  Loc: TRect;
  tempRect: TRect;
  Procedure ResetTopRow(val: integer);
  begin
    inTopLeftChanged := True;
    TopRow := val;
    inTopLeftChanged := False;
  end;
  Procedure ResetLeftcol(val: integer);
  begin
    inTopLeftChanged := True;
    LeftCol := val;
    // AdjustLeftCol;
    inTopLeftChanged := False;
  end;

begin
  if inTopLeftChanged then
    exit;

  if dgRowSelect in Options then
  begin { 8/13/96 Fix - Override toprow in case TCustomGrid changed it }
    if (dgTitles in Options) then
    begin
      if (TopRow <> 1) then
        ResetTopRow(1)
    end
    else if (TopRow <> 0) then
      ResetTopRow(0);
  end;

  if dgFixedEditable in Options then
  begin
    // ResetLeftCol(3);;
  end;

  inherited TopLeftChanged;

  if (CurrentCustomEdit <> Nil) then
  begin
    if CurrentCustomEdit.Visible then
    begin
      Loc := CellRect(col, row);
      if IsRectEmpty(Loc) then
        CurrentCustomEdit.Hide
      else
      begin
        tempRect := CellRect(col, row);
        if AdjustBoundsRect(CurrentCustomEdit) then
          InflateRect(tempRect, -1, -1);

        if not sameRect(CurrentCustomEdit.BoundsRect, tempRect) then
        begin
          HideControls;
          Invalidate;
        end
      end
    end
  end;

end;

{ Override so it allows column resizing in design mode even when DefaultFields is True }
procedure TwwDBGrid.CMDesignHitTest(var Msg: TCMDesignHitTest);
var
  yTitleOffset: integer;
begin
  Msg.result := Longint(BOOL(Sizing(Msg.Pos.X, Msg.Pos.Y)));

  if dgTitles in Options then
    yTitleOffset := 1
  else
    yTitleOffset := 0;

  if Msg.result = 0 then
    with MouseCoord(Msg.Pos.X, Msg.Pos.Y) do
      if (X >= xIndicatorOffset) and (Y < yTitleOffset) then
        Msg.result := 1;

  if (Msg.result = 1) and ((DataLink = nil) or not DataLink.active) then
    Msg.result := 0;
end;

type
  CMPBkMkRslt = integer;

const
  CMPLess = -1; { Bkm1 < Bkm2 }
  CMPEql = 0; { BookMarks are exactly the same }
  CMPGtr = 1; { Bkm1 > Bkm2 }
  CMPKeyEql = 2; { Only Bkm1.key_val = Bkm2.key_val }

procedure TwwDBGrid.SelectRecordRange(bkmrk1, bkmrk2: TBookmark);
var
  curBookmark: TBookmark;
  // res: CmpBkmkRslt;
  res: integer;
  ScrollCount, currentRow, moveByCount: integer;
  Accept: Boolean;
  // index: integer;

  Function LessThan(bookmark1, bookmark2: TBookmark): Boolean;
  begin
    res := wwDataSetCompareBookmarks(DataSource.dataset, bookmark1, bookmark2);
    { dbiCompareBookmarks((datasource.dataset as TDBDataSet).handle, bookmark1, bookmark2, res); }
    if CompareBookmarksAltMethod then
      result := integer(res) < 0
    else
      result := integer(res) = CMPLess;
  end;

begin
  if (bkmrk1 = nil) or (bkmrk2 = nil) then
    exit; { 6/11/98 Invalid bookmarks so exit }

  with DataLink.dataset do
  begin
    currentRow := GetActiveRow;

    disableControls;

    try

      if LessThan(bkmrk1, bkmrk2) then
      begin
        RemoveSelected(bkmrk1, bkmrk2);
        { Remove all selected records in range before selecting }
{$IFDEF wwDelphi2009Up}
        curBookmark := DataLink.dataset.bookmark;
{$ELSE}
        curBookmark := GetBookmark;
{$ENDIF}
        { 3/17/99 - Fire event }
        Accept := True;
        if Assigned(FOnSelectRecord) then
          FOnSelectRecord(self, True, Accept);

        if Accept then
          Bookmarks.CurrentRowSelected := True;

        while LessThan(curBookmark, bkmrk2) do
        begin
          Next;
          if Eof then
            break;
{$IFDEF wwDelphi2009Up}
          curBookmark := DataLink.dataset.bookmark;
{$ELSE}
          curBookmark := GetBookmark;
{$ENDIF}
          { 3/17/99 - Fire event }
          Accept := True;
          if Assigned(FOnSelectRecord) then
            FOnSelectRecord(self, True, Accept);

          if Accept then
            Bookmarks.CurrentRowSelected := True;
        end;
        Gotobookmark(bkmrk1);
      end
      else
        try { 12/02/97 - Added try block on gotobookmark to catch exception when bkmrk2 is invalid }
          Gotobookmark(bkmrk2);
          RemoveSelected(bkmrk2, bkmrk1);
          { Remove all selected records in range before selecting }
{$IFDEF wwDelphi2009Up}
          curBookmark := DataLink.dataset.bookmark;
{$ELSE}
          curBookmark := GetBookmark;
{$ENDIF}
          { 3/17/99 - Fire event }
          Accept := True;
          if Assigned(FOnSelectRecord) then
            FOnSelectRecord(self, True, Accept);

          if Accept then
            Bookmarks.CurrentRowSelected := True;
          while LessThan(curBookmark, bkmrk1) do
          begin
            if DataLink.ActiveRecord < VisibleRowCount - 1 then
              DataLink.ActiveRecord := DataLink.ActiveRecord + 1
            else
            begin
              Next;
              if Eof then
                break;
            end;

{$IFDEF wwDelphi2009Up}
            curBookmark := DataLink.dataset.bookmark;
{$ELSE}
            curBookmark := GetBookmark;
{$ENDIF}
            { 3/17/99 - Fire event }
            Accept := True;
            if Assigned(FOnSelectRecord) then
              FOnSelectRecord(self, True, Accept);
            if Accept then
              Bookmarks.CurrentRowSelected := True;

          end
        except
        end;

      FreeBookmark(bkmrk1);
    finally
      enableControls; { Updates display controls }
    end;

    try
      disableControls; { Disable them during setting of active row }

      { Maintain current row so grid doesn't jump }
      if GetActiveRow < currentRow then
      begin
        ScrollCount := currentRow - GetActiveRow;
        moveByCount := -(GetActiveRow + ScrollCount);
        if moveByCount <> 0 then
        begin
          MoveBy(moveByCount);
          SetActiveRow(currentRow);
        end;
      end
      else
      begin
        ScrollCount := GetActiveRow - currentRow;
        moveByCount := ((VisibleRowCount - 1) - GetActiveRow) + ScrollCount;
        If moveByCount <> 0 then
        begin
          MoveBy(moveByCount);
          SetActiveRow(currentRow);
        end;
      end;
    finally
      enableControls;
    end
  end;
end;

procedure TwwDBGrid.MouseUp(Button: TMouseButton; Shift: TShiftState;
  X, Y: integer);
var
  DrawInfo: TGridDrawInfo;
  NewSize: integer;

  function ResizeLine(const AxisInfo: TGridAxisDrawInfo): integer;
  var
    i: integer;
  begin
    with AxisInfo do
    begin
      result := 0; // FixedBoundary;
      for i := 0 to FSizingIndex - 1 do
        Inc(result, GetExtent(i) + EffectiveLineWidth);
      result := FSizingPos - result;
    end;
  end;

begin
  if (dgFixedResizable in Options) and (FGridState = gsColSizing) and
    (FSizingIndex <> -1) then
  begin
    // NewSize:= 0;
    try
      CalcDrawInfo(DrawInfo);
      if UseRightToLeftAlignment then
        FSizingPos := clientwidth - FSizingPos;
      NewSize := ResizeLine(DrawInfo.Horz);
      if NewSize > 1 then
      begin
        DrawSizingLine(DrawInfo);
        ColWidths[FSizingIndex] := NewSize;
        UpdateDesigner;
      end;
    finally
      // FGridState := gsNormal; // Done in ColWidthsChanged
      FSizingIndex := -1;
    end
  end;
  inherited MouseUp(Button, Shift, X, Y);
end;

procedure TwwDBGrid.MouseDown(Button: TMouseButton; Shift: TShiftState;
  X, Y: integer);
var
  Coord: TGridCoord;
  lookupControl: TWinControl;
  doubleClicking: Boolean;
  TempShiftSelectMode: Boolean;
  TempShiftSelectBookmark: TBookmark;
  DrawInfo: TGridDrawInfo;
  FIndicatorOffset: integer;
  ValidCell: Boolean;
  // OrigRowOffset: integer;
begin
  { Flush data }
  Coord := MouseCoord(X, Y);
  // OrigRowOffset:= RowOffset;

  if dgIndicator in Options then
    FIndicatorOffset := 1
  else
    FIndicatorOffset := 0;

  if (dgFixedResizable in Options) or (dgFixedEditable in Options) then
  begin
    CalcDrawInfo(DrawInfo);
    CalcSizingState(X, Y, FGridState, FSizingIndex, FSizingPos, FSizingOfs,
      DrawInfo);
    if FSizingIndex - FIndicatorOffset >= FixedCols then
      FSizingIndex := -1;
  end;

  TempShiftSelectMode := ShiftSelectMode;
  TempShiftSelectBookmark := ShiftSelectBookmark;
  doubleClicking := ssDouble in Shift;
  { save state before calling inherited class }

  try
    Patch[10] := True; // InMouseDown
    inherited MouseDown(Button, Shift, X, Y);
  finally
    Patch[10] := False;
  end;

  if (dgFixedEditable in Options) and (FGridState = gsNormal) and
    (Coord.X >= FIndicatorOffset) and (Coord.Y >= FixedRows) then
  begin
    if dgEditing in Options then
    begin
      if (Coord.X = col) and (Coord.Y = row) then
        if dgAlwaysShowEditor in Options then
          // 9/4/01 - don't show editor if custom control or memo field
          if not IsWWControl(col, row) and (not isMemoField(col, row)) then
            ShowEditor; { Don't show internal editor if memo field }
      Click;
    end
  end;

  if (dgFixedResizable in Options) and (FGridState = gsColSizing) and
    (FSizingIndex <> -1) then
  begin
    CalcDrawInfo(DrawInfo);
    CalcSizingState(X, Y, FGridState, FSizingIndex, FSizingPos, FSizingOfs,
      DrawInfo);
  end;

  if (Button = mbLeft) then
  begin
    update; { Allow screen repaints to finish before continuing }

    // Coord:= MouseCoord(x,y);

    if (not CanEditGrid) and not(isSelectedCheckbox(Coord.X) or
      (dgMultiSelect in Options)) then
      exit;
    ValidCell := isValidCell(Coord.X, Coord.Y);
    // 3/12/04 - Fix logic so indicator column still selects range

    // if not (isValidCell(Coord.x, Coord.y)) then exit;

    { 10/24/97 - Only toggle if can edit grid or multi-selection checkbox }
    if ValidCell and (CanEditGrid or isSelectedCheckbox(Coord.X)) then
    begin
      if doubleClicking then
      begin
        if not(ecoCheckboxSingleClick in EditControlOptions) then
        begin
          ToggleCheckBox(Coord.X, Coord.Y);
          update;
        end
      end
      else
      begin
        if (ecoCheckboxSingleClick in EditControlOptions) then
        begin
          ToggleCheckBox(Coord.X, Coord.Y);
          update;
        end
      end;
    end;

    { Draw combo area since mouse doesn't invalidate area if activerecord has not changed }
    if ValidCell and (isCustomEditCell(Coord.X, Coord.Y, lookupControl) and
      not lookupControl.Visible) then
    begin
      if (Coord.X = col) and (Coord.Y = row) then { is Selected Cell }
        DrawCell(Coord.X, Coord.Y, CellRect(Coord.X, Coord.Y), [gdSelected]);
    end;

    if (ValidCell or (Coord.X = 0)) and (dgMultiSelect in Options) and
      (ssShift in Shift) and TempShiftSelectMode then
    begin
      with DataLink.dataset do
      begin
        if (TempShiftSelectBookmark <> nil) and
          BookmarkValid(TempShiftSelectBookmark) then
        begin
          SelectRecordRange(GetBookmark, TempShiftSelectBookmark);
        end
      end
    end;

    { 5/13/98 - Free shift select bookmark }
    { 8/24/99 - Don't free bookmark if ssDouble as this can cause exception if
      dataset is closed and re-opened in OnDblClick event }
    if not(ssDouble in Shift) and
      (TempShiftSelectBookmark <> ShiftSelectBookmark) then
      if (ShiftSelectBookmark <> nil) then
      // 12/12/05 - If nil, then dataset was re-opened so bookmark was already freed
        DataLink.dataset.FreeBookmark(TempShiftSelectBookmark);

  end
end;

procedure TwwDBGrid.WMChar(var Msg: TWMChar);
var
  lookupControl: TWinControl;
  Function IsValidChar(ch: Char): Boolean;
  begin
    if StrCharMatch(ch, ^H) or ((ch >= #32) and (ch <= High(Char))) then
      result := True
    else
      result := False
  end;

begin
  if isMemoField(col, row) or IsWWControl(col, row) then
  begin
    if CanEditGrid and IsValidChar(Char(Msg.CharCode)) then
    // (Char(Msg.CharCode) in [^H, #32..#255]) then
    // if canEditGrid and (Char(Msg.CharCode) in [^H, #32..#255]) then
    begin
      if isCustomEditCell(col, row, lookupControl) then
      begin
        if lookupControl.Visible then
        begin
          lookupControl.setFocus;
          { lookupControl.selectAll; } { 6/30/98 - Rely on edit control to set focus }
          PostMessage(lookupControl.handle, WM_CHAR, word(Msg.CharCode), 0);
        end
      end;
      exit; { 6/28/99 - Used to be 2 lines down }
    end;

  end;
  inherited;
end;

procedure TwwDBGrid.CallMemoDialog;
var
  memoDlg: TwwMemoDialog;
  Cancel: Boolean;
begin
  if (mDisableDialog in MemoAttributes) then
    exit;

  memoDlg := TwwMemoDialog.Create(self);
  try
    memoDlg.DataSource := DataSource;
    memoDlg.DataField := FieldName(col);
    memoDlg.MemoAttributes := FMemoAttributes;
    memoDlg.Caption := strReplaceChar(DataLink.fields[dbCol(col)].DisplayLabel,
      '~', ' ');
    memoDlg.Font := Font;
    memoDlg.DlgLeft := -1; { leave as default size }

    if (not CanEditGrid) or (DataLink.fields[dbCol(col)].ReadOnly) then
      memoDlg.MemoAttributes := memoDlg.MemoAttributes + [mViewOnly];
    if Assigned(FOnMemoOpen) then
      FOnMemoOpen(self, memoDlg);
    Cancel := not memoDlg.Execute;
    if Assigned(FOnMemoClose) then
      FOnMemoClose(self, Cancel);
  finally
    memoDlg.Free;
    DrawCell(col, row, CellRect(col, row), [gdSelected]);
    EditorMode := False; { 2.0 }
  end;
end;

{ Override to support memo editing when F2 is entered }
procedure TwwDBGrid.KeyDown(var Key: word; Shift: TShiftState);
var
  wwControl: TWinControl;

  { 6/6/98 }
  procedure PlayKeystroke(handle: Hwnd; VKChar: word; VKShift: word);
  var
    KeyState: TKeyboardState;
    NewKeyState: TKeyboardState;
  begin
    GetKeyboardState(KeyState);
    NewKeyState := KeyState;
    NewKeyState[VKShift] := $81;
    NewKeyState[VKChar] := $81;
    SetKeyboardState(NewKeyState);
    SendMessage(handle, WM_KEYDOWN, VKChar, 1);
    SetKeyboardState(KeyState);
  end;

begin

  case Key of
    VK_DOWN:
      if isCustomEditCell(col, row, wwControl) and (ssAlt in Shift) and
        (wwControl.Visible) then
      begin
        wwControl.setFocus;
        wwPlayKeystroke(wwControl.handle, Key, VK_MENU);
        Key := 0;
        exit; // 12/11/01 - RSW - Don't pass to inherited KeyDown as then focus can get lost causing datetimepicker to not dropdown
      end
  end;

  inherited KeyDown(Key, Shift);
  if not DataLink.active or not CanGridAcceptKey(Key, Shift) then
    exit;

  // Support space toggling if row-select
  if (dgRowSelect in Options) and (Key = vk_space) then
  begin
    // 3/14/2002-PYW-Only SelectRecord/UnSelectRecord if multiselect enabled.
    if dgMultiSelect in Options then
    begin
      if IsSelected then
        UnselectRecord
      else
        SelectRecord;
      exit;
    end;
  end;

  if (not isMemoField(col, row)) and (not CanEditGrid) and
    (not isSelectedCheckbox(col)) then
    exit;

  if isCheckBox(col, row, dummy1, dummy2) then
  begin
    if Key = vk_space then
      ToggleCheckBox(col, row);
  end
  else if IsRichEditCell(col, row, wwControl) then
  begin
    if Key <> VK_F2 then
      exit;
    if (wwControl = Nil) then
      exit;
    SendMessage(wwControl.handle, WM_KEYDOWN, VK_F2, 0);
    { (wwControl as TwwDBRichEdit).execute; }
    InvalidateCell(col, row);
  end
  else if isCustomEditCell(col, row, wwControl) then
  begin
    if (Key = VK_F2) and (wwControl.Visible) then
      wwControl.setFocus;
    // if wwControl is TwwDBCustomEdit then
    if wwControl <> nil then { 6/6/98 }
    begin
      if wwIsValidChar(Key) then
      begin
        if wwControl.Visible then
        begin
          wwControl.setFocus;
          PlayKeystroke(wwControl.handle, Key, 0); { 6/6/98 }
          // InvalidateCell(col, row);
          // TwwDBCustomEdit(wwControl).KeyDown(Key, shift);  {8/22/96}
        end
      end
    end
  end
  else if isMemoField(col, row) then
  begin { 12/20/96 - Move this test at the end to give customeeditor priority }
    if Key <> VK_F2 then
      exit;
    CallMemoDialog;
    InvalidateCell(col, row);
  end
  // 6/24/01 - Delete for inplacaeeditor should delete contents even when it does
  // not have the focus yet.
  else if CanEditGrid and (Key = vk_delete) and (not isMemoField(col, row)) and
    (not IsWWControl(col, row)) then
  begin
    ShowEditor;
    if (InplaceEditor <> nil) and InplaceEditor.Visible then
      SendMessage(InplaceEditor.handle, WM_KEYDOWN, vk_delete, 0);
    { (wwControl as TwwDBRichEdit).execute; }
  end;
end;

procedure TwwDBGrid.WMLButtonDblClk(var Msg: TWMLButtonDblClk);
var
  Coord: TGridCoord;
  col, row: integer;
  wwControl: TWinControl;
begin
  Coord := MouseCoord(Msg.xpos, Msg.yPos);
  col := Coord.X;
  row := Coord.Y;
  if IsRichEditCell(col, row, wwControl) then
  begin
    inherited;
    if (wwControl <> Nil) then
      SendMessage(wwControl.handle, WM_KEYDOWN, VK_F2, 0);
    { (wwControl as TwwDBRichEdit).execute; }
  end
  else if isMemoField(col, row) then
  begin
    inherited; { Move before CallMemoDialog }
    CallMemoDialog;
  end
  else
    inherited;
end;

procedure TwwDBGrid.ToggleCheckBox(col, row: integer);
var
  dbColumn: integer;
  value: string;
  checkBoxOn, checkBoxOff: string;
  tempField: TField;
begin
  if not isCheckBox(col, row, checkBoxOn, checkBoxOff) then
    exit;

  dbColumn := dbCol(col);
  if (DataLink.fields[dbColumn].ReadOnly) then
    exit;

  if (Columns[dbColumn] <> Nil) and (Columns[dbColumn].ReadOnly) then
    exit;

  tempField := GetColField(dbColumn);
  if (tempField.calculated) and
    (lowercase(tempField.FieldName) = 'selected') then
  begin
    if IsSelected then
      UnselectRecord
    else
      SelectRecord;
  end
  else
  begin
    { 9/19/97 - This code moved so that it does not execute for multi-selection checkbox }
    if (not DataSource.autoEdit) and
      (not(DataSource.State in [dsEdit, dsInsert])) then
      exit;

    { 4/24/00 - Don't toggle checkbox when dataset is readonly }
    if (DataLink.dataset <> nil) and (not DataLink.dataset.canmodify) then
      exit;

    // 6/26/01 - Respect dgAllowInsert of False
    if (DataSource <> nil) and (DataSource.dataset <> nil) and
      (not(dgAllowInsert in KeyOptions)) and DataSource.dataset.BOF and
      DataSource.dataset.Eof and (DataSource.State = dsBrowse) then
    begin
      exit;
    end;

    DataLink.Edit;
    value := WideGetFieldValue(dbColumn);

    { 11/11/99 - Case insensitive checkbox }
    if wwEqualStr(value, checkBoxOn) then
      SetFieldValue(dbColumn, checkBoxOff)
      // if value=checkBoxOn then SetFieldValue(dbColumn, checkBoxOff)
    else
      SetFieldValue(dbColumn, checkBoxOn);

    // 9/24/03 - May not be needed at all with buffered painting - leave for now excep in case of bidi
    if not UseRightToLeftAlignment then
      DrawCell(col, row, CellRect(col, row), [gdSelected]);
  end;

end;

Function TwwDBGrid.GetRowCount: integer;
begin
  result := rowCount;
end;

Function TwwDBGrid.GetColCount: integer;
begin
  result := ColCount;
end;

Function TwwDBGrid.GetActiveRow: integer;
begin
  result := dbRow(row);
end;

Function TwwDBGrid.GetActiveCol: integer;
begin
  result := col;
end;

Function TwwDBGrid.GetActiveField: TField;
begin
  result := GetColField(dbCol(col));
end;

Procedure TwwDBGrid.SetActiveField(AFieldName: string);
var
  ACol: integer;
  curField: TField;
begin
  if not DataLink.active then
    exit;

  for ACol := 0 to ColCount - 1 do
  begin
    curField := GetColField(ACol);
    if curField = Nil then
      continue;
    if lowercase(curField.FieldName) = lowercase(AFieldName) then
    begin
      SelectedIndex := ACol;
      break;
    end
  end
end;

procedure TwwDBGrid.ColWidthsChanged;
begin
  if (dgFixedResizable in Options) and (FGridState = gsColSizing) and
    (FSizingIndex < inherited FixedCols) then
    FGridState := gsNormal; // Restore state, necessary for fixed columns
  inherited ColWidthsChanged;
end;

procedure TwwDBGrid.ColumnMoved(FromIndex, ToIndex: Longint);
begin
  HideControls;
  inherited ColumnMoved(FromIndex, ToIndex);
end;

constructor TwwMemoDialog.Create(AOwner: TComponent);
begin
  inherited Create(AOwner);
  FDataLink := TFieldDataLink.Create;
  FMemoAttributes := [mWordWrap, mSizeable];
  FFont := TFont.Create;
  DlgWidth := 561;
  DlgHeight := 396;
  DlgLeft := 0; { center dialog }
  DlgTop := 0;
  FLines := Tstringlist.Create;
  if wwGridEssentials then
  begin
    DisplayProfessionalVersionOnlyMessage(self,
      'TwwMemoDialog is only supported in the professional version');
  end;

end;

destructor TwwMemoDialog.Destroy;
begin
  FLines.Free;
  FDataLink.Free;
  FDataLink := nil;
  FFont.Free;
  inherited Destroy;
end;

Function TwwMemoDialog.GetDataSource: TDataSource;
begin
  result := FDataLink.DataSource;
end;

procedure TwwMemoDialog.SetDataSource(value: TDataSource);
begin
  FDataLink.DataSource := value;
end;

procedure TwwMemoDialog.SetDataField(value: String);
begin
  FDataLink.FieldName := value;
end;

function TwwMemoDialog.GetDataField: string;
begin
  result := FDataLink.FieldName;
end;

procedure TwwMemoDialog.SetFont(value: TFont);
begin
  FFont.assign(value);
end;

procedure TwwMemoDialog.SetCaption(value: String);
begin
  FCaption := value;
end;

function TwwMemoDialog.Execute: Boolean;
begin
  result := wwEditMemoField(screen.ActiveForm, self,
    not(mViewOnly in MemoAttributes))
end;

procedure TwwMemoDialog.SetwwMemoAttributes(sel: TwwMemoAttributes);
begin
  FMemoAttributes := sel;
end;

Procedure TwwMemoDialog.DoInitDialog;
begin
  if Assigned(FOnInitDialog) then
    OnInitDialog(Form);
end;

Procedure TwwMemoDialog.DoCloseDialog;
begin
  if Assigned(FOnCloseDialog) then
    OnCloseDialog(Form);
end;

Procedure TwwDBGrid.SelectAll;
var
  saveBK: TBookmark;
  Accept: Boolean;
begin
  with DataSource.dataset do
  begin
    If Assigned(FOnSelectAllRecords) then
    begin
      Accept := True;
      FOnSelectAllRecords(self, True, Accept);
      if not Accept then
        exit;
    end;
    SelectedList.Clear;

    saveBK := GetBookmark; { Save current record position }
    disableControls;
    First;
    while (not Eof) do
    begin
      SelectedList.CurrentRowSelected := True;
      Next;
    end;
    Gotobookmark(saveBK); { Restore original record position }
    FreeBookmark(saveBK);
    enableControls;
  end
end;

Procedure TwwDBGrid.RemoveSelected(bkmrk1, bkmrk2: TBookmark);
var
  i: integer;
  Accept: Boolean;
  {
    Function GreaterThanOrEqual(bookmark1, bookmark2: TBookmark): boolean;
    var res: integer;
    begin
    res:= wwDataSetCompareBookmarks(DataSource.DataSet, bookmark1, bookmark2);
    if CompareBookmarksAltMethod then
    result:= integer(res)>=0
    else
    result:=
    (integer(res)=CmpGtr) or
    (integer(res)=CmpKeyEql) or
    (integer(res)=CmpEql);
    end;
  }
  Function GreaterThanOrEqual(bookmark1, bookmark2: TBookmark): Boolean;
  var
    res: CMPBkMkRslt;
  begin
    res := Bookmarks.Compare(bookmark1, bookmark2);
    if CompareBookmarksAltMethod then
      result := integer(res) >= 0
    else
      result := (integer(res) = CMPGtr) or (integer(res) = CMPKeyEql) or
        (integer(res) = CMPEql);
  end;

begin
  with DataSource.dataset do
  begin
    i := 0;
    while i <= SelectedList.count - 1 do
    begin
      if GreaterThanOrEqual(bkmrk2, SelectedList[i]) and
        GreaterThanOrEqual(SelectedList[i], bkmrk1) then
      begin

        { 3/17/99 - Fire event }
        Accept := True;
        if Assigned(FOnSelectRecord) then
          FOnSelectRecord(self, False, Accept);
        if not Accept then
          continue;
        SelectedList.DeleteSelection(i);
        {
          FreeBookmark(SelectedList.Items[i]);
          SelectedList.delete(i);
        }
      end
      else
        i := i + 1;
    end;
    Invalidate;
  end
end;

Procedure TwwDBGrid.UnselectAll;
var
  i: integer;
  Accept: Boolean;
begin
  If Assigned(FOnSelectAllRecords) then
  begin
    Accept := True;
    FOnSelectAllRecords(self, False, Accept);
    if not Accept then
      exit;
  end;

  with DataSource.dataset do
  begin
    for i := 0 to SelectedList.count - 1 do
      FreeBookmark(SelectedList.items[i]);
    SelectedList.Clear;
    Invalidate;
  end;

  { Clear cached values }
  SelectedRecordList.Clear;
  for i := 0 to VisibleRowCount - 1 do
    SelectedRecordList.add('F')

end;

Procedure TwwDBGrid.UnselectRecord;
var
  Accept: Boolean;
begin
  if not IsSelected then
    exit; { Can't unselect since its not selected 6/16/96 }

  Accept := True;
  if Assigned(FOnSelectRecord) then
    FOnSelectRecord(self, False, Accept);
  if not Accept then
    exit;

  Bookmarks.CurrentRowSelected := False;
end;

procedure TwwDBGrid.SortSelectedList;
begin
  // Always sorted
end;
{
  procedure TwwDBGrid.SortSelectedList;
  var res: integer; //CmpBkmkRslt;
  Function LessThan(bookmark1, bookmark2: TBookmark): boolean;
  begin
  res:= wwDataSetCompareBookmarks(DataSource.DataSet, bookmark1, bookmark2);
  if CompareBookmarksAltMethod then
  result:= integer(res)<0
  else
  result:= integer(res)=CmpLESS;
  end;
  Function GreaterThan(bookmark1, bookmark2: TBookmark): boolean;
  begin
  res:= wwDataSetCompareBookmarks(DataSource.DataSet, bookmark1, bookmark2);
  if CompareBookmarksAltMethod then
  result:= integer(res)>0
  else
  result:= integer(res)=CmpGtr;
  end;

  procedure Partition(var i, j: integer);
  var Pivot, Temp: TBookmark;
  begin
  Pivot:= bookmarks[(i+j) div 2];
  repeat
  while LessThan(bookmarks[i], Pivot) do i:= i + 1;
  while GreaterThan(bookmarks[j], Pivot) do j:= j - 1;
  if (i<=j) then begin
  Temp:= bookmarks[i];
  bookmarks[i]:= bookmarks[j];
  bookmarks[j]:= Temp;
  i:= i +1;
  j:= j-1;
  end
  until (i>j);
  end;

  procedure QuickSort(m, n: integer);
  var i,j: integer;
  begin
  if (m<n) then begin
  i:= m; j:= n;
  Partition(i, j);
  QuickSort(m,j);
  QuickSort(i,n);
  end
  end;
  begin
  QuickSort(0, bookmarks.count-1);
  end;
}

Procedure TwwDBGrid.SelectRecord;
var
  Accept: Boolean;

  procedure UpdateGrid;
  var
    j: integer;
  begin
    for j := 0 to ColCount - 1 do
      InvalidateCell(j, row);
  end;

begin
  { 6/2/97 - Update starting shift-select record }
  if ShiftSelectMode then
  begin
    DataSource.dataset.checkBrowseMode;
{$IFDEF wwDelphi2009Up}
    ShiftSelectBookmark := DataSource.dataset.bookmark;
{$ELSE}
    ShiftSelectBookmark := DataLink.dataset.GetBookmark;
{$ENDIF}
  end;

  DataSource.dataset.checkBrowseMode;
  { 6/8/97 - Moved before isSelected - bookmarks don't work in edit mode }
  RefreshBookmarkList; { 6/8/97 - Refresh before calling isSelected }

  if IsSelected then
    exit; { Don't add if already in list 6/16/96 }

  Accept := True;
  if Assigned(FOnSelectRecord) then
    FOnSelectRecord(self, True, Accept);
  if not Accept then
    exit;

  Bookmarks.CurrentRowSelected := True;

  InvalidateCurrentRow;

end;

(*
  Procedure TwwDBGrid.SelectRecord;
  var newBookmark: TBookmark;
  accept: boolean;
  index: integer;

  procedure UpdateGrid;
  var j: integer;
  begin
  for j:= 0 to colCount-1 do InvalidateCell(j, row);
  end;

  begin
  { 6/2/97 - Update starting shift-select record }
  if ShiftSelectMode then
  begin
  Datasource.dataset.checkBrowseMode;
  // 11/25/03 - Free old ShiftSelectBookmark if not in MouseDown!
  // *************  We currently are not doing this so
  // calling SelectRecord explicitly can cause old ShiftSelectBookmark to not be free!
  //***********************************************************************************
  if (ShiftSelectBookmark<>nil) and (patch[10]=false) then // 11/28/07 - Freebookmark if called directly and not by MouseDown
  datasource.dataset.FreeBookmark(ShiftSelectBookmark);

  ShiftSelectBookmark:= DataSource.DataSet.GetBookmark;
  end;

  datasource.dataset.checkBrowseMode; {6/8/97 - Moved before isSelected - bookmarks don't work in edit mode}
  RefreshbookmarkList;   { 6/8/97 - Refresh before calling isSelected }

  if isSelected then exit; { Don't add if already in list 6/16/96}

  accept:= True;
  if Assigned(FOnSelectRecord) then FOnSelectRecord(self, True, Accept);
  if not accept then exit;

  newBookmark:= findBookmark;
  if newBookmark=Nil then
  newBookmark:= datasource.dataset.getBookmark;

  if (newBookmark<>nil) then
  begin
  // 11/16/03 - Insert as sorted list
  if Patch[9]=False then
  begin
  if not Find(self, newBookmark, Index) then
  bookmarks.Insert(Index, Newbookmark)
  end
  else begin
  bookmarks.add(newBookmark);  { 2/13/97 - Check for nil bookmark}
  end;
  end;
  InvalidateCurrentRow;

  end;
*)

Function TwwDBGrid.IsSelectedRecord: Boolean;
var
  thisTable: TDataset;
begin
  thisTable := DataSource.dataset;
  if (thisTable.State = dsEdit) or (thisTable.State = dsInsert) then
  begin
    result := False;
    exit;
  end;

  result := Bookmarks.CurrentRowSelected;
end;
(*
  Function TwwDBGrid.IsSelectedRecord: boolean;
  var i: integer;
  curBookmark: Tbookmark;
  thisTable: TDataset;
  res: integer; //CmpBkmkRslt;
  index: integer;
  begin
  thisTable:= datasource.dataset;
  if (thisTable.state=dsEdit) or (thisTable.state=dsInsert) then begin
  result:= False;
  exit;
  end;

  curBookmark:= thisTable.getBookmark;

  // 11/16/03 - New technique assumes sorted list
  if Patch[9]=False then
  begin
  result:= Find(self, curBookmark, Index);
  thisTable.freebookmark(curBookmark); { Don't use finally block - too slow }
  exit;
  end;

  // 11/16/03 - Old technique
  result:= False;
  if curBookmark=Nil then exit;

  for i:= 0 to bookmarks.count-1 do begin
  res:= wwDataSetCompareBookmarks(DataSource.DataSet, bookmarks.items[i], curBookmark);
  if CompareBookmarksAltMethod then
  begin
  if (res=0) then
  begin
  result:= True;
  break;
  end
  end
  else if (res=CMPKeyEql) or (res=CMPEql) then begin
  result:= True;
  break;
  end
  end;

  thisTable.freebookmark(curBookmark); { Don't use finally block - too slow }

  end;
*)

procedure TwwDBGrid.RefreshBookmarkList;
var
  i, OldActive: integer;
begin
  SelectedRecordList.Clear;
  if Bookmarks.count > 0 then
  begin
    OldActive := 0; { Make compiler happy }
    try
      OldActive := DataLink.ActiveRecord;
      for i := 0 to VisibleRowCount - 1 + RowOffset do
      begin
        DataLink.ActiveRecord := i;
        if IsSelectedRecord then
          SelectedRecordList.add('T')
        else
          SelectedRecordList.add('F');
      end;
      DataLink.ActiveRecord := OldActive;
    except
      DataLink.ActiveRecord := OldActive;
    end
  end
  else
  begin
    for i := 0 to VisibleRowCount - 1 do
    begin
      SelectedRecordList.add('F')
    end;
  end
end;

Function TwwDBGrid.IsSelected: Boolean;
begin
  result := IsSelectedRow(DataLink.ActiveRecord);
  { if (DataLink.ActiveRecord>=0) and (DataLink.ActiveRecord<SelectedRecordList.count) then
    result:= SelectedRecordList.strings[DataLink.ActiveRecord]='T'
    else result:= False; }
end;

// 7/29/04 - Fix bug when expanded with multi-select. It checks wrong row before
Function TwwDBGrid.IsSelectedRow(DataRow: integer): Boolean;
begin
  if (DataRow >= 0) and (DataRow { +RowOffset } < SelectedRecordList.count) then
    result := SelectedRecordList.strings[DataRow { +RowOffset } ] = 'T'
  else
    result := False;
end;

Function TwwDBGrid.findBookmark: TBookmark;
var
  i: integer;
  curBookmark: TBookmark;
  thisTable: TDataset;
  res: integer; // CmpBkmkRslt;
begin
  result := Nil;
  thisTable := DataSource.dataset;
  curBookmark := thisTable.GetBookmark;
  if curBookmark = nil then
    exit; { 2/13/97 }

  try
    for i := 0 to Bookmarks.count - 1 do
    begin
      res := wwDataSetCompareBookmarks(DataSource.dataset, Bookmarks.items[i],
        curBookmark);
      if CompareBookmarksAltMethod then
      begin
        if res = 0 then
        begin
          result := Bookmarks.items[i];
          exit;
        end
      end
      else if (res = CMPKeyEql) or (res = CMPEql) then
      begin
        result := Bookmarks.items[i];
        exit;
      end
    end;
  finally
    thisTable.FreeBookmark(curBookmark);
  end;
end;

Function TwwDBGrid.WideGetFieldValue(ACol: integer): WideString;
begin
  result := GetFieldValue(ACol);
end;

Function TwwDBGrid.GetFieldValue(ACol: integer): string;
const
  MaxMemoSize = 255;
var
  Field: TField;
  Buffer: array [0 .. MaxMemoSize] of Char;
  wwControl: TWinControl;
  rpc: TRichProtectClass;
  URLDisplay, LinkAddress: string;

{$IFDEF wwDelphi2006Up}
  Stream: TStringStream;
{$ENDIF}
  Function HandleSpecialCharacters(tempResult: string;
    numRead: Longint): string;
  var
    curpos: integer;
    result1, result2: string;
  begin
    setLength(tempResult, numRead);

    if dgWordWrap in Options then
    begin
      result := tempResult;
      exit;
    end;

    curpos := 1;
    result1 := strGetToken(tempResult, #13, curpos);
    curpos := 1;
    result2 := strGetToken(tempResult, #10, curpos);
    if length(result1) < length(result2) then
      result := result1
    else
      result := result2;
  end;

{ 9/2/97 - Override isCustomEditCell so that it ignores record-view when mapping }
  function isCustomEditCell(col, row: integer;
    var customEdit: TWinControl): Boolean;
  var
    fldName: string;
    i, Apos: integer;
    ControlType: TStrings;
  begin
    result := False;
    if ecoDisableCustomControls in EditControlOptions then
      exit;
    if not isValidCell(col, row) then
      exit;
    fldName := DataLink.fields[dbCol(col)].FieldName;

    { 8/14/97 - Optimized logic to speed painting of grid }
    ControlType := GetControlType;
    for i := 0 to ControlType.count - 1 do
    begin
      Apos := 1;
      if strGetToken(ControlType[i], ';', Apos) <> fldName then
        continue;
      if IsWWEditControl(strGetToken(ControlType[i], ';', Apos)) then
      begin
        customEdit := GetComponent(strGetToken(ControlType[i], ';', Apos));
        if customEdit = Nil then
          break;
        result := True;
        break;
      end
    end;
  end;

// 4/1/09 - Fix hint bug relating to unicode ftwidememo
  function HasRichEditContents: Boolean;
  var
    BlobStream: TStream;
    Buffer: packed array [1 .. 5] of ansichar;
  begin
    BlobStream := Nil;
    try
      result := False;

{$IFDEF wwDelphi2006Up}
      if (Field is TBlobField) and
        ((Field as TBlobField).BlobType = ftWideMemo) then
        result := copy(Field.asString, 1, 5) = '{\rtf'
      else
      begin
{$ENDIF}
        Buffer := '     ';
        BlobStream := Field.dataset.CreateBlobStream(Field, bmRead);
        BlobStream.Read(Buffer, 5);
        result := Buffer = '{\rtf';

{$IFDEF wwDelphi2006Up}
      end;
{$ENDIF}
    finally
      BlobStream.Free;
    end;
  end;

{
  function HasRichEditContents: boolean;
  var
  BlobStream: TStream;
  Buffer: packed array[1..5] of char;
  begin
  BlobStream:= Nil;
  try
  result:= False;
  BlobStream := Field.DataSet.CreateBlobStream(Field, bmRead);
  Buffer:= '     ';
  BlobStream.Read(Buffer, 5);
  result:= Buffer = '{\rtf'
  finally
  BlobStream.Free;
  end;
  end;
}

begin
  result := '';
  Field := GetColField(ACol);
  if Field <> nil then
  begin
{$IFNDEF GRIDESSENTIALS}
    if (Field is TBlobField) and { 8/12/97 - Optmization }
      IsRichEditCell(ACol + xIndicatorOffset, 1, wwControl) then
    begin
      if tempRichEdit = Nil then
      begin
        tempRichEdit := TwwDBRichEdit.Create(self);
        tempRichEdit.Visible := False;
        tempRichEdit.parent := self;
        tempRichEdit.BorderStyle := bsNone;
        if csDesigning
          in ComponentState then { otherwise shows up at design time }
        begin
          ShowWindow(tempRichEdit.handle, sw_hide);

          // Delphi 2007 seems to require this
          tempRichEdit.top := -2000;
          tempRichEdit.Height := wwMin(tempRichEdit.Height, 2000);

        end;

      end;

      rpc := TRichProtectClass.Create;
      { 1/28/97 - Allow Change of text when protected flag in richtext }
      tempRichEdit.OnProtectChange := rpc.RichProtectChange;
      if HasRichEditContents then
      begin
        if LoadAllRTF then
        begin
          // 4/1/09
{$IFDEF wwDelphi2006Up}
          if (Field is TBlobField) and
            ((Field as TBlobField).BlobType = ftWideMemo) then
          begin
            Stream := TStringStream.Create(Field.asString);
            try
              tempRichEdit.Lines.LoadFromStream(Stream);
            finally
              Stream.Free;
            end;
          end
          else
{$ENDIF}
            tempRichEdit.CopyRichEditFromBlob(Field)
        end
        else
          tempRichEdit.LoadBlobStart(Field) { 10/6/98 }
      end
      else
      begin
        // 5/20/09 - Handle case where rtf contains plain text but widememo
        if (Field is TBlobField) and
          ((Field as TBlobField).BlobType = ftWideMemo) then
        begin
          Stream := TStringStream.Create(Field.asString);
          try
            tempRichEdit.Lines.LoadFromStream(Stream);
          finally
            Stream.Free;
          end;
        end
        else
          tempRichEdit.Lines.assign(Field);

      end;
      tempRichEdit.OnProtectChange := Nil;
      rpc.Free;
      // PaintRichEdit;
      result := tempRichEdit.Text;

      { if Datalink.ActiveRecord>=0 then
        result:= String(RichEditBufferList[Datalink.ActiveRecord]); }
    end
    else {$ENDIF} if (Field.DataType in [ftMemo, ftOraClob
{$IFDEF  wwdelphi2006up}
      , ftWideMemo
{$ENDIF}
      ]) then
    begin
      // if (Field.DataType = ftMemo) then begin

      { Don't display memo if no record }
      if (DataSource.dataset.BOF and DataSource.dataset.Eof) and
        not(DataSource.dataset.State = dsInsert) then
      begin
        result := '';
        exit;
      end;

      if mGridShow in FMemoAttributes then
      begin
        try
          if (GetActiveRow = DataLink.ActiveRecord + RowOffset) then
          begin
            Field.dataset.updateCursorPos;
            result := Field.asString;
            // Result:= TMemoField(field).asString; // 12/06/04
          end
          else
          begin
            result := Field.asString;
            // Result:= TMemoField(field).asString;
          end

        except
          strcopy(Buffer, '(Memo)');
          result := strPas(Buffer);
        end
      end
      else
      begin
        strcopy(Buffer, '(Memo)');
        result := strPas(Buffer);
      end
    end
    else
    begin
      if TwwCacheColInfoItem(FCacheColInfo[ACol + FIndicatorOffset])
        .ControlType = 'URL-Link' then
      begin
        // 12/6/06 - Use DisplayText so url appears formatted in grid
        GetURLLink(Field.DisplayText, URLDisplay, LinkAddress);
        result := URLDisplay;
      end
      else if (isCustomEditCell(ACol + xIndicatorOffset, 1, wwControl)) then
      begin
        if (wwControl is TwwDBCustomEdit) then
        begin
          if not TwwDBCustomEdit(wwControl).GetFieldMapText(Field.asString,
            result) then
            result := Field.DisplayText
          else
            FieldMappedText := True;
        end
{$IFNDEF GRIDESSENTIALS}
        else if (wwControl is TwwRadioGroup) then
        begin
          result := TwwRadioGroup(wwControl).GetDisplayValue(Field.asString)
        end
{$ENDIF}
        else
          result := Field.DisplayText;
      end
      else
        result := Field.DisplayText;
    end
  end
end;

{ Set focus to this grid when changing records }
{ If focus is on currentCustomEdit then lookupcombo does not properly move }
procedure TwwDBGrid.Scroll(Distance: integer);
  Function CustomEditFocus: Boolean;
  var
    ac: TWinControl;
  begin
    result := False;
    if not(csDesigning in ComponentState) then
    begin
      ac := ValidParentForm(self).activecontrol;

      if (ac = CurrentCustomEdit) then
        result := True;
      if (ac <> nil) and (ac.parent = CurrentCustomEdit) then
        result := True;
    end
  end;

begin
  if Visible and (CurrentCustomEdit <> Nil) and (CurrentCustomEdit.Visible) and
    (CurrentCustomEdit.focused or CustomEditFocus) then
  // 3/31/03 - Call CustomEditFocus in case activecontrol is a child component of currentcustomedit
    if CanFocus then
      setFocus;

  inherited Scroll(Distance);

end;

procedure TwwDBGrid.SaveToIniFile;
var
  TempCount, i: integer;
  parts: Tstringlist;
  tempField: TField;
  tempreadonly: String;
  tempgroupname: String;
  IniFile: TCustomIniFile;

  RegFile: TRegIniFile;
  Dir: String;
begin
  if (csDesigning in ComponentState) then
    exit;
  if IniAttributes.SaveToRegistry and (IniAttributes.FileName = '') then
  begin
    if not(csDestroying in ComponentState) then
      MessageDlg('FileName required when saving to registry', mtWarning,
        [mbok], 0);
    exit;
  end;

  if (not IniAttributes.SaveToRegistry) and (IniAttributes.FileName <> '') then
  begin
    Dir := ExtractFilePath(IniAttributes.FileName);
    if (Dir <> '') and not DirectoryExists(Dir) then
    begin
      if not(csDestroying in ComponentState) then
        MessageDlg('Directory does not exist', mtWarning, [mbok], 0);
      exit;
    end;
  end;

  if IniAttributes.Delimiter = '' then
  begin
    MessageDlg('Key delimiter required', mtWarning, [mbok], 0);
    exit;
  end;

  if wwGridEssentials then
    DisplayProfessionalVersionOnlyMessage(self,
      'Saving and loading of user'' runtime grid setting of the column position is only supported in InfoPower Professional');

  if UseTFields then
  begin
    TempCount := fieldCount;
    parts := nil; { Make compiler happy }
  end
  else
  begin
    TempCount := Selected.count;
    parts := Tstringlist.Create;
  end;

  IniFile := nil;
  RegFile := nil; // Put IniFile creation in try block to catch any exception

  try
    if IniAttributes.SaveToRegistry then
      RegFile := TRegIniFile.Create(IniAttributes.FileName)
    else begin
      {$ifdef wwDelphi2013Up}
      if IniAttributes.UnicodeIniFile then
      {$else}
      if Patch[13]=true then
      {$endif}
        IniFile := TMemIniFile.Create(IniAttributes.FileName, TEncoding.Unicode)
      else
        IniFile := TIniFile.Create(IniAttributes.FileName);
    end;

    if (IniAttributes.SaveToRegistry and (RegFile = nil)) or
      ((not IniAttributes.SaveToRegistry) and (IniFile = nil)) then
    begin
      exit;
    end;

    if (TempCount > 0) then
      if IniAttributes.SaveToRegistry then
      begin
        RegFile.EraseSection(IniAttributes.SectionName);
      end
      else
      begin // 4/15/01 - Don't throw exception
        // Inifile.EraseSection(IniAttributes.SectionName)
        if not WritePrivateProfileString(PChar(IniAttributes.SectionName), nil,
          nil, PChar(IniAttributes.FileName)) then
          exit
      end;

    for i := 0 to TempCount - 1 do
    begin
      tempField := nil; { Make compiler happy }
      if UseTFields then
        tempField := fields[i]
      else
      begin // 9/30/99 - Remove dependency on Fields
        strBreakapart(Selected[i], #9, parts);
      end;

      with IniAttributes do
        if UseTFields then
        begin
          if tempField.ReadOnly or (fields[i].ReadOnly) or (Columns[i] <> Nil)
            and (Columns[i].ReadOnly) then
          begin
            if SaveToRegistry then
              RegFile.WriteString(SectionName, tempField.FieldName,
                IntToStr(tempField.DisplayWidth) + Delimiter +
                tempField.DisplayLabel + Delimiter + 'F')
              // 10/1/03 - Don't save readonly if usetfields is true
            else
              IniFile.WriteString(SectionName, tempField.FieldName,
                IntToStr(tempField.DisplayWidth) + Delimiter +
                tempField.DisplayLabel + Delimiter + 'F');
            // 10/1/03 - Don't save readonly if usetfields is true
          end
          else
          begin
            if SaveToRegistry then
              RegFile.WriteString(SectionName, tempField.FieldName,
                IntToStr(tempField.DisplayWidth) + Delimiter +
                tempField.DisplayLabel + Delimiter + 'F')
            else
              IniFile.WriteString(SectionName, tempField.FieldName,
                IntToStr(tempField.DisplayWidth) + Delimiter +
                tempField.DisplayLabel + Delimiter + 'F');
          end;
        end
        else
        begin // 9/30/99 - Remove dependency on Fields and Columns
          // 6/13/2001-PYW- Support GroupName.
          tempgroupname := '';
          if (parts.count >= 4) then
          begin
            if parts.count = 5 then
              tempgroupname := parts[4];
            tempreadonly := parts[3];
          end
          else
            tempreadonly := 'F';

          if SaveToRegistry then
          begin
            if tempgroupname = '' then
              RegFile.WriteString(SectionName, parts[0],
                parts[1] + Delimiter + parts[2] + Delimiter + tempreadonly)
            else
              RegFile.WriteString(SectionName, parts[0],
                parts[1] + Delimiter + parts[2] + Delimiter + tempreadonly +
                Delimiter + tempgroupname);
          end
          else
          begin
            if tempgroupname = '' then
              IniFile.WriteString(SectionName, parts[0],
                parts[1] + Delimiter + parts[2] + Delimiter + tempreadonly)
            else
              IniFile.WriteString(SectionName, parts[0],
                parts[1] + Delimiter + parts[2] + Delimiter + tempreadonly +
                Delimiter + tempgroupname);
          end
        end;
    end;
    // MyInifile.UpdateFile;
  finally
    if IniFile <> nil then
      IniFile.Free;
    if RegFile <> nil then
      RegFile.Free;
    if (not UseTFields) and (parts <> nil) then
      parts.Free;
  end;
end;

procedure TwwDBGrid.LoadFromIniFile;
var
  TmpIni: TIniFile;
  TmpReg: TRegIniFile;
  SectionValues: Tstringlist;
  KeyValue, Dir: String;
  i: integer;
  tempSelected: Tstringlist;
  OrigTitle: string;

  Function ReplaceStrWithStr(str: string; removestr: string;
    replaceStr: string): string;
  var
    i: integer;
  begin
    result := '';
    i := 1;
    while i <= length(str) do
    begin
      if (strlcomp(PChar(copy(str, i, length(removestr))), PChar(removestr),
        length(removestr)) <> 0) then
      begin
        result := result + str[i];
        i := i + 1;
      end
      else
      begin
        result := result + replaceStr;
        i := i + length(removestr);
      end;
    end;
  end;

{ 3/6/00 - Add new fields to dynamic selected property from selected property generated by iniattributes }
  procedure AddNewFields;
  var
    i, j, Apos: integer;
    OrigFieldName, NewFieldName: string;
    AlreadyFound: Boolean;
  begin
    if not IniAttributes.CheckNewFields then
      exit;
    for i := 0 to tempSelected.count - 1 do
    begin
      Apos := 1;
      OrigFieldName := strGetToken(tempSelected[i], #9, Apos);
      AlreadyFound := False;
      for j := 0 to Selected.count - 1 do
      begin
        Apos := 1;
        NewFieldName := strGetToken(Selected[j], #9, Apos);
        if wwEqualStr(OrigFieldName, NewFieldName) then
          AlreadyFound := True;
      end;
      if not AlreadyFound then
        Selected.add(tempSelected[i]);
    end
  end;

  function GetTitle(AFieldName: string; Selected: TStrings): string;
  var index: integer;
  begin
    if (UseTFields) and (DataLink <> Nil) and
      (DataLink.DataSet <> Nil) and (DataLink.DataSet.fieldByName
        (AFieldName) <> nil) then
      result := DataLink.DataSet.fieldByName(AFieldName).DisplayLabel
    else begin
      if not wwFindSelected(Selected, AFieldName, index) then
      begin
          result:= DataLink.DataSet.fieldByName(AFieldName).DisplayLabel;
      end
      else result := GetSelectedProp(Selected, AFieldName, sptUpdateLabel);
    end;
  end;

begin
  if csDesigning in ComponentState then
    exit;

  OrigSelected.assign(Selected);

  if IniAttributes.SaveToRegistry and (IniAttributes.FileName = '') then
  begin
    MessageDlg('FileName required when saving to registry', mtWarning,
      [mbok], 0);
    exit;
  end;

  if IniAttributes.Delimiter = '' then
  begin
    MessageDlg('Key delimiter required', mtWarning, [mbok], 0);
    exit;
  end;

  if (not IniAttributes.SaveToRegistry) and (IniAttributes.FileName <> '') then
  begin
    Dir := ExtractFilePath(IniAttributes.FileName);
    if (Dir <> '') and not DirectoryExists(Dir) then
    begin
      MessageDlg('Directory does not exist', mtWarning, [mbok], 0);
      exit;
    end;
  end;

  TmpIni := nil;
  TmpReg := nil;

  tempSelected := Tstringlist.Create;
  tempSelected.assign(Selected);

  if IniAttributes.SaveToRegistry then
  begin
    TmpReg := TRegIniFile.Create(IniAttributes.FileName)
  end
  else
    TmpIni := TIniFile.Create(IniAttributes.FileName);

  try
    if (IniAttributes.SaveToRegistry and (TmpReg = nil)) or
      ((not IniAttributes.SaveToRegistry) and (TmpIni = nil)) then
    begin
      exit;
    end;

    SectionValues := Tstringlist.Create;
    try
      if IniAttributes.SaveToRegistry then
      begin
        TmpReg.ReadSectionValues(IniAttributes.SectionName, SectionValues)
      end
      else
        TmpIni.ReadSectionValues(IniAttributes.SectionName, SectionValues);

      if SectionValues.count = 0 then
        exit;
      Selected.Clear;
      for i := 0 to SectionValues.count - 1 do
      begin
        KeyValue := SectionValues.Values[SectionValues.Names[i]];
        if AnsiPos(IniAttributes.Delimiter, KeyValue) > 0 then
          if IniAttributes.IgnoreTitles then  // Ignore title when loading
          begin
             OrigTitle:= GetTitle(SectionValues.Names[i], TempSelected);
             if OrigTitle='' then OrigTitle:= SectionValues.Names[i];

             Selected.add(SectionValues.Names[i] + #9 + ReplaceStrWithStr(KeyValue,
               IniAttributes.Delimiter, #9));
             UpdateSelectedPropWithGrid(self, Selected, SectionValues.Names[i],
                OrigTitle, sptUpdateLabel, Selected.Count-1);

          end
          else
             Selected.add(SectionValues.Names[i] + #9 + ReplaceStrWithStr(KeyValue,
               IniAttributes.Delimiter, #9))
        else
        begin
          MessageDlg('Key Delimiter Not Found', mtWarning, [mbok], 0);
          Selected.Clear;
          if not UseTFields then
            Selected.assign(tempSelected);
          exit;
        end;
      end;
      AddNewFields;
      ApplySelected;
    finally
      SectionValues.Free;
    end;
  finally
    if IniAttributes.SaveToRegistry then
      TmpReg.Free
    else
      TmpIni.Free;
    tempSelected.Free;
  end;

end;

procedure TwwDBGrid.Loaded;
var
  Form: TCustomForm;
begin
  // Initialize the FormName property here now that the parent has been assigned.
  Form := GetParentForm(self) as TCustomForm;
  { 10/10/99 Check for nil parent form }
  if Form <> nil then
    IniAttributes.FFormName := Form.name;

  inherited Loaded;
  GridIsLoaded := True;
  if not doneInitControls then
    RefreshDisplay;
end;

Procedure TwwDBGrid.AddDependent(value: TComponent);
begin
  FDependentComponents.add(value);
end;

Procedure TwwDBGrid.RemoveDependent(value: TComponent);
begin
  FDependentComponents.remove(value);
end;

Procedure TwwDBGrid.SetActiveRow(val: integer); { 10/24/96 }
var
  newrow: integer;
begin
  // 1/5/09 - Don't set row directly as this ends up calling ClampInView
  // which causes leftcol to be set to wrong position (with fixedcols)
  if dgTitles in Options then
    newrow := val + 1
  else
    newrow := val;
  if (newrow <> row) then
  begin
    MoveColRow(col, newrow, False, False);
  end;
  DataLink.ActiveRecord := val + RowOffset;
  Invalidate;
end;

procedure TwwDBGrid.ScrollCurrentToTop(currentRow: integer = 0);
// Scroll's current record to top if possible
var
  ScrollCount, moveByCount, actualMovecount: integer;
begin
  with DataSource.dataset do
  begin
    disableControls;
    try
      if GetActiveRow < currentRow then
      begin
        ScrollCount := currentRow - GetActiveRow;
        moveByCount := -(GetActiveRow + ScrollCount);
        if moveByCount <> 0 then
        begin
          actualMovecount := MoveBy(moveByCount);
          if (abs(actualMovecount) < abs(moveByCount)) then
            MoveBy(-actualMovecount) // Undo move
          else
            SetActiveRow(currentRow);
        end;
      end
      else
      begin
        ScrollCount := GetActiveRow - currentRow;
        moveByCount := ((VisibleRowCount - 1) - GetActiveRow) + ScrollCount;
        If moveByCount <> 0 then
        begin
          actualMovecount := MoveBy(moveByCount);
          if (abs(actualMovecount) < abs(moveByCount)) then
            MoveBy(-actualMovecount) // Undo move
          else
            SetActiveRow(currentRow);
        end;
      end;
    finally
      enableControls;
    end;
  end;
end;

procedure TwwDBGrid.ColEnter;
begin
  RefreshActiveControl;
  // inherited UpdateCustomEdit;
  inherited ColEnter;
end;

procedure TwwDBGrid.RefreshActiveControl;
var
  customEdit: TWinControl;
  dbColumn: integer;
  IsDateTime: Boolean;
  procedure UpdateCustomEdit;
  begin
    if customEdit.parent <> self then
      customEdit.parent := self;
    if wwGetControlDataSource(customEdit) <> DataSource then
      wwSetControlDataSource(customEdit, DataSource);
    if not wwEqualStr(wwGetControlDataField(customEdit),
      GetActiveField.FieldName) then
      wwSetControlDataField(customEdit, GetActiveField.FieldName);
    wwSetBoolean(customEdit, 'ctl3D', False);
    // TEdit(customEdit).Ctl3d:= False;
    if not wwIsClass(customEdit.classType, 'TwwDataInspector') then
      wwSetBorder(customEdit, False);
    // TEdit(customEdit).BorderStyle:= bsNone;

    // Don't support scrollbars for inspector as then text gets cutoff in csPaintCopyState
    // If find way to support csPaintCopy, then we can remove this restriction
    if wwIsClass(customEdit.classType, 'TwwDataInspector') then
      TDrawGrid(customEdit).ScrollBars := ssNone;

    // 5/18/01 - Updates currentcustomedit, and currentcustomedit position
    // code causes problem in normal expand operation when clicking on different row
    // and different column
    inherited UpdateCustomEdit;

  end;

begin
  if (csDesigning in ComponentState) then
    exit;
  if (dgRowSelect in Options) then
    exit;

  if isCustomEditCell(col, row, customEdit) and (customEdit <> FDateTimePicker)
    and { 2/19/99 - Default datetimepicker should go through default datetimepicker specfic code }
    (ShouldShowCustomControls or wwIsClass(customEdit, 'TwwExpandButton')) then
  // (not (ecoDisableCustomControls in EditControlOptions)) and
  // (canEditGrid or AlwaysShowControls) then
  begin
    UpdateCustomEdit;
  end
  else
  begin

    // 3/17/06 - Support ftTimeStamp
    IsDateTime := (GetActiveField is TDateTimeField) or
      ((GetActiveField <> nil) and (GetActiveField.DataType in [ftDate,
      ftDateTime, ftTimeStamp, ftTime]));

    if (not(ecoDisableDateTimePicker in EditControlOptions)) and IsDateTime then
    begin
      if FDateTimePicker = Nil then
      begin
        FDateTimePicker := CreateDateTimePicker;
        DoCreateDateTimePicker(FDateTimePicker);
      end;
      customEdit := FDateTimePicker;

      { 11/26/98 - Respect readonly property of related field }
      dbColumn := dbCol(col);
      FDateTimePicker.ReadOnly := (DataLink.fields[dbColumn].ReadOnly) or
        (Columns[dbColumn] <> Nil) and (Columns[dbColumn].ReadOnly);

      customEdit.Visible := False;
      UpdateCustomEdit;

      // Force handle of custom control to be created as we are going to call perform
      if not customEdit.HandleAllocated then
        customEdit.HandleNeeded; // showmessage('');

      customEdit.Perform(wm_size, SIZE_RESTORED, 0);
      { Forces SetEditRect to be called }
    end
  end;

  // inherited ColEnter; { 3/9/00 - should not colenter in this routine }
end;

procedure TwwDBGrid.CreateWnd;
begin
  inherited CreateWnd;
  if FDirtyIni then // 9/30/99 - Added LoadfromIniFile after form create.
  begin
    if IniAttributes.Enabled then
      LoadFromIniFile;
    FDirtyIni := False;
  end;
end;

procedure TwwDBGrid.CalcRowHeight;
begin
  inherited CalcRowHeight;
end;

procedure TwwDBGrid.FlushChanges;
begin
  inherited FlushChanges;
  if (CurrentCustomEdit <> Nil) and (CurrentCustomEdit.Visible) then
    CurrentCustomEdit.Perform(CM_Exit, 0, 0);
end;

{ 1/28/97 - Allow Change of text when protected flag in richtext }
procedure TRichProtectClass.RichProtectChange(Sender: TObject;
  StartPos, EndPos: integer; var AllowChange: Boolean);
begin
  AllowChange := True;
end;

procedure TwwDBGrid.GetControlInfo(AFieldName: string; var AControlType: string;
  var AParameters: string);
begin
  inherited;
  if (ecoDisableCustomControls in EditControlOptions) and
    IsWWEditControl(AControlType) then
    AControlType := '';
end;

procedure TwwDBGrid.DoCreateDateTimePicker(ADateTimePicker
  : TwwDBCustomDateTimePicker);
begin
  if Assigned(FOnCreateDateTimePicker) then
    FOnCreateDateTimePicker(self, FDateTimePicker);
end;

function TwwDBGrid.GetDateTimePicker: TwwDBCustomDateTimePicker;
begin
  if FDateTimePicker = nil then
  begin
    FDateTimePicker := CreateDateTimePicker;
    DoCreateDateTimePicker(FDateTimePicker);
  end;
  result := FDateTimePicker;
end;

function TwwDBGrid.CreateDateTimePicker: TwwDBCustomDateTimePicker;
begin
  result := TwwDBDateTimePicker.Create(self);
end;

procedure TwwMemoDialog.SetLines(val: TStrings);
begin
  if FLines <> val then
    FLines.assign(val);
end;

procedure TwwIniAttributes.SetFileName(val: string);
begin
  if val <> FFileName then
  begin
    FFileName := val;
  end;
end;

procedure TwwIniAttributes.SetSectionName(val: string);
begin
  if val <> FSectionName then
  begin
    FSectionName := val;
  end;
end;

function TwwIniAttributes.GetSectionName: string;
begin
  if FSectionName = '' then
  begin
    if not(csDesigning in Owner.ComponentState) then
      result := FFormName + Owner.name;
  end
  else
    result := FSectionName;
end;

function TwwIniAttributes.GetFileName: string;
begin
  if (FFileName = '') and wwInternational.connected then
  begin
    result := wwInternational.IniFileName;
    if not FSaveToRegistry then
      if (result = '') and not(csDesigning in Owner.ComponentState) then
        result := wwExtractFileNameOnly(Application.ExeName) + '.ini'
  end
  else
    result := FFileName;
end;

procedure TwwIniAttributes.SetEnabled(val: Boolean);
begin
  if val <> FEnabled then
  begin
    FEnabled := val;
    // NotifyOwnerWindow(Owner);
  end;
end;

procedure TwwIniAttributes.SetDelimiter(val: string);
begin
  if val <> FDelimiter then
  begin
    FDelimiter := val;
    // NotifyOwnerWindow(Owner);
  end;
end;

procedure TwwDBGrid.CalcSizingState(X, Y: integer; var State: TGridState;
  var Index: Longint; var SizingPos, SizingOfs: integer;
  var FixedInfo: TGridDrawInfo);
var
  SkipInherited: Boolean;

  function XOutsideHorzFixedBoundary: Boolean;
  begin
    with FixedInfo do
      if not UseRightToLeftAlignment then
        result := X > Horz.FixedBoundary + 3
      else
        result := X < clientwidth - Horz.FixedBoundary;
    // result:= True;
  end;

  function XOutsideOrEqualHorzFixedBoundary: Boolean;
  begin
    with FixedInfo do
      if not UseRightToLeftAlignment then
        result := X >= Horz.FixedBoundary
      else
        result := X <= clientwidth - Horz.FixedBoundary;
  end;

  procedure CalcAxisState(const AxisInfo: TGridAxisDrawInfo; Pos: integer;
    NewState: TGridState);
  var
    i, Line, Back, Range: integer;
    FIndicatorOffset: integer;
    IndicatorWidth: integer;
  begin
    if UseRightToLeftAlignment then
      Pos := clientwidth - Pos;
    with AxisInfo do
    begin
      Line := 0; // FixedBoundary;
      Range := EffectiveLineWidth;
      Back := 0;
      if Range < 7 then
      begin
        Range := 7;
        Back := (Range - EffectiveLineWidth) shr 1;
      end;
      FIndicatorOffset := 0;
      if dgIndicator in Options then
        FIndicatorOffset := 1;
      for i := 0 to GridCellCount - 1 do
      begin
        Inc(Line, GetExtent(i));
        if Line > GridBoundary then
          break;
        if (Pos >= Line - Back) and (Pos <= Line - Back + Range) and
          (i >= FIndicatorOffset) then
        begin
          State := NewState;
          SizingPos := Line;
          SizingOfs := Line - Pos;
          Index := i;
          SkipInherited := True;
          exit;
        end;
        Inc(Line, EffectiveLineWidth);
      end;
      // 5/8/2002- Handle border case for FixedCols when it is sized wider than grid.
      if (GridBoundary = GridExtent) and (Pos >= GridExtent - Back) and
        (Pos <= GridExtent) then
      begin
        State := NewState;
        SizingPos := GridExtent;
        SizingOfs := GridExtent - Pos;
        // 5/8/2002 - Check to see if we are in fixed area.
        if not XOutsideOrEqualHorzFixedBoundary then
        begin
          IndicatorWidth := FIndicatorOffset * ColWidthsPixels[0];
          // 5/8/2002 - Handle case where 1st fixed column fills grid
          if (IndicatorWidth + ColWidthsPixels[FIndicatorOffset]) >
            GridBoundary then
          begin
            Index := 1;
          end
          else
            Index := LastFullVisibleCell - 1
        end
        else
        begin
          // 5/8/2002 - Handle case where 1st fixed column fills grid
          if FixedBoundary = FullVisBoundary then
            Index := LastFullVisibleCell
          else
            Index := LastFullVisibleCell + 1;
        end;
        SkipInherited := True;
      end;
    end;
  end;

begin
  State := gsNormal;
  Index := -1;
  SkipInherited := False;
  if dgColumnResize in Options then
  begin
    with FixedInfo do
    begin
      Vert.GridExtent := ClientHeight;
      Horz.GridExtent := clientwidth;
      // 5/8/2002 - If not fixedresizable check only call if X is not in fixed area.

      // 9/4/2002 - RSW - Only override for fixed columns
      if (dgFixedResizable in Options) and (not XOutsideHorzFixedBoundary) then
      begin
        if (Y >= Vert.FixedBoundary) then
        begin
          // 10/21/02 - Check row resize
          if not(dgRowResize in Options) then
            SkipInherited := True;
        end
        else
          CalcAxisState(Horz, X, gsColSizing);
      end
    end;
  end;
  if not SkipInherited then
    inherited;
end;

procedure TwwDBGrid.DrawSizingLine(const DrawInfo: TGridDrawInfo);
var
  OldPen: TPen;
begin
  OldPen := TPen.Create;
  try
    with Canvas, DrawInfo do
    begin
      OldPen.assign(Pen);
      Pen.Style := psDot;
      Pen.Mode := pmXor;
      Pen.Width := 1;
      try
        if FGridState = gsRowSizing then
        begin
          MoveTo(0, FSizingPos);
          LineTo(Horz.GridBoundary, FSizingPos);
        end
        else
        begin
          MoveTo(FSizingPos, 0);
          LineTo(FSizingPos, Vert.GridBoundary);
        end;
      finally
        Pen := OldPen;
      end;
    end;
  finally
    OldPen.Free;
  end;
end;

procedure TwwDBGrid.MouseMove(Shift: TShiftState; X, Y: integer);
var
  DrawInfo: TGridDrawInfo;
  NewSize: integer;
  // OldCaptionWidth: integer;

  function ResizeLine(const AxisInfo: TGridAxisDrawInfo): integer;
  var
    i: integer;
  begin
    with AxisInfo do
    begin
      result := 0;
      for i := 0 to FSizingIndex - 1 do
        Inc(result, GetExtent(i) + EffectiveLineWidth);
      result := FSizingPos - result;
    end;
  end;

begin
  if (dgFixedResizable in Options) and (FGridState = gsColSizing) and
    (FSizingIndex <> -1) then
  begin
    CalcDrawInfo(DrawInfo);
    NewSize := ResizeLine(DrawInfo.Horz);
    if (NewSize > 1) then
    begin
    end;
    FSizingPos := X + FSizingOfs;
  end;
  inherited MouseMove(Shift, X, Y);
end;

procedure TwwDBGrid.ClearControls;
begin
  wwClearControls(self);
  RefreshDisplay;
end;

procedure TwwDBGrid.RestoreDesignSelected;
begin
  Selected.assign(OrigSelected);
  ApplySelected;
end;

procedure TwwDBGrid.ShowCurrentControl;
var
  tc: TWinControl;
  canShow: boolean;
begin
  if focused and (not SuppressShowEditor) and (not isMemoField(col, row)) and
  { 11/25/96 - Memo fields should never show editor }
    (dgAlwaysShowEditor in Options) and (not IsWWControl(col, row)) then
  begin
    CurrentCustomEdit := nil;
    if (InplaceEditor <> nil) and InplaceEditor.focused then
      InplaceEditor.update
    else
    begin
      ShowEditor;
    end
  end
  else
  begin
    // Otherwise not painted with selection in some cases
    // i.e. richedit first column and then tab
    if (CurrentCustomEdit = nil) and (InplaceEditor <> nil) and
      InplaceEditor.focused then
      InplaceEditor.update
    else if (CurrentCustomEdit <> nil) and FFocused then
    begin
      CanShow:=true;
      DoOnCanShowCustomControl(CurrentCustomEdit, self.GetActiveField, canShow);
      if not canShow then
      begin
          MakeCustomControlVisible:=false;
          CurrentCustomEdit.visible:=false;
      end;

      if (not CurrentCustomEdit.Visible) and CanShow then
      begin
        if MakeCustomControlVisible then
        begin
          CurrentCustomEdit.Visible := True;
        end
        else
        begin
          CurrentCustomEdit.left := Width + 10;
          CurrentCustomEdit.Visible := True;
        end;
        TEdit(CurrentCustomEdit).modified := False;
      end;

      // if (dgAlwaysShowEditor in Options) and
      // 3/11/01- Child Inspector seems to require refresh call so
      // code changed to always call refresh.  If find problems
      // then should check specifically for inspector.
      if MakeCustomControlVisible then
      begin
        if (dgAlwaysShowEditor in Options) then
        begin
          if not IsDropDownGridShowing then
          begin
            // 7/18/01 - if datainspector child edit control then don't reset focus
            // 8/2/01 - Fix problem with custom controls not respectiing alwaysshoweditor
            if wwIsClass(CurrentCustomEdit.classType, 'TwwDataInspector') then
            begin
              tc := screen.activecontrol;
              if (tc = nil) or (tc.parent <> CurrentCustomEdit) then
                CurrentCustomEdit.setFocus;
            end
            else
            begin
              // 8/22/01 - Don't set focus if active form is not for this grid
              // For instance, in case there was validation in the tfield.onvalidate event
              // which showed a message
              // 5/16/02 - Add test to see if the grid is the activecontrol before setting focus.
              // if (screen.activeform=getparentform(self)) then
              if (screen.ActiveForm = GetParentForm(self)) and
                IsActiveControl then
                CurrentCustomEdit.setFocus;
            end

          end
        end;
        CurrentCustomEdit.refresh;
      end;

      HideEditor;
    end;
  end;
end;

procedure TwwDBGrid.WMPaint(var Message: TWMPaint);
begin
  inherited;
  ShowCurrentControl;
end;

procedure TwwDBGrid.SetOrder(AFieldName: string; AClientCursor: Boolean);

  procedure SetIndexFieldNames(val: string);
  var
    PropInfo: PPropInfo;
  begin
    PropInfo := typinfo.GetPropInfo(DataSource.dataset.ClassInfo,
      'IndexFieldNames');
    if PropInfo <> Nil then
      SetStrProp(DataSource.dataset, PropInfo, val);
  end;

  function UseAllFields(value: TDataset): Boolean;
  begin
    result := wwIsClass(value.classType, 'TClientDataSet') or AClientCursor or
      ((wwGetDatabaseCursorType(value) = 1) and
      (wwIsClass(value.classType, 'TADOTable') or wwIsClass(value.classType,
      'TADODataSet')));
  end;

begin

  if (DataSource <> nil) and (DataSource.dataset <> nil) then
  begin
    if UseAllFields(DataSource.dataset) then
      SetIndexFieldNames(AFieldName)
    else
      wwSetTableIndex(DataSource.dataset, AFieldName);
  end;
end;

initialization

{ RegisterClass(TwwIButton); }
end.

