unit uListadoState;

interface

uses
  Grids, Graphics, Classes, Controls, SysUtils, Types, Windows;

const
  encabezadoBTEditar = 'TC_btEditar';
  encabezadoBTEliminar = 'TC_btEliminar';
  encabezadoBTClonar = 'TC_btClonar';
  encabezadoCheckBox = 'TC_checkBox';
  encabezadoRadioButton = 'TC_radioButton';
  encabezadoColor = 'TC_Color';
  encabezadoDisabled = 'TC_Disabled';
  encabezadoComboBox = 'TC_ComboBox';
  encabezadoBTUp = 'TC_btUp';
  encabezadoBTDown = 'TC_btDown';
  encabezadoTextoEditable = 'encabezadoTextoEditable';

type
  TID_Icono = (btEdit, btClonar, btEliminar, checkBox_0, checkBox_1,
    radioButton_0, radioButton_1, btUp, btDown);
  TDAOfColores = array of TColor;

  TTipoColumna = (TC_Texto, TC_TextoEditable, TC_btEditar, TC_btEliminar,
    TC_btClonar, TC_checkBox, TC_radioButton, TC_Color, TC_Disabled,
    TC_ComboBox, TC_RaiseException, TC_btUp, TC_btDown);

  TDAOfTTipoColumna = array of TTipoColumna;
  TDAOfTDAOfTTipoColumna = array of TDAOfTTipoColumna;

  TProcOnMouseUp = procedure(sg: TStringGrid; tipoCol: TTipoColumna;
    fila, columna: Integer) of object;

  TListadoState = class
  private
    hayTextoEditable, hayQueRedefinirDibujadoYClicks: boolean;

    colListado, filaListado: Integer;
    clickIzquierdoAbajo: boolean;
    oldXMouseMove, oldYMouseMove: Integer;
    oldColMouseMove, oldFilaMouseMove: Integer;
  public
    sg: TStringGrid;
    tiposCols: TDAOfTTipoColumna;
    iconos: TImageList;
    coloresFilas: TDAOfColores;
    procOnMouseUp: TProcOnMouseUp;

    { oldGetEditText: TGetEditEvent;
      oldOnKeyDown: TKeyEvent;
      oldOnClick, oldOnExit: TNotifyEvent; }

    oldDrawCell: TDrawCellEvent;
    oldMouseMove: TMouseMoveEvent;
    oldMouseUp, oldMouseDown: TMouseEvent;

    Constructor Create(sg: TStringGrid; const encabezados: array of String;
      seleccionarPorFilas: boolean; iconos: TImageList;
      procOnMouseUp: TProcOnMouseUp);

    { procedure listadoGetEditText(Sender: TObject; ACol, ARow: Integer;
      var Value: string);
      procedure listadoKeyDown(Sender: TObject; var Key: Word;
      Shift: TShiftState; tiposCols: TDAOfTTipoColumna;
      procValidarCelda: TFuncValidarCelda; procCambioValor: TProcCambioValor);
      procedure listadoValidarCambio(Sender: TObject;
      tiposCols: TDAOfTTipoColumna;
      procValidarCelda: TFuncValidarCelda; procCambioValor: TProcCambioValor); }

    procedure ListadoMouseDown(Sender: TObject; Button: TMouseButton;
      Shift: TShiftState; X, Y: Integer);
    procedure ListadoDrawCell(Sender: TObject; ACol, ARow: Integer;
      Rect: TRect; State: TGridDrawState);
    procedure ListadoMouseMove(Sender: TObject; Shift: TShiftState;
      X, Y: Integer);
    procedure ListadoMouseUp(Sender: TObject; Button: TMouseButton;
      Shift: TShiftState; X, Y: Integer);

    procedure Free;
  end;

implementation

const
  VALOR_FALSE = '0';
  VALOR_TRUE = '1';

function pertenece(const s: String; const arreglo: array of string): boolean;
var
  i: Integer;
begin
  Result := False;
  for i := 0 to High(arreglo) do
    if arreglo[i] = s then
    begin
      Result := True;
      break;
    end;
end;

