{
    Double Commander
    -------------------------------------------------------------------------
    Label displaying a path, highlighting directories with mouse.

    Copyright (C) 2010-2011  Przemysław Nagay (cobines@gmail.com)

    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 2 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program; if not, write to the Free Software
    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
}

unit uPathLabel;

{$mode objfpc}{$H+}

interface

uses
  Classes, SysUtils, StdCtrls;

type

  { TPathLabel }

  TPathLabel = class(TLabel)
  private
    FAllowHighlight: Boolean;
    FHighlightStartPos: Integer;
    FHighlightText: String;
    FMousePos: Integer;
    {en
       How much space to leave between the text and left border.
    }
    FLeftSpacing: Integer;

    {en
       If a user clicks on a parent directory of the path,
       this stores the full path of that parent directory.
    }
    FSelectedDir: String;

    {en
       If a mouse if over some parent directory of the currently displayed path,
       it is highlighted, so that user can click on it.
    }
    procedure Highlight;

  protected

    procedure TextChanged; override;

    procedure MouseEnter; override;
    procedure MouseMove(Shift: TShiftState; X,Y: Integer); override;
    procedure MouseLeave; override;

  public

    constructor Create(AOwner: TComponent; bAllowHighlight: Boolean = False); reintroduce;

    procedure Paint; override;

    {en
       Changes drawing colors depending active/inactive state.
    }
    procedure SetActive(Active: Boolean);

    property AllowHighlight: Boolean read FAllowHighlight write FAllowHighlight;
    property LeftSpacing: Integer read FLeftSpacing write FLeftSpacing;
    property SelectedDir: String read FSelectedDir;
  end;

implementation

uses
  Controls, Graphics, math;

{ TPathLabel }

constructor TPathLabel.Create(AOwner: TComponent; bAllowHighlight: Boolean);
begin
  FLeftSpacing := 3; // set before painting

  inherited Create(AOwner);

  FAllowHighlight := bAllowHighlight;

  FSelectedDir := '';

  FHighlightStartPos := -1;
  FHighlightText := '';

  SetActive(False);
end;

procedure TPathLabel.Paint;
var
  TextTop: Integer;
begin
  Canvas.Brush.Color := Color;
  Canvas.Font.Color  := Font.Color;

  // Center vertically.
  TextTop := (Height - Canvas.TextHeight(Text)) div 2;

  Canvas.FillRect(0, 0, Width, Height); // background
  Canvas.TextOut(LeftSpacing, TextTop, Text); // path

  // Highlight part of the path if mouse is over it.
  if FHighlightStartPos <> -1 then
  begin
    Canvas.Brush.Color := Font.Color;  // reverse colors
    Canvas.Font.Color  := Color;
    Canvas.TextOut(FHighlightStartPos, TextTop, FHighlightText);
  end;
end;

procedure TPathLabel.SetActive(Active: Boolean);
begin
  case Active of
    False:
      begin
        Color      := clBtnFace;
        Font.Color := clBtnText;
      end;
    True:
      begin
        Color      := clHighlight;
        Font.Color := clHighlightText;
      end;
  end;
end;

procedure TPathLabel.Highlight;
var
  PartText: String;
  StartPos, CurPos: Integer;
  PartWidth: Integer;
  CurrentHighlightPos, NewHighlightPos: Integer;
  TextLen: Integer;
  PathDelimWidth: Integer;
begin
  CurrentHighlightPos := LeftSpacing; // start at the beginning of the path
  NewHighlightPos := -1;

  Canvas.Font := Self.Font;
  PathDelimWidth := Canvas.TextWidth(PathDelim);
  TextLen := Length(Text);

  // Start from the first character, but omit any path delimiters at the beginning.
  StartPos := 1;
  while (StartPos <= TextLen) and (Text[StartPos] = PathDelim) do
    Inc(StartPos);

  // Move the canvas position after the skipped text (if any).
  CurrentHighlightPos := CurrentHighlightPos + (StartPos - 1) * PathDelimWidth;

  for CurPos := StartPos + 1 to TextLen - 1 do
  begin
    if Text[CurPos] = PathDelim then
    begin
      PartText := Copy(Text, StartPos, CurPos - StartPos);
      PartWidth := Canvas.TextWidth(PartText);

      // If mouse is over this part of the path - highlight it.
      if InRange(FMousePos, CurrentHighlightPos, CurrentHighlightPos + PartWidth) then
      begin
        NewHighlightPos := CurrentHighlightPos;
        Break;
      end;

      CurrentHighlightPos := CurrentHighlightPos + PartWidth + PathDelimWidth;
      StartPos := CurPos + 1;
    end;
  end;

  // Repaint if highlighted part has changed.
  if NewHighlightPos <> FHighlightStartPos then
  begin
    // Omit minimized part of the displayed path.
    if PartText = '..' then
      FHighlightStartPos := -1
    else
      FHighlightStartPos := NewHighlightPos;

    if FHighlightStartPos <> -1 then
    begin
      Cursor := crHandPoint;

      FHighlightText := PartText;
      // If clicked, this will be the new directory.
      FSelectedDir := Copy(Text, 1, CurPos - 1);
    end
    else
    begin
      Cursor := crDefault;

      FSelectedDir := '';
      FHighlightText := '';
    end;

    Self.Invalidate;
  end;
end;

procedure TPathLabel.TextChanged;
begin
  inherited TextChanged;
  if FAllowHighlight and MouseEntered then Highlight;
end;

procedure TPathLabel.MouseEnter;
begin
  inherited MouseEnter;
  if FAllowHighlight then
  begin
    Cursor := crDefault;
    Invalidate;
  end;
end;

procedure TPathLabel.MouseMove(Shift: TShiftState; X,Y: Integer);
begin
  inherited MouseMove(Shift, X, Y);
  FMousePos := X;
  if FAllowHighlight then Highlight;
end;

procedure TPathLabel.MouseLeave;
begin
  inherited MouseLeave;
  if FAllowHighlight then
  begin
    FSelectedDir := '';
    FHighlightStartPos := -1;
    FHighlightText := '';
    Cursor := crDefault;
    Invalidate;
  end;
end;

end.

