{
 *****************************************************************************
  This file is part of the Printer4Lazarus package

  See the file COPYING.modifiedLGPL.txt, included in this distribution,
  for details about the license.
 *****************************************************************************
}
unit framePageSetup;

{$mode objfpc}{$H+}

interface

uses
  Classes, SysUtils,
  // LCL
  LCLIntf, LCLProc, LResources, Controls, Graphics, Forms, ExtCtrls, StdCtrls,
  Spin, Printers,
  // Printers
  OsPrinters, CupsLCL;

type
  { TframePageSetup }

  TframePageSetup = class(TFrame)
    cbPaper: TComboBox;
    cbSource: TComboBox;
    panMargins: TPanel;
    boxShadow: TShape;
    txtLeft: TFloatSpinEdit;
    txtTop: TFloatSpinEdit;
    txtRight: TFloatSpinEdit;
    txtBottom: TFloatSpinEdit;
    gpPaper: TGroupBox;
    gpOrientation: TGroupBox;
    gpMargins: TGroupBox;
    lblSource: TLabel;
    lblPaper: TLabel;
    lblLeft: TLabel;
    lblRight: TLabel;
    lblTop: TLabel;
    lblBottom: TLabel;
    pbPreview: TPaintBox;
    panSetup: TPanel;
    panPreview: TPanel;
    radLandscape: TRadioButton;
    radPortrait: TRadioButton;
    procedure cbPaperChange(Sender: TObject);
    procedure panPreviewResize(Sender: TObject);
    procedure pbPreviewMouseDown(Sender: TObject; Button: TMouseButton;
      {%H-}Shift: TShiftState; {%H-}X, {%H-}Y: Integer);
    procedure pbPreviewMouseWheelDown(Sender: TObject; {%H-}Shift: TShiftState;
      {%H-}MousePos: TPoint; var Handled: Boolean);
    procedure pbPreviewMouseWheelUp(Sender: TObject; {%H-}Shift: TShiftState;
      {%H-}MousePos: TPoint; var Handled: Boolean);
    procedure pbPreviewPaint(Sender: TObject);
    procedure radPortraitClick(Sender: TObject);
    procedure txtLeftChange(Sender: TObject);
  private
    FHeightTallest: Integer;
    FFactorX, FFactorY, FZoom: Double;
    function NToInches: double;
    procedure RotateMargins(AOrder: boolean);
  public
    UnitInches: boolean;
    EnablePreview: boolean;
    EnableMargins: boolean;
    EnablePapers: boolean;
    EnableOrientation: boolean;
    CurPageWidth: double;
    CurPageHeight: double;
    procedure Initialize(AEnablePreview, AEnableMargins, AEnablePapers,
      AEnableOrientation: boolean);
    procedure UpdatePageSize;
    procedure UpdateMaxValues;
    procedure SetDefaultMinMargins;
  end;

implementation

{$R framepagesetup.lfm}

{ TframePageSetup }

function TframePageSetup.NToInches: double;
begin
  if UnitInches then
    Result:= 1
  else
    Result:= 1/25.4;
end;

procedure TframePageSetup.RotateMargins(AOrder: boolean);
var
  m_l, m_t, m_r, m_b: double;
begin
  m_l:= txtLeft.Value;
  m_t:= txtTop.Value;
  m_r:= txtRight.Value;
  m_b:= txtBottom.Value;

  if AOrder then
  begin
    txtLeft.Value:= m_b;
    txtTop.Value:= m_l;
    txtRight.Value:= m_t;
    txtBottom.Value:= m_r;
  end
  else
  begin
    txtLeft.Value:= m_t;
    txtTop.Value:= m_r;
    txtRight.Value:= m_b;
    txtBottom.Value:= m_l;
  end;

  // same way must change MinValues
  m_l:= txtLeft.MinValue;
  m_t:= txtTop.MinValue;
  m_r:= txtRight.MinValue;
  m_b:= txtBottom.MinValue;

  if AOrder then
  begin
    txtLeft.MinValue:= m_b;
    txtTop.MinValue:= m_l;
    txtRight.MinValue:= m_t;
    txtBottom.MinValue:= m_r;
  end
  else
  begin
    txtLeft.MinValue:= m_t;
    txtTop.MinValue:= m_r;
    txtRight.MinValue:= m_b;
    txtBottom.MinValue:= m_l;
  end;