{ TListadoState }

constructor TListadoState.Create(sg: TStringGrid;
  const encabezados: array of String; seleccionarPorFilas: boolean;
  iconos: TImageList; procOnMouseUp: TProcOnMouseUp);
var
  i: Integer;
begin
  self.sg := sg;

  hayTextoEditable := False;
  hayQueRedefinirDibujadoYClicks := False;
  for i := 0 to high(encabezados) do
    if pertenece(encabezados[i], [encabezadoBTEditar, encabezadoBTEliminar,
      encabezadoBTClonar, encabezadoCheckBox, encabezadoRadioButton,
      encabezadoColor, encabezadoDisabled, encabezadoComboBox, encabezadoBTUp,
      encabezadoBTDown]) then
    begin
      hayQueRedefinirDibujadoYClicks := True;
      break;
    end;

  if seleccionarPorFilas then
  begin
    sg.Options := sg.Options + [goRowSelect];
    sg.Options := sg.Options - [goRangeSelect];
  end
  else
  begin
    sg.Options := sg.Options - [goRowSelect];
    sg.Options := sg.Options + [goRangeSelect];
  end;

  sg.ColCount := Length(encabezados);
  SetLength(tiposCols, Length(encabezados));
  for i := 0 to high(encabezados) do
  begin
    if encabezados[i] = encabezadoBTEditar then
      tiposCols[i] := TC_btEditar
    else if encabezados[i] = encabezadoBTEliminar then
      tiposCols[i] := TC_btEliminar
    else if encabezados[i] = encabezadoBTClonar then
      tiposCols[i] := TC_btClonar
    else if pos(encabezadoCheckBox, encabezados[i]) > 0 then
    begin
      tiposCols[i] := TC_checkBox;
      sg.Cells[i, 0] := StringReplace(encabezados[i], encabezadoCheckBox, '',
        [rfReplaceAll]);
    end
    else if encabezados[i] = encabezadoRadioButton then
      tiposCols[i] := TC_radioButton
    else if encabezados[i] = encabezadoColor then
      tiposCols[i] := TC_Color
    else if encabezados[i] = encabezadoDisabled then
      tiposCols[i] := TC_Disabled
    else if encabezados[i] = encabezadoComboBox then
      tiposCols[i] := TC_ComboBox
    else if encabezados[i] = encabezadoBTUp then
      tiposCols[i] := TC_btUp
    else if encabezados[i] = encabezadoBTDown then
      tiposCols[i] := TC_btDown
    else if pos(encabezadoTextoEditable, encabezados[i]) > 0 then
    begin
      hayTextoEditable := True;
      tiposCols[i] := TC_TextoEditable;
      sg.Cells[i, 0] := StringReplace(encabezados[i], encabezadoTextoEditable,
        '', [rfReplaceAll]);
      if goRowSelect in sg.Options then
        raise Exception.Create(
          'TListadoState.Create: Creó un llistado con selección por filas y al menos una columna de tipo TextoEditable');
    end
    else
    begin
      tiposCols[i] := TC_Texto;
      sg.Cells[i, 0] := encabezados[i];
    end;
  end;
  { if not seleccionarPorFilas or (sg.RowCount = 1) then
    sgLimpiarSeleccion(sg); }

  { oldGetEditText := sg.OnGetEditText;
    oldOnKeyDown := sg.OnKeyDown;
    oldOnClick := sg.OnClick;
    oldOnExit := sg.OnExit; }

  self.iconos := iconos;
  self.coloresFilas := NIL;
  self.procOnMouseUp := procOnMouseUp;
  oldDrawCell := sg.OnDrawCell;
  oldMouseMove := sg.OnMouseMove;
  oldMouseUp := sg.OnMouseUp;
  oldMouseDown := sg.OnMouseDown;

  if hayQueRedefinirDibujadoYClicks then
  begin
    sg.OnMouseMove := self.ListadoMouseMove;
    sg.OnDrawCell := self.ListadoDrawCell;
    sg.OnMouseDown := self.ListadoMouseDown;
    sg.OnMouseUp := self.ListadoMouseUp;
  end;
