
{*******************************************************}
{                                                       }
{       TRVRuler: ruler for TRichViewEdit               }
{       Inherited from TRuler by Pieter Zijlstra.       }
{       Created by Sergey Tkachenko, basing on example  }
{       code by Pieter Zijlstra.                        }
{                                                       }
{       Sergey Tkachenko                                }
{       svt@trichview.com                               }
{       http://www.trichview.com                        }
{                                                       }
{       Pieter Zijlstra                                 }
{       p.zylstra@hccnet.nl                             }
{       http://home.hccnet.nl/p.zylstra/                }
{                                                       }
{*******************************************************}

{ --------------------------------------------------------------------------
  v1.1.1.0
  v1.4.0.0 - changed LeftInset to Inset
  v1.5.0.0 - added TableEditor
  v1.6.0.0 - added tabs
  v1.7.0.0 - improved table resizing
           - added support for Bullets & Numbering.
  v1.7.1.0 - Fix: the local OnCaretMove did not call the old handler.
  v1.7.3.0 - Added two extra checks to see if FRichViewEdit is valid.
  v1.7.5.0 - Added support for handling ruler in vertical mode.
             New: property RVRulerOptions.
  v1.7.5.1 - Minor code optimizations.
           - Scroll document when top or bottom margin has changed so that the
             document lines-up with the new top or bottom margin position.
  v1.7.5.2 - Added On***Click event handlers.
  v1.7.5.3 - New: RVRulerItemSelector, this is a component for selecting the
                  type of tab to be used when new tabs are added.
  v1.7.5.5 - It is now using the new cell properties CellHPadding and
             CellVPadding. For older richview version see RVTABLEOLDCELLPADDING.
  v1.7.6.0 - Modifications needed for new parameters in the following
             procedures; DoRulerItemMove(), DoRulerItemRelease() and
             DoRulerItemSelect(). This also enables TRVRuler to selectively
             switch on/off the inplace editor of tables. This prevents the
             dotted line to fall behind the inplace editor when a column/row
             is being moved.
  v1.7.6.1 - Fix: When PageWidth property of the ruler was changed
                  by code, the linked TRichViewEdit was not updated.
  v1.7.6.2 - Fix: When a vertical ruler and a horizontal ruler were attached
                  and then detached restoring of the original events went wrong.                   
  v1.7.7.1 - Fix: Added extra check on (FRichViewEdit.InplaceEditor <> nil) in
                  DoTableColumnChanged to prevent AVs.
             Fix: Restore the inplace editor also in DoTableColumnChanged.
                  The call in DoRulerItemReleased was too late but is still
                  needed in case no table column was changed.
  --------------------------------------------------------------------------
  What does this ruler change:
  - Left, first, right indents of the selected paragraphs
  - RichViewEdit.LeftMargin, .RightMargin. Note: they are not paper margins,
    they are "padding" of printable area.
  - If rvoClientTextWidth is not in RichViewEdit.Options, it changes
    RichViewEdit.MaxTextWidth and RichViewEdit.MinTextWidth. Note: they do
    not affect printing.
  --------------------------------------------------------------------------
  Not implemented yet:
  - ...
  Working with TDBRichViewEdit is not tested.
  --------------------------------------------------------------------------

  Properties:
  RichViewEdit: TCustomRichViewEdit - attached editor;
  LineStyle: TPenStyle - style of lines.

  Methods:
  UpdateRulerIndents - updates ruler's indents from RichViewEdit.
  Rarely needs to be called.

  UpdateRulerMargins - updates ruler's margins from RichViewEdit
  Must be called when margins of editor were changed, for example, after
  reading RVF file.

  --------------------------------------------------------------------------

  Technical notes:

  This component changes the following events of the assigned RichViewEdit:
  OnCaretMove, OnHScrolled, OnVScrolled, OnResize, OnCurParaStyleChanged.
  Its handlers for these events call old handlers. But you must not change
  these events at run time after Ruler.RichViewEdit has been assigned.
}

unit RVRuler;

interface

{$I RV_Defs.inc}

{.$define RVTABLEOLDCELLPADDING}

uses
  Windows, Classes, Graphics, Controls,
  Ruler,
  RVStyle, RVScroll, RichView, RVEdit,
  RVItem, RVTable, RVMarker;

type
  TRVRulerOption = (
    rvroAutoAdjustInset,
    rvroAutoAdjustMargins,
    rvroAutoAdjustPageSize
  );
  TRVRulerOptions = set of TRVRulerOption;

const
  DefaultRVRulerOptions = [rvroAutoAdjustInset..rvroAutoAdjustPageSize];

type
  TRVRuler = class(TRuler)
  private
    FControlCanvas: TControlCanvas;
    FFirstIndent, FLeftIndent, FRightIndent: Integer;
    FInPlaceEditorSaved: Boolean;
    FLineStyle: TPenStyle;
    FRichViewEdit: TCustomRichViewEdit;
    FRVEOldCaretMove,
    FRVEOldHScrolled,
    FRVEOldVScrolled,
    FRVEOldResize,
    FRVEOldCurParaStyleChanged: TNotifyEvent;
    FSaveX: Integer;
    FUpdating, FUpdatingTabs: Boolean;
    FRulerInset: Integer;
    FRVRulerOptions: TRVRulerOptions;
    procedure AssignRichViewEditEvents;
    procedure DrawMarkLine;
    function  GetListIndents(var FI, LI: Extended): Boolean;
    procedure RichViewEditCaretMove(Sender: TObject);
    procedure RichViewEditCurParaStyleChanged(Sender: TObject);
    procedure RichViewEditHScrolled(Sender: TObject);
    procedure RichViewEditVScrolled(Sender: TObject);
    procedure RichViewEditParaStyleConversion(Sender: TCustomRichViewEdit;
      StyleNo, UserData: Integer; AppliedToText: Boolean; var NewStyleNo: Integer);
    {$IFDEF RICHVIEWDEF4}
    procedure RichViewEditResize(Sender: TObject);
    {$ENDIF}
    procedure RestoreRichViewEditEvents;
    procedure SetRichViewEdit(const Value: TCustomRichViewEdit);
    procedure SetRVRulerOptions(const Value: TRVRulerOptions);
  protected
    procedure DoBiDiModeChanged; override;
    procedure DoIndentChanged; override;
    procedure DoLevelButtonUp(Direction: Integer); override;
    procedure DoMarginChanged; override;
    procedure DoPageWidthChanged; override;
    procedure DoTabChanged; override;
    procedure DoRulerItemMove(DragItem: TDragItem; X: Integer; Removing: Boolean); override;
    procedure DoRulerItemRelease(DragItem: TDragItem); override;
    procedure DoRulerItemSelect(DragItem: TDragItem; X: Integer); override;
    procedure DoTableColumnChanged; override;
    procedure DoTableRowChanged; override;
    procedure Loaded; override;
    procedure Notification(AComponent: TComponent; Operation: TOperation); override;
  public
    constructor Create(AOwner: TComponent); override;
    procedure AdjustPosition;
    procedure UpdateRulerIndents;
    procedure UpdateRulerMargins;
    procedure UpdateTableEditor;
    procedure UpdateListEditor;
  published
    property Inset default 0;
    property LineStyle: TPenStyle read FLineStyle write FLineStyle default psDot;
    property ListEditor;
    property OnTableRowClick;
    property OnTableRowDblClick;
    property RichViewEdit: TCustomRichViewEdit read FRichViewEdit write SetRichViewEdit;
    property RVRulerOptions: TRVRulerOptions read FRVRulerOptions write SetRVRulerOptions default DefaultRVRulerOptions;
    property TableEditor;
  end;

  TRVRulerItemSelector = class(TRulerItemSelector)
  end;

implementation

uses
  RVClasses, RVTInplace;

function Min(const A, B: Integer): Integer;
begin
  if A < B then
    Result := A
  else
    Result := B;
end;

{=============================== TRVRuler =====================================}
constructor TRVRuler.Create(AOwner: TComponent);
begin
  inherited;
  RVRulerOptions := DefaultRVRulerOptions;
  Inset := 0;
  FLineStyle := psDot;
end;
{------------------------------------------------------------------------------}
procedure TRVRuler.Notification(AComponent: TComponent;
  Operation: TOperation);
begin
  inherited;
  if (Operation = opRemove) and (AComponent = FRichViewEdit) then
  begin
    RestoreRichViewEditEvents;
    FRichViewEdit := nil;
  end;
end;
{------------------------------------------------------------------------------}
procedure TRVRuler.Loaded;
begin
  inherited;
  AssignRichViewEditEvents;
end;
{------------------------------------------------------------------------------}
procedure TRVRuler.DoBiDiModeChanged;
begin
  inherited;
  AdjustPosition;
end;
{------------------------------------------------------------------------------}
procedure TRVRuler.DoLevelButtonUp(Direction: Integer);
var
  RVEditor: TCustomRichViewEdit;
begin
  inherited;
  if not Assigned(FRichViewEdit) then
    Exit;
  RVEditor := FRichViewEdit.TopLevelEditor;
  RVEditor.BeginUndoGroup(rvutPara);
  RVEditor.SetUndoGroupMode(True);
  try
    inherited;
    RVEditor.ChangeListLevels(Direction);
  finally
    RVEditor.SetUndoGroupMode(False);
  end;
end;
{------------------------------------------------------------------------------}
procedure TRVRuler.DoRulerItemSelect(DragItem: TDragItem; X: Integer);
var
  RVEditor: TCustomRichViewEdit;
  RVItem: TCustomRVItemInfo;
begin
  inherited;
  if not Assigned(RichViewEdit) then
    Exit;

  if DragItem in [diBorder, diColumn, diRow] then
    if FRichViewEdit.GetCurrentItemEx(TRVTableItemInfo, RVEditor, RVItem) then
    begin
      TRVTableItemInfo(RVItem).SaveInplace();
      FInPlaceEditorSaved := True;
    end;

  RichViewEdit.Update;

  if not Assigned(FControlCanvas) then
    FControlCanvas := TControlCanvas.Create;
  with FControlCanvas do
  begin
    Control := RichViewEdit;
    Pen.Color := clBlack;
    Pen.Mode := pmXor;
    Pen.Style := LineStyle;
  end;
  FSaveX := X - FRulerInset;
  DrawMarkLine;
end;
{------------------------------------------------------------------------------}
procedure TRVRuler.DoRulerItemMove(DragItem: TDragItem; X: Integer; Removing: Boolean);
begin
  inherited;
  if not Assigned(RichViewEdit) then
    Exit;
  DrawMarkLine;
  if Removing then
    FSaveX := -1
  else
    FSaveX := X - FRulerInset;
  DrawMarkLine;
end;
{------------------------------------------------------------------------------}
procedure TRVRuler.DoRulerItemRelease(DragItem: TDragItem);
var
  RVEditor: TCustomRichViewEdit;
  RVItem: TCustomRVItemInfo;
begin
  inherited;
  DrawMarkLine;
  FControlCanvas.Free;
  FControlCanvas := nil;

  if FInPlaceEditorSaved then
    if FRichViewEdit.GetCurrentItemEx(TRVTableItemInfo, RVEditor, RVItem) then
    begin
      FInPlaceEditorSaved := False;
      TRVTableItemInfo(RVItem).RestoreInplace();
    end;

  if Assigned(FRichViewEdit) then
    FRichViewEdit.Refresh;
end;
{------------------------------------------------------------------------------}
procedure TRVRuler.DrawMarkLine;
begin
  if Assigned(FControlCanvas) then
    with FControlCanvas do
      if RulerType = rtHorizontal then
      begin
        if FSaveX >= 0 then
        begin
          MoveTo(FSaveX - FRichViewEdit.HScrollPos, 0);
          LineTo(FSaveX - FRichViewEdit.HScrollPos, RichViewEdit.Height);
        end;
      end
      else
      begin
        if FSaveX >= 0 then
        begin
          MoveTo(0,
            FSaveX - FRichViewEdit.VScrollPos * FRichViewEdit.VSmallStep);
          LineTo(RichViewEdit.Width,
            FSaveX - FRichViewEdit.VScrollPos * FRichViewEdit.VSmallStep);
        end;
      end;
end;
{------------------------------------------------------------------------------}
procedure TRVRuler.DoMarginChanged;
var
  DT, DB: Integer;
  OldVPos: Integer;
  BM, TM: Integer;
begin
  inherited;
  if not Assigned(FRichViewEdit) or FUpdating      then Exit;
  if not (rvroAutoAdjustMargins in RVRulerOptions) then Exit;

  if (rvfoSaveLayout in FRichViewEdit.RVFOptions) and
     not FRichViewEdit.CanChange then
    Exit;

  if RulerType = rtHorizontal then
  begin
    DT := 0;
    DB := 0;
    FRichViewEdit.LeftMargin := Round(UnitsToPixs(LeftMargin));
    FRichViewEdit.RightMargin := Round(UnitsToPixs(RightMargin));
    if not (rvoClientTextWidth in FRichViewEdit.Options) then
    begin
      FRichViewEdit.MinTextWidth :=
        Round(UnitsToPixs(PageWidth) - FRichViewEdit.LeftMargin - FRichViewEdit.RightMargin);
      FRichViewEdit.MaxTextWidth := FRichViewEdit.MinTextWidth;
    end;
  end
  else
  begin
    TM := Round(UnitsToPixs(TopMargin));
    BM := Round(UnitsToPixs(BottomMargin));
    DT := TM - FRichViewEdit.TopMargin;
    DB := BM - FRichViewEdit.BottomMargin;
    FRichViewEdit.TopMargin := TM;
    FRichViewEdit.BottomMargin := BM;
  end;
  FRichViewEdit.RVData.Format_(True, True, False, 0, FRichViewEdit.Canvas, False,
    False, False);
  if rvfoSaveLayout in FRichViewEdit.RVFOptions then
    FRichViewEdit.Change;

  // Scroll document in to position (line-up with new top or bottom margin pos).
  OldVPos := FRichViewEdit.VScrollPos * FRichViewEdit.VSmallStep;
  if DT <> 0 then
  begin
    if not ((OldVPos = 0) and (DT < 0)) then
      FRichViewEdit.ScrollTo(OldVPos - DT)
  end
  else
    with FRichViewEdit do
      if (DB <> 0) then
        if not (((VScrollMax - VScrollPos) <= VSmallStep) and (DB < 0)) then
          FRichViewEdit.ScrollTo(OldVPos + DB);

  UpdateRulerMargins;
  UpdateTableEditor;
end;
{------------------------------------------------------------------------------}
procedure TRVRuler.DoPageWidthChanged;
begin
  inherited;
  if not Assigned(FRichViewEdit) or FUpdating      then Exit;
  if not (rvroAutoAdjustPageSize in RVRulerOptions) then Exit;

  if (rvfoSaveLayout in FRichViewEdit.RVFOptions) and
     not FRichViewEdit.CanChange then
    Exit;

  if RulerType = rtHorizontal then
  begin
    if not (rvoClientTextWidth in FRichViewEdit.Options) then
    begin
      FRichViewEdit.MinTextWidth :=
        Round(UnitsToPixs(PageWidth) - FRichViewEdit.LeftMargin - FRichViewEdit.RightMargin);
      FRichViewEdit.MaxTextWidth := FRichViewEdit.MinTextWidth;
    end;

    FRichViewEdit.RVData.Format_(True, True, False, 0, FRichViewEdit.Canvas, False,
      False, False);
    if rvfoSaveLayout in FRichViewEdit.RVFOptions then
      FRichViewEdit.Change;

    UpdateRulerMargins;
    UpdateTableEditor;
  end;
end;
{------------------------------------------------------------------------------}
procedure TRVRuler.DoTabChanged;
begin
  FUpdatingTabs := True;
  try
    DoIndentChanged
  finally
    FUpdatingTabs := False;
  end;
end;
{------------------------------------------------------------------------------}
procedure TRVRuler.DoIndentChanged;
var
  FirstParaItemNo: Integer;
  I: Integer;
  ListLevel: Integer;
  ListNo: Integer;
  ListStyle: TRVListInfo;
  M: Extended;
  OldStyleConversionEvent: TRVStyleConversionEvent;
  RVEditor: TCustomRichViewEdit;
  RVListLevel: TRVListLevel;
  StartFrom: Integer;
  data: TRVDeleteUnusedStylesData;
  UseStartFrom: Boolean;
begin
  inherited;
  if not Assigned(FRichViewEdit) or FUpdating then
    Exit;

  RVEditor := FRichViewEdit.TopLevelEditor;
  M := MultiPixels; // TRichView is based on pixels
  if UseRTL then
  begin
    FFirstIndent := Round(M * FirstIndent);
    FRightIndent := Round(M * LeftIndent + FFirstIndent);
    FFirstIndent := FFirstIndent - FRightIndent;
    FLeftIndent  := Round(M * RightIndent);
  end
  else
  begin
    FFirstIndent := Round(M * FirstIndent);
    FLeftIndent  := Round(M * LeftIndent + FFirstIndent);
    FFirstIndent := FFirstIndent - FLeftIndent;
    FRightIndent := Round(M * RightIndent);
  end;

  if ListEditor.Active then
  begin
    FirstParaItemNo := RVEditor.CurItemNo;
    while not RVEditor.IsParaStart(FirstParaItemNo) do
      Dec(FirstParaItemNo);
    RVEditor.GetListMarkerInfo(FirstParaItemNo, ListNo, ListLevel, StartFrom, UseStartFrom);

    ListStyle:= TRVListInfo.Create(nil);
    try
      ListStyle.Assign(RVEditor.Style.ListStyles[ListNo]);

      RVListLevel := ListStyle.Levels[ListEditor.ListLevel];
      if UseRTL then
        RVListLevel.LeftIndent := FRightIndent
      else
        RVListLevel.LeftIndent := FLeftIndent;
      RVListLevel.FirstIndent := FFirstIndent;

      if not ListStyle.HasNumbering then
        ListNo := FRichViewEdit.Style.ListStyles.FindSuchStyle(ListStyle, True)
      else
      begin
        data := TRVDeleteUnusedStylesData.Create(False, False, True);
        FRichViewEdit.MarkStylesInUse(data);
        ListNo := -1;
        for I := 0 to FRichViewEdit.Style.ListStyles.Count - 1 do
          if (data.UsedListStyles[I] = 0) and
             FRichViewEdit.Style.ListStyles[I].IsSimpleEqual(ListStyle, False, True) then
          begin
            ListNo := I;
            Break;
          end;
        data.Free;
        if ListNo < 0 then
        begin
          FRichViewEdit.Style.ListStyles.Add.Assign(ListStyle);
          ListNo := FRichViewEdit.Style.ListStyles.Count - 1;
          FRichViewEdit.Style.ListStyles[ListNo].Standard := False;
        end;
      end;

      FRichViewEdit.ApplyListStyle(ListNo, ListLevel, StartFrom, UseStartFrom, False);
    finally
      ListStyle.Free;
    end;
  end;

  OldStyleConversionEvent := RVEditor.OnParaStyleConversion;
  try
    RVEditor.OnParaStyleConversion := RichViewEditParaStyleConversion;
    RVEditor.ApplyParaStyleConversion(0);
  finally
    RVEditor.OnParaStyleConversion := OldStyleConversionEvent;
  end;
end;
{------------------------------------------------------------------------------}
procedure TRVRuler.SetRichViewEdit(const Value: TCustomRichViewEdit);
begin
  RestoreRichViewEditEvents;
  FRichViewEdit := Value;
  if not (csLoading in ComponentState) then
    AssignRichViewEditEvents;
end;
{------------------------------------------------------------------------------}
procedure TRVRuler.SetRVRulerOptions(const Value: TRVRulerOptions);
begin
  if FRVRulerOptions <> Value then
  begin
    FRVRulerOptions := Value;
    Invalidate;
  end;
end;
{------------------------------------------------------------------------------}
procedure TRVRuler.AssignRichViewEditEvents;
begin
  if not Assigned(FRichViewEdit) or (csDesigning in ComponentState) then
    Exit;
  if RulerType = rtHorizontal then
  begin
    FRVEOldCaretMove := TRichViewEdit(FRichViewEdit).OnCaretMove;
    FRVEOldHScrolled := TRichViewEdit(FRichViewEdit).OnHScrolled;
    TRichViewEdit(FRichViewEdit).OnCaretMove := RichViewEditCaretMove;
    TRichViewEdit(FRichViewEdit).OnHScrolled := RichViewEditHScrolled;
    if Assigned(FRichViewEdit.Style) then
      DefaultTabWidth := FRichViewEdit.Style.DefTabWidth/MultiPixels;
  end
  else
  begin
    FRVEOldVScrolled := TRichViewEdit(FRichViewEdit).OnVScrolled;
    TRichViewEdit(FRichViewEdit).OnVScrolled := RichViewEditVScrolled;
  end;

  FRVEOldCurParaStyleChanged := TRichViewEdit(FRichViewEdit).OnCurParaStyleChanged;
  TRichViewEdit(FRichViewEdit).OnCurParaStyleChanged := RichViewEditCurParaStyleChanged;

  {$IFDEF RICHVIEWDEF4}
  FRVEOldResize := TRichViewEdit(FRichViewEdit).OnResize;
  TRichViewEdit(FRichViewEdit).OnResize := RichViewEditResize;
  {$ENDIF}
end;
{------------------------------------------------------------------------------}
function CanRestore(E1, E2: TNotifyEvent): Boolean;
begin
  Result := @E1 <> @E2;
end;
{------------------------------------------------------------------------------}
procedure TRVRuler.RestoreRichViewEditEvents;
begin
  if not Assigned(FRichViewEdit) or (csDesigning in ComponentState) then
    Exit;
  if RulerType = rtHorizontal then
  begin
    if CanRestore(FRVEOldCaretMove, RichViewEditCaretMove) then
      TRichViewEdit(FRichViewEdit).OnCaretMove := FRVEOldCaretMove;
    if CanRestore(FRVEOldHScrolled, RichViewEditHScrolled) then
      TRichViewEdit(FRichViewEdit).OnHScrolled := FRVEOldHScrolled;
  end
  else
    if CanRestore(FRVEOldVScrolled, RichViewEditVScrolled) then
      TRichViewEdit(FRichViewEdit).OnVScrolled := FRVEOldVScrolled;

  if CanRestore(FRVEOldCurParaStyleChanged, RichViewEditCurParaStyleChanged) then
    TRichViewEdit(FRichViewEdit).OnCurParaStyleChanged := FRVEOldCurParaStyleChanged;

  {$IFDEF RICHVIEWDEF4}
  if CanRestore(FRVEOldResize, RichViewEditResize) then
    TRichViewEdit(FRichViewEdit).OnResize := FRVEOldResize;
  {$ENDIF}

  FRVEOldCaretMove := nil;
  FRVEOldCurParaStyleChanged := nil;
  FRVEOldResize := nil;
  FRVEOldHScrolled := nil;
  FRVEOldVScrolled := nil;
end;
{------------------------------------------------------------------------------}
procedure TRVRuler.AdjustPosition;
var pt: TPoint;
begin
  if not Assigned(FRichViewEdit)                 then Exit;
  if not (rvroAutoAdjustInset in RVRulerOptions) then Exit;

  if RulerType = rtHorizontal then
  begin
    pt := FRichViewEdit.ClientOrigin;
    pt := FRichViewEdit.Parent.ScreenToClient(pt);
    Dec(pt.X, FRichViewEdit.Left);
    FRulerInset := -(FRichViewEdit.HScrollPos - pt.X);
    if UseRTL then
      with FRichViewEdit do
        Inset := Pred(Width - (pt.X + RVData.TextWidth + LeftMargin +
          RightMargin) + HScrollPos)
    else
      Inset := FRulerInset;
  end
  else
  begin
    pt := FRichViewEdit.ClientOrigin;
    pt := FRichViewEdit.Parent.ScreenToClient(pt);
    Dec(pt.Y, FRichViewEdit.Top);
    FRulerInset := pt.Y - FRichViewEdit.VScrollPos * FRichViewEdit.VSmallStep;
    Inset := FRulerInset;
  end;
end;
{------------------------------------------------------------------------------}
procedure TRVRuler.RichViewEditHScrolled(Sender: TObject);
begin
  AdjustPosition;
  if Assigned(FRVEOldHScrolled) then
    FRVEOldHScrolled(Sender);
end;
{------------------------------------------------------------------------------}
procedure TRVRuler.RichViewEditVScrolled(Sender: TObject);
begin
  AdjustPosition;
  if Assigned(FRVEOldVScrolled) then
    FRVEOldVScrolled(Sender);
end;
{------------------------------------------------------------------------------}
{$IFDEF RICHVIEWDEF4}
procedure TRVRuler.RichViewEditResize(Sender: TObject);
begin
  AdjustPosition;
  UpdateRulerMargins;
  UpdateTableEditor;
  if Assigned(FRVEOldResize) then
    FRVEOldResize(Sender);
end;
{$ENDIF}
{------------------------------------------------------------------------------}
procedure TRVRuler.RichViewEditCaretMove(Sender: TObject);
begin
  UpdateListEditor;
  if Assigned(FRVEOldCaretMove) then
    FRVEOldCaretMove(Sender);
end;
{------------------------------------------------------------------------------}
procedure TRVRuler.RichViewEditCurParaStyleChanged(Sender: TObject);
var RVBiDiMode: TRVBiDiMode;
begin
  if Assigned(FRVEOldCurParaStyleChanged) then
    FRVEOldCurParaStyleChanged(Sender);

  if FRichViewEdit.ItemCount = 0 then
    Exit;

  if RulerType = rtHorizontal then
  begin
    RVBiDiMode := FRichViewEdit.Style.ParaStyles[FRichViewEdit.CurParaStyleNo].BiDiMode;
    if RVBiDiMode = rvbdUnspecified then
      RVBiDiMode := FRichViewEdit.BiDiMode;
    if RVBiDiMode = rvbdRightToLeft then
      BiDiModeRuler := bmRightToLeft
    else
      BiDiModeRuler := bmUseBiDiMode;
  end
  else
    BiDiModeRuler := bmLeftToRight;

  UpdateTableEditor;
  UpdateRulerIndents;
  UpdateListEditor;
end;
{------------------------------------------------------------------------------}
procedure TRVRuler.RichViewEditParaStyleConversion(
  Sender: TCustomRichViewEdit; StyleNo, UserData: Integer;
  AppliedToText: Boolean; var NewStyleNo: Integer);
var ParaInfo: TParaInfo;
    {$IFNDEF RVDONOTUSETABS}
    i: Integer;
    M: Extended;
    {$ENDIF}
begin
  ParaInfo := TParaInfo.Create(nil);
  try
    ParaInfo.Assign(Sender.Style.ParaStyles[StyleNo]);

    if ListEditor.Active then
    begin
      if UseRTL then
        ParaInfo.LeftIndent  := FLeftIndent
      else
        ParaInfo.RightIndent := FRightIndent;
    end
    else
    begin
      ParaInfo.LeftIndent  := FLeftIndent;
      ParaInfo.FirstIndent := FFirstIndent;
      ParaInfo.RightIndent := FRightIndent;
    end;
    {$IFNDEF RVDONOTUSETABS}
    M := MultiPixels;
    ParaInfo.Tabs.Clear;
    for i := 0 to Tabs.Count - 1 do
      with ParaInfo.Tabs.Add do
      begin
        Position := Round(M * Tabs[i].Position);
        case Tabs[i].Align of
          taLeftAlign:   Align := rvtaLeft;
          taRightAlign:  Align := rvtaRight;
          taCenterAlign: Align := rvtaCenter;
        end;
      end;
    {$ENDIF}
    NewStyleNo := Sender.Style.ParaStyles.FindSuchStyle(StyleNo,ParaInfo,RVAllParaInfoProperties);
    if NewStyleNo = -1 then
    begin
      Sender.Style.ParaStyles.Add;
      NewStyleNo := Sender.Style.ParaStyles.Count-1;
      Sender.Style.ParaStyles[NewStyleNo].Assign(ParaInfo);
      Sender.Style.ParaStyles[NewStyleNo].Standard := False;
    end;
  finally
    ParaInfo.Free;
  end;
end;
{------------------------------------------------------------------------------}
procedure TRVRuler.UpdateRulerIndents;
var M: Extended;
    ParaNo: Integer;
    ParaStyles: TParaInfos;
    {$IFNDEF RVDONOTUSETABS}
    i: Integer;
    {$ENDIF}
begin
  if not Assigned(FRichViewEdit) or FUpdating or FUpdatingTabs or (RulerType <> rtHorizontal) then
    Exit;

  FUpdating := True;
  try
    ParaStyles := FRichViewEdit.Style.ParaStyles;
    ParaNo := FRichViewEdit.CurParaStyleNo;
    M := 1 / MultiPixels;
    if UseRTL then
    begin
      FirstIndent :=
        M * (ParaStyles[ParaNo].RightIndent + ParaStyles[ParaNo].FirstIndent);
      LeftIndent := M * -ParaStyles[ParaNo].FirstIndent;
      RightIndent := M * ParaStyles[ParaNo].LeftIndent;
    end
    else
    begin
      FirstIndent :=
        M * (ParaStyles[ParaNo].LeftIndent + ParaStyles[ParaNo].FirstIndent);
      LeftIndent  := M * -ParaStyles[ParaNo].FirstIndent;
      RightIndent := M * ParaStyles[ParaNo].RightIndent;
    end;

    {$IFNDEF RVDONOTUSETABS}
    i := ParaStyles[ParaNo].Tabs.Count;
    if i > 0 then
    begin
      Tabs.BeginUpdate;
      Tabs.Clear;
      try
        for i := 0 to i - 1 do
          with Tabs.Add do
          begin
            Position := M * ParaStyles[ParaNo].Tabs[i].Position;
            case ParaStyles[ParaNo].Tabs[i].Align of
              rvtaLeft:   Align := taLeftAlign;
              rvtaRight:  Align := taRightAlign;
              rvtaCenter: Align := taCenterAlign;
            end;
          end;
      finally
        Tabs.EndUpdate;
      end;
    end
    else
      Tabs.Clear;
    {$ENDIF}
  finally
    FUpdating := False;
  end;
end;
{------------------------------------------------------------------------------}
procedure TRVRuler.UpdateRulerMargins;
var M: Extended;
begin
  if not Assigned(FRichViewEdit) or FUpdating      then Exit;
  if not (rvroAutoAdjustMargins in RVRulerOptions) then Exit;

  FUpdating := True;
  try
    M := 1 / MultiPixels;
    if RulerType = rtHorizontal then
    begin
      LeftMargin := M * FRichViewEdit.LeftMargin;
      RightMargin := M * FRichViewEdit.RightMargin;
      PageWidth := M * (FRichViewEdit.RVData.TextWidth + FRichViewEdit.LeftMargin +
        FRichViewEdit.RightMargin);
    end
    else
    begin
      TopMargin := M * FRichViewEdit.TopMargin;
      BottomMargin := M * FRichViewEdit.BottomMargin;
      PageHeight := M * FRichViewEdit.RVData.DocumentHeight;
    end;
  finally
    FUpdating := False;
  end;
end;
{------------------------------------------------------------------------------}
procedure TRVRuler.DoTableColumnChanged;
var
  BaseTW: Integer;
  C, TW, CW: Integer;
  Data: Integer;
  M: Extended;
  ItemNo: Integer;
  OldBW: Integer;
  RVTableInplaceEdit: TRVTableInplaceEdit;
  RVEditor: TCustomRichViewEdit;
  RVItem: TCustomRVItemInfo;
  RVTable: TRVTableItemInfo;
begin
  inherited;
  if not Assigned(FRichViewEdit) or FUpdating or (RulerType <> rtHorizontal) then
    Exit;

  if TableEditor.Active and (TableEditor.DraggedDelta <> 0) then
  begin
    if FInPlaceEditorSaved then
      if FRichViewEdit.GetCurrentItemEx(TRVTableItemInfo, RVEditor, RVItem) then
      begin
        FInPlaceEditorSaved := False;
        TRVTableItemInfo(RVItem).RestoreInplace();
      end;

    M := MultiPixels;
    if FRichViewEdit.GetCurrentItemEx(TRVTableItemInfo, RVEditor, RVItem) then
    begin
      RVTable := TRVTableItemInfo(RVItem);
      ItemNo := RVEditor.GetItemNo(RVTable);
      RVEditor.BeginItemModify(ItemNo, Data);
      RVEditor.BeginUndoGroup(rvutModifyItem);
      RVEditor.SetUndoGroupMode(True);
      try
        TW := Round(M * TableEditor.TableWidth) + 2 * RVTable.BorderWidth;
        OldBW := RVTable.BestWidth;
        if OldBW < 0 then
        begin
          if (FRichViewEdit.InplaceEditor <> nil) and
             (TRVTableInplaceEdit(FRichViewEdit.InplaceEditor).InplaceEditor <> nil) then
          begin
            // Get the cell width of the table one level down.
            RVTableInplaceEdit := TRVTableInplaceEdit(FRichViewEdit.InplaceEditor);
            while TRVTableInplaceEdit(RVTableInplaceEdit.InplaceEditor).InplaceEditor <> nil do
              RVTableInplaceEdit := TRVTableInplaceEdit(RVTableInplaceEdit.InplaceEditor);
            BaseTW := RVTableInplaceEdit.FCell.Width;
          end
          else
            BaseTW := FRichViewEdit.RVData.TextWidth;
          RVTable.BestWidth := Pred(-100 * TW div BaseTW)
        end
        else
          RVTable.BestWidth := TW;
        with TableEditor do
        begin
          C := DraggedColumn;
          CW := Round(M * (Cells[C].DragBoundary + DraggedDelta +
            CellBorderWidth + CellHSpacing / 2));
          RVTable.ResizeCol(C, CW, DraggedWithShift);
        end;
        RVEditor.Change;
        // In case the table had a BestWidth of zero it needs to be restored
        // to enable RichView itself to be able to resize it.
        if OldBW = 0 then
        begin
          RVTable.BestWidth := 0;
          RVEditor.Change;
        end;
      finally
        RVEditor.SetUndoGroupMode(False);
      end;
      RVEditor.EndItemModify(ItemNo, Data);
    end;
  end;
  UpdateTableEditor;
end;
{------------------------------------------------------------------------------}
procedure TRVRuler.DoTableRowChanged;
var
  R, RH: Integer;
  M: Extended;
  RVEditor: TCustomRichViewEdit;
  RVItem: TCustomRVItemInfo;
  RVTable: TRVTableItemInfo;
begin
  inherited;
  if not Assigned(FRichViewEdit) or FUpdating or (RulerType <> rtVertical) then
    Exit;

  if TableEditor.Active and (TableEditor.DraggedDelta <> 0) then
  begin
    M := MultiPixels;
    if FRichViewEdit.GetCurrentItemEx(TRVTableItemInfo, RVEditor, RVItem) then
    begin
      RVTable := TRVTableItemInfo(RVItem);
      with TableEditor do
      begin
        R := DraggedRow;
        RH := Round(M * (Rows[R].RowHeight + CellBorderWidth + CellVSpacing / 2));
        RVTable.ResizeRow(R, RH);
      end;
    end;
  end;
  UpdateTableEditor;
end;
{------------------------------------------------------------------------------}
procedure TRVRuler.UpdateTableEditor;
var
  FI, LI: Extended;
  R, C, L, T, X, Y: Integer;
  Row, Col: Integer;
  ListEditorWasActive: Boolean;
  MainRow, MainCol: Integer;
  H: Integer;
  M: Extended;
  ParaNo: Integer;
  ParaStyles: TParaInfos;
  RVCell: TRVTableCellData;
  RVCellLast: TRVTableCellData;
  RVEditor: TCustomRichViewEdit;
  RVItem: TCustomRVItemInfo;
  RVTable: TRVTableItemInfo;
  W: Extended;
begin
  if not Assigned(FRichViewEdit) then
    Exit;

  FUpdating := True;
  try
    M := 1 / MultiPixels;
    ListEditorWasActive := ListEditor.Active;

    TableEditor.Active := FRichViewEdit.InplaceEditor <> nil;
    if TableEditor.Active then
    begin
      TableEditor.Cells.Clear;
      TableEditor.Rows.Clear;
      if FRichViewEdit.GetCurrentItemEx(TRVTableItemInfo, RVEditor, RVItem) then
      begin
        RVTable := TRVTableItemInfo(RVItem);
        if RVTable.GetEditedCell(Row, Col) <> nil then
        begin
          TableEditor.CellIndex       := Col;
          TableEditor.RowIndex        := Row;
          TableEditor.BorderHSpacing  := M * RVTable.BorderHSpacing;
          TableEditor.BorderVSpacing  := M * RVTable.BorderVSpacing;
          TableEditor.BorderWidth     := M * RVTable.BorderWidth;
          TableEditor.CellBorderWidth := M * RVTable.CellBorderWidth;
          TableEditor.CellHSpacing    := M * RVTable.CellHSpacing;
          TableEditor.CellVSpacing    := M * RVTable.CellVSpacing;
{$IFDEF RVTABLEOLDCELLPADDING}
          TableEditor.CellPadding     := M * RVTable.CellPadding;
{$ELSE}
          TableEditor.CellHPadding    := M * RVTable.CellHPadding;
          TableEditor.CellVPadding    := M * RVTable.CellVPadding;
{$ENDIF}

          if RulerType = rtHorizontal then
          begin
            // Setup columns
            RVCellLast := nil;
            for C := 0 to RVTable.Rows[Row].Count - 1 do
            begin
              RVCell := RVTable.Cells[Row, C];
              if RVCell <> nil then
              begin
                TableEditor.Cells.Add.CellWidth := M * RVCell.Width;
                RVCellLast := RVCell;
              end
              else
              begin
                RVCell := RVTable.Rows.GetMainCell(Row, C, MainRow, MainCol);
                if RVCellLast <> RVCell then
                begin
                  TableEditor.Cells.Add.CellWidth := M * RVCell.Width;
                  RVCellLast := RVCell;
                end
                else
                  TableEditor.Cells.Add.CellWidth := -1;
              end;
            end;

            // Setup Drag boundaries
            TableEditor.UseDragBoundaries := True;
            for C := 0 to RVTable.Rows[0].Count - 1 do
              if TableEditor.Cells[C].DragBoundary <= 0 then
                for R := 0 to RVTable.Rows.Count - 1 do
                  if (RVTable.Cells[R, C] <> nil) and (RVTable.Cells[R, C].ColSpan = 1) then
                  begin
                    TableEditor.Cells[C].DragBoundary := M * RVTable.Cells[R, C].Width;
                    Break;
                  end;
            for C := 0 to RVTable.Rows[0].Count - 1 do
              if TableEditor.Cells[C].DragBoundary = -1 then
                for R := 0 to RVTable.Rows.Count - 1 do
                  if (RVTable.Cells[R, C] = nil) then
                  begin
                    W := M * RVTable.Rows.GetMainCell(R, C, MainRow, MainCol).Width;
                    with TableEditor do
                    begin
                      for MainCol := MainCol to C - 1 do
                        W := W - Cells[MainCol].DragBoundary - 2 * CellBorderWidth -
                          CellHSpacing;
                      Cells[C].DragBoundary := W;
                    end;
                    Break;
                  end;

            // Setup column indents
            ParaStyles := FRichViewEdit.Style.ParaStyles;
            ParaNo := FRichViewEdit.CurParaStyleNo;
            if GetListIndents(FI, LI) then
            begin
              TableEditor.FirstIndent := FI;
              TableEditor.LeftIndent  := LI;
              if UseRTL then
                TableEditor.RightIndent := M * ParaStyles[ParaNo].LeftIndent
              else
                TableEditor.RightIndent := M * ParaStyles[ParaNo].RightIndent;
            end
            else
            begin
              if UseRTL then
              begin
                TableEditor.FirstIndent :=
                  M * (ParaStyles[ParaNo].RightIndent + ParaStyles[ParaNo].FirstIndent);
                TableEditor.LeftIndent := M * -ParaStyles[ParaNo].FirstIndent;
                TableEditor.RightIndent := M * ParaStyles[ParaNo].LeftIndent;
              end
              else
              begin
                TableEditor.FirstIndent :=
                  M * (ParaStyles[ParaNo].LeftIndent + ParaStyles[ParaNo].FirstIndent);
                TableEditor.LeftIndent := M * -ParaStyles[ParaNo].FirstIndent;
                TableEditor.RightIndent := M * ParaStyles[ParaNo].RightIndent;
              end;
            end;

            // Setup table position
            if RVEditor.GetItemCoords(RVTable.GetMyItemNo, L, T) then
            begin
              RVEditor.RVData.GetOriginEx(X, Y);
              Inc(L, X);
            end
            else
              L := 0;
            if TableEditor.TablePosition = tpFromMargin then
              L := L - FRichViewEdit.LeftMargin;
            TableEditor.TableLeft := M * (L + RVTable.BorderWidth);
            TableEditor.TableWidth := M * (RVTable.Width - 2 * RVTable.BorderWidth);
          end
          else
          begin // Vertical ruler
            // Setup rows
            for R := 0 to RVTable.Rows.Count - 1 do
            begin
              H := MAXINT;
              for C := 0 to RVTable.Rows[R].Count - 1 do
              begin
                RVCell := RVTable.Cells[R, C];
                if RVCell <> nil then
                  H := Min(H, RVCell.Height);
              end;
              if H <> MAXINT then
                TableEditor.Rows.Add.RowHeight := M * H;
            end;

            // Setup table position
            if RVEditor.GetItemCoords(RVTable.GetMyItemNo, L, T) then
            begin
              RVEditor.RVData.GetOriginEx(X, Y);
              Inc(T, Y);
            end
            else
              T := 0;
            if TableEditor.TablePosition = tpFromMargin then
              T := T - FRichViewEdit.TopMargin;
            TableEditor.TableTop := M * (T + RVTable.BorderWidth);
            TableEditor.TableHeight := M * (RVTable.Height - 2 * RVTable.BorderWidth);
          end;
        end
        else
          TableEditor.Active := False;
      end
      else
        TableEditor.Active := False;
    end;
  finally
    FUpdating := False;
  end;

  if ListEditorWasActive and not ListEditor.Active then
    UpdateRulerIndents;
end;
{------------------------------------------------------------------------------}
function TRVRuler.GetListIndents(var FI, LI: Extended): Boolean;
var
  FirstParaItemNo: Integer;
  ListLevel: Integer;
  ListNo: Integer;
  M: Extended;
  RVEditor: TCustomRichViewEdit;
  RVListLevel: TRVListLevel;
  StartFrom: Integer;
  UseStartFrom: Boolean;
begin
  Result := False;
  RVEditor := FRichViewEdit.TopLevelEditor;

  FirstParaItemNo := RVEditor.CurItemNo;
  if FirstParaItemNo < 0 then // document is cleared
    Exit;
  while not RVEditor.IsParaStart(FirstParaItemNo) do
    Dec(FirstParaItemNo);

  if RVEditor.GetItemStyle(FirstParaItemNo) = rvsListMarker then
  begin
    Result := True;
    RVEditor.GetListMarkerInfo(FirstParaItemNo,
      ListNo, ListLevel, StartFrom, UseStartFrom);
    RVListLevel := TRVMarkerItemInfo(RVEditor.GetItem(FirstParaItemNo)).GetLevelInfo(RVEditor.Style);
    if RVListLevel = nil then
    begin
      Result := False;
      Exit;
    end;
    M := 1 / MultiPixels;
    FI := M * (RVListLevel.LeftIndent + RVListLevel.FirstIndent);
    LI := M * -RVListLevel.FirstIndent;
    ListEditor.ListLevel := ListLevel;
  end;
  ListEditor.Active := Result;
end;
{------------------------------------------------------------------------------}
procedure TRVRuler.UpdateListEditor;
var
  FI, LI: Extended;
  ListEditorWasActive: Boolean;
begin
  if not Assigned(FRichViewEdit) or (RulerType <> rtHorizontal) then
    Exit;

  FUpdating := True;
  try
    ListEditorWasActive := ListEditor.Active;
    if GetListIndents(FI, LI) then
    begin
      if TableEditor.Active then
      begin
        TableEditor.FirstIndent := FI;
        TableEditor.LeftIndent  := LI;
      end
      else
      begin
        FirstIndent := FI;
        LeftIndent  := LI;
      end;
    end;
  finally
    FUpdating := False;
  end;

  if ListEditorWasActive and not ListEditor.Active then
    UpdateRulerIndents;
end;

end.