end;

procedure TframePageSetup.pbPreviewPaint(Sender: TObject);
var
  R: TRect;
  NLeft, NTop, NRight, NBottom: integer;
begin
  if not EnablePreview then
    exit;

  with pbPreview do
  begin
    Canvas.Pen.Style := psSolid;
    Canvas.Pen.Color := clBlack;
    Canvas.Brush.Color := clWhite;
    Canvas.Rectangle(0, 0, Width, Height);

    //if EnableMargins then // EnableMargins is for SpinEdits only
    begin
      NLeft := Round(txtLeft.Value * NToInches * Printer.XDPI * FFactorX * FZoom);
      NTop := Round(txtTop.Value * NToInches * Printer.YDPI * FFactorY * FZoom);
      NRight := Round(txtRight.Value * NToInches * Printer.XDPI * FFactorX * FZoom);
      NBottom := Round(txtBottom.Value * NToInches * Printer.YDPI * FFactorY * FZoom);

      R.Left := NLeft;
      R.Top := NTop;
      R.Right := Width-1-NRight;
      R.Bottom := Height-1-NBottom;

      Canvas.Pen.Color := clMedGray;
      //Canvas.Pen.Style := psDash; // AT: setting line style don't work, line is solid
      Canvas.Rectangle(R);
    end;
  end;
end;

procedure TframePageSetup.radPortraitClick(Sender: TObject);
begin
  RotateMargins(radPortrait.Checked);
  if radPortrait.Checked then
    Printer.Orientation := poPortrait
  else
    Printer.Orientation := poLandsCape;
  UpdatePageSize;
end;

procedure TframePageSetup.txtLeftChange(Sender: TObject);
begin
  pbPreview.Update;
end;

procedure TframePageSetup.cbPaperChange(Sender: TObject);
begin
  if Printer.PaperSize.DefaultPapers then
  begin
    if cbPaper.ItemIndex>=0 then
      Printer.PaperSize.PaperName := cbPaper.Items[cbPaper.ItemIndex];
  end else
    Printer.PaperSize.PaperName := GetCupsComboKeyValue(cbPaper);
  UpdatePageSize;
end;

procedure TframePageSetup.panPreviewResize(Sender: TObject);
var
  TallH: Integer;
begin
  if not EnablePreview then
    exit;

  TallH := Round(FheightTallest * FFactorY);

  with PanPreview do
  if (Height<>C_BOTHSPACES) and (TallH>(Height-C_BOTHSPACES)) then
    FZoom := (Height-C_BOTHSPACES)/TallH
  else
    FZoom := 1.0;
end;