end;

procedure TListadoState.Free;
begin
  { sg.OnGetEditText := oldGetEditText;
    sg.OnKeyDown := oldOnKeyDown;
    sg.OnClick := oldOnClick;
    sg.OnExit := oldOnExit; }
  sg.OnDrawCell := oldDrawCell;
  sg.OnMouseMove := oldMouseMove;
  sg.OnMouseUp := oldMouseUp;
  sg.OnMouseDown := oldMouseDown;
  SetLength(tiposCols, 0);

  inherited Free;
end;

procedure TListadoState.ListadoMouseDown(Sender: TObject; Button: TMouseButton;
  Shift: TShiftState; X, Y: Integer);
begin
  case Button of
    mbLeft:
      begin
        TStringGrid(Sender).MouseToCell(X, Y, colListado, filaListado);
        clickIzquierdoAbajo := True;
      end;
  end;

  if Assigned(oldMouseDown) then
    oldMouseDown(Sender, Button, Shift, X, Y);
end;

procedure TListadoState.ListadoMouseMove(Sender: TObject; Shift: TShiftState;
  X, Y: Integer);
var
  colMouse, filaMouse: Integer;
  SenderAsGrid: TStringGrid;
begin
  if (oldXMouseMove <> X) and (oldYMouseMove <> Y) then
  begin
    oldXMouseMove := X;
    oldYMouseMove := Y;

    SenderAsGrid := TStringGrid(Sender);
    SenderAsGrid.ShowHint := False;
    SenderAsGrid.MouseToCell(X, Y, colMouse, filaMouse);

    if (filaMouse >= SenderAsGrid.FixedRows) and
      (colMouse >= SenderAsGrid.FixedCols) then
    begin
      if (colMouse <> oldColMouseMove) or (filaMouse <> oldFilaMouseMove) then
      begin
        SenderAsGrid.ShowHint := False;
      end
      else
      begin
        SenderAsGrid.ShowHint := True;
        case tiposCols[colMouse] of
          TC_checkBox:
            begin
              if SenderAsGrid.Cells[colMouse, filaMouse] = VALOR_TRUE then
                SenderAsGrid.Hint := 'Desmarcar'
              else
                SenderAsGrid.Hint := 'Marcar'
            end;
          TC_btEditar:
            SenderAsGrid.Hint := 'Editar';
          TC_btEliminar:
            SenderAsGrid.Hint := 'Eliminar';
          TC_btClonar:
            SenderAsGrid.Hint := 'Clonar';
          TC_btUp:
            SenderAsGrid.Hint := 'Subir';
          TC_btDown:
            SenderAsGrid.Hint := 'Bajar';
          TC_Texto:
            begin
              if (SenderAsGrid.Canvas.TextWidth(SenderAsGrid.Cells[colMouse,
                  filaMouse]) > SenderAsGrid.ColWidths[colMouse]) then
                SenderAsGrid.Hint := SenderAsGrid.Cells[colMouse, filaMouse]
              else
                SenderAsGrid.ShowHint := False;
            end;
          TC_TextoEditable:
            begin
              if (SenderAsGrid.Canvas.TextWidth(SenderAsGrid.Cells[colMouse,
                  filaMouse]) > SenderAsGrid.ColWidths[colMouse]) then
                SenderAsGrid.Hint := SenderAsGrid.Cells[colMouse,
                  filaMouse] + ' (editable)'
              else
                SenderAsGrid.Hint := SenderAsGrid.Cells[colMouse,
                  0] + ' (editable)';
            end;
        else
          SenderAsGrid.ShowHint := False;
        end;
      end;
    end;

    oldColMouseMove := colMouse;
    oldFilaMouseMove := filaMouse;
  end;

  if Assigned(oldMouseMove) then
    oldMouseMove(Sender, Shift, X, Y);
end;

procedure TListadoState.ListadoDrawCell(Sender: TObject; ACol, ARow: Integer;
  Rect: TRect; State: TGridDrawState);
var
  SenderAsGrid: TStringGrid;
  tipoCol: TTipoColumna;

  procedure DibujarColor(colores: TDAOfColores);
  var
    SenderAsGrid: TStringGrid;
  begin
    SenderAsGrid := TStringGrid(Sender);
    if (ARow < SenderAsGrid.FixedRows) or (ACol < SenderAsGrid.FixedCols) or
      ((SenderAsGrid.FixedRows = 0) and (ARow = 0) and
        ((SenderAsGrid.RowCount <> 1) or (SenderAsGrid.ColCount <> 1)))
      then
      exit;

    if colores[ARow - SenderAsGrid.FixedRows] <> clDefault then
    begin
      if SenderAsGrid.Cells[ACol, ARow] <> '' then
        SenderAsGrid.Cells[ACol, ARow] := '';
      SenderAsGrid.Canvas.Brush.Color := colores[ARow - SenderAsGrid.FixedRows];
      SenderAsGrid.Canvas.FillRect(Rect);
    end
    else
    begin
      if SenderAsGrid.Cells[ACol, ARow] <> 'Auto' then
        SenderAsGrid.Cells[ACol, ARow] := 'Auto';
      SenderAsGrid.Canvas.Brush.Color := clWhite;
      SenderAsGrid.Canvas.FillRect(Rect);
      SenderAsGrid.Canvas.Font.Color := clBlack;
      // SenderAsGrid.Canvas.Pen.Color:= clBlack;
      SenderAsGrid.Canvas.TextOut(Rect.Left + 2, Rect.Top + 2,
        SenderAsGrid.Cells[ACol, ARow]);
    end;

    // SenderAsGrid.Canvas.Brush.Color := colores[ARow - 1];
    // SenderAsGrid.Canvas.FillRect(Rect);
  end;

  procedure DibujarBoton(kicono: TID_Icono; apretable: boolean;
    iconos: TImageList);
  var
    SenderAsGrid: TDrawGrid;
  begin
    SenderAsGrid := TDrawGrid(Sender);
    if (ARow < SenderAsGrid.FixedRows) or (SenderAsGrid.FixedRows = 0) or
      (ACol < SenderAsGrid.FixedCols) then
      exit;
    with SenderAsGrid do
    begin
      Canvas.Brush.Color := clWhite;
      Canvas.FillRect(Rect);
      iconos.Draw(Canvas, Rect.Left, Rect.Top, ord(kicono));
      if gdFocused in State then
        Canvas.DrawFocusRect(Rect);

      if apretable and clickIzquierdoAbajo and
        ((ARow = filaListado) and (ACol = col)) then
      begin
        Canvas.Pen.Color := clBlack;
        Canvas.Pen.Width := 2;
        Canvas.Polyline([Point(Rect.Left + 2, Rect.bottom - 2),
          Point(Rect.Left + 2, Rect.Top + 2), Point(Rect.Right - 2,
            Rect.Top + 2)]);
      end
    end
  end;

  procedure dibujarDisabled(Sender: TObject; col, Row: Integer; Rect: TRect;
    State: TGridDrawState);
  begin
    with TStringGrid(Sender).Canvas do
    begin
      Brush.Color := TStringGrid(Sender).FixedColor;
      FillRect(Rect);
      Pen.Style := psSolid;
      Pen.Width := 1;
      Pen.Color := clBlack;
      Polyline([Point(Rect.Left - 1, Rect.bottom + 1),
        Point(Rect.TopLeft.X - 1, Rect.TopLeft.Y - 1), Point(Rect.Right + 1,
          Rect.Top - 1)]);
      Pen.Color := clBtnHighlight;
      Polyline([Point(Rect.Left, Rect.bottom - 1), Rect.TopLeft,
        Point(Rect.Right, Rect.Top)]);
      Pen.Color := clBtnShadow;
      Polyline([Point(Rect.Left + 1, Rect.bottom - 1), Point(Rect.Right - 1,
          Rect.bottom - 1), Point(Rect.Right - 1, Rect.Top)]);
    end
  end;

  procedure dibujarTexto(Sender: TObject; col, Row: Integer; Rect: TRect;
    State: TGridDrawState);
  var
    Texto, linea: string;
    Indice: Integer;
    Posicion: Integer;
    grid: TStringGrid;
    pintarFondoDeColor: boolean;
  begin
    grid := TStringGrid(Sender);
    pintarFondoDeColor := (Row >= grid.FixedRows) and (col >= grid.FixedCols)
      and (grid.Row <> Row) or (not(goRowSelect in grid.Options));
    if pintarFondoDeColor then
    begin
      if Row mod 2 = grid.FixedRows mod 2 then
        grid.Canvas.Brush.Color := clWhite
      else
        grid.Canvas.Brush.Color := RGB(244, 244, 244);
      grid.Canvas.FillRect(Rect);
    end;

    if pos(#13, grid.Cells[col, Row]) <> 0 then
    begin
      Texto := grid.Cells[col, Row] + #13;
      grid.Canvas.FillRect(Rect);
      Indice := 0;
      repeat
        Posicion := pos(#13, Texto);
        linea := Copy(Texto, 1, Posicion - 1);
        with grid.Canvas do
          TextOut(Rect.Left + 2, Rect.Top + (Indice * TextHeight(linea)) + 2,
            linea);
        Inc(Indice);
        Delete(Texto, 1, Posicion);
      until Posicion = 0;
      { grid.RowHeights[Row] := (Indice - 1) * grid.Canvas.TextHeight
        (grid.Cells[col, Row]) + utilidades.plusHeight + 1; }
      grid.RowHeights[Row] := (Indice - 1) * grid.Canvas.TextHeight
        (grid.Cells[col, Row]) + 5;
    end
    else if pintarFondoDeColor then
      grid.Canvas.TextOut(Rect.Left + 2, Rect.Top + 2, grid.Cells[col, Row]);
  end;

begin
  SenderAsGrid := TStringGrid(Sender);
  // Celdas fijas
  // if ( ACol >=  0 ) and ( ACol < 1 )
  if (ARow < SenderAsGrid.FixedRows) or
    ((ARow = 0) and ((SenderAsGrid.RowCount <> 1) or
        (SenderAsGrid.ColCount <> 1))) then
  begin
    SenderAsGrid.Canvas.Pen.Color := clBlack;
    SenderAsGrid.Canvas.Brush.Color := SenderAsGrid.FixedColor;
    SenderAsGrid.Canvas.FillRect(Rect);
    SenderAsGrid.Canvas.Pen.Style := psSolid;
    SenderAsGrid.Canvas.Pen.Width := 1;
    SenderAsGrid.Canvas.Pen.Color := clBlack;
    SenderAsGrid.Canvas.Polyline([Point(Rect.Left - 1, Rect.bottom + 1),
      Point(Rect.TopLeft.X - 1, Rect.TopLeft.Y - 1), Point(Rect.Right + 1,
        Rect.Top - 1)]);
    SenderAsGrid.Canvas.Pen.Color := clBtnHighlight;
    SenderAsGrid.Canvas.Polyline([Point(Rect.Left, Rect.bottom - 1),
      Rect.TopLeft, Point(Rect.Right, Rect.Top)]);
    SenderAsGrid.Canvas.Pen.Color := clBtnShadow;
    SenderAsGrid.Canvas.Polyline([Point(Rect.Left + 1, Rect.bottom - 1),
      Point(Rect.Right - 1, Rect.bottom - 1), Point(Rect.Right - 1,
        Rect.Top)]);
    SenderAsGrid.Canvas.TextOut(Rect.Left + 2, Rect.Top + 2,
      SenderAsGrid.Cells[ACol, ARow]);
  end
  else
  begin
    tipoCol := tiposCols[ACol];

    case tipoCol of
      TC_Texto, TC_TextoEditable:
        dibujarTexto(Sender, ACol, ARow, Rect, State);
      TC_btEditar:
        DibujarBoton(btEdit, True, iconos);
      TC_btEliminar:
        DibujarBoton(btEliminar, True, iconos);
      TC_btClonar:
        DibujarBoton(btClonar, True, iconos);
      TC_Disabled:
        dibujarDisabled(Sender, ACol, ARow, Rect, State);
      TC_checkBox:
        if SenderAsGrid.Cells[ACol, ARow] = VALOR_FALSE then
          DibujarBoton(checkBox_0, False, iconos)
        else
          DibujarBoton(checkBox_1, False, iconos);
      // TC_ComboBox									: nada
      TC_radioButton:
        if SenderAsGrid.Cells[ACol, ARow] = VALOR_FALSE then
          DibujarBoton(radioButton_0, False, iconos)
        else
          DibujarBoton(radioButton_1, False, iconos);
      TC_Color:
        DibujarColor(coloresFilas);
      TC_btUp:
        DibujarBoton(btUp, True, iconos);
      TC_btDown:
        DibujarBoton(btDown, True, iconos);
    end // del case
  end;

  if Assigned(oldDrawCell) then
    oldDrawCell(Sender, ACol, ARow, Rect, State);
end;

procedure TListadoState.ListadoMouseUp(Sender: TObject; Button: TMouseButton;
  Shift: TShiftState; X, Y: Integer);
var
  filaMouse, colMouse, iFila: Integer;
  tipoCol: TTipoColumna;
  SenderAsGrid: TStringGrid;
begin
  SenderAsGrid := Sender as TStringGrid;

  case Button of
    mbLeft:
      SenderAsGrid.MouseToCell(X, Y, colMouse, filaMouse);
  else
    begin
      colMouse := -1;
      filaMouse := -1;
    end;
  end;

  if clickIzquierdoAbajo then
  begin
    // Si el mouse no estaba abajo, o si se levanto en otra celda que no fuera
    // en la que se bajó ignoro el mensaje
    clickIzquierdoAbajo := False;
    if (filaListado = filaMouse) and (colListado = colMouse) then
    begin
      if (filaListado < SenderAsGrid.FixedRows) or
        ((filaListado = 0) and ((SenderAsGrid.RowCount <> 1) or
            (SenderAsGrid.ColCount <> 1))) then
        // Si la fila es una de las fixed el tipo de columna es disabled
        tipoCol := TC_Disabled
      else if (filaListado >= SenderAsGrid.FixedRows) then
      begin
        SenderAsGrid.ShowHint := True;
        SenderAsGrid.Options := SenderAsGrid.Options - [goEditing];
        tipoCol := tiposCols[colListado];
        case tipoCol of
          TC_Texto, TC_btEditar, TC_btEliminar, TC_btClonar, TC_Color,
            TC_ComboBox, TC_btUp, TC_btDown:
            SenderAsGrid.ShowHint := False;
          TC_TextoEditable:
            begin
              SenderAsGrid.ShowHint := False;
              SenderAsGrid.Options := SenderAsGrid.Options + [goEditing];
            end;
          TC_checkBox:
            begin
              SenderAsGrid.ShowHint := False;
              if SenderAsGrid.Cells[colListado, filaListado] = VALOR_FALSE then
                SenderAsGrid.Cells[colListado, filaListado] := VALOR_TRUE
              else
                SenderAsGrid.Cells[colListado, filaListado] := VALOR_FALSE;
              tipoCol := TC_checkBox;
            end;
          TC_radioButton:
            begin
              SenderAsGrid.ShowHint := False;
              for iFila := 1 to SenderAsGrid.RowCount - 1 do
                SenderAsGrid.Cells[colListado, iFila] := VALOR_FALSE;
              SenderAsGrid.Cells[colListado, filaListado] := VALOR_TRUE;
              tipoCol := TC_radioButton;
            end;
          TC_Disabled:
            ;
        else
          raise Exception.Create(
            'ListadoMouseUp, res llegó con valor no asignado');
        end // del case
      end // del if
      else
        tipoCol := TC_Disabled;
      SenderAsGrid.Invalidate;

      if Assigned(procOnMouseUp) then
        procOnMouseUp(sg, tipoCol, filaListado, colListado);
    end;
  end;

  if Assigned(oldMouseUp) then
    oldMouseUp(Sender, Button, Shift, X, Y);
end;

end.