procedure TframePageSetup.pbPreviewMouseDown(Sender: TObject;
  Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
begin
  if Button=mbMiddle then
  begin
    FZoom := 1;
    UpdatePageSize;
  end;
end;

procedure TframePageSetup.pbPreviewMouseWheelDown(Sender: TObject;
  Shift: TShiftState; MousePos: TPoint; var Handled: Boolean);
begin
  FZoom := FZoom - 0.2;
  if FZoom<0.5 then
    FZoom := 0.5;
  UpdatePageSize;
  Handled := true;
end;

procedure TframePageSetup.pbPreviewMouseWheelUp(Sender: TObject;
  Shift: TShiftState; MousePos: TPoint; var Handled: Boolean);
begin
  FZoom := FZoom + 0.2;
  UpdatePageSize;
  Handled := true;
end;

procedure TframePageSetup.UpdatePageSize;
begin
  if not EnablePreview then
    exit;

  with Printer.PaperSize.PaperRect.PhysicalRect do
  begin
    PbPreview.Width := Round(FFactorX * (Right - Left) * FZoom) + 2;
    PbPreview.Height := Round(FFactorY * (Bottom - Top) * FZoom) + 2;

    boxShadow.Width := pbPreview.Width;
    boxShadow.Height := pbPreview.Height;
  end;

  with Printer.PaperSize.PaperRect do
  begin
    CurPageWidth := (PhysicalRect.Right-PhysicalRect.Left)/Printer.XDPI/NToInches;
    CurPageHeight := (PhysicalRect.Bottom-PhysicalRect.Top)/Printer.YDPI/NToInches;
  end;

  UpdateMaxValues;
end;

procedure TframePageSetup.UpdateMaxValues;

  procedure DoSetMax(Ctl: TFloatSpinEdit; Value: double);
  begin
    // because of TCustomFloatSpinEdit.GetLimitedValue
    // we cannot set MinValue=MaxValue, all validation will break
    if Ctl.MinValue > Value-0.2 then
      Ctl.MinValue := Value-0.2;
    Ctl.MaxValue := Value;
  end;

const
  cMul = 0.45; // max margin is almost 1/2 of page size
begin
  DoSetMax(txtLeft, CurPageWidth * cMul);
  DoSetMax(txtRight, CurPageWidth * cMul);
  DoSetMax(txtTop, CurPageHeight * cMul);
  DoSetMax(txtBottom, CurPageHeight * cMul);
end;

procedure TframePageSetup.Initialize(AEnablePreview, AEnableMargins, AEnablePapers,
  AEnableOrientation: boolean);
var
  i,j:Integer;
  R: TPaperRect;
begin
  EnablePreview:= AEnablePreview;
  EnableMargins:= AEnableMargins;
  EnablePapers:= AEnablePapers;
  EnableOrientation:= AEnableOrientation;

  cbPaper.Items.Clear;
  cbSource.Items.Clear;
  cbPaper.ItemIndex := -1;
  cbSource.ItemIndex := -1;

  if EnablePapers then
  begin
    SetupCupsCombo(cbSource, nil, 'InputSlot');
    SetupCupsCombo(cbPaper, nil, 'PageSize');
    if (cbPaper.Items.Count=0) then
    begin
      // no cups printer papers, use default ones
      cbPaper.Items := Printer.PaperSize.SupportedPapers;
      cbPaper.ItemIndex:= cbPaper.Items.IndexOf(Printer.PaperSize.PaperName);
    end;
  end;

  cbPaper.Enabled := cbPaper.Items.Count>0;
  cbSource.Enabled := cbSource.Items.Count>0;

  //TODO: support reverse variants too?
  gpOrientation.Enabled := EnableOrientation;
  case Printer.Orientation of
    poPortrait,poReversePortrait:
      radPortrait.Checked := true;
    poLandscape,poReverseLandscape:
      radLandscape.Checked := true;
  end;

  gpMargins.Enabled := EnableMargins;
  panPreview.Visible:= EnablePreview;

  if EnablePreview then
  begin
    // assume 100 pix = 8.5 inch (IOW, letter size width = 100 pixels)
    with ScreenInfo do
    begin
      FFactorX := (100/8.5)/Printer.XDPI;
      FFactorY := (100/8.5)*(PixelsPerInchY/PixelsPerInchX)/Printer.YDPI;
    end;

    // find the tallest paper
    FHeightTallest := 0;
    j := -1;
    if cbPaper.Enabled then
    for i:=0 to cbPaper.Items.Count-1 do
    begin
      if Printer.PaperSize.DefaultPapers then
        R := Printer.PaperSize.PaperRectOf[cbPaper.Items[i]]
      else
        R := Printer.PaperSize.PaperRectOf[GetCupsComboKeyValue(cbPaper, i)];
      with R.PhysicalRect do
      if FHeightTallest<(Bottom-Top) then
      begin
        FHeightTallest := (Bottom-Top);
        j := i;
      end;
    end;

    if j>=0 then
    begin
      {$IFDEF DebugCUPS}
      DebugLn(' Tallest Paper is: %s Height=%d %.2f Inch',
       [cbPaper.Items[j],FHeightTallest,FHeightTallest/Printer.YDPI]);
      {$ENDIF}
    end;

    // zoom factor
    FZoom := 1.0;
    UpdatePageSize;
  end;
end;

procedure TframePageSetup.SetDefaultMinMargins;
begin
  with Printer.PaperSize.PaperRect do
  begin
    txtLeft.MinValue := (WorkRect.Left-PhysicalRect.Left)/Printer.XDPI/NToInches;
    txtTop.MinValue := (WorkRect.Top-PhysicalRect.Top)/Printer.YDPI/NToInches;
    txtRight.MinValue := (PhysicalRect.Right-WorkRect.Right)/Printer.XDPI/NToInches;
    txtBottom.MinValue := (PhysicalRect.Bottom-WorkRect.Bottom)/Printer.YDPI/NToInches;
  end;
end;

end.

